2008-10-25 16 views
52

Mam trochę kodu, który generuje obraz wykresu kołowego. Jest to klasa ogólnego przeznaczenia, więc dowolną liczbę plasterków można podać jako dane wejściowe. Teraz mam problem z wybieraniem dobrych kolorów plasterków. Czy jest jakiś dobry algorytm?Jak wybrać kolory dla wykresu kołowego?

A może powinienem wybrać ręcznie i wyświetlić ustalone kolory? Ale ile. Może 10 kolorów i mam nadzieję, że nigdy nie będzie więcej niż 10 plasterków? Ponadto, które 10 kolorów do wyboru?

Kolory muszą przestrzegać pewnych zasad:

  • muszą wyglądać dobrze
  • sąsiednie kolory nie powinny być podobne (niebieskie obok zieleni jest no-go)
  • kolor pie tło jest białe , więc biały jest poza opcją

Niektóre algorytmy manipulujące wartościami RGB będą preferowanym rozwiązaniem.

+4

jaki jest niebieski podobny do zielonego? – peterchen

+10

@peterchen - bardzo podobny, jeśli jesteś ślepy w kolorze niebiesko-zielonym;) – redcalx

Odpowiedz

16

Chciałbym wstępnie skompilować listę około 20 kolorów, a następnie rozpocząć powtarzanie z 2nd kolorów. W ten sposób nie złamiesz drugiej reguły. Ponadto, jeśli ktoś tworzy wykres kołowy z więcej niż 20 plasterków, mają większe problemy. :)

+0

Dokładnie to robi aplikacja internetowa, nad którą obecnie pracuję. Tworzy 20, a następnie powtarza te 20 dziesięć razy, aby utworzyć listę 200. Mam jednak wykres kołowy z więcej niż 20 plasterkami i kolory się powtarzają. Jakieś sugestie? –

+2

Jeśli naprawdę potrzebujesz ~ 200 różnych kolorów, możesz rozważyć użycie bezpiecznej strony internetowej (lub podzbioru). http://pl.wikipedia.org/wiki/Web_colors –

+0

Po prostu implementacja sugestii @ BilltheLizard: 'if (pie.length% palette.length == 1) {palette.push (palette [1])}'. Wygląda na to, że reguła 2 pęka tylko wtedy, gdy mamy wielokrotność długości koloru + 1 plasterka; to po prostu zapewnia, że ​​powtórzymy z drugiego w przypadku łamania (przydatne przy używaniu czegoś takiego jak skala porządkowa d3) (https://github.com/mbostock/d3/wiki/Ordinal-Scales) do wyboru koloru). Teraz możemy użyć tylko kilku kolorów z ładnego (tj. Zasada 1 satysfakcjonująca) [paleta] (http://colorbrewer2.org/) i nie martwić się o złamanie reguły 2. – funseiki

1

Znalazłem tę formułę pseudokodu, która może pomóc. Możesz zacząć od zestawu do wysiewu.

Różnica Barwy Wzór

Poniżej formuła sugeruje W3C określić różnicę pomiędzy dwoma kolorami.

(maksymalna (Czerwona wartość 1, Czerwona wartość 2) - minimalna (Czerwona wartość 1, Czerwona wartość 2)) + (maksymalna (Zielona wartość 1, Zielona wartość 2) - minimalna (Zielona wartość 1, Zielona wartość 2)) + (maksymalna (wartość Niebieski 1 Blue wartość 2) - minimalna (wartość Niebieski 1 Blue wartość 2))

różnicę pomiędzy kolorem tła i kolorem powinna być większa niż 500

Here is the source

2

Istnieje generator here. Jest przeznaczony do projektowania stron internetowych, ale kolory również wyglądają świetnie na wykresie kołowym.

Można albo wstępnie skompilować listę ładnych kolorów, albo zbadać logikę za generatorem i zrobić coś podobnego.

+1

Niektóre wyjaśnienia generatora lub jego algorytmu byłoby miłe , ponieważ mięso tego postu jest ukryte za linkiem w tej chwili, a linki mają paskudny zwyczaj umierania. –

12

Spójrz na Color Brewer, narzędzie, które pomaga zdefiniować schemat kolorowania w celu przekazania informacji jakościowych lub ilościowych: mapy, wykresy itp. Spośród trzech "typów" palet, które to narzędzie może generować - sekwencyjne, jakościowe, i rozbieżne - prawdopodobnie potrzebujesz tego drugiego, rozdzielając ...

Możesz nawet pobierać pliki Excel z definicjami RGB wszystkich palet.

64

Rozwiązałem to w następujący sposób:

  1. Wybierz kolor bazową.
  2. Obliczyć jego hue (baseHue).
  3. Tworzenie koloru z tego samego nasycenie i jasność, z odcień obliczono jako:
     
        hue = baseHue + ((240/pieces) * piece % 240 
    

C#:

int n = 12; 

Color baseColor = System.Drawing.ColorTranslator.FromHtml("#8A56E2"); 
double baseHue = (new HSLColor(baseColor)).Hue; 

List<Color> colors = new List<Color>(); 
colors.Add(baseColor); 

double step = (240.0/(double)n); 

for (int i = 1; i < n; ++i) 
{ 
    HSLColor nextColor = new HSLColor(baseColor); 
    nextColor.Hue = (baseHue + step * ((double)i)) % 240.0; 
    colors.Add((Color)nextColor); 
} 

string colors = string.Join(",", colors.Select(e => e.Name.Substring(2)).ToArray()); 

I używana HSLColor class.

Google Charts example który wykorzystuje 12 sztuk i podstawowy kolor # 8A56E2:

Chart Example

+2

W twoim przykładzie dwa górne lewe kolory (10 godzin do północy) wyglądają identycznie na moim ekranie! – Brann

+0

Jak to nie całkowicie ignoruje zasadę nr 2, że podobne kolory nie powinny być w sąsiedztwie? To sprawia, że ​​ładny kolor * koło *, ale nie tak dobre dla wykresu kołowego. – Geobits

+0

Oto odpowiednik dla Androida: https://gist.github.com/siddharth96/cbb2d31509f9149e4565 To również łączy w sobie odpowiedź Pic Mickaela podaną poniżej –

4

się 1985 papieru przez „ROSS E. Roley kpt” daje algorytmu maksymalizacji rozdzielanie kolorów przy dowolnym zestawie kolorów (complete with code in FORTRAN).

(separacja kolorów wydaje się być ważnym zagadnieniem wizualizacja dla sił zbrojnych, aby zapobiec niebieski na niebiesko incydentów.)

Jednak jeśli chcesz, aby trzymać się z zestawu 20 kolorów, szybkie i proste rozwiązanie należy wybrać wierzchołki dwunastościanu i przekształcić współrzędne (x, y, z) (odpowiednio skalowane) na (r, g, b).

9

W oparciu o this solution w celu rozwiązania reguły nr 2 pytania, poniższy algorytm zamienia kolory wokół środkowego punktu wykresu kołowego. Te dwa parametry:

  1. pNbColors jest liczba plastry w tortu
  2. pNonAdjacentSimilarColor logiczna aby wskazać, czy chcesz mieć sąsiadujących podobne kolory lub nie.

Używam ColorHSL, ColorRGB i ColorUtils (przewidzianych poniżej).

public static function ColorArrayGenerator(
    pNbColors:int, 
    pNonAdjacentSimilarColor:Boolean = false):Array 
{  
    var colors:Array = new Array(); 
    var baseRGB:ColorRGB = new ColorRGB(); 
    baseRGB.setRGBFromUint(0x8A56E2); 

    var baseHSL:ColorHSL = new ColorHSL(); 
    rgbToHsl(baseHSL, baseRGB); 

    var currentHue:Number = baseHSL.Hue; 

    colors.push(baseRGB.getUintFromRGB()); 

    var step:Number = (360.0/pNbColors); 
    var nextHSL:ColorHSL; 
    var nextRGB:ColorRGB; 
    var i:int; 

    for (i = 1; i < pNbColors; i++) 
    { 
     currentHue += step; 
     if (currentHue > 360) 
     { 
      currentHue -= 360; 
     } 

     nextHSL = new ColorHSL(currentHue, baseHSL.Saturation, aseHSL.Luminance); 
     nextRGB = new ColorRGB(); 
     hslToRgb(nextRGB, nextHSL); 

     colors.push(nextRGB.getUintFromRGB()); 
    } 

    if (pNonAdjacentSimilarColor == true && 
     pNbColors > 2) 
    { 
     var holder:uint = 0; 
     var j:int; 

     for (i = 0, j = pNbColors/2; i < pNbColors/2; i += 2, j += 2) 
     { 
      holder = colors[i]; 
      colors[i] = colors[j]; 
      colors[j] = holder; 
     } 
    } 

    return colors; 
} 

To daje wyjście z boku po prawej stronie:

Comparison Image

klasy ColorHSL:

final public class ColorHSL 
{ 
    private var _hue:Number; // 0.0 .. 359.99999 

    private var _sat:Number; // 0.0 .. 100.0 

    private var _lum:Number; // 0.0 .. 100.0 

    public function ColorHSL(
     hue:Number = 0, 
     sat:Number = 0, 
     lum:Number = 0) 
    { 
     _hue = hue; 
     _sat = sat; 
     _lum = lum; 
    } 

    [Bindable]public function get Hue():Number 
    { 
     return _hue; 
    } 

    public function set Hue(value:Number):void 
    { 
     if (value > 360) 
     { 
      _hue = value % 360; 
     } // remember, hue is modulo 360 
     else if (value < 0) 
     { 
      _hue = 0; 
     } 
     else 
     { 
      _hue = value; 
     } 
    } 

    [Bindable]public function get Saturation():Number 
    { 
     return _sat; 
    } 

    public function set Saturation(value:Number):void 
    { 
     if (value > 100.0) 
     { 
      _sat = 100.0; 
     } 
     else if (value < 0) 
     { 
      _sat = 0; 
     } 
     else 
     { 
      _sat = value; 
     } 
    } 

    [Bindable]public function get Luminance():Number 
    { 
     return _lum; 
    } 

    public function set Luminance(value:Number):void 
    { 
     if (value > 100.0) 
     { 
      _lum = 100.0; 
     } 
     else if (value < 0) 
     { 
      _lum = 0; 
     } 
     else 
     { 
      _lum = value; 
     } 
    } 
} 

ColorRGB Klasa:

final public class ColorRGB 
{ 
    private var _red:uint; 
    private var _grn:uint; 
    private var _blu:uint; 
    private var _rgb:uint;  // composite form: 0xRRGGBB or #RRGGBB 

    public function ColorRGB(red:uint = 0, grn:uint = 0, blu:uint = 0) 
    { 
     setRGB(red, grn, blu); 
    } 

    [Bindable]public function get red():uint 
    { 
     return _red; 
    } 

    public function set red(value:uint):void 
    { 
     _red = (value & 0xFF); 
     updateRGB(); 
    } 

    [Bindable]public function get grn():uint 
    { 
     return _grn; 
    } 

    public function set grn(value:uint):void 
    { 
     _grn = (value & 0xFF); 
     updateRGB(); 
    } 

    [Bindable]public function get blu():uint 
    { 
     return _blu; 
    } 

    public function set blu(value:uint):void 
    { 
     _blu = (value & 0xFF); 
     updateRGB(); 
    } 

    [Bindable]public function get rgb():uint 
    { 
     return _rgb; 
    } 

    public function set rgb(value:uint):void 
    { 
     _rgb = value; 
     _red = (value >> 16) & 0xFF; 
     _grn = (value >> 8) & 0xFF; 
     _blu = value  & 0xFF; 
    } 

    public function setRGB(red:uint, grn:uint, blu:uint):void 
    { 
     this.red = red; 
     this.grn = grn; 
     this.blu = blu; 
    } 

    public function setRGBFromUint(pValue:uint):void 
    { 
     setRGB(((pValue >> 16) & 0xFF), ((pValue >> 8) & 0xFF), (pValue & 0xFF)); 
    } 

    public function getUintFromRGB():uint 
    { 
     return ((red << 16) | (grn << 8) | blu); 
    } 

    private function updateRGB():void 
    { 
     _rgb = (_red << 16) + (_grn << 8) + blu; 
    } 
} 

ColorUtils klasa:

final public class ColorUtils 
{ 
    public static function HSV2RGB(hue:Number, sat:Number, val:Number):uint 
    { 
     var red:Number = 0; 
     var grn:Number = 0; 
     var blu:Number = 0; 
     var i:Number; 
     var f:Number; 
     var p:Number; 
     var q:Number; 
     var t:Number; 
     hue%=360; 
     sat/=100; 
     val/=100; 
     hue/=60; 
     i = Math.floor(hue); 
     f = hue-i; 
     p = val*(1-sat); 
     q = val*(1-(sat*f)); 
     t = val*(1-(sat*(1-f))); 
     if (i==0) 
     { 
      red=val; 
      grn=t; 
      blu=p; 
     } 
     else if (i==1) 
     { 
      red=q; 
      grn=val; 
      blu=p; 
     } 
     else if (i==2) 
     { 
      red=p; 
      grn=val; 
      blu=t; 
     } 
     else if (i==3) 
     { 
      red=p; 
      grn=q; 
      blu=val; 
     } 
     else if (i==4) 
     { 
      red=t; 
      grn=p; 
      blu=val; 
     } 
     else if (i==5) 
     { 
      red=val; 
      grn=p; 
      blu=q; 
     } 
     red = Math.floor(red*255); 
     grn = Math.floor(grn*255); 
     blu = Math.floor(blu*255); 

     return (red<<16) | (grn << 8) | (blu); 
    } 

    // 
    public static function RGB2HSV(pColor:uint):Object 
    { 
     var red:uint = (pColor >> 16) & 0xff; 
     var grn:uint = (pColor >> 8) & 0xff; 
     var blu:uint = pColor & 0xff; 

     var x:Number; 
     var val:Number; 
     var f:Number; 
     var i:Number; 
     var hue:Number; 
     var sat:Number; 
     red/=255; 
     grn/=255; 
     blu/=255; 
     x = Math.min(Math.min(red, grn), blu); 
     val = Math.max(Math.max(red, grn), blu); 
     if (x==val){ 
      return({h:undefined, s:0, v:val*100}); 
     } 
     f = (red == x) ? grn-blu : ((grn == x) ? blu-red : red-grn); 
     i = (red == x) ? 3 : ((grn == x) ? 5 : 1); 
     hue = Math.floor((i-f/(val-x))*60)%360; 
     sat = Math.floor(((val-x)/val)*100); 
     val = Math.floor(val*100); 
     return({h:hue, s:sat, v:val}); 
    } 

    /** 
    * Generates an array of pNbColors colors (uint) 
    * The colors are generated to fill a pie chart (meaning that they circle back to the starting color) 
    * @param pNbColors The number of colors to generate (ex: Number of slices in the pie chart) 
    * @param pNonAdjacentSimilarColor Should the colors stay Adjacent or not ? 
    */ 
    public static function ColorArrayGenerator(
     pNbColors:int, 
     pNonAdjacentSimilarColor:Boolean = false):Array 
    { 
     // Based on http://www.flexspectrum.com/?p=10 

     var colors:Array = []; 
     var baseRGB:ColorRGB = new ColorRGB(); 
     baseRGB.setRGBFromUint(0x8A56E2); 

     var baseHSL:ColorHSL = new ColorHSL(); 
     rgbToHsl(baseHSL, baseRGB); 

     var currentHue:Number = baseHSL.Hue; 

     colors.push(baseRGB.getUintFromRGB()); 

     var step:Number = (360.0/pNbColors); 
     var nextHSL:ColorHSL; 
     var nextRGB:ColorRGB; 
     var i:int; 

     for (i = 1; i < pNbColors; i++) 
     { 
      currentHue += step; 

      if (currentHue > 360) 
      { 
       currentHue -= 360; 
      } 

      nextHSL = new ColorHSL(currentHue, baseHSL.Saturation, baseHSL.Luminance); 
      nextRGB = new ColorRGB(); 
      hslToRgb(nextRGB, nextHSL); 

      colors.push(nextRGB.getUintFromRGB()); 
     } 

     if (pNonAdjacentSimilarColor == true && 
      pNbColors > 2) 
     { 
      var holder:uint = 0; 
      var j:int; 

      for (i = 0, j = pNbColors/2; i < pNbColors/2; i += 2, j += 2) 
      { 
       holder = colors[i]; 
       colors[i] = colors[j]; 
       colors[j] = holder; 
      } 
     } 

     return colors; 
    } 

    static public function rgbToHsl(hsl:ColorHSL, rgb:ColorRGB):void 
    { 
     var h:Number = 0; 
     var s:Number = 0; 
     var l:Number = 0; 

     // Normalizes incoming RGB values. 
     // 
     var dRed:Number = (Number)(rgb.red/255.0); 
     var dGrn:Number = (Number)(rgb.grn/255.0); 
     var dBlu:Number = (Number)(rgb.blu/255.0); 

     var dMax:Number = Math.max(dRed, Math.max(dGrn, dBlu)); 
     var dMin:Number = Math.min(dRed, Math.min(dGrn, dBlu)); 

     //------------------------- 
     // hue 
     // 
     if (dMax == dMin) 
     { 
      h = 0;     // undefined 
     } 
     else if (dMax == dRed && dGrn >= dBlu) 
     { 
      h = 60.0 * (dGrn - dBlu)/(dMax - dMin); 
     } 
     else if (dMax == dRed && dGrn < dBlu) 
     { 
      h = 60.0 * (dGrn - dBlu)/(dMax - dMin) + 360.0; 
     } 
     else if (dMax == dGrn) 
     { 
      h = 60.0 * (dBlu - dRed)/(dMax-dMin) + 120.0; 
     } 
     else if (dMax == dBlu) 
     { 
      h = 60.0 * (dRed - dGrn)/(dMax - dMin) + 240.0; 
     } 

     //------------------------- 
     // luminance 
     // 
     l = (dMax + dMin)/2.0; 

     //------------------------- 
     // saturation 
     // 
     if (l == 0 || dMax == dMin) 
     { 
      s = 0; 
     } 
     else if (0 < l && l <= 0.5) 
     { 
      s = (dMax - dMin)/(dMax + dMin); 
     } 
     else if (l>0.5) 
     { 
      s = (dMax - dMin)/(2 - (dMax + dMin)); //(dMax-dMin > 0)? 
     } 

     hsl.Hue = h; 
     hsl.Luminance = l; 
     hsl.Saturation = s; 

    } // rgbToHsl 

    //--------------------------------------- 
    // Convert the input RGB values to the corresponding HSL values. 
    // 
    static public function hslToRgb(rgb:ColorRGB, hsl:ColorHSL):void 
    { 
     if (hsl.Saturation == 0) 
     { 
      // Achromatic color case, luminance only. 
      // 
      var lumScaled:int = (int)(hsl.Luminance * 255.0); 
      rgb.setRGB(lumScaled, lumScaled, lumScaled); 
      return; 
     } 

     // Chromatic case... 
     // 
     var dQ:Number = (hsl.Luminance < 0.5) ? (hsl.Luminance * (1.0 + hsl.Saturation)): ((hsl.Luminance + hsl.Saturation) - (hsl.Luminance * hsl.Saturation)); 
     var dP:Number = (2.0 * hsl.Luminance) - dQ; 

     var dHueAng:Number = hsl.Hue/360.0; 

     var dFactor:Number = 1.0/3.0; 

     var adT:Array = []; 

     adT[0] = dHueAng + dFactor;    // Tr 
     adT[1] = dHueAng;      // Tg 
     adT[2] = dHueAng - dFactor;    // Tb 

     for (var i:int = 0; i < 3; i++) 
     { 
      if (adT[i] < 0) 
      { 
       adT[i] += 1.0; 
      } 

      if (adT[i] > 1) 
      { 
       adT[i] -= 1.0; 
      } 

      if ((adT[i] * 6) < 1) 
      { 
       adT[i] = dP + ((dQ - dP) * 6.0 * adT[i]); 
      } 
      else if ((adT[i] * 2.0) < 1)  // (1.0/6.0) <= adT[i] && adT[i] < 0.5 
      { 
       adT[i] = dQ; 
      } 
      else if ((adT[i] * 3.0) < 2)  // 0.5 <= adT[i] && adT[i] < (2.0/3.0) 
      { 
       adT[i] = dP + (dQ-dP) * ((2.0/3.0) - adT[i]) * 6.0; 
      } 
      else 
      { 
       adT[i] = dP; 
      } 
     } 

     rgb.setRGB(adT[0] * 255.0, adT[1] * 255.0, adT[2] * 255.0); 

    } // hslToRgb 

    //--------------------------------------- 
    // Adjust the luminance value by the specified factor. 
    // 
    static public function adjustRgbLuminance(rgb:ColorRGB, factor:Number):void 
    { 
     var hsl:ColorHSL = new ColorHSL(); 

     rgbToHsl(hsl, rgb); 

     hsl.Luminance *= factor; 

     if (hsl.Luminance < 0.0) 
     { 
      hsl.Luminance = 0.0; 
     } 

     if (hsl.Luminance > 1.0) 
     { 
      hsl.Luminance = 1.0; 
     } 

     hslToRgb(rgb, hsl); 
    } 

    //--------------------------------------- 
    // 
    static public function uintTo2DigitHex(value:uint):String 
    { 
     var str:String = value.toString(16).toUpperCase(); 

     if (1 == str.length) 
     { 
      str = "0" + str; 
     } 

     return str; 
    } 

    //--------------------------------------- 
    // 
    static public function uintTo6DigitHex(value:uint):String 
    { 
     var str:String = value.toString(16).toUpperCase(); 

     if (1 == str.length) {return "00000" + str;} 
     if (2 == str.length) {return "0000" + str;} 
     if (3 == str.length) {return "000" + str;} 
     if (4 == str.length) {return "00" + str;} 
     if (5 == str.length) {return "0" + str;} 

     return str; 
    } 
} 
5

Przegląd

Konwersja RGB do HSV, a następnie dostosowanie odcienia (jak answered here) tworzy niespójne postrzeganej jasności.Żółty/zielony są znacznie lżejsze niż niebieski/fioletowy:

Inconsistent

podobny wynik bez takiej zmiany jest możliwe:

Consistent

Algorytm

Algorytm jednakże jest znacznie bardziej skomplikowany:

  1. Konwertuj kody szesnastkowe HTML na nominalne wartości RGB (dziel komponenty przez 255). Przetwarzanie wartości RGB na XYZ colour space; użyj D65 reference white sRGB working space. Konwertuj z XYZ na Lab colour space.
  2. Konwersja z L a b do LCH colour space.
  3. Obliczyć odcień koloru klina w przestrzeni kolorów LCH:
    (360.0 div $wedges) * $wedge
  4. Ponownie oblicz nowy odcień w radianach.
  5. Konwersja z powrotem z LCH na Lab colour space za pomocą nowego odcienia.
  6. Konwersja z L a b do XYZ colour space.
  7. Konwersja z XYZ na sRGB colour space.
  8. pomnożyć wartości RGB przez 255.

Wdrażania

Oto przykład implementacja w XSLT 1.0:

<?xml version="1.0"?> 
<!-- 
| The MIT License 
| 
| Copyright 2014 White Magic Software, Inc. 
| 
| Permission is hereby granted, free of charge, to any person 
| obtaining a copy of this software and associated documentation 
| files (the "Software"), to deal in the Software without 
| restriction, including without limitation the rights to use, 
| copy, modify, merge, publish, distribute, sublicense, and/or 
| sell copies of the Software, and to permit persons to whom the 
| Software is furnished to do so, subject to the following 
| conditions: 
| 
| The above copyright notice and this permission notice shall be 
| included in all copies or substantial portions of the Software. 
| 
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
| OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
| HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
| WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
| FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 
| OTHER DEALINGS IN THE SOFTWARE. 
+--> 
<xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 

<!-- Reference white (X, Y, and Z components) --> 
<xsl:variable name="X_r" select="0.950456"/> 
<xsl:variable name="Y_r" select="1.000000"/> 
<xsl:variable name="Z_r" select="1.088754"/> 
<xsl:variable name="LAB_EPSILON" select="216.0 div 24389.0"/> 
<xsl:variable name="LAB_K" select="24389.0 div 27.0"/> 

<!-- Pie wedge colours based on this hue. --> 
<xsl:variable name="base_colour" select="'46A5E5'"/> 

<!-- Pie wedge stroke colour. --> 
<xsl:variable name="stroke_colour" select="'white'"/> 

<!-- 
| Creates a colour for a particular pie wedge. 
| 
| http://en.wikipedia.org/wiki/HSL_and_HSV 
+--> 
<xsl:template name="fill"> 
    <!-- Current wedge number for generating a colour. --> 
    <xsl:param name="wedge"/> 
    <!-- Total number of wedges in the pie. --> 
    <xsl:param name="wedges"/> 
    <!-- RGB colour in hexadecimal. --> 
    <xsl:param name="colour"/> 

    <!-- Derive the colour decimal values from $colour's HEX code. --> 
    <xsl:variable name="r"> 
    <xsl:call-template name="hex2dec"> 
     <xsl:with-param name="hex" 
     select="substring($colour, 1, 2)"/> 
    </xsl:call-template> 
    </xsl:variable> 
    <xsl:variable name="g"> 
    <xsl:call-template name="hex2dec"> 
     <xsl:with-param name="hex" 
     select="substring($colour, 3, 2)"/> 
    </xsl:call-template> 
    </xsl:variable> 
    <xsl:variable name="b"> 
    <xsl:call-template name="hex2dec"> 
     <xsl:with-param name="hex" 
     select="substring($colour, 5, 2)"/> 
    </xsl:call-template> 
    </xsl:variable> 

    <!-- 
    | Convert RGB to XYZ, using nominal range for RGB. 
    | http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html 
    +--> 
    <xsl:variable name="r_n" select="$r div 255" /> 
    <xsl:variable name="g_n" select="$g div 255" /> 
    <xsl:variable name="b_n" select="$b div 255" /> 

    <!-- 
    | Assume colours are in sRGB. 
    | http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html 
    --> 
    <xsl:variable name="x" 
    select=".4124564 * $r_n + .3575761 * $g_n + .1804375 * $b_n"/> 
    <xsl:variable name="y" 
    select=".2126729 * $r_n + .7151522 * $g_n + .0721750 * $b_n"/> 
    <xsl:variable name="z" 
    select=".0193339 * $r_n + .1191920 * $g_n + .9503041 * $b_n"/> 

    <!-- 
    | Convert XYZ to L*a*b. 
    | http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html 
    +--> 
    <xsl:variable name="if_x"> 
    <xsl:call-template name="lab_f"> 
     <xsl:with-param name="xyz_n" select="$x div $X_r"/> 
    </xsl:call-template> 
    </xsl:variable> 
    <xsl:variable name="if_y"> 
    <xsl:call-template name="lab_f"> 
     <xsl:with-param name="xyz_n" select="$y div $Y_r"/> 
    </xsl:call-template> 
    </xsl:variable> 
    <xsl:variable name="if_z"> 
    <xsl:call-template name="lab_f"> 
     <xsl:with-param name="xyz_n" select="$z div $Z_r"/> 
    </xsl:call-template> 
    </xsl:variable> 

    <xsl:variable name="lab_l" select="(116.0 * $if_y) - 16.0"/> 
    <xsl:variable name="lab_a" select="500.0 * ($if_x - $if_y)"/> 
    <xsl:variable name="lab_b" select="200.0 * ($if_y - $if_z)"/> 

    <!-- 
    | Convert L*a*b to LCH. 
    | http://www.brucelindbloom.com/index.html?Eqn_Lab_to_LCH.html 
    +--> 
    <xsl:variable name="lch_l" select="$lab_l"/> 

    <xsl:variable name="lch_c"> 
    <xsl:call-template name="sqrt"> 
     <xsl:with-param name="n" select="($lab_a * $lab_a) + ($lab_b * $lab_b)"/> 
    </xsl:call-template> 
    </xsl:variable> 

    <xsl:variable name="lch_h"> 
    <xsl:call-template name="atan2"> 
     <xsl:with-param name="x" select="$lab_b"/> 
     <xsl:with-param name="y" select="$lab_a"/> 
    </xsl:call-template> 
    </xsl:variable> 

    <!-- 
    | Prevent similar adjacent colours. 
    | http://math.stackexchange.com/a/936767/7932 
    +--> 
    <xsl:variable name="wi" select="$wedge"/> 
    <xsl:variable name="wt" select="$wedges"/> 
    <xsl:variable name="w"> 
    <xsl:choose> 
     <xsl:when test="$wt &gt; 5"> 
     <xsl:variable name="weven" select="(($wi+4) mod ($wt + $wt mod 2))"/> 
     <xsl:value-of 
      select="$weven * (1-($wi mod 2)) + ($wi mod 2 * $wi)"/> 
     </xsl:when> 
     <xsl:otherwise> 
     <xsl:value-of select="$wedge"/> 
     </xsl:otherwise> 
    </xsl:choose> 
    </xsl:variable> 
    <!-- lch_l, lch_c, and lch_h are now set; rotate the hue. --> 
    <xsl:variable name="lch_wedge_h" select="(360.0 div $wedges) * $wedge"/> 

    <!-- 
    | Convert wedge's hue-adjusted LCH to L*a*b. 
    | http://www.brucelindbloom.com/index.html?Eqn_LCH_to_Lab.html 
    +--> 
    <xsl:variable name="lab_sin_h"> 
    <xsl:call-template name="sine"> 
     <xsl:with-param name="degrees" select="$lch_wedge_h"/> 
    </xsl:call-template> 
    </xsl:variable> 
    <xsl:variable name="lab_cos_h"> 
    <xsl:call-template name="cosine"> 
     <xsl:with-param name="degrees" select="$lch_wedge_h"/> 
    </xsl:call-template> 
    </xsl:variable> 

    <xsl:variable name="final_lab_l" select="$lch_l"/> 
    <xsl:variable name="final_lab_a" select="$lch_c * $lab_cos_h"/> 
    <xsl:variable name="final_lab_b" select="$lch_c * $lab_sin_h"/> 

    <!-- 
    | Convert L*a*b to XYZ. 
    | http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html 
    +--> 
    <xsl:variable name="of_y" select="($final_lab_l + 16.0) div 116.0"/> 
    <xsl:variable name="of_x" select="($final_lab_a div 500.0) + $of_y"/> 
    <xsl:variable name="of_z" select="$of_y - ($final_lab_b div 200.0)"/> 

    <xsl:variable name="of_x_pow"> 
    <xsl:call-template name="power"> 
     <xsl:with-param name="base" select="$of_x"/> 
     <xsl:with-param name="exponent" select="3"/> 
    </xsl:call-template> 
    </xsl:variable> 
    <xsl:variable name="of_z_pow"> 
    <xsl:call-template name="power"> 
     <xsl:with-param name="base" select="$of_z"/> 
     <xsl:with-param name="exponent" select="3"/> 
    </xsl:call-template> 
    </xsl:variable> 

    <xsl:variable name="ox_r"> 
    <xsl:choose> 
     <xsl:when test="$of_x_pow &gt; $LAB_EPSILON"> 
     <xsl:value-of select="$of_x_pow"/> 
     </xsl:when> 
     <xsl:otherwise> 
     <xsl:value-of select="((116.0 * $of_x) - 16.0) div $LAB_K"/> 
     </xsl:otherwise> 
    </xsl:choose> 
    </xsl:variable> 
    <xsl:variable name="oy_r"> 
    <xsl:choose> 
     <xsl:when test="$final_lab_l &gt; ($LAB_K * $LAB_EPSILON)"> 
     <xsl:call-template name="power"> 
      <xsl:with-param name="base" 
      select="($final_lab_l + 16.0) div 116.0"/> 
      <xsl:with-param name="exponent" 
      select="3"/> 
     </xsl:call-template> 
     </xsl:when> 
     <xsl:otherwise> 
     <xsl:value-of select="$final_lab_l div $LAB_K"/> 
     </xsl:otherwise> 
    </xsl:choose> 
    </xsl:variable> 
    <xsl:variable name="oz_r"> 
    <xsl:choose> 
     <xsl:when test="$of_z_pow &gt; $LAB_EPSILON"> 
     <xsl:value-of select="$of_z_pow"/> 
     </xsl:when> 
     <xsl:otherwise> 
     <xsl:value-of select="((116.0 * $of_z) - 16.0) div $LAB_K"/> 
     </xsl:otherwise> 
    </xsl:choose> 
    </xsl:variable> 

    <xsl:variable name="X" select="$ox_r * $X_r"/> 
    <xsl:variable name="Y" select="$oy_r * $Y_r"/> 
    <xsl:variable name="Z" select="$oz_r * $Z_r"/> 

    <!-- 
    | Convert XYZ to sRGB. 
    | http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html 
    +--> 
    <xsl:variable name="R" 
    select="3.2404542 * $X + -1.5371385 * $Y + -0.4985314 * $Z"/> 
    <xsl:variable name="G" 
    select="-0.9692660 * $X + 1.8760108 * $Y + 0.0415560 * $Z"/> 
    <xsl:variable name="B" 
    select="0.0556434 * $X + -0.2040259 * $Y + 1.0572252 * $Z"/> 

    <!-- Round the result. --> 
    <xsl:variable name="R_r" select="round($R * 255)"/> 
    <xsl:variable name="G_r" select="round($G * 255)"/> 
    <xsl:variable name="B_r" select="round($B * 255)"/> 

    <xsl:text>rgb(</xsl:text> 
    <xsl:value-of select="concat($R_r, ',', $G_r, ',', $B_r)"/> 
    <xsl:text>)</xsl:text> 
</xsl:template> 

<xsl:template name="lab_f"> 
    <xsl:param name="xyz_n"/> 

    <xsl:choose> 
    <xsl:when test="$xyz_n &gt; $LAB_EPSILON"> 
     <xsl:call-template name="nthroot"> 
     <xsl:with-param name="index" select="3"/> 
     <xsl:with-param name="radicand" select="$xyz_n"/> 
     </xsl:call-template> 
    </xsl:when> 
    <xsl:otherwise> 
     <xsl:value-of select="($LAB_K * $xyz_n + 16.0) div 116.0" /> 
    </xsl:otherwise> 
    </xsl:choose> 
</xsl:template> 

<!-- Converts a two-digit hexadecimal number to decimal. --> 
<xsl:template name="hex2dec"> 
    <xsl:param name="hex"/> 

    <xsl:variable name="digits" select="'ABCDEF'"/> 
    <xsl:variable name="X" select="substring($hex, 1, 1)"/> 
    <xsl:variable name="Y" select="substring($hex, 2, 1)"/> 
    <xsl:variable name="Xval" 
    select="string-length(substring-before($digits,$X))"/> 
    <xsl:variable name="Yval" 
    select="string-length(substring-before($digits,$Y))"/> 
    <xsl:value-of select="16 * $Xval + $Yval"/> 
</xsl:template> 

</xsl:stylesheet> 

W trygonometrycznych, root, i różne funkcje matematyczne są pozostawione jako ćwiczenie dla czytelnika. Ponadto, nikt na swoim prawym umyśle nie chciałby zakodować tego wszystkiego w XSLT 1.0. Natomiast XSLT 2.0 ma numer implementation here.

Resources

Dalsze czytanie:

Powiązane problemy