> For the complete documentation index, see [llms.txt](https://developers.felt.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developers.felt.com/js-sdk/building-custom-charts.md).

# 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.

Statistics are available for layers that have been uploaded to and processed by Felt — layers created at runtime with [`createLayersFromGeoJson`](/js-sdk/working-with-layers.md) cannot be queried for statistics.

## Data analysis methods

The SDK offers three complementary approaches to analyze your map data:

### 1. Aggregates: single statistics

Calculate one or more values (count, sum, average, and so on) across your dataset or a filtered subset. `aggregation.methods` is required and takes a list, so you can compute several values in one call. `attribute` must be a numeric attribute, and can be omitted when the only method is `"count"`. The result is an object keyed by method; a value is `null` when no features match.

```javascript
// Count all residential buildings
const residentialCount = await felt.getAggregates({
    layerId: "buildings",
    filters: ["type", "eq", "residential"],
    aggregation: { methods: ["count"] }
});
// returns { count: 427 }

// Average and maximum home value in a specific neighborhood
const homeValues = await felt.getAggregates({
    layerId: "buildings",
    boundary: [-122.43, 47.60, -122.33, 47.62], // neighborhood boundary
    aggregation: {
        methods: ["avg", "max"],
        attribute: "assessed_value"
    }
});
// returns { avg: 652850.32, max: 2450000 }
```

### 2. Categories: group by values

Group features by unique attribute values and calculate a statistic for each group. Each group comes back as `{ key, value }`: `key` is the attribute value and `value` is the count (or the aggregation you ask for in `values`).

```javascript
// Basic grouping: Count of buildings by type
const buildingsByType = await felt.getCategoryData({
    layerId: "buildings",
    attribute: "type"
});
/* returns:
[
  { key: "residential", value: 427 },
  { key: "commercial", value: 82 },
  { key: "mixed-use", value: 38 },
  { key: "industrial", value: 15 }
]
*/
```

### 3. Histograms: group by numeric ranges

Create bins for numeric data and calculate a statistic for each range. Each bin comes back as `{ min, max, value }`. `steps` is either an array of break points, `{ type: "equal-intervals", count }`, or, for date attributes, `{ type: "time-interval", interval }`.

```javascript
// Basic histogram: building heights in 5 equal-width bins
const buildingHeights = await felt.getHistogramData({
    layerId: "buildings",
    attribute: "height",
    steps: { type: "equal-intervals", count: 5 }
});
/* returns:
[
  { min: 0, max: 100, value: 520 },
  { min: 100, max: 200, value: 36 },
  { min: 200, max: 300, value: 4 },
  { min: 300, max: 400, value: 1 },
  { min: 400, max: 500, value: 1 }
]
*/
```

## 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)**

```javascript
// 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", "ge", 2000],
        aggregation: {
            method: "sum",
            attribute: "floor_area"
        }
    }
});
/* returns:
[
  { key: "residential", value: 1250000 },
  { key: "commercial", value: 750000 },
  { key: "mixed-use", value: 350000 },
  { key: "industrial", value: 120000 }
]
*/
```

**Comparing building heights across time periods (Histograms)**

```javascript
// 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, value: 96 },
  { min: 20, max: 50, value: 104 },
  { min: 50, max: 100, value: 37 },
  { min: 100, max: 200, value: 12 },
  { min: 200, max: 500, value: 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", "ge", 1950]
    }
});
/* returns:
[
  { min: 0, max: 20, value: 79 },
  { min: 20, max: 50, value: 99 },
  { min: 50, max: 100, value: 105 },
  { min: 100, max: 200, value: 24 },
  { min: 200, max: 500, value: 5 }
]
*/
```

**Comparing neighborhood density (Aggregates)**

```javascript
// 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: {
        methods: ["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: {
        methods: ["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:

```javascript
// 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: "value",
        labelPath: "key",
        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: {
            methods: ["avg"],
            attribute: "year_built"
        }
    });
    
    // Update the UI with these statistics
    updateStatsPanel(`Average ${buildingType} year built: ${Math.round(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.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://developers.felt.com/js-sdk/building-custom-charts.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
