package charts.graphics;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.math3.stat.StatUtils;
import org.jfree.data.Range;
import org.jfree.data.category.CategoryDataset;
import com.google.common.collect.Lists;
import com.google.common.math.DoubleMath;
public class PartitionAxisConfigurator {
private final boolean stackValues;
public PartitionAxisConfigurator() {
this(false);
}
public PartitionAxisConfigurator(boolean stackValues) {
this.stackValues = stackValues;
}
public void configurePartitions(CategoryDataset dataset, PartitionedNumberAxis axis) {
List<List<Double>> split = split(dataset);
List<Range> ranges = Lists.newArrayList();
for(List<Double> list : split) {
ranges.add(getRange(list));
}
List<Range> optimised = optimise(ranges);
List<Double> sizes = getSizes(dataset, optimised);
for(int i=0;i<optimised.size();i++) {
Range range = optimised.get(i);
Double size = sizes.get(i);
axis.addPartition(new PartitionedNumberAxis.Partition(range, size));
}
}
private List<Double> getSizes(CategoryDataset dataset, List<Range> ranges) {
double[][] distribution = { {1}, {0.75, 0.25},{0.75, 0.125, 0.125},
{0.5, 0.25, 0.125, 0.125}, {0.5, 0.125, 0.125, 0.125, 0.125}};
List<Double> values = getValues(dataset);
int[] size = new int[ranges.size()];
for(double d : values) {
for(int i = 0;i<ranges.size();i++) {
if(ranges.get(i).contains(d)) {
size[i]++;
break;
}
}
}
List<Pair<Integer, Integer>> list = Lists.newArrayList();
for(int i = 0;i<ranges.size();i++) {
list.add(Pair.of(i, size[i]));
}
Collections.sort(list, new Comparator<Pair<Integer, Integer>> () {
@Override
public int compare(Pair<Integer, Integer> o1,
Pair<Integer, Integer> o2) {
return o2.getRight().compareTo(o1.getRight());
}});
Double[] result = new Double[list.size()];
for(int i=0;i<list.size();i++) {
Pair<Integer, Integer> p = list.get(i);
result[p.getLeft()] = distribution[list.size()-1][i];
}
return Arrays.asList(result);
}
private double[] toArray(List<Double> list) {
double[] result = new double[list.size()];
for(int i=0;i<list.size();i++) {
result[i] = list.get(i);
}
return result;
}
private List<List<Double>> split(CategoryDataset dataset) {
double[] population = toArray(getValues(dataset));
double mean = StatUtils.mean(population);
double variance = StatUtils.populationVariance(population, mean);
double deviation = Math.sqrt(variance);
List<Double> xl = Lists.newArrayList();
List<Double> l = Lists.newArrayList();
List<Double> std = Lists.newArrayList();
List<Double> s = Lists.newArrayList();
List<Double> xs = Lists.newArrayList();
for(double value : population) {
if(Math.abs(mean-value) > 2*deviation) {
if(value>mean) xl.add(value); else xs.add(value);
} else if(Math.abs(mean-value) > deviation) {
if(value>mean) l.add(value); else s.add(value);
} else {
std.add(value);
}
}
List<List<Double>> combined = Lists.newArrayList();
if(!xs.isEmpty()) combined.add(xs);
if(!s.isEmpty()) combined.add(s);
if(!std.isEmpty()) combined.add(std);
if(!l.isEmpty()) combined.add(l);
if(!xl.isEmpty()) combined.add(xl);
return combined;
}
private List<Range> optimise(List<Range> ranges) {
return merge(expand(addZero(ranges)));
}
private List<Range> expand(List<Range> ranges) {
List<Range> result = Lists.newArrayList();
for(Range range : ranges) {
result.add(expand(range));
}
return result;
}
private Range expand(Range range) {
double l = Math.abs(range.getLength());
if(DoubleMath.fuzzyEquals(l, 0.0, 0.00001)) {
return new Range(range.getLowerBound()-1.0, range.getUpperBound()+1.0);
}
double expand = l*0.05;
if(range.getLowerBound() == 0.0) {
return new Range(range.getLowerBound(), range.getUpperBound()+expand);
} else if(range.getUpperBound() == 0.0) {
return new Range(range.getLowerBound()-expand, range.getUpperBound());
} else {
return new Range(range.getLowerBound()-expand/2.0, range.getUpperBound()+expand/2.0);
}
}
private List<Range> merge(List<Range> ranges) {
List<Range> result = Lists.newArrayList();
for(int i =0;i<ranges.size();i++) {
Range range = ranges.get(i);
if((i+1)<ranges.size()) {
Range next = ranges.get(i+1);
if(range.intersects(next) || DoubleMath.fuzzyEquals(
range.getUpperBound(),next.getLowerBound(), 0.0000001)) {
Range merged = Range.combine(range, next);
result.add(merged);
for(int j=i+2;j<ranges.size();j++) {
result.add(ranges.get(j));
}
result = merge(result);
break;
} else {
result.add(range);
}
} else {
result.add(range);
}
}
return result;
}
private boolean containsZero(List<Range> ranges) {
boolean result = false;
for(Range range : ranges) {
if(range.contains(0.0)) {
result = true;
break;
}
}
return result;
}
private List<Range> addZero(List<Range> ranges) {
List<Range> result;
if(containsZero(ranges)) {
result = ranges;
} else {
double closestToZero = Math.abs(ranges.get(0).getLowerBound());
for(Range r : ranges) {
closestToZero = Math.min(closestToZero, Math.abs(r.getLowerBound()));
closestToZero = Math.min(closestToZero, Math.abs(r.getUpperBound()));
}
result = Lists.newArrayList();
for(Range r : ranges) {
if(r.getLowerBound() == closestToZero) {
result.add(new Range(0, r.getUpperBound()));
} else if(r.getUpperBound() == closestToZero) {
result.add(new Range(r.getLowerBound(), 0));
} else {
result.add(r);
}
}
}
return result;
}
private Range getRange(List<Double> values) {
double min = values.get(0).doubleValue();
double max = values.get(0).doubleValue();
for(Double d : values) {
min = Math.min(min, d);
max = Math.max(max, d);
}
return new Range(min,max);
}
private List<Double> getValues(CategoryDataset dataset) {
if(this.stackValues) {
return getStackedValues(dataset);
} else {
return getValuesRegular(dataset);
}
}
private List<Double> getStackedValues(CategoryDataset dataset) {
List<Double> values = Lists.newArrayList();
for(int col=0;col < dataset.getColumnCount();col++) {
double stacked = 0.0;
for(int row=0;row<dataset.getRowCount();row++) {
Number value = dataset.getValue(row, col);
if(value != null) {
double d = value.doubleValue();
if(d>0.0) {
stacked += d;
values.add(stacked);
}
}
}
}
return values;
}
private List<Double> getValuesRegular(CategoryDataset dataset) {
List<Double> values = Lists.newArrayList();
for(int col=0;col < dataset.getColumnCount();col++) {
for(int row=0;row<dataset.getRowCount();row++) {
Number value = dataset.getValue(row, col);
if(value != null) {
values.add(value.doubleValue());
}
}
}
return values;
}
}