d032871f8e2ebf45631e63f23ed571887cfc4418
[scilab.git] / scilab / modules / renderer / src / java / org / scilab / modules / renderer / JoGLView / AxisDrawer.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2009-2012 - DIGITEO - Pierre Lando
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;
13
14 import org.scilab.forge.scirenderer.DrawingTools;
15 import org.scilab.forge.scirenderer.ruler.DefaultRulerModel;
16 import org.scilab.forge.scirenderer.ruler.RulerDrawer;
17 import org.scilab.forge.scirenderer.ruler.RulerSpriteFactory;
18 import org.scilab.forge.scirenderer.ruler.graduations.AbstractGraduations;
19 import org.scilab.forge.scirenderer.ruler.graduations.Graduations;
20 import org.scilab.forge.scirenderer.texture.Texture;
21 import org.scilab.forge.scirenderer.texture.TextureManager;
22 import org.scilab.forge.scirenderer.tranformations.Vector3d;
23 import org.scilab.modules.graphic_objects.axes.Axes;
24 import org.scilab.modules.graphic_objects.axis.Axis;
25 import org.scilab.modules.graphic_objects.textObject.FormattedText;
26 import org.scilab.modules.renderer.JoGLView.util.ColorFactory;
27 import org.scilab.modules.renderer.JoGLView.util.FormattedTextSpriteDrawer;
28 import org.scilab.modules.renderer.JoGLView.util.ScaleUtils;
29
30 import java.text.DecimalFormat;
31 import java.util.Collections;
32 import java.util.Iterator;
33 import java.util.LinkedList;
34 import java.util.List;
35
36 /**
37  * @author Pierre Lando
38  */
39 public class AxisDrawer {
40
41     /** Ticks length in pixels. */
42     private static final int TICKS_LENGTH = 8;
43
44     /** Sub-ticks length in pixels. */
45     private static final int SUB_TICKS_LENGTH = 5;
46
47     /**Ticks sprites distance in pixels. */
48     private static final int SPRITE_DISTANCE = 12;
49
50     private final DrawerVisitor drawerVisitor;
51
52     public AxisDrawer(DrawerVisitor drawerVisitor) {
53         this.drawerVisitor = drawerVisitor;
54     }
55
56     public void draw(Axes axes, Axis axis) {
57         double min;
58         double max;
59
60         DefaultRulerModel rulerModel = new DefaultRulerModel();
61         rulerModel.setSpriteDistance(SPRITE_DISTANCE);
62         rulerModel.setSubTicksLength(SUB_TICKS_LENGTH);
63         rulerModel.setTicksLength(TICKS_LENGTH);
64         boolean[] logFlags = new boolean[] {axes.getXAxisLogFlag(), axes.getYAxisLogFlag(), axes.getZAxisLogFlag()};
65
66         Double[] xTicksValues;
67         Double[] yTicksValues;
68         double[] xMinAndMax;
69         double[] yMinAndMax;
70         double[][] factors = axes.getScaleTranslateFactors();
71
72         if (axis.getXTicksCoords().length == 1) {
73             xTicksValues = axis.getXTicksCoords();
74             yTicksValues = decodeValue(axis.getYTicksCoords(), axis.getTicksStyle());
75             xMinAndMax = getMinAndMax(xTicksValues);
76             yMinAndMax = getMinAndMax(yTicksValues);
77             min = yMinAndMax[0];
78             max = yMinAndMax[1];
79             rulerModel.setUserGraduation(new AxisGraduation(axis, yTicksValues, yMinAndMax));
80         } else {
81             xTicksValues = decodeValue(axis.getXTicksCoords(), axis.getTicksStyle());
82             yTicksValues = axis.getYTicksCoords();
83             xMinAndMax = getMinAndMax(xTicksValues);
84             yMinAndMax = getMinAndMax(yTicksValues);
85             min = xMinAndMax[0];
86             max = xMinAndMax[1];
87             rulerModel.setUserGraduation(new AxisGraduation(axis, xTicksValues, xMinAndMax));
88         }
89
90         Vector3d start = new Vector3d(xMinAndMax[0], yMinAndMax[0], 0);
91         Vector3d end = new Vector3d(xMinAndMax[1], yMinAndMax[1], 0);
92         start = ScaleUtils.applyLogScale(start, logFlags);
93         end = ScaleUtils.applyLogScale(end, logFlags);
94
95         start = new Vector3d(start.getX() * factors[0][0] + factors[1][0], start.getY() * factors[0][1] + factors[1][1], start.getZ() * factors[0][2] + factors[1][2]);
96         end = new Vector3d(end.getX() * factors[0][0] + factors[1][0], end.getY() * factors[0][1] + factors[1][1], end.getZ() * factors[0][2] + factors[1][2]);
97
98         // TODO : RulerModel should be an interface.
99         rulerModel.setAutoTicks(false);
100         rulerModel.setFirstValue(0);
101         rulerModel.setSecondValue(1);
102         rulerModel.setLineVisible(axis.getTicksSegment());
103         rulerModel.setColor(ColorFactory.createColor(drawerVisitor.getColorMap(), axis.getTicksColor()));
104
105         rulerModel.setPoints(start, end);
106         rulerModel.setTicksDirection(computeTicksDirection(axis.getTicksDirectionAsEnum()));
107
108         DrawingTools drawingTools = drawerVisitor.getDrawingTools();
109
110         RulerDrawer rulerDrawer = new RulerDrawer(drawerVisitor.getCanvas().getTextureManager());
111         rulerDrawer.setSpriteFactory(new AxisSpriteFactory(axis, min, max));
112         rulerDrawer.draw(drawingTools, rulerModel);
113
114         axis.getFormatn();
115         rulerDrawer.disposeResources();
116     }
117
118     /**
119      * Return [min, max] the minimum and maximum of given values.
120      * @param values the given value.
121      * @return [min, max] the minimum and maximum of given values.
122      */
123     private double[] getMinAndMax(Double[] values) {
124         double min = +Double.MAX_VALUE;
125         double max = -Double.MAX_VALUE;
126         for (double value : values) {
127             min = Math.min(min, value);
128             max = Math.max(max, value);
129         }
130         return new double[] {min, max};
131     }
132
133     /**
134      * Decode ticks coordinate.
135      * If ticksStyle='0' then v gives the tics positions along the axis.
136      * If ticksStyle='1' then v must be of size 3. r=[xmin,xmax,n] and n gives the number of intervals.
137      * If ticksStyle='2' then v must be of size 4, r=[k1,k2,a,n]. then xmin=k1*10^a, xmax=k2*10^a and n gives the number of intervals
138      * @param v the given value.
139      * @param ticksStyle used ticks style.
140      * @return decoded ticks coordinate.
141      */
142     private Double[] decodeValue(Double[] v, int ticksStyle) {
143         if ((ticksStyle == 1) && (v.length >= 3)) {
144             double min = v[0], max = v[1];
145             int n = v[2].intValue();
146             Double[] r = new Double[n + 1];
147             double k = (max - min) / n;
148             for (int i = 0 ; i <=  n ; i++) {
149                 r[i] = min + i * k;
150             }
151             return r;
152         } else if ((ticksStyle == 2) && (v.length >= 4)) {
153             double pow10 = Math.pow(10, v[2]);
154             double min = v[0] * pow10;
155             double max = v[1] * pow10;
156             int n = v[3].intValue();
157             Double[] r = new Double[n + 1];
158             double k = (max - min) / n;
159             for (int i = 0 ; i <=  n ; i++) {
160                 r[i] = min + i * k;
161             }
162             return r;
163         } else {
164             return v;
165         }
166     }
167
168     /**
169      * Return the ticks direction in scene coordinate.
170      * @param direction the ticks direction as an {@see Axis.TicksDirection}
171      * @return the ticks direction in scene coordinate.
172      */
173     private Vector3d computeTicksDirection(Axis.TicksDirection direction) {
174         switch (direction) {
175             case TOP:
176                 return new Vector3d(0, +1, 0);
177             case BOTTOM:
178                 return new Vector3d(0, -1, 0);
179             case LEFT:
180                 return new Vector3d(-1, 0, 0);
181             default:
182                 return new Vector3d(+1, 0, 0);
183         }
184     }
185
186     private class AxisGraduation extends AbstractGraduations {
187         private final List<Double> subTicksValue;
188         private final List<Double> allValues;
189         private final Axis axis;
190
191
192         AxisGraduation(Axis axis, Double[] ticksCoordinate, double[] minAndMax) {
193             super(0., 1.);
194             this.axis = axis;
195             allValues = computeValue(ticksCoordinate, minAndMax);
196             subTicksValue = computeSubValue(allValues, axis.getSubticks());
197         }
198
199
200         /**
201          * Compute the sub-ticks value.
202          * @param allValues the sorted list of ticks value.
203          * @param subTicks the number of sub-division between two ticks.
204          * @return the sub-ticks value.
205          */
206         private List<Double> computeSubValue(List<Double> allValues, Integer subTicks) {
207             if ((allValues == null) || (allValues.size() < 2)) {
208                 return new LinkedList<Double>();
209             } else {
210                 LinkedList<Double> subTicksValue = new LinkedList<Double>();
211                 Iterator<Double> iterator = allValues.iterator();
212                 double lastValue = iterator.next();
213                 while (iterator.hasNext()) {
214                     double currentValue = iterator.next();
215                     double k = (currentValue - lastValue) / subTicks;
216                     for (int i = 1 ; i < subTicks ; i++) {
217                         subTicksValue.add(lastValue + i * k);
218                     }
219                     lastValue = currentValue;
220                 }
221                 return subTicksValue;
222             }
223         }
224
225         /**
226          * Return a list of graduation value corresponding to the given coordinate.
227          * Value are in the range [0, 1] and the coordinate are linearly mapped
228          * to this range.
229          * If the coordinates are empty, empty list is returned.
230          * If the coordinates are all the same, a same-sized list of zero is returned.
231          *
232          * @param coordinates given coordinate.
233          * @param minAndMax the min an max of the given coordinate.
234          * @return a list of graduation value corresponding to the given coordinate.
235          */
236         private List<Double> computeValue(Double[] coordinates, double[] minAndMax) {
237             if ((coordinates == null) || (coordinates.length == 0)) {
238                 return new LinkedList<Double>();
239             } else if (coordinates.length == 1) {
240                 LinkedList<Double> allValues = new LinkedList<Double>();
241                 allValues.add(0.);
242                 return allValues;
243             } else {
244                 LinkedList<Double> allValues = new LinkedList<Double>();
245                 double min = minAndMax[0];
246                 double max = minAndMax[1];
247                 if (max == min) {
248                     for (Double coordinate : coordinates) {
249                         allValues.add(0.);
250                     }
251                 } else {
252                     double k = 1 / (max - min);
253                     for (double value : coordinates) {
254                         allValues.add(k * (value - min));
255                     }
256                     Collections.sort(allValues);
257                 }
258                 return allValues;
259             }
260         }
261
262         @Override
263         public List<Double> getAllValues() {
264             return allValues;
265         }
266
267         @Override
268         public List<Double> getNewValues() {
269             return allValues;
270         }
271
272         @Override
273         public Graduations getMore() {
274             return null;
275         }
276
277         @Override
278         public Graduations getAlternative() {
279             return null;
280         }
281
282         @Override
283         public Graduations getSubGraduations() {
284             return new AbstractGraduations(this) {
285                 @Override
286                 public List<Double> getAllValues() {
287                     return subTicksValue;
288                 }
289
290                 @Override
291                 public List<Double> getNewValues() {
292                     return getAllValues();
293                 }
294
295                 @Override
296                 public Graduations getMore() {
297                     return null;
298                 }
299
300                 @Override
301                 public Graduations getAlternative() {
302                     return null;
303                 }
304
305                 @Override
306                 public Graduations getSubGraduations() {
307                     return null;
308                 }
309
310                 @Override
311                 public int getSubDensity() {
312                     return 0;
313                 }
314             };
315         }
316
317         @Override
318         public int getSubDensity() {
319             return axis.getSubticks();
320         }
321     }
322
323     private class AxisSpriteFactory implements RulerSpriteFactory {
324         private final Axis axis;
325         private final double min;
326         private final double max;
327
328         public AxisSpriteFactory(Axis axis, double min, double max) {
329             this.axis = axis;
330             this.min = min;
331             this.max = max;
332         }
333
334         @Override
335         public Texture create(double value, DecimalFormat adaptedFormat, TextureManager spriteManager) {
336             String label = getLabel(value);
337             if (label != null) {
338                 FormattedText formattedText = new FormattedText();
339                 formattedText.setFont(axis.getFont());
340                 formattedText.setText(getLabel(value));
341
342                 FormattedTextSpriteDrawer textureDrawer = new FormattedTextSpriteDrawer(drawerVisitor.getColorMap(), formattedText);
343                 Texture texture = spriteManager.createTexture();
344                 texture.setMagnificationFilter(Texture.Filter.LINEAR);
345                 texture.setMinifyingFilter(Texture.Filter.LINEAR);
346                 texture.setDrawer(textureDrawer);
347
348                 return texture;
349             } else {
350                 return null;
351             }
352         }
353
354         /**
355          * Return the label corresponding to the given value.
356          * @param value the given value.
357          * @return the label corresponding to the given value.
358          */
359         private String getLabel(double value) {
360             // 0 <= value <= 1
361             // Should find right index through given labels.
362             String[] ticksLabel = axis.getTicksLabels();
363             int index = (int) Math.round(value * (ticksLabel.length - 1));
364             if ((index < 0) || (index > ticksLabel.length)) {
365                 return null;
366             } else {
367                 return ticksLabel[index];
368             }
369         }
370     }
371 }