Widgets

View on Github

A set of advanced widgets, which allow not only a visual representation but a rich interaction with data & map layers, such as filtering or an automatic data refresh on viewport change, thanks to the connection with the CARTO slice on redux.

This package, @carto/react-widgets contains the widgets business logic and the @carto/react-ui package contains the user interface components. The UI is decoupled from the business logic so you can provide your own user interface or modify the business logic. To review interactively the UI for the widgets, check the Storybook catalogue.

Components

BarWidget

Renders a <BarWidget /> component, binded to a source at redux. From a data perspective, the BarWidget would present a behaviour equivalent to CategoryWidget (groups or ‘categories’ from a column, with an aggregated calculation), but with a different UI that uses vertical bars instead of horizontal bars.

  • Input:

  • Example:

    In this example, the widget would display the SUM of population for all the countries, grouped by continent:

import { BarWidget } from "@carto/react-widgets";
  
const customFormatter = (value) => `${value} people`;
  
return (
  <BarWidget
    id="populationByContinent"
    title="Population by continent"
    dataSource="countriesSourceId"
    column="continent"
    operationColumn="population"
    operation={AggregationTypes.SUM}
    formatter={customFormatter}
    onError={console.error}
  />
);
  
// The operationColumn wouldn't be required if using AggregationTypes.COUNT, to count the number of countries per continent

Available beginning with v1.3

You can also make calculations on widgets using multiple columns. For instance, if you are working with a dataset that contains population data disaggregated by gender: population_m and population_f, you can use an array in operationColumn and sum the values from both columns using the joinOperation property.

import { BarWidget } from "@carto/react-widgets";
  
const customFormatter = (value) => `${value} people`;
  
return (
  <BarWidget
    id="populationByContinent"
    title="Population by continent"
    dataSource="countriesSourceId"
    column="continent"
    operationColumn={["population_m", "population_f"]}
    joinOperation={AggregationTypes.SUM}
    operation={AggregationTypes.SUM}
    formatter={customFormatter}
    onError={console.error}
  />
);

CategoryWidget

Renders a <CategoryWidget /> component, binded to a source at redux.

  • Input:

  • Example:

    In this example, the widget would display the SUM of population for all the countries, grouped by continent:

import { CategoryWidget } from "@carto/react-widgets";
  
const customFormatter = (value) => `${value} people`;
  
return (
  <CategoryWidget
    id="populationByContinent"
    title="Population by continent"
    dataSource="countriesSourceId"
    column="continent"
    operationColumn="population"
    operation={AggregationTypes.SUM}
    formatter={customFormatter}
    onError={console.error}
  />
);
  
// The operationColumn wouldn't be required if using AggregationTypes.COUNT, to count the number of countries per continent

Available beginning with v1.3

You can also make calculations on widgets using multiple columns. For instance, if you are working with a dataset that contains population data disaggregated by gender: population_m and population_f, you can use an array in operationColumn and sum the values from both columns using the joinOperation property.

import { CategoryWidget } from "@carto/react-widgets";
  
const customFormatter = (value) => `${value} people`;
  
return (
  <CategoryWidget
    id="populationByContinent"
    title="Population by continent"
    dataSource="countriesSourceId"
    column="continent"
    operationColumn={["population_m", "population_f"]}
    joinOperation={AggregationTypes.SUM}
    operation={AggregationTypes.SUM}
    formatter={customFormatter}
    onError={console.error}
  />
);

FeatureSelectionWidget

Renders a <FeatureSelectionWidget /> component. The widget allows the user to draw a shape on the map and apply a filter to select the features within the shape. Once a shape has been drawn, it can be selected and modified by adding/removing vertices or translated to a new location. By default the mask is active but it can be disabled temporarily and re-enabled again.

There are different selection modes supporting different shapes. The mode selected by default is FEATURE_SELECTION_MODES.POLYGON. If you want to choose a different default selection mode, you can set the featureSelectionMode prop in the initialStateSlice.

If you want to use this widget in your app, you need to do two different things:

  1. Add the <FeatureSelectionWidget> component to the view where you want to have it available. If you are using one of the CARTO for React templates and you want to use it in all of your views, you can add it to the <MapContainer> component.

  2. Add the FeatureSelectionLayer to your layers list. If you are using one of the CARTO for React templates, you need to add it to the src/components/layers/index.js file like this:

import { FeatureSelectionLayer } from '@carto/react-widgets';

export const getLayers = () => {
  return [
    ...,
    FeatureSelectionLayer(),
  ];
}; 
  • Input:

The FeatureSelectionLayer accepts the following optional props:

  • Example:

    In this example, we add a FeatureSelectionWidget supporting just two selection modes using a specific CSS class.

import { FeatureSelectionWidget } from "@carto/react-widgets";
import { FEATURE_SELECTION_MODES } from '@carto/react-core';
  
return (
  <FeatureSelectionWidget 
    className={myCSSClassName} 
    selectionModes={[FEATURE_SELECTION_MODES.POLYGON, FEATURE_SELECTION_MODES.RECTANGLE]}
  />
);

FormulaWidget

Renders a <FormulaWidget /> component, binded to a source at redux.

  • Input:

  • Example:

    In this example, the widget would display the AVG sales for all the stores on screen:

import { FormulaWidget } from "@carto/react-widgets";
  
const customFormatter = (value) => `${value} $`;
  
return (
  <FormulaWidget
    id="averageRevenue"
    title="Average revenue"
    dataSource="storesSourceId"
    column="revenue"
    operation={AggregationTypes.AVG}
    formatter={customFormatter}
    onError={console.error}
  />
);

Available beginning with v1.3

You can also make calculations on widgets using multiple columns. For instance, if you are working with a dataset that contains revenue data disaggregated by year: revenue_2021 and revenue_2022, you can use an array in column and sum the values from both columns using the joinOperation property.

import { FormulaWidget } from "@carto/react-widgets";
  
const customFormatter = (value) => `${value} $`;
  
return (
  <FormulaWidget
    id="averageRevenue"
    title="Average revenue"
    dataSource="storesSourceId"
    column={["revenue_2021", "revenue_2022"]}
    joinOperation={AggregationTypes.SUM}
    operation={AggregationTypes.AVG}
    formatter={customFormatter}
    onError={console.error}
  />
);

GeocoderWidget

Renders a <GeocoderWidget /> component

  • Input:

  • Example:

    In this example, the widget is using the geocoder CSS custom style class and also defines a custom error handler:

import { GeocoderWidget } from "@carto/react-widgets";
  
const useStyles = makeStyles((theme) => ({
  geocoder: {
    position: 'absolute',
    top: theme.spacing(4),
    left: theme.spacing(4),
    zIndex: 1,
  }
}));

const onGeocoderWidgetError = (error) => {
  dispatch(setError(`Geocoding error: ${error.message}`));
};

return (
  <GeocoderWidget className={classes.geocoder} onError={onGeocoderWidgetError} />
);

HistogramWidget

Renders a <HistogramWidget /> component, binded to a source at redux.

  • Input:

  • Example:

    In this example, the widget would display the number of stores in different ranks, based on their number of sales.

import { HistogramWidget } from "@carto/react-widgets";
  
const customFormatter = (value) => `${value} $`;
  
return (
  <HistogramWidget
    id="storesByNumberOfSales"
    title="Stores by number of sales"
    dataSource="storesSourceId"
    operation={AggregationTypes.COUNT}
    column="salesNumber"
    ticks={[10, 100, 500, 1000]}
    onError={console.error}
  />
);
// bins for the histogram would be <10, 10 to 100, 100 to 500, 500 to 1000 and > 1000

LegendWidget

Renders a <LegendWidget /> component. The widget can display a switch to show or hide a layer and a legend for the layer. The legend representation depends on the legend type. You can check the available LEGEND_TYPES here. The widget accesses the layer information from the store and add the legend for those layers where it has been specified.

  • Input:

You can control the legend options through the following properties that must be added to the layerAttributes property for the layer in the store:

  • Example:

    If you want to show a legend for a layer, you need to do the following:

    1. Define some layer attributes (layerConfig) before you instantiate the layer. Here we are going to create a LEGEND_TYPES.BINS type legend where we are assigning colors and labels to the different legend elements. We use the same colors in the CARTO for deck.gl colorBins helper when creating the layer.

    2. When data is loaded for the layer, we add the legend information from the layerConfig object to the layer attributes in the Redux store by dispatching the updateLayer action. It is important that we call the original onDataLoad handler defined in the useCartoLayerProps hook for the other widgets in the app to work.

import { LEGEND_TYPES } from "@carto/react-ui";
import { updateLayer } from "@carto/react-redux";
import { CartoLayer, colorBins } from "@deck.gl/carto";
  
export const COLORS = [
  [247, 254, 174],
  [183, 230, 165],
  [124, 203, 162],
  [70, 174, 160],
  [4, 82, 117],
];
  
export const LABELS = [
  '$100M',
  '$500M',
  '$1B',
  '$1.5B',
];
  
const layerConfig = {
  title: 'Layer Name',
  visible: true,
  showOpacityControl: true,
  opacity: 0.6,
  legend: {
    attr: 'revenue',
    type: LEGEND_TYPES.BINS,
    labels: LABELS,
    colors: COLORS,
  },
};
  
const { myLayer } = useSelector((state) => state.carto.layers);
const source = useSelector((state) => selectSourceById(state, myLayer?.source));
const cartoLayerProps = useCartoLayerProps({ source, layerConfig: myLayer });

if (myLayer && source) {
  return new CartoLayer({
    ...cartoLayerProps,
    getFillColor: colorBins({
      attr: layerConfig.legend.attr,
      domain: [100e6, 500e6, 1e9, 1.5e9],
      colors: COLORS,
    }),
    onDataLoad: (data) => {
      dispatch(
        updateLayer({
          id: MY_LAYER_ID,
          layerAttributes: { ...layerConfig },
        })
      );
      cartoLayerProps.onDataLoad && cartoLayerProps.onDataLoad(data);
    }
  });
}

Now you can add the LegendWidget component. If you are using the CARTO for React templates, you can add it to the MapContainer component so it is shown for all views or add it to a particular view to show it in the sidebar just for that view. In this example, the widget uses a custom CSS class.

import { LegendWidget } from "@carto/react-widgets";
  
return (
  <LegendWidget className={myCSSClassName} />
);

If you just want layer switching functionality for a layer (show/hide) but you don’t want to add a legend, you can just create an empty object for the legend:

const layerConfig = {
  title: 'Layer Name',
  visible: true,
  legend: {},
};

PieWidget

Renders a <PieWidget /> component, binded to a source at redux. From a data perspective, PieWidget would present a behaviour equivalent to CategoryWidget (groups or ‘categories’ from a column, with an aggregated calculation), but with a different UI.

  • Input:

  • Example:

    In this example, the widget would display a pie chart by continent with the SUM of population for all the countries in that continent:

import { PieWidget } from "@carto/react-widgets";
  
return (
  <PieWidget
    id="populationByContinent"
    title="Population by continent"
    dataSource="countriesSourceId"
    column="continent"
    operationColumn="population"
    operation={AggregationTypes.SUM}
  />
);
  
// The operationColumn wouldn't be required if using AggregationTypes.COUNT, to count the number of countries per continent

Available beginning with v1.3

You can also make calculations on widgets using multiple columns. For instance, if you are working with a dataset that contains population data disaggregated by gender: population_m and population_f, you can use an array in operationColumn and sum the values from both columns using the joinOperation property.

import { PieWidget } from "@carto/react-widgets";
  
return (
  <PieWidget
    id="populationByContinent"
    title="Population by continent"
    dataSource="countriesSourceId"
    column="continent"
    operationColumn={["population_m", "population_f"]}
    joinOperation={AggregationTypes.SUM}
    operation={AggregationTypes.SUM}
  />
);

RangeWidget

Renders a <RangeWidget /> component, binded to a source at redux.

  • Input

  • Example:

    In this example, the widget would display the min/max values from the revenue column.

import { RangeWidget } from "@carto/react-widgets";
  
return (
  <RangeWidget
    id="revenueRange"
    title="Revenue"
    dataSource="storesSourceId"
    column="revenue"
  />
);

ScatterPlotWidget

Renders a <ScatterPlotWidget /> component, binded to a source at redux. The widget displays the calculations considering just the viewport features. From a data perspective, the ScatterPlotWidget represents two properties/columns in a cartesian chart from a data source to help understand if there is correlation between them.

  • Input:

  • Example:

    In this example, the widget would display the values from the size and revenue columns.

import { ScatterPlotWidget } from "@carto/react-widgets";
  
return (
  <ScatterPlotWidget
    id="sizeRevenueCorrelation"
    title="Size / Revenue"
    dataSource="storesSourceId"
    xAxisColumn="size"
    yAxisColumn="revenue"
  />
);

TableWidget

Renders a <TableWidget /> component, binded to a source at redux. The widget allows to configure the source columns that will be displayed. It includes functionality for data pagination and ordering by column.

  • Input:

  • Example:

    In this example, the widget would display a table with three columns for each store. All the columns are renamed and aligned to the left. The initial page size is set to 5 rows.

import { TableWidget } from "@carto/react-widgets";

return (
  <TableWidget
    id='storesTable'
    title='Stores list'
    dataSource={storesSource.id}
    initialPageSize={5}
    columns={[
      { field: 'revenue', headerName: 'Revenue', align: 'left' },
      { field: 'size_m2', headerName: 'Size (m2)', align: 'left' },
      { field: 'storetype', headerName: 'Type', align: 'left' },
    ]}
  />
);

TimeSeriesWidget

Renders a <TimeSeriesWidget /> component, binded to a source at redux. From a data perspective, the TimeSeriesWidget groups the features in time intervals and allows to play an animation that filters the features displayed based on the current interval.

  • Input:

  • Example:

    In this example, the widget will display the count of features in each time interval defined by the event_date column.

import { TimeSeriesWidget } from "@carto/react-widgets";
import { GroupDateTypes } from "@carto/react-core";
  
return (
  <TimeSeriesWidget
    id="events"
    title="Events per day"
    dataSource="eventsSourceId"
    column="event_date"
    stepSize={GroupDateTypes.DAYS}
  />
);

Last updated