1 ///
2 module plot2d.plot;
3 
4 import std.datetime.stopwatch;
5 
6 import std.stdio;
7 
8 import plot2d.drawable;
9 import plot2d.axis;
10 import plot2d.label;
11 import plot2d.style;
12 import plot2d.trtor;
13 import plot2d.chart;
14 import plot2d.util;
15 
16 ///
17 class Plot
18 {
19 public:
20     ///
21     struct Settings
22     {
23         ///
24         Viewport viewport = {w: DimSeg(0, 1), h: DimSeg(0, 1)};
25 
26         /// use summury charts viewports
27         bool autoFit = true;
28 
29         ///
30         Point minGridStep = Point(20,20);
31 
32         /// from border to axis
33         Border margin = Border(60, 10, 40, 40);
34 
35         /// from axis to charts
36         Border padding = Border(0, 10);
37     }
38 
39     ///
40     Settings settings;
41 
42     ///
43     Trtor tr;
44     /// namespaced style
45     NSStyle style;
46 
47     ///
48     Axis axis;
49     ///
50     Grid grid;
51     ///
52     LBGridLabel label;
53 
54     ///
55     Chart[] charts;
56 
57     ///
58     this()
59     {
60         tr = new Trtor;
61         style = new NSStyle;
62 
63         axis = new LBAxis(style);
64         grid = new Grid(style);
65         grid.dash = [5, 8];
66         label = new LBGridLabel(style);
67     }
68 
69     ///
70     T add(T)(T ch) if (is(T : Chart))
71     {
72         charts ~= ch;
73         if (auto sch = cast(Stylized)ch)
74             sch.setRootStyle(style);
75         return ch;
76     }
77 
78     ///
79     void updateCharts()
80     {
81         foreach (c; charts)
82             c.update();
83     }
84 
85     ///
86     void draw(Ctx cr, Point size)
87     {
88         if (recalculateTrtor(cr, size)) return;
89         drawElements(cr);
90     }
91 
92 protected:
93 
94     int recalculateTrtor(Ctx cr, Point sz)
95     {
96         auto vp = getViewport();
97 
98         tr.setSMPV(sz, settings.margin, settings.padding, vp);
99 
100         Point lmgs;
101         {
102             mixin(scopeSave!cr);
103             lmgs = label.minGridStep(cr);
104         }
105 
106         tr.optimizeGridStep(
107             Point(max(settings.minGridStep.x, lmgs.x * 1.25),
108                   max(settings.minGridStep.y, lmgs.y * 1.25)));
109 
110         // invert '<=' to '>' for nan-check
111         if (tr.gridStep.x > 0 && tr.gridStep.y > 0) { }
112         else
113         {
114             .warning("bad grid step: ", tr.gridStep);
115             return 1;
116         }
117 
118         tr.correctGridOffset();
119         return 0;
120     }
121 
122     Viewport getViewport()
123     {
124         Viewport ret = settings.viewport;
125 
126         if (charts.length == 0 ||
127             !settings.autoFit) return ret;
128 
129         bool s = false; // for setup first viewport
130         foreach (c; charts.filter!"a.visible")
131         {
132             if (s) ret.expand(c.viewport);
133             else { ret = c.viewport; s = true; }
134         }
135 
136         double minViewDiff = double.epsilon * 1e3;
137 
138         if (abs(ret.w.diff) < minViewDiff)
139             ret.w.stepExpand(minViewDiff);
140 
141         if (abs(ret.h.diff) < minViewDiff)
142             ret.h.stepExpand(minViewDiff);
143 
144         return ret;
145     }
146 
147     void drawElements(Ctx cr)
148     {
149         foreach (p; chain(only(cast(Drawable)axis,
150                                cast(Drawable)grid,
151                                cast(Drawable)label),
152                     charts.map!(a=>cast(Drawable)a)))
153         {
154             if (p is null) continue;
155             mixin(scopeSave!cr);
156             if (auto c = cast(Chart)p)
157                 if (!c.visible) continue;
158             p.draw(cr, tr);
159         }
160     }
161 }