Skip to content

Commit ac71227

Browse files
authored
Add default line colors instead of black for canvas rendering, add stroke dash support (#50)
* stroke dash format support for canvas * add default line colors * add default point color based on index
1 parent 86d7c98 commit ac71227

File tree

2 files changed

+227
-9
lines changed

2 files changed

+227
-9
lines changed
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
import React, { useRef, useEffect, useState } from "react"
2+
import { drawGraphicsToCanvas, GraphicsObject } from "../lib"
3+
import useResizeObserver from "@react-hook/resize-observer"
4+
import { InteractiveGraphicsCanvas } from "lib/react"
5+
6+
// Example graphics object with different strokeDash formats
7+
const strokeDashExamples: GraphicsObject = {
8+
title: "StrokeDash Format Examples",
9+
lines: [
10+
// String format examples
11+
{
12+
points: [
13+
{ x: 50, y: 50 },
14+
{ x: 250, y: 50 },
15+
],
16+
strokeColor: "red",
17+
strokeWidth: 3,
18+
strokeDash: "5,5", // Basic dashed line - 5px dash, 5px gap
19+
label: "String: '5,5'",
20+
},
21+
{
22+
points: [
23+
{ x: 50, y: 100 },
24+
{ x: 250, y: 100 },
25+
],
26+
strokeColor: "red",
27+
strokeWidth: 3,
28+
strokeDash: "10,5", // Longer dash, shorter gap
29+
label: "String: '10,5'",
30+
},
31+
{
32+
points: [
33+
{ x: 50, y: 150 },
34+
{ x: 250, y: 150 },
35+
],
36+
strokeColor: "red",
37+
strokeWidth: 3,
38+
strokeDash: "5", // Single value - 5px dash, 5px gap (browser default behavior)
39+
label: "String: '5'",
40+
},
41+
{
42+
points: [
43+
{ x: 50, y: 200 },
44+
{ x: 250, y: 200 },
45+
],
46+
strokeColor: "red",
47+
strokeWidth: 3,
48+
strokeDash: "10,5,2,5", // Complex pattern
49+
label: "String: '10,5,2,5'",
50+
},
51+
52+
// Array format examples - should match the string versions above
53+
{
54+
points: [
55+
{ x: 350, y: 50 },
56+
{ x: 550, y: 50 },
57+
],
58+
strokeColor: "blue",
59+
strokeWidth: 3,
60+
strokeDash: [5, 5], // Basic dashed line - equivalent to "5,5"
61+
label: "Array: [5, 5]",
62+
},
63+
{
64+
points: [
65+
{ x: 350, y: 100 },
66+
{ x: 550, y: 100 },
67+
],
68+
strokeColor: "blue",
69+
strokeWidth: 3,
70+
strokeDash: [10, 5], // Longer dash, shorter gap - equivalent to "10,5"
71+
label: "Array: [10, 5]",
72+
},
73+
{
74+
points: [
75+
{ x: 350, y: 150 },
76+
{ x: 550, y: 150 },
77+
],
78+
strokeColor: "blue",
79+
strokeWidth: 3,
80+
strokeDash: [5], // Single value - equivalent to "5" string
81+
label: "Array: [5]",
82+
},
83+
{
84+
points: [
85+
{ x: 350, y: 200 },
86+
{ x: 550, y: 200 },
87+
],
88+
strokeColor: "blue",
89+
strokeWidth: 3,
90+
strokeDash: [10, 5, 2, 5], // Complex pattern - equivalent to "10,5,2,5"
91+
label: "Array: [10, 5, 2, 5]",
92+
},
93+
94+
// Special cases
95+
{
96+
points: [
97+
{ x: 50, y: 250 },
98+
{ x: 250, y: 250 },
99+
],
100+
strokeColor: "green",
101+
strokeWidth: 3,
102+
strokeDash: "0", // Zero value should render as solid line
103+
label: "String: '0'",
104+
},
105+
{
106+
points: [
107+
{ x: 350, y: 250 },
108+
{ x: 550, y: 250 },
109+
],
110+
strokeColor: "green",
111+
strokeWidth: 3,
112+
strokeDash: [0], // Zero value array should render as solid line
113+
label: "Array: [0]",
114+
},
115+
{
116+
points: [
117+
{ x: 50, y: 300 },
118+
{ x: 250, y: 300 },
119+
],
120+
strokeColor: "purple",
121+
strokeWidth: 3,
122+
// No strokeDash property - should render as solid line
123+
label: "No strokeDash (solid)",
124+
},
125+
],
126+
// Using screen coordinates for predictable positioning
127+
coordinateSystem: "screen",
128+
}
129+
130+
export default function StrokeDashFormats() {
131+
const canvasRef = useRef<HTMLCanvasElement>(null)
132+
const containerRef = useRef<HTMLDivElement | null>(null)
133+
const [size, setSize] = useState({ width: 600, height: 400 })
134+
135+
// Monitor container size
136+
useResizeObserver(containerRef, (entry) => {
137+
setSize({
138+
width: entry.contentRect.width,
139+
height: entry.contentRect.height,
140+
})
141+
})
142+
143+
// Draw function that uses our canvas renderer
144+
const drawCanvas = () => {
145+
if (!canvasRef.current) return
146+
147+
// Make sure canvas dimensions match container
148+
canvasRef.current.width = size.width
149+
canvasRef.current.height = size.height
150+
151+
// Draw the graphics with different strokeDash formats
152+
drawGraphicsToCanvas(strokeDashExamples, canvasRef.current)
153+
154+
// Add labels for each line
155+
const ctx = canvasRef.current.getContext("2d")
156+
if (ctx) {
157+
ctx.font = "12px sans-serif"
158+
ctx.fillStyle = "black"
159+
160+
strokeDashExamples.lines?.forEach((line) => {
161+
if (line.label && line.points.length > 0) {
162+
const x = line.points[0].x
163+
const y = line.points[0].y
164+
ctx.fillText(line.label, x, y - 5)
165+
}
166+
})
167+
}
168+
}
169+
170+
// Apply the drawing when size changes
171+
useEffect(() => {
172+
drawCanvas()
173+
}, [size])
174+
175+
return (
176+
<div style={{ display: "flex", flexDirection: "column", gap: "10px" }}>
177+
<h2>StrokeDash Format Examples</h2>
178+
<p>Demonstrating both string and array formats for strokeDash property</p>
179+
<div
180+
ref={containerRef}
181+
style={{
182+
position: "relative",
183+
width: "100%",
184+
height: 350,
185+
border: "1px solid #ccc",
186+
overflow: "hidden",
187+
}}
188+
>
189+
<canvas
190+
ref={canvasRef}
191+
style={{
192+
position: "absolute",
193+
top: 0,
194+
left: 0,
195+
width: "100%",
196+
height: "100%",
197+
}}
198+
/>
199+
</div>
200+
<InteractiveGraphicsCanvas graphics={strokeDashExamples} />
201+
</div>
202+
)
203+
}

lib/drawGraphicsToCanvas.ts

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type {
1111
CenterViewbox,
1212
TransformOptions,
1313
} from "./types"
14+
import { defaultColors } from "site/components/InteractiveGraphics/defaultColors"
1415

1516
/**
1617
* Computes a transformation matrix based on a provided viewbox
@@ -215,7 +216,7 @@ export function drawGraphicsToCanvas(
215216

216217
// Draw lines
217218
if (graphics.lines && graphics.lines.length > 0) {
218-
graphics.lines.forEach((line) => {
219+
graphics.lines.forEach((line, lineIndex) => {
219220
if (line.points.length === 0) return
220221

221222
ctx.beginPath()
@@ -228,7 +229,8 @@ export function drawGraphicsToCanvas(
228229
ctx.lineTo(projected.x, projected.y)
229230
}
230231

231-
ctx.strokeStyle = line.strokeColor || "black"
232+
ctx.strokeStyle =
233+
line.strokeColor || defaultColors[lineIndex % defaultColors.length]
232234
if (line.strokeWidth) {
233235
ctx.lineWidth = line.strokeWidth * matrix.a
234236
} else {
@@ -238,13 +240,25 @@ export function drawGraphicsToCanvas(
238240

239241
if (line.strokeDash) {
240242
if (typeof line.strokeDash === "string") {
241-
ctx.setLineDash(
242-
line.strokeDash
243+
// Convert string to array of numbers, handling single values properly
244+
let dashArray: number[]
245+
246+
// If the string contains commas, split and convert to numbers
247+
if (line.strokeDash.includes(",")) {
248+
dashArray = line.strokeDash
243249
.split(",")
244-
.map(Number)
245-
.map((n) => n * Math.abs(matrix.a)),
246-
)
250+
.map((s) => parseFloat(s.trim()))
251+
.filter((n) => !Number.isNaN(n))
252+
} else {
253+
// Handle single value case
254+
const value = parseFloat(line.strokeDash.trim())
255+
dashArray = !Number.isNaN(value) ? [value] : []
256+
}
257+
258+
// Scale dash values based on transform matrix
259+
ctx.setLineDash(dashArray)
247260
} else {
261+
// Handle array format
248262
ctx.setLineDash(line.strokeDash.map((n) => n * Math.abs(matrix.a)))
249263
}
250264
} else {
@@ -257,13 +271,14 @@ export function drawGraphicsToCanvas(
257271

258272
// Draw points
259273
if (graphics.points && graphics.points.length > 0) {
260-
graphics.points.forEach((point) => {
274+
graphics.points.forEach((point, pointIndex) => {
261275
const projected = applyToPoint(matrix, point)
262276

263277
// Draw point as a small circle
264278
ctx.beginPath()
265279
ctx.arc(projected.x, projected.y, 3, 0, 2 * Math.PI)
266-
ctx.fillStyle = point.color || "black"
280+
ctx.fillStyle =
281+
point.color || defaultColors[pointIndex % defaultColors.length]
267282
ctx.fill()
268283

269284
// Draw label if present and labels aren't disabled

0 commit comments

Comments
 (0)