package gui.views.plots;
import gui.bsvComponents.BSVComboBox;
import gui.main.EventController;
import gui.settings.Settings;
import gui.views.ViewUtils;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Polygon;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.io.IOException;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import javax.media.opengl.GL2;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.DefaultComboBoxModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import com.jogamp.common.nio.Buffers;
import controller.DataHub;
import controller.ElementData;
import controller.Feature;
import controller.SelectionController;
import controller.SubspaceController;
import db.DatabaseAccessException;
/**
* This class implements a scatter plot. It uses OpenGL for accelerated drawing.
*/
public class ScatterPlot extends GLPlot {
private static final long serialVersionUID = 7401836444479998149L;
/**
* Defines modes of ScatterPlot
*/
private static enum MODES {
SCROLL_ZOOM, BOXZOOM, LASSO
};
/**
* Sets the size of the surrounding where axis and text is drawing and no points.
*/
private static final int AXIS_PART_SIZE = 60;
/**
* Sets the size of the points in pixel.
*/
private static final float DOTSIZE = 4.f;
/**
* Controls width of lines for selection tools.
*/
private static final float TOOL_LINES_WIDTH = 2.f;
/**
* Controls alpha of non selected objects.
*/
private static final float NON_SELECTED_ALPHA = 0.2f;
/**
* Controls alpha of non selected elements depending on number of shown elements.
*/
private static final float NON_SELECTED_ALPHA_BASE = 500.f;
/**
* Action that sets mode to scroll and zoom.
*/
private AbstractAction setModeScrollzoomAction;
/**
* Action that sets mode to box zoom.
*/
private AbstractAction setModeBoxzoomAction;
/**
* Action that sets mode to lasso.
*/
private AbstractAction setModeLassoAction;
/**
* Action that resets the view port.
*/
private AbstractAction resetViewportAction;
/**
* Action that clears selection.
*/
private AbstractAction clearSelectionAction;
/**
* Vertex buffer that stores the location of the points.
*
* Is used for fast redrawing and represents a buffer on graphics card memory. It is not used global but is a member
* variable to avoid garbage collection and buffer freeing.
*/
private FloatBuffer vbuffer;
/**
* Color buffer that stores the color and transparency of points. See {@link #vbuffer} for more details.
*/
private FloatBuffer cbuffer;
/**
* Id buffer that stores ids of points.
*/
private IntBuffer ibuffer;
/**
* Stores the number of points that should be drawn.
*/
private int n;
/**
* Stores the size in pixel that represents the height and width of the rectangular of the plot.
*/
private int size;
/**
* The InteractionController that is used as mouse listener and state storage.
*/
private InteractionController icontroller;
/**
* The name of the X axis.
*/
private String nameX;
/**
* The name of the Y axis.
*/
private String nameY;
/**
* Stores the x-axis feature.
*/
private Feature featureX;
/**
* Stores the y-axis feature.
*/
private Feature featureY;
/**
* Stores the range of x values.
*/
private float rangeX;
/**
* Stores the range of y values.
*/
private float rangeY;
/**
* Stores minimum x value.
*/
private float minX;
/**
* Stores minimum y value.
*/
private float minY;
/**
* Combo box for x feature.
*/
private JComboBox comboX;
/**
* Combo box for y feature.
*/
private JComboBox comboY;
/**
* Radio button for scroll zoom mode.
*/
private JToggleButton scrollZoomButton;
/**
* Radio button for box zoom mode.
*/
private JToggleButton boxzoomButton;
/**
* Radio button for lasso mode
*/
private JToggleButton lassoButton;
/**
* Constructs a scatterplot using a DataHub, a SelectionController and a SubspaceController.
*
* @param dataHub
* a preinitialized DataHub.
* @param selController
* a preinitialized SelectionController.
* @param subController
* a preinitialized SubspaceController.
*/
public ScatterPlot(DataHub dataHub, SelectionController selController, SubspaceController subController) {
super(dataHub, selController, subController);
}
@Override
protected void createUI() {
// init actions
this.setModeScrollzoomAction = new SetModeScrollzoomAction();
this.setModeBoxzoomAction = new SetModeBoxzoomAction();
this.setModeLassoAction = new SetModeLassoAction();
this.resetViewportAction = new ResetViewportAction();
this.clearSelectionAction = new ClearSelectionAction();
// init interaction controller
this.icontroller = new InteractionController();
// feature selection
this.comboX = new BSVComboBox(new Feature[0]);
this.comboY = new BSVComboBox(new Feature[0]);
JPanel panelX = new JPanel();
JPanel panelY = new JPanel();
panelX.add(new JLabel(Settings.getInstance().getResourceBundle().getString("xFeature")));
panelY.add(new JLabel(Settings.getInstance().getResourceBundle().getString("yFeature")));
panelX.add(this.comboX);
panelY.add(this.comboY);
panelX.setBackground(Color.WHITE);
panelY.setBackground(Color.WHITE);
JPanel featurePanel = new JPanel(new GridLayout(0, 1));
featurePanel.setBorder(BorderFactory.createTitledBorder(Settings.getInstance().getResourceBundle().getString(
"scatterPlotFeaturePanel")));
featurePanel.add(panelX);
featurePanel.add(panelY);
this.addToSidebar(featurePanel);
this.setInteractionHandler(this.icontroller);
this.comboX.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
JComboBox cb = (JComboBox) e.getSource();
featureX = (Feature) cb.getSelectedItem();
update(null, null);
}
});
this.comboY.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
JComboBox cb = (JComboBox) e.getSource();
featureY = (Feature) cb.getSelectedItem();
update(null, null);
}
});
// mode selection
JPanel modePanel = new JPanel(new GridLayout(1, 0));
modePanel.setBorder(BorderFactory.createTitledBorder(Settings.getInstance().getResourceBundle().getString(
"scatterPlotModePanel")));
this.scrollZoomButton = new JToggleButton();
this.boxzoomButton = new JToggleButton();
this.lassoButton = new JToggleButton();
// image tool tips
this.scrollZoomButton.setToolTipText(Settings.getInstance().getResourceBundle().getString(
"scatterPlotModeScrollZoom"));
this.boxzoomButton.setToolTipText(Settings.getInstance().getResourceBundle()
.getString("scatterPlotModeBoxzoom"));
this.lassoButton.setToolTipText(Settings.getInstance().getResourceBundle().getString("scatterPlotModeLasso"));
// load images or fall back to text
try {
this.scrollZoomButton.setIcon(new ImageIcon(ImageIO.read(this.getClass().getResourceAsStream(
"/scatterplot_move.png"))));
this.boxzoomButton.setIcon(new ImageIcon(ImageIO.read(this.getClass().getResourceAsStream(
"/scatterplot_box.png"))));
this.lassoButton.setIcon(new ImageIcon(ImageIO.read(this.getClass().getResourceAsStream(
"/scatterplot_lasso.png"))));
} catch (IOException e) {
this.scrollZoomButton.setText(Settings.getInstance().getResourceBundle().getString(
"scatterPlotModeScrollZoom"));
this.boxzoomButton.setText(Settings.getInstance().getResourceBundle().getString("scatterPlotModeBoxzoom"));
this.lassoButton.setText(Settings.getInstance().getResourceBundle().getString("scatterPlotModeLasso"));
}
modePanel.add(this.scrollZoomButton);
modePanel.add(this.boxzoomButton);
modePanel.add(this.lassoButton);
this.addToSidebar(modePanel);
ButtonGroup modeGroup = new ButtonGroup();
modeGroup.add(this.scrollZoomButton);
modeGroup.add(this.boxzoomButton);
modeGroup.add(this.lassoButton);
this.scrollZoomButton.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
JToggleButton source = (JToggleButton) e.getSource();
if (source.isSelected()) {
icontroller.setMode(MODES.SCROLL_ZOOM);
}
}
});
this.boxzoomButton.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
JToggleButton source = (JToggleButton) e.getSource();
if (source.isSelected()) {
icontroller.setMode(MODES.BOXZOOM);
}
}
});
this.lassoButton.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
JToggleButton source = (JToggleButton) e.getSource();
if (source.isSelected()) {
icontroller.setMode(MODES.LASSO);
}
}
});
this.resetMode();
// reset button
JButton resetButton = new JButton(this.resetViewportAction);
resetButton.setText(Settings.getInstance().getResourceBundle().getString("plotReset"));
JButton clearselectionButton = new JButton(this.clearSelectionAction);
clearselectionButton.setText(Settings.getInstance().getResourceBundle().getString("scatterPlotResetSelection"));
JPanel controlPanel = new JPanel(new GridLayout(0, 1));
controlPanel.setBorder(BorderFactory.createTitledBorder(Settings.getInstance().getResourceBundle().getString(
"scatterPlatControlPanel")));
controlPanel.add(resetButton);
controlPanel.add(clearselectionButton);
this.addToSidebar(controlPanel);
}
@Override
protected void registerShortcuts() {
EventController.getInstance().setAction(this.setModeScrollzoomAction, "eventModeZoomdrag");
EventController.getInstance().setAction(this.setModeBoxzoomAction, "eventModeBox");
EventController.getInstance().setAction(this.setModeLassoAction, "eventModeLasso");
EventController.getInstance().setAction(this.setModeLassoAction, "eventSetSelection");
EventController.getInstance().setAction(this.clearSelectionAction, "eventResetSelection");
}
@Override
protected void unregisterShortcuts() {
EventController.getInstance().removeAction("eventModeZoomdrag");
EventController.getInstance().removeAction("eventModeBox");
EventController.getInstance().removeAction("eventModeLasso");
EventController.getInstance().removeAction("eventSetSelection");
EventController.getInstance().removeAction("eventResetSelection");
}
/**
* Reset mode
*/
private void resetMode() {
this.scrollZoomButton.setSelected(true);
}
@Override
protected void setFeatures(Feature[] features) {
this.comboX.setModel(new DefaultComboBoxModel(features));
this.comboY.setModel(new DefaultComboBoxModel(features));
this.featureX = (Feature) comboX.getSelectedItem();
this.featureY = (Feature) comboY.getSelectedItem();
}
@Override
protected void init(GL2 gl) {
// enable smooth point/line drawing
gl.glEnable(GL2.GL_POINT_SMOOTH);
gl.glEnable(GL2.GL_LINE_SMOOTH);
}
@Override
protected void rescale(GL2 gl) {
this.size = Math.min(this.getShapeWidth(), this.getShapeHeight()) - 2 * AXIS_PART_SIZE;
this.icontroller.setShapeX(this.getShapeX() + AXIS_PART_SIZE);
this.icontroller.setShapeY(this.getShapeY());
this.icontroller.setShapeWidth(this.getShapeWidth() - AXIS_PART_SIZE);
this.icontroller.setShapeHeight(this.getShapeHeight() - AXIS_PART_SIZE);
}
@Override
protected void draw(GL2 gl) {
// get values from icontroller
final float scaleX = this.icontroller.getScaleX();
final float scaleY = this.icontroller.getScaleY();
final float dx = this.icontroller.getDX();
final float dy = this.icontroller.getDY();
// draw dots
if (this.n > 0) {
float scaleFactorX = scaleX * this.size;
float scaleFactorY = scaleY * this.size;
gl.glPushMatrix();
gl.glTranslatef(AXIS_PART_SIZE, AXIS_PART_SIZE, 0.f);
gl.glTranslatef(dx, dy, 0);
gl.glScalef(scaleFactorX, scaleFactorY, 1.f);
gl.glPointSize(DOTSIZE);
gl.glEnableClientState(GL2.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL2.GL_COLOR_ARRAY);
gl.glDrawArrays(GL2.GL_POINTS, 0, this.n);
gl.glDisableClientState(GL2.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL2.GL_COLOR_ARRAY);
gl.glPointSize(1.f);
gl.glPopMatrix();
}
// draw box
if (this.icontroller.isBoxActive()) {
gl.glLineWidth(TOOL_LINES_WIDTH);
gl.glBegin(GL2.GL_LINE_LOOP);
gl.glVertex2f(this.icontroller.getBoxStartX(), this.icontroller.getBoxStartY() + AXIS_PART_SIZE);
gl.glVertex2f(this.icontroller.getBoxEndX(), this.icontroller.getBoxStartY() + AXIS_PART_SIZE);
gl.glVertex2f(this.icontroller.getBoxEndX(), this.icontroller.getBoxEndY() + AXIS_PART_SIZE);
gl.glVertex2f(this.icontroller.getBoxStartX(), this.icontroller.getBoxEndY() + AXIS_PART_SIZE);
gl.glEnd();
gl.glLineWidth(1.f);
}
// draw lasso
if (this.icontroller.isLassoActive()) {
int[] xcoords = this.icontroller.getLassoPointsX();
int[] ycoords = this.icontroller.getLassoPointsY();
gl.glColor4f(0.f, 0.f, 0.f, 1.f);
gl.glLineWidth(TOOL_LINES_WIDTH);
gl.glPolygonMode(GL2.GL_FRONT_AND_BACK, GL2.GL_LINE);
gl.glBegin(GL2.GL_POLYGON);
for (int i = 0; (i < xcoords.length) && (i < ycoords.length); i++) {
gl.glVertex2f(xcoords[i], ycoords[i] + AXIS_PART_SIZE);
}
gl.glEnd();
gl.glColor4f(0.f, 0.f, 0.f, 1.f);
gl.glLineWidth(1.f);
gl.glPolygonMode(GL2.GL_FRONT_AND_BACK, GL2.GL_FILL);
}
// draw white background beyond axis
gl.glColor3f(1.f, 1.f, 1.f);
gl.glRectf(0, 0, AXIS_PART_SIZE, this.getShapeHeight());
gl.glRectf(0, 0, this.getShapeWidth(), AXIS_PART_SIZE);
// draw axis
gl.glPushMatrix();
float drawWidth = this.getShapeWidth() - 2 * AXIS_PART_SIZE;
float drawHeight = this.getShapeHeight() - 2 * AXIS_PART_SIZE;
float drawRangeX = this.rangeX * drawWidth / this.size;
float drawRangeY = this.rangeY * drawHeight / this.size;
float minLabelY = (0 - dy) / (scaleY * drawHeight) * drawRangeY + this.minY;
float maxLabelY = (drawHeight - dy) / (scaleY * drawHeight) * drawRangeY + this.minY;
float minLabelX = (0 - dx) / (scaleX * drawWidth) * drawRangeX + this.minX;
float maxLabelX = (drawWidth - dx) / (scaleX * drawWidth) * drawRangeX + this.minX;
if (minLabelX > maxLabelX) {
minLabelX = maxLabelX;
}
if (minLabelY > maxLabelY) {
minLabelY = maxLabelY;
}
this.drawAxis(gl, AXIS_PART_SIZE, AXIS_PART_SIZE, drawHeight, nameY, minLabelY, maxLabelY, false);
this.drawAxis(gl, AXIS_PART_SIZE, AXIS_PART_SIZE, drawWidth, nameX, minLabelX, maxLabelX, true);
gl.glPopMatrix();
}
@Override
protected void dispose(GL2 gl) {
// nothing to do
}
@Override
public String getName() {
return Settings.getInstance().getResourceBundle().getString("scatterplot");
}
@Override
protected synchronized void processData() throws DatabaseAccessException, InterruptedException {
Feature featureX = this.featureX;
Feature featureY = this.featureY;
this.nameX = featureX.getName();
this.nameY = featureY.getName();
this.minX = featureX.getMinValue();
this.minY = featureY.getMinValue();
float maxX = featureX.getMaxValue();
float maxY = featureY.getMaxValue();
this.rangeX = maxX - this.minX;
this.rangeY = maxY - this.minY;
boolean selection = this.selectionController.isSomethingSelected();
ElementData[] data = this.dataHub.getData();
this.n = data.length;
if (this.n > 0) {
this.vbuffer = Buffers.newDirectFloatBuffer(this.n * 2);
this.cbuffer = Buffers.newDirectFloatBuffer(this.n * 4);
this.ibuffer = Buffers.newDirectIntBuffer(this.n);
float nonSelectedAlphaFactor = (float) (NON_SELECTED_ALPHA * Math.min(1.f, (Math
.log(NON_SELECTED_ALPHA_BASE) / Math.log(this.n))));
for (int i = 0; i < this.n; i++) {
this.vbuffer.put((data[i].getValue(featureX) - this.minX) / this.rangeX);
this.vbuffer.put((data[i].getValue(featureY) - this.minY) / this.rangeY);
float alphaFactor = 1.f;
if (selection) {
alphaFactor = this.selectionController.isSelected(data[i].getId()) ? 1.f : nonSelectedAlphaFactor;
}
// color
Color color = ViewUtils.calcColor(data[i]);
this.cbuffer.put(color.getRed() / 255.f);
this.cbuffer.put(color.getGreen() / 255.f);
this.cbuffer.put(color.getBlue() / 255.f);
this.cbuffer.put(color.getAlpha() / 255.f * alphaFactor);
// store id for later usage
this.ibuffer.put(data[i].getId());
}
this.vbuffer.rewind();
this.cbuffer.rewind();
this.ibuffer.rewind();
}
}
@Override
protected synchronized void uploadData(GL2 gl) {
gl.glVertexPointer(2, GL2.GL_FLOAT, 0, this.vbuffer);
gl.glColorPointer(4, GL2.GL_FLOAT, 0, this.cbuffer);
}
@Override
protected void draw(Graphics2D g2d) {
// get values from icontroller
final float scaleX = this.icontroller.getScaleX();
final float scaleY = this.icontroller.getScaleY();
final float dx = this.icontroller.getDX();
final float dy = this.icontroller.getDY();
int size = Math.min(this.getShapeWidth(), this.getShapeHeight()) - 2 * AXIS_PART_SIZE;
// draw dots
for (int i = 0; i < this.n; i++) {
float x = this.vbuffer.get(i * 2) * scaleX * size + dx;
float y = this.vbuffer.get(i * 2 + 1) * scaleY * size + dy;
if ((x >= 0.f) && (x <= this.getShapeWidth() - 2 * AXIS_PART_SIZE) && (y >= 0.f)
&& (y <= this.getShapeHeight() - 2 * AXIS_PART_SIZE)) {
g2d.setColor(new Color(this.cbuffer.get(i * 4), this.cbuffer.get(i * 4 + 1), this.cbuffer
.get(i * 4 + 2), this.cbuffer.get(i * 4 + 3)));
g2d.fillOval(Math.round(x + AXIS_PART_SIZE - DOTSIZE / 2.f), Math.round(y + AXIS_PART_SIZE - DOTSIZE
/ 2.f), Math.round(DOTSIZE), Math.round(DOTSIZE));
}
}
g2d.setColor(Color.BLACK);
// draw axis
float drawWidth = this.getShapeWidth() - 2 * AXIS_PART_SIZE;
float drawHeight = this.getShapeHeight() - 2 * AXIS_PART_SIZE;
float drawRangeX = this.rangeX * drawWidth / size;
float drawRangeY = this.rangeY * drawHeight / size;
float minLabelY = (0 - dy) / (scaleY * drawHeight) * drawRangeY + this.minY;
float maxLabelY = (drawHeight - dy) / (scaleY * drawHeight) * drawRangeY + this.minY;
float minLabelX = (0 - dx) / (scaleX * drawWidth) * drawRangeX + this.minX;
float maxLabelX = (drawWidth - dx) / (scaleX * drawWidth) * drawRangeX + this.minX;
if (minLabelX > maxLabelX) {
minLabelX = maxLabelX;
}
if (minLabelY > maxLabelY) {
minLabelY = maxLabelY;
}
this.drawAxis(g2d, AXIS_PART_SIZE, AXIS_PART_SIZE, drawHeight, nameY, minLabelY, maxLabelY, false);
this.drawAxis(g2d, AXIS_PART_SIZE, AXIS_PART_SIZE, drawWidth, nameX, minLabelX, maxLabelX, true);
}
/**
* This class implements a controller that can be used as mouse listener for a scatterplot.
*
* It calculates and holds state e.g. axis scale and other results of mouse movement.
*/
private class InteractionController extends MouseAdapter implements InteractionHandler {
/**
* Sets the size of a zoom step.
*/
private static final float ZOOM_STEP = 1.2f;
/**
* Sets threshold of the size of a box that emits an action.
*/
private static final int BOX_THRESHOLD = 10;
/**
* Controls zoom limit.
*/
private static final float SCALE_LIMIT_MAX = 50000.f;
/**
* Controls negative zooming.
*/
private static final float SCALE_LIMIT_MIN = 0.8f;
/**
* Controls the minimum distance between two lasso points.
*/
private static final int LASSO_DISTANCE_THRESHOLD = 10;
/**
* Sets update intervals when using mouse moves.
*/
private static final int MOUSE_MOVE_THRESHOLD = 10;
/**
* Stores mode of interaction.
*/
private MODES imode;
/**
* Stores the x scroll level of the mouse wheel.
*/
private float scrollLevelX;
/**
* Stores the y scroll level of the mouse wheel.
*/
private float scrollLevelY;
/**
* Stores the scale of the x axis.
*/
private float scaleX;
/**
* Stores the scale of the y axis.
*/
private float scaleY;
/**
* Stores the last seen x position of the mouse.
*/
private int lastX;
/**
* Stores the last seen y position of the mouse.
*/
private int lastY;
/**
* Stores the translation of the x axis.
*/
private float dX;
/**
* Stores the translation of the y axis.
*/
private float dY;
/**
* Stores x position of render shape.
*/
private int shapeX;
/**
* Stores y position of render shape.
*/
private int shapeY;
/**
* Stores width of render shape.
*/
private int shapeWidth;
/**
* Stores height of render shape.
*/
private int shapeHeight;
/**
* Stores pause state.
*/
private boolean pause = false;
/**
* Stores box x start.
*/
private float boxStartX;
/**
* Stores box y start.
*/
private float boxStartY;
/**
* Stores box x end.
*/
private float boxEndX;
/**
* Stores box y end.
*/
private float boxEndY;
/**
* Is there an active box.
*/
private boolean boxActive;
/**
* X coordinates of lasso points.
*/
private ArrayList<Integer> lassoX;
/**
* Y coordinates of lasso points.
*/
private ArrayList<Integer> lassoY;
/**
* Is there an active lasso.
*/
private boolean lassoActive;
/**
* Construct a new interaction controller.
*/
public InteractionController() {
this.reset(false);
}
/**
* Resets view.
*
* @param rerender
* flags if OpenGL view should be rerender after reset.
*/
public void reset(boolean rerender) {
this.scrollLevelX = 0.f;
this.scrollLevelY = 0.f;
this.scaleX = 1.f;
this.scaleY = 1.f;
this.lastX = 0;
this.lastY = 0;
this.dX = 0.f;
this.dY = 0.f;
this.boxActive = false;
this.lassoActive = false;
if (!this.pause && rerender) {
rerender();
}
}
/**
* Sets interaction mode.
*
* @param m
* new interaction mode.
*/
public void setMode(MODES m) {
this.imode = m;
}
/**
* Returns the translation of the x axis.
*
* @return the translation of the x axis.
*/
public float getDX() {
return this.dX;
}
/**
* Returns the translation of the y axis.
*
* @return the translation of the y axis.
*/
public float getDY() {
return this.dY;
}
/**
* Returns the scale of the x axis.
*
* @return the scale of the x axis.
*/
public float getScaleX() {
return this.scaleX;
}
/**
* Returns the scale of the y axis.
*
* @return the scale of the y axis.
*/
public float getScaleY() {
return this.scaleY;
}
/**
* Set x position of render shape.
*
* @param x
* x position in pixel.
*/
public void setShapeX(int x) {
this.shapeX = x;
}
/**
* set y position of render shape.
*
* @param y
* y position in pixel.
*/
public void setShapeY(int y) {
this.shapeY = y;
}
/**
* Set width of render shape.
*
* @param width
* width of render shape in pixel.
*/
public void setShapeWidth(int width) {
if (this.shapeWidth != 0) {
this.dX *= (float) width / (float) this.shapeWidth;
}
this.shapeWidth = width;
}
/**
* Set height of render shape.
*
* @param height
* height of render shape in pixel.
*/
public void setShapeHeight(int height) {
if (this.shapeHeight != 0) {
this.dY *= (float) height / (float) this.shapeHeight;
}
this.shapeHeight = height;
}
/**
* Checks if there is an active box zoom selection.
*
* @return {@code true} if box is active, {@code false} otherwise.
*/
public boolean isBoxActive() {
return this.boxActive;
}
/**
* Get x coordinates of box begin in pixel.
*
* @return x coordinates of box begin.
*/
public float getBoxStartX() {
return this.boxStartX;
}
/**
* Get y coordinates of box begin in pixel.
*
* @return Y coordinates of box begin.
*/
public float getBoxStartY() {
return this.boxStartY;
}
/**
* Get x coordinates of box end in pixel.
*
* @return x coordinates of box end.
*/
public float getBoxEndX() {
return this.boxEndX;
}
/**
* Get y coordinates of box end in pixel.
*
* @return y coordinates of box end.
*/
public float getBoxEndY() {
return this.boxEndY;
}
/**
* Checks if there is an active lasso selection.
*
* @return {@code true} if there is an active lasso, {@code false} otherwise.
*/
public boolean isLassoActive() {
return this.lassoActive;
}
/**
* Get x coordinates of all lasso points.
*
* @see #isLassoActive()
* @return x coordinates of all lasso points, {@code null} if there is no active lasso.
*/
public int[] getLassoPointsX() {
int[] result = null;
if (this.lassoX != null) {
Integer[] tmp = new Integer[this.lassoX.size()];
result = new int[this.lassoX.size()];
this.lassoX.toArray(tmp);
for (int i = 0; i < tmp.length; i++) {
result[i] = tmp[i];
}
}
return result;
}
/**
* Get y coordinates of all lasso points.
*
* @see #isLassoActive()
* @return y coordinates of all lasso points, {@code null} if there is no active lasso.
*/
public int[] getLassoPointsY() {
int[] result = null;
if (this.lassoY != null) {
Integer[] tmp = new Integer[this.lassoY.size()];
result = new int[this.lassoY.size()];
this.lassoY.toArray(tmp);
for (int i = 0; i < tmp.length; i++) {
result[i] = tmp[i];
}
}
return result;
}
@Override
public void mouseDragged(MouseEvent e) {
if (!this.pause) {
float distanceX = e.getX() - this.lastX;
float distanceY = this.shapeY + this.shapeHeight - e.getY() - this.lastY;
float distance2 = distanceX * distanceX + distanceY * distanceY;
if (distance2 > MOUSE_MOVE_THRESHOLD * MOUSE_MOVE_THRESHOLD) {
if (this.imode == MODES.SCROLL_ZOOM) {
this.dX += e.getX() - this.lastX;
this.dY += this.shapeY + this.shapeHeight - e.getY() - this.lastY;
this.lastX = e.getX();
this.lastY = this.shapeY + this.shapeHeight - e.getY();
this.checkConstraints();
rerender();
} else if (this.imode == MODES.BOXZOOM) {
this.boxEndX = e.getX();
this.boxEndY = this.shapeY + this.shapeHeight - e.getY();
rerender();
} else if (this.imode == MODES.LASSO) {
this.lassoX.set(this.lassoX.size() - 1, e.getX());
this.lassoY.set(this.lassoY.size() - 1, this.shapeY + this.shapeHeight - e.getY());
if (distance2 > LASSO_DISTANCE_THRESHOLD * LASSO_DISTANCE_THRESHOLD) {
this.lassoX.add(e.getX());
this.lassoY.add(this.shapeY + this.shapeHeight - e.getY());
this.lastX = e.getX();
this.lastY = this.shapeY + this.shapeHeight - e.getY();
}
rerender();
}
}
}
}
@Override
public void mouseEntered(MouseEvent e) {
this.lastX = e.getX();
this.lastY = this.shapeY + this.shapeHeight - e.getY();
}
@Override
public void mousePressed(MouseEvent e) {
if (!this.pause) {
if (this.imode == MODES.SCROLL_ZOOM) {
this.lastX = e.getX();
this.lastY = this.shapeY + this.shapeHeight - e.getY();
} else if (this.imode == MODES.BOXZOOM) {
this.boxStartX = e.getX();
this.boxEndX = e.getX();
this.boxStartY = this.shapeY + this.shapeHeight - e.getY();
this.boxEndY = this.shapeY + this.shapeHeight - e.getY();
this.boxActive = true;
} else if (this.imode == MODES.LASSO) {
this.lastX = e.getX();
this.lastY = this.shapeY + this.shapeHeight - e.getY();
this.lassoX = new ArrayList<Integer>();
this.lassoY = new ArrayList<Integer>();
this.lassoX.add(e.getX());
this.lassoX.add(e.getX());
this.lassoY.add(this.shapeY + this.shapeHeight - e.getY());
this.lassoY.add(this.shapeY + this.shapeHeight - e.getY());
this.lassoActive = true;
}
}
}
@Override
public void mouseReleased(MouseEvent e) {
if (!this.pause) {
if (this.imode == MODES.BOXZOOM) {
float x1 = Math.min(this.boxStartX, this.boxEndX);
float x2 = Math.max(this.boxStartX, this.boxEndX);
float y1 = Math.min(this.boxStartY, this.boxEndY);
float y2 = Math.max(this.boxStartY, this.boxEndY);
if ((x2 - x1 > BOX_THRESHOLD) && (y2 - y1 > BOX_THRESHOLD)) {
float oldScaleX = this.scaleX;
float oldScaleY = this.scaleY;
this.scaleX *= this.shapeWidth / (x2 - x1);
this.scaleY *= this.shapeHeight / (y2 - y1);
this.scrollLevelX = (float) (Math.log(this.scaleX) / Math.log(ZOOM_STEP));
this.scrollLevelY = (float) (Math.log(this.scaleY) / Math.log(ZOOM_STEP));
this.checkConstraints();
this.dX = (this.dX + this.shapeX - x1) * this.scaleX / oldScaleX;
this.dY = (this.dY - y1) * this.scaleY / oldScaleY;
this.checkConstraints();
}
this.boxActive = false;
rerender();
resetMode();
} else if (this.imode == MODES.LASSO) {
Polygon shape = new Polygon(this.getLassoPointsX(), this.getLassoPointsY(), this.lassoX.size());
int[] selection = new int[n];
int spos = 0;
for (int i = 0; i < n; i++) {
float x = vbuffer.get(i * 2);
float y = vbuffer.get(i * 2 + 1);
if (shape.contains(x * size * this.scaleX + this.dX + AXIS_PART_SIZE, y * size * this.scaleY
+ this.dY)) {
selection[spos++] = ibuffer.get(i);
}
}
this.lassoActive = false;
resetMode();
selectionController.select(selection);
}
}
}
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
if (!this.pause) {
int clicks = e.getWheelRotation();
// x scrolling
this.scrollLevelX -= clicks;
// y scrolling
this.scrollLevelY -= clicks;
float oldScaleX = this.scaleX;
float oldScaleY = this.scaleY;
float mX = e.getX() - this.shapeX;
float mY = this.shapeY + this.shapeHeight - e.getY();
// calc scale
this.scaleX = (float) Math.pow(ZOOM_STEP, this.scrollLevelX);
this.scaleY = (float) Math.pow(ZOOM_STEP, this.scrollLevelY);
this.checkConstraints();
// calc delta
this.dX = mX - (this.scaleX / oldScaleX) * (mX - this.dX);
this.dY = mY - (this.scaleY / oldScaleY) * (mY - this.dY);
this.checkConstraints();
rerender();
}
}
@Override
public void pause() {
this.pause = true;
}
@Override
public void resume() {
this.pause = false;
}
/**
* Check view constraints.
*/
private void checkConstraints() {
if (this.scaleX > SCALE_LIMIT_MAX) {
this.scaleX = SCALE_LIMIT_MAX;
this.scrollLevelX = (float) (Math.log(this.scaleX) / Math.log(ZOOM_STEP));
}
if (this.scaleY > SCALE_LIMIT_MAX) {
this.scaleY = SCALE_LIMIT_MAX;
this.scrollLevelY = (float) (Math.log(this.scaleY) / Math.log(ZOOM_STEP));
}
if (this.scaleX < SCALE_LIMIT_MIN) {
this.scaleX = SCALE_LIMIT_MIN;
this.scrollLevelX = (float) (Math.log(this.scaleX) / Math.log(ZOOM_STEP));
}
if (this.scaleY < SCALE_LIMIT_MIN) {
this.scaleY = SCALE_LIMIT_MIN;
this.scrollLevelY = (float) (Math.log(this.scaleY) / Math.log(ZOOM_STEP));
}
}
}
/**
* Set mode to scroll and zoom.
*/
private class SetModeScrollzoomAction extends AbstractAction {
private static final long serialVersionUID = 3157128508764105558L;
@Override
public void actionPerformed(ActionEvent e) {
scrollZoomButton.setSelected(true);
}
}
/**
* Set mode to boxzoom.
*/
private class SetModeBoxzoomAction extends AbstractAction {
private static final long serialVersionUID = -5293254066088051287L;
@Override
public void actionPerformed(ActionEvent e) {
boxzoomButton.setSelected(true);
}
}
/**
* Set mode to lasso.
*/
private class SetModeLassoAction extends AbstractAction {
private static final long serialVersionUID = 4100672565393904157L;
@Override
public void actionPerformed(ActionEvent e) {
lassoButton.setSelected(true);
}
}
/**
* Reset view port.
*/
private class ResetViewportAction extends AbstractAction {
private static final long serialVersionUID = -2304929064208302345L;
@Override
public void actionPerformed(ActionEvent e) {
icontroller.reset(true);
}
}
/**
* Clear selection.
*/
private class ClearSelectionAction extends AbstractAction {
private static final long serialVersionUID = 5094275272711011972L;
@Override
public void actionPerformed(ActionEvent e) {
selectionController.reset();
}
}
}