1   package org.opentrafficsim.draw.graphs;
2   
3   import java.awt.BasicStroke;
4   import java.awt.Color;
5   import java.awt.Graphics2D;
6   import java.awt.Paint;
7   import java.awt.PaintContext;
8   import java.awt.Rectangle;
9   import java.awt.RenderingHints;
10  import java.awt.geom.AffineTransform;
11  import java.awt.geom.Rectangle2D;
12  import java.awt.image.ColorModel;
13  import java.awt.image.Raster;
14  import java.awt.image.WritableRaster;
15  
16  import org.djutils.exceptions.Throw;
17  import org.jfree.chart.axis.ValueAxis;
18  import org.jfree.chart.entity.EntityCollection;
19  import org.jfree.chart.plot.CrosshairState;
20  import org.jfree.chart.plot.PlotOrientation;
21  import org.jfree.chart.plot.PlotRenderingInfo;
22  import org.jfree.chart.plot.XYPlot;
23  import org.jfree.chart.renderer.PaintScale;
24  import org.jfree.chart.renderer.xy.XYBlockRenderer;
25  import org.jfree.chart.renderer.xy.XYItemRendererState;
26  import org.jfree.chart.ui.RectangleAnchor;
27  import org.jfree.chart.ui.Size2D;
28  import org.jfree.data.xy.XYDataset;
29  import org.opentrafficsim.draw.core.ColorPaintScale;
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  public class XYInterpolatedBlockRenderer extends XYBlockRenderer
47  {
48  
49      
50      private static final long serialVersionUID = 20181008L;
51  
52      
53      private boolean interpolate = true;
54  
55      
56      private final XYInterpolatedDataset xyInterpolatedDataset;
57  
58      
59  
60  
61      public XYInterpolatedBlockRenderer(final XYInterpolatedDataset xyInterpolatedDataset)
62      {
63          super(); 
64          this.xyInterpolatedDataset = xyInterpolatedDataset;
65      }
66  
67      
68  
69  
70      @Override
71      public void setPaintScale(final PaintScale scale)
72      {
73          Throw.when(!(scale instanceof ColorPaintScale), UnsupportedOperationException.class,
74                  "Class XYInterpolatedBlockRenderer requires a ColorPaintScale.");
75          super.setPaintScale(scale);
76      }
77  
78      
79  
80  
81      @Override
82      public void setBlockAnchor(final RectangleAnchor anchor)
83      {
84          throw new UnsupportedOperationException(
85                  "Class XYInterpolatedBlockRenderer does not support setting the anchor, it's coupled to interpolation.");
86      }
87  
88      
89  
90  
91  
92  
93  
94      public final void setInterpolate(final boolean interpolate)
95      {
96          this.interpolate = interpolate;
97          if (interpolate)
98          {
99              super.setBlockAnchor(RectangleAnchor.TOP_LEFT); 
100         }
101         else
102         {
103             super.setBlockAnchor(RectangleAnchor.CENTER);
104         }
105     }
106 
107     
108 
109 
110     @Override
111     @SuppressWarnings("parameternumber")
112     public void drawItem(final Graphics2D g2, final XYItemRendererState state, final Rectangle2D dataArea,
113             final PlotRenderingInfo info, final XYPlot plot, final ValueAxis domainAxis, final ValueAxis rangeAxis,
114             final XYDataset dataset, final int series, final int item, final CrosshairState crosshairState, final int pass)
115     {
116 
117         double z00 = this.xyInterpolatedDataset.getZValue(series, item);
118         Paint p;
119 
120         if (!this.interpolate)
121         {
122             
123             p = getPaintScale().getPaint(z00);
124         }
125         else
126         {
127             
128             double z10 = getAdjacentZ(series, item, true, false);
129             double z01 = getAdjacentZ(series, item, false, true);
130             double z11 = getAdjacentZ(series, item, true, true);
131 
132             
133             double z00f = fixNaN(z00, z01, z10, z11);
134             double z10f = fixNaN(z10, z00, z11, z01);
135             double z01f = fixNaN(z01, z00, z11, z10);
136             double z11f = fixNaN(z11, z10, z01, z00);
137 
138             
139             p = new Paint()
140             {
141                 
142                 @Override
143                 public int getTransparency()
144                 {
145                     return TRANSLUCENT;
146                 }
147 
148                 
149                 @Override
150                 public PaintContext createContext(final ColorModel cm, final Rectangle deviceBounds,
151                         final Rectangle2D userBounds, final AffineTransform xform, final RenderingHints hints)
152                 {
153                     return new PaintContext()
154                     {
155                         
156                         @Override
157                         public void dispose()
158                         {
159                             
160                         }
161 
162                         
163                         @Override
164                         public ColorModel getColorModel()
165                         {
166                             return ColorModel.getRGBdefault();
167                         }
168 
169                         
170                         @Override
171                         public Raster getRaster(final int x, final int y, final int w, final int h)
172                         {
173                             
174                             double wOffset = x - deviceBounds.getX();
175                             double hOffset = y - deviceBounds.getY();
176 
177                             
178                             WritableRaster raster = getColorModel().createCompatibleWritableRaster(w, h);
179 
180                             
181                             for (int k = 0; k < raster.getDataBuffer().getSize(); k++)
182                             {
183                                 
184                                 double i = hOffset + k / w;
185                                 double j = wOffset + k % w;
186 
187                                 
188                                 double bot = i / deviceBounds.getHeight();
189                                 double top = 1.0 - bot;
190                                 double rig = j / deviceBounds.getWidth();
191                                 double lef = 1.0 - rig;
192 
193                                 
194                                 double z = z00f * lef * bot + z10f * top * lef + z01f * bot * rig + z11f * top * rig;
195 
196                                 
197                                 Color c = (Color) getPaintScale().getPaint(z); 
198 
199                                 
200                                 raster.getDataBuffer().setElem(k, c.getRGB());
201                             }
202                             return raster;
203                         }
204                     };
205                 }
206             };
207 
208         }
209 
210         
211         double x = dataset.getXValue(series, item);
212         double y = dataset.getYValue(series, item);
213         Rectangle2D rect =
214                 RectangleAnchor.createRectangle(new Size2D(getBlockWidth(), getBlockHeight()), x, y, getBlockAnchor());
215         double xx0 = domainAxis.valueToJava2D(rect.getMinX(), dataArea, plot.getDomainAxisEdge());
216         double yy0 = rangeAxis.valueToJava2D(rect.getMinY(), dataArea, plot.getRangeAxisEdge());
217         double xx1 = domainAxis.valueToJava2D(rect.getMaxX(), dataArea, plot.getDomainAxisEdge());
218         double yy1 = rangeAxis.valueToJava2D(rect.getMaxY(), dataArea, plot.getRangeAxisEdge());
219 
220         
221         Rectangle2D block;
222         PlotOrientation orientation = plot.getOrientation();
223         if (orientation.equals(PlotOrientation.HORIZONTAL))
224         {
225             block = new Rectangle2D.Double(Math.min(yy0, yy1), Math.min(xx0, xx1), Math.abs(yy1 - yy0), Math.abs(xx0 - xx1));
226         }
227         else
228         {
229             block = new Rectangle2D.Double(Math.min(xx0, xx1), Math.min(yy0, yy1), Math.abs(xx1 - xx0), Math.abs(yy1 - yy0));
230         }
231         g2.setPaint(p);
232         g2.fill(block);
233         g2.setStroke(new BasicStroke(1.0f));
234         g2.draw(block);
235 
236         if (isItemLabelVisible(series, item))
237         {
238             drawItemLabel(g2, orientation, dataset, series, item, block.getCenterX(), block.getCenterY(), y < 0.0);
239         }
240 
241         int datasetIndex = plot.indexOf(dataset);
242         double transX = domainAxis.valueToJava2D(x, dataArea, plot.getDomainAxisEdge());
243         double transY = rangeAxis.valueToJava2D(y, dataArea, plot.getRangeAxisEdge());
244         updateCrosshairValues(crosshairState, x, y, datasetIndex, transX, transY, orientation);
245 
246         EntityCollection entities = state.getEntityCollection();
247         if (entities != null)
248         {
249             addEntity(entities, block, dataset, series, item, block.getCenterX(), block.getCenterY());
250         }
251     }
252 
253     
254 
255 
256 
257 
258 
259 
260 
261     private double getAdjacentZ(final int series, final int item, final boolean up, final boolean right)
262     {
263         if (up && (item + 1) % this.xyInterpolatedDataset.getRangeBinCount() == 0)
264         {
265             
266             return Double.NaN;
267         }
268         int adjacentItem = item + (up ? 1 : 0) + (right ? this.xyInterpolatedDataset.getRangeBinCount() : 0);
269         if (adjacentItem >= this.xyInterpolatedDataset.getItemCount(series))
270         {
271             
272             return Double.NaN;
273         }
274         return this.xyInterpolatedDataset.getZValue(series, adjacentItem);
275     }
276 
277     
278 
279 
280 
281 
282 
283 
284 
285 
286 
287 
288     private double fixNaN(final double value, final double adjacentCorner1, final double adjacentCorner2,
289             final double oppositeCorner)
290     {
291         if (!Double.isNaN(value))
292         {
293             return value;
294         }
295         if (Double.isNaN(adjacentCorner1))
296         {
297             if (Double.isNaN(adjacentCorner2))
298             {
299                 return oppositeCorner;
300             }
301             else
302             {
303                 return adjacentCorner2;
304             }
305         }
306         else if (Double.isNaN(adjacentCorner2))
307         {
308             return adjacentCorner1;
309         }
310         return 0.5 * (adjacentCorner1 + adjacentCorner2);
311     }
312 
313     
314     @Override
315     public String toString()
316     {
317         return "XYInterpolatedBlockRenderer [interpolate=" + this.interpolate + ", xyInterpolatedDataset="
318                 + this.xyInterpolatedDataset + "]";
319     }
320 
321 }