Browse Source

Support colors in rgb / rgba for parametric styles

pull/146/head
Florian Mounier 11 years ago
parent
commit
60daf6442c
  1. 1
      CHANGELOG
  2. 91
      pygal/colors.py
  3. 20
      pygal/ghost.py
  4. 64
      pygal/test/test_colors.py

1
CHANGELOG

@ -3,6 +3,7 @@ V 1.5.0 UNRELEASED
Add half pie (thanks philt2001) Add half pie (thanks philt2001)
Make lxml an optionnal dependency (huge speed boost in pypy) Make lxml an optionnal dependency (huge speed boost in pypy)
Add render_table (WIP) Add render_table (WIP)
Support colors in rgb / rgba for parametric styles
V 1.4.6 V 1.4.6
Add support for \n separated multiline titles (thanks sirlark) Add support for \n separated multiline titles (thanks sirlark)

91
pygal/colors.py

@ -79,27 +79,78 @@ def hsl_to_rgb(h, s, l):
return r, g, b return r, g, b
def parse_color(color):
r = g = b = a = type = None
if color.startswith('#'):
color = color[1:]
if len(color) == 3:
type = '#rgb'
color = color + 'f'
if len(color) == 4:
type = type or '#rgba'
color = ''.join([c * 2 for c in color])
if len(color) == 6:
type = type or '#rrggbb'
color = color + 'ff'
assert len(color) == 8
type = type or '#rrggbbaa'
r, g, b, a = [
int(''.join(c), 16) for c in zip(color[::2], color[1::2])]
a /= 255
elif color.startswith('rgb('):
type = 'rgb'
color = color[4:-1]
r, g, b, a = [int(c) for c in color.split(',')] + [1]
elif color.startswith('rgba('):
type = 'rgba'
color = color[5:-1]
r, g, b, a = [int(c) for c in color.split(',')[:-1]] + [
float(color.split(',')[-1])]
return r, g, b, a, type
def unparse_color(r, g, b, a, type):
if type == '#rgb':
# Don't lose precision on rgb shortcut
if r % 17 == 0 and g % 17 == 0 and b % 17 == 0:
return '#%x%x%x' % (r / 17, g / 17, b / 17)
type = '#rrggbb'
if type == '#rgba':
if r % 17 == 0 and g % 17 == 0 and b % 17 == 0:
return '#%x%x%x%x' % (r / 17, g / 17, b / 17, a * 15)
type = '#rrggbbaa'
if type == '#rrggbb':
return '#%02x%02x%02x' % (r, g, b)
if type == '#rrggbbaa':
return '#%02x%02x%02x%02x' % (r, g, b, a * 255)
if type == 'rgb':
return 'rgb(%d, %d, %d)' % (r, g, b)
if type == 'rgba':
return 'rgba(%d, %d, %d, %g)' % (r, g, b, a)
_clamp = lambda x: max(0, min(100, x))
def _adjust(hsl, attribute, percent):
hsl = list(hsl)
if attribute > 0:
hsl[attribute] = _clamp(hsl[attribute] + percent)
else:
hsl[attribute] += percent
return hsl
def adjust(color, attribute, percent): def adjust(color, attribute, percent):
assert color[0] == '#', '#rrggbb and #rgb format are supported' r, g, b, a, type = parse_color(color)
color = color[1:] r, g, b = hsl_to_rgb(*_adjust(rgb_to_hsl(r, g, b), attribute, percent))
assert len(color) in (3, 6), '#rrggbb and #rgb format are supported' return unparse_color(r, g, b, a, type)
if len(color) == 3:
color = [a for b in zip(color, color) for a in b]
bound = lambda x: max(0, min(100, x))
def _adjust(hsl):
hsl = list(hsl)
if attribute > 0:
hsl[attribute] = bound(hsl[attribute] + percent)
else:
hsl[attribute] += percent
return hsl
return '#%02x%02x%02x' % hsl_to_rgb(
*_adjust(
rgb_to_hsl(*map(lambda x: int(''.join(x), 16),
zip(color[::2], color[1::2])))))
def rotate(color, percent): def rotate(color, percent):

20
pygal/ghost.py

@ -111,8 +111,8 @@ class Ghost(object):
.make_instance(overrides=kwargs) .make_instance(overrides=kwargs)
.render(is_unicode=is_unicode)) .render(is_unicode=is_unicode))
def render_tree(self): def render_tree(self, **kwargs):
return self.make_instance().render_tree() return self.make_instance(overrides=kwargs).render_tree()
def render_table(self, **kwargs): def render_table(self, **kwargs):
# Import here to avoid lxml import # Import here to avoid lxml import
@ -130,29 +130,29 @@ class Ghost(object):
from pyquery import PyQuery as pq from pyquery import PyQuery as pq
return pq(self.render(), parser='html') return pq(self.render(), parser='html')
def render_in_browser(self): def render_in_browser(self, **kwargs):
"""Render the graph, open it in your browser with black magic""" """Render the graph, open it in your browser with black magic"""
try: try:
from lxml.html import open_in_browser from lxml.html import open_in_browser
except ImportError: except ImportError:
raise ImportError('You must install lxml to use render in browser') raise ImportError('You must install lxml to use render in browser')
open_in_browser(self.render_tree(), encoding='utf-8') open_in_browser(self.render_tree(**kwargs), encoding='utf-8')
def render_response(self): def render_response(self, **kwargs):
"""Render the graph, and return a Flask response""" """Render the graph, and return a Flask response"""
from flask import Response from flask import Response
return Response(self.render(), mimetype='image/svg+xml') return Response(self.render(**kwargs), mimetype='image/svg+xml')
def render_to_file(self, filename): def render_to_file(self, filename, **kwargs):
"""Render the graph, and write it to filename""" """Render the graph, and write it to filename"""
with io.open(filename, 'w', encoding='utf-8') as f: with io.open(filename, 'w', encoding='utf-8') as f:
f.write(self.render(is_unicode=True)) f.write(self.render(is_unicode=True, **kwargs))
def render_to_png(self, filename=None, dpi=72): def render_to_png(self, filename=None, dpi=72, **kwargs):
"""Render the graph, convert it to png and write it to filename""" """Render the graph, convert it to png and write it to filename"""
import cairosvg import cairosvg
return cairosvg.svg2png( return cairosvg.svg2png(
bytestring=self.render(), write_to=filename, dpi=dpi) bytestring=self.render(**kwargs), write_to=filename, dpi=dpi)
def render_sparktext(self, relative_to=None): def render_sparktext(self, relative_to=None):
"""Make a mini text sparkline from chart""" """Make a mini text sparkline from chart"""

64
pygal/test/test_colors.py

@ -1,21 +1,55 @@
from __future__ import division
from pygal.colors import ( from pygal.colors import (
parse_color, unparse_color,
rgb_to_hsl, hsl_to_rgb, darken, lighten, saturate, desaturate, rotate) rgb_to_hsl, hsl_to_rgb, darken, lighten, saturate, desaturate, rotate)
def test_parse_color():
assert parse_color('#123') == (17, 34, 51, 1., '#rgb')
assert parse_color('#cdf') == (204, 221, 255, 1., '#rgb')
assert parse_color('#a3d7') == (170, 51, 221, 119 / 255, '#rgba')
assert parse_color('#584b4f') == (88, 75, 79, 1., '#rrggbb')
assert parse_color('#8cbe22') == (140, 190, 34, 1., '#rrggbb')
assert parse_color('#16cbf055') == (22, 203, 240, 1 / 3, '#rrggbbaa')
assert parse_color('rgb(134, 67, 216)') == (134, 67, 216, 1., 'rgb')
assert parse_color('rgb(0, 111, 222)') == (0, 111, 222, 1., 'rgb')
assert parse_color('rgba(237, 83, 48, .8)') == (237, 83, 48, .8, 'rgba')
assert parse_color('rgba(0, 1, 0, 0.1223)') == (0, 1, 0, .1223, 'rgba')
def test_unparse_color():
assert unparse_color(17, 34, 51, 1., '#rgb') == '#123'
assert unparse_color(204, 221, 255, 1., '#rgb') == '#cdf'
assert unparse_color(170, 51, 221, 119 / 255, '#rgba') == '#a3d7'
assert unparse_color(88, 75, 79, 1., '#rrggbb') == '#584b4f'
assert unparse_color(140, 190, 34, 1., '#rrggbb') == '#8cbe22'
assert unparse_color(22, 203, 240, 1 / 3, '#rrggbbaa') == '#16cbf055'
assert unparse_color(134, 67, 216, 1., 'rgb') == 'rgb(134, 67, 216)'
assert unparse_color(0, 111, 222, 1., 'rgb') == 'rgb(0, 111, 222)'
assert unparse_color(237, 83, 48, .8, 'rgba') == 'rgba(237, 83, 48, 0.8)'
assert unparse_color(0, 1, 0, .1223, 'rgba') == 'rgba(0, 1, 0, 0.1223)'
def test_darken(): def test_darken():
assert darken('#800', 20) == '#220000' assert darken('#800', 20) == '#200'
assert darken('#800', 0) == '#880000' assert darken('#800e', 20) == '#200e'
assert darken('#800', 0) == '#800'
assert darken('#ffffff', 10) == '#e6e6e6' assert darken('#ffffff', 10) == '#e6e6e6'
assert darken('#000000', 10) == '#000000' assert darken('#000000', 10) == '#000000'
assert darken('#f3148a', 25) == '#810747' assert darken('#f3148a', 25) == '#810747'
assert darken('#f3148aab', 25) == '#810747ab'
assert darken('#121212', 1) == '#0f0f0f' assert darken('#121212', 1) == '#0f0f0f'
assert darken('#999999', 100) == '#000000' assert darken('#999999', 100) == '#000000'
assert darken('#99999999', 100) == '#00000099'
assert darken('#1479ac', 8) == '#105f87' assert darken('#1479ac', 8) == '#105f87'
assert darken('rgb(136, 0, 0)', 20) == 'rgb(34, 0, 0)'
assert darken('rgba(20, 121, 172, .13)', 8) == 'rgba(16, 95, 135, 0.13)'
def test_lighten(): def test_lighten():
assert lighten('#800', 20) == '#ee0000' assert lighten('#800', 20) == '#e00'
assert lighten('#800', 0) == '#880000' assert lighten('#800', 0) == '#800'
assert lighten('#ffffff', 10) == '#ffffff' assert lighten('#ffffff', 10) == '#ffffff'
assert lighten('#000000', 10) == '#1a1a1a' assert lighten('#000000', 10) == '#1a1a1a'
assert lighten('#f3148a', 25) == '#f98dc6' assert lighten('#f3148a', 25) == '#f98dc6'
@ -25,26 +59,26 @@ def test_lighten():
def test_saturate(): def test_saturate():
assert saturate('#000', 20) == '#000000' assert saturate('#000', 20) == '#000'
assert saturate('#fff', 20) == '#ffffff' assert saturate('#fff', 20) == '#fff'
assert saturate('#8a8', 100) == '#33ff33' assert saturate('#8a8', 100) == '#3f3'
assert saturate('#855', 20) == '#9e3f3f' assert saturate('#855', 20) == '#9e3f3f'
def test_desaturate(): def test_desaturate():
assert desaturate('#000', 20) == '#000000' assert desaturate('#000', 20) == '#000'
assert desaturate('#fff', 20) == '#ffffff' assert desaturate('#fff', 20) == '#fff'
assert desaturate('#8a8', 100) == '#999999' assert desaturate('#8a8', 100) == '#999'
assert desaturate('#855', 20) == '#726b6b' assert desaturate('#855', 20) == '#726b6b'
def test_rotate(): def test_rotate():
assert rotate('#000', 45) == '#000000' assert rotate('#000', 45) == '#000'
assert rotate('#fff', 45) == '#ffffff' assert rotate('#fff', 45) == '#fff'
assert rotate('#811', 45) == '#886a11' assert rotate('#811', 45) == '#886a11'
assert rotate('#8a8', 360) == '#88aa88' assert rotate('#8a8', 360) == '#8a8'
assert rotate('#8a8', 0) == '#88aa88' assert rotate('#8a8', 0) == '#8a8'
assert rotate('#8a8', -360) == '#88aa88' assert rotate('#8a8', -360) == '#8a8'
def test_hsl_to_rgb_part_0(): def test_hsl_to_rgb_part_0():

Loading…
Cancel
Save