1 ///
2 module plot2d.trtor;
3 
4 import plot2d.types;
5 import plot2d.util;
6 
7 ///
8 class Trtor
9 {
10 protected:
11     Point _size, _scale, _offset, _gridStep, _gridOffset;
12     Viewport _viewport;
13     Border _margin, _padding;
14 
15 public:
16     ///
17     this(){}
18 
19     @property
20     {
21         const
22         {
23             /// drawing area size
24             ref const(Point) size() { return _size; }
25             /// chart margin (axis offset)
26             ref const(Border) margin() { return _margin; }
27             ///
28             Viewport inMargin()
29             {
30                 return Viewport(
31                     DimSeg(_margin.left, _size.x - _margin.right),
32                     DimSeg(_margin.top, _size.y - _margin.bottom)
33                 );
34             }
35             ///
36             Viewport inPadding()
37             {
38                 return Viewport(
39                     DimSeg(_margin.left + _padding.left,
40                            _size.x - _margin.right - _padding.right),
41                     DimSeg(_margin.top + _padding.top,
42                            _size.y - _margin.bottom - _padding.bottom)
43                 );
44             }
45 
46             /// chart padding inside axis
47             ref const(Border) padding() { return _padding; }
48             /// limits of displayed values of data
49             ref const(Viewport) viewport() { return _viewport; }
50             /// transform coeficient
51             ref const(Point) scale() { return _scale; }
52             /// ditto
53             ref const(Point) offset() { return _offset; }
54             ///
55             ref const(Point) gridStep() { return _gridStep; }
56             ///
57             ref const(Point) gridOffset() { return _gridOffset; }
58         }
59 
60         ///
61         void size(Point s) { _size = s; recalc(); }
62 
63         ///
64         void margin(Border m) { _margin = m; recalc(); }
65 
66         ///
67         void padding(Border p) { _padding = p; recalc(); }
68 
69         ///
70         void viewport(Viewport v) { _viewport = v; recalc(); }
71 
72         ///
73         void gridStep(Point s) { _gridStep = s; }
74 
75         ///
76         void gridOffset(Point s) { _gridOffset = s; }
77     }
78 
79     ///
80     void setSMPV(Point size, Border margin,
81                  Border padding, Viewport viewport)
82     {
83         _size = size;
84         _margin = margin;
85         _padding = padding;
86         _viewport = viewport;
87         recalc();
88     }
89 
90     ///
91     void recalc()
92     {
93         _scale = calcScale(_viewport);
94         recalcOffset(); // scale used
95     }
96 
97     ///
98     Point calcScale()(auto ref const Viewport v)
99     {
100         alias m = _margin;
101         alias p = _padding;
102         return Point(
103             (_size.x - m.sx - p.sx) / v.w.diff,
104             (-_size.y + m.sy + p.sy) / v.h.diff);
105         //   ^-- drawing area has downside Y direction
106     }
107 
108     ///
109     void recalcOffset()
110     {
111         alias m = _margin;
112         alias p = _padding;
113         alias s = _size;
114         alias v = _viewport;
115         _offset = Point(m.left + p.left,
116                         s.y - m.bottom - p.bottom)
117                         - Point(v.w.min, v.h.min) * _scale;
118     }
119 
120     ///
121     void correctGridOffset()
122     {
123         auto im = inMargin;
124         auto orig = Point(im.w.min, im.h.max);
125         auto p0 = toCh(orig);
126         auto chgs = _gridStep / _scale;
127         p0 = Point(p0.x.quantize!ceil(chgs.x),
128                    p0.y.quantize!ceil(-chgs.y));
129         auto r = toDA(p0) - orig;
130         _gridOffset = Point(r.x, -r.y);
131     }
132 
133     ///
134     void optimizeGridStep(Point minGridCellSize)
135     {
136         double cs(double m, double delegate(double) stepper)
137         {
138             auto mv = max(abs(m), double.epsilon * 1e2);
139             double rs;
140             if (stepper is null)
141                 rs = roundStepFunc!10(mv);
142             else
143                 rs = stepper(mv);
144             return mv.quantize!ceil(rs);
145         }
146 
147         auto m = minGridCellSize / _scale;
148         auto r = Point(cs(m.x, calcXGridStep),
149                        cs(m.y, calcYGridStep)) * _scale;
150         _gridStep = Point(abs(r.x), abs(r.y));
151     }
152 
153     ///
154     double delegate(double) calcXGridStep, calcYGridStep;
155 
156     import std.traits : isNumeric;
157 
158     ///
159     static double roundStepFunc(alias bais)(double val) nothrow @nogc
160         if (isNumeric!(typeof(bais)))
161     {
162         alias mlog = std.math.log;
163         enum z = mlog(bais);
164         return bais ^^ floor(mlog(val) / z);
165     }
166 
167     const pure nothrow @nogc @safe
168     {
169         /// transfrom coord from chart to drawing area
170         Point toDA(double x, double y) { return toDA(Point(x,y)); }
171         /// ditto
172         Point toDA(Point p) { return p * _scale + _offset; }
173         /// ditto
174         double toDAX(double x) { return x * _scale.x + _offset.x; }
175         /// ditto
176         double toDAY(double y) { return y * _scale.y + _offset.y; }
177 
178         /// transform coord from drawing area to chart
179         Point toCh(double x, double y) { return toCh(Point(x,y)); }
180         /// ditto
181         Point toCh(Point p) { return (p - _offset) / _scale; }
182         /// ditto
183         double toChX(double x) { return (x - _offset.x) / _scale.x; }
184         /// ditto
185         double toChY(double y) { return (y - _offset.y) / _scale.y; }
186     }
187 }