The OpenD Programming Language

1 module ggplotd.scale;
2 
3 import cairo = cairo.cairo;
4 import ggplotd.bounds : Bounds, width, height;
5 
6 version (unittest)
7 {
8     import dunit.toolkit;
9 }
10 
11 alias ScaleType =
12     cairo.Context delegate(cairo.Context context, in Bounds bounds,
13     double width, double height);
14 
15 /// Scale context by plot boundaries
16 ScaleType scale()
17 {
18     return (cairo.Context context, in Bounds bounds,
19         double pixelWidth, double pixelHeight) {
20         context.translate(0, pixelHeight);
21         context.scale(pixelWidth / bounds.width, -pixelHeight / bounds.height);
22         context.translate(-bounds.min_x, -bounds.min_y);
23         return context;
24     };
25 }
26 
27 struct ScaleFunction
28 {
29     string field;
30 
31     this(string fld) {
32         field = fld;
33     }
34 
35     double delegate(double) scale;
36 }
37 
38 unittest
39 {
40     auto sf = ScaleFunction("bla");
41     assertEqual(sf.field, "bla");
42 }
43 
44 auto scale(string field = "")(string type)
45 {
46     auto sf = ScaleFunction(field);
47     import std.math : log;
48     if (type == "log10") {
49         sf.scale = (v) { return log(v)/log(10.0); };
50     } else if (type == "log") {
51         sf.scale = (v) { return log(v); };
52     } else if (type == "polar") {
53         assert(0, "Polar not implemented yet");
54     } else {
55         // dummy
56         sf.scale = (v) { return v; };
57     }
58     return sf;
59 }
60 
61 unittest
62 {
63     auto sf = scale!"bla"("log10");
64     assertEqual(sf.scale(10.0), 1);
65 }
66 
67 ///
68 void applyScaleFunction(F, X, Y, Col, Size)(in F func,
69     ref X x, ref Y y, ref Col col, ref Size size)
70 {
71     if (func.field == "x")
72         x.scaleFunction = func.scale;
73     else if (func.field == "y")
74         y.scaleFunction = func.scale;
75     else if (func.field == "colour")
76         col.scaleFunction = func.scale;
77     else if (func.field == "size")
78         size.scaleFunction = func.scale;
79 }
80 
81 auto applyScale(B, XF, XStore, YF, YStore)(ref B bounds,
82     XF xf, XStore xStore, YF yf, YStore yStore)
83 {
84     import ggplotd.algorithm : safeMax, safeMin;
85     import std.range : iota;
86     import std.algorithm : each;
87     auto xmin = xStore.min();
88     auto xmax = xStore.max();
89     auto ymin = yStore.min();
90     auto ymax = yStore.max();
91     if (!xf.scaleFunction.isNull)
92     {
93         // xmax won't be included in the iota so use that to initialize
94         xmax = xf.scaleFunction.get()(xmax);
95         xmin = xf.scaleFunction.get()(xmax);
96         foreach(x; iota(xmin, xmax, (xmax-xmin)/100.0))
97         {
98             xmin = safeMin(xmin, xf.scaleFunction.get()(x));
99             xmax = safeMax(xmax, xf.scaleFunction.get()(x));
100         }
101     }
102     if (!yf.scaleFunction.isNull)
103     {
104         // ymax won't be included in the iota so use that to initialize
105         ymax = yf.scaleFunction.get()(ymax);
106         ymin = yf.scaleFunction.get()(ymax);
107         foreach(y; iota(ymin, ymax, (ymax-ymin)/100.0))
108         {
109             ymin = safeMin(ymin, yf.scaleFunction.get()(y));
110             ymax = safeMax(ymax, yf.scaleFunction.get()(y));
111         }
112     }
113 
114     bounds.adapt(xmin, ymin);
115     bounds.adapt(xmax, ymax);
116     return bounds;
117 }