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

spatial index widgets examples #31

Merged
merged 20 commits into from
Jan 3, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Simplify and polish H3 example
  • Loading branch information
srtena committed Dec 23, 2024
commit 8e1809787f0e37046aec766f46994d98b5632b34
12 changes: 8 additions & 4 deletions widgets-h3/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
## Example: Spatial Index H3
## Example: CARTO Widgets over H3 Spatial Index sources

This is a great example on how performant spatial indexes are to visualize and operate with large geospatial datasets. In this case we're using a dataset based in an hexagonal grid (H3), from our CARTO Data Observatory. This datasets includes demographic, financial, and environmental variables across the US. CARTO + deck.gl offer native support for spatial indexes. If you want to learn more about spatial indexes, we recommend you to check our [Spatial Indexes 101 guide](https://go.carto.com/report-spatial-indexes-101).
This is an evolution of our [H3 example](https://github.com/CartoDB/deck.gl-examples/tree/master/hello-world), adding charts and filtering capabilities.

It showcases how to use [Widget models in CARTO](https://docs.carto.com/carto-for-developers/charts-and-widgets) to easily build interactive data visualizations that stay synchronized with the map, with added interactions such as filtering with inputs or by clicking in the charts. And in this case, how to integrate them into spatial index sources, such as H3 and Quadbin, for optimal performance and scalability.

The UI for the charts is built using [Chart JS](https://www.chartjs.org/) but developers can plug their own charting or data visualization library.

Uses [Vite](https://vitejs.dev/) to bundle and serve files.

## Usage

[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/CartoDB/deck.gl-examples/tree/master/spatial-features-h3?file=index.ts)
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/CartoDB/deck.gl-examples/tree/master/widgets-h3?file=index.ts)

Or run it locally:

Expand All @@ -18,5 +22,5 @@ yarn

Commands:

- `npm dev` is the development target, to serve the app and hot reload.
- `npm run dev` is the development target, to serve the app and hot reload.
- `npm run build` is the production target, to create the final bundle and write to disk.
41 changes: 21 additions & 20 deletions widgets-h3/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,60 +2,61 @@
<html>
<head>
<meta charset="UTF-8" />
<title>CARTO + deck.gl - H3 Spatial Index Widgets</title>
<title>CARTO + deck.gl - H3 Widgets</title>
</head>
<body>
<div id="map"></div>
<canvas id="deck-canvas"></canvas>
<div id="top-left">
<div id="story-card">
<p class="overline">✨👀 You're viewing</p>
<h2>CARTO Widgets for H3 Spatial Index</h2>
<h2>CARTO Widgets for H3 Sources</h2>
<p>
More info about
This example showcases how to build widgets (charts and filters) into visualizations using
CARTO + deck.gl + H3 Spatial Index sources. Learn more about
<a
href=" https://carto.com/blog/carto-spatial-features-urbanity-climatology-elevation"
href="https://docs.carto.com/carto-for-developers/reference/carto-widgets-reference/"
rel="noopener noreferrer"
target="_blank"
>H3 Spatial Index</a
>widgets in CARTO.</a
>
</p>

<div class="layer-controls">
<p class="overline">Variable</p>
<select name="variable" id="variable" class="select"></select>
<p class="overline">Agreggation method applied</p>
<p><pre id="agg-method"></pre></p>
<p><small>More information about <a href="https://docs.carto.com/carto-for-developers/carto-for-react/guides/data-sources#spatial-indexes-and-custom-geometry-column-names">aggregationExp</a></small></p>
<div>
<img
class="legend"
src="./images/scale.jpg"
alt="legend"
/>
<img class="legend" src="./images/scale.jpg" alt="legend" />
<div class="label-container">
<div>Less</div>
<div>More</div>
</div>
</div>
</div>

<div class="widgets">
<div class="widget formula-widget">
<p class="overline">Total Population</p>
<p class="overline">Total</p>
<div id="formula-data"></div>
</div>
<div class="widget histogram-widget relative">
<button class="clear-btn">Clear filter</button>
<p class="overline">Urbanity categories</p>
<p class="">You can click the bars to filter the map with the selected category</p>
<p class="overline">Population by urbanity</p>
<p class="loader hidden">Loading...</p>
<canvas id="histogram-data" height="300px"></canvas>
<canvas id="histogram-data" height="200px"></canvas>
</div>
</div>
</div>
</div>
<p class="caption">Source: <a href="https://carto.com/spatial-data-catalog/browser/dataset/cdb_spatial_fea_94e6b1f/" rel="noopener noreferrer" target="_blank">Spatial Features - United States of America (H3 Resolution 8)</a></p>
<p class="caption">
Source:
<a
href="https://carto.com/spatial-data-catalog/browser/dataset/cdb_spatial_fea_94e6b1f/"
rel="noopener noreferrer"
target="_blank"
>Spatial Features - United States of America (H3 Resolution 8)</a
>
</p>
<script type="module" src="./index.ts"></script>
</body>
</html>
39 changes: 19 additions & 20 deletions widgets-h3/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ const cartoConfig = {

const INITIAL_VIEW_STATE: MapViewState = {
// Spain
latitude: 37.3753636,
longitude: -5.9962577,
zoom: 6,
pitch: 0,
bearing: 0,
latitude: 35.3753636,
longitude: -14.9962577,
zoom: 5.2,
pitch: 50,
bearing: 5,
minZoom: 3.5,
maxZoom: 15
};
Expand All @@ -46,7 +46,6 @@ const filters: Filters = {};

// DOM elements
const variableSelector = document.getElementById('variable') as HTMLSelectElement;
const aggMethodLabel = document.getElementById('agg-method') as HTMLSelectElement;
const formulaWidget = document.getElementById('formula-data') as HTMLDivElement;
const histogramWidget = document.getElementById('histogram-data') as HTMLCanvasElement;
const histogramClearBtn = document.querySelector(
Expand All @@ -59,13 +58,11 @@ histogramClearBtn.addEventListener('click', () => {

let histogramChart: Chart;

aggMethodLabel.innerText = aggregationExp;
variableSelector?.addEventListener('change', () => {
const aggMethod = variableSelector.selectedOptions[0].dataset.aggMethod || 'SUM';

selectedVariable = variableSelector.value;
aggregationExp = `${aggMethod}(${selectedVariable})`;
aggMethodLabel.innerText = aggregationExp;

render();
});
Expand Down Expand Up @@ -93,9 +90,9 @@ function renderLayers() {
new H3TileLayer({
id: 'h3_layer',
data: source,
opacity: 0.8,
opacity: 0.75,
pickable: true,
extruded: false,
extruded: true,
getFillColor: (...args) => {
const color = colorScale(...args);
const d = args[0];
Expand All @@ -105,12 +102,15 @@ function renderLayers() {
}
return [0, 0, 0, 255 * 0.25];
},
getElevation: (...args) => {
const d = args[0];
return d.properties.value;
},
coverage: 0.95,
elevationScale: 0.2,
lineWidthMinPixels: 0.5,
getLineWidth: 0.5,
getLineColor: [255, 255, 255, 100],
onClick: info => {
console.log(info.object);
}
getLineColor: [255, 255, 255, 100]
})
];

Expand Down Expand Up @@ -153,25 +153,24 @@ async function renderHistogram(ws: WidgetSource) {

const categories = await ws.getCategories({
column: 'urbanity',
operation: 'count',
operation: 'sum',
operationColumn: selectedVariable,
filterOwner: HISTOGRAM_WIDGET_ID,
spatialFilter: getSpatialFilterFromViewState(viewState),
viewState
});

categories.sort((a, b) => a.name.localeCompare(b.name));

histogramWidget.parentElement?.querySelector('.loader')?.classList.toggle('hidden', true);
histogramWidget.classList.toggle('hidden', false);

const selectedCategory = filters['urbanity']?.[FilterType.IN]?.values[0];
const colors = categories.map(c =>
c.name === selectedCategory ? 'rgba(255, 99, 132, 0.2)' : 'rgba(54, 162, 235, 0.2)'
c.name === selectedCategory ? 'rgba(255, 99, 132, 0.8)' : 'rgba(54, 162, 235, 0.75)'
);

if (histogramChart) {
histogramChart.data.labels = categories.map(c => c.name);
histogramChart.data.datasets[0].data = categories.map(c => c.value);
histogramChart.data.datasets[0].data = categories.map(c => Math.floor(c.value));
histogramChart.data.datasets[0].backgroundColor = colors;
histogramChart.update();
} else {
Expand All @@ -182,7 +181,7 @@ async function renderHistogram(ws: WidgetSource) {
datasets: [
{
label: 'Urbanity category',
data: categories.map(c => c.value),
data: categories.map(c => Math.floor(c.value)),
backgroundColor: colors
}
]
Expand Down
2 changes: 1 addition & 1 deletion widgets-h3/selectorUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export async function initSelectors() {
const variableSelector = document.getElementById('variable') as HTMLSelectElement;
const data = await getVariables();
const options = data
.filter((variable: Variable) => variable.db_type === 'FLOAT')
.filter((variable: Variable) => ['population', 'female', 'male'].includes(variable.column_name))
.map((variable: Variable) => {
return `<option value="${variable.column_name}" data-agg-method="${variable.agg_method}">${variable.name}</option>`;
});
Expand Down
8 changes: 4 additions & 4 deletions widgets-h3/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ html {
font-family: Inter, sans-serif;
font-weight: 400;
font-size: 1rem;
}

body {
line-height: 1.5;
margin: 0;
}

Expand Down Expand Up @@ -132,7 +130,9 @@ button:hover {
font-family: monospace;
}

.relative { position: relative; }
.relative {
position: relative;
}

.loader {
font-size: 14px;
Expand Down