{"id":19723,"date":"2020-05-19T09:23:31","date_gmt":"2020-05-19T08:23:31","guid":{"rendered":"http:\/\/www.highcharts.com\/blog\/?p=19723"},"modified":"2026-01-12T11:40:24","modified_gmt":"2026-01-12T11:40:24","slug":"bring-static-data-to-life-with-an-animated-chart","status":"publish","type":"post","link":"https:\/\/www.highcharts.com\/blog\/tutorials\/bring-static-data-to-life-with-an-animated-chart\/","title":{"rendered":"Bring static data to life with an animated chart"},"content":{"rendered":"<p>Animated charts can be very effective for telling a data-story in an engaging way, and a neat trick for making static data look more like live data. In this article, we will show you how to create an animated chart from a static data set. When we are done, the chart will work like the one below (click the \u201c<strong>Start<\/strong>\u201d button to run the animation):<\/p>\n<p class=\"demo-container\"><iframe loading=\"lazy\" src=\"\/\/jsfiddle.net\/BlackLabel\/reo8p5du\/embedded\/result\/\" width=\"100%\" height=\"505\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\" title=\"An animated line chart displays confirmed cases of corona per country. By Pawel Fus\"><\/iframe><\/p>\n<p>Let\u2019s have a look at how this was made:<\/p>\n<h2>1. Create a basic interactive chart<\/h2>\n<p>Let\u2019s start by creating a basic interactive chart. Our data source will be a daily updated dataset on the infections and deaths caused by the <a href=\"https:\/\/github.com\/pomber\/covid19\">COVID-19 virus worldwide<\/a>.<\/p>\n<p>To create a chart, we simply need to fetch the data then map it to Highcharts\u2019 input format. Here is a simple snippet to do that:<\/p>\n<pre>const globalData = [];\r\nlet chart;\r\n\/\/ Fetch data:\r\nfetch('https:\/\/pomber.github.io\/covid19\/timeseries.json')\r\n  .then(response =&gt; response.json())\r\n  .then(data =&gt; {\r\n    parseData(data);\r\n    createChart();\r\n  });\r\nfunction parseData(data) {\r\n  Highcharts.objectEach(\r\n    data,\r\n    \/\/ Prepare Highcharts data-format:\r\n    \/\/ series: [{\r\n    \/\/   data: [ [x, y], [x, y], ..., [x, y]]\r\n    \/\/ }]\r\n    (countryData, country) =&gt; globalData.push({\r\n      name: country,\r\n      data: countryData.map(p =&gt; [Date.parse(p.date), p.confirmed])\r\n    })\r\n  );\r\n}\r\nfunction createChart() {\r\n  chart = Highcharts.chart('container', {\r\n    title: {\r\n      text: 'Confirmed cases per country'\r\n    },\r\n    xAxis: {\r\n      type: 'datetime'\r\n    },\r\n    yAxis: {\r\n      title: ''\r\n    },\r\n    series: globalData\r\n  });\r\n}<\/pre>\n<p>The static chart looks like the following:<\/p>\n<p class=\"demo-container\"><iframe loading=\"lazy\" src=\"\/\/jsfiddle.net\/BlackLabel\/L8etkgpb\/embedded\/result\/\" width=\"100%\" height=\"505\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\" title=\"A line chart displays confirmed cases of corona per country from Jully to January in 2020. By Pawel Fus\"><\/iframe><\/p>\n<p>Even though our chart is interactive and displays all the data, it is not yet an optimal visualization:<\/p>\n<ul>\n<li>Overloaded with data: showing all 180+ countries at the same time not only could easily decrease your browser\u2019s performance but also leave the audience confused.<\/li>\n<li>Readability: the US data is the only one visible on the chart, as the US has the biggest number of cases, whereas all the other countries\u2019 cases are barely visible on the chart.<\/li>\n<li>Design: it\u2019s a bit plain, titles are missing, and tooltips are not helpful as they could be.<\/li>\n<li>A little boring: does not tell a clear story.<\/li>\n<\/ul>\n<p>Let\u2019s improve this chart by tweaking the design and use some animation.<\/p>\n<h2>Improving the chart\u2019s design<\/h2>\n<p>To make a compelling chart it is important to think about how the chart\u2019s elements are designed, such as the styling of the title, the position of the legend, and of course use a reasonable amount of data on the chart to help the audience to get the best insights, there is no rule, just use common sense. The chart below is a design update of the previous chart:<\/p>\n<p class=\"demo-container\"><iframe loading=\"lazy\" src=\"\/\/jsfiddle.net\/BlackLabel\/zrkstb1q\/embedded\/result\/\" width=\"100%\" height=\"505\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\" title=\"An animated line chart displays confirmed cases of corona per country on 05-01-2022. By Pawel Fus\"><\/iframe><\/p>\n<p>You can tell that this chart is way better than the previous one. First of all, only the 8 top countries with confirmed cases are represented; this will allow the audience to better digest the information and get better insights by comparing 8 countries instead of 180+ countries. The second thing to notice is that the title is on the left side to give more room to the exponential progress of the data on the right side, and to the positioning of the legend. By the way, the legend also helps the audience to associate each country with the right series.<br \/>\nLet\u2019s take a look at the different Highcharts features and options used to achieve this improvement:<\/p>\n<ul>\n<li>Use the floating option to move freely your <code>chart.title<\/code> and <code>chart.subtitle<\/code> around the chart.<\/li>\n<li>Use the following setting <code>legend.layout='proximate'<\/code> to render legend items as close as possible to the last point in the data set.<\/li>\n<li>Set up the tooltip option to true <code>tooltip.split=true<\/code> to display an individual tooltip for each series, this will help the audience to find out when series-crossing have happened.<\/li>\n<\/ul>\n<p>Ok, now our chart looks much better. Animation next!<\/p>\n<h2>3. Make the chart animated<\/h2>\n<p>The third and final step is to make our chart an animated chart. This can be achieved in a few steps:<\/p>\n<ol>\n<li>Adding GUI to allow the audience to start\/stop\/replay the chart.<\/li>\n<li>Preparing dataset to be updated every x-milliseconds.<\/li>\n<li>Live updating.<\/li>\n<\/ol>\n<p><b>1. GUI<\/b><br \/>\n<i><b>Remark<\/b><br \/>\nI will not explain HTML &amp; CSS in the demo here; you can find related code in HTML and CSS tabs in the jsFiddle demo below.<\/i><\/p>\n<p>First, we need to define the following options in the code:<\/p>\n<pre>let duration = 500; \/\/ Determines how long the animation between new points should be take\r\nlet startIterator = 1; \/\/ Determines how many points will be rendered on chart's init\r\nlet currentIterator = startIterator;\r\nlet maxIterator = 1;\r\n\r\nlet guiButton = document.getElementById('start');\r\nlet guiButtonState = 'Start';\r\nlet intervalId;<\/pre>\n<p>Then create <code>initEvents()<\/code> method to manage the GUI:<\/p>\n<pre>function initEvents() {\r\n  guiButton.addEventListener('click', function() {\r\n    if (guiButtonState === 'Stop') {\r\n      \/\/ User clicked \"Stop\" -&gt; stop animation and allow to resume\r\n      intervalId = clearInterval(intervalId);\r\n      guiButton.innerText = guiButtonState = 'Resume';\r\n    } else {\r\n      \/\/ If animation has finished, recreate chart\r\n      if (guiButtonState === 'Restart') {\r\n        createChart();\r\n      }\r\n      guiButton.innerText = guiButtonState = 'Stop';\r\n      \/\/ Start animation:\r\n      redrawChart(iterator += 1);\r\n      intervalId = setInterval(function() {\r\n        \/\/ If we reached last available point, stop animation:\r\n        if (iterator === maxIterator) {\r\n          intervalId = clearInterval(intervalId);\r\n          iterator = startIterator;\r\n          guiButton.innerText = guiButtonState = 'Restart';\r\n        } else {\r\n          redrawChart(iterator += 1);\r\n        }\r\n      }, duration);\r\n    }\r\n  });\r\n}<\/pre>\n<p>And now attach events:<\/p>\n<pre>\/\/ Fetch data:\r\nfetch('https:\/\/pomber.github.io\/covid19\/timeseries.json')\r\n  .then(response =&gt; response.json())\r\n  .then(data =&gt; {\r\n    parseData(data);\r\n    createChart();\r\n    initEvents(); \/\/ Init events\r\n  });<\/pre>\n<p><b>2. Prepare the data<\/b><br \/>\nJust two, minor changes are needed:<\/p>\n<ol>\n<li>Don\u2019t set full data set on chart init, only subset of it:\n<pre>function createChart() {\r\n  ...\r\n  series: globalData.map(series =&gt; {\r\n      return {\r\n        name: series.name,\r\n        data: series.data.slice(0, startIterator)\r\n      }\r\n    })\r\n    ...\r\n}<\/pre>\n<\/li>\n<li>Calculate <code>maxIterator<\/code> so we will know when the animation should end:\n<pre>function parseData(data) {\r\n  ...\r\n  maxIterator = Math.max.apply(null, globalData.map(series => series.data.length - 1));\r\n}<\/pre>\n<\/li>\n<\/ol>\n<p><b>Live update<\/b><br \/>\nYou may have observed that in <code>initEvents()<\/code> there is a call to <code>chartRedraw(integer)<\/code> &#8211; it\u2019s time to implement it. To get nice and smooth animation, we use <code>series.addPoint()<\/code> which adds a point to one series. Here is code:<\/p>\n<pre>function redrawChart(index) {\r\n  \/\/ Set new subtitle on every redraw\r\n  chart.setTitle(null, {\r\n    text: Highcharts.dateFormat('%d-%m-%Y', globalData[0].data[index][0])\r\n  }, false);\r\n\r\n  \/\/ To each series, add a point:\r\n  chart.series.forEach(\r\n    (series, seriesIndex) =&gt;\r\n    series.addPoint(\r\n      globalData[seriesIndex].data[index],\r\n      false,\r\n      false,\r\n      false\r\n    )\r\n  );\r\n\r\n  \/\/ Now, once everything is updated, redraw chart:\r\n  chart.redraw({\r\n    duration\r\n  });\r\n}<\/pre>\n<p>The result of the changes is presented in the following chart:<\/p>\n<p class=\"demo-container\"><iframe loading=\"lazy\" src=\"\/\/jsfiddle.net\/BlackLabel\/jbo2p7xL\/embedded\/result\/\" width=\"100%\" height=\"505\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\" title=\"An animated line chart displays confirmed cases of corona per country. By Pawel Fus\"><\/iframe><\/p>\n<p>Even though the chart is animated, there are still issues to address:<\/p>\n<ol>\n<li>Initial steps in the animation are too aggressive; it\u2019s a good idea to set options like <code>yAxis.softMax<\/code> and <code>xAxis.minRange<\/code> to prevent this issue.<\/li>\n<li>Each point can have individually set option like <code>marker<\/code> and <code>dataLabels<\/code> that means we can limit noise in the chart caused by markers using these options in <code>addPoint()<\/code> method.<\/li>\n<\/ol>\n<p>The chart looks like the following after adding the changes to the code:<\/p>\n<p class=\"demo-container\"><iframe loading=\"lazy\" src=\"\/\/jsfiddle.net\/BlackLabel\/8u60jvhq\/embedded\/result\/\" width=\"100%\" height=\"505\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\" title=\"An animated line chart displays confirmed cases of corona per country. By Pawel Fus\"><\/iframe><\/p>\n<h2>What\u2019s next?<\/h2>\n<p>That\u2019s not all. Our chart looks great: it\u2019s quite easy to read which series is where, at any point, the audience can stop the animation to check the data on the chart and can easily customize it; for example, change the duration of the one-step in animation, set how many points are rendered on chart init, etc.<\/p>\n<p>There\u2019s however one more thing we can change in our dataset. Right now all series start at the same point in time and it\u2019s easy to compare values on a chart at a specific point in time. But we cannot compare trends between countries based on the start of the outbreak in each country. In order to do that, let\u2019s set the common origin, called Day #0, which is the first day with a confirmed case in each country.<br \/>\nTo do that we need to:<\/p>\n<ul>\n<li>Resign from date-time axis and use basic linear axis with <code>labels.format<\/code>.<\/li>\n<li>Remove from the dataset the points that have value=0.<\/li>\n<\/ul>\n<p>And voil\u00e0, here is the animated chart after the latest updates:<\/p>\n<p class=\"demo-container\"><iframe loading=\"lazy\" src=\"\/\/jsfiddle.net\/BlackLabel\/reo8p5du\/embedded\/result\/\" width=\"100%\" height=\"505\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\" title=\"An animated line chart displays confirmed cases of corona per country. By Pawel Fus\"><\/iframe><\/p>\n<p>You can find the changes in the source code. I will not explain them here as this is basically related to the data, not the Highcharts code.<\/p>\n<p>We could analyze further lines in the chart, but instead, let\u2019s modify our data even more. Instead of showing confirmed cases in a country, we will try to display the number of confirmed cases per 1000 inhabitants indicator. All we need is an estimated population per country. We can get that from Wikipedia. Again, we start with our <a href=\"https:\/\/jsfiddle.net\/BlackLabel\/8u60jvhq\/\">main chart<\/a> and modify it.<br \/>\nIn <code>parseData()<\/code> function we add the following:<\/p>\n<pre>Highcharts.objectEach(\r\n  data,\r\n  \/\/ Prepare Highcharts data-format:\r\n  \/\/ series: [{\r\n  \/\/   data: [ [x, y], [x, y], ..., [x, y]]\r\n  \/\/ }]\r\n  (countryData, country) =&gt; globalData.push({\r\n    name: country,\r\n    data: countryData.map(p =&gt; [Date.parse(p.date), p.confirmed \/ getCountryPopulation(country) * 1000])\r\n  })\r\n);<\/pre>\n<p>Where <code>getCountryPopulation(country)<\/code> method returns population value.<br \/>\nTo improve readability, let\u2019s create a method which can be used in <code>tooltip.pointFormatter<\/code> and <code>dataLabels.formatter<\/code>:<\/p>\n<pre>function format(y) {\r\n  return y &lt; 0.01 ? '&lt;0.01' : '~' + y.toFixed(2);\r\n}<\/pre>\n<p>Here is the result:<\/p>\n<p class=\"demo-container\"><iframe loading=\"lazy\" src=\"\/\/jsfiddle.net\/BlackLabel\/cfg5vusq\/embedded\/result\/\" width=\"100%\" height=\"505\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\" title=\"An animated line chart displays confirmed cases of corona per country. By Pawel Fus\"><\/iframe><\/p>\n<p>The <a href=\"https:\/\/github.com\/pomber\/covid19\">dataset<\/a> provides us also with other information such as the number of deaths and recovery. Here you can find demos with these numbers <a href=\"https:\/\/jsfiddle.net\/BlackLabel\/r8k135eh\/\">demo1<\/a> and <a href=\"https:\/\/jsfiddle.net\/BlackLabel\/fc4o5jeg\/\">demo2<\/a>.<\/p>\n<p>Now you have an idea about how you can create an animated chart. Let us know what else are you able to read from the charts above? Feel free to come up with your own animated charts and share with us your experience in the comment section below.<\/p>\n<p>Remember, these numbers are actual people\u2019s lives, not a ranking.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A step by step tutorial to learn how to create a compelling animated chart with Highcharts.<\/p>\n","protected":false},"author":48,"featured_media":19746,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"meta_title":"","meta_description":"","hc_selected_options":[],"footnotes":""},"categories":[210],"tags":[1094],"coauthors":[727],"class_list":["post-19723","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tutorials","tag-highcharts-core"],"_links":{"self":[{"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts\/19723","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/users\/48"}],"replies":[{"embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/comments?post=19723"}],"version-history":[{"count":1,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts\/19723\/revisions"}],"predecessor-version":[{"id":29234,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts\/19723\/revisions\/29234"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/media\/19746"}],"wp:attachment":[{"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/media?parent=19723"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/categories?post=19723"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/tags?post=19723"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/coauthors?post=19723"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}