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         super();
128         this.htmlGraphics2D = new HTMLGraphics2D();
129         this.extent = extent;
130         this.homeExtent = (Rectangle2D) extent.clone();
131         this.setBackground(Color.WHITE);
132         this.setPreferredSize(size);
133         this.size = (Dimension) size.clone();
134         this.lastDimension = this.getSize();
135     }
136 
137     /**
138      * Return the set of drawing commands.
139      * @return the set of drawing commands
140      */
141     public String getDrawingCommands()
142     {
143         this.htmlGraphics2D.clearCommand();
144         this.paintComponent(this.htmlGraphics2D);
145         return this.htmlGraphics2D.closeAndGetCommands();
146     }
147 
148     /**
149      * Draw the grid.
150      * @param g HTMLGraphics2D; the virtual Graphics2D canvas to enable writing to the browser
151      */
152     public void paintComponent(final HTMLGraphics2D g)
153     {
154         if (!this.getSize().equals(this.lastDimension))
155         {
156             this.lastDimension = this.getSize();
157             this.extent = Renderable2DInterface.Util.computeVisibleExtent(this.extent, this.getSize());
158         }
159         if (this.showGrid)
160         {
161             this.drawGrid(g);
162         }
163     }
164 
165     /**
166      * show the grid?
167      * @param bool boolean; true/false
168      */
169     public final synchronized void showGrid(final boolean bool)
170     {
171         this.showGrid = bool;
172         this.repaint();
173     }
174 
175     /**
176      * returns the extent of this panel.
177      * @return Rectangle2D
178      */
179     public final Rectangle2D getExtent()
180     {
181         return this.extent;
182     }
183 
184     /**
185      * Set the world coordinates based on a mouse move.
186      * @param point Point2D; the x,y world coordinates
187      */
188     public final synchronized void setWorldCoordinate(final Point2D point)
189     {
190         this.worldCoordinate = point;
191     }
192 
193     /**
194      * @return worldCoordinate
195      */
196     public final synchronized Point2D getWorldCoordinate()
197     {
198         return this.worldCoordinate;
199     }
200 
201     /**
202      * Display a tooltip with the last known world coordinates of the mouse, in case the tooltip should be displayed.
203      */
204     public final synchronized void displayWorldCoordinateToolTip()
205     {
206         if (this.showToolTip)
207         {
208             String worldPoint = "(x=" + this.formatter.format(this.worldCoordinate.getX()) + " ; y="
209                     + this.formatter.format(this.worldCoordinate.getY()) + ")";
210             setToolTipText(worldPoint);
211         }
212     }
213 
214     /**
215      * @return showToolTip
216      */
217     public final synchronized boolean isShowToolTip()
218     {
219         return this.showToolTip;
220     }
221 
222     /**
223      * @param showToolTip boolean; set showToolTip
224      */
225     public final synchronized void setShowToolTip(final boolean showToolTip)
226     {
227         this.showToolTip = showToolTip;
228     }
229 
230     /**
231      * pans the panel in a specified direction.
232      * @param direction int; the direction
233      * @param percentage double; the percentage
234      */
235     public final synchronized void pan(final int direction, final double percentage)
236     {
237         if (percentage <= 0 || percentage > 1.0)
238         {
239             throw new IllegalArgumentException("percentage<=0 || >1.0");
240         }
241         switch (direction)
242         {
243             case LEFT:
244                 this.extent.setRect(this.extent.getMinX() - percentage * this.extent.getWidth(), this.extent.getMinY(),
245                         this.extent.getWidth(), this.extent.getHeight());
246                 break;
247             case RIGHT:
248                 this.extent.setRect(this.extent.getMinX() + percentage * this.extent.getWidth(), this.extent.getMinY(),
249                         this.extent.getWidth(), this.extent.getHeight());
250                 break;
251             case UP:
252                 this.extent.setRect(this.extent.getMinX(), this.extent.getMinY() + percentage * this.extent.getHeight(),
253                         this.extent.getWidth(), this.extent.getHeight());
254                 break;
255             case DOWN:
256                 this.extent.setRect(this.extent.getMinX(), this.extent.getMinY() - percentage * this.extent.getHeight(),
257                         this.extent.getWidth(), this.extent.getHeight());
258                 break;
259             default:
260                 throw new IllegalArgumentException("direction unkown");
261         }
262         this.repaint();
263     }
264 
265     /**
266      * resets the panel to its original extent.
267      */
268     public final synchronized void home()
269     {
270         this.extent = Renderable2DInterface.Util.computeVisibleExtent(this.homeExtent, this.getSize());
271         this.repaint();
272     }
273 
274     /**
275      * @return Returns the showGrid.
276      */
277     public final boolean isShowGrid()
278     {
279         return this.showGrid;
280     }
281 
282     /**
283      * @param showGrid boolean; The showGrid to set.
284      */
285     public final void setShowGrid(final boolean showGrid)
286     {
287         this.showGrid = showGrid;
288     }
289 
290     /**
291      * zooms in/out.
292      * @param factor double; The zoom factor
293      */
294     public final synchronized void zoom(final double factor)
295     {
296         zoom(factor, (int) (this.getWidth() / 2.0), (int) (this.getHeight() / 2.0));
297     }
298 
299     /**
300      * zooms in/out.
301      * @param factor double; The zoom factor
302      * @param mouseX int; x-position of the mouse around which we zoom
303      * @param mouseY int; y-position of the mouse around which we zoom
304      */
305     public final synchronized void zoom(final double factor, final int mouseX, final int mouseY)
306     {
307         Point2D mwc =
308                 Renderable2DInterface.Util.getWorldCoordinates(new Point2D.Double(mouseX, mouseY), this.extent, this.getSize());
309         double minX = mwc.getX() - (mwc.getX() - this.extent.getMinX()) * factor;
310         double minY = mwc.getY() - (mwc.getY() - this.extent.getMinY()) * factor;
311         double w = this.extent.getWidth() * factor;
312         double h = this.extent.getHeight() * factor;
313 
314         this.extent.setRect(minX, minY, w, h);
315         this.repaint();
316     }
317 
318     /**
319      * Added to make sure the recursive render-call calls THIS render method instead of a potential super-class defined
320      * 'paintComponent' render method.
321      * @param g Graphics; the graphics object
322      */
323     @SuppressWarnings("checkstyle:designforextension")
324     protected synchronized void drawGrid(final Graphics g)
325     {
326         // we prepare the graphics object for the grid
327         g.setFont(g.getFont().deriveFont(11.0f));
328         g.setColor(GRIDCOLOR);
329         double scale = Renderable2DInterface.Util.getScale(this.extent, this.getSize());
330 
331         int gridSizePixels = (int) Math.round(this.gridSize / scale);
332         if (gridSizePixels < 40)
333         {
334             this.gridSize = 10 * this.gridSize;
335             int maximumNumberOfDigits = (int) Math.max(0, 1 + Math.ceil(Math.log(1 / this.gridSize) / Math.log(10)));
336             this.formatter.setMaximumFractionDigits(maximumNumberOfDigits);
337             this.drawGrid(g);
338             return;
339         }
340         if (gridSizePixels > 10 * 40)
341         {
342             int maximumNumberOfDigits = (int) Math.max(0, 2 + Math.ceil(Math.log(1 / this.gridSize) / Math.log(10)));
343             this.formatter.setMaximumFractionDigits(maximumNumberOfDigits);
344             this.gridSize = this.gridSize / 10;
345             this.drawGrid(g);
346             return;
347         }
348         // Let's draw the vertical lines
349         double mod = this.extent.getMinX() % this.gridSize;
350         int x = (int) -Math.round(mod / scale);
351         while (x < this.getWidth())
352         {
353             Point2D point =
354                     Renderable2DInterface.Util.getWorldCoordinates(new Point2D.Double(x, 0), this.extent, this.getSize());
355             if (point != null)
356             {
357                 String label = this.formatter.format(Math.round(point.getX() / this.gridSize) * this.gridSize);
358                 double labelWidth = this.getFontMetrics(this.getFont()).getStringBounds(label, g).getWidth();
359                 if (x > labelWidth + 4)
360                 {
361                     g.drawLine(x, 15, x, this.getHeight());
362                     g.drawString(label, (int) Math.round(x - 0.5 * labelWidth), 11);
363                 }
364             }
365             x = x + gridSizePixels;
366         }
367         // Let's draw the horizontal lines
368         mod = Math.abs(this.extent.getMinY()) % this.gridSize;
369         int y = (int) Math.round(this.getSize().getHeight() - (mod / scale));
370         while (y > 15)
371         {
372             Point2D point =
373                     Renderable2DInterface.Util.getWorldCoordinates(new Point2D.Double(0, y), this.extent, this.getSize());
374             if (point != null)
375             {
376                 String label = this.formatter.format(Math.round(point.getY() / this.gridSize) * this.gridSize);
377                 RectangularShape labelBounds = this.getFontMetrics(this.getFont()).getStringBounds(label, g);
378                 g.drawLine((int) Math.round(labelBounds.getWidth() + 4), y, this.getWidth(), y);
379                 g.drawString(label, 2, (int) Math.round(y + labelBounds.getHeight() * 0.3));
380             }
381             y = y - gridSizePixels;
382         }
383     }
384 
385     /**
386      * Repaint the shadow canvas.
387      */
388     public void repaint()
389     {
390         // repaint does not do any painting -- information is pulled from the browser
391         this.dirty = true;
392     }
393 
394     /**
395      * @return size
396      */
397     public final Dimension getSize()
398     {
399         return this.size;
400     }
401 
402     /**
403      * @param size Dimension; set size
404      */
405     public final void setSize(Dimension size)
406     {
407         this.size = size;
408     }
409 
410     /**
411      * @return background
412      */
413     public final Color getBackground()
414     {
415         return this.background;
416     }
417 
418     /**
419      * @param background Color; set background
420      */
421     public final void setBackground(Color background)
422     {
423         this.background = background;
424     }
425 
426     /**
427      * @return width
428      */
429     public final int getWidth()
430     {
431         return this.size.width;
432     }
433 
434     /**
435      * @return height
436      */
437     public final int getHeight()
438     {
439         return this.size.height;
440     }
441 
442     /**
443      * @return preferredSize
444      */
445     public final Dimension getPreferredSize()
446     {
447         return this.preferredSize;
448     }
449 
450     /**
451      * @param preferredSize Dimension; set preferredSize
452      */
453     public final void setPreferredSize(Dimension preferredSize)
454     {
455         this.preferredSize = preferredSize;
456     }
457 
458     /**
459      * @return toolTipText
460      */
461     public final String getToolTipText()
462     {
463         return this.toolTipText;
464     }
465 
466     /**
467      * @param toolTipText String; set toolTipText
468      */
469     public final void setToolTipText(String toolTipText)
470     {
471         this.toolTipText = toolTipText;
472     }
473 
474     /**
475      * @return showing
476      */
477     public final boolean isShowing()
478     {
479         return this.showing;
480     }
481 
482     /**
483      * @param showing boolean; set showing
484      */
485     public final void setShowing(boolean showing)
486     {
487         this.showing = showing;
488     }
489 
490     /**
491      * @return font
492      */
493     public final Font getFont()
494     {
495         return this.currentFont;
496     }
497 
498     /**
499      * @param font Font; set font
500      */
501     public final void setFont(Font font)
502     {
503         this.currentFont = font;
504     }
505 
506     /**
507      * @param font Font; the font to calculate the fontmetrics for
508      * @return fontMetrics
509      */
510     public final FontMetrics getFontMetrics(Font font)
511     {
512         return this.canvas.getFontMetrics(font);
513     }
514 
515     /**
516      * @return dirty
517      */
518     public final boolean isDirty()
519     {
520         return this.dirty;
521     }
522 
523     /** {@inheritDoc} */
524     @Override
525     public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height)
526     {
527         return false;
528     }
529 
530 }