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:

  1. At the top level - Affects both which data is included and how values are calculated

  2. 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?