Highcharts is a highly customizable library that allows users to create almost any interactive SVG based visualization one can imagine, even a game!
Previously we looked at creating an interactive puzzle using Highcharts Gantt. In this tutorial, we will look at the Highcharts library at a lower level in an effort to transform your charts into a snake game (see below):
The article has two main sections. The first section will walk through some basic concepts such as SVG Renderer and SVG Element. In the second section, you will see how to create the snake, how to make it move, and how to feed the snake.
SVG Renderer
An essential tool that might help implement custom elements that are not included in the Highcharts core is the Highcharts SVG renderer. It can be used to add to your chart a custom SVG element that can be created in any shape – the only thing that limits you is your imagination if you have used canvas API before you will feel at home.
SVG Elements
There are a few basic elements that can be rendered, you could create a circle, arc, rectangle, text element, label, button, symbol, or draw a path. This time we will focus on rendering a rectangle that will be used to create a snake body.
Create a snake
Before we can start rendering, we need to create a layer where we may place rendered elements. For this purpose, you could use the chart already existing, but it is worth mentioning that it is possible to create an independent SVG drawing.
Because I wanted to create a game that could be interactive with the chart, I’m starting from a basic scatter chart.
const chart = Highcharts.chart('container', { chart: { type: 'scatter' }, series: [{ data: [4, 3, 5, 6, 2, 3] }] });
Now the renderer can be accessed straight from the chart that we just created. To use it and create the first rectangle, we can use renderer.rect(x, y, width, height) method. The method takes four arguments:
- x – distance (in px) from the left side of the container
- y – distance from the top of the container
- width of the rectangle
- height of the rectangle
Using the renderer method returns an SVG element with the given coordinates and sizes. Still, before adding it to our chart, we need to apply some attributes like fill
, stroke
, or stroke-width
to make our rectangle visible. To do that we, could use the attr() method. After specifying those attributes, we could finally add the SVG element to our chart using the add() method.
const snake = chart.renderer.rect(0, 0, 20, 20) .attr({ fill: 'red' }) .add();
Let it move!
In the previous paragraph, we learned how to render a simple rectangle, but it is only static. Our snake should be more lively. To wake it up, we need to learn about translate()
method. It will allow us to move the element by a certain x and y values. So if we want to move snake by 100px to the right side should use:
snake.translate(100,0);
Now we can use that knowledge to tell our snake to continuously move to the right size until it cannot move further (we don’t want him to run away, right?). To achieve that, we could create a simple interval that each time will move the snake by an increased value. Then we need to find a chart property that will allow us to define the boundaries that will keep the snake inside the chart. In this case, we can use the chart.plotWidth
property. Additionally, we may use another chart properties – chart.plotLeft
and chart.plotTop
to update the snake’s initial position (right now it is rendered outside of the plot area, we want him to be inside it).
const snake = chart.renderer.rect( chart.plotLeft, chart.plotTop, 20, 20 ) .attr({ fill: 'red' }) .add(); let x = 0; setInterval(() = > { if (x + 20 < chart.plotWidth) { x += 20; snake.translate(x, 0); } }, 250);
Feed the snake
So far we have learned how to create a snake, give him the ability to move, but he still lacks a very important skill – eating points. To do that, we need to remove a point somehow when it has the same position as our snake. To make it easier, I will set the same value for all points and the min and max properties values for both axes.
const chart = Highcharts.chart('container', { chart: { type: 'scatter' }, xAxis: { min: 0, max: 8 }, yAxis: { min: 0, max: 8 }, series: [{ pointStart: 1, data: [8, 8, 8, 8, 8, 8, 8, 8] }] });
Now we are ready to implement this feature. All we have to do is to compare the pixel position of the snake with the points.
To get the actual snake position, we need to add a translate value to its initial position. Translate value can be found under snake.translateX
property, to get the initial position, we could use the attr() method to return a value of a certain attribute.
const snakePosX = snake.attr('x') + snake.translateX;
There are several ways to find a pixel coordinates of a point, but the easiest one is to use axis toPixels() method that will return a pixel position of the value on the chart or axis.
const pointPosX = xAxis.toPixels(point.x)
Now, all we have to do is create a simple function that will iterate over all points and remove whose distance is smaller than a snake size. To remove a point, we can use the remove()
method. Then we are able to call that function inside the interval that is responsible for moving the snake. Our snake finally learned how to eat chart points.
function onCollision() { const xAxis = chart.xAxis[0], points = chart.series[0].points, snakePosX = snake.attr('x') + snake.translateX; points.forEach(point = > { const pointPosX = xAxis.toPixels(point.x); if (Math.abs(snakePosX - pointPosX) < 20) { point.remove(); } }) }
Our snake has learned some basic abilities. Of course, to create a fully functional game, we still have a lot to improve, but the most important parts related to the snake’s integration with the Highcharts library are described in this article.
Feel free to check the final version of the game with the code. Let me know if you have any questions or comments, I will be happy to hear from you.
Comments
There are no comments on this post
Want to leave a comment?
Comments are moderated. They will publish only if they add to the discussion in a constructive way. Please be polite.