Interactive Filtering & Aggregation
Real data exploration requires interactivity. Users need to slice data by dimensions, filter to subsets, and see aggregated results update in real-time. This lesson covers building responsive filter controls.
The Filtering Pattern
Interactive dashboards follow this pattern:
- User selects filters → Category toggles, range sliders, dropdowns
- Data is filtered → JavaScript filters the dataset
- Aggregations recalculate → Totals, averages, counts update
- Charts redraw → Visualizations reflect filtered data
- Context is shown → "Showing 1,250 of 5,000 orders"
Try It: Data Explorer Dashboard
Build an interactive exploration of QuickBite's order data. Toggle categories, adjust filters, and watch everything update:
Building Filter Controls
Toggle Buttons (Multi-Select)
Perfect for categories where users can select multiple options:
// Track selected categories
let selectedCategories = new Set(['Pizza', 'Burgers', 'Chinese']);
// Toggle handler
button.addEventListener('click', () => {
const category = button.dataset.category;
if (selectedCategories.has(category)) {
selectedCategories.delete(category);
button.classList.remove('active');
} else {
selectedCategories.add(category);
button.classList.add('active');
}
applyFilters();
});
Range Sliders
Great for numeric ranges like ratings or prices:
<input type="range" id="rating" min="1" max="5" step="0.5" value="1">
document.getElementById('rating').addEventListener('input', (e) => {
minRating = parseFloat(e.target.value);
applyFilters();
});
Dropdowns
Best for mutually exclusive options:
<select id="orderValue">
<option value="all">All Values</option>
<option value="low">Under $25</option>
<option value="high">Over $50</option>
</select>
The Filter Function
Apply all filters to your data array:
function applyFilters() {
const filtered = allOrders.filter(order => {
// Category filter
if (!selectedCategories.has(order.category)) return false;
// Rating filter
if (order.rating < minRating) return false;
// Value filter
if (valueFilter === 'low' && order.value >= 25) return false;
if (valueFilter === 'high' && order.value < 50) return false;
return true; // Passed all filters
});
updateStats(filtered);
updateChart(filtered);
}
Aggregation Patterns
After filtering, aggregate data for visualization:
// Group by category
const byCategoryfiltered.reduce((acc, order) => {
if (!acc[order.category]) {
acc[order.category] = { count: 0, revenue: 0 };
}
acc[order.category].count++;
acc[order.category].revenue += order.value;
return acc;
}, {});
// Calculate averages
const avgRating = filtered.reduce((sum, o) => sum + o.rating, 0) / filtered.length;
Showing Context
Always help users understand the filtered view:
Counts and Percentages
"Showing 1,250 orders (23% of total)"
Comparison to Overall
"Avg rating: 4.5 (+0.3 vs overall)"
Active Filters Summary
"Filters: Pizza, Burgers | Rating 4.0+ | Over $25"
Best Practices
1. Immediate Feedback
Update visualizations instantly as filters change - no "Apply" button needed.
2. Show Empty States
When filters return no results, show a clear message with reset option.
3. Persist Filter State
Save filters to URL or localStorage so users can share or return.
4. Provide Reset
Always offer a way to clear all filters and return to full data.
5. Performance
For large datasets, debounce filter updates:
let filterTimeout;
function debouncedFilter() {
clearTimeout(filterTimeout);
filterTimeout = setTimeout(applyFilters, 150);
}
Key Takeaways
- Filter controls let users explore data subsets interactively
- Show updated aggregations immediately when filters change
- Provide context: counts, percentages, comparisons to overall
- Always include a reset button for quick return to full data
- Debounce rapid filter changes for performance
Next, we'll bring everything together to build a complete food delivery dashboard!