jflowmap-master/src/jflowmap/ui/ControlPanel.java
/*
 * This file is part of JFlowMap.
 *
 * Copyright 2009 Ilya Boyandin
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package jflowmap.ui;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.BoundedRangeModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JSlider;
import javax.swing.JSpinner;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.SpinnerModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.TableColumnModel;

import jflowmap.FlowMapAttrSpec;
import jflowmap.FlowMapColorSchemes;
import jflowmap.FlowMapGraph;
import jflowmap.bundling.ForceDirectedBundlerParameters;
import jflowmap.clustering.NodeDistanceMeasure;
import jflowmap.data.FlowMapStats;
import jflowmap.data.SeqStat;
import jflowmap.util.Pair;
import jflowmap.views.flowmap.FlowMapView;
import jflowmap.views.flowmap.VisualFlowMap;
import jflowmap.views.flowmap.VisualFlowMapModel;
import jflowmap.views.flowmap.VisualNode;
import jflowmap.views.flowmap.VisualNodeCluster;
import at.fhj.utils.graphics.AxisMarks;
import at.fhj.utils.swing.FancyTable;
import at.fhj.utils.swing.TableSorter;
import at.fhj.utils.swing.TableSorter.Directive;
import ch.unifr.dmlib.cluster.Linkage;
import ch.unifr.dmlib.cluster.Linkages;

import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;

public class ControlPanel {

    private JTabbedPane tabbedPane1;
    private JPanel panel1;
    private JCheckBox useLogColorScaleCheckbox;
    private JSlider maxEdgeWidthSlider;
    private JSpinner maxEdgeWidthSpinner;
    private JSpinner minWeightFilterSpinner;
    private JSlider minLengthFilterSlider;
    private JSpinner minLengthFilterSpinner;
    private JSlider maxWeightFilterSlider;
    private JSpinner maxWeightFilterSpinner;
    private JCheckBox autoAdjustColorScaleCheckBox;
    private JSlider maxLengthFilterSlider;
    private JSpinner maxLengthFilterSpinner;
    private JSlider minWeightFilterSlider;
    private JCheckBox useLogWidthScaleCheckbox;
    private JComboBox datasetCombo;
    private JComboBox comboBox2;
    private JComboBox comboBox3;
    private JComboBox comboBox4;
    private JComboBox colorSchemeCombo;
    private JComboBox comboBox6;
    private JSlider edgeOpacitySlider;
    private JSpinner edgeOpacitySpinner;
    private JCheckBox mapEdgeValueToCheckBox;
    private JCheckBox mapEdgeValueToCheckBox1;
    private JSlider edgeMarkerOpacitySlider;
    private JSpinner edgeMarkerOpacitySpinner;
    private JSpinner edgeStiffnessSpinner;
    private JSpinner stepSizeSpinner;
    private JSpinner edgeCompatibilityThresholdSpinner;
    private JSpinner stepDampingFactorSpinner;
    private JSpinner stepsInCycleSpinner;
    private JButton bundleButton;
    private JButton resetBundlingButton;
    private JSpinner numberOfCyclesSpinner;
    private JButton defaultValuesButton;
    private JCheckBox directionAffectsCompatibilityCheckBox;
    private JCheckBox binaryCompatibilityCheckBox;
    private JCheckBox inverseQuadraticModelCheckBox;
    private JCheckBox repulsiveEdgesCheckBox;
    private JCheckBox simpleCompatibilityMeasureCheckBox;
    private JCheckBox showNodesCheckBox;
    private JSpinner repulsionSpinner;
    private JCheckBox edgeValueAffectsAttractionCheckBox;
    private JTable clusterDistancesTable;
    private JButton clusterButton;
    private JSlider maxClusterDistanceSlider;
    private JCheckBox fillEdgesWithGradientCheckBox;
    private JCheckBox showDirectionMarkersCheckBox;
    private JSlider edgeMarkerSizeSlider;
    private JSpinner edgeMarkerSizeSpinner;
    private JCheckBox proportionalDirectionMarkersCheckBox;
    private JLabel edgeMarkerSizeLabel;
    private JLabel edgeMarkerOpacityLabel;
    private JSpinner maxClusterDistanceSpinner;
    private JComboBox distanceMeasureCombo;
    private JTabbedPane tabbedPane2;
    private JTable clusterNodesTable;
    private JTable flowsTable;
    private JComboBox linkageComboBox;
    private JButton joinClusterEdgesButton;
    private JButton resetClustersButton;
    private JLabel maxClusterDistanceLabel;
    private JSlider euclideanMaxClusterDistanceSlider;
    private JSpinner euclideanMaxClusterDistanceSpinner;
    private JCheckBox combineWithEuclideanClustersCheckBox;
    private JLabel euclideanMaxClusterDistanceLabel;
    private JLabel numberOfClustersValueLabel;
    private JLabel numberOfClustersLabel;
    private JButton resetJoinedEdgesButton;
    private JTable clustersTable;
    private JButton aggregateEdgesButton;
    private final FlowMapView jFlowMap;
    private boolean initializing;
    private ForceDirectedBundlerParameters fdBundlingParams;
    private NodeSimilarityDistancesTableModel clusterDistancesTableModel;
    private TableSorter clusterDistancesTableSorter;
    private boolean modelsInitialized;
    private ClusterNodesTableModel clusterNodesTableModel;
    private TableSorter clusterNodesTableSorter;
    private FlowsTableModel flowsTableModel;
    private TableSorter flowsTableSorter;
    private ClustersTableModel clustersTableModel;
    private VisualFlowMap visualFlowMap;
    private final FlowMapAttrSpec attrSpec;


    public ControlPanel(FlowMapView flowMap, FlowMapAttrSpec attrSpec) {
        this.jFlowMap = flowMap;
        this.attrSpec = attrSpec;
        $$$setupUI$$$();

        loadVisualFlowMap(flowMap.getVisualFlowMap());
        initUIChangeListeners();

        updateDirectionAffectsCompatibilityCheckBox();
        updateDirectionAffectsCompatibilityCheckBox();
        updateRepulsionSpinner();
        updateMarkersInputs();
    }

    public void loadVisualFlowMap(VisualFlowMap newVisualFlowMap) {
        // attach listeners
        if (visualFlowMap != null) {
            removeVisualFlowMapListeners(visualFlowMap);
        }
        visualFlowMap = newVisualFlowMap;
        attachVisualFlowMapListeners(newVisualFlowMap);

        // load data
        FlowMapGraph fmg = visualFlowMap.getFlowMapGraph();
        fdBundlingParams = new ForceDirectedBundlerParameters(
            fmg, visualFlowMap.getFlowWeightAttr());
        if (!modelsInitialized) {
            initModelsOnce();
            modelsInitialized = true;
        }
        initModels();
        setData(visualFlowMap.getModel());
    }

    private void updateRepulsionSpinner() {
        repulsionSpinner.setEnabled(repulsiveEdgesCheckBox.isSelected());
    }

    private void updateSimpleCompatibilityMeasureCheckBox() {
        if (repulsiveEdgesCheckBox.isSelected()) {
            simpleCompatibilityMeasureCheckBox.setSelected(false);
            simpleCompatibilityMeasureCheckBox.setEnabled(false);
        } else {
            simpleCompatibilityMeasureCheckBox.setEnabled(true);
        }
    }

    private void updateDirectionAffectsCompatibilityCheckBox() {
        if (simpleCompatibilityMeasureCheckBox.isSelected() || repulsiveEdgesCheckBox.isSelected()) {
            directionAffectsCompatibilityCheckBox.setSelected(false);
            directionAffectsCompatibilityCheckBox.setEnabled(false);
        } else {
            directionAffectsCompatibilityCheckBox.setEnabled(true);
        }
    }

    private void updateMarkersInputs() {
        boolean showMarkers = showDirectionMarkersCheckBox.isSelected();
        proportionalDirectionMarkersCheckBox.setEnabled(showMarkers);
        edgeMarkerSizeSlider.setEnabled(showMarkers);
        edgeMarkerSizeSpinner.setEnabled(showMarkers);
        edgeMarkerSizeLabel.setEnabled(showMarkers);
        edgeMarkerOpacitySlider.setEnabled(showMarkers);
        edgeMarkerOpacitySpinner.setEnabled(showMarkers);
        edgeMarkerOpacityLabel.setEnabled(showMarkers);
    }

    private void initModelsOnce() {
        if (attrSpec != null) {
            datasetCombo.setModel(new DefaultComboBoxModel(attrSpec.getFlowWeightAttrs().toArray()));
        } else {
            // TODO: move dataset combo out of the control panel
        }
    }

    public void initEdgeBundlingModels() {
        numberOfCyclesSpinner.setModel(new SpinnerNumberModel(fdBundlingParams.getNumCycles(), 1, 100, 1));
        stepsInCycleSpinner.setModel(new SpinnerNumberModel(fdBundlingParams.getI(), 1, 1000, 1));
        edgeCompatibilityThresholdSpinner.setModel(new SpinnerNumberModel(fdBundlingParams.getEdgeCompatibilityThreshold(), 0.0, 1.0, 0.1));

        double s = fdBundlingParams.getS();
        double sExp = AxisMarks.ordAlpha(s);
        double sStep = sExp;
//    double sStep = sExp / 100;
        double sMax = sExp * 100;
        stepSizeSpinner.setModel(new SpinnerNumberModel(s, 0.0, sMax, sStep));

        edgeStiffnessSpinner.setModel(new SpinnerNumberModel(fdBundlingParams.getK(), 0.0, 1000.0, 1.0));

        stepDampingFactorSpinner.setModel(new SpinnerNumberModel(fdBundlingParams.getStepDampingFactor(), 0.0, 1.0, 0.1));
        directionAffectsCompatibilityCheckBox.setSelected(fdBundlingParams.getDirectionAffectsCompatibility());
        binaryCompatibilityCheckBox.setSelected(fdBundlingParams.getBinaryCompatibility());
        inverseQuadraticModelCheckBox.setSelected(fdBundlingParams.getUseInverseQuadraticModel());
        repulsiveEdgesCheckBox.setSelected(fdBundlingParams.getUseRepulsionForOppositeEdges());
        simpleCompatibilityMeasureCheckBox.setSelected(fdBundlingParams.getUseSimpleCompatibilityMeasure());
        edgeValueAffectsAttractionCheckBox.setSelected(fdBundlingParams.getEdgeValueAffectsAttraction());
        repulsionSpinner.setModel(new SpinnerNumberModel(fdBundlingParams.getRepulsionAmount(), 0.0, 1.0, 0.1));
    }

    private void initModels() {
        initializing = true;
        initFilterModels();
        initAestheticsModels();
        initEdgeBundlingModels();
        initNodeClusteringModels();
        initializing = false;
    }

    private void initAestheticsModels() {
        Pair<SpinnerModel, BoundedRangeModel> edgeOpacityModels =
                new BoundSpinnerSliderModels<Integer>(
                        0, 0, 255, 1, BoundSpinnerSliderModels.MAP_ID_INTEGER
                ).build();
        edgeOpacitySpinner.setModel(edgeOpacityModels.first());
        edgeOpacitySlider.setModel(edgeOpacityModels.second());

        Pair<SpinnerModel, BoundedRangeModel> edgeMarkerSizeModels =
                new BoundSpinnerSliderModels<Double>(
                        .0, .0, 0.5, 0.01,
                        BoundSpinnerSliderModels.createLinearMapping(100.0)
                ).build();
        edgeMarkerSizeSpinner.setModel(edgeMarkerSizeModels.first());
        edgeMarkerSizeSlider.setModel(edgeMarkerSizeModels.second());

        Pair<SpinnerModel, BoundedRangeModel> edgeMarkerOpacityModels =
                new BoundSpinnerSliderModels<Integer>(
                        0, 0, 255, 1, BoundSpinnerSliderModels.MAP_ID_INTEGER
                ).build();
        edgeMarkerOpacitySpinner.setModel(edgeMarkerOpacityModels.first());
        edgeMarkerOpacitySlider.setModel(edgeMarkerOpacityModels.second());

        Pair<SpinnerModel, BoundedRangeModel> maxEdgeWidthModels =
                new BoundSpinnerSliderModels<Double>(
                        .0, .0, 100.0, 1.0, BoundSpinnerSliderModels.MAP_ID_DOUBLE
                ).build();
        maxEdgeWidthSpinner.setModel(maxEdgeWidthModels.first());
        maxEdgeWidthSlider.setModel(maxEdgeWidthModels.second());

        colorSchemeCombo.setModel(new DefaultComboBoxModel(FlowMapColorSchemes.values()));
        updateColorScheme();
    }

    public void updateColorScheme() {
      colorSchemeCombo.setSelectedItem(FlowMapColorSchemes.findByScheme(jFlowMap.getColorScheme()));
    }

    private void initFilterModels() {
        FlowMapStats stats = getGraphStats();

        SeqStat weightStats = stats.getEdgeWeightStats();

        final double minWeight = weightStats.getMin();
        final double maxWeight = weightStats.getMax();

        double weightFilterLogMappingShift = (minWeight >= 1.0 ? 0 : 1.0 - minWeight);
        Pair<SpinnerModel, BoundedRangeModel> minWeightFilterModels =
                new BoundSpinnerSliderModels<Double>(
                        minWeight, minWeight, maxWeight, 1.0,
                        BoundSpinnerSliderModels.createLogMapping(2, weightFilterLogMappingShift)
                ).build();
        minWeightFilterSpinner.setModel(minWeightFilterModels.first());
        minWeightFilterSlider.setModel(minWeightFilterModels.second());

        Pair<SpinnerModel, BoundedRangeModel> maxWeightFilterModels =
                new BoundSpinnerSliderModels<Double>(
                        maxWeight, minWeight, maxWeight, 1.0,
                        BoundSpinnerSliderModels.createLogMapping(2, 1.0 - minWeight)
                ).build();
        maxWeightFilterSpinner.setModel(maxWeightFilterModels.first());
        maxWeightFilterSlider.setModel(maxWeightFilterModels.second());

        SeqStat lengthStats = stats.getEdgeLengthStats();

        Pair<SpinnerModel, BoundedRangeModel> minLengthFilterModels =
                new BoundSpinnerSliderModels<Double>(
                        lengthStats.getMin(), lengthStats.getMin(), lengthStats.getMax(), 1.0,
                        BoundSpinnerSliderModels.MAP_ID_DOUBLE
                ).build();
        minLengthFilterSpinner.setModel(minLengthFilterModels.first());
        minLengthFilterSlider.setModel(minLengthFilterModels.second());

        Pair<SpinnerModel, BoundedRangeModel> maxLengthFilterModels =
                new BoundSpinnerSliderModels<Double>(
                        lengthStats.getMax(), lengthStats.getMin(), lengthStats.getMax(), 1.0,
                        BoundSpinnerSliderModels.MAP_ID_DOUBLE
                ).build();
        maxLengthFilterSpinner.setModel(maxLengthFilterModels.first());
        maxLengthFilterSlider.setModel(maxLengthFilterModels.second());
    }

    public void setData(VisualFlowMapModel data) {
        autoAdjustColorScaleCheckBox.setSelected(data.getAutoAdjustColorScale());
        useLogWidthScaleCheckbox.setSelected(data.getUseLogWidthScale());
        useLogColorScaleCheckbox.setSelected(data.getUseLogColorScale());

        minLengthFilterSpinner.setValue(data.getEdgeLengthFilterMin());
        maxLengthFilterSpinner.setValue(data.getEdgeLengthFilterMax());

        minWeightFilterSpinner.setValue(data.getEdgeWeightFilterMin());
        maxWeightFilterSpinner.setValue(data.getEdgeWeightFilterMax());

        edgeOpacitySpinner.setValue(data.getEdgeAlpha());
//    edgeOpacitySlider.setValue(data.getEdgeAlpha());
        edgeMarkerOpacitySpinner.setValue(data.getDirectionMarkerAlpha());
        edgeMarkerSizeSpinner.setValue(data.getDirectionMarkerSize());

        showDirectionMarkersCheckBox.setSelected(data.getShowDirectionMarkers());
        showNodesCheckBox.setSelected(data.getShowNodes());
        fillEdgesWithGradientCheckBox.setSelected(data.getFillEdgesWithGradient());
        proportionalDirectionMarkersCheckBox.setSelected(data.getUseProportionalDirectionMarkers());

        maxEdgeWidthSpinner.setValue(data.getMaxEdgeWidth());
//    maxEdgeWidthSlider.setValue((int) Math.round(data.getMaxEdgeWidth()));
    }

    private void initUIChangeListeners() {
        initDatasetListeners();
        initFilterListeners();
        initScalesListeners();
        initAestheticsListeners();
        initEdgeBundlingListeners();
        initNodeClusterListeners();
    }

    private void initDatasetListeners() {
        datasetCombo.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (initializing) return;
                jFlowMap.setSelectedFlowWeightAttr((String) datasetCombo.getSelectedItem());
                List<Directive> sort = flowsTableSorter.getSortingColumns();
                flowsTableModel.setVisualFlowMap(jFlowMap.getVisualFlowMap());
                flowsTableSorter.setSortingColumns(sort); // prevent the column sortings being reset
//                jFlowMap.fitInView();
            }
        });
    }

    private void initFilterListeners() {
        // Edge value filter
        minWeightFilterSpinner.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                if (initializing) return;
//        minWeightFilterSlider.setValue(toValueEdgeFilterSliderValue((Double) minWeightFilterSpinner.getValue()));
                getFlowMapModel().setEdgeWeightFilterMin((Double) minWeightFilterSpinner.getValue());
            }
        });

        visualFlowMap.getModel().addPropertyChangeListener(
            VisualFlowMapModel.PROPERTY_VALUE_FILTER_MIN, new PropertyChangeListener() {
              @Override
              public void propertyChange(PropertyChangeEvent evt) {
                minWeightFilterSpinner.setValue(visualFlowMap.getModel().getEdgeWeightFilterMin());
              }
            });
//    minWeightFilterSlider.addChangeListener(new ChangeListener() {
//      public void stateChanged(ChangeEvent e) {
//        if (initializing) return;
//        double value = fromValueEdgeFilterSliderValue(minWeightFilterSlider.getValue());
//        getFlowMapModel().setEdgeWeightFilterMin(value);
//        minWeightFilterSpinner.setValue(value);
//      }
//    });
        maxWeightFilterSpinner.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                if (initializing) return;
                getFlowMapModel().setEdgeWeightFilterMax((Double) maxWeightFilterSpinner.getValue());
            }
        });
//    maxWeightFilterSlider.addChangeListener(new ChangeListener() {
//      public void stateChanged(ChangeEvent e) {
//        if (initializing) return;
//        double value = fromValueEdgeFilterSliderValue(maxWeightFilterSlider.getValue());
//        maxWeightFilterSpinner.setValue(value);
//      }
//    });


        // Edge length filter
        minLengthFilterSpinner.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                if (initializing) return;
                getFlowMapModel().setEdgeLengthFilterMin((Double) minLengthFilterSpinner.getValue());
            }
        });
        maxLengthFilterSpinner.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                if (initializing) return;
                getFlowMapModel().setEdgeLengthFilterMax((Double) maxLengthFilterSpinner.getValue());
            }
        });
    }

    private void initScalesListeners() {
        useLogColorScaleCheckbox.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                if (initializing) return;
                getFlowMapModel().setUseLogColorScale(useLogColorScaleCheckbox.isSelected());
            }
        });
        useLogWidthScaleCheckbox.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                if (initializing) return;
                getFlowMapModel().setUseLogWidthScale(useLogWidthScaleCheckbox.isSelected());
            }
        });
    }

    private void initAestheticsListeners() {
        // Aesthetics
        fillEdgesWithGradientCheckBox.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                if (initializing) return;
                getFlowMapModel().setFillEdgesWithGradient(
                        fillEdgesWithGradientCheckBox.isSelected());
            }
        });
        showDirectionMarkersCheckBox.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                if (initializing) return;
                getFlowMapModel().setShowDirectionMarkers(
                        showDirectionMarkersCheckBox.isSelected());
                updateMarkersInputs();
            }
        });
        showNodesCheckBox.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                if (initializing) return;
                getFlowMapModel().setShowNodes(showNodesCheckBox.isSelected());
            }
        });
        proportionalDirectionMarkersCheckBox.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                if (initializing) return;
                getFlowMapModel().setUseProportionalDirectionMarkers(
                        proportionalDirectionMarkersCheckBox.isSelected());
            }
        });

        // Direction marker size
        edgeMarkerSizeSpinner.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                if (initializing) return;
                getFlowMapModel().setDirectionMarkerSize((Double) edgeMarkerSizeSpinner.getValue());
            }
        });

        // Edge opacity
        edgeOpacitySpinner.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                if (initializing) return;
                getFlowMapModel().setEdgeAlpha((Integer) edgeOpacitySpinner.getValue());
            }
        });

        // Edge marker opacity
        edgeMarkerOpacitySpinner.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                if (initializing) return;
                getFlowMapModel().setDirectionMarkerAlpha((Integer) edgeMarkerOpacitySpinner.getValue());
            }
        });

        // Edge width
        maxEdgeWidthSpinner.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                if (initializing) return;
                getFlowMapModel().setMaxEdgeWidth((Double) maxEdgeWidthSpinner.getValue());
            }
        });

        colorSchemeCombo.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                jFlowMap.setColorScheme(((FlowMapColorSchemes) e.getItem()).getScheme());
            }
        });
    }

    private void initEdgeBundlingListeners() {
        // Edge Bundling
        bundleButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                fdBundlingParams.setNumCycles((Integer) numberOfCyclesSpinner.getValue());
                fdBundlingParams.setI((Integer) stepsInCycleSpinner.getValue());
                fdBundlingParams.setK((Double) edgeStiffnessSpinner.getValue());
                fdBundlingParams.setEdgeCompatibilityThreshold((Double) edgeCompatibilityThresholdSpinner.getValue());
                fdBundlingParams.setS((Double) stepSizeSpinner.getValue());
                fdBundlingParams.setStepDampingFactor((Double) stepDampingFactorSpinner.getValue());
                fdBundlingParams.setDirectionAffectsCompatibility(directionAffectsCompatibilityCheckBox.isSelected());
                fdBundlingParams.setBinaryCompatibility(binaryCompatibilityCheckBox.isSelected());
                fdBundlingParams.setUseInverseQuadraticModel(inverseQuadraticModelCheckBox.isSelected());
                fdBundlingParams.setUseRepulsionForOppositeEdges(repulsiveEdgesCheckBox.isSelected());
                fdBundlingParams.setUseSimpleCompatibilityMeasure(simpleCompatibilityMeasureCheckBox.isSelected());
                fdBundlingParams.setRepulsionAmount((Double) repulsionSpinner.getValue());
                fdBundlingParams.setEdgeValueAffectsAttraction(edgeValueAffectsAttractionCheckBox.isSelected());
                getVisualFlowMap().bundleEdges(fdBundlingParams);
            }
        });
        resetBundlingButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                getVisualFlowMap().resetBundling();
            }
        });
//    aggregateBundledEdgesButton.addActionListener(new ActionListener() {
//      public void actionPerformed(ActionEvent e) {
//        getVisualFlowMap().aggregateBundledEdges();
//      }
//    });
        defaultValuesButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                fdBundlingParams.resetToDefaults();
                initEdgeBundlingModels();
            }
        });
        simpleCompatibilityMeasureCheckBox.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                updateDirectionAffectsCompatibilityCheckBox();
            }
        });
        repulsiveEdgesCheckBox.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                updateSimpleCompatibilityMeasureCheckBox();
                updateDirectionAffectsCompatibilityCheckBox();
                updateRepulsionSpinner();
            }
        });
        aggregateEdgesButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                getVisualFlowMap().aggregateBundledEdges();
            }
        });
    }

    private void initNodeClusterListeners() {
        // Node clustering
        clusterButton.addActionListener(new ActionListener() {
            @SuppressWarnings("unchecked")
            public void actionPerformed(ActionEvent e) {
                getVisualFlowMap().clusterNodes(
                        (NodeDistanceMeasure) distanceMeasureCombo.getSelectedItem(),
                        (Linkage<VisualNode>) linkageComboBox.getSelectedItem(),
                        combineWithEuclideanClustersCheckBox.isSelected()
                );
                initNodeClusteringModels();
                updateNumberOfClustersLabel();
                updateNodeClustersTables();
            }
        });
        joinClusterEdgesButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                getVisualFlowMap().joinClusterEdges();
                updateClusterButtons();
                updateNumberOfClustersLabel();
            }
        });
        resetClustersButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                getVisualFlowMap().resetClusters();
                clearClusterTableModels();
                updateClusterDistanceSliderSpinner();
                updateClusterButtons();
            }
        });
        resetJoinedEdgesButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                getVisualFlowMap().resetJoinedNodes();
                updateClusterButtons();
                updateNumberOfClustersLabel();
                updateNumberOfClustersLabel();
                updateNodeClustersTables();
            }
        });
        maxClusterDistanceSpinner.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                if (initializing) return;
                double spinnerValue = ((Number) maxClusterDistanceSpinner.getValue()).doubleValue();
                getVisualFlowMap().setClusterDistanceThreshold(spinnerValue);
                updateNumberOfClustersLabel();
                updateNodeClustersTables();
            }
        });
        euclideanMaxClusterDistanceSpinner.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                if (initializing) return;
                double spinnerValue = ((Number) euclideanMaxClusterDistanceSpinner.getValue()).doubleValue();
                getVisualFlowMap().setEuclideanClusterDistanceThreshold(spinnerValue);
                updateNumberOfClustersLabel();
                updateNodeClustersTables();
            }
        });
    }

//  private void loadFlowMap(DatasetSpec dataset) {
//    VisualFlowMap visualFlowMap = jFlowMap.loadFlowMap(dataset);
//    jFlowMap.setVisualFlowMap(visualFlowMap);
//    jFlowMap.fitFlowMapInView();
//    loadVisualFlowMap(visualFlowMap);
//  }

    private final List<PropertyChangeListener> visualFlowMapListeners =
            new ArrayList<PropertyChangeListener>();

    private void attachVisualFlowMapListeners(VisualFlowMap visualFlowMap) {
        PropertyChangeListener selectionListener = new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
                VisualNode selectedNode = getVisualFlowMap().getSelectedNode();
                if (selectedNode == null) {
                    flowsTableModel.showAllVisualEdges();
                } else {
                    flowsTableModel.setVisualEdges(selectedNode.getEdges());
                }
                flowsTableSorter.setSortingStatus(2, TableSorter.DESCENDING);
            }
        };
        visualFlowMap.addPropertyChangeListener(
                VisualFlowMap.Attributes.NODE_SELECTION.name(),
                selectionListener
        );
        visualFlowMapListeners.add(selectionListener);
    }

    private void removeVisualFlowMapListeners(VisualFlowMap visualFlowMap) {
        for (PropertyChangeListener li : visualFlowMapListeners) {
            visualFlowMap.removePropertyChangeListener(li);
        }
        visualFlowMapListeners.clear();
    }

    public VisualFlowMapModel getFlowMapModel() {
        return getVisualFlowMap().getModel();
    }

    private VisualFlowMap getVisualFlowMap() {
        return jFlowMap.getVisualFlowMap();
    }

    private FlowMapStats getGraphStats() {
        return getVisualFlowMap().getStats();
    }

    public JPanel getPanel() {
        return panel1;
    }

    private void createUIComponents() {
        // custom component creation code here

        // flowsTable
        flowsTableModel = new FlowsTableModel();
        flowsTableSorter = new TableSorter(flowsTableModel);
        flowsTable = new FancyTable(flowsTableSorter);
        flowsTable.setAutoCreateColumnsFromModel(false);
        flowsTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
        flowsTableSorter.setColumnSortable(0, true);
        flowsTableSorter.setColumnSortable(1, true);
        flowsTableSorter.setColumnSortable(2, true);
        flowsTableSorter.setTableHeader(flowsTable.getTableHeader());


        // clusterNodesTable
        clusterNodesTableModel = new ClusterNodesTableModel();
        clusterNodesTableSorter = new TableSorter(clusterNodesTableModel);
        clusterNodesTableSorter.setColumnSortable(0, true);
        clusterNodesTableSorter.setColumnSortable(1, true);
        clusterNodesTableSorter.setSortingStatus(1, TableSorter.ASCENDING);


        clusterNodesTable = new FancyTable(clusterNodesTableSorter);
        clusterNodesTable.setDefaultRenderer(ClusterIcon.class,
                ((FancyTable) clusterNodesTable).new FancyIconRenderer());
        clusterNodesTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
//    clusterNodesTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        clusterNodesTable.setAutoCreateColumnsFromModel(false);
        clusterNodesTableSorter.setTableHeader(clusterNodesTable.getTableHeader());

        TableColumnModel tcm1 = clusterNodesTable.getColumnModel();
        tcm1.getColumn(0).setPreferredWidth(300);
//    tcm1.getColumn(0).setResizable(false);
//    tcm1.getColumn(0).setMaxWidth(350);
        tcm1.getColumn(1).setPreferredWidth(50);
        tcm1.getColumn(1).setMaxWidth(100);

        // clusterDistancesTable
        clusterDistancesTableModel = new NodeSimilarityDistancesTableModel();
        clusterDistancesTableSorter = new TableSorter(clusterDistancesTableModel);
        clusterDistancesTableSorter.setColumnSortable(0, true);
        clusterDistancesTableSorter.setColumnSortable(1, true);
        clusterDistancesTableSorter.setColumnSortable(2, true);

        clusterDistancesTable = new FancyTable(clusterDistancesTableSorter);
        clusterDistancesTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
        clusterDistancesTable.setAutoCreateColumnsFromModel(false);
        clusterDistancesTableSorter.setTableHeader(clusterDistancesTable.getTableHeader());

        TableColumnModel tcm2 = clusterDistancesTable.getColumnModel();
        tcm2.getColumn(0).setPreferredWidth(100);
        tcm2.getColumn(0).setMaxWidth(150);
        tcm2.getColumn(1).setPreferredWidth(100);
        tcm2.getColumn(1).setMaxWidth(150);
        tcm2.getColumn(1).setPreferredWidth(120);
        tcm2.getColumn(1).setMaxWidth(150);


        // clusterFilter table
        clustersTableModel = new ClustersTableModel();
        clustersTable = new FancyTable(clustersTableModel);
        clustersTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
        clustersTable.setAutoCreateColumnsFromModel(false);
        clustersTable.setDefaultRenderer(ClusterIcon.class,
                ((FancyTable) clustersTable).new FancyIconRenderer());
        clustersTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            @Override
            public void valueChanged(ListSelectionEvent e) {
                if (!e.getValueIsAdjusting()) {
                    if (visualFlowMap.hasClusters()) {
                        List<VisualNodeCluster> all = visualFlowMap.getVisualNodeClusters();
                        List<VisualNodeCluster> selected = new ArrayList<VisualNodeCluster>();
                        for (int i : clustersTable.getSelectedRows()) {
                            selected.add(all.get(i));
                        }
                        visualFlowMap.setNodeClustersToShow(selected);
                    }
                }
            }
        });

        TableColumnModel tcm3 = clustersTable.getColumnModel();
        tcm3.getColumn(0).setPreferredWidth(30);
        tcm3.getColumn(0).setMinWidth(20);
        tcm3.getColumn(1).setPreferredWidth(200);
//    tcm3.getColumn(1).setMaxWidth(350);


        distanceMeasureCombo = new JComboBox(NodeDistanceMeasure.values());
        linkageComboBox = new JComboBox(new Linkage[]{
                Linkages.average(), Linkages.complete(), Linkages.single()
        });
    }

    private void initNodeClusteringModels() {
        VisualFlowMap flowMap = getVisualFlowMap();

        updateClusterDistanceSliderSpinner();
        updateClusterButtons();

        initMaxClusterDistanceControls(
                getVisualFlowMap().getClusterDistanceThreshold(),
                flowMap.getMaxNodeDistance(),
                maxClusterDistanceSpinner,
                maxClusterDistanceSlider);

        if (getVisualFlowMap().hasEuclideanClusters()) {
            initMaxClusterDistanceControls(
                    getVisualFlowMap().getEuclideanClusterDistanceThreshold(),
                    flowMap.getEuclideanMaxNodeDistance(),
                    euclideanMaxClusterDistanceSpinner,
                    euclideanMaxClusterDistanceSlider);
        } else {
            initMaxClusterDistanceControls(
                    0, 0,
                    euclideanMaxClusterDistanceSpinner,
                    euclideanMaxClusterDistanceSlider);
        }


//    clusterDistancesTableModel.setDistances(flowMap.getNodeDistanceList());
//    clusterDistancesTableSorter.setSortingStatus(2, TableSorter.ASCENDING);
//    clusterNodesTableModel.setVisualNodes(flowMap.getVisualNodes());   // to re-filter
//    clusterNodesTableSorter.setSortingStatus(1, TableSorter.ASCENDING);   // to re-sort

//    maxClusterDistanceSpinner.setEditor(new JSpinner.NumberEditor(maxClusterDistanceSpinner, "0.0######"));
//    maxClusterDistanceSpinner.setModel(new SpinnerNumberModel(0, 0, 10000, 1));
//    maxClusterDistanceSlider.setMinimum(0);
//    maxClusterDistanceSlider.setMaximum(10000);

        clearClusterTableModels();

        flowsTableModel.setVisualFlowMap(flowMap);
        flowsTableSorter.setSortingStatus(2, TableSorter.DESCENDING);

//    clusterFilterTableModel.setClusters(flowMap.);
    }

    private void updateClusterDistanceSliderSpinner() {
        boolean hasClusters = getVisualFlowMap().hasClusters();
        boolean hasEuClusters = getVisualFlowMap().hasEuclideanClusters();
        maxClusterDistanceSpinner.setEnabled(hasClusters);
        maxClusterDistanceSlider.setEnabled(hasClusters);
        maxClusterDistanceLabel.setEnabled(hasClusters);

        euclideanMaxClusterDistanceSpinner.setEnabled(hasEuClusters);
        euclideanMaxClusterDistanceSlider.setEnabled(hasEuClusters);
        euclideanMaxClusterDistanceLabel.setEnabled(hasEuClusters);
    }

    private void updateClusterButtons() {
        joinClusterEdgesButton.setEnabled(getVisualFlowMap().hasClusters());
        resetClustersButton.setEnabled(getVisualFlowMap().hasClusters());
        resetJoinedEdgesButton.setEnabled(getVisualFlowMap().hasJoinedEdges());
    }

    private void initMaxClusterDistanceControls(double val, double max, JSpinner spinner, JSlider slider) {
        Pair<SpinnerModel, BoundedRangeModel> maxClusterDistanceModels =
                new BoundSpinnerSliderModels<Double>(
                        val, 0.0, max, AxisMarks.ordAlpha(max / 100.0),
                        BoundSpinnerSliderModels.createLinearMapping(100.0 / max)
                ).build();
        spinner.setModel(maxClusterDistanceModels.first());
        slider.setModel(maxClusterDistanceModels.second());
    }

    private void updateNodeClustersTables() {
        clusterNodesTableModel.setVisualNodes(getVisualFlowMap().getVisualNodes()); // to update list (re-run filtering)
        clusterNodesTableSorter.fireTableDataChanged();
        clusterNodesTableSorter.setSortingStatus(1, TableSorter.ASCENDING); // to re-sort

        clusterDistancesTableModel.setDistances(getVisualFlowMap().getNodeDistanceList());
        clusterDistancesTableSorter.setSortingStatus(2, TableSorter.ASCENDING);

        clustersTableModel.setClusters(getVisualFlowMap().getVisualNodeClusters());
    }

    private void updateNumberOfClustersLabel() {
        String text;
        boolean enabled;
        if (getVisualFlowMap().hasClusters()) {
            text = Integer.toString(getVisualFlowMap().getNumberOfClusters());
            enabled = true;
        } else {
            text = "";
            enabled = false;
        }
        numberOfClustersLabel.setEnabled(enabled);
        numberOfClustersValueLabel.setEnabled(enabled);
        numberOfClustersValueLabel.setText(text);
    }

    private void clearClusterTableModels() {
        clusterDistancesTableModel.clearData();
        clusterNodesTableModel.clearData();
        clustersTableModel.clearData();
    }

    /**
     * Method generated by IntelliJ IDEA GUI Designer
     * >>> IMPORTANT!! <<<
     * DO NOT edit this method OR call it in your code!
     *
     * @noinspection ALL
     */
    private void $$$setupUI$$$() {
        createUIComponents();
        panel1 = new JPanel();
        panel1.setLayout(new BorderLayout(0, 0));
        tabbedPane1 = new JTabbedPane();
        panel1.add(tabbedPane1, BorderLayout.CENTER);
        final JPanel panel2 = new JPanel();
        panel2.setLayout(new FormLayout("fill:max(d;4px):noGrow,left:4dlu:noGrow,fill:187px:noGrow,left:4dlu:noGrow,fill:20px:noGrow,left:4dlu:noGrow,fill:p:noGrow,left:4dlu:noGrow,fill:119px:noGrow,left:20dlu:noGrow,fill:max(d;4px):grow", "center:d:noGrow,top:4dlu:noGrow,center:max(d;4px):noGrow,top:4dlu:noGrow,center:max(d;4px):noGrow,top:4dlu:noGrow,center:max(d;4px):noGrow,top:4dlu:noGrow,center:min(p;200px):grow"));
        tabbedPane1.addTab("Dataset", panel2);
        panel2.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10), null));
        datasetCombo = new JComboBox();
        CellConstraints cc = new CellConstraints();
        panel2.add(datasetCombo, cc.xy(3, 1));
        final JLabel label1 = new JLabel();
        label1.setText("Flow weight attr:");
        panel2.add(label1, cc.xy(1, 1));
        final JSeparator separator1 = new JSeparator();
        separator1.setOrientation(1);
        panel2.add(separator1, cc.xywh(5, 1, 1, 9, CellConstraints.CENTER, CellConstraints.FILL));
        final JLabel label2 = new JLabel();
        label2.setEnabled(false);
        label2.setText("Edge weight field:");
        panel2.add(label2, cc.xy(7, 1, CellConstraints.RIGHT, CellConstraints.DEFAULT));
        comboBox4 = new JComboBox();
        comboBox4.setEnabled(false);
        panel2.add(comboBox4, cc.xy(9, 1));
        final JLabel label3 = new JLabel();
        label3.setEnabled(false);
        label3.setText("Node label field:");
        panel2.add(label3, cc.xy(7, 3, CellConstraints.RIGHT, CellConstraints.DEFAULT));
        comboBox6 = new JComboBox();
        comboBox6.setEnabled(false);
        panel2.add(comboBox6, cc.xy(9, 3));
        final JSeparator separator2 = new JSeparator();
        separator2.setOrientation(1);
        panel2.add(separator2, cc.xywh(10, 1, 1, 9, CellConstraints.CENTER, CellConstraints.FILL));
        final JTabbedPane tabbedPane3 = new JTabbedPane();
        tabbedPane3.setTabPlacement(3);
        panel2.add(tabbedPane3, cc.xywh(11, 1, 1, 9, CellConstraints.DEFAULT, CellConstraints.FILL));
        final JScrollPane scrollPane1 = new JScrollPane();
        tabbedPane3.addTab("Flows", scrollPane1);
        scrollPane1.setBorder(BorderFactory.createTitledBorder(""));
        flowsTable.setPreferredScrollableViewportSize(new Dimension(450, 100));
        scrollPane1.setViewportView(flowsTable);
        final JLabel label4 = new JLabel();
        label4.setEnabled(false);
        label4.setText("Node X coord field:");
        panel2.add(label4, cc.xy(7, 5, CellConstraints.RIGHT, CellConstraints.DEFAULT));
        comboBox2 = new JComboBox();
        comboBox2.setEnabled(false);
        panel2.add(comboBox2, cc.xy(9, 5));
        final JLabel label5 = new JLabel();
        label5.setEnabled(false);
        label5.setText("Node Y coord field:");
        panel2.add(label5, cc.xy(7, 7, CellConstraints.RIGHT, CellConstraints.DEFAULT));
        comboBox3 = new JComboBox();
        comboBox3.setEnabled(false);
        panel2.add(comboBox3, cc.xy(9, 7));
        final JPanel panel3 = new JPanel();
        panel3.setLayout(new FormLayout("right:max(d;4px):noGrow,left:4dlu:noGrow,fill:d:grow(2.0),left:4dlu:noGrow,fill:p:noGrow,left:4dlu:noGrow,fill:20px:noGrow,left:4dlu:noGrow,right:max(d;4px):noGrow,left:4dlu:noGrow,fill:d:grow,left:4dlu:noGrow,fill:p:noGrow", "center:26px:noGrow,top:4dlu:noGrow,center:24px:noGrow,top:4dlu:noGrow,center:max(d;4px):noGrow,top:5dlu:noGrow,center:d:noGrow"));
        tabbedPane1.addTab("Filter", panel3);
        panel3.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10), null));
        minWeightFilterSpinner = new JSpinner();
        panel3.add(minWeightFilterSpinner, cc.xy(5, 1, CellConstraints.FILL, CellConstraints.DEFAULT));
        final JSeparator separator3 = new JSeparator();
        separator3.setOrientation(1);
        panel3.add(separator3, cc.xywh(7, 1, 1, 5, CellConstraints.CENTER, CellConstraints.FILL));
        final JLabel label6 = new JLabel();
        label6.setText("Min length:");
        panel3.add(label6, cc.xy(9, 1));
        minLengthFilterSlider = new JSlider();
        panel3.add(minLengthFilterSlider, cc.xy(11, 1, CellConstraints.FILL, CellConstraints.DEFAULT));
        minLengthFilterSpinner = new JSpinner();
        panel3.add(minLengthFilterSpinner, cc.xy(13, 1, CellConstraints.FILL, CellConstraints.DEFAULT));
        final JLabel label7 = new JLabel();
        label7.setText("Max edge weight:");
        panel3.add(label7, cc.xy(1, 3));
        maxWeightFilterSlider = new JSlider();
        panel3.add(maxWeightFilterSlider, cc.xy(3, 3, CellConstraints.FILL, CellConstraints.CENTER));
        maxWeightFilterSpinner = new JSpinner();
        panel3.add(maxWeightFilterSpinner, cc.xy(5, 3, CellConstraints.FILL, CellConstraints.DEFAULT));
        autoAdjustColorScaleCheckBox = new JCheckBox();
        autoAdjustColorScaleCheckBox.setEnabled(false);
        autoAdjustColorScaleCheckBox.setText("Auto adjust color scale");
        panel3.add(autoAdjustColorScaleCheckBox, cc.xyw(3, 5, 3));
        final JLabel label8 = new JLabel();
        label8.setText("Max length:");
        panel3.add(label8, cc.xy(9, 3));
        maxLengthFilterSlider = new JSlider();
        panel3.add(maxLengthFilterSlider, cc.xy(11, 3, CellConstraints.FILL, CellConstraints.DEFAULT));
        maxLengthFilterSpinner = new JSpinner();
        panel3.add(maxLengthFilterSpinner, cc.xy(13, 3, CellConstraints.FILL, CellConstraints.DEFAULT));
        final JPanel panel4 = new JPanel();
        panel4.setLayout(new FormLayout("fill:d:grow", "center:d:grow"));
        panel3.add(panel4, cc.xy(5, 7));
        final JLabel label9 = new JLabel();
        label9.setText("Min edge weight:");
        panel3.add(label9, cc.xy(1, 1));
        minWeightFilterSlider = new JSlider();
        panel3.add(minWeightFilterSlider, cc.xy(3, 1, CellConstraints.FILL, CellConstraints.DEFAULT));
        final JPanel panel5 = new JPanel();
        panel5.setLayout(new FormLayout("fill:d:noGrow,left:p:noGrow,fill:20px:noGrow,left:4dlu:noGrow,fill:p:noGrow,left:20dlu:noGrow,fill:max(d;4px):grow", "center:max(d;4px):noGrow,top:4dlu:noGrow,center:24px:noGrow,top:6dlu:noGrow,top:4dlu:noGrow"));
        tabbedPane1.addTab("Scales", panel5);
        panel5.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10), null));
        final JSeparator separator4 = new JSeparator();
        separator4.setOrientation(1);
        panel5.add(separator4, cc.xywh(3, 1, 1, 5, CellConstraints.CENTER, CellConstraints.FILL));
        useLogWidthScaleCheckbox = new JCheckBox();
        useLogWidthScaleCheckbox.setEnabled(true);
        useLogWidthScaleCheckbox.setText("Use log width scale");
        panel5.add(useLogWidthScaleCheckbox, cc.xy(2, 1));
        useLogColorScaleCheckbox = new JCheckBox();
        useLogColorScaleCheckbox.setEnabled(true);
        useLogColorScaleCheckbox.setText("Use log color scale");
        panel5.add(useLogColorScaleCheckbox, cc.xyw(1, 3, 2));
        mapEdgeValueToCheckBox = new JCheckBox();
        mapEdgeValueToCheckBox.setEnabled(false);
        mapEdgeValueToCheckBox.setText("Map edge value to color");
        panel5.add(mapEdgeValueToCheckBox, cc.xy(5, 1));
        mapEdgeValueToCheckBox1 = new JCheckBox();
        mapEdgeValueToCheckBox1.setEnabled(false);
        mapEdgeValueToCheckBox1.setText("Map edge value to width");
        panel5.add(mapEdgeValueToCheckBox1, cc.xy(5, 3));
        final JSeparator separator5 = new JSeparator();
        separator5.setOrientation(1);
        panel5.add(separator5, cc.xywh(6, 1, 1, 5, CellConstraints.CENTER, CellConstraints.FILL));
        final JPanel panel6 = new JPanel();
        panel6.setLayout(new FormLayout("fill:d:noGrow,left:4dlu:noGrow,fill:110px:noGrow,left:4dlu:noGrow,fill:20px:noGrow,left:4dlu:noGrow,fill:max(d;4px):noGrow,fill:20px:noGrow,left:4dlu:noGrow,fill:max(d;4px):noGrow,left:4dlu:noGrow,fill:max(d;4px):grow,left:4dlu:noGrow,fill:max(m;50px):noGrow", "center:d:noGrow,top:4dlu:noGrow,center:max(d;4px):noGrow,top:4dlu:noGrow,center:max(d;4px):noGrow,top:4dlu:noGrow,center:max(d;4px):noGrow"));
        tabbedPane1.addTab("Aesthetics", panel6);
        panel6.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10), null));
        final JLabel label10 = new JLabel();
        label10.setEnabled(true);
        label10.setText("Color scheme:");
        panel6.add(label10, cc.xy(1, 1));
        colorSchemeCombo = new JComboBox();
        colorSchemeCombo.setEnabled(true);
        panel6.add(colorSchemeCombo, cc.xy(3, 1));
        final JSeparator separator6 = new JSeparator();
        separator6.setOrientation(1);
        panel6.add(separator6, cc.xywh(5, 1, 1, 7, CellConstraints.CENTER, CellConstraints.FILL));
        final JLabel label11 = new JLabel();
        label11.setText("Edge width:");
        panel6.add(label11, cc.xy(10, 1, CellConstraints.RIGHT, CellConstraints.CENTER));
        maxEdgeWidthSlider = new JSlider();
        maxEdgeWidthSlider.setPaintLabels(false);
        maxEdgeWidthSlider.setPaintTicks(false);
        maxEdgeWidthSlider.setPaintTrack(true);
        panel6.add(maxEdgeWidthSlider, cc.xy(12, 1, CellConstraints.FILL, CellConstraints.DEFAULT));
        maxEdgeWidthSpinner = new JSpinner();
        panel6.add(maxEdgeWidthSpinner, cc.xy(14, 1, CellConstraints.FILL, CellConstraints.DEFAULT));
        final JLabel label12 = new JLabel();
        label12.setText("Edge opacity:");
        panel6.add(label12, cc.xy(10, 3, CellConstraints.RIGHT, CellConstraints.DEFAULT));
        edgeOpacitySlider = new JSlider();
        panel6.add(edgeOpacitySlider, cc.xy(12, 3, CellConstraints.FILL, CellConstraints.DEFAULT));
        edgeOpacitySpinner = new JSpinner();
        panel6.add(edgeOpacitySpinner, cc.xy(14, 3, CellConstraints.FILL, CellConstraints.DEFAULT));
        showNodesCheckBox = new JCheckBox();
        showNodesCheckBox.setText("Show nodes");
        panel6.add(showNodesCheckBox, cc.xy(7, 1));
        final JSeparator separator7 = new JSeparator();
        separator7.setOrientation(1);
        panel6.add(separator7, cc.xywh(8, 1, 1, 7, CellConstraints.CENTER, CellConstraints.FILL));
        showDirectionMarkersCheckBox = new JCheckBox();
        showDirectionMarkersCheckBox.setText("Show direction markers");
        panel6.add(showDirectionMarkersCheckBox, cc.xy(7, 5));
        fillEdgesWithGradientCheckBox = new JCheckBox();
        fillEdgesWithGradientCheckBox.setText("Fill edges with gradient");
        panel6.add(fillEdgesWithGradientCheckBox, cc.xy(7, 3));
        proportionalDirectionMarkersCheckBox = new JCheckBox();
        proportionalDirectionMarkersCheckBox.setText("Proportional direction markers");
        panel6.add(proportionalDirectionMarkersCheckBox, cc.xy(7, 7));
        edgeMarkerSizeSpinner = new JSpinner();
        panel6.add(edgeMarkerSizeSpinner, cc.xy(14, 5, CellConstraints.FILL, CellConstraints.DEFAULT));
        edgeMarkerSizeSlider = new JSlider();
        panel6.add(edgeMarkerSizeSlider, cc.xy(12, 5, CellConstraints.FILL, CellConstraints.DEFAULT));
        edgeMarkerOpacitySpinner = new JSpinner();
        panel6.add(edgeMarkerOpacitySpinner, cc.xy(14, 7, CellConstraints.FILL, CellConstraints.DEFAULT));
        edgeMarkerOpacitySlider = new JSlider();
        panel6.add(edgeMarkerOpacitySlider, cc.xy(12, 7, CellConstraints.FILL, CellConstraints.DEFAULT));
        edgeMarkerSizeLabel = new JLabel();
        edgeMarkerSizeLabel.setText("Direction marker size:");
        panel6.add(edgeMarkerSizeLabel, cc.xy(10, 5, CellConstraints.RIGHT, CellConstraints.DEFAULT));
        edgeMarkerOpacityLabel = new JLabel();
        edgeMarkerOpacityLabel.setText("Direction marker opacity:");
        panel6.add(edgeMarkerOpacityLabel, cc.xy(10, 7, CellConstraints.RIGHT, CellConstraints.DEFAULT));
        final JPanel panel7 = new JPanel();
        panel7.setLayout(new FormLayout("fill:d:noGrow,left:6dlu:noGrow,fill:p:noGrow,left:4dlu:noGrow,fill:p:noGrow,left:4dlu:noGrow,fill:p:noGrow,left:4dlu:noGrow,fill:12px:noGrow,left:4dlu:noGrow,fill:max(d;4px):noGrow,left:4dlu:noGrow,fill:p:noGrow,left:12dlu:noGrow,fill:p:noGrow,fill:d:noGrow,left:d:noGrow", "center:max(d;4px):noGrow,top:4dlu:noGrow,center:25px:noGrow,top:4dlu:noGrow,center:max(d;4px):noGrow,top:4dlu:noGrow,center:d:noGrow,center:max(d;4px):noGrow,top:4dlu:noGrow,center:max(d;4px):noGrow"));
        tabbedPane1.addTab("Edge bundling", panel7);
        panel7.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10), null));
        final JSeparator separator8 = new JSeparator();
        separator8.setOrientation(1);
        panel7.add(separator8, cc.xywh(9, 1, 1, 8, CellConstraints.CENTER, CellConstraints.FILL));
        final JLabel label13 = new JLabel();
        label13.setHorizontalAlignment(4);
        label13.setText("Step damping factor:");
        panel7.add(label13, cc.xy(11, 3));
        stepDampingFactorSpinner = new JSpinner();
        panel7.add(stepDampingFactorSpinner, cc.xy(13, 3, CellConstraints.FILL, CellConstraints.DEFAULT));
        stepSizeSpinner = new JSpinner();
        panel7.add(stepSizeSpinner, cc.xy(13, 1, CellConstraints.FILL, CellConstraints.DEFAULT));
        final JLabel label14 = new JLabel();
        label14.setHorizontalAlignment(4);
        label14.setText("Step size (S):");
        panel7.add(label14, cc.xy(11, 1, CellConstraints.RIGHT, CellConstraints.DEFAULT));
        final JLabel label15 = new JLabel();
        label15.setHorizontalAlignment(4);
        label15.setText("Edge stiffness (K):");
        panel7.add(label15, cc.xy(5, 3, CellConstraints.RIGHT, CellConstraints.DEFAULT));
        edgeStiffnessSpinner = new JSpinner();
        panel7.add(edgeStiffnessSpinner, cc.xy(7, 3, CellConstraints.FILL, CellConstraints.DEFAULT));
        final JSeparator separator9 = new JSeparator();
        separator9.setOrientation(1);
        panel7.add(separator9, cc.xywh(14, 1, 1, 8, CellConstraints.CENTER, CellConstraints.FILL));
        final JLabel label16 = new JLabel();
        label16.setHorizontalAlignment(4);
        label16.setText("Number of cycles:");
        panel7.add(label16, cc.xy(5, 1));
        numberOfCyclesSpinner = new JSpinner();
        panel7.add(numberOfCyclesSpinner, cc.xy(7, 1, CellConstraints.FILL, CellConstraints.DEFAULT));
        final JLabel label17 = new JLabel();
        label17.setHorizontalAlignment(4);
        label17.setText("Compatibility threshold:");
        panel7.add(label17, cc.xy(5, 5));
        edgeCompatibilityThresholdSpinner = new JSpinner();
        panel7.add(edgeCompatibilityThresholdSpinner, cc.xy(7, 5, CellConstraints.FILL, CellConstraints.DEFAULT));
        final JLabel label18 = new JLabel();
        label18.setHorizontalAlignment(4);
        label18.setText("Steps in 1st cycle (I):");
        panel7.add(label18, cc.xy(11, 5));
        stepsInCycleSpinner = new JSpinner();
        panel7.add(stepsInCycleSpinner, cc.xy(13, 5, CellConstraints.FILL, CellConstraints.DEFAULT));
        bundleButton = new JButton();
        bundleButton.setText("Bundle");
        panel7.add(bundleButton, cc.xy(1, 1));
        resetBundlingButton = new JButton();
        resetBundlingButton.setText("Reset");
        panel7.add(resetBundlingButton, cc.xy(1, 3));
        defaultValuesButton = new JButton();
        defaultValuesButton.setText("Default Values");
        panel7.add(defaultValuesButton, cc.xy(1, 5));
        final JSeparator separator10 = new JSeparator();
        separator10.setOrientation(1);
        panel7.add(separator10, cc.xywh(3, 1, 1, 8, CellConstraints.CENTER, CellConstraints.FILL));
        repulsiveEdgesCheckBox = new JCheckBox();
        repulsiveEdgesCheckBox.setText("Repulsion:");
        panel7.add(repulsiveEdgesCheckBox, cc.xy(5, 8, CellConstraints.RIGHT, CellConstraints.DEFAULT));
        repulsionSpinner = new JSpinner();
        panel7.add(repulsionSpinner, cc.xy(7, 8, CellConstraints.FILL, CellConstraints.DEFAULT));
        inverseQuadraticModelCheckBox = new JCheckBox();
        inverseQuadraticModelCheckBox.setText("Inverse-quadratic model");
        panel7.add(inverseQuadraticModelCheckBox, cc.xyw(11, 8, 3, CellConstraints.LEFT, CellConstraints.DEFAULT));
        final JSeparator separator11 = new JSeparator();
        panel7.add(separator11, cc.xyw(1, 7, 2, CellConstraints.FILL, CellConstraints.FILL));
        aggregateEdgesButton = new JButton();
        aggregateEdgesButton.setText("Aggregate edges");
        panel7.add(aggregateEdgesButton, cc.xy(1, 8));
        directionAffectsCompatibilityCheckBox = new JCheckBox();
        directionAffectsCompatibilityCheckBox.setText("Direction affects compatibility");
        panel7.add(directionAffectsCompatibilityCheckBox, cc.xy(17, 1, CellConstraints.LEFT, CellConstraints.DEFAULT));
        edgeValueAffectsAttractionCheckBox = new JCheckBox();
        edgeValueAffectsAttractionCheckBox.setText("Edge value affects attraction");
        panel7.add(edgeValueAffectsAttractionCheckBox, cc.xy(17, 3));
        simpleCompatibilityMeasureCheckBox = new JCheckBox();
        simpleCompatibilityMeasureCheckBox.setText("Simple compatibility measure");
        panel7.add(simpleCompatibilityMeasureCheckBox, cc.xy(17, 5, CellConstraints.LEFT, CellConstraints.DEFAULT));
        binaryCompatibilityCheckBox = new JCheckBox();
        binaryCompatibilityCheckBox.setText("Binary compatibility");
        panel7.add(binaryCompatibilityCheckBox, cc.xy(17, 8, CellConstraints.LEFT, CellConstraints.DEFAULT));
        final JPanel panel8 = new JPanel();
        panel8.setLayout(new FormLayout("fill:max(d;4px):noGrow,left:4dlu:noGrow,fill:20px:noGrow,left:4dlu:noGrow,fill:max(d;4px):noGrow,left:4dlu:noGrow,fill:d:noGrow,left:4dlu:noGrow,fill:max(d;4px):grow,left:4dlu:noGrow,fill:max(p;75px):noGrow,left:10dlu:noGrow,fill:1px:noGrow,left:10dlu:noGrow,fill:max(p;200px):noGrow", "center:max(p;4px):noGrow,top:4dlu:noGrow,center:max(d;4px):noGrow,top:4dlu:noGrow,center:m:noGrow,top:d:noGrow,center:max(d;6px):noGrow,center:d:noGrow,top:4dlu:noGrow,center:max(d;4px):noGrow,top:4dlu:noGrow,center:max(d;4px):noGrow,top:4dlu:noGrow,center:min(p;200px):grow"));
        tabbedPane1.addTab("Node clustering", panel8);
        panel8.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10), null));
        panel8.add(distanceMeasureCombo, cc.xyw(7, 1, 5, CellConstraints.LEFT, CellConstraints.DEFAULT));
        final JLabel label19 = new JLabel();
        label19.setText("Distance measure:");
        panel8.add(label19, cc.xy(5, 1, CellConstraints.RIGHT, CellConstraints.DEFAULT));
        tabbedPane2 = new JTabbedPane();
        tabbedPane2.setTabPlacement(3);
        panel8.add(tabbedPane2, cc.xywh(15, 1, 1, 14, CellConstraints.DEFAULT, CellConstraints.FILL));
        final JPanel panel9 = new JPanel();
        panel9.setLayout(new FormLayout("fill:d:grow", "center:d:grow"));
        tabbedPane2.addTab("Clusters", panel9);
        final JScrollPane scrollPane2 = new JScrollPane();
        panel9.add(scrollPane2, cc.xy(1, 1, CellConstraints.DEFAULT, CellConstraints.FILL));
        scrollPane2.setBorder(BorderFactory.createTitledBorder(""));
        clustersTable.setPreferredScrollableViewportSize(new Dimension(450, 100));
        scrollPane2.setViewportView(clustersTable);
        final JScrollPane scrollPane3 = new JScrollPane();
        tabbedPane2.addTab("Nodes", scrollPane3);
        scrollPane3.setBorder(BorderFactory.createTitledBorder(""));
        clusterNodesTable.setPreferredScrollableViewportSize(new Dimension(450, 100));
        scrollPane3.setViewportView(clusterNodesTable);
        final JScrollPane scrollPane4 = new JScrollPane();
        tabbedPane2.addTab("Distances", scrollPane4);
        scrollPane4.setBorder(BorderFactory.createTitledBorder(""));
        clusterDistancesTable.setPreferredScrollableViewportSize(new Dimension(450, 100));
        scrollPane4.setViewportView(clusterDistancesTable);
        final JSeparator separator12 = new JSeparator();
        separator12.setOrientation(1);
        panel8.add(separator12, cc.xywh(13, 1, 1, 14, CellConstraints.CENTER, CellConstraints.FILL));
        final JLabel label20 = new JLabel();
        label20.setText("Linkage:");
        panel8.add(label20, cc.xy(5, 3, CellConstraints.RIGHT, CellConstraints.DEFAULT));
        panel8.add(linkageComboBox, cc.xy(7, 3, CellConstraints.LEFT, CellConstraints.DEFAULT));
        maxClusterDistanceSlider = new JSlider();
        maxClusterDistanceSlider.setEnabled(false);
        panel8.add(maxClusterDistanceSlider, cc.xyw(6, 8, 4, CellConstraints.FILL, CellConstraints.DEFAULT));
        euclideanMaxClusterDistanceSlider = new JSlider();
        euclideanMaxClusterDistanceSlider.setEnabled(false);
        panel8.add(euclideanMaxClusterDistanceSlider, cc.xyw(6, 10, 4, CellConstraints.FILL, CellConstraints.DEFAULT));
        maxClusterDistanceSpinner = new JSpinner();
        maxClusterDistanceSpinner.setEnabled(false);
        panel8.add(maxClusterDistanceSpinner, cc.xy(11, 8, CellConstraints.FILL, CellConstraints.DEFAULT));
        euclideanMaxClusterDistanceSpinner = new JSpinner();
        euclideanMaxClusterDistanceSpinner.setEnabled(false);
        panel8.add(euclideanMaxClusterDistanceSpinner, cc.xy(11, 10, CellConstraints.FILL, CellConstraints.DEFAULT));
        euclideanMaxClusterDistanceLabel = new JLabel();
        euclideanMaxClusterDistanceLabel.setEnabled(false);
        euclideanMaxClusterDistanceLabel.setText("Euclidean max cluster distance:");
        panel8.add(euclideanMaxClusterDistanceLabel, cc.xy(5, 10, CellConstraints.RIGHT, CellConstraints.DEFAULT));
        maxClusterDistanceLabel = new JLabel();
        maxClusterDistanceLabel.setEnabled(false);
        maxClusterDistanceLabel.setText("Max cluster distance:");
        panel8.add(maxClusterDistanceLabel, cc.xy(5, 8, CellConstraints.RIGHT, CellConstraints.DEFAULT));
        final JSeparator separator13 = new JSeparator();
        separator13.setOrientation(1);
        panel8.add(separator13, cc.xywh(3, 1, 1, 4, CellConstraints.FILL, CellConstraints.FILL));
        clusterButton = new JButton();
        clusterButton.setText("Cluster");
        panel8.add(clusterButton, cc.xy(1, 1));
        final JSeparator separator14 = new JSeparator();
        panel8.add(separator14, cc.xyw(1, 5, 12, CellConstraints.FILL, CellConstraints.FILL));
        numberOfClustersLabel = new JLabel();
        numberOfClustersLabel.setEnabled(false);
        numberOfClustersLabel.setText("Number of clusters:");
        panel8.add(numberOfClustersLabel, cc.xy(5, 12, CellConstraints.RIGHT, CellConstraints.DEFAULT));
        numberOfClustersValueLabel = new JLabel();
        numberOfClustersValueLabel.setText("");
        panel8.add(numberOfClustersValueLabel, cc.xy(7, 12));
        joinClusterEdgesButton = new JButton();
        joinClusterEdgesButton.setEnabled(true);
        joinClusterEdgesButton.setText("Join edges");
        panel8.add(joinClusterEdgesButton, cc.xy(1, 8));
        resetClustersButton = new JButton();
        resetClustersButton.setEnabled(true);
        resetClustersButton.setText("Reset");
        panel8.add(resetClustersButton, cc.xy(1, 3));
        resetJoinedEdgesButton = new JButton();
        resetJoinedEdgesButton.setText("Reset");
        panel8.add(resetJoinedEdgesButton, cc.xy(1, 10));
        final JSeparator separator15 = new JSeparator();
        separator15.setOrientation(1);
        panel8.add(separator15, cc.xywh(3, 7, 1, 7, CellConstraints.FILL, CellConstraints.FILL));
        combineWithEuclideanClustersCheckBox = new JCheckBox();
        combineWithEuclideanClustersCheckBox.setEnabled(true);
        combineWithEuclideanClustersCheckBox.setSelected(false);
        combineWithEuclideanClustersCheckBox.setText("Combine with Euclidean clusters");
        panel8.add(combineWithEuclideanClustersCheckBox, cc.xyw(9, 3, 3));
    }

    /**
     * @noinspection ALL
     */
    public JComponent $$$getRootComponent$$$() {
        return panel1;
    }
}
