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

Diffs from cf9ec1d to ec0ed49

Commits

avatar Kolan Sh Split source files on classes. accddc2 12 days ago
avatar Kolan Sh Add copy() method to classes. 82aa857 12 days ago
avatar Kolan Sh Fixes #139: Cut lines when they are outside of the chart grid. 002e995 12 days ago
avatar Kolan Sh Merge branch '#139_cut_lines' into develop ec0ed49 12 days ago

Summary

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