Stacked Column Charts with Images — Challenge AnyChart! January 23rd, 2019 by Irina Maximova

Images in Stacked Column Charts Series explained in Challenge AnyChart!We are ready to share a new advanced JS (HTML5) data visualization tutorial. Tasks we get from our customers are always interesting, and the Challenge AnyChart! series on our blog proved to be a great way to show you some of the most compelling ones and explain how to solve them, demonstrating the power and flexibility of our JavaScript charts library. Today’s challenge is about adding images to stacked column charts using AnyChart.

Data Visualization Task

Here’s what question one of our customers asked us for help with the other day:

How to place images on top of a series in stacked bar/column charts?

To solve this data visualization task using AnyChart JS Charts, you typically need the following:

  • use methods from our JS Charts API to transform X and Y values to pixels, and
  • make a few mathematical calculations to display images in a stacked chart series the way you want.

Solution Overview

The first thing to pay attention to is the chart itself — build a stacked column chart (or, if you need horizontal bars instead of columns, build a stacked bar chart).

Then, you’ll need to create a function which overrides the default drawing. If the width and height of a certain bar/column are big enough, a proper image will be placed there. The image size will be changed proportionally when the bar/column size is changed. We’ll show you how right now.

Adding Images to Stacked Column Charts

Before putting the main script to work and deliver a nice example of interactive stacked column charts with images, cache the needed images in HTML:

<img src="http://cdn.anychart.com/ts/icons/anychart.svg">
<img src="http://cdn.anychart.com/ts/icons/anystock.svg">
<img src="http://cdn.anychart.com/ts/icons/anymap.svg">

Now, set a name and meta with the link to the corresponding image for each series, like this:

series1
    .name('AnyChart')
    .meta('image', 'http://cdn.anychart.com/ts/icons/anychart.svg');

Also, specify the necessary restrictions for the size of the images, in pixels:

var sizeThreshold = 30;

To add an image to each point of the stacked chart, define a special function that will override the default drawing. First, create a variable in the body of the function, which needs to include the shapes array and a new shape intended for drawing images:

var tmp = series.rendering().shapes();
    tmp.push({
      name: 'image',
      shapeType: 'path',
      fillName: 'fill',
      canBeHoveredSelected: true,
      strokeName: 'stroke',
      isHatchFill: false
    });

Second, pass tmp to shapes to replace the default shapes:

series.rendering()
      .shapes(tmp)
      .needsZero(true)

Third, create a function which will be used when drawing each series point and get the current state shapes:

.point(function() {
      // get shapes group
      var shapes = this.getShapesGroup(this.pointState);

Fourth, calculate the left and right values on the X axis:

var leftX = this.x - this.pointWidth / 2;
var rightX = leftX + this.pointWidth;

Fifth, draw the stacked columns and then the images on them if there’s enough room for that:

shapes['path']
        .clear()
        .moveTo(leftX, this.zero)
        .lineTo(leftX, this.value)
        .lineTo(rightX, this.value)
        .lineTo(rightX, this.zero)
        .close();

if (Math.abs(this.zero - this.value) >= sizeThreshold) {
 shapes['image']
…
}

Finally, repeat the operation to make the images work fine on hover:

shapes = this.getShapesGroup(1);
        shapes['image']

Well done! Now everything works as it should. You can check it on the AnyChart Playground and see once again that any data visualization task can be solved with the help of the AnyChart JS charting library (and thanks to quick assistance from our Support Team whenever you face any difficulties).


For more information, look through the full code of the interactive stacked column chart with images:

anychart.onDocumentReady(function() {

  // minimum size of a series logo in px
  var sizeThreshold = 30;

  var data = anychart.data.set([
    ["2018-01-01", 12, 32, 10],
    ["2018-01-02", 8, 2, 12],
    ["2018-01-03", 34, 15, 1],
    ["2018-01-04", 2, 19, 7],
    ["2018-01-05", 10, 12, 9]
  ]);

  var chart = anychart.column();
  chart.yScale().stackMode("value");
  chart.legend(true);
  chart.pointWidth('65%');
  chart.palette(["#087dc9", "#ff8500", "#fd0002"]);

  // set data from the table
  var mapping1 = data.mapAs({'x': 0, 'value': 1});
  var mapping2 = data.mapAs({'x': 0, 'value': 2});
  var mapping3 = data.mapAs({'x': 0, 'value': 3});
  var series1 = chart.column(mapping1);
  series1
    .name('AnyChart')
    .meta('image', 'http://cdn.anychart.com/ts/icons/anychart.svg');

  var series2 = chart.column(mapping2);
  series2
    .name('AnyStock')
    .meta('image', 'http://cdn.anychart.com/ts/icons/anystock.svg');

  var series3 = chart.column(mapping3);
  series3
    .name('AnyMap')
    .meta('image', 'http://cdn.anychart.com/ts/icons/anymap.svg');

  for (var i = 0; i < chart.getSeriesCount(); i++) {
    setupDrawer(chart.getSeriesAt(i), sizeThreshold);  
  }

  chart.container("container").draw();

  //custom drawer
  function setupDrawer(series, sizeThreshold) {
    var images = [];
    var tmp = series.rendering().shapes();
    tmp.push({
      name: 'image',
      shapeType: 'path',
      fillName: 'fill',
      canBeHoveredSelected: true,
      strokeName: 'stroke',
      isHatchFill: false
    });

    series.rendering()
      .shapes(tmp)
      .needsZero(true)
      .point(function() {
      // get shapes group
      var shapes = this.getShapesGroup(this.pointState);

      // calculate the left value of the X axis
      var leftX = this.x - this.pointWidth / 2;
      // calculate the right value of the x axis
      var rightX = leftX + this.pointWidth;

      shapes['path']
      // resets all 'line' operations
        .clear()
        .moveTo(leftX, this.zero)
        .lineTo(leftX, this.value)
        .lineTo(rightX, this.value)
        .lineTo(rightX, this.zero)
      // close by connecting the last point with the first straight line
        .close();

      // draw the logo if the image is greater than a threshold
      if (Math.abs(this.zero - this.value) >= sizeThreshold) {
        shapes['image']
        // resets all 'line' operations
          .clear()
          .moveTo(leftX, this.zero)
          .lineTo(leftX, this.value)
          .lineTo(rightX, this.value)
          .lineTo(rightX, this.zero)
        // close by connecting the last point with the first straight line
          .close()
          .zIndex(3)
          .fill({
          src: series.meta('image'),
          mode: "fit",
          opacity: 1
        });

        shapes = this.getShapesGroup(1);
        shapes['image']
        // resets all 'line' operations
          .clear()
          .moveTo(leftX, this.zero)
          .lineTo(leftX, this.value)
          .lineTo(rightX, this.value)
          .lineTo(rightX, this.zero)
        // close by connecting the last point with the first straight line
          .close()
          .zIndex(2);
      }
    })
  }
});

If you have a question or task you don’t know how to solve, please send it to our Support Team at support@anychart.com with “Challenge” in the subject line. We will do our best to help you deal with it!

New challenges will continue to appear in Challenge AnyChart!, and we hope you enjoyed today’s one about how to create stacked column charts with images on top of the series. Don’t miss a post, and thank you for your interest.


No Comments Yet

*