Timeline chart with highcharts using x-range with multiple stacks

highcharts column chart
highcharts weekly graph
highcharts timeline
highcharts graph
highcharts line chart
highcharts venn diagram
highcharts sample data
highcharts split y axis

I am using Highcharts to create a timeline chart which shows the flow of different "states" over time. The current implementation is at http://jsfiddle.net/hq1kdpmo/8/ and it looks like .

The current code is as follows:

Highcharts.chart('container', {
  "chart": {
    "type": "xrange"
  },
  "title": {
    "text": "State Periods"
  },
  "xAxis": {
    "type": "datetime"
  },
  "yAxis": [{
    "title": {
      "text": "Factions"
    },
    "categories": ["A", "B", "C", "D"],
    "reversed": true
  }],
  "plotOptions": {
    "xrange": {
      "borderRadius": 0,
      "borderWidth": 0,
      "grouping": false,
      "dataLabels": {
        "align": "center",
        "enabled": true,
        "format": "{point.name}"
      },
      "colorByPoint": false
    }
  },
  "tooltip": {
    "headerFormat": "<span style=\"font-size: 0.85em\">{point.x} - {point.x2}</span><br/>",
    "pointFormat": "<span style=\"color:{series.color}\">●</span> {series.name}: <b>{point.yCategory}</b><br/>"
  },
  "series": [{
    "name": "State A",
    "pointWidth": 20,
    "data": [{
      "x": 1540430613000,
      "x2": 1540633768100,
      "y": 0
    }, {
      "x": 1540191009000,
      "x2": 1540633768100,
      "y": 1
    }, {
      "x": 1540191009000,
      "x2": 1540530613000,
      "y": 2
    }, {
      "x": 1540530613000,
      "x2": 1540633768100,
      "y": 3
    }]
  }, {
    "name": "State B",
    "pointWidth": 20,
    "data": [{
      "x": 1540191009000,
      "x2": 1540430613000,
      "y": 0
    }, {
      "x": 1540530613000,
      "x2": 1540633768100,
      "y": 2
    }, {
      "x": 1540191009000,
      "x2": 1540330613000,
      "y": 3
    }]
  }, {
    "name": "State C",
    "pointWidth": 20,
    "data": [{
      "x": 1540330613000,
      "x2": 1540530613000,
      "y": 3
    }]
  }],
  "exporting": {
    "enabled": true,
    "sourceWidth": 1200
  }
});

Now what I am looking forward to is create something of this sort (pardon my paint skills).

Here the categories A to D are the only axis on y. But I would like to group a variable number of parallel ranges. The use case is that there can be multiple states at any point of time and the number of states at any point of time is variable. How do I go on about doing this?

To create such a chart you will have to add 12 yAxis (0-11) and set proper ticks and labels so that only A-D categories will be plotted. Additionally, adjust plotOptions.pointPadding and plotOptions.groupPadding properties to set points width automatically (series.pointWidth should be undefined then).

yAxis options:

  yAxis: [{
    title: {
      text: "Factions"
    },
    categories: ["A", "B", "C", "D"],
    tickPositions: [-1, 2, 5, 8, 11],
    lineWidth: 0,
    labels: {
      y: -20,
      formatter: function() {
        var chart = this.chart,
          axis = this.axis,
          label;

        if (!chart.yaxisLabelIndex) {
          chart.yaxisLabelIndex = 0;
        }

        if (this.value !== -1) {

          label = axis.categories[chart.yaxisLabelIndex];
          chart.yaxisLabelIndex++;

          if (chart.yaxisLabelIndex === 4) {
            chart.yaxisLabelIndex = 0;
          }

          return label;
        }
      },
    },
    reversed: true
  }]

Demo: https://jsfiddle.net/wchmiel/s9qefg7t/1/

series.xrange.stack, When using dual or multiple color axes, this number defines which colorAxis the particular series is connected to. It refers to either the {@link #colorAxis.id|axis  Customize a timeline chart with options that are standard to most Highcharts charts, such as data labels width, distance or using the point properties, color, x, y, markers or the connectors. In the demo below the color properties of the marker data point property is setting the color of a section in the timeline.

I have managed to solve this thanks to support from Highcharts themselves. The idea is to set the tick position on the load event and use the labels.formatter for formatting each individual label.

events: {
  load() {
    let labelGroup = document.querySelectorAll('.highcharts-yaxis-labels');
    // nodeValue is distance from top
    let ticks = document.querySelectorAll('.highcharts-yaxis-grid');
    let tickPositions = Array.from(ticks[0].childNodes).map(
        function(node){
            return +node.attributes.d.nodeValue.split(" ")[2];
        }
    );
    let labelPositions = [];
    for(let i =1 ;i<tickPositions.length;i++){
        labelPositions.push((tickPositions[i] + tickPositions[i-1])/2);
    }
    labelGroup[0].childNodes[0].attributes.y.nodeValue = labelPositions[0] + parseFloat(labelGroup[0].childNodes[0].style["font-size"], 10) / 2;
    labelGroup[0].childNodes[1].attributes.y.nodeValue = labelPositions[1] + parseFloat(labelGroup[0].childNodes[1].style["font-size"], 10) / 2;
    labelGroup[0].childNodes[2].attributes.y.nodeValue = labelPositions[2] + parseFloat(labelGroup[0].childNodes[2].style["font-size"], 10) / 2;
    labelGroup[0].childNodes[3].attributes.y.nodeValue = labelPositions[3] + parseFloat(labelGroup[0].childNodes[3].style["font-size"], 10) / 2;
    labelGroup[0].childNodes[4].attributes.y.nodeValue = labelPositions[4] + parseFloat(labelGroup[0].childNodes[4].style["font-size"], 10) / 2;
  }
}

And the labels are formatted as:

labels: {
  formatter: function() {
    var chart = this.chart,
      axis = this.axis,
      label;

    if (!chart.yaxisLabelIndex) {
      chart.yaxisLabelIndex = 0;
    }
    if (this.value !== -1) {

      label = axis.categories[chart.yaxisLabelIndex];
      chart.yaxisLabelIndex++;

      if (chart.yaxisLabelIndex === groups.length) {
        chart.yaxisLabelIndex = 0;
      }

      return label;
    }
  },
}

Fiddle at https://jsfiddle.net/yvnp4su0/42/

X-range series, X-range charts are used to visualize a range on the X-axis. This is often used in Gantt charts, as seen in this example. The X-range data series forms the basis of​  Timeline charts are used to place events on a time axis, such as this example showing the major space exploration events from 1951 to 1975. View options Edit in jsFiddle Edit in CodePen Edit in Highcharts Cloud

As I suggested in the comment above it is a better idea to use Highcharts renderer and add custom labels than manipulate Dom elements as you did in the previous answer, because it is a much cleaner solution.

Disable default labels:

  yAxis: [{
    title: {
      text: "Factions",
      margin: 35
    },
    categories: ["A", "B", "C", "D", "E"],
    tickPositions: tickPositions,
    lineWidth: 0,
    labels: {
      enabled: false
    },
    reversed: true
  }]

Add custom labels in proper positions using renderer:

  chart: {
    type: 'xrange',
    height: 500,
    marginLeft: 60,
    events: {
        load: function() {
        this.customLabels = [];
      },
      render: function() {
        var chart = this,
          yAxis = chart.yAxis[0],
          categories = yAxis.categories,
          xOffset = 15,
          yOffset = 20,
          xPos = yAxis.left - xOffset,
          tickPositions = yAxis.tickPositions,
          text,
          label,
          yPos,
          tick1Y,
          tick2Y,
          i;

        for (i = 0; i < tickPositions.length - 1; i++) {
            if (chart.customLabels[i]) {
            chart.customLabels[i].destroy();
          }

          tick1Y = yAxis.toPixels(tickPositions[i]);
          tick2Y = yAxis.toPixels(tickPositions[i + 1]);
          yPos = (tick1Y + tick2Y) / 2 + yOffset;
          text = categories[i];

          label = chart.renderer.text(text, xPos, yPos)
            .css({
              color: '#ccc',
              fontSize: '14px'
            })
            .add();

          chart.customLabels[i] = label;
        }
      }
    }
  }

Demo: https://jsfiddle.net/wchmiel/vkz7o1hw/

X range series, The X-range series displays ranges on the X axis, typically time intervals with a start and an end date. X-range is the basic series of a Gantt chart. An X-range is  This is often used in Gantt charts, as seen in this example. The X-range data series forms the basis of Highcharts Gantt, but is also available with Highcharts. The chart has 1 X axis displaying Time. Range: 2014-11-20 16:19:12 to 2014-12-23 07:40:48. The chart has 1 Y axis displaying categories.

Chart with 2 series, one xAxis and 2 yAxis stacked, 2. approach: maybe instead of using xrange series you will use Highcharts SVG Renderer? We can render a green rectangle that will start and  Chart showing use of multiple y-axes, where each series has a separate axis. Multiple axes allows data in different ranges to be visualized together. While this in some cases can cause charts to be hard to read, it can also be a powerful tool to illustrate correlations.

series.line.stack, When using dual or multiple color axes, this number defines which colorAxis the For the xrange series type and gantt charts, if the Y axis is a category axis, the  The chart shows how Highcharts and Highsoft has evolved over time, with number of employees, revenue, search popularity, office locations, and various events of interest. View as data table, Highcharts and Highsoft timeline. The chart has 1 X axis displaying Time. Range: 2009-10-13 11:31:12 to 2014-12-15 12:28:48.

plotOptions.series.stacking, When using dual or multiple color axes, this number defines which colorAxis the For the xrange series type and gantt charts, if the Y axis is a category axis, the  plotOptions.timeline. The timeline series presents given events along a drawn line. In TypeScript the type option must always be set. Configuration options for the series are given in three levels: Options for all series in a chart are defined in the plotOptions.series object. Options for all timeline series are defined in plotOptions.timeline.

Comments
  • Great answer! Although I have a few things I want to clarify. 1. What does label.y represent. 2. I want some space between each tick like the original graph. 'groupPadding` and pointPadding just reduces the thickness of the bars. 3 The tooltips have become wrong. I guess I can't use point.yCategory anymore and have to write some logic to get the correct y label 4. My requirements also states variable number of ranges for each category. Say, 4, 2, 3, 1 instead of 3, 3, 3, 3. Is it possible to do so?
  • 1. The y position offset of the label relative to the tick position on the axis. - api.highcharts.com/highcharts/yAxis.labels.y 2. This options set paddings, so increase them to have space 3. You can use tooltip.formatter - (api.highcharts.com/highcharts/tooltip.formatter) to create tooltip as you like - add a category property to each point object and use them inside formatter function, see jsfiddle.net/wchmiel/xnwed9mo. 4. Yes, however it requires different ticks settings.
  • Thanks! That explains a lot. So, I created a fiddle for the multiple ranges in each category. But as you might realise, if I have multiple ranges, setting a constant y value won't work. I am trying to execute a function to calculate the required value as y: function() {return -20;}(). How can I get the current tick? Fiddle for this jsfiddle.net/8ou3yp1L
  • You can use Highcharts renderer: api.highcharts.com/class-reference/Highcharts.SVGRenderer#text and plot custom labels exactly where you like (disable default labels).
  • Yes, using the renderer seems to be more "clean". I am implementing this but have stumbled upon an issue where long labels are spilling over the chart jsfiddle.net/7L1czde8 . For ref., this is not happening with the label formatter implementation jsfiddle.net/yvnp4su0/45 .
  • Custom labels require a bit more code and logic than editing the default ones. However, it can be done quite simple: jsfiddle.net/wchmiel/y1tob729/1