Building Interactive Dashboards
Create dynamic dashboards powered by Infactory queries
Building Interactive Dashboards with Infactory
Infactory enables you to create powerful, data-driven dashboards that provide live insights into your data. Unlike traditional dashboards, Infactory-powered dashboards can respond to natural language queries and deliver consistent, reliable results at database speed.
Why Infactory for Dashboards?
Traditional dashboard solutions have several limitations:
Limited Interactivity
Fixed visualizations with predefined filter options
Development Overhead
Requires SQL expertise and significant development time
Maintenance Burden
Updating requires technical resources and code changes
Scalability Issues
Performance degrades with complex queries or large datasets
Infactory dashboards offer significant advantages:
- Natural Language Interface: Users can ask questions in plain English
- Dynamic Visualizations: Auto-generate appropriate visualizations based on data type
- Consistent Performance: Execute at database speed regardless of complexity
- Lower Development Cost: Reduce development time with ready-to-use queries
- Flexible Integration: Embed in any web application or framework
Dashboard Architecture Overview
Define your queries
Create and deploy the queries that will power your dashboard.
Design dashboard layout
Design your dashboard layout with components for each insight you want to display.
Connect components to Infactory
Each dashboard component connects to a specific Infactory query or accepts natural language questions.
Implement visualizations
Create appropriate visualizations for each data type using a charting library.
Add interactive filters
Implement filters that modify query parameters for all or selected components.
Implementation Approaches
Fixed Query Dashboards
The simplest implementation directly maps dashboard components to specific Infactory queries:
import { useState, useEffect } from 'react';
import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend } from 'chart.js';
import { Bar } from 'react-chartjs-2';
// Register Chart.js components
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);
function SalesByRegionChart() {
const [chartData, setChartData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
try {
// Call your backend API that interfaces with Infactory
const response = await fetch('/api/dashboard/sales-by-region');
if (!response.ok) {
throw new Error('Failed to fetch chart data');
}
const data = await response.json();
// Format data for Chart.js
const formattedData = {
labels: data.data.map(item => item.region),
datasets: [
{
label: 'Sales',
data: data.data.map(item => item.sales),
backgroundColor: 'rgba(53, 162, 235, 0.5)',
},
],
};
setChartData(formattedData);
} catch (error) {
console.error('Error fetching chart data:', error);
setError('Failed to load chart data');
} finally {
setIsLoading(false);
}
}
fetchData();
}, []);
const chartOptions = {
responsive: true,
plugins: {
legend: {
position: 'top',
},
title: {
display: true,
text: 'Sales by Region',
},
},
};
if (isLoading) return <div className="loading">Loading chart data...</div>;
if (error) return <div className="error">{error}</div>;
if (!chartData) return <div className="no-data">No data available</div>;
return (
<div className="chart-container">
<Bar options={chartOptions} data={chartData} />
</div>
);
}
// Dashboard component combining multiple charts
function Dashboard() {
return (
<div className="dashboard">
<h1>Sales Performance Dashboard</h1>
<div className="dashboard-grid">
<div className="dashboard-item">
<SalesByRegionChart />
</div>
{/* Add more dashboard items/charts here */}
</div>
</div>
);
}
import { useState, useEffect } from 'react';
import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend } from 'chart.js';
import { Bar } from 'react-chartjs-2';
// Register Chart.js components
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);
function SalesByRegionChart() {
const [chartData, setChartData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
try {
// Call your backend API that interfaces with Infactory
const response = await fetch('/api/dashboard/sales-by-region');
if (!response.ok) {
throw new Error('Failed to fetch chart data');
}
const data = await response.json();
// Format data for Chart.js
const formattedData = {
labels: data.data.map(item => item.region),
datasets: [
{
label: 'Sales',
data: data.data.map(item => item.sales),
backgroundColor: 'rgba(53, 162, 235, 0.5)',
},
],
};
setChartData(formattedData);
} catch (error) {
console.error('Error fetching chart data:', error);
setError('Failed to load chart data');
} finally {
setIsLoading(false);
}
}
fetchData();
}, []);
const chartOptions = {
responsive: true,
plugins: {
legend: {
position: 'top',
},
title: {
display: true,
text: 'Sales by Region',
},
},
};
if (isLoading) return <div className="loading">Loading chart data...</div>;
if (error) return <div className="error">{error}</div>;
if (!chartData) return <div className="no-data">No data available</div>;
return (
<div className="chart-container">
<Bar options={chartOptions} data={chartData} />
</div>
);
}
// Dashboard component combining multiple charts
function Dashboard() {
return (
<div className="dashboard">
<h1>Sales Performance Dashboard</h1>
<div className="dashboard-grid">
<div className="dashboard-item">
<SalesByRegionChart />
</div>
{/* Add more dashboard items/charts here */}
</div>
</div>
);
}
<template>
<div class="chart-container">
<div v-if="isLoading" class="loading">Loading chart data...</div>
<div v-else-if="error" class="error">{{ error }}</div>
<Bar v-else :options="chartOptions" :data="chartData" />
</div>
</template>
<script>
import { defineComponent, ref, onMounted } from 'vue';
import { Bar } from 'vue-chartjs';
import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend } from 'chart.js';
// Register Chart.js components
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);
export default defineComponent({
name: 'SalesByRegionChart',
components: {
Bar
},
setup() {
const chartData = ref(null);
const isLoading = ref(true);
const error = ref(null);
const chartOptions = {
responsive: true,
plugins: {
legend: {
position: 'top',
},
title: {
display: true,
text: 'Sales by Region',
},
},
};
onMounted(async () => {
try {
// Call your backend API that interfaces with Infactory
const response = await fetch('/api/dashboard/sales-by-region');
if (!response.ok) {
throw new Error('Failed to fetch chart data');
}
const data = await response.json();
// Format data for Chart.js
chartData.value = {
labels: data.data.map(item => item.region),
datasets: [
{
label: 'Sales',
data: data.data.map(item => item.sales),
backgroundColor: 'rgba(53, 162, 235, 0.5)',
},
],
};
} catch (e) {
console.error('Error fetching chart data:', e);
error.value = 'Failed to load chart data';
} finally {
isLoading.value = false;
}
});
return {
chartData,
chartOptions,
isLoading,
error
};
}
});
</script>
Backend Implementation
Your backend needs to proxy requests to Infactory:
// Node.js/Express backend
const express = require('express');
const axios = require('axios');
const router = express.Router();
// Environment variables
const INFACTORY_API_KEY = process.env.INFACTORY_API_KEY;
const INFACTORY_PROJECT_ID = process.env.INFACTORY_PROJECT_ID;
// Endpoint for sales by region chart
router.get('/api/dashboard/sales-by-region', async (req, res) => {
try {
// Call Infactory's direct query endpoint
const response = await axios.post(
'https://api.infactory.ai/v1/queries/sales_by_region',
{}, // No parameters needed for this example
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${INFACTORY_API_KEY}`
}
}
);
res.json(response.data);
} catch (error) {
console.error('Error fetching sales by region:', error.response?.data || error.message);
res.status(500).json({
error: 'Failed to fetch dashboard data'
});
}
});
// Endpoint for product performance chart
router.get('/api/dashboard/product-performance', async (req, res) => {
try {
// Call Infactory's direct query endpoint
const response = await axios.post(
'https://api.infactory.ai/v1/queries/product_performance',
{}, // No parameters needed for this example
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${INFACTORY_API_KEY}`
}
}
);
res.json(response.data);
} catch (error) {
console.error('Error fetching product performance:', error.response?.data || error.message);
res.status(500).json({
error: 'Failed to fetch dashboard data'
});
}
});
module.exports = router;
Natural Language Dashboard
For a more flexible experience, create a dashboard that responds to natural language questions:
import { useState } from 'react';
import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, PointElement, LineElement, ArcElement, Title, Tooltip, Legend } from 'chart.js';
import { Bar, Line, Pie } from 'react-chartjs-2';
// Register Chart.js components
ChartJS.register(CategoryScale, LinearScale, BarElement, PointElement, LineElement, ArcElement, Title, Tooltip, Legend);
function NLDashboard() {
const [query, setQuery] = useState('');
const [results, setResults] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// Function to determine appropriate chart type based on data
function determineChartType(data) {
if (!data || !Array.isArray(data) || data.length === 0) return null;
const firstItem = data[0];
const keys = Object.keys(firstItem);
// Time series data - use line chart
if (keys.includes('date') || keys.includes('timestamp') || keys.some(k => k.includes('date') || k.includes('time'))) {
return 'line';
}
// If we have a few categories and a numeric value, use a bar chart
if (data.length <= 10 && keys.length === 2 && typeof firstItem[keys[1]] === 'number') {
return 'bar';
}
// Very few categories - pie chart could work
if (data.length <= 5) {
return 'pie';
}
// Default to bar chart
return 'bar';
}
// Function to prepare chart data
function prepareChartData(data, chartType) {
if (!data || data.length === 0) return null;
const firstItem = data[0];
const keys = Object.keys(firstItem);
// Determine category and value keys
let categoryKey = keys[0];
let valueKey = keys.find(key => typeof firstItem[key] === 'number');
if (!valueKey) return null;
// Colors for charts
const backgroundColor = [
'rgba(53, 162, 235, 0.5)',
'rgba(255, 99, 132, 0.5)',
'rgba(75, 192, 192, 0.5)',
'rgba(255, 205, 86, 0.5)',
'rgba(153, 102, 255, 0.5)',
'rgba(255, 159, 64, 0.5)'
];
const borderColor = backgroundColor.map(color => color.replace('0.5', '1'));
// Format data based on chart type
if (chartType === 'pie') {
return {
labels: data.map(item => item[categoryKey]),
datasets: [
{
data: data.map(item => item[valueKey]),
backgroundColor,
borderColor,
borderWidth: 1
}
]
};
}
return {
labels: data.map(item => item[categoryKey]),
datasets: [
{
label: valueKey,
data: data.map(item => item[valueKey]),
backgroundColor: backgroundColor[0],
borderColor: borderColor[0],
borderWidth: 1
}
]
};
}
async function handleSubmit(e) {
e.preventDefault();
if (!query.trim()) return;
setLoading(true);
setError(null);
try {
const response = await fetch('/api/nl-dashboard', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ question: query })
});
if (!response.ok) {
throw new Error('Failed to query data');
}
const data = await response.json();
if (!data.data || data.data.length === 0) {
setError('No data available for this query');
setResults(null);
} else {
setResults(data);
}
} catch (err) {
console.error('Error querying data:', err);
setError('Failed to process your query. Please try again.');
setResults(null);
} finally {
setLoading(false);
}
}
// Render appropriate chart based on data
function renderChart() {
if (!results || !results.data) return null;
const chartType = determineChartType(results.data);
const chartData = prepareChartData(results.data, chartType);
if (!chartData) return <div>Unable to visualize this data</div>;
const chartOptions = {
responsive: true,
plugins: {
legend: {
position: 'top',
},
title: {
display: true,
text: query,
},
},
};
switch (chartType) {
case 'bar':
return <Bar data={chartData} options={chartOptions} />;
case 'line':
return <Line data={chartData} options={chartOptions} />;
case 'pie':
return <Pie data={chartData} options={chartOptions} />;
default:
return <div>Unsupported chart type</div>;
}
}
return (
<div className="nl-dashboard">
<h1>Interactive Data Dashboard</h1>
<p className="description">
Ask any question about your data to generate visualizations
</p>
<form onSubmit={handleSubmit} className="query-form">
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="e.g., 'What are the sales by region?' or 'Show me monthly revenue trends'"
disabled={loading}
className="query-input"
/>
<button type="submit" disabled={loading} className="query-button">
{loading ? 'Loading...' : 'Visualize'}
</button>
</form>
<div className="results-container">
{loading && <div className="loading">Loading data visualization...</div>}
{error && <div className="error">{error}</div>}
{results && (
<div className="visualization">
<div className="chart-wrapper">
{renderChart()}
</div>
<div className="query-details">
<p>Query used: <code>{results.query_used}</code></p>
<p>Parameters: <code>{JSON.stringify(results.parameters)}</code></p>
<p>Execution time: <code>{results.execution_time_ms}ms</code></p>
</div>
</div>
)}
</div>
</div>
);
}
import { useState } from 'react';
import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, PointElement, LineElement, ArcElement, Title, Tooltip, Legend } from 'chart.js';
import { Bar, Line, Pie } from 'react-chartjs-2';
// Register Chart.js components
ChartJS.register(CategoryScale, LinearScale, BarElement, PointElement, LineElement, ArcElement, Title, Tooltip, Legend);
function NLDashboard() {
const [query, setQuery] = useState('');
const [results, setResults] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// Function to determine appropriate chart type based on data
function determineChartType(data) {
if (!data || !Array.isArray(data) || data.length === 0) return null;
const firstItem = data[0];
const keys = Object.keys(firstItem);
// Time series data - use line chart
if (keys.includes('date') || keys.includes('timestamp') || keys.some(k => k.includes('date') || k.includes('time'))) {
return 'line';
}
// If we have a few categories and a numeric value, use a bar chart
if (data.length <= 10 && keys.length === 2 && typeof firstItem[keys[1]] === 'number') {
return 'bar';
}
// Very few categories - pie chart could work
if (data.length <= 5) {
return 'pie';
}
// Default to bar chart
return 'bar';
}
// Function to prepare chart data
function prepareChartData(data, chartType) {
if (!data || data.length === 0) return null;
const firstItem = data[0];
const keys = Object.keys(firstItem);
// Determine category and value keys
let categoryKey = keys[0];
let valueKey = keys.find(key => typeof firstItem[key] === 'number');
if (!valueKey) return null;
// Colors for charts
const backgroundColor = [
'rgba(53, 162, 235, 0.5)',
'rgba(255, 99, 132, 0.5)',
'rgba(75, 192, 192, 0.5)',
'rgba(255, 205, 86, 0.5)',
'rgba(153, 102, 255, 0.5)',
'rgba(255, 159, 64, 0.5)'
];
const borderColor = backgroundColor.map(color => color.replace('0.5', '1'));
// Format data based on chart type
if (chartType === 'pie') {
return {
labels: data.map(item => item[categoryKey]),
datasets: [
{
data: data.map(item => item[valueKey]),
backgroundColor,
borderColor,
borderWidth: 1
}
]
};
}
return {
labels: data.map(item => item[categoryKey]),
datasets: [
{
label: valueKey,
data: data.map(item => item[valueKey]),
backgroundColor: backgroundColor[0],
borderColor: borderColor[0],
borderWidth: 1
}
]
};
}
async function handleSubmit(e) {
e.preventDefault();
if (!query.trim()) return;
setLoading(true);
setError(null);
try {
const response = await fetch('/api/nl-dashboard', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ question: query })
});
if (!response.ok) {
throw new Error('Failed to query data');
}
const data = await response.json();
if (!data.data || data.data.length === 0) {
setError('No data available for this query');
setResults(null);
} else {
setResults(data);
}
} catch (err) {
console.error('Error querying data:', err);
setError('Failed to process your query. Please try again.');
setResults(null);
} finally {
setLoading(false);
}
}
// Render appropriate chart based on data
function renderChart() {
if (!results || !results.data) return null;
const chartType = determineChartType(results.data);
const chartData = prepareChartData(results.data, chartType);
if (!chartData) return <div>Unable to visualize this data</div>;
const chartOptions = {
responsive: true,
plugins: {
legend: {
position: 'top',
},
title: {
display: true,
text: query,
},
},
};
switch (chartType) {
case 'bar':
return <Bar data={chartData} options={chartOptions} />;
case 'line':
return <Line data={chartData} options={chartOptions} />;
case 'pie':
return <Pie data={chartData} options={chartOptions} />;
default:
return <div>Unsupported chart type</div>;
}
}
return (
<div className="nl-dashboard">
<h1>Interactive Data Dashboard</h1>
<p className="description">
Ask any question about your data to generate visualizations
</p>
<form onSubmit={handleSubmit} className="query-form">
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="e.g., 'What are the sales by region?' or 'Show me monthly revenue trends'"
disabled={loading}
className="query-input"
/>
<button type="submit" disabled={loading} className="query-button">
{loading ? 'Loading...' : 'Visualize'}
</button>
</form>
<div className="results-container">
{loading && <div className="loading">Loading data visualization...</div>}
{error && <div className="error">{error}</div>}
{results && (
<div className="visualization">
<div className="chart-wrapper">
{renderChart()}
</div>
<div className="query-details">
<p>Query used: <code>{results.query_used}</code></p>
<p>Parameters: <code>{JSON.stringify(results.parameters)}</code></p>
<p>Execution time: <code>{results.execution_time_ms}ms</code></p>
</div>
</div>
)}
</div>
</div>
);
}
Interactive Filtering
Add interactive filters to your dashboard to allow users to explore different data segments:
function FilterableDashboard() {
const [filters, setFilters] = useState({
region: 'all',
timeRange: 'month',
productCategory: 'all'
});
// Update single filter
const updateFilter = (name, value) => {
setFilters(prev => ({
...prev,
[name]: value
}));
};
// Reset all filters
const resetFilters = () => {
setFilters({
region: 'all',
timeRange: 'month',
productCategory: 'all'
});
};
return (
<div className="dashboard">
<div className="filter-panel">
<h3>Dashboard Filters</h3>
<div className="filter-group">
<label htmlFor="region">Region:</label>
<select
id="region"
value={filters.region}
onChange={(e) => updateFilter('region', e.target.value)}
>
<option value="all">All Regions</option>
<option value="north">North</option>
<option value="south">South</option>
<option value="east">East</option>
<option value="west">West</option>
</select>
</div>
<div className="filter-group">
<label htmlFor="timeRange">Time Range:</label>
<select
id="timeRange"
value={filters.timeRange}
onChange={(e) => updateFilter('timeRange', e.target.value)}
>
<option value="day">Last Day</option>
<option value="week">Last Week</option>
<option value="month">Last Month</option>
<option value="quarter">Last Quarter</option>
<option value="year">Last Year</option>
</select>
</div>
<div className="filter-group">
<label htmlFor="productCategory">Product Category:</label>
<select
id="productCategory"
value={filters.productCategory}
onChange={(e) => updateFilter('productCategory', e.target.value)}
>
<option value="all">All Categories</option>
<option value="electronics">Electronics</option>
<option value="clothing">Clothing</option>
<option value="home">Home & Garden</option>
<option value="sports">Sports & Outdoors</option>
</select>
</div>
<button onClick={resetFilters} className="reset-filters-btn">
Reset Filters
</button>
</div>
<div className="dashboard-content">
{/* Pass filters to each chart component */}
<SalesByRegionChart filters={filters} />
<RevenueByTimeChart filters={filters} />
<TopProductsChart filters={filters} />
</div>
</div>
);
}
// Example of a chart component that uses filters
function SalesByRegionChart({ filters }) {
const [chartData, setChartData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
// This effect runs when filters change
async function fetchFilteredData() {
setIsLoading(true);
try {
// Convert filter values to parameters for the API
const params = {
region: filters.region !== 'all' ? filters.region : undefined,
time_range: filters.timeRange,
product_category: filters.productCategory !== 'all' ? filters.productCategory : undefined
};
// Call backend with filters
const response = await fetch('/api/dashboard/sales-by-region', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(params)
});
if (!response.ok) {
throw new Error('Failed to fetch chart data');
}
const data = await response.json();
// Format data for Chart.js
setChartData({
labels: data.data.map(item => item.region),
datasets: [
{
label: 'Sales',
data: data.data.map(item => item.sales),
backgroundColor: 'rgba(53, 162, 235, 0.5)',
},
],
});
} catch (error) {
console.error('Error fetching filtered data:', error);
} finally {
setIsLoading(false);
}
}
fetchFilteredData();
}, [filters]); // Re-fetch when filters change
// Render chart with loading state
if (isLoading) return <div className="loading-chart">Loading...</div>;
if (!chartData) return <div className="no-data">No data available</div>;
return (
<div className="chart-container">
<h3>Sales by Region</h3>
<Bar options={chartOptions} data={chartData} />
</div>
);
}
Advanced Dashboard Features
Dashboard Data Refresh
Implement automatic data refresh to keep dashboards up-to-date:
function AutoRefreshingChart({ refreshInterval = 60000 }) {
const [chartData, setChartData] = useState(null);
const [lastUpdated, setLastUpdated] = useState(null);
useEffect(() => {
// Function to fetch and update data
async function fetchData() {
try {
const response = await fetch('/api/dashboard/sales-by-region');
if (!response.ok) throw new Error('Failed to fetch data');
const data = await response.json();
setChartData(formatChartData(data));
setLastUpdated(new Date());
} catch (error) {
console.error('Error refreshing data:', error);
}
}
// Fetch initial data
fetchData();
// Set up refresh interval
const intervalId = setInterval(fetchData, refreshInterval);
// Clean up interval on component unmount
return () => clearInterval(intervalId);
}, [refreshInterval]);
return (
<div className="auto-refreshing-chart">
<div className="chart-header">
<h3>Sales by Region</h3>
{lastUpdated && (
<div className="last-updated">
Last updated: {lastUpdated.toLocaleTimeString()}
</div>
)}
</div>
{chartData ? (
<Bar data={chartData} options={chartOptions} />
) : (
<div className="loading">Loading chart data...</div>
)}
</div>
);
}
Export and Sharing
Add export and sharing capabilities to your dashboard:
function ExportableChart({ chartData, chartOptions, title }) {
const chartRef = useRef(null);
// Function to export chart as image
const exportAsImage = () => {
const canvas = chartRef.current.canvas;
const image = canvas.toDataURL('image/png');
// Create download link
const link = document.createElement('a');
link.download = `${title.replace(/\s+/g, '-').toLowerCase()}.png`;
link.href = image;
link.click();
};
// Function to export data as CSV
const exportAsCSV = () => {
if (!chartData || !chartData.datasets || !chartData.labels) return;
// Convert chart data to CSV format
const headers = ['Category', chartData.datasets[0].label];
const rows = chartData.labels.map((label, index) => [
label,
chartData.datasets[0].data[index]
]);
const csvContent = [
headers.join(','),
...rows.map(row => row.join(','))
].join('\n');
// Create download link
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.setAttribute('href', url);
link.setAttribute('download', `${title.replace(/\s+/g, '-').toLowerCase()}.csv`);
link.click();
};
return (
<div className="exportable-chart">
<div className="chart-actions">
<button onClick={exportAsImage} className="export-btn">
<i className="fas fa-download"></i> Export as Image
</button>
<button onClick={exportAsCSV} className="export-btn">
<i className="fas fa-file-csv"></i> Export as CSV
</button>
</div>
<div className="chart-container">
<h3>{title}</h3>
<Bar ref={chartRef} data={chartData} options={chartOptions} />
</div>
</div>
);
}
Dashboard Layouts
Implement responsive dashboard layouts with drag-and-drop capabilities:
import { Responsive, WidthProvider } from 'react-grid-layout';
import 'react-grid-layout/css/styles.css';
import 'react-resizable/css/styles.css';
const ResponsiveGridLayout = WidthProvider(Responsive);
function DraggableDashboard() {
// Define layouts for different screen sizes
const layouts = {
lg: [
{ i: "sales-by-region", x: 0, y: 0, w: 6, h: 2 },
{ i: "revenue-trend", x: 6, y: 0, w: 6, h: 2 },
{ i: "top-products", x: 0, y: 2, w: 4, h: 2 },
{ i: "customer-segments", x: 4, y: 2, w: 4, h: 2 },
{ i: "sales-funnel", x: 8, y: 2, w: 4, h: 2 }
],
md: [
{ i: "sales-by-region", x: 0, y: 0, w: 6, h: 2 },
{ i: "revenue-trend", x: 6, y: 0, w: 6, h: 2 },
{ i: "top-products", x: 0, y: 2, w: 4, h: 2 },
{ i: "customer-segments", x: 4, y: 2, w: 4, h: 2 },
{ i: "sales-funnel", x: 0, y: 4, w: 8, h: 2 }
],
sm: [
{ i: "sales-by-region", x: 0, y: 0, w: 6, h: 2 },
{ i: "revenue-trend", x: 0, y: 2, w: 6, h: 2 },
{ i: "top-products", x: 0, y: 4, w: 3, h: 2 },
{ i: "customer-segments", x: 3, y: 4, w: 3, h: 2 },
{ i: "sales-funnel", x: 0, y: 6, w: 6, h: 2 }
]
};
const [currentLayouts, setCurrentLayouts] = useState(layouts);
// Save layout changes
const handleLayoutChange = (layout, layouts) => {
setCurrentLayouts(layouts);
// You might want to save this to localStorage or your backend
localStorage.setItem('dashboard-layouts', JSON.stringify(layouts));
};
// Render charts based on their IDs
const renderChart = (id) => {
switch (id) {
case 'sales-by-region':
return <SalesByRegionChart />;
case 'revenue-trend':
return <RevenueTrendChart />;
case 'top-products':
return <TopProductsChart />;
case 'customer-segments':
return <CustomerSegmentsChart />;
case 'sales-funnel':
return <SalesFunnelChart />;
default:
return <div>Unknown chart: {id}</div>;
}
};
return (
<div className="draggable-dashboard">
<h1>Sales Performance Dashboard</h1>
<p className="dashboard-description">
Drag and resize dashboard components to customize your view
</p>
<ResponsiveGridLayout
className="layout"
layouts={currentLayouts}
breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
cols={{ lg: 12, md: 12, sm: 6, xs: 4, xxs: 2 }}
rowHeight={200}
onLayoutChange={handleLayoutChange}
isDraggable
isResizable
>
<div key="sales-by-region" className="dashboard-item">
{renderChart('sales-by-region')}
</div>
<div key="revenue-trend" className="dashboard-item">
{renderChart('revenue-trend')}
</div>
<div key="top-products" className="dashboard-item">
{renderChart('top-products')}
</div>
<div key="customer-segments" className="dashboard-item">
{renderChart('customer-segments')}
</div>
<div key="sales-funnel" className="dashboard-item">
{renderChart('sales-funnel')}
</div>
</ResponsiveGridLayout>
</div>
);
}
Deployment Best Practices
Optimize API Calls
Use caching for frequently accessed dashboard data to reduce API calls
Error Handling
Implement robust error handling for each dashboard component
Loading States
Show clear loading states and placeholders while data is being fetched
Performance Monitoring
Monitor dashboard performance to identify bottlenecks
Mobile Optimization
Ensure your dashboard is responsive and usable on all device sizes
Batch Requests
Batch multiple data requests to reduce network overhead
Example Dashboard Use Cases
Executive Dashboards
High-level KPI dashboards for business executives
Sales Performance
Track sales metrics, conversion rates, and revenue trends
Customer Analytics
Analyze customer behavior, segments, and lifetime value
Marketing Campaign Tracking
Monitor campaign performance and attribution
Operational Metrics
Track operational efficiency and resource utilization
Financial Analytics
Visualize financial data, expenses, and budget tracking
Next Steps
After building your dashboard, consider:
- Adding user authentication for internal dashboards
- Creating custom query templates for specialized visualizations
- Implementing performance optimizations for faster loading times
- Exploring chatbot integration to add conversational capabilities to your dashboard
Was this page helpful?
- Building Interactive Dashboards with Infactory
- Why Infactory for Dashboards?
- Dashboard Architecture Overview
- Implementation Approaches
- Fixed Query Dashboards
- Backend Implementation
- Natural Language Dashboard
- Interactive Filtering
- Advanced Dashboard Features
- Dashboard Data Refresh
- Export and Sharing
- Dashboard Layouts
- Deployment Best Practices
- Example Dashboard Use Cases
- Next Steps