1 module plot2d.label;
2 
3 import std.exception : enforce;
4 import plot2d.drawable;
5 
6 interface Label : Drawable, Stylized
7 {
8     static interface Formatter
9     {
10         string x(double value, double step);
11         string maxXValue() @property;
12 
13         string y(double value, double step);
14         string maxYValue() @property;
15     }
16 
17     ///
18     static auto defaultFmtFunc(double value, double step)
19     {
20         import std..string;
21         auto sf = format("%f", step).tr("0", " ").strip.split(".");
22         return format("%.*f", sf[1].length, value);
23     }
24 
25     ///
26     static class DefaultFormatter : Formatter
27     {
28         string maxX = "0.0", maxY = "0.0";
29     override:
30         string x(double v, double s)
31         {
32             auto r = defaultFmtFunc(v, s);
33             if (r.length > maxX.length) maxX = r;
34             return r;
35         }
36 
37         string y(double v, double s)
38         {
39             auto r = defaultFmtFunc(v, s);
40             if (r.length > maxY.length) maxY = r;
41             return r;
42         }
43 
44         string maxXValue() @property { return maxX; }
45         string maxYValue() @property { return maxY; }
46     }
47 }
48 
49 class LBGridLabel : Label
50 {
51     mixin StylizedHelper!"label";
52 
53     Formatter formatter;
54 
55     double lineSpacing = 1.5;
56 
57     bool onX = true;
58     bool onY = true;
59 
60     Point space = Point(8, 8);
61 
62     this(Style root, Formatter fmt=null)
63     {
64         setRootStyle(root);
65         formatter = fmt is null ? new DefaultFormatter : fmt;
66     }
67 
68     Point minGridStep(Ctx cr)
69     {
70         setupStyle(cr);
71 
72         Point xte, yte;
73         cr.getTextSize(formatter.maxXValue, xte.x, xte.y);
74         cr.getTextSize(formatter.maxYValue, yte.x, xte.y);
75         return Point(xte.x * 1.2, yte.y * 2.5);
76     }
77 
78     override void draw(Ctx cr, Trtor tr)
79     {
80         setupStyle(cr);
81 
82         auto fontsize = style.number.get("fontsize", 15);
83 
84         auto im = tr.inMargin;
85         auto s = tr.gridStep;
86         auto o = tr.gridOffset;
87         auto chs = s / tr.scale;
88         chs.y *= -1;
89 
90         Point te;
91 
92         if (onX) for (double i = im.w.min + o.x; i <= im.w.max; i += s.x)
93         {
94             auto str = formatter.x(tr.toChX(i), chs.x);
95             auto pnt = Point(i, im.h.max + space.y);
96             foreach (n, ln; str.split("\n"))
97             {
98                 cr.getTextSize(ln, te.x, te.y);
99                 if (n == 0 && (i - te.x/2 < im.w.min ||
100                                i + te.x/2 > im.w.max)) break;
101                 cr.moveToP(pnt + Point(-te.x/2, te.y));
102                 cr.showText(ln);
103                 pnt.y += fontsize * lineSpacing;
104             }
105         }
106 
107         if (onY) for (double i = im.h.max - o.y; i > im.h.min; i -= s.y)
108         {
109             auto str = formatter.y(tr.toChY(i), chs.y);
110             auto pnt = Point(im.w.min - space.x, i);
111             foreach (ln; str.split("\n"))
112             {
113                 cr.getTextSize(ln, te.x, te.y);
114                 cr.moveToP(pnt + Point(-te.x, te.y/2));
115                 cr.showText(ln);
116                 pnt.y += fontsize * lineSpacing;
117             }
118         }
119     }
120 
121     void setupStyle(Ctx cr)
122     {
123         cr.setFont(style.strval.get("fontface", "Monospace"),
124                    style.number.get("fontsize", 15));
125 
126         cr.setColor(style.color.get("color", Color.mono(0, 1)));
127     }
128 }