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", 10, 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 size of points in pixels (size_t))
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         @property bool empty()
267         {
268             if (length == 0)
269                 return true;
270             return false;
271         }
272 
273         ///
274         size_t length()
275         {
276             import std.algorithm : min;
277             import std.range : walkLength, isInfinite;
278 
279             size_t l = size_t.max;
280             foreach (i, type; Types[0 .. $])
281             {
282                 static if (!isInfinite!type)
283                 {
284                     if (field[i].walkLength < l)
285                         l = field[i].walkLength;
286                 }
287             }
288             return l;
289         }
290 
291         /**
292          * Comparison for equality.
293          */
294         bool opEquals(R)(R rhs) if (areCompatibleTuples!(typeof(this), R, "=="))
295         {
296             return field[] == rhs.field[];
297         }
298 
299         /// ditto
300         bool opEquals(R)(R rhs) const if (areCompatibleTuples!(typeof(this), R, "=="))
301         {
302             return field[] == rhs.field[];
303         }
304 
305         /**
306          * Comparison for ordering.
307          */
308         int opCmp(R)(R rhs) if (areCompatibleTuples!(typeof(this), R, "<"))
309         {
310             foreach (i, Unused; Types)
311             {
312                 if (field[i] != rhs.field[i])
313                 {
314                     return field[i] < rhs.field[i] ? -1 : 1;
315                 }
316             }
317             return 0;
318         }
319 
320         /// ditto
321         int opCmp(R)(R rhs) const if (areCompatibleTuples!(typeof(this), R, "<"))
322         {
323             foreach (i, Unused; Types)
324             {
325                 if (field[i] != rhs.field[i])
326                 {
327                     return field[i] < rhs.field[i] ? -1 : 1;
328                 }
329             }
330             return 0;
331         }
332 
333         /**
334          * Assignment from another tuple. Each element of the source must be
335          * implicitly assignable to the respective element of the target.
336          */
337         void opAssign(R)(auto ref R rhs) if (areCompatibleTuples!(typeof(this), R,
338                 "="))
339         {
340             import std.algorithm : swap;
341 
342             static if (is(R : Tuple!Types) && !__traits(isRef, rhs))
343             {
344                 if (__ctfe)
345                 {
346                     // Cannot use swap at compile time
347                     field[] = rhs.field[];
348                 }
349                 else
350                 {
351                     // Use swap-and-destroy to optimize rvalue assignment
352                     swap!(Tuple!Types)(this, rhs);
353                 }
354             }
355             else
356             {
357                 // Do not swap; opAssign should be called on the fields.
358                 field[] = rhs.field[];
359             }
360         }
361 
362         /**
363          * Takes a slice of the tuple.
364          *
365          * Examples:
366          * ----
367          * Tuple!(int, string, float, double) a;
368          * a[1] = "abc";
369          * a[2] = 4.5;
370          * auto s = a.slice!(1, 3);
371          * static assert(is(typeof(s) == Tuple!(string, float)));
372          * assert(s[0] == "abc" && s[1] == 4.5);
373          * ----
374          */
375         @property ref Tuple!(sliceSpecs!(from, to)) slice(size_t from, size_t to)() @trusted if (
376                 from <= to && to <= Types.length)
377         {
378             return *cast(typeof(return)*)&(field[from]);
379         }
380 
381         ///
382         size_t toHash() const nothrow @trusted
383         {
384             size_t h = 0;
385             foreach (i, T; Types)
386                 h += typeid(T).getHash(cast(const void*)&field[i]);
387             return h;
388         }
389 
390         /**
391          * Converts to string.
392          */
393         void toString(DG)(scope DG sink)
394         {
395             enum header = typeof(this).stringof ~ "(", footer = ")", separator = ", ";
396             sink(header);
397             foreach (i, Type; Types)
398             {
399                 static if (i > 0)
400                 {
401                     sink(separator);
402                 }
403                 // TODO: Change this once toString() works for shared objects.
404                 static if (is(Type == class) && is(typeof(Type.init) == shared))
405                 {
406                     sink(Type.stringof);
407                 }
408                 else
409                 {
410                     import std.format : FormatSpec, formatElement;
411 
412                     FormatSpec!char f;
413                     formatElement(sink, field[i], f);
414                 }
415             }
416             sink(footer);
417         }
418 
419         ///
420         string toString()()
421         {
422             import std.conv : to;
423 
424             return this.to!string;
425         }
426     }
427 }
428 
429 unittest
430 {
431     auto tup = Aes!(double[], "x", double[], "y", string[], "colour")([0, 1],
432         [2, 1], ["white", "white2"]);
433     auto tup2 = Aes!(double[], "x", double[], "y")([0, 1], [2, 1]);
434     assertEqual(tup.colour, ["white", "white2"]);
435     assertEqual(tup.length, 2);
436     assertEqual(tup2.length, 2);
437 
438     tup2.x ~= 0.0;
439     tup2.x ~= 0.0;
440     assertEqual(tup2.length, 2);
441     tup2.y ~= 0.0;
442     assertEqual(tup2.length, 3);
443 }
444 
445 /// Basic Aes usage
446 unittest
447 {
448     auto aes = Aes!(double[], "x", double[], "y", string[], "colour")([0, 1],
449         [2, 1], ["white", "white2"]);
450 
451     aes.popFront;
452     assertEqual(aes.front.y, 1);
453     assertEqual(aes.front.colour, "white2");
454 
455     auto aes2 = Aes!(double[], "x", double[], "y")([0, 1], [2, 1]);
456     assertEqual(aes2.front.y, 2);
457 
458     import std.range : repeat;
459 
460     auto xs = repeat(0);
461     auto aes3 = Aes!(typeof(xs), "x", double[], "y")(xs, [2, 1]);
462 
463     assertEqual(aes3.front.x, 0);
464     aes3.popFront;
465     aes3.popFront;
466     assertEqual(aes3.empty, true);
467 
468 }
469 
470 /++
471     Groups data by colour label etc.
472 
473     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.
474 +/
475 template group(Specs...)
476 {
477     string buildExtractKey()
478     {
479         static if (Specs.length == 0)
480         {
481             import std.typetuple : TypeTuple;
482             alias Specs = TypeTuple!("alpha","colour","label");
483         }
484         string types = "";
485         string values = "";
486         foreach( spec; Specs )
487         {
488             types ~= "typeof(a." ~ spec ~"),";
489             values ~= "a." ~ spec ~",";
490         }
491         string str = "auto extractKey(T)(T a) 
492             { return Tuple!(" ~ types[0..$-1] ~ ")(" ~ values[0..$-1] ~ "); }";
493         return str;
494     }
495     
496     auto group(AES)(AES aes)
497     {
498         import std.stdio;
499         mixin(buildExtractKey());
500 
501         // Attach all default fields
502         auto merged = DefaultValues.mergeRange( aes );
503         extractKey( merged.front );
504 
505         typeof(merged.front())[][typeof(extractKey(merged.front))] grouped;
506 
507         // Extract keys for aa and store in aa. Return the values of the aa
508         foreach( tup; merged )
509         {
510             auto key = extractKey(tup);
511             if (key in grouped)
512                 grouped[key] ~= tup;
513             else
514                 grouped[key] = [tup];
515         }
516         return grouped.values;
517     }
518 }
519 
520 ///
521 unittest
522 {
523     import std.range : walkLength;
524     auto aes = Aes!(double[], "x", string[], "colour", double[], "alpha")
525         ([0,1,2,3], ["a","a","b","b"], [0,1,0,1]);
526 
527     assertEqual(group!("colour","alpha")(aes).walkLength,4);
528     assertEqual(group!("alpha")(aes).walkLength,2);
529 
530     assertEqual(group(aes).walkLength,4);
531 }
532 
533 ///
534 unittest
535 {
536     auto aes = Aes!(double[], "x", double[], "y", string[], "colour")([1.0,
537         2.0, 1.1], [3.0, 1.5, 1.1], ["a", "b", "a"]);
538 
539     import std.range : walkLength, front, popFront;
540 
541     auto grouped = aes.group;
542     assertEqual(grouped.walkLength, 2);
543     assertEqual(grouped.front.walkLength, 2);
544     grouped.popFront;
545     assertEqual(grouped.front.walkLength, 1);
546 }
547 
548 ///
549 import std.range : isInputRange;
550 
551 ///
552 struct NumericLabel(T) if (isInputRange!T)
553 {
554     import std.range : ElementType;
555     import std.traits : isNumeric;
556 
557     alias E = ElementType!T;
558 
559     ///
560     this(T range)
561     {
562         original = range;
563     }
564 
565     ///
566     @property auto front()
567     {
568         import std.typecons : Tuple;
569         import std.range : front;
570         import std.conv : to;
571 
572         static if (isNumeric!E)
573             return Tuple!(double, string)(original.front.to!double, original.front.to!string);
574         else
575         {
576             if (original.front !in fromLabelMap)
577             {
578                 fromLabelMap[original.front] = fromLabelMap.length.to!double;
579                 //toLabelMap[fromLabelMap[original.front]] 
580                 //    = original.front;
581             }
582             return Tuple!(double, string)(fromLabelMap[original.front], original.front.to!string,
583                 );
584         }
585     }
586 
587     ///
588     void popFront()
589     {
590         import std.range : popFront;
591 
592         original.popFront;
593     }
594 
595     ///
596     @property bool empty()
597     {
598         import std.range : empty;
599 
600         return original.empty;
601     }
602 
603     ///
604     @property bool numeric()
605     {
606         static if (isNumeric!E)
607             return true;
608         else
609             return false;
610     }
611 
612 private:
613     T original;
614     //E[double] toLabelMap;
615     double[E] fromLabelMap;
616 }
617 
618 unittest
619 {
620     import std.stdio : writeln;
621     import std.array : array;
622     import std.algorithm : map;
623 
624     auto num = NumericLabel!(double[])([0.0, 0.1, 1.0, 0.0]);
625     assertEqual(num.map!((a) => a[0]).array, [0.0, 0.1, 1.0, 0.0]);
626     assertEqual(num.map!((a) => a[1]).array, ["0", "0.1", "1", "0"]);
627     auto strs = NumericLabel!(string[])(["a", "c", "b", "a"]);
628     assertEqual(strs.map!((a) => a[0]).array, [0, 1, 2.0, 0.0]);
629     assertEqual(strs.map!((a) => a[1]).array, ["a", "c", "b", "a"]);
630 }
631 
632 unittest
633 {
634     import painlesstraits;
635 
636     auto t = Tuple!(double,"x")(1.0);
637 
638     static assert(isFieldOrProperty!(t.x));
639 }
640 
641 /++
642 Merge two types by their members. 
643 
644 If it has similar named members, then it uses the second one.
645 
646 returns a named Tuple (or Aes) with all the members and their values. 
647 +/
648 template merge(T, U)
649 {
650     import std.traits;
651     import painlesstraits;
652     auto generateCode()
653     {
654         import std.string : split;
655         string typing = "Tuple!(";
656         //string typing = T.stringof.split("!")[0] ~ "!(";
657         //string typingU = U.stringof.split("!")[0] ~ "!(";
658         string variables = "(";
659         foreach (name; __traits(allMembers, U))
660         {
661             static if (__traits(compiles, isFieldOrProperty!(
662                             __traits(getMember, U, name)))
663                     && __traits(compiles, ( in U u ) {
664                         auto a = __traits(getMember, u, name);
665                         Tuple!(typeof(a),name)(a); } )
666                     && isFieldOrProperty!(__traits(getMember,U,name))
667                     && name[0] != "_"[0] )
668             {
669                 typing ~= "typeof(other." ~ name ~ "),\"" ~ name ~ "\",";
670                 variables ~= "other." ~ name ~ ",";
671             }
672         }
673 
674         foreach (name; __traits(allMembers, T))
675         {
676             static if (__traits(compiles, isFieldOrProperty!(
677                 __traits(getMember, T, name)))
678                      && __traits(compiles, ( in T u ) {
679                 auto a = __traits(getMember, u, name); 
680                 Tuple!(typeof(a),name)(a); } )
681                 && isFieldOrProperty!(__traits(getMember,T,name))
682                      && name[0] != "_"[0] )
683                 {
684                 bool contains = false;
685                 foreach (name2; __traits(allMembers, U))
686                 {
687                     if (name == name2)
688                         contains = true;
689                 }
690                 if (!contains)
691                 {
692                     typing ~= "typeof(base." ~ name ~ "),\"" ~ name ~ "\",";
693                     variables ~= "base." ~ name ~ ",";
694                 }
695             }
696         }
697         return "return " ~ typing[0 .. $ - 1] ~ ")" ~ variables[0 .. $ - 1] ~ ");";
698     }
699 
700     auto merge(T base, U other)
701     {
702         mixin(generateCode());
703     }
704 }
705 
706 ///
707 unittest
708 {
709     import std.range : front;
710 
711     auto xs = ["a", "b"];
712     auto ys = ["c", "d"];
713     auto labels = ["e", "f"];
714     auto aes = Aes!(string[], "x", string[], "y", string[], "label")(xs, ys, labels);
715 
716     auto nlAes = merge(aes, Aes!(NumericLabel!(string[]), "x",
717         NumericLabel!(string[]), "y")(NumericLabel!(string[])(aes.x),
718         NumericLabel!(string[])(aes.y)));
719 
720     assertEqual(nlAes.x.front[0], 0);
721     assertEqual(nlAes.label.front, "e");
722 }
723 
724 unittest
725 {
726     auto pnt = Tuple!(double, "x", double, "y", string, "label" )( 1.0, 2.0, "Point" );
727     auto merged = DefaultValues.merge( pnt );
728     assertEqual( merged.x, 1.0 );
729     assertEqual( merged.y, 2.0 );
730     assertEqual( merged.colour, "black" );
731     assertEqual( merged.label, "Point" );
732 }
733 
734 unittest
735 {
736     import std.range : walkLength;
737     auto m = [DefaultValues].mergeRange( Aes!(double[], "x")([1.0]));
738     assertEqual( m.walkLength, 1);
739 }
740 
741 /// 
742 unittest
743 {
744     struct Point { double x; double y; string label = "Point"; }
745     auto pnt = Point( 1.0, 2.0 );
746 
747     auto merged = DefaultValues.merge( pnt );
748     assertEqual( merged.x, 1.0 );
749     assertEqual( merged.y, 2.0 );
750     assertEqual( merged.colour, "black" );
751     assertEqual( merged.label, "Point" );
752 }
753 
754 /++
755   Merge the elements of two ranges. If first is not a range then merge that with each element of the second range.
756 +/
757 auto mergeRange( R1, R2 )( R1 r1, R2 r2 )
758 {
759     import std.array : array;
760     import std.range : zip, walkLength, repeat;
761     import std.algorithm : map;
762     static if (isInputRange!R1 && isInputRange!R2)
763         return r1.zip(r2).array.map!((a) => a[0].merge( a[1] ) );
764     else
765         return zip( r1.repeat(r2.walkLength),
766             r2).array.map!((a) => a[0].merge( a[1] ) );
767 }
768 
769 ///
770 unittest
771 {
772     import std.range : front;
773 
774     auto xs = ["a", "b"];
775     auto ys = ["c", "d"];
776     auto labels = ["e", "f"];
777     auto aes = Aes!(string[], "x", string[], "y", string[], "label")(xs, ys, labels);
778     auto nlAes = mergeRange(DefaultValues, aes );
779     assertEqual(nlAes.front.x, "a");
780     assertEqual(nlAes.front.label, "e");
781     assertEqual(nlAes.front.colour, "black");
782 }
783 
784 ///
785 unittest
786 {
787     import std.range : front;
788 
789     auto xs = ["a", "b"];
790     auto ys = ["c", "d"];
791     auto labels = ["e", "f"];
792     auto aes = Aes!(string[], "x", string[], "y", string[], "label")(xs, ys, labels);
793 
794     auto nlAes = mergeRange(aes, Aes!(NumericLabel!(string[]), "x",
795         NumericLabel!(string[]), "y")(NumericLabel!(string[])(aes.x),
796         NumericLabel!(string[])(aes.y)));
797 
798     assertEqual(nlAes.front.x[0], 0);
799     assertEqual(nlAes.front.label, "e");
800 }