UNPKG

chartjs-plugin-cursors

Version:

Chart.js plugin to draw and sync cursor lines

279 lines 12.4 kB
const defaultOptions = { enabled: false, visible: true, positions: [], line: { color: "blue", width: 1, dashPattern: [], }, }; const getXScale = (chart) => { const meta = chart.getDatasetMeta(0); return chart.data.datasets.length && meta.xAxisID ? chart.scales[meta.xAxisID] : null; }; const getYScale = (chart) => { const meta = chart.getDatasetMeta(0); return chart.data.datasets.length && meta.yAxisID ? chart.scales[meta.yAxisID] : null; }; const CursorsPlugin = { id: "cursors", afterInit(chart) { var _a, _b, _c, _d, _e; if (!((_b = (_a = chart.options) === null || _a === void 0 ? void 0 : _a.scales) === null || _b === void 0 ? void 0 : _b.x)) { return; } const xScaleType = chart.options.scales.x.type; if (![ "linear", "time", "timeseries", "category", "logarithmic", "realtime", ].includes(xScaleType)) { return; } if (!((_c = chart.options) === null || _c === void 0 ? void 0 : _c.plugins)) { chart.options.plugins = {}; } if (((_d = chart.options.plugins) === null || _d === void 0 ? void 0 : _d.cursors) === undefined) { chart.options.plugins.cursors = defaultOptions; } if (!chart.options.plugins.cursors.enabled) { return; } let positions = []; if (chart.options.plugins.cursors.positions) { positions = chart.options.plugins.cursors.positions.map((position) => { var _a, _b; return ({ previousX: undefined, previousY: undefined, x: (_a = position.x) !== null && _a !== void 0 ? _a : 0, y: (_b = position.y) !== null && _b !== void 0 ? _b : 0, }); }); } chart.cursors = { enabled: true, visible: (_e = chart.options.plugins.cursors.visible) !== null && _e !== void 0 ? _e : true, suppressUpdate: false, ignoreNextEvents: 0, selected: -1, positions, startTime: undefined, centreCursors: this.centreCursors, hitTest: (x, y, threshold) => this.hitTestCursor(chart, x, y, threshold), setStartTime: (time) => { chart.cursors.startTime = time; }, }; }, afterEvent(chart, args) { var _a, _b, _c, _d, _e, _f, _g; if (!((_a = chart.cursors) === null || _a === void 0 ? void 0 : _a.enabled) || chart.cursors.suppressUpdate || !((_c = (_b = chart.options) === null || _b === void 0 ? void 0 : _b.scales) === null || _c === void 0 ? void 0 : _c.x)) { return; } // do nothing if xScale is not linear, time, timeseries, category, logarithmic or realtime const xScaleType = chart.options.scales.x.type; if (![ "linear", "time", "timeseries", "category", "logarithmic", "realtime", ].includes(xScaleType)) { return; } // if startTime is not set, check options else do nothing if (chart.cursors.startTime === undefined && xScaleType === "realtime") { chart.cursors.startTime = (_e = (_d = chart.options.plugins) === null || _d === void 0 ? void 0 : _d.cursors) === null || _e === void 0 ? void 0 : _e.startTime; if (chart.cursors.startTime === undefined) { return; } } const xScale = getXScale(chart); const yScale = getYScale(chart); if (!xScale || !yScale) { return; } if (chart.cursors.ignoreNextEvents > 0) { chart.cursors.ignoreNextEvents -= 1; return; } const e = args.event; if (!e.x || !e.y) { return false; } // used to do a check for cursor outside chart here switch (e.type) { case "mousedown": const selected = chart.cursors.hitTest(e.x, e.y, 10); chart.cursors.selected = selected; break; case "mouseup": chart.cursors.selected = -1; break; } if (chart.cursors.positions && chart.cursors.selected >= chart.cursors.positions.length) { chart.cursors.selected = -1; } if (chart.cursors.selected >= 0) { chart.cursors.positions[chart.cursors.selected].x = (_f = xScale.getValueForPixel(e.x)) !== null && _f !== void 0 ? _f : 0; chart.cursors.positions[chart.cursors.selected].y = (_g = yScale.getValueForPixel(e.y)) !== null && _g !== void 0 ? _g : 0; } chart.draw(); }, afterDraw(chart) { var _a, _b, _c, _d, _e, _f, _g; if (!((_a = chart.cursors) === null || _a === void 0 ? void 0 : _a.enabled)) { return false; } const newVisible = (_c = (_b = chart.options.plugins) === null || _b === void 0 ? void 0 : _b.cursors) === null || _c === void 0 ? void 0 : _c.visible; if (!newVisible) { chart.cursors.visible = false; return false; } // if visible has changed, centre cursors if (chart.cursors.visible !== newVisible) { chart.cursors.visible = newVisible; this.centreCursors(chart); } const xScaleType = (_e = (_d = chart.options.scales) === null || _d === void 0 ? void 0 : _d.x) === null || _e === void 0 ? void 0 : _e.type; if (xScaleType === "realtime" && chart.cursors.startTime === undefined) { chart.cursors.startTime = (_g = (_f = chart.options.plugins) === null || _f === void 0 ? void 0 : _f.cursors) === null || _g === void 0 ? void 0 : _g.startTime; if (chart.cursors.startTime === undefined) { return false; } } this.drawCursors(chart); return true; }, drawCursors(chart) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v; if (!((_a = chart.cursors) === null || _a === void 0 ? void 0 : _a.enabled) || !chart.cursors.visible) { return; } const xScale = getXScale(chart); const yScale = getYScale(chart); if (!xScale || !yScale) { return; } const xScaleType = (_c = (_b = chart.options.scales) === null || _b === void 0 ? void 0 : _b.x) === null || _c === void 0 ? void 0 : _c.type; if ((_d = chart.cursors.positions) === null || _d === void 0 ? void 0 : _d.some((p) => p.x === undefined || p.y === undefined)) { this.centreCursors(chart); chart.draw(); } const xMinPixel = xScale.getPixelForValue(xScale.min); const xMaxPixel = xScale.getPixelForValue(xScale.max); const yMinPixel = yScale.getPixelForValue(yScale.min); const yMaxPixel = yScale.getPixelForValue(yScale.max); const lineWidth = (_h = (_g = (_f = (_e = chart.options.plugins) === null || _e === void 0 ? void 0 : _e.cursors) === null || _f === void 0 ? void 0 : _f.line) === null || _g === void 0 ? void 0 : _g.width) !== null && _h !== void 0 ? _h : 1; const color = (_m = (_l = (_k = (_j = chart.options.plugins) === null || _j === void 0 ? void 0 : _j.cursors) === null || _k === void 0 ? void 0 : _k.line) === null || _l === void 0 ? void 0 : _l.color) !== null && _m !== void 0 ? _m : "blue"; const dashPattern = ((_r = (_q = (_p = (_o = chart.options.plugins) === null || _o === void 0 ? void 0 : _o.cursors) === null || _p === void 0 ? void 0 : _p.line) === null || _q === void 0 ? void 0 : _q.dashPattern) !== null && _r !== void 0 ? _r : []); (_s = chart.cursors.positions) === null || _s === void 0 ? void 0 : _s.forEach((cursor, index) => { if (cursor.x === undefined || cursor.y === undefined) { return; } const cursorX = xScale.getPixelForValue(cursor.x); const cursorY = yScale.getPixelForValue(cursor.y); let xString = cursor.x.toFixed(3); if (xScaleType === "realtime" && chart.cursors.startTime !== undefined) { const secondsSinceStart = ((cursor.x - chart.cursors.startTime) / 1000).toFixed(2); xString = `(${secondsSinceStart}s`; } const ctx = chart.ctx; ctx.save(); ctx.fillStyle = color; ctx.font = "12px Arial"; if (chart.cursors.selected === index) { ctx.setLineDash([]); ctx.fillText(`${xString}, ${cursor.y.toFixed(3)})`, cursorX + 5, cursorY - 5); } else { ctx.beginPath(); ctx.arc(cursorX, cursorY, 3, 0, 2 * Math.PI); ctx.fill(); ctx.fillText(`${index + 1}`, cursorX - 12, cursorY + 12); } // Draw vertical and horizontal lines ctx.beginPath(); ctx.setLineDash(dashPattern); ctx.lineWidth = lineWidth; ctx.strokeStyle = color; ctx.moveTo(cursorX, yMaxPixel); ctx.lineTo(cursorX, yMinPixel); ctx.moveTo(xMinPixel, cursorY); ctx.lineTo(xMaxPixel, cursorY); ctx.stroke(); ctx.restore(); }); const afterMoveCallback = (_v = (_u = (_t = chart.options.plugins) === null || _t === void 0 ? void 0 : _t.cursors) === null || _u === void 0 ? void 0 : _u.callbacks) === null || _v === void 0 ? void 0 : _v.afterMove; if (afterMoveCallback && chart.cursors.positions) { const changed = chart.cursors.positions.some((cursor) => cursor.previousX !== cursor.x || cursor.previousY !== cursor.y); if (changed) { const positions = chart.cursors.positions.map((p, i) => { var _a, _b, _c, _d; return ({ index: i, x: parseFloat((_b = (_a = p.x) === null || _a === void 0 ? void 0 : _a.toFixed(2)) !== null && _b !== void 0 ? _b : "0"), y: parseFloat((_d = (_c = p.y) === null || _c === void 0 ? void 0 : _c.toFixed(2)) !== null && _d !== void 0 ? _d : "0"), }); }); afterMoveCallback(positions); chart.cursors.positions.forEach((cursor) => { cursor.previousX = cursor.x; cursor.previousY = cursor.y; }); } } }, hitTestCursor(chart, x, y, threshold) { const xScale = getXScale(chart); const yScale = getYScale(chart); if (!xScale || !yScale || !chart.cursors.positions) return -1; return chart.cursors.positions.findIndex((cursor) => { if (cursor.x === undefined || cursor.y === undefined) return false; const cursorX = xScale.getPixelForValue(cursor.x); const cursorY = yScale.getPixelForValue(cursor.y); const distance = Math.sqrt(Math.pow((x - cursorX), 2) + Math.pow((y - cursorY), 2)); return distance < threshold; }); }, centreCursors(chart) { var _a; const xScale = getXScale(chart); const yScale = getYScale(chart); if (!xScale || !yScale) { return; } const xDistance = xScale.max - xScale.min; const yDistance = yScale.max - yScale.min; const xPrecision = xDistance <= 5 ? 1 : 0; const yPrecision = yDistance <= 5 ? 1 : 0; (_a = chart.cursors.positions) === null || _a === void 0 ? void 0 : _a.forEach(function (cursor) { cursor.x = parseFloat(((xScale.min + xScale.max) / 2).toFixed(xPrecision)); cursor.y = parseFloat(((yScale.min + yScale.max) / 2).toFixed(yPrecision)); }); }, }; export default CursorsPlugin; //# sourceMappingURL=index.js.map