A waffle chart shows parts of a whole as a grid of 100 squares, one per percent. Here, how visitors reached a website: 52 squares for search, 28 direct, 12 social and 8 email.
A waffle chart turns percentages into squares you can count, so reading it is more like counting than judging angles. It works best with a few parts and whole numbers, and small shares like 8% still get a block of their own. For close comparisons, a bar chart is clearer.
Built with amCharts 5, a JavaScript library. The JavaScript includes your theme and renderer (canvas or SVG); the JSON also follows the changes you make with the controls.
npm install @amcharts/amcharts5. See Getting started.
The same chart as a JSON config. Chart Maker reads and writes this format, the library's JSON parser turns it into a chart, and the <am5-chart> element puts it on a page with no code."type" is the class to create and "settings" what you would pass to its new() in code; "properties" fill in its parts, lists such as series and axes or templates such as columns.template; "refs" holds objects used in more than one place ("#series-0" stands for the one named series-0); a string starting with "@" takes a value from another object, such as "@series.get('fill')".
A complete page. Save it as an .html file and open it in a browser.
Paste this into an AI coding assistant to build the chart from scratch. More in amCharts & AI.
// Create root element
// https://www.amcharts.com/docs/v5/getting-started/#Root_element
var root = am5.Root.new("chartdiv");
// Set themes
// https://www.amcharts.com/docs/v5/concepts/themes/
root.setThemes([
am5themes_Animated.new(root),
am5themes_Responsive.new(root)
]);
// Create chart
// https://www.amcharts.com/docs/v5/charts/xy-chart/
var chart = root.container.children.push(am5xy.XYChart.new(root, {
panX: false, // no panning or zooming: the grid stays as it is
panY: false,
wheelX: "none",
wheelY: "none",
layout: root.verticalLayout, // the legend goes under the grid
paddingLeft:5, // only 5px at the sides
paddingRight:5
}));
// Every third color of the palette, so neighboring parts stand apart
chart.get("colors").set("step", 3);
// Add legend
// https://www.amcharts.com/docs/v5/charts/xy-chart/legend-xy-series/
var legend = chart.children.push(
am5.Legend.new(root, {
centerX: am5.p50, // centered under the grid
x: am5.p50,
// as far from the chart above as from the edge below
marginTop: 15
})
);
// no cursor, so no values for the legend: without the empty value labels its items stay compact
legend.valueLabels.template.set("forceHidden", true);
// Create axes
// https://www.amcharts.com/docs/v5/charts/xy-chart/axes/
var xRenderer = am5xy.AxisRendererX.new(root, {
// no axis line: the squares stand on their own
strokeOpacity: 0,
minGridDistance: 40,
minorGridEnabled:true
});
var xAxis = chart.xAxes.push(am5xy.CategoryAxis.new(root, {
categoryField: "x",
renderer: xRenderer
}));
xRenderer.grid.template.setAll({
location: 1,
visible: false // the squares need no grid
})
xRenderer.labels.template.set("forceHidden", true); // no column numbers
// ten columns
xAxis.data.setAll([{ x: "1" }, { x: "2" }, { x: "3" }, { x: "4" }, { x: "5" }, { x: "6" }, { x: "7" }, { x: "8" }, { x: "9" }, { x: "10" }]);
var yRenderer = am5xy.AxisRendererY.new(root, {
// no axis line: the squares stand on their own
strokeOpacity: 0,
minGridDistance: 20,
minorGridEnabled:true
});
var yAxis = chart.yAxes.push(am5xy.CategoryAxis.new(root, {
categoryField: "y",
renderer: yRenderer
}));
yRenderer.grid.template.setAll({
location: 1,
visible: false // no grid
})
yRenderer.labels.template.set("forceHidden", true); // no row numbers
// ten rows, the first at the bottom
yAxis.data.setAll([{ y: "1" }, { y: "2" }, { y: "3" }, { y: "4" }, { y: "5" }, { y: "6" }, { y: "7" }, { y: "8" }, { y: "9" }, { y: "10" }]);
// Add series
// https://www.amcharts.com/docs/v5/charts/xy-chart/series/
// Each series fills the next squares of the 10 by 10 grid, column by column from the bottom left:
// one square per percent
var nextSquare = 0;
function makeSeries(name, squares) {
var series = chart.series.push(am5xy.ColumnSeries.new(root, {
name: name + " (" + squares + "%)",
xAxis: xAxis,
yAxis: yAxis,
// open and close in the same cell on both axes: each column is one square of the grid
categoryYField: "y",
openCategoryYField: "y",
categoryXField: "x",
openCategoryXField: "x",
// the series share the grid instead of sitting side by side in each cell
clustered: false
}));
// a stroke in the background color leaves a gap between the squares
series.columns.template.setAll({
width: am5.percent(100), // each square fills its whole cell
height: am5.percent(100),
stroke: root.interfaceColors.get("background"),
tooltipText: "{name}"
});
var data = [];
for (var i = 0; i < squares; i++) {
data.push({
x: String(Math.floor(nextSquare / 10) + 1), // every 10 squares start the next column...
y: String(nextSquare % 10 + 1) // ...and fill it from the bottom up
});
nextSquare++;
}
series.data.setAll(data);
series.appear();
legend.data.push(series); // each part gets its legend item as it's made
return series;
}
// How visitors reached a website
makeSeries("Search", 52);
makeSeries("Direct", 28);
makeSeries("Social", 12);
makeSeries("Email", 8);
// Make stuff animate on load
// https://www.amcharts.com/docs/v5/concepts/animations/
chart.appear(1000, 100);
{
"type": "XYChart",
"settings": {
"panX": false,
"panY": false,
"wheelX": "none",
"wheelY": "none",
"layout": {
"type": "VerticalLayout"
},
"paddingLeft": 5,
"paddingRight": 5,
"colors": {
"type": "ColorSet",
"settings": {
"step": 3,
"passOptions": {
"hue": 0.05,
"saturation": 0,
"lightness": 0
},
"colors": [
{
"type": "Color",
"value": "#67b7dc"
},
{
"type": "Color",
"value": "#6794dc"
},
{
"type": "Color",
"value": "#6771dc"
},
{
"type": "Color",
"value": "#8067dc"
},
{
"type": "Color",
"value": "#a367dc"
},
{
"type": "Color",
"value": "#c767dc"
},
{
"type": "Color",
"value": "#dc67ce"
},
{
"type": "Color",
"value": "#dc67ab"
},
{
"type": "Color",
"value": "#dc6788"
},
{
"type": "Color",
"value": "#dc6967"
}
],
"reuse": false,
"startIndex": 0,
"stateAnimationDuration": 600
}
}
},
"properties": {
"series": [
"#series-0",
"#series-1",
"#series-2",
"#series-3"
],
"xAxes": [
"#axis-0"
],
"yAxes": [
"#axis-1"
]
},
"children": [
{
"type": "Legend",
"settings": {
"centerX": {
"type": "Percent",
"value": 50
},
"x": {
"type": "Percent",
"value": 50
},
"marginTop": 15
},
"properties": {
"data": [
"#series-0",
"#series-1",
"#series-2",
"#series-3"
],
"valueLabels": {
"properties": {
"template": {
"settings": {
"forceHidden": true
}
}
}
}
}
}
],
"refs": [
{
"data-0": [
{
"x": "1",
"y": "1"
},
{
"x": "1",
"y": "2"
},
{
"x": "1",
"y": "3"
},
{
"x": "1",
"y": "4"
},
{
"x": "1",
"y": "5"
},
{
"x": "1",
"y": "6"
},
{
"x": "1",
"y": "7"
},
{
"x": "1",
"y": "8"
},
{
"x": "1",
"y": "9"
},
{
"x": "1",
"y": "10"
},
{
"x": "2",
"y": "1"
},
{
"x": "2",
"y": "2"
},
{
"x": "2",
"y": "3"
},
{
"x": "2",
"y": "4"
},
{
"x": "2",
"y": "5"
},
{
"x": "2",
"y": "6"
},
{
"x": "2",
"y": "7"
},
{
"x": "2",
"y": "8"
},
{
"x": "2",
"y": "9"
},
{
"x": "2",
"y": "10"
},
{
"x": "3",
"y": "1"
},
{
"x": "3",
"y": "2"
},
{
"x": "3",
"y": "3"
},
{
"x": "3",
"y": "4"
},
{
"x": "3",
"y": "5"
},
{
"x": "3",
"y": "6"
},
{
"x": "3",
"y": "7"
},
{
"x": "3",
"y": "8"
},
{
"x": "3",
"y": "9"
},
{
"x": "3",
"y": "10"
},
{
"x": "4",
"y": "1"
},
{
"x": "4",
"y": "2"
},
{
"x": "4",
"y": "3"
},
{
"x": "4",
"y": "4"
},
{
"x": "4",
"y": "5"
},
{
"x": "4",
"y": "6"
},
{
"x": "4",
"y": "7"
},
{
"x": "4",
"y": "8"
},
{
"x": "4",
"y": "9"
},
{
"x": "4",
"y": "10"
},
{
"x": "5",
"y": "1"
},
{
"x": "5",
"y": "2"
},
{
"x": "5",
"y": "3"
},
{
"x": "5",
"y": "4"
},
{
"x": "5",
"y": "5"
},
{
"x": "5",
"y": "6"
},
{
"x": "5",
"y": "7"
},
{
"x": "5",
"y": "8"
},
{
"x": "5",
"y": "9"
},
{
"x": "5",
"y": "10"
},
{
"x": "6",
"y": "1"
},
{
"x": "6",
"y": "2"
}
]
},
{
"data-1": [
{
"x": "6",
"y": "3"
},
{
"x": "6",
"y": "4"
},
{
"x": "6",
"y": "5"
},
{
"x": "6",
"y": "6"
},
{
"x": "6",
"y": "7"
},
{
"x": "6",
"y": "8"
},
{
"x": "6",
"y": "9"
},
{
"x": "6",
"y": "10"
},
{
"x": "7",
"y": "1"
},
{
"x": "7",
"y": "2"
},
{
"x": "7",
"y": "3"
},
{
"x": "7",
"y": "4"
},
{
"x": "7",
"y": "5"
},
{
"x": "7",
"y": "6"
},
{
"x": "7",
"y": "7"
},
{
"x": "7",
"y": "8"
},
{
"x": "7",
"y": "9"
},
{
"x": "7",
"y": "10"
},
{
"x": "8",
"y": "1"
},
{
"x": "8",
"y": "2"
},
{
"x": "8",
"y": "3"
},
{
"x": "8",
"y": "4"
},
{
"x": "8",
"y": "5"
},
{
"x": "8",
"y": "6"
},
{
"x": "8",
"y": "7"
},
{
"x": "8",
"y": "8"
},
{
"x": "8",
"y": "9"
},
{
"x": "8",
"y": "10"
}
]
},
{
"data-2": [
{
"x": "9",
"y": "1"
},
{
"x": "9",
"y": "2"
},
{
"x": "9",
"y": "3"
},
{
"x": "9",
"y": "4"
},
{
"x": "9",
"y": "5"
},
{
"x": "9",
"y": "6"
},
{
"x": "9",
"y": "7"
},
{
"x": "9",
"y": "8"
},
{
"x": "9",
"y": "9"
},
{
"x": "9",
"y": "10"
},
{
"x": "10",
"y": "1"
},
{
"x": "10",
"y": "2"
}
]
},
{
"data-3": [
{
"x": "10",
"y": "3"
},
{
"x": "10",
"y": "4"
},
{
"x": "10",
"y": "5"
},
{
"x": "10",
"y": "6"
},
{
"x": "10",
"y": "7"
},
{
"x": "10",
"y": "8"
},
{
"x": "10",
"y": "9"
},
{
"x": "10",
"y": "10"
}
]
},
{
"data-4": [
{
"x": "1"
},
{
"x": "2"
},
{
"x": "3"
},
{
"x": "4"
},
{
"x": "5"
},
{
"x": "6"
},
{
"x": "7"
},
{
"x": "8"
},
{
"x": "9"
},
{
"x": "10"
}
]
},
{
"data-5": [
{
"y": "1"
},
{
"y": "2"
},
{
"y": "3"
},
{
"y": "4"
},
{
"y": "5"
},
{
"y": "6"
},
{
"y": "7"
},
{
"y": "8"
},
{
"y": "9"
},
{
"y": "10"
}
]
},
{
"axis-0": {
"type": "CategoryAxis",
"settings": {
"categoryField": "x",
"renderer": {
"type": "AxisRendererX",
"settings": {
"strokeOpacity": 0,
"minGridDistance": 40,
"minorGridEnabled": true
},
"properties": {
"labels": {
"properties": {
"template": {
"settings": {
"forceHidden": true
}
}
}
},
"grid": {
"properties": {
"template": {
"settings": {
"location": 1,
"visible": false
}
}
}
}
}
}
},
"properties": {
"data": "#data-4"
}
}
},
{
"axis-1": {
"type": "CategoryAxis",
"settings": {
"categoryField": "y",
"renderer": {
"type": "AxisRendererY",
"settings": {
"strokeOpacity": 0,
"minGridDistance": 20,
"minorGridEnabled": true
},
"properties": {
"labels": {
"properties": {
"template": {
"settings": {
"forceHidden": true
}
}
}
},
"grid": {
"properties": {
"template": {
"settings": {
"location": 1,
"visible": false
}
}
}
}
}
}
},
"properties": {
"data": "#data-5"
}
}
},
{
"series-0": {
"type": "ColumnSeries",
"settings": {
"name": "Search (52%)",
"xAxis": "#axis-0",
"yAxis": "#axis-1",
"categoryYField": "y",
"openCategoryYField": "y",
"categoryXField": "x",
"openCategoryXField": "x",
"clustered": false
},
"properties": {
"data": "#data-0",
"columns": {
"properties": {
"template": {
"settings": {
"width": {
"type": "Percent",
"value": 100
},
"height": {
"type": "Percent",
"value": 100
},
"stroke": "@root.interfaceColors.get('background')",
"tooltipText": "{name}"
}
}
}
}
}
}
},
{
"series-1": {
"type": "ColumnSeries",
"settings": {
"name": "Direct (28%)",
"xAxis": "#axis-0",
"yAxis": "#axis-1",
"categoryYField": "y",
"openCategoryYField": "y",
"categoryXField": "x",
"openCategoryXField": "x",
"clustered": false
},
"properties": {
"data": "#data-1",
"columns": {
"properties": {
"template": {
"settings": {
"width": {
"type": "Percent",
"value": 100
},
"height": {
"type": "Percent",
"value": 100
},
"stroke": "@root.interfaceColors.get('background')",
"tooltipText": "{name}"
}
}
}
}
}
}
},
{
"series-2": {
"type": "ColumnSeries",
"settings": {
"name": "Social (12%)",
"xAxis": "#axis-0",
"yAxis": "#axis-1",
"categoryYField": "y",
"openCategoryYField": "y",
"categoryXField": "x",
"openCategoryXField": "x",
"clustered": false
},
"properties": {
"data": "#data-2",
"columns": {
"properties": {
"template": {
"settings": {
"width": {
"type": "Percent",
"value": 100
},
"height": {
"type": "Percent",
"value": 100
},
"stroke": "@root.interfaceColors.get('background')",
"tooltipText": "{name}"
}
}
}
}
}
}
},
{
"series-3": {
"type": "ColumnSeries",
"settings": {
"name": "Email (8%)",
"xAxis": "#axis-0",
"yAxis": "#axis-1",
"categoryYField": "y",
"openCategoryYField": "y",
"categoryXField": "x",
"openCategoryXField": "x",
"clustered": false
},
"properties": {
"data": "#data-3",
"columns": {
"properties": {
"template": {
"settings": {
"width": {
"type": "Percent",
"value": 100
},
"height": {
"type": "Percent",
"value": 100
},
"stroke": "@root.interfaceColors.get('background')",
"tooltipText": "{name}"
}
}
}
}
}
}
}
]
}