{"id":19801,"date":"2020-06-03T13:41:54","date_gmt":"2020-06-03T12:41:54","guid":{"rendered":"http:\/\/www.highcharts.com\/blog\/?p=19801"},"modified":"2026-01-12T11:41:05","modified_gmt":"2026-01-12T11:41:05","slug":"build-an-interactive-puzzle-using-highcharts-gantt","status":"publish","type":"post","link":"https:\/\/www.highcharts.com\/blog\/tutorials\/build-an-interactive-puzzle-using-highcharts-gantt\/","title":{"rendered":"Build an interactive puzzle using Highcharts Gantt"},"content":{"rendered":"\r\n<div class=\"wp-block-group\">\r\n<div class=\"wp-block-group__inner-container is-layout-flow wp-block-group-is-layout-flow\"><\/div>\r\n<\/div>\r\n\r\n\r\n\r\n<p>&nbsp;<\/p>\r\n<p>Bending is electronics geek for modifying something (usually something mundane) and <em>bending<\/em> it to fit a new purpose. A very basic example is something like busting open one of those big red Staples Easy buttons and replacing the standard <em>\u201cThat was easy<\/em>\u201d voice with one that yells \u201cSquirrel!\u201d In terms of Highcharts Gantt, bending means using the library\u2019s core chart-making functionality to make something other than a Gantt chart\u2026like a slide-block puzzle.<\/p>\r\n\r\n\r\n\r\n<p>The rules are simple. Vertical blocks move vertically. Horizontal blocks move horizontally. You win when you free the red block.<\/p>\r\n\r\n\r\n\r\n<p>Steps for creating a Gantt slide-block puzzle<\/p>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">1. <strong>Create an X-Range series using Highcharts Gantt<\/strong><\/h2>\r\n\r\n\r\n\r\n<p>Figure out your block layout (I copied a layout from a slide-block puzzle app,) and lay it out as a simple Gantt chart.<\/p>\r\n\r\n\r\n\r\n<p>I assigned different colors for the vertical and horizontal blocks. The red block is the one that needs freeing. I set up the axes as linear (instead of the default treegrid.) Here&#8217;s what my initial Gantt chart looks like.<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" class=\"wp-image-20130\" src=\"https:\/\/wp-assets.highcharts.com\/www-highcharts-com\/blog\/wp-content\/uploads\/2020\/05\/22175213\/puzzle1-760x456.gif\" alt=\"\" \/><\/figure>\r\n\r\n\r\n\r\n<p>Check out the snippet of my data below. The groupId assigned to each block (\u201chBlock1\u201d, \u201cvBlock1\u201d) ensures that the grouped vertical blocks slide together. For example, the vertical vBlocks 1a\/1b\/1c have a groupId of \u201cvBlock1\u201d so that the blocks will drag together regardless of which block the player interacts with.<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-code\"><code>data: [\r\n    {   \r\n        name:'hBlock1a',\r\n        groupId:'hBlock1',\r\n        color:'#A43677',\r\n        x: 0,\r\n        x2: 3,\r\n        y: 0,\r\n    }, {\r\n        name: 'vBlock1a',\r\n        groupId:'vBlock1',\r\n        color:'#f15c80',\r\n        x: 3,\r\n        x2: 4,\r\n        y: 0,\r\n    },{\r\n        name: 'vBlock1b',\r\n        groupId:'vBlock1',\r\n        color:'#f15c80',\r\n        x: 3,\r\n        x2: 4,\r\n        y: 1,\r\n    }, {\r\n        name: 'vBlock1c',\r\n        groupId:'vBlock1',\r\n        color:'#f15c80',\r\n        x: 3,\r\n        x2: 4,\r\n        y: 2,\r\n    }]\r\n}]<\/code><\/pre>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">2. Make the blocks draggable<\/h2>\r\n\r\n\r\n\r\n<p>In the plotOptions for the xRange series, add the dragDrop configuration:<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-code\"><code>dragDrop: {\r\n    draggableX: true,\r\n    draggableY: true,\r\n    dragMinY: 0,\r\n    dragMaxY: 6,\r\n    dragMinX: 0,\r\n    dragMaxX: 6,\r\n    liveRedraw: false,\r\n    groupBy: 'groupId' \r\n}<\/code><\/pre>\r\n\r\n\r\n\r\n<p>At the series level, set draggableX and draggableY to true. We want the blocks to slide anywhere on the game board, so set the drag minimums\/maximums for the x and y to 0 and 6 respectively. Finally, set the groupBy property to \u201cgroupId.\u201d<\/p>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">3. Write the drag code<\/h2>\r\n\r\n\r\n\r\n<p>To make the blocks slide, we\u2019ll use the dragStart, drag and drop functions. Under the dragDrop options, add point and set up these events:<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-code\"><code>point: {\r\n      events: {\r\n        dragStart: function (e) {},\r\n        drag: function (e) {},\r\n        drop: function (e) {}\r\n      }\r\n}\r\n<\/code><\/pre>\r\n\r\n\r\n\r\n<p>Before coding the function, let\u2019s set up some global variables that will store the positions and movements of the blocks.<\/p>\r\n\r\n\r\n\r\n<p>My approach was to create several arrays to track the state of the game board and the location of each block.<\/p>\r\n\r\n\r\n\r\n<p>The array pointMatrix track which \u201ccells\u201d on the game board are occupied (1) and which are free (0).<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-code\"><code>let pointMatrix = [\r\n[1,1,1,1,0,0],\r\n[1,1,1,1,1,1],\r\n[1,1,1,1,0,1],\r\n[1,0,1,1,1,1],\r\n[0,0,1,0,1,1],\r\n[1,1,1,0,1,0]];\r\n<\/code><\/pre>\r\n\r\n\r\n\r\n<p>The array blockProps tracks the individual position of each block, as well as the block\u2019s groupId, starting row, starting column and width or height.<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-code\"><code>var blockProps = [\r\n    ['hBlock1',0,0,3],\r\n    ['hBlock2',1,4,2],\r\n    ['hBlock3',3,3,2],\r\n    ['hBlock4',1,1,2],\r\n    ['hBlock5',2,1,2],\r\n    ['hBlock6',5,0,3],\r\n    ['vBlock2',1,0,3],\r\n    ['vBlock1',0,3,3],\r\n    ['vBlock4',2,5,3],\r\n    ['vBlock5',4,4,2],\r\n    ['vBlock3',3,2,2]];<\/code><\/pre>\r\n\r\n\r\n\r\n<p>The zones array holds the horizontal zones of the chart on the xAxis.<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-code\"><code>let zones = [\r\n    [0,1],\r\n    [1,2],\r\n    [2,3],\r\n    [3,4],\r\n    [4,5],\r\n    [5,6]\r\n];\r\n<\/code><\/pre>\r\n\r\n\r\n\r\n<p>And these following global variables track block position, drag direction and some other things that will come into play a little later.<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-code\"><code>var yStart =0;\r\nvar xStart =0;\r\nvar seriesIndex=0;\r\nvar seriesGroup='';\r\nvar orientation ='';\r\nvar startRow=0; \r\nvar startCol=0; \r\nvar size=0;\r\nvar seriesData=[];\r\nvar blocksToMove = [];\r\n<\/code><\/pre>\r\n\r\n\r\n\r\n<pre class=\"wp-block-code\"><code>The first point-event function to set up is the easiest:<\/code><\/pre>\r\n\r\n\r\n\r\n<pre class=\"wp-block-code\"><code>drop: function () {\r\n      return false;\r\n}<\/code><\/pre>\r\n\r\n\r\n\r\n<p>We disable the drop event because it interferes with block placement. When the user slides a block, we want it to snap to the next free cell. \u00a0With drop enabled, if the player releases the block too soon, the block will stop in its tracks, creating a sloppy grid.<\/p>\r\n\r\n\r\n\r\n<p>Next, we\u2019ll set up the dragStart. In this function, we collect information about the drag target (i.e. the block) and the direction the user intends to drag (up\/down\/left\/right.)<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-code\"><code>dragStart: function (e) {\r\n     yStart=e.chartY;\r\n     xStart = e.chartX;\r\n     seriesGroup = this.groupId;\r\n     seriesName = this.name;\r\n     seriesIndex = this.index;\r\n     seriesData=this.series.chart.series[0].data;<\/code><\/pre>\r\n\r\n\r\n\r\n<p>Set yStart and xStart to the x and y location of the event. We will use this x\/y information to determine the direction of the drag.<\/p>\r\n\r\n\r\n\r\n<p>The variables seriesGroup, seriesName, seriesIndex and seriesData store exactly what they\u2019re named and make it easier to work with the chart object.<\/p>\r\n\r\n\r\n\r\n<p>The local variable blockGroup holds the blockMatrix subarray that corresponds to the block the player intends to drag.<\/p>\r\n\r\n\r\n\r\n<p>The blockGroup array contains the location of the block on the game board and its size. I also find blockGroup\u2019s index and store it in blocksIndex. We\u2019ll use this information to determine if the block(s) can be moved.<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-code\"><code>blockGroup = blockMatrix.find(function(element){\r\n             if(element[0]==seriesGroup){\r\n                 return element;\r\n              }});\r\nblocksIndex = blockMatrix.findIndex(function(element){\r\n       if(element[0]==seriesGroup){\r\n              return element\r\n       }});\r\n<\/code><\/pre>\r\n\r\n\r\n\r\n<p>Determine the block\u2019s orientation by searching its name for \u201ch.\u201d \u00a0Knowing the orientation will enable us to restrict the blocks\u2019 movements. (Vertical blocks only move vertically; horizontal blocks only move horizontally.)<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-code\"><code>if(seriesName.indexOf('h')!= -1){\r\n  orientation='horizontal';\r\n}else{\r\n orientation='vertical';\r\n}<\/code><\/pre>\r\n\r\n\r\n\r\n<p>The last thing is to set the global variables startRow, startCol and size so we can look up the block\u2019s location on the game board and check if the space to the right\/left\/above\/below is blocking the block\u2019s path.<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-code\"><code>      startRow = blockGroup[1]; \/\/row \r\n      startCol = blockGroup[2]; \/\/column \r\n      size = blockGroup[3]; \/\/length or height\r\n      seriesData = this.series.chart.series[0].data;\r\n}<\/code><\/pre>\r\n\r\n\r\n\r\n<p>Next is the drag function. Since we stored the x\/y positions of the dragStart event, we can compare them to the x\/y positions of the drag event. For example, if the x value is greater, the direction of the drag is right.<\/p>\r\n\r\n\r\n\r\n<p>But before we move the block, we have to find out if it may move. Here\u2019s how I did it.<\/p>\r\n\r\n\r\n\r\n<p>Look up the block\u2019s location in the pointMatrix. For example, if the player intends to drag a horizontal block to the right, check the cell to the right of the dragged block. If that cell is set to 0, the block may move one space right.<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-code\"><code>if(pointMatrix[startRow][startCol+size] == 0){\r\n    seriesData[seriesIndex].update({x:zones[startCol+1][0],x2:zones[startCol+size][1],y:newerY});\r\n    pointMatrix[startRow][startCol+size]=1;\r\n    pointMatrix[startRow][startCol]=0;\r\n    blockProps[blocksIndex][2]=startCol + 1;\r\n}<\/code><\/pre>\r\n\r\n\r\n\r\n<p>Finally, record the block\u2019s new position in the pointMatrix array, and update the block\u2019s properties (row, column) in the blockProps array.<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-code\"><code>pointMatrix[startRow][startCol+size]=1;\r\npointMatrix[startRow][startCol]=0;\r\nblockMatrix[blocksIndex][2]=startCol + 1;<\/code><\/pre>\r\n\r\n\r\n\r\n<p>The vertical blocks follow the same idea, except we update the y value<\/p>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">4. Make the escape<\/h2>\r\n\r\n\r\n\r\n<p>We need an exit for the red block. I accomplished this with plotBands on the x and y axes.<\/p>\r\n\r\n\r\n\r\n<p>On the xAxis, set up plot bands for each \u201czone.\u201d Make the last zone\u2019s plotBand white.<\/p>\r\n\r\n\r\n\r\n<p>On the yAxis, set up two plot bands to mask the white plot band on the x axis.<\/p>\r\n\r\n\r\n\r\n<p>Then, in the section of the code that moves the block to the right, add the following code.<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-code\"><code>if(seriesGroup == 'hBlock5' &amp;&amp;seriesData[seriesIndex].x2 &gt; 5){\r\n seriesData[seriesIndex].update({x:6,x2:7,y:newerY});\r\n}<\/code><\/pre>\r\n\r\n\r\n\r\n<p>When the red block reaches a certain x position on the game board, it flies free, like a bird.<\/p>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">5. Clean it up<\/h2>\r\n\r\n\r\n\r\n<p>Let\u2019s get rid of some of the chart stuff.<\/p>\r\n\r\n\r\n\r\n<p>Instead of returning the point name in the data labels, let&#8217;s add arrows to help the player understand the restrictions on the blocks&#8217; movement.<\/p>\r\n\r\n\r\n\r\n<p>In the dataLabel formatter function, retrieve the width of each block and make the container for the arrows slightly smaller than the block so the arrows don\u2019t bleed over the edge.<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-code\"><code>formatter:function(){\r\n  pointWidth = Math.round(this.point.shapeArgs.width);\r\n  return '&lt;div style=\"color:#F0B6D9;display:grid;grid-template-columns:1fr 1fr 1fr;width:' + (pointWidth-15) + 'px;padding:0px 0px\"&gt;&lt;div&gt;&lt;i style=\"font-size:14px;\" class=\"fas fa-arrow-left\"&gt;&lt;\/i&gt;&lt;\/div&gt;&lt;div style=\"text-align:center\"&gt;&lt;span style=\"font-size:14px;font-weight:700;\"&gt;&lt;\/span&gt;&lt;\/div&gt;&lt;div style=\"text-align:right\"&gt;&lt;i style=\"font-size:14px;\" class=\"fas fa-arrow-right\"&gt;&lt;\/i&gt;&lt;\/div&gt;&lt;\/div&gt;'\r\n}<\/code><\/pre>\r\n\r\n\r\n\r\n<p>Oops. The vertical blocks have left\/right arrows, not up\/down arrows. To fix this, we set data labels at the point level for the vertical blocks. The &#8220;a&#8221; blocks get the up arrow, and the &#8220;c&#8221; blocks get the down arrow.<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-code\"><code>dataLabels: {\r\n  x:-5,\r\n  formatter:function(){\r\n  return '&lt;i style=\"color:#F0B6D9;font-size:14px;\" class=\"fas fa-arrow-down\"&gt;&lt;\/i&gt;'\r\n}<\/code><\/pre>\r\n\r\n\r\n\r\n<p>Give the red block a unique label. I labeled mine &#8220;free me&#8221; and left off the arrows to make it even more distinct.<\/p>\r\n\r\n\r\n\r\n<p>Add an instructive title and change the axes&#8217; gridLine and tickColor to &#8216;transparent.&#8217;<\/p>\r\n\r\n\r\n\r\n<p>The puzzle layout looks cramped on the left and right, and the exit isn&#8217;t visible. To fix this, set the xAxis min to -0.2 and the max to 6.2. Then, change the &#8220;from&#8221; value on your first xAxis plotBand to the new min, and the &#8220;to&#8221; value on your last plotBand to 6.2.<\/p>\r\n\r\n\r\n\r\n<p>Now, try and free the block!<\/p>\r\n\r\n\r\n\r\n<p><iframe style=\"width: 100%;\" title=\"Highcharts Gantt Block Puzzle\" src=\"https:\/\/codepen.io\/nasin\/embed\/PoPVjrr?height=265&amp;theme-id=light&amp;default-tab=result\" height=\"665\" frameborder=\"no\" scrolling=\"no\" allowfullscreen=\"allowfullscreen\" title=\"A gantt chart used to create a puzzle game. By Nancy Dillan\">\r\nSee the Pen <a href=\"https:\/\/codepen.io\/nasin\/pen\/PoPVjrr\">Highcharts Gantt Block Puzzle<\/a> by nasin\r\n(<a href=\"https:\/\/codepen.io\/nasin\">@nasin<\/a>) on <a href=\"https:\/\/codepen.io\">CodePen<\/a>.<\/iframe><\/p>\r\n\r\n\r\n\r\n<p>&nbsp;<\/p>","protected":false},"excerpt":{"rendered":"<p>Join us on bending Highcharts Gantt to make something other than a Gantt chart\u2026like a slide-block puzzle!<\/p>\n","protected":false},"author":250,"featured_media":19805,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"meta_title":"","meta_description":"","hc_selected_options":[],"footnotes":""},"categories":[210],"tags":[1095],"coauthors":[786],"class_list":["post-19801","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tutorials","tag-highcharts-gantt"],"_links":{"self":[{"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts\/19801","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\/250"}],"replies":[{"embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/comments?post=19801"}],"version-history":[{"count":1,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts\/19801\/revisions"}],"predecessor-version":[{"id":28508,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts\/19801\/revisions\/28508"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/media\/19805"}],"wp:attachment":[{"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/media?parent=19801"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/categories?post=19801"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/tags?post=19801"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/coauthors?post=19801"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}