From 0e17a93506a98c4400d89180c6b5fcf9006df30d Mon Sep 17 00:00:00 2001
From: oblonski <4sschroeder@gmail.com>
Date: Thu, 8 May 2014 11:37:42 +0200
Subject: [PATCH] cleaned and redesigned analysis.toolbox.Plotter according to
#59
---
.../java/jsprit/analysis/toolbox/Plotter.java | 537 ++++++++++--------
1 file changed, 292 insertions(+), 245 deletions(-)
diff --git a/jsprit-analysis/src/main/java/jsprit/analysis/toolbox/Plotter.java b/jsprit-analysis/src/main/java/jsprit/analysis/toolbox/Plotter.java
index b495d8760..d7a699df7 100644
--- a/jsprit-analysis/src/main/java/jsprit/analysis/toolbox/Plotter.java
+++ b/jsprit-analysis/src/main/java/jsprit/analysis/toolbox/Plotter.java
@@ -18,12 +18,16 @@
import java.awt.BasicStroke;
import java.awt.Color;
+import java.awt.Paint;
+import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
import jsprit.core.problem.VehicleRoutingProblem;
import jsprit.core.problem.job.Delivery;
@@ -41,9 +45,9 @@
import org.apache.log4j.Logger;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
+import org.jfree.chart.LegendItem;
import org.jfree.chart.LegendItemCollection;
import org.jfree.chart.LegendItemSource;
-import org.jfree.chart.annotations.XYShapeAnnotation;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.labels.XYItemLabelGenerator;
import org.jfree.chart.plot.XYPlot;
@@ -56,10 +60,79 @@
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.RectangleEdge;
+import org.jfree.util.ShapeUtilities;
+/**
+ * Visualizes problem and solution.
+ *
Note that every item to be rendered need to have coordinates.
+ *
+ * @author schroeder
+ *
+ */
public class Plotter {
+ private final static Color START_COLOR = Color.RED;
+ private final static Color END_COLOR = Color.RED;
+ private final static Color PICKUP_COLOR = Color.GREEN;
+ private final static Color DELIVERY_COLOR = Color.BLUE;
+ private final static Color SERVICE_COLOR = Color.BLUE;
+
+ private final static Shape ELLIPSE = new Ellipse2D.Double(-3, -3, 6, 6);
+
+ private static class MyActivityRenderer extends XYLineAndShapeRenderer {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ private XYSeriesCollection seriesCollection;
+
+ private Map activities;
+
+ private Set firstActivities;
+
+ public MyActivityRenderer(XYSeriesCollection seriesCollection,Map activities,Set firstActivities) {
+ super(false,true);
+ this.seriesCollection = seriesCollection;
+ this.activities = activities;
+ this.firstActivities = firstActivities;
+ super.setSeriesOutlinePaint(0, Color.DARK_GRAY);
+ super.setUseOutlinePaint(true);
+ }
+
+ @Override
+ public Shape getItemShape(int seriesIndex, int itemIndex) {
+ XYDataItem dataItem = seriesCollection.getSeries(seriesIndex).getDataItem(itemIndex);
+ if(firstActivities.contains(dataItem)){
+ return ShapeUtilities.createUpTriangle(4.0f);
+ }
+ return ELLIPSE;
+ }
+
+ @Override
+ public Paint getItemOutlinePaint(int seriesIndex, int itemIndex) {
+ XYDataItem dataItem = seriesCollection.getSeries(seriesIndex).getDataItem(itemIndex);
+ if(firstActivities.contains(dataItem)){
+ return Color.BLACK;
+ }
+ return super.getItemOutlinePaint(seriesIndex, itemIndex);
+ }
+
+ @Override
+ public Paint getItemPaint(int seriesIndex, int itemIndex) {
+ XYDataItem dataItem = seriesCollection.getSeries(seriesIndex).getDataItem(itemIndex);
+ Activity activity = activities.get(dataItem);
+ if(activity.equals(Activity.PICKUP)) return PICKUP_COLOR;
+ if(activity.equals(Activity.DELIVERY)) return DELIVERY_COLOR;
+ if(activity.equals(Activity.SERVICE)) return SERVICE_COLOR;
+ if(activity.equals(Activity.START)) return START_COLOR;
+ return END_COLOR;
+ }
+
+ }
+
private static class BoundingBox {
double minX;
double minY;
@@ -75,24 +148,22 @@ public BoundingBox(double minX, double minY, double maxX, double maxY) {
}
-// private static class NoLocationFoundException extends Exception{
-//
-// /**
-// *
-// */
-// private static final long serialVersionUID = 1L;
-//
-// }
+ private enum Activity {
+ START, END, PICKUP, DELIVERY, SERVICE
+ }
- private static Logger log = Logger.getLogger(Plotter.class);
+ private static Logger log = Logger.getLogger(Plotter.class);
+ /**
+ * Label to label ID (=jobId), SIZE (=jobSize=jobCapacityDimensions)
+ * @author schroeder
+ *
+ */
public static enum Label {
ID, SIZE, NO_LABEL
}
- private boolean showFirstActivity = true;
-
private Label label = Label.SIZE;
private VehicleRoutingProblem vrp;
@@ -105,11 +176,36 @@ public static enum Label {
private BoundingBox boundingBox = null;
+ private Map activitiesByDataItem = new HashMap();
+
+ private Map labelsByDataItem = new HashMap();
+
+ private XYSeries activities;
+
+ private Set firstActivities = new HashSet();
+
+ private boolean containsPickupAct = false;
+
+ private boolean containsDeliveryAct = false;
+
+ private boolean containsServiceAct = false;
+
+ /**
+ * Constructs Plotter with problem. Thus only the problem can be rendered.
+ *
+ * @param vrp
+ */
public Plotter(VehicleRoutingProblem vrp) {
super();
this.vrp = vrp;
}
+ /**
+ * Constructs Plotter with problem and solution to render them both.
+ *
+ * @param vrp
+ * @param solution
+ */
public Plotter(VehicleRoutingProblem vrp, VehicleRoutingProblemSolution solution) {
super();
this.vrp = vrp;
@@ -117,6 +213,12 @@ public Plotter(VehicleRoutingProblem vrp, VehicleRoutingProblemSolution solution
plotSolutionAsWell = true;
}
+ /**
+ * Constructs Plotter with problem and routes to render individual routes.
+ *
+ * @param vrp
+ * @param solution
+ */
public Plotter(VehicleRoutingProblem vrp, Collection routes) {
super();
this.vrp = vrp;
@@ -124,159 +226,179 @@ public Plotter(VehicleRoutingProblem vrp, Collection routes) {
plotSolutionAsWell = true;
}
+ /**
+ *
+ * @param show
+ * @return
+ * @deprecate always true
+ */
+ @Deprecated
public Plotter setShowFirstActivity(boolean show){
- showFirstActivity = show;
return this;
}
+ /**
+ * Sets a label.
+ * @param label
+ * @return
+ */
public Plotter setLabel(Label label){
this.label = label;
return this;
}
+ /**
+ * Sets a bounding box to zoom in to certain areas.
+ *
+ * @param minX
+ * @param minY
+ * @param maxX
+ * @param maxY
+ * @return
+ */
public Plotter setBoundingBox(double minX, double minY, double maxX, double maxY){
boundingBox = new BoundingBox(minX,minY,maxX,maxY);
return this;
}
+ /**
+ * Flag that indicates whether shipments should be rendered as well.
+ *
+ * @param plotShipments
+ * @return
+ */
public Plotter plotShipments(boolean plotShipments) {
this.plotShipments = plotShipments;
return this;
}
+ /**
+ * Plots problem and/or solution/routes.
+ *
+ * @param pngFileName - path and filename
+ * @param plotTitle - title that appears on top of image
+ */
public void plot(String pngFileName, String plotTitle){
String filename = pngFileName;
if(!pngFileName.endsWith(".png")) filename += ".png";
if(plotSolutionAsWell){
- plotSolutionAsPNG(vrp, routes, filename, plotTitle);
+ plot(vrp, routes, filename, plotTitle);
}
else if(!(vrp.getInitialVehicleRoutes().isEmpty())){
- plotSolutionAsPNG(vrp, vrp.getInitialVehicleRoutes(), filename, plotTitle);
+ plot(vrp, vrp.getInitialVehicleRoutes(), filename, plotTitle);
}
else{
- plotVrpAsPNG(vrp, filename, plotTitle);
+ plot(vrp, null, filename, plotTitle);
}
}
- private void plotVrpAsPNG(VehicleRoutingProblem vrp, String pngFile, String title){
- log.info("plot routes to " + pngFile);
- XYSeriesCollection problem;
+ private void plot(VehicleRoutingProblem vrp, final Collection routes, String pngFile, String title){
+ log.info("plot to " + pngFile);
+ XYSeriesCollection problem = null;
+ XYSeriesCollection solution = null;
final XYSeriesCollection shipments;
- Map labels = new HashMap();
try {
- problem = makeVrpSeries(vrp, labels);
- shipments = makeShipmentSeries(vrp.getJobs().values(), null);
+ retrieveActivities(vrp);
+ problem = new XYSeriesCollection(activities);
+ shipments = makeShipmentSeries(vrp.getJobs().values());
+ if(routes!=null) solution = makeSolutionSeries(vrp, routes);
} catch (NoLocationFoundException e) {
log.warn("cannot plot vrp, since coord is missing");
return;
}
- final XYPlot plot = createProblemPlot(problem, shipments, labels);
- LegendItemSource lis = new LegendItemSource() {
-
- @Override
- public LegendItemCollection getLegendItems() {
- LegendItemCollection lic = new LegendItemCollection();
- lic.addAll(plot.getRenderer(0).getLegendItems());
- if(!shipments.getSeries().isEmpty()){
- lic.add(plot.getRenderer(1).getLegendItem(1, 0));
- }
- return lic;
- }
- };
-
+ final XYPlot plot = createPlot(problem, shipments, solution);
JFreeChart chart = new JFreeChart(title, plot);
+
+ LegendTitle legend = createLegend(routes, shipments, plot);
chart.removeLegend();
- LegendTitle legend = new LegendTitle(lis);
- legend.setPosition(RectangleEdge.BOTTOM);
chart.addLegend(legend);
+
save(chart,pngFile);
+
}
-
- private void plotSolutionAsPNG(VehicleRoutingProblem vrp, Collection routes, String pngFile, String title){
- log.info("plot solution to " + pngFile);
- XYSeriesCollection problem;
- XYSeriesCollection solutionColl;
- final XYSeriesCollection shipments;
- Map labels = new HashMap();
- try {
- problem = makeVrpSeries(vrp, labels);
- shipments = makeShipmentSeries(vrp.getJobs().values(), null);
- solutionColl = makeSolutionSeries(vrp, routes);
- } catch (NoLocationFoundException e) {
- log.warn("cannot plot vrp, since coord is missing");
- return;
- }
- final XYPlot plot = createProblemSolutionPlot(problem, shipments, solutionColl, labels);
- JFreeChart chart = new JFreeChart(title, plot);
+
+ private LegendTitle createLegend(final Collection routes,final XYSeriesCollection shipments, final XYPlot plot) {
LegendItemSource lis = new LegendItemSource() {
@Override
public LegendItemCollection getLegendItems() {
LegendItemCollection lic = new LegendItemCollection();
- lic.addAll(plot.getRenderer(0).getLegendItems());
- lic.addAll(plot.getRenderer(2).getLegendItems());
+ LegendItem vehLoc = new LegendItem("vehLoc", Color.RED);
+ vehLoc.setShape(ELLIPSE);
+ vehLoc.setShapeVisible(true);
+ lic.add(vehLoc);
+ if(containsServiceAct){
+ LegendItem item = new LegendItem("service", Color.BLUE);
+ item.setShape(ELLIPSE);
+ item.setShapeVisible(true);
+ lic.add(item);
+ }
+ if(containsPickupAct){
+ LegendItem item = new LegendItem("pickup", Color.GREEN);
+ item.setShape(ELLIPSE);
+ item.setShapeVisible(true);
+ lic.add(item);
+ }
+ if(containsDeliveryAct){
+ LegendItem item = new LegendItem("delivery", Color.BLUE);
+ item.setShape(ELLIPSE);
+ item.setShapeVisible(true);
+ lic.add(item);
+ }
+ if(routes!=null){
+ LegendItem item = new LegendItem("firstActivity",Color.BLACK);
+ Shape upTriangle = ShapeUtilities.createUpTriangle(3.0f);
+ item.setShape(upTriangle);
+ item.setOutlinePaint(Color.BLACK);
+
+ item.setLine(upTriangle);
+ item.setLinePaint(Color.BLACK);
+ item.setShapeVisible(true);
+
+ lic.add(item);
+ }
if(!shipments.getSeries().isEmpty()){
lic.add(plot.getRenderer(1).getLegendItem(1, 0));
}
+ if(routes!=null){
+ lic.addAll(plot.getRenderer(2).getLegendItems());
+ }
return lic;
}
};
-
- chart.removeLegend();
LegendTitle legend = new LegendTitle(lis);
legend.setPosition(RectangleEdge.BOTTOM);
- chart.addLegend(legend);
-
- save(chart,pngFile);
-
+ return legend;
}
-
- private XYPlot createProblemPlot(final XYSeriesCollection problem, XYSeriesCollection shipments, final Map labels) {
- XYPlot plot = new XYPlot();
- plot.setBackgroundPaint(Color.LIGHT_GRAY);
- plot.setRangeGridlinePaint(Color.WHITE);
- plot.setDomainGridlinePaint(Color.WHITE);
-
- XYItemRenderer problemRenderer = new XYLineAndShapeRenderer(false, true); // Shapes only
+
+ private XYItemRenderer getShipmentRenderer(XYSeriesCollection shipments) {
+ XYItemRenderer shipmentsRenderer = new XYLineAndShapeRenderer(true, false); // Shapes only
+ for(int i=0;i labels) {
+ private XYPlot createPlot(final XYSeriesCollection problem, XYSeriesCollection shipments, XYSeriesCollection solution) {
XYPlot plot = new XYPlot();
plot.setBackgroundPaint(Color.LIGHT_GRAY);
plot.setRangeGridlinePaint(Color.WHITE);
plot.setDomainGridlinePaint(Color.WHITE);
- XYItemRenderer problemRenderer = new XYLineAndShapeRenderer(false, true); // Shapes only
- problemRenderer.setBaseItemLabelGenerator(new XYItemLabelGenerator() {
-
- @Override
- public String generateLabel(XYDataset arg0, int arg1, int arg2) {
- XYDataItem item = problem.getSeries(arg1).getDataItem(arg2);
- return labels.get(item);
- }
- });
- problemRenderer.setBaseItemLabelsVisible(true);
- problemRenderer.setBaseItemLabelPaint(Color.BLACK);
-
+ XYLineAndShapeRenderer problemRenderer = getProblemRenderer(problem);
plot.setDataset(0, problem);
plot.setRenderer(0, problemRenderer);
-// plot.setDomainAxis(0, xAxis);
-// plot.setRangeAxis(0, yAxis);
-// plot.mapDatasetToDomainAxis(0, 0);
-
- XYItemRenderer shipmentsRenderer = new XYLineAndShapeRenderer(true, false); // Shapes only
- for(int i=0;i jobs, Map labels) throws NoLocationFoundException{
+ private XYSeriesCollection makeShipmentSeries(Collection jobs) throws NoLocationFoundException{
XYSeriesCollection coll = new XYSeriesCollection();
if(!plotShipments) return coll;
int sCounter = 1;
@@ -422,85 +520,60 @@ private XYSeriesCollection makeShipmentSeries(Collection jobs, Map vehicles, Collection jobs, Map labels) throws NoLocationFoundException{
- XYSeriesCollection coll = new XYSeriesCollection();
- XYSeries vehicleSeries = new XYSeries("depot", false, true);
- for(Vehicle v : vehicles){
- Coordinate startCoord = v.getStartLocationCoordinate();
- if(startCoord == null) throw new NoLocationFoundException();
- vehicleSeries.add(startCoord.getX(),startCoord.getY());
-
- if(!v.getStartLocationId().equals(v.getEndLocationId())){
- Coordinate endCoord = v.getEndLocationCoordinate();
- if(endCoord == null) throw new NoLocationFoundException();
- vehicleSeries.add(endCoord.getX(),endCoord.getY());
- }
- }
- coll.addSeries(vehicleSeries);
-
- XYSeries serviceSeries = new XYSeries("service", false, true);
- XYSeries pickupSeries = new XYSeries("pickup", false, true);
- XYSeries deliverySeries = new XYSeries("delivery", false, true);
- for(Job job : jobs){
- addJob(labels, serviceSeries, pickupSeries, deliverySeries, job);
- }
- for(VehicleRoute r : vrp.getInitialVehicleRoutes()){
- for(Job job : r.getTourActivities().getJobs()){
- addJob(labels, serviceSeries, pickupSeries, deliverySeries, job);
- }
- }
- if(!serviceSeries.isEmpty()) coll.addSeries(serviceSeries);
- if(!pickupSeries.isEmpty()) coll.addSeries(pickupSeries);
- if(!deliverySeries.isEmpty()) coll.addSeries(deliverySeries);
- return coll;
- }
- private void addJob(Map labels, XYSeries serviceSeries,
- XYSeries pickupSeries, XYSeries deliverySeries, Job job) {
+ private void addJob(XYSeries activities, Job job) {
if(job instanceof Shipment){
Shipment s = (Shipment)job;
XYDataItem dataItem = new XYDataItem(s.getPickupCoord().getX(), s.getPickupCoord().getY());
- pickupSeries.add(dataItem);
- addLabel(labels, s, dataItem);
+ activities.add(dataItem);
+ addLabel(s, dataItem);
+ markItem(dataItem,Activity.PICKUP, job);
+ containsPickupAct = true;
XYDataItem dataItem2 = new XYDataItem(s.getDeliveryCoord().getX(), s.getDeliveryCoord().getY());
- deliverySeries.add(dataItem2);
- addLabel(labels, s, dataItem2);
+ activities.add(dataItem2);
+ addLabel(s, dataItem2);
+ markItem(dataItem2,Activity.DELIVERY, job);
+ containsDeliveryAct = true;
}
else if(job instanceof Pickup){
Pickup service = (Pickup)job;
Coordinate coord = service.getCoord();
XYDataItem dataItem = new XYDataItem(coord.getX(), coord.getY());
- pickupSeries.add(dataItem);
- addLabel(labels, service, dataItem);
-
+ activities.add(dataItem);
+ addLabel(service, dataItem);
+ markItem(dataItem, Activity.PICKUP, job);
+ containsPickupAct = true;
}
else if(job instanceof Delivery){
Delivery service = (Delivery)job;
Coordinate coord = service.getCoord();
XYDataItem dataItem = new XYDataItem(coord.getX(), coord.getY());
- deliverySeries.add(dataItem);
- addLabel(labels, service, dataItem);
+ activities.add(dataItem);
+ addLabel(service, dataItem);
+ markItem(dataItem, Activity.DELIVERY, job);
+ containsDeliveryAct = true;
}
else if(job instanceof Service){
Service service = (Service)job;
Coordinate coord = service.getCoord();
XYDataItem dataItem = new XYDataItem(coord.getX(), coord.getY());
- serviceSeries.add(dataItem);
- addLabel(labels, service, dataItem);
+ activities.add(dataItem);
+ addLabel(service, dataItem);
+ markItem(dataItem, Activity.SERVICE, job);
+ containsServiceAct = true;
}
else{
throw new IllegalStateException("job instanceof " + job.getClass().toString() + ". this is not supported.");
}
}
- private void addLabel(Map labels, Job job, XYDataItem dataItem) {
+ private void addLabel(Job job, XYDataItem dataItem) {
if(this.label.equals(Label.SIZE)){
- labels.put(dataItem, getSizeString(job));
+ labelsByDataItem.put(dataItem, getSizeString(job));
}
else if(this.label.equals(Label.ID)){
- labels.put(dataItem, String.valueOf(job.getId()));
+ labelsByDataItem.put(dataItem, String.valueOf(job.getId()));
}
}
@@ -522,63 +595,37 @@ private String getSizeString(Job job) {
return builder.toString();
}
- private XYSeriesCollection makeVrpSeries(VehicleRoutingProblem vrp, Map labels) throws NoLocationFoundException{
- return makeVrpSeries(vrp.getVehicles(), vrp.getJobs().values(), labels);
+ private void retrieveActivities(VehicleRoutingProblem vrp) throws NoLocationFoundException{
+ activities = new XYSeries("activities", false, true);
+ for(Vehicle v : vrp.getVehicles()){
+ Coordinate startCoord = v.getStartLocationCoordinate();
+ if(startCoord == null) throw new NoLocationFoundException();
+ XYDataItem item = new XYDataItem(startCoord.getX(), startCoord.getY());
+ markItem(item,Activity.START, null);
+ activities.add(item);
+
+ if(!v.getStartLocationId().equals(v.getEndLocationId())){
+ Coordinate endCoord = v.getEndLocationCoordinate();
+ if(endCoord == null) throw new NoLocationFoundException();
+ activities.add(endCoord.getX(),endCoord.getY());
+ }
+ }
+ for(Job job : vrp.getJobs().values()){
+ addJob(activities, job);
+ }
+ for(VehicleRoute r : vrp.getInitialVehicleRoutes()){
+ for(Job job : r.getTourActivities().getJobs()){
+ addJob(activities, job);
+ }
+ }
}
+ private void markItem(XYDataItem item, Activity activity, Job job) {
+ activitiesByDataItem.put(item,activity);
+ }
+
private Locations retrieveLocations(VehicleRoutingProblem vrp) throws NoLocationFoundException {
return vrp.getLocations();
-// final Map locs = new HashMap();
-//
-// for(Vehicle v : vrp.getVehicles()){
-// String startLocationId = v.getStartLocationId();
-// if(startLocationId == null) throw new NoLocationFoundException();
-// Coordinate startCoord = v.getStartLocationCoordinate();
-// if(startCoord == null) throw new NoLocationFoundException();
-// locs.put(startLocationId, startCoord);
-//
-// String endLocationId = v.getEndLocationId();
-// if(!startLocationId.equals(endLocationId)){
-// Coordinate endCoord = v.getEndLocationCoordinate();
-// if(endCoord == null) throw new NoLocationFoundException();
-// locs.put(endLocationId, endCoord);
-// }
-// }
-// for(Job j : vrp.getJobs().values()){
-// if(j instanceof Service){
-// String locationId = ((Service) j).getLocationId();
-// if(locationId == null) throw new NoLocationFoundException();
-// Coordinate coord = ((Service) j).getCoord();
-// if(coord == null) throw new NoLocationFoundException();
-// locs.put(locationId, coord);
-// }
-// else if(j instanceof Shipment){
-// {
-// String locationId = ((Shipment) j).getPickupLocation();
-// if(locationId == null) throw new NoLocationFoundException();
-// Coordinate coord = ((Shipment) j).getPickupCoord();
-// if(coord == null) throw new NoLocationFoundException();
-// locs.put(locationId, coord);
-// }
-// {
-// String locationId = ((Shipment) j).getDeliveryLocation();
-// if(locationId == null) throw new NoLocationFoundException();
-// Coordinate coord = ((Shipment) j).getDeliveryCoord();
-// if(coord == null) throw new NoLocationFoundException();
-// locs.put(locationId, coord);
-// }
-// }
-// else{
-// throw new IllegalStateException("job is neither a service nor a shipment. this is not supported yet.");
-// }
-// }
-// return new Locations() {
-//
-// @Override
-// public Coordinate getCoord(String id) {
-// return locs.get(id);
-// }
-// };
}
}