2017-02-16 15:10:53 +03:00
|
|
|
using Cairo;
|
|
|
|
|
2018-01-08 23:33:13 +03:00
|
|
|
namespace CairoChart {
|
2017-02-16 15:10:53 +03:00
|
|
|
|
|
|
|
public class Series {
|
|
|
|
|
2018-01-16 13:39:17 +03:00
|
|
|
public Point128[] points = {};
|
2017-02-16 15:10:53 +03:00
|
|
|
public enum Sort {
|
|
|
|
BY_X = 0,
|
|
|
|
BY_Y = 1,
|
2018-01-08 23:33:13 +03:00
|
|
|
UNSORTED
|
2017-02-16 15:10:53 +03:00
|
|
|
}
|
|
|
|
public Sort sort = Sort.BY_X;
|
|
|
|
|
|
|
|
public Axis axis_x = new Axis();
|
|
|
|
public Axis axis_y = new Axis();
|
|
|
|
|
2018-01-21 12:55:03 +03:00
|
|
|
public Area place = new Area();
|
2017-02-16 15:10:53 +03:00
|
|
|
public Text title = new Text ();
|
2018-01-10 12:58:35 +03:00
|
|
|
public Marker marker = new Marker ();
|
2017-02-16 15:10:53 +03:00
|
|
|
|
|
|
|
public Grid grid = new Grid ();
|
|
|
|
|
2018-01-20 13:11:17 +03:00
|
|
|
public LineStyle line_style = LineStyle ();
|
2017-02-16 15:10:53 +03:00
|
|
|
|
|
|
|
protected Color _color = Color (0.0, 0.0, 0.0, 1.0);
|
|
|
|
public Color color {
|
|
|
|
get { return _color; }
|
|
|
|
set {
|
|
|
|
_color = value;
|
|
|
|
line_style.color = _color;
|
|
|
|
axis_x.color = _color;
|
|
|
|
axis_y.color = _color;
|
|
|
|
grid.color = _color;
|
|
|
|
grid.color.alpha = 0.5;
|
|
|
|
grid.line_style.color = _color;
|
|
|
|
grid.line_style.color.alpha = 0.5;
|
|
|
|
}
|
2017-08-19 00:07:04 +03:00
|
|
|
default = Color (0.0, 0.0, 0.0, 1.0);
|
2017-02-16 15:10:53 +03:00
|
|
|
}
|
|
|
|
|
2017-10-25 19:31:30 +03:00
|
|
|
public bool zoom_show = true;
|
|
|
|
|
2018-01-16 14:29:24 +03:00
|
|
|
protected Chart chart { get; protected set; default = null; }
|
|
|
|
|
|
|
|
public Series (Chart chart) {
|
|
|
|
this.chart = chart;
|
|
|
|
}
|
|
|
|
|
2018-01-15 12:05:21 +03:00
|
|
|
public virtual Series copy () {
|
2018-01-16 14:29:24 +03:00
|
|
|
var series = new Series (chart);
|
2017-08-28 18:16:48 +03:00
|
|
|
series._color = this._color;
|
|
|
|
series.axis_x = this.axis_x.copy ();
|
|
|
|
series.axis_y = this.axis_y.copy ();
|
|
|
|
series.grid = this.grid.copy ();
|
|
|
|
series.line_style = this.line_style;
|
2018-01-10 12:58:35 +03:00
|
|
|
series.marker = this.marker;
|
2017-08-22 11:54:18 +03:00
|
|
|
series.place = this.place.copy();
|
2017-12-15 20:50:13 +03:00
|
|
|
series.points = this.points;
|
2017-08-28 18:16:48 +03:00
|
|
|
series.sort = this.sort;
|
|
|
|
series.title = this.title.copy();
|
2017-10-25 19:31:30 +03:00
|
|
|
series.zoom_show = this.zoom_show;
|
2017-08-28 18:16:48 +03:00
|
|
|
return series;
|
2017-02-16 15:10:53 +03:00
|
|
|
}
|
|
|
|
|
2018-01-16 15:06:10 +03:00
|
|
|
public virtual void draw () {
|
2018-01-18 20:43:52 +03:00
|
|
|
var points = Math.sort_points(this, sort);
|
2018-01-19 11:36:33 +03:00
|
|
|
line_style.apply(chart);
|
2018-01-15 12:32:47 +03:00
|
|
|
// draw series line
|
|
|
|
for (int i = 1; i < points.length; ++i) {
|
2018-01-16 18:49:37 +03:00
|
|
|
Point c, d;
|
2018-01-18 20:43:52 +03:00
|
|
|
if (Math.cut_line (
|
2018-01-20 20:07:06 +03:00
|
|
|
Point(chart.plarea.x0, chart.plarea.y0),
|
|
|
|
Point(chart.plarea.x1, chart.plarea.y1),
|
2018-01-16 18:49:37 +03:00
|
|
|
Point(get_scr_x(points[i - 1].x), get_scr_y(points[i - 1].y)),
|
|
|
|
Point(get_scr_x(points[i].x), get_scr_y(points[i].y)),
|
2018-01-15 12:32:47 +03:00
|
|
|
out c, out d)
|
|
|
|
) {
|
2018-01-17 10:36:10 +03:00
|
|
|
chart.ctx.move_to (c.x, c.y);
|
|
|
|
chart.ctx.line_to (d.x, d.y);
|
2018-01-15 12:32:47 +03:00
|
|
|
}
|
|
|
|
}
|
2018-01-17 10:36:10 +03:00
|
|
|
chart.ctx.stroke();
|
2018-01-15 12:32:47 +03:00
|
|
|
for (int i = 0; i < points.length; ++i) {
|
2018-01-16 18:49:37 +03:00
|
|
|
var x = get_scr_x(points[i].x);
|
|
|
|
var y = get_scr_y(points[i].y);
|
2018-01-20 20:07:06 +03:00
|
|
|
if (Math.point_in_rect (Point(x, y), chart.plarea.x0, chart.plarea.x1,
|
|
|
|
chart.plarea.y0, chart.plarea.y1))
|
2018-01-15 12:32:47 +03:00
|
|
|
marker.draw_at_pos(chart, x, y);
|
|
|
|
}
|
|
|
|
}
|
2018-01-15 13:12:42 +03:00
|
|
|
|
|
|
|
public virtual bool equal_x_axis (Series s) {
|
|
|
|
if ( axis_x.position != s.axis_x.position
|
|
|
|
|| axis_x.zoom_min != s.axis_x.zoom_min
|
|
|
|
|| axis_x.zoom_max != s.axis_x.zoom_max
|
2018-01-21 12:55:03 +03:00
|
|
|
|| place.zx0 != s.place.zx0
|
|
|
|
|| place.zx1 != s.place.zx1
|
2018-01-15 13:12:42 +03:00
|
|
|
|| axis_x.type != s.axis_x.type
|
|
|
|
)
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public virtual bool equal_y_axis (Series s) {
|
|
|
|
if ( axis_y.position != s.axis_y.position
|
|
|
|
|| axis_y.zoom_min != s.axis_y.zoom_min
|
|
|
|
|| axis_y.zoom_max != s.axis_y.zoom_max
|
2018-01-21 12:55:03 +03:00
|
|
|
|| place.zy0 != s.place.zy0
|
|
|
|
|| place.zy1 != s.place.zy1
|
2018-01-15 13:12:42 +03:00
|
|
|
|| axis_y.type != s.axis_y.type
|
|
|
|
)
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
2018-01-15 14:57:35 +03:00
|
|
|
|
2018-01-19 17:34:30 +03:00
|
|
|
public virtual void join_axes (bool is_x, int si, ref int nskip) {
|
2018-01-16 21:01:24 +03:00
|
|
|
var s = chart.series[si];
|
|
|
|
Axis axis = s.axis_x;
|
|
|
|
if (!is_x) axis = s.axis_y;
|
|
|
|
if (!s.zoom_show) return;
|
|
|
|
if (nskip != 0) {--nskip; return;}
|
|
|
|
double max_rec_width = 0; double max_rec_height = 0;
|
|
|
|
axis.calc_rec_sizes (chart, out max_rec_width, out max_rec_height, is_x);
|
2018-01-19 10:52:06 +03:00
|
|
|
var max_font_spacing = axis.font_spacing;
|
|
|
|
var max_axis_font_width = axis.title.text == "" ? 0 : axis.title.get_width(chart.ctx) + axis.font_spacing;
|
|
|
|
var max_axis_font_height = axis.title.text == "" ? 0 : axis.title.get_height(chart.ctx) + axis.font_spacing;
|
2018-01-16 21:01:24 +03:00
|
|
|
|
|
|
|
if (is_x)
|
2018-01-19 10:52:06 +03:00
|
|
|
s.join_relative_x_axes (si, true, ref max_rec_width, ref max_rec_height, ref max_font_spacing, ref max_axis_font_height, ref nskip);
|
2018-01-16 21:01:24 +03:00
|
|
|
else
|
2018-01-19 10:52:06 +03:00
|
|
|
s.join_relative_y_axes (si, true, ref max_rec_width, ref max_rec_height, ref max_font_spacing, ref max_axis_font_width, ref nskip);
|
2018-01-16 21:01:24 +03:00
|
|
|
|
|
|
|
// for 4.2. Cursor values for joint X axis
|
2018-01-18 12:29:43 +03:00
|
|
|
if (si == chart.zoom_1st_idx && chart.cursors.cursors_crossings.length != 0) {
|
2018-01-16 21:01:24 +03:00
|
|
|
switch (chart.cursors.cursor_style.orientation) {
|
|
|
|
case Cursors.Orientation.VERTICAL:
|
2018-01-20 20:07:06 +03:00
|
|
|
if (is_x && chart.joint_x) {
|
|
|
|
var tmp = max_rec_height + axis.font_spacing;
|
2018-01-16 21:01:24 +03:00
|
|
|
switch (axis.position) {
|
2018-01-20 20:07:06 +03:00
|
|
|
case Axis.Position.LOW: chart.plarea.y1 -= tmp; break;
|
|
|
|
case Axis.Position.HIGH: chart.plarea.y0 += tmp; break;
|
2018-01-16 21:01:24 +03:00
|
|
|
}
|
2018-01-20 20:07:06 +03:00
|
|
|
}
|
2018-01-16 21:01:24 +03:00
|
|
|
break;
|
|
|
|
case Cursors.Orientation.HORIZONTAL:
|
2018-01-20 20:07:06 +03:00
|
|
|
if (!is_x && chart.joint_y) {
|
|
|
|
var tmp = max_rec_width + s.axis_y.font_spacing;
|
2018-01-16 21:01:24 +03:00
|
|
|
switch (s.axis_y.position) {
|
2018-01-20 20:07:06 +03:00
|
|
|
case Axis.Position.LOW: chart.plarea.x0 += tmp; break;
|
|
|
|
case Axis.Position.HIGH: chart.plarea.x1 -= tmp; break;
|
2018-01-16 21:01:24 +03:00
|
|
|
}
|
2018-01-20 20:07:06 +03:00
|
|
|
}
|
2018-01-16 21:01:24 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2018-01-20 20:07:06 +03:00
|
|
|
if (is_x && (!chart.joint_x || si == chart.zoom_1st_idx)) {
|
|
|
|
var tmp = max_rec_height + max_font_spacing + max_axis_font_height;
|
2018-01-16 21:01:24 +03:00
|
|
|
switch (axis.position) {
|
2018-01-20 20:07:06 +03:00
|
|
|
case Axis.Position.LOW: chart.plarea.y1 -= tmp; break;
|
|
|
|
case Axis.Position.HIGH: chart.plarea.y0 += tmp; break;
|
2018-01-16 21:01:24 +03:00
|
|
|
}
|
2018-01-20 20:07:06 +03:00
|
|
|
}
|
|
|
|
if (!is_x && (!chart.joint_y || si == chart.zoom_1st_idx)) {
|
|
|
|
var tmp = max_rec_width + max_font_spacing + max_axis_font_width;
|
2018-01-16 21:01:24 +03:00
|
|
|
switch (s.axis_y.position) {
|
2018-01-20 20:07:06 +03:00
|
|
|
case Axis.Position.LOW: chart.plarea.x0 += tmp; break;
|
|
|
|
case Axis.Position.HIGH: chart.plarea.x1 -= tmp; break;
|
2018-01-16 21:01:24 +03:00
|
|
|
}
|
2018-01-20 20:07:06 +03:00
|
|
|
}
|
2018-01-16 21:01:24 +03:00
|
|
|
}
|
|
|
|
|
2018-01-16 15:06:10 +03:00
|
|
|
public virtual void join_relative_x_axes (int si,
|
2018-01-15 15:03:31 +03:00
|
|
|
bool calc_max_values,
|
|
|
|
ref double max_rec_width,
|
|
|
|
ref double max_rec_height,
|
2018-01-19 10:52:06 +03:00
|
|
|
ref double max_font_spacing,
|
2018-01-15 15:03:31 +03:00
|
|
|
ref double max_axis_font_height,
|
|
|
|
ref int nskip) {
|
|
|
|
for (int sj = si - 1; sj >= 0; --sj) {
|
|
|
|
var s2 = chart.series[sj];
|
|
|
|
if (!s2.zoom_show) continue;
|
|
|
|
bool has_intersection = false;
|
|
|
|
for (int sk = si; sk > sj; --sk) {
|
|
|
|
var s3 = chart.series[sk];
|
|
|
|
if (!s3.zoom_show) continue;
|
2018-01-21 12:55:03 +03:00
|
|
|
if (Math.coord_cross(s2.place.zx0, s2.place.zx1, s3.place.zx0, s3.place.zx1)
|
2018-01-15 15:03:31 +03:00
|
|
|
|| s2.axis_x.position != s3.axis_x.position
|
|
|
|
|| s2.axis_x.type != s3.axis_x.type) {
|
|
|
|
has_intersection = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!has_intersection) {
|
|
|
|
if (calc_max_values) {
|
|
|
|
double tmp_max_rec_width = 0; double tmp_max_rec_height = 0;
|
|
|
|
s2.axis_x.calc_rec_sizes (chart, out tmp_max_rec_width, out tmp_max_rec_height, true);
|
|
|
|
max_rec_width = double.max (max_rec_width, tmp_max_rec_width);
|
|
|
|
max_rec_height = double.max (max_rec_height, tmp_max_rec_height);
|
2018-01-19 10:52:06 +03:00
|
|
|
max_font_spacing = double.max (max_font_spacing, s2.axis_x.font_spacing);
|
2018-01-15 15:03:31 +03:00
|
|
|
max_axis_font_height = double.max (max_axis_font_height, s2.axis_x.title.text == "" ? 0 :
|
2018-01-19 10:52:06 +03:00
|
|
|
s2.axis_x.title.get_height(chart.ctx) + this.axis_x.font_spacing);
|
2018-01-15 15:03:31 +03:00
|
|
|
}
|
|
|
|
++nskip;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-16 15:06:10 +03:00
|
|
|
public virtual void join_relative_y_axes (int si,
|
2018-01-15 15:03:31 +03:00
|
|
|
bool calc_max_values,
|
|
|
|
ref double max_rec_width,
|
|
|
|
ref double max_rec_height,
|
2018-01-19 10:52:06 +03:00
|
|
|
ref double max_font_spacing,
|
2018-01-15 15:03:31 +03:00
|
|
|
ref double max_axis_font_width,
|
|
|
|
ref int nskip) {
|
2018-01-15 14:57:35 +03:00
|
|
|
for (int sj = si - 1; sj >= 0; --sj) {
|
|
|
|
var s2 = chart.series[sj];
|
|
|
|
if (!s2.zoom_show) continue;
|
|
|
|
bool has_intersection = false;
|
|
|
|
for (int sk = si; sk > sj; --sk) {
|
|
|
|
var s3 = chart.series[sk];
|
|
|
|
if (!s3.zoom_show) continue;
|
2018-01-21 12:55:03 +03:00
|
|
|
if (Math.coord_cross(s2.place.zy0, s2.place.zy1, s3.place.zy0, s3.place.zy1)
|
2018-01-15 14:57:35 +03:00
|
|
|
|| s2.axis_y.position != s3.axis_y.position
|
|
|
|
|| s2.axis_y.type != s3.axis_y.type) {
|
|
|
|
has_intersection = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!has_intersection) {
|
|
|
|
double tmp_max_rec_width = 0; double tmp_max_rec_height = 0;
|
|
|
|
s2.axis_y.calc_rec_sizes (chart, out tmp_max_rec_width, out tmp_max_rec_height, false);
|
|
|
|
max_rec_width = double.max (max_rec_width, tmp_max_rec_width);
|
|
|
|
max_rec_height = double.max (max_rec_height, tmp_max_rec_height);
|
2018-01-19 10:52:06 +03:00
|
|
|
max_font_spacing = double.max (max_font_spacing, s2.axis_y.font_spacing);
|
2018-01-15 14:57:35 +03:00
|
|
|
max_axis_font_width = double.max (max_axis_font_width, s2.axis_y.title.text == "" ? 0
|
2018-01-19 10:52:06 +03:00
|
|
|
: s2.axis_y.title.get_width(chart.ctx) + this.axis_y.font_spacing);
|
2018-01-15 14:57:35 +03:00
|
|
|
++nskip;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-01-15 19:56:35 +03:00
|
|
|
|
2018-01-16 15:06:10 +03:00
|
|
|
protected virtual void draw_horizontal_records (Float128 step, double max_rec_height, Float128 x_min) {
|
2018-01-15 19:56:35 +03:00
|
|
|
// 5. Draw records, update cur_{x,y}_{min,max}.
|
2018-01-17 10:36:10 +03:00
|
|
|
var ctx = chart.ctx;
|
2018-01-15 19:56:35 +03:00
|
|
|
var joint_x = chart.joint_x;
|
|
|
|
|
2018-01-18 20:43:52 +03:00
|
|
|
for (Float128 x = x_min, x_max = axis_x.zoom_max; Math.point_belong (x, x_min, x_max); x += step) {
|
2018-01-18 16:57:39 +03:00
|
|
|
if (joint_x) chart.color = chart.joint_color;
|
2018-01-16 20:39:59 +03:00
|
|
|
else chart.color = axis_x.color;
|
2018-01-15 19:56:35 +03:00
|
|
|
string text = "", time_text = "";
|
2018-01-16 14:59:00 +03:00
|
|
|
switch (axis_x.type) {
|
2018-01-20 20:07:06 +03:00
|
|
|
case Axis.Type.NUMBERS: text = axis_x.format.printf((LongDouble)x); break;
|
|
|
|
case Axis.Type.DATE_TIME: axis_x.format_date_time(x, out text, out time_text); break;
|
2018-01-15 19:56:35 +03:00
|
|
|
}
|
2018-01-16 18:49:37 +03:00
|
|
|
var scr_x = get_scr_x (x);
|
2018-01-16 14:59:00 +03:00
|
|
|
var text_t = new Text(text, axis_x.font_style, axis_x.color);
|
2018-01-17 10:36:10 +03:00
|
|
|
var sz = axis_x.title.get_size(ctx);
|
2018-01-15 19:56:35 +03:00
|
|
|
|
2018-01-16 14:59:00 +03:00
|
|
|
switch (axis_x.position) {
|
2018-01-15 19:56:35 +03:00
|
|
|
case Axis.Position.LOW:
|
2018-01-20 20:07:06 +03:00
|
|
|
var print_y = chart.evarea.y1 - axis_x.font_spacing - (axis_x.title.text == "" ? 0 : sz.height + axis_x.font_spacing);
|
2018-01-16 14:59:00 +03:00
|
|
|
var print_x = compact_rec_x_pos (x, text_t);
|
2018-01-17 10:36:10 +03:00
|
|
|
ctx.move_to (print_x, print_y);
|
2018-01-16 14:59:00 +03:00
|
|
|
switch (axis_x.type) {
|
2018-01-15 19:56:35 +03:00
|
|
|
case Axis.Type.NUMBERS:
|
2018-01-17 10:36:10 +03:00
|
|
|
text_t.show(ctx);
|
2018-01-15 19:56:35 +03:00
|
|
|
break;
|
|
|
|
case Axis.Type.DATE_TIME:
|
2018-01-17 10:36:10 +03:00
|
|
|
if (axis_x.date_format != "") text_t.show(ctx);
|
2018-01-16 14:59:00 +03:00
|
|
|
var time_text_t = new Text(time_text, axis_x.font_style, axis_x.color);
|
|
|
|
print_x = compact_rec_x_pos (x, time_text_t);
|
2018-01-19 10:52:06 +03:00
|
|
|
ctx.move_to (print_x, print_y - (axis_x.date_format == "" ? 0 : text_t.get_height(ctx) + axis_x.font_spacing));
|
2018-01-17 10:36:10 +03:00
|
|
|
if (axis_x.time_format != "") time_text_t.show(ctx);
|
2018-01-15 19:56:35 +03:00
|
|
|
break;
|
|
|
|
}
|
2018-01-21 12:55:03 +03:00
|
|
|
// 6. Draw grid lines to the place.zy0.
|
2018-01-16 14:59:00 +03:00
|
|
|
var line_style = grid.line_style;
|
2018-01-15 19:56:35 +03:00
|
|
|
if (joint_x) line_style.color = Color(0, 0, 0, 0.5);
|
2018-01-19 11:36:33 +03:00
|
|
|
line_style.apply(chart);
|
2018-01-20 20:07:06 +03:00
|
|
|
double y = chart.evarea.y1 - max_rec_height - axis_x.font_spacing - (axis_x.title.text == "" ? 0 : sz.height + axis_x.font_spacing);
|
2018-01-17 10:36:10 +03:00
|
|
|
ctx.move_to (scr_x, y);
|
2018-01-15 19:56:35 +03:00
|
|
|
if (joint_x)
|
2018-01-20 20:07:06 +03:00
|
|
|
ctx.line_to (scr_x, chart.plarea.y0);
|
2018-01-15 19:56:35 +03:00
|
|
|
else
|
2018-01-21 12:55:03 +03:00
|
|
|
ctx.line_to (scr_x, double.min (y, chart.plarea.y0 + chart.plarea.height * (1.0 - place.zy1)));
|
2018-01-15 19:56:35 +03:00
|
|
|
break;
|
|
|
|
case Axis.Position.HIGH:
|
2018-01-20 20:07:06 +03:00
|
|
|
var print_y = chart.evarea.y0 + max_rec_height + axis_x.font_spacing + (axis_x.title.text == "" ? 0 : sz.height + axis_x.font_spacing);
|
2018-01-16 14:59:00 +03:00
|
|
|
var print_x = compact_rec_x_pos (x, text_t);
|
2018-01-17 10:36:10 +03:00
|
|
|
ctx.move_to (print_x, print_y);
|
2018-01-15 19:56:35 +03:00
|
|
|
|
2018-01-16 14:59:00 +03:00
|
|
|
switch (axis_x.type) {
|
2018-01-15 19:56:35 +03:00
|
|
|
case Axis.Type.NUMBERS:
|
2018-01-17 10:36:10 +03:00
|
|
|
text_t.show(ctx);
|
2018-01-15 19:56:35 +03:00
|
|
|
break;
|
|
|
|
case Axis.Type.DATE_TIME:
|
2018-01-17 10:36:10 +03:00
|
|
|
if (axis_x.date_format != "") text_t.show(ctx);
|
2018-01-16 14:59:00 +03:00
|
|
|
var time_text_t = new Text(time_text, axis_x.font_style, axis_x.color);
|
|
|
|
print_x = compact_rec_x_pos (x, time_text_t);
|
2018-01-19 10:52:06 +03:00
|
|
|
ctx.move_to (print_x, print_y - (axis_x.date_format == "" ? 0 : text_t.get_height(ctx) + axis_x.font_spacing));
|
2018-01-17 10:36:10 +03:00
|
|
|
if (axis_x.time_format != "") time_text_t.show(ctx);
|
2018-01-15 19:56:35 +03:00
|
|
|
break;
|
|
|
|
}
|
2018-01-21 12:55:03 +03:00
|
|
|
// 6. Draw grid lines to the place.zy1.
|
2018-01-16 14:59:00 +03:00
|
|
|
var line_style = grid.line_style;
|
2018-01-15 19:56:35 +03:00
|
|
|
if (joint_x) line_style.color = Color(0, 0, 0, 0.5);
|
2018-01-19 11:36:33 +03:00
|
|
|
line_style.apply(chart);
|
2018-01-20 20:07:06 +03:00
|
|
|
double y = chart.evarea.y0 + max_rec_height + axis_x.font_spacing + (axis_x.title.text == "" ? 0 : sz.height + axis_x.font_spacing);
|
2018-01-17 10:36:10 +03:00
|
|
|
ctx.move_to (scr_x, y);
|
2018-01-15 19:56:35 +03:00
|
|
|
if (joint_x)
|
2018-01-20 20:07:06 +03:00
|
|
|
ctx.line_to (scr_x, chart.plarea.y1);
|
2018-01-15 19:56:35 +03:00
|
|
|
else
|
2018-01-21 12:55:03 +03:00
|
|
|
ctx.line_to (scr_x, double.max (y, chart.plarea.y0 + chart.plarea.height * (1.0 - place.zy0)));
|
2018-01-15 19:56:35 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-16 15:06:10 +03:00
|
|
|
public virtual void draw_horizontal_axis (int si, ref int nskip) {
|
2018-01-15 19:56:35 +03:00
|
|
|
var s = chart.series[si];
|
|
|
|
if (!s.zoom_show) return;
|
2018-01-18 12:29:43 +03:00
|
|
|
if (chart.joint_x && si != chart.zoom_1st_idx) return;
|
2018-01-15 19:56:35 +03:00
|
|
|
|
|
|
|
// 1. Detect max record width/height by axis.nrecords equally selected points using format.
|
|
|
|
double max_rec_width, max_rec_height;
|
|
|
|
s.axis_x.calc_rec_sizes (chart, out max_rec_width, out max_rec_height, true);
|
|
|
|
|
|
|
|
// 2. Calculate maximal available number of records, take into account the space width.
|
2018-01-21 12:55:03 +03:00
|
|
|
long max_nrecs = (long) (chart.plarea.width * (s.place.zx1 - s.place.zx0) / max_rec_width);
|
2018-01-15 19:56:35 +03:00
|
|
|
|
|
|
|
// 3. Calculate grid step.
|
2018-01-18 20:43:52 +03:00
|
|
|
Float128 step = Math.calc_round_step ((s.axis_x.zoom_max - s.axis_x.zoom_min) / max_nrecs, s.axis_x.type == Axis.Type.DATE_TIME);
|
2018-01-15 19:56:35 +03:00
|
|
|
if (step > s.axis_x.zoom_max - s.axis_x.zoom_min)
|
|
|
|
step = s.axis_x.zoom_max - s.axis_x.zoom_min;
|
|
|
|
|
|
|
|
// 4. Calculate x_min (s.axis_x.zoom_min / step, round, multiply on step, add step if < s.axis_x.zoom_min).
|
|
|
|
Float128 x_min = 0.0;
|
|
|
|
if (step >= 1) {
|
|
|
|
int64 x_min_nsteps = (int64) (s.axis_x.zoom_min / step);
|
|
|
|
x_min = x_min_nsteps * step;
|
|
|
|
} else {
|
|
|
|
int64 round_axis_x_min = (int64)s.axis_x.zoom_min;
|
|
|
|
int64 x_min_nsteps = (int64) ((s.axis_x.zoom_min - round_axis_x_min) / step);
|
|
|
|
x_min = round_axis_x_min + x_min_nsteps * step;
|
|
|
|
}
|
|
|
|
if (x_min < s.axis_x.zoom_min) x_min += step;
|
|
|
|
|
|
|
|
// 4.2. Cursor values for joint X axis
|
2018-01-16 19:38:43 +03:00
|
|
|
if (chart.joint_x && chart.cursors.cursor_style.orientation == Cursors.Orientation.VERTICAL && chart.cursors.cursors_crossings.length != 0) {
|
2018-01-20 20:07:06 +03:00
|
|
|
var tmp = max_rec_height + s.axis_x.font_spacing;
|
2018-01-15 19:56:35 +03:00
|
|
|
switch (s.axis_x.position) {
|
2018-01-20 20:07:06 +03:00
|
|
|
case Axis.Position.LOW: chart.evarea.y1 -= tmp; break;
|
|
|
|
case Axis.Position.HIGH: chart.evarea.y0 += tmp; break;
|
2018-01-15 19:56:35 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-17 10:36:10 +03:00
|
|
|
var sz = s.axis_x.title.get_size(chart.ctx);
|
2018-01-15 19:56:35 +03:00
|
|
|
|
|
|
|
// 4.5. Draw Axis title
|
|
|
|
if (s.axis_x.title.text != "") {
|
2018-01-21 12:55:03 +03:00
|
|
|
var scr_x = chart.plarea.x0 + chart.plarea.width * (s.place.zx0 + s.place.zx1) / 2.0;
|
2018-01-15 19:56:35 +03:00
|
|
|
double scr_y = 0.0;
|
|
|
|
switch (s.axis_x.position) {
|
2018-01-20 20:07:06 +03:00
|
|
|
case Axis.Position.LOW: scr_y = chart.evarea.y1 - s.axis_x.font_spacing; break;
|
|
|
|
case Axis.Position.HIGH: scr_y = chart.evarea.y0 + s.axis_x.font_spacing + sz.height; break;
|
2018-01-15 19:56:35 +03:00
|
|
|
}
|
2018-01-17 10:36:10 +03:00
|
|
|
chart.ctx.move_to(scr_x - sz.width / 2.0, scr_y);
|
2018-01-16 20:39:59 +03:00
|
|
|
chart.color = s.axis_x.color;
|
2018-01-18 16:57:39 +03:00
|
|
|
if (chart.joint_x) chart.color = chart.joint_color;
|
2018-01-17 10:36:10 +03:00
|
|
|
s.axis_x.title.show(chart.ctx);
|
2018-01-15 19:56:35 +03:00
|
|
|
}
|
|
|
|
|
2018-01-16 15:06:10 +03:00
|
|
|
s.draw_horizontal_records (step, max_rec_height, x_min);
|
2018-01-15 19:56:35 +03:00
|
|
|
|
2018-01-17 10:36:10 +03:00
|
|
|
chart.ctx.stroke ();
|
2018-01-15 19:56:35 +03:00
|
|
|
|
|
|
|
double tmp1 = 0, tmp2 = 0, tmp3 = 0, tmp4 = 0;
|
2018-01-16 15:06:10 +03:00
|
|
|
s.join_relative_x_axes (si, false, ref tmp1, ref tmp2, ref tmp3, ref tmp4, ref nskip);
|
2018-01-15 19:56:35 +03:00
|
|
|
|
|
|
|
if (nskip != 0) {--nskip; return;}
|
|
|
|
|
2018-01-20 20:07:06 +03:00
|
|
|
var tmp = max_rec_height + s.axis_x.font_spacing + (s.axis_x.title.text == "" ? 0 : sz.height + s.axis_x.font_spacing);
|
2018-01-15 19:56:35 +03:00
|
|
|
switch (s.axis_x.position) {
|
2018-01-20 20:07:06 +03:00
|
|
|
case Axis.Position.LOW: chart.evarea.y1 -= tmp; break;
|
|
|
|
case Axis.Position.HIGH: chart.evarea.y0 += tmp; break;
|
2018-01-15 19:56:35 +03:00
|
|
|
}
|
|
|
|
}
|
2018-01-15 20:21:45 +03:00
|
|
|
|
2018-01-16 15:06:10 +03:00
|
|
|
protected virtual void draw_vertical_records (Float128 step, double max_rec_width, Float128 y_min) {
|
2018-01-15 20:21:45 +03:00
|
|
|
// 5. Draw records, update cur_{x,y}_{min,max}.
|
2018-01-17 10:36:10 +03:00
|
|
|
var ctx = chart.ctx;
|
2018-01-15 20:21:45 +03:00
|
|
|
var joint_y = chart.joint_y;
|
|
|
|
|
2018-01-18 20:43:52 +03:00
|
|
|
for (Float128 y = y_min, y_max = axis_y.zoom_max; Math.point_belong (y, y_min, y_max); y += step) {
|
2018-01-18 16:57:39 +03:00
|
|
|
if (joint_y) chart.color = chart.joint_color;
|
2018-01-16 20:39:59 +03:00
|
|
|
else chart.color = axis_y.color;
|
2018-01-16 14:59:00 +03:00
|
|
|
var text = axis_y.format.printf((LongDouble)y);
|
2018-01-16 18:49:37 +03:00
|
|
|
var scr_y = get_scr_y (y);
|
2018-01-16 14:59:00 +03:00
|
|
|
var text_t = new Text(text, axis_y.font_style, axis_y.color);
|
2018-01-17 10:36:10 +03:00
|
|
|
var text_sz = text_t.get_size(ctx);
|
|
|
|
var sz = axis_y.title.get_size(ctx);
|
2018-01-15 20:21:45 +03:00
|
|
|
|
2018-01-16 14:59:00 +03:00
|
|
|
switch (axis_y.position) {
|
2018-01-15 20:21:45 +03:00
|
|
|
case Axis.Position.LOW:
|
2018-01-20 20:07:06 +03:00
|
|
|
ctx.move_to (chart.evarea.x0 + max_rec_width - text_sz.width + axis_y.font_spacing
|
2018-01-19 10:52:06 +03:00
|
|
|
+ (axis_y.title.text == "" ? 0 : sz.width + axis_y.font_spacing),
|
2018-01-16 14:59:00 +03:00
|
|
|
compact_rec_y_pos (y, text_t));
|
2018-01-17 10:36:10 +03:00
|
|
|
text_t.show(ctx);
|
2018-01-21 12:55:03 +03:00
|
|
|
// 6. Draw grid lines to the place.zx0.
|
2018-01-16 14:59:00 +03:00
|
|
|
var line_style = grid.line_style;
|
2018-01-15 20:21:45 +03:00
|
|
|
if (joint_y) line_style.color = Color(0, 0, 0, 0.5);
|
2018-01-19 11:36:33 +03:00
|
|
|
line_style.apply(chart);
|
2018-01-20 20:07:06 +03:00
|
|
|
double x = chart.evarea.x0 + max_rec_width + axis_y.font_spacing + (axis_y.title.text == "" ? 0 : sz.width + axis_y.font_spacing);
|
2018-01-17 10:36:10 +03:00
|
|
|
ctx.move_to (x, scr_y);
|
2018-01-15 20:21:45 +03:00
|
|
|
if (joint_y)
|
2018-01-20 20:07:06 +03:00
|
|
|
ctx.line_to (chart.plarea.x1, scr_y);
|
2018-01-15 20:21:45 +03:00
|
|
|
else
|
2018-01-21 12:55:03 +03:00
|
|
|
ctx.line_to (double.max (x, chart.plarea.x0 + chart.plarea.width * place.zx1), scr_y);
|
2018-01-15 20:21:45 +03:00
|
|
|
break;
|
|
|
|
case Axis.Position.HIGH:
|
2018-01-20 20:07:06 +03:00
|
|
|
ctx.move_to (chart.evarea.x1 - text_sz.width - axis_y.font_spacing
|
2018-01-19 10:52:06 +03:00
|
|
|
- (axis_y.title.text == "" ? 0 : sz.width + axis_y.font_spacing),
|
2018-01-16 14:59:00 +03:00
|
|
|
compact_rec_y_pos (y, text_t));
|
2018-01-17 10:36:10 +03:00
|
|
|
text_t.show(ctx);
|
2018-01-21 12:55:03 +03:00
|
|
|
// 6. Draw grid lines to the place.zx1.
|
2018-01-16 14:59:00 +03:00
|
|
|
var line_style = grid.line_style;
|
2018-01-15 20:21:45 +03:00
|
|
|
if (joint_y) line_style.color = Color(0, 0, 0, 0.5);
|
2018-01-19 11:36:33 +03:00
|
|
|
line_style.apply(chart);
|
2018-01-20 20:07:06 +03:00
|
|
|
double x = chart.evarea.x1 - max_rec_width - axis_y.font_spacing - (axis_y.title.text == "" ? 0 : sz.width + axis_y.font_spacing);
|
2018-01-17 10:36:10 +03:00
|
|
|
ctx.move_to (x, scr_y);
|
2018-01-15 20:21:45 +03:00
|
|
|
if (joint_y)
|
2018-01-20 20:07:06 +03:00
|
|
|
ctx.line_to (chart.plarea.x0, scr_y);
|
2018-01-15 20:21:45 +03:00
|
|
|
else
|
2018-01-21 12:55:03 +03:00
|
|
|
ctx.line_to (double.min (x, chart.plarea.x0 + chart.plarea.width * place.zx0), scr_y);
|
2018-01-15 20:21:45 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-16 15:06:10 +03:00
|
|
|
public virtual void draw_vertical_axis (int si, ref int nskip) {
|
2018-01-15 20:21:45 +03:00
|
|
|
var s = chart.series[si];
|
|
|
|
if (!s.zoom_show) return;
|
2018-01-18 12:29:43 +03:00
|
|
|
if (chart.joint_y && si != chart.zoom_1st_idx) return;
|
2018-01-15 20:21:45 +03:00
|
|
|
// 1. Detect max record width/height by axis.nrecords equally selected points using format.
|
|
|
|
double max_rec_width, max_rec_height;
|
|
|
|
s.axis_y.calc_rec_sizes (chart, out max_rec_width, out max_rec_height, false);
|
|
|
|
|
|
|
|
// 2. Calculate maximal available number of records, take into account the space width.
|
2018-01-21 12:55:03 +03:00
|
|
|
long max_nrecs = (long) (chart.plarea.height * (s.place.zy1 - s.place.zy0) / max_rec_height);
|
2018-01-15 20:21:45 +03:00
|
|
|
|
|
|
|
// 3. Calculate grid step.
|
2018-01-18 20:43:52 +03:00
|
|
|
Float128 step = Math.calc_round_step ((s.axis_y.zoom_max - s.axis_y.zoom_min) / max_nrecs);
|
2018-01-15 20:21:45 +03:00
|
|
|
if (step > s.axis_y.zoom_max - s.axis_y.zoom_min)
|
|
|
|
step = s.axis_y.zoom_max - s.axis_y.zoom_min;
|
|
|
|
|
|
|
|
// 4. Calculate y_min (s.axis_y.zoom_min / step, round, multiply on step, add step if < s.axis_y.zoom_min).
|
|
|
|
Float128 y_min = 0.0;
|
|
|
|
if (step >= 1) {
|
|
|
|
int64 y_min_nsteps = (int64) (s.axis_y.zoom_min / step);
|
|
|
|
y_min = y_min_nsteps * step;
|
|
|
|
} else {
|
|
|
|
int64 round_axis_y_min = (int64)s.axis_y.zoom_min;
|
|
|
|
int64 y_min_nsteps = (int64) ((s.axis_y.zoom_min - round_axis_y_min) / step);
|
|
|
|
y_min = round_axis_y_min + y_min_nsteps * step;
|
|
|
|
}
|
|
|
|
if (y_min < s.axis_y.zoom_min) y_min += step;
|
|
|
|
|
|
|
|
// 4.2. Cursor values for joint Y axis
|
2018-01-16 19:38:43 +03:00
|
|
|
if (chart.joint_y && chart.cursors.cursor_style.orientation == Cursors.Orientation.HORIZONTAL && chart.cursors.cursors_crossings.length != 0) {
|
2018-01-20 20:07:06 +03:00
|
|
|
var tmp = max_rec_width + s.axis_y.font_spacing;
|
2018-01-15 20:21:45 +03:00
|
|
|
switch (s.axis_y.position) {
|
2018-01-20 20:07:06 +03:00
|
|
|
case Axis.Position.LOW: chart.evarea.x0 += tmp; break;
|
|
|
|
case Axis.Position.HIGH: chart.evarea.x1 -= tmp; break;
|
2018-01-15 20:21:45 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-17 10:36:10 +03:00
|
|
|
var sz = s.axis_y.title.get_size(chart.ctx);
|
2018-01-15 20:21:45 +03:00
|
|
|
|
|
|
|
// 4.5. Draw Axis title
|
|
|
|
if (s.axis_y.title.text != "") {
|
2018-01-21 12:55:03 +03:00
|
|
|
var scr_y = chart.plarea.y0 + chart.plarea.height * (1.0 - (s.place.zy0 + s.place.zy1) / 2.0);
|
2018-01-15 20:21:45 +03:00
|
|
|
switch (s.axis_y.position) {
|
|
|
|
case Axis.Position.LOW:
|
2018-01-20 20:07:06 +03:00
|
|
|
var scr_x = chart.evarea.x0 + s.axis_y.font_spacing + sz.width;
|
2018-01-17 10:36:10 +03:00
|
|
|
chart.ctx.move_to(scr_x, scr_y + sz.height / 2.0);
|
2018-01-15 20:21:45 +03:00
|
|
|
break;
|
|
|
|
case Axis.Position.HIGH:
|
2018-01-20 20:07:06 +03:00
|
|
|
var scr_x = chart.evarea.x1 - s.axis_y.font_spacing;
|
2018-01-17 10:36:10 +03:00
|
|
|
chart.ctx.move_to(scr_x, scr_y + sz.height / 2.0);
|
2018-01-15 20:21:45 +03:00
|
|
|
break;
|
|
|
|
}
|
2018-01-16 20:39:59 +03:00
|
|
|
chart.color = s.axis_y.color;
|
2018-01-18 16:57:39 +03:00
|
|
|
if (chart.joint_y) chart.color = chart.joint_color;
|
2018-01-17 10:36:10 +03:00
|
|
|
s.axis_y.title.show(chart.ctx);
|
2018-01-15 20:21:45 +03:00
|
|
|
}
|
|
|
|
|
2018-01-16 15:06:10 +03:00
|
|
|
s.draw_vertical_records (step, max_rec_width, y_min);
|
2018-01-15 20:21:45 +03:00
|
|
|
|
2018-01-17 10:36:10 +03:00
|
|
|
chart.ctx.stroke ();
|
2018-01-15 20:21:45 +03:00
|
|
|
|
|
|
|
double tmp1 = 0, tmp2 = 0, tmp3 = 0, tmp4 = 0;
|
2018-01-16 15:06:10 +03:00
|
|
|
s.join_relative_y_axes (si, false, ref tmp1, ref tmp2, ref tmp3, ref tmp4, ref nskip);
|
2018-01-15 20:21:45 +03:00
|
|
|
|
|
|
|
if (nskip != 0) {--nskip; return;}
|
|
|
|
|
2018-01-20 20:07:06 +03:00
|
|
|
var tmp = max_rec_width + s.axis_y.font_spacing + (s.axis_y.title.text == "" ? 0 : sz.width + s.axis_y.font_spacing);
|
2018-01-15 20:21:45 +03:00
|
|
|
switch (s.axis_y.position) {
|
2018-01-20 20:07:06 +03:00
|
|
|
case Axis.Position.LOW: chart.evarea.x0 += tmp; break;
|
|
|
|
case Axis.Position.HIGH: chart.evarea.x1 -= tmp; break;
|
2018-01-15 20:21:45 +03:00
|
|
|
}
|
|
|
|
}
|
2018-01-16 14:29:24 +03:00
|
|
|
|
2018-01-16 14:42:23 +03:00
|
|
|
public virtual double compact_rec_x_pos (Float128 x, Text text) {
|
2018-01-17 10:36:10 +03:00
|
|
|
var sz = text.get_size(chart.ctx);
|
2018-01-16 18:49:37 +03:00
|
|
|
return get_scr_x(x) - sz.width / 2.0
|
2018-01-16 14:42:23 +03:00
|
|
|
- sz.width * (x - (axis_x.zoom_min + axis_x.zoom_max) / 2.0) / (axis_x.zoom_max - axis_x.zoom_min);
|
2018-01-16 14:29:24 +03:00
|
|
|
}
|
|
|
|
|
2018-01-16 14:42:23 +03:00
|
|
|
public virtual double compact_rec_y_pos (Float128 y, Text text) {
|
2018-01-17 10:36:10 +03:00
|
|
|
var sz = text.get_size(chart.ctx);
|
2018-01-16 18:49:37 +03:00
|
|
|
return get_scr_y(y) + sz.height / 2.0
|
2018-01-16 14:42:23 +03:00
|
|
|
+ sz.height * (y - (axis_y.zoom_min + axis_y.zoom_max) / 2.0) / (axis_y.zoom_max - axis_y.zoom_min);
|
2018-01-16 14:29:24 +03:00
|
|
|
}
|
2018-01-16 18:49:37 +03:00
|
|
|
|
|
|
|
public virtual double get_scr_x (Float128 x) {
|
2018-01-21 12:55:03 +03:00
|
|
|
return chart.plarea.x0 + chart.plarea.width * (place.zx0 + (x - axis_x.zoom_min) / (axis_x.zoom_max - axis_x.zoom_min) * (place.zx1 - place.zx0));
|
2018-01-16 18:49:37 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
public virtual double get_scr_y (Float128 y) {
|
2018-01-21 12:55:03 +03:00
|
|
|
return chart.plarea.y0 + chart.plarea.height * (1.0 - (place.zy0 + (y - axis_y.zoom_min) / (axis_y.zoom_max - axis_y.zoom_min) * (place.zy1 - place.zy0)));
|
2018-01-16 18:49:37 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
public virtual Point get_scr_point (Point128 p) {
|
|
|
|
return Point (get_scr_x(p.x), get_scr_y(p.y));
|
|
|
|
}
|
|
|
|
|
|
|
|
public virtual Float128 get_real_x (double scr_x) {
|
2018-01-21 12:55:03 +03:00
|
|
|
return axis_x.zoom_min + ((scr_x - chart.plarea.x0) / chart.plarea.width - place.zx0)
|
|
|
|
* (axis_x.zoom_max - axis_x.zoom_min) / (place.zx1 - place.zx0);
|
2018-01-16 18:49:37 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
public virtual Float128 get_real_y (double scr_y) {
|
2018-01-21 12:55:03 +03:00
|
|
|
return axis_y.zoom_min + ((chart.plarea.y1 - scr_y) / chart.plarea.height - place.zy0)
|
|
|
|
* (axis_y.zoom_max - axis_y.zoom_min) / (place.zy1 - place.zy0);
|
2018-01-16 18:49:37 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
public virtual Point128 get_real_point (Point p) {
|
|
|
|
return Point128 (get_real_x(p.x), get_real_y(p.y));
|
|
|
|
}
|
2018-01-19 14:09:50 +03:00
|
|
|
|
|
|
|
public virtual void unzoom () {
|
|
|
|
zoom_show = true;
|
|
|
|
axis_x.unzoom();
|
|
|
|
axis_y.unzoom();
|
|
|
|
place.unzoom();
|
|
|
|
}
|
2017-02-16 15:10:53 +03:00
|
|
|
}
|
|
|
|
}
|