Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add color_category and color_bins helpers #692

Merged
merged 22 commits into from
May 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions NEWS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,30 @@ Updates
- Add new visualization API (#662)
- Add Source class (with param detection)
- Add Dataset methods
.from_table(name, context)
.from_query(query, context)
.from_geojson(data)
.from_dataframe(data)
- Add set_default_context methods
- Add Context in cartoframes namespace
- Add Map, Layer, Source, Style in cartoframes.viz namespace
.from_table(...)
.from_query(...)
.from_geojson(...)
.from_dataframe(...)
- Add set_default_context method
- Use sources' context (credentials, bounds)
- Fix Style class API for variables
- Remove Dataset, SQL, GeoJSON sources
- Remove sources namespace
- Remove context from Map
- Remove contrib namespace
- Update docs in vis classes
- Add/Update vis tests
- Update docs in viz classes
- Add/Update viz tests
- Pass PEP 8
- Add default style, based on the geom type (#648)
- Add basemap None, bool and color interface (#635)
- Add basemap None and color interface (#635)
- Add Popup API (click and hover) (#677)
- Apply default style for not overwritten properties (#684)
- Add namespaces (#683)
- cartoframes.viz: Map, Layer, Source, Style, Popup, basemaps, helpers
- cartoframes.auth: Context, set_default_context
- Add color helpers (#692)
- color_category
- color_bins

0.9.2
-----
Expand Down
2 changes: 1 addition & 1 deletion cartoframes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
'BinMethod',

# New API
'Dataset'
'Dataset',

'__version__'
]
7 changes: 4 additions & 3 deletions cartoframes/assets/templates/viz/basic.html.j2
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,10 @@
<div class="as-map-area">
<div id="map"></div>
<div class="as-map-panels">
<div class="as-panel as-panel--right as-panel--top">
<div class="as-panel__element" id="legends" style="display: none;">
</div>
<div class="as-panel as-panel--left as-panel--top">
{% if has_legends %}
{% include 'viz/legends.html.j2' %}
{% endif %}
</div> <!-- as-panel -->
</div> <!-- as-map-panels -->
</div> <!-- as-map-area -->
Expand Down
35 changes: 35 additions & 0 deletions cartoframes/assets/templates/viz/legends.html.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{% macro legend(legend_data, id) -%}
{% set type = legend_data.type %}
{% if type == 'basic' %}
<as-basic-legend id="{{id}}" slot="legends"></as-basic-legend>
{% elif type == 'gradient' %}
<as-color-gradient-legend id="{{id}}" slot="legends"></as-color-gradient-legend>
{% elif type == 'color_steps' %}
<as-color-steps-legend id="{{id}}" slot="legends"></as-color-steps-legend>
{% else %}
<div>
<p class="as-body as-color--error">Unknown legend type {{ type }}</p>
</div>
{% endif %}
{%- endmacro %}

<div class="as-panel__element" id="legends">
{% if default_legend != none %}
<as-legends id="basicLegendContainer" heading="Map Legend">
<as-basic-legend id="basicLegend" slot="legends">
</as-basic-legend>
</as-legends>
{% endif %}

{% for source in sources %}
{% if source.legend %}
<as-legends
heading="{{source.legend.heading}}"
description="{{source.legend.description}}"
source="{{source.legend.source}}"
>
{{ legend(source.legend, 'source%d_legend' % loop.index0) }}
</as-legends>
{% endif %}
{% endfor %}
</div>
26 changes: 26 additions & 0 deletions cartoframes/assets/templates/viz/legends.js.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
function createDefaultLegend(layers) {
const defaultLegendContainer = document.querySelector('#basicLegendContainer');
defaultLegendContainer.style.display = 'none';

AsBridge.VL.Legends.layersLegend(
'#basicLegend',
layers,
{
onLoad: () => defaultLegendContainer.style.display = 'unset'
}
)
}

function createLegend(layer, legendData, layerIndex) {
const element = document.querySelector(`#source${layerIndex}_legend`);

if (legendData.ramp) {
AsBridge.VL.Legends.rampLegend(
element,
layer,
legendData.ramp
);
} else {
// TODO: we don't have a bridge for this case, should this even be a case?
}
}
15 changes: 15 additions & 0 deletions cartoframes/assets/templates/viz/map.js.j2
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
{% if has_legends %}
{% include 'viz/legends.js.j2' %}
{% endif %}
{% include 'error/parser.js.j2' %}
{% include 'utils/base64.js.j2' %}

Expand Down Expand Up @@ -39,12 +42,16 @@ function onReady() {
map.flyTo({{ camera|clear_none|tojson }});
{% endif %}

const layers = [];

sources.forEach((elem, idx) => {
const factory = new SourceFactory();
const source = factory.createSource(elem);
const viz = new carto.Viz(elem['viz']);
const layer = new carto.Layer(`layer${idx}`, source, viz);

layers.push(layer);

try {
layer._updateLayer.catch(displayError);
} catch (err) {
Expand Down Expand Up @@ -73,6 +80,10 @@ function onReady() {
}
}

if (elem.legend) {
createLegend(layer, elem.legend, idx);
}

function setPopupsClick(tempPopup, interactivity, attrs) {
interactivity.on('featureClick', (event) => {
updatePopup(tempPopup, event, attrs)
Expand Down Expand Up @@ -139,6 +150,10 @@ function onReady() {
}
}
});

{% if default_legend %}
createDefaultLegend(layers);
{% endif %}
}

function setReady () {
Expand Down
6 changes: 6 additions & 0 deletions cartoframes/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""general utility functions"""

import re
import sys
import hashlib

Expand Down Expand Up @@ -111,3 +113,7 @@ def merge_dicts(dict1, dict2):
d = dict1.copy()
d.update(dict2)
return d


def text_match(regex, text):
return len(re.findall(regex, text, re.MULTILINE)) > 0
8 changes: 4 additions & 4 deletions cartoframes/viz/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
AIRSHIP_BRIDGE_SCRIPT = '/packages/bridge/dist/asbridge.js'
AIRSHIP_STYLE = '/packages/styles/dist/airship.css'
AIRSHIP_ICONS_STYLE = '/packages/icons/dist/icons.css'
AIRSHIP_COMPONENTS_PATH = 'https://libs.cartocdn.com/airship-components/v2.0/airship.js'
AIRSHIP_BRIDGE_PATH = 'https://libs.cartocdn.com/airship-bridge/v2.0/asbridge.js'
AIRSHIP_STYLES_PATH = 'https://libs.cartocdn.com/airship-style/v2.0/airship.css'
AIRSHIP_ICONS_PATH = 'https://libs.cartocdn.com/airship-icons/v2.0/icons.css'
AIRSHIP_COMPONENTS_PATH = 'https://libs.cartocdn.com/airship-components/cartoframes/airship.js'
AIRSHIP_BRIDGE_PATH = 'https://libs.cartocdn.com/airship-bridge/cartoframes/asbridge.js'
AIRSHIP_STYLES_PATH = 'https://libs.cartocdn.com/airship-style/cartoframes/airship.css'
AIRSHIP_ICONS_PATH = 'https://libs.cartocdn.com/airship-icons/cartoframes/icons.css'

CREDENTIALS = {
'username': 'cartoframes',
Expand Down
16 changes: 16 additions & 0 deletions cartoframes/viz/helpers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from __future__ import absolute_import

from .color_category_layer import color_category_layer
from .color_bins_layer import color_bins_layer


def inspect(helper):
import inspect
lines = inspect.getsource(helper)
print(lines)


__all__ = [
'color_category_layer',
'color_bins_layer'
]
32 changes: 32 additions & 0 deletions cartoframes/viz/helpers/color_bins_layer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from __future__ import absolute_import

from ..layer import Layer


def color_bins_layer(source, value, bins=5, palette='purpor', title=''):
return Layer(
source,
style={
'point': {
'color': 'ramp(globalQuantiles(${0},{1}),reverse({2}))'.format(value, bins, palette)
},
'line': {
'color': 'ramp(globalQuantiles(${0},{1}),reverse({2}))'.format(value, bins, palette)
},
'polygon': {
'color': 'opacity(ramp(globalQuantiles(${0},{1}),reverse({2})),0.9)'.format(value, bins, palette)
}
},
popup={
'hover': {
'label': title or value,
'value': '$' + value
}
},
legend={
'type': 'basic',
'ramp': 'color',
'heading': title or value,
'description': ''
}
)
32 changes: 32 additions & 0 deletions cartoframes/viz/helpers/color_category_layer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from __future__ import absolute_import

from ..layer import Layer


def color_category_layer(source, value, top=11, palette='bold', title=''):
return Layer(
source,
style={
'point': {
'color': 'ramp(top(${0}, {1}), {2})'.format(value, top, palette)
},
'line': {
'color': 'ramp(top(${0}, {1}), {2})'.format(value, top, palette)
},
'polygon': {
'color': 'opacity(ramp(top(${0}, {1}), {2}),0.9)'.format(value, top, palette)
}
},
popup={
'hover': {
'label': title or value,
'value': '$' + value
}
},
legend={
'type': 'basic',
'ramp': 'color',
'heading': title or value,
'description': ''
}
)
26 changes: 15 additions & 11 deletions cartoframes/viz/map.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ def __init__(self,
size=None,
viewport=None,
template=None,
default_legend=None,
**kwargs):

self.layers = _init_layers(layers)
Expand All @@ -153,12 +154,18 @@ def __init__(self,
self._airship_path = kwargs.get('_airship_path', None)
self._htmlMap = HTMLMap()

if default_legend is None and all(layer.legend is None for layer in self.layers):
self.default_legend = True
else:
self.default_legend = default_legend

self._htmlMap.set_content(
size=self.size,
sources=self.sources,
bounds=self.bounds,
viewport=self.viewport,
basemap=self.basemap,
default_legend=self.default_legend,
_carto_vl_path=self._carto_vl_path,
_airship_path=self._airship_path)

Expand All @@ -179,7 +186,7 @@ def _get_bounds(bounds, layers):

def _init_layers(layers):
if layers is None:
return None
return []
if not isinstance(layers, collections.Iterable):
return [layers]
else:
Expand Down Expand Up @@ -380,14 +387,15 @@ def __init__(self):

def set_content(
self, size, sources, bounds, viewport=None, basemap=None,
default_legend=None,
_carto_vl_path=defaults.CARTO_VL_PATH, _airship_path=None):

self.html = self._parse_html_content(
size, sources, bounds, viewport, basemap,
size, sources, bounds, viewport, basemap, default_legend,
_carto_vl_path, _airship_path)

def _parse_html_content(
self, size, sources, bounds, viewport, basemap=None,
self, size, sources, bounds, viewport, basemap=None, default_legend=None,
_carto_vl_path=defaults.CARTO_VL_PATH, _airship_path=None):

token = ''
Expand All @@ -397,14 +405,6 @@ def _parse_html_content(
# No basemap
basecolor = 'white'
basemap = ''
elif isinstance(basemap, bool):
if basemap is True:
# Default basemap
basemap = Basemaps.darkmatter
else:
# No basemap
basecolor = 'white'
basemap = ''
elif isinstance(basemap, str):
if basemap not in [Basemaps.voyager, Basemaps.positron, Basemaps.darkmatter]:
# Basemap is a color
Expand Down Expand Up @@ -441,6 +441,8 @@ def _parse_html_content(
'pitch': viewport.get('pitch')
}

has_legends = any(source['legend'] is not None for source in sources) or default_legend

return self._template.render(
width=size[0] if size is not None else None,
height=size[1] if size is not None else None,
Expand All @@ -450,6 +452,8 @@ def _parse_html_content(
mapboxtoken=token,
bounds=bounds,
camera=camera,
has_legends=has_legends,
default_legend=default_legend,
carto_vl_path=_carto_vl_path,
airship_components_path=airship_components_path,
airship_bridge_path=airship_bridge_path,
Expand Down
12 changes: 10 additions & 2 deletions cartoframes/viz/popup.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,17 @@ def _init_popup(self, data):
if isinstance(data, dict):
# TODO: error control
if 'click' in data:
self._click = data.get('click', [])
click_data = data.get('click', [])
if isinstance(click_data, list):
self._click = click_data
else:
self._click = [click_data]
if 'hover' in data:
self._hover = data.get('hover', [])
hover_data = data.get('hover', [])
if isinstance(hover_data, list):
self._hover = hover_data
else:
self._hover = [hover_data]
else:
raise ValueError('Wrong popup input')

Expand Down
Loading