Skip to content

Commit e07294e

Browse files
committed
Improvement - VueUiStackbar - Add orientation config option to display bars horizontally or vertically
1 parent 77721f1 commit e07294e

File tree

1 file changed

+221
-26
lines changed

1 file changed

+221
-26
lines changed

src/components/vue-ui-stackbar.vue

Lines changed: 221 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,12 @@ function refreshSlicer() {
238238
}
239239
240240
const barSlot = computed(() => {
241-
const bs = drawingArea.value.width / (slicer.value.end - slicer.value.start);
241+
let bs;
242+
if (FINAL_CONFIG.value.orientation === 'vertical') {
243+
bs = drawingArea.value.width / (slicer.value.end - slicer.value.start);
244+
} else {
245+
bs = drawingArea.value.height / (slicer.value.end - slicer.value.start);
246+
}
242247
return bs <= 0 ? 0 : bs;
243248
});
244249
@@ -285,6 +290,9 @@ const yLabels = computed(() => {
285290
zero: drawingArea.value.bottom - (drawingArea.value.height * ((Math.abs(scale.min)) / (scale.max + Math.abs(scale.min)))),
286291
y: drawingArea.value.bottom - (drawingArea.value.height * ((t + Math.abs(scale.min)) / ((scale.max) + Math.abs(scale.min)))),
287292
x: drawingArea.value.left - 8,
293+
horizontal_zero: drawingArea.value.left + (drawingArea.value.width * ((Math.abs(scale.min)) / (scale.max + Math.abs(scale.min)))),
294+
horizontal_x: drawingArea.value.left + (drawingArea.value.width * ((t + Math.abs(scale.min)) / ((scale.max) + Math.abs(scale.min)))),
295+
horizontal_y: drawingArea.value.bottom - 8,
288296
value: t
289297
}
290298
});
@@ -299,7 +307,9 @@ const formattedDataset = computed(() => {
299307
if (!isDataset.value) return [];
300308
301309
let cumulativeY = Array(maxSeries.value).fill(0);
310+
let cumulativeX = Array(maxSeries.value).fill(0);
302311
let cumulativeNegY = Array(maxSeries.value).fill(0);
312+
let cumulativeNegX = Array(maxSeries.value).fill(0);
303313
304314
const premax = Math.max(...datasetSignedTotals.value.positive) || 0;
305315
const workingMin = Math.min(...datasetSignedTotals.value.negative);
@@ -311,7 +321,9 @@ const formattedDataset = computed(() => {
311321
const maxTotal = (MAX + (MIN >= 0 ? 0 : Math.abs(MIN))) || 1
312322
313323
const totalHeight = drawingArea.value.height;
324+
const totalWidth = drawingArea.value.width;
314325
const ZERO_POSITION = yLabels.value[0] ? yLabels.value[0].zero : drawingArea.value.bottom;
326+
const HORIZONTAL_ZERO_POSITION = yLabels.value[0] ? yLabels.value[0].horizontal_zero : drawingArea.value.left;
315327
316328
return unmutableDataset.value
317329
.filter(ds => !segregated.value.includes(ds.id))
@@ -323,6 +335,10 @@ const formattedDataset = computed(() => {
323335
return drawingArea.value.left + (barSlot.value * i) + (barSlot.value * FINAL_CONFIG.value.style.chart.bars.gapRatio / 4);
324336
});
325337
338+
const horizontal_y = slicedSeries.map((_dp, i) => {
339+
return drawingArea.value.top + (barSlot.value * i) + (barSlot.value * FINAL_CONFIG.value.style.chart.bars.gapRatio / 4);
340+
})
341+
326342
const y = slicedSeries.map((dp, i) => {
327343
const proportion = FINAL_CONFIG.value.style.chart.bars.distributed
328344
? (dp || 0) / datasetTotals.value[i]
@@ -341,6 +357,24 @@ const formattedDataset = computed(() => {
341357
return currentY;
342358
});
343359
360+
const horizontal_x = slicedSeries.map((dp, i) => {
361+
const proportion = FINAL_CONFIG.value.style.chart.bars.distributed
362+
? (dp || 0) / datasetTotals.value[i]
363+
: (dp || 0) / maxTotal;
364+
365+
let currentX, hw;
366+
if (dp > 0) {
367+
hw = totalWidth * proportion;
368+
currentX = HORIZONTAL_ZERO_POSITION + cumulativeX[i];
369+
cumulativeX[i] += hw;
370+
} else {
371+
hw = totalWidth * proportion;
372+
currentX = HORIZONTAL_ZERO_POSITION - Math.abs(hw) - cumulativeNegX[i]
373+
cumulativeNegX[i] += Math.abs(hw)
374+
}
375+
return currentX;
376+
});
377+
344378
const height = slicedSeries.map((dp, i) => {
345379
const proportion = FINAL_CONFIG.value.style.chart.bars.distributed
346380
? (dp || 0) / datasetTotals.value[i]
@@ -353,6 +387,18 @@ const formattedDataset = computed(() => {
353387
}
354388
});
355389
390+
const horizontal_width = slicedSeries.map((dp, i) => {
391+
const proportion = FINAL_CONFIG.value.style.chart.bars.distributed
392+
? (dp || 0) / datasetTotals.value[i]
393+
: (dp || 0) / maxTotal;
394+
395+
if (dp > 0) {
396+
return totalWidth * proportion
397+
} else {
398+
return totalWidth * Math.abs(proportion)
399+
}
400+
});
401+
356402
const absoluteTotal = slicedSeries.map(v => Math.abs(v)).reduce((a, b) => a + b, 0);
357403
358404
return {
@@ -369,6 +415,9 @@ const formattedDataset = computed(() => {
369415
x,
370416
y,
371417
height,
418+
horizontal_width,
419+
horizontal_y,
420+
horizontal_x
372421
};
373422
});
374423
});
@@ -719,7 +768,8 @@ defineExpose({
719768
</linearGradient>
720769
</defs>
721770
722-
<template v-if="FINAL_CONFIG.style.chart.grid.x.showHorizontalLines">
771+
<!-- HORIZONTAL LINES (vertical mode) -->
772+
<template v-if="FINAL_CONFIG.style.chart.grid.x.showHorizontalLines && FINAL_CONFIG.orientation === 'vertical'">
723773
<line
724774
v-for="(yLabel, i) in yLabels"
725775
:x1="drawingArea.left"
@@ -728,10 +778,26 @@ defineExpose({
728778
:y2="yLabel.y"
729779
:stroke="FINAL_CONFIG.style.chart.grid.x.axisColor"
730780
:stroke-width="1"
781+
stroke-linecap="round"
782+
/>
783+
</template>
784+
785+
<!-- HORIZONTAL LINES (horizontal mode) -->
786+
<template v-if="FINAL_CONFIG.style.chart.grid.x.showHorizontalLines && FINAL_CONFIG.orientation === 'horizontal'">
787+
<line
788+
v-for="(_, i) in (slicer.end - slicer.start + 1)"
789+
:x1="drawingArea.left"
790+
:x2="drawingArea.right"
791+
:y1="drawingArea.top + (barSlot * i)"
792+
:y2="drawingArea.top + (barSlot * i)"
793+
:stroke="FINAL_CONFIG.style.chart.grid.y.axisColor"
794+
:stroke-width="1"
795+
stroke-linecap="round"
731796
/>
732797
</template>
733798
734-
<template v-if="FINAL_CONFIG.style.chart.grid.y.showVerticalLines">
799+
<!-- VERTICAL LINES (vertical mode) -->
800+
<template v-if="FINAL_CONFIG.style.chart.grid.y.showVerticalLines && FINAL_CONFIG.orientation === 'vertical'">
735801
<line
736802
v-for="(_, i) in (slicer.end - slicer.start + 1)"
737803
:x1="drawingArea.left + (barSlot * i)"
@@ -740,25 +806,60 @@ defineExpose({
740806
:y2="drawingArea.bottom"
741807
:stroke="FINAL_CONFIG.style.chart.grid.y.axisColor"
742808
:stroke-width="1"
809+
stroke-linecap="round"
743810
/>
744811
</template>
745812
746-
<!-- STACKED BARS -->
747-
<g v-for="(dp, i) in formattedDataset">
748-
<rect
749-
v-for="(rect, j) in dp.x"
750-
:x="rect"
751-
:y="dp.y[j] < 0 ? 0 : dp.y[j]"
752-
:height="dp.height[j] < 0 ? 0.0001 : dp.height[j]"
753-
:rx="FINAL_CONFIG.style.chart.bars.borderRadius > dp.height[j] / 2 ? (dp.height[j] < 0 ? 0 : dp.height[j]) / 2 : FINAL_CONFIG.style.chart.bars.borderRadius "
754-
:width="barSlot * (1 - FINAL_CONFIG.style.chart.bars.gapRatio / 2)"
755-
:fill="FINAL_CONFIG.style.chart.bars.gradient.show ? `url(#gradient_${dp.id})` : dp.color"
756-
:stroke="FINAL_CONFIG.style.chart.backgroundColor"
757-
:stroke-width="FINAL_CONFIG.style.chart.bars.strokeWidth"
813+
<!-- VERTICAL LINES (horizontal mode) -->
814+
<template v-if="FINAL_CONFIG.style.chart.grid.x.showHorizontalLines && FINAL_CONFIG.orientation === 'horizontal'">
815+
<line
816+
v-for="(yLabel, i) in yLabels"
817+
:x1="yLabel.horizontal_x"
818+
:x2="yLabel.horizontal_x"
819+
:y1="drawingArea.top"
820+
:y2="drawingArea.bottom"
821+
:stroke="FINAL_CONFIG.style.chart.grid.x.axisColor"
822+
:stroke-width="1"
758823
stroke-linecap="round"
759-
stroke-linejoin="round"
760-
:class="{ 'vue-data-ui-bar-animated': FINAL_CONFIG.useCssAnimation, 'vue-data-ui-bar-transition': isLoaded }"
761-
/>
824+
/>
825+
</template>
826+
827+
<g v-for="(dp, i) in formattedDataset">
828+
<!-- STACKED BARS (vertical mode) -->
829+
<template v-if="FINAL_CONFIG.orientation === 'vertical'">
830+
<rect
831+
v-for="(rect, j) in dp.x"
832+
:x="rect"
833+
:y="dp.y[j] < 0 ? 0 : dp.y[j]"
834+
:height="dp.height[j] < 0 ? 0.0001 : dp.height[j]"
835+
:rx="FINAL_CONFIG.style.chart.bars.borderRadius > dp.height[j] / 2 ? (dp.height[j] < 0 ? 0 : dp.height[j]) / 2 : FINAL_CONFIG.style.chart.bars.borderRadius "
836+
:width="barSlot * (1 - FINAL_CONFIG.style.chart.bars.gapRatio / 2)"
837+
:fill="FINAL_CONFIG.style.chart.bars.gradient.show ? `url(#gradient_${dp.id})` : dp.color"
838+
:stroke="FINAL_CONFIG.style.chart.backgroundColor"
839+
:stroke-width="FINAL_CONFIG.style.chart.bars.strokeWidth"
840+
stroke-linecap="round"
841+
stroke-linejoin="round"
842+
:class="{ 'vue-data-ui-bar-animated': FINAL_CONFIG.useCssAnimation, 'vue-data-ui-bar-transition': isLoaded }"
843+
/>
844+
</template>
845+
846+
<!-- STACKED BARS (horizontal mode) -->
847+
<template v-else>
848+
<rect
849+
v-for="(rect, j) in dp.horizontal_x"
850+
:x="rect"
851+
:y="dp.horizontal_y[j] < 0 ? 0 : dp.horizontal_y[j]"
852+
:width="dp.horizontal_width[j] < 0 ? 0.0001 : dp.horizontal_width[j]"
853+
:rx="FINAL_CONFIG.style.chart.bars.borderRadius > dp.height[j] / 2 ? (dp.height[j] < 0 ? 0 : dp.height[j]) / 2 : FINAL_CONFIG.style.chart.bars.borderRadius "
854+
:height="barSlot * (1 - FINAL_CONFIG.style.chart.bars.gapRatio / 2)"
855+
:fill="FINAL_CONFIG.style.chart.bars.gradient.show ? `url(#gradient_${dp.id})` : dp.color"
856+
:stroke="FINAL_CONFIG.style.chart.backgroundColor"
857+
:stroke-width="FINAL_CONFIG.style.chart.bars.strokeWidth"
858+
stroke-linecap="round"
859+
stroke-linejoin="round"
860+
:class="{ 'vue-data-ui-bar-animated': FINAL_CONFIG.useCssAnimation, 'vue-data-ui-bar-transition': isLoaded }"
861+
/>
862+
</template>
762863
</g>
763864
764865
<!-- X AXIS -->
@@ -812,7 +913,8 @@ defineExpose({
812913
{{ FINAL_CONFIG.style.chart.grid.y.axisName.text }}
813914
</text>
814915
815-
<template v-if="mutableConfig.dataLabels.show">
916+
<!-- RECT DATA LABELS (vertical mode) -->
917+
<template v-if="mutableConfig.dataLabels.show && FINAL_CONFIG.orientation === 'vertical'">
816918
<g v-for="(dp, i) in formattedDataset">
817919
<!-- RECT LABELS -->
818920
<text
@@ -830,7 +932,7 @@ defineExpose({
830932
</text>
831933
</g>
832934
833-
<!-- RECT TOTAL LABEL -->
935+
<!-- RECT TOTAL LABELS -->
834936
<g v-if="FINAL_CONFIG.style.chart.bars.totalValues.show && formattedDataset.length > 1">
835937
<text
836938
v-for="(total, i) in totalLabels"
@@ -846,8 +948,42 @@ defineExpose({
846948
</g>
847949
</template>
848950
849-
<!-- Y LABELS -->
850-
<template v-if="FINAL_CONFIG.style.chart.grid.y.axisLabels.show && !FINAL_CONFIG.style.chart.bars.distributed">
951+
<!-- RECT DATA LABELS (horizontal mode) -->
952+
<template v-if="mutableConfig.dataLabels.show && FINAL_CONFIG.orientation === 'horizontal'">
953+
<g v-for="(dp, i) in formattedDataset">
954+
<!-- RECT LABELS -->
955+
<text
956+
v-for="(rect, j) in dp.horizontal_x"
957+
:x="rect + ((dp.horizontal_width[j] < 0 ? 0.0001 : dp.horizontal_width[j]) / 2)"
958+
:y="dp.horizontal_y[j] + (barSlot * (1 - FINAL_CONFIG.style.chart.bars.gapRatio / 2) / 2) + (FINAL_CONFIG.style.chart.bars.dataLabels.fontSize /3)"
959+
:font-size="FINAL_CONFIG.style.chart.bars.dataLabels.fontSize"
960+
:fill="FINAL_CONFIG.style.chart.bars.dataLabels.adaptColorToBackground ? adaptColorToBackground(dp.color) : FINAL_CONFIG.style.chart.bars.dataLabels.color"
961+
:font-weight="FINAL_CONFIG.style.chart.bars.dataLabels.bold ? 'bold' : 'normal'"
962+
text-anchor="middle"
963+
>
964+
{{ FINAL_CONFIG.style.chart.bars.showDistributedPercentage && FINAL_CONFIG.style.chart.bars.distributed ?
965+
barDataLabelPercentage(dp.proportions[j] * 100, dp, i, j) :
966+
barDataLabel(dp.series[j], dp, i, j, dp.signedSeries[j]) }}
967+
</text>
968+
</g>
969+
<!-- RECT TOTAL LABELS -->
970+
<g v-if="FINAL_CONFIG.style.chart.bars.totalValues.show && formattedDataset.length > 1">
971+
<text
972+
v-for="(total, i) in totalLabels"
973+
:x="drawingArea.right + FINAL_CONFIG.style.chart.bars.totalValues.fontSize / 3"
974+
:y="drawingArea.top + (barSlot * i) + barSlot / 2"
975+
text-anchor="start"
976+
:font-size="FINAL_CONFIG.style.chart.bars.totalValues.fontSize"
977+
:font-weight="FINAL_CONFIG.style.chart.bars.totalValues.bold ? 'bold' : 'normal'"
978+
:fill="FINAL_CONFIG.style.chart.bars.totalValues.color"
979+
>
980+
{{ barDataLabel(total.value, total, i, total.sign) }}
981+
</text>
982+
</g>
983+
</template>
984+
985+
<!-- SCALE LABELS (vertical mode) -->
986+
<template v-if="FINAL_CONFIG.style.chart.grid.y.axisLabels.show && !FINAL_CONFIG.style.chart.bars.distributed && FINAL_CONFIG.orientation === 'vertical'">
851987
<line
852988
v-for="(yLabel, i) in yLabels"
853989
:x1="drawingArea.left"
@@ -875,8 +1011,37 @@ defineExpose({
8751011
</text>
8761012
</template>
8771013
878-
<!-- TIME LABELS-->
879-
<template v-if="FINAL_CONFIG.style.chart.grid.x.timeLabels.show">
1014+
<!-- SCALE LABELS (horizontal mode) -->
1015+
<template v-if="FINAL_CONFIG.style.chart.grid.y.axisLabels.show && !FINAL_CONFIG.style.chart.bars.distributed && FINAL_CONFIG.orientation === 'horizontal'">
1016+
<line
1017+
v-for="(yLabel, i) in yLabels"
1018+
:x1="yLabel.horizontal_x"
1019+
:x2="yLabel.horizontal_x"
1020+
:y1="drawingArea.bottom"
1021+
:y2="drawingArea.bottom + 6"
1022+
:stroke="FINAL_CONFIG.style.chart.grid.x.axisColor"
1023+
:stroke-width="1"
1024+
stroke-linecap="round"
1025+
/>
1026+
<text
1027+
v-for="(yLabel, i) in yLabels"
1028+
:font-size="FINAL_CONFIG.style.chart.grid.x.timeLabels.fontSize"
1029+
:font-weight="FINAL_CONFIG.style.chart.grid.y.axisLabels.bold ? 'bold' : 'normal'"
1030+
:fill="FINAL_CONFIG.style.chart.grid.y.axisLabels.color"
1031+
:text-anchor="FINAL_CONFIG.style.chart.grid.x.timeLabels.rotation > 0 ? 'start' : FINAL_CONFIG.style.chart.grid.x.timeLabels.rotation < 0 ? 'end' : 'middle'"
1032+
:transform="`translate(${yLabel.horizontal_x}, ${drawingArea.bottom + FINAL_CONFIG.style.chart.grid.x.timeLabels.fontSize * 1.3 + FINAL_CONFIG.style.chart.grid.x.timeLabels.offsetY}), rotate(${FINAL_CONFIG.style.chart.grid.x.timeLabels.rotation})`"
1033+
>
1034+
{{ dataLabel({
1035+
p: FINAL_CONFIG.style.chart.bars.dataLabels.prefix,
1036+
v: yLabel.value,
1037+
s: FINAL_CONFIG.style.chart.bars.dataLabels.suffix,
1038+
r: FINAL_CONFIG.style.chart.grid.y.axisLabels.rounding,
1039+
}) }}
1040+
</text>
1041+
</template>
1042+
1043+
<!-- TIME LABELS VERTICAL-->
1044+
<template v-if="FINAL_CONFIG.style.chart.grid.x.timeLabels.show && FINAL_CONFIG.orientation === 'vertical'">
8801045
<text
8811046
v-for="(timeLabel, i) in timeLabels"
8821047
:text-anchor="FINAL_CONFIG.style.chart.grid.x.timeLabels.rotation > 0 ? 'start' : FINAL_CONFIG.style.chart.grid.x.timeLabels.rotation < 0 ? 'end' : 'middle'"
@@ -889,8 +1054,23 @@ defineExpose({
8891054
</text>
8901055
</template>
8911056
892-
<!-- TOOLTIP TRAPS-->
893-
<template v-if="mutableConfig.showTooltip">
1057+
<!-- TIME LABELS HORIZONTAL -->
1058+
<template v-if="FINAL_CONFIG.style.chart.grid.x.timeLabels.show && FINAL_CONFIG.orientation === 'horizontal'">
1059+
<text
1060+
v-for="(timeLabel, i) in timeLabels"
1061+
text-anchor="end"
1062+
:font-size="FINAL_CONFIG.style.chart.grid.y.axisLabels.fontSize"
1063+
:font-weight="FINAL_CONFIG.style.chart.grid.y.axisLabels.bold ? 'bold' : 'normal'"
1064+
:fill="FINAL_CONFIG.style.chart.grid.y.axisLabels.color"
1065+
:x="drawingArea.left - 8"
1066+
:y="drawingArea.top + (barSlot * i ) + (barSlot / 2) + FINAL_CONFIG.style.chart.grid.y.axisLabels.fontSize / 3"
1067+
>
1068+
{{ timeLabel }}
1069+
</text>
1070+
</template>
1071+
1072+
<!-- TOOLTIP TRAPS (vertical mode) -->
1073+
<template v-if="mutableConfig.showTooltip && FINAL_CONFIG.orientation === 'vertical'">
8941074
<rect
8951075
v-for="(_, i) in (slicer.end - slicer.start)"
8961076
:x="drawingArea.left + (i * barSlot)"
@@ -904,6 +1084,21 @@ defineExpose({
9041084
/>
9051085
</template>
9061086
1087+
<!-- TOOLTIP TRAPS (vertical mode) -->
1088+
<template v-if="mutableConfig.showTooltip && FINAL_CONFIG.orientation === 'horizontal'">
1089+
<rect
1090+
v-for="(_, i) in (slicer.end - slicer.start)"
1091+
:x="drawingArea.left"
1092+
:y="drawingArea.top + (i * barSlot)"
1093+
:width="drawingArea.width < 0 ? 0 : drawingArea.width"
1094+
:height="barSlot"
1095+
@click="selectDatapoint(i)"
1096+
@mouseenter="useTooltip(i)"
1097+
@mouseleave="trapIndex = null; isTooltip = false"
1098+
:fill="i === trapIndex ? FINAL_CONFIG.style.chart.highlighter.color + opacity[FINAL_CONFIG.style.chart.highlighter.opacity] : 'transparent'"
1099+
/>
1100+
</template>
1101+
9071102
<slot name="svg" v-bind="{ ...drawingArea }"/>
9081103
</svg>
9091104

0 commit comments

Comments
 (0)