|
10 | 10 | import os
|
11 | 11 | import time
|
12 | 12 |
|
| 13 | +import numpy as np |
| 14 | + |
13 | 15 | from branca.colormap import StepColormap
|
14 | 16 | from branca.element import CssLink, Element, Figure, JavascriptLink, MacroElement
|
15 | 17 | from branca.utilities import _parse_size, color_brewer
|
@@ -421,10 +423,11 @@ def fit_bounds(self, bounds, padding_top_left=None,
|
421 | 423 | )
|
422 | 424 |
|
423 | 425 | def choropleth(self, geo_data, data=None, columns=None, key_on=None,
|
424 |
| - threshold_scale=None, fill_color='blue', fill_opacity=0.6, |
425 |
| - line_color='black', line_weight=1, line_opacity=1, name=None, |
426 |
| - legend_name='', topojson=None, reset=False, smooth_factor=None, |
427 |
| - highlight=None): |
| 426 | + threshold_scale=None, fill_color='blue', |
| 427 | + nan_fill_color='black', fill_opacity=0.6, |
| 428 | + line_color='black', line_weight=1, line_opacity=1, |
| 429 | + name=None, legend_name='', topojson=None, |
| 430 | + reset=False, smooth_factor=None, highlight=None): |
428 | 431 | """
|
429 | 432 | Apply a GeoJSON overlay to the map.
|
430 | 433 |
|
@@ -466,14 +469,17 @@ def choropleth(self, geo_data, data=None, columns=None, key_on=None,
|
466 | 469 | start with 'feature' and be in JavaScript objection notation.
|
467 | 470 | Ex: 'feature.id' or 'feature.properties.statename'.
|
468 | 471 | threshold_scale: list, default None
|
469 |
| - Data range for D3 threshold scale. Defaults to the following range |
470 |
| - of quantiles: [0, 0.5, 0.75, 0.85, 0.9], rounded to the nearest |
471 |
| - order-of-magnitude integer. Ex: 270 rounds to 200, 5600 to 6000. |
| 472 | + Data range for D3 threshold scale. Defaults to a linear scale of |
| 473 | + 6 bins going from min(values) to max(values). |
| 474 | + Values are expected to be sorted. |
472 | 475 | fill_color: string, default 'blue'
|
473 | 476 | Area fill color. Can pass a hex code, color name, or if you are
|
474 | 477 | binding data, one of the following color brewer palettes:
|
475 | 478 | 'BuGn', 'BuPu', 'GnBu', 'OrRd', 'PuBu', 'PuBuGn', 'PuRd', 'RdPu',
|
476 | 479 | 'YlGn', 'YlGnBu', 'YlOrBr', and 'YlOrRd'.
|
| 480 | + nan_fill_color: string, default 'black' |
| 481 | + Area fill color for nan or missing values. |
| 482 | + Can pass a hex code, color name. |
477 | 483 | fill_opacity: float, default 0.6
|
478 | 484 | Area fill opacity, range 0-1.
|
479 | 485 | line_color: string, default 'black'
|
@@ -519,9 +525,6 @@ def choropleth(self, geo_data, data=None, columns=None, key_on=None,
|
519 | 525 | ... highlight=True)
|
520 | 526 |
|
521 | 527 | """
|
522 |
| - if threshold_scale is not None and len(threshold_scale) > 6: |
523 |
| - raise ValueError('The length of threshold_scale is {}, but it may ' |
524 |
| - 'not be longer than 6.'.format(len(threshold_scale))) # noqa |
525 | 528 | if data is not None and not color_brewer(fill_color):
|
526 | 529 | raise ValueError('Please pass a valid color brewer code to '
|
527 | 530 | 'fill_local. See docstring for valid codes.')
|
@@ -550,32 +553,39 @@ def choropleth(self, geo_data, data=None, columns=None, key_on=None,
|
550 | 553 | if data_min > 0 else -1)
|
551 | 554 | data_max = (data_max if data_max > 0 else 0
|
552 | 555 | if data_max < 0 else 1)
|
553 |
| - data_min, data_max = (1.01*data_min-0.01*data_max, |
554 |
| - 1.01*data_max-0.01*data_min) |
555 |
| - nb_class = 6 |
556 |
| - color_domain = [data_min+i*(data_max-data_min)*1./nb_class |
557 |
| - for i in range(1+nb_class)] |
| 556 | + nb_bins = 6 |
| 557 | + color_domain = list(np.linspace(data_min, data_max, nb_bins + 1)) |
558 | 558 | else:
|
559 | 559 | color_domain = None
|
560 | 560 |
|
561 | 561 | if color_domain and key_on is not None:
|
| 562 | + nb_bins = len(color_domain) - 1 |
562 | 563 | key_on = key_on[8:] if key_on.startswith('feature.') else key_on
|
563 |
| - color_range = color_brewer(fill_color, n=len(color_domain)) |
| 564 | + color_range = color_brewer(fill_color, n=nb_bins) |
564 | 565 |
|
565 | 566 | def get_by_key(obj, key):
|
566 | 567 | return (obj.get(key, None) if len(key.split('.')) <= 1 else
|
567 | 568 | get_by_key(obj.get(key.split('.')[0], None),
|
568 | 569 | '.'.join(key.split('.')[1:])))
|
569 | 570 |
|
570 | 571 | def color_scale_fun(x):
|
571 |
| - idx = len( |
572 |
| - [ |
573 |
| - u for u in color_domain if |
574 |
| - get_by_key(x, key_on) in color_data and |
575 |
| - u <= color_data[get_by_key(x, key_on)] |
576 |
| - ] |
577 |
| - ) |
578 |
| - return color_range[idx-1] |
| 572 | + key_of_x = get_by_key(x, key_on) |
| 573 | + if key_of_x in color_data.keys(): |
| 574 | + value_of_x = color_data[key_of_x] |
| 575 | + else: |
| 576 | + # The value is missing |
| 577 | + value_of_x = None |
| 578 | + |
| 579 | + if value_of_x is None or np.isnan(value_of_x): |
| 580 | + # The value is missing or is deliberately nan |
| 581 | + return nan_fill_color |
| 582 | + else: |
| 583 | + color_idx = int(np.digitize(value_of_x, color_domain)) - 1 |
| 584 | + # we consider that values outside the color domain |
| 585 | + # should be affected to the first (or last) color bin. |
| 586 | + color_idx = max(0, color_idx) |
| 587 | + color_idx = min(nb_bins-1, color_idx) |
| 588 | + return color_range[color_idx] |
579 | 589 | else:
|
580 | 590 | def color_scale_fun(x):
|
581 | 591 | return fill_color
|
@@ -614,9 +624,10 @@ def highlight_function(x):
|
614 | 624 |
|
615 | 625 | # Create ColorMap.
|
616 | 626 | if color_domain:
|
617 |
| - brewed = color_brewer(fill_color, n=len(color_domain)) |
| 627 | + nb_bins = len(color_domain) - 1 |
| 628 | + brewed = color_brewer(fill_color, n=nb_bins) |
618 | 629 | color_scale = StepColormap(
|
619 |
| - brewed[1:len(color_domain)], |
| 630 | + brewed, |
620 | 631 | index=color_domain,
|
621 | 632 | vmin=color_domain[0],
|
622 | 633 | vmax=color_domain[-1],
|
|
0 commit comments