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); -// } -// }; } }