@@ -498,19 +498,55 @@ const updateOffsetRight = throttle((w) => {
498498 offsetRight .value = w + FINAL_CONFIG .value .style .chart .bars .totalValues .fontSize
499499}, 100 );
500500
501+
502+ function computeRightOverhang () {
503+ if (FINAL_CONFIG .value .orientation !== ' horizontal' ) return 0 ;
504+
505+ const group = sumRight .value ;
506+ if (! group) return 0 ;
507+
508+ const texts = Array .from (group .querySelectorAll (' text' ));
509+ if (! texts .length ) return 0 ;
510+
511+ let maxRight = - Infinity ;
512+ for (const t of texts) {
513+ try {
514+ const box = t .getBBox ();
515+ const right = box .x + box .width ;
516+ if (right > maxRight) maxRight = right;
517+ } catch (_) {
518+ //
519+ }
520+ }
521+
522+ const overhang = Math .max (0 , maxRight - (drawingArea .value ? .right ?? 0 ));
523+ return overhang;
524+ }
525+
501526watchEffect ((onInvalidate ) => {
502- if (FINAL_CONFIG .value .orientation === ' vertical ' ) return ;
527+ if (FINAL_CONFIG .value .orientation !== ' horizontal ' ) return ;
503528
504529 const el = sumRight .value ;
505- if (! el) return
530+ if (! el) return ;
506531
507- const observer = new ResizeObserver (entries => {
508- updateOffsetRight (entries[0 ].contentRect .width )
509- })
510- observer .observe (el)
511- onInvalidate (() => observer .disconnect ())
532+ const onChange = () => {
533+ const overhang = computeRightOverhang ();
534+ updateOffsetRight (overhang);
535+ };
536+
537+ onChange ();
538+ const resizeObserver = new ResizeObserver (onChange);
539+ resizeObserver .observe (el);
540+ const mutationObserver = new MutationObserver (onChange);
541+ mutationObserver .observe (el, { childList: true , subtree: true , characterData: true });
542+
543+ onInvalidate (() => {
544+ resizeObserver .disconnect ();
545+ mutationObserver .disconnect ();
546+ });
512547});
513548
549+
514550onBeforeUnmount (() => {
515551 labelsXHeight .value = 0 ;
516552 offsetRight .value = 0 ;
@@ -1182,6 +1218,7 @@ function useTooltip(seriesIndex) {
11821218 }
11831219
11841220 const sum = datapoint .map (d => Math .abs (d .value )).reduce ((a , b ) => a + b, 0 );
1221+ const sumLabel = datapoint .map (d => forceValidValue (d .value )).reduce ((a , b ) => a + b, 0 );
11851222
11861223 if (isFunction (customFormat) && functionReturnsString (() => customFormat ({
11871224 seriesIndex,
@@ -1198,6 +1235,8 @@ function useTooltip(seriesIndex) {
11981235 } else {
11991236 const {
12001237 showValue,
1238+ showTotal,
1239+ totalTranslation,
12011240 showPercentage,
12021241 borderColor,
12031242 roundingValue,
@@ -1210,6 +1249,25 @@ function useTooltip(seriesIndex) {
12101249 html += ` <div style="width:100%;text-align:center;border-bottom:1px solid ${ borderColor} ;padding-bottom:6px;margin-bottom:3px;">${ FINAL_CONFIG .value .style .chart .tooltip .useDefaultTimeFormat ? timeLabels .value [seriesIndex]? .text : preciseAllTimeLabelsTooltip .value [seriesIndex]? .text || allTimeLabels .value [seriesIndex]? .text || ' ' }< / div> ` ;
12111250 }
12121251
1252+ if (showTotal) {
1253+ html += ` < div class = " vue-data-ui-tooltip-total" style= " display:flex;flex-direction:row;align-items:center;gap:4px" >
1254+ < span> ${totalTranslation}: < / span>
1255+ < span>
1256+ ${applyDataLabel (
1257+ FINAL_CONFIG .value .style .chart .bars .dataLabels .formatter ,
1258+ sumLabel,
1259+ dataLabel ({
1260+ p: FINAL_CONFIG .value .style .chart .bars .dataLabels .prefix ,
1261+ v: sumLabel,
1262+ s: FINAL_CONFIG .value .style .chart .bars .dataLabels .suffix ,
1263+ r: roundingValue
1264+ }),
1265+ { datapoint: { name: totalTranslation, value: sumLabel } }
1266+ )}
1267+ < / span>
1268+ < / div> `
1269+ }
1270+
12131271 const parenthesis = [
12141272 showValue && showPercentage ? '(' : '',
12151273 showValue && showPercentage ? ')' : '',
@@ -1219,12 +1277,16 @@ function useTooltip(seriesIndex) {
12191277 html += `
12201278 < div style= " display:flex;flex-direction:row;align-items:center;gap:4px" >
12211279 < svg viewBox= " 0 0 60 60" height= " 14" width= " 14" >< rect rx= " 5" x= " 0" y= " 0" height= " 60" width= " 60" stroke= " none" fill= " ${FINAL_CONFIG.value.style.chart.bars.gradient.show ? `url(#gradient_${ds.id})` : ds.color}" / > ${slots .pattern ? ` <rect rx="5" x="0" y="0" height="60" width="60" stroke="none" fill="url(#pattern_${ uid .value } _${ ds .absoluteIndex } )"/>` : ' ' }< / svg>
1222- ${ds .name }${showValue || showPercentage ? ' :' : ' ' } ${showValue ? dataLabel ({
1223- p: FINAL_CONFIG .value .style .chart .bars .dataLabels .prefix ,
1224- v: ds .value ,
1225- s: FINAL_CONFIG .value .style .chart .bars .dataLabels .suffix ,
1226- r: roundingValue,
1227- }) : ' ' } ${parenthesis[0 ]}${showPercentage ? dataLabel ({
1280+ ${ds .name }${showValue || showPercentage ? ' :' : ' ' } ${showValue ? applyDataLabel (
1281+ FINAL_CONFIG .value .style .chart .bars .dataLabels .formatter ,
1282+ ds .value ,
1283+ dataLabel ({
1284+ p: FINAL_CONFIG .value .style .chart .bars .dataLabels .prefix ,
1285+ v: ds .value ,
1286+ s: FINAL_CONFIG .value .style .chart .bars .dataLabels .suffix ,
1287+ r: roundingValue,
1288+ }, { datapoint: ds })
1289+ ) : ' ' } ${parenthesis[0 ]}${showPercentage ? dataLabel ({
12281290 v: isNaN (ds .value / sum) ? 0 : Math .abs (ds .value ) / sum * 100 , // Negs are absed to show relative proportion to absolute total. It's opinionated.
12291291 s: ' %' ,
12301292 r: roundingPercentage,
@@ -1616,6 +1678,67 @@ function selectX({ seriesIndex, datapoint }) {
16161678 })
16171679}
16181680
1681+ function getZeroPositions() {
1682+ const y0 = yLabels.value?.[0]?.zero ?? drawingArea.value.bottom;
1683+ const x0 = yLabels.value?.[0]?.horizontal_zero ?? drawingArea.value.left;
1684+ return { y0, x0 };
1685+ }
1686+
1687+ function placeLabelTotalY(index) {
1688+ const { y0 } = getZeroPositions();
1689+ const cfg = FINAL_CONFIG.value.style.chart.bars.totalValues;
1690+ const pad = Math.max(2, (cfg.fontSize * 0.3) + cfg.offsetY);
1691+
1692+ let minY = Infinity;
1693+ let hasPos = false;
1694+
1695+ for (const dp of formattedDataset.value || []) {
1696+ const v = dp?.series?.[index] ?? 0;
1697+ const h = dp?.height?.[index] ?? 0;
1698+ const y = dp?.y?.[index];
1699+ if (v > 0 && h > 0 && Number.isFinite(y)) {
1700+ hasPos = true;
1701+ if (y < minY) minY = y;
1702+ }
1703+ }
1704+
1705+ const topY = hasPos && Number.isFinite(minY) ? minY : y0;
1706+ const rawY = topY - pad;
1707+
1708+ const clampedY = Math.min(
1709+ Math.max(rawY, 0),
1710+ drawingArea.value.bottom
1711+ );
1712+
1713+ return clampedY;
1714+ }
1715+
1716+
1717+
1718+ function placeLabelTotalX(index) {
1719+ const { x0 } = getZeroPositions();
1720+ const pad = Math.max(2, (FINAL_CONFIG.value.style.chart.bars.totalValues.fontSize * 0.3) + FINAL_CONFIG.value.style.chart.bars.totalValues.offsetX);
1721+
1722+ let rightMost = -Infinity;
1723+ let hasPos = false;
1724+
1725+ for (const dp of formattedDataset.value || []) {
1726+ const v = dp?.series?.[index] ?? 0;
1727+ const x = dp?.horizontal_x?.[index];
1728+ const wRaw = dp?.horizontal_width?.[index];
1729+ const w = Number.isFinite(wRaw) ? Math.max(0, wRaw) : 0;
1730+ if (!Number.isFinite(x)) continue;
1731+
1732+ if (v > 0 && w > 0) {
1733+ hasPos = true;
1734+ rightMost = Math.max(rightMost, x + w);
1735+ }
1736+ }
1737+
1738+ const baseX = hasPos && Number.isFinite(rightMost) ? rightMost : x0;
1739+ return baseX + pad;
1740+ }
1741+
16191742defineExpose({
16201743 getData,
16211744 getImage,
@@ -1775,6 +1898,23 @@ defineExpose({
17751898 </linearGradient>
17761899 </defs>
17771900
1901+ <!-- FRAME -->
1902+ <rect
1903+ data-cy="frame"
1904+ v-if="FINAL_CONFIG.style.chart.grid.frame.show"
1905+ :style="{ pointerEvents: 'none', transition: 'none', animation: 'none !important' }"
1906+ :x="Math.max(0, drawingArea.left)"
1907+ :y="Math.max(0, drawingArea.top)"
1908+ :width="Math.max(0, drawingArea.width)"
1909+ :height="Math.max(0, drawingArea.height)"
1910+ fill="transparent"
1911+ :stroke="FINAL_CONFIG.style.chart.grid.frame.stroke"
1912+ :stroke-width="FINAL_CONFIG.style.chart.grid.frame.strokeWidth"
1913+ :stroke-linecap="FINAL_CONFIG.style.chart.grid.frame.strokeLinecap"
1914+ :stroke-linejoin="FINAL_CONFIG.style.chart.grid.frame.strokeLinejoin"
1915+ :stroke-dasharray="FINAL_CONFIG.style.chart.grid.frame.strokeDasharray"
1916+ />
1917+
17781918 <!-- HORIZONTAL LINES (vertical mode) -->
17791919 <template v-if="FINAL_CONFIG.style.chart.grid.x.showHorizontalLines && FINAL_CONFIG.orientation === 'vertical'">
17801920 <line
@@ -1846,7 +1986,7 @@ defineExpose({
18461986 <rect
18471987 v-for="(rect, j) in dp.x"
18481988 :x="rect"
1849- :y="dp.y[j] < 0 ? 0 : dp.y[j] "
1989+ :y="forceValidValue( dp.y[j]) "
18501990 :height="dp.height[j] < 0 ? 0.0001 : dp.height[j] || 0"
18511991 :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 "
18521992 :width="barSlot * (1 - FINAL_CONFIG.style.chart.bars.gapRatio / 2)"
@@ -1861,7 +2001,7 @@ defineExpose({
18612001 <rect
18622002 v-for="(rect, j) in dp.x"
18632003 :x="rect"
1864- :y="dp.y[j] < 0 ? 0 : dp.y[j] "
2004+ :y="forceValidValue( dp.y[j]) "
18652005 :height="dp.height[j] < 0 ? 0.0001 : dp.height[j] || 0"
18662006 :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 "
18672007 :width="barSlot * (1 - FINAL_CONFIG.style.chart.bars.gapRatio / 2)"
@@ -1996,7 +2136,7 @@ defineExpose({
19962136 data-cy="label-total"
19972137 v-if="FINAL_CONFIG.style.chart.bars.dataLabels.hideEmptyValues ? total.value !== 0 : true"
19982138 :x="drawingArea.left + (barSlot * i) + barSlot / 2"
1999- :y="FINAL_CONFIG.style.chart.bars.totalValues.fontSize "
2139+ :y="placeLabelTotalY(i) "
20002140 text-anchor="middle"
20012141 :font-size="FINAL_CONFIG.style.chart.bars.totalValues.fontSize"
20022142 :font-weight="FINAL_CONFIG.style.chart.bars.totalValues.bold ? 'bold' : 'normal'"
@@ -2035,8 +2175,8 @@ defineExpose({
20352175 <text
20362176 data-cy="label-total"
20372177 v-if="FINAL_CONFIG.style.chart.bars.dataLabels.hideEmptyValues ? total.value !== 0 : true"
2038- :x="drawingArea.right + FINAL_CONFIG.style.chart.bars.totalValues.fontSize / 3 "
2039- :y="drawingArea.top + (barSlot * i) + barSlot / 2"
2178+ :x="placeLabelTotalX(i) "
2179+ :y="drawingArea.top + (barSlot * i) + barSlot / 2 + (FINAL_CONFIG.style.chart.bars.totalValues.fontSize / 3) "
20402180 text-anchor="start"
20412181 :font-size="FINAL_CONFIG.style.chart.bars.totalValues.fontSize"
20422182 :font-weight="FINAL_CONFIG.style.chart.bars.totalValues.bold ? 'bold' : 'normal'"
0 commit comments