Skip to content

Commit

Permalink
VueUiDonut improve arcs
Browse files Browse the repository at this point in the history
  • Loading branch information
graphieros committed Jan 27, 2024
1 parent 584e9de commit 94aa031
Show file tree
Hide file tree
Showing 10 changed files with 185 additions and 124 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "vue-data-ui",
"private": false,
"version": "1.9.65",
"version": "1.9.66",
"type": "module",
"description": "A user-empowering data visualization Vue components library",
"keywords": [
Expand Down
27 changes: 14 additions & 13 deletions src/components/vue-ui-chestnut.vue
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,19 @@ defineExpose({
</div>
</div>
</foreignObject>
<!-- LABEL CONNECTOR -->
<g v-for="arc in openNut">
<path
v-if="isArcBigEnough(arc)"
:d="calcNutArrowPath(arc)"
:stroke="arc.color"
stroke-width="1"
stroke-linecap="round"
stroke-linejoin="round"
fill="none"
:class="chestnutConfig.style.chart.layout.nuts.selected.useMotion ? 'vue-ui-chestnut-animated' : ''"
/>
</g>
<circle
:cx="selectedNut.x2 + 24 + chestnutConfig.style.chart.layout.nuts.offsetX"
:cy="selectedNut.y1 + svg.branchSize / 2"
Expand Down Expand Up @@ -807,19 +820,7 @@ defineExpose({
@click="leaveNut"
:class="chestnutConfig.style.chart.layout.nuts.selected.useMotion ? 'vue-ui-chestnut-animated' : ''"
/>
<!-- LABEL CONNECTOR -->
<g v-for="arc in openNut">
<path
v-if="isArcBigEnough(arc)"
:d="calcNutArrowPath(arc)"
:stroke="arc.color"
stroke-width="1"
stroke-linecap="round"
stroke-linejoin="round"
fill="none"
:class="chestnutConfig.style.chart.layout.nuts.selected.useMotion ? 'vue-ui-chestnut-animated' : ''"
/>
</g>

<text
:x="selectedNut.x2 + 24 + chestnutConfig.style.chart.layout.nuts.offsetX"
:y="selectedNut.y1 + 8"
Expand Down
18 changes: 2 additions & 16 deletions src/components/vue-ui-donut.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe('<VueUiDonut />', () => {
cy.get(`[data-cy="donut-arc-${i}"]`)
.should('exist')
.then(($element) => {
const expectedColor = `${sortedDataset[i].color}CC`;
const expectedColor = fixture.config.style.chart.backgroundColor;
const expectedStrokeWidth = fixture.config.style.chart.layout.donut.strokeWidth;

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

cy.wrap($element)
.invoke('attr', 'stroke-width')
.should('eq', String(expectedStrokeWidth))
})
}

cy.get(`[data-cy="donut-gradient-hollow"]`).should('exist');

for (let i = 0; i < fixture.dataset.length; i += 1) {
cy.get(`[data-cy="donut-trap-${i}"]`)
.should('exist')
.then(($element) => {
const expectedStrokeWidth = fixture.config.style.chart.layout.donut.strokeWidth;

cy.wrap($element)
.invoke('attr', 'stroke-width')
.should('eq', String(expectedStrokeWidth))
.should('eq', "1")
})
}

Expand Down
21 changes: 9 additions & 12 deletions src/components/vue-ui-donut.vue
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ const legendConfig = computed(() => {
})
const currentDonut = computed(() => {
return makeDonut({ series: donutSet.value }, svg.value.width / 2, svg.value.height / 2, 100, 100)
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)
});
function isArcBigEnough(arc) {
Expand Down Expand Up @@ -399,7 +399,7 @@ defineExpose({
<g v-for="(arc, i) in currentDonut">
<path
v-if="isArcBigEnough(arc) && mutableConfig.dataLabels.show"
:d="calcNutArrowPath(arc, {x: svg.width / 2, y: svg.height / 2})"
:d="calcNutArrowPath(arc, {x: svg.width / 2, y: svg.height / 2}, 16, 16, false, false, defaultConfig.style.chart.layout.donut.strokeWidth)"
:stroke="arc.color"
stroke-width="1"
stroke-linecap="round"
Expand All @@ -412,19 +412,18 @@ defineExpose({
<path
v-for="(arc, i) in currentDonut"
:stroke="donutConfig.style.chart.backgroundColor"
:d="arc.path"
:stroke-width="defaultConfig.style.chart.layout.donut.strokeWidth"
:d="arc.arcSlice"
fill="#FFFFFF"
/>
<path
v-for="(arc, i) in currentDonut"
class="vue-ui-donut-arc-path"
:data-cy="`donut-arc-${i}`"
:d="arc.path"
:stroke="`${arc.color}CC`"
:d="arc.arcSlice"
:fill="`${arc.color}CC`"
:class="!defaultConfig.useBlurOnHover || [null, undefined].includes(selectedSerie) || selectedSerie === i ? '' : 'vue-ui-donut-blur'"
:stroke-width="defaultConfig.style.chart.layout.donut.strokeWidth"
fill="none"
:stroke="donutConfig.style.chart.backgroundColor"
:stroke-width="donutConfig.style.chart.layout.donut.borderWidth"
/>

<!-- HOLLOW -->
Expand All @@ -441,10 +440,8 @@ defineExpose({
<path
v-for="(arc, i) in currentDonut"
:data-cy="`donut-trap-${i}`"
:d="arc.path"
:stroke="selectedSerie === i ? 'rgba(0,0,0,0.1)' : 'transparent'"
:stroke-width="defaultConfig.style.chart.layout.donut.strokeWidth"
fill="none"
:d="arc.arcSlice"
:fill="selectedSerie === i ? 'rgba(0,0,0,0.1)' : 'transparent'"
@mouseenter="useTooltip(arc, i, true)"
@mouseleave="isTooltip = false; selectedSerie = null"
@click="segregate(i)"
Expand Down
3 changes: 2 additions & 1 deletion src/default_configs.json
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,8 @@
}
},
"donut": {
"strokeWidth": 64
"strokeWidth": 64,
"borderWidth": 1
}
},
"legend": {
Expand Down
110 changes: 89 additions & 21 deletions src/lib.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export function makeDonut(item, cx, cy, rx, ry) {
export function makeDonut(item, cx, cy, rx, ry, piProportion = 1.99999, piMult = 2, arcAmpl = 1.45, degrees = 360, rotation = 105.25, size = 0) {
let { series } = item;
if (!series || item.base === 0)
return {
Expand All @@ -19,17 +19,31 @@ export function makeDonut(item, cx, cy, rx, ry) {
let acc = 0;
for (let i = 0; i < series.length; i += 1) {
let proportion = series[i].value / sum;
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
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
// midProportion & midRatio are used to find the midpoint of the arc to display markers
const midProportion = series[i].value / 2 / sum;
const midRatio = midProportion * (Math.PI * 2);
const midRatio = midProportion * (Math.PI * piMult);
const { startX, startY, endX, endY, path } = createArc(
[cx, cy],
[rx, ry],
[acc, ratio],
110
rotation,
degrees,
piMult
);

const inner = createArc(
[cx, cy],
[rx - size, ry - size],
[acc, ratio],
rotation,
degrees,
piMult,
true
)

ratios.push({
arcSlice: `${path} L ${inner.startX} ${inner.startY} ${inner.path} L ${startX} ${startY}`,
cx,
cy,
...series[i],
Expand All @@ -42,9 +56,11 @@ export function makeDonut(item, cx, cy, rx, ry) {
endY,
center: createArc(
[cx, cy],
[rx * 1.45, ry * 1.45],
[rx * arcAmpl, ry * arcAmpl],
[acc, midRatio],
110
rotation,
degrees,
piMult
), // center of the arc, to display the marker. rx & ry are larger to be displayed with a slight offset
});
acc += ratio;
Expand All @@ -67,8 +83,8 @@ export function rotateMatrix(x) {
];
}

export function createArc([cx, cy], [rx, ry], [position, ratio], phi) {
ratio = ratio % (2 * Math.PI);
export function createArc([cx, cy], [rx, ry], [position, ratio], phi, degrees = 360, piMult = 2, reverse = false) {
ratio = ratio % (piMult * Math.PI);
const rotMatrix = rotateMatrix(phi);
const [sX, sY] = addVector(
matrixTimes(rotMatrix, [
Expand All @@ -85,20 +101,20 @@ export function createArc([cx, cy], [rx, ry], [position, ratio], phi) {
[cx, cy]
);
const fA = ratio > Math.PI ? 1 : 0;
const fS = ratio > 0 ? 1 : 0;
const fS = ratio > 0 ? reverse ? 0 : 1 : reverse ? 1 : 0;
return {
startX: sX,
startY: sY,
endX: eX,
endY: eY,
path: `M${sX} ${sY} A ${[
startX: reverse ? eX : sX,
startY: reverse ? eY : sY,
endX: reverse ? sX : eX,
endY: reverse ? sY : eY,
path: `M${reverse ? eX : sX} ${reverse ? eY : sY} A ${[
rx,
ry,
(phi / (2 * Math.PI)) * 360,
(phi / (piMult * Math.PI)) * degrees,
fA,
fS,
eX,
eY,
reverse ? sX : eX,
reverse ? sY : eY,
].join(" ")}`,
};
}
Expand Down Expand Up @@ -629,13 +645,65 @@ export function calcMarkerOffsetY(arc, yOffsetTop = 16, yOffsetBottom = 16) {
}
}

export function calcNutArrowPath(arc, center = false, yOffsetTop = 16, yOffsetBottom = 16, toCenter = false, hideStart = false) {
export function offsetFromCenterPoint({
initX,
initY,
offset,
centerX,
centerY
}) {
const angle = Math.atan2(initY - centerY, initX - centerX);
return {
x: initX + offset * Math.cos(angle),
y: initY + offset * Math.sin(angle)
}
}

export function findArcMidpoint(pathElement) {
const el = document.createElementNS("http://www.w3.org/2000/svg", 'path')
el.setAttribute('d', pathElement)

const length = el.getTotalLength();
let start = 0;
let end = length;
let midpointParameter = length / 2;

const epsilon = 0.01;
while (end - start > epsilon) {
const mid = (start + end) / 2;
const midPoint = el.getPointAtLength(mid);
const midLength = midPoint.x;

if (Math.abs(midLength - midpointParameter) < epsilon) {
midpointParameter = mid;
break;
} else if (midLength < midpointParameter) {
start = mid;
} else {
end = mid;
}
}
const { x, y } = el.getPointAtLength(midpointParameter);
return { x, y };
}

export function calcNutArrowPath(arc, center = false, yOffsetTop = 16, yOffsetBottom = 16, toCenter = false, hideStart = false, arcSize = 0) {
const { x, y } = findArcMidpoint(arc.path)

const { x:endX, y:endY } = offsetFromCenterPoint({
initX: x,
initY: y,
offset: arcSize,
centerX: center ? center.x : 0,
centerY: center ? center.y : 0
})

const start = `${calcMarkerOffsetX(arc).x},${calcMarkerOffsetY(arc, yOffsetTop, yOffsetBottom) - 4} `;
const end = ` ${center ? center.x : arc.center.endX},${center ? center.y : arc.center.endY}`;
const end = ` ${center ? center.x : endX},${center ? center.y : endY}`;
let mid = "";
if (arc.center.endX > arc.cx) {
if (x > arc.cx) {
mid = `${calcMarkerOffsetX(arc).x - 12},${calcMarkerOffsetY(arc, yOffsetTop, yOffsetBottom) - 4}`;
} else if (arc.center.endX < arc.cx) {
} else if (x < arc.cx) {
mid = `${calcMarkerOffsetX(arc).x + 12},${calcMarkerOffsetY(arc, yOffsetTop, yOffsetBottom) - 4}`;
} else {
mid = `${calcMarkerOffsetX(arc).x + 12},${calcMarkerOffsetY(arc, yOffsetTop, yOffsetBottom) - 4}`;
Expand Down
Loading

0 comments on commit 94aa031

Please sign in to comment.