Advanced Visualization of Spot in JS Polar Charts — Challenge AnyChart!

October 27th, 2017 by Vitaly Radionov

Advanced Visualization of Spot in JS Polar Charts — Challenge AnyChart!Meet Challenge AnyChart, a brand new feature on our blog that every dataviz engineer will fall in love with! Each article in this series will be a quick tutorial on how to complete a specific data visualization task that – at first sight – seems too complicated or even unsolvable with AnyChart JS Charts.

From now on, once in a couple of weeks, we’ll pick a thrilling challenge – one of those you’ve shared with our Support Team. And then we’ll show you the best way to deal with it. So all of you can see: Nothing is impossible in AnyChart!

The first challenge for the very first Challenge AnyChart issue is about making an advanced interactive visualization of the spot in a JavaScript polar chart.

Data Visualization Task

The task for this challenge is as follows:

How to visualize the borders of the spot and its tangent lines drawn from the center of a polar chart in the bySpot interactivity mode to highlight markers on the polar lines by area.

Take a look at the target picture:

Visualizing the spot in an interactive JavaScript (HTML5) polar chart

And right now let’s see how to easily get such a great interactive data visualization with AnyChart!

To solve this task, we need the following:

  • a few mathematical calculations;
  • methods from the API of AnyChart JS Charts;
  • methods from the API of GraphicsJS, the graphics engine in AnyChart.

Set Basic bySpot Interactivity

First things first – let’s configure the interactive features of the JS polar chart according to the task.
You can learn how to work with the bySpot mode from the chart documentation.

chart.interactivity({hoverMode: 'by-spot', spotRadius: 60});

Add Event Listeners

Now that we’ve added the bySpot interactivity to the chart, let’s proceed to create custom event listeners.

We’ll set them in two steps.

First, we assign listeners to the mouseMove and mouseOut events. The functions – drawSpotHandler и clearSpotHandler – can be defined later.

The second step is – we need to provide the polar chart with a listener to the pointsHover event that is activated when a point of the chart gets into the spot or leaves its boundaries, i.e. turns into or off the hover state.

That’s the right moment to get and store the array of all such current points and process them in the drawSpotHandler listener.

chart.listen('mouseMove', drawSpotHandler);	 	 
chart.listen('mouseOut', clearSpotHandler);	 

chart.listen('pointsHover', function (e) { ...

Custom Handlers

Let’s decide what we need to do in the custom handlers.

  1. They must work only when the mouse cursor is over the plot.
  2. The spot must be visualized only in the bySpot mode (so that nothing gets broken when the chart interactivity mode is switched to a different one).
  3. Such a visualization must be enabled only for Radar and Polar chart types (according to the request of AnyChart’s customer who submitted this challenge).

Adjust Visualization

Finally, we need to configure visualization.

The main idea here is to draw several elements with the help of the GraphicsJS JavaScript library:

  • circle to visualize the spot;
  • line from the center of the chart to the center of the circle;
  • two tangent lines from the center of the chart with the length of both equaling the chart radius.

In order to implement that, we need to calculate the following:

  • coordinates of the center of the chart;
  • coordinates of the mouse cursor which will be the center of the spot;
  • angles between the tangent lines and the circle;
  • points of intersection between the tangent lines and the chart’s edge (to make sure the length of the lines equals the radius of the polar chart).

The resulting data will allow us to add the graphical features we are looking for.

Once the mouse cursor leaves the data plot, the spot visualization needs to be removed. That can be easily implemented with the help of another handler.

function clearSpotHandler(e) {
    if (this.spotCircle) this.spotCircle.parent(null);
}

That’s it! Take a look at the interactive JavaScript (HTML5) polar chart that fully corresponds to the terms of the challenge: Visualizing the borders of the spot and its tangent lines drawn from the center of a polar chart in the bySpot interactivity mode to highlight markers on the polar lines by area.

See the Pen <a href=”https://codepen.io/Radionov/pen/mBYvqo/”>Spot’s Visualization</a> by Vitaly (<a href=”https://codepen.io/Radionov”>@Radionov</a>) on <a href=”https://codepen.io”>CodePen</a>.light

Here’s the full code of the resulting polar chart:

anychart.onDocumentReady(function () {
  // create a polar chart
  chart = anychart.polar();

  // set interactivity
  // hoverMode: by-spot – mode to apply interactivity to points within radius
  // spotRadius – spot radius setting
  chart.interactivity({hoverMode: 'by-spot', spotRadius: 40});

  // add chart listeners
  chart.listen('mouseMove', drawSpotHandler);
  chart.listen('mouseOut', clearSpotHandler);
  // create an array to store the points currently hovered
  var currentPoints = [];
  chart.listen('pointsHover', function (e) {
    // the pointsHover event is triggered before the mouseMove event
    // so we can get a list of all hovered points at this time
    if (!e.currentPoint.hovered)
      return; // quit if the hover state is already active
    // store the current set to a variable
    currentPoints = e.points;
  });

  // adjust the tooltip to display all points within the spot
  var tooltip = chart.tooltip();
  // collect data from all series
  tooltip.displayMode('union');
  // create the tooltip body
  tooltip.unionFormat(function (e) {
    var result = [];
    // store the series, current X and value – for each point
    for (var i = 0, len = currentPoints.length; len > i; i++) { 
     result.push("(" + currentPoints[i].getSeries().name() + ') ' + currentPoints[i].get('x') + ': ' + currentPoints[i].get('value')) 
    } 
    return result.join('\n'); 
  }); 

  // enable labels for all series 
  chart.labels().enabled(true); 

  // create a series 
  var series1 = chart.line([ [1, 2], [2, 3], [3, 4], [4, NaN], [0, 6], [1, 7], [2, 8], [3, NaN], [4, 10], [0, 11], [1, 12], [2, NaN], [3, 14], [4, 15], [0, 16], [1, NaN], [2, 18], [3, 19], [4, 20] ]); 
  // create the second series 
  var series2 = chart.line([ [0.5, 1], [1.5, 2], [2.5, 3], [3.5, 4], [4.5, NaN], [0.5, 6], [1.5, 7], [2.5, 8], [3.5, NaN], [4.5, 10], [0.5, 11], [1.5, 12], [2.5, NaN], [3.5, 14], [4.5, 15], [0.5, 16], [1.5, NaN], [2.5, 18], [3.5, 19], [4.5, 20] ]); 

  // configure settings for hovered points 
  series1.hovered().markers().fill('yellow'); 
  series2.hovered().markers().fill('yellow'); 
  
  // create a container and draw 
  chart.container('container').draw(); }); 
  /** 
  * function to visualize the spot
  * @param e Event
  */ 
  function drawSpotHandler(e) { 
  // quit if the legend is hovered 
  if (e['target'].paginator) { return null; } 
  
  // check whether the interactivity settings have been changed 
  var interactivity = this.interactivity(); 
  
  // quit if the interactivity mode is not bySpot 
  if (interactivity.hoverMode() != 'by-spot') return null; 

  // draw the visualization only if the chart type is radar or polar 
  if (this.getType() == 'polar' || this.getType() == 'radar') { 
    
    // store the radius settings in a variable 
    var spotRadius = interactivity.spotRadius(); 
    
    // store the DIV element in which the chart is drawn in a variable 
    var containerDivElement = this.container().container(); // calculate coordinates of the mouse cursor 
    var x = e['clientX'] - (containerDivElement.offsetLeft - (document.scrollLeft || 0)); 
    var y = e['clientY'] - (containerDivElement.offsetTop - (document.scrollTop || 0)); 
    
    // calculate dimensions of the data plot 
    var dataBounds = this.xAxis().getRemainingBounds(); 
    
    // calculate coordinates of the center and the radius of the radar or polar line 
    var radius = Math.min(dataBounds.width, dataBounds.height) / 2; 
    var cx = Math.round(dataBounds.left + dataBounds.width / 2); 
    var cy = Math.round(dataBounds.top + dataBounds.height / 2); 

    // check whether the mouse cursor is still within the chart radius 
    var clientRadius = Math.sqrt(Math.pow(cx - x, 2) + Math.pow(cy - y, 2)); 
    // if outside, clean up the visualization 
    if (clientRadius > radius) {
      if (this.spotCircle) this.spotCircle.parent(null);
      if (this.centerLine) this.centerLine.clear();
      if (this.leftLine) this.leftLine.clear();
      if (this.rightLine) this.rightLine.clear();
      return null;
    }

    // if no spot exists, draw it
    if (!this.spotCircle) {
      this.spotCircle = anychart.graphics.circle();
      this.spotCircle
          .radius(spotRadius)
          .stroke('black .5');
    }
    // if the spot has no parent, assign it
    if (!this.spotCircle.hasParent())
      this.spotCircle.parent(this.container());

    // put the spot center to the current mouse coordinates
    this.spotCircle.centerX(x).centerY(y);

    // create the central line if it does not exist
    if (!this.centerLine)
      this.centerLine = this.container().path().zIndex(1000).stroke('black .2').disablePointerEvents(true);

    // draw a line segment from center
    this.centerLine.clear().moveTo(cx, cy).lineTo(x, y);

    // calculate tangent lines from the center of the radar or polar chart
    var dx, dy, angle;
    var leftSideRatio, rightSideRatio;
    if (clientRadius - spotRadius >= 0) {
      dx = cx - x;
      dy = cy - y;

      angle = Math.atan(dx / dy);
      if (angle <= 0)
        angle += Math.PI;
      if (dx < 0 || (angle == Math.PI && dy > 0))
        angle += Math.PI;
      angle += this.startAngle();

      var dAngle = Math.asin(spotRadius / clientRadius);
      var leftSideAngle = angle + dAngle;
      var rightSideAngle = angle - dAngle;

      leftSideRatio = 1 - (leftSideAngle / (Math.PI * 2));
      rightSideRatio = 1 - (rightSideAngle / (Math.PI * 2));

      var leftA = (this.startAngle() - 90 + 360 * leftSideRatio) * Math.PI / 180;
      var rightA = (this.startAngle() - 90 + 360 * rightSideRatio) * Math.PI / 180;

      if (!this.leftLine) this.leftLine = this.container().path().zIndex(1000).stroke('black .2');
      this.leftLine.clear().moveTo(cx, cy).lineTo(cx + radius * Math.cos(leftA), cy + radius * Math.sin(leftA));

      if (!this.rightLine) this.rightLine = this.container().path().zIndex(1000).stroke('black .2');
      this.rightLine.clear().moveTo(cx, cy).lineTo(cx + radius * Math.cos(rightA), cy + radius * Math.sin(rightA));
    } else {
      // tangent lines are not drawn if the center of the radar or polar line is within the spot
      if (this.leftLine) this.leftLine.clear();
      if (this.rightLine) this.rightLine.clear();
    }
  }
}

/**
 * function to remove the visualization of the spot once the mouse cursor leaves the data plot
 * @param e
 */
function clearSpotHandler(e) {
  if (this.spotCircle) this.spotCircle.parent(null);
}

If you want to test the capabilities of the AnyChart JS Charts component with an interesting data visualization task, or if you have any doubt that our JavaScript charting libraries can do every particular thing that our competitors have (and even more!) – simply contact us at support@anychart.com with “Challenge” in the subject of your email. We’ll show you a relevant solution in detail and possibly share it with the others in one of our next Challenge AnyChart posts, so that could also be your great contribution to the community!

Believe us, all is possible with AnyChart! You’ll learn how.


No Comments Yet

*