我想使用 Chart.js 重写 vizwit ,但我很难弄清楚如何让日期/时间图表交互工作。如果您尝试在 此演示 中选择一个日期范围,您会看到它过滤了其他图表。我如何让 Chart.js 让我在它的时间刻度图表上选择一个范围?似乎默认情况下它只允许我点击特定的日期点。
谢谢你的时间。
原文由 Tobias Fünke 发布,翻译遵循 CC BY-SA 4.0 许可协议
我想使用 Chart.js 重写 vizwit ,但我很难弄清楚如何让日期/时间图表交互工作。如果您尝试在 此演示 中选择一个日期范围,您会看到它过滤了其他图表。我如何让 Chart.js 让我在它的时间刻度图表上选择一个范围?似乎默认情况下它只允许我点击特定的日期点。
谢谢你的时间。
原文由 Tobias Fünke 发布,翻译遵循 CC BY-SA 4.0 许可协议
对于所有对 Jony Adamits 解决方案感兴趣的人,我基于他的实现创建了一个 ChartJs 插件。此外,我修复了一些与调整图表大小和检测所选数据点有关的小问题。
随意使用它或为它创建一个插件 github repo。
import "chart.js";
import {Chart} from 'chart.js';
import {ChartJsPluginRangeSelect} from "./chartjs-plugin-range-select";
Chart.pluginService.register(new ChartJsPluginRangeSelect());
let chartOptions = rangeSelect: {
onSelectionChanged: (result: Array<Array<any>>) => {
console.log(result);
}
}
import {Chart, ChartSize, PluginServiceGlobalRegistration, PluginServiceRegistrationOptions} from "chart.js";
interface ChartJsPluginRangeSelectExtendedOptions {
rangeSelect?: RangeSelectOptions;
}
interface RangeSelectOptions {
onSelectionChanged?: (filteredDataSets: Array<Array<any>>) => void;
fillColor?: string | CanvasGradient | CanvasPattern;
cursorColor?: string | CanvasGradient | CanvasPattern;
cursorWidth?: number;
state?: RangeSelectState;
}
interface RangeSelectState {
canvas: HTMLCanvasElement;
}
interface ActiveSelection {
x: number;
w: number;
}
export class ChartJsPluginRangeSelect implements PluginServiceRegistrationOptions, PluginServiceGlobalRegistration {
public id = 'rangeSelect';
beforeInit(chartInstance: Chart, options?: any) {
const opts = (chartInstance.config.options as ChartJsPluginRangeSelectExtendedOptions);
if (opts.rangeSelect) {
const canvas = this.createOverlayCanvas(chartInstance);
opts.rangeSelect = Object.assign({}, opts.rangeSelect, {state: {canvas: canvas}});
chartInstance.canvas.parentElement.prepend(canvas);
}
}
resize(chartInstance: Chart, newChartSize: ChartSize, options?: any) {
const rangeSelectOptions = (chartInstance.config.options as ChartJsPluginRangeSelectExtendedOptions).rangeSelect;
if (rangeSelectOptions) {
rangeSelectOptions.state.canvas.width = newChartSize.width;
rangeSelectOptions.state.canvas.height = newChartSize.height;
}
}
destroy(chartInstance: Chart) {
const rangeSelectOptions = (chartInstance.config.options as ChartJsPluginRangeSelectExtendedOptions).rangeSelect;
if (rangeSelectOptions) {
rangeSelectOptions.state.canvas.remove();
delete rangeSelectOptions.state;
}
}
private createOverlayCanvas(chart: Chart): HTMLCanvasElement {
const rangeSelectOptions = (chart.config.options as ChartJsPluginRangeSelectExtendedOptions).rangeSelect;
const overlay = this.createOverlayHtmlCanvasElement(chart);
const ctx = overlay.getContext('2d');
let selection: ActiveSelection = {x: 0, w: 0};
let isDragging = false;
chart.canvas.addEventListener('pointerdown', evt => {
const rect = chart.canvas.getBoundingClientRect();
selection.x = this.getXInChartArea(evt.clientX - rect.left, chart);
isDragging = true;
});
chart.canvas.addEventListener('pointerleave', evt => {
if (!isDragging) {
ctx.clearRect(0, 0, overlay.width, overlay.height);
}
});
chart.canvas.addEventListener('pointermove', evt => {
ctx.clearRect(0, 0, chart.canvas.width, chart.canvas.height);
const chartContentRect = chart.canvas.getBoundingClientRect();
const currentX = this.getXInChartArea(evt.clientX - chartContentRect.left, chart);
if (isDragging) {
selection.w = currentX - selection.x;
ctx.fillStyle = rangeSelectOptions.fillColor || '#00000044';
ctx.fillRect(selection.x, chart.chartArea.top, selection.w, chart.chartArea.bottom - chart.chartArea.top);
} else {
const cursorWidth = rangeSelectOptions.cursorWidth || 1;
ctx.fillStyle = rangeSelectOptions.cursorColor || '#00000088';
ctx.fillRect(currentX, chart.chartArea.top, cursorWidth, chart.chartArea.bottom - chart.chartArea.top);
}
});
chart.canvas.addEventListener('pointerup', evt => {
const onSelectionChanged = rangeSelectOptions.onSelectionChanged;
if (onSelectionChanged) {
onSelectionChanged(this.getDataSetDataInSelection(selection, chart));
}
selection = {w: 0, x: 0};
isDragging = false;
ctx.clearRect(0, 0, overlay.width, overlay.height);
});
return overlay;
}
private createOverlayHtmlCanvasElement(chartInstance: Chart): HTMLCanvasElement {
const overlay = document.createElement('canvas');
overlay.style.position = 'absolute';
overlay.style.pointerEvents = 'none';
overlay.width = chartInstance.canvas.width;
overlay.height = chartInstance.canvas.height;
return overlay;
}
private getXInChartArea(val: number, chartInstance: Chart) {
return Math.min(Math.max(val, chartInstance.chartArea.left), chartInstance.chartArea.right);
}
private getDataSetDataInSelection(selection: ActiveSelection, chartInstance: Chart): Array<any> {
const result = [];
const xMin = Math.min(selection.x, selection.x + selection.w);
const xMax = Math.max(selection.x, selection.x + selection.w);
for (let i = 0; i < chartInstance.data.datasets.length; i++) {
result[i] = chartInstance.getDatasetMeta(i)
.data
.filter(data => xMin <= data._model.x && xMax >= data._model.x)
.map(data => chartInstance.data.datasets[i].data[data._index]);
}
return result;
}
}
原文由 Kali 发布,翻译遵循 CC BY-SA 4.0 许可协议
13 回答12.8k 阅读
7 回答2k 阅读
3 回答1.1k 阅读✓ 已解决
2 回答1.2k 阅读✓ 已解决
6 回答922 阅读✓ 已解决
2 回答1.4k 阅读✓ 已解决
6 回答1.1k 阅读
在@jordanwillis 和您的回答的基础上,您可以通过在图表顶部放置另一个画布来轻松实现您想要的任何效果。
只需将
pointer-events:none
添加到它的样式中,以确保它不会干扰图表的事件。无需使用注释插件。
例如(在此示例中
canvas
是原始图表画布,而overlay
是放置在顶部的新画布):请注意,我们将我们的事件和坐标基于原始画布,但我们在叠加层上绘制。这样我们就不会弄乱图表的功能。