gtk / cairo-chart

SSH Git

To clone this repository:

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

To push to this repository:

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

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

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

Diffs from cf9ec1d to 53d9586

Commits

avatar Kolan Sh Split source files on classes. accddc2 12 days ago
avatar Kolan Sh Add copy() method to classes. 82aa857 12 days ago
avatar Kolan Sh Bug #139: Cut lines when they are outside of the chart grid. 467ce5d 12 days ago
avatar Kolan Sh Feature #130: Zoom is in progress... a01cd4f 12 days ago
avatar Kolan Sh Feature #130: Zoom is in progress... 53d9586 12 days ago

Summary

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