package gui.views.plots; import gui.bsvComponents.BSVComboBox; import gui.bsvComponents.BSVSpinner; import gui.settings.Settings; import gui.views.ViewUtils; import java.awt.Color; import java.awt.Component; import java.awt.Graphics2D; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.util.ArrayList; import javax.media.opengl.GL2; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.DefaultComboBoxModel; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JPanel; import javax.swing.JSpinner; import javax.swing.SpinnerNumberModel; import org.apache.commons.lang3.ArrayUtils; import controller.DataHub; import controller.ElementData; import controller.Feature; import controller.SelectionController; import controller.SubspaceController; import db.DatabaseAccessException; /** * This class implements a histogram plot. It uses OpenGL for fast rendering. */ public class HistPlot extends GLPlot { private static final long serialVersionUID = -8863105505703128706L; /** * Sets the size of the white borders around the plot. */ private static final int BORDER_SIZE = 60; /** * Sets the maximum of steps, that the user can choose. */ private static final int MAX_STEPS = 500; /** * Controls alpha of non selected elements. */ private static final float NON_SELECTED_ALPHA_FACTOR = 0.2f; /** * Combo box for features selection. */ private JComboBox featureCombo; /** * Spinner for bar count. */ private JSpinner stepCountSpinner; /** * Stores the frequency values. */ private float[] values; /** * Stores red color component. */ private float[] red; /** * Stores green color component. */ private float[] green; /** * Stores blue color component. */ private float[] blue; /** * Stores alpha. */ private float[] alpha; /** * For this feature the histogram will be plotted. */ private Feature activeFeature; /** * Sets the number of steps/bars in the histogram. */ private int stepCount; /** * Stores the name of the active feature. */ private String featureName; /** * Stores the minimum value of the feature. */ private float min; /** * Stores the feature range. */ private float range; /** * Stores the maximum frequency that should fit the view. */ private float maxFrequency; /** * Constructs a histogram plot using a DataHub, a SelectionController and a SubspaceController. * * @param dataHub * a preinitialized DataHub. * @param selectionController * a preinitialized SelectionController. * @param subspaceController * a preinitialized SubspaceController. */ public HistPlot(DataHub dataHub, SelectionController selectionController, SubspaceController subspaceController) { super(dataHub, selectionController, subspaceController); } @Override protected void createUI() { this.stepCount = 10; this.featureCombo = new BSVComboBox(new Feature[0]); featureCombo.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { activeFeature = (Feature) e.getItem(); update(null, null); } }); JPanel dataPanel = new JPanel(new GridLayout(0, 1)); dataPanel.setBorder(BorderFactory.createTitledBorder(Settings.getInstance().getResourceBundle().getString( "histogramFeature"))); dataPanel.add(this.featureCombo); this.stepCountSpinner = new BSVSpinner(new SpinnerNumberModel(this.stepCount, 1, MAX_STEPS, 1)); ((JSpinner.DefaultEditor) this.stepCountSpinner.getEditor()).getTextField().addKeyListener(new KeyAdapter() { @Override public void keyReleased(final KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER) { SpinnerNumberModel model = (SpinnerNumberModel) stepCountSpinner.getModel(); int n = (Integer) model.getNumber(); stepCount = Math.max(1, Math.min(n, MAX_STEPS)); update(null, null); } } }); for (Component child : this.stepCountSpinner.getComponents()) { if ("Spinner.nextButton".equals(child.getName())) { ((JButton) child).addActionListener(new AbstractAction() { private static final long serialVersionUID = 7393143016298737890L; @Override public void actionPerformed(ActionEvent e) { SpinnerNumberModel model = (SpinnerNumberModel) stepCountSpinner.getModel(); int n = (Integer) model.getNumber(); stepCount = Math.max(1, Math.min(n + 1, MAX_STEPS)); update(null, null); } }); } if ("Spinner.previousButton".equals(child.getName())) { ((JButton) child).addActionListener(new AbstractAction() { private static final long serialVersionUID = 2393557689211586904L; @Override public void actionPerformed(ActionEvent e) { SpinnerNumberModel model = (SpinnerNumberModel) stepCountSpinner.getModel(); int n = (Integer) model.getNumber(); stepCount = Math.max(1, Math.min(n - 1, MAX_STEPS)); update(null, null); } }); } } JPanel visualPanel = new JPanel(new GridLayout(0, 1)); visualPanel.setBorder(BorderFactory.createTitledBorder(Settings.getInstance().getResourceBundle().getString( "histogramBars"))); visualPanel.add(stepCountSpinner); this.addToSidebar(dataPanel); this.addToSidebar(visualPanel); } @Override protected void setFeatures(Feature[] features) { this.featureCombo.setModel(new DefaultComboBoxModel(features)); this.activeFeature = (Feature) featureCombo.getSelectedItem(); } @Override protected void init(GL2 gl) { // ignore } @Override protected void rescale(GL2 gl) { // ignore } @Override protected void draw(GL2 gl) { // calc some things float width = this.getShapeWidth() - 2 * BORDER_SIZE; float barWidth = width / stepCount; float height = this.getShapeHeight() - 2 * BORDER_SIZE; // draw bars gl.glPushMatrix(); gl.glTranslatef(BORDER_SIZE, BORDER_SIZE, 0.f); for (int i = 0; i < stepCount; i++) { gl.glBegin(GL2.GL_POLYGON); gl.glColor4f(this.red[i], this.green[i], this.blue[i], this.alpha[i]); gl.glVertex2f(i * barWidth, 0.f); gl.glVertex2f(i * barWidth, height * this.values[i] / this.maxFrequency); gl.glVertex2f((i + 1) * barWidth, height * this.values[i] / this.maxFrequency); gl.glVertex2f((i + 1) * barWidth, 0.f); gl.glEnd(); } gl.glPopMatrix(); gl.glColor4f(0.f, 0.f, 0.f, 1.f); // draw axis this.drawAxis(gl, BORDER_SIZE, BORDER_SIZE, height, Settings.getInstance().getResourceBundle().getString( "histogramFrequency"), 0.f, this.maxFrequency, false); this.drawAxis(gl, BORDER_SIZE, BORDER_SIZE, width, this.featureName, this.min, this.min + this.range, true); } @Override protected void dispose(GL2 gl) { // ignore } @Override public String getName() { return Settings.getInstance().getResourceBundle().getString("histplot"); } @Override @SuppressWarnings("unchecked") protected synchronized void processData() throws DatabaseAccessException, InterruptedException { int steps = this.stepCount; Feature feature = this.activeFeature; boolean selection = this.selectionController.isSomethingSelected(); this.values = new float[steps]; this.red = new float[steps]; this.green = new float[steps]; this.blue = new float[steps]; this.alpha = new float[steps]; int[] count = new int[steps]; this.min = feature.getMinValue(); this.range = feature.getMaxValue() - this.min; this.featureName = feature.getName(); ArrayList<Float>[] redVars = new ArrayList[steps]; ArrayList<Float>[] greenVars = new ArrayList[steps]; ArrayList<Float>[] blueVars = new ArrayList[steps]; ArrayList<Float>[] alphaVars = new ArrayList[steps]; for (int i = 0; i < steps; i++) { redVars[i] = new ArrayList<Float>(); greenVars[i] = new ArrayList<Float>(); blueVars[i] = new ArrayList<Float>(); alphaVars[i] = new ArrayList<Float>(); } ElementData[] data = this.dataHub.getData(); float summand = 1.f / data.length; for (int i = 0; i < data.length; i++) { float value = data[i].getValue(feature); if (!Float.isNaN(value)) { int index = (int) Math.floor((value - this.min) / this.range * steps); if (index >= steps) { index = steps - 1; } if (index < 0) { index = 0; } this.values[index] += summand; float alphaFactor = 1.f; if (selection) { alphaFactor = this.selectionController.isSelected(data[i].getId()) ? 1.f : NON_SELECTED_ALPHA_FACTOR; } // get color Color color = ViewUtils.calcColor(data[i]); redVars[index].add(color.getRed() / 255.f); greenVars[index].add(color.getGreen() / 255.f); blueVars[index].add(color.getBlue() / 255.f); alphaVars[index].add(color.getAlpha() / 255.f * alphaFactor); count[index]++; } } // calc color for (int i = 0; i < steps; i++) { Float[] redVarsTmp = new Float[redVars[i].size()]; redVars[i].toArray(redVarsTmp); float[] redVarsNative = ArrayUtils.toPrimitive(redVarsTmp); Float[] greenVarsTmp = new Float[greenVars[i].size()]; greenVars[i].toArray(greenVarsTmp); float[] greenVarsNative = ArrayUtils.toPrimitive(greenVarsTmp); Float[] blueVarsTmp = new Float[blueVars[i].size()]; blueVars[i].toArray(blueVarsTmp); float[] blueVarsNative = ArrayUtils.toPrimitive(blueVarsTmp); Float[] alphaVarsTmp = new Float[alphaVars[i].size()]; alphaVars[i].toArray(alphaVarsTmp); float[] alphaVarsNative = ArrayUtils.toPrimitive(alphaVarsTmp); this.red[i] = ViewUtils.combineComponent(redVarsNative, alphaVarsNative, count[i]); this.green[i] = ViewUtils.combineComponent(greenVarsNative, alphaVarsNative, count[i]); this.blue[i] = ViewUtils.combineComponent(blueVarsNative, alphaVarsNative, count[i]); this.alpha[i] = ViewUtils.combineAlpha(alphaVarsNative, count[i]); } // scale up this.updateMaxFrequency(steps); } @Override protected synchronized void uploadData(GL2 gl) { // do nothing } @Override protected void draw(Graphics2D g2d) { // calc some things float width = this.getShapeWidth() - 2 * BORDER_SIZE; float barWidth = width / stepCount; float height = this.getShapeHeight() - 2 * BORDER_SIZE; // draw bars for (int i = 0; i < stepCount; i++) { g2d.setColor(new Color(this.red[i], this.green[i], this.blue[i], this.alpha[i])); g2d.fillRect(Math.round(i * barWidth + BORDER_SIZE), BORDER_SIZE, Math.round(barWidth), Math.round(height * this.values[i] / this.maxFrequency)); } g2d.setColor(Color.BLACK); // draw axis this.drawAxis(g2d, BORDER_SIZE, BORDER_SIZE, height, Settings.getInstance().getResourceBundle().getString( "histogramFrequency"), 0.f, this.maxFrequency, false); this.drawAxis(g2d, BORDER_SIZE, BORDER_SIZE, width, this.featureName, this.min, this.min + this.range, true); } /** * Updates the maxFrequency value. */ private synchronized void updateMaxFrequency(int steps) { this.maxFrequency = 0.f; for (int i = 0; i < steps; i++) { this.maxFrequency = Math.max(this.maxFrequency, this.values[i]); } } @Override protected void registerShortcuts() { // ignore } @Override protected void unregisterShortcuts() { // ignore } }