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: #ba8ed0f

Parent: #cf9ec1d

Split source files on classes.

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