Abstracted Stack Graphs

This commit is contained in:
Rsl1122 2018-01-31 19:48:59 +02:00
parent 4ed3b4ae62
commit ab58c06380
22 changed files with 198 additions and 88 deletions

View File

@ -34,7 +34,11 @@ public class Theme implements SubSystem {
}
public static String getValue(ThemeVal variable) {
return getInstance().getThemeValue(variable);
try {
return getInstance().getThemeValue(variable);
} catch (NullPointerException | IllegalStateException e) {
return variable.getDefaultValue();
}
}
public static String replaceColors(String resourceString) {

View File

@ -5,7 +5,7 @@
*/
package com.djrapitops.plan.utilities.comparators;
import com.djrapitops.plan.utilities.analysis.Point;
import com.djrapitops.plan.utilities.html.graphs.line.Point;
import java.util.Comparator;

View File

@ -126,7 +126,7 @@ public class HtmlExport extends SpecificExport {
"web/js/helpers.js",
"web/js/script.js",
"web/js/charts/activityPie.js",
"web/js/charts/activityStackGraph.js",
"web/js/charts/stackGraph.js",
"web/js/charts/performanceGraph.js",
"web/js/charts/playerGraph.js",
"web/js/charts/playerGraphNoNav.js",

View File

@ -8,11 +8,10 @@ import com.djrapitops.plan.data.calculation.ActivityIndex;
import com.djrapitops.plan.system.settings.theme.Theme;
import com.djrapitops.plan.system.settings.theme.ThemeVal;
import com.djrapitops.plan.utilities.FormatUtils;
import com.djrapitops.plan.utilities.html.graphs.stack.AbstractStackGraph;
import com.djrapitops.plan.utilities.html.graphs.stack.StackDataSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.UUID;
import java.util.*;
/**
* Stack Graph that represents evolution of the PlayerBase in terms of ActivityIndex Groups.
@ -21,72 +20,37 @@ import java.util.UUID;
* @see ActivityIndex
* @since 4.2.0
*/
public class ActivityStackGraph implements HighChart {
private final String[] builtSeries;
public class ActivityStackGraph extends AbstractStackGraph {
public ActivityStackGraph(TreeMap<Long, Map<String, Set<UUID>>> activityData) {
this.builtSeries = createSeries(activityData);
super(getLabels(activityData.navigableKeySet()), getDataSets(activityData));
}
public String toHighChartsLabels() {
return builtSeries[0];
private static String[] getLabels(NavigableSet<Long> dates) {
return dates.stream()
.map(FormatUtils::formatTimeStamp)
.toArray(String[]::new);
}
@Override
public String toHighChartsSeries() {
return builtSeries[1];
}
private ActivityStackGraph() {
throw new IllegalStateException("Utility Class");
}
private String[] createSeries(TreeMap<Long, Map<String, Set<UUID>>> activityData) {
private static StackDataSet[] getDataSets(TreeMap<Long, Map<String, Set<UUID>>> activityData) {
String[] groups = ActivityIndex.getGroups();
String[] colors = Theme.getValue(ThemeVal.GRAPH_ACTIVITY_PIE).split(", ");
int maxCol = colors.length;
StackDataSet[] dataSets = new StackDataSet[groups.length];
// Series 0 is Labels for Graph x-axis, others are data for each group.
StringBuilder[] series = new StringBuilder[groups.length + 1];
for (int i = 0; i <= groups.length; i++) {
series[i] = new StringBuilder();
}
for (int i = 1; i <= groups.length; i++) {
series[i] = new StringBuilder("{name: '")
.append(groups[i - 1])
.append("',color:").append(colors[(i - 1) % maxCol])
.append(",data: [");
for (int i = 0; i < groups.length; i++) {
dataSets[i] = new StackDataSet(new ArrayList<>(), groups[i], colors[(i) % maxCol]);
}
int size = activityData.size();
int i = 0;
for (Long date : activityData.navigableKeySet()) {
Map<String, Set<UUID>> data = activityData.get(date);
series[0].append("'").append(FormatUtils.formatTimeStamp(date)).append("'");
for (int j = 1; j <= groups.length; j++) {
Set<UUID> players = data.get(groups[j - 1]);
series[j].append(players != null ? players.size() : 0);
}
if (i < size - 1) {
for (int j = 0; j <= groups.length; j++) {
series[j].append(",");
}
}
i++;
}
StringBuilder seriesBuilder = new StringBuilder("[");
for (int j = 1; j <= groups.length; j++) {
seriesBuilder.append(series[j].append("]}").toString());
if (j < groups.length) {
seriesBuilder.append(",");
for (int j = 0; j < groups.length; j++) {
Set<UUID> players = data.get(groups[j]);
dataSets[j].add((double) (players != null ? players.size() : 0));
}
}
return new String[]{series[0].toString(), seriesBuilder.append("]").toString()};
return dataSets;
}
}

View File

@ -5,10 +5,9 @@
*/
package com.djrapitops.plan.utilities.html.graphs.line;
import com.djrapitops.plan.utilities.analysis.DouglasPeuckerAlgorithm;
import com.djrapitops.plan.utilities.analysis.Point;
import com.djrapitops.plan.utilities.analysis.ReduceGapTriangles;
import com.djrapitops.plan.utilities.html.graphs.HighChart;
import com.djrapitops.plan.utilities.html.graphs.line.alg.DouglasPeuckerAlgorithm;
import com.djrapitops.plan.utilities.html.graphs.line.alg.ReduceGapTriangles;
import java.util.ArrayList;
import java.util.Collection;

View File

@ -1,7 +1,6 @@
package com.djrapitops.plan.utilities.html.graphs.line;
import com.djrapitops.plan.data.container.TPS;
import com.djrapitops.plan.utilities.analysis.Point;
import java.util.List;
import java.util.stream.Collectors;

View File

@ -1,7 +1,6 @@
package com.djrapitops.plan.utilities.html.graphs.line;
import com.djrapitops.plan.data.container.TPS;
import com.djrapitops.plan.utilities.analysis.Point;
import java.util.List;
import java.util.stream.Collectors;

View File

@ -1,7 +1,6 @@
package com.djrapitops.plan.utilities.html.graphs.line;
import com.djrapitops.plan.data.container.TPS;
import com.djrapitops.plan.utilities.analysis.Point;
import java.util.List;
import java.util.stream.Collectors;

View File

@ -3,7 +3,7 @@
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.djrapitops.plan.utilities.analysis;
package com.djrapitops.plan.utilities.html.graphs.line;
/**
* This math object is used in RamerDouglasPeucker algorithm.

View File

@ -1,7 +1,6 @@
package com.djrapitops.plan.utilities.html.graphs.line;
import com.djrapitops.plan.data.container.TPS;
import com.djrapitops.plan.utilities.analysis.Point;
import java.util.List;
import java.util.stream.Collectors;

View File

@ -1,4 +1,4 @@
package com.djrapitops.plan.utilities.analysis;
package com.djrapitops.plan.utilities.html.graphs.line;
import java.util.Objects;

View File

@ -1,7 +1,6 @@
package com.djrapitops.plan.utilities.html.graphs.line;
import com.djrapitops.plan.data.container.TPS;
import com.djrapitops.plan.utilities.analysis.Point;
import java.util.List;
import java.util.stream.Collectors;

View File

@ -1,7 +1,6 @@
package com.djrapitops.plan.utilities.html.graphs.line;
import com.djrapitops.plan.data.container.TPS;
import com.djrapitops.plan.utilities.analysis.Point;
import java.util.List;
import java.util.stream.Collectors;

View File

@ -1,4 +1,7 @@
package com.djrapitops.plan.utilities.analysis;
package com.djrapitops.plan.utilities.html.graphs.line.alg;
import com.djrapitops.plan.utilities.html.graphs.line.Line;
import com.djrapitops.plan.utilities.html.graphs.line.Point;
import java.util.ArrayList;
import java.util.Arrays;

View File

@ -1,6 +1,7 @@
package com.djrapitops.plan.utilities.analysis;
package com.djrapitops.plan.utilities.html.graphs.line.alg;
import com.djrapitops.plan.utilities.comparators.PointComparator;
import com.djrapitops.plan.utilities.html.graphs.line.Point;
import com.djrapitops.plugin.api.TimeAmount;
import com.djrapitops.plugin.utilities.Verify;

View File

@ -0,0 +1,78 @@
/*
* Licence is provided in the jar as license.yml also here:
* https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/license.yml
*/
package com.djrapitops.plan.utilities.html.graphs.stack;
import com.djrapitops.plan.utilities.html.graphs.HighChart;
/**
* Utility for creating HighCharts Stack graphs.
*
* @author Rsl1122
*/
public class AbstractStackGraph implements HighChart {
private final StackDataSet[] dataSets;
private final String[] labels;
public AbstractStackGraph(String[] labels, StackDataSet... dataSets) {
this.dataSets = dataSets;
this.labels = labels;
}
public String toHighChartsLabels() {
StringBuilder labelBuilder = new StringBuilder("[");
int length = this.labels.length;
int i = 0;
for (String label : this.labels) {
labelBuilder.append("'").append(label).append("'");
if (i < length - 1) {
labelBuilder.append(",");
}
i++;
}
return labelBuilder.append("]").toString();
}
private String toSeries(StackDataSet dataSet) {
StringBuilder dataSetBuilder = new StringBuilder("{name: '");
dataSetBuilder.append(dataSet.getName()).append("',")
.append("color:").append(dataSet.getColor())
.append(",data: [");
int size = dataSet.size();
int i = 0;
for (Double value : dataSet) {
dataSetBuilder.append(value);
if (i < size - 1) {
dataSetBuilder.append(",");
}
i++;
}
return dataSetBuilder.append("]}").toString();
}
@Override
public String toHighChartsSeries() {
StringBuilder seriesBuilder = new StringBuilder("[");
int size = dataSets.length;
int i = 0;
for (StackDataSet dataSet : dataSets) {
seriesBuilder.append(toSeries(dataSet));
if (i < size - 1) {
seriesBuilder.append(",");
}
i++;
}
return seriesBuilder.append("]").toString();
}
}

View File

@ -0,0 +1,37 @@
/*
* Licence is provided in the jar as license.yml also here:
* https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/license.yml
*/
package com.djrapitops.plan.utilities.html.graphs.stack;
import java.util.ArrayList;
import java.util.List;
/**
* Represents a value set for a Stack graph.
* <p>
* Stack graphs have labels that are defined separately and each StackDataSet represents a "line" in the stack graph.
* <p>
* Each StackDataSet can have a HTML color hex.
*
* @author Rsl1122
*/
public class StackDataSet extends ArrayList<Double> {
private final String name;
private final String color;
public StackDataSet(List<Double> values, String name, String color) {
super(values);
this.name = name;
this.color = color;
}
public String getName() {
return name;
}
public String getColor() {
return color;
}
}

View File

@ -1,4 +1,4 @@
function activityStackChart(id, categories, activitySeries) {
function stackChart(id, categories, series, label) {
Highcharts.chart(id, {
chart: {
type: 'area'
@ -15,7 +15,7 @@ function activityStackChart(id, categories, activitySeries) {
},
yAxis: {
title: {
text: 'Players'
text: label
},
labels: {
formatter: function () {
@ -25,20 +25,14 @@ function activityStackChart(id, categories, activitySeries) {
},
tooltip: {
split: true,
valueSuffix: ' Players'
valueSuffix: ' ' + label
},
plotOptions: {
area: {
stacking: 'normal',
// lineColor: '#666666',
lineWidth: 1
// ,
// marker: {
// lineWidth: 1,
// lineColor: '#666666'
// }
}
},
series: activitySeries
series: series
});
}

View File

@ -943,7 +943,7 @@
<script src="js/charts/punchCard.js"></script>
<script src="js/charts/healthGauge.js"></script>
<script src="js/charts/activityPie.js"></script>
<script src="js/charts/activityStackGraph.js"></script>
<script src="js/charts/stackGraph.js"></script>
<script src="js/charts/worldPie.js"></script>
<script src="js/charts/performanceGraph.js"></script>
<script src="js/charts/tpsGraph.js"></script>
@ -958,7 +958,7 @@
global: {
timezoneOffset: ${timeZone} * 60
}
});
})
// Data Variables
var playersOnlineSeries = {
@ -1049,7 +1049,8 @@
data: ${punchCardSeries}
};
var activityStackSeries = ${activityStackSeries};
var activityCategories = [${activityStackCategories}]
var activityCategories =
${activityStackCategories}
</script>
<!-- Plan load script -->
@ -1078,7 +1079,7 @@
// Chart draw scripts
activityPie('activityPie', activitySeries);
activityStackChart('activityStackGraph', activityCategories, activityStackSeries);
stackChart('activityStackGraph', activityCategories, activityStackSeries, 'Players');
worldPie('worldPie', worldSeries, gmSeries);
playersChart('playerChartDay', playersOnlineSeries, 3);
playersChart('playerChartMonth', playersOnlineSeries, 2);

View File

@ -7,7 +7,7 @@ import com.djrapitops.plan.data.container.UserInfo;
import com.djrapitops.plan.system.settings.locale.Message;
import com.djrapitops.plan.system.settings.locale.Msg;
import com.djrapitops.plan.utilities.PassEncryptUtil;
import com.djrapitops.plan.utilities.analysis.Point;
import com.djrapitops.plan.utilities.html.graphs.line.Point;
import com.google.common.collect.Ordering;
import org.junit.Test;
import utilities.RandomData;

View File

@ -4,14 +4,16 @@
*/
package com.djrapitops.plan.utilities.html.graphs;
import com.djrapitops.plan.data.calculation.ActivityIndex;
import com.djrapitops.plan.data.container.TPS;
import com.djrapitops.plan.system.settings.Settings;
import com.djrapitops.plan.utilities.html.graphs.line.*;
import com.djrapitops.plan.utilities.html.graphs.stack.AbstractStackGraph;
import org.junit.Before;
import org.junit.Test;
import utilities.RandomData;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.*;
import static org.junit.Assert.assertTrue;
@ -23,11 +25,22 @@ import static org.junit.Assert.assertTrue;
public class GraphTest {
private final List<TPS> tpsList = new ArrayList<>();
private final TreeMap<Long, Map<String, Set<UUID>>> activityData = new TreeMap<>();
@Before
public void setUp() {
String[] groups = ActivityIndex.getGroups();
for (int i = 0; i < 10; i++) {
tpsList.add(new TPS(i, i, i, i, i, i, i));
Map<String, Set<UUID>> gData = new HashMap<>();
for (String group : groups) {
Set<UUID> uuids = new HashSet<>();
for (int j = 0; j < RandomData.randomInt(1, 20); j++) {
uuids.add(UUID.randomUUID());
}
gData.put(group, uuids);
}
activityData.put((long) i, gData);
}
}
@ -53,6 +66,28 @@ public class GraphTest {
}
}
@Test
public void testStackGraphsForBracketErrors() {
Settings.FORMAT_DECIMALS.setTemporaryValue("#.##");
AbstractStackGraph[] graphs = new AbstractStackGraph[]{
new ActivityStackGraph(activityData)
};
for (AbstractStackGraph graph : graphs) {
System.out.print("Bracket Test: " + graph.getClass().getSimpleName() + " | ");
String series = graph.toHighChartsSeries();
System.out.println(series);
char[] chars = series.toCharArray();
assertBracketMatch(chars);
String labels = graph.toHighChartsLabels();
System.out.println(labels);
chars = labels.toCharArray();
assertBracketMatch(chars);
}
}
private void assertBracketMatch(char[] chars) {
Stack<Character> bracketStack = new Stack<>();
for (int i = 0; i < chars.length; i++) {

View File

@ -5,7 +5,7 @@ import com.djrapitops.plan.data.container.Session;
import com.djrapitops.plan.data.container.TPS;
import com.djrapitops.plan.data.container.UserInfo;
import com.djrapitops.plan.utilities.PassEncryptUtil;
import com.djrapitops.plan.utilities.analysis.Point;
import com.djrapitops.plan.utilities.html.graphs.line.Point;
import org.apache.commons.lang3.RandomStringUtils;
import java.util.ArrayList;