react-plot
Version:
Library of React components to render SVG 2D plots.
161 lines • 6.13 kB
JavaScript
import { euclidean } from 'ml-distance-euclidean';
import { useLegend } from './contexts/legendContext.js';
import { usePlotContext } from './contexts/plotContext.js';
import { validateAxis } from './utils.js';
export function usePosition(config) {
const { axisContext, plotWidth, plotHeight } = usePlotContext();
const { x, y, xAxis, yAxis } = config;
const [xScale, yScale] = validateAxis(axisContext, xAxis, yAxis, {
onlyOrthogonal: true,
});
return {
x: convertValue(x, plotWidth, xScale),
y: convertValue(y, plotHeight, yScale),
};
}
export function usePointsPosition(config) {
const { axisContext, plotWidth, plotHeight } = usePlotContext();
const { points, xAxis, yAxis } = config;
const [xScale, yScale] = validateAxis(axisContext, xAxis, yAxis, {
onlyOrthogonal: true,
});
return points
.map((point) => `${convertValue(point.x, plotWidth, xScale)},${convertValue(point.y, plotHeight, yScale)}`)
.join(' ');
}
export function useRectanglePosition(config) {
const { axisContext, plotWidth, plotHeight } = usePlotContext();
const { x1, y1, x2, y2, xAxis, yAxis } = config;
const [xScale, yScale] = validateAxis(axisContext, xAxis, yAxis, {
onlyOrthogonal: true,
});
return {
x: convertMinValue(x1, x2, plotWidth, xScale),
y: convertMinValue(y1, y2, plotHeight, yScale),
width: convertDimensions(x1, x2, plotWidth, xScale),
height: convertDimensions(y1, y2, plotHeight, yScale),
};
}
export function useEllipsePosition(config) {
const { axisContext, plotWidth, plotHeight } = usePlotContext();
const { cx, cy, rx, ry, xAxis, yAxis } = config;
const [xScale, yScale] = validateAxis(axisContext, xAxis, yAxis, {
onlyOrthogonal: true,
});
return {
cx: convertValue(cx, plotWidth, xScale),
cy: convertValue(cy, plotHeight, yScale),
rx: convertValueAbs(rx, plotWidth, xScale),
ry: convertValueAbs(ry, plotHeight, yScale),
};
}
export function useBoxPlotPosition(config) {
const { axisContext, plotWidth, plotHeight } = usePlotContext();
const { min, max, q1, median, q3, width, y, xAxis, yAxis } = config;
const [xScale, yScale] = validateAxis(axisContext, xAxis, yAxis, {
onlyOrthogonal: true,
});
const horizontal = ['top', 'bottom'].includes(axisContext[xAxis]?.position);
return {
min: convertValue(min, plotWidth, xScale),
max: convertValue(max, plotWidth, xScale),
q1: convertValue(q1, plotWidth, xScale),
median: convertValue(median, plotWidth, xScale),
q3: convertValue(q3, plotWidth, xScale),
y: convertValue(y, plotHeight, yScale),
width: convertValueAbs(width, plotHeight, yScale),
horizontal,
};
}
export function useDirectedEllipsePosition(config) {
const { axisContext, plotWidth, plotHeight } = usePlotContext();
const { x1: oldX1, y1: oldY1, x2: oldX2, y2: oldY2, width, xAxis, yAxis, } = config;
const [xScale, yScale] = validateAxis(axisContext, xAxis, yAxis, {
onlyOrthogonal: true,
});
const { x1, y1, x2, y2 } = {
x1: convertValue(oldX1, plotWidth, xScale),
x2: convertValue(oldX2, plotWidth, xScale),
y1: convertValue(oldY1, plotWidth, yScale),
y2: convertValue(oldY2, plotWidth, yScale),
};
const { cx, cy } = {
cx: (x1 + x2) / 2,
cy: (y1 + y2) / 2,
};
const rotation = (y1 > y2 ? -1 : 1) *
(x1 > x2 ? -1 : 1) *
Math.asin(euclidean([x1, y1], [x1, cy]) / euclidean([x1, y1], [cx, cy]));
const { widthX, widthY } = {
widthX: (Math.sin(rotation) * convertValueAbs(width, plotHeight, xScale)) / 2,
widthY: (Math.cos(rotation) * convertValueAbs(width, plotHeight, yScale)) / 2,
};
return {
cx,
cy,
rx: euclidean([x1, y1], [x2, y2]) / 2,
ry: euclidean([0, 0], [widthX, widthY]),
rotation: radsToDegs(rotation),
};
}
function radsToDegs(rad) {
return (rad * 180) / Math.PI;
}
// convert functions
function convertString(value, total) {
return value.endsWith('%')
? (Number(value.slice(0, -1)) * total) / 100
: Number(value);
}
function convertValue(value, total, scale) {
if (scale === undefined)
return 0;
return typeof value === 'number' ? scale(value) : convertString(value, total);
}
function convertMinValue(value1, value2, total, scale) {
if (scale === undefined)
return 0;
return Math.min(typeof value2 === 'number' ? scale(value2) : convertString(value2, total), typeof value1 === 'number' ? scale(value1) : convertString(value1, total));
}
function convertValueAbs(value, total, scale) {
if (scale === undefined)
return 0;
return typeof value === 'number'
? Math.abs(scale(0) - scale(value))
: Math.abs(convertString(value, total));
}
function convertToPx(value, total, scale) {
if (scale === undefined)
return 0;
return typeof value === 'number'
? scale(value) - scale(0)
: convertString(value, total);
}
function convertDimensions(value1, value2, total, scale) {
if (scale === undefined)
return 0;
return Math.abs((typeof value2 === 'number'
? scale(value2)
: convertString(value2, total)) -
(typeof value1 === 'number'
? scale(value1)
: convertString(value1, total)));
}
// other hooks
export function useIsSeriesVisible(id) {
const [legendState] = useLegend();
const value = legendState.labels.find((label) => label.id === id);
return value ? value.isVisible : true;
}
export function useShift(options) {
const { axisContext, plotWidth, plotHeight } = usePlotContext();
const { xAxis, yAxis, xShift, yShift } = options;
const [xScale, yScale] = validateAxis(axisContext, xAxis, yAxis, {
onlyOrthogonal: true,
});
return {
xShift: convertToPx(xShift, plotWidth, xScale),
yShift: convertToPx(yShift, plotHeight, yScale),
};
}
//# sourceMappingURL=hooks.js.map