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 {
|
||||
float: right;
|
||||
height: 100px;
|
||||
width: 400px;
|
||||
margin-right: -3px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
/* Highlighted values */
|
||||
|
@ -6,6 +6,8 @@
|
||||
|
||||
<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">
|
||||
|
||||
<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 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 {
|
||||
constructor (app) {
|
||||
this._app = app
|
||||
@ -93,15 +67,71 @@ export class ServerRegistration {
|
||||
}
|
||||
|
||||
addGraphPoints (points, timestampPoints) {
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
const point = points[i]
|
||||
const timestamp = timestampPoints[i]
|
||||
this._graphData.push([timestamp, point])
|
||||
}
|
||||
this._graphData = [
|
||||
timestampPoints.map(val => Math.floor(val / 1000)),
|
||||
points
|
||||
]
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -110,11 +140,14 @@ export class ServerRegistration {
|
||||
|
||||
// Only update graph for successful pings
|
||||
// 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
|
||||
if (this._graphData.length > this._app.publicConfig.serverGraphMaxLength) {
|
||||
this._graphData.shift()
|
||||
for (const series of this._graphData) {
|
||||
if (series.length > this._app.publicConfig.serverGraphMaxLength) {
|
||||
series.shift()
|
||||
}
|
||||
}
|
||||
|
||||
this.redraw()
|
||||
@ -133,9 +166,7 @@ export class ServerRegistration {
|
||||
|
||||
redraw () {
|
||||
// Redraw the plot instance
|
||||
this._plotInstance.setData([this._graphData])
|
||||
this._plotInstance.setupGrid()
|
||||
this._plotInstance.draw()
|
||||
this._plotInstance.setData(this._graphData)
|
||||
}
|
||||
|
||||
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…
x
Reference in New Issue
Block a user