/**
*
*/
package org.opentrafficsim.swing.graphs;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import javax.swing.ButtonGroup;
import javax.swing.JMenu;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButtonMenuItem;
import org.djunits.value.vdouble.scalar.Duration;
import org.jfree.chart.ChartMouseEvent;
import org.jfree.chart.ChartMouseListener;
import org.jfree.chart.annotations.XYAnnotation;
import org.jfree.chart.annotations.XYLineAnnotation;
import org.jfree.chart.annotations.XYTextAnnotation;
import org.jfree.chart.entity.AxisEntity;
import org.jfree.chart.entity.XYItemEntity;
import org.jfree.chart.ui.TextAnchor;
import org.jfree.data.Range;
import org.opentrafficsim.draw.graphs.FundamentalDiagram;
import org.opentrafficsim.draw.graphs.FundamentalDiagram.Quantity;
import org.opentrafficsim.draw.graphs.GraphUtil;
/**
* Embed a FundamentalDiagram in a Swing JPanel.
*
* @author Alexander Verbraeck
* @author Peter Knoppers
* @author Wouter Schakel
*/
public class SwingFundamentalDiagram extends SwingPlot
{
/** */
private static final long serialVersionUID = 20190823L;
/**
* Construct a new Swing container for FundamentalDiagram plot.
* @param plot FundamentalDiagram; the plot to embed
*/
public SwingFundamentalDiagram(final FundamentalDiagram plot)
{
super(plot);
}
/** {@inheritDoc} */
@Override
protected ChartMouseListener getChartMouseListener()
{
ChartMouseListener toggle = !getPlot().hasLineFD() && getPlot().getSource().getNumberOfSeries() < 2 ? null : GraphUtil
.getToggleSeriesByLegendListener(getPlot().getLegend(), getPlot().getLaneVisible());
return new ChartMouseListener()
{
/** {@inheritDoc} */
@SuppressWarnings("unchecked")
@Override
public void chartMouseClicked(final ChartMouseEvent event)
{
if (toggle != null)
{
toggle.chartMouseClicked(event); // forward as we use two listeners
}
// remove any line annotations
for (XYAnnotation annotation : ((List) getPlot().getChart().getXYPlot().getAnnotations()))
{
if (annotation instanceof XYLineAnnotation)
{
getPlot().getChart().getXYPlot().removeAnnotation(annotation);
}
}
// add line annotation for each item in series if the user clicked in an item
if (event.getEntity() instanceof XYItemEntity)
{
XYItemEntity itemEntity = (XYItemEntity) event.getEntity();
int series = itemEntity.getSeriesIndex();
for (int i = 0; i < getPlot().getItemCount(series) - 1; i++)
{
XYLineAnnotation annotation = new XYLineAnnotation(getPlot().getXValue(series, i), getPlot().getYValue(
series, i), getPlot().getXValue(series, i + 1), getPlot().getYValue(series, i + 1), new BasicStroke(
1.0f), Color.WHITE);
getPlot().getChart().getXYPlot().addAnnotation(annotation);
}
}
else if (event.getEntity() instanceof AxisEntity)
{
if (((AxisEntity) event.getEntity()).getAxis().equals(getPlot().getChart().getXYPlot().getDomainAxis()))
{
Quantity old = getPlot().getDomainQuantity();
getPlot().setDomainQuantity(getPlot().getOtherQuantity());
getPlot().setOtherQuantity(old);
getPlot().getChart().getXYPlot().getDomainAxis().setLabel(getPlot().getDomainQuantity().label());
getPlot().getChart().getXYPlot().zoomDomainAxes(0.0, null, null);
}
else
{
Quantity old = getPlot().getRangeQuantity();
getPlot().setRangeQuantity(getPlot().getOtherQuantity());
getPlot().setOtherQuantity(old);
getPlot().getChart().getXYPlot().getRangeAxis().setLabel(getPlot().getRangeQuantity().label());
getPlot().getChart().getXYPlot().zoomRangeAxes(0.0, null, null);
}
}
}
/** {@inheritDoc} */
@SuppressWarnings("unchecked")
@Override
public void chartMouseMoved(final ChartMouseEvent event)
{
if (toggle != null)
{
toggle.chartMouseMoved(event); // forward as we use two listeners
}
boolean clearText = true;
// set text annotation and status text to time of item
if (event.getEntity() instanceof XYItemEntity)
{
// create time info for status label
XYItemEntity itemEntity = (XYItemEntity) event.getEntity();
int series = itemEntity.getSeriesIndex();
if (!getPlot().hasLineFD() || series != getPlot().getSeriesCount() - 1)
{
clearText = false;
int item = itemEntity.getItem();
double t = item * getPlot().getSource().getUpdateInterval().si;
getPlot().setTimeInfo(String.format(", %.0fs", t));
double x = getPlot().getXValue(series, item);
double y = getPlot().getYValue(series, item);
Range domain = getPlot().getChart().getXYPlot().getDomainAxis().getRange();
Range range = getPlot().getChart().getXYPlot().getRangeAxis().getRange();
TextAnchor anchor;
if (range.getUpperBound() - y < y - range.getLowerBound())
{
// upper half
if (domain.getUpperBound() - x < x - domain.getLowerBound())
{
// upper right quadrant
anchor = TextAnchor.TOP_RIGHT;
}
else
{
// upper left quadrant, can't use TOP_LEFT as text will be under mouse pointer
if ((range.getUpperBound() - y) / (range.getUpperBound() - range.getLowerBound()) < (x - domain
.getLowerBound()) / (domain.getUpperBound() - domain.getLowerBound()))
{
// closer to top (at least relatively) so move text down
anchor = TextAnchor.TOP_RIGHT;
}
else
{
// closer to left (at least relatively) so move text right
anchor = TextAnchor.BOTTOM_LEFT;
}
}
}
else if (domain.getUpperBound() - x < x - domain.getLowerBound())
{
// lower right quadrant
anchor = TextAnchor.BOTTOM_RIGHT;
}
else
{
// lower left quadrant
anchor = TextAnchor.BOTTOM_LEFT;
}
XYTextAnnotation textAnnotation = new XYTextAnnotation(String.format("%.0fs", t), x, y);
textAnnotation.setTextAnchor(anchor);
textAnnotation.setFont(textAnnotation.getFont().deriveFont(14.0f).deriveFont(Font.BOLD));
getPlot().getChart().getXYPlot().addAnnotation(textAnnotation);
}
}
// remove texts when mouse is elsewhere, or on FD line
if (clearText)
{
for (XYAnnotation annotation : ((List) getPlot().getChart().getXYPlot().getAnnotations()))
{
if (annotation instanceof XYTextAnnotation)
{
getPlot().getChart().getXYPlot().removeAnnotation(annotation);
}
}
getPlot().setTimeInfo("");
}
}
};
}
/** {@inheritDoc} */
@Override
protected void addPopUpMenuItems(final JPopupMenu popupMenu)
{
super.addPopUpMenuItems(popupMenu);
popupMenu.insert(new JPopupMenu.Separator(), 0);
JMenu updMenu = new JMenu("Update frequency");
ButtonGroup updGroup = new ButtonGroup();
for (int f : getPlot().getSource().getPossibleUpdateFrequencies())
{
String format = "%dx";
JRadioButtonMenuItem item = new JRadioButtonMenuItem(String.format(format, f));
item.setSelected(f == 1);
item.addActionListener(new ActionListener()
{
/** {@inheritDoc} */
@Override
public void actionPerformed(final ActionEvent e)
{
if ((int) (.5 + getPlot().getSource().getAggregationPeriod().si / getPlot().getSource()
.getUpdateInterval().si) != f)
{
Duration interval = Duration.instantiateSI(getPlot().getSource().getAggregationPeriod().si / f);
for (FundamentalDiagram diagram : getPlot().getSource().getDiagrams())
{
diagram.setUpdateInterval(interval);
}
// the above setUpdateInterval also recalculates the virtual last update time
// add half an interval to avoid any rounding issues
getPlot().getSource().setUpdateInterval(interval, getPlot().getUpdateTime().plus(interval.times(0.5)));
for (FundamentalDiagram diagram : getPlot().getSource().getDiagrams())
{
diagram.getChart().getXYPlot().zoomDomainAxes(0.0, null, null);
diagram.getChart().getXYPlot().zoomRangeAxes(0.0, null, null);
diagram.notifyPlotChange();
}
}
}
});
updGroup.add(item);
updMenu.add(item);
}
popupMenu.insert(updMenu, 0);
JMenu aggMenu = new JMenu("Aggregation period");
ButtonGroup aggGroup = new ButtonGroup();
for (double t : getPlot().getSource().getPossibleAggregationPeriods())
{
double t2 = t;
String format = "%.0f s";
if (t >= 60.0)
{
t2 = t / 60.0;
format = "%.0f min";
}
JRadioButtonMenuItem item = new JRadioButtonMenuItem(String.format(format, t2));
item.setSelected(t == getPlot().getSource().getAggregationPeriod().si);
item.addActionListener(new ActionListener()
{
/** {@inheritDoc} */
@Override
public void actionPerformed(final ActionEvent e)
{
if (getPlot().getSource().getAggregationPeriod().si != t)
{
int n = (int) (0.5 + getPlot().getSource().getAggregationPeriod().si / getPlot().getSource()
.getUpdateInterval().si);
Duration period = Duration.instantiateSI(t);
Duration interval = period.divide(n);
for (FundamentalDiagram diagram : getPlot().getSource().getDiagrams())
{
diagram.setUpdateInterval(interval);
}
// add half an interval to avoid any rounding issues
getPlot().getSource().setAggregationPeriod(period);
getPlot().getSource().setUpdateInterval(period.divide(n), getPlot().getUpdateTime().plus(period.divide(
n).times(0.5)));
for (FundamentalDiagram diagram : getPlot().getSource().getDiagrams())
{
diagram.getChart().getXYPlot().zoomDomainAxes(0.0, null, null);
diagram.getChart().getXYPlot().zoomRangeAxes(0.0, null, null);
diagram.notifyPlotChange();
}
}
}
});
aggGroup.add(item);
aggMenu.add(item);
}
popupMenu.insert(aggMenu, 0);
}
/**
* Retrieve the plot.
* @return AbstractPlot; the plot
*/
@Override
public FundamentalDiagram getPlot()
{
return (FundamentalDiagram) super.getPlot();
}
}