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 alias DataID = Tuple!(double, string);
558 
559 ///
560 struct NumericLabel(T) if (isInputRange!T)
561 {
562     import std.range : ElementType;
563     import std.traits : isNumeric;
564 
565     alias E = ElementType!T;
566 
567     ///
568     this(T range)
569     {
570         original = range;
571     }
572 
573     ///
574     @property auto front()
575     {
576         import std.typecons : Tuple;
577         import std.range : front;
578         import std.conv : to;
579 
580         static if (isNumeric!E)
581             return DataID(original.front.to!double, original.front.to!string);
582         else
583         {
584             if (original.front !in fromLabelMap)
585             {
586                 fromLabelMap[original.front] = fromLabelMap.length.to!double;
587                 //toLabelMap[fromLabelMap[original.front]] 
588                 //    = original.front;
589             }
590             return DataID(fromLabelMap[original.front], original.front.to!string,
591                 );
592         }
593     }
594 
595     ///
596     void popFront()
597     {
598         import std.range : popFront;
599 
600         original.popFront;
601     }
602 
603     ///
604     @property bool empty()
605     {
606         import std.range : empty;
607 
608         return original.empty;
609     }
610 
611     ///
612     @property bool numeric()
613     {
614         static if (isNumeric!E)
615             return true;
616         else
617             return false;
618     }
619 
620 private:
621     T original;
622     //E[double] toLabelMap;
623     double[E] fromLabelMap;
624 }
625 
626 unittest
627 {
628     import std.stdio : writeln;
629     import std.array : array;
630     import std.algorithm : map;
631 
632     auto num = NumericLabel!(double[])([0.0, 0.1, 1.0, 0.0]);
633     assertEqual(num.map!((a) => a[0]).array, [0.0, 0.1, 1.0, 0.0]);
634     assertEqual(num.map!((a) => a[1]).array, ["0", "0.1", "1", "0"]);
635     auto strs = NumericLabel!(string[])(["a", "c", "b", "a"]);
636     assertEqual(strs.map!((a) => a[0]).array, [0, 1, 2.0, 0.0]);
637     assertEqual(strs.map!((a) => a[1]).array, ["a", "c", "b", "a"]);
638 }
639 
640 unittest
641 {
642     import painlesstraits;
643 
644     auto t = Tuple!(double,"x")(1.0);
645 
646     static assert(isFieldOrProperty!(t.x));
647 }
648 
649 /++
650 Merge two types by their members. 
651 
652 If it has similar named members, then it uses the second one.
653 
654 returns a named Tuple (or Aes) with all the members and their values. 
655 +/
656 template merge(T, U)
657 {
658     import std.traits;
659     import painlesstraits;
660     auto generateCode()
661     {
662         import std.string : split;
663         string typing = "Tuple!(";
664         //string typing = T.stringof.split("!")[0] ~ "!(";
665         //string typingU = U.stringof.split("!")[0] ~ "!(";
666         string variables = "(";
667         foreach (name; __traits(allMembers, U))
668         {
669             static if (__traits(compiles, isFieldOrProperty!(
670                             __traits(getMember, U, name)))
671                     && __traits(compiles, ( in U u ) {
672                         auto a = __traits(getMember, u, name);
673                         Tuple!(typeof(a),name)(a); } )
674                     && isFieldOrProperty!(__traits(getMember,U,name))
675                     && name[0] != "_"[0] )
676             {
677                 typing ~= "typeof(other." ~ name ~ "),\"" ~ name ~ "\",";
678                 variables ~= "other." ~ name ~ ",";
679             }
680         }
681 
682         foreach (name; __traits(allMembers, T))
683         {
684             static if (__traits(compiles, isFieldOrProperty!(
685                 __traits(getMember, T, name)))
686                      && __traits(compiles, ( in T u ) {
687                 auto a = __traits(getMember, u, name); 
688                 Tuple!(typeof(a),name)(a); } )
689                 && isFieldOrProperty!(__traits(getMember,T,name))
690                      && name[0] != "_"[0] )
691                 {
692                 bool contains = false;
693                 foreach (name2; __traits(allMembers, U))
694                 {
695                     if (name == name2)
696                         contains = true;
697                 }
698                 if (!contains)
699                 {
700                     typing ~= "typeof(base." ~ name ~ "),\"" ~ name ~ "\",";
701                     variables ~= "base." ~ name ~ ",";
702                 }
703             }
704         }
705         return "return " ~ typing[0 .. $ - 1] ~ ")" ~ variables[0 .. $ - 1] ~ ");";
706     }
707 
708     auto merge(T base, U other)
709     {
710         mixin(generateCode());
711     }
712 }
713 
714 ///
715 unittest
716 {
717     import std.range : front;
718 
719     auto xs = ["a", "b"];
720     auto ys = ["c", "d"];
721     auto labels = ["e", "f"];
722     auto aes = Aes!(string[], "x", string[], "y", string[], "label")(xs, ys, labels);
723 
724     auto nlAes = merge(aes, Aes!(NumericLabel!(string[]), "x",
725         NumericLabel!(string[]), "y")(NumericLabel!(string[])(aes.x),
726         NumericLabel!(string[])(aes.y)));
727 
728     assertEqual(nlAes.x.front[0], 0);
729     assertEqual(nlAes.label.front, "e");
730 }
731 
732 unittest
733 {
734     auto pnt = Tuple!(double, "x", double, "y", string, "label" )( 1.0, 2.0, "Point" );
735     auto merged = DefaultValues.merge( pnt );
736     assertEqual( merged.x, 1.0 );
737     assertEqual( merged.y, 2.0 );
738     assertEqual( merged.colour, "black" );
739     assertEqual( merged.label, "Point" );
740 }
741 
742 unittest
743 {
744     import std.range : walkLength;
745     auto m = [DefaultValues].mergeRange( Aes!(double[], "x")([1.0]));
746     assertEqual( m.walkLength, 1);
747 }
748 
749 /// 
750 unittest
751 {
752     struct Point { double x; double y; string label = "Point"; }
753     auto pnt = Point( 1.0, 2.0 );
754 
755     auto merged = DefaultValues.merge( pnt );
756     assertEqual( merged.x, 1.0 );
757     assertEqual( merged.y, 2.0 );
758     assertEqual( merged.colour, "black" );
759     assertEqual( merged.label, "Point" );
760 }
761 
762 /++
763   Merge the elements of two ranges. If first is not a range then merge that with each element of the second range.
764 +/
765 auto mergeRange( R1, R2 )( R1 r1, R2 r2 )
766 {
767     import std.array : array;
768     import std.range : zip, walkLength, repeat;
769     import std.algorithm : map;
770     static if (isInputRange!R1 && isInputRange!R2)
771         return r1.zip(r2).array.map!((a) => a[0].merge( a[1] ) );
772     else
773         return zip( r1.repeat(r2.walkLength),
774             r2).array.map!((a) => a[0].merge( a[1] ) );
775 }
776 
777 ///
778 unittest
779 {
780     import std.range : front;
781 
782     auto xs = ["a", "b"];
783     auto ys = ["c", "d"];
784     auto labels = ["e", "f"];
785     auto aes = Aes!(string[], "x", string[], "y", string[], "label")(xs, ys, labels);
786     auto nlAes = mergeRange(DefaultValues, aes );
787     assertEqual(nlAes.front.x, "a");
788     assertEqual(nlAes.front.label, "e");
789     assertEqual(nlAes.front.colour, "black");
790 }
791 
792 ///
793 unittest
794 {
795     import std.range : front;
796 
797     auto xs = ["a", "b"];
798     auto ys = ["c", "d"];
799     auto labels = ["e", "f"];
800     auto aes = Aes!(string[], "x", string[], "y", string[], "label")(xs, ys, labels);
801 
802     auto nlAes = mergeRange(aes, Aes!(NumericLabel!(string[]), "x",
803         NumericLabel!(string[]), "y")(NumericLabel!(string[])(aes.x),
804         NumericLabel!(string[])(aes.y)));
805 
806     assertEqual(nlAes.front.x[0], 0);
807     assertEqual(nlAes.front.label, "e");
808 }