Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions lib/drawGraphicsToCanvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ export function computeTransformFromViewbox(
bounds = viewbox
}

const width = bounds.maxX - bounds.minX
const height = bounds.maxY - bounds.minY
const width = bounds.maxX - bounds.minX || 1
const height = bounds.maxY - bounds.minY || 1

const scale_factor = Math.min(
(canvasWidth - 2 * padding) / width,
Expand Down Expand Up @@ -79,6 +79,7 @@ export function getBounds(graphics: GraphicsObject): Viewbox {
{ x: circle.center.x, y: circle.center.y - circle.radius }, // top
{ x: circle.center.x, y: circle.center.y + circle.radius }, // bottom
]),
...(graphics.texts || []).map((text) => ({ x: text.x, y: text.y })),
]

if (points.length === 0) {
Expand Down Expand Up @@ -290,6 +291,43 @@ export function drawGraphicsToCanvas(
})
}

// Draw texts
if (graphics.texts && graphics.texts.length > 0) {
graphics.texts.forEach((text) => {
const projected = applyToPoint(matrix, { x: text.x, y: text.y })
ctx.fillStyle = text.color || "black"
ctx.font = `${text.fontSize ?? 12}px sans-serif`

const anchor = text.anchorSide ?? "center"
const alignMap: Record<string, CanvasTextAlign> = {
top_left: "left",
center_left: "left",
bottom_left: "left",
top_center: "center",
center: "center",
bottom_center: "center",
top_right: "right",
center_right: "right",
bottom_right: "right",
}
const baselineMap: Record<string, CanvasTextBaseline> = {
top_left: "top",
top_center: "top",
top_right: "top",
center_left: "middle",
center: "middle",
center_right: "middle",
bottom_left: "bottom",
bottom_center: "bottom",
bottom_right: "bottom",
}
ctx.textAlign = alignMap[anchor]
ctx.textBaseline = baselineMap[anchor]

ctx.fillText(text.text, projected.x, projected.y)
})
}

// Restore the original transform
ctx.restore()
}
69 changes: 57 additions & 12 deletions lib/getSvgFromGraphicsObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ function getBounds(graphics: GraphicsObject): Bounds {
{ x: circle.center.x, y: circle.center.y - circle.radius }, // top
{ x: circle.center.x, y: circle.center.y + circle.radius }, // bottom
]),
...(graphics.texts || []).map((t) => ({ x: t.x, y: t.y })),
]

if (points.length === 0) {
Expand All @@ -62,8 +63,8 @@ function getProjectionMatrix(
bounds: Bounds,
coordinateSystem: GraphicsObject["coordinateSystem"],
) {
const width = bounds.maxX - bounds.minX
const height = bounds.maxY - bounds.minY
const width = bounds.maxX - bounds.minX || 1
const height = bounds.maxY - bounds.minY || 1

const scale_factor = Math.min(
(DEFAULT_SVG_SIZE - 2 * PADDING) / width,
Expand All @@ -87,7 +88,7 @@ function projectPoint(point: Point, matrix: Matrix) {
export function getSvgFromGraphicsObject(
graphics: GraphicsObject,
{
includeTextLabels = false,
includeTextLabels = true,
backgroundColor,
}: {
includeTextLabels?: boolean | Array<"points" | "lines" | "rects">
Expand Down Expand Up @@ -183,26 +184,25 @@ export function getSvgFromGraphicsObject(
name: "polyline",
type: "element",
attributes: {
"data-points": line.points.map((p) => `${p.x},${p.y}`).join(" "),
"data-type": "line",
"data-label": line.label || "",
points: projectedPoints
"data-points": line.points
.map((p) => `${p.x},${p.y}`)
.join(" "),
"data-type": "line",
"data-label": line.label || "",
points: projectedPoints.map((p) => `${p.x},${p.y}`).join(" "),
fill: "none",
stroke: line.strokeColor || "black",
"stroke-width": (line.strokeWidth
? line.strokeWidth * matrix.a
: 1
).toString(),
"stroke-width": (line.strokeWidth ?? 1).toString(),
...(line.strokeDash && {
"stroke-dasharray": Array.isArray(line.strokeDash)
? line.strokeDash.join(" ")
: line.strokeDash,
}),
},
},
...(shouldRenderLabel("lines") && line.label && projectedPoints.length > 0
...(shouldRenderLabel("lines") &&
line.label &&
projectedPoints.length > 0
? [
{
name: "text",
Expand Down Expand Up @@ -300,6 +300,51 @@ export function getSvgFromGraphicsObject(
},
}
}),
// Texts
...(graphics.texts || []).map((txt) => {
const projected = projectPoint({ x: txt.x, y: txt.y }, matrix)
const anchor = txt.anchorSide ?? "center"
const alignMap: Record<string, string> = {
top_left: "start",
center_left: "start",
bottom_left: "start",
top_center: "middle",
center: "middle",
bottom_center: "middle",
top_right: "end",
center_right: "end",
bottom_right: "end",
}
const baselineMap: Record<string, string> = {
top_left: "text-before-edge",
top_center: "text-before-edge",
top_right: "text-before-edge",
center_left: "central",
center: "central",
center_right: "central",
bottom_left: "text-after-edge",
bottom_center: "text-after-edge",
bottom_right: "text-after-edge",
}
return {
name: "text",
type: "element",
attributes: {
"data-type": "text",
"data-label": txt.text,
"data-x": txt.x.toString(),
"data-y": txt.y.toString(),
x: projected.x.toString(),
y: projected.y.toString(),
fill: txt.color || "black",
"font-size": (txt.fontSize ?? 12).toString(),
"font-family": "sans-serif",
"text-anchor": alignMap[anchor],
"dominant-baseline": baselineMap[anchor],
},
children: [{ type: "text", value: txt.text }],
}
}),
// Crosshair lines and coordinates (initially hidden)
{
name: "g",
Expand Down
2 changes: 2 additions & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export type {
Line,
Rect,
Circle,
Text,
NinePointAnchor,
GraphicsObject,
Viewbox,
CenterViewbox,
Expand Down
1 change: 1 addition & 0 deletions lib/mergeGraphics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ export const mergeGraphics = (
points: [...(graphics1.points ?? []), ...(graphics2.points ?? [])],
lines: [...(graphics1.lines ?? []), ...(graphics2.lines ?? [])],
circles: [...(graphics1.circles ?? []), ...(graphics2.circles ?? [])],
texts: [...(graphics1.texts ?? []), ...(graphics2.texts ?? [])],
}
}
5 changes: 5 additions & 0 deletions lib/translateGraphics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,10 @@ export function translateGraphics(
...circle,
center: { x: circle.center.x + dx, y: circle.center.y + dy },
})),
texts: graphics.texts?.map((text) => ({
...text,
x: text.x + dx,
y: text.y + dy,
})),
}
}
23 changes: 23 additions & 0 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,34 @@ export interface Circle {
label?: string
}

export type NinePointAnchor =
| "center"
| "top_left"
| "top_center"
| "top_right"
| "center_left"
| "center_right"
| "bottom_left"
| "bottom_center"
| "bottom_right"

export interface Text {
x: number
y: number
text: string
anchorSide?: NinePointAnchor
color?: string
fontSize?: number
layer?: string
step?: number
}

export interface GraphicsObject {
points?: Point[]
lines?: Line[]
rects?: Rect[]
circles?: Circle[]
texts?: Text[]
coordinateSystem?: "cartesian" | "screen"
title?: string
}
Expand Down
21 changes: 19 additions & 2 deletions site/components/InteractiveGraphics/InteractiveGraphics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Line } from "./Line"
import { Point } from "./Point"
import { Rect } from "./Rect"
import { Circle } from "./Circle"
import { Text } from "./Text"
import { getGraphicsBounds } from "site/utils/getGraphicsBounds"
import {
useIsPointOnScreen,
Expand All @@ -23,14 +24,15 @@ import {
useFilterPoints,
useFilterRects,
useFilterCircles,
useFilterTexts,
} from "./hooks"
import { DimensionOverlay } from "../DimensionOverlay"
import { getMaxStep } from "site/utils/getMaxStep"
import { ContextMenu } from "./ContextMenu"
import { Marker, MarkerPoint } from "./Marker"

export type GraphicsObjectClickEvent = {
type: "point" | "line" | "rect" | "circle"
type: "point" | "line" | "rect" | "circle" | "text"
index: number
object: any
}
Expand Down Expand Up @@ -60,6 +62,7 @@ export const InteractiveGraphics = ({
...(graphics.lines?.map((l) => l.layer!).filter(Boolean) ?? []),
...(graphics.rects?.map((r) => r.layer!).filter(Boolean) ?? []),
...(graphics.points?.map((p) => p.layer!).filter(Boolean) ?? []),
...(graphics.texts?.map((t) => t.layer!).filter(Boolean) ?? []),
]),
)
const maxStep = getMaxStep(graphics)
Expand Down Expand Up @@ -309,6 +312,7 @@ export const InteractiveGraphics = ({
realToScreen,
size,
)
const filterTexts = useFilterTexts(isPointOnScreen, filterLayerAndStep)

const filterAndLimit = <T,>(
objects: T[] | undefined,
Expand Down Expand Up @@ -337,12 +341,17 @@ export const InteractiveGraphics = ({
() => filterAndLimit(graphics.circles, filterCircles),
[graphics.circles, filterCircles, objectLimit],
)
const filteredTexts = useMemo(
() => filterAndLimit(graphics.texts, filterTexts),
[graphics.texts, filterTexts, objectLimit],
)

const totalFilteredObjects =
filteredLines.length +
filteredRects.length +
filteredPoints.length +
filteredCircles.length
filteredCircles.length +
filteredTexts.length
const isLimitReached = objectLimit && totalFilteredObjects > objectLimit

return (
Expand Down Expand Up @@ -461,6 +470,14 @@ export const InteractiveGraphics = ({
interactiveState={interactiveState}
/>
))}
{filteredTexts.map((txt) => (
<Text
key={txt.originalIndex}
textObj={txt}
index={txt.originalIndex}
interactiveState={interactiveState}
/>
))}
<SuperGrid
stringifyCoord={(x, y) => `${x.toFixed(2)}, ${y.toFixed(2)}`}
width={size.width}
Expand Down
51 changes: 51 additions & 0 deletions site/components/InteractiveGraphics/Text.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type * as Types from "lib/types"
import { applyToPoint } from "transformation-matrix"
import type { InteractiveState } from "./InteractiveState"

export const Text = ({
textObj,
interactiveState,
index,
}: {
textObj: Types.Text
interactiveState: InteractiveState
index: number
}) => {
const { realToScreen, onObjectClicked } = interactiveState
const { x, y, text, color, fontSize, anchorSide } = textObj
const screenPos = applyToPoint(realToScreen, { x, y })

const transformMap: Record<Types.NinePointAnchor, string> = {
top_left: "translate(0%, 0%)",
top_center: "translate(-50%, 0%)",
top_right: "translate(-100%, 0%)",
center_left: "translate(0%, -50%)",
center: "translate(-50%, -50%)",
center_right: "translate(-100%, -50%)",
bottom_left: "translate(0%, -100%)",
bottom_center: "translate(-50%, -100%)",
bottom_right: "translate(-100%, -100%)",
}
const transform =
transformMap[(anchorSide ?? "center") as Types.NinePointAnchor]

return (
<div
style={{
position: "absolute",
left: screenPos.x,
top: screenPos.y,
transform,
color: color || "black",
fontSize: fontSize ?? 12,
whiteSpace: "pre",
cursor: "default",
}}
onClick={() =>
onObjectClicked?.({ type: "text", index, object: textObj })
}
>
{text}
</div>
)
}
1 change: 1 addition & 0 deletions site/components/InteractiveGraphics/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export { useFilterLines } from "./useFilterLines"
export { useFilterPoints } from "./useFilterPoints"
export { useFilterRects } from "./useFilterRects"
export { useFilterCircles } from "./useFilterCircles"
export { useFilterTexts } from "./useFilterTexts"
20 changes: 20 additions & 0 deletions site/components/InteractiveGraphics/hooks/useFilterTexts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useMemo } from "react"

interface Text {
x: number
y: number
layer?: string
step?: number
}

export const useFilterTexts = (
isPointOnScreen: (point: { x: number; y: number }) => boolean,
filterLayerAndStep: (obj: { layer?: string; step?: number }) => boolean,
) => {
return useMemo(() => {
return (text: Text) => {
if (!filterLayerAndStep(text)) return false
return isPointOnScreen({ x: text.x, y: text.y })
}
}, [isPointOnScreen, filterLayerAndStep])
}
1 change: 1 addition & 0 deletions site/components/InteractiveGraphics/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { InteractiveGraphics } from "./InteractiveGraphics"
export { InteractiveState } from "./InteractiveState"
export { ContextMenu } from "./ContextMenu"
export { Text } from "./Text"
Loading