mirror of https://github.com/Kozea/pygal.git
Python to generate nice looking SVG graph
http://pygal.org/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
204 lines
5.6 KiB
204 lines
5.6 KiB
# -*- coding: utf-8 -*- |
|
# This file is part of pygal |
|
# |
|
# A python svg graph plotting library |
|
# Copyright © 2012-2015 Kozea |
|
# |
|
# This library is free software: you can redistribute it and/or modify it under |
|
# the terms of the GNU Lesser General Public License as published by the Free |
|
# Software Foundation, either version 3 of the License, or (at your option) any |
|
# later version. |
|
# |
|
# This library is distributed in the hope that it will be useful, but WITHOUT |
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
|
# details. |
|
# |
|
# You should have received a copy of the GNU Lesser General Public License |
|
# along with pygal. If not, see <http://www.gnu.org/licenses/>. |
|
""" |
|
This package is an utility package oriented on color alteration. |
|
This is used by the :py:mod:`pygal.style` package to generate |
|
parametric styles. |
|
|
|
""" |
|
from __future__ import division |
|
|
|
|
|
def normalize_float(f): |
|
"""Round float errors""" |
|
if abs(f - round(f)) < .0000000000001: |
|
return round(f) |
|
return f |
|
|
|
|
|
def rgb_to_hsl(r, g, b): |
|
"""Convert a color in r, g, b to a color in h, s, l""" |
|
r = r or 0 |
|
g = g or 0 |
|
b = b or 0 |
|
r /= 255 |
|
g /= 255 |
|
b /= 255 |
|
max_ = max((r, g, b)) |
|
min_ = min((r, g, b)) |
|
d = max_ - min_ |
|
|
|
if not d: |
|
h = 0 |
|
elif r is max_: |
|
h = 60 * (g - b) / d |
|
elif g is max_: |
|
h = 60 * (b - r) / d + 120 |
|
else: |
|
h = 60 * (r - g) / d + 240 |
|
|
|
l = .5 * (max_ + min_) |
|
if not d: |
|
s = 0 |
|
elif l < 0.5: |
|
s = .5 * d / l |
|
else: |
|
s = .5 * d / (1 - l) |
|
return tuple(map(normalize_float, (h % 360, s * 100, l * 100))) |
|
|
|
|
|
def hsl_to_rgb(h, s, l): |
|
"""Convert a color in h, s, l to a color in r, g, b""" |
|
h /= 360 |
|
s /= 100 |
|
l /= 100 |
|
|
|
m2 = l * (s + 1) if l <= .5 else l + s - l * s |
|
m1 = 2 * l - m2 |
|
|
|
def h_to_rgb(h): |
|
h = h % 1 |
|
if 6 * h < 1: |
|
return m1 + 6 * h * (m2 - m1) |
|
if 2 * h < 1: |
|
return m2 |
|
if 3 * h < 2: |
|
return m1 + 6 * (2 / 3 - h) * (m2 - m1) |
|
return m1 |
|
r, g, b = map(lambda x: round(x * 255), |
|
map(h_to_rgb, (h + 1 / 3, h, h - 1 / 3))) |
|
|
|
return r, g, b |
|
|
|
|
|
def parse_color(color): |
|
"""Take any css color definition and give back a tuple containing the |
|
r, g, b, a values along with a type which can be: #rgb, #rgba, #rrggbb, |
|
#rrggbbaa, rgb, rgba |
|
""" |
|
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): |
|
""" |
|
Take the r, g, b, a color values and give back |
|
a type css color string. This is the inverse function of parse_color |
|
""" |
|
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' % (int(r / 17), int(g / 17), int(b / 17)) |
|
type = '#rrggbb' |
|
|
|
if type == '#rgba': |
|
if r % 17 == 0 and g % 17 == 0 and b % 17 == 0: |
|
return '#%x%x%x%x' % (int(r / 17), int(g / 17), int(b / 17), |
|
int(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, int(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) |
|
|
|
|
|
def is_foreground_light(color): |
|
""" |
|
Determine if the background color need a light or dark foreground color |
|
""" |
|
return rgb_to_hsl(*parse_color(color)[:3])[2] < 17.9 |
|
|
|
|
|
_clamp = lambda x: max(0, min(100, x)) |
|
|
|
|
|
def _adjust(hsl, attribute, percent): |
|
"""Internal adjust function""" |
|
hsl = list(hsl) |
|
if attribute > 0: |
|
hsl[attribute] = _clamp(hsl[attribute] + percent) |
|
else: |
|
hsl[attribute] += percent |
|
|
|
return hsl |
|
|
|
|
|
def adjust(color, attribute, percent): |
|
"""Adjust an attribute of color by a percent""" |
|
r, g, b, a, type = parse_color(color) |
|
r, g, b = hsl_to_rgb(*_adjust(rgb_to_hsl(r, g, b), attribute, percent)) |
|
return unparse_color(r, g, b, a, type) |
|
|
|
|
|
def rotate(color, percent): |
|
"""Rotate a color by changing its hue value by percent""" |
|
return adjust(color, 0, percent) |
|
|
|
|
|
def saturate(color, percent): |
|
"""Saturate a color by increasing its saturation by percent""" |
|
return adjust(color, 1, percent) |
|
|
|
|
|
def desaturate(color, percent): |
|
"""Desaturate a color by decreasing its saturation by percent""" |
|
return adjust(color, 1, -percent) |
|
|
|
|
|
def lighten(color, percent): |
|
"""Lighten a color by increasing its lightness by percent""" |
|
return adjust(color, 2, percent) |
|
|
|
|
|
def darken(color, percent): |
|
"""Darken a color by decreasing its lightness by percent""" |
|
return adjust(color, 2, -percent)
|
|
|