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 }