Java tutorial
/* * Copyright (C) 2016 likhachev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package com.ivli.roim.controls; import java.awt.BasicStroke; import java.awt.Cursor; import java.awt.Insets; import java.awt.Paint; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.io.IOException; import java.io.PrintWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.ListIterator; import java.util.stream.Collectors; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.SwingUtilities; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jfree.chart.ChartPanel; import org.jfree.chart.JFreeChart; import org.jfree.chart.entity.ChartEntity; import org.jfree.chart.entity.EntityCollection; import org.jfree.chart.entity.PlotEntity; import org.jfree.chart.entity.XYItemEntity; import org.jfree.chart.event.MarkerChangeEvent; import org.jfree.chart.event.MarkerChangeListener; import org.jfree.chart.plot.ValueMarker; import org.jfree.chart.plot.XYPlot; import org.jfree.data.general.SeriesChangeEvent; import org.jfree.data.general.SeriesChangeListener; import org.jfree.data.xy.XYDataItem; import org.jfree.data.xy.XYSeries; import org.jfree.data.xy.XYSeriesCollection; import org.jfree.ui.Layer; /** * * @author likhachev */ public class ChartControl extends ChartPanel { private static enum MENUS { NOP("NOP", "NOP"), ADD("MARKER_CMD_MARKER_ADD", java.util.ResourceBundle.getBundle("com/ivli/roim/Bundle") .getString("MARKER_COMMAND.MARKER_ADD")), EXPORT_CSV( "MARKER_CMD_EXPORT_CSV", java.util.ResourceBundle.getBundle("com/ivli/roim/Bundle") .getString("MARKER_COMMAND.MARKER_EXPORT_CSV")), DELETE( "MARKER_CMD_MARKER_DELETE", java.util.ResourceBundle.getBundle("com/ivli/roim/Bundle") .getString("MARKER_COMMAND.MARKER_DELETE")), DELETE_ALL( "MARKER_CMD_DELETE_ALL", java.util.ResourceBundle.getBundle("com/ivli/roim/Bundle") .getString( "MARKER_COMMAND.MARKER_DELETE_ALL")), MOVE_TO_MIN( "MARKER_CMD_MOVE_TO_MIN", java.util.ResourceBundle.getBundle( "com/ivli/roim/Bundle") .getString( "MARKER_COMMAND.MOVE_TO_MIN")), // MOVE_TO_MIN_LEFT( "MARKER_CMD_MOVE_TO_MIN_LEFT", java.util.ResourceBundle.getBundle("com/ivli/roim/Bundle").getString("MARKER_COMMAND.MOVE_TO_MIN_LEFT")), // MOVE_TO_MIN_RIGHT("MARKER_CMD_MOVE_TO_MIN_RIGHT", java.util.ResourceBundle.getBundle("com/ivli/roim/Bundle").getString("MARKER_COMMAND.MOVE_TO_MIN_RIGHT")), MOVE_TO_MAX("MARKER_CMD_MOVE_TO_MAX", java.util.ResourceBundle.getBundle("com/ivli/roim/Bundle").getString("MARKER_COMMAND.MOVE_TO_MAX")), // MOVE_TO_MAX_LEFT( "MARKER_CMD_MOVE_TO_MAX_LEFT", java.util.ResourceBundle.getBundle("com/ivli/roim/Bundle").getString("MARKER_COMMAND.MOVE_TO_MAX_LEFT")), // MOVE_TO_MAX_RIGHT("MARKER_CMD_MOVE_TO_MAX_LEFT", java.util.ResourceBundle.getBundle("com/ivli/roim/Bundle").getString("MARKER_COMMAND.MOVE_TO_MAX_RIGHT")), MOVE_TO_MEDIAN("MARKER_CMD_MOVE_TO_MEDIAN", java.util.ResourceBundle.getBundle("com/ivli/roim/Bundle") .getString("MARKER_COMMAND.MOVE_TO_MEDIAN")), FIT_LEFT( "MARKER_CMD_FIT_LEFT", java.util.ResourceBundle.getBundle("com/ivli/roim/Bundle") .getString("MARKER_COMMAND.FIT_LEFT")), FIT_RIGHT("MARKER_CMD_FIT_RIGHT", java.util.ResourceBundle.getBundle("com/ivli/roim/Bundle") .getString("MARKER_COMMAND.FIT_RIGHT")); MENUS(String aC, String aT) { iCommand = aC; iText = aT; } final String iText; final String iCommand; JMenuItem makeItem(ActionListener aL) { JMenuItem ret = new JMenuItem(iText); ret.setActionCommand(iCommand); ret.addActionListener(aL); return ret; } static MENUS translate(String aS) { for (MENUS m : MENUS.values()) if (m.iCommand.equals(aS)) return m; return MENUS.NOP; } } //private XYSeriesCollection iInterSet = new XYSeriesCollection(); /////////////////////////////////////// ChartControl(JFreeChart aChart) { super(aChart); //aChart.getXYPlot().setDataset(INTERPOLATION_DATASET, iInterSet); } void addMarker(DomainMarker aM) { getChart().getXYPlot().addDomainMarker(aM, Layer.FOREGROUND); ValueMarker vm = new ValueMarker(.0); getChart().getXYPlot().addRangeMarker(vm, Layer.FOREGROUND); aM.setLinkedMarker(vm); } void removeMarker(DomainMarker aM) { ListIterator<Interpolation> it = iInterpolations.listIterator(); while (it.hasNext()) { Interpolation i = it.next(); if (i.iLhs == aM || i.iRhs == aM) { it.remove(); i.close(); } } getChart().getXYPlot().removeRangeMarker(aM.getLinkedMarker(), Layer.FOREGROUND); getChart().getXYPlot().removeDomainMarker(aM, Layer.FOREGROUND); } private ChartEntity findEntity(MouseEvent e) { if (null != getChart()) { final Insets insets = getInsets(); final int x = (int) ((e.getX() - insets.left) / this.getScaleX()); final int y = (int) ((e.getY() - insets.top) / this.getScaleY()); if (this.getChartRenderingInfo() != null) { EntityCollection entities = this.getChartRenderingInfo().getEntityCollection(); if (entities != null) return entities.getEntity(x, y); } } return null; } Collection getDomainMarkersForSeries(XYSeries aS) { final XYPlot plot = getChart().getXYPlot(); Collection mrak = plot.getDomainMarkers(Layer.FOREGROUND); return (new ArrayList<DomainMarker>(mrak)).stream().filter((DomainMarker m) -> { return (m.getSeries() == aS); }).collect(Collectors.toList()); } private ValueMarker findMarker(MouseEvent e) { final XYPlot plot = getChart().getXYPlot(); Collection mark = plot.getDomainMarkers(Layer.FOREGROUND); if (null == mark || mark.isEmpty()) return null; final double domainX = plot.getDomainAxis().java2DToValue(e.getX(), getScreenDataArea(), plot.getDomainAxisEdge()); final double Epsilon = plot.getDataRange(plot.getDomainAxis()).getLength() * .01d; for (Object o : mark) { if (o instanceof DomainMarker) { //DomainMarker m = (DomainMarker)o; double val = ((DomainMarker) o).getValue(); if (val >= domainX - Epsilon && val <= domainX + Epsilon) { //getContentPane().setCursor(new Cursor(Cursor.HAND_CURSOR)); return (ValueMarker) o; } } } return null; } @Override public void actionPerformed(ActionEvent e) { super.actionPerformed(e); final XYPlot plot = getChart().getXYPlot(); switch (MENUS.translate(e.getActionCommand())) { case ADD: if (null != iSeries && iSeries instanceof XYSeries) { addMarker(new DomainMarker(iDataItem.getXValue(), iSeries)); } break; case EXPORT_CSV: if (null != iSeries && iSeries instanceof XYSeries) { FileOpenDialog dlg = new FileOpenDialog( java.util.ResourceBundle.getBundle("com/ivli/roim/Bundle").getString("CHOICE_FILE_TO_OPEN"), "csv", //NOI18N "CSV file", //NOI18N false); if (dlg.doModal()) { try (Writer pwr = new PrintWriter(dlg.getFileName())) { //NOI18N for (int i = 0; i < iSeries.getItemCount(); ++i) { XYDataItem xy = iSeries.getDataItem(i); pwr.append(String.format("%f\t%f\n", xy.getXValue(), xy.getYValue())); //NOI18N } pwr.flush(); pwr.close(); } catch (IOException ex) { LOG.throwing(ex); } } } break; case MOVE_TO_MAX: iMarker.setValue(XYSeriesUtilities.getDomainValueOfMaximum(((DomainMarker) iMarker).getSeries())); break;//((DomainMarker)iMarker).moveToMaximum(DomainMarker.MOVETO.GLOBAL)); break; case MOVE_TO_MIN: iMarker.setValue(XYSeriesUtilities.getDomainValueOfMinimum(((DomainMarker) iMarker).getSeries())); break; case MOVE_TO_MEDIAN: { iMarker.setValue(XYSeriesUtilities.getDomainValueOfMaximum(((DomainMarker) iMarker).getSeries())); double medY = (iSeries.getMaxY() - iSeries.getMinY()) / 2.; double val = XYSeriesUtilities.getNearestX(iSeries, medY); if (Double.isFinite(val)) iMarker.setValue(val); } ; break; case DELETE: removeMarker((DomainMarker) iMarker); break; case DELETE_ALL: plot.getDomainMarkers(Layer.FOREGROUND).clear(); iInterpolations.stream().forEach((i) -> { i.close(); }); iInterpolations.clear(); break; case FIT_LEFT: case FIT_RIGHT: { final FITDIR dir = (MENUS.FIT_LEFT == MENUS.translate(e.getActionCommand()) ? FITDIR.FIT_LEFT : FITDIR.FIT_RIGHT); List<DomainMarker> list = (new ArrayList<DomainMarker>(plot.getDomainMarkers(Layer.FOREGROUND))) .stream().filter((DomainMarker m) -> { return ((DomainMarker) iMarker).getSeries() == m.getSeries(); }).sorted((DomainMarker aLhs, DomainMarker aRhs) -> { return (int) (aLhs.getValue() - aRhs.getValue()); }).collect(Collectors.toList()); LOG.debug("-->Found " + list.size() + " markers"); switch (list.size()) { case 0: break; case 1: iInterpolations.add(new Interpolation((DomainMarker) iMarker, dir)); break; case 2: //fallthrough as default default: { DomainMarker left = null, right = null; int ndx = list.indexOf(iMarker); if (dir == FITDIR.FIT_LEFT) { if (ndx >= 1) { right = (DomainMarker) iMarker; left = list.get(--ndx); iInterpolations.add(new Interpolation(left, right)); } else { iInterpolations.add(new Interpolation((DomainMarker) iMarker, dir)); } } else if (dir == FITDIR.FIT_RIGHT) { if (ndx < list.size() - 1) { right = list.get(++ndx); left = (DomainMarker) iMarker; iInterpolations.add(new Interpolation(left, right)); } else { iInterpolations.add(new Interpolation((DomainMarker) iMarker, dir)); } } } break; } } break; default: break; } dropSelection(); } List<Interpolation> iInterpolations = new ArrayList<>(); static int iInterpolationID = 0; static enum FITDIR { FIT_LEFT, FIT_RIGHT, } private final static BasicStroke INTERPOLATION_STROKE = new BasicStroke(3, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[] { 9 }, 0); final class Interpolation implements MarkerChangeListener, SeriesChangeListener, AutoCloseable { DomainMarker iLhs; DomainMarker iRhs; XYSeries iSeries; Interpolation(DomainMarker aLhs, DomainMarker aRhs, XYSeries aSeries) { iLhs = aLhs; iRhs = aRhs; iSeries = aSeries; } Interpolation(DomainMarker aLhs, FITDIR aDir) { iLhs = aLhs; iRhs = null; double start = iLhs.getSeries().getDataItem(0).getXValue(); double finish = iLhs.getSeries().getDataItem(iLhs.getSeries().getItemCount() - 1).getXValue(); if (aDir == FITDIR.FIT_LEFT) finish = aLhs.getValue(); else start = aLhs.getValue(); iSeries = new XYSeries(String.format( java.util.ResourceBundle.getBundle("com/ivli/roim/Bundle").getString("INTERPOLATION%D"), iInterpolationID++)); XYSeriesUtilities.fit(iLhs.getSeries(), start, finish, true, iSeries); ((XYSeriesCollection) getChart().getXYPlot().getDataset()).addSeries(iSeries); int ndx = getChart().getXYPlot().getDataset().indexOf(iLhs.getSeries().getKey()); Paint p = getChart().getXYPlot().getRenderer().getSeriesPaint(ndx); getChart().getXYPlot().getRenderer().setSeriesPaint( getChart().getXYPlot().getDataset().indexOf(iSeries.getKey()), getChart().getXYPlot().getRenderer().getSeriesPaint( getChart().getXYPlot().getDataset().indexOf(iLhs.getSeries().getKey()))); getChart().getXYPlot().getRenderer().setSeriesStroke( getChart().getXYPlot().getDataset().indexOf(iSeries.getKey()), INTERPOLATION_STROKE); iLhs.getSeries().addChangeListener(this); aLhs.addChangeListener(this); } Interpolation(DomainMarker aLhs, DomainMarker aRhs) { iLhs = aLhs; iRhs = aRhs; iSeries = new XYSeries(String.format( java.util.ResourceBundle.getBundle("com/ivli/roim/Bundle").getString("INTERPOLATION%D"), iInterpolationID++)); XYSeriesUtilities.fit(iLhs.getSeries(), iLhs.getValue(), iRhs.getValue(), true, iSeries); ((XYSeriesCollection) getChart().getXYPlot().getDataset()).addSeries(iSeries); getChart().getXYPlot().getRenderer().setSeriesPaint( getChart().getXYPlot().getDataset().indexOf(iSeries.getKey()), getChart().getXYPlot().getRenderer().getSeriesPaint( getChart().getXYPlot().getDataset().indexOf(iLhs.getSeries().getKey()))); getChart().getXYPlot().getRenderer().setSeriesStroke( getChart().getXYPlot().getDataset().indexOf(iSeries.getKey()), INTERPOLATION_STROKE); iLhs.getSeries().addChangeListener(this); aLhs.addChangeListener(this); aRhs.addChangeListener(this); } @Override public void close() { iLhs.removeChangeListener(this); if (iRhs != null) iRhs.removeChangeListener(this); iSeries.clear(); } @Override public void markerChanged(MarkerChangeEvent mce) { iSeries.clear(); double left; double right; if (mce.getMarker() == iLhs) { left = ((DomainMarker) mce.getMarker()).getValue(); right = (null != iRhs) ? iRhs.getValue() : iLhs.getSeries().getDataItem(iLhs.getSeries().getItemCount() - 1).getXValue(); } else if (mce.getMarker() == iRhs) { left = iLhs.getValue(); right = ((DomainMarker) mce.getMarker()).getValue(); } else throw new IllegalArgumentException(); iSeries = XYSeriesUtilities.fit(iLhs.getSeries(), left, right, true, iSeries); } @Override public void seriesChanged(SeriesChangeEvent sce) { // it seems there's nothing to get done since marker change would fire an update of interpolation } } @Override public void mouseMoved(MouseEvent e) { super.mouseMoved(e); if (null == iEntity && null == iMarker) { ChartEntity entity = findEntity(e); if (entity instanceof XYItemEntity) { setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); } else if (entity instanceof PlotEntity) { if (null != findMarker(e)) setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); else setCursor(Cursor.getDefaultCursor()); } else { setCursor(Cursor.getDefaultCursor()); } } } @Override public void mousePressed(MouseEvent e) { ChartEntity entity = findEntity(e); if (entity instanceof XYItemEntity) { selectItem(entity); setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); } else if (null != (iMarker = findMarker(e))) { if (SwingUtilities.isLeftMouseButton(e)) setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); } else super.mousePressed(e); } @Override public void mouseReleased(MouseEvent e) { if (SwingUtilities.isRightMouseButton(e) && (iMarker instanceof DomainMarker || iSeries instanceof XYSeries)) { JPopupMenu mnu = new JPopupMenu( java.util.ResourceBundle.getBundle("com/ivli/roim/Bundle").getString("MNU_MARKER_OPERATIONS")); if (iSeries instanceof XYSeries) { mnu.add(MENUS.ADD.makeItem(this)); mnu.add(MENUS.EXPORT_CSV.makeItem(this)); mnu.add(MENUS.DELETE_ALL.makeItem(this)); } else if (iMarker instanceof DomainMarker) { JMenu mi1 = new JMenu(MENUS.MOVE_TO_MIN.iText); mi1.add(MENUS.MOVE_TO_MIN.makeItem(this)); // mi1.add(MENUS.MOVE_TO_MIN_LEFT.makeItem(this)); // mi1.add(MENUS.MOVE_TO_MIN_RIGHT.makeItem(this)); mnu.add(mi1); JMenu mi2 = new JMenu(MENUS.MOVE_TO_MAX.iText); mi2.add(MENUS.MOVE_TO_MAX.makeItem(this)); // mi2.add(MENUS.MOVE_TO_MAX_LEFT.makeItem(this)); // mi2.add(MENUS.MOVE_TO_MAX_RIGHT.makeItem(this)); mnu.add(mi2); mnu.add(MENUS.MOVE_TO_MEDIAN.makeItem(this)); //if(!getDomainMarkersForSeries()){ JMenu mi3 = new JMenu( java.util.ResourceBundle.getBundle("com/ivli/roim/Bundle").getString("MARKER_COMMAND.FIT")); mi3.add(MENUS.FIT_LEFT.makeItem(this)); mi3.add(MENUS.FIT_RIGHT.makeItem(this)); mnu.add(mi3); //} mnu.add(MENUS.DELETE.makeItem(this)); } mnu.show(this, e.getX(), e.getY()); } else { super.mouseReleased(e); dropSelection(); } } @Override public void mouseDragged(MouseEvent e) { final XYPlot plot = getChart().getXYPlot(); if (null != iEntity) { moveTo(plot.getRangeAxis().java2DToValue(e.getY(), getChartRenderingInfo().getPlotInfo().getDataArea(), plot.getRangeAxisEdge())); } if (null != iMarker) { double domainX = plot.getDomainAxis().java2DToValue(e.getX(), getScreenDataArea(), plot.getDomainAxisEdge()); iMarker.setValue(domainX); } else super.mouseDragged(e); } void dropSelection() { iEntity = null; iSeries = null; iDataItem = null; iMarker = null; setCursor(Cursor.getDefaultCursor()); } private ChartEntity iEntity = null; private XYSeries iSeries = null; private XYDataItem iDataItem = null; private ValueMarker iMarker = null; protected void selectItem(ChartEntity anEntity) { iEntity = anEntity; iSeries = ((XYSeriesCollection) ((XYItemEntity) iEntity).getDataset()) .getSeries(((XYItemEntity) iEntity).getSeriesIndex()); iDataItem = iSeries.getDataItem(((XYItemEntity) iEntity).getItem()); } protected void moveTo(double aNewY) { iDataItem.setY(aNewY); iSeries.delete(iSeries.indexOf(iDataItem.getX()), iSeries.indexOf(iDataItem.getX())); iSeries.add(iDataItem); } private static final Logger LOG = LogManager.getLogger(); }