Skip to content

Commit 3304cc9

Browse files
committed
feat: make diagrams responsive
1 parent 9a0275e commit 3304cc9

14 files changed

+138
-100
lines changed

src/components/Angle.vue

+5-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
<path
1010
:d="`M ${start.x} ${start.y} A ${scaledRadius} ${scaledRadius} 0 ${sweep} 0 ${end.x} ${end.y}`"
11-
:stroke-width="lineWidth"
11+
:stroke-width="lineWidth * invScale"
1212
:stroke="stroke"
1313
:stroke-dasharray="dashArray"
1414
fill="none"
@@ -42,7 +42,7 @@ const props = withDefaults(
4242
},
4343
);
4444
45-
const { scale, offset } = useGraphContext();
45+
const { scale, offset, invScale } = useGraphContext();
4646
const { parseColor } = useColors();
4747
4848
const stroke = parseColor(toRef(props, "color"), "stroke");
@@ -78,5 +78,7 @@ const end = computed(() => {
7878
.mul(scale.value)
7979
.add(offset.value);
8080
});
81-
const dashArray = computed(() => (props.dashed ? "6,4" : "0,0"));
81+
const dashArray = computed(() =>
82+
props.dashed ? [6 * invScale.value, 4 * invScale.value].join(",") : "0,0",
83+
);
8284
</script>

src/components/Circle.vue

+8-6
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
:r="scaledRadius"
66
:fill="fill"
77
:stroke="stroke"
8-
:stroke-width="lineWidth"
8+
:stroke-width="lineWidth * invScale"
99
:stroke-dasharray="dashArray"
1010
/>
1111
</template>
@@ -34,7 +34,7 @@ const props = withDefaults(
3434
},
3535
);
3636
37-
const context = useGraphContext();
37+
const { scale, offset, invScale } = useGraphContext();
3838
const { parseColor } = useColors();
3939
4040
const stroke = parseColor(toRef(props, "color"), "stroke");
@@ -43,9 +43,11 @@ const fill = parseColor(toRef(props, "fill"));
4343
const position = computed(() =>
4444
new Vector2(props.position)
4545
.mul(new Vector2(1, -1))
46-
.mul(context.scale.value)
47-
.add(context.offset.value),
46+
.mul(scale.value)
47+
.add(offset.value),
48+
);
49+
const scaledRadius = computed(() => props.radius * scale.value.x);
50+
const dashArray = computed(() =>
51+
props.dashed ? [6 * invScale.value, 4 * invScale.value].join(",") : "0,0",
4852
);
49-
const scaledRadius = computed(() => props.radius * context.scale.value.x);
50-
const dashArray = computed(() => (props.dashed ? "6,4" : "0,0"));
5153
</script>

src/components/Ellipse.vue

+7-7
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
:ry="scaledRadius.y"
77
:fill="fill"
88
:stroke="stroke"
9-
:stroke-width="lineWidth"
9+
:stroke-width="lineWidth * invScale"
1010
:stroke-dasharray="dashArray"
1111
/>
1212
</template>
@@ -36,7 +36,7 @@ const props = withDefaults(
3636
},
3737
);
3838
39-
const context = useGraphContext();
39+
const { offset, scale, invScale } = useGraphContext();
4040
const { parseColor } = useColors();
4141
4242
const stroke = parseColor(toRef(props, "color"), "stroke");
@@ -45,11 +45,11 @@ const fill = parseColor(toRef(props, "fill"));
4545
const position = computed(() =>
4646
new Vector2(props.position)
4747
.mul(new Vector2(1, -1))
48-
.mul(context.scale.value)
49-
.add(context.offset.value),
48+
.mul(scale.value)
49+
.add(offset.value),
5050
);
51-
const scaledRadius = computed(() =>
52-
new Vector2(props.radius).mul(context.scale.value),
51+
const scaledRadius = computed(() => new Vector2(props.radius).mul(scale.value));
52+
const dashArray = computed(() =>
53+
props.dashed ? [6 * invScale.value, 4 * invScale.value].join(",") : "0,0",
5354
);
54-
const dashArray = computed(() => (props.dashed ? "6,4" : "0,0"));
5555
</script>

src/components/FunctionPlot.vue

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
:x2="visiblePoints[i + 1].x * scale.x + offset.x"
77
:y2="visiblePoints[i + 1].y * scale.y + offset.y"
88
:stroke="color"
9-
:stroke-width="lineWidth"
9+
:stroke-width="lineWidth * invScale"
1010
/>
1111
</template>
1212

@@ -38,7 +38,7 @@ const props = withDefaults(
3838
3939
let animationFrameID: number | null = null;
4040
41-
const { domain, scale, offset, size } = useGraphContext();
41+
const { domain, scale, offset, size, invScale } = useGraphContext();
4242
const { parseColor } = useColors();
4343
4444
const color = parseColor(toRef(props, "color"), "stroke");

src/components/Graph.vue

+35-15
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
<template>
22
<svg
3+
ref="el"
34
:viewBox="`${-padding} ${-padding} ${width + padding * 2} ${height + padding * 2}`"
4-
:width="width"
5-
:height="height"
5+
:width="size.x"
6+
:height="size.y"
67
xmlns="http://www.w3.org/2000/svg"
78
>
89
<defs>
@@ -23,7 +24,7 @@
2324
:d="`M 0 0 L 0 ${scale.y * gridSize} ${scale.x * gridSize} ${scale.y * gridSize} ${scale.x * gridSize} 0 0 0`"
2425
fill="none"
2526
:stroke="colors.grid"
26-
stroke-width="0.75"
27+
:stroke-width="0.75 * invScale"
2728
/>
2829
</pattern>
2930
</defs>
@@ -37,15 +38,15 @@
3738
:x2="offset.x"
3839
:y2="height"
3940
:stroke="colors.axis"
40-
stroke-width="1.5"
41+
:stroke-width="1.5 * invScale"
4142
/>
4243
<line
4344
:x1="0"
4445
:y1="offset.y"
4546
:x2="width"
4647
:y2="offset.y"
4748
:stroke="colors.axis"
48-
stroke-width="1.5"
49+
:stroke-width="1.5 * invScale"
4950
/>
5051
</template>
5152

@@ -60,18 +61,18 @@
6061
:x2="scale.x * (i - 1) * gridSize"
6162
:y2="offset.y + 5"
6263
:stroke="colors.units"
63-
stroke-width="1"
64+
:stroke-width="1 * invScale"
6465
/>
6566

6667
<text
6768
:x="scale.x * (i - 1) * gridSize"
6869
:y="offset.y + 14"
69-
style="
70+
:style="`
7071
dominant-baseline: middle;
7172
text-anchor: middle;
72-
font-size: 12px;
73+
font-size: ${12 * invScale}px;
7374
font-family: sans-serif;
74-
"
75+
`"
7576
:fill="colors.units"
7677
>
7778
{{ formatLabelValue((i - 1) * gridSize - origin.x) }}
@@ -88,18 +89,18 @@
8889
:x2="offset.x - 5"
8990
:y2="scale.y * (i - 1) * gridSize"
9091
:stroke="colors.units"
91-
stroke-width="1"
92+
:stroke-width="1 * invScale"
9293
/>
9394

9495
<text
9596
:x="offset.x - 14"
9697
:y="scale.y * (i - 1) * gridSize"
97-
style="
98+
:style="`
9899
dominant-baseline: middle;
99100
text-anchor: middle;
100-
font-size: 12px;
101+
font-size: ${12 * invScale}px;
101102
font-family: sans-serif;
102-
"
103+
`"
103104
:fill="colors.units"
104105
>
105106
{{ formatLabelValue(origin.y - (i - 1) * gridSize) }}
@@ -114,7 +115,7 @@
114115
</template>
115116

116117
<script setup lang="ts">
117-
import { computed, provide } from "vue";
118+
import { computed, onMounted, provide, ref } from "vue";
118119
119120
import { graphContext } from "../types.ts";
120121
import { PossibleVector2, Vector2 } from "../math/Vector2.ts";
@@ -148,6 +149,8 @@ const props = withDefaults(
148149
149150
const id = Math.random().toString(16).slice(2);
150151
const { colors } = useColors();
152+
const el = ref<SVGElement | null>();
153+
const containerSize = ref(new Vector2(props.width, props.height));
151154
152155
const origin = computed(() => {
153156
if (props.origin) {
@@ -170,19 +173,36 @@ const scale = computed(
170173
props.height / Math.abs(domain.value.y.x - domain.value.y.y),
171174
),
172175
);
173-
const size = computed(() => new Vector2(props.width, props.height));
176+
const aspect = computed(() => props.width / props.height);
177+
const size = computed(() => {
178+
const width = Math.min(props.width, containerSize.value.x);
179+
const height = width / aspect.value;
180+
return new Vector2(width, height);
181+
});
182+
const invScale = computed(() => props.width / size.value.x);
174183
175184
const context = {
176185
size,
177186
scale,
178187
origin,
179188
offset,
180189
domain,
190+
invScale,
181191
};
182192
183193
provide(graphContext, context);
184194
185195
function formatLabelValue(value: number) {
186196
return value.toFixed(2).replace(/\.?0+$/, "");
187197
}
198+
199+
onMounted(() => {
200+
const observer = new ResizeObserver((entries) => {
201+
const entry = entries[0];
202+
const contentBox = entry.contentBoxSize[0];
203+
containerSize.value.x = contentBox.inlineSize;
204+
containerSize.value.y = contentBox.blockSize;
205+
});
206+
observer.observe(el.value!.parentElement!);
207+
});
188208
</script>

src/components/Label.vue

+16-11
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<text
1313
:x="position.x"
1414
:y="position.y"
15-
:style="`font-family: monospace; font-size: ${fontSizes[size]}px; dominant-baseline: middle; text-anchor: middle; font-weight: 500;`"
15+
:style="`font-family: monospace; font-size: ${fontSize}px; dominant-baseline: middle; text-anchor: middle; font-weight: 500;`"
1616
:fill="color"
1717
>
1818
{{ text }}
@@ -49,24 +49,29 @@ const sizes = {
4949
large: 12,
5050
};
5151
52-
const context = useGraphContext();
52+
const { scale, offset, invScale } = useGraphContext();
5353
const { colors, parseColor } = useColors();
5454
5555
const color = parseColor(toRef(props, "color"), "stroke");
5656
5757
const position = computed(() =>
5858
new Vector2(props.position)
5959
.mul(new Vector2(1, -1))
60-
.mul(context.scale.value)
61-
.add(context.offset.value),
60+
.mul(scale.value)
61+
.add(offset.value),
6262
);
63-
const boxWidth = computed(() =>
64-
Math.max(
65-
(props.text.length + 1) * sizes[props.size],
66-
sizes[props.size] * 2.5,
67-
),
63+
const boxWidth = computed(
64+
() =>
65+
Math.max(
66+
(props.text.length + 1) * sizes[props.size],
67+
sizes[props.size] * 2.5,
68+
) * invScale.value,
6869
);
69-
const boxHeight = computed(() =>
70-
Math.min(sizes[props.size] * 3, boxWidth.value),
70+
const boxHeight = computed(
71+
() => Math.min(sizes[props.size] * 3, boxWidth.value) * invScale.value,
7172
);
73+
const fontSize = computed(() => {
74+
console.log(invScale.value);
75+
return fontSizes[props.size] * invScale.value;
76+
});
7277
</script>

src/components/Line.vue

+23-21
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
:x2="to.x"
66
:y2="to.y"
77
:stroke="color"
8-
:stroke-width="lineWidth"
8+
:stroke-width="lineWidth * invScale"
99
:stroke-dasharray="dashArray"
1010
/>
1111
<Label
@@ -50,7 +50,7 @@ if (props.to === undefined && props.slope === undefined) {
5050
throw new Error("Line requires either a `to` prop or a `slope` prop");
5151
}
5252
53-
const context = useGraphContext();
53+
const { domain, scale, offset, invScale } = useGraphContext();
5454
const { parseColor } = useColors();
5555
5656
const color = parseColor(toRef(props, "color"), "stroke");
@@ -63,52 +63,54 @@ const from = computed(() => {
6363
if (props.from) {
6464
return new Vector2(props.from)
6565
.mul(new Vector2(1, -1))
66-
.mul(context.scale.value)
67-
.add(context.offset.value);
66+
.mul(scale.value)
67+
.add(offset.value);
6868
}
6969
7070
if (props.to) {
7171
return new Vector2(0, 0)
7272
.mul(new Vector2(1, -1))
73-
.mul(context.scale.value)
74-
.add(context.offset.value);
73+
.mul(scale.value)
74+
.add(offset.value);
7575
}
7676
77-
let x = (context.domain.value.y.x - props.yIntercept) / props.slope!;
78-
x = clamp(x, context.domain.value.x.x, context.domain.value.x.y);
77+
let x = (domain.value.y.x - props.yIntercept) / props.slope!;
78+
x = clamp(x, domain.value.x.x, domain.value.x.y);
7979
const y = props.slope! * x + props.yIntercept;
8080
return new Vector2(x, y)
8181
.mul(new Vector2(1, -1))
82-
.mul(context.scale.value)
83-
.add(context.offset.value);
82+
.mul(scale.value)
83+
.add(offset.value);
8484
});
8585
const to = computed(() => {
8686
if (props.to) {
8787
return new Vector2(props.to)
8888
.mul(new Vector2(1, -1))
89-
.mul(context.scale.value)
90-
.add(context.offset.value);
89+
.mul(scale.value)
90+
.add(offset.value);
9191
}
9292
93-
let x = (context.domain.value.y.y - props.yIntercept) / props.slope!;
94-
x = clamp(x, context.domain.value.x.x, context.domain.value.x.y);
93+
let x = (domain.value.y.y - props.yIntercept) / props.slope!;
94+
x = clamp(x, domain.value.x.x, domain.value.x.y);
9595
const y = props.slope! * x + props.yIntercept;
9696
return new Vector2(x, y)
9797
.mul(new Vector2(1, -1))
98-
.mul(context.scale.value)
99-
.add(context.offset.value);
98+
.mul(scale.value)
99+
.add(offset.value);
100100
});
101101
const labelPosition = computed(() => {
102102
const localSpaceFrom = from.value
103-
.sub(context.offset.value)
104-
.div(context.scale.value)
103+
.sub(offset.value)
104+
.div(scale.value)
105105
.mul(new Vector2(1, -1));
106106
const localSpaceTo = to.value
107-
.sub(context.offset.value)
108-
.div(context.scale.value)
107+
.sub(offset.value)
108+
.div(scale.value)
109109
.mul(new Vector2(1, -1));
110110
const diff = localSpaceTo.sub(localSpaceFrom);
111111
return localSpaceFrom.add(diff.normalized().scale(diff.length() / 2));
112112
});
113-
const dashArray = computed(() => (props.dashed ? "6,4" : "0,0"));
113+
const dashArray = computed(() =>
114+
props.dashed ? [6 * invScale.value, 4 * invScale.value].join(",") : "0,0",
115+
);
114116
</script>

0 commit comments

Comments
 (0)