京公网安备 11010802034615号
经营许可证编号:京B2-20210330
【gloomyfish】Box zoom on Category Plot in JFreeChart
Background:
currently JFreechart did not support domain axis zoom with category plot, the domain and value axis is zoomable only for XYPlot, however when category dataset contains huge categories while user could not select some categories to view by box zoom. the category plot is becoming un-usable one for user. obviously user would like to see box zoom with category plot.
Summary:
from box zoom on XYPlot in JFreechart, i read all relevant source code about zooming in JFreeChart and i found that there is a way to support box zoom on category plot by following steps:
a. support drawing the zoom rectangle in category data area (plot)
b. identify the domain axis and each category start point on domain axis.
c. measure the each category start point with zoom box
d. remove any categories if the start coordinate value is out of zoom rectangle.
Basic Design:
in order to support box zoom on category plot, we need to overwrite following methods which has been implemented in ChartPanel by JFreeChart:
1. mousePressed() - record the start zoom point
2. mouseDragged() - draw zoom box rectangle on category plot
3. mouseReleased() - zoom in the categories which is selected in rectangle.
4. paintComponent() - supporting to draw zoom rectangle
Run Result:
mouse selected rectangle - box zoom
zooming the rectangle
Code Implementation:
package test.it; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.event.MouseEvent; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import javax.swing.JPanel; import javax.swing.JPopupMenu; import org.jfree.chart.ChartPanel; import org.jfree.chart.ChartRenderingInfo; import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.Zoomable; import org.jfree.chart.renderer.category.BarRenderer; import org.jfree.data.category.CategoryDataset; import org.jfree.data.category.DefaultCategoryDataset; import org.jfree.experimental.chart.plot.CombinedCategoryPlot; import org.jfree.ui.ApplicationFrame; import org.jfree.ui.RefineryUtilities; /** * A demo for the {@link CombinedCategoryPlot} class. */ public class CombinedCategoryPlotDemo1 extends ApplicationFrame { /** * */ private static final long serialVersionUID = 8114720685282689420L; /** * Creates a new demo instance. * * @param title the frame title. */ public CombinedCategoryPlotDemo1(String title) { super(title); JPanel chartPanel = createDemoPanel(); chartPanel.setPreferredSize(new java.awt.Dimension(500, 270)); setContentPane(chartPanel); } /** * Creates a dataset. * * @return A dataset. */ public static CategoryDataset createDataset2() { DefaultCategoryDataset result = new DefaultCategoryDataset(); String series1 = "Third"; String series2 = "Fourth"; String type1 = "Type 1"; String type2 = "Type 2"; String type3 = "Type 3"; String type4 = "Type 4"; String type5 = "Type 5"; String type6 = "Type 6"; String type7 = "Type 7"; String type8 = "Type 8"; result.addValue(11.0, series1, type1); result.addValue(14.0, series1, type2); result.addValue(13.0, series1, type3); result.addValue(15.0, series1, type4); result.addValue(15.0, series1, type5); result.addValue(17.0, series1, type6); result.addValue(17.0, series1, type7); result.addValue(18.0, series1, type8); result.addValue(15.0, series2, type1); result.addValue(17.0, series2, type2); result.addValue(16.0, series2, type3); result.addValue(18.0, series2, type4); result.addValue(14.0, series2, type5); result.addValue(14.0, series2, type6); result.addValue(12.0, series2, type7); result.addValue(11.0, series2, type8); return result; } /** * Creates a chart. * * @return A chart. */ private static JFreeChart createChart() { CategoryDataset dataset2 = createDataset2(); NumberAxis rangeAxis2 = new NumberAxis("Value"); rangeAxis2.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); CategoryAxis domainAxis = new CategoryAxis("Category"); CategoryPlot plot = new CategoryPlot(dataset2, domainAxis, new NumberAxis("Range"), new BarRenderer()); JFreeChart result = new JFreeChart( "Combined Domain Category Plot Demo", new Font("SansSerif", Font.BOLD, 12), plot, true); return result; } /** * Creates a panel for the demo (used by SuperDemo.java). * * @return A panel. */ public static JPanel createDemoPanel() { JFreeChart chart = createChart(); return new ChartPanel(chart){ /** * */ private static final long serialVersionUID = -4857405671081534981L; private Point2D zoomPoint = null; private Rectangle2D zoomRectangle = null; private boolean fillZoomRectangle = true; private JPopupMenu popup; private Paint zoomOutlinePaint = Color.blue; private Paint zoomFillPaint = new Color(0, 0, 255, 63); public void mousePressed(MouseEvent e) { if (e.isPopupTrigger()) { if(popup == null) { popup = createPopupMenu(true,true,true,true); } if (this.popup != null) { displayPopupMenu(e.getX(), e.getY()); return; } } PlotOrientation orientation = ((Zoomable)this.getChart().getPlot()).getOrientation(); System.out.println("Orientation --->> " + orientation.toString()); if(orientation == PlotOrientation.HORIZONTAL) { return; } if (this.zoomRectangle == null) { Rectangle2D screenDataArea = getScreenDataArea(e.getX(), e.getY()); if (screenDataArea != null) { this.zoomPoint = getPointInRectangle(e.getX(), e.getY(), screenDataArea); } else { this.zoomPoint = null; } } } private Point2D getPointInRectangle(int x, int y, Rectangle2D area) { double xx = Math.max(area.getMinX(), Math.min(x, area.getMaxX())); double yy = Math.max(area.getMinY(), Math.min(y, area.getMaxY())); return new Point2D.Double(xx, yy); } public void mouseReleased(MouseEvent e) { if (e.isPopupTrigger()) { if(popup == null) { popup = createPopupMenu(true,true,true,true); } if (this.popup != null) { displayPopupMenu(e.getX(), e.getY()); zoomRectangle = null; return; } } if(this.getChart().getCategoryPlot().getDataset().getColumnCount() < 2) { repaint(); zoomRectangle = null; return; } if (zoomRectangle == null) { // do nothing } else { // do something here. zoom rectangle with data System.out.println("fucking........"); System.out.println("reset dataset here"); CategoryDataset dataset = this.getChart().getCategoryPlot().getDataset(); System.out.println("category count = " + dataset.getColumnCount()); System.out.println("category type = " + dataset.getRowCount()); Comparable[] rowKeys = new Comparable[dataset.getRowCount()]; rowKeys[0] = dataset.getRowKey(0); rowKeys[1] = dataset.getRowKey(1); Comparable[] columnKeys = new Comparable[dataset.getColumnCount()]; for(int i=0; i<columnKeys.length; i++) { columnKeys[i] = dataset.getColumnKey(i); } double[] endValueAxis = new double[dataset.getColumnCount()]; double[] startValueAxis = new double[dataset.getColumnCount()]; double minX = zoomRectangle.getBounds2D().getMinX(); double maxX = zoomRectangle.getBounds2D().getMaxX(); CategoryPlot plot = this.getChart().getCategoryPlot(); ChartRenderingInfo info = this.getChartRenderingInfo(); Rectangle2D dataArea = info.getPlotInfo().getDataArea(); CategoryAxis categoryaxis=this.getChart().getCategoryPlot().getDomainAxis(); for(int i=0; i<dataset.getColumnCount(); i++) { endValueAxis[i] = categoryaxis.getCategoryEnd(i, dataset.getColumnCount(), dataArea, plot.getDomainAxisEdge()); startValueAxis[i] = categoryaxis.getCategoryStart(i, dataset.getColumnCount(), dataArea, plot.getDomainAxisEdge()); } for(int i=0; i<endValueAxis.length; i++) { if(minX > startValueAxis[i] || maxX < startValueAxis[i]) { DefaultCategoryDataset defaultDataset = (DefaultCategoryDataset)dataset; defaultDataset.removeValue(rowKeys[0], columnKeys[i]); defaultDataset.removeValue(rowKeys[1], columnKeys[i]); } } } zoomRectangle = null; } public void mouseDragged(MouseEvent e) { // if no initial zoom point was set, ignore dragging... if (this.zoomPoint == null) { return; } Graphics2D g2 = (Graphics2D) getGraphics(); Rectangle2D scaledDataArea = getScreenDataArea((int) this.zoomPoint.getX(), (int) this.zoomPoint.getY()); double ymax = Math.min(e.getY(), scaledDataArea.getMaxY()); double xmax = Math.min(e.getX(), scaledDataArea.getMaxX()); this.zoomRectangle = new Rectangle2D.Double(this.zoomPoint.getX(), this.zoomPoint.getY(), xmax - this.zoomPoint.getX(), ymax - this.zoomPoint.getY()); repaint(); g2.dispose(); } public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D) g.create(); drawZoomRectangle(g2, false); g2.dispose(); } private void drawZoomRectangle(Graphics2D g2, boolean xor) { if (this.zoomRectangle != null) { if (xor) { // Set XOR mode to draw the zoom rectangle g2.setXORMode(Color.gray); } if (this.fillZoomRectangle) { g2.setPaint(this.zoomFillPaint); g2.fill(this.zoomRectangle); } else { g2.setPaint(this.zoomOutlinePaint); g2.draw(this.zoomRectangle); } if (xor) { // Reset to the default 'overwrite' mode g2.setPaintMode(); } } } }; } /** * Starting point for the demonstration application. * * @param args ignored. */ public static void main(String[] args) { String title = "Combined Category Plot Demo 1"; CombinedCategoryPlotDemo1 demo = new CombinedCategoryPlotDemo1(title); demo.pack(); RefineryUtilities.centerFrameOnScreen(demo); demo.setVisible(true); } }
I just adding some codes in the category demo program with JFreeChart, the code implementation need to be improved in future.
Drawback:
could not restore to original dataset since i just removed the categories, one way is to implement this like this:
just take back original dataset when there is only one category in plot.
Discussion:
...
数据分析咨询请扫描二维码
若不方便扫码,搜微信号:CDAshujufenxi
在统计学分析、实验研究、业务数据复盘过程中,单因素方差分析是检验自变量对因变量是否存在显著影响的核心方法。其中,两个水平 ...
2026-05-26【核心关键词】算法、客户、大数据、互联网、调优、建模、模型优化、机器学习、评分卡模型、模型开发、智能风控、业务场景、数 ...
2026-05-26 很多数据分析师写过无数个 SELECT,但当被问到“新建一张表,该如何定义字段类型来保证数据质量”“创建视图和存储物理表有 ...
2026-05-26在数据清洗、统计分析与数据质量检测工作中,箱型图(又称箱线图、Box Plot)是最直观、最高效的可视化分析工具之一。相较于柱状 ...
2026-05-25在大数据分析、数据清洗、质量管控、风险监测等领域,异常数据识别是保障数据质量、确保分析结论精准、规避业务决策失误的核心基 ...
2026-05-25 很多数据分析师精通Excel函数和透视表,但当被问到“数据从哪里来”“表和视图有什么区别”“数据库管理系统和SQL是什么关系 ...
2026-05-25数字化经营时代,企业的市场竞争早已从经验决策转向数据决策。门店营收、用户转化、产品销量、成本损耗、存量资产等所有经营行为 ...
2026-05-22在MySQL数据库日常运维、业务数据校验、数据迁移与数据清洗场景中,自增主键ID的连续性校验是一项基础且关键的工作。MySQL的Auto ...
2026-05-22 很多企业团队并非缺乏指标,而是陷入“指标失控”:仪表盘上堆满实时跳动的数据,却无法回答“当前瓶颈在哪、下一步该做什么 ...
2026-05-22【核心关键词】大数据、可视化、存储、架构、客户、离线、产品、同步、实时、数据仓库、数据分析、数据可视化、存储数据、离线 ...
2026-05-21在电商流量红利消退、公域获客成本持续走高的当下,存量用户深度挖掘已成为店铺增收增效的核心抓手。相较于付费投放获取的陌生新 ...
2026-05-21 很多数据分析师每天盯着几十个指标,但当被问到“这套指标要支撑什么业务目标”“指标之间是什么逻辑关系”“业务变化时如何 ...
2026-05-21在数据驱动决策的时代,数据质量直接决定分析结果的可靠性与准确性,而异常值作为数据清洗中的核心痛点,往往会扭曲分析结论、误 ...
2026-05-20 很多数据分析师每天盯着GMV、DAU、转化率,但当被问到“哪些指标在所有行业都适用”“哪些指标只对电商有意义”“二者如何搭 ...
2026-05-20Agent的能力边界,很大程度上取决于其掌握的Skill质量和数量。传统做法是靠人工编写和维护Skill,但这条路很快会遇到瓶颈。业务 ...
2026-05-20在统计分析中,方差分析(ANOVA)是一种常用的假设检验方法,核心用于分析“一个或多个自变量对单个因变量的影响”,广泛应用于 ...
2026-05-19 很多数据分析师每天盯着GMV、DAU、转化率,但当被问到“什么是指标”“指标和维度有什么区别”“如何定义指标值的计算规则和 ...
2026-05-19想高效备考 CDA 一级,拒绝盲目刷题、冗余学习?《CDA 一级教材知识手册》重磅来袭!以官方教材为核心,浓缩 13 章 103 个核心考 ...
2026-05-19在数据统计分析中,卡方检验是一种常用的非参数检验方法,核心用于判断两个或多个分类变量之间是否存在显著关联,广泛应用于市场 ...
2026-05-18在企业数字化转型的浪潮中,很多企业陷入了“技术堆砌”的误区——上线了ERP、CRM、BI等各类系统,积累了海量数据,却依然面临“ ...
2026-05-18