namespace CairoChart { /** * Cairo/GTK+ Chart. */ public class Chart { /** * Chart Position. */ public Cairo.Rectangle pos = Cairo.Rectangle(); /** * Cairo Context of the Drawing Area. */ public Cairo.Context ctx = null; /** * Background Color. */ public Color bg_color = Color(1, 1, 1); /** * Chart Title. */ public Text title = new Text ("Cairo Chart"); /** * Border Color. */ public Color border_color = Color(0, 0, 0, 0.3); /** * Legend. */ public Legend legend = new Legend (); /** * Chart Series. */ public Series[] series = {}; /** * Current calculated Plot Position. */ public Cairo.Rectangle calc_pos = Cairo.Rectangle() { x = 0, y = 0, width = 1, height = 1 }; // relative zoom limits public double rz_x_min { get; protected set; default = 0.0; } public double rz_x_max { get; protected set; default = 1.0; } public double rz_y_min { get; protected set; default = 0.0; } public double rz_y_max { get; protected set; default = 1.0; } public int zoom_first_show { get; protected set; default = 0; } public double title_width { get; protected set; default = 0.0; } public double title_height { get; protected set; default = 0.0; } public double title_indent = 4; public Line.Style selection_style = Line.Style (); public double plot_x_min = 0; public double plot_x_max = 0; public double plot_y_min = 0; public double plot_y_max = 0; public bool joint_x { get; protected set; default = false; } public bool joint_y { get; protected set; default = false; } public Color joint_axis_color = Color (0, 0, 0, 1); public CairoChart.Math math { get; protected set; default = new Math(); } public Cursors cursors { get; protected set; default = new Cursors (); } public Color color { private get { return Color(); } set { ctx.set_source_rgba (value.red, value.green, value.blue, value.alpha); } default = Color(); } public Chart () { } public Chart copy () { var chart = new Chart (); chart.bg_color = this.bg_color; chart.border_color = this.border_color; chart.joint_x = this.joint_x; chart.joint_y = this.joint_y; chart.ctx = this.ctx; chart.calc_pos = this.calc_pos; chart.cursors = this.cursors.copy(); chart.legend = this.legend.copy(); chart.plot_x_max = this.plot_x_max; chart.plot_x_min = this.plot_x_min; chart.plot_y_max = this.plot_y_max; chart.plot_y_min = this.plot_y_min; chart.rz_x_min = this.rz_x_min; chart.rz_x_max = this.rz_x_max; chart.rz_y_min = this.rz_y_min; chart.rz_y_max = this.rz_y_max; chart.selection_style = this.selection_style; chart.series = this.series; chart.title = this.title.copy(); chart.title_height = this.title_height; chart.title_indent = this.title_indent; chart.title_width = this.title_width; chart.pos = this.pos; chart.zoom_first_show = this.zoom_first_show; return chart; } protected virtual void fix_calc_pos () { if (calc_pos.width < 0) calc_pos.width = 0; if (calc_pos.height < 0) calc_pos.height = 0; } protected virtual void set_vertical_axes_titles () { for (var si = 0; si < series.length; ++si) { var s = series[si]; s.axis_y.title.style.orientation = Font.Orientation.VERTICAL; } } public virtual void clear () { if (ctx != null) { color = bg_color; ctx.paint(); color = Color (0, 0, 0, 1); } } public virtual bool draw () { calc_pos = pos; draw_chart_title (); fix_calc_pos (); legend.draw (this); fix_calc_pos (); set_vertical_axes_titles (); cursors.get_cursors_crossings(this); calc_plot_area (); draw_horizontal_axes (); fix_calc_pos (); draw_vertical_axes (); fix_calc_pos (); draw_plot_area_border (); fix_calc_pos (); draw_series (); fix_calc_pos (); cursors.draw_cursors (this); fix_calc_pos (); return true; } protected virtual void draw_chart_title () { var sz = title.get_size(ctx); title_height = sz.height + (legend.position == Legend.Position.TOP ? title_indent * 2 : title_indent); calc_pos.y += title_height; calc_pos.height -= title_height; color = title.color; ctx.move_to (pos.width/2 - sz.width/2, sz.height + title_indent); title.show(ctx); } public virtual void draw_selection (Cairo.Rectangle rect) { selection_style.set(this); ctx.rectangle (rect.x, rect.y, rect.width, rect.height); ctx.stroke(); } protected virtual void draw_horizontal_axes () { for (var si = series.length - 1, nskip = 0; si >=0; --si) series[si].draw_horizontal_axis (si, ref nskip); } protected virtual void draw_vertical_axes () { for (var si = series.length - 1, nskip = 0; si >=0; --si) series[si].draw_vertical_axis (si, ref nskip); } protected virtual void draw_plot_area_border () { color = border_color; ctx.set_dash(null, 0); ctx.move_to (plot_x_min, plot_y_min); ctx.line_to (plot_x_min, plot_y_max); ctx.line_to (plot_x_max, plot_y_max); ctx.line_to (plot_x_max, plot_y_min); ctx.line_to (plot_x_min, plot_y_min); ctx.stroke (); } protected virtual void draw_series () { for (var si = 0; si < series.length; ++si) { var s = series[si]; if (s.zoom_show && s.points.length != 0) s.draw(); } } public virtual void zoom_in (Cairo.Rectangle rect) { var x1 = rect.x + rect.width; var y1 = rect.y + rect.height; for (var si = 0, max_i = series.length; si < max_i; ++si) { var s = series[si]; if (!s.zoom_show) continue; var real_x0 = s.get_real_x (rect.x); var real_x1 = s.get_real_x (x1); var real_y0 = s.get_real_y (rect.y); var real_y1 = s.get_real_y (y1); // if selected square does not intersect with the series's square if ( real_x1 <= s.axis_x.zoom_min || real_x0 >= s.axis_x.zoom_max || real_y0 <= s.axis_y.zoom_min || real_y1 >= s.axis_y.zoom_max) { s.zoom_show = false; continue; } if (real_x0 >= s.axis_x.zoom_min) { s.axis_x.zoom_min = real_x0; s.place.zoom_x_min = 0.0; } else { s.place.zoom_x_min = (s.axis_x.zoom_min - real_x0) / (real_x1 - real_x0); } if (real_x1 <= s.axis_x.zoom_max) { s.axis_x.zoom_max = real_x1; s.place.zoom_x_max = 1.0; } else { s.place.zoom_x_max = (s.axis_x.zoom_max - real_x0) / (real_x1 - real_x0); } if (real_y1 >= s.axis_y.zoom_min) { s.axis_y.zoom_min = real_y1; s.place.zoom_y_min = 0.0; } else { s.place.zoom_y_min = (s.axis_y.zoom_min - real_y1) / (real_y0 - real_y1); } if (real_y0 <= s.axis_y.zoom_max) { s.axis_y.zoom_max = real_y0; s.place.zoom_y_max = 1.0; } else { s.place.zoom_y_max = (s.axis_y.zoom_max - real_y1) / (real_y0 - real_y1); } } zoom_first_show = 0; for (var si = 0, max_i = series.length; si < max_i; ++si) if (series[si].zoom_show) { zoom_first_show = si; break; } var new_rz_x_min = rz_x_min + (rect.x - plot_x_min) / (plot_x_max - plot_x_min) * (rz_x_max - rz_x_min); var new_rz_x_max = rz_x_min + (x1 - plot_x_min) / (plot_x_max - plot_x_min) * (rz_x_max - rz_x_min); var new_rz_y_min = rz_y_min + (rect.y - plot_y_min) / (plot_y_max - plot_y_min) * (rz_y_max - rz_y_min); var new_rz_y_max = rz_y_min + (y1 - plot_y_min) / (plot_y_max - plot_y_min) * (rz_y_max - rz_y_min); rz_x_min = new_rz_x_min; rz_x_max = new_rz_x_max; rz_y_min = new_rz_y_min; rz_y_max = new_rz_y_max; } public virtual void zoom_out () { foreach (var s in series) { s.zoom_show = true; s.axis_x.zoom_min = s.axis_x.min; s.axis_x.zoom_max = s.axis_x.max; s.axis_y.zoom_min = s.axis_y.min; s.axis_y.zoom_max = s.axis_y.max; s.place.zoom_x_min = s.place.x_min; s.place.zoom_x_max = s.place.x_max; s.place.zoom_y_min = s.place.y_min; s.place.zoom_y_max = s.place.y_max; } rz_x_min = 0; rz_x_max = 1; rz_y_min = 0; rz_y_max = 1; zoom_first_show = 0; } public virtual void move (Point delta) { var d = delta; d.x /= plot_x_max - plot_x_min; d.x *= - 1.0; d.y /= plot_y_max - plot_y_min; d.y *= - 1.0; var rzxmin = rz_x_min, rzxmax = rz_x_max, rzymin = rz_y_min, rzymax = rz_y_max; zoom_out(); d.x *= plot_x_max - plot_x_min; d.y *= plot_y_max - plot_y_min; var xmin = plot_x_min + (plot_x_max - plot_x_min) * rzxmin; var xmax = plot_x_min + (plot_x_max - plot_x_min) * rzxmax; var ymin = plot_y_min + (plot_y_max - plot_y_min) * rzymin; var ymax = plot_y_min + (plot_y_max - plot_y_min) * rzymax; d.x *= rzxmax - rzxmin; d.y *= rzymax - rzymin; if (xmin + d.x < plot_x_min) d.x = plot_x_min - xmin; if (xmax + d.x > plot_x_max) d.x = plot_x_max - xmax; if (ymin + d.y < plot_y_min) d.y = plot_y_min - ymin; if (ymax + d.y > plot_y_max) d.y = plot_y_max - ymax; zoom_in (Cairo.Rectangle(){x = xmin + d.x, y = ymin + d.y, width = xmax - xmin, height = ymax - ymin}); } protected virtual void join_calc (bool is_x) { for (var si = series.length - 1, nskip = 0; si >= 0; --si) series[si].join_calc(is_x, si, ref nskip); } protected virtual void calc_plot_area () { plot_x_min = calc_pos.x + legend.indent; plot_x_max = calc_pos.x + calc_pos.width - legend.indent; plot_y_min = calc_pos.y + legend.indent; plot_y_max = calc_pos.y + calc_pos.height - legend.indent; // Check for joint axes joint_x = joint_y = true; int nzoom_series_show = 0; for (var si = series.length - 1; si >=0; --si) { var s = series[si], s0 = series[0]; if (!s.zoom_show) continue; ++nzoom_series_show; if (!s.equal_x_axis(s0)) joint_x = false; if (!s.equal_y_axis(s0)) joint_y = false; } if (nzoom_series_show == 1) joint_x = joint_y = false; join_calc (true); join_calc (false); } protected virtual bool x_in_plot_area (double x) { if (math.x_in_range(x, plot_x_min, plot_x_max)) return true; return false; } protected virtual bool y_in_plot_area (double y) { if (math.y_in_range(y, plot_y_min, plot_y_max)) return true; return false; } public virtual bool point_in_plot_area (Point p) { if (math.point_in_rect (p, plot_x_min, plot_x_max, plot_y_min, plot_y_max)) return true; return false; } public virtual Float128 scr2rel_x (Float128 x) { return rz_x_min + (x - plot_x_min) / (plot_x_max - plot_x_min) * (rz_x_max - rz_x_min); } public virtual Float128 scr2rel_y (Float128 y) { return rz_y_max - (plot_y_max - y) / (plot_y_max - plot_y_min) * (rz_y_max - rz_y_min); } public virtual Point scr2rel_point (Point p) { return Point (scr2rel_x(p.x), scr2rel_y(p.y)); } public virtual Float128 rel2scr_x(Float128 x) { return plot_x_min + (plot_x_max - plot_x_min) * (x - rz_x_min) / (rz_x_max - rz_x_min); } public virtual Float128 rel2scr_y(Float128 y) { return plot_y_min + (plot_y_max - plot_y_min) * (y - rz_y_min) / (rz_y_max - rz_y_min); } public virtual Point128 rel2scr_point (Point128 p) { return Point128 (rel2scr_x(p.x), rel2scr_y(p.y)); } } }