react-plot
Version:
Library of React components to render SVG 2D plots.
172 lines • 6.22 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
import { euclidean } from 'ml-distance-euclidean';
import { useEffect, useRef } from 'react';
import { closestPoint, toNumber } from '../utils.js';
const HORIZONTAL = new Set(['bottom', 'top']);
function infoFromEvent(event, axisContext, stateSeries, target) {
const { clientX, clientY, movementX, movementY } = event;
const { left, top } = target.getBoundingClientRect();
// Calculate coordinates
const xPosition = clientX - left;
const yPosition = clientY - top;
const coordinates = {};
const clampedCoordinates = {};
const domains = {};
const movement = {};
for (const key in axisContext) {
const { scale, clampInDomain, position, domain } = axisContext[key];
if (HORIZONTAL.has(position)) {
coordinates[key] = toNumber(scale.invert(xPosition));
movement[key] = toNumber(scale.invert(movementX)) - domain[0];
}
else {
coordinates[key] = toNumber(scale.invert(yPosition));
movement[key] = toNumber(scale.invert(movementY)) - domain[1];
}
clampedCoordinates[key] = clampInDomain(coordinates[key]);
domains[key] = domain;
}
return {
event,
coordinates,
clampedCoordinates,
movement,
getClosest(method) {
return closestCalculation(method, { x: xPosition, y: yPosition }, stateSeries, axisContext);
},
domains,
};
}
function closestCalculation(method, coordinates, stateSeries, axisContext) {
const series = {};
switch (method) {
case 'x': {
for (const { id, x, data, label } of stateSeries) {
if (data) {
const point = closestPoint(data, coordinates, (point, pos) => {
const { scale } = axisContext[x.axisId];
const xVal = pos[x.axisId];
return Math.abs(scale(point.x) - xVal);
});
series[id] = { point, label, axis: axisContext[x.axisId] };
}
}
break;
}
case 'y': {
for (const { id, y, data, label } of stateSeries) {
if (data) {
const point = closestPoint(data, coordinates, (point, pos) => {
const { scale } = axisContext[y.axisId];
const yVal = pos[y.axisId];
return Math.abs(scale(point.y) - yVal);
});
series[id] = { point, label, axis: axisContext[y.axisId] };
}
}
break;
}
case 'euclidean': {
for (const { id, x, y, data, label } of stateSeries) {
if (data) {
const point = closestPoint(data, coordinates, (point, pos) => {
const { scale: xScale } = axisContext[x.axisId];
const { scale: yScale } = axisContext[y.axisId];
const xVal = pos[x.axisId];
const yVal = pos[y.axisId];
return euclidean([xScale(point.x), yScale(point.y)], [xVal, yVal]);
});
series[id] = {
point,
label,
axis: { x: axisContext[x.axisId], y: axisContext[y.axisId] },
};
}
}
break;
}
default: {
throw new Error(`Unknown distance name: ${method}`);
}
}
return series;
}
const nativeEventMap = {
pointerenter: 'onPointerEnter',
pointerdown: 'onPointerDown',
pointermove: 'onPointerMove',
pointerup: 'onPointerUp',
pointerleave: 'onPointerLeave',
click: 'onClick',
dblclick: 'onDoubleClick',
wheel: 'onWheel',
};
const nativeEventNames = [
'pointerenter',
'pointerdown',
'pointermove',
'pointerup',
'pointerleave',
'click',
'dblclick',
'wheel',
];
const globalNativeEventNames = [
'pointermove',
'pointerup',
];
export default function Tracking({ plotId, plotEvents, stateSeries, axisContext, plotHeight, plotWidth, }) {
const rectRef = useRef(null);
const plotDataRef = useRef({
plotId,
plotEvents,
stateSeries,
axisContext,
plotHeight,
plotWidth,
});
useEffect(() => {
plotDataRef.current = {
plotId,
plotEvents,
stateSeries,
axisContext,
plotHeight,
plotWidth,
};
}, [axisContext, plotEvents, plotHeight, plotId, plotWidth, stateSeries]);
useEffect(() => {
const rect = rectRef.current;
if (!rect)
return;
function eventListener(nativeEvent) {
if (nativeEvent.type === 'pointerdown') {
for (const pointerEvent of globalNativeEventNames) {
window.addEventListener(pointerEvent, eventListener);
}
}
else if (nativeEvent.type === 'pointerup') {
for (const pointerEvent of globalNativeEventNames) {
window.removeEventListener(pointerEvent, eventListener);
}
}
const info = infoFromEvent(nativeEvent, plotDataRef.current.axisContext, plotDataRef.current.stateSeries, rect);
plotEvents.handleEvent(plotId, nativeEventMap[nativeEvent.type], info);
}
for (const eventName of nativeEventNames) {
rect.addEventListener(eventName, eventListener);
}
return () => {
for (const eventName of nativeEventNames) {
rect.removeEventListener(eventName, eventListener);
}
for (const eventName of globalNativeEventNames) {
window.removeEventListener(eventName, eventListener);
}
};
}, [plotId, plotEvents]);
return (_jsx("rect", { ref: rectRef, width: plotWidth, height: plotHeight, style: {
fillOpacity: 0,
} }));
}
//# sourceMappingURL=Tracking.js.map