gtk / cairo-chart

SSH Git

To clone this repository:

git clone git@git.backbone.ws:gtk/cairo-chart.git

To push to this repository:

# Add a new remote
git remote add origin git@git.backbone.ws:gtk/cairo-chart.git

# Push the master branch to the newly added origin, and configure
# this remote and branch as the default:
git push -u origin master

# From now on you can push master to the "origin" remote with:
git push

Commit: #accddc2

Parent: #cf9ec1d

Split source files on classes.

avatar Kolan Sh 2 months ago

Summary

1 namespace Gtk.CairoChart {
2 // If one of axis:title or axis:min/max are different
3 // then draw separate axis for each/all series
4 // or specify series name near the axis
5 public class Axis {
6 public Float128 min = 0;
7 public Float128 max = 1;
8 public Text title = new Text ("");
9 public enum Type {
10 NUMBERS = 0,
11 DATE_TIME
12 }
13 public enum ScaleType {
14 LINEAR = 0, // default
15 // LOGARITHMIC, // TODO
16 // etc
17 }
18 public Type type;
19 public ScaleType scale_type;
20 public enum Position {
21 LOW = 0,
22 HIGH = 1,
23 BOTH = 2
24 }
25 public Position position = Position.LOW;
26
27 string _format = "%.2Lf";
28 string _date_format = "%Y.%m.%d";
29 string _time_format = "%H:%M:%S";
30 int _dsec_signs = 2; // 2 signs = centiseconds
31 public string format {
32 get { return _format; }
33 set {
34 // TODO: check format
35 _format = value;
36 }
37 default = "%.2Lf";
38 }
39 public string date_format {
40 get { return _date_format; }
41 set {
42 // TODO: check format
43 _date_format = value;
44 }
45 default = "%Y.%m.%d";
46 }
47 public string time_format {
48 get { return _time_format; }
49 set {
50 // TODO: check format
51 _time_format = value;
52 }
53 default = "%H:%M:%S";
54 }
55 public int dsec_signs {
56 get { return _dsec_signs; }
57 set {
58 // TODO: check format
59 _dsec_signs = value;
60 }
61 default = 2;
62 }
63 public FontStyle font_style = FontStyle ();
64 public Color color = Color ();
65 public LineStyle line_style = new LineStyle ();
66 public double font_indent = 5;
67
68 public Axis () {}
69 }
70 }
1 namespace Gtk.CairoChart {
2 public class Chart {
3
4 protected double width = 0;
5 protected double height = 0;
6
7 public Cairo.Context context = null;
8
9 public Color bg_color;
10 public bool show_legend = true;
11 public Text title = new Text ("Cairo Chart");
12 public Color border_color = Color(0, 0, 0, 0.3);
13
14
15 public Legend legend = new Legend ();
16
17 public Series[] series = {};
18
19 protected LineStyle selection_style = new LineStyle ();
20
21 public Chart () {
22 bg_color = Color (1, 1, 1);
23 }
24
25 protected double cur_x_min = 0.0;
26 protected double cur_x_max = 0.0;
27 protected double cur_y_min = 0.0;
28 protected double cur_y_max = 0.0;
29
30 public virtual void check_cur_values () {
31 if (cur_x_min > cur_x_max)
32 cur_x_max = cur_x_min;
33 if (cur_y_min > cur_y_max)
34 cur_y_max = cur_y_min;
35 }
36
37 public virtual bool draw () {
38
39 update_size ();
40
41 draw_background ();
42
43 cur_x_min = cur_y_min = 0.0;
44 cur_x_max = width;
45 cur_y_max = height;
46
47 draw_chart_title ();
48 check_cur_values ();
49
50 draw_legend ();
51 check_cur_values ();
52
53 set_vertical_axes_titles ();
54
55 calc_plot_area (); // Calculate plot area
56
57 draw_horizontal_axis ();
58 check_cur_values ();
59
60 draw_vertical_axis ();
61 check_cur_values ();
62
63 draw_plot_area_border ();
64 check_cur_values ();
65
66 draw_series ();
67 check_cur_values ();
68
69 draw_cursors ();
70 check_cur_values ();
71
72 return true;
73 }
74
75 protected virtual void update_size () {
76 if (context != null) {
77 width = context.copy_clip_rectangle_list().rectangles[0].width;
78 height = context.copy_clip_rectangle_list().rectangles[0].height;
79 }
80 }
81
82 protected virtual void set_source_rgba (Color color) {
83 context.set_source_rgba (color.red, color.green, color.blue, color.alpha);
84 }
85
86 protected virtual void draw_background () {
87 if (context != null) {
88 set_source_rgba (bg_color);
89 context.paint();
90 set_source_rgba (Color (0, 0, 0, 1));
91 }
92 }
93
94 protected double title_width = 0.0;
95 protected double title_height = 0.0;
96
97 public double title_vindent = 4;
98
99 protected virtual void show_text(Text text) {
100 context.select_font_face(text.style.family,
101 text.style.slant,
102 text.style.weight);
103 context.set_font_size(text.style.size);
104 if (text.style.orientation == FontOrient.VERTICAL) {
105 context.rotate(- Math.PI / 2.0);
106 context.show_text(text.text);
107 context.rotate(Math.PI / 2.0);
108 } else {
109 context.show_text(text.text);
110 }
111 }
112
113 protected virtual void draw_chart_title () {
114 title_width = title.get_width (context);
115 title_height = title.get_height (context) + (legend.position == Legend.Position.TOP ? title_vindent * 2 : title_vindent);
116 cur_y_min += title_height;
117 set_source_rgba(title.color);
118 context.move_to (width/2 - title_width/2 - title.get_x_bearing(context), title.get_height(context) + title_vindent);
119 show_text(title);
120 }
121
122 protected double legend_width = 0;
123 protected double legend_height = 0;
124
125 protected enum LegendProcessType {
126 CALC = 0, // default
127 DRAW
128 }
129
130 protected virtual void set_line_style (LineStyle style) {
131 set_source_rgba(style.color);
132 context.set_line_join(style.line_join);
133 context.set_line_cap(style.line_cap);
134 context.set_line_width(style.width);
135 context.set_dash(style.dashes, style.dash_offset);
136 }
137
138 protected virtual void draw_legend_rect (out double x0, out double y0) {
139 x0 = y0 = 0.0;
140 if (context != null) {
141 switch (legend.position) {
142 case Legend.Position.TOP:
143 x0 = (width - legend_width) / 2;
144 y0 = title_height;
145 break;
146
147 case Legend.Position.BOTTOM:
148 x0 = (width - legend_width) / 2;
149 y0 = height - legend_height;
150 break;
151
152 case Legend.Position.LEFT:
153 x0 = 0;
154 y0 = (height - legend_height) / 2;
155 break;
156
157 case Legend.Position.RIGHT:
158 x0 = width - legend_width;
159 y0 = (height - legend_height) / 2;
160 break;
161
162 default:
163 break;
164 }
165 set_source_rgba(legend.bg_color);
166 context.rectangle (x0, y0, legend_width, legend_height);
167 context.fill();
168 set_line_style(legend.border_style);
169 context.move_to (x0, y0);
170 context.rel_line_to (legend_width, 0);
171 context.rel_line_to (0, legend_height);
172 context.rel_line_to (-legend_width, 0);
173 context.rel_line_to (0, -legend_height);
174 context.stroke ();
175 }
176 }
177
178 public double legend_line_length = 30.0;
179 public double legend_text_hspace = 10.0;
180 public double legend_text_vspace = 2.0;
181 public double marker_size = 8.0;
182
183 protected virtual void draw_marker_at_pos (Series.MarkerType marker_type,
184 double x, double y) {
185 context.move_to (x, y);
186 switch (marker_type) {
187 case Series.MarkerType.SQUARE:
188 context.rectangle (x - marker_size / 2, y - marker_size / 2,
189 marker_size, marker_size);
190 context.fill();
191 break;
192
193 case Series.MarkerType.CIRCLE:
194 context.arc (x, y, marker_size / 2, 0, 2*Math.PI);
195 context.fill();
196 break;
197
198 case Series.MarkerType.TRIANGLE:
199 context.move_to (x - marker_size / 2, y - marker_size / 2);
200 context.line_to (x + marker_size / 2, y - marker_size / 2);
201 context.line_to (x, y + marker_size / 2);
202 context.line_to (x - marker_size / 2, y - marker_size / 2);
203 context.fill();
204 break;
205
206 case Series.MarkerType.PRICLE_SQUARE:
207 context.rectangle (x - marker_size / 2, y - marker_size / 2,
208 marker_size, marker_size);
209 context.stroke();
210 break;
211
212 case Series.MarkerType.PRICLE_CIRCLE:
213 context.arc (x, y, marker_size / 2, 0, 2*Math.PI);
214 context.stroke();
215 break;
216
217 case Series.MarkerType.PRICLE_TRIANGLE:
218 context.move_to (x - marker_size / 2, y - marker_size / 2);
219 context.line_to (x + marker_size / 2, y - marker_size / 2);
220 context.line_to (x, y + marker_size / 2);
221 context.line_to (x - marker_size / 2, y - marker_size / 2);
222 context.stroke();
223 break;
224
225 case Series.MarkerType.NONE:
226 default:
227 break;
228 }
229 }
230
231 double [] max_font_heights;
232 protected virtual void process_legend (LegendProcessType process_type) {
233 var legend_x0 = 0.0, legend_y0 = 0.0;
234 var heights_idx = 0;
235 var leg_width_sum = 0.0;
236 var leg_height_sum = 0.0;
237 double max_font_h = 0.0;
238
239 // prepare
240 switch (process_type) {
241 case LegendProcessType.CALC:
242 legend_width = 0.0;
243 legend_height = 0.0;
244 max_font_heights = {};
245 heights_idx = 0;
246 break;
247 case LegendProcessType.DRAW:
248 draw_legend_rect(out legend_x0, out legend_y0);
249 break;
250 }
251
252 foreach (var s in series) {
253
254 // carry
255 switch (legend.position) {
256 case Legend.Position.TOP:
257 case Legend.Position.BOTTOM:
258 var ser_title_width = s.title.get_width(context) + legend_line_length;
259 if (leg_width_sum + (leg_width_sum == 0 ? 0 : legend_text_hspace) + ser_title_width > width) { // carry
260 leg_height_sum += max_font_h;
261 switch (process_type) {
262 case LegendProcessType.CALC:
263 max_font_heights += max_font_h;
264 legend_width = double.max(legend_width, leg_width_sum);
265 break;
266 case LegendProcessType.DRAW:
267 heights_idx++;
268 break;
269 }
270 leg_width_sum = 0.0;
271 max_font_h = 0;
272 }
273 break;
274 }
275
276 switch (process_type) {
277 case LegendProcessType.DRAW:
278 var x = legend_x0 + leg_width_sum + (leg_width_sum == 0.0 ? 0.0 : legend_text_hspace);
279 var y = legend_y0 + leg_height_sum + max_font_heights[heights_idx];
280
281 // series title
282 context.move_to (x + legend_line_length - s.title.get_x_bearing(context), y);
283 set_source_rgba (s.title.color);
284 show_text(s.title);
285
286 // series line style
287 context.move_to (x, y - s.title.get_height(context) / 2);
288 set_line_style(s.line_style);
289 context.rel_line_to (legend_line_length, 0);
290 context.stroke();
291 draw_marker_at_pos (s.marker_type, x + legend_line_length / 2, y - s.title.get_height(context) / 2);
292 break;
293 }
294
295 switch (legend.position) {
296 case Legend.Position.TOP:
297 case Legend.Position.BOTTOM:
298 var ser_title_width = s.title.get_width(context) + legend_line_length;
299 leg_width_sum += (leg_width_sum == 0 ? 0 : legend_text_hspace) + ser_title_width;
300 max_font_h = double.max (max_font_h, s.title.get_height(context)) + (leg_height_sum != 0 ? legend_text_vspace : 0);
301 break;
302
303 case Legend.Position.LEFT:
304 case Legend.Position.RIGHT:
305 switch (process_type) {
306 case LegendProcessType.CALC:
307 max_font_heights += s.title.get_height(context) + (leg_height_sum != 0 ? legend_text_vspace : 0);
308 legend_width = double.max (legend_width, s.title.get_width(context) + legend_line_length);
309 break;
310 case LegendProcessType.DRAW:
311 heights_idx++;
312 break;
313 }
314 leg_height_sum += s.title.get_height(context) + (leg_height_sum != 0 ? legend_text_vspace : 0);
315 break;
316 }
317 }
318
319 // TOP, BOTTOM
320 switch (legend.position) {
321 case Legend.Position.TOP:
322 case Legend.Position.BOTTOM:
323 if (leg_width_sum != 0) {
324 leg_height_sum += max_font_h;
325 switch (process_type) {
326 case LegendProcessType.CALC:
327 max_font_heights += max_font_h;
328 legend_width = double.max(legend_width, leg_width_sum);
329 break;
330 }
331 }
332 break;
333 }
334
335 switch (process_type) {
336 case LegendProcessType.CALC:
337 legend_height = leg_height_sum;
338 switch (legend.position) {
339 case Legend.Position.TOP:
340 cur_y_min += legend_height;
341 break;
342 case Legend.Position.BOTTOM:
343 cur_y_max -= legend_height;
344 break;
345 case Legend.Position.LEFT:
346 cur_x_min += legend_width;
347 break;
348 case Legend.Position.RIGHT:
349 cur_x_max -= legend_width;
350 break;
351 }
352 break;
353 }
354 }
355
356 protected virtual void draw_legend () {
357 process_legend (LegendProcessType.CALC);
358 process_legend (LegendProcessType.DRAW);
359 }
360
361 int axis_rec_npoints = 128;
362
363 protected virtual void calc_axis_rec_sizes (Axis axis, out double max_rec_width, out double max_rec_height, bool is_horizontal = true) {
364 max_rec_width = max_rec_height = 0;
365 for (var i = 0; i < axis_rec_npoints; ++i) {
366 Float128 x = axis.min + (axis.max - axis.min) / axis_rec_npoints * i;
367 switch (axis.type) {
368 case Axis.Type.NUMBERS:
369 var text = new Text (axis.format.printf((LongDouble)x) + (is_horizontal ? "_" : ""));
370 text.style = axis.font_style;
371 max_rec_width = double.max (max_rec_width, text.get_width(context));
372 max_rec_height = double.max (max_rec_height, text.get_height(context));
373 break;
374 case Axis.Type.DATE_TIME:
375 var dt = new DateTime.from_unix_utc((int64)x);
376 var text = new Text("");
377 var h = 0.0;
378 if (axis.date_format != "") {
379 text = new Text (dt.format(axis.date_format) + (is_horizontal ? "_" : ""));
380 text.style = axis.font_style;
381 max_rec_width = double.max (max_rec_width, text.get_width(context));
382 h = text.get_height(context);
383 }
384 if (axis.time_format != "") {
385 var dsec_str = ("%."+(axis.dsec_signs.to_string())+"f").printf(1.0/3.0).offset(1);
386 text = new Text (dt.format(axis.time_format) + (is_horizontal ? "_" : "") + dsec_str);
387 text.style = axis.font_style;
388 max_rec_width = double.max (max_rec_width, text.get_width(context));
389 h += text.get_height(context);
390 }
391 max_rec_height = double.max (max_rec_height, h);
392 break;
393 default:
394 break;
395 }
396 }
397 }
398
399 protected virtual Float128 calc_round_step (Float128 aver_step, bool date_time = false) {
400 Float128 step = 1.0;
401
402 if (aver_step > 1.0) {
403 if (date_time) while (step < aver_step) step *= 60;
404 if (date_time) while (step < aver_step) step *= 60;
405 if (date_time) while (step < aver_step) step *= 24;
406 while (step < aver_step) step *= 10;
407 if (step / 5 > aver_step) step /= 5;
408 while (step / 2 > aver_step) step /= 2;
409 } else if (aver_step > 0) {
410 //stdout.printf("aver_step = %Lf\n", aver_step);
411 while (step / 10 > aver_step) step /= 10;
412 if (step / 5 > aver_step) step /= 5;
413 while (step / 2 > aver_step) step /= 2;
414 }
415
416 return step;
417 }
418
419 public double plot_area_x_min = 0;
420 public double plot_area_x_max = 0;
421 public double plot_area_y_min = 0;
422 public double plot_area_y_max = 0;
423
424 bool common_x_axes = false;
425 bool common_y_axes = false;
426
427 bool are_intersect (double a_min, double a_max, double b_min, double b_max) {
428 if ( a_min < a_max <= b_min < b_max
429 || b_min < b_max <= a_min < a_max)
430 return false;
431 return true;
432 }
433
434 protected virtual void set_vertical_axes_titles () {
435 for (var i = 0; i < series.length; ++i) {
436 var s = series[i];
437 s.axis_y.title.style.orientation = FontOrient.VERTICAL;
438 }
439 }
440
441 protected virtual void calc_plot_area () {
442 plot_area_x_min = cur_x_min + legend.indent;
443 plot_area_x_max = cur_x_max - legend.indent;
444 plot_area_y_min = cur_y_min + legend.indent;
445 plot_area_y_max = cur_y_max - legend.indent;
446
447 // Check for common axes
448 common_x_axes = common_y_axes = true;
449 for (int si = series.length - 1; si >=0; --si) {
450 var s = series[si];
451 if ( s.axis_x.position != series[0].axis_x.position
452 || s.axis_x.min != series[0].axis_x.min
453 || s.axis_x.max != series[0].axis_x.max
454 || s.place.x_low != series[0].place.x_low
455 || s.place.x_high != series[0].place.x_high
456 || s.axis_x.type != series[0].axis_x.type)
457 common_x_axes = false;
458 if ( s.axis_y.position != series[0].axis_y.position
459 || s.axis_y.min != series[0].axis_y.min
460 || s.axis_y.max != series[0].axis_y.max
461 || s.place.y_low != series[0].place.y_low
462 || s.place.y_high != series[0].place.y_high)
463 common_y_axes = false;
464 }
465 if (series.length == 1) common_x_axes = common_y_axes = false;
466
467 // Join and calc X-axes
468 for (int si = series.length - 1, nskip = 0; si >=0; --si) {
469 if (nskip != 0) {--nskip; continue;}
470 var s = series[si];
471 double max_rec_width = 0; double max_rec_height = 0;
472 calc_axis_rec_sizes (s.axis_x, out max_rec_width, out max_rec_height, true);
473 var max_font_indent = s.axis_x.font_indent;
474 var max_axis_font_height = s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent;
475
476 // join relative x-axes with non-intersect places
477 for (int sj = si - 1; sj >= 0; --sj) {
478 var s2 = series[sj];
479 bool has_intersection = false;
480 for (int sk = si; sk > sj; --sk) {
481 var s3 = series[sk];
482 if (are_intersect(s2.place.x_low, s2.place.x_high, s3.place.x_low, s3.place.x_high)
483 || s2.axis_x.position != s3.axis_x.position
484 || s2.axis_x.type != s3.axis_x.type) {
485 has_intersection = true;
486 break;
487 }
488 }
489 if (!has_intersection) {
490 double tmp_max_rec_width = 0; double tmp_max_rec_height = 0;
491 calc_axis_rec_sizes (s2.axis_x, out tmp_max_rec_width, out tmp_max_rec_height, true);
492 max_rec_width = double.max (max_rec_width, tmp_max_rec_width);
493 max_rec_height = double.max (max_rec_height, tmp_max_rec_height);
494 max_font_indent = double.max (max_font_indent, s2.axis_x.font_indent);
495 max_axis_font_height = double.max (max_axis_font_height, s2.axis_x.title.text == "" ? 0 :
496 s2.axis_x.title.get_height(context) + s.axis_x.font_indent);
497 ++nskip;
498 } else {
499 break;
500 }
501 }
502
503 if (!common_x_axes || si == 0)
504 switch (s.axis_x.position) {
505 case Axis.Position.LOW: plot_area_y_max -= max_rec_height + max_font_indent + max_axis_font_height; break;
506 case Axis.Position.HIGH: plot_area_y_min += max_rec_height + max_font_indent + max_axis_font_height; break;
507 case Axis.Position.BOTH: break;
508 default: break;
509 }
510 }
511
512 // Join and calc Y-axes
513 for (int si = series.length - 1, nskip = 0; si >=0; --si) {
514 if (nskip != 0) {--nskip; continue;}
515 var s = series[si];
516 double max_rec_width = 0; double max_rec_height = 0;
517 calc_axis_rec_sizes (s.axis_y, out max_rec_width, out max_rec_height, false);
518 var max_font_indent = s.axis_y.font_indent;
519 var max_axis_font_width = s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent;
520
521 // join relative x-axes with non-intersect places
522 for (int sj = si - 1; sj >= 0; --sj) {
523 var s2 = series[sj];
524 bool has_intersection = false;
525 for (int sk = si; sk > sj; --sk) {
526 var s3 = series[sk];
527 if (are_intersect(s2.place.y_low, s2.place.y_high, s3.place.y_low, s3.place.y_high)
528 || s2.axis_y.position != s3.axis_y.position
529 || s2.axis_x.type != s3.axis_x.type) {
530 has_intersection = true;
531 break;
532 }
533 }
534 if (!has_intersection) {
535 double tmp_max_rec_width = 0; double tmp_max_rec_height = 0;
536 calc_axis_rec_sizes (s2.axis_y, out tmp_max_rec_width, out tmp_max_rec_height, false);
537 max_rec_width = double.max (max_rec_width, tmp_max_rec_width);
538 max_rec_height = double.max (max_rec_height, tmp_max_rec_height);
539 max_font_indent = double.max (max_font_indent, s2.axis_y.font_indent);
540 max_axis_font_width = double.max (max_axis_font_width, s2.axis_y.title.text == "" ? 0
541 : s2.axis_y.title.get_width(context) + s.axis_y.font_indent);
542 ++nskip;
543 } else {
544 break;
545 }
546 }
547
548 if (!common_y_axes || si == 0)
549 switch (s.axis_y.position) {
550 case Axis.Position.LOW: plot_area_x_min += max_rec_width + max_font_indent + max_axis_font_width; break;
551 case Axis.Position.HIGH: plot_area_x_max -= max_rec_width + max_font_indent + max_axis_font_width; break;
552 case Axis.Position.BOTH: break;
553 default: break;
554 }
555 }
556 }
557
558 bool point_belong (Float128 p, Float128 a, Float128 b) {
559 if (a > b) { Float128 tmp = a; a = b; b = tmp; }
560 if (a <= p <= b) return true;
561 return false;
562 }
563
564 protected virtual void draw_horizontal_axis () {
565 for (int si = series.length - 1, nskip = 0; si >=0; --si) {
566 if (common_x_axes && si != 0) continue;
567 var s = series[si];
568 // 1. Detect max record width/height by axis_rec_npoints equally selected points using format.
569 double max_rec_width, max_rec_height;
570 calc_axis_rec_sizes (s.axis_x, out max_rec_width, out max_rec_height, true);
571
572 // 2. Calculate maximal available number of records, take into account the space width.
573 long max_nrecs = (long) ((plot_area_x_max - plot_area_x_min) * (s.place.x_high - s.place.x_low) / max_rec_width);
574
575 // 3. Calculate grid step.
576 Float128 step = calc_round_step ((s.axis_x.max - s.axis_x.min) / max_nrecs, s.axis_x.type == Axis.Type.DATE_TIME);
577 if (step > s.axis_x.max - s.axis_x.min)
578 step = s.axis_x.max - s.axis_x.min;
579
580 // 4. Calculate x_min (s.axis_x.min / step, round, multiply on step, add step if < s.axis_x.min).
581 Float128 x_min = 0.0;
582 if (step >= 1) {
583 int64 x_min_nsteps = (int64) (s.axis_x.min / step);
584 x_min = x_min_nsteps * step;
585 } else {
586 int64 round_axis_x_min = (int64)s.axis_x.min;
587 int64 x_min_nsteps = (int64) ((s.axis_x.min - round_axis_x_min) / step);
588 x_min = round_axis_x_min + x_min_nsteps * step;
589 }
590 if (x_min < s.axis_x.min) x_min += step;
591
592 // 4.5. Draw Axis title
593 if (s.axis_x.title.text != "")
594 switch (s.axis_x.position) {
595 case Axis.Position.LOW:
596 var scr_x = plot_area_x_min + (plot_area_x_max - plot_area_x_min) * (s.place.x_low + s.place.x_high) / 2.0;
597 var scr_y = cur_y_max - s.axis_x.font_indent;
598 context.move_to(scr_x - s.axis_x.title.get_width(context) / 2.0, scr_y);
599 set_source_rgba(s.axis_x.color);
600 if (common_x_axes) set_source_rgba(Color(0,0,0,1));
601 show_text(s.axis_x.title);
602 break;
603 case Axis.Position.HIGH:
604 var scr_x = plot_area_x_min + (plot_area_x_max - plot_area_x_min) * (s.place.x_low + s.place.x_high) / 2.0;
605 var scr_y = cur_y_min + s.axis_x.font_indent + s.axis_x.title.get_height(context);
606 context.move_to(scr_x - s.axis_x.title.get_width(context) / 2.0, scr_y);
607 set_source_rgba(s.axis_x.color);
608 if (common_x_axes) set_source_rgba(Color(0,0,0,1));
609 show_text(s.axis_x.title);
610 break;
611 case Axis.Position.BOTH:
612 break;
613 }
614
615 // 5. Draw records, update cur_{x,y}_{min,max}.
616 for (Float128 x = x_min, x_max = s.axis_x.max; point_belong (x, x_min, x_max); x += step) {
617 if (common_x_axes) set_source_rgba(Color(0,0,0,1));
618 else set_source_rgba(s.axis_x.color);
619 string text = "", time_text = "";
620 switch (s.axis_x.type) {
621 case Axis.Type.NUMBERS:
622 text = s.axis_x.format.printf((LongDouble)x);
623 break;
624 case Axis.Type.DATE_TIME:
625 var dt = new DateTime.from_unix_utc((int64)x);
626 text = dt.format(s.axis_x.date_format);
627 var dsec_str =
628 ("%."+(s.axis_x.dsec_signs.to_string())+"Lf").printf((LongDouble)(x - (int64)x)).offset(1);
629 time_text = dt.format(s.axis_x.time_format) + dsec_str;
630 break;
631 default:
632 break;
633 }
634 var scr_x = plot_area_x_min + (plot_area_x_max - plot_area_x_min)
635 * (s.place.x_low + (s.place.x_high - s.place.x_low) / (s.axis_x.max - s.axis_x.min) * (x - s.axis_x.min));
636 var text_t = new Text(text, s.axis_x.font_style, s.axis_x.color);
637 switch (s.axis_x.position) {
638 case Axis.Position.LOW:
639 var print_y = cur_y_max - s.axis_x.font_indent - (s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent);
640 switch (s.axis_x.type) {
641 case Axis.Type.NUMBERS:
642 var print_x = scr_x - text_t.get_width(context) / 2.0 - text_t.get_x_bearing(context)
643 - text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min);
644 context.move_to (print_x, print_y);
645 show_text(text_t);
646 break;
647 case Axis.Type.DATE_TIME:
648 var print_x = scr_x - text_t.get_width(context) / 2.0 - text_t.get_x_bearing(context)
649 - text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min);
650 context.move_to (print_x, print_y);
651 if (s.axis_x.date_format != "") show_text(text_t);
652 var time_text_t = new Text(time_text, s.axis_x.font_style, s.axis_x.color);
653 print_x = scr_x - time_text_t.get_width(context) / 2.0 - time_text_t.get_x_bearing(context)
654 - time_text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min);
655 context.move_to (print_x, print_y - (s.axis_x.date_format == "" ? 0 : text_t.get_height(context) + s.axis_x.font_indent));
656 if (s.axis_x.time_format != "") show_text(time_text_t);
657 break;
658 default:
659 break;
660 }
661 // 6. Draw grid lines to the s.place.y_high.
662 var line_style = s.grid.line_style;
663 if (common_x_axes) line_style.color = Color(0, 0, 0, 0.5);
664 set_line_style(line_style);
665 double y = cur_y_max - max_rec_height - s.axis_x.font_indent - (s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent);
666 context.move_to (scr_x, y);
667 if (common_x_axes)
668 context.line_to (scr_x, plot_area_y_min);
669 else
670 context.line_to (scr_x, double.min (y, plot_area_y_max - (plot_area_y_max - plot_area_y_min) * s.place.y_high));
671 break;
672 case Axis.Position.HIGH:
673 var print_y = cur_y_min + max_rec_height + s.axis_x.font_indent + (s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent);
674 switch (s.axis_x.type) {
675 case Axis.Type.NUMBERS:
676 var print_x = scr_x - text_t.get_width(context) / 2.0 - text_t.get_x_bearing(context)
677 - text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min);
678 context.move_to (print_x, print_y);
679 show_text(text_t);
680 break;
681 case Axis.Type.DATE_TIME:
682 var print_x = scr_x - text_t.get_width(context) / 2.0 - text_t.get_x_bearing(context)
683 - text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min);
684 context.move_to (print_x, print_y);
685 if (s.axis_x.date_format != "") show_text(text_t);
686 var time_text_t = new Text(time_text, s.axis_x.font_style, s.axis_x.color);
687 print_x = scr_x - time_text_t.get_width(context) / 2.0 - time_text_t.get_x_bearing(context)
688 - time_text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min);
689 context.move_to (print_x, print_y - (s.axis_x.date_format == "" ? 0 : text_t.get_height(context) + s.axis_x.font_indent));
690 if (s.axis_x.time_format != "") show_text(time_text_t);
691 break;
692 default:
693 break;
694 }
695 // 6. Draw grid lines to the s.place.y_high.
696 var line_style = s.grid.line_style;
697 if (common_x_axes) line_style.color = Color(0, 0, 0, 0.5);
698 set_line_style(line_style);
699 double y = cur_y_min + max_rec_height + s.axis_x.font_indent + (s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent);
700 context.move_to (scr_x, y);
701 if (common_x_axes)
702 context.line_to (scr_x, plot_area_y_max);
703 else
704 context.line_to (scr_x, double.max (y, plot_area_y_max - (plot_area_y_max - plot_area_y_min) * s.place.y_low));
705 break;
706 case Axis.Position.BOTH:
707 break;
708 default:
709 break;
710 }
711 context.stroke ();
712 }
713
714 // join relative x-axes with non-intersect places
715 for (int sj = si - 1; sj >= 0; --sj) {
716 var s2 = series[sj];
717 bool has_intersection = false;
718 for (int sk = si; sk > sj; --sk) {
719 var s3 = series[sk];
720 if (are_intersect(s2.place.x_low, s2.place.x_high, s3.place.x_low, s3.place.x_high)
721 || s2.axis_x.position != s3.axis_x.position
722 || s2.axis_x.type != s3.axis_x.type) {
723 has_intersection = true;
724 break;
725 }
726 }
727 if (!has_intersection) {
728 ++nskip;
729 } else {
730 break;
731 }
732 }
733
734 if (nskip != 0) {--nskip; continue;}
735
736 switch (s.axis_x.position) {
737 case Axis.Position.LOW:
738 cur_y_max -= max_rec_height + s.axis_x.font_indent
739 + (s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent);
740 break;
741 case Axis.Position.HIGH:
742 cur_y_min += max_rec_height + s.axis_x.font_indent
743 + (s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent);
744 break;
745 case Axis.Position.BOTH:
746 break;
747 default: break;
748 }
749 }
750 }
751
752 protected virtual void draw_vertical_axis () {
753 for (int si = series.length - 1, nskip = 0; si >=0; --si) {
754 if (common_y_axes && si != 0) continue;
755 var s = series[si];
756 // 1. Detect max record width/height by axis_rec_npoints equally selected points using format.
757 double max_rec_width, max_rec_height;
758 calc_axis_rec_sizes (s.axis_y, out max_rec_width, out max_rec_height, false);
759
760 // 2. Calculate maximal available number of records, take into account the space width.
761 long max_nrecs = (long) ((plot_area_y_max - plot_area_y_min) * (s.place.y_high - s.place.y_low) / max_rec_height);
762
763 // 3. Calculate grid step.
764 Float128 step = calc_round_step ((s.axis_y.max - s.axis_y.min) / max_nrecs);
765 if (step > s.axis_y.max - s.axis_y.min)
766 step = s.axis_y.max - s.axis_y.min;
767
768 // 4. Calculate y_min (s.axis_y.min / step, round, multiply on step, add step if < s.axis_y.min).
769 Float128 y_min = 0.0;
770 if (step >= 1) {
771 int64 y_min_nsteps = (int64) (s.axis_y.min / step);
772 y_min = y_min_nsteps * step;
773 } else {
774 int64 round_axis_y_min = (int64)s.axis_y.min;
775 int64 y_min_nsteps = (int64) ((s.axis_y.min - round_axis_y_min) / step);
776 y_min = round_axis_y_min + y_min_nsteps * step;
777 }
778 if (y_min < s.axis_y.min) y_min += step;
779
780 // 4.5. Draw Axis title
781 if (s.axis_y.title.text != "")
782 switch (s.axis_y.position) {
783 case Axis.Position.LOW:
784 var scr_y = plot_area_y_max - (plot_area_y_max - plot_area_y_min) * (s.place.y_low + s.place.y_high) / 2.0;
785 var scr_x = cur_x_min + s.axis_y.font_indent + s.axis_y.title.get_width(context);
786 context.move_to(scr_x, scr_y + s.axis_y.title.get_height(context) / 2.0);
787 set_source_rgba(s.axis_y.color);
788 if (common_y_axes) set_source_rgba(Color(0,0,0,1));
789 show_text(s.axis_y.title);
790 break;
791 case Axis.Position.HIGH:
792 var scr_y = plot_area_y_max - (plot_area_y_max - plot_area_y_min) * (s.place.y_low + s.place.y_high) / 2.0;
793 var scr_x = cur_x_max - s.axis_y.font_indent;
794 context.move_to(scr_x, scr_y + s.axis_y.title.get_height(context) / 2.0);
795 set_source_rgba(s.axis_y.color);
796 if (common_y_axes) set_source_rgba(Color(0,0,0,1));
797 show_text(s.axis_y.title);
798 break;
799 case Axis.Position.BOTH:
800 break;
801 }
802
803 // 5. Draw records, update cur_{x,y}_{min,max}.
804 for (Float128 y = y_min, y_max = s.axis_y.max; point_belong (y, y_min, y_max); y += step) {
805 if (common_y_axes) set_source_rgba(Color(0,0,0,1));
806 else set_source_rgba(s.axis_y.color);
807 var text = s.axis_y.format.printf((LongDouble)y);
808 var scr_y = plot_area_y_max - (plot_area_y_max - plot_area_y_min)
809 * (s.place.y_low + (s.place.y_high - s.place.y_low) / (s.axis_y.max - s.axis_y.min) * (y - s.axis_y.min));
810 var text_t = new Text(text, s.axis_y.font_style, s.axis_y.color);
811 switch (s.axis_y.position) {
812 case Axis.Position.LOW:
813 context.move_to (cur_x_min + max_rec_width - (new Text(text)).get_width(context) + s.axis_y.font_indent - text_t.get_x_bearing(context)
814 + (s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent),
815 scr_y + (new Text(text)).get_height(context) / 2.0
816 + text_t.get_height(context) * (y - (s.axis_y.min + s.axis_y.max) / 2.0) / (s.axis_y.max - s.axis_y.min));
817 show_text(text_t);
818 // 6. Draw grid lines to the s.place.y_high.
819 var line_style = s.grid.line_style;
820 if (common_y_axes) line_style.color = Color(0, 0, 0, 0.5);
821 set_line_style(line_style);
822 double x = cur_x_min + max_rec_width + s.axis_y.font_indent + (s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent);
823 context.move_to (x, scr_y);
824 if (common_y_axes)
825 context.line_to (plot_area_x_max, scr_y);
826 else
827 context.line_to (double.max (x, plot_area_x_min + (plot_area_x_max - plot_area_x_min) * s.place.x_high), scr_y);
828 break;
829 case Axis.Position.HIGH:
830 context.move_to (cur_x_max - (new Text(text)).get_width(context) - s.axis_y.font_indent - text_t.get_x_bearing(context)
831 - (s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent),
832 scr_y + (new Text(text)).get_height(context) / 2.0
833 + text_t.get_height(context) * (y - (s.axis_y.min + s.axis_y.max) / 2.0) / (s.axis_y.max - s.axis_y.min));
834 show_text(text_t);
835 // 6. Draw grid lines to the s.place.y_high.
836 var line_style = s.grid.line_style;
837 if (common_y_axes) line_style.color = Color(0, 0, 0, 0.5);
838 set_line_style(line_style);
839 double x = cur_x_max - max_rec_width - s.axis_y.font_indent - (s.axis_y.title.text == "" ? 0 :s.axis_y.title.get_width(context) + s.axis_y.font_indent);
840 context.move_to (x, scr_y);
841 if (common_y_axes)
842 context.line_to (plot_area_x_min, scr_y);
843 else
844 context.line_to (double.min (x, plot_area_x_min + (plot_area_x_max - plot_area_x_min) * s.place.x_low), scr_y);
845 break;
846 case Axis.Position.BOTH:
847 break;
848 default:
849 break;
850 }
851 context.stroke ();
852 }
853
854 // join relative x-axes with non-intersect places
855 for (int sj = si - 1; sj >= 0; --sj) {
856 var s2 = series[sj];
857 bool has_intersection = false;
858 for (int sk = si; sk > sj; --sk) {
859 var s3 = series[sk];
860 if (are_intersect(s2.place.y_low, s2.place.y_high, s3.place.y_low, s3.place.y_high)
861 || s2.axis_y.position != s3.axis_y.position) {
862 has_intersection = true;
863 break;
864 }
865 }
866 if (!has_intersection) {
867 ++nskip;
868 } else {
869 break;
870 }
871 }
872
873 if (nskip != 0) {--nskip; continue;}
874
875
876 switch (s.axis_y.position) {
877 case Axis.Position.LOW:
878 cur_x_min += max_rec_width + s.axis_y.font_indent
879 + (s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent); break;
880 case Axis.Position.HIGH:
881 cur_x_max -= max_rec_width + s.axis_y.font_indent
882 + (s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent); break;
883 case Axis.Position.BOTH:
884 break;
885 default: break;
886 }
887 }
888 }
889
890 protected virtual void draw_plot_area_border () {
891 set_source_rgba (border_color);
892 context.set_dash(null, 0);
893 context.move_to (plot_area_x_min, plot_area_y_min);
894 context.line_to (plot_area_x_min, plot_area_y_max);
895 context.line_to (plot_area_x_max, plot_area_y_max);
896 context.line_to (plot_area_x_max, plot_area_y_min);
897 context.line_to (plot_area_x_min, plot_area_y_min);
898 context.stroke ();
899 }
900
901 protected virtual double get_scr_x (Series s, Float128 x) {
902 return plot_area_x_min + (plot_area_x_max - plot_area_x_min) * (s.place.x_low + (x - s.axis_x.min)
903 / (s.axis_x.max - s.axis_x.min) * (s.place.x_high - s.place.x_low));
904 }
905
906 protected virtual double get_scr_y (Series s, Float128 y) {
907 return plot_area_y_max - (plot_area_y_max - plot_area_y_min) * (s.place.y_low + (y - s.axis_y.min)
908 / (s.axis_y.max - s.axis_y.min) * (s.place.y_high - s.place.y_low));
909 }
910
911 delegate int PointComparator(Point a, Point b);
912 void sort_points(Point[] points, PointComparator compare) {
913 for(var i = 0; i < points.length; ++i) {
914 for(var j = i + 1; j < points.length; ++j) {
915 if(compare(points[i], points[j]) > 0) {
916 var tmp = points[i];
917 points[i] = points[j];
918 points[j] = tmp;
919 }
920 }
921 }
922 }
923
924 protected virtual void draw_series () {
925 for (int si = 0; si < series.length; ++si) {
926 var s = series[si];
927 if (s.points.length == 0) continue;
928 var points = s.points.copy();
929 switch(s.sort) {
930 case Series.Sort.BY_X:
931 sort_points(points, (a, b) => {
932 if (a.x < b.x) return -1;
933 if (a.x > b.x) return 1;
934 return 0;
935 });
936 break;
937 case Series.Sort.BY_Y:
938 sort_points(points, (a, b) => {
939 if (a.y < b.y) return -1;
940 if (a.y > b.y) return 1;
941 return 0;
942 });
943 break;
944 }
945 set_line_style(s.line_style);
946 // move to s.points[0]
947 context.move_to (get_scr_x(s, points[0].x), get_scr_y(s, points[0].y));
948 // draw series line
949 for (int i = 1; i < points.length; ++i)
950 context.line_to (get_scr_x(s, points[i].x), get_scr_y(s, points[i].y));
951 context.stroke();
952 for (int i = 0; i < points.length; ++i)
953 draw_marker_at_pos(s.marker_type, get_scr_x(s, points[i].x), get_scr_y(s, points[i].y));
954 }
955 }
956
957 // TODO:
958 protected virtual void draw_cursors () {
959 }
960 }
961 }
1 namespace Gtk.CairoChart {
2 public struct Color {
3 double red;
4 double green;
5 double blue;
6 double alpha;
7
8 public Color (double red = 0.0, double green = 0.0, double blue = 0.0, double alpha = 1.0) {
9 this.red = red; this.green = green; this.blue = blue; this.alpha = alpha;
10 }
11 }
12 }
1 using Cairo;
2
3 namespace Gtk.CairoChart {
4
5 public struct Color {
6 double red;
7 double green;
8 double blue;
9 double alpha;
10
11 public Color (double red = 0.0, double green = 0.0, double blue = 0.0, double alpha = 1.0) {
12 this.red = red; this.green = green; this.blue = blue; this.alpha = alpha;
13 }
14 }
15
16 public enum FontOrient {
17 HORIZONTAL = 0,
18 VERTICAL
19 }
20 public struct FontStyle {
21 string family;
22 FontSlant slant;
23 FontWeight weight;
24
25 FontOrient orientation;
26 double size;
27
28 public FontStyle (string family = "Sans",
29 FontSlant slant = Cairo.FontSlant.NORMAL,
30 FontWeight weight = Cairo.FontWeight.NORMAL,
31 double size = 10) {
32 this.family = family;
33 this.slant = slant;
34 this.weight = weight;
35 this.size = size;
36 }
37 }
38
39 public struct LineStyle {
40 double width;
41 LineJoin line_join;
42 LineCap line_cap;
43 double[]? dashes;
44 double dash_offset;
45 Color color;
46
47 public LineStyle (double width = 1,
48 LineJoin line_join = Cairo.LineJoin.MITER,
49 LineCap line_cap = Cairo.LineCap.ROUND,
50 double[]? dashes = null, double dash_offset = 0,
51 Color color = Color()) {
52 this.width = width;
53 this.line_join = line_join;
54 this.line_cap = line_cap;
55 this.dashes = dashes;
56 this.dash_offset = dash_offset;
57 this.color = color;
58 }
59 }
60
61 [Compact]
62 public class Text {
63 public string text = "";
64 public FontStyle style = FontStyle ();
65 public Color color = Color();
66
67 TextExtents get_extents (Cairo.Context context) {
68 context.select_font_face (style.family,
69 style.slant,
70 style.weight);
71 context.set_font_size (style.size);
72 TextExtents extents;
73 context.text_extents (text, out extents);
74 return extents;
75 }
76
77 public double get_width (Cairo.Context context) {
78 var extents = get_extents (context);
79 if (style.orientation == FontOrient.HORIZONTAL)
80 return extents.width;
81 else
82 return extents.height;
83 }
84
85 public double get_height (Cairo.Context context) {
86 var extents = get_extents (context);
87 if (style.orientation == FontOrient.HORIZONTAL)
88 return extents.height;
89 else
90 return extents.width;
91 }
92
93 public double get_x_bearing (Cairo.Context context) {
94 var extents = get_extents (context);
95 if (style.orientation == FontOrient.HORIZONTAL)
96 return extents.x_bearing;
97 else
98 return extents.y_bearing;
99 }
100
101 public Text (string text = "",
102 FontStyle style = FontStyle(),
103 Color color = Color()) {
104 this.text = text;
105 this.style = style;
106 this.color = color;
107 }
108
109 public Text.by_instance (Text text) {
110 this.text = text.text;
111 this.style = text.style;
112 this.color = text.color;
113 }
114 }
115 }
1 namespace Gtk.CairoChart {
2 public enum FontOrient {
3 HORIZONTAL = 0,
4 VERTICAL
5 }
6
7 public struct FontStyle {
8 string family;
9 Cairo.FontSlant slant;
10 Cairo.FontWeight weight;
11
12 FontOrient orientation;
13 double size;
14
15 public FontStyle (string family = "Sans",
16 Cairo.FontSlant slant = Cairo.FontSlant.NORMAL,
17 Cairo.FontWeight weight = Cairo.FontWeight.NORMAL,
18 double size = 10) {
19 this.family = family;
20 this.slant = slant;
21 this.weight = weight;
22 this.size = size;
23 }
24 }
25 }
1 namespace Gtk.CairoChart {
2 public class Grid {
3 /*public enum GridType {
4 PRICK_LINE = 0, // default
5 LINE
6 }*/
7 public Color color = Color (0, 0, 0, 0.1);
8
9 public LineStyle line_style = new LineStyle ();
10
11 public Grid () {
12 line_style.dashes = {2, 3};
13 }
14 }
15 }
1 // даты/время: сетка для малых интервалов (< нескольких секунд)
2 using Cairo;
3
4 namespace Gtk.CairoChart {
5
6 public class Chart {
7
8 protected double width = 0;
9 protected double height = 0;
10
11 public Cairo.Context context = null;
12
13 public Color bg_color;
14 public bool show_legend = true;
15 public Text title = new Text ("Cairo Chart");
16 public Color border_color = Color(0, 0, 0, 0.3);
17
18 public class Legend {
19 public enum Position {
20 TOP = 0, // default
21 LEFT,
22 RIGHT,
23 BOTTOM
24 }
25 public Position position = Position.TOP;
26 public FontStyle font_style = FontStyle();
27 public Color bg_color = Color(1, 1, 1);
28 public LineStyle border_style = new LineStyle ();
29 public double indent = 5;
30
31 public Legend () {
32 border_style.color = Color (0, 0, 0, 0.3);
33 }
34 }
35
36 public Legend legend = new Legend ();
37
38 public Series[] series = {};
39
40 protected LineStyle selection_style = new LineStyle ();
41
42 public Chart () {
43 bg_color = Color (1, 1, 1);
44 }
45
46 protected double cur_x_min = 0.0;
47 protected double cur_x_max = 0.0;
48 protected double cur_y_min = 0.0;
49 protected double cur_y_max = 0.0;
50
51 public virtual void check_cur_values () {
52 if (cur_x_min > cur_x_max)
53 cur_x_max = cur_x_min;
54 if (cur_y_min > cur_y_max)
55 cur_y_max = cur_y_min;
56 }
57
58 public virtual bool draw () {
59
60 update_size ();
61
62 draw_background ();
63
64 cur_x_min = cur_y_min = 0.0;
65 cur_x_max = width;
66 cur_y_max = height;
67
68 draw_chart_title ();
69 check_cur_values ();
70
71 draw_legend ();
72 check_cur_values ();
73
74 set_vertical_axes_titles ();
75
76 calc_plot_area (); // Calculate plot area
77
78 draw_horizontal_axis ();
79 check_cur_values ();
80
81 draw_vertical_axis ();
82 check_cur_values ();
83
84 draw_plot_area_border ();
85 check_cur_values ();
86
87 draw_series ();
88 check_cur_values ();
89
90 draw_cursors ();
91 check_cur_values ();
92
93 return true;
94 }
95
96 protected virtual void update_size () {
97 if (context != null) {
98 width = context.copy_clip_rectangle_list().rectangles[0].width;
99 height = context.copy_clip_rectangle_list().rectangles[0].height;
100 }
101 }
102
103 protected virtual void set_source_rgba (Color color) {
104 context.set_source_rgba (color.red, color.green, color.blue, color.alpha);
105 }
106
107 protected virtual void draw_background () {
108 if (context != null) {
109 set_source_rgba (bg_color);
110 context.paint();
111 set_source_rgba (Color (0, 0, 0, 1));
112 }
113 }
114
115 // TODO:
116 public virtual bool button_release_event (Gdk.EventButton event) {
117 //stdout.puts ("button_release_event\n");
118 return true;
119 }
120
121 // TODO:
122 public virtual bool button_press_event (Gdk.EventButton event) {
123 //stdout.puts ("button_press_event\n");
124 return true;
125 }
126
127 // TODO:
128 public virtual bool motion_notify_event (Gdk.EventMotion event) {
129 //stdout.puts ("motion_notify_event\n");
130 return true;
131 }
132
133 // TODO:
134 public virtual bool scroll_notify_event (Gdk.EventScroll event) {
135 //stdout.puts ("scroll_notify_event\n");
136 return true;
137 }
138
139 protected double title_width = 0.0;
140 protected double title_height = 0.0;
141
142 public double title_vindent = 4;
143
144 protected virtual void show_text(Text text) {
145 context.select_font_face(text.style.family,
146 text.style.slant,
147 text.style.weight);
148 context.set_font_size(text.style.size);
149 if (text.style.orientation == FontOrient.VERTICAL) {
150 context.rotate(- Math.PI / 2.0);
151 context.show_text(text.text);
152 context.rotate(Math.PI / 2.0);
153 } else {
154 context.show_text(text.text);
155 }
156 }
157
158 protected virtual void draw_chart_title () {
159 title_width = title.get_width (context);
160 title_height = title.get_height (context) + (legend.position == Legend.Position.TOP ? title_vindent * 2 : title_vindent);
161 cur_y_min += title_height;
162 set_source_rgba(title.color);
163 context.move_to (width/2 - title_width/2 - title.get_x_bearing(context), title.get_height(context) + title_vindent);
164 show_text(title);
165 }
166
167 protected double legend_width = 0;
168 protected double legend_height = 0;
169
170 protected enum LegendProcessType {
171 CALC = 0, // default
172 DRAW
173 }
174
175 protected virtual void set_line_style (LineStyle style) {
176 set_source_rgba(style.color);
177 context.set_line_join(style.line_join);
178 context.set_line_cap(style.line_cap);
179 context.set_line_width(style.width);
180 context.set_dash(style.dashes, style.dash_offset);
181 }
182
183 protected virtual void draw_legend_rect (out double x0, out double y0) {
184 x0 = y0 = 0.0;
185 if (context != null) {
186 switch (legend.position) {
187 case Legend.Position.TOP:
188 x0 = (width - legend_width) / 2;
189 y0 = title_height;
190 break;
191
192 case Legend.Position.BOTTOM:
193 x0 = (width - legend_width) / 2;
194 y0 = height - legend_height;
195 break;
196
197 case Legend.Position.LEFT:
198 x0 = 0;
199 y0 = (height - legend_height) / 2;
200 break;
201
202 case Legend.Position.RIGHT:
203 x0 = width - legend_width;
204 y0 = (height - legend_height) / 2;
205 break;
206
207 default:
208 break;
209 }
210 set_source_rgba(legend.bg_color);
211 context.rectangle (x0, y0, legend_width, legend_height);
212 context.fill();
213 set_line_style(legend.border_style);
214 context.move_to (x0, y0);
215 context.rel_line_to (legend_width, 0);
216 context.rel_line_to (0, legend_height);
217 context.rel_line_to (-legend_width, 0);
218 context.rel_line_to (0, -legend_height);
219 context.stroke ();
220 }
221 }
222
223 public double legend_line_length = 30.0;
224 public double legend_text_hspace = 10.0;
225 public double legend_text_vspace = 2.0;
226 public double marker_size = 8.0;
227
228 protected virtual void draw_marker_at_pos (Series.MarkerType marker_type,
229 double x, double y) {
230 context.move_to (x, y);
231 switch (marker_type) {
232 case Series.MarkerType.SQUARE:
233 context.rectangle (x - marker_size / 2, y - marker_size / 2,
234 marker_size, marker_size);
235 context.fill();
236 break;
237
238 case Series.MarkerType.CIRCLE:
239 context.arc (x, y, marker_size / 2, 0, 2*Math.PI);
240 context.fill();
241 break;
242
243 case Series.MarkerType.TRIANGLE:
244 context.move_to (x - marker_size / 2, y - marker_size / 2);
245 context.line_to (x + marker_size / 2, y - marker_size / 2);
246 context.line_to (x, y + marker_size / 2);
247 context.line_to (x - marker_size / 2, y - marker_size / 2);
248 context.fill();
249 break;
250
251 case Series.MarkerType.PRICLE_SQUARE:
252 context.rectangle (x - marker_size / 2, y - marker_size / 2,
253 marker_size, marker_size);
254 context.stroke();
255 break;
256
257 case Series.MarkerType.PRICLE_CIRCLE:
258 context.arc (x, y, marker_size / 2, 0, 2*Math.PI);
259 context.stroke();
260 break;
261
262 case Series.MarkerType.PRICLE_TRIANGLE:
263 context.move_to (x - marker_size / 2, y - marker_size / 2);
264 context.line_to (x + marker_size / 2, y - marker_size / 2);
265 context.line_to (x, y + marker_size / 2);
266 context.line_to (x - marker_size / 2, y - marker_size / 2);
267 context.stroke();
268 break;
269
270 case Series.MarkerType.NONE:
271 default:
272 break;
273 }
274 }
275
276 double [] max_font_heights;
277 protected virtual void process_legend (LegendProcessType process_type) {
278 var legend_x0 = 0.0, legend_y0 = 0.0;
279 var heights_idx = 0;
280 var leg_width_sum = 0.0;
281 var leg_height_sum = 0.0;
282 double max_font_h = 0.0;
283
284 // prepare
285 switch (process_type) {
286 case LegendProcessType.CALC:
287 legend_width = 0.0;
288 legend_height = 0.0;
289 max_font_heights = {};
290 heights_idx = 0;
291 break;
292 case LegendProcessType.DRAW:
293 draw_legend_rect(out legend_x0, out legend_y0);
294 break;
295 }
296
297 foreach (var s in series) {
298
299 // carry
300 switch (legend.position) {
301 case Legend.Position.TOP:
302 case Legend.Position.BOTTOM:
303 var ser_title_width = s.title.get_width(context) + legend_line_length;
304 if (leg_width_sum + (leg_width_sum == 0 ? 0 : legend_text_hspace) + ser_title_width > width) { // carry
305 leg_height_sum += max_font_h;
306 switch (process_type) {
307 case LegendProcessType.CALC:
308 max_font_heights += max_font_h;
309 legend_width = double.max(legend_width, leg_width_sum);
310 break;
311 case LegendProcessType.DRAW:
312 heights_idx++;
313 break;
314 }
315 leg_width_sum = 0.0;
316 max_font_h = 0;
317 }
318 break;
319 }
320
321 switch (process_type) {
322 case LegendProcessType.DRAW:
323 var x = legend_x0 + leg_width_sum + (leg_width_sum == 0.0 ? 0.0 : legend_text_hspace);
324 var y = legend_y0 + leg_height_sum + max_font_heights[heights_idx];
325
326 // series title
327 context.move_to (x + legend_line_length - s.title.get_x_bearing(context), y);
328 set_source_rgba (s.title.color);
329 show_text(s.title);
330
331 // series line style
332 context.move_to (x, y - s.title.get_height(context) / 2);
333 set_line_style(s.line_style);
334 context.rel_line_to (legend_line_length, 0);
335 context.stroke();
336 draw_marker_at_pos (s.marker_type, x + legend_line_length / 2, y - s.title.get_height(context) / 2);
337 break;
338 }
339
340 switch (legend.position) {
341 case Legend.Position.TOP:
342 case Legend.Position.BOTTOM:
343 var ser_title_width = s.title.get_width(context) + legend_line_length;
344 leg_width_sum += (leg_width_sum == 0 ? 0 : legend_text_hspace) + ser_title_width;
345 max_font_h = double.max (max_font_h, s.title.get_height(context)) + (leg_height_sum != 0 ? legend_text_vspace : 0);
346 break;
347
348 case Legend.Position.LEFT:
349 case Legend.Position.RIGHT:
350 switch (process_type) {
351 case LegendProcessType.CALC:
352 max_font_heights += s.title.get_height(context) + (leg_height_sum != 0 ? legend_text_vspace : 0);
353 legend_width = double.max (legend_width, s.title.get_width(context) + legend_line_length);
354 break;
355 case LegendProcessType.DRAW:
356 heights_idx++;
357 break;
358 }
359 leg_height_sum += s.title.get_height(context) + (leg_height_sum != 0 ? legend_text_vspace : 0);
360 break;
361 }
362 }
363
364 // TOP, BOTTOM
365 switch (legend.position) {
366 case Legend.Position.TOP:
367 case Legend.Position.BOTTOM:
368 if (leg_width_sum != 0) {
369 leg_height_sum += max_font_h;
370 switch (process_type) {
371 case LegendProcessType.CALC:
372 max_font_heights += max_font_h;
373 legend_width = double.max(legend_width, leg_width_sum);
374 break;
375 }
376 }
377 break;
378 }
379
380 switch (process_type) {
381 case LegendProcessType.CALC:
382 legend_height = leg_height_sum;
383 switch (legend.position) {
384 case Legend.Position.TOP:
385 cur_y_min += legend_height;
386 break;
387 case Legend.Position.BOTTOM:
388 cur_y_max -= legend_height;
389 break;
390 case Legend.Position.LEFT:
391 cur_x_min += legend_width;
392 break;
393 case Legend.Position.RIGHT:
394 cur_x_max -= legend_width;
395 break;
396 }
397 break;
398 }
399 }
400
401 protected virtual void draw_legend () {
402 process_legend (LegendProcessType.CALC);
403 process_legend (LegendProcessType.DRAW);
404 }
405
406 int axis_rec_npoints = 128;
407
408 protected virtual void calc_axis_rec_sizes (Series.Axis axis, out double max_rec_width, out double max_rec_height, bool is_horizontal = true) {
409 max_rec_width = max_rec_height = 0;
410 for (var i = 0; i < axis_rec_npoints; ++i) {
411 Float128 x = axis.min + (axis.max - axis.min) / axis_rec_npoints * i;
412 switch (axis.type) {
413 case Series.Axis.Type.NUMBERS:
414 var text = new Text (axis.format.printf((LongDouble)x) + (is_horizontal ? "_" : ""));
415 text.style = axis.font_style;
416 max_rec_width = double.max (max_rec_width, text.get_width(context));
417 max_rec_height = double.max (max_rec_height, text.get_height(context));
418 break;
419 case Series.Axis.Type.DATE_TIME:
420 var dt = new DateTime.from_unix_utc((int64)x);
421 var text = new Text("");
422 var h = 0.0;
423 if (axis.date_format != "") {
424 text = new Text (dt.format(axis.date_format) + (is_horizontal ? "_" : ""));
425 text.style = axis.font_style;
426 max_rec_width = double.max (max_rec_width, text.get_width(context));
427 h = text.get_height(context);
428 }
429 if (axis.time_format != "") {
430 var dsec_str = ("%."+(axis.dsec_signs.to_string())+"f").printf(1.0/3.0).offset(1);
431 text = new Text (dt.format(axis.time_format) + (is_horizontal ? "_" : "") + dsec_str);
432 text.style = axis.font_style;
433 max_rec_width = double.max (max_rec_width, text.get_width(context));
434 h += text.get_height(context);
435 }
436 max_rec_height = double.max (max_rec_height, h);
437 break;
438 default:
439 break;
440 }
441 }
442 }
443
444 protected virtual Float128 calc_round_step (Float128 aver_step, bool date_time = false) {
445 Float128 step = 1.0;
446
447 if (aver_step > 1.0) {
448 if (date_time) while (step < aver_step) step *= 60;
449 if (date_time) while (step < aver_step) step *= 60;
450 if (date_time) while (step < aver_step) step *= 24;
451 while (step < aver_step) step *= 10;
452 if (step / 5 > aver_step) step /= 5;
453 while (step / 2 > aver_step) step /= 2;
454 } else if (aver_step > 0) {
455 //stdout.printf("aver_step = %Lf\n", aver_step);
456 while (step / 10 > aver_step) step /= 10;
457 if (step / 5 > aver_step) step /= 5;
458 while (step / 2 > aver_step) step /= 2;
459 }
460
461 return step;
462 }
463
464 public double plot_area_x_min = 0;
465 public double plot_area_x_max = 0;
466 public double plot_area_y_min = 0;
467 public double plot_area_y_max = 0;
468
469 bool common_x_axes = false;
470 bool common_y_axes = false;
471
472 bool are_intersect (double a_min, double a_max, double b_min, double b_max) {
473 if ( a_min < a_max <= b_min < b_max
474 || b_min < b_max <= a_min < a_max)
475 return false;
476 return true;
477 }
478
479 protected virtual void set_vertical_axes_titles () {
480 for (var i = 0; i < series.length; ++i) {
481 var s = series[i];
482 s.axis_y.title.style.orientation = FontOrient.VERTICAL;
483 }
484 }
485
486 protected virtual void calc_plot_area () {
487 plot_area_x_min = cur_x_min + legend.indent;
488 plot_area_x_max = cur_x_max - legend.indent;
489 plot_area_y_min = cur_y_min + legend.indent;
490 plot_area_y_max = cur_y_max - legend.indent;
491
492 // Check for common axes
493 common_x_axes = common_y_axes = true;
494 for (int si = series.length - 1; si >=0; --si) {
495 var s = series[si];
496 if ( s.axis_x.position != series[0].axis_x.position
497 || s.axis_x.min != series[0].axis_x.min
498 || s.axis_x.max != series[0].axis_x.max
499 || s.place.x_low != series[0].place.x_low
500 || s.place.x_high != series[0].place.x_high
501 || s.axis_x.type != series[0].axis_x.type)
502 common_x_axes = false;
503 if ( s.axis_y.position != series[0].axis_y.position
504 || s.axis_y.min != series[0].axis_y.min
505 || s.axis_y.max != series[0].axis_y.max
506 || s.place.y_low != series[0].place.y_low
507 || s.place.y_high != series[0].place.y_high)
508 common_y_axes = false;
509 }
510 if (series.length == 1) common_x_axes = common_y_axes = false;
511
512 // Join and calc X-axes
513 for (int si = series.length - 1, nskip = 0; si >=0; --si) {
514 if (nskip != 0) {--nskip; continue;}
515 var s = series[si];
516 double max_rec_width = 0; double max_rec_height = 0;
517 calc_axis_rec_sizes (s.axis_x, out max_rec_width, out max_rec_height, true);
518 var max_font_indent = s.axis_x.font_indent;
519 var max_axis_font_height = s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent;
520
521 // join relative x-axes with non-intersect places
522 for (int sj = si - 1; sj >= 0; --sj) {
523 var s2 = series[sj];
524 bool has_intersection = false;
525 for (int sk = si; sk > sj; --sk) {
526 var s3 = series[sk];
527 if (are_intersect(s2.place.x_low, s2.place.x_high, s3.place.x_low, s3.place.x_high)
528 || s2.axis_x.position != s3.axis_x.position
529 || s2.axis_x.type != s3.axis_x.type) {
530 has_intersection = true;
531 break;
532 }
533 }
534 if (!has_intersection) {
535 double tmp_max_rec_width = 0; double tmp_max_rec_height = 0;
536 calc_axis_rec_sizes (s2.axis_x, out tmp_max_rec_width, out tmp_max_rec_height, true);
537 max_rec_width = double.max (max_rec_width, tmp_max_rec_width);
538 max_rec_height = double.max (max_rec_height, tmp_max_rec_height);
539 max_font_indent = double.max (max_font_indent, s2.axis_x.font_indent);
540 max_axis_font_height = double.max (max_axis_font_height, s2.axis_x.title.text == "" ? 0 :
541 s2.axis_x.title.get_height(context) + s.axis_x.font_indent);
542 ++nskip;
543 } else {
544 break;
545 }
546 }
547
548 if (!common_x_axes || si == 0)
549 switch (s.axis_x.position) {
550 case Series.Axis.Position.LOW: plot_area_y_max -= max_rec_height + max_font_indent + max_axis_font_height; break;
551 case Series.Axis.Position.HIGH: plot_area_y_min += max_rec_height + max_font_indent + max_axis_font_height; break;
552 case Series.Axis.Position.BOTH: break;
553 default: break;
554 }
555 }
556
557 // Join and calc Y-axes
558 for (int si = series.length - 1, nskip = 0; si >=0; --si) {
559 if (nskip != 0) {--nskip; continue;}
560 var s = series[si];
561 double max_rec_width = 0; double max_rec_height = 0;
562 calc_axis_rec_sizes (s.axis_y, out max_rec_width, out max_rec_height, false);
563 var max_font_indent = s.axis_y.font_indent;
564 var max_axis_font_width = s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent;
565
566 // join relative x-axes with non-intersect places
567 for (int sj = si - 1; sj >= 0; --sj) {
568 var s2 = series[sj];
569 bool has_intersection = false;
570 for (int sk = si; sk > sj; --sk) {
571 var s3 = series[sk];
572 if (are_intersect(s2.place.y_low, s2.place.y_high, s3.place.y_low, s3.place.y_high)
573 || s2.axis_y.position != s3.axis_y.position
574 || s2.axis_x.type != s3.axis_x.type) {
575 has_intersection = true;
576 break;
577 }
578 }
579 if (!has_intersection) {
580 double tmp_max_rec_width = 0; double tmp_max_rec_height = 0;
581 calc_axis_rec_sizes (s2.axis_y, out tmp_max_rec_width, out tmp_max_rec_height, false);
582 max_rec_width = double.max (max_rec_width, tmp_max_rec_width);
583 max_rec_height = double.max (max_rec_height, tmp_max_rec_height);
584 max_font_indent = double.max (max_font_indent, s2.axis_y.font_indent);
585 max_axis_font_width = double.max (max_axis_font_width, s2.axis_y.title.text == "" ? 0
586 : s2.axis_y.title.get_width(context) + s.axis_y.font_indent);
587 ++nskip;
588 } else {
589 break;
590 }
591 }
592
593 if (!common_y_axes || si == 0)
594 switch (s.axis_y.position) {
595 case Series.Axis.Position.LOW: plot_area_x_min += max_rec_width + max_font_indent + max_axis_font_width; break;
596 case Series.Axis.Position.HIGH: plot_area_x_max -= max_rec_width + max_font_indent + max_axis_font_width; break;
597 case Series.Axis.Position.BOTH: break;
598 default: break;
599 }
600 }
601 }
602
603 bool point_belong (Float128 p, Float128 a, Float128 b) {
604 if (a > b) { Float128 tmp = a; a = b; b = tmp; }
605 if (a <= p <= b) return true;
606 return false;
607 }
608
609 protected virtual void draw_horizontal_axis () {
610 for (int si = series.length - 1, nskip = 0; si >=0; --si) {
611 if (common_x_axes && si != 0) continue;
612 var s = series[si];
613 // 1. Detect max record width/height by axis_rec_npoints equally selected points using format.
614 double max_rec_width, max_rec_height;
615 calc_axis_rec_sizes (s.axis_x, out max_rec_width, out max_rec_height, true);
616
617 // 2. Calculate maximal available number of records, take into account the space width.
618 long max_nrecs = (long) ((plot_area_x_max - plot_area_x_min) * (s.place.x_high - s.place.x_low) / max_rec_width);
619
620 // 3. Calculate grid step.
621 Float128 step = calc_round_step ((s.axis_x.max - s.axis_x.min) / max_nrecs, s.axis_x.type == Series.Axis.Type.DATE_TIME);
622 if (step > s.axis_x.max - s.axis_x.min)
623 step = s.axis_x.max - s.axis_x.min;
624
625 // 4. Calculate x_min (s.axis_x.min / step, round, multiply on step, add step if < s.axis_x.min).
626 Float128 x_min = 0.0;
627 if (step >= 1) {
628 int64 x_min_nsteps = (int64) (s.axis_x.min / step);
629 x_min = x_min_nsteps * step;
630 } else {
631 int64 round_axis_x_min = (int64)s.axis_x.min;
632 int64 x_min_nsteps = (int64) ((s.axis_x.min - round_axis_x_min) / step);
633 x_min = round_axis_x_min + x_min_nsteps * step;
634 }
635 if (x_min < s.axis_x.min) x_min += step;
636
637 // 4.5. Draw Axis title
638 if (s.axis_x.title.text != "")
639 switch (s.axis_x.position) {
640 case Series.Axis.Position.LOW:
641 var scr_x = plot_area_x_min + (plot_area_x_max - plot_area_x_min) * (s.place.x_low + s.place.x_high) / 2.0;
642 var scr_y = cur_y_max - s.axis_x.font_indent;
643 context.move_to(scr_x - s.axis_x.title.get_width(context) / 2.0, scr_y);
644 set_source_rgba(s.axis_x.color);
645 if (common_x_axes) set_source_rgba(Color(0,0,0,1));
646 show_text(s.axis_x.title);
647 break;
648 case Series.Axis.Position.HIGH:
649 var scr_x = plot_area_x_min + (plot_area_x_max - plot_area_x_min) * (s.place.x_low + s.place.x_high) / 2.0;
650 var scr_y = cur_y_min + s.axis_x.font_indent + s.axis_x.title.get_height(context);
651 context.move_to(scr_x - s.axis_x.title.get_width(context) / 2.0, scr_y);
652 set_source_rgba(s.axis_x.color);
653 if (common_x_axes) set_source_rgba(Color(0,0,0,1));
654 show_text(s.axis_x.title);
655 break;
656 case Series.Axis.Position.BOTH:
657 break;
658 }
659
660 // 5. Draw records, update cur_{x,y}_{min,max}.
661 for (Float128 x = x_min, x_max = s.axis_x.max; point_belong (x, x_min, x_max); x += step) {
662 if (common_x_axes) set_source_rgba(Color(0,0,0,1));
663 else set_source_rgba(s.axis_x.color);
664 string text = "", time_text = "";
665 switch (s.axis_x.type) {
666 case Series.Axis.Type.NUMBERS:
667 text = s.axis_x.format.printf((LongDouble)x);
668 break;
669 case Series.Axis.Type.DATE_TIME:
670 var dt = new DateTime.from_unix_utc((int64)x);
671 text = dt.format(s.axis_x.date_format);
672 var dsec_str =
673 ("%."+(s.axis_x.dsec_signs.to_string())+"Lf").printf((LongDouble)(x - (int64)x)).offset(1);
674 time_text = dt.format(s.axis_x.time_format) + dsec_str;
675 break;
676 default:
677 break;
678 }
679 var scr_x = plot_area_x_min + (plot_area_x_max - plot_area_x_min)
680 * (s.place.x_low + (s.place.x_high - s.place.x_low) / (s.axis_x.max - s.axis_x.min) * (x - s.axis_x.min));
681 var text_t = new Text(text, s.axis_x.font_style, s.axis_x.color);
682 switch (s.axis_x.position) {
683 case Series.Axis.Position.LOW:
684 var print_y = cur_y_max - s.axis_x.font_indent - (s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent);
685 switch (s.axis_x.type) {
686 case Series.Axis.Type.NUMBERS:
687 var print_x = scr_x - text_t.get_width(context) / 2.0 - text_t.get_x_bearing(context)
688 - text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min);
689 context.move_to (print_x, print_y);
690 show_text(text_t);
691 break;
692 case Series.Axis.Type.DATE_TIME:
693 var print_x = scr_x - text_t.get_width(context) / 2.0 - text_t.get_x_bearing(context)
694 - text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min);
695 context.move_to (print_x, print_y);
696 if (s.axis_x.date_format != "") show_text(text_t);
697 var time_text_t = new Text(time_text, s.axis_x.font_style, s.axis_x.color);
698 print_x = scr_x - time_text_t.get_width(context) / 2.0 - time_text_t.get_x_bearing(context)
699 - time_text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min);
700 context.move_to (print_x, print_y - (s.axis_x.date_format == "" ? 0 : text_t.get_height(context) + s.axis_x.font_indent));
701 if (s.axis_x.time_format != "") show_text(time_text_t);
702 break;
703 default:
704 break;
705 }
706 // 6. Draw grid lines to the s.place.y_high.
707 var line_style = s.grid.line_style;
708 if (common_x_axes) line_style.color = Color(0, 0, 0, 0.5);
709 set_line_style(line_style);
710 double y = cur_y_max - max_rec_height - s.axis_x.font_indent - (s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent);
711 context.move_to (scr_x, y);
712 if (common_x_axes)
713 context.line_to (scr_x, plot_area_y_min);
714 else
715 context.line_to (scr_x, double.min (y, plot_area_y_max - (plot_area_y_max - plot_area_y_min) * s.place.y_high));
716 break;
717 case Series.Axis.Position.HIGH:
718 var print_y = cur_y_min + max_rec_height + s.axis_x.font_indent + (s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent);
719 switch (s.axis_x.type) {
720 case Series.Axis.Type.NUMBERS:
721 var print_x = scr_x - text_t.get_width(context) / 2.0 - text_t.get_x_bearing(context)
722 - text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min);
723 context.move_to (print_x, print_y);
724 show_text(text_t);
725 break;
726 case Series.Axis.Type.DATE_TIME:
727 var print_x = scr_x - text_t.get_width(context) / 2.0 - text_t.get_x_bearing(context)
728 - text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min);
729 context.move_to (print_x, print_y);
730 if (s.axis_x.date_format != "") show_text(text_t);
731 var time_text_t = new Text(time_text, s.axis_x.font_style, s.axis_x.color);
732 print_x = scr_x - time_text_t.get_width(context) / 2.0 - time_text_t.get_x_bearing(context)
733 - time_text_t.get_width(context) * (x - (s.axis_x.min + s.axis_x.max) / 2.0) / (s.axis_x.max - s.axis_x.min);
734 context.move_to (print_x, print_y - (s.axis_x.date_format == "" ? 0 : text_t.get_height(context) + s.axis_x.font_indent));
735 if (s.axis_x.time_format != "") show_text(time_text_t);
736 break;
737 default:
738 break;
739 }
740 // 6. Draw grid lines to the s.place.y_high.
741 var line_style = s.grid.line_style;
742 if (common_x_axes) line_style.color = Color(0, 0, 0, 0.5);
743 set_line_style(line_style);
744 double y = cur_y_min + max_rec_height + s.axis_x.font_indent + (s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent);
745 context.move_to (scr_x, y);
746 if (common_x_axes)
747 context.line_to (scr_x, plot_area_y_max);
748 else
749 context.line_to (scr_x, double.max (y, plot_area_y_max - (plot_area_y_max - plot_area_y_min) * s.place.y_low));
750 break;
751 case Series.Axis.Position.BOTH:
752 break;
753 default:
754 break;
755 }
756 context.stroke ();
757 }
758
759 // join relative x-axes with non-intersect places
760 for (int sj = si - 1; sj >= 0; --sj) {
761 var s2 = series[sj];
762 bool has_intersection = false;
763 for (int sk = si; sk > sj; --sk) {
764 var s3 = series[sk];
765 if (are_intersect(s2.place.x_low, s2.place.x_high, s3.place.x_low, s3.place.x_high)
766 || s2.axis_x.position != s3.axis_x.position
767 || s2.axis_x.type != s3.axis_x.type) {
768 has_intersection = true;
769 break;
770 }
771 }
772 if (!has_intersection) {
773 ++nskip;
774 } else {
775 break;
776 }
777 }
778
779 if (nskip != 0) {--nskip; continue;}
780
781 switch (s.axis_x.position) {
782 case Series.Axis.Position.LOW:
783 cur_y_max -= max_rec_height + s.axis_x.font_indent
784 + (s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent);
785 break;
786 case Series.Axis.Position.HIGH:
787 cur_y_min += max_rec_height + s.axis_x.font_indent
788 + (s.axis_x.title.text == "" ? 0 : s.axis_x.title.get_height(context) + s.axis_x.font_indent);
789 break;
790 case Series.Axis.Position.BOTH:
791 break;
792 default: break;
793 }
794 }
795 }
796
797 protected virtual void draw_vertical_axis () {
798 for (int si = series.length - 1, nskip = 0; si >=0; --si) {
799 if (common_y_axes && si != 0) continue;
800 var s = series[si];
801 // 1. Detect max record width/height by axis_rec_npoints equally selected points using format.
802 double max_rec_width, max_rec_height;
803 calc_axis_rec_sizes (s.axis_y, out max_rec_width, out max_rec_height, false);
804
805 // 2. Calculate maximal available number of records, take into account the space width.
806 long max_nrecs = (long) ((plot_area_y_max - plot_area_y_min) * (s.place.y_high - s.place.y_low) / max_rec_height);
807
808 // 3. Calculate grid step.
809 Float128 step = calc_round_step ((s.axis_y.max - s.axis_y.min) / max_nrecs);
810 if (step > s.axis_y.max - s.axis_y.min)
811 step = s.axis_y.max - s.axis_y.min;
812
813 // 4. Calculate y_min (s.axis_y.min / step, round, multiply on step, add step if < s.axis_y.min).
814 Float128 y_min = 0.0;
815 if (step >= 1) {
816 int64 y_min_nsteps = (int64) (s.axis_y.min / step);
817 y_min = y_min_nsteps * step;
818 } else {
819 int64 round_axis_y_min = (int64)s.axis_y.min;
820 int64 y_min_nsteps = (int64) ((s.axis_y.min - round_axis_y_min) / step);
821 y_min = round_axis_y_min + y_min_nsteps * step;
822 }
823 if (y_min < s.axis_y.min) y_min += step;
824
825 // 4.5. Draw Axis title
826 if (s.axis_y.title.text != "")
827 switch (s.axis_y.position) {
828 case Series.Axis.Position.LOW:
829 var scr_y = plot_area_y_max - (plot_area_y_max - plot_area_y_min) * (s.place.y_low + s.place.y_high) / 2.0;
830 var scr_x = cur_x_min + s.axis_y.font_indent + s.axis_y.title.get_width(context);
831 context.move_to(scr_x, scr_y + s.axis_y.title.get_height(context) / 2.0);
832 set_source_rgba(s.axis_y.color);
833 if (common_y_axes) set_source_rgba(Color(0,0,0,1));
834 show_text(s.axis_y.title);
835 break;
836 case Series.Axis.Position.HIGH:
837 var scr_y = plot_area_y_max - (plot_area_y_max - plot_area_y_min) * (s.place.y_low + s.place.y_high) / 2.0;
838 var scr_x = cur_x_max - s.axis_y.font_indent;
839 context.move_to(scr_x, scr_y + s.axis_y.title.get_height(context) / 2.0);
840 set_source_rgba(s.axis_y.color);
841 if (common_y_axes) set_source_rgba(Color(0,0,0,1));
842 show_text(s.axis_y.title);
843 break;
844 case Series.Axis.Position.BOTH:
845 break;
846 }
847
848 // 5. Draw records, update cur_{x,y}_{min,max}.
849 for (Float128 y = y_min, y_max = s.axis_y.max; point_belong (y, y_min, y_max); y += step) {
850 if (common_y_axes) set_source_rgba(Color(0,0,0,1));
851 else set_source_rgba(s.axis_y.color);
852 var text = s.axis_y.format.printf((LongDouble)y);
853 var scr_y = plot_area_y_max - (plot_area_y_max - plot_area_y_min)
854 * (s.place.y_low + (s.place.y_high - s.place.y_low) / (s.axis_y.max - s.axis_y.min) * (y - s.axis_y.min));
855 var text_t = new Text(text, s.axis_y.font_style, s.axis_y.color);
856 switch (s.axis_y.position) {
857 case Series.Axis.Position.LOW:
858 context.move_to (cur_x_min + max_rec_width - (new Text(text)).get_width(context) + s.axis_y.font_indent - text_t.get_x_bearing(context)
859 + (s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent),
860 scr_y + (new Text(text)).get_height(context) / 2.0
861 + text_t.get_height(context) * (y - (s.axis_y.min + s.axis_y.max) / 2.0) / (s.axis_y.max - s.axis_y.min));
862 show_text(text_t);
863 // 6. Draw grid lines to the s.place.y_high.
864 var line_style = s.grid.line_style;
865 if (common_y_axes) line_style.color = Color(0, 0, 0, 0.5);
866 set_line_style(line_style);
867 double x = cur_x_min + max_rec_width + s.axis_y.font_indent + (s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent);
868 context.move_to (x, scr_y);
869 if (common_y_axes)
870 context.line_to (plot_area_x_max, scr_y);
871 else
872 context.line_to (double.max (x, plot_area_x_min + (plot_area_x_max - plot_area_x_min) * s.place.x_high), scr_y);
873 break;
874 case Series.Axis.Position.HIGH:
875 context.move_to (cur_x_max - (new Text(text)).get_width(context) - s.axis_y.font_indent - text_t.get_x_bearing(context)
876 - (s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent),
877 scr_y + (new Text(text)).get_height(context) / 2.0
878 + text_t.get_height(context) * (y - (s.axis_y.min + s.axis_y.max) / 2.0) / (s.axis_y.max - s.axis_y.min));
879 show_text(text_t);
880 // 6. Draw grid lines to the s.place.y_high.
881 var line_style = s.grid.line_style;
882 if (common_y_axes) line_style.color = Color(0, 0, 0, 0.5);
883 set_line_style(line_style);
884 double x = cur_x_max - max_rec_width - s.axis_y.font_indent - (s.axis_y.title.text == "" ? 0 :s.axis_y.title.get_width(context) + s.axis_y.font_indent);
885 context.move_to (x, scr_y);
886 if (common_y_axes)
887 context.line_to (plot_area_x_min, scr_y);
888 else
889 context.line_to (double.min (x, plot_area_x_min + (plot_area_x_max - plot_area_x_min) * s.place.x_low), scr_y);
890 break;
891 case Series.Axis.Position.BOTH:
892 break;
893 default:
894 break;
895 }
896 context.stroke ();
897 }
898
899 // join relative x-axes with non-intersect places
900 for (int sj = si - 1; sj >= 0; --sj) {
901 var s2 = series[sj];
902 bool has_intersection = false;
903 for (int sk = si; sk > sj; --sk) {
904 var s3 = series[sk];
905 if (are_intersect(s2.place.y_low, s2.place.y_high, s3.place.y_low, s3.place.y_high)
906 || s2.axis_y.position != s3.axis_y.position) {
907 has_intersection = true;
908 break;
909 }
910 }
911 if (!has_intersection) {
912 ++nskip;
913 } else {
914 break;
915 }
916 }
917
918 if (nskip != 0) {--nskip; continue;}
919
920
921 switch (s.axis_y.position) {
922 case Series.Axis.Position.LOW:
923 cur_x_min += max_rec_width + s.axis_y.font_indent
924 + (s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent); break;
925 case Series.Axis.Position.HIGH:
926 cur_x_max -= max_rec_width + s.axis_y.font_indent
927 + (s.axis_y.title.text == "" ? 0 : s.axis_y.title.get_width(context) + s.axis_y.font_indent); break;
928 case Series.Axis.Position.BOTH:
929 break;
930 default: break;
931 }
932 }
933 }
934
935 protected virtual void draw_plot_area_border () {
936 set_source_rgba (border_color);
937 context.set_dash(null, 0);
938 context.move_to (plot_area_x_min, plot_area_y_min);
939 context.line_to (plot_area_x_min, plot_area_y_max);
940 context.line_to (plot_area_x_max, plot_area_y_max);
941 context.line_to (plot_area_x_max, plot_area_y_min);
942 context.line_to (plot_area_x_min, plot_area_y_min);
943 context.stroke ();
944 }
945
946 protected virtual double get_scr_x (Series s, Float128 x) {
947 return plot_area_x_min + (plot_area_x_max - plot_area_x_min) * (s.place.x_low + (x - s.axis_x.min)
948 / (s.axis_x.max - s.axis_x.min) * (s.place.x_high - s.place.x_low));
949 }
950
951 protected virtual double get_scr_y (Series s, Float128 y) {
952 return plot_area_y_max - (plot_area_y_max - plot_area_y_min) * (s.place.y_low + (y - s.axis_y.min)
953 / (s.axis_y.max - s.axis_y.min) * (s.place.y_high - s.place.y_low));
954 }
955
956 delegate int PointComparator(Series.Point a, Series.Point b);
957 void sort_points(Series.Point[] points, PointComparator compare) {
958 for(var i = 0; i < points.length; ++i) {
959 for(var j = i + 1; j < points.length; ++j) {
960 if(compare(points[i], points[j]) > 0) {
961 var tmp = points[i];
962 points[i] = points[j];
963 points[j] = tmp;
964 }
965 }
966 }
967 }
968
969 protected virtual void draw_series () {
970 for (int si = 0; si < series.length; ++si) {
971 var s = series[si];
972 if (s.points.length == 0) continue;
973 var points = s.points.copy();
974 switch(s.sort) {
975 case Series.Sort.BY_X:
976 sort_points(points, (a, b) => {
977 if (a.x < b.x) return -1;
978 if (a.x > b.x) return 1;
979 return 0;
980 });
981 break;
982 case Series.Sort.BY_Y:
983 sort_points(points, (a, b) => {
984 if (a.y < b.y) return -1;
985 if (a.y > b.y) return 1;
986 return 0;
987 });
988 break;
989 }
990 set_line_style(s.line_style);
991 // move to s.points[0]
992 context.move_to (get_scr_x(s, points[0].x), get_scr_y(s, points[0].y));
993 // draw series line
994 for (int i = 1; i < points.length; ++i)
995 context.line_to (get_scr_x(s, points[i].x), get_scr_y(s, points[i].y));
996 context.stroke();
997 for (int i = 0; i < points.length; ++i)
998 draw_marker_at_pos(s.marker_type, get_scr_x(s, points[i].x), get_scr_y(s, points[i].y));
999 }
1000 }
1001
1002 // TODO:
1003 protected virtual void draw_cursors () {
1004 }
1005 }
1006 }
1 namespace Gtk.CairoChart {
2 public class LabelStyle {
3 FontStyle font_style = FontStyle();
4 LineStyle frame_line_style = new LineStyle();
5 Color bg_color = Color();
6 Color frame_color = Color();
7 }
8 }
1 namespace Gtk.CairoChart {
2 public class Legend {
3 public enum Position {
4 TOP = 0, // default
5 LEFT,
6 RIGHT,
7 BOTTOM
8 }
9 public Position position = Position.TOP;
10 public FontStyle font_style = FontStyle();
11 public Color bg_color = Color(1, 1, 1);
12 public LineStyle border_style = new LineStyle ();
13 public double indent = 5;
14
15 public Legend () {
16 border_style.color = Color (0, 0, 0, 0.3);
17 }
18 }
19 }
1 namespace Gtk.CairoChart {
2 public struct LineStyle {
3 double width;
4 Cairo.LineJoin line_join;
5 Cairo.LineCap line_cap;
6 double[]? dashes;
7 double dash_offset;
8 Color color;
9
10 public LineStyle (double width = 1,
11 Cairo.LineJoin line_join = Cairo.LineJoin.MITER,
12 Cairo.LineCap line_cap = Cairo.LineCap.ROUND,
13 double[]? dashes = null, double dash_offset = 0,
14 Color color = Color()) {
15 this.width = width;
16 this.line_join = line_join;
17 this.line_cap = line_cap;
18 this.dashes = dashes;
19 this.dash_offset = dash_offset;
20 this.color = color;
21 }
22 }
23 }
1 namespace Gtk.CairoChart {
2 public struct Place {
3 double x_low;
4 double x_high;
5 double y_low;
6 double y_high;
7
8 public Place (double x_low = 0, double x_high = 0, double y_low = 0, double y_high = 0) {
9 this.x_low = x_low;
10 this.x_high = x_high;
11 this.y_low = y_low;
12 this.y_high = y_high;
13 }
14 }
15 }
1 namespace Gtk.CairoChart {
2 public struct Point {
3 Float128 x;
4 Float128 y;
5
6 public Point (Float128 x, Float128 y) {
7 this.x = x; this.y = y;
8 }
9 }
10 }
4 4
5 5 public class Series {
6 6
7 public struct Point {
8 Float128 x;
9 Float128 y;
10
11 public Point (Float128 x, Float128 y) {
12 this.x = x; this.y = y;
13 }
14 }
15
16 7 public Point[] points = {};
17 8 public enum Sort {
18 9 BY_X = 0,
12 12 }
13 13 public Sort sort = Sort.BY_X;
14 14
15 // If one of axis:title or axis:min/max are different
16 // then draw separate axis for each/all series
17 // or specify series name near the axis
18 public class Axis {
19 public Float128 min = 0;
20 public Float128 max = 1;
21 public Text title = new Text ("");
22 public enum Type {
23 NUMBERS = 0,
24 DATE_TIME
25 }
26 public enum ScaleType {
27 LINEAR = 0, // default
28 // LOGARITHMIC, // TODO
29 // etc
30 }
31 public Type type;
32 public ScaleType scale_type;
33 public enum Position {
34 LOW = 0,
35 HIGH = 1,
36 BOTH = 2
37 }
38 public Position position = Position.LOW;
39
40 string _format = "%.2Lf";
41 string _date_format = "%Y.%m.%d";
42 string _time_format = "%H:%M:%S";
43 int _dsec_signs = 2; // 2 signs = centiseconds
44 public string format {
45 get { return _format; }
46 set {
47 // TODO: check format
48 _format = value;
49 }
50 default = "%.2Lf";
51 }
52 public string date_format {
53 get { return _date_format; }
54 set {
55 // TODO: check format
56 _date_format = value;
57 }
58 default = "%Y.%m.%d";
59 }
60 public string time_format {
61 get { return _time_format; }
62 set {
63 // TODO: check format
64 _time_format = value;
65 }
66 default = "%H:%M:%S";
67 }
68 public int dsec_signs {
69 get { return _dsec_signs; }
70 set {
71 // TODO: check format
72 _dsec_signs = value;
73 }
74 default = 2;
75 }
76 public FontStyle font_style = FontStyle ();
77 public Color color = Color ();
78 public LineStyle line_style = new LineStyle ();
79 public double font_indent = 5;
80
81 public Axis () {}
82 }
83
84 15 public Axis axis_x = new Axis();
85 16 public Axis axis_y = new Axis();
86 17
87 public struct Place {
88 double x_low;
89 double x_high;
90 double y_low;
91 double y_high;
92
93 public Place (double x_low = 0, double x_high = 0, double y_low = 0, double y_high = 0) {
94 this.x_low = x_low;
95 this.x_high = x_high;
96 this.y_low = y_low;
97 this.y_high = y_high;
98 }
99 }
100
101 18 public enum MarkerType {
102 19 NONE = 0, // default
103 20 SQUARE,
29 29 public Text title = new Text ();
30 30 public MarkerType marker_type = MarkerType.SQUARE;
31 31
32 public class Grid {
33 /*public enum GridType {
34 PRICK_LINE = 0, // default
35 LINE
36 }*/
37 public Color color = Color (0, 0, 0, 0.1);
38
39 public LineStyle line_style = new LineStyle ();
40
41 public Grid () {
42 line_style.dashes = {2, 3};
43 }
44 }
45
46 32 public Grid grid = new Grid ();
47 33
48 34 public GLib.List<Float128?> cursors = new List<Float128?> ();
53 53 public Series () {
54 54 }
55 55
56 public class LabelStyle {
57 FontStyle font_style = FontStyle();
58 LineStyle frame_line_style = new LineStyle();
59 Color bg_color = Color();
60 Color frame_color = Color();
61 }
62 56 }
63 57 }
1 namespace Gtk.CairoChart {
2 [Compact]
3 public class Text {
4 public string text = "";
5 public FontStyle style = FontStyle ();
6 public Color color = Color();
7
8 Cairo.TextExtents get_extents (Cairo.Context context) {
9 context.select_font_face (style.family,
10 style.slant,
11 style.weight);
12 context.set_font_size (style.size);
13 Cairo.TextExtents extents;
14 context.text_extents (text, out extents);
15 return extents;
16 }
17
18 public double get_width (Cairo.Context context) {
19 var extents = get_extents (context);
20 if (style.orientation == FontOrient.HORIZONTAL)
21 return extents.width;
22 else
23 return extents.height;
24 }
25
26 public double get_height (Cairo.Context context) {
27 var extents = get_extents (context);
28 if (style.orientation == FontOrient.HORIZONTAL)
29 return extents.height;
30 else
31 return extents.width;
32 }
33
34 public double get_x_bearing (Cairo.Context context) {
35 var extents = get_extents (context);
36 if (style.orientation == FontOrient.HORIZONTAL)
37 return extents.x_bearing;
38 else
39 return extents.y_bearing;
40 }
41
42 public Text (string text = "",
43 FontStyle style = FontStyle(),
44 Color color = Color()) {
45 this.text = text;
46 this.style = style;
47 this.color = color;
48 }
49
50 public Text.by_instance (Text text) {
51 this.text = text.text;
52 this.style = text.style;
53 this.color = text.color;
54 }
55 }
56 }
6 6 var s3 = new Series ();
7 7
8 8 s1.title = new Text("Series 1"); s1.color = new Color (1, 0, 0);
9 s1.points = {new Series.Point(0, 0), new Series.Point(2, 1), new Series.Point(1, 3)};
10 s1.axis_x.position = Series.Axis.Position.HIGH;
9 s1.points = {new Point(0, 0), new Point(2, 1), new Point(1, 3)};
10 s1.axis_x.position = Axis.Position.HIGH;
11 11 s1.axis_x.format = "%.3Lf";
12 12 s2.title = new Text("Series 2"); s2.color = new Color (0, 1, 0);
13 s2.points = {new Series.Point(5, -3), new Series.Point(25, -18), new Series.Point(-11, 173)};
13 s2.points = {new Point(5, -3), new Point(25, -18), new Point(-11, 173)};
14 14 s3.title = new Text("Series 3"); s3.color = new Color (0, 0, 1);
15 s3.points = {new Series.Point(9, 17), new Series.Point(2, 10), new Series.Point(122, 31)};
16 s3.axis_y.position = Series.Axis.Position.HIGH;
15 s3.points = {new Point(9, 17), new Point(2, 10), new Point(122, 31)};
16 s3.axis_y.position = Axis.Position.HIGH;
17 17
18 18 s1.axis_x.min = 0; s1.axis_x.max = 2;
19 19 s1.axis_y.min = 0; s1.axis_y.max = 3;
49 49 var s3 = new Series ();
50 50
51 51 s1.title = new Text("Series 1"); s1.color = new Color (1, 0, 0);
52 s1.points = {new Series.Point(-12, 0), new Series.Point(2, 1), new Series.Point(20, 3)};
53 s2.axis_y.position = Series.Axis.Position.HIGH;
52 s1.points = {new Point(-12, 0), new Point(2, 1), new Point(20, 3)};
53 s2.axis_y.position = Axis.Position.HIGH;
54 54 s1.axis_x.format = "%.3Lf";
55 55 s2.title = new Text("Series 2"); s2.color = new Color (0, 1, 0);
56 s2.points = {new Series.Point(5, -3), new Series.Point(25, -18), new Series.Point(-11, 173)};
56 s2.points = {new Point(5, -3), new Point(25, -18), new Point(-11, 173)};
57 57 s3.title = new Text("Series 3"); s3.color = new Color (0, 0, 1);
58 s3.points = {new Series.Point(9, 17), new Series.Point(2, 10), new Series.Point(-15, 31)};
59 s3.axis_y.position = Series.Axis.Position.HIGH;
58 s3.points = {new Point(9, 17), new Point(2, 10), new Point(-15, 31)};
59 s3.axis_y.position = Axis.Position.HIGH;
60 60
61 61 s1.axis_x.min = -15; s1.axis_x.max = 30;
62 62 s1.axis_y.min = 0; s1.axis_y.max = 3;
92 92 var s3 = new Series ();
93 93
94 94 s1.title = new Text("Series 1"); s1.color = new Color (1, 0, 0);
95 s1.points = {new Series.Point(0, 70), new Series.Point(2, 155), new Series.Point(1, -3)};
96 s1.axis_x.position = Series.Axis.Position.HIGH;
97 s1.axis_y.position = Series.Axis.Position.HIGH;
95 s1.points = {new Point(0, 70), new Point(2, 155), new Point(1, -3)};
96 s1.axis_x.position = Axis.Position.HIGH;
97 s1.axis_y.position = Axis.Position.HIGH;
98 98 s1.axis_x.format = "%.3Lf";
99 99 s2.title = new Text("Series 2"); s2.color = new Color (0, 1, 0);
100 s2.points = {new Series.Point(5, -3), new Series.Point(25, -18), new Series.Point(-11, 173)};
101 s2.axis_y.position = Series.Axis.Position.HIGH;
100 s2.points = {new Point(5, -3), new Point(25, -18), new Point(-11, 173)};
101 s2.axis_y.position = Axis.Position.HIGH;
102 102 s3.title = new Text("Series 3"); s3.color = new Color (0, 0, 1);
103 s3.points = {new Series.Point(9, -17), new Series.Point(2, 10), new Series.Point(122, 31)};
104 s3.axis_y.position = Series.Axis.Position.HIGH;
103 s3.points = {new Point(9, -17), new Point(2, 10), new Point(122, 31)};
104 s3.axis_y.position = Axis.Position.HIGH;
105 105
106 106 s1.axis_x.min = 0; s1.axis_x.max = 2;
107 107 s1.axis_y.min = -20; s1.axis_y.max = 200;
137 137 var s3 = new Series ();
138 138 var s4 = new Series ();
139 139
140 s1.axis_x.type = Series.Axis.Type.DATE_TIME;
141 s3.axis_x.type = Series.Axis.Type.DATE_TIME;
142 s4.axis_x.type = Series.Axis.Type.DATE_TIME;
140 s1.axis_x.type = Axis.Type.DATE_TIME;
141 s3.axis_x.type = Axis.Type.DATE_TIME;
142 s4.axis_x.type = Axis.Type.DATE_TIME;
143 143 s4.axis_x.dsec_signs = 5;
144 144
145 145 var now = new DateTime.now_local().to_unix();
146 146 var high = (uint64) (253000000000L);
147 147
148 148 s1.title = new Text("Series 1"); s1.color = new Color (1, 0, 0);
149 s1.points = {new Series.Point(now, 70), new Series.Point(now - 100000, 155), new Series.Point(now + 100000, 30)};
150 s1.axis_x.position = Series.Axis.Position.HIGH;
151 s1.axis_y.position = Series.Axis.Position.HIGH;
149 s1.points = {new Point(now, 70), new Point(now - 100000, 155), new Point(now + 100000, 30)};
150 s1.axis_x.position = Axis.Position.HIGH;
151 s1.axis_y.position = Axis.Position.HIGH;
152 152 s2.title = new Text("Series 2"); s2.color = new Color (0, 1, 0);
153 s2.points = {new Series.Point(5, -3), new Series.Point(25, -18), new Series.Point(-11, 173)};
154 s2.axis_y.position = Series.Axis.Position.HIGH;
153 s2.points = {new Point(5, -3), new Point(25, -18), new Point(-11, 173)};
154 s2.axis_y.position = Axis.Position.HIGH;
155 155 s3.title = new Text("Series 3"); s3.color = new Color (0, 0, 1);
156 s3.points = {new Series.Point(high - 2 + 0.73, -17), new Series.Point(high - 1 + 0.234, 10), new Series.Point(high + 1 + 0.411, 31)};
157 s3.axis_y.position = Series.Axis.Position.HIGH;
156 s3.points = {new Point(high - 2 + 0.73, -17), new Point(high - 1 + 0.234, 10), new Point(high + 1 + 0.411, 31)};
157 s3.axis_y.position = Axis.Position.HIGH;
158 158 s4.title = new Text("Series 4"); s4.color = new Color (0.5, 0.3, 0.9);
159 s4.points = {new Series.Point(high + 0.005, -19.05), new Series.Point(high + 0.0051, 28), new Series.Point(high + 0.0052, 55), new Series.Point(high + 0.0053, 44)};
160 s4.axis_y.position = Series.Axis.Position.HIGH;
159 s4.points = {new Point(high + 0.005, -19.05), new Point(high + 0.0051, 28), new Point(high + 0.0052, 55), new Point(high + 0.0053, 44)};
160 s4.axis_y.position = Axis.Position.HIGH;
161 161
162 162 s1.axis_x.min = now - 100000; s1.axis_x.max = now + 100000;
163 163 s1.axis_y.min = -20; s1.axis_y.max = 200;
239 239 button1.clicked.connect (() => {
240 240 chart = chart1; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height());
241 241 switch (chart.legend.position) {
242 case Chart.Legend.Position.TOP: radio_button1.set_active(true); break;
243 case Chart.Legend.Position.RIGHT: radio_button2.set_active(true); break;
244 case Chart.Legend.Position.LEFT: radio_button3.set_active(true); break;
245 case Chart.Legend.Position.BOTTOM: radio_button4.set_active(true); break;
242 case Legend.Position.TOP: radio_button1.set_active(true); break;
243 case Legend.Position.RIGHT: radio_button2.set_active(true); break;
244 case Legend.Position.LEFT: radio_button3.set_active(true); break;
245 case Legend.Position.BOTTOM: radio_button4.set_active(true); break;
246 246 default: break;
247 247 }
248 248 });
249 249 button2.clicked.connect (() => {
250 250 chart = chart2; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height());
251 251 switch (chart.legend.position) {
252 case Chart.Legend.Position.TOP: radio_button1.set_active(true); break;
253 case Chart.Legend.Position.RIGHT: radio_button2.set_active(true); break;
254 case Chart.Legend.Position.LEFT: radio_button3.set_active(true); break;
255 case Chart.Legend.Position.BOTTOM: radio_button4.set_active(true); break;
252 case Legend.Position.TOP: radio_button1.set_active(true); break;
253 case Legend.Position.RIGHT: radio_button2.set_active(true); break;
254 case Legend.Position.LEFT: radio_button3.set_active(true); break;
255 case Legend.Position.BOTTOM: radio_button4.set_active(true); break;
256 256 default: break;
257 257 }
258 258 });
259 259 button3.clicked.connect (() => {
260 260 chart = chart3; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height());
261 261 switch (chart.legend.position) {
262 case Chart.Legend.Position.TOP: radio_button1.set_active(true); break;
263 case Chart.Legend.Position.RIGHT: radio_button2.set_active(true); break;
264 case Chart.Legend.Position.LEFT: radio_button3.set_active(true); break;
265 case Chart.Legend.Position.BOTTOM: radio_button4.set_active(true); break;
262 case Legend.Position.TOP: radio_button1.set_active(true); break;
263 case Legend.Position.RIGHT: radio_button2.set_active(true); break;
264 case Legend.Position.LEFT: radio_button3.set_active(true); break;
265 case Legend.Position.BOTTOM: radio_button4.set_active(true); break;
266 266 default: break;
267 267 }
268 268 });
269 269 button4.clicked.connect (() => {
270 270 chart = chart4; da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height());
271 271 switch (chart.legend.position) {
272 case Chart.Legend.Position.TOP: radio_button1.set_active(true); break;
273 case Chart.Legend.Position.RIGHT: radio_button2.set_active(true); break;
274 case Chart.Legend.Position.LEFT: radio_button4.set_active(true); break;
275 case Chart.Legend.Position.BOTTOM: radio_button4.set_active(true); break;
272 case Legend.Position.TOP: radio_button1.set_active(true); break;
273 case Legend.Position.RIGHT: radio_button2.set_active(true); break;
274 case Legend.Position.LEFT: radio_button4.set_active(true); break;
275 case Legend.Position.BOTTOM: radio_button4.set_active(true); break;
276 276 default: break;
277 277 }
278 278 });
303 303
304 304 radio_button1.toggled.connect ((button) => {
305 305 if (button.get_active()) {
306 chart.legend.position = Chart.Legend.Position.TOP;
306 chart.legend.position = Legend.Position.TOP;
307 307 da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height());
308 308 }
309 309 });
310 310 radio_button2.toggled.connect ((button) => {
311 311 if (button.get_active()) {
312 chart.legend.position = Chart.Legend.Position.RIGHT;
312 chart.legend.position = Legend.Position.RIGHT;
313 313 da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height());
314 314 }
315 315 });
316 316 radio_button3.toggled.connect ((button) => {
317 317 if (button.get_active()) {
318 chart.legend.position = Chart.Legend.Position.LEFT;
318 chart.legend.position = Legend.Position.LEFT;
319 319 da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height());
320 320 }
321 321 });
322 322 radio_button4.toggled.connect ((button) => {
323 323 if (button.get_active()) {
324 chart.legend.position = Chart.Legend.Position.BOTTOM;
324 chart.legend.position = Legend.Position.BOTTOM;
325 325 da.queue_draw_area(0, 0, da.get_allocated_width(), da.get_allocated_height());
326 326 }
327 327 });

Comments