Tob*_*nke 11 javascript chart.js
我想用Chart.js 重写vizwit,我很难弄清楚如何让日期/时间图表交互起作用.如果您尝试在此演示中选择日期范围,您会看到它会过滤其他图表.如何让Chart.js让我在其时间尺度图表上选择这样的范围?它似乎默认情况下只允许我点击特定的日期点.
谢谢你的时间.
Jon*_*mit 23
在@ jordanwillis和你的答案的基础上,你可以轻松地实现你想要的任何东西,方法是在你的图表上放置另一个画布.
只需添加pointer-events:none它的样式,以确保它不会影响图表的事件.
无需使用注释插件.
例如(在此示例中canvas是原始图表画布,overlay并且您的新画布位于顶部):
var options = {
type: 'line',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderWidth: 1
},
{
label: '# of Points',
data: [7, 11, 5, 8, 3, 7],
borderWidth: 1
}
]
},
options: {
scales: {
yAxes: [{
ticks: {
reverse: false
}
}]
}
}
}
var canvas = document.getElementById('chartJSContainer');
var ctx = canvas.getContext('2d');
var chart = new Chart(ctx, options);
var overlay = document.getElementById('overlay');
var startIndex = 0;
overlay.width = canvas.width;
overlay.height = canvas.height;
var selectionContext = overlay.getContext('2d');
var selectionRect = {
w: 0,
startX: 0,
startY: 0
};
var drag = false;
canvas.addEventListener('pointerdown', evt => {
const points = chart.getElementsAtEventForMode(evt, 'index', {
intersect: false
});
startIndex = points[0]._index;
const rect = canvas.getBoundingClientRect();
selectionRect.startX = evt.clientX - rect.left;
selectionRect.startY = chart.chartArea.top;
drag = true;
// save points[0]._index for filtering
});
canvas.addEventListener('pointermove', evt => {
const rect = canvas.getBoundingClientRect();
if (drag) {
const rect = canvas.getBoundingClientRect();
selectionRect.w = (evt.clientX - rect.left) - selectionRect.startX;
selectionContext.globalAlpha = 0.5;
selectionContext.clearRect(0, 0, canvas.width, canvas.height);
selectionContext.fillRect(selectionRect.startX,
selectionRect.startY,
selectionRect.w,
chart.chartArea.bottom - chart.chartArea.top);
} else {
selectionContext.clearRect(0, 0, canvas.width, canvas.height);
var x = evt.clientX - rect.left;
if (x > chart.chartArea.left) {
selectionContext.fillRect(x,
chart.chartArea.top,
1,
chart.chartArea.bottom - chart.chartArea.top);
}
}
});
canvas.addEventListener('pointerup', evt => {
const points = chart.getElementsAtEventForMode(evt, 'index', {
intersect: false
});
drag = false;
console.log('implement filter between ' + options.data.labels[startIndex] + ' and ' + options.data.labels[points[0]._index]);
});Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.0/Chart.js"></script>
<body>
<canvas id="overlay" width="600" height="400" style="position:absolute;pointer-events:none;"></canvas>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
</body>Run Code Online (Sandbox Code Playgroud)
请注意,我们将事件和坐标基于原始画布,但我们在叠加层上绘制.这样我们就不会弄乱图表的功能.
对于所有对 Jony Adamits 解决方案感兴趣的人,我根据他的实现创建了一个 ChartJs 插件。另外,我修复了一些有关调整图表大小和检测所选数据点的小问题。
请随意使用它或为其创建插件 github 存储库。
import "chart.js";
import {Chart} from 'chart.js';
import {ChartJsPluginRangeSelect} from "./chartjs-plugin-range-select";
Chart.pluginService.register(new ChartJsPluginRangeSelect());
Run Code Online (Sandbox Code Playgroud)
let chartOptions = rangeSelect: {
onSelectionChanged: (result: Array<Array<any>>) => {
console.log(result);
}
}
Run Code Online (Sandbox Code Playgroud)
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;
}
}
Run Code Online (Sandbox Code Playgroud)