Building custom charts
The Felt SDK provides powerful methods to analyze your geospatial data and transform it into informative visualizations. You can calculate statistics on entire datasets or focus on specific areas using boundaries and filters, allowing you to create custom charts that reveal insights about your spatial data.
Data analysis methods
The SDK offers three complementary approaches to analyze your map data:
1. Aggregates: single statistics
Calculate individual values (count, sum, average, etc.) across your dataset or a filtered subset. If no aggregation method is provided, the count is returned.
// Count all residential buildings
const residentialCount = await felt.getAggregates({
layerId: "buildings",
filters: ["type", "eq", "residential"]
});
// returns { count: 427 }
// Calculate average home value in a specific neighborhood
const avgHomeValue = await felt.getAggregates({
layerId: "buildings",
boundary: [-122.43, 47.60, -122.33, 47.62], // neighborhood boundary
aggregation: {
method: "avg",
attribute: "assessed_value"
}
});
// returns { avg: 652850.32 }
2. Categories: group by values
Group features by unique attribute values and calculate statistics for each group.
// Basic grouping: Count of buildings by type
const buildingsByType = await felt.getCategoryData({
layerId: "buildings",
attribute: "type"
});
/* returns:
[
{ value: "residential", count: 427 },
{ value: "commercial", count: 82 },
{ value: "mixed-use", count: 38 },
{ value: "industrial", count: 15 }
]
*/
3. Histograms: group by numeric ranges
Create bins for numeric data and calculate statistics for each range.
// Basic histogram: Building heights in 5 natural break bins
const buildingHeights = await felt.getHistogramData({
layerId: "buildings",
attribute: "height",
steps: { type: "jenks", count: 5 }
});
/* returns:
[
{ min: 0, max: 20, count: 175 },
{ min: 20, max: 50, count: 203 },
{ min: 50, max: 100, count: 142 },
{ min: 100, max: 200, count: 36 },
{ min: 200, max: 500, count: 6 }
]
*/
Working with filters
You can apply filters in two powerful ways:
At the top level - Affects both which data is included and how values are calculated
In the values configuration - Only affects the calculated values while keeping all categories/bins
This two-level filtering is especially useful for creating comparative visualizations while maintaining consistent groupings.
Advanced filtering examples
Comparing building types by floor area (Categories)
// Advanced: Show all building types, but only sum floor area of recent buildings
const recentBuildingAreaByType = await felt.getCategoryData({
layerId: "buildings",
attribute: "type",
values: {
filters: ["year_built", "gte", 2000],
aggregation: {
method: "sum",
attribute: "floor_area"
}
}
});
/* returns:
[
{ value: "residential", sum: 1250000 },
{ value: "commercial", sum: 750000 },
{ value: "mixed-use", sum: 350000 },
{ value: "industrial", sum: 120000 }
]
*/
Comparing building heights across time periods (Histograms)
// Compare old vs new buildings using the same height ranges
const oldBuildingHeights = await felt.getHistogramData({
layerId: "buildings",
attribute: "height",
steps: [0, 20, 50, 100, 200, 500],
values: {
filters: ["year_built", "lt", 1950]
}
});
/* returns:
[
{ min: 0, max: 20, count: 96 },
{ min: 20, max: 50, count: 104 },
{ min: 50, max: 100, count: 37 },
{ min: 100, max: 200, count: 12 },
{ min: 200, max: 500, count: 1 }
]
*/
const newBuildingHeights = await felt.getHistogramData({
layerId: "buildings",
attribute: "height",
steps: [0, 20, 50, 100, 200, 500], // Same ranges as above
values: {
filters: ["year_built", "gte", 1950]
}
});
/* returns:
[
{ min: 0, max: 20, count: 79 },
{ min: 20, max: 50, count: 99 },
{ min: 50, max: 100, count: 105 },
{ min: 100, max: 200, count: 24 },
{ min: 200, max: 500, count: 5 }
]
*/
Comparing neighborhood density (Aggregates)
// Find average residential density across different neighborhoods
const downtownDensity = await felt.getAggregates({
layerId: "buildings",
boundary: [-122.335, 47.600, -122.330, 47.610], // downtown boundary
filters: ["type", "eq", "residential"],
aggregation: {
method: "avg",
attribute: "units_per_acre"
}
});
// returns { avg: 124.7 }
const suburbanDensity = await felt.getAggregates({
layerId: "buildings",
boundary: [-122.200, 47.650, -122.150, 47.700], // suburban boundary
filters: ["type", "eq", "residential"],
aggregation: {
method: "avg",
attribute: "units_per_acre"
}
});
// returns { avg: 8.2 }
Interactive visualization example
Here's how you might integrate these analysis methods with an interactive chart:
// Create a pie chart showing building type distribution
async function createBuildingTypePieChart() {
// Get data for the chart
const data = await felt.getCategoryData({
layerId: "buildings",
attribute: "type"
});
// Render pie chart (using a hypothetical chart library)
const chart = renderPieChart(data, {
valuePath: "count",
labelPath: "value",
onSliceClick: handleSliceClick
});
return chart;
}
// Handle user interaction with the chart
async function handleSliceClick(slice) {
const buildingType = slice.label;
// Apply filter to highlight this building type on the map
await felt.setLayerFilters({
layerId: "buildings",
filters: ["type", "eq", buildingType],
note: `Showing ${buildingType} buildings only`
});
// Get additional statistics for this building type
const stats = await felt.getAggregates({
layerId: "buildings",
filters: ["type", "eq", buildingType],
aggregation: {
method: "avg",
attribute: "year_built"
}
});
// Update the UI with these statistics
updateStatsPanel(`Average ${buildingType} building age: ${2025 - stats.avg}`);
}
// Initialize the chart when the page loads
createBuildingTypePieChart();
This example demonstrates how a user clicking on a pie chart slice could apply a filter to the map, highlighting only the buildings of that type. It also shows how you could fetch additional statistics based on the user's selection to enrich the visualization experience.
Last updated
Was this helpful?