Chart Configuration Manager

This example demonstrates a dynamic chart system with configuration management using Chart.js and the BuzzyFrameAPI for data persistence.

Key Features

  • Interactive Chart.js integration with dynamic data visualization

  • Configuration persistence storing chart settings as child data

  • Real-time chart updates when configurations change

  • Multiple chart configurations with version management

  • Chart destruction and recreation for smooth transitions

  • Sample data generation for demonstration purposes

Use Cases

Perfect for applications requiring:

  • Business intelligence dashboards

  • Data analytics platforms

  • Reporting systems with customizable charts

  • Configuration management interfaces

  • A/B testing for chart presentations

  • Historical chart configuration tracking

Implementation Overview

This widget uses the Child Table Data pattern to store chart configurations as JSON objects. Key features include:

  • Chart.js library integration for professional visualizations

  • Configuration versioning and management

  • Real-time chart updates without page refresh

  • Clean chart lifecycle management (destroy/recreate)

  • Sample data for immediate demonstration

Code Example

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Chart Configuration Manager</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 appInitData = null;
      let appConfigs = [];

      async function initApp() {
        appInitData = await buzzyFrameAPI.initialise();
        await fetchAndUpdateConfigs();

        buzzyFrameAPI.addMicroappListener({
          microAppID: appInitData.rowJSON._id,
          listener: async () => {
            console.log('Config data updated, refreshing');
            await fetchAndUpdateConfigs();
          },
        });
      }

      async function fetchAndUpdateConfigs() {
        try {
          const { rowJSON = {} } = appInitData || {};
          const children = await buzzyFrameAPI.getChildItemsByField({
            appID: rowJSON._id,
            fieldID: rowJSON.fieldID,
          });

          appConfigs = children;

          if (window.updateConfigs) {
            window.updateConfigs(appConfigs);
          }
        } catch (error) {
          console.error('Error fetching configs:', error);
          if (window.updateConfigs) {
            window.updateConfigs([]);
          }
        }
      }

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

        useEffect(() => {
          window.updateConfigs = newConfigs => {
            setConfigs(newConfigs);
            setLoading(false);
          };

          if (appConfigs.length > 0) {
            setConfigs(appConfigs);
            setLoading(false);
          }

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

        useEffect(() => {
          if (configs.length > 0 && canvasRef.current) {
            const latestConfig = configs[configs.length - 1].content;

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

            const ctx = canvasRef.current.getContext('2d');
            chartRef.current = new Chart(ctx, {
              type: latestConfig.type || 'bar',
              data: latestConfig.data || {},
              options: latestConfig.options || {},
            });
          }
        }, [configs]);

        const handleSaveConfig = async () => {
          try {
            const { rowJSON } = appInitData;

            const newConfig = {
              type: 'bar',
              data: {
                labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
                datasets: [
                  {
                    label: 'Sample Data',
                    data: [12, 19, 3, 5, 2, 3],
                    backgroundColor: [
                      'rgba(255, 99, 132, 0.2)',
                      'rgba(54, 162, 235, 0.2)',
                      'rgba(255, 206, 86, 0.2)',
                      'rgba(75, 192, 192, 0.2)',
                      'rgba(153, 102, 255, 0.2)',
                      'rgba(255, 159, 64, 0.2)',
                    ],
                    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,
                  },
                ],
              },
              options: {
                scales: {
                  y: {
                    beginAtZero: true,
                  },
                },
              },
            };

            await buzzyFrameAPI.createMicroappChild({
              microAppResourceID: rowJSON.parentResourceID,
              appID: rowJSON._id,
              fieldID: rowJSON.fieldID,
              content: newConfig,
            });
          } catch (error) {
            console.error('Error saving config:', error);
          }
        };

        const handleDeleteConfig = async childID => {
          if (!confirm('Are you sure you want to delete this configuration?'))
            return;

          try {
            await buzzyFrameAPI.removeMicroappChild({ childID });
          } catch (error) {
            console.error('Error deleting config:', error);
          }
        };

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

        return (
          <div className="container">
            <div className="box">
              <canvas ref={canvasRef}></canvas>
            </div>

            <div className="box">
              <h3 className="title is-4 mb-4">Chart Configurations</h3>
              <button
                className="button is-primary mb-4"
                onClick={handleSaveConfig}>
                <span className="icon">
                  <i className="fas fa-save"></i>
                </span>
                <span>Save Current Config</span>
              </button>

              <div className="content">
                {configs.map((config, index) => (
                  <div key={config._id} className="level is-mobile">
                    <div className="level-left">
                      <div className="level-item">
                        <span className="has-text-weight-medium">
                          Config {index + 1}
                        </span>
                      </div>
                    </div>
                    <div className="level-right">
                      <div className="level-item">
                        <button
                          className="button is-danger is-small"
                          onClick={() => handleDeleteConfig(config._id)}>
                          <span className="icon is-small">
                            <i className="fas fa-trash"></i>
                          </span>
                          <span>Delete</span>
                        </button>
                      </div>
                    </div>
                  </div>
                ))}
              </div>
            </div>
          </div>
        );
      }

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

Key Concepts

Chart.js Integration

  • External Library: Demonstrates how to integrate popular JavaScript libraries

  • Chart Lifecycle: Proper chart destruction and recreation for smooth updates

  • Canvas Management: Direct canvas manipulation with React refs

  • Configuration Objects: Complete Chart.js configuration stored as JSON

Configuration Management

  • Persistence: Chart configurations stored as child data for long-term access

  • Versioning: Multiple configurations can be saved and managed

  • Real-time Updates: Charts update immediately when configurations change

  • JSON Storage: Complex configuration objects stored efficiently

Data Visualization Patterns

  • Sample Data: Includes sample datasets for immediate demonstration

  • Color Schemes: Professional color palettes for visual appeal

  • Responsive Design: Charts adapt to container size automatically

  • Interactive Elements: Hover effects and tooltips built into Chart.js

Advanced Features

  • Memory Management: Proper cleanup of chart instances to prevent memory leaks

  • Error Handling: Graceful handling of configuration errors

  • User Feedback: Loading states and confirmation dialogs

  • Extensibility: Easy to extend with additional chart types and options

This example showcases how code widgets can integrate sophisticated charting libraries while maintaining data persistence and real-time updates, making it perfect for business intelligence and analytics applications.

Last updated