Published
Edited
Jan 28, 2022
Insert cell
D3CanvasDrag
Insert cell
import {
render,
component,
jsx,
memo,
forwardRef,
React,
ReactDOM,
createElement,
Children,
createRef,
createContext,
lazy,
Fragment,
StrictMode,
Suspense,
cloneElement,
useCallback,
useContext,
useEffect,
useImperativeHandle,
useLayoutEffect,
useMemo,
useReducer,
useRef,
useState,
useDebugValue,
createPortal,
__SECRET_SWITCHER
} from '@j-f1/react'
Insert cell
d3 = require("d3@7")
Insert cell
viewof chartcanvas = render(() => {
const canvasRef = useRef(null);
const svgRef = useRef(null);

const [context, setContext] = useState(null);
const [currentZoomState, setCurrentZoomState] = useState(d3.zoomIdentity);

// Margins for the svg that will contain the axes
const margin = { top: 20, right: 15, bottom: 60, left: 70 };

// Size of the plot
const outerWidth = 600;
const outerHeight = 800;

// Drawable area inside canvas
const chartWidth = outerWidth - margin.left - margin.right;
const chartHeight = outerHeight - margin.top - margin.bottom;

const dataExample = [{x: 10, y: 20}, {x: 20, y: 30}];
// Init Scales
const xScale = d3.scaleLinear()
.domain([0, d3.max(dataExample, (d) => d.x)])
.range([0, chartWidth])
.nice();
const yScale = d3.scaleLinear()
.domain([0, d3.max(dataExample, (d) => d.y)])
.range([chartHeight, 0])
.nice();

// Init Axis
const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale);

// Zoomed Scales
const zoomedXScale = useMemo(
() => currentZoomState.rescaleX(xScale),
[currentZoomState, xScale]
);

const zoomedYScale = useMemo(
() => currentZoomState.rescaleY(yScale),
[currentZoomState, yScale]
);

// Draw

const drawPoint = (point) => {
context.beginPath();
context.fillStyle = '#0058a3';
const px = zoomedXScale(point.x);
const py = zoomedYScale(point.y);
context.arc(px, py, 5, 0, 2 * Math.PI, true);
context.fill();
};

const draw = () => {
if (context) {
console.log('i draw');
context.save();
context.clearRect(0, 0, chartWidth, chartHeight);
context.beginPath();
dataExample.forEach((point) => {
drawPoint(point);
});
context.fill();
context.restore();
}
};

// D3 functions

// Zoom
const zoomed = d3
.zoom()
.extent([
[0, 0],
[width, chartHeight]
])
.translateExtent([
[0, 0],
[width, chartHeight]
])
.scaleExtent([1, 5]) // This control how much you can unzoom (x0.5) and zoom (x20)
/**
* SVGElement is the svg which the zoom is attached to,
* unknown references the data type in the svg, no data is passed to the svg in this case.
*/
.on('zoom', (e) => {
const zoomState = e.transform;
setCurrentZoomState(zoomState);
});

// Drag

const dragSubject = (e) => {
const x = zoomedXScale.invert(e.x);
const y = zoomedYScale.invert(e.y);
let dx, dy;
const temp = dataExample.find((node) => {
const tempNode = node;
dx = x - tempNode.x;
dy = y - tempNode.y;
if (dx * dx + dy * dy < 2 * 2) {
tempNode.x = zoomedXScale(tempNode.x);
tempNode.y = zoomedYScale(tempNode.y);
return tempNode;
}
});
return temp;
};

const dragStarted = (e) => {
e.subject.x = zoomedXScale.invert(e.x);
e.subject.y = zoomedYScale.invert(e.y);
};

const dragged = (e) => {
e.subject.x = zoomedXScale.invert(e.x);
e.subject.y = zoomedYScale.invert(e.y);
};

const dragEnded = (e) => {
// Update new coordinates to state
};

const drag = d3
.drag()
.subject(dragSubject)
.on('start', dragStarted)
.on('drag', dragged)
.on('end', dragEnded);

// UseEffects
useEffect(() => {
if (canvasRef.current) {
const canvasObj = canvasRef.current;
setContext(canvasObj.getContext('2d'));
}
});

// UseEffect for zoom - Calls zoomed on canvasRef on d3.ZoomEvent

useEffect(() => {
d3.select(canvasRef.current).call(
drag.on('start.draw drag.draw end.draw', draw)
);
}, [drag]);
useEffect(() => {
d3.select(canvasRef.current).call(zoomed).call(draw);
}, [zoomed]);

useEffect(() => {
d3.select(svgRef).append('g')
.call(yAxis)
})
return jsx`
<div style=${{ margin: 'auto', width: outerWidth, height: outerHeight }}>
<svg
ref=${svgRef}
width=${outerWidth}
height=${outerHeight}
style=${{ position: 'absolute' }}
>
<g transform=${`translate(${margin.left}, ${margin.top})`} />
</svg>
<canvas
ref=${canvasRef}
width=${chartWidth}
height=${chartHeight}
style=${{
marginLeft: `${margin.left}px`,
marginTop: `${margin.top}px`,
position: 'absolute'
}}
/>
</div>`;
})
Insert cell
chartcanvas
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more