2
votes

Crosshairs are a nifty feature of Google Charts, but if you have them enabled for a line chart in which the individual points are emphasized (by setting pointSize to slightly larger than lineWidth), then they do something unpleasant: when the user hovers over the corresponding legend entry, the crosshairs will appear for every data point in that graph trace! If you have hundreds of data points in that trace, having hundreds of crosshairs appear, all at once, makes an ugly mess.

This does not happen for line charts in which pointSize is not set (i.e., for which only the lines, and not the actual data points, are visible).

Is there any way to make crosshairs appear only when mousing over the data points on the graph, and not when mousing over the legend, in a line chart which has points made visible by setting pointSize?

Here's what the graph looks like when hovering over one of the data points:

http://www.sealevel.info/crosshairs_problem1.png

how it should look

Here's what it looks like when hovering over the corresponding legend entry:

http://www.sealevel.info/crosshairs_problem2.png

how it looks when hovering the mouse cursor over the legend entry

As you can see, the crosshairs cover up almost everything else.

Here's the web page that generated this graph:

http://sealevel.info/crosshairs_problem.html?=1&quadratic=0&lin_PI=1&boxcar=1&boxwidth=3&lin_ci=1&g_date=1930/1-2019/12&c_date=1950/1-2009/12&s_date=1930/1-2018/12

2

2 Answers

2
votes

using a combination of the chart's 'onmouseover' event,
and a mutation observer, we can prevent the crosshairs on legend hover

the properties passed to the 'onmouseover' event include row and column
of the data point that has been 'hovered'

on legend hover --> row will be null

then use mutation observer to find the new crosshair svg path elements
and change their color to 'transparent'

see following working snippet...

google.charts.load('current', {
  callback: drawChart,
  packages: ['corechart']
});

function drawChart(transparent) {
  var data = new google.visualization.DataTable();
  data.addColumn('number', 'X');
  data.addColumn('number', 'Y');

  for (var i = 0; i < 100; i++) {
    data.addRow([
      Math.floor(Math.random()*100),
      Math.floor(Math.random()*100)
    ]);
  }

  var options = {
    crosshair: {
      trigger: 'both'
    },
    legend: {
      position: 'bottom',
      textStyle: {
        bold: true,
        fontSize: 20
      }
    }
  };

  var chartDiv = document.getElementById('chart_div');
  var chart = new google.visualization.ScatterChart(chartDiv);

  var legendHover = false;

  google.visualization.events.addListener(chart, 'onmouseover', function (gglEvent) {
    // row property will be null on legend hover
    legendHover = (gglEvent.row === null);
  });

  google.visualization.events.addListener(chart, 'onmouseout', function (gglEvent) {
    legendHover = false;
  });

  var observer = new MutationObserver(function (mutations) {
    mutations.forEach(function (mutation) {
      mutation.addedNodes.forEach(function (node) {
        if (node.tagName === 'g') {
          node.childNodes.forEach(function (child) {
            if ((child.tagName === 'path') && (legendHover)) {
              child.setAttribute('stroke', 'transparent');
            }
          });
        }
      });
    });
  });
  observer.observe(chartDiv, {
    childList: true,
    subtree: true
  });

  chart.draw(data, options);
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>

EDIT

the above fails to remove the crosshairs, after selecting the legend then moving the mouse

see following working snippet to prevent this behavior
only drawback, there are no crosshairs when the legend / all points are selected

see following working snippet...

google.charts.load('current', {
  callback: drawChart,
  packages: ['corechart']
});

function drawChart(transparent) {
  var data = new google.visualization.DataTable();
  data.addColumn('number', 'X');
  data.addColumn('number', 'Y');

  for (var i = 0; i < 100; i++) {
    data.addRow([
      Math.floor(Math.random()*100),
      Math.floor(Math.random()*100)
    ]);
  }

  var options = {
    crosshair: {
      trigger: 'both'
    },
    legend: {
      position: 'bottom',
      textStyle: {
        bold: true,
        fontSize: 20
      }
    }
  };

  var chartDiv = document.getElementById('chart_div');
  var chart = new google.visualization.ScatterChart(chartDiv);

  var legendHover = false;
  var selection;

  google.visualization.events.addListener(chart, 'onmouseover', checkLegendHover);
  google.visualization.events.addListener(chart, 'onmouseout', checkLegendHover);

  function checkLegendHover(gglEvent) {
    legendHover = false;
    selection = chart.getSelection();
    if (selection.length > 0) {
      legendHover = (selection[0].row === null);
    }
    if (!legendHover) {
      legendHover = (gglEvent.row === null);
    }
  }

  var observer = new MutationObserver(function (mutations) {
    mutations.forEach(function (mutation) {
      mutation.addedNodes.forEach(function (node) {
        if (node.tagName === 'g') {
          node.childNodes.forEach(function (child) {
            if ((child.tagName === 'path') && (legendHover)) {
              child.setAttribute('stroke', 'transparent');
            }
          });
        }
      });
    });
  });
  observer.observe(chartDiv, {
    childList: true,
    subtree: true
  });

  chart.draw(data, options);
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>
0
votes

I used the mouseover event to detect where is the pointer.

If it is over the legend, remove the crosshair attribute from the chartData options else return it.

mouseOver(event) {
    // if mouse pointer over legend
    if (event.position.row === null && this.googleChartData.options.crosshair) {
      this.googleChartData.options.crosshair = undefined;
      this.chartComponent.draw();
    // if mouse pointer over something else other legend
    } else if (event.position.row !== null && this.googleChartData.options.crosshair === undefined) {
      this.initializeData();
    }
  }

initializeData reassigns googleChartData