diff --git a/demo/src/App.js b/demo/src/App.js
index 782471c7..0f5d4065 100644
--- a/demo/src/App.js
+++ b/demo/src/App.js
@@ -124,6 +124,7 @@ class App extends Component {
this.setLargeTree = this.setLargeTree.bind(this);
this.setOrientation = this.setOrientation.bind(this);
this.setPathFunc = this.setPathFunc.bind(this);
+ this.setBackgroundGrid = this.setBackgroundGrid.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleFloatChange = this.handleFloatChange.bind(this);
this.toggleCollapsible = this.toggleCollapsible.bind(this);
@@ -157,6 +158,10 @@ class App extends Component {
this.setState({ pathFunc });
}
+ setBackgroundGrid(backgroundGrid) {
+ this.setState({ backgroundGrid });
+ }
+
handleChange(evt) {
const target = evt.target;
const parsedIntValue = parseInt(target.value, 10);
@@ -389,6 +394,31 @@ class App extends Component {
+
+
Background Grid
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
index 39e68752..38828684 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -21167,4 +21167,4 @@
"dev": true
}
}
-}
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index b31aee39..81bc4969 100644
--- a/package.json
+++ b/package.json
@@ -124,4 +124,4 @@
"typedoc": "^0.19.2",
"typescript": "^4.9.4"
}
-}
+}
\ No newline at end of file
diff --git a/src/Tree/BackgroundGrid.tsx b/src/Tree/BackgroundGrid.tsx
new file mode 100644
index 00000000..a78cd8ef
--- /dev/null
+++ b/src/Tree/BackgroundGrid.tsx
@@ -0,0 +1,81 @@
+import React, { ReactElement } from "react"
+
+export type BackgroundGrid = {
+ type: "dot" | "line" | "custom",
+ thickness?: number,
+ color?: string,
+ gridCellSize?: {width: number, height: number},
+ gridCellFunc?: (options?: BackgroundGrid) => ReactElement
+}
+
+interface BackgroundGridProps extends BackgroundGrid{
+ patternInstanceRef: string //a unique className for d3zoom to specify
+}
+
+/**
+ * helper function to assign default values to `thickness`, `color` and `gridCellSize`, which are required by rendering/zooming bgGrid
+ */
+export const getDefaultBackgroundGridParam = (param: BackgroundGrid | BackgroundGridProps) => {
+ if (param === undefined) return undefined;
+ const {
+ thickness = 2,
+ color = "#bbb",
+ gridCellSize = { width: 24, height: 24 },
+ } = param;
+
+ return {
+ thickness,
+ color,
+ gridCellSize,
+ ...param
+ };
+}
+
+const BackgroundGrid = (props: BackgroundGridProps) => {
+
+ const param = getDefaultBackgroundGridParam(props);
+ const {
+ type,
+ thickness,
+ color,
+ gridCellSize,
+ gridCellFunc
+ } = param;
+
+ return <>
+
+ {
+ type === "dot"
+ ?
+ : null
+ }
+ {
+ type === "line"
+ ? <>
+
+
+ >
+ : null
+ }
+ {
+ type === "custom" && gridCellFunc
+ ? gridCellFunc(param)
+ : null
+ }
+
+
+ >
+}
+
+export default BackgroundGrid;
\ No newline at end of file
diff --git a/src/Tree/index.tsx b/src/Tree/index.tsx
index cc36898a..7f8bf8ae 100644
--- a/src/Tree/index.tsx
+++ b/src/Tree/index.tsx
@@ -12,6 +12,7 @@ import Link from '../Link/index.js';
import { TreeNodeDatum, Point, RawNodeDatum } from '../types/common.js';
import { TreeLinkEventCallback, TreeNodeEventCallback, TreeProps } from './types.js';
import globalCss from '../globalCss.js';
+import BackgroundGrid, { getDefaultBackgroundGridParam } from './backgroundGrid.js';
type TreeState = {
dataRef: TreeProps['data'];
@@ -56,6 +57,7 @@ class Tree extends React.Component {
dimensions: undefined,
centeringTransitionDuration: 800,
dataKey: undefined,
+ backgroundGrid: undefined,
};
state: TreeState = {
@@ -74,6 +76,7 @@ class Tree extends React.Component {
svgInstanceRef = `rd3t-svg-${uuidv4()}`;
gInstanceRef = `rd3t-g-${uuidv4()}`;
+ patternInstanceRef = `rd3t-pattern-${uuidv4()}`;
static getDerivedStateFromProps(nextProps: TreeProps, prevState: TreeState) {
let derivedState: Partial = null;
@@ -113,7 +116,8 @@ class Tree extends React.Component {
this.props.zoomable !== prevProps.zoomable ||
this.props.draggable !== prevProps.draggable ||
this.props.zoom !== prevProps.zoom ||
- this.props.enableLegacyTransitions !== prevProps.enableLegacyTransitions
+ this.props.enableLegacyTransitions !== prevProps.enableLegacyTransitions ||
+ this.props.backgroundGrid !== prevProps.backgroundGrid
) {
// If zoom-specific props change -> rebind listener with new values.
// Or: rebind zoom listeners to new DOM nodes in case legacy transitions were enabled/disabled.
@@ -152,6 +156,7 @@ class Tree extends React.Component {
const { zoomable, scaleExtent, translate, zoom, onUpdate, hasInteractiveNodes } = props;
const svg = select(`.${this.svgInstanceRef}`);
const g = select(`.${this.gInstanceRef}`);
+ const pattern = select(`.${this.patternInstanceRef}`);
// Sets initial offset, so that first pan and zoom does not jump back to default [0,0] coords.
// @ts-ignore
@@ -165,6 +170,7 @@ class Tree extends React.Component {
return (
event.target.classList.contains(this.svgInstanceRef) ||
event.target.classList.contains(this.gInstanceRef) ||
+ event.target.id === 'bgPatternContainer' ||
event.shiftKey
);
}
@@ -179,6 +185,22 @@ class Tree extends React.Component {
}
g.attr('transform', event.transform);
+
+ // gridCellSize is required by zooming
+ const bgGrid = getDefaultBackgroundGridParam(this.props.backgroundGrid);
+ // apply zoom effect onto bgGrid only if specified
+ if (bgGrid) {
+ pattern
+ .attr('x', event.transform.x)
+ .attr('y', event.transform.y)
+ .attr('width', bgGrid.gridCellSize.width * event.transform.k)
+ .attr('height', bgGrid.gridCellSize.height * event.transform.k)
+
+ pattern
+ .selectAll('*')
+ .attr('transform',`scale(${event.transform.k})`)
+ }
+
if (typeof onUpdate === 'function') {
// This callback is magically called not only on "zoom", but on "drag", as well,
// even though event.type == "zoom".
@@ -557,6 +579,14 @@ class Tree extends React.Component {
width="100%"
height="100%"
>
+ {
+ this.props.backgroundGrid
+ ?
+ : null
+ }
', () => {
+ it('renders dot grid elements', () => {
+ const wrapper = render();
+
+ const pattern = wrapper.find('.testingRef');
+ const dot = wrapper.find('.testingRef rect');
+ const bgRect = wrapper.find('#bgPatternContainer');
+
+ expect(dot.length).toBe(1);
+ expect(pattern.length).toBe(1);
+ expect(bgRect.length).toBe(1);
+ })
+
+ it('renders line grid elements', () => {
+ const wrapper = render();
+
+ const pattern = wrapper.find('.testingRef');
+ const lines = wrapper.find('.testingRef line');
+ const bgRect = wrapper.find('#bgPatternContainer');
+
+ expect(lines.length).toBe(2);
+ expect(pattern.length).toBe(1);
+ expect(bgRect.length).toBe(1);
+ })
+
+ it('applies backgroundGrid options to dot grid when specified', () => {
+ const wrapper = render();
+
+ const pattern = wrapper.find('.testingRef');
+ const dot = wrapper.find('.testingRef rect');
+
+ expect(pattern[0].attribs.width).toBe('200');
+ expect(pattern[0].attribs.height).toBe('400');
+ expect(dot[0].attribs.fill).toBe('red');
+ expect(dot[0].attribs.width).toBe('12');
+ expect(dot[0].attribs.height).toBe('12');
+ expect(dot[0].attribs.rx).toBe('12');
+ })
+
+ it('applies backgroundGrid options to line grid when specified', () => {
+ const wrapper = render();
+
+ const pattern = wrapper.find('.testingRef');
+ const lines = wrapper.find('.testingRef line');
+
+ expect(pattern[0].attribs.width).toBe('200');
+ expect(pattern[0].attribs.height).toBe('400');
+ expect(lines[0].attribs.stroke).toBe('red');
+ expect(lines[0].attribs['stroke-width']).toBe('12');
+ expect(lines[0].attribs.x2).toBe('200');
+ expect(lines[1].attribs.stroke).toBe('red');
+ expect(lines[1].attribs['stroke-width']).toBe('12');
+ expect(lines[1].attribs.y2).toBe('400');
+ })
+
+ it('renders custom gridCellFunc when specified', () => {
+ const wrapper = render();
+
+ const pattern = wrapper.find('.testingRef');
+ const circle = wrapper.find('.testingRef circle');
+
+ expect(circle.length).toBe(1);
+ expect(pattern.length).toBe(1);
+ expect(circle[0].attribs.r).toBe('100');
+ expect(circle[0].attribs.cx).toBe('100');
+ expect(circle[0].attribs.cy).toBe('200');
+ expect(circle[0].attribs.stroke).toBe('red');
+ expect(circle[0].attribs['stroke-width']).toBe('12');
+ })
+})
\ No newline at end of file
diff --git a/src/Tree/types.ts b/src/Tree/types.ts
index c631ae5a..e4414a5a 100644
--- a/src/Tree/types.ts
+++ b/src/Tree/types.ts
@@ -10,6 +10,7 @@ import {
RenderCustomNodeElementFn,
TreeNodeDatum,
} from '../types/common.js';
+import { BackgroundGrid } from './backgroundGrid';
export type TreeNodeEventCallback = (
node: HierarchyPointNode,
@@ -200,12 +201,12 @@ export interface TreeProps {
*/
zoomable?: boolean;
- /**
+ /**
* Toggles ability to drag the Tree.
*
* {@link Tree.defaultProps.draggable | Default value}
*/
- draggable?: boolean;
+ draggable?: boolean;
/**
* A floating point number to set the initial zoom level. It is constrained by `scaleExtent`.
@@ -319,4 +320,18 @@ export interface TreeProps {
* {@link Tree.defaultProps.dataKey | Default value}
*/
dataKey?: string;
+
+ /**
+ * Sets a background grid using svg and and a background
+ * there's 2 default type of grid: `dot` and `line`
+ * - `color`: color of each line / dot
+ * - `thickness`: thickness of each line / radius of the each dot
+ * - `gridCellSize`: the space take place by each dot / cross / customRenderFunction
+ *
+ * `type: custom` allows customizing content inside by
+ * passing `gridCellFunc` a function that return a ReactElement(svg elements)
+ *
+ * {@link Tree.defaultProps.backgroundGrid | Default value}
+ */
+ backgroundGrid?: BackgroundGrid;
}