Skip to content

Commit

Permalink
Merge pull request #345 from CartoDB/allow-bins-to-be-defined
Browse files Browse the repository at this point in the history
adds support for pre-defined bins
  • Loading branch information
andy-esch authored Dec 15, 2017
2 parents 140393e + 4c1e3e9 commit 088d019
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 33 deletions.
38 changes: 19 additions & 19 deletions cartoframes/layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ def __init__(self, query, time=None, color=None, size=None,
# style columns as keys, data types as values
self.style_cols = dict()
self.geom_type = None
self.cartocss = None
self.torque_cartocss = None

# TODO: move these if/else branches to individual methods
# color, scheme = self._get_colorscheme()
Expand Down Expand Up @@ -360,11 +362,10 @@ def _setup(self, layers, layer_idx):
elif self.style_cols[self.color] in ('number', ):
self.scheme = mint(5)
elif self.style_cols[self.color] in ('date', 'geometry', ):
raise ValueError('Cannot style column `{col}` of type '
'`{type}`. It must be numeric, text, or '
'boolean.'.format(
col=self.color,
type=self.style_cols[self.color]))
raise ValueError(
'Cannot style column `{col}` of type `{type}`. It must be '
'numeric, text, or boolean.'.format(
col=self.color, type=self.style_cols[self.color]))

if self.time:
# validate time column information
Expand Down Expand Up @@ -407,11 +408,10 @@ def _setup(self, layers, layer_idx):
]).format(col=self.color, query=self.orig_query)
agg_func = '\'CDB_Math_Mode(cf_value_{})\''.format(self.color)
self.scheme = {
'bins': ','.join(str(i) for i in range(1, 11)),
'name': (self.scheme.get('name') if self.scheme
else 'Bold'),
'bin_method': '',
}
'bins': ','.join(str(i) for i in range(1, 11)),
'name': (self.scheme.get('name') if self.scheme
else 'Bold'),
'bin_method': '', }
elif (self.color in self.style_cols and
self.style_cols[self.color] in ('number', )):
self.query = ' '.join([
Expand All @@ -421,7 +421,7 @@ def _setup(self, layers, layer_idx):
agg_func = '\'avg({})\''.format(self.color)
else:
agg_func = "'{method}(cartodb_id)'".format(
method=method)
method=method)
self.torque_cartocss = cssify({
'Map': {
'-torque-frame-count': frames,
Expand Down Expand Up @@ -456,8 +456,8 @@ def _get_cartocss(self, basemap, has_time=False):

if self.scheme:
color_style = get_scheme_cartocss(
'value' if has_time else self.color,
self.scheme)
'value' if has_time else self.color,
self.scheme)
else:
color_style = self.color

Expand All @@ -481,14 +481,14 @@ def _get_cartocss(self, basemap, has_time=False):
'#layer[{} = null]'.format(self.color): {
'marker-fill': '#666'}
})
for t in range(1, self.time['trails'] + 1):
for trail_num in range(1, self.time['trails'] + 1):
# Trails decay as 1/2^n, and grow 30% at each step
trail_temp = cssify({
'#layer[frame-offset={}]'.format(t): {
'marker-width': size_style * (1.0 + t * 0.3),
'marker-opacity': 0.9 / 2.0**t,
}
})
'#layer[frame-offset={}]'.format(trail_num): {
'marker-width': size_style * (1.0 + trail_num * 0.3),
'marker-opacity': 0.9 / 2.0**trail_num,
}
})
css += trail_temp
return css
else:
Expand Down
30 changes: 23 additions & 7 deletions cartoframes/styling.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
:alt: CARTOColors
""" # noqa


class BinMethod:
"""Data classification methods used for the styling of data on maps.
Expand All @@ -16,29 +15,43 @@ class BinMethod:
headtails (str): Head/Tails classification for quantitative data
equal (str): Equal Interval classification for quantitative data
category (str): Category classification for qualitative data
mapping (dict): The TurboCarto mappings
"""
quantiles = 'quantiles'
jenks = 'jenks'
headtails = 'headtails'
equal = 'equal'
category = 'category'

# Mappings: https://github.com/CartoDB/turbo-carto/#mappings-default-values
mapping = {
quantiles: '>',
jenks: '>',
headtails: '<',
equal: '>',
category: '=',
}


def get_scheme_cartocss(column, scheme_info):
"""Get TurboCARTO CartoCSS based on input parameters"""
if 'colors' in scheme_info:
color_scheme = '({})'.format(','.join(scheme_info['colors']))
else:
color_scheme = 'cartocolor({})'.format(scheme_info['name'])
if not isinstance(scheme_info['bins'], int):
bins = ','.join(str(i) for i in scheme_info['bins'])
else:
bins = scheme_info['bins']
bin_method = scheme_info['bin_method']
comparison = ', {}'.format(BinMethod.mapping.get(bin_method, '>='))
return ('ramp([{column}], {color_scheme}, '
'{bin_method}({bins}){comparison})').format(
column=column,
color_scheme=color_scheme,
bin_method=bin_method,
bins=scheme_info['bins'],
comparison=('' if bin_method == 'category' else ', <=')
)
bins=bins,
comparison=comparison)


def custom(colors, bins=None, bin_method=BinMethod.quantiles):
Expand All @@ -64,8 +77,11 @@ def scheme(name, bins, bin_method):
Args:
name (str): Name of a CARTOColor.
bins (int): Number of bins for classifying data. CARTOColors have 7
bins max for quantitative data, and 11 max for qualitative data.
bins (int or iterable): If an `int`, the number of bins for classifying
data. CARTOColors have 7 bins max for quantitative data, and 11 max
for qualitative data. If `bins` is a `list`, it is the upper range
for classifying data. E.g., `bins` can be of the form ``(10, 20, 30,
40, 50)``.
bin_method (str): One of methods in :obj:`BinMethod`.
.. Warning::
Expand All @@ -78,7 +94,7 @@ def scheme(name, bins, bin_method):
return {
'name': name,
'bins': bins,
'bin_method': bin_method,
'bin_method': (bin_method if isinstance(bins, int) else ''),
}


Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Map Styling Functions
---------------------
.. automodule:: styling
:members:
:member-order: bysource

BatchJobStatus
--------------
Expand Down
21 changes: 14 additions & 7 deletions test/test_styling.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ def test_styling_name(self):
self.assertEqual(self.temps['bin_method'], 'quantiles')

def test_styling_values(self):
"""styling.BinMethod, etc."""
# Raise AttributeError if invalid name is entered
with self.assertRaises(AttributeError):
styling.apple(bins=4, BinMethod=BinMethod.quantiles)
Expand All @@ -100,17 +101,23 @@ def test_get_scheme_cartocss(self):
"""styling.get_scheme_cartocss"""
# test on category
self.assertEqual(get_scheme_cartocss('acadia', self.vivid),
'ramp([acadia], cartocolor(Vivid), category(4))')
'ramp([acadia], cartocolor(Vivid), category(4), =)')
# test on quantative
self.assertEqual(get_scheme_cartocss('acadia', self.purp),
'ramp([acadia], cartocolor(Purp), quantiles(4), <=)')
'ramp([acadia], cartocolor(Purp), quantiles(4), >)')
# test on custom
self.assertEqual(
get_scheme_cartocss('acadia',
styling.custom(('#FFF', '#888', '#000'),
bins=3,
bin_method='equal')),
'ramp([acadia], (#FFF,#888,#000), equal(3), <=)')
get_scheme_cartocss('acadia',
styling.custom(('#FFF', '#888', '#000'),
bins=3,
bin_method='equal')),
'ramp([acadia], (#FFF,#888,#000), equal(3), >)')

# test with non-int quantification
self.assertEqual(
get_scheme_cartocss('acadia',
styling.sunset([1, 2, 3])),
'ramp([acadia], cartocolor(Sunset), (1,2,3), >=)')

def test_scheme(self):
"""styling.scheme"""
Expand Down

0 comments on commit 088d019

Please sign in to comment.