{"id":17958,"date":"2019-05-07T14:25:07","date_gmt":"2019-05-07T12:25:07","guid":{"rendered":"http:\/\/www.highcharts.com\/blog\/?p=17958"},"modified":"2026-01-12T11:29:40","modified_gmt":"2026-01-12T11:29:40","slug":"synchronize-selection-bi-directionally-between-chart-and-table","status":"publish","type":"post","link":"https:\/\/www.highcharts.com\/blog\/tutorials\/synchronize-selection-bi-directionally-between-chart-and-table\/","title":{"rendered":"Synchronize selection bi-directionally between chart and table"},"content":{"rendered":"<p>&nbsp;<\/p>\n<p>&nbsp;<br \/>\nWhen is it best to present your data with a chart, or use a table instead? Well, that depends on the audience and how they want to use your data and what message you want to convey.<\/p>\n<ul>\n<li><em>Graphs<\/em> work well with presenting a vast amount of data points or when you want to point out the relation between values and the shapes they form. The eye can process fast amounts of information and our brains quickly understand the information or relationships between them.<\/li>\n<li><em>Tables<\/em> work well with a lesser amount of data points opposed to graphs. Use tables when your users lookup individual values of points and compare them to other singular points. You can almost see some using his fingers to find the right point in the table.<\/li>\n<\/ul>\n<p>But what if I tell you that you don&#8217;t need to choose between a chart or a table? Get the best of both worlds by synchronizing the selection of data points bidirectionally between chart and table.<\/p>\n<p>In this article, I will show you how to code bidirectional selection between a <code>&lt;table&gt;<\/code> and a <a href=\"https:\/\/www.highcharts.com\/products\/highcharts\">Highcharts<\/a> chart. If a user makes a selection in the table, then it should highlight the data points in the chart, and vice versa, selecting outliers in a chart should highlight the cells in the table. See below for the mockup.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/wp-assets.highcharts.com\/www-highcharts-com\/blog\/wp-content\/uploads\/2019\/04\/09150546\/sync-chart-table.jpg\" alt=\"mockup\" \/><\/p>\n<h4>Let&#8217;s start coding!<\/h4>\n<p>I will explain with code snippets how I build the bi-directional selection. The full code you can find in the <a href=\"https:\/\/github.com\/gvaartjes\/highcharts-simple-sync-table\">highcharts-simple-sync-table<\/a> repository on GitHub. Find a working demo on <a href=\"https:\/\/glitch.com\/~highcharts-simple-sync-table\">Glitch<\/a> to remix and play with the code!<\/p>\n<p>Highcharts has a nifty feature where it creates an HTML table below the chart with the chart&#8217;s current data. Enable the <code>showTable<\/code> property for displaying the table. That&#8217;s convenient, now we don&#8217;t have to define the table!<\/p>\n<pre><code class=\"language-javascript\">let chart = Highcharts.chart(\"container\", {\r\n  title: {\r\n    text: \"Solar Employment Growth by Sector, 2010-2016\"\r\n  },\r\n  \r\n  series: [....],\r\n  \r\n  exporting: {\r\n    showTable: true\r\n  }\r\n}\r\n<\/code><\/pre>\n<p>Now we have a table in our page, we can work on building a synch mechanism for bi-directional selection. Let&#8217;s make a list of what is needed to make this work.<\/p>\n<ul>\n<li>Select table cell on chart point select<\/li>\n<li>Select point in the chart on selecting a table cell<\/li>\n<li>Select multiple points by dragging<\/li>\n<li>Select multiple cells by column or row selection<\/li>\n<li>Set event handlers in the chart for point select and deselect<\/li>\n<\/ul>\n<h5>Select table cell on point select<\/h5>\n<pre><code class=\"language-javascript\">\/**\r\n   * Function for hilighting a table cell corresponding a selected datapoint\r\n   * in the chart \r\n   * @param {Point} point, Highcharts.Point \r\n   * @param {Boolean} selected, Is the point selected or deselected?\r\n   *\/\r\n  const selectTableCell = function (point, highlight) {\r\n    \/\/ check the glo al properties, initially not set\r\n    vHeaders = vHeaders ? vHeaders : getVHeaders();\r\n    hHeaders = hHeaders ? hHeaders : getHHeaders();\r\n\r\n    \/\/ find corresponding cell for datapoint\r\n    let cell = getCell(vHeaders.indexOf(point.category),\r\n      hHeaders.indexOf(point.series.name));\r\n\r\n    \/\/ remove or add the classname on the element to select\/deselect the tablecell\r\n    DOMTokenList.prototype[highlight ? 'add' : 'remove']\r\n      .apply(cell.classList, ['selected']);\r\n  };\r\n<\/code><\/pre>\n<p>The above function takes a Highcharts point and a selection state: <code>true<\/code> for select table cell and <code>false<\/code> for unselect. It then locates the associated cell by looking up the horizontal and vertical indexes of the cell. We can find those easily because there is a direct relation between the vertical headers of the table (<code>vHeaders<\/code>) and the <code>point.category<\/code> as there is a relation between the horizontal headers (<code>hHeaders<\/code>) and the series name of the point (<code>point.series.name<\/code>).<\/p>\n<h5>Select point in the chart on selecting a table cell<\/h5>\n<pre><code class=\"language-javascript\">\/**\r\n  * Update the data point in the series on selecting or deselecting a table cell\r\n  * @param {Chart} chart \r\n  * @param {Array} tableCellArr \r\n  *\/\r\nconst updateSelectionOfSeriesPoint = (chart, tableCellArr) =&gt; {\r\n  tableCellArr.forEach((cell) =&gt; {\r\n    let cellIdx = cell.cellIndex;\r\n    let point = chart.series[cellIdx - 1].points[cell.parentNode.rowIndex - 1];\r\n    \/\/ contains classList selected, then deselect point and v.v.\r\n    point.select(!cell.classList.contains('selected'), true);\r\n  })\r\n}\r\n<\/code><\/pre>\n<p>Vice versa the previous function (<code>selectTableCell<\/code>) , there&#8217;s also a mapping between the cell index for retrieving the linked series in the chart. Then we use the row index for getting the index of the point in the series.<\/p>\n<pre><code class=\"language-javascript\">let point = chart.series[cellIdx - 1].points[cell.parentNode.rowIndex - 1];\r\n<\/code><\/pre>\n<p>When we have the point, we will flip the selected state of it with the code snippet below.<\/p>\n<pre><code class=\"language-javascript\">point.select(!cell.classList.contains('selected'), true);\r\n<\/code><\/pre>\n<p>The second parameter of <code>point.select()<\/code> is set to <code>true<\/code>, so the selection is added to other selected points. See also the <a href=\"https:\/\/api.highcharts.com\/class-reference\/Highcharts.Point.html#select\">Highcharts API<\/a>. We need that in order to support multiple selections of points in the demo.<\/p>\n<h5>Select multiple points by dragging<\/h5>\n<p>Ok, this going to be a little bit counterintuitive&#8230;, to support the selection of points we&#8217;re using the Highcharts zooming capabilities and the event it fires when an area of the chart has been selected to zoom in to. Selection is enabled by setting the chart&#8217;s zoomType (<code>zoomType: 'xy'<\/code>).<\/p>\n<pre><code class=\"language-javascript\">let chart = Highcharts.chart(\"container\", {\r\n  chart: {\r\n        events: {\r\n          selection: selectPointsByDrag,\r\n          click: unselectByClick\r\n        },\r\n        \/\/ necesssary to be able to select by dragging\r\n        zoomType: 'xy'\r\n     }, ....\r\n});\r\n<\/code><\/pre>\n<p>One parameter, event, is passed to the <code>selectPointsByDrag<\/code> function, containing event information. The default action for the selection event is to zoom the chart to the selected area. We prevent zooming in by calling event.preventDefault().<br \/>\nInformation on the selected area can be found through event.xAxis and event.yAxis, which are arrays containing the axes of each dimension and each axis&#8217; min and max values. See the implementation of the function here below, where we then check which points of all series in the chart are within the selected area.<\/p>\n<pre><code class=\"language-javascript\">\/**\r\n   * selected area in chart is used to filter which series points fall within the selected area\r\n   * Normally used to zoom in, but we return false to prevent that happening\r\n   * @param {Event} e\r\n   *\/\r\n  function selectPointsByDrag(e) {\r\n    \/\/ Select points\r\n    Highcharts.each(this.series, function (series) {\r\n      Highcharts.each(series.points, function (point) {\r\n        if (point.x &gt;= e.xAxis[0].min &amp;&amp; point.x &lt;= e.xAxis[0].max &amp;&amp;\r\n          point.y &gt;= e.yAxis[0].min &amp;&amp; point.y &lt;= e.yAxis[0].max) {\r\n          point.select(true, true);\r\n        }\r\n      });\r\n    });\r\n\r\n    return false; \/\/ Don't zoom\r\n  }\r\n<\/code><\/pre>\n<h5>Select multiple cells by column or row selection<\/h5>\n<p>It would be a quite nifty feature, to be able to select table cells by clicking on the row or column. We already have the <code>updateSelectionOfSeriesPoint = (chart, tableCellArr) =&gt; {}<\/code> function that takes an array of table cells for updating the selection state of the points in the chart. Attach eventListeners to the row and column header cells for selecting the cells and then pass them to the <code>updateSelectionOfSeriesPoint<\/code> function. See snippet below.<\/p>\n<pre><code class=\"language-javascript\">\/\/ Attach eventListeners for the table cells holding the point values\r\nattachEventListenerToElements(htmlCollectionToArray(\r\n  document.querySelectorAll('#highcharts-data-table-0 td.number')), 'click', function (e) {\r\n        updateSelectionOfSeriesPoint(chart, [e.target]);\r\n   })\r\n<\/code><\/pre>\n<p>Note in the snippet above, two utility functions for convenience: <code>attachEventListenerToElements<\/code> that attaches events to an array of html elements,<\/p>\n<pre><code class=\"language-javascript\">\/\/ attach eventlistener to array of HTML elements\r\n  const attachEventListenerToElements = (elementsArr, eventName, listener) =&gt; {\r\n    elementsArr.forEach(elem =&gt; elem.addEventListener(eventName, listener))\r\n  }\r\n<\/code><\/pre>\n<p>and <code>htmlCollectionToArray<\/code> for turning a HTMLCollection into an Array and use <code>forEach<\/code> when looping over the table cells.<\/p>\n<pre><code class=\"language-javascript\">const htmlCollectionToArray = (nodes) =&gt; Array.prototype.slice.call(nodes);\r\n<\/code><\/pre>\n<h5>Set eventHandlers in the chart for point select and deselect<\/h5>\n<p>The last thing we have to do, is to hook up the events for <a href=\"https:\/\/api.highcharts.com\/highcharts\/plotOptions.line.point.events.select\">selecting<\/a> and unselecting points in the chart for triggering the <code>selectTableCell<\/code> which selects or unselect the corresponding table cell.<\/p>\n<pre><code class=\"language-javascript\">plotOptions: {\r\n  series: {\r\n    point: {\r\n      events: {\r\n        select: function (e) {\r\n          selectTableCell(this, true);\r\n        },\r\n        unselect: function (e) {\r\n          selectTableCell(this, false);\r\n        }\r\n      }\r\n    }\r\n  }\r\n}\r\n<\/code><\/pre>\n<h4>Done!<\/h4>\n<p>With this article, I demonstrated how to set up bi-directional selection with Highcharts. It&#8217;s a proof of concept that surely can be improved. For example, the row and column selection in the table isn&#8217;t that optimal. It inverts the already selected cells, instead of adding the unselected cells to the selected cells. Nevertheless, I hope you get inspired by this demo and build your own solution!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>&nbsp; &nbsp; When is it best to present your data with a chart, or use a table instead? Well, that depends on the audience and how they want to use your data and what message you want to convey. Graphs work well with presenting a vast amount of data points or when you want to [&hellip;]<\/p>\n","protected":false},"author":240,"featured_media":17987,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"meta_title":"","meta_description":"","hc_selected_options":[],"footnotes":""},"categories":[210],"tags":[1063,1094],"coauthors":[771],"class_list":["post-17958","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tutorials","tag-data-visualization","tag-highcharts-core"],"_links":{"self":[{"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts\/17958","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\/240"}],"replies":[{"embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/comments?post=17958"}],"version-history":[{"count":1,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts\/17958\/revisions"}],"predecessor-version":[{"id":29196,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts\/17958\/revisions\/29196"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/media\/17987"}],"wp:attachment":[{"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/media?parent=17958"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/categories?post=17958"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/tags?post=17958"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/coauthors?post=17958"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}