Skip to content

Commit 60df991

Browse files
committed
Improve Background component
Add rotation, transform and strokeWidth props Optimize it a little and update it on-resize Also make it work if there is no GrapherContext instead of throwing
1 parent 1f0c8f3 commit 60df991

File tree

3 files changed

+106
-66
lines changed

3 files changed

+106
-66
lines changed

examples/src/App.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export default function App() {
2626
height: 400,
2727
}}>
2828
<ReactGrapher defaultNodes={nodes} defaultEdges={edges} fitView={"initial"}>
29-
<Background pattern={"grid"}/>
29+
<Background/>
3030
</ReactGrapher>
3131
</div>
3232
</div>

src/components/Background/Background.tsx

+55-15
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import React, {useContext, useEffect, useMemo, useRef} from "react";
1+
import React, {useCallback, useContext, useEffect, useMemo, useRef} from "react";
22
import styled from "@emotion/styled";
33
import {GrapherContext} from "../../context/GrapherContext";
44
import {errorGrapherContext, warnInvalidPropValue} from "../../util/log";
55
import {BACKGROUND_CLASS, Z_INDEX_BACKGROUND} from "../../util/constants";
66
import {DataType} from "csstype";
77
import {cx} from "@emotion/css";
88
import {patternDots, patternGrid, patternLines} from "./patterns";
9+
import {useCallbackState} from "../../hooks/useCallbackState";
910

1011
// eslint-disable-next-line @typescript-eslint/ban-types
1112
export type SvgPaint = "none" | "context-fill" | "context-stroke" | DataType.NamedColor | string & {}
@@ -38,6 +39,19 @@ export interface BackgroundProps {
3839
* Defaults to 'md' (medium).
3940
*/
4041
size?: "xs" | "sm" | "md" | "lg" | "xl" | number
42+
/**
43+
* Stroke width multiplier (pattern thickness). Defaults to 1
44+
*/
45+
strokeWidth?: number
46+
/**
47+
* Pattern angle, in degrees.
48+
*/
49+
angle?: number
50+
/**
51+
* If {@link angle} prop does not provide enough customisation for you, you can use this to directly set the `patternTransform` property on the
52+
* SVG `<pattern>` element.
53+
*/
54+
patternTransform?: string
4155
}
4256

4357
/**
@@ -59,12 +73,23 @@ const patternSizeMap = {
5973
"xl": 2.5,
6074
}
6175

62-
// TODO More background patterns, customize strokeWidth, rotation
63-
export function Background({id, className, pattern, color, size}: BackgroundProps) {
64-
const grapherContext = useContext(GrapherContext)
76+
// noinspection JSUnusedGlobalSymbols
77+
const defaultContext = {
78+
id: "context-error",
79+
controller: {
80+
defaultViewport: {centerX: 0, centerY: 0, zoom: 1},
81+
getViewport() {
82+
return this.defaultViewport
83+
},
84+
}
85+
}
86+
87+
// TODO More background patterns
88+
export function Background({id, className, pattern, color, size, strokeWidth, angle, patternTransform}: BackgroundProps) {
89+
let grapherContext = useContext(GrapherContext)
6590
if (grapherContext == null) {
6691
errorGrapherContext("Background")
67-
throw new ReferenceError("GrapherContext is undefined")
92+
grapherContext = defaultContext as any
6893
}
6994

7095
if (id == null) id = grapherContext.id + "-background"
@@ -73,30 +98,45 @@ export function Background({id, className, pattern, color, size}: BackgroundProp
7398
// Get pattern element
7499
const [pElem, pVBSize, pSize] = useMemo(() => {
75100
const sizeMul = typeof size === "number" ? size : patternSizeMap[size ?? "md"]
101+
const strokeWidthMul = strokeWidth ?? 1
76102
switch (pattern) {
77103
case "grid":
78-
return patternGrid(color, sizeMul)
104+
return patternGrid(color, strokeWidthMul, sizeMul)
79105
case "lines":
80-
return patternLines(color, sizeMul)
106+
return patternLines(color, strokeWidthMul, sizeMul)
81107
case "dots":
82-
return patternDots(color, sizeMul)
108+
return patternDots(color, strokeWidthMul, sizeMul)
83109
default:
84110
warnInvalidPropValue("Background", "pattern", pattern, ["grid", "lines", "dots"]);
85-
return patternGrid(color, sizeMul)
111+
return patternGrid(color, strokeWidthMul, sizeMul)
86112
}
87-
}, [pattern, color, size])
113+
}, [pattern, color, strokeWidth, size])
88114

89115

90116
const patternRef = useRef<SVGPatternElement>(null)
91117
const svgRef = useRef<SVGSVGElement>(null)
92118

93-
// Set pattern shift
119+
// Set pattern transform
94120
const viewport = grapherContext.controller.getViewport()
95-
useEffect(() => {
121+
122+
const s = useCallbackState({viewport, angle, patternTransform})
123+
const updatePatternTransform = useCallback(() => {
96124
if (patternRef.current == null || svgRef.current == null) return
97-
patternRef.current.setAttribute("x", String(-viewport.centerX * viewport.zoom + svgRef.current.clientWidth / 2))
98-
patternRef.current.setAttribute("y", String(-viewport.centerY * viewport.zoom + svgRef.current.clientHeight / 2))
99-
})
125+
const x = -s.viewport.centerX * s.viewport.zoom + svgRef.current.clientWidth / 2, y = -s.viewport.centerY * s.viewport.zoom + svgRef.current.clientHeight / 2
126+
patternRef.current.setAttribute("patternTransform", `translate(${x} ${y})` + (s.patternTransform || (s.angle ? ` rotate(${s.angle})` : "")))
127+
}, [])
128+
129+
// Update it on every render
130+
useEffect(updatePatternTransform)
131+
132+
// And on resize
133+
useEffect(() => {
134+
if (svgRef.current == null) return
135+
const obs = new ResizeObserver(updatePatternTransform)
136+
obs.observe(svgRef.current)
137+
138+
return () => obs.disconnect()
139+
}, [updatePatternTransform])
100140

101141
return <BackgroundDiv id={id} className={cx(BACKGROUND_CLASS, className)}>
102142
<svg ref={svgRef} width={"100%"} height={"100%"}>
+50-50
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,71 @@
11
import React from "react";
22
import {Pattern} from "./Background";
33

4-
export function patternGrid(color: string | undefined, sizeMul: number): Pattern {
4+
export function patternGrid(color: string | undefined, strokeWidthMul: number, sizeMul: number): Pattern {
55
if (color == null) color = "#E4E4EA"
6-
return [<rect key="pattern" x="0" y="0" width="100%" height="100%" fill="none" stroke={color} strokeWidth="3"/>, 20, 20 * sizeMul]
6+
return [<rect key="pattern" x="0" y="0" width="100%" height="100%" fill="none" stroke={color} strokeWidth={3 * strokeWidthMul}/>, 20, 20 * sizeMul]
77
}
88

9-
export function patternLines(color: string | undefined, sizeMul: number): Pattern {
9+
export function patternLines(color: string | undefined, strokeWidthMul: number, sizeMul: number): Pattern {
1010
if (color == null) color = "#DFDFE8"
1111
return [<>
12-
<line x1="49" y1="51" x2="51" y2="49" strokeWidth="1" fill="none" stroke={color}/>
13-
<line x1="44" y1="51" x2="51" y2="44" strokeWidth="1" fill="none" stroke={color}/>
14-
<line x1="39" y1="51" x2="51" y2="39" strokeWidth="1" fill="none" stroke={color}/>
15-
<line x1="34" y1="51" x2="51" y2="34" strokeWidth="1" fill="none" stroke={color}/>
16-
<line x1="29" y1="51" x2="51" y2="29" strokeWidth="1" fill="none" stroke={color}/>
17-
<line x1="24" y1="51" x2="51" y2="24" strokeWidth="1" fill="none" stroke={color}/>
18-
<line x1="19" y1="51" x2="51" y2="19" strokeWidth="1" fill="none" stroke={color}/>
19-
<line x1="14" y1="51" x2="51" y2="14" strokeWidth="1" fill="none" stroke={color}/>
20-
<line x1="09" y1="51" x2="51" y2="09" strokeWidth="1" fill="none" stroke={color}/>
21-
<line x1="04" y1="51" x2="51" y2="04" strokeWidth="1" fill="none" stroke={color}/>
12+
<line x1="49" y1="51" x2="51" y2="49" strokeWidth={strokeWidthMul} fill="none" stroke={color}/>
13+
<line x1="44" y1="51" x2="51" y2="44" strokeWidth={strokeWidthMul} fill="none" stroke={color}/>
14+
<line x1="39" y1="51" x2="51" y2="39" strokeWidth={strokeWidthMul} fill="none" stroke={color}/>
15+
<line x1="34" y1="51" x2="51" y2="34" strokeWidth={strokeWidthMul} fill="none" stroke={color}/>
16+
<line x1="29" y1="51" x2="51" y2="29" strokeWidth={strokeWidthMul} fill="none" stroke={color}/>
17+
<line x1="24" y1="51" x2="51" y2="24" strokeWidth={strokeWidthMul} fill="none" stroke={color}/>
18+
<line x1="19" y1="51" x2="51" y2="19" strokeWidth={strokeWidthMul} fill="none" stroke={color}/>
19+
<line x1="14" y1="51" x2="51" y2="14" strokeWidth={strokeWidthMul} fill="none" stroke={color}/>
20+
<line x1="09" y1="51" x2="51" y2="09" strokeWidth={strokeWidthMul} fill="none" stroke={color}/>
21+
<line x1="04" y1="51" x2="51" y2="04" strokeWidth={strokeWidthMul} fill="none" stroke={color}/>
2222

23-
<line x1="-1" y1="51" x2="51" y2="-1" strokeWidth="1" fill="none" stroke={color}/>
23+
<line x1="-1" y1="51" x2="51" y2="-1" strokeWidth={strokeWidthMul} fill="none" stroke={color}/>
2424

25-
<line x1="-1" y1="46" x2="46" y2="-1" strokeWidth="1" fill="none" stroke={color}/>
26-
<line x1="-1" y1="41" x2="41" y2="-1" strokeWidth="1" fill="none" stroke={color}/>
27-
<line x1="-1" y1="36" x2="36" y2="-1" strokeWidth="1" fill="none" stroke={color}/>
28-
<line x1="-1" y1="31" x2="31" y2="-1" strokeWidth="1" fill="none" stroke={color}/>
29-
<line x1="-1" y1="26" x2="26" y2="-1" strokeWidth="1" fill="none" stroke={color}/>
30-
<line x1="-1" y1="21" x2="21" y2="-1" strokeWidth="1" fill="none" stroke={color}/>
31-
<line x1="-1" y1="16" x2="16" y2="-1" strokeWidth="1" fill="none" stroke={color}/>
32-
<line x1="-1" y1="11" x2="11" y2="-1" strokeWidth="1" fill="none" stroke={color}/>
33-
<line x1="-1" y1="06" x2="06" y2="-1" strokeWidth="1" fill="none" stroke={color}/>
34-
<line x1="-1" y1="01" x2="01" y2="-1" strokeWidth="1" fill="none" stroke={color}/>
25+
<line x1="-1" y1="46" x2="46" y2="-1" strokeWidth={strokeWidthMul} fill="none" stroke={color}/>
26+
<line x1="-1" y1="41" x2="41" y2="-1" strokeWidth={strokeWidthMul} fill="none" stroke={color}/>
27+
<line x1="-1" y1="36" x2="36" y2="-1" strokeWidth={strokeWidthMul} fill="none" stroke={color}/>
28+
<line x1="-1" y1="31" x2="31" y2="-1" strokeWidth={strokeWidthMul} fill="none" stroke={color}/>
29+
<line x1="-1" y1="26" x2="26" y2="-1" strokeWidth={strokeWidthMul} fill="none" stroke={color}/>
30+
<line x1="-1" y1="21" x2="21" y2="-1" strokeWidth={strokeWidthMul} fill="none" stroke={color}/>
31+
<line x1="-1" y1="16" x2="16" y2="-1" strokeWidth={strokeWidthMul} fill="none" stroke={color}/>
32+
<line x1="-1" y1="11" x2="11" y2="-1" strokeWidth={strokeWidthMul} fill="none" stroke={color}/>
33+
<line x1="-1" y1="06" x2="06" y2="-1" strokeWidth={strokeWidthMul} fill="none" stroke={color}/>
34+
<line x1="-1" y1="01" x2="01" y2="-1" strokeWidth={strokeWidthMul} fill="none" stroke={color}/>
3535
</>, 50, 80 * sizeMul]
3636
}
3737

38-
export function patternDots(color: string | undefined, sizeMul: number): Pattern {
38+
export function patternDots(color: string | undefined, strokeWidthMul: number, sizeMul: number): Pattern {
3939
if (color == null) color = "#E8E8EF"
4040
return [<React.Fragment key="pattern">
41-
<circle cx="05" cy="05" r="2" fill={color} stroke="none"/>
42-
<circle cx="15" cy="05" r="2" fill={color} stroke="none"/>
43-
<circle cx="25" cy="05" r="2" fill={color} stroke="none"/>
44-
<circle cx="35" cy="05" r="2" fill={color} stroke="none"/>
45-
<circle cx="45" cy="05" r="2" fill={color} stroke="none"/>
41+
<circle cx="05" cy="05" r={strokeWidthMul * 2} fill={color} stroke="none"/>
42+
<circle cx="15" cy="05" r={strokeWidthMul * 2} fill={color} stroke="none"/>
43+
<circle cx="25" cy="05" r={strokeWidthMul * 2} fill={color} stroke="none"/>
44+
<circle cx="35" cy="05" r={strokeWidthMul * 2} fill={color} stroke="none"/>
45+
<circle cx="45" cy="05" r={strokeWidthMul * 2} fill={color} stroke="none"/>
4646

47-
<circle cx="05" cy="15" r="2" fill={color} stroke="none"/>
48-
<circle cx="15" cy="15" r="2" fill={color} stroke="none"/>
49-
<circle cx="25" cy="15" r="2" fill={color} stroke="none"/>
50-
<circle cx="35" cy="15" r="2" fill={color} stroke="none"/>
51-
<circle cx="45" cy="15" r="2" fill={color} stroke="none"/>
47+
<circle cx="05" cy="15" r={strokeWidthMul * 2} fill={color} stroke="none"/>
48+
<circle cx="15" cy="15" r={strokeWidthMul * 2} fill={color} stroke="none"/>
49+
<circle cx="25" cy="15" r={strokeWidthMul * 2} fill={color} stroke="none"/>
50+
<circle cx="35" cy="15" r={strokeWidthMul * 2} fill={color} stroke="none"/>
51+
<circle cx="45" cy="15" r={strokeWidthMul * 2} fill={color} stroke="none"/>
5252

53-
<circle cx="05" cy="25" r="2" fill={color} stroke="none"/>
54-
<circle cx="15" cy="25" r="2" fill={color} stroke="none"/>
55-
<circle cx="25" cy="25" r="2" fill={color} stroke="none"/>
56-
<circle cx="35" cy="25" r="2" fill={color} stroke="none"/>
57-
<circle cx="45" cy="25" r="2" fill={color} stroke="none"/>
53+
<circle cx="05" cy="25" r={strokeWidthMul * 2} fill={color} stroke="none"/>
54+
<circle cx="15" cy="25" r={strokeWidthMul * 2} fill={color} stroke="none"/>
55+
<circle cx="25" cy="25" r={strokeWidthMul * 2} fill={color} stroke="none"/>
56+
<circle cx="35" cy="25" r={strokeWidthMul * 2} fill={color} stroke="none"/>
57+
<circle cx="45" cy="25" r={strokeWidthMul * 2} fill={color} stroke="none"/>
5858

59-
<circle cx="05" cy="35" r="2" fill={color} stroke="none"/>
60-
<circle cx="15" cy="35" r="2" fill={color} stroke="none"/>
61-
<circle cx="25" cy="35" r="2" fill={color} stroke="none"/>
62-
<circle cx="35" cy="35" r="2" fill={color} stroke="none"/>
63-
<circle cx="45" cy="35" r="2" fill={color} stroke="none"/>
59+
<circle cx="05" cy="35" r={strokeWidthMul * 2} fill={color} stroke="none"/>
60+
<circle cx="15" cy="35" r={strokeWidthMul * 2} fill={color} stroke="none"/>
61+
<circle cx="25" cy="35" r={strokeWidthMul * 2} fill={color} stroke="none"/>
62+
<circle cx="35" cy="35" r={strokeWidthMul * 2} fill={color} stroke="none"/>
63+
<circle cx="45" cy="35" r={strokeWidthMul * 2} fill={color} stroke="none"/>
6464

65-
<circle cx="05" cy="45" r="2" fill={color} stroke="none"/>
66-
<circle cx="15" cy="45" r="2" fill={color} stroke="none"/>
67-
<circle cx="25" cy="45" r="2" fill={color} stroke="none"/>
68-
<circle cx="35" cy="45" r="2" fill={color} stroke="none"/>
69-
<circle cx="45" cy="45" r="2" fill={color} stroke="none"/>
65+
<circle cx="05" cy="45" r={strokeWidthMul * 2} fill={color} stroke="none"/>
66+
<circle cx="15" cy="45" r={strokeWidthMul * 2} fill={color} stroke="none"/>
67+
<circle cx="25" cy="45" r={strokeWidthMul * 2} fill={color} stroke="none"/>
68+
<circle cx="35" cy="45" r={strokeWidthMul * 2} fill={color} stroke="none"/>
69+
<circle cx="45" cy="45" r={strokeWidthMul * 2} fill={color} stroke="none"/>
7070
</React.Fragment>, 50, 50 * sizeMul]
7171
}

0 commit comments

Comments
 (0)