Add new positions for datatips (left, right, upper, lower).
[scilab.git] / scilab / modules / renderer / src / java / org / scilab / modules / renderer / JoGLView / datatip / DatatipTextDrawer.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2012 - Pedro Arthur dos S. Souza
4  * Copyright (C) 2012 - Caio Lucas dos S. Souza
5  *
6  * This file must be used under the terms of the CeCILL.
7  * This source file is licensed as described in the file COPYING, which
8  * you should have received as part of this distribution.  The terms
9  * are also available at
10  * http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.txt
11  *
12  */
13
14 package org.scilab.modules.renderer.JoGLView.datatip;
15
16 import org.scilab.forge.scirenderer.DrawingTools;
17 import org.scilab.forge.scirenderer.SciRendererException;
18 import org.scilab.forge.scirenderer.texture.AnchorPosition;
19 import org.scilab.forge.scirenderer.texture.Texture;
20 import org.scilab.forge.scirenderer.texture.TextureManager;
21 import org.scilab.forge.scirenderer.tranformations.DegenerateMatrixException;
22 import org.scilab.forge.scirenderer.tranformations.Transformation;
23 import org.scilab.forge.scirenderer.tranformations.TransformationFactory;
24 import org.scilab.forge.scirenderer.tranformations.Vector3d;
25 import org.scilab.modules.graphic_objects.PolylineData;
26 import org.scilab.modules.graphic_objects.axes.Axes;
27 import org.scilab.modules.graphic_objects.figure.ColorMap;
28 import org.scilab.modules.graphic_objects.graphicController.GraphicController;
29 import org.scilab.modules.graphic_objects.datatip.Datatip;
30 import org.scilab.modules.renderer.CallRenderer;
31 import org.scilab.modules.renderer.JoGLView.DrawerVisitor;
32 import org.scilab.modules.renderer.JoGLView.util.ScaleUtils;
33 import java.awt.Dimension;
34 import org.scilab.modules.renderer.JoGLView.text.TextManager;
35
36 /**
37  * Datatip text drawer
38  *
39  * Draw the datatip text according to its orientation and mark properties
40  */
41
42 public class DatatipTextDrawer extends TextManager {
43
44     public DatatipTextDrawer(TextureManager textureManager) {
45         super(textureManager);
46     }
47
48     /**
49      * Draw the given Scilab {@see Datatip} with the given {@see DrawingTools}.
50      * @param drawingTools the given {@see DrawingTools}.
51      * @param colorMap the current {@see ColorMap}
52      * @param text the given Scilab {@see Datatip}
53      * @throws SciRendererException if the draw fails.
54      */
55     public final void draw(final DrawingTools drawingTools, final ColorMap colorMap, final Datatip datatip) throws SciRendererException {
56         Texture texture = getTexture(colorMap, datatip);
57
58         /* The unscaled texture's dimensions */
59         Dimension spriteDims = getSpriteDims(colorMap, datatip);
60
61         Transformation projection = drawingTools.getTransformationManager().getCanvasProjection();
62
63         Integer parentAxesId = datatip.getParentAxes();
64         Axes parentAxes = (Axes) GraphicController.getController().getObjectFromId(parentAxesId);
65
66         /* Compute the text box vectors and the text box to texture dimension ratios */
67         Vector3d[] textBoxVectors = computeTextBoxVectors(projection, datatip, texture.getDataProvider().getTextureSize(), parentAxes);
68
69         double[] ratios = computeRatios(projection, datatip, textBoxVectors, texture.getDataProvider().getTextureSize(), spriteDims);
70
71         /* If text box mode is equal to filled, the texture must be updated */
72         if (datatip.getTextBoxMode() == 2 && ratios[0] != 1.0) {
73             texture = updateSprite(colorMap, datatip, ratios[0], ratios[1]);
74         }
75
76         /* Compute the text texture's actual position, which depends on the object's text box mode property */
77         Vector3d[] cornerPositions = computeTextPosition(projection, datatip, textBoxVectors, texture.getDataProvider().getTextureSize());
78
79         /* Draw in window coordinates */
80         drawingTools.getTransformationManager().useWindowCoordinate();
81
82         Integer size = datatip.getMarkSize();
83         Integer unit = datatip.getMarkSizeUnit();
84
85         /* calculate the size of the mark to dont draw the text over the mark*/
86         double finalSize = (unit == 1) ? (8.0 + 2.0 * size) : size;
87         finalSize /= 2.0;
88         double r = datatip.getMarkStyle() == 11 ? 1.0 : 2.0;
89         finalSize -= (finalSize >= 2.0) ? r : 0.0;
90
91         Vector3d delta = new Vector3d(finalSize, finalSize, 0);
92         /* set up the text position according to the datatip orientation*/
93         if (datatip.isAutoOrientationEnabled()) {
94             int autopos = getAutoOrientation(datatip);
95             if (autopos != -1) {
96                 Vector3d cp = cornerPositions[0], d = delta, p;
97                 if (autopos == 2 || autopos == 3) {
98                     cp = cp.minus(textBoxVectors[1]);
99                     d = d.setY(-finalSize);
100                 }
101                 if (autopos == 0 || autopos == 2) {
102                     cp = cp.minus(textBoxVectors[0]);
103                     d = d.setX(-finalSize);
104                 }
105
106                 p = projection.unproject(cp.plus(textBoxVectors[0]).plus(textBoxVectors[1]));
107                 Vector3d ucp = projection.unproject(cp);
108                 if (p.getX() < -1 || p.getX() > 1 || p.getY() < -1 || p.getY() > 1 || ucp.getX() < -1 || ucp.getX() > 1 || ucp.getY() < -1 || ucp.getY() > 1) {
109                     autopos = -1;
110                 } else {
111                     cornerPositions[0] = cp;
112                     delta = d;
113                 }
114             }
115
116             if (autopos == -1) {
117                 Vector3d position = projection.unproject(cornerPositions[0].minus(textBoxVectors[0]).plus(textBoxVectors[1]));
118                 if (position.getX() >= -1 && position.getX() <= 1 && position.getY() >= -1 && position.getY() <= 1) {
119                     cornerPositions[0] = cornerPositions[0].minus(textBoxVectors[0]);
120                     delta = delta.setX(-finalSize);
121                 } else {
122                     position = projection.unproject(cornerPositions[0].plus(textBoxVectors[0]).minus(textBoxVectors[1]));
123                     if (position.getX() >= -1 && position.getX() <= 1 && position.getY() >= -1 && position.getY() <= 1) {
124                         cornerPositions[0] = cornerPositions[0].minus(textBoxVectors[1]);
125                         delta = delta.setY(-finalSize);
126                     } else {
127                         position = projection.unproject(cornerPositions[0].minus(textBoxVectors[0]).minus(textBoxVectors[1]));
128                         if (position.getX() >= -1 && position.getX() <= 1 && position.getY() >= -1 && position.getY() <= 1) {
129                             cornerPositions[0] = cornerPositions[0].minus(textBoxVectors[1]);
130                             cornerPositions[0] = cornerPositions[0].minus(textBoxVectors[0]);
131                             delta = delta.setX(-finalSize);
132                             delta = delta.setY(-finalSize);
133                         }
134                     }
135                 }
136             }
137         } else {
138             if (datatip.getOrientation() == 2 || datatip.getOrientation() == 3) {
139                 cornerPositions[0] = cornerPositions[0].minus(textBoxVectors[1]);
140                 delta = delta.setY(-finalSize);
141             }
142             if (datatip.getOrientation() == 0 || datatip.getOrientation() == 2) {
143                 cornerPositions[0] = cornerPositions[0].minus(textBoxVectors[0]);
144                 delta = delta.setX(-finalSize);
145             }
146             if (datatip.getOrientation() == 4) {
147                 cornerPositions[0] = cornerPositions[0].minus(textBoxVectors[0]);
148                 cornerPositions[0] = cornerPositions[0].minus(textBoxVectors[1].times(0.5));
149                 delta = delta.setY(0);
150                 delta = delta.setX(Math.sqrt(2)*(-finalSize));
151             }
152             if (datatip.getOrientation() == 5) {
153                 cornerPositions[0] = cornerPositions[0].minus(textBoxVectors[1].times(0.5));
154                 delta = delta.setY(0);
155                 delta = delta.setX(Math.sqrt(2)*finalSize);
156             }
157             if (datatip.getOrientation() == 6) {
158                 cornerPositions[0] = cornerPositions[0].minus(textBoxVectors[0].times(0.5));
159                 delta = delta.setX(0);
160                 delta = delta.setY(Math.sqrt(2)*finalSize);
161             }
162             if (datatip.getOrientation() == 7) {
163                 cornerPositions[0] = cornerPositions[0].minus(textBoxVectors[0].times(0.5));
164                 cornerPositions[0] = cornerPositions[0].minus(textBoxVectors[1]);
165                 delta = delta.setX(0);
166                 delta = delta.setY(Math.sqrt(2)*(-finalSize));
167             }
168         }
169
170         cornerPositions[0] = cornerPositions[0].plus(delta);
171         cornerPositions[1] = cornerPositions[1].plus(delta);
172
173         /* The Text object's rotation direction convention is opposite to the standard one, its angle is expressed in radians. */
174         drawingTools.draw(texture, AnchorPosition.LOWER_LEFT, cornerPositions[0], -180.0 * datatip.getFontAngle() / Math.PI);
175
176         drawingTools.getTransformationManager().useSceneCoordinate();
177
178         /* Compute the corners of the text's bounding box in window coordinates */
179         Vector3d[] projCorners;
180         if (datatip.getTextBoxMode() == 2) {
181             projCorners = computeProjTextBoxCorners(cornerPositions[1], datatip.getFontAngle(), textBoxVectors);
182         } else {
183             projCorners = computeProjCorners(cornerPositions[0], datatip.getFontAngle(), texture.getDataProvider().getTextureSize());
184         }
185
186         Vector3d[] corners = computeCorners(projection, projCorners, parentAxes);
187         Double[] coordinates = cornersToCoordinateArray(corners);
188
189         /* Set the computed coordinates */
190         datatip.setCorners(coordinates);
191     }
192
193     /**
194      * Update the given datatip text corners
195      * @param datatip the given datatip
196      */
197     public static void updateTextCorners(Datatip datatip) {
198         Vector3d[] projCorners = null;
199         DrawerVisitor currentVisitor = DrawerVisitor.getVisitor(datatip.getParentFrameOrFigure());
200         Transformation currentProj = currentVisitor.getAxesDrawer().getProjection(datatip.getParentAxes());
201         Axes parentAxes = (Axes) GraphicController.getController().getObjectFromId(datatip.getParentAxes());
202         Dimension spriteDim = currentVisitor.getDatatipTextDrawer().getSpriteDims(currentVisitor.getColorMap(), datatip);
203
204         /* Compute the corners */
205         try {
206             Vector3d[] textBoxVectors = currentVisitor.getDatatipTextDrawer().computeTextBoxVectors(currentProj, datatip, spriteDim, parentAxes);
207             Vector3d[] cornerPositions = currentVisitor.getDatatipTextDrawer().computeTextPosition(currentProj, datatip, textBoxVectors, spriteDim);
208
209             Integer size = datatip.getMarkSize();
210             Integer unit = datatip.getMarkSizeUnit();
211
212             /* calculate the size of the mark to dont position the text over the mark*/
213             double finalSize = (unit == 1) ? (8.0 + 2.0 * size) : size;
214             finalSize /= 2.0;
215             double r = datatip.getMarkStyle() == 11 ? 1.0 : 2.0;
216             finalSize -= (finalSize >= 2.0) ? r : 0.0;
217
218             Vector3d delta = new Vector3d(finalSize, finalSize, 0);
219             /* set up the text position according to the datatip orientation*/
220             if (datatip.getOrientation() == 2 || datatip.getOrientation() == 3) {
221                 cornerPositions[0] = cornerPositions[0].minus(textBoxVectors[1]);
222                 delta = delta.setY(-finalSize);
223             }
224             if (datatip.getOrientation() == 0 || datatip.getOrientation() == 2) {
225                 cornerPositions[0] = cornerPositions[0].minus(textBoxVectors[0]);
226                 delta = delta.setX(-finalSize);
227             }
228             if (datatip.getOrientation() == 4) {
229                 cornerPositions[0] = cornerPositions[0].minus(textBoxVectors[0]);
230                 cornerPositions[0] = cornerPositions[0].minus(textBoxVectors[1].times(0.5));
231                 delta = delta.setY(0);
232                 delta = delta.setX(Math.sqrt(2)*(-finalSize));
233             }
234             if (datatip.getOrientation() == 5) {
235                 cornerPositions[0] = cornerPositions[0].minus(textBoxVectors[1].times(0.5));
236                 delta = delta.setY(0);
237                 delta = delta.setX(Math.sqrt(2)*finalSize);
238             }
239             if (datatip.getOrientation() == 6) {
240                 cornerPositions[0] = cornerPositions[0].minus(textBoxVectors[0].times(0.5));
241                 delta = delta.setX(0);
242                 delta = delta.setY(Math.sqrt(2)*finalSize);
243             }
244             if (datatip.getOrientation() == 7) {
245                 cornerPositions[0] = cornerPositions[0].minus(textBoxVectors[0].times(0.5));
246                 cornerPositions[0] = cornerPositions[0].minus(textBoxVectors[1]);
247                 delta = delta.setX(0);
248                 delta = delta.setY(Math.sqrt(2)*(-finalSize));
249             }
250
251             cornerPositions[0] = cornerPositions[0].plus(delta);
252             cornerPositions[1] = cornerPositions[1].plus(delta);
253
254             if (datatip.getTextBoxMode() == 2) {
255                 projCorners = currentVisitor.getDatatipTextDrawer().computeProjTextBoxCorners(cornerPositions[1], datatip.getFontAngle(), textBoxVectors);
256             } else {
257                 projCorners = currentVisitor.getDatatipTextDrawer().computeProjCorners(cornerPositions[0], datatip.getFontAngle(), spriteDim);
258             }
259         } catch (DegenerateMatrixException e) {
260             // TODO Auto-generated catch block
261             e.printStackTrace();
262         }
263
264         Vector3d[] corners = currentVisitor.getDatatipTextDrawer().computeCorners(currentProj, projCorners, parentAxes);
265         Double[] coordinates = currentVisitor.getDatatipTextDrawer().cornersToCoordinateArray(corners);
266
267         /* Set the computed coordinates */
268         datatip.setCorners(coordinates);
269     }
270
271     /**
272      * Calculates the anchor point from datatip (Used to draw the datatip mark)
273      * @param datatip the given datatip
274      * @return Vector3d the anchor point position
275      */
276     public static Vector3d calculateAnchorPoint(Datatip datatip) {
277         Axes axes = (Axes) GraphicController.getController().getObjectFromId(datatip.getParentAxes());
278         double[][] factors = axes.getScaleTranslateFactors();
279         boolean[] logFlags = new boolean[] {axes.getXAxisLogFlag(), axes.getYAxisLogFlag(), axes.getZAxisLogFlag()};
280         Vector3d v = ScaleUtils.applyLogScale(new Vector3d(datatip.getTipData()), logFlags);
281
282         return new Vector3d(v.getX() * factors[0][0] + factors[1][0], v.getY() * factors[0][1] + factors[1][1], v.getZ() * factors[0][2] + factors[1][2]);
283     }
284
285     private static int getAutoOrientation(Datatip datatip) {
286         final double[] dataX = (double[]) PolylineData.getDataX(datatip.getParent());
287         int index = datatip.getIndexes();
288         if (index == 0 || index >= dataX.length - 1) {
289             return -1;
290         }
291
292         final double[] dataY = (double[]) PolylineData.getDataY(datatip.getParent());
293         final double[] first, second, third;
294         Integer axes = datatip.getParentAxes();
295
296         if (datatip.isUsing3Component()) {
297             final double[] dataZ = (double[]) PolylineData.getDataZ(datatip.getParent());
298             first = CallRenderer.getPixelFrom3dCoordinates(axes, new double[] {dataX[index - 1], dataY[index - 1], dataZ[index - 1]});
299             second = CallRenderer.getPixelFrom3dCoordinates(axes, new double[] {dataX[index], dataY[index], dataZ[index]});
300             third = CallRenderer.getPixelFrom3dCoordinates(axes, new double[] {dataX[index + 1], dataY[index + 1], dataZ[index + 1]});
301         } else {
302             first = CallRenderer.getPixelFrom3dCoordinates(axes, new double[] {dataX[index - 1], dataY[index - 1], 0});
303             second = CallRenderer.getPixelFrom3dCoordinates(axes, new double[] {dataX[index], dataY[index], 0});
304             third = CallRenderer.getPixelFrom3dCoordinates(axes, new double[] {dataX[index + 1], dataY[index + 1], 0});
305         }
306
307         final double a = Math.atan2(second[1] - first[1], first[0] - second[0]);
308         final double b = Math.atan2(second[1] - third[1], third[0] - second[0]);
309
310         final int quadA = getQuad(a);
311         final int quadB = getQuad(b);
312         final int quadDatatip;
313
314         // UL quadrant is 1, UR quadrant is 0
315         // LL quadrant is 2, LR quadrant is 3
316
317         if (quadA == quadB) {
318             quadDatatip = (quadA + 2) % 4;
319         } else {
320             final int sum = quadA + quadB;
321             if (sum == 3) {
322                 quadDatatip = (quadA * quadB + 1) % 3;
323             } else if (sum == 1 || sum == 5) {
324                 quadDatatip = (sum + 3) % 8;
325             } else {
326                 final double M = Math.max(a, b);
327                 final double m = Math.min(a, b);
328                 if (sum == 4) {
329                     if (M - m <= Math.PI) {
330                         quadDatatip = 2;
331                     } else {
332                         quadDatatip = 0;
333                     }
334                 } else {
335                     if (M - m <= Math.PI) {
336                         quadDatatip = 1;
337                     } else {
338                         quadDatatip = 3;
339                     }
340                 }
341             }
342         }
343
344         if (quadDatatip <= 1) {
345             return 1 - quadDatatip;
346         }
347
348         return quadDatatip;
349     }
350
351     private static int getQuad(final double a) {
352         if (a >= 0) {
353             if (a <= Math.PI / 2) {
354                 return 0;
355             } else {
356                 return 1;
357             }
358         } else {
359             if (a <= -Math.PI / 2) {
360                 return 2;
361             } else {
362                 return 3;
363             }
364         }
365     }
366 }