/* ===========================================================
* 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);
}
}
}
}