
【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
LSTM 模型輸入長度選擇技巧:提升序列建模效能的關(guān)鍵? 在循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN)家族中,長短期記憶網(wǎng)絡(luò)(LSTM)憑借其解決長序列 ...
2025-07-11CDA 數(shù)據(jù)分析師報(bào)考條件詳解與準(zhǔn)備指南? ? 在數(shù)據(jù)驅(qū)動(dòng)決策的時(shí)代浪潮下,CDA 數(shù)據(jù)分析師認(rèn)證愈發(fā)受到矚目,成為眾多有志投身數(shù) ...
2025-07-11數(shù)據(jù)透視表中兩列相乘合計(jì)的實(shí)用指南? 在數(shù)據(jù)分析的日常工作中,數(shù)據(jù)透視表憑借其強(qiáng)大的數(shù)據(jù)匯總和分析功能,成為了 Excel 用戶 ...
2025-07-11尊敬的考生: 您好! 我們誠摯通知您,CDA Level I和 Level II考試大綱將于 2025年7月25日 實(shí)施重大更新。 此次更新旨在確保認(rèn) ...
2025-07-10BI 大數(shù)據(jù)分析師:連接數(shù)據(jù)與業(yè)務(wù)的價(jià)值轉(zhuǎn)化者? ? 在大數(shù)據(jù)與商業(yè)智能(Business Intelligence,簡稱 BI)深度融合的時(shí)代,BI ...
2025-07-10SQL 在預(yù)測(cè)分析中的應(yīng)用:從數(shù)據(jù)查詢到趨勢(shì)預(yù)判? ? 在數(shù)據(jù)驅(qū)動(dòng)決策的時(shí)代,預(yù)測(cè)分析作為挖掘數(shù)據(jù)潛在價(jià)值的核心手段,正被廣泛 ...
2025-07-10數(shù)據(jù)查詢結(jié)束后:分析師的收尾工作與價(jià)值深化? ? 在數(shù)據(jù)分析的全流程中,“query end”(查詢結(jié)束)并非工作的終點(diǎn),而是將數(shù) ...
2025-07-10CDA 數(shù)據(jù)分析師考試:從報(bào)考到取證的全攻略? 在數(shù)字經(jīng)濟(jì)蓬勃發(fā)展的今天,數(shù)據(jù)分析師已成為各行業(yè)爭搶的核心人才,而 CDA(Certi ...
2025-07-09【CDA干貨】單樣本趨勢(shì)性檢驗(yàn):捕捉數(shù)據(jù)背后的時(shí)間軌跡? 在數(shù)據(jù)分析的版圖中,單樣本趨勢(shì)性檢驗(yàn)如同一位耐心的偵探,專注于從單 ...
2025-07-09year_month數(shù)據(jù)類型:時(shí)間維度的精準(zhǔn)切片? ? 在數(shù)據(jù)的世界里,時(shí)間是最不可或缺的維度之一,而year_month數(shù)據(jù)類型就像一把精準(zhǔn) ...
2025-07-09CDA 備考干貨:Python 在數(shù)據(jù)分析中的核心應(yīng)用與實(shí)戰(zhàn)技巧? ? 在 CDA 數(shù)據(jù)分析師認(rèn)證考試中,Python 作為數(shù)據(jù)處理與分析的核心 ...
2025-07-08SPSS 中的 Mann-Kendall 檢驗(yàn):數(shù)據(jù)趨勢(shì)與突變分析的有力工具? ? ? 在數(shù)據(jù)分析的廣袤領(lǐng)域中,準(zhǔn)確捕捉數(shù)據(jù)的趨勢(shì)變化以及識(shí)別 ...
2025-07-08備戰(zhàn) CDA 數(shù)據(jù)分析師考試:需要多久?如何規(guī)劃? CDA(Certified Data Analyst)數(shù)據(jù)分析師認(rèn)證作為國內(nèi)權(quán)威的數(shù)據(jù)分析能力認(rèn)證 ...
2025-07-08LSTM 輸出不確定的成因、影響與應(yīng)對(duì)策略? 長短期記憶網(wǎng)絡(luò)(LSTM)作為循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN)的一種變體,憑借獨(dú)特的門控機(jī)制,在 ...
2025-07-07統(tǒng)計(jì)學(xué)方法在市場(chǎng)調(diào)研數(shù)據(jù)中的深度應(yīng)用? 市場(chǎng)調(diào)研是企業(yè)洞察市場(chǎng)動(dòng)態(tài)、了解消費(fèi)者需求的重要途徑,而統(tǒng)計(jì)學(xué)方法則是市場(chǎng)調(diào)研數(shù) ...
2025-07-07CDA數(shù)據(jù)分析師證書考試全攻略? 在數(shù)字化浪潮席卷全球的當(dāng)下,數(shù)據(jù)已成為企業(yè)決策、行業(yè)發(fā)展的核心驅(qū)動(dòng)力,數(shù)據(jù)分析師也因此成為 ...
2025-07-07剖析 CDA 數(shù)據(jù)分析師考試題型:解鎖高效備考與答題策略? CDA(Certified Data Analyst)數(shù)據(jù)分析師考試作為衡量數(shù)據(jù)專業(yè)能力的 ...
2025-07-04SQL Server 字符串截取轉(zhuǎn)日期:解鎖數(shù)據(jù)處理的關(guān)鍵技能? 在數(shù)據(jù)處理與分析工作中,數(shù)據(jù)格式的規(guī)范性是保證后續(xù)分析準(zhǔn)確性的基礎(chǔ) ...
2025-07-04CDA 數(shù)據(jù)分析師視角:從數(shù)據(jù)迷霧中探尋商業(yè)真相? 在數(shù)字化浪潮席卷全球的今天,數(shù)據(jù)已成為企業(yè)決策的核心驅(qū)動(dòng)力,CDA(Certifie ...
2025-07-04CDA 數(shù)據(jù)分析師:開啟數(shù)據(jù)職業(yè)發(fā)展新征程? ? 在數(shù)據(jù)成為核心生產(chǎn)要素的今天,數(shù)據(jù)分析師的職業(yè)價(jià)值愈發(fā)凸顯。CDA(Certified D ...
2025-07-03