1 module ggplotd.aes;
2 
3 import std.range : front, popFront, empty;
4 
5 version (unittest)
6 {
7     import dunit.toolkit;
8 }
9 
10 import std.typecons : Tuple;
11 
12 // TODO Also update default grouping if appropiate
13 
14 ///
15 static auto DefaultValues = Tuple!( 
16     string, "label", string, "colour", double, "size",
17     double, "angle", double, "alpha", bool, "mask", double, "fill" )
18     ("", "black", 1.0, 0, 1, true, 0.0);
19 
20 /++
21     Aes is used to store and access data for plotting
22 
23     Aes is an InputRange, with named Tuples as elements. The names
24     refer to certain fields, such as x, y, colour etc. If certain fields
25     are not provided then it provides a default value (see DefaultValues).
26 
27     The fields commonly used are data fields, such as "x" and "y". Which data
28     fields are required depends on the geom* function. By default the named 
29     Tuple holds the fields:
30     $(UL
31         $(LI "label": Text labels (string))
32         $(LI "colour": Identifier for the colour. In general data points with different colour ids get different colours. This can be almost any type. You can also specify the colour by name or cairo.Color type if you want to specify an exact colour (any type that isNumeric, cairo.Color.RGB(A), or can be converted to string))
33         $(LI "size": Gives the relative size of points/lineWidth etc.
34         $(LI "angle": Angle of printed labels in radians (double))
35         $(LI "alpha": Alpha value of the drawn object (double))
36         $(LI "mask": Mask the area outside the axes. Prevents you from drawing outside of the area (bool))
37         $(LI "fill": Whether to fill the object/holds the alpha value to fill with (double).))
38     +/
39 template Aes(Specs...)
40 {
41     import std.traits : Identity;
42     import std.typecons : isTuple;
43     import std.typetuple : staticMap, TypeTuple;
44     import std.range : isInputRange;
45 
46     // Parse (type,name) pairs (FieldSpecs) out of the specified
47     // arguments. Some fields would have name, others not.
48     template parseSpecs(Specs...)
49     {
50         static if (Specs.length == 0)
51         {
52             alias parseSpecs = TypeTuple!();
53         }
54         else static if (is(Specs[0]) && isInputRange!(Specs[0]))
55         {
56             static if (is(typeof(Specs[1]) : string))
57             {
58                 alias parseSpecs = TypeTuple!(FieldSpec!(Specs[0 .. 2]), parseSpecs!(Specs[2 .. $]));
59             }
60             else
61             {
62                 alias parseSpecs = TypeTuple!(FieldSpec!(Specs[0]), parseSpecs!(Specs[1 .. $]));
63             }
64         }
65         else
66         {
67             static assert(0,
68                 "Attempted to instantiate Tuple with an " ~ "invalid argument: " ~ Specs[0].stringof);
69         }
70     }
71 
72     template FieldSpec(T, string s = "")
73     {
74         alias Type = T;
75         alias name = s;
76     }
77 
78     alias fieldSpecs = parseSpecs!Specs;
79 
80     // Used with staticMap.
81     alias extractType(alias spec) = spec.Type;
82     alias extractName(alias spec) = spec.name;
83 
84     // Generates named fields as follows:
85     //    alias name_0 = Identity!(field[0]);
86     //    alias name_1 = Identity!(field[1]);
87     //      :
88     // NOTE: field[k] is an expression (which yields a symbol of a
89     //       variable) and can't be aliased directly.
90     string injectNamedFields()
91     {
92         string decl = "";
93 
94         foreach (i, name; staticMap!(extractName, fieldSpecs))
95         {
96             import std.format : format;
97 
98             decl ~= format("alias _%s = Identity!(field[%s]);", i, i);
99             if (name.length != 0)
100             {
101                 decl ~= format("alias %s = _%s;", name, i);
102             }
103         }
104         return decl;
105     }
106 
107     alias fieldNames = staticMap!(extractName, fieldSpecs);
108 
109     string injectFront()
110     {
111         import std.format : format;
112 
113         string decl = "auto front() { import std.range : ElementType;";
114         decl ~= "import std.typecons : Tuple; import std.range : front;";
115 
116         string tupleType = "Tuple!(";
117         string values = "(";
118 
119         foreach (i, name; fieldNames)
120         {
121 
122             tupleType ~= format(q{typeof(%s.front),}, name);
123             tupleType ~= "q{" ~ name ~ "},";
124             values ~= format("this.%s.front,", name);
125         }
126 
127         decl ~= "return " ~ tupleType[0 .. $ - 1] ~ ")" ~ values[0 .. $ - 1] ~ "); }";
128         //string decl2 = format("auto front() { import std.stdio; \"%s\".writeln; return 0.0; }", decl);
129         return decl;
130     }
131 
132     // Returns Specs for a subtuple this[from .. to] preserving field
133     // names if any.
134     alias sliceSpecs(size_t from, size_t to) = staticMap!(expandSpec, fieldSpecs[from .. to]);
135 
136     template expandSpec(alias spec)
137     {
138         static if (spec.name.length == 0)
139         {
140             alias expandSpec = TypeTuple!(spec.Type);
141         }
142         else
143         {
144             alias expandSpec = TypeTuple!(spec.Type, spec.name);
145         }
146     }
147 
148     enum areCompatibleTuples(Tup1, Tup2, string op) = isTuple!Tup2 && is(typeof({
149         Tup1 tup1 = void;
150         Tup2 tup2 = void;
151         static assert(tup1.field.length == tup2.field.length);
152         foreach (i, _;
153         Tup1.Types)
154         {
155             auto lhs = typeof(tup1.field[i]).init;
156             auto rhs = typeof(tup2.field[i]).init;
157             static if (op == "=")
158                 lhs = rhs;
159             else
160                 auto result = mixin("lhs " ~ op ~ " rhs");
161         }
162     }));
163 
164     enum areBuildCompatibleTuples(Tup1, Tup2) = isTuple!Tup2 && is(typeof({
165         static assert(Tup1.Types.length == Tup2.Types.length);
166         foreach (i, _;
167         Tup1.Types)
168         static assert(isBuildable!(Tup1.Types[i], Tup2.Types[i]));
169     }));
170 
171     /+ Returns $(D true) iff a $(D T) can be initialized from a $(D U). +/
172     enum isBuildable(T, U) = is(typeof({ U u = U.init; T t = u; }));
173     /+ Helper for partial instanciation +/
174     template isBuildableFrom(U)
175     {
176         enum isBuildableFrom(T) = isBuildable!(T, U);
177     }
178     struct Aes
179     {
180         /**
181          * The type of the tuple's components.
182          */
183         alias Types = staticMap!(extractType, fieldSpecs);
184 
185         /**
186          * The names of the tuple's components. Unnamed fields have empty names.
187          *
188          * Examples:
189          * ----
190          * alias Fields = Tuple!(int, "id", string, float);
191          * static assert(Fields.fieldNames == TypeTuple!("id", "", ""));
192          * ----
193          */
194         alias fieldNames = staticMap!(extractName, fieldSpecs);
195 
196         /**
197          * Use $(D t.expand) for a tuple $(D t) to expand it into its
198          * components. The result of $(D expand) acts as if the tuple components
199          * were listed as a list of values. (Ordinarily, a $(D Tuple) acts as a
200          * single value.)
201          *
202          * Examples:
203          * ----
204          * auto t = tuple(1, " hello ", 2.3);
205          * writeln(t);        // Tuple!(int, string, double)(1, " hello ", 2.3)
206          * writeln(t.expand); // 1 hello 2.3
207          * ----
208          */
209         Types expand;
210         mixin(injectNamedFields());
211 
212         // backwards compatibility
213         alias field = expand;
214 
215         /**
216          * Constructor taking one value for each field.
217          */
218         static if (Types.length > 0)
219         {
220             this(Types values)
221             {
222                 field[] = values[];
223             }
224         }
225 
226         /**
227          * Constructor taking a compatible array.
228          *
229          * Examples:
230          * ----
231          * int[2] ints;
232          * Tuple!(int, int) t = ints;
233          * ----
234          */
235         /+this(U, size_t n)(U[n] values) if (n == Types.length
236                 && allSatisfy!(isBuildableFrom!U, Types))
237         {
238             foreach (i, _; Types)
239             {
240                 field[i] = values[i];
241             }
242         }+/
243 
244         /**
245          * Constructor taking a compatible tuple.
246          */
247         this(U)(U another) if (areBuildCompatibleTuples!(typeof(this), U))
248         {
249             field[] = another.field[];
250         }
251 
252         mixin(injectFront());
253 
254         ///
255         void popFront()
256         {
257             import std.range : popFront;
258 
259             foreach (i, _; Types[0 .. $])
260             {
261                 field[i].popFront();
262             }
263         }
264 
265         ///
266         auto save() const
267         {
268             return this;
269         }
270 
271         ///
272         @property bool empty()
273         {
274             if (length == 0)
275                 return true;
276             return false;
277         }
278 
279         ///
280         size_t length()
281         {
282             import std.algorithm : min;
283             import std.range : walkLength, isInfinite;
284 
285             size_t l = size_t.max;
286             foreach (i, type; Types[0 .. $])
287             {
288                 static if (!isInfinite!type)
289                 {
290                     if (field[i].walkLength < l)
291                         l = field[i].walkLength;
292                 }
293             }
294             return l;
295         }
296 
297         /**
298          * Comparison for equality.
299          */
300         bool opEquals(R)(R rhs) if (areCompatibleTuples!(typeof(this), R, "=="))
301         {
302             return field[] == rhs.field[];
303         }
304 
305         /// ditto
306         bool opEquals(R)(R rhs) const if (areCompatibleTuples!(typeof(this), R, "=="))
307         {
308             return field[] == rhs.field[];
309         }
310 
311         /**
312          * Comparison for ordering.
313          */
314         int opCmp(R)(R rhs) if (areCompatibleTuples!(typeof(this), R, "<"))
315         {
316             foreach (i, Unused; Types)
317             {
318                 if (field[i] != rhs.field[i])
319                 {
320                     return field[i] < rhs.field[i] ? -1 : 1;
321                 }
322             }
323             return 0;
324         }
325 
326         /// ditto
327         int opCmp(R)(R rhs) const if (areCompatibleTuples!(typeof(this), R, "<"))
328         {
329             foreach (i, Unused; Types)
330             {
331                 if (field[i] != rhs.field[i])
332                 {
333                     return field[i] < rhs.field[i] ? -1 : 1;
334                 }
335             }
336             return 0;
337         }
338 
339         /**
340          * Assignment from another tuple. Each element of the source must be
341          * implicitly assignable to the respective element of the target.
342          */
343         void opAssign(R)(auto ref R rhs) if (areCompatibleTuples!(typeof(this), R,
344                 "="))
345         {
346             import std.algorithm : swap;
347 
348             static if (is(R : Tuple!Types) && !__traits(isRef, rhs))
349             {
350                 if (__ctfe)
351                 {
352                     // Cannot use swap at compile time
353                     field[] = rhs.field[];
354                 }
355                 else
356                 {
357                     // Use swap-and-destroy to optimize rvalue assignment
358                     swap!(Tuple!Types)(this, rhs);
359                 }
360             }
361             else
362             {
363                 // Do not swap; opAssign should be called on the fields.
364                 field[] = rhs.field[];
365             }
366         }
367 
368         /**
369          * Takes a slice of the tuple.
370          *
371          * Examples:
372          * ----
373          * Tuple!(int, string, float, double) a;
374          * a[1] = "abc";
375          * a[2] = 4.5;
376          * auto s = a.slice!(1, 3);
377          * static assert(is(typeof(s) == Tuple!(string, float)));
378          * assert(s[0] == "abc" && s[1] == 4.5);
379          * ----
380          */
381         @property ref Tuple!(sliceSpecs!(from, to)) slice(size_t from, size_t to)() @trusted if (
382                 from <= to && to <= Types.length)
383         {
384             return *cast(typeof(return)*)&(field[from]);
385         }
386 
387         ///
388         size_t toHash() const nothrow @trusted
389         {
390             size_t h = 0;
391             foreach (i, T; Types)
392                 h += typeid(T).getHash(cast(const void*)&field[i]);
393             return h;
394         }
395 
396         /**
397          * Converts to string.
398          */
399         void toString(DG)(scope DG sink)
400         {
401             enum header = typeof(this).stringof ~ "(", footer = ")", separator = ", ";
402             sink(header);
403             foreach (i, Type; Types)
404             {
405                 static if (i > 0)
406                 {
407                     sink(separator);
408                 }
409                 // TODO: Change this once toString() works for shared objects.
410                 static if (is(Type == class) && is(typeof(Type.init) == shared))
411                 {
412                     sink(Type.stringof);
413                 }
414                 else
415                 {
416                     import std.format : FormatSpec, formatElement;
417 
418                     FormatSpec!char f;
419                     formatElement(sink, field[i], f);
420                 }
421             }
422             sink(footer);
423         }
424 
425         ///
426         string toString()()
427         {
428             import std.conv : to;
429 
430             return this.to!string;
431         }
432     }
433 }
434 
435 unittest
436 {
437     auto tup = Aes!(double[], "x", double[], "y", string[], "colour")([0, 1],
438         [2, 1], ["white", "white2"]);
439     auto tup2 = Aes!(double[], "x", double[], "y")([0, 1], [2, 1]);
440     assertEqual(tup.colour, ["white", "white2"]);
441     assertEqual(tup.length, 2);
442     assertEqual(tup2.length, 2);
443 
444     tup2.x ~= 0.0;
445     tup2.x ~= 0.0;
446     assertEqual(tup2.length, 2);
447     tup2.y ~= 0.0;
448     assertEqual(tup2.length, 3);
449 }
450 
451 /// Basic Aes usage
452 unittest
453 {
454     auto aes = Aes!(double[], "x", double[], "y", string[], "colour")([0, 1],
455         [2, 1], ["white", "white2"]);
456 
457     aes.popFront;
458     assertEqual(aes.front.y, 1);
459     assertEqual(aes.front.colour, "white2");
460 
461     auto aes2 = Aes!(double[], "x", double[], "y")([0, 1], [2, 1]);
462     assertEqual(aes2.front.y, 2);
463 
464     import std.range : repeat;
465 
466     auto xs = repeat(0);
467     auto aes3 = Aes!(typeof(xs), "x", double[], "y")(xs, [2, 1]);
468 
469     assertEqual(aes3.front.x, 0);
470     aes3.popFront;
471     aes3.popFront;
472     assertEqual(aes3.empty, true);
473 
474 }
475 
476 /++
477     Groups data by colour label etc.
478 
479     Will also add DefaultValues for label etc to the data. It is also possible to specify exactly what to group by on as a template parameter. See example.
480 +/
481 template group(Specs...)
482 {
483     string buildExtractKey()
484     {
485         static if (Specs.length == 0)
486         {
487             import std.typetuple : TypeTuple;
488             alias Specs = TypeTuple!("alpha","colour","label");
489         }
490         string types = "";
491         string values = "";
492         foreach( spec; Specs )
493         {
494             types ~= "typeof(a." ~ spec ~"),";
495             values ~= "a." ~ spec ~",";
496         }
497         string str = "auto extractKey(T)(T a) 
498             { return Tuple!(" ~ types[0..$-1] ~ ")(" ~ values[0..$-1] ~ "); }";
499         return str;
500     }
501     
502     auto group(AES)(AES aes)
503     {
504         import std.stdio;
505         mixin(buildExtractKey());
506 
507         // Attach all default fields
508         auto merged = DefaultValues.mergeRange( aes );
509         extractKey( merged.front );
510 
511         typeof(merged.front())[][typeof(extractKey(merged.front))] grouped;
512 
513         // Extract keys for aa and store in aa. Return the values of the aa
514         foreach( tup; merged )
515         {
516             auto key = extractKey(tup);
517             if (key in grouped)
518                 grouped[key] ~= tup;
519             else
520                 grouped[key] = [tup];
521         }
522         return grouped.values;
523     }
524 }
525 
526 ///
527 unittest
528 {
529     import std.range : walkLength;
530     auto aes = Aes!(double[], "x", string[], "colour", double[], "alpha")
531         ([0,1,2,3], ["a","a","b","b"], [0,1,0,1]);
532 
533     assertEqual(group!("colour","alpha")(aes).walkLength,4);
534     assertEqual(group!("alpha")(aes).walkLength,2);
535 
536     assertEqual(group(aes).walkLength,4);
537 }
538 
539 ///
540 unittest
541 {
542     auto aes = Aes!(double[], "x", double[], "y", string[], "colour")([1.0,
543         2.0, 1.1], [3.0, 1.5, 1.1], ["a", "b", "a"]);
544 
545     import std.range : walkLength, front, popFront;
546 
547     auto grouped = aes.group;
548     assertEqual(grouped.walkLength, 2);
549     assertEqual(grouped.front.walkLength, 2);
550     grouped.popFront;
551     assertEqual(grouped.front.walkLength, 1);
552 }
553 
554 ///
555 import std.range : isInputRange;
556 
557 ///
558 struct NumericLabel(T) if (isInputRange!T)
559 {
560     import std.range : ElementType;
561     import std.traits : isNumeric;
562 
563     alias E = ElementType!T;
564 
565     ///
566     this(T range)
567     {
568         original = range;
569     }
570 
571     ///
572     @property auto front()
573     {
574         import std.typecons : Tuple;
575         import std.range : front;
576         import std.conv : to;
577 
578         static if (isNumeric!E)
579             return Tuple!(double, string)(original.front.to!double, original.front.to!string);
580         else
581         {
582             if (original.front !in fromLabelMap)
583             {
584                 fromLabelMap[original.front] = fromLabelMap.length.to!double;
585                 //toLabelMap[fromLabelMap[original.front]] 
586                 //    = original.front;
587             }
588             return Tuple!(double, string)(fromLabelMap[original.front], original.front.to!string,
589                 );
590         }
591     }
592 
593     ///
594     void popFront()
595     {
596         import std.range : popFront;
597 
598         original.popFront;
599     }
600 
601     ///
602     @property bool empty()
603     {
604         import std.range : empty;
605 
606         return original.empty;
607     }
608 
609     ///
610     @property bool numeric()
611     {
612         static if (isNumeric!E)
613             return true;
614         else
615             return false;
616     }
617 
618 private:
619     T original;
620     //E[double] toLabelMap;
621     double[E] fromLabelMap;
622 }
623 
624 unittest
625 {
626     import std.stdio : writeln;
627     import std.array : array;
628     import std.algorithm : map;
629 
630     auto num = NumericLabel!(double[])([0.0, 0.1, 1.0, 0.0]);
631     assertEqual(num.map!((a) => a[0]).array, [0.0, 0.1, 1.0, 0.0]);
632     assertEqual(num.map!((a) => a[1]).array, ["0", "0.1", "1", "0"]);
633     auto strs = NumericLabel!(string[])(["a", "c", "b", "a"]);
634     assertEqual(strs.map!((a) => a[0]).array, [0, 1, 2.0, 0.0]);
635     assertEqual(strs.map!((a) => a[1]).array, ["a", "c", "b", "a"]);
636 }
637 
638 unittest
639 {
640     import painlesstraits;
641 
642     auto t = Tuple!(double,"x")(1.0);
643 
644     static assert(isFieldOrProperty!(t.x));
645 }
646 
647 /++
648 Merge two types by their members. 
649 
650 If it has similar named members, then it uses the second one.
651 
652 returns a named Tuple (or Aes) with all the members and their values. 
653 +/
654 template merge(T, U)
655 {
656     import std.traits;
657     import painlesstraits;
658     auto generateCode()
659     {
660         import std.string : split;
661         string typing = "Tuple!(";
662         //string typing = T.stringof.split("!")[0] ~ "!(";
663         //string typingU = U.stringof.split("!")[0] ~ "!(";
664         string variables = "(";
665         foreach (name; __traits(allMembers, U))
666         {
667             static if (__traits(compiles, isFieldOrProperty!(
668                             __traits(getMember, U, name)))
669                     && __traits(compiles, ( in U u ) {
670                         auto a = __traits(getMember, u, name);
671                         Tuple!(typeof(a),name)(a); } )
672                     && isFieldOrProperty!(__traits(getMember,U,name))
673                     && name[0] != "_"[0] )
674             {
675                 typing ~= "typeof(other." ~ name ~ "),\"" ~ name ~ "\",";
676                 variables ~= "other." ~ name ~ ",";
677             }
678         }
679 
680         foreach (name; __traits(allMembers, T))
681         {
682             static if (__traits(compiles, isFieldOrProperty!(
683                 __traits(getMember, T, name)))
684                      && __traits(compiles, ( in T u ) {
685                 auto a = __traits(getMember, u, name); 
686                 Tuple!(typeof(a),name)(a); } )
687                 && isFieldOrProperty!(__traits(getMember,T,name))
688                      && name[0] != "_"[0] )
689                 {
690                 bool contains = false;
691                 foreach (name2; __traits(allMembers, U))
692                 {
693                     if (name == name2)
694                         contains = true;
695                 }
696                 if (!contains)
697                 {
698                     typing ~= "typeof(base." ~ name ~ "),\"" ~ name ~ "\",";
699                     variables ~= "base." ~ name ~ ",";
700                 }
701             }
702         }
703         return "return " ~ typing[0 .. $ - 1] ~ ")" ~ variables[0 .. $ - 1] ~ ");";
704     }
705 
706     auto merge(T base, U other)
707     {
708         mixin(generateCode());
709     }
710 }
711 
712 ///
713 unittest
714 {
715     import std.range : front;
716 
717     auto xs = ["a", "b"];
718     auto ys = ["c", "d"];
719     auto labels = ["e", "f"];
720     auto aes = Aes!(string[], "x", string[], "y", string[], "label")(xs, ys, labels);
721 
722     auto nlAes = merge(aes, Aes!(NumericLabel!(string[]), "x",
723         NumericLabel!(string[]), "y")(NumericLabel!(string[])(aes.x),
724         NumericLabel!(string[])(aes.y)));
725 
726     assertEqual(nlAes.x.front[0], 0);
727     assertEqual(nlAes.label.front, "e");
728 }
729 
730 unittest
731 {
732     auto pnt = Tuple!(double, "x", double, "y", string, "label" )( 1.0, 2.0, "Point" );
733     auto merged = DefaultValues.merge( pnt );
734     assertEqual( merged.x, 1.0 );
735     assertEqual( merged.y, 2.0 );
736     assertEqual( merged.colour, "black" );
737     assertEqual( merged.label, "Point" );
738 }
739 
740 unittest
741 {
742     import std.range : walkLength;
743     auto m = [DefaultValues].mergeRange( Aes!(double[], "x")([1.0]));
744     assertEqual( m.walkLength, 1);
745 }
746 
747 /// 
748 unittest
749 {
750     struct Point { double x; double y; string label = "Point"; }
751     auto pnt = Point( 1.0, 2.0 );
752 
753     auto merged = DefaultValues.merge( pnt );
754     assertEqual( merged.x, 1.0 );
755     assertEqual( merged.y, 2.0 );
756     assertEqual( merged.colour, "black" );
757     assertEqual( merged.label, "Point" );
758 }
759 
760 /++
761   Merge the elements of two ranges. If first is not a range then merge that with each element of the second range.
762 +/
763 auto mergeRange( R1, R2 )( R1 r1, R2 r2 )
764 {
765     import std.array : array;
766     import std.range : zip, walkLength, repeat;
767     import std.algorithm : map;
768     static if (isInputRange!R1 && isInputRange!R2)
769         return r1.zip(r2).array.map!((a) => a[0].merge( a[1] ) );
770     else
771         return zip( r1.repeat(r2.walkLength),
772             r2).array.map!((a) => a[0].merge( a[1] ) );
773 }
774 
775 ///
776 unittest
777 {
778     import std.range : front;
779 
780     auto xs = ["a", "b"];
781     auto ys = ["c", "d"];
782     auto labels = ["e", "f"];
783     auto aes = Aes!(string[], "x", string[], "y", string[], "label")(xs, ys, labels);
784     auto nlAes = mergeRange(DefaultValues, aes );
785     assertEqual(nlAes.front.x, "a");
786     assertEqual(nlAes.front.label, "e");
787     assertEqual(nlAes.front.colour, "black");
788 }
789 
790 ///
791 unittest
792 {
793     import std.range : front;
794 
795     auto xs = ["a", "b"];
796     auto ys = ["c", "d"];
797     auto labels = ["e", "f"];
798     auto aes = Aes!(string[], "x", string[], "y", string[], "label")(xs, ys, labels);
799 
800     auto nlAes = mergeRange(aes, Aes!(NumericLabel!(string[]), "x",
801         NumericLabel!(string[]), "y")(NumericLabel!(string[])(aes.x),
802         NumericLabel!(string[])(aes.y)));
803 
804     assertEqual(nlAes.front.x[0], 0);
805     assertEqual(nlAes.front.label, "e");
806 }