add interval splitting for bar charts
This commit is contained in:
@@ -126,7 +126,7 @@ public class AggregateHandlerCollection {
|
||||
if (optionalAggregator.isPresent()) {
|
||||
final CustomAggregator aggregator = optionalAggregator.get();
|
||||
if (aggregator instanceof IndexedAggregator) {
|
||||
((IndexedAggregator) aggregator).setIndex(index);
|
||||
((IndexedAggregator) aggregator).setIndex(index, dataSeries.size());
|
||||
index++;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.lucares.pdb.plot.api;
|
||||
|
||||
import org.lucares.recommind.logs.GnuplotAxis;
|
||||
|
||||
public interface BarChart extends IndexedAggregator {
|
||||
|
||||
String asCsv();
|
||||
|
||||
String getDataName();
|
||||
|
||||
/**
|
||||
* Total number of values over all intervals.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
long getCount();
|
||||
|
||||
String renderLabels(GnuplotAxis xAxis);
|
||||
|
||||
}
|
||||
@@ -5,17 +5,20 @@ import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.lucares.pdb.api.RuntimeIOException;
|
||||
import org.lucares.recommind.logs.GnuplotAxis;
|
||||
|
||||
public class BarChartAggregator implements CustomAggregator, IndexedAggregator {
|
||||
public class BarChartAggregator implements CustomAggregator, IndexedAggregator, BarChart {
|
||||
|
||||
long count = 0;
|
||||
|
||||
private final Path tmpDir;
|
||||
|
||||
private Long index = null;
|
||||
private Long numberOfDataSeries;
|
||||
|
||||
private final String dataName = "$data" + UUID.randomUUID().toString().replace("-", "");
|
||||
|
||||
@@ -25,8 +28,9 @@ public class BarChartAggregator implements CustomAggregator, IndexedAggregator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIndex(final long index) {
|
||||
public void setIndex(final long index, final long numberOfDataSeries) {
|
||||
this.index = index;
|
||||
this.numberOfDataSeries = numberOfDataSeries;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -37,6 +41,15 @@ public class BarChartAggregator implements CustomAggregator, IndexedAggregator {
|
||||
return this.index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNumberOfDataSeries() throws IllegalStateException {
|
||||
if (this.numberOfDataSeries == null) {
|
||||
throw new IllegalStateException("index was not set");
|
||||
}
|
||||
return this.numberOfDataSeries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCount() {
|
||||
return count;
|
||||
}
|
||||
@@ -46,6 +59,7 @@ public class BarChartAggregator implements CustomAggregator, IndexedAggregator {
|
||||
count++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String asCsv() {
|
||||
final StringBuilder csv = new StringBuilder();
|
||||
|
||||
@@ -77,9 +91,19 @@ public class BarChartAggregator implements CustomAggregator, IndexedAggregator {
|
||||
return Aggregate.BAR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDataName() {
|
||||
|
||||
return dataName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String renderLabels(final GnuplotAxis xAxis) {
|
||||
return String.format("set label at %s %f, %d '%s' center front offset 0,0.3", // front
|
||||
xAxis == GnuplotAxis.X1 ? "first" : "second", //
|
||||
getIndex() + 0.5, //
|
||||
getCount(), //
|
||||
String.format(Locale.US, "%,d", getCount()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
package org.lucares.pdb.plot.api;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.lucares.pdb.api.RuntimeIOException;
|
||||
import org.lucares.recommind.logs.GnuplotAxis;
|
||||
|
||||
public class BarChartAggregatorForIntervals implements CustomAggregator, IndexedAggregator, BarChart {
|
||||
|
||||
private final Path tmpDir;
|
||||
|
||||
private Long index = null;
|
||||
private Long numberOfDataSeries;
|
||||
|
||||
private final String dataName = "$data" + UUID.randomUUID().toString().replace("-", "");
|
||||
|
||||
private final Interval interval;
|
||||
|
||||
private final Map<String, AtomicLong> buckets;
|
||||
|
||||
private int count;
|
||||
|
||||
public BarChartAggregatorForIntervals(final Path tmpDir, final Interval interval) {
|
||||
this.tmpDir = tmpDir;
|
||||
this.interval = interval;
|
||||
buckets = interval.getBuckets();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIndex(final long index, final long numberOfDataSeries) {
|
||||
this.index = index;
|
||||
this.numberOfDataSeries = numberOfDataSeries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getIndex() throws IllegalStateException {
|
||||
if (this.index == null) {
|
||||
throw new IllegalStateException("index was not set");
|
||||
}
|
||||
return this.index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNumberOfDataSeries() throws IllegalStateException {
|
||||
if (this.numberOfDataSeries == null) {
|
||||
throw new IllegalStateException("index was not set");
|
||||
}
|
||||
return this.numberOfDataSeries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addValue(final long epochMilli, final long value) {
|
||||
try {
|
||||
final String bucketId = interval.toBucket(epochMilli);
|
||||
buckets.get(bucketId).incrementAndGet();
|
||||
count++;
|
||||
} catch (final NullPointerException e) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String asCsv() {
|
||||
final StringBuilder csv = new StringBuilder();
|
||||
|
||||
int offset = 0;
|
||||
for (final String bucketId : bucketIds()) {
|
||||
final long count = buckets.get(bucketId).get();
|
||||
csv.append(getIndex() + 0.5 + offset);
|
||||
csv.append(",");
|
||||
csv.append(bucketId);
|
||||
csv.append(",");
|
||||
csv.append(count);
|
||||
csv.append("\n");
|
||||
offset += numberOfDataSeries;
|
||||
}
|
||||
return csv.toString();
|
||||
}
|
||||
|
||||
private SortedSet<String> bucketIds() {
|
||||
|
||||
return new TreeSet<>(buckets.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AggregatedData getAggregatedData() {
|
||||
try {
|
||||
final File dataFile = File.createTempFile("bar", ".dat", tmpDir.toFile());
|
||||
|
||||
final String csv = asCsv();
|
||||
|
||||
Files.writeString(dataFile.toPath(), csv, StandardCharsets.UTF_8);
|
||||
final AggregatedData result = new AggregatedData("label", dataFile);
|
||||
return result;
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Aggregate getType() {
|
||||
return Aggregate.BAR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDataName() {
|
||||
|
||||
return dataName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String renderLabels(final GnuplotAxis xAxis) {
|
||||
final StringBuilder result = new StringBuilder();
|
||||
int offset = 0;
|
||||
for (final String bucketId : bucketIds()) {
|
||||
final long count = buckets.get(bucketId).get();
|
||||
final String label = String.format("set label at %s %f, %d '%s' center front offset 0,0.3\n", // front
|
||||
xAxis == GnuplotAxis.X1 ? "first" : "second", //
|
||||
getIndex() + 0.5 + offset, //
|
||||
count, //
|
||||
String.format(Locale.US, "%,d", count));
|
||||
|
||||
result.append(label);
|
||||
offset += numberOfDataSeries;
|
||||
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.lucares.recommind.logs.AxisSettings;
|
||||
@@ -43,8 +42,9 @@ public class BarChartHandler extends AggregateHandler {
|
||||
result.setType(Type.Group);
|
||||
result.setAxis(getxAxis());
|
||||
result.setTicsEnabled(false);
|
||||
// TODO revert next two lines
|
||||
result.setFrom("0");
|
||||
result.setTo(String.valueOf(dataSeries.size()));
|
||||
// result.setTo(String.valueOf(dataSeries.size()));
|
||||
|
||||
final List<String> ticsLabels = new ArrayList<>();
|
||||
int index = 1;
|
||||
@@ -60,18 +60,14 @@ public class BarChartHandler extends AggregateHandler {
|
||||
String beforePlot(final CustomAggregator aggregator, final GnuplotSettings settings) {
|
||||
final StringBuilder result = new StringBuilder();
|
||||
|
||||
final BarChartAggregator barAggregator = (BarChartAggregator) aggregator;
|
||||
final BarChart barAggregator = (BarChart) aggregator;
|
||||
|
||||
appendfln(result, "%s <<EOD", barAggregator.getDataName());
|
||||
appendln(result, barAggregator.asCsv());
|
||||
appendln(result, "EOD");
|
||||
|
||||
if (settings.isRenderLabels()) {
|
||||
appendfln(result, "set label at %s %f, %d '%s' center front offset 0,0.3", // front
|
||||
getxAxis() == GnuplotAxis.X1 ? "first" : "second", //
|
||||
barAggregator.getIndex() + 0.5, //
|
||||
barAggregator.getCount(), //
|
||||
String.format(Locale.US, "%,d", barAggregator.getCount()));
|
||||
appendfln(result, barAggregator.renderLabels(getxAxis()));
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
@@ -80,20 +76,7 @@ public class BarChartHandler extends AggregateHandler {
|
||||
@Override
|
||||
String addPlot(final CustomAggregator aggregator, final LineStyle lineStyle, final Optional<String> title) {
|
||||
|
||||
final BarChartAggregator barAggregator = (BarChartAggregator) aggregator;
|
||||
/*
|
||||
* appendfln(result,
|
||||
* "'%s' using 1:3:xtic(2) notitle with %s axes %s fs solid %s, \\", //
|
||||
* aggregatedData.getDataFile(), // GnuplotLineType.Bar, // gnuplotXYAxis(), //
|
||||
* lineStyle// );
|
||||
*/
|
||||
// return formatln("'%s' using 1:3:xtic(2) %s with %s axes %s fs solid %s, \\", //
|
||||
// barAggregator.getDataName(), //
|
||||
// gnuplotTitle(title), //
|
||||
// GnuplotLineType.Bar, //
|
||||
// gnuplotXYAxis(), //
|
||||
// lineStyle.asGnuplotLineStyleBright()//
|
||||
// );
|
||||
final BarChart barAggregator = (BarChart) aggregator;
|
||||
|
||||
return formatln("'%s' using 1:3:%stic(2) %s with %s axes %s fs solid %s, \\", //
|
||||
barAggregator.getDataName(), //
|
||||
@@ -108,7 +91,11 @@ public class BarChartHandler extends AggregateHandler {
|
||||
@Override
|
||||
CustomAggregator createCustomAggregator(final Path tmpDir, final PlotSettings plotSettings,
|
||||
final long fromEpochMilli, final long toEpochMilli) {
|
||||
return new BarChartAggregator(tmpDir);
|
||||
if (plotSettings.getInterval().isPresent()) {
|
||||
return new BarChartAggregatorForIntervals(tmpDir, plotSettings.getInterval().get());
|
||||
} else {
|
||||
return new BarChartAggregator(tmpDir);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ public interface IndexedAggregator {
|
||||
|
||||
/**
|
||||
* Set the index of this {@link CustomAggregator}.
|
||||
*
|
||||
* @param numberOfDataSeries
|
||||
*/
|
||||
public void setIndex(long index);
|
||||
public void setIndex(long index, long numberOfDataSeries);
|
||||
|
||||
/**
|
||||
* Returns the index.
|
||||
@@ -13,4 +15,12 @@ public interface IndexedAggregator {
|
||||
* @throws IllegalStateException if the index was no set
|
||||
*/
|
||||
public long getIndex() throws IllegalStateException;
|
||||
|
||||
/**
|
||||
* Returns the number of dataSeries.
|
||||
*
|
||||
* @return number of data series
|
||||
* @throws IllegalStateException if the number of data series was not set
|
||||
*/
|
||||
public long getNumberOfDataSeries() throws IllegalStateException;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
package org.lucares.pdb.plot.api;
|
||||
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.lucares.pdb.api.DateTimeRange;
|
||||
import org.lucares.utils.LongToDateBucket;
|
||||
|
||||
public class Interval {
|
||||
public enum IntervalTimeUnit {
|
||||
MINUTE, HOUR, DAY, WEEK, MONTH, YEAR;
|
||||
|
||||
public static boolean isValid(final String value) {
|
||||
for (final IntervalTimeUnit e : values()) {
|
||||
if (e.name().equals(value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public ChronoUnit toChronoUnit() {
|
||||
switch (this) {
|
||||
case MINUTE:
|
||||
return ChronoUnit.MINUTES;
|
||||
case HOUR:
|
||||
return ChronoUnit.HOURS;
|
||||
case DAY:
|
||||
return ChronoUnit.DAYS;
|
||||
case WEEK:
|
||||
return ChronoUnit.WEEKS;
|
||||
case MONTH:
|
||||
return ChronoUnit.MONTHS;
|
||||
case YEAR:
|
||||
return ChronoUnit.YEARS;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected value: " + this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final IntervalTimeUnit intervalTimeUnit;
|
||||
|
||||
private final int value;
|
||||
|
||||
private final DateTimeRange dateTimeRange;
|
||||
|
||||
private final LongToDateBucket bucketer;
|
||||
|
||||
private Interval(final String intervalTimeUnit, final int value, final DateTimeRange dateTimeRange) {
|
||||
this.dateTimeRange = dateTimeRange;
|
||||
this.intervalTimeUnit = IntervalTimeUnit.valueOf(intervalTimeUnit);
|
||||
this.value = value;
|
||||
|
||||
this.bucketer = new LongToDateBucket(toDateFormatForBucketer(this.intervalTimeUnit),
|
||||
this.intervalTimeUnit.toChronoUnit());
|
||||
}
|
||||
|
||||
private String toDateFormatForBucketer(final IntervalTimeUnit intervalTimeUnit) {
|
||||
switch (intervalTimeUnit) {
|
||||
case MINUTE:
|
||||
return "yyyyMMddHHmm";
|
||||
case HOUR:
|
||||
return "yyyyMMddHH";
|
||||
case DAY:
|
||||
return "yyyyMMdd";
|
||||
case WEEK:
|
||||
return "YYYYww"; // use week based year! Otherwise intervals over the year boundary will be wrong
|
||||
case MONTH:
|
||||
return "yyyyMM";
|
||||
case YEAR:
|
||||
return "yyyy";
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected value: " + intervalTimeUnit);
|
||||
}
|
||||
}
|
||||
|
||||
public String toBucket(final long epochMilli) {
|
||||
return bucketer.toPartitionId(epochMilli);
|
||||
}
|
||||
|
||||
public IntervalTimeUnit getIntervalTimeUnit() {
|
||||
return intervalTimeUnit;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value + " " + intervalTimeUnit;
|
||||
}
|
||||
|
||||
public static Interval create(final String intervalUnit, final int intervalValue,
|
||||
final DateTimeRange dateTimeRange) {
|
||||
if (IntervalTimeUnit.isValid(intervalUnit)) {
|
||||
return new Interval(intervalUnit, intervalValue, dateTimeRange);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Map<String, AtomicLong> getBuckets() {
|
||||
final Map<String, AtomicLong> result = new HashMap<>();
|
||||
final List<String> bucketIds = bucketer.toPartitionIds(dateTimeRange.getStart(), dateTimeRange.getEnd(),
|
||||
intervalTimeUnit.toChronoUnit());
|
||||
|
||||
for (final String bucketId : bucketIds) {
|
||||
result.put(bucketId, new AtomicLong(0));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.lucares.pdb.api.DateTimeRange;
|
||||
@@ -42,6 +43,8 @@ public class PlotSettings {
|
||||
|
||||
private boolean generateThumbnail;
|
||||
|
||||
private Interval interval;
|
||||
|
||||
public String getQuery() {
|
||||
return query;
|
||||
}
|
||||
@@ -184,6 +187,14 @@ public class PlotSettings {
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected value: " + yAxis);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Optional<Interval> getInterval() {
|
||||
return Optional.ofNullable(interval);
|
||||
}
|
||||
|
||||
public void setInterval(final Interval interval) {
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user