com.loadcoder.load.chart.logic.ChartLogic Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of loadcoder-chart Show documentation
Show all versions of loadcoder-chart Show documentation
This project contains the chart feature of Loadcoder
/*******************************************************************************
* Copyright (C) 2018 Stefan Vahlgren at Loadcoder
*
* This file is part of Loadcoder.
*
* Loadcoder is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Loadcoder is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
******************************************************************************/
package com.loadcoder.load.chart.logic;
import static com.loadcoder.load.chart.common.YCalculator.avg;
import static com.loadcoder.load.chart.common.YCalculator.max;
import static com.loadcoder.statics.Time.HOUR;
import java.awt.Color;
import java.awt.Paint;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jfree.chart.LegendItem;
import com.loadcoder.load.chart.common.CommonSample;
import com.loadcoder.load.chart.common.CommonSampleGroup;
import com.loadcoder.load.chart.common.CommonSeries;
import com.loadcoder.load.chart.common.CommonSeriesCalculator;
import com.loadcoder.load.chart.common.CommonYCalculator;
import com.loadcoder.load.chart.common.YCalculator;
import com.loadcoder.load.chart.data.DataSet;
import com.loadcoder.load.chart.data.FilteredData;
import com.loadcoder.load.chart.data.Point;
import com.loadcoder.load.chart.data.Range;
import com.loadcoder.load.chart.jfreechart.XYDottedSeriesExtension;
import com.loadcoder.load.chart.jfreechart.XYPlotExtension;
import com.loadcoder.load.chart.jfreechart.XYSeriesCollectionExtention;
import com.loadcoder.load.chart.jfreechart.XYSeriesExtension;
import com.loadcoder.load.chart.menu.DataSetUserType;
import com.loadcoder.load.chart.sampling.Sample;
import com.loadcoder.load.chart.sampling.SampleGroup;
import com.loadcoder.load.chart.utilities.ChartUtils;
import com.loadcoder.load.chart.utilities.ColorUtils;
import com.loadcoder.load.chart.utilities.SampleStatics;
import com.loadcoder.load.jfreechartfixes.XYLineAndShapeRendererExtention;
import com.loadcoder.result.TransactionExecutionResult;
public abstract class ChartLogic {
private Map seriesCommonMap = new HashMap();
private CommonSeries[] commonsToBeUsed;
protected Long earliestX;
protected long highestX = 0;
private long sampleLengthToUse;
final boolean locked;
private FilteredData filteredData;
private List removalFiltersInUse = new ArrayList();
protected final XYSeriesCollectionExtention seriesCollection;
protected final XYPlotExtension plot;
protected final XYLineAndShapeRendererExtention renderer;
protected final Map seriesVisible;
protected final List yCalculators = new ArrayList();
public YCalculator yCalculatorToUse = avg;
private final Map existingColors;
public final static int TARGET_AMOUNT_OF_POINTS_DEFAULT = 20_000;
protected static final long SAMPLELENGTH_DEFAULT = 1000;
/**
* customizedColors is not yet supported.
*/
@Deprecated
Map customizedColors;
private final List seriesKeys = new ArrayList();
protected final Map legends = new HashMap();
protected final List commonSeriesCalculators = new ArrayList();
protected final List ranges = new ArrayList();
public final List blacklistColors = new ArrayList();
protected List getSeriesKeys() {
return seriesKeys;
}
protected void addSeriesKey(String key) {
if (!seriesKeys.contains(key)) {
seriesKeys.add(key);
}
}
protected Map getCommonSeriesMap() {
return seriesCommonMap;
}
protected abstract void update(Map> listOfListOfList,
HashSet hashesGettingUpdated);
protected abstract void doUpdate();
public YCalculator getYCalculatorToUse() {
return yCalculatorToUse;
}
protected XYSeriesCollectionExtention getSeriesCollection() {
return seriesCollection;
}
public Map getExistingColors() {
return existingColors;
}
public XYPlotExtension getPlot() {
return plot;
}
// protected List getCommonSeries() {
// return commonSeries;
// }
protected long getXDiff() {
if (earliestX == null)
return 0;
long xDiff = highestX - earliestX;
return xDiff;
}
// only used for test
protected Range lookupCorrectRange(long ts) {
for (Range range : ranges) {
if (range.isTimestampInThisRange(ts))
return range;
}
throw new RuntimeException("No range was found for timestamp " + ts);
}
public void doSafeUpdate() {
synchronized (plot) {
doUpdate();
long xDiff = getXDiff();
if (xDiff > 23 * HOUR) {
plot.changeToMonthAndDayDateAxisFormat();
}
}
}
public List getRemovalFiltersInUse() {
return removalFiltersInUse;
}
protected void setFilteredData(FilteredData filteredData) {
this.filteredData = filteredData;
}
public FilteredData getFilteredData() {
return filteredData;
}
public long getSampleLengthToUse() {
return sampleLengthToUse;
}
public void setSampleLengthToUse(long sampleLengthToUse) {
this.sampleLengthToUse = sampleLengthToUse;
}
public List getyCalculators() {
return yCalculators;
}
void addToSeriesKeys(FilteredData filteredData, List seriesKeys) {
for (DataSet dataSet : filteredData.getDataSets()) {
String s = dataSet.getName();
if (!seriesKeys.contains(s))
seriesKeys.add(s);
}
}
public ChartLogic(XYSeriesCollectionExtention seriesCollection, XYPlotExtension plot,
XYLineAndShapeRendererExtention renderer, Map seriesVisible, CommonSeries[] commonSeries,
boolean locked, Map existingColors) {
this.locked = locked;
this.seriesCollection = seriesCollection;
this.plot = plot;
this.renderer = renderer;
this.seriesVisible = seriesVisible;
this.commonsToBeUsed = commonSeries == null ? CommonSeries.values() : commonSeries;
this.existingColors = existingColors;
for (CommonSeries s : commonsToBeUsed) {
existingColors.put(s.getName(), s.getColor());
}
yCalculators.add(avg);
yCalculators.add(max);
ColorUtils.defaultBlacklistColors.stream().forEach((blackListed) -> {
blacklistColors.add(blackListed);
});
// populateColorArray();
}
public static void addSurroundingTimestampsAsUpdates(Set hashesGettingUpdated, long sampleStart,
long earliest, long latest, List ranges, long currentSampleLength, Set sampleTimestamps,
Map aboutToBeUpdated) {
// check backwards
long iterator = sampleStart;
while (earliest < iterator) {
long lastTsInPrevious = iterator - 1;
long sampleLength = Range.findSampleLength(lastTsInPrevious, ranges);
long firstTsInPrevious = SampleGroup.calculateFirstTs(lastTsInPrevious, sampleLength);
boolean exists = false;
if (sampleTimestamps.contains(firstTsInPrevious) || aboutToBeUpdated.containsKey(firstTsInPrevious)) {
exists = true;
}
if (exists) {
break;
} else {
if (!hashesGettingUpdated.contains(firstTsInPrevious))
hashesGettingUpdated.add(firstTsInPrevious);
iterator = firstTsInPrevious;
}
}
// check forward
iterator = sampleStart;
while (latest > iterator) {
long sampleLength = Range.findSampleLength(iterator, ranges);
long firstTsInNext = iterator + sampleLength;
boolean exists = false;
if (sampleTimestamps.contains(firstTsInNext) || aboutToBeUpdated.containsKey(firstTsInNext)) {
exists = true;
}
if (exists) {
break;
} else {
if (!hashesGettingUpdated.contains(firstTsInNext))
hashesGettingUpdated.add(firstTsInNext);
iterator = firstTsInNext;
}
}
}
public void createCommons() {
// commonSeries = new ArrayList();
commonSeriesCalculators.clear();
// commonSeries.clear();
seriesCommonMap.clear();
Arrays.stream(commonsToBeUsed).forEach((common) -> {
Color c = existingColors.get(common.getName());
if (c == null) {
c = common.getColor();
}
XYSeriesExtension xySeries = new XYSeriesExtension(common.getName(), true, false, c);
seriesCommonMap.put(common.getName(), xySeries);
commonSeriesCalculators.add(new CommonSeriesCalculator(xySeries, common.getCommonYCalculator()));
// commonSeries.add(xySeries);
});
}
public void addSeries(XYSeriesExtension serie) {
seriesCollection.addSeries(serie);
int indexOfSeries = seriesCollection.indexOf(serie);
LegendItem legend = legends.get(serie.getKey());
if (legend == null) {
legend = plot.getRenderer().getLegendItem(0, indexOfSeries);
Color c = existingColors.get(serie.getKey());
// legend.setFillPaint(serie.getColorInTheChart());
legend.setFillPaint(c);
legend.setShapeVisible(true);
legend.setLineVisible(false);
legends.put(serie.getKey(), legend);
plot.getLegendItems().add(legend);
} else {
}
serie.setLegend(legend);
}
public void removeSeries(XYSeriesExtension serie) {
int indexOfSeries = seriesCollection.indexOf(serie);
seriesCollection.removeSeries(indexOfSeries);
}
/*
* iterate through all timestamp that are the first one in one of the updated
* samples. The series that are affected by the transaction series are going to
* be upated below
*/
void updateCommonsWithSamples(HashSet hashesGettingUpdated, Map sampleGroups,
Map samplesCommonMap, List sampleGroupCommonList) {
/*
* iterate through all timestamp that are the first one in one of the updated
* samples. The series that are affected by the transaction series are going to
* be upated below
*/
for (Long l : hashesGettingUpdated) {
for (CommonSeriesCalculator calc : commonSeriesCalculators) {
XYSeriesExtension series = calc.getSeries();
CommonYCalculator calculator = calc.getCalculator();
Range r = getSampleLength(l);
double amount = calculator.calculateCommonY(seriesKeys, l, sampleGroups, r.getSampleLength());
// get or create the samplegroup for the common series
String commonKey = series.getKey();
CommonSampleGroup commonSampleGroup = samplesCommonMap.get(commonKey);
if (commonSampleGroup == null) {
commonSampleGroup = new CommonSampleGroup(series);
samplesCommonMap.put(commonKey, commonSampleGroup);
sampleGroupCommonList.add(commonSampleGroup);
}
CommonSample cs = commonSampleGroup.getAndCreateSample(l, commonKey, r.getSampleLength());
long longAmount = Sample.amountToYValue(amount);
cs.setY(longAmount);
if (cs.getFirst() == null) {
cs.initDataItems();
series.add(cs.getFirst(), false);
if (SampleStatics.USE_TWO_SAMPLE_POINTS) {
series.add(cs.getLast(), false);
}
} else {
cs.updateDataItems();
}
}
}
}
Paint getSeriesColor(String dataSetName) {
LegendItem legend = legends.get(dataSetName);
Paint seriesColor = null;
if (legend == null) {
seriesColor = getNewColor(dataSetName);
} else {
seriesColor = legend.getFillPaint();
}
return seriesColor;
}
void getSerieses(List dataSets, boolean dottedMode, Map seriesMap) {
for (DataSet dataSet : dataSets) {
String dataSetName = dataSet.getName();
Paint seriesColor = getSeriesColor(dataSetName);
XYSeriesExtension seriesName = seriesMap.get(dataSetName);
if (seriesName == null) {
XYSeriesExtension serie = new XYSeriesExtension(dataSetName, true, false, seriesColor);
seriesMap.put(dataSetName, serie);
}
}
}
void adjustVisibilityOfSeries(XYSeriesExtension serie) {
int indexOfSeries = seriesCollection.indexOf(serie);
renderer.setSeriesShape(indexOfSeries, XYDottedSeriesExtension.DOTTEDSHAPE);
Boolean visible = seriesVisible.get(serie.getKey());
if (visible == null) {
visible = true;
seriesVisible.put(serie.getKey(), visible);
}
serie.setVisible(visible);
serie.getLegend().setShapeVisible(visible);
renderer.setSeriesLinesVisible(indexOfSeries, visible);
// is sample mode, the shapes shall always be invisible.
renderer.setSeriesShapesVisible(indexOfSeries, false);
}
void addPoints(List dataSets, Map sampleGroups, Set sampleTimestamps) {
long start = System.currentTimeMillis();
for (DataSet dataSet : dataSets) {
String dataSetName = dataSet.getName();
SampleGroup sampleGroup = sampleGroups.get(dataSetName);
createSamplesAndAddPoints(dataSetName, sampleGroup.getSeries(), dataSet, sampleGroup, sampleTimestamps);
}
}
void createSamplesGroups(Map seriesMap, Map sampleGroups) {
for (String key : seriesKeys) {
XYSeriesExtension serie = seriesMap.get(key);
SampleGroup sampleGroup = sampleGroups.get(key);
if (sampleGroup == null) {
sampleGroup = new SampleGroup(sampleLengthToUse, serie, locked);
sampleGroups.put(key, sampleGroup);
}
}
}
void createSamplesAndAddPoints(String dataSetName, XYSeriesExtension serie, DataSet dataSet,
SampleGroup sampleGroup, Set sampleTimestamps) {
for (Point point : dataSet.getPoints()) {
long x = point.getX();
if (x > highestX)
highestX = x;
if (earliestX == null || x < earliestX) {
earliestX = x;
if (ranges.isEmpty()) {
Sample s = sampleGroup.getAndCreateSample(point.getX(), dataSetName, sampleLengthToUse);
ranges.add(new Range(s.getFirstTs(), Long.MAX_VALUE, sampleLengthToUse));
} else {
/*
* if there are more than one range, and a new earliestTimestamp is added we
* must pick up the last added range here.
*/
Range r = ranges.get(ranges.size() - 1);
long sampleLengthOfTheLastAddedRange = r.getSampleLength();
/*
* the last added range will get a new start equal to the firstTs for the Sample
* of this Point that has the earliest timestamp
*/
Sample s = sampleGroup.getAndCreateSample(x, dataSetName, sampleLengthOfTheLastAddedRange);
r.setStart(s.getFirstTs());
}
}
// fetch the correct Range for the point
Range rangeToUse = getSampleLength(x);
// here the Sample for the point is either fetched of created
Sample s = sampleGroup.getAndCreateSample(point.getX(), dataSetName, rangeToUse.getSampleLength());
if (!sampleTimestamps.contains(s.getFirstTs())) {
sampleTimestamps.add(s.getFirstTs());
}
s.addPoint(point);
if (!point.isStatus()) {
s.increaseFails();
}
// This sample needs to be recalculated and redrawn!
if (!sampleGroup.getSamplesUnupdated().containsKey(s.getFirstTs())) {
sampleGroup.getSamplesUnupdated().put(s.getFirstTs(), s);
}
}
}
public void addAllCommonSeriesToTheChart() {
for (XYSeriesExtension series : getCommonSeriesMap().values()) {
addSeries(series);
int indexOfSeries = seriesCollection.indexOf(series);
Boolean visible = seriesVisible.get(series.getKey());
if (visible == null || visible) {
renderer.setSeriesLinesVisible(indexOfSeries, true);
} else {
renderer.setSeriesLinesVisible(indexOfSeries, false);
}
renderer.setSeriesShapesVisible(indexOfSeries, false);
}
}
public Range getSampleLength(long timestamp) {
for (Range range : ranges) {
if (timestamp >= range.getStart() && timestamp <= range.getEnd()) {
return range;
}
}
return null;
}
// void populateColorArray() {
// if (customizedColors != null) {
// Iterator> i = customizedColors.entrySet().iterator();
// while (i.hasNext()) {
// Entry e = i.next();
// existingColors.add(e.getValue());
// }
// }
// for (CommonSeries commonSerie : commonsToBeUsed) {
// Color c = commonSerie.getColor();
// existingColors.add(c);
// }
// }
public synchronized Color getNewColor(String seriesKey) {
// if (customizedColors != null) {
// Color color = customizedColors.get(seriesKey);
// return color;
// }
Set set = new HashSet(existingColors.values());
Color newColor = ColorUtils.getNewContrastfulColor(set, blacklistColors);
existingColors.put(seriesKey, newColor);
// existingColors.add(newColor);
return newColor;
}
public void forceRerender() {
seriesCollection.fireChange();
}
void updateSeriesWithSamples(Set hashesGettingUpdated, List dataSets,
Map sampleGroups, Set sampleTimestamps, boolean dottedMode) {
/*
* The new data has now been arranged into correct Samples. Time to calculate
* each of the affected Samples and update the points.
*/
for (String key : seriesKeys) {
SampleGroup group = sampleGroups.get(key);
XYSeriesExtension series = group.getSeries();
Set aboutToBeUpdatedKeys = group.getSamplesUnupdated().keySet();
for (Long unupdatedSampleKey : aboutToBeUpdatedKeys) {
Sample sample = group.getSamplesUnupdated().get(unupdatedSampleKey);
sample.calculateY(yCalculatorToUse);
double y = sample.getY();
if (dottedMode == false) {
// y will be -1 if there are no data in the sample. No items to add to the
// series
if (y != -1) {
// itemSeriesAdder.add(series, sample);
ChartUtils.populateSeriesWithSamples(sample, series);
}
}
long l = sample.getFirstTs();
if (!hashesGettingUpdated.contains(l)) {
hashesGettingUpdated.add(l);
}
long[] minmaxPoints = getFilteredData().getMinmaxPoints();
long earliest = minmaxPoints[0];
long latest = minmaxPoints[1];
addSurroundingTimestampsAsUpdates(hashesGettingUpdated, l, earliest, latest, ranges, sample.getLength(),
sampleTimestamps, group.getSamplesUnupdated());
}
group.getSamplesUnupdated().clear();
}
}
}