Java tutorial
/** * Copyright (C) 2001-2015 by RapidMiner and the contributors * * Complete list of developers available at our web site: * * http://rapidminer.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see http://www.gnu.org/licenses/. */ package com.rapidminer.gui.viewer.metadata; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.CardLayout; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.GradientPaint; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.RenderingHints; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.font.TextAttribute; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import org.jfree.chart.ChartPanel; import com.rapidminer.example.Attribute; import com.rapidminer.example.Attributes; import com.rapidminer.gui.tools.AttributeGuiTools; import com.rapidminer.gui.tools.SwingTools; import com.rapidminer.gui.viewer.metadata.actions.AttributePopupMenu; import com.rapidminer.gui.viewer.metadata.actions.CopyAttributeNameAction; import com.rapidminer.gui.viewer.metadata.actions.CopyDateTimeFromValueAction; import com.rapidminer.gui.viewer.metadata.actions.CopyDateTimeUntilValueAction; import com.rapidminer.gui.viewer.metadata.actions.CopyNumAvgValueAction; import com.rapidminer.gui.viewer.metadata.actions.CopyNumDeviationValueAction; import com.rapidminer.gui.viewer.metadata.actions.CopyNumMaximumValueAction; import com.rapidminer.gui.viewer.metadata.actions.CopyNumMinimumValueAction; import com.rapidminer.gui.viewer.metadata.actions.OpenChartAction; import com.rapidminer.gui.viewer.metadata.actions.ShowNomValueAction; import com.rapidminer.gui.viewer.metadata.event.AttributeStatisticsEvent; import com.rapidminer.gui.viewer.metadata.event.AttributeStatisticsEventListener; import com.rapidminer.gui.viewer.metadata.model.AbstractAttributeStatisticsModel; import com.rapidminer.gui.viewer.metadata.model.DateTimeAttributeStatisticsModel; import com.rapidminer.gui.viewer.metadata.model.NominalAttributeStatisticsModel; import com.rapidminer.gui.viewer.metadata.model.NumericalAttributeStatisticsModel; import com.rapidminer.tools.I18N; import com.rapidminer.tools.Ontology; import com.rapidminer.tools.Tools; import com.rapidminer.tools.container.ValueAndCount; /** * GUI display for meta data statistics of an {@link Attribute} backed by an * {@link AbstractAttributeStatisticsModel}. * <p> * The reason for this being just one class instead of 3 classes extending an abstract one is * simple: performance. Removing/adding these panels from/to the {@link MetaDataStatisticsViewer} * whenever sorting changes is very costly and takes too much time. Switching the models (and thus * basically only updating {@link JLabel}s) is much quicker, however we cannot switch the model * between numerical/nominal/date_time attributes when there is not one universal GUI for all 3 * different attribute value types. * * @author Marco Boeck * */ public class AttributeStatisticsPanel extends JPanel { private static final long serialVersionUID = 1L; /** the font size of header labels */ private static final float FONT_SIZE_LABEL_HEADER = 10; /** the font size of value labels */ private static final float FONT_SIZE_LABEL_VALUE = 14; /** the arc width of the corner of this panel */ private static final int CORNER_ARC_WIDTH = 12; /** the identifier for custom special attributes */ public static final String GENERIC_SPECIAL_NAME = "special"; /** the first gradient border color */ private static final Color COLOR_BORDER_GRADIENT_FIRST = new Color(125, 125, 125); /** the second gradient border color */ private static final Color COLOR_BORDER_GRADIENT_SECOND = new Color(100, 100, 100); /** the dimension for the attribute name label */ private static final Dimension DIMENSION_LABEL_ATTRIBUTE = new Dimension(230, 20); /** the dimension for the attribute type label */ private static final Dimension DIMENSION_LABEL_TYPE = new Dimension(90, 20); /** the dimension for the attribute missings label */ private static final Dimension DIMENSION_LABEL_MISSINGS = new Dimension(75, 20); /** the dimension for the attribute construction label */ private static final Dimension DIMENSION_LABEL_CONSTRUCTION = new Dimension(75, 20); /** the dimension for chart panels in an enlarged state */ private static final Dimension DIMENSION_CHART_PANEL_ENLARGED = new Dimension(200, 100); /** preferred size of a date_time panel so each column is aligned vertically */ private static final Dimension DIMENSION_PANEL_DATE_PREF_SIZE = new Dimension(165, 35); /** preferred size of a numerical panel so each column is aligned vertically */ private static final Dimension DIMENSION_PANEL_NUMERIC_PREF_SIZE = new Dimension(110, 35); /** preferred size of a numerical panel so each column is aligned vertically */ private static final Dimension DIMENSION_PANEL_NOMINAL_PREF_SIZE = new Dimension(165, 35); /** a transparent color */ private static final Color COLOR_TRANSPARENT = new Color(255, 255, 255, 0); /** {@value} */ private static final String LABEL_DOTS = "..."; /** used to open an histogram chart for an attribute */ private static final OpenChartAction OPEN_CHART_ACTION = new OpenChartAction(); /** the identifier for the numerical card */ private static final String CARD_NUMERICAL = "numericalCard"; /** the identifier for the nominal card */ private static final String CARD_NOMINAL = "nominalCard"; /** the identifier for the date_time card */ private static final String CARD_DATE_TIME = "dateTimeCard"; /** the popup shown on {@link AttributeStatisticsPanel} for numerical attributes */ private static JPopupMenu popupAttributeNumericalStatPanel; /** the popup shown on {@link AttributeStatisticsPanel} for nominal attributes */ private static JPopupMenu popupAttributeNominalStatPanel; /** the popup shown on {@link AttributeStatisticsPanel} for date_time attributes */ private static JPopupMenu popupAttributeDateTimeStatPanel; static { // populate global popup menus (used to reduce performance issues so not PAGE_SIZE * 3 popup // menues are created) popupAttributeNumericalStatPanel = new AttributePopupMenu(); popupAttributeNumericalStatPanel.add(new CopyAttributeNameAction()); popupAttributeNumericalStatPanel.add(new CopyNumAvgValueAction()); popupAttributeNumericalStatPanel.add(new CopyNumDeviationValueAction()); popupAttributeNumericalStatPanel.add(new CopyNumMinimumValueAction()); popupAttributeNumericalStatPanel.add(new CopyNumMaximumValueAction()); popupAttributeNumericalStatPanel.addSeparator(); popupAttributeNumericalStatPanel.add(OPEN_CHART_ACTION); popupAttributeNominalStatPanel = new AttributePopupMenu(); popupAttributeNominalStatPanel.add(new CopyAttributeNameAction()); popupAttributeNominalStatPanel.addSeparator(); popupAttributeNominalStatPanel.add(new ShowNomValueAction(null)); popupAttributeNominalStatPanel.add(OPEN_CHART_ACTION); popupAttributeDateTimeStatPanel = new AttributePopupMenu(); popupAttributeDateTimeStatPanel.add(new CopyAttributeNameAction()); popupAttributeDateTimeStatPanel.add(new CopyDateTimeFromValueAction()); popupAttributeDateTimeStatPanel.add(new CopyDateTimeUntilValueAction()); // popupAttributeDateTimeStatPanel.addSeparator(); popupAttributeDateTimeStatPanel.add(OPEN_CHART_ACTION); } /** true if the mouse is hovering over this panel */ private boolean hovered; /** the mouse listener for this panel to notice hovering/enlarge/shrink/popup requests */ private MouseAdapter enlargeAndHoverAndPopupMouseAdapter; /** list containing all panels which display a chart */ protected List<JPanel> listOfChartPanels; /** label displaying the attribute header (special role) if it has one */ private JLabel labelAttHeader; /** label displaying the attribute name */ private JLabel labelAttName; /** panel in which name and role reside */ private JPanel panelAttName; /** label displaying the attribute type */ private JLabel labelAttType; /** label displaying the minimum value */ private JLabel labelStatsMin; /** label displaying the maximum value */ private JLabel labelStatsMax; /** label displaying the average value */ private JLabel labelStatsAvg; /** label displaying the deviation */ private JLabel labelStatsDeviation; /** label displaying the missing value count */ private JLabel labelStatsMissing; /** the panel containing the attribute construction label */ private JPanel panelStatsConstruction; /** label displaying the attribute construction value */ private JLabel labelStatsConstruction; /** label displaying the least occuring nominal value */ private JLabel labelStatsLeast; /** label displaying the most occuring nominal value */ private JLabel labelStatsMost; /** label displaying the nominal values */ private JLabel labelStatsValues; /** the details button for nominal values to open the nominal value details dialog */ private JButton detailsButton; /** label displaying the duration value */ private JLabel labelStatsDuration; /** label displaying the from value */ private JLabel labelStatsFrom; /** label displaying the until value */ private JLabel labelStatsUntil; /** the panel which contains the cards for the different types */ private JPanel cardStatsPanel; /** the card layout used to switch between stat panels */ private CardLayout cardLayout; /** the {@link AbstractAttributeStatisticsModel} backing the GUI */ private AbstractAttributeStatisticsModel model; /** the listener which listens on the model for changes */ private AttributeStatisticsEventListener listener; /** * Creates a new {@link AttributeStatisticsPanel} instance. Before displaying the panel, an * {@link AbstractAttributeStatisticsModel} should be set via * {@link #setModel(AbstractAttributeStatisticsModel, boolean)}. * */ public AttributeStatisticsPanel() { listOfChartPanels = new LinkedList<>(); // create listener which listens for hovering/enlarge mouse events on this panel enlargeAndHoverAndPopupMouseAdapter = new MouseAdapter() { @Override public void mousePressed(final MouseEvent e) { // only popup trigger for popup menu if (e.isPopupTrigger()) { handlePopup(e); } // only left mouse button to enlarge if (!SwingUtilities.isLeftMouseButton(e)) { return; } // little hack so hovering over the details button does not remove the hover effect // (because MouseExited is called) // but clicking the button is still possible and does not enlarge the panel if (e.getSource() instanceof JButton) { ((JButton) e.getSource()).doClick(); return; } // change enlarged status if (getModel() != null) { getModel().setEnlarged(!getModel().isEnlarged()); } } @Override public void mouseExited(final MouseEvent e) { if (SwingTools.isMouseEventExitedToChildComponents(AttributeStatisticsPanel.this, e)) { // we are still hovering over the ASP, just a child component return; } hovered = false; repaint(); } @Override public void mouseEntered(final MouseEvent e) { hovered = true; repaint(); } @Override public void mouseReleased(final MouseEvent e) { if (e.isPopupTrigger()) { handlePopup(e); } } /** * Handles the popup click event. * * @param e */ private void handlePopup(final MouseEvent e) { if (model.getAttribute().isNumerical()) { popupAttributeNumericalStatPanel.show(e.getComponent(), e.getX(), e.getY()); } else if (model.getAttribute().isNominal()) { popupAttributeNominalStatPanel.show(e.getComponent(), e.getX(), e.getY()); } else if (model.getAttribute().isDateTime()) { popupAttributeDateTimeStatPanel.show(e.getComponent(), e.getX(), e.getY()); } } }; // create listener which listens for AttributeStatisticsEvents on the model listener = new AttributeStatisticsEventListener() { @Override public void modelChanged(final AttributeStatisticsEvent e) { switch (e.getEventType()) { case ALTERNATING_CHANGED: repaint(); break; case ENLARGED_CHANGED: updateCharts(); updateVisibilityOfChartPanels(); if (AttributeStatisticsPanel.this.model.getAttribute().isNominal()) { displayNominalValues(); } break; case SHOW_CONSTRUCTION_CHANGED: panelStatsConstruction.setVisible(model.isShowConstruction()); break; case STATISTICS_CHANGED: SwingUtilities.invokeLater(new Runnable() { @Override public void run() { AbstractAttributeStatisticsModel model = AttributeStatisticsPanel.this.model; if (model.getAttribute().isNumerical()) { updateNumericalElements(model); } else if (model.getAttribute().isNominal()) { updateNominalElements(model); } else { updateDateTimeElements(model); } } }); break; default: } } }; initGUI(); } /** * Initializes the GUI. * */ @SuppressWarnings({ "unchecked", "rawtypes" }) private void initGUI() { setLayout(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); // add attribute name panelAttName = new JPanel(); panelAttName.setLayout(new BoxLayout(panelAttName, BoxLayout.PAGE_AXIS)); panelAttName.setOpaque(false); // this border is to visualize that the name column can be enlarged/shrinked panelAttName.setBorder(BorderFactory.createMatteBorder(0, 0, 0, 1, Color.LIGHT_GRAY)); labelAttHeader = new JLabel(LABEL_DOTS); labelAttHeader.setFont(labelAttHeader.getFont().deriveFont(FONT_SIZE_LABEL_HEADER)); labelAttHeader.setForeground(Color.GRAY); panelAttName.add(labelAttHeader); labelAttName = new JLabel(LABEL_DOTS); labelAttName.setFont(labelAttName.getFont().deriveFont(Font.BOLD, FONT_SIZE_LABEL_VALUE)); labelAttName.setMinimumSize(DIMENSION_LABEL_ATTRIBUTE); labelAttName.setPreferredSize(DIMENSION_LABEL_ATTRIBUTE); panelAttName.add(labelAttName); gbc.gridx = 0; gbc.gridy = 0; gbc.insets = new Insets(3, 20, 3, 10); gbc.anchor = GridBagConstraints.CENTER; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 0.0; gbc.weighty = 1.0; gbc.gridheight = 2; add(panelAttName, gbc); // create value type name and bring it to a nice to read format (aka uppercase first letter // and replace '_' with ' ' gbc.gridx += 1; gbc.insets = new Insets(5, 15, 5, 10); labelAttType = new JLabel(LABEL_DOTS); labelAttType.setMinimumSize(DIMENSION_LABEL_TYPE); labelAttType.setPreferredSize(DIMENSION_LABEL_TYPE); add(labelAttType, gbc); // missings panel JPanel panelStatsMissing = new JPanel(); panelStatsMissing.setLayout(new BoxLayout(panelStatsMissing, BoxLayout.PAGE_AXIS)); panelStatsMissing.setOpaque(false); labelStatsMissing = new JLabel(LABEL_DOTS); labelStatsMissing.setMinimumSize(DIMENSION_LABEL_MISSINGS); labelStatsMissing.setPreferredSize(DIMENSION_LABEL_MISSINGS); panelStatsMissing.add(labelStatsMissing); gbc.gridx += 1; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0.0; add(panelStatsMissing, gbc); // chart panel(s) (only visible when enlarged) JPanel chartPanel = new JPanel(new BorderLayout()); chartPanel.setBackground(COLOR_TRANSPARENT); chartPanel.setOpaque(false); listOfChartPanels.add(chartPanel); updateVisibilityOfChartPanels(); gbc.fill = GridBagConstraints.NONE; gbc.weighty = 0.0; gbc.insets = new Insets(0, 10, 0, 10); for (JPanel panel : listOfChartPanels) { gbc.gridx += 1; add(panel, gbc); } // (hidden) construction panel String constructionLabel = I18N.getMessage(I18N.getGUIBundle(), "gui.label.attribute_statistics.statistics.construction.label"); panelStatsConstruction = new JPanel(); panelStatsConstruction.setLayout(new BoxLayout(panelStatsConstruction, BoxLayout.PAGE_AXIS)); panelStatsConstruction.setOpaque(false); panelStatsConstruction.setVisible(false); JLabel labelConstructionHeader = new JLabel(constructionLabel); labelConstructionHeader.setFont(labelConstructionHeader.getFont().deriveFont(FONT_SIZE_LABEL_HEADER)); labelConstructionHeader.setForeground(Color.GRAY); panelStatsConstruction.add(labelConstructionHeader); labelStatsConstruction = new JLabel(LABEL_DOTS); labelStatsConstruction.setFont(labelStatsConstruction.getFont().deriveFont(FONT_SIZE_LABEL_VALUE)); labelStatsConstruction.setMinimumSize(DIMENSION_LABEL_CONSTRUCTION); labelStatsConstruction.setPreferredSize(DIMENSION_LABEL_CONSTRUCTION); panelStatsConstruction.add(labelStatsConstruction); gbc.gridx += 1; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0.0; add(panelStatsConstruction, gbc); // statistics panel, contains different statistics panels for numerical/nominal/date_time on // a card layout // needed to switch between for model swapping cardStatsPanel = new JPanel(); cardStatsPanel.setOpaque(false); cardLayout = new CardLayout(); cardStatsPanel.setLayout(cardLayout); // numerical version JPanel statsNumPanel = new JPanel(); GridBagLayout layout = new GridBagLayout(); GridBagConstraints gbcStatPanel = new GridBagConstraints(); statsNumPanel.setLayout(layout); statsNumPanel.setOpaque(false); String avgLabel = I18N.getMessage(I18N.getGUIBundle(), "gui.label.attribute_statistics.statistics.avg.label"); String devianceLabel = I18N.getMessage(I18N.getGUIBundle(), "gui.label.attribute_statistics.statistics.variance.label"); String minLabel = I18N.getMessage(I18N.getGUIBundle(), "gui.label.attribute_statistics.statistics.min.label"); String maxLabel = I18N.getMessage(I18N.getGUIBundle(), "gui.label.attribute_statistics.statistics.max.label"); // min value panel JPanel panelStatsMin = new JPanel(); panelStatsMin.setLayout(new BoxLayout(panelStatsMin, BoxLayout.PAGE_AXIS)); panelStatsMin.setOpaque(false); JLabel labelMinHeader = new JLabel(minLabel); labelMinHeader.setFont(labelMinHeader.getFont().deriveFont(FONT_SIZE_LABEL_HEADER)); labelMinHeader.setForeground(Color.GRAY); panelStatsMin.add(labelMinHeader); labelStatsMin = new JLabel(LABEL_DOTS); labelStatsMin.setFont(labelStatsMin.getFont().deriveFont(FONT_SIZE_LABEL_VALUE)); panelStatsMin.add(labelStatsMin); // max value panel JPanel panelStatsMax = new JPanel(); panelStatsMax.setLayout(new BoxLayout(panelStatsMax, BoxLayout.PAGE_AXIS)); panelStatsMax.setOpaque(false); JLabel labelMaxHeader = new JLabel(maxLabel); labelMaxHeader.setFont(labelMaxHeader.getFont().deriveFont(FONT_SIZE_LABEL_HEADER)); labelMaxHeader.setForeground(Color.GRAY); panelStatsMax.add(labelMaxHeader); labelStatsMax = new JLabel(LABEL_DOTS); labelStatsMax.setFont(labelStatsMax.getFont().deriveFont(FONT_SIZE_LABEL_VALUE)); panelStatsMax.add(labelStatsMax); // average value panel JPanel panelStatsAvg = new JPanel(); panelStatsAvg.setLayout(new BoxLayout(panelStatsAvg, BoxLayout.PAGE_AXIS)); panelStatsAvg.setOpaque(false); JLabel labelAvgHeader = new JLabel(avgLabel); labelAvgHeader.setFont(labelAvgHeader.getFont().deriveFont(FONT_SIZE_LABEL_HEADER)); labelAvgHeader.setForeground(Color.GRAY); panelStatsAvg.add(labelAvgHeader); labelStatsAvg = new JLabel(LABEL_DOTS); labelStatsAvg.setFont(labelStatsAvg.getFont().deriveFont(FONT_SIZE_LABEL_VALUE)); panelStatsAvg.add(labelStatsAvg); // deviance value panel JPanel panelStatsDeviance = new JPanel(); panelStatsDeviance.setLayout(new BoxLayout(panelStatsDeviance, BoxLayout.PAGE_AXIS)); panelStatsDeviance.setOpaque(false); JLabel labelDevianceHeader = new JLabel(devianceLabel); labelDevianceHeader.setFont(labelDevianceHeader.getFont().deriveFont(FONT_SIZE_LABEL_HEADER)); labelDevianceHeader.setForeground(Color.GRAY); panelStatsDeviance.add(labelDevianceHeader); labelStatsDeviation = new JLabel(LABEL_DOTS); labelStatsDeviation.setFont(labelStatsDeviation.getFont().deriveFont(FONT_SIZE_LABEL_VALUE)); panelStatsDeviance.add(labelStatsDeviation); // add sub panels to stats panel gbcStatPanel.gridx = 0; gbcStatPanel.weightx = 0.0; gbcStatPanel.fill = GridBagConstraints.NONE; gbcStatPanel.insets = new Insets(0, 0, 0, 4); panelStatsMin.setPreferredSize(DIMENSION_PANEL_NUMERIC_PREF_SIZE); statsNumPanel.add(panelStatsMin, gbcStatPanel); gbcStatPanel.gridx += 1; panelStatsMax.setPreferredSize(DIMENSION_PANEL_NUMERIC_PREF_SIZE); statsNumPanel.add(panelStatsMax, gbcStatPanel); gbcStatPanel.gridx += 1; panelStatsAvg.setPreferredSize(DIMENSION_PANEL_NUMERIC_PREF_SIZE); statsNumPanel.add(panelStatsAvg, gbcStatPanel); gbcStatPanel.gridx += 1; panelStatsDeviance.setPreferredSize(DIMENSION_PANEL_NUMERIC_PREF_SIZE); statsNumPanel.add(panelStatsDeviance, gbcStatPanel); gbcStatPanel.gridx += 1; gbcStatPanel.weightx = 1.0; gbcStatPanel.fill = GridBagConstraints.HORIZONTAL; statsNumPanel.add(new JLabel(), gbcStatPanel); cardStatsPanel.add(statsNumPanel, CARD_NUMERICAL); // nominal version JPanel statsNomPanel = new JPanel(); statsNomPanel.setLayout(layout); statsNomPanel.setOpaque(false); String leastLabel = I18N.getMessage(I18N.getGUIBundle(), "gui.label.attribute_statistics.statistics.least.label"); String mostLabel = I18N.getMessage(I18N.getGUIBundle(), "gui.label.attribute_statistics.statistics.most.label"); String valuesLabel = I18N.getMessage(I18N.getGUIBundle(), "gui.label.attribute_statistics.statistics.values.label"); // least panel JPanel panelStatsLeast = new JPanel(); panelStatsLeast.setLayout(new BoxLayout(panelStatsLeast, BoxLayout.PAGE_AXIS)); panelStatsLeast.setOpaque(false); JLabel labelLeastHeader = new JLabel(leastLabel); labelLeastHeader.setFont(labelLeastHeader.getFont().deriveFont(FONT_SIZE_LABEL_HEADER)); labelLeastHeader.setForeground(Color.GRAY); labelLeastHeader.setAlignmentX(Component.LEFT_ALIGNMENT); panelStatsLeast.add(labelLeastHeader); labelStatsLeast = new JLabel(LABEL_DOTS); labelStatsLeast.setFont(labelStatsLeast.getFont().deriveFont(FONT_SIZE_LABEL_VALUE)); panelStatsLeast.add(labelStatsLeast); // most panel JPanel panelStatsMost = new JPanel(); panelStatsMost.setLayout(new BoxLayout(panelStatsMost, BoxLayout.PAGE_AXIS)); panelStatsMost.setOpaque(false); JLabel labelMostHeader = new JLabel(mostLabel); labelMostHeader.setFont(labelMostHeader.getFont().deriveFont(FONT_SIZE_LABEL_HEADER)); labelMostHeader.setForeground(Color.GRAY); labelMostHeader.setAlignmentX(Component.LEFT_ALIGNMENT); panelStatsMost.add(labelMostHeader); labelStatsMost = new JLabel(LABEL_DOTS); labelStatsMost.setFont(labelStatsMost.getFont().deriveFont(FONT_SIZE_LABEL_VALUE)); panelStatsMost.add(labelStatsMost); // values panel JPanel panelStatsValues = new JPanel(); panelStatsValues.setLayout(new BoxLayout(panelStatsValues, BoxLayout.PAGE_AXIS)); panelStatsValues.setOpaque(false); JLabel labelValuesHeader = new JLabel(valuesLabel); labelValuesHeader.setFont(labelValuesHeader.getFont().deriveFont(FONT_SIZE_LABEL_HEADER)); labelValuesHeader.setForeground(Color.GRAY); labelValuesHeader.setAlignmentX(Component.LEFT_ALIGNMENT); panelStatsValues.add(labelValuesHeader); labelStatsValues = new JLabel(LABEL_DOTS); labelStatsValues.setFont(labelStatsValues.getFont().deriveFont(FONT_SIZE_LABEL_VALUE)); panelStatsValues.add(labelStatsValues); detailsButton = new JButton(new ShowNomValueAction(this)); detailsButton.setVisible(false); detailsButton.setOpaque(false); detailsButton.setContentAreaFilled(false); detailsButton.setBorderPainted(false); detailsButton.addMouseListener(enlargeAndHoverAndPopupMouseAdapter); detailsButton.setHorizontalAlignment(SwingConstants.LEFT); detailsButton.setHorizontalTextPosition(SwingConstants.LEFT); detailsButton.setIcon(null); Font font = detailsButton.getFont(); Map attributes = font.getAttributes(); attributes.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON); detailsButton.setFont(font.deriveFont(attributes)); panelStatsValues.add(detailsButton); // add sub panel to stats panel gbcStatPanel.gridx = 0; gbcStatPanel.weightx = 0.0; gbcStatPanel.fill = GridBagConstraints.NONE; gbcStatPanel.insets = new Insets(0, 0, 0, 6); panelStatsLeast.setPreferredSize(DIMENSION_PANEL_NOMINAL_PREF_SIZE); statsNomPanel.add(panelStatsLeast, gbcStatPanel); gbcStatPanel.gridx += 1; panelStatsMost.setPreferredSize(DIMENSION_PANEL_NOMINAL_PREF_SIZE); statsNomPanel.add(panelStatsMost, gbcStatPanel); gbcStatPanel.gridx += 1; statsNomPanel.add(panelStatsValues, gbcStatPanel); gbcStatPanel.gridx += 1; gbcStatPanel.weightx = 1.0; gbcStatPanel.fill = GridBagConstraints.HORIZONTAL; statsNomPanel.add(new JLabel(), gbcStatPanel); cardStatsPanel.add(statsNomPanel, CARD_NOMINAL); // date_time version JPanel statsDateTimePanel = new JPanel(); statsDateTimePanel.setLayout(layout); statsDateTimePanel.setOpaque(false); String durationLabel = I18N.getMessage(I18N.getGUIBundle(), "gui.label.attribute_statistics.statistics.duration.label"); String fromLabel = I18N.getMessage(I18N.getGUIBundle(), "gui.label.attribute_statistics.statistics.from.label"); String untilLabel = I18N.getMessage(I18N.getGUIBundle(), "gui.label.attribute_statistics.statistics.until.label"); // min value panel JPanel panelStatsFrom = new JPanel(); panelStatsFrom.setLayout(new BoxLayout(panelStatsFrom, BoxLayout.PAGE_AXIS)); panelStatsFrom.setOpaque(false); JLabel labelFromHeader = new JLabel(fromLabel); labelFromHeader.setFont(labelFromHeader.getFont().deriveFont(FONT_SIZE_LABEL_HEADER)); labelFromHeader.setForeground(Color.GRAY); panelStatsFrom.add(labelFromHeader); labelStatsFrom = new JLabel(LABEL_DOTS); labelStatsFrom.setFont(labelStatsFrom.getFont().deriveFont(FONT_SIZE_LABEL_VALUE)); panelStatsFrom.add(labelStatsFrom); // until value panel JPanel panelStatsUntil = new JPanel(); panelStatsUntil.setLayout(new BoxLayout(panelStatsUntil, BoxLayout.PAGE_AXIS)); panelStatsUntil.setOpaque(false); JLabel labelUntilHeader = new JLabel(untilLabel); labelUntilHeader.setFont(labelUntilHeader.getFont().deriveFont(FONT_SIZE_LABEL_HEADER)); labelUntilHeader.setForeground(Color.GRAY); panelStatsUntil.add(labelUntilHeader); labelStatsUntil = new JLabel(LABEL_DOTS); labelStatsUntil.setFont(labelStatsUntil.getFont().deriveFont(FONT_SIZE_LABEL_VALUE)); panelStatsUntil.add(labelStatsUntil); // duration value panel JPanel panelStatsDuration = new JPanel(); panelStatsDuration.setLayout(new BoxLayout(panelStatsDuration, BoxLayout.PAGE_AXIS)); panelStatsDuration.setOpaque(false); JLabel labelDurationHeader = new JLabel(durationLabel); labelDurationHeader.setFont(labelDurationHeader.getFont().deriveFont(FONT_SIZE_LABEL_HEADER)); labelDurationHeader.setForeground(Color.GRAY); panelStatsDuration.add(labelDurationHeader); labelStatsDuration = new JLabel(LABEL_DOTS); labelStatsDuration.setFont(labelStatsDuration.getFont().deriveFont(FONT_SIZE_LABEL_VALUE)); panelStatsDuration.add(labelStatsDuration); // add sub panels to stats panel gbcStatPanel.gridx = 0; gbcStatPanel.weightx = 0.0; gbcStatPanel.fill = GridBagConstraints.NONE; gbcStatPanel.insets = new Insets(0, 0, 0, 6); panelStatsFrom.setPreferredSize(DIMENSION_PANEL_DATE_PREF_SIZE); statsDateTimePanel.add(panelStatsFrom, gbcStatPanel); gbcStatPanel.gridx += 1; panelStatsUntil.setPreferredSize(DIMENSION_PANEL_DATE_PREF_SIZE); statsDateTimePanel.add(panelStatsUntil, gbcStatPanel); gbcStatPanel.gridx += 1; panelStatsDuration.setPreferredSize(DIMENSION_PANEL_DATE_PREF_SIZE); statsDateTimePanel.add(panelStatsDuration, gbcStatPanel); gbcStatPanel.gridx += 1; gbcStatPanel.weightx = 1.0; gbcStatPanel.fill = GridBagConstraints.HORIZONTAL; statsDateTimePanel.add(new JLabel(), gbcStatPanel); cardStatsPanel.add(statsDateTimePanel, CARD_DATE_TIME); // add stats panel to main gui gbc.gridx += 1; gbc.insets = new Insets(5, 10, 5, 10); gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0; gbc.weighty = 0.0; gbc.anchor = GridBagConstraints.WEST; add(cardStatsPanel, gbc); // needed so we can draw our own background setOpaque(false); // handle mouse events for hover effect and enlarging/shrinking addMouseListener(enlargeAndHoverAndPopupMouseAdapter); // change cursor to indicate this component can be clicked setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); } /** * Returns the {@link AbstractAttributeStatisticsModel} used by this instance. For numerical * attributes it is a {@link NumericalAttributeStatisticsModel}, for nominal attributes it is a * {@link NominalAttributeStatisticsModel}, and for date_time attributes it is a * {@link DateTimeAttributeStatisticsModel}. * * @return the model or <code>null</code> if called before * {@link #setModel(AbstractAttributeStatisticsModel, boolean)} has been called */ public AbstractAttributeStatisticsModel getModel() { return model; } /** * Set the new {@link AbstractAttributeStatisticsModel}. The model must be an implementation of * {@link AbstractAttributeStatisticsModel} which matches the {@link Attribute} value type. In * other words, a numerical attribute must have a {@link NumericalAttributeStatisticsModel}, a * nominal attribute must have a {@link NominalAttributeStatisticsModel}, and a date_time * attribute must have a {@link DateTimeAttributeStatisticsModel}. * * @param model * @param updateGUI * if <code>true</code>, will update the GUI * @throws IllegalArgumentException * if the model is of the wrong type */ public void setModel(final AbstractAttributeStatisticsModel model, final boolean updateGUI) throws IllegalArgumentException { // make sure model is of correct type if (model.getAttribute().isNumerical()) { if (!(model instanceof NumericalAttributeStatisticsModel)) { throw new IllegalArgumentException( "model must be of type " + NumericalAttributeStatisticsModel.class.getName()); } } else if (model.getAttribute().isNominal()) { if (!(model instanceof NominalAttributeStatisticsModel)) { throw new IllegalArgumentException( "model must be of type " + NominalAttributeStatisticsModel.class.getName()); } } else { if (!(model instanceof DateTimeAttributeStatisticsModel)) { throw new IllegalArgumentException( "model must be of type " + DateTimeAttributeStatisticsModel.class.getName()); } } // switch listener if (this.model != null) { this.model.removeEventListener(listener); } this.model = model; this.model.registerEventListener(listener); // only update GUI if requested if (updateGUI) { updateGenericElements(model); if (model.getAttribute().isNumerical()) { updateNumericalElements(model); cardLayout.show(cardStatsPanel, CARD_NUMERICAL); } else if (model.getAttribute().isNominal()) { updateNominalElements(model); cardLayout.show(cardStatsPanel, CARD_NOMINAL); } else { updateDateTimeElements(model); cardLayout.show(cardStatsPanel, CARD_DATE_TIME); } // make sure to show appropriate charts/data when switching model if (model.isEnlarged()) { updateCharts(); } updateVisibilityOfChartPanels(); if (model.getAttribute().isNominal()) { displayNominalValues(); } } } /** * Adds a {@link MouseListener} for this component. Makes sure it works on resized components as * well as charts. */ @Override public void addMouseListener(final MouseListener listener) { super.addMouseListener(listener); // needed because we set the size of them/added tooltips so they intercept events now labelAttName.addMouseListener(listener); labelStatsMissing.addMouseListener(listener); labelStatsConstruction.addMouseListener(listener); labelStatsMin.addMouseListener(listener); labelStatsMax.addMouseListener(listener); labelStatsAvg.addMouseListener(listener); labelStatsDeviation.addMouseListener(listener); labelStatsLeast.addMouseListener(listener); labelStatsMost.addMouseListener(listener); labelStatsValues.addMouseListener(listener); labelStatsDuration.addMouseListener(listener); labelStatsFrom.addMouseListener(listener); labelStatsUntil.addMouseListener(listener); } /** * Removes a {@link MouseListener} from this component. Makes sure it is removed from resized * components as well as from charts. */ @Override public void removeMouseListener(final MouseListener listener) { super.removeMouseListener(listener); // needed because we set the size of them/added tooltips so they intercept events now labelAttName.removeMouseListener(listener); labelStatsMissing.removeMouseListener(listener); labelStatsConstruction.removeMouseListener(listener); labelStatsMin.removeMouseListener(listener); labelStatsMax.removeMouseListener(listener); labelStatsAvg.removeMouseListener(listener); labelStatsDeviation.removeMouseListener(listener); labelStatsLeast.removeMouseListener(listener); labelStatsMost.removeMouseListener(listener); labelStatsValues.removeMouseListener(listener); labelStatsDuration.removeMouseListener(listener); labelStatsFrom.removeMouseListener(listener); labelStatsUntil.removeMouseListener(listener); } @Override public void paintComponent(final Graphics g) { Graphics2D g2 = (Graphics2D) g; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); int x = 0; int y = 0; int width = (int) getSize().getWidth(); int height = (int) getSize().getHeight(); // draw background depending special roles and hovering if (getModel() != null && getModel().isSpecialAtt()) { if (Attributes.LABEL_NAME.equals(getModel().getSpecialAttName())) { // label special attributes have a distinct color g2.setPaint( new GradientPaint(x, y, getColorForSpecialAttribute(Attributes.LABEL_NAME, true, hovered), x, height, getColorForSpecialAttribute(Attributes.LABEL_NAME, false, hovered))); } else if (Attributes.WEIGHT_NAME.equals(getModel().getSpecialAttName())) { // weight special attributes have another distinct color g2.setPaint( new GradientPaint(x, y, getColorForSpecialAttribute(Attributes.WEIGHT_NAME, true, hovered), x, height, getColorForSpecialAttribute(Attributes.WEIGHT_NAME, false, hovered))); } else if (Attributes.PREDICTION_NAME.equals(getModel().getSpecialAttName())) { // prediction special attributes have another distinct color g2.setPaint(new GradientPaint(x, y, getColorForSpecialAttribute(Attributes.PREDICTION_NAME, true, hovered), x, height, getColorForSpecialAttribute(Attributes.PREDICTION_NAME, false, hovered))); } else if (getModel().getSpecialAttName().startsWith(Attributes.CONFIDENCE_NAME + "_")) { // confidence special attributes have another distinct color g2.setPaint(new GradientPaint(x, y, getColorForSpecialAttribute(Attributes.CONFIDENCE_NAME, true, hovered), x, height, getColorForSpecialAttribute(Attributes.CONFIDENCE_NAME, false, hovered))); } else if (Attributes.ID_NAME.equals(getModel().getSpecialAttName())) { // id special attributes have another distinct color g2.setPaint(new GradientPaint(x, y, getColorForSpecialAttribute(Attributes.ID_NAME, true, hovered), x, height, getColorForSpecialAttribute(Attributes.ID_NAME, false, hovered))); } else { // other special attributes have another color g2.setPaint( new GradientPaint(x, y, getColorForSpecialAttribute(GENERIC_SPECIAL_NAME, true, hovered), x, height, getColorForSpecialAttribute(GENERIC_SPECIAL_NAME, false, hovered))); } } else { // regular attributes g2.setPaint( new GradientPaint(x, y, getColorForSpecialAttribute(Attributes.ATTRIBUTE_NAME, true, hovered), x, height, getColorForSpecialAttribute(Attributes.ATTRIBUTE_NAME, false, hovered))); } g2.fillRoundRect(x, y, width, height, CORNER_ARC_WIDTH, CORNER_ARC_WIDTH); // draw border g2.setPaint(new GradientPaint(x, y, COLOR_BORDER_GRADIENT_FIRST, x, height, COLOR_BORDER_GRADIENT_SECOND)); if (hovered) { g2.setStroke(new BasicStroke(1.0f)); } else { g2.setStroke(new BasicStroke(0.5f)); } g2.drawRoundRect(x, y, width - 1, height - 1, CORNER_ARC_WIDTH, CORNER_ARC_WIDTH); // let Swing draw its components super.paintComponent(g2); } /** * Updates chart panel visibility depending on enlarged status. */ private void updateVisibilityOfChartPanels() { for (JPanel chartPanel : listOfChartPanels) { if (getModel() != null && getModel().isEnlarged()) { chartPanel.setVisible(true); } else { chartPanel.setVisible(false); } } } /** * Updates the charts. */ @SuppressWarnings({ "unchecked", "rawtypes" }) private void updateCharts() { for (int i = 0; i < listOfChartPanels.size(); i++) { JPanel panel = listOfChartPanels.get(i); panel.removeAll(); final ChartPanel chartPanel = new ChartPanel(getModel().getChartOrNull(i)) { private static final long serialVersionUID = -6953213567063104487L; @Override public Dimension getPreferredSize() { return DIMENSION_CHART_PANEL_ENLARGED; } }; chartPanel.setPopupMenu(null); chartPanel.setBackground(COLOR_TRANSPARENT); chartPanel.setOpaque(false); chartPanel.addMouseListener(enlargeAndHoverAndPopupMouseAdapter); panel.add(chartPanel, BorderLayout.CENTER); JPanel openChartPanel = new JPanel(new GridBagLayout()); openChartPanel.setOpaque(false); GridBagConstraints gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.CENTER; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 1.0; gbc.weighty = 1.0; JButton openChartButton = new JButton(OPEN_CHART_ACTION); openChartButton.setOpaque(false); openChartButton.setContentAreaFilled(false); openChartButton.setBorderPainted(false); openChartButton.addMouseListener(enlargeAndHoverAndPopupMouseAdapter); openChartButton.setHorizontalAlignment(SwingConstants.LEFT); openChartButton.setHorizontalTextPosition(SwingConstants.LEFT); openChartButton.setIcon(null); Font font = openChartButton.getFont(); Map attributes = font.getAttributes(); attributes.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON); openChartButton.setFont(font.deriveFont(attributes).deriveFont(10.0f)); openChartPanel.add(openChartButton, gbc); panel.add(openChartPanel, BorderLayout.SOUTH); panel.revalidate(); panel.repaint(); } } /** * Displays the nominal values according to the current settings. */ private void displayNominalValues() { List<ValueAndCount> nominalValues = ((NominalAttributeStatisticsModel) model).getNominalValuesAndCount(); String valueString = ""; // we can render enlarged panels with HTML to format things nicely, // unless someone expands thousands of attributes this will not impact // performance in a noticeable way if (getModel() != null && getModel().isEnlarged()) { StringBuilder builderHTML = new StringBuilder(); builderHTML.append("<html>"); int maxDisplayValues = NominalAttributeStatisticsModel.DEFAULT_MAX_DISPLAYED_VALUES_ENLARGED; Iterator<ValueAndCount> it = new LinkedList<>(nominalValues).iterator(); int n = 0; while (it.hasNext() && n < maxDisplayValues) { if (n % (NominalAttributeStatisticsModel.DEFAULT_MAX_DISPLAYED_VALUES_ENLARGED / 2) == 0) { builderHTML.append("<br>"); } ValueAndCount value = it.next(); String tmpString = value.getValue(); String valueStringTruncated = SwingTools.getShortenedDisplayName(tmpString, 17); builderHTML.append(valueStringTruncated + " (" + value.getCount() + ")"); // add separator if not first or last value if (n < Math.min(nominalValues.size() - 1, maxDisplayValues)) { builderHTML.append(", "); } n++; } // count how many we could not display int omittedCount = 0; while (it.hasNext()) { it.next(); omittedCount++; } if (omittedCount > 0) { builderHTML.append("<br>"); builderHTML.append(LABEL_DOTS + "[" + omittedCount + " " + I18N.getMessage(I18N.getGUIBundle(), "gui.label.attribute_statistics.statistics.values_more") + "]"); } builderHTML.append("</html>"); valueString = builderHTML.toString(); } else { StringBuilder builderDefault = new StringBuilder(); int maxDisplayValues = NominalAttributeStatisticsModel.DEFAULT_MAX_DISPLAYED_VALUES_ENLARGED / 2; Iterator<ValueAndCount> it = new LinkedList<>(nominalValues).iterator(); int n = 0; while (it.hasNext() && n < maxDisplayValues) { ValueAndCount value = it.next(); String tmpString = value.getValue(); String valueStringTruncated = SwingTools.getShortenedDisplayName(tmpString, 17); builderDefault.append(valueStringTruncated + " (" + value.getCount() + ")"); // add separator if not first or last value if (n < Math.min(nominalValues.size() - 1, maxDisplayValues)) { builderDefault.append(", "); } n++; } // count how many we could not display int omittedCount = 0; while (it.hasNext()) { it.next(); omittedCount++; } if (omittedCount > 0) { builderDefault.append(LABEL_DOTS + "[" + omittedCount + " " + I18N.getMessage(I18N.getGUIBundle(), "gui.label.attribute_statistics.statistics.values_more") + "]"); } valueString = builderDefault.toString(); } detailsButton.setVisible(getModel() != null && getModel().isEnlarged()); labelStatsValues.setText(valueString); labelStatsValues.setToolTipText(valueString); labelStatsValues.setVisible(true); } /** * Updates the generic gui elements. * * @param model */ private void updateGenericElements(final AbstractAttributeStatisticsModel model) { String attLabel = model.getAttribute().getName(); String attRole = model.getExampleSetOrNull().getAttributes().getRole(model.getAttribute()).getSpecialName(); String valueTypeString = Ontology.ATTRIBUTE_VALUE_TYPE .mapIndexToDisplayName(model.getAttribute().getValueType()); String construction = model.getConstruction(); construction = construction == null ? "-" : construction; labelAttHeader.setText(attRole); labelAttName.setText(attLabel); labelAttName.setToolTipText(attLabel); labelAttType.setText(valueTypeString); labelAttType.setIcon(AttributeGuiTools.getIconForValueType(model.getAttribute().getValueType(), true)); labelStatsMissing.setText(Tools.formatIntegerIfPossible(model.getNumberOfMissingValues(), 0)); labelStatsMissing.setToolTipText(labelStatsMissing.getText()); labelStatsConstruction.setText(construction); labelStatsConstruction.setToolTipText(labelStatsConstruction.getText()); } /** * Updates the gui elements for numerical stats. * * @param model */ private void updateNumericalElements(final AbstractAttributeStatisticsModel model) { labelStatsValues.setVisible(false); // because cardLayout dimensions are determined by all // cards, not only the visible one labelStatsMin .setText(Tools.formatIntegerIfPossible(((NumericalAttributeStatisticsModel) model).getMinimum())); labelStatsMin.setToolTipText(labelStatsMin.getText()); labelStatsMax .setText(Tools.formatIntegerIfPossible(((NumericalAttributeStatisticsModel) model).getMaximum())); labelStatsMax.setToolTipText(labelStatsMax.getText()); labelStatsAvg .setText(Tools.formatIntegerIfPossible(((NumericalAttributeStatisticsModel) model).getAverage())); labelStatsAvg.setToolTipText(labelStatsAvg.getText()); labelStatsDeviation .setText(Tools.formatIntegerIfPossible(((NumericalAttributeStatisticsModel) model).getDeviation())); labelStatsDeviation.setToolTipText(labelStatsDeviation.getText()); } /** * Updates the gui elements for nominal stats. * * @param model */ private void updateNominalElements(final AbstractAttributeStatisticsModel model) { String least = ((NominalAttributeStatisticsModel) model).getLeast(); String leastTruncated = SwingTools.getShortenedDisplayName(least, 17); String most = ((NominalAttributeStatisticsModel) model).getMost(); String mostTruncated = SwingTools.getShortenedDisplayName(most, 17); labelStatsLeast.setText(leastTruncated); labelStatsLeast.setToolTipText(least); labelStatsMost.setText(mostTruncated); labelStatsMost.setToolTipText(most); displayNominalValues(); } /** * Updates the gui elements for date_time stats. * * @param model */ private void updateDateTimeElements(final AbstractAttributeStatisticsModel model) { labelStatsValues.setVisible(false); // because cardLayout dimensions are determined by all // cards, not only the visible one labelStatsDuration.setText(((DateTimeAttributeStatisticsModel) model).getDuration()); labelStatsDuration.setToolTipText(labelStatsDuration.getText()); labelStatsFrom.setText(((DateTimeAttributeStatisticsModel) model).getFrom()); labelStatsFrom.setToolTipText(labelStatsFrom.getText()); labelStatsUntil.setText(((DateTimeAttributeStatisticsModel) model).getUntil()); labelStatsUntil.setToolTipText(labelStatsUntil.getText()); } /** * Returns the color for the specified special attribute {@link String}. See * {@link Attributes#KNOWN_ATTRIBUTE_TYPES}. * * @param specialAtt * @param gradientFirst * if <code>true</code>, will return the color, if <code>false</code> will darken it * @param hovered * different color shading to highlight hovering * @return */ private static Color getColorForSpecialAttribute(final String specialAtt, final boolean gradientFirst, final boolean hovered) { Color color = AttributeGuiTools.getColorForAttributeRole(specialAtt); // darken second gradient color so a 3D effect is created if (!gradientFirst) { color = SwingTools.darkenColor(color, 0.83f); } // highlight selected attribute with a 3D effect highlight if (hovered) { if (gradientFirst) { color = SwingTools.brightenColor(color); } else { color = SwingTools.darkenColor(color); } } return color; } /** * Sets the minimum and preferred size of the name column. Also revalidates and repaints the * panel. If not yet initialized, does nothing. * * @param nameDim * the new minimum size. If <code>null</code>, resets to the default. */ public void updateNameColumnWidth(Dimension nameDim) { if (labelAttName != null) { if (nameDim == null) { labelAttName.setMinimumSize(DIMENSION_LABEL_ATTRIBUTE); labelAttName.setPreferredSize(DIMENSION_LABEL_ATTRIBUTE); } else { // add 20px width to align columns better Dimension newDim = new Dimension(nameDim.width + 30, nameDim.height); labelAttName.setMinimumSize(newDim); labelAttName.setPreferredSize(newDim); } panelAttName.revalidate(); panelAttName.repaint(); } } }