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 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
141 @Override
142 public int getTransparency()
143 {
144 return TRANSLUCENT;
145 }
146
147
148 @Override
149 public PaintContext createContext(final ColorModel cm, final Rectangle deviceBounds,
150 final Rectangle2D userBounds, final AffineTransform xform, final RenderingHints hints)
151 {
152 return new PaintContext()
153 {
154
155 @Override
156 public void dispose()
157 {
158
159 }
160
161
162 @Override
163 public ColorModel getColorModel()
164 {
165 return ColorModel.getRGBdefault();
166 }
167
168
169 @Override
170 public Raster getRaster(final int x, final int y, final int w, final int h)
171 {
172
173 double wOffset = x - deviceBounds.getX();
174 double hOffset = y - deviceBounds.getY();
175
176
177 WritableRaster raster = getColorModel().createCompatibleWritableRaster(w, h);
178
179
180 for (int k = 0; k < raster.getDataBuffer().getSize(); k++)
181 {
182
183 double i = hOffset + k / w;
184 double j = wOffset + k % w;
185
186
187 double bot = i / deviceBounds.getHeight();
188 double top = 1.0 - bot;
189 double rig = j / deviceBounds.getWidth();
190 double lef = 1.0 - rig;
191
192
193 double z = z00f * lef * bot + z10f * top * lef + z01f * bot * rig + z11f * top * rig;
194
195
196 Color c = (Color) getPaintScale().getPaint(z);
197
198
199 raster.getDataBuffer().setElem(k, c.getRGB());
200 }
201 return raster;
202 }
203 };
204 }
205 };
206
207 }
208
209
210 double x = dataset.getXValue(series, item);
211 double y = dataset.getYValue(series, item);
212 Rectangle2D rect =
213 RectangleAnchor.createRectangle(new Size2D(getBlockWidth(), getBlockHeight()), x, y, getBlockAnchor());
214 double xx0 = domainAxis.valueToJava2D(rect.getMinX(), dataArea, plot.getDomainAxisEdge());
215 double yy0 = rangeAxis.valueToJava2D(rect.getMinY(), dataArea, plot.getRangeAxisEdge());
216 double xx1 = domainAxis.valueToJava2D(rect.getMaxX(), dataArea, plot.getDomainAxisEdge());
217 double yy1 = rangeAxis.valueToJava2D(rect.getMaxY(), dataArea, plot.getRangeAxisEdge());
218
219
220 Rectangle2D block;
221 PlotOrientation orientation = plot.getOrientation();
222 if (orientation.equals(PlotOrientation.HORIZONTAL))
223 {
224 block = new Rectangle2D.Double(Math.min(yy0, yy1), Math.min(xx0, xx1), Math.abs(yy1 - yy0), Math.abs(xx0 - xx1));
225 }
226 else
227 {
228 block = new Rectangle2D.Double(Math.min(xx0, xx1), Math.min(yy0, yy1), Math.abs(xx1 - xx0), Math.abs(yy1 - yy0));
229 }
230 g2.setPaint(p);
231 g2.fill(block);
232 g2.setStroke(new BasicStroke(1.0f));
233 g2.draw(block);
234
235 if (isItemLabelVisible(series, item))
236 {
237 drawItemLabel(g2, orientation, dataset, series, item, block.getCenterX(), block.getCenterY(), y < 0.0);
238 }
239
240 int datasetIndex = plot.indexOf(dataset);
241 double transX = domainAxis.valueToJava2D(x, dataArea, plot.getDomainAxisEdge());
242 double transY = rangeAxis.valueToJava2D(y, dataArea, plot.getRangeAxisEdge());
243 updateCrosshairValues(crosshairState, x, y, datasetIndex, transX, transY, orientation);
244
245 EntityCollection entities = state.getEntityCollection();
246 if (entities != null)
247 {
248 addEntity(entities, block, dataset, series, item, block.getCenterX(), block.getCenterY());
249 }
250 }
251
252
253
254
255
256
257
258
259
260 private double getAdjacentZ(final int series, final int item, final boolean up, final boolean right)
261 {
262 if (up && (item + 1) % this.xyInterpolatedDataset.getRangeBinCount() == 0)
263 {
264
265 return Double.NaN;
266 }
267 int adjacentItem = item + (up ? 1 : 0) + (right ? this.xyInterpolatedDataset.getRangeBinCount() : 0);
268 if (adjacentItem >= this.xyInterpolatedDataset.getItemCount(series))
269 {
270
271 return Double.NaN;
272 }
273 return this.xyInterpolatedDataset.getZValue(series, adjacentItem);
274 }
275
276
277
278
279
280
281
282
283
284
285
286
287 private double fixNaN(final double value, final double adjacentCorner1, final double adjacentCorner2,
288 final double oppositeCorner)
289 {
290 if (!Double.isNaN(value))
291 {
292 return value;
293 }
294 if (Double.isNaN(adjacentCorner1))
295 {
296 if (Double.isNaN(adjacentCorner2))
297 {
298 return oppositeCorner;
299 }
300 else
301 {
302 return adjacentCorner2;
303 }
304 }
305 else if (Double.isNaN(adjacentCorner2))
306 {
307 return adjacentCorner1;
308 }
309 return 0.5 * (adjacentCorner1 + adjacentCorner2);
310 }
311
312
313 @Override
314 public String toString()
315 {
316 return "XYInterpolatedBlockRenderer [interpolate=" + this.interpolate + ", xyInterpolatedDataset="
317 + this.xyInterpolatedDataset + "]";
318 }
319
320 }