1 module plotcli.data; 2 3 import std.typecons : Tuple; 4 5 version( unittest ) 6 { 7 import dunit.toolkit; 8 } 9 10 11 import plotcli.options : Options, OptionRange; 12 13 /** 14 Default values to use for many of the settings. 15 */ 16 auto aesDefaults() 17 { 18 import ggplotd.aes : DefaultValues, merge; 19 return DefaultValues.merge(Tuple!(string, "legend", string, "plotID", string, "type", 20 string, "plotname", string, "format", string, "xlabel", string, "ylabel", 21 string, "colourgradient", double, "x", double, "y", 22 string, "colour") 23 ( "", "", "point", "plotcli", 24 "png", "x", "y", "default", double.init, double.init, "black") ); 25 } 26 27 private int safeToIndex( string str ) 28 { 29 import std.conv : to; 30 import std.range : empty; 31 if (str.empty) 32 return -1; 33 else 34 return str.to!int; 35 } 36 37 auto toTuples( string[] columns, Options options, int lineCount ) 38 { 39 // TODO use generate? 40 struct Tuples 41 { 42 import std.regex : ctRegex, match; 43 this( string[] columns, Options options, int lineCount ) 44 { 45 import std.range : empty, repeat; 46 import std.array : array; 47 _columns = columns; 48 _lineCount = lineCount; 49 _options = options; 50 } 51 52 @property bool empty() 53 { 54 import std.conv : to; 55 import std.range : empty, front; 56 return ( 57 (!_options.values["x"].empty 58 && _options.values["x"].front.safeToIndex 59 >= _columns.length.to!int) 60 || (!_options.values["y"].empty 61 && _options.values["y"].front.safeToIndex 62 >= _columns.length.to!int) 63 || (_options.values["x"].empty && _options.values["y"].empty) 64 ); 65 } 66 67 @property auto front() 68 { 69 import std.conv : to; 70 import std.range : empty, front; 71 import ggplotd.aes : DefaultValues, merge; 72 import plotcli.parse : isInteger; 73 74 auto tuple = aesDefaults().merge(Tuple!(double, "x", double, "y", 75 string, "colour" ) 76 ( lineCount.to!double, lineCount.to!double, columnID.to!string ) 77 ); 78 79 foreach( i, field; tuple.fieldNames ) 80 { 81 if (field in _options.values && !_options.values[field].empty) 82 { 83 auto f = _options.values[field].front; 84 if (f.isInteger) 85 { 86 tuple[i] = _columns[f.to!int].to!(typeof(tuple[i])); 87 } else if (!f.empty) { 88 tuple[i] = f.to!(typeof(tuple[i])); 89 } 90 } 91 } 92 // I hoped special cases for types would not have been needed 93 // but I do not see a better way/we want to group by column 94 if (tuple.type == "box") 95 tuple.y = columnID.to!double; 96 return tuple; 97 } 98 99 void popFront() 100 { 101 import std.range : empty, popFront; 102 foreach( k, ref v; _options.values ) 103 if (!v.empty) 104 v.popFront; 105 ++columnID; 106 } 107 108 string[] _columns; 109 Options _options; 110 int columnID = 0; 111 int _lineCount; 112 } 113 114 return Tuples( columns, options, lineCount ); 115 } 116 117 unittest 118 { 119 Options options; 120 options.values["y"] = OptionRange!string("1"); 121 122 auto ts = ["1","2","3"].toTuples( options, -1 ); 123 assertEqual( ts.front.x, -1 ); 124 assertEqual( ts.front.y, 2 ); 125 126 options.values["x"] = OptionRange!string("2"); 127 128 ts = ["1","2","3"].toTuples( options, -1 ); 129 assertEqual( ts.front.x, 3 ); 130 assert(!ts.empty); 131 ts.popFront; 132 assert(ts.empty); 133 134 options.values["x"] = OptionRange!string("0,1,.."); 135 ts = ["1","2","3"].toTuples( options, -1 ); 136 assertEqual( ts.front.x, 1 ); 137 ts.popFront; 138 assertEqual( ts.front.x, 2 ); 139 ts.popFront; 140 assertEqual( ts.front.x, 3 ); 141 ts.popFront; 142 assert( ts.empty ); 143 144 import plotcli.options : defaultOptions; 145 options = defaultOptions(); 146 options.values["x"] = OptionRange!string("0,0,1,0,1"); 147 options.values["y"] = OptionRange!string(",2,,,"); 148 ts = ["0.04", "3.22", "-0.27"].toTuples( options, -1 ); 149 import std.stdio; 150 assertEqual( ts.front.x, 0.04 ); 151 assertEqual( ts.front.y, -1 ); 152 ts.popFront; 153 assertEqual( ts.front.x, 0.04 ); 154 assertEqual( ts.front.y, -0.27 ); 155 } 156 157