Chart.js integration patterns for creating interactive statistical graphs in Django templates. Covers HTML setup, data injection, and JavaScript chart rendering with datalabels plugin.
Install
npx skillscat add carlos-asg/tu-voz-en-ruta/chartjs-graphs Install via the SkillsCat registry.
SKILL.md
Chart.js Setup
CDN Includes (in template head):
{% 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/chartjs-plugin-datalabels@2.2.0/dist/chartjs-plugin-datalabels.min.js"></script>
{% endblock %}HTML Template Pattern
Canvas element with data attributes:
<!-- 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
|safefilter for JSON data - Unique
idfor each canvas (useforloop.counterin loops)
JavaScript Chart Patterns
1. Line Chart (Time Series)
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
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
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
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):
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 contextDynamic Color Palette
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
DOMContentLoadedevent listener - ✅ Check if canvas exists before creating chart
- ✅ Validate parsed JSON data before using
- ✅ Use
try-catchwhen parsing JSON - ✅ Set
responsive: trueandmaintainAspectRatio: true - ✅ Use
beginAtZero: truefor bar/line charts - ✅ Set
stepSize: 1for integer-only data - ✅ Register
ChartDataLabelsplugin when using datalabels - ✅ Use
|safefilter 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 preparationCommon Issues
Chart not rendering:
- Check if canvas ID matches JavaScript selector
- Verify Chart.js CDN loaded before custom scripts
- Check browser console for JavaScript errors
- Validate JSON data structure
DataLabels not showing:
- Include chartjs-plugin-datalabels CDN
- Register plugin:
Chart.register(ChartDataLabels) - Add plugin to chart config:
plugins: [ChartDataLabels] - Set
datalabels: { display: true }in options
Data not updating:
- Clear browser cache
- Check Django context data in view
- Verify
|safefilter used in template - Inspect canvas
data-*attributes in browser DevTools