Skip to content

Commit 88d4d45

Browse files
committed
Improvement - VueUiWheel - Improve 3d options
1 parent 18caea9 commit 88d4d45

File tree

1 file changed

+141
-95
lines changed

1 file changed

+141
-95
lines changed

src/components/vue-ui-wheel.vue

Lines changed: 141 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
createUid,
77
dataLabel,
88
error,
9+
lightenHexColor,
910
makeDonut,
1011
objectIsEmpty,
1112
shiftHue,
@@ -246,8 +247,8 @@ function buildTicks3D({
246247
cx, cy, radius, innerRatio = 0.8,
247248
count = 120,
248249
startDeg = 0,
249-
axDeg = 50, // tilt angle (bigger = more perspective)
250-
f = 520, // focal length (smaller = stronger perspective)
250+
axDeg = 50,
251+
f = 520,
251252
baseStroke = 5,
252253
activeColor,
253254
inactiveColor,
@@ -260,21 +261,17 @@ function buildTicks3D({
260261
for (let i = 0; i < count; i += 1) {
261262
const a = ((i / count) * 360 + startDeg) * Math.PI / 180;
262263
263-
// Points on the circle plane Z=0
264264
const xo = cx + outerR * Math.cos(a);
265265
const yo = cy + outerR * Math.sin(a);
266266
const xi = cx + innerR * Math.cos(a);
267267
const yi = cy + innerR * Math.sin(a);
268268
269-
// Move to local ring coords around (0,0) for rotation
270269
const po = [xo - cx, yo - cy, 0];
271270
const pi = [xi - cx, yi - cy, 0];
272271
273-
// Rotate the ring in 3D
274272
const [rxo, ryo, rzo] = rotateX(po, ax);
275273
const [rxi, ryi, rzi] = rotateX(pi, ax);
276274
277-
// Project to 2D
278275
const [pxo, pyo, , so] = perspectiveProject([rxo, ryo, rzo], f);
279276
const [pxi, pyi, , si] = perspectiveProject([rxi, ryi, rzi], f);
280277
@@ -349,22 +346,27 @@ function buildCirclePath3D({ cx, cy, r, count = 180, startDeg = -90, axDeg = 50,
349346
return { d, avgScale, pts };
350347
}
351348
352-
353349
const vb3D = computed(() => {
354350
if (FINAL_CONFIG.value.layout !== '3d') return null;
355351
356352
const f = Math.min(svg.value.width, svg.value.height) * 1.45;
357353
const ax = FINAL_CONFIG.value.style.chart.layout.wheel.tiltAngle3d;
358-
const r = wheel.value.radius;
359354
360-
const { pts } = buildCirclePath3D({
355+
356+
const outerR = wheel.value.radius;
357+
const { pts, avgScale } = (() => {
358+
const r = outerR;
359+
const axDeg = ax;
360+
const { d, avgScale, pts } = buildCirclePath3D({
361361
cx: wheel.value.centerX,
362362
cy: wheel.value.centerY,
363363
r,
364364
startDeg: -90,
365-
axDeg: ax,
365+
axDeg,
366366
f
367-
});
367+
});
368+
return { pts, avgScale };
369+
})();
368370
369371
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
370372
for (const [x, y] of pts) {
@@ -373,11 +375,23 @@ const vb3D = computed(() => {
373375
if (x > maxX) maxX = x;
374376
if (y > maxY) maxY = y;
375377
}
378+
379+
const tickStroke =
380+
(FINAL_CONFIG.value.style.chart.layout.wheel.ticks.strokeWidth / 360) *
381+
Math.min(svg.value.width, svg.value.height);
382+
383+
const innerStroke = FINAL_CONFIG.value.style.chart.layout.innerCircle.strokeWidth || 0;
384+
385+
const strokePad = 0.5 * Math.max(tickStroke, innerStroke * (avgScale || 1));
386+
const depthPad = Math.max(0, Number(FINAL_CONFIG.value.style.chart.layout.wheel.ticks.depth3d) || 0);
387+
388+
const pad = strokePad;
389+
376390
return {
377-
x: minX,
378-
y: minY,
379-
w: (maxX - minX),
380-
h: (maxY - minY),
391+
x: minX - pad,
392+
y: (minY - depthPad) - pad,
393+
w: (maxX - minX) + 2 * pad,
394+
h: (maxY - (minY - depthPad)) + 2 * pad
381395
};
382396
});
383397
@@ -422,46 +436,41 @@ function buildArcTicks3D({
422436
f = 600,
423437
activeColor,
424438
inactiveColor,
425-
getActive
439+
getActive,
440+
Y = 0
426441
}) {
427442
const ax = (axDeg * Math.PI) / 180;
428443
const outerR = radius;
429444
const innerR = radius * innerRatio;
430445
const step = (2 * Math.PI) / count;
431-
432446
const wedges = [];
433447
434448
for (let i = 0; i < count; i += 1) {
435449
const a0 = ((startDeg * Math.PI) / 180) + step * i;
436-
const a1 = a0 + step * Math.min(1, FINAL_CONFIG.value.style.chart.layout.wheel.ticks.spacingRatio3d); // 1 = no spacing
437-
438-
const o0 = projectRingPoint({ cx, cy, r: outerR, aRad: a0, ax, f });
439-
const o1 = projectRingPoint({ cx, cy, r: outerR, aRad: a1, ax, f });
440-
const i1 = projectRingPoint({ cx, cy, r: innerR, aRad: a1, ax, f });
441-
const i0 = projectRingPoint({ cx, cy, r: innerR, aRad: a0, ax, f });
442-
450+
const a1 = a0 + step * Math.min(1, FINAL_CONFIG.value.style.chart.layout.wheel.ticks.spacingRatio3d);
451+
const o0 = projectRingPoint({ cx, cy: cy + Y, r: outerR, aRad: a0, ax, f });
452+
const o1 = projectRingPoint({ cx, cy: cy + Y, r: outerR, aRad: a1, ax, f });
453+
const i1 = projectRingPoint({ cx, cy: cy + Y, r: innerR, aRad: a1, ax, f });
454+
const i0 = projectRingPoint({ cx, cy: cy + Y, r: innerR, aRad: a0, ax, f });
443455
const zAvg = (o0.z + o1.z + i0.z + i1.z) / 4;
444-
445456
const isActive = getActive ? getActive(i) : true;
446-
447457
const base = isActive
448-
? (FINAL_CONFIG.value.style.chart.layout.wheel.ticks.gradient.show
449-
? shiftHue(
450-
FINAL_CONFIG.value.style.chart.layout.wheel.ticks.activeColor,
451-
(i * (100 / count)) / 100 *
452-
(FINAL_CONFIG.value.style.chart.layout.wheel.ticks.gradient.shiftHueIntensity / 100)
453-
)
454-
: activeColor)
455-
: inactiveColor;
458+
? (FINAL_CONFIG.value.style.chart.layout.wheel.ticks.gradient.show
459+
? shiftHue(
460+
FINAL_CONFIG.value.style.chart.layout.wheel.ticks.activeColor,
461+
(i * (100 / count)) / 100 *
462+
(FINAL_CONFIG.value.style.chart.layout.wheel.ticks.gradient.shiftHueIntensity / 100)
463+
)
464+
: activeColor)
465+
: inactiveColor;
456466
457467
const depth = (() => {
458-
const amp = outerR * Math.sin(ax) || 1;
459-
return (zAvg - (-amp)) / (2 * amp);
468+
const amp = outerR * Math.sin(ax) || 1;
469+
return (zAvg - (-amp)) / (2 * amp);
460470
})();
461-
const fill = shadeColor(base, depth);
462471
472+
const fill = shadeColor(base, depth);
463473
const d = `M ${o0.x} ${o0.y} L ${o1.x} ${o1.y} L ${i1.x} ${i1.y} L ${i0.x} ${i0.y} Z`;
464-
465474
wedges.push({ i, d, fill, z: zAvg });
466475
}
467476
@@ -473,7 +482,7 @@ const arcTicks3D = computed(() => {
473482
if (FINAL_CONFIG.value.layout !== '3d') return null;
474483
475484
const count = tickAmount.value;
476-
return buildArcTicks3D({
485+
return (Y) => buildArcTicks3D({
477486
cx: wheel.value.centerX,
478487
cy: wheel.value.centerY,
479488
radius: wheel.value.radius,
@@ -484,7 +493,8 @@ const arcTicks3D = computed(() => {
484493
f: Math.min(svg.value.width, svg.value.height) * 1.45,
485494
activeColor: FINAL_CONFIG.value.style.chart.layout.wheel.ticks.activeColor,
486495
inactiveColor: FINAL_CONFIG.value.style.chart.layout.wheel.ticks.inactiveColor,
487-
getActive: (i) => activeValue.value > (i * (100 / count))
496+
getActive: (i) => activeValue.value > (i * (100 / count)),
497+
Y
488498
});
489499
});
490500
@@ -509,9 +519,12 @@ function useAnimation(targetValue) {
509519
510520
const tickAmount = computed(() => {
511521
if (FINAL_CONFIG.value.debug && FINAL_CONFIG.value.style.chart.layout.wheel.ticks.quantity < 12) {
512-
console.warn(`VueUiWheel - The minimal number of ticks is 12`)
522+
console.warn(`VueUiWheel - The min number of ticks is 12`);
523+
}
524+
if (FINAL_CONFIG.value.debug && FINAL_CONFIG.value.style.chart.layout.wheel.ticks.quantity > 200) {
525+
console.warn(`VueUiWheel - The max number of ticks is 200`);
513526
}
514-
return Math.max(12, FINAL_CONFIG.value.style.chart.layout.wheel.ticks.quantity);
527+
return Math.max(12, Math.min(FINAL_CONFIG.value.style.chart.layout.wheel.ticks.quantity, 200));
515528
});
516529
517530
const percentageToTickAmount = computed(() => 100 / tickAmount.value);
@@ -528,9 +541,9 @@ const ticks = computed(() => {
528541
x2,
529542
y2,
530543
color: FINAL_CONFIG.value.style.chart.layout.wheel.ticks.gradient.show ? shiftHue(color, (i * percentageToTickAmount.value) / tickAmount.value * (FINAL_CONFIG.value.style.chart.layout.wheel.ticks.gradient.shiftHueIntensity / 100)) : color
531-
})
544+
});
532545
}
533-
return tickArray
546+
return tickArray;
534547
});
535548
536549
const arcTicks = computed(() => {
@@ -570,16 +583,20 @@ async function getImage({ scale = 2} = {}) {
570583
}
571584
572585
const tickWidthStart = computed(() => {
573-
return FINAL_CONFIG.value.style.chart.layout.wheel.ticks.strokeWidth * 2
586+
return FINAL_CONFIG.value.style.chart.layout.wheel.ticks.strokeWidth * 2;
574587
});
575588
576589
const tickWidthMid = computed(() => {
577-
return FINAL_CONFIG.value.style.chart.layout.wheel.ticks.strokeWidth * 2 * 0.75
578-
})
590+
return FINAL_CONFIG.value.style.chart.layout.wheel.ticks.strokeWidth * 2 * 0.75;
591+
});
579592
580593
const tickWidthEnd = computed(() => {
581-
return FINAL_CONFIG.value.style.chart.layout.wheel.ticks.strokeWidth
582-
})
594+
return FINAL_CONFIG.value.style.chart.layout.wheel.ticks.strokeWidth;
595+
});
596+
597+
const depth3d = computed(() => {
598+
return Math.max(1, Math.min(20, FINAL_CONFIG.value.style.chart.layout.wheel.ticks.depth3d));
599+
});
583600
584601
defineExpose({
585602
getImage,
@@ -703,12 +720,47 @@ defineExpose({
703720
<slot name="chart-background"/>
704721
</foreignObject>
705722

723+
<!-- HOLLOW 3D -->
724+
<path
725+
class="vue-ui-wheel-inner-circle"
726+
v-if="FINAL_CONFIG.layout === '3d' && inner3D"
727+
:d="inner3D.d"
728+
:stroke="FINAL_CONFIG.style.chart.layout.innerCircle.stroke"
729+
:stroke-width="FINAL_CONFIG.style.chart.layout.innerCircle.strokeWidth"
730+
fill="none"
731+
/>
732+
733+
<!-- HOLLOW 2D -->
734+
<circle
735+
data-cy="inner-circle"
736+
class="vue-ui-wheel-inner-circle"
737+
v-else-if="FINAL_CONFIG.style.chart.layout.innerCircle.show"
738+
:cx="wheel.centerX"
739+
:cy="wheel.centerY"
740+
:r="Math.max(0, wheel.radius * FINAL_CONFIG.style.chart.layout.innerCircle.radiusRatio * 0.8)"
741+
:stroke="FINAL_CONFIG.style.chart.layout.innerCircle.stroke"
742+
:stroke-width="FINAL_CONFIG.style.chart.layout.innerCircle.strokeWidth"
743+
fill="none"
744+
/>
745+
706746
<template v-if="FINAL_CONFIG.layout === '3d'">
707747
<g v-if="FINAL_CONFIG.style.chart.layout.wheel.ticks.type === 'classic'">
748+
<g v-for="n in depth3d">
749+
<line
750+
v-for="t in ticks3D || []"
751+
:key="t.i"
752+
:x1="t.x1" :y1="t.y1 - n" :x2="t.x2" :y2="t.y2 - n"
753+
:stroke="lightenHexColor(t.color, 0.25 * n / 5)"
754+
:stroke-width="(FINAL_CONFIG.style.chart.layout.wheel.ticks.strokeWidth / 360) * Math.min(svg.width, svg.height)"
755+
:stroke-linecap="FINAL_CONFIG.style.chart.layout.wheel.ticks.rounded ? 'round' : 'butt'"
756+
stroke-linecap="round"
757+
:class="{ 'vue-ui-wheel-tick' : true, 'vue-ui-tick-animated': FINAL_CONFIG.style.chart.animation.use && (t.i * percentageToTickAmount) <= activeValue }"
758+
/>
759+
</g>
708760
<line
709761
v-for="t in ticks3D || []"
710762
:key="t.i"
711-
:x1="t.x1" :y1="t.y1" :x2="t.x2" :y2="t.y2"
763+
:x1="t.x1" :y1="t.y1 - depth3d" :x2="t.x2" :y2="t.y2 - depth3d"
712764
:stroke="t.color"
713765
:stroke-width="(FINAL_CONFIG.style.chart.layout.wheel.ticks.strokeWidth / 360) * Math.min(svg.width, svg.height)"
714766
:stroke-linecap="FINAL_CONFIG.style.chart.layout.wheel.ticks.rounded ? 'round' : 'butt'"
@@ -717,28 +769,43 @@ defineExpose({
717769
/>
718770
</g>
719771
<g v-else>
720-
<path
721-
v-for="w in arcTicks3D || []"
722-
:key="w.i"
723-
:d="w.d"
724-
:fill="FINAL_CONFIG.style.chart.layout.wheel.ticks.inactiveColor"
725-
:stroke="FINAL_CONFIG.style.chart.layout.wheel.ticks.stroke"
726-
:stroke-width="FINAL_CONFIG.style.chart.layout.wheel.ticks.strokeWidth"
727-
stroke-linecap="round"
728-
stroke-linejoin="round"
729-
class="vue-ui-wheel-tick"
730-
/>
731-
<path
732-
v-for="w in arcTicks3D || []"
733-
:key="w.i"
734-
:d="w.d"
735-
:fill="w.fill"
736-
:stroke="FINAL_CONFIG.style.chart.layout.wheel.ticks.stroke"
737-
:stroke-width="FINAL_CONFIG.style.chart.layout.wheel.ticks.strokeWidth"
738-
stroke-linecap="round"
739-
stroke-linejoin="round"
740-
:class="{ 'vue-ui-wheel-tick' : true, 'vue-ui-tick-animated-3d': FINAL_CONFIG.style.chart.animation.use && (w.i * percentageToTickAmount) <= activeValue }"
741-
/>
772+
<g v-for="n in depth3d">
773+
<path
774+
v-for="w in arcTicks3D(-n) || []"
775+
:key="w.i"
776+
:d="w.d"
777+
:fill="FINAL_CONFIG.style.chart.layout.wheel.ticks.inactiveColor"
778+
:stroke="FINAL_CONFIG.style.chart.layout.wheel.ticks.stroke"
779+
:stroke-width="FINAL_CONFIG.style.chart.layout.wheel.ticks.strokeWidth"
780+
stroke-linecap="round"
781+
stroke-linejoin="round"
782+
class="vue-ui-wheel-tick"
783+
/>
784+
<path
785+
v-for="w in arcTicks3D(-n) || []"
786+
:key="w.i"
787+
:d="w.d"
788+
:fill="lightenHexColor(w.fill, 0.5 * n / depth3d)"
789+
:stroke="FINAL_CONFIG.style.chart.layout.wheel.ticks.stroke"
790+
:stroke-width="FINAL_CONFIG.style.chart.layout.wheel.ticks.strokeWidth"
791+
stroke-linecap="round"
792+
stroke-linejoin="round"
793+
:class="{ 'vue-ui-wheel-tick' : true, 'vue-ui-tick-animated-3d': FINAL_CONFIG.style.chart.animation.use && (w.i * percentageToTickAmount) <= activeValue }"
794+
/>
795+
</g>
796+
<g>
797+
<path
798+
v-for="w in arcTicks3D(-depth3d) || []"
799+
:key="w.i"
800+
:d="w.d"
801+
:fill="w.fill"
802+
:stroke="FINAL_CONFIG.style.chart.layout.wheel.ticks.stroke"
803+
:stroke-width="FINAL_CONFIG.style.chart.layout.wheel.ticks.strokeWidth"
804+
stroke-linecap="round"
805+
stroke-linejoin="round"
806+
:class="{ 'vue-ui-wheel-tick' : true, 'vue-ui-tick-animated-3d': FINAL_CONFIG.style.chart.animation.use && (w.i * percentageToTickAmount) <= activeValue }"
807+
/>
808+
</g>
742809
</g>
743810
</template>
744811

@@ -769,29 +836,6 @@ defineExpose({
769836
/>
770837
</template>
771838
</template>
772-
773-
<!-- HOLLOW 3D -->
774-
<path
775-
class="vue-ui-wheel-inner-circle"
776-
v-if="FINAL_CONFIG.layout === '3d' && inner3D"
777-
:d="inner3D.d"
778-
:stroke="FINAL_CONFIG.style.chart.layout.innerCircle.stroke"
779-
:stroke-width="FINAL_CONFIG.style.chart.layout.innerCircle.strokeWidth"
780-
fill="none"
781-
/>
782-
783-
<!-- HOLLOW 2D -->
784-
<circle
785-
data-cy="inner-circle"
786-
class="vue-ui-wheel-inner-circle"
787-
v-else-if="FINAL_CONFIG.style.chart.layout.innerCircle.show"
788-
:cx="wheel.centerX"
789-
:cy="wheel.centerY"
790-
:r="Math.max(0, wheel.radius * FINAL_CONFIG.style.chart.layout.innerCircle.radiusRatio * 0.8)"
791-
:stroke="FINAL_CONFIG.style.chart.layout.innerCircle.stroke"
792-
:stroke-width="FINAL_CONFIG.style.chart.layout.innerCircle.strokeWidth"
793-
fill="none"
794-
/>
795839

796840
<g v-if="FINAL_CONFIG.style.chart.layout.percentage.show">
797841
<rect
@@ -815,6 +859,8 @@ defineExpose({
815859
style="font-variant-numeric:tabluar-nums"
816860
:stroke="FINAL_CONFIG.style.chart.layout.percentage.stroke"
817861
:stroke-width="FINAL_CONFIG.style.chart.layout.percentage.strokeWidth"
862+
stroke-linecap="round"
863+
stroke-linejoin="round"
818864
paint-order="stroke fill"
819865
:class="{ 'vue-ui-wheel-label': FINAL_CONFIG.layout === '3d' }"
820866
>

0 commit comments

Comments
 (0)