diff --git a/assets/html/index.html b/assets/html/index.html
index 08bfa58..5e9162c 100644
--- a/assets/html/index.html
+++ b/assets/html/index.html
@@ -91,7 +91,6 @@
-
diff --git a/assets/js/graph.js b/assets/js/graph.js
index 9c88a11..f64fefa 100644
--- a/assets/js/graph.js
+++ b/assets/js/graph.js
@@ -1,38 +1,12 @@
+import uPlot from '../lib/uPlot.esm'
+
+import { RelativeScale } from './scale'
+
import { formatNumber, formatTimestamp, isMobileBrowser } from './util'
+import { uPlotTooltipPlugin } from './tooltip'
import { FAVORITE_SERVERS_STORAGE_KEY } from './favorites'
-export const HISTORY_GRAPH_OPTIONS = {
- series: {
- shadowSize: 0
- },
- xaxis: {
- font: {
- color: '#E3E3E3'
- },
- show: false
- },
- yaxis: {
- show: true,
- ticks: 20,
- minTickSize: 10,
- tickLength: 10,
- tickFormatter: formatNumber,
- font: {
- color: '#E3E3E3'
- },
- labelWidth: -5,
- min: 0
- },
- grid: {
- hoverable: true,
- color: '#696969'
- },
- legend: {
- show: false
- }
-}
-
const HIDDEN_SERVERS_STORAGE_KEY = 'minetrack_hidden_servers'
const SHOW_FAVORITES_STORAGE_KEY = 'minetrack_show_favorites'
@@ -43,6 +17,7 @@ export class GraphDisplayManager {
constructor (app) {
this._app = app
this._graphData = []
+ this._graphTimestamps = []
this._hasLoadedSettings = false
this._initEventListenersOnce = false
this._showOnlyFavorites = false
@@ -57,15 +32,7 @@ export class GraphDisplayManager {
return
}
- const graphData = this._graphData[serverId]
-
- // Push the new data from the method call request
- graphData.push([timestamp, playerCount])
-
- // Trim any outdated entries by filtering the array into a new array
- if (graphData.length > this._app.publicConfig.graphMaxLength) {
- graphData.shift()
- }
+ // FIXME
}
loadLocalStorage () {
@@ -126,20 +93,17 @@ export class GraphDisplayManager {
}
}
- // Converts the backend data into the schema used by flot.js
getVisibleGraphData () {
- return Object.keys(this._graphData)
- .map(Number)
- .map(serverId => this._app.serverRegistry.getServerRegistration(serverId))
- .filter(serverRegistration => serverRegistration !== undefined && serverRegistration.isVisible)
- .map(serverRegistration => {
- return {
- data: this._graphData[serverRegistration.serverId],
- yaxis: 1,
- label: serverRegistration.data.name,
- color: serverRegistration.data.color
- }
- })
+ return this._app.serverRegistry.getServerRegistrations()
+ .filter(serverRegistration => serverRegistration.isVisible)
+ .map(serverRegistration => this._graphData[serverRegistration.serverId])
+ }
+
+ getPlotSize () {
+ return {
+ width: Math.max(window.innerWidth, 800) * 0.9,
+ height: 400
+ }
}
buildPlotInstance (graphData) {
@@ -150,12 +114,107 @@ export class GraphDisplayManager {
this.loadLocalStorage()
}
- this._graphData = graphData
+ // FIXME: timestamps are not shared!
+ this._graphTimestamps = graphData[0].map(val => Math.floor(val[0] / 1000))
+ this._graphData = Object.values(graphData).map(val => {
+ return val.map(element => {
+ // Safely handle null data points, they represent gaps in the graph
+ return element === null ? null : element[1]
+ })
+ })
- // Explicitly define a height so flot.js can rescale the Y axis
- document.getElementById('big-graph').style.height = '400px'
+ const series = this._app.serverRegistry.getServerRegistrations().map(serverRegistration => {
+ return {
+ scale: 'Players',
+ stroke: serverRegistration.data.color,
+ width: 2,
+ value: (_, raw) => formatNumber(raw) + ' Players',
+ show: serverRegistration.isVisible
+ }
+ })
- this._plotInstance = $.plot('#big-graph', this.getVisibleGraphData(), HISTORY_GRAPH_OPTIONS)
+ const tickCount = 10
+
+ // eslint-disable-next-line new-cap
+ this._plotInstance = new uPlot({
+ plugins: [
+ uPlotTooltipPlugin((pos, id, plot) => {
+ if (pos) {
+ // FIXME
+ let text = '' + formatTimestamp(this._graphTimestamps[id]) + '
'
+
+ for (let i = 1; i < plot.series.length; i++) {
+ const serverRegistration = this._app.serverRegistry.getServerRegistration(i - 1)
+ const serverGraphData = this._graphData[serverRegistration.serverId]
+
+ let playerCount
+
+ if (id >= serverGraphData.length) {
+ playerCount = '-'
+ } else {
+ playerCount = formatNumber(serverGraphData[id])
+ }
+
+ text += serverRegistration.data.name + ': ' + playerCount + '
'
+ }
+
+ this._app.tooltip.set(pos.left, pos.top, 10, 10, text)
+ } else {
+ this._app.tooltip.hide()
+ }
+ })
+ ],
+ ...this.getPlotSize(),
+ cursor: {
+ y: false
+ },
+ series: [
+ {
+ },
+ ...series
+ ],
+ axes: [
+ {
+ font: '14px "Open Sans", sans-serif',
+ stroke: '#FFF',
+ grid: {
+ show: false
+ },
+ space: 60
+ },
+ {
+ font: '14px "Open Sans", sans-serif',
+ stroke: '#FFF',
+ size: 60,
+ grid: {
+ stroke: '#333',
+ width: 1
+ },
+ split: () => {
+ const visibleGraphData = this.getVisibleGraphData()
+ const [, max, scale] = RelativeScale.scaleMatrix(visibleGraphData, tickCount)
+ const ticks = RelativeScale.generateTicks(0, max, scale)
+ return ticks
+ }
+ }
+ ],
+ scales: {
+ Players: {
+ auto: false,
+ range: () => {
+ const visibleGraphData = this.getVisibleGraphData()
+ const [, scaledMax] = RelativeScale.scaleMatrix(visibleGraphData, tickCount)
+ return [0, scaledMax]
+ }
+ }
+ },
+ legend: {
+ show: false
+ }
+ }, [
+ this._graphTimestamps,
+ ...this._graphData
+ ], document.getElementById('big-graph'))
// Show the settings-toggle element
document.getElementById('settings-toggle').style.display = 'inline-block'
@@ -166,11 +225,12 @@ export class GraphDisplayManager {
// This may cause unnessecary localStorage updates, but its a rare and harmless outcome
this.updateLocalStorage()
- // Fire calls to the provided graph instance
- // This allows flot.js to manage redrawing and creates a helper method to reduce code duplication
- this._plotInstance.setData(this.getVisibleGraphData())
- this._plotInstance.setupGrid()
- this._plotInstance.draw()
+ // Copy application state into the series data used by uPlot
+ for (const serverRegistration of this._app.serverRegistry.getServerRegistrations()) {
+ this._plotInstance.series[serverRegistration.serverId + 1].show = serverRegistration.isVisible
+ }
+
+ this._plotInstance.redraw()
}
requestResize () {
@@ -189,11 +249,7 @@ export class GraphDisplayManager {
}
resize = () => {
- if (this._plotInstance) {
- this._plotInstance.resize()
- this._plotInstance.setupGrid()
- this._plotInstance.draw()
- }
+ this._plotInstance.setSize(this.getPlotSize())
// undefine value so #clearTimeout is not called
// This is safe even if #resize is manually called since it removes the pending work
@@ -204,21 +260,6 @@ export class GraphDisplayManager {
this._resizeRequestTimeout = undefined
}
- // Called by flot.js when they hover over a data point.
- handlePlotHover = (event, pos, item) => {
- if (!item) {
- this._app.tooltip.hide()
- } else {
- let text = formatNumber(item.datapoint[1]) + ' Players
' + formatTimestamp(item.datapoint[0])
- // Prefix text with the series label when possible
- if (item.series && item.series.label) {
- text = '' + item.series.label + '
' + text
- }
-
- this._app.tooltip.set(item.pageX, item.pageY, 10, 10, text)
- }
- }
-
initEventListeners () {
if (!this._initEventListenersOnce) {
this._initEventListenersOnce = true
@@ -231,8 +272,6 @@ export class GraphDisplayManager {
})
}
- $('#big-graph').bind('plothover', this.handlePlotHover)
-
// These listeners should be bound each #initEventListeners call since they are for newly created elements
document.querySelectorAll('.graph-control').forEach((element) => {
element.addEventListener('click', this.handleServerButtonClick, false)
@@ -317,6 +356,7 @@ export class GraphDisplayManager {
}
reset () {
+ this._graphTimestamps = []
this._graphData = []
this._plotInstance = undefined
this._hasLoadedSettings = false
diff --git a/assets/js/scale.js b/assets/js/scale.js
index 47ee868..4a727d6 100644
--- a/assets/js/scale.js
+++ b/assets/js/scale.js
@@ -21,6 +21,26 @@ export class RelativeScale {
}
}
+ static scaleMatrix (data, tickCount) {
+ let max = Number.MIN_VALUE
+
+ for (const row of data) {
+ let testMax = Number.MIN_VALUE
+
+ for (const point of row) {
+ if (point > testMax) {
+ testMax = point
+ }
+ }
+
+ if (testMax > max) {
+ max = testMax
+ }
+ }
+
+ return RelativeScale.scale([0, max], tickCount)
+ }
+
static generateTicks (min, max, step) {
const ticks = []
for (let i = min; i <= max; i += step) {
diff --git a/assets/js/socket.js b/assets/js/socket.js
index 4fb764a..b60645d 100644
--- a/assets/js/socket.js
+++ b/assets/js/socket.js
@@ -54,7 +54,6 @@ export class SocketManager {
// Display the main page component
// Called here instead of syncComplete so the DOM can be drawn prior to the graphs being drawn
- // Otherwise flot.js will cause visual alignment bugs
this._app.setPageReady(true)
// Allow the graphDisplayManager to control whether or not the historical graph is loaded
diff --git a/lib/app.js b/lib/app.js
index 82c2b0a..36fdc90 100644
--- a/lib/app.js
+++ b/lib/app.js
@@ -50,7 +50,7 @@ class App {
// Send graphData in object wrapper to avoid needing to explicity filter
// any header data being appended by #MessageOf since the graph data is fed
- // directly into the flot.js graphing system
+ // directly into the graphing system
client.send(MessageOf('historyGraph', {
graphData: graphData
}))