diff --git a/NEWS.rst b/NEWS.rst index 5e0f29fd9..0d4a88c5f 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -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 ----- diff --git a/cartoframes/__init__.py b/cartoframes/__init__.py index d7b68e27e..10d8847d0 100644 --- a/cartoframes/__init__.py +++ b/cartoframes/__init__.py @@ -17,7 +17,7 @@ 'BinMethod', # New API - 'Dataset' + 'Dataset', '__version__' ] diff --git a/cartoframes/assets/templates/viz/basic.html.j2 b/cartoframes/assets/templates/viz/basic.html.j2 index 22a0a4931..07d914565 100644 --- a/cartoframes/assets/templates/viz/basic.html.j2 +++ b/cartoframes/assets/templates/viz/basic.html.j2 @@ -103,9 +103,10 @@
-
- +
+ {% if has_legends %} + {% include 'viz/legends.html.j2' %} + {% endif %}
diff --git a/cartoframes/assets/templates/viz/legends.html.j2 b/cartoframes/assets/templates/viz/legends.html.j2 new file mode 100644 index 000000000..63fe1dfed --- /dev/null +++ b/cartoframes/assets/templates/viz/legends.html.j2 @@ -0,0 +1,35 @@ +{% macro legend(legend_data, id) -%} + {% set type = legend_data.type %} + {% if type == 'basic' %} + + {% elif type == 'gradient' %} + + {% elif type == 'color_steps' %} + + {% else %} +
+

Unknown legend type {{ type }}

+
+ {% endif %} +{%- endmacro %} + +
+ {% if default_legend != none %} + + + + + {% endif %} + + {% for source in sources %} + {% if source.legend %} + + {{ legend(source.legend, 'source%d_legend' % loop.index0) }} + + {% endif %} + {% endfor %} +
\ No newline at end of file diff --git a/cartoframes/assets/templates/viz/legends.js.j2 b/cartoframes/assets/templates/viz/legends.js.j2 new file mode 100644 index 000000000..7c2858539 --- /dev/null +++ b/cartoframes/assets/templates/viz/legends.js.j2 @@ -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? + } +} \ No newline at end of file diff --git a/cartoframes/assets/templates/viz/map.js.j2 b/cartoframes/assets/templates/viz/map.js.j2 index eeb19f3e6..fc84d4e1d 100644 --- a/cartoframes/assets/templates/viz/map.js.j2 +++ b/cartoframes/assets/templates/viz/map.js.j2 @@ -1,3 +1,6 @@ +{% if has_legends %} + {% include 'viz/legends.js.j2' %} +{% endif %} {% include 'error/parser.js.j2' %} {% include 'utils/base64.js.j2' %} @@ -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) { @@ -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) @@ -139,6 +150,10 @@ function onReady() { } } }); + + {% if default_legend %} + createDefaultLegend(layers); + {% endif %} } function setReady () { diff --git a/cartoframes/utils.py b/cartoframes/utils.py index acff7f418..27e437782 100644 --- a/cartoframes/utils.py +++ b/cartoframes/utils.py @@ -1,4 +1,6 @@ """general utility functions""" + +import re import sys import hashlib @@ -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 diff --git a/cartoframes/viz/defaults.py b/cartoframes/viz/defaults.py index bb9cecdc4..4b207a039 100644 --- a/cartoframes/viz/defaults.py +++ b/cartoframes/viz/defaults.py @@ -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', diff --git a/cartoframes/viz/helpers/__init__.py b/cartoframes/viz/helpers/__init__.py new file mode 100644 index 000000000..acf3b9b35 --- /dev/null +++ b/cartoframes/viz/helpers/__init__.py @@ -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' +] diff --git a/cartoframes/viz/helpers/color_bins_layer.py b/cartoframes/viz/helpers/color_bins_layer.py new file mode 100644 index 000000000..4162691d2 --- /dev/null +++ b/cartoframes/viz/helpers/color_bins_layer.py @@ -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': '' + } + ) diff --git a/cartoframes/viz/helpers/color_category_layer.py b/cartoframes/viz/helpers/color_category_layer.py new file mode 100644 index 000000000..877fe9b96 --- /dev/null +++ b/cartoframes/viz/helpers/color_category_layer.py @@ -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': '' + } + ) diff --git a/cartoframes/viz/map.py b/cartoframes/viz/map.py index 5c406d071..766c7bc6f 100644 --- a/cartoframes/viz/map.py +++ b/cartoframes/viz/map.py @@ -140,6 +140,7 @@ def __init__(self, size=None, viewport=None, template=None, + default_legend=None, **kwargs): self.layers = _init_layers(layers) @@ -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) @@ -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: @@ -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 = '' @@ -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 @@ -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, @@ -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, diff --git a/cartoframes/viz/popup.py b/cartoframes/viz/popup.py index d98c21f11..a640cf5b5 100644 --- a/cartoframes/viz/popup.py +++ b/cartoframes/viz/popup.py @@ -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') diff --git a/cartoframes/viz/style.py b/cartoframes/viz/style.py index 1471073f6..0d5b79648 100644 --- a/cartoframes/viz/style.py +++ b/cartoframes/viz/style.py @@ -1,7 +1,7 @@ from __future__ import absolute_import from .defaults import STYLE_DEFAULTS, STYLE_PROPERTIES -from ..utils import merge_dicts +from ..utils import merge_dicts, text_match class Style(object): @@ -116,12 +116,12 @@ def _serialize_properties(self, properties={}): def _prune_defaults(self, defaults, style): output = defaults.copy() - if 'color:' in style: + if text_match(r'color\s*:', style): del output['color'] - if 'width:' in style: + if text_match(r'width\s*:', style): del output['width'] - if 'strokeWidth:' in style: - del output['strokeWidth'] - if 'strokeColor:' in style: + if text_match(r'strokeColor\s*:', style): del output['strokeColor'] + if text_match(r'strokeWidth\s*:', style): + del output['strokeWidth'] return output diff --git a/examples/debug/API/basemaps.ipynb b/examples/debug/API/basemaps.ipynb index 8a65bc4a6..c955cf490 100644 --- a/examples/debug/API/basemaps.ipynb +++ b/examples/debug/API/basemaps.ipynb @@ -47,10 +47,10 @@ " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", "\n", " \n", - " \n", - "\n", - " \n", - " \n", - "
\n", - "
\n", - "
\n", - "
\n", - "
\n", - "
\n", - "
\n", - "
\n", - "
\n", - "
\n", - "
\n", - "
\n", - "\n", - "
\n", - "

There is a \n", - " from the CARTO VL library:

\n", - "
\n", - " \n", - " \n", - " \n", - "
\n", - "\n", - "
\n", - " StackTrace\n", - "
    \n", - "
    \n", - "
    \n", - "\n", - " \n", - " \n", - " \n", - "\">\n", - "\n", - "" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], "source": [ "Map(Layer('populated_places'), basemap='#fabada')" ] diff --git a/examples/debug/API/helpers.ipynb b/examples/debug/API/helpers.ipynb new file mode 100644 index 000000000..801c4b23e --- /dev/null +++ b/examples/debug/API/helpers.ipynb @@ -0,0 +1,2317 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from cartoframes.auth import Context, set_default_context\n", + "from cartoframes.viz import Map\n", + "\n", + "# Context\n", + "context = Context(\n", + " base_url='https://cartovl.carto.com/',\n", + " api_key='default_public'\n", + ")\n", + "set_default_context(context)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " CARTO VL + CARTOframes\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + " \n", + "\n", + " \n", + " \n", + "
    \n", + "
    \n", + "
    \n", + "
    \n", + "
    \n", + " \n", + " \n", + "\n", + "
    \n", + " \n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
    \n", + " \n", + "
    \n", + "
    \n", + "
    \n", + "
    \n", + "
    \n", + "\n", + "
    \n", + "

    There is a \n", + " from the CARTO VL library:

    \n", + "
    \n", + " \n", + " \n", + " \n", + "
    \n", + "\n", + "
    \n", + " StackTrace\n", + "
      \n", + "
      \n", + "
      \n", + "\n", + " \n", + " \n", + " \n", + "\">\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from cartoframes.viz.helpers import color_category_layer\n", + "\n", + "# Simplest version\n", + "Map(\n", + " color_category_layer('sf_neighborhoods', 'name')\n", + ")\n", + "\n", + "# With all params\n", + "Map(\n", + " color_category_layer('sf_neighborhoods', 'name', top=11, palette='bold', title='Neighborhoods')\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " CARTO VL + CARTOframes\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + " \n", + "\n", + " \n", + " \n", + "
      \n", + "
      \n", + "
      \n", + "
      \n", + "
      \n", + " \n", + " \n", + "\n", + "
      \n", + " \n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
      \n", + " \n", + "
      \n", + "
      \n", + "
      \n", + "
      \n", + "
      \n", + "\n", + "
      \n", + "

      There is a \n", + " from the CARTO VL library:

      \n", + "
      \n", + " \n", + " \n", + " \n", + "
      \n", + "\n", + "
      \n", + " StackTrace\n", + "
        \n", + "
        \n", + "
        \n", + "\n", + " \n", + " \n", + " \n", + "\">\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from cartoframes.viz.helpers import color_category_layer\n", + "\n", + "# Simplest version\n", + "Map(\n", + " color_category_layer('sfcta_congestion_roads', 'direction')\n", + ")\n", + "\n", + "# With all params\n", + "Map(\n", + " color_category_layer('sfcta_congestion_roads', 'direction', top=4, palette='bold', title='Roads')\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " CARTO VL + CARTOframes\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + " \n", + "\n", + " \n", + " \n", + "
        \n", + "
        \n", + "
        \n", + "
        \n", + "
        \n", + " \n", + " \n", + "\n", + "
        \n", + " \n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
        \n", + " \n", + "
        \n", + "
        \n", + "
        \n", + "
        \n", + "
        \n", + "\n", + "
        \n", + "

        There is a \n", + " from the CARTO VL library:

        \n", + "
        \n", + " \n", + " \n", + " \n", + "
        \n", + "\n", + "
        \n", + " StackTrace\n", + "
          \n", + "
          \n", + "
          \n", + "\n", + " \n", + " \n", + " \n", + "\">\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from cartoframes.viz.helpers import color_bins_layer\n", + "\n", + "# Simplest version\n", + "Map(\n", + " color_bins_layer('populated_places', 'pop_max')\n", + ")\n", + "\n", + "# With all params\n", + "Map(\n", + " color_bins_layer('populated_places', 'pop_max', bins=5, palette='purpor', title='Pop max')\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "def color_category_layer(source, value, top=11, palette='bold', title=''):\n", + " return Layer(\n", + " source,\n", + " style={\n", + " 'point': {\n", + " 'color': 'ramp(top(${0}, {1}), {2})'.format(value, top, palette)\n", + " },\n", + " 'line': {\n", + " 'color': 'ramp(top(${0}, {1}), {2})'.format(value, top, palette)\n", + " },\n", + " 'polygon': {\n", + " 'color': 'ramp(top(${0}, {1}), {2})'.format(value, top, palette)\n", + " }\n", + " },\n", + " popup={\n", + " 'hover': {\n", + " 'label': title or value,\n", + " 'value': '$' + value\n", + " }\n", + " },\n", + " legend={\n", + " 'type': 'basic',\n", + " 'ramp': 'color',\n", + " 'heading': title or value,\n", + " 'description': ''\n", + " }\n", + " )\n", + "\n" + ] + } + ], + "source": [ + "from cartoframes.viz.helpers import inspect, color_category_layer\n", + "\n", + "inspect(color_category_layer)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/debug/API/popup.ipynb b/examples/debug/API/popup.ipynb index 85ab6a91e..42137af53 100644 --- a/examples/debug/API/popup.ipynb +++ b/examples/debug/API/popup.ipynb @@ -680,7 +680,7 @@ "" ], "text/plain": [ - "" + "" ] }, "execution_count": 2, @@ -707,7 +707,7 @@ " 'populated_places',\n", " 'color: blue',\n", " {\n", - " 'hover': ['$name'],\n", + " 'hover': '$name',\n", " 'click': ['$name', '$pop_max']\n", " }\n", " )\n", @@ -1377,7 +1377,7 @@ "" ], "text/plain": [ - "" + "" ] }, "execution_count": 3, @@ -2072,7 +2072,7 @@ "" ], "text/plain": [ - "" + "" ] }, "execution_count": 4, @@ -2108,10 +2108,10 @@ " 'populated_places',\n", " 'color: blue',\n", " {\n", - " 'hover': [{\n", + " 'hover': {\n", " 'label': 'Name',\n", " 'value': '$name'\n", - " }],\n", + " },\n", " 'click': [{\n", " 'label': 'Name',\n", " 'value': '$name'\n", @@ -2787,7 +2787,7 @@ "" ], "text/plain": [ - "" + "" ] }, "execution_count": 5, diff --git a/setup.py b/setup.py index 1c0f2f2f4..008368ff1 100644 --- a/setup.py +++ b/setup.py @@ -17,6 +17,7 @@ def walk_subpkg(name): data_files.append(os.path.join(sub_dir, f)) return data_files + REQUIRES = [ 'pandas>=0.20.1', 'webcolors>=1.7.0', @@ -52,7 +53,7 @@ def walk_subpkg(name): about = {} with open(os.path.join(here, 'cartoframes', '__version__.py'), 'r', 'utf-8') as f: - exec(f.read(), about) + exec(f.read(), about) setup( name=about['__title__'], diff --git a/test/test_utils.py b/test/test_utils.py index d3b024fa6..c1f123dcc 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -4,8 +4,6 @@ import unittest from collections import OrderedDict -import pandas as pd - from cartoframes.utils import (dict_items, cssify, importify_params) diff --git a/test/viz/test_popup.py b/test/viz/test_popup.py index 4dcf99dcc..0547685b2 100644 --- a/test/viz/test_popup.py +++ b/test/viz/test_popup.py @@ -23,6 +23,20 @@ def test_popup_init(self): 'value': '$pop' }]) + popup = Popup({ + 'click': '$pop', + 'hover': { + 'label': 'Pop', + 'value': '$pop' + } + }) + + self.assertEqual(popup._click, ['$pop']) + self.assertEqual(popup._hover, [{ + 'label': 'Pop', + 'value': '$pop' + }]) + def test_popup_interactivity(self): """Popup should return a proper interactivity object""" popup = Popup({