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