Chris@144
|
1 module yetilab.plot.chart;
|
Chris@108
|
2
|
Chris@128
|
3 import org.jzy3d.plot3d.text.drawable: DrawableTextBillboard, DrawableTextBitmap;
|
Chris@119
|
4 import org.jzy3d.maths: Range, Coord3d;
|
Chris@126
|
5 import org.jzy3d.plot3d.primitives: Shape, HistogramBar, FlatLine2d, Polygon, Quad, Point;
|
Chris@138
|
6 import org.jzy3d.plot3d.primitives.axes.layout.providers: StaticTickProvider, RegularTickProvider;
|
Chris@140
|
7 import org.jzy3d.plot3d.primitives.axes.layout.renderers: ITickRenderer, TickLabelMap, IntegerTickRenderer;
|
Chris@108
|
8 import org.jzy3d.chart: Chart, ChartLauncher;
|
Chris@108
|
9 import org.jzy3d.plot3d.builder: Builder;
|
Chris@144
|
10 import org.jzy3d.colors: Color;
|
Chris@108
|
11 import org.jzy3d.plot3d.rendering.canvas: Quality;
|
Chris@119
|
12 import org.jzy3d.plot3d.rendering.view.modes: ViewPositionMode;
|
Chris@108
|
13
|
Chris@137
|
14 import javax.imageio: ImageIO;
|
Chris@137
|
15
|
Chris@137
|
16 import java.io: File;
|
Chris@137
|
17
|
Chris@132
|
18 chartColours = array [
|
Chris@132
|
19 { r = 82, g = 126, b = 154 }, // dark steel blue
|
Chris@132
|
20 { r = 161, g = 54, b = 2 }, // red
|
Chris@132
|
21 { r = 207, g = 228, b = 148 }, // grey-green
|
Chris@132
|
22 { r = 21, g = 183, b = 197 }, // light blue
|
Chris@132
|
23 { r = 251, g = 116, b = 43 }, // light red
|
Chris@132
|
24 { r = 200, g = 125, b = 234 }, // light purple
|
Chris@132
|
25 { r = 126, g = 33, b = 28 }, // dried blood!
|
Chris@132
|
26 { r = 188, g = 13, b = 207 }, // mid purple
|
Chris@131
|
27 ];
|
Chris@131
|
28
|
Chris@132
|
29 chartColour n =
|
Chris@132
|
30 if n < 0
|
Chris@132
|
31 then chartColour (-n)
|
Chris@132
|
32 else
|
Chris@132
|
33 rgb = chartColours[n % (length chartColours)];
|
Chris@132
|
34 new Color(rgb.r / 255.0, rgb.g / 255.0, rgb.b / 255.0);
|
Chris@132
|
35 fi;
|
Chris@132
|
36
|
Chris@133
|
37 newPercentTickRenderer () =
|
Chris@133
|
38 (f v = " \(int (v * 100))%";
|
Chris@133
|
39 class PercentageTickRenderer extends ITickRenderer
|
Chris@133
|
40 String format(double value) f value,
|
Chris@133
|
41 String format(float value) f value
|
Chris@133
|
42 end;
|
Chris@133
|
43 new PercentageTickRenderer());
|
Chris@133
|
44
|
Chris@140
|
45 newPaddedIntTickRenderer () =
|
Chris@140
|
46 (f v = " \(int (v + 0.5))";
|
Chris@140
|
47 class PaddedIntTickRenderer extends ITickRenderer
|
Chris@140
|
48 String format(double value) f value,
|
Chris@140
|
49 String format(float value) f value
|
Chris@140
|
50 end;
|
Chris@140
|
51 new PaddedIntTickRenderer());
|
Chris@140
|
52
|
Chris@132
|
53 parseOptions options defaultKeys defaultXKeys =
|
Chris@132
|
54 (parsed = {
|
Chris@133
|
55 var keys = array (sort defaultKeys),
|
Chris@132
|
56 var labels = [:],
|
Chris@132
|
57 var animated = false,
|
Chris@132
|
58 var normalised = false,
|
Chris@132
|
59 var unit = "",
|
Chris@137
|
60 var xkeys = array (sort defaultXKeys),
|
Chris@137
|
61 var saveTo = "",
|
Chris@137
|
62 var display = true,
|
Chris@132
|
63 };
|
Chris@132
|
64 for options
|
Chris@132
|
65 \case of
|
Chris@133
|
66 Keys kk: parsed.keys := array kk;
|
Chris@133
|
67 XKeys xk: parsed.xkeys := array xk;
|
Chris@132
|
68 Animated a: parsed.animated := a;
|
Chris@132
|
69 Normalised n: parsed.normalised := n;
|
Chris@132
|
70 Unit u: parsed.unit := u;
|
Chris@132
|
71 Labels ll: parsed.labels := ll;
|
Chris@137
|
72 SaveTo file: parsed.saveTo := file;
|
Chris@137
|
73 Display d: parsed.display := d;
|
Chris@132
|
74 esac;
|
Chris@132
|
75 if empty? parsed.labels then
|
Chris@132
|
76 parsed.labels := mapIntoHash id id parsed.keys
|
Chris@132
|
77 fi;
|
Chris@132
|
78 parsed);
|
Chris@132
|
79
|
Chris@137
|
80 newChart opts =
|
Chris@137
|
81 (quality = Quality#Fastest;
|
Chris@137
|
82 quality#setAnimated(opts.animated);
|
Chris@137
|
83 // if opts.display then
|
Chris@137
|
84 new Chart(quality);
|
Chris@137
|
85 // else
|
Chris@137
|
86 // new Chart(quality, "offscreen,640,640");
|
Chris@137
|
87 // fi);
|
Chris@137
|
88 );
|
Chris@137
|
89
|
Chris@137
|
90 showChart opts chart is 'a -> ~Chart -> () =
|
Chris@141
|
91 (if opts.display then
|
Chris@137
|
92 \() ChartLauncher#openChart(chart);
|
Chris@141
|
93 else
|
Chris@141
|
94 \() ChartLauncher#openStaticChart(chart);
|
Chris@141
|
95 fi;
|
Chris@137
|
96 if opts.saveTo != "" then
|
Chris@137
|
97 \() chart#screenshot(opts.saveTo);
|
Chris@137
|
98 fi);
|
Chris@137
|
99
|
Chris@132
|
100 plotBarChart options values =
|
Chris@132
|
101 (opts = parseOptions options (keys values) [];
|
Chris@137
|
102 chart = newChart opts;
|
Chris@132
|
103 var n = length opts.keys;
|
Chris@119
|
104 scene = chart#getScene();
|
Chris@139
|
105 ticks = new double[n];
|
Chris@119
|
106 tickLabels = new TickLabelMap();
|
Chris@132
|
107 var i = 0;
|
Chris@132
|
108 var x = n - i - 1;
|
Chris@134
|
109 total = sum (map do k: if k in values then values[k] else 0 fi done opts.keys);
|
Chris@132
|
110 for opts.keys do k:
|
Chris@136
|
111 bar = new HistogramBar();
|
Chris@134
|
112 v = if k in values then values[k] else 0 fi;
|
Chris@133
|
113 v = if opts.normalised and total > 0 then v / total else v fi;
|
Chris@136
|
114 bar#setData(new Coord3d(x, 0, 0), v, 0.45, chartColour i);
|
Chris@136
|
115 bar#setWireframeDisplayed(false);
|
Chris@136
|
116 scene#add(bar);
|
Chris@132
|
117 ticks[i] := i;
|
Chris@132
|
118 tickLabels#register(x, opts.labels[k]);
|
Chris@132
|
119 i := i + 1;
|
Chris@132
|
120 x := x - 1;
|
Chris@119
|
121 done;
|
Chris@136
|
122 chart#getView()#setViewPoint(new Coord3d(pi/2, 0, 0));
|
Chris@136
|
123 axes = chart#getAxeLayout();
|
Chris@136
|
124 axes#setXAxeLabelDisplayed(false);
|
Chris@136
|
125 axes#setYAxeLabelDisplayed(false);
|
Chris@136
|
126 axes#setZAxeLabelDisplayed(true);
|
Chris@136
|
127 if opts.normalised then
|
Chris@136
|
128 axes#setZAxeLabel("");
|
Chris@136
|
129 axes#setZTickRenderer(newPercentTickRenderer ());
|
Chris@135
|
130 else
|
Chris@136
|
131 axes#setZAxeLabel(opts.unit);
|
Chris@135
|
132 fi;
|
Chris@119
|
133 axes#setXTickProvider(new StaticTickProvider(ticks));
|
Chris@119
|
134 axes#setXTickRenderer(tickLabels);
|
Chris@136
|
135 axes#setYTickLabelDisplayed(false);
|
Chris@137
|
136 showChart opts chart);
|
Chris@119
|
137
|
Chris@132
|
138 plotLines options values =
|
Chris@132
|
139 (opts = parseOptions options (keys values) (keys values[head (keys values)]);
|
Chris@137
|
140 chart = newChart opts;
|
Chris@124
|
141 scene = chart#getScene();
|
Chris@132
|
142 n = length opts.xkeys;
|
Chris@124
|
143 var z = 0;
|
Chris@132
|
144 for opts.keys do k:
|
Chris@124
|
145 v = values[k];
|
Chris@124
|
146 x = new float[n];
|
Chris@124
|
147 y = new float[n];
|
Chris@124
|
148 var i = 0;
|
Chris@132
|
149 for opts.xkeys do xk:
|
Chris@124
|
150 x[i] := i;
|
Chris@124
|
151 y[i] := if xk in v then v[xk] else 0 fi;
|
Chris@124
|
152 i := i + 1;
|
Chris@124
|
153 done;
|
Chris@124
|
154 line = new FlatLine2d(x, y, z);
|
Chris@124
|
155 line#setWireframeDisplayed(true);
|
Chris@132
|
156 line#setWireframeColor(chartColour z);
|
Chris@124
|
157 line#setWireframeWidth(2);
|
Chris@124
|
158 line#setFaceDisplayed(false);
|
Chris@124
|
159 scene#add(line);
|
Chris@124
|
160 z := z + 1;
|
Chris@124
|
161 done;
|
Chris@124
|
162 chart#getView()#setViewPoint(new Coord3d(0, 0, 0));
|
Chris@124
|
163 axes = chart#getAxeLayout();
|
Chris@124
|
164 axes#setXAxeLabelDisplayed(false);
|
Chris@124
|
165 axes#setYAxeLabelDisplayed(false);
|
Chris@124
|
166 axes#setZAxeLabelDisplayed(true);
|
Chris@132
|
167 axes#setZAxeLabel(opts.unit);
|
Chris@124
|
168 axes#setYTickLabelDisplayed(false);
|
Chris@137
|
169 showChart opts chart);
|
Chris@124
|
170
|
Chris@129
|
171 stack keys xkeys values normalised =
|
Chris@127
|
172 (stacked = mapIntoHash id \(mapIntoHash id \{ y0 = 0, y1 = 0 } xkeys) keys;
|
Chris@125
|
173 prev = mapIntoHash id \0 xkeys;
|
Chris@140
|
174 valueOf k xk =
|
Chris@140
|
175 if k in values and xk in values[k]
|
Chris@140
|
176 then values[k][xk] else 0
|
Chris@140
|
177 fi;
|
Chris@125
|
178 for xkeys do xk:
|
Chris@129
|
179 total = sum (map do k: valueOf k xk done keys);
|
Chris@125
|
180 for keys do k:
|
Chris@129
|
181 value =
|
Chris@129
|
182 if normalised and total > 0
|
Chris@129
|
183 then (valueOf k xk) / total
|
Chris@129
|
184 else (valueOf k xk)
|
Chris@129
|
185 fi;
|
Chris@125
|
186 height = prev[xk] + value;
|
Chris@127
|
187 stacked[k][xk] := { y0 = prev[xk], y1 = height };
|
Chris@125
|
188 prev[xk] := height;
|
Chris@125
|
189 done;
|
Chris@125
|
190 done;
|
Chris@125
|
191 stacked);
|
Chris@125
|
192
|
Chris@136
|
193 newRect x y0 y1 z colour is number -> number -> number -> number -> ~Color -> 'a =
|
Chris@136
|
194 (poly = new Quad();
|
Chris@136
|
195 poly#add(new Point(new Coord3d(x + 0.5, z, y0)));
|
Chris@136
|
196 poly#add(new Point(new Coord3d(x + 0.5, z, y1)));
|
Chris@136
|
197 poly#add(new Point(new Coord3d(x - 0.5, z, y1)));
|
Chris@136
|
198 poly#add(new Point(new Coord3d(x - 0.5, z, y0)));
|
Chris@136
|
199 poly#setWireframeDisplayed(true);
|
Chris@136
|
200 poly#setWireframeColor(colour);
|
Chris@136
|
201 poly#setFaceDisplayed(true);
|
Chris@136
|
202 poly#setColor(colour);
|
Chris@136
|
203 poly);
|
Chris@136
|
204
|
Chris@132
|
205 plotStacked options values =
|
Chris@132
|
206 (opts = parseOptions options (keys values) (keys values[head (keys values)]);
|
Chris@137
|
207 chart = newChart opts;
|
Chris@125
|
208 scene = chart#getScene();
|
Chris@132
|
209 stacked = stack opts.keys opts.xkeys values opts.normalised;
|
Chris@125
|
210 var z = 0;
|
Chris@130
|
211 var ty = 0;
|
Chris@138
|
212 nxk = length opts.xkeys;
|
Chris@139
|
213 xticks = new double[nxk];
|
Chris@133
|
214 xtickLabels = new TickLabelMap();
|
Chris@138
|
215 for [0..nxk - 1] do x:
|
Chris@139
|
216 xticks[x] := x;
|
Chris@139
|
217 k = opts.xkeys[x];
|
Chris@139
|
218 xtickLabels#register(x, if k in opts.labels then opts.labels[k] else k fi);
|
Chris@133
|
219 done;
|
Chris@132
|
220 for opts.keys do k:
|
Chris@127
|
221 ranges = stacked[k];
|
Chris@132
|
222 c = chartColour z;
|
Chris@138
|
223 for [0..nxk - 1] do x:
|
Chris@133
|
224 xk = opts.xkeys[x];
|
Chris@127
|
225 rect = newRect x ranges[xk].y0 ranges[xk].y1 z c;
|
Chris@127
|
226 scene#add(rect);
|
Chris@125
|
227 done;
|
Chris@138
|
228 text = new DrawableTextBitmap(opts.labels[k], new Coord3d(-(nxk/5 + 0.5), z, ty), c);
|
Chris@128
|
229 scene#add(text);
|
Chris@126
|
230 z := z - 1;
|
Chris@130
|
231 ty := ty + 0.1;
|
Chris@125
|
232 done;
|
Chris@129
|
233 chart#getView()#setViewPoint(new Coord3d(-pi/2, 0, 0));
|
Chris@125
|
234 axes = chart#getAxeLayout();
|
Chris@125
|
235 axes#setXAxeLabelDisplayed(false);
|
Chris@139
|
236 if nxk < 10 then
|
Chris@139
|
237 axes#setXTickProvider(new StaticTickProvider(xticks));
|
Chris@139
|
238 fi;
|
Chris@133
|
239 axes#setXTickRenderer(xtickLabels);
|
Chris@125
|
240 axes#setYAxeLabelDisplayed(false);
|
Chris@125
|
241 axes#setZAxeLabelDisplayed(true);
|
Chris@133
|
242 if opts.normalised then
|
Chris@133
|
243 axes#setZAxeLabel("");
|
Chris@133
|
244 axes#setZTickRenderer(newPercentTickRenderer ());
|
Chris@133
|
245 else
|
Chris@133
|
246 axes#setZAxeLabel(opts.unit);
|
Chris@140
|
247 axes#setZTickRenderer(newPaddedIntTickRenderer ());
|
Chris@133
|
248 fi;
|
Chris@125
|
249 axes#setYTickLabelDisplayed(false);
|
Chris@137
|
250 showChart opts chart);
|
Chris@124
|
251
|
Chris@115
|
252 {
|
Chris@124
|
253 plotBarChart,
|
Chris@124
|
254 plotLines,
|
Chris@125
|
255 stack,
|
Chris@125
|
256 plotStacked,
|
Chris@115
|
257 }
|
Chris@110
|
258
|