org.moeaframework.analysis.diagnostics.ApproximationSetViewer.java Source code

Java tutorial

Introduction

Here is the source code for org.moeaframework.analysis.diagnostics.ApproximationSetViewer.java

Source

/* Copyright 2009-2016 David Hadka
 *
 * This file is part of the MOEA Framework.
 *
 * The MOEA Framework 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 3 of the License, or (at your
 * option) any later version.
 *
 * The MOEA Framework 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 the MOEA Framework.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.moeaframework.analysis.diagnostics;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.Paint;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import java.util.Vector;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JSplitPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.event.ChartChangeEvent;
import org.jfree.chart.event.ChartChangeListener;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.Range;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.moeaframework.analysis.collector.Accumulator;
import org.moeaframework.core.NondominatedPopulation;
import org.moeaframework.core.Solution;
import org.moeaframework.core.variable.RealVariable;
import org.moeaframework.util.Localization;

/**
 * Window for displaying approximation set dynamics.
 */
public class ApproximationSetViewer extends JFrame
        implements ChangeListener, ActionListener, ChartChangeListener, ListSelectionListener {

    private static final long serialVersionUID = -7556845366893802202L;

    /**
     * The localization instance for produce locale-specific strings.
     */
    private static Localization localization = Localization.getLocalization(ApproximationSetViewer.class);

    /**
     * The accumulators which contain {@code "Approximation Set"} entries.
     */
    private List<Accumulator> accumulators;

    /**
     * The container of the plot.
     */
    private JPanel chartContainer;

    /**
     * The slider controlling the current NFE.
     */
    private JSlider slider;

    /**
     * The x-axis bounds of the initial approximation set(s).
     */
    private Range initialRangeBounds;

    /**
     * The y-axis bounds of the initial approximation set(s).
     */
    private Range initialDomainBounds;

    /**
     * The x-axis bounds for zooming; or {@code null} if the user has not yet
     * set zoom bounds.
     */
    private Range zoomRangeBounds;

    /**
     * The y-axis bounds for zooming; or {@code null} if the user has not yet
     * set zoom bounds.
     */
    private Range zoomDomainBounds;

    /**
     * The x-axis bounds of the reference set.
     */
    private Range referenceRangeBounds;

    /**
     * The y-axis bounds of the reference set.
     */
    private Range referenceDomainBounds;

    /**
     * The control for choosing to scale the plot using the initial
     * approximation set bounds.
     */
    private JRadioButton useInitialBounds;

    /**
     * The control for choosing to scale the plot using the reference set
     * bounds.
     */
    private JRadioButton useReferenceSetBounds;

    /**
     * The control for choosing to scale the plot dynamically at each NFE.
     */
    private JRadioButton useDynamicBounds;

    /**
     * The control for choosing to scale the plot using the user-specified zoom.
     */
    private JRadioButton useZoomBounds;

    /**
     * The reference set.
     */
    private NondominatedPopulation referenceSet;

    /**
     * The control for selecting which seeds to display in the plot.
     */
    private JList seedList;

    /**
     * The control for selecting all seeds.
     */
    private JButton selectAll;

    /**
     * The control for selecting which objective, constraint or decision
     * variable will be displayed on the x-axis.
     */
    private JComboBox xAxisSelection;

    /**
     * The control for selecting which objective, constraint or decision
     * variable will be displayed on the y-axis.
     */
    private JComboBox yAxisSelection;

    /**
     * Maintains a mapping from series key to paints displayed in the plot.
     */
    private PaintHelper paintHelper;

    /**
     * Constructs a new window for displaying approximation set dynamics.  This
     * constructor must only be invoked on the event dispatch thread.
     * 
     * @param name the name or title for the data
     * @param accumulators the accumulators containing approximation set data
     * @param referenceSet the reference set for the problem
     */
    public ApproximationSetViewer(String name, List<Accumulator> accumulators,
            NondominatedPopulation referenceSet) {
        super(localization.getString("title.approximationSetViewer", name));
        this.accumulators = accumulators;
        this.referenceSet = referenceSet;

        setSize(800, 600);
        setMinimumSize(new Dimension(800, 600));
        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

        initialize();
        layoutComponents();

        //this results in a call to update()
        selectAll.doClick();
    }

    /**
     * Initializes the reference set bounds.
     */
    protected void initializeReferenceSetBounds() {
        if (referenceSet == null) {
            return;
        }

        double domainMin = Double.POSITIVE_INFINITY;
        double domainMax = Double.NEGATIVE_INFINITY;
        double rangeMin = Double.POSITIVE_INFINITY;
        double rangeMax = Double.NEGATIVE_INFINITY;

        for (Solution solution : referenceSet) {
            domainMin = Math.min(domainMin, getValue(solution, 0));
            domainMax = Math.max(domainMax, getValue(solution, 0));
            rangeMin = Math.min(rangeMin, getValue(solution, 1));
            rangeMax = Math.max(rangeMax, getValue(solution, 1));
        }

        domainMax += (domainMax - domainMin);
        rangeMax += (rangeMax - rangeMin);

        referenceDomainBounds = new Range(domainMin, domainMax);
        referenceRangeBounds = new Range(rangeMin, rangeMax);
    }

    /**
     * Initializes this window.  This method is invoked in the constructor, and
     * should not be invoked again.
     */
    protected void initialize() {
        //initialize the NFE slider
        int minimumNFE = Integer.MAX_VALUE;
        int maximumNFE = Integer.MIN_VALUE;

        for (Accumulator accumulator : accumulators) {
            minimumNFE = Math.min(minimumNFE, (Integer) accumulator.get("NFE", 0));
            maximumNFE = Math.max(maximumNFE, (Integer) accumulator.get("NFE", accumulator.size("NFE") - 1));
        }

        slider = new JSlider(minimumNFE, maximumNFE, minimumNFE);
        slider.setPaintTicks(true);
        slider.setMinorTickSpacing(100);
        slider.setMajorTickSpacing(1000);
        slider.addChangeListener(this);

        //initializes the options available for axis plotting
        Solution solution = (Solution) ((List<?>) accumulators.get(0).get("Approximation Set", 0)).get(0);
        Vector<String> objectives = new Vector<String>();

        for (int i = 0; i < solution.getNumberOfObjectives(); i++) {
            objectives.add(localization.getString("text.objective", i + 1));
        }

        for (int i = 0; i < solution.getNumberOfConstraints(); i++) {
            objectives.add(localization.getString("text.constraint", i + 1));
        }

        for (int i = 0; i < solution.getNumberOfVariables(); i++) {
            objectives.add(localization.getString("text.variable", i + 1));
        }

        xAxisSelection = new JComboBox(objectives);
        yAxisSelection = new JComboBox(objectives);

        xAxisSelection.setSelectedIndex(0);
        yAxisSelection.setSelectedIndex(1);

        xAxisSelection.addActionListener(this);
        yAxisSelection.addActionListener(this);

        //initialize the reference set bounds
        initializeReferenceSetBounds();

        //initialize plotting controls
        useInitialBounds = new JRadioButton(localization.getString("action.useInitialBounds.name"));
        useReferenceSetBounds = new JRadioButton(localization.getString("action.useReferenceSetBounds.name"));
        useDynamicBounds = new JRadioButton(localization.getString("action.useDynamicBounds.name"));
        useZoomBounds = new JRadioButton(localization.getString("action.useZoom.name"));

        useInitialBounds.setToolTipText(localization.getString("action.useInitialBounds.description"));
        useReferenceSetBounds.setToolTipText(localization.getString("action.useReferenceSetBounds.description"));
        useDynamicBounds.setToolTipText(localization.getString("action.useDynamicBounds.description"));
        useZoomBounds.setToolTipText(localization.getString("action.useZoom.description"));

        ButtonGroup rangeButtonGroup = new ButtonGroup();
        rangeButtonGroup.add(useInitialBounds);
        rangeButtonGroup.add(useReferenceSetBounds);
        rangeButtonGroup.add(useDynamicBounds);
        rangeButtonGroup.add(useZoomBounds);

        if (referenceSet == null) {
            useReferenceSetBounds.setEnabled(false);
        }

        useInitialBounds.setSelected(true);
        useInitialBounds.addActionListener(this);
        useReferenceSetBounds.addActionListener(this);
        useDynamicBounds.addActionListener(this);
        useZoomBounds.addActionListener(this);

        //initialize the seed list
        String[] seeds = new String[accumulators.size()];

        for (int i = 0; i < accumulators.size(); i++) {
            seeds[i] = localization.getString("text.seed", i + 1);
        }

        seedList = new JList(seeds);
        seedList.addListSelectionListener(this);

        selectAll = new JButton(new AbstractAction() {

            private static final long serialVersionUID = -3709557130361259485L;

            {
                putValue(Action.NAME, localization.getString("action.selectAll.name"));
            }

            @Override
            public void actionPerformed(ActionEvent e) {
                seedList.getSelectionModel().setSelectionInterval(0, seedList.getModel().getSize() - 1);
            }

        });

        //initialize miscellaneous components
        paintHelper = new PaintHelper();
        paintHelper.set(localization.getString("text.referenceSet"), Color.BLACK);

        chartContainer = new JPanel(new BorderLayout());
    }

    /**
     * Lays out the components on this window.  This method is invoked by the
     * constructor, and should not be invoked again.
     */
    protected void layoutComponents() {
        setLayout(new BorderLayout());

        JPanel buttonPane = new JPanel(new FlowLayout(FlowLayout.CENTER));
        buttonPane.add(useInitialBounds);
        buttonPane.add(useReferenceSetBounds);
        buttonPane.add(useDynamicBounds);
        buttonPane.add(useZoomBounds);

        JPanel objectivePane = new JPanel(new FlowLayout(FlowLayout.CENTER));
        objectivePane.add(new JLabel(localization.getString("text.xAxis")));
        objectivePane.add(xAxisSelection);
        objectivePane.add(new JLabel(localization.getString("text.yAxis")));
        objectivePane.add(yAxisSelection);

        JPanel controlPane = new JPanel(new GridLayout(3, 1));
        controlPane.add(slider);
        controlPane.add(buttonPane);
        controlPane.add(objectivePane);

        JPanel rightPane = new JPanel(new BorderLayout());
        rightPane.add(chartContainer, BorderLayout.CENTER);
        rightPane.add(controlPane, BorderLayout.SOUTH);

        JPanel leftPane = new JPanel(new BorderLayout());
        leftPane.setBorder(BorderFactory.createTitledBorder(localization.getString("text.seeds")));
        leftPane.add(new JScrollPane(seedList), BorderLayout.CENTER);
        leftPane.add(selectAll, BorderLayout.SOUTH);
        leftPane.setMinimumSize(new Dimension(100, 100));

        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPane, rightPane);

        add(splitPane, BorderLayout.CENTER);
    }

    /**
     * Returns the x- or y-axis value for the specified solution.  The returned
     * value changes based on the user's preferences, and may return the value
     * stored in an objective, constraint or variable.  Returns {@code 0.0} if
     * the value could not be parsed.
     * 
     * @param solution the solution whose x- or y-axis value is returned
     * @param axis {@code 0} for x-axis, {@code 1} for y-axis
     * @return the x- or y-axis value for the specified solution
     */
    protected double getValue(Solution solution, int axis) {
        int selection = axis == 0 ? xAxisSelection.getSelectedIndex() : yAxisSelection.getSelectedIndex();

        if (selection < solution.getNumberOfObjectives()) {
            return solution.getObjective(selection);
        } else {
            selection -= solution.getNumberOfObjectives();
        }

        if (selection < solution.getNumberOfConstraints()) {
            return solution.getConstraint(selection);
        } else {
            selection -= solution.getNumberOfConstraints();
        }

        if (selection < solution.getNumberOfVariables()) {
            if (solution.getVariable(selection) instanceof RealVariable) {
                return ((RealVariable) solution.getVariable(selection)).getValue();
            } else {
                return 0.0;
            }
        } else {
            return 0.0;
        }
    }

    /**
     * Updates the display.  This method must only be invoked on the event
     * dispatch thread.
     */
    protected void update() {
        XYSeriesCollection dataset = new XYSeriesCollection();

        //generate approximation set
        for (int seedIndex : seedList.getSelectedIndices()) {
            Accumulator accumulator = accumulators.get(seedIndex);
            int index = 0;

            if (!accumulator.keySet().contains("Approximation Set")) {
                continue;
            }

            while ((index < accumulator.size("NFE") - 1)
                    && ((Integer) accumulator.get("NFE", index) < slider.getValue())) {
                index++;
            }

            List<?> list = (List<?>) accumulator.get("Approximation Set", index);
            XYSeries series = new XYSeries(localization.getString("text.seed", seedIndex + 1), false, true);

            for (Object object : list) {
                Solution solution = (Solution) object;
                series.add(getValue(solution, 0), getValue(solution, 1));
            }

            dataset.addSeries(series);
        }

        //generate reference set
        if (referenceSet != null) {
            XYSeries series = new XYSeries(localization.getString("text.referenceSet"), false, true);

            for (Solution solution : referenceSet) {
                series.add(getValue(solution, 0), getValue(solution, 1));
            }

            dataset.addSeries(series);
        }

        JFreeChart chart = ChartFactory.createScatterPlot(getTitle() + " @ " + slider.getValue() + " NFE",
                (String) xAxisSelection.getSelectedItem(), (String) yAxisSelection.getSelectedItem(), dataset,
                PlotOrientation.VERTICAL, true, true, false);

        //set the renderer to only display shapes
        XYPlot plot = chart.getXYPlot();
        XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer(false, true);

        for (int i = 0; i < dataset.getSeriesCount(); i++) {
            Paint paint = paintHelper.get(dataset.getSeriesKey(i));
            renderer.setSeriesPaint(i, paint);
        }

        plot.setRenderer(renderer);

        //set the zoom based on the user's preferences
        if ((initialRangeBounds == null) || (initialDomainBounds == null)) {
            initialRangeBounds = plot.getRangeAxis().getRange();
            initialDomainBounds = plot.getDomainAxis().getRange();
        }

        if (useInitialBounds.isSelected()) {
            plot.getRangeAxis().setRange(initialRangeBounds);
            plot.getDomainAxis().setRange(initialDomainBounds);
        } else if (useZoomBounds.isSelected()) {
            if ((zoomRangeBounds == null) || (zoomDomainBounds == null)) {
                zoomRangeBounds = initialRangeBounds;
                zoomDomainBounds = initialDomainBounds;
            }

            plot.getRangeAxis().setRange(zoomRangeBounds);
            plot.getDomainAxis().setRange(zoomDomainBounds);
        } else if (useReferenceSetBounds.isSelected()) {
            if (referenceRangeBounds.getLength() > 0.0) {
                plot.getRangeAxis().setRange(referenceRangeBounds);
            }

            if (referenceDomainBounds.getLength() > 0.0) {
                plot.getDomainAxis().setRange(referenceDomainBounds);
            }
        }

        //register with the chart to receive zoom events
        chart.addChangeListener(this);

        chartContainer.removeAll();
        chartContainer.add(new ChartPanel(chart), BorderLayout.CENTER);
        chartContainer.revalidate();
        chartContainer.repaint();
    }

    @Override
    public void stateChanged(ChangeEvent e) {
        update();
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if ((e.getSource() == xAxisSelection) || (e.getSource() == yAxisSelection)) {
            initialRangeBounds = null;
            initialDomainBounds = null;
            initializeReferenceSetBounds();
        }

        update();
    }

    @Override
    public void chartChanged(ChartChangeEvent e) {
        zoomRangeBounds = e.getChart().getXYPlot().getRangeAxis().getRange();
        zoomDomainBounds = e.getChart().getXYPlot().getDomainAxis().getRange();
        useZoomBounds.setSelected(true);
    }

    @Override
    public void valueChanged(ListSelectionEvent e) {
        if (e.getValueIsAdjusting()) {
            return;
        }

        update();
    }

}