Skip to content

Commit 88f01f8

Browse files
committed
Improvement - VueUiHeatmap - Add row and col totals with colors
1 parent 834e00f commit 88f01f8

File tree

2 files changed

+141
-9
lines changed

2 files changed

+141
-9
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,4 @@
107107
"vitest": "^3.1.1",
108108
"vue": "^3.5.13"
109109
}
110-
}
110+
}

src/components/vue-ui-heatmap.vue

+140-8
Original file line numberDiff line numberDiff line change
@@ -224,8 +224,37 @@ const cellSize = computed(() => {
224224
225225
const dataLabels = computed(() => {
226226
const yLabels = FINAL_CONFIG.value.style.layout.dataLabels.yAxis.values.length ? FINAL_CONFIG.value.style.layout.dataLabels.yAxis.values : props.dataset.map(ds => ds.name);
227-
const xLabels = FINAL_CONFIG.value.style.layout.dataLabels.xAxis.values
227+
const xLabels = FINAL_CONFIG.value.style.layout.dataLabels.xAxis.values;
228+
const _yTotals = props.dataset.map(ds => ds.values.reduce((a, b) => a + b, 0))
229+
const maxYTotal = Math.max(..._yTotals);
230+
const minYTotal = Math.min(..._yTotals);
231+
232+
const _xTotals = []
233+
234+
for (let i = 0; i < maxX.value; i += 1) {
235+
_xTotals.push(props.dataset.map(ds => ds.values[i] || 0).reduce((a, b) => a + b, 0))
236+
}
237+
238+
const maxXTotal = Math.max(..._xTotals);
239+
const minXTotal = Math.min(..._xTotals);
240+
228241
return {
242+
yTotals: _yTotals.map(rowTotal => {
243+
const proportion = isNaN(rowTotal / maxYTotal) ? 0 : rowTotal / maxYTotal;
244+
return {
245+
total: rowTotal,
246+
proportion,
247+
color: interpolateColorHex(FINAL_CONFIG.value.style.layout.cells.colors.cold, FINAL_CONFIG.value.style.layout.cells.colors.hot, minYTotal, maxYTotal, rowTotal)
248+
}
249+
}),
250+
xTotals: _xTotals.map(columnTotal => {
251+
const proportion = isNaN(columnTotal / maxXTotal) ? 0 : columnTotal / maxXTotal;
252+
return {
253+
total: columnTotal,
254+
proportion,
255+
color: interpolateColorHex(FINAL_CONFIG.value.style.layout.cells.colors.cold, FINAL_CONFIG.value.style.layout.cells.colors.hot, minXTotal, maxXTotal, columnTotal)
256+
}
257+
}),
229258
yLabels,
230259
xLabels: xLabels.slice(0, maxX.value)
231260
}
@@ -334,6 +363,34 @@ const bottomLegendIndicatorX = computed(() => {
334363
return drawingArea.value.left + ((svg.value.width - drawingArea.value.left - FINAL_CONFIG.value.style.layout.padding.right) * (hoveredValue.value / maxValue.value))
335364
});
336365
366+
function getRowTotal(index) {
367+
return applyDataLabel(
368+
FINAL_CONFIG.value.style.layout.cells.value.formatter,
369+
dataLabels.value.yTotals[index].total,
370+
dataLabel({
371+
p: FINAL_CONFIG.value.style.layout.dataLabels.prefix,
372+
v: dataLabels.value.yTotals[index].total,
373+
s: FINAL_CONFIG.value.style.layout.dataLabels.suffix,
374+
r: FINAL_CONFIG.value.style.layout.cells.value.roundingValue
375+
}),
376+
{ datapoint: dataLabels.value.yTotals[index], rowIndex: index }
377+
)
378+
}
379+
380+
function getcolumnTotal(index) {
381+
return applyDataLabel(
382+
FINAL_CONFIG.value.style.layout.cells.value.formatter,
383+
dataLabels.value.xTotals[index].total,
384+
dataLabel({
385+
p: FINAL_CONFIG.value.style.layout.dataLabels.prefix,
386+
v: dataLabels.value.xTotals[index].total,
387+
s: FINAL_CONFIG.value.style.layout.dataLabels.suffix,
388+
r: FINAL_CONFIG.value.style.layout.cells.value.roundingValue
389+
}),
390+
{ datapoint: dataLabels.value.xTotals[index], colIndex: index }
391+
)
392+
}
393+
337394
const table = computed(() => {
338395
const head = props.dataset.map(ds => {
339396
return {
@@ -387,6 +444,7 @@ function toggleAnnotator() {
387444
isAnnotator.value = !isAnnotator.value;
388445
}
389446
447+
390448
defineExpose({
391449
generatePdf,
392450
generateCsv,
@@ -516,11 +574,34 @@ defineExpose({
516574
<slot name="chart-background"/>
517575
</foreignObject>
518576

577+
<template v-if="FINAL_CONFIG.style.layout.cells.columnTotal.color.show">
578+
<rect
579+
v-for="(col, i) in dataLabels.xTotals"
580+
:x="drawingArea.left + cellSize.width * i + (FINAL_CONFIG.style.layout.cells.spacing / 2) + (FINAL_CONFIG.style.layout.cells.rowTotal.color.show ? (cellSize.height / 3 + FINAL_CONFIG.style.layout.cells.spacing) : 0)"
581+
:y="drawingArea.top - cellSize.height / 3 - FINAL_CONFIG.style.layout.cells.spacing"
582+
:height="cellSize.height / 3"
583+
:width="cellSize.width - FINAL_CONFIG.style.layout.cells.spacing"
584+
:fill="FINAL_CONFIG.style.layout.cells.colors.underlayer"
585+
:stroke="FINAL_CONFIG.style.backgroundColor"
586+
:stroke-width="FINAL_CONFIG.style.layout.cells.spacing"
587+
/>
588+
<rect
589+
v-for="(col, i) in dataLabels.xTotals"
590+
:x="drawingArea.left + cellSize.width * i + (FINAL_CONFIG.style.layout.cells.spacing / 2) + (FINAL_CONFIG.style.layout.cells.rowTotal.color.show ? (cellSize.height / 3 + FINAL_CONFIG.style.layout.cells.spacing) : 0)"
591+
:y="drawingArea.top - cellSize.height / 3 - FINAL_CONFIG.style.layout.cells.spacing"
592+
:height="cellSize.height / 3"
593+
:width="cellSize.width - FINAL_CONFIG.style.layout.cells.spacing"
594+
:fill="col.color"
595+
:stroke="FINAL_CONFIG.style.backgroundColor"
596+
:stroke-width="FINAL_CONFIG.style.layout.cells.spacing"
597+
/>
598+
</template>
599+
519600
<g v-for="(serie, i) in mutableDataset">
520601
<g v-for="(cell, j) in serie.temperatures">
521602
<rect
522603
data-cy="cell-underlayer"
523-
:x="drawingArea.left + cellSize.width * j + (FINAL_CONFIG.style.layout.cells.spacing / 2)"
604+
:x="drawingArea.left + cellSize.width * j + (FINAL_CONFIG.style.layout.cells.spacing / 2) + (FINAL_CONFIG.style.layout.cells.rowTotal.color.show ? (cellSize.height / 3 + FINAL_CONFIG.style.layout.cells.spacing) : 0)"
524605
:y="drawingArea.top + cellSize.height * i + (FINAL_CONFIG.style.layout.cells.spacing / 2)"
525606
:width="cellSize.width - FINAL_CONFIG.style.layout.cells.spacing"
526607
:height="cellSize.height - FINAL_CONFIG.style.layout.cells.spacing"
@@ -530,7 +611,7 @@ defineExpose({
530611
/>
531612
<rect
532613
data-cy="cell"
533-
:x="drawingArea.left + cellSize.width * j + (FINAL_CONFIG.style.layout.cells.spacing / 2)"
614+
:x="drawingArea.left + cellSize.width * j + (FINAL_CONFIG.style.layout.cells.spacing / 2) + (FINAL_CONFIG.style.layout.cells.rowTotal.color.show ? (cellSize.height / 3 + FINAL_CONFIG.style.layout.cells.spacing) : 0)"
534615
:y="drawingArea.top + cellSize.height * i + (FINAL_CONFIG.style.layout.cells.spacing / 2)"
535616
:width="cellSize.width - FINAL_CONFIG.style.layout.cells.spacing"
536617
:height="cellSize.height - FINAL_CONFIG.style.layout.cells.spacing"
@@ -545,7 +626,7 @@ defineExpose({
545626
:font-size="FINAL_CONFIG.style.layout.cells.value.fontSize"
546627
:font-weight="FINAL_CONFIG.style.layout.cells.value.bold ? 'bold': 'normal'"
547628
:fill="adaptColorToBackground(cell.color)"
548-
:x="(drawingArea.left + cellSize.width * j) + (cellSize.width / 2)"
629+
:x="(drawingArea.left + cellSize.width * j) + (cellSize.width / 2) + + (FINAL_CONFIG.style.layout.cells.rowTotal.color.show ? (cellSize.height / 3 + FINAL_CONFIG.style.layout.cells.spacing) : 0)"
549630
:y="(drawingArea.top + cellSize.height * i) + (cellSize.height / 2) + FINAL_CONFIG.style.layout.cells.value.fontSize / 3"
550631
>
551632
{{ applyDataLabel(
@@ -566,7 +647,7 @@ defineExpose({
566647
<!-- TOOLTIP TRAPS -->
567648
<rect
568649
data-cy="tooltip-trap"
569-
:x="drawingArea.left + cellSize.width * j"
650+
:x="drawingArea.left + cellSize.width * j + (FINAL_CONFIG.style.layout.cells.rowTotal.color.show ? (cellSize.height / 3 + FINAL_CONFIG.style.layout.cells.spacing) : 0)"
570651
:y="drawingArea.top + cellSize.height * i"
571652
:width="cellSize.width"
572653
:height="cellSize.height"
@@ -579,39 +660,90 @@ defineExpose({
579660
<g v-if="FINAL_CONFIG.style.layout.dataLabels.yAxis.show">
580661
<text
581662
data-cy="axis-y-label"
663+
class="vue-ui-heatmap-row-name"
582664
:font-size="FINAL_CONFIG.style.layout.dataLabels.yAxis.fontSize"
583665
:fill="FINAL_CONFIG.style.layout.dataLabels.yAxis.color"
584666
:x="drawingArea.left + FINAL_CONFIG.style.layout.dataLabels.yAxis.offsetX - 6"
585-
:y="drawingArea.top + (cellSize.height * i) + cellSize.height / 2 + FINAL_CONFIG.style.layout.dataLabels.yAxis.fontSize / 3 + FINAL_CONFIG.style.layout.dataLabels.yAxis.offsetY"
667+
:y="drawingArea.top + (cellSize.height * i) + cellSize.height / 2 + FINAL_CONFIG.style.layout.dataLabels.yAxis.fontSize / 3 + FINAL_CONFIG.style.layout.dataLabels.yAxis.offsetY - (FINAL_CONFIG.style.layout.cells.rowTotal.value.show ? FINAL_CONFIG.style.layout.dataLabels.yAxis.fontSize / 1.5 : 0)"
586668
text-anchor="end"
587669
:font-weight="FINAL_CONFIG.style.layout.dataLabels.yAxis.bold ? 'bold' : 'normal'"
588670
>
589671
{{ dataLabels.yLabels[i] }}
590672
</text>
673+
<text
674+
class="vue-ui-heatmap-row-total"
675+
v-if="FINAL_CONFIG.style.layout.cells.rowTotal.value.show"
676+
data-cy="axis-y-label"
677+
:font-size="FINAL_CONFIG.style.layout.dataLabels.yAxis.fontSize"
678+
:fill="FINAL_CONFIG.style.layout.dataLabels.yAxis.color"
679+
:x="drawingArea.left + FINAL_CONFIG.style.layout.dataLabels.yAxis.offsetX - 6"
680+
:y="drawingArea.top + (cellSize.height * i) + cellSize.height / 2 + FINAL_CONFIG.style.layout.dataLabels.yAxis.fontSize + FINAL_CONFIG.style.layout.dataLabels.yAxis.offsetY"
681+
text-anchor="end"
682+
:font-weight="FINAL_CONFIG.style.layout.dataLabels.yAxis.bold ? 'bold' : 'normal'"
683+
>
684+
{{ getRowTotal(i) }}
685+
</text>
591686
</g>
687+
688+
<g v-if="FINAL_CONFIG.style.layout.cells.rowTotal.color.show">
689+
<rect
690+
:x="drawingArea.left"
691+
:y="drawingArea.top + (cellSize.height * i)"
692+
:width="cellSize.height / 3"
693+
:height="cellSize.height - FINAL_CONFIG.style.layout.cells.spacing"
694+
:fill="FINAL_CONFIG.style.layout.cells.colors.underlayer"
695+
:stroke="FINAL_CONFIG.style.backgroundColor"
696+
:stroke-width="FINAL_CONFIG.style.layout.cells.spacing"
697+
/>
698+
<rect
699+
:x="drawingArea.left"
700+
:y="drawingArea.top + (cellSize.height * i) + FINAL_CONFIG.style.layout.cells.spacing / 2"
701+
:width="cellSize.height / 3"
702+
:height="cellSize.height - FINAL_CONFIG.style.layout.cells.spacing"
703+
:fill="dataLabels.yTotals[i].color"
704+
:stroke="FINAL_CONFIG.style.backgroundColor"
705+
:stroke-width="FINAL_CONFIG.style.layout.cells.spacing"
706+
/>
707+
</g>
708+
592709
</g>
593710
<g v-if="FINAL_CONFIG.style.layout.dataLabels.xAxis.show">
594711
<template v-for="(label, i) in dataLabels.xLabels">
595712
<text
713+
class="vue-ui-heatmap-col-name"
596714
data-cy="axis-x-label"
597715
v-if="!FINAL_CONFIG.style.layout.dataLabels.xAxis.showOnlyAtModulo || (FINAL_CONFIG.style.layout.dataLabels.xAxis.showOnlyAtModulo && i % FINAL_CONFIG.style.layout.dataLabels.xAxis.showOnlyAtModulo === 0)"
598716
:text-anchor="FINAL_CONFIG.style.layout.dataLabels.xAxis.rotation === 0 ? 'middle' : FINAL_CONFIG.style.layout.dataLabels.xAxis.rotation < 0 ? 'start' : 'end'"
599717
:font-size="FINAL_CONFIG.style.layout.dataLabels.xAxis.fontSize"
600718
:fill="FINAL_CONFIG.style.layout.dataLabels.xAxis.color"
601719
:font-weight="FINAL_CONFIG.style.layout.dataLabels.xAxis.bold ? 'bold' : 'normal'"
602-
:transform="`translate(${drawingArea.left + cellSize.width / 2 + (drawingArea.width / dataLabels.xLabels.length * i) + FINAL_CONFIG.style.layout.dataLabels.xAxis.offsetX}, ${drawingArea.top + FINAL_CONFIG.style.layout.dataLabels.xAxis.offsetY - 6}), rotate(${FINAL_CONFIG.style.layout.dataLabels.xAxis.rotation})`"
720+
:transform="`translate(${drawingArea.left + cellSize.width / 2 + (drawingArea.width / dataLabels.xLabels.length * i) + FINAL_CONFIG.style.layout.dataLabels.xAxis.offsetX + (FINAL_CONFIG.style.layout.cells.rowTotal.color.show ? (cellSize.height / 3 + FINAL_CONFIG.style.layout.cells.spacing) : 0)}, ${drawingArea.top + FINAL_CONFIG.style.layout.dataLabels.xAxis.offsetY - 6 - (FINAL_CONFIG.style.layout.cells.columnTotal.color.show ? (cellSize.height / 3 + FINAL_CONFIG.style.layout.cells.spacing) : 0)}), rotate(${FINAL_CONFIG.style.layout.dataLabels.xAxis.rotation})`"
603721
>
604722
{{ label }}
605723
</text>
606724
</template>
607725
</g>
608726

727+
<template v-if="FINAL_CONFIG.style.layout.cells.columnTotal.value.show">
728+
<text
729+
class="vue-ui-heatmap-col-total"
730+
v-for="(_, i) in dataLabels.xLabels"
731+
:text-anchor="FINAL_CONFIG.style.layout.cells.columnTotal.value.rotation === 0 ? 'middle' : FINAL_CONFIG.style.layout.cells.columnTotal.value.rotation < 0 ? 'end' : 'start'"
732+
:font-size="FINAL_CONFIG.style.layout.dataLabels.xAxis.fontSize"
733+
:fill="FINAL_CONFIG.style.layout.dataLabels.xAxis.color"
734+
:font-weight="FINAL_CONFIG.style.layout.dataLabels.xAxis.bold ? 'bold' : 'normal'"
735+
:transform="`translate(${drawingArea.left + cellSize.width / 2 + (drawingArea.width / dataLabels.xLabels.length * i) + FINAL_CONFIG.style.layout.dataLabels.xAxis.offsetX + FINAL_CONFIG.style.layout.cells.columnTotal.value.offsetX + (FINAL_CONFIG.style.layout.cells.rowTotal.color.show ? (cellSize.height / 3 + FINAL_CONFIG.style.layout.cells.spacing) : 0)}, ${drawingArea.bottom - FINAL_CONFIG.style.layout.dataLabels.xAxis.fontSize + FINAL_CONFIG.style.layout.cells.columnTotal.value.offsetY}), rotate(${FINAL_CONFIG.style.layout.cells.columnTotal.value.rotation})`"
736+
>
737+
{{ getcolumnTotal(i) }}
738+
</text>
739+
</template>
740+
609741
<!-- BORDER FOR SELECTED RECT, PAINTED LAST -->
610742
<g v-if="selectedClone">
611743
<rect
612744
data-cy="cell-selected"
613745
style="pointer-events: none;"
614-
:x="selectedClone.x - ((FINAL_CONFIG.style.layout.cells.selected.border) / 2) + FINAL_CONFIG.style.layout.cells.spacing"
746+
:x="selectedClone.x - ((FINAL_CONFIG.style.layout.cells.selected.border) / 2) + FINAL_CONFIG.style.layout.cells.spacing + (FINAL_CONFIG.style.layout.cells.rowTotal.color.show ? (cellSize.height / 3 + FINAL_CONFIG.style.layout.cells.spacing) : 0)"
615747
:y="selectedClone.y - (FINAL_CONFIG.style.layout.cells.selected.border / 2) + FINAL_CONFIG.style.layout.cells.spacing"
616748
:width="cellSize.width - FINAL_CONFIG.style.layout.cells.spacing + FINAL_CONFIG.style.layout.cells.selected.border - (FINAL_CONFIG.style.layout.cells.spacing)"
617749
:height="cellSize.height - FINAL_CONFIG.style.layout.cells.spacing + FINAL_CONFIG.style.layout.cells.selected.border - (FINAL_CONFIG.style.layout.cells.spacing)"

0 commit comments

Comments
 (0)