Search code examples
javascriptcssangularjsgoogle-visualizationtext-alignment

How to align axis label with extended major gridlines outside the chartarea in google bubble chart


I am using google visualization bubble chart, I need to align the vertical axis labels something like below, I want to align the labels to the margin of the chart not to the axis line, also need 2 lines and extend the major grid line to outside of the chart area. enter image description here

Also here is the code :

<div data-ng-app="mainApp" data-ng-controller="mainSearchController"
     ng-init="ShowChart()">
    <div class="row" ng-mouseover="mousepoints($event)">
        <div google-chart chart="saleChart"
             agc-on-mouseover="showTooltip(row)"
             agc-on-mouseout="hideTooltip()">
        </div>
        <div id="custom_tooltip"
             style="position:fixed; border:0px solid #777777;
                    padding-left:10px; line-height:15px; color:#5f5f5f;
                    font-family:Arial; background-color:#FFFFFF;
                    height:auto; width:auto; font-size:10px;">
        </div>
    </div>
</div>

And here is the angularjs code to bind the chart

var app = angular.module('mainApp', ['googlechart']);

app.controller('mainSearchController', function ($scope) {
    
    $scope.ShowChart = function () {     
        var saleChart = {};
        saleChart.type = 'BubbleChart';
        saleChart.cssStyle = "height:100%; width:100%;";
        var options = {
            sizeAxis: {
                maxSize: 7,
                minSize: 1
            },
            fontSize:10,
            legend: 'none',
            height: 200,
            width: 400,
            bubble: { stroke: '#fdca0f', opacity: 1 },
            colors: ['#fdca0f', '#fdca0f'],
            tooltip: {
                trigger: 'none'
            },
            hAxis: {
                ticks: [
                    { v: 800, f: '2015' },
                    { v: 1200, f: '2016' },
                    { v: 1600, f: '2017' },
                    { v: 2000, f: '2018' },
                    { v: 2400, f: '2019' },
                    { v: 2800, f: '2020' }
                ],
                gridlines: { color: '#dedede' },
                minorGridlines: { color: '#f7f7f7', count: 3 },
                textStyle: { color: '#5f5f5f' }    
            },
            vAxis: {
                ticks: [
                    { v: 1, f: 'Chennai in March' },
                    { v: 2, f: 'Mumbai in March' },
                    { v: 3, f: 'Delhi in April' },
                    { v: 4, f: 'Chennai in April' }
                    
                ],
                gridlines: { color: '#dedede' },
                textStyle: { color: '#5f5f5f' }
            }
        };
            
        var d = [
          ["Name", "Year", "Place", "", "Sales", "tooltip"],
          ["", 1000, 2, "", 26, "Sale List"],
          ["",1200,3,"",28,"Sale List"],
          ["",1400,3,"",48,"S"],
          ["",1600,3,"",29,"S"]
        ];
        saleChart.data = d;
        $scope.chartData = d;
        saleChart.options = options;
        $scope.saleChart = saleChart;        
        
    }

    var mouseX;
    var mouseY;
    $scope.mousepoints = function (e) {
        mouseX = e.pageX;
        mouseY = e.pageY;
    }

    $scope.showTooltip = function (row) {    
        var x = mouseX;
        var y = mouseY + 10;        
        if (row != null) {        
            dataTable = google.visualization.arrayToDataTable($scope.chartData);
            var v = dataTable.getValue(row, 5);
            //var v = $scope.chartData.rows[row][5];
            v = v.toString().replace(/,/g, "<br/>")

            $('#custom_tooltip').html('<div>' + v + '</div>').css({
                'top': y,
                'left': x
            }).fadeIn('slow');
        }
    }
    $scope.hideTooltip = function () {
        $('#custom_tooltip').fadeOut('fast');
    }
});

Solution

  • the requested changes can only be made by manually modifying the chart's SVG,
    this can be done on the chart's 'ready' event.

    first, add the ready event to the <div google-chart> element...

    <div google-chart chart="saleChart" agc-on-ready="onReady(chartWrapper)"
         agc-on-mouseover="showTooltip(row)" agc-on-mouseout="hideTooltip()">
    </div>
    

    then add the listener to the controller...

    in order to move the labels down, find the <text> elements,
    and the change their 'y' attribute.

    as for the grid lines (<rect>), we need to change the 'x' attribute, as well as the 'width'.
    not only on the grid lines, but the <rect> elements that contain the grid lines.

    // ready event
    $scope.onReady = function (chartWrapper) {
      // find, move labels
      var labels = chartWrapper.getChart().getContainer().getElementsByTagName('text');
      Array.prototype.forEach.call(labels, function(label) {
        if (label.getAttribute('text-anchor') === 'end') {
          var yLabel = parseFloat(label.getAttribute('y')) + (parseFloat(label.getAttribute('font-size')) * 2);
          label.setAttribute('y', yLabel);
        }
      });
    
      // find, expand grid lines
      var gridLines = chartWrapper.getChart().getContainer().getElementsByTagName('rect');
      Array.prototype.forEach.call(gridLines, function(line) {
        if ((line.getAttribute('height') === '1') ||
            ((line.getAttribute('x') !== '0') &&
            ((line.getAttribute('fill') === null) || (line.getAttribute('fill') === '#ffffff')))) {
          var lineWidth = parseFloat(line.getAttribute('width')) + parseFloat(line.getAttribute('x')) - 2;
          line.setAttribute('x', 2);
          line.setAttribute('width', lineWidth);
        }
      });
    }
    

    see following working snippet...

    var app = angular.module('mainApp', ['googlechart']);
    
    app.controller('mainSearchController', function ($scope) {
        $scope.ShowChart = function () {
            var saleChart = {};
            saleChart.type = 'BubbleChart';
            saleChart.cssStyle = "height:100%; width:100%;";
            var options = {
                sizeAxis: {
                    maxSize: 7,
                    minSize: 1
                },
                fontSize:10,
                legend: 'none',
                height: 200,
                width: 400,
                bubble: { stroke: '#fdca0f', opacity: 1 },
                colors: ['#fdca0f', '#fdca0f'],
                tooltip: {
                    trigger: 'none'
                },
                hAxis: {
                    ticks: [
                        { v: 800, f: '2015' },
                        { v: 1200, f: '2016' },
                        { v: 1600, f: '2017' },
                        { v: 2000, f: '2018' },
                        { v: 2400, f: '2019' },
                        { v: 2800, f: '2020' }
                    ],
                    gridlines: { color: '#dedede' },
                    minorGridlines: { color: '#f7f7f7', count: 3 },
                    textStyle: { color: '#5f5f5f' }
                },
                vAxis: {
                    ticks: [
                        // add line break --> \n
                        { v: 1, f: 'Chennai\nin March' },
                        { v: 2, f: 'Mumbai\nin March' },
                        { v: 3, f: 'Delhi\nin April' },
                        { v: 4, f: 'Chennai\nin April' }
                    ],
                    gridlines: { color: '#dedede' },
                    textStyle: { color: '#5f5f5f' }
                }
            };
    
            var d = [["Name", "Year", "Place", "", "Sales", "tooltip"],
            ["", 1000, 2, "", 26, "Sale List"],
            ["",1200,3,"",28,"Sale List"],
            ["",1400,3,"",48,"S"],["",1600,3,"",29,"S"]];
            saleChart.data = d;
            $scope.chartData = d;
            saleChart.options = options;
            $scope.saleChart = saleChart;
    
        }
    
        var mouseX;
        var mouseY;
        $scope.mousepoints = function (e) {
            mouseX = e.pageX;
            mouseY = e.pageY;
        }
    
        $scope.showTooltip = function (row) {
            var x = mouseX;
            var y = mouseY + 10;
            if (row != null) {
                dataTable = google.visualization.arrayToDataTable($scope.chartData);
                var v = dataTable.getValue(row, 5);
                //var v = $scope.chartData.rows[row][5];
                v = v.toString().replace(/,/g, "<br/>")
    
                $('#custom_tooltip').html('<div>' + v + '</div>').css({
                    'top': y,
                    'left': x
                }).fadeIn('slow');
            }
        }
        $scope.hideTooltip = function () {
            $('#custom_tooltip').fadeOut('fast');
        }
        
        $scope.onReady = function (chartWrapper) {
          var labels = chartWrapper.getChart().getContainer().getElementsByTagName('text');
          var labelIndex = 0;
          var nextLabels = [];
          Array.prototype.forEach.call(labels, function(label) {
            // find label
            if (label.getAttribute('text-anchor') === 'end') {
              // move label down
              var yLabel = parseFloat(label.getAttribute('y')) + (parseFloat(label.getAttribute('font-size')) * 1.5);
              label.setAttribute('y', yLabel);
              
              // set text line 1
              var labelText = chartWrapper.getOption('vAxis.ticks')[labelIndex].f.split('\n');
              label.textContent = labelText[0].toUpperCase();
              
              // save label
              nextLabels.push(label);
              labelIndex++;
            }
          });
          
          // add line 2
          nextLabels.forEach(function (label, labelIndex) {
            var yLabel = parseFloat(label.getAttribute('y')) + (parseFloat(label.getAttribute('font-size')) + 1);
            var nextLabel = label.parentNode.appendChild(label.cloneNode(true)); 
            var labelText = chartWrapper.getOption('vAxis.ticks')[labelIndex].f.split('\n');
            nextLabel.textContent = labelText[1];
            nextLabel.setAttribute('y', yLabel);
            
            // increase font size of line 1
            label.setAttribute('font-size', (parseFloat(label.getAttribute('font-size')) + 1));
            
            // re-align labels to left
            var labelWidth = label.getBBox().width;
            label.setAttribute('x', labelWidth + 2);
            labelWidth = nextLabel.getBBox().width;
            nextLabel.setAttribute('x', labelWidth + 2);
          });
    
    
          var gridLines = chartWrapper.getChart().getContainer().getElementsByTagName('rect');
          Array.prototype.forEach.call(gridLines, function(line) {
            if ((line.getAttribute('height') === '1') ||
                ((line.getAttribute('x') !== '0') &&
                ((line.getAttribute('fill') === null) || (line.getAttribute('fill') === '#ffffff')))) {
              var lineWidth = parseFloat(line.getAttribute('width')) + parseFloat(line.getAttribute('x')) - 2;
              line.setAttribute('x', 2);
              line.setAttribute('width', lineWidth);
            }
          });
        }
    });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.8/angular.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular-google-chart/0.1.0/ng-google-chart.min.js"></script>
    
    <div data-ng-app="mainApp" data-ng-controller="mainSearchController" ng-init="ShowChart()">
      <div class="row" ng-mouseover="mousepoints($event)">
        <div google-chart chart="saleChart" agc-on-mouseover="showTooltip(row)" agc-on-mouseout="hideTooltip()" agc-on-ready="onReady(chartWrapper)"></div>
        <div id="custom_tooltip" style="position:fixed; border:0px solid #777777; padding-left:10px; line-height:15px; color:#5f5f5f; font-family:Arial; background-color:#FFFFFF; height:auto; width:auto; font-size:10px;"></div>
      </div>
    </div>