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