Please review changes against upstream code using SCM,
see the Vcs-* tags in debian/control for its location.

--- dygraphs-2.2.1.orig/.github/workflows/build.yml
+++ dygraphs-2.2.1/.github/workflows/build.yml
@@ -10,20 +10,30 @@
         "url": "${{ steps.deployment.outputs.page_url }}"
       },
       "if": "github.repository == 'mirabilos/dygraphs'",
-      "runs-on": "ubuntu-latest",
+      "runs-on": "ubuntu-24.04",
       "steps": [
         {
-          "uses": "actions/checkout@v3.3.0"
+          "uses": "actions/checkout@v4.2.2"
         },
         {
-          "run": "./.pages.sh"
+          "run": "sh .pages.sh"
         },
         {
-          "uses": "actions/upload-pages-artifact@v1.0.7"
+          "uses": "actions/upload-artifact@v4.6.0",
+          "with": {
+            "name": "package-lock.json",
+            "path": "package-lock.json"
+          }
+        },
+        {
+          "uses": "actions/upload-pages-artifact@v3.0.1"
+        },
+        {
+          "run": "mksh .post.sh"
         },
         {
           "id": "deployment",
-          "uses": "actions/deploy-pages@v1.2.3"
+          "uses": "actions/deploy-pages@v4.0.5"
         }
       ]
     },
@@ -33,16 +43,26 @@
         "url": "${{ steps.deployment.outputs.page_url }}"
       },
       "if": "github.repository == 'danvk/dygraphs'",
-      "runs-on": "ubuntu-latest",
+      "runs-on": "ubuntu-24.04",
       "steps": [
         {
-          "uses": "actions/checkout@v3.3.0"
+          "uses": "actions/checkout@v4.2.2"
+        },
+        {
+          "run": "sh .pages.sh"
+        },
+        {
+          "uses": "actions/upload-artifact@v4.6.0",
+          "with": {
+            "name": "package-lock.json",
+            "path": "package-lock.json"
+          }
         },
         {
-          "run": "./.pages.sh"
+          "uses": "actions/upload-pages-artifact@v3.0.1"
         },
         {
-          "uses": "actions/upload-pages-artifact@v1.0.7"
+          "run": "mksh .post.sh"
         }
       ]
     }
--- dygraphs-2.2.1.orig/.github/workflows/gha-update.yml
+++ dygraphs-2.2.1/.github/workflows/gha-update.yml
@@ -4,13 +4,13 @@
       "runs-on": "ubuntu-latest",
       "steps": [
         {
-          "uses": "actions/checkout@v3.3.0",
+          "uses": "actions/checkout@v4.2.2",
           "with": {
             "token": "${{ secrets.WORKFLOW_SECRET }}"
           }
         },
         {
-          "uses": "saadmk11/github-actions-version-updater@v0.7.3",
+          "uses": "saadmk11/github-actions-version-updater@v0.8.1",
           "with": {
             "pull_request_user_reviewers": "mirabilos",
             "token": "${{ secrets.WORKFLOW_SECRET }}"
--- dygraphs-2.2.1.orig/.pages.sh
+++ dygraphs-2.2.1/.pages.sh
@@ -22,13 +22,16 @@ export LC_ALL=C
 unset LANGUAGE
 
 set -o pipefail
+rm -f .post.state
+if test -h .post.state || test -e .post.state; then exit 255; fi
+state=''
 
 $sudoagi eatmydata
 sudoapt clean
 sudoapt update
 #sudoapt --purge dist-upgrade -y
 $sudoagi eatmydata git \
-    ed jq jsdoc-toolkit \
+    ed jq node-jsdoc2 \
     libjs-bootstrap libjs-jquery libjs-jquery-ui \
     mksh pax python3
 
@@ -40,12 +43,12 @@ eatmydata env TMPDIR=/tmp npm install
 
 (eatmydata npm run clean || :)
 eatmydata npm run build
-eatmydata npm run test
-eatmydata npm run test-min
+eatmydata npm run test || state="$state test"
+eatmydata npm run test-min || state="$state test-min"
 if [[ $GITHUB_REPOSITORY = danvk/dygraphs ]]; then
-	eatmydata npm run coverage
-	eatmydata scripts/post-coverage.sh
-	eatmydata scripts/weigh-in.sh
+	eatmydata npm run coverage || state="$state coverage"
+	eatmydata mksh scripts/post-coverage.sh || state="$state post-coverage"
+	eatmydata mksh scripts/weigh-in.sh || state="$state weigh-in"
 fi
 
 rm -rf _site
@@ -65,3 +68,5 @@ if [[ -n $imprint_text ]]; then
 	    '
 fi
 cd ..
+set -o noglob
+echo $state >.post.state
--- /dev/null
+++ dygraphs-2.2.1/.post.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+echo ::group::Check whether .pages.sh failed anywhere
+state=$(cat .post.state || echo state-file-missing)
+if test -n "$state"; then
+	echo "E: .pages.sh failed in: $state"
+	exit 1
+fi
+echo "I: nope, ok"
+echo ::endgroup::
--- dygraphs-2.2.1.orig/CHANGES.md
+++ dygraphs-2.2.1/CHANGES.md
@@ -1,3 +1,29 @@
+# next (git)
+
+## Breaking changes
+- …
+
+## New features
+- Define `Dygraph.integerTicks` (#925), `Dygraph.pickDateTickGranularity`
+- Warn in console if points are out of range (#1050)
+- …
+
+## Bugfixes
+- Re-add missing `Dygraph.DOTTED_LINE` export
+- Some edge cases in the crosshair plugin (#1034, #1038, #1039, #1040)
+- Update list of exported symbols
+- …
+
+## Other user-visible changes
+- Add talk link to `tutorial.html`
+- …
+
+## Internal refactors/fixes
+- Switch from `jsdoc-toolkit` to `node-jsdoc2` (Debian #1074595)
+- Bump some package versions matching Debian bookworm and \*buntu 24.04
+- Add support for newer babeljs and uglifyjs3
+- …
+
 # v2.2.1 (2023-02-16)
 
 ## Future incompatibilities
--- dygraphs-2.2.1.orig/DEVELOP.md
+++ dygraphs-2.2.1/DEVELOP.md
@@ -26,7 +26,7 @@ To run the tests, run:
 The prerequisites for a full “npm run build” are:
 
     # for building
-    apt-get install ed jsdoc-toolkit mksh pax python3
+    apt-get install ed node-jsdoc2 mksh pax python3
     # for docs
     apt-get install libjs-bootstrap libjs-jquery libjs-jquery-ui
 
--- dygraphs-2.2.1.orig/LICENSE.txt
+++ dygraphs-2.2.1/LICENSE.txt
@@ -10,6 +10,7 @@ Copyright (c) 2014 mirabilos <m@mirbsd.o
 Copyright (c) 2015 Petr Shevtsov <petr.shevtsov@gmail.com>
 Copyright (c) 2022, 2023 mirabilos <t.glaser@tarent.de>
                    Deutsche Telekom LLCTO
+Copyright (c) 2025 mirabilos <tg@debian.org>
 and numerous contributors (see git log)
 
 Some tests additionally are:
@@ -33,6 +34,9 @@ Parts of the documentation are or make u
 Copyright (c) 2012 Google, Inc.
 - Robert Konigsberg <konigsberg@google.com>
 
+Further contributors can be found in the git history or on:
+https://github.com/danvk/dygraphs/graphs/contributors
+
 The automatically added browser-pack shim is:
 
 Copyright (c) 2013, 2014 James Halliday <mail@substack.net>
--- dygraphs-2.2.1.orig/auto_tests/tests/PixelSampler.js
+++ dygraphs-2.2.1/auto_tests/tests/PixelSampler.js
@@ -1,3 +1,5 @@
+'use strict';
+
 // Copyright 2012 Google Inc. All Rights Reserved.
 
 /**
@@ -7,8 +9,6 @@
  * @license MIT
  */
 
-'use strict';
-
 /**
  * @constructor
  */
--- dygraphs-2.2.1.orig/common/textarea.js
+++ dygraphs-2.2.1/common/textarea.js
@@ -1,3 +1,5 @@
+'use strict';
+
 // Copyright (c) 2012 Google, Inc.
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -23,7 +25,6 @@
  *
  * @author konigsberg@google.com (Robert Konigsberg)
  */
-"use strict";
 
 function TextArea(parent) {
   var body = document.getElementsByTagName("body")[0];
--- dygraphs-2.2.1.orig/docs/NOTES
+++ dygraphs-2.2.1/docs/NOTES
@@ -5,11 +5,11 @@ browser.
 To iterate locally on the docs, run:
 
     cd (dygraphs)/docs
-    ./ssi_server.py
+    python3 ssi_server.py
 
 Then visit localhost:8000 in your browser. You can edit/save/reload to see changes.
 
 To push documentation to the web, run:
 
     cd (dygraphs)
-    ./push-to-web.sh user@site.com:directory
+    mksh scripts/push-to-web.sh user@site.com:directory
--- dygraphs-2.2.1.orig/docs/ssi_expander.py
+++ dygraphs-2.2.1/docs/ssi_expander.py
@@ -7,7 +7,7 @@ Only files that end in '.html' are proce
 
 Usage:
 
-  ./ssi_expander.py [source_directory] destination_directory
+  python3 ssi_expander.py [source_directory] destination_directory
 
 If source_directory is not specified, then the current directory is used.
 '''
--- dygraphs-2.2.1.orig/docs/ssi_server.py
+++ dygraphs-2.2.1/docs/ssi_server.py
@@ -2,7 +2,7 @@
 '''
 Use this in the same way as Python's SimpleHTTPServer:
 
-  ./ssi_server.py [port]
+  python3 ssi_server.py [port]
 
 The only difference is that, for files ending in '.html', ssi_server will
 inline SSI (Server Side Includes) of the form:
--- dygraphs-2.2.1.orig/docs/tutorial.html
+++ dygraphs-2.2.1/docs/tutorial.html
@@ -449,4 +449,9 @@ perl -ne 'BEGIN{print "Month,Nominal,Rea
 
 <p>To get some inspiration, look at how the <a href="gallery/">charts in our gallery</a> are built.</p>
 
+<p>The original author Dan Vanderkam gave a <a
+ href="https://www.youtube.com/watch?v=I6E5e1HBFi0">42-minute talk
+ about dygraphs</a> in 2017 which might be helpful background for
+ anyone interested in using or contributing to the project.</p>
+
 <!--#include virtual="footer.html" -->
--- dygraphs-2.2.1.orig/dygraph-exports.js
+++ dygraphs-2.2.1/dygraph-exports.js
@@ -6,18 +6,30 @@
 
 goog.exportSymbol('Dygraph', Dygraph);
 
+goog.exportSymbol('Dygraph.prototype.addAndTrackEvent', Dygraph.prototype.addAndTrackEvent);
 goog.exportSymbol('Dygraph.prototype.adjustRoll', Dygraph.prototype.adjustRoll);
 goog.exportSymbol('Dygraph.prototype.annotations', Dygraph.prototype.annotations);
+goog.exportSymbol('Dygraph.prototype.axisPropertiesForSeries', Dygraph.prototype.axisPropertiesForSeries);
 goog.exportSymbol('Dygraph.prototype.clearSelection', Dygraph.prototype.clearSelection);
 goog.exportSymbol('Dygraph.prototype.destroy', Dygraph.prototype.destroy);
+goog.exportSymbol('Dygraph.prototype.doAnimatedZoom', Dygraph.prototype.doAnimatedZoom);
 goog.exportSymbol('Dygraph.prototype.eventToDomCoords', Dygraph.prototype.eventToDomCoords);
+goog.exportSymbol('Dygraph.prototype.findClosestPoint', Dygraph.prototype.findClosestPoint);
+goog.exportSymbol('Dygraph.prototype.findClosestRow', Dygraph.prototype.findClosestRow);
+goog.exportSymbol('Dygraph.prototype.findStackedPoint', Dygraph.prototype.findStackedPoint);
 goog.exportSymbol('Dygraph.prototype.getArea', Dygraph.prototype.getArea);
+goog.exportSymbol('Dygraph.prototype.getBooleanOption', Dygraph.prototype.getBooleanOption);
 goog.exportSymbol('Dygraph.prototype.getColors', Dygraph.prototype.getColors);
+goog.exportSymbol('Dygraph.prototype.getFunctionOption', Dygraph.prototype.getFunctionOption);
 goog.exportSymbol('Dygraph.prototype.getHighlightSeries', Dygraph.prototype.getHighlightSeries);
 goog.exportSymbol('Dygraph.prototype.getLabels', Dygraph.prototype.getLabels);
+goog.exportSymbol('Dygraph.prototype.getNumericOption', Dygraph.prototype.getNumericOption);
 goog.exportSymbol('Dygraph.prototype.getOption', Dygraph.prototype.getOption);
+goog.exportSymbol('Dygraph.prototype.getOptionForAxis', Dygraph.prototype.getOptionForAxis);
 goog.exportSymbol('Dygraph.prototype.getPropertiesForSeries', Dygraph.prototype.getPropertiesForSeries);
+goog.exportSymbol('Dygraph.prototype.getRowForX', Dygraph.prototype.getRowForX);
 goog.exportSymbol('Dygraph.prototype.getSelection', Dygraph.prototype.getSelection);
+goog.exportSymbol('Dygraph.prototype.getStringOption', Dygraph.prototype.getStringOption);
 goog.exportSymbol('Dygraph.prototype.getValue', Dygraph.prototype.getValue);
 goog.exportSymbol('Dygraph.prototype.indexFromSetName', Dygraph.prototype.indexFromSetName);
 goog.exportSymbol('Dygraph.prototype.isSeriesLocked', Dygraph.prototype.isSeriesLocked);
@@ -32,6 +44,7 @@ goog.exportSymbol('Dygraph.prototype.rol
 goog.exportSymbol('Dygraph.prototype.setAnnotations', Dygraph.prototype.setAnnotations);
 goog.exportSymbol('Dygraph.prototype.setSelection', Dygraph.prototype.setSelection);
 goog.exportSymbol('Dygraph.prototype.setVisibility', Dygraph.prototype.setVisibility);
+goog.exportSymbol('Dygraph.prototype.size', Dygraph.prototype.size);
 goog.exportSymbol('Dygraph.prototype.toDataCoords', Dygraph.prototype.toDataCoords);
 goog.exportSymbol('Dygraph.prototype.toDataXCoord', Dygraph.prototype.toDataXCoord);
 goog.exportSymbol('Dygraph.prototype.toDataYCoord', Dygraph.prototype.toDataYCoord);
@@ -45,6 +58,7 @@ goog.exportSymbol('Dygraph.prototype.upd
 goog.exportSymbol('Dygraph.prototype.visibility', Dygraph.prototype.visibility);
 goog.exportSymbol('Dygraph.prototype.xAxisExtremes', Dygraph.prototype.xAxisExtremes);
 goog.exportSymbol('Dygraph.prototype.xAxisRange', Dygraph.prototype.xAxisRange);
+goog.exportSymbol('Dygraph.prototype.yAxisExtremes', Dygraph.prototype.yAxisExtremes);
 goog.exportSymbol('Dygraph.prototype.yAxisRange', Dygraph.prototype.yAxisRange);
 goog.exportSymbol('Dygraph.prototype.yAxisRanges', Dygraph.prototype.yAxisRanges);
 
--- dygraphs-2.2.1.orig/package.json
+++ dygraphs-2.2.1/package.json
@@ -32,10 +32,10 @@
   },
   "homepage": "https://github.com/danvk/dygraphs",
   "devDependencies": {
-    "@babel/cli": "^7.19.3",
-    "@babel/core": "^7.20.2",
-    "@babel/plugin-transform-strict-mode": "^7.18.6",
-    "@babel/preset-env": "^7.20.2",
+    "@babel/cli": "^7.20.15",
+    "@babel/core": ">=7.20.15 <7.21.4 || ^7.21.5",
+    "@babel/plugin-transform-strict-mode": "^7.20.15",
+    "@babel/preset-env": "^7.20.15",
     "babel-plugin-add-module-exports": "^1.0.4",
     "browser-pack": "^6.1.0",
     "browserify": "^17.0.0",
@@ -58,21 +58,21 @@
     "phantomjs-function-bind-polyfill": "^1.0.0",
     "pre-commit": "^1.0.6",
     "source-map": "^0.6.1",
-    "uglify-js": "^3.17.3",
+    "uglify-js": "^3.17.4",
     "watchify": "^4.0.0"
   },
   "scripts": {
-    "build": "./scripts/build.sh",
-    "build-jsonly": "./scripts/build-js.sh",
-    "clean": "./scripts/clean.sh",
-    "coverage": "./scripts/generate-coverage.sh",
-    "site": "./scripts/run-site.sh",
+    "build": "mksh scripts/build.sh",
+    "build-jsonly": "mksh scripts/build-js.sh",
+    "clean": "mksh scripts/clean.sh",
+    "coverage": "mksh scripts/generate-coverage.sh",
+    "site": "mksh scripts/run-site.sh",
     "prepack": "rm -rf _site && cp -Lr site _site && pax -rw -l LICENSE.txt README.md _site/ && jdupes -rL dist _site src src-es5 && cd _site && mksh ../scripts/mkdiridx.sh --ah '<a href=\"https://www.npmjs.com/package/dygraphs\">dygraphs</a> manual integration tests' ./tests",
     "postpack": "rm -rf _site",
-    "watch": "./scripts/watch.sh",
-    "test": "./scripts/run-tests.sh",
-    "test-min": "./scripts/run-tests.sh min",
-    "tests-ok": "./scripts/check-no-only.sh"
+    "watch": "mksh scripts/watch.sh",
+    "test": "mksh scripts/run-tests.sh",
+    "test-min": "mksh scripts/run-tests.sh min",
+    "tests-ok": "mksh scripts/check-no-only.sh"
   },
   "pre-commit": [
     "tests-ok"
--- dygraphs-2.2.1.orig/releases.json
+++ dygraphs-2.2.1/releases.json
@@ -5,6 +5,14 @@
       "dygraph.js",
       "dygraph.css"
     ],
+    "version": "2.2.1"
+  },
+  {
+    "files": [
+      "dygraph.min.js",
+      "dygraph.js",
+      "dygraph.css"
+    ],
     "version": "2.2.0"
   },
   {
--- dygraphs-2.2.1.orig/scripts/build-docs.sh
+++ dygraphs-2.2.1/scripts/build-docs.sh
@@ -1,5 +1,9 @@
 #!/bin/mksh
 set -e
+case $KSH_VERSION {
+(*MIRBSD\ KSH*) ;;
+(*) echo E: do not call me with bash or something; exit 255 ;;
+}
 
 v=$(sed -n '/^Dygraph.VERSION = "\(.*\)";$/s//\1/p' <src/dygraph.js)
 test -n "$v" || {
@@ -7,16 +11,17 @@ test -n "$v" || {
   exit 1
 }
 
-if [[ -d debian ]]; then
-  dv=$v
+if [[ -n $IS_ACTUAL_DEBIAN_BUILD ]]; then
+  dv=$(dpkg-parsechangelog -S Version)
+  dv="$dv ($v)"
 else
   dv=
 fi
 
 rm -f docs/download.html docs/options.html docs/versions.html
-scripts/generate-download.py ${dv:+"$dv"} >docs/download.html
-scripts/generate-documentation.py >docs/options.html
-scripts/generate-versions.sh >docs/versions.html
+python3 scripts/generate-download.py ${dv:+"$dv"} >docs/download.html
+python3 scripts/generate-documentation.py >docs/options.html
+mksh scripts/generate-versions.sh >docs/versions.html
 chmod a+r docs/download.html docs/options.html docs/versions.html
 for file in docs/download.html docs/options.html docs/versions.html; do
   test -s "$file" || {
@@ -25,13 +30,13 @@ for file in docs/download.html docs/opti
   }
 done
 
-scripts/generate-jsdoc.sh
+mksh scripts/generate-jsdoc.sh
 
 rm -rf docroot
 mkdir docroot
 cd docs
 pax -rw . ../docroot/
-./ssi_expander.py ../docroot/
+python3 ssi_expander.py ../docroot/
 cd ../docroot
 rm -f NOTES TODO common footer.html header.html *.py
 mv README README-docs.txt
--- dygraphs-2.2.1.orig/scripts/build-js.sh
+++ dygraphs-2.2.1/scripts/build-js.sh
@@ -1,4 +1,8 @@
 #!/bin/mksh
+case $KSH_VERSION {
+(*MIRBSD\ KSH*) ;;
+(*) echo E: do not call me with bash or something; exit 255 ;;
+}
 
 # initialisation
 set -e
@@ -42,7 +46,7 @@ fi
 pax -rw -l auto_tests src disttmp/
 
 # licence headers for unminified and minified js, respectively
-scripts/txt2js.sh LICENSE.txt disttmp/LICENCE.js
+mksh scripts/txt2js.sh LICENSE.txt disttmp/LICENCE.js
 header="/*! @license https://github.com/danvk/dygraphs/blob/v$relv/LICENSE.txt (MIT) */"
 
 # prepare for building; avoid bad relative paths
@@ -72,7 +76,7 @@ rm -rf auto_tests src
 
 # bundle dygraph.js{,.map} and tests.js with dev env
 cp -r es5 src
-../scripts/env-patcher.sh development src
+mksh ../scripts/env-patcher.sh development src
 browserify \
     -v \
     --debug \
@@ -89,16 +93,16 @@ browserify \
     tests5/tests/*.js \
     >tests.tmp.js
 rm -rf src
-../scripts/smap-out.py dygraph.tmp.js dygraph.js dygraph.js.map
-../scripts/smap-out.py tests.tmp.js tests.tmp2.js tests.tmp.map
+python3 ../scripts/smap-out.py dygraph.tmp.js dygraph.js dygraph.js.map
+python3 ../scripts/smap-out.py tests.tmp.js tests.tmp2.js tests.tmp.map
 jq . <tests.tmp.map | perl -MCwd -pe \
     's!^ *"((?:\.\./)+)!Cwd::realpath($1) eq "/" ? "\"/" : $&!e;' \
     >tests.tmp2.map
-../scripts/smap-in.py tests.tmp2.js tests.tmp2.map tests.js #--nonl
+python3 ../scripts/smap-in.py tests.tmp2.js tests.tmp2.map tests.js #--nonl
 
 # bundle and minify dygraph.min.js{,.map} with prod env
 cp -r es5 src
-../scripts/env-patcher.sh production src
+mksh ../scripts/env-patcher.sh production src
 browserify \
     -v \
     --debug \
@@ -107,9 +111,14 @@ browserify \
     src/dygraph.js \
     >dygraph.min.tmp.js
 rm -rf src
-../scripts/smap-out.py dygraph.min.tmp.js /dev/null dygraph.min.tmp.js.map
+python3 ../scripts/smap-out.py dygraph.min.tmp.js /dev/null dygraph.min.tmp.js.map
+
+uglifyjs=$(uglifyjs --help 2>&1)
+set -A compatopts -- --no-module --v8 --webkit
+[[ $uglifyjs = *--no-module* ]] || unset compatopts[0]
 
 uglifyjs \
+    "${compatopts[@]}" \
     --compress \
     --mangle \
     --output-opts "preamble='$header'" \
--- dygraphs-2.2.1.orig/scripts/build.sh
+++ dygraphs-2.2.1/scripts/build.sh
@@ -1,20 +1,69 @@
 #!/bin/mksh
 set -ex
+case $KSH_VERSION {
+(*MIRBSD\ KSH*) ;;
+(*) echo E: do not call me with bash or something; exit 255 ;;
+}
 
 # Build code, tests, browser bundles.
-scripts/build-js.sh
+mksh scripts/build-js.sh
 
 # Build documentation.
-scripts/build-docs.sh
+mksh scripts/build-docs.sh
 
 # This is for on the webserver
 rm -rf site _site
 mkdir site
 cd docroot
 pax -rw . ../site/
-rm ../site/.jslibs/* ../site/LICENSE.txt ../site/dist
-cp -L .jslibs/* ../site/.jslibs/
+set -A torm -- ../site/LICENSE.txt ../site/dist
+[[ -n $IS_ACTUAL_DEBIAN_BUILD ]] || set -A torm+ -- ../site/.jslibs/*
+rm "${torm[@]}"
+[[ -n $IS_ACTUAL_DEBIAN_BUILD ]] || cp -L .jslibs/* ../site/.jslibs/
 cd ..
 pax -rw LICENSE.txt dist site/
 rm -f site/dist/tests.js
-find site -print0 | xargs -0r chmod a+rX --
+find site -type d -print0 | xargs -0r chmod 0755 --
+find site -type f -print0 | xargs -0r chmod 0644 --
+set +ex
+rv=0
+x=$(find site \( ! -type d -a ! -type f -a ! -type l \) -ls) || {
+	print -ru2 -- "E: could not check for bogus filetypes"
+	rv=1
+	x=
+}
+[[ -z $x ]] || {
+	print -ru2 -- "E: bogus filetypes found"
+	print -r -- "$x" | sed 's/^/N: /' >&2
+	rv=1
+}
+if [[ $(find --help 2>&1) = *' -printf '* ]]; then
+	x=$(find site -type l -printf '(%Y)%p\n') || {
+		print -ru2 -- "E: could not check for dangling symlinks"
+		rv=1
+		x=
+	}
+	[[ -z $(print -r -- "$x" | grep -v '^[(][dfN][)]') ]] || {
+		print -ru2 -- "E: bad filetypes found"
+		print -r -- "$x" | sed 's/^/N: /' >&2
+		rv=1
+	}
+	x=$(print -r -- "$x" | grep '^[(]N[)]')
+	[[ -z $x ]] || {
+		if [[ -n $IS_ACTUAL_DEBIAN_BUILD ]]; then
+			pf=W:
+			(( rv |= 2 ))
+		else
+			pf=E:
+			rv=1
+		fi
+		print -ru2 -- "$pf dangling symlinks found"
+		print -r -- "$x" | sed 's/^/N: /' >&2
+	}
+fi
+if (( rv & 1 )); then
+	exit 1
+elif (( rv == 0 )); then
+	print -ru2 -- "I: build done"
+fi
+exit 0
--- dygraphs-2.2.1.orig/scripts/env-patcher.sh
+++ dygraphs-2.2.1/scripts/env-patcher.sh
@@ -2,6 +2,10 @@
 # © 2022 mirabilos <t.glaser@tarent.de> Ⓕ MIT
 
 set -eo pipefail
+case $KSH_VERSION {
+(*MIRBSD\ KSH*) ;;
+(*) echo E: do not call me with bash or something; exit 255 ;;
+}
 mydir=$(realpath "$0/..")
 
 if [[ $1 = development ]]; then
@@ -22,9 +26,9 @@ fi
 
 grep -FrlZ process.env.NODE_ENV "$@" | while IFS= read -d '' -r fn; do
 	print -ru2 "I: patching $fn for !prod=$rpl"
-	"$mydir"/smap-out.py "$fn" env-patcher.tmp.js env-patcher.tmp.map
+	python3 "$mydir"/smap-out.py "$fn" env-patcher.tmp.js env-patcher.tmp.map
 	$node_js "$mydir"/env-patcher.js "$rpl"
-	"$mydir"/smap-in.py env-patcher.tmp.js env-patcher.tmp.map "$fn" --nonl
+	python3 "$mydir"/smap-in.py env-patcher.tmp.js env-patcher.tmp.map "$fn" --nonl
 done
 rm -f env-patcher.tmp.js env-patcher.tmp.map
 print -ru2 "I: done patching"
--- dygraphs-2.2.1.orig/scripts/generate-coverage.sh
+++ dygraphs-2.2.1/scripts/generate-coverage.sh
@@ -3,7 +3,11 @@
 # This requires dist/*.js to be in place.
 # Output is coverage/lcov.info
 
-set -o errexit
+set -e
+case $KSH_VERSION {
+(*MIRBSD\ KSH*) ;;
+(*) echo E: do not call me with bash or something; exit 255 ;;
+}
 if [[ -e node_modules ]]; then
 	babel_js=babel
 else
@@ -54,7 +58,7 @@ phantomjs \
   http://localhost:8082/auto_tests/coverage.html \
   spec '{"hooks": "mocha-phantomjs-istanbul", "coverageFile": "coverage/coverage.json"}'
 
-if [ $CI ]; then
+if test -n "$CI"; then
   # Convert the JSON coverage to LCOV for coveralls.
   istanbul report --include coverage/*.json lcovonly
 
--- dygraphs-2.2.1.orig/scripts/generate-download.py
+++ dygraphs-2.2.1/scripts/generate-download.py
@@ -2,7 +2,7 @@
 
 # Generates docs/download.html
 # Run:
-# ./generate-download.py > docs/download.html
+# python3 generate-download.py >docs/download.html
 
 import json
 import sys
--- dygraphs-2.2.1.orig/scripts/generate-jsdoc.sh
+++ dygraphs-2.2.1/scripts/generate-jsdoc.sh
@@ -3,6 +3,10 @@
 # Generates JSDoc in the /jsdoc dir. Clears any existing jsdoc there.
 
 set -e
+case $KSH_VERSION {
+(*MIRBSD\ KSH*) ;;
+(*) echo E: do not call me with bash or something; exit 255 ;;
+}
 
 v=$(sed -n '/^Dygraph.VERSION = "\(.*\)";$/s//\1/p' <src/dygraph.js)
 test -n "$v" || {
@@ -10,12 +14,20 @@ test -n "$v" || {
   exit 1
 }
 
+if [[ -n $IS_ACTUAL_DEBIAN_BUILD ]]; then
+  dv=$(dpkg-parsechangelog -S Version)
+  v="$v (Debian $dv)"
+elif [[ $GITHUB_REF = refs/heads/debian && $GITHUB_REPOSITORY = mirabilos/dygraphs ]]; then
+  v="$v+WIP"
+fi
+
 rm -rf jsdoc jsdoc.tmp
 mkdir jsdoc.tmp
 t=$PWD/jsdoc.tmp
-(cd /usr/share/jsdoc-toolkit/templates/jsdoc && pax -rw . "$t/")
+(cd /usr/share/nodejs/jsdoc2/templates/jsdoc && pax -rw . "$t/")
 find jsdoc.tmp -type f -print0 | xargs -0r perl -pi -e \
-  "s! on [{][+]new Date[(][)][+][}]! for dygraph $v!g"
+  "s/\r+$//;" -e \
+  "s! on [{][+]new Date[(][)][+][}]! for dygraph $v!g;"
 
 echo Generating JSDoc...
 srcfiles=src/dygraph.js
@@ -28,7 +40,6 @@ srcfiles+=\ src/dygraph-internal.externs
 srcfiles+=\ src/dygraph-layout.js
 srcfiles+=\ src/dygraph-options-reference.js
 srcfiles+=\ src/dygraph-options.js
-srcfiles+=\ src/dygraph-plugin-install.js
 srcfiles+=\ src/dygraph-tickers.js
 srcfiles+=\ src/dygraph-types.js
 #srcfiles+=\ src/dygraph-utils.js
@@ -53,17 +64,12 @@ srcfiles+=\ src/plugins/grid.js
 #srcfiles+=\ src/extras/super-annotations.js
 #srcfiles+=\ src/extras/synchronizer.js
 #srcfiles+=\ src/extras/unzoom.js
-jsdoc \
+jsdoc2 \
   -t="$t" \
   -d=jsdoc \
   $srcfiles \
 2>&1 | tee jsdoc.tmp/.errs
 
-ed -s jsdoc.tmp/.errs <<-\EOF
-	1g/java .*jsrun.jar/d
-	w
-	q
-EOF
 if test -s jsdoc.tmp/.errs; then errs=true; else errs=false; fi
 rm -rf jsdoc.tmp
 
--- dygraphs-2.2.1.orig/scripts/generate-versions.sh
+++ dygraphs-2.2.1/scripts/generate-versions.sh
@@ -2,6 +2,10 @@
 # © 2023 mirabilos <t.glaser@tarent.de> Ⓕ MIT
 
 set -eo pipefail
+case $KSH_VERSION {
+(*MIRBSD\ KSH*) ;;
+(*) echo E: do not call me with bash or something; exit 255 ;;
+}
 
 cat <<\EOF
 <!--#set var="pagetitle" value="version history" -->
--- dygraphs-2.2.1.orig/scripts/ghp-publish.sh
+++ dygraphs-2.2.1/scripts/ghp-publish.sh
@@ -1,5 +1,9 @@
 #!/bin/mksh
 set -exo pipefail
+case $KSH_VERSION {
+(*MIRBSD\ KSH*) ;;
+(*) echo E: do not call me with bash or something; exit 255 ;;
+}
 
 cd "$(dirname "$0")/.."
 
--- dygraphs-2.2.1.orig/scripts/mkdiridx.sh
+++ dygraphs-2.2.1/scripts/mkdiridx.sh
@@ -29,6 +29,10 @@
 
 export LC_ALL=C
 unset LANGUAGE
+case $KSH_VERSION {
+(*MIRBSD\ KSH*) ;;
+(*) echo E: do not call me with bash or something; exit 255 ;;
+}
 
 fw=' <b class="extlink" title="WARNING: accesses external resources (Google jsapi)">⚠</b>'
 ah='<a href="/">dygraphs JavaScript charting library</a> Pages'
--- dygraphs-2.2.1.orig/scripts/mkorigtgz.sh
+++ dygraphs-2.2.1/scripts/mkorigtgz.sh
@@ -13,6 +13,10 @@ export LC_ALL=C
 unset LANGUAGE
 set -e
 set -o pipefail
+case $KSH_VERSION {
+(*MIRBSD\ KSH*) ;;
+(*) echo E: do not call me with bash or something; exit 255 ;;
+}
 
 r=$(git rev-parse --show-toplevel)
 [[ $r != /* ]] || r=$(realpath "$r")
--- dygraphs-2.2.1.orig/scripts/post-coverage.sh
+++ dygraphs-2.2.1/scripts/post-coverage.sh
@@ -4,8 +4,9 @@
 # comment the line out to post
 #exit 0
 
-if [ $CI ]; then
-  <coverage/lcov.info ./node_modules/.bin/coveralls
+if test -n "$CI"; then
+  <coverage/lcov.info ./node_modules/.bin/coveralls || \
+    echo "N: above errors posting to coveralls.io are ignored"
 fi
 
 true  # reset exit code -- failure to post coverage shouldn't be an error.
--- dygraphs-2.2.1.orig/scripts/push-to-web.sh
+++ dygraphs-2.2.1/scripts/push-to-web.sh
@@ -6,8 +6,7 @@ if [[ -n $1 ]]; then
   exit 1
 fi
 
-set -x
-set -o errexit
+set -ex
 site=$1
 
 # Produce dist/*.js and docroot/ and all in site/
--- dygraphs-2.2.1.orig/scripts/run-tests.sh
+++ dygraphs-2.2.1/scripts/run-tests.sh
@@ -4,7 +4,11 @@
 # `npm run build` or `npm run watch` before running this.
 # Additional arguments are passed to mocha-phantomjs, e.g.
 # run-tests.sh --grep interaction-model
-set -o errexit
+set -e
+case $KSH_VERSION {
+(*MIRBSD\ KSH*) ;;
+(*) echo E: do not call me with bash or something; exit 255 ;;
+}
 
 # Run http-server and save its PID
 http-server -p 8081 > /dev/null &
--- dygraphs-2.2.1.orig/scripts/smap-in.py
+++ dygraphs-2.2.1/scripts/smap-in.py
@@ -14,7 +14,7 @@ elif len(sys.argv) == 5 and sys.argv[4]
     # evil hack for browserify
     donl = False
 else:
-    sys.stderr.write('E: syntax: smap-in.py in.js in.map out.js\n')
+    sys.stderr.write('E: syntax: python3 smap-in.py in.js in.map out.js\n')
     sys.exit(1)
 
 with open(sys.argv[1], 'r') as f:
--- dygraphs-2.2.1.orig/scripts/smap-out.py
+++ dygraphs-2.2.1/scripts/smap-out.py
@@ -63,7 +63,7 @@ if len(sys.argv) == 4:
 elif len(sys.argv) == 5:
     smapname = sys.argv[4]
 else:
-    sys.stderr.write('E: syntax: smap-out.py in.js out.js out.map [maplink]\n')
+    sys.stderr.write('E: syntax: python3 smap-out.py in.js out.js out.map [maplink]\n')
     sys.exit(1)
 
 with open(sys.argv[1], 'r') as f:
--- dygraphs-2.2.1.orig/scripts/txt2js.sh
+++ dygraphs-2.2.1/scripts/txt2js.sh
@@ -2,6 +2,10 @@
 # © 2022 mirabilos <t.glaser@tarent.de> Ⓕ MIT
 
 set -eo pipefail
+case $KSH_VERSION {
+(*MIRBSD\ KSH*) ;;
+(*) echo E: do not call me with bash or something; exit 255 ;;
+}
 mydir=$(realpath "$0/..")
 
 infile=$1
@@ -20,5 +24,5 @@ fi
 rm -f "$outfile" "$outfile.tmp.js" "$outfile.tmp.js.map"
 print -ru2 "I: converting $infile to $outfile"
 $node_js "$mydir"/txt2js.js "$infile" "$outfile.tmp.js"
-"$mydir"/smap-in.py "$outfile.tmp.js" "$outfile.tmp.js.map" "$outfile" --nonl
+python3 "$mydir"/smap-in.py "$outfile.tmp.js" "$outfile.tmp.js.map" "$outfile" --nonl
 rm -f "$outfile.tmp.js" "$outfile.tmp.js.map"
--- dygraphs-2.2.1.orig/scripts/weigh-in.sh
+++ dygraphs-2.2.1/scripts/weigh-in.sh
@@ -1,7 +1,11 @@
 #!/bin/mksh
 # This tracks the effect of pull requests on the size of dygraphs.
 # See https://github.com/danvk/travis-weigh-in
-set -o errexit
+set -e
+case $KSH_VERSION {
+(*MIRBSD\ KSH*) ;;
+(*) echo E: do not call me with bash or something; exit 255 ;;
+}
 
 if [ -z "$GITHUB_TOKEN" ]; then
     echo "GITHUB_TOKEN not set. Skipping size checks."
--- dygraphs-2.2.1.orig/src/datahandler/bars-custom.js
+++ dygraphs-2.2.1/src/datahandler/bars-custom.js
@@ -1,3 +1,5 @@
+'use strict';
+
 /**
  * @license
  * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com)
@@ -10,7 +12,6 @@
  */
 
 /*global Dygraph:false */
-"use strict";
 
 import BarsHandler from './bars';
 
--- dygraphs-2.2.1.orig/src/datahandler/bars-error.js
+++ dygraphs-2.2.1/src/datahandler/bars-error.js
@@ -1,3 +1,5 @@
+'use strict';
+
 /**
  * @license
  * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com)
@@ -10,7 +12,6 @@
  */
 
 /*global Dygraph:false */
-"use strict";
 
 import BarsHandler from './bars';
 
--- dygraphs-2.2.1.orig/src/datahandler/bars-fractions.js
+++ dygraphs-2.2.1/src/datahandler/bars-fractions.js
@@ -1,3 +1,5 @@
+'use strict';
+
 /**
  * @license
  * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com)
@@ -11,7 +13,6 @@
  */
 
 /*global Dygraph:false */
-"use strict";
 
 import BarsHandler from './bars';
 
--- dygraphs-2.2.1.orig/src/datahandler/bars.js
+++ dygraphs-2.2.1/src/datahandler/bars.js
@@ -1,3 +1,5 @@
+'use strict';
+
 /**
  * @license
  * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com)
@@ -13,7 +15,6 @@
 
 /*global Dygraph:false */
 /*global DygraphLayout:false */
-"use strict";
 
 import DygraphDataHandler from './datahandler';
 import DygraphLayout from '../dygraph-layout';
--- dygraphs-2.2.1.orig/src/datahandler/datahandler.js
+++ dygraphs-2.2.1/src/datahandler/datahandler.js
@@ -1,3 +1,5 @@
+'use strict';
+
 /**
  * @license
  * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com)
@@ -41,8 +43,6 @@
 /*global Dygraph:false */
 /*global DygraphLayout:false */
 
-"use strict";
-
 /**
  *
  * The data handler is responsible for all data specific operations. All of the
--- dygraphs-2.2.1.orig/src/datahandler/default-fractions.js
+++ dygraphs-2.2.1/src/datahandler/default-fractions.js
@@ -1,3 +1,5 @@
+'use strict';
+
 /**
  * @license
  * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com)
@@ -10,7 +12,6 @@
  */
 
 /*global Dygraph:false */
-"use strict";
 
 import DygraphDataHandler from './datahandler';
 import DefaultHandler from './default';
--- dygraphs-2.2.1.orig/src/datahandler/default.js
+++ dygraphs-2.2.1/src/datahandler/default.js
@@ -1,3 +1,5 @@
+'use strict';
+
 /**
  * @license
  * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com)
@@ -10,7 +12,6 @@
  */
 
 /*global Dygraph:false */
-"use strict";
 
 import DygraphDataHandler from './datahandler';
 
--- dygraphs-2.2.1.orig/src/dygraph-canvas.js
+++ dygraphs-2.2.1/src/dygraph-canvas.js
@@ -1,3 +1,5 @@
+'use strict';
+
 /**
  * @license
  * Copyright 2006 Dan Vanderkam (danvdk@gmail.com)
@@ -25,7 +27,6 @@
  */
 
 /*global Dygraph:false */
-"use strict";
 
 import * as utils from './dygraph-utils';
 import Dygraph from './dygraph';
--- dygraphs-2.2.1.orig/src/dygraph-default-attrs.js
+++ dygraphs-2.2.1/src/dygraph-default-attrs.js
@@ -1,4 +1,4 @@
-'use strict'
+'use strict';
 
 import * as DygraphTickers from './dygraph-tickers';
 import DygraphInteraction from './dygraph-interaction-model';
--- dygraphs-2.2.1.orig/src/dygraph-gviz.js
+++ dygraphs-2.2.1/src/dygraph-gviz.js
@@ -1,3 +1,5 @@
+'use strict';
+
 /**
  * @license
  * Copyright 2011 Dan Vanderkam (danvdk@gmail.com)
@@ -18,7 +20,6 @@
  */
 
 /*global Dygraph:false */
-"use strict";
 
 import Dygraph from './dygraph';
 
--- dygraphs-2.2.1.orig/src/dygraph-interaction-model.js
+++ dygraphs-2.2.1/src/dygraph-interaction-model.js
@@ -1,3 +1,5 @@
+'use strict';
+
 /**
  * @license
  * Copyright 2011 Robert Konigsberg (konigsberg@google.com)
@@ -11,7 +13,6 @@
  */
 
 /*global Dygraph:false */
-"use strict";
 
 import * as utils from './dygraph-utils';
 
--- dygraphs-2.2.1.orig/src/dygraph-layout.js
+++ dygraphs-2.2.1/src/dygraph-layout.js
@@ -1,3 +1,5 @@
+'use strict';
+
 /**
  * @license
  * Copyright 2011 Dan Vanderkam (danvdk@gmail.com)
@@ -10,7 +12,6 @@
  */
 
 /*global Dygraph:false */
-"use strict";
 
 import * as utils from './dygraph-utils';
 
@@ -247,12 +248,14 @@ DygraphLayout.prototype._evaluateLineCha
     var axis = this.dygraph_.axisPropertiesForSeries(setName);
     // TODO (konigsberg): use optionsForAxis instead.
     var logscale = this.dygraph_.attributes_.getForSeries("logscale", setName);
+    var outOfXBounds = 0, outOfYBounds = 0;
 
     for (var j = 0; j < points.length; j++) {
       var point = points[j];
 
       // Range from 0-1 where 0 represents left and 1 represents right.
       point.x = DygraphLayout.calcXNormal_(point.xval, this._xAxis, isLogscaleForX);
+      outOfXBounds += (point.x < 0) || (point.x > 1);
       // Range from 0-1 where 0 represents top and 1 represents bottom
       var yval = point.yval;
       if (isStacked) {
@@ -269,6 +272,14 @@ DygraphLayout.prototype._evaluateLineCha
         }
       }
       point.y = DygraphLayout.calcYNormal_(axis, yval, logscale);
+      outOfYBounds += (point.y < 0) || (point.y > 1);
+    }
+
+    if (outOfXBounds > 2) {
+      console.warn(outOfXBounds + ' points out of X bounds:' + this._xAxis.minval + ' - ' + this._xAxis.maxval);
+    }
+    if (outOfYBounds > 0) {
+      console.warn(outOfYBounds + ' points out of Y bounds:' + axis.minyval + ' - ' + axis.maxyval);
     }
 
     this.dygraph_.dataHandler_.onLineEvaluated(points, axis, logscale);
--- dygraphs-2.2.1.orig/src/dygraph-options-reference.js
+++ dygraphs-2.2.1/src/dygraph-options-reference.js
@@ -1,11 +1,11 @@
+'use strict';
+
 /**
  * @license
  * Copyright 2011 Dan Vanderkam (danvdk@gmail.com)
  * MIT-licenced: https://opensource.org/licenses/MIT
  */
 
-"use strict";
-
 var OPTIONS_REFERENCE = null;
 
 if (typeof process !== 'undefined' && process.env.NODE_ENV != 'production') {
--- dygraphs-2.2.1.orig/src/dygraph-options.js
+++ dygraphs-2.2.1/src/dygraph-options.js
@@ -1,3 +1,5 @@
+'use strict';
+
 /**
  * @license
  * Copyright 2011 Dan Vanderkam (danvdk@gmail.com)
@@ -11,7 +13,6 @@
 
 // TODO: remove this jshint directive & fix the warnings.
 /*jshint sub:true */
-"use strict";
 
 import * as utils from './dygraph-utils';
 import DEFAULT_ATTRS from './dygraph-default-attrs';
--- dygraphs-2.2.1.orig/src/dygraph-tickers.js
+++ dygraphs-2.2.1/src/dygraph-tickers.js
@@ -1,3 +1,5 @@
+'use strict';
+
 /**
  * @license
  * Copyright 2011 Dan Vanderkam (danvdk@gmail.com)
@@ -62,7 +64,6 @@
 
 /*jshint sub:true */
 /*global Dygraph:false */
-"use strict";
 
 import * as utils from './dygraph-utils';
 
@@ -81,8 +82,8 @@ var TickList = undefined;  // the ' = un
 var Ticker = undefined;  // the ' = undefined' keeps jshint happy.
 
 /** @type {Ticker} */
-export var numericLinearTicks = function(a, b, pixels, opts, dygraph, vals) {
-  var nonLogscaleOpts = function(opt) {
+export var numericLinearTicks = function (a, b, pixels, opts, dygraph, vals) {
+  var nonLogscaleOpts = function (opt) {
     if (opt === 'logscale') return false;
     return opts(opt);
   };
@@ -90,7 +91,7 @@ export var numericLinearTicks = function
 };
 
 /** @type {Ticker} */
-export var numericTicks = function(a, b, pixels, opts, dygraph, vals) {
+export var numericTicks = function (a, b, pixels, opts, dygraph, vals) {
   var pixels_per_tick = /** @type{number} */(opts('pixelsPerLabel'));
   var ticks = [];
   var i, j, tickV, nTicks;
@@ -208,7 +209,15 @@ export var numericTicks = function(a, b,
 };
 
 /** @type {Ticker} */
-export var dateTicker = function(a, b, pixels, opts, dygraph, vals) {
+export var integerTicks = function (a, b, pixels, opts, dygraph, vals) {
+    var allTicks = numericTicks(a, b, pixels, opts, dygraph, vals);
+    return allTicks.filter(function (tick) {
+      return tick.v % 1 === 0;
+    });
+};
+
+/** @type {Ticker} */
+export var dateTicker = function (a, b, pixels, opts, dygraph, vals) {
   var chosen = pickDateTickGranularity(a, b, pixels, opts);
 
   if (chosen >= 0) {
@@ -317,7 +326,7 @@ TICK_PLACEMENT[Granularity.CENTENNIAL]
  * NOTE: this assumes that utils.LOG_SCALE = 10.
  * @type {Array.<number>}
  */
-var PREFERRED_LOG_TICK_VALUES = (function() {
+var PREFERRED_LOG_TICK_VALUES = (function () {
   var vals = [];
   for (var power = -39; power <= 39; power++) {
     var range = Math.pow(10, power);
@@ -339,7 +348,7 @@ var PREFERRED_LOG_TICK_VALUES = (functio
  * @return {number} The appropriate axis granularity for this chart. See the
  *     enumeration of possible values in dygraph-tickers.js.
  */
-export var pickDateTickGranularity = function(a, b, pixels, opts) {
+export var pickDateTickGranularity = function (a, b, pixels, opts) {
   var pixels_per_tick = /** @type{number} */(opts('pixelsPerLabel'));
   for (var i = 0; i < Granularity.NUM_GRANULARITIES; i++) {
     var num_ticks = numDateTicks(a, b, i);
@@ -357,7 +366,7 @@ export var pickDateTickGranularity = fun
  * @param {number} granularity (one of the granularities enumerated above)
  * @return {number} (Approximate) number of ticks that would result.
  */
-var numDateTicks = function(start_time, end_time, granularity) {
+var numDateTicks = function (start_time, end_time, granularity) {
   var spacing = TICK_PLACEMENT[granularity].spacing;
   return Math.round(1.0 * (end_time - start_time) / spacing);
 };
@@ -371,7 +380,7 @@ var numDateTicks = function(start_time,
  * @param {Dygraph=} dg
  * @return {!TickList}
  */
-export var getDateAxis = function(start_time, end_time, granularity, opts, dg) {
+export var getDateAxis = function (start_time, end_time, granularity, opts, dg) {
   var formatter = /** @type{AxisLabelFormatter} */(
       opts("axisLabelFormatter"));
   var utc = opts("labelsUTC");
--- dygraphs-2.2.1.orig/src/dygraph-utils.js
+++ dygraphs-2.2.1/src/dygraph-utils.js
@@ -1,3 +1,5 @@
+'use strict';
+
 /**
  * @license
  * Copyright 2011 Dan Vanderkam (danvdk@gmail.com)
@@ -12,7 +14,6 @@
  */
 
 /*global Dygraph:false, Node:false */
-"use strict";
 
 import * as DygraphTickers from './dygraph-tickers';
 
--- dygraphs-2.2.1.orig/src/dygraph.js
+++ dygraphs-2.2.1/src/dygraph.js
@@ -1,3 +1,5 @@
+'use strict';
+
 /**
  * @license
  * Copyright 2006 Dan Vanderkam (danvdk@gmail.com)
@@ -73,8 +75,6 @@ import RangeSelectorPlugin from './plugi
 
 import GVizChart from './dygraph-gviz';
 
-"use strict";
-
 /**
  * @class Creates an interactive, zoomable chart.
  * @name Dygraph
@@ -3495,6 +3495,11 @@ Dygraph.prototype.removeTrackedEvents_ =
 };
 
 // Installed plugins, in order of precedence (most-general to most-specific).
+// This means that, in an event cascade, plugins which have registered
+// for that event will be called in reverse order.
+//
+// This is most relevant for plugins which register a layout event,
+// e.g. Axes, Legend and ChartLabels.
 Dygraph.PLUGINS = [
   LegendPlugin,
   AxesPlugin,
@@ -3507,6 +3512,7 @@ Dygraph.PLUGINS = [
 // There are many symbols which have historically been available through the
 // Dygraph class. These are exported here for backwards compatibility.
 Dygraph.GVizChart = GVizChart;
+Dygraph.DOTTED_LINE = utils.DOTTED_LINE;
 Dygraph.DASHED_LINE = utils.DASHED_LINE;
 Dygraph.DOT_DASH_LINE = utils.DOT_DASH_LINE;
 Dygraph.dateAxisLabelFormatter = utils.dateAxisLabelFormatter;
@@ -3546,8 +3552,10 @@ Dygraph.endZoom = DygraphInteraction.end
 
 Dygraph.numericLinearTicks = DygraphTickers.numericLinearTicks;
 Dygraph.numericTicks = DygraphTickers.numericTicks;
+Dygraph.integerTicks = DygraphTickers.integerTicks;
 Dygraph.dateTicker = DygraphTickers.dateTicker;
 Dygraph.Granularity = DygraphTickers.Granularity;
+Dygraph.pickDateTickGranularity = DygraphTickers.pickDateTickGranularity;
 Dygraph.getDateAxis = DygraphTickers.getDateAxis;
 Dygraph.floatFormat = utils.floatFormat;
 
--- dygraphs-2.2.1.orig/src/extras/crosshair.js
+++ dygraphs-2.2.1/src/extras/crosshair.js
@@ -18,7 +18,7 @@ if (window.Dygraph) {
 /* end of loader wrapper header */
 
 Dygraph.Plugins.Crosshair = (function _extras_crosshair_closure() {
-  "use strict";
+  'use strict';
 
   /**
    * Creates the crosshair
@@ -33,6 +33,15 @@ Dygraph.Plugins.Crosshair = (function _e
     this.strokeStyle_ = opt_options.strokeStyle || "rgba(0, 0, 0, 0.3)";
   };
 
+  crosshair.prototype.updateCanvasSize = function updateCanvasSize(width, height) {
+    if (width === this.canvas_.width && height === this.canvas_.height)
+      return;
+    this.canvas_.width = width;
+    this.canvas_.height = height;
+    this.canvas_.style.width = width + 'px';    // for IE
+    this.canvas_.style.height = height + 'px';  // for IE
+  };
+
   crosshair.prototype.toString = function toString() {
     return "Crosshair Plugin";
   };
@@ -42,6 +51,7 @@ Dygraph.Plugins.Crosshair = (function _e
    * @return {object.<string, function(ev)>} Mapping of event names to callbacks.
    */
   crosshair.prototype.activate = function activate(g) {
+    this.updateCanvasSize(g.width_, g.height_);
     g.graphDiv.appendChild(this.canvas_);
 
     return {
@@ -57,28 +67,38 @@ Dygraph.Plugins.Crosshair = (function _e
 
     var width = e.dygraph.width_;
     var height = e.dygraph.height_;
-    this.canvas_.width = width;
-    this.canvas_.height = height;
-    this.canvas_.style.width = width + "px";    // for IE
-    this.canvas_.style.height = height + "px";  // for IE
+    this.updateCanvasSize(width, height);
 
     var ctx = this.canvas_.getContext("2d");
     ctx.clearRect(0, 0, width, height);
     ctx.strokeStyle = this.strokeStyle_;
     ctx.beginPath();
 
-    var canvasx = Math.floor(e.dygraph.selPoints_[0].canvasx) + 0.5; // crisper rendering
-
-    if (this.direction_ === "vertical" || this.direction_ === "both") {
-      ctx.moveTo(canvasx, 0);
-      ctx.lineTo(canvasx, height);
+    if (this.direction_ === "both" || this.direction_ === "vertical") {
+      if (e.dygraph.selPoints_.length !== 0) {
+        var p = e.dygraph.selPoints_[0];
+        if (p.x >= 0 && p.x <= 1) {
+          var canvasx = Math.floor(p.canvasx) + 0.5; // crisper rendering
+          if (canvasx > width)
+            canvasx = width - 0.5;
+
+          ctx.moveTo(canvasx, 0);
+          ctx.lineTo(canvasx, height);
+        }
+      }
     }
 
-    if (this.direction_ === "horizontal" || this.direction_ === "both") {
+    if (this.direction_ === "both" || this.direction_ === "horizontal") {
       for (var i = 0; i < e.dygraph.selPoints_.length; i++) {
-        var canvasy = Math.floor(e.dygraph.selPoints_[i].canvasy) + 0.5; // crisper rendering
-        ctx.moveTo(0, canvasy);
-        ctx.lineTo(width, canvasy);
+        var p = e.dygraph.selPoints_[i];
+        if (p.y >= 0 && p.y <= 1) {
+          var canvasy = Math.floor(p.canvasy) + 0.5; // crisper rendering
+          if (canvasy > height)
+            canvasy = height - 0.5;
+
+          ctx.moveTo(0, canvasy);
+          ctx.lineTo(width, canvasy);
+        }
       }
     }
 
--- dygraphs-2.2.1.orig/src/extras/hairlines.js
+++ dygraphs-2.2.1/src/extras/hairlines.js
@@ -23,7 +23,7 @@ if (window.Dygraph) {
 
 Dygraph.Plugins.Hairlines = (function _extras_hairlines_closure() {
 
-"use strict";
+'use strict';
 
 /**
  * @typedef {
--- dygraphs-2.2.1.orig/src/extras/super-annotations.js
+++ dygraphs-2.2.1/src/extras/super-annotations.js
@@ -23,7 +23,7 @@ if (window.Dygraph) {
 
 Dygraph.Plugins.SuperAnnotations = (function _extras_superAnnotations_closure() {
 
-"use strict";
+'use strict';
 
 /**
  * These are just the basic requirements -- annotations can have whatever other
--- dygraphs-2.2.1.orig/src/extras/unzoom.js
+++ dygraphs-2.2.1/src/extras/unzoom.js
@@ -37,8 +37,7 @@ if (window.Dygraph) {
  * @author konigsberg@google.com (Robert Konigsberg)
  */
 Dygraph.Plugins.Unzoom = (function _extras_unzoom_closure() {
-
-  "use strict";
+  'use strict';
 
   /**
    * Create a new instance.
--- dygraphs-2.2.1.orig/src/iframe-tarp.js
+++ dygraphs-2.2.1/src/iframe-tarp.js
@@ -1,3 +1,5 @@
+'use strict';
+
 /**
  * To create a "drag" interaction, you typically register a mousedown event
  * handler on the element where the drag begins. In that handler, you register a
--- dygraphs-2.2.1.orig/src/plugins/annotations.js
+++ dygraphs-2.2.1/src/plugins/annotations.js
@@ -1,3 +1,5 @@
+'use strict';
+
 /**
  * @license
  * Copyright 2012 Dan Vanderkam (danvdk@gmail.com)
@@ -6,8 +8,6 @@
 
 /*global Dygraph:false */
 
-"use strict";
-
 /**
 Current bits of jankiness:
 - Uses dygraph.layout_ to get the parsed annotations.
--- dygraphs-2.2.1.orig/src/plugins/axes.js
+++ dygraphs-2.2.1/src/plugins/axes.js
@@ -1,3 +1,5 @@
+'use strict';
+
 /**
  * @license
  * Copyright 2012 Dan Vanderkam (danvdk@gmail.com)
@@ -6,8 +8,6 @@
 
 /*global Dygraph:false */
 
-'use strict';
-
 /*
 Bits of jankiness:
 - Direct layout access
--- dygraphs-2.2.1.orig/src/plugins/chart-labels.js
+++ dygraphs-2.2.1/src/plugins/chart-labels.js
@@ -1,3 +1,5 @@
+'use strict';
+
 /**
  * @license
  * Copyright 2012 Dan Vanderkam (danvdk@gmail.com)
@@ -5,8 +7,6 @@
  */
 /*global Dygraph:false */
 
-"use strict";
-
 // TODO(danvk): move chart label options out of dygraphs and into the plugin.
 // TODO(danvk): only tear down & rebuild the DIVs when it's necessary.
 
--- dygraphs-2.2.1.orig/src/plugins/grid.js
+++ dygraphs-2.2.1/src/plugins/grid.js
@@ -1,3 +1,5 @@
+'use strict';
+
 /**
  * @license
  * Copyright 2012 Dan Vanderkam (danvdk@gmail.com)
@@ -13,8 +15,6 @@ Current bits of jankiness:
 
 */
 
-"use strict";
-
 /**
  * Draws the gridlines, i.e. the gray horizontal & vertical lines running the
  * length of the chart.
--- dygraphs-2.2.1.orig/src/plugins/legend.js
+++ dygraphs-2.2.1/src/plugins/legend.js
@@ -1,3 +1,5 @@
+'use strict';
+
 /**
  * @license
  * Copyright 2012 Dan Vanderkam (danvdk@gmail.com)
@@ -15,7 +17,6 @@ Current bits of jankiness:
 */
 
 /*global Dygraph:false */
-"use strict";
 
 import * as utils from '../dygraph-utils';
 
--- dygraphs-2.2.1.orig/src/plugins/range-selector.js
+++ dygraphs-2.2.1/src/plugins/range-selector.js
@@ -1,3 +1,5 @@
+'use strict';
+
 /**
  * @license
  * Copyright 2011 Paul Felix (paul.eric.felix@gmail.com)
@@ -11,7 +13,6 @@
  */
 
 /*global Dygraph:false */
-"use strict";
 
 import * as utils from '../dygraph-utils';
 import DygraphInteraction from '../dygraph-interaction-model';
--- dygraphs-2.2.1.orig/tests/exported-symbols.html
+++ dygraphs-2.2.1/tests/exported-symbols.html
@@ -17,7 +17,7 @@
     <ol id="list">
     </ol>
 
-    <p>It exports these symbols inside each of those symbols and <tt>Dygraph.{Circles,Plugins}</tt>:</p>
+    <p>It exports these symbols inside each of those symbols and <tt>Dygraph.{Circles,Plugins,prototype}</tt>:</p>
     <ol id="list2">
     </ol>
 
@@ -29,7 +29,7 @@
     <ol id="list4">
     </ol>
 
-    <p>New second-level and <tt>Dygraph.{Circles,Plugins}</tt> elements after loading extras:</p>
+    <p>New second-level and <tt>Dygraph.{Circles,Plugins,prototype}</tt> elements after loading extras:</p>
     <ol id="list5">
     </ol>
 
@@ -83,6 +83,10 @@
         sym = 'Dygraph.Plugins.' + k;
         level2props.push(sym);
       }
+      for (k in Dygraph.prototype) {
+        sym = 'Dygraph.prototype.' + k;
+        level2props.push(sym);
+      }
 
       level2props.sort();
       html = '';
@@ -159,6 +163,11 @@
 	  if (!level2props.includes(sym))
 	    newl2props.push(sym);
 	}
+	for (k in Dygraph.prototype) {
+	  sym = 'Dygraph.prototype.' + k;
+	  if (!level2props.includes(sym))
+	    newl2props.push(sym);
+	}
 	newl2props.sort();
 	html = '';
 	for (i = 0; i < newl2props.length; i++)
