3a4f60db07ce5e26d9775aa794dd17e225dfab4d
[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 = calcCorrectedFactors(axes, axis);
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      * Calculates corrected axes factors when zoom is enabled
123      * @param axes the given axes
124      * @param axis the given axis
125      * @return corrected factors
126      */
127     private double[][] calcCorrectedFactors(Axes axes, Axis axis) {
128         Double [] bounds = axes.getDisplayedBounds();
129
130         double[][] zoomFactors = new double[2][];
131         zoomFactors[0] = new double[] { 2 / (bounds[1] - bounds[0]),
132                                         2 / (bounds[3] - bounds[2]),
133                                         2 / (bounds[5] - bounds[4])
134                                       };
135         zoomFactors[1] = new double[] { -(bounds[1] + bounds[0]) / (bounds[1] - bounds[0]),
136                                         -(bounds[3] + bounds[2]) / (bounds[3] - bounds[2]),
137                                         -(bounds[5] + bounds[4]) / (bounds[5] - bounds[4])
138                                       };
139         double[][] factors = axes.getScaleTranslateFactors();
140         //Z coordinate
141         double scale = factors[0][2] / (zoomFactors[0][2] == 0.0 ? 1.0 : zoomFactors[0][2]);
142         double invScale = zoomFactors[0][2] / (factors[0][2] == 0.0 ? 1.0 : factors[0][2]);
143         factors[0][2] *= scale;
144         factors[1][2] = scale * (factors[1][2] + (invScale * factors[1][2] - zoomFactors[1][2]));
145
146
147
148         if (axis.getYNumberTicks() == 1 || isConst(axis.getYTicksCoords())) {
149             scale = factors[0][1] / (zoomFactors[0][1] == 0.0 ? 1.0 : zoomFactors[0][1]);
150             invScale = zoomFactors[0][1] / (factors[0][1] == 0.0 ? 1.0 : factors[0][1]);
151             factors[0][1] *= scale;
152             factors[1][1] = scale * (factors[1][1] + (invScale * factors[1][1] - zoomFactors[1][1]));
153         }
154
155         if (axis.getXNumberTicks() == 1 || isConst(axis.getXTicksCoords())) {
156             scale = factors[0][0] / (zoomFactors[0][0] == 0.0 ? 1.0 : zoomFactors[0][0]);
157             invScale = zoomFactors[0][0] / (factors[0][0] == 0.0 ? 1.0 : factors[0][0]);
158             factors[0][0] *= scale;
159             factors[1][0] = scale * (factors[1][0] + (invScale * factors[1][0] - zoomFactors[1][0]));
160         }
161
162         return factors;
163     }
164
165     /**
166      * Checks if the given array have all its values equal(constant)
167      * @param data the given array
168      * @return true if it is constant, false otherwise
169      */
170     private boolean isConst(Double[] data) {
171         if (data.length > 0) {
172             double d = data[0];
173             for (int i = 0; i < data.length; i++) {
174                 if (data[i] != d) {
175                     return false;
176                 }
177             }
178         }
179         return true;
180     }
181
182     /**
183      * Return [min, max] the minimum and maximum of given values.
184      * @param values the given value.
185      * @return [min, max] the minimum and maximum of given values.
186      */
187     private double[] getMinAndMax(Double[] values) {
188         double min = +Double.MAX_VALUE;
189         double max = -Double.MAX_VALUE;
190         for (double value : values) {
191             min = Math.min(min, value);
192             max = Math.max(max, value);
193         }
194         return new double[] {min, max};
195     }
196
197     /**
198      * Decode ticks coordinate.
199      * If ticksStyle='0' then v gives the tics positions along the axis.
200      * If ticksStyle='1' then v must be of size 3. r=[xmin,xmax,n] and n gives the number of intervals.
201      * 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
202      * @param v the given value.
203      * @param ticksStyle used ticks style.
204      * @return decoded ticks coordinate.
205      */
206     private Double[] decodeValue(Double[] v, int ticksStyle) {
207         if ((ticksStyle == 1) && (v.length >= 3)) {
208             double min = v[0], max = v[1];
209             int n = v[2].intValue();
210             Double[] r = new Double[n + 1];
211             double k = (max - min) / n;
212             for (int i = 0 ; i <=  n ; i++) {
213                 r[i] = min + i * k;
214             }
215             return r;
216         } else if ((ticksStyle == 2) && (v.length >= 4)) {
217             double pow10 = Math.pow(10, v[2]);
218             double min = v[0] * pow10;
219             double max = v[1] * pow10;
220             int n = v[3].intValue();
221             Double[] r = new Double[n + 1];
222             double k = (max - min) / n;
223             for (int i = 0 ; i <=  n ; i++) {
224                 r[i] = min + i * k;
225             }
226             return r;
227         } else {
228             return v;
229         }
230     }
231
232     /**
233      * Return the ticks direction in scene coordinate.
234      * @param direction the ticks direction as an {@see Axis.TicksDirection}
235      * @return the ticks direction in scene coordinate.
236      */
237     private Vector3d computeTicksDirection(Axis.TicksDirection direction) {
238         switch (direction) {
239             case TOP:
240                 return new Vector3d(0, +1, 0);
241             case BOTTOM:
242                 return new Vector3d(0, -1, 0);
243             case LEFT:
244                 return new Vector3d(-1, 0, 0);
245             default:
246                 return new Vector3d(+1, 0, 0);
247         }
248     }
249
250     private class AxisGraduation extends AbstractGraduations {
251         private final List<Double> subTicksValue;
252         private final List<Double> allValues;
253         private final Axis axis;
254
255
256         AxisGraduation(Axis axis, Double[] ticksCoordinate, double[] minAndMax) {
257             super(0., 1.);
258             this.axis = axis;
259             allValues = computeValue(ticksCoordinate, minAndMax);
260             subTicksValue = computeSubValue(allValues, axis.getSubticks());
261         }
262
263
264         /**
265          * Compute the sub-ticks value.
266          * @param allValues the sorted list of ticks value.
267          * @param subTicks the number of sub-division between two ticks.
268          * @return the sub-ticks value.
269          */
270         private List<Double> computeSubValue(List<Double> allValues, Integer subTicks) {
271             if ((allValues == null) || (allValues.size() < 2)) {
272                 return new LinkedList<Double>();
273             } else {
274                 LinkedList<Double> subTicksValue = new LinkedList<Double>();
275                 Iterator<Double> iterator = allValues.iterator();
276                 double lastValue = iterator.next();
277                 while (iterator.hasNext()) {
278                     double currentValue = iterator.next();
279                     double k = (currentValue - lastValue) / subTicks;
280                     for (int i = 1 ; i < subTicks ; i++) {
281                         subTicksValue.add(lastValue + i * k);
282                     }
283                     lastValue = currentValue;
284                 }
285                 return subTicksValue;
286             }
287         }
288
289         /**
290          * Return a list of graduation value corresponding to the given coordinate.
291          * Value are in the range [0, 1] and the coordinate are linearly mapped
292          * to this range.
293          * If the coordinates are empty, empty list is returned.
294          * If the coordinates are all the same, a same-sized list of zero is returned.
295          *
296          * @param coordinates given coordinate.
297          * @param minAndMax the min an max of the given coordinate.
298          * @return a list of graduation value corresponding to the given coordinate.
299          */
300         private List<Double> computeValue(Double[] coordinates, double[] minAndMax) {
301             if ((coordinates == null) || (coordinates.length == 0)) {
302                 return new LinkedList<Double>();
303             } else if (coordinates.length == 1) {
304                 LinkedList<Double> allValues = new LinkedList<Double>();
305                 allValues.add(0.);
306                 return allValues;
307             } else {
308                 LinkedList<Double> allValues = new LinkedList<Double>();
309                 double min = minAndMax[0];
310                 double max = minAndMax[1];
311                 if (max == min) {
312                     for (Double coordinate : coordinates) {
313                         allValues.add(0.);
314                     }
315                 } else {
316                     double k = 1 / (max - min);
317                     for (double value : coordinates) {
318                         allValues.add(k * (value - min));
319                     }
320                     Collections.sort(allValues);
321                 }
322                 return allValues;
323             }
324         }
325
326         @Override
327         public List<Double> getAllValues() {
328             return allValues;
329         }
330
331         @Override
332         public List<Double> getNewValues() {
333             return allValues;
334         }
335
336         @Override
337         public Graduations getMore() {
338             return null;
339         }
340
341         @Override
342         public Graduations getAlternative() {
343             return null;
344         }
345
346         @Override
347         public Graduations getSubGraduations() {
348             return new AbstractGraduations(this) {
349                 @Override
350                 public List<Double> getAllValues() {
351                     return subTicksValue;
352                 }
353
354                 @Override
355                 public List<Double> getNewValues() {
356                     return getAllValues();
357                 }
358
359                 @Override
360                 public Graduations getMore() {
361                     return null;
362                 }
363
364                 @Override
365                 public Graduations getAlternative() {
366                     return null;
367                 }
368
369                 @Override
370                 public Graduations getSubGraduations() {
371                     return null;
372                 }
373
374                 @Override
375                 public int getSubDensity() {
376                     return 0;
377                 }
378             };
379         }
380
381         @Override
382         public int getSubDensity() {
383             return axis.getSubticks();
384         }
385     }
386
387     private class AxisSpriteFactory implements RulerSpriteFactory {
388         private final Axis axis;
389         private final double min;
390         private final double max;
391
392         public AxisSpriteFactory(Axis axis, double min, double max) {
393             this.axis = axis;
394             this.min = min;
395             this.max = max;
396         }
397
398         @Override
399         public Texture create(double value, DecimalFormat adaptedFormat, TextureManager spriteManager) {
400             String label = getLabel(value);
401             if (label != null) {
402                 FormattedText formattedText = new FormattedText();
403                 formattedText.setFont(axis.getFont());
404                 formattedText.setText(getLabel(value));
405
406                 FormattedTextSpriteDrawer textureDrawer = new FormattedTextSpriteDrawer(drawerVisitor.getColorMap(), formattedText);
407                 Texture texture = spriteManager.createTexture();
408                 texture.setMagnificationFilter(Texture.Filter.LINEAR);
409                 texture.setMinifyingFilter(Texture.Filter.LINEAR);
410                 texture.setDrawer(textureDrawer);
411
412                 return texture;
413             } else {
414                 return null;
415             }
416         }
417
418         /**
419          * Return the label corresponding to the given value.
420          * @param value the given value.
421          * @return the label corresponding to the given value.
422          */
423         private String getLabel(double value) {
424             // 0 <= value <= 1
425             // Should find right index through given labels.
426             String[] ticksLabel = axis.getTicksLabels();
427             int index = (int) Math.round(value * (ticksLabel.length - 1));
428             if ((index < 0) || (index > ticksLabel.length) || ticksLabel.length == 0) {
429                 return null;
430             } else {
431                 return ticksLabel[index];
432             }
433         }
434     }
435 }