Search code examples
amchartsamcharts5

amcharts5 - chart above another chart using same xaxis?


I am attempting to show a column chart (or a line chart would work too) above a gannt chart using the same xaxis (timestamp) and having trouble with spacing as well as getting any data to show in the column chart. I don't even know if this is possible to do, but the help docs made it sound like it was so I am more than likely missing quite a few things in the configuration or doing things wrong.

I've created a codepen with what I have so far. The original gannt chart shows fine, then I am able to get the column chart's yaxis to show (although it overlaps the gannt instead of being above it and does not show any data).

Any assistance/explanation with this would be appreciated.

// Set data
var data = [{
            "State": "Idle",
            "Start": 1424321864000,
            "End": 1424321875000,
            "Duration": 11
}, {
            "State": "Idle",
            "Start": 1424322649000,
            "End": 1424322669000,
            "Duration": 20
}, {
            "State": "Idle",
            "Start": 1424322970000,
            "End": 1424322981000,
            "Duration": 11
}, {
            "State": "Idle",
            "Start": 1424323093000,
            "End": 1424323139000,
            "Duration": 46
}, {
            "State": "Active",
            "Start": 1424323250000,
            "End": 1424323268000,
            "Duration": 18
}, {
            "State": "Idle",
            "Start": 1424323680000,
            "End": 1424323703000,
            "Duration": 23
}];

var mousekeys = [{
            "Timestamp": 1424318400000,
            "Clicks": "20",
            "Keypresses": "30"
}];
/**
 * ---------------------------------------
 * This demo was created using amCharts 5.
 * 
 * For more information visit:
 * https://www.amcharts.com/
 * 
 * Documentation is available at:
 * https://www.amcharts.com/docs/v5/
 * ---------------------------------------
 */

// Create root element
// https://www.amcharts.com/docs/v5/getting-started/#Root_element
var root = am5.Root.new("chartdiv");

//font size theme
var myTheme = am5.Theme.new(root);
myTheme.rule("Label").setAll({
  fontSize: 12
});

root.setThemes([
  am5themes_Animated.new(root),
  myTheme
]);

root.locale = am5locales_en_US;
//chartReg['chart'].utc = true; //force chart to show in UTC instead of device timezone
root.timezone = am5.Timezone.new('UTC'); //user timezone

//duration format
root.durationFormatter.setAll({
  baseUnit: "second",
  durationFormat: "hh'h' mm'm' ss's'"
});

//create chart
var chart = root.container.children.push(am5xy.XYChart.new(root, {
  panX: false,
  panY: false,
  wheelX: false,
  wheelY: false,
  layout: root.verticalLayout, 
  marginTop: 0,
  marginRight: 0,
  marginBottom: 0,
  marginLeft: 0,
  paddingTop: 0,
  paddingRight: 20,
  paddingBottom: 20,
  paddingLeft: 10,
}));

//zoom button
//chart.zoomOutButton.setAll({
//  forceHidden: true, //disables zoom button
//});

chart.zoomOutButton.get("background").setAll({
  fill: am5.color("#BDBDBD"),
});

chart.zoomOutButton.get("background").states.create("hover", {}).setAll({
  fill: am5.color("#8a8a8a"),
});

chart.zoomOutButton.get("background").states.create("down", {}).setAll({
  fill: am5.color("#707070"),
});

chart.zoomOutButton.get("background").states.create("active", {}).setAll({
  fill: am5.color("#707070"),
});

//create axes
var yAxis = chart.yAxes.push(am5xy.CategoryAxis.new(root, {
  text: "State",
  fontWeight: "500",
  height: am5.percent(70),
  categoryField: "State",
  renderer: am5xy.AxisRendererY.new(root, {}),
  tooltip: am5.Tooltip.new(root, {}),
}));


var yAxis2 = chart.yAxes.push(am5xy.ValueAxis.new(root, {
  text: "Clicks",
  fontWeight: "500",
  height: am5.percent(30),
  min: 0, //min 'hint', but not forced
  //strictMinMax: true, //forces the min/max
  //extraMax: 0.1, //extra 10% to the value range which acts as 'padding' to the yaxis
  renderer: am5xy.AxisRendererY.new(root, {})
}));

//the order is top to bottom ends up bottom to top on the chart
yAxis.data.setAll([
  { State: "Idle" },
  { State: "Active" }
]);

//y-axis dash lines
var yRenderer = yAxis.get("renderer");
yRenderer.grid.template.setAll({
  strokeDasharray: [6]
});

var xAxis = chart.xAxes.push(am5xy.DateAxis.new(root, {
  baseInterval: { timeUnit: "second", count: 1 },
  renderer: am5xy.AxisRendererX.new(root, {}),
  tooltip: am5.Tooltip.new(root, {}),
  tooltipDateFormat: "EEE, MMM dd @ h:mm:ss a"
}));

//x-axis dash lines
var xRenderer = xAxis.get("renderer");
xRenderer.grid.template.setAll({
  strokeDasharray: [6],
});

//series
var series = chart.series.push(am5xy.ColumnSeries.new(root, {
  xAxis: xAxis,
  yAxis: yAxis,
  openValueXField: "Start",
  valueXField: "End",
  categoryYField: "State",
  sequencedInterpolation: true,
  tooltip: am5.Tooltip.new(root, {
    //labelText: "[bold {Color} fontSize: 16px]{State}[/]\n[bold]Start :[/] {openValueX.formatDate(\"EEE',' MM'/'dd'/'yyyy '@' h':'mm':'ss a\")}\n[bold]End :[/] {valueX.formatDate(\"EEE',' MM'/'dd'/'yyyy '@' h':'mm':'ss a\")}\n[bold]Duration :[/] {Duration.formatDuration(\"hh'h' mm'm' ss's'\")}",
    labelText: "[bold]Start :[/] {openValueX.formatDate(\"EEE',' MM'/'dd'/'yyyy '@' h':'mm':'ss a\")}\n[bold]End :[/] {valueX.formatDate(\"EEE',' MM'/'dd'/'yyyy '@' h':'mm':'ss a\")}\n[bold]Duration :[/] {Duration.formatDuration(\"hh'h' mm'm' ss's'\")}",
    getFillFromSprite: false
  })
}));

series.get("tooltip").get("background").setAll({
  fill: am5.color("#ffffff"),
  fillOpacity: 1,
  strokeWidth: 2,
  stroke: am5.color("#bbbbbb"),
});

//series columns
series.columns.template.setAll({
  fillOpacity: 1,
  stroke: 0,
  strokeWidth: 0, //doesn't seem to work use 'stroke:0'
  tooltipText: "{State}:\n[bold]{openValueX}[/] - [bold]{valueX}[/]"
});

series.columns.template.adapters.add("fill", function (text, target, key) {
  //console.log(target);
  //have to check for empty
  //if(target.dataItem) {
  if(target.dataItem.dataContext.State == "Active") {
    return am5.color("#7bc07b"); //active
  } else {
    return am5.color("#55bfe6"); //idle
  }
  //} else {
  //    return null;
  //}
});

//series
var series2 = chart.series.push(am5xy.ColumnSeries.new(root, {
  name: "Clicks",
  fill: am5.color("#679fd0"),
  xAxis: xAxis,
  yAxis: yAxis2,
  valueYField: "Clicks",
  valueXField: "Timestamp",
  tooltip: am5.Tooltip.new(root, {
    labelText: "{valueY}",
    getFillFromSprite: false
  })
}));

series2.get("tooltip").get("background").setAll({
  fill: am5.color("#ffffff"),
  fillOpacity: 1,
  strokeWidth: 2,
  stroke: am5.color("#bbbbbb"),
});

//series columns
series2.columns.template.setAll({
  fillOpacity: 1,
  stroke: 0,
  strokeWidth: 0, //doesn't seem to work use 'stroke:0'
});

series2.get("tooltip").label.adapters.add("text", function (text, target, key) {
  //console.log(target);
  //have to check for empty
  if(target.dataItem) {
    return "Total : [bold]{Clicks}[/]\nTimestamp : [bold]{Timestamp}[/]";
  } else {
    return null;
  }
});

//title
var title = chart.children.unshift(am5.Label.new(root, {
  fontSize: 12,
  textAlign: "left",
  x: am5.percent(0),
  y: am5.percent(0),
  marginTop: 0,
  marginRight: 0,
  marginBottom: 30,
  marginLeft: 0,
  paddingTop: 0,
  paddingRight: 0,
  paddingBottom: 0,
  paddingLeft: 0,
  text: "some label"
}));

//cursor
var cursor = chart.set("cursor", am5xy.XYCursor.new(root, {
  snapToSeries: [ series ],
  snapToSeriesBy: "x",
  behavior: "zoomX"
}));
cursor.lineX.set("visible", false);
cursor.lineY.set("visible", false);

//formatting
series.data.processor = am5.DataProcessor.new(root, {
  dateFields: ["Start", "End"],
  dateFormat: "xxxxxxxxxxxxx"
});

//formatting
series2.data.processor = am5.DataProcessor.new(root, {
  numericFields: ["Clicks"],
  dateFields: ["Timestamp"],
  dateFormat: "xxxxxxxxxxxxx"
});

//scrollbar
chart.set("scrollbarX", am5.Scrollbar.new(root, {
  orientation: "horizontal",
  marginTop: 0,
  marginRight: 0,
  marginBottom: 30,
  marginLeft: 0,
  paddingTop: 0,
  paddingRight: 0,
  paddingBottom: 0,
  paddingLeft: 0,
}));

//exporting
exporting = am5plugins_exporting.Exporting.new(root, {
  dataSource: data,
  dataFields: {
    State: 'State', Start: 'Start', End: 'End', Duration: 'Duration'
  },
  dateFields: ["Start", "End"],
  dateFormat: "MM/dd/yyyy hh:mm:ss a",
  durationFields: ["Duration"],
  durationFormat: "hh'h' mm'm' ss's'",
  filePrefix: 'User Activity'
});

//add data
series.data.setAll(data);
series2.data.setAll(mousekeys);

//make stuff animate on load
series.appear(1000);
series2.appear(1000);
chart.appear(1000, 100);

Solution

  • yAxis and yAxis2 are not stacked. You have to be more explicit and add this line of code:

    chart.leftAxesContainer.set("layout", root.verticalLayout);
    

    If you want to place your column chart above, you need to push yAxis2 before yAxis or use unshift():

    var yAxis2 = chart.yAxes.unshift(am5xy.ValueAxis.new(root, {
      // ...
    }));
    

    I put your code below with these two little modifications:

    // Set data
    var data = [{
      "State": "Idle",
      "Start": 1424321864000,
      "End": 1424321875000,
      "Duration": 11
    }, {
      "State": "Idle",
      "Start": 1424322649000,
      "End": 1424322669000,
      "Duration": 20
    }, {
      "State": "Idle",
      "Start": 1424322970000,
      "End": 1424322981000,
      "Duration": 11
    }, {
      "State": "Idle",
      "Start": 1424323093000,
      "End": 1424323139000,
      "Duration": 46
    }, {
      "State": "Active",
      "Start": 1424323250000,
      "End": 1424323268000,
      "Duration": 18
    }, {
      "State": "Idle",
      "Start": 1424323680000,
      "End": 1424323703000,
      "Duration": 23
    }];
    
    var mousekeys = [{
      "Timestamp": 1424318400000,
      "Clicks": "20",
      "Keypresses": "30"
    }];
    /**
     * ---------------------------------------
     * This demo was created using amCharts 5.
     * 
     * For more information visit:
     * https://www.amcharts.com/
     * 
     * Documentation is available at:
     * https://www.amcharts.com/docs/v5/
     * ---------------------------------------
     */
    
    // Create root element
    // https://www.amcharts.com/docs/v5/getting-started/#Root_element
    var root = am5.Root.new("chartdiv");
    
    //font size theme
    var myTheme = am5.Theme.new(root);
    myTheme.rule("Label").setAll({
      fontSize: 12
    });
    
    root.setThemes([
      am5themes_Animated.new(root),
      myTheme
    ]);
    
    root.locale = am5locales_en_US;
    //chartReg['chart'].utc = true; //force chart to show in UTC instead of device timezone
    root.timezone = am5.Timezone.new('UTC'); //user timezone
    
    //duration format
    root.durationFormatter.setAll({
      baseUnit: "second",
      durationFormat: "hh'h' mm'm' ss's'"
    });
    
    //create chart
    var chart = root.container.children.push(am5xy.XYChart.new(root, {
      panX: false,
      panY: false,
      wheelX: false,
      wheelY: false,
      layout: root.verticalLayout, 
      marginTop: 0,
      marginRight: 0,
      marginBottom: 0,
      marginLeft: 0,
      paddingTop: 0,
      paddingRight: 20,
      paddingBottom: 20,
      paddingLeft: 10,
    }));
    
    chart.leftAxesContainer.set("layout", root.verticalLayout); // <--- HERE
    
    //zoom button
    //chart.zoomOutButton.setAll({
    //  forceHidden: true, //disables zoom button
    //});
    
    chart.zoomOutButton.get("background").setAll({
      fill: am5.color("#BDBDBD"),
    });
    
    chart.zoomOutButton.get("background").states.create("hover", {}).setAll({
      fill: am5.color("#8a8a8a"),
    });
    
    chart.zoomOutButton.get("background").states.create("down", {}).setAll({
      fill: am5.color("#707070"),
    });
    
    chart.zoomOutButton.get("background").states.create("active", {}).setAll({
      fill: am5.color("#707070"),
    });
    
    //create axes
    var yAxis = chart.yAxes.push(am5xy.CategoryAxis.new(root, {
      text: "State",
      fontWeight: "500",
      height: am5.percent(70),
      categoryField: "State",
      renderer: am5xy.AxisRendererY.new(root, {}),
      tooltip: am5.Tooltip.new(root, {}),
    }));
    
    
    var yAxis2 = chart.yAxes.unshift(am5xy.ValueAxis.new(root, { // <--- HERE
      text: "Clicks",
      fontWeight: "500",
      height: am5.percent(30),
      min: 0, //min 'hint', but not forced
      //strictMinMax: true, //forces the min/max
      //extraMax: 0.1, //extra 10% to the value range which acts as 'padding' to the yaxis
      renderer: am5xy.AxisRendererY.new(root, {})
    }));
    
    //the order is top to bottom ends up bottom to top on the chart
    yAxis.data.setAll([
      { State: "Idle" },
      { State: "Active" }
    ]);
    
    //y-axis dash lines
    var yRenderer = yAxis.get("renderer");
    yRenderer.grid.template.setAll({
      strokeDasharray: [6]
    });
    
    var xAxis = chart.xAxes.push(am5xy.DateAxis.new(root, {
      baseInterval: { timeUnit: "second", count: 1 },
      renderer: am5xy.AxisRendererX.new(root, {}),
      tooltip: am5.Tooltip.new(root, {}),
      tooltipDateFormat: "EEE, MMM dd @ h:mm:ss a"
    }));
    
    //x-axis dash lines
    var xRenderer = xAxis.get("renderer");
    xRenderer.grid.template.setAll({
      strokeDasharray: [6],
    });
    
    //series
    var series = chart.series.push(am5xy.ColumnSeries.new(root, {
      xAxis: xAxis,
      yAxis: yAxis,
      openValueXField: "Start",
      valueXField: "End",
      categoryYField: "State",
      sequencedInterpolation: true,
      tooltip: am5.Tooltip.new(root, {
        //labelText: "[bold {Color} fontSize: 16px]{State}[/]\n[bold]Start :[/] {openValueX.formatDate(\"EEE',' MM'/'dd'/'yyyy '@' h':'mm':'ss a\")}\n[bold]End :[/] {valueX.formatDate(\"EEE',' MM'/'dd'/'yyyy '@' h':'mm':'ss a\")}\n[bold]Duration :[/] {Duration.formatDuration(\"hh'h' mm'm' ss's'\")}",
        labelText: "[bold]Start :[/] {openValueX.formatDate(\"EEE',' MM'/'dd'/'yyyy '@' h':'mm':'ss a\")}\n[bold]End :[/] {valueX.formatDate(\"EEE',' MM'/'dd'/'yyyy '@' h':'mm':'ss a\")}\n[bold]Duration :[/] {Duration.formatDuration(\"hh'h' mm'm' ss's'\")}",
        getFillFromSprite: false
      })
    }));
    
    series.get("tooltip").get("background").setAll({
      fill: am5.color("#ffffff"),
      fillOpacity: 1,
      strokeWidth: 2,
      stroke: am5.color("#bbbbbb"),
    });
    
    //series columns
    series.columns.template.setAll({
      fillOpacity: 1,
      stroke: 0,
      strokeWidth: 0, //doesn't seem to work use 'stroke:0'
      tooltipText: "{State}:\n[bold]{openValueX}[/] - [bold]{valueX}[/]"
    });
    
    series.columns.template.adapters.add("fill", function (text, target, key) {
      //console.log(target);
      //have to check for empty
      //if(target.dataItem) {
      if(target.dataItem.dataContext.State == "Active") {
        return am5.color("#7bc07b"); //active
      } else {
        return am5.color("#55bfe6"); //idle
      }
      //} else {
      //    return null;
      //}
    });
    
    //series
    var series2 = chart.series.push(am5xy.ColumnSeries.new(root, {
      name: "Clicks",
      fill: am5.color("#679fd0"),
      xAxis: xAxis,
      yAxis: yAxis2,
      valueYField: "Clicks",
      valueXField: "Timestamp",
      tooltip: am5.Tooltip.new(root, {
        labelText: "{valueY}",
        getFillFromSprite: false
      })
    }));
    
    series2.get("tooltip").get("background").setAll({
      fill: am5.color("#ffffff"),
      fillOpacity: 1,
      strokeWidth: 2,
      stroke: am5.color("#bbbbbb"),
    });
    
    //series columns
    series2.columns.template.setAll({
      fillOpacity: 1,
      stroke: 0,
      strokeWidth: 0, //doesn't seem to work use 'stroke:0'
    });
    
    series2.get("tooltip").label.adapters.add("text", function (text, target, key) {
      //console.log(target);
      //have to check for empty
      if(target.dataItem) {
        return "Total : [bold]{Clicks}[/]\nTimestamp : [bold]{Timestamp}[/]";
      } else {
        return null;
      }
    });
    
    //title
    var title = chart.children.unshift(am5.Label.new(root, {
      fontSize: 12,
      textAlign: "left",
      x: am5.percent(0),
      y: am5.percent(0),
      marginTop: 0,
      marginRight: 0,
      marginBottom: 30,
      marginLeft: 0,
      paddingTop: 0,
      paddingRight: 0,
      paddingBottom: 0,
      paddingLeft: 0,
      text: "some label"
    }));
    
    //cursor
    var cursor = chart.set("cursor", am5xy.XYCursor.new(root, {
      snapToSeries: [ series ],
      snapToSeriesBy: "x",
      behavior: "zoomX"
    }));
    cursor.lineX.set("visible", false);
    cursor.lineY.set("visible", false);
    
    //formatting
    series.data.processor = am5.DataProcessor.new(root, {
      dateFields: ["Start", "End"],
      dateFormat: "xxxxxxxxxxxxx"
    });
    
    //formatting
    series2.data.processor = am5.DataProcessor.new(root, {
      numericFields: ["Clicks"],
      dateFields: ["Timestamp"],
      dateFormat: "xxxxxxxxxxxxx"
    });
    
    //scrollbar
    chart.set("scrollbarX", am5.Scrollbar.new(root, {
      orientation: "horizontal",
      marginTop: 0,
      marginRight: 0,
      marginBottom: 30,
      marginLeft: 0,
      paddingTop: 0,
      paddingRight: 0,
      paddingBottom: 0,
      paddingLeft: 0,
    }));
    
    //exporting
    exporting = am5plugins_exporting.Exporting.new(root, {
      dataSource: data,
      dataFields: {
        State: 'State', Start: 'Start', End: 'End', Duration: 'Duration'
      },
      dateFields: ["Start", "End"],
      dateFormat: "MM/dd/yyyy hh:mm:ss a",
      durationFields: ["Duration"],
      durationFormat: "hh'h' mm'm' ss's'",
      filePrefix: 'User Activity'
    });
    
    //add data
    series.data.setAll(data);
    series2.data.setAll(mousekeys);
    
    //make stuff animate on load
    series.appear(1000);
    series2.appear(1000);
    chart.appear(1000, 100);
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
    }
    
    #chartdiv {
      width: 100%;
      height: 500px;
    }
    <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/themes/Animated.js"></script>
    <script src="https://cdn.amcharts.com/lib/5/locales/en_US.js"></script>
    <script src="https://cdn.amcharts.com/lib/5/plugins/exporting.js"></script>
    <div id="chartdiv"></div>

    You can see a column if you use the horizontal scrollbar and go completely to the left. The reason is there:

    var xAxis = chart.xAxes.push(am5xy.DateAxis.new(root, {
      baseInterval: { timeUnit: "second", count: 1 },
      // ...
    }));
    

    Your interval is tiny (one second), so your column width is tiny. You have to tweak timeUnit or count in baseInterval if you want a wider column.