Java tutorial
/** * Copyright (c) 2015, Intel Corporation * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Intel Corporation nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /******************************************************************************* * I N T E L C O R P O R A T I O N * * Functional Group: Fabric Viewer Application * * File Name: ComponentFactory.java * * Archive Source: $Source$ * * Archive Log: $Log$ * Archive Log: Revision 1.51 2015/09/14 16:06:59 jijunwan * Archive Log: PR 130229 - The text component of all editable combo boxes should provide validation of the input * Archive Log: - apply FormattedComboBoxEditor on ComponentFactory and PreferenceWizard * Archive Log: * Archive Log: Revision 1.50 2015/08/18 14:28:33 jijunwan * Archive Log: PR 130033 - Fix critical issues found by Klocwork or FindBugs * Archive Log: - DateFormat is not thread safe. Changed to create new DateFormat to avoid sharing it among different threads * Archive Log: * Archive Log: Revision 1.49 2015/08/17 18:53:36 jijunwan * Archive Log: PR 129983 - Need to change file header's copyright text to BSD license txt * Archive Log: - changed frontend files' headers * Archive Log: * Archive Log: Revision 1.48 2015/08/17 14:22:37 rjtierne * Archive Log: PR 128979 - SM Log display * Archive Log: This is the first version of the Log Viewer which displays select lines of text from the remote SM log file. Updates include searchable raw text from file, user-defined number of lines to display, refreshing end of file, and paging. This PR is now closed and further updates can be found by referencing PR 130011 - "Enhance SM Log Viewer to include Standard and Advanced requirements". * Archive Log: * Archive Log: Revision 1.47 2015/08/11 14:36:50 jijunwan * Archive Log: PR 129917 - No update on event statistics * Archive Log: - Apply event subscriber on HealthHistoryCard. It will update either by event or period updating. * Archive Log: - Improved Health Trend chart to draw current data shape * Archive Log: - Improved Health Trend view to show current value immediately * Archive Log: * Archive Log: Revision 1.46 2015/07/28 18:29:12 fisherma * Archive Log: PR 129219 - Admin page login dialog improvement * Archive Log: * Archive Log: Revision 1.45 2015/07/17 21:20:57 jijunwan * Archive Log: PR 129528 - input validation improvement * Archive Log: - improved CompomentFactory to create text field based on argument allowEmpty * Archive Log: - apply it on Log preference and subnet name on Setup Wizard to forbid empty string * Archive Log: - apply it on key file location on Setup Wizard to allow empty string * Archive Log: * Archive Log: Revision 1.44 2015/07/17 20:32:15 jijunwan * Archive Log: PR 129528 - input validation improvement * Archive Log: - change default number format to "###", so we need to support character ',' * Archive Log: * Archive Log: Revision 1.43 2015/07/16 21:22:53 jijunwan * Archive Log: PR 129528 - input validation improvement * Archive Log: - extended SafeTextField to apply rules in name check * Archive Log: - moved valid chars to UIConstants * Archive Log: - made FieldPair more generic and flexible * Archive Log: * Archive Log: Revision 1.42 2015/07/13 19:30:57 jijunwan * Archive Log: PR 129528 - input validation improvement * Archive Log: - In interactive console, the port number must be in range (0, 65535) and the password cannot be empty. * Archive Log: * Archive Log: Revision 1.41 2015/07/13 18:37:24 jijunwan * Archive Log: PR 129528 - input validation improvement * Archive Log: - updated generic classes to use the new text field * Archive Log: * Archive Log: Revision 1.40 2015/06/29 15:05:45 jypak * Archive Log: PR 129284 - Incorrect QSFP field name. * Archive Log: Field name fix has been implemented. Also, introduced a conversion to Date object to add flexibility to display date code. * Archive Log: * Archive Log: Revision 1.39 2015/06/25 22:23:54 jijunwan * Archive Log: PR 129216 - The labels on a TopN chart may occupy too much space * Archive Log: - change parameter to set the maximum label width to be half of the chart * Archive Log: * Archive Log: Revision 1.38 2015/06/25 20:51:59 jijunwan * Archive Log: Bug 126755 - Pin Board functionality is not working in FV * Archive Log: - improved to display title annotation properly the chart is very narrow * Archive Log: * Archive Log: Revision 1.37 2015/06/22 13:11:51 jypak * Archive Log: PR 128980 - Be able to search devices by name or lid. * Archive Log: New feature added to enable search devices by name, lid or node guid. The search results are displayed as a tree and when a result node from the tree is selected, original tree is expanded and the corresponding node is highlighted. * Archive Log: * Archive Log: Revision 1.36 2015/06/10 19:58:51 jijunwan * Archive Log: PR 129120 - Some old files have no proper file header. They cannot record change logs. * Archive Log: - wrote a tool to check and insert file header * Archive Log: - applied on backend files * Archive Log: * * Overview: * * @author: jijunwan * ******************************************************************************/ package com.intel.stl.ui.common.view; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.Rectangle2D; import java.text.DecimalFormat; import java.util.List; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.InputVerifier; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JCheckBoxMenuItem; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JFormattedTextField; import javax.swing.JLabel; import javax.swing.JPasswordField; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.ToolTipManager; import javax.swing.border.Border; import javax.swing.border.TitledBorder; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.text.JTextComponent; import org.jdesktop.swingx.JXLabel; import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartUtilities; import org.jfree.chart.JFreeChart; import org.jfree.chart.annotations.CategoryTextAnnotation; import org.jfree.chart.annotations.XYTitleAnnotation; import org.jfree.chart.axis.AxisLocation; import org.jfree.chart.axis.CategoryAnchor; import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.DateAxis; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.axis.NumberTickUnit; import org.jfree.chart.axis.TickUnitSource; import org.jfree.chart.axis.TickUnits; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.block.BlockBorder; import org.jfree.chart.labels.StandardXYToolTipGenerator; import org.jfree.chart.labels.XYItemLabelGenerator; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.CrosshairState; import org.jfree.chart.plot.IntervalMarker; import org.jfree.chart.plot.PiePlot; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.PlotRenderingInfo; import org.jfree.chart.plot.XYPlot; import org.jfree.chart.renderer.category.BarRenderer; import org.jfree.chart.renderer.category.StandardBarPainter; import org.jfree.chart.renderer.xy.StackedXYBarRenderer; import org.jfree.chart.renderer.xy.StandardXYBarPainter; import org.jfree.chart.renderer.xy.XYBarRenderer; import org.jfree.chart.renderer.xy.XYItemRenderer; import org.jfree.chart.renderer.xy.XYItemRendererState; import org.jfree.chart.renderer.xy.XYStepAreaRenderer; import org.jfree.chart.title.LegendTitle; import org.jfree.data.Range; import org.jfree.data.RangeType; import org.jfree.data.category.CategoryDataset; import org.jfree.data.general.PieDataset; import org.jfree.data.xy.IntervalXYDataset; import org.jfree.data.xy.XYDataset; import org.jfree.ui.Layer; import org.jfree.ui.RectangleAnchor; import org.jfree.ui.RectangleEdge; import org.jfree.ui.RectangleInsets; import org.jfree.ui.TextAnchor; import com.intel.stl.api.StringUtils; import com.intel.stl.api.Utils; import com.intel.stl.ui.common.UIConstants; import com.intel.stl.ui.common.UIImages; import com.intel.stl.ui.common.UILabels; import com.intel.stl.ui.common.Util; import com.intel.stl.ui.common.view.SafeTextField.SafeStringFormatter; import com.intel.stl.ui.model.NodeTypeViz; /** * @author jijunwan * */ public class ComponentFactory { /** * * <i>Description:</i> simple method that creates single/multi line label * with specified label width based on a source label. For more advanced * label attributes, it's the developer's responsibility to set them back. * * @param source * @param wrap * @param maxWidth * @return */ public static JLabel deriveLabel(JLabel source, final boolean wrap, final int maxWidth) { JXLabel label = new JXLabel(source.getText(), source.getIcon(), source.getHorizontalAlignment()) { private static final long serialVersionUID = -4816144910055350011L; private Font cachedFont; private String chahedRawText, chahedText; /* * (non-Javadoc) * * @see javax.swing.JLabel#getText() */ @Override public String getText() { String text = super.getText(); if (wrap || maxWidth <= 0 || text == null || text.isEmpty()) { return text; } if (getFont().equals(cachedFont) && text.equals(chahedRawText)) { return chahedText; } chahedRawText = text; cachedFont = getFont(); FontMetrics fm = getFontMetrics(cachedFont); char[] chars = text.toCharArray(); int width = fm.charsWidth(chars, 0, chars.length); if (width < maxWidth) { chahedText = text; } else { width += fm.charWidth('.') * 3; int pos = chars.length - 1; for (; pos >= 0 && width > maxWidth; pos--) { width -= fm.charWidth(chars[pos]); } chahedText = new String(chars, 0, pos) + "..."; if (getToolTipText() == null) { setToolTipText(text); } } return chahedText; } }; if (wrap) { label.setLineWrap(true); } if (maxWidth > 0) { label.setMaxLineSpan(maxWidth); } label.setEnabled(source.isEnabled()); label.setForeground(source.getForeground()); label.setOpaque(source.isOpaque()); label.setBackground(source.getBackground()); label.setFont(source.getFont()); label.setBorder(source.getBorder()); label.setToolTipText(source.getToolTipText()); return label; } public static JLabel getIntelH1Label(String text, int style) { JLabel label = new JLabel(text); label.setForeground(UIConstants.INTEL_BLUE); label.setFont(UIConstants.H1_FONT.deriveFont(style)); return label; } public static JLabel getIntelH2Label(String text, int style) { JLabel label = new JLabel(text); label.setForeground(UIConstants.INTEL_BLUE); label.setFont(UIConstants.H2_FONT.deriveFont(style)); return label; } public static JLabel getIntelH3Label(String text, int style) { JLabel label = new JLabel(text); label.setForeground(UIConstants.INTEL_BLUE); label.setFont(UIConstants.H3_FONT.deriveFont(style)); return label; } public static JLabel getH1Label(String text, int style) { JLabel label = new JLabel(text); label.setFont(UIConstants.H1_FONT.deriveFont(style)); label.setForeground(UIConstants.INTEL_DARK_GRAY); return label; } public static JLabel getH2Label(String text, int style) { JLabel label = new JLabel(text); label.setFont(UIConstants.H2_FONT.deriveFont(style)); label.setForeground(UIConstants.INTEL_DARK_GRAY); return label; } public static JLabel getH3Label(String text, int style) { JLabel label = new JLabel(text); label.setFont(UIConstants.H3_FONT.deriveFont(style)); label.setForeground(UIConstants.INTEL_DARK_GRAY); return label; } public static JLabel getH4Label(String text, int style) { JLabel label = new JLabel(text); label.setFont(UIConstants.H4_FONT.deriveFont(style)); label.setForeground(UIConstants.INTEL_DARK_GRAY); return label; } public static JLabel getH5Label(String text, int style) { JLabel label = new JLabel(text); label.setFont(UIConstants.H5_FONT.deriveFont(style)); label.setForeground(UIConstants.INTEL_DARK_GRAY); return label; } public static JLabel getH6Label(String text, int style) { JLabel label = new JLabel(text); label.setFont(UIConstants.H6_FONT.deriveFont(style)); label.setForeground(UIConstants.INTEL_DARK_GRAY); return label; } public static JLabel getFieldLabel(String text) { JLabel label = new JLabel(text); label.setForeground(UIConstants.INTEL_DARK_GRAY); label.setFont(UIConstants.H5_FONT.deriveFont(Font.BOLD)); return label; } public static JLabel getFieldLabel(String text, Color clr) { JLabel label = new JLabel(text); label.setForeground(clr); label.setFont(UIConstants.H5_FONT.deriveFont(Font.BOLD)); return label; } public static JLabel getFieldContent(String text) { JLabel label = new JLabel(text); label.setForeground(UIConstants.INTEL_DARK_GRAY); label.setFont(UIConstants.H3_FONT); return label; } public static JLabel getFieldContent(String text, Color clr) { JLabel label = new JLabel(text); label.setForeground(clr); label.setFont(UIConstants.H3_FONT); return label; } public static JButton getImageButton(ImageIcon icon) { JButton res = new JButton(icon); res.setMargin(new Insets(0, 0, 0, 0)); res.setBorderPainted(false); res.setContentAreaFilled(false); return res; } public static JButton getIntelActionButton(String text) { JButton btn = new JButton(text); btn.setUI(new IntelButtonUI(UIConstants.INTEL_MEDIUM_BLUE, UIConstants.INTEL_MEDIUM_DARK_BLUE)); btn.setBackground(UIConstants.INTEL_BLUE); btn.setForeground(UIConstants.INTEL_WHITE); // btn.setFont(UIConstants.H5_FONT); return btn; } public static JButton getDebugActionButton(String text) { JButton btn = new JButton(text) { private static final long serialVersionUID = 9164124909538571267L; @Override public String toString() { return new String(getClass().getName() + "@" + hashCode()); } }; btn.setUI(new IntelButtonUI(UIConstants.INTEL_MEDIUM_BLUE, UIConstants.INTEL_MEDIUM_DARK_BLUE)); btn.setBackground(UIConstants.INTEL_BLUE); btn.setForeground(UIConstants.INTEL_WHITE); return btn; } // This function is used to make buttons equal width based on the // width of the widest button public static void makeSameWidthButtons(JButton[] btnGroup) { int maxWidth = 0; // Find widest button for (int i = 0; i < btnGroup.length; i++) { int width = btnGroup[i].getPreferredSize().width; maxWidth = (maxWidth > width) ? maxWidth : width; } // set all buttons to the widest width for (int i = 0; i < btnGroup.length; i++) { btnGroup[i].setPreferredSize(new java.awt.Dimension(maxWidth, btnGroup[i].getPreferredSize().height)); } } public static JButton getIntelActionButton(String text, Icon icon) { JButton btn = new JButton(text); btn.setUI(new IntelButtonUI(UIConstants.INTEL_MEDIUM_BLUE, UIConstants.INTEL_MEDIUM_DARK_BLUE)); btn.setBackground(UIConstants.INTEL_BLUE); btn.setForeground(UIConstants.INTEL_WHITE); btn.setIcon(icon); return btn; } public static JButton getIntelActionButton(AbstractAction action) { JButton btn = new JButton(action); btn.setUI(new IntelButtonUI(UIConstants.INTEL_MEDIUM_BLUE, UIConstants.INTEL_MEDIUM_DARK_BLUE)); btn.setBackground(UIConstants.INTEL_BLUE); btn.setForeground(UIConstants.INTEL_WHITE); return btn; } public static JButton getIntelActionButton(ImageIcon icon, final Color enableColor, final Color disableColor, AbstractAction action) { JButton btn = new JButton(action) { private static final long serialVersionUID = 1L; @Override public void setEnabled(boolean b) { super.setEnabled(b); setForeground(b ? enableColor : disableColor); } }; btn.setIcon(icon); btn.setUI(new IntelButtonUI(UIConstants.INTEL_MEDIUM_BLUE, UIConstants.INTEL_MEDIUM_DARK_BLUE)); return btn; } public static JButton getIntelCancelButton(String text) { JButton btn = new JButton(text); btn.setUI(new IntelButtonUI(UIConstants.INTEL_BACKGROUND_GRAY, UIConstants.INTEL_LIGHT_GRAY)); btn.setBackground(UIConstants.INTEL_TABLE_BORDER_GRAY); btn.setForeground(UIConstants.INTEL_DARK_GRAY); // btn.setFont(UIConstants.H5_FONT); return btn; } public static JButton getIntelCancelButton(AbstractAction action) { JButton btn = new JButton(action); btn.setUI(new IntelButtonUI(UIConstants.INTEL_BACKGROUND_GRAY, UIConstants.INTEL_LIGHT_GRAY)); btn.setBackground(UIConstants.INTEL_TABLE_BORDER_GRAY); btn.setForeground(UIConstants.INTEL_DARK_GRAY); // btn.setFont(UIConstants.H5_FONT); return btn; } public static JButton getIntelDeleteButton(String text) { JButton btn = new JButton(text); btn.setUI(new IntelButtonUI(UIConstants.INTEL_MEDIUM_RED, UIConstants.INTEL_MEDIUM_DARK_RED)); btn.setBackground(UIConstants.INTEL_RED); btn.setForeground(UIConstants.INTEL_WHITE); // btn.setFont(UIConstants.H5_FONT); return btn; } public static JButton getIntelDeleteButton(AbstractAction action) { JButton btn = new JButton(action); btn.setUI(new IntelButtonUI(UIConstants.INTEL_MEDIUM_RED, UIConstants.INTEL_MEDIUM_DARK_RED)); btn.setBackground(UIConstants.INTEL_RED); btn.setForeground(UIConstants.INTEL_WHITE); // btn.setFont(UIConstants.H5_FONT); return btn; } public static JButton getIntelWizardStepButton(String text, final ActionListener listener) { JButton btn = new JButton(text) { /** * */ private static final long serialVersionUID = 7408863234629205179L; @Override public void setEnabled(boolean b) { if (b) { addActionListener(listener); } else { removeActionListener(listener); } } }; btn.setUI(new IntelButtonUI(UIConstants.INTEL_MEDIUM_BLUE, UIConstants.INTEL_MEDIUM_DARK_BLUE)); btn.setBackground(UIConstants.INTEL_BLUE); btn.setForeground(UIConstants.INTEL_WHITE); return btn; } public static JCheckBox getIntelCheckBox(String text) { JCheckBox box = new JCheckBox(text); box.setIcon(UIImages.EMPTY_BOX_ICON.getImageIcon()); box.setSelectedIcon(UIImages.CHECK_BOX_ICON.getImageIcon()); box.setBackground(Color.WHITE); return box; } public static JCheckBoxMenuItem getIntelCheckBoxMenuItem(String text) { final JCheckBoxMenuItem box = new JCheckBoxMenuItem(text, UIImages.EMPTY_BOX_ICON.getImageIcon()); // should do it through L&F, but couldn't find the way to do it // so we are using action listener to dynamically change icon box.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { box.setIcon(box.isSelected() ? UIImages.CHECK_BOX_ICON.getImageIcon() : UIImages.EMPTY_BOX_ICON.getImageIcon()); } }); return box; } public static SafeNumberField<Integer> createNumericTextField(DocumentListener... docListeners) { return createNumericTextField(null, docListeners); } public static SafeNumberField<Integer> createNumericTextField(Integer max, DocumentListener... docListeners) { final SafeNumberField<Integer> field = new SafeNumberField<Integer>(new DecimalFormat("###"), 0, false, max, false); // only positive integer field.setValidChars(UIConstants.DIGITS); if (docListeners != null) { for (DocumentListener docListener : docListeners) { field.getDocument().addDocumentListener(docListener); } } // Add a mouse listener to clear out the field before the user types field.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { try { JTextComponent tc = (JTextComponent) e.getSource(); String txt = tc.getText(); if (!txt.isEmpty()) { field.getFormatter().stringToValue(txt); } } catch (Exception ex) { field.setText(""); } } }); return field; } public static void setNumberInputVerifier(JTextField field) { // Add the input verifier field.setInputVerifier(new InputVerifier() { @Override public boolean verify(JComponent input) { JTextField field = (JTextField) input; String txt = field.getText(); if (txt.isEmpty()) { field.setToolTipText(UILabels.STL50084_CANT_BE_BLANK.getDescription()); input.setBackground(UIConstants.INTEL_LIGHT_RED); return false; } try { Utils.toLong(txt); input.setBackground(UIConstants.INTEL_WHITE); return true; } catch (Exception e) { field.setToolTipText(UILabels.STL50085_MUST_BE_NUMERIC.getDescription()); input.setBackground(UIConstants.INTEL_LIGHT_RED); } return false; } }); } /** * * <i>Description:</i> * * @param validChars * the string lists valid characters, such as * "0123456789abcedfABCDEF" * @param maxLen * the maximum number of chars * @param docListeners * @return */ public static ExFormattedTextField createTextField(String validChars, boolean allowEmpty, int maxLen, DocumentListener... docListeners) { ExFormattedTextField field = new SafeTextField(allowEmpty); SafeStringFormatter formatter = (SafeStringFormatter) field.getFormatter(); if (validChars != null && !validChars.isEmpty()) { formatter.setValidCharacters(validChars); } if (maxLen > 0) { formatter.setMaxLength(maxLen); } if (docListeners != null) { for (DocumentListener docListener : docListeners) { field.getDocument().addDocumentListener(docListener); field.getDocument().putProperty("owner", field); } } return field; } public static JPasswordField createPasswordField(DocumentListener... docListeners) { final JPasswordField field = new JPasswordField(); final Border oldBorder = field.getBorder(); if (docListeners != null) { for (DocumentListener docListener : docListeners) { field.getDocument().addDocumentListener(docListener); field.getDocument().putProperty("owner", field); } } final InputVerifier verifier = new InputVerifier() { @Override public boolean verify(final JComponent input) { final JPasswordField field = (JPasswordField) input; char[] txt = field.getPassword(); if (txt.length == 0) { // Run in EDT to avoid deadlock in case this gets called // from not swing thread Util.runInEDT(new Runnable() { @Override public void run() { field.setToolTipText(UILabels.STL50084_CANT_BE_BLANK.getDescription()); input.setBackground(UIConstants.INTEL_LIGHT_RED); input.setBorder(BorderFactory.createLineBorder(UIConstants.INTEL_RED, 2)); // show tooltip immediately ToolTipManager.sharedInstance() .mouseMoved(new MouseEvent(field, 0, 0, 0, 0, 0, 0, false)); } }); return false; } else { Util.runInEDT(new Runnable() { @Override public void run() { input.setBackground(UIConstants.INTEL_WHITE); input.setBorder(oldBorder); } }); return true; } } }; DocumentListener dynamicChecker = new DocumentListener() { @Override public void insertUpdate(DocumentEvent e) { verifier.verify(field); } @Override public void removeUpdate(DocumentEvent e) { verifier.verify(field); } @Override public void changedUpdate(DocumentEvent e) { verifier.verify(field); } }; field.getDocument().addDocumentListener(dynamicChecker); // Add the input verifier field.setInputVerifier(verifier); return field; } public static JComboBox<String> createComboBox(String[] strArray, DocumentListener... docListeners) { JComboBox<String> cbox = new JComboBox<String>(strArray) { private static final long serialVersionUID = -7465791917624978560L; @Override public String toString() { return "JComboBox@" + StringUtils.intHexString(hashCode()); } }; cbox.setUI(new IntelComboBoxUI()); // Get the text editor for this combo box JTextComponent tc = (JTextComponent) cbox.getEditor().getEditorComponent(); // Initialize the document listeners for (DocumentListener docListener : docListeners) { tc.getDocument().addDocumentListener(docListener); } return cbox; } public static <T> JComboBox<T> createComboBox(T[] values, JFormattedTextField textField, DocumentListener... docListeners) { JComboBox<T> cbox = new JComboBox<T>(values); cbox.setUI(new IntelComboBoxUI()); FormattedComboBoxEditor editor = new FormattedComboBoxEditor(textField); cbox.setEditable(true); cbox.setEditor(editor); // Get the text editor for this combo box final JTextComponent tc = (JTextComponent) cbox.getEditor().getEditorComponent(); // Initialize the document listeners for (DocumentListener docListener : docListeners) { tc.getDocument().addDocumentListener(docListener); } return cbox; } public static JTable createIntelNonSortableSimpleTable(Object[][] rowData, Object[] columnNames) { JTable table = new JTable(rowData, columnNames); table.setBackground(UIConstants.INTEL_WHITE); table.getTableHeader().setBackground(UIConstants.INTEL_BORDER_GRAY); table.getTableHeader().setForeground(UIConstants.INTEL_DARK_GRAY); table.getTableHeader().setFont(UIConstants.H5_FONT.deriveFont(Font.BOLD)); return table; } public static JTable createIntelNonSortableSimpleTable() { JTable table = new JTable(); table.setBackground(UIConstants.INTEL_WHITE); table.getTableHeader().setBackground(UIConstants.INTEL_BORDER_GRAY); table.getTableHeader().setForeground(UIConstants.INTEL_DARK_GRAY); table.getTableHeader().setFont(UIConstants.H5_FONT.deriveFont(Font.BOLD)); return table; } public static TitledBorder createTitledBorder(String title) { TitledBorder res = BorderFactory.createTitledBorder(title); res.setTitleColor(UIConstants.INTEL_DARK_GRAY); // res.setTitleFont(UIConstants.H4_FONT); return res; } /** * Create a simple pie chart without title, legend, label etc. * * @param dataset * the dataset to be renderer * @param colors * an color array specify each item's color. The order of the * array correspond to the item order in dataset * @return a pie chart */ public static JFreeChart createPlainPieChart(PieDataset dataset, Color[] colors) { if (dataset == null) { throw new IllegalArgumentException("No dataset."); } if (colors != null && colors.length != dataset.getItemCount()) { throw new IllegalArgumentException("Data have " + dataset.getItemCount() + " values, while we have " + colors.length + " colors for them."); } JFreeChart jfreechart = ChartFactory.createPieChart(null, dataset, false, true, false); PiePlot pieplot = (PiePlot) jfreechart.getPlot(); if (colors != null) { for (int i = 0; i < colors.length; i++) { pieplot.setSectionPaint(dataset.getKey(i), colors[i]); } } pieplot.setBackgroundPaint(null); pieplot.setOutlineStroke(null); pieplot.setLabelGenerator(null); pieplot.setNoDataMessage(UILabels.STL40001_ERROR_No_DATA.getDescription()); pieplot.setCircular(true); pieplot.setInteriorGap(0.000001); return jfreechart; } public static JFreeChart createPlainHistoryChart(IntervalXYDataset dataset, XYItemLabelGenerator labelGenerator) { if (dataset == null) { throw new IllegalArgumentException("No dataset."); } JFreeChart jfreechart = ChartFactory.createXYBarChart(null, null, true, null, dataset, PlotOrientation.VERTICAL, false, true, false); XYPlot xyplot = (XYPlot) jfreechart.getPlot(); xyplot.setBackgroundPaint(null); xyplot.setOutlinePaint(null); XYBarRenderer renderer = (XYBarRenderer) xyplot.getRenderer(); renderer.setShadowVisible(false); renderer.setBaseItemLabelsVisible(true); if (labelGenerator != null) { renderer.setBaseItemLabelGenerator(labelGenerator); } renderer.setBaseItemLabelFont(UIConstants.H4_FONT); renderer.setBarPainter(new StandardXYBarPainter()); renderer.setSeriesPaint(0, UIConstants.INTEL_BLUE); // xyplot.getDomainAxis().setVisible(false); xyplot.getDomainAxis().setAxisLineVisible(true); xyplot.getDomainAxis().setTickLabelsVisible(false); NumberAxis axis = (NumberAxis) xyplot.getRangeAxis(); axis.setRangeType(RangeType.POSITIVE); axis.setVisible(false); return jfreechart; } public static JFreeChart createStepAreaChart(XYDataset dataset, XYItemLabelGenerator labelGenerator) { if (dataset == null) { throw new IllegalArgumentException("No dataset."); } JFreeChart jfreechart = ChartFactory.createXYLineChart(null, null, null, dataset, PlotOrientation.VERTICAL, false, true, false); XYPlot xyplot = (XYPlot) jfreechart.getPlot(); xyplot.setBackgroundPaint(UIConstants.INTEL_BACKGROUND_GRAY); // xyplot.setOutlinePaint(null); XYStepAreaRenderer xysteparearenderer = new XYStepAreaRenderer(XYStepAreaRenderer.AREA) { @Override public void drawItem(Graphics2D g2, XYItemRendererState state, Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, int series, int item, CrosshairState crosshairState, int pass) { setShapesVisible(item == dataset.getItemCount(series) - 1); super.drawItem(g2, state, dataArea, info, plot, domainAxis, rangeAxis, dataset, series, item, crosshairState, pass); } }; xysteparearenderer.setDataBoundsIncludesVisibleSeriesOnly(false); xysteparearenderer.setBaseToolTipGenerator(new StandardXYToolTipGenerator()); xysteparearenderer.setDefaultEntityRadius(6); xysteparearenderer.setShapesFilled(true); xyplot.setRenderer(xysteparearenderer); if (labelGenerator != null) { xysteparearenderer.setBaseItemLabelGenerator(labelGenerator); } xysteparearenderer.setSeriesPaint(0, UIConstants.INTEL_GREEN); xyplot.setOutlinePaint(UIConstants.INTEL_DARK_GRAY); xyplot.setDomainGridlinePaint(UIConstants.INTEL_DARK_GRAY); xyplot.setRangeGridlinePaint(UIConstants.INTEL_DARK_GRAY); xyplot.getDomainAxis().setVisible(false); NumberAxis axis = (NumberAxis) xyplot.getRangeAxis(); axis.setRangeType(RangeType.POSITIVE); axis.setAxisLineVisible(false); return jfreechart; } public static JFreeChart createXYBarChart(String xAxisLabel, String yAxisLabel, IntervalXYDataset dataset, XYItemLabelGenerator labelGenerator) { if (dataset == null) { throw new IllegalArgumentException("No dataset."); } JFreeChart jfreechart = ChartFactory.createXYBarChart(null, xAxisLabel, false, yAxisLabel, dataset, PlotOrientation.VERTICAL, false, true, false); XYPlot xyplot = (XYPlot) jfreechart.getPlot(); xyplot.setBackgroundPaint(null); xyplot.setOutlinePaint(null); xyplot.setRangeGridlinePaint(UIConstants.INTEL_DARK_GRAY); NumberAxis axis = (NumberAxis) xyplot.getRangeAxis(); axis.setRangeType(RangeType.POSITIVE); axis.setLabelFont(UIConstants.H5_FONT); xyplot.getDomainAxis().setLabelFont(UIConstants.H5_FONT); XYBarRenderer renderer = (XYBarRenderer) xyplot.getRenderer(); renderer.setShadowVisible(false); renderer.setBaseItemLabelsVisible(true); if (labelGenerator != null) { renderer.setBaseItemLabelGenerator(labelGenerator); } renderer.setBaseItemLabelFont(UIConstants.H5_FONT); renderer.setBarPainter(new StandardXYBarPainter()); renderer.setSeriesPaint(0, UIConstants.INTEL_BLUE); return jfreechart; } public static JFreeChart createBarChart(String xAxisLabel, String yAxisLabel, CategoryDataset dataset) { if (dataset == null) { throw new IllegalArgumentException("No dataset."); } JFreeChart jfreechart = ChartFactory.createBarChart(null, xAxisLabel, yAxisLabel, dataset, PlotOrientation.VERTICAL, false, true, false); CategoryPlot categoryplot = jfreechart.getCategoryPlot(); categoryplot.setBackgroundPaint(null); categoryplot.setOutlinePaint(null); categoryplot.setRangeAxisLocation(AxisLocation.BOTTOM_OR_LEFT); categoryplot.setRangePannable(true); categoryplot.setRangeGridlinePaint(UIConstants.INTEL_DARK_GRAY); categoryplot.getRangeAxis().setLabelFont(UIConstants.H5_FONT); categoryplot.getDomainAxis().setLabelFont(UIConstants.H5_FONT); BarRenderer barrenderer = (BarRenderer) categoryplot.getRenderer(); barrenderer.setBarPainter(new StandardBarPainter()); barrenderer.setBaseItemLabelsVisible(true); barrenderer.setShadowVisible(false); barrenderer.setSeriesPaint(0, UIConstants.INTEL_BLUE); CategoryAxis categoryaxis = categoryplot.getDomainAxis(); categoryaxis.setCategoryMargin(0.02D); categoryaxis.setUpperMargin(0.01D); categoryaxis.setLowerMargin(0.01D); // categoryaxis.setAxisLineVisible(false); categoryaxis.setMaximumCategoryLabelWidthRatio(0.95F); NumberAxis axis = (NumberAxis) categoryplot.getRangeAxis(); axis.setRangeType(RangeType.POSITIVE); return jfreechart; } public static JFreeChart createHistogramChart(String xAxisLabel, String yAxisLabel, IntervalXYDataset dataset) { JFreeChart jfreechart = ChartFactory.createHistogram(null, xAxisLabel, yAxisLabel, dataset, PlotOrientation.VERTICAL, false, true, false); XYPlot xyplot = (XYPlot) jfreechart.getPlot(); xyplot.setBackgroundPaint(null); xyplot.setOutlinePaint(null); xyplot.setRangeGridlinePaint(UIConstants.INTEL_DARK_GRAY); NumberAxis yAxis = (NumberAxis) xyplot.getRangeAxis(); yAxis.setRangeType(RangeType.POSITIVE); yAxis.setLabelFont(UIConstants.H5_FONT); yAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); xyplot.getDomainAxis().setLabelFont(UIConstants.H5_FONT); xyplot.getRangeAxis().setStandardTickUnits(NumberAxis.createIntegerTickUnits()); XYBarRenderer renderer = (XYBarRenderer) xyplot.getRenderer(); renderer.setShadowVisible(false); renderer.setBaseItemLabelsVisible(true); renderer.setBaseItemLabelFont(UIConstants.H5_FONT); renderer.setBarPainter(new StandardXYBarPainter()); renderer.setSeriesPaint(0, UIConstants.INTEL_BLUE); return jfreechart; } public static JFreeChart createXYAreaChart(String xAxisLabel, String yAxisLabel, XYDataset dataset, boolean includeLegend) { JFreeChart jfreechart = ChartFactory.createXYAreaChart(null, xAxisLabel, yAxisLabel, dataset, PlotOrientation.VERTICAL, false, true, false); XYPlot xyplot = (XYPlot) jfreechart.getPlot(); xyplot.setDomainPannable(true); xyplot.setBackgroundPaint(null); xyplot.setOutlinePaint(null); xyplot.setForegroundAlpha(0.8F); xyplot.setRangeGridlinePaint(UIConstants.INTEL_DARK_GRAY); DateAxis dateaxis = new DateAxis(xAxisLabel); dateaxis.setLowerMargin(0.0D); dateaxis.setUpperMargin(0.0D); xyplot.setDomainAxis(dateaxis); NumberAxis rangeAxis = (NumberAxis) xyplot.getRangeAxis(); rangeAxis.setRangeType(RangeType.POSITIVE); rangeAxis.setLabelFont(UIConstants.H5_FONT); rangeAxis.setLabelInsets(new RectangleInsets(0, 0, 0, 0)); if (includeLegend) { LegendTitle legendtitle = new LegendTitle(xyplot); legendtitle.setItemFont(UIConstants.H5_FONT); legendtitle.setBackgroundPaint(UIConstants.INTEL_WHITE); legendtitle.setFrame(new BlockBorder(UIConstants.INTEL_BLUE)); legendtitle.setPosition(RectangleEdge.BOTTOM); XYTitleAnnotation xytitleannotation = new XYTitleAnnotation(0.97999999999999998D, 0.99999999999999998D, legendtitle, RectangleAnchor.TOP_RIGHT); // xytitleannotation.setMaxWidth(0.47999999999999998D); xyplot.addAnnotation(xytitleannotation); } XYItemRenderer xyitemrenderer = xyplot.getRenderer(); xyitemrenderer.setSeriesPaint(1, UIConstants.INTEL_DARK_GRAY); xyitemrenderer.setSeriesPaint(0, NodeTypeViz.SWITCH.getColor()); xyitemrenderer.setBaseToolTipGenerator( new StandardXYToolTipGenerator("<html><b>{0}</b><br> Time: {1}<br> Data: {2}</html>", Util.getHHMMSS(), new DecimalFormat("#,##0.00"))); return jfreechart; } public static JFreeChart createXYAreaSparkline(XYDataset dataset) { JFreeChart jfreechart = ChartFactory.createXYAreaChart(null, null, null, dataset, PlotOrientation.VERTICAL, false, false, false); XYPlot xyplot = (XYPlot) jfreechart.getPlot(); xyplot.setDomainPannable(true); xyplot.setBackgroundPaint(null); xyplot.setOutlinePaint(null); xyplot.setForegroundAlpha(0.8F); xyplot.setDomainGridlinesVisible(false); xyplot.setDomainCrosshairVisible(false); xyplot.setRangeGridlinesVisible(false); xyplot.setRangeCrosshairVisible(false); DateAxis dateaxis = new DateAxis(""); dateaxis.setTickLabelsVisible(false); dateaxis.setTickMarksVisible(false); dateaxis.setAxisLineVisible(false); dateaxis.setNegativeArrowVisible(false); dateaxis.setPositiveArrowVisible(false); dateaxis.setVisible(false); xyplot.setDomainAxis(dateaxis); ValueAxis rangeAxis = xyplot.getRangeAxis(); rangeAxis.setTickLabelsVisible(false); rangeAxis.setTickMarksVisible(false); rangeAxis.setAxisLineVisible(false); rangeAxis.setNegativeArrowVisible(false); rangeAxis.setPositiveArrowVisible(false); rangeAxis.setVisible(false); XYItemRenderer xyitemrenderer = xyplot.getRenderer(); xyitemrenderer.setSeriesPaint(1, UIConstants.INTEL_DARK_GRAY); xyitemrenderer.setSeriesPaint(0, NodeTypeViz.SWITCH.getColor()); return jfreechart; } @SuppressWarnings("unchecked") public static JFreeChart createTopNBarChart2(String yAxisLabel, CategoryDataset dataset) { JFreeChart jfreechart = ChartFactory.createBarChart(null, null, yAxisLabel, dataset, PlotOrientation.HORIZONTAL, false, true, false); CategoryPlot categoryplot = jfreechart.getCategoryPlot(); categoryplot.setBackgroundPaint(null); categoryplot.setOutlinePaint(null); categoryplot.setDomainGridlinesVisible(true); categoryplot.setDomainGridlinePosition(CategoryAnchor.END); categoryplot.setDomainGridlineStroke(new BasicStroke(0.5F)); categoryplot.setDomainGridlinePaint(UIConstants.INTEL_BORDER_GRAY); categoryplot.setRangeGridlinesVisible(false); categoryplot.clearRangeMarkers(); CategoryAxis categoryaxis = categoryplot.getDomainAxis(); categoryaxis.setVisible(false); categoryaxis.setCategoryMargin(0.75D); NumberAxis axis = (NumberAxis) categoryplot.getRangeAxis(); axis.setRangeType(RangeType.POSITIVE); axis.setVisible(false); BarRenderer barrenderer = (BarRenderer) categoryplot.getRenderer(); barrenderer.setShadowVisible(false); barrenderer.setSeriesPaint(0, UIConstants.INTEL_BLUE); barrenderer.setDrawBarOutline(false); barrenderer.setBaseItemLabelsVisible(true); barrenderer.setBaseItemLabelFont(UIConstants.H5_FONT); barrenderer.setBarPainter(new StandardBarPainter()); List<String> names = dataset.getColumnKeys(); for (String name : names) { CategoryTextAnnotation categorytextannotation = new CategoryTextAnnotation(name, name, 0.0D); categorytextannotation.setFont(UIConstants.H6_FONT); categorytextannotation.setTextAnchor(TextAnchor.BOTTOM_LEFT); categorytextannotation.setCategoryAnchor(CategoryAnchor.MIDDLE); categoryplot.addAnnotation(categorytextannotation); } return jfreechart; } public static JFreeChart createTopNBarChart(String yAxisLabel, CategoryDataset dataset) { JFreeChart jfreechart = ChartFactory.createBarChart(null, null, yAxisLabel, dataset, PlotOrientation.HORIZONTAL, true, true, false); CategoryPlot categoryplot = jfreechart.getCategoryPlot(); categoryplot.setBackgroundPaint(null); categoryplot.setOutlinePaint(null); categoryplot.setRangeAxisLocation(AxisLocation.BOTTOM_OR_LEFT); categoryplot.setRangePannable(true); categoryplot.setRangeGridlinesVisible(true); categoryplot.setRangeGridlinePaint(UIConstants.INTEL_DARK_GRAY); BarRenderer barrenderer = (BarRenderer) categoryplot.getRenderer(); barrenderer.setBarPainter(new StandardBarPainter()); barrenderer.setShadowVisible(false); barrenderer.setItemMargin(0.015); barrenderer.setSeriesPaint(0, UIConstants.INTEL_BLUE); barrenderer.setSeriesPaint(1, UIConstants.INTEL_LIGHT_BLUE); CategoryAxis categoryaxis = categoryplot.getDomainAxis(); categoryaxis.setCategoryMargin(0.15D); categoryaxis.setUpperMargin(0.02D); categoryaxis.setLowerMargin(0.02D); categoryaxis.setMaximumCategoryLabelWidthRatio(0.5F); NumberAxis numberaxis = (NumberAxis) categoryplot.getRangeAxis(); numberaxis.setRangeType(RangeType.POSITIVE); numberaxis.setStandardTickUnits(createLargeNumberTickUnits()); numberaxis.setUpperMargin(0.20000000000000001D); numberaxis.setLabelFont(UIConstants.H5_FONT); numberaxis.setLabelInsets(new RectangleInsets(0, 0, 0, 0)); numberaxis.setTickMarksVisible(true); numberaxis.setTickLabelsVisible(true); LegendTitle legend = jfreechart.getLegend(); legend.setFrame(BlockBorder.NONE); legend.setItemFont(barrenderer.getBaseItemLabelFont().deriveFont(10.0f)); return jfreechart; } public static JFreeChart createBulletChart(CategoryDataset dataset, double[] thresholds, Color[] colors) { if (thresholds.length != colors.length) { throw new IllegalArgumentException( "Inconsistant array sizes: thresholds=" + thresholds.length + " colors=" + colors.length); } JFreeChart jfreechart = ChartFactory.createBarChart(null, null, null, dataset, PlotOrientation.HORIZONTAL, false, true, false); CategoryPlot categoryplot = jfreechart.getCategoryPlot(); categoryplot.setBackgroundPaint(null); categoryplot.setOutlinePaint(null); categoryplot.getDomainAxis().setVisible(false); NumberAxis rangeAxis = (NumberAxis) categoryplot.getRangeAxis(); rangeAxis.setVisible(false); rangeAxis.setRange(new Range(0, 1.0)); double last = 0.0; for (int i = 0; i < thresholds.length; i++) { IntervalMarker marker = new IntervalMarker(last, thresholds[i], colors[i]); categoryplot.addRangeMarker(marker, Layer.BACKGROUND); last = thresholds[i]; } BarRenderer renderer = (BarRenderer) categoryplot.getRenderer(); renderer.setShadowVisible(false); renderer.setMaximumBarWidth(0.33); renderer.setSeriesPaint(0, UIConstants.INTEL_DARK_GRAY); return jfreechart; } public static JFreeChart createStackedXYBarChart(XYDataset dataset, String title, String domainAxisLabel, String rangeAxisLabel, boolean legend) { DateAxis dateaxis = new DateAxis(domainAxisLabel); NumberAxis numberaxis = new NumberAxis(rangeAxisLabel); StackedXYBarRenderer stackedxybarrenderer = new StackedXYBarRenderer(0.10000000000000001D); XYPlot xyplot = new XYPlot(dataset, dateaxis, numberaxis, stackedxybarrenderer); JFreeChart jfreechart = new JFreeChart(title, UIConstants.H5_FONT, xyplot, legend); ChartUtilities.applyCurrentTheme(jfreechart); stackedxybarrenderer.setShadowVisible(false); stackedxybarrenderer.setDrawBarOutline(false); stackedxybarrenderer.setBarPainter(new StandardXYBarPainter()); stackedxybarrenderer.setBaseToolTipGenerator(new StandardXYToolTipGenerator( "<html><b>{0}</b><br> Time: {1}<br> Data: {2}</html>", Util.getHHMMSS(), new DecimalFormat("###"))); xyplot.setBackgroundPaint(null); xyplot.setOutlinePaint(null); xyplot.setRangeGridlinePaint(UIConstants.INTEL_BORDER_GRAY); dateaxis.setLabelFont(UIConstants.H5_FONT); dateaxis.setLowerMargin(0.0D); dateaxis.setUpperMargin(0.0D); numberaxis.setRangeType(RangeType.POSITIVE); numberaxis.setLabelFont(UIConstants.H5_FONT); numberaxis.setLabelInsets(new RectangleInsets(0, 0, 0, 0)); numberaxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); return jfreechart; } public static TickUnitSource createLargeNumberTickUnits() { TickUnits units = new TickUnits(); DecimalFormat df0 = new DecimalFormat("0"); DecimalFormat df1 = new DecimalFormat("#,##0"); DecimalFormat df2 = new DecimalFormat("0.##E0"); units.add(new NumberTickUnit(1, df0, 2)); units.add(new NumberTickUnit(2, df0, 2)); units.add(new NumberTickUnit(5, df0, 5)); units.add(new NumberTickUnit(10, df0, 2)); units.add(new NumberTickUnit(20, df0, 2)); units.add(new NumberTickUnit(50, df0, 5)); units.add(new NumberTickUnit(100, df0, 2)); units.add(new NumberTickUnit(200, df0, 2)); units.add(new NumberTickUnit(500, df0, 5)); units.add(new NumberTickUnit(1000, df1, 2)); units.add(new NumberTickUnit(2000, df1, 2)); units.add(new NumberTickUnit(5000, df1, 5)); units.add(new NumberTickUnit(10000, df1, 2)); units.add(new NumberTickUnit(20000, df1, 2)); units.add(new NumberTickUnit(50000, df1, 5)); units.add(new NumberTickUnit(100000, df1, 2)); units.add(new NumberTickUnit(200000, df1, 2)); units.add(new NumberTickUnit(500000, df1, 5)); units.add(new NumberTickUnit(1000000, df2, 2)); units.add(new NumberTickUnit(2000000, df2, 2)); units.add(new NumberTickUnit(5000000, df2, 5)); units.add(new NumberTickUnit(10000000, df2, 2)); units.add(new NumberTickUnit(20000000, df2, 2)); units.add(new NumberTickUnit(50000000, df2, 5)); units.add(new NumberTickUnit(100000000, df2, 2)); units.add(new NumberTickUnit(200000000, df2, 2)); units.add(new NumberTickUnit(500000000, df2, 5)); units.add(new NumberTickUnit(1000000000, df2, 2)); units.add(new NumberTickUnit(2000000000, df2, 2)); units.add(new NumberTickUnit(5000000000.0, df2, 5)); units.add(new NumberTickUnit(10000000000.0, df2, 2)); return units; } }