1 package nl.tudelft.simulation.dsol.web.animation.d2;
2
3 import java.awt.Canvas;
4 import java.awt.Color;
5 import java.awt.Dimension;
6 import java.awt.Font;
7 import java.awt.FontMetrics;
8 import java.awt.Graphics;
9 import java.awt.Image;
10 import java.awt.geom.Point2D;
11 import java.awt.geom.RectangularShape;
12 import java.awt.image.ImageObserver;
13 import java.text.NumberFormat;
14
15 import org.djutils.draw.bounds.Bounds2d;
16 import org.djutils.draw.point.Point2d;
17
18 import nl.tudelft.simulation.dsol.animation.d2.RenderableScale;
19 import nl.tudelft.simulation.dsol.web.animation.HtmlGraphics2d;
20
21
22
23
24
25
26
27
28
29 public class HtmlGridPanel implements ImageObserver
30 {
31
32 public static final int UP = 1;
33
34
35 public static final int DOWN = 2;
36
37
38 public static final int LEFT = 3;
39
40
41 public static final int RIGHT = 4;
42
43
44 public static final double ZOOMFACTOR = 1.2;
45
46
47 protected static final Color GRIDCOLOR = Color.BLACK;
48
49
50 @SuppressWarnings("checkstyle:visibilitymodifier")
51 protected Bounds2d extent = null;
52
53
54 @SuppressWarnings("checkstyle:visibilitymodifier")
55 protected Bounds2d homeExtent = null;
56
57
58 @SuppressWarnings("checkstyle:visibilitymodifier")
59 protected boolean showGrid = true;
60
61
62 @SuppressWarnings("checkstyle:visibilitymodifier")
63 protected double gridSizeX = 100.0;
64
65
66 @SuppressWarnings("checkstyle:visibilitymodifier")
67 protected double gridSizeY = 100.0;
68
69
70 @SuppressWarnings("checkstyle:visibilitymodifier")
71 protected NumberFormat formatter = NumberFormat.getInstance();
72
73
74 @SuppressWarnings("checkstyle:visibilitymodifier")
75 protected Dimension lastDimension = null;
76
77
78 @SuppressWarnings("checkstyle:visibilitymodifier")
79 protected Dimension size = null;
80
81
82 @SuppressWarnings("checkstyle:visibilitymodifier")
83 protected Dimension preferredSize = null;
84
85
86 @SuppressWarnings("checkstyle:visibilitymodifier")
87 protected Point2d worldCoordinate = new Point2d(0, 0);
88
89
90 @SuppressWarnings("checkstyle:visibilitymodifier")
91 protected boolean showToolTip = true;
92
93
94 private Color background;
95
96
97 private String toolTipText = "";
98
99
100 private boolean showing = true;
101
102
103 private Font currentFont = new Font(Font.SANS_SERIF, Font.PLAIN, 10);
104
105
106 private Canvas canvas = new Canvas();
107
108
109 @SuppressWarnings("checkstyle:visibilitymodifier")
110 protected HtmlGraphics2d htmlGraphics2D;
111
112
113 @SuppressWarnings("checkstyle:visibilitymodifier")
114 protected RenderableScale renderableScale;
115
116
117 private boolean dirty = false;
118
119
120
121
122
123 public HtmlGridPanel(final Bounds2d extent)
124 {
125 this(extent, new Dimension(600, 600));
126 }
127
128
129
130
131
132
133 public HtmlGridPanel(final Bounds2d extent, final Dimension size)
134 {
135 this.htmlGraphics2D = new HtmlGraphics2d();
136 this.homeExtent = extent;
137 this.renderableScale = new RenderableScale();
138 this.setBackground(Color.WHITE);
139 this.setPreferredSize(size);
140 this.size = (Dimension) size.clone();
141 this.lastDimension = this.getSize();
142 setExtent(this.homeExtent);
143 }
144
145
146
147
148
149 public String getDrawingCommands()
150 {
151 this.htmlGraphics2D.clearCommand();
152 this.paintComponent(this.htmlGraphics2D);
153 return this.htmlGraphics2D.closeAndGetCommands();
154 }
155
156
157
158
159
160 public void paintComponent(final HtmlGraphics2d g)
161 {
162 if (!this.getSize().equals(this.lastDimension))
163 {
164 this.lastDimension = this.getSize();
165 setExtent(this.renderableScale.computeVisibleExtent(this.extent, this.getSize()));
166 }
167 if (this.showGrid)
168 {
169 this.drawGrid(g);
170 }
171 }
172
173
174
175
176
177 public final synchronized void showGrid(final boolean bool)
178 {
179 this.showGrid = bool;
180 this.repaint();
181 }
182
183
184
185
186
187 public final Bounds2d getExtent()
188 {
189 return this.extent;
190 }
191
192
193
194
195
196 public final void setExtent(final Bounds2d extent)
197 {
198 this.extent = extent;
199 this.repaint();
200 }
201
202
203
204
205
206 public final synchronized void setWorldCoordinate(final Point2d point)
207 {
208 this.worldCoordinate = point;
209 }
210
211
212
213
214 public final synchronized Point2d getWorldCoordinate()
215 {
216 return this.worldCoordinate;
217 }
218
219
220
221
222 public final synchronized void displayWorldCoordinateToolTip()
223 {
224 if (this.showToolTip)
225 {
226 String worldPoint = "(x=" + this.formatter.format(this.worldCoordinate.getX()) + " ; y="
227 + this.formatter.format(this.worldCoordinate.getY()) + ")";
228 setToolTipText(worldPoint);
229 }
230 }
231
232
233
234
235 public final synchronized boolean isShowToolTip()
236 {
237 return this.showToolTip;
238 }
239
240
241
242
243 public final synchronized void setShowToolTip(final boolean showToolTip)
244 {
245 this.showToolTip = showToolTip;
246 }
247
248
249
250
251
252
253 public final synchronized void pan(final int direction, final double percentage)
254 {
255 if (percentage <= 0 || percentage > 1.0)
256 {
257 throw new IllegalArgumentException("percentage<=0 || >1.0");
258 }
259 switch (direction)
260 {
261 case LEFT:
262 setExtent(new Bounds2d(this.extent.getMinX() - percentage * this.extent.getDeltaX(),
263 this.extent.getMaxX() - percentage * this.extent.getDeltaX(), this.extent.getMinY(),
264 this.extent.getMaxY()));
265 break;
266 case RIGHT:
267 setExtent(new Bounds2d(this.extent.getMinX() + percentage * this.extent.getDeltaX(),
268 this.extent.getMaxX() + percentage * this.extent.getDeltaX(), this.extent.getMinY(),
269 this.extent.getMaxY()));
270 break;
271 case UP:
272 setExtent(new Bounds2d(this.extent.getMinX(), this.extent.getMaxX(),
273 this.extent.getMinY() + percentage * this.extent.getDeltaY(),
274 this.extent.getMaxY() + percentage * this.extent.getDeltaY()));
275 break;
276 case DOWN:
277 setExtent(new Bounds2d(this.extent.getMinX(), this.extent.getMaxX(),
278 this.extent.getMinY() - percentage * this.extent.getDeltaY(),
279 this.extent.getMaxY() - percentage * this.extent.getDeltaY()));
280 break;
281 default:
282 throw new IllegalArgumentException("direction unkown");
283 }
284 this.repaint();
285 }
286
287
288
289
290 public final synchronized void home()
291 {
292 setExtent(this.renderableScale.computeVisibleExtent(this.homeExtent, this.getSize()));
293 this.repaint();
294 }
295
296
297
298
299 public final boolean isShowGrid()
300 {
301 return this.showGrid;
302 }
303
304
305
306
307 public final void setShowGrid(final boolean showGrid)
308 {
309 this.showGrid = showGrid;
310 }
311
312
313
314
315
316 public final synchronized void zoom(final double factor)
317 {
318 zoom(factor, (int) (this.getWidth() / 2.0), (int) (this.getHeight() / 2.0));
319 }
320
321
322
323
324
325
326
327 public final synchronized void zoom(final double factor, final int mouseX, final int mouseY)
328 {
329 Point2d mwc = this.renderableScale.getWorldCoordinates(new Point2D.Double(mouseX, mouseY), this.extent, this.getSize());
330 double minX = mwc.getX() - (mwc.getX() - this.extent.getMinX()) * factor;
331 double minY = mwc.getY() - (mwc.getY() - this.extent.getMinY()) * factor;
332 double w = this.extent.getDeltaX() * factor;
333 double h = this.extent.getDeltaY() * factor;
334
335 setExtent(new Bounds2d(minX, minX + w, minY, minY + h));
336 this.repaint();
337 }
338
339
340
341
342
343
344 protected synchronized void drawGrid(final Graphics g)
345 {
346
347 g.setFont(g.getFont().deriveFont(11.0f));
348 g.setColor(GRIDCOLOR);
349 double scaleX = this.renderableScale.getXScale(this.extent, this.getSize());
350 double scaleY = this.renderableScale.getYScale(this.extent, this.getSize());
351
352 int count = 0;
353 int gridSizePixelsX = (int) Math.round(this.gridSizeX / scaleX);
354 while (gridSizePixelsX < 40)
355 {
356 this.gridSizeX = 10 * this.gridSizeX;
357 int maximumNumberOfDigits = (int) Math.max(0, 1 + Math.ceil(Math.log(1 / this.gridSizeX) / Math.log(10)));
358 this.formatter.setMaximumFractionDigits(maximumNumberOfDigits);
359 gridSizePixelsX = (int) Math.round(this.gridSizeX / scaleX);
360 if (count++ > 10)
361 {
362 break;
363 }
364 }
365
366 count = 0;
367 while (gridSizePixelsX > 10 * 40)
368 {
369 int maximumNumberOfDigits = (int) Math.max(0, 2 + Math.ceil(Math.log(1 / this.gridSizeX) / Math.log(10)));
370 this.formatter.setMaximumFractionDigits(maximumNumberOfDigits);
371 this.gridSizeX = this.gridSizeX / 10;
372 gridSizePixelsX = (int) Math.round(this.gridSizeX / scaleX);
373 if (count++ > 10)
374 {
375 break;
376 }
377 }
378
379 int gridSizePixelsY = (int) Math.round(this.gridSizeY / scaleY);
380 while (gridSizePixelsY < 40)
381 {
382 this.gridSizeY = 10 * this.gridSizeY;
383 int maximumNumberOfDigits = (int) Math.max(0, 1 + Math.ceil(Math.log(1 / this.gridSizeY) / Math.log(10)));
384 this.formatter.setMaximumFractionDigits(maximumNumberOfDigits);
385 gridSizePixelsY = (int) Math.round(this.gridSizeY / scaleY);
386 if (count++ > 10)
387 {
388 break;
389 }
390 }
391
392 count = 0;
393 while (gridSizePixelsY > 10 * 40)
394 {
395 int maximumNumberOfDigits = (int) Math.max(0, 2 + Math.ceil(Math.log(1 / this.gridSizeY) / Math.log(10)));
396 this.formatter.setMaximumFractionDigits(maximumNumberOfDigits);
397 this.gridSizeY = this.gridSizeY / 10;
398 gridSizePixelsY = (int) Math.round(this.gridSizeY / scaleY);
399 if (count++ > 10)
400 {
401 break;
402 }
403 }
404
405
406 double mod = this.extent.getMinX() % this.gridSizeX;
407 int x = (int) -Math.round(mod / scaleX);
408 while (x < this.getWidth())
409 {
410 Point2d point = this.renderableScale.getWorldCoordinates(new Point2D.Double(x, 0), this.extent, this.getSize());
411 if (point != null)
412 {
413 String label = this.formatter.format(Math.round(point.getX() / this.gridSizeX) * this.gridSizeX);
414 double labelWidth = this.getFontMetrics(this.getFont()).getStringBounds(label, g).getWidth();
415 if (x > labelWidth + 4)
416 {
417 g.drawLine(x, 15, x, this.getHeight());
418 g.drawString(label, (int) Math.round(x - 0.5 * labelWidth), 11);
419 }
420 }
421 x = x + gridSizePixelsX;
422 }
423
424
425 mod = Math.abs(this.extent.getMinY()) % this.gridSizeY;
426 int y = (int) Math.round(this.getSize().getHeight() - (mod / scaleY));
427 while (y > 15)
428 {
429 Point2d point = this.renderableScale.getWorldCoordinates(new Point2D.Double(0, y), this.extent, this.getSize());
430 if (point != null)
431 {
432 String label = this.formatter.format(Math.round(point.getY() / this.gridSizeY) * this.gridSizeY);
433 RectangularShape labelBounds = this.getFontMetrics(this.getFont()).getStringBounds(label, g);
434 g.drawLine((int) Math.round(labelBounds.getWidth() + 4), y, this.getWidth(), y);
435 g.drawString(label, 2, (int) Math.round(y + labelBounds.getHeight() * 0.3));
436 }
437 y = y - gridSizePixelsY;
438 }
439 }
440
441
442
443
444 public final RenderableScale getRenderableScale()
445 {
446 return this.renderableScale;
447 }
448
449
450
451
452 public final void setRenderableScale(final RenderableScale renderableScale)
453 {
454 this.renderableScale = renderableScale;
455 }
456
457
458
459
460 public void repaint()
461 {
462
463 this.dirty = true;
464 }
465
466
467
468
469 public final Dimension getSize()
470 {
471 return this.size;
472 }
473
474
475
476
477 public final void setSize(final Dimension size)
478 {
479 this.size = size;
480 }
481
482
483
484
485 public final Color getBackground()
486 {
487 return this.background;
488 }
489
490
491
492
493 public final void setBackground(final Color background)
494 {
495 this.background = background;
496 }
497
498
499
500
501 public final int getWidth()
502 {
503 return this.size.width;
504 }
505
506
507
508
509 public final int getHeight()
510 {
511 return this.size.height;
512 }
513
514
515
516
517 public final Dimension getPreferredSize()
518 {
519 return this.preferredSize;
520 }
521
522
523
524
525 public final void setPreferredSize(final Dimension preferredSize)
526 {
527 this.preferredSize = preferredSize;
528 }
529
530
531
532
533 public final String getToolTipText()
534 {
535 return this.toolTipText;
536 }
537
538
539
540
541 public final void setToolTipText(final String toolTipText)
542 {
543 this.toolTipText = toolTipText;
544 }
545
546
547
548
549 public final boolean isShowing()
550 {
551 return this.showing;
552 }
553
554
555
556
557 public final void setShowing(final boolean showing)
558 {
559 this.showing = showing;
560 }
561
562
563
564
565 public final Font getFont()
566 {
567 return this.currentFont;
568 }
569
570
571
572
573 public final void setFont(final Font font)
574 {
575 this.currentFont = font;
576 }
577
578
579
580
581
582 public final FontMetrics getFontMetrics(final Font font)
583 {
584 return this.canvas.getFontMetrics(font);
585 }
586
587
588
589
590 public final boolean isDirty()
591 {
592 return this.dirty;
593 }
594
595
596 @Override
597 public boolean imageUpdate(final Image img, final int infoflags, final int x, final int y, final int width,
598 final int height)
599 {
600 return false;
601 }
602
603 }