org.eclipse.ice.client.widgets.reactoreditor.sfr.PlotAnalysisView.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.ice.client.widgets.reactoreditor.sfr.PlotAnalysisView.java

Source

/*******************************************************************************
 * Copyright (c) 2013, 2015 UT-Battelle, LLC.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Jordan Deyton - Initial API and implementation and/or initial documentation
 *   Jordan Deyton - bug 474742
 *   
 *******************************************************************************/
package org.eclipse.ice.client.widgets.reactoreditor.sfr;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;

import org.eclipse.draw2d.LightweightSystem;
import org.eclipse.ice.analysistool.IData;
import org.eclipse.ice.analysistool.IDataProvider;
import org.eclipse.ice.client.common.ActionTree;
import org.eclipse.ice.client.widgets.reactoreditor.AnalysisView;
import org.eclipse.ice.client.widgets.reactoreditor.DataSource;
import org.eclipse.ice.client.widgets.reactoreditor.IAnalysisView;
import org.eclipse.ice.client.widgets.reactoreditor.PaletteColorFactory;
import org.eclipse.ice.client.widgets.reactoreditor.grid.Cell.State;
import org.eclipse.ice.client.widgets.reactoreditor.grid.GridEditorInput;
import org.eclipse.ice.reactor.sfr.base.SFRComponent;
import org.eclipse.ice.reactor.sfr.core.AssemblyType;
import org.eclipse.ice.reactor.sfr.core.assembly.PinAssembly;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.window.Window;
import org.eclipse.nebula.visualization.xygraph.dataprovider.CircularBufferDataProvider;
import org.eclipse.nebula.visualization.xygraph.figures.Trace;
import org.eclipse.nebula.visualization.xygraph.figures.Trace.PointStyle;
import org.eclipse.nebula.visualization.xygraph.figures.XYGraph;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;

/**
 * The PlotAnalysisView provides an {@link IAnalysisView} geared toward plotting
 * analysis data on an SWT XYGraph.
 * 
 * @author Jordan
 * 
 */
public class PlotAnalysisView extends AnalysisView {

    /**
     * The name for this type of analysis view. This can be used for the display
     * text of SWT widgets that reference this view.
     */
    protected static final String name = "Plot Analysis";
    /**
     * A brief description of this type of analysis view. This can be used for
     * ToolTips for SWT widgets referencing this view.
     */
    protected static final String description = "A plotter for graph-based analysis of sub-assembly data.";

    /* ----- GUI Components ----- */
    /**
     * The Canvas containing the XYGraph.
     */
    private Canvas canvas;
    /**
     * The LightweightSystem for the XYGraph. When we reset the graph, we need
     * to reset this LightweightSystem's contents to the new graph.
     */
    private LightweightSystem lws;
    /**
     * The XYGraph displayed on the Canvas.
     */
    private XYGraph xyGraph;

    /**
     * An ActionTree containing the available features for the
     * currently-selected assembly.
     */
    private final ActionTree featureTree;
    /* -------------------------- */

    /*----- Current state info -----*/
    /**
     * The currently-selected assembly for which data can be plotted.
     */
    private PinAssembly assembly;
    /**
     * The size of the current assembly or data.
     */
    private int size;

    /**
     * The current set of components in the assembly.
     */
    private final List<SFRComponent> assemblyLocations;
    /**
     * The current set of data providers for an assembly.
     */
    private final List<IDataProvider> assemblyData;

    /**
     * An ordered set of the available features in the assembly.
     */
    private final TreeSet<String> featureSet;
    /**
     * The currently-selected feature for which we can display data.
     */
    private String feature;

    /**
     * A map of traces plotted on the XYGraph. This is keyed on the row and
     * column through the getKey() method.
     */
    private final Map<String, Trace> traces;

    /**
     * A map of valid locations in the assembly. There is one for each feature.
     * If the bit is not set, then the IDataProvider in that location does not
     * have any data available for the feature.
     */
    private final Map<String, BitSet> validLocations;

    /**
     * A map of selected locations in the assembly. We can keep track of all
     * selected locations for each feature type. If the bit is set, then the
     * data in that position for that feature is already plotted.
     */
    private final Map<String, BitSet> selectedLocations;

    /**
     * A ColorFactory to use for the trace colors.
     */
    protected final PaletteColorFactory colorFactory;

    /*------------------------------*/

    /**
     * The default constructor.
     * 
     * @param dataSource
     *            The data source associated with this view (input, reference,
     *            comparison).
     */
    public PlotAnalysisView(DataSource dataSource) {
        super(dataSource);

        // Default values for all fields, including final collections.
        assembly = null;
        size = 0;
        assemblyLocations = new ArrayList<SFRComponent>();
        assemblyData = new ArrayList<IDataProvider>();
        featureSet = new TreeSet<String>();
        feature = null;
        traces = new HashMap<String, Trace>();
        validLocations = new HashMap<String, BitSet>();
        selectedLocations = new HashMap<String, BitSet>();
        colorFactory = new PaletteColorFactory();

        // Populate the list of actions (for ToolBar and context Menu).

        // Add an ActionTree (single button) for selecting series.
        ActionTree selectSeries = new ActionTree(new Action("Select Plotted Series") {
            @Override
            public void run() {
                requestPlotSeries();
            }
        });
        actions.add(selectSeries);

        // Add an ActionTree (single button) for clearing the plot.
        actions.add(new ActionTree(new Action("Clear Plot") {
            @Override
            public void run() {
                MessageDialog dialog = new MessageDialog(container.getShell(), "Clear Plot", null,
                        "Are you sure you want to remove all plotted series?", MessageDialog.QUESTION,
                        new String[] { "Yes", "No" }, 0);

                if (dialog.open() == Window.OK) {
                    /* ---- Clear the currently-graphed data. ---- */
                    BitSet selected = selectedLocations.get(feature);
                    if (selected != null) {
                        for (int i = selected.nextSetBit(0); i >= 0; i = selected.nextSetBit(i + 1)) {
                            unplotData(i);
                        }
                    }
                    // Reset the color palette.
                    colorFactory.reset();
                    /* ------------------------------------------- */
                }
            }
        }));

        // Add an ActionTree to list the available features.
        featureTree = new ActionTree("Data Feature");
        actions.add(featureTree);

        // Add an ActionTree (single button) for saving the viewer image.
        ActionTree saveImage = new ActionTree(new Action("Save Image") {
            @Override
            public void run() {
                saveCanvasImage(canvas);
            }
        });
        actions.add(saveImage);

        return;
    }

    /**
     * Sets the assembly for which data can be plotted. It resets all
     * currently-plotted data.
     * 
     * @param assembly
     *            The new assembly whose IDataProviders can be queried to plot
     *            feature data.
     */
    private void setAssembly(PinAssembly assembly) {
        if (assembly != null && assembly != this.assembly) {
            // Update the assembly.
            this.assembly = assembly;

            // Get the size of the assembly.
            size = assembly.getSize();
            int rows = size;
            int columns = size;
            int totalSize = rows * columns;

            /* ---- Update the components, data providers, and features. ---- */
            // Reset the lists.
            assemblyLocations.clear();
            assemblyData.clear();
            // Also clear the set of available features.
            featureSet.clear();
            validLocations.clear();
            selectedLocations.clear();

            for (int row = 0; row < rows; row++) {
                for (int column = 0; column < columns; column++) {
                    SFRComponent component = assembly.getPinByLocation(row, column);
                    IDataProvider dataProvider = assembly.getDataProviderByLocation(row, column);
                    if (dataProvider != null) {
                        // Get the features available here. Add any new feature
                        // to the set of features. Also mark a flag for a valid
                        // location if there is actually some data available for
                        // the feature.
                        for (String feature : dataProvider.getFeatureList()) {
                            if (!dataProvider.getDataAtCurrentTime(feature).isEmpty()) {
                                if (!featureSet.contains(feature)) {
                                    featureSet.add(feature);
                                    validLocations.put(feature, new BitSet(totalSize));
                                    selectedLocations.put(feature, new BitSet(totalSize));
                                }
                                validLocations.get(feature).set(row * columns + column);
                            }
                        }
                    } else {
                        // Create an empty data provider instead of putting a
                        // null value in the list.
                        dataProvider = new SFRComponent();
                    }
                    // Add the pin and its data provider to the lists.
                    assemblyLocations.add(component);
                    assemblyData.add(dataProvider);
                }
            }
            /* -------------------------------------------------------------- */

            // Add Actions for setting the features to the feature ActionTree.
            featureTree.removeAll();
            for (final String feature : featureSet) {
                Action action = new Action(feature) {
                    @Override
                    public void run() {
                        setFeature(feature);
                    }
                };
                featureTree.add(new ActionTree(action));
            }

            // Load the first available feature.
            if (!featureSet.isEmpty()) {
                // Grab the first available feature.
                feature = featureSet.first();
            } else {
                feature = null;
            }

            // Reset the graph.
            resetGraph();
        }

        return;
    }

    /**
     * Sets the current feature for which data can be plotted.
     * 
     * @param feature
     *            The new feature to use.
     */
    protected void setFeature(String feature) {
        if (feature != this.feature) {
            // Update the feature.
            this.feature = feature;

            // Reset the graph.
            resetGraph();
        }
        return;
    }

    /**
     * Creates a {@link GridEditorDialog} to prompt the user for the current
     * selection of plotted data for the current feature.
     */
    private void requestPlotSeries() {
        logger.info("Request plot series called");
        if (feature != null) {
            logger.info("Feature is " + feature);

            // Get the size of the assembly.
            int rows = size;
            int columns = size;

            // ---- Create and configure input for a GEF grid. ---- //
            // Create an IEditorInput for the GridEditorDialog.
            GridEditorInput input = new GridEditorInput(rows, columns);

            // Disable all row/column/cell labels.
            input.showCellLabels = false;
            input.showRowLabels = false;
            input.showColumnLabels = false;

            BitSet valid = validLocations.get(feature);
            if (valid == null) {
                valid = new BitSet(rows * columns);
            }
            BitSet selected = selectedLocations.get(feature);
            if (selected == null) {
                selected = new BitSet(rows * columns);
            }
            // Get the current list of valid/selected components in the assembly
            // for the current feature. Locations with no component are invalid,
            // locations with a component but no data are disabled, others are
            // either unselected or selected.
            List<State> states = new ArrayList<State>(rows * columns);
            for (int i = 0; i < rows * columns; i++) {
                State state = (assemblyLocations.get(i) != null ? State.DISABLED : State.INVALID);
                if (selected.get(i)) {
                    state = State.SELECTED;
                } else if (valid.get(i)) {
                    state = State.UNSELECTED;
                }
                states.add(state);
            }

            // Set the list of states.
            input.setStates(states);
            // ---------------------------------------------------- //

            // ---- Open the GridEditorDialog and get the selection. ---- //
            GridEditorDialog dialog = new GridEditorDialog(container.getShell(), input);
            if (dialog.open() != Window.CANCEL) {
                // Get the resulting selection.
                BitSet newSelectedLocations = dialog.getSelectedLocations();

                // Compute the removed locations. This is effectively
                // selectedLocations - newSelectedLocations.
                BitSet removedLocations = (BitSet) selected.clone();
                removedLocations.andNot(newSelectedLocations);

                // Compute the added locations. This is the same as
                // newSelectedLocations - selectedLocations.
                BitSet addedLocations = (BitSet) newSelectedLocations.clone();
                addedLocations.andNot(selected);

                // Unplot all of the removed locations.
                for (int i = removedLocations.nextSetBit(0); i >= 0; i = removedLocations.nextSetBit(i + 1)) {
                    unplotData(i);
                    states.set(i, State.UNSELECTED);
                }

                // Plot all of the added locations.
                for (int i = addedLocations.nextSetBit(0); i >= 0; i = addedLocations.nextSetBit(i + 1)) {
                    plotData(i);
                    states.set(i, State.SELECTED);
                }
            }
            // ---------------------------------------------------------- //

        }
        return;
    }

    /**
     * Plots the data for a specific IDataProvider on the plot for the current
     * feature.
     * 
     * @param index
     *            The index of the cell in the assembly grid. This corresponds
     *            to an IDataProvider.
     */
    private void plotData(int index) {
        // Get the row and column for the index.
        String key = getKey(index);

        if (index >= 0 && index < size * size && !traces.containsKey(key)) {
            // Get the data provider.
            IDataProvider dataProvider = assemblyData.get(index);
            ArrayList<IData> data = dataProvider.getDataAtCurrentTime(feature);
            int size = data.size();
            if (size > 0) {
                // Allocate the arrays of x and y values.
                double[] xValues = new double[size];
                double[] yValues = new double[size];

                // Set the x and y values from the data.
                for (int i = 0; i < data.size(); i++) {
                    xValues[i] = data.get(i).getPosition().get(2);
                    // FIXME - We may need to account for un-configured
                    // positions in another way. The default value for
                    // un-configured positions is 0.0. getPosition() always
                    // returns an ArrayList of 3 doubles (x, y, z).
                    if (xValues[i] == 0.0) {
                        xValues[i] = i;
                    }
                    yValues[i] = data.get(i).getValue();
                }

                // Create a trace data provider.
                CircularBufferDataProvider traceDataProvider = new CircularBufferDataProvider(false);
                traceDataProvider.setBufferSize(size);
                traceDataProvider.setCurrentXDataArray(xValues);
                traceDataProvider.setCurrentYDataArray(yValues);

                // Create the trace and set its properties.
                Trace trace = new Trace(key, xyGraph.primaryXAxis, xyGraph.primaryYAxis, traceDataProvider);
                trace.setPointStyle(PointStyle.XCROSS);
                int hex = colorFactory.findColor(0.0);
                trace.setTraceColor(colorFactory.createColor(canvas.getDisplay(), hex));

                // Add the trace to the graph.
                traces.put(key, trace);
                xyGraph.addTrace(trace);

                // Update the bit in the feature's selected locations BitSet.
                BitSet selected = selectedLocations.get(feature);
                selected.set(index);
            }
        }
        return;
    }

    /**
     * Removes the data for a specific IDataProvider from the plot for the
     * current feature.
     * 
     * @param index
     *            The index of the cell in the assembly grid. This corresponds
     *            to an IDataProvider.
     */
    private void unplotData(int index) {
        if (index >= 0 && index < size * size) {
            // Compute the key, e.g., Pin F,7
            String key = getKey(index);

            // Update the bit in the feature's selected locations BitSet.
            BitSet selected = selectedLocations.get(feature);
            selected.clear(index);

            // Remove the trace with the specified key from the Map of traces
            // and the graph.
            Trace trace = traces.remove(key);
            if (trace != null) {
                xyGraph.removeTrace(trace);
            }
        }
        return;
    }

    /**
     * Get a key for an assembly component based on its row and column.
     * 
     * @param row
     *            The row of the component.
     * @param col
     *            The column of the component.
     * @return A key string for the assembly component.
     */
    protected String getKey(int index) {

        String key = null;

        // Make sure the index is valid.
        if (index >= 0 && index < size * size) {
            // Get the row and column.
            int row = index / size;
            int column = index % size;

            // Get a key/
            key = Integer.toString(column) + ":" + Integer.toString(row);
        }
        return key;
    }

    /**
     * This method resets the graph and puts only the currently-selected data
     * series for the current feature on the plot. <br>
     * This method does not remove any data. To remove plotted data, loop over
     * the selected indices in {@link #selectedLocations} and
     * {@link #unplotData(int)} them.
     */
    protected void resetGraph() {

        // Update the y-axis title.
        xyGraph.primaryYAxis.setTitle((feature != null ? feature : "No feature selected"));

        // ---- Remove all the traces. ---- //
        for (Trace trace : traces.values()) {
            xyGraph.removeTrace(trace);
            // We have to manally remove the trace from both axes because
            // xyGraph.removeTrace() does not do this automatically.
            xyGraph.primaryXAxis.removeTrace(trace);
            xyGraph.primaryYAxis.removeTrace(trace);
        }
        traces.clear();
        // -------------------------------- //

        // Reset the color palette.
        colorFactory.reset();

        // ---- Add traces for selected components for the feature. ---- //
        BitSet selected = selectedLocations.get(feature);
        if (selected != null) {
            for (int i = selected.nextSetBit(0); i >= 0; i = selected.nextSetBit(i + 1)) {
                plotData(i);
            }
        }

        // Force the graph to auto-scale.
        xyGraph.primaryXAxis.performAutoScale(true);
        xyGraph.primaryYAxis.performAutoScale(true);
        // ------------------------------------------------------------- //

        return;
    }

    /* ---- Implements IAnalysisView ---- */
    /**
     * Fills out the parent Composite with information and widgets related to
     * this particular IAnalysisView.
     * 
     * @param container
     *            The Composite containing this IAnalysisView.
     */
    @Override
    public void createViewContent(Composite container) {
        super.createViewContent(container);

        // Initialize the canvas, the LightweightSystem, and the XYGraph.
        canvas = new Canvas(container, SWT.DOUBLE_BUFFERED);
        lws = new LightweightSystem(canvas);

        // Set the rightClickMenu for any Composites that will need to use it.
        canvas.setMenu(actionMenuManager.createContextMenu(container));

        // All we have is the canvas, so set the container to a FillLayout.
        container.setLayout(new FillLayout());

        // ---- Create and configure the XYGraph. ---- //
        // Create the XYGraph.
        xyGraph = new XYGraph();
        lws.setContents(xyGraph);

        // Some customization of the XYGraph.
        xyGraph.setTitle(dataSource + " Data");

        // Sets the background color of the graph to the canvas's background.
        xyGraph.setBackgroundColor(container.getBackground());

        // Customize the X axis.
        xyGraph.primaryXAxis.setTitle("Axial Level");
        xyGraph.primaryXAxis.setRange(0, 1);
        xyGraph.primaryXAxis.setAutoScale(true);
        xyGraph.primaryXAxis.setAutoScaleThreshold(0.005);

        // Customize the Y axis.
        xyGraph.primaryYAxis.setTitle("No feature selected");
        xyGraph.primaryYAxis.setRange(0, 1);
        xyGraph.primaryYAxis.setAutoScale(true);
        xyGraph.primaryYAxis.setAutoScaleThreshold(0.005);
        // ------------------------------------------- //

        return;
    }

    /**
     * Gets the name for this type of analysis view. This can be used for the
     * display text of SWT widgets that reference this view.
     * 
     * @return The IAnalysisView's name.
     */
    @Override
    public String getName() {
        return name;
    }

    /**
     * Gets a brief description of this type of analysis view. This can be used
     * for ToolTips for SWT widgets referencing this view.
     * 
     * @return The IAnalysisView's description.
     */
    @Override
    public String getDescription() {
        return description;
    }

    /* ---------------------------------- */

    /* ---- Implements IStateListener ---- */
    /**
     * Registers any keys of interest with the current broker.
     */
    @Override
    public void registerKeys() {
        String key = dataSource + "-sfr" + AssemblyType.Fuel.toString();
        SFRComponentInfo info = (SFRComponentInfo) broker.register(key, this);
        if (info != null) {
            setAssembly((PinAssembly) info.sfrComponent);
        }
    }

    /**
     * Unregisters any keys from the current broker.
     */
    @Override
    public void unregisterKeys() {
        String key = dataSource + "-sfr" + AssemblyType.Fuel.toString();
        broker.unregister(key, this);

        return;
    }

    /**
     * This is called by the broker when a key of interest has changed.
     */
    @Override
    public void update(String key, Object value) {
        // A new fuel assembly was selected.
        SFRComponentInfo info = (SFRComponentInfo) value;
        if (info != null) {
            setAssembly((PinAssembly) info.sfrComponent);
        }
    }
    /* ----------------------------------- */

}