{"action":"created","issue":{"url":"https://api.github.com/repos/danvk/dygraphs/issues/643","labels_url":"https://api.github.com/repos/danvk/dygraphs/issues/643/labels{/name}","comments_url":"https://api.github.com/repos/danvk/dygraphs/issues/643/comments","events_url":"https://api.github.com/repos/danvk/dygraphs/issues/643/events","html_url":"https://github.com/danvk/dygraphs/issues/643","id":94121241,"number":643,"title":"Issues with synchronizer.js fixed","user":{"login":"cotrina","id":13260060,"avatar_url":"https://avatars.githubusercontent.com/u/13260060?v=3","gravatar_id":"","url":"https://api.github.com/users/cotrina","html_url":"https://github.com/cotrina","followers_url":"https://api.github.com/users/cotrina/followers","following_url":"https://api.github.com/users/cotrina/following{/other_user}","gists_url":"https://api.github.com/users/cotrina/gists{/gist_id}","starred_url":"https://api.github.com/users/cotrina/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cotrina/subscriptions","organizations_url":"https://api.github.com/users/cotrina/orgs","repos_url":"https://api.github.com/users/cotrina/repos","events_url":"https://api.github.com/users/cotrina/events{/privacy}","received_events_url":"https://api.github.com/users/cotrina/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"milestone":null,"comments":2,"created_at":"2015-07-09T18:38:40Z","updated_at":"2016-01-09T12:00:07Z","closed_at":"2015-10-08T17:44:41Z","body":"I could only find broken versions of your synchronizer.js. I went through the source code of the 2 different versions (github and dygraphs.com) and found all the working pieces. This is the version I put together. It works great for me.\r\n\r\nedit\r\nI should clarify the issues I found with each version.\r\n\r\nFor the one I found here: http://dygraphs.com/extras/synchronizer.js it synced properly with selection and zoom but the range feature was not yet added.\r\n\r\nThe one I found on GitHub, which I think it the most up to date version, the range and zoom feature work fine but the selection option does not work even when the option is enabled (true). \r\n\r\n\r\n\r\n ```javascript\r\n/**\r\n * Synchronize zooming and/or selections between a set of dygraphs.\r\n *\r\n * Usage:\r\n *\r\n *   var g1 = new Dygraph(...),\r\n *       g2 = new Dygraph(...),\r\n *       ...;\r\n *   var sync = Dygraph.synchronize(g1, g2, ...);\r\n *   // charts are now synchronized\r\n *   sync.detach();\r\n *   // charts are no longer synchronized\r\n *\r\n * You can set options using the last parameter, for example:\r\n *\r\n *   var sync = Dygraph.synchronize(g1, g2, g3, {\r\n *      selection: true,\r\n *      zoom: true\r\n *   });\r\n *\r\n * The default is to synchronize both of these.\r\n *\r\n * Instead of passing one Dygraph objet as each parameter, you may also pass an\r\n * array of dygraphs:\r\n *\r\n *   var sync = Dygraph.synchronize([g1, g2, g3], {\r\n *      selection: false,\r\n *      zoom: true\r\n *   });\r\n */\r\n(function() {\r\n/* global Dygraph:false */\r\n'use strict';\r\n\r\nDygraph.synchronize = function(/* dygraphs..., opts */) {\r\n  if (arguments.length === 0) {\r\n    throw 'Invalid invocation of Dygraph.synchronize(). Need >= 1 argument.';\r\n  }\r\n\r\n  var OPTIONS = ['selection', 'zoom', 'range'];\r\n  var opts = {\r\n     selection: true,\r\n    zoom: true,\r\n    range: true\r\n  };\r\n  var dygraphs = [];\r\n\r\n  var prevCallbacks = {\r\n    draw: null,\r\n    highlight: null,\r\n    unhighlight: null\r\n  };\r\n\r\n  var parseOpts = function(obj) {\r\n    if (!(obj instanceof Object)) {\r\n      throw 'Last argument must be either Dygraph or Object.';\r\n    } else {\r\n      for (var i = 0; i < OPTIONS.length; i++) {\r\n        var optName = OPTIONS[i];\r\n        if (obj.hasOwnProperty(optName)) opts[optName] = obj[optName];\r\n      }\r\n    }\r\n  };\r\n\r\n  if (arguments[0] instanceof Dygraph) {\r\n    // Arguments are Dygraph objects.\r\n    for (var i = 0; i < arguments.length; i++) {\r\n      if (arguments[i] instanceof Dygraph) {\r\n        dygraphs.push(arguments[i]);\r\n      } else {\r\n        break;\r\n      }\r\n    }\r\n    if (i < arguments.length - 1) {\r\n      throw 'Invalid invocation of Dygraph.synchronize(). ' +\r\n            'All but the last argument must be Dygraph objects.';\r\n    } else if (i == arguments.length - 1) {\r\n      parseOpts(arguments[arguments.length - 1]);\r\n    }\r\n  } else if (arguments[0].length) {\r\n    // Invoked w/ list of dygraphs, options\r\n    for (var i = 0; i < arguments[0].length; i++) {\r\n      dygraphs.push(arguments[0][i]);\r\n    }\r\n    if (arguments.length == 2) {\r\n      parseOpts(arguments[1]);\r\n    } else if (arguments.length > 2) {\r\n      throw 'Invalid invocation of Dygraph.synchronize(). ' +\r\n            'Expected two arguments: array and optional options argument.';\r\n    }  // otherwise arguments.length == 1, which is fine.\r\n  } else {\r\n    throw 'Invalid invocation of Dygraph.synchronize(). ' +\r\n          'First parameter must be either Dygraph or list of Dygraphs.';\r\n  }\r\n\r\n  if (dygraphs.length < 2) {\r\n    throw 'Invalid invocation of Dygraph.synchronize(). ' +\r\n          'Need two or more dygraphs to synchronize.';\r\n  }\r\n  \r\n  var readycount = dygraphs.length;\r\n  for (var i = 0; i < dygraphs.length; i++) {\r\n    var g = dygraphs[i];\r\n    g.ready( function() {\r\n      if (--readycount == 0) {\r\n        // Listen for draw, highlight, unhighlight callbacks.\r\n        if (opts.zoom) {\r\n          attachZoomHandlers(dygraphs, opts, prevCallbacks);\r\n        }\r\n\r\n        if (opts.selection) {\r\n          attachSelectionHandlers(dygraphs, prevCallbacks);\r\n        }\r\n      }\r\n    });\r\n  }\r\n \r\n  return {\r\n    detach: function() {\r\n      for (var i = 0; i < dygraphs.length; i++) {\r\n        var g = dygraphs[i];\r\n        if (opts.zoom) {\r\n          g.updateOptions({drawCallback: prevCallbacks.draw});\r\n        }\r\n        if (opts.selection) {\r\n          g.updateOptions({\r\n            highlightCallback: prevCallbacks.highlight,\r\n            unhighlightCallback: prevCallbacks.unhighlight\r\n          });\r\n        }\r\n      }\r\n      // release references & make subsequent calls throw.\r\n      dygraphs = null;\r\n      opts = null;\r\n      prevCallbacks = null;\r\n    }\r\n  };\r\n};\r\n\r\nfunction attachZoomHandlers(gs, syncOpts, prevCallbacks) {\r\n  var block = false;\r\n  for (var i = 0; i < gs.length; i++) {\r\n    var g = gs[i];\r\n    prevCallbacks.draw = g.getFunctionOption('drawCallback');\r\n    g.updateOptions({\r\n      drawCallback: function(me, initial) {\r\n        if (prevCallbacks.draw) prevCallbacks.draw(me, initial);\r\n        if (block || initial) return;\r\n        block = true;\r\n        var opts = {\r\n          dateWindow: me.xAxisRange()\r\n        };\r\n        if (syncOpts.range) opts.valueRange = me.yAxisRange();\r\n\r\n        for (var j = 0; j < gs.length; j++) {\r\n          if (gs[j] == me) continue;\r\n          gs[j].updateOptions(opts);\r\n        }\r\n        block = false;\r\n      }\r\n    }, false /* no need to redraw */);\r\n  }\r\n}\r\n\r\nfunction attachSelectionHandlers(gs, prevCallbacks) {\r\n  var block = false;\r\n  for (var i = 0; i < gs.length; i++) {\r\n    var g = gs[i];\r\n    prevCallbacks.highlight = g.getFunctionOption('highlightCallback');\r\n    prevCallbacks.unhighlight = g.getFunctionOption('unhighlightCallback');\r\n    g.updateOptions({\r\n      highlightCallback: function(event, x, points, row, seriesName) {\r\n        if (prevCallbacks.highlight) {\r\n            prevCallbacks.highlight(event, x, points, row, seriesName);\r\n        }\r\n        if (block) return;\r\n        block = true;\r\n        var me = this;\r\n        for (var i = 0; i < gs.length; i++) {\r\n          if (me == gs[i]) continue;\r\n          var idx = dygraphsBinarySearch(gs[i], x);\r\n          if (idx !== null) {\r\n            gs[i].setSelection(idx, seriesName);\r\n          }\r\n        }\r\n        block = false;\r\n      },\r\n      unhighlightCallback: function(event) {\r\n        if (prevCallbacks.unhighlight) prevCallbacks.unhighlight(event);\r\n        if (block) return;\r\n        block = true;\r\n        var me = this;\r\n        for (var i = 0; i < gs.length; i++) {\r\n          if (me == gs[i]) continue;\r\n          gs[i].clearSelection();\r\n        }\r\n        block = false;\r\n      }\r\n    });\r\n  }\r\n}\r\n\r\n// Returns the index corresponding to xVal, or null if there is none.\r\nfunction dygraphsBinarySearch(g, xVal) {\r\n  var low = 0,\r\n      high = g.numRows() - 1;\r\n\r\n  while (low <= high) {\r\n    var idx = (high + low) >> 1;\r\n    var x = g.getValue(idx, 0);\r\n    if (x < xVal) {\r\n      low = idx + 1;\r\n    } else if (x > xVal) {\r\n      high = idx - 1;\r\n    } else {\r\n      return idx;\r\n    }\r\n  }\r\n\r\n  // TODO: give an option to find the closest point, i.e. not demand an exact match.\r\n  return null;\r\n}\r\n\r\n})();\r\n```"},"comment":{"url":"https://api.github.com/repos/danvk/dygraphs/issues/comments/170229441","html_url":"https://github.com/danvk/dygraphs/issues/643#issuecomment-170229441","issue_url":"https://api.github.com/repos/danvk/dygraphs/issues/643","id":170229441,"user":{"login":"nejcb1","id":12158250,"avatar_url":"https://avatars.githubusercontent.com/u/12158250?v=3","gravatar_id":"","url":"https://api.github.com/users/nejcb1","html_url":"https://github.com/nejcb1","followers_url":"https://api.github.com/users/nejcb1/followers","following_url":"https://api.github.com/users/nejcb1/following{/other_user}","gists_url":"https://api.github.com/users/nejcb1/gists{/gist_id}","starred_url":"https://api.github.com/users/nejcb1/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nejcb1/subscriptions","organizations_url":"https://api.github.com/users/nejcb1/orgs","repos_url":"https://api.github.com/users/nejcb1/repos","events_url":"https://api.github.com/users/nejcb1/events{/privacy}","received_events_url":"https://api.github.com/users/nejcb1/received_events","type":"User","site_admin":false},"created_at":"2016-01-09T12:00:07Z","updated_at":"2016-01-09T12:00:07Z","body":"Great cotrina, tnx! Your solution works with last version from GitHub."}}