• Open in:

Horizontal Serpentine Timeline

A serpentine timeline chart uses a curved, winding path to display events over an extended period. By bending the timeline back and forth—either horizontally or vertically—it makes efficient use of limited space when a continuous line wouldn’t suffice. amCharts offers the flexibility to adjust the bend count and orientation, making it perfect for visually summarizing lengthy timelines in a compact format.

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.SerpentineChart.new(root, {
  orientation: "horizontal",
  levelCount: 6,
  startLocation: 0.2,
  endLocation: 1,
  wheelY: "zoomX"
}));

chart.set("scrollbarX", am5.Scrollbar.new(root, {
  orientation: "horizontal"
}));

var yRenderer = am5timeline.AxisRendererCurveY.new(root, {
  rotateLabels: true
})

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>