Repository: CartoDB/cartoframes
Pull request #1661 (2020-07-02 09:08:07)

Added formula widget and global for every widget
Story [ch87274](https://app.clubhouse.io/cartoteam/story/87274/add-formula-widget)


Patch:
diff --git a/cartoframes/assets/init_web_sdk.js.j2 b/cartoframes/assets/init_web_sdk.js.j2
index 8d520761..cd082cad 100644
--- a/cartoframes/assets/init_web_sdk.js.j2
+++ b/cartoframes/assets/init_web_sdk.js.j2
@@ -1,3 +1,10 @@
+const GLOBAL = 'remote';
+const LOCAL = 'remote-filtered';
+
+function cleanStyleName(name) {
+  return name.replace('Style','');
+}
+
 async function initialize() {
   const basemap = '{{ basemap }}';
   const bounds = {{ bounds }};
@@ -5,31 +12,32 @@ async function initialize() {
   const layersDef = {{ layers|tojson }};
 
   let credentials = null;
+  let dataview = null;
   let layer = null;
   let source = null;
   let style = null;
-  let widget = null;
+  let styleName = null;
 
   const deckMap = carto.viz.createMap({
     basemap: basemap,
-    initialViewState: camera
+    viewState: camera
   });
 
   let layerI = layersDef.length - 1;  // Their "IDs" are reversed
   for (const layerDef of layersDef) {
     if (layerDef.type === 'Query') {
-        credentials = new carto.Credentials(layerDef.credentials.username, layerDef.credentials.api_key, layerDef.credentials.base_url);
-        source = new carto.viz.CARTOSource(layerDef.data, {credentials});
+      credentials = new carto.auth.Credentials(layerDef.credentials.username, layerDef.credentials.api_key, layerDef.credentials.base_url);
+      source = new carto.viz.source.SQL(layerDef.data, {credentials});
 
     } else {
-        source = new carto.viz.GeoJsonSource(layerDef.data);
+      source = new carto.viz.source.GeoJSON(layerDef.data);
     }
 
     if (!!layerDef.viz && !!layerDef.viz.value) {
-      style = carto.viz[layerDef.viz.name](layerDef.viz.value, layerDef.viz.options);
+      style = carto.viz.style[cleanStyleName(layerDef.viz.name)](layerDef.viz.value, layerDef.viz.options);
 
     } else if (!!layerDef.viz) {
-      style = carto.viz[layerDef.viz.name](layerDef.viz.options);
+      style = carto.viz.style[cleanStyleName(layerDef.viz.name)](layerDef.viz.options);
     }
 
     layer = new carto.viz.Layer(source, style);
@@ -47,17 +55,40 @@ async function initialize() {
     let widgetI = 0;
     for (const widgetDef of layerDef.widgets) {
       if (widgetDef.type === 'category') {
-        widget = new carto.dataview.Category(layer, widgetDef.value, {
+        dataview = new carto.viz.dataview.Category(layer, widgetDef.value, {
           operation: 'count',
-          operationColumn: 'cartodb_id',
-          mode: 'local'
+          mode: widgetDef.is_global ? GLOBAL : LOCAL
+        });
+
+        new carto.viz.widget.Category(`as-category-widget#layer${layerI}_widget${widgetI}`, dataview, {
+          showClearButton: true,
+          disableInteractivity: widgetDef.options.readOnly,
+          format: widgetDef.options.format
         });
-      }
 
-      new carto.widget.Category(`as-category-widget#layer${layerI}_widget${widgetI}`, widget, {
-        showClearButton: true,
-        disableInteractivity: widgetDef.options.readOnly
-      });
+      } else if (widgetDef.type === 'formula') {
+        dataview = new carto.viz.dataview.Formula(layer, widgetDef.value, {
+          operation: widgetDef.operation,
+          mode: widgetDef.is_global ? GLOBAL : LOCAL
+        });
+
+        new carto.viz.widget.Formula(`as-formula-widget#layer${layerI}_widget${widgetI}`, dataview, {
+          format: widgetDef.options.format
+        });
+
+      } else if (widgetDef.type === 'histogram') {
+        dataview = new carto.viz.dataview.Histogram(layer, widgetDef.value, {
+            bins: widgetDef.options.buckets,
+            mode: widgetDef.is_global ? GLOBAL : LOCAL
+          }
+        );
+
+        new carto.viz.widget.Histogram(`as-histogram-widget#layer${layerI}_widget${widgetI}`, dataview, {
+          showClear: true,
+          disableInteractivity: widgetDef.options.readOnly,
+          format: widgetDef.options.format
+        });
+      }
 
       widgetI++;
     }
@@ -65,11 +96,11 @@ async function initialize() {
     layerI--;
   }
 
-  const {viewport} = deckMap.layerManager.context;
+  const viewport = deckMap.getViewports()[0];
   const {longitude, latitude, zoom} = viewport.fitBounds(bounds);
   const viewState = Object.assign({}, {longitude, latitude, zoom}, camera);
 
-  deckMap.viewManager.setProps({
+  deckMap.setProps({
     viewState: viewState
   });
 }
diff --git a/cartoframes/assets/src/bundle.js b/cartoframes/assets/src/bundle.js
index 9c4cf6dc..c0b558eb 100644
--- a/cartoframes/assets/src/bundle.js
+++ b/cartoframes/assets/src/bundle.js
@@ -485,14 +485,14 @@ var init = (function () {
       const lat = center.lat.toFixed(6);
       const lng = center.lng.toFixed(6);
       const zoom = map.getZoom().toFixed(2);
-    
+
       mapInfo$.innerText = `viewport={'zoom': ${zoom}, 'lat': ${lat}, 'lng': ${lng}}`;
     }
 
     updateMapInfo();
 
     map.on('zoom', updateMapInfo);
-    map.on('move', updateMapInfo); 
+    map.on('move', updateMapInfo);
   }
 
   function getBasecolorSettings(basecolor) {
@@ -566,7 +566,7 @@ var init = (function () {
         }
         // Track layers to add only one feature per layer
         layerIDs.push(feature.layerId);
-    
+
         for (const item of attrs) {
           const variable = feature.variables[item.name];
           if (variable) {
@@ -945,7 +945,7 @@ var init = (function () {
     });
 
     const layerSelector = new AsBridge.VL.Layers(layerSelector$, carto, layersInfo, mapLayers);
-    
+
     layerSelector.build();
   }
 
diff --git a/cartoframes/assets/templates/viz/main.html.j2 b/cartoframes/assets/templates/viz/main.html.j2
index c764acac..7c09e59e 100644
--- a/cartoframes/assets/templates/viz/main.html.j2
+++ b/cartoframes/assets/templates/viz/main.html.j2
@@ -10,7 +10,7 @@
   {% if render == 'carto-vl' %}
     <script src="{{carto_vl_path}}"></script>
   {% else %}
-    <script src="https://unpkg.com/deck.gl@8.2.0-beta.1/dist.min.js"></script>
+    <script src="https://unpkg.com/deck.gl@8.2.0/dist.min.js"></script>
     <script src="{{web_sdk_path}}"></script>
   {% endif %}
 
diff --git a/cartoframes/assets/templates/viz/widgets/formula.html.j2 b/cartoframes/assets/templates/viz/widgets/formula.html.j2
index df6a778d..31fea494 100644
--- a/cartoframes/assets/templates/viz/widgets/formula.html.j2
+++ b/cartoframes/assets/templates/viz/widgets/formula.html.j2
@@ -1,18 +1,25 @@
 <div>
-  <as-widget-header
-    header="{{widget.title}}"
-    subheader="{{widget.description}}">
-  </as-widget-header>
-
-  <p class="as-title as-m--4 as-p--0 as-font--medium" id="{{id}}-value">
-    <span class="as-loading as-loading--s">
-      <svg viewBox="0 0 50 50">
-        <circle cx="25" cy="25" r="20" fill="none" />
-      </svg>
-    </span>
-  </p>
+  {% if render == 'carto-vl' %}
+    <as-widget-header
+      header="{{widget.title}}"
+      subheader="{{widget.description}}">
+    </as-widget-header>
 
+    <p class="as-title as-m--4 as-p--0 as-font--medium" id="{{id}}-value">
+      <span class="as-loading as-loading--s">
+        <svg viewBox="0 0 50 50">
+          <circle cx="25" cy="25" r="20" fill="none" />
+        </svg>
+      </span>
+    </p>
+  {% else %}
+    <as-formula-widget
+      id="{{id}}"
+      description="{{widget.description}}"
+      heading="{{widget.title}}">
+    </as-formula-widget>
+  {% endif %}
   {% if widget.footer %}
     <footer>{{widget.footer | safe }}</footer>
   {% endif %}
-</div>
\ No newline at end of file
+</div>
diff --git a/cartoframes/viz/constants.py b/cartoframes/viz/constants.py
index bda1fb01..8f9a7a86 100644
--- a/cartoframes/viz/constants.py
+++ b/cartoframes/viz/constants.py
@@ -3,11 +3,11 @@ CARTO_VL_DEV = '/dist/carto-vl.js'
 CARTO_VL_URL = 'https://libs.cartocdn.com/carto-vl/{}/carto-vl.min.js'.format(CARTO_VL_VERSION)
 
 # TODO: Set the right values
-WEB_SDK_VERSION = None
-WEB_SDK_DEV = None
-WEB_SDK_URL = 'https://web-sdk.now.sh/dist/umd/index.min.js'
+WEB_SDK_VERSION = 'v1.0.0-alpha.0'
+WEB_SDK_DEV = '/dist/web-sdk.js'
+WEB_SDK_URL = 'https://libs.cartocdn.com/web-sdk/{}/index.min.js'.format(WEB_SDK_VERSION)
 
-AIRSHIP_VERSION = 'v2.3'
+AIRSHIP_VERSION = 'v2.4.0'
 AIRSHIP_COMPONENTS_DEV = '/packages/components/dist/airship.js'
 AIRSHIP_BRIDGE_DEV = '/packages/bridge/dist/asbridge.js'
 AIRSHIP_MODULE_DEV = '/packages/components/dist/airship/airship.esm.js'
diff --git a/cartoframes/viz/layer.py b/cartoframes/viz/layer.py
index 028e4ccf..78b2413b 100644
--- a/cartoframes/viz/layer.py
+++ b/cartoframes/viz/layer.py
@@ -106,7 +106,7 @@ class Layer:
                  title=None,
                  parent_map=None,
                  encode_data=True,
-                 render='carto-vl'):
+                 render='web-sdk'):
 
         self._render = render  # It's here because we need it pretty soon
         self.is_basemap = False
@@ -238,6 +238,9 @@ class Layer:
         self.viz = self.style.compute_viz(self.geom_type, self._external_variables, self._render)
         self.interactivity = self.popups.get_interactivity()
 
+        if self._render != 'carto-vl':
+            self.widgets_info = self.widgets.get_widgets_info(self._render)
+
         # The TODO block above was only necesary only for `render='web-sdk` but because
         # the layers can be reused we need to do it also for `render='carto-vl' layers`
 
diff --git a/cartoframes/viz/map.py b/cartoframes/viz/map.py
index 0ec8c28d..fb9cc7ca 100644
--- a/cartoframes/viz/map.py
+++ b/cartoframes/viz/map.py
@@ -107,7 +107,7 @@ class Map:
                  description=None,
                  is_static=None,
                  layer_selector=False,
-                 render='carto-vl',
+                 render='web-sdk',
                  **kwargs):
 
         self._render = render  # It needs to be before `self.layers`
diff --git a/cartoframes/viz/widget.py b/cartoframes/viz/widget.py
index 1841cd56..15b80879 100644
--- a/cartoframes/viz/widget.py
+++ b/cartoframes/viz/widget.py
@@ -1,5 +1,6 @@
 from . import constants
 from ..utils.utils import camel_dictionary, gen_variable_name
+from ..viz.styles.utils import prop as utils_prop
 
 
 class Widget:
@@ -14,12 +15,13 @@ class Widget:
         - :py:meth:`animation_widget <cartoframes.viz.animation_widget>`
 
     """
-    def __init__(self, widget_type, value=None, title=None, description=None,
-                 footer=None, prop=None, read_only=False, buckets=20, weight=1, format=None):
+    def __init__(self, widget_type, value=None, title=None, description=None, footer=None, prop=None, read_only=False,
+                 buckets=20, weight=1, operation=None, format=None, is_global=False):
         self._check_type(widget_type)
 
         self._type = widget_type
-        self._value = value
+        self._value_original = value
+        self._value = self._get_formula_value_expression(widget_type, value, operation, is_global)
         self._title = title
         self._description = description
         self._footer = footer
@@ -27,18 +29,19 @@ class Widget:
         self._read_only = read_only
         self._buckets = buckets
         self._weight = weight
-        self._format = format
         self._variable_name = gen_variable_name(self._value) if self._value else ''
+        self._operation = operation
+        self._format = format
+        self._is_global = is_global
         self._options = self._build_options()
 
     def set_title(self, title):
         if title is not None:
             self._title = title
 
-    def get_info(self):
+    def get_info(self, render='carto-vl'):
         if self._type or self._title or self._description or self._footer:
-
-            return {
+            info = {
                 'type': self._type,
                 'prop': self._prop,
                 'value': self._value or '',
@@ -47,8 +50,16 @@ class Widget:
                 'description': self._description or '',
                 'footer': self._footer or '',
                 'has_bridge': self.has_bridge(),
+                'is_global': self._is_global,
                 'options': self._options
             }
+
+            if self._type == 'formula' and render != 'carto-vl':
+                info['value'] = self._value_original
+                info['operation'] = self._operation
+
+            return info
+
         else:
             return {}
 
@@ -58,7 +69,7 @@ class Widget:
         return constants.VIZ_PROPERTIES_MAP.get(prop)
 
     def has_bridge(self):
-        return self._type not in ('formula', 'basic')
+        return self._type not in ('formula', 'basic')  # TODO: We should remove 'formula' from here
 
     def _check_type(self, widget_type):
         if widget_type and widget_type not in constants.WIDGET_TYPES:
@@ -79,3 +90,24 @@ class Widget:
         }
 
         return camel_dictionary(options)
+
+    @classmethod
+    def _get_formula_value_expression(cls, widget_type, value, operation, is_global):
+        if widget_type != 'formula':
+            return value
+
+        if value == 'count':
+            formula_operation = cls._get_formula_operation(value, is_global)
+            return formula_operation + '()'
+        elif operation in ['avg', 'max', 'min', 'sum']:
+            formula_operation = cls._get_formula_operation(operation, is_global)
+            return formula_operation + '(' + utils_prop(value) + ')'
+        else:
+            return utils_prop(value)
+
+    @staticmethod
+    def _get_formula_operation(value_operation, is_global):
+        if is_global:
+            return constants.FORMULA_OPERATIONS_GLOBAL.get(value_operation)
+        else:
+            return constants.FORMULA_OPERATIONS_VIEWPORT.get(value_operation)
diff --git a/cartoframes/viz/widget_list.py b/cartoframes/viz/widget_list.py
index f28f8ca3..a8468585 100644
--- a/cartoframes/viz/widget_list.py
+++ b/cartoframes/viz/widget_list.py
@@ -30,11 +30,11 @@ class WidgetList:
         else:
             return []
 
-    def get_widgets_info(self):
+    def get_widgets_info(self, render='carto-vl'):
         widgets_info = []
         for widget in self._widgets:
             if widget:
-                widgets_info.append(widget.get_info())
+                widgets_info.append(widget.get_info(render))
 
         return widgets_info
 
diff --git a/cartoframes/viz/widgets/category_widget.py b/cartoframes/viz/widgets/category_widget.py
index 3c28bea7..63b49be1 100644
--- a/cartoframes/viz/widgets/category_widget.py
+++ b/cartoframes/viz/widgets/category_widget.py
@@ -1,7 +1,7 @@
 from ..widget import Widget
 
 
-def category_widget(value, title=None, description=None, footer=None, read_only=False, weight=1):
+def category_widget(value, title=None, description=None, footer=None, read_only=False, weight=1, is_global=False):
     """Helper function for quickly creating a category widget.
 
     Args:
@@ -11,6 +11,8 @@ def category_widget(value, title=None, description=None, footer=None, read_only=
         footer (str, optional): Footer text placed on the widget bottom.
         read_only (boolean, optional): Interactively filter a category by selecting it in the widget.
           Set to "False" by default.
+        is_global (boolean, optional): Account for calculations based on the entire dataset ('global') vs.
+            the default of 'viewport' features.
 
     Returns:
         cartoframes.viz.widget.Widget
@@ -23,5 +25,5 @@ def category_widget(value, title=None, description=None, footer=None, read_only=
         ...     footer='Widget footer')
 
     """
-    return Widget('category', value, title, description, footer,
-                  read_only=read_only, weight=weight)
+    return Widget('category', value, title, description, footer, read_only=read_only, weight=weight,
+                  is_global=is_global)
diff --git a/cartoframes/viz/widgets/formula_widget.py b/cartoframes/viz/widgets/formula_widget.py
index b43f327f..c73acfde 100644
--- a/cartoframes/viz/widgets/formula_widget.py
+++ b/cartoframes/viz/widgets/formula_widget.py
@@ -1,10 +1,7 @@
-from ..constants import FORMULA_OPERATIONS_GLOBAL, FORMULA_OPERATIONS_VIEWPORT
 from ..widget import Widget
-from ..styles.utils import prop
 
 
-def formula_widget(value, operation=None, title=None, description=None, footer=None,
-                   is_global=False, format=None):
+def formula_widget(value, operation=None, title=None, description=None, footer=None, is_global=False, format=None):
     """Helper function for quickly creating a formula widget.
 
     Formula widgets calculate aggregated values ('avg', 'max', 'min', 'sum') from numeric columns
@@ -45,23 +42,4 @@ def formula_widget(value, operation=None, title=None, description=None, footer=N
     """
     if isinstance(operation, str):
         operation = operation.lower()
-    value = _get_value_expression(operation, value, is_global)
-    return Widget('formula', value, title, description, footer, format=format)
-
-
-def _get_value_expression(operation, value, is_global):
-    if value == 'count':
-        formula_operation = _get_formula_operation(value, is_global)
-        return formula_operation + '()'
-    elif operation in ['avg', 'max', 'min', 'sum']:
-        formula_operation = _get_formula_operation(operation, is_global)
-        return formula_operation + '(' + prop(value) + ')'
-    else:
-        return prop(value)
-
-
-def _get_formula_operation(operation, is_global):
-    if is_global:
-        return FORMULA_OPERATIONS_GLOBAL.get(operation)
-    else:
-        return FORMULA_OPERATIONS_VIEWPORT.get(operation)
+    return Widget('formula', value, title, description, footer, operation=operation, format=format, is_global=is_global)
diff --git a/cartoframes/viz/widgets/histogram_widget.py b/cartoframes/viz/widgets/histogram_widget.py
index 353b695b..5c422e79 100644
--- a/cartoframes/viz/widgets/histogram_widget.py
+++ b/cartoframes/viz/widgets/histogram_widget.py
@@ -1,8 +1,8 @@
 from ..widget import Widget
 
 
-def histogram_widget(value, title=None, description=None, footer=None, read_only=False,
-                     buckets=20, weight=1):
+def histogram_widget(value, title=None, description=None, footer=None, read_only=False, buckets=20, weight=1,
+                     is_global=False):
     """Helper function for quickly creating a histogram widget.
 
     Histogram widgets display the distribution of a numeric attribute, in buckets, to group
@@ -19,6 +19,8 @@ def histogram_widget(value, title=None, description=None, footer=None, read_only
         read_only (boolean, optional): Interactively filter a range of numeric values by
             selecting them in the widget. Set to "False" by default.
         buckets (number, optional): Number of histogram buckets. Set to 20 by default.
+        is_global (boolean, optional): Account for calculations based on the entire dataset ('global') vs.
+            the default of 'viewport' features.
 
     Returns:
         cartoframes.viz.widget.Widget
@@ -32,5 +34,5 @@ def histogram_widget(value, title=None, description=None, footer=None, read_only
         ...     buckets=9)
 
     """
-    return Widget('histogram', value, title, description, footer,
-                  read_only=read_only, buckets=buckets, weight=weight)
+    return Widget('histogram', value, title, description, footer, read_only=read_only, buckets=buckets, weight=weight,
+                  is_global=is_global)
diff --git a/examples/_debug/web_sdk_vs_carto_vl__map_options.ipynb b/examples/_debug/web_sdk_vs_carto_vl__map_options.ipynb
index 3aac9569..aec39658 100644
--- a/examples/_debug/web_sdk_vs_carto_vl__map_options.ipynb
+++ b/examples/_debug/web_sdk_vs_carto_vl__map_options.ipynb
@@ -1,5 +1,23 @@
 {
  "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%load_ext autoreload"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%autoreload 2"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": null,
diff --git a/examples/_debug/web_sdk_vs_carto_vl__popups.ipynb b/examples/_debug/web_sdk_vs_carto_vl__popups.ipynb
index 610e3b81..6b0cc289 100644
--- a/examples/_debug/web_sdk_vs_carto_vl__popups.ipynb
+++ b/examples/_debug/web_sdk_vs_carto_vl__popups.ipynb
@@ -1,5 +1,23 @@
 {
  "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%load_ext autoreload"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%autoreload 2"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": null,
diff --git a/examples/_debug/web_sdk_vs_carto_vl__style_points.ipynb b/examples/_debug/web_sdk_vs_carto_vl__style_points.ipynb
index d0d18699..5a205ab9 100644
--- a/examples/_debug/web_sdk_vs_carto_vl__style_points.ipynb
+++ b/examples/_debug/web_sdk_vs_carto_vl__style_points.ipynb
@@ -1,5 +1,23 @@
 {
  "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%load_ext autoreload"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%autoreload 2"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": null,
@@ -34,7 +52,9 @@
   {
    "cell_type": "code",
    "execution_count": null,
-   "metadata": {},
+   "metadata": {
+    "scrolled": false
+   },
    "outputs": [],
    "source": [
     "Map(Layer('global_power_plants'), render='web-sdk')"
diff --git a/examples/_debug/web_sdk_vs_carto_vl__widgets.ipynb b/examples/_debug/web_sdk_vs_carto_vl__widgets.ipynb
index 1359daf2..0f4728a4 100644
--- a/examples/_debug/web_sdk_vs_carto_vl__widgets.ipynb
+++ b/examples/_debug/web_sdk_vs_carto_vl__widgets.ipynb
@@ -26,7 +26,7 @@
    "source": [
     "from cartoframes.auth import Credentials, set_default_credentials\n",
     "from cartoframes.viz import (\n",
-    "    category_widget,\n",
+    "    category_widget, formula_widget, histogram_widget,\n",
     "    Layer, Map\n",
     ")\n",
     "from geopandas import read_file"
@@ -36,7 +36,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "## Category widget"
+    "## Category"
    ]
   },
   {
@@ -183,6 +183,98 @@
    "source": [
     "Map(Layer(gdf, widgets=[peat_category_widget]), render='carto-vl')"
    ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Formula"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Single"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "set_default_credentials('cartoframes')\n",
+    "\n",
+    "collision_formula_widget = formula_widget(\n",
+    "    'personcount', operation='sum', title='Number of people involved',\n",
+    "    description='Zoom and/or pan the map to update count', format='.3s', is_global=False\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "Map(Layer('seattle_collisions', widgets=[collision_formula_widget]), render='web-sdk')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "Map(Layer('seattle_collisions', widgets=[collision_formula_widget]), render='carto-vl')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Histogram"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Single"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "set_default_credentials('cartoframes')\n",
+    "\n",
+    "vehicle_histogram_widget = histogram_widget(\n",
+    "    'vehcount', title='Number of vehicles involved',\n",
+    "    description='Select a range of values to filter', buckets=9, is_global=True\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "Map(Layer('seattle_collisions', widgets=[vehicle_histogram_widget]), render='web-sdk')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "Map(Layer('seattle_collisions', widgets=[vehicle_histogram_widget]), render='carto-vl')"
+   ]
   }
  ],
  "metadata": {


Test patch:
diff --git a/tests/unit/viz/test_map.py b/tests/unit/viz/test_map.py
index bcb5cf26..d6d89786 100644
--- a/tests/unit/viz/test_map.py
+++ b/tests/unit/viz/test_map.py
@@ -66,13 +66,13 @@ class TestMapLayer(object):
 
         assert map.layers == [layer]
         assert len(map.layer_defs) == 1
-        assert map.layer_defs[0].get('interactivity') == []
+        assert map.layer_defs[0].get('interactivity') == {'click': None, 'hover': None}
         assert map.layer_defs[0].get('credentials') is None
         assert map.layer_defs[0].get('legends') is not None
         assert map.layer_defs[0].get('widgets') is not None
         assert map.layer_defs[0].get('data') is not None
         assert map.layer_defs[0].get('type') == 'GeoJSON'
-        assert map.layer_defs[0].get('viz') is not None
+        assert map.layer_defs[0].get('viz') is None
 
     def test_two_layers(self):
         """Map layer should be able to initialize two layers in the correct order"""
@@ -100,32 +100,27 @@ class TestMapLayer(object):
         )
 
         map = Map(layer)
-        assert map.layer_defs[0].get('interactivity') == [
-            {
-                'event': 'click',
-                'attrs': {
-                    'name': 'v6ae999',
-                    'title': 'name',
-                    'format': None
-                }
-            },
-            {
-                'event': 'click',
-                'attrs': {
-                    'name': 'v4f197c',
-                    'title': 'pop',
-                    'format': None
+        assert map.layer_defs[0].get('interactivity') == {
+            'hover': [
+                {
+                    'format': None,
+                    'attr': 'pop',
+                    'title': 'Pop'
                 }
-            },
-            {
-                'event': 'hover',
-                'attrs': {
-                    'name': 'v4f197c',
-                    'title': 'Pop',
-                    'format': None
+            ],
+            'click': [
+                {
+                    'format': None,
+                    'attr': 'name',
+                    'title': 'name'
+                },
+                {
+                    'format': None,
+                    'attr': 'pop',
+                    'title': 'pop'
                 }
-            }
-        ]
+            ]
+        }
 
     def test_default_interactive_layer(self):
         """Map layer should get the default event if the interactivity is set to []"""
@@ -135,24 +130,24 @@ class TestMapLayer(object):
         )
 
         map = Map(layer)
-        assert map.layer_defs[0].get('interactivity') == []
+        assert map.layer_defs[0].get('interactivity') == {'click': None, 'hover': None}
 
 
 class TestMapDevelopmentPath(object):
-    def test_default_carto_vl_path(self):
+    def test_default_web_sdk_path(self):
         """Map dev path should use default paths if none are given"""
         map = Map()
         map._repr_html_()
         template = map._html_map.html
-        assert constants.CARTO_VL_URL in template
+        assert constants.WEB_SDK_URL in template
 
-    def test_custom_carto_vl_path(self):
+    def test_custom_web_sdk_path(self):
         """Map dev path should use custom paths"""
-        _carto_vl_path = 'custom_carto_vl_path'
-        map = Map(_carto_vl_path=_carto_vl_path)
+        _web_sdk_path = 'custom_web_sdk_path'
+        map = Map(_web_sdk_path=_web_sdk_path)
         map._repr_html_()
         template = map._html_map.html
-        assert _carto_vl_path + constants.CARTO_VL_DEV in template
+        assert _web_sdk_path + constants.WEB_SDK_DEV in template
 
     def test_default_airship_path(self):
         """Map dev path should use default paths if none are given"""
@@ -271,7 +266,7 @@ class TestMapPublication(object):
             _carto_vl_path=None,
             basemap='yellow',
             bounds=[[1, 4], [2, 3]],
-            camera={'bearing': None, 'center': [-10, 50], 'pitch': None, 'zoom': 5},
+            camera={'latitude': 50, 'zoom': 5, 'longitude': -10},
             description='description',
             is_embed=True,
             is_static=True,
diff --git a/tests/unit/viz/test_widget.py b/tests/unit/viz/test_widget.py
index 9596cb7b..a3c6f348 100644
--- a/tests/unit/viz/test_widget.py
+++ b/tests/unit/viz/test_widget.py
@@ -23,13 +23,14 @@ class TestWidget(object):
 
         assert widget.get_info() == {
             'type': 'formula',
-            'value': 'amount',
+            'value': "prop('amount')",
             'title': '[title]',
             'description': '[description]',
+            'is_global': False,
             'footer': '[footer]',
             'has_bridge': False,
             'prop': '',
-            'variable_name': 'v9cb6ff',
+            'variable_name': 'v6476eb',
             'options': {
                 'readOnly': False,
                 'buckets': 20,
@@ -67,7 +68,8 @@ class TestWidget(object):
                 'weight': 1,
                 'autoplay': True,
                 'format': None
-            }
+            },
+            'is_global': False
         }
 
     def test_animation_widget_prop(self):
@@ -82,6 +84,7 @@ class TestWidget(object):
             'footer': '',
             'has_bridge': True,
             'prop': 'width',
+            'is_global': False,
             'variable_name': '',
             'options': {
                 'readOnly': False,
diff --git a/tests/unit/viz/test_widget_list.py b/tests/unit/viz/test_widget_list.py
index 2641a617..b9b85b95 100644
--- a/tests/unit/viz/test_widget_list.py
+++ b/tests/unit/viz/test_widget_list.py
@@ -65,6 +65,10 @@ class TestWidgetList(object):
 
         widget_list = WidgetList([WIDGET_A, WIDGET_B])
         widgets_info = widget_list.get_widgets_info()
+
+        # print(widgets_info)
+        # raise Exception('manolo')
+
         assert widgets_info == [
             {
                 'type': 'formula',
@@ -74,6 +78,7 @@ class TestWidgetList(object):
                 'description': '[description]',
                 'footer': '[footer]',
                 'has_bridge': False,
+                'is_global': False,
                 'variable_name': 'vdb8cc1',
                 'options': {
                     'readOnly': False,
@@ -90,6 +95,7 @@ class TestWidgetList(object):
                 'description': '',
                 'footer': '',
                 'has_bridge': False,
+                'is_global': False,
                 'variable_name': '',
                 'options': {
                     'readOnly': False,