All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.cmu.tetradapp.editor.BayesPmEditorWizard Maven / Gradle / Ivy

The newest version!
///////////////////////////////////////////////////////////////////////////////
// For information as to what this class does, see the Javadoc, below.       //
// Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,       //
// 2007, 2008, 2009, 2010, 2014, 2015, 2022 by Peter Spirtes, Richard        //
// Scheines, Joseph Ramsey, and Clark Glymour.                               //
//                                                                           //
// 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 2 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, write to the Free Software               //
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA //
///////////////////////////////////////////////////////////////////////////////
package edu.cmu.tetradapp.editor;

import edu.cmu.tetrad.bayes.BayesPm;
import edu.cmu.tetrad.data.DiscreteVariable;
import edu.cmu.tetrad.graph.Graph;
import edu.cmu.tetrad.graph.Node;
import edu.cmu.tetrad.graph.NodeType;
import edu.cmu.tetrad.util.JOptionUtils;
import edu.cmu.tetradapp.util.StringTextField;
import edu.cmu.tetradapp.workbench.GraphWorkbench;

import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;

/**
 * A wizard to let the user go through a workbench systematically and set the number of categories for each node along
 * with the names of each category.
 *
 * @author josephramsey
 */
final class BayesPmEditorWizard extends JPanel {

    /**
     * The BayesPm model being edited.
     */
    private final BayesPm bayesPm;
    /**
     * Lets the user see graphically which variable is being edited and click to another variable.
     */
    private final GraphWorkbench workbench;
    /**
     * A reference to the category editor.
     */
    private final CategoryEditor categoryEditor;
    /**
     * The preset strings that will be used.
     */
    private final String[][] presetStrings = {{"Low", "High"},
            {"Low", "Medium", "High"}, {"On", "Off"}, {"Yes", "No"}};
    /**
     * ?
     */
    private final Map labels = new HashMap<>();
    /**
     * Lets the user select the variable they want to edit.
     */
    private JComboBox variableChooser;
    /**
     * True iff the editing of measured variables is allowed.
     */
    private boolean editingMeasuredVariablesAllowed;
    /**
     * True iff the editing of latent variables is allowed.
     */
    private boolean editingLatentVariablesAllowed;
    /**
     * A reference to the spinner model.
     */
    private SpinnerNumberModel spinnerModel;
    /**
     * ?
     */
    private List copiedCategories;
    /**
     * ?
     */
    private JSpinner numCategoriesSpinner;

    /**
     *
     */
    private JMenu presetMenu;

    /**
     * This is the wizard for the PMEditor class. Its function is to allow the user to enter, for each variable in the
     * associated Graph, the number of categories it may take on and the string names for each of those categories.
     *
     * @param bayesPm   a {@link edu.cmu.tetrad.bayes.BayesPm} object
     * @param workbench a {@link edu.cmu.tetradapp.workbench.GraphWorkbench} object
     */
    public BayesPmEditorWizard(BayesPm bayesPm, GraphWorkbench workbench) {
        if (bayesPm == null) {
            throw new NullPointerException();
        }

        if (workbench == null) {
            throw new NullPointerException();
        }

        this.bayesPm = bayesPm;
        this.workbench = workbench;

        workbench().setAllowDoubleClickActions(false);

        // Construct components.
        createVariableChooser(getBayesPm(), workbench());

        JButton nextButton = new JButton("Next");
        nextButton.setMnemonic('N');

        int numCategories = numCategories();

        Box b1 = Box.createVerticalBox();

        Box b2 = Box.createHorizontalBox();
        b2.add(new JLabel("Edit categories for: "));
        b2.add(this.variableChooser);
        b2.add(nextButton);
        b2.add(Box.createHorizontalGlue());
        b1.add(b2);
        b1.add(Box.createVerticalStrut(10));

        if (numCategories != 0) {
            this.spinnerModel = new SpinnerNumberModel(numCategories, 2, 1000, 1);
            this.numCategoriesSpinner = new JSpinner(this.spinnerModel) {

                private static final long serialVersionUID = -7932603602816371347L;

                @Override
                public Dimension getMaximumSize() {
                    return getPreferredSize();
                }

            };
            this.numCategoriesSpinner.setFont(new Font("Serif", Font.PLAIN, 12));
            this.numCategoriesSpinner.addChangeListener((e) -> {
                JSpinner spinner = (JSpinner) e.getSource();
                SpinnerNumberModel model
                        = (SpinnerNumberModel) spinner.getModel();
                setNumCategories(model.getNumber().intValue());
            });

            Box b3 = Box.createHorizontalBox();
            b3.add(new JLabel("Number of categories:  "));
            b3.add(this.numCategoriesSpinner);
            b3.add(Box.createHorizontalGlue());
            b1.add(b3);

            b1.add(Box.createVerticalStrut(10));
        }

        this.categoryEditor = new CategoryEditor(bayesPm, getNode());

        Box b4 = Box.createHorizontalBox();
        b4.add(new JLabel("Category names: "));
        b4.add(Box.createHorizontalGlue());
        b1.add(b4);

        b1.add(Box.createVerticalStrut(10));

        Box b5 = Box.createHorizontalBox();
        b5.add(this.categoryEditor);
        b1.add(b5);

        Box b6 = Box.createHorizontalBox();
        b6.add(Box.createRigidArea(new Dimension(400, 0)));
        b1.add(b6);
        b1.add(Box.createVerticalGlue());

        JMenuBar menuBar = createMenuBar();

        b1.setBorder(new EmptyBorder(10, 10, 0, 10));

        setLayout(new BorderLayout());
        add(b1, BorderLayout.CENTER);
        add(menuBar, BorderLayout.NORTH);

        workbench.addPropertyChangeListener((evt) -> {
            if (evt.getPropertyName().equals("selectedNodes")) {
                List selection = (List) (evt.getNewValue());
                if (selection.size() == 1) {
                    Node node = (Node) (selection.get(0));
                    this.variableChooser.setSelectedItem(node);
                }
            }
        });

        this.variableChooser.addActionListener((e) -> {
            Node n = (Node) (this.variableChooser.getSelectedItem());
            workbench().scrollWorkbenchToNode(n);
            setNode(n);
        });

        nextButton.addActionListener((e) -> {
            int current = this.variableChooser.getSelectedIndex();
            int max = this.variableChooser.getItemCount();

            ++current;

            if (current == max) {
                JOptionPane.showMessageDialog(this,
                        "There are no more variables.");
            }

            int set = (current < max) ? current : 0;

            this.variableChooser.setSelectedIndex(set);
        });

        enableByNodeType();
    }

    private void enableByNodeType() {
        if (!isEditingMeasuredVariablesAllowed() && this.categoryEditor.getNode().getNodeType() == NodeType.MEASURED) {
            setEnabled(false);
        } else
            setEnabled(isEditingLatentVariablesAllowed() || this.categoryEditor.getNode().getNodeType() != NodeType.LATENT);
    }

    /**
     * {@inheritDoc}
     */
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);
        this.numCategoriesSpinner.setEnabled(enabled);
        this.categoryEditor.setEnabled(enabled);
    }

    private void setNumCategories(int numCategories) {
        this.categoryEditor.setNumCategories(numCategories);
        firePropertyChange("modelChanged", null, null);
    }

    private JMenuBar createMenuBar() {
        JMenuBar menuBar = new JMenuBar();
        JMenu presetMenu = new JMenu("Presets");
        this.presetMenu = presetMenu;
        menuBar.add(presetMenu);

        for (int i = 0; i < this.presetStrings.length; i++) {
            StringBuilder buf = new StringBuilder();

            for (int j = 0; j < this.presetStrings[i].length; j++) {
                buf.append(this.presetStrings[i][j]);

                if (j < this.presetStrings[i].length - 1) {
                    buf.append("-");
                }
            }

            Action action = new IndexedAction(buf.toString(), i) {

                private static final long serialVersionUID = 5052478563546335636L;

                @Override
                public void actionPerformed(ActionEvent e) {
                    setCategories(Arrays.asList(BayesPmEditorWizard.this.presetStrings[getIndex()]));
                }

            };

            presetMenu.add(action);
        }

        presetMenu.addSeparator();

        Action sequence = new AbstractAction("x1, x2, x3, ...") {

            private static final long serialVersionUID = 4377386270269629176L;

            @Override
            public void actionPerformed(ActionEvent e) {
                List categories = new ArrayList();
                String ret = JOptionPane.showInputDialog(
                        JOptionUtils.centeringComp(),
                        "Please input a prefix string for the sequence: ",
                        "category");

                int numCategories = numCategories();

                for (int i = 0; i < numCategories; i++) {
                    categories.add(ret + (i + 1));
                }

                setCategories(categories);
            }
        };

        presetMenu.add(sequence);

        JMenu transfer = new JMenu("Transfer");
        JMenuItem copy = new JMenuItem("Copy categories");
        JMenuItem paste = new JMenuItem("Paste categories");

        copy.addActionListener((e) -> {
            copyCategories();

            JOptionPane.showMessageDialog(JOptionUtils.centeringComp(),
                    ""
                    + "The categories for this node have been copied; to transfer "
                    + "
these categories, choose another node and paste. You may" + "
paste multiple times." + ""); }); paste.addActionListener((e) -> pasteCategories()); copy.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK)); paste.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_DOWN_MASK)); transfer.add(copy); transfer.add(paste); menuBar.add(transfer); return menuBar; } private void copyCategories() { Node node = (Node) this.variableChooser.getSelectedItem(); DiscreteVariable variable = (DiscreteVariable) this.bayesPm.getVariable(node); this.copiedCategories = variable.getCategories(); } private void pasteCategories() { if (this.copiedCategories != null) { setCategories(this.copiedCategories); } } private void setCategories(List categories) { this.categoryEditor.setCategories(categories); this.spinnerModel.setValue(categories.size()); firePropertyChange("modelChanged", null, null); } private void createVariableChooser(BayesPm bayesPm, GraphWorkbench workbench) { this.variableChooser = new JComboBox<>(); this.variableChooser.setBackground(Color.white); Graph graphModel = bayesPm.getDag(); List nodes = graphModel.getNodes().stream().sorted().collect(Collectors.toList()); nodes.forEach(this.variableChooser::addItem); if (this.variableChooser.getItemCount() > 0) { this.variableChooser.setSelectedIndex(0); } workbench.scrollWorkbenchToNode((Node) this.variableChooser.getSelectedItem()); } private GraphWorkbench workbench() { return this.workbench; } private int numCategories() { return getBayesPm().getNumCategories(getNode()); } private BayesPm getBayesPm() { return this.bayesPm; } private Node getNode() { Node selectedItem = (Node) this.variableChooser.getSelectedItem(); if (selectedItem == null) { throw new NullPointerException(); } return selectedItem; } private void setNode(Node node) { this.categoryEditor.setNode(node); int numCategories = this.bayesPm.getNumCategories(node); this.spinnerModel.setValue(numCategories); firePropertyChange("modelChanged", null, null); enableByNodeType(); } private boolean isEditingMeasuredVariablesAllowed() { return this.editingMeasuredVariablesAllowed; } /** *

Setter for the field editingMeasuredVariablesAllowed.

* * @param editingMeasuredVariablesAllowed a boolean */ public void setEditingMeasuredVariablesAllowed(boolean editingMeasuredVariablesAllowed) { this.editingMeasuredVariablesAllowed = editingMeasuredVariablesAllowed; setNode(this.categoryEditor.getNode()); this.presetMenu.setEnabled(editingMeasuredVariablesAllowed); } private boolean isEditingLatentVariablesAllowed() { return this.editingLatentVariablesAllowed; } /** *

Setter for the field editingLatentVariablesAllowed.

* * @param editingLatentVariablesAllowed a boolean */ public void setEditingLatentVariablesAllowed(boolean editingLatentVariablesAllowed) { this.editingLatentVariablesAllowed = editingLatentVariablesAllowed; setNode(this.categoryEditor.getNode()); if (!editingLatentVariablesAllowed) { this.presetMenu.setEnabled(false); } } /** * The actionPerformed method is still abstract. */ abstract static class IndexedAction extends AbstractAction { private static final long serialVersionUID = -8261331986030513841L; private final int index; public IndexedAction(String name, int index) { super(name); this.index = index; } public int getIndex() { return this.index; } } /** * Edits categories for each variable of a Bayes PM. * * @author josephramsey */ class CategoryEditor extends JPanel { private static final long serialVersionUID = -7488118975131239436L; private final BayesPm bayesPm; private final LinkedList focusTraveralOrder = new LinkedList(); private Node node; private StringTextField[] categoryFields; public CategoryEditor(BayesPm bayesPm, Node node) { if (bayesPm == null) { throw new NullPointerException(); } setLayout(new BorderLayout()); if (node == null) { // return; throw new NullPointerException(); } this.bayesPm = bayesPm; this.node = node; setNumCategories(numCategories()); } public void setNumCategories(int numCategories) { removeAll(); JComponent categoryFieldsPanel = createCategoryFieldsPanel(numCategories); add(categoryFieldsPanel, BorderLayout.CENTER); revalidate(); repaint(); firePropertyChange("modelChanged", null, null); } public BayesPm getBayesPm() { return this.bayesPm; } private JComponent createCategoryFieldsPanel(int numCategories) { if (numCategories != this.bayesPm.getNumCategories(getNode())) { this.bayesPm.setNumCategories(getNode(), numCategories); } Box panel = Box.createVerticalBox(); createCategoryFields(); for (int i = 0; i < this.bayesPm.getNumCategories(getNode()); i++) { Box row = Box.createHorizontalBox(); row.add(Box.createRigidArea(new Dimension(10, 0))); row.add(new JLabel((i + 1) + ".")); row.add(Box.createRigidArea(new Dimension(4, 0))); row.add(this.categoryFields[i]); row.add(Box.createHorizontalGlue()); panel.add(row); } setLayout(new BorderLayout()); add(panel, BorderLayout.CENTER); setFocusTraversalPolicy(new FocusTraversalPolicy() { @Override public Component getComponentAfter(Container focusCycleRoot, Component aComponent) { int index = CategoryEditor.this.focusTraveralOrder.indexOf(aComponent); int size = CategoryEditor.this.focusTraveralOrder.size(); if (index != -1) { return (Component) CategoryEditor.this.focusTraveralOrder.get( (index + 1) % size); } else { return getFirstComponent(focusCycleRoot); } } @Override public Component getComponentBefore(Container focusCycleRoot, Component aComponent) { int index = CategoryEditor.this.focusTraveralOrder.indexOf(aComponent); int size = CategoryEditor.this.focusTraveralOrder.size(); if (index != -1) { return (Component) CategoryEditor.this.focusTraveralOrder.get( (index - 1) % size); } else { return getFirstComponent(focusCycleRoot); } } @Override public Component getFirstComponent(Container focusCycleRoot) { return (Component) CategoryEditor.this.focusTraveralOrder.getFirst(); } @Override public Component getLastComponent(Container focusCycleRoot) { return (Component) CategoryEditor.this.focusTraveralOrder.getLast(); } @Override public Component getDefaultComponent(Container focusCycleRoot) { return getFirstComponent(focusCycleRoot); } }); setFocusCycleRoot(true); return panel; } @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); for (StringTextField field : this.categoryFields) { field.setEnabled(enabled); } } private void createCategoryFields() { this.categoryFields = new StringTextField[numCategories()]; for (int i = 0; i < numCategories(); i++) { this.categoryFields[i] = new StringTextField(category(i), 10); StringTextField _field = this.categoryFields[i]; this.categoryFields[i].setFilter((String value, String oldValue) -> { if (BayesPmEditorWizard.this.labels.get(_field) != null) { int index = BayesPmEditorWizard.this.labels.get(_field); if (value == null) { value = category(index); } for (int j = 0; j < numCategories(); j++) { if (j != index && category(j).equals(value)) { value = category(index); } } setCategory(index, value); } return value; }); BayesPmEditorWizard.this.labels.put(this.categoryFields[i], i); this.focusTraveralOrder.add(this.categoryFields[i]); } } private int numCategories() { return this.bayesPm.getNumCategories(getNode()); } private String category(int index) { return this.bayesPm.getCategory(getNode(), index); } private void setCategory(int index, String value) { DiscreteVariable variable = (DiscreteVariable) this.bayesPm.getVariable(getNode()); List categories = new ArrayList<>(variable.getCategories()); categories.set(index, value); this.bayesPm.setCategories(this.node, categories); firePropertyChange("modelChanged", null, null); } public void setCategories(List categories) { if (categories == null) { throw new NullPointerException(); } if (categories.size() < 2) { throw new IllegalArgumentException( "Number of categories must be" + " >= 2: " + categories.size()); } for (Object category : categories) { if (category == null) { throw new NullPointerException(); } } removeAll(); JComponent categoryFieldsPanel = createCategoryFieldsPanel(categories.size()); for (int i = 0; i < categories.size(); i++) { this.categoryFields[i].setValue((String) categories.get(i)); } add(categoryFieldsPanel, BorderLayout.CENTER); revalidate(); repaint(); } public Node getNode() { return this.node; } public void setNode(Node node) { for (int i = 0; i < numCategories(); i++) { this.categoryFields[i].setValue(this.categoryFields[i].getText()); } this.node = node; setNumCategories(this.bayesPm.getNumCategories(node)); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy