EasyPieChart.js

Lightweight, Animated Circular Progress Indicators

EasyPieChart is a lightweight plugin to render simple, animated and retina-optimized pie charts. It uses the HTML5 canvas element for rendering and is designed to work well with percentages and visualize progress metrics in a compact, circular format.

These circular charts are perfect for dashboards, project progress tracking, and anywhere you need to show percentage-based information in a visually appealing way.

Pro Tip: EasyPieChart works seamlessly with SmartAdmin's color system and can be combined with other elements like counters, icons, or text to create rich, informative widgets.

Installation

EasyPieChart is included in the SmartAdmin theme. There are two ways to use it:

1. Automatic Implementation with Data Attributes

// The easypiechart.js file is already included in SmartAdmin
// and automatically initializes all charts with the 'js-easy-pie-chart' class

// Simply add elements with this class and configuration via data attributes:
<div class="js-easy-pie-chart" data-percent="75" data-piesize="100" data-linewidth="10">
    <span class="js-percent">0</span>%
</div>

// No JavaScript needed - charts initialize automatically on page load

2. Manual Implementation with Direct API

// Import the EasyPieChart constructor directly
import EasyPieChart from './thirdparty/easypiechart/easypiechart.es6.js';

document.addEventListener('DOMContentLoaded', function() {
    // Basic initialization
    const element = document.querySelector('.my-chart');
    new EasyPieChart(element, {
        barColor: 'var(--primary-500)',
        trackColor: 'var(--primary-200)',
        scaleColor: false,
        lineWidth: 20,
        size: 150,
        animate: 1000,
        onStep: function(from, to, percent) {
            this.el.querySelector('.percent').textContent = Math.round(percent);
        }
    });
    
    // You can update the chart programmatically
    // chart.update(65); // Updates to 65%
});

HTML Structure

<!-- For automatic initialization with easypiechart.js -->
<div class="js-easy-pie-chart" data-percent="75">
    <span class="js-percent">0</span>%
</div>

<!-- With data attributes for configuration -->
<div class="js-easy-pie-chart" 
    data-percent="65" 
    data-piesize="100"
    data-linewidth="10" 
    data-linecap="round">
    <span class="js-percent">0</span>%
</div>

<!-- For manual initialization with easypiechart.es6.js -->
<div class="my-chart" data-percent="33">
    <svg class="sa-icon">
        <use href="img/sprite.svg#star"></use>
    </svg>
    <div class="percent text-center mt-2">33</div>
</div>
When to use each approach:
  • Automatic Implementation: Best for typical use cases. Just add HTML with the proper class and data attributes - no JavaScript needed.
  • Manual Implementation: Use when you need more control over initialization timing, want to update charts dynamically, or need custom behavior.
Important:
  • The element must have a data-percent attribute with the percentage value to display.
  • For automatic initialization, use .js-percent for the percentage text to update.
  • For manual initialization, use whatever selector you reference in your onStep callback.
  • EasyPieChart uses the HTML5 Canvas element, so it's not supported in very old browsers.

Configuration Options

EasyPieChart offers various configuration options to customize the appearance and behavior of your charts:

Option Type Default Description
barColor String | Function '#ef1e25' The color of the progress bar. Can be a CSS color or a function that returns a color based on the current percentage.
trackColor String '#f2f2f2' The color of the track (background) of the progress bar.
scaleColor String | Boolean '#dfe0e0' The color of the scale lines. Set to false to disable scale lines.
scaleLength Number 5 Length of the scale lines (in pixels).
lineCap String 'round' Shape of the line cap: 'butt', 'round', or 'square'.
lineWidth Number 3 Width of the progress bar line (in pixels).
size Number 110 Size of the pie chart (in pixels).
rotate Number 0 Rotation of the chart in degrees (0-360).
animate Boolean | Object {duration: 1000, enabled: true} Animation configuration. Set to false to disable animation.
easing String | Function 'linear' Animation easing function: 'linear', 'easeInOutQuad', etc.
onStart Function null Callback function called at the start of animation.
onStep Function null Callback function called during each animation step. Receives the from, to, and current percentage values.
onStop Function null Callback function called at the end of animation.
Pro Tip: You can also set these options using HTML data attributes by prefixing the option name with data-. For example, data-bar-color, data-track-color, etc.

Usage Examples

Here are some common usage patterns for EasyPieChart:

Basic Usage

// Basic chart with default values
new EasyPieChart(document.querySelector('.chart-1'), {
    barColor: 'var(--primary-500)',
    trackColor: 'var(--primary-200)',
    scaleColor: false,
    lineWidth: 8,
    size: 100
});

Dynamic Color Based on Percentage

// Chart color changes based on percentage value
new EasyPieChart(document.querySelector('.chart-2'), {
    barColor: function(percent) {
        if (percent < 25) {
            return 'var(--danger-500)'; // Red for low percentages
        } else if (percent < 50) {
            return 'var(--warning-500)'; // Orange for medium-low
        } else if (percent < 75) {
            return 'var(--info-500)'; // Blue for medium-high
        } else {
            return 'var(--success-500)'; // Green for high percentages
        }
    },
    trackColor: 'var(--gray-300)',
    scaleColor: false,
    lineWidth: 10,
    size: 120
});

With Animation and Callback

// Chart with custom animation and callbacks
new EasyPieChart(document.querySelector('.chart-3'), {
    barColor: 'var(--info-500)',
    trackColor: 'var(--info-200)',
    scaleColor: false,
    lineWidth: 15,
    size: 150,
    animate: {
        duration: 2000,
        enabled: true
    },
    easing: 'easeOutBounce',
    onStart: function() {
        console.log('Animation started');
    },
    onStep: function(from, to, percent) {
        this.el.querySelector('.percent').textContent = Math.round(percent);
    },
    onStop: function() {
        console.log('Animation stopped');
    }
});

Updating Chart Values

// Create chart instance
const element = document.querySelector('.chart-4');
const chart = new EasyPieChart(element, {
    barColor: 'var(--success-500)',
    trackColor: 'var(--success-200)',
    scaleColor: false,
    lineWidth: 10,
    size: 120
});

// Update chart value (e.g., after user action or data refresh)
document.getElementById('update-btn').addEventListener('click', function() {
    // Generate random percentage
    const newValue = Math.floor(Math.random() * 100);
    
    // Update chart
    chart.update(newValue);
    
    // Update display text
    element.querySelector('.percent').textContent = newValue;
});

SmartAdmin Integration

Here are some tips for integrating EasyPieChart with SmartAdmin:

Using Color Variables

// Use SmartAdmin CSS variables for consistent colors
new EasyPieChart(element, {
    barColor: 'var(--primary-500)', // Primary theme color
    trackColor: 'var(--primary-200)', // Lighter shade of primary 
    scaleColor: false,
    lineWidth: 8,
    size: 100
});

Creating Panel-Based Widgets

<div class="panel">
    <div class="panel-hdr">
        <h2>Project <span class="fw-300"><i>Progress</i></span></h2>
        <div class="panel-toolbar">
            <button class="btn btn-panel" data-action="panel-collapse">
                <svg class="sa-icon">
                    <use href="img/sprite.svg#minus-circle"></use>
                </svg>
            </button>
        </div>
    </div>
    <div class="panel-container show">
        <div class="panel-content">
            <div class="row">
                <div class="col-md-4 text-center">
                    <div class="js-pie-chart mb-2" data-percent="65" data-bar-color="var(--primary-500)" data-track-color="var(--primary-200)" data-line-width="5" data-size="100">
                        <span class="percent"></span>%
                    </div>
                    <h5>Phase 1</h5>
                </div>
                <div class="col-md-4 text-center">
                    <div class="js-pie-chart mb-2" data-percent="42" data-bar-color="var(--success-500)" data-track-color="var(--success-200)" data-line-width="5" data-size="100">
                        <span class="percent"></span>%
                    </div>
                    <h5>Phase 2</h5>
                </div>
                <div class="col-md-4 text-center">
                    <div class="js-pie-chart mb-2" data-percent="10" data-bar-color="var(--info-500)" data-track-color="var(--info-200)" data-line-width="5" data-size="100">
                        <span class="percent"></span>%
                    </div>
                    <h5>Phase 3</h5>
                </div>
            </div>
        </div>
    </div>
</div>

Batch Initialization

// Initialize all pie charts on the page
document.addEventListener('DOMContentLoaded', function() {
    // Select all elements with the js-pie-chart class
    const elements = document.querySelectorAll('.js-pie-chart');
    
    // Initialize each chart with options from data attributes
    elements.forEach(function(element) {
        // Get options from data attributes
        const options = {
            barColor: element.dataset.barColor || 'var(--primary-500)',
            trackColor: element.dataset.trackColor || 'var(--primary-200)',
            scaleColor: element.dataset.scaleColor === 'false' ? false : element.dataset.scaleColor,
            lineWidth: parseInt(element.dataset.lineWidth || 3, 10),
            size: parseInt(element.dataset.size || 110, 10),
            animate: element.dataset.animate === 'false' ? false : parseInt(element.dataset.animate || 1000, 10),
            onStep: function(from, to, percent) {
                const percentElement = element.querySelector('.percent');
                if (percentElement) {
                    percentElement.textContent = Math.round(percent);
                }
            }
        };
        
        // Initialize the chart
        new EasyPieChart(element, options);
    });
});

Advanced Techniques

Custom Easing Functions

// Custom easing function
const customEasing = function(t, b, c, d) {
    // t: current time, b: beginning value, c: change in value, d: duration
    if ((t /= d / 2) < 1) return c / 2 * t * t * t + b;
    return c / 2 * ((t -= 2) * t * t + 2) + b;
};

// Use custom easing
new EasyPieChart(element, {
    barColor: 'var(--primary-500)',
    trackColor: 'var(--primary-200)',
    scaleColor: false,
    lineWidth: 10,
    size: 120,
    easing: customEasing
});

Responsive Size Adjustment

// Make charts responsive
function initResponsiveCharts() {
    const charts = [];
    
    // Initialize charts
    document.querySelectorAll('.js-pie-chart').forEach(function(element) {
        // Store original size
        element.dataset.originalSize = element.dataset.size || '110';
        
        // Initialize with current size
        const size = window.innerWidth < 768 ? 
            Math.floor(parseInt(element.dataset.originalSize, 10) * 0.7) : // Smaller size on mobile
            parseInt(element.dataset.originalSize, 10); // Original size on desktop
            
        const chart = new EasyPieChart(element, {
            barColor: element.dataset.barColor || 'var(--primary-500)',
            trackColor: element.dataset.trackColor || 'var(--primary-200)',
            scaleColor: false,
            lineWidth: parseInt(element.dataset.lineWidth || 3, 10),
            size: size,
            animate: 1000
        });
        
        // Store chart instance for later updates
        charts.push({ element: element, chart: chart });
    });
    
    // Update sizes on window resize
    window.addEventListener('resize', function() {
        charts.forEach(function(item) {
            // Recalculate size
            const size = window.innerWidth < 768 ? 
                Math.floor(parseInt(item.element.dataset.originalSize, 10) * 0.7) : 
                parseInt(item.element.dataset.originalSize, 10);
                
            // Update chart
            item.chart.options.size = size;
            item.chart.update(parseInt(item.element.dataset.percent, 10));
        });
    });
}

// Call initialization on DOMContentLoaded
document.addEventListener('DOMContentLoaded', initResponsiveCharts);

Animated Progress Updates

// Function to animate a chart from current value to target value
function animateProgress(chartElement, targetPercent, duration = 1500, steps = 30) {
    // Get chart instance
    const chart = chartElement.__chart;
    if (!chart) return;
    
    // Get current percent
    const currentPercent = parseInt(chartElement.dataset.percent, 10);
    
    // Calculate step size
    const stepSize = (targetPercent - currentPercent) / steps;
    const stepTime = duration / steps;
    
    // Current step counter
    let currentStep = 0;
    
    // Update function
    function updateStep() {
        currentStep++;
        
        // Calculate current value
        const newPercent = Math.round(currentPercent + (stepSize * currentStep));
        
        // Update chart
        chartElement.dataset.percent = newPercent;
        chart.update(newPercent);
        
        // Continue animation if not complete
        if (currentStep < steps) {
            setTimeout(updateStep, stepTime);
        }
    }
    
    // Start animation
    updateStep();
}

// Example usage
const progressChart = document.querySelector('.project-progress');
document.getElementById('complete-task').addEventListener('click', function() {
    animateProgress(progressChart, 75); // Animate to 75%
});

Troubleshooting

Common Issues:
  • Chart not rendering? Ensure the container element exists and has dimensions before initializing.
  • Percentage not updating during animation? Verify that you have included a .percent element and proper onStep callback.
  • Colors not applying? Check that CSS variables are correctly defined and accessible.
  • Canvas rendering issues? Ensure the browser supports HTML5 Canvas.

Debugging Tips

// Debug chart initialization
try {
    const element = document.querySelector('.chart');
    console.log('Chart element:', element);
    
    if (!element) {
        console.error('Chart element not found!');
        return;
    }
    
    // Log dimensions
    console.log('Element size:', element.offsetWidth, element.offsetHeight);
    
    // Check if data-percent exists
    console.log('Percent value:', element.dataset.percent);
    
    // Initialize with debug callback
    const chart = new EasyPieChart(element, {
        barColor: 'var(--primary-500)',
        trackColor: 'var(--primary-200)',
        scaleColor: false,
        lineWidth: 10,
        size: 100,
        onStart: function() {
            console.log('Animation started');
        },
        onStep: function(from, to, percent) {
            console.log('Step:', from, to, percent);
            const percentElement = element.querySelector('.percent');
            if (percentElement) {
                percentElement.textContent = Math.round(percent);
            } else {
                console.warn('No .percent element found inside chart container');
            }
        },
        onStop: function() {
            console.log('Animation completed');
        }
    });
} catch (error) {
    console.error('Chart initialization error:', error);
}

Handling Hidden Elements

Charts initialized in hidden elements (e.g., collapsed panels, inactive tabs) may not render properly. Initialize or update them when they become visible:

// Initialize charts in tabs when they become visible
document.querySelectorAll('[data-bs-toggle="tab"]').forEach(function(tab) {
    tab.addEventListener('shown.bs.tab', function(e) {
        // Get the newly activated tab content
        const tabContent = document.querySelector(e.target.getAttribute('href'));
        
        // Find and initialize/update charts
        tabContent.querySelectorAll('.js-pie-chart').forEach(function(element) {
            // If chart already initialized, update it
            if (element.__chart) {
                element.__chart.update(parseInt(element.dataset.percent, 10));
            } else {
                // Otherwise initialize new chart
                const chart = new EasyPieChart(element, {
                    barColor: element.dataset.barColor || 'var(--primary-500)',
                    trackColor: element.dataset.trackColor || 'var(--primary-200)',
                    scaleColor: false,
                    lineWidth: parseInt(element.dataset.lineWidth || 3, 10),
                    size: parseInt(element.dataset.size || 110, 10)
                });
                
                // Store reference to chart instance
                element.__chart = chart;
            }
        });
    });
});

Further Resources

For more advanced usage and detailed API documentation, refer to these resources: