Spiral timeline
A spiral timeline chart is a circular data visualization that arranges events along a spiraling path, ideal for representing long or recurring time periods in a compact and visually engaging format. Unlike linear timelines, the spiral layout wraps the timeline inward or outward in loops, allowing you to display a large number of events or extended durations within a limited area.
This format is especially effective for showing cyclical patterns—such as seasons, fiscal quarters, or historical cycles—while still allowing viewers to follow a chronological flow. The spiral’s density and number of turns can be customized, enabling you to highlight specific time intervals or accommodate different types of datasets.
amCharts allows you to create highly customizable spiral timelines, with adjustable loop counts, spacing, and orientation. It’s a powerful tool for visual storytelling, pattern recognition, and presenting complex temporal data in a space-saving and intuitive way.
Demo source
<!-- Styles -->
<style>
#chartdiv {
width: 100%;
height: 600px;
}
</style>
<!-- Resources -->
<script src="https://cdn.amcharts.com/lib/5/index.js"></script>
<script src="https://cdn.amcharts.com/lib/5/xy.js"></script>
<script src="https://cdn.amcharts.com/lib/5/timeline.js"></script>
<script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script>
<!-- Chart code -->
<script>
am5.ready(function() {
var root = am5.Root.new("chartdiv");
// Set themes
// https://www.amcharts.com/docs/v5/concepts/themes/
root.setThemes([
am5themes_Animated.new(root)
]);
// Create chart
var chart = root.container.children.push(am5timeline.SpiralChart.new(root, {
levelCount: 2,
wheelY: "zoomX"
}));
chart.set("scrollbarX", am5.Scrollbar.new(root, {
orientation: "horizontal"
}));
var yRenderer = am5timeline.AxisRendererCurveY.new(root, {
})
yRenderer.labels.template.setAll({
centerY: am5.p50,
centerX: am5.p100,
fontSize: 11
});
yRenderer.grid.template.set("forceHidden", true);
// Create axes and their renderers
var xRenderer = am5timeline.AxisRendererCurveX.new(root, {
yRenderer: yRenderer,
strokeDasharray: [2, 3],
strokeOpacity: 0.5,
stroke: am5.color(0x000000)
});
xRenderer.labels.template.setAll({
centerY: am5.p50,
fontSize: 11,
minPosition: 0.01
});
xRenderer.labels.template.setup = function(target) {
target.set("layer", 30);
target.set("background", am5.Rectangle.new(root, {
fill: am5.color(0xffffff),
fillOpacity: 1
}));
}
var yAxis = chart.yAxes.push(am5xy.CategoryAxis.new(root, {
maxDeviation: 0,
categoryField: "category",
renderer: yRenderer
}));
var xAxis = chart.xAxes.push(am5xy.DateAxis.new(root, {
baseInterval: { timeUnit: "day", count: 1 },
renderer: xRenderer,
tooltip: am5.Tooltip.new(root, {})
}));
// Data
var colorSet = chart.get("colors");
var data = [{
"category": "Module #1",
"start": new Date("2019-01-10").getTime(),
"end": new Date("2019-01-13").getTime(),
"color": colorSet.getIndex(0),
"task": "Gathering requirements"
}, {
"category": "Module #1",
"start": new Date("2019-02-05").getTime(),
"end": new Date("2019-04-18").getTime(),
"color": colorSet.getIndex(0),
"task": "Development"
}, {
"category": "Module #2",
"start": new Date("2019-01-08").getTime(),
"end": new Date("2019-01-10").getTime(),
"color": colorSet.getIndex(5),
"task": "Gathering requirements"
}, {
"category": "Module #2",
"start": new Date("2019-01-12").getTime(),
"end": new Date("2019-01-15").getTime(),
"color": colorSet.getIndex(5),
"task": "Producing specifications"
}, {
"category": "Module #2",
"start": new Date("2019-01-16").getTime(),
"end": new Date("2019-02-05").getTime(),
"color": colorSet.getIndex(5),
"task": "Development"
}, {
"category": "Module #2",
"start": new Date("2019-02-10").getTime(),
"end": new Date("2019-02-18").getTime(),
"color": colorSet.getIndex(5),
"task": "Testing and QA"
}, {
"category": ""
}, {
"category": "Module #3",
"start": new Date("2019-01-01").getTime(),
"end": new Date("2019-01-19").getTime(),
"color": colorSet.getIndex(9),
"task": "Gathering requirements"
}, {
"category": "Module #3",
"start": new Date("2019-02-01").getTime(),
"end": new Date("2019-02-10").getTime(),
"color": colorSet.getIndex(9),
"task": "Producing specifications"
}, {
"category": "Module #3",
"start": new Date("2019-03-10").getTime(),
"end": new Date("2019-04-15").getTime(),
"color": colorSet.getIndex(9),
"task": "Development"
}, {
"category": "Module #3",
"start": new Date("2019-04-20").getTime(),
"end": new Date("2019-04-30").getTime(),
"color": colorSet.getIndex(9),
"task": "Testing and QA",
"disabled2": false,
"image2": "/wp-content/uploads/assets/timeline/rachel.jpg",
"location": 0
}, {
"category": "Module #4",
"start": new Date("2019-01-15").getTime(),
"end": new Date("2019-02-12").getTime(),
"color": colorSet.getIndex(15),
"task": "Gathering requirements",
"disabled1": false,
"image1": "/wp-content/uploads/assets/timeline/monica.jpg"
}, {
"category": "Module #4",
"start": new Date("2019-02-25").getTime(),
"end": new Date("2019-03-10").getTime(),
"color": colorSet.getIndex(15),
"task": "Development"
}, {
"category": "Module #4",
"start": new Date("2019-03-23").getTime(),
"end": new Date("2019-04-29").getTime(),
"color": colorSet.getIndex(15),
"task": "Testing and QA"
}];
// Add series
// https://www.amcharts.com/docs/v5/charts/xy-chart/series/
var series = chart.series.push(am5timeline.CurveColumnSeries.new(root, {
xAxis: xAxis,
yAxis: yAxis,
baseAxis: yAxis,
valueXField: "end",
openValueXField: "start",
categoryYField: "category",
layer: 30
}));
series.columns.template.setAll({
height: am5.percent(10),
strokeOpacity: 0
})
series.bullets.push(function(root, series, dataItem) {
var circle = am5.Circle.new(root, {
radius: 4,
fill: chart.get("colors").getIndex(series.dataItems.indexOf(dataItem)),
strokeWidth: 2,
strokeOpacity: 0.5,
layer: 30
});
return am5.Bullet.new(root, {
sprite: circle,
locationX: 0,
locationY: 0.5
})
})
series.bullets.push(function(root, series, dataItem) {
var circle = am5.Circle.new(root, {
radius: 4,
fill: chart.get("colors").getIndex(series.dataItems.indexOf(dataItem)),
strokeWidth: 2,
strokeOpacity: 0.5,
layer: 30
});
return am5.Bullet.new(root, {
sprite: circle, locationX: 1
})
})
series.columns.template.adapters.add("fill", function(fill, target) {
return chart.get("colors").getIndex(series.dataItems.indexOf(target.dataItem));
})
// Line series for flags
var lineSeries = chart.series.push(am5timeline.CurveLineSeries.new(root, {
xAxis: xAxis, yAxis: yAxis, categoryYField: "category", valueXField: "date"
}));
lineSeries.strokes.template.set("forceHidden", true);
lineSeries.bullets.push(function(root, series, dataItem) {
var flag = am5.Tooltip.new(root, {
centerY: 28,
paddingBottom: 4, paddingLeft: 7, paddingRight: 7, paddingTop: 4, layer: 30
})
flag.get("background")?.setAll({
stroke: am5.color(0x000000), cornerRadius: 0
})
flag.label.setAll({
fill: am5.color(0x000000),
text: dataItem.dataContext.letter,
fontSize: "0.8em"
});
flag.show();
return am5.Bullet.new(root, {
sprite: flag,
locationX: 0.5, locationY: 0.5
})
});
lineSeries.data.setAll([
{ category: "", eventDate: "2019-01-15", letter: "A", description: "Something happened here" },
{ category: "", date: new Date("2019-01-23").getTime(), letter: "B", description: "Something happened here" },
{ category: "", date: new Date("2019-02-10").getTime(), letter: "C", description: "Something happened here" },
{ category: "", date: new Date("2019-02-29").getTime(), letter: "D", description: "Something happened here" },
{ category: "", date: new Date("2019-03-06").getTime(), letter: "E", description: "Something happened here" },
{ category: "", date: new Date("2019-03-12").getTime(), letter: "F", description: "Something happened here" },
{ category: "", date: new Date("2019-03-22").getTime(), letter: "G", description: "Something happened here" }
]);
var cursor = chart.set("cursor", am5timeline.CurveCursor.new(root, {
behavior: "zoomX",
xAxis: xAxis,
yAxis: yAxis
}));
series.data.setAll(data);
yAxis.data.setAll([
{ category: "Module #1" },
{ category: "Module #2" },
{ category: "" },
{ category: "Module #3" },
{ category: "Module #4" }
]);
// Animate chart and series in
// https://www.amcharts.com/docs/v5/concepts/animations/#Initial_animation
series.appear(1000);
chart.appear(1000, 100);
}); // end am5.ready()
</script>
<!-- HTML -->
<div id="chartdiv"></div>