mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2025-03-07 17:28:03 +08:00
Abstracted Stack Graphs
This commit is contained in:
parent
4ed3b4ae62
commit
ab58c06380
@ -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) {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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 Ramer–Douglas–Peucker algorithm.
|
@ -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;
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.djrapitops.plan.utilities.analysis;
|
||||
package com.djrapitops.plan.utilities.html.graphs.line;
|
||||
|
||||
import java.util.Objects;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
||||
});
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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++) {
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user