/* =========================================================== * JFreeChart : a free chart library for the Java(tm) platform * =========================================================== * * (C) Copyright 2000-2013, by Object Refinery Limited and Contributors. * * Project Info: http://www.jfree.org/jfreechart/index.html * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library 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 Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. * * [Java is a trademark or registered trademark of Sun Microsystems, Inc. * in the United States and other countries.] * * --------------------------- * EntitySelectionManager.java * --------------------------- * (C) Copyright 2013, by Michael Zinsmaier and Contributors. * * Original Author: Michael Zinsmaier; * Contributor(s): David Gilbert (for Object Refinery Limited); * * Changes: * -------- * 17-Sep-2013 : Version 1 from MZ (DG); * */ package org.jfree.chart.panel.selectionhandler; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.GeneralPath; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import org.jfree.chart.ChartPanel; import org.jfree.chart.entity.ChartEntity; import org.jfree.chart.entity.DataItemEntity; import org.jfree.chart.entity.EntityCollection; import org.jfree.data.extension.DatasetCursor; import org.jfree.data.extension.DatasetSelectionExtension; import org.jfree.data.extension.impl.DatasetExtensionManager; import org.jfree.data.general.Dataset; /** * Selects data items based on the shape of their rendered entities and a given * point or region selection in the ChartPanel plane.<br> * <br> * region selection:<br> * Depending on the rendering mode {@link #intersectionMode} selects either all * entities that intersect the selection region or all entities that are * completely inside the selection region.<br> * <br> * point selection:<br> * Selects all entities that contain the selection point <br> * <br> * The entities are retrieved from the ChartPanel * * @author zinsmaie */ public class EntitySelectionManager implements SelectionManager { /** a generated serial id. */ private static final long serialVersionUID = -8963792184797912675L; /** * if true a entity has to intersect the selection region to be selected if * false a entity has to be completely inside the selection region to be * selected. */ private boolean intersectionMode; /** the ChartPanel this manager is registered on */ private final ChartPanel renderSourcePanel; /** * couples datasets and selection information of datasets that do not * support {@link DatasetSelectionExtension} */ private final DatasetExtensionManager extensionManager; /** all datasets that are handled by the manager. */ private final Dataset[] datasets; /** * Constructs a new selection manager. Use this constructor if all datasets * support {@link DatasetSelectionExtension} * * @param renderSourcePanel {@link #renderSourcePanel} * @param datasets {@link #datasets} */ public EntitySelectionManager(ChartPanel renderSourcePanel, Dataset[] datasets) { this.renderSourcePanel = renderSourcePanel; this.datasets = datasets; // initialize an extension manager without registered extensions this.extensionManager = new DatasetExtensionManager(); this.intersectionMode = false; } /** * constructs a new selection manager and provides a extension manager. Use * this constructor if some of the used datasets do not support * {@link DatasetSelectionExtension}. These datasets can be coupled with * appropriate helper objects by registering them to the extension manager * before the constructor call. * * @param renderSourcePanel {@link #renderSourcePanel} * @param datasets {@link #datasets} * @param extensionManager {@link #extensionManager} */ public EntitySelectionManager(ChartPanel renderSourcePanel, Dataset[] datasets, DatasetExtensionManager extensionManager) { this.renderSourcePanel = renderSourcePanel; this.datasets = datasets; // set an extension manager that may manager helper objects to extend // old datasets this.extensionManager = extensionManager; this.intersectionMode = false; } /** * @param on {@link #intersectionMode} */ public void setIntersectionSelection(boolean on) { this.intersectionMode = on; } /** * {@link SelectionManager#select(double, double)} <br> * Selection based on the shape of the data items */ public void select(double x, double y) { // scale if necessary double scaleX = this.renderSourcePanel.getScaleX(); double scaleY = this.renderSourcePanel.getScaleY(); if (scaleX != 1.0d || scaleY != 1.0d) { x = x / scaleX; y = y / scaleY; } if (this.renderSourcePanel.getChartRenderingInfo() != null) { EntityCollection entities = this.renderSourcePanel .getChartRenderingInfo().getEntityCollection(); for (ChartEntity ce : entities.getEntities()) { if (ce instanceof DataItemEntity) { DataItemEntity e = (DataItemEntity) ce; // simple check if the entity shape area contains the point if (e.getArea().contains(new Point2D.Double(x, y))) { select(e); } } } } } /** * * {@link SelectionManager#select(Rectangle2D)} <br> * Selection based on the shape of the data items */ public void select(Rectangle2D pSelection) { // scale if necessary Rectangle2D selection; double scaleX = this.renderSourcePanel.getScaleX(); double scaleY = this.renderSourcePanel.getScaleY(); if (scaleX != 1.0d || scaleY != 1.0d) { AffineTransform st = AffineTransform.getScaleInstance(1.0 / scaleX, 1.0 / scaleY); Area selectionArea = new Area(pSelection); selectionArea.transform(st); selection = selectionArea.getBounds2D(); } else { selection = pSelection; } if (this.renderSourcePanel.getChartRenderingInfo() != null) { muteAll(); { EntityCollection entities = this.renderSourcePanel .getChartRenderingInfo().getEntityCollection(); for (ChartEntity ce : entities.getEntities()) { if (ce instanceof DataItemEntity) { DataItemEntity e = (DataItemEntity) ce; boolean match; if (e.getArea() instanceof Rectangle2D) { Rectangle2D entityRect = (Rectangle2D) e.getArea(); // use fast rectangle to rectangle test if (this.intersectionMode) { match = selection.intersects(entityRect); } else { match = selection.contains(entityRect); } } else { // general shape test Area selectionShape = new Area(selection); Area entityShape = new Area(e.getArea()); // fast test if completely inside the solution must be true if (selectionShape.contains(entityShape.getBounds())) { match = true; } else { if (this.intersectionMode) { // test if the shapes intersect entityShape.intersect(selectionShape); match = !entityShape.isEmpty(); } else { // test if the entity shape is completely // covered by the selection entityShape.subtract(selectionShape); match = entityShape.isEmpty(); } } } if (match) { select(e); } } } } unmuteAndTrigger(); } } /** * {@link SelectionManager#select(GeneralPath)} <br> * Selection based on the shape of the data items */ public void select(GeneralPath pSelection) { // scale if necessary GeneralPath selection; double scaleX = this.renderSourcePanel.getScaleX(); double scaleY = this.renderSourcePanel.getScaleY(); if (scaleX != 1.0d || scaleY != 1.0d) { AffineTransform st = AffineTransform.getScaleInstance(1.0 / scaleX, 1.0 / scaleY); Area selectionArea = new Area(pSelection); selectionArea.transform(st); selection = new GeneralPath(selectionArea); } else { selection = pSelection; } if (this.renderSourcePanel.getChartRenderingInfo() != null) { muteAll(); { EntityCollection entities = this.renderSourcePanel .getChartRenderingInfo().getEntityCollection(); for (ChartEntity ce : entities.getEntities()) { if (ce instanceof DataItemEntity) { DataItemEntity e = (DataItemEntity) ce; Area selectionShape = new Area(selection); Area entityShape = new Area(e.getArea()); // fast test if completely inside the solution must be true if (selectionShape.contains(entityShape.getBounds())) { select(e); } else { if (this.intersectionMode) { // test if the shapes intersect entityShape.intersect(selectionShape); if (!entityShape.isEmpty()) { select(e); } } else { // test if the entity shape is completely covered by //the selection entityShape.subtract(selectionShape); if (entityShape.isEmpty()) { select(e); } } } } } } unmuteAndTrigger(); } } /** * {@link SelectionManager#clearSelection()} */ public void clearSelection() { for (int i = 0; i < this.datasets.length; i++) { if (this.extensionManager.supports(this.datasets[i], DatasetSelectionExtension.class)) { DatasetSelectionExtension<?> selectionExtension = (DatasetSelectionExtension<?>) this.extensionManager .getExtension(this.datasets[i], DatasetSelectionExtension.class); selectionExtension.clearSelection(); } } } /** * tests if the dataset is handled by the selection manager (part of * {@link #datasets}) and if it either supports * {@link DatasetSelectionExtension} directly or via the extension manager.<br> * <br> * The selects the specified data item. * * @param e * data item that should be selected */ private void select(DataItemEntity e) { // to support propper clear functionality we must maintain // all datasets that we change! boolean handled = false; for (int i = 0; i < this.datasets.length; i++) { if (datasets[i].equals(e.getGeneralDataset())) { handled = true; break; } } if (handled) { if (this.extensionManager.supports(e.getGeneralDataset(), DatasetSelectionExtension.class)) { //TODO a type save solution would be nice DatasetCursor cursor = e.getItemCursor(); DatasetSelectionExtension selectionExtension = this.extensionManager.getExtension( e.getGeneralDataset(), DatasetSelectionExtension.class); // work on the data selectionExtension.setSelected(cursor, true); } } } /** * mutes the selection change listener for all handled datasets (in * {@link #datasets} and supports {@link DatasetSelectionExtension} */ private void muteAll() { setNotifyOnListenerExtensions(false); } /** * unmutes the selection change listener for all handled datasets (in * {@link #datasets} and supports {@link DatasetSelectionExtension} * * unmute should trigger a selection changed event if something happened * since mute (but this is controlled by the implementing classes and can * only be assumed here) */ private void unmuteAndTrigger() { setNotifyOnListenerExtensions(true); } /** * mutes / unmutes the selection change listener for all handled datasets * (in {@link #datasets} and supports {@link DatasetSelectionExtension} * * unmute should trigger a selection changed event if something happened * since mute (but this is controlled by the implementing classes and can * only be assumed here) * * @param notify * false to mute true to unmute */ private void setNotifyOnListenerExtensions(boolean notify) { for (int i = 0; i < this.datasets.length; i++) { if (this.extensionManager.supports(datasets[i], DatasetSelectionExtension.class)) { DatasetSelectionExtension<?> selectionExtension = (DatasetSelectionExtension<?>) this.extensionManager .getExtension(datasets[i], DatasetSelectionExtension.class); selectionExtension.setNotify(notify); } } } }