
【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:
...
數(shù)據(jù)分析咨詢請(qǐng)掃描二維碼
若不方便掃碼,搜微信號(hào):CDAshujufenxi
SQL Server 中 CONVERT 函數(shù)的日期轉(zhuǎn)換:從基礎(chǔ)用法到實(shí)戰(zhàn)優(yōu)化 在 SQL Server 的數(shù)據(jù)處理中,日期格式轉(zhuǎn)換是高頻需求 —— 無(wú)論 ...
2025-09-18MySQL 大表拆分與關(guān)聯(lián)查詢效率:打破 “拆分必慢” 的認(rèn)知誤區(qū) 在 MySQL 數(shù)據(jù)庫(kù)管理中,“大表” 始終是性能優(yōu)化繞不開的話題。 ...
2025-09-18CDA 數(shù)據(jù)分析師:表結(jié)構(gòu)數(shù)據(jù) “獲取 - 加工 - 使用” 全流程的賦能者 表結(jié)構(gòu)數(shù)據(jù)(如數(shù)據(jù)庫(kù)表、Excel 表、CSV 文件)是企業(yè)數(shù)字 ...
2025-09-18DSGE 模型中的 Et:理性預(yù)期算子的內(nèi)涵、作用與應(yīng)用解析 動(dòng)態(tài)隨機(jī)一般均衡(Dynamic Stochastic General Equilibrium, DSGE)模 ...
2025-09-17Python 提取 TIF 中地名的完整指南 一、先明確:TIF 中的地名有哪兩種存在形式? 在開始提取前,需先判斷 TIF 文件的類型 —— ...
2025-09-17CDA 數(shù)據(jù)分析師:解鎖表結(jié)構(gòu)數(shù)據(jù)特征價(jià)值的專業(yè)核心 表結(jié)構(gòu)數(shù)據(jù)(以 “行 - 列” 規(guī)范存儲(chǔ)的結(jié)構(gòu)化數(shù)據(jù),如數(shù)據(jù)庫(kù)表、Excel 表、 ...
2025-09-17Excel 導(dǎo)入數(shù)據(jù)含缺失值?詳解 dropna 函數(shù)的功能與實(shí)戰(zhàn)應(yīng)用 在用 Python(如 pandas 庫(kù))處理 Excel 數(shù)據(jù)時(shí),“缺失值” 是高頻 ...
2025-09-16深入解析卡方檢驗(yàn)與 t 檢驗(yàn):差異、適用場(chǎng)景與實(shí)踐應(yīng)用 在數(shù)據(jù)分析與統(tǒng)計(jì)學(xué)領(lǐng)域,假設(shè)檢驗(yàn)是驗(yàn)證研究假設(shè)、判斷數(shù)據(jù)差異是否 “ ...
2025-09-16CDA 數(shù)據(jù)分析師:掌控表格結(jié)構(gòu)數(shù)據(jù)全功能周期的專業(yè)操盤手 表格結(jié)構(gòu)數(shù)據(jù)(以 “行 - 列” 存儲(chǔ)的結(jié)構(gòu)化數(shù)據(jù),如 Excel 表、數(shù)據(jù) ...
2025-09-16MySQL 執(zhí)行計(jì)劃中 rows 數(shù)量的準(zhǔn)確性解析:原理、影響因素與優(yōu)化 在 MySQL SQL 調(diào)優(yōu)中,EXPLAIN執(zhí)行計(jì)劃是核心工具,而其中的row ...
2025-09-15解析 Python 中 Response 對(duì)象的 text 與 content:區(qū)別、場(chǎng)景與實(shí)踐指南 在 Python 進(jìn)行 HTTP 網(wǎng)絡(luò)請(qǐng)求開發(fā)時(shí)(如使用requests ...
2025-09-15CDA 數(shù)據(jù)分析師:激活表格結(jié)構(gòu)數(shù)據(jù)價(jià)值的核心操盤手 表格結(jié)構(gòu)數(shù)據(jù)(如 Excel 表格、數(shù)據(jù)庫(kù)表)是企業(yè)最基礎(chǔ)、最核心的數(shù)據(jù)形態(tài) ...
2025-09-15Python HTTP 請(qǐng)求工具對(duì)比:urllib.request 與 requests 的核心差異與選擇指南 在 Python 處理 HTTP 請(qǐng)求(如接口調(diào)用、數(shù)據(jù)爬取 ...
2025-09-12解決 pd.read_csv 讀取長(zhǎng)浮點(diǎn)數(shù)據(jù)的科學(xué)計(jì)數(shù)法問題 為幫助 Python 數(shù)據(jù)從業(yè)者解決pd.read_csv讀取長(zhǎng)浮點(diǎn)數(shù)據(jù)時(shí)的科學(xué)計(jì)數(shù)法問題 ...
2025-09-12CDA 數(shù)據(jù)分析師:業(yè)務(wù)數(shù)據(jù)分析步驟的落地者與價(jià)值優(yōu)化者 業(yè)務(wù)數(shù)據(jù)分析是企業(yè)解決日常運(yùn)營(yíng)問題、提升執(zhí)行效率的核心手段,其價(jià)值 ...
2025-09-12用 SQL 驗(yàn)證業(yè)務(wù)邏輯:從規(guī)則拆解到數(shù)據(jù)把關(guān)的實(shí)戰(zhàn)指南 在業(yè)務(wù)系統(tǒng)落地過程中,“業(yè)務(wù)邏輯” 是連接 “需求設(shè)計(jì)” 與 “用戶體驗(yàn) ...
2025-09-11塔吉特百貨孕婦營(yíng)銷案例:數(shù)據(jù)驅(qū)動(dòng)下的精準(zhǔn)零售革命與啟示 在零售行業(yè) “流量紅利見頂” 的當(dāng)下,精準(zhǔn)營(yíng)銷成為企業(yè)突圍的核心方 ...
2025-09-11CDA 數(shù)據(jù)分析師與戰(zhàn)略 / 業(yè)務(wù)數(shù)據(jù)分析:概念辨析與協(xié)同價(jià)值 在數(shù)據(jù)驅(qū)動(dòng)決策的體系中,“戰(zhàn)略數(shù)據(jù)分析”“業(yè)務(wù)數(shù)據(jù)分析” 是企業(yè) ...
2025-09-11Excel 數(shù)據(jù)聚類分析:從操作實(shí)踐到業(yè)務(wù)價(jià)值挖掘 在數(shù)據(jù)分析場(chǎng)景中,聚類分析作為 “無(wú)監(jiān)督分組” 的核心工具,能從雜亂數(shù)據(jù)中挖 ...
2025-09-10統(tǒng)計(jì)模型的核心目的:從數(shù)據(jù)解讀到?jīng)Q策支撐的價(jià)值導(dǎo)向 統(tǒng)計(jì)模型作為數(shù)據(jù)分析的核心工具,并非簡(jiǎn)單的 “公式堆砌”,而是圍繞特定 ...
2025-09-10