replace Flot.js with uPlot for server graphs
This commit is contained in:
parent
414775f630
commit
b96b9dacc5
@ -298,11 +298,8 @@ footer a:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.server .column-graph {
|
.server .column-graph {
|
||||||
float: right;
|
|
||||||
height: 100px;
|
height: 100px;
|
||||||
width: 400px;
|
width: 400px;
|
||||||
margin-right: -3px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Highlighted values */
|
/* Highlighted values */
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="../css/main.css">
|
<link rel="stylesheet" type="text/css" href="../css/main.css">
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="../lib/uPlot.min.css">
|
||||||
|
|
||||||
<link rel="icon" type="image/svg+xml" href="../images/logo.svg">
|
<link rel="icon" type="image/svg+xml" href="../images/logo.svg">
|
||||||
|
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
53
assets/js/scale.js
Normal file
53
assets/js/scale.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
class RelativeScale {
|
||||||
|
static scale (data, tickCount) {
|
||||||
|
const [min, max] = RelativeScale.calculateBounds(data)
|
||||||
|
|
||||||
|
let factor = 1
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const scale = Math.pow(10, factor)
|
||||||
|
|
||||||
|
const scaledMin = min - (min % scale)
|
||||||
|
const scaledMax = max + (max % scale === 0 ? 0 : (scale - (max % scale)))
|
||||||
|
|
||||||
|
const ticks = (scaledMax - scaledMin) / scale
|
||||||
|
|
||||||
|
if (ticks + 1 <= tickCount) {
|
||||||
|
return [scaledMin, scaledMax, scale]
|
||||||
|
} else {
|
||||||
|
// Too many steps between min/max, increase factor and try again
|
||||||
|
factor++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static generateTicks (min, max, step) {
|
||||||
|
const ticks = []
|
||||||
|
for (let i = min; i <= max; i += step) {
|
||||||
|
ticks.push(i)
|
||||||
|
}
|
||||||
|
return ticks
|
||||||
|
}
|
||||||
|
|
||||||
|
static calculateBounds (data) {
|
||||||
|
if (data.length === 0) {
|
||||||
|
return [0, 0]
|
||||||
|
} else {
|
||||||
|
let min = Number.MAX_VALUE
|
||||||
|
let max = Number.MIN_VALUE
|
||||||
|
|
||||||
|
for (const point of data) {
|
||||||
|
if (point > max) {
|
||||||
|
max = point
|
||||||
|
}
|
||||||
|
if (point < min) {
|
||||||
|
min = point
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [min, max]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = RelativeScale
|
@ -1,37 +1,11 @@
|
|||||||
|
import uPlot from '../lib/uPlot.esm'
|
||||||
|
|
||||||
|
import RelativeScale from './scale'
|
||||||
|
|
||||||
import { formatNumber, formatTimestamp, formatDate, formatMinecraftServerAddress, formatMinecraftVersions } from './util'
|
import { formatNumber, formatTimestamp, formatDate, formatMinecraftServerAddress, formatMinecraftVersions } from './util'
|
||||||
|
|
||||||
import MISSING_FAVICON from '../images/missing_favicon.svg'
|
import MISSING_FAVICON from '../images/missing_favicon.svg'
|
||||||
|
|
||||||
export const SERVER_GRAPH_OPTIONS = {
|
|
||||||
series: {
|
|
||||||
shadowSize: 0
|
|
||||||
},
|
|
||||||
xaxis: {
|
|
||||||
font: {
|
|
||||||
color: '#E3E3E3'
|
|
||||||
},
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
yaxis: {
|
|
||||||
minTickSize: 100,
|
|
||||||
tickDecimals: 0,
|
|
||||||
show: true,
|
|
||||||
tickLength: 10,
|
|
||||||
tickFormatter: formatNumber,
|
|
||||||
font: {
|
|
||||||
color: '#E3E3E3'
|
|
||||||
},
|
|
||||||
labelWidth: -10
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
hoverable: true,
|
|
||||||
color: '#696969'
|
|
||||||
},
|
|
||||||
colors: [
|
|
||||||
'#E9E581'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ServerRegistry {
|
export class ServerRegistry {
|
||||||
constructor (app) {
|
constructor (app) {
|
||||||
this._app = app
|
this._app = app
|
||||||
@ -93,15 +67,71 @@ export class ServerRegistration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addGraphPoints (points, timestampPoints) {
|
addGraphPoints (points, timestampPoints) {
|
||||||
for (let i = 0; i < points.length; i++) {
|
this._graphData = [
|
||||||
const point = points[i]
|
timestampPoints.map(val => Math.floor(val / 1000)),
|
||||||
const timestamp = timestampPoints[i]
|
points
|
||||||
this._graphData.push([timestamp, point])
|
]
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildPlotInstance () {
|
buildPlotInstance () {
|
||||||
this._plotInstance = $.plot('#chart_' + this.serverId, [this._graphData], SERVER_GRAPH_OPTIONS)
|
const tickCount = 5
|
||||||
|
|
||||||
|
// eslint-disable-next-line new-cap
|
||||||
|
this._plotInstance = new uPlot({
|
||||||
|
height: 100,
|
||||||
|
width: 400,
|
||||||
|
cursor: {
|
||||||
|
y: false,
|
||||||
|
drag: {
|
||||||
|
setScale: false,
|
||||||
|
x: false,
|
||||||
|
y: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
scale: 'Players',
|
||||||
|
stroke: '#E9E581',
|
||||||
|
width: 2,
|
||||||
|
value: (_, raw) => formatNumber(raw) + ' Players'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
axes: [
|
||||||
|
{
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ticks: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
font: '14px "Open Sans", sans-serif',
|
||||||
|
stroke: '#A3A3A3',
|
||||||
|
size: 55,
|
||||||
|
grid: {
|
||||||
|
stroke: '#333',
|
||||||
|
width: 1
|
||||||
|
},
|
||||||
|
split: (self) => {
|
||||||
|
const [min, max, scale] = RelativeScale.scale(self.data[1], tickCount)
|
||||||
|
const ticks = RelativeScale.generateTicks(min, max, scale)
|
||||||
|
return ticks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
scales: {
|
||||||
|
Players: {
|
||||||
|
auto: false,
|
||||||
|
range: (self) => {
|
||||||
|
const [scaledMin, scaledMax] = RelativeScale.scale(self.data[1], tickCount)
|
||||||
|
return [scaledMin, scaledMax]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
}, this._graphData, document.getElementById('chart_' + this.serverId))
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePing (payload, timestamp) {
|
handlePing (payload, timestamp) {
|
||||||
@ -110,11 +140,14 @@ export class ServerRegistration {
|
|||||||
|
|
||||||
// Only update graph for successful pings
|
// Only update graph for successful pings
|
||||||
// This intentionally pauses the server graph when pings begin to fail
|
// This intentionally pauses the server graph when pings begin to fail
|
||||||
this._graphData.push([timestamp, this.playerCount])
|
this._graphData[0].push(Math.floor(timestamp / 1000))
|
||||||
|
this._graphData[1].push(this.playerCount)
|
||||||
|
|
||||||
// Trim graphData to within the max length by shifting out the leading elements
|
// Trim graphData to within the max length by shifting out the leading elements
|
||||||
if (this._graphData.length > this._app.publicConfig.serverGraphMaxLength) {
|
for (const series of this._graphData) {
|
||||||
this._graphData.shift()
|
if (series.length > this._app.publicConfig.serverGraphMaxLength) {
|
||||||
|
series.shift()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.redraw()
|
this.redraw()
|
||||||
@ -133,9 +166,7 @@ export class ServerRegistration {
|
|||||||
|
|
||||||
redraw () {
|
redraw () {
|
||||||
// Redraw the plot instance
|
// Redraw the plot instance
|
||||||
this._plotInstance.setData([this._graphData])
|
this._plotInstance.setData(this._graphData)
|
||||||
this._plotInstance.setupGrid()
|
|
||||||
this._plotInstance.draw()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateServerRankIndex (rankIndex) {
|
updateServerRankIndex (rankIndex) {
|
||||||
|
2640
assets/lib/uPlot.esm.js
Normal file
2640
assets/lib/uPlot.esm.js
Normal file
File diff suppressed because it is too large
Load Diff
1
assets/lib/uPlot.min.css
vendored
Normal file
1
assets/lib/uPlot.min.css
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.uplot, .uplot *, .uplot *::before, .uplot *::after {box-sizing: border-box;}.uplot {font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";line-height: 1.5;width: max-content;}.uplot .title {text-align: center;font-size: 18px;font-weight: bold;}.uplot .wrap {position: relative;user-select: none;}.uplot .over, .uplot .under {position: absolute;overflow: hidden;}.uplot canvas {display: block;position: relative;width: 100%;height: 100%;}.uplot .legend {font-size: 14px;margin: auto;text-align: center;}.uplot .legend.inline {display: block;}.uplot .legend.inline * {display: inline-block;}.uplot .legend.inline tr {margin-right: 16px;}.uplot .legend th {font-weight: 600;}.uplot .legend th > * {vertical-align: middle;display: inline-block;}.uplot .legend .ident {width: 1em;height: 1em;margin-right: 4px;border: 2px solid transparent;}.uplot .legend.inline th::after {content: ":";vertical-align: middle;}.uplot .legend .series > * {padding: 4px;}.uplot .legend .series th {cursor: pointer;}.uplot .legend .off > * {opacity: 0.3;}.uplot .select {background: rgba(0,0,0,0.07);position: absolute;pointer-events: none;}.uplot .select.off {display: none;}.uplot .cursor-x, .uplot .cursor-y {position: absolute;left: 0;top: 0;pointer-events: none;will-change: transform;z-index: 100;}.uplot .cursor-x {height: 100%;border-right: 1px dashed #607D8B;}.uplot .cursor-y {width: 100%;border-bottom: 1px dashed #607D8B;}.uplot .cursor-pt {position: absolute;top: 0;left: 0;border-radius: 50%;filter: brightness(85%);pointer-events: none;will-change: transform;z-index: 100;}
|
Loading…
Reference in New Issue
Block a user