View Javadoc
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.Rectangle2D;
12  import java.awt.geom.RectangularShape;
13  import java.awt.image.ImageObserver;
14  import java.text.NumberFormat;
15  
16  import nl.tudelft.simulation.dsol.animation.D2.Renderable2DInterface;
17  import nl.tudelft.simulation.dsol.web.animation.HTMLGraphics2D;
18  
19  /**
20   * The GridPanel introduces the gridPanel.
21   * <p>
22   * Copyright (c) 2002-2020 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
23   * for project information <a href="https://simulation.tudelft.nl/" target="_blank"> https://simulation.tudelft.nl</a>. The DSOL
24   * project is distributed under a three-clause BSD-style license, which can be found at
25   * <a href="https://simulation.tudelft.nl/dsol/3.0/license.html" target="_blank">
26   * https://simulation.tudelft.nl/dsol/3.0/license.html</a>.
27   * </p>
28   * @author <a href="mailto:nlang@fbk.eur.nl">Niels Lang </a>, <a href="http://www.peter-jacobs.com">Peter Jacobs </a>
29   */
30  public class HTMLGridPanel implements ImageObserver
31  {
32      /** the UP directions for moving/zooming. */
33      public static final int UP = 1;
34  
35      /** the DOWN directions for moving/zooming. */
36      public static final int DOWN = 2;
37  
38      /** the LEFT directions for moving/zooming. */
39      public static final int LEFT = 3;
40  
41      /** the RIGHT directions for moving/zooming. */
42      public static final int RIGHT = 4;
43  
44      /** the ZOOM factor. */
45      public static final double ZOOMFACTOR = 1.2;
46  
47      /** gridColor. */
48      protected static final Color GRIDCOLOR = Color.BLACK;
49  
50      /** the extent of this panel. */
51      @SuppressWarnings("checkstyle:visibilitymodifier")
52      protected Rectangle2D extent = null;
53  
54      /** the extent of this panel. */
55      @SuppressWarnings("checkstyle:visibilitymodifier")
56      protected Rectangle2D homeExtent = null;
57  
58      /** show the grid. */
59      @SuppressWarnings("checkstyle:visibilitymodifier")
60      protected boolean showGrid = true;
61  
62      /** the gridSize in world Units. */
63      @SuppressWarnings("checkstyle:visibilitymodifier")
64      protected double gridSize = 100.0;
65  
66      /** the formatter to use. */
67      @SuppressWarnings("checkstyle:visibilitymodifier")
68      protected NumberFormat formatter = NumberFormat.getInstance();
69  
70      /** the last computed Dimension. */
71      @SuppressWarnings("checkstyle:visibilitymodifier")
72      protected Dimension lastDimension = null;
73  
74      /** the last computed Dimension. */
75      @SuppressWarnings("checkstyle:visibilitymodifier")
76      protected Dimension size = null;
77  
78      /** the last computed Dimension. */
79      @SuppressWarnings("checkstyle:visibilitymodifier")
80      protected Dimension preferredSize = null;
81  
82      /** the last known world coordinate of the mouse. */
83      @SuppressWarnings("checkstyle:visibilitymodifier")
84      protected Point2D worldCoordinate = new Point2D.Double();
85  
86      /** whether to show a tooltip with the coordinates or not. */
87      @SuppressWarnings("checkstyle:visibilitymodifier")
88      protected boolean showToolTip = true;
89  
90      /** the background color. */
91      private Color background;
92  
93      /** The tooltip text which shows the coordinates. */
94      private String toolTipText = "";
95  
96      /** Whether the panel is showing or not. */
97      private boolean showing = true;
98  
99      /** the current font. */
100     private Font currentFont = new Font(Font.SANS_SERIF, Font.PLAIN, 10);
101 
102     /** the canvas to determine the font metrics. */
103     private Canvas canvas = new Canvas();
104 
105     /** the HTMLGraphics2D 'shadow' canvas. */
106     protected HTMLGraphics2D htmlGraphics2D;
107 
108     /** dirty flag. */
109     private boolean dirty = false;
110 
111     /**
112      * constructs a new GridPanel.
113      * @param extent Rectangle2D; the extent to show.
114      */
115     public HTMLGridPanel(final Rectangle2D extent)
116     {
117         this(extent, new Dimension(600, 600));
118     }
119 
120     /**
121      * constructs a new GridPanel.
122      * @param extent Rectangle2D; the initial extent.
123      * @param size Dimension; the size of the panel in pixels.
124      */
125     public HTMLGridPanel(final Rectangle2D extent, final Dimension size)
126     {
127         this.htmlGraphics2D = new HTMLGraphics2D();
128         this.extent = extent;
129         this.homeExtent = (Rectangle2D) extent.clone();
130         this.setBackground(Color.WHITE);
131         this.setPreferredSize(size);
132         this.size = (Dimension) size.clone();
133         this.lastDimension = this.getSize();
134     }
135 
136     /**
137      * Return the set of drawing commands.
138      * @return the set of drawing commands
139      */
140     public String getDrawingCommands()
141     {
142         this.htmlGraphics2D.clearCommand();
143         this.paintComponent(this.htmlGraphics2D);
144         return this.htmlGraphics2D.closeAndGetCommands();
145     }
146 
147     /**
148      * Draw the grid.
149      * @param g HTMLGraphics2D; the virtual Graphics2D canvas to enable writing to the browser
150      */
151     public void paintComponent(final HTMLGraphics2D g)
152     {
153         if (!this.getSize().equals(this.lastDimension))
154         {
155             this.lastDimension = this.getSize();
156             this.extent = Renderable2DInterface.Util.computeVisibleExtent(this.extent, this.getSize());
157         }
158         if (this.showGrid)
159         {
160             this.drawGrid(g);
161         }
162     }
163 
164     /**
165      * show the grid?
166      * @param bool boolean; true/false
167      */
168     public final synchronized void showGrid(final boolean bool)
169     {
170         this.showGrid = bool;
171         this.repaint();
172     }
173 
174     /**
175      * returns the extent of this panel.
176      * @return Rectangle2D
177      */
178     public final Rectangle2D getExtent()
179     {
180         return this.extent;
181     }
182 
183     /**
184      * Set the world coordinates based on a mouse move.
185      * @param point Point2D; the x,y world coordinates
186      */
187     public final synchronized void setWorldCoordinate(final Point2D point)
188     {
189         this.worldCoordinate = point;
190     }
191 
192     /**
193      * @return worldCoordinate
194      */
195     public final synchronized Point2D getWorldCoordinate()
196     {
197         return this.worldCoordinate;
198     }
199 
200     /**
201      * Display a tooltip with the last known world coordinates of the mouse, in case the tooltip should be displayed.
202      */
203     public final synchronized void displayWorldCoordinateToolTip()
204     {
205         if (this.showToolTip)
206         {
207             String worldPoint = "(x=" + this.formatter.format(this.worldCoordinate.getX()) + " ; y="
208                     + this.formatter.format(this.worldCoordinate.getY()) + ")";
209             setToolTipText(worldPoint);
210         }
211     }
212 
213     /**
214      * @return showToolTip
215      */
216     public final synchronized boolean isShowToolTip()
217     {
218         return this.showToolTip;
219     }
220 
221     /**
222      * @param showToolTip boolean; set showToolTip
223      */
224     public final synchronized void setShowToolTip(final boolean showToolTip)
225     {
226         this.showToolTip = showToolTip;
227     }
228 
229     /**
230      * pans the panel in a specified direction.
231      * @param direction int; the direction
232      * @param percentage double; the percentage
233      */
234     public final synchronized void pan(final int direction, final double percentage)
235     {
236         if (percentage <= 0 || percentage > 1.0)
237         {
238             throw new IllegalArgumentException("percentage<=0 || >1.0");
239         }
240         switch (direction)
241         {
242             case LEFT:
243                 this.extent.setRect(this.extent.getMinX() - percentage * this.extent.getWidth(), this.extent.getMinY(),
244                         this.extent.getWidth(), this.extent.getHeight());
245                 break;
246             case RIGHT:
247                 this.extent.setRect(this.extent.getMinX() + percentage * this.extent.getWidth(), this.extent.getMinY(),
248                         this.extent.getWidth(), this.extent.getHeight());
249                 break;
250             case UP:
251                 this.extent.setRect(this.extent.getMinX(), this.extent.getMinY() + percentage * this.extent.getHeight(),
252                         this.extent.getWidth(), this.extent.getHeight());
253                 break;
254             case DOWN:
255                 this.extent.setRect(this.extent.getMinX(), this.extent.getMinY() - percentage * this.extent.getHeight(),
256                         this.extent.getWidth(), this.extent.getHeight());
257                 break;
258             default:
259                 throw new IllegalArgumentException("direction unkown");
260         }
261         this.repaint();
262     }
263 
264     /**
265      * resets the panel to its original extent.
266      */
267     public final synchronized void home()
268     {
269         this.extent = Renderable2DInterface.Util.computeVisibleExtent(this.homeExtent, this.getSize());
270         this.repaint();
271     }
272 
273     /**
274      * @return Returns the showGrid.
275      */
276     public final boolean isShowGrid()
277     {
278         return this.showGrid;
279     }
280 
281     /**
282      * @param showGrid boolean; The showGrid to set.
283      */
284     public final void setShowGrid(final boolean showGrid)
285     {
286         this.showGrid = showGrid;
287     }
288 
289     /**
290      * zooms in/out.
291      * @param factor double; The zoom factor
292      */
293     public final synchronized void zoom(final double factor)
294     {
295         zoom(factor, (int) (this.getWidth() / 2.0), (int) (this.getHeight() / 2.0));
296     }
297 
298     /**
299      * zooms in/out.
300      * @param factor double; The zoom factor
301      * @param mouseX int; x-position of the mouse around which we zoom
302      * @param mouseY int; y-position of the mouse around which we zoom
303      */
304     public final synchronized void zoom(final double factor, final int mouseX, final int mouseY)
305     {
306         Point2D mwc =
307                 Renderable2DInterface.Util.getWorldCoordinates(new Point2D.Double(mouseX, mouseY), this.extent, this.getSize());
308         double minX = mwc.getX() - (mwc.getX() - this.extent.getMinX()) * factor;
309         double minY = mwc.getY() - (mwc.getY() - this.extent.getMinY()) * factor;
310         double w = this.extent.getWidth() * factor;
311         double h = this.extent.getHeight() * factor;
312 
313         this.extent.setRect(minX, minY, w, h);
314         this.repaint();
315     }
316 
317     /**
318      * Added to make sure the recursive render-call calls THIS render method instead of a potential super-class defined
319      * 'paintComponent' render method.
320      * @param g Graphics; the graphics object
321      */
322     @SuppressWarnings("checkstyle:designforextension")
323     protected synchronized void drawGrid(final Graphics g)
324     {
325         // we prepare the graphics object for the grid
326         g.setFont(g.getFont().deriveFont(11.0f));
327         g.setColor(GRIDCOLOR);
328         double scale = Renderable2DInterface.Util.getScale(this.extent, this.getSize());
329 
330         int gridSizePixels = (int) Math.round(this.gridSize / scale);
331         if (gridSizePixels < 40)
332         {
333             this.gridSize = 10 * this.gridSize;
334             int maximumNumberOfDigits = (int) Math.max(0, 1 + Math.ceil(Math.log(1 / this.gridSize) / Math.log(10)));
335             this.formatter.setMaximumFractionDigits(maximumNumberOfDigits);
336             this.drawGrid(g);
337             return;
338         }
339         if (gridSizePixels > 10 * 40)
340         {
341             int maximumNumberOfDigits = (int) Math.max(0, 2 + Math.ceil(Math.log(1 / this.gridSize) / Math.log(10)));
342             this.formatter.setMaximumFractionDigits(maximumNumberOfDigits);
343             this.gridSize = this.gridSize / 10;
344             this.drawGrid(g);
345             return;
346         }
347         // Let's draw the vertical lines
348         double mod = this.extent.getMinX() % this.gridSize;
349         int x = (int) -Math.round(mod / scale);
350         while (x < this.getWidth())
351         {
352             Point2D point =
353                     Renderable2DInterface.Util.getWorldCoordinates(new Point2D.Double(x, 0), this.extent, this.getSize());
354             if (point != null)
355             {
356                 String label = this.formatter.format(Math.round(point.getX() / this.gridSize) * this.gridSize);
357                 double labelWidth = this.getFontMetrics(this.getFont()).getStringBounds(label, g).getWidth();
358                 if (x > labelWidth + 4)
359                 {
360                     g.drawLine(x, 15, x, this.getHeight());
361                     g.drawString(label, (int) Math.round(x - 0.5 * labelWidth), 11);
362                 }
363             }
364             x = x + gridSizePixels;
365         }
366         // Let's draw the horizontal lines
367         mod = Math.abs(this.extent.getMinY()) % this.gridSize;
368         int y = (int) Math.round(this.getSize().getHeight() - (mod / scale));
369         while (y > 15)
370         {
371             Point2D point =
372                     Renderable2DInterface.Util.getWorldCoordinates(new Point2D.Double(0, y), this.extent, this.getSize());
373             if (point != null)
374             {
375                 String label = this.formatter.format(Math.round(point.getY() / this.gridSize) * this.gridSize);
376                 RectangularShape labelBounds = this.getFontMetrics(this.getFont()).getStringBounds(label, g);
377                 g.drawLine((int) Math.round(labelBounds.getWidth() + 4), y, this.getWidth(), y);
378                 g.drawString(label, 2, (int) Math.round(y + labelBounds.getHeight() * 0.3));
379             }
380             y = y - gridSizePixels;
381         }
382     }
383 
384     /**
385      * Repaint the shadow canvas.
386      */
387     public void repaint()
388     {
389         // repaint does not do any painting -- information is pulled from the browser
390         this.dirty = true;
391     }
392 
393     /**
394      * @return size
395      */
396     public final Dimension getSize()
397     {
398         return this.size;
399     }
400 
401     /**
402      * @param size Dimension; set size
403      */
404     public final void setSize(Dimension size)
405     {
406         this.size = size;
407     }
408 
409     /**
410      * @return background
411      */
412     public final Color getBackground()
413     {
414         return this.background;
415     }
416 
417     /**
418      * @param background Color; set background
419      */
420     public final void setBackground(Color background)
421     {
422         this.background = background;
423     }
424 
425     /**
426      * @return width
427      */
428     public final int getWidth()
429     {
430         return this.size.width;
431     }
432 
433     /**
434      * @return height
435      */
436     public final int getHeight()
437     {
438         return this.size.height;
439     }
440 
441     /**
442      * @return preferredSize
443      */
444     public final Dimension getPreferredSize()
445     {
446         return this.preferredSize;
447     }
448 
449     /**
450      * @param preferredSize Dimension; set preferredSize
451      */
452     public final void setPreferredSize(Dimension preferredSize)
453     {
454         this.preferredSize = preferredSize;
455     }
456 
457     /**
458      * @return toolTipText
459      */
460     public final String getToolTipText()
461     {
462         return this.toolTipText;
463     }
464 
465     /**
466      * @param toolTipText String; set toolTipText
467      */
468     public final void setToolTipText(String toolTipText)
469     {
470         this.toolTipText = toolTipText;
471     }
472 
473     /**
474      * @return showing
475      */
476     public final boolean isShowing()
477     {
478         return this.showing;
479     }
480 
481     /**
482      * @param showing boolean; set showing
483      */
484     public final void setShowing(boolean showing)
485     {
486         this.showing = showing;
487     }
488 
489     /**
490      * @return font
491      */
492     public final Font getFont()
493     {
494         return this.currentFont;
495     }
496 
497     /**
498      * @param font Font; set font
499      */
500     public final void setFont(Font font)
501     {
502         this.currentFont = font;
503     }
504 
505     /**
506      * @param font Font; the font to calculate the fontmetrics for
507      * @return fontMetrics
508      */
509     public final FontMetrics getFontMetrics(Font font)
510     {
511         return this.canvas.getFontMetrics(font);
512     }
513 
514     /**
515      * @return dirty
516      */
517     public final boolean isDirty()
518     {
519         return this.dirty;
520     }
521 
522     /** {@inheritDoc} */
523     @Override
524     public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height)
525     {
526         return false;
527     }
528 
529 }