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 5597e2e to 30e052e

Commits

avatar Kolan Sh In progress: scrolling added. cf9ec1d 12 days ago
avatar Kolan Sh Feature #130: Zoom is in progress... 34d63f7 12 days ago
avatar Kolan Sh Split source files on classes. fa1c3ab 12 days ago
avatar Kolan Sh Add copy() method to classes. 22fb4b4 12 days ago
avatar Kolan Sh Bug #139: Cut lines when they are outside of the chart grid. cddb9ac 12 days ago
avatar Kolan Sh Feature #130: Zoom is in progress... 30e052e 12 days ago

Summary

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