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 }