package holeg.ui.view.information; import holeg.model.AbstractCanvasObject; import holeg.model.Flexibility; import holeg.model.GroupNode; import holeg.model.HolonElement; import holeg.model.HolonObject; import holeg.model.HolonObject.HolonObjectState; import holeg.model.HolonSwitch; import holeg.model.Node; import holeg.preferences.ColorPreference; import holeg.preferences.ImagePreference; import holeg.ui.controller.Control; import holeg.ui.model.GuiSettings; import holeg.ui.view.image.Import; import holeg.utility.math.decimal.Format; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.Insets; import java.util.List; import java.util.Map; import java.util.logging.Logger; import java.util.stream.Collectors; import javax.swing.BorderFactory; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JCheckBoxMenuItem; import javax.swing.JLabel; import javax.swing.JLayeredPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JToggleButton; import javax.swing.OverlayLayout; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import org.knowm.xchart.PieChart; import org.knowm.xchart.PieChartBuilder; import org.knowm.xchart.PieSeries.PieSeriesRenderStyle; import org.knowm.xchart.XChartPanel; import org.knowm.xchart.style.PieStyler; import org.knowm.xchart.style.PieStyler.LabelType; public class HolonInformationPanel extends JPanel { private static final Logger log = Logger.getLogger(HolonInformationPanel.class.getName()); private static final Color[] supplyStateColors = new Color[]{ColorPreference.HolonObject.Producer, ColorPreference.HolonObject.OverSupplied, ColorPreference.HolonObject.Supplied, ColorPreference.HolonObject.PartiallySupplied, ColorPreference.HolonObject.NotSupplied, ColorPreference.HolonObject.NoEnergy, ColorPreference.InformationPanel.NoData}; private static final Color[] productionColors = new Color[]{ColorPreference.Energy.Production, ColorPreference.Energy.Consumption, ColorPreference.InformationPanel.NoData}; private static final Color[] activeColors = new Color[]{ColorPreference.Element.Active, ColorPreference.Element.Inactive, ColorPreference.InformationPanel.NoData}; private static final Color[] flexibilityColors = new Color[]{ColorPreference.Flexibility.Offered, ColorPreference.Flexibility.InUse, ColorPreference.Flexibility.OnCooldown, ColorPreference.Flexibility.NotOffered, ColorPreference.Flexibility.Unavailable, ColorPreference.Flexibility.NoFlexibility, ColorPreference.InformationPanel.NoData}; private static final Color[] priorityColors = new Color[]{ ColorPreference.Element.Priority.Essential, ColorPreference.Element.Priority.High, ColorPreference.Element.Priority.Medium, ColorPreference.Element.Priority.Low, ColorPreference.InformationPanel.NoData}; private final int defaultWidth = 50; private final int defaultHeight = 200; private final PieChart supplyChart = createSupplyStateChart(); private final PieChart priorityChart = createPriorityChart(); private final PieChart flexibilityChart = createFlexibilityChart(); private final PieChart energyChart = createProductionChart(); private final PieChart activeChart = createActiveChart(); private final JPanel graphPanel = new JPanel(new GridLayout(0, 2)); private final JCheckBoxMenuItem producerCheckBox = new JCheckBoxMenuItem("Producer", true); private final JCheckBoxMenuItem overSuppliedCheckBox = new JCheckBoxMenuItem("Over supplied", true); private final JCheckBoxMenuItem suppliedCheckBox = new JCheckBoxMenuItem("Supplied", true); private final JCheckBoxMenuItem partiallySuppliedCheckBox = new JCheckBoxMenuItem( "Partial supplied", true); private final JCheckBoxMenuItem notSuppliedCheckBox = new JCheckBoxMenuItem("Not supplied", true); private final JCheckBoxMenuItem noEnergyCheckBox = new JCheckBoxMenuItem("No energy", true); private final JCheckBoxMenuItem essentialCheckBox = new JCheckBoxMenuItem("Essential", true); private final JCheckBoxMenuItem highCheckBox = new JCheckBoxMenuItem("High", true); private final JCheckBoxMenuItem mediumBox = new JCheckBoxMenuItem("Medium", true); private final JCheckBoxMenuItem lowCheckBox = new JCheckBoxMenuItem("Low", true); private final JCheckBoxMenuItem activeCheckBox = new JCheckBoxMenuItem("Active", true); private final JCheckBoxMenuItem inactiveCheckBox = new JCheckBoxMenuItem("Inactive", true); private final JCheckBoxMenuItem noFlexibilityCheckBox = new JCheckBoxMenuItem("No Flexibility", true); private final JCheckBoxMenuItem inUseCheckBox = new JCheckBoxMenuItem("In use", true); private final JCheckBoxMenuItem offeredCheckBox = new JCheckBoxMenuItem("Offered", true); private final JCheckBoxMenuItem notOfferedCheckBox = new JCheckBoxMenuItem("Not offered", true); private final JCheckBoxMenuItem onCooldownCheckBox = new JCheckBoxMenuItem("On cooldown", true); private final JCheckBoxMenuItem unavailableCheckBox = new JCheckBoxMenuItem("Unavailable", true); private final Control control; private final JLabel differenceEnergyLabelAmount = new JLabel(""); public HolonInformationPanel(Control control) { control.OnSelectionChanged.addListener(this::updateCharts); control.OnCanvasUpdate.addListener(this::updateCharts); this.control = control; this.setLayout(new BorderLayout()); initGraphPanel(); this.setBackground(ColorPreference.Panel.Background); this.add(graphPanel, BorderLayout.CENTER); } public void updateCharts() { TempGroupNode tempGroupNode = new TempGroupNode(); if (GuiSettings.getSelectedObjects().isEmpty()) { tempGroupNode.add(control.getModel().getCanvas()); } else { tempGroupNode.addAll(GuiSettings.getSelectedObjects()); } List filteredHolonObjectList = tempGroupNode.getAllHolonObjectsRecursive() .filter(this::stateFilter).toList(); Map stateMap = filteredHolonObjectList.stream() .collect(Collectors.groupingBy(HolonObject::getState, Collectors.counting())); // UPDATE SUPPLY STATE int producerAmount = Math.toIntExact(stateMap.getOrDefault(HolonObjectState.PRODUCER, 0L)); int overSuppliedAmount = Math.toIntExact( stateMap.getOrDefault(HolonObjectState.OVER_SUPPLIED, 0L)); int suppliedAmount = Math.toIntExact(stateMap.getOrDefault(HolonObjectState.SUPPLIED, 0L)); int partiallySuppliedAmount = Math.toIntExact( stateMap.getOrDefault(HolonObjectState.PARTIALLY_SUPPLIED, 0L)); int notSuppliedAmount = Math.toIntExact( stateMap.getOrDefault(HolonObjectState.NOT_SUPPLIED, 0L)); int noEnergyAmount = Math.toIntExact(stateMap.getOrDefault(HolonObjectState.NO_ENERGY, 0L)); supplyChart.updatePieSeries("Producer", producerAmount); supplyChart.updatePieSeries("Over supplied", overSuppliedAmount); supplyChart.updatePieSeries("Supplied", suppliedAmount); supplyChart.updatePieSeries("Partial supplied", partiallySuppliedAmount); supplyChart.updatePieSeries("Not supplied", notSuppliedAmount); supplyChart.updatePieSeries("No energy", noEnergyAmount); supplyChart.updatePieSeries("No Data", filteredHolonObjectList.isEmpty() ? 1 : 0); //UPDATE PRIORITIES List filteredHolonElements = filteredHolonObjectList.stream() .flatMap(HolonObject::elementsStream) .filter(ele -> priorityFilter(ele) && activeFilter(ele) && flexibilityFilter(ele)).toList(); Map priorityCounts = filteredHolonElements.stream() .collect(Collectors.groupingBy(HolonElement::getPriority, Collectors.counting())); long essential = priorityCounts.getOrDefault(HolonElement.Priority.Essential, 0L); long high = priorityCounts.getOrDefault(HolonElement.Priority.High, 0L); long medium = priorityCounts.getOrDefault(HolonElement.Priority.Medium, 0L); long low = priorityCounts.getOrDefault(HolonElement.Priority.Low, 0L); priorityChart.updatePieSeries("Essential", essential); priorityChart.updatePieSeries("High", high); priorityChart.updatePieSeries("Medium", medium); priorityChart.updatePieSeries("Low", low); boolean hasPriority = essential + high + medium + low > 0; priorityChart.updatePieSeries("No Data", hasPriority ? 0 : 1); // UPDATE PRODUCTION float consumption = filteredHolonElements.stream() .filter(element -> element.getActualEnergy() < 0) .map(element -> -element.getActualEnergy()).reduce(0.0f, Float::sum); float production = filteredHolonElements.stream() .map(HolonElement::getActualEnergy).filter(energy -> energy > 0).reduce(0.0f, Float::sum); float difference = Math.abs(production - consumption); energyChart.updatePieSeries("Production", production); energyChart.updatePieSeries("Consumption", consumption); energyChart.updatePieSeries("No Data", production + consumption == 0 ? 1 : 0); differenceEnergyLabelAmount.setText(Format.doubleFixedPlaces(1, difference)); // UPDATE FLEXIBILITIES int inUse = 0; int offered = 0; int onCooldown = 0; int notOffered = 0; int unavailable = 0; List flexList = filteredHolonElements.stream() .flatMap(ele -> ele.flexList.stream()).toList(); for (Flexibility flex : flexList) { switch (flex.getState()) { case IN_USE -> inUse++; case NOT_OFFERED -> notOffered++; case OFFERED -> offered++; case ON_COOLDOWN -> onCooldown++; case UNAVAILABLE -> unavailable++; default -> { } } } int noFlexibility = (int) filteredHolonElements.stream().filter(ele -> ele.flexList.isEmpty()) .count(); flexibilityChart.updatePieSeries("No flexibility", noFlexibility); flexibilityChart.updatePieSeries("Offered", offered); flexibilityChart.updatePieSeries("In use", inUse); flexibilityChart.updatePieSeries("On cooldown", onCooldown); flexibilityChart.updatePieSeries("Not offered", notOffered); flexibilityChart.updatePieSeries("Unavailable", unavailable); boolean hasFlex = noFlexibility + offered + inUse + onCooldown + notOffered + unavailable > 0; flexibilityChart.updatePieSeries("No Data", hasFlex ? 0 : 1); Map activeCounts = filteredHolonElements.stream() .collect(Collectors.groupingBy(HolonElement::isOn, Collectors.counting())); // UPDATE ActiveInActive int activeAmount = Math.toIntExact(activeCounts.getOrDefault(true, 0L)); int inactiveAmounts = Math.toIntExact(activeCounts.getOrDefault(false, 0L)); activeChart.updatePieSeries("Active", activeAmount); activeChart.updatePieSeries("Inactive", inactiveAmounts); activeChart.updatePieSeries("No Data", activeAmount + inactiveAmounts == 0 ? 1 : 0); this.revalidate(); this.repaint(); } private JPopupMenu createHolonStateSelection() { JPopupMenu menu = new JPopupMenu(); producerCheckBox.addActionListener(clicked -> updateCharts()); overSuppliedCheckBox.addActionListener(clicked -> updateCharts()); suppliedCheckBox.addActionListener(clicked -> updateCharts()); partiallySuppliedCheckBox.addActionListener(clicked -> updateCharts()); notSuppliedCheckBox.addActionListener(clicked -> updateCharts()); noEnergyCheckBox.addActionListener(clicked -> updateCharts()); menu.add(producerCheckBox); menu.add(overSuppliedCheckBox); menu.add(suppliedCheckBox); menu.add(partiallySuppliedCheckBox); menu.add(notSuppliedCheckBox); menu.add(noEnergyCheckBox); return menu; } private JPopupMenu createPrioritySelection() { JPopupMenu menu = new JPopupMenu(); essentialCheckBox.addActionListener(clicked -> updateCharts()); highCheckBox.addActionListener(clicked -> updateCharts()); mediumBox.addActionListener(clicked -> updateCharts()); lowCheckBox.addActionListener(clicked -> updateCharts()); menu.add(essentialCheckBox); menu.add(highCheckBox); menu.add(mediumBox); menu.add(lowCheckBox); return menu; } private JPopupMenu createActiveSelection() { JPopupMenu menu = new JPopupMenu(); activeCheckBox.addActionListener(clicked -> updateCharts()); inactiveCheckBox.addActionListener(clicked -> updateCharts()); menu.add(activeCheckBox); menu.add(inactiveCheckBox); return menu; } private JPopupMenu createFlexibilitySelection() { JPopupMenu menu = new JPopupMenu(); noFlexibilityCheckBox.addActionListener(clicked -> updateCharts()); offeredCheckBox.addActionListener(clicked -> updateCharts()); notOfferedCheckBox.addActionListener(clicked -> updateCharts()); unavailableCheckBox.addActionListener(clicked -> updateCharts()); inUseCheckBox.addActionListener(clicked -> updateCharts()); onCooldownCheckBox.addActionListener(clicked -> updateCharts()); menu.add(noFlexibilityCheckBox); menu.add(offeredCheckBox); menu.add(notOfferedCheckBox); menu.add(unavailableCheckBox); menu.add(inUseCheckBox); menu.add(onCooldownCheckBox); return menu; } private JPanel initMenuButtonPanel(JPopupMenu menu) { JPanel buttonPanel = new JPanel(new BorderLayout()); MenuButton button = new MenuButton( new ImageIcon(Import.loadImage(ImagePreference.HolonInformationPanel.Filter, 20, 20)), menu); buttonPanel.add(button, BorderLayout.LINE_END); buttonPanel.setOpaque(false); button.setPressedIcon(new ImageIcon( Import.loadImage(ImagePreference.HolonInformationPanel.FilterHovered, 20, 20))); button.setRolloverIcon(new ImageIcon( Import.loadImage(ImagePreference.HolonInformationPanel.FilterHovered, 20, 20))); button.setBorderPainted(false); button.setBorder(null); button.setFocusable(false); button.setMargin(new Insets(0, 0, 0, 0)); button.setContentAreaFilled(false); return buttonPanel; } private boolean stateFilter(HolonObject holonObject) { return switch (holonObject.getState()) { case PRODUCER -> producerCheckBox.isSelected(); case OVER_SUPPLIED -> overSuppliedCheckBox.isSelected(); case SUPPLIED -> suppliedCheckBox.isSelected(); case PARTIALLY_SUPPLIED -> partiallySuppliedCheckBox.isSelected(); case NOT_SUPPLIED -> notSuppliedCheckBox.isSelected(); case NO_ENERGY -> noEnergyCheckBox.isSelected(); }; } private boolean priorityFilter(HolonElement ele) { return switch (ele.priority) { case Essential -> essentialCheckBox.isSelected(); case High -> highCheckBox.isSelected(); case Medium -> mediumBox.isSelected(); case Low -> lowCheckBox.isSelected(); }; } private boolean activeFilter(HolonElement ele) { return ele.active ? activeCheckBox.isSelected() : inactiveCheckBox.isSelected(); } private boolean flexibilityFilter(HolonElement ele) { if (ele.flexList.isEmpty()) { return noFlexibilityCheckBox.isSelected(); } Map flexes = ele.flexList.stream() .collect(Collectors.groupingBy(Flexibility::getState, Collectors.counting())); return flexes.keySet().stream().anyMatch(flexState -> switch (flexState) { case IN_USE -> inUseCheckBox.isSelected(); case UNAVAILABLE -> unavailableCheckBox.isSelected(); case OFFERED -> offeredCheckBox.isSelected(); case NOT_OFFERED -> notOfferedCheckBox.isSelected(); case ON_COOLDOWN -> onCooldownCheckBox.isSelected(); }); } private void initGraphPanel() { graphPanel.setBackground(ColorPreference.Panel.Background); XChartPanel panelHolonObject = new XChartPanel<>(supplyChart); panelHolonObject.setLayout(new BorderLayout()); panelHolonObject.add(initMenuButtonPanel(createHolonStateSelection()), BorderLayout.PAGE_START); panelHolonObject.setBackground(ColorPreference.Panel.Background); graphPanel.add(panelHolonObject); XChartPanel panelPriority = new XChartPanel<>(priorityChart); panelPriority.setLayout(new BorderLayout()); panelPriority.add(initMenuButtonPanel(createPrioritySelection()), BorderLayout.PAGE_START); panelPriority.setBackground(ColorPreference.Panel.Background); graphPanel.add(panelPriority); XChartPanel panelFlexibility = new XChartPanel<>(flexibilityChart); panelFlexibility.setLayout(new BorderLayout()); panelFlexibility.add(initMenuButtonPanel(createFlexibilitySelection()), BorderLayout.PAGE_START); panelFlexibility.setBackground(ColorPreference.Panel.Background); graphPanel.add(panelFlexibility); Component panel = initEnergyChart(); graphPanel.add(panel); XChartPanel panelActive = new XChartPanel<>(activeChart); panelActive.setLayout(new BorderLayout()); panelActive.add(initMenuButtonPanel(createActiveSelection()), BorderLayout.PAGE_START); panelActive.setBackground(ColorPreference.Panel.Background); graphPanel.add(panelActive); graphPanel.setBorder(BorderFactory.createLineBorder(Color.lightGray)); } private JLayeredPane initEnergyChart() { JLayeredPane panel = new JLayeredPane(); JPanel panelMiddle = new JPanel(new GridBagLayout()); XChartPanel panelEnergy = new XChartPanel<>(energyChart); panelEnergy.setBackground(ColorPreference.Panel.Background); GridBagConstraints c = new GridBagConstraints(); c.gridx = 0; c.gridy = 0; c.insets = new Insets(15, 0, 0, 0); // top padding JLabel difference = new JLabel("Difference:"); difference.setHorizontalAlignment(JLabel.CENTER); difference.setAlignmentX(Component.CENTER_ALIGNMENT); difference.setForeground(ColorPreference.Panel.Title); difference.setFont(new java.awt.Font("Arial", java.awt.Font.BOLD, 10)); panelMiddle.add(difference, c); differenceEnergyLabelAmount.setHorizontalAlignment(JLabel.CENTER); differenceEnergyLabelAmount.setForeground(Color.red); differenceEnergyLabelAmount.setAlignmentX(Component.CENTER_ALIGNMENT); differenceEnergyLabelAmount.setFont(new java.awt.Font("Arial", java.awt.Font.BOLD, 12)); c.insets = new Insets(0, 0, 0, 0); c.gridy = 1; panelMiddle.add(differenceEnergyLabelAmount, c); panel.setLayout(new OverlayLayout(panel)); panelEnergy.setOpaque(false); panelMiddle.setOpaque(false); panel.add(panelMiddle, Integer.valueOf(0)); panel.add(panelEnergy, Integer.valueOf(1)); return panel; } public void setDefaultPieChartSettings(PieChart chart) { PieStyler styler = chart.getStyler(); styler.setChartTitleVisible(true); styler.setDefaultSeriesRenderStyle(PieSeriesRenderStyle.Donut); styler.setLabelsFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 14)); styler.setLabelType(LabelType.Percentage); styler.setLabelsFontColor(Color.black); styler.setLabelsFontColorAutomaticEnabled(false); styler.setLabelsDistance(0.8); styler.setChartFontColor(ColorPreference.Panel.Title); styler.setToolTipsEnabled(true); styler.setPlotContentSize(0.9); styler.setPlotBackgroundColor(ColorPreference.Panel.Transparent); styler.setPlotBorderColor(ColorPreference.Panel.Transparent); styler.setLegendVisible(false); styler.setChartBackgroundColor(ColorPreference.Panel.Transparent); } public PieChart createSupplyStateChart() { PieChart chart = new PieChartBuilder().width(defaultWidth).height(defaultHeight) .title("HolonObjects").build(); setDefaultPieChartSettings(chart); chart.getStyler().setSeriesColors(supplyStateColors); // Series chart.addSeries("Producer", 0); chart.addSeries("Over supplied", 0); chart.addSeries("Supplied", 0); chart.addSeries("Partial supplied", 0); chart.addSeries("Not supplied", 0); chart.addSeries("No energy", 0); chart.addSeries("No Data", 0); return chart; } public PieChart createPriorityChart() { PieChart chart = new PieChartBuilder().width(defaultWidth).height(defaultHeight) .title("Priorities").build(); setDefaultPieChartSettings(chart); // Customize Chart chart.getStyler().setSeriesColors(priorityColors); // Series chart.addSeries("Essential", 0); chart.addSeries("High", 0); chart.addSeries("Medium", 0); chart.addSeries("Low", 0); chart.addSeries("No Data", 0); return chart; } public PieChart createFlexibilityChart() { PieChart chart = new PieChartBuilder().width(defaultWidth).height(defaultHeight) .title("Flexibilities").build(); setDefaultPieChartSettings(chart); // Customize Chart chart.getStyler().setSeriesColors(flexibilityColors); // Series chart.addSeries("Offered", 0); chart.addSeries("In use", 0); chart.addSeries("On cooldown", 0); chart.addSeries("Not offered", 0); chart.addSeries("Unavailable", 0); chart.addSeries("No flexibility", 0); chart.addSeries("No Data", 0); return chart; } public PieChart createProductionChart() { PieChart chart = new PieChartBuilder().width(defaultWidth).height(defaultHeight) .title("Production vs. Consumption").build(); setDefaultPieChartSettings(chart); // Customize Chart // Customize Chart chart.getStyler().setSeriesColors(productionColors); // Series chart.addSeries("Production", 0); chart.addSeries("Consumption", 0); chart.addSeries("No Data", 0); return chart; } public PieChart createActiveChart() { PieChart chart = new PieChartBuilder().width(defaultWidth).height(defaultHeight).title("Active") .build(); setDefaultPieChartSettings(chart); // Customize Chart // Customize Chart chart.getStyler().setSeriesColors(activeColors); // Series chart.addSeries("Active", 0); chart.addSeries("Inactive", 0); chart.addSeries("No Data", 0); return chart; } public static class MenuButton extends JToggleButton { JPopupMenu popup; public MenuButton(Icon icon, JPopupMenu menu) { super(icon); this.popup = menu; addActionListener(clicked -> { if (MenuButton.this.isSelected()) { popup.show(MenuButton.this, 0, MenuButton.this.getBounds().height); } else { popup.setVisible(false); } }); popup.addPopupMenuListener(new PopupMenuListener() { @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) { } @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { MenuButton.this.setSelected(false); } @Override public void popupMenuCanceled(PopupMenuEvent e) { } }); } } private static class TempGroupNode extends GroupNode { public TempGroupNode() { super("temp"); } public void add(AbstractCanvasObject object) { if (object instanceof HolonObject hObject) { objectList.add(hObject); } else if (object instanceof HolonSwitch hSwitch) { switchList.add(hSwitch); } else if (object instanceof Node node) { nodeList.add(node); } else if (object instanceof GroupNode groupNode) { groupNodeList.add(groupNode); } } public void remove(AbstractCanvasObject object) { if (object instanceof HolonObject hObject) { objectList.remove(hObject); } else if (object instanceof HolonSwitch hSwitch) { switchList.remove(hSwitch); } else if (object instanceof Node node) { nodeList.remove(node); } else if (object instanceof GroupNode groupNode) { groupNodeList.remove(groupNode); } } } }