Bug 9031 fixed: Misalignment when using xstring with a matrix
[scilab.git] / scilab / modules / renderer / src / java / org / scilab / modules / renderer / JoGLView / legend / LegendDrawer.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2011-2012 - DIGITEO - Manuel JULIACHS
4  *
5  * This file must be used under the terms of the CeCILL.
6  * This source file is licensed as described in the file COPYING, which
7  * you should have received as part of this distribution.  The terms
8  * are also available at
9  * http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.txt
10  */
11
12 package org.scilab.modules.renderer.JoGLView.legend;
13
14 import org.scilab.forge.scirenderer.Canvas;
15 import org.scilab.forge.scirenderer.DrawingTools;
16 import org.scilab.forge.scirenderer.SciRendererException;
17 import org.scilab.forge.scirenderer.buffers.ElementsBuffer;
18 import org.scilab.forge.scirenderer.buffers.IndicesBuffer;
19 import org.scilab.forge.scirenderer.shapes.appearance.Appearance;
20 import org.scilab.forge.scirenderer.shapes.geometry.DefaultGeometry;
21 import org.scilab.forge.scirenderer.shapes.geometry.Geometry;
22 import org.scilab.forge.scirenderer.texture.AnchorPosition;
23 import org.scilab.forge.scirenderer.texture.Texture;
24 import org.scilab.forge.scirenderer.texture.TextureManager;
25 import org.scilab.forge.scirenderer.tranformations.Transformation;
26 import org.scilab.forge.scirenderer.tranformations.TransformationFactory;
27 import org.scilab.forge.scirenderer.tranformations.TransformationStack;
28 import org.scilab.forge.scirenderer.tranformations.Vector3d;
29 import org.scilab.modules.graphic_objects.axes.Axes;
30 import org.scilab.modules.graphic_objects.figure.ColorMap;
31 import org.scilab.modules.graphic_objects.graphicController.GraphicController;
32 import org.scilab.modules.graphic_objects.graphicObject.GraphicObjectProperties;
33 import org.scilab.modules.graphic_objects.legend.Legend;
34 import org.scilab.modules.graphic_objects.legend.Legend.LegendLocation;
35 import org.scilab.modules.graphic_objects.polyline.Polyline;
36 import org.scilab.modules.renderer.JoGLView.DrawerVisitor;
37 import org.scilab.modules.renderer.JoGLView.mark.MarkSpriteManager;
38 import org.scilab.modules.renderer.JoGLView.util.ColorFactory;
39
40 import java.awt.Dimension;
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.HashSet;
44 import java.util.Map;
45 import java.util.Set;
46 import java.util.concurrent.ConcurrentHashMap;
47
48
49 /**
50  * LegendDrawer class.
51  * Utility class used by DrawerVisitor to draw the Legend object.
52  *
53  * To do:
54  * - clean up the code
55  * - modify bounds depending on the location (for OUT_* values)
56  * - take into account the actual tick/label size (instead of an arbitrary value)
57  * - implement box clipping mode
58  * - use the GraphicController to update links / move the update code somewhere else
59  *   in order to perform it when objects are deleted, add the Links property to the set of
60  *   properties affecting the sprite's update.
61  *
62  * @author Manuel JULIACHS
63  */
64 public class LegendDrawer {
65
66     /**
67      * Set of properties that affect the legend sprite
68      */
69     private static final Set<Integer> SPRITE_PROPERTIES = new HashSet<Integer>(Arrays.asList(
70                 GraphicObjectProperties.__GO_FONT_SIZE__,
71                 GraphicObjectProperties.__GO_FONT_COLOR__,
72                 GraphicObjectProperties.__GO_FONT_STYLE__,
73                 GraphicObjectProperties.__GO_FONT_FRACTIONAL__,
74                 GraphicObjectProperties.__GO_TEXT_ARRAY_DIMENSIONS__,
75                 GraphicObjectProperties.__GO_TEXT_STRINGS__
76             ));
77
78     /** The height of a bar relative to the height difference between two lines */
79     private static final float BAR_HEIGHT = 0.5f;
80
81     /** The DrawerVisitor used */
82     private final DrawerVisitor visitor;
83
84     /** The SpriteManager used */
85     private final TextureManager textureManager;
86
87     /** The MarkSpriteManager used */
88     private final MarkSpriteManager markManager;
89
90     /** The Legend sprite drawer */
91     private LegendSpriteDrawer legendSpriteDrawer;
92
93     /** The relative line width */
94     private static final double LINE_WIDTH = 0.1;
95
96     /** The relative y-offset */
97     private static final double Y_OFFSET = 0.01;
98
99     /** The relative tick and label size (arbitrarily chosen) */
100     private static final double TICK_LABEL_SIZE = 0.055;
101
102     /** The z-value corresponding to the frontmost position */
103     private static final float Z_FRONT = 0.99f;
104
105     /** The legend background's vertices */
106     private ElementsBuffer rectangleVertices;
107
108     /** The vertices used to draw lines */
109     private ElementsBuffer lineVertices;
110
111     /** The vertices used to draw bars */
112     private ElementsBuffer barVertices;
113
114     /** The indices used to draw lines */
115     private IndicesBuffer lineIndices;
116
117     /** The indices used to draw a rectangle */
118     private IndicesBuffer rectangleIndices;
119
120     /** The indices used to draw the outline of a rectangle */
121     private IndicesBuffer rectangleOutlineIndices;
122
123     /** The map storing legend text sprites */
124     private Map<Integer, Texture> textureMap;
125
126     /**
127      * Constructor.
128      * @param visitor the DrawerVisitor {@see DrawerVisitor}.
129      */
130     public LegendDrawer(DrawerVisitor visitor) {
131         this.visitor = visitor;
132         this.textureManager = visitor.getCanvas().getTextureManager();
133         this.markManager = visitor.getMarkManager();
134
135         rectangleVertices = visitor.getCanvas().getBuffersManager().createElementsBuffer();
136         lineVertices = visitor.getCanvas().getBuffersManager().createElementsBuffer();
137         barVertices = visitor.getCanvas().getBuffersManager().createElementsBuffer();
138         lineIndices = visitor.getCanvas().getBuffersManager().createIndicesBuffer();
139         rectangleIndices = visitor.getCanvas().getBuffersManager().createIndicesBuffer();
140         rectangleOutlineIndices = visitor.getCanvas().getBuffersManager().createIndicesBuffer();
141
142         textureMap = new ConcurrentHashMap<Integer, Texture>();
143
144         int[] lineIndexData = new int[] {0, 1, 1, 2};
145         lineIndices.setData(lineIndexData);
146     }
147
148     /**
149      * Draws the given Legend.
150      * @param legend the Legend to draw.
151      * @throws SciRendererException if the draw fail.
152      */
153     public void draw(Legend legend) throws SciRendererException {
154         /* The coordinates of the legend box's lower-left corner */
155         double [] legendCorner = new double[] {0.25, 0.75, Z_FRONT};
156
157         DrawingTools drawingTools = visitor.getDrawingTools();
158         ColorMap colorMap = visitor.getColorMap();
159         Canvas canvas = visitor.getCanvas();
160
161         Integer [] links = legend.getLinks();
162
163         /*
164          * Determine whether any links have become invalid,
165          * force the sprite's update and update the Legend's
166          * links property as needed.
167          */
168         int nbValidLinks = getNumberValidLinks(legend);
169
170         if (nbValidLinks < links.length) {
171             dispose(legend.getIdentifier());
172             updateLinks(legend, nbValidLinks);
173         }
174
175         links = legend.getLinks();
176
177         /*
178          * Set the projection and modelview transformations so that coordinates
179          * are specified in the {0,+1,0,+1,0,+1} space.
180          */
181         TransformationStack modelViewStack = drawingTools.getTransformationManager().getModelViewStack();
182         TransformationStack projectionStack = drawingTools.getTransformationManager().getProjectionStack();
183
184         Transformation identity = TransformationFactory.getIdentity();
185         modelViewStack.push(identity);
186
187         Transformation orthoProj = TransformationFactory.getOrthographic(0.0, 1.0, 0.0, 1.0, -1.0, 0.0);
188         projectionStack.push(orthoProj);
189
190
191         /* First, compute the legend box's position and dimensions from the Axes' parameters and the text sprite's dimensions */
192
193         Integer parentAxesID = legend.getParentAxes();
194         Axes parentAxes = (Axes) GraphicController.getController().getObjectFromId(parentAxesID);
195
196         Double [] axesBounds = parentAxes.getAxesBounds();
197         Double [] margins = parentAxes.getMargins();
198
199         int xAxisLocation = parentAxes.getXAxisLocation();
200         int yAxisLocation = parentAxes.getYAxisLocation();
201
202
203         int canvasWidth = canvas.getWidth();
204         int canvasHeight = canvas.getHeight();
205
206         if (canvasWidth == 0) {
207             canvasWidth = 1;
208         }
209
210         if (canvasHeight == 0) {
211             canvasHeight = 1;
212         }
213
214         Texture legendSprite = null;
215
216         if (nbValidLinks > 0) {
217             legendSprite = getTexture(colorMap, legend);
218         }
219
220         double normSpriteWidth = 0;
221         double normSpriteHeight = 0;
222
223         if (nbValidLinks > 0) {
224             Dimension textureSize = legendSprite.getDataProvider().getTextureSize();
225             normSpriteWidth = textureSize.getWidth() / (double) canvasWidth;
226             normSpriteHeight = textureSize.getHeight() / (double) canvasHeight;
227         }
228
229         double lineWidth;
230
231         /* The legend box's width and height */
232         double [] legendDims = new double[2];
233
234         double [] axesPos = new double[2];
235         double [] axesDims = new double[2];
236
237         lineWidth = LINE_WIDTH * (axesBounds[2]) * (1.0 - margins[0] - margins[1]);
238
239         double xOffset = lineWidth / 8.0;
240         double yOffset = (Y_OFFSET * (axesBounds[3]) * (1.0 - margins[2] - margins[3]));
241
242         legendDims[0] = normSpriteWidth + lineWidth + 3.0 * xOffset;
243         legendDims[1] = normSpriteHeight + 2.0 * yOffset;
244
245         /* Save the legend box size */
246         Double[] DimsToSet = { legendDims[0], legendDims[1]};
247         legend.setSize(DimsToSet);
248
249         axesPos[0] = axesBounds[0];
250         axesPos[1] = 1.0 - (axesBounds[1] + axesBounds[3]);
251
252         axesDims[0] = axesBounds[2];
253         axesDims[1] = axesBounds[3];
254
255         /* The {x, y} coordinates of the axes box's lower-left and upper-right corners (as defined by bounds and margins) */
256         double [] llBoxCorner = new double[2];
257         double [] urBoxCorner = new double[2];
258
259         LegendLocation legendLocation = legend.getLegendLocationAsEnum();
260
261         llBoxCorner[0] = axesPos[0] + margins[0] * axesDims[0];
262         llBoxCorner[1] = axesPos[1] + margins[3] * axesDims[1];
263
264         urBoxCorner[0] = axesPos[0] + (1.0 - margins[1]) * axesDims[0];
265         urBoxCorner[1] = axesPos[1] + (1.0 - margins[2]) * axesDims[1];
266
267         if (legendLocation == LegendLocation.IN_UPPER_RIGHT) {
268             legendCorner[0] = (float) (urBoxCorner[0] - xOffset - legendDims[0]);
269             legendCorner[1] = (float) (urBoxCorner[1] - yOffset - legendDims[1]);
270         } else if (legendLocation == LegendLocation.IN_UPPER_LEFT) {
271             legendCorner[0] = (float) (llBoxCorner[0] + xOffset);
272             legendCorner[1] = (float) (urBoxCorner[1] - yOffset - legendDims[1]);
273         } else if (legendLocation == LegendLocation.IN_LOWER_RIGHT) {
274             legendCorner[0] = (float) (urBoxCorner[0] - xOffset - legendDims[0]);
275             legendCorner[1] = (float) (llBoxCorner[1] + yOffset);
276         } else if (legendLocation == LegendLocation.IN_LOWER_LEFT) {
277             legendCorner[0] = (float) (llBoxCorner[0] + xOffset);
278             legendCorner[1] = (float) (llBoxCorner[1] + yOffset);
279         } else if (legendLocation == LegendLocation.OUT_UPPER_RIGHT) {
280             legendCorner[0] = (float) (urBoxCorner[0] + xOffset);
281             legendCorner[1] = (float) (urBoxCorner[1] - legendDims[1]);
282         } else if (legendLocation == LegendLocation.OUT_UPPER_LEFT) {
283             legendCorner[0] = (float) (llBoxCorner[0] - xOffset - legendDims[0]);
284             legendCorner[1] = (float) (urBoxCorner[1] - legendDims[1]);
285         } else if (legendLocation == LegendLocation.OUT_LOWER_RIGHT) {
286             legendCorner[0] = (float) (urBoxCorner[0] + xOffset);
287             legendCorner[1] = (float) (llBoxCorner[1]);
288         } else if (legendLocation == LegendLocation.OUT_LOWER_LEFT) {
289             legendCorner[0] = (float) (llBoxCorner[0] - xOffset - legendDims[0]);
290             legendCorner[1] = (float) (llBoxCorner[1]);
291         } else if (legendLocation == LegendLocation.UPPER_CAPTION) {
292             legendCorner[0] = (float) (llBoxCorner[0]);
293             legendCorner[1] = (float) (urBoxCorner[1] + yOffset);
294
295             /* x-axis at the top */
296             if (xAxisLocation == 1) {
297                 /* To do: use the actual label+tick bounding box height */
298                 legendCorner[1] += TICK_LABEL_SIZE;
299             }
300         } else if (legendLocation == LegendLocation.LOWER_CAPTION) {
301             legendCorner[0] = (float) (llBoxCorner[0]);
302             legendCorner[1] = (float) (llBoxCorner[1] - yOffset - legendDims[1]);
303
304             /* x-axis at the bottom */
305             if (xAxisLocation == 0) {
306                 /* To do: use the actual label+tick bounding box height */
307                 legendCorner[1] -= TICK_LABEL_SIZE;
308             }
309         } else if (legendLocation == LegendLocation.BY_COORDINATES) {
310             Double [] legPos = legend.getPosition();
311
312             legendCorner[0] = (float) (axesPos[0] + legPos[0] * axesBounds[2]);
313             legendCorner[1] = (float) (axesPos[1] + (1.0 - legPos[1]) * axesBounds[3] - legendDims[1]);
314         }
315
316         /* y-axis positioned to the left */
317         if ((legendLocation == LegendLocation.OUT_UPPER_LEFT || legendLocation == LegendLocation.OUT_LOWER_LEFT) && yAxisLocation == 4) {
318             /* To do: use the actual label+tick bounding box width */
319             legendCorner[0] -= TICK_LABEL_SIZE;
320             /* y-axis positioned to the right */
321         } else if ((legendLocation == LegendLocation.OUT_UPPER_RIGHT || legendLocation == LegendLocation.OUT_LOWER_RIGHT) && yAxisLocation == 5) {
322             /* To do: use the actual label+tick bounding box width */
323             legendCorner[0] += TICK_LABEL_SIZE;
324         }
325
326
327         /* Afterwards, draw the elements making up the Legend using the previously computed values */
328
329         /* Legend background vertex data: lower-left, lower-right, upper-left and upper-right corners */
330         float [] rectangleVertexData = new float[] {
331             (float)legendCorner[0], (float)legendCorner[1], Z_FRONT, 1.0f,
332             (float)(legendCorner[0] + legendDims[0]), (float)legendCorner[1], Z_FRONT, 1.0f,
333             (float)legendCorner[0], (float)(legendCorner[1] + legendDims[1]), Z_FRONT, 1.0f,
334             (float)(legendCorner[0] + legendDims[0]), (float)(legendCorner[1] + legendDims[1]), Z_FRONT, 1.0f
335         };
336
337         /* The indices of a rectangle's triangles and a rectangle outline's segment loop */
338         int[] rectangleIndexData = new int[] {0, 1, 3, 0, 3, 2};
339         int[] rectangleOutlineIndexData = new int[] {0, 1, 1, 3, 3, 2, 2, 0};
340
341         rectangleIndices.setData(rectangleIndexData);
342         rectangleOutlineIndices.setData(rectangleOutlineIndexData);
343
344         rectangleVertices.setData(rectangleVertexData, 4);
345
346         /* Legend rectangle background and outline */
347         DefaultGeometry legendRectangle = new DefaultGeometry();
348         legendRectangle.setVertices(rectangleVertices);
349         legendRectangle.setIndices(rectangleIndices);
350
351         Appearance appearance = new Appearance();
352
353         if (legend.getFillMode()) {
354             legendRectangle.setFillDrawingMode(Geometry.FillDrawingMode.TRIANGLES);
355             appearance.setFillColor(ColorFactory.createColor(colorMap, legend.getBackground()));
356         } else {
357             legendRectangle.setFillDrawingMode(Geometry.FillDrawingMode.NONE);
358         }
359
360         /* Legend outline */
361         if (legend.getLineMode()) {
362             legendRectangle.setLineDrawingMode(Geometry.LineDrawingMode.SEGMENTS);
363             legendRectangle.setWireIndices(rectangleOutlineIndices);
364
365             appearance.setLineColor(ColorFactory.createColor(colorMap, legend.getLineColor()));
366             appearance.setLineWidth(legend.getLineThickness().floatValue());
367             appearance.setLinePattern(legend.getLineStyleAsEnum().asPattern());
368         } else {
369             legendRectangle.setLineDrawingMode(Geometry.LineDrawingMode.NONE);
370         }
371
372         drawingTools.draw(legendRectangle, appearance);
373
374         /* Lines: 3 vertices each, left, middle, and right */
375         float [] lineVertexData = new float[] {0.25f, 0.75f, Z_FRONT, 1.0f,
376                                                0.5f, 0.75f, Z_FRONT, 1.0f,
377                                                0.75f, 0.75f, Z_FRONT, 1.0f
378                                               };
379
380         double normSpriteMargin = 0.0;
381
382         if (nbValidLinks > 0) {
383             normSpriteMargin = (double) legendSpriteDrawer.getVMargin() / (double) canvasHeight;
384         }
385
386         lineVertexData[0] = (float) (legendCorner[0] + xOffset);
387         lineVertexData[1] = (float) (legendCorner[1] + normSpriteMargin + yOffset);
388
389         lineVertexData[8] = lineVertexData[0] + (float) lineWidth;
390         lineVertexData[9] = lineVertexData[1];
391
392         lineVertexData[4] = 0.5f * (lineVertexData[0] + lineVertexData[8]);
393         lineVertexData[5] = lineVertexData[1];
394
395         float deltaHeight = 0.0f;
396
397         if (links.length > 0) {
398             deltaHeight = (float) (normSpriteHeight - 2.0 * normSpriteMargin) / ((float)(links.length));
399         }
400
401         lineVertexData[1] = lineVertexData[1] + 0.5f * deltaHeight;
402         lineVertexData[5] = lineVertexData[5] + 0.5f * deltaHeight;
403         lineVertexData[9] = lineVertexData[9] + 0.5f * deltaHeight;
404
405         /* Bar vertex data: lower-left, lower-right, upper-left and upper-right corners */
406         float [] barVertexData = new float[] {0.25f, 0.75f, Z_FRONT, 1.0f,
407                                               0.75f, 0.75f, Z_FRONT, 1.0f,
408                                               0.25f, 1.00f, Z_FRONT, 1.0f,
409                                               0.75f, 1.00f, Z_FRONT, 1.0f
410                                              };
411
412         float barHeight = BAR_HEIGHT * deltaHeight;
413
414         barVertexData[0] = (float) (legendCorner[0] + xOffset);
415         barVertexData[1] = (float) (legendCorner[1] + normSpriteMargin + yOffset) + 0.5f * (deltaHeight - barHeight);
416
417         barVertexData[4] = barVertexData[0] + (float) lineWidth;
418         barVertexData[5] = barVertexData[1];
419
420         barVertexData[8] = barVertexData[0];
421         barVertexData[9] = barVertexData[1] + barHeight;
422
423         barVertexData[12] = barVertexData[4];
424         barVertexData[13] = barVertexData[9];
425
426         for (Integer link : links) {
427             Polyline currentLine = (Polyline) GraphicController.getController().getObjectFromId(link);
428
429             drawLegendItem(drawingTools, colorMap, currentLine, barVertexData, lineVertexData);
430
431             /* Update the vertex data's vertical position */
432             lineVertexData[1] += deltaHeight;
433             lineVertexData[5] += deltaHeight;
434             lineVertexData[9] += deltaHeight;
435
436             barVertexData[1] += deltaHeight;
437             barVertexData[5] += deltaHeight;
438             barVertexData[9] += deltaHeight;
439             barVertexData[13] += deltaHeight;
440         }
441
442         /* Legend text */
443         float [] spritePosition = new float[] {lineVertexData[8] + (float) xOffset, (float) (legendCorner[1] + yOffset), Z_FRONT};
444
445         /* Draw the sprite only if there are valid links */
446         if (nbValidLinks > 0) {
447             drawingTools.draw(legendSprite, AnchorPosition.LOWER_LEFT, new Vector3d(spritePosition));
448         }
449
450         /* Restore the transformation stacks */
451         modelViewStack.pop();
452         projectionStack.pop();
453
454         /* Output the position if required */
455         Double [] legendPosition = new Double[2];
456
457         if (axesDims[0] == 0.0) {
458             axesDims[0] = 1.0;
459         }
460
461         if (axesDims[1] == 0.0) {
462             axesDims[1] = 1.0;
463         }
464
465         legendPosition[0] = (legendCorner[0] - axesPos[0]) / axesDims[0];
466         legendPosition[1] = 1.0 - (legendCorner[1] + legendDims[1] - axesPos[1]) / axesDims[1];
467
468         if (legendLocation != LegendLocation.BY_COORDINATES) {
469             legend.setPosition(legendPosition);
470         }
471     }
472
473     /**
474      * Draw the legend item corresponding to the given polyline.
475      * It draws either a horizontal line or bar depending on the polyline's properties (style, fill and line modes).
476      * @param drawingTools the DrawingTools {@see DrawingTools} used to draw the Legend.
477      * @param colorMap the colorMap used.
478      * @param polyline the given polyline.
479      * @param barVertexData a bar's vertex data (4 consecutive (x,y,z,w) quadruplets: lower-left, lower-right, upper-left and upper-right corners.
480      * @param lineVertexData a line's vertex data (3 consecutive (x,y,z,w) quadruplets: left, middle and right vertices).
481      * @throws org.scilab.forge.scirenderer.SciRendererException if the draw fail.
482      */
483     private void drawLegendItem(DrawingTools drawingTools, ColorMap colorMap, Polyline polyline, float[] barVertexData, float[] lineVertexData) throws SciRendererException {
484         int polylineStyle = polyline.getPolylineStyle();
485
486         int lineColor = polyline.getLineColor();
487         double lineThickness = polyline.getLineThickness();
488         short linePattern = polyline.getLineStyleAsEnum().asPattern();
489
490         boolean isBar = (polylineStyle == 6) || (polylineStyle == 7);
491         boolean barDrawn = isBar || polyline.getFillMode();
492
493         /* Draw a bar if the curve is a bar or if it is filled */
494         if (barDrawn) {
495             barVertices.setData(barVertexData, 4);
496
497             DefaultGeometry bar = new DefaultGeometry();
498             bar.setFillDrawingMode(Geometry.FillDrawingMode.TRIANGLES);
499
500             Appearance barAppearance = new Appearance();
501             barAppearance.setFillColor(ColorFactory.createColor(colorMap, polyline.getBackground()));
502             bar.setVertices(barVertices);
503             bar.setIndices(rectangleIndices);
504
505             /* Bar outline */
506             if (isBar || polyline.getLineMode()) {
507                 bar.setLineDrawingMode(Geometry.LineDrawingMode.SEGMENTS);
508                 bar.setWireIndices(rectangleOutlineIndices);
509
510                 barAppearance.setLineColor(ColorFactory.createColor(colorMap, polyline.getLineColor()));
511                 barAppearance.setLineWidth((float) lineThickness);
512                 barAppearance.setLinePattern(linePattern);
513             } else {
514                 bar.setLineDrawingMode(Geometry.LineDrawingMode.NONE);
515             }
516
517             drawingTools.draw(bar, barAppearance);
518         }
519
520         /* Draw a line otherwise */
521         if (!barDrawn) {
522             lineVertices.setData(lineVertexData, 4);
523
524             /* A line must also be drawn for the vertical polyline style (3), whatever line mode's value */
525             if (polyline.getLineMode() || polylineStyle == 3) {
526                 DefaultGeometry line = new DefaultGeometry();
527                 line.setFillDrawingMode(Geometry.FillDrawingMode.NONE);
528                 line.setLineDrawingMode(Geometry.LineDrawingMode.SEGMENTS_STRIP);
529                 line.setVertices(lineVertices);
530                 Appearance lineAppearance = new Appearance();
531                 lineAppearance.setLineColor(ColorFactory.createColor(colorMap, lineColor));
532                 lineAppearance.setLineWidth((float) lineThickness);
533                 lineAppearance.setLinePattern(linePattern);
534
535                 drawingTools.draw(line, lineAppearance);
536             }
537         }
538
539         /* Draw arrows */
540         if (polylineStyle == 4) {
541             if (barDrawn) {
542                 /*
543                  * Overlap can occur between the arrow heads of the bar's two smallest segments and the adjacent items.
544                  * To do: adjust arrow size to correct this.
545                  */
546                 visitor.getArrowDrawer().drawArrows(polyline.getParentAxes(), barVertices, rectangleOutlineIndices,
547                                                     polyline.getArrowSizeFactor(), lineThickness, lineColor, false);
548             } else {
549                 visitor.getArrowDrawer().drawArrows(polyline.getParentAxes(), lineVertices, lineIndices,
550                                                     polyline.getArrowSizeFactor(), lineThickness, lineColor, false);
551             }
552         }
553
554         if (polyline.getMarkMode()) {
555             Appearance lineAppearance = new Appearance();
556             lineAppearance.setLineWidth((float) lineThickness);
557             Texture markTexture = markManager.getMarkSprite(polyline, colorMap, lineAppearance);
558             if (barDrawn) {
559                 drawingTools.draw(markTexture, AnchorPosition.CENTER, barVertices);
560             } else {
561                 drawingTools.draw(markTexture, AnchorPosition.CENTER, lineVertices);
562             }
563         }
564
565     }
566
567     /**
568      * Updates the legend by disposing its sprite.
569      * @param id the legend id.
570      * @param property the property to update.
571      */
572     public void update(Integer id, int property) {
573         if (textureMap.containsKey(id)) {
574             if (SPRITE_PROPERTIES.contains(property)) {
575                 dispose(id);
576             }
577         }
578     }
579
580     /**
581      * Disposes the Legend sprite corresponding to the given id.
582      * @param id the legend id.
583      */
584     public void dispose(Integer id) {
585         Texture texture = textureMap.get(id);
586         if (texture != null) {
587             textureManager.dispose(texture);
588             textureMap.remove(id);
589         }
590     }
591
592     /**
593      * Disposes all the Legend resources.
594      */
595     public void disposeAll() {
596         visitor.getCanvas().getBuffersManager().dispose(rectangleVertices);
597         visitor.getCanvas().getBuffersManager().dispose(lineVertices);
598         visitor.getCanvas().getBuffersManager().dispose(barVertices);
599         visitor.getCanvas().getBuffersManager().dispose(lineIndices);
600         visitor.getCanvas().getBuffersManager().dispose(rectangleIndices);
601         visitor.getCanvas().getBuffersManager().dispose(rectangleOutlineIndices);
602
603         textureManager.dispose(textureMap.values());
604         textureMap.clear();
605     }
606
607     /**
608      * Returns the legend text texture.
609      * @param colorMap the color map.
610      * @param legend the Legend.
611      * @return the text sprite.
612      */
613     private Texture getTexture(ColorMap colorMap, Legend legend) {
614         Texture texture = textureMap.get(legend.getIdentifier());
615         if (texture == null) {
616             this.legendSpriteDrawer = new LegendSpriteDrawer(colorMap, legend);
617             texture = textureManager.createTexture();
618             texture.setDrawer(legendSpriteDrawer);
619             textureMap.put(legend.getIdentifier(), texture);
620         }
621         return texture;
622     }
623
624     /**
625      * Determines and returns the number of valid links for the given Legend object.
626      * @param legend the given Legend.
627      * @return the number of valid links.
628      */
629     private int getNumberValidLinks(Legend legend) {
630         int nbValidLinks = 0;
631         Integer [] links = legend.getLinks();
632
633         for (Integer link : links) {
634             Polyline currentLine = (Polyline) GraphicController.getController().getObjectFromId(link);
635
636             if (currentLine != null) {
637                 nbValidLinks++;
638             }
639         }
640
641         return nbValidLinks;
642     }
643
644     /**
645      * Updates the links and text properties of the Legend depending
646      * on the number of valid links provided (the number of links
647      * to non-null objects).
648      * To do: use the graphic controller to perform the update;
649      * move the link update from LegendDrawer to somewhere more appropriate.
650      * @param legend the Legend to update.
651      * @param nbValidLinks the number of valid links.
652      */
653     private void updateLinks(Legend legend, int nbValidLinks) {
654         int i1 = 0;
655         ArrayList <Integer> newLinks = new ArrayList<Integer>(0);
656         String[] newStrings;
657         Integer[] newDims = new Integer[2];
658
659         /*
660          * In case there are no valid links, we create a single empty String
661          * in order to retain the Legend's font properties.
662          */
663         if (nbValidLinks == 0) {
664             newDims[0] = 1;
665         } else {
666             newDims[0] = nbValidLinks;
667         }
668
669         newDims[1] = 1;
670
671         newStrings = new String[newDims[0]];
672
673         if (nbValidLinks == 0) {
674             newStrings[0] = new String("");
675         }
676
677         Integer[] links = legend.getLinks();
678         String[] strings = legend.getTextStrings();
679
680         for (int i = 0; i < links.length; i++) {
681             Polyline currentLine = (Polyline) GraphicController.getController().getObjectFromId(links[i]);
682
683             /* Text strings are stored in reverse order relative to links. */
684             if (currentLine != null) {
685                 newLinks.add(links[i]);
686
687                 newStrings[nbValidLinks - i1 - 1] = new String(strings[strings.length - i - 1]);
688                 i1++;
689             }
690         }
691
692         /* Update the legend's links and text */
693         legend.setLinks(newLinks);
694
695         legend.setTextArrayDimensions(newDims);
696         legend.setTextStrings(newStrings);
697     }
698 }