Add Arc component
This commit is contained in:
parent
0db5ab19ed
commit
e36914714f
2 changed files with 199 additions and 0 deletions
168
src/lib/Arc.svelte
Normal file
168
src/lib/Arc.svelte
Normal 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
31
src/lib/svg-path.ts
Normal 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;
|
||||
},
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue