SiLeBAT/FSK-Lab

View on GitHub
de.bund.bfr.knime.pmm.common/src/de/bund/bfr/knime/pmm/common/chart/ChartSelectionPanel.java

Summary

Maintainability
F
2 wks
Test Coverage
/*******************************************************************************
 * Copyright (c) 2015 Federal Institute for Risk Assessment (BfR), Germany
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Contributors:
 *     Department Biological Safety - BfR
 *******************************************************************************/
package de.bund.bfr.knime.pmm.common.chart;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.AbstractCellEditor;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JColorChooser;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.RowFilter;
import javax.swing.ScrollPaneConstants;
import javax.swing.SortOrder;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableRowSorter;

import de.bund.bfr.knime.pmm.common.TimeSeriesXml;
import de.bund.bfr.knime.pmm.common.ui.FormattedDoubleTextField;
import de.bund.bfr.knime.pmm.common.ui.SpacePanel;
import de.bund.bfr.knime.pmm.common.ui.StringTextField;
import de.bund.bfr.knime.pmm.common.ui.TimeSeriesDialog;
import de.bund.bfr.knime.pmm.common.ui.UI;

public class ChartSelectionPanel extends JPanel implements ActionListener,
        CellEditorListener, ListSelectionListener {

    private static final String ID = "ID";
    private static final String SELECTED = "Selected";
    public static final String COLOR = "Color";
    public static final String SHAPE = "Shape";
    public static final String DATA = "Data Points";
    public static final String PARAMETERS = "Parameters";
    public static final String VARIABLES = "Variables";
    public static final String FORMULA = "Math Formula";

    private static final long serialVersionUID = 1L;

    private static final int MIN_COLUMN_WIDTH = 15;
    private static final int MAX_COLUMN_WIDTH = 2147483647;
    private static final int PREFERRED_COLUMN_WIDTH = 75;

    private List<SelectionListener> listeners;

    private ColorAndShapeCreator colorAndShapes;

    private List<String> invalidIds;

    private List<String> ids;
    private boolean selectionExlusive;
    private List<List<TimeSeriesXml>> data;
    private List<String> formulas;
    private List<Map<String, Double>> parameters;
    private List<Map<String, String>> variables;
    private Map<String, List<String>> stringColumns;
    private Map<String, List<Double>> qualityColumns;
    private List<String> conditions;
    private List<List<Double>> conditionValues;
    private List<List<Double>> conditionMinValues;
    private List<List<Double>> conditionMaxValues;
    private List<List<String>> conditionUnits;

    private List<String> conditionStandardUnits;
    private List<String> visualizationColumns;
    private List<String> miscellaneousColumns;

    private JTable selectTable;
    private JComponent optionsPanel;

    private JScrollPane tableScrollPane;
    private JButton selectAllButton;
    private JButton unselectAllButton;
    private JButton invertSelectionButton;
    private JButton customizeColumnsButton;
    private JButton resizeColumnsButton;
    private Map<String, JComboBox<String>> comboBoxes;

    private boolean hasConditionRanges;

    public ChartSelectionPanel(List<String> ids, boolean selectionsExclusive,
            Map<String, List<String>> stringColumns,
            Map<String, List<Double>> qualityColumns, List<String> conditions,
            List<List<Double>> conditionValues,
            List<List<Double>> conditionMinValues,
            List<List<Double>> conditionMaxValues,
            List<List<String>> conditionUnits, List<String> visibleColumns,
            List<String> filterableColumns, List<List<TimeSeriesXml>> data,
            List<Map<String, Double>> parameters,
            List<Map<String, String>> variables, List<String> formulas) {
        
        this(ids, selectionsExclusive, stringColumns, qualityColumns,
                conditions, conditionValues, conditionMinValues,
                conditionMaxValues, conditionUnits, visibleColumns,
                filterableColumns, data, parameters, variables, formulas, null);
    }

    public ChartSelectionPanel(List<String> ids, boolean selectionsExclusive,
            Map<String, List<String>> stringColumns,
            Map<String, List<Double>> qualityColumns, List<String> conditions,
            List<List<Double>> conditionValues,
            List<List<Double>> conditionMinValues,
            List<List<Double>> conditionMaxValues,
            List<List<String>> conditionUnits, List<String> visibleColumns,
            List<String> filterableStringColumns,
            List<List<TimeSeriesXml>> data,
            List<Map<String, Double>> parameters,
            List<Map<String, String>> variables, List<String> formulas,
            List<Integer> colorCounts) {
        if (stringColumns == null) {
            stringColumns = new LinkedHashMap<>();
        }

        if (qualityColumns == null) {
            qualityColumns = new LinkedHashMap<>();
        }

        if (conditions == null) {
            conditions = new ArrayList<>();
        }

        this.ids = ids;
        this.selectionExlusive = selectionsExclusive;
        this.data = data;
        this.formulas = formulas;
        this.parameters = parameters;
        this.variables = variables;
        this.stringColumns = stringColumns;
        this.qualityColumns = qualityColumns;
        this.conditions = conditions;
        this.conditionValues = conditionValues;
        this.conditionMinValues = conditionMinValues;
        this.conditionMaxValues = conditionMaxValues;
        this.conditionUnits = conditionUnits;

        invalidIds = new ArrayList<>();
        conditionStandardUnits = new ArrayList<>();

        for (int i = 0; i < conditions.size(); i++) {
            String standardUnit = null;
            boolean multipleUnits = false;

            for (String unit : conditionUnits.get(i)) {
                if (standardUnit == null) {
                    standardUnit = unit;
                } else if (unit == null || unit.equals(standardUnit)) {
                    // Do nothing
                } else {
                    standardUnit = null;
                    multipleUnits = true;
                    break;
                }
            }

            if (!multipleUnits) {
                conditionStandardUnits.add(standardUnit);
            } else {
                conditionStandardUnits = null;
                break;
            }
        }

        visualizationColumns = Arrays.asList(COLOR, SHAPE);
        miscellaneousColumns = new ArrayList<>();

        if (data != null) {
            miscellaneousColumns.add(DATA);
        }

        if (formulas != null) {
            miscellaneousColumns.add(FORMULA);
        }

        if (parameters != null) {
            miscellaneousColumns.add(PARAMETERS);
        }

        if (variables != null) {
            miscellaneousColumns.add(VARIABLES);
        }

        miscellaneousColumns.addAll(stringColumns.keySet());

        if (conditionValues == null && conditionMinValues != null
                && conditionMaxValues != null) {
            hasConditionRanges = true;
        } else {
            hasConditionRanges = false;
        }

        listeners = new ArrayList<>();

        JPanel upperPanel = new JPanel();

        upperPanel.setLayout(new BoxLayout(upperPanel, BoxLayout.Y_AXIS));

        if (!filterableStringColumns.isEmpty()) {
            JPanel upperPanel1 = new JPanel();
            JPanel filterPanel = new JPanel();

            upperPanel1.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
            filterPanel.setBorder(BorderFactory.createTitledBorder("Filter"));
            filterPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
            comboBoxes = new LinkedHashMap<>();

            for (String column : filterableStringColumns) {
                List<String> values = new ArrayList<>();
                Set<String> valueSet = new LinkedHashSet<>(
                        stringColumns.get(column));

                valueSet.remove(null);
                values.add("");
                values.addAll(valueSet);
                Collections.sort(values);

                JComboBox<String> box = new JComboBox<>(
                        values.toArray(new String[0]));

                box.addActionListener(this);
                filterPanel.add(new JLabel(column + ":"));
                filterPanel.add(box);
                comboBoxes.put(column, box);
            }

            upperPanel1.add(filterPanel);
            upperPanel.add(new SpacePanel(upperPanel1));
        }

        JPanel upperPanel2 = new JPanel();

        upperPanel2.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));

        if (!selectionsExclusive) {
            JPanel selectPanel = new JPanel();

            selectAllButton = new JButton("All");
            selectAllButton.addActionListener(this);
            unselectAllButton = new JButton("None");
            unselectAllButton.addActionListener(this);
            invertSelectionButton = new JButton("Invert");
            invertSelectionButton.addActionListener(this);

            selectPanel.setBorder(BorderFactory.createTitledBorder("Select"));
            selectPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
            selectPanel.add(selectAllButton);
            selectPanel.add(unselectAllButton);
            selectPanel.add(invertSelectionButton);
            upperPanel2.add(selectPanel);
        }

        JPanel columnPanel = new JPanel();

        customizeColumnsButton = new JButton("Customize");
        customizeColumnsButton.addActionListener(this);
        resizeColumnsButton = new JButton("Set Optimal Width");
        resizeColumnsButton.addActionListener(this);
        columnPanel.setBorder(BorderFactory.createTitledBorder("Columns"));
        columnPanel.add(customizeColumnsButton);
        columnPanel.add(resizeColumnsButton);

        upperPanel2.add(columnPanel);
        upperPanel.add(new SpacePanel(upperPanel2));

        SelectTableModel model;

        if (colorCounts == null) {
            colorAndShapes = new ColorAndShapeCreator(ids.size());
            model = new SelectTableModel(colorAndShapes.getColorList(),
                    colorAndShapes.getShapeNameList(), false);
        } else {
            List<List<Color>> colorLists = new ArrayList<>();
            List<List<String>> shapeLists = new ArrayList<>();

            colorAndShapes = new ColorAndShapeCreator(
                    Collections.max(colorCounts));

            for (int n : colorCounts) {
                ArrayList<Color> colors = new ArrayList<>();
                ArrayList<String> shapes = new ArrayList<>();

                for (int i = 0; i < n; i++) {
                    colors.add(colorAndShapes.getColorList().get(i));
                    shapes.add(colorAndShapes.getShapeNameList().get(i));
                }

                colorLists.add(colors);
                shapeLists.add(shapes);
            }

            model = new SelectTableModel(colorLists, shapeLists, true);
        }

        selectTable = new JTable(model);
        selectTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        selectTable.getSelectionModel().addListSelectionListener(this);
        selectTable.setRowHeight((new JComboBox<String>()).getPreferredSize().height);
        selectTable.setRowSorter(new SelectTableRowSorter(model, null));
        selectTable.getColumn(ID).setMinWidth(0);
        selectTable.getColumn(ID).setMaxWidth(0);
        selectTable.getColumn(ID).setPreferredWidth(0);
        selectTable.getColumn(SELECTED).setCellEditor(new CheckBoxEditor());
        selectTable.getColumn(SELECTED).setCellRenderer(new CheckBoxRenderer());
        selectTable.getColumn(SELECTED).getCellEditor()
                .addCellEditorListener(this);

        if (colorCounts == null) {
            selectTable.getColumn(COLOR).setCellEditor(new ColorEditor());
            selectTable.getColumn(COLOR).setCellRenderer(new ColorRenderer());
            selectTable.getColumn(SHAPE).setCellEditor(
                    new DefaultCellEditor(new JComboBox<>(
                            ColorAndShapeCreator.SHAPE_NAMES)));
        } else {
            selectTable.getColumn(COLOR).setCellEditor(new ColorListEditor());
            selectTable.getColumn(COLOR).setCellRenderer(
                    new ColorListRenderer());
            selectTable.getColumn(SHAPE).setCellEditor(new ShapeListEditor());
            selectTable.getColumn(SHAPE).setCellRenderer(
                    new ShapeListRenderer());
        }

        selectTable.getColumn(COLOR).getCellEditor()
                .addCellEditorListener(this);
        selectTable.getColumn(SHAPE).getCellEditor()
                .addCellEditorListener(this);
        selectTable.getColumn(DATA).setCellEditor(new TimeSeriesEditor());
        selectTable.getColumn(DATA).setCellRenderer(new ViewRenderer());
        selectTable.getColumn(FORMULA).setCellEditor(new FormulaEditor());
        selectTable.getColumn(FORMULA).setCellRenderer(new ViewRenderer());
        selectTable.getColumn(PARAMETERS).setCellEditor(new ParameterEditor());
        selectTable.getColumn(PARAMETERS).setCellRenderer(new ViewRenderer());
        selectTable.getColumn(VARIABLES).setCellEditor(new VariableEditor());
        selectTable.getColumn(VARIABLES).setCellRenderer(new ViewRenderer());
        selectTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        applyColumnSelection(visibleColumns);

        optionsPanel = new SpacePanel(upperPanel);
        tableScrollPane = new JScrollPane(selectTable,
                ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);

        setLayout(new BorderLayout());
        add(optionsPanel, BorderLayout.NORTH);
        add(tableScrollPane, BorderLayout.CENTER);
    }

    public void selectFirstRow() {
        selectTable.getSelectionModel().setSelectionInterval(0, 0);
    }

    public String getFocusedID() {
        int row = selectTable.getSelectedRow();

        if (row != -1) {
            return (String) selectTable.getValueAt(row, 0);
        }

        return null;
    }

    public void setInvalidIds(List<String> invalidIds) {
        this.invalidIds = invalidIds;
    }

    public List<String> getSelectedIDs() {
        List<String> selectedIDs = new ArrayList<>();

        for (int i = 0; i < selectTable.getRowCount(); i++) {
            if ((Boolean) selectTable.getValueAt(i, 1)) {
                selectedIDs.add((String) selectTable.getValueAt(i, 0));
            }
        }

        return selectedIDs;
    }

    public void setSelectedIDs(List<String> selectedIDs) {
        Set<String> idSet = new LinkedHashSet<>(selectedIDs);

        for (int i = 0; i < selectTable.getRowCount(); i++) {
            if (idSet.contains(selectTable.getValueAt(i, 0))) {
                selectTable.setValueAt(true, i, 1);
            } else {
                selectTable.setValueAt(false, i, 1);
            }
        }

        fireSelectionChanged();
    }

    public String getFilter(String column) {
        if (comboBoxes.containsKey(column)) {
            return (String) comboBoxes.get(column).getSelectedItem();
        } else {
            return null;
        }
    }

    public void setFilter(String column, String filter) {
        if (comboBoxes.containsKey(column)) {
            if (filter != null) {
                comboBoxes.get(column).setSelectedItem(filter);
            } else {
                comboBoxes.get(column).setSelectedItem("");
            }

            applyFilters();
        }
    }

    public Map<String, Color> getColors() {
        Map<String, Color> paints = new LinkedHashMap<>(
                selectTable.getRowCount());

        for (int i = 0; i < selectTable.getRowCount(); i++) {
            paints.put((String) selectTable.getValueAt(i, 0),
                    (Color) selectTable.getValueAt(i, 2));
        }

        return paints;
    }

    public void setColors(Map<String, Color> colors) {
        for (int i = 0; i < selectTable.getRowCount(); i++) {
            Color color = colors.get(selectTable.getValueAt(i, 0));

            if (color != null) {
                selectTable.setValueAt(color, i, 2);
            }
        }
    }

    public Map<String, Shape> getShapes() {
        Map<String, Shape> shapes = new LinkedHashMap<>(
                selectTable.getRowCount());
        Map<String, Shape> shapeMap = colorAndShapes.getShapeByNameMap();

        for (int i = 0; i < selectTable.getRowCount(); i++) {
            shapes.put((String) selectTable.getValueAt(i, 0),
                    shapeMap.get(selectTable.getValueAt(i, 3)));
        }

        return shapes;
    }

    public void setShapes(Map<String, Shape> shapes) {
        Map<Shape, String> shapeMap = colorAndShapes.getNameByShapeMap();

        for (int i = 0; i < selectTable.getRowCount(); i++) {
            Shape shape = shapes.get(selectTable.getValueAt(i, 0));

            if (shape != null) {
                selectTable.setValueAt(shapeMap.get(shape), i, 3);
            }
        }
    }

    @SuppressWarnings("unchecked")
    public Map<String, List<Color>> getColorLists() {
        Map<String, List<Color>> paints = new LinkedHashMap<>(
                selectTable.getRowCount());

        for (int i = 0; i < selectTable.getRowCount(); i++) {
            paints.put((String) selectTable.getValueAt(i, 0),
                    (List<Color>) selectTable.getValueAt(i, 2));
        }

        return paints;
    }

    public void setColorLists(Map<String, List<Color>> colorLists) {
        for (int i = 0; i < selectTable.getRowCount(); i++) {
            List<Color> colorList = colorLists
                    .get(selectTable.getValueAt(i, 0));

            if (colorList != null) {
                selectTable.setValueAt(colorList, i, 2);
            }
        }
    }

    @SuppressWarnings("unchecked")
    public Map<String, List<Shape>> getShapeLists() {
        Map<String, List<Shape>> shapes = new LinkedHashMap<>(
                selectTable.getRowCount());
        Map<String, Shape> shapeMap = colorAndShapes.getShapeByNameMap();

        for (int i = 0; i < selectTable.getRowCount(); i++) {
            List<Shape> list = new ArrayList<>();

            for (String name : (List<String>) selectTable.getValueAt(i, 3)) {
                list.add(shapeMap.get(name));
            }

            shapes.put((String) selectTable.getValueAt(i, 0), list);
        }

        return shapes;
    }

    public void setShapeLists(Map<String, List<Shape>> shapeLists) {
        Map<Shape, String> shapeMap = colorAndShapes.getNameByShapeMap();

        for (int i = 0; i < selectTable.getRowCount(); i++) {
            List<Shape> shapeList = shapeLists
                    .get(selectTable.getValueAt(i, 0));

            if (shapeList != null) {
                List<String> list = new ArrayList<>();

                for (Shape shape : shapeList) {
                    list.add(shapeMap.get(shape));
                }

                selectTable.setValueAt(list, i, 3);
            }
        }
    }

    public List<String> getVisibleColumns() {
        List<String> visibleColumns = new ArrayList<>();
        List<String> columns = new ArrayList<>();

        columns.addAll(Arrays.asList(COLOR, SHAPE, DATA, FORMULA, PARAMETERS,
                VARIABLES));
        columns.addAll(stringColumns.keySet());
        columns.addAll(qualityColumns.keySet());

        for (String column : columns) {
            if (selectTable.getColumn(column).getMaxWidth() != 0) {
                visibleColumns.add(column);
            }
        }

        for (int i = 0; i < conditions.size(); i++) {
            String column = conditions.get(i);

            if (conditionStandardUnits != null) {
                String unit = conditionStandardUnits.get(i);

                if (!hasConditionRanges
                        && selectTable.getColumn(column + " (" + unit + ")")
                                .getMaxWidth() != 0) {
                    visibleColumns.add(column);
                } else if (hasConditionRanges
                        && selectTable.getColumn(
                                "Min " + column + " (" + unit + ")")
                                .getMaxWidth() != 0) {
                    visibleColumns.add(column);
                }
            } else {
                if (!hasConditionRanges
                        && selectTable.getColumn(column).getMaxWidth() != 0) {
                    visibleColumns.add(column);
                } else if (hasConditionRanges
                        && selectTable.getColumn("Min " + column).getMaxWidth() != 0) {
                    visibleColumns.add(column);
                }
            }
        }

        return visibleColumns;
    }

    public Map<String, Integer> getColumnWidths() {
        Map<String, Integer> widths = new LinkedHashMap<>();
        Enumeration<TableColumn> columns = selectTable.getColumnModel()
                .getColumns();

        while (columns.hasMoreElements()) {
            TableColumn column = columns.nextElement();

            if (column.getWidth() != 0) {
                widths.put((String) column.getHeaderValue(), column.getWidth());
            }
        }

        return widths;
    }

    public void setColumnWidths(Map<String, Integer> widths) {
        Enumeration<TableColumn> columns = selectTable.getColumnModel()
                .getColumns();

        while (columns.hasMoreElements()) {
            TableColumn column = columns.nextElement();

            if (widths.containsKey(column.getHeaderValue())) {
                column.setPreferredWidth(widths.get(column.getHeaderValue()));
            }
        }
    }

    public void addSelectionListener(SelectionListener listener) {
        listeners.add(listener);
    }

    public void removeSelectionListener(SelectionListener listener) {
        listeners.remove(listener);
    }

    public void fireSelectionChanged() {
        for (SelectionListener listener : listeners) {
            listener.selectionChanged();
        }
    }

    public void fireInfoSelectionChanged() {
        for (SelectionListener listener : listeners) {
            listener.focusChanged();
        }
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == selectAllButton) {
            for (int i = 0; i < selectTable.getRowCount(); i++) {
                selectTable.setValueAt(true, i, 1);
            }
        } else if (e.getSource() == unselectAllButton) {
            for (int i = 0; i < selectTable.getRowCount(); i++) {
                selectTable.setValueAt(false, i, 1);
            }
        } else if (e.getSource() == invertSelectionButton) {
            for (int i = 0; i < selectTable.getRowCount(); i++) {
                selectTable.setValueAt(!(Boolean) selectTable.getValueAt(i, 1),
                        i, 1);
            }
        } else if (e.getSource() == customizeColumnsButton) {
            ColumnSelectionDialog dialog = new ColumnSelectionDialog(
                    getVisibleColumns());

            dialog.setVisible(true);

            if (dialog.isApproved()) {
                applyColumnSelection(dialog.getSelection());
            }
        } else if (e.getSource() == resizeColumnsButton) {
            packColumns();
        } else {
            applyFilters();
        }

        fireSelectionChanged();
    }

    @Override
    public void editingStopped(ChangeEvent e) {
        fireSelectionChanged();
    }

    @Override
    public void editingCanceled(ChangeEvent e) {
    }

    @Override
    public void valueChanged(ListSelectionEvent e) {
        fireInfoSelectionChanged();
    }

    private void applyFilters() {
        Map<String, String> filters = new LinkedHashMap<>();

        for (String column : comboBoxes.keySet()) {
            JComboBox<String> box = comboBoxes.get(column);

            if (!box.getSelectedItem().equals("")) {
                filters.put(column, (String) box.getSelectedItem());
            }
        }

        selectTable.setRowSorter(new SelectTableRowSorter(
                (SelectTableModel) selectTable.getModel(), filters));
    }

    private void applyColumnSelection(List<String> visibleColumns) {
        List<String> columns = new ArrayList<>();

        columns.addAll(Arrays.asList(COLOR, SHAPE, DATA, FORMULA, PARAMETERS,
                VARIABLES));
        columns.addAll(stringColumns.keySet());
        columns.addAll(qualityColumns.keySet());

        for (String column : columns) {
            setColumnVisible(column, visibleColumns.contains(column));
        }

        if (!hasConditionRanges) {
            for (int i = 0; i < conditions.size(); i++) {
                String column = conditions.get(i);

                if (conditionStandardUnits != null) {
                    String unit = conditionStandardUnits.get(i);

                    setColumnVisible(column + " (" + unit + ")",
                            visibleColumns.contains(column));
                } else {
                    setColumnVisible(column, visibleColumns.contains(column));
                    setColumnVisible(column + " Unit",
                            visibleColumns.contains(column));
                }
            }
        } else {
            for (int i = 0; i < conditions.size(); i++) {
                String column = conditions.get(i);

                if (conditionStandardUnits != null) {
                    String unit = conditionStandardUnits.get(i);

                    setColumnVisible("Min " + column + " (" + unit + ")",
                            visibleColumns.contains(column));
                    setColumnVisible("Max " + column + " (" + unit + ")",
                            visibleColumns.contains(column));
                } else {
                    setColumnVisible("Min " + column,
                            visibleColumns.contains(column));
                    setColumnVisible("Max " + column,
                            visibleColumns.contains(column));
                    setColumnVisible(column + " Unit",
                            visibleColumns.contains(column));
                }
            }
        }
    }

    private void setColumnVisible(String column, boolean value) {
        if (value) {
            selectTable.getColumn(column).setMinWidth(MIN_COLUMN_WIDTH);
            selectTable.getColumn(column).setMaxWidth(MAX_COLUMN_WIDTH);

            if (selectTable.getColumn(column).getPreferredWidth() == 0) {
                selectTable.getColumn(column).setPreferredWidth(
                        PREFERRED_COLUMN_WIDTH);
            }
        } else {
            selectTable.getColumn(column).setMinWidth(0);
            selectTable.getColumn(column).setMaxWidth(0);
            selectTable.getColumn(column).setPreferredWidth(0);
        }
    }

    private void packColumns() {

        for (int c = 0; c < selectTable.getColumnCount(); c++) {
            TableColumn col = selectTable.getColumnModel().getColumn(c);

            if (col.getPreferredWidth() == 0) {
                continue;
            }

            TableCellRenderer renderer = col.getHeaderRenderer();
            Component comp = selectTable
                    .getTableHeader()
                    .getDefaultRenderer()
                    .getTableCellRendererComponent(selectTable,
                            col.getHeaderValue(), false, false, 0, 0);
            int width = comp.getPreferredSize().width;

            for (int r = 0; r < selectTable.getRowCount(); r++) {
                renderer = selectTable.getCellRenderer(r, c);
                comp = renderer.getTableCellRendererComponent(selectTable,
                        selectTable.getValueAt(r, c), false, false, r, c);
                width = Math.max(width, comp.getPreferredSize().width);
            }

            col.setPreferredWidth(width += 5);
        }

        revalidate();
    }

    public static interface SelectionListener {

        public void selectionChanged();

        public void focusChanged();
    }

    private abstract class AbstractSelectTableModel extends AbstractTableModel {

        private static final long serialVersionUID = 1L;

        private boolean listBased;

        private List<Boolean> selections;
        private List<Color> colors;
        private List<List<Color>> colorLists;
        private List<String> shapes;
        private List<List<String>> shapeLists;

        @SuppressWarnings("unchecked")
        public AbstractSelectTableModel(List<?> colors, List<?> shapes,
                boolean listBased) {
            this.listBased = listBased;

            if (!listBased) {
                this.colors = (List<Color>) colors;
                this.shapes = (List<String>) shapes;
            } else {
                this.colorLists = (List<List<Color>>) colors;
                this.shapeLists = (List<List<String>>) shapes;
            }

            selections = new ArrayList<>(Collections.nCopies(ids.size(), false));
        }

        @Override
        public int getColumnCount() {
            int conditionCount = conditions.size();

            if (!hasConditionRanges) {
                conditionCount *= 2;
            } else {
                conditionCount *= 3;
            }

            if (conditionStandardUnits != null) {
                conditionCount -= conditions.size();
            }

            return 8 + stringColumns.size() + qualityColumns.size()
                    + conditionCount;
        }

        @Override
        public String getColumnName(int column) {
            switch (column) {
            case 0:
                return ID;
            case 1:
                return SELECTED;
            case 2:
                return COLOR;
            case 3:
                return SHAPE;
            case 4:
                return DATA;
            case 5:
                return FORMULA;
            case 6:
                return PARAMETERS;
            case 7:
                return VARIABLES;
            default:
                int i1 = column - 8;
                int i2 = i1 - stringColumns.size();
                int i3 = i2 - qualityColumns.size();

                if (i1 < stringColumns.size()) {
                    return new ArrayList<>(stringColumns.keySet()).get(i1);
                } else if (i2 < qualityColumns.size()) {
                    return new ArrayList<>(qualityColumns.keySet()).get(i2);
                } else if (!hasConditionRanges) {
                    if (conditionStandardUnits != null) {
                        return conditions.get(i3) + " ("
                                + conditionStandardUnits.get(i3) + ")";
                    } else {
                        if (i3 % 2 == 0) {
                            return conditions.get(i3 / 2);
                        } else {
                            return conditions.get(i3 / 2) + " Unit";
                        }
                    }
                } else {
                    if (conditionStandardUnits != null) {
                        if (i3 % 2 == 0) {
                            return "Min " + conditions.get(i3 / 2) + " ("
                                    + conditionStandardUnits.get(i3 / 2) + ")";
                        } else {
                            return "Max " + conditions.get(i3 / 2) + " ("
                                    + conditionStandardUnits.get(i3 / 2) + ")";
                        }
                    } else {
                        if (i3 % 3 == 0) {
                            return "Min " + conditions.get(i3 / 3);
                        } else if (i3 % 3 == 1) {
                            return "Max " + conditions.get(i3 / 3);
                        } else {
                            return conditions.get(i3 / 3) + " Unit";
                        }
                    }
                }
            }
        }

        @Override
        public int getRowCount() {
            return ids.size();
        }

        @Override
        public Object getValueAt(int row, int column) {
            switch (column) {
            case 0:
                return ids.get(row);
            case 1:
                return selections.get(row);
            case 2:
                if (!listBased) {
                    return colors.get(row);
                } else {
                    return colorLists.get(row);
                }
            case 3:
                if (!listBased) {
                    return shapes.get(row);
                } else {
                    return shapeLists.get(row);
                }
            case 4:
                return data != null ? data.get(row) : null;
            case 5:
                return formulas != null ? formulas.get(row) : null;
            case 6:
                return parameters != null ? parameters.get(row) : null;
            case 7:
                return variables != null ? variables.get(row) : null;
            default:
                int i1 = column - 8;
                int i2 = i1 - stringColumns.size();
                int i3 = i2 - qualityColumns.size();

                if (i1 < stringColumns.size()) {
                    return new ArrayList<>(stringColumns.values()).get(i1).get(
                            row);
                } else if (i2 < qualityColumns.size()) {
                    return new ArrayList<>(qualityColumns.values()).get(i2)
                            .get(row);
                } else if (!hasConditionRanges) {
                    if (conditionStandardUnits != null) {
                        return conditionValues.get(i3).get(row);
                    } else {
                        if (i3 % 2 == 0) {
                            return conditionValues.get(i3 / 2).get(row);
                        } else {
                            return conditionUnits.get(i3 / 2).get(row);
                        }
                    }
                } else {
                    if (conditionStandardUnits != null) {
                        if (i3 % 2 == 0) {
                            return conditionMinValues.get(i3 / 2).get(row);
                        } else {
                            return conditionMaxValues.get(i3 / 2).get(row);
                        }
                    } else {
                        if (i3 % 3 == 0) {
                            return conditionMinValues.get(i3 / 3).get(row);
                        } else if (i3 % 3 == 1) {
                            return conditionMaxValues.get(i3 / 3).get(row);
                        } else {
                            return conditionUnits.get(i3 / 3).get(row);
                        }
                    }
                }
            }
        }

        @Override
        public Class<?> getColumnClass(int column) {
            switch (column) {
            case 0:
                return String.class;
            case 1:
                return Boolean.class;
            case 2:
                if (!listBased) {
                    return Color.class;
                } else {
                    return List.class;
                }
            case 3:
                if (!listBased) {
                    return String.class;
                } else {
                    return List.class;
                }
            case 4:
                return List.class;
            case 5:
                return String.class;
            case 6:
                return Map.class;
            case 7:
                return Map.class;
            default:
                int i1 = column - 8;
                int i2 = i1 - stringColumns.size();
                int i3 = i2 - qualityColumns.size();

                if (i1 < stringColumns.size()) {
                    return String.class;
                } else if (i2 < qualityColumns.size()) {
                    return Double.class;
                } else if (!hasConditionRanges) {
                    if (conditionStandardUnits != null) {
                        return Double.class;
                    } else {
                        if (i3 % 2 == 0) {
                            return Double.class;
                        } else {
                            return String.class;
                        }
                    }
                } else {
                    if (conditionStandardUnits != null) {
                        if (i3 % 2 == 0) {
                            return Double.class;
                        } else {
                            return Double.class;
                        }
                    } else {
                        if (i3 % 3 == 0) {
                            return Double.class;
                        } else if (i3 % 3 == 1) {
                            return Double.class;
                        } else {
                            return String.class;
                        }
                    }
                }
            }
        }

        @SuppressWarnings("unchecked")
        @Override
        public void setValueAt(Object value, int row, int column) {
            switch (column) {
            case 1:
                selections.set(row, (Boolean) value);
                break;
            case 2:
                if (!listBased) {
                    colors.set(row, (Color) value);
                } else {
                    colorLists.set(row, (List<Color>) value);
                }
                break;
            case 3:
                if (!listBased) {
                    shapes.set(row, (String) value);
                } else {
                    shapeLists.set(row, (List<String>) value);
                }
                break;
            default:
                // Do nothing
            }
        }

        @Override
        public boolean isCellEditable(int row, int column) {
            return column == 1 || column == 2 || column == 3 || column == 4
                    || column == 5 || column == 6 || column == 7;
        }
    }

    private class SelectTableModel extends AbstractSelectTableModel {

        private static final long serialVersionUID = 1L;

        public SelectTableModel(List<?> colors, List<?> shapes,
                boolean listBased) {
            super(colors, shapes, listBased);
        }

        @Override
        public void setValueAt(Object value, int row, int column) {
            super.setValueAt(value, row, column);

            if (selectionExlusive && column == 1 && value.equals(Boolean.TRUE)) {
                for (int i = 0; i < getRowCount(); i++) {
                    if (i != row) {
                        super.setValueAt(false, i, 1);
                        fireTableCellUpdated(i, 1);
                    }
                }
            }

            fireTableCellUpdated(row, column);
        }
    }

    private class ViewRenderer implements TableCellRenderer {

        @Override
        public Component getTableCellRendererComponent(JTable table,
                Object value, boolean isSelected, boolean hasFocus, int row,
                int column) {
            return new JButton("View");
        }
    }

    private class ColorRenderer extends JLabel implements TableCellRenderer {

        private static final long serialVersionUID = 1L;

        public ColorRenderer() {
            setOpaque(true);
        }

        @Override
        public Component getTableCellRendererComponent(JTable table,
                Object color, boolean isSelected, boolean hasFocus, int row,
                int column) {
            setBackground((Color) color);

            return this;
        }
    }

    private class ColorEditor extends AbstractCellEditor implements
            TableCellEditor {

        private static final long serialVersionUID = 1L;

        private JButton colorButton;

        public ColorEditor() {
            colorButton = new JButton();
            colorButton.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent arg0) {
                    Color newColor = JColorChooser.showDialog(colorButton,
                            "Choose Color", colorButton.getBackground());

                    if (newColor != null) {
                        colorButton.setBackground(newColor);
                        stopCellEditing();
                    }
                }
            });
        }

        @Override
        public Component getTableCellEditorComponent(JTable table,
                Object value, boolean isSelected, int row, int column) {
            colorButton.setBackground((Color) value);

            return colorButton;
        }

        @Override
        public Object getCellEditorValue() {
            return colorButton.getBackground();
        }

    }

    private class ColorListRenderer extends JComponent implements
            TableCellRenderer {

        private static final long serialVersionUID = 1L;

        private List<Color> colorList;

        public ColorListRenderer() {
            colorList = new ArrayList<>();
        }

        @SuppressWarnings("unchecked")
        @Override
        public Component getTableCellRendererComponent(JTable table,
                Object color, boolean isSelected, boolean hasFocus, int row,
                int column) {
            colorList = (List<Color>) color;

            return this;
        }

        @Override
        protected void paintComponent(Graphics g) {
            if (colorList.isEmpty()) {
                super.paintComponent(g);
            } else {
                double w = (double) getWidth() / (double) colorList.size();

                for (int i = 0; i < colorList.size(); i++) {
                    g.setColor(colorList.get(i));
                    g.fillRect((int) (i * w), 0, (int) Math.max(w, 1),
                            getHeight());
                }
            }
        }
    }

    private class ColorListEditor extends AbstractCellEditor implements
            TableCellEditor {

        private static final long serialVersionUID = 1L;

        private JButton button;
        private List<Color> colorList;

        public ColorListEditor() {
            button = new JButton();
            colorList = new ArrayList<>();
            button.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent arg0) {
                    ColorListDialog dialog = new ColorListDialog(colorList);

                    dialog.setVisible(true);

                    if (dialog.isApproved()) {
                        colorList = dialog.getColorList();
                        stopCellEditing();
                    }
                }
            });
        }

        @SuppressWarnings("unchecked")
        @Override
        public Component getTableCellEditorComponent(JTable table,
                Object value, boolean isSelected, int row, int column) {
            colorList = (List<Color>) value;

            return button;
        }

        @Override
        public Object getCellEditorValue() {
            return colorList;
        }
    }

    private class ShapeListRenderer extends JLabel implements TableCellRenderer {

        private static final long serialVersionUID = 1L;

        public ShapeListRenderer() {
        }

        @Override
        public Component getTableCellRendererComponent(JTable table,
                Object color, boolean isSelected, boolean hasFocus, int row,
                int column) {
            return this;
        }
    }

    private class ShapeListEditor extends AbstractCellEditor implements
            TableCellEditor {

        private static final long serialVersionUID = 1L;

        private JButton button;
        private List<String> shapeList;

        public ShapeListEditor() {
            button = new JButton();
            shapeList = new ArrayList<>();
            button.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent arg0) {
                    ShapeListDialog dialog = new ShapeListDialog(shapeList);

                    dialog.setVisible(true);

                    if (dialog.isApproved()) {
                        shapeList = dialog.getShapeList();
                        stopCellEditing();
                    }
                }
            });
        }

        @SuppressWarnings("unchecked")
        @Override
        public Component getTableCellEditorComponent(JTable table,
                Object value, boolean isSelected, int row, int column) {
            shapeList = (List<String>) value;

            return button;
        }

        @Override
        public Object getCellEditorValue() {
            return shapeList;
        }
    }

    private class TimeSeriesEditor extends AbstractCellEditor implements
            TableCellEditor, ActionListener {

        private static final long serialVersionUID = 1L;

        private JButton button;
        private List<TimeSeriesXml> timeSeries;

        public TimeSeriesEditor() {
            button = new JButton("View");
            button.addActionListener(this);
            timeSeries = new ArrayList<>();
        }

        @SuppressWarnings("unchecked")
        @Override
        public Component getTableCellEditorComponent(JTable table,
                Object value, boolean isSelected, int row, int column) {
            timeSeries = (List<TimeSeriesXml>) value;

            return button;
        }

        @Override
        public Object getCellEditorValue() {
            return timeSeries;
        }

        @Override
        public void actionPerformed(ActionEvent arg0) {
            TimeSeriesDialog dialog = new TimeSeriesDialog(button, timeSeries,
                    false);

            dialog.setVisible(true);
        }
    }

    private class FormulaEditor extends AbstractCellEditor implements
            TableCellEditor, ActionListener {

        private static final long serialVersionUID = 1L;

        private JButton button;
        private String formula;

        public FormulaEditor() {
            button = new JButton("View");
            button.addActionListener(this);
            formula = "";
        }

        @Override
        public Component getTableCellEditorComponent(JTable table,
                Object value, boolean isSelected, int row, int column) {
            formula = (String) value;

            return button;
        }

        @Override
        public Object getCellEditorValue() {
            return formula;
        }

        @Override
        public void actionPerformed(ActionEvent arg0) {
            FormulaDialog dialog = new FormulaDialog(formula);

            dialog.setVisible(true);
        }
    }

    private class ParameterEditor extends AbstractCellEditor implements
            TableCellEditor, ActionListener {

        private static final long serialVersionUID = 1L;

        private JButton button;
        private Map<String, Double> parameters;

        public ParameterEditor() {
            button = new JButton("View");
            button.addActionListener(this);
            parameters = new LinkedHashMap<>();
        }

        @SuppressWarnings("unchecked")
        @Override
        public Component getTableCellEditorComponent(JTable table,
                Object value, boolean isSelected, int row, int column) {
            parameters = (Map<String, Double>) value;

            return button;
        }

        @Override
        public Object getCellEditorValue() {
            return parameters;
        }

        @Override
        public void actionPerformed(ActionEvent arg0) {
            ParameterDialog dialog = new ParameterDialog(parameters);

            dialog.setVisible(true);
        }
    }

    private class VariableEditor extends AbstractCellEditor implements
            TableCellEditor, ActionListener {

        private static final long serialVersionUID = 1L;

        private JButton button;
        private Map<String, String> variables;

        public VariableEditor() {
            button = new JButton("View");
            button.addActionListener(this);
            variables = new LinkedHashMap<>();
        }

        @SuppressWarnings("unchecked")
        @Override
        public Component getTableCellEditorComponent(JTable table,
                Object value, boolean isSelected, int row, int column) {
            variables = (Map<String, String>) value;

            return button;
        }

        @Override
        public Object getCellEditorValue() {
            return variables;
        }

        @Override
        public void actionPerformed(ActionEvent arg0) {
            VariableDialog dialog = new VariableDialog(variables);

            dialog.setVisible(true);
        }
    }

    private class CheckBoxRenderer extends JCheckBox implements
            TableCellRenderer {

        private static final long serialVersionUID = -8337460338388283099L;

        public CheckBoxRenderer() {
            super();
            setHorizontalAlignment(SwingConstants.CENTER);
            setBorderPainted(true);
        }

        @Override
        public Component getTableCellRendererComponent(JTable table,
                Object value, boolean isSelected, boolean hasFocus, int row,
                int column) {
            int statusColumn = -1;

            for (int i = 0; i < table.getColumnCount(); i++) {
                if (table.getColumnName(i).equals(ChartConstants.STATUS)) {
                    statusColumn = i;
                    break;
                }
            }

            if (invalidIds.contains(table.getValueAt(row, 0))) {
                setEnabled(false);
            } else {
                setEnabled(true);
            }

            if (isSelected) {
                setForeground(table.getSelectionForeground());
                setBackground(table.getSelectionBackground());
            } else if (statusColumn != -1) {
                String statusValue = (String) table.getValueAt(row,
                        statusColumn);

                if (statusValue.equals(ChartConstants.OK)) {
                    setForeground(table.getForeground());
                    setBackground(table.getBackground());
                } else if (statusValue.equals(ChartConstants.FAILED)) {
                    setForeground(Color.RED);
                    setBackground(Color.RED);
                } else if (statusValue.equals(ChartConstants.OUT_OF_LIMITS)) {
                    setForeground(Color.YELLOW);
                    setBackground(Color.YELLOW);
                } else if (statusValue.equals(ChartConstants.NO_COVARIANCE)) {
                    setForeground(Color.YELLOW);
                    setBackground(Color.YELLOW);
                } else if (statusValue.equals(ChartConstants.NOT_SIGNIFICANT)) {
                    setForeground(Color.YELLOW);
                    setBackground(Color.YELLOW);
                }
            } else {
                setForeground(table.getForeground());
                setBackground(table.getBackground());
            }

            setSelected((value != null && ((Boolean) value).booleanValue()));

            if (hasFocus) {
                setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
            } else {
                setBorder(new EmptyBorder(1, 1, 1, 1));
            }

            return this;
        }
    }

    private class CheckBoxEditor extends DefaultCellEditor {

        private static final long serialVersionUID = 1L;

        public CheckBoxEditor() {
            super(new JCheckBox());
            ((JCheckBox) getComponent())
                    .setHorizontalAlignment(SwingConstants.CENTER);
        }
    }

    private class SelectTableRowSorter extends TableRowSorter<SelectTableModel> {

        private Map<Integer, String> filters;

        public SelectTableRowSorter(SelectTableModel model,
                Map<String, String> filters) {
            super(model);
            this.filters = new LinkedHashMap<>();

            if (filters != null) {
                for (String column : filters.keySet()) {
                    for (int i = 0; i < model.getColumnCount(); i++) {
                        if (column.equals(model.getColumnName(i))) {
                            this.filters.put(i, filters.get(column));
                        }
                    }
                }

                addFilters();
            }
        }

        @Override
        public void toggleSortOrder(int column) {
            List<? extends SortKey> sortKeys = getSortKeys();

            if (sortKeys.size() > 0) {
                if (sortKeys.get(0).getColumn() == column
                        && sortKeys.get(0).getSortOrder() == SortOrder.DESCENDING) {
                    setSortKeys(null);
                    return;
                }
            }

            super.toggleSortOrder(column);
        }

        private void addFilters() {
            setRowFilter(new RowFilter<SelectTableModel, Object>() {

                @Override
                public boolean include(
                        javax.swing.RowFilter.Entry<? extends SelectTableModel, ? extends Object> entry) {
                    for (int column : filters.keySet()) {
                        if (!entry.getStringValue(column).equals(
                                filters.get(column))) {
                            return false;
                        }
                    }

                    return true;
                }
            });
        }
    }

    private class ColorListDialog extends JDialog implements ActionListener {

        private static final long serialVersionUID = 1L;

        private boolean approved;
        private List<Color> colorList;

        private List<JButton> colorButtons;

        private JButton okButton;
        private JButton cancelButton;

        public ColorListDialog(List<Color> initialColors) {
            super(SwingUtilities.getWindowAncestor(ChartSelectionPanel.this),
                    "Color Palette", DEFAULT_MODALITY_TYPE);

            approved = false;
            colorList = null;

            colorButtons = new ArrayList<>();
            okButton = new JButton("OK");
            okButton.addActionListener(this);
            cancelButton = new JButton("Cancel");
            cancelButton.addActionListener(this);

            JPanel centerPanel = new JPanel();
            JPanel bottomPanel = new JPanel();

            centerPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
            centerPanel
                    .setLayout(new GridLayout(initialColors.size(), 1, 5, 5));

            for (Color color : initialColors) {
                JButton button = new JButton();

                button.setBackground(color);
                button.setPreferredSize(new Dimension(
                        button.getPreferredSize().width, 20));
                button.addActionListener(this);
                colorButtons.add(button);
                centerPanel.add(button);
            }

            bottomPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
            bottomPanel.add(okButton);
            bottomPanel.add(cancelButton);

            JPanel scrollPanel = new JPanel();

            scrollPanel.setLayout(new BorderLayout());
            scrollPanel.add(centerPanel, BorderLayout.NORTH);

            setLayout(new BorderLayout());
            add(new JScrollPane(scrollPanel), BorderLayout.CENTER);
            add(bottomPanel, BorderLayout.SOUTH);
            pack();

            setLocationRelativeTo(ChartSelectionPanel.this);
            UI.adjustDialog(this);
        }

        public boolean isApproved() {
            return approved;
        }

        public List<Color> getColorList() {
            return colorList;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() == okButton) {
                approved = true;
                colorList = new ArrayList<>();

                for (JButton button : colorButtons) {
                    colorList.add(button.getBackground());
                }

                dispose();
            } else if (e.getSource() == cancelButton) {
                dispose();
            } else {
                JButton button = (JButton) e.getSource();
                Color newColor = JColorChooser.showDialog(button,
                        "Choose Color", button.getBackground());

                if (newColor != null) {
                    button.setBackground(newColor);
                }
            }
        }
    }

    private class ShapeListDialog extends JDialog implements ActionListener {

        private static final long serialVersionUID = 1L;

        private boolean approved;
        private List<String> shapeList;

        private List<JComboBox<String>> shapeBoxes;

        private JButton okButton;
        private JButton cancelButton;

        public ShapeListDialog(List<String> initialShapes) {
            super(SwingUtilities.getWindowAncestor(ChartSelectionPanel.this),
                    "Shape Palette", DEFAULT_MODALITY_TYPE);

            approved = false;
            shapeList = null;

            shapeBoxes = new ArrayList<>();
            okButton = new JButton("OK");
            okButton.addActionListener(this);
            cancelButton = new JButton("Cancel");
            cancelButton.addActionListener(this);

            JPanel centerPanel = new JPanel();
            JPanel bottomPanel = new JPanel();

            centerPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
            centerPanel
                    .setLayout(new GridLayout(initialShapes.size(), 1, 5, 5));

            for (String shape : initialShapes) {
                JComboBox<String> box = new JComboBox<>(
                        ColorAndShapeCreator.SHAPE_NAMES);

                box.setSelectedItem(shape);
                shapeBoxes.add(box);
                centerPanel.add(box);
            }

            bottomPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
            bottomPanel.add(okButton);
            bottomPanel.add(cancelButton);

            JPanel scrollPanel = new JPanel();

            scrollPanel.setLayout(new BorderLayout());
            scrollPanel.add(centerPanel, BorderLayout.NORTH);

            setLayout(new BorderLayout());
            add(new JScrollPane(scrollPanel), BorderLayout.CENTER);
            add(bottomPanel, BorderLayout.SOUTH);
            pack();

            setLocationRelativeTo(ChartSelectionPanel.this);
            UI.adjustDialog(this);
        }

        public boolean isApproved() {
            return approved;
        }

        public List<String> getShapeList() {
            return shapeList;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() == okButton) {
                approved = true;
                shapeList = new ArrayList<>();

                for (JComboBox<String> box : shapeBoxes) {
                    shapeList.add((String) box.getSelectedItem());
                }

                dispose();
            } else if (e.getSource() == cancelButton) {
                dispose();
            }
        }
    }

    private class FormulaDialog extends JDialog implements ActionListener {

        private static final long serialVersionUID = 1L;

        private JButton okButton;

        public FormulaDialog(String formula) {
            super(SwingUtilities.getWindowAncestor(ChartSelectionPanel.this),
                    "Formula", DEFAULT_MODALITY_TYPE);

            okButton = new JButton("OK");
            okButton.addActionListener(this);

            StringTextField field = new StringTextField(true);

            field.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
            field.setValue(formula);
            field.setEditable(false);
            field.setPreferredSize(new Dimension(
                    field.getPreferredSize().width + 10, field
                            .getPreferredSize().height));

            JPanel mainPanel = new JPanel();

            mainPanel.setLayout(new BorderLayout());
            mainPanel.add(field, BorderLayout.NORTH);

            JPanel bottomPanel = new JPanel();

            bottomPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
            bottomPanel.add(okButton);

            setLayout(new BorderLayout());
            add(new JScrollPane(mainPanel), BorderLayout.CENTER);
            add(bottomPanel, BorderLayout.SOUTH);
            pack();

            setLocationRelativeTo(ChartSelectionPanel.this);
            UI.adjustDialog(this);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            dispose();
        }
    }

    private class ParameterDialog extends JDialog implements ActionListener {

        private static final long serialVersionUID = 1L;

        private JButton okButton;

        public ParameterDialog(Map<String, Double> parameters) {
            super(SwingUtilities.getWindowAncestor(ChartSelectionPanel.this),
                    "Parameters", DEFAULT_MODALITY_TYPE);

            okButton = new JButton("OK");
            okButton.addActionListener(this);

            JPanel leftPanel = new JPanel();
            JPanel rightPanel = new JPanel();

            leftPanel.setLayout(new GridLayout(parameters.size(), 1, 5, 5));
            rightPanel.setLayout(new GridLayout(parameters.size(), 1, 5, 5));

            for (String param : parameters.keySet()) {
                FormattedDoubleTextField field = new FormattedDoubleTextField(
                        true);
                Double value = parameters.get(param);

                field.setValue(value);
                field.setEditable(false);

                leftPanel.add(new JLabel(param + ":"));
                rightPanel.add(field);
            }

            JPanel panel = new JPanel();

            panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
            panel.setLayout(new BorderLayout(5, 5));
            panel.add(leftPanel, BorderLayout.WEST);
            panel.add(rightPanel, BorderLayout.CENTER);

            JPanel mainPanel = new JPanel();

            mainPanel.setLayout(new BorderLayout());
            mainPanel.add(panel, BorderLayout.NORTH);

            JPanel bottomPanel = new JPanel();

            bottomPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
            bottomPanel.add(okButton);

            setLayout(new BorderLayout());
            add(new JScrollPane(mainPanel), BorderLayout.CENTER);
            add(bottomPanel, BorderLayout.SOUTH);
            pack();

            setLocationRelativeTo(ChartSelectionPanel.this);
            UI.adjustDialog(this);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            dispose();
        }
    }

    private class VariableDialog extends JDialog implements ActionListener {

        private static final long serialVersionUID = 1L;

        private JButton okButton;

        public VariableDialog(Map<String, String> variables) {
            super(SwingUtilities.getWindowAncestor(ChartSelectionPanel.this),
                    "Variables", DEFAULT_MODALITY_TYPE);

            okButton = new JButton("OK");
            okButton.addActionListener(this);

            JPanel leftPanel = new JPanel();
            JPanel rightPanel = new JPanel();

            leftPanel.setLayout(new GridLayout(variables.size(), 1, 5, 5));
            rightPanel.setLayout(new GridLayout(variables.size(), 1, 5, 5));

            for (String param : variables.keySet()) {
                StringTextField field = new StringTextField(true);
                String value = variables.get(param);

                field.setValue(" " + value);
                field.setHorizontalAlignment(SwingConstants.RIGHT);
                field.setEditable(false);

                leftPanel.add(new JLabel(param + ":"));
                rightPanel.add(field);
            }

            JPanel panel = new JPanel();

            panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
            panel.setLayout(new BorderLayout(5, 5));
            panel.add(leftPanel, BorderLayout.WEST);
            panel.add(rightPanel, BorderLayout.CENTER);

            JPanel mainPanel = new JPanel();

            mainPanel.setLayout(new BorderLayout());
            mainPanel.add(panel, BorderLayout.NORTH);

            JPanel bottomPanel = new JPanel();

            bottomPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
            bottomPanel.add(okButton);

            setLayout(new BorderLayout());
            add(new JScrollPane(mainPanel), BorderLayout.CENTER);
            add(bottomPanel, BorderLayout.SOUTH);
            pack();

            setLocationRelativeTo(ChartSelectionPanel.this);
            UI.adjustDialog(this);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            dispose();
        }
    }

    private class ColumnSelectionDialog extends JDialog implements
            ActionListener {

        private static final long serialVersionUID = 1L;

        private boolean approved;
        private List<String> selection;
        private Map<String, JCheckBox> selectionBoxes;
        private JCheckBox visualizationBox;
        private JCheckBox miscellaneousBox;
        private JCheckBox qualityBox;
        private JCheckBox conditionsBox;

        private JButton okButton;
        private JButton cancelButton;

        public ColumnSelectionDialog(List<String> initialSelection) {
            super(SwingUtilities.getWindowAncestor(ChartSelectionPanel.this),
                    "Column Selection", DEFAULT_MODALITY_TYPE);
            approved = false;
            selection = null;
            selectionBoxes = new LinkedHashMap<>();

            JPanel visualizationPanel = new JPanel();

            visualizationPanel.setLayout(new GridLayout(visualizationColumns
                    .size() + 1, 1, 5, 5));
            visualizationBox = new JCheckBox("All");
            visualizationBox.addActionListener(this);
            visualizationPanel.add(visualizationBox);

            for (String column : visualizationColumns) {
                JCheckBox box = new JCheckBox(column);

                if (initialSelection.contains(column)) {
                    box.setSelected(true);
                } else {
                    box.setSelected(false);
                }

                box.addActionListener(this);
                selectionBoxes.put(column, box);
                visualizationPanel.add(box);
            }

            JPanel miscellaneousPanel = new JPanel();

            miscellaneousPanel.setLayout(new GridLayout(miscellaneousColumns
                    .size() + 1, 1, 5, 5));
            miscellaneousBox = new JCheckBox("All");
            miscellaneousBox.addActionListener(this);
            miscellaneousPanel.add(miscellaneousBox);

            for (String column : miscellaneousColumns) {
                JCheckBox box = new JCheckBox(column);

                if (initialSelection.contains(column)) {
                    box.setSelected(true);
                } else {
                    box.setSelected(false);
                }

                box.addActionListener(this);
                selectionBoxes.put(column, box);
                miscellaneousPanel.add(box);
            }

            JPanel qualityPanel = new JPanel();

            qualityPanel.setLayout(new GridLayout(qualityColumns.size() + 1, 1,
                    5, 5));
            qualityBox = new JCheckBox("All");
            qualityBox.addActionListener(this);
            qualityPanel.add(qualityBox);

            for (String column : qualityColumns.keySet()) {
                JCheckBox box = new JCheckBox(column);

                if (initialSelection.contains(column)) {
                    box.setSelected(true);
                } else {
                    box.setSelected(false);
                }

                box.addActionListener(this);
                selectionBoxes.put(column, box);
                qualityPanel.add(box);
            }

            JPanel conditionsPanel = new JPanel();

            conditionsPanel.setLayout(new GridLayout(conditions.size() + 1, 1,
                    5, 5));
            conditionsBox = new JCheckBox("All");
            conditionsBox.addActionListener(this);
            conditionsPanel.add(conditionsBox);

            for (String column : conditions) {
                JCheckBox box = new JCheckBox(column);

                if (initialSelection.contains(column)) {
                    box.setSelected(true);
                } else {
                    box.setSelected(false);
                }

                box.addActionListener(this);
                selectionBoxes.put(column, box);
                conditionsPanel.add(box);
            }

            updateCheckBoxes();

            JPanel centerPanel = new JPanel();

            centerPanel.setLayout(new BoxLayout(centerPanel, BoxLayout.X_AXIS));

            centerPanel.add(createNorthPanel(visualizationPanel,
                    "Visualization"));

            if (!miscellaneousColumns.isEmpty()) {
                centerPanel.add(createNorthPanel(miscellaneousPanel,
                        "Miscellaneous"));
            }

            if (!qualityColumns.isEmpty()) {
                centerPanel.add(createNorthPanel(qualityPanel,
                        "Quality Criteria"));
            }

            if (!conditions.isEmpty()) {
                centerPanel
                        .add(createNorthPanel(conditionsPanel, "Conditions"));
            }

            okButton = new JButton("OK");
            okButton.addActionListener(this);
            cancelButton = new JButton("Cancel");
            cancelButton.addActionListener(this);

            JPanel bottomPanel = new JPanel();

            bottomPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
            bottomPanel.add(okButton);
            bottomPanel.add(cancelButton);

            setLayout(new BorderLayout());
            add(centerPanel, BorderLayout.CENTER);
            add(bottomPanel, BorderLayout.SOUTH);
            pack();

            setLocationRelativeTo(ChartSelectionPanel.this);
            UI.adjustDialog(this);
        }

        public boolean isApproved() {
            return approved;
        }

        public List<String> getSelection() {
            return selection;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() == okButton) {
                approved = true;
                selection = new ArrayList<>();

                for (String column : selectionBoxes.keySet()) {
                    if (selectionBoxes.get(column).isSelected()) {
                        selection.add(column);
                    }
                }

                dispose();
            } else if (e.getSource() == cancelButton) {
                dispose();
            } else if (e.getSource() == visualizationBox) {
                if (visualizationBox.isSelected()) {
                    setColumnsTo(visualizationColumns, true);
                } else {
                    setColumnsTo(visualizationColumns, false);
                }
            } else if (e.getSource() == miscellaneousBox) {
                if (miscellaneousBox.isSelected()) {
                    setColumnsTo(miscellaneousColumns, true);
                } else {
                    setColumnsTo(miscellaneousColumns, false);
                }
            } else if (e.getSource() == qualityBox) {
                if (qualityBox.isSelected()) {
                    setColumnsTo(qualityColumns.keySet(), true);
                } else {
                    setColumnsTo(qualityColumns.keySet(), false);
                }
            } else if (e.getSource() == conditionsBox) {
                if (conditionsBox.isSelected()) {
                    setColumnsTo(conditions, true);
                } else {
                    setColumnsTo(conditions, false);
                }
            } else {
                updateCheckBoxes();
            }
        }

        private JComponent createNorthPanel(JComponent comp, String name) {
            JPanel panel = new JPanel();

            panel.setBorder(BorderFactory.createTitledBorder(name));
            panel.setLayout(new BorderLayout());
            panel.add(comp, BorderLayout.NORTH);
            panel.setPreferredSize(new Dimension(Math.max(
                    panel.getPreferredSize().width, 100), panel
                    .getPreferredSize().height));

            return new JScrollPane(panel,
                    ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                    ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        }

        private void updateCheckBoxes() {
            boolean allVisualizationSelected = true;
            boolean allMiscellaneousSelected = true;
            boolean allQualitySelected = true;
            boolean allConditionsSelected = true;

            for (String column : visualizationColumns) {
                if (!selectionBoxes.get(column).isSelected()) {
                    allVisualizationSelected = false;
                    break;
                }
            }

            for (String column : miscellaneousColumns) {
                if (!selectionBoxes.get(column).isSelected()) {
                    allMiscellaneousSelected = false;
                    break;
                }
            }

            for (String column : qualityColumns.keySet()) {
                if (!selectionBoxes.get(column).isSelected()) {
                    allQualitySelected = false;
                    break;
                }
            }

            for (String column : conditions) {
                if (!selectionBoxes.get(column).isSelected()) {
                    allConditionsSelected = false;
                    break;
                }
            }

            if (allVisualizationSelected) {
                visualizationBox.setSelected(true);
            } else {
                visualizationBox.setSelected(false);
            }

            if (allMiscellaneousSelected) {
                miscellaneousBox.setSelected(true);
            } else {
                miscellaneousBox.setSelected(false);
            }

            if (allQualitySelected) {
                qualityBox.setSelected(true);
            } else {
                qualityBox.setSelected(false);
            }

            if (allConditionsSelected) {
                conditionsBox.setSelected(true);
            } else {
                conditionsBox.setSelected(false);
            }
        }

        private void setColumnsTo(Collection<String> columns, boolean value) {
            for (String column : columns) {
                selectionBoxes.get(column).setSelected(value);
            }
        }
    }

}