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