如何在Chart.js中实现画布输入框的时间范围选择与区间高亮?
实现Chart.js时间轴拖动选框与区间高亮
核心思路
通过绝对定位的输入框实现拖动交互,利用Chart.js的坐标转换方法关联像素位置与时间值,再借助annotation插件实现区间颜色填充。
完整代码实现
HTML结构
<div style="position: relative; width: 800px; margin: 0 auto;"> <input type="text" id="startInput" placeholder="开始时间" style="position: absolute; top: 0; left: 50px; z-index: 10;"> <input type="text" id="endInput" placeholder="结束时间" style="position: absolute; top: 0; left: 200px; z-index: 10;"> <canvas id="myChart"></canvas> </div>
CSS样式
input { padding: 4px 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 12px; cursor: grab; } input:active { cursor: grabbing; }
JavaScript逻辑
// 先在HTML中加载依赖脚本 // <script src="https://cdn.jsdelivr.net/npm/chart.js@4.2.1/dist/chart.umd.js"></script> // <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js"></script> // <script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-annotation@2.1.1/dist/chartjs-plugin-annotation.min.js"></script> const ctx = document.getElementById('myChart').getContext('2d'); // 生成模拟时间序列数据 const generateTimeData = () => { const data = []; let currentDate = new Date(); for (let i = 0; i < 30; i++) { data.push({ x: new Date(currentDate.getTime() - (30 - i) * 3600000), // 每小时一个数据点 y: Math.floor(Math.random() * 50) + 10 }); currentDate.setHours(currentDate.getHours() - 1); } return data; }; // 初始化图表 const chart = new Chart(ctx, { type: 'line', data: { datasets: [{ label: '时间序列数据', data: generateTimeData(), borderColor: '#36A2EB', tension: 0.1, fill: false }] }, options: { scales: { x: { type: 'time', time: { unit: 'hour', displayFormats: { hour: 'yyyy-MM-dd HH:mm' } }, position: 'bottom' }, y: { beginAtZero: true } }, plugins: { annotation: { annotations: { highlightRange: { type: 'box', xMin: null, xMax: null, backgroundColor: 'rgba(255, 99, 132, 0.2)', borderColor: 'rgba(255, 99, 132, 0.5)', borderWidth: 1 } } } } }, plugins: [ChartAnnotation] }); // 获取输入框元素 const startInput = document.getElementById('startInput'); const endInput = document.getElementById('endInput'); let draggingElement = null; // 格式化时间显示 const formatTime = (date) => { return date.toISOString().slice(0, 16).replace('T', ' '); }; // 更新输入框位置与对应时间值 const updateInput = (input, pixelX) => { const xAxis = chart.scales.x; // 限制输入框在X轴范围内拖动 const minPixel = xAxis.getPixelForValue(xAxis.min); const maxPixel = xAxis.getPixelForValue(xAxis.max); const clampedPixel = Math.max(minPixel, Math.min(maxPixel, pixelX)); input.style.left = `${clampedPixel}px`; // 转换像素位置为时间值 const timeValue = xAxis.getValueForPixel(clampedPixel); input.value = formatTime(timeValue); // 更新高亮区间 updateHighlightRange(); }; // 更新区间高亮效果 const updateHighlightRange = () => { const startValue = startInput.value ? new Date(startInput.value) : null; const endValue = endInput.value ? new Date(endInput.value) : null; if (startValue && endValue) { const minTime = new Date(Math.min(startValue.getTime(), endValue.getTime())); const maxTime = new Date(Math.max(startValue.getTime(), endValue.getTime())); chart.options.plugins.annotation.annotations.highlightRange.xMin = minTime; chart.options.plugins.annotation.annotations.highlightRange.xMax = maxTime; chart.update(); } }; // 拖动事件处理 const handleMouseDown = (e) => { draggingElement = e.target; }; const handleMouseMove = (e) => { if (!draggingElement) return; const containerRect = ctx.canvas.parentElement.getBoundingClientRect(); const pixelX = e.clientX - containerRect.left; updateInput(draggingElement, pixelX); }; const handleMouseUp = () => { draggingElement = null; }; // 绑定拖动事件 startInput.addEventListener('mousedown', handleMouseDown); endInput.addEventListener('mousedown', handleMouseDown); document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); // 初始化输入框默认位置与值 const initInputs = () => { const xAxis = chart.scales.x; const startPixel = xAxis.getPixelForValue(xAxis.min) + 50; const midPixel = (xAxis.getPixelForValue(xAxis.min) + xAxis.getPixelForValue(xAxis.max)) / 2; updateInput(startInput, startPixel); updateInput(endInput, midPixel); }; // 图表渲染完成后初始化输入框 chart.addEventListener('render', initInputs);
关键细节说明
- 坐标转换:利用Chart.js的
getPixelForValue和getValueForPixel方法,实现像素位置与时间值的双向映射,这是拖动交互的核心。 - 边界限制:通过X轴的最小/最大像素值,限制输入框拖动范围,避免生成无效时间值。
- 区间高亮:使用
chartjs-plugin-annotation插件绘制半透明矩形,实现两输入框之间的区域高亮;也可通过Chart.js的beforeDraw钩子自定义绘制逻辑。 - 时间适配:依托
chartjs-adapter-date-fns插件,实现时间轴的解析与格式化,确保输入框显示格式与X轴刻度一致。
内容的提问来源于stack exchange,提问作者Nit_16
相关产品推荐
相关产品推荐

