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:

  1. Natural Language Interface: Users can ask questions in plain English
  2. Dynamic Visualizations: Auto-generate appropriate visualizations based on data type
  3. Consistent Performance: Execute at database speed regardless of complexity
  4. Lower Development Cost: Reduce development time with ready-to-use queries
  5. Flexible Integration: Embed in any web application or framework

Dashboard Architecture Overview

1

Define your queries

Create and deploy the queries that will power your dashboard.

2

Design dashboard layout

Design your dashboard layout with components for each insight you want to display.

3

Connect components to Infactory

Each dashboard component connects to a specific Infactory query or accepts natural language questions.

4

Implement visualizations

Create appropriate visualizations for each data type using a charting library.

5

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>
  );
}

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>
  );
}

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: