diff --git a/src/studio/ui/chart/Chart.java b/src/studio/ui/chart/Chart.java index 5bfc958..aacb175 100755 --- a/src/studio/ui/chart/Chart.java +++ b/src/studio/ui/chart/Chart.java @@ -49,7 +49,7 @@ public class Chart implements ComponentListener { private JPanel contentPane; private ChartConfigPanel pnlConfig; - private List yIndex; + private final List indexes = new ArrayList<>(); private final String defaultTitle; @@ -90,23 +90,21 @@ public Chart(KTableModel table) { } private void initComponents() { - List names = new ArrayList<>(); - List xIndex = new ArrayList<>(); - yIndex = new ArrayList<>(); - for (int index = 0; index namesList = new ArrayList<>(); + for (int index = 0; index < table.getColumnCount(); index++) { Class clazz = table.getColumnClass(index); if (supportedClasses.contains(clazz)) { - xIndex.add(index); - yIndex.add(index); + indexes.add(index); + namesList.add(table.getColumnName(index)); } } - if (xIndex.size() == 0 || yIndex.size() ==0) { - log.info("Nothing to chart. Number of columns for x axes is {}. Number of columns for y axes is {}", xIndex.size(), yIndex.size()); + if (indexes.size() < 2) { + log.info("Nothing to chart. Number of columns which can be casted to decimal in {}", indexes.size()); StudioOptionPane.showWarning(null, "It turns out that nothing is found to chart.", "Nothing to chart"); return; } + String[] names = namesList.toArray(new String[namesList.size()]); int plotMoveModifier = Util.MAC_OS_X ? KeyEvent.ALT_MASK : KeyEvent.CTRL_MASK; int lineDragModifier = Util.MAC_OS_X ? KeyEvent.META_MASK : KeyEvent.CTRL_MASK; @@ -121,7 +119,7 @@ private void initComponents() { JLabel lbl = new JLabel(defaultLabelText); chartPanel = createChartPanel(); - pnlConfig = new ChartConfigPanel(this, names, xIndex, yIndex); + pnlConfig = new ChartConfigPanel(this, names); JToolBar toolbar = new Toolbar(); toolbar.setLayout(new BoxLayout(toolbar, BoxLayout.X_AXIS)); @@ -129,7 +127,10 @@ private void initComponents() { toolbar.add(chartPanel.getLineAction()); toolbar.add(chartPanel.getCopyAction()).setFocusable(false); toolbar.add(chartPanel.getSaveAction()).setFocusable(false); - chartPanel.addNewLineListener(e -> chartPanel.getRootPane().requestFocusInWindow()); + chartPanel.addNewLineListener(e -> { + chartPanel.getRootPane().requestFocusInWindow(); + pnlConfig.addLine(e.getLine()); + }); chartPanel.addLineSelectionListener(e -> lbl.setText(e.getLine() == null ? defaultLabelText : selectedLineText) ); JPanel rightPanel = new JPanel(new BorderLayout()); @@ -251,7 +252,7 @@ public void refreshPlot() { plot.setRenderer(i, null); } - int xIndex = pnlConfig.getDomainIndex(); + int xIndex = indexes.get(pnlConfig.getDomainIndex()); Class xClazz = table.getColumnClass(xIndex); NumberAxis xAxis = new NumberAxis(""); @@ -264,11 +265,13 @@ public void refreshPlot() { plot.setRangePannable(true); plot.setDatasetRenderingOrder(DatasetRenderingOrder.FORWARD); int datasetIndex = 0; - for (int index = 0; index names; - private final List xIndex; - private final List yIndex; private final JComboBox comboCharType; private final JComboBox comboX; - private final JCheckBox chkAll; - private final JCheckBox[] chkY; - private final LegendIcon[] icons; - private final JPanel pnlLagend; - - private final LegendIcon colorChoosePreviewIcon; - - private final static Border EMPTY_BORDER = BorderFactory.createEmptyBorder(2,0,2,0); - private final static Border SELECTED_BORDER = BorderFactory.createEtchedBorder(EtchedBorder.LOWERED); - - private static Paint[] colors = DefaultDrawingSupplier.DEFAULT_PAINT_SEQUENCE; - private static Shape[] shapes = DefaultDrawingSupplier.DEFAULT_SHAPE_SEQUENCE; - private static BasicStroke[] strokes = new BasicStroke[] { - new BasicStroke(1f), - new BasicStroke(1f,BasicStroke.CAP_BUTT, - BasicStroke.JOIN_BEVEL,1f,new float[] {10,10},0f - ), - new BasicStroke(1f,BasicStroke.CAP_BUTT, - BasicStroke.JOIN_BEVEL,1f,new float[] {10,5},0f - ), - new BasicStroke(1f,BasicStroke.CAP_BUTT, - BasicStroke.JOIN_BEVEL,1f,new float[] {5,5},0f - ), - new BasicStroke(1f,BasicStroke.CAP_BUTT, - BasicStroke.JOIN_BEVEL,1f,new float[] {1.5f,3},0f - ), - new BasicStroke(1f,BasicStroke.CAP_BUTT, - BasicStroke.JOIN_BEVEL,1f,new float[] {10,3,3,3},0f - ), - }; - - private static class StrokeWidth { - String title; - float width; - StrokeWidth(String title, float width) { - this.title = title; - this.width = width; - } - } - - private static StrokeWidth[] strokeWidths = new StrokeWidth[] { - new StrokeWidth("x 1", 1), - new StrokeWidth("x 1.5", 1.5f), - new StrokeWidth("x 2", 2), - new StrokeWidth("x 3", 3), - }; + private final LegendListPanel listSeries; + private final LegendListPanel listLines; - //@TODO: May be it is better to have a cache of all possible strokes to avoid unneseccary garbage ? - private static BasicStroke strokeWithWidth(BasicStroke stroke, float width) { - if (stroke.getLineWidth() == width) return stroke; - - return new BasicStroke(width, stroke.getEndCap(), stroke.getLineJoin(), - stroke.getMiterLimit(), stroke.getDashArray(), stroke.getDashPhase()); - } - private static BasicStroke defaultStroke = strokeWithWidth(strokes[0], 2f); - - public ChartConfigPanel(Chart chart, List names, List xIndex, List yIndex) { + public ChartConfigPanel(Chart chart, String[] names) { super(BoxLayout.Y_AXIS); this.chart = chart; - this.names = names; - this.xIndex = xIndex; - this.yIndex = yIndex; - int count = yIndex.size(); setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); - Box boxStyle = Box.createHorizontalBox(); - boxStyle.add(new JLabel("Type: ")); - comboCharType = new JComboBox<>(ChartType.values()); comboCharType.setMaximumSize(new Dimension(Integer.MAX_VALUE, comboCharType.getPreferredSize().height)); comboCharType.addActionListener(this::charTypeSelected); - ChartType chartType = (ChartType) comboCharType.getSelectedItem(); + Box boxStyle = Box.createHorizontalBox(); + boxStyle.add(new JLabel("Type: ")); boxStyle.add(comboCharType); boxStyle.add(Box.createHorizontalGlue()); add(boxStyle); + comboX = new JComboBox<>(names); + comboX.setMaximumSize(new Dimension(Integer.MAX_VALUE, comboX.getPreferredSize().height)); + comboX.addActionListener(e -> validateState() ); + Box boxDomain = Box.createHorizontalBox(); boxDomain.add(new JLabel("Domain axis: ")); - - String[] xItems = xIndex.stream().map(names::get).toArray(String[]::new); - comboX = new JComboBox<>(xItems); - comboX.setMaximumSize(new Dimension(Integer.MAX_VALUE, comboX.getPreferredSize().height)); - comboX.addActionListener(this); boxDomain.add(comboX); boxDomain.add(Box.createHorizontalGlue()); add(boxDomain); - Box boxSeries = Box.createHorizontalBox(); - boxSeries.add(new JLabel("Series:")); - boxSeries.add(Box.createHorizontalGlue()); - - chkAll = new JCheckBox("All", true); - chkAll.addActionListener(this::allSeriesClicked); - boxSeries.add(chkAll); - - add(boxSeries); - - pnlLagend = new JPanel(); - GroupLayout layout = new GroupLayout(pnlLagend); - pnlLagend.setLayout(layout); - - GroupLayout.ParallelGroup chkGroup = layout.createParallelGroup(GroupLayout.Alignment.LEADING); - GroupLayout.ParallelGroup iconGroup = layout.createParallelGroup(GroupLayout.Alignment.LEADING); - GroupLayout.SequentialGroup rowsGroup = layout.createSequentialGroup(); - - chkY = new JCheckBox[count]; - icons = new LegendIcon[count]; - JComponent[] iconPanels = new JComponent[count]; - for (int i=0; i refresh() ); + for (int index = 0; index < names.length; index++) { + LegendIcon icon = new LegendIcon(LegendButton.BASE_COLORS[index % LegendButton.BASE_COLORS.length], LegendButton.SHAPES[index % LegendButton.SHAPES.length], + LegendButton.getDefaultStroke()); + icon.setChartType(ChartType.values()[0]); + listSeries.add(names[index], icon); } + listSeries.setEnabled(0, false); + + listLines = new LegendListPanel("Lines:", true, false, false); + listLines.addChangeListener(e -> refresh() ); - Box glue = Box.createVerticalBox(); + Box boxIndent = Box.createHorizontalBox(); + boxIndent.add(Box.createRigidArea(new Dimension(5,20))); + JComponent filler = new Box.Filler(new Dimension(0,0), new Dimension(0, 0), new Dimension(Integer.MAX_VALUE, 1)); + filler.setBorder(BorderFactory.createLineBorder(Color.GRAY)); + boxIndent.add(filler); + boxIndent.add(Box.createRigidArea(new Dimension(5,20))); - chkGroup.addComponent(glue); - rowsGroup.addComponent(glue); - layout.setHorizontalGroup(layout.createSequentialGroup() - .addGroup(chkGroup) - .addGroup(iconGroup) - ); + Box scrollPaneContent = Box.createVerticalBox(); + scrollPaneContent.add(listSeries); + scrollPaneContent.add(boxIndent); + scrollPaneContent.add(listLines); + scrollPaneContent.add(Box.createVerticalBox()); - layout.setVerticalGroup(rowsGroup); + scrollPaneContent.setBorder(BorderFactory.createEmptyBorder(2,2,2,2)); + add(new JScrollPane(scrollPaneContent)); - add(new JScrollPane(pnlLagend)); - validateState(); - colorChoosePreviewIcon = new LegendIcon(null, null, null); + listSeries.setEnabled(0, false); } public int getDomainIndex() { - return xIndex.get(comboX.getSelectedIndex()); + return comboX.getSelectedIndex(); } public boolean isSeriesEnables(int index) { - return chkY[index].isSelected() && chkY[index].isEnabled(); - } - - public Paint getColor(int index) { - return icons[index].getColor(); + return listSeries.isSelected(index); } - public Shape getShape(int index) { - return icons[index].getShape(); + public LegendIcon getLegendIcon(int index) { + return listSeries.getIcon(index); } - public Stroke getStroke(int index) { - return icons[index].getStroke(); + public void addLine(Line aLine) { + LegendIcon icon = new LegendIcon(Color.BLACK, null, LegendButton.getDefaultStroke()); + listLines.add("Line " + (1+ listLines.getListSize()), icon); } - public ChartType getChartType(int index) { - return icons[index].getChartType(); - } - - @Override - public void actionPerformed(ActionEvent e) { - pnlLagend.repaint(); - validateState(); + private void refresh() { + invalidate(); + repaint(); chart.refreshPlot(); } private void validateState() { - for (JCheckBox checkBox: chkY) { - checkBox.setEnabled(true); - } - int x = xIndex.get(comboX.getSelectedIndex()); - int index = yIndex.indexOf(x); - if (index > -1) { - chkY[index].setEnabled(false); - } - } - - private void allSeriesClicked(ActionEvent e) { - for (JCheckBox checkBox: chkY) { - checkBox.setSelected(chkAll.isSelected()); + int domainIndex = getDomainIndex(); + int count = listSeries.getListSize(); + for (int index = 0; index < count; index++) { + listSeries.setEnabled(index, index !=domainIndex); } - actionPerformed(e); + refresh(); } private void charTypeSelected(ActionEvent e) { ChartType chartType = (ChartType) comboCharType.getSelectedItem(); - for (LegendIcon icon: icons) { - icon.setChartType(chartType); - } - actionPerformed(e); - } - - private void legendPressed(int index, MouseEvent e) { - Paint theColor = icons[index].getColor(); - Shape theShape = icons[index].getShape(); - BasicStroke theStroke = icons[index].getStroke(); - ChartType theChartType = icons[index].getChartType(); - - JPopupMenu popup = new JPopupMenu(); - - JMenuItem menu = new JMenuItem("Change color", new SquareIcon(theColor, 15)); - menu.addActionListener(actionEvent -> showChangeColor(index, actionEvent)); - popup.add(menu); - - JMenu subMenu = new JMenu("Change type"); - for (ChartType chartType : ChartType.values()) { - LegendIcon icon = new LegendIcon(theColor, theShape, theStroke); - icon.setChartType(chartType); - - JCheckBoxMenuItem item = new JCheckBoxMenuItem(chartType.toString(), icon, theChartType == chartType); - item.addActionListener(actionEvent -> changeChartType(index, chartType, actionEvent)); - subMenu.add(item); - } - popup.add(subMenu); - - if (icons[index].getChartType().hasShape()) { - subMenu = new JMenu("Change shape"); - for (Shape shape: shapes) { - LegendIcon icon = new LegendIcon(theColor, shape, theStroke); - icon.setChartType(theChartType); - - JCheckBoxMenuItem item = new JCheckBoxMenuItem("", icon, theShape == shape); - item.addActionListener(actionEvent -> changeShape(index, shape, actionEvent)); - subMenu.add(item); - } - popup.add(subMenu); - - menu = new JMenuItem("Set this shape to all"); - menu.addActionListener(actionEvent -> setShapeToAll(index, actionEvent)); - popup.add(menu); - } - - if (icons[index].getChartType().hasLine()) { - float theWidth = theStroke.getLineWidth(); - subMenu = new JMenu("Change stroke"); - for (BasicStroke stroke: strokes) { - BasicStroke aStroke = strokeWithWidth(stroke, theWidth); - LegendIcon icon = new LegendIcon(theColor, null, aStroke); - JCheckBoxMenuItem item = new JCheckBoxMenuItem("", icon, theStroke.equals(strokeWithWidth(stroke, theWidth))); - item.addActionListener(actionEvent -> changeStroke(index, aStroke, actionEvent)); - subMenu.add(item); - } - subMenu.addSeparator(); - - for (StrokeWidth strokeWidth: strokeWidths) { - boolean selected = theWidth == strokeWidth.width; - LegendIcon icon = new LegendIcon(theColor, null, strokeWithWidth(theStroke, strokeWidth.width)); - - JCheckBoxMenuItem item = new JCheckBoxMenuItem(strokeWidth.title, icon, selected); - item.addActionListener(actionEvent -> changeStrokeWidth(index, strokeWidth.width, actionEvent)); - subMenu.add(item); - } - popup.add(subMenu); - - menu = new JMenuItem("Set this stroke to all"); - menu.addActionListener(actionEvent -> setStrokeToAll(index, actionEvent)); - popup.add(menu); - } - - popup.show(e.getComponent(), e.getX(), e.getY()); - } - - private void showChangeColor(int index, ActionEvent e) { - Paint paint = icons[index].getColor(); - Color color = Color.BLACK; - if (paint instanceof Color) { - color = (Color)paint; - } - colorChoosePreviewIcon.setColor(color); - colorChoosePreviewIcon.setShape(icons[index].getShape()); - colorChoosePreviewIcon.setStroke(icons[index].getStroke()); - colorChoosePreviewIcon.setChartType(icons[index].getChartType()); - Color result = ColorChooser.chooseColor(pnlLagend, "Choose color", color, - new JLabel(colorChoosePreviewIcon, SwingConstants.CENTER), - newColor -> colorChoosePreviewIcon.setColor(newColor) - ); - - if (result != null) { - icons[index].setColor(result); - actionPerformed(e); - } - } - - private void changeChartType(int index, ChartType chartType, ActionEvent e) { - icons[index].setChartType(chartType); - actionPerformed(e); - } - - private void changeShape(int index, Shape shape, ActionEvent e) { - icons[index].setShape(shape); - actionPerformed(e); - } - - private void setShapeToAll(int index, ActionEvent e) { - Shape shape = icons[index].getShape(); - for (LegendIcon icon: icons) { - icon.setShape(shape); - } - actionPerformed(e); - } - - private void changeStroke(int index, BasicStroke stroke, ActionEvent e) { - icons[index].setStroke(stroke); - actionPerformed(e); - } - - private void changeStrokeWidth(int index, float width, ActionEvent e) { - icons[index].setStroke(strokeWithWidth(icons[index].getStroke(), width)); - actionPerformed(e); - } - - private void setStrokeToAll(int index, ActionEvent e) { - BasicStroke stroke = icons[index].getStroke(); - for (LegendIcon icon: icons) { - icon.setStroke(stroke); + int count = listSeries.getListSize(); + for (int index = 0; index < count; index++) { + listSeries.getIcon(index).setChartType(chartType); } - actionPerformed(e); + refresh(); } } diff --git a/src/studio/ui/chart/LegendButton.java b/src/studio/ui/chart/LegendButton.java new file mode 100644 index 0000000..f2f742e --- /dev/null +++ b/src/studio/ui/chart/LegendButton.java @@ -0,0 +1,257 @@ +package studio.ui.chart; + +import org.jfree.chart.plot.DefaultDrawingSupplier; +import studio.ui.ColorChooser; +import studio.ui.chart.event.LegendChangeEvent; +import studio.ui.chart.event.LegendChangeListener; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.EtchedBorder; +import javax.swing.event.EventListenerList; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.util.ArrayList; +import java.util.List; + +public class LegendButton extends JLabel implements MouseListener { + + private final EventListenerList listenerList = new EventListenerList(); + private final List chartTypeList = new ArrayList<>(4); + + private final static Border EMPTY_BORDER = BorderFactory.createEmptyBorder(2,2,2,2); + private final static Border SELECTED_BORDER = BorderFactory.createEtchedBorder(EtchedBorder.LOWERED); + + public static final Paint[] BASE_COLORS = DefaultDrawingSupplier.DEFAULT_PAINT_SEQUENCE; + public static final Shape[] SHAPES = DefaultDrawingSupplier.DEFAULT_SHAPE_SEQUENCE; + public static final BasicStroke[] BASE_STROKES = new BasicStroke[] { + new BasicStroke(1f), + new BasicStroke(1f,BasicStroke.CAP_BUTT, + BasicStroke.JOIN_BEVEL,1f,new float[] {10,10},0f + ), + new BasicStroke(1f,BasicStroke.CAP_BUTT, + BasicStroke.JOIN_BEVEL,1f,new float[] {10,5},0f + ), + new BasicStroke(1f,BasicStroke.CAP_BUTT, + BasicStroke.JOIN_BEVEL,1f,new float[] {5,5},0f + ), + new BasicStroke(1f,BasicStroke.CAP_BUTT, + BasicStroke.JOIN_BEVEL,1f,new float[] {1.5f,3},0f + ), + new BasicStroke(1f,BasicStroke.CAP_BUTT, + BasicStroke.JOIN_BEVEL,1f,new float[] {10,3,3,3},0f + ), + }; + + private static class StrokeWidth { + String title; + float width; + StrokeWidth(String title, float width) { + this.title = title; + this.width = width; + } + } + + private static StrokeWidth[] STROKE_WIDTHS = new StrokeWidth[] { + new StrokeWidth("x 1", 1), + new StrokeWidth("x 1.5", 1.5f), + new StrokeWidth("x 2", 2), + new StrokeWidth("x 3", 3), + }; + + //@TODO: May be it is better to have a cache of all possible strokes to avoid unneseccary garbage ? + private static BasicStroke strokeWithWidth(BasicStroke stroke, float width) { + if (stroke.getLineWidth() == width) return stroke; + + return new BasicStroke(width, stroke.getEndCap(), stroke.getLineJoin(), + stroke.getMiterLimit(), stroke.getDashArray(), stroke.getDashPhase()); + } + + public static BasicStroke getDefaultStroke() { + return strokeWithWidth(BASE_STROKES[0], STROKE_WIDTHS[0].width); + } + + public LegendButton(LegendIcon icon, boolean line, boolean shape, boolean bar) { + super(icon); + + if (line) chartTypeList.add(ChartType.LINE); + if (shape) chartTypeList.add(ChartType.SHAPE); + if (line && shape) chartTypeList.add(ChartType.LINE_SHAPE); + if (bar) chartTypeList.add(ChartType.BAR); + + setBorder(EMPTY_BORDER); + addMouseListener(this); + } + + public LegendIcon getLegendIcon() { + return (LegendIcon) getIcon(); + } + + public void addChangeListener(LegendChangeListener listener) { + listenerList.add(LegendChangeListener.class, listener); + } + + public void removeChangeListener(LegendChangeListener listener) { + listenerList.remove(LegendChangeListener.class, listener); + } + + private void notifyChange() { + LegendChangeEvent event = new LegendChangeEvent(this, getLegendIcon()); + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length - 2; i >= 0; i -= 2) { + if (listeners[i] == LegendChangeListener.class) { + ((LegendChangeListener) listeners[i + 1]).legendChanged(event); + } + } + } + + private void notifyChangeAllStrokes() { + LegendChangeEvent event = new LegendChangeEvent(this, getLegendIcon()); + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length - 2; i >= 0; i -= 2) { + if (listeners[i] == LegendChangeListener.class) { + ((LegendChangeListener) listeners[i + 1]).changeAllStrokes(event); + } + } + } + + private void notifyChangeAllShapes() { + LegendChangeEvent event = new LegendChangeEvent(this, getLegendIcon()); + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length - 2; i >= 0; i -= 2) { + if (listeners[i] == LegendChangeListener.class) { + ((LegendChangeListener) listeners[i + 1]).changeAllShapes(event); + } + } + } + + @Override + public void mouseClicked(MouseEvent e) {} + @Override + public void mouseReleased(MouseEvent e) {} + + @Override + public void mouseEntered(MouseEvent e) { + if (!isEnabled()) return; + setBorder(SELECTED_BORDER); + } + + @Override + public void mouseExited(MouseEvent e) { + if (!isEnabled()) return; + setBorder(EMPTY_BORDER); + } + + @Override + public void mousePressed(MouseEvent event) { + if (!isEnabled()) return; + + LegendIcon icon = getLegendIcon(); + Paint color = icon.getColor(); + Shape shape = icon.getShape(); + BasicStroke stroke = icon.getStroke(); + ChartType chartType = icon.getChartType(); + + JPopupMenu popup = new JPopupMenu(); + + JMenuItem menu = new JMenuItem("Change color", new SquareIcon(color, 15)); + menu.addActionListener(this::showChangeColor); + popup.add(menu); + + if (chartTypeList.size() > 1) { + JMenu subMenu = new JMenu("Change type"); + for (ChartType menuCharType: chartTypeList) { + LegendIcon menuIcon = new LegendIcon(color, shape, stroke); + menuIcon.setChartType(menuCharType); + + JCheckBoxMenuItem item = new JCheckBoxMenuItem(menuCharType.toString(), menuIcon, chartType == menuCharType); + item.addActionListener(e -> { + icon.setChartType(menuCharType); + notifyChange(); + }); + subMenu.add(item); + } + popup.add(subMenu); + } + + if (chartType.hasShape()) { + JMenu subMenu = new JMenu("Change shape"); + for (Shape menuShape: SHAPES) { + LegendIcon menuIcon = new LegendIcon(color, menuShape, stroke); + menuIcon.setChartType(chartType); + + JCheckBoxMenuItem item = new JCheckBoxMenuItem("", menuIcon, shape == menuShape); + item.addActionListener(e -> { + icon.setShape(menuShape); + notifyChange(); + }); + subMenu.add(item); + } + popup.add(subMenu); + + menu = new JMenuItem("Set this shape to all"); + menu.addActionListener(e -> notifyChangeAllShapes() ); + popup.add(menu); + } + + if (chartType.hasLine()) { + float width = stroke.getLineWidth(); + JMenu subMenu = new JMenu("Change stroke"); + for (BasicStroke baseStroke: BASE_STROKES) { + BasicStroke menuStroke = strokeWithWidth(baseStroke, width); + LegendIcon menuIcon = new LegendIcon(color, null, menuStroke); + JCheckBoxMenuItem item = new JCheckBoxMenuItem("", menuIcon, menuStroke.equals(stroke)); + item.addActionListener(e -> { + icon.setStroke(menuStroke); + notifyChange(); + }); + subMenu.add(item); + } + subMenu.addSeparator(); + + for (StrokeWidth strokeWidth: STROKE_WIDTHS) { + boolean selected = width == strokeWidth.width; + BasicStroke menuStroke = strokeWithWidth(stroke, strokeWidth.width); + LegendIcon menuIcon = new LegendIcon(color, null, menuStroke); + + JCheckBoxMenuItem item = new JCheckBoxMenuItem(strokeWidth.title, menuIcon, selected); + item.addActionListener(e -> { + icon.setStroke(menuStroke); + notifyChange(); + }); + subMenu.add(item); + } + popup.add(subMenu); + + menu = new JMenuItem("Set this stroke to all"); + menu.addActionListener(e -> notifyChangeAllStrokes() ); + popup.add(menu); + } + + popup.show(event.getComponent(), event.getX(), event.getY()); + + } + + private void showChangeColor(ActionEvent e) { + LegendIcon icon = getLegendIcon(); + Paint paint = icon.getColor(); + Color color = Color.BLACK; + if (paint instanceof Color) { + color = (Color)paint; + } + LegendIcon colorChoosePreviewIcon = new LegendIcon(color, icon.getShape(), icon.getStroke()); + colorChoosePreviewIcon.setChartType(icon.getChartType()); + Color result = ColorChooser.chooseColor(this, "Choose color", color, + new JLabel(colorChoosePreviewIcon, SwingConstants.CENTER), + colorChoosePreviewIcon::setColor + ); + + if (result != null) { + icon.setColor(result); + notifyChange(); + } + } + +} diff --git a/src/studio/ui/chart/LegendListPanel.java b/src/studio/ui/chart/LegendListPanel.java new file mode 100644 index 0000000..8241055 --- /dev/null +++ b/src/studio/ui/chart/LegendListPanel.java @@ -0,0 +1,148 @@ +package studio.ui.chart; + +import studio.ui.chart.event.LegendChangeEvent; +import studio.ui.chart.event.LegendChangeListener; + +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.EventListenerList; +import java.util.ArrayList; +import java.util.List; + +public class LegendListPanel extends Box implements LegendChangeListener { + + private final boolean line, shape, bar; + private final List checkBoxes = new ArrayList<>(); + private final List buttons = new ArrayList<>(); + private final JPanel panel; + private final EventListenerList listenerList = new EventListenerList(); + + + public LegendListPanel(String labelText, boolean line, boolean shape, boolean bar) { + super(BoxLayout.Y_AXIS); + this.line = line; + this.shape = shape; + this.bar = bar; + + JCheckBox chkAll = new JCheckBox("All", true); + chkAll.addActionListener(e -> { + for (JCheckBox checkBox: checkBoxes) { + checkBox.setSelected(chkAll.isSelected()); + } + notifyListeners(); + }); + + panel = new JPanel(); + + Box boxTop = Box.createHorizontalBox(); + boxTop.add(new JLabel(labelText)); + boxTop.add(Box.createHorizontalGlue()); + boxTop.add(chkAll); + + Box boxCentral = Box.createHorizontalBox(); + boxCentral.add(panel); + boxCentral.add(Box.createHorizontalGlue()); + + add(boxTop); + add(boxCentral); + + updateLayout(); + } + + public void add(String title, LegendIcon icon) { + JCheckBox checkBox = new JCheckBox(title, true); + checkBox.addActionListener(e -> notifyListeners() ); + LegendButton button = new LegendButton(icon, line, shape, bar); + button.addChangeListener(this); + + checkBoxes.add(checkBox); + buttons.add(button); + updateLayout(); + } + + public int getListSize() { + return buttons.size(); + } + + public LegendIcon getIcon(int index) { + return buttons.get(index).getLegendIcon(); + } + + public boolean isSelected(int index) { + return checkBoxes.get(index).isSelected(); + } + + public void setEnabled(int index, boolean enabled) { + checkBoxes.get(index).setEnabled(enabled); + buttons.get(index).setEnabled(enabled); + buttons.get(index).invalidate(); + } + + private void updateLayout() { + GroupLayout layout = new GroupLayout(panel); + + GroupLayout.ParallelGroup chkGroup = layout.createParallelGroup(GroupLayout.Alignment.LEADING); + GroupLayout.ParallelGroup iconGroup = layout.createParallelGroup(GroupLayout.Alignment.LEADING); + GroupLayout.SequentialGroup rowsGroup = layout.createSequentialGroup(); + + for (int i=0; i < buttons.size(); i++) { + JCheckBox chk = checkBoxes.get(i); + LegendButton button = buttons.get(i); + chkGroup.addComponent(chk); + iconGroup.addComponent(button); + + rowsGroup.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(chk) + .addComponent(button) ); + } + + layout.setHorizontalGroup(layout.createSequentialGroup() + .addGroup(chkGroup) + .addGroup(iconGroup) ); + + layout.setVerticalGroup(rowsGroup); + + panel.setLayout(layout); + } + + @Override + public void legendChanged(LegendChangeEvent event) { + notifyListeners(); + } + + @Override + public void changeAllStrokes(LegendChangeEvent event) { + for (int i=0; i < buttons.size(); i++) { + buttons.get(i).getLegendIcon().setStroke(event.getIcon().getStroke()); + } + notifyListeners(); + } + + @Override + public void changeAllShapes(LegendChangeEvent event) { + for (int i=0; i < buttons.size(); i++) { + buttons.get(i).getLegendIcon().setShape(event.getIcon().getShape()); + } + notifyListeners(); + } + + public void addChangeListener(ChangeListener listener) { + listenerList.add(ChangeListener.class, listener); + } + + public void removeChangeListener(ChangeListener listener) { + listenerList.remove(ChangeListener.class, listener); + } + + private void notifyListeners() { + this.repaint(); + ChangeEvent event = new ChangeEvent(this); + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length - 2; i >= 0; i -= 2) { + if (listeners[i] == ChangeListener.class) { + ((ChangeListener) listeners[i + 1]).stateChanged(event); + } + } + } +} diff --git a/src/studio/ui/chart/event/LegendChangeEvent.java b/src/studio/ui/chart/event/LegendChangeEvent.java new file mode 100644 index 0000000..874e8ff --- /dev/null +++ b/src/studio/ui/chart/event/LegendChangeEvent.java @@ -0,0 +1,19 @@ +package studio.ui.chart.event; + +import studio.ui.chart.LegendIcon; + +import java.util.EventObject; + +public class LegendChangeEvent extends EventObject { + + private LegendIcon icon; + + public LegendChangeEvent(Object source, LegendIcon icon) { + super(source); + this.icon = icon; + } + + public LegendIcon getIcon() { + return icon; + } +} diff --git a/src/studio/ui/chart/event/LegendChangeListener.java b/src/studio/ui/chart/event/LegendChangeListener.java new file mode 100644 index 0000000..e441b55 --- /dev/null +++ b/src/studio/ui/chart/event/LegendChangeListener.java @@ -0,0 +1,10 @@ +package studio.ui.chart.event; + +import java.util.EventListener; + +public interface LegendChangeListener extends EventListener { + + void legendChanged(LegendChangeEvent event); + void changeAllStrokes(LegendChangeEvent event); + void changeAllShapes(LegendChangeEvent event); +}