Skip to content

Commit 94aa031

Browse files
committed
VueUiDonut improve arcs
1 parent 584e9de commit 94aa031

File tree

10 files changed

+185
-124
lines changed

10 files changed

+185
-124
lines changed
106 KB
Loading

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "vue-data-ui",
33
"private": false,
4-
"version": "1.9.65",
4+
"version": "1.9.66",
55
"type": "module",
66
"description": "A user-empowering data visualization Vue components library",
77
"keywords": [

src/components/vue-ui-chestnut.vue

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,19 @@ defineExpose({
765765
</div>
766766
</div>
767767
</foreignObject>
768+
<!-- LABEL CONNECTOR -->
769+
<g v-for="arc in openNut">
770+
<path
771+
v-if="isArcBigEnough(arc)"
772+
:d="calcNutArrowPath(arc)"
773+
:stroke="arc.color"
774+
stroke-width="1"
775+
stroke-linecap="round"
776+
stroke-linejoin="round"
777+
fill="none"
778+
:class="chestnutConfig.style.chart.layout.nuts.selected.useMotion ? 'vue-ui-chestnut-animated' : ''"
779+
/>
780+
</g>
768781
<circle
769782
:cx="selectedNut.x2 + 24 + chestnutConfig.style.chart.layout.nuts.offsetX"
770783
:cy="selectedNut.y1 + svg.branchSize / 2"
@@ -807,19 +820,7 @@ defineExpose({
807820
@click="leaveNut"
808821
:class="chestnutConfig.style.chart.layout.nuts.selected.useMotion ? 'vue-ui-chestnut-animated' : ''"
809822
/>
810-
<!-- LABEL CONNECTOR -->
811-
<g v-for="arc in openNut">
812-
<path
813-
v-if="isArcBigEnough(arc)"
814-
:d="calcNutArrowPath(arc)"
815-
:stroke="arc.color"
816-
stroke-width="1"
817-
stroke-linecap="round"
818-
stroke-linejoin="round"
819-
fill="none"
820-
:class="chestnutConfig.style.chart.layout.nuts.selected.useMotion ? 'vue-ui-chestnut-animated' : ''"
821-
/>
822-
</g>
823+
823824
<text
824825
:x="selectedNut.x2 + 24 + chestnutConfig.style.chart.layout.nuts.offsetX"
825826
:y="selectedNut.y1 + 8"

src/components/vue-ui-donut.cy.js

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ describe('<VueUiDonut />', () => {
7676
cy.get(`[data-cy="donut-arc-${i}"]`)
7777
.should('exist')
7878
.then(($element) => {
79-
const expectedColor = `${sortedDataset[i].color}CC`;
79+
const expectedColor = fixture.config.style.chart.backgroundColor;
8080
const expectedStrokeWidth = fixture.config.style.chart.layout.donut.strokeWidth;
8181

8282
cy.wrap($element)
@@ -85,21 +85,7 @@ describe('<VueUiDonut />', () => {
8585

8686
cy.wrap($element)
8787
.invoke('attr', 'stroke-width')
88-
.should('eq', String(expectedStrokeWidth))
89-
})
90-
}
91-
92-
cy.get(`[data-cy="donut-gradient-hollow"]`).should('exist');
93-
94-
for (let i = 0; i < fixture.dataset.length; i += 1) {
95-
cy.get(`[data-cy="donut-trap-${i}"]`)
96-
.should('exist')
97-
.then(($element) => {
98-
const expectedStrokeWidth = fixture.config.style.chart.layout.donut.strokeWidth;
99-
100-
cy.wrap($element)
101-
.invoke('attr', 'stroke-width')
102-
.should('eq', String(expectedStrokeWidth))
88+
.should('eq', "1")
10389
})
10490
}
10591

src/components/vue-ui-donut.vue

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ const legendConfig = computed(() => {
149149
})
150150
151151
const currentDonut = computed(() => {
152-
return makeDonut({ series: donutSet.value }, svg.value.width / 2, svg.value.height / 2, 100, 100)
152+
return makeDonut({ series: donutSet.value }, svg.value.width / 2, svg.value.height / 2, 130, 130, 1.99999, 2, 1, 360, 105.25, defaultConfig.value.style.chart.layout.donut.strokeWidth)
153153
});
154154
155155
function isArcBigEnough(arc) {
@@ -399,7 +399,7 @@ defineExpose({
399399
<g v-for="(arc, i) in currentDonut">
400400
<path
401401
v-if="isArcBigEnough(arc) && mutableConfig.dataLabels.show"
402-
:d="calcNutArrowPath(arc, {x: svg.width / 2, y: svg.height / 2})"
402+
:d="calcNutArrowPath(arc, {x: svg.width / 2, y: svg.height / 2}, 16, 16, false, false, defaultConfig.style.chart.layout.donut.strokeWidth)"
403403
:stroke="arc.color"
404404
stroke-width="1"
405405
stroke-linecap="round"
@@ -412,19 +412,18 @@ defineExpose({
412412
<path
413413
v-for="(arc, i) in currentDonut"
414414
:stroke="donutConfig.style.chart.backgroundColor"
415-
:d="arc.path"
416-
:stroke-width="defaultConfig.style.chart.layout.donut.strokeWidth"
415+
:d="arc.arcSlice"
417416
fill="#FFFFFF"
418417
/>
419418
<path
420419
v-for="(arc, i) in currentDonut"
421420
class="vue-ui-donut-arc-path"
422421
:data-cy="`donut-arc-${i}`"
423-
:d="arc.path"
424-
:stroke="`${arc.color}CC`"
422+
:d="arc.arcSlice"
423+
:fill="`${arc.color}CC`"
425424
:class="!defaultConfig.useBlurOnHover || [null, undefined].includes(selectedSerie) || selectedSerie === i ? '' : 'vue-ui-donut-blur'"
426-
:stroke-width="defaultConfig.style.chart.layout.donut.strokeWidth"
427-
fill="none"
425+
:stroke="donutConfig.style.chart.backgroundColor"
426+
:stroke-width="donutConfig.style.chart.layout.donut.borderWidth"
428427
/>
429428

430429
<!-- HOLLOW -->
@@ -441,10 +440,8 @@ defineExpose({
441440
<path
442441
v-for="(arc, i) in currentDonut"
443442
:data-cy="`donut-trap-${i}`"
444-
:d="arc.path"
445-
:stroke="selectedSerie === i ? 'rgba(0,0,0,0.1)' : 'transparent'"
446-
:stroke-width="defaultConfig.style.chart.layout.donut.strokeWidth"
447-
fill="none"
443+
:d="arc.arcSlice"
444+
:fill="selectedSerie === i ? 'rgba(0,0,0,0.1)' : 'transparent'"
448445
@mouseenter="useTooltip(arc, i, true)"
449446
@mouseleave="isTooltip = false; selectedSerie = null"
450447
@click="segregate(i)"

src/default_configs.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,8 @@
382382
}
383383
},
384384
"donut": {
385-
"strokeWidth": 64
385+
"strokeWidth": 64,
386+
"borderWidth": 1
386387
}
387388
},
388389
"legend": {

src/lib.js

Lines changed: 89 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export function makeDonut(item, cx, cy, rx, ry) {
1+
export function makeDonut(item, cx, cy, rx, ry, piProportion = 1.99999, piMult = 2, arcAmpl = 1.45, degrees = 360, rotation = 105.25, size = 0) {
22
let { series } = item;
33
if (!series || item.base === 0)
44
return {
@@ -19,17 +19,31 @@ export function makeDonut(item, cx, cy, rx, ry) {
1919
let acc = 0;
2020
for (let i = 0; i < series.length; i += 1) {
2121
let proportion = series[i].value / sum;
22-
const ratio = proportion * (Math.PI * 1.9999); // (Math.PI * 2) fails to display a donut with only one value > 0 as it goes full circle again
22+
const ratio = proportion * (Math.PI * piProportion); // (Math.PI * 2) fails to display a donut with only one value > 0 as it goes full circle again
2323
// midProportion & midRatio are used to find the midpoint of the arc to display markers
2424
const midProportion = series[i].value / 2 / sum;
25-
const midRatio = midProportion * (Math.PI * 2);
25+
const midRatio = midProportion * (Math.PI * piMult);
2626
const { startX, startY, endX, endY, path } = createArc(
2727
[cx, cy],
2828
[rx, ry],
2929
[acc, ratio],
30-
110
30+
rotation,
31+
degrees,
32+
piMult
3133
);
34+
35+
const inner = createArc(
36+
[cx, cy],
37+
[rx - size, ry - size],
38+
[acc, ratio],
39+
rotation,
40+
degrees,
41+
piMult,
42+
true
43+
)
44+
3245
ratios.push({
46+
arcSlice: `${path} L ${inner.startX} ${inner.startY} ${inner.path} L ${startX} ${startY}`,
3347
cx,
3448
cy,
3549
...series[i],
@@ -42,9 +56,11 @@ export function makeDonut(item, cx, cy, rx, ry) {
4256
endY,
4357
center: createArc(
4458
[cx, cy],
45-
[rx * 1.45, ry * 1.45],
59+
[rx * arcAmpl, ry * arcAmpl],
4660
[acc, midRatio],
47-
110
61+
rotation,
62+
degrees,
63+
piMult
4864
), // center of the arc, to display the marker. rx & ry are larger to be displayed with a slight offset
4965
});
5066
acc += ratio;
@@ -67,8 +83,8 @@ export function rotateMatrix(x) {
6783
];
6884
}
6985

70-
export function createArc([cx, cy], [rx, ry], [position, ratio], phi) {
71-
ratio = ratio % (2 * Math.PI);
86+
export function createArc([cx, cy], [rx, ry], [position, ratio], phi, degrees = 360, piMult = 2, reverse = false) {
87+
ratio = ratio % (piMult * Math.PI);
7288
const rotMatrix = rotateMatrix(phi);
7389
const [sX, sY] = addVector(
7490
matrixTimes(rotMatrix, [
@@ -85,20 +101,20 @@ export function createArc([cx, cy], [rx, ry], [position, ratio], phi) {
85101
[cx, cy]
86102
);
87103
const fA = ratio > Math.PI ? 1 : 0;
88-
const fS = ratio > 0 ? 1 : 0;
104+
const fS = ratio > 0 ? reverse ? 0 : 1 : reverse ? 1 : 0;
89105
return {
90-
startX: sX,
91-
startY: sY,
92-
endX: eX,
93-
endY: eY,
94-
path: `M${sX} ${sY} A ${[
106+
startX: reverse ? eX : sX,
107+
startY: reverse ? eY : sY,
108+
endX: reverse ? sX : eX,
109+
endY: reverse ? sY : eY,
110+
path: `M${reverse ? eX : sX} ${reverse ? eY : sY} A ${[
95111
rx,
96112
ry,
97-
(phi / (2 * Math.PI)) * 360,
113+
(phi / (piMult * Math.PI)) * degrees,
98114
fA,
99115
fS,
100-
eX,
101-
eY,
116+
reverse ? sX : eX,
117+
reverse ? sY : eY,
102118
].join(" ")}`,
103119
};
104120
}
@@ -629,13 +645,65 @@ export function calcMarkerOffsetY(arc, yOffsetTop = 16, yOffsetBottom = 16) {
629645
}
630646
}
631647

632-
export function calcNutArrowPath(arc, center = false, yOffsetTop = 16, yOffsetBottom = 16, toCenter = false, hideStart = false) {
648+
export function offsetFromCenterPoint({
649+
initX,
650+
initY,
651+
offset,
652+
centerX,
653+
centerY
654+
}) {
655+
const angle = Math.atan2(initY - centerY, initX - centerX);
656+
return {
657+
x: initX + offset * Math.cos(angle),
658+
y: initY + offset * Math.sin(angle)
659+
}
660+
}
661+
662+
export function findArcMidpoint(pathElement) {
663+
const el = document.createElementNS("http://www.w3.org/2000/svg", 'path')
664+
el.setAttribute('d', pathElement)
665+
666+
const length = el.getTotalLength();
667+
let start = 0;
668+
let end = length;
669+
let midpointParameter = length / 2;
670+
671+
const epsilon = 0.01;
672+
while (end - start > epsilon) {
673+
const mid = (start + end) / 2;
674+
const midPoint = el.getPointAtLength(mid);
675+
const midLength = midPoint.x;
676+
677+
if (Math.abs(midLength - midpointParameter) < epsilon) {
678+
midpointParameter = mid;
679+
break;
680+
} else if (midLength < midpointParameter) {
681+
start = mid;
682+
} else {
683+
end = mid;
684+
}
685+
}
686+
const { x, y } = el.getPointAtLength(midpointParameter);
687+
return { x, y };
688+
}
689+
690+
export function calcNutArrowPath(arc, center = false, yOffsetTop = 16, yOffsetBottom = 16, toCenter = false, hideStart = false, arcSize = 0) {
691+
const { x, y } = findArcMidpoint(arc.path)
692+
693+
const { x:endX, y:endY } = offsetFromCenterPoint({
694+
initX: x,
695+
initY: y,
696+
offset: arcSize,
697+
centerX: center ? center.x : 0,
698+
centerY: center ? center.y : 0
699+
})
700+
633701
const start = `${calcMarkerOffsetX(arc).x},${calcMarkerOffsetY(arc, yOffsetTop, yOffsetBottom) - 4} `;
634-
const end = ` ${center ? center.x : arc.center.endX},${center ? center.y : arc.center.endY}`;
702+
const end = ` ${center ? center.x : endX},${center ? center.y : endY}`;
635703
let mid = "";
636-
if (arc.center.endX > arc.cx) {
704+
if (x > arc.cx) {
637705
mid = `${calcMarkerOffsetX(arc).x - 12},${calcMarkerOffsetY(arc, yOffsetTop, yOffsetBottom) - 4}`;
638-
} else if (arc.center.endX < arc.cx) {
706+
} else if (x < arc.cx) {
639707
mid = `${calcMarkerOffsetX(arc).x + 12},${calcMarkerOffsetY(arc, yOffsetTop, yOffsetBottom) - 4}`;
640708
} else {
641709
mid = `${calcMarkerOffsetX(arc).x + 12},${calcMarkerOffsetY(arc, yOffsetTop, yOffsetBottom) - 4}`;

0 commit comments

Comments
 (0)