Add Arc component

This commit is contained in:
Anders Englöf Ytterström 2025-04-16 00:19:52 +02:00
parent 0db5ab19ed
commit e36914714f
2 changed files with 199 additions and 0 deletions

168
src/lib/Arc.svelte Normal file
View file

@ -0,0 +1,168 @@
<script lang="ts">
import DietProgress from "./DietProgress.svelte";
import P from "./svg-path";
import { gym, cardio, diet } from "./store";
export const DEG_TO_RAD = Math.PI / 180;
export const RAD_TO_DEG = 180 / Math.PI;
export const FULL_CIRCLE_IN_RADIANS = 2 * Math.PI;
const size = 400;
const segmentHeight = 16;
const span = 0.8 * FULL_CIRCLE_IN_RADIANS;
const startAngle = 0.25 * FULL_CIRCLE_IN_RADIANS + span / 2;
const perimiterWidth = size * Math.PI * (span / FULL_CIRCLE_IN_RADIANS);
const pixelToRadians = span / perimiterWidth;
const x = 0;
const y = 0;
const points = (radius: number, radLength: number, thickness: number) => {
const borderRadius = thickness / 2;
const outerRadius = radius;
const innerRadius = outerRadius - thickness;
const radEndAngle = startAngle - radLength;
const borderRadiusAngle =
(borderRadius / (outerRadius * FULL_CIRCLE_IN_RADIANS)) *
FULL_CIRCLE_IN_RADIANS;
const isLongTrack = radLength - 2 * borderRadiusAngle > Math.PI;
return P()
.moveTo(
-Math.sin(startAngle) * (outerRadius - borderRadius),
Math.cos(startAngle) * (outerRadius - borderRadius),
)
.arcTo(
borderRadius,
borderRadius,
false,
true,
-Math.sin(startAngle - borderRadiusAngle) * outerRadius,
Math.cos(startAngle - borderRadiusAngle) * outerRadius,
)
.arcTo(
outerRadius,
outerRadius,
isLongTrack,
true,
-Math.sin(radEndAngle + borderRadiusAngle) * outerRadius,
Math.cos(radEndAngle + borderRadiusAngle) * outerRadius,
)
.arcTo(
borderRadius,
borderRadius,
false,
true,
-Math.sin(radEndAngle) * (outerRadius - borderRadius),
Math.cos(radEndAngle) * (outerRadius - borderRadius),
)
.lineTo(
-Math.sin(radEndAngle) * (innerRadius + borderRadius),
Math.cos(radEndAngle) * (innerRadius + borderRadius),
)
.arcTo(
borderRadius,
borderRadius,
false,
true,
-Math.sin(radEndAngle + borderRadiusAngle) * innerRadius,
Math.cos(radEndAngle + borderRadiusAngle) * innerRadius,
)
.arcTo(
innerRadius,
innerRadius,
isLongTrack,
false,
-Math.sin(startAngle - borderRadiusAngle) * innerRadius,
Math.cos(startAngle - borderRadiusAngle) * innerRadius,
)
.arcTo(
borderRadius,
borderRadius,
false,
true,
-Math.sin(startAngle) * (innerRadius + borderRadius),
Math.cos(startAngle) * (innerRadius + borderRadius),
)
.close()
.stringify();
};
let items = [
{
c: "gym",
progress: $gym.filter((c) => c.completed).length / $gym.length,
level: 0,
},
{
c: "cardio",
progress: $diet.filter((c) => !c.excluded).length / $cardio.length,
level: 1,
},
{
c: "diet",
progress:
$cardio.filter((c) => c.completed).length /
$diet.filter((c) => !c.excluded).length,
level: 2,
},
];
</script>
<figure>
<svg viewBox="0 0 {size} {size}" role="presentation">
<g transform="${`translate(${x},${y})`}" fill="none">
<g transform={`translate(${size / 2},${size / 2})`}>
{#each items as { c, progress, level }}
<path
fill="#fff"
opacity="0.066"
d={points(
size / 2 - level * (segmentHeight + 4),
span,
segmentHeight,
)}
/>
<path
class={c}
fill="currentColor"
d={points(
size / 2 - level * (segmentHeight + 4),
progress * span,
segmentHeight,
)}
/>
{/each}
</g>
</g>
</svg>
</figure>
<style>
svg {
display: block;
max-width: 80%;
aspect-ratio: 1;
}
figure {
display: flex;
align-items: center;
place-content: center;
width: 100%;
padding: 0;
margin: 2rem 0 1.5rem;
}
.cardio {
color: #5dc5f8;
}
.gym {
color: #f62b5a;
}
.diet {
color: #35d450;
}
</style>

31
src/lib/svg-path.ts Normal file
View file

@ -0,0 +1,31 @@
export default function () {
const commands: string[] = [];
return {
stringify() {
return `${commands.join("\n")}`;
},
arcTo(
ry: number,
rx: number,
long: boolean,
cw: boolean,
y: number,
x: number,
) {
commands.push(`A ${rx} ${ry} 0 ${long ? 1 : 0} ${cw ? 1 : 0} ${x} ${y}`);
return this;
},
moveTo(y: number, x: number) {
commands.push(`M ${x} ${y}`);
return this;
},
lineTo(y: number, x: number) {
commands.push(`L ${x} ${y}`);
return this;
},
close() {
commands.push("z");
return this;
},
};
}