2018-01-08 23:33:13 +03:00
|
|
|
namespace CairoChart {
|
2018-01-22 14:31:34 +03:00
|
|
|
|
|
|
|
/**
|
2018-01-22 14:44:31 +03:00
|
|
|
* ``Chart`` axis.
|
2018-01-22 14:31:34 +03:00
|
|
|
*/
|
2017-08-28 14:47:31 +03:00
|
|
|
public class Axis {
|
2018-01-22 14:44:31 +03:00
|
|
|
|
2018-01-23 12:29:59 +03:00
|
|
|
protected unowned Chart chart;
|
|
|
|
protected unowned Series ser;
|
2018-01-22 18:21:29 +03:00
|
|
|
protected string _format = "%.2Lf";
|
|
|
|
protected string _date_format = "%Y.%m.%d";
|
|
|
|
protected string _time_format = "%H:%M:%S";
|
|
|
|
protected int _dsec_signs = 2; // 2 signs = centiseconds
|
2018-01-23 13:18:27 +03:00
|
|
|
protected bool is_x;
|
2018-01-22 14:44:31 +03:00
|
|
|
|
2018-01-22 16:14:49 +03:00
|
|
|
/**
|
|
|
|
* ``Axis`` title.
|
|
|
|
*/
|
|
|
|
public Text title;
|
|
|
|
|
2018-01-22 14:44:31 +03:00
|
|
|
/**
|
|
|
|
* ``Axis`` range/limits.
|
|
|
|
*/
|
|
|
|
public Range range = new Range();
|
|
|
|
|
2018-01-23 12:29:59 +03:00
|
|
|
/**
|
|
|
|
* ``Axis`` relative range/limits.
|
|
|
|
*/
|
|
|
|
public Range place = new Range();
|
|
|
|
|
2018-01-22 14:44:31 +03:00
|
|
|
/**
|
|
|
|
* Data type.
|
|
|
|
*/
|
2018-01-22 14:31:34 +03:00
|
|
|
public enum DType {
|
2018-01-22 14:44:31 +03:00
|
|
|
/**
|
|
|
|
* Float128 numbers.
|
|
|
|
*/
|
2017-08-28 14:47:31 +03:00
|
|
|
NUMBERS = 0,
|
2018-01-22 14:44:31 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Date/Time.
|
|
|
|
*/
|
2017-08-28 14:47:31 +03:00
|
|
|
DATE_TIME
|
|
|
|
}
|
2018-01-22 14:44:31 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Data type.
|
|
|
|
*/
|
|
|
|
public DType dtype;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ``Axis`` scale type.
|
|
|
|
*/
|
2018-01-22 14:46:00 +03:00
|
|
|
public enum Scale {
|
2018-01-22 14:44:31 +03:00
|
|
|
/**
|
|
|
|
* Linear scale.
|
|
|
|
*/
|
|
|
|
LINEAR = 0,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Logarithmic scale.
|
|
|
|
*/
|
|
|
|
// LOGARITHMIC,
|
2017-08-28 14:47:31 +03:00
|
|
|
}
|
2018-01-22 14:44:31 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Scale type.
|
|
|
|
*/
|
2018-01-22 14:46:00 +03:00
|
|
|
public Scale scale;
|
2018-01-22 14:44:31 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* ``Axis`` position.
|
|
|
|
*/
|
2017-08-28 14:47:31 +03:00
|
|
|
public enum Position {
|
2018-01-22 14:54:08 +03:00
|
|
|
/**
|
|
|
|
* Bottom/Left ``Axis``.
|
|
|
|
*/
|
2017-08-28 14:47:31 +03:00
|
|
|
LOW = 0,
|
2018-01-22 14:54:08 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Top/Right ``Axis``.
|
|
|
|
*/
|
2017-08-28 14:47:31 +03:00
|
|
|
HIGH = 1,
|
2018-01-22 14:54:08 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 2 ``Axes``.
|
|
|
|
*/
|
2017-08-28 14:47:31 +03:00
|
|
|
BOTH = 2
|
|
|
|
}
|
2018-01-22 14:54:08 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Position.
|
|
|
|
*/
|
2017-08-28 14:47:31 +03:00
|
|
|
public Position position = Position.LOW;
|
|
|
|
|
2018-01-22 14:54:08 +03:00
|
|
|
/**
|
|
|
|
* Float128 numbers print string format.
|
|
|
|
*/
|
|
|
|
public virtual string format {
|
2017-08-28 14:47:31 +03:00
|
|
|
get { return _format; }
|
|
|
|
set {
|
|
|
|
// TODO: check format
|
|
|
|
_format = value;
|
|
|
|
}
|
|
|
|
default = "%.2Lf";
|
|
|
|
}
|
2018-01-22 14:54:08 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Date print string format.
|
|
|
|
*/
|
|
|
|
public virtual string date_format {
|
2017-08-28 14:47:31 +03:00
|
|
|
get { return _date_format; }
|
|
|
|
set {
|
|
|
|
// TODO: check format
|
|
|
|
_date_format = value;
|
|
|
|
}
|
|
|
|
default = "%Y.%m.%d";
|
|
|
|
}
|
2018-01-22 14:54:08 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Time print string format.
|
|
|
|
*/
|
|
|
|
public virtual string time_format {
|
2017-08-28 14:47:31 +03:00
|
|
|
get { return _time_format; }
|
|
|
|
set {
|
|
|
|
// TODO: check format
|
|
|
|
_time_format = value;
|
|
|
|
}
|
|
|
|
default = "%H:%M:%S";
|
|
|
|
}
|
2018-01-22 14:54:08 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Number of second's signs after point.
|
|
|
|
*
|
|
|
|
* 2 signs means centiseconds, 3 signs means milliseconds, etc...
|
|
|
|
*/
|
|
|
|
public virtual int dsec_signs {
|
2017-08-28 14:47:31 +03:00
|
|
|
get { return _dsec_signs; }
|
|
|
|
set {
|
|
|
|
// TODO: check format
|
|
|
|
_dsec_signs = value;
|
|
|
|
}
|
|
|
|
default = 2;
|
|
|
|
}
|
2018-01-22 14:58:41 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* ``Axis`` Font style.
|
|
|
|
*/
|
2018-01-22 14:54:58 +03:00
|
|
|
public Font font = new Font ();
|
2018-01-22 14:58:41 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* ``Axis`` color.
|
|
|
|
*/
|
2017-08-28 14:47:31 +03:00
|
|
|
public Color color = Color ();
|
2018-01-22 14:58:41 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* ``Axis`` line style.
|
|
|
|
*/
|
2018-01-20 13:11:17 +03:00
|
|
|
public LineStyle line_style = LineStyle ();
|
2018-01-22 14:58:41 +03:00
|
|
|
|
2018-01-22 15:10:47 +03:00
|
|
|
/**
|
|
|
|
* Number of equally placed points to evaluate records sizes.
|
|
|
|
*/
|
|
|
|
public int nrecords = 128;
|
|
|
|
|
2018-01-22 15:07:19 +03:00
|
|
|
/**
|
|
|
|
* Constructs a new ``Axis``.
|
2018-01-23 12:29:59 +03:00
|
|
|
* @param chart ``Chart`` instance.
|
|
|
|
* @param ser ``Series`` instance.
|
2018-01-23 13:18:27 +03:00
|
|
|
* @param is_x is X-axis or not (Y-axis otherwise).
|
2018-01-22 15:07:19 +03:00
|
|
|
*/
|
2018-01-23 13:18:27 +03:00
|
|
|
public Axis (Chart chart, Series ser, bool is_x) {
|
2018-01-22 14:44:31 +03:00
|
|
|
this.chart = chart;
|
2018-01-23 12:29:59 +03:00
|
|
|
this.ser = ser;
|
2018-01-23 13:18:27 +03:00
|
|
|
this.is_x = is_x;
|
2018-01-22 14:44:31 +03:00
|
|
|
title = new Text (chart, "");
|
|
|
|
}
|
|
|
|
|
2018-01-22 15:10:47 +03:00
|
|
|
/**
|
|
|
|
* Gets a copy of the ``Axis``.
|
|
|
|
*/
|
2018-01-15 12:05:21 +03:00
|
|
|
public virtual Axis copy () {
|
2018-01-23 13:18:27 +03:00
|
|
|
var axis = new Axis (chart, ser, is_x);
|
2017-08-28 18:16:48 +03:00
|
|
|
axis._date_format = this._date_format;
|
|
|
|
axis._dsec_signs = this._dsec_signs;
|
|
|
|
axis._format = this._format;
|
|
|
|
axis._time_format = this._time_format;
|
|
|
|
axis.color = this.color;
|
2018-01-22 15:49:49 +03:00
|
|
|
axis.font = this.font.copy();
|
2017-08-28 18:16:48 +03:00
|
|
|
axis.line_style = this.line_style;
|
2018-01-22 15:49:49 +03:00
|
|
|
axis.range = this.range.copy();
|
2018-01-23 12:29:59 +03:00
|
|
|
axis.place = this.place.copy();
|
2017-08-28 18:16:48 +03:00
|
|
|
axis.position = this.position;
|
2018-01-22 14:46:00 +03:00
|
|
|
axis.scale = this.scale;
|
2017-08-28 18:16:48 +03:00
|
|
|
axis.title = this.title.copy();
|
2018-01-22 14:31:34 +03:00
|
|
|
axis.dtype = this.dtype;
|
2018-01-10 14:15:42 +03:00
|
|
|
axis.nrecords = this.nrecords;
|
2017-08-28 18:16:48 +03:00
|
|
|
return axis;
|
|
|
|
}
|
|
|
|
|
2018-01-22 16:00:39 +03:00
|
|
|
/**
|
|
|
|
* Prints date/time to strings with a current formats.
|
2018-01-22 18:37:27 +03:00
|
|
|
* @param date returns formatted date string.
|
|
|
|
* @param time returns formatted time string.
|
2018-01-22 16:00:39 +03:00
|
|
|
*/
|
|
|
|
public virtual void print_dt (Float128 x, out string date, out string time) {
|
2018-01-10 14:15:42 +03:00
|
|
|
date = time = "";
|
|
|
|
var dt = new DateTime.from_unix_utc((int64)x);
|
|
|
|
date = dt.format(date_format);
|
|
|
|
var dsec_str =
|
|
|
|
("%."+(dsec_signs.to_string())+"Lf").printf((LongDouble)(x - (int64)x)).offset(1);
|
2018-01-23 18:14:39 +03:00
|
|
|
if (time_format != "")
|
|
|
|
time = dt.format(time_format) + dsec_str;
|
2018-01-10 14:15:42 +03:00
|
|
|
}
|
|
|
|
|
2018-01-22 15:52:50 +03:00
|
|
|
/**
|
|
|
|
* Zooms out ``Axis``.
|
|
|
|
*/
|
2018-01-22 14:44:31 +03:00
|
|
|
public virtual void zoom_out () {
|
|
|
|
range.zoom_out();
|
2018-01-23 12:29:59 +03:00
|
|
|
place.zoom_out();
|
2018-01-19 14:09:50 +03:00
|
|
|
}
|
2018-01-23 11:39:01 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Joins equal axes.
|
|
|
|
* @param nskip returns number of series to skip printing.
|
|
|
|
*/
|
2018-01-23 12:56:47 +03:00
|
|
|
public virtual void join_axes (ref int nskip) {
|
2018-01-23 12:29:59 +03:00
|
|
|
Axis axis = this;
|
|
|
|
if (!ser.zoom_show) return;
|
2018-01-23 11:39:01 +03:00
|
|
|
if (nskip != 0) {--nskip; return;}
|
|
|
|
var max_rec_width = 0.0, max_rec_height = 0.0;
|
|
|
|
calc_rec_sizes (axis, out max_rec_width, out max_rec_height, is_x);
|
|
|
|
var max_font_spacing = is_x ? axis.font.vspacing : axis.font.hspacing;
|
|
|
|
var max_axis_font_width = axis.title.text == "" ? 0 : axis.title.width + axis.font.hspacing;
|
|
|
|
var max_axis_font_height = axis.title.text == "" ? 0 : axis.title.height + axis.font.vspacing;
|
|
|
|
|
2018-01-23 12:29:59 +03:00
|
|
|
var si = Math.find_arr<Series>(chart.series, ser);
|
2018-01-23 11:39:01 +03:00
|
|
|
if (si == -1) return;
|
|
|
|
|
|
|
|
if (is_x)
|
2018-01-23 13:36:56 +03:00
|
|
|
join_rel_axes (si, true, ref max_rec_width, ref max_rec_height, ref max_font_spacing, ref max_axis_font_height, ref nskip);
|
2018-01-23 11:39:01 +03:00
|
|
|
else
|
2018-01-23 13:36:56 +03:00
|
|
|
join_rel_axes (si, true, ref max_rec_width, ref max_rec_height, ref max_font_spacing, ref max_axis_font_width, ref nskip);
|
2018-01-23 11:39:01 +03:00
|
|
|
|
|
|
|
// for 4.2. Cursor values for joint X axis
|
|
|
|
if (si == chart.zoom_1st_idx && chart.cursors.has_crossings) {
|
|
|
|
switch (chart.cursors.style.orientation) {
|
|
|
|
case Cursors.Orientation.VERTICAL:
|
|
|
|
if (is_x && chart.joint_x) {
|
|
|
|
var tmp = max_rec_height + axis.font.vspacing;
|
|
|
|
switch (axis.position) {
|
|
|
|
case Axis.Position.LOW: chart.plarea.y1 -= tmp; break;
|
|
|
|
case Axis.Position.HIGH: chart.plarea.y0 += tmp; break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case Cursors.Orientation.HORIZONTAL:
|
|
|
|
if (!is_x && chart.joint_y) {
|
2018-01-23 12:41:20 +03:00
|
|
|
var tmp = max_rec_width + font.hspacing;
|
|
|
|
switch (position) {
|
2018-01-23 11:39:01 +03:00
|
|
|
case Axis.Position.LOW: chart.plarea.x0 += tmp; break;
|
|
|
|
case Axis.Position.HIGH: chart.plarea.x1 -= tmp; break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (is_x && (!chart.joint_x || si == chart.zoom_1st_idx)) {
|
|
|
|
var tmp = max_rec_height + max_font_spacing + max_axis_font_height;
|
|
|
|
switch (axis.position) {
|
|
|
|
case Axis.Position.LOW: chart.plarea.y1 -= tmp; break;
|
|
|
|
case Axis.Position.HIGH: chart.plarea.y0 += tmp; break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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-23 12:41:20 +03:00
|
|
|
switch (position) {
|
2018-01-23 11:39:01 +03:00
|
|
|
case Axis.Position.LOW: chart.plarea.x0 += tmp; break;
|
|
|
|
case Axis.Position.HIGH: chart.plarea.x1 -= tmp; break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Draws horizontal axis.
|
|
|
|
* @param nskip number of series to skip printing.
|
|
|
|
*/
|
2018-01-23 15:14:54 +03:00
|
|
|
public virtual void draw (ref int nskip) {
|
2018-01-23 12:41:20 +03:00
|
|
|
if (!ser.zoom_show) return;
|
2018-01-23 11:39:01 +03:00
|
|
|
|
2018-01-23 12:41:20 +03:00
|
|
|
var si = Math.find_arr<Series>(chart.series, ser);
|
2018-01-23 11:39:01 +03:00
|
|
|
if (si == -1) return;
|
|
|
|
|
2018-01-23 15:14:54 +03:00
|
|
|
if ((is_x && chart.joint_x || !is_x && chart.joint_y) && si != chart.zoom_1st_idx) return;
|
2018-01-23 11:39:01 +03:00
|
|
|
|
|
|
|
// 1. Detect max record width/height by axis.nrecords equally selected points using format.
|
|
|
|
double max_rec_width, max_rec_height;
|
2018-01-23 15:14:54 +03:00
|
|
|
calc_rec_sizes (this, out max_rec_width, out max_rec_height, is_x);
|
2018-01-23 11:39:01 +03:00
|
|
|
|
|
|
|
// 2. Calculate maximal available number of records, take into account the space width.
|
2018-01-23 15:14:54 +03:00
|
|
|
long max_nrecs = (long) (is_x ? chart.plarea.width * place.zrange / max_rec_width
|
|
|
|
: chart.plarea.height * place.zrange / max_rec_height);
|
2018-01-23 11:39:01 +03:00
|
|
|
|
|
|
|
// 3. Calculate grid step.
|
2018-01-23 12:41:20 +03:00
|
|
|
Float128 step = Math.calc_round_step (range.zrange / max_nrecs, dtype == Axis.DType.DATE_TIME);
|
|
|
|
if (step > range.zrange)
|
|
|
|
step = range.zrange;
|
2018-01-23 11:39:01 +03:00
|
|
|
|
2018-01-23 15:14:54 +03:00
|
|
|
// 4. Calculate min (range.zmin / step, round, multiply on step, add step if < range.zmin).
|
|
|
|
Float128 min = 0;
|
2018-01-23 11:39:01 +03:00
|
|
|
if (step >= 1) {
|
2018-01-23 15:14:54 +03:00
|
|
|
int64 min_nsteps = (int64) (range.zmin / step);
|
|
|
|
min = min_nsteps * step;
|
2018-01-23 11:39:01 +03:00
|
|
|
} else {
|
2018-01-23 15:14:54 +03:00
|
|
|
int64 round_axis_min = (int64)range.zmin;
|
|
|
|
int64 min_nsteps = (int64) ((range.zmin - round_axis_min) / step);
|
|
|
|
min = round_axis_min + min_nsteps * step;
|
2018-01-23 11:39:01 +03:00
|
|
|
}
|
2018-01-23 15:14:54 +03:00
|
|
|
if (min < range.zmin) min += step;
|
|
|
|
|
|
|
|
// 4.2. Cursor values for joint axes
|
|
|
|
if (chart.cursors.has_crossings)
|
|
|
|
if ( is_x && chart.joint_x && chart.cursors.style.orientation == Cursors.Orientation.VERTICAL
|
|
|
|
|| !is_x && chart.joint_y && chart.cursors.style.orientation == Cursors.Orientation.HORIZONTAL) {
|
|
|
|
var tmp = 0.0;
|
|
|
|
if (is_x) tmp = max_rec_height + font.vspacing;
|
|
|
|
else tmp = max_rec_width + font.hspacing;
|
|
|
|
switch (position) {
|
|
|
|
case Axis.Position.LOW:
|
|
|
|
if (is_x) chart.evarea.y1 -= tmp;
|
|
|
|
else chart.evarea.x0 += tmp;
|
|
|
|
break;
|
|
|
|
case Axis.Position.HIGH:
|
|
|
|
if (is_x) chart.evarea.y0 += tmp;
|
|
|
|
else chart.evarea.x1 -= tmp;
|
|
|
|
break;
|
|
|
|
}
|
2018-01-23 11:39:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// 4.5. Draw Axis title
|
2018-01-23 12:41:20 +03:00
|
|
|
if (title.text != "") {
|
2018-01-23 15:14:54 +03:00
|
|
|
if (is_x) {
|
|
|
|
var scr_x = chart.plarea.x0 + chart.plarea.width * (place.zmin + place.zmax) / 2;
|
|
|
|
var scr_y = 0.0;
|
|
|
|
switch (position) {
|
|
|
|
case Axis.Position.LOW: scr_y = chart.evarea.y1 - font.vspacing; break;
|
|
|
|
case Axis.Position.HIGH: scr_y = chart.evarea.y0 + font.vspacing + title.height; break;
|
|
|
|
}
|
|
|
|
chart.ctx.move_to(scr_x - title.width / 2, scr_y);
|
|
|
|
chart.color = color;
|
|
|
|
} else {
|
|
|
|
var scr_y = chart.plarea.y0 + chart.plarea.height * (1 - (place.zmin + place.zmax) / 2);
|
|
|
|
switch (position) {
|
|
|
|
case Axis.Position.LOW:
|
|
|
|
var scr_x = chart.evarea.x0 + font.hspacing + title.width;
|
|
|
|
chart.ctx.move_to(scr_x, scr_y + title.height / 2);
|
|
|
|
break;
|
|
|
|
case Axis.Position.HIGH:
|
|
|
|
var scr_x = chart.evarea.x1 - font.hspacing;
|
|
|
|
chart.ctx.move_to(scr_x, scr_y + title.height / 2);
|
|
|
|
break;
|
|
|
|
}
|
2018-01-23 11:39:01 +03:00
|
|
|
}
|
2018-01-23 15:20:13 +03:00
|
|
|
if (is_x && chart.joint_x || !is_x && chart.joint_y)
|
|
|
|
chart.color = chart.joint_color;
|
|
|
|
else
|
|
|
|
chart.color = color;
|
2018-01-23 12:41:20 +03:00
|
|
|
title.show();
|
2018-01-23 11:39:01 +03:00
|
|
|
}
|
|
|
|
|
2018-01-23 17:11:13 +03:00
|
|
|
if (is_x) draw_recs (min, step, max_rec_height);
|
|
|
|
else draw_recs (min, step, max_rec_width);
|
2018-01-23 11:39:01 +03:00
|
|
|
|
|
|
|
chart.ctx.stroke ();
|
|
|
|
|
|
|
|
var tmp1 = 0.0, tmp2 = 0.0, tmp3 = 0.0, tmp4 = 0.0;
|
2018-01-23 13:36:56 +03:00
|
|
|
join_rel_axes (si, false, ref tmp1, ref tmp2, ref tmp3, ref tmp4, ref nskip);
|
2018-01-23 11:39:01 +03:00
|
|
|
|
|
|
|
if (nskip != 0) {--nskip; return;}
|
|
|
|
|
2018-01-23 15:14:54 +03:00
|
|
|
var tmp = 0.0;
|
|
|
|
if (is_x) tmp = max_rec_height + font.vspacing + (title.text == "" ? 0 : title.height + font.vspacing);
|
|
|
|
else tmp = max_rec_width + font.hspacing + (title.text == "" ? 0 : title.width + font.hspacing);
|
2018-01-23 12:41:20 +03:00
|
|
|
switch (position) {
|
2018-01-23 15:14:54 +03:00
|
|
|
case Axis.Position.LOW:
|
|
|
|
if (is_x) chart.evarea.y1 -= tmp;
|
|
|
|
else chart.evarea.x0 += tmp;
|
|
|
|
break;
|
|
|
|
case Axis.Position.HIGH:
|
|
|
|
if (is_x) chart.evarea.y0 += tmp;
|
|
|
|
else chart.evarea.x1 -= tmp;
|
|
|
|
break;
|
2018-01-23 11:39:01 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-01-23 13:18:27 +03:00
|
|
|
* Gets compact placement position on the screen.
|
|
|
|
* @param axis_value real ``Axis`` value.
|
2018-01-23 11:39:01 +03:00
|
|
|
* @param text to place on the screen.
|
|
|
|
*/
|
2018-01-23 13:18:27 +03:00
|
|
|
public virtual double compact_rec_pos (Float128 axis_value, Text text) {
|
|
|
|
return is_x ? scr_pos(axis_value) - text.width / 2 - text.width * (axis_value - (range.zmin + range.zmax) / 2) / range.zrange
|
|
|
|
: scr_pos(axis_value) + text.height / 2 + text.height * (axis_value - (range.zmin + range.zmax) / 2) / range.zrange;
|
2018-01-23 11:39:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-01-23 13:18:27 +03:00
|
|
|
* Gets screen position by real ``Axis`` value.
|
|
|
|
* @param axis_value real ``Axis`` value.
|
2018-01-23 11:39:01 +03:00
|
|
|
*/
|
2018-01-23 13:18:27 +03:00
|
|
|
public virtual double scr_pos (Float128 axis_value) {
|
|
|
|
return is_x ? chart.plarea.x0 + chart.plarea.width * (place.zmin + (axis_value - range.zmin) / range.zrange * place.zrange)
|
|
|
|
: chart.plarea.y0 + chart.plarea.height * (1 - (place.zmin + (axis_value - range.zmin) / range.zrange * place.zrange));
|
2018-01-23 11:39:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-01-23 13:18:27 +03:00
|
|
|
* Gets real ``Axis`` value by screen position.
|
|
|
|
* @param scr_pos screen position.
|
2018-01-23 11:39:01 +03:00
|
|
|
*/
|
2018-01-23 13:18:27 +03:00
|
|
|
public virtual Float128 axis_val (double scr_pos) {
|
|
|
|
return is_x ? range.zmin + ((scr_pos - chart.plarea.x0) / chart.plarea.width - place.zmin) * range.zrange / place.zrange
|
|
|
|
: range.zmin + ((chart.plarea.y1 - scr_pos) / chart.plarea.height - place.zmin) * range.zrange / place.zrange;
|
2018-01-23 11:39:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
protected virtual void calc_rec_sizes (Axis axis, out double max_rec_width, out double max_rec_height, bool horizontal = true) {
|
|
|
|
max_rec_width = max_rec_height = 0;
|
|
|
|
for (var i = 0; i < axis.nrecords; ++i) {
|
|
|
|
Float128 x = (int64)(axis.range.zmin + axis.range.zrange / axis.nrecords * i) + 1/3.0;
|
|
|
|
switch (axis.dtype) {
|
|
|
|
case Axis.DType.NUMBERS:
|
|
|
|
var text = new Text (chart, axis.format.printf((LongDouble)x) + (horizontal ? "_" : ""), axis.font);
|
|
|
|
max_rec_width = double.max (max_rec_width, text.width);
|
|
|
|
max_rec_height = double.max (max_rec_height, text.height);
|
|
|
|
break;
|
|
|
|
case Axis.DType.DATE_TIME:
|
|
|
|
string date, time;
|
|
|
|
axis.print_dt(x, out date, out time);
|
|
|
|
|
|
|
|
var h = 0.0;
|
|
|
|
if (axis.date_format != "") {
|
|
|
|
var text = new Text (chart, date + (horizontal ? "_" : ""), axis.font);
|
|
|
|
max_rec_width = double.max (max_rec_width, text.width);
|
|
|
|
h = text.height;
|
|
|
|
}
|
|
|
|
if (axis.time_format != "") {
|
|
|
|
var text = new Text (chart, time + (horizontal ? "_" : ""), axis.font);
|
|
|
|
max_rec_width = double.max (max_rec_width, text.width);
|
|
|
|
h += text.height;
|
|
|
|
}
|
|
|
|
max_rec_height = double.max (max_rec_height, h);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-23 13:36:56 +03:00
|
|
|
protected virtual void join_rel_axes (int si,
|
|
|
|
bool calc_max_values,
|
|
|
|
ref double max_rec_width,
|
|
|
|
ref double max_rec_height,
|
|
|
|
ref double max_font_spacing,
|
|
|
|
ref double max_axis_font_size,
|
|
|
|
ref int nskip) {
|
2018-01-23 11:39:01 +03:00
|
|
|
for (int sj = si - 1; sj >= 0; --sj) {
|
|
|
|
var s2 = chart.series[sj];
|
|
|
|
if (!s2.zoom_show) continue;
|
|
|
|
bool has_intersection = false;
|
2018-01-23 13:46:55 +03:00
|
|
|
Axis a2 = s2.axis_x; if (!is_x) a2 = s2.axis_y;
|
2018-01-23 11:39:01 +03:00
|
|
|
for (int sk = si; sk > sj; --sk) {
|
|
|
|
var s3 = chart.series[sk];
|
|
|
|
if (!s3.zoom_show) continue;
|
2018-01-23 13:46:55 +03:00
|
|
|
Axis a3 = s3.axis_x; if (!is_x) a3 = s3.axis_y;
|
|
|
|
if (Math.coord_cross(a2.place.zmin, a2.place.zmax, a3.place.zmin, a3.place.zmax)
|
|
|
|
|| a2.position != a3.position || a2.dtype != a3.dtype) {
|
2018-01-23 13:40:45 +03:00
|
|
|
has_intersection = true;
|
|
|
|
break;
|
2018-01-23 11:39:01 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!has_intersection) {
|
|
|
|
if (calc_max_values) {
|
|
|
|
var tmp_max_rec_width = 0.0, tmp_max_rec_height = 0.0;
|
2018-01-23 13:46:55 +03:00
|
|
|
calc_rec_sizes (a2, out tmp_max_rec_width, out tmp_max_rec_height, is_x);
|
2018-01-23 11:39:01 +03:00
|
|
|
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-23 13:46:55 +03:00
|
|
|
max_font_spacing = double.max (max_font_spacing, is_x ? a2.font.vspacing : a2.font.hspacing);
|
|
|
|
max_axis_font_size = double.max (max_axis_font_size,
|
|
|
|
a2.title.text == "" ? 0
|
|
|
|
: is_x ? a2.title.height + font.vspacing
|
|
|
|
: a2.title.width + font.hspacing);
|
2018-01-23 11:39:01 +03:00
|
|
|
}
|
|
|
|
++nskip;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-23 17:11:13 +03:00
|
|
|
protected virtual void draw_recs (Float128 min, Float128 step, double max_rec_size) {
|
|
|
|
for (Float128 v = min; Math.point_belong (v, min, range.zmax); v += step) {
|
2018-01-23 17:01:47 +03:00
|
|
|
if (is_x && chart.joint_x || !is_x && chart.joint_y) {
|
|
|
|
chart.color = chart.joint_color;
|
|
|
|
ser.grid.style.color = Color(0, 0, 0, 0.5);
|
2018-01-23 18:27:09 +03:00
|
|
|
} else
|
|
|
|
chart.color = color;
|
2018-01-23 18:08:33 +03:00
|
|
|
string text = "", time_text = ""; var time_text_t = new Text(chart); var crpt = 0.0;
|
2018-01-23 12:41:20 +03:00
|
|
|
switch (dtype) {
|
2018-01-23 16:37:46 +03:00
|
|
|
case Axis.DType.NUMBERS: text = format.printf((LongDouble)v); break;
|
2018-01-23 18:06:13 +03:00
|
|
|
case Axis.DType.DATE_TIME:
|
|
|
|
print_dt(v, out text, out time_text);
|
|
|
|
time_text_t = new Text(chart, time_text, font, color);
|
2018-01-23 18:08:33 +03:00
|
|
|
crpt = compact_rec_pos (v, time_text_t);
|
2018-01-23 18:06:13 +03:00
|
|
|
break;
|
2018-01-23 11:39:01 +03:00
|
|
|
}
|
2018-01-23 16:37:46 +03:00
|
|
|
var scr_v = scr_pos (v);
|
2018-01-23 12:41:20 +03:00
|
|
|
var text_t = new Text(chart, text, font, color);
|
2018-01-23 18:01:18 +03:00
|
|
|
var tthv = title.text == "" ? font.vspacing : title.height + 2 * font.vspacing;
|
|
|
|
var ttwh = title.text == "" ? font.hspacing : title.width + 2 * font.hspacing;
|
2018-01-23 17:40:37 +03:00
|
|
|
var dtf = (date_format == "" ? 0 : text_t.height + font.vspacing);
|
2018-01-23 17:57:08 +03:00
|
|
|
var py0 = chart.plarea.y0, ey0 = chart.evarea.y0, py1 = chart.plarea.y1, ey1 = chart.evarea.y1;
|
2018-01-23 18:01:18 +03:00
|
|
|
var px0 = chart.plarea.x0, ex0 = chart.evarea.x0, px1 = chart.plarea.x1, ex1 = chart.evarea.x1;
|
|
|
|
var ph = chart.plarea.height, pw = chart.plarea.width;
|
2018-01-23 12:41:20 +03:00
|
|
|
switch (position) {
|
2018-01-23 11:39:01 +03:00
|
|
|
case Axis.Position.LOW:
|
2018-01-23 16:37:46 +03:00
|
|
|
if (is_x) {
|
2018-01-23 17:57:08 +03:00
|
|
|
var print_y = ey1 - tthv;
|
2018-01-23 18:22:52 +03:00
|
|
|
chart.ctx.move_to (compact_rec_pos (v, text_t), print_y); text_t.show();
|
|
|
|
if (dtype == Axis.DType.DATE_TIME)
|
|
|
|
{ chart.ctx.move_to (crpt, print_y - dtf); time_text_t.show(); }
|
2018-01-23 17:01:47 +03:00
|
|
|
ser.grid.style.apply(chart);
|
2018-01-23 18:27:09 +03:00
|
|
|
double y = ey1 - max_rec_size - tthv; chart.ctx.move_to (scr_v, y);
|
2018-01-23 17:57:08 +03:00
|
|
|
if (chart.joint_x) chart.ctx.line_to (scr_v, py0);
|
2018-01-23 18:01:18 +03:00
|
|
|
else chart.ctx.line_to (scr_v, double.min (y, py0 + ph * (1 - ser.axis_y.place.zmax)));
|
2018-01-23 16:37:46 +03:00
|
|
|
} else {
|
2018-01-23 18:01:18 +03:00
|
|
|
chart.ctx.move_to (ex0 + max_rec_size - text_t.width + ttwh, compact_rec_pos (v, text_t));
|
2018-01-23 18:27:09 +03:00
|
|
|
text_t.show(); ser.grid.style.apply(chart);
|
2018-01-23 18:01:18 +03:00
|
|
|
double x = ex0 + max_rec_size + ttwh;
|
2018-01-23 17:08:55 +03:00
|
|
|
chart.ctx.move_to (x, scr_v);
|
2018-01-23 18:01:18 +03:00
|
|
|
if (chart.joint_y) chart.ctx.line_to (px1, scr_v);
|
|
|
|
else chart.ctx.line_to (double.max (x, px0 + pw * ser.axis_x.place.zmax), scr_v);
|
2018-01-23 11:39:01 +03:00
|
|
|
}
|
2018-01-23 18:16:52 +03:00
|
|
|
break;
|
2018-01-23 11:39:01 +03:00
|
|
|
case Axis.Position.HIGH:
|
2018-01-23 16:37:46 +03:00
|
|
|
if (is_x) {
|
2018-01-23 17:57:08 +03:00
|
|
|
var print_y = ey0 + max_rec_size + tthv;
|
2018-01-23 17:38:51 +03:00
|
|
|
chart.ctx.move_to (compact_rec_pos (v, text_t), print_y);
|
2018-01-23 16:37:46 +03:00
|
|
|
switch (dtype) {
|
2018-01-23 17:08:55 +03:00
|
|
|
case Axis.DType.NUMBERS: text_t.show(); break;
|
2018-01-23 16:37:46 +03:00
|
|
|
case Axis.DType.DATE_TIME:
|
2018-01-23 18:14:39 +03:00
|
|
|
text_t.show(); chart.ctx.move_to (crpt, print_y - dtf); time_text_t.show();
|
2018-01-23 16:37:46 +03:00
|
|
|
break;
|
|
|
|
}
|
2018-01-23 17:01:47 +03:00
|
|
|
ser.grid.style.apply(chart);
|
2018-01-23 18:27:09 +03:00
|
|
|
double y = ey0 + max_rec_size + tthv; chart.ctx.move_to (scr_v, y);
|
2018-01-23 17:57:08 +03:00
|
|
|
if (chart.joint_x) chart.ctx.line_to (scr_v, py1);
|
2018-01-23 18:01:18 +03:00
|
|
|
else chart.ctx.line_to (scr_v, double.max (y, py0 + ph * (1 - ser.axis_y.place.zmin)));
|
2018-01-23 16:37:46 +03:00
|
|
|
} else {
|
2018-01-23 18:01:18 +03:00
|
|
|
chart.ctx.move_to (ex1 - text_t.width - ttwh, compact_rec_pos (v, text_t));
|
2018-01-23 18:27:09 +03:00
|
|
|
text_t.show(); ser.grid.style.apply(chart);
|
2018-01-23 18:01:18 +03:00
|
|
|
double x = ex1 - max_rec_size - ttwh;
|
2018-01-23 17:08:55 +03:00
|
|
|
chart.ctx.move_to (x, scr_v);
|
2018-01-23 18:01:18 +03:00
|
|
|
if (chart.joint_y) chart.ctx.line_to (px0, scr_v);
|
|
|
|
else chart.ctx.line_to (double.min (x, px0 + pw * ser.axis_x.place.zmin), scr_v);
|
2018-01-23 11:39:01 +03:00
|
|
|
}
|
2018-01-23 18:16:52 +03:00
|
|
|
break;
|
2018-01-23 11:39:01 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-08-28 14:47:31 +03:00
|
|
|
}
|
|
|
|
}
|