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 {
184 this.drawGrid(g);
185 }
186 }
187
188
189
190
191
192 public synchronized void showGrid(final boolean bool)
193 {
194 this.showGrid = bool;
195 this.repaint();
196 }
197
198
199
200
201
202 public Bounds2d getExtent()
203 {
204 return this.extent;
205 }
206
207
208
209
210
211 public void setExtent(final Bounds2d extent)
212 {
213 if (this.lastScreen != null)
214 {
215
216 this.lastXScale = this.getRenderableScale().getXScale(extent, this.lastScreen);
217 this.lastYScale = this.getRenderableScale().getYScale(extent, this.lastScreen);
218 }
219 this.extent = extent;
220 this.repaint();
221 }
222
223
224
225
226
227 public synchronized void setWorldCoordinate(final Point2d point)
228 {
229 this.worldCoordinate = point;
230 }
231
232
233
234
235 public synchronized Point2d getWorldCoordinate()
236 {
237 return this.worldCoordinate;
238 }
239
240
241
242
243 public synchronized void displayWorldCoordinateToolTip()
244 {
245 if (this.showToolTip)
246 {
247 String worldPoint = "(x=" + this.formatter.format(this.worldCoordinate.getX()) + " ; y="
248 + this.formatter.format(this.worldCoordinate.getY()) + ")";
249 setToolTipText(worldPoint);
250 }
251 }
252
253
254
255
256 public synchronized boolean isShowToolTip()
257 {
258 return this.showToolTip;
259 }
260
261
262
263
264 public synchronized void setShowToolTip(final boolean showToolTip)
265 {
266 this.showToolTip = showToolTip;
267 }
268
269
270
271
272
273
274 public synchronized void pan(final int direction, final double percentage)
275 {
276 if (percentage <= 0 || percentage > 1.0)
277 {
278 throw new IllegalArgumentException("percentage<=0 || >1.0");
279 }
280 switch (direction)
281 {
282 case LEFT:
283 this.extent = new Bounds2d(this.extent.getMinX() - percentage * this.extent.getDeltaX(),
284 this.extent.getMaxX() - percentage * this.extent.getDeltaX(), this.extent.getMinY(),
285 this.extent.getMaxY());
286 break;
287 case RIGHT:
288 this.extent = new Bounds2d(this.extent.getMinX() + percentage * this.extent.getDeltaX(),
289 this.extent.getMaxX() + percentage * this.extent.getDeltaX(), this.extent.getMinY(),
290 this.extent.getMaxY());
291 break;
292 case UP:
293 this.extent = new Bounds2d(this.extent.getMinX(), this.extent.getMaxX(),
294 this.extent.getMinY() + percentage * this.extent.getDeltaY(),
295 this.extent.getMaxY() + percentage * this.extent.getDeltaY());
296 break;
297 case DOWN:
298 this.extent = new Bounds2d(this.extent.getMinX(), this.extent.getMaxX(),
299 this.extent.getMinY() - percentage * this.extent.getDeltaY(),
300 this.extent.getMaxY() - percentage * this.extent.getDeltaY());
301 break;
302 default:
303 throw new IllegalArgumentException("direction unkown");
304 }
305 this.repaint();
306 }
307
308
309
310
311 public synchronized void home()
312 {
313 this.extent = computeVisibleExtent(this.homeExtent);
314 this.repaint();
315 }
316
317
318
319
320 public boolean isShowGrid()
321 {
322 return this.showGrid;
323 }
324
325
326
327
328 public void setShowGrid(final boolean showGrid)
329 {
330 this.showGrid = showGrid;
331 }
332
333
334
335
336
337 public Color getGridColor()
338 {
339 return this.gridColor;
340 }
341
342
343
344
345
346 public void setGridColor(final Color gridColor)
347 {
348 this.gridColor = gridColor;
349 }
350
351
352
353
354
355 public synchronized void zoom(final double factor)
356 {
357 zoom(factor, (int) (this.getWidth() / 2.0), (int) (this.getHeight() / 2.0));
358 }
359
360
361
362
363
364
365
366 public synchronized void zoom(final double factor, final int mouseX, final int mouseY)
367 {
368 Point2d mwc = this.renderableScale.getWorldCoordinates(new Point2D.Double(mouseX, mouseY), this.extent, this.getSize());
369 double minX = mwc.getX() - (mwc.getX() - this.extent.getMinX()) * factor;
370 double minY = mwc.getY() - (mwc.getY() - this.extent.getMinY()) * factor;
371 double w = this.extent.getDeltaX() * factor;
372 double h = this.extent.getDeltaY() * factor;
373
374 this.extent = new Bounds2d(minX, minX + w, minY, minY + h);
375 this.repaint();
376 }
377
378
379
380
381
382
383 protected synchronized void drawGrid(final Graphics g)
384 {
385
386 g.setFont(g.getFont().deriveFont(11.0f));
387 g.setColor(this.gridColor);
388 double scaleX = this.renderableScale.getXScale(this.extent, this.getSize());
389 double scaleY = this.renderableScale.getYScale(this.extent, this.getSize());
390
391 int count = 0;
392 int gridSizePixelsX = (int) Math.round(this.gridSizeX / scaleX);
393 while (gridSizePixelsX < 40)
394 {
395 this.gridSizeX = 10 * this.gridSizeX;
396 int maximumNumberOfDigits = (int) Math.max(0, 1 + Math.ceil(Math.log(1 / this.gridSizeX) / Math.log(10)));
397 this.formatter.setMaximumFractionDigits(maximumNumberOfDigits);
398 gridSizePixelsX = (int) Math.round(this.gridSizeX / scaleX);
399 if (count++ > 10)
400 {
401 break;
402 }
403 }
404
405 count = 0;
406 while (gridSizePixelsX > 10 * 40)
407 {
408 int maximumNumberOfDigits = (int) Math.max(0, 2 + Math.ceil(Math.log(1 / this.gridSizeX) / Math.log(10)));
409 this.formatter.setMaximumFractionDigits(maximumNumberOfDigits);
410 this.gridSizeX = this.gridSizeX / 10;
411 gridSizePixelsX = (int) Math.round(this.gridSizeX / scaleX);
412 if (count++ > 10)
413 {
414 break;
415 }
416 }
417
418 int gridSizePixelsY = (int) Math.round(this.gridSizeY / scaleY);
419 while (gridSizePixelsY < 40)
420 {
421 this.gridSizeY = 10 * this.gridSizeY;
422 int maximumNumberOfDigits = (int) Math.max(0, 1 + Math.ceil(Math.log(1 / this.gridSizeY) / Math.log(10)));
423 this.formatter.setMaximumFractionDigits(maximumNumberOfDigits);
424 gridSizePixelsY = (int) Math.round(this.gridSizeY / scaleY);
425 if (count++ > 10)
426 {
427 break;
428 }
429 }
430
431 count = 0;
432 while (gridSizePixelsY > 10 * 40)
433 {
434 int maximumNumberOfDigits = (int) Math.max(0, 2 + Math.ceil(Math.log(1 / this.gridSizeY) / Math.log(10)));
435 this.formatter.setMaximumFractionDigits(maximumNumberOfDigits);
436 this.gridSizeY = this.gridSizeY / 10;
437 gridSizePixelsY = (int) Math.round(this.gridSizeY / scaleY);
438 if (count++ > 10)
439 {
440 break;
441 }
442 }
443
444
445 double mod = this.extent.getMinX() % this.gridSizeX;
446 int x = (int) -Math.round(mod / scaleX);
447 while (x < this.getWidth())
448 {
449 Point2d point = this.renderableScale.getWorldCoordinates(new Point2D.Double(x, 0), this.extent, this.getSize());
450 if (point != null)
451 {
452 String label = this.formatter.format(Math.round(point.getX() / this.gridSizeX) * this.gridSizeX);
453 double labelWidth = this.getFontMetrics(this.getFont()).getStringBounds(label, g).getWidth();
454 if (x > labelWidth + 4)
455 {
456 g.drawLine(x, 15, x, this.getHeight());
457 g.drawString(label, (int) Math.round(x - 0.5 * labelWidth), 11);
458 }
459 }
460 x = x + gridSizePixelsX;
461 }
462
463
464 mod = Math.abs(this.extent.getMinY()) % this.gridSizeY;
465 int y = (int) Math.round(this.getSize().getHeight() - (mod / scaleY));
466 while (y > 15)
467 {
468 Point2d point = this.renderableScale.getWorldCoordinates(new Point2D.Double(0, y), this.extent, this.getSize());
469 if (point != null)
470 {
471 String label = this.formatter.format(Math.round(point.getY() / this.gridSizeY) * this.gridSizeY);
472 RectangularShape labelBounds = this.getFontMetrics(this.getFont()).getStringBounds(label, g);
473 g.drawLine((int) Math.round(labelBounds.getWidth() + 4), y, this.getWidth(), y);
474 g.drawString(label, 2, (int) Math.round(y + labelBounds.getHeight() * 0.3));
475 }
476 y = y - gridSizePixelsY;
477 }
478 }
479
480
481
482
483 public void repaint()
484 {
485
486 this.dirty = true;
487 }
488
489
490
491
492 public Dimension getSize()
493 {
494 return this.size;
495 }
496
497
498
499
500 public void setSize(final Dimension size)
501 {
502 this.size = size;
503 }
504
505
506
507
508 public Color getBackground()
509 {
510 return this.background;
511 }
512
513
514
515
516 public void setBackground(final Color background)
517 {
518 this.background = background;
519 }
520
521
522
523
524 public int getWidth()
525 {
526 return this.size.width;
527 }
528
529
530
531
532 public int getHeight()
533 {
534 return this.size.height;
535 }
536
537
538
539
540 public Dimension getPreferredSize()
541 {
542 return this.preferredSize;
543 }
544
545
546
547
548 public void setPreferredSize(final Dimension preferredSize)
549 {
550 this.preferredSize = preferredSize;
551 }
552
553
554
555
556 public String getToolTipText()
557 {
558 return this.toolTipText;
559 }
560
561
562
563
564 public void setToolTipText(final String toolTipText)
565 {
566 this.toolTipText = toolTipText;
567 }
568
569
570
571
572 public boolean isShowing()
573 {
574 return this.showing;
575 }
576
577
578
579
580 public void setShowing(final boolean showing)
581 {
582 this.showing = showing;
583 }
584
585
586
587
588 public Font getFont()
589 {
590 return this.currentFont;
591 }
592
593
594
595
596 public void setFont(final Font font)
597 {
598 this.currentFont = font;
599 }
600
601
602
603
604
605 public FontMetrics getFontMetrics(final Font font)
606 {
607 return this.canvas.getFontMetrics(font);
608 }
609
610
611
612
613 public boolean isDirty()
614 {
615 return this.dirty;
616 }
617
618 @Override
619 public boolean imageUpdate(final Image img, final int infoflags, final int x, final int y, final int width,
620 final int height)
621 {
622 return false;
623 }
624
625
626
627
628 public RenderableScale getRenderableScale()
629 {
630 return this.renderableScale;
631 }
632
633
634
635
636 public void setRenderableScale(final RenderableScale renderableScale)
637 {
638 this.renderableScale = renderableScale;
639 }
640
641
642
643
644
645
646 public Bounds2d computeVisibleExtent(final Bounds2d extent)
647 {
648 Dimension screen = getSize();
649 double xScale = this.renderableScale.getXScale(extent, screen);
650 double yScale = this.renderableScale.getYScale(extent, screen);
651 Bounds2d result;
652 if (this.lastYScale != null && yScale == this.lastYScale)
653 {
654 result = new Bounds2d(extent.midPoint().getX() - 0.5 * screen.getWidth() * yScale,
655 extent.midPoint().getX() + 0.5 * screen.getWidth() * yScale, extent.getMinY(), extent.getMaxY());
656 xScale = yScale;
657 }
658 else if (this.lastXScale != null && xScale == this.lastXScale)
659 {
660 result = new Bounds2d(extent.getMinX(), extent.getMaxX(),
661 extent.midPoint().getY() - 0.5 * screen.getHeight() * xScale * this.renderableScale.getYScaleRatio(),
662 extent.midPoint().getY() + 0.5 * screen.getHeight() * xScale * this.renderableScale.getYScaleRatio());
663 yScale = xScale;
664 }
665 else
666 {
667 double scale = this.lastXScale == null ? Math.min(xScale, yScale)
668 : this.lastXScale * this.lastScreen.getWidth() / screen.getWidth();
669 result = new Bounds2d(extent.midPoint().getX() - 0.5 * screen.getWidth() * scale,
670 extent.midPoint().getX() + 0.5 * screen.getWidth() * scale,
671 extent.midPoint().getY() - 0.5 * screen.getHeight() * scale * this.renderableScale.getYScaleRatio(),
672 extent.midPoint().getY() + 0.5 * screen.getHeight() * scale * this.renderableScale.getYScaleRatio());
673 yScale = scale;
674 xScale = scale;
675 }
676 this.lastXScale = xScale;
677 this.lastYScale = yScale;
678 this.lastScreen = screen;
679 return result;
680 }
681
682 }