Как подобрать цвета для круговой диаграммы?
У меня есть некоторый код, который генерирует изображение круговой диаграммы. Это класс общего назначения, поэтому в качестве входных данных может быть задано любое количество срезов. Теперь у меня есть проблема выбора хороших цветов для ломтиков. Есть какой-то алгоритм, который хорош в этом?
или, может быть, я должен просто выбрать и перечислить фиксированные цвета? Но сколько их было. Может быть, 10 цветов и надеюсь, что не будет больше, чем 10 кусочков когда-либо? Кроме того, какие 10 цветов выбрать?
цвета должны следовать некоторые правила:
- они должны выглядеть красиво
- соседние цвета не должны быть похожи (синий рядом с зеленым-это no-go)
- цвет фона пирога белый, поэтому белый не подходит
предпочтительным решением будет некоторый алгоритм, манипулирующий значениями RGB.
8 ответов:
Я бы предварительно скомпилировал список из примерно 20 цветов, а затем начал повторять с 2-й цвета. Таким образом, вы не нарушите свое второе правило. Кроме того, если кто-то делает круговую диаграмму с более чем 20 кусочками, у них есть большие проблемы. :)
я решил это следующим образом:
- выбрать базовый цвет.
- вычислить hue (
baseHue
).- создайте цвет с такой же насыщенностью и яркостью, с его оттенком, рассчитанным как:
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());
я использовал класс HSLColor.
The Google Charts пример который использует 12 частей и низкопробный цвет #8A56E2:
посмотри Палитр, инструмент, который помогает определить схему раскраски для передачи качественной или количественной информации: карты, диаграммы и т. д. Из трех "типов" палитр, которые может генерировать этот инструмент - последовательные, качественные и расходящиеся - вам, вероятно, нужен последний, расходящийся...
вы даже можете скачать файлы Excel с определениями RGB всех палитр.
на данное решение чтобы решить Правило № 2 вопроса, следующий алгоритм меняет цвета вокруг средней точки пирога. Два параметра:
- pNbColors это количество кусочков в пироге
- pNonAdjacentSimilarColor логическое значение, указывающее, хотите ли вы иметь смежные похожие цвета или нет.
Я использую ColorHSL,ColorRGB и ColorUtils (представлен ниже).
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; }
это производит выход правой стороны:
класс 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:
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:
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; } }
обзор
преобразование из RGB в HSV, а затем регулировка оттенка (как ответ тут) создает непоследовательную воспринимаемую яркость. Желтый / зеленый заметно светлее, чем синий / фиолетовый:
аналогичный результат без такой вариация возможен:
алгоритм
алгоритм, однако, гораздо сложнее:
- конвертировать HTML шестнадцатеричный коды к номинальным значениям RGB (разделить компоненты на 255).
- преобразование значений RGB в цветовое пространство XYZ; используйте D65 ссылка белый sRGB рабочее место.
- конвертировать из XYZ в Laб цветовое пространство.
- конвертировать из Laб цветовое пространство LCH.
- вычислить цвет клина пирога оттенок в цвете LCH космос:
(360.0 div $wedges) * $wedge- пересчитать новый оттенок в радианах.
- конвертировать обратно из LCH в Laб цветовое пространство использование нового оттенка.
- конвертировать из Laб цветовое пространство XYZ.
- конвертировать из XYZ в sRGB цветовое пространство.
- умножьте значения RGB на 255.
реализация
вот пример реализации в 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 > 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 > $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 > ($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 > $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 > $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="'0123456789ABCDEF'"/> <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>
тригонометрические, корневые и другие математические функции остаются в качестве упражнения для читателя. Кроме того, никто в здравом уме не захочет кодировать все это в XSLT 1.0. XSLT 2.0, с другой стороны, имеет реализация здесь.
ресурсы
читайте далее:
- создание цветовых палитр с помощью интуитивно понятных параметров
- синтез цвета Палитры
- Полезные Уравнения Цвета
- введение в цветовые пространства
- равномерно распределенные цвета в перцептивно когерентном пространстве
- алгоритм выбора высококонтрастных цветовых наборов
- выбор цветов для статистической графики
- Colourspace конверсии
- Brewer palettes
эта статья 1985 года "ROSS E. ROLEY, CAPT" дает алгоритм максимизации цветоделения для произвольного набора цветов (С кодом на Фортране).
(разделение цветов, по-видимому, является важной проблемой визуализации для военных сил, чтобы предотвратить инциденты с синим на синем.)
однако если вы хотите придерживаться набора из 20 цветов, быстрым и простым решением будет выбрать вершины додекаэдра и преобразовать (x, y, z) координаты (соответствующим образом масштабированные) до (r,g,b).
генератор здесь. Он предназначен для веб-дизайна, но цвета будут отлично смотреться и на круговой диаграмме.
вы можете либо предварительно составить список приятных цветов, либо изучить логику генератора и сделать что-то подобное самостоятельно.
Я нашел эту формулу псевдокода, которая может помочь. Вы можете начать с набора, чтобы посеять его.
Формула Разницы В Цвете
ниже приведена формула, предложенная W3C для определения разницы между двумя цветами.
(максимум (Красное значение 1, красное значение 2) - минимум (Красное значение 1, красное значение 2)) + (максимум (зеленое значение 1, зеленое значение 2) - минимум (зеленое значение 1, зеленое значение 2)) + (максимум (синее значение 1, синее значение 2) - минимум (синее значение 1, Синее значение 2))
разница между цветом фона и цветом переднего плана должна быть больше 500.