京公网安备 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
在数据分析、机器学习的实操场景中,聚类分析与主成分分析(PCA)是两种高频使用的统计与数据处理方法。二者常被用于数据预处理 ...
2026-02-24在聚类分析的实操场景中,K-Means算法因其简单高效、易落地的特点,成为处理无监督分类问题的首选工具——无论是用户画像分层、 ...
2026-02-24数字化浪潮下,数据已成为企业核心竞争力,“用数据说话、用数据决策”成为企业发展的核心逻辑。CDA(Certified Data Analyst) ...
2026-02-24CDA一级知识点汇总手册 第五章 业务数据的特征、处理与透视分析考点52:业务数据分析基础考点53:输入和资源需求考点54:业务数 ...
2026-02-23CDA一级知识点汇总手册 第四章 战略与业务数据分析考点43:战略数据分析基础考点44:表格结构数据的使用考点45:输入数据和资源 ...
2026-02-22CDA一级知识点汇总手册 第三章 商业数据分析框架考点27:商业数据分析体系的核心逻辑——BSC五视角框架考点28:战略视角考点29: ...
2026-02-20CDA一级知识点汇总手册 第二章 数据分析方法考点7:基础范式的核心逻辑(本体论与流程化)考点8:分类分析(本体论核心应用)考 ...
2026-02-18第一章:数据分析思维考点1:UVCA时代的特点考点2:数据分析背后的逻辑思维方法论考点3:流程化企业的数据分析需求考点4:企业数 ...
2026-02-16在数据分析、业务决策、科学研究等领域,统计模型是连接原始数据与业务价值的核心工具——它通过对数据的规律提炼、变量关联分析 ...
2026-02-14在SQL查询实操中,SELECT * 与 SELECT 字段1, 字段2,...(指定个别字段)是最常用的两种查询方式。很多开发者在日常开发中,为了 ...
2026-02-14对CDA(Certified Data Analyst)数据分析师而言,数据分析的核心不是孤立解读单个指标数值,而是构建一套科学、完整、贴合业务 ...
2026-02-14在Power BI实操中,函数是实现数据清洗、建模计算、可视化呈现的核心工具——无论是简单的数据筛选、异常值处理,还是复杂的度量 ...
2026-02-13在互联网运营、产品迭代、用户增长等工作中,“留存率”是衡量产品核心价值、用户粘性的核心指标——而次日留存率,作为留存率体 ...
2026-02-13对CDA(Certified Data Analyst)数据分析师而言,指标是贯穿工作全流程的核心载体,更是连接原始数据与业务洞察的关键桥梁。CDA ...
2026-02-13在机器学习建模实操中,“特征选择”是提升模型性能、简化模型复杂度、解读数据逻辑的核心步骤——而随机森林(Random Forest) ...
2026-02-12在MySQL数据查询实操中,按日期分组统计是高频需求——比如统计每日用户登录量、每日订单量、每日销售额,需要按日期分组展示, ...
2026-02-12对CDA(Certified Data Analyst)数据分析师而言,描述性统计是贯穿实操全流程的核心基础,更是从“原始数据”到“初步洞察”的 ...
2026-02-12备考CDA的小伙伴,专属宠粉福利来啦! 不用拼运气抽奖,不用复杂操作,只要转发CDA真题海报到朋友圈集赞,就能免费抱走实用好礼 ...
2026-02-11在数据科学、机器学习实操中,Anaconda是必备工具——它集成了Python解释器、conda包管理器,能快速搭建独立的虚拟环境,便捷安 ...
2026-02-11在Tableau数据可视化实操中,多表连接是高频操作——无论是将“产品表”与“销量表”连接分析产品销量,还是将“用户表”与“消 ...
2026-02-11