/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* 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
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.gui.new_plotter.engine.jfreechart.link_and_brush;
import com.rapidminer.gui.new_plotter.engine.jfreechart.link_and_brush.listener.LinkAndBrushSelection;
import com.rapidminer.gui.new_plotter.engine.jfreechart.link_and_brush.listener.LinkAndBrushSelection.SelectionType;
import com.rapidminer.gui.new_plotter.engine.jfreechart.link_and_brush.listener.LinkAndBrushSelectionListener;
import com.rapidminer.gui.new_plotter.engine.jfreechart.link_and_brush.plots.LinkAndBrushPlot;
import com.rapidminer.gui.plotter.CoordinateTransformation;
import com.rapidminer.gui.plotter.NullCoordinateTransformation;
import com.rapidminer.tools.container.Pair;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Transparency;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.panel.Overlay;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.Pannable;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.PlotRenderingInfo;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.Range;
/**
* A Swing GUI component for displaying a {@link JFreeChart} object. The chart will be buffered.
* <P>
*
* @author Nils Woehler
*
*/
public class LinkAndBrushChartPanel extends ChartPanel {
private static final long serialVersionUID = 1L;
private boolean zoomOnLinkAndBrushSelection;
private boolean blockSelectionOrZoom = false;
/**
* This is a transformation which transforms the components coordinates to screen coordinates.
* If is null, no transformation is needed.
*/
private transient CoordinateTransformation coordinateTransformation = new NullCoordinateTransformation();
private transient List<WeakReference<LinkAndBrushSelectionListener>> listeners = new LinkedList<WeakReference<LinkAndBrushSelectionListener>>();
public LinkAndBrushChartPanel(JFreeChart chart, boolean zoomOnLinkAndBrushSelection) {
super(chart, 600, 400, 200, 133, DEFAULT_MAXIMUM_DRAW_WIDTH, DEFAULT_MAXIMUM_DRAW_HEIGHT, DEFAULT_BUFFER_USED,
false, // copy
false, // properties
false, // save
false, // print
false, // zoom
true); // tooltips
this.zoomOnLinkAndBrushSelection = zoomOnLinkAndBrushSelection;
setInitialDelay(200);
setMouseWheelEnabled(false);
}
public LinkAndBrushChartPanel(JFreeChart chart, int defaultWidth, int defaultHeigth, int minDrawWidth,
int minDrawHeigth, boolean zoomOnLinkAndBrush) {
super(chart, defaultWidth, defaultHeigth, minDrawWidth, minDrawHeigth, DEFAULT_MAXIMUM_DRAW_WIDTH,
DEFAULT_MAXIMUM_DRAW_HEIGHT, DEFAULT_BUFFER_USED, false, // copy
false, // properties
false, // save
false, // print
false, // zoom
true); // tooltips
this.zoomOnLinkAndBrushSelection = zoomOnLinkAndBrush;
setInitialDelay(200);
setMouseWheelEnabled(false);
}
public LinkAndBrushChartPanel(JFreeChart chart, int defaultWidth, int defaultHeigth, int minDrawWidth,
int minDrawHeigth, boolean zoomOnLinkAndBrush, boolean useBuffer) {
super(chart, defaultWidth, defaultHeigth, minDrawWidth, minDrawHeigth, DEFAULT_MAXIMUM_DRAW_WIDTH,
DEFAULT_MAXIMUM_DRAW_HEIGHT, useBuffer, false, // copy
false, // properties
false, // save
false, // print
false, // zoom
true); // tooltips
this.zoomOnLinkAndBrushSelection = zoomOnLinkAndBrush;
setMouseWheelEnabled(false);
}
@Override
public void restoreAutoBounds() {
Plot plot = getChart().getPlot();
if (plot == null) {
return;
}
// here we tweak the notify flag on the plot so that only
// one notification happens even though we update multiple
// axes...
boolean savedNotify = plot.isNotify();
plot.setNotify(false);
if (plot instanceof LinkAndBrushPlot) {
LinkAndBrushPlot LABPlot = (LinkAndBrushPlot) plot;
List<Pair<Integer, Range>> zoomedDomainAxisRanges = new LinkedList<Pair<Integer, Range>>();
List<Pair<Integer, Range>> zoomedRangeAxisRanges = new LinkedList<Pair<Integer, Range>>();
zoomedDomainAxisRanges.addAll(LABPlot.restoreAutoDomainAxisBounds(zoomOnLinkAndBrushSelection));
zoomedRangeAxisRanges.addAll(LABPlot.restoreAutoRangeAxisBounds(zoomOnLinkAndBrushSelection));
if (zoomOnLinkAndBrushSelection) {
informLinkAndBrushSelectionListeners(new LinkAndBrushSelection(SelectionType.RESTORE_AUTO_BOUNDS,
zoomedDomainAxisRanges, zoomedRangeAxisRanges));
} else {
informLinkAndBrushSelectionListeners(new LinkAndBrushSelection(SelectionType.RESTORE_SELECTION,
zoomedDomainAxisRanges, zoomedRangeAxisRanges));
}
} else {
restoreAutoDomainBounds();
restoreAutoRangeBounds();
}
plot.setNotify(savedNotify);
}
@Override
public void zoom(Rectangle2D selection) {
// get the origin of the zoom selection in the Java2D space used for
// drawing the chart (that is, before any scaling to fit the panel)
Point2D selectOrigin = translateScreenToJava2D(new Point((int) Math.ceil(selection.getX()),
(int) Math.ceil(selection.getY())));
PlotRenderingInfo plotInfo = getChartRenderingInfo().getPlotInfo();
Rectangle2D scaledDataArea = getScreenDataArea((int) selection.getCenterX(), (int) selection.getCenterY());
if ((selection.getHeight() > 0) && (selection.getWidth() > 0)) {
double hLower = (selection.getMinX() - scaledDataArea.getMinX()) / scaledDataArea.getWidth();
double hUpper = (selection.getMaxX() - scaledDataArea.getMinX()) / scaledDataArea.getWidth();
double vLower = (scaledDataArea.getMaxY() - selection.getMaxY()) / scaledDataArea.getHeight();
double vUpper = (scaledDataArea.getMaxY() - selection.getMinY()) / scaledDataArea.getHeight();
Plot p = getChart().getPlot();
if (p instanceof LinkAndBrushPlot) {
PlotOrientation orientation = null;
if (p instanceof XYPlot) {
XYPlot xyPlot = (XYPlot) p;
orientation = xyPlot.getOrientation();
}
if (p instanceof CategoryPlot) {
CategoryPlot categoryPlot = (CategoryPlot) p;
orientation = categoryPlot.getOrientation();
}
// here we tweak the notify flag on the plot so that only
// one notification happens even though we update multiple
// axes...
boolean savedNotify = p.isNotify();
p.setNotify(false);
LinkAndBrushPlot LABPlot = (LinkAndBrushPlot) p;
List<Pair<Integer, Range>> zoomedDomainAxisRanges = new LinkedList<Pair<Integer, Range>>();
List<Pair<Integer, Range>> zoomedRangeAxisRanges = new LinkedList<Pair<Integer, Range>>();
if (orientation == PlotOrientation.HORIZONTAL) {
zoomedDomainAxisRanges.addAll(LABPlot.calculateDomainAxesZoom(vLower, vUpper,
zoomOnLinkAndBrushSelection));
zoomedRangeAxisRanges.addAll(LABPlot.calculateRangeAxesZoom(hLower, hUpper, plotInfo, selectOrigin,
zoomOnLinkAndBrushSelection));
} else {
zoomedDomainAxisRanges.addAll(LABPlot.calculateDomainAxesZoom(hLower, hUpper,
zoomOnLinkAndBrushSelection));
zoomedRangeAxisRanges.addAll(LABPlot.calculateRangeAxesZoom(vLower, vUpper, plotInfo, selectOrigin,
zoomOnLinkAndBrushSelection));
}
p.setNotify(savedNotify);
if (zoomOnLinkAndBrushSelection) {
informLinkAndBrushSelectionListeners(new LinkAndBrushSelection(SelectionType.ZOOM_IN,
zoomedDomainAxisRanges, zoomedRangeAxisRanges));
} else {
informLinkAndBrushSelectionListeners(new LinkAndBrushSelection(SelectionType.SELECTION,
zoomedDomainAxisRanges, zoomedRangeAxisRanges));
}
} else {
super.zoom(selection);
}
}
}
/**
* If set to <code>true</code>, will zoom on selection, otherwise will do a selection.
*
* @param zoomOnLinkAndBrushSelection
*/
public void setZoomOnLinkAndBrushSelection(boolean zoomOnLinkAndBrushSelection) {
this.zoomOnLinkAndBrushSelection = zoomOnLinkAndBrushSelection;
}
/**
* Returns whether the panel will zoom on selection or not.
*
* @return
*/
public boolean getZoomOnLinkAndBrushSelection() {
return zoomOnLinkAndBrushSelection;
}
@SuppressWarnings("unchecked")
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (getChart() == null) {
return;
}
Graphics2D g2 = (Graphics2D) g.create();
// first determine the size of the chart rendering area...
Dimension size = getSize();
Insets insets = getInsets();
Rectangle2D available = new Rectangle2D.Double(insets.left, insets.top,
size.getWidth() - insets.left - insets.right, size.getHeight() - insets.top - insets.bottom);
// work out if scaling is required...
boolean scale = false;
double drawWidth = available.getWidth();
double drawHeight = available.getHeight();
setChartFieldValue(getChartFieldByName("scaleX"), 1.0);
// this.scaleX = 1.0;
setChartFieldValue(getChartFieldByName("scaleY"), 1.0);
// this.scaleY = 1.0;
if (drawWidth < getMinimumDrawWidth()) {
setChartFieldValue(getChartFieldByName("scaleX"), drawWidth / getMinimumDrawWidth());
// this.scaleX = drawWidth / getMinimumDrawWidth();
drawWidth = getMinimumDrawWidth();
scale = true;
} else if (drawWidth > getMaximumDrawWidth()) {
setChartFieldValue(getChartFieldByName("scaleX"), drawWidth / getMaximumDrawWidth());
// this.scaleX = drawWidth / getMaximumDrawWidth();
drawWidth = getMaximumDrawWidth();
scale = true;
}
if (drawHeight < getMinimumDrawHeight()) {
setChartFieldValue(getChartFieldByName("scaleY"), drawHeight / getMinimumDrawHeight());
// this.scaleY = drawHeight / getMinimumDrawHeight();
drawHeight = getMinimumDrawHeight();
scale = true;
} else if (drawHeight > getMaximumDrawHeight()) {
setChartFieldValue(getChartFieldByName("scaleY"), drawHeight / getMaximumDrawHeight());
// this.scaleY = drawHeight / getMaximumDrawHeight();
drawHeight = getMaximumDrawHeight();
scale = true;
}
Rectangle2D chartArea = new Rectangle2D.Double(0.0, 0.0, drawWidth, drawHeight);
// are we using the chart buffer?
if ((Boolean) getChartFieldValueByName("useBuffer")) {
// do we need to resize the buffer?
if ((getChartFieldValueByName("chartBuffer") == null)
|| ((Integer) getChartFieldValueByName("chartBufferWidth") != available.getWidth())
|| ((Integer) getChartFieldValueByName("chartBufferHeight") != available.getHeight())) {
setChartFieldValue(getChartFieldByName("chartBufferWidth"), (int) available.getWidth());
// this.chartBufferWidth = (int) available.getWidth();
setChartFieldValue(getChartFieldByName("chartBufferHeight"), (int) available.getHeight());
// this.chartBufferHeight = (int) available.getHeight();
GraphicsConfiguration gc = g2.getDeviceConfiguration();
setChartFieldValue(getChartFieldByName("chartBuffer"), gc.createCompatibleImage(
(Integer) getChartFieldValueByName("chartBufferWidth"),
(Integer) getChartFieldValueByName("chartBufferHeight"), Transparency.TRANSLUCENT));
// this.chartBuffer = gc.createCompatibleImage(this.chartBufferWidth,
// this.chartBufferHeight, Transparency.TRANSLUCENT);
setRefreshBuffer(true);
}
// do we need to redraw the buffer?
if (getRefreshBuffer()) {
setRefreshBuffer(false); // clear the flag
Rectangle2D bufferArea = new Rectangle2D.Double(0, 0,
(Integer) getChartFieldValueByName("chartBufferWidth"),
(Integer) getChartFieldValueByName("chartBufferHeight"));
Graphics2D bufferG2 = (Graphics2D) ((Image) getChartFieldValueByName("chartBuffer")).getGraphics();
Rectangle r = new Rectangle(0, 0, (Integer) getChartFieldValueByName("chartBufferWidth"),
(Integer) getChartFieldValueByName("chartBufferHeight"));
bufferG2.setPaint(getBackground());
bufferG2.fill(r);
if (scale) {
AffineTransform saved = bufferG2.getTransform();
AffineTransform st = AffineTransform.getScaleInstance((Double) getChartFieldValueByName("scaleX"),
(Double) getChartFieldValueByName("scaleY"));
bufferG2.transform(st);
getChart().draw(bufferG2, chartArea, getAnchor(), getChartRenderingInfo());
bufferG2.setTransform(saved);
} else {
getChart().draw(bufferG2, bufferArea, getAnchor(), getChartRenderingInfo());
}
}
// zap the buffer onto the panel...
g2.drawImage((Image) getChartFieldValueByName("chartBuffer"), insets.left, insets.top, this);
}
// or redrawing the chart every time...
else {
AffineTransform saved = g2.getTransform();
g2.translate(insets.left, insets.top);
if (scale) {
AffineTransform st = AffineTransform.getScaleInstance((Double) getChartFieldValueByName("scaleX"),
(Double) getChartFieldValueByName("scaleY"));
g2.transform(st);
}
getChart().draw(g2, chartArea, getAnchor(), getChartRenderingInfo());
g2.setTransform(saved);
}
for (Overlay overlay : (List<Overlay>) getChartFieldValueByName("overlays")) {
overlay.paintOverlay(g2, this);
}
// redraw the zoom rectangle (if present) - if useBuffer is false,
// we use XOR so we can XOR the rectangle away again without redrawing
// the chart
drawZoomRectangle(g2, !(Boolean) getChartFieldValueByName("useBuffer"));
g2.dispose();
setAnchor(null);
setVerticalTraceLine(null);
setHorizontalTraceLine(null);
}
@SuppressWarnings("unchecked")
public List<Overlay> getOverlayList() {
return (List<Overlay>) getChartFieldValueByName("overlays");
}
public void setOverlayList(List<Overlay> list) {
setChartFieldValue(getChartFieldByName("overlays"), list);
}
@Override
public void mousePressed(MouseEvent e) {
// this is used to only allow left mouse button zoom / selection
if (!SwingUtilities.isLeftMouseButton(e)) {
blockSelectionOrZoom = true;
} else {
blockSelectionOrZoom = false;
}
super.mousePressed(e);
}
@Override
public void mouseDragged(MouseEvent e) {
// when not allowed to zoom / select, return
if (blockSelectionOrZoom) {
return;
}
// if the popup menu has already been triggered, then ignore dragging...
if (getChartFieldValueByName("popup") != null && ((JPopupMenu) getChartFieldValueByName("popup")).isShowing()) {
return;
}
// handle panning if we have a start point
if (getChartFieldValueByName("panLast") != null) {
double dx = e.getX() - ((Point) getChartFieldValueByName("panLast")).getX();
double dy = e.getY() - ((Point) getChartFieldValueByName("panLast")).getY();
if (dx == 0.0 && dy == 0.0) {
return;
}
double wPercent = -dx / ((Double) getChartFieldValueByName("panW"));
double hPercent = dy / ((Double) getChartFieldValueByName("panH"));
boolean old = getChart().getPlot().isNotify();
getChart().getPlot().setNotify(false);
Pannable p = (Pannable) getChart().getPlot();
if (p.getOrientation() == PlotOrientation.VERTICAL) {
p.panDomainAxes(wPercent, getChartRenderingInfo().getPlotInfo(), (Point) getChartFieldValueByName("panLast"));
p.panRangeAxes(hPercent, getChartRenderingInfo().getPlotInfo(), (Point) getChartFieldValueByName("panLast"));
} else {
p.panDomainAxes(hPercent, getChartRenderingInfo().getPlotInfo(), (Point) getChartFieldValueByName("panLast"));
p.panRangeAxes(wPercent, getChartRenderingInfo().getPlotInfo(), (Point) getChartFieldValueByName("panLast"));
}
setChartFieldValue((getChartFieldByName("panLast")), e.getPoint());
getChart().getPlot().setNotify(old);
return;
}
// if no initial zoom point was set, ignore dragging...
if (getChartFieldValueByName("zoomPoint") == null) {
return;
}
Graphics2D g2 = (Graphics2D) getGraphics();
// erase the previous zoom rectangle (if any). We only need to do
// this is we are using XOR mode, which we do when we're not using
// the buffer (if there is a buffer, then at the end of this method we
// just trigger a repaint)
if (!(Boolean) getChartFieldValueByName("useBuffer")) {
drawZoomRectangle(g2, true);
}
boolean hZoom = false;
boolean vZoom = false;
if ((PlotOrientation) getChartFieldValueByName("orientation") == PlotOrientation.HORIZONTAL) {
hZoom = (Boolean) getChartFieldValueByName("rangeZoomable");
vZoom = (Boolean) getChartFieldValueByName("domainZoomable");
} else {
hZoom = (Boolean) getChartFieldValueByName("domainZoomable");
vZoom = (Boolean) getChartFieldValueByName("rangeZoomable");
}
Point2D zoomPoint = (Point2D) getChartFieldValueByName("zoomPoint");
Rectangle2D scaledDataArea = getScreenDataArea((int) zoomPoint.getX(), (int) zoomPoint.getY());
if (hZoom && vZoom) {
// selected rectangle shouldn't extend outside the data area...
double xmax = Math.min(e.getX(), scaledDataArea.getMaxX());
double ymax = Math.min(e.getY(), scaledDataArea.getMaxY());
setChartFieldValue(
getChartFieldByName("zoomRectangle"),
new Rectangle2D.Double(zoomPoint.getX(), zoomPoint.getY(), xmax - zoomPoint.getX(), ymax
- zoomPoint.getY()));
} else if (hZoom) {
double xmax = Math.min(e.getX(), scaledDataArea.getMaxX());
setChartFieldValue(getChartFieldByName("zoomRectangle"),
new Rectangle2D.Double(zoomPoint.getX(), scaledDataArea.getMinY(), xmax - zoomPoint.getX(),
scaledDataArea.getHeight()));
} else if (vZoom) {
double ymax = Math.min(e.getY(), scaledDataArea.getMaxY());
setChartFieldValue(getChartFieldByName("zoomRectangle"), new Rectangle2D.Double(scaledDataArea.getMinX(),
zoomPoint.getY(), scaledDataArea.getWidth(), ymax - zoomPoint.getY()));
}
// Draw the new zoom rectangle...
if ((Boolean) getChartFieldValueByName("useBuffer")) {
repaint();
} else {
// with no buffer, we use XOR to draw the rectangle "over" the
// chart...
drawZoomRectangle(g2, true);
}
g2.dispose();
}
@Override
public void mouseReleased(MouseEvent e) {
// if we've been panning, we need to reset now that the mouse is
// released...
Rectangle2D zoomRectangle = (Rectangle2D) getChartFieldValueByName("zoomRectangle");
Point2D zoomPoint = (Point2D) getChartFieldValueByName("zoomPoint");
if (getChartFieldValueByName("panLast") != null) {
setChartFieldValue(getChartFieldByName("panLast"), null);
setCursor(Cursor.getDefaultCursor());
} else if (zoomRectangle != null) {
boolean hZoom = false;
boolean vZoom = false;
if ((PlotOrientation) getChartFieldValueByName("orientation") == PlotOrientation.HORIZONTAL) {
hZoom = (Boolean) getChartFieldValueByName("rangeZoomable");
vZoom = (Boolean) getChartFieldValueByName("domainZoomable");
} else {
hZoom = (Boolean) getChartFieldValueByName("domainZoomable");
vZoom = (Boolean) getChartFieldValueByName("rangeZoomable");
}
boolean zoomTrigger1 = hZoom
&& Math.abs(e.getX() - zoomPoint.getX()) >= (Integer) getChartFieldValueByName("zoomTriggerDistance");
boolean zoomTrigger2 = vZoom
&& Math.abs(e.getY() - zoomPoint.getY()) >= (Integer) getChartFieldValueByName("zoomTriggerDistance");
if (zoomTrigger1 || zoomTrigger2) {
if (hZoom && e.getX() < zoomPoint.getX() || vZoom && e.getY() < zoomPoint.getY()) {
restoreAutoBounds();
} else {
double x, y, w, h;
Rectangle2D screenDataArea = getScreenDataArea((int) zoomPoint.getX(), (int) zoomPoint.getY());
double maxX = screenDataArea.getMaxX();
double maxY = screenDataArea.getMaxY();
// for mouseReleased event, (horizontalZoom || verticalZoom)
// will be true, so we can just test for either being false;
// otherwise both are true
if (!vZoom) {
x = zoomPoint.getX();
y = screenDataArea.getMinY();
w = Math.min(zoomRectangle.getWidth(), maxX - zoomPoint.getX());
h = screenDataArea.getHeight();
} else if (!hZoom) {
x = screenDataArea.getMinX();
y = zoomPoint.getY();
w = screenDataArea.getWidth();
h = Math.min(zoomRectangle.getHeight(), maxY - zoomPoint.getY());
} else {
x = zoomPoint.getX();
y = zoomPoint.getY();
w = Math.min(zoomRectangle.getWidth(), maxX - zoomPoint.getX());
h = Math.min(zoomRectangle.getHeight(), maxY - zoomPoint.getY());
}
Rectangle2D zoomArea = new Rectangle2D.Double(x, y, w, h);
zoom(zoomArea);
}
setChartFieldValue(getChartFieldByName("zoomPoint"), null);
setChartFieldValue(getChartFieldByName("zoomRectangle"), null);
} else {
// erase the zoom rectangle
Graphics2D g2 = (Graphics2D) getGraphics();
if ((Boolean) getChartFieldValueByName("useBuffer")) {
repaint();
} else {
drawZoomRectangle(g2, true);
}
g2.dispose();
setChartFieldValue(getChartFieldByName("zoomPoint"), null);
setChartFieldValue(getChartFieldByName("zoomRectangle"), null);
}
}
else if (e.isPopupTrigger()) {
if (getChartFieldValueByName("popup") != null) {
displayPopupMenu(e.getX(), e.getY());
}
}
}
/**
* Draws zoom rectangle (if present). The drawing is performed in XOR mode, therefore when this
* method is called twice in a row, the second call will completely restore the state of the
* canvas.
*
* @param g2
* the graphics device.
* @param xor
* use XOR for drawing?
*/
private void drawZoomRectangle(Graphics2D g2, boolean xor) {
Rectangle2D zoomRectangle = (Rectangle2D) getChartFieldValueByName("zoomRectangle");
if (zoomRectangle != null) {
// fix rectangle parameters when chart is transformed
zoomRectangle = coordinateTransformation.transformRectangle(zoomRectangle, this);
if (!(coordinateTransformation instanceof NullCoordinateTransformation)) {
g2 = coordinateTransformation.getTransformedGraphics(this);
}
if (xor) {
// Set XOR mode to draw the zoom rectangle
g2.setXORMode(Color.gray);
}
if ((Boolean) getChartFieldValueByName("fillZoomRectangle")) {
g2.setPaint((Paint) getChartFieldValueByName("zoomFillPaint"));
g2.fill(zoomRectangle);
} else {
g2.setPaint((Paint) getChartFieldValueByName("zoomOutlinePaint"));
g2.draw(zoomRectangle);
}
if (xor) {
// Reset to the default 'overwrite' mode
g2.setPaintMode();
}
}
}
/**
* Add a {@link LinkAndBrushSelectionListener}. The listener is saved as a {@link WeakReference}
* . Thus listener must not be hidden (anonymous) classes!
*/
public void addLinkAndBrushSelectionListener(LinkAndBrushSelectionListener l) {
listeners.add(new WeakReference<LinkAndBrushSelectionListener>(l));
}
/**
* Remove a {@link LinkAndBrushSelectionListener}.
*
* @param l
*/
public void removeLinkAndBrushSelectionListener(LinkAndBrushSelectionListener l) {
Iterator<WeakReference<LinkAndBrushSelectionListener>> it = listeners.iterator();
while (it.hasNext()) {
WeakReference<LinkAndBrushSelectionListener> wrl = it.next();
LinkAndBrushSelectionListener listener = wrl.get();
if (listener == l || listener == null) {
it.remove();
}
}
}
/**
* Informs all {@link LinkAndBrushSelectionListener} of a {@link LinkAndBrushSelection}.
*
* @param e
*/
public void informLinkAndBrushSelectionListeners(LinkAndBrushSelection e) {
// create a copy to avoid ConcurrentModificationException when informing and remove listener
// happens
List<WeakReference<LinkAndBrushSelectionListener>> listenersCopy = new LinkedList<WeakReference<LinkAndBrushSelectionListener>>();
listenersCopy.addAll(listeners);
Iterator<WeakReference<LinkAndBrushSelectionListener>> it = listenersCopy.iterator();
while (it.hasNext()) {
WeakReference<LinkAndBrushSelectionListener> wrl = it.next();
LinkAndBrushSelectionListener l = wrl.get();
if (l != null) {
l.selectedLinkAndBrushRectangle(e);
} else {
it.remove();
}
}
}
/**
* Returns the value of a {@link Field} of the {@link ChartPanel}. If the field can not be
* found, returns {@code null}.
*
* @param name
* the name of the {@link Field}
* @return
*/
private Object getChartFieldValueByName(String name) {
try {
Field field = ChartPanel.class.getDeclaredField(name);
field.setAccessible(true);
Object value = field.get(this);
field.setAccessible(false);
return value;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* Returns the {@link Field} of the {@link ChartPanel}. If the field can not be found, returns
* {@code null}.
*
* @param name
* the name of the {@link Field}
* @return
*/
private Field getChartFieldByName(String name) {
try {
Field field = ChartPanel.class.getDeclaredField(name);
field.setAccessible(true);
return field;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* Sets the given value for the given {@link Field}.
*
* @param field
* the {@link Field} to change
* @param value
* the new value for the {@link Field}
*/
private void setChartFieldValue(Field field, Object value) {
try {
field.setAccessible(true);
field.set(this, value);
field.setAccessible(false);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* This method sets the coordinate transformation for this component.
*/
public void setCoordinateTransformation(CoordinateTransformation transformation) {
this.coordinateTransformation = transformation;
}
}