Coverage for gramex\pptgen\color.py : 53%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2A color and gradient management system.
4*Do not pick colors yourself*. A lot of research has gone into color
5palettes. Specifically, for:
7Numeric data
8 use `ColorBrewer <http://colorbrewer2.org/>`_ themes
10Non-numeric data
11 use `ColorBrewer <http://colorbrewer2.org/>`_
12 or `Microsoft Office themes`_
14Page design
15 use `kuler.adobe.com <https://kuler.adobe.com/#create/fromanimage>`_
16 *from an image* (not directly).
19Microsoft office themes
20-----------------------
22The following color palettes based on Microsoft Office are available:
24Office, Adjacency, Apex, Apothecary, Aspect, Austin, BlackTie, Civic, Clarity,
25Composite, Concourse, Couture, Elemental, Equity, Essential, Executive, Flow,
26Foundry, Grid, Hardcover, Horizon, Median, Metro, Module, Newsprint, Opulent,
27Oriel, Origin, Paper, Perspective, Pushpin, SlipStream, Solstice, Technic,
28Thatch, Trek, Urban, Verve, Waveform
30A palette's colours can be accessed as an array or as an attribute. For e.g.:
32>>> Office[0] # The first element
33u'#4f81bd'
34>>> Office.accent_1 # ... is called .accent_1
35u'#4f81bd'
36>>> Office[1] # The next element
37u'#c0504d'
38>>> Office.accent_2 # ... is called .accent_2
39u'#c0504d'
41The following 10 attributes are available (in order):
430. accent_1
441. accent_2
452. accent_3
463. accent_4
474. accent_5
485. accent_6
496. light_2
507. dark_2
518. light_1
529. dark_1
54"""
56import os
57import re
58import six
59import json
60import operator
61import colorsys
62import warnings
63import numpy as np
64from io import open
67BASE_0 = 0
68BASE_1 = 1
69BASE_2 = 2
70BASE_3 = 3
71BASE_4 = 4
72BASE_5 = 5
73BASE_7 = 7
74BASE_9 = 9
75BASE_15 = 15
76BASE_16 = 16
77BASE_255 = 255
78BASE_256 = 256
81class _MSO(object):
82 """
83 Microsoft office themes. Refer to colors in any of these ways:
85 color['accent_1']
86 color[0]
87 color.accent_1
88 color[:1]
89 """
91 _lookup = {
92 'accent_1': 0,
93 'accent_2': 1,
94 'accent_3': 2,
95 'accent_4': 3,
96 'accent_5': 4,
97 'accent_6': 5,
98 'light_2': 6,
99 'dark_2': 7,
100 'light_1': 8,
101 'dark_1': 9,
102 }
104 def __init__(self, *values):
105 self.values = values
107 def __getitem__(self, key):
108 if isinstance(key, slice) or type(key) is int:
109 return self.values.__getitem__(key)
110 elif key in self._lookup:
111 return self.values[self._lookup[key]]
113 def __getattr__(self, key):
114 if key in self._lookup:
115 return self.values[self._lookup[key]]
117 def __len__(self):
118 return len(self.values)
120 def __str__(self):
121 return ' '.join(self.values)
123 def __repr__(self):
124 return '_MSO' + repr(self.values)
127def _rgb(color):
128 """
129 .. deprecated:: 0.1
130 Use :func:`rgba` instead
131 """
132 warnings.warn('Use color.rgba instead of color._rgb',
133 FutureWarning, stacklevel=2)
134 return (int(color[-6:-4], BASE_16), int(color[-4:-2], BASE_16),
135 int(color[-2:], BASE_16))
138def gradient(value, grad, opacity=1, sort=True):
139 """
140 Converts a number or list ``x`` into a color using a gradient.
143 :arg number value: int, float, list, numpy array or any iterable.
144 If an iterable is passed, a list of colors is returned.
145 :arg list grad: tuple/list of ``(value, color)`` tuples
146 For example, ``((0, 'red'), (.5, 'yellow'), (1, 'green'))``.
147 Values will be sorted automatically
148 :arg float opacity: optional alpha to be added to the returned color
149 :arg bool sort: optional. If ``grad`` is already sorted, set ``sort=False``
151 These gradients are available:
153 **divergent gradients** : on a ``[-1, 1]`` scale
154 multi-color
155 ``RdGy``, ``RdYlGn``, ``Spectral``
156 ... also print-friendly and color-blind safe
157 ``BrBG``, ``PiYG``, ``PRGn``, ``RdBu``, ``RdYlBu``
158 ... and also photocopyable
159 ``PuOr``
160 **sequential gradients** : on a ``[0, 1]`` scale
161 one-color
162 ``Reds``, ``Blues``, ``Greys``, ``Greens``, ``Oranges``,
163 ``Purples``, ``Browns``, ``Yellows``
164 two-color
165 ``BuGn``, ``BuPu``, ``GnBu``, ``OrRd``, ``PuBu``, ``PuRd``,
166 ``RdPu``, ``YlGn``
167 three-color
168 ``YlGnBu``, ``YlOrBr``, ``YlOrRd``, ``PuBuGn``
170 The following are also available, but do not use them.
172 - ``RYG`` maps ``[0, 1]`` to Red-Yellow-Green
173 - ``RWG`` maps ``[0, 1]`` to Red-White-Green
174 - ``RYG_1`` maps ``[-1, -1]`` to Red-Yellow-Green
175 - ``RWG_1`` maps ``[-1, -1]`` to Red-White-Green
177 Examples::
179 # Get a value that is 40% blue and 60% white, use:
180 >>> gradient(0.4, ((0, 'blue'), (1, 'white')))
181 '#66f'
183 # A list (or any iterable) input returns a list of colors
184 >>> gradient([0.2, 0.6, 0.8], ((0, 'blue'), (1, 'white')))
185 ['#33f', '#99f', '#ccf']
187 # Values out of range are truncated.
188 >>> gradient([-10, +10], ((0, 'blue'), (1, 'white')))
189 ['blue', 'white']
191 # Use a pre-defined gradient to classify a value between -1 to 1 on a
192 # Red-Yellow-Green scale. (Returns a value between Yellow and Green).
193 >>> gradient(0.5, RdYlGn)
194 '#8ccb87'
195 """
196 if sort:
197 grad = sorted(grad, key=operator.itemgetter(BASE_0))
199 # If value is iterable, apply gradient to each value (recursively)
200 if np.ndim(value) > BASE_0:
201 return [gradient(val, grad, opacity=opacity, sort=False)
202 for val in value]
204 value = float(value) if not np.isnan(value) else BASE_0
205 if value <= grad[BASE_0][BASE_0]:
206 return grad[BASE_0][BASE_1]
208 grd_idx = -1
209 if value >= grad[grd_idx][BASE_0]: 209 ↛ 210line 209 didn't jump to line 210, because the condition on line 209 was never true
210 return grad[grd_idx][BASE_1]
211 i = BASE_0
212 for i, (start, color1) in enumerate(grad): 212 ↛ 215line 212 didn't jump to line 215, because the loop on line 212 didn't complete
213 if value <= start:
214 break
215 dist1 = (value - grad[i - BASE_1][BASE_0]) / (
216 grad[i][BASE_0] - grad[i - BASE_1][BASE_0])
218 sec_dist = 1.0
219 dist2 = sec_dist - dist1
220 color1 = rgba(grad[i - BASE_1][BASE_1])
221 color2 = rgba(grad[i][BASE_1])
222 return name(color1[BASE_0] * dist2 + color2[BASE_0] * dist1,
223 color1[BASE_1] * dist2 + color2[BASE_1] * dist1,
224 color1[BASE_2] * dist2 + color2[BASE_2] * dist1,
225 opacity)
228_DISTINCTS = [
229 "#1f77b4", "#aec7e8",
230 "#ff7f0e", "#ffbb78",
231 "#2ca02c", "#98df8a",
232 "#d62728", "#ff9896",
233 "#9467bd", "#c5b0d5",
234 "#8c564b", "#c49c94",
235 "#e377c2", "#f7b6d2",
236 "#7f7f7f", "#c7c7c7",
237 "#bcbd22", "#dbdb8d",
238 "#17becf", "#9edae5"
239]
242def distinct(count):
243 """
244 Generates a list of ``count`` distinct colors, for up to 20 colors.
246 :arg int count: number of colors to return
248 Examples::
250 >>> distinct(4)
251 ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728']
253 Notes:
255 - Colour conversion between RGB and HCL are available on
256 `color.py <http://2sn.org/python/color.py>`_ and
257 `python-colormath <http://python-colormath.readthedocs.org/>`_
258 - People seem to struggle with about
259 `26 colours
260 <http://eleanormaclure.files.wordpress.com/2011/03/colour-coding.pdf>`_
261 so this may be an upper bound
262 - A good starting point for resources is on
263 `stackoverflow <http://stackoverflow.com/q/470690/100904>`_ and the
264 `graphic design <http://graphicdesign.stackexchange.com/q/3682>`_
265 stackexchange
266 - More palettes are available from
267 `Kenneth Kelly (22) <http://www.iscc.org/pdf/PC54_1724_001.pdf>`_ and
268 `ggplot2 <http://learnr.wordpress.com/2009/04/15/ggplot2/>`_
269 - See also: http://epub.wu.ac.at/1692/1/document.pdf
270 """
271 tens_count = 10
272 twenty_count = 20
273 _distinct = 2
274 if count <= tens_count:
275 return [_DISTINCTS[_distinct * i] for i in range(count)]
276 elif count <= twenty_count:
277 return _DISTINCTS[:count]
278 else:
279 return _DISTINCTS[:]
282def contrast(color, white='#ffffff', black='#000000'):
283 """
284 Returns the colour (white or black) that contrasts best with a given
285 color.
287 :arg color color: color string recognied by :func:`rgba`.
288 If this color is dark, returns white. Else, black
289 :arg color white: color string, optional.
290 Replace this with any light colour you want to use instead of white
291 :arg color black: color string, optional.
292 Replace this with any dark colour you want to use instead of black
294 Examples::
296 >>> contrast('blue')
297 '#fff'
298 >>> contrast('lime')
299 '#000'
301 Note: `Decolorize <http://www.eyemaginary.com/Rendering/TurnColorsGray.pdf>`_
302 algorithm used. Appears to be the most preferred
303 (`ref <http://dcgi.felk.cvut.cz/home/cadikm/color_to_gray_evaluation/>`_).
304 """
305 idx = 3
306 red, green, blue = rgba(color)[:idx]
307 red_opac = 0.299
308 blue_opac = 0.114
309 green_opac = 0.582
310 cut_off = .5
312 luminosity = red_opac * red + green_opac * green + blue_opac * blue
313 return black if luminosity > cut_off else white
316def brighten(color, percent):
317 """
318 Brighten or darken a color by percentage. If ``percent`` is positive,
319 brighten the color. Else darken the color.
321 :arg str color: color string recognied by :func:`rgba`
322 :arg float percent: -1 indicates black, +1 indicates white. Rest are interpolated.
324 Examples::
326 >>> brighten('lime', -1) # 100% darker, i.e. black
327 '#000'
328 >>> brighten('lime', -0.5) # 50% darker
329 '#007f00'
330 >>> brighten('lime', 0) # Returns the color as-is
331 '#0f0'
332 >>> brighten('lime', 0.5) # 50% brighter
333 '#7fff7f'
334 >>> brighten('lime', 1) # 100% brighter, i.e. white
335 '#fff'
336 >>> brighten('lime', 2) # Values beyond +1 or -1 are truncated
337 '#fff'
338 """
339 _min = -1
340 _mid = 0
341 _max = +1
342 black = '#0000000'
343 white = '#ffffff'
344 return gradient(percent, ((_min, black), (_mid, color), (_max, white)))
347def msrgb(value, grad=None):
348 """
349 Returns the Microsoft
350 `RGB color <http://msdn.microsoft.com/en-in/library/dd355244.aspx>`_
351 corresponding to a given a color. This is used for Microsoft office
352 output (e.g. Excel, PowerPoint, etc.)
354 :arg color value: color or int/float. If ``grad`` is not specified, this
355 should be a color that will get converted into a Microsoft RGB value. If
356 ``grad`` is specified, this should be a number. It will be converted into
357 a color using the gradient, and then into a Microsoft RGB value.
358 :arg tuple grad: tuple/list of ``(value, color)`` tuples For example, ``((0,
359 'red'), (.5, 'yellow'), (1, 'green'))``. This is passed as an input to
360 :func:`gradient`
362 Examples::
364 >>> msrgb('#fff')
365 16777215
366 >>> msrgb('red')
367 255
368 >>> msrgb(0.5, RYG) # == msrgb(gradient(0.5, RYG))
369 65535
370 """
371 idx = 3
372 red, green, blue = rgba(gradient(value, grad) if grad else value)[:idx]
373 return int((blue * BASE_255 * BASE_256 + green * BASE_255) * BASE_256 + red * BASE_255)
376def msrgbt(value, grad=None):
377 """
378 Returns the Microsoft RGB value (same as :func:`msrgbt`) and transparency
379 as a tuple.
381 For example::
383 color, transparency = msrgbt('rgba(255, 0, 0, .5)')
384 shp.Fill.ForeColor.RGB, shp.Fill.Transparency = color, transparency
386 See :func:`msrgbt`. The parameters are identical.
388 Examples::
390 >>> msrgbt('rgba(255,0,0,.5)')
391 (255, 0.5)
392 >>> msrgbt('red')
393 (255, 0.0)
394 """
395 _alpha = 1
396 red, green, blue, alpha = rgba(gradient(value, grad) if grad else value)
397 alpha = _alpha - alpha
398 col = (blue * BASE_255 * BASE_256 + green * BASE_255) * BASE_256 + red * BASE_255
399 return int(col), alpha
402def rgba(color):
403 """
404 Returns red, green, blue and alpha values (as a 0-1 float) of a color.
406 :arg color color: a string representing the color.
407 Most color formats defined in
408 `CSS3 <http://dev.w3.org/csswg/css3-color/>`_ are allowed.
410 Examples::
412 >>> rgba('#f00')
413 (1.0, 0.0, 0.0, 1.0)
414 >>> rgba('#f003')
415 (1.0, 0.0, 0.0, 0.2)
416 >>> rgba('#ff0000')
417 (1.0, 0.0, 0.0, 1.0)
418 >>> rgba('#ff000033')
419 (1.0, 0.0, 0.0, 0.2)
420 >>> rgba('rgb(255,0,0)')
421 (1.0, 0.0, 0.0, 1.0)
422 >>> rgba('rgba(255,0,0,.2)')
423 (1.0, 0.0, 0.0, 0.2)
424 >>> rgba('red')
425 (1.0, 0.0, 0.0, 1.0)
426 >>> rgba('white')
427 (1.0, 1.0, 1.0, 1.0)
428 >>> rgba('hsl(0,1,1)')
429 (1.0, 0.0, 0.0, 1.0)
430 >>> rgba('hsla(0,1,1,.2)')
431 (1.0, 0.0, 0.0, 0.2)
432 >>> rgba('hsl(0, 100%, 50%)')
433 (0.5, 0.0, 0.0, 1.0)
434 >>> rgba('hsla(0, 100%, 100%, .9)')
435 (1.0, 0.0, 0.0, 0.9)
436 >>> rgba('hsla(360, 100%, 100%, 1.9)')
437 (1.0, 0.0, 0.0, 1.0)
438 >>> rgba('hsla(360, 0%, 50%, .5)')
439 (0.5, 0.5, 0.5, 0.5)
440 >>> rgba('hsla(0, 0%, 50%, .5)')
441 (0.5, 0.5, 0.5, 0.5)
442 """
443 result = []
444 if color.startswith('#'): 444 ↛ 466line 444 didn't jump to line 466, because the condition on line 444 was never false
445 if len(color) == BASE_9: 445 ↛ 446line 445 didn't jump to line 446, because the condition on line 445 was never true
446 result = [int(color[BASE_1:BASE_3], BASE_16) / float(BASE_255),
447 int(color[BASE_3:BASE_5], BASE_16) / float(BASE_255),
448 int(color[BASE_5:BASE_7], BASE_16) / float(BASE_255),
449 int(color[BASE_7:BASE_9], BASE_16) / float(BASE_255)]
450 elif len(color) == BASE_7: 450 ↛ 454line 450 didn't jump to line 454, because the condition on line 450 was never false
451 result = [int(color[BASE_1:BASE_3], BASE_16) / float(BASE_255),
452 int(color[BASE_3:BASE_5], BASE_16) / float(BASE_255),
453 int(color[BASE_5:BASE_7], BASE_16) / float(BASE_255)]
454 elif len(color) == BASE_5:
455 result = [int(color[BASE_1:BASE_2], BASE_16) / float(BASE_15),
456 int(color[BASE_2:BASE_3], BASE_16) / float(BASE_15),
457 int(color[BASE_3:BASE_4], BASE_16) / float(BASE_15),
458 int(color[BASE_4:BASE_5], BASE_16) / float(BASE_15)]
459 elif len(color) == BASE_4:
460 result = [int(color[BASE_1:BASE_2], BASE_16) / float(BASE_15),
461 int(color[BASE_2:BASE_3], BASE_16) / float(BASE_15),
462 int(color[BASE_3:BASE_4], BASE_16) / float(BASE_15)]
463 else:
464 result = []
466 elif color.startswith('rgb(') or color.startswith('rgba('):
467 for i, val in enumerate(re.findall(r'[0-9\.%]+', color.split('(')[1])):
468 if val.endswith('%'):
469 result.append(float(val[:-1]) / 100)
470 elif i < 3:
471 result.append(float(val) / float(BASE_255))
472 else:
473 result.append(float(val))
475 elif color.startswith('hsl(') or color.startswith('hsla('):
476 for i, val in enumerate(re.findall(r'[0-9\.%]+', color.split('(')[BASE_1])):
477 if val.endswith('%'):
478 val_idx = -1
479 div = 100
480 result.append(float(val[:val_idx]) / div)
481 elif i == BASE_0:
482 div_base = 360
483 result.append(float(val) / div_base % BASE_1)
484 else:
485 result.append(float(val))
486 result[BASE_0], result[BASE_1], result[BASE_2] = colorsys.hsv_to_rgb(
487 result[BASE_0], result[BASE_1], result[BASE_2])
489 elif color in _COLORNAMES:
490 result = [val / float(BASE_255) for val in _COLORNAMES[color]]
492 if len(result) == BASE_3: 492 ↛ 495line 492 didn't jump to line 495, because the condition on line 492 was never false
493 result.append(float(BASE_1))
495 if len(result) != BASE_4: 495 ↛ 496line 495 didn't jump to line 496, because the condition on line 495 was never true
496 raise ValueError('%s: invalid color' % color)
498 return tuple(
499 float(BASE_0) if val < BASE_0 else float(
500 BASE_1) if val > BASE_1 else val for val in result)
503def hsla(color):
504 """
505 Returns hue, saturation, luminosity and alpha values (as a 0-1 float) for
506 a color.
508 :arg color color: a string representing the color.
509 Most color formats defined in
510 `CSS3 <http://dev.w3.org/csswg/css3-color/>`_ are allowed.
512 Examples::
514 >>> hsla('#f00')
515 (0.0, 1.0, 1.0, 1.0)
516 >>> hsla('#f003')
517 (0.0, 1.0, 1.0, 0.2)
518 >>> hsla('#ff0000')
519 (0.0, 1.0, 1.0, 1.0)
520 >>> hsla('#ff000033')
521 (0.0, 1.0, 1.0, 0.2)
522 >>> hsla('rgb(255,0,0)')
523 (0.0, 1.0, 1.0, 1.0)
524 >>> hsla('rgba(255,0,0,.2)')
525 (0.0, 1.0, 1.0, 0.2)
526 >>> hsla('red')
527 (0.0, 1.0, 1.0, 1.0)
528 >>> hsla('hsl(0,1,1)')
529 (0.0, 1.0, 1.0, 1.0)
530 >>> hsla('hsla(0,1,1,.2)')
531 (0.0, 1.0, 1.0, 0.2)
532 """
533 result = rgba(color)
534 return colorsys.rgb_to_hsv(
535 result[BASE_0], result[BASE_1], result[BASE_2]) + (result[BASE_3], )
538def name(red, green, blue, alpha=1):
539 """
540 Returns a short color string
542 :arg float red: red color value (0-1)
543 :arg float green: green color value (0-1)
544 :arg float blue: blue color value (0-1)
545 :arg float alpha: float, optional transparency value between 0-1.
546 ``alpha=1`` produces color strings like ``#abc``
547 Lower values produce ``rgba(...)```.
549 Examples::
551 >>> name(1, 0, 0) # Short color versions preferred
552 '#f00'
553 >>> name(1, 0, 0, .2) # Alpha creates rgba() with 2 decimals
554 'rgba(255,0,0,0.20)'
555 >>> name(.5, .25, .75) # Multiply by 255 and round to nearest
556 '#8040bf'
557 >>> name(-1, 2, 0) # Values are truncated to 0-1
558 '#0f0'
559 """
560 red = int(round(
561 BASE_255 * (BASE_0 if red < BASE_0 else BASE_1 if red > BASE_1 else red),
562 BASE_0))
564 green = int(round(
565 BASE_255 * (BASE_0 if green < BASE_0 else BASE_1 if green > BASE_1 else green),
566 BASE_0))
568 blue = int(round(
569 BASE_255 * (BASE_0 if blue < BASE_0 else BASE_1 if blue > BASE_1 else blue),
570 BASE_0))
572 alpha = BASE_0 if alpha < BASE_0 else BASE_1 if alpha > BASE_1 else alpha
573 if alpha == BASE_1: 573 ↛ 576line 573 didn't jump to line 576, because the condition on line 573 was never false
574 return '#%02x%02x%02x' % (red, green, blue)
575 else:
576 return 'rgba(%d,%d,%d,%0.2f)' % (red, green, blue, alpha)
579def _add_gradients(target, data):
580 """Add gradients to the module"""
581 mid_val = 0.5
582 min_val = -1.0
583 for grad, colors in six.iteritems(data['sequential']):
584 target[grad] = [[float(BASE_0), colors[BASE_0]],
585 [mid_val, colors[BASE_1]],
586 [float(BASE_1), colors[BASE_2]]]
587 for grad, colors in six.iteritems(data['divergent']):
588 target[grad] = [[min_val, colors[BASE_0]],
589 [float(BASE_0), colors[BASE_1]],
590 [float(BASE_1), colors[BASE_2]]]
591 for grad, colors in six.iteritems(data['office']):
592 target[grad] = _MSO(*colors)
595_DATA = json.load(open(os.path.join(os.path.dirname(
596 os.path.realpath(__file__)), 'colors.json'), encoding='utf-8'))
597_COLORNAMES = _DATA['names']
598_add_gradients(globals(), _DATA)