Skip to content

Commit 0ff66ea

Browse files
buck54321chappjc
authored andcommitted
explorer charts: refactor legend formatting
1 parent 43a63cf commit 0ff66ea

File tree

3 files changed

+102
-104
lines changed

3 files changed

+102
-104
lines changed

public/js/controllers/charts_controller.js

Lines changed: 73 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@ const windowScales = ['ticket-price', 'pow-difficulty', 'missed-votes']
2020
const lineScales = ['ticket-price']
2121
// index 0 represents y1 and 1 represents y2 axes.
2222
const yValueRanges = { 'ticket-price': [1] }
23+
var chainworkUnits = ['exahash', 'zettahash', 'yottahash']
24+
var hashrateUnits = ['Th/s', 'Ph/s', 'Eh/s']
2325
var ticketPoolSizeTarget, premine, stakeValHeight, stakeShare
2426
var baseSubsidy, subsidyInterval, subsidyExponent, windowSize, avgBlockTime
2527
var rawCoinSupply, rawPoolValue
28+
var yFormatter, legendEntry, legendMarker, legendElement
2629

2730
function usesWindowUnits (chart) {
2831
return windowScales.indexOf(chart) > -1
@@ -57,20 +60,9 @@ function axesToRestoreYRange (chartName, origYRange, newYRange) {
5760
return axes
5861
}
5962

60-
function formatHashRate (value, displayType) {
61-
value = parseInt(value)
62-
if (value <= 0) return value
63-
var shortUnits = ['Th', 'Ph', 'Eh']
64-
var labelUnits = ['terahash/s', 'petahash/s', 'exahash/s']
65-
for (var i = 0; i < labelUnits.length; i++) {
66-
var quo = Math.pow(1000, i)
67-
var max = Math.pow(1000, i + 1)
68-
if ((value > quo && value <= max) || i + 1 === labelUnits.length) {
69-
var data = intComma(Math.floor(value / quo))
70-
if (displayType === 'axis') return data + '' + shortUnits[i]
71-
return data + ' ' + labelUnits[i]
72-
}
73-
}
63+
function withBigUnits (v, units) {
64+
var i = v === 0 ? 0 : Math.floor(Math.log10(v) / 3)
65+
return (v / Math.pow(1000, i)).toFixed(3) + ' ' + units[i]
7466
}
7567

7668
function blockReward (height) {
@@ -80,74 +72,30 @@ function blockReward (height) {
8072
return 0
8173
}
8274

83-
function legendFormatter (data) {
84-
var html = ''
85-
if (data.x == null) {
86-
let dashLabels = data.series.reduce((nodes, series) => {
87-
return `${nodes} <div class="pr-2">${series.dashHTML} ${series.labelHTML}</div>`
88-
}, '')
89-
html = `<div class="d-flex flex-wrap justify-content-center align-items-center">
90-
<div class="pr-3">${this.getLabels()[0]}: N/A</div>
91-
<div class="d-flex flex-wrap">${dashLabels}</div>
92-
</div>`
93-
} else {
94-
var i = data.dygraph.getOption('legendIndex')
95-
var extraHTML = ''
96-
// The circulation chart has an additional legend entry showing percent
97-
// difference.
98-
if (data.series.length === 2 && data.series[0].label.toLowerCase().includes('coin supply')) {
99-
let inflation = data.dygraph.getOption('inflation')
100-
if (i < inflation.length) {
101-
let actual = data.series[0].y
102-
let predicted = inflation[i]
103-
let unminted = predicted - actual
104-
let change = ((unminted / predicted) * 100).toFixed(2)
105-
extraHTML = `<div class="pr-2">&nbsp;&nbsp;Unminted: ${intComma(unminted)} DCR (${change}%)</div>`
106-
}
107-
}
108-
109-
let yVals = data.series.reduce((nodes, series) => {
110-
if (!series.isVisible) return nodes
111-
let yVal = series.yHTML
112-
switch (series.label.toLowerCase()) {
113-
case 'ticket pool value':
114-
case 'inflation limit':
115-
case 'coin supply':
116-
yVal = intComma(series.y) + ' DCR'
117-
break
118-
119-
case 'total fee':
120-
case 'ticket price':
121-
yVal = series.y + ' DCR'
122-
break
123-
124-
case 'hashrate':
125-
yVal = formatHashRate(series.y)
126-
break
127-
128-
case 'stake participation':
129-
yVal = series.y.toFixed(4) + '%'
130-
break
131-
}
132-
let result = `${nodes} <div class="pr-2">${series.dashHTML} ${series.labelHTML}: ${yVal}</div>`
75+
function addLegendEntryFmt (div, series, fmt) {
76+
div.appendChild(legendEntry(`${series.dashHTML} ${series.labelHTML}: ${fmt(series.y)}`))
77+
}
13378

134-
if (series.label.toLowerCase() === 'stake participation' && rawCoinSupply.length === rawPoolValue.length &&
135-
rawPoolValue.length !== 0 && i !== null) {
136-
result += `<div class="pr-2"><div class="dygraph-legend-line"></div> Ticket Pool Value: ${intComma(rawPoolValue[i])} DCR</div>
137-
<div class="pr-2"><div class="dygraph-legend-line"></div> Coin Supply: ${intComma(rawCoinSupply[i])} DCR</div>`
138-
}
79+
function addLegendEntry (div, series) {
80+
div.appendChild(legendEntry(`${series.dashHTML} ${series.labelHTML}: ${series.yHTML}`))
81+
}
13982

140-
return result
141-
}, '')
83+
function defaultYFormatter (div, data) {
84+
addLegendEntry(div, data.series[0])
85+
}
14286

143-
html = `<div class="d-flex flex-wrap justify-content-center align-items-center">
144-
<div class="pr-3">${this.getLabels()[0]}: ${data.xHTML}</div>
145-
<div class="d-flex flex-wrap"> ${yVals}</div>
146-
</div>${extraHTML}`
147-
}
87+
function customYFormatter (fmt) {
88+
return (div, data) => addLegendEntryFmt(div, data.series[0], fmt)
89+
}
14890

149-
dompurify.sanitize(html, { FORBID_TAGS: ['svg', 'math'] })
150-
return html
91+
function legendFormatter (data) {
92+
if (data.x == null) return legendElement.classList.add('d-hide')
93+
legendElement.classList.remove('d-hide')
94+
var div = document.createElement('div')
95+
div.appendChild(legendEntry(`${data.dygraph.getLabels()[0]}: ${data.xHTML}`))
96+
yFormatter(div, data, data.dygraph.getOption('legendIndex'))
97+
dompurify.sanitize(div, { IN_PLACE: true, FORBID_TAGS: ['svg', 'math'] })
98+
return div.innerHTML
15199
}
152100

153101
function nightModeOptions (nightModeOn) {
@@ -349,7 +297,9 @@ export default class extends Controller {
349297
'ticketsPurchase',
350298
'ticketsPrice',
351299
'vSelector',
352-
'binSize'
300+
'binSize',
301+
'legendEntry',
302+
'legendMarker'
353303
]
354304
}
355305

@@ -364,6 +314,25 @@ export default class extends Controller {
364314
subsidyExponent = parseFloat(this.data.get('mulSubsidy')) / parseFloat(this.data.get('divSubsidy'))
365315
windowSize = parseInt(this.data.get('windowSize'))
366316
avgBlockTime = parseInt(this.data.get('blockTime')) * 1000
317+
legendElement = this.labelsTarget
318+
319+
// Prepare the legend element generators.
320+
var lm = this.legendMarkerTarget
321+
lm.remove()
322+
lm.removeAttribute('data-target')
323+
legendMarker = () => {
324+
let node = document.createElement('div')
325+
node.appendChild(lm.cloneNode())
326+
return node.innerHTML
327+
}
328+
var le = this.legendEntryTarget
329+
le.remove()
330+
le.removeAttribute('data-target')
331+
legendEntry = s => {
332+
let node = le.cloneNode()
333+
node.innerHTML = s
334+
return node
335+
}
367336

368337
this.settings = TurboQuery.nullTemplate(['chart', 'zoom', 'scale', 'bin', 'axis'])
369338
this.query.update(this.settings)
@@ -405,7 +374,7 @@ export default class extends Controller {
405374
pointSize: 0.25,
406375
legend: 'always',
407376
labelsSeparateLines: true,
408-
labelsDiv: this.labelsTarget,
377+
labelsDiv: legendElement,
409378
legendFormatter: legendFormatter,
410379
highlightCircleSize: 4,
411380
ylabel: 'Ticket Price',
@@ -449,6 +418,7 @@ export default class extends Controller {
449418
}
450419
rawPoolValue = []
451420
rawCoinSupply = []
421+
yFormatter = defaultYFormatter
452422
var xlabel = data.t ? 'Date' : 'Block Height'
453423

454424
switch (chartName) {
@@ -465,6 +435,7 @@ export default class extends Controller {
465435
valueRange: [0, windowSize * 20 * 8],
466436
axisLabelFormatter: (y) => Math.round(y)
467437
}
438+
yFormatter = customYFormatter(y => y.toFixed(8) + ' DCR')
468439
break
469440

470441
case 'ticket-pool-size': // pool size graph
@@ -479,18 +450,25 @@ export default class extends Controller {
479450
color: '#888'
480451
}
481452
}
453+
yFormatter = customYFormatter(y => `${intComma(y)} tickets &nbsp;&nbsp; (network target ${intComma(ticketPoolSizeTarget)})`)
482454
break
483455

484456
case 'stake-participation':
485457
d = percentStakedFunc(data)
486458
assign(gOptions, mapDygraphOptions(d, [xlabel, 'Stake Participation'], true,
487459
'Stake Participation (%)', true, false))
460+
yFormatter = (div, data, i) => {
461+
addLegendEntryFmt(div, data.series[0], y => y.toFixed(4) + '%')
462+
div.appendChild(legendEntry(`${legendMarker()} Ticket Pool Value: ${intComma(rawPoolValue[i])} DCR`))
463+
div.appendChild(legendEntry(`${legendMarker()} Coin Supply: ${intComma(rawCoinSupply[i])} DCR`))
464+
}
488465
break
489466

490467
case 'ticket-pool-value': // pool value graph
491468
d = zip2D(data, data.poolval, atomsToDCR)
492469
assign(gOptions, mapDygraphOptions(d, [xlabel, 'Ticket Pool Value'], true,
493470
'Ticket Pool Value (DCR)', true, false))
471+
yFormatter = customYFormatter(y => intComma(y) + ' DCR')
494472
break
495473

496474
case 'block-size': // block size graph
@@ -527,6 +505,16 @@ export default class extends Controller {
527505
}
528506
}
529507
gOptions.inflation = d.inflation
508+
yFormatter = (div, data, i) => {
509+
addLegendEntryFmt(div, data.series[0], y => intComma(y) + ' DCR')
510+
var change = 0
511+
if (i < d.inflation.length) {
512+
let predicted = d.inflation[i]
513+
let unminted = predicted - data.series[0].y
514+
change = ((unminted / predicted) * 100).toFixed(2)
515+
div.appendChild(legendEntry(`${legendMarker()} Unminted: ${intComma(unminted)} DCR (${change}%)`))
516+
}
517+
}
530518
break
531519

532520
case 'fees': // block fee graph
@@ -544,12 +532,14 @@ export default class extends Controller {
544532
d = zip2D(data, data.work)
545533
assign(gOptions, mapDygraphOptions(d, [xlabel, 'Cumulative Chainwork (exahash)'],
546534
false, 'Cumulative Chainwork (exahash)', true, false))
535+
yFormatter = customYFormatter(y => withBigUnits(y, chainworkUnits))
547536
break
548537

549538
case 'hashrate': // Total chainwork over time
550-
d = zip2D(data, data.rate, 1, data.offset)
551-
assign(gOptions, mapDygraphOptions(d, [xlabel, 'Network Hashrate (terahash/s)'],
552-
false, 'Network Hashrate (terahash/s)', true, false))
539+
d = zip2D(data, data.rate, 1e-3, data.offset)
540+
assign(gOptions, mapDygraphOptions(d, [xlabel, 'Network Hashrate (petahash/s)'],
541+
false, 'Network Hashrate (petahash/s)', true, false))
542+
yFormatter = customYFormatter(y => withBigUnits(y * 1e3, hashrateUnits))
553543
break
554544

555545
case 'missed-votes':

public/scss/charts.scss

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,28 @@
268268
transform: rotate(-90deg);
269269
}
270270

271+
.chartview {
272+
width: 96%;
273+
min-height: 73vh;
274+
margin: 0 2%;
275+
}
276+
277+
.chartview .dygraph-ylabel {
278+
color: #2970ff;
279+
}
280+
281+
body.darkBG .chartview .dygraph-ylabel {
282+
color: #2dd8a3;
283+
}
284+
285+
.chartview .dygraph-y2label {
286+
color: #066;
287+
}
288+
289+
body.darkBG .chartview .dygraph-y2label {
290+
color: #2970ff;
291+
}
292+
271293
#tickets_by_purchase_date .dygraph-ylabel {
272294
color: #2971ff;
273295
font-weight: bold;
@@ -349,7 +371,7 @@
349371
.legend {
350372
background: #ececec;
351373
z-index: 10000;
352-
padding: 0 2px 0 5px;
374+
padding: 5px 10px 5px 20px;
353375
font-size: 14px;
354376
}
355377

@@ -454,22 +476,6 @@ body.darkBG .customcheck input:checked ~ .tickets-bought {
454476
background-color: #2970ff;
455477
}
456478

457-
#chart .dygraph-ylabel {
458-
color: #2970ff;
459-
}
460-
461-
body.darkBG #chart .dygraph-ylabel {
462-
color: #2dd8a3;
463-
}
464-
465-
#chart .dygraph-y2label {
466-
color: #066;
467-
}
468-
469-
body.darkBG #chart .dygraph-y2label {
470-
color: #2970ff;
471-
}
472-
473479
.checkmark::after {
474480
content: "";
475481
position: absolute;

views/charts.tmpl

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -195,9 +195,9 @@
195195

196196

197197
<div data-target="charts.chartWrapper" class="chart-wrapper pl-2 pr-2 mb-5">
198-
<div id="chart"
199-
data-target="charts.chartsView"
200-
style="width:100%; height:73vh; margin:0 auto;">
198+
<div
199+
class="chartview"
200+
data-target="charts.chartsView">
201201
</div>
202202
<div class="d-flex flex-wrap justify-content-center align-items-center mb-1 mt-1">
203203
<div class="chart-control chart-control-wrapper">
@@ -231,7 +231,9 @@
231231
</div>
232232
</div>
233233
<div class="d-flex justify-content-center legend-wrapper">
234-
<div class="legend d-flex" data-target="charts.labels"></div>
234+
<div class="legend d-flex align-items-center" data-target="charts.labels">
235+
<div class="pr-3" data-target="charts.legendEntry"><div class="dygraph-legend-line" data-target="charts.legendMarker"></div></div>
236+
</div>
235237
</div>
236238
</div>
237239

0 commit comments

Comments
 (0)