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