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