diff --git a/Plan/src/main/java/com/djrapitops/plan/data/store/containers/AnalysisContainer.java b/Plan/src/main/java/com/djrapitops/plan/data/store/containers/AnalysisContainer.java index 843f735a7..2e0ce8d5e 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/store/containers/AnalysisContainer.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/store/containers/AnalysisContainer.java @@ -188,6 +188,17 @@ public class AnalysisContainer extends DataContainer { putSupplier(AnalysisKeys.AVG_PLAYERS_NEW_WEEK, () -> getUnsafe(newWeek).averageNewPerDay()); putSupplier(AnalysisKeys.AVG_PLAYERS_NEW_MONTH, () -> getUnsafe(newMonth).averageNewPerDay()); + putSupplier(AnalysisKeys.UNIQUE_PLAYERS_SERIES, () -> + new AbstractLineGraph(MutatorFunctions.toPoints( + getUnsafe(AnalysisKeys.SESSIONS_MUTATOR).uniqueJoinsPerDay()) + ).toHighChartsSeries() + ); + putSupplier(AnalysisKeys.NEW_PLAYERS_SERIES, () -> + new AbstractLineGraph(MutatorFunctions.toPoints( + getUnsafe(AnalysisKeys.PLAYERS_MUTATOR).newPerDay()) + ).toHighChartsSeries() + ); + Key retentionDay = new Key<>(Integer.class, "RETENTION_DAY"); // compareAndFindThoseLikelyToBeRetained can throw exception. putSupplier(retentionDay, () -> getUnsafe(AnalysisKeys.PLAYERS_MUTATOR).compareAndFindThoseLikelyToBeRetained( diff --git a/Plan/src/main/java/com/djrapitops/plan/data/store/keys/AnalysisKeys.java b/Plan/src/main/java/com/djrapitops/plan/data/store/keys/AnalysisKeys.java index c7c152671..9bfed1df3 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/store/keys/AnalysisKeys.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/store/keys/AnalysisKeys.java @@ -127,6 +127,8 @@ public class AnalysisKeys { public static final PlaceholderKey ACTIVITY_STACK_CATEGORIES = CommonPlaceholderKeys.ACTIVITY_STACK_CATEGORIES; public static final PlaceholderKey ACTIVITY_PIE_SERIES = CommonPlaceholderKeys.ACTIVITY_PIE_SERIES; public static final PlaceholderKey CALENDAR_SERIES = new PlaceholderKey<>(String.class, "calendarSeries"); + public static final PlaceholderKey UNIQUE_PLAYERS_SERIES = new PlaceholderKey<>(String.class, "uniquePlayersSeries"); + public static final PlaceholderKey NEW_PLAYERS_SERIES = new PlaceholderKey<>(String.class, "newPlayersSeries"); // Variables used only during analysis public static final Key SESSIONS_MUTATOR = CommonKeys.SESSIONS_MUTATOR; public static final Key TPS_MUTATOR = CommonKeys.TPS_MUTATOR; diff --git a/Plan/src/main/java/com/djrapitops/plan/data/store/mutators/DateHoldersMutator.java b/Plan/src/main/java/com/djrapitops/plan/data/store/mutators/DateHoldersMutator.java index 73e1fc5de..9c2fe15d6 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/store/mutators/DateHoldersMutator.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/store/mutators/DateHoldersMutator.java @@ -18,6 +18,10 @@ public class DateHoldersMutator { public TreeMap> groupByStartOfDay() { TreeMap> map = new TreeMap<>(); + if (dateHolders.isEmpty()) { + return map; + } + long twentyFourHours = 24L * TimeAmount.HOUR.ms(); for (T holder : dateHolders) { long date = holder.getDate(); @@ -28,11 +32,16 @@ public class DateHoldersMutator { map.put(startOfDate, list); } - // Add missing in-between dates - for (long date = map.firstKey(); date < map.lastKey(); date += twentyFourHours) { - map.putIfAbsent(date, new ArrayList<>()); + // Empty map firstKey attempt causes NPE if not checked. + if (!map.isEmpty()) { + // Add missing in-between dates + long start = map.firstKey(); + long now = System.currentTimeMillis(); + long end = now - (now % twentyFourHours); + for (long date = map.firstKey(); date < end; date += twentyFourHours) { + map.putIfAbsent(date, new ArrayList<>()); + } } - return map; } diff --git a/Plan/src/main/java/com/djrapitops/plan/system/webserver/pages/parsing/AnalysisPage.java b/Plan/src/main/java/com/djrapitops/plan/system/webserver/pages/parsing/AnalysisPage.java index 8ad99f87b..156942581 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/webserver/pages/parsing/AnalysisPage.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/webserver/pages/parsing/AnalysisPage.java @@ -74,7 +74,8 @@ public class AnalysisPage implements Page { TPS_SERIES, CPU_SERIES, RAM_SERIES, ENTITY_SERIES, CHUNK_SERIES, PUNCHCARD_SERIES, WORLD_MAP_SERIES, ACTIVITY_STACK_SERIES, ACTIVITY_STACK_CATEGORIES, - ACTIVITY_PIE_SERIES, CALENDAR_SERIES + ACTIVITY_PIE_SERIES, CALENDAR_SERIES, + UNIQUE_PLAYERS_SERIES, NEW_PLAYERS_SERIES ); placeholderReplacer.addAllPlaceholdersFrom(analysisContainer, FormatUtils::cutDecimals, AVG_TPS_MONTH, AVG_TPS_WEEK, AVG_TPS_DAY, diff --git a/Plan/src/main/java/com/djrapitops/plan/utilities/file/export/HtmlExport.java b/Plan/src/main/java/com/djrapitops/plan/utilities/file/export/HtmlExport.java index a9407078a..c91dbfb55 100644 --- a/Plan/src/main/java/com/djrapitops/plan/utilities/file/export/HtmlExport.java +++ b/Plan/src/main/java/com/djrapitops/plan/utilities/file/export/HtmlExport.java @@ -121,6 +121,7 @@ public class HtmlExport extends SpecificExport { "web/js/helpers.js", "web/js/script.js", "web/js/charts/activityPie.js", + "web/js/charts/lineGraph.js", "web/js/charts/stackGraph.js", "web/js/charts/performanceGraph.js", "web/js/charts/playerGraph.js", diff --git a/Plan/src/main/resources/web/js/charts/lineGraph.js b/Plan/src/main/resources/web/js/charts/lineGraph.js new file mode 100644 index 000000000..3dc505205 --- /dev/null +++ b/Plan/src/main/resources/web/js/charts/lineGraph.js @@ -0,0 +1,36 @@ +function lineChart(id, series) { + Highcharts.stockChart(id, { + rangeSelector: { + selected: 2, + buttons: [{ + type: 'hour', + count: 12, + text: '12h' + }, { + type: 'hour', + count: 24, + text: '24h' + }, { + type: 'day', + count: 7, + text: '7d' + }, { + type: 'month', + count: 1, + text: '30d' + }, { + type: 'all', + text: 'All' + }] + }, + yAxis: { + softMax: 2, + softMin: 0 + }, + title: {text: ''}, + legend: { + enabled: true + }, + series: series + }); +} \ No newline at end of file diff --git a/Plan/src/main/resources/web/server.html b/Plan/src/main/resources/web/server.html index 3a8d3b082..5b569c2d4 100644 --- a/Plan/src/main/resources/web/server.html +++ b/Plan/src/main/resources/web/server.html @@ -1119,6 +1119,7 @@ + @@ -1141,6 +1142,7 @@ var v = { colors: { playersOnline: '${playersGraphColor}', + newPlayers: '#8BC34A', tpsLow: '${tpsLowColor}', tpsMed: '${tpsMediumColor}', tpsHigh: '${tpsHighColor}', @@ -1159,6 +1161,8 @@ }, data: { playersOnline: ${playersOnlineSeries}, + uniquePlayers: ${uniquePlayersSeries}, + newPlayers: ${newPlayersSeries}, tps: ${tpsSeries}, cpu: ${cpuSeries}, ram: ${ramSeries}, @@ -1180,6 +1184,8 @@ var s = { name: { playersOnline: 'Players Online', + uniquePlayers: 'Unique Players', + newPlayers: 'New Players', tps: 'TPS', cpu: 'CPU Usage (%)', ram: 'RAM Usage (MB)', @@ -1220,6 +1226,20 @@ color: v.colors.playersOnline, yAxis: 0 }, + uniquePlayers: { + name: s.name.uniquePlayers, + type: s.type.spline, + tooltip: s.tooltip.zeroDecimals, + data: v.data.uniquePlayers, + color: v.colors.playersOnline + }, + newPlayers: { + name: s.name.newPlayers, + type: s.type.spline, + tooltip: s.tooltip.zeroDecimals, + data: v.data.newPlayers, + color: v.colors.newPlayers + }, tps: { name: s.name.tps, type: s.type.spline, @@ -1346,6 +1366,7 @@ // Chart draw scripts activityPie('activityPie', series.activityPie); + lineChart('uniqueChart', [series.uniquePlayers, series.newPlayers]); stackChart('activityStackGraph', series.activityStackCategories, series.activityStack, 'Players'); worldPie('worldPie', series.worldPie, series.worldPieDrillDown); playersChart('playerChartDay', series.playersOnline, 3);