SwingContourPlot.java
package org.opentrafficsim.swing.graphs;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.swing.ButtonGroup;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JMenu;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButtonMenuItem;
import org.djutils.event.Event;
import org.djutils.event.EventListener;
import org.opentrafficsim.base.OtsRuntimeException;
import org.opentrafficsim.draw.graphs.AbstractContourPlot;
import org.opentrafficsim.draw.graphs.ContourDataSource;
import org.opentrafficsim.draw.graphs.ContourDataSource.Dimension;
/**
* Embed a ContourPlot in a Swing JPanel.
* <p>
* Copyright (c) 2023-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
* BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
* </p>
* @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
* @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
* @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
*/
public class SwingContourPlot extends SwingSpaceTimePlot implements EventListener
{
/** */
private static final long serialVersionUID = 20190823L;
/** Map to set time granularity. */
private Map<JRadioButtonMenuItem, Double> timeGranularityButtons;
/** Map to set space granularity. */
private Map<JRadioButtonMenuItem, Double> spaceGranularityButtons;
/** Check box for smoothing. */
private JCheckBoxMenuItem smoothCheckBox;
/** Check box for interpolation. */
private JCheckBoxMenuItem interpolateCheckBox;
/** Whether to ignore events as this is itself the plot causing change of granularity/interpolate/smooth setting. */
private boolean ignoreEvent = false;
/**
* Create a new SwingContourPlot with embedded plot.
* @param plot the plot to embed
*/
public SwingContourPlot(final AbstractContourPlot<?> plot)
{
super(plot);
plot.getDataPool().addListener(this, ContourDataSource.GRANULARITY);
plot.getDataPool().addListener(this, ContourDataSource.INTERPOLATE);
plot.getDataPool().addListener(this, ContourDataSource.SMOOTH);
}
@Override
protected void addPopUpMenuItems(final JPopupMenu popupMenu)
{
this.timeGranularityButtons = new LinkedHashMap<>();
this.spaceGranularityButtons = new LinkedHashMap<>();
super.addPopUpMenuItems(popupMenu);
JMenu spaceGranularityMenu = buildMenu("Distance granularity", "%.0f m", 1000, "%.0f km", "setSpaceGranularity",
getPlot().getDataPool().getGranularities(Dimension.DISTANCE),
getPlot().getDataPool().getGranularity(Dimension.DISTANCE), this.spaceGranularityButtons);
popupMenu.insert(spaceGranularityMenu, 0);
JMenu timeGranularityMenu = buildMenu("Time granularity", "%.0f s", 60.0, "%.0f min", "setTimeGranularity",
getPlot().getDataPool().getGranularities(Dimension.TIME),
getPlot().getDataPool().getGranularity(Dimension.TIME), this.timeGranularityButtons);
popupMenu.insert(timeGranularityMenu, 1);
this.smoothCheckBox = new JCheckBoxMenuItem("Adaptive smoothing method", false);
this.smoothCheckBox.addActionListener((e) ->
{
SwingContourPlot.this.ignoreEvent = true;
getPlot().getDataPool().setSmooth(((JCheckBoxMenuItem) e.getSource()).isSelected());
getPlot().notifyPlotChange();
SwingContourPlot.this.ignoreEvent = false;
});
popupMenu.insert(this.smoothCheckBox, 2);
this.interpolateCheckBox = new JCheckBoxMenuItem("Bilinear interpolation", true);
this.interpolateCheckBox.addActionListener((e) ->
{
SwingContourPlot.this.ignoreEvent = true;
boolean interpolate = ((JCheckBoxMenuItem) e.getSource()).isSelected();
getPlot().getBlockRenderer().setInterpolate(interpolate);
getPlot().getDataPool().setInterpolate(interpolate);
getPlot().notifyPlotChange();
SwingContourPlot.this.ignoreEvent = false;
});
popupMenu.insert(this.interpolateCheckBox, 3);
}
/**
* Create a JMenu to let the user set the granularity.
* @param menuName caption for the new JMenu
* @param format1 format string for the values in the items under the new JMenu, below formatValue
* @param formatValue format value
* @param format2 format string for the values in the items under the new JMenu, above and equal to formatValue
* @param command prefix for the actionCommand of the items under the new JMenu
* @param values array of values to be formatted using the format strings to yield the items under the new JMenu
* @param initialValue the currently selected value (used to put the bullet on the correct item)
* @param granularityButtons map in to which buttons should be added
* @return JMenu with JRadioMenuItems for the values and a bullet on the currentValue item
*/
private JMenu buildMenu(final String menuName, final String format1, final double formatValue, final String format2,
final String command, final double[] values, final double initialValue,
final Map<JRadioButtonMenuItem, Double> granularityButtons)
{
JMenu result = new JMenu(menuName);
ButtonGroup group = new ButtonGroup();
for (double value : values)
{
JRadioButtonMenuItem item = new JRadioButtonMenuItem(
String.format(value < formatValue ? format1 : format2, value < formatValue ? value : value / formatValue));
granularityButtons.put(item, value);
item.setSelected(value == initialValue);
item.setActionCommand(command);
item.addActionListener((actionEvent) ->
{
SwingContourPlot.this.ignoreEvent = true;
if (command.equalsIgnoreCase("setSpaceGranularity"))
{
double granularity = SwingContourPlot.this.spaceGranularityButtons.get(actionEvent.getSource());
// offer instead of setting as data pool may be working in the background using the granularity
getPlot().getDataPool().offerGranularity(Dimension.DISTANCE, granularity);
}
else if (command.equalsIgnoreCase("setTimeGranularity"))
{
double granularity = SwingContourPlot.this.timeGranularityButtons.get(actionEvent.getSource());
// offer instead of setting as data pool may be working in the background using the granularity
getPlot().getDataPool().offerGranularity(Dimension.TIME, granularity);
}
else
{
throw new OtsRuntimeException("Unknown ActionEvent");
}
SwingContourPlot.this.ignoreEvent = false;
});
result.add(item);
group.add(item);
}
return result;
}
@Override
public AbstractContourPlot<?> getPlot()
{
return (AbstractContourPlot<?>) super.getPlot();
}
@Override
public void notify(final Event event)
{
if (this.ignoreEvent)
{
return;
}
if (event.getType().equals(ContourDataSource.GRANULARITY))
{
Object[] payload = (Object[]) event.getContent();
Dimension dimension = (Dimension) payload[0];
double granularity = (double) payload[1];
Map<JRadioButtonMenuItem, Double> buttonMap =
Dimension.DISTANCE.equals(dimension) ? this.spaceGranularityButtons : this.timeGranularityButtons;
for (JRadioButtonMenuItem button : buttonMap.keySet())
{
button.setSelected(Math.abs(buttonMap.get(button) - granularity) < 0.001);
}
}
else if (event.getType().equals(ContourDataSource.INTERPOLATE))
{
this.interpolateCheckBox.setSelected((boolean) event.getContent());
}
else if (event.getType().equals(ContourDataSource.SMOOTH))
{
this.smoothCheckBox.setSelected((boolean) event.getContent());
}
}
}