Fruit Distribution Chart

This example demonstrates real-time data visualization using Chart.js with automatic updates when underlying data changes.

Key Features

  • Real-time data visualization with automatic chart updates

  • Pie chart with percentages showing data distribution

  • Dynamic data fetching from related datatables

  • Professional styling with custom colors and tooltips

  • Responsive design that adapts to container size

  • Error handling with graceful fallbacks

Use Cases

Ideal for applications requiring:

  • Real-time dashboards and analytics

  • Inventory tracking and distribution

  • Survey results visualization

  • Sales performance monitoring

  • Resource allocation displays

  • Any scenario requiring live data charts

Implementation Overview

This widget uses the Other Tables Data pattern, fetching data from a separate "Fruit Counters" datatable. Key implementation features:

  • Automatic chart updates when source data changes

  • Professional Chart.js pie chart with custom styling

  • Percentage calculations in tooltips

  • Dynamic color assignment for visual appeal

  • Responsive design principles

Code Example

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Fruit Distribution Chart</title>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <script
      src="https://cdn.jsdelivr.net/npm/[email protected]/umd/react.production.min.js"
      crossorigin
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/[email protected]/umd/react-dom.production.min.js"
      crossorigin
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/@babel/standalone/babel.min.js"
      crossorigin
    ></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
  </head>
  <body>
    <div id="root"></div>
    <script type="text/babel">
      const buzzyFrameAPI = new BuzzyFrameAPI();
      let appData = [];
      let dataTableID = null;

      async function initApp() {
        const initData = await buzzyFrameAPI.initialise();

        const { resourceJSON } = initData || {};
        const dataTable = resourceJSON?.children?.find(
          child => child.title === 'Fruit Counters',
        );
        dataTableID = dataTable?._id;

        if (dataTableID) {
          await fetchAndUpdateData();

          buzzyFrameAPI.addMicroappListener({
            microAppID: dataTableID,
            listener: async () => {
              console.log('Data updated, refreshing chart');
              await fetchAndUpdateData();
            },
          });
        }
      }

      async function fetchAndUpdateData() {
        try {
          if (dataTableID) {
            const fruitTableRowData = await buzzyFrameAPI.fetchDataTableRows({
              microAppID: dataTableID,
            });
            appData = fruitTableRowData;

            if (window.updateChart) {
              window.updateChart(appData);
            }
          }
        } catch (error) {
          console.error('Error fetching data:', error);
          if (window.updateChart) {
            window.updateChart([]);
          }
        }
      }

      function App() {
        const { useEffect, useState, useRef } = React;
        const [data, setData] = useState([]);
        const [loading, setLoading] = useState(true);
        const chartRef = useRef(null);
        const canvasRef = useRef(null);

        useEffect(() => {
          window.updateChart = newData => {
            setData(newData);
            setLoading(false);
          };

          if (appData.length > 0) {
            setData(appData);
            setLoading(false);
          }

          return () => {
            window.updateChart = null;
          };
        }, []);

        useEffect(() => {
          if (data.length > 0 && canvasRef.current) {
            const labels = data.map(fruit => fruit['Fruit Type']);
            const counts = data.map(fruit => fruit['Count']);

            const chartData = {
              labels,
              datasets: [
                {
                  data: counts,
                  backgroundColor: [
                    'rgba(255, 99, 132, 0.8)',
                    'rgba(54, 162, 235, 0.8)',
                    'rgba(255, 206, 86, 0.8)',
                    'rgba(75, 192, 192, 0.8)',
                    'rgba(153, 102, 255, 0.8)',
                    'rgba(255, 159, 64, 0.8)',
                  ],
                  borderColor: [
                    'rgba(255, 99, 132, 1)',
                    'rgba(54, 162, 235, 1)',
                    'rgba(255, 206, 86, 1)',
                    'rgba(75, 192, 192, 1)',
                    'rgba(153, 102, 255, 1)',
                    'rgba(255, 159, 64, 1)',
                  ],
                  borderWidth: 1,
                },
              ],
            };

            if (chartRef.current) {
              chartRef.current.destroy();
            }

            const ctx = canvasRef.current.getContext('2d');
            chartRef.current = new Chart(ctx, {
              type: 'pie',
              data: chartData,
              options: {
                responsive: true,
                plugins: {
                  legend: {
                    position: 'top',
                  },
                  tooltip: {
                    callbacks: {
                      label: function (context) {
                        const label = context.label || '';
                        const value = context.raw || 0;
                        const total = context.dataset.data.reduce(
                          (a, b) => a + b,
                          0,
                        );
                        const percentage = ((value / total) * 100).toFixed(1);
                        return label + ': ' + value + ' (' + percentage + '%)';
                      },
                    },
                  },
                },
              },
            });
          }
        }, [data]);

        if (loading)
          return <div className="has-text-centered p-4">Loading...</div>;

        return (
          <div className="container">
            <div className="box has-text-centered">
              <h3 className="title is-4 mb-4">Fruit Distribution</h3>
              <canvas ref={canvasRef}></canvas>
            </div>
          </div>
        );
      }

      initApp().then(() => {
        ReactDOM.render(<App />, document.getElementById('root'));
      });
    </script>
  </body>
</html>

Key Concepts

Real-time Data Integration

  • Dynamic Data Source: Fetches data from "Fruit Counters" datatable by name

  • Automatic Updates: Chart refreshes immediately when source data changes

  • Error Handling: Graceful fallback when data source is unavailable

  • Resource Discovery: Finds datatables by title from resourceJSON

Chart.js Pie Chart Features

  • Percentage Tooltips: Custom tooltip showing both count and percentage

  • Professional Colors: Carefully selected color palette for visual appeal

  • Responsive Design: Chart adapts to container size automatically

  • Legend Positioning: Top-positioned legend for optimal layout

Data Processing

  • Field Mapping: Maps "Fruit Type" and "Count" fields to chart data

  • Dynamic Labels: Chart labels generated from actual data

  • Color Assignment: Automatic color assignment for up to 6 categories

  • Total Calculations: Real-time percentage calculations in tooltips

Performance Optimization

  • Chart Lifecycle: Proper chart destruction and recreation for updates

  • Memory Management: Prevents memory leaks with proper cleanup

  • Efficient Updates: Only updates when data actually changes

  • Loading States: User-friendly loading indicators

This example demonstrates how code widgets can create professional data visualizations that update in real-time, perfect for dashboards and analytics applications where data freshness is critical.

Last updated