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