[javascript] How to add text inside the doughnut chart using Chart.js?

How to render Text inside the doughnut chart, I am using ChartJs.

This question is related to javascript chart.js

The answer is


Base on @rap-2-h answer,Here the code for using text on doughnut chart on Chart.js for using in dashboard like. It has dynamic font-size for responsive option.

HTML:

<div>text
<canvas id="chart-area" width="300" height="300" style="border:1px solid"/><div>

Script:

var doughnutData = [
            {
                value: 100,
                color:"#F7464A",
                highlight: "#FF5A5E",
                label: "Red"
            },
            {
                value: 50,
                color: "#CCCCCC",
                highlight: "#5AD3D1",
                label: "Green"
            }
        ];

$(document).ready(function(){
  var ctx = $('#chart-area').get(0).getContext("2d");

  var myDoughnut = new Chart(ctx).Doughnut(doughnutData,{
     animation:true,
     responsive: true,
     showTooltips: false,
     percentageInnerCutout : 70,
     segmentShowStroke : false,
     onAnimationComplete: function() {

     var canvasWidthvar = $('#chart-area').width();
     var canvasHeight = $('#chart-area').height();
     //this constant base on canvasHeight / 2.8em
     var constant = 114;
     var fontsize = (canvasHeight/constant).toFixed(2);
     ctx.font=fontsize +"em Verdana";
     ctx.textBaseline="middle"; 
     var total = 0;
     $.each(doughnutData,function() {
       total += parseInt(this.value,10);
   });
  var tpercentage = ((doughnutData[0].value/total)*100).toFixed(2)+"%";
  var textWidth = ctx.measureText(tpercentage).width;

   var txtPosx = Math.round((canvasWidthvar - textWidth)/2);
    ctx.fillText(tpercentage, txtPosx, canvasHeight/2);
  }
 });
});

Here the sample code.try to resize the window. http://jsbin.com/wapono/13/edit


You can also paste mayankcpdixit's code in onAnimationComplete option :

// ...
var myDoughnutChart = new Chart(ctx).Doughnut(data, {
    onAnimationComplete: function() {
        ctx.fillText(data[0].value + "%", 100 - 20, 100, 200);
    }
});

Text will be shown after animation


This is also working at my end...

<div style="width: 100px; height: 100px; float: left; position: relative;">
    <div
        style="width: 100%; height: 40px; position: absolute; top: 50%; left: 0; margin-top: -20px; line-height:19px; text-align: center; z-index: 999999999999999">
        99%<Br />
        Total
    </div>
    <canvas id="chart-area" width="100" height="100" />
</div>

enter image description here


I'd avoid modifying the chart.js code to accomplish this, since it's pretty easy with regular CSS and HTML. Here's my solution:

HTML:

<canvas id="productChart1" width="170"></canvas>
<div class="donut-inner">
    <h5>47 / 60 st</h5>
    <span>(30 / 25 st)</span>
</div>

CSS:

.donut-inner {
   margin-top: -100px;
   margin-bottom: 100px;
}
.donut-inner h5 {
   margin-bottom: 5px;
   margin-top: 0;
}
.donut-inner span {
   font-size: 12px;
}

The output looks like this:

enter image description here


None of the other answers resize the text based off the amount of text and the size of the doughnut. Here is a small script you can use to dynamically place any amount of text in the middle, and it will automatically resize it.

Example: http://jsfiddle.net/kdvuxbtj/

Doughnut With Dynamic Text in the Middle

It will take any amount of text in the doughnut sized perfect for the doughnut. To avoid touching the edges you can set a side-padding as a percentage of the diameter of the inside of the circle. If you don't set it, it will default to 20. You also the color, the font, and the text. The plugin takes care of the rest.

The plugin code will start with a base font size of 30px. From there it will check the width of the text and compare it against the radius of the circle and resize it based off the circle/text width ratio.

It has a default minimum font size of 20px. If the text would exceed the bounds at the minimum font size, it will wrap the text. The default line height when wrapping the text is 25px, but you can change it. If you set the default minimum font size to false, the text will become infinitely small and will not wrap.

It also has a default max font size of 75px in case there is not enough text and the lettering would be too big.

This is the plugin code

Chart.pluginService.register({
  beforeDraw: function(chart) {
    if (chart.config.options.elements.center) {
      // Get ctx from string
      var ctx = chart.chart.ctx;

      // Get options from the center object in options
      var centerConfig = chart.config.options.elements.center;
      var fontStyle = centerConfig.fontStyle || 'Arial';
      var txt = centerConfig.text;
      var color = centerConfig.color || '#000';
      var maxFontSize = centerConfig.maxFontSize || 75;
      var sidePadding = centerConfig.sidePadding || 20;
      var sidePaddingCalculated = (sidePadding / 100) * (chart.innerRadius * 2)
      // Start with a base font of 30px
      ctx.font = "30px " + fontStyle;

      // Get the width of the string and also the width of the element minus 10 to give it 5px side padding
      var stringWidth = ctx.measureText(txt).width;
      var elementWidth = (chart.innerRadius * 2) - sidePaddingCalculated;

      // Find out how much the font can grow in width.
      var widthRatio = elementWidth / stringWidth;
      var newFontSize = Math.floor(30 * widthRatio);
      var elementHeight = (chart.innerRadius * 2);

      // Pick a new font size so it will not be larger than the height of label.
      var fontSizeToUse = Math.min(newFontSize, elementHeight, maxFontSize);
      var minFontSize = centerConfig.minFontSize;
      var lineHeight = centerConfig.lineHeight || 25;
      var wrapText = false;

      if (minFontSize === undefined) {
        minFontSize = 20;
      }

      if (minFontSize && fontSizeToUse < minFontSize) {
        fontSizeToUse = minFontSize;
        wrapText = true;
      }

      // Set font settings to draw it correctly.
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      var centerX = ((chart.chartArea.left + chart.chartArea.right) / 2);
      var centerY = ((chart.chartArea.top + chart.chartArea.bottom) / 2);
      ctx.font = fontSizeToUse + "px " + fontStyle;
      ctx.fillStyle = color;

      if (!wrapText) {
        ctx.fillText(txt, centerX, centerY);
        return;
      }

      var words = txt.split(' ');
      var line = '';
      var lines = [];

      // Break words up into multiple lines if necessary
      for (var n = 0; n < words.length; n++) {
        var testLine = line + words[n] + ' ';
        var metrics = ctx.measureText(testLine);
        var testWidth = metrics.width;
        if (testWidth > elementWidth && n > 0) {
          lines.push(line);
          line = words[n] + ' ';
        } else {
          line = testLine;
        }
      }

      // Move the center up depending on line height and number of lines
      centerY -= (lines.length / 2) * lineHeight;

      for (var n = 0; n < lines.length; n++) {
        ctx.fillText(lines[n], centerX, centerY);
        centerY += lineHeight;
      }
      //Draw text in center
      ctx.fillText(line, centerX, centerY);
    }
  }
});

And you use the following options in your chart object

options: {
  elements: {
    center: {
      text: 'Red is 2/3 the total numbers',
      color: '#FF6384', // Default is #000000
      fontStyle: 'Arial', // Default is Arial
      sidePadding: 20, // Default is 20 (as a percentage)
      minFontSize: 20, // Default is 20 (in px), set to false and text will not wrap.
      lineHeight: 25 // Default is 25 (in px), used for when text wraps
    }
  }
}

Credit to @Jenna Sloan for help with the math used in this solution.


Here is cleaned up and combined example of above solutions - responsive (try to resize the window), supports animation self-aligning, supports tooltips

https://jsfiddle.net/cmyker/u6rr5moq/

_x000D_
_x000D_
Chart.types.Doughnut.extend({_x000D_
    name: "DoughnutTextInside",_x000D_
    showTooltip: function() {_x000D_
        this.chart.ctx.save();_x000D_
        Chart.types.Doughnut.prototype.showTooltip.apply(this, arguments);_x000D_
        this.chart.ctx.restore();_x000D_
    },_x000D_
    draw: function() {_x000D_
        Chart.types.Doughnut.prototype.draw.apply(this, arguments);_x000D_
_x000D_
        var width = this.chart.width,_x000D_
            height = this.chart.height;_x000D_
_x000D_
        var fontSize = (height / 114).toFixed(2);_x000D_
        this.chart.ctx.font = fontSize + "em Verdana";_x000D_
        this.chart.ctx.textBaseline = "middle";_x000D_
_x000D_
        var text = "82%",_x000D_
            textX = Math.round((width - this.chart.ctx.measureText(text).width) / 2),_x000D_
            textY = height / 2;_x000D_
_x000D_
        this.chart.ctx.fillText(text, textX, textY);_x000D_
    }_x000D_
});_x000D_
_x000D_
var data = [{_x000D_
    value: 30,_x000D_
    color: "#F7464A"_x000D_
}, {_x000D_
    value: 50,_x000D_
    color: "#E2EAE9"_x000D_
}, {_x000D_
    value: 100,_x000D_
    color: "#D4CCC5"_x000D_
}, {_x000D_
    value: 40,_x000D_
    color: "#949FB1"_x000D_
}, {_x000D_
    value: 120,_x000D_
    color: "#4D5360"_x000D_
}];_x000D_
_x000D_
var DoughnutTextInsideChart = new Chart($('#myChart')[0].getContext('2d')).DoughnutTextInside(data, {_x000D_
    responsive: true_x000D_
});
_x000D_
<html>_x000D_
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>_x000D_
<script src="//cdnjs.cloudflare.com/ajax/libs/Chart.js/1.0.2/Chart.min.js"></script>_x000D_
<body>_x000D_
    <canvas id="myChart"></canvas>_x000D_
</body>_x000D_
</html>
_x000D_
_x000D_
_x000D_

UPDATE 17.06.16:

Same functionality but for chart.js version 2:

https://jsfiddle.net/cmyker/ooxdL2vj/

_x000D_
_x000D_
var data = {_x000D_
  labels: [_x000D_
    "Red",_x000D_
    "Blue",_x000D_
    "Yellow"_x000D_
  ],_x000D_
  datasets: [_x000D_
    {_x000D_
      data: [300, 50, 100],_x000D_
      backgroundColor: [_x000D_
        "#FF6384",_x000D_
        "#36A2EB",_x000D_
        "#FFCE56"_x000D_
      ],_x000D_
      hoverBackgroundColor: [_x000D_
        "#FF6384",_x000D_
        "#36A2EB",_x000D_
        "#FFCE56"_x000D_
      ]_x000D_
    }]_x000D_
};_x000D_
_x000D_
Chart.pluginService.register({_x000D_
  beforeDraw: function(chart) {_x000D_
    var width = chart.chart.width,_x000D_
        height = chart.chart.height,_x000D_
        ctx = chart.chart.ctx;_x000D_
_x000D_
    ctx.restore();_x000D_
    var fontSize = (height / 114).toFixed(2);_x000D_
    ctx.font = fontSize + "em sans-serif";_x000D_
    ctx.textBaseline = "middle";_x000D_
_x000D_
    var text = "75%",_x000D_
        textX = Math.round((width - ctx.measureText(text).width) / 2),_x000D_
        textY = height / 2;_x000D_
_x000D_
    ctx.fillText(text, textX, textY);_x000D_
    ctx.save();_x000D_
  }_x000D_
});_x000D_
_x000D_
var chart = new Chart(document.getElementById('myChart'), {_x000D_
  type: 'doughnut',_x000D_
  data: data,_x000D_
  options: {_x000D_
   responsive: true,_x000D_
    legend: {_x000D_
      display: false_x000D_
    }_x000D_
  }_x000D_
});
_x000D_
<script src="//cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.6/Chart.bundle.js"></script>_x000D_
<canvas id="myChart"></canvas>
_x000D_
_x000D_
_x000D_


You can use css with relative/absolute positioning if you want it responsive. Plus it can handle easily the multi-line.

https://jsfiddle.net/mgyp0jkk/

<div class="relative">
  <canvas id="myChart"></canvas>      
  <div class="absolute-center text-center">
    <p>Some text</p>
    <p>Some text</p>
  </div>
</div>

I create a demo with 7 jQueryUI Slider and ChartJs (with dynamic text inside)

Chart.types.Doughnut.extend({
        name: "DoughnutTextInside",
        showTooltip: function() {
            this.chart.ctx.save();
            Chart.types.Doughnut.prototype.showTooltip.apply(this, arguments);
            this.chart.ctx.restore();
        },
        draw: function() {
            Chart.types.Doughnut.prototype.draw.apply(this, arguments);

            var width = this.chart.width,
                height = this.chart.height;

            var fontSize = (height / 140).toFixed(2);
            this.chart.ctx.font = fontSize + "em Verdana";
            this.chart.ctx.textBaseline = "middle";

            var red = $( "#red" ).slider( "value" ),
            green = $( "#green" ).slider( "value" ),
            blue = $( "#blue" ).slider( "value" ),
            yellow = $( "#yellow" ).slider( "value" ),
            sienna = $( "#sienna" ).slider( "value" ),
            gold = $( "#gold" ).slider( "value" ),
            violet = $( "#violet" ).slider( "value" );
            var text = (red+green+blue+yellow+sienna+gold+violet) + " minutes";
            var textX = Math.round((width - this.chart.ctx.measureText(text).width) / 2);
            var textY = height / 2;
            this.chart.ctx.fillStyle = '#000000';
            this.chart.ctx.fillText(text, textX, textY);
        }
    });


var ctx = $("#myChart").get(0).getContext("2d");
var myDoughnutChart = new Chart(ctx).DoughnutTextInside(data, {
    responsive: false
});

DEMO IN JSFIDDLE

enter image description here


Alesana's solution works very nicely for me in general, but like others, I wanted to be able to specify where line breaks occur. I made some simple modifications to wrap lines at '\n' characters, as long as the text is already being wrapped. A more complete solution would force wrapping if there are any '\n' characters in the text, but I don't have time at the moment to make that work with font sizing. The change also centers a little better horizontally when wrapping (avoids trailing spaces). The code's below (I can't post comments yet).

It would be cool if someone put this plug-in on GitHub...

Chart.pluginService.register({
  beforeDraw: function(chart) {
    if (chart.config.options.elements.center) {
      // Get ctx from string
      var ctx = chart.chart.ctx;

      // Get options from the center object in options
      var centerConfig = chart.config.options.elements.center;
      var fontStyle = centerConfig.fontStyle || 'Arial';
      var txt = centerConfig.text;
      var color = centerConfig.color || '#000';
      var maxFontSize = centerConfig.maxFontSize || 75;
      var sidePadding = centerConfig.sidePadding || 20;
      var sidePaddingCalculated = (sidePadding / 100) * (chart.innerRadius * 2)
      // Start with a base font of 30px
      ctx.font = "30px " + fontStyle;

      // Get the width of the string and also the width of the element minus 10 to give it 5px side padding
      var stringWidth = ctx.measureText(txt).width;
      var elementWidth = (chart.innerRadius * 2) - sidePaddingCalculated;

      // Find out how much the font can grow in width.
      var widthRatio = elementWidth / stringWidth;
      var newFontSize = Math.floor(30 * widthRatio);
      var elementHeight = (chart.innerRadius * 2);

      // Pick a new font size so it will not be larger than the height of label.
      var fontSizeToUse = Math.min(newFontSize, elementHeight, maxFontSize);
      var minFontSize = centerConfig.minFontSize;
      var lineHeight = centerConfig.lineHeight || 25;
      var wrapText = false;

      if (minFontSize === undefined) {
        minFontSize = 20;
      }

      if (minFontSize && fontSizeToUse < minFontSize) {
        fontSizeToUse = minFontSize;
        wrapText = true;
      }

      // Set font settings to draw it correctly.
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      var centerX = ((chart.chartArea.left + chart.chartArea.right) / 2);
      var centerY = ((chart.chartArea.top + chart.chartArea.bottom) / 2);
      ctx.font = fontSizeToUse + "px " + fontStyle;
      ctx.fillStyle = color;

      if (!wrapText) {
        ctx.fillText(txt, centerX, centerY);
        return;
      }

      var lines = [];
      var chunks = txt.split('\n');
      for (var m = 0; m < chunks.length; m++) {
        var words = chunks[m].split(' ');
        var line;

        // Break words up into multiple lines if necessary
        for (var n = 0; n < words.length; n++) {
          var testLine = (n == 0) ? words[n] : line + ' ' + words[n];
          var metrics = ctx.measureText(testLine);
          var testWidth = metrics.width;
          if (testWidth > elementWidth && n > 0) {
            lines.push(line);
            line = words[n];
          } else {
            line = testLine;
          }
        }
        lines.push(line);
      }

      // Move the center up depending on line height and number of lines
      centerY -= ((lines.length-1) / 2) * lineHeight;

      // All but last line
      for (var n = 0; n < lines.length; n++) {
        ctx.fillText(lines[n], centerX, centerY);
        centerY += lineHeight;
      }
    }
  }
});

@Cmyker, great solution for chart.js v2

One little enhancement: It makes sense to check for the appropriate canvas id, see the modified snippet below. Otherwise the text (i.e. 75%) is also rendered in middle of other chart types within the page.

  Chart.pluginService.register({
    beforeDraw: function(chart) {
      if (chart.canvas.id === 'doghnutChart') {
        let width = chart.chart.width,
            height = chart.chart.outerRadius * 2,
            ctx = chart.chart.ctx;

        rewardImg.width = 40;
        rewardImg.height = 40;
        let imageX = Math.round((width - rewardImg.width) / 2),
            imageY = (height - rewardImg.height ) / 2;

        ctx.drawImage(rewardImg, imageX, imageY, 40, 40);
        ctx.save();
      }
    }
  });

Since a legend (see: http://www.chartjs.org/docs/latest/configuration/legend.html) magnifies the chart height, the value for height should be obtained by the radius.


This is based on Cmyker's update for Chart.js 2. (posted as another answer as I can't comment yet)

I had an issue with the text alignment on Chrome when the legend is displayed as the chart height does not include this so it's not aligned correctly in the middle. Fixed this by accounting for this in the calculation of fontSize and textY.

I calculated percentage inside the method rather than a set value as I have multiple of these on the page. Assumptions are that your chart only has 2 values (otherwise what is the percentage of? and that the first is the one you want to show the percentage for. I have a bunch of other charts too so I do a check for type = doughnut. I'm only using doughnuts to show percentages so it works for me.

Text color seems a bit hit and miss depending on what order things run in etc so I ran into an issue when resizing that the text would change color (between black and the primary color in one case, and secondary color and white in another) so I "save" whatever the existing fill style was, draw the text (in the color of the primary data) then restore the old fill style. (Preserving the old fill style doesn't seem needed but you never know.)

https://jsfiddle.net/g733tj8h/

Chart.pluginService.register({
  beforeDraw: function(chart) {
    var width = chart.chart.width,
        height = chart.chart.height,
        ctx = chart.chart.ctx,
        type = chart.config.type;

    if (type == 'doughnut')
    {
      var percent = Math.round((chart.config.data.datasets[0].data[0] * 100) /
                    (chart.config.data.datasets[0].data[0] +
                    chart.config.data.datasets[0].data[1]));
      var oldFill = ctx.fillStyle;
      var fontSize = ((height - chart.chartArea.top) / 100).toFixed(2);

      ctx.restore();
      ctx.font = fontSize + "em sans-serif";
      ctx.textBaseline = "middle"

      var text = percent + "%",
          textX = Math.round((width - ctx.measureText(text).width) / 2),
          textY = (height + chart.chartArea.top) / 2;

      ctx.fillStyle = chart.config.data.datasets[0].backgroundColor[0];
      ctx.fillText(text, textX, textY);
      ctx.fillStyle = oldFill;
      ctx.save();
    }
  }
});

_x000D_
_x000D_
var data = {_x000D_
  labels: ["Red","Blue"],_x000D_
  datasets: [_x000D_
    {_x000D_
      data: [300, 50],_x000D_
      backgroundColor: ["#FF6384","#36A2EB"],_x000D_
    }]_x000D_
};_x000D_
_x000D_
Chart.pluginService.register({_x000D_
  beforeDraw: function(chart) {_x000D_
    var width = chart.chart.width,_x000D_
        height = chart.chart.height,_x000D_
        ctx = chart.chart.ctx,_x000D_
        type = chart.config.type;_x000D_
_x000D_
    if (type == 'doughnut')_x000D_
    {_x000D_
     var percent = Math.round((chart.config.data.datasets[0].data[0] * 100) /_x000D_
                    (chart.config.data.datasets[0].data[0] +_x000D_
                    chart.config.data.datasets[0].data[1]));_x000D_
   var oldFill = ctx.fillStyle;_x000D_
      var fontSize = ((height - chart.chartArea.top) / 100).toFixed(2);_x000D_
      _x000D_
      ctx.restore();_x000D_
      ctx.font = fontSize + "em sans-serif";_x000D_
      ctx.textBaseline = "middle"_x000D_
_x000D_
      var text = percent + "%",_x000D_
          textX = Math.round((width - ctx.measureText(text).width) / 2),_x000D_
          textY = (height + chart.chartArea.top) / 2;_x000D_
   _x000D_
      ctx.fillStyle = chart.config.data.datasets[0].backgroundColor[0];_x000D_
      ctx.fillText(text, textX, textY);_x000D_
      ctx.fillStyle = oldFill;_x000D_
      ctx.save();_x000D_
    }_x000D_
  }_x000D_
});_x000D_
_x000D_
var myChart = new Chart(document.getElementById('myChart'), {_x000D_
  type: 'doughnut',_x000D_
  data: data,_x000D_
  options: {_x000D_
   responsive: true,_x000D_
    legend: {_x000D_
      display: true_x000D_
    }_x000D_
  }_x000D_
});
_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.6/Chart.bundle.js"></script>_x000D_
<canvas id="myChart"></canvas>
_x000D_
_x000D_
_x000D_


First of all, kudos on choosing Chart.js! I'm using it on one of my current projects and I absolutely love it - it does the job perfectly.

Although labels/tooltips are not part of the library yet, you may want to take a look at these three pull requests:

And, as Cracker0dks mentioned, Chart.js uses canvas for rendering so you may as well just implement your own tooltips by interacting with it directly.

Hope this helps.


@rap-2-h and @Ztuons Ch's answer doesn't allow for the showTooltips option to be active, but what you can do is create and layer a second canvas object behind the one rendering the chart.

The important part is the styling required in the divs and for the canvas object itself so that they render on top of each other.

_x000D_
_x000D_
var data = [_x000D_
    {value : 100, color : 'rgba(226,151,093,1)', highlight : 'rgba(226,151,093,0.75)', label : "Sector 1"},_x000D_
    {value : 100, color : 'rgba(214,113,088,1)', highlight : 'rgba(214,113,088,0.75)', label : "Sector 2"},_x000D_
    {value : 100, color : 'rgba(202,097,096,1)', highlight : 'rgba(202,097,096,0.75)', label : "Sector 3"}_x000D_
]_x000D_
_x000D_
var options = { showTooltips : true };_x000D_
     _x000D_
var total = 0;_x000D_
for (i = 0; i < data.length; i++) {_x000D_
     total = total + data[i].value;_x000D_
}_x000D_
_x000D_
var chartCtx = $("#canvas").get(0).getContext("2d");_x000D_
var chart = new Chart(chartCtx).Doughnut(data, options);_x000D_
_x000D_
var textCtx = $("#text").get(0).getContext("2d");_x000D_
textCtx.textAlign = "center";_x000D_
textCtx.textBaseline = "middle";_x000D_
textCtx.font = "30px sans-serif";_x000D_
textCtx.fillText(total, 150, 150);
_x000D_
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>_x000D_
<script src="//cdnjs.cloudflare.com/ajax/libs/Chart.js/1.0.2/Chart.min.js"></script>_x000D_
<html>_x000D_
<body>_x000D_
<div style="position: relative; width:300px; height:300px;">_x000D_
    <canvas id="text" _x000D_
            style="z-index: 1; _x000D_
                   position: absolute;_x000D_
                   left: 0px; _x000D_
                   top: 0px;" _x000D_
            height="300" _x000D_
            width="300"></canvas>_x000D_
    <canvas id="canvas" _x000D_
            style="z-index: 2; _x000D_
                   position: absolute;_x000D_
                   left: 0px; _x000D_
                   top: 0px;" _x000D_
            height="300" _x000D_
            width="300"></canvas>_x000D_
</div>_x000D_
</body>_x000D_
</html>
_x000D_
_x000D_
_x000D_

Here's the jsfiddle: https://jsfiddle.net/68vxqyak/1/