Search code examples
chartsannotationsgoogle-visualizationstacked-chartreact-google-charts

google charts bar horizontal move annotation position to center of stacked chart


I am using Google Chart's column graph chart. The chart is a stacked column chart with annotations for every data point of the stacked column. The annotation are at the right of the inside of the bar but I would like them to be centered inside of the bar.

I'm looking this page with a begin solution. This page with an function "moveAnnotations()", but I can't get the code for a horizontal bar chart.

Thanks for your help, i'm lost. :(

 
/** Valeurs pour le graph bar 1 */
var data_graph_bar_1 = [
    ['Years', 'Beer & other', 'Water', 'CSD', 'Sensitive'],
    ['2014', 71, 56, 79, 59],
    ['2019', 70, 74, 75, 65]
];
 
        // Load the Visualization API and the corechart package.
        google.charts.load('current', {'packages':['corechart']});

        // Set a callback to run when the Google Visualization API is loaded.
        google.charts.setOnLoadCallback(function(){drawChart(data_graph_bar_1)});

        // Callback that creates and populates a data table,
      // instantiates the pie chart, passes in the data and
      // draws it.
      function drawChart(datas) {

            var data = google.visualization.arrayToDataTable(datas);
            var view = new google.visualization.DataView(data);

            view.setColumns([0,
                1, {
                calc: function (dt, row) {
                    return dt.getValue(row, 1);
                },
                type: "number",
                role: "annotation"
                },
                2, {
                calc: function (dt, row) {
                    return dt.getValue(row, 2);
                },
                type: "number",
                role: "annotation"
                },
                3, {
                calc: function (dt, row) {
                    return dt.getValue(row, 3);
                },
                type: "number",
                role: "annotation"
                },
                4, {
                calc: function (dt, row) {
                    return dt.getValue(row, 4);
                },
                type: "number",
                role: "annotation"
                },
                {
                calc: function (dt, row) {
                    return 0;
                },
                label: "Total",
                type: "number",
                },
                {
                calc: function (dt, row) {
                    return dt.getValue(row, 1) + dt.getValue(row, 2)+ dt.getValue(row, 3)+ dt.getValue(row, 4);
                },
                type: "number",
                role: "annotation"
                }
            ]);
            var options = {
                height: 130,
                colors: ['#B4CC00', '#3399FF', '#E64B00','#FF8B00'],
                legend: 'none',
                bar: { groupWidth: '75%' },
                isStacked: true,
                displayAnnotations: true,
                annotations: {
                    textStyle: {
                    // The color of the text.
                        color: '#000000',
                        fontSize: 15
                    },
                },
                hAxis: {
                    gridlines: {
                        count: 0
                    },
                    textPosition: 'none',
                    textStyle : {
                        fontSize: 15
                    }
                },
                vAxis: {
                    textStyle: {
                        bold: true,
                        fontSize: '20',
                    }
                },
                chartArea:{
                    left:50,
                },
            };
            
            // Instantiate and draw our chart, passing in some options.
            var chart = new google.visualization.BarChart(document.getElementById('chart_div'));
            chart.draw(view, options);
        }
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>


Solution

  • instead of adjusting the 'y' attribute of the label,
    we just need to adjust the 'x' attribute...

    first, we need to locate the annotation label.
    here, we loop all the row and columns, then use the value to find the label.

    we exclude labels with black color, because these are shown in the tooltip,
    we do not want to move those.

      for (var r = 0; r < data.getNumberOfRows(); r++) {
        for (var c = 1; c < data.getNumberOfColumns(); c++) {
          var labels = chart.getContainer().getElementsByTagName('text');
          Array.prototype.forEach.call(labels, function(label, index) {
            if ((label.textContent === data.getValue(r, c).toFixed(0)) &&
                (label.getAttribute('fill') !== '#000000')) {
              barBounds = chartLayout.getBoundingBox('bar#' + (c - 1) + '#' + r);
              xAdj = (barBounds.width / 2);
              label.setAttribute('x', barBounds.left + xAdj);
              label.setAttribute('text-anchor', 'middle');
            }
          });
        }
      }
    

    see following working snippet...

    var data_graph_bar_1 = [
        ['Years', 'Beer & other', 'Water', 'CSD', 'Sensitive'],
        ['2014', 71, 56, 79, 59],
        ['2019', 70, 74, 75, 65]
    ];
    
    google.charts.load('current', {'packages':['corechart']});
    google.charts.setOnLoadCallback(function(){drawChart(data_graph_bar_1)});
    
    function drawChart(datas) {
    
        var data = google.visualization.arrayToDataTable(datas);
        var view = new google.visualization.DataView(data);
    
        view.setColumns([0,
            1, {
            calc: function (dt, row) {
                return dt.getValue(row, 1);
            },
            type: "number",
            role: "annotation"
            },
            2, {
            calc: function (dt, row) {
                return dt.getValue(row, 2);
            },
            type: "number",
            role: "annotation"
            },
            3, {
            calc: function (dt, row) {
                return dt.getValue(row, 3);
            },
            type: "number",
            role: "annotation"
            },
            4, {
            calc: function (dt, row) {
                return dt.getValue(row, 4);
            },
            type: "number",
            role: "annotation"
            },
            {
            calc: function (dt, row) {
                return 0;
            },
            label: "Total",
            type: "number",
            },
            {
            calc: function (dt, row) {
                return dt.getValue(row, 1) + dt.getValue(row, 2)+ dt.getValue(row, 3)+ dt.getValue(row, 4);
            },
            type: "number",
            role: "annotation"
            }
        ]);
        var options = {
            height: 130,
            colors: ['#B4CC00', '#3399FF', '#E64B00','#FF8B00'],
            legend: 'none',
            bar: { groupWidth: '75%' },
            isStacked: true,
            displayAnnotations: true,
            annotations: {
                textStyle: {
                // The color of the text.
                    color: '#000000',
                    fontSize: 15
                },
            },
            hAxis: {
                gridlines: {
                    count: 0
                },
                textPosition: 'none',
                textStyle : {
                    fontSize: 15
                }
            },
            vAxis: {
                textStyle: {
                    bold: true,
                    fontSize: '20',
                }
            },
            chartArea:{
                left:50,
            },
        };
    
        // Instantiate and draw our chart, passing in some options.
        var chart = new google.visualization.BarChart(document.getElementById('chart_div'));
    
        google.visualization.events.addListener(chart, 'ready', function () {
          var observer = new MutationObserver(moveAnnotations);
          observer.observe(chart.getContainer(), {
            childList: true,
            subtree: true
          });
        });
    
        function moveAnnotations() {
          var chartLayout = chart.getChartLayoutInterface();
          var barBounds;
          var xAdj;
    
          for (var r = 0; r < data.getNumberOfRows(); r++) {
            for (var c = 1; c < data.getNumberOfColumns(); c++) {
              var labels = chart.getContainer().getElementsByTagName('text');
              Array.prototype.forEach.call(labels, function(label, index) {
                if ((label.textContent === data.getValue(r, c).toFixed(0)) && (label.getAttribute('fill') !== '#000000')) {
                  barBounds = chartLayout.getBoundingBox('bar#' + (c - 1) + '#' + r);
                  xAdj = (barBounds.width / 2);
                  label.setAttribute('x', barBounds.left + xAdj);
                  label.setAttribute('text-anchor', 'middle');
                }
              });
            }
          }
        }
    
        chart.draw(view, options);
    }
    <script src="https://www.gstatic.com/charts/loader.js"></script>
    <div id="chart_div"></div>

    EDIT

    if there are more than one annotations with the same value,
    then we need to add another condition.

    similar to getting the bar bounds from the chart's interface,
    we can get the annotation bounds.

    labelBounds = chartLayout.getBoundingBox('annotationtext#' + (c - 1) + '#' + r + '#0');
    

    and since the initial alignment of the annotation is to the right,
    or text-anchor="end",
    this means the difference between the 'x' attribute on the <text> element and the left property of the bounds will equal the width of the bounds.
    we can use this difference to ensure we have the correct annotation before moving it.

    ((parseFloat(label.getAttribute('x')) - labelBounds.left) === labelBounds.width)
    

    see following working snippet...

    var data_graph_bar_1 = [
        ['Years', 'Beer & other', 'Water', 'CSD', 'Sensitive'],
        ['2014', 71, 56, 74, 59],
        ['2019', 70, 74, 75, 65]
    ];
    
    google.charts.load('current', {'packages':['corechart']});
    google.charts.setOnLoadCallback(function(){drawChart(data_graph_bar_1)});
    
    function drawChart(datas) {
    
        var data = google.visualization.arrayToDataTable(datas);
        var view = new google.visualization.DataView(data);
    
        view.setColumns([0,
            1, {
            calc: function (dt, row) {
                return dt.getValue(row, 1);
            },
            type: "number",
            role: "annotation"
            },
            2, {
            calc: function (dt, row) {
                return dt.getValue(row, 2);
            },
            type: "number",
            role: "annotation"
            },
            3, {
            calc: function (dt, row) {
                return dt.getValue(row, 3);
            },
            type: "number",
            role: "annotation"
            },
            4, {
            calc: function (dt, row) {
                return dt.getValue(row, 4);
            },
            type: "number",
            role: "annotation"
            },
            {
            calc: function (dt, row) {
                return 0;
            },
            label: "Total",
            type: "number",
            },
            {
            calc: function (dt, row) {
                return dt.getValue(row, 1) + dt.getValue(row, 2)+ dt.getValue(row, 3)+ dt.getValue(row, 4);
            },
            type: "number",
            role: "annotation"
            }
        ]);
        var options = {
            height: 130,
            colors: ['#B4CC00', '#3399FF', '#E64B00','#FF8B00'],
            legend: 'none',
            bar: { groupWidth: '75%' },
            isStacked: true,
            displayAnnotations: true,
            annotations: {
                textStyle: {
                // The color of the text.
                    color: '#000000',
                    fontSize: 15
                },
            },
            hAxis: {
                gridlines: {
                    count: 0
                },
                textPosition: 'none',
                textStyle : {
                    fontSize: 15
                }
            },
            vAxis: {
                textStyle: {
                    bold: true,
                    fontSize: '20',
                }
            },
            chartArea:{
                left:50,
            },
        };
    
        // Instantiate and draw our chart, passing in some options.
        var chart = new google.visualization.BarChart(document.getElementById('chart_div'));
    
        google.visualization.events.addListener(chart, 'ready', function () {
          var observer = new MutationObserver(moveAnnotations);
          observer.observe(chart.getContainer(), {
            childList: true,
            subtree: true
          });
        });
    
        function moveAnnotations() {
          var chartLayout = chart.getChartLayoutInterface();
          var barBounds;
          var xAdj;
    
          for (var r = 0; r < data.getNumberOfRows(); r++) {
            for (var c = 1; c < data.getNumberOfColumns(); c++) {
              var labels = chart.getContainer().getElementsByTagName('text');
              Array.prototype.forEach.call(labels, function(label, index) {
                labelBounds = chartLayout.getBoundingBox('annotationtext#' + (c - 1) + '#' + r + '#0');
                barBounds = chartLayout.getBoundingBox('bar#' + (c - 1) + '#' + r);
                if ((label.textContent === data.getValue(r, c).toFixed(0)) && (label.getAttribute('fill') !== '#000000') && ((parseFloat(label.getAttribute('x')) - labelBounds.left) === labelBounds.width)) {
                  xAdj = (barBounds.width / 2);
                  label.setAttribute('x', barBounds.left + xAdj);
                  label.setAttribute('text-anchor', 'middle');
                }
              });
            }
          }
        }
    
        chart.draw(view, options);
    }
    <script src="https://www.gstatic.com/charts/loader.js"></script>
    <div id="chart_div"></div>