# coding=utf-8
"""Ladybug color, colorsets and colorrange."""
[docs]class Color(object):
"""Ladybug RGB color.
Attributes:
r: red value 0-255, default: 0
g: green value 0-255, default: 0
b: blue red value 0-255, default: 0
"""
def __init__(self, r, g, b):
"""Generate RGB Color.
Args:
r: red value 0-255, default: 0
g: green value 0-255, default: 0
b: blue red value 0-255, default: 0
"""
self.r = r
self.g = g
self.b = b
@property
def r(self):
"""Return R value."""
return self._r
@r.setter
def r(self, value):
assert 0 <= int(value) <= 255, "%d is out of range. " % value + \
"R value should be between 0-255"
self._r = int(value)
@property
def g(self):
"""Return G value."""
return self._g
@g.setter
def g(self, value):
assert 0 <= int(value) <= 255, "%d is out of range. " % value + \
"G value should be between 0-255"
self._g = int(value)
@property
def b(self):
"""Return B value."""
return self._b
@b.setter
def b(self, value):
assert 0 <= int(value) <= 255, "%d is out of range. " % value + \
"B value should be between 0-255"
self._b = int(value)
[docs] def ToString(self):
"""Overwrite .NET ToString."""
return self.__repr__()
def __repr__(self):
"""Return RGB values."""
return "<R:%d, G:%d, B:%d>" % (self._r, self._g, self._b)
# TODO: Add support for CMYK
[docs]class Colorset(object):
"""Ladybug Color-range repository.
A list of default Ladybug colorsets for color range:
0 - original Ladybug
1 - nuanced Ladybug
2 - Multi-colored Ladybug
3 - View Analysis 1
4 - View Analysis 2 (Red,Green,Blue)
5 - Sunlight Hours
6 - ecotect
7 - thermal Comfort Percentage
8 - thermal Comfort Colors
9 - thermal Comfort Colors (UTCI)
10 - Hot Hours
11 - Cold Hours
12 - Shade Benefit/Harm
13 - thermal Comfort Colors v2 (UTCI)
14 - Shade Harm
15 - Shade Benefit
16 - Black to White
17 - CFD Colors 1
18 - CFD Colors 2
19 - Energy Balance
20 - THERM
21 - Cloud Cover
Usage:
# initiare colorsets
cs = Colorset()
print(cs[0])
>> [<R:75, G:107, B:169>, <R:115, G:147, B:202>, <R:170, G:200, B:247>,
<R:193, G:213, B:208>, <R:245, G:239, B:103>, <R:252, G:230, B:74>,
<R:239, G:156, B:21>, <R:234, G:123, B:0>, <R:234, G:74, B:0>,
<R:234, G:38, B:0>]
"""
_colors = {
0: [(75, 107, 169), (115, 147, 202), (170, 200, 247), (193, 213, 208),
(245, 239, 103), (252, 230, 74), (239, 156, 21), (234, 123, 0),
(234, 74, 0), (234, 38, 0)],
1: [(49, 54, 149), (69, 117, 180), (116, 173, 209), (171, 217, 233),
(224, 243, 248), (255, 255, 191), (254, 224, 144), (253, 174, 97),
(244, 109, 67), (215, 48, 39), (165, 0, 38)],
2: [(4, 25, 145), (7, 48, 224), (7, 88, 255), (1, 232, 255),
(97, 246, 156), (166, 249, 86), (254, 244, 1), (255, 121, 0),
(239, 39, 0), (138, 17, 0)],
3: [(255, 20, 147), (240, 47, 145), (203, 117, 139), (160, 196, 133),
(132, 248, 129), (124, 253, 132), (96, 239, 160), (53, 217, 203),
(15, 198, 240), (0, 191, 255)],
4: [(0, 13, 255), (0, 41, 234), (0, 113, 181), (0, 194, 122),
(0, 248, 82), (8, 247, 75), (64, 191, 58), (150, 105, 32),
(225, 30, 9), (255, 0, 0)],
5: [(55, 55, 55), (235, 235, 235)],
6: [(0, 0, 255), (53, 0, 202), (107, 0, 148), (160, 0, 95), (214, 0, 41),
(255, 12, 0), (255, 66, 0), (255, 119, 0), (255, 173, 0), (255, 226, 0),
(255, 255, 0)],
7: [(0, 0, 0), (110, 0, 153), (255, 0, 0), (255, 255, 102), (255, 255, 255)],
8: [(0, 136, 255), (200, 225, 255), (255, 255, 255), (255, 230, 230),
(255, 0, 0)],
9: [(0, 136, 255), (67, 176, 255), (134, 215, 255), (174, 228, 255),
(215, 242, 255), (255, 255, 255), (255, 243, 243), (255, 0, 0)],
10: [(255, 255, 255), (255, 0, 0)],
11: [(255, 255, 255), (0, 136, 255)],
12: [(5, 48, 97), (33, 102, 172), (67, 147, 195), (146, 197, 222),
(209, 229, 240), (255, 255, 255), (253, 219, 199), (244, 165, 130),
(214, 96, 77), (178, 24, 43), (103, 0, 31)],
13: [(5, 48, 97), (33, 102, 172), (67, 147, 195), (146, 197, 222),
(209, 229, 240), (255, 255, 255), (244, 165, 130), (178, 24, 43)],
14: [(255, 255, 255), (253, 219, 199), (244, 165, 130), (214, 96, 77),
(178, 24, 43), (103, 0, 31)],
15: [(255, 255, 255), (209, 229, 240), (146, 197, 222), (67, 147, 195),
(33, 102, 172), (5, 48, 97)],
16: [(0, 0, 0), (255, 255, 255)],
17: [(0, 16, 120), (38, 70, 160), (5, 180, 222), (16, 180, 109),
(59, 183, 35), (143, 209, 19), (228, 215, 29), (246, 147, 17),
(243, 74, 0), (255, 0, 0)],
18: [(69, 92, 166), (66, 128, 167), (62, 176, 168), (78, 181, 137),
(120, 188, 59), (139, 184, 46), (197, 157, 54), (220, 144, 57),
(228, 100, 59), (233, 68, 60)],
19: [(138, 17, 0), (239, 39, 0), (255, 121, 0), (254, 244, 1),
(166, 249, 86), (97, 246, 156), (1, 232, 255), (7, 88, 255),
(4, 25, 145), (128, 102, 64)],
20: [(0, 0, 0), (137, 0, 139), (218, 0, 218), (196, 0, 255), (0, 92, 255),
(0, 198, 252), (0, 244, 215), (0, 220, 101), (7, 193, 0), (115, 220, 0),
(249, 251, 0), (254, 178, 0), (253, 77, 0), (255, 15, 15),
(255, 135, 135), (255, 255, 255)],
21: [(0, 251, 255), (255, 255, 255), (217, 217, 217), (83, 114, 115)]
}
def __init__(self):
"""Initialize Color-sets."""
pass
[docs] @classmethod
def original(cls):
"""original Ladybug colors."""
return tuple(Color(*color) for color in cls._colors[0])
[docs] @classmethod
def nuanced(cls):
"""nuanced Ladybug colors."""
return tuple(Color(*color) for color in cls._colors[1])
[docs] @classmethod
def multi_colored(cls):
"""Multi-colored legend."""
return tuple(Color(*color) for color in cls._colors[2])
[docs] @classmethod
def view_analysis1(cls):
"""View analysis colors."""
return tuple(Color(*color) for color in cls._colors[3])
[docs] @classmethod
def view_analysis2(cls):
"""View Analysis 2 colors."""
return tuple(Color(*color) for color in cls._colors[4])
[docs] @classmethod
def sunlight_hours(cls):
"""sunlight_hours colors."""
return tuple(Color(*color) for color in cls._colors[5])
[docs] @classmethod
def ecotect(cls):
"""ecotect colors."""
return tuple(Color(*color) for color in cls._colors[6])
[docs] @classmethod
def thermal_comfort_percentage(cls):
"""thermal Comfort percentage."""
return tuple(Color(*color) for color in cls._colors[7])
[docs] @classmethod
def thermal_comfort(cls):
"""thermal Comfort colors."""
return tuple(Color(*color) for color in cls._colors[8])
[docs] @classmethod
def thermal_comfort_utci_1(cls):
"""thermal Comfort UTCI 1."""
return tuple(Color(*color) for color in cls._colors[9])
[docs] @classmethod
def hot_hours(cls):
"""Hot Hours."""
return tuple(Color(*color) for color in cls._colors[10])
[docs] @classmethod
def cold_hours(cls):
"""Cold Hours."""
return tuple(Color(*color) for color in cls._colors[11])
[docs] @classmethod
def shade_benefit_harm(cls):
"""Shade Benefit Harm colors."""
return tuple(Color(*color) for color in cls._colors[12])
[docs] @classmethod
def thermal_comfort_utci_2(cls):
"""thermal Comfort UTCI 2."""
return tuple(Color(*color) for color in cls._colors[13])
[docs] @classmethod
def shade_harm(cls):
"""Shade Harm colors."""
return tuple(Color(*color) for color in cls._colors[14])
[docs] @classmethod
def shade_benefit(cls):
"""Shade Benefit colors."""
return tuple(Color(*color) for color in cls._colors[15])
[docs] @classmethod
def black_to_white(cls):
"""Black to white colors."""
return tuple(Color(*color) for color in cls._colors[16])
[docs] @classmethod
def cfd_colors_1(cls):
"""CFD colors 1."""
return tuple(Color(*color) for color in cls._colors[17])
[docs] @classmethod
def cfd_colors_2(cls):
"""CFD colors 2."""
return tuple(Color(*color) for color in cls._colors[18])
[docs] @classmethod
def energy_balance(cls):
"""Energy Balance colors."""
return tuple(Color(*color) for color in cls._colors[19])
[docs] @classmethod
def therm(cls):
"""THERM colors."""
return tuple(Color(*color) for color in cls._colors[20])
[docs] @classmethod
def cloud_cover(cls):
"""Cloud Cover colors."""
return tuple(Color(*color) for color in cls._colors[21])
def __len__(self):
"""Return length of colors."""
return len(self._colors)
def __getitem__(self, key):
"""Return key item from the color list."""
return [Color(*color) for color in self._colors[key]]
def __setitem__(self, key, value):
"""Set a color to a new color in color list."""
self._colors[key] = value
def __delitem__(self, key):
"""Remove a color from the color list."""
del self._colors[key]
def __iter__(self):
"""Use colors to iterate."""
return iter(self._colors)
[docs]class ColorRange(object):
"""Ladybug Color-range repository.
A list of default Ladybug colorRanges
Args:
range:
colors: A list of colors. Colors should be input as R, G, B values.
Default: Colorset[1]
domain: A list of numbers or strings. For numerical values it should be
sorted from min to max. Default: ['min', 'max']
chart_type: 0: continuous, 1: segmented, 2: ordinal. Default: 0
In segmented and ordinal mode number of values should match number of colors
Ordinal values can be strings and well as numericals
Usage:
##
colorRange = ColorRange(chart_type = 1)
colorRange.domain = [100, 2000]
colorRange.colors = [Color(75, 107, 169), Color(245, 239, 103),
Color(234, 38, 0)]
print(colorRange.color(99))
print(colorRange.color(100))
print(colorRange.color(2000))
print(colorRange.color(2001))
>> <R:75, G:107, B:169>
>> <R:245, G:239, B:103>
>> <R:245, G:239, B:103>
>> <R:234, G:38, B:0>
##
colorRange = ColorRange(chart_type = 1)
colorRange.domain = [100, 2000]
colorRange.colors = [Color(75, 107, 169), Color(245, 239, 103),
Color(234, 38, 0)]
colorRange.color(300)
>> <R:245, G:239, B:103>
##
colorRange = ColorRange(chart_type = 2)
colorRange.domain = ["cold", "comfortable", "hot"]
colorRange.colors = [Color(75, 107, 169), Color(245, 239, 103),
Color(234, 38, 0)]
colorRange.color("comfortable")
>> <R:245, G:239, B:103>
"""
# TODO: write a Color object
def __init__(self, colors=None, domain=None, chart_type=0):
"""Initiate Ladybug color range."""
self.ctype = chart_type
self._is_domain_set = False
self.colors = colors
self.domain = domain
@property
def is_domain_set(self):
"""Return if Domain is set for this color-range."""
return self._is_domain_set
@property
def domain(self):
"""Return domain."""
return self._domain
@domain.setter
def domain(self, dom):
# check and prepare domain
if not dom:
dom = [0, 1]
if 'min' in dom or 'max' in dom:
self._domain = dom
self._is_domain_set = False
else:
assert hasattr(dom, "__iter__"), "Domain should be an iterable type."
# if domain is numerical it should be sorted
try:
dom = sorted(map(float, dom))
except ValueError:
if self._ctype != 2:
print("Text domains can only be used in ordinal mode.\n" +
"Type is changed to ordinal.")
self.ctype == 2
if self._ctype == 0:
# continuous
# if type is continuous domain can only be 2 values
# or at least 1 value less than number of colors
if len(dom) == 2:
# remap domain based on colors
_step = float(dom[1] - dom[0]) / (len(self._colors) - 1)
_n = dom[0]
dom = [_n + c * _step for c in range(len(self._colors))]
assert len(self._colors) >= len(dom), \
"For continuous colors length of domain should be 2 or equal" \
" to number of colors"
elif self._ctype == 1:
# segmented
# Number of colors should be at least one more than number
# of domain Values
assert len(self._colors) > len(dom), "Length of colors " + \
"should be more than domain values for segmented colors"
self._domain = dom
self._is_domain_set = True
@property
def colors(self):
"""Return list of colors."""
return self._colors
@colors.setter
def colors(self, cols):
if not cols:
self._colors = Colorset()[1]
else:
assert hasattr(cols, "__iter__"), "Colors should be an iterable type"
try:
cols = [col if isinstance(col, Color) else Color(
col.R, col.G, col.B) for col in cols]
except Exception:
try:
cols = [Color(col.Red, col.Green, col.Blue) for col in cols]
except Exception:
raise ValueError("%s is not a vlid color" % str(cols))
self._colors = cols
@property
def ctype(self):
"""Chart type."""
return self._ctype
@ctype.setter
def ctype(self, t):
assert 0 <= int(t) <= 2, "Chart Type should be between 0-2\n" + \
"0: continuous, 1: segmented, 2: ordinal"
self._ctype = int(t)
[docs] def color(self, value):
"""Return color for an input value."""
assert self._is_domain_set, \
"Domain is not set. Use self.domain to set the domain."
if self._ctype == 2:
# if ordinal map the value and color
try:
return self._colors[self._domain.index(value)]
except ValueError:
raise ValueError(
"%s is not a valid input for ordinal type.\n" % str(value) +
"List of valid values are %s" % ";".join(map(str, self._domain))
)
if value < self._domain[0]:
return self._colors[0]
if value > self._domain[-1]:
return self._colors[-1]
# find the index of the value in domain
for count, d in enumerate(self._domain):
if d <= value <= self._domain[count + 1]:
if self._ctype == 0:
return self._cal_color(value, count)
if self._ctype == 1:
return self._colors[count + 1]
def _cal_color(self, value, color_index):
"""Blend between two colors based on input value."""
range_min_p = self._domain[color_index]
range_p = self._domain[color_index + 1] - range_min_p
try:
factor = (value - range_min_p) / range_p
except ZeroDivisionError:
factor = 0
min_color = self.colors[color_index]
max_color = self.colors[color_index + 1]
red = round(factor * (max_color.r - min_color.r) + min_color.r)
green = round(factor * (max_color.g - min_color.g) + min_color.g)
blue = round(factor * (max_color.b - min_color.b) + min_color.b)
return Color(red, green, blue)
def __len__(self):
"""Return length of colors."""
return len(self._colors)
def __getitem__(self, key):
"""Return key item from the color list."""
return self._colors[key]
def __setitem__(self, key, value):
"""Set a color to a new color in color list."""
self._colors[key] = value
def __delitem__(self, key):
"""Remove a color from the color list."""
del self._colors[key]
def __iter__(self):
"""Use colors to iterate."""
return iter(self._colors)