如何在JFreeChart中实现两次鼠标选择显示双十字准线?
JFreeChart实现双十字准线方案
JFreeChart原生仅支持单条十字准线,要实现两次点击后显示两条独立十字准线,需要通过捕获鼠标点击事件保存坐标 + 自定义叠加绘制来实现,以下是具体思路和代码:
核心思路
- 记录两次鼠标点击对应的**域轴(X轴)和范围轴(Y轴)**数据点坐标
- 通过
ChartPanel的Overlay叠加层,在图表绘制完成后额外绘制两条十字准线 - 处理多次点击的逻辑(比如超过两次时,将最早的十字准线替换为最新点击的位置)
代码实现
1. 十字准线位置存储类
用于保存两次点击的数据点坐标:
class CrosshairPositions { private Double domain1, range1; private Double domain2, range2; // 添加新的点击位置,超过两次时自动替换最早的记录 public void addPosition(double domain, double range) { if (domain1 == null) { domain1 = domain; range1 = range; } else if (domain2 == null) { domain2 = domain; range2 = range; } else { // 重置:把第一条移到第二条的位置,新位置作为第二条 domain1 = domain2; range1 = range2; domain2 = domain; range2 = range; } } // Getter方法 public Double getDomain1() { return domain1; } public Double getRange1() { return range1; } public Double getDomain2() { return domain2; } public Double getRange2() { return range2; } }
2. 捕获鼠标点击事件
通过ChartMouseListener监听图表点击,获取点击位置对应的真实数据值:
CrosshairPositions crosshairPositions = new CrosshairPositions(); ChartPanel chartPanel = new ChartPanel(yourChart); // 替换成你的JFreeChart实例 chartPanel.addChartMouseListener(new ChartMouseListener() { @Override public void chartMouseClicked(ChartMouseEvent event) { ChartEntity entity = event.getEntity(); // 仅处理数据点的点击事件 if (entity instanceof XYItemEntity) { XYItemEntity itemEntity = (XYItemEntity) entity; XYDataset dataset = itemEntity.getDataset(); int seriesIdx = itemEntity.getSeriesIndex(); int itemIdx = itemEntity.getItem(); // 获取点击位置对应的域轴和范围轴数值 double domainValue = dataset.getXValue(seriesIdx, itemIdx); double rangeValue = dataset.getYValue(seriesIdx, itemIdx); crosshairPositions.addPosition(domainValue, rangeValue); chartPanel.repaint(); // 触发重绘,更新十字准线 } } @Override public void chartMouseMoved(ChartMouseEvent event) { // 无需处理鼠标移动事件 } });
3. 叠加绘制双十字准线
使用Overlay在图表上绘制两条自定义十字准线,支持区分样式(比如颜色、虚线):
chartPanel.addOverlay(new Overlay() { @Override public void paintOverlay(Graphics2D g2, ChartPanel panel) { XYPlot plot = (XYPlot) yourChart.getPlot(); Rectangle2D dataArea = panel.getScreenDataArea(); // 绘制第一条十字准线(红色虚线) if (crosshairPositions.getDomain1() != null && crosshairPositions.getRange1() != null) { drawCustomCrosshair(g2, plot, dataArea, crosshairPositions.getDomain1(), crosshairPositions.getRange1(), Color.RED, new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0f, new float[]{5f,5f}, 0f)); } // 绘制第二条十字准线(蓝色实线) if (crosshairPositions.getDomain2() != null && crosshairPositions.getRange2() != null) { drawCustomCrosshair(g2, plot, dataArea, crosshairPositions.getDomain2(), crosshairPositions.getRange2(), Color.BLUE, new BasicStroke(1.5f)); } } // 通用绘制十字准线方法 private void drawCustomCrosshair(Graphics2D g2, XYPlot plot, Rectangle2D dataArea, double domainVal, double rangeVal, Color color, Stroke stroke) { // 将数据坐标转换为屏幕坐标 ValueAxis domainAxis = plot.getDomainAxis(); ValueAxis rangeAxis = plot.getRangeAxis(); double screenX = domainAxis.valueToJava2D(domainVal, dataArea, plot.getDomainAxisEdge()); double screenY = rangeAxis.valueToJava2D(rangeVal, dataArea, plot.getRangeAxisEdge()); // 保存原有绘图状态,避免影响其他绘制 Stroke originalStroke = g2.getStroke(); Color originalColor = g2.getColor(); // 设置当前十字线样式 g2.setStroke(stroke); g2.setColor(color); // 绘制垂直十字线(贯穿整个数据区高度) g2.draw(new Line2D.Double(screenX, dataArea.getMinY(), screenX, dataArea.getMaxY())); // 绘制水平十字线(贯穿整个数据区宽度) g2.draw(new Line2D.Double(dataArea.getMinX(), screenY, dataArea.getMaxX(), screenY)); // 恢复原有绘图状态 g2.setStroke(originalStroke); g2.setColor(originalColor); } });
4. 可选:关闭原生十字准线
如果不需要原生的单条十字准线,可关闭:
XYPlot plot = (XYPlot) yourChart.getPlot(); plot.setDomainCrosshairVisible(false); plot.setRangeCrosshairVisible(false);
注意事项
- 代码中
yourChart需要替换为你自己创建的JFreeChart实例 - 可根据需求调整十字线的颜色、粗细、虚线样式
- 若需要支持更多十字准线,只需扩展
CrosshairPositions的存储逻辑即可
内容的提问来源于stack exchange,提问作者mahesh bhagirath
相关产品推荐
相关产品推荐

