Skip to content

Commit 5cec9bd

Browse files
committed
Improvement - VueUiTreemap - Add breadcrumbs
1 parent 9d7925b commit 5cec9bd

File tree

6 files changed

+169
-26
lines changed

6 files changed

+169
-26
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ From the dataset you pass into the props, this component will produce the most a
447447
| `VueUiStripPlot` | `VueUiStripPlotDataset[]` | `VueUiStripPlotConfig` | `@selectDatapoint`, `getData`, `generatePdf`, `generateCsv`, `generateImage`, `toggleTable`, `toggleLabels`, `toggleTooltip` | `#svg`, `#legend`, `#tooltip-before`, `#tooltip-after`, `#watermark`, `#chart-background` |||
448448
| `VueUiThermometer` | `VueUiThermometerDataset` | `VueUiThermometerConfig` | `generatePdf`, `generateImage` | `#svg`, `#watermark`, `#chart-background` |||
449449
| `VueUiTiremarks` | `VueUiTiremarksDataset` | `VueUiTiremarksConfig` | `generatePdf`, `generateImage` | `#svg`, `#legend`, `#watermark`, `#chart-background` |||
450-
| `VueUiTreemap` | `VueUiTreemapDatasetItem[]` | `VueUiTreemapConfig` | `@selectLegend`, `@selectDatapoint`, `getData`, `generatePdf`, `generateCsv`, `generateImage`, `toggleTable`, `toggleTooltip` | `#svg`, `#rect`, `#legend`, `#tooltip-before`, `#tooltip-after`, `#watermark` |||
450+
| `VueUiTreemap` | `VueUiTreemapDatasetItem[]` | `VueUiTreemapConfig` | `@selectLegend`, `@selectDatapoint`, `getData`, `generatePdf`, `generateCsv`, `generateImage`, `toggleTable`, `toggleTooltip` | `#svg`, `#rect`, `#legend`, `#tooltip-before`, `#tooltip-after`, `#watermark`, `#breadcrumb-label`, `#breadcrumb-arrow` |||
451451
| `VueUiVerticalBar` | `VueUiVerticalBarDatasetItem[]` | `VueUiWheelConfig` | `@selectLegend`, `getData`, `generatePdf`, `generateCsv`, `generateImage`, `toggleTable`, `toggleSort`, `toggleTooltip` | `#svg`, `#legend`, `#tooltip-before`, `#tooltip-after`, `#watermark`, `#chart-background`, `#pattern` |||
452452
| `VueUiWaffle` | `VueUiWaffleDatasetItem[]` | `VueUiWaffleConfig` | `@selectLegend`, `getData`, `generatePdf`, `generateCsv`, `generateImage`, `toggleTable`, `toggleTooltip` | `#svg`, `#legend`, `#tooltip-before`, `#tooltip-after`, `#watermark`, `#pattern` |||
453453
| `VueUiWheel` | `VueUiWheelDataset` | `VueUiWheelConfig` | `generatePdf`, `generateImage` | `#svg`, `#watermark`, `#chart-background` |||

TestingArena/ArenaVueUiTreemap.vue

+23-7
Original file line numberDiff line numberDiff line change
@@ -10,28 +10,38 @@ const { local, build, vduiLocal, vduiBuild, toggleTable } = useArena()
1010
1111
const dataset = ref([
1212
{
13-
name: 'S1',
13+
name: 'Some datapoint',
1414
value: 100,
1515
children: [
1616
{
17-
name: 'S1 - C1',
17+
name: 'Some kind of child',
1818
value: 50
1919
},
2020
{
21-
name: 'S1 -C2',
21+
name: 'Some other child',
2222
value: 25
2323
},
2424
{
25-
name: 'S1 - C3',
25+
name: 'Yet another child with a long name',
2626
value: 12.5,
2727
children: [
2828
{
29-
name: 'S1 - C3 - CC1',
29+
name: 'Some nested child',
3030
value: 6
3131
},
3232
{
33-
name: 'S1 - C3 - CC2',
34-
value: 6.5
33+
name: 'Some other nested child with a very long name',
34+
value: 6.5,
35+
children: [
36+
{
37+
name: 'kiddo1',
38+
value: 6
39+
},
40+
{
41+
name: 'kiddo2',
42+
value: 6.5
43+
},
44+
]
3545
},
3646
]
3747
}
@@ -355,6 +365,12 @@ function selectDatapoint(datapoint) {
355365

356366
<template #VDUI-local>
357367
<LocalVueDataUi component="VueUiTreemap" :dataset="dataset" :config="isPropsToggled ? alternateConfig : config" :key="`VDUI-lodal_${step}`" @selectDatapoint="selectDatapoint" @selectLegend="selectLegend" ref="vduiLocal">
368+
<template #breadcrumb-label="{ crumb }">
369+
{{ crumb.label }}
370+
</template>
371+
<!-- <template #breadcrumb-arrow>
372+
373+
</template> -->
358374
<template #svg="{ svg }">
359375
<circle :cx="svg.width / 2" :cy="svg.height / 2" :r="30" fill="#42d392" />
360376
<text :x="svg.width / 2" :y="svg.height / 2" text-anchor="middle">#SVG</text>

documentation/installation.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ From the dataset you pass into the props, this component will produce the most a
157157
| `VueUiGauge` | `VueUiGaugeDataset` | `VueUiGaugeConfig` | `generatePdf`, `generateImage` | `#svg`, `#legend`, `#watermark`, `#chart-background`, `#pattern` |||
158158
| `VueUiHeatmap` | `VueUiHeatmapDatasetItem[]` | `VueUiHeatmapConfig` | `generatePdf`, `generateCsv`, `generateImage`, `toggleTable`, `toggleTooltip` | `#svg`, `#tooltip-before`, `#tooltip-after`, `#watermark`, `#chart-background` |||
159159
| `VueUiHistoryPlot` | `VueUiHistoryPlotDatasetItem[]` | `VueUiHistoryPlotConfig` | `@selectDatapoint`, `@selectLegend`, `getData`, `generatePdf`, `generateCsv`, `generateImage`, `toggleTable`, `toggleTooltip` | `#svg`, `#legend`, `#tooltip-before`, `#tooltip-after`, `#watermark`, `#chart-background` |||
160-
| `VueUiMolecule` | `VueUiMoleculeDatasetNode[]` | `VueUiMoleculeConfig` | `getData`, `generatePdf`, `generateCsv`, `generateImage`, `toggleTable`, `toggleLabels`, `toggleTooltip` | `#svg`, `#tooltip-before`, `#tooltip-after`, `#watermark`, `#chart-background` |||
160+
| `VueUiMolecule` | `VueUiMoleculeDatasetNode[]` | `VueUiMoleculeConfig` | `@selectNode`, `getData`, `generatePdf`, `generateCsv`, `generateImage`, `toggleTable`, `toggleLabels`, `toggleTooltip` | `#node`, `#svg`, `#tooltip-before`, `#tooltip-after`, `#watermark`, `#chart-background` |||
161161
| `VueUiMoodRadar` | `VueUiMoodRadarDataset` | `VueUiMoodRadarConfig` | `getData`, `generatePdf`, `generateCsv`, `generateImage`, `toggleTable` | `#svg`, `#legend`, `#watermark`, `#chart-background` |||
162162
| `VueUiNestedDonuts` | `VueUiNestedDonutsDatasetItem[]` | `VueUiNestedDonutsConfig` | `@selectDatapoint`, `@selectLegend`, `getData`, `generatePdf`, `generateCsv`, `generateImage`, `toggleTable`, `toggleLabels`, `toggleTooltip` | `#svg`, `#legend`, `#tooltip-before`, `#tooltip-after`, `#watermark`, `#chart-background` |||
163163
| `VueUiOnion` | `VueUiOnionDatasetItem[]` | `VueUiOnionConfig` | `@selectLegend`, `getData`, `generatePdf`, `generateCsv`, `generateImage`, `toggleTable`, `toggleTooltip` | `#svg`, `#legend`, `#tooltip-before`, `#tooltip-after`, `#watermark`, `#chart-background` |||
@@ -170,7 +170,7 @@ From the dataset you pass into the props, this component will produce the most a
170170
| `VueUiStripPlot` | `VueUiStripPlotDataset[]` | `VueUiStripPlotConfig` | `@selectDatapoint`, `getData`, `generatePdf`, `generateCsv`, `generateImage`, `toggleTable`, `toggleLabels`, `toggleTooltip` | `#svg`, `#legend`, `#tooltip-before`, `#tooltip-after`, `#watermark`, `#chart-background` |||
171171
| `VueUiThermometer` | `VueUiThermometerDataset` | `VueUiThermometerConfig` | `generatePdf`, `generateImage` | `#svg`, `#watermark`, `#chart-background` |||
172172
| `VueUiTiremarks` | `VueUiTiremarksDataset` | `VueUiTiremarksConfig` | `generatePdf`, `generateImage` | `#svg`, `#legend`, `#watermark`, `#chart-background` |||
173-
| `VueUiTreemap` | `VueUiTreemapDatasetItem[]` | `VueUiTreemapConfig` | `@selectLegend`, `@selectDatapoint`, `getData`, `generatePdf`, `generateCsv`, `generateImage`, `toggleTable`, `toggleTooltip` | `#svg`, `#rect`, `#legend`, `#tooltip-before`, `#tooltip-after`, `#watermark` |||
173+
| `VueUiTreemap` | `VueUiTreemapDatasetItem[]` | `VueUiTreemapConfig` | `@selectLegend`, `@selectDatapoint`, `getData`, `generatePdf`, `generateCsv`, `generateImage`, `toggleTable`, `toggleTooltip` | `#svg`, `#rect`, `#legend`, `#tooltip-before`, `#tooltip-after`, `#watermark`, `#breadcrumb-label`, `#breadcrumb-arrow` |||
174174
| `VueUiVerticalBar` | `VueUiVerticalBarDatasetItem[]` | `VueUiWheelConfig` | `@selectLegend`, `getData`, `generatePdf`, `generateCsv`, `generateImage`, `toggleTable`, `toggleSort`, `toggleTooltip` | `#svg`, `#legend`, `#tooltip-before`, `#tooltip-after`, `#watermark`, `#chart-background`, `#pattern` |||
175175
| `VueUiWaffle` | `VueUiWaffleDatasetItem[]` | `VueUiWaffleConfig` | `@selectLegend`, `getData`, `generatePdf`, `generateCsv`, `generateImage`, `toggleTable`, `toggleTooltip` | `#svg`, `#legend`, `#tooltip-before`, `#tooltip-after`, `#watermark`, `#pattern` |||
176176
| `VueUiWheel` | `VueUiWheelDataset` | `VueUiWheelConfig` | `generatePdf`, `generateImage` | `#svg`, `#watermark`, `#chart-background` |||

package.json

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

src/App.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ const components = ref([ //------|
120120
* Modify the index to display a component
121121
* [0] = VueUiXy
122122
*/
123-
const selectedComponent = ref(components.value[29]);
123+
const selectedComponent = ref(components.value[2]);
124124
125125
/**
126126
* Legacy testing arena where some non chart components can be tested

src/components/vue-ui-treemap.vue

+141-14
Original file line numberDiff line numberDiff line change
@@ -321,9 +321,7 @@ const viewBox = computed(() => {
321321
width: svg.value.vbWidth,
322322
height: svg.value.vbHeight,
323323
}
324-
})
325-
326-
const isZoom = ref(false);
324+
});
327325
328326
function findNodeById(id, nodes = immutableDataset.value) {
329327
for (const node of nodes) {
@@ -340,20 +338,72 @@ function findNodeById(id, nodes = immutableDataset.value) {
340338
return null;
341339
};
342340
341+
const drillStack = ref([]);
342+
343+
const isZoom = computed(() => drillStack.value.length > 0);
344+
343345
function zoom(rect) {
344-
if(isZoom.value) {
345-
emit('selectDatapoint', undefined);
346-
currentSet.value = immutableDataset.value;
347-
} else {
348-
emit('selectDatapoint', rect);
349-
if(!findNodeById(rect.parentId)) {
350-
return
346+
if (!rect) {
347+
currentSet.value = immutableDataset.value.slice()
348+
emit('selectDatapoint', undefined)
349+
drillStack.value = []
350+
return
351+
}
352+
353+
const node = findNodeById(rect.id)
354+
355+
if (node && node.children?.length) {
356+
drillStack.value.push(node.id)
357+
currentSet.value = node.children.slice()
358+
emit('selectDatapoint', rect)
359+
360+
} else if (rect.parentId) {
361+
drillStack.value.push(rect.parentId)
362+
const parent = findNodeById(rect.parentId)
363+
currentSet.value = parent.children.slice()
364+
emit('selectDatapoint', rect)
365+
366+
} else if (drillStack.value.length > 0) {
367+
drillStack.value.pop()
368+
const topId = drillStack.value[drillStack.value.length - 1]
369+
if (topId) {
370+
const upNode = findNodeById(topId)
371+
currentSet.value = upNode.children.slice()
372+
} else {
373+
currentSet.value = immutableDataset.value.slice()
374+
drillStack.value = []
375+
emit('selectDatapoint', undefined)
351376
}
352-
currentSet.value = [findNodeById(rect.parentId)];
353377
}
354-
isZoom.value = !isZoom.value;
355378
}
356379
380+
const breadcrumbs = computed(() => {
381+
const crumbs = [
382+
{ id: null, label: 'All' }
383+
];
384+
385+
if (drillStack.value.length > 0) {
386+
let node = findNodeById(drillStack.value[drillStack.value.length - 1]);
387+
const path = [];
388+
389+
while (node) {
390+
path.unshift(node);
391+
node = node.parentId
392+
? findNodeById(node.parentId)
393+
: null;
394+
}
395+
396+
for (const n of path) {
397+
crumbs.push({
398+
id: n.id,
399+
label: n.name,
400+
node: n
401+
});
402+
}
403+
}
404+
return crumbs;
405+
});
406+
357407
const selectedRect = ref(null);
358408
359409
const legendSet = computed(() => {
@@ -365,7 +415,7 @@ const legendSet = computed(() => {
365415
}
366416
})
367417
.sort((a,b) => b.value - a.value)
368-
.map((el, i) => {
418+
.map((el, _i) => {
369419
return {
370420
...el,
371421
proportion: el.value / immutableDataset.value.map(m => m.value).reduce((a, b) => a + b, 0),
@@ -656,6 +706,35 @@ defineExpose({
656706
</template>
657707
</UserOptions>
658708
709+
<nav class="vue-ui-treemap-breadcrumbs" v-if="breadcrumbs.length > 1" data-html2canvas-ignore>
710+
<span
711+
v-for="(crumb, i) in breadcrumbs"
712+
:key="crumb.id || 'root'"
713+
@click="i === breadcrumbs.length - 1 ? () => {} : zoom(crumb.node)"
714+
class="vue-ui-treemap-crumb"
715+
:data-last-crumb="i === breadcrumbs.length - 1"
716+
:style="{
717+
color: FINAL_CONFIG.style.chart.color
718+
}"
719+
>
720+
<span
721+
class="vue-ui-treemap-crumb-unit"
722+
>
723+
<span class="vue-ui-treemap-crumb-unit-label">
724+
<slot name="breadcrumb-label" v-bind="{crumb}">
725+
{{ crumb.label }}
726+
</slot>
727+
</span>
728+
729+
<span v-if="i < breadcrumbs.length - 1" class="vue-ui-treemap-crumb-unit-arrow">
730+
<slot name="breadcrumb-arrow">
731+
732+
</slot>
733+
</span>
734+
</span>
735+
</span>
736+
</nav>
737+
659738
<!-- CHART -->
660739
<svg
661740
ref="svgRef"
@@ -688,7 +767,7 @@ defineExpose({
688767
:rx="FINAL_CONFIG.style.chart.layout.rects.borderRadius"
689768
:stroke="selectedRect && selectedRect.id === rect.id ? FINAL_CONFIG.style.chart.layout.rects.selected.stroke : FINAL_CONFIG.style.chart.layout.rects.stroke"
690769
:stroke-width="selectedRect && selectedRect.id === rect.id ? FINAL_CONFIG.style.chart.layout.rects.selected.strokeWidth : FINAL_CONFIG.style.chart.layout.rects.strokeWidth"
691-
@click="zoom(rect)"
770+
@click.stop="zoom(rect)"
692771
@mouseenter="() => useTooltip({
693772
datapoint: rect,
694773
seriesIndex: i,
@@ -916,4 +995,52 @@ defineExpose({
916995
opacity: 1;
917996
}
918997
}
998+
999+
.vue-ui-treemap-breadcrumbs {
1000+
display: flex;
1001+
flex-wrap: nowrap;
1002+
overflow-x: auto;
1003+
padding: 0.5rem 1rem;
1004+
gap: 0.5rem;
1005+
scrollbar-width: none;
1006+
}
1007+
.vue-ui-treemap-breadcrumbs::-webkit-scrollbar {
1008+
display: none;
1009+
}
1010+
1011+
.vue-ui-treemap-crumb {
1012+
flex-shrink: 1;
1013+
min-width: 40px;
1014+
cursor: pointer;
1015+
}
1016+
1017+
.vue-ui-treemap-crumb-unit-label {
1018+
flex-shrink: 1;
1019+
min-width: 0;
1020+
white-space: nowrap;
1021+
overflow: hidden;
1022+
text-overflow: ellipsis;
1023+
cursor: pointer;
1024+
}
1025+
1026+
.vue-ui-treemap-crumb-unit-arrow {
1027+
min-width: 12px;
1028+
}
1029+
1030+
.vue-ui-treemap-crumb[data-last-crumb="true"] {
1031+
pointer-events: none;
1032+
cursor: default;
1033+
font-weight: bold;
1034+
}
1035+
1036+
.vue-ui-treemap-crumb:hover .vue-ui-treemap-crumb-unit-label {
1037+
text-decoration: underline;
1038+
}
1039+
1040+
.vue-ui-treemap-crumb-unit {
1041+
display: flex;
1042+
flex-direction: row;
1043+
align-items:center;
1044+
gap: 3px;
1045+
}
9191046
</style>

0 commit comments

Comments
 (0)