setUserOptionsVisibility(true)" @mouseleave="() => setUserOptionsVisibility(false)">
+
setUserOptionsVisibility(true)" @mouseleave="() => setUserOptionsVisibility(false)">
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
- {{ makeDataLabel(serie.value, serie, i, serie.sign) }}
-
-
-
-
- {{ serie.name }}
-
-
-
-
- {{ getParentData(serie, i).name }}
-
-
- {{ makeDataLabel(getParentData(serie, i).value), getParentData(serie, i), i, serie.parentSign || serie.sign }}
-
-
-
-
+
+
+
+
+
+
+ {{ makeDataLabel(serie.value, serie, i, serie.sign) }}
+
+
+
+
+ {{ serie.name }}
+
+
+
+
+
+
+ {{ getParentData(serie, i).name }}
+
+
+ {{ makeDataLabel(getParentData(serie, i).value), getParentData(serie, i), i, serie.parentSign || serie.sign }}
+
+
+
+
+
+
+
-
-
-
@@ -1237,6 +1401,9 @@ defineExpose({
+
+
+
diff --git a/src/components/vue-ui-waffle.vue b/src/components/vue-ui-waffle.vue
index 8fbd23b2..0f699342 100644
--- a/src/components/vue-ui-waffle.vue
+++ b/src/components/vue-ui-waffle.vue
@@ -1,5 +1,5 @@
- const autoScalePlots = datapoint.series.map((plot, j) => {
- if(![undefined, null].includes(datapoint.absoluteValues[j])) {
- return {
- x: this.checkNaN((this.drawingArea.left + (this.slot.line/2)) + (this.slot.line * j)),
- y: this.checkNaN(this.drawingArea.bottom - yOffset - ((individualHeight * autoScaleRatiosToNiceScale[j]) || 0)),
- value: datapoint.absoluteValues[j],
- comment: datapoint.comments ? datapoint.comments.slice(this.slicer.start, this.slicer.end)[j] || '' : ''
- }
- } else {
- return {
- x: this.checkNaN((this.drawingArea.left + (this.slot.line/2)) + (this.slot.line * j)),
- y: zeroPosition,
- value: datapoint.absoluteValues[j],
- comment: datapoint.comments ? datapoint.comments.slice(this.slicer.start, this.slicer.end)[j] || '' : ''
- }
- }
- })
+
+ setUserOptionsVisibility(true)" @mouseleave="() => setUserOptionsVisibility(false)">
+
+
+
- const curve = this.FINAL_CONFIG.line.cutNullValues
- ? this.createSmoothPathWithCuts(plots)
- : this.createSmoothPath(plots.filter(p => p.value !== null));
-
- const autoScaleCurve = this.FINAL_CONFIG.line.cutNullValues
- ? this.createSmoothPathWithCuts(autoScalePlots)
- : this.createSmoothPath(autoScalePlots.filter(p => p.value !== null));
-
- const straight = this.FINAL_CONFIG.line.cutNullValues
- ? this.createStraightPathWithCuts(plots)
- : this.createStraightPath(plots.filter(p => p.value !== null));
-
- const autoScaleStraight = this.FINAL_CONFIG.line.cutNullValues
- ? this.createStraightPathWithCuts(autoScalePlots)
- : this.createStraightPath(autoScalePlots.filter(p => p.value !== null));
-
- const scaleYLabels = individualScale.ticks.map(t => {
- return {
- y: t >= 0 ? zeroPosition - (individualHeight * (t / individualMax)) : zeroPosition + (individualHeight * Math.abs(t) / individualMax),
- value: t,
- prefix: datapoint.prefix || this.FINAL_CONFIG.chart.labels.prefix,
- suffix: datapoint.suffix || this.FINAL_CONFIG.chart.labels.suffix,
- datapoint
- }
- })
+
+
+
+
- const autoScaleYLabels = autoScaleSteps.ticks.map(t => {
- const v = (t - autoScaleSteps.min) / (autoScaleSteps.max - autoScaleSteps.min);
- return {
- y: t >= 0 ? autoScaleZeroPosition - (individualHeight * v) : autoScaleZeroPosition + (individualHeight * v),
- value: t,
- prefix: datapoint.prefix || this.FINAL_CONFIG.chart.labels.prefix,
- suffix: datapoint.suffix || this.FINAL_CONFIG.chart.labels.suffix,
- datapoint
- }
- });
+
- this.scaleGroups[datapoint.scaleLabel].name = datapoint.name;
- this.scaleGroups[datapoint.scaleLabel].groupName = datapoint.scaleLabel;
- this.scaleGroups[datapoint.scaleLabel].groupColor = this.FINAL_CONFIG.chart.grid.labels.yAxis.groupColor || datapoint.color;
- this.scaleGroups[datapoint.scaleLabel].color = datapoint.color;
- this.scaleGroups[datapoint.scaleLabel].scaleYLabels = datapoint.autoScaling ? autoScaleYLabels : scaleYLabels;
- this.scaleGroups[datapoint.scaleLabel].zeroPosition = datapoint.autoScaling ? autoScaleZeroPosition : zeroPosition;
- this.scaleGroups[datapoint.scaleLabel].individualMax = datapoint.autoScaling ? autoScaleMax : individualMax;
- this.scaleGroups[datapoint.scaleLabel].scaleLabel = datapoint.scaleLabel;
- this.scaleGroups[datapoint.scaleLabel].id = datapoint.id;
- this.scaleGroups[datapoint.scaleLabel].yOffset = yOffset;
- this.scaleGroups[datapoint.scaleLabel].individualHeight = individualHeight;
- this.scaleGroups[datapoint.scaleLabel].autoScaleYLabels = autoScaleYLabels;
- this.scaleGroups[datapoint.scaleLabel].unique = this.activeSeriesWithStackRatios.filter(el => el.scaleLabel === datapoint.scaleLabel).length === 1
-
- const areaZeroPosition = this.mutableConfig.useIndividualScale ? datapoint.autoScaling ? autoScaleZeroPosition : zeroPosition : this.zero;
- const adustedAreaZeroPosition = Math.max(Math.max(datapoint.autoScaling ? autoScaleZeroPosition : scaleYLabels.at(-1).y || 0, this.drawingArea.top), areaZeroPosition);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- return {
- ...datapoint,
- yOffset,
- autoScaleYLabels,
- individualHeight,
- scaleYLabels: datapoint.autoScaling ? autoScaleYLabels : scaleYLabels,
- individualScale: datapoint.autoScaling ? autoScaleSteps : individualScale,
- individualMax: datapoint.autoScaling ? autoScaleMax : individualMax,
- zeroPosition: datapoint.autoScaling ? autoScaleZeroPosition : zeroPosition,
- curve: datapoint.autoScaling ? autoScaleCurve : curve,
- plots: datapoint.autoScaling ? autoScalePlots : plots,
- area: !datapoint.useArea
- ? ''
- : this.mutableConfig.useIndividualScale
- ? this.FINAL_CONFIG.line.cutNullValues
- ? this.createIndividualAreaWithCuts(datapoint.autoScaling
- ? autoScalePlots
- : plots,
- adustedAreaZeroPosition,
- )
- : this.createIndividualArea(datapoint.autoScaling
- ? autoScalePlots.filter(p => p.value !== null)
- : plots.filter(p => p.value !== null),
- adustedAreaZeroPosition)
- : this.createIndividualArea(plots.filter(p => p.value !== null), adustedAreaZeroPosition),
- curveAreas: !datapoint.useArea
- ? []
- :createSmoothAreaSegments(
- datapoint.autoScaling
- ? this.FINAL_CONFIG.line.cutNullValues
- ? autoScalePlots
- : autoScalePlots.filter(p => p.value !== null)
- : this.FINAL_CONFIG.line.cutNullValues
- ? plots
- : plots.filter(p => p.value !== null),
- adustedAreaZeroPosition,
- this.FINAL_CONFIG.line.cutNullValues),
- straight: datapoint.autoScaling ? autoScaleStraight : straight,
- groupId: this.scaleGroups[datapoint.scaleLabel].groupId
- }
- });
- },
- plotSet() {
- const stackSeries = this.activeSeriesWithStackRatios.filter(s => ['bar','line','plot'].includes(s.type));
- const totalSeries = stackSeries.length;
- const gap = this.FINAL_CONFIG.chart.grid.labels.yAxis.gap;
- const stacked = this.mutableConfig.isStacked;
- const totalGap = stacked ? gap * (totalSeries - 1) : 0;
- const usableHeight = this.drawingArea.height - totalGap;
-
- return stackSeries.filter(s => s.type === 'plot').map((datapoint) => {
- this.checkAutoScaleError(datapoint);
- const min = this.scaleGroups[datapoint.scaleLabel].min;
- const max = this.scaleGroups[datapoint.scaleLabel].max;
- const autoScaledRatios = datapoint.absoluteValues.filter(v => ![null, undefined].includes(v)).map(v => {
- return (v - min) / (max - min)
- });
+
+
+
- const autoScale = {
- ratios: autoScaledRatios,
- valueMin: min,
- valueMax: max,
- }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- const individualExtremes = {
- max: datapoint.scaleMax || Math.max(...datapoint.absoluteValues) || 1,
- min: datapoint.scaleMin || Math.min(...datapoint.absoluteValues) > 0 ? 0 : Math.min(...datapoint.absoluteValues)
- };
-
- const scaleSteps = datapoint.scaleSteps || this.FINAL_CONFIG.chart.grid.labels.yAxis.commonScaleSteps;
-
- const corrector = 1.0000001;
-
- const individualScale = this.calculateNiceScaleWithExactExtremes(individualExtremes.min, individualExtremes.max === individualExtremes.min ? individualExtremes.max * corrector : individualExtremes.max, scaleSteps);
-
- const autoScaleSteps = this.calculateNiceScaleWithExactExtremes(autoScale.valueMin, autoScale.valueMax === autoScale.valueMin ? autoScale.valueMax * corrector : autoScale.valueMax, scaleSteps);
-
- const individualZero = individualScale.min >= 0 ? 0 : Math.abs(individualScale.min);
- const autoScaleZero = 0;
-
- const individualMax = individualScale.max + individualZero;
- const autoScaleMax = autoScaleSteps.max + Math.abs(autoScaleZero);
-
- const origIdx = datapoint.stackIndex;
- const flippedIdx = totalSeries - 1 - origIdx;
- const flippedLowerRatio = stacked ? 1 - datapoint.cumulatedStackRatio : 0;
- const yOffset = stacked ? usableHeight * flippedLowerRatio + gap * flippedIdx : 0;
- const individualHeight = stacked ? usableHeight * datapoint.stackRatio : this.drawingArea.height;
-
- const zeroPosition = this.drawingArea.bottom - yOffset - ((individualHeight) * individualZero / individualMax);
- const autoScaleZeroPosition = this.drawingArea.bottom - yOffset - (individualHeight * autoScaleZero / autoScaleMax);
-
- const plots = datapoint.series.map((plot, j) => {
- const yRatio = this.mutableConfig.useIndividualScale ? ((datapoint.absoluteValues[j] + Math.abs(individualZero)) / individualMax) : this.ratioToMax(plot)
- return {
- x: this.checkNaN((this.drawingArea.left + (this.slot.plot / 2)) + (this.slot.plot * j)),
- y: this.checkNaN(this.drawingArea.bottom - yOffset - (individualHeight * yRatio)),
- value: datapoint.absoluteValues[j],
- comment: datapoint.comments ? datapoint.comments.slice(this.slicer.start, this.slicer.end)[j] || '' : ''
- }
- })
+
+
+
+
- const autoScaleRatiosToNiceScale = datapoint.absoluteValues.map(v => {
- if(autoScaleSteps.min >= 0) {
- return (v - Math.abs(autoScaleSteps.min)) / (autoScaleSteps.max - Math.abs(autoScaleSteps.min))
- } else {
- return (v + Math.abs(autoScaleSteps.min)) / (autoScaleSteps.max + Math.abs(autoScaleSteps.min))
- }
- })
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- const autoScalePlots = datapoint.series.map((plot, j) => {
- return {
- x: this.checkNaN((this.drawingArea.left + (this.slot.plot / 2)) + (this.slot.plot * j)),
- y: this.checkNaN(this.drawingArea.bottom - yOffset - ((individualHeight * autoScaleRatiosToNiceScale[j]) || 0)),
- value: datapoint.absoluteValues[j],
- comment: datapoint.comments ? datapoint.comments.slice(this.slicer.start, this.slicer.end)[j] || '' : ''
- }
- })
+
+
+
+
+
+
+
+
+
- const scaleYLabels = individualScale.ticks.map(t => {
- return {
- y: t >= 0 ? zeroPosition - (individualHeight * (t / individualMax)) : zeroPosition + (individualHeight * Math.abs(t) / individualMax),
- value: t,
- prefix: datapoint.prefix || this.FINAL_CONFIG.chart.labels.prefix,
- suffix: datapoint.suffix || this.FINAL_CONFIG.chart.labels.suffix,
- datapoint,
- }
- })
+
+
+
+
+
+
+
+
+
+
+
+
+
- const autoScaleYLabels = autoScaleSteps.ticks.map(t => {
- const v = (t - autoScaleSteps.min) / (autoScaleSteps.max - autoScaleSteps.min);
- return {
- y: t >= 0 ? autoScaleZeroPosition - (individualHeight * v) : autoScaleZeroPosition + (individualHeight * v),
- value: t,
- prefix: datapoint.prefix || this.FINAL_CONFIG.chart.labels.prefix,
- suffix: datapoint.suffix || this.FINAL_CONFIG.chart.labels.suffix,
- datapoint
- }
- });
+
+
+
+
+
+
+
+
+
+
+
+ {{ oneArea.caption.text }}
+
+
+
+
+
- this.scaleGroups[datapoint.scaleLabel].name = datapoint.name;
- this.scaleGroups[datapoint.scaleLabel].groupName = datapoint.scaleLabel;
- this.scaleGroups[datapoint.scaleLabel].groupColor = this.FINAL_CONFIG.chart.grid.labels.yAxis.groupColor || datapoint.color;
- this.scaleGroups[datapoint.scaleLabel].color = datapoint.color;
- this.scaleGroups[datapoint.scaleLabel].scaleYLabels = datapoint.autoScaling ? autoScaleYLabels : scaleYLabels;
- this.scaleGroups[datapoint.scaleLabel].zeroPosition = datapoint.autoScaling ? autoScaleZeroPosition : zeroPosition;
- this.scaleGroups[datapoint.scaleLabel].individualMax = datapoint.autoScaling ? autoScaleMax : individualMax;
- this.scaleGroups[datapoint.scaleLabel].scaleLabel = datapoint.scaleLabel;
- this.scaleGroups[datapoint.scaleLabel].id = datapoint.id;
- this.scaleGroups[datapoint.scaleLabel].yOffset = yOffset;
- this.scaleGroups[datapoint.scaleLabel].individualHeight = individualHeight;
- this.scaleGroups[datapoint.scaleLabel].autoScaleYLabels = autoScaleYLabels;
- this.scaleGroups[datapoint.scaleLabel].unique = this.activeSeriesWithStackRatios.filter(el => el.scaleLabel === datapoint.scaleLabel).length === 1
+
+
+
+
+
+
- return {
- ...datapoint,
- yOffset,
- autoScaleYLabels,
- individualHeight,
- scaleYLabels: datapoint.autoScaling ? autoScaleYLabels : scaleYLabels,
- individualScale: datapoint.autoScaling ? autoScaleSteps : individualScale,
- individualMax: datapoint.autoScaling ? autoScaleMax : individualMax,
- zeroPosition: datapoint.autoScaling ? autoScaleZeroPosition : zeroPosition,
- plots: datapoint.autoScaling ? autoScalePlots : plots,
- groupId: this.scaleGroups[datapoint.scaleLabel].groupId
- }
- })
- },
- drawingArea() {
- function getUniqueScaleLabelsCount(dataset) {
- const uniqueLabels = new Set();
- dataset.forEach(item => {
- const label = item.scaleLabel || '__noScaleLabel__';
- uniqueLabels.add(label);
- });
- return uniqueLabels.size;
- }
+
+
+
+
+
+
- const len = getUniqueScaleLabelsCount(this.absoluteDataset.filter(s => !this.segregatedSeries.includes(s.id)));
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ dataLabel({
+ v: calcLinearProgression(serie.plots).trend * 100,
+ s: '%',
+ r: 2,
+ }) }}
+
+
+
+
- const individualScalesPadding = this.mutableConfig.useIndividualScale && this.FINAL_CONFIG.chart.grid.labels.show ? len * (this.mutableConfig.isStacked ? 0 : this.FINAL_CONFIG.chart.grid.labels.yAxis.labelWidth) : 0;
- return {
- top: this.FINAL_CONFIG.chart.padding.top,
- right: this.width - this.FINAL_CONFIG.chart.padding.right,
- bottom: this.height - this.FINAL_CONFIG.chart.padding.bottom,
- left: this.FINAL_CONFIG.chart.padding.left + individualScalesPadding,
- height: this.height - (this.FINAL_CONFIG.chart.padding.top + this.FINAL_CONFIG.chart.padding.bottom),
- width: this.width - (this.FINAL_CONFIG.chart.padding.right + this.FINAL_CONFIG.chart.padding.left + individualScalesPadding)
- }
- },
- max(){
- if (this.FINAL_CONFIG.chart.grid.labels.yAxis.scaleMax) {
- return this.FINAL_CONFIG.chart.grid.labels.yAxis.scaleMax
- }
- return Math.max(...this.safeDataset.filter(s => !this.segregatedSeries.includes(s.id)).map(datapoint => Math.max(...datapoint.series)));
- },
- min() {
- if (this.FINAL_CONFIG.chart.grid.labels.yAxis.scaleMin !== null) {
- return this.FINAL_CONFIG.chart.grid.labels.yAxis.scaleMin
- }
- const min = Math.min(...this.safeDataset.filter(s => !this.segregatedSeries.includes(s.id)).map(datapoint => Math.min(...datapoint.series)));
- if(min > 0) return 0;
- return min;
- },
- niceScale() {
- return this.FINAL_CONFIG.chart.grid.labels.yAxis.useNiceScale ? this.calculateNiceScale(this.min, this.max < 0 ? 0 : this.max, this.FINAL_CONFIG.chart.grid.labels.yAxis.commonScaleSteps) : this.calculateNiceScaleWithExactExtremes(this.min, this.max < 0 ? 0 : this.max, this.FINAL_CONFIG.chart.grid.labels.yAxis.commonScaleSteps)
- },
- maxSeries(){
- return this.slicer.end - this.slicer.start;
- },
- timeLabels() {
- const max = Math.max(...this.dataset.map(datapoint => this.largestTriangleThreeBucketsArray({data:datapoint.series, threshold: this.FINAL_CONFIG.downsample.threshold}).length));
-
- return useTimeLabels({
- values: this.FINAL_CONFIG.chart.grid.labels.xAxisLabels.values,
- maxDatapoints: max,
- formatter: this.FINAL_CONFIG.chart.grid.labels.xAxisLabels.datetimeFormatter,
- start: this.slicer.start,
- end: this.slicer.end
- });
- },
- slot() {
- return {
- bar: this.drawingArea.width / this.maxSeries / this.safeDataset.filter(serie => serie.type === 'bar').filter(s => !this.segregatedSeries.includes(s.id)).length,
- plot: this.drawingArea.width / this.maxSeries,
- line: this.drawingArea.width / this.maxSeries,
- }
- },
- barSlot() {
- const len = this.safeDataset.filter(serie => serie.type === 'bar').filter(s => !this.segregatedSeries.includes(s.id)).length
- return (this.drawingArea.width) / this.maxSeries / len - (this.barPeriodGap * len)
- },
- barPeriodGap() {
- return this.slot.line * this.FINAL_CONFIG.bar.periodGap;
- },
- maxSlot(){
- return Math.max(...Object.values(this.slot).filter(e => e !== Infinity))
- },
- table() {
- if(this.safeDataset.length === 0) return { head: [], body: [], config: {}, columnNames: []};
+
+
+
+
- const head = this.relativeDataset.map(s => {
- return {
- label: s.name,
- color: s.color,
- type: s.type
- }
- })
+
+
+
- const body = [];
+
+
+
+
+
+
+
+
+
+
+
+ {{ el.name }} {{ el.scaleLabel && el.unique && el.scaleLabel !== el.id ? `-
+ ${el.scaleLabel}` : '' }}
+
+
+
+
+
+ {{
+ applyDataLabel(
+ FINAL_CONFIG.chart.grid.labels.yAxis.formatter,
+ yLabel.value,
+ dataLabel({
+ p: yLabel.prefix,
+ v: yLabel.value,
+ s: yLabel.suffix,
+ r: FINAL_CONFIG.chart.grid.labels.yAxis.rounding,
+ }),
+ { datapoint: yLabel.datapoint, seriesIndex: j }
+ )
+ }}
+
+
+
+
+
+
+
+ {{ canShowValue(yLabel.value) ? applyDataLabel(
+ FINAL_CONFIG.chart.grid.labels.yAxis.formatter,
+ yLabel.value,
+ dataLabel({
+ p: yLabel.prefix,
+ v: yLabel.value,
+ s: yLabel.suffix,
+ r: FINAL_CONFIG.chart.grid.labels.yAxis.rounding,
+ })) : ''
+ }}
+
+
+
+
- this.timeLabels.forEach((t, i) => {
- const row = [t.text];
- this.relativeDataset.forEach(s => {
- row.push(this.canShowValue(s.absoluteValues[i]) ? Number(s.absoluteValues[i].toFixed(this.FINAL_CONFIG.table.rounding)) : '')
- });
- body.push(row);
- })
+
+
+
+
- return { head, body};
- },
- dataTable() {
- const showSum = this.FINAL_CONFIG.table.showSum;
- let head = [''].concat(this.relativeDataset.map(ds => ds.name))
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ dataLabel({
+ v: calcLinearProgression(serie.plots).trend * 100,
+ s: '%',
+ r: 2,
+ }) }}
+
+
+
- if(showSum) {
- head = head.concat(` `)
- }
+
+
+
- let body = [];
- for(let i = 0; i < this.maxSeries; i += 1) {
- const sum = this.relativeDataset.map(ds => {
- return ds.absoluteValues[i] ?? 0
- }).reduce((a, b) => a + b, 0)
-
- body.push([
- this.timeLabels[i].text ?? '-']
- .concat(this.relativeDataset
- .map(ds => {
- return this.applyDataLabel(
- ds.type === 'line' ? this.FINAL_CONFIG.line.labels.formatter :
- ds.type === 'bar' ? this.FINAL_CONFIG.bar.labels.formatter :
- this.FINAL_CONFIG.plot.labels.formatter,
- ds.absoluteValues[i] ?? 0,
- this.dataLabel({
- p: ds.prefix || this.FINAL_CONFIG.chart.labels.prefix,
- v: ds.absoluteValues[i] ?? 0,
- s: ds.suffix || this.FINAL_CONFIG.chart.labels.suffix,
- r: this.FINAL_CONFIG.table.rounding
- })
- )}
- ))
- .concat(showSum ? (sum ?? 0).toFixed(this.FINAL_CONFIG.table.rounding) : [])
- )
- }
+
+
- const config = {
- th: {
- backgroundColor: this.FINAL_CONFIG.table.th.backgroundColor,
- color: this.FINAL_CONFIG.table.th.color,
- outline: this.FINAL_CONFIG.table.th.outline
- },
- td: {
- backgroundColor: this.FINAL_CONFIG.table.td.backgroundColor,
- color: this.FINAL_CONFIG.table.td.color,
- outline: this.FINAL_CONFIG.table.td.outline
- },
- breakpoint: this.FINAL_CONFIG.table.responsiveBreakpoint
- }
- const colNames = [this.FINAL_CONFIG.table.columnNames.period].concat(this.relativeDataset.map(ds => ds.name)).concat(this.FINAL_CONFIG.table.columnNames.total)
+
+
+
- return { head, body, config, colNames}
- },
- dataTooltipSlot() {
- return {
- datapoint: this.selectedSeries,
- seriesIndex: this.selectedSerieIndex,
- series: this.absoluteDataset,
- bars: this.barSet,
- lines: this.lineSet,
- plots: this.plotSet,
- config: this.FINAL_CONFIG
- }
- },
- selectedSeries() {
- return this.relativeDataset.map(datapoint => {
- return {
- slotAbsoluteIndex: datapoint.slotAbsoluteIndex,
- shape: datapoint.shape || null,
- name: datapoint.name,
- color: datapoint.color,
- type: datapoint.type,
- value: datapoint.absoluteValues.find((_s,i) => i === this.selectedSerieIndex),
- comments: datapoint.comments || [],
- prefix: datapoint.prefix || this.FINAL_CONFIG.chart.labels.prefix,
- suffix: datapoint.suffix || this.FINAL_CONFIG.chart.labels.suffix,
- }
- });
- },
- tooltipContent() {
- let html = "";
-
- let sum = this.selectedSeries.map(s => s.value).filter(s => this.isSafeValue(s) && s !== null).reduce((a,b) => Math.abs(a) + Math.abs(b), 0);
-
- const time = this.timeLabels[this.selectedSerieIndex];
- const customFormat = this.FINAL_CONFIG.chart.tooltip.customFormat;
-
- if(this.isFunction(customFormat) && this.functionReturnsString(() => customFormat({
- seriesIndex: this.selectedSerieIndex,
- datapoint: this.selectedSeries,
- series: this.absoluteDataset,
- bars: this.barSet,
- lines: this.lineSet,
- plots: this.plotSet,
- config: this.FINAL_CONFIG
- }))) {
- return customFormat({
- seriesIndex: this.selectedSerieIndex,
- datapoint: this.selectedSeries,
- series: this.absoluteDataset,
- bars: this.barSet,
- lines: this.lineSet,
- plots: this.plotSet,
- config: this.FINAL_CONFIG
- })
- } else {
- if(time && time.text && this.FINAL_CONFIG.chart.tooltip.showTimeLabel) {
- html += `${time.text}
`;
- }
- this.selectedSeries.forEach(s => {
- if(this.isSafeValue(s.value)) {
- let shape = '';
- let insideShape = '';
- switch (this.icons[s.type]) {
- case 'bar':
- shape = `${this.$slots.pattern ? ` `: ''} `;
- break;
-
- case 'line':
- if(!s.shape || !['star', 'triangle', 'square', 'diamond', 'pentagon', 'hexagon'].includes(s.shape)) {
- insideShape = ` `
- } else if(s.shape === 'triangle') {
- insideShape = ` `
- } else if(s.shape === 'square') {
- insideShape = ` `
- } else if(s.shape === 'diamond') {
- insideShape = ` `
- } else if(s.shape === 'pentagon') {
- insideShape = ` `
- } else if(s.shape === 'hexagon') {
- insideShape = ` `
- } else if(s.shape === 'star') {
- insideShape = ` `
- }
- shape = ` ${insideShape} `;
- break;
-
- case 'plot':
- if (!s.shape || !['star', 'triangle', 'square', 'diamond', 'pentagon', 'hexagon'].includes(s.shape)) {
- shape = ` `;
- break;
- }
- if(s.shape === 'star') {
- shape = ` `;
- break;
- }
- if(s.shape === 'triangle') {
- shape = ` `;
- break;
- }
- if(s.shape === 'square') {
- shape = ` `;
- break;
- }
- if(s.shape === 'diamond') {
- shape = ` `;
- break;
- }
- if(s.shape === 'pentagon') {
- shape = ` `;
- break;
- }
- if(s.shape === 'hexagon') {
- shape = ` `;
- break;
- }
- default:
- break;
- }
- html += `${shape}
${s.name}:
${this.FINAL_CONFIG.chart.tooltip.showValue ?
- this.applyDataLabel(
- s.type === 'line' ? this.FINAL_CONFIG.line.labels.formatter :
- s.type === 'bar' ? this.FINAL_CONFIG.bar.labels.formatter :
- this.FINAL_CONFIG.plot.labels.formatter,
- s.value,
- this.dataLabel({
- p: s.prefix,
- v: s.value,
- s: s.suffix,
- r: this.FINAL_CONFIG.chart.tooltip.roundingValue,
- }),
- { datapoint: s }
- ) : ''} ${this.FINAL_CONFIG.chart.tooltip.showPercentage ? `(${dataLabel({
- v: this.checkNaN(Math.abs(s.value) / sum * 100),
- s: '%',
- r: this.FINAL_CONFIG.chart.tooltip.roundingPercentage
- })})` : ''}
`;
+
+
+
+
- if (this.FINAL_CONFIG.chart.comments.showInTooltip && s.comments.length && s.comments.slice(this.slicer.start, this.slicer.end)[this.selectedSerieIndex]) {
- html += ``
- }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- }
- });
- return `${html}
`;
- }
- },
- svg() {
- return {
- height: this.height,
- width: this.width
- }
- },
- yLabels() {
- return this.niceScale.ticks.map(t => {
- return {
- y: t >= 0 ? this.zero - (this.drawingArea.height * this.ratioToMax(t)) : this.zero + (this.drawingArea.height * this.ratioToMax(Math.abs(t))),
- value: t,
- prefix: this.FINAL_CONFIG.chart.labels.prefix,
- suffix: this.FINAL_CONFIG.chart.labels.suffix,
- }
- })
- },
- zero(){
- if (isNaN(this.ratioToMax(this.relativeZero))) {
- return this.drawingArea.bottom;
- } else {
- return this.drawingArea.bottom - (this.drawingArea.height * this.ratioToMax(this.relativeZero));
- }
- },
- annotationsY() {
- const ann = this.FINAL_CONFIG.chart.annotations;
- if (!ann || !Array.isArray(ann) || ann.every(a => !a.show)) return [];
-
- const visible = ann.filter(a =>
- a.show &&
- (a.yAxis.yTop != null || a.yAxis.yBottom != null)
- );
-
- if (!visible.length) return [];
-
- const { left, right } = this.drawingArea;
- const zeroY = this.zero;
- const height = this.drawingArea.height;
- const min = this.niceScale.min;
- const max = this.niceScale.max;
- const range = max - min;
-
- const toY = v => {
- const ratio = (v - 0) / range;
- return zeroY - (ratio * height);
- };
-
- return visible.map(annotation => {
- const { yAxis: { yTop: rawTop, yBottom: rawBottom, label } } = annotation;
- const hasArea = rawTop != null && rawBottom != null && rawTop !== rawBottom;
-
- const yTop = rawTop == null ? null : toY(rawTop);
- const yBottom = rawBottom == null ? null : toY(rawBottom);
-
- const ctx = this.getTextMeasurer(label.fontSize);
- ctx.font = `${label.fontSize}px sans-serif`;
- const textWidth = ctx.measureText(label.text).width;
- const textHeight = label.fontSize;
-
- const xText = (label.position === 'start' ? left + label.padding.left : right - label.padding.right) + label.offsetX;
-
- const baselineY = (yTop != null && yBottom != null)
- ? Math.min(yTop, yBottom)
- : (yTop != null ? yTop : yBottom);
-
- const yText = baselineY - (label.fontSize / 3) + label.offsetY - label.padding.top;
-
- let rectX;
- if (label.textAnchor === 'middle') {
- rectX = xText - (textWidth / 2) - label.padding.left;
- } else if (label.textAnchor === 'end') {
- rectX = xText - textWidth - label.padding.right;
- } else {
- rectX = xText - label.padding.left;
- }
+
+
+
+
+
+
- const rectY = yText - (textHeight * 0.75) - label.padding.top;
- const show = ![yTop, yBottom, rectY].includes(NaN);
+
+
+
+
+
+
+
+
- return {
- show,
- id: `annotation_y_${this.createUid()}`,
- hasArea,
- areaHeight: hasArea ? Math.abs(yTop - yBottom) : 0,
- yTop,
- yBottom,
- config: annotation.yAxis,
- x1: left,
- x2: right,
- _text: { x: xText, y: yText },
- _box: {
- x: rectX,
- y: rectY,
- width: textWidth + label.padding.left + label.padding.right,
- height: textHeight + label.padding.top + label.padding.bottom,
- fill: label.backgroundColor,
- stroke: label.border.stroke,
- rx: label.border.rx,
- ry: label.border.ry,
- strokeWidth: label.border.strokeWidth
- }
- };
- });
- },
- },
- mounted() {
- this.svgRef = this.$refs.svgRef;
- this.prepareChart();
- this.setupSlicer();
-
- document.addEventListener("mousemove", (e) => {
- this.clientPosition = {
- x: e.clientX,
- y: e.clientY
- }
- });
-
- document.addEventListener('scroll', this.hideTags);
- },
- beforeUnmount() {
- document.removeEventListener('scroll', this.hideTags);
- if (this.resizeObserver) {
- this.resizeObserver.unobserve(this.observedEl);
- this.resizeObserver.disconnect();
- }
- },
- methods: {
- abbreviate,
- adaptColorToBackground,
- applyDataLabel,
- assignStackRatios,
- calcLinearProgression,
- calculateNiceScale,
- calculateNiceScaleWithExactExtremes,
- checkNaN,
- closestDecimal,
- convertColorToHex,
- convertConfigColors,
- convertCustomPalette,
- createCsvContent,
- createSmoothPath,
- createStraightPath,
- createTSpans,
- createTSpansFromLineBreaksOnX,
- dataLabel,
- downloadCsv,
- error,
- forceValidValue,
- functionReturnsString,
- hasDeepProperty,
- isFunction,
- isSafeValue,
- largestTriangleThreeBucketsArray,
- objectIsEmpty,
- setOpacity,
- shiftHue,
- translateSize,
- treeShake,
- useMouse,
- useNestedProp,
- createUid,
- placeXYTag,
- createSmoothPathWithCuts,
- createStraightPathWithCuts,
- createAreaWithCuts,
- createIndividualAreaWithCuts,
- createSmoothAreaSegments,
- createIndividualArea,
- usesSelectTimeLabelEvent() {
- return !!this.$.vnode.props?.onSelectTimeLabel;
- },
- getTextMeasurer(fontSize, fontFamily, fontWeight) {
- if (!this._textMeasurer) {
- const canvas = document.createElement('canvas')
- this._textMeasurer = canvas.getContext('2d')
- }
- this._textMeasurer.font =
- `${fontWeight || 'normal'} ${fontSize}px ${fontFamily || 'sans-serif'}`
- return this._textMeasurer
- },
- hideTags() {
- const tags = document.querySelectorAll('.vue-ui-xy-tag')
- if (tags.length) {
- Array.from(tags).forEach(tag => tag.style.opacity = '0')
- }
- },
- setTagRef(i, j, el, position, type) {
- if (el) this.tagRefs[`${i}_${j}_${position}_${type}`] = el;
- },
- setUserOptionsVisibility(state = false) {
- if (!this.showUserOptionsOnChartHover) return;
- this.userOptionsVisible = state
- },
- toggleAnnotator() {
- this.isAnnotator = !this.isAnnotator;
- },
- selectTimeLabel(label, relativeIndex) {
- const datapoint = this.relativeDataset.map(datapoint => {
- return {
- shape: datapoint.shape || null,
- name: datapoint.name,
- color: datapoint.color,
- type: datapoint.type,
- value: datapoint.absoluteValues.find((_s,i) => i === relativeIndex),
- comments: datapoint.comments || [],
- prefix: datapoint.prefix || this.FINAL_CONFIG.chart.labels.prefix,
- suffix: datapoint.suffix || this.FINAL_CONFIG.chart.labels.suffix,
- }
- })
- this.$emit('selectTimeLabel', {
- datapoint,
- absoluteIndex: label.absoluteIndex,
- label: label.text
- })
- },
- getHighlightAreaPosition(area) {
- const x = this.drawingArea.left + (this.drawingArea.width / this.maxSeries) * (area.from - this.slicer.start);
+
+
+
+
+
+
+
+
+ {{ dataLabel({
+ v: calcLinearProgression(serie.plots).trend * 100,
+ s: '%',
+ r: 2,
+ }) }}
+
+
+
+
+
+
+
+
+
+ {{ canShowValue(plot.value) ? applyDataLabel(
+ FINAL_CONFIG.bar.labels.formatter,
+ plot.value,
+ dataLabel({
+ p: serie.prefix || FINAL_CONFIG.chart.labels.prefix,
+ v: plot.value,
+ s: serie.suffix || FINAL_CONFIG.chart.labels.suffix,
+ r: FINAL_CONFIG.bar.labels.rounding,
+ }),
+ {
+ datapoint: plot,
+ serie,
+ }
+ ) : ''
+ }}
+
+
+ {{ FINAL_CONFIG.bar.serieName.useAbbreviation ? abbreviate({
+ source: serie.name,
+ length: FINAL_CONFIG.bar.serieName.abbreviationSize}) : serie.name }}
+
+
+
+
- const width = (this.drawingArea.width / (this.slicer.end - this.slicer.start)) * area.span < 0 ? 0.00001 : (this.drawingArea.width / (this.slicer.end - this.slicer.start)) * area.span
+
+
+
+
+
+ {{ canShowValue(plot.value) ? applyDataLabel(
+ FINAL_CONFIG.plot.labels.formatter,
+ plot.value,
+ dataLabel({
+ p: serie.prefix || FINAL_CONFIG.chart.labels.prefix,
+ v: plot.value,
+ s: serie.suffix || FINAL_CONFIG.chart.labels.suffix,
+ r: FINAL_CONFIG.plot.labels.rounding,
+ }),
+ {
+ datapoint: plot,
+ serie,
+ }
+ ) : ''
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ canShowValue(plot.value) ? applyDataLabel(
+ FINAL_CONFIG.line.labels.formatter,
+ plot.value,
+ dataLabel({
+ p: serie.prefix || FINAL_CONFIG.chart.labels.prefix,
+ v: plot.value,
+ s: serie.suffix || FINAL_CONFIG.chart.labels.suffix,
+ r: FINAL_CONFIG.line.labels.rounding,
+ }),
+ {
+ datapoint: plot,
+ serie,
+ }
+ ) : ''
+ }}
+
+
+
+
- return {
- x: x < this.drawingArea.left ? this.drawingArea.left : x,
- width: width
- }
- },
- prepareConfig() {
- const DEFAULT_CONFIG = useConfig().vue_ui_xy;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- if(!Object.keys(this.config || {}).length) {
- return DEFAULT_CONFIG
- }
+
+
+
+
+
+
+
- const mergedConfig = this.useNestedProp({
- userConfig: this.config,
- defaultConfig: DEFAULT_CONFIG
- });
+
+
+
+
+
+
+
- // ------------------------------ OVERRIDES -----------------------------------
+
+
+
+
+
+
+
+
+
+
- if (this.config && this.hasDeepProperty(this.config, 'chart.highlightArea')) {
- if (!Array.isArray(this.config.chart.highlightArea)) {
- mergedConfig.chart.highlightArea = [this.config.chart.highlightArea] // FIXME: should be sanitized using useNestedPropToo
- } else {
- mergedConfig.chart.highlightArea = this.config.chart.highlightArea;
- }
- }
-
- if (this.config && this.hasDeepProperty(this.config, 'chart.grid.labels.yAxis.scaleMin')) {
- mergedConfig.chart.grid.labels.yAxis.scaleMin = this.config.chart.grid.labels.yAxis.scaleMin;
- } else {
- mergedConfig.chart.grid.labels.yAxis.scaleMin = null;
- }
-
- if (this.config && this.hasDeepProperty(this.config, 'chart.grid.labels.yAxis.scaleMax')) {
- mergedConfig.chart.grid.labels.yAxis.scaleMax = this.config.chart.grid.labels.yAxis.scaleMax;
- } else {
- mergedConfig.chart.grid.labels.yAxis.scaleMax = null;
- }
+
+
+
+ {{ FINAL_CONFIG.chart.grid.labels.axis.yLabel }}
+
+
+ {{ FINAL_CONFIG.chart.grid.labels.axis.xLabel }}
+
+
- if (this.config && this.hasDeepProperty(this.config, 'chart.zoom.startIndex')) {
- mergedConfig.chart.zoom.startIndex = this.config.chart.zoom.startIndex;
- } else {
- mergedConfig.chart.zoom.startIndex = null;
- }
+
+
+
+
+
+
+
+
+
+
+
+ selectTimeLabel(label, i)">
+ {{ label.text || "" }}
+
+
+
+ selectTimeLabel(label, i)" />
+
+
+
+
- if (this.config && this.hasDeepProperty(this.config, 'chart.zoom.endIndex')) {
- mergedConfig.chart.zoom.endIndex = this.config.chart.zoom.endIndex;
- } else {
- mergedConfig.chart.zoom.endIndex = null;
- }
+
+
+
+
+
+
+
+
+
+ {{ annotation.config.label.text }}
+
+
+
- if (this.config && this.hasDeepProperty(this.config, 'chart.grid.labels.yAxis.groupColor')) {
- mergedConfig.chart.grid.labels.yAxis.groupColor = this.config.chart.grid.labels.yAxis.groupColor;
- } else {
- mergedConfig.chart.grid.labels.yAxis.groupColor = null;
- }
+
+
+
+
+
+
+
+
- if (this.config && this.config.chart.annotations && Array.isArray(this.config.chart.annotations) && this.config.chart.annotations.length) {
- mergedConfig.chart.annotations = this.config.chart.annotations.map(annotation => {
- return useNestedProp({
- defaultConfig: DEFAULT_CONFIG.chart.annotations[0],
- userConfig: annotation
- })
- })
- } else {
- mergedConfig.chart.annotations = [];
- }
+
+
- // ----------------------------------------------------------------------------
+
+
+
- if (mergedConfig.theme) {
- return {
- ...useNestedProp({
- userConfig: this.themes.vue_ui_xy[mergedConfig.theme] || this.config,
- defaultConfig: mergedConfig
- }),
- customPalette: this.themePalettes[mergedConfig.theme] || this.palette
- }
- } else {
- return mergedConfig
- }
- },
- prepareChart() {
- if(this.objectIsEmpty(this.dataset)) {
- this.error({
- componentName: 'VueUiXy',
- type: 'dataset'
- })
- } else {
- this.dataset.forEach((ds, i) => {
- if([null, undefined].includes(ds.name)) {
- this.error({
- componentName: 'VueUiXy',
- type: 'datasetSerieAttribute',
- property: 'name (string)',
- index: i
- })
- }
- })
- }
+
+
+
- if(this.FINAL_CONFIG.showWarnings) {
- this.dataset.forEach((datapoint) => {
- datapoint.series.forEach((s, j) => {
- if(!this.isSafeValue(s)) {
- console.warn(`VueUiXy has detected an unsafe value in your dataset:\n-----> The serie '${datapoint.name}' contains the value '${s}' at index ${j}.\n'${s}' was converted to null to allow the chart to display.`)
+
+
+
+
+
+
+
- // Legend height to substract
- let legend = null;
- let legendHeight = 0
- if (this.FINAL_CONFIG.chart.legend.show && this.$refs.chartLegend) {
- legend = this.$refs.chartLegend;
- legendHeight = legend.getBoundingClientRect().height;
- }
+
+
+
+
+
+
+
- // Source height to substract
- let sourceHeight = 0;
- if (this.$refs.source) {
- sourceHeight = this.$refs.source.getBoundingClientRect().height;
- }
+
+ setPrecog('start', v)"
+ @futureEnd="v => setPrecog('end', v)"
+ :timeLabels="allTimeLabels"
+ :isPreview="isPrecog"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
- // NoTitle height to substract
- let noTitleHeight = 0;
- if (this.$refs.noTitle) {
- noTitleHeight = this.$refs.noTitle.getBoundingClientRect().height;
- }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ legendItem.name }}
+
+
+
+
+
+
- this.height = height
- - titleHeight
- - legendHeight
- - slicerHeight
- - sourceHeight
- - noTitleHeight
- - 12;
-
- this.width = width;
- this.viewBox = `0 0 ${this.width < 0 ? 10 : this.width} ${this.height < 0 ? 10 : this.height}`;
- this.convertSizes();
-
- const ro = new ResizeObserver((entries) => {
- for(const entry of entries) {
- if (this.FINAL_CONFIG.chart.title.show && this.$refs.chartTitle) {
- titleHeight = this.$refs.chartTitle.getBoundingClientRect().height;
- } else {
- titleHeight = 0;
- }
- if (this.$refs.chartSlicer && this.$refs.chartSlicer.$el) {
- slicerHeight = this.$refs.chartSlicer.$el.getBoundingClientRect().height;
- } else {
- slicerHeight = 0;
- }
- if (this.$refs.chartLegend) {
- legendHeight = this.$refs.chartLegend.getBoundingClientRect().height;
- } else {
- legendHeight = 0;
- }
- if (this.$refs.source) {
- sourceHeight = this.$refs.source.getBoundingClientRect().height;
- } else {
- sourceHeight = 0;
- }
- if (this.$refs.noTitle) {
- noTitleHeight = this.$refs.noTitle.getBoundingClientRect().height;
- } else {
- noTitleHeight = 0;
- }
- requestAnimationFrame(() => {
- this.height = entry.contentRect.height
- - titleHeight
- - legendHeight
- - slicerHeight
- - sourceHeight
- - noTitleHeight
- - 12
-
- this.width = entry.contentBoxSize[0].inlineSize;
- this.viewBox = `0 0 ${this.width < 0 ? 10 : this.width} ${this.height < 0 ? 10 : this.height}`;
- this.convertSizes();
- })
- }
- })
- this.resizeObserver = ro;
- this.observedEl = parent;
+
+
+
- ro.observe(parent);
+
+
+
+
+
+
+
+
+
- } else {
- this.height = this.FINAL_CONFIG.chart.height;
- this.width = this.FINAL_CONFIG.chart.width;
- this.viewBox = `0 0 ${this.width} ${this.height}`;
- this.fontSizes.dataLabels = this.FINAL_CONFIG.chart.grid.labels.fontSize;
- this.fontSizes.yAxis = this.FINAL_CONFIG.chart.grid.labels.axis.fontSize;
- this.fontSizes.xAxis = this.FINAL_CONFIG.chart.grid.labels.xAxisLabels.fontSize;
- this.fontSizes.plotLabels = this.FINAL_CONFIG.chart.labels.fontSize;
- this.plotRadii.plot = this.FINAL_CONFIG.plot.radius;
- this.plotRadii.line = this.FINAL_CONFIG.line.radius;
- }
- },
- selectMinimapIndex(minimapIndex) {
- this.selectedMinimapIndex = minimapIndex;
- },
- convertSizes() {
- if (!this.FINAL_CONFIG.responsiveProportionalSizing) {
- this.fontSizes.dataLabels = this.FINAL_CONFIG.chart.grid.labels.fontSize;
- this.fontSizes.yAxis = this.FINAL_CONFIG.chart.grid.labels.axis.fontSize;
- this.fontSizes.xAxis = this.FINAL_CONFIG.chart.grid.labels.xAxisLabels.fontSize;
- this.fontSizes.plotLabels = this.FINAL_CONFIG.chart.labels.fontSize;
- this.plotRadii.plot = this.FINAL_CONFIG.plot.radius;
- this.plotRadii.line = this.FINAL_CONFIG.line.radius;
- return;
- }
- // Adaptative sizes in responsive mode
- this.fontSizes.dataLabels = this.translateSize({
- relator: this.height,
- adjuster: 400,
- source: this.FINAL_CONFIG.chart.grid.labels.fontSize,
- threshold: 10,
- fallback: 10
- });
- this.fontSizes.yAxis = this.translateSize({
- relator: this.width,
- adjuster: 1000,
- source: this.FINAL_CONFIG.chart.grid.labels.axis.fontSize,
- threshold: 10,
- fallback: 10
- });
- this.fontSizes.xAxis = this.translateSize({
- relator: this.width,
- adjuster: 1000,
- source: this.FINAL_CONFIG.chart.grid.labels.xAxisLabels.fontSize,
- threshold: 10,
- fallback: 10
- });
- this.fontSizes.plotLabels = this.translateSize({
- relator: this.width,
- adjuster: 800,
- source: this.FINAL_CONFIG.chart.labels.fontSize,
- threshold: 10,
- fallback: 10
- });
- this.plotRadii.plot = this.translateSize({
- relator: this.width,
- adjuster: 800,
- source: this.FINAL_CONFIG.plot.radius,
- threshold: 1,
- fallback: 1
- });
- this.plotRadii.line = this.translateSize({
- relator: this.width,
- adjuster: 800,
- source: this.FINAL_CONFIG.line.radius,
- threshold: 1,
- fallback: 1
- })
- },
- toggleStack() {
- this.mutableConfig.isStacked = !this.mutableConfig.isStacked
- if (!this.mutableConfig.isStacked) {
- this.mutableConfig.useIndividualScale = this.FINAL_CONFIG.chart.grid.labels.yAxis.useIndividualScale;
- } else {
- this.mutableConfig.useIndividualScale = true
- }
- },
- toggleTable() {
- this.mutableConfig.showTable = !this.mutableConfig.showTable;
- },
- toggleLabels() {
- this.mutableConfig.dataLabels.show = !this.mutableConfig.dataLabels.show;
- },
- toggleTooltip() {
- this.mutableConfig.showTooltip = !this.mutableConfig.showTooltip;
- },
- checkAutoScaleError(datapoint) {
- if (datapoint.autoScaling) {
- if (!this.FINAL_CONFIG.chart.grid.labels.yAxis.useIndividualScale) {
- console.warn(`VueUiXy (datapoint: ${datapoint.name}) : autoScaling only works when config.chart.grid.labels.yAxis.useIndividualScale is set to true`)
- }
- if (!this.FINAL_CONFIG.chart.grid.labels.yAxis.stacked) {
- console.warn(`VueUiXy (datapoint: ${datapoint.name}) : autoScaling only works when config.chart.grid.labels.yAxis.stacked is set to true`)
- }
- }
- },
- createArea(plots, zero) {
- if(!plots[0]) return [-10,-10, '', -10, -10];
- const start = { x: plots[0].x, y: zero };
- const end = { x: plots.at(-1).x, y: zero };
- const path = [];
- plots.forEach(plot => {
- path.push(`${plot.x},${plot.y} `);
- });
- return [ start.x, start.y, ...path, end.x, end.y].toString();
- },
- createStar,
- createPolygonPath,
- fillArray(len, source) {
- let res = Array(len).fill(0);
- for (let i = 0; i < source.length && i < len; i += 1) {
- res[i] = source[i];
- }
- return res;
- },
- async setupSlicer() {
- if ((this.FINAL_CONFIG.chart.zoom.startIndex !== null || this.FINAL_CONFIG.chart.zoom.endIndex !== null) && this.$refs.chartSlicer) {
- if (this.FINAL_CONFIG.chart.zoom.startIndex !== null) {
- await this.$nextTick();
- await this.$nextTick();
- this.$refs.chartSlicer.setStartValue(this.FINAL_CONFIG.chart.zoom.startIndex);
- }
- if (this.FINAL_CONFIG.chart.zoom.endIndex !== null) {
- await this.$nextTick();
- await this.$nextTick();
- this.$refs.chartSlicer.setEndValue(this.validSlicerEnd(this.FINAL_CONFIG.chart.zoom.endIndex + 1));
- }
- } else {
- this.slicer = {
- start: 0,
- end: Math.max(...this.dataset.map(datapoint => this.largestTriangleThreeBucketsArray({data:datapoint.series, threshold: this.FINAL_CONFIG.downsample.threshold}).length))
- };
- this.slicerStep += 1;
- }
- },
- refreshSlicer() {
- this.setupSlicer();
- },
- validSlicerEnd(v) {
- const max = Math.max(...this.dataset.map(datapoint => this.largestTriangleThreeBucketsArray({data:datapoint.series, threshold: this.FINAL_CONFIG.downsample.threshold}).length));
- if (v > max) {
- return max;
- }
- if (v < 0 || (this.FINAL_CONFIG.chart.zoom.startIndex !== null && v < this.FINAL_CONFIG.chart.zoom.startIndex)) {
- if (this.FINAL_CONFIG.chart.zoom.startIndex !== null) {
- return this.FINAL_CONFIG.chart.zoom.startIndex + 1
- } else {
- return 1
- }
+
+
+
+
+
+
+
+
+
+
+
+ {{ !isNaN(Number(td)) ? dataLabel({
+ p: FINAL_CONFIG.chart.labels.prefix,
+ v: td,
+ s: FINAL_CONFIG.chart.labels.suffix,
+ r: FINAL_CONFIG.table.rounding,
+ }) : td }}
+
+
+
+
+
- if(plot.value >= 0) {
- return this.checkNaN(zeroForPositiveValuesOnly - plot.y <= 0 ? 0.00001 : zeroForPositiveValuesOnly - plot.y);
- } else {
- return this.checkNaN(plot.y - this.zero <= 0 ? 0.00001 : plot.y - this.zero);
- }
- },
- calcIndividualHeight(plot) {
- if(plot.value >= 0) {
- return this.checkNaN(plot.zeroPosition - plot.y <= 0 ? 0.00001 : plot.zeroPosition - plot.y)
- } else {
- return this.checkNaN(plot.y - plot.zeroPosition <= 0 ? 0.00001 : plot.zeroPosition - plot.y)
- }
- },
- calcRectWidth() {
- if(this.mutableConfig.useIndividualScale && this.mutableConfig.isStacked) {
- return this.slot.line - ((this.drawingArea.width / this.maxSeries) * 0.1);
- }
- return this.slot.bar;
- },
- calcRectX(plot) {
- if (this.mutableConfig.useIndividualScale && this.mutableConfig.isStacked) {
- return plot.x + ((this.drawingArea.width / this.maxSeries) * 0.05)
- }
- return plot.x + (this.slot.bar / 2);
- },
- calcRectY(plot) {
- if(plot.value >= 0) return plot.y;
- return [null, undefined, NaN, Infinity, -Infinity].includes(this.zero) ? this.drawingArea.bottom : this.zero;
- },
- calcIndividualRectY(plot) {
- if(plot.value >= 0) return plot.y;
- return [null, undefined, NaN, Infinity, -Infinity].includes(plot.zeroPosition) ? 0 : plot.zeroPosition;
- },
- canShowValue(value) {
- return ![null, undefined, NaN, Infinity, -Infinity].includes(value);
- },
- findClosestValue(val, arr) {
- let closest = arr[0];
- let minDifference = Math.abs(val - arr[0]);
- for (let i = 1; i < arr.length; i += 1) {
- const difference = Math.abs(val - arr[i]);
- if (difference < minDifference && arr[i] < val) {
- closest = arr[i];
- minDifference = difference;
- }
- }
- return closest;
- },
- ratioToMax(value) {
- return value / (this.canShowValue(this.absoluteMax) ? this.absoluteMax : 1);
- },
- selectX(index) {
- this.$emit('selectX',
- {
- dataset: this.relativeDataset.map(s => {
- return {
- name: s.name,
- value: [null, undefined, NaN].includes(s.absoluteValues[index]) ? null : s.absoluteValues[index],
- color: s.color,
- type: s.type
- }
- }),
- index,
- indexLabel: this.FINAL_CONFIG.chart.grid.labels.xAxisLabels.values[index]
- }
- );
- },
- getData(){
- return this.absoluteDataset.map(s => {
- return {
- values: s.absoluteValues,
- color: s.color,
- name: s.name,
- type: s.type
- }
- });
- },
- async getImage({ scale = 2} = {}) {
- if (!this.$refs.chart) return
- const { width, height } = this.$refs.chart.getBoundingClientRect();
- const aspectRatio = width / height;
- const { imageUri, base64 } = await img({ domElement: this.$refs.chart, base64: true, img: true, scale})
- return {
- imageUri,
- base64,
- title: this.FINAL_CONFIG.chart.title.text,
- width,
- height,
- aspectRatio
- }
- },
- segregate(legendItem){
- if(this.segregatedSeries.includes(legendItem.id)) {
- this.segregatedSeries = this.segregatedSeries.filter(id => id !== legendItem.id);
- }else {
- if(this.segregatedSeries.length + 1 === this.safeDataset.length) return;
- this.segregatedSeries.push(legendItem.id);
- }
- this.$emit('selectLegend', this.relativeDataset.map(s => {
- return {
- name: s.name,
- values: s.absoluteValues,
- color: s.color,
- type: s.type
- }
- }));
- this.segregateStep += 1;
- },
- toggleTooltipVisibility(show, selectedIndex = null) {
- this.isTooltip = show;
- if(show) {
- this.selectedSerieIndex = selectedIndex;
- }else{
- this.selectedSerieIndex = null;
- }
- },
- toggleFullscreen(state) {
- this.isFullscreen = state;
- this.step += 1;
- },
- showSpinnerPdf() {
- this.isPrinting = true;
- },
- async generatePdf() {
- this.showSpinnerPdf();
- clearTimeout(this.__to__);
- this.isPrinting = true; // Set isPrinting to true before starting
-
- this.__to__ = setTimeout(async () => {
- try {
- const { default: pdf } = await import('../pdf.js');
- await pdf({
- domElement: document.getElementById(`vue-ui-xy_${this.uniqueId}`),
- fileName: this.FINAL_CONFIG.chart.title.text || 'vue-ui-xy',
- options: this.FINAL_CONFIG.chart.userOptions.print
- });
- } catch (error) {
- console.error('Error generating PDF:', error);
- } finally {
- this.isPrinting = false;
- }
- }, 100);
- },
- generateCsv(callback=null) {
- const title = [[this.FINAL_CONFIG.chart.title.text], [this.FINAL_CONFIG.chart.title.subtitle.text], [""]];
- const head = ["",...this.table.head.map(h => h.label)]
- const body = this.table.body
- const table = title.concat([head]).concat(body);
- const csvContent = this.createCsvContent(table);
- if(!callback) {
- this.downloadCsv({ csvContent, title: this.FINAL_CONFIG.chart.title.text || 'vue-ui-xy'});
- } else {
- callback(csvContent);
- }
-
- },
- showSpinnerImage() {
- this.isImaging = true;
- },
- async generateImage() {
- this.showSpinnerImage();
- clearTimeout(this.__to__);
- this.isImaging = true;
- this.__to__ = setTimeout(async () => {
- try {
- const { default: img } = await import('../img.js');
- await img({
- domElement: document.getElementById(`vue-ui-xy_${this.uniqueId}`),
- fileName: this.FINAL_CONFIG.chart.title.text || 'vue-ui-xy',
- format: 'png',
- options: this.FINAL_CONFIG.chart.userOptions.print
- });
- } catch (error) {
- console.error('Error generating image:', error);
- } finally {
- this.isImaging = false;
- }
- }, 100);
- },
- }
-}
-
+
+
+
+
\ No newline at end of file
diff --git a/src/directives/vFitText.js b/src/directives/vFitText.js
new file mode 100644
index 00000000..dc6e68f7
--- /dev/null
+++ b/src/directives/vFitText.js
@@ -0,0 +1,75 @@
+export default {
+ mounted(el, binding) {
+ fit(el, binding.value)
+ },
+ updated(el, binding) {
+ fit(el, binding.value)
+ }
+}
+
+function fit(el, {
+ cellWidth,
+ cellHeight,
+ maxFontSize,
+ minFontSize,
+ index,
+ reportRotation,
+ reportHide,
+ rotateAll,
+ hideAll
+}) {
+ el.removeAttribute('transform');
+ el.removeAttribute('visibility');
+
+ let localRotate = false;
+ let localHide = false;
+
+ el.setAttribute('font-size', maxFontSize);
+ const length = el.getComputedTextLength();
+
+ if (length <= cellWidth) {
+ reportRotation(index, false);
+ reportHide(index, false);
+ } else {
+ const shrinkFs = Math.floor(maxFontSize * cellWidth / length);
+ if (shrinkFs >= minFontSize) {
+ el.setAttribute('font-size', shrinkFs);
+ reportRotation(index, false);
+ reportHide(index, false);
+ } else {
+ reportRotation(index, true);
+ reportHide(index, false);
+ localRotate = true;
+ }
+ }
+
+ if (rotateAll) localRotate = true;
+
+ if (localRotate) {
+ el.setAttribute('font-size', maxFontSize);
+
+ const bb = el.getBBox();
+ const cx = bb.x + bb.width / 2;
+ const cy = bb.y + bb.height / 2;
+
+ el.setAttribute('transform', `rotate(-90 ${cx} ${cy})`);
+
+ const needed = el.getBBox().width;
+ if (needed <= cellHeight) {
+ reportHide(index, false);
+ } else {
+ const shrinkFs2 = Math.floor(maxFontSize * cellHeight / needed);
+ if (shrinkFs2 >= minFontSize) {
+ el.setAttribute('font-size', shrinkFs2);
+ reportHide(index, false);
+ } else {
+ localHide = true;
+ reportHide(index, true);
+ }
+ }
+ }
+
+ if (hideAll || localHide) {
+ el.setAttribute('visibility', 'hidden');
+ }
+}
diff --git a/src/index.js b/src/index.js
index f6456aea..60117014 100755
--- a/src/index.js
+++ b/src/index.js
@@ -1,7 +1,17 @@
import { defineAsyncComponent } from "vue";
import getVueDataUiConfig from "./getVueDataUiConfig";
import getThemeConfig from "./getThemeConfig";
-import { getPalette, createWordCloudDatasetFromPlainText, abbreviate, createTSpans, createStraightPath, createSmoothPath, getCumulativeAverage, getCumulativeMedian } from "./lib";
+import {
+ abbreviate,
+ createSmoothPath,
+ createStraightPath,
+ createTSpans,
+ createWordCloudDatasetFromPlainText,
+ formatSmallValue,
+ getCumulativeAverage,
+ getCumulativeMedian,
+ getPalette,
+} from "./lib";
import { lightenColor, darkenColor, shiftColorHue, mergeConfigs } from "./exposedLib";
import { useObjectBindings } from "./useObjectBindings";
@@ -55,6 +65,7 @@ export const VueUiTimer = defineAsyncComponent(() => import("./components/vue-ui
export const VueUiTiremarks = defineAsyncComponent(() => import("./components/vue-ui-tiremarks.vue"))
export const VueUiTreemap = defineAsyncComponent(() => import("./components/vue-ui-treemap.vue"))
export const VueUiVerticalBar = defineAsyncComponent(() => import("./components/vue-ui-vertical-bar.vue"))
+export const VueUiHorizontalBar = defineAsyncComponent(() => import("./components/vue-ui-vertical-bar.vue"))
export const VueUiWaffle = defineAsyncComponent(() => import("./components/vue-ui-waffle.vue"))
export const VueUiWheel = defineAsyncComponent(() => import("./components/vue-ui-wheel.vue"))
export const VueUiWordCloud = defineAsyncComponent(() => import("./components/vue-ui-word-cloud.vue"))
@@ -78,6 +89,7 @@ export {
createTSpans,
createWordCloudDatasetFromPlainText,
darkenColor,
+ formatSmallValue,
getCumulativeAverage,
getCumulativeMedian,
getPalette,
diff --git a/src/lib.js b/src/lib.js
index 2feca3d0..e8f50c81 100755
--- a/src/lib.js
+++ b/src/lib.js
@@ -1,3 +1,4 @@
+import { toRaw, isRef, unref } from "vue";
import errors from "./errors.json";
export function makeDonut(
@@ -161,6 +162,17 @@ export function treeShake({ defaultConfig, userConfig }) {
}
}
});
+
+ // Allow override of default empty objects in config
+ Object.keys(userConfig).forEach(key => {
+ if (!Object.hasOwn(finalConfig, key)) {
+ const val = userConfig[key];
+ finalConfig[key] = (val && typeof val === 'object' && !Array.isArray(val))
+ ? { ...val }
+ : val;
+ }
+ });
+
return finalConfig;
}
@@ -404,11 +416,33 @@ export function convertColorToHex(color) {
return null;
}
+ color = isRef?.(color) ? unref(color) : color;
+
color = convertNameColorToHex(color);
- if (color === 'transparent') {
- return "#FFFFFF00";
+ if (Array.isArray(color)) {
+ const [r, g, b, a = 1] = color;
+ color = `rgba(${r},${g},${b},${a})`;
+ } else if (typeof color === 'object') {
+ if (Number.isFinite(color.r) && Number.isFinite(color.g) && Number.isFinite(color.b)) {
+ const a = Number.isFinite(color.a) ? color.a : 1;
+ color = `rgba(${color.r},${color.g},${color.b},${a})`;
+ } else {
+ return null;
+ }
+ } else if (typeof color === 'number') {
+ const n = color >>> 0; // uint32
+ const hex = n.toString(16).padStart(n <= 0xFFFFFF ? 6 : 8, '0');
+ return `#${hex.length === 6 ? hex + 'ff' : hex}`;
+ } else if (typeof color !== 'string') {
+ return null;
+ }
+
+ color = color.trim();
+
+ if (color.toLowerCase() === 'transparent') {
+ return '#FFFFFF00';
}
color = color.replace(shorthandRegex, (_, r, g, b, a) => {
@@ -635,50 +669,64 @@ export function createStar({
}
export function giftWrap({ series }) {
- series = series.sort((a, b) => a.x - b.x);
- function polarAngle(a, b, c) {
- const x = (a.x - b.x) * (c.x - b.x) + (a.y - b.y) * (c.y - b.y);
- const y = (a.x - b.x) * (c.y - b.y) - (c.x - b.x) * (a.y - b.y);
- return Math.atan2(y, x);
- }
- const perimeter = [];
- let currentPoint;
- currentPoint = series[0];
- for (const p of series) {
- if (p.x < currentPoint.x) {
- currentPoint = p;
- }
- }
- perimeter[0] = currentPoint;
- let endpoint, secondlast;
- let minAngle, newEnd;
- endpoint = perimeter[0];
- secondlast = { x: endpoint.x, y: endpoint.y + 1 };
- do {
- minAngle = Math.PI;
- for (const p of series) {
- currentPoint = polarAngle(secondlast, endpoint, p);
- if (currentPoint <= minAngle) {
- newEnd = p;
- minAngle = currentPoint;
+ if (!Array.isArray(series) || series.length === 0) return "";
+
+ const pts = Array.from(
+ new Map(
+ series
+ .filter(p => p && Number.isFinite(p.x) && Number.isFinite(p.y))
+ .map(p => [`${p.x},${p.y}`, { x: +p.x, y: +p.y }])
+ ).values()
+ );
+ if (pts.length === 0) return "";
+ if (pts.length === 1) return `${Math.round(pts[0].x)},${Math.round(pts[0].y)} `;
+
+ const dist2 = (a, b) => {
+ const dx = a.x - b.x, dy = a.y - b.y;
+ return dx * dx + dy * dy;
+ };
+ const cross = (o, a, b) =>
+ (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
+
+ let start = pts[0];
+ for (const p of pts) {
+ if (p.x < start.x || (p.x === start.x && p.y < start.y)) start = p;
+ }
+
+ const hull = [start];
+ let endpoint = start;
+
+ const maxSteps = pts.length + 2;
+ let steps = 0;
+
+ while (true) {
+ if (++steps > maxSteps) break;
+ let candidate = pts[0] === endpoint ? pts[1] : pts[0];
+ for (const p of pts) {
+ if (p === endpoint || p === candidate) continue;
+ const c = cross(endpoint, candidate, p);
+ if (c < 0) continue;
+ if (c > 0) {
+ candidate = p;
+ } else {
+ if (dist2(endpoint, p) > dist2(endpoint, candidate)) {
+ candidate = p;
+ }
}
}
- if (newEnd !== perimeter[0]) {
- perimeter.push(newEnd);
- secondlast = endpoint;
- endpoint = newEnd;
- }
- } while (newEnd !== perimeter[0]);
- let result;
- perimeter.forEach((res) => {
- if (res && res.x && res.y) {
- result += `${Math.round(res.x)},${Math.round(res.y)} `;
- }
- });
- result = result.replaceAll("undefined", "");
+ if (candidate === start) break;
+ hull.push(candidate);
+ endpoint = candidate;
+ }
+
+ let result = "";
+ for (const p of hull) {
+ result += `${Math.round(p.x)},${Math.round(p.y)} `;
+ }
return result;
}
+
export function degreesToRadians(degrees) {
return (degrees * Math.PI) / 180;
}
@@ -727,21 +775,71 @@ export function adaptColorToBackground(bgColor) {
return "#000000";
}
-export function convertConfigColors(config) {
- for (const key in config) {
- if (typeof config[key] === 'object' && !Array.isArray(config[key]) && config[key] !== null) {
- convertConfigColors(config[key]);
- } else if (['color', 'backgroundColor', 'stroke'].includes(key)) {
- if (config[key] === '') {
- config[key] = '#000000';
- } else if (config[key] === 'transparent') {
- config[key] = '#FFFFFF00'
- } else {
- config[key] = convertColorToHex(config[key]);
+function isPlainObject(x) {
+ return (
+ x !== null &&
+ typeof x === 'object' &&
+ Object.prototype.toString.call(x) === '[object Object]' &&
+ (x.constructor === Object || x.constructor == null)
+ );
+}
+
+// Vue reactivity objects often expose these fields; avoid descending into them
+function looksLikeVueReactive(x) {
+ return !!x && (
+ x.__v_isRef ||
+ x.__v_isReactive ||
+ x.__v_isReadonly ||
+ x.effect ||
+ x.dep || x.deps || x.subs
+ );
+}
+
+function normalizeColor(raw) {
+ if (raw === '') return '#000000';
+ if (raw === 'transparent') return '#FFFFFF00';
+ const hex = convertColorToHex(raw);
+ return hex ?? raw;
+}
+
+export function convertConfigColors(config, seen = new WeakSet()) {
+ const obj = toRaw(config);
+ if (!isPlainObject(obj) || seen.has(obj)) return obj;
+ seen.add(obj);
+
+ for (const key in obj) {
+ const v = isRef(obj[key]) ? unref(obj[key]) : obj[key];
+
+ if (key === 'color' || key === 'backgroundColor') {
+ if (typeof v === 'string') obj[key] = normalizeColor(v);
+ continue;
+ }
+
+ // "stroke" can be EITHER a color string OR a nested object
+ if (key === 'stroke') {
+ if (typeof v === 'string') {
+ obj[key] = normalizeColor(v);
+ } else if (isPlainObject(v) && !looksLikeVueReactive(v)) {
+ convertConfigColors(v, seen);
+ }
+ continue;
+ }
+
+ if (Array.isArray(v)) {
+ for (const item of v) {
+ if (isPlainObject(item) && !looksLikeVueReactive(item)) {
+ convertConfigColors(item, seen);
+ }
}
+ continue;
+ }
+
+ if (isPlainObject(v) && !looksLikeVueReactive(v)) {
+ convertConfigColors(v, seen);
}
}
- return config;
+
+ return obj;
}
export function calcLinearProgression(plots) {
@@ -1320,7 +1418,8 @@ export function objectIsEmpty(obj) {
return Object.keys(obj).length === 0
}
-export function error({ componentName, type, property = '', index = '', key = '', warn = true }) {
+export function error({ componentName, type, property = '', index = '', key = '', warn = true, debug = true }) {
+ if (!debug) return;
const message = `\n> ${errors[type].replace('#COMP#', componentName).replace('#ATTR#', property).replace('#INDX#', index).replace('#KEY#', key)}\n`;
if (warn) {
console.warn(message)
@@ -1343,28 +1442,64 @@ export function generateSpiralCoordinates({ points, a, b, angleStep, startX, sta
return coordinates;
}
-export function createSpiralPath({ points, a, b, angleStep, startX, startY }) {
- const coordinates = generateSpiralCoordinates({ points, a: a || 6, b: b || 6, angleStep: angleStep || 0.07, startX, startY });
- let path = `M${coordinates[0].x} ${coordinates[0].y}`;
+function boundsOf(points) {
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
+ for (const p of points) {
+ if (p.x < minX) minX = p.x;
+ if (p.y < minY) minY = p.y;
+ if (p.x > maxX) maxX = p.x;
+ if (p.y > maxY) maxY = p.y;
+ }
+ return { minX, minY, maxX, maxY, width: maxX - minX || 1, height: maxY - minY || 1 };
+}
+function buildSmoothPath(coordinates) {
+ if (!coordinates.length) return "";
+ let path = `M${coordinates[0].x} ${coordinates[0].y}`;
for (let i = 1; i < coordinates.length - 2; i += 2) {
const p0 = coordinates[i - 1];
const p1 = coordinates[i];
const p2 = coordinates[i + 1];
const p3 = coordinates[i + 2];
-
const xc1 = (p0.x + p1.x) / 2;
const yc1 = (p0.y + p1.y) / 2;
const xc2 = (p1.x + p2.x) / 2;
const yc2 = (p1.y + p2.y) / 2;
const xc3 = (p2.x + p3.x) / 2;
const yc3 = (p2.y + p3.y) / 2;
-
path += ` C${xc1} ${yc1}, ${xc2} ${yc2}, ${xc3} ${yc3}`;
}
return path;
}
+export function createSpiralPath({
+ maxPoints,
+ a = 6,
+ b = 6,
+ angleStep = 0.07,
+ startX,
+ startY,
+ boxWidth,
+ boxHeight,
+ padding = 12
+}) {
+ const raw = generateSpiralCoordinates({ points: maxPoints, a, b, angleStep, startX: 0, startY: 0 });
+ const { minX, minY, maxX, maxY, width, height } = boundsOf(raw);
+ const cx = (minX + maxX) / 2;
+ const cy = (minY + maxY) / 2;
+ const availW = Math.max(1, boxWidth - 2 * padding);
+ const availH = Math.max(1, boxHeight - 2 * padding);
+ const s = Math.min(availW / width, availH / height);
+ const tx = startX - cx * s;
+ const ty = startY - cy * s;
+ return function toPath(endPoints) {
+ const n = Math.max(2, Math.min(Math.round(endPoints), raw.length));
+ const fitted = raw.slice(0, n).map(p => ({ x: p.x * s + tx, y: p.y * s + ty }));
+ return buildSmoothPath(fitted);
+ };
+}
+
+
export function calculateDistance(point1, point2) {
return Math.sqrt((point1.x - point2.x) ** 2 + (point1.y - point2.y) ** 2);
}
@@ -1433,151 +1568,160 @@ export function getMissingDatasetAttributes({ datasetObject, requiredAttributes
return errors;
}
+const COLOR_MAP = {
+ ALICEBLUE: "#F0F8FF",
+ ANTIQUEWHITE: "#FAEBD7",
+ AQUA: "#00FFFF",
+ AQUAMARINE: "#7FFFD4",
+ AZURE: "#F0FFFF",
+ BEIGE: "#F5F5DC",
+ BISQUE: "#FFE4C4",
+ BLACK: "#000000",
+ BLANCHEDALMOND: "#FFEBCD",
+ BLUE: "#0000FF",
+ BLUEVIOLET: "#8A2BE2",
+ BROWN: "#A52A2A",
+ BURLYWOOD: "#DEB887",
+ CADETBLUE: "#5F9EA0",
+ CHARTREUSE: "#7FFF00",
+ CHOCOLATE: "#D2691E",
+ CORAL: "#FF7F50",
+ CORNFLOWERBLUE: "#6495ED",
+ CORNSILK: "#FFF8DC",
+ CRIMSON: "#DC143C",
+ CYAN: "#00FFFF",
+ DARKBLUE: "#00008B",
+ DARKCYAN: "#008B8B",
+ DARKGOLDENROD: "#B8860B",
+ DARKGREY: "#A9A9A9",
+ DARKGREEN: "#006400",
+ DARKKHAKI: "#BDB76B",
+ DARKMAGENTA: "#8B008B",
+ DARKOLIVEGREEN: "#556B2F",
+ DARKORANGE: "#FF8C00",
+ DARKORCHID: "#9932CC",
+ DARKRED: "#8B0000",
+ DARKSALMON: "#E9967A",
+ DARKSEAGREEN: "#8FBC8F",
+ DARKSLATEBLUE: "#483D8B",
+ DARKSLATEGREY: "#2F4F4F",
+ DARKTURQUOISE: "#00CED1",
+ DARKVIOLET: "#9400D3",
+ DEEPPINK: "#FF1493",
+ DEEPSKYBLUE: "#00BFFF",
+ DIMGRAY: "#696969",
+ DODGERBLUE: "#1E90FF",
+ FIREBRICK: "#B22222",
+ FLORALWHITE: "#FFFAF0",
+ FORESTGREEN: "#228B22",
+ FUCHSIA: "#FF00FF",
+ GAINSBORO: "#DCDCDC",
+ GHOSTWHITE: "#F8F8FF",
+ GOLD: "#FFD700",
+ GOLDENROD: "#DAA520",
+ GREY: "#808080",
+ GREEN: "#008000",
+ GREENYELLOW: "#ADFF2F",
+ HONEYDEW: "#F0FFF0",
+ HOTPINK: "#FF69B4",
+ INDIANRED: "#CD5C5C",
+ INDIGO: "#4B0082",
+ IVORY: "#FFFFF0",
+ KHAKI: "#F0E68C",
+ LAVENDER: "#E6E6FA",
+ LAVENDERBLUSH: "#FFF0F5",
+ LAWNGREEN: "#7CFC00",
+ LEMONCHIFFON: "#FFFACD",
+ LIGHTBLUE: "#ADD8E6",
+ LIGHTCORAL: "#F08080",
+ LIGHTCYAN: "#E0FFFF",
+ LIGHTGOLDENRODYELLOW: "#FAFAD2",
+ LIGHTGREY: "#D3D3D3",
+ LIGHTGREEN: "#90EE90",
+ LIGHTPINK: "#FFB6C1",
+ LIGHTSALMON: "#FFA07A",
+ LIGHTSEAGREEN: "#20B2AA",
+ LIGHTSKYBLUE: "#87CEFA",
+ LIGHTSLATEGREY: "#778899",
+ LIGHTSTEELBLUE: "#B0C4DE",
+ LIGHTYELLOW: "#FFFFE0",
+ LIME: "#00FF00",
+ LIMEGREEN: "#32CD32",
+ LINEN: "#FAF0E6",
+ MAGENTA: "#FF00FF",
+ MAROON: "#800000",
+ MEDIUMAQUAMARINE: "#66CDAA",
+ MEDIUMBLUE: "#0000CD",
+ MEDIUMORCHID: "#BA55D3",
+ MEDIUMPURPLE: "#9370D8",
+ MEDIUMSEAGREEN: "#3CB371",
+ MEDIUMSLATEBLUE: "#7B68EE",
+ MEDIUMSPRINGGREEN: "#00FA9A",
+ MEDIUMTURQUOISE: "#48D1CC",
+ MEDIUMVIOLETRED: "#C71585",
+ MIDNIGHTBLUE: "#191970",
+ MINTCREAM: "#F5FFFA",
+ MISTYROSE: "#FFE4E1",
+ MOCCASIN: "#FFE4B5",
+ NAVAJOWHITE: "#FFDEAD",
+ NAVY: "#000080",
+ OLDLACE: "#FDF5E6",
+ OLIVE: "#808000",
+ OLIVEDRAB: "#6B8E23",
+ ORANGE: "#FFA500",
+ ORANGERED: "#FF4500",
+ ORCHID: "#DA70D6",
+ PALEGOLDENROD: "#EEE8AA",
+ PALEGREEN: "#98FB98",
+ PALETURQUOISE: "#AFEEEE",
+ PALEVIOLETRED: "#D87093",
+ PAPAYAWHIP: "#FFEFD5",
+ PEACHPUFF: "#FFDAB9",
+ PERU: "#CD853F",
+ PINK: "#FFC0CB",
+ PLUM: "#DDA0DD",
+ POWDERBLUE: "#B0E0E6",
+ PURPLE: "#800080",
+ RED: "#FF0000",
+ ROSYBROWN: "#BC8F8F",
+ ROYALBLUE: "#4169E1",
+ SADDLEBROWN: "#8B4513",
+ SALMON: "#FA8072",
+ SANDYBROWN: "#F4A460",
+ SEAGREEN: "#2E8B57",
+ SEASHELL: "#FFF5EE",
+ SIENNA: "#A0522D",
+ SILVER: "#C0C0C0",
+ SKYBLUE: "#87CEEB",
+ SLATEBLUE: "#6A5ACD",
+ SLATEGREY: "#708090",
+ SNOW: "#FFFAFA",
+ SPRINGGREEN: "#00FF7F",
+ STEELBLUE: "#4682B4",
+ TAN: "#D2B48C",
+ TEAL: "#008080",
+ THISTLE: "#D8BFD8",
+ TOMATO: "#FF6347",
+ TURQUOISE: "#40E0D0",
+ VIOLET: "#EE82EE",
+ WHEAT: "#F5DEB3",
+ WHITE: "#FFFFFF",
+ WHITESMOKE: "#F5F5F5",
+ YELLOW: "#FFFF00",
+ YELLOWGREEN: "#9ACD32",
+ REBECCAPURPLE: "#663399"
+};
+
export function convertNameColorToHex(colorName) {
- const colorMap = {
- ALICEBLUE: "#F0F8FF",
- ANTIQUEWHITE: "#FAEBD7",
- AQUA: "#00FFFF",
- AQUAMARINE: "#7FFFD4",
- AZURE: "#F0FFFF",
- BEIGE: "#F5F5DC",
- BISQUE: "#FFE4C4",
- BLACK: "#000000",
- BLANCHEDALMOND: "#FFEBCD",
- BLUE: "#0000FF",
- BLUEVIOLET: "#8A2BE2",
- BROWN: "#A52A2A",
- BURLYWOOD: "#DEB887",
- CADETBLUE: "#5F9EA0",
- CHARTREUSE: "#7FFF00",
- CHOCOLATE: "#D2691E",
- CORAL: "#FF7F50",
- CORNFLOWERBLUE: "#6495ED",
- CORNSILK: "#FFF8DC",
- CRIMSON: "#DC143C",
- CYAN: "#00FFFF",
- DARKBLUE: "#00008B",
- DARKCYAN: "#008B8B",
- DARKGOLDENROD: "#B8860B",
- DARKGREY: "#A9A9A9",
- DARKGREEN: "#006400",
- DARKKHAKI: "#BDB76B",
- DARKMAGENTA: "#8B008B",
- DARKOLIVEGREEN: "#556B2F",
- DARKORANGE: "#FF8C00",
- DARKORCHID: "#9932CC",
- DARKRED: "#8B0000",
- DARKSALMON: "#E9967A",
- DARKSEAGREEN: "#8FBC8F",
- DARKSLATEBLUE: "#483D8B",
- DARKSLATEGREY: "#2F4F4F",
- DARKTURQUOISE: "#00CED1",
- DARKVIOLET: "#9400D3",
- DEEPPINK: "#FF1493",
- DEEPSKYBLUE: "#00BFFF",
- DIMGRAY: "#696969",
- DODGERBLUE: "#1E90FF",
- FIREBRICK: "#B22222",
- FLORALWHITE: "#FFFAF0",
- FORESTGREEN: "#228B22",
- FUCHSIA: "#FF00FF",
- GAINSBORO: "#DCDCDC",
- GHOSTWHITE: "#F8F8FF",
- GOLD: "#FFD700",
- GOLDENROD: "#DAA520",
- GREY: "#808080",
- GREEN: "#008000",
- GREENYELLOW: "#ADFF2F",
- HONEYDEW: "#F0FFF0",
- HOTPINK: "#FF69B4",
- INDIANRED: "#CD5C5C",
- INDIGO: "#4B0082",
- IVORY: "#FFFFF0",
- KHAKI: "#F0E68C",
- LAVENDER: "#E6E6FA",
- LAVENDERBLUSH: "#FFF0F5",
- LAWNGREEN: "#7CFC00",
- LEMONCHIFFON: "#FFFACD",
- LIGHTBLUE: "#ADD8E6",
- LIGHTCORAL: "#F08080",
- LIGHTCYAN: "#E0FFFF",
- LIGHTGOLDENRODYELLOW: "#FAFAD2",
- LIGHTGREY: "#D3D3D3",
- LIGHTGREEN: "#90EE90",
- LIGHTPINK: "#FFB6C1",
- LIGHTSALMON: "#FFA07A",
- LIGHTSEAGREEN: "#20B2AA",
- LIGHTSKYBLUE: "#87CEFA",
- LIGHTSLATEGREY: "#778899",
- LIGHTSTEELBLUE: "#B0C4DE",
- LIGHTYELLOW: "#FFFFE0",
- LIME: "#00FF00",
- LIMEGREEN: "#32CD32",
- LINEN: "#FAF0E6",
- MAGENTA: "#FF00FF",
- MAROON: "#800000",
- MEDIUMAQUAMARINE: "#66CDAA",
- MEDIUMBLUE: "#0000CD",
- MEDIUMORCHID: "#BA55D3",
- MEDIUMPURPLE: "#9370D8",
- MEDIUMSEAGREEN: "#3CB371",
- MEDIUMSLATEBLUE: "#7B68EE",
- MEDIUMSPRINGGREEN: "#00FA9A",
- MEDIUMTURQUOISE: "#48D1CC",
- MEDIUMVIOLETRED: "#C71585",
- MIDNIGHTBLUE: "#191970",
- MINTCREAM: "#F5FFFA",
- MISTYROSE: "#FFE4E1",
- MOCCASIN: "#FFE4B5",
- NAVAJOWHITE: "#FFDEAD",
- NAVY: "#000080",
- OLDLACE: "#FDF5E6",
- OLIVE: "#808000",
- OLIVEDRAB: "#6B8E23",
- ORANGE: "#FFA500",
- ORANGERED: "#FF4500",
- ORCHID: "#DA70D6",
- PALEGOLDENROD: "#EEE8AA",
- PALEGREEN: "#98FB98",
- PALETURQUOISE: "#AFEEEE",
- PALEVIOLETRED: "#D87093",
- PAPAYAWHIP: "#FFEFD5",
- PEACHPUFF: "#FFDAB9",
- PERU: "#CD853F",
- PINK: "#FFC0CB",
- PLUM: "#DDA0DD",
- POWDERBLUE: "#B0E0E6",
- PURPLE: "#800080",
- RED: "#FF0000",
- ROSYBROWN: "#BC8F8F",
- ROYALBLUE: "#4169E1",
- SADDLEBROWN: "#8B4513",
- SALMON: "#FA8072",
- SANDYBROWN: "#F4A460",
- SEAGREEN: "#2E8B57",
- SEASHELL: "#FFF5EE",
- SIENNA: "#A0522D",
- SILVER: "#C0C0C0",
- SKYBLUE: "#87CEEB",
- SLATEBLUE: "#6A5ACD",
- SLATEGREY: "#708090",
- SNOW: "#FFFAFA",
- SPRINGGREEN: "#00FF7F",
- STEELBLUE: "#4682B4",
- TAN: "#D2B48C",
- TEAL: "#008080",
- THISTLE: "#D8BFD8",
- TOMATO: "#FF6347",
- TURQUOISE: "#40E0D0",
- VIOLET: "#EE82EE",
- WHEAT: "#F5DEB3",
- WHITE: "#FFFFFF",
- WHITESMOKE: "#F5F5F5",
- YELLOW: "#FFFF00",
- YELLOWGREEN: "#9ACD32",
- REBECCAPURPLE: "#663399"
- };
- return colorMap[colorName.toUpperCase()] || colorName;
+ const v = isRef?.(colorName) ? unref(colorName) : colorName;
+ if (typeof v !== 'string') return v;
+ const s = v.trim();
+ if (s === '') return s;
+ if (s[0] === '#') return s;
+ if (s.toLowerCase() === 'transparent') return '#FFFFFF00';
+ const upper = s.toUpperCase();
+ const normalized = upper.replace(/GRAY/g, 'GREY');
+ return COLOR_MAP[upper] || COLOR_MAP[normalized] || s;
}
export const XMLNS = "http://www.w3.org/2000/svg";
@@ -1616,7 +1760,7 @@ export function createTSpansFromLineBreaksOnX({ content, fontSize, fill, x, y })
const lines = content.split('\n');
return lines
.map((line, idx) =>
- `
${line} `
+ `
${line} `
)
.join('');
}
@@ -1625,7 +1769,7 @@ export function createTSpansFromLineBreaksOnY({ content, fontSize, fill, x }) {
const lines = content.split('\n');
return lines
.map((line, idx) => {
- const dy = idx === 0 ? 0 : fontSize * 1.3;
+ const dy = idx === 0 ? 0 : fontSize;
return `
${line} `;
})
.join('');
@@ -1945,9 +2089,6 @@ export function hasDeepProperty(obj, path) {
export function sanitizeArray(arr, keys = []) {
function sanitizeValue(value) {
- if ([NaN, undefined, Infinity, -Infinity, null].includes(value)) {
- console.warn(`A non processable value was detected : ${value}`)
- }
if (typeof value === 'string' && isNaN(Number(value))) return value;
return (typeof value === 'number' && isFinite(value)) ? value : 0;
}
@@ -2614,6 +2755,483 @@ export function getCumulativeMedian({ values, config = {} }) {
return medians;
}
+export function setOpacityIfWithinBBox({
+ el,
+ container,
+ padding = 1
+}) {
+ if (!el || !container) return;
+
+ let elBB = el.getBBox();
+ let contBB = container.getBBox();
+
+ if (elBB.x < contBB.x + padding || elBB.y < contBB.y + padding || (elBB.x + elBB.width) > (contBB.x + contBB.width - padding) || (elBB.y + elBB.height) > (contBB.y + contBB.height - padding)) {
+ el.style.opacity = '0'
+ } else {
+ el.style.opacity = '1'
+ }
+}
+
+export function autoFontSize({
+ el,
+ bounds,
+ currentFontSize,
+ minFontSize = 6,
+ attempts = 200,
+ padding = 1
+}) {
+ if (!el || !currentFontSize) return 0;
+
+ let fontSize = currentFontSize;
+ el.setAttribute('font-size', fontSize);
+
+ const { x, y, width: W, height: H } = bounds;
+ const cLeft = x + padding;
+ const cTop = y + padding;
+ const cRight = x + W - padding;
+ const cBottom = y + H - padding;
+
+ let er = el.getBBox();
+
+ if (
+ er.x >= cLeft + padding &&
+ er.y >= cTop + padding &&
+ er.x + er.width <= cRight - padding &&
+ er.y + er.height <= cBottom - padding
+ ) {
+ return fontSize;
+ }
+
+ let tries = attempts;
+
+ while (tries-- > 0 && fontSize > minFontSize) {
+ fontSize--;
+ el.setAttribute('font-size', fontSize);
+ er = el.getBBox();
+ if (
+ er.x >= cLeft + padding &&
+ er.y >= cTop + padding &&
+ er.x + er.width <= cRight - padding &&
+ er.y + er.height <= cBottom - padding
+ ) {
+ break;
+ }
+ }
+ if (fontSize < minFontSize) {
+ fontSize = 0;
+ el.setAttribute('font-size', fontSize);
+ }
+ return fontSize;
+}
+
+/**
+ * Starts observing for nodes with a given CSS class inside a specific container
+ * and fires callback whenever at least one such node is present.
+ *
+ * @param {HTMLElement} container container element to observe
+ * @param {string} cssClass class name without the leading dot
+ * @param {() => void} onNodesPresent function to call when matching elements appear
+ * @returns {MutationObserver} the observer instance (so you can disconnect it later)
+ */
+export function observeClassPresenceIn(container, cssClass, onNodesPresent) {
+ if (typeof cssClass !== 'string' || !cssClass.trim()) {
+ console.error('Vue Data UI - observeClassPresenceIn: cssClass must be a non-empty string');
+ }
+ if (typeof onNodesPresent !== 'function') {
+ console.error('Vue Data UI - observeClassPresenceIn: onNodesPresent must be a function');
+ }
+
+ const selector = `.${cssClass}`;
+ let hasSeen = false;
+
+ // Check the container for matching elements and trigger callback once per transition 0 → >0
+ function checkAndTrigger() {
+ const nodes = container.querySelectorAll(selector);
+ if (nodes.length > 0) {
+ if (!hasSeen) {
+ hasSeen = true;
+ onNodesPresent();
+ }
+ } else {
+ hasSeen = false;
+ }
+ }
+
+ // Observe the container for additions/removals anywhere in its subtree
+ const observer = new MutationObserver((mutations) => {
+ for (const m of mutations) {
+ if (m.addedNodes.length || m.removedNodes.length) {
+ checkAndTrigger();
+ break;
+ }
+ }
+ });
+
+ observer.observe(container, {
+ childList: true,
+ subtree: true,
+ });
+
+ // Initial check in case elements are already present
+ checkAndTrigger();
+
+ return observer;
+}
+
+/**
+ * Formats numeric values with a controlled number of decimal places,
+ * applying maxDecimals for all values when no fallbackFormatter is given,
+ * or calling the fallbackFormatter for values ≥ 1 if provided.
+ *
+ * @param {Object} options
+ * @param {number} options.value The numeric value to format.
+ * @param {number} [options.maxDecimals=4] Max number of decimal places.
+ * @param {Function} [options.fallbackFormatter] Callback for values ≥ 1; receives the raw value and must return a string
+ * @param {boolean} [options.removeTrailingZero=true] Whether to strip unnecessary trailing zeros.
+ * @returns {string} The formatted number as a string.
+ */
+export function formatSmallValue({
+ value,
+ maxDecimals = 4,
+ fallbackFormatter,
+ removeTrailingZero = true,
+}) {
+ if (value === 0) {
+ return '0';
+ }
+
+ const abs = Math.abs(value);
+
+ if (abs >= 1 && typeof fallbackFormatter === 'function') {
+ const fb = fallbackFormatter(value);
+ return String(fb);
+ }
+
+ let decimals;
+ if (abs < 1) {
+ const exp = Math.floor(Math.log10(abs));
+ decimals = Math.min(Math.max(1 - exp, 1), maxDecimals);
+ } else {
+ decimals = maxDecimals;
+ }
+
+ let str = value.toFixed(decimals);
+
+ if (removeTrailingZero) {
+ str = str
+ .replace(/(\.\d*?[1-9])0+$/, '$1') // drop zeros after last non-zero
+ .replace(/\.0+$/, ''); // drop ".0"
+ }
+
+ return str;
+}
+
+// Create skeleton dataset with basic fib
+export function fib(n) {
+ const a = [];
+ for (let i = 0; i < n; i += 1) {
+ a.push(i === 0 ? 0 : i === 1 ? 1 : a[i - 1] + a[i - 2]);
+ }
+ return a;
+}
+
+export function wrapText(str, maxChars = 20) {
+ str = str.replace(/[\r\n]+/g, " ");
+
+ const words = str.split(" ");
+ let line = "";
+ let result = "";
+
+ for (let word of words) {
+ if ((line + (line ? " " : "") + word).length <= maxChars) {
+ line += (line ? " " : "") + word;
+ } else {
+ if (line) {
+ result += (result ? "\n" : "") + line;
+ }
+ line = word;
+ }
+ }
+
+ if (line) {
+ result += (result ? "\n" : "") + line;
+ }
+
+ return result;
+}
+
+/**
+ * Build SVG polygons representing the filled area(s) between two lines.
+ * - Works with straight or smoothed (monotone cubic) lines.
+ * - Samples both lines on a common X grid (pixel step).
+ * - Optionally merges consecutive intervals with the same nature
+ * into larger polygons (fewer path nodes).
+ *
+ * @param {{
+* lineA: Array<{x:number,y:number,value?:number|null}>,
+* lineB: Array<{x:number,y:number,value?:number|null}>,
+* colorLineA: string,
+* colorLineB: string,
+* smoothA?: boolean,
+* smoothB?: boolean,
+* sampleStepPx?: number,
+* cutNullValues?: boolean,
+* merge?: boolean
+* }} opts
+* @returns {Array<{ d: string, color: string }>}
+*/
+export function buildInterLineAreas(opts) {
+ const {
+ lineA,
+ lineB,
+ colorLineA, // fill when A is above
+ colorLineB, // fill when B is above
+ smoothA = false,
+ smoothB = false,
+ sampleStepPx = 2,
+ cutNullValues = true, // break across gaps
+ merge = true // merge into large polygons
+ } = opts || {};
+
+ if (!Array.isArray(lineA) || !Array.isArray(lineB) || !lineA.length || !lineB.length) {
+ return [];
+ }
+
+ const isNum = (n) => Number.isFinite(n);
+
+ function getSegments(points) {
+ if (!cutNullValues) return [points.filter(p => p && isNum(p.x) && isNum(p.y))];
+ const segs = [];
+ let curr = [];
+ for (const p of points) {
+ const ok = p && isNum(p.x) && isNum(p.y) && !(p.value == null);
+ if (ok) {
+ curr.push({ x: p.x, y: p.y });
+ } else {
+ if (curr.length > 1) segs.push(curr);
+ curr = [];
+ }
+ }
+ if (curr.length > 1) segs.push(curr);
+ return segs;
+ }
+
+ function computeTangents(seg) {
+ const n = seg.length - 1;
+ const dx = new Array(n), dy = new Array(n), slopes = new Array(n), m = new Array(seg.length);
+ for (let i = 0; i < n; i += 1) {
+ dx[i] = seg[i + 1].x - seg[i].x;
+ dy[i] = seg[i + 1].y - seg[i].y;
+ slopes[i] = dy[i] / dx[i];
+ }
+ m[0] = slopes[0];
+ m[n] = slopes[n - 1];
+ for (let i = 1; i < n; i += 1) {
+ if (slopes[i - 1] * slopes[i] <= 0) {
+ m[i] = 0;
+ } else {
+ m[i] = (2 * slopes[i - 1] * slopes[i]) / (slopes[i - 1] + slopes[i]);
+ }
+ }
+ return m;
+ }
+
+ function evalMonotone(p0, p1, m0, m1, x) {
+ const x0 = p0.x, x1 = p1.x, y0 = p0.y, y1 = p1.y;
+ const h = x1 - x0;
+ if (h === 0) return y0;
+ const t = (x - x0) / h, t2 = t * t, t3 = t2 * t;
+ const h00 = 2 * t3 - 3 * t2 + 1;
+ const h10 = t3 - 2 * t2 + t;
+ const h01 = -2 * t3 + 3 * t2;
+ const h11 = t3 - t2;
+ return h00 * y0 + h10 * (m0 * h) + h01 * y1 + h11 * (m1 * h);
+ }
+
+ // Sample a polyline or smoothed line on a uniform X grid
+ function sampleLine(points, smooth) {
+ const segments = getSegments(points);
+ if (!segments.length) return [];
+
+ // global bounds
+ let xmin = Infinity, xmax = -Infinity;
+ for (const seg of segments) {
+ xmin = Math.min(xmin, seg[0].x);
+ xmax = Math.max(xmax, seg[seg.length - 1].x);
+ }
+ if (!isNum(xmin) || !isNum(xmax) || xmax <= xmin) return [];
+
+ const step = Math.max(1, sampleStepPx);
+ const xs = [];
+ for (let x = xmin; x <= xmax; x += step) xs.push(x);
+ if (xs[xs.length - 1] < xmax) xs.push(xmax);
+
+ const out = [];
+ for (const x of xs) {
+ let y = null;
+ let covered = false;
+
+ for (const seg of segments) {
+ const last = seg.length - 1;
+ if (x < seg[0].x - 1e-9 || x > seg[last].x + 1e-9) continue;
+
+ // locate local segment
+ for (let i = 0; i < last; i += 1) {
+ const p0 = seg[i], p1 = seg[i + 1];
+ if (x + 1e-9 < p0.x || x - 1e-9 > p1.x) continue;
+
+ if (!smooth) {
+ const t = (x - p0.x) / (p1.x - p0.x || 1);
+ y = p0.y + t * (p1.y - p0.y);
+ } else {
+ const m = seg.__tangents || (seg.__tangents = computeTangents(seg));
+ y = evalMonotone(p0, p1, m[i], m[i + 1], x);
+ }
+ covered = true;
+ break;
+ }
+ if (covered) break;
+ }
+
+ if (y == null) {
+ out.push({ x, y: null, hole: true });
+ } else {
+ out.push({ x, y, hole: false });
+ }
+ }
+ return out;
+ }
+
+ function lerp(a, b, t) { return a + t * (b - a); }
+
+ // Refine by inserting exact crossing points so top only changes at sample boundaries
+ function refineWithCrossings(sA, sB) {
+ const A = [], B = [];
+ const N = Math.min(sA.length, sB.length);
+ for (let i = 0; i < N - 1; i += 1) {
+ const A0 = sA[i], A1 = sA[i + 1];
+ const B0 = sB[i], B1 = sB[i + 1];
+
+ // propagate current sample
+ A.push(A0); B.push(B0);
+
+ // skip if any hole on this interval
+ if (A0.hole || A1.hole || B0.hole || B1.hole || A0.y == null || A1.y == null || B0.y == null || B1.y == null) {
+ continue;
+ }
+
+ const d0 = A0.y - B0.y;
+ const d1 = A1.y - B1.y;
+
+ if ((d0 > 0 && d1 < 0) || (d0 < 0 && d1 > 0)) {
+ const t = d0 / (d0 - d1);
+ const xc = lerp(A0.x, A1.x, t);
+ const yc = lerp(A0.y, A1.y, t); // equal for both
+ const crossA = { x: xc, y: yc, hole: false };
+ const crossB = { x: xc, y: yc, hole: false };
+ A.push(crossA);
+ B.push(crossB);
+ }
+ }
+ // add the last samples
+ if (N > 0) { A.push(sA[N - 1]); B.push(sB[N - 1]); }
+ return { A, B };
+ }
+
+ // Build small per-interval polygons (no merge)
+ function buildPerIntervalPolys(sA, sB) {
+ const out = [];
+ const N = Math.min(sA.length, sB.length);
+ for (let i = 0; i < N - 1; i += 1) {
+ const A0 = sA[i], A1 = sA[i + 1];
+ const B0 = sB[i], B1 = sB[i + 1];
+ if (A0.hole || A1.hole || B0.hole || B1.hole || A0.y == null || A1.y == null || B0.y == null || B1.y == null) continue;
+
+ const d0 = A0.y - B0.y;
+ const d1 = A1.y - B1.y;
+
+ // after refinement, a sign change cannot happen across an interval
+ const top0 = d0 <= 0 ? A0 : B0;
+ const top1 = d1 <= 0 ? A1 : B1;
+ const bot1 = d1 <= 0 ? B1 : A1;
+ const bot0 = d0 <= 0 ? B0 : A0;
+ const color = d0 <= 0 ? colorLineA : colorLineB;
+
+ const d = [
+ `M${top0.x},${top0.y}`,
+ `L${top1.x},${top1.y}`,
+ `L${bot1.x},${bot1.y}`,
+ `L${bot0.x},${bot0.y}`,
+ `Z`
+ ].join(' ');
+ out.push({ d, color });
+ }
+ return out;
+ }
+
+ // Build merged polygons for consecutive same-top runs
+ function buildMergedPolys(sA, sB) {
+ const out = [];
+ const N = Math.min(sA.length, sB.length);
+ if (N < 2) return out;
+
+ let i = 0;
+ while (i < N - 1) {
+ // skip holes
+ while (i < N - 1) {
+ const A0 = sA[i], B0 = sB[i], A1 = sA[i + 1], B1 = sB[i + 1];
+ if (!A0.hole && !B0.hole && !A1.hole && !B1.hole && A0.y != null && B0.y != null && A1.y != null && B1.y != null) break;
+ i += 1;
+ }
+ if (i >= N - 1) break;
+
+ const start = i;
+ const sign = Math.sign((sB[i].y - sA[i].y) || 0) || 1; // default "A above" when equal
+
+ // extend run while top stays same and no holes
+ i += 1;
+ while (i < N - 1) {
+ const A0 = sA[i], B0 = sB[i], A1 = sA[i + 1], B1 = sB[i + 1];
+ if (A0.hole || B0.hole || A1.hole || B1.hole || A0.y == null || B0.y == null || A1.y == null || B1.y == null) break;
+ const s = Math.sign((B0.y - A0.y) || 0) || 1;
+ if (s !== sign) break;
+ i += 1;
+ }
+ const end = i + 0; // inclusive end index for vertices
+
+ // collect polygon points
+ const top = sign >= 0 ? sA : sB;
+ const bot = sign >= 0 ? sB : sA;
+ const color = sign >= 0 ? colorLineA : colorLineB;
+
+ const topPts = [];
+ for (let k = start; k <= end; k += 1) {
+ topPts.push(`${top[k].x},${top[k].y}`)
+ };
+
+ const botPts = [];
+ for (let k = end; k >= start; k -= 1) {
+ botPts.push(`${bot[k].x},${bot[k].y}`)
+ };
+
+ const d = `M${topPts[0]} L${topPts.slice(1).join(' L')} L${botPts.join(' L')} Z`;
+ out.push({ d, color });
+ }
+
+ return out;
+ }
+
+ const sampledA = sampleLine(lineA, smoothA);
+ const sampledB = sampleLine(lineB, smoothB);
+
+ // insert crossing points so top is constant between consecutive samples
+ const { A: refinedA, B: refinedB } = refineWithCrossings(sampledA, sampledB);
+
+ return merge
+ ? buildMergedPolys(refinedA, refinedB)
+ : buildPerIntervalPolys(refinedA, refinedB);
+}
+
const lib = {
XMLNS,
@@ -2622,6 +3240,8 @@ const lib = {
addVector,
applyDataLabel,
assignStackRatios,
+ autoFontSize,
+ buildInterLineAreas,
calcLinearProgression,
calcMarkerOffsetX,
calcMarkerOffsetY,
@@ -2639,16 +3259,22 @@ const lib = {
convertColorToHex,
convertConfigColors,
convertCustomPalette,
+ createAreaWithCuts,
createCsvContent,
createHalfCircleArc,
+ createIndividualArea,
+ createIndividualAreaWithCuts,
createPolarAreas,
createPolygonPath,
createShadesOfGrey,
+ createSmoothAreaSegments,
createSmoothPath,
createSmoothPathVertical,
+ createSmoothPathWithCuts,
createSpiralPath,
createStar,
createStraightPath,
+ createStraightPathWithCuts,
createTSpans,
createTSpansFromLineBreaksOnX,
createTSpansFromLineBreaksOnY,
@@ -2663,9 +3289,12 @@ const lib = {
easeOutCubic,
emptyObjectToNull,
error,
+ fib,
forceValidValue,
+ formatSmallValue,
functionReturnsString,
generateSpiralCoordinates,
+ getAreaSegments,
getCloserPoint,
getCumulativeAverage,
getCumulativeMedian,
@@ -2686,6 +3315,7 @@ const lib = {
matrixTimes,
mergePointsByProximity,
objectIsEmpty,
+ observeClassPresenceIn,
opacity,
palette,
placeHTMLElementAtSVGCoordinates,
@@ -2693,6 +3323,7 @@ const lib = {
rotateMatrix,
sanitizeArray,
setOpacity,
+ setOpacityIfWithinBBox,
shiftHue,
slugify,
sumByAttribute,
@@ -2700,12 +3331,6 @@ const lib = {
themePalettes,
translateSize,
treeShake,
- createStraightPathWithCuts,
- createSmoothPathWithCuts,
- getAreaSegments,
- createAreaWithCuts,
- createIndividualAreaWithCuts,
- createSmoothAreaSegments,
- createIndividualArea,
+ wrapText
};
export default lib;
\ No newline at end of file
diff --git a/src/main.js b/src/main.js
index ad92b609..8a37bfb7 100755
--- a/src/main.js
+++ b/src/main.js
@@ -41,6 +41,7 @@ import {
VueUiTiremarks,
VueUiTreemap,
VueUiVerticalBar,
+ VueUiHorizontalBar, // v3 renaming
VueUiWaffle,
VueUiWheel,
VueUiXy,
@@ -111,6 +112,7 @@ app.component("VueUiThermometer", VueUiThermometer);
app.component("VueUiTiremarks", VueUiTiremarks);
app.component("VueUiTreemap", VueUiTreemap);
app.component("VueUiVerticalBar", VueUiVerticalBar);
+app.component("VueUiHorizontalBar", VueUiHorizontalBar); // v3 renaming
app.component("VueUiWaffle", VueUiWaffle);
app.component("VueUiWheel", VueUiWheel);
app.component("VueUiXy", VueUiXy);
diff --git a/src/pdf.js b/src/pdf.js
index 948eb040..11b93d0c 100644
--- a/src/pdf.js
+++ b/src/pdf.js
@@ -1,23 +1,27 @@
import { domToPng } from "./dom-to-png";
-export default async function pdf({ domElement, fileName, scale = 2, options = {} }) {
+export default async function pdf({
+ domElement,
+ fileName,
+ scale = 2,
+ orientation = "auto", // 'auto' | 'portrait' | 'landscape'
+ overflowTolerance = 0.2, // up to +n% height overflow gets squeezed onto 1 page
+}) {
if (!domElement) return Promise.reject("No domElement provided");
- const isSafari = typeof navigator !== 'undefined' &&
+ const isSafari =
+ typeof navigator !== "undefined" &&
/^((?!chrome|android).)*safari/i.test(navigator.userAgent);
let JsPDF;
-
try {
- JsPDF = (await import('jspdf')).default;
- } catch (e) {
- return Promise.reject('jspdf is not installed. Run npm install jspdf')
+ JsPDF = (await import("jspdf")).default;
+ } catch (_) {
+ return Promise.reject("jspdf is not installed. Run npm install jspdf");
}
- const a4 = {
- width: 595.28,
- height: 841.89,
- };
+ const A4_PORTRAIT = { width: 595.28, height: 841.89 };
+ const A4_LANDSCAPE = { width: 841.89, height: 595.28 };
if (isSafari) {
// Warming up in Safari, because it never works on the first try
@@ -32,54 +36,82 @@ export default async function pdf({ domElement, fileName, scale = 2, options = {
}
const imgData = await domToPng({ container: domElement, scale });
+
return await new Promise((resolve, reject) => {
const img = new window.Image();
img.onload = function () {
+ const EPS = 0.5; // small epsilon to avoid off-by-one paging due to rounding
+
const contentWidth = img.naturalWidth;
const contentHeight = img.naturalHeight;
- let imgWidth = a4.width;
- let imgHeight = (a4.width / contentWidth) * contentHeight;
-
- const pdf = new JsPDF("", "pt", "a4");
- let position = 0;
- let leftHeight = contentHeight;
- const pageHeight = (contentWidth / a4.width) * a4.height;
-
- if (leftHeight < pageHeight) {
- pdf.addImage(
- imgData,
- "PNG",
- 0,
- 0,
- imgWidth,
- imgHeight,
- "",
- "FAST"
- );
+ const chosenOrientation =
+ orientation === "auto"
+ ? contentHeight >= contentWidth
+ ? "p"
+ : "l"
+ : orientation;
+
+ const a4 =
+ chosenOrientation === "l" ? A4_LANDSCAPE : A4_PORTRAIT;
+
+ const ratioToWidth = a4.width / contentWidth;
+ const ratioToHeight = a4.height / contentHeight;
+ const scaledHeightAtWidth = contentHeight * ratioToWidth;
+
+ let mode = "single"; // 'single' | 'multi'
+ let ratio;
+
+ if (scaledHeightAtWidth <= a4.height + EPS) {
+ ratio = ratioToWidth;
+ } else if (scaledHeightAtWidth <= a4.height * (1 + overflowTolerance)) {
+ ratio = Math.min(ratioToWidth, ratioToHeight);
+ } else {
+ mode = "multi";
+ ratio = ratioToWidth;
+ }
+
+ const imgWidth = contentWidth * ratio;
+ const imgHeight = contentHeight * ratio;
+
+ const x = (a4.width - imgWidth) / 2;
+
+ const pdf = new JsPDF({
+ orientation: chosenOrientation,
+ unit: "pt",
+ format: "a4"
+ });
+
+ if (mode === "single") {
+ const y = (a4.height - imgHeight) / 2;
+ pdf.addImage(imgData, "PNG", x, y, imgWidth, imgHeight, "", "FAST");
} else {
- while (leftHeight > 0) {
+ const pageHeightInImagePx = a4.height / ratio;
+
+ let leftHeight = contentHeight;
+ let positionY = 0;
+
+ while (leftHeight > EPS) {
pdf.addImage(
imgData,
"PNG",
- 0,
- position,
+ x,
+ positionY,
imgWidth,
imgHeight,
"",
"FAST"
);
- leftHeight -= pageHeight;
- position -= a4.height;
- if (leftHeight > 0) {
- pdf.addPage();
- }
+ leftHeight -= pageHeightInImagePx;
+ positionY -= a4.height;
+ if (leftHeight > EPS) pdf.addPage();
}
}
+
pdf.save(`${fileName}.pdf`);
resolve();
};
- img.onerror = err => reject("Failed to load image for PDF: " + err);
+ img.onerror = (err) => reject("Failed to load image for PDF: " + err);
img.src = imgData;
});
}
diff --git a/src/themes.json b/src/themes.json
index f14d691b..f35bf71b 100755
--- a/src/themes.json
+++ b/src/themes.json
@@ -4012,6 +4012,280 @@
}
}
},
+ "vue_ui_horizontal_bar": {
+ "default": {},
+ "celebration": {
+ "style": {
+ "chart": {
+ "backgroundColor":"#FFF8E1",
+ "color": "#424242",
+ "layout": {
+ "bars": {
+ "borderRadius": 0,
+ "useGradient": false,
+ "dataLabels": {
+ "color": "#424242"
+ },
+ "nameLabels": {
+ "color": "#424242"
+ },
+ "parentLabels": {
+ "color": "#424242"
+ }
+ },
+ "highlighter": {
+ "color": "#FF8A65"
+ },
+ "separators": {
+ "show": false
+ }
+ },
+ "legend": {
+ "backgroundColor":"#FFF8E1",
+ "color": "#424242"
+ },
+ "title": {
+ "color": "#424242",
+ "subtitle": {
+ "color": "#757575"
+ }
+ },
+ "tooltip": {
+ "backgroundColor": "#FFECB3",
+ "backgroundOpacity": 30,
+ "color": "#424242",
+ "borderColor": "#FF8A65"
+ }
+ }
+ },
+ "table": {
+ "th": {
+ "backgroundColor": "#FFF8E1",
+ "color": "#424242"
+ },
+ "td": {
+ "backgroundColor": "#FFF8E1",
+ "color": "#424242"
+ }
+ }
+ },
+ "celebrationNight": {
+ "style": {
+ "chart": {
+ "backgroundColor":"#1E1E1E",
+ "color": "#BDBDBD",
+ "layout": {
+ "bars": {
+ "borderRadius": 0,
+ "useGradient": false,
+ "dataLabels": {
+ "color": "#BDBDBD"
+ },
+ "nameLabels": {
+ "color": "#BDBDBD"
+ },
+ "parentLabels": {
+ "color": "#FFF8E1"
+ }
+ },
+ "highlighter": {
+ "color": "#FF8A65"
+ },
+ "separators": {
+ "show": false
+ }
+ },
+ "legend": {
+ "backgroundColor":"#1E1E1E",
+ "color": "#BDBDBD"
+ },
+ "title": {
+ "color": "#FFF8E1",
+ "subtitle": {
+ "color": "#BDBDBD"
+ }
+ },
+ "tooltip": {
+ "backgroundColor": "#1E1E1E",
+ "backgroundOpacity": 30,
+ "color": "#FFF8E1",
+ "borderColor": "#FF8A65"
+ }
+ }
+ },
+ "table": {
+ "th": {
+ "backgroundColor": "#1E1E1E",
+ "color": "#BDBDBD"
+ },
+ "td": {
+ "backgroundColor": "#1E1E1E",
+ "color": "#BDBDBD"
+ }
+ }
+ },
+ "hack": {
+ "style": {
+ "chart": {
+ "backgroundColor": "#1A1A1A",
+ "color": "#99AA99",
+ "layout": {
+ "bars": {
+ "borderRadius": 0,
+ "useGradient": false,
+ "dataLabels": {
+ "color": "#AACCAA"
+ },
+ "nameLabels": {
+ "color": "#99CC99"
+ },
+ "parentLabels": {
+ "color": "#99CC99"
+ }
+ },
+ "highlighter": {
+ "color": "#66CC66"
+ },
+ "separators": {
+ "show": false
+ }
+ },
+ "legend": {
+ "backgroundColor": "#1A1A1A",
+ "color": "#99AA99"
+ },
+ "title": {
+ "color": "#66CC66",
+ "subtitle": {
+ "color": "#99AA99"
+ }
+ },
+ "tooltip": {
+ "backgroundColor": "#2A2F2A",
+ "color": "#AACCAA",
+ "borderColor": "#66CC66"
+ }
+ }
+ },
+ "table": {
+ "th": {
+ "backgroundColor": "#1A1A1A",
+ "color": "#99AA99"
+ },
+ "td": {
+ "backgroundColor": "#1A1A1A",
+ "color": "#AACCAA"
+ }
+ }
+ },
+ "zen": {
+ "style": {
+ "chart": {
+ "backgroundColor": "#fbfafa",
+ "color": "#8A9892",
+ "layout": {
+ "bars": {
+ "borderRadius": 8,
+ "useGradient": true,
+ "dataLabels": {
+ "color": "#A0AC94"
+ },
+ "nameLabels": {
+ "color": "#8A9892"
+ },
+ "parentLabels": {
+ "color": "#8A9892"
+ }
+ },
+ "highlighter": {
+ "color": "#8F837A"
+ },
+ "separators": {
+ "show": false
+ }
+ },
+ "legend": {
+ "backgroundColor": "#fbfafa",
+ "color": "#99AA99"
+ },
+ "title": {
+ "color": "#8A9892",
+ "subtitle": {
+ "color": "#99AA99"
+ }
+ },
+ "tooltip": {
+ "backgroundColor": "#fbfafa",
+ "color": "#8A9892"
+ }
+ }
+ },
+ "table": {
+ "th": {
+ "backgroundColor": "#fbfafa",
+ "color": "#8F837A"
+ },
+ "td": {
+ "backgroundColor": "#fbfafa",
+ "color": "#8F837A"
+ }
+ }
+ },
+ "concrete": {
+ "style": {
+ "chart": {
+ "backgroundColor": "#f6f6fb",
+ "color": "#50606C",
+ "layout": {
+ "bars": {
+ "borderRadius": 0,
+ "useGradient": true,
+ "dataLabels": {
+ "color": "#61747E"
+ },
+ "nameLabels": {
+ "color": "#8A9892"
+ },
+ "parentLabels": {
+ "color": "#8A9892"
+ }
+ },
+ "highlighter": {
+ "color": "#50606C"
+ },
+ "separators": {
+ "show": true,
+ "color": "#DEE1DE"
+ }
+ },
+ "legend": {
+ "backgroundColor": "#f6f6fb",
+ "color": "#61747E"
+ },
+ "title": {
+ "color": "#50606C",
+ "subtitle": {
+ "color": "#718890"
+ }
+ },
+ "tooltip": {
+ "backgroundColor": "#f6f6fb",
+ "color": "#50606C"
+ }
+ }
+ },
+ "table": {
+ "th": {
+ "backgroundColor": "#f6f6fb",
+ "color": "#50606C"
+ },
+ "td": {
+ "backgroundColor": "#f6f6fb",
+ "color": "#50606C"
+ }
+ }
+ }
+ },
"vue_ui_heatmap": {
"default": {},
"celebration": {
diff --git a/src/useAutoSizeLabelsInsideViewbox.js b/src/useAutoSizeLabelsInsideViewbox.js
new file mode 100644
index 00000000..ae472b77
--- /dev/null
+++ b/src/useAutoSizeLabelsInsideViewbox.js
@@ -0,0 +1,128 @@
+import { autoFontSize } from "./lib";
+
+export function useAutoSizeLabelsInsideViewbox({
+ svgRef,
+ fontSize,
+ minFontSize,
+ sizeRef,
+ labelClass,
+ labelTypes = []
+}) {
+ let rafScheduled = null;
+
+ function minOf(arr) {
+ let m = arr[0];
+ for (let i = 1; i < arr.length; i += 1) if (arr[i] < m) m = arr[i];
+ return m;
+ }
+ function maxOf(arr) {
+ let m = arr[0];
+ for (let i = 1; i < arr.length; i += 1) if (arr[i] > m) m = arr[i];
+ return m;
+ }
+
+ function getTransformedBBox(el) {
+ const svg = el.ownerSVGElement;
+ if (!svg) return { x: 0, y: 0, width: 0, height: 0 };
+
+ const b = el.getBBox();
+ const m = el.getCTM();
+
+ const corners = [
+ { x: b.x, y: b.y },
+ { x: b.x + b.width, y: b.y },
+ { x: b.x, y: b.y + b.height },
+ { x: b.x + b.width, y: b.y + b.height }
+ ].map((p) => {
+ const pt = svg.createSVGPoint();
+ pt.x = p.x;
+ pt.y = p.y;
+ const t = m ? pt.matrixTransform(m) : pt;
+ return { x: t.x, y: t.y };
+ });
+
+ const xs = corners.map((p) => p.x);
+ const ys = corners.map((p) => p.y);
+
+ const minX = minOf(xs);
+ const maxX = maxOf(xs);
+ const minY = minOf(ys);
+ const maxY = maxOf(ys);
+
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
+ }
+
+ function fitsWithinBounds(el, bounds, padding = 1) {
+ const { x, y, width, height } = getTransformedBBox(el);
+ const leftOK = x >= bounds.x + padding;
+ const rightOK = x + width <= bounds.x + bounds.width - padding;
+ const topOK = y >= bounds.y + padding;
+ const bottomOK = y + height <= bounds.y + bounds.height - padding;
+ return leftOK && rightOK && topOK && bottomOK;
+ }
+
+ function shrinkToFit(el, bounds, startSize, minSize, attempts = 120, padding = 1) {
+ let current = startSize;
+ let tries = 0;
+ while (tries < attempts) {
+ el.setAttribute('font-size', current);
+ if (fitsWithinBounds(el, bounds, padding)) break;
+ if (current <= minSize) break;
+ current -= 0.5;
+ tries += 1;
+ }
+ return current < minSize ? minSize : current;
+ }
+
+ function autoSizeLabelsNow() {
+ const container = svgRef.value;
+ if (!container) return;
+
+ const [x, y, w, h] = container.getAttribute("viewBox").split(" ").map(Number);
+ const bounds = { x, y, width: w, height: h };
+
+ if (!labelTypes.length) {
+ labelTypes = [
+ {
+ selector: labelClass,
+ baseSize: fontSize,
+ minSize: minFontSize,
+ sizeRef
+ }
+ ];
+ }
+
+ const totalMatches = labelTypes
+ .map((lt) => container.querySelectorAll(lt.selector).length)
+ .reduce((a, b) => a + b, 0);
+
+ if (totalMatches === 0) return;
+
+ labelTypes.forEach(({ selector, baseSize, minSize, sizeRef }) => {
+ container.querySelectorAll(selector).forEach((el) => {
+ const initial = autoFontSize({
+ el,
+ bounds,
+ currentFontSize: baseSize,
+ minFontSize: minSize,
+ attempts: 200,
+ padding: 1
+ });
+
+ const final = shrinkToFit(el, bounds, initial, minSize, 120, 1);
+ el.setAttribute('font-size', final);
+ sizeRef.value = final;
+ });
+ });
+ }
+
+ function autoSizeLabels() {
+ if (rafScheduled) cancelAnimationFrame(rafScheduled);
+ rafScheduled = requestAnimationFrame(() => {
+ rafScheduled = null;
+ autoSizeLabelsNow();
+ });
+ }
+
+ return { autoSizeLabels };
+}
diff --git a/src/useConfig.js b/src/useConfig.js
index 9027f238..f645669e 100755
--- a/src/useConfig.js
+++ b/src/useConfig.js
@@ -13,6 +13,8 @@ export function useConfig() {
const COLOR_YELLOW = '#FFD055'
const FONT = {
+ _6: 6,
+ _8: 8,
_10: 10,
_12: 12,
_14: 14,
@@ -24,6 +26,8 @@ export function useConfig() {
_48: 48
}
+ const MIN_FONT_SIZE = FONT._6;
+
// -------------------------
// COMBOS TO APPLY
@@ -70,7 +74,7 @@ export function useConfig() {
}
const LTTB = {
- threshold: 500,
+ threshold: 1095, // v2 = 500
}
const TITLE = {
@@ -108,7 +112,9 @@ export function useConfig() {
borderWidth: 1,
backgroundOpacity: 100,
position: POSITION.CENTER,
- offsetY: 24
+ offsetY: 24,
+ smooth: true, // v3
+ backdropFilter: true, // v3
}
const AXIS_DATE_FORMATTER = {
@@ -201,12 +207,9 @@ export function useConfig() {
},
buttonTitles,
print: {
- allowTaint: false,
- backgroundColor: COLOR_WHITE,
- useCORS: false,
- onclone: null,
scale: 2,
- logging: false
+ orientation: 'auto', // 'auto' | 'l' | 'p'
+ overflowTolerance: 0.2,
}
}
}
@@ -264,10 +267,17 @@ export function useConfig() {
}
const vue_ui_stackbar = {
+ loading: false, // v3
+ debug: false, // v3
theme: '',
responsive: false,
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
customPalette: [],
- useCssAnimation: true,
+ useCssAnimation: false, // v3 (v2 = true)
orientation: 'vertical', // or 'horizontal'
table: {
...TABLE,
@@ -298,9 +308,12 @@ export function useConfig() {
color: COLOR_BLACK,
height: 500,
width: 800,
- padding: PADDING([24, 24, 36, 48]),
+ padding: PADDING([12, 12, 12, 12]),
title: TITLE,
- legend: LEGEND,
+ legend: {
+ ...LEGEND,
+ position: 'bottom'
+ },
zoom: ZOOM,
tooltip: {
...TOOLTIP,
@@ -375,6 +388,10 @@ export function useConfig() {
datetimeFormatter: AXIS_DATE_FORMATTER,
offsetY: 0,
rotation: 0,
+ autoRotate: { // v3
+ enable: true, // v3
+ angle: -30 // v3
+ },
fontSize: FONT._14,
color: COLOR_BLACK,
bold: false
@@ -412,11 +429,18 @@ export function useConfig() {
// NOTE: Any update to this config will be reflected in VueUiRidgeline, which uses VueUiXy in its dialog.
const vue_ui_xy = {
+ debug: false, // v3
theme: '',
responsive: false,
+ loading: false, // v3
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null // v3
+ },
responsiveProportionalSizing: true,
customPalette: [],
- useCssAnimation: true,
+ useCssAnimation: false, // v3 (v2 = true)
downsample: LTTB,
chart: {
fontFamily: 'inherit',
@@ -437,7 +461,7 @@ export function useConfig() {
position: 'start', // or end
offsetX: 0,
offsetY: 0,
- padding: PADDING([5, 10, 5, 10]),
+ padding: PADDING([12, 12, 12, 12]),
border: {
stroke: COLOR_WHITE,
strokeWidth: 1,
@@ -463,8 +487,18 @@ export function useConfig() {
zoom: {
...ZOOM,
minimap: MINIMAP,
+ preview: { // v3
+ enable: true,
+ fill: '#CCCCCC50',
+ stroke: '#6A6A6A',
+ strokeWidth: 2,
+ strokeDasharray: 0,
+ },
+ useDefaultFormat: true,
+ timeFormat: 'yyyy-MM-dd HH:mm:ss', // When datetimeFormatter is enabled
+ customFormat: null // overrides all if callback => string
},
- padding: PADDING([36, 24, 64, 48]),
+ padding: PADDING([12, 12, 6, 6]),
highlighter: {
color: COLOR_BLACK,
opacity: 5,
@@ -499,7 +533,10 @@ export function useConfig() {
circleMarker: {
radius: 3,
color: COLOR_BLACK
- }
+ },
+ useDefaultFormat: true,
+ timeFormat: 'yyyy-MM-dd HH:mm:ss', // When datetimeFormatter is enabled
+ customFormat: null // overrides all if callback => string
},
grid: {
stroke: COLOR_GREY_LIGHT,
@@ -522,7 +559,7 @@ export function useConfig() {
yLabel: '',
yLabelOffsetX: 0,
xLabel: '',
- xLabelOffsetY: 14,
+ xLabelOffsetY: 0,
fontSize: FONT._14
},
zeroLine: {
@@ -544,13 +581,15 @@ export function useConfig() {
useNiceScale: false,
stacked: false,
gap: 12,
- labelWidth: 40,
+ labelWidth: 64,
formatter: null,
scaleMin: null, // Overrides auto scaling
scaleMax: null, // idem
groupColor: null, // force yAxis labels color
scaleLabelOffsetX: 0,
- scaleValueOffsetX: 0
+ scaleValueOffsetX: 0,
+ rounding: 1,
+ serieNameFormatter: null // v3, for individual scale & stacked modes
},
xAxisLabels: {
color: COLOR_BLACK,
@@ -562,7 +601,11 @@ export function useConfig() {
showOnlyAtModulo: false,
modulo: 12,
yOffset: 24,
- rotation: 0
+ rotation: 0,
+ autoRotate: { // v3
+ enable: true, // v3
+ angle: -30, // v3
+ }
}
}
},
@@ -581,7 +624,8 @@ export function useConfig() {
legend: {
color: COLOR_BLACK,
show: true,
- fontSize: FONT._14
+ fontSize: FONT._14,
+ position: 'bottom', // bottom | top
},
title: {
...TITLE,
@@ -593,7 +637,9 @@ export function useConfig() {
showValue: true,
showPercentage: true,
roundingValue: 0,
- roundingPercentage: 0
+ roundingPercentage: 0,
+ useDefaultTimeFormat: true,
+ timeFormat: 'yyyy-MM-dd HH:mm:ss', // When datetimeFormatter is used
},
userOptions: USER_OPTIONS({
tooltip: true,
@@ -608,9 +654,12 @@ export function useConfig() {
})
},
bar: {
+ showTransition: true,
+ transitionDurationMs: 300,
borderRadius: 2,
useGradient: true,
periodGap: 0.1,
+ innerGap: 0,
border: {
useSerieColor: false,
strokeWidth: 0,
@@ -634,10 +683,17 @@ export function useConfig() {
}
},
line: {
+ showTransition: true,
+ transitionDurationMs: 300,
radius: 3,
useGradient: true,
strokeWidth: 3,
cutNullValues: false,
+ interLine: {
+ pairs: [],
+ colors: [],
+ fillOpacity: 0.25,
+ },
dot: {
hideAboveMaxSerieLength: 62,
useSerieColor: true,
@@ -662,6 +718,8 @@ export function useConfig() {
}
},
plot: {
+ showTransition: true,
+ transitionDurationMs: 300,
radius: 3,
useGradient: true,
dot: {
@@ -698,22 +756,26 @@ export function useConfig() {
}
const vue_ui_donut = {
+ debug: false, // v3
type: 'classic',
+ loading: false, // v3
+ pie: false, // v3
+ autoSize: true, // false = v2
responsive: false,
theme: '',
customPalette: [],
- useCssAnimation: true,
- events: {
- datapointEnter: null,
- datapointLeave: null,
- datapointClick: null
+ useCssAnimation: false, // v3 (v2 = true)
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null // v3
},
serieToggleAnimation: {
show: true,
durationMs: 500,
},
startAnimation: {
- show: true,
+ show: false, // v3 (v2 = true)
durationMs: 1000,
staggerMs: 50
},
@@ -757,7 +819,7 @@ export function useConfig() {
width: 512,
height: 360,
layout: {
- curvedMarkers: false,
+ curvedMarkers: true, // v2 = false
labels: {
dataLabels: {
show: true,
@@ -775,13 +837,15 @@ export function useConfig() {
color: COLOR_BLACK,
bold: true,
fontSize: FONT._18,
+ minFontSize: MIN_FONT_SIZE, // v3
rounding: 0,
formatter: null
},
name: {
color: COLOR_BLACK,
bold: false,
- fontSize: FONT._14
+ fontSize: FONT._14,
+ minFontSize: MIN_FONT_SIZE, // v3
},
hollow: {
show: true,
@@ -824,7 +888,8 @@ export function useConfig() {
}
},
donut: {
- strokeWidth: 55,
+ radiusRatio: 0.3, // v3 (clamped between 0.1 and 0.5)
+ strokeWidth: 64, // v3 (v2 = 55)
borderWidth: 1,
useShadow: false,
shadowColor: COLOR_BLACK,
@@ -846,7 +911,8 @@ export function useConfig() {
roundingValue: 0,
roundingPercentage: 0,
showPercentage: true,
- showValue: true
+ showValue: true,
+ position: 'bottom'
},
tooltip: {
...TOOLTIP,
@@ -861,7 +927,14 @@ export function useConfig() {
}
const vue_ui_treemap = {
+ debug: false, // v3
+ loading: false, // v3
responsive: false,
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
theme: '',
customPalette: [],
userOptions: USER_OPTIONS({
@@ -880,7 +953,7 @@ export function useConfig() {
color: COLOR_BLACK,
height: 500,
width: 800,
- padding: PADDING([0, 6, 12, 6]),
+ padding: PADDING([0, 0, 0, 0]),
layout: {
sorted: true,
rects: {
@@ -914,7 +987,8 @@ export function useConfig() {
roundingValue: 0,
roundingPercentage: 0,
showValue: true,
- showPercentage: true
+ showPercentage: true,
+ position: 'bottom'
},
title: TITLE,
tooltip: {
@@ -940,7 +1014,14 @@ export function useConfig() {
}
const vue_ui_waffle = {
+ debug: false, // v3
+ loading: false, // v3
responsive: false,
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
theme: '',
customPalette: [],
useBlurOnHover: true,
@@ -999,7 +1080,8 @@ export function useConfig() {
roundingValue: 0,
roundingPercentage: 0,
showValue: true,
- showPercentage: true
+ showPercentage: true,
+ position: 'bottom'
}
}
},
@@ -1029,10 +1111,17 @@ export function useConfig() {
}
const vue_ui_radar = {
+ debug: false, // v3
+ loading: false, // v3
responsive: false,
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
theme: '',
customPalette: [],
- useCssAnimation: true,
+ useCssAnimation: false, // v2 = true
style: {
fontFamily: 'inherit',
chart: {
@@ -1081,7 +1170,8 @@ export function useConfig() {
},
legend: {
...LEGEND,
- roundingPercentage: 0
+ roundingPercentage: 0,
+ position: 'bottom'
}
}
},
@@ -1111,10 +1201,17 @@ export function useConfig() {
}
const vue_ui_quadrant = {
+ debug: false, // v3
+ loading: false, // v3
responsive: false,
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
theme: '',
customPalette: [],
- useCssAnimation: true,
+ useCssAnimation: false, // v2 = true
zoomAnimationFrames: 20,
downsample: LTTB,
style: {
@@ -1220,7 +1317,10 @@ export function useConfig() {
roundingValue: 0,
showShape: true,
},
- legend: LEGEND
+ legend: {
+ ...LEGEND,
+ position: 'bottom'
+ }
}
},
table: {
@@ -1249,6 +1349,8 @@ export function useConfig() {
}
const vue_ui_gauge = {
+ debug: false, // v3
+ loading: false, // v3
responsive: false,
theme: '',
customPalette: [],
@@ -1292,6 +1394,7 @@ export function useConfig() {
curved: true,
offsetRatio: 1.1,
fontSize: FONT._16,
+ minFontSize: MIN_FONT_SIZE, // v3
useSerieColor: true,
color: COLOR_BLACK,
bold: false,
@@ -1343,6 +1446,8 @@ export function useConfig() {
}
const vue_ui_wheel = {
+ debug: false, // v3
+ loading: false, // v3
responsive: false,
theme: '',
style: {
@@ -1396,12 +1501,17 @@ export function useConfig() {
}
const vue_ui_tiremarks = {
+ debug: false, // v3
+ loading: false, // v3
+ responsive: false, // v3
theme: '',
style: {
fontFamily: 'inherit',
chart: {
backgroundColor: COLOR_WHITE,
color: COLOR_BLACK,
+ width: 312, // v3
+ height: 56, // v3
animation: {
use: true,
speed: 0.5,
@@ -1445,6 +1555,8 @@ export function useConfig() {
}
const vue_ui_chestnut = {
+ debug: false, // v3
+ loading: false, // v3
theme: '',
customPalette: [],
style: {
@@ -1617,10 +1729,17 @@ export function useConfig() {
}
const vue_ui_onion = {
+ debug: false, // v3
+ loading: false, // v3
responsive: false,
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
theme: '',
customPalette: [],
- useCssAnimation: true,
+ useCssAnimation: false, // v2 = true
useStartAnimation: true,
useBlurOnHover: true,
style: {
@@ -1642,6 +1761,7 @@ export function useConfig() {
labels: {
show: true,
fontSize: FONT._14,
+ minFontSize: MIN_FONT_SIZE, // v3
color: COLOR_BLACK,
roundingValue: 0,
roundingPercentage: 0,
@@ -1661,7 +1781,8 @@ export function useConfig() {
legend: {
...LEGEND,
roundingValue: 0,
- roundingPercentage: 0
+ roundingPercentage: 0,
+ position: 'bottom'
},
tooltip: {
...TOOLTIP,
@@ -1697,11 +1818,14 @@ export function useConfig() {
}
}
- const vue_ui_vertical_bar = {
+ const vue_ui_vertical_bar = { // v3 renamed to _horizontal_ (yet still works)
+ debug: false, // v3
+ loading: false, // v3
+ autoSize: true, // v3
responsive: false,
theme: '',
customPalette: [],
- useCssAnimation: true,
+ useCssAnimation: false, // v3 (v2 = true)
events: {
datapointEnter: null,
datapointLeave: null,
@@ -1729,7 +1853,7 @@ export function useConfig() {
dataLabels: {
color: COLOR_BLACK,
bold: true,
- fontSize: FONT._12,
+ fontSize: FONT._14, // v3 increased
value: {
show: true,
roundingValue: 0,
@@ -1747,14 +1871,14 @@ export function useConfig() {
show: true,
color: COLOR_BLACK,
bold: false,
- fontSize: FONT._10,
+ fontSize: FONT._14, // v3 increased
offsetX: 0
},
parentLabels: {
show: true,
color: COLOR_BLACK,
bold: false,
- fontSize: FONT._10,
+ fontSize: FONT._14, // v3 increased
offsetX: 0
}
},
@@ -1763,9 +1887,10 @@ export function useConfig() {
opacity: 5
},
separators: {
- show: true,
+ show: false,
color: COLOR_GREY_LIGHT,
- strokeWidth: 1
+ strokeWidth: 1,
+ fullWidth: true,
}
},
title: TITLE,
@@ -1818,16 +1943,28 @@ export function useConfig() {
}
}
+ const vue_ui_horizontal_bar = vue_ui_vertical_bar;
+
const vue_ui_heatmap = {
+ debug: false, // v3
+ loading: false, // v3
+ responsive: false, // v3
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null // v3
+ },
theme: '',
style: {
fontFamily: 'inherit',
backgroundColor: COLOR_WHITE,
color: COLOR_BLACK,
layout: {
- padding: PADDING([36, 12, 12, 48]),
+ height: 300, // v3
+ width: 1000, // v3
+ padding: PADDING([0, 0, 0, 0]),
cells: {
- height: 36,
+ // height: 36, // v3 deprecated
rowTotal: {
value: {
show: false,
@@ -1840,6 +1977,10 @@ export function useConfig() {
value: {
show: false,
rotation: 0,
+ autoRotate: { // v3
+ enable: true, // v3
+ angle: -30, // v3
+ },
offsetX: 0,
offsetY: 0
},
@@ -1872,8 +2013,13 @@ export function useConfig() {
xAxis: {
show: true,
values: [],
+ datetimeFormatter: AXIS_DATE_FORMATTER,
showOnlyAtModulo: null,
rotation: 0,
+ autoRotate: { // v3
+ enable: true, // v3
+ angle: -30, // v3
+ },
fontSize: FONT._10,
color: COLOR_BLACK,
bold: false,
@@ -1883,6 +2029,7 @@ export function useConfig() {
yAxis: {
show: true,
values: [],
+ datetimeFormatter: AXIS_DATE_FORMATTER,
fontSize: FONT._10,
color: COLOR_BLACK,
bold: false,
@@ -1896,8 +2043,9 @@ export function useConfig() {
...LEGEND,
fontSize: FONT._12,
roundingValue: 0,
- position: POSITION.RIGHT,
- scaleBorderRadius: 18
+ width: 24,
+ // position: POSITION.RIGHT, // v3 deprecated
+ // scaleBorderRadius: 18 // v3 deprecated
},
tooltip: {
...TOOLTIP,
@@ -1927,10 +2075,17 @@ export function useConfig() {
}
const vue_ui_scatter = {
+ debug: false, // v3
+ loading: false, // v3
responsive: false,
+ events: {
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
theme: '',
customPalette: [],
- useCssAnimation: true,
+ useCssAnimation: false, // v2 = true
downsample: LTTB,
style: {
fontFamily: 'inherit',
@@ -1939,7 +2094,7 @@ export function useConfig() {
layout: {
height: 316,
width: 512,
- padding: PADDING([36, 48, 36, 48]),
+ padding: PADDING([0, 0, 0, 0]), // v3 modification
axis: {
show: true,
stroke: COLOR_GREY_LIGHT,
@@ -1956,7 +2111,16 @@ export function useConfig() {
borderRadius: 2,
useGradient: true,
showLines: false,
- linesStrokeWidth: 1
+ linesStrokeWidth: 1,
+ highlighter: {
+ show: true,
+ opacity: 0.1,
+ color: COLOR_BLACK,
+ stroke: COLOR_BLACK,
+ strokeWidth: 0.5,
+ strokeDasharray: 2,
+ highlightBothAxes: false,
+ }
},
plots: {
radius: 2,
@@ -2046,7 +2210,8 @@ export function useConfig() {
title: TITLE,
legend: {
...LEGEND,
- roundingValue: 0
+ roundingValue: 0,
+ position: 'bottom'
},
tooltip: {
...TOOLTIP,
@@ -2083,10 +2248,17 @@ export function useConfig() {
}
const vue_ui_candlestick = {
+ debug: false, // v3
+ loading: false, // v3
responsive: false,
responsiveProportionalSizing: true,
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
theme: '',
- useCssAnimation: true,
+ useCssAnimation: false, // v3 (v2 = true)
style: {
fontFamily: 'inherit',
backgroundColor: COLOR_WHITE,
@@ -2094,7 +2266,7 @@ export function useConfig() {
height: 316,
width: 512,
layout: {
- padding: PADDING([36, 48, 36, 48]),
+ padding: PADDING([0, 0, 0, 0]),
selector: {
color: COLOR_GREY_LIGHT,
opacity: 10
@@ -2111,6 +2283,10 @@ export function useConfig() {
offsetY: 0,
bold: false,
rotation: 0,
+ autoRotate: { // v3
+ enable: true, // v3
+ angle: -30 // v3
+ },
datetimeFormatter: AXIS_DATE_FORMATTER
},
},
@@ -2195,17 +2371,24 @@ export function useConfig() {
}
const vue_ui_sparkline = {
+ debug: false, // v3
+ loading: false, // v3
theme: '',
responsive: false,
type: SHAPE.LINE,
downsample: LTTB,
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null // v3
+ },
style: {
chartWidth: 290,
animation: {
show: true,
animationFrames: 360
},
- padding: PADDING([12, 0, 3, 0]),
+ padding: PADDING([12, 12, 3, 0]),
fontFamily: 'inherit',
backgroundColor: COLOR_WHITE,
scaleMin: null,
@@ -2247,7 +2430,8 @@ export function useConfig() {
valueType: 'latest',
prefix: '',
suffix: '',
- formatter: null
+ formatter: null,
+ datetimeFormatter: AXIS_DATE_FORMATTER // v3
},
title: {
show: true,
@@ -2278,6 +2462,13 @@ export function useConfig() {
}
const vue_ui_sparkbar = {
+ debug: false, // v3
+ loading: false, // v3
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
theme: '',
customPalette: [],
style: {
@@ -2337,6 +2528,13 @@ export function useConfig() {
}
const vue_ui_sparkstackbar = {
+ debug: false, // v3
+ loading: false, // v3
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
theme: '',
customPalette: [],
style: {
@@ -2390,6 +2588,14 @@ export function useConfig() {
}
const vue_ui_sparkhistogram = {
+ debug: false, // v3
+ loading: false, // v3
+ responsive: false, // v3
+ events: {
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
theme: '',
style: {
fontFamily: 'inherit',
@@ -2419,28 +2625,32 @@ export function useConfig() {
labels: {
value: {
fontSize: FONT._14,
+ minFontSize: MIN_FONT_SIZE, // v3
color: COLOR_BLACK,
bold: true,
rounding: 1,
prefix: '',
suffix: '',
offsetY: 0,
- formatter: null
+ formatter: null,
},
valueLabel: {
fontSize: FONT._14,
+ minFontSize: MIN_FONT_SIZE, // v3
color: COLOR_BLACK,
bold: false,
- rounding: 0
+ rounding: 0,
},
timeLabel: {
fontSize: FONT._12,
+ minFontSize: MIN_FONT_SIZE, // v3
color: COLOR_BLACK,
- bold: false
+ bold: false,
}
},
selector: {
stroke: COLOR_BLUE,
+ fill: '#2D353C10',
strokeWidth: 2,
strokeDasharray: 0,
borderRadius: 2
@@ -2463,6 +2673,8 @@ export function useConfig() {
}
const vue_ui_sparkgauge = {
+ debug: false, // v3
+ loading: false, // v3
theme: '',
style: {
fontFamily: 'inherit',
@@ -2510,11 +2722,16 @@ export function useConfig() {
}
const vue_ui_spark_trend = {
+ debug: false, // v3
+ loading: false, // v3
+ responsive: false, // v3
theme: '',
downsample: LTTB,
style: {
fontFamily: 'inherit',
backgroundColor: COLOR_WHITE,
+ height: 80,
+ width: 300,
animation: {
show: true,
animationFrames: 20
@@ -2563,6 +2780,13 @@ export function useConfig() {
}
const vue_ui_quick_chart = {
+ debug: false, // v3
+ loading: false, // v3
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
responsive: false,
theme: '',
axisLabelsFontSize: FONT._12,
@@ -2578,10 +2802,12 @@ export function useConfig() {
dataLabelRoundingPercentage: 1,
dataLabelRoundingValue: 0,
donutHideLabelUnderPercentage: 3,
+ donutCurvedMarkers: true, // v3
donutLabelMarkerStrokeWidth: 1,
donutRadiusRatio: 0.4,
donutShowTotal: true,
donutStrokeWidth: 2,
+ donutStroke: '#FFFFFF',
donutThicknessRatio: 0.18,
donutTotalLabelFontSize: FONT._24,
donutTotalLabelOffsetY: 0,
@@ -2594,6 +2820,7 @@ export function useConfig() {
legendFontSize: FONT._12,
legendIcon: 'circleFill',
legendIconSize: FONT._12,
+ legendPosition: 'bottom',
lineAnimated: true,
lineSmooth: true,
lineStrokeWidth: 2,
@@ -2622,12 +2849,9 @@ export function useConfig() {
annotator: 'Toggle annotator'
},
userOptionsPrint: {
- allowTaint: false,
- backgroundColor: COLOR_WHITE,
- useCORS: false,
- onclone: null,
+ overflowTolerance: 0.2,
+ orientation: 'auto',
scale: 2,
- logging: false,
},
userOptionsCallbacks: {
tooltip: null,
@@ -2648,6 +2872,8 @@ export function useConfig() {
tooltipFontSize: FONT._14,
tooltipPosition: POSITION.CENTER,
tooltipOffsetY: 24,
+ tooltipSmooth: true,
+ tooltipBackdropFilter: true,
useCustomLegend: false,
valuePrefix: '',
valueSuffix: '',
@@ -2661,11 +2887,15 @@ export function useConfig() {
xyHighlighterOpacity: 0.05,
xyLabelsXFontSize: FONT._10,
xyLabelsYFontSize: FONT._12,
- xyPaddingBottom: 48,
- xyPaddingLeft: 48,
+ xyPaddingBottom: 12,
+ xyPaddingLeft: 12,
xyPaddingRight: 12,
- xyPaddingTop: 24,
+ xyPaddingTop: 12,
xyPeriodLabelsRotation: 0,
+ xyPeriodLabelsAutoRotate: { // v3
+ enable: true, // v3
+ angle: -30, // v3
+ },
xyPeriods: [],
datetimeFormatter: AXIS_DATE_FORMATTER,
xyPeriodsShowOnlyAtModulo: false,
@@ -2688,7 +2918,14 @@ export function useConfig() {
}
const vue_ui_age_pyramid = {
+ debug: false, // v3
+ loading: false, // v3
responsive: false,
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null // v3
+ },
theme: '',
style: {
fontFamily: 'inherit',
@@ -2697,7 +2934,7 @@ export function useConfig() {
height: 500,
width: 500,
layout: {
- padding: PADDING([36, 12, 48, 12]),
+ padding: PADDING([12, 12, 36, 12]),
grid: {
show: true,
stroke: COLOR_GREY_LIGHT,
@@ -2719,7 +2956,12 @@ export function useConfig() {
bold: false,
scale: 1000,
translation: 'in thousands',
- formatter: null
+ formatter: null,
+ rotation: 0, // v3
+ autoRotate: { // v3
+ enable: true, // v3
+ angle: -30 // v3
+ }
},
yAxis: {
show: true,
@@ -2785,7 +3027,14 @@ export function useConfig() {
}
const vue_ui_relation_circle = {
+ debug: false, // v3
+ loading: false, // v3
responsive: false,
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
responsiveProportionalSizing: true,
theme: '',
customPalette: [],
@@ -2801,7 +3050,8 @@ export function useConfig() {
},
labels: {
color: COLOR_BLACK,
- fontSize: FONT._10
+ fontSize: FONT._14,
+ minFontSize: MIN_FONT_SIZE, // v3
},
weightLabels: {
size: 8,
@@ -2837,6 +3087,9 @@ export function useConfig() {
}
const vue_ui_thermometer = {
+ debug: false, // v3
+ loading: false, // v3
+ responsive: false, // v3
theme: '',
customPalette: [],
style: {
@@ -2845,10 +3098,15 @@ export function useConfig() {
backgroundColor: COLOR_WHITE,
color: COLOR_BLACK,
height: 360,
+ width: 256, // v3
thermometer: {
width: 48
},
- padding: PADDING([12, 64, 12, 64]),
+ padding: {
+ // v3 left and right are deprecated
+ top: 12,
+ bottom: 12,
+ },
graduations: {
show: true,
sides: 'both',
@@ -2866,7 +3124,9 @@ export function useConfig() {
speedMs: 1000
},
label: {
+ show: true, // v3
fontSize: FONT._20,
+ minFontSize: MIN_FONT_SIZE, // v3
rounding: 1,
bold: true,
color: COLOR_BLACK,
@@ -2886,10 +3146,17 @@ export function useConfig() {
}
const vue_ui_rings = {
+ debug: false, // v3
+ loading: false, // v3
responsive: false,
+ events: {
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
theme: '',
customPalette: [],
- useCssAnimation: true,
+ useCssAnimation: false, // v2 = true
useBlurOnHover: true,
style: {
fontFamily: 'inherit',
@@ -2920,7 +3187,8 @@ export function useConfig() {
roundingValue: 0,
roundingPercentage: 0,
showValue: true,
- showPercentage: true
+ showPercentage: true,
+ position: 'bottom'
},
title: TITLE,
tooltip: {
@@ -2958,7 +3226,15 @@ export function useConfig() {
}
const vue_ui_donut_evolution = {
+ debug: false, // v3
+ loading: false, // v3
+ responsive: false, // v3
theme: '',
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
customPalette: [],
style: {
fontFamily: 'inherit',
@@ -3025,12 +3301,20 @@ export function useConfig() {
layout: {
height: 316,
width: 512,
- padding: PADDING([24, 48, 24, 48]),
+ padding: PADDING([5, 10, 5, 10]),
grid: {
show: true,
stroke: COLOR_GREY_LIGHT,
strokeWidth: 0.7,
showVerticalLines: true,
+ axis: {
+ yLabel: '',
+ yLabelOffsetX: 0,
+ xLabel: '',
+ xLabelOffsetY: 0,
+ fontSize: FONT._14,
+ color: COLOR_BLACK,
+ },
yAxis: {
dataLabels: {
show: true,
@@ -3051,6 +3335,10 @@ export function useConfig() {
showOnlyFirstAndLast: false,
color: COLOR_BLACK,
rotation: 0,
+ autoRotate: { // v3
+ enable: true, // v3
+ angle: -30, // v3
+ },
offsetY: 0
}
}
@@ -3082,7 +3370,8 @@ export function useConfig() {
roundingValue: 0,
roundingPercentage: 0,
showValue: true,
- showPercentage: true
+ showPercentage: true,
+ position: 'bottom'
}
}
},
@@ -3110,6 +3399,14 @@ export function useConfig() {
}
const vue_ui_mood_radar = {
+ debug: false, // v3
+ loading: false, // v3
+ responsive: false, // v3
+ events: {
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
theme: '',
style: {
fontFamily: 'inherit',
@@ -3160,7 +3457,8 @@ export function useConfig() {
legend: {
...LEGEND,
roundingValue: 0,
- roundingPercentage: 0
+ roundingPercentage: 0,
+ position: 'bottom'
}
}
},
@@ -3189,6 +3487,13 @@ export function useConfig() {
}
const vue_ui_molecule = {
+ debug: false, // v3
+ loading: false, // v3
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
theme: '',
customPalette: [],
style: {
@@ -3233,17 +3538,24 @@ export function useConfig() {
}
const vue_ui_nested_donuts = {
+ debug: false, // v3
+ loading: false, // v3
responsive: false,
theme: '',
customPalette: [],
- useCssAnimation: true,
+ useCssAnimation: false, // v3 (v2 = true)
useBlurOnHover: true,
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null // v3
+ },
serieToggleAnimation: {
show: true,
durationMs: 500,
},
startAnimation: {
- show: true,
+ show: false, // v3 (v2 = false)
durationMs: 1000,
staggerMs: 50
},
@@ -3287,7 +3599,8 @@ export function useConfig() {
roundingPercentage: 0,
showDonutName: true,
boldDonutName: true,
- donutNameAbbreviation: true,
+ curvedDonutName: true, // v3
+ donutNameAbbreviation: false, // v2 = true
donutNameMaxAbbreviationSize: 3,
donutNameOffsetY: 0,
formatter: null
@@ -3310,7 +3623,8 @@ export function useConfig() {
roundingValue: 0,
roundingPercentage: 0,
showValue: true,
- showPercentage: true
+ showPercentage: true,
+ position: 'bottom'
},
title: TITLE,
tooltip: {
@@ -3340,9 +3654,17 @@ export function useConfig() {
}
const vue_ui_galaxy = {
+ debug: false, // v3
+ loading: false, // v3
+ responsive: false, // v3
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
theme: '',
customPalette: [],
- useCssAnimation: true,
+ useCssAnimation: false, // v2 = true
useBlurOnHover: true,
style: {
fontFamily: 'inherit',
@@ -3378,7 +3700,8 @@ export function useConfig() {
roundingValue: 0,
roundingPercentage: 0,
showValue: true,
- showPercentage: true
+ showPercentage: true,
+ position: 'bottom'
},
title: TITLE,
tooltip: {
@@ -3416,7 +3739,14 @@ export function useConfig() {
}
const vue_ui_strip_plot = {
+ debug: false, // v3
+ loading: false, // v3
responsive: false,
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
responsiveProportionalSizing: true,
theme: '',
customPalette: [],
@@ -3437,8 +3767,9 @@ export function useConfig() {
backgroundColor: COLOR_WHITE,
color: COLOR_BLACK,
height: 600,
- stripWidth: 120,
- padding: PADDING([24, 24, 64, 64]),
+ width: 600, // v3
+ // stripWidth: 120, v3 deprecated
+ padding: PADDING([12, 12, 12, 12]), // v3 modified
grid: {
show: true,
stroke: COLOR_GREY_MID,
@@ -3492,7 +3823,12 @@ export function useConfig() {
show: true,
color: COLOR_BLACK,
fontSize: FONT._14,
- offsetY: 0
+ offsetY: 0,
+ rotation: 0, // v3,
+ autoRotate: { // v3
+ enable: true, // v3
+ angle: -30, // v3
+ }
},
yAxisLabels: {
show: true,
@@ -3524,9 +3860,16 @@ export function useConfig() {
}
const vue_ui_dumbbell = {
+ debug: false, // v3
+ loading: false, // v3
responsive: false,
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
theme: '',
- useAnimation: true,
+ useAnimation: false, // v2 = true
animationSpeed: 2,
userOptions: USER_OPTIONS({
pdf: true,
@@ -3542,11 +3885,17 @@ export function useConfig() {
backgroundColor: COLOR_WHITE,
color: COLOR_BLACK,
width: 600,
- rowHeight: 40,
- padding: PADDING([12, 24, 12, 100]),
+ rowHeight: 48, // v3 modified
+ padding: PADDING([12, 24, 12, 12]), // v3 modified
plots: {
startColor: COLOR_RED,
endColor: COLOR_BLUE,
+ evaluationColors: { // v3
+ enable: false, // v3
+ positive: '#2ca02c', // v3
+ negative: '#d62728', // v3
+ neutral: '#c7c7c7', // v3
+ },
radius: 6,
stroke: COLOR_WHITE,
strokeWidth: 1,
@@ -3562,6 +3911,8 @@ export function useConfig() {
grid: {
strokeWidth: 1,
scaleSteps: 10,
+ scaleMin: null, // v3
+ scaleMax: null, // V3
horizontalGrid: {
show: true,
stroke: COLOR_GREY_MID,
@@ -3575,10 +3926,33 @@ export function useConfig() {
strokeDasharray: 0
}
},
+ comparisonLines: { // v3
+ show: true,// v3
+ strokeWidth: 1, // v3
+ strokeDasharray: 4, // v3
+ showRect: true, // v3
+ rectColor: COLOR_BLACK, // v3
+ rectOpacity: 5, // v3
+ showLabel: true, // v3
+ labelColor: COLOR_BLACK, // v3
+ labelFontSize: FONT._12, // v3
+ },
+ highlighter: { // v3
+ color: COLOR_BLACK, // v3
+ opacity: 5, // v3
+ },
labels: {
prefix: '',
suffix: '',
formatter: null,
+ axis: { // v3
+ yLabel: '', // v3
+ yLabelOffsetX: 0, // v3
+ xLabel: '', // v3
+ xLabelOffsetY: 0, // v3
+ fontSize: FONT._14, // v3
+ color: COLOR_BLACK, // v3
+ },
yAxisLabels: {
show: true,
fontSize: FONT._14,
@@ -3586,7 +3960,8 @@ export function useConfig() {
offsetX: 0,
bold: true,
showProgression: true,
- rounding: 1
+ rounding: 1,
+ formatter: null // v3
},
xAxisLabels: {
show: true,
@@ -3594,7 +3969,12 @@ export function useConfig() {
color: COLOR_BLACK,
offsetY: 0,
bold: false,
- rounding: 0
+ rounding: 0,
+ rotation: 0, // v3
+ autoRotate: { // v3
+ enable: true, // v3
+ angle: -30, // v3
+ }
},
startLabels: {
show: true,
@@ -3602,7 +3982,8 @@ export function useConfig() {
color: COLOR_BLACK,
offsetY: 0,
rounding: 0,
- useStartColor: true
+ useStartColor: true,
+ useEvaluationColor: true, // v3
},
endLabels: {
show: true,
@@ -3610,13 +3991,18 @@ export function useConfig() {
color: COLOR_BLACK,
offsetY: 0,
rounding: 0,
- useEndColor: true
+ useEndColor: true,
+ useEvaluationColor: true, // v3
}
},
legend: {
...LEGEND,
labelStart: 'start',
- labelEnd: 'end'
+ labelEnd: 'end',
+ labelPositive: 'positive', // v3
+ labelNegative: 'negative', // v3
+ labelNeutral: 'neutral', // v3
+ position: 'bottom'
},
title: TITLE
}
@@ -3639,8 +4025,17 @@ export function useConfig() {
}
const vue_ui_3d_bar = {
+ debug: false, // v3
+ loading: false, // v3
+ responsive: false, // v3
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
theme: '',
customPalette: [],
+ useCssAnimation: false, // v3
style: {
fontFamily: 'inherit',
shape: SHAPE.BAR,
@@ -3840,10 +4235,17 @@ export function useConfig() {
}
const vue_ui_word_cloud = {
+ debug: false, // v3
+ loading: false, // v3
responsive: false,
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
theme: '',
customPalette: [],
- useCssAnimation: true,
+ useCssAnimation: false, // v2 = true
animationDelayMs: 20,
strictPixelPadding: false, // If true, strict per-pixel padding is used (dilateWordMask); if false, just rectangular bounding box (or pad).
userOptions: USER_OPTIONS({
@@ -3866,9 +4268,6 @@ export function useConfig() {
width: 512,
zoom: {
show: true,
- color: COLOR_GREY_MID, // deprecated
- highlightColor: COLOR_GREY_DARK, // deprecated
- useResetSlot: false // deprecated
},
words: {
maxFontSize: 100,
@@ -3941,7 +4340,10 @@ export function useConfig() {
showHorizontalSelector: false,
},
tooltip: TOOLTIP,
- legend: LEGEND,
+ legend: {
+ ...LEGEND,
+ position: 'bottom',
+ },
title: TITLE,
grid: {
y: {
@@ -4040,6 +4442,14 @@ export function useConfig() {
}
const vue_ui_flow = {
+ debug: false, // v3
+ loading: false, // v3
+ responsive: false, // v3
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
theme: '',
customPalette: [],
userOptions: USER_OPTIONS({
@@ -4057,8 +4467,10 @@ export function useConfig() {
fontFamily: 'inherit',
chart: {
backgroundColor: COLOR_WHITE,
+ width: 1000, // v3
+ height: 600, // v3
color: COLOR_BLACK,
- padding: PADDING([12, 12, 12, 12]),
+ padding: PADDING([12, 12, 12, 12]), // v3 update
title: TITLE,
tooltip: {
...TOOLTIP,
@@ -4072,12 +4484,14 @@ export function useConfig() {
},
legend: {
...LEGEND,
+ position: 'bottom'
},
nodes: {
gap: 10,
- minHeight: 20,
+ // minHeight: 20, // v3 deprecated
width: 40,
labels: {
+ show: true, // v3
fontSize: FONT._14,
abbreviation: {
use: true,
@@ -4092,7 +4506,7 @@ export function useConfig() {
strokeWidth: 1
},
links: {
- width: 200,
+ // width: 200, // v3 deprecated
opacity: 0.8,
stroke: COLOR_WHITE,
strokeWidth: 1
@@ -4112,10 +4526,17 @@ export function useConfig() {
}
const vue_ui_parallel_coordinate_plot = {
+ debug: false, // v3
+ loading: false, // v3
responsive: false,
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
responsiveProportionalSizing: true,
theme: '',
- useCssAnimation: true,
+ useCssAnimation: false, // v3
customPalette: [],
userOptions: USER_OPTIONS({
tooltip: true,
@@ -4134,7 +4555,7 @@ export function useConfig() {
color: COLOR_BLACK,
height: 600,
width: 1000,
- padding: PADDING([24, 24, 36, 36]),
+ padding: PADDING([0, 0, 0, 0]),
comments: {
show: true,
showInTooltip: true,
@@ -4159,6 +4580,11 @@ export function useConfig() {
labels: {
showAxisNames: true,
axisNames: [],
+ axisNamesRotation: 0, // v3
+ axisNamesAutoRotate: { // v3
+ enable: true, // v3
+ angle: -30 // v3
+ },
axisNamesColor: COLOR_BLACK,
axisNamesFontSize: FONT._16,
axisNamesBold: true,
@@ -4186,7 +4612,10 @@ export function useConfig() {
}
},
title: TITLE,
- legend: LEGEND,
+ legend: {
+ ...LEGEND,
+ position: 'bottom'
+ },
tooltip: TOOLTIP
}
},
@@ -4316,6 +4745,7 @@ export function useConfig() {
}
const vue_ui_kpi = {
+ debug: false, // v3
animationFrames: 60,
animationValueStart: 0,
backgroundColor: COLOR_WHITE,
@@ -5071,6 +5501,8 @@ export function useConfig() {
}
const vue_ui_gizmo = {
+ debug: false, // v3
+ loading: false, // v3
type: 'battery', // battery | gauge
size: 64,
stroke: COLOR_GREY_MID,
@@ -5084,6 +5516,9 @@ export function useConfig() {
}
const vue_ui_bullet = {
+ debug: false, // v3
+ loading: false, // v3
+ responsive: false, // v3
theme: '',
userOptions: USER_OPTIONS({
tooltip: false,
@@ -5128,6 +5563,7 @@ export function useConfig() {
}
},
target: {
+ show: true, // v3
onTop: true,
color: COLOR_BLACK,
rounded: true,
@@ -5156,7 +5592,8 @@ export function useConfig() {
},
legend: {
...LEGEND,
- roundingValue: 0
+ roundingValue: 0,
+ position: 'bottom'
},
}
}
@@ -5256,11 +5693,18 @@ export function useConfig() {
}
const vue_ui_history_plot = {
+ debug: false, // v3
+ loading: false, // v3
responsive: false,
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
responsiveProportionalSizing: true,
theme: '',
customPalette: [],
- useCssAnimation: true,
+ useCssAnimation: false, // v2 = true
userOptions: USER_OPTIONS({
tooltip: true,
pdf: true,
@@ -5293,7 +5737,7 @@ export function useConfig() {
color: COLOR_BLACK,
height: 500,
width: 600,
- padding: PADDING([12, 24, 48, 48]),
+ padding: PADDING([12, 12, 12, 12]),
grid: {
xAxis: {
show: true,
@@ -5329,6 +5773,10 @@ export function useConfig() {
rounding: 1,
offsetY: 0,
rotation: 0,
+ autoRotate: { // v3
+ enable: true, // v3
+ angle: -30, // v3
+ },
formatter: null,
prefix: '',
suffix: ''
@@ -5402,6 +5850,7 @@ export function useConfig() {
},
legend: {
...LEGEND,
+ position: 'bottom'
},
title: TITLE,
tooltip: TOOLTIP
@@ -5411,6 +5860,13 @@ export function useConfig() {
}
const vue_ui_circle_pack = {
+ debug: false, // v3
+ loading: false, // v3
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
theme: '',
customPalette: [],
userOptions: USER_OPTIONS({
@@ -5497,6 +5953,13 @@ export function useConfig() {
}
const vue_ui_world = {
+ debug: false, // v3
+ loading: false, // v3
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
userOptions: USER_OPTIONS({
tooltip: true,
pdf: true,
@@ -5555,7 +6018,10 @@ export function useConfig() {
showMinimap: true
},
title: TITLE,
- legend: LEGEND
+ legend: {
+ ...LEGEND,
+ position: 'bottom'
+ }
}
},
table: {
@@ -5574,10 +6040,17 @@ export function useConfig() {
}
const vue_ui_ridgeline = {
+ debug: false, // v3
+ loading: false, // v3
+ responsive: false,
+ events: { // v3
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
theme: '',
customPalette: [],
- responsive: false,
- useCssAnimation: true,
+ useCssAnimation: false, // v2 = true
userOptions: USER_OPTIONS({
tooltip: false,
pdf: true,
@@ -5621,6 +6094,7 @@ export function useConfig() {
responsive: true,
line: {
...vue_ui_xy.line,
+ showTransition: false,
labels: {
...vue_ui_xy.line.labels,
show: true
@@ -5628,12 +6102,6 @@ export function useConfig() {
},
chart: {
...vue_ui_xy.chart,
- padding: {
- top: 24,
- right: 25,
- bottom: 24,
- left: 64
- },
tooltip: {
...vue_ui_xy.chart.tooltip,
showPercentage: false
@@ -5707,6 +6175,10 @@ export function useConfig() {
prefix: '',
suffix: '',
rotation: 0,
+ autoRotate: { // v3
+ enable: true, // v3
+ angle: -30 // v3
+ },
values: [],
datetimeFormatter: AXIS_DATE_FORMATTER,
color: COLOR_BLACK,
@@ -5731,12 +6203,19 @@ export function useConfig() {
}
const vue_ui_chord = {
+ debug: false, // v3
+ loading: false, // v3
+ responsive: false,
+ events: {
+ datapointEnter: null, // v3
+ datapointLeave: null, // v3
+ datapointClick: null, // v3
+ },
theme: '',
customPalette: [],
enableRotation: true,
initialRotation: 0,
- useCssAnimation: true,
- responsive: false,
+ useCssAnimation: false, // v2 = true
userOptions: USER_OPTIONS({
tooltip: false,
pdf: true,
@@ -5758,7 +6237,10 @@ export function useConfig() {
chart: {
backgroundColor: COLOR_WHITE,
color: COLOR_BLACK,
- legend: LEGEND,
+ legend: {
+ ...LEGEND,
+ position: 'bottom'
+ },
title: TITLE,
arcs: {
innerRadiusRatio: 1,
@@ -5822,7 +6304,8 @@ export function useConfig() {
vue_ui_tiremarks,
vue_ui_chestnut,
vue_ui_onion,
- vue_ui_vertical_bar,
+ vue_ui_vertical_bar, // deprecate in v4
+ vue_ui_horizontal_bar, // v3
vue_ui_heatmap,
vue_ui_scatter,
vue_ui_candlestick,
diff --git a/src/useFitSvgText.js b/src/useFitSvgText.js
new file mode 100644
index 00000000..2d8e0374
--- /dev/null
+++ b/src/useFitSvgText.js
@@ -0,0 +1,108 @@
+import { nextTick } from "vue";
+
+export function useFitSvgText({
+ svgRef,
+ unitWidth,
+ fontSize = 12,
+ step = 0.5,
+ maxIterations = 60,
+ hideUnderMin = true
+}) {
+ const originals = new WeakMap();
+
+ const parsePx = (v) => {
+ if (typeof v === "number") return v;
+ if (!v) return NaN;
+ const n = parseFloat(String(v).replace("px", ""));
+ return Number.isFinite(n) ? n : NaN;
+ };
+
+ const getFontSize = (el) => {
+ const attr = parsePx(el.getAttribute("font-size"));
+ if (Number.isFinite(attr)) return attr;
+ const cs = window.getComputedStyle(el);
+ return parsePx(cs.fontSize) || fontSize;
+ };
+
+ const setFontSize = (el, sizePx) => {
+ el.setAttribute("font-size", String(sizePx));
+ };
+
+ const resetFontSize = (el) => {
+ if (originals.has(el)) {
+ setFontSize(el, originals.get(el));
+ } else {
+ const base = getFontSize(el);
+ originals.set(el, base);
+ setFontSize(el, base);
+ }
+ };
+
+ const measureWidth = (el) => {
+ try {
+ const box = el.getBBox();
+ return box.width || 0;
+ } catch {
+ return 0;
+ }
+ };
+
+ const fitText = async (selector, minFontSize = 6) => {
+ await nextTick();
+ const svg = svgRef?.value;
+ if (!svg) return;
+
+ const nodes = svg.querySelectorAll(selector);
+ if (!nodes.length) return;
+
+ const targetWidth = Math.max(0, unitWidth.value);
+ if (targetWidth <= 0) return;
+
+ const results = [];
+ nodes.forEach((el) => {
+ const prevDisplay = el.style.display;
+ const prevOpacity = el.style.opacity;
+ el.style.display = "";
+ el.style.opacity = "0";
+
+ resetFontSize(el);
+
+ let width = measureWidth(el);
+ let baseSize = getFontSize(el);
+ let size = baseSize;
+ let iter = 0;
+
+ if (width <= targetWidth) {
+ results.push({ el, finalSize: baseSize, fits: true });
+ } else {
+ while (width > targetWidth && size > minFontSize && iter < maxIterations) {
+ size = Math.max(minFontSize, size - step);
+ setFontSize(el, size);
+ width = measureWidth(el);
+ iter += 1;
+ }
+ const fits = width <= targetWidth && size > minFontSize;
+ results.push({ el, finalSize: size, fits });
+
+ setFontSize(el, baseSize);
+ }
+ el.style.display = prevDisplay;
+ el.style.opacity = prevOpacity;
+ });
+
+ const shouldHideAll = hideUnderMin && results.some(r => !r.fits);
+
+ results.forEach(({ el, finalSize, fits }) => {
+ if (shouldHideAll) {
+ el.style.display = "none";
+ } else {
+ el.style.display = "";
+ setFontSize(el, finalSize);
+ }
+ });
+ };
+
+ return {
+ fitText
+ };
+}
diff --git a/src/useLoading.js b/src/useLoading.js
new file mode 100644
index 00000000..b596a0ac
--- /dev/null
+++ b/src/useLoading.js
@@ -0,0 +1,34 @@
+import { ref, watchEffect, unref, computed } from 'vue';
+
+export function useLoading({
+ config,
+ dataset,
+ skeletonDataset,
+ skeletonConfig,
+ FINAL_CONFIG,
+ prepareConfig,
+ callback = null,
+ dsIsNumber = false
+}) {
+ const manualLoading = ref(false);
+
+ const loading = computed(() => {
+ const configLoading = unref(config)?.loading ?? false;
+ const ds = unref(dataset);
+ const datasetEmpty = dsIsNumber ? [null, undefined].includes(ds) : ds == null
+ || (Array.isArray(ds) && ds.length === 0)
+ || Object.keys(ds).length === 0
+
+ return manualLoading.value || configLoading || datasetEmpty;
+ });
+
+ const FINAL_DATASET = ref(unref(dataset));
+
+ watchEffect(() => {
+ FINAL_DATASET.value = loading.value ? skeletonDataset : unref(dataset);
+ FINAL_CONFIG.value = loading.value ? skeletonConfig : prepareConfig();
+ callback && callback();
+ });
+
+ return { loading, FINAL_DATASET, manualLoading, skeletonDataset, skeletonConfig };
+}
\ No newline at end of file
diff --git a/src/usePanZoom.js b/src/usePanZoom.js
index 4f73dbcb..ba68a0e5 100644
--- a/src/usePanZoom.js
+++ b/src/usePanZoom.js
@@ -1,174 +1,125 @@
-import { ref, onMounted, onUnmounted, watch, nextTick, watchEffect, computed } from 'vue';
+import { ref, onMounted, onUnmounted, watch, watchEffect, computed } from 'vue';
-export default function usePanZoom(svgRef, initialViewBox = { x: 0, y: 0, width: 100, height: 100 }, speed = 1, activeRef) {
- const viewBox = ref({ ...initialViewBox });
+export default function usePanZoom(
+ svgRef,
+ initialViewBox = { x: 0, y: 0, width: 100, height: 100 },
+ speed = 1,
+ activeRef
+) {
+ const initial = ref({ ...initialViewBox });
+ const viewBox = ref({ ...initial.value });
const scale = ref(1);
const isPanning = ref(false);
- const startPoint = ref({ x: 0, y: 0 });
+ const isPinching = ref(false);
const pinchStartDist = ref(0);
const pinchStartViewBox = ref(null);
- const isPinching = ref(false);
+ const startClient = ref({ x: 0, y: 0 });
- const isZoom = computed(() => {
- return viewBox.value.x !== initialViewBox.x ||
- viewBox.value.y !== initialViewBox.y ||
- viewBox.value.width !== initialViewBox.width ||
- viewBox.value.height !== initialViewBox.height
- })
+ const isZoom = computed(() =>
+ viewBox.value.x !== initial.value.x ||
+ viewBox.value.y !== initial.value.y ||
+ viewBox.value.width !== initial.value.width ||
+ viewBox.value.height !== initial.value.height
+ );
- let velocity = { x: 0, y: 0 };
- let animationFrame = null;
let zoomAnimationFrame = null;
function getTouchDistance(touches) {
if (touches.length < 2) return 0;
const dx = touches[0].clientX - touches[1].clientX;
const dy = touches[0].clientY - touches[1].clientY;
- return Math.sqrt(dx * dx + dy * dy);
+ return Math.hypot(dx, dy);
}
- function toSvgPoint(event) {
+ function toSvgPoint(evt) {
const svg = svgRef.value;
if (!svg) return { x: 0, y: 0 };
+ const pt = svg.createSVGPoint();
+ pt.x = evt.clientX;
+ pt.y = evt.clientY;
+ const inv = svg.getScreenCTM()?.inverse();
+ return inv ? pt.matrixTransform(inv) : { x: 0, y: 0 };
+ }
- const point = svg.createSVGPoint();
- point.x = event.clientX;
- point.y = event.clientY;
-
- const matrix = svg.getScreenCTM()?.inverse();
- return matrix ? point.matrixTransform(matrix) : { x: 0, y: 0 };
- };
+ function pxToVb(dxPx, dyPx) {
+ const svg = svgRef.value;
+ if (!svg) return { dx: 0, dy: 0 };
+ const w = Math.max(1, svg.clientWidth);
+ const h = Math.max(1, svg.clientHeight);
+ const sx = viewBox.value.width / w;
+ const sy = viewBox.value.height / h;
+ return { dx: dxPx * sx, dy: dyPx * sy };
+ }
- function startPan(event) {
+ function startPan(e) {
isPanning.value = true;
- const point = toSvgPoint(event.touches ? event.touches[0] : event);
- startPoint.value = { x: point.x, y: point.y };
- velocity = { x: 0, y: 0 };
- };
+ const t = e.touches?.[0] || e;
+ startClient.value = { x: t.clientX, y: t.clientY };
+ if (svgRef.value) svgRef.value.style.cursor = 'grabbing';
+ }
- function doPan(event) {
+ function doPan(e) {
if (!isPanning.value) return;
-
- const point = toSvgPoint(event.touches ? event.touches[0] : event);
-
- let dx = point.x - startPoint.value.x;
- let dy = point.y - startPoint.value.y;
-
- if (Math.abs(dx) < 0.3 && Math.abs(dy) < 0.3) return;
-
- velocity.x = dx * 0.8 + velocity.x * 0.2;
- velocity.y = dy * 0.8 + velocity.y * 0.2;
-
- startPoint.value = point;
-
- if (!animationFrame) {
- animationFrame = requestAnimationFrame(applyPan);
- }
- };
-
- function applyPan() {
- viewBox.value.x -= velocity.x;
- viewBox.value.y -= velocity.y;
- animationFrame = null;
- };
+ const t = e.touches?.[0] || e;
+ const dxPx = t.clientX - startClient.value.x;
+ const dyPx = t.clientY - startClient.value.y;
+ if (dxPx === 0 && dyPx === 0) return;
+ const { dx, dy } = pxToVb(dxPx, dyPx);
+ viewBox.value.x -= dx;
+ viewBox.value.y -= dy;
+ startClient.value = { x: t.clientX, y: t.clientY };
+ }
function endPan() {
isPanning.value = false;
- };
-
- function resetZoom(animated = false) {
- if (!animated) {
- viewBox.value = { ...initialViewBox };
- scale.value = 1;
- return;
- }
-
- const startViewBox = { ...viewBox.value };
- const startScale = scale.value;
- const duration = 300;
- let start = null;
-
- function animate(ts) {
- if (!start) start = ts;
- const progress = Math.min((ts - start) / duration, 1);
-
- viewBox.value = {
- x: startViewBox.x + (initialViewBox.x - startViewBox.x) * progress,
- y: startViewBox.y + (initialViewBox.y - startViewBox.y) * progress,
- width: startViewBox.width + (initialViewBox.width - startViewBox.width) * progress,
- height: startViewBox.height + (initialViewBox.height - startViewBox.height) * progress,
- };
- scale.value = startScale + (1 - startScale) * progress;
-
- if (progress < 1) {
- requestAnimationFrame(animate);
- } else {
- viewBox.value = { ...initialViewBox };
- scale.value = 1;
- }
- }
- requestAnimationFrame(animate);
+ if (svgRef.value) svgRef.value.style.cursor = '';
}
function zoom(event) {
event.preventDefault();
- const zoomFactor = event.deltaY > 0 ? 0.9 : 1.1;
- applyZoom(zoomFactor, toSvgPoint(event));
- };
+ const factor = event.deltaY > 0 ? 0.9 : 1.1;
+ applyZoom(factor, toSvgPoint(event));
+ }
function doubleClickZoom(event) {
event.preventDefault();
const cursorPoint = toSvgPoint(event);
- const zoomFactor = 1.02 * (1 + (speed / 100)); // Always zoom in
-
- animateZoom(zoomFactor, cursorPoint);
- };
+ const factor = 1.02 * (1 + speed / 100);
+ animateZoom(factor, cursorPoint);
+ }
- function animateZoom(zoomFactor, cursorPoint) {
+ function animateZoom(factor, cursorPoint) {
if (zoomAnimationFrame) cancelAnimationFrame(zoomAnimationFrame);
-
- let startScale = scale.value;
- let targetScale = startScale * zoomFactor;
+ const startScale = scale.value;
+ const targetScale = startScale * factor;
let progress = 0;
- const animate = () => {
- progress += 0.02;
-
- let currentScale = startScale + (targetScale - startScale) * easeInOutQuad(progress);
-
- if (progress >= 1) {
- scale.value = targetScale;
- zoomAnimationFrame = null;
- return;
- }
+ const ease = (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t);
+ const step = () => {
+ progress += 0.02;
+ const currentScale = startScale + (targetScale - startScale) * ease(progress);
applyZoom(currentScale / startScale, cursorPoint);
- zoomAnimationFrame = requestAnimationFrame(animate);
+ if (progress < 1) zoomAnimationFrame = requestAnimationFrame(step);
+ else zoomAnimationFrame = null;
};
-
- animate();
- };
-
- const easeInOutQuad = (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
+ step();
+ }
function applyZoom(zoomFactor, cursorPoint) {
const newScale = scale.value * zoomFactor;
- const scaleFactor = newScale / scale.value;
-
- const newWidth = viewBox.value.width / scaleFactor;
- const newHeight = viewBox.value.height / scaleFactor;
-
- const dx = (cursorPoint.x - viewBox.value.x) * (1 - 1 / scaleFactor);
- const dy = (cursorPoint.y - viewBox.value.y) * (1 - 1 / scaleFactor);
-
+ const k = newScale / scale.value;
+ const newW = viewBox.value.width / k;
+ const newH = viewBox.value.height / k;
+ const dx = (cursorPoint.x - viewBox.value.x) * (1 - 1 / k);
+ const dy = (cursorPoint.y - viewBox.value.y) * (1 - 1 / k);
viewBox.value.x += dx;
viewBox.value.y += dy;
- viewBox.value.width = newWidth;
- viewBox.value.height = newHeight;
-
+ viewBox.value.width = newW;
+ viewBox.value.height = newH;
scale.value = newScale;
- };
+ }
function handleTouchStart(event) {
if (event.touches.length === 2) {
@@ -189,9 +140,9 @@ export default function usePanZoom(svgRef, initialViewBox = { x: 0, y: 0, width:
const zoomFactor = dist / pinchStartDist.value;
const svg = svgRef.value;
const rect = svg.getBoundingClientRect();
- const midX = (event.touches[0].clientX + event.touches[1].clientX) / 2 - rect.left;
- const midY = (event.touches[0].clientY + event.touches[1].clientY) / 2 - rect.top;
- const midPoint = toSvgPoint({ clientX: midX + rect.left, clientY: midY + rect.top });
+ const midX = (event.touches[0].clientX + event.touches[1].clientX) / 2;
+ const midY = (event.touches[0].clientY + event.touches[1].clientY) / 2;
+ const midPoint = toSvgPoint({ clientX: midX, clientY: midY });
viewBox.value = { ...pinchStartViewBox.value };
applyZoom(zoomFactor, midPoint);
}
@@ -202,25 +153,57 @@ export default function usePanZoom(svgRef, initialViewBox = { x: 0, y: 0, width:
}
function handleTouchEnd(event) {
- if (event.touches.length < 2) {
- isPinching.value = false;
- }
+ if (event.touches.length < 2) isPinching.value = false;
endPan();
}
- onMounted(addEventListeners);
- onUnmounted(removeEventListeners);
+ function resetZoom(animated = false) {
+ if (!animated) {
+ viewBox.value = { ...initial.value };
+ scale.value = 1;
+ return;
+ }
+
+ const from = { ...viewBox.value };
+ const fromScale = scale.value;
+ const duration = 300;
+ let t0 = null;
+
+ const step = (ts) => {
+ if (t0 == null) t0 = ts;
+ const p = Math.min((ts - t0) / duration, 1);
+ viewBox.value = {
+ x: from.x + (initial.value.x - from.x) * p,
+ y: from.y + (initial.value.y - from.y) * p,
+ width: from.width + (initial.value.width - from.width) * p,
+ height: from.height + (initial.value.height - from.height) * p,
+ };
+ scale.value = fromScale + (1 - fromScale) * p;
+ if (p < 1) requestAnimationFrame(step);
+ };
+ requestAnimationFrame(step);
+ }
+
+ function setInitialViewBox(box, opts = {}) {
+ const { overwriteCurrentIfNotZoomed = true } = opts;
+ initial.value = { ...box };
+ if (!isZoom.value && overwriteCurrentIfNotZoomed) {
+ viewBox.value = { ...initial.value };
+ scale.value = 1;
+ }
+ }
function addEventListeners() {
const svg = svgRef.value;
if (!svg) return;
-
svg.addEventListener('mousedown', startPan);
svg.addEventListener('mousemove', doPan);
svg.addEventListener('mouseup', endPan);
svg.addEventListener('mouseleave', endPan);
+
svg.addEventListener('wheel', zoom, { passive: false });
svg.addEventListener('dblclick', doubleClickZoom);
+
svg.addEventListener('touchstart', handleTouchStart, { passive: false });
svg.addEventListener('touchmove', handleTouchMove, { passive: false });
svg.addEventListener('touchend', handleTouchEnd);
@@ -230,29 +213,36 @@ export default function usePanZoom(svgRef, initialViewBox = { x: 0, y: 0, width:
function removeEventListeners() {
const svg = svgRef.value;
if (!svg) return;
+
svg.removeEventListener('mousedown', startPan);
svg.removeEventListener('mousemove', doPan);
svg.removeEventListener('mouseup', endPan);
svg.removeEventListener('mouseleave', endPan);
+
svg.removeEventListener('wheel', zoom);
svg.removeEventListener('dblclick', doubleClickZoom);
+
svg.removeEventListener('touchstart', handleTouchStart);
svg.removeEventListener('touchmove', handleTouchMove);
svg.removeEventListener('touchend', handleTouchEnd);
svg.removeEventListener('touchcancel', handleTouchEnd);
}
+ onMounted(addEventListeners);
+ onUnmounted(removeEventListeners);
+
+ // Rebind if zoom is toggled on/off
watchEffect(() => {
- if (activeRef.value) {
- addEventListeners();
- } else {
- removeEventListeners();
- }
+ if (activeRef?.value) addEventListeners();
+ else removeEventListeners();
+ return () => removeEventListeners();
+ });
- return () => {
- removeEventListeners();
- };
+ // Rebind if the svg node itself changes
+ watch(svgRef, (_el, _oldEl) => {
+ removeEventListeners();
+ addEventListeners();
});
- return { viewBox, resetZoom, isZoom };
+ return { viewBox, resetZoom, isZoom, setInitialViewBox };
}
diff --git a/src/usePrinter.js b/src/usePrinter.js
index fa5b7567..5e1e96af 100644
--- a/src/usePrinter.js
+++ b/src/usePrinter.js
@@ -21,7 +21,9 @@ export function usePrinter({
await pdf({
domElement: document.getElementById(elementId),
fileName,
- options
+ orientation: options.orientation,
+ overflowTolerance: options.overflowTolerance,
+ scale: options.scale
});
} catch (error) {
console.error("Error generating PDF:", error);
diff --git a/src/useTimeLabelCollider.js b/src/useTimeLabelCollider.js
new file mode 100644
index 00000000..26a2ca37
--- /dev/null
+++ b/src/useTimeLabelCollider.js
@@ -0,0 +1,130 @@
+import { nextTick, watch } from 'vue';
+
+export function useTimeLabelCollision({
+ timeLabelsEls,
+ timeLabels,
+ slicer,
+ configRef,
+ rotationPath,
+ autoRotatePath,
+ isAutoSize,
+ setViewBox,
+ forceResizeObserver,
+ callback,
+ targetClass = '.vue-data-ui-time-label',
+ rotation = -30.0001,
+ height = null,
+ width = null
+}) {
+
+ function getNestedProp(obj, path) {
+ return path.reduce((acc, key) => acc && acc[key], obj);
+ }
+
+ function setNestedProp(obj, path, value) {
+ path.slice(0, -1).reduce((acc, key) => acc[key], obj)[path.slice(-1)] = value;
+ }
+
+ function parseTranslate(transformStr) {
+ const match = /translate\(\s*([^\s,]+)\s*,\s*([^\s,]+)\s*\)/.exec(transformStr);
+ if (!match) {
+ return { x: 0, y: 0 };
+ }
+ return {
+ x: parseFloat(match[1]),
+ y: parseFloat(match[2])
+ };
+ }
+
+ async function detectTimeLabelCollision() {
+ await nextTick();
+ const container = timeLabelsEls.value;
+ if (!container) return;
+
+ const texts = Array.from(container.querySelectorAll(targetClass));
+ const textCoordinates = texts.map(t => ({
+ ...parseTranslate(t.getAttribute('transform')),
+ width: t.getBBox().width
+ }));
+
+ let collision = false;
+ const collisionMargin = 4;
+
+ for (let i = 0; i < textCoordinates.length && !collision; i += 1) {
+ for (let j = i + 1; j < textCoordinates.length; j += 1) {
+ const a = textCoordinates[i];
+ const b = textCoordinates[j];
+
+ if (!(
+ a.x + a.width + collisionMargin < b.x ||
+ b.x + b.width + collisionMargin < a.x
+ )) {
+ collision = true;
+ break;
+ }
+ }
+ }
+
+ const currentRotation = getNestedProp(configRef.value, rotationPath);
+
+ if (collision && !currentRotation) {
+ setNestedProp(configRef.value, rotationPath, rotation);
+ callback && callback({ collision })
+ if (isAutoSize.value && setViewBox && forceResizeObserver) {
+ setViewBox();
+ forceResizeObserver();
+ }
+ } else if (!collision && currentRotation === rotation) {
+ setNestedProp(configRef.value, rotationPath, 0);
+ callback && callback({ collision })
+ }
+ }
+
+ function debounce(fn, delay) {
+ let timeout;
+ return (...args) => {
+ clearTimeout(timeout);
+ timeout = setTimeout(() => fn(...args), delay);
+ };
+ }
+
+ const debouncedDetect = debounce(detectTimeLabelCollision, 200);
+
+ if (height && width) {
+ watch([() => width.value, () => height.value] , async([newW, newH], [oldW, oldH]) => {
+ const autoRotate = getNestedProp(configRef.value, autoRotatePath);
+ if (!autoRotate) return;
+ const ratChanged = newW !== oldW || newH !== oldH;
+
+ if (ratChanged) {
+ debouncedDetect();
+ } else {
+ await detectTimeLabelCollision();
+ }
+ })
+ }
+
+ watch(
+ [
+ () => timeLabels.value,
+ () => getNestedProp(configRef.value, rotationPath),
+ () => slicer.value.start,
+ () => slicer.value.end
+ ],
+ async ([, , newStart, newEnd], [, , oldStart, oldEnd]) => {
+ const autoRotate = getNestedProp(configRef.value, autoRotatePath);
+ if (!autoRotate) return;
+
+ const slicerChanged = newStart !== oldStart || newEnd !== oldEnd;
+
+ if (slicerChanged) {
+ debouncedDetect();
+ } else {
+ await detectTimeLabelCollision();
+ }
+ },
+ { immediate: true }
+ );
+
+ return { detectTimeLabelCollision };
+}
diff --git a/src/useTimeLabels.js b/src/useTimeLabels.js
index 9a87875a..d712fb1a 100644
--- a/src/useTimeLabels.js
+++ b/src/useTimeLabels.js
@@ -18,7 +18,7 @@ export function useTimeLabels({
end: sliceEnd
}) {
const out = [];
- if (!xl.enable) {
+ if (!xl.enable || values.length === 0) {
for (let i = sliceStart; i < sliceEnd; i++) {
out.push({ text: String(values[i] ?? i), absoluteIndex: i });
}
diff --git a/tests/lib.test.js b/tests/lib.test.js
index ff1e4285..e46b2a0a 100644
--- a/tests/lib.test.js
+++ b/tests/lib.test.js
@@ -13,6 +13,8 @@ import {
addVector,
applyDataLabel,
assignStackRatios,
+ autoFontSize,
+ buildInterLineAreas,
calcLinearProgression,
calcMedian,
calcPercentageTrend,
@@ -22,6 +24,7 @@ import {
calculateNiceScale,
calculateNiceScaleWithExactExtremes,
checkArray,
+ checkFormatter,
checkNaN,
checkObj,
closestDecimal,
@@ -29,29 +32,38 @@ import {
convertCustomPalette,
convertNameColorToHex,
createArc,
+ createAreaWithCuts,
createHalfCircleArc,
+ createIndividualArea,
+ createIndividualAreaWithCuts,
createPolarAreas,
createPolygonPath,
+ createSmoothAreaSegments,
createSmoothPath,
+ createSmoothPathWithCuts,
createSpiralPath,
createStar,
+ createStraightPathWithCuts,
createTSpans,
createTSpansFromLineBreaksOnX,
createTSpansFromLineBreaksOnY,
createWordCloudDatasetFromPlainText,
- checkFormatter,
darkenHexColor,
dataLabel,
degreesToRadians,
error,
forceValidValue,
+ formatSmallValue,
functionReturnsString,
generateSpiralCoordinates,
+ getAreaSegments,
getCloserPoint,
getCumulativeAverage,
getCumulativeMedian,
getMissingDatasetAttributes,
+ getPathLengthFromCoordinates,
getScaleFactorUsingArcSize,
+ getValidSegments,
hasDeepProperty,
hslToRgba,
interpolateColorHex,
@@ -66,24 +78,18 @@ import {
matrixTimes,
niceNum,
objectIsEmpty,
+ observeClassPresenceIn,
placeHTMLElementAtSVGCoordinates,
rotateMatrix,
sanitizeArray,
setOpacity,
+ setOpacityIfWithinBBox,
shiftHue,
sumByAttribute,
+ sumSeries,
translateSize,
treeShake,
- getPathLengthFromCoordinates,
- sumSeries,
- getAreaSegments,
- createAreaWithCuts,
- createIndividualArea,
- createIndividualAreaWithCuts,
- getValidSegments,
- createStraightPathWithCuts,
- createSmoothPathWithCuts,
- createSmoothAreaSegments
+ wrapText
} from "../src/lib";
describe("calcTrend", () => {
@@ -259,9 +265,12 @@ describe("treeShake", () => {
key3: {
subkey: {
subsubkey: "subsubkey",
- withNull: null
+ withNull: null,
+ bool1: true,
+ bool0: false
},
},
+ key4: {}
};
const userConfig0 = {};
@@ -278,9 +287,14 @@ describe("treeShake", () => {
key3: {
subkey: {
subsubkey: "test",
- withNull: null
+ withNull: null,
+ bool1: true,
+ bool0: false
},
},
+ key4: {
+ A: '1',
+ }
};
expect(treeShake({ defaultConfig, userConfig: userConfig0 })).toStrictEqual(
@@ -292,9 +306,12 @@ describe("treeShake", () => {
key3: {
subkey: {
subsubkey: "subsubkey",
- withNull: null
+ withNull: null,
+ bool1: true,
+ bool0: false
},
},
+ key4: {}
}
);
@@ -307,9 +324,12 @@ describe("treeShake", () => {
key3: {
subkey: {
subsubkey: "subsubkey",
- withNull: null
+ withNull: null,
+ bool1: true,
+ bool0: false
},
},
+ key4: {}
}
);
@@ -322,9 +342,12 @@ describe("treeShake", () => {
key3: {
subkey: {
subsubkey: "subsubkey",
- withNull: null
+ withNull: null,
+ bool1: true,
+ bool0: false
},
},
+ key4: {}
}
);
@@ -337,9 +360,14 @@ describe("treeShake", () => {
key3: {
subkey: {
subsubkey: "test",
- withNull: null
+ withNull: null,
+ bool1: true,
+ bool0: false
},
},
+ key4: {
+ A: '1',
+ }
}
);
});
@@ -1202,64 +1230,24 @@ describe("error", () => {
});
});
-describe("generateSpiralCoordinates and createSpiralPath", () => {
+describe("createSpiralPath", () => {
const config = {
- points: 10,
+ maxPoints: 10,
a: 6,
b: 6,
angleStep: 0.07,
startX: 100,
startY: 100,
+ boxWidth: 300,
+ boxHeight: 300,
+ padding: 12
};
- test("creates spiral coordinates", () => {
- expect(generateSpiralCoordinates(config)).toStrictEqual([
- {
- x: 106,
- y: 100,
- },
- {
- x: 106.40427742162605,
- y: 100.44903307990695,
- },
- {
- x: 106.77307741409444,
- y: 100.95447490416657,
- },
- {
- x: 107.10050444089731,
- y: 101.51341887288268,
- },
- {
- x: 107.38090576622672,
- y: 102.1224113809724,
- },
- {
- x: 107.60891897406377,
- y: 102.77747224038916,
- },
- {
- x: 107.77951777146086,
- y: 103.47411906006754,
- },
- {
- x: 107.88805575597449,
- y: 104.20739544025015,
- },
- {
- x: 107.93030783908557,
- y: 104.97190281253947,
- },
- {
- x: 107.90250903129285,
- y: 105.7618357326754,
- },
- ]);
- });
-
test("creates a spiral path", () => {
- expect(createSpiralPath(config)).toStrictEqual(
- "M106 100 C106.20213871081302 100.22451653995347, 106.58867741786025 100.70175399203677, 106.93679092749588 101.23394688852463 C106.93679092749588 101.23394688852463, 107.24070510356202 101.81791512692755, 107.49491237014524 102.44994181068077 C107.49491237014524 102.44994181068077, 107.69421837276232 103.12579565022835, 107.83378676371768 103.84075725015884 C107.83378676371768 103.84075725015884, 107.90918179753004 104.58964912639482, 107.91640843518921 105.36686927260743"
+ const path = createSpiralPath(config)(10);
+
+ expect(path).toStrictEqual(
+ "M53.76777573106551 -38 C63.45050303757455 -27.245343341576394, 81.96624838241718 -4.38500110238048, 98.64137296373207 21.10778387891686 C98.64137296373207 21.10778387891686, 113.19928757392643 49.080680239911175, 125.376171614387 79.35564343031365 C125.376171614387 79.35564343031365, 134.9232082316163 111.7299887552394, 141.60872959857008 145.9776505658255 C141.60872959857008 145.9776505658255, 145.22025729523827 181.8506201246352, 145.56642335870694 219.08055348392566"
);
});
});
@@ -3548,7 +3536,7 @@ describe('createTSpansFromLineBreaksOnX', () => {
x: 5,
y: 10,
});
- const lineHeight = 12 * 1.3;
+ const lineHeight = 12;
const expected = [
`
Line1 `,
`
Line2 `
@@ -3576,7 +3564,7 @@ describe('createTSpansFromLineBreaksOnX', () => {
x: 2,
y: 3,
});
- const lineHeight = 15 * 1.3;
+ const lineHeight = 15;
const expected = [
`
A `,
`
B `,
@@ -3593,7 +3581,7 @@ describe('createTSpansFromLineBreaksOnX', () => {
x: 0,
y: 0,
});
- const lineHeight = 5 * 1.3;
+ const lineHeight = 5;
const expected = [
`
X `,
`
`,
@@ -3623,7 +3611,7 @@ describe('createTSpansFromLineBreaksOnY', () => {
fill: '#00f',
x: 0,
});
- const dy = fontSize * 1.3;
+ const dy = fontSize;
const expected = [
`
Line1 `,
`
Line2 `,
@@ -3650,7 +3638,7 @@ describe('createTSpansFromLineBreaksOnY', () => {
fill: 'green',
x: 1,
});
- const dy = fontSize * 1.3;
+ const dy = fontSize;
const expected = [
`
A `,
`
B `,
@@ -3667,7 +3655,7 @@ describe('createTSpansFromLineBreaksOnY', () => {
fill: 'blue',
x: 3,
});
- const dy = fontSize * 1.3;
+ const dy = fontSize;
const expected = [
`
X `,
`
`,
@@ -3675,4 +3663,539 @@ describe('createTSpansFromLineBreaksOnY', () => {
].join('');
expect(result).toBe(expected);
});
-})
\ No newline at end of file
+})
+
+describe('observeClassPresenceIn', () => {
+ // Stub MutationObserver so we control when it fires
+ class FakeMutationObserver {
+ constructor(cb) {
+ this.cb = cb
+ this.options = null
+ this.target = null
+ }
+ observe(target, options) {
+ this.target = target
+ this.options = options
+ }
+ disconnect() {
+ this.disconnected = true
+ }
+ // Simulate DOM mutations
+ simulate(mutations) {
+ this.cb(mutations)
+ }
+ }
+
+ let OldMO;
+ beforeEach(() => {
+ OldMO = global.MutationObserver;
+ global.MutationObserver = FakeMutationObserver;
+ })
+ afterEach(() => {
+ global.MutationObserver = OldMO;
+ })
+
+ // --- Fake container that can toggle “does it have .foo?” ---
+ function makeContainer(initialCount = 0) {
+ let count = initialCount;
+ return {
+ querySelectorAll(selector) {
+ // always just return an array of length `count`
+ return new Array(count).fill({ selector });
+ },
+ __setCount(n) { count = n },
+ }
+ }
+
+ test('logs error if cssClass is invalid', () => {
+ const spy = vi.spyOn(console, 'error').mockImplementation(() => { });
+ const c = makeContainer();
+ observeClassPresenceIn(c, '', () => { });
+ expect(spy).toHaveBeenCalledWith(
+ 'Vue Data UI - observeClassPresenceIn: cssClass must be a non-empty string'
+ );
+ observeClassPresenceIn(c, ' ', () => { });
+ expect(spy).toHaveBeenCalledWith(
+ 'Vue Data UI - observeClassPresenceIn: cssClass must be a non-empty string'
+ );
+ spy.mockRestore();
+ });
+
+ test('logs error if onNodesPresent is not a function', () => {
+ const spy = vi.spyOn(console, 'error').mockImplementation(() => { });
+ const c = makeContainer();
+ observeClassPresenceIn(c, 'foo', null);
+ expect(spy).toHaveBeenCalledWith(
+ 'Vue Data UI - observeClassPresenceIn: onNodesPresent must be a function'
+ );
+ observeClassPresenceIn(c, 'foo', {});
+ expect(spy).toHaveBeenCalledWith(
+ 'Vue Data UI - observeClassPresenceIn: onNodesPresent must be a function'
+ );
+ spy.mockRestore();
+ });
+
+ test('calls callback immediately if initial count > 0', () => {
+ const container = makeContainer(2);
+ const spy = vi.fn();
+ observeClassPresenceIn(container, 'foo', spy);
+ expect(spy).toHaveBeenCalledTimes(1);
+ });
+
+ test('does not call callback initially if count = 0', () => {
+ const container = makeContainer(0);
+ const spy = vi.fn();
+ observeClassPresenceIn(container, 'foo', spy);
+ expect(spy).not.toHaveBeenCalled();
+ });
+
+ test('invokes callback only when elements first appear', () => {
+ const container = makeContainer(0);
+ const spy = vi.fn();
+ const observer = observeClassPresenceIn(container, 'foo', spy);
+
+ // Simulate adding the first node
+ container.__setCount(1);
+ observer.simulate([{ addedNodes: [{}], removedNodes: [] }]);
+ expect(spy).toHaveBeenCalledTimes(1);
+
+ // Simulate adding more (still > 0): no extra call
+ container.__setCount(2);
+ observer.simulate([{ addedNodes: [{}], removedNodes: [] }]);
+ expect(spy).toHaveBeenCalledTimes(1);
+ });
+
+ test('calls again after removal then re-add', () => {
+ const container = makeContainer(0);
+ const spy = vi.fn();
+ const observer = observeClassPresenceIn(container, 'foo', spy);
+
+ // Add
+ container.__setCount(1);
+ observer.simulate([{ addedNodes: [{}], removedNodes: [] }]);
+ expect(spy).toHaveBeenCalledTimes(1);
+
+ // Remove
+ container.__setCount(0);
+ observer.simulate([{ addedNodes: [], removedNodes: [{}] }]);
+ // No call on remove
+
+ // Re-add
+ container.__setCount(1);
+ observer.simulate([{ addedNodes: [{}], removedNodes: [] }]);
+ expect(spy).toHaveBeenCalledTimes(2);
+ });
+
+ test('observer.disconnect() exists and works', () => {
+ const container = makeContainer(0);
+ const spy = vi.fn();
+ const observer = observeClassPresenceIn(container, 'foo', spy);
+ expect(typeof observer.disconnect).toBe('function');
+ expect(() => observer.disconnect()).not.toThrow();
+ });
+});
+
+describe('autoFontSize', () => {
+ function makeMocks({
+ bounds = { x: 0, y: 0, width: 100, height: 100 },
+ elementBBoxes = {},
+ ctm = { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 },
+ }) {
+ const el = {
+ style: { fontSize: '' },
+ setAttribute(name, value) {
+ if (name === 'font-size') {
+ this.style.fontSize = String(value)
+ } else {
+ // no-op for other attributes in this test context
+ }
+ },
+ getBBox() {
+ const size = parseInt(this.style.fontSize, 10) || 0
+ const box = elementBBoxes[size]
+ if (!box) throw new Error(`no bbox for fontSize ${size}`)
+ return box
+ },
+ getCTM() {
+ return ctm
+ },
+ };
+ return { el, bounds };
+ }
+
+ test('returns 0 if el is missing or currentFontSize is zero', () => {
+ const goodBounds = { x: 0, y: 0, width: 10, height: 10 };
+ expect(
+ autoFontSize({ el: null, bounds: goodBounds, currentFontSize: 10 })
+ ).toBe(0);
+ expect(
+ autoFontSize({ el: {}, bounds: goodBounds, currentFontSize: 0 })
+ ).toBe(0);
+ });
+
+ test('fits at full size → no shrink', () => {
+ const { el, bounds } = makeMocks({
+ bounds: { x: 0, y: 0, width: 200, height: 50 },
+ elementBBoxes: {
+ 14: { x: 10, y: 5, width: 140, height: 25 },
+ },
+ });
+
+ const result = autoFontSize({
+ el: el,
+ bounds,
+ currentFontSize: 14,
+ minFontSize: 6,
+ attempts: 10,
+ padding: 0,
+ });
+
+ expect(result).toBe(14);
+ expect(parseInt((el).style.fontSize, 10)).toBe(14);
+ });
+
+ test('shrinks down until fits', () => {
+ const { el, bounds } = makeMocks({
+ bounds: { x: 0, y: 0, width: 100, height: 100 },
+ elementBBoxes: {
+ 14: { x: 0, y: 0, width: 120, height: 10 },
+ 13: { x: 0, y: 0, width: 110, height: 10 },
+ 12: { x: 5, y: 5, width: 90, height: 10 },
+ },
+ });
+
+ const result = autoFontSize({
+ el: el,
+ bounds,
+ currentFontSize: 14,
+ minFontSize: 8,
+ attempts: 10,
+ padding: 0,
+ });
+
+ expect(result).toBe(12);
+ expect(parseInt((el).style.fontSize, 10)).toBe(12);
+ });
+
+ test('stops at minFontSize if still overflowing', () => {
+ const elementBBoxes = {};
+ for (let s = 10; s >= 6; s -= 1) {
+ elementBBoxes[s] = { x: 0, y: 0, width: s * 20, height: 10 }
+ }
+ const { el, bounds } = makeMocks({
+ bounds: { x: 0, y: 0, width: 100, height: 100 },
+ elementBBoxes,
+ });
+
+ const result = autoFontSize({
+ el: el,
+ bounds,
+ currentFontSize: 10,
+ minFontSize: 6,
+ attempts: 10,
+ padding: 0,
+ });
+
+ expect(result).toBe(6)
+ expect(parseInt((el).style.fontSize, 10)).toBe(6)
+ });
+});
+
+describe('setOpacityIfWithinBBox', () => {
+ function mockBBox({ x, y, width, height }) {
+ return { x, y, width, height };
+ }
+
+ function createMockElement(bbox) {
+ return {
+ getBBox: () => bbox,
+ style: { opacity: '' },
+ };
+ }
+
+ test('does nothing if el is missing', () => {
+ const container = createMockElement(mockBBox({ x: 0, y: 0, width: 100, height: 100 }));
+ expect(() => setOpacityIfWithinBBox({ el: null, container })).not.toThrow();
+ });
+
+ test('does nothing if container is missing', () => {
+ const el = createMockElement(mockBBox({ x: 10, y: 10, width: 10, height: 10 }));
+ expect(() => setOpacityIfWithinBBox({ el, container: null })).not.toThrow();
+ });
+
+ test('sets opacity to 1 if el is fully inside container (no padding)', () => {
+ const el = createMockElement(mockBBox({ x: 10, y: 10, width: 10, height: 10 }));
+ const container = createMockElement(mockBBox({ x: 0, y: 0, width: 100, height: 100 }));
+ setOpacityIfWithinBBox({ el, container, padding: 0 });
+ expect(el.style.opacity).toBe('1');
+ });
+
+ test('sets opacity to 0 if el overflows left edge', () => {
+ const el = createMockElement(mockBBox({ x: 0, y: 10, width: 10, height: 10 }));
+ const container = createMockElement(mockBBox({ x: 5, y: 0, width: 100, height: 100 }));
+ setOpacityIfWithinBBox({ el, container });
+ expect(el.style.opacity).toBe('0');
+ });
+
+ test('sets opacity to 0 if el overflows top edge', () => {
+ const el = createMockElement(mockBBox({ x: 10, y: 0, width: 10, height: 10 }));
+ const container = createMockElement(mockBBox({ x: 0, y: 5, width: 100, height: 100 }));
+
+ setOpacityIfWithinBBox({ el, container });
+ expect(el.style.opacity).toBe('0');
+ });
+
+ test('sets opacity to 0 if el overflows right edge', () => {
+ const el = createMockElement(mockBBox({ x: 95, y: 10, width: 10, height: 10 }));
+ const container = createMockElement(mockBBox({ x: 0, y: 0, width: 100, height: 100 }));
+ setOpacityIfWithinBBox({ el, container });
+ expect(el.style.opacity).toBe('0');
+ });
+
+ test('sets opacity to 0 if el overflows bottom edge', () => {
+ const el = createMockElement(mockBBox({ x: 10, y: 95, width: 10, height: 10 }));
+ const container = createMockElement(mockBBox({ x: 0, y: 0, width: 100, height: 100 }));
+ setOpacityIfWithinBBox({ el, container });
+ expect(el.style.opacity).toBe('0');
+ });
+
+ test('respects custom padding', () => {
+ const el = createMockElement(mockBBox({ x: 5, y: 5, width: 10, height: 10 }));
+ const container = createMockElement(mockBBox({ x: 0, y: 0, width: 20, height: 20 }));
+ setOpacityIfWithinBBox({ el, container, padding: 6 });
+ expect(el.style.opacity).toBe('0');
+ });
+
+ test('defaults to padding = 1', () => {
+ const el = createMockElement(mockBBox({ x: 1, y: 1, width: 18, height: 18 }));
+ const container = createMockElement(mockBBox({ x: 0, y: 0, width: 20, height: 20 }));
+ setOpacityIfWithinBBox({ el, container });
+ expect(el.style.opacity).toBe('1');
+ });
+});
+
+describe('formatSmallValue()', () => {
+ test('returns "0" for zero values', () => {
+ expect(formatSmallValue({ value: 0 })).toBe('0')
+ expect(formatSmallValue({ value: 0, maxDecimals: 2 })).toBe('0')
+ const fb0 = vi.fn((v) => `X${v}`)
+ expect(formatSmallValue({ value: 0, fallbackFormatter: fb0 })).toBe('0')
+ expect(fb0).not.toHaveBeenCalled()
+ })
+
+ test('values ≥ 1 without fallbackFormatter are stringified', () => {
+ expect(formatSmallValue({ value: 1 })).toBe('1')
+ expect(formatSmallValue({ value: 1.61803 })).toBe('1.618')
+ expect(formatSmallValue({ value: 1.6181 })).toBe('1.6181')
+ expect(formatSmallValue({ value: 1.6181, maxDecimals: 2 })).toBe('1.62')
+ expect(formatSmallValue({ value: -99 })).toBe('-99')
+ })
+
+ test('values ≥ 1 use fallbackFormatter when provided', () => {
+ const fb1 = vi.fn((v) => `P${v.toFixed(1)}S`)
+ expect(formatSmallValue({ value: 1.618, fallbackFormatter: fb1 })).toBe('P1.6S')
+ expect(fb1).toHaveBeenCalledTimes(1)
+ expect(fb1).toHaveBeenCalledWith(1.618)
+ })
+
+ test('small positive values < 1 are formatted to the right number of decimals', () => {
+ expect(formatSmallValue({ value: 0.5 })).toBe('0.5')
+ expect(formatSmallValue({ value: 0.0123 })).toBe('0.012')
+ expect(formatSmallValue({ value: 0.0000123 })).toBe('0')
+ })
+
+ test('small negative values < 1 keep their sign and correct decimals', () => {
+ expect(formatSmallValue({ value: -0.05 })).toBe('-0.05')
+ })
+
+ test('respects custom maxDecimals for very small values', () => {
+ expect(formatSmallValue({ value: 0.00000123 })).toBe('0')
+ expect(formatSmallValue({ value: 0.00000123, maxDecimals: 2 })).toBe('0')
+ expect(formatSmallValue({ value: 0.00000123, maxDecimals: 6 })).toBe('0.000001')
+ })
+
+ test('fallbackFormatter is ignored for small values < 1', () => {
+ const fb2 = vi.fn((v) => 'SHOULD_NOT_BE_USED')
+ expect(formatSmallValue({ value: 0.1234, fallbackFormatter: fb2 })).toBe('0.12')
+ expect(fb2).not.toHaveBeenCalled()
+ })
+
+ test('exact boundary values (1 and -1) behave like ≥1 cases', () => {
+ expect(formatSmallValue({ value: 1 })).toBe('1')
+ expect(formatSmallValue({ value: -1 })).toBe('-1')
+ const fb3 = vi.fn((v) => `P${v}S`)
+ expect(formatSmallValue({ value: 1, fallbackFormatter: fb3 })).toBe('P1S')
+ expect(fb3).toHaveBeenCalledTimes(1)
+ })
+
+ test('preserves trailing zeros when removeTrailingZero is false for small values', () => {
+ expect(formatSmallValue({ value: 0.5, removeTrailingZero: false })).toBe('0.50')
+ expect(formatSmallValue({ value: 0.0000123, removeTrailingZero: false })).toBe('0.0000')
+ })
+})
+
+describe('wrapText', () => {
+ test('does not error on empy string', () => {
+ expect(wrapText('')).toBe('');
+ });
+
+ test('preserves the original string if under maxChar', () => {
+ expect(wrapText('Some normal text')).toBe('Some normal text');
+ });
+
+ test('adds a line break when the original string exceeds maxChar', () => {
+ expect(wrapText('Some text that is too long')).toBe('Some text that is\ntoo long');
+ expect(wrapText('Some normal text', 10)).toBe('Some\nnormal\ntext');
+ });
+
+ test('only wraps full words', () => {
+ expect(wrapText('ABCDEFGHIJKLMNOPQRSTUVWXYZ')).toBe('ABCDEFGHIJKLMNOPQRSTUVWXYZ');
+ });
+});
+
+const GREEN = '#00FF00';
+const RED = '#FF0000';
+
+function allClosed(areas) {
+ return areas.every(a => a.d.startsWith('M') && a.d.trim().endsWith('Z'));
+}
+
+describe('buildInterLineAreas', () => {
+ test('returns [] for invalid inputs', () => {
+ expect(buildInterLineAreas({})).toEqual([]);
+ expect(buildInterLineAreas({
+ lineA: [],
+ lineB: [],
+ colorLineA: GREEN,
+ colorLineB: RED
+ })).toEqual([]);
+ });
+
+ test('straight lines, no crossing: A always above ⇒ uses colorLineA', () => {
+ const A = [{ x: 0, y: 0, value: 1 }, { x: 10, y: 0, value: 1 }];
+ const B = [{ x: 0, y: 10, value: 1 }, { x: 10, y: 10, value: 1 }];
+ const areas = buildInterLineAreas({
+ lineA: A, lineB: B,
+ colorLineA: GREEN, colorLineB: RED,
+ smoothA: false, smoothB: false,
+ sampleStepPx: 2, merge: true
+ });
+ expect(areas.length).toBeGreaterThan(0);
+ expect(areas.every(a => a.color === GREEN)).toBe(true);
+ expect(allClosed(areas)).toBe(true);
+ });
+
+ test('straight lines, one crossing: color flips from B to A across the crossing', () => {
+ const A = [{ x: 0, y: 12, value: 1 }, { x: 10, y: 0, value: 1 }];
+ const B = [{ x: 0, y: 0, value: 1 }, { x: 10, y: 12, value: 1 }];
+ const areas = buildInterLineAreas({
+ lineA: A, lineB: B,
+ colorLineA: GREEN, colorLineB: RED,
+ smoothA: false, smoothB: false,
+ sampleStepPx: 2, merge: true
+ });
+ expect(areas.length).toBe(2);
+ expect(areas[0].color).toBe(RED);
+ expect(areas[1].color).toBe(GREEN);
+ expect(allClosed(areas)).toBe(true);
+ });
+
+ test('smooth lines behave like straight lines for color / region logic', () => {
+ const A = [{ x: 0, y: 12, value: 1 }, { x: 10, y: 0, value: 1 }];
+ const B = [{ x: 0, y: 0, value: 1 }, { x: 10, y: 12, value: 1 }];
+ const smoothAreas = buildInterLineAreas({
+ lineA: A, lineB: B,
+ colorLineA: GREEN, colorLineB: RED,
+ smoothA: true, smoothB: true,
+ sampleStepPx: 2, merge: true
+ });
+ expect(smoothAreas.length).toBe(2);
+ expect(smoothAreas[0].color).toBe(RED);
+ expect(smoothAreas[1].color).toBe(GREEN);
+ expect(allClosed(smoothAreas)).toBe(true);
+ });
+
+ test('mixed smoothness (A smooth, B straight) still yields correct colors', () => {
+ const A = [{ x: 0, y: 12, value: 1 }, { x: 10, y: 0, value: 1 }];
+ const B = [{ x: 0, y: 0, value: 1 }, { x: 10, y: 12, value: 1 }];
+ const areas = buildInterLineAreas({
+ lineA: A, lineB: B,
+ colorLineA: GREEN, colorLineB: RED,
+ smoothA: true, smoothB: false,
+ sampleStepPx: 2, merge: true
+ });
+ expect(areas.length).toBe(2);
+ expect(areas[0].color).toBe(RED);
+ expect(areas[1].color).toBe(GREEN);
+ expect(allClosed(areas)).toBe(true);
+ });
+
+ test('cutNullValues=true drops trailing single point segments', () => {
+ const A = [
+ { x: 0, y: 0, value: 1 },
+ { x: 4, y: 0, value: 1 },
+ { x: 5, y: 0, value: null },
+ { x: 6, y: 0, value: 1 },
+ ];
+ const B = [
+ { x: 0, y: 10, value: 1 },
+ { x: 4, y: 10, value: 1 },
+ { x: 5, y: 10, value: null },
+ { x: 6, y: 10, value: 1 },
+ ];
+ const withCuts = buildInterLineAreas({
+ lineA: A, lineB: B,
+ colorLineA: GREEN, colorLineB: RED,
+ cutNullValues: true,
+ merge: false,
+ sampleStepPx: 2
+ });
+ const noCuts = buildInterLineAreas({
+ lineA: A, lineB: B,
+ colorLineA: GREEN, colorLineB: RED,
+ cutNullValues: false,
+ merge: false,
+ sampleStepPx: 2
+ });
+ expect(withCuts.length).toBeGreaterThanOrEqual(1);
+ expect(noCuts.length).toBeGreaterThan(withCuts.length);
+ const allClosed = (arr) => arr.every(a => a.d.startsWith('M') && a.d.trim().endsWith('Z'));
+ expect(allClosed(withCuts)).toBe(true);
+ expect(allClosed(noCuts)).toBe(true);
+ });
+
+ test('merge=false yields more/smaller polygons than merge=true', () => {
+ const A = [{ x: 0, y: 2, value: 1 }, { x: 20, y: 2, value: 1 }];
+ const B = [{ x: 0, y: 10, value: 1 }, { x: 20, y: 10, value: 1 }];
+ const perInterval = buildInterLineAreas({
+ lineA: A, lineB: B,
+ colorLineA: GREEN, colorLineB: RED,
+ smoothA: false, smoothB: false,
+ sampleStepPx: 2, merge: false
+ });
+ const merged = buildInterLineAreas({
+ lineA: A, lineB: B,
+ colorLineA: GREEN, colorLineB: RED,
+ smoothA: false, smoothB: false,
+ sampleStepPx: 2, merge: true
+ });
+ expect(perInterval.length).toBeGreaterThan(merged.length);
+ expect(merged.length).toBeGreaterThan(0);
+ expect(perInterval.every(a => a.color === GREEN)).toBe(true);
+ expect(merged.every(a => a.color === GREEN)).toBe(true);
+ expect(allClosed(perInterval)).toBe(true);
+ expect(allClosed(merged)).toBe(true);
+ });
+
+ test('paths are valid SVG polygons for a simple case', () => {
+ const A = [{ x: 0, y: 0, value: 1 }, { x: 10, y: 0, value: 1 }];
+ const B = [{ x: 0, y: 5, value: 1 }, { x: 10, y: 5, value: 1 }];
+ const areas = buildInterLineAreas({
+ lineA: A, lineB: B,
+ colorLineA: GREEN, colorLineB: RED,
+ sampleStepPx: 2, merge: true
+ });
+ expect(areas.length).toBeGreaterThan(0);
+ expect(allClosed(areas)).toBe(true);
+ });
+});
\ No newline at end of file
diff --git a/tests/useTimeLabels.test.js b/tests/useTimeLabels.test.js
new file mode 100644
index 00000000..8d8d5911
--- /dev/null
+++ b/tests/useTimeLabels.test.js
@@ -0,0 +1,165 @@
+import { describe, expect, test, vi } from 'vitest'
+import { useTimeLabels } from '../src/useTimeLabels'
+
+// Stub out useDateTime so formatDate just returns the format string
+vi.mock('../src/useDateTime', () => ({
+ useDateTime: () => ({
+ formatDate: (_d, fmt) => fmt,
+ }),
+}))
+
+const baseFormatter = {
+ enable: true,
+ useUTC: false,
+ locale: 'en',
+ januaryAsYear: false,
+ options: {
+ year: 'yyyy',
+ month: 'MMM',
+ day: 'dd/MM',
+ hour: 'HH:mm',
+ minute: 'mm',
+ second: 'ss',
+ },
+}
+
+const makeDate = iso => new Date(iso).getTime()
+
+describe('useTimeLabels composable', () => {
+ test('returns raw values when formatting disabled', () => {
+ const result = useTimeLabels({
+ values: [10, 20, 30, 40],
+ maxDatapoints: 4,
+ formatter: { ...baseFormatter, enable: false },
+ start: 1,
+ end: 3,
+ })
+ expect(result).toEqual([
+ { text: '20', absoluteIndex: 1 },
+ { text: '30', absoluteIndex: 2 },
+ ])
+ })
+
+ test('second interval uses second format', () => {
+ const vals = [
+ makeDate('2025-08-01T00:00:00Z'),
+ makeDate('2025-08-01T00:00:00Z') + 500
+ ]
+ const res = useTimeLabels({
+ values: vals,
+ maxDatapoints: vals.length,
+ formatter: baseFormatter,
+ start: 0,
+ end: 2,
+ })
+ expect(res.every(r => r.text === 'ss')).toBe(true)
+ })
+
+ test('minute interval falls back to second format (per current logic)', () => {
+ const vals = [
+ makeDate('2025-08-01T00:00:00Z'),
+ makeDate('2025-08-01T00:01:00Z')
+ ]
+ const res = useTimeLabels({
+ values: vals,
+ maxDatapoints: vals.length,
+ formatter: baseFormatter,
+ start: 0,
+ end: 2,
+ })
+ expect(res.every(r => r.text === 'ss')).toBe(true)
+ })
+
+ test('hour interval falls back to minute format (per current logic)', () => {
+ const vals = [
+ makeDate('2025-08-01T00:00:00Z'),
+ makeDate('2025-08-01T02:00:00Z')
+ ]
+ const res = useTimeLabels({
+ values: vals,
+ maxDatapoints: vals.length,
+ formatter: baseFormatter,
+ start: 0,
+ end: 2,
+ })
+ expect(res.every(r => r.text === 'mm')).toBe(true)
+ })
+
+ test('day interval falls back to hour format (per current logic)', () => {
+ const vals = [
+ makeDate('2025-08-01T00:00:00Z'),
+ makeDate('2025-08-03T00:00:00Z')
+ ]
+ const res = useTimeLabels({
+ values: vals,
+ maxDatapoints: vals.length,
+ formatter: baseFormatter,
+ start: 0,
+ end: 2,
+ })
+ expect(res.every(r => r.text === 'HH:mm')).toBe(true)
+ })
+
+ test('month interval uses month format', () => {
+ const vals = [
+ makeDate('2025-06-15T12:00:00Z'),
+ makeDate('2025-08-15T12:00:00Z')
+ ]
+ const res = useTimeLabels({
+ values: vals,
+ maxDatapoints: vals.length,
+ formatter: baseFormatter,
+ start: 0,
+ end: 2,
+ })
+ expect(res.every(r => r.text === 'MMM')).toBe(true)
+ })
+
+ test('year interval picks year format', () => {
+ const vals = [
+ makeDate('2020-07-01T00:00:00Z'),
+ makeDate('2025-07-01T00:00:00Z')
+ ]
+ const res = useTimeLabels({
+ values: vals,
+ maxDatapoints: vals.length,
+ formatter: baseFormatter,
+ start: 0,
+ end: 2,
+ })
+ expect(res.every(r => r.text === 'yyyy')).toBe(true)
+ })
+
+ test('januaryAsYear renders year for January label', () => {
+ const vals = [
+ makeDate('2025-01-01T00:00:00Z'),
+ makeDate('2025-02-01T00:00:00Z')
+ ]
+ const fmt = { ...baseFormatter, januaryAsYear: true }
+ const res = useTimeLabels({
+ values: vals,
+ maxDatapoints: vals.length,
+ formatter: fmt,
+ start: 0,
+ end: 2,
+ })
+ expect(res[0].text).toBe('yyyy')
+ expect(res[1].text).toBe('MMM')
+ })
+
+ test('midnight labels currently show time format (no override)', () => {
+ const vals = [
+ makeDate('2025-08-01T00:00:00Z'),
+ makeDate('2025-08-01T05:30:00Z')
+ ]
+ const res = useTimeLabels({
+ values: vals,
+ maxDatapoints: vals.length,
+ formatter: baseFormatter,
+ start: 0,
+ end: 2,
+ })
+ expect(res[0].text).toBe('HH:mm')
+ expect(res[1].text).toBe('HH:mm')
+ })
+})
diff --git a/types/vue-data-ui.d.ts b/types/vue-data-ui.d.ts
index fef10588..aa7fc592 100644
--- a/types/vue-data-ui.d.ts
+++ b/types/vue-data-ui.d.ts
@@ -39,12 +39,14 @@ declare module "vue-data-ui" {
| VueUiThermometerDataset
| VueUiTiremarksDataset
| VueUiVerticalBarDatasetItem[]
+ | VueUiHorizontalBarDatasetItem[]
| VueUiWaffleDatasetItem[]
| VueUiWheelDataset
| VueUiXyDatasetItem[]
| VueUiTreemapDatasetItem[]
| VueUiQuickChartDataset
| number[]
+ | Array
| VueUiStripPlotDataset[]
| VueUiWordCloudDatasetItem[]
| VueUiXyCanvasDatasetItem[]
@@ -95,6 +97,7 @@ declare module "vue-data-ui" {
| VueUiThermometerConfig
| VueUiTiremarksConfig
| VueUiVerticalBarConfig
+ | VueUiHorizontalBarConfig
| VueUiWaffleConfig
| VueUiWheelConfig
| VueUiXyConfig
@@ -122,6 +125,41 @@ declare module "vue-data-ui" {
| VueUiChordConfig;
}>;
+ export type ChartEvent = null | (({ datapoint, seriesIndex} : { datapoint: T, seriesIndex: number}) => void);
+
+ export type VueUiFlowEvent = ChartEvent;
+ export type VueUi3dBarEvent = ChartEvent;
+ export type VueUiDonutEvent = ChartEvent;
+ export type VueUiRadarEvent = ChartEvent;
+ export type VueUiXyEvent = ChartEvent;
+ export type VueUiRingsEvent = ChartEvent;
+ export type VueUiOnionEvent = ChartEvent;
+ export type VueUiWorldEvent = ChartEvent;
+ export type VueUiGalaxyEvent = ChartEvent;
+ export type VueUiWaffleEvent = ChartEvent;
+ export type VueUiScatterEvent = ChartEvent;
+ export type VueUiTreemapEvent = ChartEvent;
+ export type VueUiDumbbellEvent = ChartEvent;
+ export type VueUiMoleculeEvent = ChartEvent;
+ export type VueUiQuadrantEvent = ChartEvent;
+ export type VueUiSparkbarEvent = ChartEvent;
+ export type VueUiWordCloudEvent = ChartEvent;
+ export type VueUiStripPlotEvent = ChartEvent;
+ export type VueUiMoodRadarEvent = ChartEvent;
+ export type VueUiCirclePackEvent = ChartEvent;
+ export type VueUiSparklineEvent = ChartEvent;
+ export type VueUiAgePyramidEvent = ChartEvent;
+ export type VueUiStackbarEvent = ChartEvent;
+ export type VueUiCandlestickEvent = ChartEvent;
+ export type VueUiRidgelineEvent = ChartEvent;
+ export type VueUiSparkStackbarEvent = ChartEvent;
+ export type VueUiHistoryPlotEvent = ChartEvent;
+ export type VueUiRelationCircleEvent = ChartEvent;
+ export type VueUiDonutEvolutionEvent = ChartEvent;
+ export type VueUiSparkHistogramEvent = ChartEvent;
+ export type VueUiChordEvent = ChartEvent;
+ export type VueUiParallelCoordinatePlotEvent = ChartEvent;
+
export type VueUiPatternName =
| "bubbles"
| "flooring"
@@ -229,14 +267,10 @@ declare module "vue-data-ui" {
table?: null | (() => void);
tooltip?: null | (() => void);
};
- // old html2canvas options
print?: {
- allowTaint?: boolean;
- backgroundColor?: string;
- useCORS?: boolean;
- onclone?: null | ((doc: Document) => void);
scale?: number;
- logging?: boolean;
+ orientation?: 'auto' | 'l' | 'p';
+ overflowTolerance?: number;
};
};
@@ -257,6 +291,8 @@ declare module "vue-data-ui" {
backgroundOpacity?: number;
position?: TooltipPosition;
offsetY?: number;
+ smooth?: boolean;
+ backdropFilter?: boolean;
};
export type ZoomMinimap = {
@@ -329,6 +365,12 @@ declare module "vue-data-ui" {
}
}
+ export type MinimalCustomFormatParams = {
+ absoluteIndex: number;
+ seriesIndex: number;
+ datapoint: TDatapoint;
+ };
+
export type VueUiTooltipParams<
TDatapoint,
TSeries,
@@ -365,7 +407,14 @@ declare module "vue-data-ui" {
};
export type VueUiTreemapConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
responsive?: boolean;
+ events?: {
+ datapointEnter?: VueUiTreemapEvent; // v3
+ datapointLeave?: VueUiTreemapEvent; // v3
+ datapointClick?: VueUiTreemapEvent; // v3
+ };
theme?: Theme;
customPalette?: string[];
userOptions?: ChartUserOptions;
@@ -411,6 +460,7 @@ declare module "vue-data-ui" {
roundingPercentage?: number;
showValue?: boolean;
showPercentage?: boolean;
+ position?: 'bottom' | 'top';
};
title?: ChartTitle;
tooltip?: ChartTooltip & {
@@ -450,6 +500,7 @@ declare module "vue-data-ui" {
name: string;
normalizedValue: number;
parentName?: string;
+ parentId?: string;
proportion: number;
value: number;
x0: number;
@@ -487,6 +538,7 @@ declare module "vue-data-ui" {
>;
export type VueUiKpiConfig = {
+ debug?: boolean;
animationFrames?: number;
animationValueStart?: number;
backgroundColor?: string;
@@ -525,6 +577,14 @@ declare module "vue-data-ui" {
export type VueUiGalaxyDatasetItem = VueUiDonutDatasetItem;
export type VueUiGalaxyConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
+ responsive?: boolean; // v3
+ events?: { // v3
+ datapointEnter?: VueUiGalaxyEvent; // v3
+ datapointLeave?: VueUiGalaxyEvent; // v3
+ datapointClick?: VueUiGalaxyEvent; // v3
+ };
theme?: Theme;
customPalette?: string[];
useCssAnimation?: boolean;
@@ -564,6 +624,7 @@ declare module "vue-data-ui" {
roundingPercentage?: number;
showValue?: boolean;
showPercentage?: boolean;
+ position?: 'bottom' | 'top';
};
title?: ChartTitle;
tooltip?: ChartTooltip & {
@@ -608,6 +669,7 @@ declare module "vue-data-ui" {
proportion: number;
seriesIndex: number;
value: number;
+ absoluteIndex: number;
};
export type VueUiGalaxySeriesItem = VueUiGalaxyDatasetItem & {
@@ -649,6 +711,8 @@ declare module "vue-data-ui" {
};
export type VueUiSparkgaugeConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
theme?: Theme;
style?: {
fontFamily?: string;
@@ -830,6 +894,13 @@ declare module "vue-data-ui" {
};
export type VueUiMoleculeConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
+ events?: {
+ datapointEnter?: VueUiMoleculeEvent; // v3
+ datapointLeave?: VueUiMoleculeEvent; // v3
+ datapointClick?: VueUiMoleculeEvent; // v3
+ };
theme?: Theme;
customPalette?: string[];
style?: {
@@ -942,7 +1013,30 @@ declare module "vue-data-ui" {
}>;
};
+ export type VueUi3dBarDatapoint = {
+ breakdown: null | Array<{
+ name: string;
+ value: number;
+ }>;
+ color: string;
+ fill: Object; // Feeling too lazy to drill that one
+ id: string;
+ name: string;
+ proportion: number;
+ seriesIndex: number;
+ value: number;
+ }
+
export type VueUi3dBarConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
+ responsive?: boolean; // v3
+ events?: { // v3
+ datapointEnter?: VueUi3dBarEvent; // v3
+ datapointLeave?: VueUi3dBarEvent; // v3
+ datapointClick?: VueUi3dBarEvent; // v3
+ };
+ useCssAnimation?: boolean; // v3
theme?: Theme;
customPalette?: string[];
style?: {
@@ -1041,7 +1135,23 @@ declare module "vue-data-ui" {
"5": number;
};
+ export type VueUiMoodRadarDatapoint = {
+ index: number;
+ key: "1" | "2" | "3" | "4" | "5";
+ value: number;
+ proportion: number;
+ color: string;
+ }
+
export type VueUiMoodRadarConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
+ responsive?: boolean; // v3
+ events?: {
+ datapointEnter?: VueUiMoodRadarEvent; // v3
+ datapointLeave?: VueUiMoodRadarEvent; // v3
+ datapointClick?: VueUiMoodRadarEvent; // v3
+ };
theme?: Theme;
style?: {
fontFamily?: string;
@@ -1093,6 +1203,7 @@ declare module "vue-data-ui" {
backgroundColor?: string;
roundingPercentage?: number;
roundingValue?: number;
+ position?: 'bottom' | 'top';
};
};
};
@@ -1306,7 +1417,30 @@ declare module "vue-data-ui" {
isSpin?: boolean;
}>;
+ export type VueUiDonutEvolutionDatapoint = {
+ activeRadius: number;
+ donut: VueUiDonutDatapoint[];
+ donutFocus: VueUiDonutDatapoint[];
+ donutHover: VueUiDonutDatapoint[];
+ hoverRadius: number;
+ index: number;
+ percentages: number[0];
+ radius: number;
+ subtotal: number;
+ values: Array;
+ x: number;
+ y: number;
+ }
+
export type VueUiDonutEvolutionConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
+ responsive?: boolean; // v3
+ events?: { // v3
+ datapointEnter?: VueUiDonutEvolutionEvent; // v3
+ datapointLeave?: VueUiDonutEvolutionEvent; // v3
+ datapointClick?: VueUiDonutEvolutionEvent; // v3
+ };
theme?: Theme;
customPalette?: string[];
style?: {
@@ -1344,6 +1478,14 @@ declare module "vue-data-ui" {
stroke?: string;
strokeWidth?: number;
showVerticalLines?: boolean;
+ axis?: { // v3
+ yLabel?: string; // v3
+ yLabelOffsetX?: number; // v3
+ xLabel?: string; // v3
+ xLabelOffsetY?: number; // v3
+ fontSize?: number; // v3
+ color?: string; // v3
+ };
yAxis?: {
dataLabels?: {
show?: boolean;
@@ -1364,6 +1506,10 @@ declare module "vue-data-ui" {
showOnlyFirstAndLast?: boolean;
color?: string;
rotation?: number;
+ autoRotate?: { // v3
+ enable?: boolean; // v3
+ angle?: number; // v3
+ };
offsetY?: number;
};
};
@@ -1396,6 +1542,7 @@ declare module "vue-data-ui" {
roundingValue?: number;
showValue?: boolean;
showPercentage?: boolean;
+ position?: 'bottom' | 'top';
};
};
};
@@ -1417,7 +1564,7 @@ declare module "vue-data-ui" {
export type VueUiDonutEvolutionDatasetItem = {
name: string;
- values: number[];
+ values: Array;
color?: string;
};
@@ -1447,11 +1594,16 @@ declare module "vue-data-ui" {
>;
export type VueUiTiremarksConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
+ responsive?: boolean; // v3
theme?: Theme;
userOptions?: ChartUserOptions;
style?: {
fontFamily?: string;
chart?: {
+ width?: number; // v3
+ height?: number; // v3
backgroundColor?: string;
color?: string;
animation?: {
@@ -1511,6 +1663,8 @@ declare module "vue-data-ui" {
>;
export type VueUiWheelConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
responsive?: boolean;
theme?: Theme;
style?: {
@@ -1579,7 +1733,14 @@ declare module "vue-data-ui" {
>;
export type VueUiRingsConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
responsive?: boolean;
+ events?: {
+ datapointEnter?: VueUiRingsEvent; // v3
+ datapointLeave?: VueUiRingsEvent; // v3
+ datapointClick?: VueUiRingsEvent; // v3
+ };
theme?: Theme;
customPalette?: string[];
useCssAnimation?: boolean;
@@ -1614,6 +1775,7 @@ declare module "vue-data-ui" {
roundingPercentage?: number;
showValue?: boolean;
showPercentage?: boolean;
+ position?: 'bottom' | 'top';
};
title?: ChartTitle;
tooltip?: ChartTooltip & {
@@ -1658,6 +1820,7 @@ declare module "vue-data-ui" {
strokeWidth: number;
uid: string;
value: number;
+ absoluteIndex: number;
};
export type VueUiRingsDatasetItem = {
@@ -1693,6 +1856,14 @@ declare module "vue-data-ui" {
>;
export type VueUiSparkHistogramConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
+ responsive?: boolean; // v3
+ events?: {
+ datapointEnter?: VueUiSparkHistogramEvent; // v3
+ datapointLeave?: VueUiSparkHistogramEvent; // v3
+ datapointClick?: VueUiSparkHistogramEvent; // v3
+ };
theme?: Theme;
style?: {
backgroundColor?: string;
@@ -1729,6 +1900,7 @@ declare module "vue-data-ui" {
labels?: {
value?: {
fontSize?: number;
+ minFontSize?: number; // v3
color?: string;
bold?: boolean;
rounding?: number;
@@ -1739,12 +1911,14 @@ declare module "vue-data-ui" {
};
valueLabel?: {
fontSize?: number;
+ minFontSize?: number; // v3
color?: string;
bold?: boolean;
rounding?: number;
};
timeLabel?: {
fontSize?: number;
+ minFontSize?: number; // v3
color?: string;
bold?: boolean;
};
@@ -1783,7 +1957,7 @@ declare module "vue-data-ui" {
timeLabel?: string;
trapX?: number;
unitWidth?: number;
- value?: number;
+ value?: number | null;
valueLabel?: string;
width?: number;
x?: number;
@@ -1795,7 +1969,26 @@ declare module "vue-data-ui" {
dataset: VueUiSparkHistogramDatasetItem[];
}>;
+ export type VueUiSparkStackbarDatapoint = {
+ color: string;
+ id: string;
+ name: string;
+ proportion: number;
+ proportionLabel: string;
+ seriesIndex: number;
+ start: number;
+ value: number;
+ width: number;
+ }
+
export type VueUiSparkStackbarConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
+ events?: {
+ datapointEnter?: VueUiSparkStackbarEvent; // v3
+ datapointLeave?: VueUiSparkStackbarEvent; // v3
+ datapointClick?: VueUiSparkStackbarEvent; // v3
+ }
theme?: Theme;
customPalette?: string[];
style?: {
@@ -1873,7 +2066,7 @@ declare module "vue-data-ui" {
proportion?: number;
proportionLabel?: string;
start?: number;
- value?: number;
+ value?: number | null;
width?: number;
};
@@ -1883,6 +2076,9 @@ declare module "vue-data-ui" {
}>;
export type VueUiThermometerConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
+ responsive?: boolean; // v3
theme?: Theme;
customPalette?: string[];
style?: {
@@ -1891,10 +2087,15 @@ declare module "vue-data-ui" {
backgroundColor?: string;
color?: string;
height?: number;
+ width?: number; // v3
thermometer?: {
width?: number;
};
- padding?: ChartPadding;
+ padding?: {
+ // v3 left and right are deprecated
+ top?: number;
+ bottom?: number;
+ };
graduations?: {
show?: boolean;
sides?: "left" | "right" | "both" | "none";
@@ -1912,7 +2113,9 @@ declare module "vue-data-ui" {
speedMs?: number;
};
label?: {
+ show?: boolean; // v3
fontSize?: number;
+ minFontSize?: number;
rounding?: number;
bold?: boolean;
prefix?: string;
@@ -1954,7 +2157,14 @@ declare module "vue-data-ui" {
>;
export type VueUiRelationCircleConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
responsive?: boolean;
+ events?: { // v3
+ datapointEnter?: VueUiRelationCircleEvent; // v3
+ datapointLeave?: VueUiRelationCircleEvent; // v3
+ datapointClick?: VueUiRelationCircleEvent; // v3
+ };
responsiveProportionalSizing?: boolean;
theme?: Theme;
customPalette?: string[];
@@ -1971,6 +2181,7 @@ declare module "vue-data-ui" {
labels?: {
color?: string;
fontSize?: number;
+ minFontSize?: number; // v3
};
weightLabels?: {
size?: number;
@@ -2008,6 +2219,18 @@ declare module "vue-data-ui" {
color?: string;
};
+ export type VueUiRelationCircleDatapoint = {
+ color: string;
+ id: string;
+ label: string;
+ regAngle: number;
+ relations: string[];
+ totalWeight: number;
+ weights: number[];
+ x: number;
+ y: number;
+ }
+
export type VueUiRelationCircleExpose = {
getImage(options?: { scale?: number }): GetImagePromise
generatePdf(): void
@@ -2146,7 +2369,7 @@ declare module "vue-data-ui" {
export type VueUiSparkbarDatasetItem = {
name: string;
- value: number;
+ value: number | null;
suffix?: string;
prefix?: string;
rounding?: number;
@@ -2156,7 +2379,14 @@ declare module "vue-data-ui" {
};
export type VueUiSparkbarConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
theme?: Theme;
+ events?: { // v3
+ datapointEnter?: VueUiSparkbarEvent; // v3
+ datapointLeave?: VueUiSparkbarEvent; // v3
+ datapointClick?: VueUiSparkbarEvent; // v3
+ }
customPalette?: string[];
style?: {
backgroundColor?: string;
@@ -2226,10 +2456,24 @@ declare module "vue-data-ui" {
dataset: VueUiSparkbarDatasetItem[];
}>;
- export type VueUiAgePyramidDataset = Array>;
+ export type VueUiAgePyramidDatasetRow = [
+ year: string,
+ rank: number,
+ v1: number | null,
+ v2: number | null
+ ]
+
+ export type VueUiAgePyramidDataset = VueUiAgePyramidDatasetRow[];
export type VueUiAgePyramidConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
responsive?: boolean;
+ events?: { // v3
+ datapointEnter?: VueUiAgePyramidEvent; // v3
+ datapointLeave?: VueUiAgePyramidEvent; // v3
+ datapointClick?: VueUiAgePyramidEvent; // v3
+ };
theme?: Theme;
style?: {
backgroundColor?: string;
@@ -2261,6 +2505,11 @@ declare module "vue-data-ui" {
scale?: number;
translation?: string;
formatter?: Formatter;
+ rotation?: number;
+ autoRotate?: { // v3
+ enable?: boolean; // v3
+ angle?: number; // v3
+ }
};
yAxis?: {
show?: boolean;
@@ -2370,7 +2619,14 @@ declare module "vue-data-ui" {
>;
export type VueUiCandlestickConfig = {
+ debug?: boolean;
+ loading?: boolean;
responsive?: boolean;
+ events?: { // v3
+ datapointEnter?: VueUiCandlestickEvent; // v3
+ datapointLeave?: VueUiCandlestickEvent; // v3
+ datapointClick?: VueUiCandlestickEvent; // v3
+ };
responsiveProportionalSizing?: boolean;
theme?: Theme;
useCssAnimation?: boolean;
@@ -2398,6 +2654,10 @@ declare module "vue-data-ui" {
offsetY?: number;
bold?: boolean;
rotation?: number;
+ autoRotate?: { // v3
+ enable?: boolean; // v3
+ angle?: number; // v3
+ }
};
timeLabels?: {
datetimeFormatter?: AxisDateFormatter
@@ -2528,7 +2788,14 @@ declare module "vue-data-ui" {
};
export type VueUiScatterConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
responsive?: boolean;
+ events?: {
+ datapointEnter?: VueUiScatterEvent; // v3
+ datapointLeave?: VueUiScatterEvent; // v3
+ datapointClick?: VueUiScatterEvent; // v3
+ };
theme?: Theme;
customPalette?: string[];
useCssAnimation?: boolean;
@@ -2609,6 +2876,15 @@ declare module "vue-data-ui" {
useGradient?: boolean;
showLines?: boolean;
linesStrokeWidth?: number;
+ highlighter?: {
+ show?: boolean;
+ opacity?: number;
+ color?: string;
+ stroke?: string;
+ strokeWidth?: number;
+ strokeDasharray?: number;
+ highlightBothAxes?: boolean;
+ }
};
correlation?: {
show?: boolean;
@@ -2650,6 +2926,7 @@ declare module "vue-data-ui" {
legend?: ChartBaseLegend & {
backgroundColor?: string;
roundingValue?: number;
+ position?: 'bottom' | 'top';
};
tooltip?: ChartTooltip & {
roundingValue?: number;
@@ -2740,19 +3017,28 @@ declare module "vue-data-ui" {
>;
export type VueUiHeatmapConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
+ responsive?: boolean; // v3
theme?: Theme;
style?: {
backgroundColor?: string;
color?: string;
fontFamily?: string;
layout?: {
+ height?: number;
+ width?: number;
padding?: ChartPadding;
cells?: {
- height?: number;
+ // height?: number; // v3 deprecated
columnTotal?: {
value?: {
show?: boolean;
rotation?: number;
+ autoRotate?: { // v3
+ enable?: boolean; // v3
+ angle?: number; // v3
+ };
offsetX?: number;
offsetY?: number;
};
@@ -2793,8 +3079,13 @@ declare module "vue-data-ui" {
xAxis?: {
show?: boolean;
values?: Array;
+ datetimeFormatter?: AxisDateFormatter; // v3
showOnlyAtModulo?: number | null;
rotation?: number;
+ autoRotate?: { // v3
+ enable?: boolean; // v3
+ angle?: number; // v3
+ };
fontSize?: number;
color?: string;
bold?: boolean;
@@ -2804,6 +3095,7 @@ declare module "vue-data-ui" {
yAxis?: {
show?: boolean;
values?: Array;
+ datetimeFormatter?: AxisDateFormatter; // v3
fontSize?: number;
color?: string;
bold?: boolean;
@@ -2816,8 +3108,9 @@ declare module "vue-data-ui" {
legend?: ChartBaseLegend & {
backgroundColor?: string;
roundingValue?: number;
- position?: "right" | "bottom";
- scaleBorderRadius?: number;
+ width?: number;
+ // position?: "right" | "bottom"; // v3 deprecated
+ // scaleBorderRadius?: number; // v3 deprecated
};
tooltip?: ChartTooltip & {
roundingValue?: number;
@@ -2851,7 +3144,7 @@ declare module "vue-data-ui" {
id: string;
ratio: number;
side: "up" | "down";
- value: number;
+ value: number | null;
xAxisName: string | undefined;
yAxisName: string | undefined;
};
@@ -2859,12 +3152,12 @@ declare module "vue-data-ui" {
export type VueUiHeatmapRow = {
name: string;
temperatures: VueUiHeatmapDatapoint[];
- values: number[];
+ values: Array;
};
export type VueUiHeatmapDatasetItem = {
- name: string;
- values: number[];
+ name: string | number;
+ values: Array;
};
export type VueUiHeatmapExpose = {
@@ -2920,8 +3213,8 @@ declare module "vue-data-ui" {
export type VueUiXyAnnotation = {
show?: boolean;
yAxis?: {
- yTop?: number;
- yBottom?: number;
+ yTop?: number | null;
+ yBottom?: number | null;
label?: {
text?: string;
textAnchor?: 'start' | 'end';
@@ -2952,7 +3245,14 @@ declare module "vue-data-ui" {
}
export type VueUiXyConfig = {
- responsive?: boolean;
+ debug?: boolean; // v3
+ responsive?: boolean; // v3
+ loading?: boolean; // v3
+ events?: { // v3
+ datapointEnter?: VueUiXyEvent; // v3
+ datapointLeave?: VueUiXyEvent; // v3
+ datapointClick?: VueUiXyEvent; // v3
+ };
responsiveProportionalSizing?: boolean;
theme?: Theme;
customPalette?: string[];
@@ -2966,7 +3266,20 @@ declare module "vue-data-ui" {
color?: string;
height?: number;
width?: number;
- zoom?: ChartZoom;
+ zoom?: ChartZoom & {
+ preview?: {
+ enable?: boolean;
+ fill?: string;
+ stroke?: string;
+ strokeWidth?: number;
+ strokeDasharray?: number;
+ };
+ useDefaultFormat?: boolean;
+ timeFormat?: string;
+ customFormat?:
+ | null
+ | ((params: MinimalCustomFormatParams) => string);
+ };
padding?: ChartPadding;
annotations?: VueUiXyAnnotation[];
highlighter?: {
@@ -2985,6 +3298,11 @@ declare module "vue-data-ui" {
radius?: number;
color?: string;
};
+ useDefaultFormat?: boolean;
+ timeFormat?: string;
+ customFormat?:
+ | null
+ | ((params: MinimalCustomFormatParams) => string);
};
highlightArea?: VueUiXyHighlightArea | VueUiXyHighlightArea[];
grid?: {
@@ -3030,6 +3348,8 @@ declare module "vue-data-ui" {
groupColor?: string | null;
scaleLabelOffsetX?: number;
scaleValueOffsetX?: number;
+ rounding?: number;
+ serieNameFormatter?: Formatter;
};
xAxis?: {
showBaseline?: boolean;
@@ -3048,6 +3368,10 @@ declare module "vue-data-ui" {
showOnlyAtModulo?: boolean;
modulo?: number;
datetimeFormatter?: AxisDateFormatter;
+ autoRotate?: { // v3
+ enable?: boolean; // v3
+ angle?: number; // v3
+ }
};
};
};
@@ -3061,6 +3385,7 @@ declare module "vue-data-ui" {
color?: string;
show?: boolean;
fontSize?: number;
+ position?: 'bottom' | 'top';
};
title?: {
show?: boolean;
@@ -3096,13 +3421,18 @@ declare module "vue-data-ui" {
>
) => string);
showTimeLabel?: boolean;
+ useDefaultTimeFormat?: boolean;
+ timeFormat?: string;
};
userOptions?: ChartUserOptions;
};
bar?: {
+ showTransition?: boolean;
+ transitionDurationMs?: number;
borderRadius?: number;
useGradient?: boolean;
periodGap?: number;
+ innerGap?: number;
border?: {
useSerieColor?: boolean;
strokeWidth?: number;
@@ -3126,10 +3456,17 @@ declare module "vue-data-ui" {
};
};
line?: {
+ showTransition?: boolean;
+ transitionDurationMs?: number;
radius?: number;
useGradient?: boolean;
strokeWidth?: number;
cutNullValues?: boolean;
+ interLine?: {
+ pairs?: [string, string][];
+ colors?: [string | undefined, string | undefined][];
+ fillOpacity?: number;
+ };
dot?: {
hideAboveMaxSerieLength?: number;
useSerieColor?: boolean;
@@ -3154,6 +3491,8 @@ declare module "vue-data-ui" {
};
};
plot?: {
+ showTransition?: boolean;
+ transitionDurationMs?: number;
radius?: number;
useGradient?: boolean;
dot?: {
@@ -3191,7 +3530,7 @@ declare module "vue-data-ui" {
export type VueUiXyDatasetItem = {
name: string;
- series: number[];
+ series: Array;
type: "bar" | "line" | "plot";
color?: string;
dashed?: boolean;
@@ -3214,17 +3553,17 @@ declare module "vue-data-ui" {
};
export type VueUiXyDatasetBarItem = {
- absoluteValues: number[];
+ absoluteValues: Array;
color: string;
id: string;
name: string;
plots: Array<{ x: number; y: number; value: number }>;
- series: number[];
+ series: Array;
type: "bar";
};
export type VueUiXyDatasetLineItem = {
- absoluteValues: number[];
+ absoluteValues: Array;
area: string;
color: string;
curve: string;
@@ -3232,7 +3571,7 @@ declare module "vue-data-ui" {
id: string;
name: string;
plots: Array<{ x: number; y: number; value: number }>;
- series: number[];
+ series: Array;
shape: Shape | null;
type: "line";
useArea: boolean;
@@ -3242,12 +3581,12 @@ declare module "vue-data-ui" {
};
export type VueUiXyDatasetPlotItem = {
- absoluteValues: number[];
+ absoluteValues: Array;
color: string;
id: string;
name: string;
plots: Array<{ x: number; y: number; value: number }>;
- series: number[];
+ series: Array;
shape: Shape | null;
type: "plot";
useTag?: boolean;
@@ -3263,7 +3602,7 @@ declare module "vue-data-ui" {
name: string;
shape: Shape | null;
type: "bar" | "line" | "plot";
- value: number;
+ value: number | null;
};
export type VueUiXyExpose = {
@@ -3284,14 +3623,17 @@ declare module "vue-data-ui" {
{
config?: VueUiXyConfig;
dataset: VueUiXyDatasetItem[];
+ selectedXIndex?: number | null; // v3
},
VueUiXyExpose
>;
- export type VueUiDonutEvent = null | (({datapoint, seriesIndex }: { datapoint: VueUiDonutDatapoint; seriesIndex: number }) => void);
-
export type VueUiDonutConfig = {
+ debug?: boolean;
type?: "classic" | "polar";
+ loading?: boolean;
+ pie?: boolean;
+ autoSize?: boolean;
responsive?: boolean;
theme?: Theme;
customPalette?: string[];
@@ -3340,6 +3682,7 @@ declare module "vue-data-ui" {
color?: string;
bold?: boolean;
fontSize?: number;
+ minFontSize?: number;
rounding?: number;
formatter?: Formatter;
};
@@ -3347,6 +3690,7 @@ declare module "vue-data-ui" {
color?: string;
bold?: boolean;
fontSize?: number;
+ minFontSize?: number;
};
hollow?: {
show?: boolean;
@@ -3406,6 +3750,7 @@ declare module "vue-data-ui" {
roundingPercentage?: number;
showPercentage?: boolean;
showValue?: boolean;
+ position?: 'bottom' | 'top';
};
title?: ChartTitle;
tooltip?: ChartTooltip & {
@@ -3509,10 +3854,17 @@ declare module "vue-data-ui" {
};
export type VueUiNestedDonutsConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
responsive?: boolean;
theme?: Theme;
customPalette?: string[];
useCssAnimation?: boolean;
+ events?: {
+ datapointEnter?: VueUiDonutEvent;
+ datapointLeave?: VueUiDonutEvent;
+ datapointClick?: VueUiDonutEvent;
+ };
serieToggleAnimation?: {
show?: boolean;
durationMs?: number;
@@ -3554,6 +3906,7 @@ declare module "vue-data-ui" {
useSerieColor?: boolean;
showDonutName?: boolean;
boldDonutName?: boolean;
+ curvedDonutName?: boolean;
donutNameAbbreviation?: boolean;
donutNameOffsetY?: number;
donutNameMaxAbbreviationSize?: number;
@@ -3561,6 +3914,7 @@ declare module "vue-data-ui" {
};
};
donut?: {
+ radiusRatio?: number;
strokeWidth?: number;
borderWidth?: number;
spacingRatio?: number;
@@ -3578,6 +3932,7 @@ declare module "vue-data-ui" {
roundingPercentage?: number;
showValue?: boolean;
showPercentage?: boolean;
+ position?: 'bottom' | 'bottom';
};
title?: ChartTitle;
tooltip?: ChartTooltip & {
@@ -3702,7 +4057,14 @@ declare module "vue-data-ui" {
>;
export type VueUiWaffleConfig = {
+ debug?: boolean;
+ loading?: boolean;
responsive?: boolean;
+ events?: { // v3
+ datapointEnter?: VueUiWaffleEvent; // v3
+ datapointLeave?: VueUiWaffleEvent; // v3
+ datapointClick?: VueUiWaffleEvent; // v3
+ };
theme?: Theme;
customPalette?: string[];
useBlurOnHover?: boolean;
@@ -3770,6 +4132,7 @@ declare module "vue-data-ui" {
roundingPercentage?: number;
showValue?: boolean;
showPercentage?: boolean;
+ position?: 'bottom' | 'top';
};
};
};
@@ -3844,7 +4207,14 @@ declare module "vue-data-ui" {
>;
export type VueUiRadarConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
responsive?: boolean;
+ events?: {
+ datapointEnter?: VueUiRadarEvent; // v3
+ datapointLeave?: VueUiRadarEvent; // v3
+ datapointClick?: VueUiRadarEvent; // v3
+ }
theme?: Theme;
customPalette?: string[];
useCssAnimation?: boolean;
@@ -3905,6 +4275,7 @@ declare module "vue-data-ui" {
legend?: ChartBaseLegend & {
backgroundColor?: string;
roundingPercentage?: number;
+ position?: 'bottom' | 'top';
};
};
};
@@ -3934,6 +4305,7 @@ declare module "vue-data-ui" {
values: number[];
x: number;
y: number;
+ formatter: Formatter
};
export type VueUiRadarCategory = {
@@ -4023,7 +4395,14 @@ declare module "vue-data-ui" {
};
export type VueUiQuadrantConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
responsive?: boolean;
+ events?: { // v3
+ datapointEnter?: VueUiQuadrantEvent; // v3
+ datapointLeave?: VueUiQuadrantEvent; // v3
+ datapointClick?: VueUiQuadrantEvent; // v3
+ };
theme?: Theme;
useCssAnimation?: boolean;
zoomAnimationFrames?: number;
@@ -4124,6 +4503,7 @@ declare module "vue-data-ui" {
};
legend?: ChartBaseLegend & {
backgroundColor?: string;
+ position?: 'bottom' | 'top'
};
};
};
@@ -4212,6 +4592,8 @@ declare module "vue-data-ui" {
};
export type VueUiGaugeConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
responsive?: boolean;
theme?: Theme;
customPalette?: string[];
@@ -4255,6 +4637,7 @@ declare module "vue-data-ui" {
curved?: boolean;
offsetRatio?: number;
fontSize?: number;
+ minFontSize?: number; // v3
useSerieColor?: boolean;
color?: string;
bold?: boolean;
@@ -4335,6 +4718,8 @@ declare module "vue-data-ui" {
};
export type VueUiChestnutConfig = {
+ debug?: boolean;
+ loading?: boolean;
theme?: Theme;
customPalette?: string[];
style?: {
@@ -4544,7 +4929,14 @@ declare module "vue-data-ui" {
};
export type VueUiOnionConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
responsive?: boolean;
+ events?: {
+ datapointEnter?: VueUiOnionEvent; // v3
+ datapointLeave?: VueUiOnionEvent; // v3
+ datapointClick?: VueUiOnionEvent; // v3
+ };
theme?: Theme;
customPalette?: string[];
useCssAnimation?: boolean;
@@ -4569,6 +4961,7 @@ declare module "vue-data-ui" {
labels?: {
show?: boolean;
fontSize?: number;
+ minFontSize?: number; // v3
color?: string;
roundingValue?: number;
roundingPercentage?: number;
@@ -4589,6 +4982,7 @@ declare module "vue-data-ui" {
backgroundColor?: string;
roundingValue?: number;
roundingPercentage?: number;
+ position?: 'bottom' | 'top';
};
tooltip?: ChartTooltip & {
showValue?: boolean;
@@ -4690,27 +5084,36 @@ declare module "vue-data-ui" {
export type VueUiVerticalBarDatasetChild = {
name: string;
- value: number;
+ value: number | null;
};
+ export type VueUiHorizontalBarDatasetChild = VueUiVerticalBarDatasetChild; // v3 renaming
+
export type VueUiVerticalBarDatasetItem = {
name: string;
- value: number;
+ value: number | null;
color?: string;
- children?: VueUiVerticalBarDatasetChild[];
+ children?: VueUiVerticalBarDatasetChild[] | VueUiHorizontalBarDatasetChild[];
};
- export type VueUiVerticalBarEvent = null | (({ datapoint, seriesIndex }: { datapoint: VueUiVerticalBarDatapoint; seriesIndex: number }) => void);
+ export type VueUiHorizontalBarDatasetItem = VueUiVerticalBarDatasetItem // v3 renaming
+
+ export type VueUiVerticalBarEvent = null | (({ datapoint, seriesIndex }: { datapoint: VueUiVerticalBarDatapoint | VueUiHorizontalBarDatapoint; seriesIndex: number }) => void);
+
+ export type VueUiHorizontalBarEvent = VueUiVerticalBarEvent; // v3 renaming
export type VueUiVerticalBarConfig = {
+ debug?: boolean;
+ loading?: boolean;
+ autoSize?: boolean;
responsive?: boolean;
theme?: Theme;
customPalette?: string[];
useCssAnimation?: boolean;
events?: {
- datapointEnter?: VueUiVerticalBarEvent;
- datapointLeave?: VueUiVerticalBarEvent;
- datapointClick?: VueUiVerticalBarEvent;
+ datapointEnter?: VueUiVerticalBarEvent | VueUiHorizontalBarEvent;
+ datapointLeave?: VueUiVerticalBarEvent | VueUiHorizontalBarEvent;
+ datapointClick?: VueUiVerticalBarEvent | VueUiHorizontalBarEvent;
};
style?: {
fontFamily?: string;
@@ -4771,6 +5174,7 @@ declare module "vue-data-ui" {
show?: boolean;
color?: string;
strokeWidth?: number;
+ fullWidth?: boolean;
};
};
title?: ChartTitle;
@@ -4793,9 +5197,9 @@ declare module "vue-data-ui" {
| null
| ((
params: VueUiTooltipParams<
- VueUiVerticalBarDatapoint,
- VueUiVerticalBarSerie[],
- VueUiVerticalBarConfig
+ VueUiVerticalBarDatapoint | VueUiHorizontalBarDatapoint,
+ VueUiVerticalBarSerie[] | VueUiHorizontalBarSerie[],
+ VueUiVerticalBarConfig | VueUiHorizontalBarConfig
>
) => string);
};
@@ -4822,6 +5226,8 @@ declare module "vue-data-ui" {
};
};
+ export type VueUiHorizontalBarConfig = VueUiVerticalBarConfig; // v3 renaming;
+
export type VueUiVerticalBarDatapoint = {
children?: Array;
childIndex?: number;
@@ -4838,8 +5244,10 @@ declare module "vue-data-ui" {
value: number;
};
+ export type VueUiHorizontalBarDatapoint = VueUiVerticalBarDatapoint; // v3 renaming
+
export type VueUiVerticalBarSerie = {
- children: VueUiVerticalBarDatapoint[];
+ children: VueUiVerticalBarDatapoint[] | VueUiHorizontalBarDatapoint[];
color: string;
hasChildren: boolean;
is: string;
@@ -4850,8 +5258,10 @@ declare module "vue-data-ui" {
value: number;
};
+ export type VueUiHorizontalBarSerie = VueUiVerticalBarSerie; // v3 renaming
+
export type VueUiVerticalBarExpose = {
- getData(): Promise>>
+ getData(): Promise>> | Promise>>
getImage(options?: { scale?: number }): GetImagePromise
recalculateHeight(): void,
generateCsv(): void
@@ -4864,6 +5274,8 @@ declare module "vue-data-ui" {
toggleFullscreen(): void
}
+ export type VueUiHorizontalBarExpose = VueUiVerticalBarExpose; // v3 renaming
+
export const VueUiVerticalBar: DefineComponent<
{
config?: VueUiVerticalBarConfig;
@@ -4872,10 +5284,21 @@ declare module "vue-data-ui" {
VueUiVerticalBarExpose
>;
+ /**
+ * Renamed from the v2 VueUiVerticalBar
+ */
+ export const VueUiHorizontalBar: DefineComponent<
+ {
+ config?: VueUiHorizontalBarConfig;
+ dataset: VueUiHorizontalBarDatasetItem[];
+ },
+ VueUiHorizontalBarExpose
+ >;
+
export type VueUiSparklineDatasetItem = {
- period: string;
- value: number;
- absoluteValue?: number;
+ period: string | number;
+ value: number | null;
+ absoluteValue?: number | null;
id?: string;
plotValue?: number;
toMax?: number;
@@ -4885,9 +5308,16 @@ declare module "vue-data-ui" {
};
export type VueUiSparklineConfig = {
+ loading?: boolean; // v3
+ debug?: boolean; // v3
theme?: Theme;
type?: "line" | "bar";
responsive?: boolean;
+ events?: { // v3
+ datapointEnter?: VueUiSparklineEvent; // v3
+ datapointLeave?: VueUiSparklineEvent; // v3
+ datapointClick?: VueUiSparklineEvent; // v3
+ };
downsample?: {
threshold?: number;
};
@@ -4955,6 +5385,7 @@ declare module "vue-data-ui" {
prefix?: string;
suffix?: string;
formatter?: Formatter;
+ datetimeFormatter?: AxisDateFormatter; // v3
};
title?: {
show?: boolean;
@@ -5594,6 +6025,13 @@ declare module "vue-data-ui" {
}>;
export type VueUiQuickChartConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
+ events?: {
+ datapointEnter?: ChartEvent;
+ datapointLeave?: ChartEvent;
+ datapointClick?: ChartEvent;
+ };
responsive?: boolean;
theme?: Theme;
axisLabelsFontSize?: number;
@@ -5608,11 +6046,13 @@ declare module "vue-data-ui" {
dataLabelFontSize?: number;
dataLabelRoundingPercentage?: number;
dataLabelRoundingValue?: number;
+ donutCurvedMarkers?: boolean; // v3
donutHideLabelUnderPercentage?: number;
donutLabelMarkerStrokeWidth?: number;
donutRadiusRatio?: number;
donutShowTotal?: boolean;
donutStrokeWidth?: number;
+ donutStroke?: string; // v3
donutThicknessRatio?: number;
donutTotalLabelFontSize?: number;
donutTotalLabelOffsetY?: number;
@@ -5625,6 +6065,7 @@ declare module "vue-data-ui" {
legendFontSize?: number;
legendIcon?: VueUiIconName;
legendIconSize?: number;
+ legendPosition?: 'bottom' | 'top';
lineAnimated?: boolean;
lineSmooth?: boolean;
lineStrokeWidth?: number;
@@ -5645,6 +6086,8 @@ declare module "vue-data-ui" {
tooltipFontSize?: number;
tooltipPosition?: TooltipPosition;
tooltipOffsetY?: number;
+ tooltipSmooth?: boolean;
+ tooltipBackdropFilter?: boolean;
useCustomLegend?: boolean;
valuePrefix?: string;
valueSuffix?: string;
@@ -5665,6 +6108,10 @@ declare module "vue-data-ui" {
xyPeriods?: Array;
datetimeFormatter?: AxisDateFormatter;
xyPeriodLabelsRotation?: number;
+ xyPeriodLabelsAutoRotate?: { // v3
+ enable?: boolean; // v3
+ angle?: number; // v3
+ };
xyPeriodsShowOnlyAtModulo?: boolean;
xyPeriodsModulo?: number;
xyScaleSegments?: number;
@@ -5700,12 +6147,9 @@ declare module "vue-data-ui" {
annotator?: string;
};
userOptionsPrint?: {
- allowTaint?: boolean;
- backgroundColor?: string;
- useCORS?: boolean;
- onclone?: null | ((doc: Document) => void),
scale?: number;
- logging?: boolean;
+ overflowTolerance?: number;
+ orientation?: 'auto' | 'l' | 'p';
};
userOptionsCallbacks?: {
tooltip?: null | (() => void);
@@ -5719,11 +6163,11 @@ declare module "vue-data-ui" {
};
export type VueUiQuickChartDatasetObjectItem = {
- [key: string]: string | number | number[];
+ [key: string]: string | number | Array;
};
export type VueUiQuickChartDataset =
- | number[]
+ | Array
| VueUiQuickChartDatasetObjectItem
| VueUiQuickChartDatasetObjectItem[];
@@ -5776,6 +6220,9 @@ declare module "vue-data-ui" {
}>;
export type VueUiSparkTrendConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
+ responsive?: boolean; // v3
theme?: Theme;
downsample?: {
threshold?: number;
@@ -5783,6 +6230,8 @@ declare module "vue-data-ui" {
style?: {
backgroundColor?: string;
fontFamily?: string;
+ width?: number; // v3
+ height?: number; // v3
animation?: {
show?: boolean;
animationFrames?: number;
@@ -5831,12 +6280,19 @@ declare module "vue-data-ui" {
};
export const VueUiSparkTrend: DefineComponent<{
- dataset: number[];
+ dataset: Array;
config?: VueUiSparkTrendConfig;
}>;
export type VueUiStripPlotConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
responsive?: boolean;
+ events?: { // v3
+ datapointEnter?: VueUiStripPlotEvent; // v3
+ datapointLeave?: VueUiStripPlotEvent; // v3
+ datapointClick?: VueUiStripPlotEvent; // v3
+ };
responsiveProportionalSizing?: boolean;
theme?: Theme;
customPalette?: string[];
@@ -5860,7 +6316,8 @@ declare module "vue-data-ui" {
backgroundColor?: string;
color?: string;
height?: number;
- stripWidth?: number;
+ width?: number; // v3
+ // stripWidth?: number; // v3 deprecated
padding?: ChartPadding;
grid?: {
show?: boolean;
@@ -5916,6 +6373,11 @@ declare module "vue-data-ui" {
color?: string;
fontSize?: number;
offsetY?: number;
+ rotation?: number; // v3
+ autoRotate?: { // v3
+ enable?: boolean; // v3
+ angle?: number; // v3
+ }
};
yAxisLabels?: {
show?: boolean;
@@ -5947,6 +6409,9 @@ declare module "vue-data-ui" {
id: string;
name: string;
parentId: string;
+ parentIndex: number;
+ parentName: string;
+ plotIndex: number;
value: number;
x: number;
y: number;
@@ -6010,8 +6475,32 @@ declare module "vue-data-ui" {
show?: boolean;
};
+ export type VueUiDumbbellDatapoint = {
+ centerX: number;
+ end: number | null;
+ endVal: number;
+ endX: number;
+ id: string;
+ name: string;
+ start: number | null;
+ startX: number;
+ y: number;
+ evaluationColor: string;
+ evaluationGrad: string;
+ isPositive: boolean;
+ isNegative: boolean;
+ isNeutral: boolean;
+ }
+
export type VueUiDumbbellConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
responsive?: boolean;
+ events?: { // v3
+ datapointEnter?: VueUiDumbbellEvent; // v3
+ datapointLeave?: VueUiDumbbellEvent; // v3
+ datapointClick?: VueUiDumbbellEvent; // v3
+ };
theme?: Theme;
useAnimation?: boolean;
animationSpeed?: number;
@@ -6027,6 +6516,12 @@ declare module "vue-data-ui" {
plots?: {
startColor?: string;
endColor?: string;
+ evaluationColors?: { // v3
+ enable?: boolean; // v3
+ positive?: string; // v3
+ negative?: string; // v3
+ neutral?: string; // v3
+ };
radius?: number;
stroke?: string;
strokeWidth?: number;
@@ -6042,6 +6537,8 @@ declare module "vue-data-ui" {
grid?: {
strokeWidth?: number;
scaleSteps?: number;
+ scaleMin?: number | null; // v3
+ scaleMax?: number | null; // v3
horizontalGrid?: {
show?: boolean;
stroke?: string;
@@ -6055,6 +6552,21 @@ declare module "vue-data-ui" {
strokeDasharray?: number;
};
};
+ comparisonLines?: { // v3
+ show?: boolean; // v3
+ strokeWidth?: number; // v3
+ strokeDasharray?: number; // v3
+ showRect?: boolean; // v3
+ rectColor?: string; // v3
+ rectOpacity?: number; // v3
+ showLabel?: boolean; // v3
+ labelColor?: string; // v3
+ labelFontSize?: number; // v3
+ }; // v3
+ highlighter?: { // v3
+ color?: string; // v3
+ opacity?: number; // v3
+ };
labels?: {
prefix?: string;
suffix?: string;
@@ -6067,21 +6579,41 @@ declare module "vue-data-ui" {
rounding?: number;
show?: boolean;
showProgression?: boolean;
+ formatter?: Formatter; // v3
+ };
+ axis?: { // v3
+ yLabel?: string; // v3
+ yLabelOffsetX?: number; // v3
+ xLabel?: string; // v3
+ xLabelOffsetY?: number; // v3
+ fontSize?: number; // v3
+ color?: string; // v3
};
xAxisLabels?: VueUiDumbbellConfigLabel & {
bold?: boolean;
+ rotation?: number; // v3
+ autoRotate?: { // v3
+ enable?: boolean; // v3
+ angle?: number; // v3
+ }
};
startLabels?: VueUiDumbbellConfigLabel & {
useStartColor?: boolean;
+ useEvaluationColor?: boolean; // v3
};
endLabels?: VueUiDumbbellConfigLabel & {
useEndColor?: boolean;
+ useEvaluationColor?: boolean;
};
};
legend?: ChartBaseLegend & {
+ backgroundColor?: string;
labelStart?: string;
labelEnd?: string;
- backgroundColor?: string;
+ labelPositive?: string; // v3
+ labelNegative?: string; // v3
+ labelNeutral?: string; // v3
+ position?: 'bottom' | 'top';
};
title?: ChartTitle;
};
@@ -6105,16 +6637,16 @@ declare module "vue-data-ui" {
export type VueUiDumbbellDataset = {
name: string;
- start: number;
- end: number;
+ start: number | null;
+ end: number | null;
};
export type VueUiDumbbellExpose = {
getData(): Promise>
getImage(options?: { scale?: number }): GetImagePromise
generatePdf(): void
@@ -6151,7 +6683,14 @@ declare module "vue-data-ui" {
};
export type VueUiWordCloudConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
responsive?: boolean;
+ events?: { // v3
+ datapointEnter?: VueUiWordCloudEvent; // v3
+ datapointLeave?: VueUiWordCloudEvent; // v3
+ datapointClick?: VueUiWordCloudEvent; // v3
+ }
theme?: string;
customPalette?: string[];
userOptions?: ChartUserOptions;
@@ -6243,7 +6782,7 @@ declare module "vue-data-ui" {
export type VueUiXyCanvasDatasetItem = {
name: string;
- series: number[];
+ series: Array;
color?: string;
type?: "line" | "plot" | "bar";
useArea?: boolean;
@@ -6303,6 +6842,7 @@ declare module "vue-data-ui" {
show?: boolean;
fontSize?: number;
bold?: boolean;
+ position?: 'bottom' | 'top';
};
title?: ChartTitle;
grid?: {
@@ -6433,7 +6973,7 @@ declare module "vue-data-ui" {
VueUiXyCanvasExpose
>;
- export type VueUiFlowDatasetItem = [string, string, number];
+ export type VueUiFlowDatasetItem = [string, string, number | null];
export type VueUiFlowNode = {
color: string;
@@ -6442,6 +6982,7 @@ declare module "vue-data-ui" {
inflow?: number;
outflow?: number;
percentOfTotal: number;
+ name?: string;
}
export type VueUiFlowFormattedDataset = {
@@ -6467,6 +7008,14 @@ declare module "vue-data-ui" {
}
export type VueUiFlowConfig = {
+ debug?: boolean;
+ loading?: boolean;
+ responsive?: boolean;
+ events?: {
+ datapointEnter?: VueUiFlowEvent;
+ datapointLeave?: VueUiFlowEvent;
+ datapointClick?: VueUiFlowEvent;
+ };
theme?: Theme;
customPalette?: string[];
userOptions?: ChartUserOptions;
@@ -6476,9 +7025,12 @@ declare module "vue-data-ui" {
fontFamily?: string;
chart?: {
backgroundColor?: string;
+ width?: number; // v3
+ height?: number; // v3
color?: string;
legend?: ChartBaseLegend & {
backgroundColor?: string;
+ position?: 'bottom' | 'top';
};
tooltip?: ChartTooltip & {
showPercentage?: boolean;
@@ -6507,7 +7059,7 @@ declare module "vue-data-ui" {
title?: ChartTitle;
nodes?: {
gap?: number;
- minHeight?: number;
+ // minHeight?: number; // v3 deprecated
width?: number;
labels?: {
fontSize?: number;
@@ -6524,7 +7076,7 @@ declare module "vue-data-ui" {
strokeWidth?: number;
};
links?: {
- width?: number;
+ // width?: number; // v3 deprecated
opacity?: number;
stroke?: string;
strokeWidth?: number;
@@ -6596,7 +7148,7 @@ declare module "vue-data-ui" {
export type VueUiParallelCoordinatePlotDatasetSerieItem = {
name: string;
- values: number[];
+ values: Array;
};
export type VueUiParallelCoordinatePlotDatasetItem = {
@@ -6607,7 +7159,14 @@ declare module "vue-data-ui" {
};
export type VueUiParallelCoordinatePlotConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
responsive?: boolean;
+ events?: { // v3
+ datapointEnter?: VueUiParallelCoordinatePlotEvent; // v3
+ datapointLeave?: VueUiParallelCoordinatePlotEvent; // v3
+ datapointClick?: VueUiParallelCoordinatePlotEvent; // v3
+ },
responsiveProportionalSizing?: boolean;
theme?: Theme;
useCssAnimation?: boolean;
@@ -6646,6 +7205,11 @@ declare module "vue-data-ui" {
axisNames?: string[];
axisNamesColor?: string;
axisNamesFontSize?: number;
+ axisNamesRotation?: number; // v3
+ axisNamesAutoRotate?: { // v3
+ enable?: boolean; // v3
+ angle?: number; // v3
+ };
axisNamesBold?: boolean;
roundings?: number[];
prefixes?: string[];
@@ -6673,6 +7237,7 @@ declare module "vue-data-ui" {
title?: ChartTitle;
legend?: ChartBaseLegend & {
backgroundColor?: string;
+ position?: 'bottom' | 'top';
};
tooltip?: ChartTooltip & {
customFormat?:
@@ -6700,6 +7265,28 @@ declare module "vue-data-ui" {
};
};
+ export type VueUiParallelCoordinatePlotEventDatapoint = {
+ color: string;
+ datapoints: Array<{
+ axisIndex: number;
+ comment: string;
+ datapointIndex: number;
+ name: string;
+ seriesIndex: number;
+ seriesName: string;
+ value: number;
+ x: number;
+ y: number;
+ }>;
+ id: string;
+ name: string;
+ pathLength: number;
+ shape: Shape;
+ smoothPath: string;
+ straightPath: string;
+ values: number[];
+ }
+
export type VueUiParallelCoordinatePlotDatapointSelection = {
id: string;
name: string;
@@ -6918,6 +7505,8 @@ declare module "vue-data-ui" {
}>;
export type VueUiGizmoConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
type?: "battery" | "gauge";
size?: number;
stroke?: string;
@@ -6939,7 +7528,7 @@ declare module "vue-data-ui" {
export type VueUiStackbarDatasetItem = {
name: string;
- series: number[];
+ series: Array;
color?: string;
};
@@ -6967,6 +7556,11 @@ declare module "vue-data-ui" {
responsive?: boolean;
customPalette?: string[];
useCssAnimation?: boolean;
+ events?: { // v3
+ datapointEnter?: VueUiStackbarEvent; // v3
+ datapointLeave?: VueUiStackbarEvent; // v3
+ datapointClick?: VueUiStackbarEvent; // v3
+ };
orientation?: "vertical" | "horizontal";
table?: {
show?: boolean;
@@ -6992,6 +7586,7 @@ declare module "vue-data-ui" {
title?: ChartTitle;
legend?: ChartBaseLegend & {
backgroundColor?: string;
+ position?: 'bottom' | 'top';
};
zoom?: ChartZoom;
tooltip?: ChartTooltip & {
@@ -7075,6 +7670,10 @@ declare module "vue-data-ui" {
datetimeFormatter?: AxisDateFormatter;
offsetY?: number;
rotation?: number;
+ autoRotate?: { // v3
+ enable?: boolean; // v3
+ angle?: number; // v3
+ };
fontSize?: number;
color?: string;
bold?: boolean;
@@ -7159,6 +7758,9 @@ declare module "vue-data-ui" {
};
export type VueUiBulletConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
+ responsive?: boolean; // v3
theme?: Theme;
userOptions?: ChartUserOptions;
style?: {
@@ -7193,6 +7795,7 @@ declare module "vue-data-ui" {
};
};
target?: {
+ show?: boolean;
onTop?: boolean;
color?: string;
rounded?: boolean;
@@ -7217,6 +7820,7 @@ declare module "vue-data-ui" {
title?: ChartTitle;
legend?: ChartBaseLegend & {
roundingValue?: number;
+ position?: 'bottom' | 'top';
};
};
}
@@ -7371,7 +7975,14 @@ declare module "vue-data-ui" {
};
export type VueUiHistoryPlotConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
responsive?: boolean;
+ events?: { // v3
+ datapointEnter?: VueUiHistoryPlotEvent; // v3
+ datapointLeave?: VueUiHistoryPlotEvent; // v3
+ datapointClick?: VueUiHistoryPlotEvent; // v3
+ }
responsiveProportionalSizing?: boolean;
theme?: Theme;
customPalette?: string[];
@@ -7434,6 +8045,10 @@ declare module "vue-data-ui" {
rounding?: number;
offsetY?: number;
rotation?: number;
+ autoRotate?: { // v3
+ enable?: boolean; // v3
+ angle?: number; // v3
+ };
formatter?: Formatter;
prefix?: string;
suffix?: string;
@@ -7507,6 +8122,7 @@ declare module "vue-data-ui" {
};
legend?: ChartBaseLegend & {
backgroundColor?: string;
+ position?: 'bottom' | 'top';
};
title?: ChartTitle;
tooltip?: ChartTooltip & {
@@ -7535,6 +8151,11 @@ declare module "vue-data-ui" {
y: number;
};
+ export type VueUiHistoryPlotDatapointEvent = VueUiHistoryPlotDatapoint & {
+ plotIndex: number;
+ seriesIndex: number;
+ }
+
export type VueUiHistoryPlotDatpointSeries = VueUiHistoryPlotDatasetItem & {
seriesIndex: number;
};
@@ -7585,7 +8206,24 @@ declare module "vue-data-ui" {
color?: string;
};
+ export type VueUiCirclePackDatapoint = {
+ name: string;
+ value: number;
+ r: number;
+ id: string;
+ color: string;
+ x: number;
+ y: number;
+ }
+
export type VueUiCirclePackConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
+ events?: { // v3
+ datapointEnter?: VueUiCirclePackEvent; // v3
+ datapointLeave?: VueUiCirclePackEvent; // v3
+ datapointClick?: VueUiCirclePackEvent; // v3
+ };
theme?: Theme;
customPalette?: string[];
userOptions?: ChartUserOptions;
@@ -7663,15 +8301,7 @@ declare module "vue-data-ui" {
};
export type VueUiCirclePackExpose = {
- getData(): Promise>
+ getData(): Promise>
getImage(options?: { scale?: number }): GetImagePromise
generateCsv(): void
generateImage(): void
@@ -7689,7 +8319,41 @@ declare module "vue-data-ui" {
VueUiCirclePackExpose
>;
+ export type VueUiWorldDatapoint = {
+ category: string | null;
+ code: string;
+ color: string;
+ geo: {
+ geometry: {
+ coordinates: Array>>>;
+ type: string;
+ };
+ properties: {
+ admin: string;
+ iso_a3: string;
+ name: string;
+ };
+ type: string;
+ },
+ geometry: {
+ coordinates: Array>>;
+ type: string;
+ };
+ isActive: boolean;
+ name: string;
+ path: string;
+ uid: string;
+ value: number | null;
+ }
+
export type VueUiWorldConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
+ events?: {
+ datapointEnter?: VueUiWorldEvent; // v3
+ datapointLeave?: VueUiWorldEvent; // v3
+ datapointClick?: VueUiWorldEvent; // v3
+ };
userOptions?: ChartUserOptions;
customPalette?: string[];
projection?: 'aitoff' | 'azimuthalEquidistant' | 'bonne' | 'equirectangular' | 'gallPeters' | 'globe' | 'hammer' | 'mercator' | 'mollweide' | 'robinson' | 'sinusoidal' | 'vanDerGrinten' | 'winkelTripel',
@@ -7738,6 +8402,7 @@ declare module "vue-data-ui" {
title?: ChartTitle;
legend?: ChartBaseLegend & {
backgroundColor?: string;
+ position?: 'bottom' | 'top';
};
}
};
@@ -7786,7 +8451,7 @@ declare module "vue-data-ui" {
export type VueUiRidgelineDatapoint = {
name: string;
- values: number[];
+ values: Array;
color?: string;
}
@@ -7795,10 +8460,31 @@ declare module "vue-data-ui" {
datapoints: VueUiRidgelineDatapoint[]
}
+ export type VueUiRidgelineDatapointEventUnit = {
+ color: string;
+ name: string;
+ values: Array;
+ id: string;
+ };
+
+ export type VueUiRidgelineDatapointEventEntry = {
+ dp: VueUiRidgelineDatapointEventUnit;
+ selected: number;
+ };
+
+ export type VueUiRidgelineDatapointEvent = VueUiRidgelineDatapointEventEntry[][];
+
export type VueUiRidgelineConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
+ responsive?: boolean;
+ events?: { // v3
+ datapointEnter?: VueUiRidgelineEvent; // v3
+ datapointLeave?: VueUiRidgelineEvent; // v3
+ datapointClick?: VueUiRidgelineEvent; // v3
+ };
theme?: Theme;
customPalette?: string[];
- responsive?: boolean;
userOptions?: ChartUserOptions;
useCssAnimation?: boolean;
table?: {
@@ -7883,6 +8569,10 @@ declare module "vue-data-ui" {
prefix?: string;
suffix?: string;
rotation?: number;
+ autoRotate?: { // v3
+ enable?: boolean; // v3
+ angle?: number; // v3
+ };
values?: Array;
datetimeFormatter?: AxisDateFormatter;
color?: string;
@@ -7897,7 +8587,7 @@ declare module "vue-data-ui" {
yAxis?: {
labels?: {
fontSize?: number;
- bold?: number;
+ bold?: boolean;
color?: string;
offsetX?: number;
};
@@ -7954,18 +8644,58 @@ declare module "vue-data-ui" {
>
export type VueUiChordDataset = {
- matrix: number[][];
+ matrix: Array>;
labels?: string[];
colors?: string[];
}
+ export type VueUiChordDatapointArc = {
+ color: string;
+ endAngle: number;
+ id: string;
+ index: number;
+ name: string;
+ pattern: string;
+ proportion: number;
+ startAngle: number;
+ }
+
+ export type VueUiChordNode = {
+ endAngle: number;
+ groupColor: string;
+ groupId: string;
+ groupName: string;
+ index: number;
+ midAngle: number;
+ midBaseX: number;
+ midBaseY: number;
+ pattern: string;
+ startAngle: number;
+ subIndex: number;
+ value: number;
+ }
+
+ export type VueUiChordDatapointRibbon = {
+ color: string;
+ id: string;
+ source: VueUiChordNode;
+ target: VueUiChordNode;
+ }
+
export type VueUiChordConfig = {
+ debug?: boolean; // v3
+ loading?: boolean; // v3
+ responsive?: boolean;
+ events?: {
+ datapointEnter?: VueUiChordEvent; // v3
+ datapointLeave?: VueUiChordEvent; // v3
+ datapointClick?: VueUiChordEvent; // v3
+ };
theme?: Theme;
customPalette?: string[];
enableRotation?: boolean;
initialRotation?: nulber;
useCssAnimation?: boolean;
- responsive?: boolean;
userOptions?: ChartUserOptions;
table?: {
show?: boolean;
@@ -7980,6 +8710,7 @@ declare module "vue-data-ui" {
color?: string;
legend?: ChartBaseLegend & {
backgroundColor?: string;
+ position?: 'bottom' | 'top';
};
title?: ChartTitle;
arcs?: {
@@ -7987,7 +8718,7 @@ declare module "vue-data-ui" {
outerRadiusRatio?: number;
padAngle?: number;
stroke?: string;
- strokeWidth?: string;
+ strokeWidth?: number;
labels?: {
show?: boolean;
fontSize?: number;
@@ -8096,6 +8827,7 @@ declare module "vue-data-ui" {
| VueUiThermometerConfig
| VueUiTiremarksConfig
| VueUiVerticalBarConfig
+ | VueUiHorizontalBarConfig
| VueUiWaffleConfig
| VueUiWheelConfig
| VueUiXyConfig
@@ -8159,6 +8891,7 @@ declare module "vue-data-ui" {
| "vue_ui_thermometer"
| "vue_ui_tiremarks"
| "vue_ui_vertical_bar"
+ | "vue_ui_horizontal_bar"
| "vue_ui_waffle"
| "vue_ui_wheel"
| "vue_ui_xy"
@@ -8474,6 +9207,55 @@ declare module "vue-data-ui" {
* @returns The shifted color in hexadecimal format.
*/
export const shiftColorHue: (color: string, strength: number) => string;
+
+ export type FormatSmallValueArgs = {
+ value: number;
+ maxDecimals?: number;
+ fallbackFormatter?: (value: number) => string;
+ removeTrailingZero?: boolean
+ }
+
+ /**
+ * Vue Data UI utility
+ * ---
+ * Formats numeric values with a controlled number of decimal places,
+ * applying maxDecimals for all values when no fallbackFormatter is given,
+ * or calling the fallbackFormatter for values ≥ 1 if provided.
+ * ___
+ * @example
+ * // Zero value
+ * formatSmallValue({ value: 0 }); // "0"
+ *
+ * // Values < 1 use minimal decimals
+ * formatSmallValue({ value: 0.9 }); // "0.9"
+ * formatSmallValue({ value: 0.0042 }); // "0.0042"
+ * formatSmallValue({ value: 0.00420001 }); // "0.0042"
+ *
+ * // Retain trailing zeros
+ * formatSmallValue({ value: 0.9, removeTrailingZero: false }); // "0.90"
+ *
+ * // Values ≥ 1 without fallback apply maxDecimals
+ * formatSmallValue({ value: 1.61803, maxDecimals: 3 }); // "1.618"
+ *
+ * // Values ≥ 1 with fallbackFormatter
+ * formatSmallValue({ value: 2.5, fallbackFormatter: v => v.toFixed(1) }); // "2.5"
+ *
+ * // Negative values
+ * formatSmallValue({ value: -0.056 }); // "-0.056"
+ *
+ * @param {FormatSmallValueArgs} options - Configuration object for formatting.
+ * @param {number} options.value - The numeric value to format.
+ * @param {number} [options.maxDecimals=4] - Maximum decimal places to use.
+ * @param {(value: number) => string} [options.fallbackFormatter] - Formatter for values ≥ 1.
+ * @param {boolean} [options.removeTrailingZero=true] - Whether to strip unnecessary trailing zeros.
+ * @returns {string} The formatted number as a string.
+ */
+ export const formatSmallValue: ({
+ value,
+ maxDecimals,
+ fallbackFormatter,
+ removeTrailingZero
+ }: FormatSmallValueArgs) => string
export type CreateTSpansArgs = {
content: string;
@@ -8521,60 +9303,15 @@ declare module "vue-data-ui" {
y
}: CreateTSpansArgs) => string;
- export type UseObjectBindingsOptions = {
- /** Delimiter to join object‑path segments */
- delimiter?: string;
- /** If true, array indices will not be traversed */
- skipArrays?: boolean;
- };
-
- /**
- * Recursively build a union of dot‑delimited paths for an object type,
- * but skip arrays (we don’t traverse them by default at runtime).
- */
- type Paths = T extends object
- ? T extends any[]
- ? never
- : {
- [K in Extract]:
- // if the property is itself an object, recurse
- T[K] extends object
- ? `${K}` | `${K}.${Paths}`
- : `${K}`;
- }[Extract]
- : never;
-
- /**
- * Given an object type `T` and one of its path strings `P`,
- * resolve the type at that path.
- */
- type PathValue =
- P extends `${infer K}.${infer Rest}`
- ? K extends keyof T
- ? PathValue
- : never
- : P extends keyof T
- ? T[P]
- : never;
-
- /**
- * A fully‑typed bindings record: for each valid path `P` in `T`,
- * `ComputedRef` of the exact `PathValue`.
- */
- export type TypedBindings = {
- [P in Paths]: WritableComputedRef>;
- };
-
/**
* Vue Data UI composable
* ---
* Flattens a reactive config object into computed refs for every leaf property.
*
* @template T extends object
- * @param configRef A Vue `Ref` holding your object.
+ * @param configRef A Vue `Ref` holding your object.
* @param options Optional settings: `delimiter` (default `"."`) and `skipArrays` (default `true`).
- * @returns A `TypedBindings` whose keys are every “leaf” path in `T`
- * and whose values are `WritableComputedRef` of the exact property type.
+ * @returns An object with flatten config as refs
*
* ___
* @example
@@ -8604,8 +9341,11 @@ declare module "vue-data-ui" {
*
* ```
*/
- export function useObjectBindings(
- configRef: Ref,
- options?: UseObjectBindingsOptions
- ): TypedBindings;
+ export function useObjectBindings(
+ configRef: Ref>,
+ options?: {
+ delimiter?: string
+ skipArrays?: boolean
+ }
+ ): Record>;
}