1 module ggplotd.colour;
2 
3 import std.range : ElementType;
4 import std.typecons : Tuple;
5 
6 import ggplotd.aes : NumericLabel;
7 import ggplotd.colourspace : RGBA;
8 
9 
10 version (unittest)
11 {
12     import dunit.toolkit;
13 }
14 
15 /++
16     Returns an associative array with names as key and colours as values
17 
18     Set of colors defined by X11, adopted by the W3C, SVG, and other popular libraries.
19     +/
20 auto createNamedColours()
21 {
22     RGBA[string] nameMap;
23     nameMap["none"] = RGBA(0, 0, 0, 0);
24     nameMap["aliceBlue"] = RGBA(0.941176,0.972549,1.0,1);
25     nameMap["antiqueWhite"] = RGBA(0.980392,0.921569,0.843137,1);
26     nameMap["aqua"] = RGBA(0.0,1.0,1.0,1);
27     nameMap["aquamarine"] = RGBA(0.498039,1.0,0.831373,1);
28     nameMap["azure"] = RGBA(0.941176,1.0,1.0,1);
29     nameMap["beige"] = RGBA(0.960784,0.960784,0.862745,1);
30     nameMap["bisque"] = RGBA(1.0,0.894118,0.768627,1);
31     nameMap["black"] = RGBA(0.0,0.0,0.0,1);
32     nameMap["blanchedAlmond"] = RGBA(1.0,0.921569,0.803922,1);
33     nameMap["blue"] = RGBA(0.0,0.0,1.0,1);
34     nameMap["blueViolet"] = RGBA(0.541176,0.168627,0.886275,1);
35     nameMap["brown"] = RGBA(0.647059,0.164706,0.164706,1);
36     nameMap["burlyWood"] = RGBA(0.870588,0.721569,0.529412,1);
37     nameMap["cadetBlue"] = RGBA(0.372549,0.619608,0.627451,1);
38     nameMap["chartreuse"] = RGBA(0.498039,1.0,0.0,1);
39     nameMap["chocolate"] = RGBA(0.823529,0.411765,0.117647,1);
40     nameMap["coral"] = RGBA(1.0,0.498039,0.313725,1);
41     nameMap["cornflowerBlue"] = RGBA(0.392157,0.584314,0.929412,1);
42     nameMap["cornsilk"] = RGBA(1.0,0.972549,0.862745,1);
43     nameMap["crimson"] = RGBA(0.862745,0.078431,0.235294,1);
44     nameMap["cyan"] = RGBA(0.0,1.0,1.0,1);
45     nameMap["darkBlue"] = RGBA(0.0,0.0,0.545098,1);
46     nameMap["darkCyan"] = RGBA(0.0,0.545098,0.545098,1);
47     nameMap["darkGoldenrod"] = RGBA(0.721569,0.52549,0.043137,1);
48     nameMap["darkGray"] = RGBA(0.662745,0.662745,0.662745,1);
49     nameMap["darkGrey"] = RGBA(0.662745,0.662745,0.662745,1);
50     nameMap["darkGreen"] = RGBA(0.0,0.392157,0.0,1);
51     nameMap["darkKhaki"] = RGBA(0.741176,0.717647,0.419608,1);
52     nameMap["darkMagenta"] = RGBA(0.545098,0.0,0.545098,1);
53     nameMap["darkOliveGreen"] = RGBA(0.333333,0.419608,0.184314,1);
54     nameMap["darkOrange"] = RGBA(1.0,0.54902,0.0,1);
55     nameMap["darkOrchid"] = RGBA(0.6,0.196078,0.8,1);
56     nameMap["darkRed"] = RGBA(0.545098,0.0,0.0,1);
57     nameMap["darkSalmon"] = RGBA(0.913725,0.588235,0.478431,1);
58     nameMap["darkSeaGreen"] = RGBA(0.560784,0.737255,0.560784,1);
59     nameMap["darkSlateBlue"] = RGBA(0.282353,0.239216,0.545098,1);
60     nameMap["darkSlateGray"] = RGBA(0.184314,0.309804,0.309804,1);
61     nameMap["darkSlateGrey"] = RGBA(0.184314,0.309804,0.309804,1);
62     nameMap["darkTurquoise"] = RGBA(0.0,0.807843,0.819608,1);
63     nameMap["darkViolet"] = RGBA(0.580392,0.0,0.827451,1);
64     nameMap["deepPink"] = RGBA(1.0,0.078431,0.576471,1);
65     nameMap["deepSkyBlue"] = RGBA(0.0,0.74902,1.0,1);
66     nameMap["dimGray"] = RGBA(0.411765,0.411765,0.411765,1);
67     nameMap["dimGrey"] = RGBA(0.411765,0.411765,0.411765,1);
68     nameMap["dodgerBlue"] = RGBA(0.117647,0.564706,1.0,1);
69     nameMap["fireBrick"] = RGBA(0.698039,0.133333,0.133333,1);
70     nameMap["floralWhite"] = RGBA(1.0,0.980392,0.941176,1);
71     nameMap["forestGreen"] = RGBA(0.133333,0.545098,0.133333,1);
72     nameMap["fuchsia"] = RGBA(1.0,0.0,1.0,1);
73     nameMap["gainsboro"] = RGBA(0.862745,0.862745,0.862745,1);
74     nameMap["ghostWhite"] = RGBA(0.972549,0.972549,1.0,1);
75     nameMap["gold"] = RGBA(1.0,0.843137,0.0,1);
76     nameMap["goldenrod"] = RGBA(0.854902,0.647059,0.12549,1);
77     nameMap["gray"] = RGBA(0.501961,0.501961,0.501961,1);
78     nameMap["grey"] = RGBA(0.501961,0.501961,0.501961,1);
79     nameMap["green"] = RGBA(0.0,0.501961,0.0,1);
80     nameMap["greenYellow"] = RGBA(0.678431,1.0,0.184314,1);
81     nameMap["honeydew"] = RGBA(0.941176,1.0,0.941176,1);
82     nameMap["hotPink"] = RGBA(1.0,0.411765,0.705882,1);
83     nameMap["indianRed"] = RGBA(0.803922,0.360784,0.360784,1);
84     nameMap["indigo"] = RGBA(0.294118,0.0,0.509804,1);
85     nameMap["ivory"] = RGBA(1.0,1.0,0.941176,1);
86     nameMap["khaki"] = RGBA(0.941176,0.901961,0.54902,1);
87     nameMap["lavender"] = RGBA(0.901961,0.901961,0.980392,1);
88     nameMap["lavenderBlush"] = RGBA(1.0,0.941176,0.960784,1);
89     nameMap["lawnGreen"] = RGBA(0.486275,0.988235,0.0,1);
90     nameMap["lemonChiffon"] = RGBA(1.0,0.980392,0.803922,1);
91     nameMap["lightBlue"] = RGBA(0.678431,0.847059,0.901961,1);
92     nameMap["lightCoral"] = RGBA(0.941176,0.501961,0.501961,1);
93     nameMap["lightCyan"] = RGBA(0.878431,1.0,1.0,1);
94     nameMap["lightGoldenrodYellow"] = RGBA(0.980392,0.980392,0.823529,1);
95     nameMap["lightGray"] = RGBA(0.827451,0.827451,0.827451,1);
96     nameMap["lightGrey"] = RGBA(0.827451,0.827451,0.827451,1);
97     nameMap["lightGreen"] = RGBA(0.564706,0.933333,0.564706,1);
98     nameMap["lightPink"] = RGBA(1.0,0.713725,0.756863,1);
99     nameMap["lightSalmon"] = RGBA(1.0,0.627451,0.478431,1);
100     nameMap["lightSeaGreen"] = RGBA(0.12549,0.698039,0.666667,1);
101     nameMap["lightSkyBlue"] = RGBA(0.529412,0.807843,0.980392,1);
102     nameMap["lightSlateGray"] = RGBA(0.466667,0.533333,0.6,1);
103     nameMap["lightSlateGrey"] = RGBA(0.466667,0.533333,0.6,1);
104     nameMap["lightSteelBlue"] = RGBA(0.690196,0.768627,0.870588,1);
105     nameMap["lightYellow"] = RGBA(1.0,1.0,0.878431,1);
106     nameMap["lime"] = RGBA(0.0,1.0,0.0,1);
107     nameMap["limeGreen"] = RGBA(0.196078,0.803922,0.196078,1);
108     nameMap["linen"] = RGBA(0.980392,0.941176,0.901961,1);
109     nameMap["magenta"] = RGBA(1.0,0.0,1.0,1);
110     nameMap["maroon"] = RGBA(0.501961,0.0,0.0,1);
111     nameMap["mediumAquamarine"] = RGBA(0.4,0.803922,0.666667,1);
112     nameMap["mediumBlue"] = RGBA(0.0,0.0,0.803922,1);
113     nameMap["mediumOrchid"] = RGBA(0.729412,0.333333,0.827451,1);
114     nameMap["mediumPurple"] = RGBA(0.576471,0.439216,0.858824,1);
115     nameMap["mediumSeaGreen"] = RGBA(0.235294,0.701961,0.443137,1);
116     nameMap["mediumSlateBlue"] = RGBA(0.482353,0.407843,0.933333,1);
117     nameMap["mediumSpringGreen"] = RGBA(0.0,0.980392,0.603922,1);
118     nameMap["mediumTurquoise"] = RGBA(0.282353,0.819608,0.8,1);
119     nameMap["mediumVioletRed"] = RGBA(0.780392,0.082353,0.521569,1);
120     nameMap["midnightBlue"] = RGBA(0.098039,0.098039,0.439216,1);
121     nameMap["mintCream"] = RGBA(0.960784,1.0,0.980392,1);
122     nameMap["mistyRose"] = RGBA(1.0,0.894118,0.882353,1);
123     nameMap["moccasin"] = RGBA(1.0,0.894118,0.709804,1);
124     nameMap["navajoWhite"] = RGBA(1.0,0.870588,0.678431,1);
125     nameMap["navy"] = RGBA(0.0,0.0,0.501961,1);
126     nameMap["oldLace"] = RGBA(0.992157,0.960784,0.901961,1);
127     nameMap["olive"] = RGBA(0.501961,0.501961,0.0,1);
128     nameMap["oliveDrab"] = RGBA(0.419608,0.556863,0.137255,1);
129     nameMap["orange"] = RGBA(1.0,0.647059,0.0,1);
130     nameMap["orangeRed"] = RGBA(1.0,0.270588,0.0,1);
131     nameMap["orchid"] = RGBA(0.854902,0.439216,0.839216,1);
132     nameMap["paleGoldenrod"] = RGBA(0.933333,0.909804,0.666667,1);
133     nameMap["paleGreen"] = RGBA(0.596078,0.984314,0.596078,1);
134     nameMap["paleTurquoise"] = RGBA(0.686275,0.933333,0.933333,1);
135     nameMap["paleVioletRed"] = RGBA(0.858824,0.439216,0.576471,1);
136     nameMap["papayaWhip"] = RGBA(1.0,0.937255,0.835294,1);
137     nameMap["peachPuff"] = RGBA(1.0,0.854902,0.72549,1);
138     nameMap["peru"] = RGBA(0.803922,0.521569,0.247059,1);
139     nameMap["pink"] = RGBA(1.0,0.752941,0.796078,1);
140     nameMap["plum"] = RGBA(0.866667,0.627451,0.866667,1);
141     nameMap["powderBlue"] = RGBA(0.690196,0.878431,0.901961,1);
142     nameMap["purple"] = RGBA(0.501961,0.0,0.501961,1);
143     nameMap["red"] = RGBA(1.0,0.0,0.0,1);
144     nameMap["rosyBrown"] = RGBA(0.737255,0.560784,0.560784,1);
145     nameMap["royalBlue"] = RGBA(0.254902,0.411765,0.882353,1);
146     nameMap["saddleBrown"] = RGBA(0.545098,0.270588,0.07451,1);
147     nameMap["salmon"] = RGBA(0.980392,0.501961,0.447059,1);
148     nameMap["sandyBrown"] = RGBA(0.956863,0.643137,0.376471,1);
149     nameMap["seaGreen"] = RGBA(0.180392,0.545098,0.341176,1);
150     nameMap["seashell"] = RGBA(1.0,0.960784,0.933333,1);
151     nameMap["sienna"] = RGBA(0.627451,0.321569,0.176471,1);
152     nameMap["silver"] = RGBA(0.752941,0.752941,0.752941,1);
153     nameMap["skyBlue"] = RGBA(0.529412,0.807843,0.921569,1);
154     nameMap["slateBlue"] = RGBA(0.415686,0.352941,0.803922,1);
155     nameMap["slateGray"] = RGBA(0.439216,0.501961,0.564706,1);
156     nameMap["slateGrey"] = RGBA(0.439216,0.501961,0.564706,1);
157     nameMap["snow"] = RGBA(1.0,0.980392,0.980392,1);
158     nameMap["springGreen"] = RGBA(0.0,1.0,0.498039,1);
159     nameMap["steelBlue"] = RGBA(0.27451,0.509804,0.705882,1);
160     nameMap["tan"] = RGBA(0.823529,0.705882,0.54902,1);
161     nameMap["teal"] = RGBA(0.0,0.501961,0.501961,1);
162     nameMap["thistle"] = RGBA(0.847059,0.74902,0.847059,1);
163     nameMap["tomato"] = RGBA(1.0,0.388235,0.278431,1);
164     nameMap["turquoise"] = RGBA(0.25098,0.878431,0.815686,1);
165     nameMap["violet"] = RGBA(0.933333,0.509804,0.933333,1);
166     nameMap["wheat"] = RGBA(0.960784,0.870588,0.701961,1);
167     nameMap["white"] = RGBA(1.0,1.0,1.0,1);
168     nameMap["whiteSmoke"] = RGBA(0.960784,0.960784,0.960784,1);
169     nameMap["yellow"] = RGBA(1.0,1.0,0.0,1);
170     nameMap["yellowGreen"] = RGBA(0.603922,0.803922,0.196078,1);
171     return nameMap;
172 }
173 
174 /// Converts any type into a double string pair, which is used by colour maps
175 struct ColourID
176 {
177     import std.typecons : Tuple;
178 
179     ///
180     this(T)(in T setId)
181     {
182         import std.math : isNumeric;
183         import std.conv : to;
184 
185         import ggplotd.colourspace : isColour, toColourSpace;
186 
187         id[2] = RGBA(-1,-1,-1,-1);
188 
189         static if (isNumeric!T)
190         {
191             id[0] = setId.to!double;
192         } else
193           static if (isColour!T)
194             id[2] = setId.toColourSpace!(RGBA,T);
195           else
196             id[1] = setId.to!string;
197     }
198 
199     /// Initialize using rgba colour
200     this( double r, double g, double b, double a = 1 )
201     {
202         id[2] = RGBA( r, g, b, a );
203     }
204 
205     Tuple!(double, string, RGBA) id; ///
206 
207     alias id this; ///
208 }
209 
210 unittest
211 {
212     import std.math : isNaN;
213     import std.range : empty;
214     import ggplotd.colourspace : RGB;
215 
216     auto cID = ColourID("a");
217     assert(isNaN(cID[0]));
218     assertEqual(cID[1], "a");
219     auto numID = ColourID(0);
220     assertEqual(numID[0], 0);
221     assert(numID[1].empty);
222     assertEqual( numID[2].r, -1 );
223 
224     cID = ColourID(RGBA(0,0,0,0));
225     assertEqual( cID[2].r, 0 );
226 
227     cID = ColourID(RGB(1,1,1));
228     assertEqual( cID[2].r, 1 );
229 }
230 
231 import std.range : isInputRange;
232 import std.range : ElementType;
233 
234 ///
235 struct ColourIDRange(T) if (isInputRange!T && is(ElementType!T == ColourID))
236 {
237     ///
238     this(T range)
239     {
240         original = range;
241         namedColours = createNamedColours();
242     }
243 
244     ///
245     @property auto front()
246     {
247         import std.range : front;
248         import std.math : isNaN;
249 
250         if (!isNaN(original.front[0]) || original.front[1] in namedColours)
251             return original.front;
252         else if (original.front[1] !in labelMap)
253         {
254             import std.conv : to;
255 
256             labelMap[original.front[1]] = labelMap.length.to!double;
257         }
258         original.front[0] = labelMap[original.front[1]];
259         return original.front;
260     }
261 
262     ///
263     void popFront()
264     {
265         import std.range : popFront;
266 
267         original.popFront;
268     }
269 
270     ///
271     @property bool empty()
272     {
273         import std.range : empty;
274 
275         return original.empty;
276     }
277 
278     @property auto save()
279     {
280         return this;
281     }
282 
283 private:
284     double[string] labelMap;
285     T original;
286     //E[double] toLabelMap;
287     RGBA[string] namedColours;
288 }
289 
290 unittest
291 {
292     import std.math : isNaN;
293 
294     auto ids = [ColourID("black"), ColourID(-1), ColourID("a"), ColourID("b"), ColourID("a")];
295     auto cids = ColourIDRange!(typeof(ids))(ids);
296 
297     assertEqual(cids.front[1], "black");
298     assert(isNaN(cids.front[0]));
299     cids.popFront;
300     assertEqual(cids.front[1], "");
301     assertEqual(cids.front[0], -1);
302     cids.popFront;
303     assertEqual(cids.front[1], "a");
304     assertEqual(cids.front[0], 0);
305     cids.popFront;
306     assertEqual(cids.front[1], "b");
307     assertEqual(cids.front[0], 1);
308     cids.popFront;
309     assertEqual(cids.front[1], "a");
310     assertEqual(cids.front[0], 0);
311 }
312 
313 private auto safeMax(T)(T a, T b)
314 {
315     import std.math : isNaN;
316     import std.algorithm : max;
317 
318     if (isNaN(b))
319         return a;
320     if (isNaN(a))
321         return b;
322     return max(a, b);
323 }
324 
325 private auto safeMin(T)(T a, T b)
326 {
327     import std.math : isNaN;
328     import std.algorithm : min;
329 
330     if (isNaN(b))
331         return a;
332     if (isNaN(a))
333         return b;
334     return min(a, b);
335 }
336 
337 alias ColourMap = RGBA delegate(ColourID tup);
338 
339 ///
340 auto createColourMap(R)(R colourIDs, ColourGradientFunction gradient) if (is(ElementType!R == Tuple!(double,
341         string)) || is(ElementType!R == ColourID))
342 {
343     import std.algorithm : filter, map, reduce;
344     import std.math : isNaN;
345     import std.array : array;
346     import std.typecons : Tuple;
347 
348     import ggplotd.colourspace : toCairoRGBA;
349 
350     auto validatedIDs = ColourIDRange!R(colourIDs);
351 
352     auto minmax = Tuple!(double, double)(0, 0);
353     if (!validatedIDs.empty)
354         minmax = validatedIDs.save
355             .map!((a) => a[0]).reduce!((a, b) => safeMin(a,
356             b), (a, b) => safeMax(a, b));
357 
358     auto namedColours = createNamedColours;
359     import std.algorithm : find;
360 
361     return (ColourID tup) {
362         if (tup[2].r >= 0)
363             return tup[2];
364         else if (tup[1] in namedColours)
365             return namedColours[tup[1]];
366         else if (isNaN(tup[0])) 
367         {
368             return gradient((validatedIDs.find!("a[1] == b")(tup[1]).front)[0], 
369                 minmax[0], minmax[1]);
370         }
371         return gradient(tup[0], minmax[0], minmax[1]);
372     };
373 }
374 
375 unittest
376 {
377     import std.typecons : Tuple;
378     import std.array : array;
379     import std.range : iota;
380     import std.algorithm : map;
381 
382     import ggplotd.colourspace : HCY, toCairoRGBA;
383 
384     auto dc = colourGradient!HCY("");
385 
386     assertFalse(
387         createColourMap([ColourID("a"), ColourID("b")], dc )(ColourID("a")) 
388          == createColourMap([ColourID("a"), ColourID("b")], dc )(
389             ColourID("b"))
390     );
391 
392     assertEqual(createColourMap([ColourID("a"), ColourID("b")], 
393         dc )(ColourID("black")),
394             RGBA(0, 0, 0, 1));
395 
396     assertEqual(createColourMap([ColourID("black")], dc)(ColourID("black")), 
397         RGBA(0, 0, 0, 1));
398 
399     auto cM = iota(0.0,8.0,1.0).map!((a) => ColourID(a)).
400             createColourMap(dc);
401     assert( cM( ColourID(0) ) != cM( ColourID(1) ) );
402     assertEqual( cM( ColourID(0) ), cM( ColourID(0) ) );
403 }
404 
405 struct ColourGradient(C)
406 {
407     import ggplotd.colourspace : toColourSpace;
408     void put(double value, C colour)
409     {
410         import std.range : back, empty;
411         if (!stops.data.empty)
412             assert( value > stops.data.back[0], 
413                 "Stops must be added in increasing value" );
414         stops.put( Tuple!(double, C)( value, colour ) );
415     }
416 
417     void put( double value, string name )
418     {
419         auto rgb = createNamedColours[name];
420         auto colour = toColourSpace!C( rgb );
421         this.put( value, colour );
422     }
423 
424     /**
425         To find the interval within which a value falls
426     
427         If value to high or low return respectively the highest two or lowest two
428     */
429     auto interval( double value ) const
430     {
431         import std.algorithm : findSplitBefore;
432         import std.range : empty, front, back;
433         assert(stops.data.length > 1, "Need at least two stops");
434         // Split stops around it. If one empty take two from other and warn value
435         // outside of coverage (debug), else take back and front from splitted
436         auto splitted = stops.data.findSplitBefore!"a[0]>b"([value]);
437 
438         if (splitted[0].empty)
439             return stops.data[0..2];
440         else if (splitted[1].empty)
441             return stops.data[($-2)..$];
442         return [splitted[0].back, splitted[1].front];
443     }
444 
445     /**
446     Get the colour associated with passed value
447     */
448     auto colour( double value ) const
449     {
450         import ggplotd.colourspace : toTuple;
451         // When returning colour by value, try zip(c1, c2).map!( (a,b) => a+v*(b-a)) or something
452         auto inval = interval( value );
453         auto sc = (value-inval[0][0])/(inval[1][0]-inval[0][0]);
454         auto minC = inval[0][1].toTuple;
455         auto maxC = inval[1][1].toTuple;
456         return ( C( 
457             minC[0] + sc*(maxC[0]-minC[0]),
458             minC[1] + sc*(maxC[1]-minC[1]),
459             minC[2] + sc*(maxC[2]-minC[2]) ) );
460     }
461 
462 private:
463     import std.range : Appender;
464     Appender!(Tuple!(double,C)[]) stops;
465 }
466 
467 unittest
468 {
469     import ggplotd.colourspace;
470     import std.range : back, front;
471 
472     ColourGradient!RGB cg;
473 
474     cg.put( 0, RGB(0,0,0) );
475     cg.put( 1, "white" );
476 
477     auto ans = cg.interval( 0.1 );
478     assertEqual( ans.front[0], 0 );
479     assertEqual( ans.back[0], 1 );
480 
481     cg = ColourGradient!RGB();
482 
483     cg.put( 0, RGB(0,0,0) );
484     cg.put( 0.2, RGB(0.5,0.6,0.8) );
485     cg.put( 1, "white" );
486     ans = cg.interval( 0.1 );
487     assertEqual( ans.front[0], 0 );
488     assertEqual( ans.back[0], 0.2 );
489 
490     ans = cg.interval( 1.1 );
491     assertEqual( ans.front[0], 0.2 );
492     assertEqual( ans.back[0], 1.0 );
493 
494     auto col = cg.colour( 0.1 );
495     assertEqual( col, RGB(0.25,0.3,0.4) );
496 }
497 
498 alias ColourGradientFunction = RGBA delegate( double value, double from, double till );
499 
500 /**
501 Function returning a colourgradient function based on a specified ColourGradient
502 
503 Params:
504     cg =        A ColourGradient
505     absolute =  Whether the cg is an absolute scale or relative (between 0 and 1)
506 
507 Examples:
508 -----------------
509 auto cg = ColourGradient!HCY();
510 cg.put( 0, HCY(200, 0.5, 0) ); 
511 cg.put( 100, HCY(200, 0.5, 0) ); 
512 GGPlotD().put( colourGradient( cg );
513 -----------------
514 */
515 ColourGradientFunction colourGradient(T)( in ColourGradient!T cg, 
516     bool absolute=true )
517 {
518     if (absolute) {
519         return ( double value, double from, double till ) 
520         {
521             import ggplotd.colourspace : RGBA, toColourSpace;
522             return cg.colour( value ).toColourSpace!RGBA;
523         };
524     }
525     return ( double value, double from, double till ) 
526     { 
527         import ggplotd.colourspace : RGBA, toColourSpace;
528         return cg.colour( (value-from)/(till-from) ).toColourSpace!RGBA;
529     };
530 }
531 
532 /**
533 Function returning a named colourgradient.
534 
535 Colours can be specified with colour names separated by dashes:
536 "white-red" will result in a colourgradient from white to red. You can specify more than two colours "blue-white-red". "default" will result in the default (blueish) colourgradient.
537 
538 Examples:
539 -----------------
540 GGPlotD().put( colourGradient( "blue-red" );
541 -----------------
542 */
543 ColourGradientFunction colourGradient(T)( string name )
544 {
545     import std.algorithm : splitter;
546     import std.range : empty, walkLength;
547     if ( !name.empty && name != "default" )
548     {
549         import ggplotd.colourspace : toColourSpace;
550         auto namedColours = createNamedColours();
551         auto cg = ColourGradient!T();
552         auto splitted = name.splitter("-");
553         auto dim = splitted.walkLength;
554         if (dim == 1)
555         {
556             auto c = namedColours[splitted.front].toColourSpace!T; 
557             cg.put(0, c );
558             cg.put(1, c );
559         }
560         if (dim > 2)
561         {
562             auto value = 0.0;
563             auto width = 1.0/(dim-1);
564             foreach( sp ; splitted )
565             {
566                 cg.put( value, namedColours[sp].toColourSpace!T );
567                 value += width;
568             }
569         }
570         return colourGradient(cg, false);
571     }
572     import ggplotd.colourspace : HCY;
573     auto cg = ColourGradient!HCY();
574     cg.put( 0, HCY(200, 0.5, 0) ); 
575     cg.put( 1, HCY(200, 0.5, 0.7) ); 
576     return colourGradient(cg, false);
577 }
578 
579 unittest
580 {
581     import ggplotd.colourspace : HCY;
582     auto cf = colourGradient!HCY( "red-white-blue" );
583     assertEqual( cf( -1, -1, 2 ).r, 1 );
584     assertEqual( cf( -1, -1, 2 ).g, 0 );
585     assertEqual( cf( 2, -1, 2 ).b, 1 );
586     assertLessThan( cf( 2, -1, 2 ).g, 1e-5 );
587     assertEqual( cf( 0.5, -1, 2 ).b, 1 );
588     assertEqual( cf( 0.5, -1, 2 ).g, 1 );
589 }