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 }