home / skills / carlos-asg / tu-voz-en-ruta / chartjs-graphs

chartjs-graphs skill

/skills/chartjs-graphs

This skill helps you render interactive Chart.js graphs in Django templates by wiring data via data-* attributes and datalabels.

npx playbooks add skill carlos-asg/tu-voz-en-ruta --skill chartjs-graphs

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
10.3 KB
---
name: chartjs-graphs
description: >
  Chart.js integration patterns for creating interactive statistical graphs in Django templates.
  Covers HTML setup, data injection, and JavaScript chart rendering with datalabels plugin.
license: Apache-2.0
metadata:
  author: Carlos
  version: "1.0"
  scope: [root]
  auto_invoke:
    - "Creating Chart.js graphs in Django"
    - "Implementing statistical dashboards with charts"
allowed-tools: Read, Edit, Write, Glob, Grep, Bash
---

## Chart.js Setup

**CDN Includes (in template head):**

```html
{% block extra_css %}
<!-- Chart.js CDN -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<!-- Chart.js DataLabels Plugin (shows values on chart) -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chartjs-plugin-datalabels.min.js"></script>
{% endblock %}
```

## HTML Template Pattern

**Canvas element with data attributes:**

```html
<!-- Line Chart Example -->
<canvas id="surveysChart" data-timeline='{{ timeline_data_json|safe }}'></canvas>

<!-- Bar Chart Example -->
<canvas id="complaintsChart" data-complaints='{{ complaints_data_json|safe }}'></canvas>

<!-- Dynamic Chart from Loop -->
<canvas id="chart-{{ forloop.counter }}" 
        data-question="{{ question_text }}"
        data-type="{{ data.type }}"
        data-summary='{{ data.summary|safe }}'></canvas>
```

**Key Points:**
- Use `data-*` attributes to pass JSON from Django context
- Always use `|safe` filter for JSON data
- Unique `id` for each canvas (use `forloop.counter` in loops)

## JavaScript Chart Patterns

### 1. Line Chart (Time Series)

```javascript
document.addEventListener('DOMContentLoaded', function() {
    const canvas = document.getElementById('surveysChart');
    if (!canvas) return;
    
    // Get data from HTML data attribute
    const timelineDataRaw = canvas.getAttribute('data-timeline');
    const timelineData = JSON.parse(timelineDataRaw);
    
    // Validate data
    if (!timelineData || !timelineData.dates || timelineData.dates.length === 0) {
        console.warn('No data available');
        return;
    }
    
    // Create chart
    new Chart(canvas.getContext('2d'), {
        type: 'line',
        data: {
            labels: timelineData.dates,  // X-axis labels
            datasets: [{
                label: 'Survey Submissions',
                data: timelineData.counts,  // Y-axis values
                borderColor: 'rgba(52, 152, 219, 1)',
                backgroundColor: 'rgba(52, 152, 219, 0.1)',
                borderWidth: 3,
                fill: true,
                tension: 0.4,  // Line curve (0=straight, 0.4=smooth)
                pointRadius: 5,
                pointBackgroundColor: 'rgba(52, 152, 219, 1)',
            }]
        },
        options: {
            responsive: true,
            maintainAspectRatio: true,
            plugins: {
                legend: { display: false },
                datalabels: { display: false }  // Disable labels on line points
            },
            scales: {
                y: {
                    beginAtZero: true,
                    ticks: { stepSize: 1 }
                }
            }
        }
    });
});
```

### 2. Horizontal Bar Chart

```javascript
document.addEventListener('DOMContentLoaded', function() {
    const canvas = document.getElementById('complaintsChart');
    if (!canvas) return;
    
    const complaintsDataRaw = canvas.getAttribute('data-complaints');
    const complaintsData = JSON.parse(complaintsDataRaw);
    
    // Convert object to arrays
    const labels = Object.keys(complaintsData);    // ['Reason 1', 'Reason 2']
    const counts = Object.values(complaintsData);  // [5, 3]
    
    new Chart(canvas.getContext('2d'), {
        type: 'bar',
        data: {
            labels: labels,
            datasets: [{
                label: 'Number of Complaints',
                data: counts,
                backgroundColor: 'rgba(231, 76, 60, 0.7)',
                borderColor: 'rgba(231, 76, 60, 1)',
                borderWidth: 2,
                borderRadius: 6,
                maxBarThickness: 50,
            }]
        },
        options: {
            indexAxis: 'y',  // Horizontal bars
            responsive: true,
            plugins: {
                legend: { display: false },
                datalabels: {
                    display: true,
                    anchor: 'end',
                    align: 'end',
                    color: '#333',
                    font: { weight: 'bold', size: 12 },
                    formatter: (value) => value  // Show numeric value
                }
            },
            scales: {
                x: {
                    beginAtZero: true,
                    ticks: { stepSize: 1 }
                }
            }
        },
        plugins: [ChartDataLabels]  // Enable datalabels plugin
    });
});
```

### 3. Pie Chart

```javascript
document.addEventListener('DOMContentLoaded', function() {
    // Register datalabels plugin globally
    if (typeof Chart !== 'undefined' && typeof ChartDataLabels !== 'undefined') {
        Chart.register(ChartDataLabels);
    }
    
    const canvas = document.getElementById('pieChart');
    const summaryData = JSON.parse(canvas.dataset.summary);
    
    const labels = Object.keys(summaryData);
    const values = Object.values(summaryData);
    
    // Generate dynamic colors
    const colors = [
        'rgba(52, 152, 219, 0.7)',   // Blue
        'rgba(46, 204, 113, 0.7)',   // Green
        'rgba(241, 196, 15, 0.7)',   // Yellow
        'rgba(231, 76, 60, 0.7)',    // Red
        'rgba(155, 89, 182, 0.7)',   // Purple
    ];
    
    new Chart(canvas.getContext('2d'), {
        type: 'pie',
        data: {
            labels: labels,
            datasets: [{
                data: values,
                backgroundColor: colors,
                borderWidth: 2,
                borderColor: '#fff'
            }]
        },
        options: {
            responsive: true,
            plugins: {
                legend: {
                    position: 'bottom',
                    labels: { padding: 15, font: { size: 12 } }
                },
                datalabels: {
                    color: '#ffffff',
                    formatter: (value, ctx) => {
                        const total = ctx.dataset.data.reduce((a, b) => a + b, 0);
                        const pct = (value / total) * 100;
                        return `${pct.toFixed(1)}%`;
                    },
                    font: { weight: '600', size: 11 }
                }
            }
        }
    });
});
```

### 4. Vertical Bar Chart

```javascript
new Chart(canvas.getContext('2d'), {
    type: 'bar',
    data: {
        labels: labels,
        datasets: [{
            label: 'Number of Responses',
            data: values,
            backgroundColor: 'rgba(52, 152, 219, 0.7)',
        }]
    },
    options: {
        responsive: true,
        plugins: {
            legend: { display: false },
            datalabels: {
                anchor: 'end',
                align: 'end',
                color: '#000',
                formatter: (value) => value
            }
        },
        scales: {
            y: {
                beginAtZero: true,
                ticks: { stepSize: 1, precision: 0 }
            }
        }
    },
    plugins: [ChartDataLabels]
});
```

## Data Injection from Django

**In Django view (CBV):**

```python
import json
from django.views.generic import TemplateView

class DashboardView(TemplateView):
    template_name = "dashboard.html"
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        
        # Prepare data as dictionary
        timeline_data = {
            'dates': ['2024-01-01', '2024-01-02', '2024-01-03'],
            'counts': [5, 8, 12]
        }
        
        complaints_data = {
            'Bad Service': 10,
            'Late Arrival': 5,
            'Dirty Vehicle': 3
        }
        
        # Convert to JSON string
        context['timeline_data_json'] = json.dumps(timeline_data)
        context['complaints_data_json'] = json.dumps(complaints_data)
        
        return context
```

## Dynamic Color Palette

```javascript
const chartColors = {
    primary: '#3498db',
    success: '#2ecc71',
    warning: '#f39c12',
    danger: '#e74c3c',
    info: '#1abc9c',
    purple: '#9b59b6',
    pink: '#e91e63',
    orange: '#ff5722',
};

const colorPalette = Object.values(chartColors);

// Use in chart
backgroundColor: labels.map((_, i) => colorPalette[i % colorPalette.length])
```

## Best Practices

**ALWAYS:**
- ✅ Wrap code in `DOMContentLoaded` event listener
- ✅ Check if canvas exists before creating chart
- ✅ Validate parsed JSON data before using
- ✅ Use `try-catch` when parsing JSON
- ✅ Set `responsive: true` and `maintainAspectRatio: true`
- ✅ Use `beginAtZero: true` for bar/line charts
- ✅ Set `stepSize: 1` for integer-only data
- ✅ Register `ChartDataLabels` plugin when using datalabels
- ✅ Use `|safe` filter in Django template for JSON data

**NEVER:**
- ❌ Forget to check if canvas element exists
- ❌ Skip JSON parsing error handling
- ❌ Hard-code chart data in JavaScript (use Django context)
- ❌ Create charts without validating data first
- ❌ Forget to include Chart.js CDN in template

## File Structure

```
app/
├── templates/
│   └── app/
│       └── dashboard.html          # Canvas elements with data-* attributes
├── static/
│   └── app/
│       ├── js/
│       │   ├── line_chart.js       # One file per chart type
│       │   ├── bar_chart.js
│       │   └── pie_chart.js
│       └── css/
│           └── dashboard.css
└── views.py                        # JSON data preparation
```

## Common Issues

**Chart not rendering:**
1. Check if canvas ID matches JavaScript selector
2. Verify Chart.js CDN loaded before custom scripts
3. Check browser console for JavaScript errors
4. Validate JSON data structure

**DataLabels not showing:**
1. Include chartjs-plugin-datalabels CDN
2. Register plugin: `Chart.register(ChartDataLabels)`
3. Add plugin to chart config: `plugins: [ChartDataLabels]`
4. Set `datalabels: { display: true }` in options

**Data not updating:**
1. Clear browser cache
2. Check Django context data in view
3. Verify `|safe` filter used in template
4. Inspect canvas `data-*` attributes in browser DevTools

Overview

This skill provides Chart.js integration patterns for rendering interactive statistical graphs inside Django templates. It shows how to pass JSON data from Django context into canvas data-* attributes, include the datalabels plugin, and render line, bar, horizontal bar, and pie charts with responsive options and validation.

How this skill works

Place canvas elements in your Django templates and attach JSON payloads via data-* attributes using the |safe filter. Include Chart.js and chartjs-plugin-datalabels via CDN in the template head. Use small, focused JavaScript modules that run on DOMContentLoaded to parse data, validate structure, register the datalabels plugin, and create charts with sensible defaults and responsive options.

When to use it

  • Dashboard pages that summarize survey submissions, complaints, or responses over time.
  • Question-based result pages where multiple charts are rendered in a loop.
  • Reporting interfaces that need labeled values on bars or pie segments.
  • Prototypes where quick CDN setup is acceptable and server-side JSON injection is preferred.
  • When you want consistent chart color palettes and reusable chart patterns across templates.

Best practices

  • Wrap chart creation code in a DOMContentLoaded listener and verify the canvas element exists.
  • Send pre-serialized JSON from the view and use the |safe filter to inject it into data-* attributes.
  • Validate parsed JSON before rendering and handle parsing errors with try-catch.
  • Register ChartDataLabels when using datalabels and add the plugin to individual charts if required.
  • Use responsive: true, maintainAspectRatio: true, beginAtZero for numeric axes, and stepSize: 1 for integer counts.
  • Keep one JavaScript file per chart type and reuse a shared color palette for consistency.

Example use cases

  • Line chart showing daily survey submissions with dates on the x-axis and counts on the y-axis.
  • Horizontal bar chart ranking complaint reasons with numeric labels displayed via datalabels.
  • Pie chart summarizing response distribution with percentage labels calculated in the formatter.
  • Loop-generated canvases for each questionnaire question using unique ids and per-item summary data.
  • Admin dashboard combining time series, vertical bars, and pie charts using the same color palette.

FAQ

How should I pass data from Django to the template?

Prepare dictionaries or lists in the view, json.dumps them into context, and output into canvas data-* attributes using the |safe filter.

Why are datalabels not showing?

Ensure the datalabels CDN is included, call Chart.register(ChartDataLabels), and add the plugin in the chart config or plugins array.