Skip to content

Commit f7194ad

Browse files
committed
Merge pull request python-visualization#28 from adamrpah/expanded_color_support
Added expanded color support to 253 colors with a sequential colorbrewer...
2 parents 4bf398f + 54267a7 commit f7194ad

File tree

3 files changed

+136
-8
lines changed

3 files changed

+136
-8
lines changed

folium/folium.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -629,14 +629,15 @@ def json_style(style_cnt, line_color, line_weight, line_opacity,
629629
#D3 Color scale
630630
series = data[columns[1]]
631631
domain = threshold_scale or utilities.split_six(series=series)
632-
if len(domain) > 6:
633-
raise ValueError('The threshold scale must be of length <= 6')
632+
if len(domain) > 253:
633+
raise ValueError('The threshold scale must be of length <= 253')
634634
if not utilities.color_brewer(fill_color):
635635
raise ValueError('Please pass a valid color brewer code to '
636636
'fill_local. See docstring for valid codes.')
637637

638-
palette = utilities.color_brewer(fill_color)
638+
palette = utilities.color_brewer(fill_color, len(domain))
639639
d3range = palette[0: len(domain) + 1]
640+
tick_labels = utilities.legend_scaler(domain)
640641

641642
color_temp = self.env.get_template('d3_threshold.js')
642643
d3scale = color_temp.render({'domain': domain,
@@ -647,6 +648,7 @@ def json_style(style_cnt, line_color, line_weight, line_opacity,
647648
name = legend_name or columns[1]
648649
leg_templ = self.env.get_template('d3_map_legend.js')
649650
legend = leg_templ.render({'lin_max': int(domain[-1]*1.1),
651+
'tick_labels': tick_labels,
650652
'caption': name})
651653
self.template_vars.setdefault('map_legends', []).append(legend)
652654

folium/templates/d3_map_legend.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
.scale(x)
1313
.orient("top")
1414
.tickSize(1)
15-
.tickValues(color.domain())
15+
.tickValues({{ tick_labels }});
1616

1717
var svg = d3.select(".legend.leaflet-control").append("svg")
1818
.attr("id", 'legend')
@@ -40,4 +40,4 @@
4040
g.call(xAxis).append("text")
4141
.attr("class", "caption")
4242
.attr("y", 21)
43-
.text('{{ caption }}');
43+
.text('{{ caption }}');

folium/utilities.py

Lines changed: 129 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,101 @@ def get_templates():
2727
'''Get Jinja templates'''
2828
return Environment(loader=PackageLoader('folium', 'templates'))
2929

30+
def legend_scaler(legend_values, max_labels=10.0):
31+
'''
32+
Downsamples the number of legend values so that there isn't a collision
33+
of text on the legend colorbar (within reason). The colorbar seems to
34+
support ~10 entries as a maximum
35+
'''
36+
import math
37+
38+
if len(legend_values)<max_labels:
39+
legend_ticks = legend_values
40+
else:
41+
spacer = int(math.ceil(len(legend_values)/max_labels))
42+
legend_ticks = []
43+
for i in legend_values[::spacer]:
44+
legend_ticks += [i]
45+
legend_ticks += ['']*(spacer-1)
46+
return legend_ticks
47+
48+
49+
def linear_gradient(hexList, nColors):
50+
"""Given a list of hexcode values, will return a list of length
51+
nColors where the colors are linearly interpolated between the
52+
(r, g, b) tuples that are given.
53+
54+
Example:
55+
linear_gradient([(0, 0, 0), (255, 0, 0), (255, 255, 0)], 100)
56+
"""
57+
def _scale(start, finish, length, i):
58+
"""Return the value correct value of a number that is inbetween start
59+
and finish, for use in a loop of length *length*"""
60+
base=16
61+
62+
fraction = float(i) / (length - 1)
63+
raynge = int(finish, base) - int(start, base)
64+
thex = hex(int(int(start, base) + fraction * raynge)).split('x')[-1]
65+
if len(thex)!=2:
66+
thex ='0' + thex
67+
return thex
68+
3069

31-
def color_brewer(color_code):
70+
allColors = []
71+
# separate (r, g, b) pairs
72+
for start, end in zip(hexList[:-1], hexList[1:]):
73+
# linearly intepolate between pair of hex ###### values and add to list
74+
nInterpolate = 765
75+
for index in range(nInterpolate):
76+
r = _scale(start[1:3], end[1:3], nInterpolate, index)
77+
g = _scale(start[3:5], end[3:5], nInterpolate, index)
78+
b = _scale(start[5:7], end[5:7], nInterpolate, index)
79+
allColors.append(''.join(['#',r,g,b]))
80+
81+
# pick only nColors colors from the total list
82+
result = []
83+
for counter in range(nColors):
84+
fraction = float(counter) / (nColors - 1)
85+
index = int(fraction * (len(allColors) - 1))
86+
result.append(allColors[index])
87+
return result
88+
89+
90+
def color_brewer(color_code, n=6):
3291
'''Generate a colorbrewer color scheme of length 'len', type 'scheme.
3392
Live examples can be seen at http://colorbrewer2.org/'''
93+
maximum_n = 253
94+
95+
scheme_info = {'BuGn': 'Sequential',
96+
'BuPu': 'Sequential',
97+
'GnBu': 'Sequential',
98+
'OrRd': 'Sequential',
99+
'PuBu': 'Sequential',
100+
'PuBuGn': 'Sequential',
101+
'PuRd': 'Sequential',
102+
'RdPu': 'Sequential',
103+
'YlGn': 'Sequential',
104+
'YlGnBu': 'Sequential',
105+
'YlOrBr': 'Sequential',
106+
'YlOrRd': 'Sequential',
107+
'BrBg': 'Diverging',
108+
'PiYG': 'Diverging',
109+
'PRGn': 'Diverging',
110+
'PuOr': 'Diverging',
111+
'RdBu': 'Diverging',
112+
'RdGy': 'Diverging',
113+
'RdYlBu': 'Diverging',
114+
'RdYlGn': 'Diverging',
115+
'Spectral': 'Diverging',
116+
'Accent': 'Qualitative',
117+
'Dark2': 'Qualitative',
118+
'Paired': 'Qualitative',
119+
'Pastel1': 'Qualitative',
120+
'Pastel2': 'Qualitative',
121+
'Set1': 'Qualitative',
122+
'Set2': 'Qualitative',
123+
'Set3': 'Qualitative',
124+
}
34125

35126
schemes = {'BuGn': ['#EDF8FB', '#CCECE6', '#CCECE6', '#66C2A4', '#41AE76',
36127
'#238B45', '#005824'],
@@ -55,9 +146,44 @@ def color_brewer(color_code):
55146
'YlOrBr': ['#FFFFD4', '#FEE391', '#FEC44F', '#FE9929', '#EC7014',
56147
'#CC4C02', '#8C2D04'],
57148
'YlOrRd': ['#FFFFB2', '#FED976', '#FEB24C', '#FD8D3C', '#FC4E2A',
58-
'#E31A1C', '#B10026']}
149+
'#E31A1C', '#B10026'],
150+
'BrBg': ['#8c510a', '#d8b365', '#f6e8c3', '#c7eae5', '#5ab4ac', '#01665e'],
151+
'PiYG': ['#c51b7d', '#e9a3c9', '#fde0ef', '#e6f5d0', '#a1d76a', '#4d9221'],
152+
'PRGn': ['#762a83', '#af8dc3', '#e7d4e8', '#d9f0d3', '#7fbf7b', '#1b7837'],
153+
'PuOr': ['#b35806', '#f1a340', '#fee0b6', '#d8daeb', '#998ec3', '#542788'],
154+
'RdBu': ['#b2182b', '#ef8a62', '#fddbc7', '#d1e5f0', '#67a9cf', '#2166ac'],
155+
'RdGy': ['#b2182b', '#ef8a62', '#fddbc7', '#e0e0e0', '#999999', '#4d4d4d'],
156+
'RdYlBu': ['#d73027', '#fc8d59', '#fee090', '#e0f3f8', '#91bfdb', '#4575b4'],
157+
'RdYlGn': ['#d73027', '#fc8d59', '#fee08b', '#d9ef8b', '#91cf60', '#1a9850'],
158+
'Spectral': ['#d53e4f' '#fc8d59' '#fee08b' '#e6f598' '#99d594' '#3288bd'],
159+
'Accent': ['#7fc97f', '#beaed4', '#fdc086', '#ffff99', '#386cb0', '#f0027f'],
160+
'Dark2': ['#1b9e77', '#d95f02', '#7570b3', '#e7298a', '#66a61e', '#e6ab02'],
161+
'Paired': ['#a6cee3', '#1f78b4', '#b2df8a', '#33a02c', '#fb9a99', '#e31a1c'],
162+
'Pastel1': ['#fbb4ae', '#b3cde3', '#ccebc5', '#decbe4', '#fed9a6', '#ffffcc'],
163+
'Pastel2': ['#b3e2cd', '#fdcdac', '#cbd5e8', '#f4cae4', '#e6f5c9', '#fff2ae'],
164+
'Set1': ['#e41a1c', '#377eb8', '#4daf4a', '#984ea3', '#ff7f00', '#ffff33'],
165+
'Set2': ['#66c2a5', '#fc8d62', '#8da0cb', '#e78ac3', '#a6d854', '#ffd92f'],
166+
'Set3': ['#8dd3c7', '#ffffb3', '#bebada', '#fb8072', '#80b1d3', '#fdb462'],
167+
}
168+
169+
#Raise an error if the n requested is greater than the maximum
170+
if n > maximum_n:
171+
raise ValueError("The maximum number of colors in a ColorBrewer sequential color series is 253")
172+
173+
#Only if n is greater than six do we interpolate values
174+
if n > 6:
175+
if color_code not in schemes:
176+
color_scheme= None
177+
else:
178+
#Check to make sure that it is not a qualitative scheme
179+
if scheme_info[color_code]=='Qualitative':
180+
raise ValueError("Expanded color support is not available for Qualitative schemes, restrict number of colors to 6")
181+
else:
182+
color_scheme = linear_gradient(schemes.get(color_code), n)
183+
else:
184+
color_scheme = schemes.get(color_code, None)
185+
return color_scheme
59186

60-
return schemes.get(color_code, None)
61187

62188

63189
def transform_data(data):

0 commit comments

Comments
 (0)