diff --git a/package-lock.json b/package-lock.json index 8b424ea5..f35f2044 100644 --- a/package-lock.json +++ b/package-lock.json @@ -96,28 +96,28 @@ "dev": true }, "@nodelib/fs.scandir": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.1.tgz", - "integrity": "sha512-NT/skIZjgotDSiXs0WqYhgcuBKhUMgfekCmCGtkUAiLqZdOnrdjmZr9wRl3ll64J9NF79uZ4fk16Dx0yMc/Xbg==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", + "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", "dev": true, "requires": { - "@nodelib/fs.stat": "2.0.1", + "@nodelib/fs.stat": "2.0.3", "run-parallel": "^1.1.9" } }, "@nodelib/fs.stat": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.1.tgz", - "integrity": "sha512-+RqhBlLn6YRBGOIoVYthsG0J9dfpO79eJyN7BYBkZJtfqrBwf2KK+rD/M/yjZR6WBmIhAgOV7S60eCgaSWtbFw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", "dev": true }, "@nodelib/fs.walk": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.2.tgz", - "integrity": "sha512-J/DR3+W12uCzAJkw7niXDcqcKBg6+5G5Q/ZpThpGNzAUz70eOR6RV4XnnSN01qHZiVl0eavoxJsBypQoKsV2QQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", + "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", "dev": true, "requires": { - "@nodelib/fs.scandir": "2.1.1", + "@nodelib/fs.scandir": "2.1.3", "fastq": "^1.6.0" } }, @@ -203,9 +203,9 @@ "dev": true }, "@types/node": { - "version": "12.6.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.6.2.tgz", - "integrity": "sha512-gojym4tX0FWeV2gsW4Xmzo5wxGjXGm550oVUII7f7G5o4BV6c7DBdiG1RRQd+y1bvqRyYtPfMK85UM95vsapqQ==", + "version": "12.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.7.tgz", + "integrity": "sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w==", "dev": true }, "a-big-triangle": { @@ -278,6 +278,24 @@ "robust-orientation": "^1.1.3" } }, + "aggregate-error": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", + "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "dependencies": { + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + } + } + }, "ajv": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", @@ -557,9 +575,9 @@ } }, "array-normalize": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/array-normalize/-/array-normalize-1.1.3.tgz", - "integrity": "sha1-c/uDf0gW7BkVHTxejYU6RZDOAb0=", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array-normalize/-/array-normalize-1.1.4.tgz", + "integrity": "sha512-fCp0wKFLjvSPmCn4F5Tiw4M3lpMZoHlCjfcs7nNzuj3vqQQ1/a8cgB9DXcpDSn18c+coLnaW7rqfcYCvKbyJXg==", "dev": true, "requires": { "array-bounds": "^1.0.0" @@ -944,9 +962,9 @@ "dev": true }, "acorn-jsx": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.2.tgz", - "integrity": "sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz", + "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==", "dev": true }, "chalk": { @@ -1211,6 +1229,12 @@ "uniq": "^1.0.1" } }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, "cli-cursor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", @@ -1553,8 +1577,7 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "country-regex": { "version": "1.1.0", @@ -1780,9 +1803,9 @@ "dev": true }, "d3-color": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.3.0.tgz", - "integrity": "sha512-NHODMBlj59xPAwl2BDiO2Mog6V+PrGRtBfWKqKRrs9MCqlSkIEb0Z/SfY7jW29ReHTDC/j+vwXhnZcXI3+3fbg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.0.tgz", + "integrity": "sha512-TzNPeJy2+iEepfiL92LAAB7fvnp/dV2YwANPVHdDWmYMm23qIJBYww3qT8I8C1wXrmrg4UWs7BKc2tKIgyjzHg==", "dev": true }, "d3-dispatch": { @@ -1967,22 +1990,25 @@ "dev": true }, "del": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-5.0.0.tgz", - "integrity": "sha512-TfU3nUY0WDIhN18eq+pgpbLY9AfL5RfiE9czKaTSolc6aK7qASXfDErvYgjV1UqCR4sNXDoxO0/idPmhDUt2Sg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/del/-/del-5.1.0.tgz", + "integrity": "sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA==", "dev": true, "requires": { - "globby": "^10.0.0", - "is-path-cwd": "^2.0.0", - "is-path-in-cwd": "^2.0.0", - "p-map": "^2.0.0", - "rimraf": "^2.6.3" + "globby": "^10.0.1", + "graceful-fs": "^4.2.2", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.1", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "slash": "^3.0.0" }, "dependencies": { "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -1993,10 +2019,16 @@ "path-is-absolute": "^1.0.0" } }, + "graceful-fs": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "dev": true + }, "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz", + "integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==", "dev": true, "requires": { "glob": "^7.1.3" @@ -2151,9 +2183,9 @@ } }, "earcut": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.1.5.tgz", - "integrity": "sha512-QFWC7ywTVLtvRAJTVp8ugsuuGQ5mVqNmJ1cRYeLrSHgP3nycr2RHTJob9OtM0v8ujuoKN0NY1a93J/omeTL1PA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.1.tgz", + "integrity": "sha512-5jIMi2RB3HtGPHcYd9Yyl0cczo84y+48lgKPxMijliNQaKAHEZJbdzLmKmdxG/mCdS/YD9DQ1gihL8mxzR0F9w==", "dev": true }, "ecc-jsbn": { @@ -2209,23 +2241,27 @@ } }, "es-abstract": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", - "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.0.tgz", + "integrity": "sha512-xdQnfykZ9JMEiasTAJZJdMWCQ1Vm00NBw79/AWi7ELfZuuPCSOMDZbT9mkOfSctVtfhb+sAAzrm+j//GjjLHLg==", "dev": true, "requires": { "es-to-primitive": "^1.2.0", "function-bind": "^1.1.1", "has": "^1.0.3", + "has-symbols": "^1.0.0", "is-callable": "^1.1.4", "is-regex": "^1.0.4", - "object-keys": "^1.0.12" + "object-inspect": "^1.6.0", + "object-keys": "^1.1.1", + "string.prototype.trimleft": "^2.1.0", + "string.prototype.trimright": "^2.1.0" } }, "es-to-primitive": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", - "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, "requires": { "is-callable": "^1.1.4", @@ -2424,12 +2460,6 @@ } } }, - "esm": { - "version": "3.0.84", - "resolved": "https://registry.npmjs.org/esm/-/esm-3.0.84.tgz", - "integrity": "sha512-SzSGoZc17S7P+12R9cg21Bdb7eybX25RnIeRZ80xZs+VZ3kdQKzqTp2k4hZJjR7p9l0186TTXSgrxzlMDBktlw==", - "dev": true - }, "espree": { "version": "3.5.4", "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", @@ -2533,6 +2563,23 @@ "homedir-polyfill": "^1.0.1" } }, + "ext": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.2.0.tgz", + "integrity": "sha512-0ccUQK/9e3NreLFg6K6np8aPyRgwycx+oFGtfx1dSp7Wj00Ozw9r05FgBRlzjf2XBM7LAzwgLyDscRrtSU91hA==", + "dev": true, + "requires": { + "type": "^2.0.0" + }, + "dependencies": { + "type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz", + "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==", + "dev": true + } + } + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -2676,16 +2723,15 @@ "dev": true }, "fast-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.0.4.tgz", - "integrity": "sha512-wkIbV6qg37xTJwqSsdnIphL1e+LaGz4AIQqr00mIubMaEhv1/HEmJ0uuCGZRNRUkZZmOB5mJKO0ZUTVq+SxMQg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.1.0.tgz", + "integrity": "sha512-TrUz3THiq2Vy3bjfQUB2wNyPdGBeGmdjbzzBLhfHN4YFurYptCKwGq/TfiRavbGywFRzY6U2CdmQ1zmsY5yYaw==", "dev": true, "requires": { - "@nodelib/fs.stat": "^2.0.1", - "@nodelib/fs.walk": "^1.2.1", - "glob-parent": "^5.0.0", - "is-glob": "^4.0.1", - "merge2": "^1.2.3", + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", "micromatch": "^4.0.2" }, "dependencies": { @@ -2708,9 +2754,9 @@ } }, "glob-parent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz", - "integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", "dev": true, "requires": { "is-glob": "^4.0.1" @@ -3731,16 +3777,23 @@ } }, "gl-cone3d": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/gl-cone3d/-/gl-cone3d-1.3.1.tgz", - "integrity": "sha512-ftw75smsDy5nU94susUNimXo8H40BEOVjaFrjT387vP4fJqkSVpzVK7jGrPA8/nSrFCOIQ0msWNYL9MMqQ3hjg==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/gl-cone3d/-/gl-cone3d-1.5.1.tgz", + "integrity": "sha512-R8m2lPfVN5ip/IPzykvMNgUUGWTkp9rMuCrVknKIkhjH+gaQeGfwF3+WrB0kwq3FRWvlYWcfdvabv37sZ2rKYA==", "dev": true, "requires": { + "colormap": "^2.3.1", + "gl-buffer": "^2.1.2", + "gl-mat4": "^1.2.0", "gl-shader": "^4.2.1", + "gl-texture2d": "^2.1.0", + "gl-vao": "^1.3.0", "gl-vec3": "^1.1.3", "glsl-inverse": "^1.0.0", "glsl-out-of-range": "^1.0.4", - "glslify": "^7.0.0" + "glsl-specular-cook-torrance": "^2.0.1", + "glslify": "^7.0.0", + "ndarray": "^1.0.18" } }, "gl-constants": { @@ -3905,9 +3958,9 @@ } }, "gl-plot3d": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/gl-plot3d/-/gl-plot3d-2.2.2.tgz", - "integrity": "sha512-is8RoDVUEbUM7kJ2qjhKJlfGLECH3ML9pTCW1V7ylUdmUACmcZ4lzJrQr/NIRkHC5WcUNOp3QJKPjBND3ngZ2A==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/gl-plot3d/-/gl-plot3d-2.3.0.tgz", + "integrity": "sha512-qg269QiLpaw16d2D5Gz9fa8vsLcA8kbX/cv1u9S7BsH6jD9qGYxsY8iWJ8ea9/68WhPS5En2kUavkXINkmHsOQ==", "dev": true, "requires": { "3d-view": "^2.0.0", @@ -4030,14 +4083,17 @@ } }, "gl-streamtube3d": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/gl-streamtube3d/-/gl-streamtube3d-1.2.1.tgz", - "integrity": "sha512-1aj1noU+jJirl5IwFXk29eDx1nO7PQk4r0UZK3My56J3vDSfRR+IbMq2YBhBkjfCWsKY1nc9ESD8t9EcqZY91w==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/gl-streamtube3d/-/gl-streamtube3d-1.4.0.tgz", + "integrity": "sha512-WgRtdB77uFCN1lBZ6ogz7VTK4J8WwW5DGHvyB3LaBSZF3t5lf/KWeXPgm+xnNINlOy4JqJIgny+CtzwTHAk3Ew==", "dev": true, "requires": { - "gl-vec3": "^1.0.0", + "gl-cone3d": "^1.5.0", + "gl-vec3": "^1.1.3", + "gl-vec4": "^1.0.1", "glsl-inverse": "^1.0.0", "glsl-out-of-range": "^1.0.4", + "glsl-specular-cook-torrance": "^2.0.1", "glslify": "^7.0.0" } }, @@ -4094,14 +4150,36 @@ }, "dependencies": { "es5-ext": { - "version": "0.10.50", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.50.tgz", - "integrity": "sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==", + "version": "0.10.52", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.52.tgz", + "integrity": "sha512-bWCbE9fbpYQY4CU6hJbJ1vSz70EClMlDgJ7BmwI+zEJhxrwjesZRPglGJlsZhu0334U3hI+gaspwksH9IGD6ag==", "dev": true, "requires": { "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.1", - "next-tick": "^1.0.0" + "es6-symbol": "~3.1.2", + "next-tick": "~1.0.0" + }, + "dependencies": { + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dev": true, + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + } } }, "es6-weak-map": { @@ -4276,9 +4354,9 @@ }, "dependencies": { "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -4290,9 +4368,9 @@ } }, "ignore": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.2.tgz", - "integrity": "sha512-vdqWBp7MyzdmHkkRWV5nY+PfGRbYbahfuvsBCh277tq+w9zyNi7h5CYJCK0kmzti9kU+O/cB7sE8HvKv6aXAKQ==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", + "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", "dev": true } } @@ -5037,8 +5115,7 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { "version": "1.3.5", @@ -5384,23 +5461,11 @@ "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", "dev": true }, - "is-path-in-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", - "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", - "dev": true, - "requires": { - "is-path-inside": "^2.1.0" - } - }, "is-path-inside": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", - "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", - "dev": true, - "requires": { - "path-is-inside": "^1.0.2" - } + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", + "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==", + "dev": true }, "is-plain-obj": { "version": "1.1.0", @@ -5504,8 +5569,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", @@ -5522,8 +5586,7 @@ "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "jquery": { "version": "3.4.1", @@ -5794,9 +5857,9 @@ } }, "magic-string": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.3.tgz", - "integrity": "sha512-6QK0OpF/phMz0Q2AxILkX2mFhi7m+WMwTRg0LQKq/WBB0cDP4rYH3Wp4/d3OTXlrPLVJT/RFqj8tFeAR4nk8AA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.4.tgz", + "integrity": "sha512-oycWO9nEVAP2RVPbIoDoA4Y7LFIJ3xRYov93gAyJhZkET1tNuB0u7uWkZS2LpBWTJUWnmau/To8ECWRC+jKNfw==", "dev": true, "requires": { "sourcemap-codec": "^1.4.4" @@ -5868,9 +5931,9 @@ } }, "mapbox-gl": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-1.1.1.tgz", - "integrity": "sha512-i57kASg8J/U/lJzBePyqTP2ImKUcx8FkHyCjb3ssWYaBBXHUeZ4STGXXfU9u1AQU9170PjDIJLubUUB1vLLSBQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-1.3.2.tgz", + "integrity": "sha512-6Ro7GbTMWxcbc836m6rbBNkesgTncbE1yXWeuHlr89esSqaItKr0+ntOu8rZie3fv+GtitkbODysXzIGCA7G+w==", "dev": true, "requires": { "@mapbox/geojson-rewind": "^0.4.0", @@ -5884,7 +5947,6 @@ "@mapbox/whoots-js": "^3.1.0", "csscolorparser": "~1.0.2", "earcut": "^2.1.5", - "esm": "~3.0.84", "geojson-vt": "^3.2.1", "gl-matrix": "^3.0.0", "grid-index": "^1.1.0", @@ -6040,9 +6102,9 @@ "dev": true }, "merge2": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.3.tgz", - "integrity": "sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz", + "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==", "dev": true }, "micromatch": { @@ -6742,10 +6804,13 @@ } }, "p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "dev": true + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } }, "pad-left": { "version": "1.0.2", @@ -6881,9 +6946,9 @@ } }, "pbf": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.0.tgz", - "integrity": "sha512-98Eh7rsJNJF/Im6XYMLaOW3cLnNyedlOd6hu3iWMD5I7FZGgpw8yN3vQBrmLbLodu7G784Irb9Qsv2yFrxSAGw==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz", + "integrity": "sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==", "dev": true, "requires": { "ieee754": "^1.1.12", @@ -6922,9 +6987,9 @@ "dev": true }, "picomatch": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz", - "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.1.1.tgz", + "integrity": "sha512-OYMyqkKzK7blWO/+XZYP6w8hH0LDvkBvdvKukti+7kqYFCiEAk+gI3DWnryapc0Dau05ugGTy0foQ6mqn4AHYA==", "dev": true }, "pify": { @@ -6974,37 +7039,18 @@ } }, "plexer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/plexer/-/plexer-1.0.2.tgz", - "integrity": "sha512-YvKSr441x/P9tNuNHUyDWGUSnzsCNXox425j14/3+YcwMyVnJs11qiz/ZWti/y+UNhCFu4SkDSelHYY5hXpifQ==", - "dev": true, + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/plexer/-/plexer-1.0.3.tgz", + "integrity": "sha512-U8iVMgA9Asku2az1HCoYUywL54hypBQ0d2svuDb3aO8MsuvhQKz/I3db2WtZcRQWe42N/3E8Pru/F/WvzIh6UA==", "requires": { - "debug": "2.6.1", "isstream": "^0.1.2", "readable-stream": "^2.0.2" - }, - "dependencies": { - "debug": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.1.tgz", - "integrity": "sha1-eYVQkLosTjEVzH2HaUkdWPBJE1E=", - "dev": true, - "requires": { - "ms": "0.7.2" - } - }, - "ms": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", - "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", - "dev": true - } } }, "plotly.js": { - "version": "1.49.4", - "resolved": "https://registry.npmjs.org/plotly.js/-/plotly.js-1.49.4.tgz", - "integrity": "sha512-yOcA1GKLY6vsGLWYoGa/jWTPXcHPTyTWwkgAhopNtCgxTVexXzKfnnGQ2SslL//+TkpNX8CpCzC88HQVV4p42Q==", + "version": "1.51.1", + "resolved": "https://registry.npmjs.org/plotly.js/-/plotly.js-1.51.1.tgz", + "integrity": "sha512-EOAP6hne7e8RyH2fo0dyTuRKiAO04TVYECK65EMrK8jnP5AqSbHIypnroNCm/nfYj3sn5DTl+GX5XsJKpyM4Aw==", "dev": true, "requires": { "@plotly/d3-sankey": "0.7.2", @@ -7014,6 +7060,7 @@ "alpha-shape": "^1.0.0", "canvas-fit": "^1.5.0", "color-normalize": "^1.5.0", + "color-rgba": "^2.1.1", "convex-hull": "^1.0.3", "country-regex": "^1.1.0", "d3": "^3.5.12", @@ -7023,7 +7070,7 @@ "delaunay-triangulate": "^1.1.6", "es6-promise": "^3.0.2", "fast-isnumeric": "^1.1.3", - "gl-cone3d": "^1.3.1", + "gl-cone3d": "^1.5.1", "gl-contour2d": "^1.1.6", "gl-error3d": "^1.0.15", "gl-heatmap2d": "^1.0.5", @@ -7031,18 +7078,18 @@ "gl-mat4": "^1.2.0", "gl-mesh3d": "^2.1.1", "gl-plot2d": "^1.4.2", - "gl-plot3d": "^2.2.2", + "gl-plot3d": "^2.3.0", "gl-pointcloud2d": "^1.0.2", "gl-scatter3d": "^1.2.2", "gl-select-box": "^1.0.3", "gl-spikes2d": "^1.0.2", - "gl-streamtube3d": "^1.2.1", + "gl-streamtube3d": "^1.4.0", "gl-surface3d": "^1.4.6", "gl-text": "^1.1.8", "glslify": "^7.0.0", "has-hover": "^1.0.1", "has-passive-events": "^1.0.0", - "mapbox-gl": "1.1.1", + "mapbox-gl": "1.3.2", "matrix-camera-controller": "^2.1.3", "mouse-change": "^1.4.0", "mouse-event-offset": "^3.0.2", @@ -7050,13 +7097,13 @@ "ndarray": "^1.0.18", "ndarray-fill": "^1.0.2", "ndarray-homography": "^1.0.0", - "point-cluster": "^3.1.4", + "point-cluster": "^3.1.8", "polybooljs": "^1.2.0", "regl": "^1.3.11", "regl-error2d": "^2.0.8", - "regl-line2d": "^3.0.14", - "regl-scatter2d": "^3.1.5", - "regl-splom": "^1.0.7", + "regl-line2d": "^3.0.15", + "regl-scatter2d": "^3.1.7", + "regl-splom": "^1.0.8", "right-now": "^1.0.0", "robust-orientation": "^1.1.3", "sane-topojson": "^4.0.0", @@ -7128,21 +7175,23 @@ "dev": true }, "point-cluster": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/point-cluster/-/point-cluster-3.1.5.tgz", - "integrity": "sha512-KpVtB1mXDlo6yzv80MA6oUq+1519CMeeUd4PPluM4ZlAQgHi/qeBrLY2G53RLy41kas7XvKol0FM98MSrjNH7Q==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/point-cluster/-/point-cluster-3.1.8.tgz", + "integrity": "sha512-7klIr45dpMeZuqjIK9+qBg3m2IhyZJNJkdqjJFw0Olq75FM8ojrTMjClVUrMjNYRVqtwztxCHH71Fyjhg+YwyQ==", "dev": true, "requires": { "array-bounds": "^1.0.1", - "array-normalize": "^1.1.3", + "array-normalize": "^1.1.4", "binary-search-bounds": "^2.0.4", "bubleify": "^1.1.0", "clamp": "^1.0.1", + "defined": "^1.0.0", "dtype": "^2.0.0", - "flatten-vertex-data": "^1.0.0", + "flatten-vertex-data": "^1.0.2", "is-obj": "^1.0.1", "math-log2": "^1.0.1", - "parse-rect": "^1.2.0" + "parse-rect": "^1.2.0", + "pick-by-alias": "^1.2.0" } }, "point-in-big-polygon": { @@ -7181,9 +7230,9 @@ } }, "popper.js": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.15.0.tgz", - "integrity": "sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.0.tgz", + "integrity": "sha512-+G+EkOPoE5S/zChTpmBSSDYmhXJ5PsW8eMhH8cP/CQHMFPBG/kC9Y5IIw6qNYgdJ+/COf0ddY2li28iHaZRSjw==", "dev": true }, "posix-character-classes": { @@ -7294,9 +7343,9 @@ }, "dependencies": { "is-buffer": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", - "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", "dev": true } } @@ -7438,7 +7487,6 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -7452,8 +7500,7 @@ "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" } } }, @@ -7558,9 +7605,9 @@ "dev": true }, "regexpu-core": { - "version": "4.5.5", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.5.5.tgz", - "integrity": "sha512-FpI67+ky9J+cDizQUJlIlNZFKual/lUkFr1AG6zOCpwZ9cLrg8UUVakyUQJD7fCDIe9Z2nwTQJNPyonatNmDFQ==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.6.0.tgz", + "integrity": "sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==", "dev": true, "requires": { "regenerate": "^1.4.0", @@ -7572,9 +7619,9 @@ } }, "regjsgen": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz", - "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", + "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==", "dev": true }, "regjsparser": { @@ -7587,9 +7634,9 @@ } }, "regl": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/regl/-/regl-1.3.11.tgz", - "integrity": "sha512-tmt6CRhRqbcsYDWNwv+iG7GGOXdgoOBC7lKzoPMgnzpt3WKBQ3c8i7AxgbvTRZzty29hrW92fAJeZkPFQehfWA==", + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/regl/-/regl-1.3.13.tgz", + "integrity": "sha512-TTiCabJbbUykCL4otjqOvKqDFJhvJOT7xB51JxcDeSHGrEJl1zz4RthPcoOogqfuR3ECN4Te790DfHCXzli5WQ==", "dev": true }, "regl-error2d": { @@ -7609,13 +7656,13 @@ } }, "regl-line2d": { - "version": "3.0.14", - "resolved": "https://registry.npmjs.org/regl-line2d/-/regl-line2d-3.0.14.tgz", - "integrity": "sha512-F5Ru1Bugi6Xkk2JJ4EuzAybuL99CtnAr6VIrJVJdsaFzWmI9GfPFtwbNZROeOrXXX7yElyc0HQsQDJaNpSeWmg==", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/regl-line2d/-/regl-line2d-3.0.15.tgz", + "integrity": "sha512-RuQbg9iZ6MyuInG8izF6zjQ/2g4qL6sg1egiuFalWzaGSvuve/IWBsIcqKTlwpiEsRt9b4cHu9NYs2fLt1gYJw==", "dev": true, "requires": { "array-bounds": "^1.0.1", - "array-normalize": "^1.1.3", + "array-normalize": "^1.1.4", "bubleify": "^1.2.0", "color-normalize": "^1.5.0", "earcut": "^2.1.5", @@ -7629,14 +7676,36 @@ }, "dependencies": { "es5-ext": { - "version": "0.10.50", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.50.tgz", - "integrity": "sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==", + "version": "0.10.52", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.52.tgz", + "integrity": "sha512-bWCbE9fbpYQY4CU6hJbJ1vSz70EClMlDgJ7BmwI+zEJhxrwjesZRPglGJlsZhu0334U3hI+gaspwksH9IGD6ag==", "dev": true, "requires": { "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.1", - "next-tick": "^1.0.0" + "es6-symbol": "~3.1.2", + "next-tick": "~1.0.0" + }, + "dependencies": { + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dev": true, + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + } } }, "es6-weak-map": { @@ -7654,9 +7723,9 @@ } }, "regl-scatter2d": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/regl-scatter2d/-/regl-scatter2d-3.1.5.tgz", - "integrity": "sha512-VCmASgrNIQXzDxmTLpLA4MAlbi+kdjKcVR9XugmFCTnWY7zytIhuMyIoPxinpaejGXzsC0Lq5oKvOnWFMQFGng==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/regl-scatter2d/-/regl-scatter2d-3.1.7.tgz", + "integrity": "sha512-FWw1hMsQrV3Y0zMU8YOytGjwSBuV3V58t8GR/mhlSL2S04jXLK1m2eAa/rDP3SpvMDkdVEr744PPDeHwsZVUhA==", "dev": true, "requires": { "array-range": "^1.0.1", @@ -7672,15 +7741,15 @@ "object-assign": "^4.1.1", "parse-rect": "^1.2.0", "pick-by-alias": "^1.2.0", - "point-cluster": "^3.1.5", + "point-cluster": "^3.1.8", "to-float32": "^1.0.1", "update-diff": "^1.1.0" } }, "regl-splom": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/regl-splom/-/regl-splom-1.0.7.tgz", - "integrity": "sha512-17ltp68/pCMFOU2gSIIFRTRMmsCRpDFzPUF9jkZhT8IDimI83jkU/2nP4vAHTIfd+HZ/Ip+eFrNx2aKV9FMDwQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/regl-splom/-/regl-splom-1.0.8.tgz", + "integrity": "sha512-4GQTgcArCbGLsXhgalWVBxeW7OXllnu+Gvil/4SbQQmtiqLCl+xgF79pISKY9mLXTlobxiX7cVKdjGjp25559A==", "dev": true, "requires": { "array-bounds": "^1.0.1", @@ -7692,7 +7761,7 @@ "left-pad": "^1.3.0", "parse-rect": "^1.2.0", "pick-by-alias": "^1.2.0", - "point-cluster": "^3.1.4", + "point-cluster": "^3.1.8", "raf": "^3.4.1", "regl-scatter2d": "^3.1.2" } @@ -8048,8 +8117,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -8794,11 +8862,30 @@ "function-bind": "^1.0.2" } }, + "string.prototype.trimleft": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", + "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", + "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -9025,9 +9112,9 @@ }, "dependencies": { "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -9360,6 +9447,12 @@ "integrity": "sha1-MdPzIjnk9zHsqd+RVeKyl/AIq2Q=", "dev": true }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "dev": true + }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -9619,8 +9712,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "utils-copy": { "version": "1.1.1", diff --git a/package.json b/package.json index 513f1b85..4a0b6bfa 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "devDependencies": { "bootstrap": "4.3.1", - "del": "^5.0.0", + "del": "^5.1.0", "font-awesome": "^4.7.0", "gulp": "^4.0.2", "gulp-concat": "^2.6.1", @@ -21,8 +21,8 @@ "gulp-uglify": "^3.0.2", "jquery": "^3.4.1", "moment": "^2.24.0", - "plotly.js": "^1.49.4", - "popper.js": "^1.15.0", + "plotly.js": "^1.51.1", + "popper.js": "^1.16.0", "pump": "^3.0.0", "tempusdominus-bootstrap-4": "^5.1.2", "tempusdominus-core": "^5.0.3" diff --git a/static/babybuddy/js/graph.a7dcc3322745.js b/static/babybuddy/js/graph.a7dcc3322745.js new file mode 100644 index 00000000..891482bb --- /dev/null +++ b/static/babybuddy/js/graph.a7dcc3322745.js @@ -0,0 +1,85457 @@ +/** +* plotly.js (cartesian) v1.51.1 +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* Licensed under the MIT license +*/ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Plotly = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i:not(.watermark)": "opacity:0;-webkit-transition:opacity 0.3s ease 0s;-moz-transition:opacity 0.3s ease 0s;-ms-transition:opacity 0.3s ease 0s;-o-transition:opacity 0.3s ease 0s;transition:opacity 0.3s ease 0s;", + "X:hover .modebar--hover .modebar-group": "opacity:1;", + "X .modebar-group": "float:left;display:inline-block;box-sizing:border-box;padding-left:8px;position:relative;vertical-align:middle;white-space:nowrap;", + "X .modebar-btn": "position:relative;font-size:16px;padding:3px 4px;height:22px;cursor:pointer;line-height:normal;box-sizing:border-box;", + "X .modebar-btn svg": "position:relative;top:2px;", + "X .modebar.vertical": "display:flex;flex-direction:column;flex-wrap:wrap;align-content:flex-end;max-height:100%;", + "X .modebar.vertical svg": "top:-1px;", + "X .modebar.vertical .modebar-group": "display:block;float:none;padding-left:0px;padding-bottom:8px;", + "X .modebar.vertical .modebar-group .modebar-btn": "display:block;text-align:center;", + "X [data-title]:before,X [data-title]:after": "position:absolute;-webkit-transform:translate3d(0, 0, 0);-moz-transform:translate3d(0, 0, 0);-ms-transform:translate3d(0, 0, 0);-o-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);display:none;opacity:0;z-index:1001;pointer-events:none;top:110%;right:50%;", + "X [data-title]:hover:before,X [data-title]:hover:after": "display:block;opacity:1;", + "X [data-title]:before": "content:'';position:absolute;background:transparent;border:6px solid transparent;z-index:1002;margin-top:-12px;border-bottom-color:#69738a;margin-right:-6px;", + "X [data-title]:after": "content:attr(data-title);background:#69738a;color:white;padding:8px 10px;font-size:12px;line-height:12px;white-space:nowrap;margin-right:-18px;border-radius:2px;", + "X .vertical [data-title]:before,X .vertical [data-title]:after": "top:0%;right:200%;", + "X .vertical [data-title]:before": "border:6px solid transparent;border-left-color:#69738a;margin-top:8px;margin-right:-30px;", + "X .select-outline": "fill:none;stroke-width:1;shape-rendering:crispEdges;", + "X .select-outline-1": "stroke:white;", + "X .select-outline-2": "stroke:black;stroke-dasharray:2px 2px;", + Y: "font-family:'Open Sans';position:fixed;top:50px;right:20px;z-index:10000;font-size:10pt;max-width:180px;", + "Y p": "margin:0;", + "Y .notifier-note": "min-width:180px;max-width:250px;border:1px solid #fff;z-index:3000;margin:0;background-color:#8c97af;background-color:rgba(140,151,175,0.9);color:#fff;padding:10px;overflow-wrap:break-word;word-wrap:break-word;-ms-hyphens:auto;-webkit-hyphens:auto;hyphens:auto;", + "Y .notifier-close": "color:#fff;opacity:0.8;float:right;padding:0 5px;background:none;border:none;font-size:20px;font-weight:bold;line-height:20px;", + "Y .notifier-close:hover": "color:#444;text-decoration:none;cursor:pointer;" +}; + +for(var selector in rules) { + var fullSelector = selector.replace(/^,/,' ,') + .replace(/X/g, '.js-plotly-plot .plotly') + .replace(/Y/g, '.plotly-notifier'); + Lib.addStyleRule(fullSelector, rules[selector]); +} + +},{"../src/lib":169}],2:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = _dereq_('../src/traces/bar'); + +},{"../src/traces/bar":275}],3:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = _dereq_('../src/traces/box'); + +},{"../src/traces/box":289}],4:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = _dereq_('../src/traces/contour'); + +},{"../src/traces/contour":309}],5:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = _dereq_('../src/core'); + +},{"../src/core":151}],6:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = _dereq_('../src/traces/heatmap'); + +},{"../src/traces/heatmap":325}],7:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = _dereq_('../src/traces/histogram'); + +},{"../src/traces/histogram":343}],8:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = _dereq_('../src/traces/histogram2d'); + +},{"../src/traces/histogram2d":349}],9:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = _dereq_('../src/traces/histogram2dcontour'); + +},{"../src/traces/histogram2dcontour":353}],10:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = _dereq_('../src/traces/image'); + +},{"../src/traces/image":360}],11:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Plotly = _dereq_('./core'); + +Plotly.register([ + _dereq_('./bar'), + _dereq_('./box'), + _dereq_('./heatmap'), + _dereq_('./histogram'), + _dereq_('./histogram2d'), + _dereq_('./histogram2dcontour'), + _dereq_('./image'), + _dereq_('./pie'), + _dereq_('./contour'), + _dereq_('./scatterternary'), + _dereq_('./violin') +]); + +module.exports = Plotly; + +},{"./bar":2,"./box":3,"./contour":4,"./core":5,"./heatmap":6,"./histogram":7,"./histogram2d":8,"./histogram2dcontour":9,"./image":10,"./pie":12,"./scatterternary":13,"./violin":14}],12:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = _dereq_('../src/traces/pie'); + +},{"../src/traces/pie":369}],13:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = _dereq_('../src/traces/scatterternary'); + +},{"../src/traces/scatterternary":407}],14:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = _dereq_('../src/traces/violin'); + +},{"../src/traces/violin":415}],15:[function(_dereq_,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var objectCreate = Object.create || objectCreatePolyfill +var objectKeys = Object.keys || objectKeysPolyfill +var bind = Function.prototype.bind || functionBindPolyfill + +function EventEmitter() { + if (!this._events || !Object.prototype.hasOwnProperty.call(this, '_events')) { + this._events = objectCreate(null); + this._eventsCount = 0; + } + + this._maxListeners = this._maxListeners || undefined; +} +module.exports = EventEmitter; + +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +var defaultMaxListeners = 10; + +var hasDefineProperty; +try { + var o = {}; + if (Object.defineProperty) Object.defineProperty(o, 'x', { value: 0 }); + hasDefineProperty = o.x === 0; +} catch (err) { hasDefineProperty = false } +if (hasDefineProperty) { + Object.defineProperty(EventEmitter, 'defaultMaxListeners', { + enumerable: true, + get: function() { + return defaultMaxListeners; + }, + set: function(arg) { + // check whether the input is a positive number (whose value is zero or + // greater and not a NaN). + if (typeof arg !== 'number' || arg < 0 || arg !== arg) + throw new TypeError('"defaultMaxListeners" must be a positive number'); + defaultMaxListeners = arg; + } + }); +} else { + EventEmitter.defaultMaxListeners = defaultMaxListeners; +} + +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) { + if (typeof n !== 'number' || n < 0 || isNaN(n)) + throw new TypeError('"n" argument must be a positive number'); + this._maxListeners = n; + return this; +}; + +function $getMaxListeners(that) { + if (that._maxListeners === undefined) + return EventEmitter.defaultMaxListeners; + return that._maxListeners; +} + +EventEmitter.prototype.getMaxListeners = function getMaxListeners() { + return $getMaxListeners(this); +}; + +// These standalone emit* functions are used to optimize calling of event +// handlers for fast cases because emit() itself often has a variable number of +// arguments and can be deoptimized because of that. These functions always have +// the same number of arguments and thus do not get deoptimized, so the code +// inside them can execute faster. +function emitNone(handler, isFn, self) { + if (isFn) + handler.call(self); + else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + listeners[i].call(self); + } +} +function emitOne(handler, isFn, self, arg1) { + if (isFn) + handler.call(self, arg1); + else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + listeners[i].call(self, arg1); + } +} +function emitTwo(handler, isFn, self, arg1, arg2) { + if (isFn) + handler.call(self, arg1, arg2); + else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + listeners[i].call(self, arg1, arg2); + } +} +function emitThree(handler, isFn, self, arg1, arg2, arg3) { + if (isFn) + handler.call(self, arg1, arg2, arg3); + else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + listeners[i].call(self, arg1, arg2, arg3); + } +} + +function emitMany(handler, isFn, self, args) { + if (isFn) + handler.apply(self, args); + else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + listeners[i].apply(self, args); + } +} + +EventEmitter.prototype.emit = function emit(type) { + var er, handler, len, args, i, events; + var doError = (type === 'error'); + + events = this._events; + if (events) + doError = (doError && events.error == null); + else if (!doError) + return false; + + // If there is no 'error' event listener then throw. + if (doError) { + if (arguments.length > 1) + er = arguments[1]; + if (er instanceof Error) { + throw er; // Unhandled 'error' event + } else { + // At least give some kind of context to the user + var err = new Error('Unhandled "error" event. (' + er + ')'); + err.context = er; + throw err; + } + return false; + } + + handler = events[type]; + + if (!handler) + return false; + + var isFn = typeof handler === 'function'; + len = arguments.length; + switch (len) { + // fast cases + case 1: + emitNone(handler, isFn, this); + break; + case 2: + emitOne(handler, isFn, this, arguments[1]); + break; + case 3: + emitTwo(handler, isFn, this, arguments[1], arguments[2]); + break; + case 4: + emitThree(handler, isFn, this, arguments[1], arguments[2], arguments[3]); + break; + // slower + default: + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + emitMany(handler, isFn, this, args); + } + + return true; +}; + +function _addListener(target, type, listener, prepend) { + var m; + var events; + var existing; + + if (typeof listener !== 'function') + throw new TypeError('"listener" argument must be a function'); + + events = target._events; + if (!events) { + events = target._events = objectCreate(null); + target._eventsCount = 0; + } else { + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (events.newListener) { + target.emit('newListener', type, + listener.listener ? listener.listener : listener); + + // Re-assign `events` because a newListener handler could have caused the + // this._events to be assigned to a new object + events = target._events; + } + existing = events[type]; + } + + if (!existing) { + // Optimize the case of one listener. Don't need the extra array object. + existing = events[type] = listener; + ++target._eventsCount; + } else { + if (typeof existing === 'function') { + // Adding the second element, need to change to array. + existing = events[type] = + prepend ? [listener, existing] : [existing, listener]; + } else { + // If we've already got an array, just append. + if (prepend) { + existing.unshift(listener); + } else { + existing.push(listener); + } + } + + // Check for listener leak + if (!existing.warned) { + m = $getMaxListeners(target); + if (m && m > 0 && existing.length > m) { + existing.warned = true; + var w = new Error('Possible EventEmitter memory leak detected. ' + + existing.length + ' "' + String(type) + '" listeners ' + + 'added. Use emitter.setMaxListeners() to ' + + 'increase limit.'); + w.name = 'MaxListenersExceededWarning'; + w.emitter = target; + w.type = type; + w.count = existing.length; + if (typeof console === 'object' && console.warn) { + console.warn('%s: %s', w.name, w.message); + } + } + } + } + + return target; +} + +EventEmitter.prototype.addListener = function addListener(type, listener) { + return _addListener(this, type, listener, false); +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.prependListener = + function prependListener(type, listener) { + return _addListener(this, type, listener, true); + }; + +function onceWrapper() { + if (!this.fired) { + this.target.removeListener(this.type, this.wrapFn); + this.fired = true; + switch (arguments.length) { + case 0: + return this.listener.call(this.target); + case 1: + return this.listener.call(this.target, arguments[0]); + case 2: + return this.listener.call(this.target, arguments[0], arguments[1]); + case 3: + return this.listener.call(this.target, arguments[0], arguments[1], + arguments[2]); + default: + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) + args[i] = arguments[i]; + this.listener.apply(this.target, args); + } + } +} + +function _onceWrap(target, type, listener) { + var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener }; + var wrapped = bind.call(onceWrapper, state); + wrapped.listener = listener; + state.wrapFn = wrapped; + return wrapped; +} + +EventEmitter.prototype.once = function once(type, listener) { + if (typeof listener !== 'function') + throw new TypeError('"listener" argument must be a function'); + this.on(type, _onceWrap(this, type, listener)); + return this; +}; + +EventEmitter.prototype.prependOnceListener = + function prependOnceListener(type, listener) { + if (typeof listener !== 'function') + throw new TypeError('"listener" argument must be a function'); + this.prependListener(type, _onceWrap(this, type, listener)); + return this; + }; + +// Emits a 'removeListener' event if and only if the listener was removed. +EventEmitter.prototype.removeListener = + function removeListener(type, listener) { + var list, events, position, i, originalListener; + + if (typeof listener !== 'function') + throw new TypeError('"listener" argument must be a function'); + + events = this._events; + if (!events) + return this; + + list = events[type]; + if (!list) + return this; + + if (list === listener || list.listener === listener) { + if (--this._eventsCount === 0) + this._events = objectCreate(null); + else { + delete events[type]; + if (events.removeListener) + this.emit('removeListener', type, list.listener || listener); + } + } else if (typeof list !== 'function') { + position = -1; + + for (i = list.length - 1; i >= 0; i--) { + if (list[i] === listener || list[i].listener === listener) { + originalListener = list[i].listener; + position = i; + break; + } + } + + if (position < 0) + return this; + + if (position === 0) + list.shift(); + else + spliceOne(list, position); + + if (list.length === 1) + events[type] = list[0]; + + if (events.removeListener) + this.emit('removeListener', type, originalListener || listener); + } + + return this; + }; + +EventEmitter.prototype.removeAllListeners = + function removeAllListeners(type) { + var listeners, events, i; + + events = this._events; + if (!events) + return this; + + // not listening for removeListener, no need to emit + if (!events.removeListener) { + if (arguments.length === 0) { + this._events = objectCreate(null); + this._eventsCount = 0; + } else if (events[type]) { + if (--this._eventsCount === 0) + this._events = objectCreate(null); + else + delete events[type]; + } + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + var keys = objectKeys(events); + var key; + for (i = 0; i < keys.length; ++i) { + key = keys[i]; + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = objectCreate(null); + this._eventsCount = 0; + return this; + } + + listeners = events[type]; + + if (typeof listeners === 'function') { + this.removeListener(type, listeners); + } else if (listeners) { + // LIFO order + for (i = listeners.length - 1; i >= 0; i--) { + this.removeListener(type, listeners[i]); + } + } + + return this; + }; + +function _listeners(target, type, unwrap) { + var events = target._events; + + if (!events) + return []; + + var evlistener = events[type]; + if (!evlistener) + return []; + + if (typeof evlistener === 'function') + return unwrap ? [evlistener.listener || evlistener] : [evlistener]; + + return unwrap ? unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length); +} + +EventEmitter.prototype.listeners = function listeners(type) { + return _listeners(this, type, true); +}; + +EventEmitter.prototype.rawListeners = function rawListeners(type) { + return _listeners(this, type, false); +}; + +EventEmitter.listenerCount = function(emitter, type) { + if (typeof emitter.listenerCount === 'function') { + return emitter.listenerCount(type); + } else { + return listenerCount.call(emitter, type); + } +}; + +EventEmitter.prototype.listenerCount = listenerCount; +function listenerCount(type) { + var events = this._events; + + if (events) { + var evlistener = events[type]; + + if (typeof evlistener === 'function') { + return 1; + } else if (evlistener) { + return evlistener.length; + } + } + + return 0; +} + +EventEmitter.prototype.eventNames = function eventNames() { + return this._eventsCount > 0 ? Reflect.ownKeys(this._events) : []; +}; + +// About 1.5x faster than the two-arg version of Array#splice(). +function spliceOne(list, index) { + for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1) + list[i] = list[k]; + list.pop(); +} + +function arrayClone(arr, n) { + var copy = new Array(n); + for (var i = 0; i < n; ++i) + copy[i] = arr[i]; + return copy; +} + +function unwrapListeners(arr) { + var ret = new Array(arr.length); + for (var i = 0; i < ret.length; ++i) { + ret[i] = arr[i].listener || arr[i]; + } + return ret; +} + +function objectCreatePolyfill(proto) { + var F = function() {}; + F.prototype = proto; + return new F; +} +function objectKeysPolyfill(obj) { + var keys = []; + for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k)) { + keys.push(k); + } + return k; +} +function functionBindPolyfill(context) { + var fn = this; + return function () { + return fn.apply(context, arguments); + }; +} + +},{}],16:[function(_dereq_,module,exports){ +!function() { + var d3 = { + version: "3.5.17" + }; + var d3_arraySlice = [].slice, d3_array = function(list) { + return d3_arraySlice.call(list); + }; + var d3_document = this.document; + function d3_documentElement(node) { + return node && (node.ownerDocument || node.document || node).documentElement; + } + function d3_window(node) { + return node && (node.ownerDocument && node.ownerDocument.defaultView || node.document && node || node.defaultView); + } + if (d3_document) { + try { + d3_array(d3_document.documentElement.childNodes)[0].nodeType; + } catch (e) { + d3_array = function(list) { + var i = list.length, array = new Array(i); + while (i--) array[i] = list[i]; + return array; + }; + } + } + if (!Date.now) Date.now = function() { + return +new Date(); + }; + if (d3_document) { + try { + d3_document.createElement("DIV").style.setProperty("opacity", 0, ""); + } catch (error) { + var d3_element_prototype = this.Element.prototype, d3_element_setAttribute = d3_element_prototype.setAttribute, d3_element_setAttributeNS = d3_element_prototype.setAttributeNS, d3_style_prototype = this.CSSStyleDeclaration.prototype, d3_style_setProperty = d3_style_prototype.setProperty; + d3_element_prototype.setAttribute = function(name, value) { + d3_element_setAttribute.call(this, name, value + ""); + }; + d3_element_prototype.setAttributeNS = function(space, local, value) { + d3_element_setAttributeNS.call(this, space, local, value + ""); + }; + d3_style_prototype.setProperty = function(name, value, priority) { + d3_style_setProperty.call(this, name, value + "", priority); + }; + } + } + d3.ascending = d3_ascending; + function d3_ascending(a, b) { + return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; + } + d3.descending = function(a, b) { + return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN; + }; + d3.min = function(array, f) { + var i = -1, n = array.length, a, b; + if (arguments.length === 1) { + while (++i < n) if ((b = array[i]) != null && b >= b) { + a = b; + break; + } + while (++i < n) if ((b = array[i]) != null && a > b) a = b; + } else { + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) { + a = b; + break; + } + while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b; + } + return a; + }; + d3.max = function(array, f) { + var i = -1, n = array.length, a, b; + if (arguments.length === 1) { + while (++i < n) if ((b = array[i]) != null && b >= b) { + a = b; + break; + } + while (++i < n) if ((b = array[i]) != null && b > a) a = b; + } else { + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) { + a = b; + break; + } + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b; + } + return a; + }; + d3.extent = function(array, f) { + var i = -1, n = array.length, a, b, c; + if (arguments.length === 1) { + while (++i < n) if ((b = array[i]) != null && b >= b) { + a = c = b; + break; + } + while (++i < n) if ((b = array[i]) != null) { + if (a > b) a = b; + if (c < b) c = b; + } + } else { + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) { + a = c = b; + break; + } + while (++i < n) if ((b = f.call(array, array[i], i)) != null) { + if (a > b) a = b; + if (c < b) c = b; + } + } + return [ a, c ]; + }; + function d3_number(x) { + return x === null ? NaN : +x; + } + function d3_numeric(x) { + return !isNaN(x); + } + d3.sum = function(array, f) { + var s = 0, n = array.length, a, i = -1; + if (arguments.length === 1) { + while (++i < n) if (d3_numeric(a = +array[i])) s += a; + } else { + while (++i < n) if (d3_numeric(a = +f.call(array, array[i], i))) s += a; + } + return s; + }; + d3.mean = function(array, f) { + var s = 0, n = array.length, a, i = -1, j = n; + if (arguments.length === 1) { + while (++i < n) if (d3_numeric(a = d3_number(array[i]))) s += a; else --j; + } else { + while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) s += a; else --j; + } + if (j) return s / j; + }; + d3.quantile = function(values, p) { + var H = (values.length - 1) * p + 1, h = Math.floor(H), v = +values[h - 1], e = H - h; + return e ? v + e * (values[h] - v) : v; + }; + d3.median = function(array, f) { + var numbers = [], n = array.length, a, i = -1; + if (arguments.length === 1) { + while (++i < n) if (d3_numeric(a = d3_number(array[i]))) numbers.push(a); + } else { + while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) numbers.push(a); + } + if (numbers.length) return d3.quantile(numbers.sort(d3_ascending), .5); + }; + d3.variance = function(array, f) { + var n = array.length, m = 0, a, d, s = 0, i = -1, j = 0; + if (arguments.length === 1) { + while (++i < n) { + if (d3_numeric(a = d3_number(array[i]))) { + d = a - m; + m += d / ++j; + s += d * (a - m); + } + } + } else { + while (++i < n) { + if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) { + d = a - m; + m += d / ++j; + s += d * (a - m); + } + } + } + if (j > 1) return s / (j - 1); + }; + d3.deviation = function() { + var v = d3.variance.apply(this, arguments); + return v ? Math.sqrt(v) : v; + }; + function d3_bisector(compare) { + return { + left: function(a, x, lo, hi) { + if (arguments.length < 3) lo = 0; + if (arguments.length < 4) hi = a.length; + while (lo < hi) { + var mid = lo + hi >>> 1; + if (compare(a[mid], x) < 0) lo = mid + 1; else hi = mid; + } + return lo; + }, + right: function(a, x, lo, hi) { + if (arguments.length < 3) lo = 0; + if (arguments.length < 4) hi = a.length; + while (lo < hi) { + var mid = lo + hi >>> 1; + if (compare(a[mid], x) > 0) hi = mid; else lo = mid + 1; + } + return lo; + } + }; + } + var d3_bisect = d3_bisector(d3_ascending); + d3.bisectLeft = d3_bisect.left; + d3.bisect = d3.bisectRight = d3_bisect.right; + d3.bisector = function(f) { + return d3_bisector(f.length === 1 ? function(d, x) { + return d3_ascending(f(d), x); + } : f); + }; + d3.shuffle = function(array, i0, i1) { + if ((m = arguments.length) < 3) { + i1 = array.length; + if (m < 2) i0 = 0; + } + var m = i1 - i0, t, i; + while (m) { + i = Math.random() * m-- | 0; + t = array[m + i0], array[m + i0] = array[i + i0], array[i + i0] = t; + } + return array; + }; + d3.permute = function(array, indexes) { + var i = indexes.length, permutes = new Array(i); + while (i--) permutes[i] = array[indexes[i]]; + return permutes; + }; + d3.pairs = function(array) { + var i = 0, n = array.length - 1, p0, p1 = array[0], pairs = new Array(n < 0 ? 0 : n); + while (i < n) pairs[i] = [ p0 = p1, p1 = array[++i] ]; + return pairs; + }; + d3.transpose = function(matrix) { + if (!(n = matrix.length)) return []; + for (var i = -1, m = d3.min(matrix, d3_transposeLength), transpose = new Array(m); ++i < m; ) { + for (var j = -1, n, row = transpose[i] = new Array(n); ++j < n; ) { + row[j] = matrix[j][i]; + } + } + return transpose; + }; + function d3_transposeLength(d) { + return d.length; + } + d3.zip = function() { + return d3.transpose(arguments); + }; + d3.keys = function(map) { + var keys = []; + for (var key in map) keys.push(key); + return keys; + }; + d3.values = function(map) { + var values = []; + for (var key in map) values.push(map[key]); + return values; + }; + d3.entries = function(map) { + var entries = []; + for (var key in map) entries.push({ + key: key, + value: map[key] + }); + return entries; + }; + d3.merge = function(arrays) { + var n = arrays.length, m, i = -1, j = 0, merged, array; + while (++i < n) j += arrays[i].length; + merged = new Array(j); + while (--n >= 0) { + array = arrays[n]; + m = array.length; + while (--m >= 0) { + merged[--j] = array[m]; + } + } + return merged; + }; + var abs = Math.abs; + d3.range = function(start, stop, step) { + if (arguments.length < 3) { + step = 1; + if (arguments.length < 2) { + stop = start; + start = 0; + } + } + if ((stop - start) / step === Infinity) throw new Error("infinite range"); + var range = [], k = d3_range_integerScale(abs(step)), i = -1, j; + start *= k, stop *= k, step *= k; + if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k); else while ((j = start + step * ++i) < stop) range.push(j / k); + return range; + }; + function d3_range_integerScale(x) { + var k = 1; + while (x * k % 1) k *= 10; + return k; + } + function d3_class(ctor, properties) { + for (var key in properties) { + Object.defineProperty(ctor.prototype, key, { + value: properties[key], + enumerable: false + }); + } + } + d3.map = function(object, f) { + var map = new d3_Map(); + if (object instanceof d3_Map) { + object.forEach(function(key, value) { + map.set(key, value); + }); + } else if (Array.isArray(object)) { + var i = -1, n = object.length, o; + if (arguments.length === 1) while (++i < n) map.set(i, object[i]); else while (++i < n) map.set(f.call(object, o = object[i], i), o); + } else { + for (var key in object) map.set(key, object[key]); + } + return map; + }; + function d3_Map() { + this._ = Object.create(null); + } + var d3_map_proto = "__proto__", d3_map_zero = "\x00"; + d3_class(d3_Map, { + has: d3_map_has, + get: function(key) { + return this._[d3_map_escape(key)]; + }, + set: function(key, value) { + return this._[d3_map_escape(key)] = value; + }, + remove: d3_map_remove, + keys: d3_map_keys, + values: function() { + var values = []; + for (var key in this._) values.push(this._[key]); + return values; + }, + entries: function() { + var entries = []; + for (var key in this._) entries.push({ + key: d3_map_unescape(key), + value: this._[key] + }); + return entries; + }, + size: d3_map_size, + empty: d3_map_empty, + forEach: function(f) { + for (var key in this._) f.call(this, d3_map_unescape(key), this._[key]); + } + }); + function d3_map_escape(key) { + return (key += "") === d3_map_proto || key[0] === d3_map_zero ? d3_map_zero + key : key; + } + function d3_map_unescape(key) { + return (key += "")[0] === d3_map_zero ? key.slice(1) : key; + } + function d3_map_has(key) { + return d3_map_escape(key) in this._; + } + function d3_map_remove(key) { + return (key = d3_map_escape(key)) in this._ && delete this._[key]; + } + function d3_map_keys() { + var keys = []; + for (var key in this._) keys.push(d3_map_unescape(key)); + return keys; + } + function d3_map_size() { + var size = 0; + for (var key in this._) ++size; + return size; + } + function d3_map_empty() { + for (var key in this._) return false; + return true; + } + d3.nest = function() { + var nest = {}, keys = [], sortKeys = [], sortValues, rollup; + function map(mapType, array, depth) { + if (depth >= keys.length) return rollup ? rollup.call(nest, array) : sortValues ? array.sort(sortValues) : array; + var i = -1, n = array.length, key = keys[depth++], keyValue, object, setter, valuesByKey = new d3_Map(), values; + while (++i < n) { + if (values = valuesByKey.get(keyValue = key(object = array[i]))) { + values.push(object); + } else { + valuesByKey.set(keyValue, [ object ]); + } + } + if (mapType) { + object = mapType(); + setter = function(keyValue, values) { + object.set(keyValue, map(mapType, values, depth)); + }; + } else { + object = {}; + setter = function(keyValue, values) { + object[keyValue] = map(mapType, values, depth); + }; + } + valuesByKey.forEach(setter); + return object; + } + function entries(map, depth) { + if (depth >= keys.length) return map; + var array = [], sortKey = sortKeys[depth++]; + map.forEach(function(key, keyMap) { + array.push({ + key: key, + values: entries(keyMap, depth) + }); + }); + return sortKey ? array.sort(function(a, b) { + return sortKey(a.key, b.key); + }) : array; + } + nest.map = function(array, mapType) { + return map(mapType, array, 0); + }; + nest.entries = function(array) { + return entries(map(d3.map, array, 0), 0); + }; + nest.key = function(d) { + keys.push(d); + return nest; + }; + nest.sortKeys = function(order) { + sortKeys[keys.length - 1] = order; + return nest; + }; + nest.sortValues = function(order) { + sortValues = order; + return nest; + }; + nest.rollup = function(f) { + rollup = f; + return nest; + }; + return nest; + }; + d3.set = function(array) { + var set = new d3_Set(); + if (array) for (var i = 0, n = array.length; i < n; ++i) set.add(array[i]); + return set; + }; + function d3_Set() { + this._ = Object.create(null); + } + d3_class(d3_Set, { + has: d3_map_has, + add: function(key) { + this._[d3_map_escape(key += "")] = true; + return key; + }, + remove: d3_map_remove, + values: d3_map_keys, + size: d3_map_size, + empty: d3_map_empty, + forEach: function(f) { + for (var key in this._) f.call(this, d3_map_unescape(key)); + } + }); + d3.behavior = {}; + function d3_identity(d) { + return d; + } + d3.rebind = function(target, source) { + var i = 1, n = arguments.length, method; + while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]); + return target; + }; + function d3_rebind(target, source, method) { + return function() { + var value = method.apply(source, arguments); + return value === source ? target : value; + }; + } + function d3_vendorSymbol(object, name) { + if (name in object) return name; + name = name.charAt(0).toUpperCase() + name.slice(1); + for (var i = 0, n = d3_vendorPrefixes.length; i < n; ++i) { + var prefixName = d3_vendorPrefixes[i] + name; + if (prefixName in object) return prefixName; + } + } + var d3_vendorPrefixes = [ "webkit", "ms", "moz", "Moz", "o", "O" ]; + function d3_noop() {} + d3.dispatch = function() { + var dispatch = new d3_dispatch(), i = -1, n = arguments.length; + while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); + return dispatch; + }; + function d3_dispatch() {} + d3_dispatch.prototype.on = function(type, listener) { + var i = type.indexOf("."), name = ""; + if (i >= 0) { + name = type.slice(i + 1); + type = type.slice(0, i); + } + if (type) return arguments.length < 2 ? this[type].on(name) : this[type].on(name, listener); + if (arguments.length === 2) { + if (listener == null) for (type in this) { + if (this.hasOwnProperty(type)) this[type].on(name, null); + } + return this; + } + }; + function d3_dispatch_event(dispatch) { + var listeners = [], listenerByName = new d3_Map(); + function event() { + var z = listeners, i = -1, n = z.length, l; + while (++i < n) if (l = z[i].on) l.apply(this, arguments); + return dispatch; + } + event.on = function(name, listener) { + var l = listenerByName.get(name), i; + if (arguments.length < 2) return l && l.on; + if (l) { + l.on = null; + listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1)); + listenerByName.remove(name); + } + if (listener) listeners.push(listenerByName.set(name, { + on: listener + })); + return dispatch; + }; + return event; + } + d3.event = null; + function d3_eventPreventDefault() { + d3.event.preventDefault(); + } + function d3_eventSource() { + var e = d3.event, s; + while (s = e.sourceEvent) e = s; + return e; + } + function d3_eventDispatch(target) { + var dispatch = new d3_dispatch(), i = 0, n = arguments.length; + while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); + dispatch.of = function(thiz, argumentz) { + return function(e1) { + try { + var e0 = e1.sourceEvent = d3.event; + e1.target = target; + d3.event = e1; + dispatch[e1.type].apply(thiz, argumentz); + } finally { + d3.event = e0; + } + }; + }; + return dispatch; + } + d3.requote = function(s) { + return s.replace(d3_requote_re, "\\$&"); + }; + var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; + var d3_subclass = {}.__proto__ ? function(object, prototype) { + object.__proto__ = prototype; + } : function(object, prototype) { + for (var property in prototype) object[property] = prototype[property]; + }; + function d3_selection(groups) { + d3_subclass(groups, d3_selectionPrototype); + return groups; + } + var d3_select = function(s, n) { + return n.querySelector(s); + }, d3_selectAll = function(s, n) { + return n.querySelectorAll(s); + }, d3_selectMatches = function(n, s) { + var d3_selectMatcher = n.matches || n[d3_vendorSymbol(n, "matchesSelector")]; + d3_selectMatches = function(n, s) { + return d3_selectMatcher.call(n, s); + }; + return d3_selectMatches(n, s); + }; + if (typeof Sizzle === "function") { + d3_select = function(s, n) { + return Sizzle(s, n)[0] || null; + }; + d3_selectAll = Sizzle; + d3_selectMatches = Sizzle.matchesSelector; + } + d3.selection = function() { + return d3.select(d3_document.documentElement); + }; + var d3_selectionPrototype = d3.selection.prototype = []; + d3_selectionPrototype.select = function(selector) { + var subgroups = [], subgroup, subnode, group, node; + selector = d3_selection_selector(selector); + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + subgroup.parentNode = (group = this[j]).parentNode; + for (var i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroup.push(subnode = selector.call(node, node.__data__, i, j)); + if (subnode && "__data__" in node) subnode.__data__ = node.__data__; + } else { + subgroup.push(null); + } + } + } + return d3_selection(subgroups); + }; + function d3_selection_selector(selector) { + return typeof selector === "function" ? selector : function() { + return d3_select(selector, this); + }; + } + d3_selectionPrototype.selectAll = function(selector) { + var subgroups = [], subgroup, node; + selector = d3_selection_selectorAll(selector); + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i, j))); + subgroup.parentNode = node; + } + } + } + return d3_selection(subgroups); + }; + function d3_selection_selectorAll(selector) { + return typeof selector === "function" ? selector : function() { + return d3_selectAll(selector, this); + }; + } + var d3_nsXhtml = "http://www.w3.org/1999/xhtml"; + var d3_nsPrefix = { + svg: "http://www.w3.org/2000/svg", + xhtml: d3_nsXhtml, + xlink: "http://www.w3.org/1999/xlink", + xml: "http://www.w3.org/XML/1998/namespace", + xmlns: "http://www.w3.org/2000/xmlns/" + }; + d3.ns = { + prefix: d3_nsPrefix, + qualify: function(name) { + var i = name.indexOf(":"), prefix = name; + if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns") name = name.slice(i + 1); + return d3_nsPrefix.hasOwnProperty(prefix) ? { + space: d3_nsPrefix[prefix], + local: name + } : name; + } + }; + d3_selectionPrototype.attr = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") { + var node = this.node(); + name = d3.ns.qualify(name); + return name.local ? node.getAttributeNS(name.space, name.local) : node.getAttribute(name); + } + for (value in name) this.each(d3_selection_attr(value, name[value])); + return this; + } + return this.each(d3_selection_attr(name, value)); + }; + function d3_selection_attr(name, value) { + name = d3.ns.qualify(name); + function attrNull() { + this.removeAttribute(name); + } + function attrNullNS() { + this.removeAttributeNS(name.space, name.local); + } + function attrConstant() { + this.setAttribute(name, value); + } + function attrConstantNS() { + this.setAttributeNS(name.space, name.local, value); + } + function attrFunction() { + var x = value.apply(this, arguments); + if (x == null) this.removeAttribute(name); else this.setAttribute(name, x); + } + function attrFunctionNS() { + var x = value.apply(this, arguments); + if (x == null) this.removeAttributeNS(name.space, name.local); else this.setAttributeNS(name.space, name.local, x); + } + return value == null ? name.local ? attrNullNS : attrNull : typeof value === "function" ? name.local ? attrFunctionNS : attrFunction : name.local ? attrConstantNS : attrConstant; + } + function d3_collapse(s) { + return s.trim().replace(/\s+/g, " "); + } + d3_selectionPrototype.classed = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") { + var node = this.node(), n = (name = d3_selection_classes(name)).length, i = -1; + if (value = node.classList) { + while (++i < n) if (!value.contains(name[i])) return false; + } else { + value = node.getAttribute("class"); + while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false; + } + return true; + } + for (value in name) this.each(d3_selection_classed(value, name[value])); + return this; + } + return this.each(d3_selection_classed(name, value)); + }; + function d3_selection_classedRe(name) { + return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g"); + } + function d3_selection_classes(name) { + return (name + "").trim().split(/^|\s+/); + } + function d3_selection_classed(name, value) { + name = d3_selection_classes(name).map(d3_selection_classedName); + var n = name.length; + function classedConstant() { + var i = -1; + while (++i < n) name[i](this, value); + } + function classedFunction() { + var i = -1, x = value.apply(this, arguments); + while (++i < n) name[i](this, x); + } + return typeof value === "function" ? classedFunction : classedConstant; + } + function d3_selection_classedName(name) { + var re = d3_selection_classedRe(name); + return function(node, value) { + if (c = node.classList) return value ? c.add(name) : c.remove(name); + var c = node.getAttribute("class") || ""; + if (value) { + re.lastIndex = 0; + if (!re.test(c)) node.setAttribute("class", d3_collapse(c + " " + name)); + } else { + node.setAttribute("class", d3_collapse(c.replace(re, " "))); + } + }; + } + d3_selectionPrototype.style = function(name, value, priority) { + var n = arguments.length; + if (n < 3) { + if (typeof name !== "string") { + if (n < 2) value = ""; + for (priority in name) this.each(d3_selection_style(priority, name[priority], value)); + return this; + } + if (n < 2) { + var node = this.node(); + return d3_window(node).getComputedStyle(node, null).getPropertyValue(name); + } + priority = ""; + } + return this.each(d3_selection_style(name, value, priority)); + }; + function d3_selection_style(name, value, priority) { + function styleNull() { + this.style.removeProperty(name); + } + function styleConstant() { + this.style.setProperty(name, value, priority); + } + function styleFunction() { + var x = value.apply(this, arguments); + if (x == null) this.style.removeProperty(name); else this.style.setProperty(name, x, priority); + } + return value == null ? styleNull : typeof value === "function" ? styleFunction : styleConstant; + } + d3_selectionPrototype.property = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") return this.node()[name]; + for (value in name) this.each(d3_selection_property(value, name[value])); + return this; + } + return this.each(d3_selection_property(name, value)); + }; + function d3_selection_property(name, value) { + function propertyNull() { + delete this[name]; + } + function propertyConstant() { + this[name] = value; + } + function propertyFunction() { + var x = value.apply(this, arguments); + if (x == null) delete this[name]; else this[name] = x; + } + return value == null ? propertyNull : typeof value === "function" ? propertyFunction : propertyConstant; + } + d3_selectionPrototype.text = function(value) { + return arguments.length ? this.each(typeof value === "function" ? function() { + var v = value.apply(this, arguments); + this.textContent = v == null ? "" : v; + } : value == null ? function() { + this.textContent = ""; + } : function() { + this.textContent = value; + }) : this.node().textContent; + }; + d3_selectionPrototype.html = function(value) { + return arguments.length ? this.each(typeof value === "function" ? function() { + var v = value.apply(this, arguments); + this.innerHTML = v == null ? "" : v; + } : value == null ? function() { + this.innerHTML = ""; + } : function() { + this.innerHTML = value; + }) : this.node().innerHTML; + }; + d3_selectionPrototype.append = function(name) { + name = d3_selection_creator(name); + return this.select(function() { + return this.appendChild(name.apply(this, arguments)); + }); + }; + function d3_selection_creator(name) { + function create() { + var document = this.ownerDocument, namespace = this.namespaceURI; + return namespace === d3_nsXhtml && document.documentElement.namespaceURI === d3_nsXhtml ? document.createElement(name) : document.createElementNS(namespace, name); + } + function createNS() { + return this.ownerDocument.createElementNS(name.space, name.local); + } + return typeof name === "function" ? name : (name = d3.ns.qualify(name)).local ? createNS : create; + } + d3_selectionPrototype.insert = function(name, before) { + name = d3_selection_creator(name); + before = d3_selection_selector(before); + return this.select(function() { + return this.insertBefore(name.apply(this, arguments), before.apply(this, arguments) || null); + }); + }; + d3_selectionPrototype.remove = function() { + return this.each(d3_selectionRemove); + }; + function d3_selectionRemove() { + var parent = this.parentNode; + if (parent) parent.removeChild(this); + } + d3_selectionPrototype.data = function(value, key) { + var i = -1, n = this.length, group, node; + if (!arguments.length) { + value = new Array(n = (group = this[0]).length); + while (++i < n) { + if (node = group[i]) { + value[i] = node.__data__; + } + } + return value; + } + function bind(group, groupData) { + var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), updateNodes = new Array(m), enterNodes = new Array(m), exitNodes = new Array(n), node, nodeData; + if (key) { + var nodeByKeyValue = new d3_Map(), keyValues = new Array(n), keyValue; + for (i = -1; ++i < n; ) { + if (node = group[i]) { + if (nodeByKeyValue.has(keyValue = key.call(node, node.__data__, i))) { + exitNodes[i] = node; + } else { + nodeByKeyValue.set(keyValue, node); + } + keyValues[i] = keyValue; + } + } + for (i = -1; ++i < m; ) { + if (!(node = nodeByKeyValue.get(keyValue = key.call(groupData, nodeData = groupData[i], i)))) { + enterNodes[i] = d3_selection_dataNode(nodeData); + } else if (node !== true) { + updateNodes[i] = node; + node.__data__ = nodeData; + } + nodeByKeyValue.set(keyValue, true); + } + for (i = -1; ++i < n; ) { + if (i in keyValues && nodeByKeyValue.get(keyValues[i]) !== true) { + exitNodes[i] = group[i]; + } + } + } else { + for (i = -1; ++i < n0; ) { + node = group[i]; + nodeData = groupData[i]; + if (node) { + node.__data__ = nodeData; + updateNodes[i] = node; + } else { + enterNodes[i] = d3_selection_dataNode(nodeData); + } + } + for (;i < m; ++i) { + enterNodes[i] = d3_selection_dataNode(groupData[i]); + } + for (;i < n; ++i) { + exitNodes[i] = group[i]; + } + } + enterNodes.update = updateNodes; + enterNodes.parentNode = updateNodes.parentNode = exitNodes.parentNode = group.parentNode; + enter.push(enterNodes); + update.push(updateNodes); + exit.push(exitNodes); + } + var enter = d3_selection_enter([]), update = d3_selection([]), exit = d3_selection([]); + if (typeof value === "function") { + while (++i < n) { + bind(group = this[i], value.call(group, group.parentNode.__data__, i)); + } + } else { + while (++i < n) { + bind(group = this[i], value); + } + } + update.enter = function() { + return enter; + }; + update.exit = function() { + return exit; + }; + return update; + }; + function d3_selection_dataNode(data) { + return { + __data__: data + }; + } + d3_selectionPrototype.datum = function(value) { + return arguments.length ? this.property("__data__", value) : this.property("__data__"); + }; + d3_selectionPrototype.filter = function(filter) { + var subgroups = [], subgroup, group, node; + if (typeof filter !== "function") filter = d3_selection_filter(filter); + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + subgroup.parentNode = (group = this[j]).parentNode; + for (var i = 0, n = group.length; i < n; i++) { + if ((node = group[i]) && filter.call(node, node.__data__, i, j)) { + subgroup.push(node); + } + } + } + return d3_selection(subgroups); + }; + function d3_selection_filter(selector) { + return function() { + return d3_selectMatches(this, selector); + }; + } + d3_selectionPrototype.order = function() { + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0; ) { + if (node = group[i]) { + if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next); + next = node; + } + } + } + return this; + }; + d3_selectionPrototype.sort = function(comparator) { + comparator = d3_selection_sortComparator.apply(this, arguments); + for (var j = -1, m = this.length; ++j < m; ) this[j].sort(comparator); + return this.order(); + }; + function d3_selection_sortComparator(comparator) { + if (!arguments.length) comparator = d3_ascending; + return function(a, b) { + return a && b ? comparator(a.__data__, b.__data__) : !a - !b; + }; + } + d3_selectionPrototype.each = function(callback) { + return d3_selection_each(this, function(node, i, j) { + callback.call(node, node.__data__, i, j); + }); + }; + function d3_selection_each(groups, callback) { + for (var j = 0, m = groups.length; j < m; j++) { + for (var group = groups[j], i = 0, n = group.length, node; i < n; i++) { + if (node = group[i]) callback(node, i, j); + } + } + return groups; + } + d3_selectionPrototype.call = function(callback) { + var args = d3_array(arguments); + callback.apply(args[0] = this, args); + return this; + }; + d3_selectionPrototype.empty = function() { + return !this.node(); + }; + d3_selectionPrototype.node = function() { + for (var j = 0, m = this.length; j < m; j++) { + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + var node = group[i]; + if (node) return node; + } + } + return null; + }; + d3_selectionPrototype.size = function() { + var n = 0; + d3_selection_each(this, function() { + ++n; + }); + return n; + }; + function d3_selection_enter(selection) { + d3_subclass(selection, d3_selection_enterPrototype); + return selection; + } + var d3_selection_enterPrototype = []; + d3.selection.enter = d3_selection_enter; + d3.selection.enter.prototype = d3_selection_enterPrototype; + d3_selection_enterPrototype.append = d3_selectionPrototype.append; + d3_selection_enterPrototype.empty = d3_selectionPrototype.empty; + d3_selection_enterPrototype.node = d3_selectionPrototype.node; + d3_selection_enterPrototype.call = d3_selectionPrototype.call; + d3_selection_enterPrototype.size = d3_selectionPrototype.size; + d3_selection_enterPrototype.select = function(selector) { + var subgroups = [], subgroup, subnode, upgroup, group, node; + for (var j = -1, m = this.length; ++j < m; ) { + upgroup = (group = this[j]).update; + subgroups.push(subgroup = []); + subgroup.parentNode = group.parentNode; + for (var i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i, j)); + subnode.__data__ = node.__data__; + } else { + subgroup.push(null); + } + } + } + return d3_selection(subgroups); + }; + d3_selection_enterPrototype.insert = function(name, before) { + if (arguments.length < 2) before = d3_selection_enterInsertBefore(this); + return d3_selectionPrototype.insert.call(this, name, before); + }; + function d3_selection_enterInsertBefore(enter) { + var i0, j0; + return function(d, i, j) { + var group = enter[j].update, n = group.length, node; + if (j != j0) j0 = j, i0 = 0; + if (i >= i0) i0 = i + 1; + while (!(node = group[i0]) && ++i0 < n) ; + return node; + }; + } + d3.select = function(node) { + var group; + if (typeof node === "string") { + group = [ d3_select(node, d3_document) ]; + group.parentNode = d3_document.documentElement; + } else { + group = [ node ]; + group.parentNode = d3_documentElement(node); + } + return d3_selection([ group ]); + }; + d3.selectAll = function(nodes) { + var group; + if (typeof nodes === "string") { + group = d3_array(d3_selectAll(nodes, d3_document)); + group.parentNode = d3_document.documentElement; + } else { + group = d3_array(nodes); + group.parentNode = null; + } + return d3_selection([ group ]); + }; + d3_selectionPrototype.on = function(type, listener, capture) { + var n = arguments.length; + if (n < 3) { + if (typeof type !== "string") { + if (n < 2) listener = false; + for (capture in type) this.each(d3_selection_on(capture, type[capture], listener)); + return this; + } + if (n < 2) return (n = this.node()["__on" + type]) && n._; + capture = false; + } + return this.each(d3_selection_on(type, listener, capture)); + }; + function d3_selection_on(type, listener, capture) { + var name = "__on" + type, i = type.indexOf("."), wrap = d3_selection_onListener; + if (i > 0) type = type.slice(0, i); + var filter = d3_selection_onFilters.get(type); + if (filter) type = filter, wrap = d3_selection_onFilter; + function onRemove() { + var l = this[name]; + if (l) { + this.removeEventListener(type, l, l.$); + delete this[name]; + } + } + function onAdd() { + var l = wrap(listener, d3_array(arguments)); + onRemove.call(this); + this.addEventListener(type, this[name] = l, l.$ = capture); + l._ = listener; + } + function removeAll() { + var re = new RegExp("^__on([^.]+)" + d3.requote(type) + "$"), match; + for (var name in this) { + if (match = name.match(re)) { + var l = this[name]; + this.removeEventListener(match[1], l, l.$); + delete this[name]; + } + } + } + return i ? listener ? onAdd : onRemove : listener ? d3_noop : removeAll; + } + var d3_selection_onFilters = d3.map({ + mouseenter: "mouseover", + mouseleave: "mouseout" + }); + if (d3_document) { + d3_selection_onFilters.forEach(function(k) { + if ("on" + k in d3_document) d3_selection_onFilters.remove(k); + }); + } + function d3_selection_onListener(listener, argumentz) { + return function(e) { + var o = d3.event; + d3.event = e; + argumentz[0] = this.__data__; + try { + listener.apply(this, argumentz); + } finally { + d3.event = o; + } + }; + } + function d3_selection_onFilter(listener, argumentz) { + var l = d3_selection_onListener(listener, argumentz); + return function(e) { + var target = this, related = e.relatedTarget; + if (!related || related !== target && !(related.compareDocumentPosition(target) & 8)) { + l.call(target, e); + } + }; + } + var d3_event_dragSelect, d3_event_dragId = 0; + function d3_event_dragSuppress(node) { + var name = ".dragsuppress-" + ++d3_event_dragId, click = "click" + name, w = d3.select(d3_window(node)).on("touchmove" + name, d3_eventPreventDefault).on("dragstart" + name, d3_eventPreventDefault).on("selectstart" + name, d3_eventPreventDefault); + if (d3_event_dragSelect == null) { + d3_event_dragSelect = "onselectstart" in node ? false : d3_vendorSymbol(node.style, "userSelect"); + } + if (d3_event_dragSelect) { + var style = d3_documentElement(node).style, select = style[d3_event_dragSelect]; + style[d3_event_dragSelect] = "none"; + } + return function(suppressClick) { + w.on(name, null); + if (d3_event_dragSelect) style[d3_event_dragSelect] = select; + if (suppressClick) { + var off = function() { + w.on(click, null); + }; + w.on(click, function() { + d3_eventPreventDefault(); + off(); + }, true); + setTimeout(off, 0); + } + }; + } + d3.mouse = function(container) { + return d3_mousePoint(container, d3_eventSource()); + }; + var d3_mouse_bug44083 = this.navigator && /WebKit/.test(this.navigator.userAgent) ? -1 : 0; + function d3_mousePoint(container, e) { + if (e.changedTouches) e = e.changedTouches[0]; + var svg = container.ownerSVGElement || container; + if (svg.createSVGPoint) { + var point = svg.createSVGPoint(); + if (d3_mouse_bug44083 < 0) { + var window = d3_window(container); + if (window.scrollX || window.scrollY) { + svg = d3.select("body").append("svg").style({ + position: "absolute", + top: 0, + left: 0, + margin: 0, + padding: 0, + border: "none" + }, "important"); + var ctm = svg[0][0].getScreenCTM(); + d3_mouse_bug44083 = !(ctm.f || ctm.e); + svg.remove(); + } + } + if (d3_mouse_bug44083) point.x = e.pageX, point.y = e.pageY; else point.x = e.clientX, + point.y = e.clientY; + point = point.matrixTransform(container.getScreenCTM().inverse()); + return [ point.x, point.y ]; + } + var rect = container.getBoundingClientRect(); + return [ e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop ]; + } + d3.touch = function(container, touches, identifier) { + if (arguments.length < 3) identifier = touches, touches = d3_eventSource().changedTouches; + if (touches) for (var i = 0, n = touches.length, touch; i < n; ++i) { + if ((touch = touches[i]).identifier === identifier) { + return d3_mousePoint(container, touch); + } + } + }; + d3.behavior.drag = function() { + var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null, mousedown = dragstart(d3_noop, d3.mouse, d3_window, "mousemove", "mouseup"), touchstart = dragstart(d3_behavior_dragTouchId, d3.touch, d3_identity, "touchmove", "touchend"); + function drag() { + this.on("mousedown.drag", mousedown).on("touchstart.drag", touchstart); + } + function dragstart(id, position, subject, move, end) { + return function() { + var that = this, target = d3.event.target.correspondingElement || d3.event.target, parent = that.parentNode, dispatch = event.of(that, arguments), dragged = 0, dragId = id(), dragName = ".drag" + (dragId == null ? "" : "-" + dragId), dragOffset, dragSubject = d3.select(subject(target)).on(move + dragName, moved).on(end + dragName, ended), dragRestore = d3_event_dragSuppress(target), position0 = position(parent, dragId); + if (origin) { + dragOffset = origin.apply(that, arguments); + dragOffset = [ dragOffset.x - position0[0], dragOffset.y - position0[1] ]; + } else { + dragOffset = [ 0, 0 ]; + } + dispatch({ + type: "dragstart" + }); + function moved() { + var position1 = position(parent, dragId), dx, dy; + if (!position1) return; + dx = position1[0] - position0[0]; + dy = position1[1] - position0[1]; + dragged |= dx | dy; + position0 = position1; + dispatch({ + type: "drag", + x: position1[0] + dragOffset[0], + y: position1[1] + dragOffset[1], + dx: dx, + dy: dy + }); + } + function ended() { + if (!position(parent, dragId)) return; + dragSubject.on(move + dragName, null).on(end + dragName, null); + dragRestore(dragged); + dispatch({ + type: "dragend" + }); + } + }; + } + drag.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + return drag; + }; + return d3.rebind(drag, event, "on"); + }; + function d3_behavior_dragTouchId() { + return d3.event.changedTouches[0].identifier; + } + d3.touches = function(container, touches) { + if (arguments.length < 2) touches = d3_eventSource().touches; + return touches ? d3_array(touches).map(function(touch) { + var point = d3_mousePoint(container, touch); + point.identifier = touch.identifier; + return point; + }) : []; + }; + var ε = 1e-6, ε2 = ε * ε, π = Math.PI, τ = 2 * π, τε = τ - ε, halfπ = π / 2, d3_radians = π / 180, d3_degrees = 180 / π; + function d3_sgn(x) { + return x > 0 ? 1 : x < 0 ? -1 : 0; + } + function d3_cross2d(a, b, c) { + return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]); + } + function d3_acos(x) { + return x > 1 ? 0 : x < -1 ? π : Math.acos(x); + } + function d3_asin(x) { + return x > 1 ? halfπ : x < -1 ? -halfπ : Math.asin(x); + } + function d3_sinh(x) { + return ((x = Math.exp(x)) - 1 / x) / 2; + } + function d3_cosh(x) { + return ((x = Math.exp(x)) + 1 / x) / 2; + } + function d3_tanh(x) { + return ((x = Math.exp(2 * x)) - 1) / (x + 1); + } + function d3_haversin(x) { + return (x = Math.sin(x / 2)) * x; + } + var ρ = Math.SQRT2, ρ2 = 2, ρ4 = 4; + d3.interpolateZoom = function(p0, p1) { + var ux0 = p0[0], uy0 = p0[1], w0 = p0[2], ux1 = p1[0], uy1 = p1[1], w1 = p1[2], dx = ux1 - ux0, dy = uy1 - uy0, d2 = dx * dx + dy * dy, i, S; + if (d2 < ε2) { + S = Math.log(w1 / w0) / ρ; + i = function(t) { + return [ ux0 + t * dx, uy0 + t * dy, w0 * Math.exp(ρ * t * S) ]; + }; + } else { + var d1 = Math.sqrt(d2), b0 = (w1 * w1 - w0 * w0 + ρ4 * d2) / (2 * w0 * ρ2 * d1), b1 = (w1 * w1 - w0 * w0 - ρ4 * d2) / (2 * w1 * ρ2 * d1), r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0), r1 = Math.log(Math.sqrt(b1 * b1 + 1) - b1); + S = (r1 - r0) / ρ; + i = function(t) { + var s = t * S, coshr0 = d3_cosh(r0), u = w0 / (ρ2 * d1) * (coshr0 * d3_tanh(ρ * s + r0) - d3_sinh(r0)); + return [ ux0 + u * dx, uy0 + u * dy, w0 * coshr0 / d3_cosh(ρ * s + r0) ]; + }; + } + i.duration = S * 1e3; + return i; + }; + d3.behavior.zoom = function() { + var view = { + x: 0, + y: 0, + k: 1 + }, translate0, center0, center, size = [ 960, 500 ], scaleExtent = d3_behavior_zoomInfinity, duration = 250, zooming = 0, mousedown = "mousedown.zoom", mousemove = "mousemove.zoom", mouseup = "mouseup.zoom", mousewheelTimer, touchstart = "touchstart.zoom", touchtime, event = d3_eventDispatch(zoom, "zoomstart", "zoom", "zoomend"), x0, x1, y0, y1; + if (!d3_behavior_zoomWheel) { + d3_behavior_zoomWheel = "onwheel" in d3_document ? (d3_behavior_zoomDelta = function() { + return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1); + }, "wheel") : "onmousewheel" in d3_document ? (d3_behavior_zoomDelta = function() { + return d3.event.wheelDelta; + }, "mousewheel") : (d3_behavior_zoomDelta = function() { + return -d3.event.detail; + }, "MozMousePixelScroll"); + } + function zoom(g) { + g.on(mousedown, mousedowned).on(d3_behavior_zoomWheel + ".zoom", mousewheeled).on("dblclick.zoom", dblclicked).on(touchstart, touchstarted); + } + zoom.event = function(g) { + g.each(function() { + var dispatch = event.of(this, arguments), view1 = view; + if (d3_transitionInheritId) { + d3.select(this).transition().each("start.zoom", function() { + view = this.__chart__ || { + x: 0, + y: 0, + k: 1 + }; + zoomstarted(dispatch); + }).tween("zoom:zoom", function() { + var dx = size[0], dy = size[1], cx = center0 ? center0[0] : dx / 2, cy = center0 ? center0[1] : dy / 2, i = d3.interpolateZoom([ (cx - view.x) / view.k, (cy - view.y) / view.k, dx / view.k ], [ (cx - view1.x) / view1.k, (cy - view1.y) / view1.k, dx / view1.k ]); + return function(t) { + var l = i(t), k = dx / l[2]; + this.__chart__ = view = { + x: cx - l[0] * k, + y: cy - l[1] * k, + k: k + }; + zoomed(dispatch); + }; + }).each("interrupt.zoom", function() { + zoomended(dispatch); + }).each("end.zoom", function() { + zoomended(dispatch); + }); + } else { + this.__chart__ = view; + zoomstarted(dispatch); + zoomed(dispatch); + zoomended(dispatch); + } + }); + }; + zoom.translate = function(_) { + if (!arguments.length) return [ view.x, view.y ]; + view = { + x: +_[0], + y: +_[1], + k: view.k + }; + rescale(); + return zoom; + }; + zoom.scale = function(_) { + if (!arguments.length) return view.k; + view = { + x: view.x, + y: view.y, + k: null + }; + scaleTo(+_); + rescale(); + return zoom; + }; + zoom.scaleExtent = function(_) { + if (!arguments.length) return scaleExtent; + scaleExtent = _ == null ? d3_behavior_zoomInfinity : [ +_[0], +_[1] ]; + return zoom; + }; + zoom.center = function(_) { + if (!arguments.length) return center; + center = _ && [ +_[0], +_[1] ]; + return zoom; + }; + zoom.size = function(_) { + if (!arguments.length) return size; + size = _ && [ +_[0], +_[1] ]; + return zoom; + }; + zoom.duration = function(_) { + if (!arguments.length) return duration; + duration = +_; + return zoom; + }; + zoom.x = function(z) { + if (!arguments.length) return x1; + x1 = z; + x0 = z.copy(); + view = { + x: 0, + y: 0, + k: 1 + }; + return zoom; + }; + zoom.y = function(z) { + if (!arguments.length) return y1; + y1 = z; + y0 = z.copy(); + view = { + x: 0, + y: 0, + k: 1 + }; + return zoom; + }; + function location(p) { + return [ (p[0] - view.x) / view.k, (p[1] - view.y) / view.k ]; + } + function point(l) { + return [ l[0] * view.k + view.x, l[1] * view.k + view.y ]; + } + function scaleTo(s) { + view.k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s)); + } + function translateTo(p, l) { + l = point(l); + view.x += p[0] - l[0]; + view.y += p[1] - l[1]; + } + function zoomTo(that, p, l, k) { + that.__chart__ = { + x: view.x, + y: view.y, + k: view.k + }; + scaleTo(Math.pow(2, k)); + translateTo(center0 = p, l); + that = d3.select(that); + if (duration > 0) that = that.transition().duration(duration); + that.call(zoom.event); + } + function rescale() { + if (x1) x1.domain(x0.range().map(function(x) { + return (x - view.x) / view.k; + }).map(x0.invert)); + if (y1) y1.domain(y0.range().map(function(y) { + return (y - view.y) / view.k; + }).map(y0.invert)); + } + function zoomstarted(dispatch) { + if (!zooming++) dispatch({ + type: "zoomstart" + }); + } + function zoomed(dispatch) { + rescale(); + dispatch({ + type: "zoom", + scale: view.k, + translate: [ view.x, view.y ] + }); + } + function zoomended(dispatch) { + if (!--zooming) dispatch({ + type: "zoomend" + }), center0 = null; + } + function mousedowned() { + var that = this, dispatch = event.of(that, arguments), dragged = 0, subject = d3.select(d3_window(that)).on(mousemove, moved).on(mouseup, ended), location0 = location(d3.mouse(that)), dragRestore = d3_event_dragSuppress(that); + d3_selection_interrupt.call(that); + zoomstarted(dispatch); + function moved() { + dragged = 1; + translateTo(d3.mouse(that), location0); + zoomed(dispatch); + } + function ended() { + subject.on(mousemove, null).on(mouseup, null); + dragRestore(dragged); + zoomended(dispatch); + } + } + function touchstarted() { + var that = this, dispatch = event.of(that, arguments), locations0 = {}, distance0 = 0, scale0, zoomName = ".zoom-" + d3.event.changedTouches[0].identifier, touchmove = "touchmove" + zoomName, touchend = "touchend" + zoomName, targets = [], subject = d3.select(that), dragRestore = d3_event_dragSuppress(that); + started(); + zoomstarted(dispatch); + subject.on(mousedown, null).on(touchstart, started); + function relocate() { + var touches = d3.touches(that); + scale0 = view.k; + touches.forEach(function(t) { + if (t.identifier in locations0) locations0[t.identifier] = location(t); + }); + return touches; + } + function started() { + var target = d3.event.target; + d3.select(target).on(touchmove, moved).on(touchend, ended); + targets.push(target); + var changed = d3.event.changedTouches; + for (var i = 0, n = changed.length; i < n; ++i) { + locations0[changed[i].identifier] = null; + } + var touches = relocate(), now = Date.now(); + if (touches.length === 1) { + if (now - touchtime < 500) { + var p = touches[0]; + zoomTo(that, p, locations0[p.identifier], Math.floor(Math.log(view.k) / Math.LN2) + 1); + d3_eventPreventDefault(); + } + touchtime = now; + } else if (touches.length > 1) { + var p = touches[0], q = touches[1], dx = p[0] - q[0], dy = p[1] - q[1]; + distance0 = dx * dx + dy * dy; + } + } + function moved() { + var touches = d3.touches(that), p0, l0, p1, l1; + d3_selection_interrupt.call(that); + for (var i = 0, n = touches.length; i < n; ++i, l1 = null) { + p1 = touches[i]; + if (l1 = locations0[p1.identifier]) { + if (l0) break; + p0 = p1, l0 = l1; + } + } + if (l1) { + var distance1 = (distance1 = p1[0] - p0[0]) * distance1 + (distance1 = p1[1] - p0[1]) * distance1, scale1 = distance0 && Math.sqrt(distance1 / distance0); + p0 = [ (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2 ]; + l0 = [ (l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2 ]; + scaleTo(scale1 * scale0); + } + touchtime = null; + translateTo(p0, l0); + zoomed(dispatch); + } + function ended() { + if (d3.event.touches.length) { + var changed = d3.event.changedTouches; + for (var i = 0, n = changed.length; i < n; ++i) { + delete locations0[changed[i].identifier]; + } + for (var identifier in locations0) { + return void relocate(); + } + } + d3.selectAll(targets).on(zoomName, null); + subject.on(mousedown, mousedowned).on(touchstart, touchstarted); + dragRestore(); + zoomended(dispatch); + } + } + function mousewheeled() { + var dispatch = event.of(this, arguments); + if (mousewheelTimer) clearTimeout(mousewheelTimer); else d3_selection_interrupt.call(this), + translate0 = location(center0 = center || d3.mouse(this)), zoomstarted(dispatch); + mousewheelTimer = setTimeout(function() { + mousewheelTimer = null; + zoomended(dispatch); + }, 50); + d3_eventPreventDefault(); + scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * view.k); + translateTo(center0, translate0); + zoomed(dispatch); + } + function dblclicked() { + var p = d3.mouse(this), k = Math.log(view.k) / Math.LN2; + zoomTo(this, p, location(p), d3.event.shiftKey ? Math.ceil(k) - 1 : Math.floor(k) + 1); + } + return d3.rebind(zoom, event, "on"); + }; + var d3_behavior_zoomInfinity = [ 0, Infinity ], d3_behavior_zoomDelta, d3_behavior_zoomWheel; + d3.color = d3_color; + function d3_color() {} + d3_color.prototype.toString = function() { + return this.rgb() + ""; + }; + d3.hsl = d3_hsl; + function d3_hsl(h, s, l) { + return this instanceof d3_hsl ? void (this.h = +h, this.s = +s, this.l = +l) : arguments.length < 2 ? h instanceof d3_hsl ? new d3_hsl(h.h, h.s, h.l) : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl) : new d3_hsl(h, s, l); + } + var d3_hslPrototype = d3_hsl.prototype = new d3_color(); + d3_hslPrototype.brighter = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return new d3_hsl(this.h, this.s, this.l / k); + }; + d3_hslPrototype.darker = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return new d3_hsl(this.h, this.s, k * this.l); + }; + d3_hslPrototype.rgb = function() { + return d3_hsl_rgb(this.h, this.s, this.l); + }; + function d3_hsl_rgb(h, s, l) { + var m1, m2; + h = isNaN(h) ? 0 : (h %= 360) < 0 ? h + 360 : h; + s = isNaN(s) ? 0 : s < 0 ? 0 : s > 1 ? 1 : s; + l = l < 0 ? 0 : l > 1 ? 1 : l; + m2 = l <= .5 ? l * (1 + s) : l + s - l * s; + m1 = 2 * l - m2; + function v(h) { + if (h > 360) h -= 360; else if (h < 0) h += 360; + if (h < 60) return m1 + (m2 - m1) * h / 60; + if (h < 180) return m2; + if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60; + return m1; + } + function vv(h) { + return Math.round(v(h) * 255); + } + return new d3_rgb(vv(h + 120), vv(h), vv(h - 120)); + } + d3.hcl = d3_hcl; + function d3_hcl(h, c, l) { + return this instanceof d3_hcl ? void (this.h = +h, this.c = +c, this.l = +l) : arguments.length < 2 ? h instanceof d3_hcl ? new d3_hcl(h.h, h.c, h.l) : h instanceof d3_lab ? d3_lab_hcl(h.l, h.a, h.b) : d3_lab_hcl((h = d3_rgb_lab((h = d3.rgb(h)).r, h.g, h.b)).l, h.a, h.b) : new d3_hcl(h, c, l); + } + var d3_hclPrototype = d3_hcl.prototype = new d3_color(); + d3_hclPrototype.brighter = function(k) { + return new d3_hcl(this.h, this.c, Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1))); + }; + d3_hclPrototype.darker = function(k) { + return new d3_hcl(this.h, this.c, Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1))); + }; + d3_hclPrototype.rgb = function() { + return d3_hcl_lab(this.h, this.c, this.l).rgb(); + }; + function d3_hcl_lab(h, c, l) { + if (isNaN(h)) h = 0; + if (isNaN(c)) c = 0; + return new d3_lab(l, Math.cos(h *= d3_radians) * c, Math.sin(h) * c); + } + d3.lab = d3_lab; + function d3_lab(l, a, b) { + return this instanceof d3_lab ? void (this.l = +l, this.a = +a, this.b = +b) : arguments.length < 2 ? l instanceof d3_lab ? new d3_lab(l.l, l.a, l.b) : l instanceof d3_hcl ? d3_hcl_lab(l.h, l.c, l.l) : d3_rgb_lab((l = d3_rgb(l)).r, l.g, l.b) : new d3_lab(l, a, b); + } + var d3_lab_K = 18; + var d3_lab_X = .95047, d3_lab_Y = 1, d3_lab_Z = 1.08883; + var d3_labPrototype = d3_lab.prototype = new d3_color(); + d3_labPrototype.brighter = function(k) { + return new d3_lab(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); + }; + d3_labPrototype.darker = function(k) { + return new d3_lab(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); + }; + d3_labPrototype.rgb = function() { + return d3_lab_rgb(this.l, this.a, this.b); + }; + function d3_lab_rgb(l, a, b) { + var y = (l + 16) / 116, x = y + a / 500, z = y - b / 200; + x = d3_lab_xyz(x) * d3_lab_X; + y = d3_lab_xyz(y) * d3_lab_Y; + z = d3_lab_xyz(z) * d3_lab_Z; + return new d3_rgb(d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - .4985314 * z), d3_xyz_rgb(-.969266 * x + 1.8760108 * y + .041556 * z), d3_xyz_rgb(.0556434 * x - .2040259 * y + 1.0572252 * z)); + } + function d3_lab_hcl(l, a, b) { + return l > 0 ? new d3_hcl(Math.atan2(b, a) * d3_degrees, Math.sqrt(a * a + b * b), l) : new d3_hcl(NaN, NaN, l); + } + function d3_lab_xyz(x) { + return x > .206893034 ? x * x * x : (x - 4 / 29) / 7.787037; + } + function d3_xyz_lab(x) { + return x > .008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29; + } + function d3_xyz_rgb(r) { + return Math.round(255 * (r <= .00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - .055)); + } + d3.rgb = d3_rgb; + function d3_rgb(r, g, b) { + return this instanceof d3_rgb ? void (this.r = ~~r, this.g = ~~g, this.b = ~~b) : arguments.length < 2 ? r instanceof d3_rgb ? new d3_rgb(r.r, r.g, r.b) : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb) : new d3_rgb(r, g, b); + } + function d3_rgbNumber(value) { + return new d3_rgb(value >> 16, value >> 8 & 255, value & 255); + } + function d3_rgbString(value) { + return d3_rgbNumber(value) + ""; + } + var d3_rgbPrototype = d3_rgb.prototype = new d3_color(); + d3_rgbPrototype.brighter = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + var r = this.r, g = this.g, b = this.b, i = 30; + if (!r && !g && !b) return new d3_rgb(i, i, i); + if (r && r < i) r = i; + if (g && g < i) g = i; + if (b && b < i) b = i; + return new d3_rgb(Math.min(255, r / k), Math.min(255, g / k), Math.min(255, b / k)); + }; + d3_rgbPrototype.darker = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return new d3_rgb(k * this.r, k * this.g, k * this.b); + }; + d3_rgbPrototype.hsl = function() { + return d3_rgb_hsl(this.r, this.g, this.b); + }; + d3_rgbPrototype.toString = function() { + return "#" + d3_rgb_hex(this.r) + d3_rgb_hex(this.g) + d3_rgb_hex(this.b); + }; + function d3_rgb_hex(v) { + return v < 16 ? "0" + Math.max(0, v).toString(16) : Math.min(255, v).toString(16); + } + function d3_rgb_parse(format, rgb, hsl) { + var r = 0, g = 0, b = 0, m1, m2, color; + m1 = /([a-z]+)\((.*)\)/.exec(format = format.toLowerCase()); + if (m1) { + m2 = m1[2].split(","); + switch (m1[1]) { + case "hsl": + { + return hsl(parseFloat(m2[0]), parseFloat(m2[1]) / 100, parseFloat(m2[2]) / 100); + } + + case "rgb": + { + return rgb(d3_rgb_parseNumber(m2[0]), d3_rgb_parseNumber(m2[1]), d3_rgb_parseNumber(m2[2])); + } + } + } + if (color = d3_rgb_names.get(format)) { + return rgb(color.r, color.g, color.b); + } + if (format != null && format.charAt(0) === "#" && !isNaN(color = parseInt(format.slice(1), 16))) { + if (format.length === 4) { + r = (color & 3840) >> 4; + r = r >> 4 | r; + g = color & 240; + g = g >> 4 | g; + b = color & 15; + b = b << 4 | b; + } else if (format.length === 7) { + r = (color & 16711680) >> 16; + g = (color & 65280) >> 8; + b = color & 255; + } + } + return rgb(r, g, b); + } + function d3_rgb_hsl(r, g, b) { + var min = Math.min(r /= 255, g /= 255, b /= 255), max = Math.max(r, g, b), d = max - min, h, s, l = (max + min) / 2; + if (d) { + s = l < .5 ? d / (max + min) : d / (2 - max - min); + if (r == max) h = (g - b) / d + (g < b ? 6 : 0); else if (g == max) h = (b - r) / d + 2; else h = (r - g) / d + 4; + h *= 60; + } else { + h = NaN; + s = l > 0 && l < 1 ? 0 : h; + } + return new d3_hsl(h, s, l); + } + function d3_rgb_lab(r, g, b) { + r = d3_rgb_xyz(r); + g = d3_rgb_xyz(g); + b = d3_rgb_xyz(b); + var x = d3_xyz_lab((.4124564 * r + .3575761 * g + .1804375 * b) / d3_lab_X), y = d3_xyz_lab((.2126729 * r + .7151522 * g + .072175 * b) / d3_lab_Y), z = d3_xyz_lab((.0193339 * r + .119192 * g + .9503041 * b) / d3_lab_Z); + return d3_lab(116 * y - 16, 500 * (x - y), 200 * (y - z)); + } + function d3_rgb_xyz(r) { + return (r /= 255) <= .04045 ? r / 12.92 : Math.pow((r + .055) / 1.055, 2.4); + } + function d3_rgb_parseNumber(c) { + var f = parseFloat(c); + return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f; + } + var d3_rgb_names = d3.map({ + aliceblue: 15792383, + antiquewhite: 16444375, + aqua: 65535, + aquamarine: 8388564, + azure: 15794175, + beige: 16119260, + bisque: 16770244, + black: 0, + blanchedalmond: 16772045, + blue: 255, + blueviolet: 9055202, + brown: 10824234, + burlywood: 14596231, + cadetblue: 6266528, + chartreuse: 8388352, + chocolate: 13789470, + coral: 16744272, + cornflowerblue: 6591981, + cornsilk: 16775388, + crimson: 14423100, + cyan: 65535, + darkblue: 139, + darkcyan: 35723, + darkgoldenrod: 12092939, + darkgray: 11119017, + darkgreen: 25600, + darkgrey: 11119017, + darkkhaki: 12433259, + darkmagenta: 9109643, + darkolivegreen: 5597999, + darkorange: 16747520, + darkorchid: 10040012, + darkred: 9109504, + darksalmon: 15308410, + darkseagreen: 9419919, + darkslateblue: 4734347, + darkslategray: 3100495, + darkslategrey: 3100495, + darkturquoise: 52945, + darkviolet: 9699539, + deeppink: 16716947, + deepskyblue: 49151, + dimgray: 6908265, + dimgrey: 6908265, + dodgerblue: 2003199, + firebrick: 11674146, + floralwhite: 16775920, + forestgreen: 2263842, + fuchsia: 16711935, + gainsboro: 14474460, + ghostwhite: 16316671, + gold: 16766720, + goldenrod: 14329120, + gray: 8421504, + green: 32768, + greenyellow: 11403055, + grey: 8421504, + honeydew: 15794160, + hotpink: 16738740, + indianred: 13458524, + indigo: 4915330, + ivory: 16777200, + khaki: 15787660, + lavender: 15132410, + lavenderblush: 16773365, + lawngreen: 8190976, + lemonchiffon: 16775885, + lightblue: 11393254, + lightcoral: 15761536, + lightcyan: 14745599, + lightgoldenrodyellow: 16448210, + lightgray: 13882323, + lightgreen: 9498256, + lightgrey: 13882323, + lightpink: 16758465, + lightsalmon: 16752762, + lightseagreen: 2142890, + lightskyblue: 8900346, + lightslategray: 7833753, + lightslategrey: 7833753, + lightsteelblue: 11584734, + lightyellow: 16777184, + lime: 65280, + limegreen: 3329330, + linen: 16445670, + magenta: 16711935, + maroon: 8388608, + mediumaquamarine: 6737322, + mediumblue: 205, + mediumorchid: 12211667, + mediumpurple: 9662683, + mediumseagreen: 3978097, + mediumslateblue: 8087790, + mediumspringgreen: 64154, + mediumturquoise: 4772300, + mediumvioletred: 13047173, + midnightblue: 1644912, + mintcream: 16121850, + mistyrose: 16770273, + moccasin: 16770229, + navajowhite: 16768685, + navy: 128, + oldlace: 16643558, + olive: 8421376, + olivedrab: 7048739, + orange: 16753920, + orangered: 16729344, + orchid: 14315734, + palegoldenrod: 15657130, + palegreen: 10025880, + paleturquoise: 11529966, + palevioletred: 14381203, + papayawhip: 16773077, + peachpuff: 16767673, + peru: 13468991, + pink: 16761035, + plum: 14524637, + powderblue: 11591910, + purple: 8388736, + rebeccapurple: 6697881, + red: 16711680, + rosybrown: 12357519, + royalblue: 4286945, + saddlebrown: 9127187, + salmon: 16416882, + sandybrown: 16032864, + seagreen: 3050327, + seashell: 16774638, + sienna: 10506797, + silver: 12632256, + skyblue: 8900331, + slateblue: 6970061, + slategray: 7372944, + slategrey: 7372944, + snow: 16775930, + springgreen: 65407, + steelblue: 4620980, + tan: 13808780, + teal: 32896, + thistle: 14204888, + tomato: 16737095, + turquoise: 4251856, + violet: 15631086, + wheat: 16113331, + white: 16777215, + whitesmoke: 16119285, + yellow: 16776960, + yellowgreen: 10145074 + }); + d3_rgb_names.forEach(function(key, value) { + d3_rgb_names.set(key, d3_rgbNumber(value)); + }); + function d3_functor(v) { + return typeof v === "function" ? v : function() { + return v; + }; + } + d3.functor = d3_functor; + d3.xhr = d3_xhrType(d3_identity); + function d3_xhrType(response) { + return function(url, mimeType, callback) { + if (arguments.length === 2 && typeof mimeType === "function") callback = mimeType, + mimeType = null; + return d3_xhr(url, mimeType, response, callback); + }; + } + function d3_xhr(url, mimeType, response, callback) { + var xhr = {}, dispatch = d3.dispatch("beforesend", "progress", "load", "error"), headers = {}, request = new XMLHttpRequest(), responseType = null; + if (this.XDomainRequest && !("withCredentials" in request) && /^(http(s)?:)?\/\//.test(url)) request = new XDomainRequest(); + "onload" in request ? request.onload = request.onerror = respond : request.onreadystatechange = function() { + request.readyState > 3 && respond(); + }; + function respond() { + var status = request.status, result; + if (!status && d3_xhrHasResponse(request) || status >= 200 && status < 300 || status === 304) { + try { + result = response.call(xhr, request); + } catch (e) { + dispatch.error.call(xhr, e); + return; + } + dispatch.load.call(xhr, result); + } else { + dispatch.error.call(xhr, request); + } + } + request.onprogress = function(event) { + var o = d3.event; + d3.event = event; + try { + dispatch.progress.call(xhr, request); + } finally { + d3.event = o; + } + }; + xhr.header = function(name, value) { + name = (name + "").toLowerCase(); + if (arguments.length < 2) return headers[name]; + if (value == null) delete headers[name]; else headers[name] = value + ""; + return xhr; + }; + xhr.mimeType = function(value) { + if (!arguments.length) return mimeType; + mimeType = value == null ? null : value + ""; + return xhr; + }; + xhr.responseType = function(value) { + if (!arguments.length) return responseType; + responseType = value; + return xhr; + }; + xhr.response = function(value) { + response = value; + return xhr; + }; + [ "get", "post" ].forEach(function(method) { + xhr[method] = function() { + return xhr.send.apply(xhr, [ method ].concat(d3_array(arguments))); + }; + }); + xhr.send = function(method, data, callback) { + if (arguments.length === 2 && typeof data === "function") callback = data, data = null; + request.open(method, url, true); + if (mimeType != null && !("accept" in headers)) headers["accept"] = mimeType + ",*/*"; + if (request.setRequestHeader) for (var name in headers) request.setRequestHeader(name, headers[name]); + if (mimeType != null && request.overrideMimeType) request.overrideMimeType(mimeType); + if (responseType != null) request.responseType = responseType; + if (callback != null) xhr.on("error", callback).on("load", function(request) { + callback(null, request); + }); + dispatch.beforesend.call(xhr, request); + request.send(data == null ? null : data); + return xhr; + }; + xhr.abort = function() { + request.abort(); + return xhr; + }; + d3.rebind(xhr, dispatch, "on"); + return callback == null ? xhr : xhr.get(d3_xhr_fixCallback(callback)); + } + function d3_xhr_fixCallback(callback) { + return callback.length === 1 ? function(error, request) { + callback(error == null ? request : null); + } : callback; + } + function d3_xhrHasResponse(request) { + var type = request.responseType; + return type && type !== "text" ? request.response : request.responseText; + } + d3.dsv = function(delimiter, mimeType) { + var reFormat = new RegExp('["' + delimiter + "\n]"), delimiterCode = delimiter.charCodeAt(0); + function dsv(url, row, callback) { + if (arguments.length < 3) callback = row, row = null; + var xhr = d3_xhr(url, mimeType, row == null ? response : typedResponse(row), callback); + xhr.row = function(_) { + return arguments.length ? xhr.response((row = _) == null ? response : typedResponse(_)) : row; + }; + return xhr; + } + function response(request) { + return dsv.parse(request.responseText); + } + function typedResponse(f) { + return function(request) { + return dsv.parse(request.responseText, f); + }; + } + dsv.parse = function(text, f) { + var o; + return dsv.parseRows(text, function(row, i) { + if (o) return o(row, i - 1); + var a = new Function("d", "return {" + row.map(function(name, i) { + return JSON.stringify(name) + ": d[" + i + "]"; + }).join(",") + "}"); + o = f ? function(row, i) { + return f(a(row), i); + } : a; + }); + }; + dsv.parseRows = function(text, f) { + var EOL = {}, EOF = {}, rows = [], N = text.length, I = 0, n = 0, t, eol; + function token() { + if (I >= N) return EOF; + if (eol) return eol = false, EOL; + var j = I; + if (text.charCodeAt(j) === 34) { + var i = j; + while (i++ < N) { + if (text.charCodeAt(i) === 34) { + if (text.charCodeAt(i + 1) !== 34) break; + ++i; + } + } + I = i + 2; + var c = text.charCodeAt(i + 1); + if (c === 13) { + eol = true; + if (text.charCodeAt(i + 2) === 10) ++I; + } else if (c === 10) { + eol = true; + } + return text.slice(j + 1, i).replace(/""/g, '"'); + } + while (I < N) { + var c = text.charCodeAt(I++), k = 1; + if (c === 10) eol = true; else if (c === 13) { + eol = true; + if (text.charCodeAt(I) === 10) ++I, ++k; + } else if (c !== delimiterCode) continue; + return text.slice(j, I - k); + } + return text.slice(j); + } + while ((t = token()) !== EOF) { + var a = []; + while (t !== EOL && t !== EOF) { + a.push(t); + t = token(); + } + if (f && (a = f(a, n++)) == null) continue; + rows.push(a); + } + return rows; + }; + dsv.format = function(rows) { + if (Array.isArray(rows[0])) return dsv.formatRows(rows); + var fieldSet = new d3_Set(), fields = []; + rows.forEach(function(row) { + for (var field in row) { + if (!fieldSet.has(field)) { + fields.push(fieldSet.add(field)); + } + } + }); + return [ fields.map(formatValue).join(delimiter) ].concat(rows.map(function(row) { + return fields.map(function(field) { + return formatValue(row[field]); + }).join(delimiter); + })).join("\n"); + }; + dsv.formatRows = function(rows) { + return rows.map(formatRow).join("\n"); + }; + function formatRow(row) { + return row.map(formatValue).join(delimiter); + } + function formatValue(text) { + return reFormat.test(text) ? '"' + text.replace(/\"/g, '""') + '"' : text; + } + return dsv; + }; + d3.csv = d3.dsv(",", "text/csv"); + d3.tsv = d3.dsv(" ", "text/tab-separated-values"); + var d3_timer_queueHead, d3_timer_queueTail, d3_timer_interval, d3_timer_timeout, d3_timer_frame = this[d3_vendorSymbol(this, "requestAnimationFrame")] || function(callback) { + setTimeout(callback, 17); + }; + d3.timer = function() { + d3_timer.apply(this, arguments); + }; + function d3_timer(callback, delay, then) { + var n = arguments.length; + if (n < 2) delay = 0; + if (n < 3) then = Date.now(); + var time = then + delay, timer = { + c: callback, + t: time, + n: null + }; + if (d3_timer_queueTail) d3_timer_queueTail.n = timer; else d3_timer_queueHead = timer; + d3_timer_queueTail = timer; + if (!d3_timer_interval) { + d3_timer_timeout = clearTimeout(d3_timer_timeout); + d3_timer_interval = 1; + d3_timer_frame(d3_timer_step); + } + return timer; + } + function d3_timer_step() { + var now = d3_timer_mark(), delay = d3_timer_sweep() - now; + if (delay > 24) { + if (isFinite(delay)) { + clearTimeout(d3_timer_timeout); + d3_timer_timeout = setTimeout(d3_timer_step, delay); + } + d3_timer_interval = 0; + } else { + d3_timer_interval = 1; + d3_timer_frame(d3_timer_step); + } + } + d3.timer.flush = function() { + d3_timer_mark(); + d3_timer_sweep(); + }; + function d3_timer_mark() { + var now = Date.now(), timer = d3_timer_queueHead; + while (timer) { + if (now >= timer.t && timer.c(now - timer.t)) timer.c = null; + timer = timer.n; + } + return now; + } + function d3_timer_sweep() { + var t0, t1 = d3_timer_queueHead, time = Infinity; + while (t1) { + if (t1.c) { + if (t1.t < time) time = t1.t; + t1 = (t0 = t1).n; + } else { + t1 = t0 ? t0.n = t1.n : d3_timer_queueHead = t1.n; + } + } + d3_timer_queueTail = t0; + return time; + } + function d3_format_precision(x, p) { + return p - (x ? Math.ceil(Math.log(x) / Math.LN10) : 1); + } + d3.round = function(x, n) { + return n ? Math.round(x * (n = Math.pow(10, n))) / n : Math.round(x); + }; + var d3_formatPrefixes = [ "y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" ].map(d3_formatPrefix); + d3.formatPrefix = function(value, precision) { + var i = 0; + if (value = +value) { + if (value < 0) value *= -1; + if (precision) value = d3.round(value, d3_format_precision(value, precision)); + i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10); + i = Math.max(-24, Math.min(24, Math.floor((i - 1) / 3) * 3)); + } + return d3_formatPrefixes[8 + i / 3]; + }; + function d3_formatPrefix(d, i) { + var k = Math.pow(10, abs(8 - i) * 3); + return { + scale: i > 8 ? function(d) { + return d / k; + } : function(d) { + return d * k; + }, + symbol: d + }; + } + function d3_locale_numberFormat(locale) { + var locale_decimal = locale.decimal, locale_thousands = locale.thousands, locale_grouping = locale.grouping, locale_currency = locale.currency, formatGroup = locale_grouping && locale_thousands ? function(value, width) { + var i = value.length, t = [], j = 0, g = locale_grouping[0], length = 0; + while (i > 0 && g > 0) { + if (length + g + 1 > width) g = Math.max(1, width - length); + t.push(value.substring(i -= g, i + g)); + if ((length += g + 1) > width) break; + g = locale_grouping[j = (j + 1) % locale_grouping.length]; + } + return t.reverse().join(locale_thousands); + } : d3_identity; + return function(specifier) { + var match = d3_format_re.exec(specifier), fill = match[1] || " ", align = match[2] || ">", sign = match[3] || "-", symbol = match[4] || "", zfill = match[5], width = +match[6], comma = match[7], precision = match[8], type = match[9], scale = 1, prefix = "", suffix = "", integer = false, exponent = true; + if (precision) precision = +precision.substring(1); + if (zfill || fill === "0" && align === "=") { + zfill = fill = "0"; + align = "="; + } + switch (type) { + case "n": + comma = true; + type = "g"; + break; + + case "%": + scale = 100; + suffix = "%"; + type = "f"; + break; + + case "p": + scale = 100; + suffix = "%"; + type = "r"; + break; + + case "b": + case "o": + case "x": + case "X": + if (symbol === "#") prefix = "0" + type.toLowerCase(); + + case "c": + exponent = false; + + case "d": + integer = true; + precision = 0; + break; + + case "s": + scale = -1; + type = "r"; + break; + } + if (symbol === "$") prefix = locale_currency[0], suffix = locale_currency[1]; + if (type == "r" && !precision) type = "g"; + if (precision != null) { + if (type == "g") precision = Math.max(1, Math.min(21, precision)); else if (type == "e" || type == "f") precision = Math.max(0, Math.min(20, precision)); + } + type = d3_format_types.get(type) || d3_format_typeDefault; + var zcomma = zfill && comma; + return function(value) { + var fullSuffix = suffix; + if (integer && value % 1) return ""; + var negative = value < 0 || value === 0 && 1 / value < 0 ? (value = -value, "-") : sign === "-" ? "" : sign; + if (scale < 0) { + var unit = d3.formatPrefix(value, precision); + value = unit.scale(value); + fullSuffix = unit.symbol + suffix; + } else { + value *= scale; + } + value = type(value, precision); + var i = value.lastIndexOf("."), before, after; + if (i < 0) { + var j = exponent ? value.lastIndexOf("e") : -1; + if (j < 0) before = value, after = ""; else before = value.substring(0, j), after = value.substring(j); + } else { + before = value.substring(0, i); + after = locale_decimal + value.substring(i + 1); + } + if (!zfill && comma) before = formatGroup(before, Infinity); + var length = prefix.length + before.length + after.length + (zcomma ? 0 : negative.length), padding = length < width ? new Array(length = width - length + 1).join(fill) : ""; + if (zcomma) before = formatGroup(padding + before, padding.length ? width - after.length : Infinity); + negative += prefix; + value = before + after; + return (align === "<" ? negative + value + padding : align === ">" ? padding + negative + value : align === "^" ? padding.substring(0, length >>= 1) + negative + value + padding.substring(length) : negative + (zcomma ? value : padding + value)) + fullSuffix; + }; + }; + } + var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i; + var d3_format_types = d3.map({ + b: function(x) { + return x.toString(2); + }, + c: function(x) { + return String.fromCharCode(x); + }, + o: function(x) { + return x.toString(8); + }, + x: function(x) { + return x.toString(16); + }, + X: function(x) { + return x.toString(16).toUpperCase(); + }, + g: function(x, p) { + return x.toPrecision(p); + }, + e: function(x, p) { + return x.toExponential(p); + }, + f: function(x, p) { + return x.toFixed(p); + }, + r: function(x, p) { + return (x = d3.round(x, d3_format_precision(x, p))).toFixed(Math.max(0, Math.min(20, d3_format_precision(x * (1 + 1e-15), p)))); + } + }); + function d3_format_typeDefault(x) { + return x + ""; + } + var d3_time = d3.time = {}, d3_date = Date; + function d3_date_utc() { + this._ = new Date(arguments.length > 1 ? Date.UTC.apply(this, arguments) : arguments[0]); + } + d3_date_utc.prototype = { + getDate: function() { + return this._.getUTCDate(); + }, + getDay: function() { + return this._.getUTCDay(); + }, + getFullYear: function() { + return this._.getUTCFullYear(); + }, + getHours: function() { + return this._.getUTCHours(); + }, + getMilliseconds: function() { + return this._.getUTCMilliseconds(); + }, + getMinutes: function() { + return this._.getUTCMinutes(); + }, + getMonth: function() { + return this._.getUTCMonth(); + }, + getSeconds: function() { + return this._.getUTCSeconds(); + }, + getTime: function() { + return this._.getTime(); + }, + getTimezoneOffset: function() { + return 0; + }, + valueOf: function() { + return this._.valueOf(); + }, + setDate: function() { + d3_time_prototype.setUTCDate.apply(this._, arguments); + }, + setDay: function() { + d3_time_prototype.setUTCDay.apply(this._, arguments); + }, + setFullYear: function() { + d3_time_prototype.setUTCFullYear.apply(this._, arguments); + }, + setHours: function() { + d3_time_prototype.setUTCHours.apply(this._, arguments); + }, + setMilliseconds: function() { + d3_time_prototype.setUTCMilliseconds.apply(this._, arguments); + }, + setMinutes: function() { + d3_time_prototype.setUTCMinutes.apply(this._, arguments); + }, + setMonth: function() { + d3_time_prototype.setUTCMonth.apply(this._, arguments); + }, + setSeconds: function() { + d3_time_prototype.setUTCSeconds.apply(this._, arguments); + }, + setTime: function() { + d3_time_prototype.setTime.apply(this._, arguments); + } + }; + var d3_time_prototype = Date.prototype; + function d3_time_interval(local, step, number) { + function round(date) { + var d0 = local(date), d1 = offset(d0, 1); + return date - d0 < d1 - date ? d0 : d1; + } + function ceil(date) { + step(date = local(new d3_date(date - 1)), 1); + return date; + } + function offset(date, k) { + step(date = new d3_date(+date), k); + return date; + } + function range(t0, t1, dt) { + var time = ceil(t0), times = []; + if (dt > 1) { + while (time < t1) { + if (!(number(time) % dt)) times.push(new Date(+time)); + step(time, 1); + } + } else { + while (time < t1) times.push(new Date(+time)), step(time, 1); + } + return times; + } + function range_utc(t0, t1, dt) { + try { + d3_date = d3_date_utc; + var utc = new d3_date_utc(); + utc._ = t0; + return range(utc, t1, dt); + } finally { + d3_date = Date; + } + } + local.floor = local; + local.round = round; + local.ceil = ceil; + local.offset = offset; + local.range = range; + var utc = local.utc = d3_time_interval_utc(local); + utc.floor = utc; + utc.round = d3_time_interval_utc(round); + utc.ceil = d3_time_interval_utc(ceil); + utc.offset = d3_time_interval_utc(offset); + utc.range = range_utc; + return local; + } + function d3_time_interval_utc(method) { + return function(date, k) { + try { + d3_date = d3_date_utc; + var utc = new d3_date_utc(); + utc._ = date; + return method(utc, k)._; + } finally { + d3_date = Date; + } + }; + } + d3_time.year = d3_time_interval(function(date) { + date = d3_time.day(date); + date.setMonth(0, 1); + return date; + }, function(date, offset) { + date.setFullYear(date.getFullYear() + offset); + }, function(date) { + return date.getFullYear(); + }); + d3_time.years = d3_time.year.range; + d3_time.years.utc = d3_time.year.utc.range; + d3_time.day = d3_time_interval(function(date) { + var day = new d3_date(2e3, 0); + day.setFullYear(date.getFullYear(), date.getMonth(), date.getDate()); + return day; + }, function(date, offset) { + date.setDate(date.getDate() + offset); + }, function(date) { + return date.getDate() - 1; + }); + d3_time.days = d3_time.day.range; + d3_time.days.utc = d3_time.day.utc.range; + d3_time.dayOfYear = function(date) { + var year = d3_time.year(date); + return Math.floor((date - year - (date.getTimezoneOffset() - year.getTimezoneOffset()) * 6e4) / 864e5); + }; + [ "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday" ].forEach(function(day, i) { + i = 7 - i; + var interval = d3_time[day] = d3_time_interval(function(date) { + (date = d3_time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7); + return date; + }, function(date, offset) { + date.setDate(date.getDate() + Math.floor(offset) * 7); + }, function(date) { + var day = d3_time.year(date).getDay(); + return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i); + }); + d3_time[day + "s"] = interval.range; + d3_time[day + "s"].utc = interval.utc.range; + d3_time[day + "OfYear"] = function(date) { + var day = d3_time.year(date).getDay(); + return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7); + }; + }); + d3_time.week = d3_time.sunday; + d3_time.weeks = d3_time.sunday.range; + d3_time.weeks.utc = d3_time.sunday.utc.range; + d3_time.weekOfYear = d3_time.sundayOfYear; + function d3_locale_timeFormat(locale) { + var locale_dateTime = locale.dateTime, locale_date = locale.date, locale_time = locale.time, locale_periods = locale.periods, locale_days = locale.days, locale_shortDays = locale.shortDays, locale_months = locale.months, locale_shortMonths = locale.shortMonths; + function d3_time_format(template) { + var n = template.length; + function format(date) { + var string = [], i = -1, j = 0, c, p, f; + while (++i < n) { + if (template.charCodeAt(i) === 37) { + string.push(template.slice(j, i)); + if ((p = d3_time_formatPads[c = template.charAt(++i)]) != null) c = template.charAt(++i); + if (f = d3_time_formats[c]) c = f(date, p == null ? c === "e" ? " " : "0" : p); + string.push(c); + j = i + 1; + } + } + string.push(template.slice(j, i)); + return string.join(""); + } + format.parse = function(string) { + var d = { + y: 1900, + m: 0, + d: 1, + H: 0, + M: 0, + S: 0, + L: 0, + Z: null + }, i = d3_time_parse(d, template, string, 0); + if (i != string.length) return null; + if ("p" in d) d.H = d.H % 12 + d.p * 12; + var localZ = d.Z != null && d3_date !== d3_date_utc, date = new (localZ ? d3_date_utc : d3_date)(); + if ("j" in d) date.setFullYear(d.y, 0, d.j); else if ("W" in d || "U" in d) { + if (!("w" in d)) d.w = "W" in d ? 1 : 0; + date.setFullYear(d.y, 0, 1); + date.setFullYear(d.y, 0, "W" in d ? (d.w + 6) % 7 + d.W * 7 - (date.getDay() + 5) % 7 : d.w + d.U * 7 - (date.getDay() + 6) % 7); + } else date.setFullYear(d.y, d.m, d.d); + date.setHours(d.H + (d.Z / 100 | 0), d.M + d.Z % 100, d.S, d.L); + return localZ ? date._ : date; + }; + format.toString = function() { + return template; + }; + return format; + } + function d3_time_parse(date, template, string, j) { + var c, p, t, i = 0, n = template.length, m = string.length; + while (i < n) { + if (j >= m) return -1; + c = template.charCodeAt(i++); + if (c === 37) { + t = template.charAt(i++); + p = d3_time_parsers[t in d3_time_formatPads ? template.charAt(i++) : t]; + if (!p || (j = p(date, string, j)) < 0) return -1; + } else if (c != string.charCodeAt(j++)) { + return -1; + } + } + return j; + } + d3_time_format.utc = function(template) { + var local = d3_time_format(template); + function format(date) { + try { + d3_date = d3_date_utc; + var utc = new d3_date(); + utc._ = date; + return local(utc); + } finally { + d3_date = Date; + } + } + format.parse = function(string) { + try { + d3_date = d3_date_utc; + var date = local.parse(string); + return date && date._; + } finally { + d3_date = Date; + } + }; + format.toString = local.toString; + return format; + }; + d3_time_format.multi = d3_time_format.utc.multi = d3_time_formatMulti; + var d3_time_periodLookup = d3.map(), d3_time_dayRe = d3_time_formatRe(locale_days), d3_time_dayLookup = d3_time_formatLookup(locale_days), d3_time_dayAbbrevRe = d3_time_formatRe(locale_shortDays), d3_time_dayAbbrevLookup = d3_time_formatLookup(locale_shortDays), d3_time_monthRe = d3_time_formatRe(locale_months), d3_time_monthLookup = d3_time_formatLookup(locale_months), d3_time_monthAbbrevRe = d3_time_formatRe(locale_shortMonths), d3_time_monthAbbrevLookup = d3_time_formatLookup(locale_shortMonths); + locale_periods.forEach(function(p, i) { + d3_time_periodLookup.set(p.toLowerCase(), i); + }); + var d3_time_formats = { + a: function(d) { + return locale_shortDays[d.getDay()]; + }, + A: function(d) { + return locale_days[d.getDay()]; + }, + b: function(d) { + return locale_shortMonths[d.getMonth()]; + }, + B: function(d) { + return locale_months[d.getMonth()]; + }, + c: d3_time_format(locale_dateTime), + d: function(d, p) { + return d3_time_formatPad(d.getDate(), p, 2); + }, + e: function(d, p) { + return d3_time_formatPad(d.getDate(), p, 2); + }, + H: function(d, p) { + return d3_time_formatPad(d.getHours(), p, 2); + }, + I: function(d, p) { + return d3_time_formatPad(d.getHours() % 12 || 12, p, 2); + }, + j: function(d, p) { + return d3_time_formatPad(1 + d3_time.dayOfYear(d), p, 3); + }, + L: function(d, p) { + return d3_time_formatPad(d.getMilliseconds(), p, 3); + }, + m: function(d, p) { + return d3_time_formatPad(d.getMonth() + 1, p, 2); + }, + M: function(d, p) { + return d3_time_formatPad(d.getMinutes(), p, 2); + }, + p: function(d) { + return locale_periods[+(d.getHours() >= 12)]; + }, + S: function(d, p) { + return d3_time_formatPad(d.getSeconds(), p, 2); + }, + U: function(d, p) { + return d3_time_formatPad(d3_time.sundayOfYear(d), p, 2); + }, + w: function(d) { + return d.getDay(); + }, + W: function(d, p) { + return d3_time_formatPad(d3_time.mondayOfYear(d), p, 2); + }, + x: d3_time_format(locale_date), + X: d3_time_format(locale_time), + y: function(d, p) { + return d3_time_formatPad(d.getFullYear() % 100, p, 2); + }, + Y: function(d, p) { + return d3_time_formatPad(d.getFullYear() % 1e4, p, 4); + }, + Z: d3_time_zone, + "%": function() { + return "%"; + } + }; + var d3_time_parsers = { + a: d3_time_parseWeekdayAbbrev, + A: d3_time_parseWeekday, + b: d3_time_parseMonthAbbrev, + B: d3_time_parseMonth, + c: d3_time_parseLocaleFull, + d: d3_time_parseDay, + e: d3_time_parseDay, + H: d3_time_parseHour24, + I: d3_time_parseHour24, + j: d3_time_parseDayOfYear, + L: d3_time_parseMilliseconds, + m: d3_time_parseMonthNumber, + M: d3_time_parseMinutes, + p: d3_time_parseAmPm, + S: d3_time_parseSeconds, + U: d3_time_parseWeekNumberSunday, + w: d3_time_parseWeekdayNumber, + W: d3_time_parseWeekNumberMonday, + x: d3_time_parseLocaleDate, + X: d3_time_parseLocaleTime, + y: d3_time_parseYear, + Y: d3_time_parseFullYear, + Z: d3_time_parseZone, + "%": d3_time_parseLiteralPercent + }; + function d3_time_parseWeekdayAbbrev(date, string, i) { + d3_time_dayAbbrevRe.lastIndex = 0; + var n = d3_time_dayAbbrevRe.exec(string.slice(i)); + return n ? (date.w = d3_time_dayAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseWeekday(date, string, i) { + d3_time_dayRe.lastIndex = 0; + var n = d3_time_dayRe.exec(string.slice(i)); + return n ? (date.w = d3_time_dayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseMonthAbbrev(date, string, i) { + d3_time_monthAbbrevRe.lastIndex = 0; + var n = d3_time_monthAbbrevRe.exec(string.slice(i)); + return n ? (date.m = d3_time_monthAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseMonth(date, string, i) { + d3_time_monthRe.lastIndex = 0; + var n = d3_time_monthRe.exec(string.slice(i)); + return n ? (date.m = d3_time_monthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseLocaleFull(date, string, i) { + return d3_time_parse(date, d3_time_formats.c.toString(), string, i); + } + function d3_time_parseLocaleDate(date, string, i) { + return d3_time_parse(date, d3_time_formats.x.toString(), string, i); + } + function d3_time_parseLocaleTime(date, string, i) { + return d3_time_parse(date, d3_time_formats.X.toString(), string, i); + } + function d3_time_parseAmPm(date, string, i) { + var n = d3_time_periodLookup.get(string.slice(i, i += 2).toLowerCase()); + return n == null ? -1 : (date.p = n, i); + } + return d3_time_format; + } + var d3_time_formatPads = { + "-": "", + _: " ", + "0": "0" + }, d3_time_numberRe = /^\s*\d+/, d3_time_percentRe = /^%/; + function d3_time_formatPad(value, fill, width) { + var sign = value < 0 ? "-" : "", string = (sign ? -value : value) + "", length = string.length; + return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string); + } + function d3_time_formatRe(names) { + return new RegExp("^(?:" + names.map(d3.requote).join("|") + ")", "i"); + } + function d3_time_formatLookup(names) { + var map = new d3_Map(), i = -1, n = names.length; + while (++i < n) map.set(names[i].toLowerCase(), i); + return map; + } + function d3_time_parseWeekdayNumber(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 1)); + return n ? (date.w = +n[0], i + n[0].length) : -1; + } + function d3_time_parseWeekNumberSunday(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i)); + return n ? (date.U = +n[0], i + n[0].length) : -1; + } + function d3_time_parseWeekNumberMonday(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i)); + return n ? (date.W = +n[0], i + n[0].length) : -1; + } + function d3_time_parseFullYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 4)); + return n ? (date.y = +n[0], i + n[0].length) : -1; + } + function d3_time_parseYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.y = d3_time_expandYear(+n[0]), i + n[0].length) : -1; + } + function d3_time_parseZone(date, string, i) { + return /^[+-]\d{4}$/.test(string = string.slice(i, i + 5)) ? (date.Z = -string, + i + 5) : -1; + } + function d3_time_expandYear(d) { + return d + (d > 68 ? 1900 : 2e3); + } + function d3_time_parseMonthNumber(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.m = n[0] - 1, i + n[0].length) : -1; + } + function d3_time_parseDay(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.d = +n[0], i + n[0].length) : -1; + } + function d3_time_parseDayOfYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 3)); + return n ? (date.j = +n[0], i + n[0].length) : -1; + } + function d3_time_parseHour24(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.H = +n[0], i + n[0].length) : -1; + } + function d3_time_parseMinutes(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.M = +n[0], i + n[0].length) : -1; + } + function d3_time_parseSeconds(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.S = +n[0], i + n[0].length) : -1; + } + function d3_time_parseMilliseconds(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 3)); + return n ? (date.L = +n[0], i + n[0].length) : -1; + } + function d3_time_zone(d) { + var z = d.getTimezoneOffset(), zs = z > 0 ? "-" : "+", zh = abs(z) / 60 | 0, zm = abs(z) % 60; + return zs + d3_time_formatPad(zh, "0", 2) + d3_time_formatPad(zm, "0", 2); + } + function d3_time_parseLiteralPercent(date, string, i) { + d3_time_percentRe.lastIndex = 0; + var n = d3_time_percentRe.exec(string.slice(i, i + 1)); + return n ? i + n[0].length : -1; + } + function d3_time_formatMulti(formats) { + var n = formats.length, i = -1; + while (++i < n) formats[i][0] = this(formats[i][0]); + return function(date) { + var i = 0, f = formats[i]; + while (!f[1](date)) f = formats[++i]; + return f[0](date); + }; + } + d3.locale = function(locale) { + return { + numberFormat: d3_locale_numberFormat(locale), + timeFormat: d3_locale_timeFormat(locale) + }; + }; + var d3_locale_enUS = d3.locale({ + decimal: ".", + thousands: ",", + grouping: [ 3 ], + currency: [ "$", "" ], + dateTime: "%a %b %e %X %Y", + date: "%m/%d/%Y", + time: "%H:%M:%S", + periods: [ "AM", "PM" ], + days: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], + shortDays: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], + months: [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ], + shortMonths: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ] + }); + d3.format = d3_locale_enUS.numberFormat; + d3.geo = {}; + function d3_adder() {} + d3_adder.prototype = { + s: 0, + t: 0, + add: function(y) { + d3_adderSum(y, this.t, d3_adderTemp); + d3_adderSum(d3_adderTemp.s, this.s, this); + if (this.s) this.t += d3_adderTemp.t; else this.s = d3_adderTemp.t; + }, + reset: function() { + this.s = this.t = 0; + }, + valueOf: function() { + return this.s; + } + }; + var d3_adderTemp = new d3_adder(); + function d3_adderSum(a, b, o) { + var x = o.s = a + b, bv = x - a, av = x - bv; + o.t = a - av + (b - bv); + } + d3.geo.stream = function(object, listener) { + if (object && d3_geo_streamObjectType.hasOwnProperty(object.type)) { + d3_geo_streamObjectType[object.type](object, listener); + } else { + d3_geo_streamGeometry(object, listener); + } + }; + function d3_geo_streamGeometry(geometry, listener) { + if (geometry && d3_geo_streamGeometryType.hasOwnProperty(geometry.type)) { + d3_geo_streamGeometryType[geometry.type](geometry, listener); + } + } + var d3_geo_streamObjectType = { + Feature: function(feature, listener) { + d3_geo_streamGeometry(feature.geometry, listener); + }, + FeatureCollection: function(object, listener) { + var features = object.features, i = -1, n = features.length; + while (++i < n) d3_geo_streamGeometry(features[i].geometry, listener); + } + }; + var d3_geo_streamGeometryType = { + Sphere: function(object, listener) { + listener.sphere(); + }, + Point: function(object, listener) { + object = object.coordinates; + listener.point(object[0], object[1], object[2]); + }, + MultiPoint: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) object = coordinates[i], listener.point(object[0], object[1], object[2]); + }, + LineString: function(object, listener) { + d3_geo_streamLine(object.coordinates, listener, 0); + }, + MultiLineString: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) d3_geo_streamLine(coordinates[i], listener, 0); + }, + Polygon: function(object, listener) { + d3_geo_streamPolygon(object.coordinates, listener); + }, + MultiPolygon: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) d3_geo_streamPolygon(coordinates[i], listener); + }, + GeometryCollection: function(object, listener) { + var geometries = object.geometries, i = -1, n = geometries.length; + while (++i < n) d3_geo_streamGeometry(geometries[i], listener); + } + }; + function d3_geo_streamLine(coordinates, listener, closed) { + var i = -1, n = coordinates.length - closed, coordinate; + listener.lineStart(); + while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1], coordinate[2]); + listener.lineEnd(); + } + function d3_geo_streamPolygon(coordinates, listener) { + var i = -1, n = coordinates.length; + listener.polygonStart(); + while (++i < n) d3_geo_streamLine(coordinates[i], listener, 1); + listener.polygonEnd(); + } + d3.geo.area = function(object) { + d3_geo_areaSum = 0; + d3.geo.stream(object, d3_geo_area); + return d3_geo_areaSum; + }; + var d3_geo_areaSum, d3_geo_areaRingSum = new d3_adder(); + var d3_geo_area = { + sphere: function() { + d3_geo_areaSum += 4 * π; + }, + point: d3_noop, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + d3_geo_areaRingSum.reset(); + d3_geo_area.lineStart = d3_geo_areaRingStart; + }, + polygonEnd: function() { + var area = 2 * d3_geo_areaRingSum; + d3_geo_areaSum += area < 0 ? 4 * π + area : area; + d3_geo_area.lineStart = d3_geo_area.lineEnd = d3_geo_area.point = d3_noop; + } + }; + function d3_geo_areaRingStart() { + var λ00, φ00, λ0, cosφ0, sinφ0; + d3_geo_area.point = function(λ, φ) { + d3_geo_area.point = nextPoint; + λ0 = (λ00 = λ) * d3_radians, cosφ0 = Math.cos(φ = (φ00 = φ) * d3_radians / 2 + π / 4), + sinφ0 = Math.sin(φ); + }; + function nextPoint(λ, φ) { + λ *= d3_radians; + φ = φ * d3_radians / 2 + π / 4; + var dλ = λ - λ0, sdλ = dλ >= 0 ? 1 : -1, adλ = sdλ * dλ, cosφ = Math.cos(φ), sinφ = Math.sin(φ), k = sinφ0 * sinφ, u = cosφ0 * cosφ + k * Math.cos(adλ), v = k * sdλ * Math.sin(adλ); + d3_geo_areaRingSum.add(Math.atan2(v, u)); + λ0 = λ, cosφ0 = cosφ, sinφ0 = sinφ; + } + d3_geo_area.lineEnd = function() { + nextPoint(λ00, φ00); + }; + } + function d3_geo_cartesian(spherical) { + var λ = spherical[0], φ = spherical[1], cosφ = Math.cos(φ); + return [ cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ) ]; + } + function d3_geo_cartesianDot(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + } + function d3_geo_cartesianCross(a, b) { + return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ]; + } + function d3_geo_cartesianAdd(a, b) { + a[0] += b[0]; + a[1] += b[1]; + a[2] += b[2]; + } + function d3_geo_cartesianScale(vector, k) { + return [ vector[0] * k, vector[1] * k, vector[2] * k ]; + } + function d3_geo_cartesianNormalize(d) { + var l = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); + d[0] /= l; + d[1] /= l; + d[2] /= l; + } + function d3_geo_spherical(cartesian) { + return [ Math.atan2(cartesian[1], cartesian[0]), d3_asin(cartesian[2]) ]; + } + function d3_geo_sphericalEqual(a, b) { + return abs(a[0] - b[0]) < ε && abs(a[1] - b[1]) < ε; + } + d3.geo.bounds = function() { + var λ0, φ0, λ1, φ1, λ_, λ__, φ__, p0, dλSum, ranges, range; + var bound = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + bound.point = ringPoint; + bound.lineStart = ringStart; + bound.lineEnd = ringEnd; + dλSum = 0; + d3_geo_area.polygonStart(); + }, + polygonEnd: function() { + d3_geo_area.polygonEnd(); + bound.point = point; + bound.lineStart = lineStart; + bound.lineEnd = lineEnd; + if (d3_geo_areaRingSum < 0) λ0 = -(λ1 = 180), φ0 = -(φ1 = 90); else if (dλSum > ε) φ1 = 90; else if (dλSum < -ε) φ0 = -90; + range[0] = λ0, range[1] = λ1; + } + }; + function point(λ, φ) { + ranges.push(range = [ λ0 = λ, λ1 = λ ]); + if (φ < φ0) φ0 = φ; + if (φ > φ1) φ1 = φ; + } + function linePoint(λ, φ) { + var p = d3_geo_cartesian([ λ * d3_radians, φ * d3_radians ]); + if (p0) { + var normal = d3_geo_cartesianCross(p0, p), equatorial = [ normal[1], -normal[0], 0 ], inflection = d3_geo_cartesianCross(equatorial, normal); + d3_geo_cartesianNormalize(inflection); + inflection = d3_geo_spherical(inflection); + var dλ = λ - λ_, s = dλ > 0 ? 1 : -1, λi = inflection[0] * d3_degrees * s, antimeridian = abs(dλ) > 180; + if (antimeridian ^ (s * λ_ < λi && λi < s * λ)) { + var φi = inflection[1] * d3_degrees; + if (φi > φ1) φ1 = φi; + } else if (λi = (λi + 360) % 360 - 180, antimeridian ^ (s * λ_ < λi && λi < s * λ)) { + var φi = -inflection[1] * d3_degrees; + if (φi < φ0) φ0 = φi; + } else { + if (φ < φ0) φ0 = φ; + if (φ > φ1) φ1 = φ; + } + if (antimeridian) { + if (λ < λ_) { + if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ; + } else { + if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ; + } + } else { + if (λ1 >= λ0) { + if (λ < λ0) λ0 = λ; + if (λ > λ1) λ1 = λ; + } else { + if (λ > λ_) { + if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ; + } else { + if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ; + } + } + } + } else { + point(λ, φ); + } + p0 = p, λ_ = λ; + } + function lineStart() { + bound.point = linePoint; + } + function lineEnd() { + range[0] = λ0, range[1] = λ1; + bound.point = point; + p0 = null; + } + function ringPoint(λ, φ) { + if (p0) { + var dλ = λ - λ_; + dλSum += abs(dλ) > 180 ? dλ + (dλ > 0 ? 360 : -360) : dλ; + } else λ__ = λ, φ__ = φ; + d3_geo_area.point(λ, φ); + linePoint(λ, φ); + } + function ringStart() { + d3_geo_area.lineStart(); + } + function ringEnd() { + ringPoint(λ__, φ__); + d3_geo_area.lineEnd(); + if (abs(dλSum) > ε) λ0 = -(λ1 = 180); + range[0] = λ0, range[1] = λ1; + p0 = null; + } + function angle(λ0, λ1) { + return (λ1 -= λ0) < 0 ? λ1 + 360 : λ1; + } + function compareRanges(a, b) { + return a[0] - b[0]; + } + function withinRange(x, range) { + return range[0] <= range[1] ? range[0] <= x && x <= range[1] : x < range[0] || range[1] < x; + } + return function(feature) { + φ1 = λ1 = -(λ0 = φ0 = Infinity); + ranges = []; + d3.geo.stream(feature, bound); + var n = ranges.length; + if (n) { + ranges.sort(compareRanges); + for (var i = 1, a = ranges[0], b, merged = [ a ]; i < n; ++i) { + b = ranges[i]; + if (withinRange(b[0], a) || withinRange(b[1], a)) { + if (angle(a[0], b[1]) > angle(a[0], a[1])) a[1] = b[1]; + if (angle(b[0], a[1]) > angle(a[0], a[1])) a[0] = b[0]; + } else { + merged.push(a = b); + } + } + var best = -Infinity, dλ; + for (var n = merged.length - 1, i = 0, a = merged[n], b; i <= n; a = b, ++i) { + b = merged[i]; + if ((dλ = angle(a[1], b[0])) > best) best = dλ, λ0 = b[0], λ1 = a[1]; + } + } + ranges = range = null; + return λ0 === Infinity || φ0 === Infinity ? [ [ NaN, NaN ], [ NaN, NaN ] ] : [ [ λ0, φ0 ], [ λ1, φ1 ] ]; + }; + }(); + d3.geo.centroid = function(object) { + d3_geo_centroidW0 = d3_geo_centroidW1 = d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0; + d3.geo.stream(object, d3_geo_centroid); + var x = d3_geo_centroidX2, y = d3_geo_centroidY2, z = d3_geo_centroidZ2, m = x * x + y * y + z * z; + if (m < ε2) { + x = d3_geo_centroidX1, y = d3_geo_centroidY1, z = d3_geo_centroidZ1; + if (d3_geo_centroidW1 < ε) x = d3_geo_centroidX0, y = d3_geo_centroidY0, z = d3_geo_centroidZ0; + m = x * x + y * y + z * z; + if (m < ε2) return [ NaN, NaN ]; + } + return [ Math.atan2(y, x) * d3_degrees, d3_asin(z / Math.sqrt(m)) * d3_degrees ]; + }; + var d3_geo_centroidW0, d3_geo_centroidW1, d3_geo_centroidX0, d3_geo_centroidY0, d3_geo_centroidZ0, d3_geo_centroidX1, d3_geo_centroidY1, d3_geo_centroidZ1, d3_geo_centroidX2, d3_geo_centroidY2, d3_geo_centroidZ2; + var d3_geo_centroid = { + sphere: d3_noop, + point: d3_geo_centroidPoint, + lineStart: d3_geo_centroidLineStart, + lineEnd: d3_geo_centroidLineEnd, + polygonStart: function() { + d3_geo_centroid.lineStart = d3_geo_centroidRingStart; + }, + polygonEnd: function() { + d3_geo_centroid.lineStart = d3_geo_centroidLineStart; + } + }; + function d3_geo_centroidPoint(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + d3_geo_centroidPointXYZ(cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ)); + } + function d3_geo_centroidPointXYZ(x, y, z) { + ++d3_geo_centroidW0; + d3_geo_centroidX0 += (x - d3_geo_centroidX0) / d3_geo_centroidW0; + d3_geo_centroidY0 += (y - d3_geo_centroidY0) / d3_geo_centroidW0; + d3_geo_centroidZ0 += (z - d3_geo_centroidZ0) / d3_geo_centroidW0; + } + function d3_geo_centroidLineStart() { + var x0, y0, z0; + d3_geo_centroid.point = function(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + x0 = cosφ * Math.cos(λ); + y0 = cosφ * Math.sin(λ); + z0 = Math.sin(φ); + d3_geo_centroid.point = nextPoint; + d3_geo_centroidPointXYZ(x0, y0, z0); + }; + function nextPoint(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), w = Math.atan2(Math.sqrt((w = y0 * z - z0 * y) * w + (w = z0 * x - x0 * z) * w + (w = x0 * y - y0 * x) * w), x0 * x + y0 * y + z0 * z); + d3_geo_centroidW1 += w; + d3_geo_centroidX1 += w * (x0 + (x0 = x)); + d3_geo_centroidY1 += w * (y0 + (y0 = y)); + d3_geo_centroidZ1 += w * (z0 + (z0 = z)); + d3_geo_centroidPointXYZ(x0, y0, z0); + } + } + function d3_geo_centroidLineEnd() { + d3_geo_centroid.point = d3_geo_centroidPoint; + } + function d3_geo_centroidRingStart() { + var λ00, φ00, x0, y0, z0; + d3_geo_centroid.point = function(λ, φ) { + λ00 = λ, φ00 = φ; + d3_geo_centroid.point = nextPoint; + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + x0 = cosφ * Math.cos(λ); + y0 = cosφ * Math.sin(λ); + z0 = Math.sin(φ); + d3_geo_centroidPointXYZ(x0, y0, z0); + }; + d3_geo_centroid.lineEnd = function() { + nextPoint(λ00, φ00); + d3_geo_centroid.lineEnd = d3_geo_centroidLineEnd; + d3_geo_centroid.point = d3_geo_centroidPoint; + }; + function nextPoint(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), cx = y0 * z - z0 * y, cy = z0 * x - x0 * z, cz = x0 * y - y0 * x, m = Math.sqrt(cx * cx + cy * cy + cz * cz), u = x0 * x + y0 * y + z0 * z, v = m && -d3_acos(u) / m, w = Math.atan2(m, u); + d3_geo_centroidX2 += v * cx; + d3_geo_centroidY2 += v * cy; + d3_geo_centroidZ2 += v * cz; + d3_geo_centroidW1 += w; + d3_geo_centroidX1 += w * (x0 + (x0 = x)); + d3_geo_centroidY1 += w * (y0 + (y0 = y)); + d3_geo_centroidZ1 += w * (z0 + (z0 = z)); + d3_geo_centroidPointXYZ(x0, y0, z0); + } + } + function d3_geo_compose(a, b) { + function compose(x, y) { + return x = a(x, y), b(x[0], x[1]); + } + if (a.invert && b.invert) compose.invert = function(x, y) { + return x = b.invert(x, y), x && a.invert(x[0], x[1]); + }; + return compose; + } + function d3_true() { + return true; + } + function d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener) { + var subject = [], clip = []; + segments.forEach(function(segment) { + if ((n = segment.length - 1) <= 0) return; + var n, p0 = segment[0], p1 = segment[n]; + if (d3_geo_sphericalEqual(p0, p1)) { + listener.lineStart(); + for (var i = 0; i < n; ++i) listener.point((p0 = segment[i])[0], p0[1]); + listener.lineEnd(); + return; + } + var a = new d3_geo_clipPolygonIntersection(p0, segment, null, true), b = new d3_geo_clipPolygonIntersection(p0, null, a, false); + a.o = b; + subject.push(a); + clip.push(b); + a = new d3_geo_clipPolygonIntersection(p1, segment, null, false); + b = new d3_geo_clipPolygonIntersection(p1, null, a, true); + a.o = b; + subject.push(a); + clip.push(b); + }); + clip.sort(compare); + d3_geo_clipPolygonLinkCircular(subject); + d3_geo_clipPolygonLinkCircular(clip); + if (!subject.length) return; + for (var i = 0, entry = clipStartInside, n = clip.length; i < n; ++i) { + clip[i].e = entry = !entry; + } + var start = subject[0], points, point; + while (1) { + var current = start, isSubject = true; + while (current.v) if ((current = current.n) === start) return; + points = current.z; + listener.lineStart(); + do { + current.v = current.o.v = true; + if (current.e) { + if (isSubject) { + for (var i = 0, n = points.length; i < n; ++i) listener.point((point = points[i])[0], point[1]); + } else { + interpolate(current.x, current.n.x, 1, listener); + } + current = current.n; + } else { + if (isSubject) { + points = current.p.z; + for (var i = points.length - 1; i >= 0; --i) listener.point((point = points[i])[0], point[1]); + } else { + interpolate(current.x, current.p.x, -1, listener); + } + current = current.p; + } + current = current.o; + points = current.z; + isSubject = !isSubject; + } while (!current.v); + listener.lineEnd(); + } + } + function d3_geo_clipPolygonLinkCircular(array) { + if (!(n = array.length)) return; + var n, i = 0, a = array[0], b; + while (++i < n) { + a.n = b = array[i]; + b.p = a; + a = b; + } + a.n = b = array[0]; + b.p = a; + } + function d3_geo_clipPolygonIntersection(point, points, other, entry) { + this.x = point; + this.z = points; + this.o = other; + this.e = entry; + this.v = false; + this.n = this.p = null; + } + function d3_geo_clip(pointVisible, clipLine, interpolate, clipStart) { + return function(rotate, listener) { + var line = clipLine(listener), rotatedClipStart = rotate.invert(clipStart[0], clipStart[1]); + var clip = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + clip.point = pointRing; + clip.lineStart = ringStart; + clip.lineEnd = ringEnd; + segments = []; + polygon = []; + }, + polygonEnd: function() { + clip.point = point; + clip.lineStart = lineStart; + clip.lineEnd = lineEnd; + segments = d3.merge(segments); + var clipStartInside = d3_geo_pointInPolygon(rotatedClipStart, polygon); + if (segments.length) { + if (!polygonStarted) listener.polygonStart(), polygonStarted = true; + d3_geo_clipPolygon(segments, d3_geo_clipSort, clipStartInside, interpolate, listener); + } else if (clipStartInside) { + if (!polygonStarted) listener.polygonStart(), polygonStarted = true; + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + } + if (polygonStarted) listener.polygonEnd(), polygonStarted = false; + segments = polygon = null; + }, + sphere: function() { + listener.polygonStart(); + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + listener.polygonEnd(); + } + }; + function point(λ, φ) { + var point = rotate(λ, φ); + if (pointVisible(λ = point[0], φ = point[1])) listener.point(λ, φ); + } + function pointLine(λ, φ) { + var point = rotate(λ, φ); + line.point(point[0], point[1]); + } + function lineStart() { + clip.point = pointLine; + line.lineStart(); + } + function lineEnd() { + clip.point = point; + line.lineEnd(); + } + var segments; + var buffer = d3_geo_clipBufferListener(), ringListener = clipLine(buffer), polygonStarted = false, polygon, ring; + function pointRing(λ, φ) { + ring.push([ λ, φ ]); + var point = rotate(λ, φ); + ringListener.point(point[0], point[1]); + } + function ringStart() { + ringListener.lineStart(); + ring = []; + } + function ringEnd() { + pointRing(ring[0][0], ring[0][1]); + ringListener.lineEnd(); + var clean = ringListener.clean(), ringSegments = buffer.buffer(), segment, n = ringSegments.length; + ring.pop(); + polygon.push(ring); + ring = null; + if (!n) return; + if (clean & 1) { + segment = ringSegments[0]; + var n = segment.length - 1, i = -1, point; + if (n > 0) { + if (!polygonStarted) listener.polygonStart(), polygonStarted = true; + listener.lineStart(); + while (++i < n) listener.point((point = segment[i])[0], point[1]); + listener.lineEnd(); + } + return; + } + if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift())); + segments.push(ringSegments.filter(d3_geo_clipSegmentLength1)); + } + return clip; + }; + } + function d3_geo_clipSegmentLength1(segment) { + return segment.length > 1; + } + function d3_geo_clipBufferListener() { + var lines = [], line; + return { + lineStart: function() { + lines.push(line = []); + }, + point: function(λ, φ) { + line.push([ λ, φ ]); + }, + lineEnd: d3_noop, + buffer: function() { + var buffer = lines; + lines = []; + line = null; + return buffer; + }, + rejoin: function() { + if (lines.length > 1) lines.push(lines.pop().concat(lines.shift())); + } + }; + } + function d3_geo_clipSort(a, b) { + return ((a = a.x)[0] < 0 ? a[1] - halfπ - ε : halfπ - a[1]) - ((b = b.x)[0] < 0 ? b[1] - halfπ - ε : halfπ - b[1]); + } + var d3_geo_clipAntimeridian = d3_geo_clip(d3_true, d3_geo_clipAntimeridianLine, d3_geo_clipAntimeridianInterpolate, [ -π, -π / 2 ]); + function d3_geo_clipAntimeridianLine(listener) { + var λ0 = NaN, φ0 = NaN, sλ0 = NaN, clean; + return { + lineStart: function() { + listener.lineStart(); + clean = 1; + }, + point: function(λ1, φ1) { + var sλ1 = λ1 > 0 ? π : -π, dλ = abs(λ1 - λ0); + if (abs(dλ - π) < ε) { + listener.point(λ0, φ0 = (φ0 + φ1) / 2 > 0 ? halfπ : -halfπ); + listener.point(sλ0, φ0); + listener.lineEnd(); + listener.lineStart(); + listener.point(sλ1, φ0); + listener.point(λ1, φ0); + clean = 0; + } else if (sλ0 !== sλ1 && dλ >= π) { + if (abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε; + if (abs(λ1 - sλ1) < ε) λ1 -= sλ1 * ε; + φ0 = d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1); + listener.point(sλ0, φ0); + listener.lineEnd(); + listener.lineStart(); + listener.point(sλ1, φ0); + clean = 0; + } + listener.point(λ0 = λ1, φ0 = φ1); + sλ0 = sλ1; + }, + lineEnd: function() { + listener.lineEnd(); + λ0 = φ0 = NaN; + }, + clean: function() { + return 2 - clean; + } + }; + } + function d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1) { + var cosφ0, cosφ1, sinλ0_λ1 = Math.sin(λ0 - λ1); + return abs(sinλ0_λ1) > ε ? Math.atan((Math.sin(φ0) * (cosφ1 = Math.cos(φ1)) * Math.sin(λ1) - Math.sin(φ1) * (cosφ0 = Math.cos(φ0)) * Math.sin(λ0)) / (cosφ0 * cosφ1 * sinλ0_λ1)) : (φ0 + φ1) / 2; + } + function d3_geo_clipAntimeridianInterpolate(from, to, direction, listener) { + var φ; + if (from == null) { + φ = direction * halfπ; + listener.point(-π, φ); + listener.point(0, φ); + listener.point(π, φ); + listener.point(π, 0); + listener.point(π, -φ); + listener.point(0, -φ); + listener.point(-π, -φ); + listener.point(-π, 0); + listener.point(-π, φ); + } else if (abs(from[0] - to[0]) > ε) { + var s = from[0] < to[0] ? π : -π; + φ = direction * s / 2; + listener.point(-s, φ); + listener.point(0, φ); + listener.point(s, φ); + } else { + listener.point(to[0], to[1]); + } + } + function d3_geo_pointInPolygon(point, polygon) { + var meridian = point[0], parallel = point[1], meridianNormal = [ Math.sin(meridian), -Math.cos(meridian), 0 ], polarAngle = 0, winding = 0; + d3_geo_areaRingSum.reset(); + for (var i = 0, n = polygon.length; i < n; ++i) { + var ring = polygon[i], m = ring.length; + if (!m) continue; + var point0 = ring[0], λ0 = point0[0], φ0 = point0[1] / 2 + π / 4, sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), j = 1; + while (true) { + if (j === m) j = 0; + point = ring[j]; + var λ = point[0], φ = point[1] / 2 + π / 4, sinφ = Math.sin(φ), cosφ = Math.cos(φ), dλ = λ - λ0, sdλ = dλ >= 0 ? 1 : -1, adλ = sdλ * dλ, antimeridian = adλ > π, k = sinφ0 * sinφ; + d3_geo_areaRingSum.add(Math.atan2(k * sdλ * Math.sin(adλ), cosφ0 * cosφ + k * Math.cos(adλ))); + polarAngle += antimeridian ? dλ + sdλ * τ : dλ; + if (antimeridian ^ λ0 >= meridian ^ λ >= meridian) { + var arc = d3_geo_cartesianCross(d3_geo_cartesian(point0), d3_geo_cartesian(point)); + d3_geo_cartesianNormalize(arc); + var intersection = d3_geo_cartesianCross(meridianNormal, arc); + d3_geo_cartesianNormalize(intersection); + var φarc = (antimeridian ^ dλ >= 0 ? -1 : 1) * d3_asin(intersection[2]); + if (parallel > φarc || parallel === φarc && (arc[0] || arc[1])) { + winding += antimeridian ^ dλ >= 0 ? 1 : -1; + } + } + if (!j++) break; + λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ, point0 = point; + } + } + return (polarAngle < -ε || polarAngle < ε && d3_geo_areaRingSum < -ε) ^ winding & 1; + } + function d3_geo_clipCircle(radius) { + var cr = Math.cos(radius), smallRadius = cr > 0, notHemisphere = abs(cr) > ε, interpolate = d3_geo_circleInterpolate(radius, 6 * d3_radians); + return d3_geo_clip(visible, clipLine, interpolate, smallRadius ? [ 0, -radius ] : [ -π, radius - π ]); + function visible(λ, φ) { + return Math.cos(λ) * Math.cos(φ) > cr; + } + function clipLine(listener) { + var point0, c0, v0, v00, clean; + return { + lineStart: function() { + v00 = v0 = false; + clean = 1; + }, + point: function(λ, φ) { + var point1 = [ λ, φ ], point2, v = visible(λ, φ), c = smallRadius ? v ? 0 : code(λ, φ) : v ? code(λ + (λ < 0 ? π : -π), φ) : 0; + if (!point0 && (v00 = v0 = v)) listener.lineStart(); + if (v !== v0) { + point2 = intersect(point0, point1); + if (d3_geo_sphericalEqual(point0, point2) || d3_geo_sphericalEqual(point1, point2)) { + point1[0] += ε; + point1[1] += ε; + v = visible(point1[0], point1[1]); + } + } + if (v !== v0) { + clean = 0; + if (v) { + listener.lineStart(); + point2 = intersect(point1, point0); + listener.point(point2[0], point2[1]); + } else { + point2 = intersect(point0, point1); + listener.point(point2[0], point2[1]); + listener.lineEnd(); + } + point0 = point2; + } else if (notHemisphere && point0 && smallRadius ^ v) { + var t; + if (!(c & c0) && (t = intersect(point1, point0, true))) { + clean = 0; + if (smallRadius) { + listener.lineStart(); + listener.point(t[0][0], t[0][1]); + listener.point(t[1][0], t[1][1]); + listener.lineEnd(); + } else { + listener.point(t[1][0], t[1][1]); + listener.lineEnd(); + listener.lineStart(); + listener.point(t[0][0], t[0][1]); + } + } + } + if (v && (!point0 || !d3_geo_sphericalEqual(point0, point1))) { + listener.point(point1[0], point1[1]); + } + point0 = point1, v0 = v, c0 = c; + }, + lineEnd: function() { + if (v0) listener.lineEnd(); + point0 = null; + }, + clean: function() { + return clean | (v00 && v0) << 1; + } + }; + } + function intersect(a, b, two) { + var pa = d3_geo_cartesian(a), pb = d3_geo_cartesian(b); + var n1 = [ 1, 0, 0 ], n2 = d3_geo_cartesianCross(pa, pb), n2n2 = d3_geo_cartesianDot(n2, n2), n1n2 = n2[0], determinant = n2n2 - n1n2 * n1n2; + if (!determinant) return !two && a; + var c1 = cr * n2n2 / determinant, c2 = -cr * n1n2 / determinant, n1xn2 = d3_geo_cartesianCross(n1, n2), A = d3_geo_cartesianScale(n1, c1), B = d3_geo_cartesianScale(n2, c2); + d3_geo_cartesianAdd(A, B); + var u = n1xn2, w = d3_geo_cartesianDot(A, u), uu = d3_geo_cartesianDot(u, u), t2 = w * w - uu * (d3_geo_cartesianDot(A, A) - 1); + if (t2 < 0) return; + var t = Math.sqrt(t2), q = d3_geo_cartesianScale(u, (-w - t) / uu); + d3_geo_cartesianAdd(q, A); + q = d3_geo_spherical(q); + if (!two) return q; + var λ0 = a[0], λ1 = b[0], φ0 = a[1], φ1 = b[1], z; + if (λ1 < λ0) z = λ0, λ0 = λ1, λ1 = z; + var δλ = λ1 - λ0, polar = abs(δλ - π) < ε, meridian = polar || δλ < ε; + if (!polar && φ1 < φ0) z = φ0, φ0 = φ1, φ1 = z; + if (meridian ? polar ? φ0 + φ1 > 0 ^ q[1] < (abs(q[0] - λ0) < ε ? φ0 : φ1) : φ0 <= q[1] && q[1] <= φ1 : δλ > π ^ (λ0 <= q[0] && q[0] <= λ1)) { + var q1 = d3_geo_cartesianScale(u, (-w + t) / uu); + d3_geo_cartesianAdd(q1, A); + return [ q, d3_geo_spherical(q1) ]; + } + } + function code(λ, φ) { + var r = smallRadius ? radius : π - radius, code = 0; + if (λ < -r) code |= 1; else if (λ > r) code |= 2; + if (φ < -r) code |= 4; else if (φ > r) code |= 8; + return code; + } + } + function d3_geom_clipLine(x0, y0, x1, y1) { + return function(line) { + var a = line.a, b = line.b, ax = a.x, ay = a.y, bx = b.x, by = b.y, t0 = 0, t1 = 1, dx = bx - ax, dy = by - ay, r; + r = x0 - ax; + if (!dx && r > 0) return; + r /= dx; + if (dx < 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } else if (dx > 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } + r = x1 - ax; + if (!dx && r < 0) return; + r /= dx; + if (dx < 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } else if (dx > 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } + r = y0 - ay; + if (!dy && r > 0) return; + r /= dy; + if (dy < 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } else if (dy > 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } + r = y1 - ay; + if (!dy && r < 0) return; + r /= dy; + if (dy < 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } else if (dy > 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } + if (t0 > 0) line.a = { + x: ax + t0 * dx, + y: ay + t0 * dy + }; + if (t1 < 1) line.b = { + x: ax + t1 * dx, + y: ay + t1 * dy + }; + return line; + }; + } + var d3_geo_clipExtentMAX = 1e9; + d3.geo.clipExtent = function() { + var x0, y0, x1, y1, stream, clip, clipExtent = { + stream: function(output) { + if (stream) stream.valid = false; + stream = clip(output); + stream.valid = true; + return stream; + }, + extent: function(_) { + if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ]; + clip = d3_geo_clipExtent(x0 = +_[0][0], y0 = +_[0][1], x1 = +_[1][0], y1 = +_[1][1]); + if (stream) stream.valid = false, stream = null; + return clipExtent; + } + }; + return clipExtent.extent([ [ 0, 0 ], [ 960, 500 ] ]); + }; + function d3_geo_clipExtent(x0, y0, x1, y1) { + return function(listener) { + var listener_ = listener, bufferListener = d3_geo_clipBufferListener(), clipLine = d3_geom_clipLine(x0, y0, x1, y1), segments, polygon, ring; + var clip = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + listener = bufferListener; + segments = []; + polygon = []; + clean = true; + }, + polygonEnd: function() { + listener = listener_; + segments = d3.merge(segments); + var clipStartInside = insidePolygon([ x0, y1 ]), inside = clean && clipStartInside, visible = segments.length; + if (inside || visible) { + listener.polygonStart(); + if (inside) { + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + } + if (visible) { + d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener); + } + listener.polygonEnd(); + } + segments = polygon = ring = null; + } + }; + function insidePolygon(p) { + var wn = 0, n = polygon.length, y = p[1]; + for (var i = 0; i < n; ++i) { + for (var j = 1, v = polygon[i], m = v.length, a = v[0], b; j < m; ++j) { + b = v[j]; + if (a[1] <= y) { + if (b[1] > y && d3_cross2d(a, b, p) > 0) ++wn; + } else { + if (b[1] <= y && d3_cross2d(a, b, p) < 0) --wn; + } + a = b; + } + } + return wn !== 0; + } + function interpolate(from, to, direction, listener) { + var a = 0, a1 = 0; + if (from == null || (a = corner(from, direction)) !== (a1 = corner(to, direction)) || comparePoints(from, to) < 0 ^ direction > 0) { + do { + listener.point(a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0); + } while ((a = (a + direction + 4) % 4) !== a1); + } else { + listener.point(to[0], to[1]); + } + } + function pointVisible(x, y) { + return x0 <= x && x <= x1 && y0 <= y && y <= y1; + } + function point(x, y) { + if (pointVisible(x, y)) listener.point(x, y); + } + var x__, y__, v__, x_, y_, v_, first, clean; + function lineStart() { + clip.point = linePoint; + if (polygon) polygon.push(ring = []); + first = true; + v_ = false; + x_ = y_ = NaN; + } + function lineEnd() { + if (segments) { + linePoint(x__, y__); + if (v__ && v_) bufferListener.rejoin(); + segments.push(bufferListener.buffer()); + } + clip.point = point; + if (v_) listener.lineEnd(); + } + function linePoint(x, y) { + x = Math.max(-d3_geo_clipExtentMAX, Math.min(d3_geo_clipExtentMAX, x)); + y = Math.max(-d3_geo_clipExtentMAX, Math.min(d3_geo_clipExtentMAX, y)); + var v = pointVisible(x, y); + if (polygon) ring.push([ x, y ]); + if (first) { + x__ = x, y__ = y, v__ = v; + first = false; + if (v) { + listener.lineStart(); + listener.point(x, y); + } + } else { + if (v && v_) listener.point(x, y); else { + var l = { + a: { + x: x_, + y: y_ + }, + b: { + x: x, + y: y + } + }; + if (clipLine(l)) { + if (!v_) { + listener.lineStart(); + listener.point(l.a.x, l.a.y); + } + listener.point(l.b.x, l.b.y); + if (!v) listener.lineEnd(); + clean = false; + } else if (v) { + listener.lineStart(); + listener.point(x, y); + clean = false; + } + } + } + x_ = x, y_ = y, v_ = v; + } + return clip; + }; + function corner(p, direction) { + return abs(p[0] - x0) < ε ? direction > 0 ? 0 : 3 : abs(p[0] - x1) < ε ? direction > 0 ? 2 : 1 : abs(p[1] - y0) < ε ? direction > 0 ? 1 : 0 : direction > 0 ? 3 : 2; + } + function compare(a, b) { + return comparePoints(a.x, b.x); + } + function comparePoints(a, b) { + var ca = corner(a, 1), cb = corner(b, 1); + return ca !== cb ? ca - cb : ca === 0 ? b[1] - a[1] : ca === 1 ? a[0] - b[0] : ca === 2 ? a[1] - b[1] : b[0] - a[0]; + } + } + function d3_geo_conic(projectAt) { + var φ0 = 0, φ1 = π / 3, m = d3_geo_projectionMutator(projectAt), p = m(φ0, φ1); + p.parallels = function(_) { + if (!arguments.length) return [ φ0 / π * 180, φ1 / π * 180 ]; + return m(φ0 = _[0] * π / 180, φ1 = _[1] * π / 180); + }; + return p; + } + function d3_geo_conicEqualArea(φ0, φ1) { + var sinφ0 = Math.sin(φ0), n = (sinφ0 + Math.sin(φ1)) / 2, C = 1 + sinφ0 * (2 * n - sinφ0), ρ0 = Math.sqrt(C) / n; + function forward(λ, φ) { + var ρ = Math.sqrt(C - 2 * n * Math.sin(φ)) / n; + return [ ρ * Math.sin(λ *= n), ρ0 - ρ * Math.cos(λ) ]; + } + forward.invert = function(x, y) { + var ρ0_y = ρ0 - y; + return [ Math.atan2(x, ρ0_y) / n, d3_asin((C - (x * x + ρ0_y * ρ0_y) * n * n) / (2 * n)) ]; + }; + return forward; + } + (d3.geo.conicEqualArea = function() { + return d3_geo_conic(d3_geo_conicEqualArea); + }).raw = d3_geo_conicEqualArea; + d3.geo.albers = function() { + return d3.geo.conicEqualArea().rotate([ 96, 0 ]).center([ -.6, 38.7 ]).parallels([ 29.5, 45.5 ]).scale(1070); + }; + d3.geo.albersUsa = function() { + var lower48 = d3.geo.albers(); + var alaska = d3.geo.conicEqualArea().rotate([ 154, 0 ]).center([ -2, 58.5 ]).parallels([ 55, 65 ]); + var hawaii = d3.geo.conicEqualArea().rotate([ 157, 0 ]).center([ -3, 19.9 ]).parallels([ 8, 18 ]); + var point, pointStream = { + point: function(x, y) { + point = [ x, y ]; + } + }, lower48Point, alaskaPoint, hawaiiPoint; + function albersUsa(coordinates) { + var x = coordinates[0], y = coordinates[1]; + point = null; + (lower48Point(x, y), point) || (alaskaPoint(x, y), point) || hawaiiPoint(x, y); + return point; + } + albersUsa.invert = function(coordinates) { + var k = lower48.scale(), t = lower48.translate(), x = (coordinates[0] - t[0]) / k, y = (coordinates[1] - t[1]) / k; + return (y >= .12 && y < .234 && x >= -.425 && x < -.214 ? alaska : y >= .166 && y < .234 && x >= -.214 && x < -.115 ? hawaii : lower48).invert(coordinates); + }; + albersUsa.stream = function(stream) { + var lower48Stream = lower48.stream(stream), alaskaStream = alaska.stream(stream), hawaiiStream = hawaii.stream(stream); + return { + point: function(x, y) { + lower48Stream.point(x, y); + alaskaStream.point(x, y); + hawaiiStream.point(x, y); + }, + sphere: function() { + lower48Stream.sphere(); + alaskaStream.sphere(); + hawaiiStream.sphere(); + }, + lineStart: function() { + lower48Stream.lineStart(); + alaskaStream.lineStart(); + hawaiiStream.lineStart(); + }, + lineEnd: function() { + lower48Stream.lineEnd(); + alaskaStream.lineEnd(); + hawaiiStream.lineEnd(); + }, + polygonStart: function() { + lower48Stream.polygonStart(); + alaskaStream.polygonStart(); + hawaiiStream.polygonStart(); + }, + polygonEnd: function() { + lower48Stream.polygonEnd(); + alaskaStream.polygonEnd(); + hawaiiStream.polygonEnd(); + } + }; + }; + albersUsa.precision = function(_) { + if (!arguments.length) return lower48.precision(); + lower48.precision(_); + alaska.precision(_); + hawaii.precision(_); + return albersUsa; + }; + albersUsa.scale = function(_) { + if (!arguments.length) return lower48.scale(); + lower48.scale(_); + alaska.scale(_ * .35); + hawaii.scale(_); + return albersUsa.translate(lower48.translate()); + }; + albersUsa.translate = function(_) { + if (!arguments.length) return lower48.translate(); + var k = lower48.scale(), x = +_[0], y = +_[1]; + lower48Point = lower48.translate(_).clipExtent([ [ x - .455 * k, y - .238 * k ], [ x + .455 * k, y + .238 * k ] ]).stream(pointStream).point; + alaskaPoint = alaska.translate([ x - .307 * k, y + .201 * k ]).clipExtent([ [ x - .425 * k + ε, y + .12 * k + ε ], [ x - .214 * k - ε, y + .234 * k - ε ] ]).stream(pointStream).point; + hawaiiPoint = hawaii.translate([ x - .205 * k, y + .212 * k ]).clipExtent([ [ x - .214 * k + ε, y + .166 * k + ε ], [ x - .115 * k - ε, y + .234 * k - ε ] ]).stream(pointStream).point; + return albersUsa; + }; + return albersUsa.scale(1070); + }; + var d3_geo_pathAreaSum, d3_geo_pathAreaPolygon, d3_geo_pathArea = { + point: d3_noop, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + d3_geo_pathAreaPolygon = 0; + d3_geo_pathArea.lineStart = d3_geo_pathAreaRingStart; + }, + polygonEnd: function() { + d3_geo_pathArea.lineStart = d3_geo_pathArea.lineEnd = d3_geo_pathArea.point = d3_noop; + d3_geo_pathAreaSum += abs(d3_geo_pathAreaPolygon / 2); + } + }; + function d3_geo_pathAreaRingStart() { + var x00, y00, x0, y0; + d3_geo_pathArea.point = function(x, y) { + d3_geo_pathArea.point = nextPoint; + x00 = x0 = x, y00 = y0 = y; + }; + function nextPoint(x, y) { + d3_geo_pathAreaPolygon += y0 * x - x0 * y; + x0 = x, y0 = y; + } + d3_geo_pathArea.lineEnd = function() { + nextPoint(x00, y00); + }; + } + var d3_geo_pathBoundsX0, d3_geo_pathBoundsY0, d3_geo_pathBoundsX1, d3_geo_pathBoundsY1; + var d3_geo_pathBounds = { + point: d3_geo_pathBoundsPoint, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: d3_noop, + polygonEnd: d3_noop + }; + function d3_geo_pathBoundsPoint(x, y) { + if (x < d3_geo_pathBoundsX0) d3_geo_pathBoundsX0 = x; + if (x > d3_geo_pathBoundsX1) d3_geo_pathBoundsX1 = x; + if (y < d3_geo_pathBoundsY0) d3_geo_pathBoundsY0 = y; + if (y > d3_geo_pathBoundsY1) d3_geo_pathBoundsY1 = y; + } + function d3_geo_pathBuffer() { + var pointCircle = d3_geo_pathBufferCircle(4.5), buffer = []; + var stream = { + point: point, + lineStart: function() { + stream.point = pointLineStart; + }, + lineEnd: lineEnd, + polygonStart: function() { + stream.lineEnd = lineEndPolygon; + }, + polygonEnd: function() { + stream.lineEnd = lineEnd; + stream.point = point; + }, + pointRadius: function(_) { + pointCircle = d3_geo_pathBufferCircle(_); + return stream; + }, + result: function() { + if (buffer.length) { + var result = buffer.join(""); + buffer = []; + return result; + } + } + }; + function point(x, y) { + buffer.push("M", x, ",", y, pointCircle); + } + function pointLineStart(x, y) { + buffer.push("M", x, ",", y); + stream.point = pointLine; + } + function pointLine(x, y) { + buffer.push("L", x, ",", y); + } + function lineEnd() { + stream.point = point; + } + function lineEndPolygon() { + buffer.push("Z"); + } + return stream; + } + function d3_geo_pathBufferCircle(radius) { + return "m0," + radius + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius + "a" + radius + "," + radius + " 0 1,1 0," + 2 * radius + "z"; + } + var d3_geo_pathCentroid = { + point: d3_geo_pathCentroidPoint, + lineStart: d3_geo_pathCentroidLineStart, + lineEnd: d3_geo_pathCentroidLineEnd, + polygonStart: function() { + d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidRingStart; + }, + polygonEnd: function() { + d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint; + d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidLineStart; + d3_geo_pathCentroid.lineEnd = d3_geo_pathCentroidLineEnd; + } + }; + function d3_geo_pathCentroidPoint(x, y) { + d3_geo_centroidX0 += x; + d3_geo_centroidY0 += y; + ++d3_geo_centroidZ0; + } + function d3_geo_pathCentroidLineStart() { + var x0, y0; + d3_geo_pathCentroid.point = function(x, y) { + d3_geo_pathCentroid.point = nextPoint; + d3_geo_pathCentroidPoint(x0 = x, y0 = y); + }; + function nextPoint(x, y) { + var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy); + d3_geo_centroidX1 += z * (x0 + x) / 2; + d3_geo_centroidY1 += z * (y0 + y) / 2; + d3_geo_centroidZ1 += z; + d3_geo_pathCentroidPoint(x0 = x, y0 = y); + } + } + function d3_geo_pathCentroidLineEnd() { + d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint; + } + function d3_geo_pathCentroidRingStart() { + var x00, y00, x0, y0; + d3_geo_pathCentroid.point = function(x, y) { + d3_geo_pathCentroid.point = nextPoint; + d3_geo_pathCentroidPoint(x00 = x0 = x, y00 = y0 = y); + }; + function nextPoint(x, y) { + var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy); + d3_geo_centroidX1 += z * (x0 + x) / 2; + d3_geo_centroidY1 += z * (y0 + y) / 2; + d3_geo_centroidZ1 += z; + z = y0 * x - x0 * y; + d3_geo_centroidX2 += z * (x0 + x); + d3_geo_centroidY2 += z * (y0 + y); + d3_geo_centroidZ2 += z * 3; + d3_geo_pathCentroidPoint(x0 = x, y0 = y); + } + d3_geo_pathCentroid.lineEnd = function() { + nextPoint(x00, y00); + }; + } + function d3_geo_pathContext(context) { + var pointRadius = 4.5; + var stream = { + point: point, + lineStart: function() { + stream.point = pointLineStart; + }, + lineEnd: lineEnd, + polygonStart: function() { + stream.lineEnd = lineEndPolygon; + }, + polygonEnd: function() { + stream.lineEnd = lineEnd; + stream.point = point; + }, + pointRadius: function(_) { + pointRadius = _; + return stream; + }, + result: d3_noop + }; + function point(x, y) { + context.moveTo(x + pointRadius, y); + context.arc(x, y, pointRadius, 0, τ); + } + function pointLineStart(x, y) { + context.moveTo(x, y); + stream.point = pointLine; + } + function pointLine(x, y) { + context.lineTo(x, y); + } + function lineEnd() { + stream.point = point; + } + function lineEndPolygon() { + context.closePath(); + } + return stream; + } + function d3_geo_resample(project) { + var δ2 = .5, cosMinDistance = Math.cos(30 * d3_radians), maxDepth = 16; + function resample(stream) { + return (maxDepth ? resampleRecursive : resampleNone)(stream); + } + function resampleNone(stream) { + return d3_geo_transformPoint(stream, function(x, y) { + x = project(x, y); + stream.point(x[0], x[1]); + }); + } + function resampleRecursive(stream) { + var λ00, φ00, x00, y00, a00, b00, c00, λ0, x0, y0, a0, b0, c0; + var resample = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + stream.polygonStart(); + resample.lineStart = ringStart; + }, + polygonEnd: function() { + stream.polygonEnd(); + resample.lineStart = lineStart; + } + }; + function point(x, y) { + x = project(x, y); + stream.point(x[0], x[1]); + } + function lineStart() { + x0 = NaN; + resample.point = linePoint; + stream.lineStart(); + } + function linePoint(λ, φ) { + var c = d3_geo_cartesian([ λ, φ ]), p = project(λ, φ); + resampleLineTo(x0, y0, λ0, a0, b0, c0, x0 = p[0], y0 = p[1], λ0 = λ, a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, stream); + stream.point(x0, y0); + } + function lineEnd() { + resample.point = point; + stream.lineEnd(); + } + function ringStart() { + lineStart(); + resample.point = ringPoint; + resample.lineEnd = ringEnd; + } + function ringPoint(λ, φ) { + linePoint(λ00 = λ, φ00 = φ), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0; + resample.point = linePoint; + } + function ringEnd() { + resampleLineTo(x0, y0, λ0, a0, b0, c0, x00, y00, λ00, a00, b00, c00, maxDepth, stream); + resample.lineEnd = lineEnd; + lineEnd(); + } + return resample; + } + function resampleLineTo(x0, y0, λ0, a0, b0, c0, x1, y1, λ1, a1, b1, c1, depth, stream) { + var dx = x1 - x0, dy = y1 - y0, d2 = dx * dx + dy * dy; + if (d2 > 4 * δ2 && depth--) { + var a = a0 + a1, b = b0 + b1, c = c0 + c1, m = Math.sqrt(a * a + b * b + c * c), φ2 = Math.asin(c /= m), λ2 = abs(abs(c) - 1) < ε || abs(λ0 - λ1) < ε ? (λ0 + λ1) / 2 : Math.atan2(b, a), p = project(λ2, φ2), x2 = p[0], y2 = p[1], dx2 = x2 - x0, dy2 = y2 - y0, dz = dy * dx2 - dx * dy2; + if (dz * dz / d2 > δ2 || abs((dx * dx2 + dy * dy2) / d2 - .5) > .3 || a0 * a1 + b0 * b1 + c0 * c1 < cosMinDistance) { + resampleLineTo(x0, y0, λ0, a0, b0, c0, x2, y2, λ2, a /= m, b /= m, c, depth, stream); + stream.point(x2, y2); + resampleLineTo(x2, y2, λ2, a, b, c, x1, y1, λ1, a1, b1, c1, depth, stream); + } + } + } + resample.precision = function(_) { + if (!arguments.length) return Math.sqrt(δ2); + maxDepth = (δ2 = _ * _) > 0 && 16; + return resample; + }; + return resample; + } + d3.geo.path = function() { + var pointRadius = 4.5, projection, context, projectStream, contextStream, cacheStream; + function path(object) { + if (object) { + if (typeof pointRadius === "function") contextStream.pointRadius(+pointRadius.apply(this, arguments)); + if (!cacheStream || !cacheStream.valid) cacheStream = projectStream(contextStream); + d3.geo.stream(object, cacheStream); + } + return contextStream.result(); + } + path.area = function(object) { + d3_geo_pathAreaSum = 0; + d3.geo.stream(object, projectStream(d3_geo_pathArea)); + return d3_geo_pathAreaSum; + }; + path.centroid = function(object) { + d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0; + d3.geo.stream(object, projectStream(d3_geo_pathCentroid)); + return d3_geo_centroidZ2 ? [ d3_geo_centroidX2 / d3_geo_centroidZ2, d3_geo_centroidY2 / d3_geo_centroidZ2 ] : d3_geo_centroidZ1 ? [ d3_geo_centroidX1 / d3_geo_centroidZ1, d3_geo_centroidY1 / d3_geo_centroidZ1 ] : d3_geo_centroidZ0 ? [ d3_geo_centroidX0 / d3_geo_centroidZ0, d3_geo_centroidY0 / d3_geo_centroidZ0 ] : [ NaN, NaN ]; + }; + path.bounds = function(object) { + d3_geo_pathBoundsX1 = d3_geo_pathBoundsY1 = -(d3_geo_pathBoundsX0 = d3_geo_pathBoundsY0 = Infinity); + d3.geo.stream(object, projectStream(d3_geo_pathBounds)); + return [ [ d3_geo_pathBoundsX0, d3_geo_pathBoundsY0 ], [ d3_geo_pathBoundsX1, d3_geo_pathBoundsY1 ] ]; + }; + path.projection = function(_) { + if (!arguments.length) return projection; + projectStream = (projection = _) ? _.stream || d3_geo_pathProjectStream(_) : d3_identity; + return reset(); + }; + path.context = function(_) { + if (!arguments.length) return context; + contextStream = (context = _) == null ? new d3_geo_pathBuffer() : new d3_geo_pathContext(_); + if (typeof pointRadius !== "function") contextStream.pointRadius(pointRadius); + return reset(); + }; + path.pointRadius = function(_) { + if (!arguments.length) return pointRadius; + pointRadius = typeof _ === "function" ? _ : (contextStream.pointRadius(+_), +_); + return path; + }; + function reset() { + cacheStream = null; + return path; + } + return path.projection(d3.geo.albersUsa()).context(null); + }; + function d3_geo_pathProjectStream(project) { + var resample = d3_geo_resample(function(x, y) { + return project([ x * d3_degrees, y * d3_degrees ]); + }); + return function(stream) { + return d3_geo_projectionRadians(resample(stream)); + }; + } + d3.geo.transform = function(methods) { + return { + stream: function(stream) { + var transform = new d3_geo_transform(stream); + for (var k in methods) transform[k] = methods[k]; + return transform; + } + }; + }; + function d3_geo_transform(stream) { + this.stream = stream; + } + d3_geo_transform.prototype = { + point: function(x, y) { + this.stream.point(x, y); + }, + sphere: function() { + this.stream.sphere(); + }, + lineStart: function() { + this.stream.lineStart(); + }, + lineEnd: function() { + this.stream.lineEnd(); + }, + polygonStart: function() { + this.stream.polygonStart(); + }, + polygonEnd: function() { + this.stream.polygonEnd(); + } + }; + function d3_geo_transformPoint(stream, point) { + return { + point: point, + sphere: function() { + stream.sphere(); + }, + lineStart: function() { + stream.lineStart(); + }, + lineEnd: function() { + stream.lineEnd(); + }, + polygonStart: function() { + stream.polygonStart(); + }, + polygonEnd: function() { + stream.polygonEnd(); + } + }; + } + d3.geo.projection = d3_geo_projection; + d3.geo.projectionMutator = d3_geo_projectionMutator; + function d3_geo_projection(project) { + return d3_geo_projectionMutator(function() { + return project; + })(); + } + function d3_geo_projectionMutator(projectAt) { + var project, rotate, projectRotate, projectResample = d3_geo_resample(function(x, y) { + x = project(x, y); + return [ x[0] * k + δx, δy - x[1] * k ]; + }), k = 150, x = 480, y = 250, λ = 0, φ = 0, δλ = 0, δφ = 0, δγ = 0, δx, δy, preclip = d3_geo_clipAntimeridian, postclip = d3_identity, clipAngle = null, clipExtent = null, stream; + function projection(point) { + point = projectRotate(point[0] * d3_radians, point[1] * d3_radians); + return [ point[0] * k + δx, δy - point[1] * k ]; + } + function invert(point) { + point = projectRotate.invert((point[0] - δx) / k, (δy - point[1]) / k); + return point && [ point[0] * d3_degrees, point[1] * d3_degrees ]; + } + projection.stream = function(output) { + if (stream) stream.valid = false; + stream = d3_geo_projectionRadians(preclip(rotate, projectResample(postclip(output)))); + stream.valid = true; + return stream; + }; + projection.clipAngle = function(_) { + if (!arguments.length) return clipAngle; + preclip = _ == null ? (clipAngle = _, d3_geo_clipAntimeridian) : d3_geo_clipCircle((clipAngle = +_) * d3_radians); + return invalidate(); + }; + projection.clipExtent = function(_) { + if (!arguments.length) return clipExtent; + clipExtent = _; + postclip = _ ? d3_geo_clipExtent(_[0][0], _[0][1], _[1][0], _[1][1]) : d3_identity; + return invalidate(); + }; + projection.scale = function(_) { + if (!arguments.length) return k; + k = +_; + return reset(); + }; + projection.translate = function(_) { + if (!arguments.length) return [ x, y ]; + x = +_[0]; + y = +_[1]; + return reset(); + }; + projection.center = function(_) { + if (!arguments.length) return [ λ * d3_degrees, φ * d3_degrees ]; + λ = _[0] % 360 * d3_radians; + φ = _[1] % 360 * d3_radians; + return reset(); + }; + projection.rotate = function(_) { + if (!arguments.length) return [ δλ * d3_degrees, δφ * d3_degrees, δγ * d3_degrees ]; + δλ = _[0] % 360 * d3_radians; + δφ = _[1] % 360 * d3_radians; + δγ = _.length > 2 ? _[2] % 360 * d3_radians : 0; + return reset(); + }; + d3.rebind(projection, projectResample, "precision"); + function reset() { + projectRotate = d3_geo_compose(rotate = d3_geo_rotation(δλ, δφ, δγ), project); + var center = project(λ, φ); + δx = x - center[0] * k; + δy = y + center[1] * k; + return invalidate(); + } + function invalidate() { + if (stream) stream.valid = false, stream = null; + return projection; + } + return function() { + project = projectAt.apply(this, arguments); + projection.invert = project.invert && invert; + return reset(); + }; + } + function d3_geo_projectionRadians(stream) { + return d3_geo_transformPoint(stream, function(x, y) { + stream.point(x * d3_radians, y * d3_radians); + }); + } + function d3_geo_equirectangular(λ, φ) { + return [ λ, φ ]; + } + (d3.geo.equirectangular = function() { + return d3_geo_projection(d3_geo_equirectangular); + }).raw = d3_geo_equirectangular.invert = d3_geo_equirectangular; + d3.geo.rotation = function(rotate) { + rotate = d3_geo_rotation(rotate[0] % 360 * d3_radians, rotate[1] * d3_radians, rotate.length > 2 ? rotate[2] * d3_radians : 0); + function forward(coordinates) { + coordinates = rotate(coordinates[0] * d3_radians, coordinates[1] * d3_radians); + return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates; + } + forward.invert = function(coordinates) { + coordinates = rotate.invert(coordinates[0] * d3_radians, coordinates[1] * d3_radians); + return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates; + }; + return forward; + }; + function d3_geo_identityRotation(λ, φ) { + return [ λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ ]; + } + d3_geo_identityRotation.invert = d3_geo_equirectangular; + function d3_geo_rotation(δλ, δφ, δγ) { + return δλ ? δφ || δγ ? d3_geo_compose(d3_geo_rotationλ(δλ), d3_geo_rotationφγ(δφ, δγ)) : d3_geo_rotationλ(δλ) : δφ || δγ ? d3_geo_rotationφγ(δφ, δγ) : d3_geo_identityRotation; + } + function d3_geo_forwardRotationλ(δλ) { + return function(λ, φ) { + return λ += δλ, [ λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ ]; + }; + } + function d3_geo_rotationλ(δλ) { + var rotation = d3_geo_forwardRotationλ(δλ); + rotation.invert = d3_geo_forwardRotationλ(-δλ); + return rotation; + } + function d3_geo_rotationφγ(δφ, δγ) { + var cosδφ = Math.cos(δφ), sinδφ = Math.sin(δφ), cosδγ = Math.cos(δγ), sinδγ = Math.sin(δγ); + function rotation(λ, φ) { + var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδφ + x * sinδφ; + return [ Math.atan2(y * cosδγ - k * sinδγ, x * cosδφ - z * sinδφ), d3_asin(k * cosδγ + y * sinδγ) ]; + } + rotation.invert = function(λ, φ) { + var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδγ - y * sinδγ; + return [ Math.atan2(y * cosδγ + z * sinδγ, x * cosδφ + k * sinδφ), d3_asin(k * cosδφ - x * sinδφ) ]; + }; + return rotation; + } + d3.geo.circle = function() { + var origin = [ 0, 0 ], angle, precision = 6, interpolate; + function circle() { + var center = typeof origin === "function" ? origin.apply(this, arguments) : origin, rotate = d3_geo_rotation(-center[0] * d3_radians, -center[1] * d3_radians, 0).invert, ring = []; + interpolate(null, null, 1, { + point: function(x, y) { + ring.push(x = rotate(x, y)); + x[0] *= d3_degrees, x[1] *= d3_degrees; + } + }); + return { + type: "Polygon", + coordinates: [ ring ] + }; + } + circle.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + return circle; + }; + circle.angle = function(x) { + if (!arguments.length) return angle; + interpolate = d3_geo_circleInterpolate((angle = +x) * d3_radians, precision * d3_radians); + return circle; + }; + circle.precision = function(_) { + if (!arguments.length) return precision; + interpolate = d3_geo_circleInterpolate(angle * d3_radians, (precision = +_) * d3_radians); + return circle; + }; + return circle.angle(90); + }; + function d3_geo_circleInterpolate(radius, precision) { + var cr = Math.cos(radius), sr = Math.sin(radius); + return function(from, to, direction, listener) { + var step = direction * precision; + if (from != null) { + from = d3_geo_circleAngle(cr, from); + to = d3_geo_circleAngle(cr, to); + if (direction > 0 ? from < to : from > to) from += direction * τ; + } else { + from = radius + direction * τ; + to = radius - .5 * step; + } + for (var point, t = from; direction > 0 ? t > to : t < to; t -= step) { + listener.point((point = d3_geo_spherical([ cr, -sr * Math.cos(t), -sr * Math.sin(t) ]))[0], point[1]); + } + }; + } + function d3_geo_circleAngle(cr, point) { + var a = d3_geo_cartesian(point); + a[0] -= cr; + d3_geo_cartesianNormalize(a); + var angle = d3_acos(-a[1]); + return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - ε) % (2 * Math.PI); + } + d3.geo.distance = function(a, b) { + var Δλ = (b[0] - a[0]) * d3_radians, φ0 = a[1] * d3_radians, φ1 = b[1] * d3_radians, sinΔλ = Math.sin(Δλ), cosΔλ = Math.cos(Δλ), sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), sinφ1 = Math.sin(φ1), cosφ1 = Math.cos(φ1), t; + return Math.atan2(Math.sqrt((t = cosφ1 * sinΔλ) * t + (t = cosφ0 * sinφ1 - sinφ0 * cosφ1 * cosΔλ) * t), sinφ0 * sinφ1 + cosφ0 * cosφ1 * cosΔλ); + }; + d3.geo.graticule = function() { + var x1, x0, X1, X0, y1, y0, Y1, Y0, dx = 10, dy = dx, DX = 90, DY = 360, x, y, X, Y, precision = 2.5; + function graticule() { + return { + type: "MultiLineString", + coordinates: lines() + }; + } + function lines() { + return d3.range(Math.ceil(X0 / DX) * DX, X1, DX).map(X).concat(d3.range(Math.ceil(Y0 / DY) * DY, Y1, DY).map(Y)).concat(d3.range(Math.ceil(x0 / dx) * dx, x1, dx).filter(function(x) { + return abs(x % DX) > ε; + }).map(x)).concat(d3.range(Math.ceil(y0 / dy) * dy, y1, dy).filter(function(y) { + return abs(y % DY) > ε; + }).map(y)); + } + graticule.lines = function() { + return lines().map(function(coordinates) { + return { + type: "LineString", + coordinates: coordinates + }; + }); + }; + graticule.outline = function() { + return { + type: "Polygon", + coordinates: [ X(X0).concat(Y(Y1).slice(1), X(X1).reverse().slice(1), Y(Y0).reverse().slice(1)) ] + }; + }; + graticule.extent = function(_) { + if (!arguments.length) return graticule.minorExtent(); + return graticule.majorExtent(_).minorExtent(_); + }; + graticule.majorExtent = function(_) { + if (!arguments.length) return [ [ X0, Y0 ], [ X1, Y1 ] ]; + X0 = +_[0][0], X1 = +_[1][0]; + Y0 = +_[0][1], Y1 = +_[1][1]; + if (X0 > X1) _ = X0, X0 = X1, X1 = _; + if (Y0 > Y1) _ = Y0, Y0 = Y1, Y1 = _; + return graticule.precision(precision); + }; + graticule.minorExtent = function(_) { + if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ]; + x0 = +_[0][0], x1 = +_[1][0]; + y0 = +_[0][1], y1 = +_[1][1]; + if (x0 > x1) _ = x0, x0 = x1, x1 = _; + if (y0 > y1) _ = y0, y0 = y1, y1 = _; + return graticule.precision(precision); + }; + graticule.step = function(_) { + if (!arguments.length) return graticule.minorStep(); + return graticule.majorStep(_).minorStep(_); + }; + graticule.majorStep = function(_) { + if (!arguments.length) return [ DX, DY ]; + DX = +_[0], DY = +_[1]; + return graticule; + }; + graticule.minorStep = function(_) { + if (!arguments.length) return [ dx, dy ]; + dx = +_[0], dy = +_[1]; + return graticule; + }; + graticule.precision = function(_) { + if (!arguments.length) return precision; + precision = +_; + x = d3_geo_graticuleX(y0, y1, 90); + y = d3_geo_graticuleY(x0, x1, precision); + X = d3_geo_graticuleX(Y0, Y1, 90); + Y = d3_geo_graticuleY(X0, X1, precision); + return graticule; + }; + return graticule.majorExtent([ [ -180, -90 + ε ], [ 180, 90 - ε ] ]).minorExtent([ [ -180, -80 - ε ], [ 180, 80 + ε ] ]); + }; + function d3_geo_graticuleX(y0, y1, dy) { + var y = d3.range(y0, y1 - ε, dy).concat(y1); + return function(x) { + return y.map(function(y) { + return [ x, y ]; + }); + }; + } + function d3_geo_graticuleY(x0, x1, dx) { + var x = d3.range(x0, x1 - ε, dx).concat(x1); + return function(y) { + return x.map(function(x) { + return [ x, y ]; + }); + }; + } + function d3_source(d) { + return d.source; + } + function d3_target(d) { + return d.target; + } + d3.geo.greatArc = function() { + var source = d3_source, source_, target = d3_target, target_; + function greatArc() { + return { + type: "LineString", + coordinates: [ source_ || source.apply(this, arguments), target_ || target.apply(this, arguments) ] + }; + } + greatArc.distance = function() { + return d3.geo.distance(source_ || source.apply(this, arguments), target_ || target.apply(this, arguments)); + }; + greatArc.source = function(_) { + if (!arguments.length) return source; + source = _, source_ = typeof _ === "function" ? null : _; + return greatArc; + }; + greatArc.target = function(_) { + if (!arguments.length) return target; + target = _, target_ = typeof _ === "function" ? null : _; + return greatArc; + }; + greatArc.precision = function() { + return arguments.length ? greatArc : 0; + }; + return greatArc; + }; + d3.geo.interpolate = function(source, target) { + return d3_geo_interpolate(source[0] * d3_radians, source[1] * d3_radians, target[0] * d3_radians, target[1] * d3_radians); + }; + function d3_geo_interpolate(x0, y0, x1, y1) { + var cy0 = Math.cos(y0), sy0 = Math.sin(y0), cy1 = Math.cos(y1), sy1 = Math.sin(y1), kx0 = cy0 * Math.cos(x0), ky0 = cy0 * Math.sin(x0), kx1 = cy1 * Math.cos(x1), ky1 = cy1 * Math.sin(x1), d = 2 * Math.asin(Math.sqrt(d3_haversin(y1 - y0) + cy0 * cy1 * d3_haversin(x1 - x0))), k = 1 / Math.sin(d); + var interpolate = d ? function(t) { + var B = Math.sin(t *= d) * k, A = Math.sin(d - t) * k, x = A * kx0 + B * kx1, y = A * ky0 + B * ky1, z = A * sy0 + B * sy1; + return [ Math.atan2(y, x) * d3_degrees, Math.atan2(z, Math.sqrt(x * x + y * y)) * d3_degrees ]; + } : function() { + return [ x0 * d3_degrees, y0 * d3_degrees ]; + }; + interpolate.distance = d; + return interpolate; + } + d3.geo.length = function(object) { + d3_geo_lengthSum = 0; + d3.geo.stream(object, d3_geo_length); + return d3_geo_lengthSum; + }; + var d3_geo_lengthSum; + var d3_geo_length = { + sphere: d3_noop, + point: d3_noop, + lineStart: d3_geo_lengthLineStart, + lineEnd: d3_noop, + polygonStart: d3_noop, + polygonEnd: d3_noop + }; + function d3_geo_lengthLineStart() { + var λ0, sinφ0, cosφ0; + d3_geo_length.point = function(λ, φ) { + λ0 = λ * d3_radians, sinφ0 = Math.sin(φ *= d3_radians), cosφ0 = Math.cos(φ); + d3_geo_length.point = nextPoint; + }; + d3_geo_length.lineEnd = function() { + d3_geo_length.point = d3_geo_length.lineEnd = d3_noop; + }; + function nextPoint(λ, φ) { + var sinφ = Math.sin(φ *= d3_radians), cosφ = Math.cos(φ), t = abs((λ *= d3_radians) - λ0), cosΔλ = Math.cos(t); + d3_geo_lengthSum += Math.atan2(Math.sqrt((t = cosφ * Math.sin(t)) * t + (t = cosφ0 * sinφ - sinφ0 * cosφ * cosΔλ) * t), sinφ0 * sinφ + cosφ0 * cosφ * cosΔλ); + λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ; + } + } + function d3_geo_azimuthal(scale, angle) { + function azimuthal(λ, φ) { + var cosλ = Math.cos(λ), cosφ = Math.cos(φ), k = scale(cosλ * cosφ); + return [ k * cosφ * Math.sin(λ), k * Math.sin(φ) ]; + } + azimuthal.invert = function(x, y) { + var ρ = Math.sqrt(x * x + y * y), c = angle(ρ), sinc = Math.sin(c), cosc = Math.cos(c); + return [ Math.atan2(x * sinc, ρ * cosc), Math.asin(ρ && y * sinc / ρ) ]; + }; + return azimuthal; + } + var d3_geo_azimuthalEqualArea = d3_geo_azimuthal(function(cosλcosφ) { + return Math.sqrt(2 / (1 + cosλcosφ)); + }, function(ρ) { + return 2 * Math.asin(ρ / 2); + }); + (d3.geo.azimuthalEqualArea = function() { + return d3_geo_projection(d3_geo_azimuthalEqualArea); + }).raw = d3_geo_azimuthalEqualArea; + var d3_geo_azimuthalEquidistant = d3_geo_azimuthal(function(cosλcosφ) { + var c = Math.acos(cosλcosφ); + return c && c / Math.sin(c); + }, d3_identity); + (d3.geo.azimuthalEquidistant = function() { + return d3_geo_projection(d3_geo_azimuthalEquidistant); + }).raw = d3_geo_azimuthalEquidistant; + function d3_geo_conicConformal(φ0, φ1) { + var cosφ0 = Math.cos(φ0), t = function(φ) { + return Math.tan(π / 4 + φ / 2); + }, n = φ0 === φ1 ? Math.sin(φ0) : Math.log(cosφ0 / Math.cos(φ1)) / Math.log(t(φ1) / t(φ0)), F = cosφ0 * Math.pow(t(φ0), n) / n; + if (!n) return d3_geo_mercator; + function forward(λ, φ) { + if (F > 0) { + if (φ < -halfπ + ε) φ = -halfπ + ε; + } else { + if (φ > halfπ - ε) φ = halfπ - ε; + } + var ρ = F / Math.pow(t(φ), n); + return [ ρ * Math.sin(n * λ), F - ρ * Math.cos(n * λ) ]; + } + forward.invert = function(x, y) { + var ρ0_y = F - y, ρ = d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y); + return [ Math.atan2(x, ρ0_y) / n, 2 * Math.atan(Math.pow(F / ρ, 1 / n)) - halfπ ]; + }; + return forward; + } + (d3.geo.conicConformal = function() { + return d3_geo_conic(d3_geo_conicConformal); + }).raw = d3_geo_conicConformal; + function d3_geo_conicEquidistant(φ0, φ1) { + var cosφ0 = Math.cos(φ0), n = φ0 === φ1 ? Math.sin(φ0) : (cosφ0 - Math.cos(φ1)) / (φ1 - φ0), G = cosφ0 / n + φ0; + if (abs(n) < ε) return d3_geo_equirectangular; + function forward(λ, φ) { + var ρ = G - φ; + return [ ρ * Math.sin(n * λ), G - ρ * Math.cos(n * λ) ]; + } + forward.invert = function(x, y) { + var ρ0_y = G - y; + return [ Math.atan2(x, ρ0_y) / n, G - d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y) ]; + }; + return forward; + } + (d3.geo.conicEquidistant = function() { + return d3_geo_conic(d3_geo_conicEquidistant); + }).raw = d3_geo_conicEquidistant; + var d3_geo_gnomonic = d3_geo_azimuthal(function(cosλcosφ) { + return 1 / cosλcosφ; + }, Math.atan); + (d3.geo.gnomonic = function() { + return d3_geo_projection(d3_geo_gnomonic); + }).raw = d3_geo_gnomonic; + function d3_geo_mercator(λ, φ) { + return [ λ, Math.log(Math.tan(π / 4 + φ / 2)) ]; + } + d3_geo_mercator.invert = function(x, y) { + return [ x, 2 * Math.atan(Math.exp(y)) - halfπ ]; + }; + function d3_geo_mercatorProjection(project) { + var m = d3_geo_projection(project), scale = m.scale, translate = m.translate, clipExtent = m.clipExtent, clipAuto; + m.scale = function() { + var v = scale.apply(m, arguments); + return v === m ? clipAuto ? m.clipExtent(null) : m : v; + }; + m.translate = function() { + var v = translate.apply(m, arguments); + return v === m ? clipAuto ? m.clipExtent(null) : m : v; + }; + m.clipExtent = function(_) { + var v = clipExtent.apply(m, arguments); + if (v === m) { + if (clipAuto = _ == null) { + var k = π * scale(), t = translate(); + clipExtent([ [ t[0] - k, t[1] - k ], [ t[0] + k, t[1] + k ] ]); + } + } else if (clipAuto) { + v = null; + } + return v; + }; + return m.clipExtent(null); + } + (d3.geo.mercator = function() { + return d3_geo_mercatorProjection(d3_geo_mercator); + }).raw = d3_geo_mercator; + var d3_geo_orthographic = d3_geo_azimuthal(function() { + return 1; + }, Math.asin); + (d3.geo.orthographic = function() { + return d3_geo_projection(d3_geo_orthographic); + }).raw = d3_geo_orthographic; + var d3_geo_stereographic = d3_geo_azimuthal(function(cosλcosφ) { + return 1 / (1 + cosλcosφ); + }, function(ρ) { + return 2 * Math.atan(ρ); + }); + (d3.geo.stereographic = function() { + return d3_geo_projection(d3_geo_stereographic); + }).raw = d3_geo_stereographic; + function d3_geo_transverseMercator(λ, φ) { + return [ Math.log(Math.tan(π / 4 + φ / 2)), -λ ]; + } + d3_geo_transverseMercator.invert = function(x, y) { + return [ -y, 2 * Math.atan(Math.exp(x)) - halfπ ]; + }; + (d3.geo.transverseMercator = function() { + var projection = d3_geo_mercatorProjection(d3_geo_transverseMercator), center = projection.center, rotate = projection.rotate; + projection.center = function(_) { + return _ ? center([ -_[1], _[0] ]) : (_ = center(), [ _[1], -_[0] ]); + }; + projection.rotate = function(_) { + return _ ? rotate([ _[0], _[1], _.length > 2 ? _[2] + 90 : 90 ]) : (_ = rotate(), + [ _[0], _[1], _[2] - 90 ]); + }; + return rotate([ 0, 0, 90 ]); + }).raw = d3_geo_transverseMercator; + d3.geom = {}; + function d3_geom_pointX(d) { + return d[0]; + } + function d3_geom_pointY(d) { + return d[1]; + } + d3.geom.hull = function(vertices) { + var x = d3_geom_pointX, y = d3_geom_pointY; + if (arguments.length) return hull(vertices); + function hull(data) { + if (data.length < 3) return []; + var fx = d3_functor(x), fy = d3_functor(y), i, n = data.length, points = [], flippedPoints = []; + for (i = 0; i < n; i++) { + points.push([ +fx.call(this, data[i], i), +fy.call(this, data[i], i), i ]); + } + points.sort(d3_geom_hullOrder); + for (i = 0; i < n; i++) flippedPoints.push([ points[i][0], -points[i][1] ]); + var upper = d3_geom_hullUpper(points), lower = d3_geom_hullUpper(flippedPoints); + var skipLeft = lower[0] === upper[0], skipRight = lower[lower.length - 1] === upper[upper.length - 1], polygon = []; + for (i = upper.length - 1; i >= 0; --i) polygon.push(data[points[upper[i]][2]]); + for (i = +skipLeft; i < lower.length - skipRight; ++i) polygon.push(data[points[lower[i]][2]]); + return polygon; + } + hull.x = function(_) { + return arguments.length ? (x = _, hull) : x; + }; + hull.y = function(_) { + return arguments.length ? (y = _, hull) : y; + }; + return hull; + }; + function d3_geom_hullUpper(points) { + var n = points.length, hull = [ 0, 1 ], hs = 2; + for (var i = 2; i < n; i++) { + while (hs > 1 && d3_cross2d(points[hull[hs - 2]], points[hull[hs - 1]], points[i]) <= 0) --hs; + hull[hs++] = i; + } + return hull.slice(0, hs); + } + function d3_geom_hullOrder(a, b) { + return a[0] - b[0] || a[1] - b[1]; + } + d3.geom.polygon = function(coordinates) { + d3_subclass(coordinates, d3_geom_polygonPrototype); + return coordinates; + }; + var d3_geom_polygonPrototype = d3.geom.polygon.prototype = []; + d3_geom_polygonPrototype.area = function() { + var i = -1, n = this.length, a, b = this[n - 1], area = 0; + while (++i < n) { + a = b; + b = this[i]; + area += a[1] * b[0] - a[0] * b[1]; + } + return area * .5; + }; + d3_geom_polygonPrototype.centroid = function(k) { + var i = -1, n = this.length, x = 0, y = 0, a, b = this[n - 1], c; + if (!arguments.length) k = -1 / (6 * this.area()); + while (++i < n) { + a = b; + b = this[i]; + c = a[0] * b[1] - b[0] * a[1]; + x += (a[0] + b[0]) * c; + y += (a[1] + b[1]) * c; + } + return [ x * k, y * k ]; + }; + d3_geom_polygonPrototype.clip = function(subject) { + var input, closed = d3_geom_polygonClosed(subject), i = -1, n = this.length - d3_geom_polygonClosed(this), j, m, a = this[n - 1], b, c, d; + while (++i < n) { + input = subject.slice(); + subject.length = 0; + b = this[i]; + c = input[(m = input.length - closed) - 1]; + j = -1; + while (++j < m) { + d = input[j]; + if (d3_geom_polygonInside(d, a, b)) { + if (!d3_geom_polygonInside(c, a, b)) { + subject.push(d3_geom_polygonIntersect(c, d, a, b)); + } + subject.push(d); + } else if (d3_geom_polygonInside(c, a, b)) { + subject.push(d3_geom_polygonIntersect(c, d, a, b)); + } + c = d; + } + if (closed) subject.push(subject[0]); + a = b; + } + return subject; + }; + function d3_geom_polygonInside(p, a, b) { + return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]); + } + function d3_geom_polygonIntersect(c, d, a, b) { + var x1 = c[0], x3 = a[0], x21 = d[0] - x1, x43 = b[0] - x3, y1 = c[1], y3 = a[1], y21 = d[1] - y1, y43 = b[1] - y3, ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21); + return [ x1 + ua * x21, y1 + ua * y21 ]; + } + function d3_geom_polygonClosed(coordinates) { + var a = coordinates[0], b = coordinates[coordinates.length - 1]; + return !(a[0] - b[0] || a[1] - b[1]); + } + var d3_geom_voronoiEdges, d3_geom_voronoiCells, d3_geom_voronoiBeaches, d3_geom_voronoiBeachPool = [], d3_geom_voronoiFirstCircle, d3_geom_voronoiCircles, d3_geom_voronoiCirclePool = []; + function d3_geom_voronoiBeach() { + d3_geom_voronoiRedBlackNode(this); + this.edge = this.site = this.circle = null; + } + function d3_geom_voronoiCreateBeach(site) { + var beach = d3_geom_voronoiBeachPool.pop() || new d3_geom_voronoiBeach(); + beach.site = site; + return beach; + } + function d3_geom_voronoiDetachBeach(beach) { + d3_geom_voronoiDetachCircle(beach); + d3_geom_voronoiBeaches.remove(beach); + d3_geom_voronoiBeachPool.push(beach); + d3_geom_voronoiRedBlackNode(beach); + } + function d3_geom_voronoiRemoveBeach(beach) { + var circle = beach.circle, x = circle.x, y = circle.cy, vertex = { + x: x, + y: y + }, previous = beach.P, next = beach.N, disappearing = [ beach ]; + d3_geom_voronoiDetachBeach(beach); + var lArc = previous; + while (lArc.circle && abs(x - lArc.circle.x) < ε && abs(y - lArc.circle.cy) < ε) { + previous = lArc.P; + disappearing.unshift(lArc); + d3_geom_voronoiDetachBeach(lArc); + lArc = previous; + } + disappearing.unshift(lArc); + d3_geom_voronoiDetachCircle(lArc); + var rArc = next; + while (rArc.circle && abs(x - rArc.circle.x) < ε && abs(y - rArc.circle.cy) < ε) { + next = rArc.N; + disappearing.push(rArc); + d3_geom_voronoiDetachBeach(rArc); + rArc = next; + } + disappearing.push(rArc); + d3_geom_voronoiDetachCircle(rArc); + var nArcs = disappearing.length, iArc; + for (iArc = 1; iArc < nArcs; ++iArc) { + rArc = disappearing[iArc]; + lArc = disappearing[iArc - 1]; + d3_geom_voronoiSetEdgeEnd(rArc.edge, lArc.site, rArc.site, vertex); + } + lArc = disappearing[0]; + rArc = disappearing[nArcs - 1]; + rArc.edge = d3_geom_voronoiCreateEdge(lArc.site, rArc.site, null, vertex); + d3_geom_voronoiAttachCircle(lArc); + d3_geom_voronoiAttachCircle(rArc); + } + function d3_geom_voronoiAddBeach(site) { + var x = site.x, directrix = site.y, lArc, rArc, dxl, dxr, node = d3_geom_voronoiBeaches._; + while (node) { + dxl = d3_geom_voronoiLeftBreakPoint(node, directrix) - x; + if (dxl > ε) node = node.L; else { + dxr = x - d3_geom_voronoiRightBreakPoint(node, directrix); + if (dxr > ε) { + if (!node.R) { + lArc = node; + break; + } + node = node.R; + } else { + if (dxl > -ε) { + lArc = node.P; + rArc = node; + } else if (dxr > -ε) { + lArc = node; + rArc = node.N; + } else { + lArc = rArc = node; + } + break; + } + } + } + var newArc = d3_geom_voronoiCreateBeach(site); + d3_geom_voronoiBeaches.insert(lArc, newArc); + if (!lArc && !rArc) return; + if (lArc === rArc) { + d3_geom_voronoiDetachCircle(lArc); + rArc = d3_geom_voronoiCreateBeach(lArc.site); + d3_geom_voronoiBeaches.insert(newArc, rArc); + newArc.edge = rArc.edge = d3_geom_voronoiCreateEdge(lArc.site, newArc.site); + d3_geom_voronoiAttachCircle(lArc); + d3_geom_voronoiAttachCircle(rArc); + return; + } + if (!rArc) { + newArc.edge = d3_geom_voronoiCreateEdge(lArc.site, newArc.site); + return; + } + d3_geom_voronoiDetachCircle(lArc); + d3_geom_voronoiDetachCircle(rArc); + var lSite = lArc.site, ax = lSite.x, ay = lSite.y, bx = site.x - ax, by = site.y - ay, rSite = rArc.site, cx = rSite.x - ax, cy = rSite.y - ay, d = 2 * (bx * cy - by * cx), hb = bx * bx + by * by, hc = cx * cx + cy * cy, vertex = { + x: (cy * hb - by * hc) / d + ax, + y: (bx * hc - cx * hb) / d + ay + }; + d3_geom_voronoiSetEdgeEnd(rArc.edge, lSite, rSite, vertex); + newArc.edge = d3_geom_voronoiCreateEdge(lSite, site, null, vertex); + rArc.edge = d3_geom_voronoiCreateEdge(site, rSite, null, vertex); + d3_geom_voronoiAttachCircle(lArc); + d3_geom_voronoiAttachCircle(rArc); + } + function d3_geom_voronoiLeftBreakPoint(arc, directrix) { + var site = arc.site, rfocx = site.x, rfocy = site.y, pby2 = rfocy - directrix; + if (!pby2) return rfocx; + var lArc = arc.P; + if (!lArc) return -Infinity; + site = lArc.site; + var lfocx = site.x, lfocy = site.y, plby2 = lfocy - directrix; + if (!plby2) return lfocx; + var hl = lfocx - rfocx, aby2 = 1 / pby2 - 1 / plby2, b = hl / plby2; + if (aby2) return (-b + Math.sqrt(b * b - 2 * aby2 * (hl * hl / (-2 * plby2) - lfocy + plby2 / 2 + rfocy - pby2 / 2))) / aby2 + rfocx; + return (rfocx + lfocx) / 2; + } + function d3_geom_voronoiRightBreakPoint(arc, directrix) { + var rArc = arc.N; + if (rArc) return d3_geom_voronoiLeftBreakPoint(rArc, directrix); + var site = arc.site; + return site.y === directrix ? site.x : Infinity; + } + function d3_geom_voronoiCell(site) { + this.site = site; + this.edges = []; + } + d3_geom_voronoiCell.prototype.prepare = function() { + var halfEdges = this.edges, iHalfEdge = halfEdges.length, edge; + while (iHalfEdge--) { + edge = halfEdges[iHalfEdge].edge; + if (!edge.b || !edge.a) halfEdges.splice(iHalfEdge, 1); + } + halfEdges.sort(d3_geom_voronoiHalfEdgeOrder); + return halfEdges.length; + }; + function d3_geom_voronoiCloseCells(extent) { + var x0 = extent[0][0], x1 = extent[1][0], y0 = extent[0][1], y1 = extent[1][1], x2, y2, x3, y3, cells = d3_geom_voronoiCells, iCell = cells.length, cell, iHalfEdge, halfEdges, nHalfEdges, start, end; + while (iCell--) { + cell = cells[iCell]; + if (!cell || !cell.prepare()) continue; + halfEdges = cell.edges; + nHalfEdges = halfEdges.length; + iHalfEdge = 0; + while (iHalfEdge < nHalfEdges) { + end = halfEdges[iHalfEdge].end(), x3 = end.x, y3 = end.y; + start = halfEdges[++iHalfEdge % nHalfEdges].start(), x2 = start.x, y2 = start.y; + if (abs(x3 - x2) > ε || abs(y3 - y2) > ε) { + halfEdges.splice(iHalfEdge, 0, new d3_geom_voronoiHalfEdge(d3_geom_voronoiCreateBorderEdge(cell.site, end, abs(x3 - x0) < ε && y1 - y3 > ε ? { + x: x0, + y: abs(x2 - x0) < ε ? y2 : y1 + } : abs(y3 - y1) < ε && x1 - x3 > ε ? { + x: abs(y2 - y1) < ε ? x2 : x1, + y: y1 + } : abs(x3 - x1) < ε && y3 - y0 > ε ? { + x: x1, + y: abs(x2 - x1) < ε ? y2 : y0 + } : abs(y3 - y0) < ε && x3 - x0 > ε ? { + x: abs(y2 - y0) < ε ? x2 : x0, + y: y0 + } : null), cell.site, null)); + ++nHalfEdges; + } + } + } + } + function d3_geom_voronoiHalfEdgeOrder(a, b) { + return b.angle - a.angle; + } + function d3_geom_voronoiCircle() { + d3_geom_voronoiRedBlackNode(this); + this.x = this.y = this.arc = this.site = this.cy = null; + } + function d3_geom_voronoiAttachCircle(arc) { + var lArc = arc.P, rArc = arc.N; + if (!lArc || !rArc) return; + var lSite = lArc.site, cSite = arc.site, rSite = rArc.site; + if (lSite === rSite) return; + var bx = cSite.x, by = cSite.y, ax = lSite.x - bx, ay = lSite.y - by, cx = rSite.x - bx, cy = rSite.y - by; + var d = 2 * (ax * cy - ay * cx); + if (d >= -ε2) return; + var ha = ax * ax + ay * ay, hc = cx * cx + cy * cy, x = (cy * ha - ay * hc) / d, y = (ax * hc - cx * ha) / d, cy = y + by; + var circle = d3_geom_voronoiCirclePool.pop() || new d3_geom_voronoiCircle(); + circle.arc = arc; + circle.site = cSite; + circle.x = x + bx; + circle.y = cy + Math.sqrt(x * x + y * y); + circle.cy = cy; + arc.circle = circle; + var before = null, node = d3_geom_voronoiCircles._; + while (node) { + if (circle.y < node.y || circle.y === node.y && circle.x <= node.x) { + if (node.L) node = node.L; else { + before = node.P; + break; + } + } else { + if (node.R) node = node.R; else { + before = node; + break; + } + } + } + d3_geom_voronoiCircles.insert(before, circle); + if (!before) d3_geom_voronoiFirstCircle = circle; + } + function d3_geom_voronoiDetachCircle(arc) { + var circle = arc.circle; + if (circle) { + if (!circle.P) d3_geom_voronoiFirstCircle = circle.N; + d3_geom_voronoiCircles.remove(circle); + d3_geom_voronoiCirclePool.push(circle); + d3_geom_voronoiRedBlackNode(circle); + arc.circle = null; + } + } + function d3_geom_voronoiClipEdges(extent) { + var edges = d3_geom_voronoiEdges, clip = d3_geom_clipLine(extent[0][0], extent[0][1], extent[1][0], extent[1][1]), i = edges.length, e; + while (i--) { + e = edges[i]; + if (!d3_geom_voronoiConnectEdge(e, extent) || !clip(e) || abs(e.a.x - e.b.x) < ε && abs(e.a.y - e.b.y) < ε) { + e.a = e.b = null; + edges.splice(i, 1); + } + } + } + function d3_geom_voronoiConnectEdge(edge, extent) { + var vb = edge.b; + if (vb) return true; + var va = edge.a, x0 = extent[0][0], x1 = extent[1][0], y0 = extent[0][1], y1 = extent[1][1], lSite = edge.l, rSite = edge.r, lx = lSite.x, ly = lSite.y, rx = rSite.x, ry = rSite.y, fx = (lx + rx) / 2, fy = (ly + ry) / 2, fm, fb; + if (ry === ly) { + if (fx < x0 || fx >= x1) return; + if (lx > rx) { + if (!va) va = { + x: fx, + y: y0 + }; else if (va.y >= y1) return; + vb = { + x: fx, + y: y1 + }; + } else { + if (!va) va = { + x: fx, + y: y1 + }; else if (va.y < y0) return; + vb = { + x: fx, + y: y0 + }; + } + } else { + fm = (lx - rx) / (ry - ly); + fb = fy - fm * fx; + if (fm < -1 || fm > 1) { + if (lx > rx) { + if (!va) va = { + x: (y0 - fb) / fm, + y: y0 + }; else if (va.y >= y1) return; + vb = { + x: (y1 - fb) / fm, + y: y1 + }; + } else { + if (!va) va = { + x: (y1 - fb) / fm, + y: y1 + }; else if (va.y < y0) return; + vb = { + x: (y0 - fb) / fm, + y: y0 + }; + } + } else { + if (ly < ry) { + if (!va) va = { + x: x0, + y: fm * x0 + fb + }; else if (va.x >= x1) return; + vb = { + x: x1, + y: fm * x1 + fb + }; + } else { + if (!va) va = { + x: x1, + y: fm * x1 + fb + }; else if (va.x < x0) return; + vb = { + x: x0, + y: fm * x0 + fb + }; + } + } + } + edge.a = va; + edge.b = vb; + return true; + } + function d3_geom_voronoiEdge(lSite, rSite) { + this.l = lSite; + this.r = rSite; + this.a = this.b = null; + } + function d3_geom_voronoiCreateEdge(lSite, rSite, va, vb) { + var edge = new d3_geom_voronoiEdge(lSite, rSite); + d3_geom_voronoiEdges.push(edge); + if (va) d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, va); + if (vb) d3_geom_voronoiSetEdgeEnd(edge, rSite, lSite, vb); + d3_geom_voronoiCells[lSite.i].edges.push(new d3_geom_voronoiHalfEdge(edge, lSite, rSite)); + d3_geom_voronoiCells[rSite.i].edges.push(new d3_geom_voronoiHalfEdge(edge, rSite, lSite)); + return edge; + } + function d3_geom_voronoiCreateBorderEdge(lSite, va, vb) { + var edge = new d3_geom_voronoiEdge(lSite, null); + edge.a = va; + edge.b = vb; + d3_geom_voronoiEdges.push(edge); + return edge; + } + function d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, vertex) { + if (!edge.a && !edge.b) { + edge.a = vertex; + edge.l = lSite; + edge.r = rSite; + } else if (edge.l === rSite) { + edge.b = vertex; + } else { + edge.a = vertex; + } + } + function d3_geom_voronoiHalfEdge(edge, lSite, rSite) { + var va = edge.a, vb = edge.b; + this.edge = edge; + this.site = lSite; + this.angle = rSite ? Math.atan2(rSite.y - lSite.y, rSite.x - lSite.x) : edge.l === lSite ? Math.atan2(vb.x - va.x, va.y - vb.y) : Math.atan2(va.x - vb.x, vb.y - va.y); + } + d3_geom_voronoiHalfEdge.prototype = { + start: function() { + return this.edge.l === this.site ? this.edge.a : this.edge.b; + }, + end: function() { + return this.edge.l === this.site ? this.edge.b : this.edge.a; + } + }; + function d3_geom_voronoiRedBlackTree() { + this._ = null; + } + function d3_geom_voronoiRedBlackNode(node) { + node.U = node.C = node.L = node.R = node.P = node.N = null; + } + d3_geom_voronoiRedBlackTree.prototype = { + insert: function(after, node) { + var parent, grandpa, uncle; + if (after) { + node.P = after; + node.N = after.N; + if (after.N) after.N.P = node; + after.N = node; + if (after.R) { + after = after.R; + while (after.L) after = after.L; + after.L = node; + } else { + after.R = node; + } + parent = after; + } else if (this._) { + after = d3_geom_voronoiRedBlackFirst(this._); + node.P = null; + node.N = after; + after.P = after.L = node; + parent = after; + } else { + node.P = node.N = null; + this._ = node; + parent = null; + } + node.L = node.R = null; + node.U = parent; + node.C = true; + after = node; + while (parent && parent.C) { + grandpa = parent.U; + if (parent === grandpa.L) { + uncle = grandpa.R; + if (uncle && uncle.C) { + parent.C = uncle.C = false; + grandpa.C = true; + after = grandpa; + } else { + if (after === parent.R) { + d3_geom_voronoiRedBlackRotateLeft(this, parent); + after = parent; + parent = after.U; + } + parent.C = false; + grandpa.C = true; + d3_geom_voronoiRedBlackRotateRight(this, grandpa); + } + } else { + uncle = grandpa.L; + if (uncle && uncle.C) { + parent.C = uncle.C = false; + grandpa.C = true; + after = grandpa; + } else { + if (after === parent.L) { + d3_geom_voronoiRedBlackRotateRight(this, parent); + after = parent; + parent = after.U; + } + parent.C = false; + grandpa.C = true; + d3_geom_voronoiRedBlackRotateLeft(this, grandpa); + } + } + parent = after.U; + } + this._.C = false; + }, + remove: function(node) { + if (node.N) node.N.P = node.P; + if (node.P) node.P.N = node.N; + node.N = node.P = null; + var parent = node.U, sibling, left = node.L, right = node.R, next, red; + if (!left) next = right; else if (!right) next = left; else next = d3_geom_voronoiRedBlackFirst(right); + if (parent) { + if (parent.L === node) parent.L = next; else parent.R = next; + } else { + this._ = next; + } + if (left && right) { + red = next.C; + next.C = node.C; + next.L = left; + left.U = next; + if (next !== right) { + parent = next.U; + next.U = node.U; + node = next.R; + parent.L = node; + next.R = right; + right.U = next; + } else { + next.U = parent; + parent = next; + node = next.R; + } + } else { + red = node.C; + node = next; + } + if (node) node.U = parent; + if (red) return; + if (node && node.C) { + node.C = false; + return; + } + do { + if (node === this._) break; + if (node === parent.L) { + sibling = parent.R; + if (sibling.C) { + sibling.C = false; + parent.C = true; + d3_geom_voronoiRedBlackRotateLeft(this, parent); + sibling = parent.R; + } + if (sibling.L && sibling.L.C || sibling.R && sibling.R.C) { + if (!sibling.R || !sibling.R.C) { + sibling.L.C = false; + sibling.C = true; + d3_geom_voronoiRedBlackRotateRight(this, sibling); + sibling = parent.R; + } + sibling.C = parent.C; + parent.C = sibling.R.C = false; + d3_geom_voronoiRedBlackRotateLeft(this, parent); + node = this._; + break; + } + } else { + sibling = parent.L; + if (sibling.C) { + sibling.C = false; + parent.C = true; + d3_geom_voronoiRedBlackRotateRight(this, parent); + sibling = parent.L; + } + if (sibling.L && sibling.L.C || sibling.R && sibling.R.C) { + if (!sibling.L || !sibling.L.C) { + sibling.R.C = false; + sibling.C = true; + d3_geom_voronoiRedBlackRotateLeft(this, sibling); + sibling = parent.L; + } + sibling.C = parent.C; + parent.C = sibling.L.C = false; + d3_geom_voronoiRedBlackRotateRight(this, parent); + node = this._; + break; + } + } + sibling.C = true; + node = parent; + parent = parent.U; + } while (!node.C); + if (node) node.C = false; + } + }; + function d3_geom_voronoiRedBlackRotateLeft(tree, node) { + var p = node, q = node.R, parent = p.U; + if (parent) { + if (parent.L === p) parent.L = q; else parent.R = q; + } else { + tree._ = q; + } + q.U = parent; + p.U = q; + p.R = q.L; + if (p.R) p.R.U = p; + q.L = p; + } + function d3_geom_voronoiRedBlackRotateRight(tree, node) { + var p = node, q = node.L, parent = p.U; + if (parent) { + if (parent.L === p) parent.L = q; else parent.R = q; + } else { + tree._ = q; + } + q.U = parent; + p.U = q; + p.L = q.R; + if (p.L) p.L.U = p; + q.R = p; + } + function d3_geom_voronoiRedBlackFirst(node) { + while (node.L) node = node.L; + return node; + } + function d3_geom_voronoi(sites, bbox) { + var site = sites.sort(d3_geom_voronoiVertexOrder).pop(), x0, y0, circle; + d3_geom_voronoiEdges = []; + d3_geom_voronoiCells = new Array(sites.length); + d3_geom_voronoiBeaches = new d3_geom_voronoiRedBlackTree(); + d3_geom_voronoiCircles = new d3_geom_voronoiRedBlackTree(); + while (true) { + circle = d3_geom_voronoiFirstCircle; + if (site && (!circle || site.y < circle.y || site.y === circle.y && site.x < circle.x)) { + if (site.x !== x0 || site.y !== y0) { + d3_geom_voronoiCells[site.i] = new d3_geom_voronoiCell(site); + d3_geom_voronoiAddBeach(site); + x0 = site.x, y0 = site.y; + } + site = sites.pop(); + } else if (circle) { + d3_geom_voronoiRemoveBeach(circle.arc); + } else { + break; + } + } + if (bbox) d3_geom_voronoiClipEdges(bbox), d3_geom_voronoiCloseCells(bbox); + var diagram = { + cells: d3_geom_voronoiCells, + edges: d3_geom_voronoiEdges + }; + d3_geom_voronoiBeaches = d3_geom_voronoiCircles = d3_geom_voronoiEdges = d3_geom_voronoiCells = null; + return diagram; + } + function d3_geom_voronoiVertexOrder(a, b) { + return b.y - a.y || b.x - a.x; + } + d3.geom.voronoi = function(points) { + var x = d3_geom_pointX, y = d3_geom_pointY, fx = x, fy = y, clipExtent = d3_geom_voronoiClipExtent; + if (points) return voronoi(points); + function voronoi(data) { + var polygons = new Array(data.length), x0 = clipExtent[0][0], y0 = clipExtent[0][1], x1 = clipExtent[1][0], y1 = clipExtent[1][1]; + d3_geom_voronoi(sites(data), clipExtent).cells.forEach(function(cell, i) { + var edges = cell.edges, site = cell.site, polygon = polygons[i] = edges.length ? edges.map(function(e) { + var s = e.start(); + return [ s.x, s.y ]; + }) : site.x >= x0 && site.x <= x1 && site.y >= y0 && site.y <= y1 ? [ [ x0, y1 ], [ x1, y1 ], [ x1, y0 ], [ x0, y0 ] ] : []; + polygon.point = data[i]; + }); + return polygons; + } + function sites(data) { + return data.map(function(d, i) { + return { + x: Math.round(fx(d, i) / ε) * ε, + y: Math.round(fy(d, i) / ε) * ε, + i: i + }; + }); + } + voronoi.links = function(data) { + return d3_geom_voronoi(sites(data)).edges.filter(function(edge) { + return edge.l && edge.r; + }).map(function(edge) { + return { + source: data[edge.l.i], + target: data[edge.r.i] + }; + }); + }; + voronoi.triangles = function(data) { + var triangles = []; + d3_geom_voronoi(sites(data)).cells.forEach(function(cell, i) { + var site = cell.site, edges = cell.edges.sort(d3_geom_voronoiHalfEdgeOrder), j = -1, m = edges.length, e0, s0, e1 = edges[m - 1].edge, s1 = e1.l === site ? e1.r : e1.l; + while (++j < m) { + e0 = e1; + s0 = s1; + e1 = edges[j].edge; + s1 = e1.l === site ? e1.r : e1.l; + if (i < s0.i && i < s1.i && d3_geom_voronoiTriangleArea(site, s0, s1) < 0) { + triangles.push([ data[i], data[s0.i], data[s1.i] ]); + } + } + }); + return triangles; + }; + voronoi.x = function(_) { + return arguments.length ? (fx = d3_functor(x = _), voronoi) : x; + }; + voronoi.y = function(_) { + return arguments.length ? (fy = d3_functor(y = _), voronoi) : y; + }; + voronoi.clipExtent = function(_) { + if (!arguments.length) return clipExtent === d3_geom_voronoiClipExtent ? null : clipExtent; + clipExtent = _ == null ? d3_geom_voronoiClipExtent : _; + return voronoi; + }; + voronoi.size = function(_) { + if (!arguments.length) return clipExtent === d3_geom_voronoiClipExtent ? null : clipExtent && clipExtent[1]; + return voronoi.clipExtent(_ && [ [ 0, 0 ], _ ]); + }; + return voronoi; + }; + var d3_geom_voronoiClipExtent = [ [ -1e6, -1e6 ], [ 1e6, 1e6 ] ]; + function d3_geom_voronoiTriangleArea(a, b, c) { + return (a.x - c.x) * (b.y - a.y) - (a.x - b.x) * (c.y - a.y); + } + d3.geom.delaunay = function(vertices) { + return d3.geom.voronoi().triangles(vertices); + }; + d3.geom.quadtree = function(points, x1, y1, x2, y2) { + var x = d3_geom_pointX, y = d3_geom_pointY, compat; + if (compat = arguments.length) { + x = d3_geom_quadtreeCompatX; + y = d3_geom_quadtreeCompatY; + if (compat === 3) { + y2 = y1; + x2 = x1; + y1 = x1 = 0; + } + return quadtree(points); + } + function quadtree(data) { + var d, fx = d3_functor(x), fy = d3_functor(y), xs, ys, i, n, x1_, y1_, x2_, y2_; + if (x1 != null) { + x1_ = x1, y1_ = y1, x2_ = x2, y2_ = y2; + } else { + x2_ = y2_ = -(x1_ = y1_ = Infinity); + xs = [], ys = []; + n = data.length; + if (compat) for (i = 0; i < n; ++i) { + d = data[i]; + if (d.x < x1_) x1_ = d.x; + if (d.y < y1_) y1_ = d.y; + if (d.x > x2_) x2_ = d.x; + if (d.y > y2_) y2_ = d.y; + xs.push(d.x); + ys.push(d.y); + } else for (i = 0; i < n; ++i) { + var x_ = +fx(d = data[i], i), y_ = +fy(d, i); + if (x_ < x1_) x1_ = x_; + if (y_ < y1_) y1_ = y_; + if (x_ > x2_) x2_ = x_; + if (y_ > y2_) y2_ = y_; + xs.push(x_); + ys.push(y_); + } + } + var dx = x2_ - x1_, dy = y2_ - y1_; + if (dx > dy) y2_ = y1_ + dx; else x2_ = x1_ + dy; + function insert(n, d, x, y, x1, y1, x2, y2) { + if (isNaN(x) || isNaN(y)) return; + if (n.leaf) { + var nx = n.x, ny = n.y; + if (nx != null) { + if (abs(nx - x) + abs(ny - y) < .01) { + insertChild(n, d, x, y, x1, y1, x2, y2); + } else { + var nPoint = n.point; + n.x = n.y = n.point = null; + insertChild(n, nPoint, nx, ny, x1, y1, x2, y2); + insertChild(n, d, x, y, x1, y1, x2, y2); + } + } else { + n.x = x, n.y = y, n.point = d; + } + } else { + insertChild(n, d, x, y, x1, y1, x2, y2); + } + } + function insertChild(n, d, x, y, x1, y1, x2, y2) { + var xm = (x1 + x2) * .5, ym = (y1 + y2) * .5, right = x >= xm, below = y >= ym, i = below << 1 | right; + n.leaf = false; + n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode()); + if (right) x1 = xm; else x2 = xm; + if (below) y1 = ym; else y2 = ym; + insert(n, d, x, y, x1, y1, x2, y2); + } + var root = d3_geom_quadtreeNode(); + root.add = function(d) { + insert(root, d, +fx(d, ++i), +fy(d, i), x1_, y1_, x2_, y2_); + }; + root.visit = function(f) { + d3_geom_quadtreeVisit(f, root, x1_, y1_, x2_, y2_); + }; + root.find = function(point) { + return d3_geom_quadtreeFind(root, point[0], point[1], x1_, y1_, x2_, y2_); + }; + i = -1; + if (x1 == null) { + while (++i < n) { + insert(root, data[i], xs[i], ys[i], x1_, y1_, x2_, y2_); + } + --i; + } else data.forEach(root.add); + xs = ys = data = d = null; + return root; + } + quadtree.x = function(_) { + return arguments.length ? (x = _, quadtree) : x; + }; + quadtree.y = function(_) { + return arguments.length ? (y = _, quadtree) : y; + }; + quadtree.extent = function(_) { + if (!arguments.length) return x1 == null ? null : [ [ x1, y1 ], [ x2, y2 ] ]; + if (_ == null) x1 = y1 = x2 = y2 = null; else x1 = +_[0][0], y1 = +_[0][1], x2 = +_[1][0], + y2 = +_[1][1]; + return quadtree; + }; + quadtree.size = function(_) { + if (!arguments.length) return x1 == null ? null : [ x2 - x1, y2 - y1 ]; + if (_ == null) x1 = y1 = x2 = y2 = null; else x1 = y1 = 0, x2 = +_[0], y2 = +_[1]; + return quadtree; + }; + return quadtree; + }; + function d3_geom_quadtreeCompatX(d) { + return d.x; + } + function d3_geom_quadtreeCompatY(d) { + return d.y; + } + function d3_geom_quadtreeNode() { + return { + leaf: true, + nodes: [], + point: null, + x: null, + y: null + }; + } + function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) { + if (!f(node, x1, y1, x2, y2)) { + var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, children = node.nodes; + if (children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy); + if (children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy); + if (children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2); + if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2); + } + } + function d3_geom_quadtreeFind(root, x, y, x0, y0, x3, y3) { + var minDistance2 = Infinity, closestPoint; + (function find(node, x1, y1, x2, y2) { + if (x1 > x3 || y1 > y3 || x2 < x0 || y2 < y0) return; + if (point = node.point) { + var point, dx = x - node.x, dy = y - node.y, distance2 = dx * dx + dy * dy; + if (distance2 < minDistance2) { + var distance = Math.sqrt(minDistance2 = distance2); + x0 = x - distance, y0 = y - distance; + x3 = x + distance, y3 = y + distance; + closestPoint = point; + } + } + var children = node.nodes, xm = (x1 + x2) * .5, ym = (y1 + y2) * .5, right = x >= xm, below = y >= ym; + for (var i = below << 1 | right, j = i + 4; i < j; ++i) { + if (node = children[i & 3]) switch (i & 3) { + case 0: + find(node, x1, y1, xm, ym); + break; + + case 1: + find(node, xm, y1, x2, ym); + break; + + case 2: + find(node, x1, ym, xm, y2); + break; + + case 3: + find(node, xm, ym, x2, y2); + break; + } + } + })(root, x0, y0, x3, y3); + return closestPoint; + } + d3.interpolateRgb = d3_interpolateRgb; + function d3_interpolateRgb(a, b) { + a = d3.rgb(a); + b = d3.rgb(b); + var ar = a.r, ag = a.g, ab = a.b, br = b.r - ar, bg = b.g - ag, bb = b.b - ab; + return function(t) { + return "#" + d3_rgb_hex(Math.round(ar + br * t)) + d3_rgb_hex(Math.round(ag + bg * t)) + d3_rgb_hex(Math.round(ab + bb * t)); + }; + } + d3.interpolateObject = d3_interpolateObject; + function d3_interpolateObject(a, b) { + var i = {}, c = {}, k; + for (k in a) { + if (k in b) { + i[k] = d3_interpolate(a[k], b[k]); + } else { + c[k] = a[k]; + } + } + for (k in b) { + if (!(k in a)) { + c[k] = b[k]; + } + } + return function(t) { + for (k in i) c[k] = i[k](t); + return c; + }; + } + d3.interpolateNumber = d3_interpolateNumber; + function d3_interpolateNumber(a, b) { + a = +a, b = +b; + return function(t) { + return a * (1 - t) + b * t; + }; + } + d3.interpolateString = d3_interpolateString; + function d3_interpolateString(a, b) { + var bi = d3_interpolate_numberA.lastIndex = d3_interpolate_numberB.lastIndex = 0, am, bm, bs, i = -1, s = [], q = []; + a = a + "", b = b + ""; + while ((am = d3_interpolate_numberA.exec(a)) && (bm = d3_interpolate_numberB.exec(b))) { + if ((bs = bm.index) > bi) { + bs = b.slice(bi, bs); + if (s[i]) s[i] += bs; else s[++i] = bs; + } + if ((am = am[0]) === (bm = bm[0])) { + if (s[i]) s[i] += bm; else s[++i] = bm; + } else { + s[++i] = null; + q.push({ + i: i, + x: d3_interpolateNumber(am, bm) + }); + } + bi = d3_interpolate_numberB.lastIndex; + } + if (bi < b.length) { + bs = b.slice(bi); + if (s[i]) s[i] += bs; else s[++i] = bs; + } + return s.length < 2 ? q[0] ? (b = q[0].x, function(t) { + return b(t) + ""; + }) : function() { + return b; + } : (b = q.length, function(t) { + for (var i = 0, o; i < b; ++i) s[(o = q[i]).i] = o.x(t); + return s.join(""); + }); + } + var d3_interpolate_numberA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g, d3_interpolate_numberB = new RegExp(d3_interpolate_numberA.source, "g"); + d3.interpolate = d3_interpolate; + function d3_interpolate(a, b) { + var i = d3.interpolators.length, f; + while (--i >= 0 && !(f = d3.interpolators[i](a, b))) ; + return f; + } + d3.interpolators = [ function(a, b) { + var t = typeof b; + return (t === "string" ? d3_rgb_names.has(b.toLowerCase()) || /^(#|rgb\(|hsl\()/i.test(b) ? d3_interpolateRgb : d3_interpolateString : b instanceof d3_color ? d3_interpolateRgb : Array.isArray(b) ? d3_interpolateArray : t === "object" && isNaN(b) ? d3_interpolateObject : d3_interpolateNumber)(a, b); + } ]; + d3.interpolateArray = d3_interpolateArray; + function d3_interpolateArray(a, b) { + var x = [], c = [], na = a.length, nb = b.length, n0 = Math.min(a.length, b.length), i; + for (i = 0; i < n0; ++i) x.push(d3_interpolate(a[i], b[i])); + for (;i < na; ++i) c[i] = a[i]; + for (;i < nb; ++i) c[i] = b[i]; + return function(t) { + for (i = 0; i < n0; ++i) c[i] = x[i](t); + return c; + }; + } + var d3_ease_default = function() { + return d3_identity; + }; + var d3_ease = d3.map({ + linear: d3_ease_default, + poly: d3_ease_poly, + quad: function() { + return d3_ease_quad; + }, + cubic: function() { + return d3_ease_cubic; + }, + sin: function() { + return d3_ease_sin; + }, + exp: function() { + return d3_ease_exp; + }, + circle: function() { + return d3_ease_circle; + }, + elastic: d3_ease_elastic, + back: d3_ease_back, + bounce: function() { + return d3_ease_bounce; + } + }); + var d3_ease_mode = d3.map({ + "in": d3_identity, + out: d3_ease_reverse, + "in-out": d3_ease_reflect, + "out-in": function(f) { + return d3_ease_reflect(d3_ease_reverse(f)); + } + }); + d3.ease = function(name) { + var i = name.indexOf("-"), t = i >= 0 ? name.slice(0, i) : name, m = i >= 0 ? name.slice(i + 1) : "in"; + t = d3_ease.get(t) || d3_ease_default; + m = d3_ease_mode.get(m) || d3_identity; + return d3_ease_clamp(m(t.apply(null, d3_arraySlice.call(arguments, 1)))); + }; + function d3_ease_clamp(f) { + return function(t) { + return t <= 0 ? 0 : t >= 1 ? 1 : f(t); + }; + } + function d3_ease_reverse(f) { + return function(t) { + return 1 - f(1 - t); + }; + } + function d3_ease_reflect(f) { + return function(t) { + return .5 * (t < .5 ? f(2 * t) : 2 - f(2 - 2 * t)); + }; + } + function d3_ease_quad(t) { + return t * t; + } + function d3_ease_cubic(t) { + return t * t * t; + } + function d3_ease_cubicInOut(t) { + if (t <= 0) return 0; + if (t >= 1) return 1; + var t2 = t * t, t3 = t2 * t; + return 4 * (t < .5 ? t3 : 3 * (t - t2) + t3 - .75); + } + function d3_ease_poly(e) { + return function(t) { + return Math.pow(t, e); + }; + } + function d3_ease_sin(t) { + return 1 - Math.cos(t * halfπ); + } + function d3_ease_exp(t) { + return Math.pow(2, 10 * (t - 1)); + } + function d3_ease_circle(t) { + return 1 - Math.sqrt(1 - t * t); + } + function d3_ease_elastic(a, p) { + var s; + if (arguments.length < 2) p = .45; + if (arguments.length) s = p / τ * Math.asin(1 / a); else a = 1, s = p / 4; + return function(t) { + return 1 + a * Math.pow(2, -10 * t) * Math.sin((t - s) * τ / p); + }; + } + function d3_ease_back(s) { + if (!s) s = 1.70158; + return function(t) { + return t * t * ((s + 1) * t - s); + }; + } + function d3_ease_bounce(t) { + return t < 1 / 2.75 ? 7.5625 * t * t : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75 : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375 : 7.5625 * (t -= 2.625 / 2.75) * t + .984375; + } + d3.interpolateHcl = d3_interpolateHcl; + function d3_interpolateHcl(a, b) { + a = d3.hcl(a); + b = d3.hcl(b); + var ah = a.h, ac = a.c, al = a.l, bh = b.h - ah, bc = b.c - ac, bl = b.l - al; + if (isNaN(bc)) bc = 0, ac = isNaN(ac) ? b.c : ac; + if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; else if (bh > 180) bh -= 360; else if (bh < -180) bh += 360; + return function(t) { + return d3_hcl_lab(ah + bh * t, ac + bc * t, al + bl * t) + ""; + }; + } + d3.interpolateHsl = d3_interpolateHsl; + function d3_interpolateHsl(a, b) { + a = d3.hsl(a); + b = d3.hsl(b); + var ah = a.h, as = a.s, al = a.l, bh = b.h - ah, bs = b.s - as, bl = b.l - al; + if (isNaN(bs)) bs = 0, as = isNaN(as) ? b.s : as; + if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; else if (bh > 180) bh -= 360; else if (bh < -180) bh += 360; + return function(t) { + return d3_hsl_rgb(ah + bh * t, as + bs * t, al + bl * t) + ""; + }; + } + d3.interpolateLab = d3_interpolateLab; + function d3_interpolateLab(a, b) { + a = d3.lab(a); + b = d3.lab(b); + var al = a.l, aa = a.a, ab = a.b, bl = b.l - al, ba = b.a - aa, bb = b.b - ab; + return function(t) { + return d3_lab_rgb(al + bl * t, aa + ba * t, ab + bb * t) + ""; + }; + } + d3.interpolateRound = d3_interpolateRound; + function d3_interpolateRound(a, b) { + b -= a; + return function(t) { + return Math.round(a + b * t); + }; + } + d3.transform = function(string) { + var g = d3_document.createElementNS(d3.ns.prefix.svg, "g"); + return (d3.transform = function(string) { + if (string != null) { + g.setAttribute("transform", string); + var t = g.transform.baseVal.consolidate(); + } + return new d3_transform(t ? t.matrix : d3_transformIdentity); + })(string); + }; + function d3_transform(m) { + var r0 = [ m.a, m.b ], r1 = [ m.c, m.d ], kx = d3_transformNormalize(r0), kz = d3_transformDot(r0, r1), ky = d3_transformNormalize(d3_transformCombine(r1, r0, -kz)) || 0; + if (r0[0] * r1[1] < r1[0] * r0[1]) { + r0[0] *= -1; + r0[1] *= -1; + kx *= -1; + kz *= -1; + } + this.rotate = (kx ? Math.atan2(r0[1], r0[0]) : Math.atan2(-r1[0], r1[1])) * d3_degrees; + this.translate = [ m.e, m.f ]; + this.scale = [ kx, ky ]; + this.skew = ky ? Math.atan2(kz, ky) * d3_degrees : 0; + } + d3_transform.prototype.toString = function() { + return "translate(" + this.translate + ")rotate(" + this.rotate + ")skewX(" + this.skew + ")scale(" + this.scale + ")"; + }; + function d3_transformDot(a, b) { + return a[0] * b[0] + a[1] * b[1]; + } + function d3_transformNormalize(a) { + var k = Math.sqrt(d3_transformDot(a, a)); + if (k) { + a[0] /= k; + a[1] /= k; + } + return k; + } + function d3_transformCombine(a, b, k) { + a[0] += k * b[0]; + a[1] += k * b[1]; + return a; + } + var d3_transformIdentity = { + a: 1, + b: 0, + c: 0, + d: 1, + e: 0, + f: 0 + }; + d3.interpolateTransform = d3_interpolateTransform; + function d3_interpolateTransformPop(s) { + return s.length ? s.pop() + "," : ""; + } + function d3_interpolateTranslate(ta, tb, s, q) { + if (ta[0] !== tb[0] || ta[1] !== tb[1]) { + var i = s.push("translate(", null, ",", null, ")"); + q.push({ + i: i - 4, + x: d3_interpolateNumber(ta[0], tb[0]) + }, { + i: i - 2, + x: d3_interpolateNumber(ta[1], tb[1]) + }); + } else if (tb[0] || tb[1]) { + s.push("translate(" + tb + ")"); + } + } + function d3_interpolateRotate(ra, rb, s, q) { + if (ra !== rb) { + if (ra - rb > 180) rb += 360; else if (rb - ra > 180) ra += 360; + q.push({ + i: s.push(d3_interpolateTransformPop(s) + "rotate(", null, ")") - 2, + x: d3_interpolateNumber(ra, rb) + }); + } else if (rb) { + s.push(d3_interpolateTransformPop(s) + "rotate(" + rb + ")"); + } + } + function d3_interpolateSkew(wa, wb, s, q) { + if (wa !== wb) { + q.push({ + i: s.push(d3_interpolateTransformPop(s) + "skewX(", null, ")") - 2, + x: d3_interpolateNumber(wa, wb) + }); + } else if (wb) { + s.push(d3_interpolateTransformPop(s) + "skewX(" + wb + ")"); + } + } + function d3_interpolateScale(ka, kb, s, q) { + if (ka[0] !== kb[0] || ka[1] !== kb[1]) { + var i = s.push(d3_interpolateTransformPop(s) + "scale(", null, ",", null, ")"); + q.push({ + i: i - 4, + x: d3_interpolateNumber(ka[0], kb[0]) + }, { + i: i - 2, + x: d3_interpolateNumber(ka[1], kb[1]) + }); + } else if (kb[0] !== 1 || kb[1] !== 1) { + s.push(d3_interpolateTransformPop(s) + "scale(" + kb + ")"); + } + } + function d3_interpolateTransform(a, b) { + var s = [], q = []; + a = d3.transform(a), b = d3.transform(b); + d3_interpolateTranslate(a.translate, b.translate, s, q); + d3_interpolateRotate(a.rotate, b.rotate, s, q); + d3_interpolateSkew(a.skew, b.skew, s, q); + d3_interpolateScale(a.scale, b.scale, s, q); + a = b = null; + return function(t) { + var i = -1, n = q.length, o; + while (++i < n) s[(o = q[i]).i] = o.x(t); + return s.join(""); + }; + } + function d3_uninterpolateNumber(a, b) { + b = (b -= a = +a) || 1 / b; + return function(x) { + return (x - a) / b; + }; + } + function d3_uninterpolateClamp(a, b) { + b = (b -= a = +a) || 1 / b; + return function(x) { + return Math.max(0, Math.min(1, (x - a) / b)); + }; + } + d3.layout = {}; + d3.layout.bundle = function() { + return function(links) { + var paths = [], i = -1, n = links.length; + while (++i < n) paths.push(d3_layout_bundlePath(links[i])); + return paths; + }; + }; + function d3_layout_bundlePath(link) { + var start = link.source, end = link.target, lca = d3_layout_bundleLeastCommonAncestor(start, end), points = [ start ]; + while (start !== lca) { + start = start.parent; + points.push(start); + } + var k = points.length; + while (end !== lca) { + points.splice(k, 0, end); + end = end.parent; + } + return points; + } + function d3_layout_bundleAncestors(node) { + var ancestors = [], parent = node.parent; + while (parent != null) { + ancestors.push(node); + node = parent; + parent = parent.parent; + } + ancestors.push(node); + return ancestors; + } + function d3_layout_bundleLeastCommonAncestor(a, b) { + if (a === b) return a; + var aNodes = d3_layout_bundleAncestors(a), bNodes = d3_layout_bundleAncestors(b), aNode = aNodes.pop(), bNode = bNodes.pop(), sharedNode = null; + while (aNode === bNode) { + sharedNode = aNode; + aNode = aNodes.pop(); + bNode = bNodes.pop(); + } + return sharedNode; + } + d3.layout.chord = function() { + var chord = {}, chords, groups, matrix, n, padding = 0, sortGroups, sortSubgroups, sortChords; + function relayout() { + var subgroups = {}, groupSums = [], groupIndex = d3.range(n), subgroupIndex = [], k, x, x0, i, j; + chords = []; + groups = []; + k = 0, i = -1; + while (++i < n) { + x = 0, j = -1; + while (++j < n) { + x += matrix[i][j]; + } + groupSums.push(x); + subgroupIndex.push(d3.range(n)); + k += x; + } + if (sortGroups) { + groupIndex.sort(function(a, b) { + return sortGroups(groupSums[a], groupSums[b]); + }); + } + if (sortSubgroups) { + subgroupIndex.forEach(function(d, i) { + d.sort(function(a, b) { + return sortSubgroups(matrix[i][a], matrix[i][b]); + }); + }); + } + k = (τ - padding * n) / k; + x = 0, i = -1; + while (++i < n) { + x0 = x, j = -1; + while (++j < n) { + var di = groupIndex[i], dj = subgroupIndex[di][j], v = matrix[di][dj], a0 = x, a1 = x += v * k; + subgroups[di + "-" + dj] = { + index: di, + subindex: dj, + startAngle: a0, + endAngle: a1, + value: v + }; + } + groups[di] = { + index: di, + startAngle: x0, + endAngle: x, + value: groupSums[di] + }; + x += padding; + } + i = -1; + while (++i < n) { + j = i - 1; + while (++j < n) { + var source = subgroups[i + "-" + j], target = subgroups[j + "-" + i]; + if (source.value || target.value) { + chords.push(source.value < target.value ? { + source: target, + target: source + } : { + source: source, + target: target + }); + } + } + } + if (sortChords) resort(); + } + function resort() { + chords.sort(function(a, b) { + return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2); + }); + } + chord.matrix = function(x) { + if (!arguments.length) return matrix; + n = (matrix = x) && matrix.length; + chords = groups = null; + return chord; + }; + chord.padding = function(x) { + if (!arguments.length) return padding; + padding = x; + chords = groups = null; + return chord; + }; + chord.sortGroups = function(x) { + if (!arguments.length) return sortGroups; + sortGroups = x; + chords = groups = null; + return chord; + }; + chord.sortSubgroups = function(x) { + if (!arguments.length) return sortSubgroups; + sortSubgroups = x; + chords = null; + return chord; + }; + chord.sortChords = function(x) { + if (!arguments.length) return sortChords; + sortChords = x; + if (chords) resort(); + return chord; + }; + chord.chords = function() { + if (!chords) relayout(); + return chords; + }; + chord.groups = function() { + if (!groups) relayout(); + return groups; + }; + return chord; + }; + d3.layout.force = function() { + var force = {}, event = d3.dispatch("start", "tick", "end"), timer, size = [ 1, 1 ], drag, alpha, friction = .9, linkDistance = d3_layout_forceLinkDistance, linkStrength = d3_layout_forceLinkStrength, charge = -30, chargeDistance2 = d3_layout_forceChargeDistance2, gravity = .1, theta2 = .64, nodes = [], links = [], distances, strengths, charges; + function repulse(node) { + return function(quad, x1, _, x2) { + if (quad.point !== node) { + var dx = quad.cx - node.x, dy = quad.cy - node.y, dw = x2 - x1, dn = dx * dx + dy * dy; + if (dw * dw / theta2 < dn) { + if (dn < chargeDistance2) { + var k = quad.charge / dn; + node.px -= dx * k; + node.py -= dy * k; + } + return true; + } + if (quad.point && dn && dn < chargeDistance2) { + var k = quad.pointCharge / dn; + node.px -= dx * k; + node.py -= dy * k; + } + } + return !quad.charge; + }; + } + force.tick = function() { + if ((alpha *= .99) < .005) { + timer = null; + event.end({ + type: "end", + alpha: alpha = 0 + }); + return true; + } + var n = nodes.length, m = links.length, q, i, o, s, t, l, k, x, y; + for (i = 0; i < m; ++i) { + o = links[i]; + s = o.source; + t = o.target; + x = t.x - s.x; + y = t.y - s.y; + if (l = x * x + y * y) { + l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l; + x *= l; + y *= l; + t.x -= x * (k = s.weight + t.weight ? s.weight / (s.weight + t.weight) : .5); + t.y -= y * k; + s.x += x * (k = 1 - k); + s.y += y * k; + } + } + if (k = alpha * gravity) { + x = size[0] / 2; + y = size[1] / 2; + i = -1; + if (k) while (++i < n) { + o = nodes[i]; + o.x += (x - o.x) * k; + o.y += (y - o.y) * k; + } + } + if (charge) { + d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges); + i = -1; + while (++i < n) { + if (!(o = nodes[i]).fixed) { + q.visit(repulse(o)); + } + } + } + i = -1; + while (++i < n) { + o = nodes[i]; + if (o.fixed) { + o.x = o.px; + o.y = o.py; + } else { + o.x -= (o.px - (o.px = o.x)) * friction; + o.y -= (o.py - (o.py = o.y)) * friction; + } + } + event.tick({ + type: "tick", + alpha: alpha + }); + }; + force.nodes = function(x) { + if (!arguments.length) return nodes; + nodes = x; + return force; + }; + force.links = function(x) { + if (!arguments.length) return links; + links = x; + return force; + }; + force.size = function(x) { + if (!arguments.length) return size; + size = x; + return force; + }; + force.linkDistance = function(x) { + if (!arguments.length) return linkDistance; + linkDistance = typeof x === "function" ? x : +x; + return force; + }; + force.distance = force.linkDistance; + force.linkStrength = function(x) { + if (!arguments.length) return linkStrength; + linkStrength = typeof x === "function" ? x : +x; + return force; + }; + force.friction = function(x) { + if (!arguments.length) return friction; + friction = +x; + return force; + }; + force.charge = function(x) { + if (!arguments.length) return charge; + charge = typeof x === "function" ? x : +x; + return force; + }; + force.chargeDistance = function(x) { + if (!arguments.length) return Math.sqrt(chargeDistance2); + chargeDistance2 = x * x; + return force; + }; + force.gravity = function(x) { + if (!arguments.length) return gravity; + gravity = +x; + return force; + }; + force.theta = function(x) { + if (!arguments.length) return Math.sqrt(theta2); + theta2 = x * x; + return force; + }; + force.alpha = function(x) { + if (!arguments.length) return alpha; + x = +x; + if (alpha) { + if (x > 0) { + alpha = x; + } else { + timer.c = null, timer.t = NaN, timer = null; + event.end({ + type: "end", + alpha: alpha = 0 + }); + } + } else if (x > 0) { + event.start({ + type: "start", + alpha: alpha = x + }); + timer = d3_timer(force.tick); + } + return force; + }; + force.start = function() { + var i, n = nodes.length, m = links.length, w = size[0], h = size[1], neighbors, o; + for (i = 0; i < n; ++i) { + (o = nodes[i]).index = i; + o.weight = 0; + } + for (i = 0; i < m; ++i) { + o = links[i]; + if (typeof o.source == "number") o.source = nodes[o.source]; + if (typeof o.target == "number") o.target = nodes[o.target]; + ++o.source.weight; + ++o.target.weight; + } + for (i = 0; i < n; ++i) { + o = nodes[i]; + if (isNaN(o.x)) o.x = position("x", w); + if (isNaN(o.y)) o.y = position("y", h); + if (isNaN(o.px)) o.px = o.x; + if (isNaN(o.py)) o.py = o.y; + } + distances = []; + if (typeof linkDistance === "function") for (i = 0; i < m; ++i) distances[i] = +linkDistance.call(this, links[i], i); else for (i = 0; i < m; ++i) distances[i] = linkDistance; + strengths = []; + if (typeof linkStrength === "function") for (i = 0; i < m; ++i) strengths[i] = +linkStrength.call(this, links[i], i); else for (i = 0; i < m; ++i) strengths[i] = linkStrength; + charges = []; + if (typeof charge === "function") for (i = 0; i < n; ++i) charges[i] = +charge.call(this, nodes[i], i); else for (i = 0; i < n; ++i) charges[i] = charge; + function position(dimension, size) { + if (!neighbors) { + neighbors = new Array(n); + for (j = 0; j < n; ++j) { + neighbors[j] = []; + } + for (j = 0; j < m; ++j) { + var o = links[j]; + neighbors[o.source.index].push(o.target); + neighbors[o.target.index].push(o.source); + } + } + var candidates = neighbors[i], j = -1, l = candidates.length, x; + while (++j < l) if (!isNaN(x = candidates[j][dimension])) return x; + return Math.random() * size; + } + return force.resume(); + }; + force.resume = function() { + return force.alpha(.1); + }; + force.stop = function() { + return force.alpha(0); + }; + force.drag = function() { + if (!drag) drag = d3.behavior.drag().origin(d3_identity).on("dragstart.force", d3_layout_forceDragstart).on("drag.force", dragmove).on("dragend.force", d3_layout_forceDragend); + if (!arguments.length) return drag; + this.on("mouseover.force", d3_layout_forceMouseover).on("mouseout.force", d3_layout_forceMouseout).call(drag); + }; + function dragmove(d) { + d.px = d3.event.x, d.py = d3.event.y; + force.resume(); + } + return d3.rebind(force, event, "on"); + }; + function d3_layout_forceDragstart(d) { + d.fixed |= 2; + } + function d3_layout_forceDragend(d) { + d.fixed &= ~6; + } + function d3_layout_forceMouseover(d) { + d.fixed |= 4; + d.px = d.x, d.py = d.y; + } + function d3_layout_forceMouseout(d) { + d.fixed &= ~4; + } + function d3_layout_forceAccumulate(quad, alpha, charges) { + var cx = 0, cy = 0; + quad.charge = 0; + if (!quad.leaf) { + var nodes = quad.nodes, n = nodes.length, i = -1, c; + while (++i < n) { + c = nodes[i]; + if (c == null) continue; + d3_layout_forceAccumulate(c, alpha, charges); + quad.charge += c.charge; + cx += c.charge * c.cx; + cy += c.charge * c.cy; + } + } + if (quad.point) { + if (!quad.leaf) { + quad.point.x += Math.random() - .5; + quad.point.y += Math.random() - .5; + } + var k = alpha * charges[quad.point.index]; + quad.charge += quad.pointCharge = k; + cx += k * quad.point.x; + cy += k * quad.point.y; + } + quad.cx = cx / quad.charge; + quad.cy = cy / quad.charge; + } + var d3_layout_forceLinkDistance = 20, d3_layout_forceLinkStrength = 1, d3_layout_forceChargeDistance2 = Infinity; + d3.layout.hierarchy = function() { + var sort = d3_layout_hierarchySort, children = d3_layout_hierarchyChildren, value = d3_layout_hierarchyValue; + function hierarchy(root) { + var stack = [ root ], nodes = [], node; + root.depth = 0; + while ((node = stack.pop()) != null) { + nodes.push(node); + if ((childs = children.call(hierarchy, node, node.depth)) && (n = childs.length)) { + var n, childs, child; + while (--n >= 0) { + stack.push(child = childs[n]); + child.parent = node; + child.depth = node.depth + 1; + } + if (value) node.value = 0; + node.children = childs; + } else { + if (value) node.value = +value.call(hierarchy, node, node.depth) || 0; + delete node.children; + } + } + d3_layout_hierarchyVisitAfter(root, function(node) { + var childs, parent; + if (sort && (childs = node.children)) childs.sort(sort); + if (value && (parent = node.parent)) parent.value += node.value; + }); + return nodes; + } + hierarchy.sort = function(x) { + if (!arguments.length) return sort; + sort = x; + return hierarchy; + }; + hierarchy.children = function(x) { + if (!arguments.length) return children; + children = x; + return hierarchy; + }; + hierarchy.value = function(x) { + if (!arguments.length) return value; + value = x; + return hierarchy; + }; + hierarchy.revalue = function(root) { + if (value) { + d3_layout_hierarchyVisitBefore(root, function(node) { + if (node.children) node.value = 0; + }); + d3_layout_hierarchyVisitAfter(root, function(node) { + var parent; + if (!node.children) node.value = +value.call(hierarchy, node, node.depth) || 0; + if (parent = node.parent) parent.value += node.value; + }); + } + return root; + }; + return hierarchy; + }; + function d3_layout_hierarchyRebind(object, hierarchy) { + d3.rebind(object, hierarchy, "sort", "children", "value"); + object.nodes = object; + object.links = d3_layout_hierarchyLinks; + return object; + } + function d3_layout_hierarchyVisitBefore(node, callback) { + var nodes = [ node ]; + while ((node = nodes.pop()) != null) { + callback(node); + if ((children = node.children) && (n = children.length)) { + var n, children; + while (--n >= 0) nodes.push(children[n]); + } + } + } + function d3_layout_hierarchyVisitAfter(node, callback) { + var nodes = [ node ], nodes2 = []; + while ((node = nodes.pop()) != null) { + nodes2.push(node); + if ((children = node.children) && (n = children.length)) { + var i = -1, n, children; + while (++i < n) nodes.push(children[i]); + } + } + while ((node = nodes2.pop()) != null) { + callback(node); + } + } + function d3_layout_hierarchyChildren(d) { + return d.children; + } + function d3_layout_hierarchyValue(d) { + return d.value; + } + function d3_layout_hierarchySort(a, b) { + return b.value - a.value; + } + function d3_layout_hierarchyLinks(nodes) { + return d3.merge(nodes.map(function(parent) { + return (parent.children || []).map(function(child) { + return { + source: parent, + target: child + }; + }); + })); + } + d3.layout.partition = function() { + var hierarchy = d3.layout.hierarchy(), size = [ 1, 1 ]; + function position(node, x, dx, dy) { + var children = node.children; + node.x = x; + node.y = node.depth * dy; + node.dx = dx; + node.dy = dy; + if (children && (n = children.length)) { + var i = -1, n, c, d; + dx = node.value ? dx / node.value : 0; + while (++i < n) { + position(c = children[i], x, d = c.value * dx, dy); + x += d; + } + } + } + function depth(node) { + var children = node.children, d = 0; + if (children && (n = children.length)) { + var i = -1, n; + while (++i < n) d = Math.max(d, depth(children[i])); + } + return 1 + d; + } + function partition(d, i) { + var nodes = hierarchy.call(this, d, i); + position(nodes[0], 0, size[0], size[1] / depth(nodes[0])); + return nodes; + } + partition.size = function(x) { + if (!arguments.length) return size; + size = x; + return partition; + }; + return d3_layout_hierarchyRebind(partition, hierarchy); + }; + d3.layout.pie = function() { + var value = Number, sort = d3_layout_pieSortByValue, startAngle = 0, endAngle = τ, padAngle = 0; + function pie(data) { + var n = data.length, values = data.map(function(d, i) { + return +value.call(pie, d, i); + }), a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle), da = (typeof endAngle === "function" ? endAngle.apply(this, arguments) : endAngle) - a, p = Math.min(Math.abs(da) / n, +(typeof padAngle === "function" ? padAngle.apply(this, arguments) : padAngle)), pa = p * (da < 0 ? -1 : 1), sum = d3.sum(values), k = sum ? (da - n * pa) / sum : 0, index = d3.range(n), arcs = [], v; + if (sort != null) index.sort(sort === d3_layout_pieSortByValue ? function(i, j) { + return values[j] - values[i]; + } : function(i, j) { + return sort(data[i], data[j]); + }); + index.forEach(function(i) { + arcs[i] = { + data: data[i], + value: v = values[i], + startAngle: a, + endAngle: a += v * k + pa, + padAngle: p + }; + }); + return arcs; + } + pie.value = function(_) { + if (!arguments.length) return value; + value = _; + return pie; + }; + pie.sort = function(_) { + if (!arguments.length) return sort; + sort = _; + return pie; + }; + pie.startAngle = function(_) { + if (!arguments.length) return startAngle; + startAngle = _; + return pie; + }; + pie.endAngle = function(_) { + if (!arguments.length) return endAngle; + endAngle = _; + return pie; + }; + pie.padAngle = function(_) { + if (!arguments.length) return padAngle; + padAngle = _; + return pie; + }; + return pie; + }; + var d3_layout_pieSortByValue = {}; + d3.layout.stack = function() { + var values = d3_identity, order = d3_layout_stackOrderDefault, offset = d3_layout_stackOffsetZero, out = d3_layout_stackOut, x = d3_layout_stackX, y = d3_layout_stackY; + function stack(data, index) { + if (!(n = data.length)) return data; + var series = data.map(function(d, i) { + return values.call(stack, d, i); + }); + var points = series.map(function(d) { + return d.map(function(v, i) { + return [ x.call(stack, v, i), y.call(stack, v, i) ]; + }); + }); + var orders = order.call(stack, points, index); + series = d3.permute(series, orders); + points = d3.permute(points, orders); + var offsets = offset.call(stack, points, index); + var m = series[0].length, n, i, j, o; + for (j = 0; j < m; ++j) { + out.call(stack, series[0][j], o = offsets[j], points[0][j][1]); + for (i = 1; i < n; ++i) { + out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]); + } + } + return data; + } + stack.values = function(x) { + if (!arguments.length) return values; + values = x; + return stack; + }; + stack.order = function(x) { + if (!arguments.length) return order; + order = typeof x === "function" ? x : d3_layout_stackOrders.get(x) || d3_layout_stackOrderDefault; + return stack; + }; + stack.offset = function(x) { + if (!arguments.length) return offset; + offset = typeof x === "function" ? x : d3_layout_stackOffsets.get(x) || d3_layout_stackOffsetZero; + return stack; + }; + stack.x = function(z) { + if (!arguments.length) return x; + x = z; + return stack; + }; + stack.y = function(z) { + if (!arguments.length) return y; + y = z; + return stack; + }; + stack.out = function(z) { + if (!arguments.length) return out; + out = z; + return stack; + }; + return stack; + }; + function d3_layout_stackX(d) { + return d.x; + } + function d3_layout_stackY(d) { + return d.y; + } + function d3_layout_stackOut(d, y0, y) { + d.y0 = y0; + d.y = y; + } + var d3_layout_stackOrders = d3.map({ + "inside-out": function(data) { + var n = data.length, i, j, max = data.map(d3_layout_stackMaxIndex), sums = data.map(d3_layout_stackReduceSum), index = d3.range(n).sort(function(a, b) { + return max[a] - max[b]; + }), top = 0, bottom = 0, tops = [], bottoms = []; + for (i = 0; i < n; ++i) { + j = index[i]; + if (top < bottom) { + top += sums[j]; + tops.push(j); + } else { + bottom += sums[j]; + bottoms.push(j); + } + } + return bottoms.reverse().concat(tops); + }, + reverse: function(data) { + return d3.range(data.length).reverse(); + }, + "default": d3_layout_stackOrderDefault + }); + var d3_layout_stackOffsets = d3.map({ + silhouette: function(data) { + var n = data.length, m = data[0].length, sums = [], max = 0, i, j, o, y0 = []; + for (j = 0; j < m; ++j) { + for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; + if (o > max) max = o; + sums.push(o); + } + for (j = 0; j < m; ++j) { + y0[j] = (max - sums[j]) / 2; + } + return y0; + }, + wiggle: function(data) { + var n = data.length, x = data[0], m = x.length, i, j, k, s1, s2, s3, dx, o, o0, y0 = []; + y0[0] = o = o0 = 0; + for (j = 1; j < m; ++j) { + for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1]; + for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) { + for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) { + s3 += (data[k][j][1] - data[k][j - 1][1]) / dx; + } + s2 += s3 * data[i][j][1]; + } + y0[j] = o -= s1 ? s2 / s1 * dx : 0; + if (o < o0) o0 = o; + } + for (j = 0; j < m; ++j) y0[j] -= o0; + return y0; + }, + expand: function(data) { + var n = data.length, m = data[0].length, k = 1 / n, i, j, o, y0 = []; + for (j = 0; j < m; ++j) { + for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; + if (o) for (i = 0; i < n; i++) data[i][j][1] /= o; else for (i = 0; i < n; i++) data[i][j][1] = k; + } + for (j = 0; j < m; ++j) y0[j] = 0; + return y0; + }, + zero: d3_layout_stackOffsetZero + }); + function d3_layout_stackOrderDefault(data) { + return d3.range(data.length); + } + function d3_layout_stackOffsetZero(data) { + var j = -1, m = data[0].length, y0 = []; + while (++j < m) y0[j] = 0; + return y0; + } + function d3_layout_stackMaxIndex(array) { + var i = 1, j = 0, v = array[0][1], k, n = array.length; + for (;i < n; ++i) { + if ((k = array[i][1]) > v) { + j = i; + v = k; + } + } + return j; + } + function d3_layout_stackReduceSum(d) { + return d.reduce(d3_layout_stackSum, 0); + } + function d3_layout_stackSum(p, d) { + return p + d[1]; + } + d3.layout.histogram = function() { + var frequency = true, valuer = Number, ranger = d3_layout_histogramRange, binner = d3_layout_histogramBinSturges; + function histogram(data, i) { + var bins = [], values = data.map(valuer, this), range = ranger.call(this, values, i), thresholds = binner.call(this, range, values, i), bin, i = -1, n = values.length, m = thresholds.length - 1, k = frequency ? 1 : 1 / n, x; + while (++i < m) { + bin = bins[i] = []; + bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]); + bin.y = 0; + } + if (m > 0) { + i = -1; + while (++i < n) { + x = values[i]; + if (x >= range[0] && x <= range[1]) { + bin = bins[d3.bisect(thresholds, x, 1, m) - 1]; + bin.y += k; + bin.push(data[i]); + } + } + } + return bins; + } + histogram.value = function(x) { + if (!arguments.length) return valuer; + valuer = x; + return histogram; + }; + histogram.range = function(x) { + if (!arguments.length) return ranger; + ranger = d3_functor(x); + return histogram; + }; + histogram.bins = function(x) { + if (!arguments.length) return binner; + binner = typeof x === "number" ? function(range) { + return d3_layout_histogramBinFixed(range, x); + } : d3_functor(x); + return histogram; + }; + histogram.frequency = function(x) { + if (!arguments.length) return frequency; + frequency = !!x; + return histogram; + }; + return histogram; + }; + function d3_layout_histogramBinSturges(range, values) { + return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1)); + } + function d3_layout_histogramBinFixed(range, n) { + var x = -1, b = +range[0], m = (range[1] - b) / n, f = []; + while (++x <= n) f[x] = m * x + b; + return f; + } + function d3_layout_histogramRange(values) { + return [ d3.min(values), d3.max(values) ]; + } + d3.layout.pack = function() { + var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), padding = 0, size = [ 1, 1 ], radius; + function pack(d, i) { + var nodes = hierarchy.call(this, d, i), root = nodes[0], w = size[0], h = size[1], r = radius == null ? Math.sqrt : typeof radius === "function" ? radius : function() { + return radius; + }; + root.x = root.y = 0; + d3_layout_hierarchyVisitAfter(root, function(d) { + d.r = +r(d.value); + }); + d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings); + if (padding) { + var dr = padding * (radius ? 1 : Math.max(2 * root.r / w, 2 * root.r / h)) / 2; + d3_layout_hierarchyVisitAfter(root, function(d) { + d.r += dr; + }); + d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings); + d3_layout_hierarchyVisitAfter(root, function(d) { + d.r -= dr; + }); + } + d3_layout_packTransform(root, w / 2, h / 2, radius ? 1 : 1 / Math.max(2 * root.r / w, 2 * root.r / h)); + return nodes; + } + pack.size = function(_) { + if (!arguments.length) return size; + size = _; + return pack; + }; + pack.radius = function(_) { + if (!arguments.length) return radius; + radius = _ == null || typeof _ === "function" ? _ : +_; + return pack; + }; + pack.padding = function(_) { + if (!arguments.length) return padding; + padding = +_; + return pack; + }; + return d3_layout_hierarchyRebind(pack, hierarchy); + }; + function d3_layout_packSort(a, b) { + return a.value - b.value; + } + function d3_layout_packInsert(a, b) { + var c = a._pack_next; + a._pack_next = b; + b._pack_prev = a; + b._pack_next = c; + c._pack_prev = b; + } + function d3_layout_packSplice(a, b) { + a._pack_next = b; + b._pack_prev = a; + } + function d3_layout_packIntersects(a, b) { + var dx = b.x - a.x, dy = b.y - a.y, dr = a.r + b.r; + return .999 * dr * dr > dx * dx + dy * dy; + } + function d3_layout_packSiblings(node) { + if (!(nodes = node.children) || !(n = nodes.length)) return; + var nodes, xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, a, b, c, i, j, k, n; + function bound(node) { + xMin = Math.min(node.x - node.r, xMin); + xMax = Math.max(node.x + node.r, xMax); + yMin = Math.min(node.y - node.r, yMin); + yMax = Math.max(node.y + node.r, yMax); + } + nodes.forEach(d3_layout_packLink); + a = nodes[0]; + a.x = -a.r; + a.y = 0; + bound(a); + if (n > 1) { + b = nodes[1]; + b.x = b.r; + b.y = 0; + bound(b); + if (n > 2) { + c = nodes[2]; + d3_layout_packPlace(a, b, c); + bound(c); + d3_layout_packInsert(a, c); + a._pack_prev = c; + d3_layout_packInsert(c, b); + b = a._pack_next; + for (i = 3; i < n; i++) { + d3_layout_packPlace(a, b, c = nodes[i]); + var isect = 0, s1 = 1, s2 = 1; + for (j = b._pack_next; j !== b; j = j._pack_next, s1++) { + if (d3_layout_packIntersects(j, c)) { + isect = 1; + break; + } + } + if (isect == 1) { + for (k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) { + if (d3_layout_packIntersects(k, c)) { + break; + } + } + } + if (isect) { + if (s1 < s2 || s1 == s2 && b.r < a.r) d3_layout_packSplice(a, b = j); else d3_layout_packSplice(a = k, b); + i--; + } else { + d3_layout_packInsert(a, c); + b = c; + bound(c); + } + } + } + } + var cx = (xMin + xMax) / 2, cy = (yMin + yMax) / 2, cr = 0; + for (i = 0; i < n; i++) { + c = nodes[i]; + c.x -= cx; + c.y -= cy; + cr = Math.max(cr, c.r + Math.sqrt(c.x * c.x + c.y * c.y)); + } + node.r = cr; + nodes.forEach(d3_layout_packUnlink); + } + function d3_layout_packLink(node) { + node._pack_next = node._pack_prev = node; + } + function d3_layout_packUnlink(node) { + delete node._pack_next; + delete node._pack_prev; + } + function d3_layout_packTransform(node, x, y, k) { + var children = node.children; + node.x = x += k * node.x; + node.y = y += k * node.y; + node.r *= k; + if (children) { + var i = -1, n = children.length; + while (++i < n) d3_layout_packTransform(children[i], x, y, k); + } + } + function d3_layout_packPlace(a, b, c) { + var db = a.r + c.r, dx = b.x - a.x, dy = b.y - a.y; + if (db && (dx || dy)) { + var da = b.r + c.r, dc = dx * dx + dy * dy; + da *= da; + db *= db; + var x = .5 + (db - da) / (2 * dc), y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc); + c.x = a.x + x * dx + y * dy; + c.y = a.y + x * dy - y * dx; + } else { + c.x = a.x + db; + c.y = a.y; + } + } + d3.layout.tree = function() { + var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = null; + function tree(d, i) { + var nodes = hierarchy.call(this, d, i), root0 = nodes[0], root1 = wrapTree(root0); + d3_layout_hierarchyVisitAfter(root1, firstWalk), root1.parent.m = -root1.z; + d3_layout_hierarchyVisitBefore(root1, secondWalk); + if (nodeSize) d3_layout_hierarchyVisitBefore(root0, sizeNode); else { + var left = root0, right = root0, bottom = root0; + d3_layout_hierarchyVisitBefore(root0, function(node) { + if (node.x < left.x) left = node; + if (node.x > right.x) right = node; + if (node.depth > bottom.depth) bottom = node; + }); + var tx = separation(left, right) / 2 - left.x, kx = size[0] / (right.x + separation(right, left) / 2 + tx), ky = size[1] / (bottom.depth || 1); + d3_layout_hierarchyVisitBefore(root0, function(node) { + node.x = (node.x + tx) * kx; + node.y = node.depth * ky; + }); + } + return nodes; + } + function wrapTree(root0) { + var root1 = { + A: null, + children: [ root0 ] + }, queue = [ root1 ], node1; + while ((node1 = queue.pop()) != null) { + for (var children = node1.children, child, i = 0, n = children.length; i < n; ++i) { + queue.push((children[i] = child = { + _: children[i], + parent: node1, + children: (child = children[i].children) && child.slice() || [], + A: null, + a: null, + z: 0, + m: 0, + c: 0, + s: 0, + t: null, + i: i + }).a = child); + } + } + return root1.children[0]; + } + function firstWalk(v) { + var children = v.children, siblings = v.parent.children, w = v.i ? siblings[v.i - 1] : null; + if (children.length) { + d3_layout_treeShift(v); + var midpoint = (children[0].z + children[children.length - 1].z) / 2; + if (w) { + v.z = w.z + separation(v._, w._); + v.m = v.z - midpoint; + } else { + v.z = midpoint; + } + } else if (w) { + v.z = w.z + separation(v._, w._); + } + v.parent.A = apportion(v, w, v.parent.A || siblings[0]); + } + function secondWalk(v) { + v._.x = v.z + v.parent.m; + v.m += v.parent.m; + } + function apportion(v, w, ancestor) { + if (w) { + var vip = v, vop = v, vim = w, vom = vip.parent.children[0], sip = vip.m, sop = vop.m, sim = vim.m, som = vom.m, shift; + while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) { + vom = d3_layout_treeLeft(vom); + vop = d3_layout_treeRight(vop); + vop.a = v; + shift = vim.z + sim - vip.z - sip + separation(vim._, vip._); + if (shift > 0) { + d3_layout_treeMove(d3_layout_treeAncestor(vim, v, ancestor), v, shift); + sip += shift; + sop += shift; + } + sim += vim.m; + sip += vip.m; + som += vom.m; + sop += vop.m; + } + if (vim && !d3_layout_treeRight(vop)) { + vop.t = vim; + vop.m += sim - sop; + } + if (vip && !d3_layout_treeLeft(vom)) { + vom.t = vip; + vom.m += sip - som; + ancestor = v; + } + } + return ancestor; + } + function sizeNode(node) { + node.x *= size[0]; + node.y = node.depth * size[1]; + } + tree.separation = function(x) { + if (!arguments.length) return separation; + separation = x; + return tree; + }; + tree.size = function(x) { + if (!arguments.length) return nodeSize ? null : size; + nodeSize = (size = x) == null ? sizeNode : null; + return tree; + }; + tree.nodeSize = function(x) { + if (!arguments.length) return nodeSize ? size : null; + nodeSize = (size = x) == null ? null : sizeNode; + return tree; + }; + return d3_layout_hierarchyRebind(tree, hierarchy); + }; + function d3_layout_treeSeparation(a, b) { + return a.parent == b.parent ? 1 : 2; + } + function d3_layout_treeLeft(v) { + var children = v.children; + return children.length ? children[0] : v.t; + } + function d3_layout_treeRight(v) { + var children = v.children, n; + return (n = children.length) ? children[n - 1] : v.t; + } + function d3_layout_treeMove(wm, wp, shift) { + var change = shift / (wp.i - wm.i); + wp.c -= change; + wp.s += shift; + wm.c += change; + wp.z += shift; + wp.m += shift; + } + function d3_layout_treeShift(v) { + var shift = 0, change = 0, children = v.children, i = children.length, w; + while (--i >= 0) { + w = children[i]; + w.z += shift; + w.m += shift; + shift += w.s + (change += w.c); + } + } + function d3_layout_treeAncestor(vim, v, ancestor) { + return vim.a.parent === v.parent ? vim.a : ancestor; + } + d3.layout.cluster = function() { + var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = false; + function cluster(d, i) { + var nodes = hierarchy.call(this, d, i), root = nodes[0], previousNode, x = 0; + d3_layout_hierarchyVisitAfter(root, function(node) { + var children = node.children; + if (children && children.length) { + node.x = d3_layout_clusterX(children); + node.y = d3_layout_clusterY(children); + } else { + node.x = previousNode ? x += separation(node, previousNode) : 0; + node.y = 0; + previousNode = node; + } + }); + var left = d3_layout_clusterLeft(root), right = d3_layout_clusterRight(root), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2; + d3_layout_hierarchyVisitAfter(root, nodeSize ? function(node) { + node.x = (node.x - root.x) * size[0]; + node.y = (root.y - node.y) * size[1]; + } : function(node) { + node.x = (node.x - x0) / (x1 - x0) * size[0]; + node.y = (1 - (root.y ? node.y / root.y : 1)) * size[1]; + }); + return nodes; + } + cluster.separation = function(x) { + if (!arguments.length) return separation; + separation = x; + return cluster; + }; + cluster.size = function(x) { + if (!arguments.length) return nodeSize ? null : size; + nodeSize = (size = x) == null; + return cluster; + }; + cluster.nodeSize = function(x) { + if (!arguments.length) return nodeSize ? size : null; + nodeSize = (size = x) != null; + return cluster; + }; + return d3_layout_hierarchyRebind(cluster, hierarchy); + }; + function d3_layout_clusterY(children) { + return 1 + d3.max(children, function(child) { + return child.y; + }); + } + function d3_layout_clusterX(children) { + return children.reduce(function(x, child) { + return x + child.x; + }, 0) / children.length; + } + function d3_layout_clusterLeft(node) { + var children = node.children; + return children && children.length ? d3_layout_clusterLeft(children[0]) : node; + } + function d3_layout_clusterRight(node) { + var children = node.children, n; + return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node; + } + d3.layout.treemap = function() { + var hierarchy = d3.layout.hierarchy(), round = Math.round, size = [ 1, 1 ], padding = null, pad = d3_layout_treemapPadNull, sticky = false, stickies, mode = "squarify", ratio = .5 * (1 + Math.sqrt(5)); + function scale(children, k) { + var i = -1, n = children.length, child, area; + while (++i < n) { + area = (child = children[i]).value * (k < 0 ? 0 : k); + child.area = isNaN(area) || area <= 0 ? 0 : area; + } + } + function squarify(node) { + var children = node.children; + if (children && children.length) { + var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = mode === "slice" ? rect.dx : mode === "dice" ? rect.dy : mode === "slice-dice" ? node.depth & 1 ? rect.dy : rect.dx : Math.min(rect.dx, rect.dy), n; + scale(remaining, rect.dx * rect.dy / node.value); + row.area = 0; + while ((n = remaining.length) > 0) { + row.push(child = remaining[n - 1]); + row.area += child.area; + if (mode !== "squarify" || (score = worst(row, u)) <= best) { + remaining.pop(); + best = score; + } else { + row.area -= row.pop().area; + position(row, u, rect, false); + u = Math.min(rect.dx, rect.dy); + row.length = row.area = 0; + best = Infinity; + } + } + if (row.length) { + position(row, u, rect, true); + row.length = row.area = 0; + } + children.forEach(squarify); + } + } + function stickify(node) { + var children = node.children; + if (children && children.length) { + var rect = pad(node), remaining = children.slice(), child, row = []; + scale(remaining, rect.dx * rect.dy / node.value); + row.area = 0; + while (child = remaining.pop()) { + row.push(child); + row.area += child.area; + if (child.z != null) { + position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length); + row.length = row.area = 0; + } + } + children.forEach(stickify); + } + } + function worst(row, u) { + var s = row.area, r, rmax = 0, rmin = Infinity, i = -1, n = row.length; + while (++i < n) { + if (!(r = row[i].area)) continue; + if (r < rmin) rmin = r; + if (r > rmax) rmax = r; + } + s *= s; + u *= u; + return s ? Math.max(u * rmax * ratio / s, s / (u * rmin * ratio)) : Infinity; + } + function position(row, u, rect, flush) { + var i = -1, n = row.length, x = rect.x, y = rect.y, v = u ? round(row.area / u) : 0, o; + if (u == rect.dx) { + if (flush || v > rect.dy) v = rect.dy; + while (++i < n) { + o = row[i]; + o.x = x; + o.y = y; + o.dy = v; + x += o.dx = Math.min(rect.x + rect.dx - x, v ? round(o.area / v) : 0); + } + o.z = true; + o.dx += rect.x + rect.dx - x; + rect.y += v; + rect.dy -= v; + } else { + if (flush || v > rect.dx) v = rect.dx; + while (++i < n) { + o = row[i]; + o.x = x; + o.y = y; + o.dx = v; + y += o.dy = Math.min(rect.y + rect.dy - y, v ? round(o.area / v) : 0); + } + o.z = false; + o.dy += rect.y + rect.dy - y; + rect.x += v; + rect.dx -= v; + } + } + function treemap(d) { + var nodes = stickies || hierarchy(d), root = nodes[0]; + root.x = root.y = 0; + if (root.value) root.dx = size[0], root.dy = size[1]; else root.dx = root.dy = 0; + if (stickies) hierarchy.revalue(root); + scale([ root ], root.dx * root.dy / root.value); + (stickies ? stickify : squarify)(root); + if (sticky) stickies = nodes; + return nodes; + } + treemap.size = function(x) { + if (!arguments.length) return size; + size = x; + return treemap; + }; + treemap.padding = function(x) { + if (!arguments.length) return padding; + function padFunction(node) { + var p = x.call(treemap, node, node.depth); + return p == null ? d3_layout_treemapPadNull(node) : d3_layout_treemapPad(node, typeof p === "number" ? [ p, p, p, p ] : p); + } + function padConstant(node) { + return d3_layout_treemapPad(node, x); + } + var type; + pad = (padding = x) == null ? d3_layout_treemapPadNull : (type = typeof x) === "function" ? padFunction : type === "number" ? (x = [ x, x, x, x ], + padConstant) : padConstant; + return treemap; + }; + treemap.round = function(x) { + if (!arguments.length) return round != Number; + round = x ? Math.round : Number; + return treemap; + }; + treemap.sticky = function(x) { + if (!arguments.length) return sticky; + sticky = x; + stickies = null; + return treemap; + }; + treemap.ratio = function(x) { + if (!arguments.length) return ratio; + ratio = x; + return treemap; + }; + treemap.mode = function(x) { + if (!arguments.length) return mode; + mode = x + ""; + return treemap; + }; + return d3_layout_hierarchyRebind(treemap, hierarchy); + }; + function d3_layout_treemapPadNull(node) { + return { + x: node.x, + y: node.y, + dx: node.dx, + dy: node.dy + }; + } + function d3_layout_treemapPad(node, padding) { + var x = node.x + padding[3], y = node.y + padding[0], dx = node.dx - padding[1] - padding[3], dy = node.dy - padding[0] - padding[2]; + if (dx < 0) { + x += dx / 2; + dx = 0; + } + if (dy < 0) { + y += dy / 2; + dy = 0; + } + return { + x: x, + y: y, + dx: dx, + dy: dy + }; + } + d3.random = { + normal: function(µ, σ) { + var n = arguments.length; + if (n < 2) σ = 1; + if (n < 1) µ = 0; + return function() { + var x, y, r; + do { + x = Math.random() * 2 - 1; + y = Math.random() * 2 - 1; + r = x * x + y * y; + } while (!r || r > 1); + return µ + σ * x * Math.sqrt(-2 * Math.log(r) / r); + }; + }, + logNormal: function() { + var random = d3.random.normal.apply(d3, arguments); + return function() { + return Math.exp(random()); + }; + }, + bates: function(m) { + var random = d3.random.irwinHall(m); + return function() { + return random() / m; + }; + }, + irwinHall: function(m) { + return function() { + for (var s = 0, j = 0; j < m; j++) s += Math.random(); + return s; + }; + } + }; + d3.scale = {}; + function d3_scaleExtent(domain) { + var start = domain[0], stop = domain[domain.length - 1]; + return start < stop ? [ start, stop ] : [ stop, start ]; + } + function d3_scaleRange(scale) { + return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range()); + } + function d3_scale_bilinear(domain, range, uninterpolate, interpolate) { + var u = uninterpolate(domain[0], domain[1]), i = interpolate(range[0], range[1]); + return function(x) { + return i(u(x)); + }; + } + function d3_scale_nice(domain, nice) { + var i0 = 0, i1 = domain.length - 1, x0 = domain[i0], x1 = domain[i1], dx; + if (x1 < x0) { + dx = i0, i0 = i1, i1 = dx; + dx = x0, x0 = x1, x1 = dx; + } + domain[i0] = nice.floor(x0); + domain[i1] = nice.ceil(x1); + return domain; + } + function d3_scale_niceStep(step) { + return step ? { + floor: function(x) { + return Math.floor(x / step) * step; + }, + ceil: function(x) { + return Math.ceil(x / step) * step; + } + } : d3_scale_niceIdentity; + } + var d3_scale_niceIdentity = { + floor: d3_identity, + ceil: d3_identity + }; + function d3_scale_polylinear(domain, range, uninterpolate, interpolate) { + var u = [], i = [], j = 0, k = Math.min(domain.length, range.length) - 1; + if (domain[k] < domain[0]) { + domain = domain.slice().reverse(); + range = range.slice().reverse(); + } + while (++j <= k) { + u.push(uninterpolate(domain[j - 1], domain[j])); + i.push(interpolate(range[j - 1], range[j])); + } + return function(x) { + var j = d3.bisect(domain, x, 1, k) - 1; + return i[j](u[j](x)); + }; + } + d3.scale.linear = function() { + return d3_scale_linear([ 0, 1 ], [ 0, 1 ], d3_interpolate, false); + }; + function d3_scale_linear(domain, range, interpolate, clamp) { + var output, input; + function rescale() { + var linear = Math.min(domain.length, range.length) > 2 ? d3_scale_polylinear : d3_scale_bilinear, uninterpolate = clamp ? d3_uninterpolateClamp : d3_uninterpolateNumber; + output = linear(domain, range, uninterpolate, interpolate); + input = linear(range, domain, uninterpolate, d3_interpolate); + return scale; + } + function scale(x) { + return output(x); + } + scale.invert = function(y) { + return input(y); + }; + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = x.map(Number); + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.rangeRound = function(x) { + return scale.range(x).interpolate(d3_interpolateRound); + }; + scale.clamp = function(x) { + if (!arguments.length) return clamp; + clamp = x; + return rescale(); + }; + scale.interpolate = function(x) { + if (!arguments.length) return interpolate; + interpolate = x; + return rescale(); + }; + scale.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + scale.tickFormat = function(m, format) { + return d3_scale_linearTickFormat(domain, m, format); + }; + scale.nice = function(m) { + d3_scale_linearNice(domain, m); + return rescale(); + }; + scale.copy = function() { + return d3_scale_linear(domain, range, interpolate, clamp); + }; + return rescale(); + } + function d3_scale_linearRebind(scale, linear) { + return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp"); + } + function d3_scale_linearNice(domain, m) { + d3_scale_nice(domain, d3_scale_niceStep(d3_scale_linearTickRange(domain, m)[2])); + d3_scale_nice(domain, d3_scale_niceStep(d3_scale_linearTickRange(domain, m)[2])); + return domain; + } + function d3_scale_linearTickRange(domain, m) { + if (m == null) m = 10; + var extent = d3_scaleExtent(domain), span = extent[1] - extent[0], step = Math.pow(10, Math.floor(Math.log(span / m) / Math.LN10)), err = m / span * step; + if (err <= .15) step *= 10; else if (err <= .35) step *= 5; else if (err <= .75) step *= 2; + extent[0] = Math.ceil(extent[0] / step) * step; + extent[1] = Math.floor(extent[1] / step) * step + step * .5; + extent[2] = step; + return extent; + } + function d3_scale_linearTicks(domain, m) { + return d3.range.apply(d3, d3_scale_linearTickRange(domain, m)); + } + function d3_scale_linearTickFormat(domain, m, format) { + var range = d3_scale_linearTickRange(domain, m); + if (format) { + var match = d3_format_re.exec(format); + match.shift(); + if (match[8] === "s") { + var prefix = d3.formatPrefix(Math.max(abs(range[0]), abs(range[1]))); + if (!match[7]) match[7] = "." + d3_scale_linearPrecision(prefix.scale(range[2])); + match[8] = "f"; + format = d3.format(match.join("")); + return function(d) { + return format(prefix.scale(d)) + prefix.symbol; + }; + } + if (!match[7]) match[7] = "." + d3_scale_linearFormatPrecision(match[8], range); + format = match.join(""); + } else { + format = ",." + d3_scale_linearPrecision(range[2]) + "f"; + } + return d3.format(format); + } + var d3_scale_linearFormatSignificant = { + s: 1, + g: 1, + p: 1, + r: 1, + e: 1 + }; + function d3_scale_linearPrecision(value) { + return -Math.floor(Math.log(value) / Math.LN10 + .01); + } + function d3_scale_linearFormatPrecision(type, range) { + var p = d3_scale_linearPrecision(range[2]); + return type in d3_scale_linearFormatSignificant ? Math.abs(p - d3_scale_linearPrecision(Math.max(abs(range[0]), abs(range[1])))) + +(type !== "e") : p - (type === "%") * 2; + } + d3.scale.log = function() { + return d3_scale_log(d3.scale.linear().domain([ 0, 1 ]), 10, true, [ 1, 10 ]); + }; + function d3_scale_log(linear, base, positive, domain) { + function log(x) { + return (positive ? Math.log(x < 0 ? 0 : x) : -Math.log(x > 0 ? 0 : -x)) / Math.log(base); + } + function pow(x) { + return positive ? Math.pow(base, x) : -Math.pow(base, -x); + } + function scale(x) { + return linear(log(x)); + } + scale.invert = function(x) { + return pow(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return domain; + positive = x[0] >= 0; + linear.domain((domain = x.map(Number)).map(log)); + return scale; + }; + scale.base = function(_) { + if (!arguments.length) return base; + base = +_; + linear.domain(domain.map(log)); + return scale; + }; + scale.nice = function() { + var niced = d3_scale_nice(domain.map(log), positive ? Math : d3_scale_logNiceNegative); + linear.domain(niced); + domain = niced.map(pow); + return scale; + }; + scale.ticks = function() { + var extent = d3_scaleExtent(domain), ticks = [], u = extent[0], v = extent[1], i = Math.floor(log(u)), j = Math.ceil(log(v)), n = base % 1 ? 2 : base; + if (isFinite(j - i)) { + if (positive) { + for (;i < j; i++) for (var k = 1; k < n; k++) ticks.push(pow(i) * k); + ticks.push(pow(i)); + } else { + ticks.push(pow(i)); + for (;i++ < j; ) for (var k = n - 1; k > 0; k--) ticks.push(pow(i) * k); + } + for (i = 0; ticks[i] < u; i++) {} + for (j = ticks.length; ticks[j - 1] > v; j--) {} + ticks = ticks.slice(i, j); + } + return ticks; + }; + scale.tickFormat = function(n, format) { + if (!arguments.length) return d3_scale_logFormat; + if (arguments.length < 2) format = d3_scale_logFormat; else if (typeof format !== "function") format = d3.format(format); + var k = Math.max(1, base * n / scale.ticks().length); + return function(d) { + var i = d / pow(Math.round(log(d))); + if (i * base < base - .5) i *= base; + return i <= k ? format(d) : ""; + }; + }; + scale.copy = function() { + return d3_scale_log(linear.copy(), base, positive, domain); + }; + return d3_scale_linearRebind(scale, linear); + } + var d3_scale_logFormat = d3.format(".0e"), d3_scale_logNiceNegative = { + floor: function(x) { + return -Math.ceil(-x); + }, + ceil: function(x) { + return -Math.floor(-x); + } + }; + d3.scale.pow = function() { + return d3_scale_pow(d3.scale.linear(), 1, [ 0, 1 ]); + }; + function d3_scale_pow(linear, exponent, domain) { + var powp = d3_scale_powPow(exponent), powb = d3_scale_powPow(1 / exponent); + function scale(x) { + return linear(powp(x)); + } + scale.invert = function(x) { + return powb(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return domain; + linear.domain((domain = x.map(Number)).map(powp)); + return scale; + }; + scale.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + scale.tickFormat = function(m, format) { + return d3_scale_linearTickFormat(domain, m, format); + }; + scale.nice = function(m) { + return scale.domain(d3_scale_linearNice(domain, m)); + }; + scale.exponent = function(x) { + if (!arguments.length) return exponent; + powp = d3_scale_powPow(exponent = x); + powb = d3_scale_powPow(1 / exponent); + linear.domain(domain.map(powp)); + return scale; + }; + scale.copy = function() { + return d3_scale_pow(linear.copy(), exponent, domain); + }; + return d3_scale_linearRebind(scale, linear); + } + function d3_scale_powPow(e) { + return function(x) { + return x < 0 ? -Math.pow(-x, e) : Math.pow(x, e); + }; + } + d3.scale.sqrt = function() { + return d3.scale.pow().exponent(.5); + }; + d3.scale.ordinal = function() { + return d3_scale_ordinal([], { + t: "range", + a: [ [] ] + }); + }; + function d3_scale_ordinal(domain, ranger) { + var index, range, rangeBand; + function scale(x) { + return range[((index.get(x) || (ranger.t === "range" ? index.set(x, domain.push(x)) : NaN)) - 1) % range.length]; + } + function steps(start, step) { + return d3.range(domain.length).map(function(i) { + return start + step * i; + }); + } + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = []; + index = new d3_Map(); + var i = -1, n = x.length, xi; + while (++i < n) if (!index.has(xi = x[i])) index.set(xi, domain.push(xi)); + return scale[ranger.t].apply(scale, ranger.a); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + rangeBand = 0; + ranger = { + t: "range", + a: arguments + }; + return scale; + }; + scale.rangePoints = function(x, padding) { + if (arguments.length < 2) padding = 0; + var start = x[0], stop = x[1], step = domain.length < 2 ? (start = (start + stop) / 2, + 0) : (stop - start) / (domain.length - 1 + padding); + range = steps(start + step * padding / 2, step); + rangeBand = 0; + ranger = { + t: "rangePoints", + a: arguments + }; + return scale; + }; + scale.rangeRoundPoints = function(x, padding) { + if (arguments.length < 2) padding = 0; + var start = x[0], stop = x[1], step = domain.length < 2 ? (start = stop = Math.round((start + stop) / 2), + 0) : (stop - start) / (domain.length - 1 + padding) | 0; + range = steps(start + Math.round(step * padding / 2 + (stop - start - (domain.length - 1 + padding) * step) / 2), step); + rangeBand = 0; + ranger = { + t: "rangeRoundPoints", + a: arguments + }; + return scale; + }; + scale.rangeBands = function(x, padding, outerPadding) { + if (arguments.length < 2) padding = 0; + if (arguments.length < 3) outerPadding = padding; + var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = (stop - start) / (domain.length - padding + 2 * outerPadding); + range = steps(start + step * outerPadding, step); + if (reverse) range.reverse(); + rangeBand = step * (1 - padding); + ranger = { + t: "rangeBands", + a: arguments + }; + return scale; + }; + scale.rangeRoundBands = function(x, padding, outerPadding) { + if (arguments.length < 2) padding = 0; + if (arguments.length < 3) outerPadding = padding; + var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = Math.floor((stop - start) / (domain.length - padding + 2 * outerPadding)); + range = steps(start + Math.round((stop - start - (domain.length - padding) * step) / 2), step); + if (reverse) range.reverse(); + rangeBand = Math.round(step * (1 - padding)); + ranger = { + t: "rangeRoundBands", + a: arguments + }; + return scale; + }; + scale.rangeBand = function() { + return rangeBand; + }; + scale.rangeExtent = function() { + return d3_scaleExtent(ranger.a[0]); + }; + scale.copy = function() { + return d3_scale_ordinal(domain, ranger); + }; + return scale.domain(domain); + } + d3.scale.category10 = function() { + return d3.scale.ordinal().range(d3_category10); + }; + d3.scale.category20 = function() { + return d3.scale.ordinal().range(d3_category20); + }; + d3.scale.category20b = function() { + return d3.scale.ordinal().range(d3_category20b); + }; + d3.scale.category20c = function() { + return d3.scale.ordinal().range(d3_category20c); + }; + var d3_category10 = [ 2062260, 16744206, 2924588, 14034728, 9725885, 9197131, 14907330, 8355711, 12369186, 1556175 ].map(d3_rgbString); + var d3_category20 = [ 2062260, 11454440, 16744206, 16759672, 2924588, 10018698, 14034728, 16750742, 9725885, 12955861, 9197131, 12885140, 14907330, 16234194, 8355711, 13092807, 12369186, 14408589, 1556175, 10410725 ].map(d3_rgbString); + var d3_category20b = [ 3750777, 5395619, 7040719, 10264286, 6519097, 9216594, 11915115, 13556636, 9202993, 12426809, 15186514, 15190932, 8666169, 11356490, 14049643, 15177372, 8077683, 10834324, 13528509, 14589654 ].map(d3_rgbString); + var d3_category20c = [ 3244733, 7057110, 10406625, 13032431, 15095053, 16616764, 16625259, 16634018, 3253076, 7652470, 10607003, 13101504, 7695281, 10394312, 12369372, 14342891, 6513507, 9868950, 12434877, 14277081 ].map(d3_rgbString); + d3.scale.quantile = function() { + return d3_scale_quantile([], []); + }; + function d3_scale_quantile(domain, range) { + var thresholds; + function rescale() { + var k = 0, q = range.length; + thresholds = []; + while (++k < q) thresholds[k - 1] = d3.quantile(domain, k / q); + return scale; + } + function scale(x) { + if (!isNaN(x = +x)) return range[d3.bisect(thresholds, x)]; + } + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = x.map(d3_number).filter(d3_numeric).sort(d3_ascending); + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.quantiles = function() { + return thresholds; + }; + scale.invertExtent = function(y) { + y = range.indexOf(y); + return y < 0 ? [ NaN, NaN ] : [ y > 0 ? thresholds[y - 1] : domain[0], y < thresholds.length ? thresholds[y] : domain[domain.length - 1] ]; + }; + scale.copy = function() { + return d3_scale_quantile(domain, range); + }; + return rescale(); + } + d3.scale.quantize = function() { + return d3_scale_quantize(0, 1, [ 0, 1 ]); + }; + function d3_scale_quantize(x0, x1, range) { + var kx, i; + function scale(x) { + return range[Math.max(0, Math.min(i, Math.floor(kx * (x - x0))))]; + } + function rescale() { + kx = range.length / (x1 - x0); + i = range.length - 1; + return scale; + } + scale.domain = function(x) { + if (!arguments.length) return [ x0, x1 ]; + x0 = +x[0]; + x1 = +x[x.length - 1]; + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.invertExtent = function(y) { + y = range.indexOf(y); + y = y < 0 ? NaN : y / kx + x0; + return [ y, y + 1 / kx ]; + }; + scale.copy = function() { + return d3_scale_quantize(x0, x1, range); + }; + return rescale(); + } + d3.scale.threshold = function() { + return d3_scale_threshold([ .5 ], [ 0, 1 ]); + }; + function d3_scale_threshold(domain, range) { + function scale(x) { + if (x <= x) return range[d3.bisect(domain, x)]; + } + scale.domain = function(_) { + if (!arguments.length) return domain; + domain = _; + return scale; + }; + scale.range = function(_) { + if (!arguments.length) return range; + range = _; + return scale; + }; + scale.invertExtent = function(y) { + y = range.indexOf(y); + return [ domain[y - 1], domain[y] ]; + }; + scale.copy = function() { + return d3_scale_threshold(domain, range); + }; + return scale; + } + d3.scale.identity = function() { + return d3_scale_identity([ 0, 1 ]); + }; + function d3_scale_identity(domain) { + function identity(x) { + return +x; + } + identity.invert = identity; + identity.domain = identity.range = function(x) { + if (!arguments.length) return domain; + domain = x.map(identity); + return identity; + }; + identity.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + identity.tickFormat = function(m, format) { + return d3_scale_linearTickFormat(domain, m, format); + }; + identity.copy = function() { + return d3_scale_identity(domain); + }; + return identity; + } + d3.svg = {}; + function d3_zero() { + return 0; + } + d3.svg.arc = function() { + var innerRadius = d3_svg_arcInnerRadius, outerRadius = d3_svg_arcOuterRadius, cornerRadius = d3_zero, padRadius = d3_svg_arcAuto, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle, padAngle = d3_svg_arcPadAngle; + function arc() { + var r0 = Math.max(0, +innerRadius.apply(this, arguments)), r1 = Math.max(0, +outerRadius.apply(this, arguments)), a0 = startAngle.apply(this, arguments) - halfπ, a1 = endAngle.apply(this, arguments) - halfπ, da = Math.abs(a1 - a0), cw = a0 > a1 ? 0 : 1; + if (r1 < r0) rc = r1, r1 = r0, r0 = rc; + if (da >= τε) return circleSegment(r1, cw) + (r0 ? circleSegment(r0, 1 - cw) : "") + "Z"; + var rc, cr, rp, ap, p0 = 0, p1 = 0, x0, y0, x1, y1, x2, y2, x3, y3, path = []; + if (ap = (+padAngle.apply(this, arguments) || 0) / 2) { + rp = padRadius === d3_svg_arcAuto ? Math.sqrt(r0 * r0 + r1 * r1) : +padRadius.apply(this, arguments); + if (!cw) p1 *= -1; + if (r1) p1 = d3_asin(rp / r1 * Math.sin(ap)); + if (r0) p0 = d3_asin(rp / r0 * Math.sin(ap)); + } + if (r1) { + x0 = r1 * Math.cos(a0 + p1); + y0 = r1 * Math.sin(a0 + p1); + x1 = r1 * Math.cos(a1 - p1); + y1 = r1 * Math.sin(a1 - p1); + var l1 = Math.abs(a1 - a0 - 2 * p1) <= π ? 0 : 1; + if (p1 && d3_svg_arcSweep(x0, y0, x1, y1) === cw ^ l1) { + var h1 = (a0 + a1) / 2; + x0 = r1 * Math.cos(h1); + y0 = r1 * Math.sin(h1); + x1 = y1 = null; + } + } else { + x0 = y0 = 0; + } + if (r0) { + x2 = r0 * Math.cos(a1 - p0); + y2 = r0 * Math.sin(a1 - p0); + x3 = r0 * Math.cos(a0 + p0); + y3 = r0 * Math.sin(a0 + p0); + var l0 = Math.abs(a0 - a1 + 2 * p0) <= π ? 0 : 1; + if (p0 && d3_svg_arcSweep(x2, y2, x3, y3) === 1 - cw ^ l0) { + var h0 = (a0 + a1) / 2; + x2 = r0 * Math.cos(h0); + y2 = r0 * Math.sin(h0); + x3 = y3 = null; + } + } else { + x2 = y2 = 0; + } + if (da > ε && (rc = Math.min(Math.abs(r1 - r0) / 2, +cornerRadius.apply(this, arguments))) > .001) { + cr = r0 < r1 ^ cw ? 0 : 1; + var rc1 = rc, rc0 = rc; + if (da < π) { + var oc = x3 == null ? [ x2, y2 ] : x1 == null ? [ x0, y0 ] : d3_geom_polygonIntersect([ x0, y0 ], [ x3, y3 ], [ x1, y1 ], [ x2, y2 ]), ax = x0 - oc[0], ay = y0 - oc[1], bx = x1 - oc[0], by = y1 - oc[1], kc = 1 / Math.sin(Math.acos((ax * bx + ay * by) / (Math.sqrt(ax * ax + ay * ay) * Math.sqrt(bx * bx + by * by))) / 2), lc = Math.sqrt(oc[0] * oc[0] + oc[1] * oc[1]); + rc0 = Math.min(rc, (r0 - lc) / (kc - 1)); + rc1 = Math.min(rc, (r1 - lc) / (kc + 1)); + } + if (x1 != null) { + var t30 = d3_svg_arcCornerTangents(x3 == null ? [ x2, y2 ] : [ x3, y3 ], [ x0, y0 ], r1, rc1, cw), t12 = d3_svg_arcCornerTangents([ x1, y1 ], [ x2, y2 ], r1, rc1, cw); + if (rc === rc1) { + path.push("M", t30[0], "A", rc1, ",", rc1, " 0 0,", cr, " ", t30[1], "A", r1, ",", r1, " 0 ", 1 - cw ^ d3_svg_arcSweep(t30[1][0], t30[1][1], t12[1][0], t12[1][1]), ",", cw, " ", t12[1], "A", rc1, ",", rc1, " 0 0,", cr, " ", t12[0]); + } else { + path.push("M", t30[0], "A", rc1, ",", rc1, " 0 1,", cr, " ", t12[0]); + } + } else { + path.push("M", x0, ",", y0); + } + if (x3 != null) { + var t03 = d3_svg_arcCornerTangents([ x0, y0 ], [ x3, y3 ], r0, -rc0, cw), t21 = d3_svg_arcCornerTangents([ x2, y2 ], x1 == null ? [ x0, y0 ] : [ x1, y1 ], r0, -rc0, cw); + if (rc === rc0) { + path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t21[1], "A", r0, ",", r0, " 0 ", cw ^ d3_svg_arcSweep(t21[1][0], t21[1][1], t03[1][0], t03[1][1]), ",", 1 - cw, " ", t03[1], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]); + } else { + path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]); + } + } else { + path.push("L", x2, ",", y2); + } + } else { + path.push("M", x0, ",", y0); + if (x1 != null) path.push("A", r1, ",", r1, " 0 ", l1, ",", cw, " ", x1, ",", y1); + path.push("L", x2, ",", y2); + if (x3 != null) path.push("A", r0, ",", r0, " 0 ", l0, ",", 1 - cw, " ", x3, ",", y3); + } + path.push("Z"); + return path.join(""); + } + function circleSegment(r1, cw) { + return "M0," + r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + -r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + r1; + } + arc.innerRadius = function(v) { + if (!arguments.length) return innerRadius; + innerRadius = d3_functor(v); + return arc; + }; + arc.outerRadius = function(v) { + if (!arguments.length) return outerRadius; + outerRadius = d3_functor(v); + return arc; + }; + arc.cornerRadius = function(v) { + if (!arguments.length) return cornerRadius; + cornerRadius = d3_functor(v); + return arc; + }; + arc.padRadius = function(v) { + if (!arguments.length) return padRadius; + padRadius = v == d3_svg_arcAuto ? d3_svg_arcAuto : d3_functor(v); + return arc; + }; + arc.startAngle = function(v) { + if (!arguments.length) return startAngle; + startAngle = d3_functor(v); + return arc; + }; + arc.endAngle = function(v) { + if (!arguments.length) return endAngle; + endAngle = d3_functor(v); + return arc; + }; + arc.padAngle = function(v) { + if (!arguments.length) return padAngle; + padAngle = d3_functor(v); + return arc; + }; + arc.centroid = function() { + var r = (+innerRadius.apply(this, arguments) + +outerRadius.apply(this, arguments)) / 2, a = (+startAngle.apply(this, arguments) + +endAngle.apply(this, arguments)) / 2 - halfπ; + return [ Math.cos(a) * r, Math.sin(a) * r ]; + }; + return arc; + }; + var d3_svg_arcAuto = "auto"; + function d3_svg_arcInnerRadius(d) { + return d.innerRadius; + } + function d3_svg_arcOuterRadius(d) { + return d.outerRadius; + } + function d3_svg_arcStartAngle(d) { + return d.startAngle; + } + function d3_svg_arcEndAngle(d) { + return d.endAngle; + } + function d3_svg_arcPadAngle(d) { + return d && d.padAngle; + } + function d3_svg_arcSweep(x0, y0, x1, y1) { + return (x0 - x1) * y0 - (y0 - y1) * x0 > 0 ? 0 : 1; + } + function d3_svg_arcCornerTangents(p0, p1, r1, rc, cw) { + var x01 = p0[0] - p1[0], y01 = p0[1] - p1[1], lo = (cw ? rc : -rc) / Math.sqrt(x01 * x01 + y01 * y01), ox = lo * y01, oy = -lo * x01, x1 = p0[0] + ox, y1 = p0[1] + oy, x2 = p1[0] + ox, y2 = p1[1] + oy, x3 = (x1 + x2) / 2, y3 = (y1 + y2) / 2, dx = x2 - x1, dy = y2 - y1, d2 = dx * dx + dy * dy, r = r1 - rc, D = x1 * y2 - x2 * y1, d = (dy < 0 ? -1 : 1) * Math.sqrt(Math.max(0, r * r * d2 - D * D)), cx0 = (D * dy - dx * d) / d2, cy0 = (-D * dx - dy * d) / d2, cx1 = (D * dy + dx * d) / d2, cy1 = (-D * dx + dy * d) / d2, dx0 = cx0 - x3, dy0 = cy0 - y3, dx1 = cx1 - x3, dy1 = cy1 - y3; + if (dx0 * dx0 + dy0 * dy0 > dx1 * dx1 + dy1 * dy1) cx0 = cx1, cy0 = cy1; + return [ [ cx0 - ox, cy0 - oy ], [ cx0 * r1 / r, cy0 * r1 / r ] ]; + } + function d3_svg_line(projection) { + var x = d3_geom_pointX, y = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, tension = .7; + function line(data) { + var segments = [], points = [], i = -1, n = data.length, d, fx = d3_functor(x), fy = d3_functor(y); + function segment() { + segments.push("M", interpolate(projection(points), tension)); + } + while (++i < n) { + if (defined.call(this, d = data[i], i)) { + points.push([ +fx.call(this, d, i), +fy.call(this, d, i) ]); + } else if (points.length) { + segment(); + points = []; + } + } + if (points.length) segment(); + return segments.length ? segments.join("") : null; + } + line.x = function(_) { + if (!arguments.length) return x; + x = _; + return line; + }; + line.y = function(_) { + if (!arguments.length) return y; + y = _; + return line; + }; + line.defined = function(_) { + if (!arguments.length) return defined; + defined = _; + return line; + }; + line.interpolate = function(_) { + if (!arguments.length) return interpolateKey; + if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key; + return line; + }; + line.tension = function(_) { + if (!arguments.length) return tension; + tension = _; + return line; + }; + return line; + } + d3.svg.line = function() { + return d3_svg_line(d3_identity); + }; + var d3_svg_lineInterpolators = d3.map({ + linear: d3_svg_lineLinear, + "linear-closed": d3_svg_lineLinearClosed, + step: d3_svg_lineStep, + "step-before": d3_svg_lineStepBefore, + "step-after": d3_svg_lineStepAfter, + basis: d3_svg_lineBasis, + "basis-open": d3_svg_lineBasisOpen, + "basis-closed": d3_svg_lineBasisClosed, + bundle: d3_svg_lineBundle, + cardinal: d3_svg_lineCardinal, + "cardinal-open": d3_svg_lineCardinalOpen, + "cardinal-closed": d3_svg_lineCardinalClosed, + monotone: d3_svg_lineMonotone + }); + d3_svg_lineInterpolators.forEach(function(key, value) { + value.key = key; + value.closed = /-closed$/.test(key); + }); + function d3_svg_lineLinear(points) { + return points.length > 1 ? points.join("L") : points + "Z"; + } + function d3_svg_lineLinearClosed(points) { + return points.join("L") + "Z"; + } + function d3_svg_lineStep(points) { + var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; + while (++i < n) path.push("H", (p[0] + (p = points[i])[0]) / 2, "V", p[1]); + if (n > 1) path.push("H", p[0]); + return path.join(""); + } + function d3_svg_lineStepBefore(points) { + var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; + while (++i < n) path.push("V", (p = points[i])[1], "H", p[0]); + return path.join(""); + } + function d3_svg_lineStepAfter(points) { + var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; + while (++i < n) path.push("H", (p = points[i])[0], "V", p[1]); + return path.join(""); + } + function d3_svg_lineCardinalOpen(points, tension) { + return points.length < 4 ? d3_svg_lineLinear(points) : points[1] + d3_svg_lineHermite(points.slice(1, -1), d3_svg_lineCardinalTangents(points, tension)); + } + function d3_svg_lineCardinalClosed(points, tension) { + return points.length < 3 ? d3_svg_lineLinearClosed(points) : points[0] + d3_svg_lineHermite((points.push(points[0]), + points), d3_svg_lineCardinalTangents([ points[points.length - 2] ].concat(points, [ points[1] ]), tension)); + } + function d3_svg_lineCardinal(points, tension) { + return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineCardinalTangents(points, tension)); + } + function d3_svg_lineHermite(points, tangents) { + if (tangents.length < 1 || points.length != tangents.length && points.length != tangents.length + 2) { + return d3_svg_lineLinear(points); + } + var quad = points.length != tangents.length, path = "", p0 = points[0], p = points[1], t0 = tangents[0], t = t0, pi = 1; + if (quad) { + path += "Q" + (p[0] - t0[0] * 2 / 3) + "," + (p[1] - t0[1] * 2 / 3) + "," + p[0] + "," + p[1]; + p0 = points[1]; + pi = 2; + } + if (tangents.length > 1) { + t = tangents[1]; + p = points[pi]; + pi++; + path += "C" + (p0[0] + t0[0]) + "," + (p0[1] + t0[1]) + "," + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1]; + for (var i = 2; i < tangents.length; i++, pi++) { + p = points[pi]; + t = tangents[i]; + path += "S" + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1]; + } + } + if (quad) { + var lp = points[pi]; + path += "Q" + (p[0] + t[0] * 2 / 3) + "," + (p[1] + t[1] * 2 / 3) + "," + lp[0] + "," + lp[1]; + } + return path; + } + function d3_svg_lineCardinalTangents(points, tension) { + var tangents = [], a = (1 - tension) / 2, p0, p1 = points[0], p2 = points[1], i = 1, n = points.length; + while (++i < n) { + p0 = p1; + p1 = p2; + p2 = points[i]; + tangents.push([ a * (p2[0] - p0[0]), a * (p2[1] - p0[1]) ]); + } + return tangents; + } + function d3_svg_lineBasis(points) { + if (points.length < 3) return d3_svg_lineLinear(points); + var i = 1, n = points.length, pi = points[0], x0 = pi[0], y0 = pi[1], px = [ x0, x0, x0, (pi = points[1])[0] ], py = [ y0, y0, y0, pi[1] ], path = [ x0, ",", y0, "L", d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ]; + points.push(points[n - 1]); + while (++i <= n) { + pi = points[i]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + points.pop(); + path.push("L", pi); + return path.join(""); + } + function d3_svg_lineBasisOpen(points) { + if (points.length < 4) return d3_svg_lineLinear(points); + var path = [], i = -1, n = points.length, pi, px = [ 0 ], py = [ 0 ]; + while (++i < 3) { + pi = points[i]; + px.push(pi[0]); + py.push(pi[1]); + } + path.push(d3_svg_lineDot4(d3_svg_lineBasisBezier3, px) + "," + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py)); + --i; + while (++i < n) { + pi = points[i]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); + } + function d3_svg_lineBasisClosed(points) { + var path, i = -1, n = points.length, m = n + 4, pi, px = [], py = []; + while (++i < 4) { + pi = points[i % n]; + px.push(pi[0]); + py.push(pi[1]); + } + path = [ d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ]; + --i; + while (++i < m) { + pi = points[i % n]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); + } + function d3_svg_lineBundle(points, tension) { + var n = points.length - 1; + if (n) { + var x0 = points[0][0], y0 = points[0][1], dx = points[n][0] - x0, dy = points[n][1] - y0, i = -1, p, t; + while (++i <= n) { + p = points[i]; + t = i / n; + p[0] = tension * p[0] + (1 - tension) * (x0 + t * dx); + p[1] = tension * p[1] + (1 - tension) * (y0 + t * dy); + } + } + return d3_svg_lineBasis(points); + } + function d3_svg_lineDot4(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]; + } + var d3_svg_lineBasisBezier1 = [ 0, 2 / 3, 1 / 3, 0 ], d3_svg_lineBasisBezier2 = [ 0, 1 / 3, 2 / 3, 0 ], d3_svg_lineBasisBezier3 = [ 0, 1 / 6, 2 / 3, 1 / 6 ]; + function d3_svg_lineBasisBezier(path, x, y) { + path.push("C", d3_svg_lineDot4(d3_svg_lineBasisBezier1, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier1, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, y)); + } + function d3_svg_lineSlope(p0, p1) { + return (p1[1] - p0[1]) / (p1[0] - p0[0]); + } + function d3_svg_lineFiniteDifferences(points) { + var i = 0, j = points.length - 1, m = [], p0 = points[0], p1 = points[1], d = m[0] = d3_svg_lineSlope(p0, p1); + while (++i < j) { + m[i] = (d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1]))) / 2; + } + m[i] = d; + return m; + } + function d3_svg_lineMonotoneTangents(points) { + var tangents = [], d, a, b, s, m = d3_svg_lineFiniteDifferences(points), i = -1, j = points.length - 1; + while (++i < j) { + d = d3_svg_lineSlope(points[i], points[i + 1]); + if (abs(d) < ε) { + m[i] = m[i + 1] = 0; + } else { + a = m[i] / d; + b = m[i + 1] / d; + s = a * a + b * b; + if (s > 9) { + s = d * 3 / Math.sqrt(s); + m[i] = s * a; + m[i + 1] = s * b; + } + } + } + i = -1; + while (++i <= j) { + s = (points[Math.min(j, i + 1)][0] - points[Math.max(0, i - 1)][0]) / (6 * (1 + m[i] * m[i])); + tangents.push([ s || 0, m[i] * s || 0 ]); + } + return tangents; + } + function d3_svg_lineMonotone(points) { + return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineMonotoneTangents(points)); + } + d3.svg.line.radial = function() { + var line = d3_svg_line(d3_svg_lineRadial); + line.radius = line.x, delete line.x; + line.angle = line.y, delete line.y; + return line; + }; + function d3_svg_lineRadial(points) { + var point, i = -1, n = points.length, r, a; + while (++i < n) { + point = points[i]; + r = point[0]; + a = point[1] - halfπ; + point[0] = r * Math.cos(a); + point[1] = r * Math.sin(a); + } + return points; + } + function d3_svg_area(projection) { + var x0 = d3_geom_pointX, x1 = d3_geom_pointX, y0 = 0, y1 = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, interpolateReverse = interpolate, L = "L", tension = .7; + function area(data) { + var segments = [], points0 = [], points1 = [], i = -1, n = data.length, d, fx0 = d3_functor(x0), fy0 = d3_functor(y0), fx1 = x0 === x1 ? function() { + return x; + } : d3_functor(x1), fy1 = y0 === y1 ? function() { + return y; + } : d3_functor(y1), x, y; + function segment() { + segments.push("M", interpolate(projection(points1), tension), L, interpolateReverse(projection(points0.reverse()), tension), "Z"); + } + while (++i < n) { + if (defined.call(this, d = data[i], i)) { + points0.push([ x = +fx0.call(this, d, i), y = +fy0.call(this, d, i) ]); + points1.push([ +fx1.call(this, d, i), +fy1.call(this, d, i) ]); + } else if (points0.length) { + segment(); + points0 = []; + points1 = []; + } + } + if (points0.length) segment(); + return segments.length ? segments.join("") : null; + } + area.x = function(_) { + if (!arguments.length) return x1; + x0 = x1 = _; + return area; + }; + area.x0 = function(_) { + if (!arguments.length) return x0; + x0 = _; + return area; + }; + area.x1 = function(_) { + if (!arguments.length) return x1; + x1 = _; + return area; + }; + area.y = function(_) { + if (!arguments.length) return y1; + y0 = y1 = _; + return area; + }; + area.y0 = function(_) { + if (!arguments.length) return y0; + y0 = _; + return area; + }; + area.y1 = function(_) { + if (!arguments.length) return y1; + y1 = _; + return area; + }; + area.defined = function(_) { + if (!arguments.length) return defined; + defined = _; + return area; + }; + area.interpolate = function(_) { + if (!arguments.length) return interpolateKey; + if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key; + interpolateReverse = interpolate.reverse || interpolate; + L = interpolate.closed ? "M" : "L"; + return area; + }; + area.tension = function(_) { + if (!arguments.length) return tension; + tension = _; + return area; + }; + return area; + } + d3_svg_lineStepBefore.reverse = d3_svg_lineStepAfter; + d3_svg_lineStepAfter.reverse = d3_svg_lineStepBefore; + d3.svg.area = function() { + return d3_svg_area(d3_identity); + }; + d3.svg.area.radial = function() { + var area = d3_svg_area(d3_svg_lineRadial); + area.radius = area.x, delete area.x; + area.innerRadius = area.x0, delete area.x0; + area.outerRadius = area.x1, delete area.x1; + area.angle = area.y, delete area.y; + area.startAngle = area.y0, delete area.y0; + area.endAngle = area.y1, delete area.y1; + return area; + }; + d3.svg.chord = function() { + var source = d3_source, target = d3_target, radius = d3_svg_chordRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle; + function chord(d, i) { + var s = subgroup(this, source, d, i), t = subgroup(this, target, d, i); + return "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t.p0) + arc(t.r, t.p1, t.a1 - t.a0) + curve(t.r, t.p1, s.r, s.p0)) + "Z"; + } + function subgroup(self, f, d, i) { + var subgroup = f.call(self, d, i), r = radius.call(self, subgroup, i), a0 = startAngle.call(self, subgroup, i) - halfπ, a1 = endAngle.call(self, subgroup, i) - halfπ; + return { + r: r, + a0: a0, + a1: a1, + p0: [ r * Math.cos(a0), r * Math.sin(a0) ], + p1: [ r * Math.cos(a1), r * Math.sin(a1) ] + }; + } + function equals(a, b) { + return a.a0 == b.a0 && a.a1 == b.a1; + } + function arc(r, p, a) { + return "A" + r + "," + r + " 0 " + +(a > π) + ",1 " + p; + } + function curve(r0, p0, r1, p1) { + return "Q 0,0 " + p1; + } + chord.radius = function(v) { + if (!arguments.length) return radius; + radius = d3_functor(v); + return chord; + }; + chord.source = function(v) { + if (!arguments.length) return source; + source = d3_functor(v); + return chord; + }; + chord.target = function(v) { + if (!arguments.length) return target; + target = d3_functor(v); + return chord; + }; + chord.startAngle = function(v) { + if (!arguments.length) return startAngle; + startAngle = d3_functor(v); + return chord; + }; + chord.endAngle = function(v) { + if (!arguments.length) return endAngle; + endAngle = d3_functor(v); + return chord; + }; + return chord; + }; + function d3_svg_chordRadius(d) { + return d.radius; + } + d3.svg.diagonal = function() { + var source = d3_source, target = d3_target, projection = d3_svg_diagonalProjection; + function diagonal(d, i) { + var p0 = source.call(this, d, i), p3 = target.call(this, d, i), m = (p0.y + p3.y) / 2, p = [ p0, { + x: p0.x, + y: m + }, { + x: p3.x, + y: m + }, p3 ]; + p = p.map(projection); + return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3]; + } + diagonal.source = function(x) { + if (!arguments.length) return source; + source = d3_functor(x); + return diagonal; + }; + diagonal.target = function(x) { + if (!arguments.length) return target; + target = d3_functor(x); + return diagonal; + }; + diagonal.projection = function(x) { + if (!arguments.length) return projection; + projection = x; + return diagonal; + }; + return diagonal; + }; + function d3_svg_diagonalProjection(d) { + return [ d.x, d.y ]; + } + d3.svg.diagonal.radial = function() { + var diagonal = d3.svg.diagonal(), projection = d3_svg_diagonalProjection, projection_ = diagonal.projection; + diagonal.projection = function(x) { + return arguments.length ? projection_(d3_svg_diagonalRadialProjection(projection = x)) : projection; + }; + return diagonal; + }; + function d3_svg_diagonalRadialProjection(projection) { + return function() { + var d = projection.apply(this, arguments), r = d[0], a = d[1] - halfπ; + return [ r * Math.cos(a), r * Math.sin(a) ]; + }; + } + d3.svg.symbol = function() { + var type = d3_svg_symbolType, size = d3_svg_symbolSize; + function symbol(d, i) { + return (d3_svg_symbols.get(type.call(this, d, i)) || d3_svg_symbolCircle)(size.call(this, d, i)); + } + symbol.type = function(x) { + if (!arguments.length) return type; + type = d3_functor(x); + return symbol; + }; + symbol.size = function(x) { + if (!arguments.length) return size; + size = d3_functor(x); + return symbol; + }; + return symbol; + }; + function d3_svg_symbolSize() { + return 64; + } + function d3_svg_symbolType() { + return "circle"; + } + function d3_svg_symbolCircle(size) { + var r = Math.sqrt(size / π); + return "M0," + r + "A" + r + "," + r + " 0 1,1 0," + -r + "A" + r + "," + r + " 0 1,1 0," + r + "Z"; + } + var d3_svg_symbols = d3.map({ + circle: d3_svg_symbolCircle, + cross: function(size) { + var r = Math.sqrt(size / 5) / 2; + return "M" + -3 * r + "," + -r + "H" + -r + "V" + -3 * r + "H" + r + "V" + -r + "H" + 3 * r + "V" + r + "H" + r + "V" + 3 * r + "H" + -r + "V" + r + "H" + -3 * r + "Z"; + }, + diamond: function(size) { + var ry = Math.sqrt(size / (2 * d3_svg_symbolTan30)), rx = ry * d3_svg_symbolTan30; + return "M0," + -ry + "L" + rx + ",0" + " 0," + ry + " " + -rx + ",0" + "Z"; + }, + square: function(size) { + var r = Math.sqrt(size) / 2; + return "M" + -r + "," + -r + "L" + r + "," + -r + " " + r + "," + r + " " + -r + "," + r + "Z"; + }, + "triangle-down": function(size) { + var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2; + return "M0," + ry + "L" + rx + "," + -ry + " " + -rx + "," + -ry + "Z"; + }, + "triangle-up": function(size) { + var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2; + return "M0," + -ry + "L" + rx + "," + ry + " " + -rx + "," + ry + "Z"; + } + }); + d3.svg.symbolTypes = d3_svg_symbols.keys(); + var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * d3_radians); + d3_selectionPrototype.transition = function(name) { + var id = d3_transitionInheritId || ++d3_transitionId, ns = d3_transitionNamespace(name), subgroups = [], subgroup, node, transition = d3_transitionInherit || { + time: Date.now(), + ease: d3_ease_cubicInOut, + delay: 0, + duration: 250 + }; + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) d3_transitionNode(node, i, ns, id, transition); + subgroup.push(node); + } + } + return d3_transition(subgroups, ns, id); + }; + d3_selectionPrototype.interrupt = function(name) { + return this.each(name == null ? d3_selection_interrupt : d3_selection_interruptNS(d3_transitionNamespace(name))); + }; + var d3_selection_interrupt = d3_selection_interruptNS(d3_transitionNamespace()); + function d3_selection_interruptNS(ns) { + return function() { + var lock, activeId, active; + if ((lock = this[ns]) && (active = lock[activeId = lock.active])) { + active.timer.c = null; + active.timer.t = NaN; + if (--lock.count) delete lock[activeId]; else delete this[ns]; + lock.active += .5; + active.event && active.event.interrupt.call(this, this.__data__, active.index); + } + }; + } + function d3_transition(groups, ns, id) { + d3_subclass(groups, d3_transitionPrototype); + groups.namespace = ns; + groups.id = id; + return groups; + } + var d3_transitionPrototype = [], d3_transitionId = 0, d3_transitionInheritId, d3_transitionInherit; + d3_transitionPrototype.call = d3_selectionPrototype.call; + d3_transitionPrototype.empty = d3_selectionPrototype.empty; + d3_transitionPrototype.node = d3_selectionPrototype.node; + d3_transitionPrototype.size = d3_selectionPrototype.size; + d3.transition = function(selection, name) { + return selection && selection.transition ? d3_transitionInheritId ? selection.transition(name) : selection : d3.selection().transition(selection); + }; + d3.transition.prototype = d3_transitionPrototype; + d3_transitionPrototype.select = function(selector) { + var id = this.id, ns = this.namespace, subgroups = [], subgroup, subnode, node; + selector = d3_selection_selector(selector); + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if ((node = group[i]) && (subnode = selector.call(node, node.__data__, i, j))) { + if ("__data__" in node) subnode.__data__ = node.__data__; + d3_transitionNode(subnode, i, ns, id, node[ns][id]); + subgroup.push(subnode); + } else { + subgroup.push(null); + } + } + } + return d3_transition(subgroups, ns, id); + }; + d3_transitionPrototype.selectAll = function(selector) { + var id = this.id, ns = this.namespace, subgroups = [], subgroup, subnodes, node, subnode, transition; + selector = d3_selection_selectorAll(selector); + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + transition = node[ns][id]; + subnodes = selector.call(node, node.__data__, i, j); + subgroups.push(subgroup = []); + for (var k = -1, o = subnodes.length; ++k < o; ) { + if (subnode = subnodes[k]) d3_transitionNode(subnode, k, ns, id, transition); + subgroup.push(subnode); + } + } + } + } + return d3_transition(subgroups, ns, id); + }; + d3_transitionPrototype.filter = function(filter) { + var subgroups = [], subgroup, group, node; + if (typeof filter !== "function") filter = d3_selection_filter(filter); + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + if ((node = group[i]) && filter.call(node, node.__data__, i, j)) { + subgroup.push(node); + } + } + } + return d3_transition(subgroups, this.namespace, this.id); + }; + d3_transitionPrototype.tween = function(name, tween) { + var id = this.id, ns = this.namespace; + if (arguments.length < 2) return this.node()[ns][id].tween.get(name); + return d3_selection_each(this, tween == null ? function(node) { + node[ns][id].tween.remove(name); + } : function(node) { + node[ns][id].tween.set(name, tween); + }); + }; + function d3_transition_tween(groups, name, value, tween) { + var id = groups.id, ns = groups.namespace; + return d3_selection_each(groups, typeof value === "function" ? function(node, i, j) { + node[ns][id].tween.set(name, tween(value.call(node, node.__data__, i, j))); + } : (value = tween(value), function(node) { + node[ns][id].tween.set(name, value); + })); + } + d3_transitionPrototype.attr = function(nameNS, value) { + if (arguments.length < 2) { + for (value in nameNS) this.attr(value, nameNS[value]); + return this; + } + var interpolate = nameNS == "transform" ? d3_interpolateTransform : d3_interpolate, name = d3.ns.qualify(nameNS); + function attrNull() { + this.removeAttribute(name); + } + function attrNullNS() { + this.removeAttributeNS(name.space, name.local); + } + function attrTween(b) { + return b == null ? attrNull : (b += "", function() { + var a = this.getAttribute(name), i; + return a !== b && (i = interpolate(a, b), function(t) { + this.setAttribute(name, i(t)); + }); + }); + } + function attrTweenNS(b) { + return b == null ? attrNullNS : (b += "", function() { + var a = this.getAttributeNS(name.space, name.local), i; + return a !== b && (i = interpolate(a, b), function(t) { + this.setAttributeNS(name.space, name.local, i(t)); + }); + }); + } + return d3_transition_tween(this, "attr." + nameNS, value, name.local ? attrTweenNS : attrTween); + }; + d3_transitionPrototype.attrTween = function(nameNS, tween) { + var name = d3.ns.qualify(nameNS); + function attrTween(d, i) { + var f = tween.call(this, d, i, this.getAttribute(name)); + return f && function(t) { + this.setAttribute(name, f(t)); + }; + } + function attrTweenNS(d, i) { + var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local)); + return f && function(t) { + this.setAttributeNS(name.space, name.local, f(t)); + }; + } + return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween); + }; + d3_transitionPrototype.style = function(name, value, priority) { + var n = arguments.length; + if (n < 3) { + if (typeof name !== "string") { + if (n < 2) value = ""; + for (priority in name) this.style(priority, name[priority], value); + return this; + } + priority = ""; + } + function styleNull() { + this.style.removeProperty(name); + } + function styleString(b) { + return b == null ? styleNull : (b += "", function() { + var a = d3_window(this).getComputedStyle(this, null).getPropertyValue(name), i; + return a !== b && (i = d3_interpolate(a, b), function(t) { + this.style.setProperty(name, i(t), priority); + }); + }); + } + return d3_transition_tween(this, "style." + name, value, styleString); + }; + d3_transitionPrototype.styleTween = function(name, tween, priority) { + if (arguments.length < 3) priority = ""; + function styleTween(d, i) { + var f = tween.call(this, d, i, d3_window(this).getComputedStyle(this, null).getPropertyValue(name)); + return f && function(t) { + this.style.setProperty(name, f(t), priority); + }; + } + return this.tween("style." + name, styleTween); + }; + d3_transitionPrototype.text = function(value) { + return d3_transition_tween(this, "text", value, d3_transition_text); + }; + function d3_transition_text(b) { + if (b == null) b = ""; + return function() { + this.textContent = b; + }; + } + d3_transitionPrototype.remove = function() { + var ns = this.namespace; + return this.each("end.transition", function() { + var p; + if (this[ns].count < 2 && (p = this.parentNode)) p.removeChild(this); + }); + }; + d3_transitionPrototype.ease = function(value) { + var id = this.id, ns = this.namespace; + if (arguments.length < 1) return this.node()[ns][id].ease; + if (typeof value !== "function") value = d3.ease.apply(d3, arguments); + return d3_selection_each(this, function(node) { + node[ns][id].ease = value; + }); + }; + d3_transitionPrototype.delay = function(value) { + var id = this.id, ns = this.namespace; + if (arguments.length < 1) return this.node()[ns][id].delay; + return d3_selection_each(this, typeof value === "function" ? function(node, i, j) { + node[ns][id].delay = +value.call(node, node.__data__, i, j); + } : (value = +value, function(node) { + node[ns][id].delay = value; + })); + }; + d3_transitionPrototype.duration = function(value) { + var id = this.id, ns = this.namespace; + if (arguments.length < 1) return this.node()[ns][id].duration; + return d3_selection_each(this, typeof value === "function" ? function(node, i, j) { + node[ns][id].duration = Math.max(1, value.call(node, node.__data__, i, j)); + } : (value = Math.max(1, value), function(node) { + node[ns][id].duration = value; + })); + }; + d3_transitionPrototype.each = function(type, listener) { + var id = this.id, ns = this.namespace; + if (arguments.length < 2) { + var inherit = d3_transitionInherit, inheritId = d3_transitionInheritId; + try { + d3_transitionInheritId = id; + d3_selection_each(this, function(node, i, j) { + d3_transitionInherit = node[ns][id]; + type.call(node, node.__data__, i, j); + }); + } finally { + d3_transitionInherit = inherit; + d3_transitionInheritId = inheritId; + } + } else { + d3_selection_each(this, function(node) { + var transition = node[ns][id]; + (transition.event || (transition.event = d3.dispatch("start", "end", "interrupt"))).on(type, listener); + }); + } + return this; + }; + d3_transitionPrototype.transition = function() { + var id0 = this.id, id1 = ++d3_transitionId, ns = this.namespace, subgroups = [], subgroup, group, node, transition; + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + if (node = group[i]) { + transition = node[ns][id0]; + d3_transitionNode(node, i, ns, id1, { + time: transition.time, + ease: transition.ease, + delay: transition.delay + transition.duration, + duration: transition.duration + }); + } + subgroup.push(node); + } + } + return d3_transition(subgroups, ns, id1); + }; + function d3_transitionNamespace(name) { + return name == null ? "__transition__" : "__transition_" + name + "__"; + } + function d3_transitionNode(node, i, ns, id, inherit) { + var lock = node[ns] || (node[ns] = { + active: 0, + count: 0 + }), transition = lock[id], time, timer, duration, ease, tweens; + function schedule(elapsed) { + var delay = transition.delay; + timer.t = delay + time; + if (delay <= elapsed) return start(elapsed - delay); + timer.c = start; + } + function start(elapsed) { + var activeId = lock.active, active = lock[activeId]; + if (active) { + active.timer.c = null; + active.timer.t = NaN; + --lock.count; + delete lock[activeId]; + active.event && active.event.interrupt.call(node, node.__data__, active.index); + } + for (var cancelId in lock) { + if (+cancelId < id) { + var cancel = lock[cancelId]; + cancel.timer.c = null; + cancel.timer.t = NaN; + --lock.count; + delete lock[cancelId]; + } + } + timer.c = tick; + d3_timer(function() { + if (timer.c && tick(elapsed || 1)) { + timer.c = null; + timer.t = NaN; + } + return 1; + }, 0, time); + lock.active = id; + transition.event && transition.event.start.call(node, node.__data__, i); + tweens = []; + transition.tween.forEach(function(key, value) { + if (value = value.call(node, node.__data__, i)) { + tweens.push(value); + } + }); + ease = transition.ease; + duration = transition.duration; + } + function tick(elapsed) { + var t = elapsed / duration, e = ease(t), n = tweens.length; + while (n > 0) { + tweens[--n].call(node, e); + } + if (t >= 1) { + transition.event && transition.event.end.call(node, node.__data__, i); + if (--lock.count) delete lock[id]; else delete node[ns]; + return 1; + } + } + if (!transition) { + time = inherit.time; + timer = d3_timer(schedule, 0, time); + transition = lock[id] = { + tween: new d3_Map(), + time: time, + timer: timer, + delay: inherit.delay, + duration: inherit.duration, + ease: inherit.ease, + index: i + }; + inherit = null; + ++lock.count; + } + } + d3.svg.axis = function() { + var scale = d3.scale.linear(), orient = d3_svg_axisDefaultOrient, innerTickSize = 6, outerTickSize = 6, tickPadding = 3, tickArguments_ = [ 10 ], tickValues = null, tickFormat_; + function axis(g) { + g.each(function() { + var g = d3.select(this); + var scale0 = this.__chart__ || scale, scale1 = this.__chart__ = scale.copy(); + var ticks = tickValues == null ? scale1.ticks ? scale1.ticks.apply(scale1, tickArguments_) : scale1.domain() : tickValues, tickFormat = tickFormat_ == null ? scale1.tickFormat ? scale1.tickFormat.apply(scale1, tickArguments_) : d3_identity : tickFormat_, tick = g.selectAll(".tick").data(ticks, scale1), tickEnter = tick.enter().insert("g", ".domain").attr("class", "tick").style("opacity", ε), tickExit = d3.transition(tick.exit()).style("opacity", ε).remove(), tickUpdate = d3.transition(tick.order()).style("opacity", 1), tickSpacing = Math.max(innerTickSize, 0) + tickPadding, tickTransform; + var range = d3_scaleRange(scale1), path = g.selectAll(".domain").data([ 0 ]), pathUpdate = (path.enter().append("path").attr("class", "domain"), + d3.transition(path)); + tickEnter.append("line"); + tickEnter.append("text"); + var lineEnter = tickEnter.select("line"), lineUpdate = tickUpdate.select("line"), text = tick.select("text").text(tickFormat), textEnter = tickEnter.select("text"), textUpdate = tickUpdate.select("text"), sign = orient === "top" || orient === "left" ? -1 : 1, x1, x2, y1, y2; + if (orient === "bottom" || orient === "top") { + tickTransform = d3_svg_axisX, x1 = "x", y1 = "y", x2 = "x2", y2 = "y2"; + text.attr("dy", sign < 0 ? "0em" : ".71em").style("text-anchor", "middle"); + pathUpdate.attr("d", "M" + range[0] + "," + sign * outerTickSize + "V0H" + range[1] + "V" + sign * outerTickSize); + } else { + tickTransform = d3_svg_axisY, x1 = "y", y1 = "x", x2 = "y2", y2 = "x2"; + text.attr("dy", ".32em").style("text-anchor", sign < 0 ? "end" : "start"); + pathUpdate.attr("d", "M" + sign * outerTickSize + "," + range[0] + "H0V" + range[1] + "H" + sign * outerTickSize); + } + lineEnter.attr(y2, sign * innerTickSize); + textEnter.attr(y1, sign * tickSpacing); + lineUpdate.attr(x2, 0).attr(y2, sign * innerTickSize); + textUpdate.attr(x1, 0).attr(y1, sign * tickSpacing); + if (scale1.rangeBand) { + var x = scale1, dx = x.rangeBand() / 2; + scale0 = scale1 = function(d) { + return x(d) + dx; + }; + } else if (scale0.rangeBand) { + scale0 = scale1; + } else { + tickExit.call(tickTransform, scale1, scale0); + } + tickEnter.call(tickTransform, scale0, scale1); + tickUpdate.call(tickTransform, scale1, scale1); + }); + } + axis.scale = function(x) { + if (!arguments.length) return scale; + scale = x; + return axis; + }; + axis.orient = function(x) { + if (!arguments.length) return orient; + orient = x in d3_svg_axisOrients ? x + "" : d3_svg_axisDefaultOrient; + return axis; + }; + axis.ticks = function() { + if (!arguments.length) return tickArguments_; + tickArguments_ = d3_array(arguments); + return axis; + }; + axis.tickValues = function(x) { + if (!arguments.length) return tickValues; + tickValues = x; + return axis; + }; + axis.tickFormat = function(x) { + if (!arguments.length) return tickFormat_; + tickFormat_ = x; + return axis; + }; + axis.tickSize = function(x) { + var n = arguments.length; + if (!n) return innerTickSize; + innerTickSize = +x; + outerTickSize = +arguments[n - 1]; + return axis; + }; + axis.innerTickSize = function(x) { + if (!arguments.length) return innerTickSize; + innerTickSize = +x; + return axis; + }; + axis.outerTickSize = function(x) { + if (!arguments.length) return outerTickSize; + outerTickSize = +x; + return axis; + }; + axis.tickPadding = function(x) { + if (!arguments.length) return tickPadding; + tickPadding = +x; + return axis; + }; + axis.tickSubdivide = function() { + return arguments.length && axis; + }; + return axis; + }; + var d3_svg_axisDefaultOrient = "bottom", d3_svg_axisOrients = { + top: 1, + right: 1, + bottom: 1, + left: 1 + }; + function d3_svg_axisX(selection, x0, x1) { + selection.attr("transform", function(d) { + var v0 = x0(d); + return "translate(" + (isFinite(v0) ? v0 : x1(d)) + ",0)"; + }); + } + function d3_svg_axisY(selection, y0, y1) { + selection.attr("transform", function(d) { + var v0 = y0(d); + return "translate(0," + (isFinite(v0) ? v0 : y1(d)) + ")"; + }); + } + d3.svg.brush = function() { + var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"), x = null, y = null, xExtent = [ 0, 0 ], yExtent = [ 0, 0 ], xExtentDomain, yExtentDomain, xClamp = true, yClamp = true, resizes = d3_svg_brushResizes[0]; + function brush(g) { + g.each(function() { + var g = d3.select(this).style("pointer-events", "all").style("-webkit-tap-highlight-color", "rgba(0,0,0,0)").on("mousedown.brush", brushstart).on("touchstart.brush", brushstart); + var background = g.selectAll(".background").data([ 0 ]); + background.enter().append("rect").attr("class", "background").style("visibility", "hidden").style("cursor", "crosshair"); + g.selectAll(".extent").data([ 0 ]).enter().append("rect").attr("class", "extent").style("cursor", "move"); + var resize = g.selectAll(".resize").data(resizes, d3_identity); + resize.exit().remove(); + resize.enter().append("g").attr("class", function(d) { + return "resize " + d; + }).style("cursor", function(d) { + return d3_svg_brushCursor[d]; + }).append("rect").attr("x", function(d) { + return /[ew]$/.test(d) ? -3 : null; + }).attr("y", function(d) { + return /^[ns]/.test(d) ? -3 : null; + }).attr("width", 6).attr("height", 6).style("visibility", "hidden"); + resize.style("display", brush.empty() ? "none" : null); + var gUpdate = d3.transition(g), backgroundUpdate = d3.transition(background), range; + if (x) { + range = d3_scaleRange(x); + backgroundUpdate.attr("x", range[0]).attr("width", range[1] - range[0]); + redrawX(gUpdate); + } + if (y) { + range = d3_scaleRange(y); + backgroundUpdate.attr("y", range[0]).attr("height", range[1] - range[0]); + redrawY(gUpdate); + } + redraw(gUpdate); + }); + } + brush.event = function(g) { + g.each(function() { + var event_ = event.of(this, arguments), extent1 = { + x: xExtent, + y: yExtent, + i: xExtentDomain, + j: yExtentDomain + }, extent0 = this.__chart__ || extent1; + this.__chart__ = extent1; + if (d3_transitionInheritId) { + d3.select(this).transition().each("start.brush", function() { + xExtentDomain = extent0.i; + yExtentDomain = extent0.j; + xExtent = extent0.x; + yExtent = extent0.y; + event_({ + type: "brushstart" + }); + }).tween("brush:brush", function() { + var xi = d3_interpolateArray(xExtent, extent1.x), yi = d3_interpolateArray(yExtent, extent1.y); + xExtentDomain = yExtentDomain = null; + return function(t) { + xExtent = extent1.x = xi(t); + yExtent = extent1.y = yi(t); + event_({ + type: "brush", + mode: "resize" + }); + }; + }).each("end.brush", function() { + xExtentDomain = extent1.i; + yExtentDomain = extent1.j; + event_({ + type: "brush", + mode: "resize" + }); + event_({ + type: "brushend" + }); + }); + } else { + event_({ + type: "brushstart" + }); + event_({ + type: "brush", + mode: "resize" + }); + event_({ + type: "brushend" + }); + } + }); + }; + function redraw(g) { + g.selectAll(".resize").attr("transform", function(d) { + return "translate(" + xExtent[+/e$/.test(d)] + "," + yExtent[+/^s/.test(d)] + ")"; + }); + } + function redrawX(g) { + g.select(".extent").attr("x", xExtent[0]); + g.selectAll(".extent,.n>rect,.s>rect").attr("width", xExtent[1] - xExtent[0]); + } + function redrawY(g) { + g.select(".extent").attr("y", yExtent[0]); + g.selectAll(".extent,.e>rect,.w>rect").attr("height", yExtent[1] - yExtent[0]); + } + function brushstart() { + var target = this, eventTarget = d3.select(d3.event.target), event_ = event.of(target, arguments), g = d3.select(target), resizing = eventTarget.datum(), resizingX = !/^(n|s)$/.test(resizing) && x, resizingY = !/^(e|w)$/.test(resizing) && y, dragging = eventTarget.classed("extent"), dragRestore = d3_event_dragSuppress(target), center, origin = d3.mouse(target), offset; + var w = d3.select(d3_window(target)).on("keydown.brush", keydown).on("keyup.brush", keyup); + if (d3.event.changedTouches) { + w.on("touchmove.brush", brushmove).on("touchend.brush", brushend); + } else { + w.on("mousemove.brush", brushmove).on("mouseup.brush", brushend); + } + g.interrupt().selectAll("*").interrupt(); + if (dragging) { + origin[0] = xExtent[0] - origin[0]; + origin[1] = yExtent[0] - origin[1]; + } else if (resizing) { + var ex = +/w$/.test(resizing), ey = +/^n/.test(resizing); + offset = [ xExtent[1 - ex] - origin[0], yExtent[1 - ey] - origin[1] ]; + origin[0] = xExtent[ex]; + origin[1] = yExtent[ey]; + } else if (d3.event.altKey) center = origin.slice(); + g.style("pointer-events", "none").selectAll(".resize").style("display", null); + d3.select("body").style("cursor", eventTarget.style("cursor")); + event_({ + type: "brushstart" + }); + brushmove(); + function keydown() { + if (d3.event.keyCode == 32) { + if (!dragging) { + center = null; + origin[0] -= xExtent[1]; + origin[1] -= yExtent[1]; + dragging = 2; + } + d3_eventPreventDefault(); + } + } + function keyup() { + if (d3.event.keyCode == 32 && dragging == 2) { + origin[0] += xExtent[1]; + origin[1] += yExtent[1]; + dragging = 0; + d3_eventPreventDefault(); + } + } + function brushmove() { + var point = d3.mouse(target), moved = false; + if (offset) { + point[0] += offset[0]; + point[1] += offset[1]; + } + if (!dragging) { + if (d3.event.altKey) { + if (!center) center = [ (xExtent[0] + xExtent[1]) / 2, (yExtent[0] + yExtent[1]) / 2 ]; + origin[0] = xExtent[+(point[0] < center[0])]; + origin[1] = yExtent[+(point[1] < center[1])]; + } else center = null; + } + if (resizingX && move1(point, x, 0)) { + redrawX(g); + moved = true; + } + if (resizingY && move1(point, y, 1)) { + redrawY(g); + moved = true; + } + if (moved) { + redraw(g); + event_({ + type: "brush", + mode: dragging ? "move" : "resize" + }); + } + } + function move1(point, scale, i) { + var range = d3_scaleRange(scale), r0 = range[0], r1 = range[1], position = origin[i], extent = i ? yExtent : xExtent, size = extent[1] - extent[0], min, max; + if (dragging) { + r0 -= position; + r1 -= size + position; + } + min = (i ? yClamp : xClamp) ? Math.max(r0, Math.min(r1, point[i])) : point[i]; + if (dragging) { + max = (min += position) + size; + } else { + if (center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min)); + if (position < min) { + max = min; + min = position; + } else { + max = position; + } + } + if (extent[0] != min || extent[1] != max) { + if (i) yExtentDomain = null; else xExtentDomain = null; + extent[0] = min; + extent[1] = max; + return true; + } + } + function brushend() { + brushmove(); + g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null); + d3.select("body").style("cursor", null); + w.on("mousemove.brush", null).on("mouseup.brush", null).on("touchmove.brush", null).on("touchend.brush", null).on("keydown.brush", null).on("keyup.brush", null); + dragRestore(); + event_({ + type: "brushend" + }); + } + } + brush.x = function(z) { + if (!arguments.length) return x; + x = z; + resizes = d3_svg_brushResizes[!x << 1 | !y]; + return brush; + }; + brush.y = function(z) { + if (!arguments.length) return y; + y = z; + resizes = d3_svg_brushResizes[!x << 1 | !y]; + return brush; + }; + brush.clamp = function(z) { + if (!arguments.length) return x && y ? [ xClamp, yClamp ] : x ? xClamp : y ? yClamp : null; + if (x && y) xClamp = !!z[0], yClamp = !!z[1]; else if (x) xClamp = !!z; else if (y) yClamp = !!z; + return brush; + }; + brush.extent = function(z) { + var x0, x1, y0, y1, t; + if (!arguments.length) { + if (x) { + if (xExtentDomain) { + x0 = xExtentDomain[0], x1 = xExtentDomain[1]; + } else { + x0 = xExtent[0], x1 = xExtent[1]; + if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1); + if (x1 < x0) t = x0, x0 = x1, x1 = t; + } + } + if (y) { + if (yExtentDomain) { + y0 = yExtentDomain[0], y1 = yExtentDomain[1]; + } else { + y0 = yExtent[0], y1 = yExtent[1]; + if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1); + if (y1 < y0) t = y0, y0 = y1, y1 = t; + } + } + return x && y ? [ [ x0, y0 ], [ x1, y1 ] ] : x ? [ x0, x1 ] : y && [ y0, y1 ]; + } + if (x) { + x0 = z[0], x1 = z[1]; + if (y) x0 = x0[0], x1 = x1[0]; + xExtentDomain = [ x0, x1 ]; + if (x.invert) x0 = x(x0), x1 = x(x1); + if (x1 < x0) t = x0, x0 = x1, x1 = t; + if (x0 != xExtent[0] || x1 != xExtent[1]) xExtent = [ x0, x1 ]; + } + if (y) { + y0 = z[0], y1 = z[1]; + if (x) y0 = y0[1], y1 = y1[1]; + yExtentDomain = [ y0, y1 ]; + if (y.invert) y0 = y(y0), y1 = y(y1); + if (y1 < y0) t = y0, y0 = y1, y1 = t; + if (y0 != yExtent[0] || y1 != yExtent[1]) yExtent = [ y0, y1 ]; + } + return brush; + }; + brush.clear = function() { + if (!brush.empty()) { + xExtent = [ 0, 0 ], yExtent = [ 0, 0 ]; + xExtentDomain = yExtentDomain = null; + } + return brush; + }; + brush.empty = function() { + return !!x && xExtent[0] == xExtent[1] || !!y && yExtent[0] == yExtent[1]; + }; + return d3.rebind(brush, event, "on"); + }; + var d3_svg_brushCursor = { + n: "ns-resize", + e: "ew-resize", + s: "ns-resize", + w: "ew-resize", + nw: "nwse-resize", + ne: "nesw-resize", + se: "nwse-resize", + sw: "nesw-resize" + }; + var d3_svg_brushResizes = [ [ "n", "e", "s", "w", "nw", "ne", "se", "sw" ], [ "e", "w" ], [ "n", "s" ], [] ]; + var d3_time_format = d3_time.format = d3_locale_enUS.timeFormat; + var d3_time_formatUtc = d3_time_format.utc; + var d3_time_formatIso = d3_time_formatUtc("%Y-%m-%dT%H:%M:%S.%LZ"); + d3_time_format.iso = Date.prototype.toISOString && +new Date("2000-01-01T00:00:00.000Z") ? d3_time_formatIsoNative : d3_time_formatIso; + function d3_time_formatIsoNative(date) { + return date.toISOString(); + } + d3_time_formatIsoNative.parse = function(string) { + var date = new Date(string); + return isNaN(date) ? null : date; + }; + d3_time_formatIsoNative.toString = d3_time_formatIso.toString; + d3_time.second = d3_time_interval(function(date) { + return new d3_date(Math.floor(date / 1e3) * 1e3); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 1e3); + }, function(date) { + return date.getSeconds(); + }); + d3_time.seconds = d3_time.second.range; + d3_time.seconds.utc = d3_time.second.utc.range; + d3_time.minute = d3_time_interval(function(date) { + return new d3_date(Math.floor(date / 6e4) * 6e4); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 6e4); + }, function(date) { + return date.getMinutes(); + }); + d3_time.minutes = d3_time.minute.range; + d3_time.minutes.utc = d3_time.minute.utc.range; + d3_time.hour = d3_time_interval(function(date) { + var timezone = date.getTimezoneOffset() / 60; + return new d3_date((Math.floor(date / 36e5 - timezone) + timezone) * 36e5); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 36e5); + }, function(date) { + return date.getHours(); + }); + d3_time.hours = d3_time.hour.range; + d3_time.hours.utc = d3_time.hour.utc.range; + d3_time.month = d3_time_interval(function(date) { + date = d3_time.day(date); + date.setDate(1); + return date; + }, function(date, offset) { + date.setMonth(date.getMonth() + offset); + }, function(date) { + return date.getMonth(); + }); + d3_time.months = d3_time.month.range; + d3_time.months.utc = d3_time.month.utc.range; + function d3_time_scale(linear, methods, format) { + function scale(x) { + return linear(x); + } + scale.invert = function(x) { + return d3_time_scaleDate(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return linear.domain().map(d3_time_scaleDate); + linear.domain(x); + return scale; + }; + function tickMethod(extent, count) { + var span = extent[1] - extent[0], target = span / count, i = d3.bisect(d3_time_scaleSteps, target); + return i == d3_time_scaleSteps.length ? [ methods.year, d3_scale_linearTickRange(extent.map(function(d) { + return d / 31536e6; + }), count)[2] ] : !i ? [ d3_time_scaleMilliseconds, d3_scale_linearTickRange(extent, count)[2] ] : methods[target / d3_time_scaleSteps[i - 1] < d3_time_scaleSteps[i] / target ? i - 1 : i]; + } + scale.nice = function(interval, skip) { + var domain = scale.domain(), extent = d3_scaleExtent(domain), method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" && tickMethod(extent, interval); + if (method) interval = method[0], skip = method[1]; + function skipped(date) { + return !isNaN(date) && !interval.range(date, d3_time_scaleDate(+date + 1), skip).length; + } + return scale.domain(d3_scale_nice(domain, skip > 1 ? { + floor: function(date) { + while (skipped(date = interval.floor(date))) date = d3_time_scaleDate(date - 1); + return date; + }, + ceil: function(date) { + while (skipped(date = interval.ceil(date))) date = d3_time_scaleDate(+date + 1); + return date; + } + } : interval)); + }; + scale.ticks = function(interval, skip) { + var extent = d3_scaleExtent(scale.domain()), method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" ? tickMethod(extent, interval) : !interval.range && [ { + range: interval + }, skip ]; + if (method) interval = method[0], skip = method[1]; + return interval.range(extent[0], d3_time_scaleDate(+extent[1] + 1), skip < 1 ? 1 : skip); + }; + scale.tickFormat = function() { + return format; + }; + scale.copy = function() { + return d3_time_scale(linear.copy(), methods, format); + }; + return d3_scale_linearRebind(scale, linear); + } + function d3_time_scaleDate(t) { + return new Date(t); + } + var d3_time_scaleSteps = [ 1e3, 5e3, 15e3, 3e4, 6e4, 3e5, 9e5, 18e5, 36e5, 108e5, 216e5, 432e5, 864e5, 1728e5, 6048e5, 2592e6, 7776e6, 31536e6 ]; + var d3_time_scaleLocalMethods = [ [ d3_time.second, 1 ], [ d3_time.second, 5 ], [ d3_time.second, 15 ], [ d3_time.second, 30 ], [ d3_time.minute, 1 ], [ d3_time.minute, 5 ], [ d3_time.minute, 15 ], [ d3_time.minute, 30 ], [ d3_time.hour, 1 ], [ d3_time.hour, 3 ], [ d3_time.hour, 6 ], [ d3_time.hour, 12 ], [ d3_time.day, 1 ], [ d3_time.day, 2 ], [ d3_time.week, 1 ], [ d3_time.month, 1 ], [ d3_time.month, 3 ], [ d3_time.year, 1 ] ]; + var d3_time_scaleLocalFormat = d3_time_format.multi([ [ ".%L", function(d) { + return d.getMilliseconds(); + } ], [ ":%S", function(d) { + return d.getSeconds(); + } ], [ "%I:%M", function(d) { + return d.getMinutes(); + } ], [ "%I %p", function(d) { + return d.getHours(); + } ], [ "%a %d", function(d) { + return d.getDay() && d.getDate() != 1; + } ], [ "%b %d", function(d) { + return d.getDate() != 1; + } ], [ "%B", function(d) { + return d.getMonth(); + } ], [ "%Y", d3_true ] ]); + var d3_time_scaleMilliseconds = { + range: function(start, stop, step) { + return d3.range(Math.ceil(start / step) * step, +stop, step).map(d3_time_scaleDate); + }, + floor: d3_identity, + ceil: d3_identity + }; + d3_time_scaleLocalMethods.year = d3_time.year; + d3_time.scale = function() { + return d3_time_scale(d3.scale.linear(), d3_time_scaleLocalMethods, d3_time_scaleLocalFormat); + }; + var d3_time_scaleUtcMethods = d3_time_scaleLocalMethods.map(function(m) { + return [ m[0].utc, m[1] ]; + }); + var d3_time_scaleUtcFormat = d3_time_formatUtc.multi([ [ ".%L", function(d) { + return d.getUTCMilliseconds(); + } ], [ ":%S", function(d) { + return d.getUTCSeconds(); + } ], [ "%I:%M", function(d) { + return d.getUTCMinutes(); + } ], [ "%I %p", function(d) { + return d.getUTCHours(); + } ], [ "%a %d", function(d) { + return d.getUTCDay() && d.getUTCDate() != 1; + } ], [ "%b %d", function(d) { + return d.getUTCDate() != 1; + } ], [ "%B", function(d) { + return d.getUTCMonth(); + } ], [ "%Y", d3_true ] ]); + d3_time_scaleUtcMethods.year = d3_time.year.utc; + d3_time.scale.utc = function() { + return d3_time_scale(d3.scale.linear(), d3_time_scaleUtcMethods, d3_time_scaleUtcFormat); + }; + d3.text = d3_xhrType(function(request) { + return request.responseText; + }); + d3.json = function(url, callback) { + return d3_xhr(url, "application/json", d3_json, callback); + }; + function d3_json(request) { + return JSON.parse(request.responseText); + } + d3.html = function(url, callback) { + return d3_xhr(url, "text/html", d3_html, callback); + }; + function d3_html(request) { + var range = d3_document.createRange(); + range.selectNode(d3_document.body); + return range.createContextualFragment(request.responseText); + } + d3.xml = d3_xhrType(function(request) { + return request.responseXML; + }); + if (typeof define === "function" && define.amd) this.d3 = d3, define(d3); else if (typeof module === "object" && module.exports) module.exports = d3; else this.d3 = d3; +}(); +},{}],17:[function(_dereq_,module,exports){ +(function (process,global){ +/*! + * @overview es6-promise - a tiny implementation of Promises/A+. + * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald) + * @license Licensed under MIT license + * See https://raw.githubusercontent.com/stefanpenner/es6-promise/master/LICENSE + * @version 3.3.1 + */ + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.ES6Promise = factory()); +}(this, (function () { 'use strict'; + +function objectOrFunction(x) { + return typeof x === 'function' || typeof x === 'object' && x !== null; +} + +function isFunction(x) { + return typeof x === 'function'; +} + +var _isArray = undefined; +if (!Array.isArray) { + _isArray = function (x) { + return Object.prototype.toString.call(x) === '[object Array]'; + }; +} else { + _isArray = Array.isArray; +} + +var isArray = _isArray; + +var len = 0; +var vertxNext = undefined; +var customSchedulerFn = undefined; + +var asap = function asap(callback, arg) { + queue[len] = callback; + queue[len + 1] = arg; + len += 2; + if (len === 2) { + // If len is 2, that means that we need to schedule an async flush. + // If additional callbacks are queued before the queue is flushed, they + // will be processed by this flush that we are scheduling. + if (customSchedulerFn) { + customSchedulerFn(flush); + } else { + scheduleFlush(); + } + } +}; + +function setScheduler(scheduleFn) { + customSchedulerFn = scheduleFn; +} + +function setAsap(asapFn) { + asap = asapFn; +} + +var browserWindow = typeof window !== 'undefined' ? window : undefined; +var browserGlobal = browserWindow || {}; +var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver; +var isNode = typeof self === 'undefined' && typeof process !== 'undefined' && ({}).toString.call(process) === '[object process]'; + +// test for web worker but not in IE10 +var isWorker = typeof Uint8ClampedArray !== 'undefined' && typeof importScripts !== 'undefined' && typeof MessageChannel !== 'undefined'; + +// node +function useNextTick() { + // node version 0.10.x displays a deprecation warning when nextTick is used recursively + // see https://github.com/cujojs/when/issues/410 for details + return function () { + return process.nextTick(flush); + }; +} + +// vertx +function useVertxTimer() { + return function () { + vertxNext(flush); + }; +} + +function useMutationObserver() { + var iterations = 0; + var observer = new BrowserMutationObserver(flush); + var node = document.createTextNode(''); + observer.observe(node, { characterData: true }); + + return function () { + node.data = iterations = ++iterations % 2; + }; +} + +// web worker +function useMessageChannel() { + var channel = new MessageChannel(); + channel.port1.onmessage = flush; + return function () { + return channel.port2.postMessage(0); + }; +} + +function useSetTimeout() { + // Store setTimeout reference so es6-promise will be unaffected by + // other code modifying setTimeout (like sinon.useFakeTimers()) + var globalSetTimeout = setTimeout; + return function () { + return globalSetTimeout(flush, 1); + }; +} + +var queue = new Array(1000); +function flush() { + for (var i = 0; i < len; i += 2) { + var callback = queue[i]; + var arg = queue[i + 1]; + + callback(arg); + + queue[i] = undefined; + queue[i + 1] = undefined; + } + + len = 0; +} + +function attemptVertx() { + try { + var r = _dereq_; + var vertx = r('vertx'); + vertxNext = vertx.runOnLoop || vertx.runOnContext; + return useVertxTimer(); + } catch (e) { + return useSetTimeout(); + } +} + +var scheduleFlush = undefined; +// Decide what async method to use to triggering processing of queued callbacks: +if (isNode) { + scheduleFlush = useNextTick(); +} else if (BrowserMutationObserver) { + scheduleFlush = useMutationObserver(); +} else if (isWorker) { + scheduleFlush = useMessageChannel(); +} else if (browserWindow === undefined && typeof _dereq_ === 'function') { + scheduleFlush = attemptVertx(); +} else { + scheduleFlush = useSetTimeout(); +} + +function then(onFulfillment, onRejection) { + var _arguments = arguments; + + var parent = this; + + var child = new this.constructor(noop); + + if (child[PROMISE_ID] === undefined) { + makePromise(child); + } + + var _state = parent._state; + + if (_state) { + (function () { + var callback = _arguments[_state - 1]; + asap(function () { + return invokeCallback(_state, child, callback, parent._result); + }); + })(); + } else { + subscribe(parent, child, onFulfillment, onRejection); + } + + return child; +} + +/** + `Promise.resolve` returns a promise that will become resolved with the + passed `value`. It is shorthand for the following: + + ```javascript + let promise = new Promise(function(resolve, reject){ + resolve(1); + }); + + promise.then(function(value){ + // value === 1 + }); + ``` + + Instead of writing the above, your code now simply becomes the following: + + ```javascript + let promise = Promise.resolve(1); + + promise.then(function(value){ + // value === 1 + }); + ``` + + @method resolve + @static + @param {Any} value value that the returned promise will be resolved with + Useful for tooling. + @return {Promise} a promise that will become fulfilled with the given + `value` +*/ +function resolve(object) { + /*jshint validthis:true */ + var Constructor = this; + + if (object && typeof object === 'object' && object.constructor === Constructor) { + return object; + } + + var promise = new Constructor(noop); + _resolve(promise, object); + return promise; +} + +var PROMISE_ID = Math.random().toString(36).substring(16); + +function noop() {} + +var PENDING = void 0; +var FULFILLED = 1; +var REJECTED = 2; + +var GET_THEN_ERROR = new ErrorObject(); + +function selfFulfillment() { + return new TypeError("You cannot resolve a promise with itself"); +} + +function cannotReturnOwn() { + return new TypeError('A promises callback cannot return that same promise.'); +} + +function getThen(promise) { + try { + return promise.then; + } catch (error) { + GET_THEN_ERROR.error = error; + return GET_THEN_ERROR; + } +} + +function tryThen(then, value, fulfillmentHandler, rejectionHandler) { + try { + then.call(value, fulfillmentHandler, rejectionHandler); + } catch (e) { + return e; + } +} + +function handleForeignThenable(promise, thenable, then) { + asap(function (promise) { + var sealed = false; + var error = tryThen(then, thenable, function (value) { + if (sealed) { + return; + } + sealed = true; + if (thenable !== value) { + _resolve(promise, value); + } else { + fulfill(promise, value); + } + }, function (reason) { + if (sealed) { + return; + } + sealed = true; + + _reject(promise, reason); + }, 'Settle: ' + (promise._label || ' unknown promise')); + + if (!sealed && error) { + sealed = true; + _reject(promise, error); + } + }, promise); +} + +function handleOwnThenable(promise, thenable) { + if (thenable._state === FULFILLED) { + fulfill(promise, thenable._result); + } else if (thenable._state === REJECTED) { + _reject(promise, thenable._result); + } else { + subscribe(thenable, undefined, function (value) { + return _resolve(promise, value); + }, function (reason) { + return _reject(promise, reason); + }); + } +} + +function handleMaybeThenable(promise, maybeThenable, then$$) { + if (maybeThenable.constructor === promise.constructor && then$$ === then && maybeThenable.constructor.resolve === resolve) { + handleOwnThenable(promise, maybeThenable); + } else { + if (then$$ === GET_THEN_ERROR) { + _reject(promise, GET_THEN_ERROR.error); + } else if (then$$ === undefined) { + fulfill(promise, maybeThenable); + } else if (isFunction(then$$)) { + handleForeignThenable(promise, maybeThenable, then$$); + } else { + fulfill(promise, maybeThenable); + } + } +} + +function _resolve(promise, value) { + if (promise === value) { + _reject(promise, selfFulfillment()); + } else if (objectOrFunction(value)) { + handleMaybeThenable(promise, value, getThen(value)); + } else { + fulfill(promise, value); + } +} + +function publishRejection(promise) { + if (promise._onerror) { + promise._onerror(promise._result); + } + + publish(promise); +} + +function fulfill(promise, value) { + if (promise._state !== PENDING) { + return; + } + + promise._result = value; + promise._state = FULFILLED; + + if (promise._subscribers.length !== 0) { + asap(publish, promise); + } +} + +function _reject(promise, reason) { + if (promise._state !== PENDING) { + return; + } + promise._state = REJECTED; + promise._result = reason; + + asap(publishRejection, promise); +} + +function subscribe(parent, child, onFulfillment, onRejection) { + var _subscribers = parent._subscribers; + var length = _subscribers.length; + + parent._onerror = null; + + _subscribers[length] = child; + _subscribers[length + FULFILLED] = onFulfillment; + _subscribers[length + REJECTED] = onRejection; + + if (length === 0 && parent._state) { + asap(publish, parent); + } +} + +function publish(promise) { + var subscribers = promise._subscribers; + var settled = promise._state; + + if (subscribers.length === 0) { + return; + } + + var child = undefined, + callback = undefined, + detail = promise._result; + + for (var i = 0; i < subscribers.length; i += 3) { + child = subscribers[i]; + callback = subscribers[i + settled]; + + if (child) { + invokeCallback(settled, child, callback, detail); + } else { + callback(detail); + } + } + + promise._subscribers.length = 0; +} + +function ErrorObject() { + this.error = null; +} + +var TRY_CATCH_ERROR = new ErrorObject(); + +function tryCatch(callback, detail) { + try { + return callback(detail); + } catch (e) { + TRY_CATCH_ERROR.error = e; + return TRY_CATCH_ERROR; + } +} + +function invokeCallback(settled, promise, callback, detail) { + var hasCallback = isFunction(callback), + value = undefined, + error = undefined, + succeeded = undefined, + failed = undefined; + + if (hasCallback) { + value = tryCatch(callback, detail); + + if (value === TRY_CATCH_ERROR) { + failed = true; + error = value.error; + value = null; + } else { + succeeded = true; + } + + if (promise === value) { + _reject(promise, cannotReturnOwn()); + return; + } + } else { + value = detail; + succeeded = true; + } + + if (promise._state !== PENDING) { + // noop + } else if (hasCallback && succeeded) { + _resolve(promise, value); + } else if (failed) { + _reject(promise, error); + } else if (settled === FULFILLED) { + fulfill(promise, value); + } else if (settled === REJECTED) { + _reject(promise, value); + } +} + +function initializePromise(promise, resolver) { + try { + resolver(function resolvePromise(value) { + _resolve(promise, value); + }, function rejectPromise(reason) { + _reject(promise, reason); + }); + } catch (e) { + _reject(promise, e); + } +} + +var id = 0; +function nextId() { + return id++; +} + +function makePromise(promise) { + promise[PROMISE_ID] = id++; + promise._state = undefined; + promise._result = undefined; + promise._subscribers = []; +} + +function Enumerator(Constructor, input) { + this._instanceConstructor = Constructor; + this.promise = new Constructor(noop); + + if (!this.promise[PROMISE_ID]) { + makePromise(this.promise); + } + + if (isArray(input)) { + this._input = input; + this.length = input.length; + this._remaining = input.length; + + this._result = new Array(this.length); + + if (this.length === 0) { + fulfill(this.promise, this._result); + } else { + this.length = this.length || 0; + this._enumerate(); + if (this._remaining === 0) { + fulfill(this.promise, this._result); + } + } + } else { + _reject(this.promise, validationError()); + } +} + +function validationError() { + return new Error('Array Methods must be provided an Array'); +}; + +Enumerator.prototype._enumerate = function () { + var length = this.length; + var _input = this._input; + + for (var i = 0; this._state === PENDING && i < length; i++) { + this._eachEntry(_input[i], i); + } +}; + +Enumerator.prototype._eachEntry = function (entry, i) { + var c = this._instanceConstructor; + var resolve$$ = c.resolve; + + if (resolve$$ === resolve) { + var _then = getThen(entry); + + if (_then === then && entry._state !== PENDING) { + this._settledAt(entry._state, i, entry._result); + } else if (typeof _then !== 'function') { + this._remaining--; + this._result[i] = entry; + } else if (c === Promise) { + var promise = new c(noop); + handleMaybeThenable(promise, entry, _then); + this._willSettleAt(promise, i); + } else { + this._willSettleAt(new c(function (resolve$$) { + return resolve$$(entry); + }), i); + } + } else { + this._willSettleAt(resolve$$(entry), i); + } +}; + +Enumerator.prototype._settledAt = function (state, i, value) { + var promise = this.promise; + + if (promise._state === PENDING) { + this._remaining--; + + if (state === REJECTED) { + _reject(promise, value); + } else { + this._result[i] = value; + } + } + + if (this._remaining === 0) { + fulfill(promise, this._result); + } +}; + +Enumerator.prototype._willSettleAt = function (promise, i) { + var enumerator = this; + + subscribe(promise, undefined, function (value) { + return enumerator._settledAt(FULFILLED, i, value); + }, function (reason) { + return enumerator._settledAt(REJECTED, i, reason); + }); +}; + +/** + `Promise.all` accepts an array of promises, and returns a new promise which + is fulfilled with an array of fulfillment values for the passed promises, or + rejected with the reason of the first passed promise to be rejected. It casts all + elements of the passed iterable to promises as it runs this algorithm. + + Example: + + ```javascript + let promise1 = resolve(1); + let promise2 = resolve(2); + let promise3 = resolve(3); + let promises = [ promise1, promise2, promise3 ]; + + Promise.all(promises).then(function(array){ + // The array here would be [ 1, 2, 3 ]; + }); + ``` + + If any of the `promises` given to `all` are rejected, the first promise + that is rejected will be given as an argument to the returned promises's + rejection handler. For example: + + Example: + + ```javascript + let promise1 = resolve(1); + let promise2 = reject(new Error("2")); + let promise3 = reject(new Error("3")); + let promises = [ promise1, promise2, promise3 ]; + + Promise.all(promises).then(function(array){ + // Code here never runs because there are rejected promises! + }, function(error) { + // error.message === "2" + }); + ``` + + @method all + @static + @param {Array} entries array of promises + @param {String} label optional string for labeling the promise. + Useful for tooling. + @return {Promise} promise that is fulfilled when all `promises` have been + fulfilled, or rejected if any of them become rejected. + @static +*/ +function all(entries) { + return new Enumerator(this, entries).promise; +} + +/** + `Promise.race` returns a new promise which is settled in the same way as the + first passed promise to settle. + + Example: + + ```javascript + let promise1 = new Promise(function(resolve, reject){ + setTimeout(function(){ + resolve('promise 1'); + }, 200); + }); + + let promise2 = new Promise(function(resolve, reject){ + setTimeout(function(){ + resolve('promise 2'); + }, 100); + }); + + Promise.race([promise1, promise2]).then(function(result){ + // result === 'promise 2' because it was resolved before promise1 + // was resolved. + }); + ``` + + `Promise.race` is deterministic in that only the state of the first + settled promise matters. For example, even if other promises given to the + `promises` array argument are resolved, but the first settled promise has + become rejected before the other promises became fulfilled, the returned + promise will become rejected: + + ```javascript + let promise1 = new Promise(function(resolve, reject){ + setTimeout(function(){ + resolve('promise 1'); + }, 200); + }); + + let promise2 = new Promise(function(resolve, reject){ + setTimeout(function(){ + reject(new Error('promise 2')); + }, 100); + }); + + Promise.race([promise1, promise2]).then(function(result){ + // Code here never runs + }, function(reason){ + // reason.message === 'promise 2' because promise 2 became rejected before + // promise 1 became fulfilled + }); + ``` + + An example real-world use case is implementing timeouts: + + ```javascript + Promise.race([ajax('foo.json'), timeout(5000)]) + ``` + + @method race + @static + @param {Array} promises array of promises to observe + Useful for tooling. + @return {Promise} a promise which settles in the same way as the first passed + promise to settle. +*/ +function race(entries) { + /*jshint validthis:true */ + var Constructor = this; + + if (!isArray(entries)) { + return new Constructor(function (_, reject) { + return reject(new TypeError('You must pass an array to race.')); + }); + } else { + return new Constructor(function (resolve, reject) { + var length = entries.length; + for (var i = 0; i < length; i++) { + Constructor.resolve(entries[i]).then(resolve, reject); + } + }); + } +} + +/** + `Promise.reject` returns a promise rejected with the passed `reason`. + It is shorthand for the following: + + ```javascript + let promise = new Promise(function(resolve, reject){ + reject(new Error('WHOOPS')); + }); + + promise.then(function(value){ + // Code here doesn't run because the promise is rejected! + }, function(reason){ + // reason.message === 'WHOOPS' + }); + ``` + + Instead of writing the above, your code now simply becomes the following: + + ```javascript + let promise = Promise.reject(new Error('WHOOPS')); + + promise.then(function(value){ + // Code here doesn't run because the promise is rejected! + }, function(reason){ + // reason.message === 'WHOOPS' + }); + ``` + + @method reject + @static + @param {Any} reason value that the returned promise will be rejected with. + Useful for tooling. + @return {Promise} a promise rejected with the given `reason`. +*/ +function reject(reason) { + /*jshint validthis:true */ + var Constructor = this; + var promise = new Constructor(noop); + _reject(promise, reason); + return promise; +} + +function needsResolver() { + throw new TypeError('You must pass a resolver function as the first argument to the promise constructor'); +} + +function needsNew() { + throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function."); +} + +/** + Promise objects represent the eventual result of an asynchronous operation. The + primary way of interacting with a promise is through its `then` method, which + registers callbacks to receive either a promise's eventual value or the reason + why the promise cannot be fulfilled. + + Terminology + ----------- + + - `promise` is an object or function with a `then` method whose behavior conforms to this specification. + - `thenable` is an object or function that defines a `then` method. + - `value` is any legal JavaScript value (including undefined, a thenable, or a promise). + - `exception` is a value that is thrown using the throw statement. + - `reason` is a value that indicates why a promise was rejected. + - `settled` the final resting state of a promise, fulfilled or rejected. + + A promise can be in one of three states: pending, fulfilled, or rejected. + + Promises that are fulfilled have a fulfillment value and are in the fulfilled + state. Promises that are rejected have a rejection reason and are in the + rejected state. A fulfillment value is never a thenable. + + Promises can also be said to *resolve* a value. If this value is also a + promise, then the original promise's settled state will match the value's + settled state. So a promise that *resolves* a promise that rejects will + itself reject, and a promise that *resolves* a promise that fulfills will + itself fulfill. + + + Basic Usage: + ------------ + + ```js + let promise = new Promise(function(resolve, reject) { + // on success + resolve(value); + + // on failure + reject(reason); + }); + + promise.then(function(value) { + // on fulfillment + }, function(reason) { + // on rejection + }); + ``` + + Advanced Usage: + --------------- + + Promises shine when abstracting away asynchronous interactions such as + `XMLHttpRequest`s. + + ```js + function getJSON(url) { + return new Promise(function(resolve, reject){ + let xhr = new XMLHttpRequest(); + + xhr.open('GET', url); + xhr.onreadystatechange = handler; + xhr.responseType = 'json'; + xhr.setRequestHeader('Accept', 'application/json'); + xhr.send(); + + function handler() { + if (this.readyState === this.DONE) { + if (this.status === 200) { + resolve(this.response); + } else { + reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']')); + } + } + }; + }); + } + + getJSON('/posts.json').then(function(json) { + // on fulfillment + }, function(reason) { + // on rejection + }); + ``` + + Unlike callbacks, promises are great composable primitives. + + ```js + Promise.all([ + getJSON('/posts'), + getJSON('/comments') + ]).then(function(values){ + values[0] // => postsJSON + values[1] // => commentsJSON + + return values; + }); + ``` + + @class Promise + @param {function} resolver + Useful for tooling. + @constructor +*/ +function Promise(resolver) { + this[PROMISE_ID] = nextId(); + this._result = this._state = undefined; + this._subscribers = []; + + if (noop !== resolver) { + typeof resolver !== 'function' && needsResolver(); + this instanceof Promise ? initializePromise(this, resolver) : needsNew(); + } +} + +Promise.all = all; +Promise.race = race; +Promise.resolve = resolve; +Promise.reject = reject; +Promise._setScheduler = setScheduler; +Promise._setAsap = setAsap; +Promise._asap = asap; + +Promise.prototype = { + constructor: Promise, + + /** + The primary way of interacting with a promise is through its `then` method, + which registers callbacks to receive either a promise's eventual value or the + reason why the promise cannot be fulfilled. + + ```js + findUser().then(function(user){ + // user is available + }, function(reason){ + // user is unavailable, and you are given the reason why + }); + ``` + + Chaining + -------- + + The return value of `then` is itself a promise. This second, 'downstream' + promise is resolved with the return value of the first promise's fulfillment + or rejection handler, or rejected if the handler throws an exception. + + ```js + findUser().then(function (user) { + return user.name; + }, function (reason) { + return 'default name'; + }).then(function (userName) { + // If `findUser` fulfilled, `userName` will be the user's name, otherwise it + // will be `'default name'` + }); + + findUser().then(function (user) { + throw new Error('Found user, but still unhappy'); + }, function (reason) { + throw new Error('`findUser` rejected and we're unhappy'); + }).then(function (value) { + // never reached + }, function (reason) { + // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'. + // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'. + }); + ``` + If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream. + + ```js + findUser().then(function (user) { + throw new PedagogicalException('Upstream error'); + }).then(function (value) { + // never reached + }).then(function (value) { + // never reached + }, function (reason) { + // The `PedgagocialException` is propagated all the way down to here + }); + ``` + + Assimilation + ------------ + + Sometimes the value you want to propagate to a downstream promise can only be + retrieved asynchronously. This can be achieved by returning a promise in the + fulfillment or rejection handler. The downstream promise will then be pending + until the returned promise is settled. This is called *assimilation*. + + ```js + findUser().then(function (user) { + return findCommentsByAuthor(user); + }).then(function (comments) { + // The user's comments are now available + }); + ``` + + If the assimliated promise rejects, then the downstream promise will also reject. + + ```js + findUser().then(function (user) { + return findCommentsByAuthor(user); + }).then(function (comments) { + // If `findCommentsByAuthor` fulfills, we'll have the value here + }, function (reason) { + // If `findCommentsByAuthor` rejects, we'll have the reason here + }); + ``` + + Simple Example + -------------- + + Synchronous Example + + ```javascript + let result; + + try { + result = findResult(); + // success + } catch(reason) { + // failure + } + ``` + + Errback Example + + ```js + findResult(function(result, err){ + if (err) { + // failure + } else { + // success + } + }); + ``` + + Promise Example; + + ```javascript + findResult().then(function(result){ + // success + }, function(reason){ + // failure + }); + ``` + + Advanced Example + -------------- + + Synchronous Example + + ```javascript + let author, books; + + try { + author = findAuthor(); + books = findBooksByAuthor(author); + // success + } catch(reason) { + // failure + } + ``` + + Errback Example + + ```js + + function foundBooks(books) { + + } + + function failure(reason) { + + } + + findAuthor(function(author, err){ + if (err) { + failure(err); + // failure + } else { + try { + findBoooksByAuthor(author, function(books, err) { + if (err) { + failure(err); + } else { + try { + foundBooks(books); + } catch(reason) { + failure(reason); + } + } + }); + } catch(error) { + failure(err); + } + // success + } + }); + ``` + + Promise Example; + + ```javascript + findAuthor(). + then(findBooksByAuthor). + then(function(books){ + // found books + }).catch(function(reason){ + // something went wrong + }); + ``` + + @method then + @param {Function} onFulfilled + @param {Function} onRejected + Useful for tooling. + @return {Promise} + */ + then: then, + + /** + `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same + as the catch block of a try/catch statement. + + ```js + function findAuthor(){ + throw new Error('couldn't find that author'); + } + + // synchronous + try { + findAuthor(); + } catch(reason) { + // something went wrong + } + + // async with promises + findAuthor().catch(function(reason){ + // something went wrong + }); + ``` + + @method catch + @param {Function} onRejection + Useful for tooling. + @return {Promise} + */ + 'catch': function _catch(onRejection) { + return this.then(null, onRejection); + } +}; + +function polyfill() { + var local = undefined; + + if (typeof global !== 'undefined') { + local = global; + } else if (typeof self !== 'undefined') { + local = self; + } else { + try { + local = Function('return this')(); + } catch (e) { + throw new Error('polyfill failed because global object is unavailable in this environment'); + } + } + + var P = local.Promise; + + if (P) { + var promiseToString = null; + try { + promiseToString = Object.prototype.toString.call(P.resolve()); + } catch (e) { + // silently ignored + } + + if (promiseToString === '[object Promise]' && !P.cast) { + return; + } + } + + local.Promise = Promise; +} + +polyfill(); +// Strange compat.. +Promise.polyfill = polyfill; +Promise.Promise = Promise; + +return Promise; + +}))); + +}).call(this,_dereq_('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"_process":33}],18:[function(_dereq_,module,exports){ +/** + * inspired by is-number + * but significantly simplified and sped up by ignoring number and string constructors + * ie these return false: + * new Number(1) + * new String('1') + */ + +'use strict'; + +var allBlankCharCodes = _dereq_('is-string-blank'); + +module.exports = function(n) { + var type = typeof n; + if(type === 'string') { + var original = n; + n = +n; + // whitespace strings cast to zero - filter them out + if(n===0 && allBlankCharCodes(original)) return false; + } + else if(type !== 'number') return false; + + return n - n < 1; +}; + +},{"is-string-blank":23}],19:[function(_dereq_,module,exports){ +module.exports = fromQuat; + +/** + * Creates a matrix from a quaternion rotation. + * + * @param {mat4} out mat4 receiving operation result + * @param {quat4} q Rotation quaternion + * @returns {mat4} out + */ +function fromQuat(out, q) { + var x = q[0], y = q[1], z = q[2], w = q[3], + x2 = x + x, + y2 = y + y, + z2 = z + z, + + xx = x * x2, + yx = y * x2, + yy = y * y2, + zx = z * x2, + zy = z * y2, + zz = z * z2, + wx = w * x2, + wy = w * y2, + wz = w * z2; + + out[0] = 1 - yy - zz; + out[1] = yx + wz; + out[2] = zx - wy; + out[3] = 0; + + out[4] = yx - wz; + out[5] = 1 - xx - zz; + out[6] = zy + wx; + out[7] = 0; + + out[8] = zx + wy; + out[9] = zy - wx; + out[10] = 1 - xx - yy; + out[11] = 0; + + out[12] = 0; + out[13] = 0; + out[14] = 0; + out[15] = 1; + + return out; +}; +},{}],20:[function(_dereq_,module,exports){ +(function (global){ +'use strict' + +var isBrowser = _dereq_('is-browser') +var hasHover + +if (typeof global.matchMedia === 'function') { + hasHover = !global.matchMedia('(hover: none)').matches +} +else { + hasHover = isBrowser +} + +module.exports = hasHover + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"is-browser":22}],21:[function(_dereq_,module,exports){ +'use strict' + +var isBrowser = _dereq_('is-browser') + +function detect() { + var supported = false + + try { + var opts = Object.defineProperty({}, 'passive', { + get: function() { + supported = true + } + }) + + window.addEventListener('test', null, opts) + window.removeEventListener('test', null, opts) + } catch(e) { + supported = false + } + + return supported +} + +module.exports = isBrowser && detect() + +},{"is-browser":22}],22:[function(_dereq_,module,exports){ +module.exports = true; +},{}],23:[function(_dereq_,module,exports){ +'use strict'; + +/** + * Is this string all whitespace? + * This solution kind of makes my brain hurt, but it's significantly faster + * than !str.trim() or any other solution I could find. + * + * whitespace codes from: http://en.wikipedia.org/wiki/Whitespace_character + * and verified with: + * + * for(var i = 0; i < 65536; i++) { + * var s = String.fromCharCode(i); + * if(+s===0 && !s.trim()) console.log(i, s); + * } + * + * which counts a couple of these as *not* whitespace, but finds nothing else + * that *is* whitespace. Note that charCodeAt stops at 16 bits, but it appears + * that there are no whitespace characters above this, and code points above + * this do not map onto white space characters. + */ + +module.exports = function(str){ + var l = str.length, + a; + for(var i = 0; i < l; i++) { + a = str.charCodeAt(i); + if((a < 9 || a > 13) && (a !== 32) && (a !== 133) && (a !== 160) && + (a !== 5760) && (a !== 6158) && (a < 8192 || a > 8205) && + (a !== 8232) && (a !== 8233) && (a !== 8239) && (a !== 8287) && + (a !== 8288) && (a !== 12288) && (a !== 65279)) { + return false; + } + } + return true; +} + +},{}],24:[function(_dereq_,module,exports){ +var rootPosition = { left: 0, top: 0 } + +module.exports = mouseEventOffset +function mouseEventOffset (ev, target, out) { + target = target || ev.currentTarget || ev.srcElement + if (!Array.isArray(out)) { + out = [ 0, 0 ] + } + var cx = ev.clientX || 0 + var cy = ev.clientY || 0 + var rect = getBoundingClientOffset(target) + out[0] = cx - rect.left + out[1] = cy - rect.top + return out +} + +function getBoundingClientOffset (element) { + if (element === window || + element === document || + element === document.body) { + return rootPosition + } else { + return element.getBoundingClientRect() + } +} + +},{}],25:[function(_dereq_,module,exports){ +/* + * @copyright 2016 Sean Connelly (@voidqk), http://syntheti.cc + * @license MIT + * @preserve Project Home: https://github.com/voidqk/polybooljs + */ + +var BuildLog = _dereq_('./lib/build-log'); +var Epsilon = _dereq_('./lib/epsilon'); +var Intersecter = _dereq_('./lib/intersecter'); +var SegmentChainer = _dereq_('./lib/segment-chainer'); +var SegmentSelector = _dereq_('./lib/segment-selector'); +var GeoJSON = _dereq_('./lib/geojson'); + +var buildLog = false; +var epsilon = Epsilon(); + +var PolyBool; +PolyBool = { + // getter/setter for buildLog + buildLog: function(bl){ + if (bl === true) + buildLog = BuildLog(); + else if (bl === false) + buildLog = false; + return buildLog === false ? false : buildLog.list; + }, + // getter/setter for epsilon + epsilon: function(v){ + return epsilon.epsilon(v); + }, + + // core API + segments: function(poly){ + var i = Intersecter(true, epsilon, buildLog); + poly.regions.forEach(i.addRegion); + return { + segments: i.calculate(poly.inverted), + inverted: poly.inverted + }; + }, + combine: function(segments1, segments2){ + var i3 = Intersecter(false, epsilon, buildLog); + return { + combined: i3.calculate( + segments1.segments, segments1.inverted, + segments2.segments, segments2.inverted + ), + inverted1: segments1.inverted, + inverted2: segments2.inverted + }; + }, + selectUnion: function(combined){ + return { + segments: SegmentSelector.union(combined.combined, buildLog), + inverted: combined.inverted1 || combined.inverted2 + } + }, + selectIntersect: function(combined){ + return { + segments: SegmentSelector.intersect(combined.combined, buildLog), + inverted: combined.inverted1 && combined.inverted2 + } + }, + selectDifference: function(combined){ + return { + segments: SegmentSelector.difference(combined.combined, buildLog), + inverted: combined.inverted1 && !combined.inverted2 + } + }, + selectDifferenceRev: function(combined){ + return { + segments: SegmentSelector.differenceRev(combined.combined, buildLog), + inverted: !combined.inverted1 && combined.inverted2 + } + }, + selectXor: function(combined){ + return { + segments: SegmentSelector.xor(combined.combined, buildLog), + inverted: combined.inverted1 !== combined.inverted2 + } + }, + polygon: function(segments){ + return { + regions: SegmentChainer(segments.segments, epsilon, buildLog), + inverted: segments.inverted + }; + }, + + // GeoJSON converters + polygonFromGeoJSON: function(geojson){ + return GeoJSON.toPolygon(PolyBool, geojson); + }, + polygonToGeoJSON: function(poly){ + return GeoJSON.fromPolygon(PolyBool, epsilon, poly); + }, + + // helper functions for common operations + union: function(poly1, poly2){ + return operate(poly1, poly2, PolyBool.selectUnion); + }, + intersect: function(poly1, poly2){ + return operate(poly1, poly2, PolyBool.selectIntersect); + }, + difference: function(poly1, poly2){ + return operate(poly1, poly2, PolyBool.selectDifference); + }, + differenceRev: function(poly1, poly2){ + return operate(poly1, poly2, PolyBool.selectDifferenceRev); + }, + xor: function(poly1, poly2){ + return operate(poly1, poly2, PolyBool.selectXor); + } +}; + +function operate(poly1, poly2, selector){ + var seg1 = PolyBool.segments(poly1); + var seg2 = PolyBool.segments(poly2); + var comb = PolyBool.combine(seg1, seg2); + var seg3 = selector(comb); + return PolyBool.polygon(seg3); +} + +if (typeof window === 'object') + window.PolyBool = PolyBool; + +module.exports = PolyBool; + +},{"./lib/build-log":26,"./lib/epsilon":27,"./lib/geojson":28,"./lib/intersecter":29,"./lib/segment-chainer":31,"./lib/segment-selector":32}],26:[function(_dereq_,module,exports){ +// (c) Copyright 2016, Sean Connelly (@voidqk), http://syntheti.cc +// MIT License +// Project Home: https://github.com/voidqk/polybooljs + +// +// used strictly for logging the processing of the algorithm... only useful if you intend on +// looking under the covers (for pretty UI's or debugging) +// + +function BuildLog(){ + var my; + var nextSegmentId = 0; + var curVert = false; + + function push(type, data){ + my.list.push({ + type: type, + data: data ? JSON.parse(JSON.stringify(data)) : void 0 + }); + return my; + } + + my = { + list: [], + segmentId: function(){ + return nextSegmentId++; + }, + checkIntersection: function(seg1, seg2){ + return push('check', { seg1: seg1, seg2: seg2 }); + }, + segmentChop: function(seg, end){ + push('div_seg', { seg: seg, pt: end }); + return push('chop', { seg: seg, pt: end }); + }, + statusRemove: function(seg){ + return push('pop_seg', { seg: seg }); + }, + segmentUpdate: function(seg){ + return push('seg_update', { seg: seg }); + }, + segmentNew: function(seg, primary){ + return push('new_seg', { seg: seg, primary: primary }); + }, + segmentRemove: function(seg){ + return push('rem_seg', { seg: seg }); + }, + tempStatus: function(seg, above, below){ + return push('temp_status', { seg: seg, above: above, below: below }); + }, + rewind: function(seg){ + return push('rewind', { seg: seg }); + }, + status: function(seg, above, below){ + return push('status', { seg: seg, above: above, below: below }); + }, + vert: function(x){ + if (x === curVert) + return my; + curVert = x; + return push('vert', { x: x }); + }, + log: function(data){ + if (typeof data !== 'string') + data = JSON.stringify(data, false, ' '); + return push('log', { txt: data }); + }, + reset: function(){ + return push('reset'); + }, + selected: function(segs){ + return push('selected', { segs: segs }); + }, + chainStart: function(seg){ + return push('chain_start', { seg: seg }); + }, + chainRemoveHead: function(index, pt){ + return push('chain_rem_head', { index: index, pt: pt }); + }, + chainRemoveTail: function(index, pt){ + return push('chain_rem_tail', { index: index, pt: pt }); + }, + chainNew: function(pt1, pt2){ + return push('chain_new', { pt1: pt1, pt2: pt2 }); + }, + chainMatch: function(index){ + return push('chain_match', { index: index }); + }, + chainClose: function(index){ + return push('chain_close', { index: index }); + }, + chainAddHead: function(index, pt){ + return push('chain_add_head', { index: index, pt: pt }); + }, + chainAddTail: function(index, pt){ + return push('chain_add_tail', { index: index, pt: pt, }); + }, + chainConnect: function(index1, index2){ + return push('chain_con', { index1: index1, index2: index2 }); + }, + chainReverse: function(index){ + return push('chain_rev', { index: index }); + }, + chainJoin: function(index1, index2){ + return push('chain_join', { index1: index1, index2: index2 }); + }, + done: function(){ + return push('done'); + } + }; + return my; +} + +module.exports = BuildLog; + +},{}],27:[function(_dereq_,module,exports){ +// (c) Copyright 2016, Sean Connelly (@voidqk), http://syntheti.cc +// MIT License +// Project Home: https://github.com/voidqk/polybooljs + +// +// provides the raw computation functions that takes epsilon into account +// +// zero is defined to be between (-epsilon, epsilon) exclusive +// + +function Epsilon(eps){ + if (typeof eps !== 'number') + eps = 0.0000000001; // sane default? sure why not + var my = { + epsilon: function(v){ + if (typeof v === 'number') + eps = v; + return eps; + }, + pointAboveOrOnLine: function(pt, left, right){ + var Ax = left[0]; + var Ay = left[1]; + var Bx = right[0]; + var By = right[1]; + var Cx = pt[0]; + var Cy = pt[1]; + return (Bx - Ax) * (Cy - Ay) - (By - Ay) * (Cx - Ax) >= -eps; + }, + pointBetween: function(p, left, right){ + // p must be collinear with left->right + // returns false if p == left, p == right, or left == right + var d_py_ly = p[1] - left[1]; + var d_rx_lx = right[0] - left[0]; + var d_px_lx = p[0] - left[0]; + var d_ry_ly = right[1] - left[1]; + + var dot = d_px_lx * d_rx_lx + d_py_ly * d_ry_ly; + // if `dot` is 0, then `p` == `left` or `left` == `right` (reject) + // if `dot` is less than 0, then `p` is to the left of `left` (reject) + if (dot < eps) + return false; + + var sqlen = d_rx_lx * d_rx_lx + d_ry_ly * d_ry_ly; + // if `dot` > `sqlen`, then `p` is to the right of `right` (reject) + // therefore, if `dot - sqlen` is greater than 0, then `p` is to the right of `right` (reject) + if (dot - sqlen > -eps) + return false; + + return true; + }, + pointsSameX: function(p1, p2){ + return Math.abs(p1[0] - p2[0]) < eps; + }, + pointsSameY: function(p1, p2){ + return Math.abs(p1[1] - p2[1]) < eps; + }, + pointsSame: function(p1, p2){ + return my.pointsSameX(p1, p2) && my.pointsSameY(p1, p2); + }, + pointsCompare: function(p1, p2){ + // returns -1 if p1 is smaller, 1 if p2 is smaller, 0 if equal + if (my.pointsSameX(p1, p2)) + return my.pointsSameY(p1, p2) ? 0 : (p1[1] < p2[1] ? -1 : 1); + return p1[0] < p2[0] ? -1 : 1; + }, + pointsCollinear: function(pt1, pt2, pt3){ + // does pt1->pt2->pt3 make a straight line? + // essentially this is just checking to see if the slope(pt1->pt2) === slope(pt2->pt3) + // if slopes are equal, then they must be collinear, because they share pt2 + var dx1 = pt1[0] - pt2[0]; + var dy1 = pt1[1] - pt2[1]; + var dx2 = pt2[0] - pt3[0]; + var dy2 = pt2[1] - pt3[1]; + return Math.abs(dx1 * dy2 - dx2 * dy1) < eps; + }, + linesIntersect: function(a0, a1, b0, b1){ + // returns false if the lines are coincident (e.g., parallel or on top of each other) + // + // returns an object if the lines intersect: + // { + // pt: [x, y], where the intersection point is at + // alongA: where intersection point is along A, + // alongB: where intersection point is along B + // } + // + // alongA and alongB will each be one of: -2, -1, 0, 1, 2 + // + // with the following meaning: + // + // -2 intersection point is before segment's first point + // -1 intersection point is directly on segment's first point + // 0 intersection point is between segment's first and second points (exclusive) + // 1 intersection point is directly on segment's second point + // 2 intersection point is after segment's second point + var adx = a1[0] - a0[0]; + var ady = a1[1] - a0[1]; + var bdx = b1[0] - b0[0]; + var bdy = b1[1] - b0[1]; + + var axb = adx * bdy - ady * bdx; + if (Math.abs(axb) < eps) + return false; // lines are coincident + + var dx = a0[0] - b0[0]; + var dy = a0[1] - b0[1]; + + var A = (bdx * dy - bdy * dx) / axb; + var B = (adx * dy - ady * dx) / axb; + + var ret = { + alongA: 0, + alongB: 0, + pt: [ + a0[0] + A * adx, + a0[1] + A * ady + ] + }; + + // categorize where intersection point is along A and B + + if (A <= -eps) + ret.alongA = -2; + else if (A < eps) + ret.alongA = -1; + else if (A - 1 <= -eps) + ret.alongA = 0; + else if (A - 1 < eps) + ret.alongA = 1; + else + ret.alongA = 2; + + if (B <= -eps) + ret.alongB = -2; + else if (B < eps) + ret.alongB = -1; + else if (B - 1 <= -eps) + ret.alongB = 0; + else if (B - 1 < eps) + ret.alongB = 1; + else + ret.alongB = 2; + + return ret; + }, + pointInsideRegion: function(pt, region){ + var x = pt[0]; + var y = pt[1]; + var last_x = region[region.length - 1][0]; + var last_y = region[region.length - 1][1]; + var inside = false; + for (var i = 0; i < region.length; i++){ + var curr_x = region[i][0]; + var curr_y = region[i][1]; + + // if y is between curr_y and last_y, and + // x is to the right of the boundary created by the line + if ((curr_y - y > eps) != (last_y - y > eps) && + (last_x - curr_x) * (y - curr_y) / (last_y - curr_y) + curr_x - x > eps) + inside = !inside + + last_x = curr_x; + last_y = curr_y; + } + return inside; + } + }; + return my; +} + +module.exports = Epsilon; + +},{}],28:[function(_dereq_,module,exports){ +// (c) Copyright 2017, Sean Connelly (@voidqk), http://syntheti.cc +// MIT License +// Project Home: https://github.com/voidqk/polybooljs + +// +// convert between PolyBool polygon format and GeoJSON formats (Polygon and MultiPolygon) +// + +var GeoJSON = { + // convert a GeoJSON object to a PolyBool polygon + toPolygon: function(PolyBool, geojson){ + + // converts list of LineString's to segments + function GeoPoly(coords){ + // check for empty coords + if (coords.length <= 0) + return PolyBool.segments({ inverted: false, regions: [] }); + + // convert LineString to segments + function LineString(ls){ + // remove tail which should be the same as head + var reg = ls.slice(0, ls.length - 1); + return PolyBool.segments({ inverted: false, regions: [reg] }); + } + + // the first LineString is considered the outside + var out = LineString(coords[0]); + + // the rest of the LineStrings are considered interior holes, so subtract them from the + // current result + for (var i = 1; i < coords.length; i++) + out = PolyBool.selectDifference(PolyBool.combine(out, LineString(coords[i]))); + + return out; + } + + if (geojson.type === 'Polygon'){ + // single polygon, so just convert it and we're done + return PolyBool.polygon(GeoPoly(geojson.coordinates)); + } + else if (geojson.type === 'MultiPolygon'){ + // multiple polygons, so union all the polygons together + var out = PolyBool.segments({ inverted: false, regions: [] }); + for (var i = 0; i < geojson.coordinates.length; i++) + out = PolyBool.selectUnion(PolyBool.combine(out, GeoPoly(geojson.coordinates[i]))); + return PolyBool.polygon(out); + } + throw new Error('PolyBool: Cannot convert GeoJSON object to PolyBool polygon'); + }, + + // convert a PolyBool polygon to a GeoJSON object + fromPolygon: function(PolyBool, eps, poly){ + // make sure out polygon is clean + poly = PolyBool.polygon(PolyBool.segments(poly)); + + // test if r1 is inside r2 + function regionInsideRegion(r1, r2){ + // we're guaranteed no lines intersect (because the polygon is clean), but a vertex + // could be on the edge -- so we just average pt[0] and pt[1] to produce a point on the + // edge of the first line, which cannot be on an edge + return eps.pointInsideRegion([ + (r1[0][0] + r1[1][0]) * 0.5, + (r1[0][1] + r1[1][1]) * 0.5 + ], r2); + } + + // calculate inside heirarchy + // + // _____________________ _______ roots -> A -> F + // | A | | F | | | + // | _______ _______ | | ___ | +-- B +-- G + // | | B | | C | | | | | | | | + // | | ___ | | ___ | | | | | | | +-- D + // | | | D | | | | E | | | | | G | | | + // | | |___| | | |___| | | | | | | +-- C + // | |_______| |_______| | | |___| | | + // |_____________________| |_______| +-- E + + function newNode(region){ + return { + region: region, + children: [] + }; + } + + var roots = newNode(null); + + function addChild(root, region){ + // first check if we're inside any children + for (var i = 0; i < root.children.length; i++){ + var child = root.children[i]; + if (regionInsideRegion(region, child.region)){ + // we are, so insert inside them instead + addChild(child, region); + return; + } + } + + // not inside any children, so check to see if any children are inside us + var node = newNode(region); + for (var i = 0; i < root.children.length; i++){ + var child = root.children[i]; + if (regionInsideRegion(child.region, region)){ + // oops... move the child beneath us, and remove them from root + node.children.push(child); + root.children.splice(i, 1); + i--; + } + } + + // now we can add ourselves + root.children.push(node); + } + + // add all regions to the root + for (var i = 0; i < poly.regions.length; i++){ + var region = poly.regions[i]; + if (region.length < 3) // regions must have at least 3 points (sanity check) + continue; + addChild(roots, region); + } + + // with our heirarchy, we can distinguish between exterior borders, and interior holes + // the root nodes are exterior, children are interior, children's children are exterior, + // children's children's children are interior, etc + + // while we're at it, exteriors are counter-clockwise, and interiors are clockwise + + function forceWinding(region, clockwise){ + // first, see if we're clockwise or counter-clockwise + // https://en.wikipedia.org/wiki/Shoelace_formula + var winding = 0; + var last_x = region[region.length - 1][0]; + var last_y = region[region.length - 1][1]; + var copy = []; + for (var i = 0; i < region.length; i++){ + var curr_x = region[i][0]; + var curr_y = region[i][1]; + copy.push([curr_x, curr_y]); // create a copy while we're at it + winding += curr_y * last_x - curr_x * last_y; + last_x = curr_x; + last_y = curr_y; + } + // this assumes Cartesian coordinates (Y is positive going up) + var isclockwise = winding < 0; + if (isclockwise !== clockwise) + copy.reverse(); + // while we're here, the last point must be the first point... + copy.push([copy[0][0], copy[0][1]]); + return copy; + } + + var geopolys = []; + + function addExterior(node){ + var poly = [forceWinding(node.region, false)]; + geopolys.push(poly); + // children of exteriors are interior + for (var i = 0; i < node.children.length; i++) + poly.push(getInterior(node.children[i])); + } + + function getInterior(node){ + // children of interiors are exterior + for (var i = 0; i < node.children.length; i++) + addExterior(node.children[i]); + // return the clockwise interior + return forceWinding(node.region, true); + } + + // root nodes are exterior + for (var i = 0; i < roots.children.length; i++) + addExterior(roots.children[i]); + + // lastly, construct the approrpriate GeoJSON object + + if (geopolys.length <= 0) // empty GeoJSON Polygon + return { type: 'Polygon', coordinates: [] }; + if (geopolys.length == 1) // use a GeoJSON Polygon + return { type: 'Polygon', coordinates: geopolys[0] }; + return { // otherwise, use a GeoJSON MultiPolygon + type: 'MultiPolygon', + coordinates: geopolys + }; + } +}; + +module.exports = GeoJSON; + +},{}],29:[function(_dereq_,module,exports){ +// (c) Copyright 2016, Sean Connelly (@voidqk), http://syntheti.cc +// MIT License +// Project Home: https://github.com/voidqk/polybooljs + +// +// this is the core work-horse +// + +var LinkedList = _dereq_('./linked-list'); + +function Intersecter(selfIntersection, eps, buildLog){ + // selfIntersection is true/false depending on the phase of the overall algorithm + + // + // segment creation + // + + function segmentNew(start, end){ + return { + id: buildLog ? buildLog.segmentId() : -1, + start: start, + end: end, + myFill: { + above: null, // is there fill above us? + below: null // is there fill below us? + }, + otherFill: null + }; + } + + function segmentCopy(start, end, seg){ + return { + id: buildLog ? buildLog.segmentId() : -1, + start: start, + end: end, + myFill: { + above: seg.myFill.above, + below: seg.myFill.below + }, + otherFill: null + }; + } + + // + // event logic + // + + var event_root = LinkedList.create(); + + function eventCompare(p1_isStart, p1_1, p1_2, p2_isStart, p2_1, p2_2){ + // compare the selected points first + var comp = eps.pointsCompare(p1_1, p2_1); + if (comp !== 0) + return comp; + // the selected points are the same + + if (eps.pointsSame(p1_2, p2_2)) // if the non-selected points are the same too... + return 0; // then the segments are equal + + if (p1_isStart !== p2_isStart) // if one is a start and the other isn't... + return p1_isStart ? 1 : -1; // favor the one that isn't the start + + // otherwise, we'll have to calculate which one is below the other manually + return eps.pointAboveOrOnLine(p1_2, + p2_isStart ? p2_1 : p2_2, // order matters + p2_isStart ? p2_2 : p2_1 + ) ? 1 : -1; + } + + function eventAdd(ev, other_pt){ + event_root.insertBefore(ev, function(here){ + // should ev be inserted before here? + var comp = eventCompare( + ev .isStart, ev .pt, other_pt, + here.isStart, here.pt, here.other.pt + ); + return comp < 0; + }); + } + + function eventAddSegmentStart(seg, primary){ + var ev_start = LinkedList.node({ + isStart: true, + pt: seg.start, + seg: seg, + primary: primary, + other: null, + status: null + }); + eventAdd(ev_start, seg.end); + return ev_start; + } + + function eventAddSegmentEnd(ev_start, seg, primary){ + var ev_end = LinkedList.node({ + isStart: false, + pt: seg.end, + seg: seg, + primary: primary, + other: ev_start, + status: null + }); + ev_start.other = ev_end; + eventAdd(ev_end, ev_start.pt); + } + + function eventAddSegment(seg, primary){ + var ev_start = eventAddSegmentStart(seg, primary); + eventAddSegmentEnd(ev_start, seg, primary); + return ev_start; + } + + function eventUpdateEnd(ev, end){ + // slides an end backwards + // (start)------------(end) to: + // (start)---(end) + + if (buildLog) + buildLog.segmentChop(ev.seg, end); + + ev.other.remove(); + ev.seg.end = end; + ev.other.pt = end; + eventAdd(ev.other, ev.pt); + } + + function eventDivide(ev, pt){ + var ns = segmentCopy(pt, ev.seg.end, ev.seg); + eventUpdateEnd(ev, pt); + return eventAddSegment(ns, ev.primary); + } + + function calculate(primaryPolyInverted, secondaryPolyInverted){ + // if selfIntersection is true then there is no secondary polygon, so that isn't used + + // + // status logic + // + + var status_root = LinkedList.create(); + + function statusCompare(ev1, ev2){ + var a1 = ev1.seg.start; + var a2 = ev1.seg.end; + var b1 = ev2.seg.start; + var b2 = ev2.seg.end; + + if (eps.pointsCollinear(a1, b1, b2)){ + if (eps.pointsCollinear(a2, b1, b2)) + return 1;//eventCompare(true, a1, a2, true, b1, b2); + return eps.pointAboveOrOnLine(a2, b1, b2) ? 1 : -1; + } + return eps.pointAboveOrOnLine(a1, b1, b2) ? 1 : -1; + } + + function statusFindSurrounding(ev){ + return status_root.findTransition(function(here){ + var comp = statusCompare(ev, here.ev); + return comp > 0; + }); + } + + function checkIntersection(ev1, ev2){ + // returns the segment equal to ev1, or false if nothing equal + + var seg1 = ev1.seg; + var seg2 = ev2.seg; + var a1 = seg1.start; + var a2 = seg1.end; + var b1 = seg2.start; + var b2 = seg2.end; + + if (buildLog) + buildLog.checkIntersection(seg1, seg2); + + var i = eps.linesIntersect(a1, a2, b1, b2); + + if (i === false){ + // segments are parallel or coincident + + // if points aren't collinear, then the segments are parallel, so no intersections + if (!eps.pointsCollinear(a1, a2, b1)) + return false; + // otherwise, segments are on top of each other somehow (aka coincident) + + if (eps.pointsSame(a1, b2) || eps.pointsSame(a2, b1)) + return false; // segments touch at endpoints... no intersection + + var a1_equ_b1 = eps.pointsSame(a1, b1); + var a2_equ_b2 = eps.pointsSame(a2, b2); + + if (a1_equ_b1 && a2_equ_b2) + return ev2; // segments are exactly equal + + var a1_between = !a1_equ_b1 && eps.pointBetween(a1, b1, b2); + var a2_between = !a2_equ_b2 && eps.pointBetween(a2, b1, b2); + + // handy for debugging: + // buildLog.log({ + // a1_equ_b1: a1_equ_b1, + // a2_equ_b2: a2_equ_b2, + // a1_between: a1_between, + // a2_between: a2_between + // }); + + if (a1_equ_b1){ + if (a2_between){ + // (a1)---(a2) + // (b1)----------(b2) + eventDivide(ev2, a2); + } + else{ + // (a1)----------(a2) + // (b1)---(b2) + eventDivide(ev1, b2); + } + return ev2; + } + else if (a1_between){ + if (!a2_equ_b2){ + // make a2 equal to b2 + if (a2_between){ + // (a1)---(a2) + // (b1)-----------------(b2) + eventDivide(ev2, a2); + } + else{ + // (a1)----------(a2) + // (b1)----------(b2) + eventDivide(ev1, b2); + } + } + + // (a1)---(a2) + // (b1)----------(b2) + eventDivide(ev2, a1); + } + } + else{ + // otherwise, lines intersect at i.pt, which may or may not be between the endpoints + + // is A divided between its endpoints? (exclusive) + if (i.alongA === 0){ + if (i.alongB === -1) // yes, at exactly b1 + eventDivide(ev1, b1); + else if (i.alongB === 0) // yes, somewhere between B's endpoints + eventDivide(ev1, i.pt); + else if (i.alongB === 1) // yes, at exactly b2 + eventDivide(ev1, b2); + } + + // is B divided between its endpoints? (exclusive) + if (i.alongB === 0){ + if (i.alongA === -1) // yes, at exactly a1 + eventDivide(ev2, a1); + else if (i.alongA === 0) // yes, somewhere between A's endpoints (exclusive) + eventDivide(ev2, i.pt); + else if (i.alongA === 1) // yes, at exactly a2 + eventDivide(ev2, a2); + } + } + return false; + } + + // + // main event loop + // + var segments = []; + while (!event_root.isEmpty()){ + var ev = event_root.getHead(); + + if (buildLog) + buildLog.vert(ev.pt[0]); + + if (ev.isStart){ + + if (buildLog) + buildLog.segmentNew(ev.seg, ev.primary); + + var surrounding = statusFindSurrounding(ev); + var above = surrounding.before ? surrounding.before.ev : null; + var below = surrounding.after ? surrounding.after.ev : null; + + if (buildLog){ + buildLog.tempStatus( + ev.seg, + above ? above.seg : false, + below ? below.seg : false + ); + } + + function checkBothIntersections(){ + if (above){ + var eve = checkIntersection(ev, above); + if (eve) + return eve; + } + if (below) + return checkIntersection(ev, below); + return false; + } + + var eve = checkBothIntersections(); + if (eve){ + // ev and eve are equal + // we'll keep eve and throw away ev + + // merge ev.seg's fill information into eve.seg + + if (selfIntersection){ + var toggle; // are we a toggling edge? + if (ev.seg.myFill.below === null) + toggle = true; + else + toggle = ev.seg.myFill.above !== ev.seg.myFill.below; + + // merge two segments that belong to the same polygon + // think of this as sandwiching two segments together, where `eve.seg` is + // the bottom -- this will cause the above fill flag to toggle + if (toggle) + eve.seg.myFill.above = !eve.seg.myFill.above; + } + else{ + // merge two segments that belong to different polygons + // each segment has distinct knowledge, so no special logic is needed + // note that this can only happen once per segment in this phase, because we + // are guaranteed that all self-intersections are gone + eve.seg.otherFill = ev.seg.myFill; + } + + if (buildLog) + buildLog.segmentUpdate(eve.seg); + + ev.other.remove(); + ev.remove(); + } + + if (event_root.getHead() !== ev){ + // something was inserted before us in the event queue, so loop back around and + // process it before continuing + if (buildLog) + buildLog.rewind(ev.seg); + continue; + } + + // + // calculate fill flags + // + if (selfIntersection){ + var toggle; // are we a toggling edge? + if (ev.seg.myFill.below === null) // if we are a new segment... + toggle = true; // then we toggle + else // we are a segment that has previous knowledge from a division + toggle = ev.seg.myFill.above !== ev.seg.myFill.below; // calculate toggle + + // next, calculate whether we are filled below us + if (!below){ // if nothing is below us... + // we are filled below us if the polygon is inverted + ev.seg.myFill.below = primaryPolyInverted; + } + else{ + // otherwise, we know the answer -- it's the same if whatever is below + // us is filled above it + ev.seg.myFill.below = below.seg.myFill.above; + } + + // since now we know if we're filled below us, we can calculate whether + // we're filled above us by applying toggle to whatever is below us + if (toggle) + ev.seg.myFill.above = !ev.seg.myFill.below; + else + ev.seg.myFill.above = ev.seg.myFill.below; + } + else{ + // now we fill in any missing transition information, since we are all-knowing + // at this point + + if (ev.seg.otherFill === null){ + // if we don't have other information, then we need to figure out if we're + // inside the other polygon + var inside; + if (!below){ + // if nothing is below us, then we're inside if the other polygon is + // inverted + inside = + ev.primary ? secondaryPolyInverted : primaryPolyInverted; + } + else{ // otherwise, something is below us + // so copy the below segment's other polygon's above + if (ev.primary === below.primary) + inside = below.seg.otherFill.above; + else + inside = below.seg.myFill.above; + } + ev.seg.otherFill = { + above: inside, + below: inside + }; + } + } + + if (buildLog){ + buildLog.status( + ev.seg, + above ? above.seg : false, + below ? below.seg : false + ); + } + + // insert the status and remember it for later removal + ev.other.status = surrounding.insert(LinkedList.node({ ev: ev })); + } + else{ + var st = ev.status; + + if (st === null){ + throw new Error('PolyBool: Zero-length segment detected; your epsilon is ' + + 'probably too small or too large'); + } + + // removing the status will create two new adjacent edges, so we'll need to check + // for those + if (status_root.exists(st.prev) && status_root.exists(st.next)) + checkIntersection(st.prev.ev, st.next.ev); + + if (buildLog) + buildLog.statusRemove(st.ev.seg); + + // remove the status + st.remove(); + + // if we've reached this point, we've calculated everything there is to know, so + // save the segment for reporting + if (!ev.primary){ + // make sure `seg.myFill` actually points to the primary polygon though + var s = ev.seg.myFill; + ev.seg.myFill = ev.seg.otherFill; + ev.seg.otherFill = s; + } + segments.push(ev.seg); + } + + // remove the event and continue + event_root.getHead().remove(); + } + + if (buildLog) + buildLog.done(); + + return segments; + } + + // return the appropriate API depending on what we're doing + if (!selfIntersection){ + // performing combination of polygons, so only deal with already-processed segments + return { + calculate: function(segments1, inverted1, segments2, inverted2){ + // segmentsX come from the self-intersection API, or this API + // invertedX is whether we treat that list of segments as an inverted polygon or not + // returns segments that can be used for further operations + segments1.forEach(function(seg){ + eventAddSegment(segmentCopy(seg.start, seg.end, seg), true); + }); + segments2.forEach(function(seg){ + eventAddSegment(segmentCopy(seg.start, seg.end, seg), false); + }); + return calculate(inverted1, inverted2); + } + }; + } + + // otherwise, performing self-intersection, so deal with regions + return { + addRegion: function(region){ + // regions are a list of points: + // [ [0, 0], [100, 0], [50, 100] ] + // you can add multiple regions before running calculate + var pt1; + var pt2 = region[region.length - 1]; + for (var i = 0; i < region.length; i++){ + pt1 = pt2; + pt2 = region[i]; + + var forward = eps.pointsCompare(pt1, pt2); + if (forward === 0) // points are equal, so we have a zero-length segment + continue; // just skip it + + eventAddSegment( + segmentNew( + forward < 0 ? pt1 : pt2, + forward < 0 ? pt2 : pt1 + ), + true + ); + } + }, + calculate: function(inverted){ + // is the polygon inverted? + // returns segments + return calculate(inverted, false); + } + }; +} + +module.exports = Intersecter; + +},{"./linked-list":30}],30:[function(_dereq_,module,exports){ +// (c) Copyright 2016, Sean Connelly (@voidqk), http://syntheti.cc +// MIT License +// Project Home: https://github.com/voidqk/polybooljs + +// +// simple linked list implementation that allows you to traverse down nodes and save positions +// + +var LinkedList = { + create: function(){ + var my = { + root: { root: true, next: null }, + exists: function(node){ + if (node === null || node === my.root) + return false; + return true; + }, + isEmpty: function(){ + return my.root.next === null; + }, + getHead: function(){ + return my.root.next; + }, + insertBefore: function(node, check){ + var last = my.root; + var here = my.root.next; + while (here !== null){ + if (check(here)){ + node.prev = here.prev; + node.next = here; + here.prev.next = node; + here.prev = node; + return; + } + last = here; + here = here.next; + } + last.next = node; + node.prev = last; + node.next = null; + }, + findTransition: function(check){ + var prev = my.root; + var here = my.root.next; + while (here !== null){ + if (check(here)) + break; + prev = here; + here = here.next; + } + return { + before: prev === my.root ? null : prev, + after: here, + insert: function(node){ + node.prev = prev; + node.next = here; + prev.next = node; + if (here !== null) + here.prev = node; + return node; + } + }; + } + }; + return my; + }, + node: function(data){ + data.prev = null; + data.next = null; + data.remove = function(){ + data.prev.next = data.next; + if (data.next) + data.next.prev = data.prev; + data.prev = null; + data.next = null; + }; + return data; + } +}; + +module.exports = LinkedList; + +},{}],31:[function(_dereq_,module,exports){ +// (c) Copyright 2016, Sean Connelly (@voidqk), http://syntheti.cc +// MIT License +// Project Home: https://github.com/voidqk/polybooljs + +// +// converts a list of segments into a list of regions, while also removing unnecessary verticies +// + +function SegmentChainer(segments, eps, buildLog){ + var chains = []; + var regions = []; + + segments.forEach(function(seg){ + var pt1 = seg.start; + var pt2 = seg.end; + if (eps.pointsSame(pt1, pt2)){ + console.warn('PolyBool: Warning: Zero-length segment detected; your epsilon is ' + + 'probably too small or too large'); + return; + } + + if (buildLog) + buildLog.chainStart(seg); + + // search for two chains that this segment matches + var first_match = { + index: 0, + matches_head: false, + matches_pt1: false + }; + var second_match = { + index: 0, + matches_head: false, + matches_pt1: false + }; + var next_match = first_match; + function setMatch(index, matches_head, matches_pt1){ + // return true if we've matched twice + next_match.index = index; + next_match.matches_head = matches_head; + next_match.matches_pt1 = matches_pt1; + if (next_match === first_match){ + next_match = second_match; + return false; + } + next_match = null; + return true; // we've matched twice, we're done here + } + for (var i = 0; i < chains.length; i++){ + var chain = chains[i]; + var head = chain[0]; + var head2 = chain[1]; + var tail = chain[chain.length - 1]; + var tail2 = chain[chain.length - 2]; + if (eps.pointsSame(head, pt1)){ + if (setMatch(i, true, true)) + break; + } + else if (eps.pointsSame(head, pt2)){ + if (setMatch(i, true, false)) + break; + } + else if (eps.pointsSame(tail, pt1)){ + if (setMatch(i, false, true)) + break; + } + else if (eps.pointsSame(tail, pt2)){ + if (setMatch(i, false, false)) + break; + } + } + + if (next_match === first_match){ + // we didn't match anything, so create a new chain + chains.push([ pt1, pt2 ]); + if (buildLog) + buildLog.chainNew(pt1, pt2); + return; + } + + if (next_match === second_match){ + // we matched a single chain + + if (buildLog) + buildLog.chainMatch(first_match.index); + + // add the other point to the apporpriate end, and check to see if we've closed the + // chain into a loop + + var index = first_match.index; + var pt = first_match.matches_pt1 ? pt2 : pt1; // if we matched pt1, then we add pt2, etc + var addToHead = first_match.matches_head; // if we matched at head, then add to the head + + var chain = chains[index]; + var grow = addToHead ? chain[0] : chain[chain.length - 1]; + var grow2 = addToHead ? chain[1] : chain[chain.length - 2]; + var oppo = addToHead ? chain[chain.length - 1] : chain[0]; + var oppo2 = addToHead ? chain[chain.length - 2] : chain[1]; + + if (eps.pointsCollinear(grow2, grow, pt)){ + // grow isn't needed because it's directly between grow2 and pt: + // grow2 ---grow---> pt + if (addToHead){ + if (buildLog) + buildLog.chainRemoveHead(first_match.index, pt); + chain.shift(); + } + else{ + if (buildLog) + buildLog.chainRemoveTail(first_match.index, pt); + chain.pop(); + } + grow = grow2; // old grow is gone... new grow is what grow2 was + } + + if (eps.pointsSame(oppo, pt)){ + // we're closing the loop, so remove chain from chains + chains.splice(index, 1); + + if (eps.pointsCollinear(oppo2, oppo, grow)){ + // oppo isn't needed because it's directly between oppo2 and grow: + // oppo2 ---oppo--->grow + if (addToHead){ + if (buildLog) + buildLog.chainRemoveTail(first_match.index, grow); + chain.pop(); + } + else{ + if (buildLog) + buildLog.chainRemoveHead(first_match.index, grow); + chain.shift(); + } + } + + if (buildLog) + buildLog.chainClose(first_match.index); + + // we have a closed chain! + regions.push(chain); + return; + } + + // not closing a loop, so just add it to the apporpriate side + if (addToHead){ + if (buildLog) + buildLog.chainAddHead(first_match.index, pt); + chain.unshift(pt); + } + else{ + if (buildLog) + buildLog.chainAddTail(first_match.index, pt); + chain.push(pt); + } + return; + } + + // otherwise, we matched two chains, so we need to combine those chains together + + function reverseChain(index){ + if (buildLog) + buildLog.chainReverse(index); + chains[index].reverse(); // gee, that's easy + } + + function appendChain(index1, index2){ + // index1 gets index2 appended to it, and index2 is removed + var chain1 = chains[index1]; + var chain2 = chains[index2]; + var tail = chain1[chain1.length - 1]; + var tail2 = chain1[chain1.length - 2]; + var head = chain2[0]; + var head2 = chain2[1]; + + if (eps.pointsCollinear(tail2, tail, head)){ + // tail isn't needed because it's directly between tail2 and head + // tail2 ---tail---> head + if (buildLog) + buildLog.chainRemoveTail(index1, tail); + chain1.pop(); + tail = tail2; // old tail is gone... new tail is what tail2 was + } + + if (eps.pointsCollinear(tail, head, head2)){ + // head isn't needed because it's directly between tail and head2 + // tail ---head---> head2 + if (buildLog) + buildLog.chainRemoveHead(index2, head); + chain2.shift(); + } + + if (buildLog) + buildLog.chainJoin(index1, index2); + chains[index1] = chain1.concat(chain2); + chains.splice(index2, 1); + } + + var F = first_match.index; + var S = second_match.index; + + if (buildLog) + buildLog.chainConnect(F, S); + + var reverseF = chains[F].length < chains[S].length; // reverse the shorter chain, if needed + if (first_match.matches_head){ + if (second_match.matches_head){ + if (reverseF){ + // <<<< F <<<< --- >>>> S >>>> + reverseChain(F); + // >>>> F >>>> --- >>>> S >>>> + appendChain(F, S); + } + else{ + // <<<< F <<<< --- >>>> S >>>> + reverseChain(S); + // <<<< F <<<< --- <<<< S <<<< logically same as: + // >>>> S >>>> --- >>>> F >>>> + appendChain(S, F); + } + } + else{ + // <<<< F <<<< --- <<<< S <<<< logically same as: + // >>>> S >>>> --- >>>> F >>>> + appendChain(S, F); + } + } + else{ + if (second_match.matches_head){ + // >>>> F >>>> --- >>>> S >>>> + appendChain(F, S); + } + else{ + if (reverseF){ + // >>>> F >>>> --- <<<< S <<<< + reverseChain(F); + // <<<< F <<<< --- <<<< S <<<< logically same as: + // >>>> S >>>> --- >>>> F >>>> + appendChain(S, F); + } + else{ + // >>>> F >>>> --- <<<< S <<<< + reverseChain(S); + // >>>> F >>>> --- >>>> S >>>> + appendChain(F, S); + } + } + } + }); + + return regions; +} + +module.exports = SegmentChainer; + +},{}],32:[function(_dereq_,module,exports){ +// (c) Copyright 2016, Sean Connelly (@voidqk), http://syntheti.cc +// MIT License +// Project Home: https://github.com/voidqk/polybooljs + +// +// filter a list of segments based on boolean operations +// + +function select(segments, selection, buildLog){ + var result = []; + segments.forEach(function(seg){ + var index = + (seg.myFill.above ? 8 : 0) + + (seg.myFill.below ? 4 : 0) + + ((seg.otherFill && seg.otherFill.above) ? 2 : 0) + + ((seg.otherFill && seg.otherFill.below) ? 1 : 0); + if (selection[index] !== 0){ + // copy the segment to the results, while also calculating the fill status + result.push({ + id: buildLog ? buildLog.segmentId() : -1, + start: seg.start, + end: seg.end, + myFill: { + above: selection[index] === 1, // 1 if filled above + below: selection[index] === 2 // 2 if filled below + }, + otherFill: null + }); + } + }); + + if (buildLog) + buildLog.selected(result); + + return result; +} + +var SegmentSelector = { + union: function(segments, buildLog){ // primary | secondary + // above1 below1 above2 below2 Keep? Value + // 0 0 0 0 => no 0 + // 0 0 0 1 => yes filled below 2 + // 0 0 1 0 => yes filled above 1 + // 0 0 1 1 => no 0 + // 0 1 0 0 => yes filled below 2 + // 0 1 0 1 => yes filled below 2 + // 0 1 1 0 => no 0 + // 0 1 1 1 => no 0 + // 1 0 0 0 => yes filled above 1 + // 1 0 0 1 => no 0 + // 1 0 1 0 => yes filled above 1 + // 1 0 1 1 => no 0 + // 1 1 0 0 => no 0 + // 1 1 0 1 => no 0 + // 1 1 1 0 => no 0 + // 1 1 1 1 => no 0 + return select(segments, [ + 0, 2, 1, 0, + 2, 2, 0, 0, + 1, 0, 1, 0, + 0, 0, 0, 0 + ], buildLog); + }, + intersect: function(segments, buildLog){ // primary & secondary + // above1 below1 above2 below2 Keep? Value + // 0 0 0 0 => no 0 + // 0 0 0 1 => no 0 + // 0 0 1 0 => no 0 + // 0 0 1 1 => no 0 + // 0 1 0 0 => no 0 + // 0 1 0 1 => yes filled below 2 + // 0 1 1 0 => no 0 + // 0 1 1 1 => yes filled below 2 + // 1 0 0 0 => no 0 + // 1 0 0 1 => no 0 + // 1 0 1 0 => yes filled above 1 + // 1 0 1 1 => yes filled above 1 + // 1 1 0 0 => no 0 + // 1 1 0 1 => yes filled below 2 + // 1 1 1 0 => yes filled above 1 + // 1 1 1 1 => no 0 + return select(segments, [ + 0, 0, 0, 0, + 0, 2, 0, 2, + 0, 0, 1, 1, + 0, 2, 1, 0 + ], buildLog); + }, + difference: function(segments, buildLog){ // primary - secondary + // above1 below1 above2 below2 Keep? Value + // 0 0 0 0 => no 0 + // 0 0 0 1 => no 0 + // 0 0 1 0 => no 0 + // 0 0 1 1 => no 0 + // 0 1 0 0 => yes filled below 2 + // 0 1 0 1 => no 0 + // 0 1 1 0 => yes filled below 2 + // 0 1 1 1 => no 0 + // 1 0 0 0 => yes filled above 1 + // 1 0 0 1 => yes filled above 1 + // 1 0 1 0 => no 0 + // 1 0 1 1 => no 0 + // 1 1 0 0 => no 0 + // 1 1 0 1 => yes filled above 1 + // 1 1 1 0 => yes filled below 2 + // 1 1 1 1 => no 0 + return select(segments, [ + 0, 0, 0, 0, + 2, 0, 2, 0, + 1, 1, 0, 0, + 0, 1, 2, 0 + ], buildLog); + }, + differenceRev: function(segments, buildLog){ // secondary - primary + // above1 below1 above2 below2 Keep? Value + // 0 0 0 0 => no 0 + // 0 0 0 1 => yes filled below 2 + // 0 0 1 0 => yes filled above 1 + // 0 0 1 1 => no 0 + // 0 1 0 0 => no 0 + // 0 1 0 1 => no 0 + // 0 1 1 0 => yes filled above 1 + // 0 1 1 1 => yes filled above 1 + // 1 0 0 0 => no 0 + // 1 0 0 1 => yes filled below 2 + // 1 0 1 0 => no 0 + // 1 0 1 1 => yes filled below 2 + // 1 1 0 0 => no 0 + // 1 1 0 1 => no 0 + // 1 1 1 0 => no 0 + // 1 1 1 1 => no 0 + return select(segments, [ + 0, 2, 1, 0, + 0, 0, 1, 1, + 0, 2, 0, 2, + 0, 0, 0, 0 + ], buildLog); + }, + xor: function(segments, buildLog){ // primary ^ secondary + // above1 below1 above2 below2 Keep? Value + // 0 0 0 0 => no 0 + // 0 0 0 1 => yes filled below 2 + // 0 0 1 0 => yes filled above 1 + // 0 0 1 1 => no 0 + // 0 1 0 0 => yes filled below 2 + // 0 1 0 1 => no 0 + // 0 1 1 0 => no 0 + // 0 1 1 1 => yes filled above 1 + // 1 0 0 0 => yes filled above 1 + // 1 0 0 1 => no 0 + // 1 0 1 0 => no 0 + // 1 0 1 1 => yes filled below 2 + // 1 1 0 0 => no 0 + // 1 1 0 1 => yes filled above 1 + // 1 1 1 0 => yes filled below 2 + // 1 1 1 1 => no 0 + return select(segments, [ + 0, 2, 1, 0, + 2, 0, 0, 1, + 1, 0, 0, 2, + 0, 1, 2, 0 + ], buildLog); + } +}; + +module.exports = SegmentSelector; + +},{}],33:[function(_dereq_,module,exports){ +// shim for using process in browser +var process = module.exports = {}; + +// cached from whatever global is present so that test runners that stub it +// don't break things. But we need to wrap it in a try catch in case it is +// wrapped in strict mode code which doesn't define any globals. It's inside a +// function because try/catches deoptimize in certain engines. + +var cachedSetTimeout; +var cachedClearTimeout; + +function defaultSetTimout() { + throw new Error('setTimeout has not been defined'); +} +function defaultClearTimeout () { + throw new Error('clearTimeout has not been defined'); +} +(function () { + try { + if (typeof setTimeout === 'function') { + cachedSetTimeout = setTimeout; + } else { + cachedSetTimeout = defaultSetTimout; + } + } catch (e) { + cachedSetTimeout = defaultSetTimout; + } + try { + if (typeof clearTimeout === 'function') { + cachedClearTimeout = clearTimeout; + } else { + cachedClearTimeout = defaultClearTimeout; + } + } catch (e) { + cachedClearTimeout = defaultClearTimeout; + } +} ()) +function runTimeout(fun) { + if (cachedSetTimeout === setTimeout) { + //normal enviroments in sane situations + return setTimeout(fun, 0); + } + // if setTimeout wasn't available but was latter defined + if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { + cachedSetTimeout = setTimeout; + return setTimeout(fun, 0); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedSetTimeout(fun, 0); + } catch(e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedSetTimeout.call(null, fun, 0); + } catch(e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error + return cachedSetTimeout.call(this, fun, 0); + } + } + + +} +function runClearTimeout(marker) { + if (cachedClearTimeout === clearTimeout) { + //normal enviroments in sane situations + return clearTimeout(marker); + } + // if clearTimeout wasn't available but was latter defined + if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { + cachedClearTimeout = clearTimeout; + return clearTimeout(marker); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedClearTimeout(marker); + } catch (e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedClearTimeout.call(null, marker); + } catch (e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. + // Some versions of I.E. have different rules for clearTimeout vs setTimeout + return cachedClearTimeout.call(this, marker); + } + } + + + +} +var queue = []; +var draining = false; +var currentQueue; +var queueIndex = -1; + +function cleanUpNextTick() { + if (!draining || !currentQueue) { + return; + } + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); + } +} + +function drainQueue() { + if (draining) { + return; + } + var timeout = runTimeout(cleanUpNextTick); + draining = true; + + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + if (currentQueue) { + currentQueue[queueIndex].run(); + } + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + runClearTimeout(timeout); +} + +process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + runTimeout(drainQueue); + } +}; + +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; + +function noop() {} + +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; +process.prependListener = noop; +process.prependOnceListener = noop; + +process.listeners = function (name) { return [] } + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; + +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function() { return 0; }; + +},{}],34:[function(_dereq_,module,exports){ +// TinyColor v1.4.1 +// https://github.com/bgrins/TinyColor +// Brian Grinstead, MIT License + +(function(Math) { + +var trimLeft = /^\s+/, + trimRight = /\s+$/, + tinyCounter = 0, + mathRound = Math.round, + mathMin = Math.min, + mathMax = Math.max, + mathRandom = Math.random; + +function tinycolor (color, opts) { + + color = (color) ? color : ''; + opts = opts || { }; + + // If input is already a tinycolor, return itself + if (color instanceof tinycolor) { + return color; + } + // If we are called as a function, call using new instead + if (!(this instanceof tinycolor)) { + return new tinycolor(color, opts); + } + + var rgb = inputToRGB(color); + this._originalInput = color, + this._r = rgb.r, + this._g = rgb.g, + this._b = rgb.b, + this._a = rgb.a, + this._roundA = mathRound(100*this._a) / 100, + this._format = opts.format || rgb.format; + this._gradientType = opts.gradientType; + + // Don't let the range of [0,255] come back in [0,1]. + // Potentially lose a little bit of precision here, but will fix issues where + // .5 gets interpreted as half of the total, instead of half of 1 + // If it was supposed to be 128, this was already taken care of by `inputToRgb` + if (this._r < 1) { this._r = mathRound(this._r); } + if (this._g < 1) { this._g = mathRound(this._g); } + if (this._b < 1) { this._b = mathRound(this._b); } + + this._ok = rgb.ok; + this._tc_id = tinyCounter++; +} + +tinycolor.prototype = { + isDark: function() { + return this.getBrightness() < 128; + }, + isLight: function() { + return !this.isDark(); + }, + isValid: function() { + return this._ok; + }, + getOriginalInput: function() { + return this._originalInput; + }, + getFormat: function() { + return this._format; + }, + getAlpha: function() { + return this._a; + }, + getBrightness: function() { + //http://www.w3.org/TR/AERT#color-contrast + var rgb = this.toRgb(); + return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000; + }, + getLuminance: function() { + //http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef + var rgb = this.toRgb(); + var RsRGB, GsRGB, BsRGB, R, G, B; + RsRGB = rgb.r/255; + GsRGB = rgb.g/255; + BsRGB = rgb.b/255; + + if (RsRGB <= 0.03928) {R = RsRGB / 12.92;} else {R = Math.pow(((RsRGB + 0.055) / 1.055), 2.4);} + if (GsRGB <= 0.03928) {G = GsRGB / 12.92;} else {G = Math.pow(((GsRGB + 0.055) / 1.055), 2.4);} + if (BsRGB <= 0.03928) {B = BsRGB / 12.92;} else {B = Math.pow(((BsRGB + 0.055) / 1.055), 2.4);} + return (0.2126 * R) + (0.7152 * G) + (0.0722 * B); + }, + setAlpha: function(value) { + this._a = boundAlpha(value); + this._roundA = mathRound(100*this._a) / 100; + return this; + }, + toHsv: function() { + var hsv = rgbToHsv(this._r, this._g, this._b); + return { h: hsv.h * 360, s: hsv.s, v: hsv.v, a: this._a }; + }, + toHsvString: function() { + var hsv = rgbToHsv(this._r, this._g, this._b); + var h = mathRound(hsv.h * 360), s = mathRound(hsv.s * 100), v = mathRound(hsv.v * 100); + return (this._a == 1) ? + "hsv(" + h + ", " + s + "%, " + v + "%)" : + "hsva(" + h + ", " + s + "%, " + v + "%, "+ this._roundA + ")"; + }, + toHsl: function() { + var hsl = rgbToHsl(this._r, this._g, this._b); + return { h: hsl.h * 360, s: hsl.s, l: hsl.l, a: this._a }; + }, + toHslString: function() { + var hsl = rgbToHsl(this._r, this._g, this._b); + var h = mathRound(hsl.h * 360), s = mathRound(hsl.s * 100), l = mathRound(hsl.l * 100); + return (this._a == 1) ? + "hsl(" + h + ", " + s + "%, " + l + "%)" : + "hsla(" + h + ", " + s + "%, " + l + "%, "+ this._roundA + ")"; + }, + toHex: function(allow3Char) { + return rgbToHex(this._r, this._g, this._b, allow3Char); + }, + toHexString: function(allow3Char) { + return '#' + this.toHex(allow3Char); + }, + toHex8: function(allow4Char) { + return rgbaToHex(this._r, this._g, this._b, this._a, allow4Char); + }, + toHex8String: function(allow4Char) { + return '#' + this.toHex8(allow4Char); + }, + toRgb: function() { + return { r: mathRound(this._r), g: mathRound(this._g), b: mathRound(this._b), a: this._a }; + }, + toRgbString: function() { + return (this._a == 1) ? + "rgb(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ")" : + "rgba(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ", " + this._roundA + ")"; + }, + toPercentageRgb: function() { + return { r: mathRound(bound01(this._r, 255) * 100) + "%", g: mathRound(bound01(this._g, 255) * 100) + "%", b: mathRound(bound01(this._b, 255) * 100) + "%", a: this._a }; + }, + toPercentageRgbString: function() { + return (this._a == 1) ? + "rgb(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%)" : + "rgba(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%, " + this._roundA + ")"; + }, + toName: function() { + if (this._a === 0) { + return "transparent"; + } + + if (this._a < 1) { + return false; + } + + return hexNames[rgbToHex(this._r, this._g, this._b, true)] || false; + }, + toFilter: function(secondColor) { + var hex8String = '#' + rgbaToArgbHex(this._r, this._g, this._b, this._a); + var secondHex8String = hex8String; + var gradientType = this._gradientType ? "GradientType = 1, " : ""; + + if (secondColor) { + var s = tinycolor(secondColor); + secondHex8String = '#' + rgbaToArgbHex(s._r, s._g, s._b, s._a); + } + + return "progid:DXImageTransform.Microsoft.gradient("+gradientType+"startColorstr="+hex8String+",endColorstr="+secondHex8String+")"; + }, + toString: function(format) { + var formatSet = !!format; + format = format || this._format; + + var formattedString = false; + var hasAlpha = this._a < 1 && this._a >= 0; + var needsAlphaFormat = !formatSet && hasAlpha && (format === "hex" || format === "hex6" || format === "hex3" || format === "hex4" || format === "hex8" || format === "name"); + + if (needsAlphaFormat) { + // Special case for "transparent", all other non-alpha formats + // will return rgba when there is transparency. + if (format === "name" && this._a === 0) { + return this.toName(); + } + return this.toRgbString(); + } + if (format === "rgb") { + formattedString = this.toRgbString(); + } + if (format === "prgb") { + formattedString = this.toPercentageRgbString(); + } + if (format === "hex" || format === "hex6") { + formattedString = this.toHexString(); + } + if (format === "hex3") { + formattedString = this.toHexString(true); + } + if (format === "hex4") { + formattedString = this.toHex8String(true); + } + if (format === "hex8") { + formattedString = this.toHex8String(); + } + if (format === "name") { + formattedString = this.toName(); + } + if (format === "hsl") { + formattedString = this.toHslString(); + } + if (format === "hsv") { + formattedString = this.toHsvString(); + } + + return formattedString || this.toHexString(); + }, + clone: function() { + return tinycolor(this.toString()); + }, + + _applyModification: function(fn, args) { + var color = fn.apply(null, [this].concat([].slice.call(args))); + this._r = color._r; + this._g = color._g; + this._b = color._b; + this.setAlpha(color._a); + return this; + }, + lighten: function() { + return this._applyModification(lighten, arguments); + }, + brighten: function() { + return this._applyModification(brighten, arguments); + }, + darken: function() { + return this._applyModification(darken, arguments); + }, + desaturate: function() { + return this._applyModification(desaturate, arguments); + }, + saturate: function() { + return this._applyModification(saturate, arguments); + }, + greyscale: function() { + return this._applyModification(greyscale, arguments); + }, + spin: function() { + return this._applyModification(spin, arguments); + }, + + _applyCombination: function(fn, args) { + return fn.apply(null, [this].concat([].slice.call(args))); + }, + analogous: function() { + return this._applyCombination(analogous, arguments); + }, + complement: function() { + return this._applyCombination(complement, arguments); + }, + monochromatic: function() { + return this._applyCombination(monochromatic, arguments); + }, + splitcomplement: function() { + return this._applyCombination(splitcomplement, arguments); + }, + triad: function() { + return this._applyCombination(triad, arguments); + }, + tetrad: function() { + return this._applyCombination(tetrad, arguments); + } +}; + +// If input is an object, force 1 into "1.0" to handle ratios properly +// String input requires "1.0" as input, so 1 will be treated as 1 +tinycolor.fromRatio = function(color, opts) { + if (typeof color == "object") { + var newColor = {}; + for (var i in color) { + if (color.hasOwnProperty(i)) { + if (i === "a") { + newColor[i] = color[i]; + } + else { + newColor[i] = convertToPercentage(color[i]); + } + } + } + color = newColor; + } + + return tinycolor(color, opts); +}; + +// Given a string or object, convert that input to RGB +// Possible string inputs: +// +// "red" +// "#f00" or "f00" +// "#ff0000" or "ff0000" +// "#ff000000" or "ff000000" +// "rgb 255 0 0" or "rgb (255, 0, 0)" +// "rgb 1.0 0 0" or "rgb (1, 0, 0)" +// "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1" +// "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1" +// "hsl(0, 100%, 50%)" or "hsl 0 100% 50%" +// "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1" +// "hsv(0, 100%, 100%)" or "hsv 0 100% 100%" +// +function inputToRGB(color) { + + var rgb = { r: 0, g: 0, b: 0 }; + var a = 1; + var s = null; + var v = null; + var l = null; + var ok = false; + var format = false; + + if (typeof color == "string") { + color = stringInputToObject(color); + } + + if (typeof color == "object") { + if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) { + rgb = rgbToRgb(color.r, color.g, color.b); + ok = true; + format = String(color.r).substr(-1) === "%" ? "prgb" : "rgb"; + } + else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) { + s = convertToPercentage(color.s); + v = convertToPercentage(color.v); + rgb = hsvToRgb(color.h, s, v); + ok = true; + format = "hsv"; + } + else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) { + s = convertToPercentage(color.s); + l = convertToPercentage(color.l); + rgb = hslToRgb(color.h, s, l); + ok = true; + format = "hsl"; + } + + if (color.hasOwnProperty("a")) { + a = color.a; + } + } + + a = boundAlpha(a); + + return { + ok: ok, + format: color.format || format, + r: mathMin(255, mathMax(rgb.r, 0)), + g: mathMin(255, mathMax(rgb.g, 0)), + b: mathMin(255, mathMax(rgb.b, 0)), + a: a + }; +} + + +// Conversion Functions +// -------------------- + +// `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from: +// + +// `rgbToRgb` +// Handle bounds / percentage checking to conform to CSS color spec +// +// *Assumes:* r, g, b in [0, 255] or [0, 1] +// *Returns:* { r, g, b } in [0, 255] +function rgbToRgb(r, g, b){ + return { + r: bound01(r, 255) * 255, + g: bound01(g, 255) * 255, + b: bound01(b, 255) * 255 + }; +} + +// `rgbToHsl` +// Converts an RGB color value to HSL. +// *Assumes:* r, g, and b are contained in [0, 255] or [0, 1] +// *Returns:* { h, s, l } in [0,1] +function rgbToHsl(r, g, b) { + + r = bound01(r, 255); + g = bound01(g, 255); + b = bound01(b, 255); + + var max = mathMax(r, g, b), min = mathMin(r, g, b); + var h, s, l = (max + min) / 2; + + if(max == min) { + h = s = 0; // achromatic + } + else { + var d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch(max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + + h /= 6; + } + + return { h: h, s: s, l: l }; +} + +// `hslToRgb` +// Converts an HSL color value to RGB. +// *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100] +// *Returns:* { r, g, b } in the set [0, 255] +function hslToRgb(h, s, l) { + var r, g, b; + + h = bound01(h, 360); + s = bound01(s, 100); + l = bound01(l, 100); + + function hue2rgb(p, q, t) { + if(t < 0) t += 1; + if(t > 1) t -= 1; + if(t < 1/6) return p + (q - p) * 6 * t; + if(t < 1/2) return q; + if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; + return p; + } + + if(s === 0) { + r = g = b = l; // achromatic + } + else { + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hue2rgb(p, q, h + 1/3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1/3); + } + + return { r: r * 255, g: g * 255, b: b * 255 }; +} + +// `rgbToHsv` +// Converts an RGB color value to HSV +// *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1] +// *Returns:* { h, s, v } in [0,1] +function rgbToHsv(r, g, b) { + + r = bound01(r, 255); + g = bound01(g, 255); + b = bound01(b, 255); + + var max = mathMax(r, g, b), min = mathMin(r, g, b); + var h, s, v = max; + + var d = max - min; + s = max === 0 ? 0 : d / max; + + if(max == min) { + h = 0; // achromatic + } + else { + switch(max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + h /= 6; + } + return { h: h, s: s, v: v }; +} + +// `hsvToRgb` +// Converts an HSV color value to RGB. +// *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100] +// *Returns:* { r, g, b } in the set [0, 255] + function hsvToRgb(h, s, v) { + + h = bound01(h, 360) * 6; + s = bound01(s, 100); + v = bound01(v, 100); + + var i = Math.floor(h), + f = h - i, + p = v * (1 - s), + q = v * (1 - f * s), + t = v * (1 - (1 - f) * s), + mod = i % 6, + r = [v, q, p, p, t, v][mod], + g = [t, v, v, q, p, p][mod], + b = [p, p, t, v, v, q][mod]; + + return { r: r * 255, g: g * 255, b: b * 255 }; +} + +// `rgbToHex` +// Converts an RGB color to hex +// Assumes r, g, and b are contained in the set [0, 255] +// Returns a 3 or 6 character hex +function rgbToHex(r, g, b, allow3Char) { + + var hex = [ + pad2(mathRound(r).toString(16)), + pad2(mathRound(g).toString(16)), + pad2(mathRound(b).toString(16)) + ]; + + // Return a 3 character hex if possible + if (allow3Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) { + return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0); + } + + return hex.join(""); +} + +// `rgbaToHex` +// Converts an RGBA color plus alpha transparency to hex +// Assumes r, g, b are contained in the set [0, 255] and +// a in [0, 1]. Returns a 4 or 8 character rgba hex +function rgbaToHex(r, g, b, a, allow4Char) { + + var hex = [ + pad2(mathRound(r).toString(16)), + pad2(mathRound(g).toString(16)), + pad2(mathRound(b).toString(16)), + pad2(convertDecimalToHex(a)) + ]; + + // Return a 4 character hex if possible + if (allow4Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1) && hex[3].charAt(0) == hex[3].charAt(1)) { + return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0); + } + + return hex.join(""); +} + +// `rgbaToArgbHex` +// Converts an RGBA color to an ARGB Hex8 string +// Rarely used, but required for "toFilter()" +function rgbaToArgbHex(r, g, b, a) { + + var hex = [ + pad2(convertDecimalToHex(a)), + pad2(mathRound(r).toString(16)), + pad2(mathRound(g).toString(16)), + pad2(mathRound(b).toString(16)) + ]; + + return hex.join(""); +} + +// `equals` +// Can be called with any tinycolor input +tinycolor.equals = function (color1, color2) { + if (!color1 || !color2) { return false; } + return tinycolor(color1).toRgbString() == tinycolor(color2).toRgbString(); +}; + +tinycolor.random = function() { + return tinycolor.fromRatio({ + r: mathRandom(), + g: mathRandom(), + b: mathRandom() + }); +}; + + +// Modification Functions +// ---------------------- +// Thanks to less.js for some of the basics here +// + +function desaturate(color, amount) { + amount = (amount === 0) ? 0 : (amount || 10); + var hsl = tinycolor(color).toHsl(); + hsl.s -= amount / 100; + hsl.s = clamp01(hsl.s); + return tinycolor(hsl); +} + +function saturate(color, amount) { + amount = (amount === 0) ? 0 : (amount || 10); + var hsl = tinycolor(color).toHsl(); + hsl.s += amount / 100; + hsl.s = clamp01(hsl.s); + return tinycolor(hsl); +} + +function greyscale(color) { + return tinycolor(color).desaturate(100); +} + +function lighten (color, amount) { + amount = (amount === 0) ? 0 : (amount || 10); + var hsl = tinycolor(color).toHsl(); + hsl.l += amount / 100; + hsl.l = clamp01(hsl.l); + return tinycolor(hsl); +} + +function brighten(color, amount) { + amount = (amount === 0) ? 0 : (amount || 10); + var rgb = tinycolor(color).toRgb(); + rgb.r = mathMax(0, mathMin(255, rgb.r - mathRound(255 * - (amount / 100)))); + rgb.g = mathMax(0, mathMin(255, rgb.g - mathRound(255 * - (amount / 100)))); + rgb.b = mathMax(0, mathMin(255, rgb.b - mathRound(255 * - (amount / 100)))); + return tinycolor(rgb); +} + +function darken (color, amount) { + amount = (amount === 0) ? 0 : (amount || 10); + var hsl = tinycolor(color).toHsl(); + hsl.l -= amount / 100; + hsl.l = clamp01(hsl.l); + return tinycolor(hsl); +} + +// Spin takes a positive or negative amount within [-360, 360] indicating the change of hue. +// Values outside of this range will be wrapped into this range. +function spin(color, amount) { + var hsl = tinycolor(color).toHsl(); + var hue = (hsl.h + amount) % 360; + hsl.h = hue < 0 ? 360 + hue : hue; + return tinycolor(hsl); +} + +// Combination Functions +// --------------------- +// Thanks to jQuery xColor for some of the ideas behind these +// + +function complement(color) { + var hsl = tinycolor(color).toHsl(); + hsl.h = (hsl.h + 180) % 360; + return tinycolor(hsl); +} + +function triad(color) { + var hsl = tinycolor(color).toHsl(); + var h = hsl.h; + return [ + tinycolor(color), + tinycolor({ h: (h + 120) % 360, s: hsl.s, l: hsl.l }), + tinycolor({ h: (h + 240) % 360, s: hsl.s, l: hsl.l }) + ]; +} + +function tetrad(color) { + var hsl = tinycolor(color).toHsl(); + var h = hsl.h; + return [ + tinycolor(color), + tinycolor({ h: (h + 90) % 360, s: hsl.s, l: hsl.l }), + tinycolor({ h: (h + 180) % 360, s: hsl.s, l: hsl.l }), + tinycolor({ h: (h + 270) % 360, s: hsl.s, l: hsl.l }) + ]; +} + +function splitcomplement(color) { + var hsl = tinycolor(color).toHsl(); + var h = hsl.h; + return [ + tinycolor(color), + tinycolor({ h: (h + 72) % 360, s: hsl.s, l: hsl.l}), + tinycolor({ h: (h + 216) % 360, s: hsl.s, l: hsl.l}) + ]; +} + +function analogous(color, results, slices) { + results = results || 6; + slices = slices || 30; + + var hsl = tinycolor(color).toHsl(); + var part = 360 / slices; + var ret = [tinycolor(color)]; + + for (hsl.h = ((hsl.h - (part * results >> 1)) + 720) % 360; --results; ) { + hsl.h = (hsl.h + part) % 360; + ret.push(tinycolor(hsl)); + } + return ret; +} + +function monochromatic(color, results) { + results = results || 6; + var hsv = tinycolor(color).toHsv(); + var h = hsv.h, s = hsv.s, v = hsv.v; + var ret = []; + var modification = 1 / results; + + while (results--) { + ret.push(tinycolor({ h: h, s: s, v: v})); + v = (v + modification) % 1; + } + + return ret; +} + +// Utility Functions +// --------------------- + +tinycolor.mix = function(color1, color2, amount) { + amount = (amount === 0) ? 0 : (amount || 50); + + var rgb1 = tinycolor(color1).toRgb(); + var rgb2 = tinycolor(color2).toRgb(); + + var p = amount / 100; + + var rgba = { + r: ((rgb2.r - rgb1.r) * p) + rgb1.r, + g: ((rgb2.g - rgb1.g) * p) + rgb1.g, + b: ((rgb2.b - rgb1.b) * p) + rgb1.b, + a: ((rgb2.a - rgb1.a) * p) + rgb1.a + }; + + return tinycolor(rgba); +}; + + +// Readability Functions +// --------------------- +// false +// tinycolor.isReadable("#000", "#111",{level:"AA",size:"large"}) => false +tinycolor.isReadable = function(color1, color2, wcag2) { + var readability = tinycolor.readability(color1, color2); + var wcag2Parms, out; + + out = false; + + wcag2Parms = validateWCAG2Parms(wcag2); + switch (wcag2Parms.level + wcag2Parms.size) { + case "AAsmall": + case "AAAlarge": + out = readability >= 4.5; + break; + case "AAlarge": + out = readability >= 3; + break; + case "AAAsmall": + out = readability >= 7; + break; + } + return out; + +}; + +// `mostReadable` +// Given a base color and a list of possible foreground or background +// colors for that base, returns the most readable color. +// Optionally returns Black or White if the most readable color is unreadable. +// *Example* +// tinycolor.mostReadable(tinycolor.mostReadable("#123", ["#124", "#125"],{includeFallbackColors:false}).toHexString(); // "#112255" +// tinycolor.mostReadable(tinycolor.mostReadable("#123", ["#124", "#125"],{includeFallbackColors:true}).toHexString(); // "#ffffff" +// tinycolor.mostReadable("#a8015a", ["#faf3f3"],{includeFallbackColors:true,level:"AAA",size:"large"}).toHexString(); // "#faf3f3" +// tinycolor.mostReadable("#a8015a", ["#faf3f3"],{includeFallbackColors:true,level:"AAA",size:"small"}).toHexString(); // "#ffffff" +tinycolor.mostReadable = function(baseColor, colorList, args) { + var bestColor = null; + var bestScore = 0; + var readability; + var includeFallbackColors, level, size ; + args = args || {}; + includeFallbackColors = args.includeFallbackColors ; + level = args.level; + size = args.size; + + for (var i= 0; i < colorList.length ; i++) { + readability = tinycolor.readability(baseColor, colorList[i]); + if (readability > bestScore) { + bestScore = readability; + bestColor = tinycolor(colorList[i]); + } + } + + if (tinycolor.isReadable(baseColor, bestColor, {"level":level,"size":size}) || !includeFallbackColors) { + return bestColor; + } + else { + args.includeFallbackColors=false; + return tinycolor.mostReadable(baseColor,["#fff", "#000"],args); + } +}; + + +// Big List of Colors +// ------------------ +// +var names = tinycolor.names = { + aliceblue: "f0f8ff", + antiquewhite: "faebd7", + aqua: "0ff", + aquamarine: "7fffd4", + azure: "f0ffff", + beige: "f5f5dc", + bisque: "ffe4c4", + black: "000", + blanchedalmond: "ffebcd", + blue: "00f", + blueviolet: "8a2be2", + brown: "a52a2a", + burlywood: "deb887", + burntsienna: "ea7e5d", + cadetblue: "5f9ea0", + chartreuse: "7fff00", + chocolate: "d2691e", + coral: "ff7f50", + cornflowerblue: "6495ed", + cornsilk: "fff8dc", + crimson: "dc143c", + cyan: "0ff", + darkblue: "00008b", + darkcyan: "008b8b", + darkgoldenrod: "b8860b", + darkgray: "a9a9a9", + darkgreen: "006400", + darkgrey: "a9a9a9", + darkkhaki: "bdb76b", + darkmagenta: "8b008b", + darkolivegreen: "556b2f", + darkorange: "ff8c00", + darkorchid: "9932cc", + darkred: "8b0000", + darksalmon: "e9967a", + darkseagreen: "8fbc8f", + darkslateblue: "483d8b", + darkslategray: "2f4f4f", + darkslategrey: "2f4f4f", + darkturquoise: "00ced1", + darkviolet: "9400d3", + deeppink: "ff1493", + deepskyblue: "00bfff", + dimgray: "696969", + dimgrey: "696969", + dodgerblue: "1e90ff", + firebrick: "b22222", + floralwhite: "fffaf0", + forestgreen: "228b22", + fuchsia: "f0f", + gainsboro: "dcdcdc", + ghostwhite: "f8f8ff", + gold: "ffd700", + goldenrod: "daa520", + gray: "808080", + green: "008000", + greenyellow: "adff2f", + grey: "808080", + honeydew: "f0fff0", + hotpink: "ff69b4", + indianred: "cd5c5c", + indigo: "4b0082", + ivory: "fffff0", + khaki: "f0e68c", + lavender: "e6e6fa", + lavenderblush: "fff0f5", + lawngreen: "7cfc00", + lemonchiffon: "fffacd", + lightblue: "add8e6", + lightcoral: "f08080", + lightcyan: "e0ffff", + lightgoldenrodyellow: "fafad2", + lightgray: "d3d3d3", + lightgreen: "90ee90", + lightgrey: "d3d3d3", + lightpink: "ffb6c1", + lightsalmon: "ffa07a", + lightseagreen: "20b2aa", + lightskyblue: "87cefa", + lightslategray: "789", + lightslategrey: "789", + lightsteelblue: "b0c4de", + lightyellow: "ffffe0", + lime: "0f0", + limegreen: "32cd32", + linen: "faf0e6", + magenta: "f0f", + maroon: "800000", + mediumaquamarine: "66cdaa", + mediumblue: "0000cd", + mediumorchid: "ba55d3", + mediumpurple: "9370db", + mediumseagreen: "3cb371", + mediumslateblue: "7b68ee", + mediumspringgreen: "00fa9a", + mediumturquoise: "48d1cc", + mediumvioletred: "c71585", + midnightblue: "191970", + mintcream: "f5fffa", + mistyrose: "ffe4e1", + moccasin: "ffe4b5", + navajowhite: "ffdead", + navy: "000080", + oldlace: "fdf5e6", + olive: "808000", + olivedrab: "6b8e23", + orange: "ffa500", + orangered: "ff4500", + orchid: "da70d6", + palegoldenrod: "eee8aa", + palegreen: "98fb98", + paleturquoise: "afeeee", + palevioletred: "db7093", + papayawhip: "ffefd5", + peachpuff: "ffdab9", + peru: "cd853f", + pink: "ffc0cb", + plum: "dda0dd", + powderblue: "b0e0e6", + purple: "800080", + rebeccapurple: "663399", + red: "f00", + rosybrown: "bc8f8f", + royalblue: "4169e1", + saddlebrown: "8b4513", + salmon: "fa8072", + sandybrown: "f4a460", + seagreen: "2e8b57", + seashell: "fff5ee", + sienna: "a0522d", + silver: "c0c0c0", + skyblue: "87ceeb", + slateblue: "6a5acd", + slategray: "708090", + slategrey: "708090", + snow: "fffafa", + springgreen: "00ff7f", + steelblue: "4682b4", + tan: "d2b48c", + teal: "008080", + thistle: "d8bfd8", + tomato: "ff6347", + turquoise: "40e0d0", + violet: "ee82ee", + wheat: "f5deb3", + white: "fff", + whitesmoke: "f5f5f5", + yellow: "ff0", + yellowgreen: "9acd32" +}; + +// Make it easy to access colors via `hexNames[hex]` +var hexNames = tinycolor.hexNames = flip(names); + + +// Utilities +// --------- + +// `{ 'name1': 'val1' }` becomes `{ 'val1': 'name1' }` +function flip(o) { + var flipped = { }; + for (var i in o) { + if (o.hasOwnProperty(i)) { + flipped[o[i]] = i; + } + } + return flipped; +} + +// Return a valid alpha value [0,1] with all invalid values being set to 1 +function boundAlpha(a) { + a = parseFloat(a); + + if (isNaN(a) || a < 0 || a > 1) { + a = 1; + } + + return a; +} + +// Take input from [0, n] and return it as [0, 1] +function bound01(n, max) { + if (isOnePointZero(n)) { n = "100%"; } + + var processPercent = isPercentage(n); + n = mathMin(max, mathMax(0, parseFloat(n))); + + // Automatically convert percentage into number + if (processPercent) { + n = parseInt(n * max, 10) / 100; + } + + // Handle floating point rounding errors + if ((Math.abs(n - max) < 0.000001)) { + return 1; + } + + // Convert into [0, 1] range if it isn't already + return (n % max) / parseFloat(max); +} + +// Force a number between 0 and 1 +function clamp01(val) { + return mathMin(1, mathMax(0, val)); +} + +// Parse a base-16 hex value into a base-10 integer +function parseIntFromHex(val) { + return parseInt(val, 16); +} + +// Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1 +// +function isOnePointZero(n) { + return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1; +} + +// Check to see if string passed in is a percentage +function isPercentage(n) { + return typeof n === "string" && n.indexOf('%') != -1; +} + +// Force a hex value to have 2 characters +function pad2(c) { + return c.length == 1 ? '0' + c : '' + c; +} + +// Replace a decimal with it's percentage value +function convertToPercentage(n) { + if (n <= 1) { + n = (n * 100) + "%"; + } + + return n; +} + +// Converts a decimal to a hex value +function convertDecimalToHex(d) { + return Math.round(parseFloat(d) * 255).toString(16); +} +// Converts a hex value to a decimal +function convertHexToDecimal(h) { + return (parseIntFromHex(h) / 255); +} + +var matchers = (function() { + + // + var CSS_INTEGER = "[-\\+]?\\d+%?"; + + // + var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?"; + + // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome. + var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")"; + + // Actual matching. + // Parentheses and commas are optional, but not required. + // Whitespace can take the place of commas or opening paren + var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; + var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; + + return { + CSS_UNIT: new RegExp(CSS_UNIT), + rgb: new RegExp("rgb" + PERMISSIVE_MATCH3), + rgba: new RegExp("rgba" + PERMISSIVE_MATCH4), + hsl: new RegExp("hsl" + PERMISSIVE_MATCH3), + hsla: new RegExp("hsla" + PERMISSIVE_MATCH4), + hsv: new RegExp("hsv" + PERMISSIVE_MATCH3), + hsva: new RegExp("hsva" + PERMISSIVE_MATCH4), + hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, + hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/, + hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, + hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/ + }; +})(); + +// `isValidCSSUnit` +// Take in a single string / number and check to see if it looks like a CSS unit +// (see `matchers` above for definition). +function isValidCSSUnit(color) { + return !!matchers.CSS_UNIT.exec(color); +} + +// `stringInputToObject` +// Permissive string parsing. Take in a number of formats, and output an object +// based on detected format. Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}` +function stringInputToObject(color) { + + color = color.replace(trimLeft,'').replace(trimRight, '').toLowerCase(); + var named = false; + if (names[color]) { + color = names[color]; + named = true; + } + else if (color == 'transparent') { + return { r: 0, g: 0, b: 0, a: 0, format: "name" }; + } + + // Try to match string input using regular expressions. + // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360] + // Just return an object and let the conversion functions handle that. + // This way the result will be the same whether the tinycolor is initialized with string or object. + var match; + if ((match = matchers.rgb.exec(color))) { + return { r: match[1], g: match[2], b: match[3] }; + } + if ((match = matchers.rgba.exec(color))) { + return { r: match[1], g: match[2], b: match[3], a: match[4] }; + } + if ((match = matchers.hsl.exec(color))) { + return { h: match[1], s: match[2], l: match[3] }; + } + if ((match = matchers.hsla.exec(color))) { + return { h: match[1], s: match[2], l: match[3], a: match[4] }; + } + if ((match = matchers.hsv.exec(color))) { + return { h: match[1], s: match[2], v: match[3] }; + } + if ((match = matchers.hsva.exec(color))) { + return { h: match[1], s: match[2], v: match[3], a: match[4] }; + } + if ((match = matchers.hex8.exec(color))) { + return { + r: parseIntFromHex(match[1]), + g: parseIntFromHex(match[2]), + b: parseIntFromHex(match[3]), + a: convertHexToDecimal(match[4]), + format: named ? "name" : "hex8" + }; + } + if ((match = matchers.hex6.exec(color))) { + return { + r: parseIntFromHex(match[1]), + g: parseIntFromHex(match[2]), + b: parseIntFromHex(match[3]), + format: named ? "name" : "hex" + }; + } + if ((match = matchers.hex4.exec(color))) { + return { + r: parseIntFromHex(match[1] + '' + match[1]), + g: parseIntFromHex(match[2] + '' + match[2]), + b: parseIntFromHex(match[3] + '' + match[3]), + a: convertHexToDecimal(match[4] + '' + match[4]), + format: named ? "name" : "hex8" + }; + } + if ((match = matchers.hex3.exec(color))) { + return { + r: parseIntFromHex(match[1] + '' + match[1]), + g: parseIntFromHex(match[2] + '' + match[2]), + b: parseIntFromHex(match[3] + '' + match[3]), + format: named ? "name" : "hex" + }; + } + + return false; +} + +function validateWCAG2Parms(parms) { + // return valid WCAG2 parms for isReadable. + // If input parms are invalid, return {"level":"AA", "size":"small"} + var level, size; + parms = parms || {"level":"AA", "size":"small"}; + level = (parms.level || "AA").toUpperCase(); + size = (parms.size || "small").toLowerCase(); + if (level !== "AA" && level !== "AAA") { + level = "AA"; + } + if (size !== "small" && size !== "large") { + size = "small"; + } + return {"level":level, "size":size}; +} + +// Node: Export function +if (typeof module !== "undefined" && module.exports) { + module.exports = tinycolor; +} +// AMD/requirejs: Define the module +else if (typeof define === 'function' && define.amd) { + define(function () {return tinycolor;}); +} +// Browser: Expose to window +else { + window.tinycolor = tinycolor; +} + +})(Math); + +},{}],35:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +/** + * All paths are tuned for maximum scalability of the arrowhead, + * ie throughout arrowwidth=0.3..3 the head is joined smoothly + * to the line, with the line coming from the left and ending at (0, 0). + * + * `backoff` is the distance to move the arrowhead and the end of the line, + * in order that the arrowhead points to the desired place, either at + * the tip of the arrow or (in the case of circle or square) + * the center of the symbol. + * + * `noRotate`, if truthy, says that this arrowhead should not rotate with the + * arrow. That's the case for squares, which should always be straight, and + * circles, for which it's irrelevant. + */ + +module.exports = [ + // no arrow + { + path: '', + backoff: 0 + }, + // wide with flat back + { + path: 'M-2.4,-3V3L0.6,0Z', + backoff: 0.6 + }, + // narrower with flat back + { + path: 'M-3.7,-2.5V2.5L1.3,0Z', + backoff: 1.3 + }, + // barbed + { + path: 'M-4.45,-3L-1.65,-0.2V0.2L-4.45,3L1.55,0Z', + backoff: 1.55 + }, + // wide line-drawn + { + path: 'M-2.2,-2.2L-0.2,-0.2V0.2L-2.2,2.2L-1.4,3L1.6,0L-1.4,-3Z', + backoff: 1.6 + }, + // narrower line-drawn + { + path: 'M-4.4,-2.1L-0.6,-0.2V0.2L-4.4,2.1L-4,3L2,0L-4,-3Z', + backoff: 2 + }, + // circle + { + path: 'M2,0A2,2 0 1,1 0,-2A2,2 0 0,1 2,0Z', + backoff: 0, + noRotate: true + }, + // square + { + path: 'M2,2V-2H-2V2Z', + backoff: 0, + noRotate: true + } +]; + +},{}],36:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var ARROWPATHS = _dereq_('./arrow_paths'); +var fontAttrs = _dereq_('../../plots/font_attributes'); +var cartesianConstants = _dereq_('../../plots/cartesian/constants'); +var templatedArray = _dereq_('../../plot_api/plot_template').templatedArray; + + +module.exports = templatedArray('annotation', { + visible: { + valType: 'boolean', + + dflt: true, + editType: 'calc+arraydraw', + + }, + + text: { + valType: 'string', + + editType: 'calc+arraydraw', + + }, + textangle: { + valType: 'angle', + dflt: 0, + + editType: 'calc+arraydraw', + + }, + font: fontAttrs({ + editType: 'calc+arraydraw', + colorEditType: 'arraydraw', + + }), + width: { + valType: 'number', + min: 1, + dflt: null, + + editType: 'calc+arraydraw', + + }, + height: { + valType: 'number', + min: 1, + dflt: null, + + editType: 'calc+arraydraw', + + }, + opacity: { + valType: 'number', + min: 0, + max: 1, + dflt: 1, + + editType: 'arraydraw', + + }, + align: { + valType: 'enumerated', + values: ['left', 'center', 'right'], + dflt: 'center', + + editType: 'arraydraw', + + }, + valign: { + valType: 'enumerated', + values: ['top', 'middle', 'bottom'], + dflt: 'middle', + + editType: 'arraydraw', + + }, + bgcolor: { + valType: 'color', + dflt: 'rgba(0,0,0,0)', + + editType: 'arraydraw', + + }, + bordercolor: { + valType: 'color', + dflt: 'rgba(0,0,0,0)', + + editType: 'arraydraw', + + }, + borderpad: { + valType: 'number', + min: 0, + dflt: 1, + + editType: 'calc+arraydraw', + + }, + borderwidth: { + valType: 'number', + min: 0, + dflt: 1, + + editType: 'calc+arraydraw', + + }, + // arrow + showarrow: { + valType: 'boolean', + dflt: true, + + editType: 'calc+arraydraw', + + }, + arrowcolor: { + valType: 'color', + + editType: 'arraydraw', + + }, + arrowhead: { + valType: 'integer', + min: 0, + max: ARROWPATHS.length, + dflt: 1, + + editType: 'arraydraw', + + }, + startarrowhead: { + valType: 'integer', + min: 0, + max: ARROWPATHS.length, + dflt: 1, + + editType: 'arraydraw', + + }, + arrowside: { + valType: 'flaglist', + flags: ['end', 'start'], + extras: ['none'], + dflt: 'end', + + editType: 'arraydraw', + + }, + arrowsize: { + valType: 'number', + min: 0.3, + dflt: 1, + + editType: 'calc+arraydraw', + + }, + startarrowsize: { + valType: 'number', + min: 0.3, + dflt: 1, + + editType: 'calc+arraydraw', + + }, + arrowwidth: { + valType: 'number', + min: 0.1, + + editType: 'calc+arraydraw', + + }, + standoff: { + valType: 'number', + min: 0, + dflt: 0, + + editType: 'calc+arraydraw', + + }, + startstandoff: { + valType: 'number', + min: 0, + dflt: 0, + + editType: 'calc+arraydraw', + + }, + ax: { + valType: 'any', + + editType: 'calc+arraydraw', + + }, + ay: { + valType: 'any', + + editType: 'calc+arraydraw', + + }, + axref: { + valType: 'enumerated', + dflt: 'pixel', + values: [ + 'pixel', + cartesianConstants.idRegex.x.toString() + ], + + editType: 'calc', + + }, + ayref: { + valType: 'enumerated', + dflt: 'pixel', + values: [ + 'pixel', + cartesianConstants.idRegex.y.toString() + ], + + editType: 'calc', + + }, + // positioning + xref: { + valType: 'enumerated', + values: [ + 'paper', + cartesianConstants.idRegex.x.toString() + ], + + editType: 'calc', + + }, + x: { + valType: 'any', + + editType: 'calc+arraydraw', + + }, + xanchor: { + valType: 'enumerated', + values: ['auto', 'left', 'center', 'right'], + dflt: 'auto', + + editType: 'calc+arraydraw', + + }, + xshift: { + valType: 'number', + dflt: 0, + + editType: 'calc+arraydraw', + + }, + yref: { + valType: 'enumerated', + values: [ + 'paper', + cartesianConstants.idRegex.y.toString() + ], + + editType: 'calc', + + }, + y: { + valType: 'any', + + editType: 'calc+arraydraw', + + }, + yanchor: { + valType: 'enumerated', + values: ['auto', 'top', 'middle', 'bottom'], + dflt: 'auto', + + editType: 'calc+arraydraw', + + }, + yshift: { + valType: 'number', + dflt: 0, + + editType: 'calc+arraydraw', + + }, + clicktoshow: { + valType: 'enumerated', + values: [false, 'onoff', 'onout'], + dflt: false, + + editType: 'arraydraw', + + }, + xclick: { + valType: 'any', + + editType: 'arraydraw', + + }, + yclick: { + valType: 'any', + + editType: 'arraydraw', + + }, + hovertext: { + valType: 'string', + + editType: 'arraydraw', + + }, + hoverlabel: { + bgcolor: { + valType: 'color', + + editType: 'arraydraw', + + }, + bordercolor: { + valType: 'color', + + editType: 'arraydraw', + + }, + font: fontAttrs({ + editType: 'arraydraw', + + }), + editType: 'arraydraw' + }, + captureevents: { + valType: 'boolean', + + editType: 'arraydraw', + + }, + editType: 'calc', + + _deprecated: { + ref: { + valType: 'string', + + editType: 'calc', + + } + } +}); + +},{"../../plot_api/plot_template":203,"../../plots/cartesian/constants":219,"../../plots/font_attributes":239,"./arrow_paths":35}],37:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Lib = _dereq_('../../lib'); +var Axes = _dereq_('../../plots/cartesian/axes'); + +var draw = _dereq_('./draw').draw; + + +module.exports = function calcAutorange(gd) { + var fullLayout = gd._fullLayout; + var annotationList = Lib.filterVisible(fullLayout.annotations); + + if(annotationList.length && gd._fullData.length) { + return Lib.syncOrAsync([draw, annAutorange], gd); + } +}; + +function annAutorange(gd) { + var fullLayout = gd._fullLayout; + + // find the bounding boxes for each of these annotations' + // relative to their anchor points + // use the arrow and the text bg rectangle, + // as the whole anno may include hidden text in its bbox + Lib.filterVisible(fullLayout.annotations).forEach(function(ann) { + var xa = Axes.getFromId(gd, ann.xref); + var ya = Axes.getFromId(gd, ann.yref); + + ann._extremes = {}; + if(xa) calcAxisExpansion(ann, xa); + if(ya) calcAxisExpansion(ann, ya); + }); +} + +function calcAxisExpansion(ann, ax) { + var axId = ax._id; + var letter = axId.charAt(0); + var pos = ann[letter]; + var apos = ann['a' + letter]; + var ref = ann[letter + 'ref']; + var aref = ann['a' + letter + 'ref']; + var padplus = ann['_' + letter + 'padplus']; + var padminus = ann['_' + letter + 'padminus']; + var shift = {x: 1, y: -1}[letter] * ann[letter + 'shift']; + var headSize = 3 * ann.arrowsize * ann.arrowwidth || 0; + var headPlus = headSize + shift; + var headMinus = headSize - shift; + var startHeadSize = 3 * ann.startarrowsize * ann.arrowwidth || 0; + var startHeadPlus = startHeadSize + shift; + var startHeadMinus = startHeadSize - shift; + var extremes; + + if(aref === ref) { + // expand for the arrowhead (padded by arrowhead) + var extremeArrowHead = Axes.findExtremes(ax, [ax.r2c(pos)], { + ppadplus: headPlus, + ppadminus: headMinus + }); + // again for the textbox (padded by textbox) + var extremeText = Axes.findExtremes(ax, [ax.r2c(apos)], { + ppadplus: Math.max(padplus, startHeadPlus), + ppadminus: Math.max(padminus, startHeadMinus) + }); + extremes = { + min: [extremeArrowHead.min[0], extremeText.min[0]], + max: [extremeArrowHead.max[0], extremeText.max[0]] + }; + } else { + startHeadPlus = apos ? startHeadPlus + apos : startHeadPlus; + startHeadMinus = apos ? startHeadMinus - apos : startHeadMinus; + extremes = Axes.findExtremes(ax, [ax.r2c(pos)], { + ppadplus: Math.max(padplus, headPlus, startHeadPlus), + ppadminus: Math.max(padminus, headMinus, startHeadMinus) + }); + } + + ann._extremes[axId] = extremes; +} + +},{"../../lib":169,"../../plots/cartesian/axes":213,"./draw":42}],38:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var Registry = _dereq_('../../registry'); +var arrayEditor = _dereq_('../../plot_api/plot_template').arrayEditor; + +module.exports = { + hasClickToShow: hasClickToShow, + onClick: onClick +}; + +/* + * hasClickToShow: does the given hoverData have ANY annotations which will + * turn ON if we click here? (used by hover events to set cursor) + * + * gd: graphDiv + * hoverData: a hoverData array, as included with the *plotly_hover* or + * *plotly_click* events in the `points` attribute + * + * returns: boolean + */ +function hasClickToShow(gd, hoverData) { + var sets = getToggleSets(gd, hoverData); + return sets.on.length > 0 || sets.explicitOff.length > 0; +} + +/* + * onClick: perform the toggling (via Plotly.update) implied by clicking + * at this hoverData + * + * gd: graphDiv + * hoverData: a hoverData array, as included with the *plotly_hover* or + * *plotly_click* events in the `points` attribute + * + * returns: Promise that the update is complete + */ +function onClick(gd, hoverData) { + var toggleSets = getToggleSets(gd, hoverData); + var onSet = toggleSets.on; + var offSet = toggleSets.off.concat(toggleSets.explicitOff); + var update = {}; + var annotationsOut = gd._fullLayout.annotations; + var i, editHelpers; + + if(!(onSet.length || offSet.length)) return; + + for(i = 0; i < onSet.length; i++) { + editHelpers = arrayEditor(gd.layout, 'annotations', annotationsOut[onSet[i]]); + editHelpers.modifyItem('visible', true); + Lib.extendFlat(update, editHelpers.getUpdateObj()); + } + + for(i = 0; i < offSet.length; i++) { + editHelpers = arrayEditor(gd.layout, 'annotations', annotationsOut[offSet[i]]); + editHelpers.modifyItem('visible', false); + Lib.extendFlat(update, editHelpers.getUpdateObj()); + } + + return Registry.call('update', gd, {}, update); +} + +/* + * getToggleSets: find the annotations which will turn on or off at this + * hoverData + * + * gd: graphDiv + * hoverData: a hoverData array, as included with the *plotly_hover* or + * *plotly_click* events in the `points` attribute + * + * returns: { + * on: Array (indices of annotations to turn on), + * off: Array (indices to turn off because you're not hovering on them), + * explicitOff: Array (indices to turn off because you *are* hovering on them) + * } + */ +function getToggleSets(gd, hoverData) { + var annotations = gd._fullLayout.annotations; + var onSet = []; + var offSet = []; + var explicitOffSet = []; + var hoverLen = (hoverData || []).length; + + var i, j, anni, showMode, pointj, xa, ya, toggleType; + + for(i = 0; i < annotations.length; i++) { + anni = annotations[i]; + showMode = anni.clicktoshow; + + if(showMode) { + for(j = 0; j < hoverLen; j++) { + pointj = hoverData[j]; + xa = pointj.xaxis; + ya = pointj.yaxis; + + if(xa._id === anni.xref && + ya._id === anni.yref && + xa.d2r(pointj.x) === clickData2r(anni._xclick, xa) && + ya.d2r(pointj.y) === clickData2r(anni._yclick, ya) + ) { + // match! toggle this annotation + // regardless of its clicktoshow mode + // but if it's onout mode, off is implicit + if(anni.visible) { + if(showMode === 'onout') toggleType = offSet; + else toggleType = explicitOffSet; + } else { + toggleType = onSet; + } + toggleType.push(i); + break; + } + } + + if(j === hoverLen) { + // no match - only turn this annotation OFF, and only if + // showmode is 'onout' + if(anni.visible && showMode === 'onout') offSet.push(i); + } + } + } + + return {on: onSet, off: offSet, explicitOff: explicitOffSet}; +} + +// to handle log axes until v2 +function clickData2r(d, ax) { + return ax.type === 'log' ? ax.l2r(d) : ax.d2r(d); +} + +},{"../../lib":169,"../../plot_api/plot_template":203,"../../registry":258}],39:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var Color = _dereq_('../color'); + +// defaults common to 'annotations' and 'annotations3d' +module.exports = function handleAnnotationCommonDefaults(annIn, annOut, fullLayout, coerce) { + coerce('opacity'); + var bgColor = coerce('bgcolor'); + + var borderColor = coerce('bordercolor'); + var borderOpacity = Color.opacity(borderColor); + + coerce('borderpad'); + + var borderWidth = coerce('borderwidth'); + var showArrow = coerce('showarrow'); + + coerce('text', showArrow ? ' ' : fullLayout._dfltTitle.annotation); + coerce('textangle'); + Lib.coerceFont(coerce, 'font', fullLayout.font); + + coerce('width'); + coerce('align'); + + var h = coerce('height'); + if(h) coerce('valign'); + + if(showArrow) { + var arrowside = coerce('arrowside'); + var arrowhead; + var arrowsize; + + if(arrowside.indexOf('end') !== -1) { + arrowhead = coerce('arrowhead'); + arrowsize = coerce('arrowsize'); + } + + if(arrowside.indexOf('start') !== -1) { + coerce('startarrowhead', arrowhead); + coerce('startarrowsize', arrowsize); + } + coerce('arrowcolor', borderOpacity ? annOut.bordercolor : Color.defaultLine); + coerce('arrowwidth', ((borderOpacity && borderWidth) || 1) * 2); + coerce('standoff'); + coerce('startstandoff'); + } + + var hoverText = coerce('hovertext'); + var globalHoverLabel = fullLayout.hoverlabel || {}; + + if(hoverText) { + var hoverBG = coerce('hoverlabel.bgcolor', globalHoverLabel.bgcolor || + (Color.opacity(bgColor) ? Color.rgb(bgColor) : Color.defaultLine) + ); + + var hoverBorder = coerce('hoverlabel.bordercolor', globalHoverLabel.bordercolor || + Color.contrast(hoverBG) + ); + + Lib.coerceFont(coerce, 'hoverlabel.font', { + family: globalHoverLabel.font.family, + size: globalHoverLabel.font.size, + color: globalHoverLabel.font.color || hoverBorder + }); + } + + coerce('captureevents', !!hoverText); +}; + +},{"../../lib":169,"../color":51}],40:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); +var toLogRange = _dereq_('../../lib/to_log_range'); + +/* + * convertCoords: when converting an axis between log and linear + * you need to alter any annotations on that axis to keep them + * pointing at the same data point. + * In v2.0 this will become obsolete + * + * gd: the plot div + * ax: the axis being changed + * newType: the type it's getting + * doExtra: function(attr, val) from inside relayout that sets the attribute. + * Use this to make the changes as it's aware if any other changes in the + * same relayout call should override this conversion. + */ +module.exports = function convertCoords(gd, ax, newType, doExtra) { + ax = ax || {}; + + var toLog = (newType === 'log') && (ax.type === 'linear'); + var fromLog = (newType === 'linear') && (ax.type === 'log'); + + if(!(toLog || fromLog)) return; + + var annotations = gd._fullLayout.annotations; + var axLetter = ax._id.charAt(0); + var ann; + var attrPrefix; + + function convert(attr) { + var currentVal = ann[attr]; + var newVal = null; + + if(toLog) newVal = toLogRange(currentVal, ax.range); + else newVal = Math.pow(10, currentVal); + + // if conversion failed, delete the value so it gets a default value + if(!isNumeric(newVal)) newVal = null; + + doExtra(attrPrefix + attr, newVal); + } + + for(var i = 0; i < annotations.length; i++) { + ann = annotations[i]; + attrPrefix = 'annotations[' + i + '].'; + + if(ann[axLetter + 'ref'] === ax._id) convert(axLetter); + if(ann['a' + axLetter + 'ref'] === ax._id) convert('a' + axLetter); + } +}; + +},{"../../lib/to_log_range":192,"fast-isnumeric":18}],41:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Lib = _dereq_('../../lib'); +var Axes = _dereq_('../../plots/cartesian/axes'); +var handleArrayContainerDefaults = _dereq_('../../plots/array_container_defaults'); + +var handleAnnotationCommonDefaults = _dereq_('./common_defaults'); +var attributes = _dereq_('./attributes'); + + +module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) { + handleArrayContainerDefaults(layoutIn, layoutOut, { + name: 'annotations', + handleItemDefaults: handleAnnotationDefaults + }); +}; + +function handleAnnotationDefaults(annIn, annOut, fullLayout) { + function coerce(attr, dflt) { + return Lib.coerce(annIn, annOut, attributes, attr, dflt); + } + + var visible = coerce('visible'); + var clickToShow = coerce('clicktoshow'); + + if(!(visible || clickToShow)) return; + + handleAnnotationCommonDefaults(annIn, annOut, fullLayout, coerce); + + var showArrow = annOut.showarrow; + + // positioning + var axLetters = ['x', 'y']; + var arrowPosDflt = [-10, -30]; + var gdMock = {_fullLayout: fullLayout}; + + for(var i = 0; i < 2; i++) { + var axLetter = axLetters[i]; + + // xref, yref + var axRef = Axes.coerceRef(annIn, annOut, gdMock, axLetter, '', 'paper'); + + if(axRef !== 'paper') { + var ax = Axes.getFromId(gdMock, axRef); + ax._annIndices.push(annOut._index); + } + + // x, y + Axes.coercePosition(annOut, gdMock, coerce, axRef, axLetter, 0.5); + + if(showArrow) { + var arrowPosAttr = 'a' + axLetter; + // axref, ayref + var aaxRef = Axes.coerceRef(annIn, annOut, gdMock, arrowPosAttr, 'pixel'); + + // for now the arrow can only be on the same axis or specified as pixels + // TODO: sometime it might be interesting to allow it to be on *any* axis + // but that would require updates to drawing & autorange code and maybe more + if(aaxRef !== 'pixel' && aaxRef !== axRef) { + aaxRef = annOut[arrowPosAttr] = 'pixel'; + } + + // ax, ay + var aDflt = (aaxRef === 'pixel') ? arrowPosDflt[i] : 0.4; + Axes.coercePosition(annOut, gdMock, coerce, aaxRef, arrowPosAttr, aDflt); + } + + // xanchor, yanchor + coerce(axLetter + 'anchor'); + + // xshift, yshift + coerce(axLetter + 'shift'); + } + + // if you have one coordinate you should have both + Lib.noneOrAll(annIn, annOut, ['x', 'y']); + + // if you have one part of arrow length you should have both + if(showArrow) { + Lib.noneOrAll(annIn, annOut, ['ax', 'ay']); + } + + if(clickToShow) { + var xClick = coerce('xclick'); + var yClick = coerce('yclick'); + + // put the actual click data to bind to into private attributes + // so we don't have to do this little bit of logic on every hover event + annOut._xclick = (xClick === undefined) ? + annOut.x : + Axes.cleanPosition(xClick, gdMock, annOut.xref); + annOut._yclick = (yClick === undefined) ? + annOut.y : + Axes.cleanPosition(yClick, gdMock, annOut.yref); + } +} + +},{"../../lib":169,"../../plots/array_container_defaults":209,"../../plots/cartesian/axes":213,"./attributes":36,"./common_defaults":39}],42:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); + +var Registry = _dereq_('../../registry'); +var Plots = _dereq_('../../plots/plots'); +var Lib = _dereq_('../../lib'); +var Axes = _dereq_('../../plots/cartesian/axes'); +var Color = _dereq_('../color'); +var Drawing = _dereq_('../drawing'); +var Fx = _dereq_('../fx'); +var svgTextUtils = _dereq_('../../lib/svg_text_utils'); +var setCursor = _dereq_('../../lib/setcursor'); +var dragElement = _dereq_('../dragelement'); +var arrayEditor = _dereq_('../../plot_api/plot_template').arrayEditor; + +var drawArrowHead = _dereq_('./draw_arrow_head'); + +// Annotations are stored in gd.layout.annotations, an array of objects +// index can point to one item in this array, +// or non-numeric to simply add a new one +// or -1 to modify all existing +// opt can be the full options object, or one key (to be set to value) +// or undefined to simply redraw +// if opt is blank, val can be 'add' or a full options object to add a new +// annotation at that point in the array, or 'remove' to delete this one + +module.exports = { + draw: draw, + drawOne: drawOne, + drawRaw: drawRaw +}; + +/* + * draw: draw all annotations without any new modifications + */ +function draw(gd) { + var fullLayout = gd._fullLayout; + + fullLayout._infolayer.selectAll('.annotation').remove(); + + for(var i = 0; i < fullLayout.annotations.length; i++) { + if(fullLayout.annotations[i].visible) { + drawOne(gd, i); + } + } + + return Plots.previousPromises(gd); +} + +/* + * drawOne: draw a single cartesian or paper-ref annotation, potentially with modifications + * + * index (int): the annotation to draw + */ +function drawOne(gd, index) { + var fullLayout = gd._fullLayout; + var options = fullLayout.annotations[index] || {}; + var xa = Axes.getFromId(gd, options.xref); + var ya = Axes.getFromId(gd, options.yref); + + if(xa) xa.setScale(); + if(ya) ya.setScale(); + + drawRaw(gd, options, index, false, xa, ya); +} + +/** + * drawRaw: draw a single annotation, potentially with modifications + * + * @param {DOM element} gd + * @param {object} options : this annotation's fullLayout options + * @param {integer} index : index in 'annotations' container of the annotation to draw + * @param {string} subplotId : id of the annotation's subplot + * - use false for 2d (i.e. cartesian or paper-ref) annotations + * @param {object | undefined} xa : full x-axis object to compute subplot pos-to-px + * @param {object | undefined} ya : ... y-axis + */ +function drawRaw(gd, options, index, subplotId, xa, ya) { + var fullLayout = gd._fullLayout; + var gs = gd._fullLayout._size; + var edits = gd._context.edits; + + var className, containerStr; + + if(subplotId) { + className = 'annotation-' + subplotId; + containerStr = subplotId + '.annotations'; + } else { + className = 'annotation'; + containerStr = 'annotations'; + } + + var editHelpers = arrayEditor(gd.layout, containerStr, options); + var modifyBase = editHelpers.modifyBase; + var modifyItem = editHelpers.modifyItem; + var getUpdateObj = editHelpers.getUpdateObj; + + // remove the existing annotation if there is one + fullLayout._infolayer + .selectAll('.' + className + '[data-index="' + index + '"]') + .remove(); + + var annClipID = 'clip' + fullLayout._uid + '_ann' + index; + + // this annotation is gone - quit now after deleting it + // TODO: use d3 idioms instead of deleting and redrawing every time + if(!options._input || options.visible === false) { + d3.selectAll('#' + annClipID).remove(); + return; + } + + // calculated pixel positions + // x & y each will get text, head, and tail as appropriate + var annPosPx = {x: {}, y: {}}; + var textangle = +options.textangle || 0; + + // create the components + // made a single group to contain all, so opacity can work right + // with border/arrow together this could handle a whole bunch of + // cleanup at this point, but works for now + var annGroup = fullLayout._infolayer.append('g') + .classed(className, true) + .attr('data-index', String(index)) + .style('opacity', options.opacity); + + // another group for text+background so that they can rotate together + var annTextGroup = annGroup.append('g') + .classed('annotation-text-g', true); + + var editTextPosition = edits[options.showarrow ? 'annotationTail' : 'annotationPosition']; + var textEvents = options.captureevents || edits.annotationText || editTextPosition; + + function makeEventData(initialEvent) { + var eventData = { + index: index, + annotation: options._input, + fullAnnotation: options, + event: initialEvent + }; + if(subplotId) { + eventData.subplotId = subplotId; + } + return eventData; + } + + var annTextGroupInner = annTextGroup.append('g') + .style('pointer-events', textEvents ? 'all' : null) + .call(setCursor, 'pointer') + .on('click', function() { + gd._dragging = false; + gd.emit('plotly_clickannotation', makeEventData(d3.event)); + }); + + if(options.hovertext) { + annTextGroupInner + .on('mouseover', function() { + var hoverOptions = options.hoverlabel; + var hoverFont = hoverOptions.font; + var bBox = this.getBoundingClientRect(); + var bBoxRef = gd.getBoundingClientRect(); + + Fx.loneHover({ + x0: bBox.left - bBoxRef.left, + x1: bBox.right - bBoxRef.left, + y: (bBox.top + bBox.bottom) / 2 - bBoxRef.top, + text: options.hovertext, + color: hoverOptions.bgcolor, + borderColor: hoverOptions.bordercolor, + fontFamily: hoverFont.family, + fontSize: hoverFont.size, + fontColor: hoverFont.color + }, { + container: fullLayout._hoverlayer.node(), + outerContainer: fullLayout._paper.node(), + gd: gd + }); + }) + .on('mouseout', function() { + Fx.loneUnhover(fullLayout._hoverlayer.node()); + }); + } + + var borderwidth = options.borderwidth; + var borderpad = options.borderpad; + var borderfull = borderwidth + borderpad; + + var annTextBG = annTextGroupInner.append('rect') + .attr('class', 'bg') + .style('stroke-width', borderwidth + 'px') + .call(Color.stroke, options.bordercolor) + .call(Color.fill, options.bgcolor); + + var isSizeConstrained = options.width || options.height; + + var annTextClip = fullLayout._topclips + .selectAll('#' + annClipID) + .data(isSizeConstrained ? [0] : []); + + annTextClip.enter().append('clipPath') + .classed('annclip', true) + .attr('id', annClipID) + .append('rect'); + annTextClip.exit().remove(); + + var font = options.font; + + var text = fullLayout._meta ? + Lib.templateString(options.text, fullLayout._meta) : + options.text; + + var annText = annTextGroupInner.append('text') + .classed('annotation-text', true) + .text(text); + + function textLayout(s) { + s.call(Drawing.font, font) + .attr({ + 'text-anchor': { + left: 'start', + right: 'end' + }[options.align] || 'middle' + }); + + svgTextUtils.convertToTspans(s, gd, drawGraphicalElements); + return s; + } + + function drawGraphicalElements() { + // if the text has *only* a link, make the whole box into a link + var anchor3 = annText.selectAll('a'); + if(anchor3.size() === 1 && anchor3.text() === annText.text()) { + var wholeLink = annTextGroupInner.insert('a', ':first-child').attr({ + 'xlink:xlink:href': anchor3.attr('xlink:href'), + 'xlink:xlink:show': anchor3.attr('xlink:show') + }) + .style({cursor: 'pointer'}); + + wholeLink.node().appendChild(annTextBG.node()); + } + + var mathjaxGroup = annTextGroupInner.select('.annotation-text-math-group'); + var hasMathjax = !mathjaxGroup.empty(); + var anntextBB = Drawing.bBox( + (hasMathjax ? mathjaxGroup : annText).node()); + var textWidth = anntextBB.width; + var textHeight = anntextBB.height; + var annWidth = options.width || textWidth; + var annHeight = options.height || textHeight; + var outerWidth = Math.round(annWidth + 2 * borderfull); + var outerHeight = Math.round(annHeight + 2 * borderfull); + + function shiftFraction(v, anchor) { + if(anchor === 'auto') { + if(v < 1 / 3) anchor = 'left'; + else if(v > 2 / 3) anchor = 'right'; + else anchor = 'center'; + } + return { + center: 0, + middle: 0, + left: 0.5, + bottom: -0.5, + right: -0.5, + top: 0.5 + }[anchor]; + } + + var annotationIsOffscreen = false; + var letters = ['x', 'y']; + + for(var i = 0; i < letters.length; i++) { + var axLetter = letters[i]; + var axRef = options[axLetter + 'ref'] || axLetter; + var tailRef = options['a' + axLetter + 'ref']; + var ax = {x: xa, y: ya}[axLetter]; + var dimAngle = (textangle + (axLetter === 'x' ? 0 : -90)) * Math.PI / 180; + // note that these two can be either positive or negative + var annSizeFromWidth = outerWidth * Math.cos(dimAngle); + var annSizeFromHeight = outerHeight * Math.sin(dimAngle); + // but this one is the positive total size + var annSize = Math.abs(annSizeFromWidth) + Math.abs(annSizeFromHeight); + var anchor = options[axLetter + 'anchor']; + var overallShift = options[axLetter + 'shift'] * (axLetter === 'x' ? 1 : -1); + var posPx = annPosPx[axLetter]; + var basePx; + var textPadShift; + var alignPosition; + var autoAlignFraction; + var textShift; + + /* + * calculate the *primary* pixel position + * which is the arrowhead if there is one, + * otherwise the text anchor point + */ + if(ax) { + // check if annotation is off screen, to bypass DOM manipulations + var posFraction = ax.r2fraction(options[axLetter]); + if(posFraction < 0 || posFraction > 1) { + if(tailRef === axRef) { + posFraction = ax.r2fraction(options['a' + axLetter]); + if(posFraction < 0 || posFraction > 1) { + annotationIsOffscreen = true; + } + } else { + annotationIsOffscreen = true; + } + } + basePx = ax._offset + ax.r2p(options[axLetter]); + autoAlignFraction = 0.5; + } else { + if(axLetter === 'x') { + alignPosition = options[axLetter]; + basePx = gs.l + gs.w * alignPosition; + } else { + alignPosition = 1 - options[axLetter]; + basePx = gs.t + gs.h * alignPosition; + } + autoAlignFraction = options.showarrow ? 0.5 : alignPosition; + } + + // now translate this into pixel positions of head, tail, and text + // as well as paddings for autorange + if(options.showarrow) { + posPx.head = basePx; + + var arrowLength = options['a' + axLetter]; + + // with an arrow, the text rotates around the anchor point + textShift = annSizeFromWidth * shiftFraction(0.5, options.xanchor) - + annSizeFromHeight * shiftFraction(0.5, options.yanchor); + + if(tailRef === axRef) { + posPx.tail = ax._offset + ax.r2p(arrowLength); + // tail is data-referenced: autorange pads the text in px from the tail + textPadShift = textShift; + } else { + posPx.tail = basePx + arrowLength; + // tail is specified in px from head, so autorange also pads vs head + textPadShift = textShift + arrowLength; + } + + posPx.text = posPx.tail + textShift; + + // constrain pixel/paper referenced so the draggers are at least + // partially visible + var maxPx = fullLayout[(axLetter === 'x') ? 'width' : 'height']; + if(axRef === 'paper') { + posPx.head = Lib.constrain(posPx.head, 1, maxPx - 1); + } + if(tailRef === 'pixel') { + var shiftPlus = -Math.max(posPx.tail - 3, posPx.text); + var shiftMinus = Math.min(posPx.tail + 3, posPx.text) - maxPx; + if(shiftPlus > 0) { + posPx.tail += shiftPlus; + posPx.text += shiftPlus; + } else if(shiftMinus > 0) { + posPx.tail -= shiftMinus; + posPx.text -= shiftMinus; + } + } + + posPx.tail += overallShift; + posPx.head += overallShift; + } else { + // with no arrow, the text rotates and *then* we put the anchor + // relative to the new bounding box + textShift = annSize * shiftFraction(autoAlignFraction, anchor); + textPadShift = textShift; + posPx.text = basePx + textShift; + } + + posPx.text += overallShift; + textShift += overallShift; + textPadShift += overallShift; + + // padplus/minus are used by autorange + options['_' + axLetter + 'padplus'] = (annSize / 2) + textPadShift; + options['_' + axLetter + 'padminus'] = (annSize / 2) - textPadShift; + + // size/shift are used during dragging + options['_' + axLetter + 'size'] = annSize; + options['_' + axLetter + 'shift'] = textShift; + } + + if(annotationIsOffscreen) { + annTextGroupInner.remove(); + return; + } + + var xShift = 0; + var yShift = 0; + + if(options.align !== 'left') { + xShift = (annWidth - textWidth) * (options.align === 'center' ? 0.5 : 1); + } + if(options.valign !== 'top') { + yShift = (annHeight - textHeight) * (options.valign === 'middle' ? 0.5 : 1); + } + + if(hasMathjax) { + mathjaxGroup.select('svg').attr({ + x: borderfull + xShift - 1, + y: borderfull + yShift + }) + .call(Drawing.setClipUrl, isSizeConstrained ? annClipID : null, gd); + } else { + var texty = borderfull + yShift - anntextBB.top; + var textx = borderfull + xShift - anntextBB.left; + + annText.call(svgTextUtils.positionText, textx, texty) + .call(Drawing.setClipUrl, isSizeConstrained ? annClipID : null, gd); + } + + annTextClip.select('rect').call(Drawing.setRect, borderfull, borderfull, + annWidth, annHeight); + + annTextBG.call(Drawing.setRect, borderwidth / 2, borderwidth / 2, + outerWidth - borderwidth, outerHeight - borderwidth); + + annTextGroupInner.call(Drawing.setTranslate, + Math.round(annPosPx.x.text - outerWidth / 2), + Math.round(annPosPx.y.text - outerHeight / 2)); + + /* + * rotate text and background + * we already calculated the text center position *as rotated* + * because we needed that for autoranging anyway, so now whether + * we have an arrow or not, we rotate about the text center. + */ + annTextGroup.attr({transform: 'rotate(' + textangle + ',' + + annPosPx.x.text + ',' + annPosPx.y.text + ')'}); + + /* + * add the arrow + * uses options[arrowwidth,arrowcolor,arrowhead] for styling + * dx and dy are normally zero, but when you are dragging the textbox + * while the head stays put, dx and dy are the pixel offsets + */ + var drawArrow = function(dx, dy) { + annGroup + .selectAll('.annotation-arrow-g') + .remove(); + + var headX = annPosPx.x.head; + var headY = annPosPx.y.head; + var tailX = annPosPx.x.tail + dx; + var tailY = annPosPx.y.tail + dy; + var textX = annPosPx.x.text + dx; + var textY = annPosPx.y.text + dy; + + // find the edge of the text box, where we'll start the arrow: + // create transform matrix to rotate the text box corners + var transform = Lib.rotationXYMatrix(textangle, textX, textY); + var applyTransform = Lib.apply2DTransform(transform); + var applyTransform2 = Lib.apply2DTransform2(transform); + + // calculate and transform bounding box + var width = +annTextBG.attr('width'); + var height = +annTextBG.attr('height'); + var xLeft = textX - 0.5 * width; + var xRight = xLeft + width; + var yTop = textY - 0.5 * height; + var yBottom = yTop + height; + var edges = [ + [xLeft, yTop, xLeft, yBottom], + [xLeft, yBottom, xRight, yBottom], + [xRight, yBottom, xRight, yTop], + [xRight, yTop, xLeft, yTop] + ].map(applyTransform2); + + // Remove the line if it ends inside the box. Use ray + // casting for rotated boxes: see which edges intersect a + // line from the arrowhead to far away and reduce with xor + // to get the parity of the number of intersections. + if(edges.reduce(function(a, x) { + return a ^ + !!Lib.segmentsIntersect(headX, headY, headX + 1e6, headY + 1e6, + x[0], x[1], x[2], x[3]); + }, false)) { + // no line or arrow - so quit drawArrow now + return; + } + + edges.forEach(function(x) { + var p = Lib.segmentsIntersect(tailX, tailY, headX, headY, + x[0], x[1], x[2], x[3]); + if(p) { + tailX = p.x; + tailY = p.y; + } + }); + + var strokewidth = options.arrowwidth; + var arrowColor = options.arrowcolor; + var arrowSide = options.arrowside; + + var arrowGroup = annGroup.append('g') + .style({opacity: Color.opacity(arrowColor)}) + .classed('annotation-arrow-g', true); + + var arrow = arrowGroup.append('path') + .attr('d', 'M' + tailX + ',' + tailY + 'L' + headX + ',' + headY) + .style('stroke-width', strokewidth + 'px') + .call(Color.stroke, Color.rgb(arrowColor)); + + drawArrowHead(arrow, arrowSide, options); + + // the arrow dragger is a small square right at the head, then a line to the tail, + // all expanded by a stroke width of 6px plus the arrow line width + if(edits.annotationPosition && arrow.node().parentNode && !subplotId) { + var arrowDragHeadX = headX; + var arrowDragHeadY = headY; + if(options.standoff) { + var arrowLength = Math.sqrt(Math.pow(headX - tailX, 2) + Math.pow(headY - tailY, 2)); + arrowDragHeadX += options.standoff * (tailX - headX) / arrowLength; + arrowDragHeadY += options.standoff * (tailY - headY) / arrowLength; + } + var arrowDrag = arrowGroup.append('path') + .classed('annotation-arrow', true) + .classed('anndrag', true) + .classed('cursor-move', true) + .attr({ + d: 'M3,3H-3V-3H3ZM0,0L' + (tailX - arrowDragHeadX) + ',' + (tailY - arrowDragHeadY), + transform: 'translate(' + arrowDragHeadX + ',' + arrowDragHeadY + ')' + }) + .style('stroke-width', (strokewidth + 6) + 'px') + .call(Color.stroke, 'rgba(0,0,0,0)') + .call(Color.fill, 'rgba(0,0,0,0)'); + + var annx0, anny0; + + // dragger for the arrow & head: translates the whole thing + // (head/tail/text) all together + dragElement.init({ + element: arrowDrag.node(), + gd: gd, + prepFn: function() { + var pos = Drawing.getTranslate(annTextGroupInner); + + annx0 = pos.x; + anny0 = pos.y; + if(xa && xa.autorange) { + modifyBase(xa._name + '.autorange', true); + } + if(ya && ya.autorange) { + modifyBase(ya._name + '.autorange', true); + } + }, + moveFn: function(dx, dy) { + var annxy0 = applyTransform(annx0, anny0); + var xcenter = annxy0[0] + dx; + var ycenter = annxy0[1] + dy; + annTextGroupInner.call(Drawing.setTranslate, xcenter, ycenter); + + modifyItem('x', xa ? + xa.p2r(xa.r2p(options.x) + dx) : + (options.x + (dx / gs.w))); + modifyItem('y', ya ? + ya.p2r(ya.r2p(options.y) + dy) : + (options.y - (dy / gs.h))); + + if(options.axref === options.xref) { + modifyItem('ax', xa.p2r(xa.r2p(options.ax) + dx)); + } + + if(options.ayref === options.yref) { + modifyItem('ay', ya.p2r(ya.r2p(options.ay) + dy)); + } + + arrowGroup.attr('transform', 'translate(' + dx + ',' + dy + ')'); + annTextGroup.attr({ + transform: 'rotate(' + textangle + ',' + + xcenter + ',' + ycenter + ')' + }); + }, + doneFn: function() { + Registry.call('_guiRelayout', gd, getUpdateObj()); + var notesBox = document.querySelector('.js-notes-box-panel'); + if(notesBox) notesBox.redraw(notesBox.selectedObj); + } + }); + } + }; + + if(options.showarrow) drawArrow(0, 0); + + // user dragging the annotation (text, not arrow) + if(editTextPosition) { + var baseTextTransform; + + // dragger for the textbox: if there's an arrow, just drag the + // textbox and tail, leave the head untouched + dragElement.init({ + element: annTextGroupInner.node(), + gd: gd, + prepFn: function() { + baseTextTransform = annTextGroup.attr('transform'); + }, + moveFn: function(dx, dy) { + var csr = 'pointer'; + if(options.showarrow) { + if(options.axref === options.xref) { + modifyItem('ax', xa.p2r(xa.r2p(options.ax) + dx)); + } else { + modifyItem('ax', options.ax + dx); + } + + if(options.ayref === options.yref) { + modifyItem('ay', ya.p2r(ya.r2p(options.ay) + dy)); + } else { + modifyItem('ay', options.ay + dy); + } + + drawArrow(dx, dy); + } else if(!subplotId) { + var xUpdate, yUpdate; + if(xa) { + xUpdate = xa.p2r(xa.r2p(options.x) + dx); + } else { + var widthFraction = options._xsize / gs.w; + var xLeft = options.x + (options._xshift - options.xshift) / gs.w - widthFraction / 2; + + xUpdate = dragElement.align(xLeft + dx / gs.w, + widthFraction, 0, 1, options.xanchor); + } + + if(ya) { + yUpdate = ya.p2r(ya.r2p(options.y) + dy); + } else { + var heightFraction = options._ysize / gs.h; + var yBottom = options.y - (options._yshift + options.yshift) / gs.h - heightFraction / 2; + + yUpdate = dragElement.align(yBottom - dy / gs.h, + heightFraction, 0, 1, options.yanchor); + } + modifyItem('x', xUpdate); + modifyItem('y', yUpdate); + if(!xa || !ya) { + csr = dragElement.getCursor( + xa ? 0.5 : xUpdate, + ya ? 0.5 : yUpdate, + options.xanchor, options.yanchor + ); + } + } else return; + + annTextGroup.attr({ + transform: 'translate(' + dx + ',' + dy + ')' + baseTextTransform + }); + + setCursor(annTextGroupInner, csr); + }, + clickFn: function(_, initialEvent) { + if(options.captureevents) { + gd.emit('plotly_clickannotation', makeEventData(initialEvent)); + } + }, + doneFn: function() { + setCursor(annTextGroupInner); + Registry.call('_guiRelayout', gd, getUpdateObj()); + var notesBox = document.querySelector('.js-notes-box-panel'); + if(notesBox) notesBox.redraw(notesBox.selectedObj); + } + }); + } + } + + if(edits.annotationText) { + annText.call(svgTextUtils.makeEditable, {delegate: annTextGroupInner, gd: gd}) + .call(textLayout) + .on('edit', function(_text) { + options.text = _text; + + this.call(textLayout); + + modifyItem('text', _text); + + if(xa && xa.autorange) { + modifyBase(xa._name + '.autorange', true); + } + if(ya && ya.autorange) { + modifyBase(ya._name + '.autorange', true); + } + + Registry.call('_guiRelayout', gd, getUpdateObj()); + }); + } else annText.call(textLayout); +} + +},{"../../lib":169,"../../lib/setcursor":188,"../../lib/svg_text_utils":190,"../../plot_api/plot_template":203,"../../plots/cartesian/axes":213,"../../plots/plots":245,"../../registry":258,"../color":51,"../dragelement":69,"../drawing":72,"../fx":89,"./draw_arrow_head":43,"d3":16}],43:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var d3 = _dereq_('d3'); + +var Color = _dereq_('../color'); + +var ARROWPATHS = _dereq_('./arrow_paths'); + +/** + * Add arrowhead(s) to a path or line element + * + * @param {d3.selection} el3: a d3-selected line or path element + * + * @param {string} ends: 'none', 'start', 'end', or 'start+end' for which ends get arrowheads + * + * @param {object} options: style information. Must have all the following: + * @param {number} options.arrowhead: end head style - see ./arrow_paths + * @param {number} options.startarrowhead: start head style - see ./arrow_paths + * @param {number} options.arrowsize: relative size of the end head vs line width + * @param {number} options.startarrowsize: relative size of the start head vs line width + * @param {number} options.standoff: distance in px to move the end arrow point from its target + * @param {number} options.startstandoff: distance in px to move the start arrow point from its target + * @param {number} options.arrowwidth: width of the arrow line + * @param {string} options.arrowcolor: color of the arrow line, for the head to match + * Note that the opacity of this color is ignored, as it's assumed the container + * of both the line and head has opacity applied to it so there isn't greater opacity + * where they overlap. + */ +module.exports = function drawArrowHead(el3, ends, options) { + var el = el3.node(); + var headStyle = ARROWPATHS[options.arrowhead || 0]; + var startHeadStyle = ARROWPATHS[options.startarrowhead || 0]; + var scale = (options.arrowwidth || 1) * (options.arrowsize || 1); + var startScale = (options.arrowwidth || 1) * (options.startarrowsize || 1); + var doStart = ends.indexOf('start') >= 0; + var doEnd = ends.indexOf('end') >= 0; + var backOff = headStyle.backoff * scale + options.standoff; + var startBackOff = startHeadStyle.backoff * startScale + options.startstandoff; + + var start, end, startRot, endRot; + + if(el.nodeName === 'line') { + start = {x: +el3.attr('x1'), y: +el3.attr('y1')}; + end = {x: +el3.attr('x2'), y: +el3.attr('y2')}; + + var dx = start.x - end.x; + var dy = start.y - end.y; + + startRot = Math.atan2(dy, dx); + endRot = startRot + Math.PI; + if(backOff && startBackOff) { + if(backOff + startBackOff > Math.sqrt(dx * dx + dy * dy)) { + hideLine(); + return; + } + } + + if(backOff) { + if(backOff * backOff > dx * dx + dy * dy) { + hideLine(); + return; + } + var backOffX = backOff * Math.cos(startRot); + var backOffY = backOff * Math.sin(startRot); + + end.x += backOffX; + end.y += backOffY; + el3.attr({x2: end.x, y2: end.y}); + } + + if(startBackOff) { + if(startBackOff * startBackOff > dx * dx + dy * dy) { + hideLine(); + return; + } + var startBackOffX = startBackOff * Math.cos(startRot); + var startbackOffY = startBackOff * Math.sin(startRot); + + start.x -= startBackOffX; + start.y -= startbackOffY; + el3.attr({x1: start.x, y1: start.y}); + } + } else if(el.nodeName === 'path') { + var pathlen = el.getTotalLength(); + // using dash to hide the backOff region of the path. + // if we ever allow dash for the arrow we'll have to + // do better than this hack... maybe just manually + // combine the two + var dashArray = ''; + + if(pathlen < backOff + startBackOff) { + hideLine(); + return; + } + + + var start0 = el.getPointAtLength(0); + var dstart = el.getPointAtLength(0.1); + + startRot = Math.atan2(start0.y - dstart.y, start0.x - dstart.x); + start = el.getPointAtLength(Math.min(startBackOff, pathlen)); + + dashArray = '0px,' + startBackOff + 'px,'; + + var end0 = el.getPointAtLength(pathlen); + var dend = el.getPointAtLength(pathlen - 0.1); + + endRot = Math.atan2(end0.y - dend.y, end0.x - dend.x); + end = el.getPointAtLength(Math.max(0, pathlen - backOff)); + + var shortening = dashArray ? startBackOff + backOff : backOff; + dashArray += (pathlen - shortening) + 'px,' + pathlen + 'px'; + + el3.style('stroke-dasharray', dashArray); + } + + function hideLine() { el3.style('stroke-dasharray', '0px,100px'); } + + function drawhead(arrowHeadStyle, p, rot, arrowScale) { + if(!arrowHeadStyle.path) return; + if(arrowHeadStyle.noRotate) rot = 0; + + d3.select(el.parentNode).append('path') + .attr({ + 'class': el3.attr('class'), + d: arrowHeadStyle.path, + transform: + 'translate(' + p.x + ',' + p.y + ')' + + (rot ? 'rotate(' + (rot * 180 / Math.PI) + ')' : '') + + 'scale(' + arrowScale + ')' + }) + .style({ + fill: Color.rgb(options.arrowcolor), + 'stroke-width': 0 + }); + } + + if(doStart) drawhead(startHeadStyle, start, startRot, startScale); + if(doEnd) drawhead(headStyle, end, endRot, scale); +}; + +},{"../color":51,"./arrow_paths":35,"d3":16}],44:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var drawModule = _dereq_('./draw'); +var clickModule = _dereq_('./click'); + +module.exports = { + moduleType: 'component', + name: 'annotations', + + layoutAttributes: _dereq_('./attributes'), + supplyLayoutDefaults: _dereq_('./defaults'), + includeBasePlot: _dereq_('../../plots/cartesian/include_components')('annotations'), + + calcAutorange: _dereq_('./calc_autorange'), + draw: drawModule.draw, + drawOne: drawModule.drawOne, + drawRaw: drawModule.drawRaw, + + hasClickToShow: clickModule.hasClickToShow, + onClick: clickModule.onClick, + + convertCoords: _dereq_('./convert_coords') +}; + +},{"../../plots/cartesian/include_components":223,"./attributes":36,"./calc_autorange":37,"./click":38,"./convert_coords":40,"./defaults":41,"./draw":42}],45:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var annAttrs = _dereq_('../annotations/attributes'); +var overrideAll = _dereq_('../../plot_api/edit_types').overrideAll; +var templatedArray = _dereq_('../../plot_api/plot_template').templatedArray; + +module.exports = overrideAll(templatedArray('annotation', { + visible: annAttrs.visible, + x: { + valType: 'any', + + + }, + y: { + valType: 'any', + + + }, + z: { + valType: 'any', + + + }, + ax: { + valType: 'number', + + + }, + ay: { + valType: 'number', + + + }, + + xanchor: annAttrs.xanchor, + xshift: annAttrs.xshift, + yanchor: annAttrs.yanchor, + yshift: annAttrs.yshift, + + text: annAttrs.text, + textangle: annAttrs.textangle, + font: annAttrs.font, + width: annAttrs.width, + height: annAttrs.height, + opacity: annAttrs.opacity, + align: annAttrs.align, + valign: annAttrs.valign, + bgcolor: annAttrs.bgcolor, + bordercolor: annAttrs.bordercolor, + borderpad: annAttrs.borderpad, + borderwidth: annAttrs.borderwidth, + showarrow: annAttrs.showarrow, + arrowcolor: annAttrs.arrowcolor, + arrowhead: annAttrs.arrowhead, + startarrowhead: annAttrs.startarrowhead, + arrowside: annAttrs.arrowside, + arrowsize: annAttrs.arrowsize, + startarrowsize: annAttrs.startarrowsize, + arrowwidth: annAttrs.arrowwidth, + standoff: annAttrs.standoff, + startstandoff: annAttrs.startstandoff, + hovertext: annAttrs.hovertext, + hoverlabel: annAttrs.hoverlabel, + captureevents: annAttrs.captureevents, + + // maybes later? + // clicktoshow: annAttrs.clicktoshow, + // xclick: annAttrs.xclick, + // yclick: annAttrs.yclick, + + // not needed! + // axref: 'pixel' + // ayref: 'pixel' + // xref: 'x' + // yref: 'y + // zref: 'z' +}), 'calc', 'from-root'); + +},{"../../plot_api/edit_types":196,"../../plot_api/plot_template":203,"../annotations/attributes":36}],46:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var Axes = _dereq_('../../plots/cartesian/axes'); + +module.exports = function convert(scene) { + var fullSceneLayout = scene.fullSceneLayout; + var anns = fullSceneLayout.annotations; + + for(var i = 0; i < anns.length; i++) { + mockAnnAxes(anns[i], scene); + } + + scene.fullLayout._infolayer + .selectAll('.annotation-' + scene.id) + .remove(); +}; + +function mockAnnAxes(ann, scene) { + var fullSceneLayout = scene.fullSceneLayout; + var domain = fullSceneLayout.domain; + var size = scene.fullLayout._size; + + var base = { + // this gets fill in on render + pdata: null, + + // to get setConvert to not execute cleanly + type: 'linear', + + // don't try to update them on `editable: true` + autorange: false, + + // set infinite range so that annotation draw routine + // does not try to remove 'outside-range' annotations, + // this case is handled in the render loop + range: [-Infinity, Infinity] + }; + + ann._xa = {}; + Lib.extendFlat(ann._xa, base); + Axes.setConvert(ann._xa); + ann._xa._offset = size.l + domain.x[0] * size.w; + ann._xa.l2p = function() { + return 0.5 * (1 + ann._pdata[0] / ann._pdata[3]) * size.w * (domain.x[1] - domain.x[0]); + }; + + ann._ya = {}; + Lib.extendFlat(ann._ya, base); + Axes.setConvert(ann._ya); + ann._ya._offset = size.t + (1 - domain.y[1]) * size.h; + ann._ya.l2p = function() { + return 0.5 * (1 - ann._pdata[1] / ann._pdata[3]) * size.h * (domain.y[1] - domain.y[0]); + }; +} + +},{"../../lib":169,"../../plots/cartesian/axes":213}],47:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var Axes = _dereq_('../../plots/cartesian/axes'); +var handleArrayContainerDefaults = _dereq_('../../plots/array_container_defaults'); +var handleAnnotationCommonDefaults = _dereq_('../annotations/common_defaults'); +var attributes = _dereq_('./attributes'); + +module.exports = function handleDefaults(sceneLayoutIn, sceneLayoutOut, opts) { + handleArrayContainerDefaults(sceneLayoutIn, sceneLayoutOut, { + name: 'annotations', + handleItemDefaults: handleAnnotationDefaults, + fullLayout: opts.fullLayout + }); +}; + +function handleAnnotationDefaults(annIn, annOut, sceneLayout, opts) { + function coerce(attr, dflt) { + return Lib.coerce(annIn, annOut, attributes, attr, dflt); + } + + function coercePosition(axLetter) { + var axName = axLetter + 'axis'; + + // mock in such way that getFromId grabs correct 3D axis + var gdMock = { _fullLayout: {} }; + gdMock._fullLayout[axName] = sceneLayout[axName]; + + return Axes.coercePosition(annOut, gdMock, coerce, axLetter, axLetter, 0.5); + } + + + var visible = coerce('visible'); + if(!visible) return; + + handleAnnotationCommonDefaults(annIn, annOut, opts.fullLayout, coerce); + + coercePosition('x'); + coercePosition('y'); + coercePosition('z'); + + // if you have one coordinate you should all three + Lib.noneOrAll(annIn, annOut, ['x', 'y', 'z']); + + // hard-set here for completeness + annOut.xref = 'x'; + annOut.yref = 'y'; + annOut.zref = 'z'; + + coerce('xanchor'); + coerce('yanchor'); + coerce('xshift'); + coerce('yshift'); + + if(annOut.showarrow) { + annOut.axref = 'pixel'; + annOut.ayref = 'pixel'; + + // TODO maybe default values should be bigger than the 2D case? + coerce('ax', -10); + coerce('ay', -30); + + // if you have one part of arrow length you should have both + Lib.noneOrAll(annIn, annOut, ['ax', 'ay']); + } +} + +},{"../../lib":169,"../../plots/array_container_defaults":209,"../../plots/cartesian/axes":213,"../annotations/common_defaults":39,"./attributes":45}],48:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var drawRaw = _dereq_('../annotations/draw').drawRaw; +var project = _dereq_('../../plots/gl3d/project'); +var axLetters = ['x', 'y', 'z']; + +module.exports = function draw(scene) { + var fullSceneLayout = scene.fullSceneLayout; + var dataScale = scene.dataScale; + var anns = fullSceneLayout.annotations; + + for(var i = 0; i < anns.length; i++) { + var ann = anns[i]; + var annotationIsOffscreen = false; + + for(var j = 0; j < 3; j++) { + var axLetter = axLetters[j]; + var pos = ann[axLetter]; + var ax = fullSceneLayout[axLetter + 'axis']; + var posFraction = ax.r2fraction(pos); + + if(posFraction < 0 || posFraction > 1) { + annotationIsOffscreen = true; + break; + } + } + + if(annotationIsOffscreen) { + scene.fullLayout._infolayer + .select('.annotation-' + scene.id + '[data-index="' + i + '"]') + .remove(); + } else { + ann._pdata = project(scene.glplot.cameraParams, [ + fullSceneLayout.xaxis.r2l(ann.x) * dataScale[0], + fullSceneLayout.yaxis.r2l(ann.y) * dataScale[1], + fullSceneLayout.zaxis.r2l(ann.z) * dataScale[2] + ]); + + drawRaw(scene.graphDiv, ann, i, scene.id, ann._xa, ann._ya); + } + } +}; + +},{"../../plots/gl3d/project":242,"../annotations/draw":42}],49:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Registry = _dereq_('../../registry'); +var Lib = _dereq_('../../lib'); + +module.exports = { + moduleType: 'component', + name: 'annotations3d', + + schema: { + subplots: { + scene: {annotations: _dereq_('./attributes')} + } + }, + + layoutAttributes: _dereq_('./attributes'), + handleDefaults: _dereq_('./defaults'), + includeBasePlot: includeGL3D, + + convert: _dereq_('./convert'), + draw: _dereq_('./draw') +}; + +function includeGL3D(layoutIn, layoutOut) { + var GL3D = Registry.subplotsRegistry.gl3d; + if(!GL3D) return; + + var attrRegex = GL3D.attrRegex; + + var keys = Object.keys(layoutIn); + for(var i = 0; i < keys.length; i++) { + var k = keys[i]; + if(attrRegex.test(k) && (layoutIn[k].annotations || []).length) { + Lib.pushUnique(layoutOut._basePlotModules, GL3D); + Lib.pushUnique(layoutOut._subplots.gl3d, k); + } + } +} + +},{"../../lib":169,"../../registry":258,"./attributes":45,"./convert":46,"./defaults":47,"./draw":48}],50:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + + +// IMPORTANT - default colors should be in hex for compatibility +exports.defaults = [ + '#1f77b4', // muted blue + '#ff7f0e', // safety orange + '#2ca02c', // cooked asparagus green + '#d62728', // brick red + '#9467bd', // muted purple + '#8c564b', // chestnut brown + '#e377c2', // raspberry yogurt pink + '#7f7f7f', // middle gray + '#bcbd22', // curry yellow-green + '#17becf' // blue-teal +]; + +exports.defaultLine = '#444'; + +exports.lightLine = '#eee'; + +exports.background = '#fff'; + +exports.borderLine = '#BEC8D9'; + +// with axis.color and Color.interp we aren't using lightLine +// itself anymore, instead interpolating between axis.color +// and the background color using tinycolor.mix. lightFraction +// gives back exactly lightLine if the other colors are defaults. +exports.lightFraction = 100 * (0xe - 0x4) / (0xf - 0x4); + +},{}],51:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var tinycolor = _dereq_('tinycolor2'); +var isNumeric = _dereq_('fast-isnumeric'); + +var color = module.exports = {}; + +var colorAttrs = _dereq_('./attributes'); +color.defaults = colorAttrs.defaults; +var defaultLine = color.defaultLine = colorAttrs.defaultLine; +color.lightLine = colorAttrs.lightLine; +var background = color.background = colorAttrs.background; + +/* + * tinyRGB: turn a tinycolor into an rgb string, but + * unlike the built-in tinycolor.toRgbString this never includes alpha + */ +color.tinyRGB = function(tc) { + var c = tc.toRgb(); + return 'rgb(' + Math.round(c.r) + ', ' + + Math.round(c.g) + ', ' + Math.round(c.b) + ')'; +}; + +color.rgb = function(cstr) { return color.tinyRGB(tinycolor(cstr)); }; + +color.opacity = function(cstr) { return cstr ? tinycolor(cstr).getAlpha() : 0; }; + +color.addOpacity = function(cstr, op) { + var c = tinycolor(cstr).toRgb(); + return 'rgba(' + Math.round(c.r) + ', ' + + Math.round(c.g) + ', ' + Math.round(c.b) + ', ' + op + ')'; +}; + +// combine two colors into one apparent color +// if back has transparency or is missing, +// color.background is assumed behind it +color.combine = function(front, back) { + var fc = tinycolor(front).toRgb(); + if(fc.a === 1) return tinycolor(front).toRgbString(); + + var bc = tinycolor(back || background).toRgb(); + var bcflat = bc.a === 1 ? bc : { + r: 255 * (1 - bc.a) + bc.r * bc.a, + g: 255 * (1 - bc.a) + bc.g * bc.a, + b: 255 * (1 - bc.a) + bc.b * bc.a + }; + var fcflat = { + r: bcflat.r * (1 - fc.a) + fc.r * fc.a, + g: bcflat.g * (1 - fc.a) + fc.g * fc.a, + b: bcflat.b * (1 - fc.a) + fc.b * fc.a + }; + return tinycolor(fcflat).toRgbString(); +}; + +/* + * Create a color that contrasts with cstr. + * + * If cstr is a dark color, we lighten it; if it's light, we darken. + * + * If lightAmount / darkAmount are used, we adjust by these percentages, + * otherwise we go all the way to white or black. + */ +color.contrast = function(cstr, lightAmount, darkAmount) { + var tc = tinycolor(cstr); + + if(tc.getAlpha() !== 1) tc = tinycolor(color.combine(cstr, background)); + + var newColor = tc.isDark() ? + (lightAmount ? tc.lighten(lightAmount) : background) : + (darkAmount ? tc.darken(darkAmount) : defaultLine); + + return newColor.toString(); +}; + +color.stroke = function(s, c) { + var tc = tinycolor(c); + s.style({'stroke': color.tinyRGB(tc), 'stroke-opacity': tc.getAlpha()}); +}; + +color.fill = function(s, c) { + var tc = tinycolor(c); + s.style({ + 'fill': color.tinyRGB(tc), + 'fill-opacity': tc.getAlpha() + }); +}; + +// search container for colors with the deprecated rgb(fractions) format +// and convert them to rgb(0-255 values) +color.clean = function(container) { + if(!container || typeof container !== 'object') return; + + var keys = Object.keys(container); + var i, j, key, val; + + for(i = 0; i < keys.length; i++) { + key = keys[i]; + val = container[key]; + + if(key.substr(key.length - 5) === 'color') { + // only sanitize keys that end in "color" or "colorscale" + + if(Array.isArray(val)) { + for(j = 0; j < val.length; j++) val[j] = cleanOne(val[j]); + } else container[key] = cleanOne(val); + } else if(key.substr(key.length - 10) === 'colorscale' && Array.isArray(val)) { + // colorscales have the format [[0, color1], [frac, color2], ... [1, colorN]] + + for(j = 0; j < val.length; j++) { + if(Array.isArray(val[j])) val[j][1] = cleanOne(val[j][1]); + } + } else if(Array.isArray(val)) { + // recurse into arrays of objects, and plain objects + + var el0 = val[0]; + if(!Array.isArray(el0) && el0 && typeof el0 === 'object') { + for(j = 0; j < val.length; j++) color.clean(val[j]); + } + } else if(val && typeof val === 'object') color.clean(val); + } +}; + +function cleanOne(val) { + if(isNumeric(val) || typeof val !== 'string') return val; + + var valTrim = val.trim(); + if(valTrim.substr(0, 3) !== 'rgb') return val; + + var match = valTrim.match(/^rgba?\s*\(([^()]*)\)$/); + if(!match) return val; + + var parts = match[1].trim().split(/\s*[\s,]\s*/); + var rgba = valTrim.charAt(3) === 'a' && parts.length === 4; + if(!rgba && parts.length !== 3) return val; + + for(var i = 0; i < parts.length; i++) { + if(!parts[i].length) return val; + parts[i] = Number(parts[i]); + + if(!(parts[i] >= 0)) { + // all parts must be non-negative numbers + + return val; + } + + if(i === 3) { + // alpha>1 gets clipped to 1 + + if(parts[i] > 1) parts[i] = 1; + } else if(parts[i] >= 1) { + // r, g, b must be < 1 (ie 1 itself is not allowed) + + return val; + } + } + + var rgbStr = Math.round(parts[0] * 255) + ', ' + + Math.round(parts[1] * 255) + ', ' + + Math.round(parts[2] * 255); + + if(rgba) return 'rgba(' + rgbStr + ', ' + parts[3] + ')'; + return 'rgb(' + rgbStr + ')'; +} + +},{"./attributes":50,"fast-isnumeric":18,"tinycolor2":34}],52:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var axesAttrs = _dereq_('../../plots/cartesian/layout_attributes'); +var fontAttrs = _dereq_('../../plots/font_attributes'); +var extendFlat = _dereq_('../../lib/extend').extendFlat; +var overrideAll = _dereq_('../../plot_api/edit_types').overrideAll; + + +module.exports = overrideAll({ +// TODO: only right is supported currently +// orient: { +// valType: 'enumerated', +// +// values: ['left', 'right', 'top', 'bottom'], +// dflt: 'right', +// +// }, + thicknessmode: { + valType: 'enumerated', + values: ['fraction', 'pixels'], + + dflt: 'pixels', + + }, + thickness: { + valType: 'number', + + min: 0, + dflt: 30, + + }, + lenmode: { + valType: 'enumerated', + values: ['fraction', 'pixels'], + + dflt: 'fraction', + + }, + len: { + valType: 'number', + min: 0, + dflt: 1, + + + }, + x: { + valType: 'number', + dflt: 1.02, + min: -2, + max: 3, + + + }, + xanchor: { + valType: 'enumerated', + values: ['left', 'center', 'right'], + dflt: 'left', + + + }, + xpad: { + valType: 'number', + + min: 0, + dflt: 10, + + }, + y: { + valType: 'number', + + dflt: 0.5, + min: -2, + max: 3, + + }, + yanchor: { + valType: 'enumerated', + values: ['top', 'middle', 'bottom'], + + dflt: 'middle', + + }, + ypad: { + valType: 'number', + + min: 0, + dflt: 10, + + }, + // a possible line around the bar itself + outlinecolor: axesAttrs.linecolor, + outlinewidth: axesAttrs.linewidth, + // Should outlinewidth have {dflt: 0} ? + // another possible line outside the padding and tick labels + bordercolor: axesAttrs.linecolor, + borderwidth: { + valType: 'number', + + min: 0, + dflt: 0, + + }, + bgcolor: { + valType: 'color', + + dflt: 'rgba(0,0,0,0)', + + }, + // tick and title properties named and function exactly as in axes + tickmode: axesAttrs.tickmode, + nticks: axesAttrs.nticks, + tick0: axesAttrs.tick0, + dtick: axesAttrs.dtick, + tickvals: axesAttrs.tickvals, + ticktext: axesAttrs.ticktext, + ticks: extendFlat({}, axesAttrs.ticks, {dflt: ''}), + ticklen: axesAttrs.ticklen, + tickwidth: axesAttrs.tickwidth, + tickcolor: axesAttrs.tickcolor, + showticklabels: axesAttrs.showticklabels, + tickfont: fontAttrs({ + + }), + tickangle: axesAttrs.tickangle, + tickformat: axesAttrs.tickformat, + tickformatstops: axesAttrs.tickformatstops, + tickprefix: axesAttrs.tickprefix, + showtickprefix: axesAttrs.showtickprefix, + ticksuffix: axesAttrs.ticksuffix, + showticksuffix: axesAttrs.showticksuffix, + separatethousands: axesAttrs.separatethousands, + exponentformat: axesAttrs.exponentformat, + showexponent: axesAttrs.showexponent, + title: { + text: { + valType: 'string', + + + }, + font: fontAttrs({ + + }), + side: { + valType: 'enumerated', + values: ['right', 'top', 'bottom'], + + dflt: 'top', + + } + }, + + _deprecated: { + title: { + valType: 'string', + + + }, + titlefont: fontAttrs({ + + }), + titleside: { + valType: 'enumerated', + values: ['right', 'top', 'bottom'], + + dflt: 'top', + + } + } +}, 'colorbars', 'from-root'); + +},{"../../lib/extend":164,"../../plot_api/edit_types":196,"../../plots/cartesian/layout_attributes":225,"../../plots/font_attributes":239}],53:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + cn: { + colorbar: 'colorbar', + cbbg: 'cbbg', + cbfill: 'cbfill', + cbfills: 'cbfills', + cbline: 'cbline', + cblines: 'cblines', + cbaxis: 'cbaxis', + cbtitleunshift: 'cbtitleunshift', + cbtitle: 'cbtitle', + cboutline: 'cboutline', + crisp: 'crisp', + jsPlaceholder: 'js-placeholder' + } +}; + +},{}],54:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Lib = _dereq_('../../lib'); +var Template = _dereq_('../../plot_api/plot_template'); + +var handleTickValueDefaults = _dereq_('../../plots/cartesian/tick_value_defaults'); +var handleTickMarkDefaults = _dereq_('../../plots/cartesian/tick_mark_defaults'); +var handleTickLabelDefaults = _dereq_('../../plots/cartesian/tick_label_defaults'); + +var attributes = _dereq_('./attributes'); + +module.exports = function colorbarDefaults(containerIn, containerOut, layout) { + var colorbarOut = Template.newContainer(containerOut, 'colorbar'); + var colorbarIn = containerIn.colorbar || {}; + + function coerce(attr, dflt) { + return Lib.coerce(colorbarIn, colorbarOut, attributes, attr, dflt); + } + + var thicknessmode = coerce('thicknessmode'); + coerce('thickness', (thicknessmode === 'fraction') ? + 30 / (layout.width - layout.margin.l - layout.margin.r) : + 30 + ); + + var lenmode = coerce('lenmode'); + coerce('len', (lenmode === 'fraction') ? + 1 : + layout.height - layout.margin.t - layout.margin.b + ); + + coerce('x'); + coerce('xanchor'); + coerce('xpad'); + coerce('y'); + coerce('yanchor'); + coerce('ypad'); + Lib.noneOrAll(colorbarIn, colorbarOut, ['x', 'y']); + + coerce('outlinecolor'); + coerce('outlinewidth'); + coerce('bordercolor'); + coerce('borderwidth'); + coerce('bgcolor'); + + handleTickValueDefaults(colorbarIn, colorbarOut, coerce, 'linear'); + + var opts = {outerTicks: false, font: layout.font}; + handleTickLabelDefaults(colorbarIn, colorbarOut, coerce, 'linear', opts); + handleTickMarkDefaults(colorbarIn, colorbarOut, coerce, 'linear', opts); + + coerce('title.text', layout._dfltTitle.colorbar); + Lib.coerceFont(coerce, 'title.font', layout.font); + coerce('title.side'); +}; + +},{"../../lib":169,"../../plot_api/plot_template":203,"../../plots/cartesian/tick_label_defaults":232,"../../plots/cartesian/tick_mark_defaults":233,"../../plots/cartesian/tick_value_defaults":234,"./attributes":52}],55:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); +var tinycolor = _dereq_('tinycolor2'); + +var Plots = _dereq_('../../plots/plots'); +var Registry = _dereq_('../../registry'); +var Axes = _dereq_('../../plots/cartesian/axes'); +var dragElement = _dereq_('../dragelement'); +var Lib = _dereq_('../../lib'); +var extendFlat = _dereq_('../../lib/extend').extendFlat; +var setCursor = _dereq_('../../lib/setcursor'); +var Drawing = _dereq_('../drawing'); +var Color = _dereq_('../color'); +var Titles = _dereq_('../titles'); +var svgTextUtils = _dereq_('../../lib/svg_text_utils'); +var flipScale = _dereq_('../colorscale/helpers').flipScale; + +var handleAxisDefaults = _dereq_('../../plots/cartesian/axis_defaults'); +var handleAxisPositionDefaults = _dereq_('../../plots/cartesian/position_defaults'); +var axisLayoutAttrs = _dereq_('../../plots/cartesian/layout_attributes'); + +var alignmentConstants = _dereq_('../../constants/alignment'); +var LINE_SPACING = alignmentConstants.LINE_SPACING; +var FROM_TL = alignmentConstants.FROM_TL; +var FROM_BR = alignmentConstants.FROM_BR; + +var cn = _dereq_('./constants').cn; + +function draw(gd) { + var fullLayout = gd._fullLayout; + + var colorBars = fullLayout._infolayer + .selectAll('g.' + cn.colorbar) + .data(makeColorBarData(gd), function(opts) { return opts._id; }); + + colorBars.enter().append('g') + .attr('class', function(opts) { return opts._id; }) + .classed(cn.colorbar, true); + + colorBars.each(function(opts) { + var g = d3.select(this); + + Lib.ensureSingle(g, 'rect', cn.cbbg); + Lib.ensureSingle(g, 'g', cn.cbfills); + Lib.ensureSingle(g, 'g', cn.cblines); + Lib.ensureSingle(g, 'g', cn.cbaxis, function(s) { s.classed(cn.crisp, true); }); + Lib.ensureSingle(g, 'g', cn.cbtitleunshift, function(s) { s.append('g').classed(cn.cbtitle, true); }); + Lib.ensureSingle(g, 'rect', cn.cboutline); + + var done = drawColorBar(g, opts, gd); + if(done && done.then) (gd._promises || []).push(done); + + if(gd._context.edits.colorbarPosition) { + makeEditable(g, opts, gd); + } + }); + + colorBars.exit() + .each(function(opts) { Plots.autoMargin(gd, opts._id); }) + .remove(); + + colorBars.order(); +} + +function makeColorBarData(gd) { + var fullLayout = gd._fullLayout; + var calcdata = gd.calcdata; + var out = []; + + // single out item + var opts; + // colorbar attr parent container + var cont; + // trace attr container + var trace; + // colorbar options + var cbOpt; + + function initOpts(opts) { + return extendFlat(opts, { + // fillcolor can be a d3 scale, domain is z values, range is colors + // or leave it out for no fill, + // or set to a string constant for single-color fill + _fillcolor: null, + // line.color has the same options as fillcolor + _line: {color: null, width: null, dash: null}, + // levels of lines to draw. + // note that this DOES NOT determine the extent of the bar + // that's given by the domain of fillcolor + // (or line.color if no fillcolor domain) + _levels: {start: null, end: null, size: null}, + // separate fill levels (for example, heatmap coloring of a + // contour map) if this is omitted, fillcolors will be + // evaluated halfway between levels + _filllevels: null, + // for continuous colorscales: fill with a gradient instead of explicit levels + // value should be the colorscale [[0, c0], [v1, c1], ..., [1, cEnd]] + _fillgradient: null, + // when using a gradient, we need the data range specified separately + _zrange: null + }); + } + + function calcOpts() { + if(typeof cbOpt.calc === 'function') { + cbOpt.calc(gd, trace, opts); + } else { + opts._fillgradient = cont.reversescale ? + flipScale(cont.colorscale) : + cont.colorscale; + opts._zrange = [cont[cbOpt.min], cont[cbOpt.max]]; + } + } + + for(var i = 0; i < calcdata.length; i++) { + var cd = calcdata[i]; + trace = cd[0].trace; + var moduleOpts = trace._module.colorbar; + + if(trace.visible === true && moduleOpts) { + var allowsMultiplotCbs = Array.isArray(moduleOpts); + var cbOpts = allowsMultiplotCbs ? moduleOpts : [moduleOpts]; + + for(var j = 0; j < cbOpts.length; j++) { + cbOpt = cbOpts[j]; + var contName = cbOpt.container; + cont = contName ? trace[contName] : trace; + + if(cont && cont.showscale) { + opts = initOpts(cont.colorbar); + opts._id = 'cb' + trace.uid + (allowsMultiplotCbs && contName ? '-' + contName : ''); + opts._traceIndex = trace.index; + opts._propPrefix = (contName ? contName + '.' : '') + 'colorbar.'; + opts._meta = trace._meta; + calcOpts(); + out.push(opts); + } + } + } + } + + for(var k in fullLayout._colorAxes) { + cont = fullLayout[k]; + + if(cont.showscale) { + var colorAxOpts = fullLayout._colorAxes[k]; + + opts = initOpts(cont.colorbar); + opts._id = 'cb' + k; + opts._propPrefix = k + '.colorbar.'; + opts._meta = fullLayout._meta; + + cbOpt = {min: 'cmin', max: 'cmax'}; + if(colorAxOpts[0] !== 'heatmap') { + trace = colorAxOpts[1]; + cbOpt.calc = trace._module.colorbar.calc; + } + + calcOpts(); + out.push(opts); + } + } + + return out; +} + +function drawColorBar(g, opts, gd) { + var fullLayout = gd._fullLayout; + var gs = fullLayout._size; + + var fillColor = opts._fillcolor; + var line = opts._line; + var title = opts.title; + var titleSide = title.side; + + var zrange = opts._zrange || + d3.extent((typeof fillColor === 'function' ? fillColor : line.color).domain()); + + var lineColormap = typeof line.color === 'function' ? + line.color : + function() { return line.color; }; + var fillColormap = typeof fillColor === 'function' ? + fillColor : + function() { return fillColor; }; + + var levelsIn = opts._levels; + var levelsOut = calcLevels(gd, opts, zrange); + var fillLevels = levelsOut.fill; + var lineLevels = levelsOut.line; + + // we calculate pixel sizes based on the specified graph size, + // not the actual (in case something pushed the margins around) + // which is a little odd but avoids an odd iterative effect + // when the colorbar itself is pushing the margins. + // but then the fractional size is calculated based on the + // actual graph size, so that the axes will size correctly. + var thickPx = Math.round(opts.thickness * (opts.thicknessmode === 'fraction' ? gs.w : 1)); + var thickFrac = thickPx / gs.w; + var lenPx = Math.round(opts.len * (opts.lenmode === 'fraction' ? gs.h : 1)); + var lenFrac = lenPx / gs.h; + var xpadFrac = opts.xpad / gs.w; + var yExtraPx = (opts.borderwidth + opts.outlinewidth) / 2; + var ypadFrac = opts.ypad / gs.h; + + // x positioning: do it initially just for left anchor, + // then fix at the end (since we don't know the width yet) + var xLeft = Math.round(opts.x * gs.w + opts.xpad); + // for dragging... this is getting a little muddled... + var xLeftFrac = opts.x - thickFrac * ({middle: 0.5, right: 1}[opts.xanchor] || 0); + + // y positioning we can do correctly from the start + var yBottomFrac = opts.y + lenFrac * (({top: -0.5, bottom: 0.5}[opts.yanchor] || 0) - 0.5); + var yBottomPx = Math.round(gs.h * (1 - yBottomFrac)); + var yTopPx = yBottomPx - lenPx; + + // stash a few things for makeEditable + opts._lenFrac = lenFrac; + opts._thickFrac = thickFrac; + opts._xLeftFrac = xLeftFrac; + opts._yBottomFrac = yBottomFrac; + + // stash mocked axis for contour label formatting + var ax = opts._axis = mockColorBarAxis(gd, opts, zrange); + + // position can't go in through supplyDefaults + // because that restricts it to [0,1] + ax.position = opts.x + xpadFrac + thickFrac; + + if(['top', 'bottom'].indexOf(titleSide) !== -1) { + ax.title.side = titleSide; + ax.titlex = opts.x + xpadFrac; + ax.titley = yBottomFrac + (title.side === 'top' ? lenFrac - ypadFrac : ypadFrac); + } + + if(line.color && opts.tickmode === 'auto') { + ax.tickmode = 'linear'; + ax.tick0 = levelsIn.start; + var dtick = levelsIn.size; + // expand if too many contours, so we don't get too many ticks + var autoNtick = Lib.constrain((yBottomPx - yTopPx) / 50, 4, 15) + 1; + var dtFactor = (zrange[1] - zrange[0]) / ((opts.nticks || autoNtick) * dtick); + if(dtFactor > 1) { + var dtexp = Math.pow(10, Math.floor(Math.log(dtFactor) / Math.LN10)); + dtick *= dtexp * Lib.roundUp(dtFactor / dtexp, [2, 5, 10]); + // if the contours are at round multiples, reset tick0 + // so they're still at round multiples. Otherwise, + // keep the first label on the first contour level + if((Math.abs(levelsIn.start) / levelsIn.size + 1e-6) % 1 < 2e-6) { + ax.tick0 = 0; + } + } + ax.dtick = dtick; + } + + // set domain after init, because we may want to + // allow it outside [0,1] + ax.domain = [ + yBottomFrac + ypadFrac, + yBottomFrac + lenFrac - ypadFrac + ]; + + ax.setScale(); + + g.attr('transform', 'translate(' + Math.round(gs.l) + ',' + Math.round(gs.t) + ')'); + + var titleCont = g.select('.' + cn.cbtitleunshift) + .attr('transform', 'translate(-' + Math.round(gs.l) + ',-' + Math.round(gs.t) + ')'); + + var axLayer = g.select('.' + cn.cbaxis); + var titleEl; + var titleHeight = 0; + + function drawTitle(titleClass, titleOpts) { + var dfltTitleOpts = { + propContainer: ax, + propName: opts._propPrefix + 'title', + traceIndex: opts._traceIndex, + _meta: opts._meta, + placeholder: fullLayout._dfltTitle.colorbar, + containerGroup: g.select('.' + cn.cbtitle) + }; + + // this class-to-rotate thing with convertToTspans is + // getting hackier and hackier... delete groups with the + // wrong class (in case earlier the colorbar was drawn on + // a different side, I think?) + var otherClass = titleClass.charAt(0) === 'h' ? + titleClass.substr(1) : + 'h' + titleClass; + g.selectAll('.' + otherClass + ',.' + otherClass + '-math-group').remove(); + + Titles.draw(gd, titleClass, extendFlat(dfltTitleOpts, titleOpts || {})); + } + + function drawDummyTitle() { + if(['top', 'bottom'].indexOf(titleSide) !== -1) { + // draw the title so we know how much room it needs + // when we squish the axis. This one only applies to + // top or bottom titles, not right side. + var x = gs.l + (opts.x + xpadFrac) * gs.w; + var fontSize = ax.title.font.size; + var y; + + if(titleSide === 'top') { + y = (1 - (yBottomFrac + lenFrac - ypadFrac)) * gs.h + + gs.t + 3 + fontSize * 0.75; + } else { + y = (1 - (yBottomFrac + ypadFrac)) * gs.h + + gs.t - 3 - fontSize * 0.25; + } + drawTitle(ax._id + 'title', { + attributes: {x: x, y: y, 'text-anchor': 'start'} + }); + } + } + + function drawCbTitle() { + if(['top', 'bottom'].indexOf(titleSide) === -1) { + var fontSize = ax.title.font.size; + var y = ax._offset + ax._length / 2; + var x = gs.l + (ax.position || 0) * gs.w + ((ax.side === 'right') ? + 10 + fontSize * ((ax.showticklabels ? 1 : 0.5)) : + -10 - fontSize * ((ax.showticklabels ? 0.5 : 0))); + + // the 'h' + is a hack to get around the fact that + // convertToTspans rotates any 'y...' class by 90 degrees. + // TODO: find a better way to control this. + drawTitle('h' + ax._id + 'title', { + avoid: { + selection: d3.select(gd).selectAll('g.' + ax._id + 'tick'), + side: titleSide, + offsetLeft: gs.l, + offsetTop: 0, + maxShift: fullLayout.width + }, + attributes: {x: x, y: y, 'text-anchor': 'middle'}, + transform: {rotate: '-90', offset: 0} + }); + } + } + + function drawAxis() { + if(['top', 'bottom'].indexOf(titleSide) !== -1) { + // squish the axis top to make room for the title + var titleGroup = g.select('.' + cn.cbtitle); + var titleText = titleGroup.select('text'); + var titleTrans = [-opts.outlinewidth / 2, opts.outlinewidth / 2]; + var mathJaxNode = titleGroup + .select('.h' + ax._id + 'title-math-group') + .node(); + var lineSize = 15.6; + if(titleText.node()) { + lineSize = parseInt(titleText.node().style.fontSize, 10) * LINE_SPACING; + } + if(mathJaxNode) { + titleHeight = Drawing.bBox(mathJaxNode).height; + if(titleHeight > lineSize) { + // not entirely sure how mathjax is doing + // vertical alignment, but this seems to work. + titleTrans[1] -= (titleHeight - lineSize) / 2; + } + } else if(titleText.node() && !titleText.classed(cn.jsPlaceholder)) { + titleHeight = Drawing.bBox(titleText.node()).height; + } + if(titleHeight) { + // buffer btwn colorbar and title + // TODO: configurable + titleHeight += 5; + + if(titleSide === 'top') { + ax.domain[1] -= titleHeight / gs.h; + titleTrans[1] *= -1; + } else { + ax.domain[0] += titleHeight / gs.h; + var nlines = svgTextUtils.lineCount(titleText); + titleTrans[1] += (1 - nlines) * lineSize; + } + + titleGroup.attr('transform', 'translate(' + titleTrans + ')'); + ax.setScale(); + } + } + + g.selectAll('.' + cn.cbfills + ',.' + cn.cblines) + .attr('transform', 'translate(0,' + Math.round(gs.h * (1 - ax.domain[1])) + ')'); + + axLayer.attr('transform', 'translate(0,' + Math.round(-gs.t) + ')'); + + var fills = g.select('.' + cn.cbfills) + .selectAll('rect.' + cn.cbfill) + .data(fillLevels); + fills.enter().append('rect') + .classed(cn.cbfill, true) + .style('stroke', 'none'); + fills.exit().remove(); + + var zBounds = zrange + .map(ax.c2p) + .map(Math.round) + .sort(function(a, b) { return a - b; }); + + fills.each(function(d, i) { + var z = [ + (i === 0) ? zrange[0] : (fillLevels[i] + fillLevels[i - 1]) / 2, + (i === fillLevels.length - 1) ? zrange[1] : (fillLevels[i] + fillLevels[i + 1]) / 2 + ] + .map(ax.c2p) + .map(Math.round); + + // offset the side adjoining the next rectangle so they + // overlap, to prevent antialiasing gaps + z[1] = Lib.constrain(z[1] + (z[1] > z[0]) ? 1 : -1, zBounds[0], zBounds[1]); + + + // Colorbar cannot currently support opacities so we + // use an opaque fill even when alpha channels present + var fillEl = d3.select(this).attr({ + x: xLeft, + width: Math.max(thickPx, 2), + y: d3.min(z), + height: Math.max(d3.max(z) - d3.min(z), 2), + }); + + if(opts._fillgradient) { + Drawing.gradient(fillEl, gd, opts._id, 'vertical', opts._fillgradient, 'fill'); + } else { + // tinycolor can't handle exponents and + // at this scale, removing it makes no difference. + var colorString = fillColormap(d).replace('e-', ''); + fillEl.attr('fill', tinycolor(colorString).toHexString()); + } + }); + + var lines = g.select('.' + cn.cblines) + .selectAll('path.' + cn.cbline) + .data(line.color && line.width ? lineLevels : []); + lines.enter().append('path') + .classed(cn.cbline, true); + lines.exit().remove(); + lines.each(function(d) { + d3.select(this) + .attr('d', 'M' + xLeft + ',' + + (Math.round(ax.c2p(d)) + (line.width / 2) % 1) + 'h' + thickPx) + .call(Drawing.lineGroupStyle, line.width, lineColormap(d), line.dash); + }); + + // force full redraw of labels and ticks + axLayer.selectAll('g.' + ax._id + 'tick,path').remove(); + + var shift = xLeft + thickPx + + (opts.outlinewidth || 0) / 2 - (opts.ticks === 'outside' ? 1 : 0); + + var vals = Axes.calcTicks(ax); + var transFn = Axes.makeTransFn(ax); + var tickSign = Axes.getTickSigns(ax)[2]; + + Axes.drawTicks(gd, ax, { + vals: ax.ticks === 'inside' ? Axes.clipEnds(ax, vals) : vals, + layer: axLayer, + path: Axes.makeTickPath(ax, shift, tickSign), + transFn: transFn + }); + + return Axes.drawLabels(gd, ax, { + vals: vals, + layer: axLayer, + transFn: transFn, + labelFns: Axes.makeLabelFns(ax, shift) + }); + } + + // wait for the axis & title to finish rendering before + // continuing positioning + // TODO: why are we redrawing multiple times now with this? + // I guess autoMargin doesn't like being post-promise? + function positionCB() { + var innerWidth = thickPx + opts.outlinewidth / 2 + Drawing.bBox(axLayer.node()).width; + titleEl = titleCont.select('text'); + + if(titleEl.node() && !titleEl.classed(cn.jsPlaceholder)) { + var mathJaxNode = titleCont.select('.h' + ax._id + 'title-math-group').node(); + var titleWidth; + if(mathJaxNode && ['top', 'bottom'].indexOf(titleSide) !== -1) { + titleWidth = Drawing.bBox(mathJaxNode).width; + } else { + // note: the formula below works for all title sides, + // (except for top/bottom mathjax, above) + // but the weird gs.l is because the titleunshift + // transform gets removed by Drawing.bBox + titleWidth = Drawing.bBox(titleCont.node()).right - xLeft - gs.l; + } + innerWidth = Math.max(innerWidth, titleWidth); + } + + var outerwidth = 2 * opts.xpad + innerWidth + opts.borderwidth + opts.outlinewidth / 2; + var outerheight = yBottomPx - yTopPx; + + g.select('.' + cn.cbbg).attr({ + x: xLeft - opts.xpad - (opts.borderwidth + opts.outlinewidth) / 2, + y: yTopPx - yExtraPx, + width: Math.max(outerwidth, 2), + height: Math.max(outerheight + 2 * yExtraPx, 2) + }) + .call(Color.fill, opts.bgcolor) + .call(Color.stroke, opts.bordercolor) + .style('stroke-width', opts.borderwidth); + + g.selectAll('.' + cn.cboutline).attr({ + x: xLeft, + y: yTopPx + opts.ypad + (titleSide === 'top' ? titleHeight : 0), + width: Math.max(thickPx, 2), + height: Math.max(outerheight - 2 * opts.ypad - titleHeight, 2) + }) + .call(Color.stroke, opts.outlinecolor) + .style({ + fill: 'none', + 'stroke-width': opts.outlinewidth + }); + + // fix positioning for xanchor!='left' + var xoffset = ({center: 0.5, right: 1}[opts.xanchor] || 0) * outerwidth; + g.attr('transform', 'translate(' + (gs.l - xoffset) + ',' + gs.t + ')'); + + // auto margin adjustment + var marginOpts = {}; + var tFrac = FROM_TL[opts.yanchor]; + var bFrac = FROM_BR[opts.yanchor]; + if(opts.lenmode === 'pixels') { + marginOpts.y = opts.y; + marginOpts.t = outerheight * tFrac; + marginOpts.b = outerheight * bFrac; + } else { + marginOpts.t = marginOpts.b = 0; + marginOpts.yt = opts.y + opts.len * tFrac; + marginOpts.yb = opts.y - opts.len * bFrac; + } + + var lFrac = FROM_TL[opts.xanchor]; + var rFrac = FROM_BR[opts.xanchor]; + if(opts.thicknessmode === 'pixels') { + marginOpts.x = opts.x; + marginOpts.l = outerwidth * lFrac; + marginOpts.r = outerwidth * rFrac; + } else { + var extraThickness = outerwidth - thickPx; + marginOpts.l = extraThickness * lFrac; + marginOpts.r = extraThickness * rFrac; + marginOpts.xl = opts.x - opts.thickness * lFrac; + marginOpts.xr = opts.x + opts.thickness * rFrac; + } + + Plots.autoMargin(gd, opts._id, marginOpts); + } + + return Lib.syncOrAsync([ + Plots.previousPromises, + drawDummyTitle, + drawAxis, + drawCbTitle, + Plots.previousPromises, + positionCB + ], gd); +} + +function makeEditable(g, opts, gd) { + var fullLayout = gd._fullLayout; + var gs = fullLayout._size; + var t0, xf, yf; + + dragElement.init({ + element: g.node(), + gd: gd, + prepFn: function() { + t0 = g.attr('transform'); + setCursor(g); + }, + moveFn: function(dx, dy) { + g.attr('transform', t0 + ' ' + 'translate(' + dx + ',' + dy + ')'); + + xf = dragElement.align(opts._xLeftFrac + (dx / gs.w), opts._thickFrac, + 0, 1, opts.xanchor); + yf = dragElement.align(opts._yBottomFrac - (dy / gs.h), opts._lenFrac, + 0, 1, opts.yanchor); + + var csr = dragElement.getCursor(xf, yf, opts.xanchor, opts.yanchor); + setCursor(g, csr); + }, + doneFn: function() { + setCursor(g); + + if(xf !== undefined && yf !== undefined) { + var update = {}; + update[opts._propPrefix + 'x'] = xf; + update[opts._propPrefix + 'y'] = yf; + if(opts._traceIndex !== undefined) { + Registry.call('_guiRestyle', gd, update, opts._traceIndex); + } else { + Registry.call('_guiRelayout', gd, update); + } + } + } + }); +} + +function calcLevels(gd, opts, zrange) { + var levelsIn = opts._levels; + var lineLevels = []; + var fillLevels = []; + var l; + var i; + + var l0 = levelsIn.end + levelsIn.size / 100; + var ls = levelsIn.size; + var zr0 = (1.001 * zrange[0] - 0.001 * zrange[1]); + var zr1 = (1.001 * zrange[1] - 0.001 * zrange[0]); + + for(i = 0; i < 1e5; i++) { + l = levelsIn.start + i * ls; + if(ls > 0 ? (l >= l0) : (l <= l0)) break; + if(l > zr0 && l < zr1) lineLevels.push(l); + } + + if(opts._fillgradient) { + fillLevels = [0]; + } else if(typeof opts._fillcolor === 'function') { + var fillLevelsIn = opts._filllevels; + + if(fillLevelsIn) { + l0 = fillLevelsIn.end + fillLevelsIn.size / 100; + ls = fillLevelsIn.size; + for(i = 0; i < 1e5; i++) { + l = fillLevelsIn.start + i * ls; + if(ls > 0 ? (l >= l0) : (l <= l0)) break; + if(l > zrange[0] && l < zrange[1]) fillLevels.push(l); + } + } else { + fillLevels = lineLevels.map(function(v) { + return v - levelsIn.size / 2; + }); + fillLevels.push(fillLevels[fillLevels.length - 1] + levelsIn.size); + } + } else if(opts._fillcolor && typeof opts._fillcolor === 'string') { + // doesn't matter what this value is, with a single value + // we'll make a single fill rect covering the whole bar + fillLevels = [0]; + } + + if(levelsIn.size < 0) { + lineLevels.reverse(); + fillLevels.reverse(); + } + + return {line: lineLevels, fill: fillLevels}; +} + +function mockColorBarAxis(gd, opts, zrange) { + var fullLayout = gd._fullLayout; + + var cbAxisIn = { + type: 'linear', + range: zrange, + tickmode: opts.tickmode, + nticks: opts.nticks, + tick0: opts.tick0, + dtick: opts.dtick, + tickvals: opts.tickvals, + ticktext: opts.ticktext, + ticks: opts.ticks, + ticklen: opts.ticklen, + tickwidth: opts.tickwidth, + tickcolor: opts.tickcolor, + showticklabels: opts.showticklabels, + tickfont: opts.tickfont, + tickangle: opts.tickangle, + tickformat: opts.tickformat, + exponentformat: opts.exponentformat, + separatethousands: opts.separatethousands, + showexponent: opts.showexponent, + showtickprefix: opts.showtickprefix, + tickprefix: opts.tickprefix, + showticksuffix: opts.showticksuffix, + ticksuffix: opts.ticksuffix, + title: opts.title, + showline: true, + anchor: 'free', + side: 'right', + position: 1 + }; + + var cbAxisOut = { + type: 'linear', + _id: 'y' + opts._id + }; + + var axisOptions = { + letter: 'y', + font: fullLayout.font, + noHover: true, + noTickson: true, + calendar: fullLayout.calendar // not really necessary (yet?) + }; + + function coerce(attr, dflt) { + return Lib.coerce(cbAxisIn, cbAxisOut, axisLayoutAttrs, attr, dflt); + } + + handleAxisDefaults(cbAxisIn, cbAxisOut, coerce, axisOptions, fullLayout); + handleAxisPositionDefaults(cbAxisIn, cbAxisOut, coerce, axisOptions); + + return cbAxisOut; +} + +module.exports = { + draw: draw +}; + +},{"../../constants/alignment":145,"../../lib":169,"../../lib/extend":164,"../../lib/setcursor":188,"../../lib/svg_text_utils":190,"../../plots/cartesian/axes":213,"../../plots/cartesian/axis_defaults":215,"../../plots/cartesian/layout_attributes":225,"../../plots/cartesian/position_defaults":228,"../../plots/plots":245,"../../registry":258,"../color":51,"../colorscale/helpers":62,"../dragelement":69,"../drawing":72,"../titles":138,"./constants":53,"d3":16,"tinycolor2":34}],56:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Lib = _dereq_('../../lib'); + + +module.exports = function hasColorbar(container) { + return Lib.isPlainObject(container.colorbar); +}; + +},{"../../lib":169}],57:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + moduleType: 'component', + name: 'colorbar', + + attributes: _dereq_('./attributes'), + supplyDefaults: _dereq_('./defaults'), + + draw: _dereq_('./draw').draw, + hasColorbar: _dereq_('./has_colorbar') +}; + +},{"./attributes":52,"./defaults":54,"./draw":55,"./has_colorbar":56}],58:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var colorbarAttrs = _dereq_('../colorbar/attributes'); +var counterRegex = _dereq_('../../lib/regex').counter; + +var palettes = _dereq_('./scales.js').scales; +var paletteStr = Object.keys(palettes); + +function code(s) { + return '`' + s + '`'; +} + +/** + * Make colorscale attribute declarations for + * + * - colorscale, + * - (c|z)auto, (c|z)min, (c|z)max, + * - autocolorscale, reversescale, + * - showscale (optionally) + * - color (optionally) + * + * @param {string} context (dflt: '', i.e. from trace root): + * the container this is in ('', *marker*, *marker.line* etc) + * + * @param {object} opts: + * - cLetter {string} (dflt: 'c'): + * leading letter for 'min', 'max and 'auto' attribute (either 'z' or 'c') + * + * - colorAttr {string} (dflt: 'z' if `cLetter: 'z'`, 'color' if `cLetter: 'c'`): + * (for descriptions) sets the name of the color attribute that maps to the colorscale. + * + * N.B. if `colorAttr: 'color'`, we include the `color` declaration here. + * + * - onlyIfNumerical {string} (dflt: false' if `cLetter: 'z'`, true if `cLetter: 'c'`): + * (for descriptions) set to true if colorscale attribute only + * + * - colorscaleDflt {string}: + * overrides the colorscale dflt + * + * - autoColorDflt {boolean} (dflt true): + * normally autocolorscale.dflt is `true`, but pass `false` to override + * + * - noScale {boolean} (dflt: true if `context: 'marker.line'`, false otherwise): + * set to `false` to not include showscale attribute (e.g. for 'marker.line') + * + * - showScaleDflt {boolean} (dflt: true if `cLetter: 'z'`, false otherwise) + * + * - editTypeOverride {boolean} (dflt: ''): + * most of these attributes already require a recalc, but the ones that do not + * have editType *style* or *plot* unless you override (presumably with *calc*) + * + * - anim {boolean) (dflt: undefined): is 'color' animatable? + * + * @return {object} + */ +module.exports = function colorScaleAttrs(context, opts) { + context = context || ''; + opts = opts || {}; + + var cLetter = opts.cLetter || 'c'; + var onlyIfNumerical = ('onlyIfNumerical' in opts) ? opts.onlyIfNumerical : Boolean(context); + var noScale = ('noScale' in opts) ? opts.noScale : context === 'marker.line'; + var showScaleDflt = ('showScaleDflt' in opts) ? opts.showScaleDflt : cLetter === 'z'; + var colorscaleDflt = typeof opts.colorscaleDflt === 'string' ? palettes[opts.colorscaleDflt] : null; + var editTypeOverride = opts.editTypeOverride || ''; + var contextHead = context ? (context + '.') : ''; + + var colorAttr, colorAttrFull; + + if('colorAttr' in opts) { + colorAttr = opts.colorAttr; + colorAttrFull = opts.colorAttr; + } else { + colorAttr = {z: 'z', c: 'color'}[cLetter]; + colorAttrFull = 'in ' + code(contextHead + colorAttr); + } + + var effectDesc = onlyIfNumerical ? + ' Has an effect only if ' + colorAttrFull + 'is set to a numerical array.' : + ''; + + var auto = cLetter + 'auto'; + var min = cLetter + 'min'; + var max = cLetter + 'max'; + var mid = cLetter + 'mid'; + var autoFull = code(contextHead + auto); + var minFull = code(contextHead + min); + var maxFull = code(contextHead + max); + var minmaxFull = minFull + ' and ' + maxFull; + var autoImpliedEdits = {}; + autoImpliedEdits[min] = autoImpliedEdits[max] = undefined; + var minmaxImpliedEdits = {}; + minmaxImpliedEdits[auto] = false; + + var attrs = {}; + + if(colorAttr === 'color') { + attrs.color = { + valType: 'color', + arrayOk: true, + + editType: editTypeOverride || 'style', + + }; + + if(opts.anim) { + attrs.color.anim = true; + } + } + + attrs[auto] = { + valType: 'boolean', + + dflt: true, + editType: 'calc', + impliedEdits: autoImpliedEdits, + + }; + + attrs[min] = { + valType: 'number', + + dflt: null, + editType: editTypeOverride || 'plot', + impliedEdits: minmaxImpliedEdits, + + }; + + attrs[max] = { + valType: 'number', + + dflt: null, + editType: editTypeOverride || 'plot', + impliedEdits: minmaxImpliedEdits, + + }; + + attrs[mid] = { + valType: 'number', + + dflt: null, + editType: 'calc', + impliedEdits: autoImpliedEdits, + + }; + + attrs.colorscale = { + valType: 'colorscale', + + editType: 'calc', + dflt: colorscaleDflt, + impliedEdits: {autocolorscale: false}, + + }; + + attrs.autocolorscale = { + valType: 'boolean', + + // gets overrode in 'heatmap' & 'surface' for backwards comp. + dflt: opts.autoColorDflt === false ? false : true, + editType: 'calc', + impliedEdits: {colorscale: undefined}, + + }; + + attrs.reversescale = { + valType: 'boolean', + + dflt: false, + editType: 'plot', + + }; + + if(!noScale) { + attrs.showscale = { + valType: 'boolean', + + dflt: showScaleDflt, + editType: 'calc', + + }; + + attrs.colorbar = colorbarAttrs; + } + + if(!opts.noColorAxis) { + attrs.coloraxis = { + valType: 'subplotid', + + regex: counterRegex('coloraxis'), + dflt: null, + editType: 'calc', + + }; + } + + return attrs; +}; + +},{"../../lib/regex":184,"../colorbar/attributes":52,"./scales.js":66}],59:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); + +var Lib = _dereq_('../../lib'); +var extractOpts = _dereq_('./helpers').extractOpts; + +module.exports = function calc(gd, trace, opts) { + var fullLayout = gd._fullLayout; + var vals = opts.vals; + var containerStr = opts.containerStr; + + var container = containerStr ? + Lib.nestedProperty(trace, containerStr).get() : + trace; + + var cOpts = extractOpts(container); + var auto = cOpts.auto !== false; + var min = cOpts.min; + var max = cOpts.max; + var mid = cOpts.mid; + + var minVal = function() { return Lib.aggNums(Math.min, null, vals); }; + var maxVal = function() { return Lib.aggNums(Math.max, null, vals); }; + + if(min === undefined) { + min = minVal(); + } else if(auto) { + if(container._colorAx && isNumeric(min)) { + min = Math.min(min, minVal()); + } else { + min = minVal(); + } + } + + if(max === undefined) { + max = maxVal(); + } else if(auto) { + if(container._colorAx && isNumeric(max)) { + max = Math.max(max, maxVal()); + } else { + max = maxVal(); + } + } + + if(auto && mid !== undefined) { + if(max - mid > mid - min) { + min = mid - (max - mid); + } else if(max - mid < mid - min) { + max = mid + (mid - min); + } + } + + if(min === max) { + min -= 0.5; + max += 0.5; + } + + cOpts._sync('min', min); + cOpts._sync('max', max); + + if(cOpts.autocolorscale) { + var scl; + if(min * max < 0) scl = fullLayout.colorscale.diverging; + else if(min >= 0) scl = fullLayout.colorscale.sequential; + else scl = fullLayout.colorscale.sequentialminus; + cOpts._sync('colorscale', scl); + } +}; + +},{"../../lib":169,"./helpers":62,"fast-isnumeric":18}],60:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var hasColorscale = _dereq_('./helpers').hasColorscale; +var extractOpts = _dereq_('./helpers').extractOpts; + +module.exports = function crossTraceDefaults(fullData, fullLayout) { + function replace(cont, k) { + var val = cont['_' + k]; + if(val !== undefined) { + cont[k] = val; + } + } + + function relinkColorAttrs(outerCont, cbOpt) { + var cont = cbOpt.container ? + Lib.nestedProperty(outerCont, cbOpt.container).get() : + outerCont; + + if(cont) { + if(cont.coloraxis) { + // stash ref to color axis + cont._colorAx = fullLayout[cont.coloraxis]; + } else { + var cOpts = extractOpts(cont); + var isAuto = cOpts.auto; + + if(isAuto || cOpts.min === undefined) { + replace(cont, cbOpt.min); + } + if(isAuto || cOpts.max === undefined) { + replace(cont, cbOpt.max); + } + if(cOpts.autocolorscale) { + replace(cont, 'colorscale'); + } + } + } + } + + for(var i = 0; i < fullData.length; i++) { + var trace = fullData[i]; + var cbOpts = trace._module.colorbar; + + if(cbOpts) { + if(Array.isArray(cbOpts)) { + for(var j = 0; j < cbOpts.length; j++) { + relinkColorAttrs(trace, cbOpts[j]); + } + } else { + relinkColorAttrs(trace, cbOpts); + } + } + + if(hasColorscale(trace, 'marker.line')) { + relinkColorAttrs(trace, { + container: 'marker.line', + min: 'cmin', + max: 'cmax' + }); + } + } + + for(var k in fullLayout._colorAxes) { + relinkColorAttrs(fullLayout[k], {min: 'cmin', max: 'cmax'}); + } +}; + +},{"../../lib":169,"./helpers":62}],61:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); + +var Lib = _dereq_('../../lib'); +var hasColorbar = _dereq_('../colorbar/has_colorbar'); +var colorbarDefaults = _dereq_('../colorbar/defaults'); + +var isValidScale = _dereq_('./scales').isValid; +var traceIs = _dereq_('../../registry').traceIs; + +function npMaybe(parentCont, prefix) { + var containerStr = prefix.slice(0, prefix.length - 1); + return prefix ? + Lib.nestedProperty(parentCont, containerStr).get() || {} : + parentCont; +} + +/** + * Colorscale / colorbar default handler + * + * @param {object} parentContIn : user (input) parent container (e.g. trace or layout coloraxis object) + * @param {object} parentContOut : full parent container + * @param {object} layout : (full) layout object + * @param {fn} coerce : Lib.coerce wrapper + * @param {object} opts : + * - prefix {string} : attr string prefix to colorscale container from parent root + * - cLetter {string} : 'c or 'z' color letter + */ +module.exports = function colorScaleDefaults(parentContIn, parentContOut, layout, coerce, opts) { + var prefix = opts.prefix; + var cLetter = opts.cLetter; + var inTrace = '_module' in parentContOut; + var containerIn = npMaybe(parentContIn, prefix); + var containerOut = npMaybe(parentContOut, prefix); + var template = npMaybe(parentContOut._template || {}, prefix) || {}; + + // colorScaleDefaults wrapper called if-ever we need to reset the colorscale + // attributes for containers that were linked to invalid color axes + var thisFn = function() { + delete parentContIn.coloraxis; + delete parentContOut.coloraxis; + return colorScaleDefaults(parentContIn, parentContOut, layout, coerce, opts); + }; + + if(inTrace) { + var colorAxes = layout._colorAxes || {}; + var colorAx = coerce(prefix + 'coloraxis'); + + if(colorAx) { + var colorbarVisuals = ( + traceIs(parentContOut, 'contour') && + Lib.nestedProperty(parentContOut, 'contours.coloring').get() + ) || 'heatmap'; + + var stash = colorAxes[colorAx]; + + if(stash) { + stash[2].push(thisFn); + + if(stash[0] !== colorbarVisuals) { + stash[0] = false; + Lib.warn([ + 'Ignoring coloraxis:', colorAx, 'setting', + 'as it is linked to incompatible colorscales.' + ].join(' ')); + } + } else { + // stash: + // - colorbar visual 'type' + // - colorbar options to help in Colorbar.draw + // - list of colorScaleDefaults wrapper functions + colorAxes[colorAx] = [colorbarVisuals, parentContOut, [thisFn]]; + } + return; + } + } + + var minIn = containerIn[cLetter + 'min']; + var maxIn = containerIn[cLetter + 'max']; + var validMinMax = isNumeric(minIn) && isNumeric(maxIn) && (minIn < maxIn); + var auto = coerce(prefix + cLetter + 'auto', !validMinMax); + + if(auto) { + coerce(prefix + cLetter + 'mid'); + } else { + coerce(prefix + cLetter + 'min'); + coerce(prefix + cLetter + 'max'); + } + + // handles both the trace case (autocolorscale is false by default) and + // the marker and marker.line case (autocolorscale is true by default) + var sclIn = containerIn.colorscale; + var sclTemplate = template.colorscale; + var autoColorscaleDflt; + if(sclIn !== undefined) autoColorscaleDflt = !isValidScale(sclIn); + if(sclTemplate !== undefined) autoColorscaleDflt = !isValidScale(sclTemplate); + coerce(prefix + 'autocolorscale', autoColorscaleDflt); + + coerce(prefix + 'colorscale'); + coerce(prefix + 'reversescale'); + + if(prefix !== 'marker.line.') { + // handles both the trace case where the dflt is listed in attributes and + // the marker case where the dflt is determined by hasColorbar + var showScaleDflt; + if(prefix && inTrace) showScaleDflt = hasColorbar(containerIn); + + var showScale = coerce(prefix + 'showscale', showScaleDflt); + if(showScale) colorbarDefaults(containerIn, containerOut, layout); + } +}; + +},{"../../lib":169,"../../registry":258,"../colorbar/defaults":54,"../colorbar/has_colorbar":56,"./scales":66,"fast-isnumeric":18}],62:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); +var tinycolor = _dereq_('tinycolor2'); +var isNumeric = _dereq_('fast-isnumeric'); + +var Lib = _dereq_('../../lib'); +var Color = _dereq_('../color'); + +var isValidScale = _dereq_('./scales').isValid; + +function hasColorscale(trace, containerStr, colorKey) { + var container = containerStr ? + Lib.nestedProperty(trace, containerStr).get() || {} : + trace; + var color = container[colorKey || 'color']; + + var isArrayWithOneNumber = false; + if(Lib.isArrayOrTypedArray(color)) { + for(var i = 0; i < color.length; i++) { + if(isNumeric(color[i])) { + isArrayWithOneNumber = true; + break; + } + } + } + + return ( + Lib.isPlainObject(container) && ( + isArrayWithOneNumber || + container.showscale === true || + (isNumeric(container.cmin) && isNumeric(container.cmax)) || + isValidScale(container.colorscale) || + Lib.isPlainObject(container.colorbar) + ) + ); +} + +var constantAttrs = ['showscale', 'autocolorscale', 'colorscale', 'reversescale', 'colorbar']; +var letterAttrs = ['min', 'max', 'mid', 'auto']; + +/** + * Extract 'c' / 'z', trace / color axis colorscale options + * + * Note that it would be nice to replace all z* with c* equivalents in v2 + * + * @param {object} cont : attribute container + * @return {object}: + * - min: cmin or zmin + * - max: cmax or zmax + * - mid: cmid or zmid + * - auto: cauto or zauto + * - *scale: *scale attrs + * - colorbar: colorbar + * - _sync: function syncing attr and underscore dual (useful when calc'ing min/max) + */ +function extractOpts(cont) { + var colorAx = cont._colorAx; + var cont2 = colorAx ? colorAx : cont; + var out = {}; + var cLetter; + var i, k; + + for(i = 0; i < constantAttrs.length; i++) { + k = constantAttrs[i]; + out[k] = cont2[k]; + } + + if(colorAx) { + cLetter = 'c'; + for(i = 0; i < letterAttrs.length; i++) { + k = letterAttrs[i]; + out[k] = cont2['c' + k]; + } + } else { + var k2; + for(i = 0; i < letterAttrs.length; i++) { + k = letterAttrs[i]; + k2 = 'c' + k; + if(k2 in cont2) { + out[k] = cont2[k2]; + continue; + } + k2 = 'z' + k; + if(k2 in cont2) { + out[k] = cont2[k2]; + } + } + cLetter = k2.charAt(0); + } + + out._sync = function(k, v) { + var k2 = letterAttrs.indexOf(k) !== -1 ? cLetter + k : k; + cont2[k2] = cont2['_' + k2] = v; + }; + + return out; +} + +/** + * Extract colorscale into numeric domain and color range. + * + * @param {object} cont colorscale container (e.g. trace, marker) + * - colorscale {array of arrays} + * - cmin/zmin {number} + * - cmax/zmax {number} + * - reversescale {boolean} + * + * @return {object} + * - domain {array} + * - range {array} + */ +function extractScale(cont) { + var cOpts = extractOpts(cont); + var cmin = cOpts.min; + var cmax = cOpts.max; + + var scl = cOpts.reversescale ? + flipScale(cOpts.colorscale) : + cOpts.colorscale; + + var N = scl.length; + var domain = new Array(N); + var range = new Array(N); + + for(var i = 0; i < N; i++) { + var si = scl[i]; + domain[i] = cmin + si[0] * (cmax - cmin); + range[i] = si[1]; + } + + return {domain: domain, range: range}; +} + +function flipScale(scl) { + var N = scl.length; + var sclNew = new Array(N); + + for(var i = N - 1, j = 0; i >= 0; i--, j++) { + var si = scl[i]; + sclNew[j] = [1 - si[0], si[1]]; + } + return sclNew; +} + +/** + * General colorscale function generator. + * + * @param {object} specs output of Colorscale.extractScale or precomputed domain, range. + * - domain {array} + * - range {array} + * + * @param {object} opts + * - noNumericCheck {boolean} if true, scale func bypasses numeric checks + * - returnArray {boolean} if true, scale func return 4-item array instead of color strings + * + * @return {function} + */ +function makeColorScaleFunc(specs, opts) { + opts = opts || {}; + + var domain = specs.domain; + var range = specs.range; + var N = range.length; + var _range = new Array(N); + + for(var i = 0; i < N; i++) { + var rgba = tinycolor(range[i]).toRgb(); + _range[i] = [rgba.r, rgba.g, rgba.b, rgba.a]; + } + + var _sclFunc = d3.scale.linear() + .domain(domain) + .range(_range) + .clamp(true); + + var noNumericCheck = opts.noNumericCheck; + var returnArray = opts.returnArray; + var sclFunc; + + if(noNumericCheck && returnArray) { + sclFunc = _sclFunc; + } else if(noNumericCheck) { + sclFunc = function(v) { + return colorArray2rbga(_sclFunc(v)); + }; + } else if(returnArray) { + sclFunc = function(v) { + if(isNumeric(v)) return _sclFunc(v); + else if(tinycolor(v).isValid()) return v; + else return Color.defaultLine; + }; + } else { + sclFunc = function(v) { + if(isNumeric(v)) return colorArray2rbga(_sclFunc(v)); + else if(tinycolor(v).isValid()) return v; + else return Color.defaultLine; + }; + } + + // colorbar draw looks into the d3 scale closure for domain and range + sclFunc.domain = _sclFunc.domain; + sclFunc.range = function() { return range; }; + + return sclFunc; +} + +function makeColorScaleFuncFromTrace(trace, opts) { + return makeColorScaleFunc(extractScale(trace), opts); +} + +function colorArray2rbga(colorArray) { + var colorObj = { + r: colorArray[0], + g: colorArray[1], + b: colorArray[2], + a: colorArray[3] + }; + + return tinycolor(colorObj).toRgbString(); +} + +module.exports = { + hasColorscale: hasColorscale, + extractOpts: extractOpts, + extractScale: extractScale, + flipScale: flipScale, + makeColorScaleFunc: makeColorScaleFunc, + makeColorScaleFuncFromTrace: makeColorScaleFuncFromTrace +}; + +},{"../../lib":169,"../color":51,"./scales":66,"d3":16,"fast-isnumeric":18,"tinycolor2":34}],63:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var scales = _dereq_('./scales'); +var helpers = _dereq_('./helpers'); + +module.exports = { + moduleType: 'component', + name: 'colorscale', + + attributes: _dereq_('./attributes'), + layoutAttributes: _dereq_('./layout_attributes'), + + supplyLayoutDefaults: _dereq_('./layout_defaults'), + handleDefaults: _dereq_('./defaults'), + crossTraceDefaults: _dereq_('./cross_trace_defaults'), + + calc: _dereq_('./calc'), + + // ./scales.js is required in lib/coerce.js ; + // it needs to be a seperate module to avoid circular a dependency + scales: scales.scales, + defaultScale: scales.defaultScale, + getScale: scales.get, + isValidScale: scales.isValid, + + hasColorscale: helpers.hasColorscale, + extractOpts: helpers.extractOpts, + extractScale: helpers.extractScale, + flipScale: helpers.flipScale, + makeColorScaleFunc: helpers.makeColorScaleFunc, + makeColorScaleFuncFromTrace: helpers.makeColorScaleFuncFromTrace +}; + +},{"./attributes":58,"./calc":59,"./cross_trace_defaults":60,"./defaults":61,"./helpers":62,"./layout_attributes":64,"./layout_defaults":65,"./scales":66}],64:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var extendFlat = _dereq_('../../lib/extend').extendFlat; + +var colorScaleAttrs = _dereq_('./attributes'); +var scales = _dereq_('./scales').scales; + +var msg = 'Note that `autocolorscale` must be true for this attribute to work.'; + +module.exports = { + editType: 'calc', + + colorscale: { + editType: 'calc', + + sequential: { + valType: 'colorscale', + dflt: scales.Reds, + + editType: 'calc', + + }, + sequentialminus: { + valType: 'colorscale', + dflt: scales.Blues, + + editType: 'calc', + + }, + diverging: { + valType: 'colorscale', + dflt: scales.RdBu, + + editType: 'calc', + + } + }, + + coloraxis: extendFlat({ + // not really a 'subplot' attribute container, + // but this is the flag we use to denote attributes that + // support yaxis, yaxis2, yaxis3, ... counters + _isSubplotObj: true, + editType: 'calc', + + }, colorScaleAttrs('', { + colorAttr: 'corresponding trace color array(s)', + noColorAxis: true, + showScaleDflt: true + })) +}; + +},{"../../lib/extend":164,"./attributes":58,"./scales":66}],65:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var Template = _dereq_('../../plot_api/plot_template'); + +var colorScaleAttrs = _dereq_('./layout_attributes'); +var colorScaleDefaults = _dereq_('./defaults'); + +module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) { + function coerce(attr, dflt) { + return Lib.coerce(layoutIn, layoutOut, colorScaleAttrs, attr, dflt); + } + + coerce('colorscale.sequential'); + coerce('colorscale.sequentialminus'); + coerce('colorscale.diverging'); + + var colorAxes = layoutOut._colorAxes; + var colorAxIn, colorAxOut; + + function coerceAx(attr, dflt) { + return Lib.coerce(colorAxIn, colorAxOut, colorScaleAttrs.coloraxis, attr, dflt); + } + + for(var k in colorAxes) { + var stash = colorAxes[k]; + + if(stash[0]) { + colorAxIn = layoutIn[k] || {}; + colorAxOut = Template.newContainer(layoutOut, k, 'coloraxis'); + colorAxOut._name = k; + colorScaleDefaults(colorAxIn, colorAxOut, layoutOut, coerceAx, {prefix: '', cLetter: 'c'}); + } else { + // re-coerce colorscale attributes w/o coloraxis + for(var i = 0; i < stash[2].length; i++) { + stash[2][i](); + } + delete layoutOut._colorAxes[k]; + } + } +}; + +},{"../../lib":169,"../../plot_api/plot_template":203,"./defaults":61,"./layout_attributes":64}],66:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var tinycolor = _dereq_('tinycolor2'); + +var scales = { + 'Greys': [ + [0, 'rgb(0,0,0)'], [1, 'rgb(255,255,255)'] + ], + + 'YlGnBu': [ + [0, 'rgb(8,29,88)'], [0.125, 'rgb(37,52,148)'], + [0.25, 'rgb(34,94,168)'], [0.375, 'rgb(29,145,192)'], + [0.5, 'rgb(65,182,196)'], [0.625, 'rgb(127,205,187)'], + [0.75, 'rgb(199,233,180)'], [0.875, 'rgb(237,248,217)'], + [1, 'rgb(255,255,217)'] + ], + + 'Greens': [ + [0, 'rgb(0,68,27)'], [0.125, 'rgb(0,109,44)'], + [0.25, 'rgb(35,139,69)'], [0.375, 'rgb(65,171,93)'], + [0.5, 'rgb(116,196,118)'], [0.625, 'rgb(161,217,155)'], + [0.75, 'rgb(199,233,192)'], [0.875, 'rgb(229,245,224)'], + [1, 'rgb(247,252,245)'] + ], + + 'YlOrRd': [ + [0, 'rgb(128,0,38)'], [0.125, 'rgb(189,0,38)'], + [0.25, 'rgb(227,26,28)'], [0.375, 'rgb(252,78,42)'], + [0.5, 'rgb(253,141,60)'], [0.625, 'rgb(254,178,76)'], + [0.75, 'rgb(254,217,118)'], [0.875, 'rgb(255,237,160)'], + [1, 'rgb(255,255,204)'] + ], + + 'Bluered': [ + [0, 'rgb(0,0,255)'], [1, 'rgb(255,0,0)'] + ], + + // modified RdBu based on + // http://www.kennethmoreland.com/color-maps/ + 'RdBu': [ + [0, 'rgb(5,10,172)'], [0.35, 'rgb(106,137,247)'], + [0.5, 'rgb(190,190,190)'], [0.6, 'rgb(220,170,132)'], + [0.7, 'rgb(230,145,90)'], [1, 'rgb(178,10,28)'] + ], + + // Scale for non-negative numeric values + 'Reds': [ + [0, 'rgb(220,220,220)'], [0.2, 'rgb(245,195,157)'], + [0.4, 'rgb(245,160,105)'], [1, 'rgb(178,10,28)'] + ], + + // Scale for non-positive numeric values + 'Blues': [ + [0, 'rgb(5,10,172)'], [0.35, 'rgb(40,60,190)'], + [0.5, 'rgb(70,100,245)'], [0.6, 'rgb(90,120,245)'], + [0.7, 'rgb(106,137,247)'], [1, 'rgb(220,220,220)'] + ], + + 'Picnic': [ + [0, 'rgb(0,0,255)'], [0.1, 'rgb(51,153,255)'], + [0.2, 'rgb(102,204,255)'], [0.3, 'rgb(153,204,255)'], + [0.4, 'rgb(204,204,255)'], [0.5, 'rgb(255,255,255)'], + [0.6, 'rgb(255,204,255)'], [0.7, 'rgb(255,153,255)'], + [0.8, 'rgb(255,102,204)'], [0.9, 'rgb(255,102,102)'], + [1, 'rgb(255,0,0)'] + ], + + 'Rainbow': [ + [0, 'rgb(150,0,90)'], [0.125, 'rgb(0,0,200)'], + [0.25, 'rgb(0,25,255)'], [0.375, 'rgb(0,152,255)'], + [0.5, 'rgb(44,255,150)'], [0.625, 'rgb(151,255,0)'], + [0.75, 'rgb(255,234,0)'], [0.875, 'rgb(255,111,0)'], + [1, 'rgb(255,0,0)'] + ], + + 'Portland': [ + [0, 'rgb(12,51,131)'], [0.25, 'rgb(10,136,186)'], + [0.5, 'rgb(242,211,56)'], [0.75, 'rgb(242,143,56)'], + [1, 'rgb(217,30,30)'] + ], + + 'Jet': [ + [0, 'rgb(0,0,131)'], [0.125, 'rgb(0,60,170)'], + [0.375, 'rgb(5,255,255)'], [0.625, 'rgb(255,255,0)'], + [0.875, 'rgb(250,0,0)'], [1, 'rgb(128,0,0)'] + ], + + 'Hot': [ + [0, 'rgb(0,0,0)'], [0.3, 'rgb(230,0,0)'], + [0.6, 'rgb(255,210,0)'], [1, 'rgb(255,255,255)'] + ], + + 'Blackbody': [ + [0, 'rgb(0,0,0)'], [0.2, 'rgb(230,0,0)'], + [0.4, 'rgb(230,210,0)'], [0.7, 'rgb(255,255,255)'], + [1, 'rgb(160,200,255)'] + ], + + 'Earth': [ + [0, 'rgb(0,0,130)'], [0.1, 'rgb(0,180,180)'], + [0.2, 'rgb(40,210,40)'], [0.4, 'rgb(230,230,50)'], + [0.6, 'rgb(120,70,20)'], [1, 'rgb(255,255,255)'] + ], + + 'Electric': [ + [0, 'rgb(0,0,0)'], [0.15, 'rgb(30,0,100)'], + [0.4, 'rgb(120,0,100)'], [0.6, 'rgb(160,90,0)'], + [0.8, 'rgb(230,200,0)'], [1, 'rgb(255,250,220)'] + ], + + 'Viridis': [ + [0, '#440154'], [0.06274509803921569, '#48186a'], + [0.12549019607843137, '#472d7b'], [0.18823529411764706, '#424086'], + [0.25098039215686274, '#3b528b'], [0.3137254901960784, '#33638d'], + [0.3764705882352941, '#2c728e'], [0.4392156862745098, '#26828e'], + [0.5019607843137255, '#21918c'], [0.5647058823529412, '#1fa088'], + [0.6274509803921569, '#28ae80'], [0.6901960784313725, '#3fbc73'], + [0.7529411764705882, '#5ec962'], [0.8156862745098039, '#84d44b'], + [0.8784313725490196, '#addc30'], [0.9411764705882353, '#d8e219'], + [1, '#fde725'] + ], + + 'Cividis': [ + [0.000000, 'rgb(0,32,76)'], [0.058824, 'rgb(0,42,102)'], + [0.117647, 'rgb(0,52,110)'], [0.176471, 'rgb(39,63,108)'], + [0.235294, 'rgb(60,74,107)'], [0.294118, 'rgb(76,85,107)'], + [0.352941, 'rgb(91,95,109)'], [0.411765, 'rgb(104,106,112)'], + [0.470588, 'rgb(117,117,117)'], [0.529412, 'rgb(131,129,120)'], + [0.588235, 'rgb(146,140,120)'], [0.647059, 'rgb(161,152,118)'], + [0.705882, 'rgb(176,165,114)'], [0.764706, 'rgb(192,177,109)'], + [0.823529, 'rgb(209,191,102)'], [0.882353, 'rgb(225,204,92)'], + [0.941176, 'rgb(243,219,79)'], [1.000000, 'rgb(255,233,69)'] + ] +}; + +var defaultScale = scales.RdBu; + +function getScale(scl, dflt) { + if(!dflt) dflt = defaultScale; + if(!scl) return dflt; + + function parseScale() { + try { + scl = scales[scl] || JSON.parse(scl); + } catch(e) { + scl = dflt; + } + } + + if(typeof scl === 'string') { + parseScale(); + // occasionally scl is double-JSON encoded... + if(typeof scl === 'string') parseScale(); + } + + if(!isValidScaleArray(scl)) return dflt; + return scl; +} + + +function isValidScaleArray(scl) { + var highestVal = 0; + + if(!Array.isArray(scl) || scl.length < 2) return false; + + if(!scl[0] || !scl[scl.length - 1]) return false; + + if(+scl[0][0] !== 0 || +scl[scl.length - 1][0] !== 1) return false; + + for(var i = 0; i < scl.length; i++) { + var si = scl[i]; + + if(si.length !== 2 || +si[0] < highestVal || !tinycolor(si[1]).isValid()) { + return false; + } + + highestVal = +si[0]; + } + + return true; +} + +function isValidScale(scl) { + if(scales[scl] !== undefined) return true; + else return isValidScaleArray(scl); +} + +module.exports = { + scales: scales, + defaultScale: defaultScale, + + get: getScale, + isValid: isValidScale +}; + +},{"tinycolor2":34}],67:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + + +// for automatic alignment on dragging, <1/3 means left align, +// >2/3 means right, and between is center. Pick the right fraction +// based on where you are, and return the fraction corresponding to +// that position on the object +module.exports = function align(v, dv, v0, v1, anchor) { + var vmin = (v - v0) / (v1 - v0); + var vmax = vmin + dv / (v1 - v0); + var vc = (vmin + vmax) / 2; + + // explicitly specified anchor + if(anchor === 'left' || anchor === 'bottom') return vmin; + if(anchor === 'center' || anchor === 'middle') return vc; + if(anchor === 'right' || anchor === 'top') return vmax; + + // automatic based on position + if(vmin < (2 / 3) - vc) return vmin; + if(vmax > (4 / 3) - vc) return vmax; + return vc; +}; + +},{}],68:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Lib = _dereq_('../../lib'); + + +// set cursors pointing toward the closest corner/side, +// to indicate alignment +// x and y are 0-1, fractions of the plot area +var cursorset = [ + ['sw-resize', 's-resize', 'se-resize'], + ['w-resize', 'move', 'e-resize'], + ['nw-resize', 'n-resize', 'ne-resize'] +]; + +module.exports = function getCursor(x, y, xanchor, yanchor) { + if(xanchor === 'left') x = 0; + else if(xanchor === 'center') x = 1; + else if(xanchor === 'right') x = 2; + else x = Lib.constrain(Math.floor(x * 3), 0, 2); + + if(yanchor === 'bottom') y = 0; + else if(yanchor === 'middle') y = 1; + else if(yanchor === 'top') y = 2; + else y = Lib.constrain(Math.floor(y * 3), 0, 2); + + return cursorset[y][x]; +}; + +},{"../../lib":169}],69:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var mouseOffset = _dereq_('mouse-event-offset'); +var hasHover = _dereq_('has-hover'); +var supportsPassive = _dereq_('has-passive-events'); + +var removeElement = _dereq_('../../lib').removeElement; +var constants = _dereq_('../../plots/cartesian/constants'); + +var dragElement = module.exports = {}; + +dragElement.align = _dereq_('./align'); +dragElement.getCursor = _dereq_('./cursor'); + +var unhover = _dereq_('./unhover'); +dragElement.unhover = unhover.wrapped; +dragElement.unhoverRaw = unhover.raw; + +/** + * Abstracts click & drag interactions + * + * During the interaction, a "coverSlip" element - a transparent + * div covering the whole page - is created, which has two key effects: + * - Lets you drag beyond the boundaries of the plot itself without + * dropping (but if you drag all the way out of the browser window the + * interaction will end) + * - Freezes the cursor: whatever mouse cursor the drag element had when the + * interaction started gets copied to the coverSlip for use until mouseup + * + * If the user executes a drag bigger than MINDRAG, callbacks will fire as: + * prepFn, moveFn (1 or more times), doneFn + * If the user does not drag enough, prepFn and clickFn will fire. + * + * Note: If you cancel contextmenu, clickFn will fire even with a right click + * (unlike native events) so you'll get a `plotly_click` event. Cancel context eg: + * gd.addEventListener('contextmenu', function(e) { e.preventDefault(); }); + * TODO: we should probably turn this into a `config` parameter, so we can fix it + * such that if you *don't* cancel contextmenu, we can prevent partial drags, which + * put you in a weird state. + * + * If the user clicks multiple times quickly, clickFn will fire each time + * but numClicks will increase to help you recognize doubleclicks. + * + * @param {object} options with keys: + * element (required) the DOM element to drag + * prepFn (optional) function(event, startX, startY) + * executed on mousedown + * startX and startY are the clientX and clientY pixel position + * of the mousedown event + * moveFn (optional) function(dx, dy) + * executed on move, ONLY after we've exceeded MINDRAG + * (we keep executing moveFn if you move back to where you started) + * dx and dy are the net pixel offset of the drag, + * dragged is true/false, has the mouse moved enough to + * constitute a drag + * doneFn (optional) function(e) + * executed on mouseup, ONLY if we exceeded MINDRAG (so you can be + * sure that moveFn has been called at least once) + * numClicks is how many clicks we've registered within + * a doubleclick time + * e is the original mouseup event + * clickFn (optional) function(numClicks, e) + * executed on mouseup if we have NOT exceeded MINDRAG (ie moveFn + * has not been called at all) + * numClicks is how many clicks we've registered within + * a doubleclick time + * e is the original mousedown event + * clampFn (optional, function(dx, dy) return [dx2, dy2]) + * Provide custom clamping function for small displacements. + * By default, clamping is done using `minDrag` to x and y displacements + * independently. + */ +dragElement.init = function init(options) { + var gd = options.gd; + var numClicks = 1; + var doubleClickDelay = gd._context.doubleClickDelay; + var element = options.element; + + var startX, + startY, + newMouseDownTime, + cursor, + dragCover, + initialEvent, + initialTarget, + rightClick; + + if(!gd._mouseDownTime) gd._mouseDownTime = 0; + + element.style.pointerEvents = 'all'; + + element.onmousedown = onStart; + + if(!supportsPassive) { + element.ontouchstart = onStart; + } else { + if(element._ontouchstart) { + element.removeEventListener('touchstart', element._ontouchstart); + } + element._ontouchstart = onStart; + element.addEventListener('touchstart', onStart, {passive: false}); + } + + function _clampFn(dx, dy, minDrag) { + if(Math.abs(dx) < minDrag) dx = 0; + if(Math.abs(dy) < minDrag) dy = 0; + return [dx, dy]; + } + + var clampFn = options.clampFn || _clampFn; + + function onStart(e) { + // make dragging and dragged into properties of gd + // so that others can look at and modify them + gd._dragged = false; + gd._dragging = true; + var offset = pointerOffset(e); + startX = offset[0]; + startY = offset[1]; + initialTarget = e.target; + initialEvent = e; + rightClick = e.buttons === 2 || e.ctrlKey; + + // fix Fx.hover for touch events + if(typeof e.clientX === 'undefined' && typeof e.clientY === 'undefined') { + e.clientX = startX; + e.clientY = startY; + } + + newMouseDownTime = (new Date()).getTime(); + if(newMouseDownTime - gd._mouseDownTime < doubleClickDelay) { + // in a click train + numClicks += 1; + } else { + // new click train + numClicks = 1; + gd._mouseDownTime = newMouseDownTime; + } + + if(options.prepFn) options.prepFn(e, startX, startY); + + if(hasHover && !rightClick) { + dragCover = coverSlip(); + dragCover.style.cursor = window.getComputedStyle(element).cursor; + } else if(!hasHover) { + // document acts as a dragcover for mobile, bc we can't create dragcover dynamically + dragCover = document; + cursor = window.getComputedStyle(document.documentElement).cursor; + document.documentElement.style.cursor = window.getComputedStyle(element).cursor; + } + + document.addEventListener('mouseup', onDone); + document.addEventListener('touchend', onDone); + + if(options.dragmode !== false) { + e.preventDefault(); + document.addEventListener('mousemove', onMove); + document.addEventListener('touchmove', onMove, {passive: false}); + } + + return; + } + + function onMove(e) { + e.preventDefault(); + + var offset = pointerOffset(e); + var minDrag = options.minDrag || constants.MINDRAG; + var dxdy = clampFn(offset[0] - startX, offset[1] - startY, minDrag); + var dx = dxdy[0]; + var dy = dxdy[1]; + + if(dx || dy) { + gd._dragged = true; + dragElement.unhover(gd); + } + + if(gd._dragged && options.moveFn && !rightClick) { + gd._dragdata = { + element: element, + dx: dx, + dy: dy + }; + options.moveFn(dx, dy); + } + + return; + } + + function onDone(e) { + delete gd._dragdata; + + if(options.dragmode !== false) { + e.preventDefault(); + document.removeEventListener('mousemove', onMove); + document.removeEventListener('touchmove', onMove); + } + + document.removeEventListener('mouseup', onDone); + document.removeEventListener('touchend', onDone); + + if(hasHover) { + removeElement(dragCover); + } else if(cursor) { + dragCover.documentElement.style.cursor = cursor; + cursor = null; + } + + if(!gd._dragging) { + gd._dragged = false; + return; + } + gd._dragging = false; + + // don't count as a dblClick unless the mouseUp is also within + // the dblclick delay + if((new Date()).getTime() - gd._mouseDownTime > doubleClickDelay) { + numClicks = Math.max(numClicks - 1, 1); + } + + if(gd._dragged) { + if(options.doneFn) options.doneFn(); + } else { + if(options.clickFn) options.clickFn(numClicks, initialEvent); + + // If we haven't dragged, this should be a click. But because of the + // coverSlip changing the element, the natural system might not generate one, + // so we need to make our own. But right clicks don't normally generate + // click events, only contextmenu events, which happen on mousedown. + if(!rightClick) { + var e2; + + try { + e2 = new MouseEvent('click', e); + } catch(err) { + var offset = pointerOffset(e); + e2 = document.createEvent('MouseEvents'); + e2.initMouseEvent('click', + e.bubbles, e.cancelable, + e.view, e.detail, + e.screenX, e.screenY, + offset[0], offset[1], + e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, + e.button, e.relatedTarget); + } + + initialTarget.dispatchEvent(e2); + } + } + + gd._dragging = false; + gd._dragged = false; + return; + } +}; + +function coverSlip() { + var cover = document.createElement('div'); + + cover.className = 'dragcover'; + var cStyle = cover.style; + cStyle.position = 'fixed'; + cStyle.left = 0; + cStyle.right = 0; + cStyle.top = 0; + cStyle.bottom = 0; + cStyle.zIndex = 999999999; + cStyle.background = 'none'; + + document.body.appendChild(cover); + + return cover; +} + +dragElement.coverSlip = coverSlip; + +function pointerOffset(e) { + return mouseOffset( + e.changedTouches ? e.changedTouches[0] : e, + document.body + ); +} + +},{"../../lib":169,"../../plots/cartesian/constants":219,"./align":67,"./cursor":68,"./unhover":70,"has-hover":20,"has-passive-events":21,"mouse-event-offset":24}],70:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Events = _dereq_('../../lib/events'); +var throttle = _dereq_('../../lib/throttle'); +var getGraphDiv = _dereq_('../../lib/dom').getGraphDiv; + +var hoverConstants = _dereq_('../fx/constants'); + +var unhover = module.exports = {}; + +unhover.wrapped = function(gd, evt, subplot) { + gd = getGraphDiv(gd); + + // Important, clear any queued hovers + if(gd._fullLayout) { + throttle.clear(gd._fullLayout._uid + hoverConstants.HOVERID); + } + + unhover.raw(gd, evt, subplot); +}; + + +// remove hover effects on mouse out, and emit unhover event +unhover.raw = function raw(gd, evt) { + var fullLayout = gd._fullLayout; + var oldhoverdata = gd._hoverdata; + + if(!evt) evt = {}; + if(evt.target && + Events.triggerHandler(gd, 'plotly_beforehover', evt) === false) { + return; + } + + fullLayout._hoverlayer.selectAll('g').remove(); + fullLayout._hoverlayer.selectAll('line').remove(); + fullLayout._hoverlayer.selectAll('circle').remove(); + gd._hoverdata = undefined; + + if(evt.target && oldhoverdata) { + gd.emit('plotly_unhover', { + event: evt, + points: oldhoverdata + }); + } +}; + +},{"../../lib/dom":162,"../../lib/events":163,"../../lib/throttle":191,"../fx/constants":84}],71:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +exports.dash = { + valType: 'string', + // string type usually doesn't take values... this one should really be + // a special type or at least a special coercion function, from the GUI + // you only get these values but elsewhere the user can supply a list of + // dash lengths in px, and it will be honored + values: ['solid', 'dot', 'dash', 'longdash', 'dashdot', 'longdashdot'], + dflt: 'solid', + + editType: 'style', + +}; + +},{}],72:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var d3 = _dereq_('d3'); +var isNumeric = _dereq_('fast-isnumeric'); +var tinycolor = _dereq_('tinycolor2'); + +var Registry = _dereq_('../../registry'); +var Color = _dereq_('../color'); +var Colorscale = _dereq_('../colorscale'); +var Lib = _dereq_('../../lib'); +var svgTextUtils = _dereq_('../../lib/svg_text_utils'); + +var xmlnsNamespaces = _dereq_('../../constants/xmlns_namespaces'); +var alignment = _dereq_('../../constants/alignment'); +var LINE_SPACING = alignment.LINE_SPACING; +var DESELECTDIM = _dereq_('../../constants/interactions').DESELECTDIM; + +var subTypes = _dereq_('../../traces/scatter/subtypes'); +var makeBubbleSizeFn = _dereq_('../../traces/scatter/make_bubble_size_func'); + +var drawing = module.exports = {}; +var appendArrayPointValue = _dereq_('../fx/helpers').appendArrayPointValue; + +// ----------------------------------------------------- +// styling functions for plot elements +// ----------------------------------------------------- + +drawing.font = function(s, family, size, color) { + // also allow the form font(s, {family, size, color}) + if(Lib.isPlainObject(family)) { + color = family.color; + size = family.size; + family = family.family; + } + if(family) s.style('font-family', family); + if(size + 1) s.style('font-size', size + 'px'); + if(color) s.call(Color.fill, color); +}; + +/* + * Positioning helpers + * Note: do not use `setPosition` with nodes modified by + * `svgTextUtils.convertToTspans`. Use `svgTextUtils.positionText` + * instead, so that elements get updated to match. + */ +drawing.setPosition = function(s, x, y) { s.attr('x', x).attr('y', y); }; +drawing.setSize = function(s, w, h) { s.attr('width', w).attr('height', h); }; +drawing.setRect = function(s, x, y, w, h) { + s.call(drawing.setPosition, x, y).call(drawing.setSize, w, h); +}; + +/** Translate node + * + * @param {object} d : calcdata point item + * @param {sel} sel : d3 selction of node to translate + * @param {object} xa : corresponding full xaxis object + * @param {object} ya : corresponding full yaxis object + * + * @return {boolean} : + * true if selection got translated + * false if selection could not get translated + */ +drawing.translatePoint = function(d, sel, xa, ya) { + var x = xa.c2p(d.x); + var y = ya.c2p(d.y); + + if(isNumeric(x) && isNumeric(y) && sel.node()) { + // for multiline text this works better + if(sel.node().nodeName === 'text') { + sel.attr('x', x).attr('y', y); + } else { + sel.attr('transform', 'translate(' + x + ',' + y + ')'); + } + } else { + return false; + } + + return true; +}; + +drawing.translatePoints = function(s, xa, ya) { + s.each(function(d) { + var sel = d3.select(this); + drawing.translatePoint(d, sel, xa, ya); + }); +}; + +drawing.hideOutsideRangePoint = function(d, sel, xa, ya, xcalendar, ycalendar) { + sel.attr( + 'display', + (xa.isPtWithinRange(d, xcalendar) && ya.isPtWithinRange(d, ycalendar)) ? null : 'none' + ); +}; + +drawing.hideOutsideRangePoints = function(traceGroups, subplot) { + if(!subplot._hasClipOnAxisFalse) return; + + var xa = subplot.xaxis; + var ya = subplot.yaxis; + + traceGroups.each(function(d) { + var trace = d[0].trace; + var xcalendar = trace.xcalendar; + var ycalendar = trace.ycalendar; + var selector = Registry.traceIs(trace, 'bar-like') ? '.bartext' : '.point,.textpoint'; + + traceGroups.selectAll(selector).each(function(d) { + drawing.hideOutsideRangePoint(d, d3.select(this), xa, ya, xcalendar, ycalendar); + }); + }); +}; + +drawing.crispRound = function(gd, lineWidth, dflt) { + // for lines that disable antialiasing we want to + // make sure the width is an integer, and at least 1 if it's nonzero + + if(!lineWidth || !isNumeric(lineWidth)) return dflt || 0; + + // but not for static plots - these don't get antialiased anyway. + if(gd._context.staticPlot) return lineWidth; + + if(lineWidth < 1) return 1; + return Math.round(lineWidth); +}; + +drawing.singleLineStyle = function(d, s, lw, lc, ld) { + s.style('fill', 'none'); + var line = (((d || [])[0] || {}).trace || {}).line || {}; + var lw1 = lw || line.width || 0; + var dash = ld || line.dash || ''; + + Color.stroke(s, lc || line.color); + drawing.dashLine(s, dash, lw1); +}; + +drawing.lineGroupStyle = function(s, lw, lc, ld) { + s.style('fill', 'none') + .each(function(d) { + var line = (((d || [])[0] || {}).trace || {}).line || {}; + var lw1 = lw || line.width || 0; + var dash = ld || line.dash || ''; + + d3.select(this) + .call(Color.stroke, lc || line.color) + .call(drawing.dashLine, dash, lw1); + }); +}; + +drawing.dashLine = function(s, dash, lineWidth) { + lineWidth = +lineWidth || 0; + + dash = drawing.dashStyle(dash, lineWidth); + + s.style({ + 'stroke-dasharray': dash, + 'stroke-width': lineWidth + 'px' + }); +}; + +drawing.dashStyle = function(dash, lineWidth) { + lineWidth = +lineWidth || 1; + var dlw = Math.max(lineWidth, 3); + + if(dash === 'solid') dash = ''; + else if(dash === 'dot') dash = dlw + 'px,' + dlw + 'px'; + else if(dash === 'dash') dash = (3 * dlw) + 'px,' + (3 * dlw) + 'px'; + else if(dash === 'longdash') dash = (5 * dlw) + 'px,' + (5 * dlw) + 'px'; + else if(dash === 'dashdot') { + dash = (3 * dlw) + 'px,' + dlw + 'px,' + dlw + 'px,' + dlw + 'px'; + } else if(dash === 'longdashdot') { + dash = (5 * dlw) + 'px,' + (2 * dlw) + 'px,' + dlw + 'px,' + (2 * dlw) + 'px'; + } + // otherwise user wrote the dasharray themselves - leave it be + + return dash; +}; + +// Same as fillGroupStyle, except in this case the selection may be a transition +drawing.singleFillStyle = function(sel) { + var node = d3.select(sel.node()); + var data = node.data(); + var fillcolor = (((data[0] || [])[0] || {}).trace || {}).fillcolor; + if(fillcolor) { + sel.call(Color.fill, fillcolor); + } +}; + +drawing.fillGroupStyle = function(s) { + s.style('stroke-width', 0) + .each(function(d) { + var shape = d3.select(this); + // N.B. 'd' won't be a calcdata item when + // fill !== 'none' on a segment-less and marker-less trace + if(d[0].trace) { + shape.call(Color.fill, d[0].trace.fillcolor); + } + }); +}; + +var SYMBOLDEFS = _dereq_('./symbol_defs'); + +drawing.symbolNames = []; +drawing.symbolFuncs = []; +drawing.symbolNeedLines = {}; +drawing.symbolNoDot = {}; +drawing.symbolNoFill = {}; +drawing.symbolList = []; + +Object.keys(SYMBOLDEFS).forEach(function(k) { + var symDef = SYMBOLDEFS[k]; + drawing.symbolList = drawing.symbolList.concat( + [symDef.n, k, symDef.n + 100, k + '-open']); + drawing.symbolNames[symDef.n] = k; + drawing.symbolFuncs[symDef.n] = symDef.f; + if(symDef.needLine) { + drawing.symbolNeedLines[symDef.n] = true; + } + if(symDef.noDot) { + drawing.symbolNoDot[symDef.n] = true; + } else { + drawing.symbolList = drawing.symbolList.concat( + [symDef.n + 200, k + '-dot', symDef.n + 300, k + '-open-dot']); + } + if(symDef.noFill) { + drawing.symbolNoFill[symDef.n] = true; + } +}); + +var MAXSYMBOL = drawing.symbolNames.length; +// add a dot in the middle of the symbol +var DOTPATH = 'M0,0.5L0.5,0L0,-0.5L-0.5,0Z'; + +drawing.symbolNumber = function(v) { + if(typeof v === 'string') { + var vbase = 0; + if(v.indexOf('-open') > 0) { + vbase = 100; + v = v.replace('-open', ''); + } + if(v.indexOf('-dot') > 0) { + vbase += 200; + v = v.replace('-dot', ''); + } + v = drawing.symbolNames.indexOf(v); + if(v >= 0) { v += vbase; } + } + if((v % 100 >= MAXSYMBOL) || v >= 400) { return 0; } + return Math.floor(Math.max(v, 0)); +}; + +function makePointPath(symbolNumber, r) { + var base = symbolNumber % 100; + return drawing.symbolFuncs[base](r) + (symbolNumber >= 200 ? DOTPATH : ''); +} + +var HORZGRADIENT = {x1: 1, x2: 0, y1: 0, y2: 0}; +var VERTGRADIENT = {x1: 0, x2: 0, y1: 1, y2: 0}; +var stopFormatter = d3.format('~.1f'); +var gradientInfo = { + radial: {node: 'radialGradient'}, + radialreversed: {node: 'radialGradient', reversed: true}, + horizontal: {node: 'linearGradient', attrs: HORZGRADIENT}, + horizontalreversed: {node: 'linearGradient', attrs: HORZGRADIENT, reversed: true}, + vertical: {node: 'linearGradient', attrs: VERTGRADIENT}, + verticalreversed: {node: 'linearGradient', attrs: VERTGRADIENT, reversed: true} +}; + +/** + * gradient: create and apply a gradient fill + * + * @param {object} sel: d3 selection to apply this gradient to + * You can use `selection.call(Drawing.gradient, ...)` + * @param {DOM element} gd: the graph div `sel` is part of + * @param {string} gradientID: a unique (within this plot) identifier + * for this gradient, so that we don't create unnecessary definitions + * @param {string} type: 'radial', 'horizontal', or 'vertical', optionally with + * 'reversed' at the end. Normally radial goes center to edge, + * horizontal goes right to left, and vertical goes bottom to top + * @param {array} colorscale: as in attribute values, [[fraction, color], ...] + * @param {string} prop: the property to apply to, 'fill' or 'stroke' + */ +drawing.gradient = function(sel, gd, gradientID, type, colorscale, prop) { + var len = colorscale.length; + var info = gradientInfo[type]; + var colorStops = new Array(len); + for(var i = 0; i < len; i++) { + if(info.reversed) { + colorStops[len - 1 - i] = [stopFormatter((1 - colorscale[i][0]) * 100), colorscale[i][1]]; + } else { + colorStops[i] = [stopFormatter(colorscale[i][0] * 100), colorscale[i][1]]; + } + } + + var fullID = 'g' + gd._fullLayout._uid + '-' + gradientID; + + var gradient = gd._fullLayout._defs.select('.gradients') + .selectAll('#' + fullID) + .data([type + colorStops.join(';')], Lib.identity); + + gradient.exit().remove(); + + gradient.enter() + .append(info.node) + .each(function() { + var el = d3.select(this); + if(info.attrs) el.attr(info.attrs); + + el.attr('id', fullID); + + var stops = el.selectAll('stop') + .data(colorStops); + stops.exit().remove(); + stops.enter().append('stop'); + + stops.each(function(d) { + var tc = tinycolor(d[1]); + d3.select(this).attr({ + offset: d[0] + '%', + 'stop-color': Color.tinyRGB(tc), + 'stop-opacity': tc.getAlpha() + }); + }); + }); + + sel.style(prop, getFullUrl(fullID, gd)) + .style(prop + '-opacity', null); +}; + +/* + * Make the gradients container and clear out any previous gradients. + * We never collect all the gradients we need in one place, + * so we can't ever remove gradients that have stopped being useful, + * except all at once before a full redraw. + * The upside of this is arbitrary points can share gradient defs + */ +drawing.initGradients = function(gd) { + var gradientsGroup = Lib.ensureSingle(gd._fullLayout._defs, 'g', 'gradients'); + gradientsGroup.selectAll('linearGradient,radialGradient').remove(); +}; + + +drawing.pointStyle = function(s, trace, gd) { + if(!s.size()) return; + + var fns = drawing.makePointStyleFns(trace); + + s.each(function(d) { + drawing.singlePointStyle(d, d3.select(this), trace, fns, gd); + }); +}; + +drawing.singlePointStyle = function(d, sel, trace, fns, gd) { + var marker = trace.marker; + var markerLine = marker.line; + + sel.style('opacity', + fns.selectedOpacityFn ? fns.selectedOpacityFn(d) : + (d.mo === undefined ? marker.opacity : d.mo) + ); + + if(fns.ms2mrc) { + var r; + + // handle multi-trace graph edit case + if(d.ms === 'various' || marker.size === 'various') { + r = 3; + } else { + r = fns.ms2mrc(d.ms); + } + + // store the calculated size so hover can use it + d.mrc = r; + + if(fns.selectedSizeFn) { + r = d.mrc = fns.selectedSizeFn(d); + } + + // turn the symbol into a sanitized number + var x = drawing.symbolNumber(d.mx || marker.symbol) || 0; + + // save if this marker is open + // because that impacts how to handle colors + d.om = x % 200 >= 100; + + sel.attr('d', makePointPath(x, r)); + } + + var perPointGradient = false; + var fillColor, lineColor, lineWidth; + + // 'so' is suspected outliers, for box plots + if(d.so) { + lineWidth = markerLine.outlierwidth; + lineColor = markerLine.outliercolor; + fillColor = marker.outliercolor; + } else { + var markerLineWidth = (markerLine || {}).width; + + lineWidth = ( + d.mlw + 1 || + markerLineWidth + 1 || + // TODO: we need the latter for legends... can we get rid of it? + (d.trace ? (d.trace.marker.line || {}).width : 0) + 1 + ) - 1 || 0; + + if('mlc' in d) lineColor = d.mlcc = fns.lineScale(d.mlc); + // weird case: array wasn't long enough to apply to every point + else if(Lib.isArrayOrTypedArray(markerLine.color)) lineColor = Color.defaultLine; + else lineColor = markerLine.color; + + if(Lib.isArrayOrTypedArray(marker.color)) { + fillColor = Color.defaultLine; + perPointGradient = true; + } + + if('mc' in d) { + fillColor = d.mcc = fns.markerScale(d.mc); + } else { + fillColor = marker.color || 'rgba(0,0,0,0)'; + } + + if(fns.selectedColorFn) { + fillColor = fns.selectedColorFn(d); + } + } + + if(d.om) { + // open markers can't have zero linewidth, default to 1px, + // and use fill color as stroke color + sel.call(Color.stroke, fillColor) + .style({ + 'stroke-width': (lineWidth || 1) + 'px', + fill: 'none' + }); + } else { + sel.style('stroke-width', (d.isBlank ? 0 : lineWidth) + 'px'); + + var markerGradient = marker.gradient; + + var gradientType = d.mgt; + if(gradientType) perPointGradient = true; + else gradientType = markerGradient && markerGradient.type; + + // for legend - arrays will propagate through here, but we don't need + // to treat it as per-point. + if(Array.isArray(gradientType)) { + gradientType = gradientType[0]; + if(!gradientInfo[gradientType]) gradientType = 0; + } + + if(gradientType && gradientType !== 'none') { + var gradientColor = d.mgc; + if(gradientColor) perPointGradient = true; + else gradientColor = markerGradient.color; + + var gradientID = trace.uid; + if(perPointGradient) gradientID += '-' + d.i; + + drawing.gradient(sel, gd, gradientID, gradientType, + [[0, gradientColor], [1, fillColor]], 'fill'); + } else { + Color.fill(sel, fillColor); + } + + if(lineWidth) { + Color.stroke(sel, lineColor); + } + } +}; + +drawing.makePointStyleFns = function(trace) { + var out = {}; + var marker = trace.marker; + + // allow array marker and marker line colors to be + // scaled by given max and min to colorscales + out.markerScale = drawing.tryColorscale(marker, ''); + out.lineScale = drawing.tryColorscale(marker, 'line'); + + if(Registry.traceIs(trace, 'symbols')) { + out.ms2mrc = subTypes.isBubble(trace) ? + makeBubbleSizeFn(trace) : + function() { return (marker.size || 6) / 2; }; + } + + if(trace.selectedpoints) { + Lib.extendFlat(out, drawing.makeSelectedPointStyleFns(trace)); + } + + return out; +}; + +drawing.makeSelectedPointStyleFns = function(trace) { + var out = {}; + + var selectedAttrs = trace.selected || {}; + var unselectedAttrs = trace.unselected || {}; + + var marker = trace.marker || {}; + var selectedMarker = selectedAttrs.marker || {}; + var unselectedMarker = unselectedAttrs.marker || {}; + + var mo = marker.opacity; + var smo = selectedMarker.opacity; + var usmo = unselectedMarker.opacity; + var smoIsDefined = smo !== undefined; + var usmoIsDefined = usmo !== undefined; + + if(Lib.isArrayOrTypedArray(mo) || smoIsDefined || usmoIsDefined) { + out.selectedOpacityFn = function(d) { + var base = d.mo === undefined ? marker.opacity : d.mo; + + if(d.selected) { + return smoIsDefined ? smo : base; + } else { + return usmoIsDefined ? usmo : DESELECTDIM * base; + } + }; + } + + var mc = marker.color; + var smc = selectedMarker.color; + var usmc = unselectedMarker.color; + + if(smc || usmc) { + out.selectedColorFn = function(d) { + var base = d.mcc || mc; + + if(d.selected) { + return smc || base; + } else { + return usmc || base; + } + }; + } + + var ms = marker.size; + var sms = selectedMarker.size; + var usms = unselectedMarker.size; + var smsIsDefined = sms !== undefined; + var usmsIsDefined = usms !== undefined; + + if(Registry.traceIs(trace, 'symbols') && (smsIsDefined || usmsIsDefined)) { + out.selectedSizeFn = function(d) { + var base = d.mrc || ms / 2; + + if(d.selected) { + return smsIsDefined ? sms / 2 : base; + } else { + return usmsIsDefined ? usms / 2 : base; + } + }; + } + + return out; +}; + +drawing.makeSelectedTextStyleFns = function(trace) { + var out = {}; + + var selectedAttrs = trace.selected || {}; + var unselectedAttrs = trace.unselected || {}; + + var textFont = trace.textfont || {}; + var selectedTextFont = selectedAttrs.textfont || {}; + var unselectedTextFont = unselectedAttrs.textfont || {}; + + var tc = textFont.color; + var stc = selectedTextFont.color; + var utc = unselectedTextFont.color; + + out.selectedTextColorFn = function(d) { + var base = d.tc || tc; + + if(d.selected) { + return stc || base; + } else { + if(utc) return utc; + else return stc ? base : Color.addOpacity(base, DESELECTDIM); + } + }; + + return out; +}; + +drawing.selectedPointStyle = function(s, trace) { + if(!s.size() || !trace.selectedpoints) return; + + var fns = drawing.makeSelectedPointStyleFns(trace); + var marker = trace.marker || {}; + var seq = []; + + if(fns.selectedOpacityFn) { + seq.push(function(pt, d) { + pt.style('opacity', fns.selectedOpacityFn(d)); + }); + } + + if(fns.selectedColorFn) { + seq.push(function(pt, d) { + Color.fill(pt, fns.selectedColorFn(d)); + }); + } + + if(fns.selectedSizeFn) { + seq.push(function(pt, d) { + var mx = d.mx || marker.symbol || 0; + var mrc2 = fns.selectedSizeFn(d); + + pt.attr('d', makePointPath(drawing.symbolNumber(mx), mrc2)); + + // save for Drawing.selectedTextStyle + d.mrc2 = mrc2; + }); + } + + if(seq.length) { + s.each(function(d) { + var pt = d3.select(this); + for(var i = 0; i < seq.length; i++) { + seq[i](pt, d); + } + }); + } +}; + +drawing.tryColorscale = function(marker, prefix) { + var cont = prefix ? Lib.nestedProperty(marker, prefix).get() : marker; + + if(cont) { + var colorArray = cont.color; + if((cont.colorscale || cont._colorAx) && Lib.isArrayOrTypedArray(colorArray)) { + return Colorscale.makeColorScaleFuncFromTrace(cont); + } + } + return Lib.identity; +}; + +var TEXTOFFSETSIGN = { + start: 1, end: -1, middle: 0, bottom: 1, top: -1 +}; + +function textPointPosition(s, textPosition, fontSize, markerRadius) { + var group = d3.select(s.node().parentNode); + + var v = textPosition.indexOf('top') !== -1 ? + 'top' : + textPosition.indexOf('bottom') !== -1 ? 'bottom' : 'middle'; + var h = textPosition.indexOf('left') !== -1 ? + 'end' : + textPosition.indexOf('right') !== -1 ? 'start' : 'middle'; + + // if markers are shown, offset a little more than + // the nominal marker size + // ie 2/1.6 * nominal, bcs some markers are a bit bigger + var r = markerRadius ? markerRadius / 0.8 + 1 : 0; + + var numLines = (svgTextUtils.lineCount(s) - 1) * LINE_SPACING + 1; + var dx = TEXTOFFSETSIGN[h] * r; + var dy = fontSize * 0.75 + TEXTOFFSETSIGN[v] * r + + (TEXTOFFSETSIGN[v] - 1) * numLines * fontSize / 2; + + // fix the overall text group position + s.attr('text-anchor', h); + group.attr('transform', 'translate(' + dx + ',' + dy + ')'); +} + +function extracTextFontSize(d, trace) { + var fontSize = d.ts || trace.textfont.size; + return (isNumeric(fontSize) && fontSize > 0) ? fontSize : 0; +} + +// draw text at points +drawing.textPointStyle = function(s, trace, gd, inLegend) { + if(!s.size()) return; + + var selectedTextColorFn; + + if(trace.selectedpoints) { + var fns = drawing.makeSelectedTextStyleFns(trace); + selectedTextColorFn = fns.selectedTextColorFn; + } + + var template = trace.texttemplate; + // If styling text in legends, do not use texttemplate + if(inLegend) template = false; + s.each(function(d) { + var p = d3.select(this); + var text = Lib.extractOption(d, trace, template ? 'txt' : 'tx', template ? 'texttemplate' : 'text'); + + if(!text && text !== 0) { + p.remove(); + return; + } + + if(template) { + var pt = {}; + appendArrayPointValue(pt, trace, d.i); + text = Lib.texttemplateString(text, {}, gd._fullLayout._d3locale, pt, d, trace._meta || {}); + } + + var pos = d.tp || trace.textposition; + var fontSize = extracTextFontSize(d, trace); + var fontColor = selectedTextColorFn ? + selectedTextColorFn(d) : + (d.tc || trace.textfont.color); + + p.call(drawing.font, + d.tf || trace.textfont.family, + fontSize, + fontColor) + .text(text) + .call(svgTextUtils.convertToTspans, gd) + .call(textPointPosition, pos, fontSize, d.mrc); + }); +}; + +drawing.selectedTextStyle = function(s, trace) { + if(!s.size() || !trace.selectedpoints) return; + + var fns = drawing.makeSelectedTextStyleFns(trace); + + s.each(function(d) { + var tx = d3.select(this); + var tc = fns.selectedTextColorFn(d); + var tp = d.tp || trace.textposition; + var fontSize = extracTextFontSize(d, trace); + + Color.fill(tx, tc); + textPointPosition(tx, tp, fontSize, d.mrc2 || d.mrc); + }); +}; + +// generalized Catmull-Rom splines, per +// http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf +var CatmullRomExp = 0.5; +drawing.smoothopen = function(pts, smoothness) { + if(pts.length < 3) { return 'M' + pts.join('L');} + var path = 'M' + pts[0]; + var tangents = []; + var i; + for(i = 1; i < pts.length - 1; i++) { + tangents.push(makeTangent(pts[i - 1], pts[i], pts[i + 1], smoothness)); + } + path += 'Q' + tangents[0][0] + ' ' + pts[1]; + for(i = 2; i < pts.length - 1; i++) { + path += 'C' + tangents[i - 2][1] + ' ' + tangents[i - 1][0] + ' ' + pts[i]; + } + path += 'Q' + tangents[pts.length - 3][1] + ' ' + pts[pts.length - 1]; + return path; +}; + +drawing.smoothclosed = function(pts, smoothness) { + if(pts.length < 3) { return 'M' + pts.join('L') + 'Z'; } + var path = 'M' + pts[0]; + var pLast = pts.length - 1; + var tangents = [makeTangent(pts[pLast], pts[0], pts[1], smoothness)]; + var i; + for(i = 1; i < pLast; i++) { + tangents.push(makeTangent(pts[i - 1], pts[i], pts[i + 1], smoothness)); + } + tangents.push( + makeTangent(pts[pLast - 1], pts[pLast], pts[0], smoothness) + ); + + for(i = 1; i <= pLast; i++) { + path += 'C' + tangents[i - 1][1] + ' ' + tangents[i][0] + ' ' + pts[i]; + } + path += 'C' + tangents[pLast][1] + ' ' + tangents[0][0] + ' ' + pts[0] + 'Z'; + return path; +}; + +function makeTangent(prevpt, thispt, nextpt, smoothness) { + var d1x = prevpt[0] - thispt[0]; + var d1y = prevpt[1] - thispt[1]; + var d2x = nextpt[0] - thispt[0]; + var d2y = nextpt[1] - thispt[1]; + var d1a = Math.pow(d1x * d1x + d1y * d1y, CatmullRomExp / 2); + var d2a = Math.pow(d2x * d2x + d2y * d2y, CatmullRomExp / 2); + var numx = (d2a * d2a * d1x - d1a * d1a * d2x) * smoothness; + var numy = (d2a * d2a * d1y - d1a * d1a * d2y) * smoothness; + var denom1 = 3 * d2a * (d1a + d2a); + var denom2 = 3 * d1a * (d1a + d2a); + return [ + [ + d3.round(thispt[0] + (denom1 && numx / denom1), 2), + d3.round(thispt[1] + (denom1 && numy / denom1), 2) + ], [ + d3.round(thispt[0] - (denom2 && numx / denom2), 2), + d3.round(thispt[1] - (denom2 && numy / denom2), 2) + ] + ]; +} + +// step paths - returns a generator function for paths +// with the given step shape +var STEPPATH = { + hv: function(p0, p1) { + return 'H' + d3.round(p1[0], 2) + 'V' + d3.round(p1[1], 2); + }, + vh: function(p0, p1) { + return 'V' + d3.round(p1[1], 2) + 'H' + d3.round(p1[0], 2); + }, + hvh: function(p0, p1) { + return 'H' + d3.round((p0[0] + p1[0]) / 2, 2) + 'V' + + d3.round(p1[1], 2) + 'H' + d3.round(p1[0], 2); + }, + vhv: function(p0, p1) { + return 'V' + d3.round((p0[1] + p1[1]) / 2, 2) + 'H' + + d3.round(p1[0], 2) + 'V' + d3.round(p1[1], 2); + } +}; +var STEPLINEAR = function(p0, p1) { + return 'L' + d3.round(p1[0], 2) + ',' + d3.round(p1[1], 2); +}; +drawing.steps = function(shape) { + var onestep = STEPPATH[shape] || STEPLINEAR; + return function(pts) { + var path = 'M' + d3.round(pts[0][0], 2) + ',' + d3.round(pts[0][1], 2); + for(var i = 1; i < pts.length; i++) { + path += onestep(pts[i - 1], pts[i]); + } + return path; + }; +}; + +// off-screen svg render testing element, shared by the whole page +// uses the id 'js-plotly-tester' and stores it in drawing.tester +drawing.makeTester = function() { + var tester = Lib.ensureSingleById(d3.select('body'), 'svg', 'js-plotly-tester', function(s) { + s.attr(xmlnsNamespaces.svgAttrs) + .style({ + position: 'absolute', + left: '-10000px', + top: '-10000px', + width: '9000px', + height: '9000px', + 'z-index': '1' + }); + }); + + // browsers differ on how they describe the bounding rect of + // the svg if its contents spill over... so make a 1x1px + // reference point we can measure off of. + var testref = Lib.ensureSingle(tester, 'path', 'js-reference-point', function(s) { + s.attr('d', 'M0,0H1V1H0Z') + .style({ + 'stroke-width': 0, + fill: 'black' + }); + }); + + drawing.tester = tester; + drawing.testref = testref; +}; + +/* + * use our offscreen tester to get a clientRect for an element, + * in a reference frame where it isn't translated (or transformed) and + * its anchor point is at (0,0) + * always returns a copy of the bbox, so the caller can modify it safely + * + * @param {SVGElement} node: the element to measure. If possible this should be + * a or MathJax element that's already passed through + * `convertToTspans` because in that case we can cache the results, but it's + * possible to pass in any svg element. + * + * @param {boolean} inTester: is this element already in `drawing.tester`? + * If you are measuring a dummy element, rather than one you really intend + * to use on the plot, making it in `drawing.tester` in the first place + * allows us to test faster because it cuts out cloning and appending it. + * + * @param {string} hash: for internal use only, if we already know the cache key + * for this element beforehand. + * + * @return {object}: a plain object containing the width, height, left, right, + * top, and bottom of `node` + */ +drawing.savedBBoxes = {}; +var savedBBoxesCount = 0; +var maxSavedBBoxes = 10000; + +drawing.bBox = function(node, inTester, hash) { + /* + * Cache elements we've already measured so we don't have to + * remeasure the same thing many times + * We have a few bBox callers though who pass a node larger than + * a or a MathJax , such as an axis group containing many labels. + * These will not generate a hash (unless we figure out an appropriate + * hash key for them) and thus we will not hash them. + */ + if(!hash) hash = nodeHash(node); + var out; + if(hash) { + out = drawing.savedBBoxes[hash]; + if(out) return Lib.extendFlat({}, out); + } else if(node.childNodes.length === 1) { + /* + * If we have only one child element, which is itself hashable, make + * a new hash from this element plus its x,y,transform + * These bounding boxes *include* x,y,transform - mostly for use by + * callers trying to avoid overlaps (ie titles) + */ + var innerNode = node.childNodes[0]; + + hash = nodeHash(innerNode); + if(hash) { + var x = +innerNode.getAttribute('x') || 0; + var y = +innerNode.getAttribute('y') || 0; + var transform = innerNode.getAttribute('transform'); + + if(!transform) { + // in this case, just varying x and y, don't bother caching + // the final bBox because the alteration is quick. + var innerBB = drawing.bBox(innerNode, false, hash); + if(x) { + innerBB.left += x; + innerBB.right += x; + } + if(y) { + innerBB.top += y; + innerBB.bottom += y; + } + return innerBB; + } + /* + * else we have a transform - rather than make a complicated + * (and error-prone and probably slow) transform parser/calculator, + * just continue on calculating the boundingClientRect of the group + * and use the new composite hash to cache it. + * That said, `innerNode.transform.baseVal` is an array of + * `SVGTransform` objects, that *do* seem to have a nice matrix + * multiplication interface that we could use to avoid making + * another getBoundingClientRect call... + */ + hash += '~' + x + '~' + y + '~' + transform; + + out = drawing.savedBBoxes[hash]; + if(out) return Lib.extendFlat({}, out); + } + } + var testNode, tester; + if(inTester) { + testNode = node; + } else { + tester = drawing.tester.node(); + + // copy the node to test into the tester + testNode = node.cloneNode(true); + tester.appendChild(testNode); + } + + // standardize its position (and newline tspans if any) + d3.select(testNode) + .attr('transform', null) + .call(svgTextUtils.positionText, 0, 0); + + var testRect = testNode.getBoundingClientRect(); + var refRect = drawing.testref + .node() + .getBoundingClientRect(); + + if(!inTester) tester.removeChild(testNode); + + var bb = { + height: testRect.height, + width: testRect.width, + left: testRect.left - refRect.left, + top: testRect.top - refRect.top, + right: testRect.right - refRect.left, + bottom: testRect.bottom - refRect.top + }; + + // make sure we don't have too many saved boxes, + // or a long session could overload on memory + // by saving boxes for long-gone elements + if(savedBBoxesCount >= maxSavedBBoxes) { + drawing.savedBBoxes = {}; + savedBBoxesCount = 0; + } + + // cache this bbox + if(hash) drawing.savedBBoxes[hash] = bb; + savedBBoxesCount++; + + return Lib.extendFlat({}, bb); +}; + +// capture everything about a node (at least in our usage) that +// impacts its bounding box, given that bBox clears x, y, and transform +function nodeHash(node) { + var inputText = node.getAttribute('data-unformatted'); + if(inputText === null) return; + return inputText + + node.getAttribute('data-math') + + node.getAttribute('text-anchor') + + node.getAttribute('style'); +} + +/** + * Set clipPath URL in a way that work for all situations. + * + * In details, graphs on pages with HTML tags need to prepend + * the clip path ids with the page's base url EXCEPT during toImage exports. + * + * @param {d3 selection} s : node to add clip-path attribute + * @param {string} localId : local clip-path (w/o base url) id + * @param {DOM element || object} gd + * - context._baseUrl {string} + * - context._exportedPlot {boolean} + */ +drawing.setClipUrl = function(s, localId, gd) { + s.attr('clip-path', getFullUrl(localId, gd)); +}; + +function getFullUrl(localId, gd) { + if(!localId) return null; + + var context = gd._context; + var baseUrl = context._exportedPlot ? '' : (context._baseUrl || ''); + return 'url(\'' + baseUrl + '#' + localId + '\')'; +} + +drawing.getTranslate = function(element) { + // Note the separator [^\d] between x and y in this regex + // We generally use ',' but IE will convert it to ' ' + var re = /.*\btranslate\((-?\d*\.?\d*)[^-\d]*(-?\d*\.?\d*)[^\d].*/; + var getter = element.attr ? 'attr' : 'getAttribute'; + var transform = element[getter]('transform') || ''; + + var translate = transform.replace(re, function(match, p1, p2) { + return [p1, p2].join(' '); + }) + .split(' '); + + return { + x: +translate[0] || 0, + y: +translate[1] || 0 + }; +}; + +drawing.setTranslate = function(element, x, y) { + var re = /(\btranslate\(.*?\);?)/; + var getter = element.attr ? 'attr' : 'getAttribute'; + var setter = element.attr ? 'attr' : 'setAttribute'; + var transform = element[getter]('transform') || ''; + + x = x || 0; + y = y || 0; + + transform = transform.replace(re, '').trim(); + transform += ' translate(' + x + ', ' + y + ')'; + transform = transform.trim(); + + element[setter]('transform', transform); + + return transform; +}; + +drawing.getScale = function(element) { + var re = /.*\bscale\((\d*\.?\d*)[^\d]*(\d*\.?\d*)[^\d].*/; + var getter = element.attr ? 'attr' : 'getAttribute'; + var transform = element[getter]('transform') || ''; + + var translate = transform.replace(re, function(match, p1, p2) { + return [p1, p2].join(' '); + }) + .split(' '); + + return { + x: +translate[0] || 1, + y: +translate[1] || 1 + }; +}; + +drawing.setScale = function(element, x, y) { + var re = /(\bscale\(.*?\);?)/; + var getter = element.attr ? 'attr' : 'getAttribute'; + var setter = element.attr ? 'attr' : 'setAttribute'; + var transform = element[getter]('transform') || ''; + + x = x || 1; + y = y || 1; + + transform = transform.replace(re, '').trim(); + transform += ' scale(' + x + ', ' + y + ')'; + transform = transform.trim(); + + element[setter]('transform', transform); + + return transform; +}; + +var SCALE_RE = /\s*sc.*/; + +drawing.setPointGroupScale = function(selection, xScale, yScale) { + xScale = xScale || 1; + yScale = yScale || 1; + + if(!selection) return; + + // The same scale transform for every point: + var scale = (xScale === 1 && yScale === 1) ? + '' : + ' scale(' + xScale + ',' + yScale + ')'; + + selection.each(function() { + var t = (this.getAttribute('transform') || '').replace(SCALE_RE, ''); + t += scale; + t = t.trim(); + this.setAttribute('transform', t); + }); +}; + +var TEXT_POINT_LAST_TRANSLATION_RE = /translate\([^)]*\)\s*$/; + +drawing.setTextPointsScale = function(selection, xScale, yScale) { + if(!selection) return; + + selection.each(function() { + var transforms; + var el = d3.select(this); + var text = el.select('text'); + + if(!text.node()) return; + + var x = parseFloat(text.attr('x') || 0); + var y = parseFloat(text.attr('y') || 0); + + var existingTransform = (el.attr('transform') || '').match(TEXT_POINT_LAST_TRANSLATION_RE); + + if(xScale === 1 && yScale === 1) { + transforms = []; + } else { + transforms = [ + 'translate(' + x + ',' + y + ')', + 'scale(' + xScale + ',' + yScale + ')', + 'translate(' + (-x) + ',' + (-y) + ')', + ]; + } + + if(existingTransform) { + transforms.push(existingTransform); + } + + el.attr('transform', transforms.join(' ')); + }); +}; + +},{"../../constants/alignment":145,"../../constants/interactions":148,"../../constants/xmlns_namespaces":150,"../../lib":169,"../../lib/svg_text_utils":190,"../../registry":258,"../../traces/scatter/make_bubble_size_func":392,"../../traces/scatter/subtypes":399,"../color":51,"../colorscale":63,"../fx/helpers":86,"./symbol_defs":73,"d3":16,"fast-isnumeric":18,"tinycolor2":34}],73:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var d3 = _dereq_('d3'); + +/** Marker symbol definitions + * users can specify markers either by number or name + * add 100 (or '-open') and you get an open marker + * open markers have no fill and use line color as the stroke color + * add 200 (or '-dot') and you get a dot in the middle + * add both and you get both + */ + +module.exports = { + circle: { + n: 0, + f: function(r) { + var rs = d3.round(r, 2); + return 'M' + rs + ',0A' + rs + ',' + rs + ' 0 1,1 0,-' + rs + + 'A' + rs + ',' + rs + ' 0 0,1 ' + rs + ',0Z'; + } + }, + square: { + n: 1, + f: function(r) { + var rs = d3.round(r, 2); + return 'M' + rs + ',' + rs + 'H-' + rs + 'V-' + rs + 'H' + rs + 'Z'; + } + }, + diamond: { + n: 2, + f: function(r) { + var rd = d3.round(r * 1.3, 2); + return 'M' + rd + ',0L0,' + rd + 'L-' + rd + ',0L0,-' + rd + 'Z'; + } + }, + cross: { + n: 3, + f: function(r) { + var rc = d3.round(r * 0.4, 2); + var rc2 = d3.round(r * 1.2, 2); + return 'M' + rc2 + ',' + rc + 'H' + rc + 'V' + rc2 + 'H-' + rc + + 'V' + rc + 'H-' + rc2 + 'V-' + rc + 'H-' + rc + 'V-' + rc2 + + 'H' + rc + 'V-' + rc + 'H' + rc2 + 'Z'; + } + }, + x: { + n: 4, + f: function(r) { + var rx = d3.round(r * 0.8 / Math.sqrt(2), 2); + var ne = 'l' + rx + ',' + rx; + var se = 'l' + rx + ',-' + rx; + var sw = 'l-' + rx + ',-' + rx; + var nw = 'l-' + rx + ',' + rx; + return 'M0,' + rx + ne + se + sw + se + sw + nw + sw + nw + ne + nw + ne + 'Z'; + } + }, + 'triangle-up': { + n: 5, + f: function(r) { + var rt = d3.round(r * 2 / Math.sqrt(3), 2); + var r2 = d3.round(r / 2, 2); + var rs = d3.round(r, 2); + return 'M-' + rt + ',' + r2 + 'H' + rt + 'L0,-' + rs + 'Z'; + } + }, + 'triangle-down': { + n: 6, + f: function(r) { + var rt = d3.round(r * 2 / Math.sqrt(3), 2); + var r2 = d3.round(r / 2, 2); + var rs = d3.round(r, 2); + return 'M-' + rt + ',-' + r2 + 'H' + rt + 'L0,' + rs + 'Z'; + } + }, + 'triangle-left': { + n: 7, + f: function(r) { + var rt = d3.round(r * 2 / Math.sqrt(3), 2); + var r2 = d3.round(r / 2, 2); + var rs = d3.round(r, 2); + return 'M' + r2 + ',-' + rt + 'V' + rt + 'L-' + rs + ',0Z'; + } + }, + 'triangle-right': { + n: 8, + f: function(r) { + var rt = d3.round(r * 2 / Math.sqrt(3), 2); + var r2 = d3.round(r / 2, 2); + var rs = d3.round(r, 2); + return 'M-' + r2 + ',-' + rt + 'V' + rt + 'L' + rs + ',0Z'; + } + }, + 'triangle-ne': { + n: 9, + f: function(r) { + var r1 = d3.round(r * 0.6, 2); + var r2 = d3.round(r * 1.2, 2); + return 'M-' + r2 + ',-' + r1 + 'H' + r1 + 'V' + r2 + 'Z'; + } + }, + 'triangle-se': { + n: 10, + f: function(r) { + var r1 = d3.round(r * 0.6, 2); + var r2 = d3.round(r * 1.2, 2); + return 'M' + r1 + ',-' + r2 + 'V' + r1 + 'H-' + r2 + 'Z'; + } + }, + 'triangle-sw': { + n: 11, + f: function(r) { + var r1 = d3.round(r * 0.6, 2); + var r2 = d3.round(r * 1.2, 2); + return 'M' + r2 + ',' + r1 + 'H-' + r1 + 'V-' + r2 + 'Z'; + } + }, + 'triangle-nw': { + n: 12, + f: function(r) { + var r1 = d3.round(r * 0.6, 2); + var r2 = d3.round(r * 1.2, 2); + return 'M-' + r1 + ',' + r2 + 'V-' + r1 + 'H' + r2 + 'Z'; + } + }, + pentagon: { + n: 13, + f: function(r) { + var x1 = d3.round(r * 0.951, 2); + var x2 = d3.round(r * 0.588, 2); + var y0 = d3.round(-r, 2); + var y1 = d3.round(r * -0.309, 2); + var y2 = d3.round(r * 0.809, 2); + return 'M' + x1 + ',' + y1 + 'L' + x2 + ',' + y2 + 'H-' + x2 + + 'L-' + x1 + ',' + y1 + 'L0,' + y0 + 'Z'; + } + }, + hexagon: { + n: 14, + f: function(r) { + var y0 = d3.round(r, 2); + var y1 = d3.round(r / 2, 2); + var x = d3.round(r * Math.sqrt(3) / 2, 2); + return 'M' + x + ',-' + y1 + 'V' + y1 + 'L0,' + y0 + + 'L-' + x + ',' + y1 + 'V-' + y1 + 'L0,-' + y0 + 'Z'; + } + }, + hexagon2: { + n: 15, + f: function(r) { + var x0 = d3.round(r, 2); + var x1 = d3.round(r / 2, 2); + var y = d3.round(r * Math.sqrt(3) / 2, 2); + return 'M-' + x1 + ',' + y + 'H' + x1 + 'L' + x0 + + ',0L' + x1 + ',-' + y + 'H-' + x1 + 'L-' + x0 + ',0Z'; + } + }, + octagon: { + n: 16, + f: function(r) { + var a = d3.round(r * 0.924, 2); + var b = d3.round(r * 0.383, 2); + return 'M-' + b + ',-' + a + 'H' + b + 'L' + a + ',-' + b + 'V' + b + + 'L' + b + ',' + a + 'H-' + b + 'L-' + a + ',' + b + 'V-' + b + 'Z'; + } + }, + star: { + n: 17, + f: function(r) { + var rs = r * 1.4; + var x1 = d3.round(rs * 0.225, 2); + var x2 = d3.round(rs * 0.951, 2); + var x3 = d3.round(rs * 0.363, 2); + var x4 = d3.round(rs * 0.588, 2); + var y0 = d3.round(-rs, 2); + var y1 = d3.round(rs * -0.309, 2); + var y3 = d3.round(rs * 0.118, 2); + var y4 = d3.round(rs * 0.809, 2); + var y5 = d3.round(rs * 0.382, 2); + return 'M' + x1 + ',' + y1 + 'H' + x2 + 'L' + x3 + ',' + y3 + + 'L' + x4 + ',' + y4 + 'L0,' + y5 + 'L-' + x4 + ',' + y4 + + 'L-' + x3 + ',' + y3 + 'L-' + x2 + ',' + y1 + 'H-' + x1 + + 'L0,' + y0 + 'Z'; + } + }, + hexagram: { + n: 18, + f: function(r) { + var y = d3.round(r * 0.66, 2); + var x1 = d3.round(r * 0.38, 2); + var x2 = d3.round(r * 0.76, 2); + return 'M-' + x2 + ',0l-' + x1 + ',-' + y + 'h' + x2 + + 'l' + x1 + ',-' + y + 'l' + x1 + ',' + y + 'h' + x2 + + 'l-' + x1 + ',' + y + 'l' + x1 + ',' + y + 'h-' + x2 + + 'l-' + x1 + ',' + y + 'l-' + x1 + ',-' + y + 'h-' + x2 + 'Z'; + } + }, + 'star-triangle-up': { + n: 19, + f: function(r) { + var x = d3.round(r * Math.sqrt(3) * 0.8, 2); + var y1 = d3.round(r * 0.8, 2); + var y2 = d3.round(r * 1.6, 2); + var rc = d3.round(r * 4, 2); + var aPart = 'A ' + rc + ',' + rc + ' 0 0 1 '; + return 'M-' + x + ',' + y1 + aPart + x + ',' + y1 + + aPart + '0,-' + y2 + aPart + '-' + x + ',' + y1 + 'Z'; + } + }, + 'star-triangle-down': { + n: 20, + f: function(r) { + var x = d3.round(r * Math.sqrt(3) * 0.8, 2); + var y1 = d3.round(r * 0.8, 2); + var y2 = d3.round(r * 1.6, 2); + var rc = d3.round(r * 4, 2); + var aPart = 'A ' + rc + ',' + rc + ' 0 0 1 '; + return 'M' + x + ',-' + y1 + aPart + '-' + x + ',-' + y1 + + aPart + '0,' + y2 + aPart + x + ',-' + y1 + 'Z'; + } + }, + 'star-square': { + n: 21, + f: function(r) { + var rp = d3.round(r * 1.1, 2); + var rc = d3.round(r * 2, 2); + var aPart = 'A ' + rc + ',' + rc + ' 0 0 1 '; + return 'M-' + rp + ',-' + rp + aPart + '-' + rp + ',' + rp + + aPart + rp + ',' + rp + aPart + rp + ',-' + rp + + aPart + '-' + rp + ',-' + rp + 'Z'; + } + }, + 'star-diamond': { + n: 22, + f: function(r) { + var rp = d3.round(r * 1.4, 2); + var rc = d3.round(r * 1.9, 2); + var aPart = 'A ' + rc + ',' + rc + ' 0 0 1 '; + return 'M-' + rp + ',0' + aPart + '0,' + rp + + aPart + rp + ',0' + aPart + '0,-' + rp + + aPart + '-' + rp + ',0' + 'Z'; + } + }, + 'diamond-tall': { + n: 23, + f: function(r) { + var x = d3.round(r * 0.7, 2); + var y = d3.round(r * 1.4, 2); + return 'M0,' + y + 'L' + x + ',0L0,-' + y + 'L-' + x + ',0Z'; + } + }, + 'diamond-wide': { + n: 24, + f: function(r) { + var x = d3.round(r * 1.4, 2); + var y = d3.round(r * 0.7, 2); + return 'M0,' + y + 'L' + x + ',0L0,-' + y + 'L-' + x + ',0Z'; + } + }, + hourglass: { + n: 25, + f: function(r) { + var rs = d3.round(r, 2); + return 'M' + rs + ',' + rs + 'H-' + rs + 'L' + rs + ',-' + rs + 'H-' + rs + 'Z'; + }, + noDot: true + }, + bowtie: { + n: 26, + f: function(r) { + var rs = d3.round(r, 2); + return 'M' + rs + ',' + rs + 'V-' + rs + 'L-' + rs + ',' + rs + 'V-' + rs + 'Z'; + }, + noDot: true + }, + 'circle-cross': { + n: 27, + f: function(r) { + var rs = d3.round(r, 2); + return 'M0,' + rs + 'V-' + rs + 'M' + rs + ',0H-' + rs + + 'M' + rs + ',0A' + rs + ',' + rs + ' 0 1,1 0,-' + rs + + 'A' + rs + ',' + rs + ' 0 0,1 ' + rs + ',0Z'; + }, + needLine: true, + noDot: true + }, + 'circle-x': { + n: 28, + f: function(r) { + var rs = d3.round(r, 2); + var rc = d3.round(r / Math.sqrt(2), 2); + return 'M' + rc + ',' + rc + 'L-' + rc + ',-' + rc + + 'M' + rc + ',-' + rc + 'L-' + rc + ',' + rc + + 'M' + rs + ',0A' + rs + ',' + rs + ' 0 1,1 0,-' + rs + + 'A' + rs + ',' + rs + ' 0 0,1 ' + rs + ',0Z'; + }, + needLine: true, + noDot: true + }, + 'square-cross': { + n: 29, + f: function(r) { + var rs = d3.round(r, 2); + return 'M0,' + rs + 'V-' + rs + 'M' + rs + ',0H-' + rs + + 'M' + rs + ',' + rs + 'H-' + rs + 'V-' + rs + 'H' + rs + 'Z'; + }, + needLine: true, + noDot: true + }, + 'square-x': { + n: 30, + f: function(r) { + var rs = d3.round(r, 2); + return 'M' + rs + ',' + rs + 'L-' + rs + ',-' + rs + + 'M' + rs + ',-' + rs + 'L-' + rs + ',' + rs + + 'M' + rs + ',' + rs + 'H-' + rs + 'V-' + rs + 'H' + rs + 'Z'; + }, + needLine: true, + noDot: true + }, + 'diamond-cross': { + n: 31, + f: function(r) { + var rd = d3.round(r * 1.3, 2); + return 'M' + rd + ',0L0,' + rd + 'L-' + rd + ',0L0,-' + rd + 'Z' + + 'M0,-' + rd + 'V' + rd + 'M-' + rd + ',0H' + rd; + }, + needLine: true, + noDot: true + }, + 'diamond-x': { + n: 32, + f: function(r) { + var rd = d3.round(r * 1.3, 2); + var r2 = d3.round(r * 0.65, 2); + return 'M' + rd + ',0L0,' + rd + 'L-' + rd + ',0L0,-' + rd + 'Z' + + 'M-' + r2 + ',-' + r2 + 'L' + r2 + ',' + r2 + + 'M-' + r2 + ',' + r2 + 'L' + r2 + ',-' + r2; + }, + needLine: true, + noDot: true + }, + 'cross-thin': { + n: 33, + f: function(r) { + var rc = d3.round(r * 1.4, 2); + return 'M0,' + rc + 'V-' + rc + 'M' + rc + ',0H-' + rc; + }, + needLine: true, + noDot: true, + noFill: true + }, + 'x-thin': { + n: 34, + f: function(r) { + var rx = d3.round(r, 2); + return 'M' + rx + ',' + rx + 'L-' + rx + ',-' + rx + + 'M' + rx + ',-' + rx + 'L-' + rx + ',' + rx; + }, + needLine: true, + noDot: true, + noFill: true + }, + asterisk: { + n: 35, + f: function(r) { + var rc = d3.round(r * 1.2, 2); + var rs = d3.round(r * 0.85, 2); + return 'M0,' + rc + 'V-' + rc + 'M' + rc + ',0H-' + rc + + 'M' + rs + ',' + rs + 'L-' + rs + ',-' + rs + + 'M' + rs + ',-' + rs + 'L-' + rs + ',' + rs; + }, + needLine: true, + noDot: true, + noFill: true + }, + hash: { + n: 36, + f: function(r) { + var r1 = d3.round(r / 2, 2); + var r2 = d3.round(r, 2); + return 'M' + r1 + ',' + r2 + 'V-' + r2 + + 'm-' + r2 + ',0V' + r2 + + 'M' + r2 + ',' + r1 + 'H-' + r2 + + 'm0,-' + r2 + 'H' + r2; + }, + needLine: true, + noFill: true + }, + 'y-up': { + n: 37, + f: function(r) { + var x = d3.round(r * 1.2, 2); + var y0 = d3.round(r * 1.6, 2); + var y1 = d3.round(r * 0.8, 2); + return 'M-' + x + ',' + y1 + 'L0,0M' + x + ',' + y1 + 'L0,0M0,-' + y0 + 'L0,0'; + }, + needLine: true, + noDot: true, + noFill: true + }, + 'y-down': { + n: 38, + f: function(r) { + var x = d3.round(r * 1.2, 2); + var y0 = d3.round(r * 1.6, 2); + var y1 = d3.round(r * 0.8, 2); + return 'M-' + x + ',-' + y1 + 'L0,0M' + x + ',-' + y1 + 'L0,0M0,' + y0 + 'L0,0'; + }, + needLine: true, + noDot: true, + noFill: true + }, + 'y-left': { + n: 39, + f: function(r) { + var y = d3.round(r * 1.2, 2); + var x0 = d3.round(r * 1.6, 2); + var x1 = d3.round(r * 0.8, 2); + return 'M' + x1 + ',' + y + 'L0,0M' + x1 + ',-' + y + 'L0,0M-' + x0 + ',0L0,0'; + }, + needLine: true, + noDot: true, + noFill: true + }, + 'y-right': { + n: 40, + f: function(r) { + var y = d3.round(r * 1.2, 2); + var x0 = d3.round(r * 1.6, 2); + var x1 = d3.round(r * 0.8, 2); + return 'M-' + x1 + ',' + y + 'L0,0M-' + x1 + ',-' + y + 'L0,0M' + x0 + ',0L0,0'; + }, + needLine: true, + noDot: true, + noFill: true + }, + 'line-ew': { + n: 41, + f: function(r) { + var rc = d3.round(r * 1.4, 2); + return 'M' + rc + ',0H-' + rc; + }, + needLine: true, + noDot: true, + noFill: true + }, + 'line-ns': { + n: 42, + f: function(r) { + var rc = d3.round(r * 1.4, 2); + return 'M0,' + rc + 'V-' + rc; + }, + needLine: true, + noDot: true, + noFill: true + }, + 'line-ne': { + n: 43, + f: function(r) { + var rx = d3.round(r, 2); + return 'M' + rx + ',-' + rx + 'L-' + rx + ',' + rx; + }, + needLine: true, + noDot: true, + noFill: true + }, + 'line-nw': { + n: 44, + f: function(r) { + var rx = d3.round(r, 2); + return 'M' + rx + ',' + rx + 'L-' + rx + ',-' + rx; + }, + needLine: true, + noDot: true, + noFill: true + } +}; + +},{"d3":16}],74:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + + +module.exports = { + visible: { + valType: 'boolean', + + editType: 'calc', + + }, + type: { + valType: 'enumerated', + values: ['percent', 'constant', 'sqrt', 'data'], + + editType: 'calc', + + }, + symmetric: { + valType: 'boolean', + + editType: 'calc', + + }, + array: { + valType: 'data_array', + editType: 'calc', + + }, + arrayminus: { + valType: 'data_array', + editType: 'calc', + + }, + value: { + valType: 'number', + min: 0, + dflt: 10, + + editType: 'calc', + + }, + valueminus: { + valType: 'number', + min: 0, + dflt: 10, + + editType: 'calc', + + }, + traceref: { + valType: 'integer', + min: 0, + dflt: 0, + + editType: 'style' + }, + tracerefminus: { + valType: 'integer', + min: 0, + dflt: 0, + + editType: 'style' + }, + copy_ystyle: { + valType: 'boolean', + + editType: 'plot' + }, + copy_zstyle: { + valType: 'boolean', + + editType: 'style' + }, + color: { + valType: 'color', + + editType: 'style', + + }, + thickness: { + valType: 'number', + min: 0, + dflt: 2, + + editType: 'style', + + }, + width: { + valType: 'number', + min: 0, + + editType: 'plot', + + }, + editType: 'calc', + + _deprecated: { + opacity: { + valType: 'number', + + editType: 'style', + + } + } +}; + +},{}],75:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); + +var Registry = _dereq_('../../registry'); +var Axes = _dereq_('../../plots/cartesian/axes'); +var Lib = _dereq_('../../lib'); + +var makeComputeError = _dereq_('./compute_error'); + +module.exports = function calc(gd) { + var calcdata = gd.calcdata; + + for(var i = 0; i < calcdata.length; i++) { + var calcTrace = calcdata[i]; + var trace = calcTrace[0].trace; + + if(trace.visible === true && Registry.traceIs(trace, 'errorBarsOK')) { + var xa = Axes.getFromId(gd, trace.xaxis); + var ya = Axes.getFromId(gd, trace.yaxis); + calcOneAxis(calcTrace, trace, xa, 'x'); + calcOneAxis(calcTrace, trace, ya, 'y'); + } + } +}; + +function calcOneAxis(calcTrace, trace, axis, coord) { + var opts = trace['error_' + coord] || {}; + var isVisible = (opts.visible && ['linear', 'log'].indexOf(axis.type) !== -1); + var vals = []; + + if(!isVisible) return; + + var computeError = makeComputeError(opts); + + for(var i = 0; i < calcTrace.length; i++) { + var calcPt = calcTrace[i]; + + var iIn = calcPt.i; + + // for types that don't include `i` in each calcdata point + if(iIn === undefined) iIn = i; + + // for stacked area inserted points + // TODO: errorbars have been tested cursorily with stacked area, + // but not thoroughly. It's not even really clear what you want to do: + // Should it just be calculated based on that trace's size data? + // Should you add errors from below in quadrature? + // And what about normalization, where in principle the errors shrink + // again when you get up to the top end? + // One option would be to forbid errorbars with stacking until we + // decide how to handle these questions. + else if(iIn === null) continue; + + var calcCoord = calcPt[coord]; + + if(!isNumeric(axis.c2l(calcCoord))) continue; + + var errors = computeError(calcCoord, iIn); + if(isNumeric(errors[0]) && isNumeric(errors[1])) { + var shoe = calcPt[coord + 's'] = calcCoord - errors[0]; + var hat = calcPt[coord + 'h'] = calcCoord + errors[1]; + vals.push(shoe, hat); + } + } + + var axId = axis._id; + var baseExtremes = trace._extremes[axId]; + var extremes = Axes.findExtremes( + axis, + vals, + Lib.extendFlat({tozero: baseExtremes.opts.tozero}, {padded: true}) + ); + baseExtremes.min = baseExtremes.min.concat(extremes.min); + baseExtremes.max = baseExtremes.max.concat(extremes.max); +} + +},{"../../lib":169,"../../plots/cartesian/axes":213,"../../registry":258,"./compute_error":76,"fast-isnumeric":18}],76:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + + +/** + * Error bar computing function generator + * + * N.B. The generated function does not clean the dataPt entries. Non-numeric + * entries result in undefined error magnitudes. + * + * @param {object} opts error bar attributes + * + * @return {function} : + * @param {numeric} dataPt data point from where to compute the error magnitude + * @param {number} index index of dataPt in its corresponding data array + * @return {array} + * - error[0] : error magnitude in the negative direction + * - error[1] : " " " " positive " + */ +module.exports = function makeComputeError(opts) { + var type = opts.type; + var symmetric = opts.symmetric; + + if(type === 'data') { + var array = opts.array || []; + + if(symmetric) { + return function computeError(dataPt, index) { + var val = +(array[index]); + return [val, val]; + }; + } else { + var arrayminus = opts.arrayminus || []; + return function computeError(dataPt, index) { + var val = +array[index]; + var valMinus = +arrayminus[index]; + // in case one is present and the other is missing, fill in 0 + // so we still see the present one. Mostly useful during manual + // data entry. + if(!isNaN(val) || !isNaN(valMinus)) { + return [valMinus || 0, val || 0]; + } + return [NaN, NaN]; + }; + } + } else { + var computeErrorValue = makeComputeErrorValue(type, opts.value); + var computeErrorValueMinus = makeComputeErrorValue(type, opts.valueminus); + + if(symmetric || opts.valueminus === undefined) { + return function computeError(dataPt) { + var val = computeErrorValue(dataPt); + return [val, val]; + }; + } else { + return function computeError(dataPt) { + return [ + computeErrorValueMinus(dataPt), + computeErrorValue(dataPt) + ]; + }; + } + } +}; + +/** + * Compute error bar magnitude (for all types except data) + * + * @param {string} type error bar type + * @param {numeric} value error bar value + * + * @return {function} : + * @param {numeric} dataPt + */ +function makeComputeErrorValue(type, value) { + if(type === 'percent') { + return function(dataPt) { + return Math.abs(dataPt * value / 100); + }; + } + if(type === 'constant') { + return function() { + return Math.abs(value); + }; + } + if(type === 'sqrt') { + return function(dataPt) { + return Math.sqrt(Math.abs(dataPt)); + }; + } +} + +},{}],77:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); + +var Registry = _dereq_('../../registry'); +var Lib = _dereq_('../../lib'); +var Template = _dereq_('../../plot_api/plot_template'); + +var attributes = _dereq_('./attributes'); + + +module.exports = function(traceIn, traceOut, defaultColor, opts) { + var objName = 'error_' + opts.axis; + var containerOut = Template.newContainer(traceOut, objName); + var containerIn = traceIn[objName] || {}; + + function coerce(attr, dflt) { + return Lib.coerce(containerIn, containerOut, attributes, attr, dflt); + } + + var hasErrorBars = ( + containerIn.array !== undefined || + containerIn.value !== undefined || + containerIn.type === 'sqrt' + ); + + var visible = coerce('visible', hasErrorBars); + + if(visible === false) return; + + var type = coerce('type', 'array' in containerIn ? 'data' : 'percent'); + var symmetric = true; + + if(type !== 'sqrt') { + symmetric = coerce('symmetric', + !((type === 'data' ? 'arrayminus' : 'valueminus') in containerIn)); + } + + if(type === 'data') { + coerce('array'); + coerce('traceref'); + if(!symmetric) { + coerce('arrayminus'); + coerce('tracerefminus'); + } + } else if(type === 'percent' || type === 'constant') { + coerce('value'); + if(!symmetric) coerce('valueminus'); + } + + var copyAttr = 'copy_' + opts.inherit + 'style'; + if(opts.inherit) { + var inheritObj = traceOut['error_' + opts.inherit]; + if((inheritObj || {}).visible) { + coerce(copyAttr, !(containerIn.color || + isNumeric(containerIn.thickness) || + isNumeric(containerIn.width))); + } + } + if(!opts.inherit || !containerOut[copyAttr]) { + coerce('color', defaultColor); + coerce('thickness'); + coerce('width', Registry.traceIs(traceOut, 'gl3d') ? 0 : 4); + } +}; + +},{"../../lib":169,"../../plot_api/plot_template":203,"../../registry":258,"./attributes":74,"fast-isnumeric":18}],78:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var overrideAll = _dereq_('../../plot_api/edit_types').overrideAll; + +var attributes = _dereq_('./attributes'); + +var xyAttrs = { + error_x: Lib.extendFlat({}, attributes), + error_y: Lib.extendFlat({}, attributes) +}; +delete xyAttrs.error_x.copy_zstyle; +delete xyAttrs.error_y.copy_zstyle; +delete xyAttrs.error_y.copy_ystyle; + +var xyzAttrs = { + error_x: Lib.extendFlat({}, attributes), + error_y: Lib.extendFlat({}, attributes), + error_z: Lib.extendFlat({}, attributes) +}; +delete xyzAttrs.error_x.copy_ystyle; +delete xyzAttrs.error_y.copy_ystyle; +delete xyzAttrs.error_z.copy_ystyle; +delete xyzAttrs.error_z.copy_zstyle; + +module.exports = { + moduleType: 'component', + name: 'errorbars', + + schema: { + traces: { + scatter: xyAttrs, + bar: xyAttrs, + histogram: xyAttrs, + scatter3d: overrideAll(xyzAttrs, 'calc', 'nested'), + scattergl: overrideAll(xyAttrs, 'calc', 'nested') + } + }, + + supplyDefaults: _dereq_('./defaults'), + + calc: _dereq_('./calc'), + makeComputeError: _dereq_('./compute_error'), + + plot: _dereq_('./plot'), + style: _dereq_('./style'), + hoverInfo: hoverInfo +}; + +function hoverInfo(calcPoint, trace, hoverPoint) { + if((trace.error_y || {}).visible) { + hoverPoint.yerr = calcPoint.yh - calcPoint.y; + if(!trace.error_y.symmetric) hoverPoint.yerrneg = calcPoint.y - calcPoint.ys; + } + if((trace.error_x || {}).visible) { + hoverPoint.xerr = calcPoint.xh - calcPoint.x; + if(!trace.error_x.symmetric) hoverPoint.xerrneg = calcPoint.x - calcPoint.xs; + } +} + +},{"../../lib":169,"../../plot_api/edit_types":196,"./attributes":74,"./calc":75,"./compute_error":76,"./defaults":77,"./plot":79,"./style":80}],79:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var d3 = _dereq_('d3'); +var isNumeric = _dereq_('fast-isnumeric'); + +var Drawing = _dereq_('../drawing'); +var subTypes = _dereq_('../../traces/scatter/subtypes'); + +module.exports = function plot(gd, traces, plotinfo, transitionOpts) { + var isNew; + + var xa = plotinfo.xaxis; + var ya = plotinfo.yaxis; + + var hasAnimation = transitionOpts && transitionOpts.duration > 0; + + traces.each(function(d) { + var trace = d[0].trace; + // || {} is in case the trace (specifically scatterternary) + // doesn't support error bars at all, but does go through + // the scatter.plot mechanics, which calls ErrorBars.plot + // internally + var xObj = trace.error_x || {}; + var yObj = trace.error_y || {}; + + var keyFunc; + + if(trace.ids) { + keyFunc = function(d) {return d.id;}; + } + + var sparse = ( + subTypes.hasMarkers(trace) && + trace.marker.maxdisplayed > 0 + ); + + if(!yObj.visible && !xObj.visible) d = []; + + var errorbars = d3.select(this).selectAll('g.errorbar') + .data(d, keyFunc); + + errorbars.exit().remove(); + + if(!d.length) return; + + if(!xObj.visible) errorbars.selectAll('path.xerror').remove(); + if(!yObj.visible) errorbars.selectAll('path.yerror').remove(); + + errorbars.style('opacity', 1); + + var enter = errorbars.enter().append('g') + .classed('errorbar', true); + + if(hasAnimation) { + enter.style('opacity', 0).transition() + .duration(transitionOpts.duration) + .style('opacity', 1); + } + + Drawing.setClipUrl(errorbars, plotinfo.layerClipId, gd); + + errorbars.each(function(d) { + var errorbar = d3.select(this); + var coords = errorCoords(d, xa, ya); + + if(sparse && !d.vis) return; + + var path; + + var yerror = errorbar.select('path.yerror'); + if(yObj.visible && isNumeric(coords.x) && + isNumeric(coords.yh) && + isNumeric(coords.ys)) { + var yw = yObj.width; + + path = 'M' + (coords.x - yw) + ',' + + coords.yh + 'h' + (2 * yw) + // hat + 'm-' + yw + ',0V' + coords.ys; // bar + + + if(!coords.noYS) path += 'm-' + yw + ',0h' + (2 * yw); // shoe + + isNew = !yerror.size(); + + if(isNew) { + yerror = errorbar.append('path') + .style('vector-effect', 'non-scaling-stroke') + .classed('yerror', true); + } else if(hasAnimation) { + yerror = yerror + .transition() + .duration(transitionOpts.duration) + .ease(transitionOpts.easing); + } + + yerror.attr('d', path); + } else yerror.remove(); + + var xerror = errorbar.select('path.xerror'); + if(xObj.visible && isNumeric(coords.y) && + isNumeric(coords.xh) && + isNumeric(coords.xs)) { + var xw = (xObj.copy_ystyle ? yObj : xObj).width; + + path = 'M' + coords.xh + ',' + + (coords.y - xw) + 'v' + (2 * xw) + // hat + 'm0,-' + xw + 'H' + coords.xs; // bar + + if(!coords.noXS) path += 'm0,-' + xw + 'v' + (2 * xw); // shoe + + isNew = !xerror.size(); + + if(isNew) { + xerror = errorbar.append('path') + .style('vector-effect', 'non-scaling-stroke') + .classed('xerror', true); + } else if(hasAnimation) { + xerror = xerror + .transition() + .duration(transitionOpts.duration) + .ease(transitionOpts.easing); + } + + xerror.attr('d', path); + } else xerror.remove(); + }); + }); +}; + +// compute the coordinates of the error-bar objects +function errorCoords(d, xa, ya) { + var out = { + x: xa.c2p(d.x), + y: ya.c2p(d.y) + }; + + // calculate the error bar size and hat and shoe locations + if(d.yh !== undefined) { + out.yh = ya.c2p(d.yh); + out.ys = ya.c2p(d.ys); + + // if the shoes go off-scale (ie log scale, error bars past zero) + // clip the bar and hide the shoes + if(!isNumeric(out.ys)) { + out.noYS = true; + out.ys = ya.c2p(d.ys, true); + } + } + + if(d.xh !== undefined) { + out.xh = xa.c2p(d.xh); + out.xs = xa.c2p(d.xs); + + if(!isNumeric(out.xs)) { + out.noXS = true; + out.xs = xa.c2p(d.xs, true); + } + } + + return out; +} + +},{"../../traces/scatter/subtypes":399,"../drawing":72,"d3":16,"fast-isnumeric":18}],80:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var d3 = _dereq_('d3'); + +var Color = _dereq_('../color'); + + +module.exports = function style(traces) { + traces.each(function(d) { + var trace = d[0].trace; + var yObj = trace.error_y || {}; + var xObj = trace.error_x || {}; + + var s = d3.select(this); + + s.selectAll('path.yerror') + .style('stroke-width', yObj.thickness + 'px') + .call(Color.stroke, yObj.color); + + if(xObj.copy_ystyle) xObj = yObj; + + s.selectAll('path.xerror') + .style('stroke-width', xObj.thickness + 'px') + .call(Color.stroke, xObj.color); + }); +}; + +},{"../color":51,"d3":16}],81:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var fontAttrs = _dereq_('../../plots/font_attributes'); +var hoverLabelAttrs = _dereq_('./layout_attributes').hoverlabel; +var extendFlat = _dereq_('../../lib/extend').extendFlat; + +module.exports = { + hoverlabel: { + bgcolor: extendFlat({}, hoverLabelAttrs.bgcolor, { + arrayOk: true, + + }), + bordercolor: extendFlat({}, hoverLabelAttrs.bordercolor, { + arrayOk: true, + + }), + font: fontAttrs({ + arrayOk: true, + editType: 'none', + + }), + align: extendFlat({}, hoverLabelAttrs.align, {arrayOk: true}), + namelength: extendFlat({}, hoverLabelAttrs.namelength, {arrayOk: true}), + editType: 'none' + } +}; + +},{"../../lib/extend":164,"../../plots/font_attributes":239,"./layout_attributes":90}],82:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var Registry = _dereq_('../../registry'); + +module.exports = function calc(gd) { + var calcdata = gd.calcdata; + var fullLayout = gd._fullLayout; + + function makeCoerceHoverInfo(trace) { + return function(val) { + return Lib.coerceHoverinfo({hoverinfo: val}, {_module: trace._module}, fullLayout); + }; + } + + for(var i = 0; i < calcdata.length; i++) { + var cd = calcdata[i]; + var trace = cd[0].trace; + + // don't include hover calc fields for pie traces + // as calcdata items might be sorted by value and + // won't match the data array order. + if(Registry.traceIs(trace, 'pie-like')) continue; + + var fillFn = Registry.traceIs(trace, '2dMap') ? paste : Lib.fillArray; + + fillFn(trace.hoverinfo, cd, 'hi', makeCoerceHoverInfo(trace)); + + if(trace.hovertemplate) fillFn(trace.hovertemplate, cd, 'ht'); + + if(!trace.hoverlabel) continue; + + fillFn(trace.hoverlabel.bgcolor, cd, 'hbg'); + fillFn(trace.hoverlabel.bordercolor, cd, 'hbc'); + fillFn(trace.hoverlabel.font.size, cd, 'hts'); + fillFn(trace.hoverlabel.font.color, cd, 'htc'); + fillFn(trace.hoverlabel.font.family, cd, 'htf'); + fillFn(trace.hoverlabel.namelength, cd, 'hnl'); + fillFn(trace.hoverlabel.align, cd, 'hta'); + } +}; + +function paste(traceAttr, cd, cdAttr, fn) { + fn = fn || Lib.identity; + + if(Array.isArray(traceAttr)) { + cd[0][cdAttr] = fn(traceAttr); + } +} + +},{"../../lib":169,"../../registry":258}],83:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Registry = _dereq_('../../registry'); +var hover = _dereq_('./hover').hover; + +module.exports = function click(gd, evt, subplot) { + var annotationsDone = Registry.getComponentMethod('annotations', 'onClick')(gd, gd._hoverdata); + + // fallback to fail-safe in case the plot type's hover method doesn't pass the subplot. + // Ternary, for example, didn't, but it was caught because tested. + if(subplot !== undefined) { + // The true flag at the end causes it to re-run the hover computation to figure out *which* + // point is being clicked. Without this, clicking is somewhat unreliable. + hover(gd, evt, subplot, true); + } + + function emitClick() { gd.emit('plotly_click', {points: gd._hoverdata, event: evt}); } + + if(gd._hoverdata && evt && evt.target) { + if(annotationsDone && annotationsDone.then) { + annotationsDone.then(emitClick); + } else emitClick(); + + // why do we get a double event without this??? + if(evt.stopImmediatePropagation) evt.stopImmediatePropagation(); + } +}; + +},{"../../registry":258,"./hover":87}],84:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + // hover labels for multiple horizontal bars get tilted by this angle + YANGLE: 60, + + // size and display constants for hover text + + // pixel size of hover arrows + HOVERARROWSIZE: 6, + // pixels padding around text + HOVERTEXTPAD: 3, + // hover font + HOVERFONTSIZE: 13, + HOVERFONT: 'Arial, sans-serif', + + // minimum time (msec) between hover calls + HOVERMINTIME: 50, + + // ID suffix (with fullLayout._uid) for hover events in the throttle cache + HOVERID: '-hover' +}; + +},{}],85:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var attributes = _dereq_('./attributes'); +var handleHoverLabelDefaults = _dereq_('./hoverlabel_defaults'); + +module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { + function coerce(attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); + } + + var opts = Lib.extendFlat({}, layout.hoverlabel); + if(traceOut.hovertemplate) opts.namelength = -1; + + handleHoverLabelDefaults(traceIn, traceOut, coerce, opts); +}; + +},{"../../lib":169,"./attributes":81,"./hoverlabel_defaults":88}],86:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); + +// look for either subplot or xaxis and yaxis attributes +// does not handle splom case +exports.getSubplot = function getSubplot(trace) { + return trace.subplot || (trace.xaxis + trace.yaxis) || trace.geo; +}; + +// is trace in given list of subplots? +// does handle splom case +exports.isTraceInSubplots = function isTraceInSubplots(trace, subplots) { + if(trace.type === 'splom') { + var xaxes = trace.xaxes || []; + var yaxes = trace.yaxes || []; + for(var i = 0; i < xaxes.length; i++) { + for(var j = 0; j < yaxes.length; j++) { + if(subplots.indexOf(xaxes[i] + yaxes[j]) !== -1) { + return true; + } + } + } + return false; + } + + return subplots.indexOf(exports.getSubplot(trace)) !== -1; +}; + +// convenience functions for mapping all relevant axes +exports.flat = function flat(subplots, v) { + var out = new Array(subplots.length); + for(var i = 0; i < subplots.length; i++) { + out[i] = v; + } + return out; +}; + +exports.p2c = function p2c(axArray, v) { + var out = new Array(axArray.length); + for(var i = 0; i < axArray.length; i++) { + out[i] = axArray[i].p2c(v); + } + return out; +}; + +exports.getDistanceFunction = function getDistanceFunction(mode, dx, dy, dxy) { + if(mode === 'closest') return dxy || exports.quadrature(dx, dy); + return mode === 'x' ? dx : dy; +}; + +exports.getClosest = function getClosest(cd, distfn, pointData) { + // do we already have a point number? (array mode only) + if(pointData.index !== false) { + if(pointData.index >= 0 && pointData.index < cd.length) { + pointData.distance = 0; + } else pointData.index = false; + } else { + // apply the distance function to each data point + // this is the longest loop... if this bogs down, we may need + // to create pre-sorted data (by x or y), not sure how to + // do this for 'closest' + for(var i = 0; i < cd.length; i++) { + var newDistance = distfn(cd[i]); + if(newDistance <= pointData.distance) { + pointData.index = i; + pointData.distance = newDistance; + } + } + } + return pointData; +}; + +/* + * pseudo-distance function for hover effects on areas: inside the region + * distance is finite (`passVal`), outside it's Infinity. + * + * @param {number} v0: signed difference between the current position and the left edge + * @param {number} v1: signed difference between the current position and the right edge + * @param {number} passVal: the value to return on success + */ +exports.inbox = function inbox(v0, v1, passVal) { + return (v0 * v1 < 0 || v0 === 0) ? passVal : Infinity; +}; + +exports.quadrature = function quadrature(dx, dy) { + return function(di) { + var x = dx(di); + var y = dy(di); + return Math.sqrt(x * x + y * y); + }; +}; + +/** Fill event data point object for hover and selection. + * Invokes _module.eventData if present. + * + * N.B. note that point 'index' corresponds to input data array index + * whereas 'number' is its post-transform version. + * + * If the hovered/selected pt corresponds to an multiple input points + * (e.g. for histogram and transformed traces), 'pointNumbers` and 'pointIndices' + * are include in the event data. + * + * @param {object} pt + * @param {object} trace + * @param {object} cd + * @return {object} + */ +exports.makeEventData = function makeEventData(pt, trace, cd) { + // hover uses 'index', select uses 'pointNumber' + var pointNumber = 'index' in pt ? pt.index : pt.pointNumber; + + var out = { + data: trace._input, + fullData: trace, + curveNumber: trace.index, + pointNumber: pointNumber + }; + + if(trace._indexToPoints) { + var pointIndices = trace._indexToPoints[pointNumber]; + + if(pointIndices.length === 1) { + out.pointIndex = pointIndices[0]; + } else { + out.pointIndices = pointIndices; + } + } else { + out.pointIndex = pointNumber; + } + + if(trace._module.eventData) { + out = trace._module.eventData(out, pt, trace, cd, pointNumber); + } else { + if('xVal' in pt) out.x = pt.xVal; + else if('x' in pt) out.x = pt.x; + + if('yVal' in pt) out.y = pt.yVal; + else if('y' in pt) out.y = pt.y; + + if(pt.xa) out.xaxis = pt.xa; + if(pt.ya) out.yaxis = pt.ya; + if(pt.zLabelVal !== undefined) out.z = pt.zLabelVal; + } + + exports.appendArrayPointValue(out, trace, pointNumber); + + return out; +}; + +/** Appends values inside array attributes corresponding to given point number + * + * @param {object} pointData : point data object (gets mutated here) + * @param {object} trace : full trace object + * @param {number|Array(number)} pointNumber : point number. May be a length-2 array + * [row, col] to dig into 2D arrays + */ +exports.appendArrayPointValue = function(pointData, trace, pointNumber) { + var arrayAttrs = trace._arrayAttrs; + + if(!arrayAttrs) { + return; + } + + for(var i = 0; i < arrayAttrs.length; i++) { + var astr = arrayAttrs[i]; + var key = getPointKey(astr); + + if(pointData[key] === undefined) { + var val = Lib.nestedProperty(trace, astr).get(); + var pointVal = getPointData(val, pointNumber); + + if(pointVal !== undefined) pointData[key] = pointVal; + } + } +}; + +/** + * Appends values inside array attributes corresponding to given point number array + * For use when pointData references a plot entity that arose (or potentially arose) + * from multiple points in the input data + * + * @param {object} pointData : point data object (gets mutated here) + * @param {object} trace : full trace object + * @param {Array(number)|Array(Array(number))} pointNumbers : Array of point numbers. + * Each entry in the array may itself be a length-2 array [row, col] to dig into 2D arrays + */ +exports.appendArrayMultiPointValues = function(pointData, trace, pointNumbers) { + var arrayAttrs = trace._arrayAttrs; + + if(!arrayAttrs) { + return; + } + + for(var i = 0; i < arrayAttrs.length; i++) { + var astr = arrayAttrs[i]; + var key = getPointKey(astr); + + if(pointData[key] === undefined) { + var val = Lib.nestedProperty(trace, astr).get(); + var keyVal = new Array(pointNumbers.length); + + for(var j = 0; j < pointNumbers.length; j++) { + keyVal[j] = getPointData(val, pointNumbers[j]); + } + pointData[key] = keyVal; + } + } +}; + +var pointKeyMap = { + ids: 'id', + locations: 'location', + labels: 'label', + values: 'value', + 'marker.colors': 'color', + parents: 'parent' +}; + +function getPointKey(astr) { + return pointKeyMap[astr] || astr; +} + +function getPointData(val, pointNumber) { + if(Array.isArray(pointNumber)) { + if(Array.isArray(val) && Array.isArray(val[pointNumber[0]])) { + return val[pointNumber[0]][pointNumber[1]]; + } + } else { + return val[pointNumber]; + } +} + +},{"../../lib":169}],87:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); +var isNumeric = _dereq_('fast-isnumeric'); +var tinycolor = _dereq_('tinycolor2'); + +var Lib = _dereq_('../../lib'); +var Events = _dereq_('../../lib/events'); +var svgTextUtils = _dereq_('../../lib/svg_text_utils'); +var overrideCursor = _dereq_('../../lib/override_cursor'); +var Drawing = _dereq_('../drawing'); +var Color = _dereq_('../color'); +var dragElement = _dereq_('../dragelement'); +var Axes = _dereq_('../../plots/cartesian/axes'); +var Registry = _dereq_('../../registry'); + +var helpers = _dereq_('./helpers'); +var constants = _dereq_('./constants'); + +// hover labels for multiple horizontal bars get tilted by some angle, +// then need to be offset differently if they overlap +var YANGLE = constants.YANGLE; +var YA_RADIANS = Math.PI * YANGLE / 180; + +// expansion of projected height +var YFACTOR = 1 / Math.sin(YA_RADIANS); + +// to make the appropriate post-rotation x offset, +// you need both x and y offsets +var YSHIFTX = Math.cos(YA_RADIANS); +var YSHIFTY = Math.sin(YA_RADIANS); + +// size and display constants for hover text +var HOVERARROWSIZE = constants.HOVERARROWSIZE; +var HOVERTEXTPAD = constants.HOVERTEXTPAD; + +// fx.hover: highlight data on hover +// evt can be a mousemove event, or an object with data about what points +// to hover on +// {xpx,ypx[,hovermode]} - pixel locations from top left +// (with optional overriding hovermode) +// {xval,yval[,hovermode]} - data values +// [{curveNumber,(pointNumber|xval and/or yval)}] - +// array of specific points to highlight +// pointNumber is a single integer if gd.data[curveNumber] is 1D, +// or a two-element array if it's 2D +// xval and yval are data values, +// 1D data may specify either or both, +// 2D data must specify both +// subplot is an id string (default "xy") +// makes use of gl.hovermode, which can be: +// x (find the points with the closest x values, ie a column), +// closest (find the single closest point) +// internally there are two more that occasionally get used: +// y (pick out a row - only used for multiple horizontal bar charts) +// array (used when the user specifies an explicit +// array of points to hover on) +// +// We wrap the hovers in a timer, to limit their frequency. +// The actual rendering is done by private function _hover. +exports.hover = function hover(gd, evt, subplot, noHoverEvent) { + gd = Lib.getGraphDiv(gd); + + Lib.throttle( + gd._fullLayout._uid + constants.HOVERID, + constants.HOVERMINTIME, + function() { _hover(gd, evt, subplot, noHoverEvent); } + ); +}; + +/* + * Draw a single hover item or an array of hover item in a pre-existing svg container somewhere + * hoverItem should have keys: + * - x and y (or x0, x1, y0, and y1): + * the pixel position to mark, relative to opts.container + * - xLabel, yLabel, zLabel, text, and name: + * info to go in the label + * - color: + * the background color for the label. + * - idealAlign (optional): + * 'left' or 'right' for which side of the x/y box to try to put this on first + * - borderColor (optional): + * color for the border, defaults to strongest contrast with color + * - fontFamily (optional): + * string, the font for this label, defaults to constants.HOVERFONT + * - fontSize (optional): + * the label font size, defaults to constants.HOVERFONTSIZE + * - fontColor (optional): + * defaults to borderColor + * opts should have keys: + * - bgColor: + * the background color this is against, used if the trace is + * non-opaque, and for the name, which goes outside the box + * - container: + * a or element to add the hover label to + * - outerContainer: + * normally a parent of `container`, sets the bounding box to use to + * constrain the hover label and determine whether to show it on the left or right + * opts can have optional keys: + * - anchorIndex: + the index of the hover item used as an anchor for positioning. + The other hover items will be pushed up or down to prevent overlap. + */ +exports.loneHover = function loneHover(hoverItems, opts) { + var multiHover = true; + if(!Array.isArray(hoverItems)) { + multiHover = false; + hoverItems = [hoverItems]; + } + + var pointsData = hoverItems.map(function(hoverItem) { + return { + color: hoverItem.color || Color.defaultLine, + x0: hoverItem.x0 || hoverItem.x || 0, + x1: hoverItem.x1 || hoverItem.x || 0, + y0: hoverItem.y0 || hoverItem.y || 0, + y1: hoverItem.y1 || hoverItem.y || 0, + xLabel: hoverItem.xLabel, + yLabel: hoverItem.yLabel, + zLabel: hoverItem.zLabel, + text: hoverItem.text, + name: hoverItem.name, + idealAlign: hoverItem.idealAlign, + + // optional extra bits of styling + borderColor: hoverItem.borderColor, + fontFamily: hoverItem.fontFamily, + fontSize: hoverItem.fontSize, + fontColor: hoverItem.fontColor, + nameLength: hoverItem.nameLength, + textAlign: hoverItem.textAlign, + + // filler to make createHoverText happy + trace: hoverItem.trace || { + index: 0, + hoverinfo: '' + }, + xa: {_offset: 0}, + ya: {_offset: 0}, + index: 0, + + hovertemplate: hoverItem.hovertemplate || false, + eventData: hoverItem.eventData || false, + hovertemplateLabels: hoverItem.hovertemplateLabels || false, + }; + }); + + var container3 = d3.select(opts.container); + var outerContainer3 = opts.outerContainer ? d3.select(opts.outerContainer) : container3; + + var fullOpts = { + hovermode: 'closest', + rotateLabels: false, + bgColor: opts.bgColor || Color.background, + container: container3, + outerContainer: outerContainer3 + }; + + var hoverLabel = createHoverText(pointsData, fullOpts, opts.gd); + + // Fix vertical overlap + var tooltipSpacing = 5; + var lastBottomY = 0; + var anchor = 0; + hoverLabel + .sort(function(a, b) {return a.y0 - b.y0;}) + .each(function(d, i) { + var topY = d.y0 - d.by / 2; + + if((topY - tooltipSpacing) < lastBottomY) { + d.offset = (lastBottomY - topY) + tooltipSpacing; + } else { + d.offset = 0; + } + + lastBottomY = topY + d.by + d.offset; + + if(i === opts.anchorIndex || 0) anchor = d.offset; + }) + .each(function(d) { + d.offset -= anchor; + }); + + alignHoverText(hoverLabel, fullOpts.rotateLabels); + + return multiHover ? hoverLabel : hoverLabel.node(); +}; + +// The actual implementation is here: +function _hover(gd, evt, subplot, noHoverEvent) { + if(!subplot) subplot = 'xy'; + + // if the user passed in an array of subplots, + // use those instead of finding overlayed plots + var subplots = Array.isArray(subplot) ? subplot : [subplot]; + + var fullLayout = gd._fullLayout; + var plots = fullLayout._plots || []; + var plotinfo = plots[subplot]; + var hasCartesian = fullLayout._has('cartesian'); + + // list of all overlaid subplots to look at + if(plotinfo) { + var overlayedSubplots = plotinfo.overlays.map(function(pi) { + return pi.id; + }); + + subplots = subplots.concat(overlayedSubplots); + } + + var len = subplots.length; + var xaArray = new Array(len); + var yaArray = new Array(len); + var supportsCompare = false; + + for(var i = 0; i < len; i++) { + var spId = subplots[i]; + + if(plots[spId]) { + // 'cartesian' case + supportsCompare = true; + xaArray[i] = plots[spId].xaxis; + yaArray[i] = plots[spId].yaxis; + } else if(fullLayout[spId] && fullLayout[spId]._subplot) { + // other subplot types + var _subplot = fullLayout[spId]._subplot; + xaArray[i] = _subplot.xaxis; + yaArray[i] = _subplot.yaxis; + } else { + Lib.warn('Unrecognized subplot: ' + spId); + return; + } + } + + var hovermode = evt.hovermode || fullLayout.hovermode; + + if(hovermode && !supportsCompare) hovermode = 'closest'; + + if(['x', 'y', 'closest'].indexOf(hovermode) === -1 || !gd.calcdata || + gd.querySelector('.zoombox') || gd._dragging) { + return dragElement.unhoverRaw(gd, evt); + } + + var hoverdistance = fullLayout.hoverdistance === -1 ? Infinity : fullLayout.hoverdistance; + var spikedistance = fullLayout.spikedistance === -1 ? Infinity : fullLayout.spikedistance; + + // hoverData: the set of candidate points we've found to highlight + var hoverData = []; + + // searchData: the data to search in. Mostly this is just a copy of + // gd.calcdata, filtered to the subplot and overlays we're on + // but if a point array is supplied it will be a mapping + // of indicated curves + var searchData = []; + + // [x|y]valArray: the axis values of the hover event + // mapped onto each of the currently selected overlaid subplots + var xvalArray, yvalArray; + + var itemnum, curvenum, cd, trace, subplotId, subploti, mode, + xval, yval, pointData, closedataPreviousLength; + + // spikePoints: the set of candidate points we've found to draw spikes to + var spikePoints = { + hLinePoint: null, + vLinePoint: null + }; + + // does subplot have one (or more) horizontal traces? + // This is used to determine whether we rotate the labels or not + var hasOneHorizontalTrace = false; + + // Figure out what we're hovering on: + // mouse location or user-supplied data + + if(Array.isArray(evt)) { + // user specified an array of points to highlight + hovermode = 'array'; + for(itemnum = 0; itemnum < evt.length; itemnum++) { + cd = gd.calcdata[evt[itemnum].curveNumber || 0]; + if(cd) { + trace = cd[0].trace; + if(cd[0].trace.hoverinfo !== 'skip') { + searchData.push(cd); + if(trace.orientation === 'h') { + hasOneHorizontalTrace = true; + } + } + } + } + } else { + for(curvenum = 0; curvenum < gd.calcdata.length; curvenum++) { + cd = gd.calcdata[curvenum]; + trace = cd[0].trace; + if(trace.hoverinfo !== 'skip' && helpers.isTraceInSubplots(trace, subplots)) { + searchData.push(cd); + if(trace.orientation === 'h') { + hasOneHorizontalTrace = true; + } + } + } + + // [x|y]px: the pixels (from top left) of the mouse location + // on the currently selected plot area + // add pointerX|Y property for drawing the spikes in spikesnap 'cursor' situation + var hasUserCalledHover = !evt.target; + var xpx, ypx; + + if(hasUserCalledHover) { + if('xpx' in evt) xpx = evt.xpx; + else xpx = xaArray[0]._length / 2; + + if('ypx' in evt) ypx = evt.ypx; + else ypx = yaArray[0]._length / 2; + } else { + // fire the beforehover event and quit if it returns false + // note that we're only calling this on real mouse events, so + // manual calls to fx.hover will always run. + if(Events.triggerHandler(gd, 'plotly_beforehover', evt) === false) { + return; + } + + var dbb = evt.target.getBoundingClientRect(); + + xpx = evt.clientX - dbb.left; + ypx = evt.clientY - dbb.top; + + // in case hover was called from mouseout into hovertext, + // it's possible you're not actually over the plot anymore + if(xpx < 0 || xpx > xaArray[0]._length || ypx < 0 || ypx > yaArray[0]._length) { + return dragElement.unhoverRaw(gd, evt); + } + } + + evt.pointerX = xpx + xaArray[0]._offset; + evt.pointerY = ypx + yaArray[0]._offset; + + if('xval' in evt) xvalArray = helpers.flat(subplots, evt.xval); + else xvalArray = helpers.p2c(xaArray, xpx); + + if('yval' in evt) yvalArray = helpers.flat(subplots, evt.yval); + else yvalArray = helpers.p2c(yaArray, ypx); + + if(!isNumeric(xvalArray[0]) || !isNumeric(yvalArray[0])) { + Lib.warn('Fx.hover failed', evt, gd); + return dragElement.unhoverRaw(gd, evt); + } + } + + // the pixel distance to beat as a matching point + // in 'x' or 'y' mode this resets for each trace + var distance = Infinity; + + // find the closest point in each trace + // this is minimum dx and/or dy, depending on mode + // and the pixel position for the label (labelXpx, labelYpx) + for(curvenum = 0; curvenum < searchData.length; curvenum++) { + cd = searchData[curvenum]; + + // filter out invisible or broken data + if(!cd || !cd[0] || !cd[0].trace) continue; + + trace = cd[0].trace; + + if(trace.visible !== true || trace._length === 0) continue; + + // Explicitly bail out for these two. I don't know how to otherwise prevent + // the rest of this function from running and failing + if(['carpet', 'contourcarpet'].indexOf(trace._module.name) !== -1) continue; + + if(trace.type === 'splom') { + // splom traces do not generate overlay subplots, + // it is safe to assume here splom traces correspond to the 0th subplot + subploti = 0; + subplotId = subplots[subploti]; + } else { + subplotId = helpers.getSubplot(trace); + subploti = subplots.indexOf(subplotId); + } + + // within one trace mode can sometimes be overridden + mode = hovermode; + + // container for new point, also used to pass info into module.hoverPoints + pointData = { + // trace properties + cd: cd, + trace: trace, + xa: xaArray[subploti], + ya: yaArray[subploti], + + // max distances for hover and spikes - for points that want to show but do not + // want to override other points, set distance/spikeDistance equal to max*Distance + // and it will not get filtered out but it will be guaranteed to have a greater + // distance than any point that calculated a real distance. + maxHoverDistance: hoverdistance, + maxSpikeDistance: spikedistance, + + // point properties - override all of these + index: false, // point index in trace - only used by plotly.js hoverdata consumers + distance: Math.min(distance, hoverdistance), // pixel distance or pseudo-distance + + // distance/pseudo-distance for spikes. This distance should always be calculated + // as if in "closest" mode, and should only be set if this point should + // generate a spike. + spikeDistance: Infinity, + + // in some cases the spikes have different positioning from the hover label + // they don't need x0/x1, just one position + xSpike: undefined, + ySpike: undefined, + + // where and how to display the hover label + color: Color.defaultLine, // trace color + name: trace.name, + x0: undefined, + x1: undefined, + y0: undefined, + y1: undefined, + xLabelVal: undefined, + yLabelVal: undefined, + zLabelVal: undefined, + text: undefined + }; + + // add ref to subplot object (non-cartesian case) + if(fullLayout[subplotId]) { + pointData.subplot = fullLayout[subplotId]._subplot; + } + // add ref to splom scene + if(fullLayout._splomScenes && fullLayout._splomScenes[trace.uid]) { + pointData.scene = fullLayout._splomScenes[trace.uid]; + } + + closedataPreviousLength = hoverData.length; + + // for a highlighting array, figure out what + // we're searching for with this element + if(mode === 'array') { + var selection = evt[curvenum]; + if('pointNumber' in selection) { + pointData.index = selection.pointNumber; + mode = 'closest'; + } else { + mode = ''; + if('xval' in selection) { + xval = selection.xval; + mode = 'x'; + } + if('yval' in selection) { + yval = selection.yval; + mode = mode ? 'closest' : 'y'; + } + } + } else { + xval = xvalArray[subploti]; + yval = yvalArray[subploti]; + } + + // Now if there is range to look in, find the points to hover. + if(hoverdistance !== 0) { + if(trace._module && trace._module.hoverPoints) { + var newPoints = trace._module.hoverPoints(pointData, xval, yval, mode, fullLayout._hoverlayer); + if(newPoints) { + var newPoint; + for(var newPointNum = 0; newPointNum < newPoints.length; newPointNum++) { + newPoint = newPoints[newPointNum]; + if(isNumeric(newPoint.x0) && isNumeric(newPoint.y0)) { + hoverData.push(cleanPoint(newPoint, hovermode)); + } + } + } + } else { + Lib.log('Unrecognized trace type in hover:', trace); + } + } + + // in closest mode, remove any existing (farther) points + // and don't look any farther than this latest point (or points, some + // traces like box & violin make multiple hover labels at once) + if(hovermode === 'closest' && hoverData.length > closedataPreviousLength) { + hoverData.splice(0, closedataPreviousLength); + distance = hoverData[0].distance; + } + + // Now if there is range to look in, find the points to draw the spikelines + // Do it only if there is no hoverData + if(hasCartesian && (spikedistance !== 0)) { + if(hoverData.length === 0) { + pointData.distance = spikedistance; + pointData.index = false; + var closestPoints = trace._module.hoverPoints(pointData, xval, yval, 'closest', fullLayout._hoverlayer); + if(closestPoints) { + closestPoints = closestPoints.filter(function(point) { + // some hover points, like scatter fills, do not allow spikes, + // so will generate a hover point but without a valid spikeDistance + return point.spikeDistance <= spikedistance; + }); + } + if(closestPoints && closestPoints.length) { + var tmpPoint; + var closestVPoints = closestPoints.filter(function(point) { + return point.xa.showspikes; + }); + if(closestVPoints.length) { + var closestVPt = closestVPoints[0]; + if(isNumeric(closestVPt.x0) && isNumeric(closestVPt.y0)) { + tmpPoint = fillSpikePoint(closestVPt); + if(!spikePoints.vLinePoint || (spikePoints.vLinePoint.spikeDistance > tmpPoint.spikeDistance)) { + spikePoints.vLinePoint = tmpPoint; + } + } + } + + var closestHPoints = closestPoints.filter(function(point) { + return point.ya.showspikes; + }); + if(closestHPoints.length) { + var closestHPt = closestHPoints[0]; + if(isNumeric(closestHPt.x0) && isNumeric(closestHPt.y0)) { + tmpPoint = fillSpikePoint(closestHPt); + if(!spikePoints.hLinePoint || (spikePoints.hLinePoint.spikeDistance > tmpPoint.spikeDistance)) { + spikePoints.hLinePoint = tmpPoint; + } + } + } + } + } + } + } + + function selectClosestPoint(pointsData, spikedistance) { + var resultPoint = null; + var minDistance = Infinity; + var thisSpikeDistance; + for(var i = 0; i < pointsData.length; i++) { + thisSpikeDistance = pointsData[i].spikeDistance; + if(thisSpikeDistance < minDistance && thisSpikeDistance <= spikedistance) { + resultPoint = pointsData[i]; + minDistance = thisSpikeDistance; + } + } + return resultPoint; + } + + function fillSpikePoint(point) { + if(!point) return null; + return { + xa: point.xa, + ya: point.ya, + x: point.xSpike !== undefined ? point.xSpike : (point.x0 + point.x1) / 2, + y: point.ySpike !== undefined ? point.ySpike : (point.y0 + point.y1) / 2, + distance: point.distance, + spikeDistance: point.spikeDistance, + curveNumber: point.trace.index, + color: point.color, + pointNumber: point.index + }; + } + + var spikelineOpts = { + fullLayout: fullLayout, + container: fullLayout._hoverlayer, + outerContainer: fullLayout._paperdiv, + event: evt + }; + var oldspikepoints = gd._spikepoints; + var newspikepoints = { + vLinePoint: spikePoints.vLinePoint, + hLinePoint: spikePoints.hLinePoint + }; + gd._spikepoints = newspikepoints; + + // Now if it is not restricted by spikedistance option, set the points to draw the spikelines + if(hasCartesian && (spikedistance !== 0)) { + if(hoverData.length !== 0) { + var tmpHPointData = hoverData.filter(function(point) { + return point.ya.showspikes; + }); + var tmpHPoint = selectClosestPoint(tmpHPointData, spikedistance); + spikePoints.hLinePoint = fillSpikePoint(tmpHPoint); + + var tmpVPointData = hoverData.filter(function(point) { + return point.xa.showspikes; + }); + var tmpVPoint = selectClosestPoint(tmpVPointData, spikedistance); + spikePoints.vLinePoint = fillSpikePoint(tmpVPoint); + } + } + + // if hoverData is empty check for the spikes to draw and quit if there are none + if(hoverData.length === 0) { + var result = dragElement.unhoverRaw(gd, evt); + if(hasCartesian && ((spikePoints.hLinePoint !== null) || (spikePoints.vLinePoint !== null))) { + if(spikesChanged(oldspikepoints)) { + createSpikelines(gd, spikePoints, spikelineOpts); + } + } + return result; + } + + if(hasCartesian) { + if(spikesChanged(oldspikepoints)) { + createSpikelines(gd, spikePoints, spikelineOpts); + } + } + + hoverData.sort(function(d1, d2) { return d1.distance - d2.distance; }); + + // lastly, emit custom hover/unhover events + var oldhoverdata = gd._hoverdata; + var newhoverdata = []; + + // pull out just the data that's useful to + // other people and send it to the event + for(itemnum = 0; itemnum < hoverData.length; itemnum++) { + var pt = hoverData[itemnum]; + var eventData = helpers.makeEventData(pt, pt.trace, pt.cd); + + if(pt.hovertemplate !== false) { + var ht = false; + if(pt.cd[pt.index] && pt.cd[pt.index].ht) { + ht = pt.cd[pt.index].ht; + } + pt.hovertemplate = ht || pt.trace.hovertemplate || false; + } + + pt.eventData = [eventData]; + newhoverdata.push(eventData); + } + + gd._hoverdata = newhoverdata; + + var rotateLabels = ( + (hovermode === 'y' && (searchData.length > 1 || hoverData.length > 1)) || + (hovermode === 'closest' && hasOneHorizontalTrace && hoverData.length > 1) + ); + + var bgColor = Color.combine( + fullLayout.plot_bgcolor || Color.background, + fullLayout.paper_bgcolor + ); + + var labelOpts = { + hovermode: hovermode, + rotateLabels: rotateLabels, + bgColor: bgColor, + container: fullLayout._hoverlayer, + outerContainer: fullLayout._paperdiv, + commonLabelOpts: fullLayout.hoverlabel, + hoverdistance: fullLayout.hoverdistance + }; + + var hoverLabels = createHoverText(hoverData, labelOpts, gd); + + hoverAvoidOverlaps(hoverLabels, rotateLabels ? 'xa' : 'ya', fullLayout); + + alignHoverText(hoverLabels, rotateLabels); + + // TODO: tagName hack is needed to appease geo.js's hack of using evt.target=true + // we should improve the "fx" API so other plots can use it without these hack. + if(evt.target && evt.target.tagName) { + var hasClickToShow = Registry.getComponentMethod('annotations', 'hasClickToShow')(gd, newhoverdata); + overrideCursor(d3.select(evt.target), hasClickToShow ? 'pointer' : ''); + } + + // don't emit events if called manually + if(!evt.target || noHoverEvent || !hoverChanged(gd, evt, oldhoverdata)) return; + + if(oldhoverdata) { + gd.emit('plotly_unhover', { + event: evt, + points: oldhoverdata + }); + } + + gd.emit('plotly_hover', { + event: evt, + points: gd._hoverdata, + xaxes: xaArray, + yaxes: yaArray, + xvals: xvalArray, + yvals: yvalArray + }); +} + +var EXTRA_STRING_REGEX = /([\s\S]*)<\/extra>/; + +function createHoverText(hoverData, opts, gd) { + var fullLayout = gd._fullLayout; + var hovermode = opts.hovermode; + var rotateLabels = opts.rotateLabels; + var bgColor = opts.bgColor; + var container = opts.container; + var outerContainer = opts.outerContainer; + var commonLabelOpts = opts.commonLabelOpts || {}; + + // opts.fontFamily/Size are used for the common label + // and as defaults for each hover label, though the individual labels + // can override this. + var fontFamily = opts.fontFamily || constants.HOVERFONT; + var fontSize = opts.fontSize || constants.HOVERFONTSIZE; + + var c0 = hoverData[0]; + var xa = c0.xa; + var ya = c0.ya; + var commonAttr = hovermode === 'y' ? 'yLabel' : 'xLabel'; + var t0 = c0[commonAttr]; + var t00 = (String(t0) || '').split(' ')[0]; + var outerContainerBB = outerContainer.node().getBoundingClientRect(); + var outerTop = outerContainerBB.top; + var outerWidth = outerContainerBB.width; + var outerHeight = outerContainerBB.height; + + // show the common label, if any, on the axis + // never show a common label in array mode, + // even if sometimes there could be one + var showCommonLabel = ( + (t0 !== undefined) && + (c0.distance <= opts.hoverdistance) && + (hovermode === 'x' || hovermode === 'y') + ); + + // all hover traces hoverinfo must contain the hovermode + // to have common labels + if(showCommonLabel) { + var allHaveZ = true; + var i, traceHoverinfo; + for(i = 0; i < hoverData.length; i++) { + if(allHaveZ && hoverData[i].zLabel === undefined) allHaveZ = false; + + traceHoverinfo = hoverData[i].hoverinfo || hoverData[i].trace.hoverinfo; + if(traceHoverinfo) { + var parts = Array.isArray(traceHoverinfo) ? traceHoverinfo : traceHoverinfo.split('+'); + if(parts.indexOf('all') === -1 && + parts.indexOf(hovermode) === -1) { + showCommonLabel = false; + break; + } + } + } + + // xyz labels put all info in their main label, so have no need of a common label + if(allHaveZ) showCommonLabel = false; + } + + var commonLabel = container.selectAll('g.axistext') + .data(showCommonLabel ? [0] : []); + commonLabel.enter().append('g') + .classed('axistext', true); + commonLabel.exit().remove(); + + commonLabel.each(function() { + var label = d3.select(this); + var lpath = Lib.ensureSingle(label, 'path', '', function(s) { + s.style({'stroke-width': '1px'}); + }); + var ltext = Lib.ensureSingle(label, 'text', '', function(s) { + // prohibit tex interpretation until we can handle + // tex and regular text together + s.attr('data-notex', 1); + }); + + var commonBgColor = commonLabelOpts.bgcolor || Color.defaultLine; + var commonStroke = commonLabelOpts.bordercolor || Color.contrast(commonBgColor); + var contrastColor = Color.contrast(commonBgColor); + var commonLabelFont = { + family: commonLabelOpts.font.family || fontFamily, + size: commonLabelOpts.font.size || fontSize, + color: commonLabelOpts.font.color || contrastColor + }; + + lpath.style({ + fill: commonBgColor, + stroke: commonStroke + }); + + ltext.text(t0) + .call(Drawing.font, commonLabelFont) + .call(svgTextUtils.positionText, 0, 0) + .call(svgTextUtils.convertToTspans, gd); + + label.attr('transform', ''); + + var tbb = ltext.node().getBoundingClientRect(); + var lx, ly; + + if(hovermode === 'x') { + var topsign = xa.side === 'top' ? '-' : ''; + + ltext.attr('text-anchor', 'middle') + .call(svgTextUtils.positionText, 0, (xa.side === 'top' ? + (outerTop - tbb.bottom - HOVERARROWSIZE - HOVERTEXTPAD) : + (outerTop - tbb.top + HOVERARROWSIZE + HOVERTEXTPAD))); + + lx = xa._offset + (c0.x0 + c0.x1) / 2; + ly = ya._offset + (xa.side === 'top' ? 0 : ya._length); + + var halfWidth = tbb.width / 2 + HOVERTEXTPAD; + + if(lx < halfWidth) { + lx = halfWidth; + + lpath.attr('d', 'M-' + (halfWidth - HOVERARROWSIZE) + ',0' + + 'L-' + (halfWidth - HOVERARROWSIZE * 2) + ',' + topsign + HOVERARROWSIZE + + 'H' + (HOVERTEXTPAD + tbb.width / 2) + + 'v' + topsign + (HOVERTEXTPAD * 2 + tbb.height) + + 'H-' + halfWidth + + 'V' + topsign + HOVERARROWSIZE + + 'Z'); + } else if(lx > (fullLayout.width - halfWidth)) { + lx = fullLayout.width - halfWidth; + + lpath.attr('d', 'M' + (halfWidth - HOVERARROWSIZE) + ',0' + + 'L' + halfWidth + ',' + topsign + HOVERARROWSIZE + + 'v' + topsign + (HOVERTEXTPAD * 2 + tbb.height) + + 'H-' + halfWidth + + 'V' + topsign + HOVERARROWSIZE + + 'H' + (halfWidth - HOVERARROWSIZE * 2) + 'Z'); + } else { + lpath.attr('d', 'M0,0' + + 'L' + HOVERARROWSIZE + ',' + topsign + HOVERARROWSIZE + + 'H' + (HOVERTEXTPAD + tbb.width / 2) + + 'v' + topsign + (HOVERTEXTPAD * 2 + tbb.height) + + 'H-' + (HOVERTEXTPAD + tbb.width / 2) + + 'V' + topsign + HOVERARROWSIZE + + 'H-' + HOVERARROWSIZE + 'Z'); + } + } else { + var anchor; + var sgn; + var leftsign; + if(ya.side === 'right') { + anchor = 'start'; + sgn = 1; + leftsign = ''; + lx = xa._offset + xa._length; + } else { + anchor = 'end'; + sgn = -1; + leftsign = '-'; + lx = xa._offset; + } + + ly = ya._offset + (c0.y0 + c0.y1) / 2; + + ltext.attr('text-anchor', anchor); + + lpath.attr('d', 'M0,0' + + 'L' + leftsign + HOVERARROWSIZE + ',' + HOVERARROWSIZE + + 'V' + (HOVERTEXTPAD + tbb.height / 2) + + 'h' + leftsign + (HOVERTEXTPAD * 2 + tbb.width) + + 'V-' + (HOVERTEXTPAD + tbb.height / 2) + + 'H' + leftsign + HOVERARROWSIZE + 'V-' + HOVERARROWSIZE + 'Z'); + + var halfHeight = tbb.height / 2; + var lty = outerTop - tbb.top - halfHeight; + var clipId = 'clip' + fullLayout._uid + 'commonlabel' + ya._id; + var clipPath; + + if(lx < (tbb.width + 2 * HOVERTEXTPAD + HOVERARROWSIZE)) { + clipPath = 'M-' + (HOVERARROWSIZE + HOVERTEXTPAD) + '-' + halfHeight + + 'h-' + (tbb.width - HOVERTEXTPAD) + + 'V' + halfHeight + + 'h' + (tbb.width - HOVERTEXTPAD) + 'Z'; + + var ltx = tbb.width - lx + HOVERTEXTPAD; + svgTextUtils.positionText(ltext, ltx, lty); + + // shift each line (except the longest) so that start-of-line + // is always visible + if(anchor === 'end') { + ltext.selectAll('tspan').each(function() { + var s = d3.select(this); + var dummy = Drawing.tester.append('text') + .text(s.text()) + .call(Drawing.font, commonLabelFont); + var dummyBB = dummy.node().getBoundingClientRect(); + if(dummyBB.width < tbb.width) { + s.attr('x', ltx - dummyBB.width); + } + dummy.remove(); + }); + } + } else { + svgTextUtils.positionText(ltext, sgn * (HOVERTEXTPAD + HOVERARROWSIZE), lty); + clipPath = null; + } + + var textClip = fullLayout._topclips.selectAll('#' + clipId).data(clipPath ? [0] : []); + textClip.enter().append('clipPath').attr('id', clipId).append('path'); + textClip.exit().remove(); + textClip.select('path').attr('d', clipPath); + Drawing.setClipUrl(ltext, clipPath ? clipId : null, gd); + } + + label.attr('transform', 'translate(' + lx + ',' + ly + ')'); + + // remove the "close but not quite" points + // because of error bars, only take up to a space + hoverData = hoverData.filter(function(d) { + return (d.zLabelVal !== undefined) || + (d[commonAttr] || '').split(' ')[0] === t00; + }); + }); + + // show all the individual labels + + // first create the objects + var hoverLabels = container.selectAll('g.hovertext') + .data(hoverData, function(d) { + // N.B. when multiple items have the same result key-function value, + // only the first of those items in hoverData gets rendered + return [d.trace.index, d.index, d.x0, d.y0, d.name, d.attr, d.xa, d.ya || ''].join(','); + }); + hoverLabels.enter().append('g') + .classed('hovertext', true) + .each(function() { + var g = d3.select(this); + // trace name label (rect and text.name) + g.append('rect') + .call(Color.fill, Color.addOpacity(bgColor, 0.8)); + g.append('text').classed('name', true); + // trace data label (path and text.nums) + g.append('path') + .style('stroke-width', '1px'); + g.append('text').classed('nums', true) + .call(Drawing.font, fontFamily, fontSize); + }); + hoverLabels.exit().remove(); + + // then put the text in, position the pointer to the data, + // and figure out sizes + hoverLabels.each(function(d) { + var g = d3.select(this).attr('transform', ''); + var name = ''; + var text = ''; + + // combine possible non-opaque trace color with bgColor + var color0 = d.bgcolor || d.color; + // color for 'nums' part of the label + var numsColor = Color.combine( + Color.opacity(color0) ? color0 : Color.defaultLine, + bgColor + ); + // color for 'name' part of the label + var nameColor = Color.combine( + Color.opacity(d.color) ? d.color : Color.defaultLine, + bgColor + ); + // find a contrasting color for border and text + var contrastColor = d.borderColor || Color.contrast(numsColor); + + // to get custom 'name' labels pass cleanPoint + if(d.nameOverride !== undefined) d.name = d.nameOverride; + + if(d.name) { + if(d.trace._meta) { + d.name = Lib.templateString(d.name, d.trace._meta); + } + name = plainText(d.name, d.nameLength); + } + + if(d.zLabel !== undefined) { + if(d.xLabel !== undefined) text += 'x: ' + d.xLabel + '
'; + if(d.yLabel !== undefined) text += 'y: ' + d.yLabel + '
'; + if(d.trace.type !== 'choropleth' && d.trace.type !== 'choroplethmapbox') { + text += (text ? 'z: ' : '') + d.zLabel; + } + } else if(showCommonLabel && d[hovermode + 'Label'] === t0) { + text = d[(hovermode === 'x' ? 'y' : 'x') + 'Label'] || ''; + } else if(d.xLabel === undefined) { + if(d.yLabel !== undefined && d.trace.type !== 'scattercarpet') { + text = d.yLabel; + } + } else if(d.yLabel === undefined) text = d.xLabel; + else text = '(' + d.xLabel + ', ' + d.yLabel + ')'; + + if((d.text || d.text === 0) && !Array.isArray(d.text)) { + text += (text ? '
' : '') + d.text; + } + + // used by other modules (initially just ternary) that + // manage their own hoverinfo independent of cleanPoint + // the rest of this will still apply, so such modules + // can still put things in (x|y|z)Label, text, and name + // and hoverinfo will still determine their visibility + if(d.extraText !== undefined) text += (text ? '
' : '') + d.extraText; + + // if 'text' is empty at this point, + // and hovertemplate is not defined, + // put 'name' in main label and don't show secondary label + if(text === '' && !d.hovertemplate) { + // if 'name' is also empty, remove entire label + if(name === '') g.remove(); + text = name; + } + + // hovertemplate + var d3locale = fullLayout._d3locale; + var hovertemplate = d.hovertemplate || false; + var hovertemplateLabels = d.hovertemplateLabels || d; + var eventData = d.eventData[0] || {}; + if(hovertemplate) { + text = Lib.hovertemplateString( + hovertemplate, + hovertemplateLabels, + d3locale, + eventData, + d.trace._meta + ); + + text = text.replace(EXTRA_STRING_REGEX, function(match, extra) { + // assign name for secondary text label + name = plainText(extra, d.nameLength); + // remove from main text label + return ''; + }); + } + + // main label + var tx = g.select('text.nums') + .call(Drawing.font, + d.fontFamily || fontFamily, + d.fontSize || fontSize, + d.fontColor || contrastColor) + .text(text) + .attr('data-notex', 1) + .call(svgTextUtils.positionText, 0, 0) + .call(svgTextUtils.convertToTspans, gd); + + var tx2 = g.select('text.name'); + var tx2width = 0; + var tx2height = 0; + + // secondary label for non-empty 'name' + if(name && name !== text) { + tx2.call(Drawing.font, + d.fontFamily || fontFamily, + d.fontSize || fontSize, + nameColor) + .text(name) + .attr('data-notex', 1) + .call(svgTextUtils.positionText, 0, 0) + .call(svgTextUtils.convertToTspans, gd); + + var t2bb = tx2.node().getBoundingClientRect(); + tx2width = t2bb.width + 2 * HOVERTEXTPAD; + tx2height = t2bb.height + 2 * HOVERTEXTPAD; + } else { + tx2.remove(); + g.select('rect').remove(); + } + + g.select('path').style({ + fill: numsColor, + stroke: contrastColor + }); + + var tbb = tx.node().getBoundingClientRect(); + var htx = d.xa._offset + (d.x0 + d.x1) / 2; + var hty = d.ya._offset + (d.y0 + d.y1) / 2; + var dx = Math.abs(d.x1 - d.x0); + var dy = Math.abs(d.y1 - d.y0); + var txTotalWidth = tbb.width + HOVERARROWSIZE + HOVERTEXTPAD + tx2width; + var anchorStartOK, anchorEndOK; + + d.ty0 = outerTop - tbb.top; + d.bx = tbb.width + 2 * HOVERTEXTPAD; + d.by = Math.max(tbb.height + 2 * HOVERTEXTPAD, tx2height); + d.anchor = 'start'; + d.txwidth = tbb.width; + d.tx2width = tx2width; + d.offset = 0; + + if(rotateLabels) { + d.pos = htx; + anchorStartOK = hty + dy / 2 + txTotalWidth <= outerHeight; + anchorEndOK = hty - dy / 2 - txTotalWidth >= 0; + if((d.idealAlign === 'top' || !anchorStartOK) && anchorEndOK) { + hty -= dy / 2; + d.anchor = 'end'; + } else if(anchorStartOK) { + hty += dy / 2; + d.anchor = 'start'; + } else d.anchor = 'middle'; + } else { + d.pos = hty; + anchorStartOK = htx + dx / 2 + txTotalWidth <= outerWidth; + anchorEndOK = htx - dx / 2 - txTotalWidth >= 0; + + if((d.idealAlign === 'left' || !anchorStartOK) && anchorEndOK) { + htx -= dx / 2; + d.anchor = 'end'; + } else if(anchorStartOK) { + htx += dx / 2; + d.anchor = 'start'; + } else { + d.anchor = 'middle'; + + var txHalfWidth = txTotalWidth / 2; + var overflowR = htx + txHalfWidth - outerWidth; + var overflowL = htx - txHalfWidth; + if(overflowR > 0) htx -= overflowR; + if(overflowL < 0) htx += -overflowL; + } + } + + tx.attr('text-anchor', d.anchor); + if(tx2width) tx2.attr('text-anchor', d.anchor); + g.attr('transform', 'translate(' + htx + ',' + hty + ')' + + (rotateLabels ? 'rotate(' + YANGLE + ')' : '')); + }); + + return hoverLabels; +} + +// Make groups of touching points, and within each group +// move each point so that no labels overlap, but the average +// label position is the same as it was before moving. Indicentally, +// this is equivalent to saying all the labels are on equal linear +// springs about their initial position. Initially, each point is +// its own group, but as we find overlaps we will clump the points. +// +// Also, there are hard constraints at the edges of the graphs, +// that push all groups to the middle so they are visible. I don't +// know what happens if the group spans all the way from one edge to +// the other, though it hardly matters - there's just too much +// information then. +function hoverAvoidOverlaps(hoverLabels, axKey, fullLayout) { + var nummoves = 0; + var axSign = 1; + var nLabels = hoverLabels.size(); + + // make groups of touching points + var pointgroups = new Array(nLabels); + var k = 0; + + hoverLabels.each(function(d) { + var ax = d[axKey]; + var axIsX = ax._id.charAt(0) === 'x'; + var rng = ax.range; + + if(k === 0 && rng && ((rng[0] > rng[1]) !== axIsX)) { + axSign = -1; + } + pointgroups[k++] = [{ + datum: d, + traceIndex: d.trace.index, + dp: 0, + pos: d.pos, + posref: d.posref, + size: d.by * (axIsX ? YFACTOR : 1) / 2, + pmin: 0, + pmax: (axIsX ? fullLayout.width : fullLayout.height) + }]; + }); + + pointgroups.sort(function(a, b) { + return (a[0].posref - b[0].posref) || + // for equal positions, sort trace indices increasing or decreasing + // depending on whether the axis is reversed or not... so stacked + // traces will generally keep their order even if one trace adds + // nothing to the stack. + (axSign * (b[0].traceIndex - a[0].traceIndex)); + }); + + var donepositioning, topOverlap, bottomOverlap, i, j, pti, sumdp; + + function constrainGroup(grp) { + var minPt = grp[0]; + var maxPt = grp[grp.length - 1]; + + // overlap with the top - positive vals are overlaps + topOverlap = minPt.pmin - minPt.pos - minPt.dp + minPt.size; + + // overlap with the bottom - positive vals are overlaps + bottomOverlap = maxPt.pos + maxPt.dp + maxPt.size - minPt.pmax; + + // check for min overlap first, so that we always + // see the largest labels + // allow for .01px overlap, so we don't get an + // infinite loop from rounding errors + if(topOverlap > 0.01) { + for(j = grp.length - 1; j >= 0; j--) grp[j].dp += topOverlap; + donepositioning = false; + } + if(bottomOverlap < 0.01) return; + if(topOverlap < -0.01) { + // make sure we're not pushing back and forth + for(j = grp.length - 1; j >= 0; j--) grp[j].dp -= bottomOverlap; + donepositioning = false; + } + if(!donepositioning) return; + + // no room to fix positioning, delete off-screen points + + // first see how many points we need to delete + var deleteCount = 0; + for(i = 0; i < grp.length; i++) { + pti = grp[i]; + if(pti.pos + pti.dp + pti.size > minPt.pmax) deleteCount++; + } + + // start by deleting points whose data is off screen + for(i = grp.length - 1; i >= 0; i--) { + if(deleteCount <= 0) break; + pti = grp[i]; + + // pos has already been constrained to [pmin,pmax] + // so look for points close to that to delete + if(pti.pos > minPt.pmax - 1) { + pti.del = true; + deleteCount--; + } + } + for(i = 0; i < grp.length; i++) { + if(deleteCount <= 0) break; + pti = grp[i]; + + // pos has already been constrained to [pmin,pmax] + // so look for points close to that to delete + if(pti.pos < minPt.pmin + 1) { + pti.del = true; + deleteCount--; + + // shift the whole group minus into this new space + bottomOverlap = pti.size * 2; + for(j = grp.length - 1; j >= 0; j--) grp[j].dp -= bottomOverlap; + } + } + // then delete points that go off the bottom + for(i = grp.length - 1; i >= 0; i--) { + if(deleteCount <= 0) break; + pti = grp[i]; + if(pti.pos + pti.dp + pti.size > minPt.pmax) { + pti.del = true; + deleteCount--; + } + } + } + + // loop through groups, combining them if they overlap, + // until nothing moves + while(!donepositioning && nummoves <= nLabels) { + // to avoid infinite loops, don't move more times + // than there are traces + nummoves++; + + // assume nothing will move in this iteration, + // reverse this if it does + donepositioning = true; + i = 0; + while(i < pointgroups.length - 1) { + // the higher (g0) and lower (g1) point group + var g0 = pointgroups[i]; + var g1 = pointgroups[i + 1]; + + // the lowest point in the higher group (p0) + // the highest point in the lower group (p1) + var p0 = g0[g0.length - 1]; + var p1 = g1[0]; + topOverlap = p0.pos + p0.dp + p0.size - p1.pos - p1.dp + p1.size; + + // Only group points that lie on the same axes + if(topOverlap > 0.01 && (p0.pmin === p1.pmin) && (p0.pmax === p1.pmax)) { + // push the new point(s) added to this group out of the way + for(j = g1.length - 1; j >= 0; j--) g1[j].dp += topOverlap; + + // add them to the group + g0.push.apply(g0, g1); + pointgroups.splice(i + 1, 1); + + // adjust for minimum average movement + sumdp = 0; + for(j = g0.length - 1; j >= 0; j--) sumdp += g0[j].dp; + bottomOverlap = sumdp / g0.length; + for(j = g0.length - 1; j >= 0; j--) g0[j].dp -= bottomOverlap; + donepositioning = false; + } else i++; + } + + // check if we're going off the plot on either side and fix + pointgroups.forEach(constrainGroup); + } + + // now put these offsets into hoverData + for(i = pointgroups.length - 1; i >= 0; i--) { + var grp = pointgroups[i]; + for(j = grp.length - 1; j >= 0; j--) { + var pt = grp[j]; + var hoverPt = pt.datum; + hoverPt.offset = pt.dp; + hoverPt.del = pt.del; + } + } +} + +function alignHoverText(hoverLabels, rotateLabels) { + // finally set the text positioning relative to the data and draw the + // box around it + hoverLabels.each(function(d) { + var g = d3.select(this); + if(d.del) return g.remove(); + + var tx = g.select('text.nums'); + var anchor = d.anchor; + var horzSign = anchor === 'end' ? -1 : 1; + var alignShift = {start: 1, end: -1, middle: 0}[anchor]; + var txx = alignShift * (HOVERARROWSIZE + HOVERTEXTPAD); + var tx2x = txx + alignShift * (d.txwidth + HOVERTEXTPAD); + var offsetX = 0; + var offsetY = d.offset; + + if(anchor === 'middle') { + txx -= d.tx2width / 2; + tx2x += d.txwidth / 2 + HOVERTEXTPAD; + } + if(rotateLabels) { + offsetY *= -YSHIFTY; + offsetX = d.offset * YSHIFTX; + } + + g.select('path').attr('d', anchor === 'middle' ? + // middle aligned: rect centered on data + ('M-' + (d.bx / 2 + d.tx2width / 2) + ',' + (offsetY - d.by / 2) + + 'h' + d.bx + 'v' + d.by + 'h-' + d.bx + 'Z') : + // left or right aligned: side rect with arrow to data + ('M0,0L' + (horzSign * HOVERARROWSIZE + offsetX) + ',' + (HOVERARROWSIZE + offsetY) + + 'v' + (d.by / 2 - HOVERARROWSIZE) + + 'h' + (horzSign * d.bx) + + 'v-' + d.by + + 'H' + (horzSign * HOVERARROWSIZE + offsetX) + + 'V' + (offsetY - HOVERARROWSIZE) + + 'Z')); + + var posX = txx + offsetX; + var posY = offsetY + d.ty0 - d.by / 2 + HOVERTEXTPAD; + var textAlign = d.textAlign || 'auto'; + + if(textAlign !== 'auto') { + if(textAlign === 'left' && anchor !== 'start') { + tx.attr('text-anchor', 'start'); + posX = anchor === 'middle' ? + -d.bx / 2 - d.tx2width / 2 + HOVERTEXTPAD : + -d.bx - HOVERTEXTPAD; + } else if(textAlign === 'right' && anchor !== 'end') { + tx.attr('text-anchor', 'end'); + posX = anchor === 'middle' ? + d.bx / 2 - d.tx2width / 2 - HOVERTEXTPAD : + d.bx + HOVERTEXTPAD; + } + } + + tx.call(svgTextUtils.positionText, posX, posY); + + if(d.tx2width) { + g.select('text.name') + .call(svgTextUtils.positionText, + tx2x + alignShift * HOVERTEXTPAD + offsetX, + offsetY + d.ty0 - d.by / 2 + HOVERTEXTPAD); + g.select('rect') + .call(Drawing.setRect, + tx2x + (alignShift - 1) * d.tx2width / 2 + offsetX, + offsetY - d.by / 2 - 1, + d.tx2width, d.by + 2); + } + }); +} + +function cleanPoint(d, hovermode) { + var index = d.index; + var trace = d.trace || {}; + var cd0 = d.cd[0]; + var cd = d.cd[index] || {}; + + function pass(v) { + return v || (isNumeric(v) && v === 0); + } + + var getVal = Array.isArray(index) ? + function(calcKey, traceKey) { + var v = Lib.castOption(cd0, index, calcKey); + return pass(v) ? v : Lib.extractOption({}, trace, '', traceKey); + } : + function(calcKey, traceKey) { + return Lib.extractOption(cd, trace, calcKey, traceKey); + }; + + function fill(key, calcKey, traceKey) { + var val = getVal(calcKey, traceKey); + if(pass(val)) d[key] = val; + } + + fill('hoverinfo', 'hi', 'hoverinfo'); + fill('bgcolor', 'hbg', 'hoverlabel.bgcolor'); + fill('borderColor', 'hbc', 'hoverlabel.bordercolor'); + fill('fontFamily', 'htf', 'hoverlabel.font.family'); + fill('fontSize', 'hts', 'hoverlabel.font.size'); + fill('fontColor', 'htc', 'hoverlabel.font.color'); + fill('nameLength', 'hnl', 'hoverlabel.namelength'); + fill('textAlign', 'hta', 'hoverlabel.align'); + + d.posref = (hovermode === 'y' || (hovermode === 'closest' && trace.orientation === 'h')) ? + (d.xa._offset + (d.x0 + d.x1) / 2) : + (d.ya._offset + (d.y0 + d.y1) / 2); + + // then constrain all the positions to be on the plot + d.x0 = Lib.constrain(d.x0, 0, d.xa._length); + d.x1 = Lib.constrain(d.x1, 0, d.xa._length); + d.y0 = Lib.constrain(d.y0, 0, d.ya._length); + d.y1 = Lib.constrain(d.y1, 0, d.ya._length); + + // and convert the x and y label values into formatted text + if(d.xLabelVal !== undefined) { + d.xLabel = ('xLabel' in d) ? d.xLabel : Axes.hoverLabelText(d.xa, d.xLabelVal); + d.xVal = d.xa.c2d(d.xLabelVal); + } + if(d.yLabelVal !== undefined) { + d.yLabel = ('yLabel' in d) ? d.yLabel : Axes.hoverLabelText(d.ya, d.yLabelVal); + d.yVal = d.ya.c2d(d.yLabelVal); + } + + // Traces like heatmaps generate the zLabel in their hoverPoints function + if(d.zLabelVal !== undefined && d.zLabel === undefined) { + d.zLabel = String(d.zLabelVal); + } + + // for box means and error bars, add the range to the label + if(!isNaN(d.xerr) && !(d.xa.type === 'log' && d.xerr <= 0)) { + var xeText = Axes.tickText(d.xa, d.xa.c2l(d.xerr), 'hover').text; + if(d.xerrneg !== undefined) { + d.xLabel += ' +' + xeText + ' / -' + + Axes.tickText(d.xa, d.xa.c2l(d.xerrneg), 'hover').text; + } else d.xLabel += ' ± ' + xeText; + + // small distance penalty for error bars, so that if there are + // traces with errors and some without, the error bar label will + // hoist up to the point + if(hovermode === 'x') d.distance += 1; + } + if(!isNaN(d.yerr) && !(d.ya.type === 'log' && d.yerr <= 0)) { + var yeText = Axes.tickText(d.ya, d.ya.c2l(d.yerr), 'hover').text; + if(d.yerrneg !== undefined) { + d.yLabel += ' +' + yeText + ' / -' + + Axes.tickText(d.ya, d.ya.c2l(d.yerrneg), 'hover').text; + } else d.yLabel += ' ± ' + yeText; + + if(hovermode === 'y') d.distance += 1; + } + + var infomode = d.hoverinfo || d.trace.hoverinfo; + + if(infomode && infomode !== 'all') { + infomode = Array.isArray(infomode) ? infomode : infomode.split('+'); + if(infomode.indexOf('x') === -1) d.xLabel = undefined; + if(infomode.indexOf('y') === -1) d.yLabel = undefined; + if(infomode.indexOf('z') === -1) d.zLabel = undefined; + if(infomode.indexOf('text') === -1) d.text = undefined; + if(infomode.indexOf('name') === -1) d.name = undefined; + } + + return d; +} + +function createSpikelines(gd, closestPoints, opts) { + var container = opts.container; + var fullLayout = opts.fullLayout; + var gs = fullLayout._size; + var evt = opts.event; + var showY = !!closestPoints.hLinePoint; + var showX = !!closestPoints.vLinePoint; + + var xa, ya; + + // Remove old spikeline items + container.selectAll('.spikeline').remove(); + + if(!(showX || showY)) return; + + var contrastColor = Color.combine(fullLayout.plot_bgcolor, fullLayout.paper_bgcolor); + + // Horizontal line (to y-axis) + if(showY) { + var hLinePoint = closestPoints.hLinePoint; + var hLinePointX, hLinePointY; + + xa = hLinePoint && hLinePoint.xa; + ya = hLinePoint && hLinePoint.ya; + var ySnap = ya.spikesnap; + + if(ySnap === 'cursor') { + hLinePointX = evt.pointerX; + hLinePointY = evt.pointerY; + } else { + hLinePointX = xa._offset + hLinePoint.x; + hLinePointY = ya._offset + hLinePoint.y; + } + var dfltHLineColor = tinycolor.readability(hLinePoint.color, contrastColor) < 1.5 ? + Color.contrast(contrastColor) : hLinePoint.color; + var yMode = ya.spikemode; + var yThickness = ya.spikethickness; + var yColor = ya.spikecolor || dfltHLineColor; + var xEdge = Axes.getPxPosition(gd, ya); + var xBase, xEndSpike; + + if(yMode.indexOf('toaxis') !== -1 || yMode.indexOf('across') !== -1) { + if(yMode.indexOf('toaxis') !== -1) { + xBase = xEdge; + xEndSpike = hLinePointX; + } + if(yMode.indexOf('across') !== -1) { + var xAcross0 = ya._counterDomainMin; + var xAcross1 = ya._counterDomainMax; + if(ya.anchor === 'free') { + xAcross0 = Math.min(xAcross0, ya.position); + xAcross1 = Math.max(xAcross1, ya.position); + } + xBase = gs.l + xAcross0 * gs.w; + xEndSpike = gs.l + xAcross1 * gs.w; + } + + // Foreground horizontal line (to y-axis) + container.insert('line', ':first-child') + .attr({ + x1: xBase, + x2: xEndSpike, + y1: hLinePointY, + y2: hLinePointY, + 'stroke-width': yThickness, + stroke: yColor, + 'stroke-dasharray': Drawing.dashStyle(ya.spikedash, yThickness) + }) + .classed('spikeline', true) + .classed('crisp', true); + + // Background horizontal Line (to y-axis) + container.insert('line', ':first-child') + .attr({ + x1: xBase, + x2: xEndSpike, + y1: hLinePointY, + y2: hLinePointY, + 'stroke-width': yThickness + 2, + stroke: contrastColor + }) + .classed('spikeline', true) + .classed('crisp', true); + } + // Y axis marker + if(yMode.indexOf('marker') !== -1) { + container.insert('circle', ':first-child') + .attr({ + cx: xEdge + (ya.side !== 'right' ? yThickness : -yThickness), + cy: hLinePointY, + r: yThickness, + fill: yColor + }) + .classed('spikeline', true); + } + } + + if(showX) { + var vLinePoint = closestPoints.vLinePoint; + var vLinePointX, vLinePointY; + + xa = vLinePoint && vLinePoint.xa; + ya = vLinePoint && vLinePoint.ya; + var xSnap = xa.spikesnap; + + if(xSnap === 'cursor') { + vLinePointX = evt.pointerX; + vLinePointY = evt.pointerY; + } else { + vLinePointX = xa._offset + vLinePoint.x; + vLinePointY = ya._offset + vLinePoint.y; + } + var dfltVLineColor = tinycolor.readability(vLinePoint.color, contrastColor) < 1.5 ? + Color.contrast(contrastColor) : vLinePoint.color; + var xMode = xa.spikemode; + var xThickness = xa.spikethickness; + var xColor = xa.spikecolor || dfltVLineColor; + var yEdge = Axes.getPxPosition(gd, xa); + var yBase, yEndSpike; + + if(xMode.indexOf('toaxis') !== -1 || xMode.indexOf('across') !== -1) { + if(xMode.indexOf('toaxis') !== -1) { + yBase = yEdge; + yEndSpike = vLinePointY; + } + if(xMode.indexOf('across') !== -1) { + var yAcross0 = xa._counterDomainMin; + var yAcross1 = xa._counterDomainMax; + if(xa.anchor === 'free') { + yAcross0 = Math.min(yAcross0, xa.position); + yAcross1 = Math.max(yAcross1, xa.position); + } + yBase = gs.t + (1 - yAcross1) * gs.h; + yEndSpike = gs.t + (1 - yAcross0) * gs.h; + } + + // Foreground vertical line (to x-axis) + container.insert('line', ':first-child') + .attr({ + x1: vLinePointX, + x2: vLinePointX, + y1: yBase, + y2: yEndSpike, + 'stroke-width': xThickness, + stroke: xColor, + 'stroke-dasharray': Drawing.dashStyle(xa.spikedash, xThickness) + }) + .classed('spikeline', true) + .classed('crisp', true); + + // Background vertical line (to x-axis) + container.insert('line', ':first-child') + .attr({ + x1: vLinePointX, + x2: vLinePointX, + y1: yBase, + y2: yEndSpike, + 'stroke-width': xThickness + 2, + stroke: contrastColor + }) + .classed('spikeline', true) + .classed('crisp', true); + } + + // X axis marker + if(xMode.indexOf('marker') !== -1) { + container.insert('circle', ':first-child') + .attr({ + cx: vLinePointX, + cy: yEdge - (xa.side !== 'top' ? xThickness : -xThickness), + r: xThickness, + fill: xColor + }) + .classed('spikeline', true); + } + } +} + +function hoverChanged(gd, evt, oldhoverdata) { + // don't emit any events if nothing changed + if(!oldhoverdata || oldhoverdata.length !== gd._hoverdata.length) return true; + + for(var i = oldhoverdata.length - 1; i >= 0; i--) { + var oldPt = oldhoverdata[i]; + var newPt = gd._hoverdata[i]; + + if(oldPt.curveNumber !== newPt.curveNumber || + String(oldPt.pointNumber) !== String(newPt.pointNumber) || + String(oldPt.pointNumbers) !== String(newPt.pointNumbers) + ) { + return true; + } + } + return false; +} + +function spikesChanged(gd, oldspikepoints) { + // don't relayout the plot because of new spikelines if spikelines points didn't change + if(!oldspikepoints) return true; + if(oldspikepoints.vLinePoint !== gd._spikepoints.vLinePoint || + oldspikepoints.hLinePoint !== gd._spikepoints.hLinePoint + ) return true; + return false; +} + +function plainText(s, len) { + return svgTextUtils.plainText(s || '', { + len: len, + allowedTags: ['br', 'sub', 'sup', 'b', 'i', 'em'] + }); +} + +},{"../../lib":169,"../../lib/events":163,"../../lib/override_cursor":180,"../../lib/svg_text_utils":190,"../../plots/cartesian/axes":213,"../../registry":258,"../color":51,"../dragelement":69,"../drawing":72,"./constants":84,"./helpers":86,"d3":16,"fast-isnumeric":18,"tinycolor2":34}],88:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); + +module.exports = function handleHoverLabelDefaults(contIn, contOut, coerce, opts) { + opts = opts || {}; + + coerce('hoverlabel.bgcolor', opts.bgcolor); + coerce('hoverlabel.bordercolor', opts.bordercolor); + coerce('hoverlabel.namelength', opts.namelength); + Lib.coerceFont(coerce, 'hoverlabel.font', opts.font); + coerce('hoverlabel.align', opts.align); +}; + +},{"../../lib":169}],89:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); +var Lib = _dereq_('../../lib'); +var dragElement = _dereq_('../dragelement'); +var helpers = _dereq_('./helpers'); +var layoutAttributes = _dereq_('./layout_attributes'); +var hoverModule = _dereq_('./hover'); + +module.exports = { + moduleType: 'component', + name: 'fx', + + constants: _dereq_('./constants'), + schema: { + layout: layoutAttributes + }, + + attributes: _dereq_('./attributes'), + layoutAttributes: layoutAttributes, + + supplyLayoutGlobalDefaults: _dereq_('./layout_global_defaults'), + supplyDefaults: _dereq_('./defaults'), + supplyLayoutDefaults: _dereq_('./layout_defaults'), + + calc: _dereq_('./calc'), + + getDistanceFunction: helpers.getDistanceFunction, + getClosest: helpers.getClosest, + inbox: helpers.inbox, + quadrature: helpers.quadrature, + appendArrayPointValue: helpers.appendArrayPointValue, + + castHoverOption: castHoverOption, + castHoverinfo: castHoverinfo, + + hover: hoverModule.hover, + unhover: dragElement.unhover, + + loneHover: hoverModule.loneHover, + loneUnhover: loneUnhover, + + click: _dereq_('./click') +}; + +function loneUnhover(containerOrSelection) { + // duck type whether the arg is a d3 selection because ie9 doesn't + // handle instanceof like modern browsers do. + var selection = Lib.isD3Selection(containerOrSelection) ? + containerOrSelection : + d3.select(containerOrSelection); + + selection.selectAll('g.hovertext').remove(); + selection.selectAll('.spikeline').remove(); +} + +// helpers for traces that use Fx.loneHover + +function castHoverOption(trace, ptNumber, attr) { + return Lib.castOption(trace, ptNumber, 'hoverlabel.' + attr); +} + +function castHoverinfo(trace, fullLayout, ptNumber) { + function _coerce(val) { + return Lib.coerceHoverinfo({hoverinfo: val}, {_module: trace._module}, fullLayout); + } + + return Lib.castOption(trace, ptNumber, 'hoverinfo', _coerce); +} + +},{"../../lib":169,"../dragelement":69,"./attributes":81,"./calc":82,"./click":83,"./constants":84,"./defaults":85,"./helpers":86,"./hover":87,"./layout_attributes":90,"./layout_defaults":91,"./layout_global_defaults":92,"d3":16}],90:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var constants = _dereq_('./constants'); + +var fontAttrs = _dereq_('../../plots/font_attributes')({ + editType: 'none', + +}); +fontAttrs.family.dflt = constants.HOVERFONT; +fontAttrs.size.dflt = constants.HOVERFONTSIZE; + +module.exports = { + clickmode: { + valType: 'flaglist', + + flags: ['event', 'select'], + dflt: 'event', + editType: 'plot', + extras: ['none'], + + }, + dragmode: { + valType: 'enumerated', + + values: ['zoom', 'pan', 'select', 'lasso', 'orbit', 'turntable', false], + dflt: 'zoom', + editType: 'modebar', + + }, + hovermode: { + valType: 'enumerated', + + values: ['x', 'y', 'closest', false], + editType: 'modebar', + + }, + hoverdistance: { + valType: 'integer', + min: -1, + dflt: 20, + + editType: 'none', + + }, + spikedistance: { + valType: 'integer', + min: -1, + dflt: 20, + + editType: 'none', + + }, + hoverlabel: { + bgcolor: { + valType: 'color', + + editType: 'none', + + }, + bordercolor: { + valType: 'color', + + editType: 'none', + + }, + font: fontAttrs, + align: { + valType: 'enumerated', + values: ['left', 'right', 'auto'], + dflt: 'auto', + + editType: 'none', + + }, + namelength: { + valType: 'integer', + min: -1, + dflt: 15, + + editType: 'none', + + }, + editType: 'none' + }, + selectdirection: { + valType: 'enumerated', + + values: ['h', 'v', 'd', 'any'], + dflt: 'any', + + editType: 'none' + } +}; + +},{"../../plots/font_attributes":239,"./constants":84}],91:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var layoutAttributes = _dereq_('./layout_attributes'); + +module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { + function coerce(attr, dflt) { + return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt); + } + + var clickmode = coerce('clickmode'); + + var dragMode = coerce('dragmode'); + if(dragMode === 'select') coerce('selectdirection'); + + var hovermodeDflt; + if(layoutOut._has('cartesian')) { + if(clickmode.indexOf('select') > -1) { + hovermodeDflt = 'closest'; + } else { + // flag for 'horizontal' plots: + // determines the state of the mode bar 'compare' hovermode button + layoutOut._isHoriz = isHoriz(fullData, layoutOut); + hovermodeDflt = layoutOut._isHoriz ? 'y' : 'x'; + } + } else hovermodeDflt = 'closest'; + + var hoverMode = coerce('hovermode', hovermodeDflt); + if(hoverMode) { + coerce('hoverdistance'); + coerce('spikedistance'); + } + + // if only mapbox or geo subplots is present on graph, + // reset 'zoom' dragmode to 'pan' until 'zoom' is implemented, + // so that the correct modebar button is active + var hasMapbox = layoutOut._has('mapbox'); + var hasGeo = layoutOut._has('geo'); + var len = layoutOut._basePlotModules.length; + + if(layoutOut.dragmode === 'zoom' && ( + ((hasMapbox || hasGeo) && len === 1) || + (hasMapbox && hasGeo && len === 2) + )) { + layoutOut.dragmode = 'pan'; + } +}; + +function isHoriz(fullData, fullLayout) { + var stackOpts = fullLayout._scatterStackOpts || {}; + + for(var i = 0; i < fullData.length; i++) { + var trace = fullData[i]; + var subplot = trace.xaxis + trace.yaxis; + var subplotStackOpts = stackOpts[subplot] || {}; + var groupOpts = subplotStackOpts[trace.stackgroup] || {}; + + if(trace.orientation !== 'h' && groupOpts.orientation !== 'h') { + return false; + } + } + + return true; +} + +},{"../../lib":169,"./layout_attributes":90}],92:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var handleHoverLabelDefaults = _dereq_('./hoverlabel_defaults'); +var layoutAttributes = _dereq_('./layout_attributes'); + +module.exports = function supplyLayoutGlobalDefaults(layoutIn, layoutOut) { + function coerce(attr, dflt) { + return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt); + } + + handleHoverLabelDefaults(layoutIn, layoutOut, coerce); +}; + +},{"../../lib":169,"./hoverlabel_defaults":88,"./layout_attributes":90}],93:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var counterRegex = _dereq_('../../lib/regex').counter; +var domainAttrs = _dereq_('../../plots/domain').attributes; +var cartesianIdRegex = _dereq_('../../plots/cartesian/constants').idRegex; +var Template = _dereq_('../../plot_api/plot_template'); + +var gridAttrs = { + rows: { + valType: 'integer', + min: 1, + + editType: 'plot', + + }, + roworder: { + valType: 'enumerated', + values: ['top to bottom', 'bottom to top'], + dflt: 'top to bottom', + + editType: 'plot', + + }, + columns: { + valType: 'integer', + min: 1, + + editType: 'plot', + + }, + subplots: { + valType: 'info_array', + freeLength: true, + dimensions: 2, + items: {valType: 'enumerated', values: [counterRegex('xy').toString(), ''], editType: 'plot'}, + + editType: 'plot', + + }, + xaxes: { + valType: 'info_array', + freeLength: true, + items: {valType: 'enumerated', values: [cartesianIdRegex.x.toString(), ''], editType: 'plot'}, + + editType: 'plot', + + }, + yaxes: { + valType: 'info_array', + freeLength: true, + items: {valType: 'enumerated', values: [cartesianIdRegex.y.toString(), ''], editType: 'plot'}, + + editType: 'plot', + + }, + pattern: { + valType: 'enumerated', + values: ['independent', 'coupled'], + dflt: 'coupled', + + editType: 'plot', + + }, + xgap: { + valType: 'number', + min: 0, + max: 1, + + editType: 'plot', + + }, + ygap: { + valType: 'number', + min: 0, + max: 1, + + editType: 'plot', + + }, + domain: domainAttrs({name: 'grid', editType: 'plot', noGridCell: true}, { + + }), + xside: { + valType: 'enumerated', + values: ['bottom', 'bottom plot', 'top plot', 'top'], + dflt: 'bottom plot', + + editType: 'plot', + + }, + yside: { + valType: 'enumerated', + values: ['left', 'left plot', 'right plot', 'right'], + dflt: 'left plot', + + editType: 'plot', + + }, + editType: 'plot' +}; + +function getAxes(layout, grid, axLetter) { + var gridVal = grid[axLetter + 'axes']; + var splomVal = Object.keys((layout._splomAxes || {})[axLetter] || {}); + + if(Array.isArray(gridVal)) return gridVal; + if(splomVal.length) return splomVal; +} + +// the shape of the grid - this needs to be done BEFORE supplyDataDefaults +// so that non-subplot traces can place themselves in the grid +function sizeDefaults(layoutIn, layoutOut) { + var gridIn = layoutIn.grid || {}; + var xAxes = getAxes(layoutOut, gridIn, 'x'); + var yAxes = getAxes(layoutOut, gridIn, 'y'); + + if(!layoutIn.grid && !xAxes && !yAxes) return; + + var hasSubplotGrid = Array.isArray(gridIn.subplots) && Array.isArray(gridIn.subplots[0]); + var hasXaxes = Array.isArray(xAxes); + var hasYaxes = Array.isArray(yAxes); + var isSplomGenerated = ( + hasXaxes && xAxes !== gridIn.xaxes && + hasYaxes && yAxes !== gridIn.yaxes + ); + + var dfltRows, dfltColumns; + + if(hasSubplotGrid) { + dfltRows = gridIn.subplots.length; + dfltColumns = gridIn.subplots[0].length; + } else { + if(hasYaxes) dfltRows = yAxes.length; + if(hasXaxes) dfltColumns = xAxes.length; + } + + var gridOut = Template.newContainer(layoutOut, 'grid'); + + function coerce(attr, dflt) { + return Lib.coerce(gridIn, gridOut, gridAttrs, attr, dflt); + } + + var rows = coerce('rows', dfltRows); + var columns = coerce('columns', dfltColumns); + + if(!(rows * columns > 1)) { + delete layoutOut.grid; + return; + } + + if(!hasSubplotGrid && !hasXaxes && !hasYaxes) { + var useDefaultSubplots = coerce('pattern') === 'independent'; + if(useDefaultSubplots) hasSubplotGrid = true; + } + gridOut._hasSubplotGrid = hasSubplotGrid; + + var rowOrder = coerce('roworder'); + var reversed = rowOrder === 'top to bottom'; + + var dfltGapX = hasSubplotGrid ? 0.2 : 0.1; + var dfltGapY = hasSubplotGrid ? 0.3 : 0.1; + + var dfltSideX, dfltSideY; + if(isSplomGenerated && layoutOut._splomGridDflt) { + dfltSideX = layoutOut._splomGridDflt.xside; + dfltSideY = layoutOut._splomGridDflt.yside; + } + + gridOut._domains = { + x: fillGridPositions('x', coerce, dfltGapX, dfltSideX, columns), + y: fillGridPositions('y', coerce, dfltGapY, dfltSideY, rows, reversed) + }; +} + +// coerce x or y sizing attributes and return an array of domains for this direction +function fillGridPositions(axLetter, coerce, dfltGap, dfltSide, len, reversed) { + var dirGap = coerce(axLetter + 'gap', dfltGap); + var domain = coerce('domain.' + axLetter); + coerce(axLetter + 'side', dfltSide); + + var out = new Array(len); + var start = domain[0]; + var step = (domain[1] - start) / (len - dirGap); + var cellDomain = step * (1 - dirGap); + for(var i = 0; i < len; i++) { + var cellStart = start + step * i; + out[reversed ? (len - 1 - i) : i] = [cellStart, cellStart + cellDomain]; + } + return out; +} + +// the (cartesian) contents of the grid - this needs to happen AFTER supplyDataDefaults +// so that we know what cartesian subplots are available +function contentDefaults(layoutIn, layoutOut) { + var gridOut = layoutOut.grid; + // make sure we got to the end of handleGridSizing + if(!gridOut || !gridOut._domains) return; + + var gridIn = layoutIn.grid || {}; + var subplots = layoutOut._subplots; + var hasSubplotGrid = gridOut._hasSubplotGrid; + var rows = gridOut.rows; + var columns = gridOut.columns; + var useDefaultSubplots = gridOut.pattern === 'independent'; + + var i, j, xId, yId, subplotId, subplotsOut, yPos; + + var axisMap = gridOut._axisMap = {}; + + if(hasSubplotGrid) { + var subplotsIn = gridIn.subplots || []; + subplotsOut = gridOut.subplots = new Array(rows); + var index = 1; + + for(i = 0; i < rows; i++) { + var rowOut = subplotsOut[i] = new Array(columns); + var rowIn = subplotsIn[i] || []; + for(j = 0; j < columns; j++) { + if(useDefaultSubplots) { + subplotId = (index === 1) ? 'xy' : ('x' + index + 'y' + index); + index++; + } else subplotId = rowIn[j]; + + rowOut[j] = ''; + + if(subplots.cartesian.indexOf(subplotId) !== -1) { + yPos = subplotId.indexOf('y'); + xId = subplotId.slice(0, yPos); + yId = subplotId.slice(yPos); + if((axisMap[xId] !== undefined && axisMap[xId] !== j) || + (axisMap[yId] !== undefined && axisMap[yId] !== i) + ) { + continue; + } + + rowOut[j] = subplotId; + axisMap[xId] = j; + axisMap[yId] = i; + } + } + } + } else { + var xAxes = getAxes(layoutOut, gridIn, 'x'); + var yAxes = getAxes(layoutOut, gridIn, 'y'); + gridOut.xaxes = fillGridAxes(xAxes, subplots.xaxis, columns, axisMap, 'x'); + gridOut.yaxes = fillGridAxes(yAxes, subplots.yaxis, rows, axisMap, 'y'); + } + + var anchors = gridOut._anchors = {}; + var reversed = gridOut.roworder === 'top to bottom'; + + for(var axisId in axisMap) { + var axLetter = axisId.charAt(0); + var side = gridOut[axLetter + 'side']; + + var i0, inc, iFinal; + + if(side.length < 8) { + // grid edge - ie not "* plot" - make these as free axes + // since we're not guaranteed to have a subplot there at all + anchors[axisId] = 'free'; + } else if(axLetter === 'x') { + if((side.charAt(0) === 't') === reversed) { + i0 = 0; + inc = 1; + iFinal = rows; + } else { + i0 = rows - 1; + inc = -1; + iFinal = -1; + } + if(hasSubplotGrid) { + var column = axisMap[axisId]; + for(i = i0; i !== iFinal; i += inc) { + subplotId = subplotsOut[i][column]; + if(!subplotId) continue; + yPos = subplotId.indexOf('y'); + if(subplotId.slice(0, yPos) === axisId) { + anchors[axisId] = subplotId.slice(yPos); + break; + } + } + } else { + for(i = i0; i !== iFinal; i += inc) { + yId = gridOut.yaxes[i]; + if(subplots.cartesian.indexOf(axisId + yId) !== -1) { + anchors[axisId] = yId; + break; + } + } + } + } else { + if((side.charAt(0) === 'l')) { + i0 = 0; + inc = 1; + iFinal = columns; + } else { + i0 = columns - 1; + inc = -1; + iFinal = -1; + } + if(hasSubplotGrid) { + var row = axisMap[axisId]; + for(i = i0; i !== iFinal; i += inc) { + subplotId = subplotsOut[row][i]; + if(!subplotId) continue; + yPos = subplotId.indexOf('y'); + if(subplotId.slice(yPos) === axisId) { + anchors[axisId] = subplotId.slice(0, yPos); + break; + } + } + } else { + for(i = i0; i !== iFinal; i += inc) { + xId = gridOut.xaxes[i]; + if(subplots.cartesian.indexOf(xId + axisId) !== -1) { + anchors[axisId] = xId; + break; + } + } + } + } + } +} + +function fillGridAxes(axesIn, axesAllowed, len, axisMap, axLetter) { + var out = new Array(len); + var i; + + function fillOneAxis(i, axisId) { + if(axesAllowed.indexOf(axisId) !== -1 && axisMap[axisId] === undefined) { + out[i] = axisId; + axisMap[axisId] = i; + } else out[i] = ''; + } + + if(Array.isArray(axesIn)) { + for(i = 0; i < len; i++) { + fillOneAxis(i, axesIn[i]); + } + } else { + // default axis list is the first `len` axis ids + fillOneAxis(0, axLetter); + for(i = 1; i < len; i++) { + fillOneAxis(i, axLetter + (i + 1)); + } + } + + return out; +} + +module.exports = { + moduleType: 'component', + name: 'grid', + + schema: { + layout: {grid: gridAttrs} + }, + + layoutAttributes: gridAttrs, + sizeDefaults: sizeDefaults, + contentDefaults: contentDefaults +}; + +},{"../../lib":169,"../../lib/regex":184,"../../plot_api/plot_template":203,"../../plots/cartesian/constants":219,"../../plots/domain":238}],94:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var cartesianConstants = _dereq_('../../plots/cartesian/constants'); +var templatedArray = _dereq_('../../plot_api/plot_template').templatedArray; + + +module.exports = templatedArray('image', { + visible: { + valType: 'boolean', + + dflt: true, + editType: 'arraydraw', + + }, + + source: { + valType: 'string', + + editType: 'arraydraw', + + }, + + layer: { + valType: 'enumerated', + values: ['below', 'above'], + dflt: 'above', + + editType: 'arraydraw', + + }, + + sizex: { + valType: 'number', + + dflt: 0, + editType: 'arraydraw', + + }, + + sizey: { + valType: 'number', + + dflt: 0, + editType: 'arraydraw', + + }, + + sizing: { + valType: 'enumerated', + values: ['fill', 'contain', 'stretch'], + dflt: 'contain', + + editType: 'arraydraw', + + }, + + opacity: { + valType: 'number', + + min: 0, + max: 1, + dflt: 1, + editType: 'arraydraw', + + }, + + x: { + valType: 'any', + + dflt: 0, + editType: 'arraydraw', + + }, + + y: { + valType: 'any', + + dflt: 0, + editType: 'arraydraw', + + }, + + xanchor: { + valType: 'enumerated', + values: ['left', 'center', 'right'], + dflt: 'left', + + editType: 'arraydraw', + + }, + + yanchor: { + valType: 'enumerated', + values: ['top', 'middle', 'bottom'], + dflt: 'top', + + editType: 'arraydraw', + + }, + + xref: { + valType: 'enumerated', + values: [ + 'paper', + cartesianConstants.idRegex.x.toString() + ], + dflt: 'paper', + + editType: 'arraydraw', + + }, + + yref: { + valType: 'enumerated', + values: [ + 'paper', + cartesianConstants.idRegex.y.toString() + ], + dflt: 'paper', + + editType: 'arraydraw', + + }, + editType: 'arraydraw' +}); + +},{"../../plot_api/plot_template":203,"../../plots/cartesian/constants":219}],95:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); +var toLogRange = _dereq_('../../lib/to_log_range'); + +/* + * convertCoords: when converting an axis between log and linear + * you need to alter any images on that axis to keep them + * pointing at the same data point. + * In v2.0 this will become obsolete (or perhaps size will still need conversion?) + * we convert size by declaring that the maximum extent *in data units* should be + * the same, assuming the image is anchored by its center (could remove that restriction + * if we think it's important) even though the actual left and right values will not be + * quite the same since the scale becomes nonlinear (and central anchor means the pixel + * center of the image, not the data units center) + * + * gd: the plot div + * ax: the axis being changed + * newType: the type it's getting + * doExtra: function(attr, val) from inside relayout that sets the attribute. + * Use this to make the changes as it's aware if any other changes in the + * same relayout call should override this conversion. + */ +module.exports = function convertCoords(gd, ax, newType, doExtra) { + ax = ax || {}; + + var toLog = (newType === 'log') && (ax.type === 'linear'); + var fromLog = (newType === 'linear') && (ax.type === 'log'); + + if(!(toLog || fromLog)) return; + + var images = gd._fullLayout.images; + var axLetter = ax._id.charAt(0); + var image; + var attrPrefix; + + for(var i = 0; i < images.length; i++) { + image = images[i]; + attrPrefix = 'images[' + i + '].'; + + if(image[axLetter + 'ref'] === ax._id) { + var currentPos = image[axLetter]; + var currentSize = image['size' + axLetter]; + var newPos = null; + var newSize = null; + + if(toLog) { + newPos = toLogRange(currentPos, ax.range); + + // this is the inverse of the conversion we do in fromLog below + // so that the conversion is reversible (notice the fromLog conversion + // is like sinh, and this one looks like arcsinh) + var dx = currentSize / Math.pow(10, newPos) / 2; + newSize = 2 * Math.log(dx + Math.sqrt(1 + dx * dx)) / Math.LN10; + } else { + newPos = Math.pow(10, currentPos); + newSize = newPos * (Math.pow(10, currentSize / 2) - Math.pow(10, -currentSize / 2)); + } + + // if conversion failed, delete the value so it can get a default later on + if(!isNumeric(newPos)) { + newPos = null; + newSize = null; + } else if(!isNumeric(newSize)) newSize = null; + + doExtra(attrPrefix + axLetter, newPos); + doExtra(attrPrefix + 'size' + axLetter, newSize); + } + } +}; + +},{"../../lib/to_log_range":192,"fast-isnumeric":18}],96:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var Axes = _dereq_('../../plots/cartesian/axes'); +var handleArrayContainerDefaults = _dereq_('../../plots/array_container_defaults'); + +var attributes = _dereq_('./attributes'); +var name = 'images'; + +module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) { + var opts = { + name: name, + handleItemDefaults: imageDefaults + }; + + handleArrayContainerDefaults(layoutIn, layoutOut, opts); +}; + + +function imageDefaults(imageIn, imageOut, fullLayout) { + function coerce(attr, dflt) { + return Lib.coerce(imageIn, imageOut, attributes, attr, dflt); + } + + var source = coerce('source'); + var visible = coerce('visible', !!source); + + if(!visible) return imageOut; + + coerce('layer'); + coerce('xanchor'); + coerce('yanchor'); + coerce('sizex'); + coerce('sizey'); + coerce('sizing'); + coerce('opacity'); + + var gdMock = { _fullLayout: fullLayout }; + var axLetters = ['x', 'y']; + + for(var i = 0; i < 2; i++) { + // 'paper' is the fallback axref + var axLetter = axLetters[i]; + var axRef = Axes.coerceRef(imageIn, imageOut, gdMock, axLetter, 'paper'); + + if(axRef !== 'paper') { + var ax = Axes.getFromId(gdMock, axRef); + ax._imgIndices.push(imageOut._index); + } + + Axes.coercePosition(imageOut, gdMock, coerce, axRef, axLetter, 0); + } + + return imageOut; +} + +},{"../../lib":169,"../../plots/array_container_defaults":209,"../../plots/cartesian/axes":213,"./attributes":94}],97:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); +var Drawing = _dereq_('../drawing'); +var Axes = _dereq_('../../plots/cartesian/axes'); +var xmlnsNamespaces = _dereq_('../../constants/xmlns_namespaces'); + +module.exports = function draw(gd) { + var fullLayout = gd._fullLayout; + var imageDataAbove = []; + var imageDataSubplot = {}; + var imageDataBelow = []; + var subplot; + var i; + + // Sort into top, subplot, and bottom layers + for(i = 0; i < fullLayout.images.length; i++) { + var img = fullLayout.images[i]; + + if(img.visible) { + if(img.layer === 'below' && img.xref !== 'paper' && img.yref !== 'paper') { + subplot = img.xref + img.yref; + + var plotinfo = fullLayout._plots[subplot]; + + if(!plotinfo) { + // Fall back to _imageLowerLayer in case the requested subplot doesn't exist. + // This can happen if you reference the image to an x / y axis combination + // that doesn't have any data on it (and layer is below) + imageDataBelow.push(img); + continue; + } + + if(plotinfo.mainplot) { + subplot = plotinfo.mainplot.id; + } + + if(!imageDataSubplot[subplot]) { + imageDataSubplot[subplot] = []; + } + imageDataSubplot[subplot].push(img); + } else if(img.layer === 'above') { + imageDataAbove.push(img); + } else { + imageDataBelow.push(img); + } + } + } + + + var anchors = { + x: { + left: { sizing: 'xMin', offset: 0 }, + center: { sizing: 'xMid', offset: -1 / 2 }, + right: { sizing: 'xMax', offset: -1 } + }, + y: { + top: { sizing: 'YMin', offset: 0 }, + middle: { sizing: 'YMid', offset: -1 / 2 }, + bottom: { sizing: 'YMax', offset: -1 } + } + }; + + + // Images must be converted to dataURL's for exporting. + function setImage(d) { + var thisImage = d3.select(this); + + if(this._imgSrc === d.source) { + return; + } + + thisImage.attr('xmlns', xmlnsNamespaces.svg); + + if(d.source && d.source.slice(0, 5) === 'data:') { + thisImage.attr('xlink:href', d.source); + this._imgSrc = d.source; + } else { + var imagePromise = new Promise(function(resolve) { + var img = new Image(); + this.img = img; + + // If not set, a `tainted canvas` error is thrown + img.setAttribute('crossOrigin', 'anonymous'); + img.onerror = errorHandler; + img.onload = function() { + var canvas = document.createElement('canvas'); + canvas.width = this.width; + canvas.height = this.height; + + var ctx = canvas.getContext('2d'); + ctx.drawImage(this, 0, 0); + + var dataURL = canvas.toDataURL('image/png'); + + thisImage.attr('xlink:href', dataURL); + + // resolve promise in onload handler instead of on 'load' to support IE11 + // see https://github.com/plotly/plotly.js/issues/1685 + // for more details + resolve(); + }; + + thisImage.on('error', errorHandler); + + img.src = d.source; + this._imgSrc = d.source; + + function errorHandler() { + thisImage.remove(); + resolve(); + } + }.bind(this)); + + gd._promises.push(imagePromise); + } + } + + function applyAttributes(d) { + var thisImage = d3.select(this); + + // Axes if specified + var xa = Axes.getFromId(gd, d.xref); + var ya = Axes.getFromId(gd, d.yref); + + var size = fullLayout._size; + var width = xa ? Math.abs(xa.l2p(d.sizex) - xa.l2p(0)) : d.sizex * size.w; + var height = ya ? Math.abs(ya.l2p(d.sizey) - ya.l2p(0)) : d.sizey * size.h; + + // Offsets for anchor positioning + var xOffset = width * anchors.x[d.xanchor].offset; + var yOffset = height * anchors.y[d.yanchor].offset; + + var sizing = anchors.x[d.xanchor].sizing + anchors.y[d.yanchor].sizing; + + // Final positions + var xPos = (xa ? xa.r2p(d.x) + xa._offset : d.x * size.w + size.l) + xOffset; + var yPos = (ya ? ya.r2p(d.y) + ya._offset : size.h - d.y * size.h + size.t) + yOffset; + + // Construct the proper aspectRatio attribute + switch(d.sizing) { + case 'fill': + sizing += ' slice'; + break; + + case 'stretch': + sizing = 'none'; + break; + } + + thisImage.attr({ + x: xPos, + y: yPos, + width: width, + height: height, + preserveAspectRatio: sizing, + opacity: d.opacity + }); + + + // Set proper clipping on images + var xId = xa ? xa._id : ''; + var yId = ya ? ya._id : ''; + var clipAxes = xId + yId; + + Drawing.setClipUrl( + thisImage, + clipAxes ? ('clip' + fullLayout._uid + clipAxes) : null, + gd + ); + } + + var imagesBelow = fullLayout._imageLowerLayer.selectAll('image') + .data(imageDataBelow); + var imagesAbove = fullLayout._imageUpperLayer.selectAll('image') + .data(imageDataAbove); + + imagesBelow.enter().append('image'); + imagesAbove.enter().append('image'); + + imagesBelow.exit().remove(); + imagesAbove.exit().remove(); + + imagesBelow.each(function(d) { + setImage.bind(this)(d); + applyAttributes.bind(this)(d); + }); + imagesAbove.each(function(d) { + setImage.bind(this)(d); + applyAttributes.bind(this)(d); + }); + + var allSubplots = Object.keys(fullLayout._plots); + for(i = 0; i < allSubplots.length; i++) { + subplot = allSubplots[i]; + var subplotObj = fullLayout._plots[subplot]; + + // filter out overlaid plots (which havd their images on the main plot) + // and gl2d plots (which don't support below images, at least not yet) + if(!subplotObj.imagelayer) continue; + + var imagesOnSubplot = subplotObj.imagelayer.selectAll('image') + // even if there are no images on this subplot, we need to run + // enter and exit in case there were previously + .data(imageDataSubplot[subplot] || []); + + imagesOnSubplot.enter().append('image'); + imagesOnSubplot.exit().remove(); + + imagesOnSubplot.each(function(d) { + setImage.bind(this)(d); + applyAttributes.bind(this)(d); + }); + } +}; + +},{"../../constants/xmlns_namespaces":150,"../../plots/cartesian/axes":213,"../drawing":72,"d3":16}],98:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + moduleType: 'component', + name: 'images', + + layoutAttributes: _dereq_('./attributes'), + supplyLayoutDefaults: _dereq_('./defaults'), + includeBasePlot: _dereq_('../../plots/cartesian/include_components')('images'), + + draw: _dereq_('./draw'), + + convertCoords: _dereq_('./convert_coords') +}; + +},{"../../plots/cartesian/include_components":223,"./attributes":94,"./convert_coords":95,"./defaults":96,"./draw":97}],99:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var fontAttrs = _dereq_('../../plots/font_attributes'); +var colorAttrs = _dereq_('../color/attributes'); + + +module.exports = { + bgcolor: { + valType: 'color', + + editType: 'legend', + + }, + bordercolor: { + valType: 'color', + dflt: colorAttrs.defaultLine, + + editType: 'legend', + + }, + borderwidth: { + valType: 'number', + min: 0, + dflt: 0, + + editType: 'legend', + + }, + font: fontAttrs({ + editType: 'legend', + + }), + orientation: { + valType: 'enumerated', + values: ['v', 'h'], + dflt: 'v', + + editType: 'legend', + + }, + traceorder: { + valType: 'flaglist', + flags: ['reversed', 'grouped'], + extras: ['normal'], + + editType: 'legend', + + }, + tracegroupgap: { + valType: 'number', + min: 0, + dflt: 10, + + editType: 'legend', + + }, + itemsizing: { + valType: 'enumerated', + values: ['trace', 'constant'], + dflt: 'trace', + + editType: 'legend', + + }, + + itemclick: { + valType: 'enumerated', + values: ['toggle', 'toggleothers', false], + dflt: 'toggle', + + editType: 'legend', + + }, + itemdoubleclick: { + valType: 'enumerated', + values: ['toggle', 'toggleothers', false], + dflt: 'toggleothers', + + editType: 'legend', + + }, + + x: { + valType: 'number', + min: -2, + max: 3, + + editType: 'legend', + + }, + xanchor: { + valType: 'enumerated', + values: ['auto', 'left', 'center', 'right'], + dflt: 'left', + + editType: 'legend', + + }, + y: { + valType: 'number', + min: -2, + max: 3, + + editType: 'legend', + + }, + yanchor: { + valType: 'enumerated', + values: ['auto', 'top', 'middle', 'bottom'], + + editType: 'legend', + + }, + uirevision: { + valType: 'any', + + editType: 'none', + + }, + valign: { + valType: 'enumerated', + values: ['top', 'middle', 'bottom'], + dflt: 'middle', + + editType: 'legend', + + }, + editType: 'legend' +}; + +},{"../../plots/font_attributes":239,"../color/attributes":50}],100:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + scrollBarWidth: 6, + scrollBarMinHeight: 20, + scrollBarColor: '#808BA4', + scrollBarMargin: 4, + scrollBarEnterAttrs: {rx: 20, ry: 3, width: 0, height: 0}, + + // number of px between legend symbol and legend text (always in x direction) + textGap: 40, + // number of px between each legend item (x and/or y direction) + itemGap: 5 +}; + +},{}],101:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Registry = _dereq_('../../registry'); +var Lib = _dereq_('../../lib'); +var Template = _dereq_('../../plot_api/plot_template'); + +var attributes = _dereq_('./attributes'); +var basePlotLayoutAttributes = _dereq_('../../plots/layout_attributes'); +var helpers = _dereq_('./helpers'); + + +module.exports = function legendDefaults(layoutIn, layoutOut, fullData) { + var containerIn = layoutIn.legend || {}; + + var legendTraceCount = 0; + var legendReallyHasATrace = false; + var defaultOrder = 'normal'; + + for(var i = 0; i < fullData.length; i++) { + var trace = fullData[i]; + + if(!trace.visible) continue; + + // Note that we explicitly count any trace that is either shown or + // *would* be shown by default, toward the two traces you need to + // ensure the legend is shown by default, because this can still help + // disambiguate. + if(trace.showlegend || trace._dfltShowLegend) { + legendTraceCount++; + if(trace.showlegend) { + legendReallyHasATrace = true; + // Always show the legend by default if there's a pie, + // or if there's only one trace but it's explicitly shown + if(Registry.traceIs(trace, 'pie-like') || + trace._input.showlegend === true + ) { + legendTraceCount++; + } + } + } + + if((Registry.traceIs(trace, 'bar') && layoutOut.barmode === 'stack') || + ['tonextx', 'tonexty'].indexOf(trace.fill) !== -1) { + defaultOrder = helpers.isGrouped({traceorder: defaultOrder}) ? + 'grouped+reversed' : 'reversed'; + } + + if(trace.legendgroup !== undefined && trace.legendgroup !== '') { + defaultOrder = helpers.isReversed({traceorder: defaultOrder}) ? + 'reversed+grouped' : 'grouped'; + } + } + + var showLegend = Lib.coerce(layoutIn, layoutOut, + basePlotLayoutAttributes, 'showlegend', + legendReallyHasATrace && legendTraceCount > 1); + + if(showLegend === false && !containerIn.uirevision) return; + + var containerOut = Template.newContainer(layoutOut, 'legend'); + + function coerce(attr, dflt) { + return Lib.coerce(containerIn, containerOut, attributes, attr, dflt); + } + + coerce('uirevision', layoutOut.uirevision); + + if(showLegend === false) return; + + coerce('bgcolor', layoutOut.paper_bgcolor); + coerce('bordercolor'); + coerce('borderwidth'); + Lib.coerceFont(coerce, 'font', layoutOut.font); + + var orientation = coerce('orientation'); + var defaultX, defaultY, defaultYAnchor; + + if(orientation === 'h') { + defaultX = 0; + + if(Registry.getComponentMethod('rangeslider', 'isVisible')(layoutIn.xaxis)) { + defaultY = 1.1; + defaultYAnchor = 'bottom'; + } else { + // maybe use y=1.1 / yanchor=bottom as above + // to avoid https://github.com/plotly/plotly.js/issues/1199 + // in v2 + defaultY = -0.1; + defaultYAnchor = 'top'; + } + } else { + defaultX = 1.02; + defaultY = 1; + defaultYAnchor = 'auto'; + } + + coerce('traceorder', defaultOrder); + if(helpers.isGrouped(layoutOut.legend)) coerce('tracegroupgap'); + + coerce('itemsizing'); + + coerce('itemclick'); + coerce('itemdoubleclick'); + + coerce('x', defaultX); + coerce('xanchor'); + coerce('y', defaultY); + coerce('yanchor', defaultYAnchor); + coerce('valign'); + Lib.noneOrAll(containerIn, containerOut, ['x', 'y']); +}; + +},{"../../lib":169,"../../plot_api/plot_template":203,"../../plots/layout_attributes":243,"../../registry":258,"./attributes":99,"./helpers":105}],102:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); + +var Lib = _dereq_('../../lib'); +var Plots = _dereq_('../../plots/plots'); +var Registry = _dereq_('../../registry'); +var Events = _dereq_('../../lib/events'); +var dragElement = _dereq_('../dragelement'); +var Drawing = _dereq_('../drawing'); +var Color = _dereq_('../color'); +var svgTextUtils = _dereq_('../../lib/svg_text_utils'); +var handleClick = _dereq_('./handle_click'); + +var constants = _dereq_('./constants'); +var alignmentConstants = _dereq_('../../constants/alignment'); +var LINE_SPACING = alignmentConstants.LINE_SPACING; +var FROM_TL = alignmentConstants.FROM_TL; +var FROM_BR = alignmentConstants.FROM_BR; + +var getLegendData = _dereq_('./get_legend_data'); +var style = _dereq_('./style'); +var helpers = _dereq_('./helpers'); + +module.exports = function draw(gd) { + var fullLayout = gd._fullLayout; + var clipId = 'legend' + fullLayout._uid; + + if(!fullLayout._infolayer || !gd.calcdata) return; + + if(!gd._legendMouseDownTime) gd._legendMouseDownTime = 0; + + var opts = fullLayout.legend; + var legendData = fullLayout.showlegend && getLegendData(gd.calcdata, opts); + var hiddenSlices = fullLayout.hiddenlabels || []; + + if(!fullLayout.showlegend || !legendData.length) { + fullLayout._infolayer.selectAll('.legend').remove(); + fullLayout._topdefs.select('#' + clipId).remove(); + return Plots.autoMargin(gd, 'legend'); + } + + var legend = Lib.ensureSingle(fullLayout._infolayer, 'g', 'legend', function(s) { + s.attr('pointer-events', 'all'); + }); + + var clipPath = Lib.ensureSingleById(fullLayout._topdefs, 'clipPath', clipId, function(s) { + s.append('rect'); + }); + + var bg = Lib.ensureSingle(legend, 'rect', 'bg', function(s) { + s.attr('shape-rendering', 'crispEdges'); + }); + bg.call(Color.stroke, opts.bordercolor) + .call(Color.fill, opts.bgcolor) + .style('stroke-width', opts.borderwidth + 'px'); + + var scrollBox = Lib.ensureSingle(legend, 'g', 'scrollbox'); + + var scrollBar = Lib.ensureSingle(legend, 'rect', 'scrollbar', function(s) { + s.attr(constants.scrollBarEnterAttrs) + .call(Color.fill, constants.scrollBarColor); + }); + + var groups = scrollBox.selectAll('g.groups').data(legendData); + groups.enter().append('g').attr('class', 'groups'); + groups.exit().remove(); + + var traces = groups.selectAll('g.traces').data(Lib.identity); + traces.enter().append('g').attr('class', 'traces'); + traces.exit().remove(); + + traces.style('opacity', function(d) { + var trace = d[0].trace; + if(Registry.traceIs(trace, 'pie-like')) { + return hiddenSlices.indexOf(d[0].label) !== -1 ? 0.5 : 1; + } else { + return trace.visible === 'legendonly' ? 0.5 : 1; + } + }) + .each(function() { d3.select(this).call(drawTexts, gd); }) + .call(style, gd) + .each(function() { d3.select(this).call(setupTraceToggle, gd); }); + + Lib.syncOrAsync([ + Plots.previousPromises, + function() { return computeLegendDimensions(gd, groups, traces); }, + function() { + // IF expandMargin return a Promise (which is truthy), + // we're under a doAutoMargin redraw, so we don't have to + // draw the remaining pieces below + if(expandMargin(gd)) return; + + var gs = fullLayout._size; + var bw = opts.borderwidth; + + var lx = gs.l + gs.w * opts.x - FROM_TL[getXanchor(opts)] * opts._width; + var ly = gs.t + gs.h * (1 - opts.y) - FROM_TL[getYanchor(opts)] * opts._effHeight; + + if(fullLayout.margin.autoexpand) { + var lx0 = lx; + var ly0 = ly; + + lx = Lib.constrain(lx, 0, fullLayout.width - opts._width); + ly = Lib.constrain(ly, 0, fullLayout.height - opts._effHeight); + + if(lx !== lx0) { + Lib.log('Constrain legend.x to make legend fit inside graph'); + } + if(ly !== ly0) { + Lib.log('Constrain legend.y to make legend fit inside graph'); + } + } + + // Set size and position of all the elements that make up a legend: + // legend, background and border, scroll box and scroll bar + Drawing.setTranslate(legend, lx, ly); + + // to be safe, remove previous listeners + scrollBar.on('.drag', null); + legend.on('wheel', null); + + if(opts._height <= opts._maxHeight || gd._context.staticPlot) { + // if scrollbar should not be shown. + bg.attr({ + width: opts._width - bw, + height: opts._effHeight - bw, + x: bw / 2, + y: bw / 2 + }); + + Drawing.setTranslate(scrollBox, 0, 0); + + clipPath.select('rect').attr({ + width: opts._width - 2 * bw, + height: opts._effHeight - 2 * bw, + x: bw, + y: bw + }); + + Drawing.setClipUrl(scrollBox, clipId, gd); + + Drawing.setRect(scrollBar, 0, 0, 0, 0); + delete opts._scrollY; + } else { + var scrollBarHeight = Math.max(constants.scrollBarMinHeight, + opts._effHeight * opts._effHeight / opts._height); + var scrollBarYMax = opts._effHeight - + scrollBarHeight - + 2 * constants.scrollBarMargin; + var scrollBoxYMax = opts._height - opts._effHeight; + var scrollRatio = scrollBarYMax / scrollBoxYMax; + + var scrollBoxY = Math.min(opts._scrollY || 0, scrollBoxYMax); + + // increase the background and clip-path width + // by the scrollbar width and margin + bg.attr({ + width: opts._width - + 2 * bw + + constants.scrollBarWidth + + constants.scrollBarMargin, + height: opts._effHeight - bw, + x: bw / 2, + y: bw / 2 + }); + + clipPath.select('rect').attr({ + width: opts._width - + 2 * bw + + constants.scrollBarWidth + + constants.scrollBarMargin, + height: opts._effHeight - 2 * bw, + x: bw, + y: bw + scrollBoxY + }); + + Drawing.setClipUrl(scrollBox, clipId, gd); + + scrollHandler(scrollBoxY, scrollBarHeight, scrollRatio); + + // scroll legend by mousewheel or touchpad swipe up/down + legend.on('wheel', function() { + scrollBoxY = Lib.constrain( + opts._scrollY + + ((d3.event.deltaY / scrollBarYMax) * scrollBoxYMax), + 0, scrollBoxYMax); + scrollHandler(scrollBoxY, scrollBarHeight, scrollRatio); + if(scrollBoxY !== 0 && scrollBoxY !== scrollBoxYMax) { + d3.event.preventDefault(); + } + }); + + var eventY0, eventY1, scrollBoxY0; + + var getScrollBarDragY = function(scrollBoxY0, eventY0, eventY1) { + var y = ((eventY1 - eventY0) / scrollRatio) + scrollBoxY0; + return Lib.constrain(y, 0, scrollBoxYMax); + }; + + var getNaturalDragY = function(scrollBoxY0, eventY0, eventY1) { + var y = ((eventY0 - eventY1) / scrollRatio) + scrollBoxY0; + return Lib.constrain(y, 0, scrollBoxYMax); + }; + + // scroll legend by dragging scrollBAR + var scrollBarDrag = d3.behavior.drag() + .on('dragstart', function() { + var e = d3.event.sourceEvent; + if(e.type === 'touchstart') { + eventY0 = e.changedTouches[0].clientY; + } else { + eventY0 = e.clientY; + } + scrollBoxY0 = scrollBoxY; + }) + .on('drag', function() { + var e = d3.event.sourceEvent; + if(e.buttons === 2 || e.ctrlKey) return; + if(e.type === 'touchmove') { + eventY1 = e.changedTouches[0].clientY; + } else { + eventY1 = e.clientY; + } + scrollBoxY = getScrollBarDragY(scrollBoxY0, eventY0, eventY1); + scrollHandler(scrollBoxY, scrollBarHeight, scrollRatio); + }); + scrollBar.call(scrollBarDrag); + + // scroll legend by touch-dragging scrollBOX + var scrollBoxTouchDrag = d3.behavior.drag() + .on('dragstart', function() { + var e = d3.event.sourceEvent; + if(e.type === 'touchstart') { + eventY0 = e.changedTouches[0].clientY; + scrollBoxY0 = scrollBoxY; + } + }) + .on('drag', function() { + var e = d3.event.sourceEvent; + if(e.type === 'touchmove') { + eventY1 = e.changedTouches[0].clientY; + scrollBoxY = getNaturalDragY(scrollBoxY0, eventY0, eventY1); + scrollHandler(scrollBoxY, scrollBarHeight, scrollRatio); + } + }); + scrollBox.call(scrollBoxTouchDrag); + } + + function scrollHandler(scrollBoxY, scrollBarHeight, scrollRatio) { + opts._scrollY = gd._fullLayout.legend._scrollY = scrollBoxY; + Drawing.setTranslate(scrollBox, 0, -scrollBoxY); + + Drawing.setRect( + scrollBar, + opts._width, + constants.scrollBarMargin + scrollBoxY * scrollRatio, + constants.scrollBarWidth, + scrollBarHeight + ); + clipPath.select('rect').attr('y', bw + scrollBoxY); + } + + if(gd._context.edits.legendPosition) { + var xf, yf, x0, y0; + + legend.classed('cursor-move', true); + + dragElement.init({ + element: legend.node(), + gd: gd, + prepFn: function() { + var transform = Drawing.getTranslate(legend); + x0 = transform.x; + y0 = transform.y; + }, + moveFn: function(dx, dy) { + var newX = x0 + dx; + var newY = y0 + dy; + + Drawing.setTranslate(legend, newX, newY); + + xf = dragElement.align(newX, 0, gs.l, gs.l + gs.w, opts.xanchor); + yf = dragElement.align(newY, 0, gs.t + gs.h, gs.t, opts.yanchor); + }, + doneFn: function() { + if(xf !== undefined && yf !== undefined) { + Registry.call('_guiRelayout', gd, {'legend.x': xf, 'legend.y': yf}); + } + }, + clickFn: function(numClicks, e) { + var clickedTrace = fullLayout._infolayer.selectAll('g.traces').filter(function() { + var bbox = this.getBoundingClientRect(); + return ( + e.clientX >= bbox.left && e.clientX <= bbox.right && + e.clientY >= bbox.top && e.clientY <= bbox.bottom + ); + }); + if(clickedTrace.size() > 0) { + clickOrDoubleClick(gd, legend, clickedTrace, numClicks, e); + } + } + }); + } + }], gd); +}; + +function clickOrDoubleClick(gd, legend, legendItem, numClicks, evt) { + var trace = legendItem.data()[0][0].trace; + var evtData = { + event: evt, + node: legendItem.node(), + curveNumber: trace.index, + expandedIndex: trace._expandedIndex, + data: gd.data, + layout: gd.layout, + frames: gd._transitionData._frames, + config: gd._context, + fullData: gd._fullData, + fullLayout: gd._fullLayout + }; + + if(trace._group) { + evtData.group = trace._group; + } + if(Registry.traceIs(trace, 'pie-like')) { + evtData.label = legendItem.datum()[0].label; + } + + var clickVal = Events.triggerHandler(gd, 'plotly_legendclick', evtData); + if(clickVal === false) return; + + if(numClicks === 1) { + legend._clickTimeout = setTimeout(function() { + handleClick(legendItem, gd, numClicks); + }, gd._context.doubleClickDelay); + } else if(numClicks === 2) { + if(legend._clickTimeout) clearTimeout(legend._clickTimeout); + gd._legendMouseDownTime = 0; + + var dblClickVal = Events.triggerHandler(gd, 'plotly_legenddoubleclick', evtData); + if(dblClickVal !== false) handleClick(legendItem, gd, numClicks); + } +} + +function drawTexts(g, gd) { + var legendItem = g.data()[0][0]; + var fullLayout = gd._fullLayout; + var opts = fullLayout.legend; + var trace = legendItem.trace; + var isPieLike = Registry.traceIs(trace, 'pie-like'); + var traceIndex = trace.index; + var isEditable = gd._context.edits.legendText && !isPieLike; + var maxNameLength = opts._maxNameLength; + + var name = isPieLike ? legendItem.label : trace.name; + if(trace._meta) { + name = Lib.templateString(name, trace._meta); + } + + var textEl = Lib.ensureSingle(g, 'text', 'legendtext'); + + textEl.attr('text-anchor', 'start') + .classed('user-select-none', true) + .call(Drawing.font, fullLayout.legend.font) + .text(isEditable ? ensureLength(name, maxNameLength) : name); + + svgTextUtils.positionText(textEl, constants.textGap, 0); + + function textLayout(s) { + svgTextUtils.convertToTspans(s, gd, function() { + computeTextDimensions(g, gd); + }); + } + + if(isEditable) { + textEl.call(svgTextUtils.makeEditable, {gd: gd, text: name}) + .call(textLayout) + .on('edit', function(newName) { + this.text(ensureLength(newName, maxNameLength)) + .call(textLayout); + + var fullInput = legendItem.trace._fullInput || {}; + var update = {}; + + if(Registry.hasTransform(fullInput, 'groupby')) { + var groupbyIndices = Registry.getTransformIndices(fullInput, 'groupby'); + var index = groupbyIndices[groupbyIndices.length - 1]; + + var kcont = Lib.keyedContainer(fullInput, 'transforms[' + index + '].styles', 'target', 'value.name'); + + kcont.set(legendItem.trace._group, newName); + + update = kcont.constructUpdate(); + } else { + update.name = newName; + } + + return Registry.call('_guiRestyle', gd, update, traceIndex); + }); + } else { + textLayout(textEl); + } +} + +/* + * Make sure we have a reasonably clickable region. + * If this string is missing or very short, pad it with spaces out to at least + * 4 characters, up to the max length of other labels, on the assumption that + * most characters are wider than spaces so a string of spaces will usually be + * no wider than the real labels. + */ +function ensureLength(str, maxLength) { + var targetLength = Math.max(4, maxLength); + if(str && str.trim().length >= targetLength / 2) return str; + str = str || ''; + for(var i = targetLength - str.length; i > 0; i--) str += ' '; + return str; +} + +function setupTraceToggle(g, gd) { + var doubleClickDelay = gd._context.doubleClickDelay; + var newMouseDownTime; + var numClicks = 1; + + var traceToggle = Lib.ensureSingle(g, 'rect', 'legendtoggle', function(s) { + s.style('cursor', 'pointer') + .attr('pointer-events', 'all') + .call(Color.fill, 'rgba(0,0,0,0)'); + }); + + traceToggle.on('mousedown', function() { + newMouseDownTime = (new Date()).getTime(); + if(newMouseDownTime - gd._legendMouseDownTime < doubleClickDelay) { + // in a click train + numClicks += 1; + } else { + // new click train + numClicks = 1; + gd._legendMouseDownTime = newMouseDownTime; + } + }); + traceToggle.on('mouseup', function() { + if(gd._dragged || gd._editing) return; + var legend = gd._fullLayout.legend; + + if((new Date()).getTime() - gd._legendMouseDownTime > doubleClickDelay) { + numClicks = Math.max(numClicks - 1, 1); + } + + clickOrDoubleClick(gd, legend, g, numClicks, d3.event); + }); +} + +function computeTextDimensions(g, gd) { + var legendItem = g.data()[0][0]; + + if(!legendItem.trace.showlegend) { + g.remove(); + return; + } + + var mathjaxGroup = g.select('g[class*=math-group]'); + var mathjaxNode = mathjaxGroup.node(); + var opts = gd._fullLayout.legend; + var lineHeight = opts.font.size * LINE_SPACING; + var height, width; + + if(mathjaxNode) { + var mathjaxBB = Drawing.bBox(mathjaxNode); + + height = mathjaxBB.height; + width = mathjaxBB.width; + + Drawing.setTranslate(mathjaxGroup, 0, (height / 4)); + } else { + var text = g.select('.legendtext'); + var textLines = svgTextUtils.lineCount(text); + var textNode = text.node(); + + height = lineHeight * textLines; + width = textNode ? Drawing.bBox(textNode).width : 0; + + // approximation to height offset to center the font + // to avoid getBoundingClientRect + var textY = lineHeight * (0.3 + (1 - textLines) / 2); + svgTextUtils.positionText(text, constants.textGap, textY); + } + + legendItem.lineHeight = lineHeight; + legendItem.height = Math.max(height, 16) + 3; + legendItem.width = width; +} + +/* + * Computes in fullLayout.legend: + * + * - _height: legend height including items past scrollbox height + * - _maxHeight: maximum legend height before scrollbox is required + * - _effHeight: legend height w/ or w/o scrollbox + * + * - _width: legend width + * - _maxWidth (for orientation:h only): maximum width before starting new row + */ +function computeLegendDimensions(gd, groups, traces) { + var fullLayout = gd._fullLayout; + var opts = fullLayout.legend; + var gs = fullLayout._size; + var isVertical = helpers.isVertical(opts); + var isGrouped = helpers.isGrouped(opts); + + var bw = opts.borderwidth; + var bw2 = 2 * bw; + var textGap = constants.textGap; + var itemGap = constants.itemGap; + var endPad = 2 * (bw + itemGap); + + var yanchor = getYanchor(opts); + var isBelowPlotArea = opts.y < 0 || (opts.y === 0 && yanchor === 'top'); + var isAbovePlotArea = opts.y > 1 || (opts.y === 1 && yanchor === 'bottom'); + + // - if below/above plot area, give it the maximum potential margin-push value + // - otherwise, extend the height of the plot area + opts._maxHeight = Math.max( + (isBelowPlotArea || isAbovePlotArea) ? fullLayout.height / 2 : gs.h, + 30 + ); + + var toggleRectWidth = 0; + opts._width = 0; + opts._height = 0; + + if(isVertical) { + traces.each(function(d) { + var h = d[0].height; + Drawing.setTranslate(this, bw, itemGap + bw + opts._height + h / 2); + opts._height += h; + opts._width = Math.max(opts._width, d[0].width); + }); + + toggleRectWidth = textGap + opts._width; + opts._width += itemGap + textGap + bw2; + opts._height += endPad; + + if(isGrouped) { + groups.each(function(d, i) { + Drawing.setTranslate(this, 0, i * opts.tracegroupgap); + }); + opts._height += (opts._lgroupsLength - 1) * opts.tracegroupgap; + } + } else { + var xanchor = getXanchor(opts); + var isLeftOfPlotArea = opts.x < 0 || (opts.x === 0 && xanchor === 'right'); + var isRightOfPlotArea = opts.x > 1 || (opts.x === 1 && xanchor === 'left'); + var isBeyondPlotAreaY = isAbovePlotArea || isBelowPlotArea; + var hw = fullLayout.width / 2; + + // - if placed within x-margins, extend the width of the plot area + // - else if below/above plot area and anchored in the margin, extend to opposite margin, + // - otherwise give it the maximum potential margin-push value + opts._maxWidth = Math.max( + isLeftOfPlotArea ? ((isBeyondPlotAreaY && xanchor === 'left') ? gs.l + gs.w : hw) : + isRightOfPlotArea ? ((isBeyondPlotAreaY && xanchor === 'right') ? gs.r + gs.w : hw) : + gs.w, + 2 * textGap); + var maxItemWidth = 0; + var combinedItemWidth = 0; + traces.each(function(d) { + var w = d[0].width + textGap; + maxItemWidth = Math.max(maxItemWidth, w); + combinedItemWidth += w; + }); + + toggleRectWidth = null; + var maxRowWidth = 0; + + if(isGrouped) { + var maxGroupHeightInRow = 0; + var groupOffsetX = 0; + var groupOffsetY = 0; + groups.each(function() { + var maxWidthInGroup = 0; + var offsetY = 0; + d3.select(this).selectAll('g.traces').each(function(d) { + var h = d[0].height; + Drawing.setTranslate(this, 0, itemGap + bw + h / 2 + offsetY); + offsetY += h; + maxWidthInGroup = Math.max(maxWidthInGroup, textGap + d[0].width); + }); + maxGroupHeightInRow = Math.max(maxGroupHeightInRow, offsetY); + + var next = maxWidthInGroup + itemGap; + + if((next + bw + groupOffsetX) > opts._maxWidth) { + maxRowWidth = Math.max(maxRowWidth, groupOffsetX); + groupOffsetX = 0; + groupOffsetY += maxGroupHeightInRow + opts.tracegroupgap; + maxGroupHeightInRow = offsetY; + } + + Drawing.setTranslate(this, groupOffsetX, groupOffsetY); + + groupOffsetX += next; + }); + + opts._width = Math.max(maxRowWidth, groupOffsetX) + bw; + opts._height = groupOffsetY + maxGroupHeightInRow + endPad; + } else { + var nTraces = traces.size(); + var oneRowLegend = (combinedItemWidth + bw2 + (nTraces - 1) * itemGap) < opts._maxWidth; + + var maxItemHeightInRow = 0; + var offsetX = 0; + var offsetY = 0; + var rowWidth = 0; + traces.each(function(d) { + var h = d[0].height; + var w = textGap + d[0].width; + var next = (oneRowLegend ? w : maxItemWidth) + itemGap; + + if((next + bw + offsetX) > opts._maxWidth) { + maxRowWidth = Math.max(maxRowWidth, rowWidth); + offsetX = 0; + offsetY += maxItemHeightInRow; + opts._height += maxItemHeightInRow; + maxItemHeightInRow = 0; + } + + Drawing.setTranslate(this, bw + offsetX, itemGap + bw + h / 2 + offsetY); + + rowWidth = offsetX + w + itemGap; + offsetX += next; + maxItemHeightInRow = Math.max(maxItemHeightInRow, h); + }); + + if(oneRowLegend) { + opts._width = offsetX + bw2; + opts._height = maxItemHeightInRow + endPad; + } else { + opts._width = Math.max(maxRowWidth, rowWidth) + bw2; + opts._height += maxItemHeightInRow + endPad; + } + } + } + + opts._width = Math.ceil(opts._width); + opts._height = Math.ceil(opts._height); + + opts._effHeight = Math.min(opts._height, opts._maxHeight); + + var edits = gd._context.edits; + var isEditable = edits.legendText || edits.legendPosition; + traces.each(function(d) { + var traceToggle = d3.select(this).select('.legendtoggle'); + var h = d[0].height; + var w = isEditable ? textGap : (toggleRectWidth || (textGap + d[0].width)); + if(!isVertical) w += itemGap / 2; + Drawing.setRect(traceToggle, 0, -h / 2, w, h); + }); +} + +function expandMargin(gd) { + var fullLayout = gd._fullLayout; + var opts = fullLayout.legend; + var xanchor = getXanchor(opts); + var yanchor = getYanchor(opts); + + return Plots.autoMargin(gd, 'legend', { + x: opts.x, + y: opts.y, + l: opts._width * (FROM_TL[xanchor]), + r: opts._width * (FROM_BR[xanchor]), + b: opts._effHeight * (FROM_BR[yanchor]), + t: opts._effHeight * (FROM_TL[yanchor]) + }); +} + +function getXanchor(opts) { + return Lib.isRightAnchor(opts) ? 'right' : + Lib.isCenterAnchor(opts) ? 'center' : + 'left'; +} + +function getYanchor(opts) { + return Lib.isBottomAnchor(opts) ? 'bottom' : + Lib.isMiddleAnchor(opts) ? 'middle' : + 'top'; +} + +},{"../../constants/alignment":145,"../../lib":169,"../../lib/events":163,"../../lib/svg_text_utils":190,"../../plots/plots":245,"../../registry":258,"../color":51,"../dragelement":69,"../drawing":72,"./constants":100,"./get_legend_data":103,"./handle_click":104,"./helpers":105,"./style":107,"d3":16}],103:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Registry = _dereq_('../../registry'); +var helpers = _dereq_('./helpers'); + +module.exports = function getLegendData(calcdata, opts) { + var lgroupToTraces = {}; + var lgroups = []; + var hasOneNonBlankGroup = false; + var slicesShown = {}; + var lgroupi = 0; + var maxNameLength = 0; + var i, j; + + function addOneItem(legendGroup, legendItem) { + // each '' legend group is treated as a separate group + if(legendGroup === '' || !helpers.isGrouped(opts)) { + // TODO: check this against fullData legendgroups? + var uniqueGroup = '~~i' + lgroupi; + lgroups.push(uniqueGroup); + lgroupToTraces[uniqueGroup] = [[legendItem]]; + lgroupi++; + } else if(lgroups.indexOf(legendGroup) === -1) { + lgroups.push(legendGroup); + hasOneNonBlankGroup = true; + lgroupToTraces[legendGroup] = [[legendItem]]; + } else { + lgroupToTraces[legendGroup].push([legendItem]); + } + } + + // build an { legendgroup: [cd0, cd0], ... } object + for(i = 0; i < calcdata.length; i++) { + var cd = calcdata[i]; + var cd0 = cd[0]; + var trace = cd0.trace; + var lgroup = trace.legendgroup; + + if(!trace.visible || !trace.showlegend) continue; + + if(Registry.traceIs(trace, 'pie-like')) { + if(!slicesShown[lgroup]) slicesShown[lgroup] = {}; + + for(j = 0; j < cd.length; j++) { + var labelj = cd[j].label; + + if(!slicesShown[lgroup][labelj]) { + addOneItem(lgroup, { + label: labelj, + color: cd[j].color, + i: cd[j].i, + trace: trace, + pts: cd[j].pts + }); + + slicesShown[lgroup][labelj] = true; + maxNameLength = Math.max(maxNameLength, (labelj || '').length); + } + } + } else { + addOneItem(lgroup, cd0); + maxNameLength = Math.max(maxNameLength, (trace.name || '').length); + } + } + + // won't draw a legend in this case + if(!lgroups.length) return []; + + // rearrange lgroupToTraces into a d3-friendly array of arrays + var lgroupsLength = lgroups.length; + var ltraces; + var legendData; + + if(hasOneNonBlankGroup && helpers.isGrouped(opts)) { + legendData = new Array(lgroupsLength); + + for(i = 0; i < lgroupsLength; i++) { + ltraces = lgroupToTraces[lgroups[i]]; + legendData[i] = helpers.isReversed(opts) ? ltraces.reverse() : ltraces; + } + } else { + // collapse all groups into one if all groups are blank + legendData = [new Array(lgroupsLength)]; + + for(i = 0; i < lgroupsLength; i++) { + ltraces = lgroupToTraces[lgroups[i]][0]; + legendData[0][helpers.isReversed(opts) ? lgroupsLength - i - 1 : i] = ltraces; + } + lgroupsLength = 1; + } + + // number of legend groups - needed in legend/draw.js + opts._lgroupsLength = lgroupsLength; + // maximum name/label length - needed in legend/draw.js + opts._maxNameLength = maxNameLength; + + return legendData; +}; + +},{"../../registry":258,"./helpers":105}],104:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var Registry = _dereq_('../../registry'); + +var SHOWISOLATETIP = true; + +module.exports = function handleClick(g, gd, numClicks) { + var fullLayout = gd._fullLayout; + + if(gd._dragged || gd._editing) return; + + var itemClick = fullLayout.legend.itemclick; + var itemDoubleClick = fullLayout.legend.itemdoubleclick; + + if(numClicks === 1 && itemClick === 'toggle' && itemDoubleClick === 'toggleothers' && + SHOWISOLATETIP && gd.data && gd._context.showTips + ) { + Lib.notifier(Lib._(gd, 'Double-click on legend to isolate one trace'), 'long'); + SHOWISOLATETIP = false; + } else { + SHOWISOLATETIP = false; + } + + var mode; + if(numClicks === 1) mode = itemClick; + else if(numClicks === 2) mode = itemDoubleClick; + if(!mode) return; + + var hiddenSlices = fullLayout.hiddenlabels ? + fullLayout.hiddenlabels.slice() : + []; + + var legendItem = g.data()[0][0]; + var fullData = gd._fullData; + var fullTrace = legendItem.trace; + var legendgroup = fullTrace.legendgroup; + + var i, j, kcont, key, keys, val; + var attrUpdate = {}; + var attrIndices = []; + var carrs = []; + var carrIdx = []; + + function insertUpdate(traceIndex, key, value) { + var attrIndex = attrIndices.indexOf(traceIndex); + var valueArray = attrUpdate[key]; + if(!valueArray) { + valueArray = attrUpdate[key] = []; + } + + if(attrIndices.indexOf(traceIndex) === -1) { + attrIndices.push(traceIndex); + attrIndex = attrIndices.length - 1; + } + + valueArray[attrIndex] = value; + + return attrIndex; + } + + function setVisibility(fullTrace, visibility) { + var fullInput = fullTrace._fullInput; + if(Registry.hasTransform(fullInput, 'groupby')) { + var kcont = carrs[fullInput.index]; + if(!kcont) { + var groupbyIndices = Registry.getTransformIndices(fullInput, 'groupby'); + var lastGroupbyIndex = groupbyIndices[groupbyIndices.length - 1]; + kcont = Lib.keyedContainer(fullInput, 'transforms[' + lastGroupbyIndex + '].styles', 'target', 'value.visible'); + carrs[fullInput.index] = kcont; + } + + var curState = kcont.get(fullTrace._group); + + // If not specified, assume visible. This happens if there are other style + // properties set for a group but not the visibility. There are many similar + // ways to do this (e.g. why not just `curState = fullTrace.visible`??? The + // answer is: because it breaks other things like groupby trace names in + // subtle ways.) + if(curState === undefined) { + curState = true; + } + + if(curState !== false) { + // true -> legendonly. All others toggle to true: + kcont.set(fullTrace._group, visibility); + } + carrIdx[fullInput.index] = insertUpdate(fullInput.index, 'visible', fullInput.visible === false ? false : true); + } else { + // false -> false (not possible since will not be visible in legend) + // true -> legendonly + // legendonly -> true + var nextVisibility = fullInput.visible === false ? false : visibility; + + insertUpdate(fullInput.index, 'visible', nextVisibility); + } + } + + if(Registry.traceIs(fullTrace, 'pie-like')) { + var thisLabel = legendItem.label; + var thisLabelIndex = hiddenSlices.indexOf(thisLabel); + + if(mode === 'toggle') { + if(thisLabelIndex === -1) hiddenSlices.push(thisLabel); + else hiddenSlices.splice(thisLabelIndex, 1); + } else if(mode === 'toggleothers') { + hiddenSlices = []; + gd.calcdata[0].forEach(function(d) { + if(thisLabel !== d.label) { + hiddenSlices.push(d.label); + } + }); + if(gd._fullLayout.hiddenlabels && gd._fullLayout.hiddenlabels.length === hiddenSlices.length && thisLabelIndex === -1) { + hiddenSlices = []; + } + } + + Registry.call('_guiRelayout', gd, 'hiddenlabels', hiddenSlices); + } else { + var hasLegendgroup = legendgroup && legendgroup.length; + var traceIndicesInGroup = []; + var tracei; + if(hasLegendgroup) { + for(i = 0; i < fullData.length; i++) { + tracei = fullData[i]; + if(!tracei.visible) continue; + if(tracei.legendgroup === legendgroup) { + traceIndicesInGroup.push(i); + } + } + } + + if(mode === 'toggle') { + var nextVisibility; + + switch(fullTrace.visible) { + case true: + nextVisibility = 'legendonly'; + break; + case false: + nextVisibility = false; + break; + case 'legendonly': + nextVisibility = true; + break; + } + + if(hasLegendgroup) { + for(i = 0; i < fullData.length; i++) { + if(fullData[i].visible !== false && fullData[i].legendgroup === legendgroup) { + setVisibility(fullData[i], nextVisibility); + } + } + } else { + setVisibility(fullTrace, nextVisibility); + } + } else if(mode === 'toggleothers') { + // Compute the clicked index. expandedIndex does what we want for expanded traces + // but also culls hidden traces. That means we have some work to do. + var isClicked, isInGroup, otherState; + var isIsolated = true; + for(i = 0; i < fullData.length; i++) { + isClicked = fullData[i] === fullTrace; + if(isClicked) continue; + + isInGroup = (hasLegendgroup && fullData[i].legendgroup === legendgroup); + + if(!isInGroup && fullData[i].visible === true && !Registry.traceIs(fullData[i], 'notLegendIsolatable')) { + isIsolated = false; + break; + } + } + + for(i = 0; i < fullData.length; i++) { + // False is sticky; we don't change it. + if(fullData[i].visible === false) continue; + + if(Registry.traceIs(fullData[i], 'notLegendIsolatable')) { + continue; + } + + switch(fullTrace.visible) { + case 'legendonly': + setVisibility(fullData[i], true); + break; + case true: + otherState = isIsolated ? true : 'legendonly'; + isClicked = fullData[i] === fullTrace; + isInGroup = isClicked || (hasLegendgroup && fullData[i].legendgroup === legendgroup); + setVisibility(fullData[i], isInGroup ? true : otherState); + break; + } + } + } + + for(i = 0; i < carrs.length; i++) { + kcont = carrs[i]; + if(!kcont) continue; + var update = kcont.constructUpdate(); + + var updateKeys = Object.keys(update); + for(j = 0; j < updateKeys.length; j++) { + key = updateKeys[j]; + val = attrUpdate[key] = attrUpdate[key] || []; + val[carrIdx[i]] = update[key]; + } + } + + // The length of the value arrays should be equal and any unspecified + // values should be explicitly undefined for them to get properly culled + // as updates and not accidentally reset to the default value. This fills + // out sparse arrays with the required number of undefined values: + keys = Object.keys(attrUpdate); + for(i = 0; i < keys.length; i++) { + key = keys[i]; + for(j = 0; j < attrIndices.length; j++) { + // Use hasOwnPropety to protect against falsey values: + if(!attrUpdate[key].hasOwnProperty(j)) { + attrUpdate[key][j] = undefined; + } + } + } + + Registry.call('_guiRestyle', gd, attrUpdate, attrIndices); + } +}; + +},{"../../lib":169,"../../registry":258}],105:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +exports.isGrouped = function isGrouped(legendLayout) { + return (legendLayout.traceorder || '').indexOf('grouped') !== -1; +}; + +exports.isVertical = function isVertical(legendLayout) { + return legendLayout.orientation !== 'h'; +}; + +exports.isReversed = function isReversed(legendLayout) { + return (legendLayout.traceorder || '').indexOf('reversed') !== -1; +}; + +},{}],106:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + + +module.exports = { + moduleType: 'component', + name: 'legend', + + layoutAttributes: _dereq_('./attributes'), + supplyLayoutDefaults: _dereq_('./defaults'), + + draw: _dereq_('./draw'), + style: _dereq_('./style') +}; + +},{"./attributes":99,"./defaults":101,"./draw":102,"./style":107}],107:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); + +var Registry = _dereq_('../../registry'); +var Lib = _dereq_('../../lib'); +var Drawing = _dereq_('../drawing'); +var Color = _dereq_('../color'); + +var subTypes = _dereq_('../../traces/scatter/subtypes'); +var stylePie = _dereq_('../../traces/pie/style_one'); +var pieCastOption = _dereq_('../../traces/pie/helpers').castOption; + +var CST_MARKER_SIZE = 12; +var CST_LINE_WIDTH = 5; +var CST_MARKER_LINE_WIDTH = 2; +var MAX_LINE_WIDTH = 10; +var MAX_MARKER_LINE_WIDTH = 5; + +module.exports = function style(s, gd) { + var fullLayout = gd._fullLayout; + var legend = fullLayout.legend; + var constantItemSizing = legend.itemsizing === 'constant'; + + function boundLineWidth(mlw, cont, max, cst) { + var v; + if(mlw + 1) { + v = mlw; + } else if(cont && cont.width > 0) { + v = cont.width; + } else { + return 0; + } + return constantItemSizing ? cst : Math.min(v, max); + } + + s.each(function(d) { + var traceGroup = d3.select(this); + + var layers = Lib.ensureSingle(traceGroup, 'g', 'layers'); + layers.style('opacity', d[0].trace.opacity); + + var valign = legend.valign; + var lineHeight = d[0].lineHeight; + var height = d[0].height; + + if(valign === 'middle' || !lineHeight || !height) { + layers.attr('transform', null); + } else { + var factor = {top: 1, bottom: -1}[valign]; + var markerOffsetY = factor * (0.5 * (lineHeight - height + 3)); + layers.attr('transform', 'translate(0,' + markerOffsetY + ')'); + } + + var fill = layers + .selectAll('g.legendfill') + .data([d]); + fill.enter().append('g') + .classed('legendfill', true); + + var line = layers + .selectAll('g.legendlines') + .data([d]); + line.enter().append('g') + .classed('legendlines', true); + + var symbol = layers + .selectAll('g.legendsymbols') + .data([d]); + symbol.enter().append('g') + .classed('legendsymbols', true); + + symbol.selectAll('g.legendpoints') + .data([d]) + .enter().append('g') + .classed('legendpoints', true); + }) + .each(styleWaterfalls) + .each(styleFunnels) + .each(styleBars) + .each(styleBoxes) + .each(styleFunnelareas) + .each(stylePies) + .each(styleLines) + .each(stylePoints) + .each(styleCandles) + .each(styleOHLC); + + function styleLines(d) { + var d0 = d[0]; + var trace = d0.trace; + var showFill = trace.visible && trace.fill && trace.fill !== 'none'; + var showLine = subTypes.hasLines(trace); + var contours = trace.contours; + var showGradientLine = false; + var showGradientFill = false; + var dMod, tMod; + + if(contours) { + var coloring = contours.coloring; + + if(coloring === 'lines') { + showGradientLine = true; + } else { + showLine = coloring === 'none' || coloring === 'heatmap' || contours.showlines; + } + + if(contours.type === 'constraint') { + showFill = contours._operation !== '='; + } else if(coloring === 'fill' || coloring === 'heatmap') { + showGradientFill = true; + } + } + + // with fill and no markers or text, move the line and fill up a bit + // so it's more centered + var markersOrText = subTypes.hasMarkers(trace) || subTypes.hasText(trace); + var anyFill = showFill || showGradientFill; + var anyLine = showLine || showGradientLine; + var pathStart = (markersOrText || !anyFill) ? 'M5,0' : + // with a line leave it slightly below center, to leave room for the + // line thickness and because the line is usually more prominent + anyLine ? 'M5,-2' : 'M5,-3'; + + var this3 = d3.select(this); + + var fill = this3.select('.legendfill').selectAll('path') + .data(showFill || showGradientFill ? [d] : []); + fill.enter().append('path').classed('js-fill', true); + fill.exit().remove(); + fill.attr('d', pathStart + 'h30v6h-30z') + .call(showFill ? Drawing.fillGroupStyle : fillGradient); + + if(showLine || showGradientLine) { + var lw = boundLineWidth(undefined, trace.line, MAX_LINE_WIDTH, CST_LINE_WIDTH); + tMod = Lib.minExtend(trace, {line: {width: lw}}); + dMod = [Lib.minExtend(d0, {trace: tMod})]; + } + + var line = this3.select('.legendlines').selectAll('path') + .data(showLine || showGradientLine ? [dMod] : []); + line.enter().append('path').classed('js-line', true); + line.exit().remove(); + + // this is ugly... but you can't apply a gradient to a perfectly + // horizontal or vertical line. Presumably because then + // the system doesn't know how to scale vertical variation, even + // though there *is* no vertical variation in this case. + // so add an invisibly small angle to the line + // This issue (and workaround) exist across (Mac) Chrome, FF, and Safari + line.attr('d', pathStart + (showGradientLine ? 'l30,0.0001' : 'h30')) + .call(showLine ? Drawing.lineGroupStyle : lineGradient); + + function fillGradient(s) { + if(s.size()) { + var gradientID = 'legendfill-' + trace.uid; + Drawing.gradient(s, gd, gradientID, 'horizontalreversed', + trace.colorscale, 'fill'); + } + } + + function lineGradient(s) { + if(s.size()) { + var gradientID = 'legendline-' + trace.uid; + Drawing.lineGroupStyle(s); + Drawing.gradient(s, gd, gradientID, 'horizontalreversed', + trace.colorscale, 'stroke'); + } + } + } + + function stylePoints(d) { + var d0 = d[0]; + var trace = d0.trace; + var showMarkers = subTypes.hasMarkers(trace); + var showText = subTypes.hasText(trace); + var showLines = subTypes.hasLines(trace); + var dMod, tMod; + + // 'scatter3d' don't use gd.calcdata, + // use d0.trace to infer arrayOk attributes + + function boundVal(attrIn, arrayToValFn, bounds, cst) { + var valIn = Lib.nestedProperty(trace, attrIn).get(); + var valToBound = (Lib.isArrayOrTypedArray(valIn) && arrayToValFn) ? + arrayToValFn(valIn) : + valIn; + + if(constantItemSizing && valToBound && cst !== undefined) { + valToBound = cst; + } + + if(bounds) { + if(valToBound < bounds[0]) return bounds[0]; + else if(valToBound > bounds[1]) return bounds[1]; + } + return valToBound; + } + + function pickFirst(array) { return array[0]; } + + // constrain text, markers, etc so they'll fit on the legend + if(showMarkers || showText || showLines) { + var dEdit = {}; + var tEdit = {}; + + if(showMarkers) { + dEdit.mc = boundVal('marker.color', pickFirst); + dEdit.mx = boundVal('marker.symbol', pickFirst); + dEdit.mo = boundVal('marker.opacity', Lib.mean, [0.2, 1]); + dEdit.mlc = boundVal('marker.line.color', pickFirst); + dEdit.mlw = boundVal('marker.line.width', Lib.mean, [0, 5], CST_MARKER_LINE_WIDTH); + tEdit.marker = { + sizeref: 1, + sizemin: 1, + sizemode: 'diameter' + }; + + var ms = boundVal('marker.size', Lib.mean, [2, 16], CST_MARKER_SIZE); + dEdit.ms = ms; + tEdit.marker.size = ms; + } + + if(showLines) { + tEdit.line = { + width: boundVal('line.width', pickFirst, [0, 10], CST_LINE_WIDTH) + }; + } + + if(showText) { + dEdit.tx = 'Aa'; + dEdit.tp = boundVal('textposition', pickFirst); + dEdit.ts = 10; + dEdit.tc = boundVal('textfont.color', pickFirst); + dEdit.tf = boundVal('textfont.family', pickFirst); + } + + dMod = [Lib.minExtend(d0, dEdit)]; + tMod = Lib.minExtend(trace, tEdit); + + // always show legend items in base state + tMod.selectedpoints = null; + } + + var ptgroup = d3.select(this).select('g.legendpoints'); + + var pts = ptgroup.selectAll('path.scatterpts') + .data(showMarkers ? dMod : []); + // make sure marker is on the bottom, in case it enters after text + pts.enter().insert('path', ':first-child') + .classed('scatterpts', true) + .attr('transform', 'translate(20,0)'); + pts.exit().remove(); + pts.call(Drawing.pointStyle, tMod, gd); + + // 'mrc' is set in pointStyle and used in textPointStyle: + // constrain it here + if(showMarkers) dMod[0].mrc = 3; + + var txt = ptgroup.selectAll('g.pointtext') + .data(showText ? dMod : []); + txt.enter() + .append('g').classed('pointtext', true) + .append('text').attr('transform', 'translate(20,0)'); + txt.exit().remove(); + txt.selectAll('text').call(Drawing.textPointStyle, tMod, gd, true); + } + + function styleWaterfalls(d) { + var trace = d[0].trace; + + var ptsData = []; + if(trace.type === 'waterfall' && trace.visible) { + ptsData = d[0].hasTotals ? + [['increasing', 'M-6,-6V6H0Z'], ['totals', 'M6,6H0L-6,-6H-0Z'], ['decreasing', 'M6,6V-6H0Z']] : + [['increasing', 'M-6,-6V6H6Z'], ['decreasing', 'M6,6V-6H-6Z']]; + } + + var pts = d3.select(this).select('g.legendpoints') + .selectAll('path.legendwaterfall') + .data(ptsData); + pts.enter().append('path').classed('legendwaterfall', true) + .attr('transform', 'translate(20,0)') + .style('stroke-miterlimit', 1); + pts.exit().remove(); + + pts.each(function(dd) { + var pt = d3.select(this); + var cont = trace[dd[0]].marker; + var lw = boundLineWidth(undefined, cont.line, MAX_MARKER_LINE_WIDTH, CST_MARKER_LINE_WIDTH); + + pt.attr('d', dd[1]) + .style('stroke-width', lw + 'px') + .call(Color.fill, cont.color); + + if(lw) { + pt.call(Color.stroke, cont.line.color); + } + }); + } + + function styleBars(d) { + styleBarLike(d, this); + } + + function styleFunnels(d) { + styleBarLike(d, this, 'funnel'); + } + + function styleBarLike(d, lThis, desiredType) { + var trace = d[0].trace; + var marker = trace.marker || {}; + var markerLine = marker.line || {}; + + var isVisible = (!desiredType) ? Registry.traceIs(trace, 'bar') : + (trace.type === desiredType && trace.visible); + + var barpath = d3.select(lThis).select('g.legendpoints') + .selectAll('path.legend' + desiredType) + .data(isVisible ? [d] : []); + barpath.enter().append('path').classed('legend' + desiredType, true) + .attr('d', 'M6,6H-6V-6H6Z') + .attr('transform', 'translate(20,0)'); + barpath.exit().remove(); + + barpath.each(function(d) { + var p = d3.select(this); + var d0 = d[0]; + var w = boundLineWidth(d0.mlw, marker.line, MAX_MARKER_LINE_WIDTH, CST_MARKER_LINE_WIDTH); + + p.style('stroke-width', w + 'px') + .call(Color.fill, d0.mc || marker.color); + + if(w) Color.stroke(p, d0.mlc || markerLine.color); + }); + } + + function styleBoxes(d) { + var trace = d[0].trace; + + var pts = d3.select(this).select('g.legendpoints') + .selectAll('path.legendbox') + .data(Registry.traceIs(trace, 'box-violin') && trace.visible ? [d] : []); + pts.enter().append('path').classed('legendbox', true) + // if we want the median bar, prepend M6,0H-6 + .attr('d', 'M6,6H-6V-6H6Z') + .attr('transform', 'translate(20,0)'); + pts.exit().remove(); + + pts.each(function() { + var p = d3.select(this); + + if((trace.boxpoints === 'all' || trace.points === 'all') && + Color.opacity(trace.fillcolor) === 0 && Color.opacity((trace.line || {}).color) === 0 + ) { + var tMod = Lib.minExtend(trace, { + marker: { + size: constantItemSizing ? CST_MARKER_SIZE : Lib.constrain(trace.marker.size, 2, 16), + sizeref: 1, + sizemin: 1, + sizemode: 'diameter' + } + }); + pts.call(Drawing.pointStyle, tMod, gd); + } else { + var w = boundLineWidth(undefined, trace.line, MAX_MARKER_LINE_WIDTH, CST_MARKER_LINE_WIDTH); + + p.style('stroke-width', w + 'px') + .call(Color.fill, trace.fillcolor); + + if(w) Color.stroke(p, trace.line.color); + } + }); + } + + function styleCandles(d) { + var trace = d[0].trace; + + var pts = d3.select(this).select('g.legendpoints') + .selectAll('path.legendcandle') + .data(trace.type === 'candlestick' && trace.visible ? [d, d] : []); + pts.enter().append('path').classed('legendcandle', true) + .attr('d', function(_, i) { + if(i) return 'M-15,0H-8M-8,6V-6H8Z'; // increasing + return 'M15,0H8M8,-6V6H-8Z'; // decreasing + }) + .attr('transform', 'translate(20,0)') + .style('stroke-miterlimit', 1); + pts.exit().remove(); + + pts.each(function(_, i) { + var p = d3.select(this); + var cont = trace[i ? 'increasing' : 'decreasing']; + var w = boundLineWidth(undefined, cont.line, MAX_MARKER_LINE_WIDTH, CST_MARKER_LINE_WIDTH); + + p.style('stroke-width', w + 'px') + .call(Color.fill, cont.fillcolor); + + if(w) Color.stroke(p, cont.line.color); + }); + } + + function styleOHLC(d) { + var trace = d[0].trace; + + var pts = d3.select(this).select('g.legendpoints') + .selectAll('path.legendohlc') + .data(trace.type === 'ohlc' && trace.visible ? [d, d] : []); + pts.enter().append('path').classed('legendohlc', true) + .attr('d', function(_, i) { + if(i) return 'M-15,0H0M-8,-6V0'; // increasing + return 'M15,0H0M8,6V0'; // decreasing + }) + .attr('transform', 'translate(20,0)') + .style('stroke-miterlimit', 1); + pts.exit().remove(); + + pts.each(function(_, i) { + var p = d3.select(this); + var cont = trace[i ? 'increasing' : 'decreasing']; + var w = boundLineWidth(undefined, cont.line, MAX_MARKER_LINE_WIDTH, CST_MARKER_LINE_WIDTH); + + p.style('fill', 'none') + .call(Drawing.dashLine, cont.line.dash, w); + + if(w) Color.stroke(p, cont.line.color); + }); + } + + function stylePies(d) { + stylePieLike(d, this, 'pie'); + } + + function styleFunnelareas(d) { + stylePieLike(d, this, 'funnelarea'); + } + + function stylePieLike(d, lThis, desiredType) { + var d0 = d[0]; + var trace = d0.trace; + + var isVisible = (!desiredType) ? Registry.traceIs(trace, desiredType) : + (trace.type === desiredType && trace.visible); + + var pts = d3.select(lThis).select('g.legendpoints') + .selectAll('path.legend' + desiredType) + .data(isVisible ? [d] : []); + pts.enter().append('path').classed('legend' + desiredType, true) + .attr('d', 'M6,6H-6V-6H6Z') + .attr('transform', 'translate(20,0)'); + pts.exit().remove(); + + if(pts.size()) { + var cont = (trace.marker || {}).line; + var lw = boundLineWidth(pieCastOption(cont.width, d0.pts), cont, MAX_MARKER_LINE_WIDTH, CST_MARKER_LINE_WIDTH); + + var tMod = Lib.minExtend(trace, {marker: {line: {width: lw}}}); + // since minExtend do not slice more than 3 items we need to patch line.color here + tMod.marker.line.color = cont.color; + + var d0Mod = Lib.minExtend(d0, {trace: tMod}); + + stylePie(pts, d0Mod, tMod); + } + } +}; + +},{"../../lib":169,"../../registry":258,"../../traces/pie/helpers":368,"../../traces/pie/style_one":374,"../../traces/scatter/subtypes":399,"../color":51,"../drawing":72,"d3":16}],108:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Registry = _dereq_('../../registry'); +var Plots = _dereq_('../../plots/plots'); +var axisIds = _dereq_('../../plots/cartesian/axis_ids'); +var Lib = _dereq_('../../lib'); +var Icons = _dereq_('../../fonts/ploticon'); + +var _ = Lib._; + +var modeBarButtons = module.exports = {}; + +/** + * ModeBar buttons configuration + * + * @param {string} name + * name / id of the buttons (for tracking) + * @param {string} title + * text that appears while hovering over the button, + * enter null, false or '' for no hover text + * @param {string} icon + * svg icon object associated with the button + * can be linked to Plotly.Icons to use the default plotly icons + * @param {string} [gravity] + * icon positioning + * @param {function} click + * click handler associated with the button, a function of + * 'gd' (the main graph object) and + * 'ev' (the event object) + * @param {string} [attr] + * attribute associated with button, + * use this with 'val' to keep track of the state + * @param {*} [val] + * initial 'attr' value, can be a function of gd + * @param {boolean} [toggle] + * is the button a toggle button? + */ +modeBarButtons.toImage = { + name: 'toImage', + title: function(gd) { + var opts = gd._context.toImageButtonOptions || {}; + var format = opts.format || 'png'; + return format === 'png' ? + _(gd, 'Download plot as a png') : // legacy text + _(gd, 'Download plot'); // generic non-PNG text + }, + icon: Icons.camera, + click: function(gd) { + var toImageButtonOptions = gd._context.toImageButtonOptions; + var opts = {format: toImageButtonOptions.format || 'png'}; + + Lib.notifier(_(gd, 'Taking snapshot - this may take a few seconds'), 'long'); + + if(opts.format !== 'svg' && Lib.isIE()) { + Lib.notifier(_(gd, 'IE only supports svg. Changing format to svg.'), 'long'); + opts.format = 'svg'; + } + + ['filename', 'width', 'height', 'scale'].forEach(function(key) { + if(key in toImageButtonOptions) { + opts[key] = toImageButtonOptions[key]; + } + }); + + Registry.call('downloadImage', gd, opts) + .then(function(filename) { + Lib.notifier(_(gd, 'Snapshot succeeded') + ' - ' + filename, 'long'); + }) + .catch(function() { + Lib.notifier(_(gd, 'Sorry, there was a problem downloading your snapshot!'), 'long'); + }); + } +}; + +modeBarButtons.sendDataToCloud = { + name: 'sendDataToCloud', + title: function(gd) { return _(gd, 'Edit in Chart Studio'); }, + icon: Icons.disk, + click: function(gd) { + Plots.sendDataToCloud(gd); + } +}; + +modeBarButtons.editInChartStudio = { + name: 'editInChartStudio', + title: function(gd) { return _(gd, 'Edit in Chart Studio'); }, + icon: Icons.pencil, + click: function(gd) { + Plots.sendDataToCloud(gd); + } +}; + +modeBarButtons.zoom2d = { + name: 'zoom2d', + title: function(gd) { return _(gd, 'Zoom'); }, + attr: 'dragmode', + val: 'zoom', + icon: Icons.zoombox, + click: handleCartesian +}; + +modeBarButtons.pan2d = { + name: 'pan2d', + title: function(gd) { return _(gd, 'Pan'); }, + attr: 'dragmode', + val: 'pan', + icon: Icons.pan, + click: handleCartesian +}; + +modeBarButtons.select2d = { + name: 'select2d', + title: function(gd) { return _(gd, 'Box Select'); }, + attr: 'dragmode', + val: 'select', + icon: Icons.selectbox, + click: handleCartesian +}; + +modeBarButtons.lasso2d = { + name: 'lasso2d', + title: function(gd) { return _(gd, 'Lasso Select'); }, + attr: 'dragmode', + val: 'lasso', + icon: Icons.lasso, + click: handleCartesian +}; + +modeBarButtons.zoomIn2d = { + name: 'zoomIn2d', + title: function(gd) { return _(gd, 'Zoom in'); }, + attr: 'zoom', + val: 'in', + icon: Icons.zoom_plus, + click: handleCartesian +}; + +modeBarButtons.zoomOut2d = { + name: 'zoomOut2d', + title: function(gd) { return _(gd, 'Zoom out'); }, + attr: 'zoom', + val: 'out', + icon: Icons.zoom_minus, + click: handleCartesian +}; + +modeBarButtons.autoScale2d = { + name: 'autoScale2d', + title: function(gd) { return _(gd, 'Autoscale'); }, + attr: 'zoom', + val: 'auto', + icon: Icons.autoscale, + click: handleCartesian +}; + +modeBarButtons.resetScale2d = { + name: 'resetScale2d', + title: function(gd) { return _(gd, 'Reset axes'); }, + attr: 'zoom', + val: 'reset', + icon: Icons.home, + click: handleCartesian +}; + +modeBarButtons.hoverClosestCartesian = { + name: 'hoverClosestCartesian', + title: function(gd) { return _(gd, 'Show closest data on hover'); }, + attr: 'hovermode', + val: 'closest', + icon: Icons.tooltip_basic, + gravity: 'ne', + click: handleCartesian +}; + +modeBarButtons.hoverCompareCartesian = { + name: 'hoverCompareCartesian', + title: function(gd) { return _(gd, 'Compare data on hover'); }, + attr: 'hovermode', + val: function(gd) { + return gd._fullLayout._isHoriz ? 'y' : 'x'; + }, + icon: Icons.tooltip_compare, + gravity: 'ne', + click: handleCartesian +}; + +function handleCartesian(gd, ev) { + var button = ev.currentTarget; + var astr = button.getAttribute('data-attr'); + var val = button.getAttribute('data-val') || true; + var fullLayout = gd._fullLayout; + var aobj = {}; + var axList = axisIds.list(gd, null, true); + var allSpikesEnabled = fullLayout._cartesianSpikesEnabled; + + var ax, i; + + if(astr === 'zoom') { + var mag = (val === 'in') ? 0.5 : 2; + var r0 = (1 + mag) / 2; + var r1 = (1 - mag) / 2; + var axName; + + for(i = 0; i < axList.length; i++) { + ax = axList[i]; + + if(!ax.fixedrange) { + axName = ax._name; + if(val === 'auto') { + aobj[axName + '.autorange'] = true; + } else if(val === 'reset') { + if(ax._rangeInitial === undefined) { + aobj[axName + '.autorange'] = true; + } else { + var rangeInitial = ax._rangeInitial.slice(); + aobj[axName + '.range[0]'] = rangeInitial[0]; + aobj[axName + '.range[1]'] = rangeInitial[1]; + } + + // N.B. "reset" also resets showspikes + if(ax._showSpikeInitial !== undefined) { + aobj[axName + '.showspikes'] = ax._showSpikeInitial; + if(allSpikesEnabled === 'on' && !ax._showSpikeInitial) { + allSpikesEnabled = 'off'; + } + } + } else { + var rangeNow = [ + ax.r2l(ax.range[0]), + ax.r2l(ax.range[1]), + ]; + + var rangeNew = [ + r0 * rangeNow[0] + r1 * rangeNow[1], + r0 * rangeNow[1] + r1 * rangeNow[0] + ]; + + aobj[axName + '.range[0]'] = ax.l2r(rangeNew[0]); + aobj[axName + '.range[1]'] = ax.l2r(rangeNew[1]); + } + } + } + } else { + // if ALL traces have orientation 'h', 'hovermode': 'x' otherwise: 'y' + if(astr === 'hovermode' && (val === 'x' || val === 'y')) { + val = fullLayout._isHoriz ? 'y' : 'x'; + button.setAttribute('data-val', val); + } + + aobj[astr] = val; + } + + fullLayout._cartesianSpikesEnabled = allSpikesEnabled; + + Registry.call('_guiRelayout', gd, aobj); +} + +modeBarButtons.zoom3d = { + name: 'zoom3d', + title: function(gd) { return _(gd, 'Zoom'); }, + attr: 'scene.dragmode', + val: 'zoom', + icon: Icons.zoombox, + click: handleDrag3d +}; + +modeBarButtons.pan3d = { + name: 'pan3d', + title: function(gd) { return _(gd, 'Pan'); }, + attr: 'scene.dragmode', + val: 'pan', + icon: Icons.pan, + click: handleDrag3d +}; + +modeBarButtons.orbitRotation = { + name: 'orbitRotation', + title: function(gd) { return _(gd, 'Orbital rotation'); }, + attr: 'scene.dragmode', + val: 'orbit', + icon: Icons['3d_rotate'], + click: handleDrag3d +}; + +modeBarButtons.tableRotation = { + name: 'tableRotation', + title: function(gd) { return _(gd, 'Turntable rotation'); }, + attr: 'scene.dragmode', + val: 'turntable', + icon: Icons['z-axis'], + click: handleDrag3d +}; + +function handleDrag3d(gd, ev) { + var button = ev.currentTarget; + var attr = button.getAttribute('data-attr'); + var val = button.getAttribute('data-val') || true; + var sceneIds = gd._fullLayout._subplots.gl3d; + var layoutUpdate = {}; + + var parts = attr.split('.'); + + for(var i = 0; i < sceneIds.length; i++) { + layoutUpdate[sceneIds[i] + '.' + parts[1]] = val; + } + + // for multi-type subplots + var val2d = (val === 'pan') ? val : 'zoom'; + layoutUpdate.dragmode = val2d; + + Registry.call('_guiRelayout', gd, layoutUpdate); +} + +modeBarButtons.resetCameraDefault3d = { + name: 'resetCameraDefault3d', + title: function(gd) { return _(gd, 'Reset camera to default'); }, + attr: 'resetDefault', + icon: Icons.home, + click: handleCamera3d +}; + +modeBarButtons.resetCameraLastSave3d = { + name: 'resetCameraLastSave3d', + title: function(gd) { return _(gd, 'Reset camera to last save'); }, + attr: 'resetLastSave', + icon: Icons.movie, + click: handleCamera3d +}; + +function handleCamera3d(gd, ev) { + var button = ev.currentTarget; + var attr = button.getAttribute('data-attr'); + var fullLayout = gd._fullLayout; + var sceneIds = fullLayout._subplots.gl3d || []; + var aobj = {}; + + for(var i = 0; i < sceneIds.length; i++) { + var sceneId = sceneIds[i]; + var camera = sceneId + '.camera'; + var aspectratio = sceneId + '.aspectratio'; + var scene = fullLayout[sceneId]._scene; + var didUpdate; + + if(attr === 'resetLastSave') { + aobj[camera + '.up'] = scene.viewInitial.up; + aobj[camera + '.eye'] = scene.viewInitial.eye; + aobj[camera + '.center'] = scene.viewInitial.center; + didUpdate = true; + } else if(attr === 'resetDefault') { + aobj[camera + '.up'] = null; + aobj[camera + '.eye'] = null; + aobj[camera + '.center'] = null; + didUpdate = true; + } + + if(didUpdate) { + aobj[aspectratio + '.x'] = scene.viewInitial.aspectratio.x; + aobj[aspectratio + '.y'] = scene.viewInitial.aspectratio.y; + aobj[aspectratio + '.z'] = scene.viewInitial.aspectratio.z; + } + } + + Registry.call('_guiRelayout', gd, aobj); +} + +modeBarButtons.hoverClosest3d = { + name: 'hoverClosest3d', + title: function(gd) { return _(gd, 'Toggle show closest data on hover'); }, + attr: 'hovermode', + val: null, + toggle: true, + icon: Icons.tooltip_basic, + gravity: 'ne', + click: handleHover3d +}; + +function getNextHover3d(gd, ev) { + var button = ev.currentTarget; + var val = button._previousVal; + var fullLayout = gd._fullLayout; + var sceneIds = fullLayout._subplots.gl3d || []; + + var axes = ['xaxis', 'yaxis', 'zaxis']; + + // initialize 'current spike' object to be stored in the DOM + var currentSpikes = {}; + var layoutUpdate = {}; + + if(val) { + layoutUpdate = val; + button._previousVal = null; + } else { + for(var i = 0; i < sceneIds.length; i++) { + var sceneId = sceneIds[i]; + var sceneLayout = fullLayout[sceneId]; + + var hovermodeAStr = sceneId + '.hovermode'; + currentSpikes[hovermodeAStr] = sceneLayout.hovermode; + layoutUpdate[hovermodeAStr] = false; + + // copy all the current spike attrs + for(var j = 0; j < 3; j++) { + var axis = axes[j]; + var spikeAStr = sceneId + '.' + axis + '.showspikes'; + layoutUpdate[spikeAStr] = false; + currentSpikes[spikeAStr] = sceneLayout[axis].showspikes; + } + } + + button._previousVal = currentSpikes; + } + return layoutUpdate; +} + +function handleHover3d(gd, ev) { + var layoutUpdate = getNextHover3d(gd, ev); + Registry.call('_guiRelayout', gd, layoutUpdate); +} + +modeBarButtons.zoomInGeo = { + name: 'zoomInGeo', + title: function(gd) { return _(gd, 'Zoom in'); }, + attr: 'zoom', + val: 'in', + icon: Icons.zoom_plus, + click: handleGeo +}; + +modeBarButtons.zoomOutGeo = { + name: 'zoomOutGeo', + title: function(gd) { return _(gd, 'Zoom out'); }, + attr: 'zoom', + val: 'out', + icon: Icons.zoom_minus, + click: handleGeo +}; + +modeBarButtons.resetGeo = { + name: 'resetGeo', + title: function(gd) { return _(gd, 'Reset'); }, + attr: 'reset', + val: null, + icon: Icons.autoscale, + click: handleGeo +}; + +modeBarButtons.hoverClosestGeo = { + name: 'hoverClosestGeo', + title: function(gd) { return _(gd, 'Toggle show closest data on hover'); }, + attr: 'hovermode', + val: null, + toggle: true, + icon: Icons.tooltip_basic, + gravity: 'ne', + click: toggleHover +}; + +function handleGeo(gd, ev) { + var button = ev.currentTarget; + var attr = button.getAttribute('data-attr'); + var val = button.getAttribute('data-val') || true; + var fullLayout = gd._fullLayout; + var geoIds = fullLayout._subplots.geo; + + for(var i = 0; i < geoIds.length; i++) { + var id = geoIds[i]; + var geoLayout = fullLayout[id]; + + if(attr === 'zoom') { + var scale = geoLayout.projection.scale; + var newScale = (val === 'in') ? 2 * scale : 0.5 * scale; + + Registry.call('_guiRelayout', gd, id + '.projection.scale', newScale); + } else if(attr === 'reset') { + resetView(gd, 'geo'); + } + } +} + +modeBarButtons.hoverClosestGl2d = { + name: 'hoverClosestGl2d', + title: function(gd) { return _(gd, 'Toggle show closest data on hover'); }, + attr: 'hovermode', + val: null, + toggle: true, + icon: Icons.tooltip_basic, + gravity: 'ne', + click: toggleHover +}; + +modeBarButtons.hoverClosestPie = { + name: 'hoverClosestPie', + title: function(gd) { return _(gd, 'Toggle show closest data on hover'); }, + attr: 'hovermode', + val: 'closest', + icon: Icons.tooltip_basic, + gravity: 'ne', + click: toggleHover +}; + +function getNextHover(gd) { + var fullLayout = gd._fullLayout; + + if(fullLayout.hovermode) return false; + + if(fullLayout._has('cartesian')) { + return fullLayout._isHoriz ? 'y' : 'x'; + } + return 'closest'; +} + +function toggleHover(gd) { + var newHover = getNextHover(gd); + Registry.call('_guiRelayout', gd, 'hovermode', newHover); +} + +modeBarButtons.resetViewSankey = { + name: 'resetSankeyGroup', + title: function(gd) { return _(gd, 'Reset view'); }, + icon: Icons.home, + click: function(gd) { + var aObj = { + 'node.groups': [], + 'node.x': [], + 'node.y': [] + }; + for(var i = 0; i < gd._fullData.length; i++) { + var viewInitial = gd._fullData[i]._viewInitial; + aObj['node.groups'].push(viewInitial.node.groups.slice()); + aObj['node.x'].push(viewInitial.node.x.slice()); + aObj['node.y'].push(viewInitial.node.y.slice()); + } + Registry.call('restyle', gd, aObj); + } +}; + +// buttons when more then one plot types are present + +modeBarButtons.toggleHover = { + name: 'toggleHover', + title: function(gd) { return _(gd, 'Toggle show closest data on hover'); }, + attr: 'hovermode', + val: null, + toggle: true, + icon: Icons.tooltip_basic, + gravity: 'ne', + click: function(gd, ev) { + var layoutUpdate = getNextHover3d(gd, ev); + layoutUpdate.hovermode = getNextHover(gd); + + Registry.call('_guiRelayout', gd, layoutUpdate); + } +}; + +modeBarButtons.resetViews = { + name: 'resetViews', + title: function(gd) { return _(gd, 'Reset views'); }, + icon: Icons.home, + click: function(gd, ev) { + var button = ev.currentTarget; + + button.setAttribute('data-attr', 'zoom'); + button.setAttribute('data-val', 'reset'); + handleCartesian(gd, ev); + + button.setAttribute('data-attr', 'resetLastSave'); + handleCamera3d(gd, ev); + + resetView(gd, 'geo'); + resetView(gd, 'mapbox'); + } +}; + +modeBarButtons.toggleSpikelines = { + name: 'toggleSpikelines', + title: function(gd) { return _(gd, 'Toggle Spike Lines'); }, + icon: Icons.spikeline, + attr: '_cartesianSpikesEnabled', + val: 'on', + click: function(gd) { + var fullLayout = gd._fullLayout; + var allSpikesEnabled = fullLayout._cartesianSpikesEnabled; + + fullLayout._cartesianSpikesEnabled = allSpikesEnabled === 'on' ? 'off' : 'on'; + Registry.call('_guiRelayout', gd, setSpikelineVisibility(gd)); + } +}; + +function setSpikelineVisibility(gd) { + var fullLayout = gd._fullLayout; + var areSpikesOn = fullLayout._cartesianSpikesEnabled === 'on'; + var axList = axisIds.list(gd, null, true); + var aobj = {}; + + for(var i = 0; i < axList.length; i++) { + var ax = axList[i]; + aobj[ax._name + '.showspikes'] = areSpikesOn ? true : ax._showSpikeInitial; + } + + return aobj; +} + +modeBarButtons.resetViewMapbox = { + name: 'resetViewMapbox', + title: function(gd) { return _(gd, 'Reset view'); }, + attr: 'reset', + icon: Icons.home, + click: function(gd) { + resetView(gd, 'mapbox'); + } +}; + +function resetView(gd, subplotType) { + var fullLayout = gd._fullLayout; + var subplotIds = fullLayout._subplots[subplotType] || []; + var aObj = {}; + + for(var i = 0; i < subplotIds.length; i++) { + var id = subplotIds[i]; + var subplotObj = fullLayout[id]._subplot; + var viewInitial = subplotObj.viewInitial; + var viewKeys = Object.keys(viewInitial); + + for(var j = 0; j < viewKeys.length; j++) { + var key = viewKeys[j]; + aObj[id + '.' + key] = viewInitial[key]; + } + } + + Registry.call('_guiRelayout', gd, aObj); +} + +},{"../../fonts/ploticon":153,"../../lib":169,"../../plots/cartesian/axis_ids":216,"../../plots/plots":245,"../../registry":258}],109:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +exports.manage = _dereq_('./manage'); + +},{"./manage":110}],110:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var axisIds = _dereq_('../../plots/cartesian/axis_ids'); +var scatterSubTypes = _dereq_('../../traces/scatter/subtypes'); +var Registry = _dereq_('../../registry'); + +var createModeBar = _dereq_('./modebar'); +var modeBarButtons = _dereq_('./buttons'); + +/** + * ModeBar wrapper around 'create' and 'update', + * chooses buttons to pass to ModeBar constructor based on + * plot type and plot config. + * + * @param {object} gd main plot object + * + */ +module.exports = function manageModeBar(gd) { + var fullLayout = gd._fullLayout; + var context = gd._context; + var modeBar = fullLayout._modeBar; + + if(!context.displayModeBar && !context.watermark) { + if(modeBar) { + modeBar.destroy(); + delete fullLayout._modeBar; + } + return; + } + + if(!Array.isArray(context.modeBarButtonsToRemove)) { + throw new Error([ + '*modeBarButtonsToRemove* configuration options', + 'must be an array.' + ].join(' ')); + } + + if(!Array.isArray(context.modeBarButtonsToAdd)) { + throw new Error([ + '*modeBarButtonsToAdd* configuration options', + 'must be an array.' + ].join(' ')); + } + + var customButtons = context.modeBarButtons; + var buttonGroups; + + if(Array.isArray(customButtons) && customButtons.length) { + buttonGroups = fillCustomButton(customButtons); + } else if(!context.displayModeBar && context.watermark) { + buttonGroups = []; + } else { + buttonGroups = getButtonGroups(gd); + } + + if(modeBar) modeBar.update(gd, buttonGroups); + else fullLayout._modeBar = createModeBar(gd, buttonGroups); +}; + +// logic behind which buttons are displayed by default +function getButtonGroups(gd) { + var fullLayout = gd._fullLayout; + var fullData = gd._fullData; + var context = gd._context; + var buttonsToRemove = context.modeBarButtonsToRemove; + var buttonsToAdd = context.modeBarButtonsToAdd; + + var hasCartesian = fullLayout._has('cartesian'); + var hasGL3D = fullLayout._has('gl3d'); + var hasGeo = fullLayout._has('geo'); + var hasPie = fullLayout._has('pie'); + var hasFunnelarea = fullLayout._has('funnelarea'); + var hasGL2D = fullLayout._has('gl2d'); + var hasTernary = fullLayout._has('ternary'); + var hasMapbox = fullLayout._has('mapbox'); + var hasPolar = fullLayout._has('polar'); + var hasSankey = fullLayout._has('sankey'); + var allAxesFixed = areAllAxesFixed(fullLayout); + + var groups = []; + + function addGroup(newGroup) { + if(!newGroup.length) return; + + var out = []; + + for(var i = 0; i < newGroup.length; i++) { + var button = newGroup[i]; + if(buttonsToRemove.indexOf(button) !== -1) continue; + out.push(modeBarButtons[button]); + } + + groups.push(out); + } + + // buttons common to all plot types + var commonGroup = ['toImage']; + if(context.showEditInChartStudio) commonGroup.push('editInChartStudio'); + else if(context.showSendToCloud) commonGroup.push('sendDataToCloud'); + addGroup(commonGroup); + + var zoomGroup = []; + var hoverGroup = []; + var resetGroup = []; + var dragModeGroup = []; + + if((hasCartesian || hasGL2D || hasPie || hasFunnelarea || hasTernary) + hasGeo + hasGL3D + hasMapbox + hasPolar > 1) { + // graphs with more than one plot types get 'union buttons' + // which reset the view or toggle hover labels across all subplots. + hoverGroup = ['toggleHover']; + resetGroup = ['resetViews']; + } else if(hasGeo) { + zoomGroup = ['zoomInGeo', 'zoomOutGeo']; + hoverGroup = ['hoverClosestGeo']; + resetGroup = ['resetGeo']; + } else if(hasGL3D) { + hoverGroup = ['hoverClosest3d']; + resetGroup = ['resetCameraDefault3d', 'resetCameraLastSave3d']; + } else if(hasMapbox) { + hoverGroup = ['toggleHover']; + resetGroup = ['resetViewMapbox']; + } else if(hasGL2D) { + hoverGroup = ['hoverClosestGl2d']; + } else if(hasPie) { + hoverGroup = ['hoverClosestPie']; + } else if(hasSankey) { + hoverGroup = ['hoverClosestCartesian', 'hoverCompareCartesian']; + resetGroup = ['resetViewSankey']; + } else { // hasPolar, hasTernary + // always show at least one hover icon. + hoverGroup = ['toggleHover']; + } + // if we have cartesian, allow switching between closest and compare + // regardless of what other types are on the plot, since they'll all + // just treat any truthy hovermode as 'closest' + if(hasCartesian) { + hoverGroup = ['toggleSpikelines', 'hoverClosestCartesian', 'hoverCompareCartesian']; + } + if(hasNoHover(fullData)) { + hoverGroup = []; + } + + if((hasCartesian || hasGL2D) && !allAxesFixed) { + zoomGroup = ['zoomIn2d', 'zoomOut2d', 'autoScale2d']; + if(resetGroup[0] !== 'resetViews') resetGroup = ['resetScale2d']; + } + + if(hasGL3D) { + dragModeGroup = ['zoom3d', 'pan3d', 'orbitRotation', 'tableRotation']; + } else if(((hasCartesian || hasGL2D) && !allAxesFixed) || hasTernary) { + dragModeGroup = ['zoom2d', 'pan2d']; + } else if(hasMapbox || hasGeo) { + dragModeGroup = ['pan2d']; + } else if(hasPolar) { + dragModeGroup = ['zoom2d']; + } + if(isSelectable(fullData)) { + dragModeGroup.push('select2d', 'lasso2d'); + } + + addGroup(dragModeGroup); + addGroup(zoomGroup.concat(resetGroup)); + addGroup(hoverGroup); + + return appendButtonsToGroups(groups, buttonsToAdd); +} + +function areAllAxesFixed(fullLayout) { + var axList = axisIds.list({_fullLayout: fullLayout}, null, true); + + for(var i = 0; i < axList.length; i++) { + if(!axList[i].fixedrange) { + return false; + } + } + + return true; +} + +// look for traces that support selection +// to be updated as we add more selectPoints handlers +function isSelectable(fullData) { + var selectable = false; + + for(var i = 0; i < fullData.length; i++) { + if(selectable) break; + + var trace = fullData[i]; + + if(!trace._module || !trace._module.selectPoints) continue; + + if(Registry.traceIs(trace, 'scatter-like')) { + if(scatterSubTypes.hasMarkers(trace) || scatterSubTypes.hasText(trace)) { + selectable = true; + } + } else if(Registry.traceIs(trace, 'box-violin')) { + if(trace.boxpoints === 'all' || trace.points === 'all') { + selectable = true; + } + } else { + // assume that in general if the trace module has selectPoints, + // then it's selectable. Scatter is an exception to this because it must + // have markers or text, not just be a scatter type. + + selectable = true; + } + } + + return selectable; +} + +// check whether all trace are 'noHover' +function hasNoHover(fullData) { + for(var i = 0; i < fullData.length; i++) { + if(!Registry.traceIs(fullData[i], 'noHover')) return false; + } + return true; +} + +function appendButtonsToGroups(groups, buttons) { + if(buttons.length) { + if(Array.isArray(buttons[0])) { + for(var i = 0; i < buttons.length; i++) { + groups.push(buttons[i]); + } + } else groups.push(buttons); + } + + return groups; +} + +// fill in custom buttons referring to default mode bar buttons +function fillCustomButton(customButtons) { + for(var i = 0; i < customButtons.length; i++) { + var buttonGroup = customButtons[i]; + + for(var j = 0; j < buttonGroup.length; j++) { + var button = buttonGroup[j]; + + if(typeof button === 'string') { + if(modeBarButtons[button] !== undefined) { + customButtons[i][j] = modeBarButtons[button]; + } else { + throw new Error([ + '*modeBarButtons* configuration options', + 'invalid button name' + ].join(' ')); + } + } + } + } + + return customButtons; +} + +},{"../../plots/cartesian/axis_ids":216,"../../registry":258,"../../traces/scatter/subtypes":399,"./buttons":108,"./modebar":111}],111:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var d3 = _dereq_('d3'); +var isNumeric = _dereq_('fast-isnumeric'); + +var Lib = _dereq_('../../lib'); +var Icons = _dereq_('../../fonts/ploticon'); +var Parser = new DOMParser(); + +/** + * UI controller for interactive plots + * @Class + * @Param {object} opts + * @Param {object} opts.buttons nested arrays of grouped buttons config objects + * @Param {object} opts.container container div to append modeBar + * @Param {object} opts.graphInfo primary plot object containing data and layout + */ +function ModeBar(opts) { + this.container = opts.container; + this.element = document.createElement('div'); + + this.update(opts.graphInfo, opts.buttons); + + this.container.appendChild(this.element); +} + +var proto = ModeBar.prototype; + +/** + * Update modeBar (buttons and logo) + * + * @param {object} graphInfo primary plot object containing data and layout + * @param {array of arrays} buttons nested arrays of grouped buttons to initialize + * + */ +proto.update = function(graphInfo, buttons) { + this.graphInfo = graphInfo; + + var context = this.graphInfo._context; + var fullLayout = this.graphInfo._fullLayout; + var modeBarId = 'modebar-' + fullLayout._uid; + + this.element.setAttribute('id', modeBarId); + this._uid = modeBarId; + + this.element.className = 'modebar'; + if(context.displayModeBar === 'hover') this.element.className += ' modebar--hover ease-bg'; + + if(fullLayout.modebar.orientation === 'v') { + this.element.className += ' vertical'; + buttons = buttons.reverse(); + } + + var style = fullLayout.modebar; + var bgSelector = context.displayModeBar === 'hover' ? '.js-plotly-plot .plotly:hover ' : ''; + + Lib.deleteRelatedStyleRule(modeBarId); + Lib.addRelatedStyleRule(modeBarId, bgSelector + '#' + modeBarId + ' .modebar-group', 'background-color: ' + style.bgcolor); + Lib.addRelatedStyleRule(modeBarId, '#' + modeBarId + ' .modebar-btn .icon path', 'fill: ' + style.color); + Lib.addRelatedStyleRule(modeBarId, '#' + modeBarId + ' .modebar-btn:hover .icon path', 'fill: ' + style.activecolor); + Lib.addRelatedStyleRule(modeBarId, '#' + modeBarId + ' .modebar-btn.active .icon path', 'fill: ' + style.activecolor); + + // if buttons or logo have changed, redraw modebar interior + var needsNewButtons = !this.hasButtons(buttons); + var needsNewLogo = (this.hasLogo !== context.displaylogo); + var needsNewLocale = (this.locale !== context.locale); + + this.locale = context.locale; + + if(needsNewButtons || needsNewLogo || needsNewLocale) { + this.removeAllButtons(); + + this.updateButtons(buttons); + + if(context.watermark || context.displaylogo) { + var logoGroup = this.getLogo(); + if(context.watermark) { + logoGroup.className = logoGroup.className + ' watermark'; + } + + if(fullLayout.modebar.orientation === 'v') { + this.element.insertBefore(logoGroup, this.element.childNodes[0]); + } else { + this.element.appendChild(logoGroup); + } + + this.hasLogo = true; + } + } + + this.updateActiveButton(); +}; + +proto.updateButtons = function(buttons) { + var _this = this; + + this.buttons = buttons; + this.buttonElements = []; + this.buttonsNames = []; + + this.buttons.forEach(function(buttonGroup) { + var group = _this.createGroup(); + + buttonGroup.forEach(function(buttonConfig) { + var buttonName = buttonConfig.name; + if(!buttonName) { + throw new Error('must provide button \'name\' in button config'); + } + if(_this.buttonsNames.indexOf(buttonName) !== -1) { + throw new Error('button name \'' + buttonName + '\' is taken'); + } + _this.buttonsNames.push(buttonName); + + var button = _this.createButton(buttonConfig); + _this.buttonElements.push(button); + group.appendChild(button); + }); + + _this.element.appendChild(group); + }); +}; + +/** + * Empty div for containing a group of buttons + * @Return {HTMLelement} + */ +proto.createGroup = function() { + var group = document.createElement('div'); + group.className = 'modebar-group'; + return group; +}; + +/** + * Create a new button div and set constant and configurable attributes + * @Param {object} config (see ./buttons.js for more info) + * @Return {HTMLelement} + */ +proto.createButton = function(config) { + var _this = this; + var button = document.createElement('a'); + + button.setAttribute('rel', 'tooltip'); + button.className = 'modebar-btn'; + + var title = config.title; + if(title === undefined) title = config.name; + // for localization: allow title to be a callable that takes gd as arg + else if(typeof title === 'function') title = title(this.graphInfo); + + if(title || title === 0) button.setAttribute('data-title', title); + + if(config.attr !== undefined) button.setAttribute('data-attr', config.attr); + + var val = config.val; + if(val !== undefined) { + if(typeof val === 'function') val = val(this.graphInfo); + button.setAttribute('data-val', val); + } + + var click = config.click; + if(typeof click !== 'function') { + throw new Error('must provide button \'click\' function in button config'); + } else { + button.addEventListener('click', function(ev) { + config.click(_this.graphInfo, ev); + + // only needed for 'hoverClosestGeo' which does not call relayout + _this.updateActiveButton(ev.currentTarget); + }); + } + + button.setAttribute('data-toggle', config.toggle || false); + if(config.toggle) d3.select(button).classed('active', true); + + var icon = config.icon; + if(typeof icon === 'function') { + button.appendChild(icon()); + } else { + button.appendChild(this.createIcon(icon || Icons.question)); + } + button.setAttribute('data-gravity', config.gravity || 'n'); + + return button; +}; + +/** + * Add an icon to a button + * @Param {object} thisIcon + * @Param {number} thisIcon.width + * @Param {string} thisIcon.path + * @Param {string} thisIcon.color + * @Return {HTMLelement} + */ +proto.createIcon = function(thisIcon) { + var iconHeight = isNumeric(thisIcon.height) ? + Number(thisIcon.height) : + thisIcon.ascent - thisIcon.descent; + var svgNS = 'http://www.w3.org/2000/svg'; + var icon; + + if(thisIcon.path) { + icon = document.createElementNS(svgNS, 'svg'); + icon.setAttribute('viewBox', [0, 0, thisIcon.width, iconHeight].join(' ')); + icon.setAttribute('class', 'icon'); + + var path = document.createElementNS(svgNS, 'path'); + path.setAttribute('d', thisIcon.path); + + if(thisIcon.transform) { + path.setAttribute('transform', thisIcon.transform); + } else if(thisIcon.ascent !== undefined) { + // Legacy icon transform calculation + path.setAttribute('transform', 'matrix(1 0 0 -1 0 ' + thisIcon.ascent + ')'); + } + + icon.appendChild(path); + } + + if(thisIcon.svg) { + var svgDoc = Parser.parseFromString(thisIcon.svg, 'application/xml'); + icon = svgDoc.childNodes[0]; + } + + icon.setAttribute('height', '1em'); + icon.setAttribute('width', '1em'); + + return icon; +}; + +/** + * Updates active button with attribute specified in layout + * @Param {object} graphInfo plot object containing data and layout + * @Return {HTMLelement} + */ +proto.updateActiveButton = function(buttonClicked) { + var fullLayout = this.graphInfo._fullLayout; + var dataAttrClicked = (buttonClicked !== undefined) ? + buttonClicked.getAttribute('data-attr') : + null; + + this.buttonElements.forEach(function(button) { + var thisval = button.getAttribute('data-val') || true; + var dataAttr = button.getAttribute('data-attr'); + var isToggleButton = (button.getAttribute('data-toggle') === 'true'); + var button3 = d3.select(button); + + // Use 'data-toggle' and 'buttonClicked' to toggle buttons + // that have no one-to-one equivalent in fullLayout + if(isToggleButton) { + if(dataAttr === dataAttrClicked) { + button3.classed('active', !button3.classed('active')); + } + } else { + var val = (dataAttr === null) ? + dataAttr : + Lib.nestedProperty(fullLayout, dataAttr).get(); + + button3.classed('active', val === thisval); + } + }); +}; + +/** + * Check if modeBar is configured as button configuration argument + * + * @Param {object} buttons 2d array of grouped button config objects + * @Return {boolean} + */ +proto.hasButtons = function(buttons) { + var currentButtons = this.buttons; + + if(!currentButtons) return false; + + if(buttons.length !== currentButtons.length) return false; + + for(var i = 0; i < buttons.length; ++i) { + if(buttons[i].length !== currentButtons[i].length) return false; + for(var j = 0; j < buttons[i].length; j++) { + if(buttons[i][j].name !== currentButtons[i][j].name) return false; + } + } + + return true; +}; + +/** + * @return {HTMLDivElement} The logo image wrapped in a group + */ +proto.getLogo = function() { + var group = this.createGroup(); + var a = document.createElement('a'); + + a.href = 'https://plot.ly/'; + a.target = '_blank'; + a.setAttribute('data-title', Lib._(this.graphInfo, 'Produced with Plotly')); + a.className = 'modebar-btn plotlyjsicon modebar-btn--logo'; + + a.appendChild(this.createIcon(Icons.newplotlylogo)); + + group.appendChild(a); + return group; +}; + +proto.removeAllButtons = function() { + while(this.element.firstChild) { + this.element.removeChild(this.element.firstChild); + } + + this.hasLogo = false; +}; + +proto.destroy = function() { + Lib.removeElement(this.container.querySelector('.modebar')); + Lib.deleteRelatedStyleRule(this._uid); +}; + +function createModeBar(gd, buttons) { + var fullLayout = gd._fullLayout; + + var modeBar = new ModeBar({ + graphInfo: gd, + container: fullLayout._modebardiv.node(), + buttons: buttons + }); + + if(fullLayout._privateplot) { + d3.select(modeBar.element).append('span') + .classed('badge-private float--left', true) + .text('PRIVATE'); + } + + return modeBar; +} + +module.exports = createModeBar; + +},{"../../fonts/ploticon":153,"../../lib":169,"d3":16,"fast-isnumeric":18}],112:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var fontAttrs = _dereq_('../../plots/font_attributes'); +var colorAttrs = _dereq_('../color/attributes'); +var templatedArray = _dereq_('../../plot_api/plot_template').templatedArray; + +var buttonAttrs = templatedArray('button', { + visible: { + valType: 'boolean', + + dflt: true, + editType: 'plot', + + }, + step: { + valType: 'enumerated', + + values: ['month', 'year', 'day', 'hour', 'minute', 'second', 'all'], + dflt: 'month', + editType: 'plot', + + }, + stepmode: { + valType: 'enumerated', + + values: ['backward', 'todate'], + dflt: 'backward', + editType: 'plot', + + }, + count: { + valType: 'number', + + min: 0, + dflt: 1, + editType: 'plot', + + }, + label: { + valType: 'string', + + editType: 'plot', + + }, + editType: 'plot', + +}); + +module.exports = { + visible: { + valType: 'boolean', + + editType: 'plot', + + }, + + buttons: buttonAttrs, + + x: { + valType: 'number', + min: -2, + max: 3, + + editType: 'plot', + + }, + xanchor: { + valType: 'enumerated', + values: ['auto', 'left', 'center', 'right'], + dflt: 'left', + + editType: 'plot', + + }, + y: { + valType: 'number', + min: -2, + max: 3, + + editType: 'plot', + + }, + yanchor: { + valType: 'enumerated', + values: ['auto', 'top', 'middle', 'bottom'], + dflt: 'bottom', + + editType: 'plot', + + }, + + font: fontAttrs({ + editType: 'plot', + + }), + + bgcolor: { + valType: 'color', + dflt: colorAttrs.lightLine, + + editType: 'plot', + + }, + activecolor: { + valType: 'color', + + editType: 'plot', + + }, + bordercolor: { + valType: 'color', + dflt: colorAttrs.defaultLine, + + editType: 'plot', + + }, + borderwidth: { + valType: 'number', + min: 0, + dflt: 0, + + editType: 'plot', + + }, + editType: 'plot' +}; + +},{"../../plot_api/plot_template":203,"../../plots/font_attributes":239,"../color/attributes":50}],113:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + + +module.exports = { + + // 'y' position pad above counter axis domain + yPad: 0.02, + + // minimum button width (regardless of text size) + minButtonWidth: 30, + + // buttons rect radii + rx: 3, + ry: 3, + + // light fraction used to compute the 'activecolor' default + lightAmount: 25, + darkAmount: 10 +}; + +},{}],114:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var Color = _dereq_('../color'); +var Template = _dereq_('../../plot_api/plot_template'); +var handleArrayContainerDefaults = _dereq_('../../plots/array_container_defaults'); + +var attributes = _dereq_('./attributes'); +var constants = _dereq_('./constants'); + + +module.exports = function handleDefaults(containerIn, containerOut, layout, counterAxes, calendar) { + var selectorIn = containerIn.rangeselector || {}; + var selectorOut = Template.newContainer(containerOut, 'rangeselector'); + + function coerce(attr, dflt) { + return Lib.coerce(selectorIn, selectorOut, attributes, attr, dflt); + } + + var buttons = handleArrayContainerDefaults(selectorIn, selectorOut, { + name: 'buttons', + handleItemDefaults: buttonDefaults, + calendar: calendar + }); + + var visible = coerce('visible', buttons.length > 0); + if(visible) { + var posDflt = getPosDflt(containerOut, layout, counterAxes); + coerce('x', posDflt[0]); + coerce('y', posDflt[1]); + Lib.noneOrAll(containerIn, containerOut, ['x', 'y']); + + coerce('xanchor'); + coerce('yanchor'); + + Lib.coerceFont(coerce, 'font', layout.font); + + var bgColor = coerce('bgcolor'); + coerce('activecolor', Color.contrast(bgColor, constants.lightAmount, constants.darkAmount)); + coerce('bordercolor'); + coerce('borderwidth'); + } +}; + +function buttonDefaults(buttonIn, buttonOut, selectorOut, opts) { + var calendar = opts.calendar; + + function coerce(attr, dflt) { + return Lib.coerce(buttonIn, buttonOut, attributes.buttons, attr, dflt); + } + + var visible = coerce('visible'); + + if(visible) { + var step = coerce('step'); + if(step !== 'all') { + if(calendar && calendar !== 'gregorian' && (step === 'month' || step === 'year')) { + buttonOut.stepmode = 'backward'; + } else { + coerce('stepmode'); + } + + coerce('count'); + } + + coerce('label'); + } +} + +function getPosDflt(containerOut, layout, counterAxes) { + var anchoredList = counterAxes.filter(function(ax) { + return layout[ax].anchor === containerOut._id; + }); + + var posY = 0; + for(var i = 0; i < anchoredList.length; i++) { + var domain = layout[anchoredList[i]].domain; + if(domain) posY = Math.max(domain[1], posY); + } + + return [containerOut.domain[0], posY + constants.yPad]; +} + +},{"../../lib":169,"../../plot_api/plot_template":203,"../../plots/array_container_defaults":209,"../color":51,"./attributes":112,"./constants":113}],115:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); + +var Registry = _dereq_('../../registry'); +var Plots = _dereq_('../../plots/plots'); +var Color = _dereq_('../color'); +var Drawing = _dereq_('../drawing'); +var Lib = _dereq_('../../lib'); +var svgTextUtils = _dereq_('../../lib/svg_text_utils'); +var axisIds = _dereq_('../../plots/cartesian/axis_ids'); + +var alignmentConstants = _dereq_('../../constants/alignment'); +var LINE_SPACING = alignmentConstants.LINE_SPACING; +var FROM_TL = alignmentConstants.FROM_TL; +var FROM_BR = alignmentConstants.FROM_BR; + +var constants = _dereq_('./constants'); +var getUpdateObject = _dereq_('./get_update_object'); + +module.exports = function draw(gd) { + var fullLayout = gd._fullLayout; + + var selectors = fullLayout._infolayer.selectAll('.rangeselector') + .data(makeSelectorData(gd), selectorKeyFunc); + + selectors.enter().append('g') + .classed('rangeselector', true); + + selectors.exit().remove(); + + selectors.style({ + cursor: 'pointer', + 'pointer-events': 'all' + }); + + selectors.each(function(d) { + var selector = d3.select(this); + var axisLayout = d; + var selectorLayout = axisLayout.rangeselector; + + var buttons = selector.selectAll('g.button') + .data(Lib.filterVisible(selectorLayout.buttons)); + + buttons.enter().append('g') + .classed('button', true); + + buttons.exit().remove(); + + buttons.each(function(d) { + var button = d3.select(this); + var update = getUpdateObject(axisLayout, d); + + d._isActive = isActive(axisLayout, d, update); + + button.call(drawButtonRect, selectorLayout, d); + button.call(drawButtonText, selectorLayout, d, gd); + + button.on('click', function() { + if(gd._dragged) return; + + Registry.call('_guiRelayout', gd, update); + }); + + button.on('mouseover', function() { + d._isHovered = true; + button.call(drawButtonRect, selectorLayout, d); + }); + + button.on('mouseout', function() { + d._isHovered = false; + button.call(drawButtonRect, selectorLayout, d); + }); + }); + + reposition(gd, buttons, selectorLayout, axisLayout._name, selector); + }); +}; + +function makeSelectorData(gd) { + var axes = axisIds.list(gd, 'x', true); + var data = []; + + for(var i = 0; i < axes.length; i++) { + var axis = axes[i]; + + if(axis.rangeselector && axis.rangeselector.visible) { + data.push(axis); + } + } + + return data; +} + +function selectorKeyFunc(d) { + return d._id; +} + +function isActive(axisLayout, opts, update) { + if(opts.step === 'all') { + return axisLayout.autorange === true; + } else { + var keys = Object.keys(update); + + return ( + axisLayout.range[0] === update[keys[0]] && + axisLayout.range[1] === update[keys[1]] + ); + } +} + +function drawButtonRect(button, selectorLayout, d) { + var rect = Lib.ensureSingle(button, 'rect', 'selector-rect', function(s) { + s.attr('shape-rendering', 'crispEdges'); + }); + + rect.attr({ + 'rx': constants.rx, + 'ry': constants.ry + }); + + rect.call(Color.stroke, selectorLayout.bordercolor) + .call(Color.fill, getFillColor(selectorLayout, d)) + .style('stroke-width', selectorLayout.borderwidth + 'px'); +} + +function getFillColor(selectorLayout, d) { + return (d._isActive || d._isHovered) ? + selectorLayout.activecolor : + selectorLayout.bgcolor; +} + +function drawButtonText(button, selectorLayout, d, gd) { + function textLayout(s) { + svgTextUtils.convertToTspans(s, gd); + } + + var text = Lib.ensureSingle(button, 'text', 'selector-text', function(s) { + s.classed('user-select-none', true) + .attr('text-anchor', 'middle'); + }); + + text.call(Drawing.font, selectorLayout.font) + .text(getLabel(d, gd._fullLayout._meta)) + .call(textLayout); +} + +function getLabel(opts, _meta) { + if(opts.label) { + return _meta ? + Lib.templateString(opts.label, _meta) : + opts.label; + } + + if(opts.step === 'all') return 'all'; + + return opts.count + opts.step.charAt(0); +} + +function reposition(gd, buttons, opts, axName, selector) { + var width = 0; + var height = 0; + + var borderWidth = opts.borderwidth; + + buttons.each(function() { + var button = d3.select(this); + var text = button.select('.selector-text'); + + var tHeight = opts.font.size * LINE_SPACING; + var hEff = Math.max(tHeight * svgTextUtils.lineCount(text), 16) + 3; + + height = Math.max(height, hEff); + }); + + buttons.each(function() { + var button = d3.select(this); + var rect = button.select('.selector-rect'); + var text = button.select('.selector-text'); + + var tWidth = text.node() && Drawing.bBox(text.node()).width; + var tHeight = opts.font.size * LINE_SPACING; + var tLines = svgTextUtils.lineCount(text); + + var wEff = Math.max(tWidth + 10, constants.minButtonWidth); + + // TODO add MathJax support + + // TODO add buttongap attribute + + button.attr('transform', 'translate(' + + (borderWidth + width) + ',' + borderWidth + + ')'); + + rect.attr({ + x: 0, + y: 0, + width: wEff, + height: height + }); + + svgTextUtils.positionText(text, wEff / 2, + height / 2 - ((tLines - 1) * tHeight / 2) + 3); + + width += wEff + 5; + }); + + var graphSize = gd._fullLayout._size; + var lx = graphSize.l + graphSize.w * opts.x; + var ly = graphSize.t + graphSize.h * (1 - opts.y); + + var xanchor = 'left'; + if(Lib.isRightAnchor(opts)) { + lx -= width; + xanchor = 'right'; + } + if(Lib.isCenterAnchor(opts)) { + lx -= width / 2; + xanchor = 'center'; + } + + var yanchor = 'top'; + if(Lib.isBottomAnchor(opts)) { + ly -= height; + yanchor = 'bottom'; + } + if(Lib.isMiddleAnchor(opts)) { + ly -= height / 2; + yanchor = 'middle'; + } + + width = Math.ceil(width); + height = Math.ceil(height); + lx = Math.round(lx); + ly = Math.round(ly); + + Plots.autoMargin(gd, axName + '-range-selector', { + x: opts.x, + y: opts.y, + l: width * FROM_TL[xanchor], + r: width * FROM_BR[xanchor], + b: height * FROM_BR[yanchor], + t: height * FROM_TL[yanchor] + }); + + selector.attr('transform', 'translate(' + lx + ',' + ly + ')'); +} + +},{"../../constants/alignment":145,"../../lib":169,"../../lib/svg_text_utils":190,"../../plots/cartesian/axis_ids":216,"../../plots/plots":245,"../../registry":258,"../color":51,"../drawing":72,"./constants":113,"./get_update_object":116,"d3":16}],116:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var d3 = _dereq_('d3'); + +module.exports = function getUpdateObject(axisLayout, buttonLayout) { + var axName = axisLayout._name; + var update = {}; + + if(buttonLayout.step === 'all') { + update[axName + '.autorange'] = true; + } else { + var xrange = getXRange(axisLayout, buttonLayout); + + update[axName + '.range[0]'] = xrange[0]; + update[axName + '.range[1]'] = xrange[1]; + } + + return update; +}; + +function getXRange(axisLayout, buttonLayout) { + var currentRange = axisLayout.range; + var base = new Date(axisLayout.r2l(currentRange[1])); + var step = buttonLayout.step; + var count = buttonLayout.count; + var range0; + + switch(buttonLayout.stepmode) { + case 'backward': + range0 = axisLayout.l2r(+d3.time[step].utc.offset(base, -count)); + break; + + case 'todate': + var base2 = d3.time[step].utc.offset(base, -count); + + range0 = axisLayout.l2r(+d3.time[step].utc.ceil(base2)); + break; + } + + var range1 = currentRange[1]; + + return [range0, range1]; +} + +},{"d3":16}],117:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + moduleType: 'component', + name: 'rangeselector', + + schema: { + subplots: { + xaxis: {rangeselector: _dereq_('./attributes')} + } + }, + + layoutAttributes: _dereq_('./attributes'), + handleDefaults: _dereq_('./defaults'), + + draw: _dereq_('./draw') +}; + +},{"./attributes":112,"./defaults":114,"./draw":115}],118:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var colorAttributes = _dereq_('../color/attributes'); + +module.exports = { + bgcolor: { + valType: 'color', + dflt: colorAttributes.background, + + editType: 'plot', + + }, + bordercolor: { + valType: 'color', + dflt: colorAttributes.defaultLine, + + editType: 'plot', + + }, + borderwidth: { + valType: 'integer', + dflt: 0, + min: 0, + + editType: 'plot', + + }, + autorange: { + valType: 'boolean', + dflt: true, + + editType: 'calc', + impliedEdits: {'range[0]': undefined, 'range[1]': undefined}, + + }, + range: { + valType: 'info_array', + + items: [ + {valType: 'any', editType: 'calc', impliedEdits: {'^autorange': false}}, + {valType: 'any', editType: 'calc', impliedEdits: {'^autorange': false}} + ], + editType: 'calc', + impliedEdits: {'autorange': false}, + + }, + thickness: { + valType: 'number', + dflt: 0.15, + min: 0, + max: 1, + + editType: 'plot', + + }, + visible: { + valType: 'boolean', + dflt: true, + + editType: 'calc', + + }, + editType: 'calc' +}; + +},{"../color/attributes":50}],119:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var listAxes = _dereq_('../../plots/cartesian/axis_ids').list; +var getAutoRange = _dereq_('../../plots/cartesian/autorange').getAutoRange; +var constants = _dereq_('./constants'); + +module.exports = function calcAutorange(gd) { + var axes = listAxes(gd, 'x', true); + + // Compute new slider range using axis autorange if necessary. + // + // Copy back range to input range slider container to skip + // this step in subsequent draw calls. + + for(var i = 0; i < axes.length; i++) { + var ax = axes[i]; + var opts = ax[constants.name]; + + if(opts && opts.visible && opts.autorange) { + opts._input.autorange = true; + opts._input.range = opts.range = getAutoRange(gd, ax); + } + } +}; + +},{"../../plots/cartesian/autorange":212,"../../plots/cartesian/axis_ids":216,"./constants":120}],120:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + + // attribute container name + name: 'rangeslider', + + // class names + + containerClassName: 'rangeslider-container', + bgClassName: 'rangeslider-bg', + rangePlotClassName: 'rangeslider-rangeplot', + + maskMinClassName: 'rangeslider-mask-min', + maskMaxClassName: 'rangeslider-mask-max', + slideBoxClassName: 'rangeslider-slidebox', + + grabberMinClassName: 'rangeslider-grabber-min', + grabAreaMinClassName: 'rangeslider-grabarea-min', + handleMinClassName: 'rangeslider-handle-min', + + grabberMaxClassName: 'rangeslider-grabber-max', + grabAreaMaxClassName: 'rangeslider-grabarea-max', + handleMaxClassName: 'rangeslider-handle-max', + + maskMinOppAxisClassName: 'rangeslider-mask-min-opp-axis', + maskMaxOppAxisClassName: 'rangeslider-mask-max-opp-axis', + + // style constants + + maskColor: 'rgba(0,0,0,0.4)', + maskOppAxisColor: 'rgba(0,0,0,0.2)', + + slideBoxFill: 'transparent', + slideBoxCursor: 'ew-resize', + + grabAreaFill: 'transparent', + grabAreaCursor: 'col-resize', + grabAreaWidth: 10, + + handleWidth: 4, + handleRadius: 1, + handleStrokeWidth: 1, + + extraPad: 15 +}; + +},{}],121:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var Template = _dereq_('../../plot_api/plot_template'); +var axisIds = _dereq_('../../plots/cartesian/axis_ids'); + +var attributes = _dereq_('./attributes'); +var oppAxisAttrs = _dereq_('./oppaxis_attributes'); + +module.exports = function handleDefaults(layoutIn, layoutOut, axName) { + var axIn = layoutIn[axName]; + var axOut = layoutOut[axName]; + + if(!(axIn.rangeslider || layoutOut._requestRangeslider[axOut._id])) return; + + // not super proud of this (maybe store _ in axis object instead + if(!Lib.isPlainObject(axIn.rangeslider)) { + axIn.rangeslider = {}; + } + + var containerIn = axIn.rangeslider; + var containerOut = Template.newContainer(axOut, 'rangeslider'); + + function coerce(attr, dflt) { + return Lib.coerce(containerIn, containerOut, attributes, attr, dflt); + } + + var rangeContainerIn, rangeContainerOut; + function coerceRange(attr, dflt) { + return Lib.coerce(rangeContainerIn, rangeContainerOut, oppAxisAttrs, attr, dflt); + } + + var visible = coerce('visible'); + if(!visible) return; + + coerce('bgcolor', layoutOut.plot_bgcolor); + coerce('bordercolor'); + coerce('borderwidth'); + coerce('thickness'); + + coerce('autorange', !axOut.isValidRange(containerIn.range)); + coerce('range'); + + var subplots = layoutOut._subplots; + if(subplots) { + var yIds = subplots.cartesian + .filter(function(subplotId) { + return subplotId.substr(0, subplotId.indexOf('y')) === axisIds.name2id(axName); + }) + .map(function(subplotId) { + return subplotId.substr(subplotId.indexOf('y'), subplotId.length); + }); + var yNames = Lib.simpleMap(yIds, axisIds.id2name); + for(var i = 0; i < yNames.length; i++) { + var yName = yNames[i]; + + rangeContainerIn = containerIn[yName] || {}; + rangeContainerOut = Template.newContainer(containerOut, yName, 'yaxis'); + + var yAxOut = layoutOut[yName]; + + var rangemodeDflt; + if(rangeContainerIn.range && yAxOut.isValidRange(rangeContainerIn.range)) { + rangemodeDflt = 'fixed'; + } + + var rangeMode = coerceRange('rangemode', rangemodeDflt); + if(rangeMode !== 'match') { + coerceRange('range', yAxOut.range.slice()); + } + } + } + + // to map back range slider (auto) range + containerOut._input = containerIn; +}; + +},{"../../lib":169,"../../plot_api/plot_template":203,"../../plots/cartesian/axis_ids":216,"./attributes":118,"./oppaxis_attributes":125}],122:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); + +var Registry = _dereq_('../../registry'); +var Plots = _dereq_('../../plots/plots'); + +var Lib = _dereq_('../../lib'); +var Drawing = _dereq_('../drawing'); +var Color = _dereq_('../color'); +var Titles = _dereq_('../titles'); + +var Cartesian = _dereq_('../../plots/cartesian'); +var axisIDs = _dereq_('../../plots/cartesian/axis_ids'); + +var dragElement = _dereq_('../dragelement'); +var setCursor = _dereq_('../../lib/setcursor'); + +var constants = _dereq_('./constants'); + +module.exports = function(gd) { + var fullLayout = gd._fullLayout; + var rangeSliderData = fullLayout._rangeSliderData; + for(var i = 0; i < rangeSliderData.length; i++) { + var opts = rangeSliderData[i][constants.name]; + // fullLayout._uid may not exist when we call makeData + opts._clipId = opts._id + '-' + fullLayout._uid; + } + + /* + * + * + * < .... range plot /> + * + * + * + * + * + * + * + * + * + * + * ... + */ + + function keyFunction(axisOpts) { + return axisOpts._name; + } + + var rangeSliders = fullLayout._infolayer + .selectAll('g.' + constants.containerClassName) + .data(rangeSliderData, keyFunction); + + // remove exiting sliders and their corresponding clip paths + rangeSliders.exit().each(function(axisOpts) { + var opts = axisOpts[constants.name]; + fullLayout._topdefs.select('#' + opts._clipId).remove(); + }).remove(); + + // return early if no range slider is visible + if(rangeSliderData.length === 0) return; + + rangeSliders.enter().append('g') + .classed(constants.containerClassName, true) + .attr('pointer-events', 'all'); + + // for all present range sliders + rangeSliders.each(function(axisOpts) { + var rangeSlider = d3.select(this); + var opts = axisOpts[constants.name]; + var oppAxisOpts = fullLayout[axisIDs.id2name(axisOpts.anchor)]; + var oppAxisRangeOpts = opts[axisIDs.id2name(axisOpts.anchor)]; + + // update range + // Expand slider range to the axis range + if(opts.range) { + var rng = Lib.simpleMap(opts.range, axisOpts.r2l); + var axRng = Lib.simpleMap(axisOpts.range, axisOpts.r2l); + var newRng; + + if(axRng[0] < axRng[1]) { + newRng = [ + Math.min(rng[0], axRng[0]), + Math.max(rng[1], axRng[1]) + ]; + } else { + newRng = [ + Math.max(rng[0], axRng[0]), + Math.min(rng[1], axRng[1]) + ]; + } + + opts.range = opts._input.range = Lib.simpleMap(newRng, axisOpts.l2r); + } + + axisOpts.cleanRange('rangeslider.range'); + + // update range slider dimensions + + var gs = fullLayout._size; + var domain = axisOpts.domain; + + opts._width = gs.w * (domain[1] - domain[0]); + + var x = Math.round(gs.l + (gs.w * domain[0])); + + var y = Math.round( + gs.t + gs.h * (1 - axisOpts._counterDomainMin) + + (axisOpts.side === 'bottom' ? axisOpts._depth : 0) + + opts._offsetShift + constants.extraPad + ); + + rangeSlider.attr('transform', 'translate(' + x + ',' + y + ')'); + + // update data <--> pixel coordinate conversion methods + + var range0 = axisOpts.r2l(opts.range[0]); + var range1 = axisOpts.r2l(opts.range[1]); + var dist = range1 - range0; + + opts.p2d = function(v) { + return (v / opts._width) * dist + range0; + }; + + opts.d2p = function(v) { + return (v - range0) / dist * opts._width; + }; + + opts._rl = [range0, range1]; + + if(oppAxisRangeOpts.rangemode !== 'match') { + var range0OppAxis = oppAxisOpts.r2l(oppAxisRangeOpts.range[0]); + var range1OppAxis = oppAxisOpts.r2l(oppAxisRangeOpts.range[1]); + var distOppAxis = range1OppAxis - range0OppAxis; + + opts.d2pOppAxis = function(v) { + return (v - range0OppAxis) / distOppAxis * opts._height; + }; + } + + // update inner nodes + + rangeSlider + .call(drawBg, gd, axisOpts, opts) + .call(addClipPath, gd, axisOpts, opts) + .call(drawRangePlot, gd, axisOpts, opts) + .call(drawMasks, gd, axisOpts, opts, oppAxisRangeOpts) + .call(drawSlideBox, gd, axisOpts, opts) + .call(drawGrabbers, gd, axisOpts, opts); + + // setup drag element + setupDragElement(rangeSlider, gd, axisOpts, opts); + + // update current range + setPixelRange(rangeSlider, gd, axisOpts, opts, oppAxisOpts, oppAxisRangeOpts); + + // title goes next to range slider instead of tick labels, so + // just take it over and draw it from here + if(axisOpts.side === 'bottom') { + Titles.draw(gd, axisOpts._id + 'title', { + propContainer: axisOpts, + propName: axisOpts._name + '.title', + placeholder: fullLayout._dfltTitle.x, + attributes: { + x: axisOpts._offset + axisOpts._length / 2, + y: y + opts._height + opts._offsetShift + 10 + 1.5 * axisOpts.title.font.size, + 'text-anchor': 'middle' + } + }); + } + }); +}; + +function setupDragElement(rangeSlider, gd, axisOpts, opts) { + var slideBox = rangeSlider.select('rect.' + constants.slideBoxClassName).node(); + var grabAreaMin = rangeSlider.select('rect.' + constants.grabAreaMinClassName).node(); + var grabAreaMax = rangeSlider.select('rect.' + constants.grabAreaMaxClassName).node(); + + rangeSlider.on('mousedown', function() { + var event = d3.event; + var target = event.target; + var startX = event.clientX; + var offsetX = startX - rangeSlider.node().getBoundingClientRect().left; + var minVal = opts.d2p(axisOpts._rl[0]); + var maxVal = opts.d2p(axisOpts._rl[1]); + + var dragCover = dragElement.coverSlip(); + + dragCover.addEventListener('mousemove', mouseMove); + dragCover.addEventListener('mouseup', mouseUp); + + function mouseMove(e) { + var delta = +e.clientX - startX; + var pixelMin, pixelMax, cursor; + + switch(target) { + case slideBox: + cursor = 'ew-resize'; + pixelMin = minVal + delta; + pixelMax = maxVal + delta; + break; + + case grabAreaMin: + cursor = 'col-resize'; + pixelMin = minVal + delta; + pixelMax = maxVal; + break; + + case grabAreaMax: + cursor = 'col-resize'; + pixelMin = minVal; + pixelMax = maxVal + delta; + break; + + default: + cursor = 'ew-resize'; + pixelMin = offsetX; + pixelMax = offsetX + delta; + break; + } + + if(pixelMax < pixelMin) { + var tmp = pixelMax; + pixelMax = pixelMin; + pixelMin = tmp; + } + + opts._pixelMin = pixelMin; + opts._pixelMax = pixelMax; + + setCursor(d3.select(dragCover), cursor); + setDataRange(rangeSlider, gd, axisOpts, opts); + } + + function mouseUp() { + dragCover.removeEventListener('mousemove', mouseMove); + dragCover.removeEventListener('mouseup', mouseUp); + Lib.removeElement(dragCover); + } + }); +} + +function setDataRange(rangeSlider, gd, axisOpts, opts) { + function clamp(v) { + return axisOpts.l2r(Lib.constrain(v, opts._rl[0], opts._rl[1])); + } + + var dataMin = clamp(opts.p2d(opts._pixelMin)); + var dataMax = clamp(opts.p2d(opts._pixelMax)); + + window.requestAnimationFrame(function() { + Registry.call('_guiRelayout', gd, axisOpts._name + '.range', [dataMin, dataMax]); + }); +} + +function setPixelRange(rangeSlider, gd, axisOpts, opts, oppAxisOpts, oppAxisRangeOpts) { + var hw2 = constants.handleWidth / 2; + + function clamp(v) { + return Lib.constrain(v, 0, opts._width); + } + + function clampOppAxis(v) { + return Lib.constrain(v, 0, opts._height); + } + + function clampHandle(v) { + return Lib.constrain(v, -hw2, opts._width + hw2); + } + + var pixelMin = clamp(opts.d2p(axisOpts._rl[0])); + var pixelMax = clamp(opts.d2p(axisOpts._rl[1])); + + rangeSlider.select('rect.' + constants.slideBoxClassName) + .attr('x', pixelMin) + .attr('width', pixelMax - pixelMin); + + rangeSlider.select('rect.' + constants.maskMinClassName) + .attr('width', pixelMin); + + rangeSlider.select('rect.' + constants.maskMaxClassName) + .attr('x', pixelMax) + .attr('width', opts._width - pixelMax); + + if(oppAxisRangeOpts.rangemode !== 'match') { + var pixelMinOppAxis = opts._height - clampOppAxis(opts.d2pOppAxis(oppAxisOpts._rl[1])); + var pixelMaxOppAxis = opts._height - clampOppAxis(opts.d2pOppAxis(oppAxisOpts._rl[0])); + + rangeSlider.select('rect.' + constants.maskMinOppAxisClassName) + .attr('x', pixelMin) + .attr('height', pixelMinOppAxis) + .attr('width', pixelMax - pixelMin); + + rangeSlider.select('rect.' + constants.maskMaxOppAxisClassName) + .attr('x', pixelMin) + .attr('y', pixelMaxOppAxis) + .attr('height', opts._height - pixelMaxOppAxis) + .attr('width', pixelMax - pixelMin); + + rangeSlider.select('rect.' + constants.slideBoxClassName) + .attr('y', pixelMinOppAxis) + .attr('height', pixelMaxOppAxis - pixelMinOppAxis); + } + + // add offset for crispier corners + // https://github.com/plotly/plotly.js/pull/1409 + var offset = 0.5; + + var xMin = Math.round(clampHandle(pixelMin - hw2)) - offset; + var xMax = Math.round(clampHandle(pixelMax - hw2)) + offset; + + rangeSlider.select('g.' + constants.grabberMinClassName) + .attr('transform', 'translate(' + xMin + ',' + offset + ')'); + + rangeSlider.select('g.' + constants.grabberMaxClassName) + .attr('transform', 'translate(' + xMax + ',' + offset + ')'); +} + +function drawBg(rangeSlider, gd, axisOpts, opts) { + var bg = Lib.ensureSingle(rangeSlider, 'rect', constants.bgClassName, function(s) { + s.attr({ + x: 0, + y: 0, + 'shape-rendering': 'crispEdges' + }); + }); + + var borderCorrect = (opts.borderwidth % 2) === 0 ? + opts.borderwidth : + opts.borderwidth - 1; + + var offsetShift = -opts._offsetShift; + var lw = Drawing.crispRound(gd, opts.borderwidth); + + bg.attr({ + width: opts._width + borderCorrect, + height: opts._height + borderCorrect, + transform: 'translate(' + offsetShift + ',' + offsetShift + ')', + fill: opts.bgcolor, + stroke: opts.bordercolor, + 'stroke-width': lw + }); +} + +function addClipPath(rangeSlider, gd, axisOpts, opts) { + var fullLayout = gd._fullLayout; + + var clipPath = Lib.ensureSingleById(fullLayout._topdefs, 'clipPath', opts._clipId, function(s) { + s.append('rect').attr({ x: 0, y: 0 }); + }); + + clipPath.select('rect').attr({ + width: opts._width, + height: opts._height + }); +} + +function drawRangePlot(rangeSlider, gd, axisOpts, opts) { + var calcData = gd.calcdata; + + var rangePlots = rangeSlider.selectAll('g.' + constants.rangePlotClassName) + .data(axisOpts._subplotsWith, Lib.identity); + + rangePlots.enter().append('g') + .attr('class', function(id) { return constants.rangePlotClassName + ' ' + id; }) + .call(Drawing.setClipUrl, opts._clipId, gd); + + rangePlots.order(); + + rangePlots.exit().remove(); + + var mainplotinfo; + + rangePlots.each(function(id, i) { + var plotgroup = d3.select(this); + var isMainPlot = (i === 0); + + var oppAxisOpts = axisIDs.getFromId(gd, id, 'y'); + var oppAxisName = oppAxisOpts._name; + var oppAxisRangeOpts = opts[oppAxisName]; + + var mockFigure = { + data: [], + layout: { + xaxis: { + type: axisOpts.type, + domain: [0, 1], + range: opts.range.slice(), + calendar: axisOpts.calendar + }, + width: opts._width, + height: opts._height, + margin: { t: 0, b: 0, l: 0, r: 0 } + }, + _context: gd._context + }; + + mockFigure.layout[oppAxisName] = { + type: oppAxisOpts.type, + domain: [0, 1], + range: oppAxisRangeOpts.rangemode !== 'match' ? oppAxisRangeOpts.range.slice() : oppAxisOpts.range.slice(), + calendar: oppAxisOpts.calendar + }; + + Plots.supplyDefaults(mockFigure); + + var xa = mockFigure._fullLayout.xaxis; + var ya = mockFigure._fullLayout[oppAxisName]; + + xa.clearCalc(); + xa.setScale(); + ya.clearCalc(); + ya.setScale(); + + var plotinfo = { + id: id, + plotgroup: plotgroup, + xaxis: xa, + yaxis: ya, + isRangePlot: true + }; + + if(isMainPlot) mainplotinfo = plotinfo; + else { + plotinfo.mainplot = 'xy'; + plotinfo.mainplotinfo = mainplotinfo; + } + + Cartesian.rangePlot(gd, plotinfo, filterRangePlotCalcData(calcData, id)); + }); +} + +function filterRangePlotCalcData(calcData, subplotId) { + var out = []; + + for(var i = 0; i < calcData.length; i++) { + var calcTrace = calcData[i]; + var trace = calcTrace[0].trace; + + if(trace.xaxis + trace.yaxis === subplotId) { + out.push(calcTrace); + } + } + + return out; +} + +function drawMasks(rangeSlider, gd, axisOpts, opts, oppAxisRangeOpts) { + var maskMin = Lib.ensureSingle(rangeSlider, 'rect', constants.maskMinClassName, function(s) { + s.attr({ + x: 0, + y: 0, + 'shape-rendering': 'crispEdges' + }); + }); + + maskMin + .attr('height', opts._height) + .call(Color.fill, constants.maskColor); + + var maskMax = Lib.ensureSingle(rangeSlider, 'rect', constants.maskMaxClassName, function(s) { + s.attr({ + y: 0, + 'shape-rendering': 'crispEdges' + }); + }); + + maskMax + .attr('height', opts._height) + .call(Color.fill, constants.maskColor); + + // masks used for oppAxis zoom + if(oppAxisRangeOpts.rangemode !== 'match') { + var maskMinOppAxis = Lib.ensureSingle(rangeSlider, 'rect', constants.maskMinOppAxisClassName, function(s) { + s.attr({ + y: 0, + 'shape-rendering': 'crispEdges' + }); + }); + + maskMinOppAxis + .attr('width', opts._width) + .call(Color.fill, constants.maskOppAxisColor); + + var maskMaxOppAxis = Lib.ensureSingle(rangeSlider, 'rect', constants.maskMaxOppAxisClassName, function(s) { + s.attr({ + y: 0, + 'shape-rendering': 'crispEdges' + }); + }); + + maskMaxOppAxis + .attr('width', opts._width) + .style('border-top', constants.maskOppBorder) + .call(Color.fill, constants.maskOppAxisColor); + } +} + +function drawSlideBox(rangeSlider, gd, axisOpts, opts) { + if(gd._context.staticPlot) return; + + var slideBox = Lib.ensureSingle(rangeSlider, 'rect', constants.slideBoxClassName, function(s) { + s.attr({ + y: 0, + cursor: constants.slideBoxCursor, + 'shape-rendering': 'crispEdges' + }); + }); + + slideBox.attr({ + height: opts._height, + fill: constants.slideBoxFill + }); +} + +function drawGrabbers(rangeSlider, gd, axisOpts, opts) { + // + var grabberMin = Lib.ensureSingle(rangeSlider, 'g', constants.grabberMinClassName); + var grabberMax = Lib.ensureSingle(rangeSlider, 'g', constants.grabberMaxClassName); + + // + var handleFixAttrs = { + x: 0, + width: constants.handleWidth, + rx: constants.handleRadius, + fill: Color.background, + stroke: Color.defaultLine, + 'stroke-width': constants.handleStrokeWidth, + 'shape-rendering': 'crispEdges' + }; + var handleDynamicAttrs = { + y: Math.round(opts._height / 4), + height: Math.round(opts._height / 2), + }; + var handleMin = Lib.ensureSingle(grabberMin, 'rect', constants.handleMinClassName, function(s) { + s.attr(handleFixAttrs); + }); + handleMin.attr(handleDynamicAttrs); + + var handleMax = Lib.ensureSingle(grabberMax, 'rect', constants.handleMaxClassName, function(s) { + s.attr(handleFixAttrs); + }); + handleMax.attr(handleDynamicAttrs); + + // + if(gd._context.staticPlot) return; + + var grabAreaFixAttrs = { + width: constants.grabAreaWidth, + x: 0, + y: 0, + fill: constants.grabAreaFill, + cursor: constants.grabAreaCursor + }; + + var grabAreaMin = Lib.ensureSingle(grabberMin, 'rect', constants.grabAreaMinClassName, function(s) { + s.attr(grabAreaFixAttrs); + }); + grabAreaMin.attr('height', opts._height); + + var grabAreaMax = Lib.ensureSingle(grabberMax, 'rect', constants.grabAreaMaxClassName, function(s) { + s.attr(grabAreaFixAttrs); + }); + grabAreaMax.attr('height', opts._height); +} + +},{"../../lib":169,"../../lib/setcursor":188,"../../plots/cartesian":224,"../../plots/cartesian/axis_ids":216,"../../plots/plots":245,"../../registry":258,"../color":51,"../dragelement":69,"../drawing":72,"../titles":138,"./constants":120,"d3":16}],123:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var axisIDs = _dereq_('../../plots/cartesian/axis_ids'); +var svgTextUtils = _dereq_('../../lib/svg_text_utils'); +var constants = _dereq_('./constants'); +var LINE_SPACING = _dereq_('../../constants/alignment').LINE_SPACING; +var name = constants.name; + +function isVisible(ax) { + var rangeSlider = ax && ax[name]; + return rangeSlider && rangeSlider.visible; +} +exports.isVisible = isVisible; + +exports.makeData = function(fullLayout) { + var axes = axisIDs.list({ _fullLayout: fullLayout }, 'x', true); + var margin = fullLayout.margin; + var rangeSliderData = []; + + if(!fullLayout._has('gl2d')) { + for(var i = 0; i < axes.length; i++) { + var ax = axes[i]; + + if(isVisible(ax)) { + rangeSliderData.push(ax); + + var opts = ax[name]; + opts._id = name + ax._id; + opts._height = (fullLayout.height - margin.b - margin.t) * opts.thickness; + opts._offsetShift = Math.floor(opts.borderwidth / 2); + } + } + } + + fullLayout._rangeSliderData = rangeSliderData; +}; + +exports.autoMarginOpts = function(gd, ax) { + var fullLayout = gd._fullLayout; + var opts = ax[name]; + var axLetter = ax._id.charAt(0); + + var bottomDepth = 0; + var titleHeight = 0; + if(ax.side === 'bottom') { + bottomDepth = ax._depth; + if(ax.title.text !== fullLayout._dfltTitle[axLetter]) { + // as in rangeslider/draw.js + titleHeight = 1.5 * ax.title.font.size + 10 + opts._offsetShift; + // multi-line extra bump + var extraLines = (ax.title.text.match(svgTextUtils.BR_TAG_ALL) || []).length; + titleHeight += extraLines * ax.title.font.size * LINE_SPACING; + } + } + + return { + x: 0, + y: ax._counterDomainMin, + l: 0, + r: 0, + t: 0, + b: opts._height + bottomDepth + Math.max(fullLayout.margin.b, titleHeight), + pad: constants.extraPad + opts._offsetShift * 2 + }; +}; + +},{"../../constants/alignment":145,"../../lib/svg_text_utils":190,"../../plots/cartesian/axis_ids":216,"./constants":120}],124:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var attrs = _dereq_('./attributes'); +var oppAxisAttrs = _dereq_('./oppaxis_attributes'); +var helpers = _dereq_('./helpers'); + +module.exports = { + moduleType: 'component', + name: 'rangeslider', + + schema: { + subplots: { + xaxis: { + rangeslider: Lib.extendFlat({}, attrs, { + yaxis: oppAxisAttrs + }) + } + } + }, + + layoutAttributes: _dereq_('./attributes'), + handleDefaults: _dereq_('./defaults'), + calcAutorange: _dereq_('./calc_autorange'), + draw: _dereq_('./draw'), + isVisible: helpers.isVisible, + makeData: helpers.makeData, + autoMarginOpts: helpers.autoMarginOpts +}; + +},{"../../lib":169,"./attributes":118,"./calc_autorange":119,"./defaults":121,"./draw":122,"./helpers":123,"./oppaxis_attributes":125}],125:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + // not really a 'subplot' attribute container, + // but this is the flag we use to denote attributes that + // support yaxis, yaxis2, yaxis3, ... counters + _isSubplotObj: true, + + rangemode: { + valType: 'enumerated', + values: ['auto', 'fixed', 'match'], + dflt: 'match', + + editType: 'calc', + + }, + range: { + valType: 'info_array', + + items: [ + {valType: 'any', editType: 'plot'}, + {valType: 'any', editType: 'plot'} + ], + editType: 'plot', + + }, + editType: 'calc' +}; + +},{}],126:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var annAttrs = _dereq_('../annotations/attributes'); +var scatterLineAttrs = _dereq_('../../traces/scatter/attributes').line; +var dash = _dereq_('../drawing/attributes').dash; +var extendFlat = _dereq_('../../lib/extend').extendFlat; +var templatedArray = _dereq_('../../plot_api/plot_template').templatedArray; + +module.exports = templatedArray('shape', { + visible: { + valType: 'boolean', + + dflt: true, + editType: 'calc+arraydraw', + + }, + + type: { + valType: 'enumerated', + values: ['circle', 'rect', 'path', 'line'], + + editType: 'calc+arraydraw', + + }, + + layer: { + valType: 'enumerated', + values: ['below', 'above'], + dflt: 'above', + + editType: 'arraydraw', + + }, + + xref: extendFlat({}, annAttrs.xref, { + + }), + xsizemode: { + valType: 'enumerated', + values: ['scaled', 'pixel'], + dflt: 'scaled', + + editType: 'calc+arraydraw', + + }, + xanchor: { + valType: 'any', + + editType: 'calc+arraydraw', + + }, + x0: { + valType: 'any', + + editType: 'calc+arraydraw', + + }, + x1: { + valType: 'any', + + editType: 'calc+arraydraw', + + }, + + yref: extendFlat({}, annAttrs.yref, { + + }), + ysizemode: { + valType: 'enumerated', + values: ['scaled', 'pixel'], + dflt: 'scaled', + + editType: 'calc+arraydraw', + + }, + yanchor: { + valType: 'any', + + editType: 'calc+arraydraw', + + }, + y0: { + valType: 'any', + + editType: 'calc+arraydraw', + + }, + y1: { + valType: 'any', + + editType: 'calc+arraydraw', + + }, + + path: { + valType: 'string', + + editType: 'calc+arraydraw', + + }, + + opacity: { + valType: 'number', + min: 0, + max: 1, + dflt: 1, + + editType: 'arraydraw', + + }, + line: { + color: extendFlat({}, scatterLineAttrs.color, {editType: 'arraydraw'}), + width: extendFlat({}, scatterLineAttrs.width, {editType: 'calc+arraydraw'}), + dash: extendFlat({}, dash, {editType: 'arraydraw'}), + + editType: 'calc+arraydraw' + }, + fillcolor: { + valType: 'color', + dflt: 'rgba(0,0,0,0)', + + editType: 'arraydraw', + + }, + editType: 'arraydraw' +}); + +},{"../../lib/extend":164,"../../plot_api/plot_template":203,"../../traces/scatter/attributes":376,"../annotations/attributes":36,"../drawing/attributes":71}],127:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var Axes = _dereq_('../../plots/cartesian/axes'); + +var constants = _dereq_('./constants'); +var helpers = _dereq_('./helpers'); + + +module.exports = function calcAutorange(gd) { + var fullLayout = gd._fullLayout; + var shapeList = Lib.filterVisible(fullLayout.shapes); + + if(!shapeList.length || !gd._fullData.length) return; + + for(var i = 0; i < shapeList.length; i++) { + var shape = shapeList[i]; + shape._extremes = {}; + + var ax, bounds; + + if(shape.xref !== 'paper') { + var vx0 = shape.xsizemode === 'pixel' ? shape.xanchor : shape.x0; + var vx1 = shape.xsizemode === 'pixel' ? shape.xanchor : shape.x1; + ax = Axes.getFromId(gd, shape.xref); + + bounds = shapeBounds(ax, vx0, vx1, shape.path, constants.paramIsX); + if(bounds) { + shape._extremes[ax._id] = Axes.findExtremes(ax, bounds, calcXPaddingOptions(shape)); + } + } + + if(shape.yref !== 'paper') { + var vy0 = shape.ysizemode === 'pixel' ? shape.yanchor : shape.y0; + var vy1 = shape.ysizemode === 'pixel' ? shape.yanchor : shape.y1; + ax = Axes.getFromId(gd, shape.yref); + + bounds = shapeBounds(ax, vy0, vy1, shape.path, constants.paramIsY); + if(bounds) { + shape._extremes[ax._id] = Axes.findExtremes(ax, bounds, calcYPaddingOptions(shape)); + } + } + } +}; + +function calcXPaddingOptions(shape) { + return calcPaddingOptions(shape.line.width, shape.xsizemode, shape.x0, shape.x1, shape.path, false); +} + +function calcYPaddingOptions(shape) { + return calcPaddingOptions(shape.line.width, shape.ysizemode, shape.y0, shape.y1, shape.path, true); +} + +function calcPaddingOptions(lineWidth, sizeMode, v0, v1, path, isYAxis) { + var ppad = lineWidth / 2; + var axisDirectionReverted = isYAxis; + + if(sizeMode === 'pixel') { + var coords = path ? + helpers.extractPathCoords(path, isYAxis ? constants.paramIsY : constants.paramIsX) : + [v0, v1]; + var maxValue = Lib.aggNums(Math.max, null, coords); + var minValue = Lib.aggNums(Math.min, null, coords); + var beforePad = minValue < 0 ? Math.abs(minValue) + ppad : ppad; + var afterPad = maxValue > 0 ? maxValue + ppad : ppad; + + return { + ppad: ppad, + ppadplus: axisDirectionReverted ? beforePad : afterPad, + ppadminus: axisDirectionReverted ? afterPad : beforePad + }; + } else { + return {ppad: ppad}; + } +} + +function shapeBounds(ax, v0, v1, path, paramsToUse) { + var convertVal = (ax.type === 'category' || ax.type === 'multicategory') ? ax.r2c : ax.d2c; + + if(v0 !== undefined) return [convertVal(v0), convertVal(v1)]; + if(!path) return; + + var min = Infinity; + var max = -Infinity; + var segments = path.match(constants.segmentRE); + var i; + var segment; + var drawnParam; + var params; + var val; + + if(ax.type === 'date') convertVal = helpers.decodeDate(convertVal); + + for(i = 0; i < segments.length; i++) { + segment = segments[i]; + drawnParam = paramsToUse[segment.charAt(0)].drawn; + if(drawnParam === undefined) continue; + + params = segments[i].substr(1).match(constants.paramRE); + if(!params || params.length < drawnParam) continue; + + val = convertVal(params[drawnParam]); + if(val < min) min = val; + if(val > max) max = val; + } + if(max >= min) return [min, max]; +} + +},{"../../lib":169,"../../plots/cartesian/axes":213,"./constants":128,"./helpers":131}],128:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + + +module.exports = { + segmentRE: /[MLHVQCTSZ][^MLHVQCTSZ]*/g, + paramRE: /[^\s,]+/g, + + // which numbers in each path segment are x (or y) values + // drawn is which param is a drawn point, as opposed to a + // control point (which doesn't count toward autorange. + // TODO: this means curved paths could extend beyond the + // autorange bounds. This is a bit tricky to get right + // unless we revert to bounding boxes, but perhaps there's + // a calculation we could do...) + paramIsX: { + M: {0: true, drawn: 0}, + L: {0: true, drawn: 0}, + H: {0: true, drawn: 0}, + V: {}, + Q: {0: true, 2: true, drawn: 2}, + C: {0: true, 2: true, 4: true, drawn: 4}, + T: {0: true, drawn: 0}, + S: {0: true, 2: true, drawn: 2}, + // A: {0: true, 5: true}, + Z: {} + }, + + paramIsY: { + M: {1: true, drawn: 1}, + L: {1: true, drawn: 1}, + H: {}, + V: {0: true, drawn: 0}, + Q: {1: true, 3: true, drawn: 3}, + C: {1: true, 3: true, 5: true, drawn: 5}, + T: {1: true, drawn: 1}, + S: {1: true, 3: true, drawn: 5}, + // A: {1: true, 6: true}, + Z: {} + }, + + numParams: { + M: 2, + L: 2, + H: 1, + V: 1, + Q: 4, + C: 6, + T: 2, + S: 4, + // A: 7, + Z: 0 + } +}; + +},{}],129:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Lib = _dereq_('../../lib'); +var Axes = _dereq_('../../plots/cartesian/axes'); +var handleArrayContainerDefaults = _dereq_('../../plots/array_container_defaults'); + +var attributes = _dereq_('./attributes'); +var helpers = _dereq_('./helpers'); + + +module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) { + handleArrayContainerDefaults(layoutIn, layoutOut, { + name: 'shapes', + handleItemDefaults: handleShapeDefaults + }); +}; + +function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { + function coerce(attr, dflt) { + return Lib.coerce(shapeIn, shapeOut, attributes, attr, dflt); + } + + var visible = coerce('visible'); + + if(!visible) return; + + coerce('layer'); + coerce('opacity'); + coerce('fillcolor'); + coerce('line.color'); + coerce('line.width'); + coerce('line.dash'); + + var dfltType = shapeIn.path ? 'path' : 'rect'; + var shapeType = coerce('type', dfltType); + var xSizeMode = coerce('xsizemode'); + var ySizeMode = coerce('ysizemode'); + + // positioning + var axLetters = ['x', 'y']; + for(var i = 0; i < 2; i++) { + var axLetter = axLetters[i]; + var attrAnchor = axLetter + 'anchor'; + var sizeMode = axLetter === 'x' ? xSizeMode : ySizeMode; + var gdMock = {_fullLayout: fullLayout}; + var ax; + var pos2r; + var r2pos; + + // xref, yref + var axRef = Axes.coerceRef(shapeIn, shapeOut, gdMock, axLetter, '', 'paper'); + + if(axRef !== 'paper') { + ax = Axes.getFromId(gdMock, axRef); + ax._shapeIndices.push(shapeOut._index); + r2pos = helpers.rangeToShapePosition(ax); + pos2r = helpers.shapePositionToRange(ax); + } else { + pos2r = r2pos = Lib.identity; + } + + // Coerce x0, x1, y0, y1 + if(shapeType !== 'path') { + var dflt0 = 0.25; + var dflt1 = 0.75; + + // hack until V2.0 when log has regular range behavior - make it look like other + // ranges to send to coerce, then put it back after + // this is all to give reasonable default position behavior on log axes, which is + // a pretty unimportant edge case so we could just ignore this. + var attr0 = axLetter + '0'; + var attr1 = axLetter + '1'; + var in0 = shapeIn[attr0]; + var in1 = shapeIn[attr1]; + shapeIn[attr0] = pos2r(shapeIn[attr0], true); + shapeIn[attr1] = pos2r(shapeIn[attr1], true); + + if(sizeMode === 'pixel') { + coerce(attr0, 0); + coerce(attr1, 10); + } else { + Axes.coercePosition(shapeOut, gdMock, coerce, axRef, attr0, dflt0); + Axes.coercePosition(shapeOut, gdMock, coerce, axRef, attr1, dflt1); + } + + // hack part 2 + shapeOut[attr0] = r2pos(shapeOut[attr0]); + shapeOut[attr1] = r2pos(shapeOut[attr1]); + shapeIn[attr0] = in0; + shapeIn[attr1] = in1; + } + + // Coerce xanchor and yanchor + if(sizeMode === 'pixel') { + // Hack for log axis described above + var inAnchor = shapeIn[attrAnchor]; + shapeIn[attrAnchor] = pos2r(shapeIn[attrAnchor], true); + + Axes.coercePosition(shapeOut, gdMock, coerce, axRef, attrAnchor, 0.25); + + // Hack part 2 + shapeOut[attrAnchor] = r2pos(shapeOut[attrAnchor]); + shapeIn[attrAnchor] = inAnchor; + } + } + + if(shapeType === 'path') { + coerce('path'); + } else { + Lib.noneOrAll(shapeIn, shapeOut, ['x0', 'x1', 'y0', 'y1']); + } +} + +},{"../../lib":169,"../../plots/array_container_defaults":209,"../../plots/cartesian/axes":213,"./attributes":126,"./helpers":131}],130:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Registry = _dereq_('../../registry'); +var Lib = _dereq_('../../lib'); +var Axes = _dereq_('../../plots/cartesian/axes'); +var Color = _dereq_('../color'); +var Drawing = _dereq_('../drawing'); +var arrayEditor = _dereq_('../../plot_api/plot_template').arrayEditor; + +var dragElement = _dereq_('../dragelement'); +var setCursor = _dereq_('../../lib/setcursor'); + +var constants = _dereq_('./constants'); +var helpers = _dereq_('./helpers'); + + +// Shapes are stored in gd.layout.shapes, an array of objects +// index can point to one item in this array, +// or non-numeric to simply add a new one +// or -1 to modify all existing +// opt can be the full options object, or one key (to be set to value) +// or undefined to simply redraw +// if opt is blank, val can be 'add' or a full options object to add a new +// annotation at that point in the array, or 'remove' to delete this one + +module.exports = { + draw: draw, + drawOne: drawOne +}; + +function draw(gd) { + var fullLayout = gd._fullLayout; + + // Remove previous shapes before drawing new in shapes in fullLayout.shapes + fullLayout._shapeUpperLayer.selectAll('path').remove(); + fullLayout._shapeLowerLayer.selectAll('path').remove(); + + for(var k in fullLayout._plots) { + var shapelayer = fullLayout._plots[k].shapelayer; + if(shapelayer) shapelayer.selectAll('path').remove(); + } + + for(var i = 0; i < fullLayout.shapes.length; i++) { + if(fullLayout.shapes[i].visible) { + drawOne(gd, i); + } + } + + // may need to resurrect this if we put text (LaTeX) in shapes + // return Plots.previousPromises(gd); +} + +function drawOne(gd, index) { + // remove the existing shape if there is one. + // because indices can change, we need to look in all shape layers + gd._fullLayout._paperdiv + .selectAll('.shapelayer [data-index="' + index + '"]') + .remove(); + + var options = gd._fullLayout.shapes[index] || {}; + + // this shape is gone - quit now after deleting it + // TODO: use d3 idioms instead of deleting and redrawing every time + if(!options._input || options.visible === false) return; + + if(options.layer !== 'below') { + drawShape(gd._fullLayout._shapeUpperLayer); + } else if(options.xref === 'paper' || options.yref === 'paper') { + drawShape(gd._fullLayout._shapeLowerLayer); + } else { + var plotinfo = gd._fullLayout._plots[options.xref + options.yref]; + if(plotinfo) { + var mainPlot = plotinfo.mainplotinfo || plotinfo; + drawShape(mainPlot.shapelayer); + } else { + // Fall back to _shapeLowerLayer in case the requested subplot doesn't exist. + // This can happen if you reference the shape to an x / y axis combination + // that doesn't have any data on it (and layer is below) + drawShape(gd._fullLayout._shapeLowerLayer); + } + } + + function drawShape(shapeLayer) { + var attrs = { + 'data-index': index, + 'fill-rule': 'evenodd', + d: getPathString(gd, options) + }; + var lineColor = options.line.width ? options.line.color : 'rgba(0,0,0,0)'; + + var path = shapeLayer.append('path') + .attr(attrs) + .style('opacity', options.opacity) + .call(Color.stroke, lineColor) + .call(Color.fill, options.fillcolor) + .call(Drawing.dashLine, options.line.dash, options.line.width); + + setClipPath(path, gd, options); + + if(gd._context.edits.shapePosition) setupDragElement(gd, path, options, index, shapeLayer); + } +} + +function setClipPath(shapePath, gd, shapeOptions) { + // note that for layer="below" the clipAxes can be different from the + // subplot we're drawing this in. This could cause problems if the shape + // spans two subplots. See https://github.com/plotly/plotly.js/issues/1452 + var clipAxes = (shapeOptions.xref + shapeOptions.yref).replace(/paper/g, ''); + + Drawing.setClipUrl( + shapePath, + clipAxes ? 'clip' + gd._fullLayout._uid + clipAxes : null, + gd + ); +} + +function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer) { + var MINWIDTH = 10; + var MINHEIGHT = 10; + + var xPixelSized = shapeOptions.xsizemode === 'pixel'; + var yPixelSized = shapeOptions.ysizemode === 'pixel'; + var isLine = shapeOptions.type === 'line'; + var isPath = shapeOptions.type === 'path'; + + var editHelpers = arrayEditor(gd.layout, 'shapes', shapeOptions); + var modifyItem = editHelpers.modifyItem; + + var x0, y0, x1, y1, xAnchor, yAnchor; + var n0, s0, w0, e0, optN, optS, optW, optE; + var pathIn; + + // setup conversion functions + var xa = Axes.getFromId(gd, shapeOptions.xref); + var ya = Axes.getFromId(gd, shapeOptions.yref); + var x2p = helpers.getDataToPixel(gd, xa); + var y2p = helpers.getDataToPixel(gd, ya, true); + var p2x = helpers.getPixelToData(gd, xa); + var p2y = helpers.getPixelToData(gd, ya, true); + + var sensoryElement = obtainSensoryElement(); + var dragOptions = { + element: sensoryElement.node(), + gd: gd, + prepFn: startDrag, + doneFn: endDrag, + clickFn: abortDrag + }; + var dragMode; + + dragElement.init(dragOptions); + + sensoryElement.node().onmousemove = updateDragMode; + + function obtainSensoryElement() { + return isLine ? createLineDragHandles() : shapePath; + } + + function createLineDragHandles() { + var minSensoryWidth = 10; + var sensoryWidth = Math.max(shapeOptions.line.width, minSensoryWidth); + + // Helper shapes group + // Note that by setting the `data-index` attr, it is ensured that + // the helper group is purged in this modules `draw` function + var g = shapeLayer.append('g') + .attr('data-index', index); + + // Helper path for moving + g.append('path') + .attr('d', shapePath.attr('d')) + .style({ + 'cursor': 'move', + 'stroke-width': sensoryWidth, + 'stroke-opacity': '0' // ensure not visible + }); + + // Helper circles for resizing + var circleStyle = { + 'fill-opacity': '0' // ensure not visible + }; + var circleRadius = sensoryWidth / 2 > minSensoryWidth ? sensoryWidth / 2 : minSensoryWidth; + + g.append('circle') + .attr({ + 'data-line-point': 'start-point', + 'cx': xPixelSized ? x2p(shapeOptions.xanchor) + shapeOptions.x0 : x2p(shapeOptions.x0), + 'cy': yPixelSized ? y2p(shapeOptions.yanchor) - shapeOptions.y0 : y2p(shapeOptions.y0), + 'r': circleRadius + }) + .style(circleStyle) + .classed('cursor-grab', true); + + g.append('circle') + .attr({ + 'data-line-point': 'end-point', + 'cx': xPixelSized ? x2p(shapeOptions.xanchor) + shapeOptions.x1 : x2p(shapeOptions.x1), + 'cy': yPixelSized ? y2p(shapeOptions.yanchor) - shapeOptions.y1 : y2p(shapeOptions.y1), + 'r': circleRadius + }) + .style(circleStyle) + .classed('cursor-grab', true); + + return g; + } + + function updateDragMode(evt) { + if(isLine) { + if(evt.target.tagName === 'path') { + dragMode = 'move'; + } else { + dragMode = evt.target.attributes['data-line-point'].value === 'start-point' ? + 'resize-over-start-point' : 'resize-over-end-point'; + } + } else { + // element might not be on screen at time of setup, + // so obtain bounding box here + var dragBBox = dragOptions.element.getBoundingClientRect(); + + // choose 'move' or 'resize' + // based on initial position of cursor within the drag element + var w = dragBBox.right - dragBBox.left; + var h = dragBBox.bottom - dragBBox.top; + var x = evt.clientX - dragBBox.left; + var y = evt.clientY - dragBBox.top; + var cursor = (!isPath && w > MINWIDTH && h > MINHEIGHT && !evt.shiftKey) ? + dragElement.getCursor(x / w, 1 - y / h) : + 'move'; + + setCursor(shapePath, cursor); + + // possible values 'move', 'sw', 'w', 'se', 'e', 'ne', 'n', 'nw' and 'w' + dragMode = cursor.split('-')[0]; + } + } + + function startDrag(evt) { + // setup update strings and initial values + if(xPixelSized) { + xAnchor = x2p(shapeOptions.xanchor); + } + if(yPixelSized) { + yAnchor = y2p(shapeOptions.yanchor); + } + + if(shapeOptions.type === 'path') { + pathIn = shapeOptions.path; + } else { + x0 = xPixelSized ? shapeOptions.x0 : x2p(shapeOptions.x0); + y0 = yPixelSized ? shapeOptions.y0 : y2p(shapeOptions.y0); + x1 = xPixelSized ? shapeOptions.x1 : x2p(shapeOptions.x1); + y1 = yPixelSized ? shapeOptions.y1 : y2p(shapeOptions.y1); + } + + if(x0 < x1) { + w0 = x0; + optW = 'x0'; + e0 = x1; + optE = 'x1'; + } else { + w0 = x1; + optW = 'x1'; + e0 = x0; + optE = 'x0'; + } + + // For fixed size shapes take opposing direction of y-axis into account. + // Hint: For data sized shapes this is done by the y2p function. + if((!yPixelSized && y0 < y1) || (yPixelSized && y0 > y1)) { + n0 = y0; + optN = 'y0'; + s0 = y1; + optS = 'y1'; + } else { + n0 = y1; + optN = 'y1'; + s0 = y0; + optS = 'y0'; + } + + // setup dragMode and the corresponding handler + updateDragMode(evt); + renderVisualCues(shapeLayer, shapeOptions); + deactivateClipPathTemporarily(shapePath, shapeOptions, gd); + dragOptions.moveFn = (dragMode === 'move') ? moveShape : resizeShape; + } + + function endDrag() { + setCursor(shapePath); + removeVisualCues(shapeLayer); + + // Don't rely on clipPath being activated during re-layout + setClipPath(shapePath, gd, shapeOptions); + Registry.call('_guiRelayout', gd, editHelpers.getUpdateObj()); + } + + function abortDrag() { + removeVisualCues(shapeLayer); + } + + function moveShape(dx, dy) { + if(shapeOptions.type === 'path') { + var noOp = function(coord) { return coord; }; + var moveX = noOp; + var moveY = noOp; + + if(xPixelSized) { + modifyItem('xanchor', shapeOptions.xanchor = p2x(xAnchor + dx)); + } else { + moveX = function moveX(x) { return p2x(x2p(x) + dx); }; + if(xa && xa.type === 'date') moveX = helpers.encodeDate(moveX); + } + + if(yPixelSized) { + modifyItem('yanchor', shapeOptions.yanchor = p2y(yAnchor + dy)); + } else { + moveY = function moveY(y) { return p2y(y2p(y) + dy); }; + if(ya && ya.type === 'date') moveY = helpers.encodeDate(moveY); + } + + modifyItem('path', shapeOptions.path = movePath(pathIn, moveX, moveY)); + } else { + if(xPixelSized) { + modifyItem('xanchor', shapeOptions.xanchor = p2x(xAnchor + dx)); + } else { + modifyItem('x0', shapeOptions.x0 = p2x(x0 + dx)); + modifyItem('x1', shapeOptions.x1 = p2x(x1 + dx)); + } + + if(yPixelSized) { + modifyItem('yanchor', shapeOptions.yanchor = p2y(yAnchor + dy)); + } else { + modifyItem('y0', shapeOptions.y0 = p2y(y0 + dy)); + modifyItem('y1', shapeOptions.y1 = p2y(y1 + dy)); + } + } + + shapePath.attr('d', getPathString(gd, shapeOptions)); + renderVisualCues(shapeLayer, shapeOptions); + } + + function resizeShape(dx, dy) { + if(isPath) { + // TODO: implement path resize, don't forget to update dragMode code + var noOp = function(coord) { return coord; }; + var moveX = noOp; + var moveY = noOp; + + if(xPixelSized) { + modifyItem('xanchor', shapeOptions.xanchor = p2x(xAnchor + dx)); + } else { + moveX = function moveX(x) { return p2x(x2p(x) + dx); }; + if(xa && xa.type === 'date') moveX = helpers.encodeDate(moveX); + } + + if(yPixelSized) { + modifyItem('yanchor', shapeOptions.yanchor = p2y(yAnchor + dy)); + } else { + moveY = function moveY(y) { return p2y(y2p(y) + dy); }; + if(ya && ya.type === 'date') moveY = helpers.encodeDate(moveY); + } + + modifyItem('path', shapeOptions.path = movePath(pathIn, moveX, moveY)); + } else if(isLine) { + if(dragMode === 'resize-over-start-point') { + var newX0 = x0 + dx; + var newY0 = yPixelSized ? y0 - dy : y0 + dy; + modifyItem('x0', shapeOptions.x0 = xPixelSized ? newX0 : p2x(newX0)); + modifyItem('y0', shapeOptions.y0 = yPixelSized ? newY0 : p2y(newY0)); + } else if(dragMode === 'resize-over-end-point') { + var newX1 = x1 + dx; + var newY1 = yPixelSized ? y1 - dy : y1 + dy; + modifyItem('x1', shapeOptions.x1 = xPixelSized ? newX1 : p2x(newX1)); + modifyItem('y1', shapeOptions.y1 = yPixelSized ? newY1 : p2y(newY1)); + } + } else { + var newN = (~dragMode.indexOf('n')) ? n0 + dy : n0; + var newS = (~dragMode.indexOf('s')) ? s0 + dy : s0; + var newW = (~dragMode.indexOf('w')) ? w0 + dx : w0; + var newE = (~dragMode.indexOf('e')) ? e0 + dx : e0; + + // Do things in opposing direction for y-axis. + // Hint: for data-sized shapes the reversal of axis direction is done in p2y. + if(~dragMode.indexOf('n') && yPixelSized) newN = n0 - dy; + if(~dragMode.indexOf('s') && yPixelSized) newS = s0 - dy; + + // Update shape eventually. Again, be aware of the + // opposing direction of the y-axis of fixed size shapes. + if((!yPixelSized && newS - newN > MINHEIGHT) || + (yPixelSized && newN - newS > MINHEIGHT)) { + modifyItem(optN, shapeOptions[optN] = yPixelSized ? newN : p2y(newN)); + modifyItem(optS, shapeOptions[optS] = yPixelSized ? newS : p2y(newS)); + } + if(newE - newW > MINWIDTH) { + modifyItem(optW, shapeOptions[optW] = xPixelSized ? newW : p2x(newW)); + modifyItem(optE, shapeOptions[optE] = xPixelSized ? newE : p2x(newE)); + } + } + + shapePath.attr('d', getPathString(gd, shapeOptions)); + renderVisualCues(shapeLayer, shapeOptions); + } + + function renderVisualCues(shapeLayer, shapeOptions) { + if(xPixelSized || yPixelSized) { + renderAnchor(); + } + + function renderAnchor() { + var isNotPath = shapeOptions.type !== 'path'; + + // d3 join with dummy data to satisfy d3 data-binding + var visualCues = shapeLayer.selectAll('.visual-cue').data([0]); + + // Enter + var strokeWidth = 1; + visualCues.enter() + .append('path') + .attr({ + 'fill': '#fff', + 'fill-rule': 'evenodd', + 'stroke': '#000', + 'stroke-width': strokeWidth + }) + .classed('visual-cue', true); + + // Update + var posX = x2p( + xPixelSized ? + shapeOptions.xanchor : + Lib.midRange( + isNotPath ? + [shapeOptions.x0, shapeOptions.x1] : + helpers.extractPathCoords(shapeOptions.path, constants.paramIsX)) + ); + var posY = y2p( + yPixelSized ? + shapeOptions.yanchor : + Lib.midRange( + isNotPath ? + [shapeOptions.y0, shapeOptions.y1] : + helpers.extractPathCoords(shapeOptions.path, constants.paramIsY)) + ); + + posX = helpers.roundPositionForSharpStrokeRendering(posX, strokeWidth); + posY = helpers.roundPositionForSharpStrokeRendering(posY, strokeWidth); + + if(xPixelSized && yPixelSized) { + var crossPath = 'M' + (posX - 1 - strokeWidth) + ',' + (posY - 1 - strokeWidth) + + 'h-8v2h8 v8h2v-8 h8v-2h-8 v-8h-2 Z'; + visualCues.attr('d', crossPath); + } else if(xPixelSized) { + var vBarPath = 'M' + (posX - 1 - strokeWidth) + ',' + (posY - 9 - strokeWidth) + + 'v18 h2 v-18 Z'; + visualCues.attr('d', vBarPath); + } else { + var hBarPath = 'M' + (posX - 9 - strokeWidth) + ',' + (posY - 1 - strokeWidth) + + 'h18 v2 h-18 Z'; + visualCues.attr('d', hBarPath); + } + } + } + + function removeVisualCues(shapeLayer) { + shapeLayer.selectAll('.visual-cue').remove(); + } + + function deactivateClipPathTemporarily(shapePath, shapeOptions, gd) { + var xref = shapeOptions.xref; + var yref = shapeOptions.yref; + var xa = Axes.getFromId(gd, xref); + var ya = Axes.getFromId(gd, yref); + + var clipAxes = ''; + if(xref !== 'paper' && !xa.autorange) clipAxes += xref; + if(yref !== 'paper' && !ya.autorange) clipAxes += yref; + + Drawing.setClipUrl( + shapePath, + clipAxes ? 'clip' + gd._fullLayout._uid + clipAxes : null, + gd + ); + } +} + +function getPathString(gd, options) { + var type = options.type; + var xa = Axes.getFromId(gd, options.xref); + var ya = Axes.getFromId(gd, options.yref); + var gs = gd._fullLayout._size; + var x2r, x2p, y2r, y2p; + var x0, x1, y0, y1; + + if(xa) { + x2r = helpers.shapePositionToRange(xa); + x2p = function(v) { return xa._offset + xa.r2p(x2r(v, true)); }; + } else { + x2p = function(v) { return gs.l + gs.w * v; }; + } + + if(ya) { + y2r = helpers.shapePositionToRange(ya); + y2p = function(v) { return ya._offset + ya.r2p(y2r(v, true)); }; + } else { + y2p = function(v) { return gs.t + gs.h * (1 - v); }; + } + + if(type === 'path') { + if(xa && xa.type === 'date') x2p = helpers.decodeDate(x2p); + if(ya && ya.type === 'date') y2p = helpers.decodeDate(y2p); + return convertPath(options, x2p, y2p); + } + + if(options.xsizemode === 'pixel') { + var xAnchorPos = x2p(options.xanchor); + x0 = xAnchorPos + options.x0; + x1 = xAnchorPos + options.x1; + } else { + x0 = x2p(options.x0); + x1 = x2p(options.x1); + } + + if(options.ysizemode === 'pixel') { + var yAnchorPos = y2p(options.yanchor); + y0 = yAnchorPos - options.y0; + y1 = yAnchorPos - options.y1; + } else { + y0 = y2p(options.y0); + y1 = y2p(options.y1); + } + + if(type === 'line') return 'M' + x0 + ',' + y0 + 'L' + x1 + ',' + y1; + if(type === 'rect') return 'M' + x0 + ',' + y0 + 'H' + x1 + 'V' + y1 + 'H' + x0 + 'Z'; + + // circle + var cx = (x0 + x1) / 2; + var cy = (y0 + y1) / 2; + var rx = Math.abs(cx - x0); + var ry = Math.abs(cy - y0); + var rArc = 'A' + rx + ',' + ry; + var rightPt = (cx + rx) + ',' + cy; + var topPt = cx + ',' + (cy - ry); + return 'M' + rightPt + rArc + ' 0 1,1 ' + topPt + + rArc + ' 0 0,1 ' + rightPt + 'Z'; +} + + +function convertPath(options, x2p, y2p) { + var pathIn = options.path; + var xSizemode = options.xsizemode; + var ySizemode = options.ysizemode; + var xAnchor = options.xanchor; + var yAnchor = options.yanchor; + + return pathIn.replace(constants.segmentRE, function(segment) { + var paramNumber = 0; + var segmentType = segment.charAt(0); + var xParams = constants.paramIsX[segmentType]; + var yParams = constants.paramIsY[segmentType]; + var nParams = constants.numParams[segmentType]; + + var paramString = segment.substr(1).replace(constants.paramRE, function(param) { + if(xParams[paramNumber]) { + if(xSizemode === 'pixel') param = x2p(xAnchor) + Number(param); + else param = x2p(param); + } else if(yParams[paramNumber]) { + if(ySizemode === 'pixel') param = y2p(yAnchor) - Number(param); + else param = y2p(param); + } + paramNumber++; + + if(paramNumber > nParams) param = 'X'; + return param; + }); + + if(paramNumber > nParams) { + paramString = paramString.replace(/[\s,]*X.*/, ''); + Lib.log('Ignoring extra params in segment ' + segment); + } + + return segmentType + paramString; + }); +} + +function movePath(pathIn, moveX, moveY) { + return pathIn.replace(constants.segmentRE, function(segment) { + var paramNumber = 0; + var segmentType = segment.charAt(0); + var xParams = constants.paramIsX[segmentType]; + var yParams = constants.paramIsY[segmentType]; + var nParams = constants.numParams[segmentType]; + + var paramString = segment.substr(1).replace(constants.paramRE, function(param) { + if(paramNumber >= nParams) return param; + + if(xParams[paramNumber]) param = moveX(param); + else if(yParams[paramNumber]) param = moveY(param); + + paramNumber++; + + return param; + }); + + return segmentType + paramString; + }); +} + +},{"../../lib":169,"../../lib/setcursor":188,"../../plot_api/plot_template":203,"../../plots/cartesian/axes":213,"../../registry":258,"../color":51,"../dragelement":69,"../drawing":72,"./constants":128,"./helpers":131}],131:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var constants = _dereq_('./constants'); + +var Lib = _dereq_('../../lib'); + +// special position conversion functions... category axis positions can't be +// specified by their data values, because they don't make a continuous mapping. +// so these have to be specified in terms of the category serial numbers, +// but can take fractional values. Other axis types we specify position based on +// the actual data values. +// TODO: in V2.0 (when log axis ranges are in data units) range and shape position +// will be identical, so rangeToShapePosition and shapePositionToRange can be +// removed entirely. + +exports.rangeToShapePosition = function(ax) { + return (ax.type === 'log') ? ax.r2d : function(v) { return v; }; +}; + +exports.shapePositionToRange = function(ax) { + return (ax.type === 'log') ? ax.d2r : function(v) { return v; }; +}; + +exports.decodeDate = function(convertToPx) { + return function(v) { + if(v.replace) v = v.replace('_', ' '); + return convertToPx(v); + }; +}; + +exports.encodeDate = function(convertToDate) { + return function(v) { return convertToDate(v).replace(' ', '_'); }; +}; + +exports.extractPathCoords = function(path, paramsToUse) { + var extractedCoordinates = []; + + var segments = path.match(constants.segmentRE); + segments.forEach(function(segment) { + var relevantParamIdx = paramsToUse[segment.charAt(0)].drawn; + if(relevantParamIdx === undefined) return; + + var params = segment.substr(1).match(constants.paramRE); + if(!params || params.length < relevantParamIdx) return; + + extractedCoordinates.push(Lib.cleanNumber(params[relevantParamIdx])); + }); + + return extractedCoordinates; +}; + +exports.getDataToPixel = function(gd, axis, isVertical) { + var gs = gd._fullLayout._size; + var dataToPixel; + + if(axis) { + var d2r = exports.shapePositionToRange(axis); + + dataToPixel = function(v) { + return axis._offset + axis.r2p(d2r(v, true)); + }; + + if(axis.type === 'date') dataToPixel = exports.decodeDate(dataToPixel); + } else if(isVertical) { + dataToPixel = function(v) { return gs.t + gs.h * (1 - v); }; + } else { + dataToPixel = function(v) { return gs.l + gs.w * v; }; + } + + return dataToPixel; +}; + +exports.getPixelToData = function(gd, axis, isVertical) { + var gs = gd._fullLayout._size; + var pixelToData; + + if(axis) { + var r2d = exports.rangeToShapePosition(axis); + pixelToData = function(p) { return r2d(axis.p2r(p - axis._offset)); }; + } else if(isVertical) { + pixelToData = function(p) { return 1 - (p - gs.t) / gs.h; }; + } else { + pixelToData = function(p) { return (p - gs.l) / gs.w; }; + } + + return pixelToData; +}; + +/** + * Based on the given stroke width, rounds the passed + * position value to represent either a full or half pixel. + * + * In case of an odd stroke width (e.g. 1), this measure ensures + * that a stroke positioned at the returned position isn't rendered + * blurry due to anti-aliasing. + * + * In case of an even stroke width (e.g. 2), this measure ensures + * that the position value is transformed to a full pixel value + * so that anti-aliasing doesn't take effect either. + * + * @param {number} pos The raw position value to be transformed + * @param {number} strokeWidth The stroke width + * @returns {number} either an integer or a .5 decimal number + */ +exports.roundPositionForSharpStrokeRendering = function(pos, strokeWidth) { + var strokeWidthIsOdd = Math.round(strokeWidth % 2) === 1; + var posValAsInt = Math.round(pos); + + return strokeWidthIsOdd ? posValAsInt + 0.5 : posValAsInt; +}; + +},{"../../lib":169,"./constants":128}],132:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var drawModule = _dereq_('./draw'); + +module.exports = { + moduleType: 'component', + name: 'shapes', + + layoutAttributes: _dereq_('./attributes'), + supplyLayoutDefaults: _dereq_('./defaults'), + includeBasePlot: _dereq_('../../plots/cartesian/include_components')('shapes'), + + calcAutorange: _dereq_('./calc_autorange'), + draw: drawModule.draw, + drawOne: drawModule.drawOne +}; + +},{"../../plots/cartesian/include_components":223,"./attributes":126,"./calc_autorange":127,"./defaults":129,"./draw":130}],133:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var fontAttrs = _dereq_('../../plots/font_attributes'); +var padAttrs = _dereq_('../../plots/pad_attributes'); +var extendDeepAll = _dereq_('../../lib/extend').extendDeepAll; +var overrideAll = _dereq_('../../plot_api/edit_types').overrideAll; +var animationAttrs = _dereq_('../../plots/animation_attributes'); +var templatedArray = _dereq_('../../plot_api/plot_template').templatedArray; +var constants = _dereq_('./constants'); + +var stepsAttrs = templatedArray('step', { + visible: { + valType: 'boolean', + + dflt: true, + + }, + method: { + valType: 'enumerated', + values: ['restyle', 'relayout', 'animate', 'update', 'skip'], + dflt: 'restyle', + + + }, + args: { + valType: 'info_array', + + freeLength: true, + items: [ + { valType: 'any' }, + { valType: 'any' }, + { valType: 'any' } + ], + + }, + label: { + valType: 'string', + + + }, + value: { + valType: 'string', + + + }, + execute: { + valType: 'boolean', + + dflt: true, + + } +}); + +module.exports = overrideAll(templatedArray('slider', { + visible: { + valType: 'boolean', + + dflt: true, + + }, + + active: { + valType: 'number', + + min: 0, + dflt: 0, + + }, + + steps: stepsAttrs, + + lenmode: { + valType: 'enumerated', + values: ['fraction', 'pixels'], + + dflt: 'fraction', + + }, + len: { + valType: 'number', + min: 0, + dflt: 1, + + + }, + x: { + valType: 'number', + min: -2, + max: 3, + dflt: 0, + + + }, + pad: extendDeepAll(padAttrs({editType: 'arraydraw'}), { + + }, {t: {dflt: 20}}), + xanchor: { + valType: 'enumerated', + values: ['auto', 'left', 'center', 'right'], + dflt: 'left', + + + }, + y: { + valType: 'number', + min: -2, + max: 3, + dflt: 0, + + + }, + yanchor: { + valType: 'enumerated', + values: ['auto', 'top', 'middle', 'bottom'], + dflt: 'top', + + + }, + + transition: { + duration: { + valType: 'number', + + min: 0, + dflt: 150, + + }, + easing: { + valType: 'enumerated', + values: animationAttrs.transition.easing.values, + + dflt: 'cubic-in-out', + + } + }, + + currentvalue: { + visible: { + valType: 'boolean', + + dflt: true, + + }, + + xanchor: { + valType: 'enumerated', + values: ['left', 'center', 'right'], + dflt: 'left', + + + }, + + offset: { + valType: 'number', + dflt: 10, + + + }, + + prefix: { + valType: 'string', + + + }, + + suffix: { + valType: 'string', + + + }, + + font: fontAttrs({ + + }) + }, + + font: fontAttrs({ + + }), + + activebgcolor: { + valType: 'color', + + dflt: constants.gripBgActiveColor, + + }, + bgcolor: { + valType: 'color', + + dflt: constants.railBgColor, + + }, + bordercolor: { + valType: 'color', + dflt: constants.railBorderColor, + + + }, + borderwidth: { + valType: 'number', + min: 0, + dflt: constants.railBorderWidth, + + + }, + ticklen: { + valType: 'number', + min: 0, + dflt: constants.tickLength, + + + }, + tickcolor: { + valType: 'color', + dflt: constants.tickColor, + + + }, + tickwidth: { + valType: 'number', + min: 0, + dflt: 1, + + + }, + minorticklen: { + valType: 'number', + min: 0, + dflt: constants.minorTickLength, + + + } +}), 'arraydraw', 'from-root'); + +},{"../../lib/extend":164,"../../plot_api/edit_types":196,"../../plot_api/plot_template":203,"../../plots/animation_attributes":208,"../../plots/font_attributes":239,"../../plots/pad_attributes":244,"./constants":134}],134:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + + +module.exports = { + + // layout attribute name + name: 'sliders', + + // class names + containerClassName: 'slider-container', + groupClassName: 'slider-group', + inputAreaClass: 'slider-input-area', + railRectClass: 'slider-rail-rect', + railTouchRectClass: 'slider-rail-touch-rect', + gripRectClass: 'slider-grip-rect', + tickRectClass: 'slider-tick-rect', + inputProxyClass: 'slider-input-proxy', + labelsClass: 'slider-labels', + labelGroupClass: 'slider-label-group', + labelClass: 'slider-label', + currentValueClass: 'slider-current-value', + + railHeight: 5, + + // DOM attribute name in button group keeping track + // of active update menu + menuIndexAttrName: 'slider-active-index', + + // id root pass to Plots.autoMargin + autoMarginIdRoot: 'slider-', + + // min item width / height + minWidth: 30, + minHeight: 30, + + // padding around item text + textPadX: 40, + + // arrow offset off right edge + arrowOffsetX: 4, + + railRadius: 2, + railWidth: 5, + railBorder: 4, + railBorderWidth: 1, + railBorderColor: '#bec8d9', + railBgColor: '#f8fafc', + + // The distance of the rail from the edge of the touchable area + // Slightly less than the step inset because of the curved edges + // of the rail + railInset: 8, + + // The distance from the extremal tick marks to the edge of the + // touchable area. This is basically the same as the grip radius, + // but for other styles it wouldn't really need to be. + stepInset: 10, + + gripRadius: 10, + gripWidth: 20, + gripHeight: 20, + gripBorder: 20, + gripBorderWidth: 1, + gripBorderColor: '#bec8d9', + gripBgColor: '#f6f8fa', + gripBgActiveColor: '#dbdde0', + + labelPadding: 8, + labelOffset: 0, + + tickWidth: 1, + tickColor: '#333', + tickOffset: 25, + tickLength: 7, + + minorTickOffset: 25, + minorTickColor: '#333', + minorTickLength: 4, + + // Extra space below the current value label: + currentValuePadding: 8, + currentValueInset: 0, +}; + +},{}],135:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var handleArrayContainerDefaults = _dereq_('../../plots/array_container_defaults'); + +var attributes = _dereq_('./attributes'); +var constants = _dereq_('./constants'); + +var name = constants.name; +var stepAttrs = attributes.steps; + + +module.exports = function slidersDefaults(layoutIn, layoutOut) { + handleArrayContainerDefaults(layoutIn, layoutOut, { + name: name, + handleItemDefaults: sliderDefaults + }); +}; + +function sliderDefaults(sliderIn, sliderOut, layoutOut) { + function coerce(attr, dflt) { + return Lib.coerce(sliderIn, sliderOut, attributes, attr, dflt); + } + + var steps = handleArrayContainerDefaults(sliderIn, sliderOut, { + name: 'steps', + handleItemDefaults: stepDefaults + }); + + var stepCount = 0; + for(var i = 0; i < steps.length; i++) { + if(steps[i].visible) stepCount++; + } + + var visible; + // If it has fewer than two options, it's not really a slider + if(stepCount < 2) visible = sliderOut.visible = false; + else visible = coerce('visible'); + if(!visible) return; + + sliderOut._stepCount = stepCount; + var visSteps = sliderOut._visibleSteps = Lib.filterVisible(steps); + + var active = coerce('active'); + if(!(steps[active] || {}).visible) sliderOut.active = visSteps[0]._index; + + coerce('x'); + coerce('y'); + Lib.noneOrAll(sliderIn, sliderOut, ['x', 'y']); + + coerce('xanchor'); + coerce('yanchor'); + + coerce('len'); + coerce('lenmode'); + + coerce('pad.t'); + coerce('pad.r'); + coerce('pad.b'); + coerce('pad.l'); + + Lib.coerceFont(coerce, 'font', layoutOut.font); + + var currentValueIsVisible = coerce('currentvalue.visible'); + + if(currentValueIsVisible) { + coerce('currentvalue.xanchor'); + coerce('currentvalue.prefix'); + coerce('currentvalue.suffix'); + coerce('currentvalue.offset'); + + Lib.coerceFont(coerce, 'currentvalue.font', sliderOut.font); + } + + coerce('transition.duration'); + coerce('transition.easing'); + + coerce('bgcolor'); + coerce('activebgcolor'); + coerce('bordercolor'); + coerce('borderwidth'); + coerce('ticklen'); + coerce('tickwidth'); + coerce('tickcolor'); + coerce('minorticklen'); +} + +function stepDefaults(valueIn, valueOut) { + function coerce(attr, dflt) { + return Lib.coerce(valueIn, valueOut, stepAttrs, attr, dflt); + } + + var visible; + if(valueIn.method !== 'skip' && !Array.isArray(valueIn.args)) { + visible = valueOut.visible = false; + } else visible = coerce('visible'); + + if(visible) { + coerce('method'); + coerce('args'); + var label = coerce('label', 'step-' + valueOut._index); + coerce('value', label); + coerce('execute'); + } +} + +},{"../../lib":169,"../../plots/array_container_defaults":209,"./attributes":133,"./constants":134}],136:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); + +var Plots = _dereq_('../../plots/plots'); +var Color = _dereq_('../color'); +var Drawing = _dereq_('../drawing'); +var Lib = _dereq_('../../lib'); +var svgTextUtils = _dereq_('../../lib/svg_text_utils'); +var arrayEditor = _dereq_('../../plot_api/plot_template').arrayEditor; + +var constants = _dereq_('./constants'); +var alignmentConstants = _dereq_('../../constants/alignment'); +var LINE_SPACING = alignmentConstants.LINE_SPACING; +var FROM_TL = alignmentConstants.FROM_TL; +var FROM_BR = alignmentConstants.FROM_BR; + +module.exports = function draw(gd) { + var fullLayout = gd._fullLayout; + var sliderData = makeSliderData(fullLayout, gd); + + // draw a container for *all* sliders: + var sliders = fullLayout._infolayer + .selectAll('g.' + constants.containerClassName) + .data(sliderData.length > 0 ? [0] : []); + + sliders.enter().append('g') + .classed(constants.containerClassName, true) + .style('cursor', 'ew-resize'); + + function clearSlider(sliderOpts) { + if(sliderOpts._commandObserver) { + sliderOpts._commandObserver.remove(); + delete sliderOpts._commandObserver; + } + + // Most components don't need to explicitly remove autoMargin, because + // marginPushers does this - but slider updates don't go through + // a full replot so we need to explicitly remove it. + Plots.autoMargin(gd, autoMarginId(sliderOpts)); + } + + sliders.exit().each(function() { + d3.select(this).selectAll('g.' + constants.groupClassName) + .each(clearSlider); + }) + .remove(); + + // Return early if no menus visible: + if(sliderData.length === 0) return; + + var sliderGroups = sliders.selectAll('g.' + constants.groupClassName) + .data(sliderData, keyFunction); + + sliderGroups.enter().append('g') + .classed(constants.groupClassName, true); + + sliderGroups.exit() + .each(clearSlider) + .remove(); + + // Find the dimensions of the sliders: + for(var i = 0; i < sliderData.length; i++) { + var sliderOpts = sliderData[i]; + findDimensions(gd, sliderOpts); + } + + sliderGroups.each(function(sliderOpts) { + var gSlider = d3.select(this); + + computeLabelSteps(sliderOpts); + + Plots.manageCommandObserver(gd, sliderOpts, sliderOpts._visibleSteps, function(data) { + // NB: Same as below. This is *not* always the same as sliderOpts since + // if a new set of steps comes in, the reference in this callback would + // be invalid. We need to refetch it from the slider group, which is + // the join data that creates this slider. So if this slider still exists, + // the group should be valid, *to the best of my knowledge.* If not, + // we'd have to look it up by d3 data join index/key. + var opts = gSlider.data()[0]; + + if(opts.active === data.index) return; + if(opts._dragging) return; + + setActive(gd, gSlider, opts, data.index, false, true); + }); + + drawSlider(gd, d3.select(this), sliderOpts); + }); +}; + +function autoMarginId(sliderOpts) { + return constants.autoMarginIdRoot + sliderOpts._index; +} + +// This really only just filters by visibility: +function makeSliderData(fullLayout, gd) { + var contOpts = fullLayout[constants.name]; + var sliderData = []; + + for(var i = 0; i < contOpts.length; i++) { + var item = contOpts[i]; + if(!item.visible) continue; + item._gd = gd; + sliderData.push(item); + } + + return sliderData; +} + +// This is set in the defaults step: +function keyFunction(opts) { + return opts._index; +} + +// Compute the dimensions (mutates sliderOpts): +function findDimensions(gd, sliderOpts) { + var sliderLabels = Drawing.tester.selectAll('g.' + constants.labelGroupClass) + .data(sliderOpts._visibleSteps); + + sliderLabels.enter().append('g') + .classed(constants.labelGroupClass, true); + + // loop over fake buttons to find width / height + var maxLabelWidth = 0; + var labelHeight = 0; + sliderLabels.each(function(stepOpts) { + var labelGroup = d3.select(this); + + var text = drawLabel(labelGroup, {step: stepOpts}, sliderOpts); + + var textNode = text.node(); + if(textNode) { + var bBox = Drawing.bBox(textNode); + labelHeight = Math.max(labelHeight, bBox.height); + maxLabelWidth = Math.max(maxLabelWidth, bBox.width); + } + }); + + sliderLabels.remove(); + + var dims = sliderOpts._dims = {}; + + dims.inputAreaWidth = Math.max( + constants.railWidth, + constants.gripHeight + ); + + // calculate some overall dimensions - some of these are needed for + // calculating the currentValue dimensions + var graphSize = gd._fullLayout._size; + dims.lx = graphSize.l + graphSize.w * sliderOpts.x; + dims.ly = graphSize.t + graphSize.h * (1 - sliderOpts.y); + + if(sliderOpts.lenmode === 'fraction') { + // fraction: + dims.outerLength = Math.round(graphSize.w * sliderOpts.len); + } else { + // pixels: + dims.outerLength = sliderOpts.len; + } + + // The length of the rail, *excluding* padding on either end: + dims.inputAreaStart = 0; + dims.inputAreaLength = Math.round(dims.outerLength - sliderOpts.pad.l - sliderOpts.pad.r); + + var textableInputLength = dims.inputAreaLength - 2 * constants.stepInset; + var availableSpacePerLabel = textableInputLength / (sliderOpts._stepCount - 1); + var computedSpacePerLabel = maxLabelWidth + constants.labelPadding; + dims.labelStride = Math.max(1, Math.ceil(computedSpacePerLabel / availableSpacePerLabel)); + dims.labelHeight = labelHeight; + + // loop over all possible values for currentValue to find the + // area we need for it + dims.currentValueMaxWidth = 0; + dims.currentValueHeight = 0; + dims.currentValueTotalHeight = 0; + dims.currentValueMaxLines = 1; + + if(sliderOpts.currentvalue.visible) { + // Get the dimensions of the current value label: + var dummyGroup = Drawing.tester.append('g'); + + sliderLabels.each(function(stepOpts) { + var curValPrefix = drawCurrentValue(dummyGroup, sliderOpts, stepOpts.label); + var curValSize = (curValPrefix.node() && Drawing.bBox(curValPrefix.node())) || {width: 0, height: 0}; + var lines = svgTextUtils.lineCount(curValPrefix); + dims.currentValueMaxWidth = Math.max(dims.currentValueMaxWidth, Math.ceil(curValSize.width)); + dims.currentValueHeight = Math.max(dims.currentValueHeight, Math.ceil(curValSize.height)); + dims.currentValueMaxLines = Math.max(dims.currentValueMaxLines, lines); + }); + + dims.currentValueTotalHeight = dims.currentValueHeight + sliderOpts.currentvalue.offset; + + dummyGroup.remove(); + } + + dims.height = dims.currentValueTotalHeight + constants.tickOffset + sliderOpts.ticklen + constants.labelOffset + dims.labelHeight + sliderOpts.pad.t + sliderOpts.pad.b; + + var xanchor = 'left'; + if(Lib.isRightAnchor(sliderOpts)) { + dims.lx -= dims.outerLength; + xanchor = 'right'; + } + if(Lib.isCenterAnchor(sliderOpts)) { + dims.lx -= dims.outerLength / 2; + xanchor = 'center'; + } + + var yanchor = 'top'; + if(Lib.isBottomAnchor(sliderOpts)) { + dims.ly -= dims.height; + yanchor = 'bottom'; + } + if(Lib.isMiddleAnchor(sliderOpts)) { + dims.ly -= dims.height / 2; + yanchor = 'middle'; + } + + dims.outerLength = Math.ceil(dims.outerLength); + dims.height = Math.ceil(dims.height); + dims.lx = Math.round(dims.lx); + dims.ly = Math.round(dims.ly); + + var marginOpts = { + y: sliderOpts.y, + b: dims.height * FROM_BR[yanchor], + t: dims.height * FROM_TL[yanchor] + }; + + if(sliderOpts.lenmode === 'fraction') { + marginOpts.l = 0; + marginOpts.xl = sliderOpts.x - sliderOpts.len * FROM_TL[xanchor]; + marginOpts.r = 0; + marginOpts.xr = sliderOpts.x + sliderOpts.len * FROM_BR[xanchor]; + } else { + marginOpts.x = sliderOpts.x; + marginOpts.l = dims.outerLength * FROM_TL[xanchor]; + marginOpts.r = dims.outerLength * FROM_BR[xanchor]; + } + + Plots.autoMargin(gd, autoMarginId(sliderOpts), marginOpts); +} + +function drawSlider(gd, sliderGroup, sliderOpts) { + // This is related to the other long notes in this file regarding what happens + // when slider steps disappear. This particular fix handles what happens when + // the *current* slider step is removed. The drawing functions will error out + // when they fail to find it, so the fix for now is that it will just draw the + // slider in the first position but will not execute the command. + if(!((sliderOpts.steps[sliderOpts.active] || {}).visible)) { + sliderOpts.active = sliderOpts._visibleSteps[0]._index; + } + + // These are carefully ordered for proper z-ordering: + sliderGroup + .call(drawCurrentValue, sliderOpts) + .call(drawRail, sliderOpts) + .call(drawLabelGroup, sliderOpts) + .call(drawTicks, sliderOpts) + .call(drawTouchRect, gd, sliderOpts) + .call(drawGrip, gd, sliderOpts); + + var dims = sliderOpts._dims; + + // Position the rectangle: + Drawing.setTranslate(sliderGroup, dims.lx + sliderOpts.pad.l, dims.ly + sliderOpts.pad.t); + + sliderGroup.call(setGripPosition, sliderOpts, false); + sliderGroup.call(drawCurrentValue, sliderOpts); +} + +function drawCurrentValue(sliderGroup, sliderOpts, valueOverride) { + if(!sliderOpts.currentvalue.visible) return; + + var dims = sliderOpts._dims; + var x0, textAnchor; + + switch(sliderOpts.currentvalue.xanchor) { + case 'right': + // This is anchored left and adjusted by the width of the longest label + // so that the prefix doesn't move. The goal of this is to emphasize + // what's actually changing and make the update less distracting. + x0 = dims.inputAreaLength - constants.currentValueInset - dims.currentValueMaxWidth; + textAnchor = 'left'; + break; + case 'center': + x0 = dims.inputAreaLength * 0.5; + textAnchor = 'middle'; + break; + default: + x0 = constants.currentValueInset; + textAnchor = 'left'; + } + + var text = Lib.ensureSingle(sliderGroup, 'text', constants.labelClass, function(s) { + s.classed('user-select-none', true) + .attr({ + 'text-anchor': textAnchor, + 'data-notex': 1 + }); + }); + + var str = sliderOpts.currentvalue.prefix ? sliderOpts.currentvalue.prefix : ''; + + if(typeof valueOverride === 'string') { + str += valueOverride; + } else { + var curVal = sliderOpts.steps[sliderOpts.active].label; + var _meta = sliderOpts._gd._fullLayout._meta; + if(_meta) curVal = Lib.templateString(curVal, _meta); + str += curVal; + } + + if(sliderOpts.currentvalue.suffix) { + str += sliderOpts.currentvalue.suffix; + } + + text.call(Drawing.font, sliderOpts.currentvalue.font) + .text(str) + .call(svgTextUtils.convertToTspans, sliderOpts._gd); + + var lines = svgTextUtils.lineCount(text); + + var y0 = (dims.currentValueMaxLines + 1 - lines) * + sliderOpts.currentvalue.font.size * LINE_SPACING; + + svgTextUtils.positionText(text, x0, y0); + + return text; +} + +function drawGrip(sliderGroup, gd, sliderOpts) { + var grip = Lib.ensureSingle(sliderGroup, 'rect', constants.gripRectClass, function(s) { + s.call(attachGripEvents, gd, sliderGroup, sliderOpts) + .style('pointer-events', 'all'); + }); + + grip.attr({ + width: constants.gripWidth, + height: constants.gripHeight, + rx: constants.gripRadius, + ry: constants.gripRadius, + }) + .call(Color.stroke, sliderOpts.bordercolor) + .call(Color.fill, sliderOpts.bgcolor) + .style('stroke-width', sliderOpts.borderwidth + 'px'); +} + +function drawLabel(item, data, sliderOpts) { + var text = Lib.ensureSingle(item, 'text', constants.labelClass, function(s) { + s.classed('user-select-none', true) + .attr({ + 'text-anchor': 'middle', + 'data-notex': 1 + }); + }); + + var tx = data.step.label; + var _meta = sliderOpts._gd._fullLayout._meta; + if(_meta) tx = Lib.templateString(tx, _meta); + + text.call(Drawing.font, sliderOpts.font) + .text(tx) + .call(svgTextUtils.convertToTspans, sliderOpts._gd); + + return text; +} + +function drawLabelGroup(sliderGroup, sliderOpts) { + var labels = Lib.ensureSingle(sliderGroup, 'g', constants.labelsClass); + var dims = sliderOpts._dims; + + var labelItems = labels.selectAll('g.' + constants.labelGroupClass) + .data(dims.labelSteps); + + labelItems.enter().append('g') + .classed(constants.labelGroupClass, true); + + labelItems.exit().remove(); + + labelItems.each(function(d) { + var item = d3.select(this); + + item.call(drawLabel, d, sliderOpts); + + Drawing.setTranslate(item, + normalizedValueToPosition(sliderOpts, d.fraction), + constants.tickOffset + + sliderOpts.ticklen + + // position is the baseline of the top line of text only, even + // if the label spans multiple lines + sliderOpts.font.size * LINE_SPACING + + constants.labelOffset + + dims.currentValueTotalHeight + ); + }); +} + +function handleInput(gd, sliderGroup, sliderOpts, normalizedPosition, doTransition) { + var quantizedPosition = Math.round(normalizedPosition * (sliderOpts._stepCount - 1)); + var quantizedIndex = sliderOpts._visibleSteps[quantizedPosition]._index; + + if(quantizedIndex !== sliderOpts.active) { + setActive(gd, sliderGroup, sliderOpts, quantizedIndex, true, doTransition); + } +} + +function setActive(gd, sliderGroup, sliderOpts, index, doCallback, doTransition) { + var previousActive = sliderOpts.active; + sliderOpts.active = index; + + // due to templating, it's possible this slider doesn't even exist yet + arrayEditor(gd.layout, constants.name, sliderOpts) + .applyUpdate('active', index); + + var step = sliderOpts.steps[sliderOpts.active]; + + sliderGroup.call(setGripPosition, sliderOpts, doTransition); + sliderGroup.call(drawCurrentValue, sliderOpts); + + gd.emit('plotly_sliderchange', { + slider: sliderOpts, + step: sliderOpts.steps[sliderOpts.active], + interaction: doCallback, + previousActive: previousActive + }); + + if(step && step.method && doCallback) { + if(sliderGroup._nextMethod) { + // If we've already queued up an update, just overwrite it with the most recent: + sliderGroup._nextMethod.step = step; + sliderGroup._nextMethod.doCallback = doCallback; + sliderGroup._nextMethod.doTransition = doTransition; + } else { + sliderGroup._nextMethod = {step: step, doCallback: doCallback, doTransition: doTransition}; + sliderGroup._nextMethodRaf = window.requestAnimationFrame(function() { + var _step = sliderGroup._nextMethod.step; + if(!_step.method) return; + + if(_step.execute) { + Plots.executeAPICommand(gd, _step.method, _step.args); + } + + sliderGroup._nextMethod = null; + sliderGroup._nextMethodRaf = null; + }); + } + } +} + +function attachGripEvents(item, gd, sliderGroup) { + var node = sliderGroup.node(); + var $gd = d3.select(gd); + + // NB: This is *not* the same as sliderOpts itself! These callbacks + // are in a closure so this array won't actually be correct if the + // steps have changed since this was initialized. The sliderGroup, + // however, has not changed since that *is* the slider, so it must + // be present to receive mouse events. + function getSliderOpts() { + return sliderGroup.data()[0]; + } + + item.on('mousedown', function() { + var sliderOpts = getSliderOpts(); + gd.emit('plotly_sliderstart', {slider: sliderOpts}); + + var grip = sliderGroup.select('.' + constants.gripRectClass); + + d3.event.stopPropagation(); + d3.event.preventDefault(); + grip.call(Color.fill, sliderOpts.activebgcolor); + + var normalizedPosition = positionToNormalizedValue(sliderOpts, d3.mouse(node)[0]); + handleInput(gd, sliderGroup, sliderOpts, normalizedPosition, true); + sliderOpts._dragging = true; + + $gd.on('mousemove', function() { + var sliderOpts = getSliderOpts(); + var normalizedPosition = positionToNormalizedValue(sliderOpts, d3.mouse(node)[0]); + handleInput(gd, sliderGroup, sliderOpts, normalizedPosition, false); + }); + + $gd.on('mouseup', function() { + var sliderOpts = getSliderOpts(); + sliderOpts._dragging = false; + grip.call(Color.fill, sliderOpts.bgcolor); + $gd.on('mouseup', null); + $gd.on('mousemove', null); + + gd.emit('plotly_sliderend', { + slider: sliderOpts, + step: sliderOpts.steps[sliderOpts.active] + }); + }); + }); +} + +function drawTicks(sliderGroup, sliderOpts) { + var tick = sliderGroup.selectAll('rect.' + constants.tickRectClass) + .data(sliderOpts._visibleSteps); + var dims = sliderOpts._dims; + + tick.enter().append('rect') + .classed(constants.tickRectClass, true); + + tick.exit().remove(); + + tick.attr({ + width: sliderOpts.tickwidth + 'px', + 'shape-rendering': 'crispEdges' + }); + + tick.each(function(d, i) { + var isMajor = i % dims.labelStride === 0; + var item = d3.select(this); + + item + .attr({height: isMajor ? sliderOpts.ticklen : sliderOpts.minorticklen}) + .call(Color.fill, isMajor ? sliderOpts.tickcolor : sliderOpts.tickcolor); + + Drawing.setTranslate(item, + normalizedValueToPosition(sliderOpts, i / (sliderOpts._stepCount - 1)) - 0.5 * sliderOpts.tickwidth, + (isMajor ? constants.tickOffset : constants.minorTickOffset) + dims.currentValueTotalHeight + ); + }); +} + +function computeLabelSteps(sliderOpts) { + var dims = sliderOpts._dims; + dims.labelSteps = []; + var nsteps = sliderOpts._stepCount; + + for(var i = 0; i < nsteps; i += dims.labelStride) { + dims.labelSteps.push({ + fraction: i / (nsteps - 1), + step: sliderOpts._visibleSteps[i] + }); + } +} + +function setGripPosition(sliderGroup, sliderOpts, doTransition) { + var grip = sliderGroup.select('rect.' + constants.gripRectClass); + + var quantizedIndex = 0; + for(var i = 0; i < sliderOpts._stepCount; i++) { + if(sliderOpts._visibleSteps[i]._index === sliderOpts.active) { + quantizedIndex = i; + break; + } + } + + var x = normalizedValueToPosition(sliderOpts, quantizedIndex / (sliderOpts._stepCount - 1)); + + // If this is true, then *this component* is already invoking its own command + // and has triggered its own animation. + if(sliderOpts._invokingCommand) return; + + var el = grip; + if(doTransition && sliderOpts.transition.duration > 0) { + el = el.transition() + .duration(sliderOpts.transition.duration) + .ease(sliderOpts.transition.easing); + } + + // Drawing.setTranslate doesn't work here becasue of the transition duck-typing. + // It's also not necessary because there are no other transitions to preserve. + el.attr('transform', 'translate(' + (x - constants.gripWidth * 0.5) + ',' + (sliderOpts._dims.currentValueTotalHeight) + ')'); +} + +// Convert a number from [0-1] to a pixel position relative to the slider group container: +function normalizedValueToPosition(sliderOpts, normalizedPosition) { + var dims = sliderOpts._dims; + return dims.inputAreaStart + constants.stepInset + + (dims.inputAreaLength - 2 * constants.stepInset) * Math.min(1, Math.max(0, normalizedPosition)); +} + +// Convert a position relative to the slider group to a nubmer in [0, 1] +function positionToNormalizedValue(sliderOpts, position) { + var dims = sliderOpts._dims; + return Math.min(1, Math.max(0, (position - constants.stepInset - dims.inputAreaStart) / (dims.inputAreaLength - 2 * constants.stepInset - 2 * dims.inputAreaStart))); +} + +function drawTouchRect(sliderGroup, gd, sliderOpts) { + var dims = sliderOpts._dims; + var rect = Lib.ensureSingle(sliderGroup, 'rect', constants.railTouchRectClass, function(s) { + s.call(attachGripEvents, gd, sliderGroup, sliderOpts) + .style('pointer-events', 'all'); + }); + + rect.attr({ + width: dims.inputAreaLength, + height: Math.max(dims.inputAreaWidth, constants.tickOffset + sliderOpts.ticklen + dims.labelHeight) + }) + .call(Color.fill, sliderOpts.bgcolor) + .attr('opacity', 0); + + Drawing.setTranslate(rect, 0, dims.currentValueTotalHeight); +} + +function drawRail(sliderGroup, sliderOpts) { + var dims = sliderOpts._dims; + var computedLength = dims.inputAreaLength - constants.railInset * 2; + var rect = Lib.ensureSingle(sliderGroup, 'rect', constants.railRectClass); + + rect.attr({ + width: computedLength, + height: constants.railWidth, + rx: constants.railRadius, + ry: constants.railRadius, + 'shape-rendering': 'crispEdges' + }) + .call(Color.stroke, sliderOpts.bordercolor) + .call(Color.fill, sliderOpts.bgcolor) + .style('stroke-width', sliderOpts.borderwidth + 'px'); + + Drawing.setTranslate(rect, + constants.railInset, + (dims.inputAreaWidth - constants.railWidth) * 0.5 + dims.currentValueTotalHeight + ); +} + +},{"../../constants/alignment":145,"../../lib":169,"../../lib/svg_text_utils":190,"../../plot_api/plot_template":203,"../../plots/plots":245,"../color":51,"../drawing":72,"./constants":134,"d3":16}],137:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var constants = _dereq_('./constants'); + +module.exports = { + moduleType: 'component', + name: constants.name, + + layoutAttributes: _dereq_('./attributes'), + supplyLayoutDefaults: _dereq_('./defaults'), + + draw: _dereq_('./draw') +}; + +},{"./attributes":133,"./constants":134,"./defaults":135,"./draw":136}],138:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var d3 = _dereq_('d3'); +var isNumeric = _dereq_('fast-isnumeric'); + +var Plots = _dereq_('../../plots/plots'); +var Registry = _dereq_('../../registry'); +var Lib = _dereq_('../../lib'); +var Drawing = _dereq_('../drawing'); +var Color = _dereq_('../color'); +var svgTextUtils = _dereq_('../../lib/svg_text_utils'); +var interactConstants = _dereq_('../../constants/interactions'); + +var OPPOSITE_SIDE = _dereq_('../../constants/alignment').OPPOSITE_SIDE; +var numStripRE = / [XY][0-9]* /; + +/** + * Titles - (re)draw titles on the axes and plot: + * @param {DOM element} gd - the graphDiv + * @param {string} titleClass - the css class of this title + * @param {object} options - how and what to draw + * propContainer - the layout object containing `title` and `titlefont` + * attributes that apply to this title + * propName - the full name of the title property (for Plotly.relayout) + * [traceIndex] - include only if this property applies to one trace + * (such as a colorbar title) - then editing pipes to Plotly.restyle + * instead of Plotly.relayout + * placeholder - placeholder text for an empty editable title + * [avoid] {object} - include if this title should move to avoid other elements + * selection - d3 selection of elements to avoid + * side - which direction to move if there is a conflict + * [offsetLeft] - if these elements are subject to a translation + * wrt the title element + * [offsetTop] + * attributes {object} - position and alignment attributes + * x - pixels + * y - pixels + * text-anchor - start|middle|end + * transform {object} - how to transform the title after positioning + * rotate - degrees + * offset - shift up/down in the rotated frame (unused?) + * containerGroup - if an svg element already exists to hold this + * title, include here. Otherwise it will go in fullLayout._infolayer + * _meta {object (optional} - meta key-value to for title with + * Lib.templateString, default to fullLayout._meta, if not provided + * + * @return {selection} d3 selection of title container group + */ +function draw(gd, titleClass, options) { + var cont = options.propContainer; + var prop = options.propName; + var placeholder = options.placeholder; + var traceIndex = options.traceIndex; + var avoid = options.avoid || {}; + var attributes = options.attributes; + var transform = options.transform; + var group = options.containerGroup; + + var fullLayout = gd._fullLayout; + + var opacity = 1; + var isplaceholder = false; + var title = cont.title; + var txt = (title && title.text ? title.text : '').trim(); + + var font = title && title.font ? title.font : {}; + var fontFamily = font.family; + var fontSize = font.size; + var fontColor = font.color; + + // only make this title editable if we positively identify its property + // as one that has editing enabled. + var editAttr; + if(prop === 'title.text') editAttr = 'titleText'; + else if(prop.indexOf('axis') !== -1) editAttr = 'axisTitleText'; + else if(prop.indexOf('colorbar' !== -1)) editAttr = 'colorbarTitleText'; + var editable = gd._context.edits[editAttr]; + + if(txt === '') opacity = 0; + // look for placeholder text while stripping out numbers from eg X2, Y3 + // this is just for backward compatibility with the old version that had + // "Click to enter X2 title" and may have gotten saved in some old plots, + // we don't want this to show up when these are displayed. + else if(txt.replace(numStripRE, ' % ') === placeholder.replace(numStripRE, ' % ')) { + opacity = 0.2; + isplaceholder = true; + if(!editable) txt = ''; + } + + if(options._meta) { + txt = Lib.templateString(txt, options._meta); + } else if(fullLayout._meta) { + txt = Lib.templateString(txt, fullLayout._meta); + } + + var elShouldExist = txt || editable; + + if(!group) { + group = Lib.ensureSingle(fullLayout._infolayer, 'g', 'g-' + titleClass); + } + + var el = group.selectAll('text') + .data(elShouldExist ? [0] : []); + el.enter().append('text'); + el.text(txt) + // this is hacky, but convertToTspans uses the class + // to determine whether to rotate mathJax... + // so we need to clear out any old class and put the + // correct one (only relevant for colorbars, at least + // for now) - ie don't use .classed + .attr('class', titleClass); + el.exit().remove(); + + if(!elShouldExist) return group; + + function titleLayout(titleEl) { + Lib.syncOrAsync([drawTitle, scootTitle], titleEl); + } + + function drawTitle(titleEl) { + var transformVal; + + if(transform) { + transformVal = ''; + if(transform.rotate) { + transformVal += 'rotate(' + [transform.rotate, attributes.x, attributes.y] + ')'; + } + if(transform.offset) { + transformVal += 'translate(0, ' + transform.offset + ')'; + } + } else { + transformVal = null; + } + + titleEl.attr('transform', transformVal); + + titleEl.style({ + 'font-family': fontFamily, + 'font-size': d3.round(fontSize, 2) + 'px', + fill: Color.rgb(fontColor), + opacity: opacity * Color.opacity(fontColor), + 'font-weight': Plots.fontWeight + }) + .attr(attributes) + .call(svgTextUtils.convertToTspans, gd); + + return Plots.previousPromises(gd); + } + + function scootTitle(titleElIn) { + var titleGroup = d3.select(titleElIn.node().parentNode); + + if(avoid && avoid.selection && avoid.side && txt) { + titleGroup.attr('transform', null); + + // move toward avoid.side (= left, right, top, bottom) if needed + // can include pad (pixels, default 2) + var backside = OPPOSITE_SIDE[avoid.side]; + var shiftSign = (avoid.side === 'left' || avoid.side === 'top') ? -1 : 1; + var pad = isNumeric(avoid.pad) ? avoid.pad : 2; + + var titlebb = Drawing.bBox(titleGroup.node()); + var paperbb = { + left: 0, + top: 0, + right: fullLayout.width, + bottom: fullLayout.height + }; + + var maxshift = avoid.maxShift || + shiftSign * (paperbb[avoid.side] - titlebb[avoid.side]); + var shift = 0; + + // Prevent the title going off the paper + if(maxshift < 0) { + shift = maxshift; + } else { + // so we don't have to offset each avoided element, + // give the title the opposite offset + var offsetLeft = avoid.offsetLeft || 0; + var offsetTop = avoid.offsetTop || 0; + titlebb.left -= offsetLeft; + titlebb.right -= offsetLeft; + titlebb.top -= offsetTop; + titlebb.bottom -= offsetTop; + + // iterate over a set of elements (avoid.selection) + // to avoid collisions with + avoid.selection.each(function() { + var avoidbb = Drawing.bBox(this); + + if(Lib.bBoxIntersect(titlebb, avoidbb, pad)) { + shift = Math.max(shift, shiftSign * ( + avoidbb[avoid.side] - titlebb[backside]) + pad); + } + }); + shift = Math.min(maxshift, shift); + } + + if(shift > 0 || maxshift < 0) { + var shiftTemplate = { + left: [-shift, 0], + right: [shift, 0], + top: [0, -shift], + bottom: [0, shift] + }[avoid.side]; + titleGroup.attr('transform', 'translate(' + shiftTemplate + ')'); + } + } + } + + el.call(titleLayout); + + function setPlaceholder() { + opacity = 0; + isplaceholder = true; + el.text(placeholder) + .on('mouseover.opacity', function() { + d3.select(this).transition() + .duration(interactConstants.SHOW_PLACEHOLDER).style('opacity', 1); + }) + .on('mouseout.opacity', function() { + d3.select(this).transition() + .duration(interactConstants.HIDE_PLACEHOLDER).style('opacity', 0); + }); + } + + if(editable) { + if(!txt) setPlaceholder(); + else el.on('.opacity', null); + + el.call(svgTextUtils.makeEditable, {gd: gd}) + .on('edit', function(text) { + if(traceIndex !== undefined) { + Registry.call('_guiRestyle', gd, prop, text, traceIndex); + } else { + Registry.call('_guiRelayout', gd, prop, text); + } + }) + .on('cancel', function() { + this.text(this.attr('data-unformatted')) + .call(titleLayout); + }) + .on('input', function(d) { + this.text(d || ' ') + .call(svgTextUtils.positionText, attributes.x, attributes.y); + }); + } + el.classed('js-placeholder', isplaceholder); + + return group; +} + +module.exports = { + draw: draw +}; + +},{"../../constants/alignment":145,"../../constants/interactions":148,"../../lib":169,"../../lib/svg_text_utils":190,"../../plots/plots":245,"../../registry":258,"../color":51,"../drawing":72,"d3":16,"fast-isnumeric":18}],139:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var fontAttrs = _dereq_('../../plots/font_attributes'); +var colorAttrs = _dereq_('../color/attributes'); +var extendFlat = _dereq_('../../lib/extend').extendFlat; +var overrideAll = _dereq_('../../plot_api/edit_types').overrideAll; +var padAttrs = _dereq_('../../plots/pad_attributes'); +var templatedArray = _dereq_('../../plot_api/plot_template').templatedArray; + +var buttonsAttrs = templatedArray('button', { + visible: { + valType: 'boolean', + + + }, + method: { + valType: 'enumerated', + values: ['restyle', 'relayout', 'animate', 'update', 'skip'], + dflt: 'restyle', + + + }, + args: { + valType: 'info_array', + + freeLength: true, + items: [ + {valType: 'any'}, + {valType: 'any'}, + {valType: 'any'} + ], + + }, + args2: { + valType: 'info_array', + + freeLength: true, + items: [ + {valType: 'any'}, + {valType: 'any'}, + {valType: 'any'} + ], + + }, + label: { + valType: 'string', + + dflt: '', + + }, + execute: { + valType: 'boolean', + + dflt: true, + + } +}); + +module.exports = overrideAll(templatedArray('updatemenu', { + _arrayAttrRegexps: [/^updatemenus\[(0|[1-9][0-9]+)\]\.buttons/], + + visible: { + valType: 'boolean', + + + }, + + type: { + valType: 'enumerated', + values: ['dropdown', 'buttons'], + dflt: 'dropdown', + + + }, + + direction: { + valType: 'enumerated', + values: ['left', 'right', 'up', 'down'], + dflt: 'down', + + + }, + + active: { + valType: 'integer', + + min: -1, + dflt: 0, + + }, + + showactive: { + valType: 'boolean', + + dflt: true, + + }, + + buttons: buttonsAttrs, + + x: { + valType: 'number', + min: -2, + max: 3, + dflt: -0.05, + + + }, + xanchor: { + valType: 'enumerated', + values: ['auto', 'left', 'center', 'right'], + dflt: 'right', + + + }, + y: { + valType: 'number', + min: -2, + max: 3, + dflt: 1, + + + }, + yanchor: { + valType: 'enumerated', + values: ['auto', 'top', 'middle', 'bottom'], + dflt: 'top', + + + }, + + pad: extendFlat(padAttrs({editType: 'arraydraw'}), { + + }), + + font: fontAttrs({ + + }), + + bgcolor: { + valType: 'color', + + + }, + bordercolor: { + valType: 'color', + dflt: colorAttrs.borderLine, + + + }, + borderwidth: { + valType: 'number', + min: 0, + dflt: 1, + + editType: 'arraydraw', + + } +}), 'arraydraw', 'from-root'); + +},{"../../lib/extend":164,"../../plot_api/edit_types":196,"../../plot_api/plot_template":203,"../../plots/font_attributes":239,"../../plots/pad_attributes":244,"../color/attributes":50}],140:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + + +module.exports = { + + // layout attribute name + name: 'updatemenus', + + // class names + containerClassName: 'updatemenu-container', + headerGroupClassName: 'updatemenu-header-group', + headerClassName: 'updatemenu-header', + headerArrowClassName: 'updatemenu-header-arrow', + dropdownButtonGroupClassName: 'updatemenu-dropdown-button-group', + dropdownButtonClassName: 'updatemenu-dropdown-button', + buttonClassName: 'updatemenu-button', + itemRectClassName: 'updatemenu-item-rect', + itemTextClassName: 'updatemenu-item-text', + + // DOM attribute name in button group keeping track + // of active update menu + menuIndexAttrName: 'updatemenu-active-index', + + // id root pass to Plots.autoMargin + autoMarginIdRoot: 'updatemenu-', + + // options when 'active: -1' + blankHeaderOpts: { label: ' ' }, + + // min item width / height + minWidth: 30, + minHeight: 30, + + // padding around item text + textPadX: 24, + arrowPadX: 16, + + // item rect radii + rx: 2, + ry: 2, + + // item text x offset off left edge + textOffsetX: 12, + + // item text y offset (w.r.t. middle) + textOffsetY: 3, + + // arrow offset off right edge + arrowOffsetX: 4, + + // gap between header and buttons + gapButtonHeader: 5, + + // gap between between buttons + gapButton: 2, + + // color given to active buttons + activeColor: '#F4FAFF', + + // color given to hovered buttons + hoverColor: '#F4FAFF', + + // symbol for menu open arrow + arrowSymbol: { + left: '◄', + right: '►', + up: '▲', + down: '▼' + } +}; + +},{}],141:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var handleArrayContainerDefaults = _dereq_('../../plots/array_container_defaults'); + +var attributes = _dereq_('./attributes'); +var constants = _dereq_('./constants'); + +var name = constants.name; +var buttonAttrs = attributes.buttons; + + +module.exports = function updateMenusDefaults(layoutIn, layoutOut) { + var opts = { + name: name, + handleItemDefaults: menuDefaults + }; + + handleArrayContainerDefaults(layoutIn, layoutOut, opts); +}; + +function menuDefaults(menuIn, menuOut, layoutOut) { + function coerce(attr, dflt) { + return Lib.coerce(menuIn, menuOut, attributes, attr, dflt); + } + + var buttons = handleArrayContainerDefaults(menuIn, menuOut, { + name: 'buttons', + handleItemDefaults: buttonDefaults + }); + + var visible = coerce('visible', buttons.length > 0); + if(!visible) return; + + coerce('active'); + coerce('direction'); + coerce('type'); + coerce('showactive'); + + coerce('x'); + coerce('y'); + Lib.noneOrAll(menuIn, menuOut, ['x', 'y']); + + coerce('xanchor'); + coerce('yanchor'); + + coerce('pad.t'); + coerce('pad.r'); + coerce('pad.b'); + coerce('pad.l'); + + Lib.coerceFont(coerce, 'font', layoutOut.font); + + coerce('bgcolor', layoutOut.paper_bgcolor); + coerce('bordercolor'); + coerce('borderwidth'); +} + +function buttonDefaults(buttonIn, buttonOut) { + function coerce(attr, dflt) { + return Lib.coerce(buttonIn, buttonOut, buttonAttrs, attr, dflt); + } + + var visible = coerce('visible', + (buttonIn.method === 'skip' || Array.isArray(buttonIn.args))); + if(visible) { + coerce('method'); + coerce('args'); + coerce('args2'); + coerce('label'); + coerce('execute'); + } +} + +},{"../../lib":169,"../../plots/array_container_defaults":209,"./attributes":139,"./constants":140}],142:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var d3 = _dereq_('d3'); + +var Plots = _dereq_('../../plots/plots'); +var Color = _dereq_('../color'); +var Drawing = _dereq_('../drawing'); +var Lib = _dereq_('../../lib'); +var svgTextUtils = _dereq_('../../lib/svg_text_utils'); +var arrayEditor = _dereq_('../../plot_api/plot_template').arrayEditor; + +var LINE_SPACING = _dereq_('../../constants/alignment').LINE_SPACING; + +var constants = _dereq_('./constants'); +var ScrollBox = _dereq_('./scrollbox'); + +module.exports = function draw(gd) { + var fullLayout = gd._fullLayout; + var menuData = Lib.filterVisible(fullLayout[constants.name]); + + /* Update menu data is bound to the header-group. + * The items in the header group are always present. + * + * Upon clicking on a header its corresponding button + * data is bound to the button-group. + * + * We draw all headers in one group before all buttons + * so that the buttons *always* appear above the headers. + * + * Note that only one set of buttons are visible at once. + * + * + * + * + * + * + * + * + * + * ... + * + * + * + * + * ... + */ + + function clearAutoMargin(menuOpts) { + Plots.autoMargin(gd, autoMarginId(menuOpts)); + } + + // draw update menu container + var menus = fullLayout._menulayer + .selectAll('g.' + constants.containerClassName) + .data(menuData.length > 0 ? [0] : []); + + menus.enter().append('g') + .classed(constants.containerClassName, true) + .style('cursor', 'pointer'); + + menus.exit().each(function() { + // Most components don't need to explicitly remove autoMargin, because + // marginPushers does this - but updatemenu updates don't go through + // a full replot so we need to explicitly remove it. + // This is for removing *all* updatemenus, removing individuals is + // handled below, in headerGroups.exit + d3.select(this).selectAll('g.' + constants.headerGroupClassName) + .each(clearAutoMargin); + }).remove(); + + // return early if no update menus are visible + if(menuData.length === 0) return; + + // join header group + var headerGroups = menus.selectAll('g.' + constants.headerGroupClassName) + .data(menuData, keyFunction); + + headerGroups.enter().append('g') + .classed(constants.headerGroupClassName, true); + + // draw dropdown button container + var gButton = Lib.ensureSingle(menus, 'g', constants.dropdownButtonGroupClassName, function(s) { + s.style('pointer-events', 'all'); + }); + + // find dimensions before plotting anything (this mutates menuOpts) + for(var i = 0; i < menuData.length; i++) { + var menuOpts = menuData[i]; + findDimensions(gd, menuOpts); + } + + // setup scrollbox + var scrollBoxId = 'updatemenus' + fullLayout._uid; + var scrollBox = new ScrollBox(gd, gButton, scrollBoxId); + + // remove exiting header, remove dropped buttons and reset margins + if(headerGroups.enter().size()) { + // make sure gButton is on top of all headers + gButton.node().parentNode.appendChild(gButton.node()); + gButton.call(removeAllButtons); + } + + headerGroups.exit().each(function(menuOpts) { + gButton.call(removeAllButtons); + clearAutoMargin(menuOpts); + }).remove(); + + // draw headers! + headerGroups.each(function(menuOpts) { + var gHeader = d3.select(this); + + var _gButton = menuOpts.type === 'dropdown' ? gButton : null; + + Plots.manageCommandObserver(gd, menuOpts, menuOpts.buttons, function(data) { + setActive(gd, menuOpts, menuOpts.buttons[data.index], gHeader, _gButton, scrollBox, data.index, true); + }); + + if(menuOpts.type === 'dropdown') { + drawHeader(gd, gHeader, gButton, scrollBox, menuOpts); + + // if this menu is active, update the dropdown container + if(isActive(gButton, menuOpts)) { + drawButtons(gd, gHeader, gButton, scrollBox, menuOpts); + } + } else { + drawButtons(gd, gHeader, null, null, menuOpts); + } + }); +}; + +// Note that '_index' is set at the default step, +// it corresponds to the menu index in the user layout update menu container. +// Because a menu can be set invisible, +// this is a more 'consistent' field than the index in the menuData. +function keyFunction(menuOpts) { + return menuOpts._index; +} + +function isFolded(gButton) { + return +gButton.attr(constants.menuIndexAttrName) === -1; +} + +function isActive(gButton, menuOpts) { + return +gButton.attr(constants.menuIndexAttrName) === menuOpts._index; +} + +function setActive(gd, menuOpts, buttonOpts, gHeader, gButton, scrollBox, buttonIndex, isSilentUpdate) { + // update 'active' attribute in menuOpts + menuOpts.active = buttonIndex; + + // due to templating, it's possible this slider doesn't even exist yet + arrayEditor(gd.layout, constants.name, menuOpts) + .applyUpdate('active', buttonIndex); + + if(menuOpts.type === 'buttons') { + drawButtons(gd, gHeader, null, null, menuOpts); + } else if(menuOpts.type === 'dropdown') { + // fold up buttons and redraw header + gButton.attr(constants.menuIndexAttrName, '-1'); + + drawHeader(gd, gHeader, gButton, scrollBox, menuOpts); + + if(!isSilentUpdate) { + drawButtons(gd, gHeader, gButton, scrollBox, menuOpts); + } + } +} + +function drawHeader(gd, gHeader, gButton, scrollBox, menuOpts) { + var header = Lib.ensureSingle(gHeader, 'g', constants.headerClassName, function(s) { + s.style('pointer-events', 'all'); + }); + + var dims = menuOpts._dims; + var active = menuOpts.active; + var headerOpts = menuOpts.buttons[active] || constants.blankHeaderOpts; + var posOpts = { y: menuOpts.pad.t, yPad: 0, x: menuOpts.pad.l, xPad: 0, index: 0 }; + var positionOverrides = { + width: dims.headerWidth, + height: dims.headerHeight + }; + + header + .call(drawItem, menuOpts, headerOpts, gd) + .call(setItemPosition, menuOpts, posOpts, positionOverrides); + + // draw drop arrow at the right edge + var arrow = Lib.ensureSingle(gHeader, 'text', constants.headerArrowClassName, function(s) { + s.classed('user-select-none', true) + .attr('text-anchor', 'end') + .call(Drawing.font, menuOpts.font) + .text(constants.arrowSymbol[menuOpts.direction]); + }); + + arrow.attr({ + x: dims.headerWidth - constants.arrowOffsetX + menuOpts.pad.l, + y: dims.headerHeight / 2 + constants.textOffsetY + menuOpts.pad.t + }); + + header.on('click', function() { + gButton.call(removeAllButtons, + String(isActive(gButton, menuOpts) ? -1 : menuOpts._index) + ); + + drawButtons(gd, gHeader, gButton, scrollBox, menuOpts); + }); + + header.on('mouseover', function() { + header.call(styleOnMouseOver); + }); + + header.on('mouseout', function() { + header.call(styleOnMouseOut, menuOpts); + }); + + // translate header group + Drawing.setTranslate(gHeader, dims.lx, dims.ly); +} + +function drawButtons(gd, gHeader, gButton, scrollBox, menuOpts) { + // If this is a set of buttons, set pointer events = all since we play + // some minor games with which container is which in order to simplify + // the drawing of *either* buttons or menus + if(!gButton) { + gButton = gHeader; + gButton.attr('pointer-events', 'all'); + } + + var buttonData = (!isFolded(gButton) || menuOpts.type === 'buttons') ? + menuOpts.buttons : + []; + + var klass = menuOpts.type === 'dropdown' ? constants.dropdownButtonClassName : constants.buttonClassName; + + var buttons = gButton.selectAll('g.' + klass) + .data(Lib.filterVisible(buttonData)); + + var enter = buttons.enter().append('g') + .classed(klass, true); + + var exit = buttons.exit(); + + if(menuOpts.type === 'dropdown') { + enter.attr('opacity', '0') + .transition() + .attr('opacity', '1'); + + exit.transition() + .attr('opacity', '0') + .remove(); + } else { + exit.remove(); + } + + var x0 = 0; + var y0 = 0; + var dims = menuOpts._dims; + + var isVertical = ['up', 'down'].indexOf(menuOpts.direction) !== -1; + + if(menuOpts.type === 'dropdown') { + if(isVertical) { + y0 = dims.headerHeight + constants.gapButtonHeader; + } else { + x0 = dims.headerWidth + constants.gapButtonHeader; + } + } + + if(menuOpts.type === 'dropdown' && menuOpts.direction === 'up') { + y0 = -constants.gapButtonHeader + constants.gapButton - dims.openHeight; + } + + if(menuOpts.type === 'dropdown' && menuOpts.direction === 'left') { + x0 = -constants.gapButtonHeader + constants.gapButton - dims.openWidth; + } + + var posOpts = { + x: dims.lx + x0 + menuOpts.pad.l, + y: dims.ly + y0 + menuOpts.pad.t, + yPad: constants.gapButton, + xPad: constants.gapButton, + index: 0, + }; + + var scrollBoxPosition = { + l: posOpts.x + menuOpts.borderwidth, + t: posOpts.y + menuOpts.borderwidth + }; + + buttons.each(function(buttonOpts, buttonIndex) { + var button = d3.select(this); + + button + .call(drawItem, menuOpts, buttonOpts, gd) + .call(setItemPosition, menuOpts, posOpts); + + button.on('click', function() { + // skip `dragend` events + if(d3.event.defaultPrevented) return; + + if(buttonOpts.execute) { + if(buttonOpts.args2 && menuOpts.active === buttonIndex) { + setActive(gd, menuOpts, buttonOpts, gHeader, gButton, scrollBox, -1); + Plots.executeAPICommand(gd, buttonOpts.method, buttonOpts.args2); + } else { + setActive(gd, menuOpts, buttonOpts, gHeader, gButton, scrollBox, buttonIndex); + Plots.executeAPICommand(gd, buttonOpts.method, buttonOpts.args); + } + } + + gd.emit('plotly_buttonclicked', {menu: menuOpts, button: buttonOpts, active: menuOpts.active}); + }); + + button.on('mouseover', function() { + button.call(styleOnMouseOver); + }); + + button.on('mouseout', function() { + button.call(styleOnMouseOut, menuOpts); + buttons.call(styleButtons, menuOpts); + }); + }); + + buttons.call(styleButtons, menuOpts); + + if(isVertical) { + scrollBoxPosition.w = Math.max(dims.openWidth, dims.headerWidth); + scrollBoxPosition.h = posOpts.y - scrollBoxPosition.t; + } else { + scrollBoxPosition.w = posOpts.x - scrollBoxPosition.l; + scrollBoxPosition.h = Math.max(dims.openHeight, dims.headerHeight); + } + + scrollBoxPosition.direction = menuOpts.direction; + + if(scrollBox) { + if(buttons.size()) { + drawScrollBox(gd, gHeader, gButton, scrollBox, menuOpts, scrollBoxPosition); + } else { + hideScrollBox(scrollBox); + } + } +} + +function drawScrollBox(gd, gHeader, gButton, scrollBox, menuOpts, position) { + // enable the scrollbox + var direction = menuOpts.direction; + var isVertical = (direction === 'up' || direction === 'down'); + var dims = menuOpts._dims; + + var active = menuOpts.active; + var translateX, translateY; + var i; + if(isVertical) { + translateY = 0; + for(i = 0; i < active; i++) { + translateY += dims.heights[i] + constants.gapButton; + } + } else { + translateX = 0; + for(i = 0; i < active; i++) { + translateX += dims.widths[i] + constants.gapButton; + } + } + + scrollBox.enable(position, translateX, translateY); + + if(scrollBox.hbar) { + scrollBox.hbar + .attr('opacity', '0') + .transition() + .attr('opacity', '1'); + } + + if(scrollBox.vbar) { + scrollBox.vbar + .attr('opacity', '0') + .transition() + .attr('opacity', '1'); + } +} + +function hideScrollBox(scrollBox) { + var hasHBar = !!scrollBox.hbar; + var hasVBar = !!scrollBox.vbar; + + if(hasHBar) { + scrollBox.hbar + .transition() + .attr('opacity', '0') + .each('end', function() { + hasHBar = false; + if(!hasVBar) scrollBox.disable(); + }); + } + + if(hasVBar) { + scrollBox.vbar + .transition() + .attr('opacity', '0') + .each('end', function() { + hasVBar = false; + if(!hasHBar) scrollBox.disable(); + }); + } +} + +function drawItem(item, menuOpts, itemOpts, gd) { + item.call(drawItemRect, menuOpts) + .call(drawItemText, menuOpts, itemOpts, gd); +} + +function drawItemRect(item, menuOpts) { + var rect = Lib.ensureSingle(item, 'rect', constants.itemRectClassName, function(s) { + s.attr({ + rx: constants.rx, + ry: constants.ry, + 'shape-rendering': 'crispEdges' + }); + }); + + rect.call(Color.stroke, menuOpts.bordercolor) + .call(Color.fill, menuOpts.bgcolor) + .style('stroke-width', menuOpts.borderwidth + 'px'); +} + +function drawItemText(item, menuOpts, itemOpts, gd) { + var text = Lib.ensureSingle(item, 'text', constants.itemTextClassName, function(s) { + s.classed('user-select-none', true) + .attr({ + 'text-anchor': 'start', + 'data-notex': 1 + }); + }); + + var tx = itemOpts.label; + var _meta = gd._fullLayout._meta; + if(_meta) tx = Lib.templateString(tx, _meta); + + text.call(Drawing.font, menuOpts.font) + .text(tx) + .call(svgTextUtils.convertToTspans, gd); +} + +function styleButtons(buttons, menuOpts) { + var active = menuOpts.active; + + buttons.each(function(buttonOpts, i) { + var button = d3.select(this); + + if(i === active && menuOpts.showactive) { + button.select('rect.' + constants.itemRectClassName) + .call(Color.fill, constants.activeColor); + } + }); +} + +function styleOnMouseOver(item) { + item.select('rect.' + constants.itemRectClassName) + .call(Color.fill, constants.hoverColor); +} + +function styleOnMouseOut(item, menuOpts) { + item.select('rect.' + constants.itemRectClassName) + .call(Color.fill, menuOpts.bgcolor); +} + +// find item dimensions (this mutates menuOpts) +function findDimensions(gd, menuOpts) { + var dims = menuOpts._dims = { + width1: 0, + height1: 0, + heights: [], + widths: [], + totalWidth: 0, + totalHeight: 0, + openWidth: 0, + openHeight: 0, + lx: 0, + ly: 0 + }; + + var fakeButtons = Drawing.tester.selectAll('g.' + constants.dropdownButtonClassName) + .data(Lib.filterVisible(menuOpts.buttons)); + + fakeButtons.enter().append('g') + .classed(constants.dropdownButtonClassName, true); + + var isVertical = ['up', 'down'].indexOf(menuOpts.direction) !== -1; + + // loop over fake buttons to find width / height + fakeButtons.each(function(buttonOpts, i) { + var button = d3.select(this); + + button.call(drawItem, menuOpts, buttonOpts, gd); + + var text = button.select('.' + constants.itemTextClassName); + + // width is given by max width of all buttons + var tWidth = text.node() && Drawing.bBox(text.node()).width; + var wEff = Math.max(tWidth + constants.textPadX, constants.minWidth); + + // height is determined by item text + var tHeight = menuOpts.font.size * LINE_SPACING; + var tLines = svgTextUtils.lineCount(text); + var hEff = Math.max(tHeight * tLines, constants.minHeight) + constants.textOffsetY; + + hEff = Math.ceil(hEff); + wEff = Math.ceil(wEff); + + // Store per-item sizes since a row of horizontal buttons, for example, + // don't all need to be the same width: + dims.widths[i] = wEff; + dims.heights[i] = hEff; + + // Height and width of individual element: + dims.height1 = Math.max(dims.height1, hEff); + dims.width1 = Math.max(dims.width1, wEff); + + if(isVertical) { + dims.totalWidth = Math.max(dims.totalWidth, wEff); + dims.openWidth = dims.totalWidth; + dims.totalHeight += hEff + constants.gapButton; + dims.openHeight += hEff + constants.gapButton; + } else { + dims.totalWidth += wEff + constants.gapButton; + dims.openWidth += wEff + constants.gapButton; + dims.totalHeight = Math.max(dims.totalHeight, hEff); + dims.openHeight = dims.totalHeight; + } + }); + + if(isVertical) { + dims.totalHeight -= constants.gapButton; + } else { + dims.totalWidth -= constants.gapButton; + } + + + dims.headerWidth = dims.width1 + constants.arrowPadX; + dims.headerHeight = dims.height1; + + if(menuOpts.type === 'dropdown') { + if(isVertical) { + dims.width1 += constants.arrowPadX; + dims.totalHeight = dims.height1; + } else { + dims.totalWidth = dims.width1; + } + dims.totalWidth += constants.arrowPadX; + } + + fakeButtons.remove(); + + var paddedWidth = dims.totalWidth + menuOpts.pad.l + menuOpts.pad.r; + var paddedHeight = dims.totalHeight + menuOpts.pad.t + menuOpts.pad.b; + + var graphSize = gd._fullLayout._size; + dims.lx = graphSize.l + graphSize.w * menuOpts.x; + dims.ly = graphSize.t + graphSize.h * (1 - menuOpts.y); + + var xanchor = 'left'; + if(Lib.isRightAnchor(menuOpts)) { + dims.lx -= paddedWidth; + xanchor = 'right'; + } + if(Lib.isCenterAnchor(menuOpts)) { + dims.lx -= paddedWidth / 2; + xanchor = 'center'; + } + + var yanchor = 'top'; + if(Lib.isBottomAnchor(menuOpts)) { + dims.ly -= paddedHeight; + yanchor = 'bottom'; + } + if(Lib.isMiddleAnchor(menuOpts)) { + dims.ly -= paddedHeight / 2; + yanchor = 'middle'; + } + + dims.totalWidth = Math.ceil(dims.totalWidth); + dims.totalHeight = Math.ceil(dims.totalHeight); + dims.lx = Math.round(dims.lx); + dims.ly = Math.round(dims.ly); + + Plots.autoMargin(gd, autoMarginId(menuOpts), { + x: menuOpts.x, + y: menuOpts.y, + l: paddedWidth * ({right: 1, center: 0.5}[xanchor] || 0), + r: paddedWidth * ({left: 1, center: 0.5}[xanchor] || 0), + b: paddedHeight * ({top: 1, middle: 0.5}[yanchor] || 0), + t: paddedHeight * ({bottom: 1, middle: 0.5}[yanchor] || 0) + }); +} + +function autoMarginId(menuOpts) { + return constants.autoMarginIdRoot + menuOpts._index; +} + +// set item positions (mutates posOpts) +function setItemPosition(item, menuOpts, posOpts, overrideOpts) { + overrideOpts = overrideOpts || {}; + var rect = item.select('.' + constants.itemRectClassName); + var text = item.select('.' + constants.itemTextClassName); + var borderWidth = menuOpts.borderwidth; + var index = posOpts.index; + var dims = menuOpts._dims; + + Drawing.setTranslate(item, borderWidth + posOpts.x, borderWidth + posOpts.y); + + var isVertical = ['up', 'down'].indexOf(menuOpts.direction) !== -1; + var finalHeight = overrideOpts.height || (isVertical ? dims.heights[index] : dims.height1); + + rect.attr({ + x: 0, + y: 0, + width: overrideOpts.width || (isVertical ? dims.width1 : dims.widths[index]), + height: finalHeight + }); + + var tHeight = menuOpts.font.size * LINE_SPACING; + var tLines = svgTextUtils.lineCount(text); + var spanOffset = ((tLines - 1) * tHeight / 2); + + svgTextUtils.positionText(text, constants.textOffsetX, + finalHeight / 2 - spanOffset + constants.textOffsetY); + + if(isVertical) { + posOpts.y += dims.heights[index] + posOpts.yPad; + } else { + posOpts.x += dims.widths[index] + posOpts.xPad; + } + + posOpts.index++; +} + +function removeAllButtons(gButton, newMenuIndexAttr) { + gButton + .attr(constants.menuIndexAttrName, newMenuIndexAttr || '-1') + .selectAll('g.' + constants.dropdownButtonClassName).remove(); +} + +},{"../../constants/alignment":145,"../../lib":169,"../../lib/svg_text_utils":190,"../../plot_api/plot_template":203,"../../plots/plots":245,"../color":51,"../drawing":72,"./constants":140,"./scrollbox":144,"d3":16}],143:[function(_dereq_,module,exports){ +arguments[4][137][0].apply(exports,arguments) +},{"./attributes":139,"./constants":140,"./defaults":141,"./draw":142,"dup":137}],144:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = ScrollBox; + +var d3 = _dereq_('d3'); + +var Color = _dereq_('../color'); +var Drawing = _dereq_('../drawing'); + +var Lib = _dereq_('../../lib'); + +/** + * Helper class to setup a scroll box + * + * @class + * @param gd Plotly's graph div + * @param container Container to be scroll-boxed (as a D3 selection) + * @param {string} id Id for the clip path to implement the scroll box + */ +function ScrollBox(gd, container, id) { + this.gd = gd; + this.container = container; + this.id = id; + + // See ScrollBox.prototype.enable for further definition + this.position = null; // scrollbox position + this.translateX = null; // scrollbox horizontal translation + this.translateY = null; // scrollbox vertical translation + this.hbar = null; // horizontal scrollbar D3 selection + this.vbar = null; // vertical scrollbar D3 selection + + // element to capture pointer events + this.bg = this.container.selectAll('rect.scrollbox-bg').data([0]); + + this.bg.exit() + .on('.drag', null) + .on('wheel', null) + .remove(); + + this.bg.enter().append('rect') + .classed('scrollbox-bg', true) + .style('pointer-events', 'all') + .attr({ + opacity: 0, + x: 0, + y: 0, + width: 0, + height: 0 + }); +} + +// scroll bar dimensions +ScrollBox.barWidth = 2; +ScrollBox.barLength = 20; +ScrollBox.barRadius = 2; +ScrollBox.barPad = 1; +ScrollBox.barColor = '#808BA4'; + +/** + * If needed, setup a clip path and scrollbars + * + * @method + * @param {Object} position + * @param {number} position.l Left side position (in pixels) + * @param {number} position.t Top side (in pixels) + * @param {number} position.w Width (in pixels) + * @param {number} position.h Height (in pixels) + * @param {string} [position.direction='down'] + * Either 'down', 'left', 'right' or 'up' + * @param {number} [translateX=0] Horizontal offset (in pixels) + * @param {number} [translateY=0] Vertical offset (in pixels) + */ +ScrollBox.prototype.enable = function enable(position, translateX, translateY) { + var fullLayout = this.gd._fullLayout; + var fullWidth = fullLayout.width; + var fullHeight = fullLayout.height; + + // compute position of scrollbox + this.position = position; + + var l = this.position.l; + var w = this.position.w; + var t = this.position.t; + var h = this.position.h; + var direction = this.position.direction; + var isDown = (direction === 'down'); + var isLeft = (direction === 'left'); + var isRight = (direction === 'right'); + var isUp = (direction === 'up'); + var boxW = w; + var boxH = h; + var boxL, boxR; + var boxT, boxB; + + if(!isDown && !isLeft && !isRight && !isUp) { + this.position.direction = 'down'; + isDown = true; + } + + var isVertical = isDown || isUp; + if(isVertical) { + boxL = l; + boxR = boxL + boxW; + + if(isDown) { + // anchor to top side + boxT = t; + boxB = Math.min(boxT + boxH, fullHeight); + boxH = boxB - boxT; + } else { + // anchor to bottom side + boxB = t + boxH; + boxT = Math.max(boxB - boxH, 0); + boxH = boxB - boxT; + } + } else { + boxT = t; + boxB = boxT + boxH; + + if(isLeft) { + // anchor to right side + boxR = l + boxW; + boxL = Math.max(boxR - boxW, 0); + boxW = boxR - boxL; + } else { + // anchor to left side + boxL = l; + boxR = Math.min(boxL + boxW, fullWidth); + boxW = boxR - boxL; + } + } + + this._box = { + l: boxL, + t: boxT, + w: boxW, + h: boxH + }; + + // compute position of horizontal scroll bar + var needsHorizontalScrollBar = (w > boxW); + var hbarW = ScrollBox.barLength + 2 * ScrollBox.barPad; + var hbarH = ScrollBox.barWidth + 2 * ScrollBox.barPad; + // draw horizontal scrollbar on the bottom side + var hbarL = l; + var hbarT = t + h; + + if(hbarT + hbarH > fullHeight) hbarT = fullHeight - hbarH; + + var hbar = this.container.selectAll('rect.scrollbar-horizontal').data( + (needsHorizontalScrollBar) ? [0] : []); + + hbar.exit() + .on('.drag', null) + .remove(); + + hbar.enter().append('rect') + .classed('scrollbar-horizontal', true) + .call(Color.fill, ScrollBox.barColor); + + if(needsHorizontalScrollBar) { + this.hbar = hbar.attr({ + 'rx': ScrollBox.barRadius, + 'ry': ScrollBox.barRadius, + 'x': hbarL, + 'y': hbarT, + 'width': hbarW, + 'height': hbarH + }); + + // hbar center moves between hbarXMin and hbarXMin + hbarTranslateMax + this._hbarXMin = hbarL + hbarW / 2; + this._hbarTranslateMax = boxW - hbarW; + } else { + delete this.hbar; + delete this._hbarXMin; + delete this._hbarTranslateMax; + } + + // compute position of vertical scroll bar + var needsVerticalScrollBar = (h > boxH); + var vbarW = ScrollBox.barWidth + 2 * ScrollBox.barPad; + var vbarH = ScrollBox.barLength + 2 * ScrollBox.barPad; + // draw vertical scrollbar on the right side + var vbarL = l + w; + var vbarT = t; + + if(vbarL + vbarW > fullWidth) vbarL = fullWidth - vbarW; + + var vbar = this.container.selectAll('rect.scrollbar-vertical').data( + (needsVerticalScrollBar) ? [0] : []); + + vbar.exit() + .on('.drag', null) + .remove(); + + vbar.enter().append('rect') + .classed('scrollbar-vertical', true) + .call(Color.fill, ScrollBox.barColor); + + if(needsVerticalScrollBar) { + this.vbar = vbar.attr({ + 'rx': ScrollBox.barRadius, + 'ry': ScrollBox.barRadius, + 'x': vbarL, + 'y': vbarT, + 'width': vbarW, + 'height': vbarH + }); + + // vbar center moves between vbarYMin and vbarYMin + vbarTranslateMax + this._vbarYMin = vbarT + vbarH / 2; + this._vbarTranslateMax = boxH - vbarH; + } else { + delete this.vbar; + delete this._vbarYMin; + delete this._vbarTranslateMax; + } + + // setup a clip path (if scroll bars are needed) + var clipId = this.id; + var clipL = boxL - 0.5; + var clipR = (needsVerticalScrollBar) ? boxR + vbarW + 0.5 : boxR + 0.5; + var clipT = boxT - 0.5; + var clipB = (needsHorizontalScrollBar) ? boxB + hbarH + 0.5 : boxB + 0.5; + + var clipPath = fullLayout._topdefs.selectAll('#' + clipId) + .data((needsHorizontalScrollBar || needsVerticalScrollBar) ? [0] : []); + + clipPath.exit().remove(); + + clipPath.enter() + .append('clipPath').attr('id', clipId) + .append('rect'); + + if(needsHorizontalScrollBar || needsVerticalScrollBar) { + this._clipRect = clipPath.select('rect').attr({ + x: Math.floor(clipL), + y: Math.floor(clipT), + width: Math.ceil(clipR) - Math.floor(clipL), + height: Math.ceil(clipB) - Math.floor(clipT) + }); + + this.container.call(Drawing.setClipUrl, clipId, this.gd); + + this.bg.attr({ + x: l, + y: t, + width: w, + height: h + }); + } else { + this.bg.attr({ + width: 0, + height: 0 + }); + this.container + .on('wheel', null) + .on('.drag', null) + .call(Drawing.setClipUrl, null); + delete this._clipRect; + } + + // set up drag listeners (if scroll bars are needed) + if(needsHorizontalScrollBar || needsVerticalScrollBar) { + var onBoxDrag = d3.behavior.drag() + .on('dragstart', function() { + d3.event.sourceEvent.preventDefault(); + }) + .on('drag', this._onBoxDrag.bind(this)); + + this.container + .on('wheel', null) + .on('wheel', this._onBoxWheel.bind(this)) + .on('.drag', null) + .call(onBoxDrag); + + var onBarDrag = d3.behavior.drag() + .on('dragstart', function() { + d3.event.sourceEvent.preventDefault(); + d3.event.sourceEvent.stopPropagation(); + }) + .on('drag', this._onBarDrag.bind(this)); + + if(needsHorizontalScrollBar) { + this.hbar + .on('.drag', null) + .call(onBarDrag); + } + + if(needsVerticalScrollBar) { + this.vbar + .on('.drag', null) + .call(onBarDrag); + } + } + + // set scrollbox translation + this.setTranslate(translateX, translateY); +}; + +/** + * If present, remove clip-path and scrollbars + * + * @method + */ +ScrollBox.prototype.disable = function disable() { + if(this.hbar || this.vbar) { + this.bg.attr({ + width: 0, + height: 0 + }); + this.container + .on('wheel', null) + .on('.drag', null) + .call(Drawing.setClipUrl, null); + delete this._clipRect; + } + + if(this.hbar) { + this.hbar.on('.drag', null); + this.hbar.remove(); + delete this.hbar; + delete this._hbarXMin; + delete this._hbarTranslateMax; + } + + if(this.vbar) { + this.vbar.on('.drag', null); + this.vbar.remove(); + delete this.vbar; + delete this._vbarYMin; + delete this._vbarTranslateMax; + } +}; + +/** + * Handles scroll box drag events + * + * @method + */ +ScrollBox.prototype._onBoxDrag = function _onBoxDrag() { + var translateX = this.translateX; + var translateY = this.translateY; + + if(this.hbar) { + translateX -= d3.event.dx; + } + + if(this.vbar) { + translateY -= d3.event.dy; + } + + this.setTranslate(translateX, translateY); +}; + +/** + * Handles scroll box wheel events + * + * @method + */ +ScrollBox.prototype._onBoxWheel = function _onBoxWheel() { + var translateX = this.translateX; + var translateY = this.translateY; + + if(this.hbar) { + translateX += d3.event.deltaY; + } + + if(this.vbar) { + translateY += d3.event.deltaY; + } + + this.setTranslate(translateX, translateY); +}; + +/** + * Handles scroll bar drag events + * + * @method + */ +ScrollBox.prototype._onBarDrag = function _onBarDrag() { + var translateX = this.translateX; + var translateY = this.translateY; + + if(this.hbar) { + var xMin = translateX + this._hbarXMin; + var xMax = xMin + this._hbarTranslateMax; + var x = Lib.constrain(d3.event.x, xMin, xMax); + var xf = (x - xMin) / (xMax - xMin); + + var translateXMax = this.position.w - this._box.w; + + translateX = xf * translateXMax; + } + + if(this.vbar) { + var yMin = translateY + this._vbarYMin; + var yMax = yMin + this._vbarTranslateMax; + var y = Lib.constrain(d3.event.y, yMin, yMax); + var yf = (y - yMin) / (yMax - yMin); + + var translateYMax = this.position.h - this._box.h; + + translateY = yf * translateYMax; + } + + this.setTranslate(translateX, translateY); +}; + +/** + * Set clip path and scroll bar translate transform + * + * @method + * @param {number} [translateX=0] Horizontal offset (in pixels) + * @param {number} [translateY=0] Vertical offset (in pixels) + */ +ScrollBox.prototype.setTranslate = function setTranslate(translateX, translateY) { + // store translateX and translateY (needed by mouse event handlers) + var translateXMax = this.position.w - this._box.w; + var translateYMax = this.position.h - this._box.h; + + translateX = Lib.constrain(translateX || 0, 0, translateXMax); + translateY = Lib.constrain(translateY || 0, 0, translateYMax); + + this.translateX = translateX; + this.translateY = translateY; + + this.container.call(Drawing.setTranslate, + this._box.l - this.position.l - translateX, + this._box.t - this.position.t - translateY); + + if(this._clipRect) { + this._clipRect.attr({ + x: Math.floor(this.position.l + translateX - 0.5), + y: Math.floor(this.position.t + translateY - 0.5) + }); + } + + if(this.hbar) { + var xf = translateX / translateXMax; + + this.hbar.call(Drawing.setTranslate, + translateX + xf * this._hbarTranslateMax, + translateY); + } + + if(this.vbar) { + var yf = translateY / translateYMax; + + this.vbar.call(Drawing.setTranslate, + translateX, + translateY + yf * this._vbarTranslateMax); + } +}; + +},{"../../lib":169,"../color":51,"../drawing":72,"d3":16}],145:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +// fraction of some size to get to a named position +module.exports = { + // from bottom left: this is the origin of our paper-reference + // positioning system + FROM_BL: { + left: 0, + center: 0.5, + right: 1, + bottom: 0, + middle: 0.5, + top: 1 + }, + // from top left: this is the screen pixel positioning origin + FROM_TL: { + left: 0, + center: 0.5, + right: 1, + bottom: 1, + middle: 0.5, + top: 0 + }, + // from bottom right: sometimes you just need the opposite of ^^ + FROM_BR: { + left: 1, + center: 0.5, + right: 0, + bottom: 0, + middle: 0.5, + top: 1 + }, + // multiple of fontSize to get the vertical offset between lines + LINE_SPACING: 1.3, + + // multiple of fontSize to shift from the baseline + // to the cap (captical letter) line + // (to use when we don't calculate this shift from Drawing.bBox) + // This is an approximation since in reality cap height can differ + // from font to font. However, according to Wikipedia + // an "average" font might have a cap height of 70% of the em + // https://en.wikipedia.org/wiki/Em_(typography)#History + CAP_SHIFT: 0.70, + + // half the cap height (distance between baseline and cap line) + // of an "average" font (for more info see above). + MID_SHIFT: 0.35, + + OPPOSITE_SIDE: { + left: 'right', + right: 'left', + top: 'bottom', + bottom: 'top' + } +}; + +},{}],146:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + FORMAT_LINK: 'https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md#d3_format', + DATE_FORMAT_LINK: 'https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md#format' +}; + +},{}],147:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + COMPARISON_OPS: ['=', '!=', '<', '>=', '>', '<='], + COMPARISON_OPS2: ['=', '<', '>=', '>', '<='], + INTERVAL_OPS: ['[]', '()', '[)', '(]', '][', ')(', '](', ')['], + SET_OPS: ['{}', '}{'], + CONSTRAINT_REDUCTION: { + // for contour constraints, open/closed endpoints are equivalent + '=': '=', + + '<': '<', + '<=': '<', + + '>': '>', + '>=': '>', + + '[]': '[]', + '()': '[]', + '[)': '[]', + '(]': '[]', + + '][': '][', + ')(': '][', + '](': '][', + ')[': '][' + } +}; + +},{}],148:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + + +module.exports = { + /** + * Timing information for interactive elements + */ + SHOW_PLACEHOLDER: 100, + HIDE_PLACEHOLDER: 1000, + + // opacity dimming fraction for points that are not in selection + DESELECTDIM: 0.2 +}; + +},{}],149:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + + +module.exports = { + /** + * Standardize all missing data in calcdata to use undefined + * never null or NaN. + * That way we can use !==undefined, or !== BADNUM, + * to test for real data + */ + BADNUM: undefined, + + /* + * Limit certain operations to well below floating point max value + * to avoid glitches: Make sure that even when you multiply it by the + * number of pixels on a giant screen it still works + */ + FP_SAFE: Number.MAX_VALUE / 10000, + + /* + * conversion of date units to milliseconds + * year and month constants are marked "AVG" + * to remind us that not all years and months + * have the same length + */ + ONEAVGYEAR: 31557600000, // 365.25 days + ONEAVGMONTH: 2629800000, // 1/12 of ONEAVGYEAR + ONEDAY: 86400000, + ONEHOUR: 3600000, + ONEMIN: 60000, + ONESEC: 1000, + + /* + * For fast conversion btwn world calendars and epoch ms, the Julian Day Number + * of the unix epoch. From calendars.instance().newDate(1970, 1, 1).toJD() + */ + EPOCHJD: 2440587.5, + + /* + * Are two values nearly equal? Compare to 1PPM + */ + ALMOST_EQUAL: 1 - 1e-6, + + /* + * If we're asked to clip a non-positive log value, how far off-screen + * do we put it? + */ + LOG_CLIP: 10, + + /* + * not a number, but for displaying numbers: the "minus sign" symbol is + * wider than the regular ascii dash "-" + */ + MINUS_SIGN: '\u2212' +}; + +},{}],150:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + + +exports.xmlns = 'http://www.w3.org/2000/xmlns/'; +exports.svg = 'http://www.w3.org/2000/svg'; +exports.xlink = 'http://www.w3.org/1999/xlink'; + +// the 'old' d3 quirk got fix in v3.5.7 +// https://github.com/mbostock/d3/commit/a6f66e9dd37f764403fc7c1f26be09ab4af24fed +exports.svgAttrs = { + xmlns: exports.svg, + 'xmlns:xlink': exports.xlink +}; + +},{}],151:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +// package version injected by `npm run preprocess` +exports.version = '1.51.1'; + +// inject promise polyfill +_dereq_('es6-promise').polyfill(); + +// inject plot css +_dereq_('../build/plotcss'); + +// inject default MathJax config +_dereq_('./fonts/mathjax_config')(); + +// include registry module and expose register method +var Registry = _dereq_('./registry'); +var register = exports.register = Registry.register; + +// expose plot api methods +var plotApi = _dereq_('./plot_api'); +var methodNames = Object.keys(plotApi); +for(var i = 0; i < methodNames.length; i++) { + var name = methodNames[i]; + // _ -> private API methods, but still registered for internal use + if(name.charAt(0) !== '_') exports[name] = plotApi[name]; + register({ + moduleType: 'apiMethod', + name: name, + fn: plotApi[name] + }); +} + +// scatter is the only trace included by default +register(_dereq_('./traces/scatter')); + +// register all registrable components modules +register([ + _dereq_('./components/fx'), + _dereq_('./components/legend'), + _dereq_('./components/annotations'), + _dereq_('./components/annotations3d'), + _dereq_('./components/shapes'), + _dereq_('./components/images'), + _dereq_('./components/updatemenus'), + _dereq_('./components/sliders'), + _dereq_('./components/rangeslider'), + _dereq_('./components/rangeselector'), + _dereq_('./components/grid'), + _dereq_('./components/errorbars'), + _dereq_('./components/colorscale'), + _dereq_('./components/colorbar') +]); + +// locales en and en-US are required for default behavior +register([ + _dereq_('./locale-en'), + _dereq_('./locale-en-us') +]); + +// plot icons +exports.Icons = _dereq_('./fonts/ploticon'); + +// unofficial 'beta' plot methods, use at your own risk +exports.Plots = _dereq_('./plots/plots'); +exports.Fx = _dereq_('./components/fx'); +exports.Snapshot = _dereq_('./snapshot'); +exports.PlotSchema = _dereq_('./plot_api/plot_schema'); +exports.Queue = _dereq_('./lib/queue'); + +// export d3 used in the bundle +exports.d3 = _dereq_('d3'); + +},{"../build/plotcss":1,"./components/annotations":44,"./components/annotations3d":49,"./components/colorbar":57,"./components/colorscale":63,"./components/errorbars":78,"./components/fx":89,"./components/grid":93,"./components/images":98,"./components/legend":106,"./components/rangeselector":117,"./components/rangeslider":124,"./components/shapes":132,"./components/sliders":137,"./components/updatemenus":143,"./fonts/mathjax_config":152,"./fonts/ploticon":153,"./lib/queue":183,"./locale-en":194,"./locale-en-us":193,"./plot_api":198,"./plot_api/plot_schema":202,"./plots/plots":245,"./registry":258,"./snapshot":263,"./traces/scatter":387,"d3":16,"es6-promise":17}],152:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +/* global MathJax:false */ + +module.exports = function() { + if(typeof MathJax !== 'undefined') { + var globalConfig = (window.PlotlyConfig || {}).MathJaxConfig !== 'local'; + + if(globalConfig) { + MathJax.Hub.Config({ + messageStyle: 'none', + skipStartupTypeset: true, + displayAlign: 'left', + tex2jax: { + inlineMath: [['$', '$'], ['\\(', '\\)']] + } + }); + MathJax.Hub.Configured(); + } + } +}; + +},{}],153:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + 'undo': { + 'width': 857.1, + 'height': 1000, + 'path': 'm857 350q0-87-34-166t-91-137-137-92-166-34q-96 0-183 41t-147 114q-4 6-4 13t5 11l76 77q6 5 14 5 9-1 13-7 41-53 100-82t126-29q58 0 110 23t92 61 61 91 22 111-22 111-61 91-92 61-110 23q-55 0-105-20t-90-57l77-77q17-16 8-38-10-23-33-23h-250q-15 0-25 11t-11 25v250q0 24 22 33 22 10 39-8l72-72q60 57 137 88t159 31q87 0 166-34t137-92 91-137 34-166z', + 'transform': 'matrix(1 0 0 -1 0 850)' + }, + 'home': { + 'width': 928.6, + 'height': 1000, + 'path': 'm786 296v-267q0-15-11-26t-25-10h-214v214h-143v-214h-214q-15 0-25 10t-11 26v267q0 1 0 2t0 2l321 264 321-264q1-1 1-4z m124 39l-34-41q-5-5-12-6h-2q-7 0-12 3l-386 322-386-322q-7-4-13-4-7 2-12 7l-35 41q-4 5-3 13t6 12l401 334q18 15 42 15t43-15l136-114v109q0 8 5 13t13 5h107q8 0 13-5t5-13v-227l122-102q5-5 6-12t-4-13z', + 'transform': 'matrix(1 0 0 -1 0 850)' + }, + 'camera-retro': { + 'width': 1000, + 'height': 1000, + 'path': 'm518 386q0 8-5 13t-13 5q-37 0-63-27t-26-63q0-8 5-13t13-5 12 5 5 13q0 23 16 38t38 16q8 0 13 5t5 13z m125-73q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z m-572-320h858v71h-858v-71z m643 320q0 89-62 152t-152 62-151-62-63-152 63-151 151-63 152 63 62 151z m-571 358h214v72h-214v-72z m-72-107h858v143h-462l-36-71h-360v-72z m929 143v-714q0-30-21-51t-50-21h-858q-29 0-50 21t-21 51v714q0 30 21 51t50 21h858q29 0 50-21t21-51z', + 'transform': 'matrix(1 0 0 -1 0 850)' + }, + 'zoombox': { + 'width': 1000, + 'height': 1000, + 'path': 'm1000-25l-250 251c40 63 63 138 63 218 0 224-182 406-407 406-224 0-406-182-406-406s183-406 407-406c80 0 155 22 218 62l250-250 125 125z m-812 250l0 438 437 0 0-438-437 0z m62 375l313 0 0-312-313 0 0 312z', + 'transform': 'matrix(1 0 0 -1 0 850)' + }, + 'pan': { + 'width': 1000, + 'height': 1000, + 'path': 'm1000 350l-187 188 0-125-250 0 0 250 125 0-188 187-187-187 125 0 0-250-250 0 0 125-188-188 186-187 0 125 252 0 0-250-125 0 187-188 188 188-125 0 0 250 250 0 0-126 187 188z', + 'transform': 'matrix(1 0 0 -1 0 850)' + }, + 'zoom_plus': { + 'width': 875, + 'height': 1000, + 'path': 'm1 787l0-875 875 0 0 875-875 0z m687-500l-187 0 0-187-125 0 0 187-188 0 0 125 188 0 0 187 125 0 0-187 187 0 0-125z', + 'transform': 'matrix(1 0 0 -1 0 850)' + }, + 'zoom_minus': { + 'width': 875, + 'height': 1000, + 'path': 'm0 788l0-876 875 0 0 876-875 0z m688-500l-500 0 0 125 500 0 0-125z', + 'transform': 'matrix(1 0 0 -1 0 850)' + }, + 'autoscale': { + 'width': 1000, + 'height': 1000, + 'path': 'm250 850l-187 0-63 0 0-62 0-188 63 0 0 188 187 0 0 62z m688 0l-188 0 0-62 188 0 0-188 62 0 0 188 0 62-62 0z m-875-938l0 188-63 0 0-188 0-62 63 0 187 0 0 62-187 0z m875 188l0-188-188 0 0-62 188 0 62 0 0 62 0 188-62 0z m-125 188l-1 0-93-94-156 156 156 156 92-93 2 0 0 250-250 0 0-2 93-92-156-156-156 156 94 92 0 2-250 0 0-250 0 0 93 93 157-156-157-156-93 94 0 0 0-250 250 0 0 0-94 93 156 157 156-157-93-93 0 0 250 0 0 250z', + 'transform': 'matrix(1 0 0 -1 0 850)' + }, + 'tooltip_basic': { + 'width': 1500, + 'height': 1000, + 'path': 'm375 725l0 0-375-375 375-374 0-1 1125 0 0 750-1125 0z', + 'transform': 'matrix(1 0 0 -1 0 850)' + }, + 'tooltip_compare': { + 'width': 1125, + 'height': 1000, + 'path': 'm187 786l0 2-187-188 188-187 0 0 937 0 0 373-938 0z m0-499l0 1-187-188 188-188 0 0 937 0 0 376-938-1z', + 'transform': 'matrix(1 0 0 -1 0 850)' + }, + 'plotlylogo': { + 'width': 1542, + 'height': 1000, + 'path': 'm0-10h182v-140h-182v140z m228 146h183v-286h-183v286z m225 714h182v-1000h-182v1000z m225-285h182v-715h-182v715z m225 142h183v-857h-183v857z m231-428h182v-429h-182v429z m225-291h183v-138h-183v138z', + 'transform': 'matrix(1 0 0 -1 0 850)' + }, + 'z-axis': { + 'width': 1000, + 'height': 1000, + 'path': 'm833 5l-17 108v41l-130-65 130-66c0 0 0 38 0 39 0-1 36-14 39-25 4-15-6-22-16-30-15-12-39-16-56-20-90-22-187-23-279-23-261 0-341 34-353 59 3 60 228 110 228 110-140-8-351-35-351-116 0-120 293-142 474-142 155 0 477 22 477 142 0 50-74 79-163 96z m-374 94c-58-5-99-21-99-40 0-24 65-43 144-43 79 0 143 19 143 43 0 19-42 34-98 40v216h87l-132 135-133-135h88v-216z m167 515h-136v1c16 16 31 34 46 52l84 109v54h-230v-71h124v-1c-16-17-28-32-44-51l-89-114v-51h245v72z', + 'transform': 'matrix(1 0 0 -1 0 850)' + }, + '3d_rotate': { + 'width': 1000, + 'height': 1000, + 'path': 'm922 660c-5 4-9 7-14 11-359 263-580-31-580-31l-102 28 58-400c0 1 1 1 2 2 118 108 351 249 351 249s-62 27-100 42c88 83 222 183 347 122 16-8 30-17 44-27-2 1-4 2-6 4z m36-329c0 0 64 229-88 296-62 27-124 14-175-11 157-78 225-208 249-266 8-19 11-31 11-31 2 5 6 15 11 32-5-13-8-20-8-20z m-775-239c70-31 117-50 198-32-121 80-199 346-199 346l-96-15-58-12c0 0 55-226 155-287z m603 133l-317-139c0 0 4-4 19-14 7-5 24-15 24-15s-177-147-389 4c235-287 536-112 536-112l31-22 100 299-4-1z m-298-153c6-4 14-9 24-15 0 0-17 10-24 15z', + 'transform': 'matrix(1 0 0 -1 0 850)' + }, + 'camera': { + 'width': 1000, + 'height': 1000, + 'path': 'm500 450c-83 0-150-67-150-150 0-83 67-150 150-150 83 0 150 67 150 150 0 83-67 150-150 150z m400 150h-120c-16 0-34 13-39 29l-31 93c-6 15-23 28-40 28h-340c-16 0-34-13-39-28l-31-94c-6-15-23-28-40-28h-120c-55 0-100-45-100-100v-450c0-55 45-100 100-100h800c55 0 100 45 100 100v450c0 55-45 100-100 100z m-400-550c-138 0-250 112-250 250 0 138 112 250 250 250 138 0 250-112 250-250 0-138-112-250-250-250z m365 380c-19 0-35 16-35 35 0 19 16 35 35 35 19 0 35-16 35-35 0-19-16-35-35-35z', + 'transform': 'matrix(1 0 0 -1 0 850)' + }, + 'movie': { + 'width': 1000, + 'height': 1000, + 'path': 'm938 413l-188-125c0 37-17 71-44 94 64 38 107 107 107 187 0 121-98 219-219 219-121 0-219-98-219-219 0-61 25-117 66-156h-115c30 33 49 76 49 125 0 103-84 187-187 187s-188-84-188-187c0-57 26-107 65-141-38-22-65-62-65-109v-250c0-70 56-126 125-126h500c69 0 125 56 125 126l188-126c34 0 62 28 62 63v375c0 35-28 63-62 63z m-750 0c-69 0-125 56-125 125s56 125 125 125 125-56 125-125-56-125-125-125z m406-1c-87 0-157 70-157 157 0 86 70 156 157 156s156-70 156-156-70-157-156-157z', + 'transform': 'matrix(1 0 0 -1 0 850)' + }, + 'question': { + 'width': 857.1, + 'height': 1000, + 'path': 'm500 82v107q0 8-5 13t-13 5h-107q-8 0-13-5t-5-13v-107q0-8 5-13t13-5h107q8 0 13 5t5 13z m143 375q0 49-31 91t-77 65-95 23q-136 0-207-119-9-14 4-24l74-55q4-4 10-4 9 0 14 7 30 38 48 51 19 14 48 14 27 0 48-15t21-33q0-21-11-34t-38-25q-35-16-65-48t-29-70v-20q0-8 5-13t13-5h107q8 0 13 5t5 13q0 10 12 27t30 28q18 10 28 16t25 19 25 27 16 34 7 45z m214-107q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z', + 'transform': 'matrix(1 0 0 -1 0 850)' + }, + 'disk': { + 'width': 857.1, + 'height': 1000, + 'path': 'm214-7h429v214h-429v-214z m500 0h72v500q0 8-6 21t-11 20l-157 156q-5 6-19 12t-22 5v-232q0-22-15-38t-38-16h-322q-22 0-37 16t-16 38v232h-72v-714h72v232q0 22 16 38t37 16h465q22 0 38-16t15-38v-232z m-214 518v178q0 8-5 13t-13 5h-107q-7 0-13-5t-5-13v-178q0-8 5-13t13-5h107q7 0 13 5t5 13z m357-18v-518q0-22-15-38t-38-16h-750q-23 0-38 16t-16 38v750q0 22 16 38t38 16h517q23 0 50-12t42-26l156-157q16-15 27-42t11-49z', + 'transform': 'matrix(1 0 0 -1 0 850)' + }, + 'lasso': { + 'width': 1031, + 'height': 1000, + 'path': 'm1018 538c-36 207-290 336-568 286-277-48-473-256-436-463 10-57 36-108 76-151-13-66 11-137 68-183 34-28 75-41 114-42l-55-70 0 0c-2-1-3-2-4-3-10-14-8-34 5-45 14-11 34-8 45 4 1 1 2 3 2 5l0 0 113 140c16 11 31 24 45 40 4 3 6 7 8 11 48-3 100 0 151 9 278 48 473 255 436 462z m-624-379c-80 14-149 48-197 96 42 42 109 47 156 9 33-26 47-66 41-105z m-187-74c-19 16-33 37-39 60 50-32 109-55 174-68-42-25-95-24-135 8z m360 75c-34-7-69-9-102-8 8 62-16 128-68 170-73 59-175 54-244-5-9 20-16 40-20 61-28 159 121 317 333 354s407-60 434-217c28-159-121-318-333-355z', + 'transform': 'matrix(1 0 0 -1 0 850)' + }, + 'selectbox': { + 'width': 1000, + 'height': 1000, + 'path': 'm0 850l0-143 143 0 0 143-143 0z m286 0l0-143 143 0 0 143-143 0z m285 0l0-143 143 0 0 143-143 0z m286 0l0-143 143 0 0 143-143 0z m-857-286l0-143 143 0 0 143-143 0z m857 0l0-143 143 0 0 143-143 0z m-857-285l0-143 143 0 0 143-143 0z m857 0l0-143 143 0 0 143-143 0z m-857-286l0-143 143 0 0 143-143 0z m286 0l0-143 143 0 0 143-143 0z m285 0l0-143 143 0 0 143-143 0z m286 0l0-143 143 0 0 143-143 0z', + 'transform': 'matrix(1 0 0 -1 0 850)' + }, + 'spikeline': { + 'width': 1000, + 'height': 1000, + 'path': 'M512 409c0-57-46-104-103-104-57 0-104 47-104 104 0 57 47 103 104 103 57 0 103-46 103-103z m-327-39l92 0 0 92-92 0z m-185 0l92 0 0 92-92 0z m370-186l92 0 0 93-92 0z m0-184l92 0 0 92-92 0z', + 'transform': 'matrix(1.5 0 0 -1.5 0 850)' + }, + 'pencil': { + 'width': 1792, + 'height': 1792, + 'path': 'M491 1536l91-91-235-235-91 91v107h128v128h107zm523-928q0-22-22-22-10 0-17 7l-542 542q-7 7-7 17 0 22 22 22 10 0 17-7l542-542q7-7 7-17zm-54-192l416 416-832 832h-416v-416zm683 96q0 53-37 90l-166 166-416-416 166-165q36-38 90-38 53 0 91 38l235 234q37 39 37 91z', + 'transform': 'matrix(1 0 0 1 0 1)' + }, + 'newplotlylogo': { + 'name': 'newplotlylogo', + 'svg': 'plotly-logomark' + } +}; + +},{}],154:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + + +/** + * Determine the position anchor property of x/y xanchor/yanchor components. + * + * - values < 1/3 align the low side at that fraction, + * - values [1/3, 2/3] align the center at that fraction, + * - values > 2/3 align the right at that fraction. + */ + + +exports.isLeftAnchor = function isLeftAnchor(opts) { + return ( + opts.xanchor === 'left' || + (opts.xanchor === 'auto' && opts.x <= 1 / 3) + ); +}; + +exports.isCenterAnchor = function isCenterAnchor(opts) { + return ( + opts.xanchor === 'center' || + (opts.xanchor === 'auto' && opts.x > 1 / 3 && opts.x < 2 / 3) + ); +}; + +exports.isRightAnchor = function isRightAnchor(opts) { + return ( + opts.xanchor === 'right' || + (opts.xanchor === 'auto' && opts.x >= 2 / 3) + ); +}; + +exports.isTopAnchor = function isTopAnchor(opts) { + return ( + opts.yanchor === 'top' || + (opts.yanchor === 'auto' && opts.y >= 2 / 3) + ); +}; + +exports.isMiddleAnchor = function isMiddleAnchor(opts) { + return ( + opts.yanchor === 'middle' || + (opts.yanchor === 'auto' && opts.y > 1 / 3 && opts.y < 2 / 3) + ); +}; + +exports.isBottomAnchor = function isBottomAnchor(opts) { + return ( + opts.yanchor === 'bottom' || + (opts.yanchor === 'auto' && opts.y <= 1 / 3) + ); +}; + +},{}],155:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var modModule = _dereq_('./mod'); +var mod = modModule.mod; +var modHalf = modModule.modHalf; + +var PI = Math.PI; +var twoPI = 2 * PI; + +function deg2rad(deg) { return deg / 180 * PI; } + +function rad2deg(rad) { return rad / PI * 180; } + +/** + * is sector a full circle? + * ... this comes up a lot in SVG path-drawing routines + * + * N.B. we consider all sectors that span more that 2pi 'full' circles + * + * @param {2-item array} aBnds : angular bounds in *radians* + * @return {boolean} + */ +function isFullCircle(aBnds) { + return Math.abs(aBnds[1] - aBnds[0]) > twoPI - 1e-14; +} + +/** + * angular delta between angle 'a' and 'b' + * solution taken from: https://stackoverflow.com/a/2007279 + * + * @param {number} a : first angle in *radians* + * @param {number} b : second angle in *radians* + * @return {number} angular delta in *radians* + */ +function angleDelta(a, b) { + return modHalf(b - a, twoPI); +} + +/** + * angular distance between angle 'a' and 'b' + * + * @param {number} a : first angle in *radians* + * @param {number} b : second angle in *radians* + * @return {number} angular distance in *radians* + */ +function angleDist(a, b) { + return Math.abs(angleDelta(a, b)); +} + +/** + * is angle inside sector? + * + * @param {number} a : angle to test in *radians* + * @param {2-item array} aBnds : sector's angular bounds in *radians* + * @param {boolean} + */ +function isAngleInsideSector(a, aBnds) { + if(isFullCircle(aBnds)) return true; + + var s0, s1; + + if(aBnds[0] < aBnds[1]) { + s0 = aBnds[0]; + s1 = aBnds[1]; + } else { + s0 = aBnds[1]; + s1 = aBnds[0]; + } + + s0 = mod(s0, twoPI); + s1 = mod(s1, twoPI); + if(s0 > s1) s1 += twoPI; + + var a0 = mod(a, twoPI); + var a1 = a0 + twoPI; + + return (a0 >= s0 && a0 <= s1) || (a1 >= s0 && a1 <= s1); +} + +/** + * is pt (r,a) inside sector? + * + * @param {number} r : pt's radial coordinate + * @param {number} a : pt's angular coordinate in *radians* + * @param {2-item array} rBnds : sector's radial bounds + * @param {2-item array} aBnds : sector's angular bounds in *radians* + * @return {boolean} + */ +function isPtInsideSector(r, a, rBnds, aBnds) { + if(!isAngleInsideSector(a, aBnds)) return false; + + var r0, r1; + + if(rBnds[0] < rBnds[1]) { + r0 = rBnds[0]; + r1 = rBnds[1]; + } else { + r0 = rBnds[1]; + r1 = rBnds[0]; + } + + return r >= r0 && r <= r1; +} + +// common to pathArc, pathSector and pathAnnulus +function _path(r0, r1, a0, a1, cx, cy, isClosed) { + cx = cx || 0; + cy = cy || 0; + + var isCircle = isFullCircle([a0, a1]); + var aStart, aMid, aEnd; + var rStart, rEnd; + + if(isCircle) { + aStart = 0; + aMid = PI; + aEnd = twoPI; + } else { + if(a0 < a1) { + aStart = a0; + aEnd = a1; + } else { + aStart = a1; + aEnd = a0; + } + } + + if(r0 < r1) { + rStart = r0; + rEnd = r1; + } else { + rStart = r1; + rEnd = r0; + } + + // N.B. svg coordinates here, where y increases downward + function pt(r, a) { + return [r * Math.cos(a) + cx, cy - r * Math.sin(a)]; + } + + var largeArc = Math.abs(aEnd - aStart) <= PI ? 0 : 1; + function arc(r, a, cw) { + return 'A' + [r, r] + ' ' + [0, largeArc, cw] + ' ' + pt(r, a); + } + + var p; + + if(isCircle) { + if(rStart === null) { + p = 'M' + pt(rEnd, aStart) + + arc(rEnd, aMid, 0) + + arc(rEnd, aEnd, 0) + 'Z'; + } else { + p = 'M' + pt(rStart, aStart) + + arc(rStart, aMid, 0) + + arc(rStart, aEnd, 0) + 'Z' + + 'M' + pt(rEnd, aStart) + + arc(rEnd, aMid, 1) + + arc(rEnd, aEnd, 1) + 'Z'; + } + } else { + if(rStart === null) { + p = 'M' + pt(rEnd, aStart) + arc(rEnd, aEnd, 0); + if(isClosed) p += 'L0,0Z'; + } else { + p = 'M' + pt(rStart, aStart) + + 'L' + pt(rEnd, aStart) + + arc(rEnd, aEnd, 0) + + 'L' + pt(rStart, aEnd) + + arc(rStart, aStart, 1) + 'Z'; + } + } + + return p; +} + +/** + * path an arc + * + * @param {number} r : radius + * @param {number} a0 : first angular coordinate in *radians* + * @param {number} a1 : second angular coordinate in *radians* + * @param {number (optional)} cx : x coordinate of center + * @param {number (optional)} cy : y coordinate of center + * @return {string} svg path + */ +function pathArc(r, a0, a1, cx, cy) { + return _path(null, r, a0, a1, cx, cy, 0); +} + +/** + * path a sector + * + * @param {number} r : radius + * @param {number} a0 : first angular coordinate in *radians* + * @param {number} a1 : second angular coordinate in *radians* + * @param {number (optional)} cx : x coordinate of center + * @param {number (optional)} cy : y coordinate of center + * @return {string} svg path + */ +function pathSector(r, a0, a1, cx, cy) { + return _path(null, r, a0, a1, cx, cy, 1); +} + +/** + * path an annulus + * + * @param {number} r0 : first radial coordinate + * @param {number} r1 : second radial coordinate + * @param {number} a0 : first angular coordinate in *radians* + * @param {number} a1 : second angular coordinate in *radians* + * @param {number (optional)} cx : x coordinate of center + * @param {number (optional)} cy : y coordinate of center + * @return {string} svg path + */ +function pathAnnulus(r0, r1, a0, a1, cx, cy) { + return _path(r0, r1, a0, a1, cx, cy, 1); +} + +module.exports = { + deg2rad: deg2rad, + rad2deg: rad2deg, + angleDelta: angleDelta, + angleDist: angleDist, + isFullCircle: isFullCircle, + isAngleInsideSector: isAngleInsideSector, + isPtInsideSector: isPtInsideSector, + pathArc: pathArc, + pathSector: pathSector, + pathAnnulus: pathAnnulus +}; + +},{"./mod":176}],156:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var isArray = Array.isArray; + +// IE9 fallbacks + +var ab = (typeof ArrayBuffer === 'undefined' || !ArrayBuffer.isView) ? + {isView: function() { return false; }} : + ArrayBuffer; + +var dv = (typeof DataView === 'undefined') ? + function() {} : + DataView; + +function isTypedArray(a) { + return ab.isView(a) && !(a instanceof dv); +} +exports.isTypedArray = isTypedArray; + +function isArrayOrTypedArray(a) { + return isArray(a) || isTypedArray(a); +} +exports.isArrayOrTypedArray = isArrayOrTypedArray; + +/* + * Test whether an input object is 1D. + * + * Assumes we already know the object is an array. + * + * Looks only at the first element, if the dimensionality is + * not consistent we won't figure that out here. + */ +function isArray1D(a) { + return !isArrayOrTypedArray(a[0]); +} +exports.isArray1D = isArray1D; + +/* + * Ensures an array has the right amount of storage space. If it doesn't + * exist, it creates an array. If it does exist, it returns it if too + * short or truncates it in-place. + * + * The goal is to just reuse memory to avoid a bit of excessive garbage + * collection. + */ +exports.ensureArray = function(out, n) { + // TODO: typed array support here? This is only used in + // traces/carpet/compute_control_points + if(!isArray(out)) out = []; + + // If too long, truncate. (If too short, it will grow + // automatically so we don't care about that case) + out.length = n; + + return out; +}; + +/* + * TypedArray-compatible concatenation of n arrays + * if all arrays are the same type it will preserve that type, + * otherwise it falls back on Array. + * Also tries to avoid copying, in case one array has zero length + * But never mutates an existing array + */ +exports.concat = function() { + var args = []; + var allArray = true; + var totalLen = 0; + + var _constructor, arg0, i, argi, posi, leni, out, j; + + for(i = 0; i < arguments.length; i++) { + argi = arguments[i]; + leni = argi.length; + if(leni) { + if(arg0) args.push(argi); + else { + arg0 = argi; + posi = leni; + } + + if(isArray(argi)) { + _constructor = false; + } else { + allArray = false; + if(!totalLen) { + _constructor = argi.constructor; + } else if(_constructor !== argi.constructor) { + // TODO: in principle we could upgrade here, + // ie keep typed array but convert all to Float64Array? + _constructor = false; + } + } + + totalLen += leni; + } + } + + if(!totalLen) return []; + if(!args.length) return arg0; + + if(allArray) return arg0.concat.apply(arg0, args); + if(_constructor) { + // matching typed arrays + out = new _constructor(totalLen); + out.set(arg0); + for(i = 0; i < args.length; i++) { + argi = args[i]; + out.set(argi, posi); + posi += argi.length; + } + return out; + } + + // mismatched types or Array + typed + out = new Array(totalLen); + for(j = 0; j < arg0.length; j++) out[j] = arg0[j]; + for(i = 0; i < args.length; i++) { + argi = args[i]; + for(j = 0; j < argi.length; j++) out[posi + j] = argi[j]; + posi += j; + } + return out; +}; + +exports.maxRowLength = function(z) { + return _rowLength(z, Math.max, 0); +}; + +exports.minRowLength = function(z) { + return _rowLength(z, Math.min, Infinity); +}; + +function _rowLength(z, fn, len0) { + if(isArrayOrTypedArray(z)) { + if(isArrayOrTypedArray(z[0])) { + var len = len0; + for(var i = 0; i < z.length; i++) { + len = fn(len, z[i].length); + } + return len; + } else { + return z.length; + } + } + return 0; +} + +},{}],157:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); + +var BADNUM = _dereq_('../constants/numerical').BADNUM; + +// precompile for speed +var JUNK = /^['"%,$#\s']+|[, ]|['"%,$#\s']+$/g; + +/** + * cleanNumber: remove common leading and trailing cruft + * Always returns either a number or BADNUM. + */ +module.exports = function cleanNumber(v) { + if(typeof v === 'string') { + v = v.replace(JUNK, ''); + } + + if(isNumeric(v)) return Number(v); + + return BADNUM; +}; + +},{"../constants/numerical":149,"fast-isnumeric":18}],158:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +/** + * Clear gl frame (if any). This is a common pattern as + * we usually set `preserveDrawingBuffer: true` during + * gl context creation (e.g. via `reglUtils.prepare`). + * + * @param {DOM node or object} gd : graph div object + */ +module.exports = function clearGlCanvases(gd) { + var fullLayout = gd._fullLayout; + + if(fullLayout._glcanvas && fullLayout._glcanvas.size()) { + fullLayout._glcanvas.each(function(d) { + if(d.regl) d.regl.clear({color: true, depth: true}); + }); + } +}; + +},{}],159:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +/** + * Clear responsive handlers (if any). + * + * @param {DOM node or object} gd : graph div object + */ +module.exports = function clearResponsive(gd) { + if(gd._responsiveChartHandler) { + window.removeEventListener('resize', gd._responsiveChartHandler); + delete gd._responsiveChartHandler; + } +}; + +},{}],160:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); +var tinycolor = _dereq_('tinycolor2'); + +var baseTraceAttrs = _dereq_('../plots/attributes'); +var colorscales = _dereq_('../components/colorscale/scales'); +var DESELECTDIM = _dereq_('../constants/interactions').DESELECTDIM; + +var nestedProperty = _dereq_('./nested_property'); +var counterRegex = _dereq_('./regex').counter; +var modHalf = _dereq_('./mod').modHalf; +var isArrayOrTypedArray = _dereq_('./array').isArrayOrTypedArray; + +exports.valObjectMeta = { + data_array: { + // You can use *dflt=[] to force said array to exist though. + + + + coerceFunction: function(v, propOut, dflt) { + // TODO maybe `v: {type: 'float32', vals: [/* ... */]}` also + if(isArrayOrTypedArray(v)) propOut.set(v); + else if(dflt !== undefined) propOut.set(dflt); + } + }, + enumerated: { + + + + coerceFunction: function(v, propOut, dflt, opts) { + if(opts.coerceNumber) v = +v; + if(opts.values.indexOf(v) === -1) propOut.set(dflt); + else propOut.set(v); + }, + validateFunction: function(v, opts) { + if(opts.coerceNumber) v = +v; + + var values = opts.values; + for(var i = 0; i < values.length; i++) { + var k = String(values[i]); + + if((k.charAt(0) === '/' && k.charAt(k.length - 1) === '/')) { + var regex = new RegExp(k.substr(1, k.length - 2)); + if(regex.test(v)) return true; + } else if(v === values[i]) return true; + } + return false; + } + }, + 'boolean': { + + + + coerceFunction: function(v, propOut, dflt) { + if(v === true || v === false) propOut.set(v); + else propOut.set(dflt); + } + }, + number: { + + + + coerceFunction: function(v, propOut, dflt, opts) { + if(!isNumeric(v) || + (opts.min !== undefined && v < opts.min) || + (opts.max !== undefined && v > opts.max)) { + propOut.set(dflt); + } else propOut.set(+v); + } + }, + integer: { + + + + coerceFunction: function(v, propOut, dflt, opts) { + if(v % 1 || !isNumeric(v) || + (opts.min !== undefined && v < opts.min) || + (opts.max !== undefined && v > opts.max)) { + propOut.set(dflt); + } else propOut.set(+v); + } + }, + string: { + + + // TODO 'values shouldn't be in there (edge case: 'dash' in Scatter) + + coerceFunction: function(v, propOut, dflt, opts) { + if(typeof v !== 'string') { + var okToCoerce = (typeof v === 'number'); + + if(opts.strict === true || !okToCoerce) propOut.set(dflt); + else propOut.set(String(v)); + } else if(opts.noBlank && !v) propOut.set(dflt); + else propOut.set(v); + } + }, + color: { + + + + coerceFunction: function(v, propOut, dflt) { + if(tinycolor(v).isValid()) propOut.set(v); + else propOut.set(dflt); + } + }, + colorlist: { + + + + coerceFunction: function(v, propOut, dflt) { + function isColor(color) { + return tinycolor(color).isValid(); + } + if(!Array.isArray(v) || !v.length) propOut.set(dflt); + else if(v.every(isColor)) propOut.set(v); + else propOut.set(dflt); + } + }, + colorscale: { + + + + coerceFunction: function(v, propOut, dflt) { + propOut.set(colorscales.get(v, dflt)); + } + }, + angle: { + + + + coerceFunction: function(v, propOut, dflt) { + if(v === 'auto') propOut.set('auto'); + else if(!isNumeric(v)) propOut.set(dflt); + else propOut.set(modHalf(+v, 360)); + } + }, + subplotid: { + + + + coerceFunction: function(v, propOut, dflt, opts) { + var regex = opts.regex || counterRegex(dflt); + if(typeof v === 'string' && regex.test(v)) { + propOut.set(v); + return; + } + propOut.set(dflt); + }, + validateFunction: function(v, opts) { + var dflt = opts.dflt; + + if(v === dflt) return true; + if(typeof v !== 'string') return false; + if(counterRegex(dflt).test(v)) return true; + + return false; + } + }, + flaglist: { + + + + coerceFunction: function(v, propOut, dflt, opts) { + if(typeof v !== 'string') { + propOut.set(dflt); + return; + } + if((opts.extras || []).indexOf(v) !== -1) { + propOut.set(v); + return; + } + var vParts = v.split('+'); + var i = 0; + while(i < vParts.length) { + var vi = vParts[i]; + if(opts.flags.indexOf(vi) === -1 || vParts.indexOf(vi) < i) { + vParts.splice(i, 1); + } else i++; + } + if(!vParts.length) propOut.set(dflt); + else propOut.set(vParts.join('+')); + } + }, + any: { + + + + coerceFunction: function(v, propOut, dflt) { + if(v === undefined) propOut.set(dflt); + else propOut.set(v); + } + }, + info_array: { + + + // set `dimensions=2` for a 2D array or '1-2' for either + // `items` may be a single object instead of an array, in which case + // `freeLength` must be true. + // if `dimensions='1-2'` and items is a 1D array, then the value can + // either be a matching 1D array or an array of such matching 1D arrays + + coerceFunction: function(v, propOut, dflt, opts) { + // simplified coerce function just for array items + function coercePart(v, opts, dflt) { + var out; + var propPart = {set: function(v) { out = v; }}; + + if(dflt === undefined) dflt = opts.dflt; + + exports.valObjectMeta[opts.valType].coerceFunction(v, propPart, dflt, opts); + + return out; + } + + var twoD = opts.dimensions === 2 || (opts.dimensions === '1-2' && Array.isArray(v) && Array.isArray(v[0])); + + if(!Array.isArray(v)) { + propOut.set(dflt); + return; + } + + var items = opts.items; + var vOut = []; + var arrayItems = Array.isArray(items); + var arrayItems2D = arrayItems && twoD && Array.isArray(items[0]); + var innerItemsOnly = twoD && arrayItems && !arrayItems2D; + var len = (arrayItems && !innerItemsOnly) ? items.length : v.length; + + var i, j, row, item, len2, vNew; + + dflt = Array.isArray(dflt) ? dflt : []; + + if(twoD) { + for(i = 0; i < len; i++) { + vOut[i] = []; + row = Array.isArray(v[i]) ? v[i] : []; + if(innerItemsOnly) len2 = items.length; + else if(arrayItems) len2 = items[i].length; + else len2 = row.length; + + for(j = 0; j < len2; j++) { + if(innerItemsOnly) item = items[j]; + else if(arrayItems) item = items[i][j]; + else item = items; + + vNew = coercePart(row[j], item, (dflt[i] || [])[j]); + if(vNew !== undefined) vOut[i][j] = vNew; + } + } + } else { + for(i = 0; i < len; i++) { + vNew = coercePart(v[i], arrayItems ? items[i] : items, dflt[i]); + if(vNew !== undefined) vOut[i] = vNew; + } + } + + propOut.set(vOut); + }, + validateFunction: function(v, opts) { + if(!Array.isArray(v)) return false; + + var items = opts.items; + var arrayItems = Array.isArray(items); + var twoD = opts.dimensions === 2; + + // when free length is off, input and declared lengths must match + if(!opts.freeLength && v.length !== items.length) return false; + + // valid when all input items are valid + for(var i = 0; i < v.length; i++) { + if(twoD) { + if(!Array.isArray(v[i]) || (!opts.freeLength && v[i].length !== items[i].length)) { + return false; + } + for(var j = 0; j < v[i].length; j++) { + if(!validate(v[i][j], arrayItems ? items[i][j] : items)) { + return false; + } + } + } else if(!validate(v[i], arrayItems ? items[i] : items)) return false; + } + + return true; + } + } +}; + +/** + * Ensures that container[attribute] has a valid value. + * + * attributes[attribute] is an object with possible keys: + * - valType: data_array, enumerated, boolean, ... as in valObjectMeta + * - values: (enumerated only) array of allowed vals + * - min, max: (number, integer only) inclusive bounds on allowed vals + * either or both may be omitted + * - dflt: if attribute is invalid or missing, use this default + * if dflt is provided as an argument to lib.coerce it takes precedence + * as a convenience, returns the value it finally set + */ +exports.coerce = function(containerIn, containerOut, attributes, attribute, dflt) { + var opts = nestedProperty(attributes, attribute).get(); + var propIn = nestedProperty(containerIn, attribute); + var propOut = nestedProperty(containerOut, attribute); + var v = propIn.get(); + + var template = containerOut._template; + if(v === undefined && template) { + v = nestedProperty(template, attribute).get(); + // already used the template value, so short-circuit the second check + template = 0; + } + + if(dflt === undefined) dflt = opts.dflt; + + /** + * arrayOk: value MAY be an array, then we do no value checking + * at this point, because it can be more complicated than the + * individual form (eg. some array vals can be numbers, even if the + * single values must be color strings) + */ + if(opts.arrayOk && isArrayOrTypedArray(v)) { + propOut.set(v); + return v; + } + + var coerceFunction = exports.valObjectMeta[opts.valType].coerceFunction; + coerceFunction(v, propOut, dflt, opts); + + var out = propOut.get(); + // in case v was provided but invalid, try the template again so it still + // overrides the regular default + if(template && out === dflt && !validate(v, opts)) { + v = nestedProperty(template, attribute).get(); + coerceFunction(v, propOut, dflt, opts); + out = propOut.get(); + } + return out; +}; + +/** + * Variation on coerce + * + * Uses coerce to get attribute value if user input is valid, + * returns attribute default if user input it not valid or + * returns false if there is no user input. + */ +exports.coerce2 = function(containerIn, containerOut, attributes, attribute, dflt) { + var propIn = nestedProperty(containerIn, attribute); + var propOut = exports.coerce(containerIn, containerOut, attributes, attribute, dflt); + var valIn = propIn.get(); + + return (valIn !== undefined && valIn !== null) ? propOut : false; +}; + +/* + * Shortcut to coerce the three font attributes + * + * 'coerce' is a lib.coerce wrapper with implied first three arguments + */ +exports.coerceFont = function(coerce, attr, dfltObj) { + var out = {}; + + dfltObj = dfltObj || {}; + + out.family = coerce(attr + '.family', dfltObj.family); + out.size = coerce(attr + '.size', dfltObj.size); + out.color = coerce(attr + '.color', dfltObj.color); + + return out; +}; + +/** Coerce shortcut for 'hoverinfo' + * handling 1-vs-multi-trace dflt logic + * + * @param {object} traceIn : user trace object + * @param {object} traceOut : full trace object (requires _module ref) + * @param {object} layoutOut : full layout object (require _dataLength ref) + * @return {any} : the coerced value + */ +exports.coerceHoverinfo = function(traceIn, traceOut, layoutOut) { + var moduleAttrs = traceOut._module.attributes; + var attrs = moduleAttrs.hoverinfo ? moduleAttrs : baseTraceAttrs; + + var valObj = attrs.hoverinfo; + var dflt; + + if(layoutOut._dataLength === 1) { + var flags = valObj.dflt === 'all' ? + valObj.flags.slice() : + valObj.dflt.split('+'); + + flags.splice(flags.indexOf('name'), 1); + dflt = flags.join('+'); + } + + return exports.coerce(traceIn, traceOut, attrs, 'hoverinfo', dflt); +}; + +/** Coerce shortcut for [un]selected.marker.opacity, + * which has special default logic, to ensure that it corresponds to the + * default selection behavior while allowing to be overtaken by any other + * [un]selected attribute. + * + * N.B. This must be called *after* coercing all the other [un]selected attrs, + * to give the intended result. + * + * @param {object} traceOut : fullData item + * @param {function} coerce : lib.coerce wrapper with implied first three arguments + */ +exports.coerceSelectionMarkerOpacity = function(traceOut, coerce) { + if(!traceOut.marker) return; + + var mo = traceOut.marker.opacity; + // you can still have a `marker` container with no markers if there's text + if(mo === undefined) return; + + var smoDflt; + var usmoDflt; + + // Don't give [un]selected.marker.opacity a default value if + // marker.opacity is an array: handle this during style step. + // + // Only give [un]selected.marker.opacity a default value if you don't + // set any other [un]selected attributes. + if(!isArrayOrTypedArray(mo) && !traceOut.selected && !traceOut.unselected) { + smoDflt = mo; + usmoDflt = DESELECTDIM * mo; + } + + coerce('selected.marker.opacity', smoDflt); + coerce('unselected.marker.opacity', usmoDflt); +}; + +function validate(value, opts) { + var valObjectDef = exports.valObjectMeta[opts.valType]; + + if(opts.arrayOk && isArrayOrTypedArray(value)) return true; + + if(valObjectDef.validateFunction) { + return valObjectDef.validateFunction(value, opts); + } + + var failed = {}; + var out = failed; + var propMock = { set: function(v) { out = v; } }; + + // 'failed' just something mutable that won't be === anything else + + valObjectDef.coerceFunction(value, propMock, failed, opts); + return out !== failed; +} +exports.validate = validate; + +},{"../components/colorscale/scales":66,"../constants/interactions":148,"../plots/attributes":210,"./array":156,"./mod":176,"./nested_property":177,"./regex":184,"fast-isnumeric":18,"tinycolor2":34}],161:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var d3 = _dereq_('d3'); +var isNumeric = _dereq_('fast-isnumeric'); + +var Loggers = _dereq_('./loggers'); +var mod = _dereq_('./mod').mod; + +var constants = _dereq_('../constants/numerical'); +var BADNUM = constants.BADNUM; +var ONEDAY = constants.ONEDAY; +var ONEHOUR = constants.ONEHOUR; +var ONEMIN = constants.ONEMIN; +var ONESEC = constants.ONESEC; +var EPOCHJD = constants.EPOCHJD; + +var Registry = _dereq_('../registry'); + +var utcFormat = d3.time.format.utc; + +var DATETIME_REGEXP = /^\s*(-?\d\d\d\d|\d\d)(-(\d?\d)(-(\d?\d)([ Tt]([01]?\d|2[0-3])(:([0-5]\d)(:([0-5]\d(\.\d+)?))?(Z|z|[+\-]\d\d:?\d\d)?)?)?)?)?\s*$/m; +// special regex for chinese calendars to support yyyy-mmi-dd etc for intercalary months +var DATETIME_REGEXP_CN = /^\s*(-?\d\d\d\d|\d\d)(-(\d?\di?)(-(\d?\d)([ Tt]([01]?\d|2[0-3])(:([0-5]\d)(:([0-5]\d(\.\d+)?))?(Z|z|[+\-]\d\d:?\d\d)?)?)?)?)?\s*$/m; + +// for 2-digit years, the first year we map them onto +var YFIRST = new Date().getFullYear() - 70; + +function isWorldCalendar(calendar) { + return ( + calendar && + Registry.componentsRegistry.calendars && + typeof calendar === 'string' && calendar !== 'gregorian' + ); +} + +/* + * dateTick0: get the canonical tick for this calendar + * + * bool sunday is for week ticks, shift it to a Sunday. + */ +exports.dateTick0 = function(calendar, sunday) { + if(isWorldCalendar(calendar)) { + return sunday ? + Registry.getComponentMethod('calendars', 'CANONICAL_SUNDAY')[calendar] : + Registry.getComponentMethod('calendars', 'CANONICAL_TICK')[calendar]; + } else { + return sunday ? '2000-01-02' : '2000-01-01'; + } +}; + +/* + * dfltRange: for each calendar, give a valid default range + */ +exports.dfltRange = function(calendar) { + if(isWorldCalendar(calendar)) { + return Registry.getComponentMethod('calendars', 'DFLTRANGE')[calendar]; + } else { + return ['2000-01-01', '2001-01-01']; + } +}; + +// is an object a javascript date? +exports.isJSDate = function(v) { + return typeof v === 'object' && v !== null && typeof v.getTime === 'function'; +}; + +// The absolute limits of our date-time system +// This is a little weird: we use MIN_MS and MAX_MS in dateTime2ms +// but we use dateTime2ms to calculate them (after defining it!) +var MIN_MS, MAX_MS; + +/** + * dateTime2ms - turn a date object or string s into milliseconds + * (relative to 1970-01-01, per javascript standard) + * optional calendar (string) to use a non-gregorian calendar + * + * Returns BADNUM if it doesn't find a date + * + * strings should have the form: + * + * -?YYYY-mm-ddHH:MM:SS.sss? + * + * : space (our normal standard) or T or t (ISO-8601) + * : Z, z, or [+\-]HH:?MM and we THROW IT AWAY + * this format comes from https://tools.ietf.org/html/rfc3339#section-5.6 + * but we allow it even with a space as the separator + * + * May truncate after any full field, and sss can be any length + * even >3 digits, though javascript dates truncate to milliseconds, + * we keep as much as javascript numeric precision can hold, but we only + * report back up to 100 microsecond precision, because most dates support + * this precision (close to 1970 support more, very far away support less) + * + * Expanded to support negative years to -9999 but you must always + * give 4 digits, except for 2-digit positive years which we assume are + * near the present time. + * Note that we follow ISO 8601:2004: there *is* a year 0, which + * is 1BC/BCE, and -1===2BC etc. + * + * World calendars: not all of these *have* agreed extensions to this full range, + * if you have another calendar system but want a date range outside its validity, + * you can use a gregorian date string prefixed with 'G' or 'g'. + * + * Where to cut off 2-digit years between 1900s and 2000s? + * from http://support.microsoft.com/kb/244664: + * 1930-2029 (the most retro of all...) + * but in my mac chrome from eg. d=new Date(Date.parse('8/19/50')): + * 1950-2049 + * by Java, from http://stackoverflow.com/questions/2024273/: + * now-80 - now+19 + * or FileMaker Pro, from + * http://www.filemaker.com/12help/html/add_view_data.4.21.html: + * now-70 - now+29 + * but python strptime etc, via + * http://docs.python.org/py3k/library/time.html: + * 1969-2068 (super forward-looking, but static, not sliding!) + * + * lets go with now-70 to now+29, and if anyone runs into this problem + * they can learn the hard way not to use 2-digit years, as no choice we + * make now will cover all possibilities. mostly this will all be taken + * care of in initial parsing, should only be an issue for hand-entered data + * currently (2016) this range is: + * 1946-2045 + */ +exports.dateTime2ms = function(s, calendar) { + // first check if s is a date object + if(exports.isJSDate(s)) { + // Convert to the UTC milliseconds that give the same + // hours as this date has in the local timezone + var tzOffset = s.getTimezoneOffset() * ONEMIN; + var offsetTweak = (s.getUTCMinutes() - s.getMinutes()) * ONEMIN + + (s.getUTCSeconds() - s.getSeconds()) * ONESEC + + (s.getUTCMilliseconds() - s.getMilliseconds()); + + if(offsetTweak) { + var comb = 3 * ONEMIN; + tzOffset = tzOffset - comb / 2 + mod(offsetTweak - tzOffset + comb / 2, comb); + } + s = Number(s) - tzOffset; + if(s >= MIN_MS && s <= MAX_MS) return s; + return BADNUM; + } + // otherwise only accept strings and numbers + if(typeof s !== 'string' && typeof s !== 'number') return BADNUM; + + s = String(s); + + var isWorld = isWorldCalendar(calendar); + + // to handle out-of-range dates in international calendars, accept + // 'G' as a prefix to force the built-in gregorian calendar. + var s0 = s.charAt(0); + if(isWorld && (s0 === 'G' || s0 === 'g')) { + s = s.substr(1); + calendar = ''; + } + + var isChinese = isWorld && calendar.substr(0, 7) === 'chinese'; + + var match = s.match(isChinese ? DATETIME_REGEXP_CN : DATETIME_REGEXP); + if(!match) return BADNUM; + var y = match[1]; + var m = match[3] || '1'; + var d = Number(match[5] || 1); + var H = Number(match[7] || 0); + var M = Number(match[9] || 0); + var S = Number(match[11] || 0); + + if(isWorld) { + // disallow 2-digit years for world calendars + if(y.length === 2) return BADNUM; + y = Number(y); + + var cDate; + try { + var calInstance = Registry.getComponentMethod('calendars', 'getCal')(calendar); + if(isChinese) { + var isIntercalary = m.charAt(m.length - 1) === 'i'; + m = parseInt(m, 10); + cDate = calInstance.newDate(y, calInstance.toMonthIndex(y, m, isIntercalary), d); + } else { + cDate = calInstance.newDate(y, Number(m), d); + } + } catch(e) { return BADNUM; } // Invalid ... date + + if(!cDate) return BADNUM; + + return ((cDate.toJD() - EPOCHJD) * ONEDAY) + + (H * ONEHOUR) + (M * ONEMIN) + (S * ONESEC); + } + + if(y.length === 2) { + y = (Number(y) + 2000 - YFIRST) % 100 + YFIRST; + } else y = Number(y); + + // new Date uses months from 0; subtract 1 here just so we + // don't have to do it again during the validity test below + m -= 1; + + // javascript takes new Date(0..99,m,d) to mean 1900-1999, so + // to support years 0-99 we need to use setFullYear explicitly + // Note that 2000 is a leap year. + var date = new Date(Date.UTC(2000, m, d, H, M)); + date.setUTCFullYear(y); + + if(date.getUTCMonth() !== m) return BADNUM; + if(date.getUTCDate() !== d) return BADNUM; + + return date.getTime() + S * ONESEC; +}; + +MIN_MS = exports.MIN_MS = exports.dateTime2ms('-9999'); +MAX_MS = exports.MAX_MS = exports.dateTime2ms('9999-12-31 23:59:59.9999'); + +// is string s a date? (see above) +exports.isDateTime = function(s, calendar) { + return (exports.dateTime2ms(s, calendar) !== BADNUM); +}; + +// pad a number with zeroes, to given # of digits before the decimal point +function lpad(val, digits) { + return String(val + Math.pow(10, digits)).substr(1); +} + +/** + * Turn ms into string of the form YYYY-mm-dd HH:MM:SS.ssss + * Crop any trailing zeros in time, except never stop right after hours + * (we could choose to crop '-01' from date too but for now we always + * show the whole date) + * Optional range r is the data range that applies, also in ms. + * If rng is big, the later parts of time will be omitted + */ +var NINETYDAYS = 90 * ONEDAY; +var THREEHOURS = 3 * ONEHOUR; +var FIVEMIN = 5 * ONEMIN; +exports.ms2DateTime = function(ms, r, calendar) { + if(typeof ms !== 'number' || !(ms >= MIN_MS && ms <= MAX_MS)) return BADNUM; + + if(!r) r = 0; + + var msecTenths = Math.floor(mod(ms + 0.05, 1) * 10); + var msRounded = Math.round(ms - msecTenths / 10); + var dateStr, h, m, s, msec10, d; + + if(isWorldCalendar(calendar)) { + var dateJD = Math.floor(msRounded / ONEDAY) + EPOCHJD; + var timeMs = Math.floor(mod(ms, ONEDAY)); + try { + dateStr = Registry.getComponentMethod('calendars', 'getCal')(calendar) + .fromJD(dateJD).formatDate('yyyy-mm-dd'); + } catch(e) { + // invalid date in this calendar - fall back to Gyyyy-mm-dd + dateStr = utcFormat('G%Y-%m-%d')(new Date(msRounded)); + } + + // yyyy does NOT guarantee 4-digit years. YYYY mostly does, but does + // other things for a few calendars, so we can't trust it. Just pad + // it manually (after the '-' if there is one) + if(dateStr.charAt(0) === '-') { + while(dateStr.length < 11) dateStr = '-0' + dateStr.substr(1); + } else { + while(dateStr.length < 10) dateStr = '0' + dateStr; + } + + // TODO: if this is faster, we could use this block for extracting + // the time components of regular gregorian too + h = (r < NINETYDAYS) ? Math.floor(timeMs / ONEHOUR) : 0; + m = (r < NINETYDAYS) ? Math.floor((timeMs % ONEHOUR) / ONEMIN) : 0; + s = (r < THREEHOURS) ? Math.floor((timeMs % ONEMIN) / ONESEC) : 0; + msec10 = (r < FIVEMIN) ? (timeMs % ONESEC) * 10 + msecTenths : 0; + } else { + d = new Date(msRounded); + + dateStr = utcFormat('%Y-%m-%d')(d); + + // <90 days: add hours and minutes - never *only* add hours + h = (r < NINETYDAYS) ? d.getUTCHours() : 0; + m = (r < NINETYDAYS) ? d.getUTCMinutes() : 0; + // <3 hours: add seconds + s = (r < THREEHOURS) ? d.getUTCSeconds() : 0; + // <5 minutes: add ms (plus one extra digit, this is msec*10) + msec10 = (r < FIVEMIN) ? d.getUTCMilliseconds() * 10 + msecTenths : 0; + } + + return includeTime(dateStr, h, m, s, msec10); +}; + +// For converting old-style milliseconds to date strings, +// we use the local timezone rather than UTC like we use +// everywhere else, both for backward compatibility and +// because that's how people mostly use javasript date objects. +// Clip one extra day off our date range though so we can't get +// thrown beyond the range by the timezone shift. +exports.ms2DateTimeLocal = function(ms) { + if(!(ms >= MIN_MS + ONEDAY && ms <= MAX_MS - ONEDAY)) return BADNUM; + + var msecTenths = Math.floor(mod(ms + 0.05, 1) * 10); + var d = new Date(Math.round(ms - msecTenths / 10)); + var dateStr = d3.time.format('%Y-%m-%d')(d); + var h = d.getHours(); + var m = d.getMinutes(); + var s = d.getSeconds(); + var msec10 = d.getUTCMilliseconds() * 10 + msecTenths; + + return includeTime(dateStr, h, m, s, msec10); +}; + +function includeTime(dateStr, h, m, s, msec10) { + // include each part that has nonzero data in or after it + if(h || m || s || msec10) { + dateStr += ' ' + lpad(h, 2) + ':' + lpad(m, 2); + if(s || msec10) { + dateStr += ':' + lpad(s, 2); + if(msec10) { + var digits = 4; + while(msec10 % 10 === 0) { + digits -= 1; + msec10 /= 10; + } + dateStr += '.' + lpad(msec10, digits); + } + } + } + return dateStr; +} + +// normalize date format to date string, in case it starts as +// a Date object or milliseconds +// optional dflt is the return value if cleaning fails +exports.cleanDate = function(v, dflt, calendar) { + // let us use cleanDate to provide a missing default without an error + if(v === BADNUM) return dflt; + if(exports.isJSDate(v) || (typeof v === 'number' && isFinite(v))) { + // do not allow milliseconds (old) or jsdate objects (inherently + // described as gregorian dates) with world calendars + if(isWorldCalendar(calendar)) { + Loggers.error('JS Dates and milliseconds are incompatible with world calendars', v); + return dflt; + } + + // NOTE: if someone puts in a year as a number rather than a string, + // this will mistakenly convert it thinking it's milliseconds from 1970 + // that is: '2012' -> Jan. 1, 2012, but 2012 -> 2012 epoch milliseconds + v = exports.ms2DateTimeLocal(+v); + if(!v && dflt !== undefined) return dflt; + } else if(!exports.isDateTime(v, calendar)) { + Loggers.error('unrecognized date', v); + return dflt; + } + return v; +}; + +/* + * Date formatting for ticks and hovertext + */ + +/* + * modDateFormat: Support world calendars, and add one item to + * d3's vocabulary: + * %{n}f where n is the max number of digits of fractional seconds + */ +var fracMatch = /%\d?f/g; +function modDateFormat(fmt, x, formatter, calendar) { + fmt = fmt.replace(fracMatch, function(match) { + var digits = Math.min(+(match.charAt(1)) || 6, 6); + var fracSecs = ((x / 1000 % 1) + 2) + .toFixed(digits) + .substr(2).replace(/0+$/, '') || '0'; + return fracSecs; + }); + + var d = new Date(Math.floor(x + 0.05)); + + if(isWorldCalendar(calendar)) { + try { + fmt = Registry.getComponentMethod('calendars', 'worldCalFmt')(fmt, x, calendar); + } catch(e) { + return 'Invalid'; + } + } + return formatter(fmt)(d); +} + +/* + * formatTime: create a time string from: + * x: milliseconds + * tr: tickround ('M', 'S', or # digits) + * only supports UTC times (where every day is 24 hours and 0 is at midnight) + */ +var MAXSECONDS = [59, 59.9, 59.99, 59.999, 59.9999]; +function formatTime(x, tr) { + var timePart = mod(x + 0.05, ONEDAY); + + var timeStr = lpad(Math.floor(timePart / ONEHOUR), 2) + ':' + + lpad(mod(Math.floor(timePart / ONEMIN), 60), 2); + + if(tr !== 'M') { + if(!isNumeric(tr)) tr = 0; // should only be 'S' + + /* + * this is a weird one - and shouldn't come up unless people + * monkey with tick0 in weird ways, but we need to do something! + * IN PARTICULAR we had better not display garbage (see below) + * for numbers we always round to the nearest increment of the + * precision we're showing, and this seems like the right way to + * handle seconds and milliseconds, as they have a decimal point + * and people will interpret that to mean rounding like numbers. + * but for larger increments we floor the value: it's always + * 2013 until the ball drops on the new year. We could argue about + * which field it is where we start rounding (should 12:08:59 + * round to 12:09 if we're stopping at minutes?) but for now I'll + * say we round seconds but floor everything else. BUT that means + * we need to never round up to 60 seconds, ie 23:59:60 + */ + var sec = Math.min(mod(x / ONESEC, 60), MAXSECONDS[tr]); + + var secStr = (100 + sec).toFixed(tr).substr(1); + if(tr > 0) { + secStr = secStr.replace(/0+$/, '').replace(/[\.]$/, ''); + } + + timeStr += ':' + secStr; + } + return timeStr; +} + +/* + * formatDate: turn a date into tick or hover label text. + * + * x: milliseconds, the value to convert + * fmt: optional, an explicit format string (d3 format, even for world calendars) + * tr: tickround ('y', 'm', 'd', 'M', 'S', or # digits) + * used if no explicit fmt is provided + * formatter: locale-aware d3 date formatter for standard gregorian calendars + * should be the result of exports.getD3DateFormat(gd) + * calendar: optional string, the world calendar system to use + * + * returns the date/time as a string, potentially with the leading portion + * on a separate line (after '\n') + * Note that this means if you provide an explicit format which includes '\n' + * the axis may choose to strip things after it when they don't change from + * one tick to the next (as it does with automatic formatting) + */ +exports.formatDate = function(x, fmt, tr, formatter, calendar, extraFormat) { + calendar = isWorldCalendar(calendar) && calendar; + + if(!fmt) { + if(tr === 'y') fmt = extraFormat.year; + else if(tr === 'm') fmt = extraFormat.month; + else if(tr === 'd') { + fmt = extraFormat.dayMonth + '\n' + extraFormat.year; + } else { + return formatTime(x, tr) + '\n' + modDateFormat(extraFormat.dayMonthYear, x, formatter, calendar); + } + } + + return modDateFormat(fmt, x, formatter, calendar); +}; + +/* + * incrementMonth: make a new milliseconds value from the given one, + * having changed the month + * + * special case for world calendars: multiples of 12 are treated as years, + * even for calendar systems that don't have (always or ever) 12 months/year + * TODO: perhaps we need a different code for year increments to support this? + * + * ms (number): the initial millisecond value + * dMonth (int): the (signed) number of months to shift + * calendar (string): the calendar system to use + * + * changing month does not (and CANNOT) always preserve day, since + * months have different lengths. The worst example of this is: + * d = new Date(1970,0,31); d.setMonth(1) -> Feb 31 turns into Mar 3 + * + * But we want to be able to iterate over the last day of each month, + * regardless of what its number is. + * So shift 3 days forward, THEN set the new month, then unshift: + * 1/31 -> 2/28 (or 29) -> 3/31 -> 4/30 -> ... + * + * Note that odd behavior still exists if you start from the 26th-28th: + * 1/28 -> 2/28 -> 3/31 + * but at least you can't shift any dates into the wrong month, + * and ticks on these days incrementing by month would be very unusual + */ +var THREEDAYS = 3 * ONEDAY; +exports.incrementMonth = function(ms, dMonth, calendar) { + calendar = isWorldCalendar(calendar) && calendar; + + // pull time out and operate on pure dates, then add time back at the end + // this gives maximum precision - not that we *normally* care if we're + // incrementing by month, but better to be safe! + var timeMs = mod(ms, ONEDAY); + ms = Math.round(ms - timeMs); + + if(calendar) { + try { + var dateJD = Math.round(ms / ONEDAY) + EPOCHJD; + var calInstance = Registry.getComponentMethod('calendars', 'getCal')(calendar); + var cDate = calInstance.fromJD(dateJD); + + if(dMonth % 12) calInstance.add(cDate, dMonth, 'm'); + else calInstance.add(cDate, dMonth / 12, 'y'); + + return (cDate.toJD() - EPOCHJD) * ONEDAY + timeMs; + } catch(e) { + Loggers.error('invalid ms ' + ms + ' in calendar ' + calendar); + // then keep going in gregorian even though the result will be 'Invalid' + } + } + + var y = new Date(ms + THREEDAYS); + return y.setUTCMonth(y.getUTCMonth() + dMonth) + timeMs - THREEDAYS; +}; + +/* + * findExactDates: what fraction of data is exact days, months, or years? + * + * data: array of millisecond values + * calendar (string) the calendar to test against + */ +exports.findExactDates = function(data, calendar) { + var exactYears = 0; + var exactMonths = 0; + var exactDays = 0; + var blankCount = 0; + var d; + var di; + + var calInstance = ( + isWorldCalendar(calendar) && + Registry.getComponentMethod('calendars', 'getCal')(calendar) + ); + + for(var i = 0; i < data.length; i++) { + di = data[i]; + + // not date data at all + if(!isNumeric(di)) { + blankCount ++; + continue; + } + + // not an exact date + if(di % ONEDAY) continue; + + if(calInstance) { + try { + d = calInstance.fromJD(di / ONEDAY + EPOCHJD); + if(d.day() === 1) { + if(d.month() === 1) exactYears++; + else exactMonths++; + } else exactDays++; + } catch(e) { + // invalid date in this calendar - ignore it here. + } + } else { + d = new Date(di); + if(d.getUTCDate() === 1) { + if(d.getUTCMonth() === 0) exactYears++; + else exactMonths++; + } else exactDays++; + } + } + exactMonths += exactYears; + exactDays += exactMonths; + + var dataCount = data.length - blankCount; + + return { + exactYears: exactYears / dataCount, + exactMonths: exactMonths / dataCount, + exactDays: exactDays / dataCount + }; +}; + +},{"../constants/numerical":149,"../registry":258,"./loggers":173,"./mod":176,"d3":16,"fast-isnumeric":18}],162:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); +var loggers = _dereq_('./loggers'); + +/** + * Allow referencing a graph DOM element either directly + * or by its id string + * + * @param {HTMLDivElement|string} gd: a graph element or its id + * + * @returns {HTMLDivElement} the DOM element of the graph + */ +function getGraphDiv(gd) { + var gdElement; + + if(typeof gd === 'string') { + gdElement = document.getElementById(gd); + + if(gdElement === null) { + throw new Error('No DOM element with id \'' + gd + '\' exists on the page.'); + } + + return gdElement; + } else if(gd === null || gd === undefined) { + throw new Error('DOM element provided is null or undefined'); + } + + // otherwise assume that gd is a DOM element + return gd; +} + +function isPlotDiv(el) { + var el3 = d3.select(el); + return el3.node() instanceof HTMLElement && + el3.size() && + el3.classed('js-plotly-plot'); +} + +function removeElement(el) { + var elParent = el && el.parentNode; + if(elParent) elParent.removeChild(el); +} + +/** + * for dynamically adding style rules + * makes one stylesheet that contains all rules added + * by all calls to this function + */ +function addStyleRule(selector, styleString) { + addRelatedStyleRule('global', selector, styleString); +} + +/** + * for dynamically adding style rules + * to a stylesheet uniquely identified by a uid + */ +function addRelatedStyleRule(uid, selector, styleString) { + var id = 'plotly.js-style-' + uid; + var style = document.getElementById(id); + if(!style) { + style = document.createElement('style'); + style.setAttribute('id', id); + // WebKit hack :( + style.appendChild(document.createTextNode('')); + document.head.appendChild(style); + } + var styleSheet = style.sheet; + + if(styleSheet.insertRule) { + styleSheet.insertRule(selector + '{' + styleString + '}', 0); + } else if(styleSheet.addRule) { + styleSheet.addRule(selector, styleString, 0); + } else loggers.warn('addStyleRule failed'); +} + +/** + * to remove from the page a stylesheet identified by a given uid + */ +function deleteRelatedStyleRule(uid) { + var id = 'plotly.js-style-' + uid; + var style = document.getElementById(id); + if(style) removeElement(style); +} + +module.exports = { + getGraphDiv: getGraphDiv, + isPlotDiv: isPlotDiv, + removeElement: removeElement, + addStyleRule: addStyleRule, + addRelatedStyleRule: addRelatedStyleRule, + deleteRelatedStyleRule: deleteRelatedStyleRule +}; + +},{"./loggers":173,"d3":16}],163:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +/* global jQuery:false */ + +var EventEmitter = _dereq_('events').EventEmitter; + +var Events = { + + init: function(plotObj) { + /* + * If we have already instantiated an emitter for this plot + * return early. + */ + if(plotObj._ev instanceof EventEmitter) return plotObj; + + var ev = new EventEmitter(); + var internalEv = new EventEmitter(); + + /* + * Assign to plot._ev while we still live in a land + * where plot is a DOM element with stuff attached to it. + * In the future we can make plot the event emitter itself. + */ + plotObj._ev = ev; + + /* + * Create a second event handler that will manage events *internally*. + * This allows parts of plotly to respond to thing like relayout without + * having to use the user-facing event handler. They cannot peacefully + * coexist on the same handler because a user invoking + * plotObj.removeAllListeners() would detach internal events, breaking + * plotly. + */ + plotObj._internalEv = internalEv; + + /* + * Assign bound methods from the ev to the plot object. These methods + * will reference the 'this' of plot._ev even though they are methods + * of plot. This will keep the event machinery away from the plot object + * which currently is often a DOM element but presents an API that will + * continue to function when plot becomes an emitter. Not all EventEmitter + * methods have been bound to `plot` as some do not currently add value to + * the Plotly event API. + */ + plotObj.on = ev.on.bind(ev); + plotObj.once = ev.once.bind(ev); + plotObj.removeListener = ev.removeListener.bind(ev); + plotObj.removeAllListeners = ev.removeAllListeners.bind(ev); + + /* + * Create functions for managing internal events. These are *only* triggered + * by the mirroring of external events via the emit function. + */ + plotObj._internalOn = internalEv.on.bind(internalEv); + plotObj._internalOnce = internalEv.once.bind(internalEv); + plotObj._removeInternalListener = internalEv.removeListener.bind(internalEv); + plotObj._removeAllInternalListeners = internalEv.removeAllListeners.bind(internalEv); + + /* + * We must wrap emit to continue to support JQuery events. The idea + * is to check to see if the user is using JQuery events, if they are + * we emit JQuery events to trigger user handlers as well as the EventEmitter + * events. + */ + plotObj.emit = function(event, data) { + if(typeof jQuery !== 'undefined') { + jQuery(plotObj).trigger(event, data); + } + + ev.emit(event, data); + internalEv.emit(event, data); + }; + + return plotObj; + }, + + /* + * This function behaves like jQuery's triggerHandler. It calls + * all handlers for a particular event and returns the return value + * of the LAST handler. This function also triggers jQuery's + * triggerHandler for backwards compatibility. + */ + triggerHandler: function(plotObj, event, data) { + var jQueryHandlerValue; + var nodeEventHandlerValue; + + /* + * If jQuery exists run all its handlers for this event and + * collect the return value of the LAST handler function + */ + if(typeof jQuery !== 'undefined') { + jQueryHandlerValue = jQuery(plotObj).triggerHandler(event, data); + } + + /* + * Now run all the node style event handlers + */ + var ev = plotObj._ev; + if(!ev) return jQueryHandlerValue; + + var handlers = ev._events[event]; + if(!handlers) return jQueryHandlerValue; + + // making sure 'this' is the EventEmitter instance + function apply(handler) { + // The 'once' case, we can't just call handler() as we need + // the return value here. So, + // - remove handler + // - call listener and grab return value! + // - stash 'fired' key to not call handler twice + if(handler.listener) { + ev.removeListener(event, handler.listener); + if(!handler.fired) { + handler.fired = true; + return handler.listener.apply(ev, [data]); + } + } else { + return handler.apply(ev, [data]); + } + } + + // handlers can be function or an array of functions + handlers = Array.isArray(handlers) ? handlers : [handlers]; + + var i; + for(i = 0; i < handlers.length - 1; i++) { + apply(handlers[i]); + } + // now call the final handler and collect its value + nodeEventHandlerValue = apply(handlers[i]); + + /* + * Return either the jQuery handler value if it exists or the + * nodeEventHandler value. jQuery event value supersedes nodejs + * events for backwards compatibility reasons. + */ + return jQueryHandlerValue !== undefined ? + jQueryHandlerValue : + nodeEventHandlerValue; + }, + + purge: function(plotObj) { + delete plotObj._ev; + delete plotObj.on; + delete plotObj.once; + delete plotObj.removeListener; + delete plotObj.removeAllListeners; + delete plotObj.emit; + + delete plotObj._ev; + delete plotObj._internalEv; + delete plotObj._internalOn; + delete plotObj._internalOnce; + delete plotObj._removeInternalListener; + delete plotObj._removeAllInternalListeners; + + return plotObj; + } + +}; + +module.exports = Events; + +},{"events":15}],164:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var isPlainObject = _dereq_('./is_plain_object.js'); +var isArray = Array.isArray; + +function primitivesLoopSplice(source, target) { + var i, value; + for(i = 0; i < source.length; i++) { + value = source[i]; + if(value !== null && typeof(value) === 'object') { + return false; + } + if(value !== void(0)) { + target[i] = value; + } + } + return true; +} + +exports.extendFlat = function() { + return _extend(arguments, false, false, false); +}; + +exports.extendDeep = function() { + return _extend(arguments, true, false, false); +}; + +exports.extendDeepAll = function() { + return _extend(arguments, true, true, false); +}; + +exports.extendDeepNoArrays = function() { + return _extend(arguments, true, false, true); +}; + +/* + * Inspired by https://github.com/justmoon/node-extend/blob/master/index.js + * All credit to the jQuery authors for perfecting this amazing utility. + * + * API difference with jQuery version: + * - No optional boolean (true -> deep extend) first argument, + * use `extendFlat` for first-level only extend and + * use `extendDeep` for a deep extend. + * + * Other differences with jQuery version: + * - Uses a modern (and faster) isPlainObject routine. + * - Expected to work with object {} and array [] arguments only. + * - Does not check for circular structure. + * FYI: jQuery only does a check across one level. + * Warning: this might result in infinite loops. + * + */ +function _extend(inputs, isDeep, keepAllKeys, noArrayCopies) { + var target = inputs[0]; + var length = inputs.length; + + var input, key, src, copy, copyIsArray, clone, allPrimitives; + + // TODO does this do the right thing for typed arrays? + + if(length === 2 && isArray(target) && isArray(inputs[1]) && target.length === 0) { + allPrimitives = primitivesLoopSplice(inputs[1], target); + + if(allPrimitives) { + return target; + } else { + target.splice(0, target.length); // reset target and continue to next block + } + } + + for(var i = 1; i < length; i++) { + input = inputs[i]; + + for(key in input) { + src = target[key]; + copy = input[key]; + + if(noArrayCopies && isArray(copy)) { + // Stop early and just transfer the array if array copies are disallowed: + + target[key] = copy; + } else if(isDeep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) { + // recurse if we're merging plain objects or arrays + + if(copyIsArray) { + copyIsArray = false; + clone = src && isArray(src) ? src : []; + } else { + clone = src && isPlainObject(src) ? src : {}; + } + + // never move original objects, clone them + target[key] = _extend([clone, copy], isDeep, keepAllKeys, noArrayCopies); + } else if(typeof copy !== 'undefined' || keepAllKeys) { + // don't bring in undefined values, except for extendDeepAll + + target[key] = copy; + } + } + } + + return target; +} + +},{"./is_plain_object.js":170}],165:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + + +/** + * Return news array containing only the unique items + * found in input array. + * + * IMPORTANT: Note that items are considered unique + * if `String({})` is unique. For example; + * + * Lib.filterUnique([ { a: 1 }, { b: 2 } ]) + * + * returns [{ a: 1 }] + * + * and + * + * Lib.filterUnique([ '1', 1 ]) + * + * returns ['1'] + * + * + * @param {array} array base array + * @return {array} new filtered array + */ +module.exports = function filterUnique(array) { + var seen = {}; + var out = []; + var j = 0; + + for(var i = 0; i < array.length; i++) { + var item = array[i]; + + if(seen[item] !== 1) { + seen[item] = 1; + out[j++] = item; + } + } + + return out; +}; + +},{}],166:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +/** Filter out object items with visible !== true + * insider array container. + * + * @param {array of objects} container + * @return {array of objects} of length <= container + * + */ +module.exports = function filterVisible(container) { + var filterFn = isCalcData(container) ? calcDataFilter : baseFilter; + var out = []; + + for(var i = 0; i < container.length; i++) { + var item = container[i]; + if(filterFn(item)) out.push(item); + } + + return out; +}; + +function baseFilter(item) { + return item.visible === true; +} + +function calcDataFilter(item) { + var trace = item[0].trace; + return trace.visible === true && trace._length !== 0; +} + +function isCalcData(cont) { + return ( + Array.isArray(cont) && + Array.isArray(cont[0]) && + cont[0][0] && + cont[0][0].trace + ); +} + +},{}],167:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var mod = _dereq_('./mod').mod; + +/* + * look for intersection of two line segments + * (1->2 and 3->4) - returns array [x,y] if they do, null if not + */ +exports.segmentsIntersect = segmentsIntersect; +function segmentsIntersect(x1, y1, x2, y2, x3, y3, x4, y4) { + var a = x2 - x1; + var b = x3 - x1; + var c = x4 - x3; + var d = y2 - y1; + var e = y3 - y1; + var f = y4 - y3; + var det = a * f - c * d; + // parallel lines? intersection is undefined + // ignore the case where they are colinear + if(det === 0) return null; + var t = (b * f - c * e) / det; + var u = (b * d - a * e) / det; + // segments do not intersect? + if(u < 0 || u > 1 || t < 0 || t > 1) return null; + + return {x: x1 + a * t, y: y1 + d * t}; +} + +/* + * find the minimum distance between two line segments (1->2 and 3->4) + */ +exports.segmentDistance = function segmentDistance(x1, y1, x2, y2, x3, y3, x4, y4) { + if(segmentsIntersect(x1, y1, x2, y2, x3, y3, x4, y4)) return 0; + + // the two segments and their lengths squared + var x12 = x2 - x1; + var y12 = y2 - y1; + var x34 = x4 - x3; + var y34 = y4 - y3; + var ll12 = x12 * x12 + y12 * y12; + var ll34 = x34 * x34 + y34 * y34; + + // calculate distance squared, then take the sqrt at the very end + var dist2 = Math.min( + perpDistance2(x12, y12, ll12, x3 - x1, y3 - y1), + perpDistance2(x12, y12, ll12, x4 - x1, y4 - y1), + perpDistance2(x34, y34, ll34, x1 - x3, y1 - y3), + perpDistance2(x34, y34, ll34, x2 - x3, y2 - y3) + ); + + return Math.sqrt(dist2); +}; + +/* + * distance squared from segment ab to point c + * [xab, yab] is the vector b-a + * [xac, yac] is the vector c-a + * llab is the length squared of (b-a), just to simplify calculation + */ +function perpDistance2(xab, yab, llab, xac, yac) { + var fcAB = (xac * xab + yac * yab); + if(fcAB < 0) { + // point c is closer to point a + return xac * xac + yac * yac; + } else if(fcAB > llab) { + // point c is closer to point b + var xbc = xac - xab; + var ybc = yac - yab; + return xbc * xbc + ybc * ybc; + } else { + // perpendicular distance is the shortest + var crossProduct = xac * yab - yac * xab; + return crossProduct * crossProduct / llab; + } +} + +// a very short-term cache for getTextLocation, just because +// we're often looping over the same locations multiple times +// invalidated as soon as we look at a different path +var locationCache, workingPath, workingTextWidth; + +// turn a path and position along it into x, y, and angle for the given text +exports.getTextLocation = function getTextLocation(path, totalPathLen, positionOnPath, textWidth) { + if(path !== workingPath || textWidth !== workingTextWidth) { + locationCache = {}; + workingPath = path; + workingTextWidth = textWidth; + } + if(locationCache[positionOnPath]) { + return locationCache[positionOnPath]; + } + + // for the angle, use points on the path separated by the text width + // even though due to curvature, the text will cover a bit more than that + var p0 = path.getPointAtLength(mod(positionOnPath - textWidth / 2, totalPathLen)); + var p1 = path.getPointAtLength(mod(positionOnPath + textWidth / 2, totalPathLen)); + // note: atan handles 1/0 nicely + var theta = Math.atan((p1.y - p0.y) / (p1.x - p0.x)); + // center the text at 2/3 of the center position plus 1/3 the p0/p1 midpoint + // that's the average position of this segment, assuming it's roughly quadratic + var pCenter = path.getPointAtLength(mod(positionOnPath, totalPathLen)); + var x = (pCenter.x * 4 + p0.x + p1.x) / 6; + var y = (pCenter.y * 4 + p0.y + p1.y) / 6; + + var out = {x: x, y: y, theta: theta}; + locationCache[positionOnPath] = out; + return out; +}; + +exports.clearLocationCache = function() { + workingPath = null; +}; + +/* + * Find the segment of `path` that's within the visible area + * given by `bounds` {left, right, top, bottom}, to within a + * precision of `buffer` px + * + * returns: undefined if nothing is visible, else object: + * { + * min: position where the path first enters bounds, or 0 if it + * starts within bounds + * max: position where the path last exits bounds, or the path length + * if it finishes within bounds + * len: max - min, ie the length of visible path + * total: the total path length - just included so the caller doesn't + * need to call path.getTotalLength() again + * isClosed: true iff the start and end points of the path are both visible + * and are at the same point + * } + * + * Works by starting from either end and repeatedly finding the distance from + * that point to the plot area, and if it's outside the plot, moving along the + * path by that distance (because the plot must be at least that far away on + * the path). Note that if a path enters, exits, and re-enters the plot, we + * will not capture this behavior. + */ +exports.getVisibleSegment = function getVisibleSegment(path, bounds, buffer) { + var left = bounds.left; + var right = bounds.right; + var top = bounds.top; + var bottom = bounds.bottom; + + var pMin = 0; + var pTotal = path.getTotalLength(); + var pMax = pTotal; + + var pt0, ptTotal; + + function getDistToPlot(len) { + var pt = path.getPointAtLength(len); + + // hold on to the start and end points for `closed` + if(len === 0) pt0 = pt; + else if(len === pTotal) ptTotal = pt; + + var dx = (pt.x < left) ? left - pt.x : (pt.x > right ? pt.x - right : 0); + var dy = (pt.y < top) ? top - pt.y : (pt.y > bottom ? pt.y - bottom : 0); + return Math.sqrt(dx * dx + dy * dy); + } + + var distToPlot = getDistToPlot(pMin); + while(distToPlot) { + pMin += distToPlot + buffer; + if(pMin > pMax) return; + distToPlot = getDistToPlot(pMin); + } + + distToPlot = getDistToPlot(pMax); + while(distToPlot) { + pMax -= distToPlot + buffer; + if(pMin > pMax) return; + distToPlot = getDistToPlot(pMax); + } + + return { + min: pMin, + max: pMax, + len: pMax - pMin, + total: pTotal, + isClosed: pMin === 0 && pMax === pTotal && + Math.abs(pt0.x - ptTotal.x) < 0.1 && + Math.abs(pt0.y - ptTotal.y) < 0.1 + }; +}; + +/** + * Find point on SVG path corresponding to a given constraint coordinate + * + * @param {SVGPathElement} path + * @param {Number} val : constraint coordinate value + * @param {String} coord : 'x' or 'y' the constraint coordinate + * @param {Object} opts : + * - {Number} pathLength : supply total path length before hand + * - {Number} tolerance + * - {Number} iterationLimit + * @return {SVGPoint} + */ +exports.findPointOnPath = function findPointOnPath(path, val, coord, opts) { + opts = opts || {}; + + var pathLength = opts.pathLength || path.getTotalLength(); + var tolerance = opts.tolerance || 1e-3; + var iterationLimit = opts.iterationLimit || 30; + + // if path starts at a val greater than the path tail (like on vertical violins), + // we must flip the sign of the computed diff. + var mul = path.getPointAtLength(0)[coord] > path.getPointAtLength(pathLength)[coord] ? -1 : 1; + + var i = 0; + var b0 = 0; + var b1 = pathLength; + var mid; + var pt; + var diff; + + while(i < iterationLimit) { + mid = (b0 + b1) / 2; + pt = path.getPointAtLength(mid); + diff = pt[coord] - val; + + if(Math.abs(diff) < tolerance) { + return pt; + } else { + if(mul * diff > 0) { + b1 = mid; + } else { + b0 = mid; + } + i++; + } + } + return pt; +}; + +},{"./mod":176}],168:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +// Simple helper functions +// none of these need any external deps + +module.exports = function identity(d) { return d; }; + +},{}],169:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); +var isNumeric = _dereq_('fast-isnumeric'); + +var numConstants = _dereq_('../constants/numerical'); +var FP_SAFE = numConstants.FP_SAFE; +var BADNUM = numConstants.BADNUM; + +var lib = module.exports = {}; + +lib.nestedProperty = _dereq_('./nested_property'); +lib.keyedContainer = _dereq_('./keyed_container'); +lib.relativeAttr = _dereq_('./relative_attr'); +lib.isPlainObject = _dereq_('./is_plain_object'); +lib.toLogRange = _dereq_('./to_log_range'); +lib.relinkPrivateKeys = _dereq_('./relink_private'); + +var arrayModule = _dereq_('./array'); +lib.isTypedArray = arrayModule.isTypedArray; +lib.isArrayOrTypedArray = arrayModule.isArrayOrTypedArray; +lib.isArray1D = arrayModule.isArray1D; +lib.ensureArray = arrayModule.ensureArray; +lib.concat = arrayModule.concat; +lib.maxRowLength = arrayModule.maxRowLength; +lib.minRowLength = arrayModule.minRowLength; + +var modModule = _dereq_('./mod'); +lib.mod = modModule.mod; +lib.modHalf = modModule.modHalf; + +var coerceModule = _dereq_('./coerce'); +lib.valObjectMeta = coerceModule.valObjectMeta; +lib.coerce = coerceModule.coerce; +lib.coerce2 = coerceModule.coerce2; +lib.coerceFont = coerceModule.coerceFont; +lib.coerceHoverinfo = coerceModule.coerceHoverinfo; +lib.coerceSelectionMarkerOpacity = coerceModule.coerceSelectionMarkerOpacity; +lib.validate = coerceModule.validate; + +var datesModule = _dereq_('./dates'); +lib.dateTime2ms = datesModule.dateTime2ms; +lib.isDateTime = datesModule.isDateTime; +lib.ms2DateTime = datesModule.ms2DateTime; +lib.ms2DateTimeLocal = datesModule.ms2DateTimeLocal; +lib.cleanDate = datesModule.cleanDate; +lib.isJSDate = datesModule.isJSDate; +lib.formatDate = datesModule.formatDate; +lib.incrementMonth = datesModule.incrementMonth; +lib.dateTick0 = datesModule.dateTick0; +lib.dfltRange = datesModule.dfltRange; +lib.findExactDates = datesModule.findExactDates; +lib.MIN_MS = datesModule.MIN_MS; +lib.MAX_MS = datesModule.MAX_MS; + +var searchModule = _dereq_('./search'); +lib.findBin = searchModule.findBin; +lib.sorterAsc = searchModule.sorterAsc; +lib.sorterDes = searchModule.sorterDes; +lib.distinctVals = searchModule.distinctVals; +lib.roundUp = searchModule.roundUp; +lib.sort = searchModule.sort; +lib.findIndexOfMin = searchModule.findIndexOfMin; + +var statsModule = _dereq_('./stats'); +lib.aggNums = statsModule.aggNums; +lib.len = statsModule.len; +lib.mean = statsModule.mean; +lib.median = statsModule.median; +lib.midRange = statsModule.midRange; +lib.variance = statsModule.variance; +lib.stdev = statsModule.stdev; +lib.interp = statsModule.interp; + +var matrixModule = _dereq_('./matrix'); +lib.init2dArray = matrixModule.init2dArray; +lib.transposeRagged = matrixModule.transposeRagged; +lib.dot = matrixModule.dot; +lib.translationMatrix = matrixModule.translationMatrix; +lib.rotationMatrix = matrixModule.rotationMatrix; +lib.rotationXYMatrix = matrixModule.rotationXYMatrix; +lib.apply2DTransform = matrixModule.apply2DTransform; +lib.apply2DTransform2 = matrixModule.apply2DTransform2; + +var anglesModule = _dereq_('./angles'); +lib.deg2rad = anglesModule.deg2rad; +lib.rad2deg = anglesModule.rad2deg; +lib.angleDelta = anglesModule.angleDelta; +lib.angleDist = anglesModule.angleDist; +lib.isFullCircle = anglesModule.isFullCircle; +lib.isAngleInsideSector = anglesModule.isAngleInsideSector; +lib.isPtInsideSector = anglesModule.isPtInsideSector; +lib.pathArc = anglesModule.pathArc; +lib.pathSector = anglesModule.pathSector; +lib.pathAnnulus = anglesModule.pathAnnulus; + +var anchorUtils = _dereq_('./anchor_utils'); +lib.isLeftAnchor = anchorUtils.isLeftAnchor; +lib.isCenterAnchor = anchorUtils.isCenterAnchor; +lib.isRightAnchor = anchorUtils.isRightAnchor; +lib.isTopAnchor = anchorUtils.isTopAnchor; +lib.isMiddleAnchor = anchorUtils.isMiddleAnchor; +lib.isBottomAnchor = anchorUtils.isBottomAnchor; + +var geom2dModule = _dereq_('./geometry2d'); +lib.segmentsIntersect = geom2dModule.segmentsIntersect; +lib.segmentDistance = geom2dModule.segmentDistance; +lib.getTextLocation = geom2dModule.getTextLocation; +lib.clearLocationCache = geom2dModule.clearLocationCache; +lib.getVisibleSegment = geom2dModule.getVisibleSegment; +lib.findPointOnPath = geom2dModule.findPointOnPath; + +var extendModule = _dereq_('./extend'); +lib.extendFlat = extendModule.extendFlat; +lib.extendDeep = extendModule.extendDeep; +lib.extendDeepAll = extendModule.extendDeepAll; +lib.extendDeepNoArrays = extendModule.extendDeepNoArrays; + +var loggersModule = _dereq_('./loggers'); +lib.log = loggersModule.log; +lib.warn = loggersModule.warn; +lib.error = loggersModule.error; + +var regexModule = _dereq_('./regex'); +lib.counterRegex = regexModule.counter; + +var throttleModule = _dereq_('./throttle'); +lib.throttle = throttleModule.throttle; +lib.throttleDone = throttleModule.done; +lib.clearThrottle = throttleModule.clear; + +var domModule = _dereq_('./dom'); +lib.getGraphDiv = domModule.getGraphDiv; +lib.isPlotDiv = domModule.isPlotDiv; +lib.removeElement = domModule.removeElement; +lib.addStyleRule = domModule.addStyleRule; +lib.addRelatedStyleRule = domModule.addRelatedStyleRule; +lib.deleteRelatedStyleRule = domModule.deleteRelatedStyleRule; + +lib.clearResponsive = _dereq_('./clear_responsive'); + +lib.makeTraceGroups = _dereq_('./make_trace_groups'); + +lib._ = _dereq_('./localize'); + +lib.notifier = _dereq_('./notifier'); + +lib.filterUnique = _dereq_('./filter_unique'); +lib.filterVisible = _dereq_('./filter_visible'); +lib.pushUnique = _dereq_('./push_unique'); + +lib.cleanNumber = _dereq_('./clean_number'); + +lib.ensureNumber = function ensureNumber(v) { + if(!isNumeric(v)) return BADNUM; + v = Number(v); + if(v < -FP_SAFE || v > FP_SAFE) return BADNUM; + return isNumeric(v) ? Number(v) : BADNUM; +}; + +/** + * Is v a valid array index? Accepts numeric strings as well as numbers. + * + * @param {any} v: the value to test + * @param {Optional[integer]} len: the array length we are indexing + * + * @return {bool}: v is a valid array index + */ +lib.isIndex = function(v, len) { + if(len !== undefined && v >= len) return false; + return isNumeric(v) && (v >= 0) && (v % 1 === 0); +}; + +lib.noop = _dereq_('./noop'); +lib.identity = _dereq_('./identity'); + +/** + * create an array of length 'cnt' filled with 'v' at all indices + * + * @param {any} v + * @param {number} cnt + * @return {array} + */ +lib.repeat = function(v, cnt) { + var out = new Array(cnt); + for(var i = 0; i < cnt; i++) { + out[i] = v; + } + return out; +}; + +/** + * swap x and y of the same attribute in container cont + * specify attr with a ? in place of x/y + * you can also swap other things than x/y by providing part1 and part2 + */ +lib.swapAttrs = function(cont, attrList, part1, part2) { + if(!part1) part1 = 'x'; + if(!part2) part2 = 'y'; + for(var i = 0; i < attrList.length; i++) { + var attr = attrList[i]; + var xp = lib.nestedProperty(cont, attr.replace('?', part1)); + var yp = lib.nestedProperty(cont, attr.replace('?', part2)); + var temp = xp.get(); + xp.set(yp.get()); + yp.set(temp); + } +}; + +/** + * SVG painter's algo worked around with reinsertion + */ +lib.raiseToTop = function raiseToTop(elem) { + elem.parentNode.appendChild(elem); +}; + +/** + * cancel a possibly pending transition; returned selection may be used by caller + */ +lib.cancelTransition = function(selection) { + return selection.transition().duration(0); +}; + +// constrain - restrict a number v to be between v0 and v1 +lib.constrain = function(v, v0, v1) { + if(v0 > v1) return Math.max(v1, Math.min(v0, v)); + return Math.max(v0, Math.min(v1, v)); +}; + +/** + * do two bounding boxes from getBoundingClientRect, + * ie {left,right,top,bottom,width,height}, overlap? + * takes optional padding pixels + */ +lib.bBoxIntersect = function(a, b, pad) { + pad = pad || 0; + return (a.left <= b.right + pad && + b.left <= a.right + pad && + a.top <= b.bottom + pad && + b.top <= a.bottom + pad); +}; + +/* + * simpleMap: alternative to Array.map that only + * passes on the element and up to 2 extra args you + * provide (but not the array index or the whole array) + * + * array: the array to map it to + * func: the function to apply + * x1, x2: optional extra args + */ +lib.simpleMap = function(array, func, x1, x2) { + var len = array.length; + var out = new Array(len); + for(var i = 0; i < len; i++) out[i] = func(array[i], x1, x2); + return out; +}; + +/** + * Random string generator + * + * @param {object} existing + * pass in strings to avoid as keys with truthy values + * @param {int} bits + * bits of information in the output string, default 24 + * @param {int} base + * base of string representation, default 16. Should be a power of 2. + */ +lib.randstr = function randstr(existing, bits, base, _recursion) { + if(!base) base = 16; + if(bits === undefined) bits = 24; + if(bits <= 0) return '0'; + + var digits = Math.log(Math.pow(2, bits)) / Math.log(base); + var res = ''; + var i, b, x; + + for(i = 2; digits === Infinity; i *= 2) { + digits = Math.log(Math.pow(2, bits / i)) / Math.log(base) * i; + } + + var rem = digits - Math.floor(digits); + + for(i = 0; i < Math.floor(digits); i++) { + x = Math.floor(Math.random() * base).toString(base); + res = x + res; + } + + if(rem) { + b = Math.pow(base, rem); + x = Math.floor(Math.random() * b).toString(base); + res = x + res; + } + + var parsed = parseInt(res, base); + if((existing && existing[res]) || + (parsed !== Infinity && parsed >= Math.pow(2, bits))) { + if(_recursion > 10) { + lib.warn('randstr failed uniqueness'); + return res; + } + return randstr(existing, bits, base, (_recursion || 0) + 1); + } else return res; +}; + +lib.OptionControl = function(opt, optname) { + /* + * An environment to contain all option setters and + * getters that collectively modify opts. + * + * You can call up opts from any function in new object + * as this.optname || this.opt + * + * See FitOpts for example of usage + */ + if(!opt) opt = {}; + if(!optname) optname = 'opt'; + + var self = {}; + self.optionList = []; + + self._newoption = function(optObj) { + optObj[optname] = opt; + self[optObj.name] = optObj; + self.optionList.push(optObj); + }; + + self['_' + optname] = opt; + return self; +}; + +/** + * lib.smooth: smooth arrayIn by convolving with + * a hann window with given full width at half max + * bounce the ends in, so the output has the same length as the input + */ +lib.smooth = function(arrayIn, FWHM) { + FWHM = Math.round(FWHM) || 0; // only makes sense for integers + if(FWHM < 2) return arrayIn; + + var alen = arrayIn.length; + var alen2 = 2 * alen; + var wlen = 2 * FWHM - 1; + var w = new Array(wlen); + var arrayOut = new Array(alen); + var i; + var j; + var k; + var v; + + // first make the window array + for(i = 0; i < wlen; i++) { + w[i] = (1 - Math.cos(Math.PI * (i + 1) / FWHM)) / (2 * FWHM); + } + + // now do the convolution + for(i = 0; i < alen; i++) { + v = 0; + for(j = 0; j < wlen; j++) { + k = i + j + 1 - FWHM; + + // multibounce + if(k < -alen) k -= alen2 * Math.round(k / alen2); + else if(k >= alen2) k -= alen2 * Math.floor(k / alen2); + + // single bounce + if(k < 0) k = - 1 - k; + else if(k >= alen) k = alen2 - 1 - k; + + v += arrayIn[k] * w[j]; + } + arrayOut[i] = v; + } + + return arrayOut; +}; + +/** + * syncOrAsync: run a sequence of functions synchronously + * as long as its returns are not promises (ie have no .then) + * includes one argument arg to send to all functions... + * this is mainly just to prevent us having to make wrapper functions + * when the only purpose of the wrapper is to reference gd + * and a final step to be executed at the end + * TODO: if there's an error and everything is sync, + * this doesn't happen yet because we want to make sure + * that it gets reported + */ +lib.syncOrAsync = function(sequence, arg, finalStep) { + var ret, fni; + + function continueAsync() { + return lib.syncOrAsync(sequence, arg, finalStep); + } + + while(sequence.length) { + fni = sequence.splice(0, 1)[0]; + ret = fni(arg); + + if(ret && ret.then) { + return ret.then(continueAsync) + .then(undefined, lib.promiseError); + } + } + + return finalStep && finalStep(arg); +}; + + +/** + * Helper to strip trailing slash, from + * http://stackoverflow.com/questions/6680825/return-string-without-trailing-slash + */ +lib.stripTrailingSlash = function(str) { + if(str.substr(-1) === '/') return str.substr(0, str.length - 1); + return str; +}; + +lib.noneOrAll = function(containerIn, containerOut, attrList) { + /** + * some attributes come together, so if you have one of them + * in the input, you should copy the default values of the others + * to the input as well. + */ + if(!containerIn) return; + + var hasAny = false; + var hasAll = true; + var i; + var val; + + for(i = 0; i < attrList.length; i++) { + val = containerIn[attrList[i]]; + if(val !== undefined && val !== null) hasAny = true; + else hasAll = false; + } + + if(hasAny && !hasAll) { + for(i = 0; i < attrList.length; i++) { + containerIn[attrList[i]] = containerOut[attrList[i]]; + } + } +}; + +/** merges calcdata field (given by cdAttr) with traceAttr values + * + * N.B. Loop over minimum of cd.length and traceAttr.length + * i.e. it does not try to fill in beyond traceAttr.length-1 + * + * @param {array} traceAttr : trace attribute + * @param {object} cd : calcdata trace + * @param {string} cdAttr : calcdata key + */ +lib.mergeArray = function(traceAttr, cd, cdAttr, fn) { + var hasFn = typeof fn === 'function'; + if(lib.isArrayOrTypedArray(traceAttr)) { + var imax = Math.min(traceAttr.length, cd.length); + for(var i = 0; i < imax; i++) { + var v = traceAttr[i]; + cd[i][cdAttr] = hasFn ? fn(v) : v; + } + } +}; + +// cast numbers to positive numbers, returns 0 if not greater than 0 +lib.mergeArrayCastPositive = function(traceAttr, cd, cdAttr) { + return lib.mergeArray(traceAttr, cd, cdAttr, function(v) { + var w = +v; + return !isFinite(w) ? 0 : w > 0 ? w : 0; + }); +}; + +/** fills calcdata field (given by cdAttr) with traceAttr values + * or function of traceAttr values (e.g. some fallback) + * + * N.B. Loops over all cd items. + * + * @param {array} traceAttr : trace attribute + * @param {object} cd : calcdata trace + * @param {string} cdAttr : calcdata key + * @param {function} [fn] : optional function to apply to each array item + */ +lib.fillArray = function(traceAttr, cd, cdAttr, fn) { + fn = fn || lib.identity; + + if(lib.isArrayOrTypedArray(traceAttr)) { + for(var i = 0; i < cd.length; i++) { + cd[i][cdAttr] = fn(traceAttr[i]); + } + } +}; + +/** Handler for trace-wide vs per-point options + * + * @param {object} trace : (full) trace object + * @param {number} ptNumber : index of the point in question + * @param {string} astr : attribute string + * @param {function} [fn] : optional function to apply to each array item + * + * @return {any} + */ +lib.castOption = function(trace, ptNumber, astr, fn) { + fn = fn || lib.identity; + + var val = lib.nestedProperty(trace, astr).get(); + + if(lib.isArrayOrTypedArray(val)) { + if(Array.isArray(ptNumber) && lib.isArrayOrTypedArray(val[ptNumber[0]])) { + return fn(val[ptNumber[0]][ptNumber[1]]); + } else { + return fn(val[ptNumber]); + } + } else { + return val; + } +}; + +/** Extract option from calcdata item, correctly falling back to + * trace value if not found. + * + * @param {object} calcPt : calcdata[i][j] item + * @param {object} trace : (full) trace object + * @param {string} calcKey : calcdata key + * @param {string} traceKey : aka trace attribute string + * @return {any} + */ +lib.extractOption = function(calcPt, trace, calcKey, traceKey) { + if(calcKey in calcPt) return calcPt[calcKey]; + + // fallback to trace value, + // must check if value isn't itself an array + // which means the trace attribute has a corresponding + // calcdata key, but its value is falsy + var traceVal = lib.nestedProperty(trace, traceKey).get(); + if(!Array.isArray(traceVal)) return traceVal; +}; + +function makePtIndex2PtNumber(indexToPoints) { + var ptIndex2ptNumber = {}; + for(var k in indexToPoints) { + var pts = indexToPoints[k]; + for(var j = 0; j < pts.length; j++) { + ptIndex2ptNumber[pts[j]] = +k; + } + } + return ptIndex2ptNumber; +} + +/** Tag selected calcdata items + * + * N.B. note that point 'index' corresponds to input data array index + * whereas 'number' is its post-transform version. + * + * @param {array} calcTrace + * @param {object} trace + * - selectedpoints {array} + * - _indexToPoints {object} + * @param {ptNumber2cdIndex} ptNumber2cdIndex (optional) + * optional map object for trace types that do not have 1-to-1 point number to + * calcdata item index correspondence (e.g. histogram) + */ +lib.tagSelected = function(calcTrace, trace, ptNumber2cdIndex) { + var selectedpoints = trace.selectedpoints; + var indexToPoints = trace._indexToPoints; + var ptIndex2ptNumber; + + // make pt index-to-number map object, which takes care of transformed traces + if(indexToPoints) { + ptIndex2ptNumber = makePtIndex2PtNumber(indexToPoints); + } + + function isCdIndexValid(v) { + return v !== undefined && v < calcTrace.length; + } + + for(var i = 0; i < selectedpoints.length; i++) { + var ptIndex = selectedpoints[i]; + + if(lib.isIndex(ptIndex)) { + var ptNumber = ptIndex2ptNumber ? ptIndex2ptNumber[ptIndex] : ptIndex; + var cdIndex = ptNumber2cdIndex ? ptNumber2cdIndex[ptNumber] : ptNumber; + + if(isCdIndexValid(cdIndex)) { + calcTrace[cdIndex].selected = 1; + } + } + } +}; + +lib.selIndices2selPoints = function(trace) { + var selectedpoints = trace.selectedpoints; + var indexToPoints = trace._indexToPoints; + + if(indexToPoints) { + var ptIndex2ptNumber = makePtIndex2PtNumber(indexToPoints); + var out = []; + + for(var i = 0; i < selectedpoints.length; i++) { + var ptIndex = selectedpoints[i]; + if(lib.isIndex(ptIndex)) { + var ptNumber = ptIndex2ptNumber[ptIndex]; + if(lib.isIndex(ptNumber)) { + out.push(ptNumber); + } + } + } + + return out; + } else { + return selectedpoints; + } +}; + +/** Returns target as set by 'target' transform attribute + * + * @param {object} trace : full trace object + * @param {object} transformOpts : transform option object + * - target (string} : + * either an attribute string referencing an array in the trace object, or + * a set array. + * + * @return {array or false} : the target array (NOT a copy!!) or false if invalid + */ +lib.getTargetArray = function(trace, transformOpts) { + var target = transformOpts.target; + + if(typeof target === 'string' && target) { + var array = lib.nestedProperty(trace, target).get(); + return Array.isArray(array) ? array : false; + } else if(Array.isArray(target)) { + return target; + } + + return false; +}; + +/** + * modified version of jQuery's extend to strip out private objs and functions, + * and cut arrays down to first or 1 elements + * because extend-like algorithms are hella slow + * obj2 is assumed to already be clean of these things (including no arrays) + */ +lib.minExtend = function(obj1, obj2) { + var objOut = {}; + if(typeof obj2 !== 'object') obj2 = {}; + var arrayLen = 3; + var keys = Object.keys(obj1); + var i, k, v; + + for(i = 0; i < keys.length; i++) { + k = keys[i]; + v = obj1[k]; + if(k.charAt(0) === '_' || typeof v === 'function') continue; + else if(k === 'module') objOut[k] = v; + else if(Array.isArray(v)) { + if(k === 'colorscale') { + objOut[k] = v.slice(); + } else { + objOut[k] = v.slice(0, arrayLen); + } + } else if(lib.isTypedArray(v)) { + objOut[k] = v.subarray(0, arrayLen); + } else if(v && (typeof v === 'object')) objOut[k] = lib.minExtend(obj1[k], obj2[k]); + else objOut[k] = v; + } + + keys = Object.keys(obj2); + for(i = 0; i < keys.length; i++) { + k = keys[i]; + v = obj2[k]; + if(typeof v !== 'object' || !(k in objOut) || typeof objOut[k] !== 'object') { + objOut[k] = v; + } + } + + return objOut; +}; + +lib.titleCase = function(s) { + return s.charAt(0).toUpperCase() + s.substr(1); +}; + +lib.containsAny = function(s, fragments) { + for(var i = 0; i < fragments.length; i++) { + if(s.indexOf(fragments[i]) !== -1) return true; + } + return false; +}; + +lib.isIE = function() { + return typeof window.navigator.msSaveBlob !== 'undefined'; +}; + +var IS_IE9_OR_BELOW_REGEX = /MSIE [1-9]\./; +lib.isIE9orBelow = function() { + return lib.isIE() && IS_IE9_OR_BELOW_REGEX.test(window.navigator.userAgent); +}; + +var IS_SAFARI_REGEX = /Version\/[\d\.]+.*Safari/; +lib.isSafari = function() { + return IS_SAFARI_REGEX.test(window.navigator.userAgent); +}; + +/** + * Duck typing to recognize a d3 selection, mostly for IE9's benefit + * because it doesn't handle instanceof like modern browsers + */ +lib.isD3Selection = function(obj) { + return obj && (typeof obj.classed === 'function'); +}; + +/** + * Append element to DOM only if not present. + * + * @param {d3 selection} parent : parent selection of the element in question + * @param {string} nodeType : node type of element to append + * @param {string} className (optional) : class name of element in question + * @param {fn} enterFn (optional) : optional fn applied to entering elements only + * @return {d3 selection} selection of new layer + * + * Previously, we were using the following pattern: + * + * ``` + * var sel = parent.selectAll('.' + className) + * .data([0]); + * + * sel.enter().append(nodeType) + * .classed(className, true); + * + * return sel; + * ``` + * + * in numerous places in our codebase to achieve the same behavior. + * + * The logic below performs much better, mostly as we are using + * `.select` instead `.selectAll` that is `querySelector` instead of + * `querySelectorAll`. + * + */ +lib.ensureSingle = function(parent, nodeType, className, enterFn) { + var sel = parent.select(nodeType + (className ? '.' + className : '')); + if(sel.size()) return sel; + + var layer = parent.append(nodeType); + if(className) layer.classed(className, true); + if(enterFn) layer.call(enterFn); + + return layer; +}; + +/** + * Same as Lib.ensureSingle, but using id as selector. + * This version is mostly used for clipPath nodes. + * + * @param {d3 selection} parent : parent selection of the element in question + * @param {string} nodeType : node type of element to append + * @param {string} id : id of element in question + * @param {fn} enterFn (optional) : optional fn applied to entering elements only + * @return {d3 selection} selection of new layer + */ +lib.ensureSingleById = function(parent, nodeType, id, enterFn) { + var sel = parent.select(nodeType + '#' + id); + if(sel.size()) return sel; + + var layer = parent.append(nodeType).attr('id', id); + if(enterFn) layer.call(enterFn); + + return layer; +}; + +/** + * Converts a string path to an object. + * + * When given a string containing an array element, it will create a `null` + * filled array of the given size. + * + * @example + * lib.objectFromPath('nested.test[2].path', 'value'); + * // returns { nested: { test: [null, null, { path: 'value' }]} + * + * @param {string} path to nested value + * @param {*} any value to be set + * + * @return {Object} the constructed object with a full nested path + */ +lib.objectFromPath = function(path, value) { + var keys = path.split('.'); + var tmpObj; + var obj = tmpObj = {}; + + for(var i = 0; i < keys.length; i++) { + var key = keys[i]; + var el = null; + + var parts = keys[i].match(/(.*)\[([0-9]+)\]/); + + if(parts) { + key = parts[1]; + el = parts[2]; + + tmpObj = tmpObj[key] = []; + + if(i === keys.length - 1) { + tmpObj[el] = value; + } else { + tmpObj[el] = {}; + } + + tmpObj = tmpObj[el]; + } else { + if(i === keys.length - 1) { + tmpObj[key] = value; + } else { + tmpObj[key] = {}; + } + + tmpObj = tmpObj[key]; + } + } + + return obj; +}; + +/** + * Iterate through an object in-place, converting dotted properties to objects. + * + * Examples: + * + * lib.expandObjectPaths({'nested.test.path': 'value'}); + * => { nested: { test: {path: 'value'}}} + * + * It also handles array notation, e.g.: + * + * lib.expandObjectPaths({'foo[1].bar': 'value'}); + * => { foo: [null, {bar: value}] } + * + * It handles merges the results when two properties are specified in parallel: + * + * lib.expandObjectPaths({'foo[1].bar': 10, 'foo[0].bar': 20}); + * => { foo: [{bar: 10}, {bar: 20}] } + * + * It does NOT, however, merge mulitple mutliply-nested arrays:: + * + * lib.expandObjectPaths({'marker[1].range[1]': 5, 'marker[1].range[0]': 4}) + * => { marker: [null, {range: 4}] } + */ + +// Store this to avoid recompiling regex on *every* prop since this may happen many +// many times for animations. Could maybe be inside the function. Not sure about +// scoping vs. recompilation tradeoff, but at least it's not just inlining it into +// the inner loop. +var dottedPropertyRegex = /^([^\[\.]+)\.(.+)?/; +var indexedPropertyRegex = /^([^\.]+)\[([0-9]+)\](\.)?(.+)?/; + +lib.expandObjectPaths = function(data) { + var match, key, prop, datum, idx, dest, trailingPath; + if(typeof data === 'object' && !Array.isArray(data)) { + for(key in data) { + if(data.hasOwnProperty(key)) { + if((match = key.match(dottedPropertyRegex))) { + datum = data[key]; + prop = match[1]; + + delete data[key]; + + data[prop] = lib.extendDeepNoArrays(data[prop] || {}, lib.objectFromPath(key, lib.expandObjectPaths(datum))[prop]); + } else if((match = key.match(indexedPropertyRegex))) { + datum = data[key]; + + prop = match[1]; + idx = parseInt(match[2]); + + delete data[key]; + + data[prop] = data[prop] || []; + + if(match[3] === '.') { + // This is the case where theere are subsequent properties into which + // we must recurse, e.g. transforms[0].value + trailingPath = match[4]; + dest = data[prop][idx] = data[prop][idx] || {}; + + // NB: Extend deep no arrays prevents this from working on multiple + // nested properties in the same object, e.g. + // + // { + // foo[0].bar[1].range + // foo[0].bar[0].range + // } + // + // In this case, the extendDeepNoArrays will overwrite one array with + // the other, so that both properties *will not* be present in the + // result. Fixing this would require a more intelligent tracking + // of changes and merging than extendDeepNoArrays currently accomplishes. + lib.extendDeepNoArrays(dest, lib.objectFromPath(trailingPath, lib.expandObjectPaths(datum))); + } else { + // This is the case where this property is the end of the line, + // e.g. xaxis.range[0] + data[prop][idx] = lib.expandObjectPaths(datum); + } + } else { + data[key] = lib.expandObjectPaths(data[key]); + } + } + } + } + + return data; +}; + +/** + * Converts value to string separated by the provided separators. + * + * @example + * lib.numSeparate(2016, '.,'); + * // returns '2016' + * + * @example + * lib.numSeparate(3000, '.,', true); + * // returns '3,000' + * + * @example + * lib.numSeparate(1234.56, '|,') + * // returns '1,234|56' + * + * @param {string|number} value the value to be converted + * @param {string} separators string of decimal, then thousands separators + * @param {boolean} separatethousands boolean, 4-digit integers are separated if true + * + * @return {string} the value that has been separated + */ +lib.numSeparate = function(value, separators, separatethousands) { + if(!separatethousands) separatethousands = false; + + if(typeof separators !== 'string' || separators.length === 0) { + throw new Error('Separator string required for formatting!'); + } + + if(typeof value === 'number') { + value = String(value); + } + + var thousandsRe = /(\d+)(\d{3})/; + var decimalSep = separators.charAt(0); + var thouSep = separators.charAt(1); + + var x = value.split('.'); + var x1 = x[0]; + var x2 = x.length > 1 ? decimalSep + x[1] : ''; + + // Years are ignored for thousands separators + if(thouSep && (x.length > 1 || x1.length > 4 || separatethousands)) { + while(thousandsRe.test(x1)) { + x1 = x1.replace(thousandsRe, '$1' + thouSep + '$2'); + } + } + + return x1 + x2; +}; + +lib.TEMPLATE_STRING_REGEX = /%{([^\s%{}:]*)([:|\|][^}]*)?}/g; +var SIMPLE_PROPERTY_REGEX = /^\w*$/; + +/** + * Substitute values from an object into a string + * + * Examples: + * Lib.templateString('name: %{trace}', {trace: 'asdf'}) --> 'name: asdf' + * Lib.templateString('name: %{trace[0].name}', {trace: [{name: 'asdf'}]}) --> 'name: asdf' + * + * @param {string} input string containing %{...} template strings + * @param {obj} data object containing substitution values + * + * @return {string} templated string + */ +lib.templateString = function(string, obj) { + // Not all that useful, but cache nestedProperty instantiation + // just in case it speeds things up *slightly*: + var getterCache = {}; + + return string.replace(lib.TEMPLATE_STRING_REGEX, function(dummy, key) { + if(SIMPLE_PROPERTY_REGEX.test(key)) { + return obj[key] || ''; + } + getterCache[key] = getterCache[key] || lib.nestedProperty(obj, key).get; + return getterCache[key]() || ''; + }); +}; + +var hovertemplateWarnings = { + max: 10, + count: 0, + name: 'hovertemplate' +}; +lib.hovertemplateString = function() { + return templateFormatString.apply(hovertemplateWarnings, arguments); +}; + +var texttemplateWarnings = { + max: 10, + count: 0, + name: 'texttemplate' +}; +lib.texttemplateString = function() { + return templateFormatString.apply(texttemplateWarnings, arguments); +}; + +var TEMPLATE_STRING_FORMAT_SEPARATOR = /^[:|\|]/; +/** + * Substitute values from an object into a string and optionally formats them using d3-format, + * or fallback to associated labels. + * + * Examples: + * Lib.hovertemplateString('name: %{trace}', {trace: 'asdf'}) --> 'name: asdf' + * Lib.hovertemplateString('name: %{trace[0].name}', {trace: [{name: 'asdf'}]}) --> 'name: asdf' + * Lib.hovertemplateString('price: %{y:$.2f}', {y: 1}) --> 'price: $1.00' + * + * @param {string} input string containing %{...:...} template strings + * @param {obj} data object containing fallback text when no formatting is specified, ex.: {yLabel: 'formattedYValue'} + * @param {obj} d3 locale + * @param {obj} data objects containing substitution values + * + * @return {string} templated string + */ +function templateFormatString(string, labels, d3locale) { + var opts = this; + var args = arguments; + if(!labels) labels = {}; + // Not all that useful, but cache nestedProperty instantiation + // just in case it speeds things up *slightly*: + var getterCache = {}; + + return string.replace(lib.TEMPLATE_STRING_REGEX, function(match, key, format) { + var obj, value, i; + for(i = 3; i < args.length; i++) { + obj = args[i]; + if(!obj) continue; + if(obj.hasOwnProperty(key)) { + value = obj[key]; + break; + } + + if(!SIMPLE_PROPERTY_REGEX.test(key)) { + value = getterCache[key] || lib.nestedProperty(obj, key).get(); + if(value) getterCache[key] = value; + } + if(value !== undefined) break; + } + + if(value === undefined && opts) { + if(opts.count < opts.max) { + lib.warn('Variable \'' + key + '\' in ' + opts.name + ' could not be found!'); + value = match; + } + + if(opts.count === opts.max) { + lib.warn('Too many ' + opts.name + ' warnings - additional warnings will be suppressed'); + } + opts.count++; + + return match; + } + + if(format) { + var fmt; + if(format[0] === ':') { + fmt = d3locale ? d3locale.numberFormat : d3.format; + value = fmt(format.replace(TEMPLATE_STRING_FORMAT_SEPARATOR, ''))(value); + } + + if(format[0] === '|') { + fmt = d3locale ? d3locale.timeFormat.utc : d3.time.format.utc; + var ms = lib.dateTime2ms(value); + value = lib.formatDate(ms, format.replace(TEMPLATE_STRING_FORMAT_SEPARATOR, ''), false, fmt); + } + } else { + if(labels.hasOwnProperty(key + 'Label')) value = labels[key + 'Label']; + } + return value; + }); +} + +/* + * alphanumeric string sort, tailored for subplot IDs like scene2, scene10, x10y13 etc + */ +var char0 = 48; +var char9 = 57; +lib.subplotSort = function(a, b) { + var l = Math.min(a.length, b.length) + 1; + var numA = 0; + var numB = 0; + for(var i = 0; i < l; i++) { + var charA = a.charCodeAt(i) || 0; + var charB = b.charCodeAt(i) || 0; + var isNumA = charA >= char0 && charA <= char9; + var isNumB = charB >= char0 && charB <= char9; + + if(isNumA) numA = 10 * numA + charA - char0; + if(isNumB) numB = 10 * numB + charB - char0; + + if(!isNumA || !isNumB) { + if(numA !== numB) return numA - numB; + if(charA !== charB) return charA - charB; + } + } + return numB - numA; +}; + +// repeatable pseudorandom generator +var randSeed = 2000000000; + +lib.seedPseudoRandom = function() { + randSeed = 2000000000; +}; + +lib.pseudoRandom = function() { + var lastVal = randSeed; + randSeed = (69069 * randSeed + 1) % 4294967296; + // don't let consecutive vals be too close together + // gets away from really trying to be random, in favor of better local uniformity + if(Math.abs(randSeed - lastVal) < 429496729) return lib.pseudoRandom(); + return randSeed / 4294967296; +}; + + +/** Fill hover 'pointData' container with 'correct' hover text value + * + * - If trace hoverinfo contains a 'text' flag and hovertext is not set, + * the text elements will be seen in the hover labels. + * + * - If trace hoverinfo contains a 'text' flag and hovertext is set, + * hovertext takes precedence over text + * i.e. the hoverinfo elements will be seen in the hover labels + * + * @param {object} calcPt + * @param {object} trace + * @param {object || array} contOut (mutated here) + */ +lib.fillText = function(calcPt, trace, contOut) { + var fill = Array.isArray(contOut) ? + function(v) { contOut.push(v); } : + function(v) { contOut.text = v; }; + + var htx = lib.extractOption(calcPt, trace, 'htx', 'hovertext'); + if(lib.isValidTextValue(htx)) return fill(htx); + + var tx = lib.extractOption(calcPt, trace, 'tx', 'text'); + if(lib.isValidTextValue(tx)) return fill(tx); +}; + +// accept all truthy values and 0 (which gets cast to '0' in the hover labels) +lib.isValidTextValue = function(v) { + return v || v === 0; +}; + +/** + * @param {number} ratio + * @param {number} n (number of decimal places) + */ +lib.formatPercent = function(ratio, n) { + n = n || 0; + var str = (Math.round(100 * ratio * Math.pow(10, n)) * Math.pow(0.1, n)).toFixed(n) + '%'; + for(var i = 0; i < n; i++) { + if(str.indexOf('.') !== -1) { + str = str.replace('0%', '%'); + str = str.replace('.%', '%'); + } + } + return str; +}; + +lib.isHidden = function(gd) { + var display = window.getComputedStyle(gd).display; + return !display || display === 'none'; +}; + +lib.getTextTransform = function(opts) { + var textX = opts.textX; + var textY = opts.textY; + var targetX = opts.targetX; + var targetY = opts.targetY; + var scale = opts.scale; + var rotate = opts.rotate; + + var transformScale; + var transformRotate; + var transformTranslate; + + if(scale < 1) transformScale = 'scale(' + scale + ') '; + else { + scale = 1; + transformScale = ''; + } + + transformRotate = (rotate) ? + 'rotate(' + rotate + ' ' + textX + ' ' + textY + ') ' : ''; + + // Note that scaling also affects the center of the text box + var translateX = (targetX - scale * textX); + var translateY = (targetY - scale * textY); + transformTranslate = 'translate(' + translateX + ' ' + translateY + ')'; + + return transformTranslate + transformScale + transformRotate; +}; + +},{"../constants/numerical":149,"./anchor_utils":154,"./angles":155,"./array":156,"./clean_number":157,"./clear_responsive":159,"./coerce":160,"./dates":161,"./dom":162,"./extend":164,"./filter_unique":165,"./filter_visible":166,"./geometry2d":167,"./identity":168,"./is_plain_object":170,"./keyed_container":171,"./localize":172,"./loggers":173,"./make_trace_groups":174,"./matrix":175,"./mod":176,"./nested_property":177,"./noop":178,"./notifier":179,"./push_unique":182,"./regex":184,"./relative_attr":185,"./relink_private":186,"./search":187,"./stats":189,"./throttle":191,"./to_log_range":192,"d3":16,"fast-isnumeric":18}],170:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +// more info: http://stackoverflow.com/questions/18531624/isplainobject-thing +module.exports = function isPlainObject(obj) { + // We need to be a little less strict in the `imagetest` container because + // of how async image requests are handled. + // + // N.B. isPlainObject(new Constructor()) will return true in `imagetest` + if(window && window.process && window.process.versions) { + return Object.prototype.toString.call(obj) === '[object Object]'; + } + + return ( + Object.prototype.toString.call(obj) === '[object Object]' && + Object.getPrototypeOf(obj) === Object.prototype + ); +}; + +},{}],171:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var nestedProperty = _dereq_('./nested_property'); + +var SIMPLE_PROPERTY_REGEX = /^\w*$/; + +// bitmask for deciding what's updated. Sometimes the name needs to be updated, +// sometimes the value needs to be updated, and sometimes both do. This is just +// a simple way to track what's updated such that it's a simple OR operation to +// assimilate new updates. +// +// The only exception is the UNSET bit that tracks when we need to explicitly +// unset and remove the property. This concrn arises because of the special +// way in which nestedProperty handles null/undefined. When you specify `null`, +// it prunes any unused items in the tree. I ran into some issues with it getting +// null vs undefined confused, so UNSET is just a bit that forces the property +// update to send `null`, removing the property explicitly rather than setting +// it to undefined. +var NONE = 0; +var NAME = 1; +var VALUE = 2; +var BOTH = 3; +var UNSET = 4; + +module.exports = function keyedContainer(baseObj, path, keyName, valueName) { + keyName = keyName || 'name'; + valueName = valueName || 'value'; + var i, arr, baseProp; + var changeTypes = {}; + + if(path && path.length) { + baseProp = nestedProperty(baseObj, path); + arr = baseProp.get(); + } else { + arr = baseObj; + } + + path = path || ''; + + // Construct an index: + var indexLookup = {}; + if(arr) { + for(i = 0; i < arr.length; i++) { + indexLookup[arr[i][keyName]] = i; + } + } + + var isSimpleValueProp = SIMPLE_PROPERTY_REGEX.test(valueName); + + var obj = { + set: function(name, value) { + var changeType = value === null ? UNSET : NONE; + + // create the base array if necessary + if(!arr) { + if(!baseProp || changeType === UNSET) return; + + arr = []; + baseProp.set(arr); + } + + var idx = indexLookup[name]; + if(idx === undefined) { + if(changeType === UNSET) return; + + changeType = changeType | BOTH; + idx = arr.length; + indexLookup[name] = idx; + } else if(value !== (isSimpleValueProp ? arr[idx][valueName] : nestedProperty(arr[idx], valueName).get())) { + changeType = changeType | VALUE; + } + + var newValue = arr[idx] = arr[idx] || {}; + newValue[keyName] = name; + + if(isSimpleValueProp) { + newValue[valueName] = value; + } else { + nestedProperty(newValue, valueName).set(value); + } + + // If it's not an unset, force that bit to be unset. This is all related to the fact + // that undefined and null are a bit specially implemented in nestedProperties. + if(value !== null) { + changeType = changeType & ~UNSET; + } + + changeTypes[idx] = changeTypes[idx] | changeType; + + return obj; + }, + get: function(name) { + if(!arr) return; + + var idx = indexLookup[name]; + + if(idx === undefined) { + return undefined; + } else if(isSimpleValueProp) { + return arr[idx][valueName]; + } else { + return nestedProperty(arr[idx], valueName).get(); + } + }, + rename: function(name, newName) { + var idx = indexLookup[name]; + + if(idx === undefined) return obj; + changeTypes[idx] = changeTypes[idx] | NAME; + + indexLookup[newName] = idx; + delete indexLookup[name]; + + arr[idx][keyName] = newName; + + return obj; + }, + remove: function(name) { + var idx = indexLookup[name]; + + if(idx === undefined) return obj; + + var object = arr[idx]; + if(Object.keys(object).length > 2) { + // This object contains more than just the key/value, so unset + // the value without modifying the entry otherwise: + changeTypes[idx] = changeTypes[idx] | VALUE; + return obj.set(name, null); + } + + if(isSimpleValueProp) { + for(i = idx; i < arr.length; i++) { + changeTypes[i] = changeTypes[i] | BOTH; + } + for(i = idx; i < arr.length; i++) { + indexLookup[arr[i][keyName]]--; + } + arr.splice(idx, 1); + delete(indexLookup[name]); + } else { + // Perform this update *strictly* so we can check whether the result's + // been pruned. If so, it's a removal. If not, it's a value unset only. + nestedProperty(object, valueName).set(null); + + // Now check if the top level nested property has any keys left. If so, + // the object still has values so we only want to unset the key. If not, + // the entire object can be removed since there's no other data. + // var topLevelKeys = Object.keys(object[valueName.split('.')[0]] || []); + + changeTypes[idx] = changeTypes[idx] | VALUE | UNSET; + } + + return obj; + }, + constructUpdate: function() { + var astr, idx; + var update = {}; + var changed = Object.keys(changeTypes); + for(var i = 0; i < changed.length; i++) { + idx = changed[i]; + astr = path + '[' + idx + ']'; + if(arr[idx]) { + if(changeTypes[idx] & NAME) { + update[astr + '.' + keyName] = arr[idx][keyName]; + } + if(changeTypes[idx] & VALUE) { + if(isSimpleValueProp) { + update[astr + '.' + valueName] = (changeTypes[idx] & UNSET) ? null : arr[idx][valueName]; + } else { + update[astr + '.' + valueName] = (changeTypes[idx] & UNSET) ? null : nestedProperty(arr[idx], valueName).get(); + } + } + } else { + update[astr] = null; + } + } + + return update; + } + }; + + return obj; +}; + +},{"./nested_property":177}],172:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Registry = _dereq_('../registry'); + +/** + * localize: translate a string for the current locale + * + * @param {object} gd: the graphDiv for context + * gd._context.locale determines the language (& optional region/country) + * the dictionary for each locale may either be supplied in + * gd._context.locales or globally via Plotly.register + * @param {string} s: the string to translate + */ +module.exports = function localize(gd, s) { + var locale = gd._context.locale; + + /* + * Priority of lookup: + * contextDicts[locale], + * registeredDicts[locale], + * contextDicts[baseLocale], (if baseLocale is distinct) + * registeredDicts[baseLocale] + * Return the first translation we find. + * This way if you have a regionalization you are allowed to specify + * only what's different from the base locale, everything else will + * fall back on the base. + */ + for(var i = 0; i < 2; i++) { + var locales = gd._context.locales; + for(var j = 0; j < 2; j++) { + var dict = (locales[locale] || {}).dictionary; + if(dict) { + var out = dict[s]; + if(out) return out; + } + locales = Registry.localeRegistry; + } + + var baseLocale = locale.split('-')[0]; + if(baseLocale === locale) break; + locale = baseLocale; + } + + return s; +}; + +},{"../registry":258}],173:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +/* eslint-disable no-console */ + +var dfltConfig = _dereq_('../plot_api/plot_config').dfltConfig; + +var loggers = module.exports = {}; + +/** + * ------------------------------------------ + * debugging tools + * ------------------------------------------ + */ + +loggers.log = function() { + if(dfltConfig.logging > 1) { + var messages = ['LOG:']; + + for(var i = 0; i < arguments.length; i++) { + messages.push(arguments[i]); + } + + apply(console.trace || console.log, messages); + } +}; + +loggers.warn = function() { + if(dfltConfig.logging > 0) { + var messages = ['WARN:']; + + for(var i = 0; i < arguments.length; i++) { + messages.push(arguments[i]); + } + + apply(console.trace || console.log, messages); + } +}; + +loggers.error = function() { + if(dfltConfig.logging > 0) { + var messages = ['ERROR:']; + + for(var i = 0; i < arguments.length; i++) { + messages.push(arguments[i]); + } + + apply(console.error, messages); + } +}; + +/* + * Robust apply, for IE9 where console.log doesn't support + * apply like other functions do + */ +function apply(f, args) { + if(f && f.apply) { + try { + // `this` should always be console, since here we're always + // applying a method of the console object. + f.apply(console, args); + return; + } catch(e) { /* in case apply failed, fall back on the code below */ } + } + + // no apply - just try calling the function on each arg independently + for(var i = 0; i < args.length; i++) { + try { + f(args[i]); + } catch(e) { + // still fails - last resort simple console.log + console.log(args[i]); + } + } +} + +},{"../plot_api/plot_config":201}],174:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); + +/** + * General helper to manage trace groups based on calcdata + * + * @param {d3.selection} traceLayer: a selection containing a single group + * to draw these traces into + * @param {array} cdModule: array of calcdata items for this + * module and subplot combination. Assumes the calcdata item for each + * trace is an array with the fullData trace attached to the first item. + * @param {string} cls: the class attribute to give each trace group + * so you can give multiple classes separated by spaces + */ +module.exports = function makeTraceGroups(traceLayer, cdModule, cls) { + var traces = traceLayer.selectAll('g.' + cls.replace(/\s/g, '.')) + .data(cdModule, function(cd) { return cd[0].trace.uid; }); + + traces.exit().remove(); + + traces.enter().append('g') + .attr('class', cls); + + traces.order(); + + // stash ref node to trace group in calcdata, + // useful for (fast) styleOnSelect + var k = traceLayer.classed('rangeplot') ? 'nodeRangePlot3' : 'node3'; + traces.each(function(cd) { cd[0][k] = d3.select(this); }); + + return traces; +}; + +},{"d3":16}],175:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + + +exports.init2dArray = function(rowLength, colLength) { + var array = new Array(rowLength); + for(var i = 0; i < rowLength; i++) array[i] = new Array(colLength); + return array; +}; + +/** + * transpose a (possibly ragged) 2d array z. inspired by + * http://stackoverflow.com/questions/17428587/ + * transposing-a-2d-array-in-javascript + */ +exports.transposeRagged = function(z) { + var maxlen = 0; + var zlen = z.length; + var i, j; + // Maximum row length: + for(i = 0; i < zlen; i++) maxlen = Math.max(maxlen, z[i].length); + + var t = new Array(maxlen); + for(i = 0; i < maxlen; i++) { + t[i] = new Array(zlen); + for(j = 0; j < zlen; j++) t[i][j] = z[j][i]; + } + + return t; +}; + +// our own dot function so that we don't need to include numeric +exports.dot = function(x, y) { + if(!(x.length && y.length) || x.length !== y.length) return null; + + var len = x.length; + var out; + var i; + + if(x[0].length) { + // mat-vec or mat-mat + out = new Array(len); + for(i = 0; i < len; i++) out[i] = exports.dot(x[i], y); + } else if(y[0].length) { + // vec-mat + var yTranspose = exports.transposeRagged(y); + out = new Array(yTranspose.length); + for(i = 0; i < yTranspose.length; i++) out[i] = exports.dot(x, yTranspose[i]); + } else { + // vec-vec + out = 0; + for(i = 0; i < len; i++) out += x[i] * y[i]; + } + + return out; +}; + +// translate by (x,y) +exports.translationMatrix = function(x, y) { + return [[1, 0, x], [0, 1, y], [0, 0, 1]]; +}; + +// rotate by alpha around (0,0) +exports.rotationMatrix = function(alpha) { + var a = alpha * Math.PI / 180; + return [[Math.cos(a), -Math.sin(a), 0], + [Math.sin(a), Math.cos(a), 0], + [0, 0, 1]]; +}; + +// rotate by alpha around (x,y) +exports.rotationXYMatrix = function(a, x, y) { + return exports.dot( + exports.dot(exports.translationMatrix(x, y), + exports.rotationMatrix(a)), + exports.translationMatrix(-x, -y)); +}; + +// applies a 2D transformation matrix to either x and y params or an [x,y] array +exports.apply2DTransform = function(transform) { + return function() { + var args = arguments; + if(args.length === 3) { + args = args[0]; + }// from map + var xy = arguments.length === 1 ? args[0] : [args[0], args[1]]; + return exports.dot(transform, [xy[0], xy[1], 1]).slice(0, 2); + }; +}; + +// applies a 2D transformation matrix to an [x1,y1,x2,y2] array (to transform a segment) +exports.apply2DTransform2 = function(transform) { + var at = exports.apply2DTransform(transform); + return function(xys) { + return at(xys.slice(0, 2)).concat(at(xys.slice(2, 4))); + }; +}; + +},{}],176:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +/** + * sanitized modulus function that always returns in the range [0, d) + * rather than (-d, 0] if v is negative + */ +function mod(v, d) { + var out = v % d; + return out < 0 ? out + d : out; +} + +/** + * sanitized modulus function that always returns in the range [-d/2, d/2] + * rather than (-d, 0] if v is negative + */ +function modHalf(v, d) { + return Math.abs(v) > (d / 2) ? + v - Math.round(v / d) * d : + v; +} + +module.exports = { + mod: mod, + modHalf: modHalf +}; + +},{}],177:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); +var isArrayOrTypedArray = _dereq_('./array').isArrayOrTypedArray; + +/** + * convert a string s (such as 'xaxis.range[0]') + * representing a property of nested object into set and get methods + * also return the string and object so we don't have to keep track of them + * allows [-1] for an array index, to set a property inside all elements + * of an array + * eg if obj = {arr: [{a: 1}, {a: 2}]} + * you can do p = nestedProperty(obj, 'arr[-1].a') + * but you cannot set the array itself this way, to do that + * just set the whole array. + * eg if obj = {arr: [1, 2, 3]} + * you can't do nestedProperty(obj, 'arr[-1]').set(5) + * but you can do nestedProperty(obj, 'arr').set([5, 5, 5]) + */ +module.exports = function nestedProperty(container, propStr) { + if(isNumeric(propStr)) propStr = String(propStr); + else if(typeof propStr !== 'string' || + propStr.substr(propStr.length - 4) === '[-1]') { + throw 'bad property string'; + } + + var j = 0; + var propParts = propStr.split('.'); + var indexed; + var indices; + var i; + + // check for parts of the nesting hierarchy that are numbers (ie array elements) + while(j < propParts.length) { + // look for non-bracket chars, then any number of [##] blocks + indexed = String(propParts[j]).match(/^([^\[\]]*)((\[\-?[0-9]*\])+)$/); + if(indexed) { + if(indexed[1]) propParts[j] = indexed[1]; + // allow propStr to start with bracketed array indices + else if(j === 0) propParts.splice(0, 1); + else throw 'bad property string'; + + indices = indexed[2] + .substr(1, indexed[2].length - 2) + .split(']['); + + for(i = 0; i < indices.length; i++) { + j++; + propParts.splice(j, 0, Number(indices[i])); + } + } + j++; + } + + if(typeof container !== 'object') { + return badContainer(container, propStr, propParts); + } + + return { + set: npSet(container, propParts, propStr), + get: npGet(container, propParts), + astr: propStr, + parts: propParts, + obj: container + }; +}; + +function npGet(cont, parts) { + return function() { + var curCont = cont; + var curPart; + var allSame; + var out; + var i; + var j; + + for(i = 0; i < parts.length - 1; i++) { + curPart = parts[i]; + if(curPart === -1) { + allSame = true; + out = []; + for(j = 0; j < curCont.length; j++) { + out[j] = npGet(curCont[j], parts.slice(i + 1))(); + if(out[j] !== out[0]) allSame = false; + } + return allSame ? out[0] : out; + } + if(typeof curPart === 'number' && !isArrayOrTypedArray(curCont)) { + return undefined; + } + curCont = curCont[curPart]; + if(typeof curCont !== 'object' || curCont === null) { + return undefined; + } + } + + // only hit this if parts.length === 1 + if(typeof curCont !== 'object' || curCont === null) return undefined; + + out = curCont[parts[i]]; + if(out === null) return undefined; + return out; + }; +} + +/* + * Can this value be deleted? We can delete `undefined`, and `null` except INSIDE an + * *args* array. + * + * Previously we also deleted some `{}` and `[]`, in order to try and make set/unset + * a net noop; but this causes far more complication than it's worth, and still had + * lots of exceptions. See https://github.com/plotly/plotly.js/issues/1410 + * + * *args* arrays get passed directly to API methods and we should respect null if + * the user put it there, but otherwise null is deleted as we use it as code + * in restyle/relayout/update for "delete this value" whereas undefined means + * "ignore this edit" + */ +var ARGS_PATTERN = /(^|\.)args\[/; +function isDeletable(val, propStr) { + return (val === undefined) || (val === null && !propStr.match(ARGS_PATTERN)); +} + +function npSet(cont, parts, propStr) { + return function(val) { + var curCont = cont; + var propPart = ''; + var containerLevels = [[cont, propPart]]; + var toDelete = isDeletable(val, propStr); + var curPart; + var i; + + for(i = 0; i < parts.length - 1; i++) { + curPart = parts[i]; + + if(typeof curPart === 'number' && !isArrayOrTypedArray(curCont)) { + throw 'array index but container is not an array'; + } + + // handle special -1 array index + if(curPart === -1) { + toDelete = !setArrayAll(curCont, parts.slice(i + 1), val, propStr); + if(toDelete) break; + else return; + } + + if(!checkNewContainer(curCont, curPart, parts[i + 1], toDelete)) { + break; + } + + curCont = curCont[curPart]; + + if(typeof curCont !== 'object' || curCont === null) { + throw 'container is not an object'; + } + + propPart = joinPropStr(propPart, curPart); + + containerLevels.push([curCont, propPart]); + } + + if(toDelete) { + if(i === parts.length - 1) { + delete curCont[parts[i]]; + + // The one bit of pruning we still do: drop `undefined` from the end of arrays. + // In case someone has already unset previous items, continue until we hit a + // non-undefined value. + if(Array.isArray(curCont) && +parts[i] === curCont.length - 1) { + while(curCont.length && curCont[curCont.length - 1] === undefined) { + curCont.pop(); + } + } + } + } else curCont[parts[i]] = val; + }; +} + +function joinPropStr(propStr, newPart) { + var toAdd = newPart; + if(isNumeric(newPart)) toAdd = '[' + newPart + ']'; + else if(propStr) toAdd = '.' + newPart; + + return propStr + toAdd; +} + +// handle special -1 array index +function setArrayAll(containerArray, innerParts, val, propStr) { + var arrayVal = isArrayOrTypedArray(val); + var allSet = true; + var thisVal = val; + var thisPropStr = propStr.replace('-1', 0); + var deleteThis = arrayVal ? false : isDeletable(val, thisPropStr); + var firstPart = innerParts[0]; + var i; + + for(i = 0; i < containerArray.length; i++) { + thisPropStr = propStr.replace('-1', i); + if(arrayVal) { + thisVal = val[i % val.length]; + deleteThis = isDeletable(thisVal, thisPropStr); + } + if(deleteThis) allSet = false; + if(!checkNewContainer(containerArray, i, firstPart, deleteThis)) { + continue; + } + npSet(containerArray[i], innerParts, propStr.replace('-1', i))(thisVal); + } + return allSet; +} + +/** + * make new sub-container as needed. + * returns false if there's no container and none is needed + * because we're only deleting an attribute + */ +function checkNewContainer(container, part, nextPart, toDelete) { + if(container[part] === undefined) { + if(toDelete) return false; + + if(typeof nextPart === 'number') container[part] = []; + else container[part] = {}; + } + return true; +} + +function badContainer(container, propStr, propParts) { + return { + set: function() { throw 'bad container'; }, + get: function() {}, + astr: propStr, + parts: propParts, + obj: container + }; +} + +},{"./array":156,"fast-isnumeric":18}],178:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +// Simple helper functions +// none of these need any external deps + +module.exports = function noop() {}; + +},{}],179:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var d3 = _dereq_('d3'); +var isNumeric = _dereq_('fast-isnumeric'); + +var NOTEDATA = []; + +/** + * notifier + * @param {String} text The person's user name + * @param {Number} [delay=1000] The delay time in milliseconds + * or 'long' which provides 2000 ms delay time. + * @return {undefined} this function does not return a value + */ +module.exports = function(text, displayLength) { + if(NOTEDATA.indexOf(text) !== -1) return; + + NOTEDATA.push(text); + + var ts = 1000; + if(isNumeric(displayLength)) ts = displayLength; + else if(displayLength === 'long') ts = 3000; + + var notifierContainer = d3.select('body') + .selectAll('.plotly-notifier') + .data([0]); + notifierContainer.enter() + .append('div') + .classed('plotly-notifier', true); + + var notes = notifierContainer.selectAll('.notifier-note').data(NOTEDATA); + + function killNote(transition) { + transition + .duration(700) + .style('opacity', 0) + .each('end', function(thisText) { + var thisIndex = NOTEDATA.indexOf(thisText); + if(thisIndex !== -1) NOTEDATA.splice(thisIndex, 1); + d3.select(this).remove(); + }); + } + + notes.enter().append('div') + .classed('notifier-note', true) + .style('opacity', 0) + .each(function(thisText) { + var note = d3.select(this); + + note.append('button') + .classed('notifier-close', true) + .html('×') + .on('click', function() { + note.transition().call(killNote); + }); + + var p = note.append('p'); + var lines = thisText.split(//g); + for(var i = 0; i < lines.length; i++) { + if(i) p.append('br'); + p.append('span').text(lines[i]); + } + + note.transition() + .duration(700) + .style('opacity', 1) + .transition() + .delay(ts) + .call(killNote); + }); +}; + +},{"d3":16,"fast-isnumeric":18}],180:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var setCursor = _dereq_('./setcursor'); + +var STASHATTR = 'data-savedcursor'; +var NO_CURSOR = '!!'; + +/* + * works with our CSS cursor classes (see css/_cursor.scss) + * to override a previous cursor set on d3 single-element selections, + * by moving the name of the original cursor to the data-savedcursor attr. + * omit cursor to revert to the previously set value. + */ +module.exports = function overrideCursor(el3, csr) { + var savedCursor = el3.attr(STASHATTR); + if(csr) { + if(!savedCursor) { + var classes = (el3.attr('class') || '').split(' '); + for(var i = 0; i < classes.length; i++) { + var cls = classes[i]; + if(cls.indexOf('cursor-') === 0) { + el3.attr(STASHATTR, cls.substr(7)) + .classed(cls, false); + } + } + if(!el3.attr(STASHATTR)) { + el3.attr(STASHATTR, NO_CURSOR); + } + } + setCursor(el3, csr); + } else if(savedCursor) { + el3.attr(STASHATTR, null); + + if(savedCursor === NO_CURSOR) setCursor(el3); + else setCursor(el3, savedCursor); + } +}; + +},{"./setcursor":188}],181:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var dot = _dereq_('./matrix').dot; +var BADNUM = _dereq_('../constants/numerical').BADNUM; + +var polygon = module.exports = {}; + +/** + * Turn an array of [x, y] pairs into a polygon object + * that can test if points are inside it + * + * @param ptsIn Array of [x, y] pairs + * + * @returns polygon Object {xmin, xmax, ymin, ymax, pts, contains} + * (x|y)(min|max) are the bounding rect of the polygon + * pts is the original array, with the first pair repeated at the end + * contains is a function: (pt, omitFirstEdge) + * pt is the [x, y] pair to test + * omitFirstEdge truthy means points exactly on the first edge don't + * count. This is for use adding one polygon to another so we + * don't double-count the edge where they meet. + * returns boolean: is pt inside the polygon (including on its edges) + */ +polygon.tester = function tester(ptsIn) { + var pts = ptsIn.slice(); + var xmin = pts[0][0]; + var xmax = xmin; + var ymin = pts[0][1]; + var ymax = ymin; + var i; + + pts.push(pts[0]); + for(i = 1; i < pts.length; i++) { + xmin = Math.min(xmin, pts[i][0]); + xmax = Math.max(xmax, pts[i][0]); + ymin = Math.min(ymin, pts[i][1]); + ymax = Math.max(ymax, pts[i][1]); + } + + // do we have a rectangle? Handle this here, so we can use the same + // tester for the rectangular case without sacrificing speed + + var isRect = false; + var rectFirstEdgeTest; + + if(pts.length === 5) { + if(pts[0][0] === pts[1][0]) { // vert, horz, vert, horz + if(pts[2][0] === pts[3][0] && + pts[0][1] === pts[3][1] && + pts[1][1] === pts[2][1]) { + isRect = true; + rectFirstEdgeTest = function(pt) { return pt[0] === pts[0][0]; }; + } + } else if(pts[0][1] === pts[1][1]) { // horz, vert, horz, vert + if(pts[2][1] === pts[3][1] && + pts[0][0] === pts[3][0] && + pts[1][0] === pts[2][0]) { + isRect = true; + rectFirstEdgeTest = function(pt) { return pt[1] === pts[0][1]; }; + } + } + } + + function rectContains(pt, omitFirstEdge) { + var x = pt[0]; + var y = pt[1]; + + if(x === BADNUM || x < xmin || x > xmax || y === BADNUM || y < ymin || y > ymax) { + // pt is outside the bounding box of polygon + return false; + } + if(omitFirstEdge && rectFirstEdgeTest(pt)) return false; + + return true; + } + + function contains(pt, omitFirstEdge) { + var x = pt[0]; + var y = pt[1]; + + if(x === BADNUM || x < xmin || x > xmax || y === BADNUM || y < ymin || y > ymax) { + // pt is outside the bounding box of polygon + return false; + } + + var imax = pts.length; + var x1 = pts[0][0]; + var y1 = pts[0][1]; + var crossings = 0; + var i; + var x0; + var y0; + var xmini; + var ycross; + + for(i = 1; i < imax; i++) { + // find all crossings of a vertical line upward from pt with + // polygon segments + // crossings exactly at xmax don't count, unless the point is + // exactly on the segment, then it counts as inside. + x0 = x1; + y0 = y1; + x1 = pts[i][0]; + y1 = pts[i][1]; + xmini = Math.min(x0, x1); + + if(x < xmini || x > Math.max(x0, x1) || y > Math.max(y0, y1)) { + // outside the bounding box of this segment, it's only a crossing + // if it's below the box. + + continue; + } else if(y < Math.min(y0, y1)) { + // don't count the left-most point of the segment as a crossing + // because we don't want to double-count adjacent crossings + // UNLESS the polygon turns past vertical at exactly this x + // Note that this is repeated below, but we can't factor it out + // because + if(x !== xmini) crossings++; + } else { + // inside the bounding box, check the actual line intercept + + // vertical segment - we know already that the point is exactly + // on the segment, so mark the crossing as exactly at the point. + if(x1 === x0) ycross = y; + // any other angle + else ycross = y0 + (x - x0) * (y1 - y0) / (x1 - x0); + + // exactly on the edge: counts as inside the polygon, unless it's the + // first edge and we're omitting it. + if(y === ycross) { + if(i === 1 && omitFirstEdge) return false; + return true; + } + + if(y <= ycross && x !== xmini) crossings++; + } + } + + // if we've gotten this far, odd crossings means inside, even is outside + return crossings % 2 === 1; + } + + // detect if poly is degenerate + var degenerate = true; + var lastPt = pts[0]; + for(i = 1; i < pts.length; i++) { + if(lastPt[0] !== pts[i][0] || lastPt[1] !== pts[i][1]) { + degenerate = false; + break; + } + } + + return { + xmin: xmin, + xmax: xmax, + ymin: ymin, + ymax: ymax, + pts: pts, + contains: isRect ? rectContains : contains, + isRect: isRect, + degenerate: degenerate + }; +}; + +/** + * Test if a segment of a points array is bent or straight + * + * @param pts Array of [x, y] pairs + * @param start the index of the proposed start of the straight section + * @param end the index of the proposed end point + * @param tolerance the max distance off the line connecting start and end + * before the line counts as bent + * @returns boolean: true means this segment is bent, false means straight + */ +polygon.isSegmentBent = function isSegmentBent(pts, start, end, tolerance) { + var startPt = pts[start]; + var segment = [pts[end][0] - startPt[0], pts[end][1] - startPt[1]]; + var segmentSquared = dot(segment, segment); + var segmentLen = Math.sqrt(segmentSquared); + var unitPerp = [-segment[1] / segmentLen, segment[0] / segmentLen]; + var i; + var part; + var partParallel; + + for(i = start + 1; i < end; i++) { + part = [pts[i][0] - startPt[0], pts[i][1] - startPt[1]]; + partParallel = dot(part, segment); + + if(partParallel < 0 || partParallel > segmentSquared || + Math.abs(dot(part, unitPerp)) > tolerance) return true; + } + return false; +}; + +/** + * Make a filtering polygon, to minimize the number of segments + * + * @param pts Array of [x, y] pairs (must start with at least 1 pair) + * @param tolerance the maximum deviation from straight allowed for + * removing points to simplify the polygon + * + * @returns Object {addPt, raw, filtered} + * addPt is a function(pt: [x, y] pair) to add a raw point and + * continue filtering + * raw is all the input points + * filtered is the resulting filtered Array of [x, y] pairs + */ +polygon.filter = function filter(pts, tolerance) { + var ptsFiltered = [pts[0]]; + var doneRawIndex = 0; + var doneFilteredIndex = 0; + + function addPt(pt) { + pts.push(pt); + var prevFilterLen = ptsFiltered.length; + var iLast = doneRawIndex; + ptsFiltered.splice(doneFilteredIndex + 1); + + for(var i = iLast + 1; i < pts.length; i++) { + if(i === pts.length - 1 || polygon.isSegmentBent(pts, iLast, i + 1, tolerance)) { + ptsFiltered.push(pts[i]); + if(ptsFiltered.length < prevFilterLen - 2) { + doneRawIndex = i; + doneFilteredIndex = ptsFiltered.length - 1; + } + iLast = i; + } + } + } + + if(pts.length > 1) { + var lastPt = pts.pop(); + addPt(lastPt); + } + + return { + addPt: addPt, + raw: pts, + filtered: ptsFiltered + }; +}; + +},{"../constants/numerical":149,"./matrix":175}],182:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +/** + * Push array with unique items + * + * Ignores falsy items, except 0 so we can use it to construct arrays of indices. + * + * @param {array} array + * array to be filled + * @param {any} item + * item to be or not to be inserted + * @return {array} + * ref to array (now possibly containing one more item) + * + */ +module.exports = function pushUnique(array, item) { + if(item instanceof RegExp) { + var itemStr = item.toString(); + for(var i = 0; i < array.length; i++) { + if(array[i] instanceof RegExp && array[i].toString() === itemStr) { + return array; + } + } + array.push(item); + } else if((item || item === 0) && array.indexOf(item) === -1) array.push(item); + + return array; +}; + +},{}],183:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../lib'); +var dfltConfig = _dereq_('../plot_api/plot_config').dfltConfig; + +/** + * Copy arg array *without* removing `undefined` values from objects. + * + * @param gd + * @param args + * @returns {Array} + */ +function copyArgArray(gd, args) { + var copy = []; + var arg; + + for(var i = 0; i < args.length; i++) { + arg = args[i]; + + if(arg === gd) copy[i] = arg; + else if(typeof arg === 'object') { + copy[i] = Array.isArray(arg) ? + Lib.extendDeep([], arg) : + Lib.extendDeepAll({}, arg); + } else copy[i] = arg; + } + + return copy; +} + + +// ----------------------------------------------------- +// Undo/Redo queue for plots +// ----------------------------------------------------- + + +var queue = {}; + +// TODO: disable/enable undo and redo buttons appropriately + +/** + * Add an item to the undoQueue for a graphDiv + * + * @param gd + * @param undoFunc Function undo this operation + * @param undoArgs Args to supply undoFunc with + * @param redoFunc Function to redo this operation + * @param redoArgs Args to supply redoFunc with + */ +queue.add = function(gd, undoFunc, undoArgs, redoFunc, redoArgs) { + var queueObj, + queueIndex; + + // make sure we have the queue and our position in it + gd.undoQueue = gd.undoQueue || {index: 0, queue: [], sequence: false}; + queueIndex = gd.undoQueue.index; + + // if we're already playing an undo or redo, or if this is an auto operation + // (like pane resize... any others?) then we don't save this to the undo queue + if(gd.autoplay) { + if(!gd.undoQueue.inSequence) gd.autoplay = false; + return; + } + + // if we're not in a sequence or are just starting, we need a new queue item + if(!gd.undoQueue.sequence || gd.undoQueue.beginSequence) { + queueObj = {undo: {calls: [], args: []}, redo: {calls: [], args: []}}; + gd.undoQueue.queue.splice(queueIndex, gd.undoQueue.queue.length - queueIndex, queueObj); + gd.undoQueue.index += 1; + } else { + queueObj = gd.undoQueue.queue[queueIndex - 1]; + } + gd.undoQueue.beginSequence = false; + + // we unshift to handle calls for undo in a forward for loop later + if(queueObj) { + queueObj.undo.calls.unshift(undoFunc); + queueObj.undo.args.unshift(undoArgs); + queueObj.redo.calls.push(redoFunc); + queueObj.redo.args.push(redoArgs); + } + + if(gd.undoQueue.queue.length > dfltConfig.queueLength) { + gd.undoQueue.queue.shift(); + gd.undoQueue.index--; + } +}; + +/** + * Begin a sequence of undoQueue changes + * + * @param gd + */ +queue.startSequence = function(gd) { + gd.undoQueue = gd.undoQueue || {index: 0, queue: [], sequence: false}; + gd.undoQueue.sequence = true; + gd.undoQueue.beginSequence = true; +}; + +/** + * Stop a sequence of undoQueue changes + * + * Call this *after* you're sure your undo chain has ended + * + * @param gd + */ +queue.stopSequence = function(gd) { + gd.undoQueue = gd.undoQueue || {index: 0, queue: [], sequence: false}; + gd.undoQueue.sequence = false; + gd.undoQueue.beginSequence = false; +}; + +/** + * Move one step back in the undo queue, and undo the object there. + * + * @param gd + */ +queue.undo = function undo(gd) { + var queueObj, i; + + if(gd.framework && gd.framework.isPolar) { + gd.framework.undo(); + return; + } + if(gd.undoQueue === undefined || + isNaN(gd.undoQueue.index) || + gd.undoQueue.index <= 0) { + return; + } + + // index is pointing to next *forward* queueObj, point to the one we're undoing + gd.undoQueue.index--; + + // get the queueObj for instructions on how to undo + queueObj = gd.undoQueue.queue[gd.undoQueue.index]; + + // this sequence keeps things from adding to the queue during undo/redo + gd.undoQueue.inSequence = true; + for(i = 0; i < queueObj.undo.calls.length; i++) { + queue.plotDo(gd, queueObj.undo.calls[i], queueObj.undo.args[i]); + } + gd.undoQueue.inSequence = false; + gd.autoplay = false; +}; + +/** + * Redo the current object in the undo, then move forward in the queue. + * + * @param gd + */ +queue.redo = function redo(gd) { + var queueObj, i; + + if(gd.framework && gd.framework.isPolar) { + gd.framework.redo(); + return; + } + if(gd.undoQueue === undefined || + isNaN(gd.undoQueue.index) || + gd.undoQueue.index >= gd.undoQueue.queue.length) { + return; + } + + // get the queueObj for instructions on how to undo + queueObj = gd.undoQueue.queue[gd.undoQueue.index]; + + // this sequence keeps things from adding to the queue during undo/redo + gd.undoQueue.inSequence = true; + for(i = 0; i < queueObj.redo.calls.length; i++) { + queue.plotDo(gd, queueObj.redo.calls[i], queueObj.redo.args[i]); + } + gd.undoQueue.inSequence = false; + gd.autoplay = false; + + // index is pointing to the thing we just redid, move it + gd.undoQueue.index++; +}; + +/** + * Called by undo/redo to make the actual changes. + * + * Not meant to be called publically, but included for mocking out in tests. + * + * @param gd + * @param func + * @param args + */ +queue.plotDo = function(gd, func, args) { + gd.autoplay = true; + + // this *won't* copy gd and it preserves `undefined` properties! + args = copyArgArray(gd, args); + + // call the supplied function + func.apply(null, args); +}; + +module.exports = queue; + +},{"../lib":169,"../plot_api/plot_config":201}],184:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +/* + * make a regex for matching counter ids/names ie xaxis, xaxis2, xaxis10... + * + * @param {string} head: the head of the pattern, eg 'x' matches 'x', 'x2', 'x10' etc. + * 'xy' is a special case for cartesian subplots: it matches 'x2y3' etc + * @param {Optional(string)} tail: a fixed piece after the id + * eg counterRegex('scene', '.annotations') for scene2.annotations etc. + * @param {boolean} openEnded: if true, the string may continue past the match. + * @param {boolean} matchBeginning: if false, the string may start before the match. + */ +exports.counter = function(head, tail, openEnded, matchBeginning) { + var fullTail = (tail || '') + (openEnded ? '' : '$'); + var startWithPrefix = matchBeginning === false ? '' : '^'; + if(head === 'xy') { + return new RegExp(startWithPrefix + 'x([2-9]|[1-9][0-9]+)?y([2-9]|[1-9][0-9]+)?' + fullTail); + } + return new RegExp(startWithPrefix + head + '([2-9]|[1-9][0-9]+)?' + fullTail); +}; + +},{}],185:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +// ASCEND: chop off the last nesting level - either [] or . - to ascend +// the attribute tree. the remaining attrString is in match[1] +var ASCEND = /^(.*)(\.[^\.\[\]]+|\[\d\])$/; + +// SIMPLEATTR: is this an un-nested attribute? (no dots or brackets) +var SIMPLEATTR = /^[^\.\[\]]+$/; + +/* + * calculate a relative attribute string, similar to a relative path + * + * @param {string} baseAttr: + * an attribute string, such as 'annotations[3].x'. The "current location" + * is the attribute string minus the last component ('annotations[3]') + * @param {string} relativeAttr: + * a route to the desired attribute string, using '^' to ascend + * + * @return {string} attrString: + * for example: + * relativeAttr('annotations[3].x', 'y') = 'annotations[3].y' + * relativeAttr('annotations[3].x', '^[2].z') = 'annotations[2].z' + * relativeAttr('annotations[3].x', '^^margin') = 'margin' + * relativeAttr('annotations[3].x', '^^margin.r') = 'margin.r' + */ +module.exports = function(baseAttr, relativeAttr) { + while(relativeAttr) { + var match = baseAttr.match(ASCEND); + + if(match) baseAttr = match[1]; + else if(baseAttr.match(SIMPLEATTR)) baseAttr = ''; + else throw new Error('bad relativeAttr call:' + [baseAttr, relativeAttr]); + + if(relativeAttr.charAt(0) === '^') relativeAttr = relativeAttr.slice(1); + else break; + } + + if(baseAttr && relativeAttr.charAt(0) !== '[') { + return baseAttr + '.' + relativeAttr; + } + return baseAttr + relativeAttr; +}; + +},{}],186:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var isArrayOrTypedArray = _dereq_('./array').isArrayOrTypedArray; +var isPlainObject = _dereq_('./is_plain_object'); + +/** + * Relink private _keys and keys with a function value from one container + * to the new container. + * Relink means copying if object is pass-by-value and adding a reference + * if object is pass-by-ref. + * This prevents deepCopying massive structures like a webgl context. + */ +module.exports = function relinkPrivateKeys(toContainer, fromContainer) { + for(var k in fromContainer) { + var fromVal = fromContainer[k]; + var toVal = toContainer[k]; + + if(toVal === fromVal) { + continue; + } + if(k.charAt(0) === '_' || typeof fromVal === 'function') { + // if it already exists at this point, it's something + // that we recreate each time around, so ignore it + if(k in toContainer) continue; + + toContainer[k] = fromVal; + } else if(isArrayOrTypedArray(fromVal) && isArrayOrTypedArray(toVal) && isPlainObject(fromVal[0])) { + // filter out data_array items that can contain user objects + // most of the time the toVal === fromVal check will catch these early + // but if the user makes new ones we also don't want to recurse in. + if(k === 'customdata' || k === 'ids') continue; + + // recurse into arrays containers + var minLen = Math.min(fromVal.length, toVal.length); + for(var j = 0; j < minLen; j++) { + if((toVal[j] !== fromVal[j]) && isPlainObject(fromVal[j]) && isPlainObject(toVal[j])) { + relinkPrivateKeys(toVal[j], fromVal[j]); + } + } + } else if(isPlainObject(fromVal) && isPlainObject(toVal)) { + // recurse into objects, but only if they still exist + relinkPrivateKeys(toVal, fromVal); + + if(!Object.keys(toVal).length) delete toContainer[k]; + } + } +}; + +},{"./array":156,"./is_plain_object":170}],187:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); +var loggers = _dereq_('./loggers'); +var identity = _dereq_('./identity'); + +// don't trust floating point equality - fraction of bin size to call +// "on the line" and ensure that they go the right way specified by +// linelow +var roundingError = 1e-9; + + +/** + * findBin - find the bin for val - note that it can return outside the + * bin range any pos. or neg. integer for linear bins, or -1 or + * bins.length-1 for explicit. + * bins is either an object {start,size,end} or an array length #bins+1 + * bins can be either increasing or decreasing but must be monotonic + * for linear bins, we can just calculate. For listed bins, run a binary + * search linelow (truthy) says the bin boundary should be attributed to + * the lower bin rather than the default upper bin + */ +exports.findBin = function(val, bins, linelow) { + if(isNumeric(bins.start)) { + return linelow ? + Math.ceil((val - bins.start) / bins.size - roundingError) - 1 : + Math.floor((val - bins.start) / bins.size + roundingError); + } else { + var n1 = 0; + var n2 = bins.length; + var c = 0; + var binSize = (n2 > 1) ? (bins[n2 - 1] - bins[0]) / (n2 - 1) : 1; + var n, test; + if(binSize >= 0) { + test = linelow ? lessThan : lessOrEqual; + } else { + test = linelow ? greaterOrEqual : greaterThan; + } + val += binSize * roundingError * (linelow ? -1 : 1) * (binSize >= 0 ? 1 : -1); + // c is just to avoid infinite loops if there's an error + while(n1 < n2 && c++ < 100) { + n = Math.floor((n1 + n2) / 2); + if(test(bins[n], val)) n1 = n + 1; + else n2 = n; + } + if(c > 90) loggers.log('Long binary search...'); + return n1 - 1; + } +}; + +function lessThan(a, b) { return a < b; } +function lessOrEqual(a, b) { return a <= b; } +function greaterThan(a, b) { return a > b; } +function greaterOrEqual(a, b) { return a >= b; } + +exports.sorterAsc = function(a, b) { return a - b; }; +exports.sorterDes = function(a, b) { return b - a; }; + +/** + * find distinct values in an array, lumping together ones that appear to + * just be off by a rounding error + * return the distinct values and the minimum difference between any two + */ +exports.distinctVals = function(valsIn) { + var vals = valsIn.slice(); // otherwise we sort the original array... + vals.sort(exports.sorterAsc); + + var l = vals.length - 1; + var minDiff = (vals[l] - vals[0]) || 1; + var errDiff = minDiff / (l || 1) / 10000; + var v2 = [vals[0]]; + + for(var i = 0; i < l; i++) { + // make sure values aren't just off by a rounding error + if(vals[i + 1] > vals[i] + errDiff) { + minDiff = Math.min(minDiff, vals[i + 1] - vals[i]); + v2.push(vals[i + 1]); + } + } + + return {vals: v2, minDiff: minDiff}; +}; + +/** + * return the smallest element from (sorted) array arrayIn that's bigger than val, + * or (reverse) the largest element smaller than val + * used to find the best tick given the minimum (non-rounded) tick + * particularly useful for date/time where things are not powers of 10 + * binary search is probably overkill here... + */ +exports.roundUp = function(val, arrayIn, reverse) { + var low = 0; + var high = arrayIn.length - 1; + var mid; + var c = 0; + var dlow = reverse ? 0 : 1; + var dhigh = reverse ? 1 : 0; + var rounded = reverse ? Math.ceil : Math.floor; + // c is just to avoid infinite loops if there's an error + while(low < high && c++ < 100) { + mid = rounded((low + high) / 2); + if(arrayIn[mid] <= val) low = mid + dlow; + else high = mid - dhigh; + } + return arrayIn[low]; +}; + +/** + * Tweak to Array.sort(sortFn) that improves performance for pre-sorted arrays + * + * Note that newer browsers (such as Chrome v70+) are starting to pick up + * on pre-sorted arrays which may render the following optimization unnecessary + * in the future. + * + * Motivation: sometimes we need to sort arrays but the input is likely to + * already be sorted. Browsers don't seem to pick up on pre-sorted arrays, + * and in fact Chrome is actually *slower* sorting pre-sorted arrays than purely + * random arrays. FF is at least faster if the array is pre-sorted, but still + * not as fast as it could be. + * Here's how this plays out sorting a length-1e6 array: + * + * Calls to Sort FN | Chrome bare | FF bare | Chrome tweak | FF tweak + * | v68.0 Mac | v61.0 Mac| | + * ------------------+---------------+-----------+----------------+------------ + * ordered | 30.4e6 | 10.1e6 | 1e6 | 1e6 + * reversed | 29.4e6 | 9.9e6 | 1e6 + reverse | 1e6 + reverse + * random | ~21e6 | ~18.7e6 | ~21e6 | ~18.7e6 + * + * So this is a substantial win for pre-sorted (ordered or exactly reversed) + * arrays. Including this wrapper on an unsorted array adds a penalty that will + * in general be only a few calls to the sort function. The only case this + * penalty will be significant is if the array is mostly sorted but there are + * a few unsorted items near the end, but the penalty is still at most N calls + * out of (for N=1e6) ~20N total calls + * + * @param {Array} array: the array, to be sorted in place + * @param {function} sortFn: As in Array.sort, function(a, b) that puts + * item a before item b if the return is negative, a after b if positive, + * and no change if zero. + * @return {Array}: the original array, sorted in place. + */ +exports.sort = function(array, sortFn) { + var notOrdered = 0; + var notReversed = 0; + for(var i = 1; i < array.length; i++) { + var pairOrder = sortFn(array[i], array[i - 1]); + if(pairOrder < 0) notOrdered = 1; + else if(pairOrder > 0) notReversed = 1; + if(notOrdered && notReversed) return array.sort(sortFn); + } + return notReversed ? array : array.reverse(); +}; + +/** + * find index in array 'arr' that minimizes 'fn' + * + * @param {array} arr : array where to search + * @param {fn (optional)} fn : function to minimize, + * if not given, fn is the identity function + * @return {integer} + */ +exports.findIndexOfMin = function(arr, fn) { + fn = fn || identity; + + var min = Infinity; + var ind; + + for(var i = 0; i < arr.length; i++) { + var v = fn(arr[i]); + if(v < min) { + min = v; + ind = i; + } + } + return ind; +}; + +},{"./identity":168,"./loggers":173,"fast-isnumeric":18}],188:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +// works with our CSS cursor classes (see css/_cursor.scss) +// to apply cursors to d3 single-element selections. +// omit cursor to revert to the default. +module.exports = function setCursor(el3, csr) { + (el3.attr('class') || '').split(' ').forEach(function(cls) { + if(cls.indexOf('cursor-') === 0) el3.classed(cls, false); + }); + + if(csr) el3.classed('cursor-' + csr, true); +}; + +},{}],189:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); +var isArrayOrTypedArray = _dereq_('./array').isArrayOrTypedArray; + +/** + * aggNums() returns the result of an aggregate function applied to an array of + * values, where non-numerical values have been tossed out. + * + * @param {function} f - aggregation function (e.g., Math.min) + * @param {Number} v - initial value (continuing from previous calls) + * if there's no continuing value, use null for selector-type + * functions (max,min), or 0 for summations + * @param {Array} a - array to aggregate (may be nested, we will recurse, + * but all elements must have the same dimension) + * @param {Number} len - maximum length of a to aggregate + * @return {Number} - result of f applied to a starting from v + */ +exports.aggNums = function(f, v, a, len) { + var i, + b; + if(!len || len > a.length) len = a.length; + if(!isNumeric(v)) v = false; + if(isArrayOrTypedArray(a[0])) { + b = new Array(len); + for(i = 0; i < len; i++) b[i] = exports.aggNums(f, v, a[i]); + a = b; + } + + for(i = 0; i < len; i++) { + if(!isNumeric(v)) v = a[i]; + else if(isNumeric(a[i])) v = f(+v, +a[i]); + } + return v; +}; + +/** + * mean & std dev functions using aggNums, so it handles non-numerics nicely + * even need to use aggNums instead of .length, to toss out non-numerics + */ +exports.len = function(data) { + return exports.aggNums(function(a) { return a + 1; }, 0, data); +}; + +exports.mean = function(data, len) { + if(!len) len = exports.len(data); + return exports.aggNums(function(a, b) { return a + b; }, 0, data) / len; +}; + +exports.midRange = function(numArr) { + if(numArr === undefined || numArr.length === 0) return undefined; + return (exports.aggNums(Math.max, null, numArr) + exports.aggNums(Math.min, null, numArr)) / 2; +}; + +exports.variance = function(data, len, mean) { + if(!len) len = exports.len(data); + if(!isNumeric(mean)) mean = exports.mean(data, len); + + return exports.aggNums(function(a, b) { + return a + Math.pow(b - mean, 2); + }, 0, data) / len; +}; + +exports.stdev = function(data, len, mean) { + return Math.sqrt(exports.variance(data, len, mean)); +}; + +/** + * median of a finite set of numbers + * reference page: https://en.wikipedia.org/wiki/Median#Finite_set_of_numbers +**/ +exports.median = function(data) { + var b = data.slice().sort(); + return exports.interp(b, 0.5); +}; + +/** + * interp() computes a percentile (quantile) for a given distribution. + * We interpolate the distribution (to compute quantiles, we follow method #10 here: + * http://www.amstat.org/publications/jse/v14n3/langford.html). + * Typically the index or rank (n * arr.length) may be non-integer. + * For reference: ends are clipped to the extreme values in the array; + * For box plots: index you get is half a point too high (see + * http://en.wikipedia.org/wiki/Percentile#Nearest_rank) but note that this definition + * indexes from 1 rather than 0, so we subtract 1/2 (instead of add). + * + * @param {Array} arr - This array contains the values that make up the distribution. + * @param {Number} n - Between 0 and 1, n = p/100 is such that we compute the p^th percentile. + * For example, the 50th percentile (or median) corresponds to n = 0.5 + * @return {Number} - percentile + */ +exports.interp = function(arr, n) { + if(!isNumeric(n)) throw 'n should be a finite number'; + n = n * arr.length - 0.5; + if(n < 0) return arr[0]; + if(n > arr.length - 1) return arr[arr.length - 1]; + var frac = n % 1; + return frac * arr[Math.ceil(n)] + (1 - frac) * arr[Math.floor(n)]; +}; + +},{"./array":156,"fast-isnumeric":18}],190:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +/* global MathJax:false */ + +var d3 = _dereq_('d3'); + +var Lib = _dereq_('../lib'); +var xmlnsNamespaces = _dereq_('../constants/xmlns_namespaces'); +var LINE_SPACING = _dereq_('../constants/alignment').LINE_SPACING; + +// text converter + +function getSize(_selection, _dimension) { + return _selection.node().getBoundingClientRect()[_dimension]; +} + +var FIND_TEX = /([^$]*)([$]+[^$]*[$]+)([^$]*)/; + +exports.convertToTspans = function(_context, gd, _callback) { + var str = _context.text(); + + // Until we get tex integrated more fully (so it can be used along with non-tex) + // allow some elements to prohibit it by attaching 'data-notex' to the original + var tex = (!_context.attr('data-notex')) && + (typeof MathJax !== 'undefined') && + str.match(FIND_TEX); + + var parent = d3.select(_context.node().parentNode); + if(parent.empty()) return; + var svgClass = (_context.attr('class')) ? _context.attr('class').split(' ')[0] : 'text'; + svgClass += '-math'; + parent.selectAll('svg.' + svgClass).remove(); + parent.selectAll('g.' + svgClass + '-group').remove(); + _context.style('display', null) + .attr({ + // some callers use data-unformatted *from the element* in 'cancel' + // so we need it here even if we're going to turn it into math + // these two (plus style and text-anchor attributes) form the key we're + // going to use for Drawing.bBox + 'data-unformatted': str, + 'data-math': 'N' + }); + + function showText() { + if(!parent.empty()) { + svgClass = _context.attr('class') + '-math'; + parent.select('svg.' + svgClass).remove(); + } + _context.text('') + .style('white-space', 'pre'); + + var hasLink = buildSVGText(_context.node(), str); + + if(hasLink) { + // at least in Chrome, pointer-events does not seem + // to be honored in children of elements + // so if we have an anchor, we have to make the + // whole element respond + _context.style('pointer-events', 'all'); + } + + exports.positionText(_context); + + if(_callback) _callback.call(_context); + } + + if(tex) { + ((gd && gd._promises) || []).push(new Promise(function(resolve) { + _context.style('display', 'none'); + var fontSize = parseInt(_context.node().style.fontSize, 10); + var config = {fontSize: fontSize}; + + texToSVG(tex[2], config, function(_svgEl, _glyphDefs, _svgBBox) { + parent.selectAll('svg.' + svgClass).remove(); + parent.selectAll('g.' + svgClass + '-group').remove(); + + var newSvg = _svgEl && _svgEl.select('svg'); + if(!newSvg || !newSvg.node()) { + showText(); + resolve(); + return; + } + + var mathjaxGroup = parent.append('g') + .classed(svgClass + '-group', true) + .attr({ + 'pointer-events': 'none', + 'data-unformatted': str, + 'data-math': 'Y' + }); + + mathjaxGroup.node().appendChild(newSvg.node()); + + // stitch the glyph defs + if(_glyphDefs && _glyphDefs.node()) { + newSvg.node().insertBefore(_glyphDefs.node().cloneNode(true), + newSvg.node().firstChild); + } + + newSvg.attr({ + 'class': svgClass, + height: _svgBBox.height, + preserveAspectRatio: 'xMinYMin meet' + }) + .style({overflow: 'visible', 'pointer-events': 'none'}); + + var fill = _context.node().style.fill || 'black'; + var g = newSvg.select('g'); + g.attr({fill: fill, stroke: fill}); + + var newSvgW = getSize(g, 'width'); + var newSvgH = getSize(g, 'height'); + var newX = +_context.attr('x') - newSvgW * + {start: 0, middle: 0.5, end: 1}[_context.attr('text-anchor') || 'start']; + // font baseline is about 1/4 fontSize below centerline + var textHeight = fontSize || getSize(_context, 'height'); + var dy = -textHeight / 4; + + if(svgClass[0] === 'y') { + mathjaxGroup.attr({ + transform: 'rotate(' + [-90, +_context.attr('x'), +_context.attr('y')] + + ') translate(' + [-newSvgW / 2, dy - newSvgH / 2] + ')' + }); + newSvg.attr({x: +_context.attr('x'), y: +_context.attr('y')}); + } else if(svgClass[0] === 'l') { + newSvg.attr({x: _context.attr('x'), y: dy - (newSvgH / 2)}); + } else if(svgClass[0] === 'a' && svgClass.indexOf('atitle') !== 0) { + newSvg.attr({x: 0, y: dy}); + } else { + newSvg.attr({x: newX, y: (+_context.attr('y') + dy - newSvgH / 2)}); + } + + if(_callback) _callback.call(_context, mathjaxGroup); + resolve(mathjaxGroup); + }); + })); + } else showText(); + + return _context; +}; + + +// MathJax + +var LT_MATCH = /(<|<|<)/g; +var GT_MATCH = /(>|>|>)/g; + +function cleanEscapesForTex(s) { + return s.replace(LT_MATCH, '\\lt ') + .replace(GT_MATCH, '\\gt '); +} + +function texToSVG(_texString, _config, _callback) { + var originalRenderer, + originalConfig, + originalProcessSectionDelay, + tmpDiv; + + MathJax.Hub.Queue( + function() { + originalConfig = Lib.extendDeepAll({}, MathJax.Hub.config); + + originalProcessSectionDelay = MathJax.Hub.processSectionDelay; + if(MathJax.Hub.processSectionDelay !== undefined) { + // MathJax 2.5+ + MathJax.Hub.processSectionDelay = 0; + } + + return MathJax.Hub.Config({ + messageStyle: 'none', + tex2jax: { + inlineMath: [['$', '$'], ['\\(', '\\)']] + }, + displayAlign: 'left', + }); + }, + function() { + // Get original renderer + originalRenderer = MathJax.Hub.config.menuSettings.renderer; + if(originalRenderer !== 'SVG') { + return MathJax.Hub.setRenderer('SVG'); + } + }, + function() { + var randomID = 'math-output-' + Lib.randstr({}, 64); + tmpDiv = d3.select('body').append('div') + .attr({id: randomID}) + .style({visibility: 'hidden', position: 'absolute'}) + .style({'font-size': _config.fontSize + 'px'}) + .text(cleanEscapesForTex(_texString)); + + return MathJax.Hub.Typeset(tmpDiv.node()); + }, + function() { + var glyphDefs = d3.select('body').select('#MathJax_SVG_glyphs'); + + if(tmpDiv.select('.MathJax_SVG').empty() || !tmpDiv.select('svg').node()) { + Lib.log('There was an error in the tex syntax.', _texString); + _callback(); + } else { + var svgBBox = tmpDiv.select('svg').node().getBoundingClientRect(); + _callback(tmpDiv.select('.MathJax_SVG'), glyphDefs, svgBBox); + } + + tmpDiv.remove(); + + if(originalRenderer !== 'SVG') { + return MathJax.Hub.setRenderer(originalRenderer); + } + }, + function() { + if(originalProcessSectionDelay !== undefined) { + MathJax.Hub.processSectionDelay = originalProcessSectionDelay; + } + return MathJax.Hub.Config(originalConfig); + }); +} + +var TAG_STYLES = { + // would like to use baseline-shift for sub/sup but FF doesn't support it + // so we need to use dy along with the uber hacky shift-back-to + // baseline below + sup: 'font-size:70%', + sub: 'font-size:70%', + b: 'font-weight:bold', + i: 'font-style:italic', + a: 'cursor:pointer', + span: '', + em: 'font-style:italic;font-weight:bold' +}; + +// baseline shifts for sub and sup +var SHIFT_DY = { + sub: '0.3em', + sup: '-0.6em' +}; +// reset baseline by adding a tspan (empty except for a zero-width space) +// with dy of -70% * SHIFT_DY (because font-size=70%) +var RESET_DY = { + sub: '-0.21em', + sup: '0.42em' +}; +var ZERO_WIDTH_SPACE = '\u200b'; + +/* + * Whitelist of protocols in user-supplied urls. Mostly we want to avoid javascript + * and related attack vectors. The empty items are there for IE, that in various + * versions treats relative paths as having different flavors of no protocol, while + * other browsers have these explicitly inherit the protocol of the page they're in. + */ +var PROTOCOLS = ['http:', 'https:', 'mailto:', '', undefined, ':']; + +var NEWLINES = exports.NEWLINES = /(\r\n?|\n)/g; + +var SPLIT_TAGS = /(<[^<>]*>)/; + +var ONE_TAG = /<(\/?)([^ >]*)(\s+(.*))?>/i; + +var BR_TAG = //i; +exports.BR_TAG_ALL = //gi; + +/* + * style and href: pull them out of either single or double quotes. Also + * - target: (_blank|_self|_parent|_top|framename) + * note that you can't use target to get a popup but if you use popup, + * a `framename` will be passed along as the name of the popup window. + * per the spec, cannot contain whitespace. + * for backward compatibility we default to '_blank' + * - popup: a custom one for us to enable popup (new window) links. String + * for window.open -> strWindowFeatures, like 'menubar=yes,width=500,height=550' + * note that at least in Chrome, you need to give at least one property + * in this string or the page will open in a new tab anyway. We follow this + * convention and will not make a popup if this string is empty. + * per the spec, cannot contain whitespace. + * + * Because we hack in other attributes with style (sub & sup), drop any trailing + * semicolon in user-supplied styles so we can consistently append the tag-dependent style + * + * These are for tag attributes; Chrome anyway will convert entities in + * attribute values, but not in attribute names + * you can test this by for example: + * > p = document.createElement('p') + * > p.innerHTML = 'Hi' + * > p.innerHTML + * <- 'Hi' + */ +var STYLEMATCH = /(^|[\s"'])style\s*=\s*("([^"]*);?"|'([^']*);?')/i; +var HREFMATCH = /(^|[\s"'])href\s*=\s*("([^"]*)"|'([^']*)')/i; +var TARGETMATCH = /(^|[\s"'])target\s*=\s*("([^"\s]*)"|'([^'\s]*)')/i; +var POPUPMATCH = /(^|[\s"'])popup\s*=\s*("([\w=,]*)"|'([\w=,]*)')/i; + +// dedicated matcher for these quoted regexes, that can return their results +// in two different places +function getQuotedMatch(_str, re) { + if(!_str) return null; + var match = _str.match(re); + var result = match && (match[3] || match[4]); + return result && convertEntities(result); +} + +var COLORMATCH = /(^|;)\s*color:/; + +/** + * Strip string of tags + * + * @param {string} _str : input string + * @param {object} opts : + * - len {number} max length of output string + * - allowedTags {array} list of pseudo-html tags to NOT strip + * @return {string} + */ +exports.plainText = function(_str, opts) { + opts = opts || {}; + + var len = (opts.len !== undefined && opts.len !== -1) ? opts.len : Infinity; + var allowedTags = opts.allowedTags !== undefined ? opts.allowedTags : ['br']; + + var ellipsis = '...'; + var eLen = ellipsis.length; + + var oldParts = _str.split(SPLIT_TAGS); + var newParts = []; + var prevTag = ''; + var l = 0; + + for(var i = 0; i < oldParts.length; i++) { + var p = oldParts[i]; + var match = p.match(ONE_TAG); + var tagType = match && match[2].toLowerCase(); + + if(tagType) { + // N.B. tags do not count towards string length + if(allowedTags.indexOf(tagType) !== -1) { + newParts.push(p); + prevTag = tagType; + } + } else { + var pLen = p.length; + + if((l + pLen) < len) { + newParts.push(p); + l += pLen; + } else if(l < len) { + var pLen2 = len - l; + + if(prevTag && (prevTag !== 'br' || pLen2 <= eLen || pLen <= eLen)) { + newParts.pop(); + } + + if(len > eLen) { + newParts.push(p.substr(0, pLen2 - eLen) + ellipsis); + } else { + newParts.push(p.substr(0, pLen2)); + } + break; + } + + prevTag = ''; + } + } + + return newParts.join(''); +}; + +/* + * N.B. HTML entities are listed without the leading '&' and trailing ';' + * https://www.freeformatter.com/html-entities.html + * + * FWIW if we wanted to support the full set, it has 2261 entries: + * https://www.w3.org/TR/html5/entities.json + * though I notice that some of these are duplicates and/or are missing ";" + * eg: "&", "&", "&", and "&" all map to "&" + * We no longer need to include numeric entities here, these are now handled + * by String.fromCodePoint/fromCharCode + * + * Anyway the only ones that are really important to allow are the HTML special + * chars <, >, and &, because these ones can trigger special processing if not + * replaced by the corresponding entity. + */ +var entityToUnicode = { + mu: 'μ', + amp: '&', + lt: '<', + gt: '>', + nbsp: ' ', + times: '×', + plusmn: '±', + deg: '°' +}; + +// NOTE: in general entities can contain uppercase too (so [a-zA-Z]) but all the +// ones we support use only lowercase. If we ever change that, update the regex. +var ENTITY_MATCH = /&(#\d+|#x[\da-fA-F]+|[a-z]+);/g; +function convertEntities(_str) { + return _str.replace(ENTITY_MATCH, function(fullMatch, innerMatch) { + var outChar; + if(innerMatch.charAt(0) === '#') { + // cannot use String.fromCodePoint in IE + outChar = fromCodePoint( + innerMatch.charAt(1) === 'x' ? + parseInt(innerMatch.substr(2), 16) : + parseInt(innerMatch.substr(1), 10) + ); + } else outChar = entityToUnicode[innerMatch]; + + // as in regular HTML, if we didn't decode the entity just + // leave the raw text in place. + return outChar || fullMatch; + }); +} +exports.convertEntities = convertEntities; + +function fromCodePoint(code) { + // Don't allow overflow. In Chrome this turns into � but I feel like it's + // more useful to just not convert it at all. + if(code > 0x10FFFF) return; + var stringFromCodePoint = String.fromCodePoint; + if(stringFromCodePoint) return stringFromCodePoint(code); + + // IE doesn't have String.fromCodePoint + // see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint + var stringFromCharCode = String.fromCharCode; + if(code <= 0xFFFF) return stringFromCharCode(code); + return stringFromCharCode( + (code >> 10) + 0xD7C0, + (code % 0x400) + 0xDC00 + ); +} + +/* + * buildSVGText: convert our pseudo-html into SVG tspan elements, and attach these + * to containerNode + * + * @param {svg text element} containerNode: the node to insert this text into + * @param {string} str: the pseudo-html string to convert to svg + * + * @returns {bool}: does the result contain any links? We need to handle the text element + * somewhat differently if it does, so just keep track of this when it happens. + */ +function buildSVGText(containerNode, str) { + /* + * Normalize behavior between IE and others wrt newlines and whitespace:pre + * this combination makes IE barf https://github.com/plotly/plotly.js/issues/746 + * Chrome and FF display \n, \r, or \r\n as a space in this mode. + * I feel like at some point we turned these into
but currently we don't so + * I'm just going to cement what we do now in Chrome and FF + */ + str = str.replace(NEWLINES, ' '); + + var hasLink = false; + + // as we're building the text, keep track of what elements we're nested inside + // nodeStack will be an array of {node, type, style, href, target, popup} + // where only type: 'a' gets the last 3 and node is only added when it's created + var nodeStack = []; + var currentNode; + var currentLine = -1; + + function newLine() { + currentLine++; + + var lineNode = document.createElementNS(xmlnsNamespaces.svg, 'tspan'); + d3.select(lineNode).attr({ + class: 'line', + dy: (currentLine * LINE_SPACING) + 'em' + }); + containerNode.appendChild(lineNode); + + currentNode = lineNode; + + var oldNodeStack = nodeStack; + nodeStack = [{node: lineNode}]; + + if(oldNodeStack.length > 1) { + for(var i = 1; i < oldNodeStack.length; i++) { + enterNode(oldNodeStack[i]); + } + } + } + + function enterNode(nodeSpec) { + var type = nodeSpec.type; + var nodeAttrs = {}; + var nodeType; + + if(type === 'a') { + nodeType = 'a'; + var target = nodeSpec.target; + var href = nodeSpec.href; + var popup = nodeSpec.popup; + if(href) { + nodeAttrs = { + 'xlink:xlink:show': (target === '_blank' || target.charAt(0) !== '_') ? 'new' : 'replace', + target: target, + 'xlink:xlink:href': href + }; + if(popup) { + // security: href and target are not inserted as code but + // as attributes. popup is, but limited to /[A-Za-z0-9_=,]/ + nodeAttrs.onclick = 'window.open(this.href.baseVal,this.target.baseVal,"' + + popup + '");return false;'; + } + } + } else nodeType = 'tspan'; + + if(nodeSpec.style) nodeAttrs.style = nodeSpec.style; + + var newNode = document.createElementNS(xmlnsNamespaces.svg, nodeType); + + if(type === 'sup' || type === 'sub') { + addTextNode(currentNode, ZERO_WIDTH_SPACE); + currentNode.appendChild(newNode); + + var resetter = document.createElementNS(xmlnsNamespaces.svg, 'tspan'); + addTextNode(resetter, ZERO_WIDTH_SPACE); + d3.select(resetter).attr('dy', RESET_DY[type]); + nodeAttrs.dy = SHIFT_DY[type]; + + currentNode.appendChild(newNode); + currentNode.appendChild(resetter); + } else { + currentNode.appendChild(newNode); + } + + d3.select(newNode).attr(nodeAttrs); + + currentNode = nodeSpec.node = newNode; + nodeStack.push(nodeSpec); + } + + function addTextNode(node, text) { + node.appendChild(document.createTextNode(text)); + } + + function exitNode(type) { + // A bare closing tag can't close the root node. If we encounter this it + // means there's an extra closing tag that can just be ignored: + if(nodeStack.length === 1) { + Lib.log('Ignoring unexpected end tag .', str); + return; + } + + var innerNode = nodeStack.pop(); + + if(type !== innerNode.type) { + Lib.log('Start tag <' + innerNode.type + '> doesnt match end tag <' + + type + '>. Pretending it did match.', str); + } + currentNode = nodeStack[nodeStack.length - 1].node; + } + + var hasLines = BR_TAG.test(str); + + if(hasLines) newLine(); + else { + currentNode = containerNode; + nodeStack = [{node: containerNode}]; + } + + var parts = str.split(SPLIT_TAGS); + for(var i = 0; i < parts.length; i++) { + var parti = parts[i]; + var match = parti.match(ONE_TAG); + var tagType = match && match[2].toLowerCase(); + var tagStyle = TAG_STYLES[tagType]; + + if(tagType === 'br') { + newLine(); + } else if(tagStyle === undefined) { + addTextNode(currentNode, convertEntities(parti)); + } else { + // tag - open or close + if(match[1]) { + exitNode(tagType); + } else { + var extra = match[4]; + + var nodeSpec = {type: tagType}; + + // now add style, from both the tag name and any extra css + // Most of the svg css that users will care about is just like html, + // but font color is different (uses fill). Let our users ignore this. + var css = getQuotedMatch(extra, STYLEMATCH); + if(css) { + css = css.replace(COLORMATCH, '$1 fill:'); + if(tagStyle) css += ';' + tagStyle; + } else if(tagStyle) css = tagStyle; + + if(css) nodeSpec.style = css; + + if(tagType === 'a') { + hasLink = true; + + var href = getQuotedMatch(extra, HREFMATCH); + + if(href) { + // check safe protocols + var dummyAnchor = document.createElement('a'); + dummyAnchor.href = href; + if(PROTOCOLS.indexOf(dummyAnchor.protocol) !== -1) { + // Decode href to allow both already encoded and not encoded + // URIs. Without decoding prior encoding, an already encoded + // URI would be encoded twice producing a semantically different URI. + nodeSpec.href = encodeURI(decodeURI(href)); + nodeSpec.target = getQuotedMatch(extra, TARGETMATCH) || '_blank'; + nodeSpec.popup = getQuotedMatch(extra, POPUPMATCH); + } + } + } + + enterNode(nodeSpec); + } + } + } + + return hasLink; +} + +exports.lineCount = function lineCount(s) { + return s.selectAll('tspan.line').size() || 1; +}; + +exports.positionText = function positionText(s, x, y) { + return s.each(function() { + var text = d3.select(this); + + function setOrGet(attr, val) { + if(val === undefined) { + val = text.attr(attr); + if(val === null) { + text.attr(attr, 0); + val = 0; + } + } else text.attr(attr, val); + return val; + } + + var thisX = setOrGet('x', x); + var thisY = setOrGet('y', y); + + if(this.nodeName === 'text') { + text.selectAll('tspan.line').attr({x: thisX, y: thisY}); + } + }); +}; + +function alignHTMLWith(_base, container, options) { + var alignH = options.horizontalAlign; + var alignV = options.verticalAlign || 'top'; + var bRect = _base.node().getBoundingClientRect(); + var cRect = container.node().getBoundingClientRect(); + var thisRect; + var getTop; + var getLeft; + + if(alignV === 'bottom') { + getTop = function() { return bRect.bottom - thisRect.height; }; + } else if(alignV === 'middle') { + getTop = function() { return bRect.top + (bRect.height - thisRect.height) / 2; }; + } else { // default: top + getTop = function() { return bRect.top; }; + } + + if(alignH === 'right') { + getLeft = function() { return bRect.right - thisRect.width; }; + } else if(alignH === 'center') { + getLeft = function() { return bRect.left + (bRect.width - thisRect.width) / 2; }; + } else { // default: left + getLeft = function() { return bRect.left; }; + } + + return function() { + thisRect = this.node().getBoundingClientRect(); + this.style({ + top: (getTop() - cRect.top) + 'px', + left: (getLeft() - cRect.left) + 'px', + 'z-index': 1000 + }); + return this; + }; +} + +/* + * Editable title + * @param {d3.selection} context: the element being edited. Normally text, + * but if it isn't, you should provide the styling options + * @param {object} options: + * @param {div} options.gd: graphDiv + * @param {d3.selection} options.delegate: item to bind events to if not this + * @param {boolean} options.immediate: start editing now (true) or on click (false, default) + * @param {string} options.fill: font color if not as shown + * @param {string} options.background: background color if not as shown + * @param {string} options.text: initial text, if not as shown + * @param {string} options.horizontalAlign: alignment of the edit box wrt. the bound element + * @param {string} options.verticalAlign: alignment of the edit box wrt. the bound element + */ + +exports.makeEditable = function(context, options) { + var gd = options.gd; + var _delegate = options.delegate; + var dispatch = d3.dispatch('edit', 'input', 'cancel'); + var handlerElement = _delegate || context; + + context.style({'pointer-events': _delegate ? 'none' : 'all'}); + + if(context.size() !== 1) throw new Error('boo'); + + function handleClick() { + appendEditable(); + context.style({opacity: 0}); + // also hide any mathjax svg + var svgClass = handlerElement.attr('class'); + var mathjaxClass; + if(svgClass) mathjaxClass = '.' + svgClass.split(' ')[0] + '-math-group'; + else mathjaxClass = '[class*=-math-group]'; + if(mathjaxClass) { + d3.select(context.node().parentNode).select(mathjaxClass).style({opacity: 0}); + } + } + + function selectElementContents(_el) { + var el = _el.node(); + var range = document.createRange(); + range.selectNodeContents(el); + var sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + el.focus(); + } + + function appendEditable() { + var plotDiv = d3.select(gd); + var container = plotDiv.select('.svg-container'); + var div = container.append('div'); + var cStyle = context.node().style; + var fontSize = parseFloat(cStyle.fontSize || 12); + + var initialText = options.text; + if(initialText === undefined) initialText = context.attr('data-unformatted'); + + div.classed('plugin-editable editable', true) + .style({ + position: 'absolute', + 'font-family': cStyle.fontFamily || 'Arial', + 'font-size': fontSize, + color: options.fill || cStyle.fill || 'black', + opacity: 1, + 'background-color': options.background || 'transparent', + outline: '#ffffff33 1px solid', + margin: [-fontSize / 8 + 1, 0, 0, -1].join('px ') + 'px', + padding: '0', + 'box-sizing': 'border-box' + }) + .attr({contenteditable: true}) + .text(initialText) + .call(alignHTMLWith(context, container, options)) + .on('blur', function() { + gd._editing = false; + context.text(this.textContent) + .style({opacity: 1}); + var svgClass = d3.select(this).attr('class'); + var mathjaxClass; + if(svgClass) mathjaxClass = '.' + svgClass.split(' ')[0] + '-math-group'; + else mathjaxClass = '[class*=-math-group]'; + if(mathjaxClass) { + d3.select(context.node().parentNode).select(mathjaxClass).style({opacity: 0}); + } + var text = this.textContent; + d3.select(this).transition().duration(0).remove(); + d3.select(document).on('mouseup', null); + dispatch.edit.call(context, text); + }) + .on('focus', function() { + var editDiv = this; + gd._editing = true; + d3.select(document).on('mouseup', function() { + if(d3.event.target === editDiv) return false; + if(document.activeElement === div.node()) div.node().blur(); + }); + }) + .on('keyup', function() { + if(d3.event.which === 27) { + gd._editing = false; + context.style({opacity: 1}); + d3.select(this) + .style({opacity: 0}) + .on('blur', function() { return false; }) + .transition().remove(); + dispatch.cancel.call(context, this.textContent); + } else { + dispatch.input.call(context, this.textContent); + d3.select(this).call(alignHTMLWith(context, container, options)); + } + }) + .on('keydown', function() { + if(d3.event.which === 13) this.blur(); + }) + .call(selectElementContents); + } + + if(options.immediate) handleClick(); + else handlerElement.on('click', handleClick); + + return d3.rebind(context, dispatch, 'on'); +}; + +},{"../constants/alignment":145,"../constants/xmlns_namespaces":150,"../lib":169,"d3":16}],191:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var timerCache = {}; + +/** + * Throttle a callback. `callback` executes synchronously only if + * more than `minInterval` milliseconds have already elapsed since the latest + * call (if any). Otherwise we wait until `minInterval` is over and execute the + * last callback received while waiting. + * So the first and last events in a train are always executed (eventually) + * but some of the events in the middle can be dropped. + * + * @param {string} id: an identifier to mark events to throttle together + * @param {number} minInterval: minimum time, in milliseconds, between + * invocations of `callback` + * @param {function} callback: the function to throttle. `callback` itself + * should be a purely synchronous function. + */ +exports.throttle = function throttle(id, minInterval, callback) { + var cache = timerCache[id]; + var now = Date.now(); + + if(!cache) { + /* + * Throw out old items before making a new one, to prevent the cache + * getting overgrown, for example from old plots that have been replaced. + * 1 minute age is arbitrary. + */ + for(var idi in timerCache) { + if(timerCache[idi].ts < now - 60000) { + delete timerCache[idi]; + } + } + cache = timerCache[id] = {ts: 0, timer: null}; + } + + _clearTimeout(cache); + + function exec() { + callback(); + cache.ts = Date.now(); + if(cache.onDone) { + cache.onDone(); + cache.onDone = null; + } + } + + if(now > cache.ts + minInterval) { + exec(); + return; + } + + cache.timer = setTimeout(function() { + exec(); + cache.timer = null; + }, minInterval); +}; + +exports.done = function(id) { + var cache = timerCache[id]; + if(!cache || !cache.timer) return Promise.resolve(); + + return new Promise(function(resolve) { + var previousOnDone = cache.onDone; + cache.onDone = function onDone() { + if(previousOnDone) previousOnDone(); + resolve(); + cache.onDone = null; + }; + }); +}; + +/** + * Clear the throttle cache for one or all timers + * @param {optional string} id: + * if provided, clear just this timer + * if omitted, clear all timers (mainly useful for testing) + */ +exports.clear = function(id) { + if(id) { + _clearTimeout(timerCache[id]); + delete timerCache[id]; + } else { + for(var idi in timerCache) exports.clear(idi); + } +}; + +function _clearTimeout(cache) { + if(cache && cache.timer !== null) { + clearTimeout(cache.timer); + cache.timer = null; + } +} + +},{}],192:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); + +/** + * convert a linear value into a logged value, folding negative numbers into + * the given range + */ +module.exports = function toLogRange(val, range) { + if(val > 0) return Math.log(val) / Math.LN10; + + // move a negative value reference to a log axis - just put the + // result at the lowest range value on the plot (or if the range also went negative, + // one millionth of the top of the range) + var newVal = Math.log(Math.min(range[0], range[1])) / Math.LN10; + if(!isNumeric(newVal)) newVal = Math.log(Math.max(range[0], range[1])) / Math.LN10 - 6; + return newVal; +}; + +},{"fast-isnumeric":18}],193:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + moduleType: 'locale', + name: 'en-US', + dictionary: { + 'Click to enter Colorscale title': 'Click to enter Colorscale title' + }, + format: { + date: '%m/%d/%Y' + } +}; + +},{}],194:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + moduleType: 'locale', + name: 'en', + dictionary: { + 'Click to enter Colorscale title': 'Click to enter Colourscale title' + }, + format: { + days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + shortDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + months: [ + 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December' + ], + shortMonths: [ + 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' + ], + periods: ['AM', 'PM'], + dateTime: '%a %b %e %X %Y', + date: '%d/%m/%Y', + time: '%H:%M:%S', + decimal: '.', + thousands: ',', + grouping: [3], + currency: ['$', ''], + year: '%Y', + month: '%b %Y', + dayMonth: '%b %-d', + dayMonthYear: '%b %-d, %Y' + } +}; + +},{}],195:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Registry = _dereq_('../registry'); + +/* + * containerArrayMatch: does this attribute string point into a + * layout container array? + * + * @param {String} astr: an attribute string, like *annotations[2].text* + * + * @returns {Object | false} Returns false if `astr` doesn't match a container + * array. If it does, returns: + * {array: {String}, index: {Number}, property: {String}} + * ie the attribute string for the array, the index within the array (or '' + * if the whole array) and the property within that (or '' if the whole array + * or the whole object) + */ +module.exports = function containerArrayMatch(astr) { + var rootContainers = Registry.layoutArrayContainers; + var regexpContainers = Registry.layoutArrayRegexes; + var rootPart = astr.split('[')[0]; + var arrayStr; + var match; + + // look for regexp matches first, because they may be nested inside root matches + // eg updatemenus[i].buttons is nested inside updatemenus + for(var i = 0; i < regexpContainers.length; i++) { + match = astr.match(regexpContainers[i]); + if(match && match.index === 0) { + arrayStr = match[0]; + break; + } + } + + // now look for root matches + if(!arrayStr) arrayStr = rootContainers[rootContainers.indexOf(rootPart)]; + + if(!arrayStr) return false; + + var tail = astr.substr(arrayStr.length); + if(!tail) return {array: arrayStr, index: '', property: ''}; + + match = tail.match(/^\[(0|[1-9][0-9]*)\](\.(.+))?$/); + if(!match) return false; + + return {array: arrayStr, index: Number(match[1]), property: match[3] || ''}; +}; + +},{"../registry":258}],196:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../lib'); +var extendFlat = Lib.extendFlat; +var isPlainObject = Lib.isPlainObject; + +var traceOpts = { + valType: 'flaglist', + extras: ['none'], + flags: ['calc', 'clearAxisTypes', 'plot', 'style', 'markerSize', 'colorbars'], + +}; + +var layoutOpts = { + valType: 'flaglist', + extras: ['none'], + flags: [ + 'calc', 'plot', 'legend', 'ticks', 'axrange', + 'layoutstyle', 'modebar', 'camera', 'arraydraw', 'colorbars' + ], + +}; + +// flags for inside restyle/relayout include a few extras +// that shouldn't be used in attributes, to deal with certain +// combinations and conditionals efficiently +var traceEditTypeFlags = traceOpts.flags.slice() + .concat(['fullReplot']); + +var layoutEditTypeFlags = layoutOpts.flags.slice() + .concat('layoutReplot'); + +module.exports = { + traces: traceOpts, + layout: layoutOpts, + /* + * default (all false) edit flags for restyle (traces) + * creates a new object each call, so the caller can mutate freely + */ + traceFlags: function() { return falseObj(traceEditTypeFlags); }, + + /* + * default (all false) edit flags for relayout + * creates a new object each call, so the caller can mutate freely + */ + layoutFlags: function() { return falseObj(layoutEditTypeFlags); }, + + /* + * update `flags` with the `editType` values found in `attr` + */ + update: function(flags, attr) { + var editType = attr.editType; + if(editType && editType !== 'none') { + var editTypeParts = editType.split('+'); + for(var i = 0; i < editTypeParts.length; i++) { + flags[editTypeParts[i]] = true; + } + } + }, + + overrideAll: overrideAll +}; + +function falseObj(keys) { + var out = {}; + for(var i = 0; i < keys.length; i++) out[keys[i]] = false; + return out; +} + +/** + * For attributes that are largely copied from elsewhere into a plot type that doesn't + * support partial redraws - overrides the editType field of all attributes in the object + * + * @param {object} attrs: the attributes to override. Will not be mutated. + * @param {string} editTypeOverride: the new editType to use + * @param {'nested'|'from-root'} overrideContainers: + * - 'nested' will override editType for nested containers but not the root. + * - 'from-root' will also override editType of the root container. + * Containers below the absolute top level (trace or layout root) DO need an + * editType even if they are not `valObject`s themselves (eg `scatter.marker`) + * to handle the case where you edit the whole container. + * + * @return {object} a new attributes object with `editType` modified as directed + */ +function overrideAll(attrs, editTypeOverride, overrideContainers) { + var out = extendFlat({}, attrs); + for(var key in out) { + var attr = out[key]; + if(isPlainObject(attr)) { + out[key] = overrideOne(attr, editTypeOverride, overrideContainers, key); + } + } + if(overrideContainers === 'from-root') out.editType = editTypeOverride; + + return out; +} + +function overrideOne(attr, editTypeOverride, overrideContainers, key) { + if(attr.valType) { + var out = extendFlat({}, attr); + out.editType = editTypeOverride; + + if(Array.isArray(attr.items)) { + out.items = new Array(attr.items.length); + for(var i = 0; i < attr.items.length; i++) { + out.items[i] = overrideOne(attr.items[i], editTypeOverride, 'from-root'); + } + } + return out; + } else { + // don't provide an editType for the _deprecated container + return overrideAll(attr, editTypeOverride, + (key.charAt(0) === '_') ? 'nested' : 'from-root'); + } +} + +},{"../lib":169}],197:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); +var m4FromQuat = _dereq_('gl-mat4/fromQuat'); + +var Registry = _dereq_('../registry'); +var Lib = _dereq_('../lib'); +var Plots = _dereq_('../plots/plots'); +var AxisIds = _dereq_('../plots/cartesian/axis_ids'); +var Color = _dereq_('../components/color'); + +var cleanId = AxisIds.cleanId; +var getFromTrace = AxisIds.getFromTrace; +var traceIs = Registry.traceIs; + +// clear the promise queue if one of them got rejected +exports.clearPromiseQueue = function(gd) { + if(Array.isArray(gd._promises) && gd._promises.length > 0) { + Lib.log('Clearing previous rejected promises from queue.'); + } + + gd._promises = []; +}; + +// make a few changes to the layout right away +// before it gets used for anything +// backward compatibility and cleanup of nonstandard options +exports.cleanLayout = function(layout) { + var i, j; + + if(!layout) layout = {}; + + // cannot have (x|y)axis1, numbering goes axis, axis2, axis3... + if(layout.xaxis1) { + if(!layout.xaxis) layout.xaxis = layout.xaxis1; + delete layout.xaxis1; + } + if(layout.yaxis1) { + if(!layout.yaxis) layout.yaxis = layout.yaxis1; + delete layout.yaxis1; + } + if(layout.scene1) { + if(!layout.scene) layout.scene = layout.scene1; + delete layout.scene1; + } + + var axisAttrRegex = (Plots.subplotsRegistry.cartesian || {}).attrRegex; + var polarAttrRegex = (Plots.subplotsRegistry.polar || {}).attrRegex; + var ternaryAttrRegex = (Plots.subplotsRegistry.ternary || {}).attrRegex; + var sceneAttrRegex = (Plots.subplotsRegistry.gl3d || {}).attrRegex; + + var keys = Object.keys(layout); + for(i = 0; i < keys.length; i++) { + var key = keys[i]; + + if(axisAttrRegex && axisAttrRegex.test(key)) { + // modifications to cartesian axes + + var ax = layout[key]; + if(ax.anchor && ax.anchor !== 'free') { + ax.anchor = cleanId(ax.anchor); + } + if(ax.overlaying) ax.overlaying = cleanId(ax.overlaying); + + // old method of axis type - isdate and islog (before category existed) + if(!ax.type) { + if(ax.isdate) ax.type = 'date'; + else if(ax.islog) ax.type = 'log'; + else if(ax.isdate === false && ax.islog === false) ax.type = 'linear'; + } + if(ax.autorange === 'withzero' || ax.autorange === 'tozero') { + ax.autorange = true; + ax.rangemode = 'tozero'; + } + delete ax.islog; + delete ax.isdate; + delete ax.categories; // replaced by _categories + + // prune empty domain arrays made before the new nestedProperty + if(emptyContainer(ax, 'domain')) delete ax.domain; + + // autotick -> tickmode + if(ax.autotick !== undefined) { + if(ax.tickmode === undefined) { + ax.tickmode = ax.autotick ? 'auto' : 'linear'; + } + delete ax.autotick; + } + + cleanTitle(ax); + } else if(polarAttrRegex && polarAttrRegex.test(key)) { + // modifications for polar + + var polar = layout[key]; + cleanTitle(polar.radialaxis); + } else if(ternaryAttrRegex && ternaryAttrRegex.test(key)) { + // modifications for ternary + + var ternary = layout[key]; + cleanTitle(ternary.aaxis); + cleanTitle(ternary.baxis); + cleanTitle(ternary.caxis); + } else if(sceneAttrRegex && sceneAttrRegex.test(key)) { + // modifications for 3D scenes + + var scene = layout[key]; + + // clean old Camera coords + var cameraposition = scene.cameraposition; + + if(Array.isArray(cameraposition) && cameraposition[0].length === 4) { + var rotation = cameraposition[0]; + var center = cameraposition[1]; + var radius = cameraposition[2]; + var mat = m4FromQuat([], rotation); + var eye = []; + + for(j = 0; j < 3; ++j) { + eye[j] = center[j] + radius * mat[2 + 4 * j]; + } + + scene.camera = { + eye: {x: eye[0], y: eye[1], z: eye[2]}, + center: {x: center[0], y: center[1], z: center[2]}, + up: {x: 0, y: 0, z: 1} // we just ignore calculating camera z up in this case + }; + + delete scene.cameraposition; + } + + // clean axis titles + cleanTitle(scene.xaxis); + cleanTitle(scene.yaxis); + cleanTitle(scene.zaxis); + } + } + + var annotationsLen = Array.isArray(layout.annotations) ? layout.annotations.length : 0; + for(i = 0; i < annotationsLen; i++) { + var ann = layout.annotations[i]; + + if(!Lib.isPlainObject(ann)) continue; + + if(ann.ref) { + if(ann.ref === 'paper') { + ann.xref = 'paper'; + ann.yref = 'paper'; + } else if(ann.ref === 'data') { + ann.xref = 'x'; + ann.yref = 'y'; + } + delete ann.ref; + } + + cleanAxRef(ann, 'xref'); + cleanAxRef(ann, 'yref'); + } + + var shapesLen = Array.isArray(layout.shapes) ? layout.shapes.length : 0; + for(i = 0; i < shapesLen; i++) { + var shape = layout.shapes[i]; + + if(!Lib.isPlainObject(shape)) continue; + + cleanAxRef(shape, 'xref'); + cleanAxRef(shape, 'yref'); + } + + var legend = layout.legend; + if(legend) { + // check for old-style legend positioning (x or y is +/- 100) + if(legend.x > 3) { + legend.x = 1.02; + legend.xanchor = 'left'; + } else if(legend.x < -2) { + legend.x = -0.02; + legend.xanchor = 'right'; + } + + if(legend.y > 3) { + legend.y = 1.02; + legend.yanchor = 'bottom'; + } else if(legend.y < -2) { + legend.y = -0.02; + legend.yanchor = 'top'; + } + } + + // clean plot title + cleanTitle(layout); + + /* + * Moved from rotate -> orbit for dragmode + */ + if(layout.dragmode === 'rotate') layout.dragmode = 'orbit'; + + // sanitize rgb(fractions) and rgba(fractions) that old tinycolor + // supported, but new tinycolor does not because they're not valid css + Color.clean(layout); + + // clean the layout container in layout.template + if(layout.template && layout.template.layout) { + exports.cleanLayout(layout.template.layout); + } + + return layout; +}; + +function cleanAxRef(container, attr) { + var valIn = container[attr]; + var axLetter = attr.charAt(0); + if(valIn && valIn !== 'paper') { + container[attr] = cleanId(valIn, axLetter); + } +} + +/** + * Cleans up old title attribute structure (flat) in favor of the new one (nested). + * + * @param {Object} titleContainer - an object potentially including deprecated title attributes + */ +function cleanTitle(titleContainer) { + if(titleContainer) { + // title -> title.text + // (although title used to be a string attribute, + // numbers are accepted as well) + if(typeof titleContainer.title === 'string' || typeof titleContainer.title === 'number') { + titleContainer.title = { + text: titleContainer.title + }; + } + + rewireAttr('titlefont', 'font'); + rewireAttr('titleposition', 'position'); + rewireAttr('titleside', 'side'); + rewireAttr('titleoffset', 'offset'); + } + + function rewireAttr(oldAttrName, newAttrName) { + var oldAttrSet = titleContainer[oldAttrName]; + var newAttrSet = titleContainer.title && titleContainer.title[newAttrName]; + + if(oldAttrSet && !newAttrSet) { + // Ensure title object exists + if(!titleContainer.title) { + titleContainer.title = {}; + } + + titleContainer.title[newAttrName] = titleContainer[oldAttrName]; + delete titleContainer[oldAttrName]; + } + } +} + +/* + * cleanData: Make a few changes to the data for backward compatibility + * before it gets used for anything. Modifies the data traces users provide. + * + * Important: if you're going to add something here that modifies a data array, + * update it in place so the new array === the old one. + */ +exports.cleanData = function(data) { + for(var tracei = 0; tracei < data.length; tracei++) { + var trace = data[tracei]; + var i; + + // use xbins to bin data in x, and ybins to bin data in y + if(trace.type === 'histogramy' && 'xbins' in trace && !('ybins' in trace)) { + trace.ybins = trace.xbins; + delete trace.xbins; + } + + // error_y.opacity is obsolete - merge into color + if(trace.error_y && 'opacity' in trace.error_y) { + var dc = Color.defaults; + var yeColor = trace.error_y.color || (traceIs(trace, 'bar') ? + Color.defaultLine : + dc[tracei % dc.length]); + trace.error_y.color = Color.addOpacity( + Color.rgb(yeColor), + Color.opacity(yeColor) * trace.error_y.opacity); + delete trace.error_y.opacity; + } + + // convert bardir to orientation, and put the data into + // the axes it's eventually going to be used with + if('bardir' in trace) { + if(trace.bardir === 'h' && (traceIs(trace, 'bar') || + trace.type.substr(0, 9) === 'histogram')) { + trace.orientation = 'h'; + exports.swapXYData(trace); + } + delete trace.bardir; + } + + // now we have only one 1D histogram type, and whether + // it uses x or y data depends on trace.orientation + if(trace.type === 'histogramy') exports.swapXYData(trace); + if(trace.type === 'histogramx' || trace.type === 'histogramy') { + trace.type = 'histogram'; + } + + // scl->scale, reversescl->reversescale + if('scl' in trace && !('colorscale' in trace)) { + trace.colorscale = trace.scl; + delete trace.scl; + } + if('reversescl' in trace && !('reversescale' in trace)) { + trace.reversescale = trace.reversescl; + delete trace.reversescl; + } + + // axis ids x1 -> x, y1-> y + if(trace.xaxis) trace.xaxis = cleanId(trace.xaxis, 'x'); + if(trace.yaxis) trace.yaxis = cleanId(trace.yaxis, 'y'); + + // scene ids scene1 -> scene + if(traceIs(trace, 'gl3d') && trace.scene) { + trace.scene = Plots.subplotsRegistry.gl3d.cleanId(trace.scene); + } + + if(!traceIs(trace, 'pie-like') && !traceIs(trace, 'bar-like')) { + if(Array.isArray(trace.textposition)) { + for(i = 0; i < trace.textposition.length; i++) { + trace.textposition[i] = cleanTextPosition(trace.textposition[i]); + } + } else if(trace.textposition) { + trace.textposition = cleanTextPosition(trace.textposition); + } + } + + // fix typo in colorscale definition + var _module = Registry.getModule(trace); + if(_module && _module.colorbar) { + var containerName = _module.colorbar.container; + var container = containerName ? trace[containerName] : trace; + if(container && container.colorscale) { + if(container.colorscale === 'YIGnBu') container.colorscale = 'YlGnBu'; + if(container.colorscale === 'YIOrRd') container.colorscale = 'YlOrRd'; + } + } + + // fix typo in surface 'highlight*' definitions + if(trace.type === 'surface' && Lib.isPlainObject(trace.contours)) { + var dims = ['x', 'y', 'z']; + + for(i = 0; i < dims.length; i++) { + var opts = trace.contours[dims[i]]; + + if(!Lib.isPlainObject(opts)) continue; + + if(opts.highlightColor) { + opts.highlightcolor = opts.highlightColor; + delete opts.highlightColor; + } + + if(opts.highlightWidth) { + opts.highlightwidth = opts.highlightWidth; + delete opts.highlightWidth; + } + } + } + + // fixes from converting finance from transforms to real trace types + if(trace.type === 'candlestick' || trace.type === 'ohlc') { + var increasingShowlegend = (trace.increasing || {}).showlegend !== false; + var decreasingShowlegend = (trace.decreasing || {}).showlegend !== false; + var increasingName = cleanFinanceDir(trace.increasing); + var decreasingName = cleanFinanceDir(trace.decreasing); + + // now figure out something smart to do with the separate direction + // names we removed + if((increasingName !== false) && (decreasingName !== false)) { + // both sub-names existed: base name previously had no effect + // so ignore it and try to find a shared part of the sub-names + + var newName = commonPrefix( + increasingName, decreasingName, + increasingShowlegend, decreasingShowlegend + ); + // if no common part, leave whatever name was (or wasn't) there + if(newName) trace.name = newName; + } else if((increasingName || decreasingName) && !trace.name) { + // one sub-name existed but not the base name - just use the sub-name + trace.name = increasingName || decreasingName; + } + } + + // transforms backward compatibility fixes + if(Array.isArray(trace.transforms)) { + var transforms = trace.transforms; + + for(i = 0; i < transforms.length; i++) { + var transform = transforms[i]; + + if(!Lib.isPlainObject(transform)) continue; + + switch(transform.type) { + case 'filter': + if(transform.filtersrc) { + transform.target = transform.filtersrc; + delete transform.filtersrc; + } + + if(transform.calendar) { + if(!transform.valuecalendar) { + transform.valuecalendar = transform.calendar; + } + delete transform.calendar; + } + break; + + case 'groupby': + // Name has changed from `style` to `styles`, so use `style` but prefer `styles`: + transform.styles = transform.styles || transform.style; + + if(transform.styles && !Array.isArray(transform.styles)) { + var prevStyles = transform.styles; + var styleKeys = Object.keys(prevStyles); + + transform.styles = []; + for(var j = 0; j < styleKeys.length; j++) { + transform.styles.push({ + target: styleKeys[j], + value: prevStyles[styleKeys[j]] + }); + } + } + break; + } + } + } + + // prune empty containers made before the new nestedProperty + if(emptyContainer(trace, 'line')) delete trace.line; + if('marker' in trace) { + if(emptyContainer(trace.marker, 'line')) delete trace.marker.line; + if(emptyContainer(trace, 'marker')) delete trace.marker; + } + + // sanitize rgb(fractions) and rgba(fractions) that old tinycolor + // supported, but new tinycolor does not because they're not valid css + Color.clean(trace); + + // remove obsolete autobin(x|y) attributes, but only if true + // if false, this needs to happen in Histogram.calc because it + // can be a one-time autobin so we need to know the results before + // we can push them back into the trace. + if(trace.autobinx) { + delete trace.autobinx; + delete trace.xbins; + } + if(trace.autobiny) { + delete trace.autobiny; + delete trace.ybins; + } + + cleanTitle(trace); + if(trace.colorbar) cleanTitle(trace.colorbar); + if(trace.marker && trace.marker.colorbar) cleanTitle(trace.marker.colorbar); + if(trace.line && trace.line.colorbar) cleanTitle(trace.line.colorbar); + if(trace.aaxis) cleanTitle(trace.aaxis); + if(trace.baxis) cleanTitle(trace.baxis); + } +}; + +function cleanFinanceDir(dirContainer) { + if(!Lib.isPlainObject(dirContainer)) return false; + + var dirName = dirContainer.name; + + delete dirContainer.name; + delete dirContainer.showlegend; + + return (typeof dirName === 'string' || typeof dirName === 'number') && String(dirName); +} + +function commonPrefix(name1, name2, show1, show2) { + // if only one is shown in the legend, use that + if(show1 && !show2) return name1; + if(show2 && !show1) return name2; + + // if both or neither are in the legend, check if one is blank (or whitespace) + // and use the other one + // note that hover labels can still use the name even if the legend doesn't + if(!name1.trim()) return name2; + if(!name2.trim()) return name1; + + var minLen = Math.min(name1.length, name2.length); + var i; + for(i = 0; i < minLen; i++) { + if(name1.charAt(i) !== name2.charAt(i)) break; + } + + var out = name1.substr(0, i); + return out.trim(); +} + +// textposition - support partial attributes (ie just 'top') +// and incorrect use of middle / center etc. +function cleanTextPosition(textposition) { + var posY = 'middle'; + var posX = 'center'; + + if(typeof textposition === 'string') { + if(textposition.indexOf('top') !== -1) posY = 'top'; + else if(textposition.indexOf('bottom') !== -1) posY = 'bottom'; + + if(textposition.indexOf('left') !== -1) posX = 'left'; + else if(textposition.indexOf('right') !== -1) posX = 'right'; + } + + return posY + ' ' + posX; +} + +function emptyContainer(outer, innerStr) { + return (innerStr in outer) && + (typeof outer[innerStr] === 'object') && + (Object.keys(outer[innerStr]).length === 0); +} + + +// swap all the data and data attributes associated with x and y +exports.swapXYData = function(trace) { + var i; + Lib.swapAttrs(trace, ['?', '?0', 'd?', '?bins', 'nbins?', 'autobin?', '?src', 'error_?']); + if(Array.isArray(trace.z) && Array.isArray(trace.z[0])) { + if(trace.transpose) delete trace.transpose; + else trace.transpose = true; + } + if(trace.error_x && trace.error_y) { + var errorY = trace.error_y; + var copyYstyle = ('copy_ystyle' in errorY) ? + errorY.copy_ystyle : + !(errorY.color || errorY.thickness || errorY.width); + Lib.swapAttrs(trace, ['error_?.copy_ystyle']); + if(copyYstyle) { + Lib.swapAttrs(trace, ['error_?.color', 'error_?.thickness', 'error_?.width']); + } + } + if(typeof trace.hoverinfo === 'string') { + var hoverInfoParts = trace.hoverinfo.split('+'); + for(i = 0; i < hoverInfoParts.length; i++) { + if(hoverInfoParts[i] === 'x') hoverInfoParts[i] = 'y'; + else if(hoverInfoParts[i] === 'y') hoverInfoParts[i] = 'x'; + } + trace.hoverinfo = hoverInfoParts.join('+'); + } +}; + +// coerce traceIndices input to array of trace indices +exports.coerceTraceIndices = function(gd, traceIndices) { + if(isNumeric(traceIndices)) { + return [traceIndices]; + } else if(!Array.isArray(traceIndices) || !traceIndices.length) { + return gd.data.map(function(_, i) { return i; }); + } else if(Array.isArray(traceIndices)) { + var traceIndicesOut = []; + for(var i = 0; i < traceIndices.length; i++) { + if(Lib.isIndex(traceIndices[i], gd.data.length)) { + traceIndicesOut.push(traceIndices[i]); + } else { + Lib.warn('trace index (', traceIndices[i], ') is not a number or is out of bounds'); + } + } + return traceIndicesOut; + } + + return traceIndices; +}; + +/** + * Manages logic around array container item creation / deletion / update + * that nested property alone can't handle. + * + * @param {Object} np + * nested property of update attribute string about trace or layout object + * @param {*} newVal + * update value passed to restyle / relayout / update + * @param {Object} undoit + * undo hash (N.B. undoit may be mutated here). + * + */ +exports.manageArrayContainers = function(np, newVal, undoit) { + var obj = np.obj; + var parts = np.parts; + var pLength = parts.length; + var pLast = parts[pLength - 1]; + + var pLastIsNumber = isNumeric(pLast); + + if(pLastIsNumber && newVal === null) { + // delete item + + // Clear item in array container when new value is null + var contPath = parts.slice(0, pLength - 1).join('.'); + var cont = Lib.nestedProperty(obj, contPath).get(); + cont.splice(pLast, 1); + + // Note that nested property clears null / undefined at end of + // array container, but not within them. + } else if(pLastIsNumber && np.get() === undefined) { + // create item + + // When adding a new item, make sure undo command will remove it + if(np.get() === undefined) undoit[np.astr] = null; + + np.set(newVal); + } else { + // update item + + // If the last part of attribute string isn't a number, + // np.set is all we need. + np.set(newVal); + } +}; + +/* + * Match the part to strip off to turn an attribute into its parent + * really it should be either '.some_characters' or '[number]' + * but we're a little more permissive here and match either + * '.not_brackets_or_dot' or '[not_brackets_or_dot]' + */ +var ATTR_TAIL_RE = /(\.[^\[\]\.]+|\[[^\[\]\.]+\])$/; + +function getParent(attr) { + var tail = attr.search(ATTR_TAIL_RE); + if(tail > 0) return attr.substr(0, tail); +} + +/* + * hasParent: does an attribute object contain a parent of the given attribute? + * for example, given 'images[2].x' do we also have 'images' or 'images[2]'? + * + * @param {Object} aobj + * update object, whose keys are attribute strings and values are their new settings + * @param {string} attr + * the attribute string to test against + * @returns {Boolean} + * is a parent of attr present in aobj? + */ +exports.hasParent = function(aobj, attr) { + var attrParent = getParent(attr); + while(attrParent) { + if(attrParent in aobj) return true; + attrParent = getParent(attrParent); + } + return false; +}; + +/** + * Empty out types for all axes containing these traces so we auto-set them again + * + * @param {object} gd + * @param {[integer]} traces: trace indices to search for axes to clear the types of + * @param {object} layoutUpdate: any update being done concurrently to the layout, + * which may supercede clearing the axis types + */ +var axLetters = ['x', 'y', 'z']; +exports.clearAxisTypes = function(gd, traces, layoutUpdate) { + for(var i = 0; i < traces.length; i++) { + var trace = gd._fullData[i]; + for(var j = 0; j < 3; j++) { + var ax = getFromTrace(gd, trace, axLetters[j]); + + // do not clear log type - that's never an auto result so must have been intentional + if(ax && ax.type !== 'log') { + var axAttr = ax._name; + var sceneName = ax._id.substr(1); + if(sceneName.substr(0, 5) === 'scene') { + if(layoutUpdate[sceneName] !== undefined) continue; + axAttr = sceneName + '.' + axAttr; + } + var typeAttr = axAttr + '.type'; + + if(layoutUpdate[axAttr] === undefined && layoutUpdate[typeAttr] === undefined) { + Lib.nestedProperty(gd.layout, typeAttr).set(null); + } + } + } + } +}; + +},{"../components/color":51,"../lib":169,"../plots/cartesian/axis_ids":216,"../plots/plots":245,"../registry":258,"fast-isnumeric":18,"gl-mat4/fromQuat":19}],198:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var main = _dereq_('./plot_api'); + +exports.plot = main.plot; +exports.newPlot = main.newPlot; +exports.restyle = main.restyle; +exports.relayout = main.relayout; +exports.redraw = main.redraw; +exports.update = main.update; +exports._guiRestyle = main._guiRestyle; +exports._guiRelayout = main._guiRelayout; +exports._guiUpdate = main._guiUpdate; +exports._storeDirectGUIEdit = main._storeDirectGUIEdit; +exports.react = main.react; +exports.extendTraces = main.extendTraces; +exports.prependTraces = main.prependTraces; +exports.addTraces = main.addTraces; +exports.deleteTraces = main.deleteTraces; +exports.moveTraces = main.moveTraces; +exports.purge = main.purge; +exports.addFrames = main.addFrames; +exports.deleteFrames = main.deleteFrames; +exports.animate = main.animate; +exports.setPlotConfig = main.setPlotConfig; + +exports.toImage = _dereq_('./to_image'); +exports.validate = _dereq_('./validate'); +exports.downloadImage = _dereq_('../snapshot/download'); + +var templateApi = _dereq_('./template_api'); +exports.makeTemplate = templateApi.makeTemplate; +exports.validateTemplate = templateApi.validateTemplate; + +},{"../snapshot/download":260,"./plot_api":200,"./template_api":205,"./to_image":206,"./validate":207}],199:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var isPlainObject = _dereq_('../lib/is_plain_object'); +var noop = _dereq_('../lib/noop'); +var Loggers = _dereq_('../lib/loggers'); +var sorterAsc = _dereq_('../lib/search').sorterAsc; +var Registry = _dereq_('../registry'); + + +exports.containerArrayMatch = _dereq_('./container_array_match'); + +var isAddVal = exports.isAddVal = function isAddVal(val) { + return val === 'add' || isPlainObject(val); +}; + +var isRemoveVal = exports.isRemoveVal = function isRemoveVal(val) { + return val === null || val === 'remove'; +}; + +/* + * applyContainerArrayChanges: for managing arrays of layout components in relayout + * handles them all with a consistent interface. + * + * Here are the supported actions -> relayout calls -> edits we get here + * (as prepared in _relayout): + * + * add an empty obj -> {'annotations[2]': 'add'} -> {2: {'': 'add'}} + * add a specific obj -> {'annotations[2]': {attrs}} -> {2: {'': {attrs}}} + * delete an obj -> {'annotations[2]': 'remove'} -> {2: {'': 'remove'}} + * -> {'annotations[2]': null} -> {2: {'': null}} + * delete the whole array -> {'annotations': 'remove'} -> {'': {'': 'remove'}} + * -> {'annotations': null} -> {'': {'': null}} + * edit an object -> {'annotations[2].text': 'boo'} -> {2: {'text': 'boo'}} + * + * You can combine many edits to different objects. Objects are added and edited + * in ascending order, then removed in descending order. + * For example, starting with [a, b, c], if you want to: + * - replace b with d: + * {'annotations[1]': d, 'annotations[2]': null} (b is item 2 after adding d) + * - add a new item d between a and b, and edit b: + * {'annotations[1]': d, 'annotations[2].x': newX} (b is item 2 after adding d) + * - delete b and edit c: + * {'annotations[1]': null, 'annotations[2].x': newX} (c is edited before b is removed) + * + * You CANNOT combine adding/deleting an item at index `i` with edits to the same index `i` + * You CANNOT combine replacing/deleting the whole array with anything else (for the same array). + * + * @param {HTMLDivElement} gd + * the DOM element of the graph container div + * @param {Lib.nestedProperty} componentType: the array we are editing + * @param {Object} edits + * the changes to make; keys are indices to edit, values are themselves objects: + * {attr: newValue} of changes to make to that index (with add/remove behavior + * in special values of the empty attr) + * @param {Object} flags + * the flags for which actions we're going to perform to display these (and + * any other) changes. If we're already `recalc`ing, we don't need to redraw + * individual items + * @param {function} _nestedProperty + * a (possibly modified for gui edits) nestedProperty constructor + * The modified version takes a 3rd argument, for a prefix to the attribute + * string necessary for storing GUI edits + * + * @returns {bool} `true` if it managed to complete drawing of the changes + * `false` would mean the parent should replot. + */ +exports.applyContainerArrayChanges = function applyContainerArrayChanges(gd, np, edits, flags, _nestedProperty) { + var componentType = np.astr; + var supplyComponentDefaults = Registry.getComponentMethod(componentType, 'supplyLayoutDefaults'); + var draw = Registry.getComponentMethod(componentType, 'draw'); + var drawOne = Registry.getComponentMethod(componentType, 'drawOne'); + var replotLater = flags.replot || flags.recalc || (supplyComponentDefaults === noop) || (draw === noop); + var layout = gd.layout; + var fullLayout = gd._fullLayout; + + if(edits['']) { + if(Object.keys(edits).length > 1) { + Loggers.warn('Full array edits are incompatible with other edits', + componentType); + } + + var fullVal = edits['']['']; + + if(isRemoveVal(fullVal)) np.set(null); + else if(Array.isArray(fullVal)) np.set(fullVal); + else { + Loggers.warn('Unrecognized full array edit value', componentType, fullVal); + return true; + } + + if(replotLater) return false; + + supplyComponentDefaults(layout, fullLayout); + draw(gd); + return true; + } + + var componentNums = Object.keys(edits).map(Number).sort(sorterAsc); + var componentArrayIn = np.get(); + var componentArray = componentArrayIn || []; + // componentArrayFull is used just to keep splices in line between + // full and input arrays, so private keys can be copied over after + // redoing supplyDefaults + // TODO: this assumes componentArray is in gd.layout - which will not be + // true after we extend this to restyle + var componentArrayFull = _nestedProperty(fullLayout, componentType).get(); + + var deletes = []; + var firstIndexChange = -1; + var maxIndex = componentArray.length; + var i; + var j; + var componentNum; + var objEdits; + var objKeys; + var objVal; + var adding, prefix; + + // first make the add and edit changes + for(i = 0; i < componentNums.length; i++) { + componentNum = componentNums[i]; + objEdits = edits[componentNum]; + objKeys = Object.keys(objEdits); + objVal = objEdits[''], + adding = isAddVal(objVal); + + if(componentNum < 0 || componentNum > componentArray.length - (adding ? 0 : 1)) { + Loggers.warn('index out of range', componentType, componentNum); + continue; + } + + if(objVal !== undefined) { + if(objKeys.length > 1) { + Loggers.warn( + 'Insertion & removal are incompatible with edits to the same index.', + componentType, componentNum); + } + + if(isRemoveVal(objVal)) { + deletes.push(componentNum); + } else if(adding) { + if(objVal === 'add') objVal = {}; + componentArray.splice(componentNum, 0, objVal); + if(componentArrayFull) componentArrayFull.splice(componentNum, 0, {}); + } else { + Loggers.warn('Unrecognized full object edit value', + componentType, componentNum, objVal); + } + + if(firstIndexChange === -1) firstIndexChange = componentNum; + } else { + for(j = 0; j < objKeys.length; j++) { + prefix = componentType + '[' + componentNum + '].'; + _nestedProperty(componentArray[componentNum], objKeys[j], prefix) + .set(objEdits[objKeys[j]]); + } + } + } + + // now do deletes + for(i = deletes.length - 1; i >= 0; i--) { + componentArray.splice(deletes[i], 1); + // TODO: this drops private keys that had been stored in componentArrayFull + // does this have any ill effects? + if(componentArrayFull) componentArrayFull.splice(deletes[i], 1); + } + + if(!componentArray.length) np.set(null); + else if(!componentArrayIn) np.set(componentArray); + + if(replotLater) return false; + + supplyComponentDefaults(layout, fullLayout); + + // finally draw all the components we need to + // if we added or removed any, redraw all after it + if(drawOne !== noop) { + var indicesToDraw; + if(firstIndexChange === -1) { + // there's no re-indexing to do, so only redraw components that changed + indicesToDraw = componentNums; + } else { + // in case the component array was shortened, we still need do call + // drawOne on the latter items so they get properly removed + maxIndex = Math.max(componentArray.length, maxIndex); + indicesToDraw = []; + for(i = 0; i < componentNums.length; i++) { + componentNum = componentNums[i]; + if(componentNum >= firstIndexChange) break; + indicesToDraw.push(componentNum); + } + for(i = firstIndexChange; i < maxIndex; i++) { + indicesToDraw.push(i); + } + } + for(i = 0; i < indicesToDraw.length; i++) { + drawOne(gd, indicesToDraw[i]); + } + } else draw(gd); + + return true; +}; + +},{"../lib/is_plain_object":170,"../lib/loggers":173,"../lib/noop":178,"../lib/search":187,"../registry":258,"./container_array_match":195}],200:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); +var isNumeric = _dereq_('fast-isnumeric'); +var hasHover = _dereq_('has-hover'); + +var Lib = _dereq_('../lib'); +var nestedProperty = Lib.nestedProperty; + +var Events = _dereq_('../lib/events'); +var Queue = _dereq_('../lib/queue'); + +var Registry = _dereq_('../registry'); +var PlotSchema = _dereq_('./plot_schema'); +var Plots = _dereq_('../plots/plots'); +var Polar = _dereq_('../plots/polar/legacy'); + +var Axes = _dereq_('../plots/cartesian/axes'); +var Drawing = _dereq_('../components/drawing'); +var Color = _dereq_('../components/color'); +var initInteractions = _dereq_('../plots/cartesian/graph_interact').initInteractions; +var xmlnsNamespaces = _dereq_('../constants/xmlns_namespaces'); +var svgTextUtils = _dereq_('../lib/svg_text_utils'); +var clearSelect = _dereq_('../plots/cartesian/select').clearSelect; + +var dfltConfig = _dereq_('./plot_config').dfltConfig; +var manageArrays = _dereq_('./manage_arrays'); +var helpers = _dereq_('./helpers'); +var subroutines = _dereq_('./subroutines'); +var editTypes = _dereq_('./edit_types'); + +var AX_NAME_PATTERN = _dereq_('../plots/cartesian/constants').AX_NAME_PATTERN; + +var numericNameWarningCount = 0; +var numericNameWarningCountLimit = 5; + +/** + * Main plot-creation function + * + * @param {string id or DOM element} gd + * the id or DOM element of the graph container div + * @param {array of objects} data + * array of traces, containing the data and display information for each trace + * @param {object} layout + * object describing the overall display of the plot, + * all the stuff that doesn't pertain to any individual trace + * @param {object} config + * configuration options (see ./plot_config.js for more info) + * + * OR + * + * @param {string id or DOM element} gd + * the id or DOM element of the graph container div + * @param {object} figure + * object containing `data`, `layout`, `config`, and `frames` members + * + */ +function plot(gd, data, layout, config) { + var frames; + + gd = Lib.getGraphDiv(gd); + + // Events.init is idempotent and bails early if gd has already been init'd + Events.init(gd); + + if(Lib.isPlainObject(data)) { + var obj = data; + data = obj.data; + layout = obj.layout; + config = obj.config; + frames = obj.frames; + } + + var okToPlot = Events.triggerHandler(gd, 'plotly_beforeplot', [data, layout, config]); + if(okToPlot === false) return Promise.reject(); + + // if there's no data or layout, and this isn't yet a plotly plot + // container, log a warning to help plotly.js users debug + if(!data && !layout && !Lib.isPlotDiv(gd)) { + Lib.warn('Calling Plotly.plot as if redrawing ' + + 'but this container doesn\'t yet have a plot.', gd); + } + + function addFrames() { + if(frames) { + return exports.addFrames(gd, frames); + } + } + + // transfer configuration options to gd until we move over to + // a more OO like model + setPlotContext(gd, config); + + if(!layout) layout = {}; + + // hook class for plots main container (in case of plotly.js + // this won't be #embedded-graph or .js-tab-contents) + d3.select(gd).classed('js-plotly-plot', true); + + // off-screen getBoundingClientRect testing space, + // in #js-plotly-tester (and stored as Drawing.tester) + // so we can share cached text across tabs + Drawing.makeTester(); + + // collect promises for any async actions during plotting + // any part of the plotting code can push to gd._promises, then + // before we move to the next step, we check that they're all + // complete, and empty out the promise list again. + if(!Array.isArray(gd._promises)) gd._promises = []; + + var graphWasEmpty = ((gd.data || []).length === 0 && Array.isArray(data)); + + // if there is already data on the graph, append the new data + // if you only want to redraw, pass a non-array for data + if(Array.isArray(data)) { + helpers.cleanData(data); + + if(graphWasEmpty) gd.data = data; + else gd.data.push.apply(gd.data, data); + + // for routines outside graph_obj that want a clean tab + // (rather than appending to an existing one) gd.empty + // is used to determine whether to make a new tab + gd.empty = false; + } + + if(!gd.layout || graphWasEmpty) { + gd.layout = helpers.cleanLayout(layout); + } + + Plots.supplyDefaults(gd); + + var fullLayout = gd._fullLayout; + var hasCartesian = fullLayout._has('cartesian'); + + // Legacy polar plots + if(!fullLayout._has('polar') && data && data[0] && data[0].r) { + Lib.log('Legacy polar charts are deprecated!'); + return plotLegacyPolar(gd, data, layout); + } + + // so we don't try to re-call Plotly.plot from inside + // legend and colorbar, if margins changed + fullLayout._replotting = true; + + // make or remake the framework if we need to + if(graphWasEmpty) makePlotFramework(gd); + + // polar need a different framework + if(gd.framework !== makePlotFramework) { + gd.framework = makePlotFramework; + makePlotFramework(gd); + } + + // clear gradient defs on each .plot call, because we know we'll loop through all traces + Drawing.initGradients(gd); + + // save initial show spikes once per graph + if(graphWasEmpty) Axes.saveShowSpikeInitial(gd); + + // prepare the data and find the autorange + + // generate calcdata, if we need to + // to force redoing calcdata, just delete it before calling Plotly.plot + var recalc = !gd.calcdata || gd.calcdata.length !== (gd._fullData || []).length; + if(recalc) Plots.doCalcdata(gd); + + // in case it has changed, attach fullData traces to calcdata + for(var i = 0; i < gd.calcdata.length; i++) { + gd.calcdata[i][0].trace = gd._fullData[i]; + } + + // make the figure responsive + if(gd._context.responsive) { + if(!gd._responsiveChartHandler) { + // Keep a reference to the resize handler to purge it down the road + gd._responsiveChartHandler = function() { if(!Lib.isHidden(gd)) Plots.resize(gd); }; + + // Listen to window resize + window.addEventListener('resize', gd._responsiveChartHandler); + } + } else { + Lib.clearResponsive(gd); + } + + /* + * start async-friendly code - now we're actually drawing things + */ + + var oldMargins = Lib.extendFlat({}, fullLayout._size); + + // draw framework first so that margin-pushing + // components can position themselves correctly + var drawFrameworkCalls = 0; + function drawFramework() { + var basePlotModules = fullLayout._basePlotModules; + + for(var i = 0; i < basePlotModules.length; i++) { + if(basePlotModules[i].drawFramework) { + basePlotModules[i].drawFramework(gd); + } + } + + if(!fullLayout._glcanvas && fullLayout._has('gl')) { + fullLayout._glcanvas = fullLayout._glcontainer.selectAll('.gl-canvas').data([{ + key: 'contextLayer', + context: true, + pick: false + }, { + key: 'focusLayer', + context: false, + pick: false + }, { + key: 'pickLayer', + context: false, + pick: true + }], function(d) { return d.key; }); + + fullLayout._glcanvas.enter().append('canvas') + .attr('class', function(d) { + return 'gl-canvas gl-canvas-' + d.key.replace('Layer', ''); + }) + .style({ + position: 'absolute', + top: 0, + left: 0, + overflow: 'visible', + 'pointer-events': 'none' + }); + } + + if(fullLayout._glcanvas) { + fullLayout._glcanvas + .attr('width', fullLayout.width) + .attr('height', fullLayout.height); + + var regl = fullLayout._glcanvas.data()[0].regl; + if(regl) { + // Unfortunately, this can happen when relayouting to large + // width/height on some browsers. + if(Math.floor(fullLayout.width) !== regl._gl.drawingBufferWidth || + Math.floor(fullLayout.height) !== regl._gl.drawingBufferHeight + ) { + var msg = 'WebGL context buffer and canvas dimensions do not match due to browser/WebGL bug.'; + if(drawFrameworkCalls) { + Lib.error(msg); + } else { + Lib.log(msg + ' Clearing graph and plotting again.'); + Plots.cleanPlot([], {}, gd._fullData, fullLayout); + Plots.supplyDefaults(gd); + fullLayout = gd._fullLayout; + Plots.doCalcdata(gd); + drawFrameworkCalls++; + return drawFramework(); + } + } + } + } + + if(fullLayout.modebar.orientation === 'h') { + fullLayout._modebardiv + .style('height', null) + .style('width', '100%'); + } else { + fullLayout._modebardiv + .style('width', null) + .style('height', fullLayout.height + 'px'); + } + + return Plots.previousPromises(gd); + } + + // draw anything that can affect margins. + function marginPushers() { + // First reset the list of things that are allowed to change the margins + // So any deleted traces or components will be wiped out of the + // automargin calculation. + // This means *every* margin pusher must be listed here, even if it + // doesn't actually try to push the margins until later. + Plots.clearAutoMarginIds(gd); + + subroutines.drawMarginPushers(gd); + Axes.allowAutoMargin(gd); + + // TODO can this be moved elsewhere? + if(fullLayout._has('pie')) { + var fullData = gd._fullData; + for(var i = 0; i < fullData.length; i++) { + var trace = fullData[i]; + if(trace.type === 'pie' && trace.automargin) { + Plots.allowAutoMargin(gd, 'pie.' + trace.uid + '.automargin'); + } + } + } + + Plots.doAutoMargin(gd); + return Plots.previousPromises(gd); + } + + // in case the margins changed, draw margin pushers again + function marginPushersAgain() { + if(!Plots.didMarginChange(oldMargins, fullLayout._size)) return; + + return Lib.syncOrAsync([ + marginPushers, + subroutines.layoutStyles + ], gd); + } + + function positionAndAutorange() { + if(!recalc) { + doAutoRangeAndConstraints(); + return; + } + + // TODO: autosize extra for text markers and images + // see https://github.com/plotly/plotly.js/issues/1111 + return Lib.syncOrAsync([ + Registry.getComponentMethod('shapes', 'calcAutorange'), + Registry.getComponentMethod('annotations', 'calcAutorange'), + doAutoRangeAndConstraints + ], gd); + } + + function doAutoRangeAndConstraints() { + if(gd._transitioning) return; + + subroutines.doAutoRangeAndConstraints(gd); + + // store initial ranges *after* enforcing constraints, otherwise + // we will never look like we're at the initial ranges + if(graphWasEmpty) Axes.saveRangeInitial(gd); + + // this one is different from shapes/annotations calcAutorange + // the others incorporate those components into ax._extremes, + // this one actually sets the ranges in rangesliders. + Registry.getComponentMethod('rangeslider', 'calcAutorange')(gd); + } + + // draw ticks, titles, and calculate axis scaling (._b, ._m) + function drawAxes() { + return Axes.draw(gd, graphWasEmpty ? '' : 'redraw'); + } + + var seq = [ + Plots.previousPromises, + addFrames, + drawFramework, + marginPushers, + marginPushersAgain + ]; + + if(hasCartesian) seq.push(positionAndAutorange); + + seq.push(subroutines.layoutStyles); + if(hasCartesian) seq.push(drawAxes); + + seq.push( + subroutines.drawData, + subroutines.finalDraw, + initInteractions, + Plots.addLinks, + Plots.rehover, + Plots.redrag, + // TODO: doAutoMargin is only needed here for axis automargin, which + // happens outside of marginPushers where all the other automargins are + // calculated. Would be much better to separate margin calculations from + // component drawing - see https://github.com/plotly/plotly.js/issues/2704 + Plots.doAutoMargin, + Plots.previousPromises + ); + + // even if everything we did was synchronous, return a promise + // so that the caller doesn't care which route we took + var plotDone = Lib.syncOrAsync(seq, gd); + if(!plotDone || !plotDone.then) plotDone = Promise.resolve(); + + return plotDone.then(function() { + emitAfterPlot(gd); + return gd; + }); +} + +function emitAfterPlot(gd) { + var fullLayout = gd._fullLayout; + + if(fullLayout._redrawFromAutoMarginCount) { + fullLayout._redrawFromAutoMarginCount--; + } else { + gd.emit('plotly_afterplot'); + } +} + +function setPlotConfig(obj) { + return Lib.extendFlat(dfltConfig, obj); +} + +function setBackground(gd, bgColor) { + try { + gd._fullLayout._paper.style('background', bgColor); + } catch(e) { + Lib.error(e); + } +} + +function opaqueSetBackground(gd, bgColor) { + var blend = Color.combine(bgColor, 'white'); + setBackground(gd, blend); +} + +function setPlotContext(gd, config) { + if(!gd._context) { + gd._context = Lib.extendDeep({}, dfltConfig); + + // stash href, used to make robust clipPath URLs + var base = d3.select('base'); + gd._context._baseUrl = base.size() && base.attr('href') ? + window.location.href.split('#')[0] : + ''; + } + + var context = gd._context; + + var i, keys, key; + + if(config) { + keys = Object.keys(config); + for(i = 0; i < keys.length; i++) { + key = keys[i]; + if(key === 'editable' || key === 'edits') continue; + if(key in context) { + if(key === 'setBackground' && config[key] === 'opaque') { + context[key] = opaqueSetBackground; + } else { + context[key] = config[key]; + } + } + } + + // map plot3dPixelRatio to plotGlPixelRatio for backward compatibility + if(config.plot3dPixelRatio && !context.plotGlPixelRatio) { + context.plotGlPixelRatio = context.plot3dPixelRatio; + } + + // now deal with editable and edits - first editable overrides + // everything, then edits refines + var editable = config.editable; + if(editable !== undefined) { + // we're not going to *use* context.editable, we're only going to + // use context.edits... but keep it for the record + context.editable = editable; + + keys = Object.keys(context.edits); + for(i = 0; i < keys.length; i++) { + context.edits[keys[i]] = editable; + } + } + if(config.edits) { + keys = Object.keys(config.edits); + for(i = 0; i < keys.length; i++) { + key = keys[i]; + if(key in context.edits) { + context.edits[key] = config.edits[key]; + } + } + } + + // not part of the user-facing config options + context._exportedPlot = config._exportedPlot; + } + + // staticPlot forces a bunch of others: + if(context.staticPlot) { + context.editable = false; + context.edits = {}; + context.autosizable = false; + context.scrollZoom = false; + context.doubleClick = false; + context.showTips = false; + context.showLink = false; + context.displayModeBar = false; + } + + // make sure hover-only devices have mode bar visible + if(context.displayModeBar === 'hover' && !hasHover) { + context.displayModeBar = true; + } + + // default and fallback for setBackground + if(context.setBackground === 'transparent' || typeof context.setBackground !== 'function') { + context.setBackground = setBackground; + } + + // Check if gd has a specified widht/height to begin with + context._hasZeroHeight = context._hasZeroHeight || gd.clientHeight === 0; + context._hasZeroWidth = context._hasZeroWidth || gd.clientWidth === 0; + + // fill context._scrollZoom helper to help manage scrollZoom flaglist + var szIn = context.scrollZoom; + var szOut = context._scrollZoom = {}; + if(szIn === true) { + szOut.cartesian = 1; + szOut.gl3d = 1; + szOut.geo = 1; + szOut.mapbox = 1; + } else if(typeof szIn === 'string') { + var parts = szIn.split('+'); + for(i = 0; i < parts.length; i++) { + szOut[parts[i]] = 1; + } + } else if(szIn !== false) { + szOut.gl3d = 1; + szOut.geo = 1; + szOut.mapbox = 1; + } +} + +function plotLegacyPolar(gd, data, layout) { + // build or reuse the container skeleton + var plotContainer = d3.select(gd).selectAll('.plot-container') + .data([0]); + plotContainer.enter() + .insert('div', ':first-child') + .classed('plot-container plotly', true); + var paperDiv = plotContainer.selectAll('.svg-container') + .data([0]); + paperDiv.enter().append('div') + .classed('svg-container', true) + .style('position', 'relative'); + + // empty it everytime for now + paperDiv.html(''); + + // fulfill gd requirements + if(data) gd.data = data; + if(layout) gd.layout = layout; + Polar.manager.fillLayout(gd); + + // resize canvas + paperDiv.style({ + width: gd._fullLayout.width + 'px', + height: gd._fullLayout.height + 'px' + }); + + // instantiate framework + gd.framework = Polar.manager.framework(gd); + + // plot + gd.framework({data: gd.data, layout: gd.layout}, paperDiv.node()); + + // set undo point + gd.framework.setUndoPoint(); + + // get the resulting svg for extending it + var polarPlotSVG = gd.framework.svg(); + + // editable title + var opacity = 1; + var txt = gd._fullLayout.title ? gd._fullLayout.title.text : ''; + if(txt === '' || !txt) opacity = 0; + + var titleLayout = function() { + this.call(svgTextUtils.convertToTspans, gd); + // TODO: html/mathjax + // TODO: center title + }; + + var title = polarPlotSVG.select('.title-group text') + .call(titleLayout); + + if(gd._context.edits.titleText) { + var placeholderText = Lib._(gd, 'Click to enter Plot title'); + if(!txt || txt === placeholderText) { + opacity = 0.2; + // placeholder is not going through convertToTspans + // so needs explicit data-unformatted + title.attr({'data-unformatted': placeholderText}) + .text(placeholderText) + .style({opacity: opacity}) + .on('mouseover.opacity', function() { + d3.select(this).transition().duration(100) + .style('opacity', 1); + }) + .on('mouseout.opacity', function() { + d3.select(this).transition().duration(1000) + .style('opacity', 0); + }); + } + + var setContenteditable = function() { + this.call(svgTextUtils.makeEditable, {gd: gd}) + .on('edit', function(text) { + gd.framework({layout: {title: {text: text}}}); + this.text(text) + .call(titleLayout); + this.call(setContenteditable); + }) + .on('cancel', function() { + var txt = this.attr('data-unformatted'); + this.text(txt).call(titleLayout); + }); + }; + title.call(setContenteditable); + } + + gd._context.setBackground(gd, gd._fullLayout.paper_bgcolor); + Plots.addLinks(gd); + + return Promise.resolve(); +} + +// convenience function to force a full redraw, mostly for use by plotly.js +function redraw(gd) { + gd = Lib.getGraphDiv(gd); + + if(!Lib.isPlotDiv(gd)) { + throw new Error('This element is not a Plotly plot: ' + gd); + } + + helpers.cleanData(gd.data); + helpers.cleanLayout(gd.layout); + + gd.calcdata = undefined; + return exports.plot(gd).then(function() { + gd.emit('plotly_redraw'); + return gd; + }); +} + +/** + * Convenience function to make idempotent plot option obvious to users. + * + * @param gd + * @param {Object[]} data + * @param {Object} layout + * @param {Object} config + */ +function newPlot(gd, data, layout, config) { + gd = Lib.getGraphDiv(gd); + + // remove gl contexts + Plots.cleanPlot([], {}, gd._fullData || [], gd._fullLayout || {}); + + Plots.purge(gd); + return exports.plot(gd, data, layout, config); +} + +/** + * Wrap negative indicies to their positive counterparts. + * + * @param {Number[]} indices An array of indices + * @param {Number} maxIndex The maximum index allowable (arr.length - 1) + */ +function positivifyIndices(indices, maxIndex) { + var parentLength = maxIndex + 1; + var positiveIndices = []; + var i; + var index; + + for(i = 0; i < indices.length; i++) { + index = indices[i]; + if(index < 0) { + positiveIndices.push(parentLength + index); + } else { + positiveIndices.push(index); + } + } + return positiveIndices; +} + +/** + * Ensures that an index array for manipulating gd.data is valid. + * + * Intended for use with addTraces, deleteTraces, and moveTraces. + * + * @param gd + * @param indices + * @param arrayName + */ +function assertIndexArray(gd, indices, arrayName) { + var i, + index; + + for(i = 0; i < indices.length; i++) { + index = indices[i]; + + // validate that indices are indeed integers + if(index !== parseInt(index, 10)) { + throw new Error('all values in ' + arrayName + ' must be integers'); + } + + // check that all indices are in bounds for given gd.data array length + if(index >= gd.data.length || index < -gd.data.length) { + throw new Error(arrayName + ' must be valid indices for gd.data.'); + } + + // check that indices aren't repeated + if(indices.indexOf(index, i + 1) > -1 || + index >= 0 && indices.indexOf(-gd.data.length + index) > -1 || + index < 0 && indices.indexOf(gd.data.length + index) > -1) { + throw new Error('each index in ' + arrayName + ' must be unique.'); + } + } +} + +/** + * Private function used by Plotly.moveTraces to check input args + * + * @param gd + * @param currentIndices + * @param newIndices + */ +function checkMoveTracesArgs(gd, currentIndices, newIndices) { + // check that gd has attribute 'data' and 'data' is array + if(!Array.isArray(gd.data)) { + throw new Error('gd.data must be an array.'); + } + + // validate currentIndices array + if(typeof currentIndices === 'undefined') { + throw new Error('currentIndices is a required argument.'); + } else if(!Array.isArray(currentIndices)) { + currentIndices = [currentIndices]; + } + assertIndexArray(gd, currentIndices, 'currentIndices'); + + // validate newIndices array if it exists + if(typeof newIndices !== 'undefined' && !Array.isArray(newIndices)) { + newIndices = [newIndices]; + } + if(typeof newIndices !== 'undefined') { + assertIndexArray(gd, newIndices, 'newIndices'); + } + + // check currentIndices and newIndices are the same length if newIdices exists + if(typeof newIndices !== 'undefined' && currentIndices.length !== newIndices.length) { + throw new Error('current and new indices must be of equal length.'); + } +} +/** + * A private function to reduce the type checking clutter in addTraces. + * + * @param gd + * @param traces + * @param newIndices + */ +function checkAddTracesArgs(gd, traces, newIndices) { + var i, value; + + // check that gd has attribute 'data' and 'data' is array + if(!Array.isArray(gd.data)) { + throw new Error('gd.data must be an array.'); + } + + // make sure traces exists + if(typeof traces === 'undefined') { + throw new Error('traces must be defined.'); + } + + // make sure traces is an array + if(!Array.isArray(traces)) { + traces = [traces]; + } + + // make sure each value in traces is an object + for(i = 0; i < traces.length; i++) { + value = traces[i]; + if(typeof value !== 'object' || (Array.isArray(value) || value === null)) { + throw new Error('all values in traces array must be non-array objects'); + } + } + + // make sure we have an index for each trace + if(typeof newIndices !== 'undefined' && !Array.isArray(newIndices)) { + newIndices = [newIndices]; + } + if(typeof newIndices !== 'undefined' && newIndices.length !== traces.length) { + throw new Error( + 'if indices is specified, traces.length must equal indices.length' + ); + } +} + +/** + * A private function to reduce the type checking clutter in spliceTraces. + * Get all update Properties from gd.data. Validate inputs and outputs. + * Used by prependTrace and extendTraces + * + * @param gd + * @param update + * @param indices + * @param maxPoints + */ +function assertExtendTracesArgs(gd, update, indices, maxPoints) { + var maxPointsIsObject = Lib.isPlainObject(maxPoints); + + if(!Array.isArray(gd.data)) { + throw new Error('gd.data must be an array'); + } + if(!Lib.isPlainObject(update)) { + throw new Error('update must be a key:value object'); + } + + if(typeof indices === 'undefined') { + throw new Error('indices must be an integer or array of integers'); + } + + assertIndexArray(gd, indices, 'indices'); + + for(var key in update) { + /* + * Verify that the attribute to be updated contains as many trace updates + * as indices. Failure must result in throw and no-op + */ + if(!Array.isArray(update[key]) || update[key].length !== indices.length) { + throw new Error('attribute ' + key + ' must be an array of length equal to indices array length'); + } + + /* + * if maxPoints is an object it must match keys and array lengths of 'update' 1:1 + */ + if(maxPointsIsObject && + (!(key in maxPoints) || !Array.isArray(maxPoints[key]) || + maxPoints[key].length !== update[key].length)) { + throw new Error('when maxPoints is set as a key:value object it must contain a 1:1 ' + + 'corrispondence with the keys and number of traces in the update object'); + } + } +} + +/** + * A private function to reduce the type checking clutter in spliceTraces. + * + * @param {Object|HTMLDivElement} gd + * @param {Object} update + * @param {Number[]} indices + * @param {Number||Object} maxPoints + * @return {Object[]} + */ +function getExtendProperties(gd, update, indices, maxPoints) { + var maxPointsIsObject = Lib.isPlainObject(maxPoints); + var updateProps = []; + var trace, target, prop, insert, maxp; + + // allow scalar index to represent a single trace position + if(!Array.isArray(indices)) indices = [indices]; + + // negative indices are wrapped around to their positive value. Equivalent to python indexing. + indices = positivifyIndices(indices, gd.data.length - 1); + + // loop through all update keys and traces and harvest validated data. + for(var key in update) { + for(var j = 0; j < indices.length; j++) { + /* + * Choose the trace indexed by the indices map argument and get the prop setter-getter + * instance that references the key and value for this particular trace. + */ + trace = gd.data[indices[j]]; + prop = nestedProperty(trace, key); + + /* + * Target is the existing gd.data.trace.dataArray value like "x" or "marker.size" + * Target must exist as an Array to allow the extend operation to be performed. + */ + target = prop.get(); + insert = update[key][j]; + + if(!Lib.isArrayOrTypedArray(insert)) { + throw new Error('attribute: ' + key + ' index: ' + j + ' must be an array'); + } + if(!Lib.isArrayOrTypedArray(target)) { + throw new Error('cannot extend missing or non-array attribute: ' + key); + } + if(target.constructor !== insert.constructor) { + throw new Error('cannot extend array with an array of a different type: ' + key); + } + + /* + * maxPoints may be an object map or a scalar. If object select the key:value, else + * Use the scalar maxPoints for all key and trace combinations. + */ + maxp = maxPointsIsObject ? maxPoints[key][j] : maxPoints; + + // could have chosen null here, -1 just tells us to not take a window + if(!isNumeric(maxp)) maxp = -1; + + /* + * Wrap the nestedProperty in an object containing required data + * for lengthening and windowing this particular trace - key combination. + * Flooring maxp mirrors the behaviour of floats in the Array.slice JSnative function. + */ + updateProps.push({ + prop: prop, + target: target, + insert: insert, + maxp: Math.floor(maxp) + }); + } + } + + // all target and insertion data now validated + return updateProps; +} + +/** + * A private function to key Extend and Prepend traces DRY + * + * @param {Object|HTMLDivElement} gd + * @param {Object} update + * @param {Number[]} indices + * @param {Number||Object} maxPoints + * @param {Function} updateArray + * @return {Object} + */ +function spliceTraces(gd, update, indices, maxPoints, updateArray) { + assertExtendTracesArgs(gd, update, indices, maxPoints); + + var updateProps = getExtendProperties(gd, update, indices, maxPoints); + var undoUpdate = {}; + var undoPoints = {}; + + for(var i = 0; i < updateProps.length; i++) { + var prop = updateProps[i].prop; + var maxp = updateProps[i].maxp; + + // return new array and remainder + var out = updateArray(updateProps[i].target, updateProps[i].insert, maxp); + prop.set(out[0]); + + // build the inverse update object for the undo operation + if(!Array.isArray(undoUpdate[prop.astr])) undoUpdate[prop.astr] = []; + undoUpdate[prop.astr].push(out[1]); + + // build the matching maxPoints undo object containing original trace lengths + if(!Array.isArray(undoPoints[prop.astr])) undoPoints[prop.astr] = []; + undoPoints[prop.astr].push(updateProps[i].target.length); + } + + return {update: undoUpdate, maxPoints: undoPoints}; +} + +function concatTypedArray(arr0, arr1) { + var arr2 = new arr0.constructor(arr0.length + arr1.length); + arr2.set(arr0); + arr2.set(arr1, arr0.length); + return arr2; +} + +/** + * extend && prepend traces at indices with update arrays, window trace lengths to maxPoints + * + * Extend and Prepend have identical APIs. Prepend inserts an array at the head while Extend + * inserts an array off the tail. Prepend truncates the tail of the array - counting maxPoints + * from the head, whereas Extend truncates the head of the array, counting backward maxPoints + * from the tail. + * + * If maxPoints is undefined, nonNumeric, negative or greater than extended trace length no + * truncation / windowing will be performed. If its zero, well the whole trace is truncated. + * + * @param {Object|HTMLDivElement} gd The graph div + * @param {Object} update The key:array map of target attributes to extend + * @param {Number|Number[]} indices The locations of traces to be extended + * @param {Number|Object} [maxPoints] Number of points for trace window after lengthening. + * + */ +function extendTraces(gd, update, indices, maxPoints) { + gd = Lib.getGraphDiv(gd); + + function updateArray(target, insert, maxp) { + var newArray, remainder; + + if(Lib.isTypedArray(target)) { + if(maxp < 0) { + var none = new target.constructor(0); + var both = concatTypedArray(target, insert); + + if(maxp < 0) { + newArray = both; + remainder = none; + } else { + newArray = none; + remainder = both; + } + } else { + newArray = new target.constructor(maxp); + remainder = new target.constructor(target.length + insert.length - maxp); + + if(maxp === insert.length) { + newArray.set(insert); + remainder.set(target); + } else if(maxp < insert.length) { + var numberOfItemsFromInsert = insert.length - maxp; + + newArray.set(insert.subarray(numberOfItemsFromInsert)); + remainder.set(target); + remainder.set(insert.subarray(0, numberOfItemsFromInsert), target.length); + } else { + var numberOfItemsFromTarget = maxp - insert.length; + var targetBegin = target.length - numberOfItemsFromTarget; + + newArray.set(target.subarray(targetBegin)); + newArray.set(insert, numberOfItemsFromTarget); + remainder.set(target.subarray(0, targetBegin)); + } + } + } else { + newArray = target.concat(insert); + remainder = (maxp >= 0 && maxp < newArray.length) ? + newArray.splice(0, newArray.length - maxp) : + []; + } + + return [newArray, remainder]; + } + + var undo = spliceTraces(gd, update, indices, maxPoints, updateArray); + var promise = exports.redraw(gd); + var undoArgs = [gd, undo.update, indices, undo.maxPoints]; + Queue.add(gd, exports.prependTraces, undoArgs, extendTraces, arguments); + + return promise; +} + +function prependTraces(gd, update, indices, maxPoints) { + gd = Lib.getGraphDiv(gd); + + function updateArray(target, insert, maxp) { + var newArray, remainder; + + if(Lib.isTypedArray(target)) { + if(maxp <= 0) { + var none = new target.constructor(0); + var both = concatTypedArray(insert, target); + + if(maxp < 0) { + newArray = both; + remainder = none; + } else { + newArray = none; + remainder = both; + } + } else { + newArray = new target.constructor(maxp); + remainder = new target.constructor(target.length + insert.length - maxp); + + if(maxp === insert.length) { + newArray.set(insert); + remainder.set(target); + } else if(maxp < insert.length) { + var numberOfItemsFromInsert = insert.length - maxp; + + newArray.set(insert.subarray(0, numberOfItemsFromInsert)); + remainder.set(insert.subarray(numberOfItemsFromInsert)); + remainder.set(target, numberOfItemsFromInsert); + } else { + var numberOfItemsFromTarget = maxp - insert.length; + + newArray.set(insert); + newArray.set(target.subarray(0, numberOfItemsFromTarget), insert.length); + remainder.set(target.subarray(numberOfItemsFromTarget)); + } + } + } else { + newArray = insert.concat(target); + remainder = (maxp >= 0 && maxp < newArray.length) ? + newArray.splice(maxp, newArray.length) : + []; + } + + return [newArray, remainder]; + } + + var undo = spliceTraces(gd, update, indices, maxPoints, updateArray); + var promise = exports.redraw(gd); + var undoArgs = [gd, undo.update, indices, undo.maxPoints]; + Queue.add(gd, exports.extendTraces, undoArgs, prependTraces, arguments); + + return promise; +} + +/** + * Add data traces to an existing graph div. + * + * @param {Object|HTMLDivElement} gd The graph div + * @param {Object[]} gd.data The array of traces we're adding to + * @param {Object[]|Object} traces The object or array of objects to add + * @param {Number[]|Number} [newIndices=[gd.data.length]] Locations to add traces + * + */ +function addTraces(gd, traces, newIndices) { + gd = Lib.getGraphDiv(gd); + + var currentIndices = []; + var undoFunc = exports.deleteTraces; + var redoFunc = addTraces; + var undoArgs = [gd, currentIndices]; + var redoArgs = [gd, traces]; // no newIndices here + var i; + var promise; + + // all validation is done elsewhere to remove clutter here + checkAddTracesArgs(gd, traces, newIndices); + + // make sure traces is an array + if(!Array.isArray(traces)) { + traces = [traces]; + } + + // make sure traces do not repeat existing ones + traces = traces.map(function(trace) { + return Lib.extendFlat({}, trace); + }); + + helpers.cleanData(traces); + + // add the traces to gd.data (no redrawing yet!) + for(i = 0; i < traces.length; i++) { + gd.data.push(traces[i]); + } + + // to continue, we need to call moveTraces which requires currentIndices + for(i = 0; i < traces.length; i++) { + currentIndices.push(-traces.length + i); + } + + // if the user didn't define newIndices, they just want the traces appended + // i.e., we can simply redraw and be done + if(typeof newIndices === 'undefined') { + promise = exports.redraw(gd); + Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); + return promise; + } + + // make sure indices is property defined + if(!Array.isArray(newIndices)) { + newIndices = [newIndices]; + } + + try { + // this is redundant, but necessary to not catch later possible errors! + checkMoveTracesArgs(gd, currentIndices, newIndices); + } catch(error) { + // something went wrong, reset gd to be safe and rethrow error + gd.data.splice(gd.data.length - traces.length, traces.length); + throw error; + } + + // if we're here, the user has defined specific places to place the new traces + // this requires some extra work that moveTraces will do + Queue.startSequence(gd); + Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); + promise = exports.moveTraces(gd, currentIndices, newIndices); + Queue.stopSequence(gd); + return promise; +} + +/** + * Delete traces at `indices` from gd.data array. + * + * @param {Object|HTMLDivElement} gd The graph div + * @param {Object[]} gd.data The array of traces we're removing from + * @param {Number|Number[]} indices The indices + */ +function deleteTraces(gd, indices) { + gd = Lib.getGraphDiv(gd); + + var traces = []; + var undoFunc = exports.addTraces; + var redoFunc = deleteTraces; + var undoArgs = [gd, traces, indices]; + var redoArgs = [gd, indices]; + var i; + var deletedTrace; + + // make sure indices are defined + if(typeof indices === 'undefined') { + throw new Error('indices must be an integer or array of integers.'); + } else if(!Array.isArray(indices)) { + indices = [indices]; + } + assertIndexArray(gd, indices, 'indices'); + + // convert negative indices to positive indices + indices = positivifyIndices(indices, gd.data.length - 1); + + // we want descending here so that splicing later doesn't affect indexing + indices.sort(Lib.sorterDes); + for(i = 0; i < indices.length; i += 1) { + deletedTrace = gd.data.splice(indices[i], 1)[0]; + traces.push(deletedTrace); + } + + var promise = exports.redraw(gd); + Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); + + return promise; +} + +/** + * Move traces at currentIndices array to locations in newIndices array. + * + * If newIndices is omitted, currentIndices will be moved to the end. E.g., + * these are equivalent: + * + * Plotly.moveTraces(gd, [1, 2, 3], [-3, -2, -1]) + * Plotly.moveTraces(gd, [1, 2, 3]) + * + * @param {Object|HTMLDivElement} gd The graph div + * @param {Object[]} gd.data The array of traces we're removing from + * @param {Number|Number[]} currentIndices The locations of traces to be moved + * @param {Number|Number[]} [newIndices] The locations to move traces to + * + * Example calls: + * + * // move trace i to location x + * Plotly.moveTraces(gd, i, x) + * + * // move trace i to end of array + * Plotly.moveTraces(gd, i) + * + * // move traces i, j, k to end of array (i != j != k) + * Plotly.moveTraces(gd, [i, j, k]) + * + * // move traces [i, j, k] to [x, y, z] (i != j != k) (x != y != z) + * Plotly.moveTraces(gd, [i, j, k], [x, y, z]) + * + * // reorder all traces (assume there are 5--a, b, c, d, e) + * Plotly.moveTraces(gd, [b, d, e, a, c]) // same as 'move to end' + */ +function moveTraces(gd, currentIndices, newIndices) { + gd = Lib.getGraphDiv(gd); + + var newData = []; + var movingTraceMap = []; + var undoFunc = moveTraces; + var redoFunc = moveTraces; + var undoArgs = [gd, newIndices, currentIndices]; + var redoArgs = [gd, currentIndices, newIndices]; + var i; + + // to reduce complexity here, check args elsewhere + // this throws errors where appropriate + checkMoveTracesArgs(gd, currentIndices, newIndices); + + // make sure currentIndices is an array + currentIndices = Array.isArray(currentIndices) ? currentIndices : [currentIndices]; + + // if undefined, define newIndices to point to the end of gd.data array + if(typeof newIndices === 'undefined') { + newIndices = []; + for(i = 0; i < currentIndices.length; i++) { + newIndices.push(-currentIndices.length + i); + } + } + + // make sure newIndices is an array if it's user-defined + newIndices = Array.isArray(newIndices) ? newIndices : [newIndices]; + + // convert negative indices to positive indices (they're the same length) + currentIndices = positivifyIndices(currentIndices, gd.data.length - 1); + newIndices = positivifyIndices(newIndices, gd.data.length - 1); + + // at this point, we've coerced the index arrays into predictable forms + + // get the traces that aren't being moved around + for(i = 0; i < gd.data.length; i++) { + // if index isn't in currentIndices, include it in ignored! + if(currentIndices.indexOf(i) === -1) { + newData.push(gd.data[i]); + } + } + + // get a mapping of indices to moving traces + for(i = 0; i < currentIndices.length; i++) { + movingTraceMap.push({newIndex: newIndices[i], trace: gd.data[currentIndices[i]]}); + } + + // reorder this mapping by newIndex, ascending + movingTraceMap.sort(function(a, b) { + return a.newIndex - b.newIndex; + }); + + // now, add the moving traces back in, in order! + for(i = 0; i < movingTraceMap.length; i += 1) { + newData.splice(movingTraceMap[i].newIndex, 0, movingTraceMap[i].trace); + } + + gd.data = newData; + + var promise = exports.redraw(gd); + Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); + + return promise; +} + +/** + * restyle: update trace attributes of an existing plot + * + * Can be called two ways. + * + * Signature 1: + * @param {String | HTMLDivElement} gd + * the id or DOM element of the graph container div + * @param {String} astr + * attribute string (like `'marker.symbol'`) to update + * @param {*} val + * value to give this attribute + * @param {Number[] | Number} [traces] + * integer or array of integers for the traces to alter (all if omitted) + * + * Signature 2: + * @param {String | HTMLDivElement} gd + * (as in signature 1) + * @param {Object} aobj + * attribute object `{astr1: val1, astr2: val2 ...}` + * allows setting multiple attributes simultaneously + * @param {Number[] | Number} [traces] + * (as in signature 1) + * + * `val` (or `val1`, `val2` ... in the object form) can be an array, + * to apply different values to each trace. + * + * If the array is too short, it will wrap around (useful for + * style files that want to specify cyclical default values). + */ +function restyle(gd, astr, val, _traces) { + gd = Lib.getGraphDiv(gd); + helpers.clearPromiseQueue(gd); + + var aobj = {}; + if(typeof astr === 'string') aobj[astr] = val; + else if(Lib.isPlainObject(astr)) { + // the 3-arg form + aobj = Lib.extendFlat({}, astr); + if(_traces === undefined) _traces = val; + } else { + Lib.warn('Restyle fail.', astr, val, _traces); + return Promise.reject(); + } + + if(Object.keys(aobj).length) gd.changed = true; + + var traces = helpers.coerceTraceIndices(gd, _traces); + + var specs = _restyle(gd, aobj, traces); + var flags = specs.flags; + + // clear calcdata and/or axis types if required so they get regenerated + if(flags.calc) gd.calcdata = undefined; + if(flags.clearAxisTypes) helpers.clearAxisTypes(gd, traces, {}); + + // fill in redraw sequence + var seq = []; + + if(flags.fullReplot) { + seq.push(exports.plot); + } else { + seq.push(Plots.previousPromises); + + // maybe only call Plots.supplyDataDefaults in the splom case, + // to skip over long and slow axes defaults + Plots.supplyDefaults(gd); + + if(flags.markerSize) { + Plots.doCalcdata(gd); + addAxRangeSequence(seq); + + // TODO + // if all axes have autorange:false, then + // proceed to subroutines.doTraceStyle(), + // otherwise we must go through addAxRangeSequence, + // which in general must redraws 'all' axes + } + + if(flags.style) seq.push(subroutines.doTraceStyle); + if(flags.colorbars) seq.push(subroutines.doColorBars); + + seq.push(emitAfterPlot); + } + + seq.push(Plots.rehover, Plots.redrag); + + Queue.add(gd, + restyle, [gd, specs.undoit, specs.traces], + restyle, [gd, specs.redoit, specs.traces] + ); + + var plotDone = Lib.syncOrAsync(seq, gd); + if(!plotDone || !plotDone.then) plotDone = Promise.resolve(); + + return plotDone.then(function() { + gd.emit('plotly_restyle', specs.eventData); + return gd; + }); +} + +// for undo: undefined initial vals must be turned into nulls +// so that we unset rather than ignore them +function undefinedToNull(val) { + if(val === undefined) return null; + return val; +} + +/** + * Factory function to wrap nestedProperty with GUI edits if necessary + * with GUI edits we add an optional prefix to the nestedProperty constructor + * to prepend to the attribute string in the preGUI store. + */ +function makeNP(preGUI, guiEditFlag) { + if(!guiEditFlag) return nestedProperty; + + return function(container, attr, prefix) { + var np = nestedProperty(container, attr); + var npSet = np.set; + np.set = function(val) { + var fullAttr = (prefix || '') + attr; + storeCurrent(fullAttr, np.get(), val, preGUI); + npSet(val); + }; + return np; + }; +} + +function storeCurrent(attr, val, newVal, preGUI) { + if(Array.isArray(val) || Array.isArray(newVal)) { + var arrayVal = Array.isArray(val) ? val : []; + var arrayNew = Array.isArray(newVal) ? newVal : []; + var maxLen = Math.max(arrayVal.length, arrayNew.length); + for(var i = 0; i < maxLen; i++) { + storeCurrent(attr + '[' + i + ']', arrayVal[i], arrayNew[i], preGUI); + } + } else if(Lib.isPlainObject(val) || Lib.isPlainObject(newVal)) { + var objVal = Lib.isPlainObject(val) ? val : {}; + var objNew = Lib.isPlainObject(newVal) ? newVal : {}; + var objBoth = Lib.extendFlat({}, objVal, objNew); + for(var key in objBoth) { + storeCurrent(attr + '.' + key, objVal[key], objNew[key], preGUI); + } + } else if(preGUI[attr] === undefined) { + preGUI[attr] = undefinedToNull(val); + } +} + +/** + * storeDirectGUIEdit: for routines that skip restyle/relayout and mock it + * by emitting a plotly_restyle or plotly_relayout event, this routine + * keeps track of the initial state in _preGUI for use by uirevision + * Does *not* apply these changes to data/layout - that's the responsibility + * of the calling routine. + * + * @param {object} container: the input attributes container (eg `layout` or a `trace`) + * @param {object} preGUI: where original values should be stored, either + * `layout._preGUI` or `layout._tracePreGUI[uid]` + * @param {object} edits: the {attr: val} object as normally passed to `relayout` etc + */ +function _storeDirectGUIEdit(container, preGUI, edits) { + for(var attr in edits) { + var np = nestedProperty(container, attr); + storeCurrent(attr, np.get(), edits[attr], preGUI); + } +} + +function _restyle(gd, aobj, traces) { + var fullLayout = gd._fullLayout; + var fullData = gd._fullData; + var data = gd.data; + var guiEditFlag = fullLayout._guiEditing; + var layoutNP = makeNP(fullLayout._preGUI, guiEditFlag); + var eventData = Lib.extendDeepAll({}, aobj); + var i; + + cleanDeprecatedAttributeKeys(aobj); + + // initialize flags + var flags = editTypes.traceFlags(); + + // copies of the change (and previous values of anything affected) + // for the undo / redo queue + var redoit = {}; + var undoit = {}; + var axlist; + + // make a new empty vals array for undoit + function a0() { return traces.map(function() { return undefined; }); } + + // for autoranging multiple axes + function addToAxlist(axid) { + var axName = Axes.id2name(axid); + if(axlist.indexOf(axName) === -1) axlist.push(axName); + } + + function autorangeAttr(axName) { return 'LAYOUT' + axName + '.autorange'; } + + function rangeAttr(axName) { return 'LAYOUT' + axName + '.range'; } + + function getFullTrace(traceIndex) { + // usually fullData maps 1:1 onto data, but with groupby transforms + // the fullData index can be greater. Take the *first* matching trace. + for(var j = traceIndex; j < fullData.length; j++) { + if(fullData[j]._input === data[traceIndex]) return fullData[j]; + } + // should never get here - and if we *do* it should cause an error + // later on undefined fullTrace is passed to nestedProperty. + } + + // for attrs that interact (like scales & autoscales), save the + // old vals before making the change + // val=undefined will not set a value, just record what the value was. + // val=null will delete the attribute + // attr can be an array to set several at once (all to the same val) + function doextra(attr, val, i) { + if(Array.isArray(attr)) { + attr.forEach(function(a) { doextra(a, val, i); }); + return; + } + // quit if explicitly setting this elsewhere + if(attr in aobj || helpers.hasParent(aobj, attr)) return; + + var extraparam; + if(attr.substr(0, 6) === 'LAYOUT') { + extraparam = layoutNP(gd.layout, attr.replace('LAYOUT', '')); + } else { + var tracei = traces[i]; + var preGUI = fullLayout._tracePreGUI[getFullTrace(tracei)._fullInput.uid]; + extraparam = makeNP(preGUI, guiEditFlag)(data[tracei], attr); + } + + if(!(attr in undoit)) { + undoit[attr] = a0(); + } + if(undoit[attr][i] === undefined) { + undoit[attr][i] = undefinedToNull(extraparam.get()); + } + if(val !== undefined) { + extraparam.set(val); + } + } + + function allBins(binAttr) { + return function(j) { + return fullData[j][binAttr]; + }; + } + + function arrayBins(binAttr) { + return function(vij, j) { + return vij === false ? fullData[traces[j]][binAttr] : null; + }; + } + + // now make the changes to gd.data (and occasionally gd.layout) + // and figure out what kind of graphics update we need to do + for(var ai in aobj) { + if(helpers.hasParent(aobj, ai)) { + throw new Error('cannot set ' + ai + ' and a parent attribute simultaneously'); + } + + var vi = aobj[ai]; + var cont; + var contFull; + var param; + var oldVal; + var newVal; + var valObject; + + // Backward compatibility shim for turning histogram autobin on, + // or freezing previous autobinned values. + // Replace obsolete `autobin(x|y): true` with `(x|y)bins: null` + // and `autobin(x|y): false` with the `(x|y)bins` in `fullData` + if(ai === 'autobinx' || ai === 'autobiny') { + ai = ai.charAt(ai.length - 1) + 'bins'; + if(Array.isArray(vi)) vi = vi.map(arrayBins(ai)); + else if(vi === false) vi = traces.map(allBins(ai)); + else vi = null; + } + + redoit[ai] = vi; + + if(ai.substr(0, 6) === 'LAYOUT') { + param = layoutNP(gd.layout, ai.replace('LAYOUT', '')); + undoit[ai] = [undefinedToNull(param.get())]; + // since we're allowing val to be an array, allow it here too, + // even though that's meaningless + param.set(Array.isArray(vi) ? vi[0] : vi); + // ironically, the layout attrs in restyle only require replot, + // not relayout + flags.calc = true; + continue; + } + + // set attribute in gd.data + undoit[ai] = a0(); + for(i = 0; i < traces.length; i++) { + cont = data[traces[i]]; + contFull = getFullTrace(traces[i]); + var preGUI = fullLayout._tracePreGUI[contFull._fullInput.uid]; + param = makeNP(preGUI, guiEditFlag)(cont, ai); + oldVal = param.get(); + newVal = Array.isArray(vi) ? vi[i % vi.length] : vi; + + if(newVal === undefined) continue; + + var finalPart = param.parts[param.parts.length - 1]; + var prefix = ai.substr(0, ai.length - finalPart.length - 1); + var prefixDot = prefix ? prefix + '.' : ''; + var innerContFull = prefix ? + nestedProperty(contFull, prefix).get() : contFull; + + valObject = PlotSchema.getTraceValObject(contFull, param.parts); + + if(valObject && valObject.impliedEdits && newVal !== null) { + for(var impliedKey in valObject.impliedEdits) { + doextra(Lib.relativeAttr(ai, impliedKey), valObject.impliedEdits[impliedKey], i); + } + } else if((finalPart === 'thicknessmode' || finalPart === 'lenmode') && + oldVal !== newVal && + (newVal === 'fraction' || newVal === 'pixels') && + innerContFull + ) { + // changing colorbar size modes, + // make the resulting size not change + // note that colorbar fractional sizing is based on the + // original plot size, before anything (like a colorbar) + // increases the margins + + var gs = fullLayout._size; + var orient = innerContFull.orient; + var topOrBottom = (orient === 'top') || (orient === 'bottom'); + if(finalPart === 'thicknessmode') { + var thicknorm = topOrBottom ? gs.h : gs.w; + doextra(prefixDot + 'thickness', innerContFull.thickness * + (newVal === 'fraction' ? 1 / thicknorm : thicknorm), i); + } else { + var lennorm = topOrBottom ? gs.w : gs.h; + doextra(prefixDot + 'len', innerContFull.len * + (newVal === 'fraction' ? 1 / lennorm : lennorm), i); + } + } else if(ai === 'type' && ( + (newVal === 'pie') !== (oldVal === 'pie') || + (newVal === 'funnelarea') !== (oldVal === 'funnelarea') + )) { + var labelsTo = 'x'; + var valuesTo = 'y'; + if((newVal === 'bar' || oldVal === 'bar') && cont.orientation === 'h') { + labelsTo = 'y'; + valuesTo = 'x'; + } + Lib.swapAttrs(cont, ['?', '?src'], 'labels', labelsTo); + Lib.swapAttrs(cont, ['d?', '?0'], 'label', labelsTo); + Lib.swapAttrs(cont, ['?', '?src'], 'values', valuesTo); + + if(oldVal === 'pie' || oldVal === 'funnelarea') { + nestedProperty(cont, 'marker.color') + .set(nestedProperty(cont, 'marker.colors').get()); + + // super kludgy - but if all pies are gone we won't remove them otherwise + fullLayout._pielayer.selectAll('g.trace').remove(); + } else if(Registry.traceIs(cont, 'cartesian')) { + nestedProperty(cont, 'marker.colors') + .set(nestedProperty(cont, 'marker.color').get()); + } + } + + undoit[ai][i] = undefinedToNull(oldVal); + // set the new value - if val is an array, it's one el per trace + // first check for attributes that get more complex alterations + var swapAttrs = [ + 'swapxy', 'swapxyaxes', 'orientation', 'orientationaxes' + ]; + if(swapAttrs.indexOf(ai) !== -1) { + // setting an orientation: make sure it's changing + // before we swap everything else + if(ai === 'orientation') { + param.set(newVal); + // obnoxious that we need this level of coupling... but in order to + // properly handle setting orientation to `null` we need to mimic + // the logic inside Bars.supplyDefaults for default orientation + var defaultOrientation = (cont.x && !cont.y) ? 'h' : 'v'; + if((param.get() || defaultOrientation) === contFull.orientation) { + continue; + } + } else if(ai === 'orientationaxes') { + // orientationaxes has no value, + // it flips everything and the axes + + cont.orientation = + {v: 'h', h: 'v'}[contFull.orientation]; + } + helpers.swapXYData(cont); + flags.calc = flags.clearAxisTypes = true; + } else if(Plots.dataArrayContainers.indexOf(param.parts[0]) !== -1) { + // TODO: use manageArrays.applyContainerArrayChanges here too + helpers.manageArrayContainers(param, newVal, undoit); + flags.calc = true; + } else { + if(valObject) { + // must redo calcdata when restyling array values of arrayOk attributes + // ... but no need to this for regl-based traces + if(valObject.arrayOk && + !Registry.traceIs(contFull, 'regl') && + (Lib.isArrayOrTypedArray(newVal) || Lib.isArrayOrTypedArray(oldVal)) + ) { + flags.calc = true; + } else editTypes.update(flags, valObject); + } else { + /* + * if we couldn't find valObject, assume a full recalc. + * This can happen if you're changing type and making + * some other edits too, so the modules we're + * looking at don't have these attributes in them. + */ + flags.calc = true; + } + + // all the other ones, just modify that one attribute + param.set(newVal); + } + } + + // swap the data attributes of the relevant x and y axes? + if(['swapxyaxes', 'orientationaxes'].indexOf(ai) !== -1) { + Axes.swap(gd, traces); + } + + // swap hovermode if set to "compare x/y data" + if(ai === 'orientationaxes') { + var hovermode = nestedProperty(gd.layout, 'hovermode'); + if(hovermode.get() === 'x') { + hovermode.set('y'); + } else if(hovermode.get() === 'y') { + hovermode.set('x'); + } + } + + // Major enough changes deserve autoscale and + // non-reversed axes so people don't get confused + // + // Note: autobin (or its new analog bin clearing) is not included here + // since we're not pushing bins back to gd.data, so if we have bin + // info it was explicitly provided by the user. + if(['orientation', 'type'].indexOf(ai) !== -1) { + axlist = []; + for(i = 0; i < traces.length; i++) { + var trace = data[traces[i]]; + + if(Registry.traceIs(trace, 'cartesian')) { + addToAxlist(trace.xaxis || 'x'); + addToAxlist(trace.yaxis || 'y'); + } + } + + doextra(axlist.map(autorangeAttr), true, 0); + doextra(axlist.map(rangeAttr), [0, 1], 0); + } + } + + if(flags.calc || flags.plot) { + flags.fullReplot = true; + } + + return { + flags: flags, + undoit: undoit, + redoit: redoit, + traces: traces, + eventData: Lib.extendDeepNoArrays([], [eventData, traces]) + }; +} + +/** + * Converts deprecated attribute keys to + * the current API to ensure backwards compatibility. + * + * This is needed for the update mechanism to determine which + * subroutines to run based on the actual attribute + * definitions (that don't include the deprecated ones). + * + * E.g. Maps {'xaxis.title': 'A chart'} to {'xaxis.title.text': 'A chart'} + * and {titlefont: {...}} to {'title.font': {...}}. + * + * @param aobj + */ +function cleanDeprecatedAttributeKeys(aobj) { + var oldAxisTitleRegex = Lib.counterRegex('axis', '\.title', false, false); + var colorbarRegex = /colorbar\.title$/; + var keys = Object.keys(aobj); + var i, key, value; + + for(i = 0; i < keys.length; i++) { + key = keys[i]; + value = aobj[key]; + + if((key === 'title' || oldAxisTitleRegex.test(key) || colorbarRegex.test(key)) && + (typeof value === 'string' || typeof value === 'number')) { + replace(key, key.replace('title', 'title.text')); + } else if(key.indexOf('titlefont') > -1) { + replace(key, key.replace('titlefont', 'title.font')); + } else if(key.indexOf('titleposition') > -1) { + replace(key, key.replace('titleposition', 'title.position')); + } else if(key.indexOf('titleside') > -1) { + replace(key, key.replace('titleside', 'title.side')); + } else if(key.indexOf('titleoffset') > -1) { + replace(key, key.replace('titleoffset', 'title.offset')); + } + } + + function replace(oldAttrStr, newAttrStr) { + aobj[newAttrStr] = aobj[oldAttrStr]; + delete aobj[oldAttrStr]; + } +} + +/** + * relayout: update layout attributes of an existing plot + * + * Can be called two ways: + * + * Signature 1: + * @param {String | HTMLDivElement} gd + * the id or dom element of the graph container div + * @param {String} astr + * attribute string (like `'xaxis.range[0]'`) to update + * @param {*} val + * value to give this attribute + * + * Signature 2: + * @param {String | HTMLDivElement} gd + * (as in signature 1) + * @param {Object} aobj + * attribute object `{astr1: val1, astr2: val2 ...}` + * allows setting multiple attributes simultaneously + */ +function relayout(gd, astr, val) { + gd = Lib.getGraphDiv(gd); + helpers.clearPromiseQueue(gd); + + if(gd.framework && gd.framework.isPolar) { + return Promise.resolve(gd); + } + + var aobj = {}; + if(typeof astr === 'string') { + aobj[astr] = val; + } else if(Lib.isPlainObject(astr)) { + aobj = Lib.extendFlat({}, astr); + } else { + Lib.warn('Relayout fail.', astr, val); + return Promise.reject(); + } + + if(Object.keys(aobj).length) gd.changed = true; + + var specs = _relayout(gd, aobj); + var flags = specs.flags; + + // clear calcdata if required + if(flags.calc) gd.calcdata = undefined; + + // fill in redraw sequence + + // even if we don't have anything left in aobj, + // something may have happened within relayout that we + // need to wait for + var seq = [Plots.previousPromises]; + + if(flags.layoutReplot) { + seq.push(subroutines.layoutReplot); + } else if(Object.keys(aobj).length) { + axRangeSupplyDefaultsByPass(gd, flags, specs) || Plots.supplyDefaults(gd); + + if(flags.legend) seq.push(subroutines.doLegend); + if(flags.layoutstyle) seq.push(subroutines.layoutStyles); + if(flags.axrange) addAxRangeSequence(seq, specs.rangesAltered); + if(flags.ticks) seq.push(subroutines.doTicksRelayout); + if(flags.modebar) seq.push(subroutines.doModeBar); + if(flags.camera) seq.push(subroutines.doCamera); + if(flags.colorbars) seq.push(subroutines.doColorBars); + + seq.push(emitAfterPlot); + } + + seq.push(Plots.rehover, Plots.redrag); + + Queue.add(gd, + relayout, [gd, specs.undoit], + relayout, [gd, specs.redoit] + ); + + var plotDone = Lib.syncOrAsync(seq, gd); + if(!plotDone || !plotDone.then) plotDone = Promise.resolve(gd); + + return plotDone.then(function() { + gd.emit('plotly_relayout', specs.eventData); + return gd; + }); +} + +// Optimization mostly for large splom traces where +// Plots.supplyDefaults can take > 100ms +function axRangeSupplyDefaultsByPass(gd, flags, specs) { + var fullLayout = gd._fullLayout; + + if(!flags.axrange) return false; + + for(var k in flags) { + if(k !== 'axrange' && flags[k]) return false; + } + + for(var axId in specs.rangesAltered) { + var axName = Axes.id2name(axId); + var axIn = gd.layout[axName]; + var axOut = fullLayout[axName]; + axOut.autorange = axIn.autorange; + axOut.range = axIn.range.slice(); + axOut.cleanRange(); + + if(axOut._matchGroup) { + for(var axId2 in axOut._matchGroup) { + if(axId2 !== axId) { + var ax2 = fullLayout[Axes.id2name(axId2)]; + ax2.autorange = axOut.autorange; + ax2.range = axOut.range.slice(); + ax2._input.range = axOut.range.slice(); + } + } + } + } + + return true; +} + +function addAxRangeSequence(seq, rangesAltered) { + // N.B. leave as sequence of subroutines (for now) instead of + // subroutine of its own so that finalDraw always gets + // executed after drawData + var drawAxes = rangesAltered ? + function(gd) { + var axIds = []; + var skipTitle = true; + + for(var id in rangesAltered) { + var ax = Axes.getFromId(gd, id); + axIds.push(id); + + if(ax._matchGroup) { + for(var id2 in ax._matchGroup) { + if(!rangesAltered[id2]) { + axIds.push(id2); + } + } + } + + if(ax.automargin) skipTitle = false; + } + + return Axes.draw(gd, axIds, {skipTitle: skipTitle}); + } : + function(gd) { + return Axes.draw(gd, 'redraw'); + }; + + seq.push( + clearSelect, + subroutines.doAutoRangeAndConstraints, + drawAxes, + subroutines.drawData, + subroutines.finalDraw + ); +} + +var AX_RANGE_RE = /^[xyz]axis[0-9]*\.range(\[[0|1]\])?$/; +var AX_AUTORANGE_RE = /^[xyz]axis[0-9]*\.autorange$/; +var AX_DOMAIN_RE = /^[xyz]axis[0-9]*\.domain(\[[0|1]\])?$/; + +function _relayout(gd, aobj) { + var layout = gd.layout; + var fullLayout = gd._fullLayout; + var guiEditFlag = fullLayout._guiEditing; + var layoutNP = makeNP(fullLayout._preGUI, guiEditFlag); + var keys = Object.keys(aobj); + var axes = Axes.list(gd); + var eventData = Lib.extendDeepAll({}, aobj); + var arrayEdits = {}; + + var arrayStr, i, j; + + cleanDeprecatedAttributeKeys(aobj); + keys = Object.keys(aobj); + + // look for 'allaxes', split out into all axes + // in case of 3D the axis are nested within a scene which is held in _id + for(i = 0; i < keys.length; i++) { + if(keys[i].indexOf('allaxes') === 0) { + for(j = 0; j < axes.length; j++) { + var scene = axes[j]._id.substr(1); + var axisAttr = (scene.indexOf('scene') !== -1) ? (scene + '.') : ''; + var newkey = keys[i].replace('allaxes', axisAttr + axes[j]._name); + + if(!aobj[newkey]) aobj[newkey] = aobj[keys[i]]; + } + + delete aobj[keys[i]]; + } + } + + // initialize flags + var flags = editTypes.layoutFlags(); + + // copies of the change (and previous values of anything affected) + // for the undo / redo queue + var redoit = {}; + var undoit = {}; + + // for attrs that interact (like scales & autoscales), save the + // old vals before making the change + // val=undefined will not set a value, just record what the value was. + // attr can be an array to set several at once (all to the same val) + function doextra(attr, val) { + if(Array.isArray(attr)) { + attr.forEach(function(a) { doextra(a, val); }); + return; + } + + // if we have another value for this attribute (explicitly or + // via a parent) do not override with this auto-generated extra + if(attr in aobj || helpers.hasParent(aobj, attr)) return; + + var p = layoutNP(layout, attr); + if(!(attr in undoit)) { + undoit[attr] = undefinedToNull(p.get()); + } + if(val !== undefined) p.set(val); + } + + // for constraint enforcement: keep track of all axes (as {id: name}) + // we're editing the (auto)range of, so we can tell the others constrained + // to scale with them that it's OK for them to shrink + var rangesAltered = {}; + var axId; + + function recordAlteredAxis(pleafPlus) { + var axId = Axes.name2id(pleafPlus.split('.')[0]); + rangesAltered[axId] = 1; + return axId; + } + + // alter gd.layout + for(var ai in aobj) { + if(helpers.hasParent(aobj, ai)) { + throw new Error('cannot set ' + ai + ' and a parent attribute simultaneously'); + } + + var p = layoutNP(layout, ai); + var vi = aobj[ai]; + var plen = p.parts.length; + // p.parts may end with an index integer if the property is an array + var pend = plen - 1; + while(pend > 0 && typeof p.parts[pend] !== 'string') pend--; + // last property in chain (leaf node) + var pleaf = p.parts[pend]; + // leaf plus immediate parent + var pleafPlus = p.parts[pend - 1] + '.' + pleaf; + // trunk nodes (everything except the leaf) + var ptrunk = p.parts.slice(0, pend).join('.'); + var parentIn = nestedProperty(gd.layout, ptrunk).get(); + var parentFull = nestedProperty(fullLayout, ptrunk).get(); + var vOld = p.get(); + + if(vi === undefined) continue; + + redoit[ai] = vi; + + // axis reverse is special - it is its own inverse + // op and has no flag. + undoit[ai] = (pleaf === 'reverse') ? vi : undefinedToNull(vOld); + + var valObject = PlotSchema.getLayoutValObject(fullLayout, p.parts); + + if(valObject && valObject.impliedEdits && vi !== null) { + for(var impliedKey in valObject.impliedEdits) { + doextra(Lib.relativeAttr(ai, impliedKey), valObject.impliedEdits[impliedKey]); + } + } + + // Setting width or height to null must reset the graph's width / height + // back to its initial value as computed during the first pass in Plots.plotAutoSize. + // + // To do so, we must manually set them back here using the _initialAutoSize cache. + // can't use impliedEdits for this because behavior depends on vi + if(['width', 'height'].indexOf(ai) !== -1) { + if(vi) { + doextra('autosize', null); + // currently we don't support autosize one dim only - so + // explicitly set the other one. Note that doextra will + // ignore this if the same relayout call also provides oppositeAttr + var oppositeAttr = ai === 'height' ? 'width' : 'height'; + doextra(oppositeAttr, fullLayout[oppositeAttr]); + } else { + fullLayout[ai] = gd._initialAutoSize[ai]; + } + } else if(ai === 'autosize') { + // depends on vi here too, so again can't use impliedEdits + doextra('width', vi ? null : fullLayout.width); + doextra('height', vi ? null : fullLayout.height); + } else if(pleafPlus.match(AX_RANGE_RE)) { + // check autorange vs range + + recordAlteredAxis(pleafPlus); + nestedProperty(fullLayout, ptrunk + '._inputRange').set(null); + } else if(pleafPlus.match(AX_AUTORANGE_RE)) { + recordAlteredAxis(pleafPlus); + nestedProperty(fullLayout, ptrunk + '._inputRange').set(null); + var axFull = nestedProperty(fullLayout, ptrunk).get(); + if(axFull._inputDomain) { + // if we're autoranging and this axis has a constrained domain, + // reset it so we don't get locked into a shrunken size + axFull._input.domain = axFull._inputDomain.slice(); + } + } else if(pleafPlus.match(AX_DOMAIN_RE)) { + nestedProperty(fullLayout, ptrunk + '._inputDomain').set(null); + } + + // toggling axis type between log and linear: we need to convert + // positions for components that are still using linearized values, + // not data values like newer components. + // previously we did this for log <-> not-log, but now only do it + // for log <-> linear + if(pleaf === 'type') { + var ax = parentIn; + var toLog = parentFull.type === 'linear' && vi === 'log'; + var fromLog = parentFull.type === 'log' && vi === 'linear'; + + if(toLog || fromLog) { + if(!ax || !ax.range) { + // 2D never gets here, but 3D does + // I don't think this is needed, but left here in case there + // are edge cases I'm not thinking of. + doextra(ptrunk + '.autorange', true); + } else if(!parentFull.autorange) { + // toggling log without autorange: need to also recalculate ranges + // because log axes use linearized values for range endpoints + var r0 = ax.range[0]; + var r1 = ax.range[1]; + if(toLog) { + // if both limits are negative, autorange + if(r0 <= 0 && r1 <= 0) { + doextra(ptrunk + '.autorange', true); + } + // if one is negative, set it 6 orders below the other. + if(r0 <= 0) r0 = r1 / 1e6; + else if(r1 <= 0) r1 = r0 / 1e6; + // now set the range values as appropriate + doextra(ptrunk + '.range[0]', Math.log(r0) / Math.LN10); + doextra(ptrunk + '.range[1]', Math.log(r1) / Math.LN10); + } else { + doextra(ptrunk + '.range[0]', Math.pow(10, r0)); + doextra(ptrunk + '.range[1]', Math.pow(10, r1)); + } + } else if(toLog) { + // just make sure the range is positive and in the right + // order, it'll get recalculated later + ax.range = (ax.range[1] > ax.range[0]) ? [1, 2] : [2, 1]; + } + + // clear polar view initial stash for radial range so that + // value get recomputed in correct units + if(Array.isArray(fullLayout._subplots.polar) && + fullLayout._subplots.polar.length && + fullLayout[p.parts[0]] && + p.parts[1] === 'radialaxis' + ) { + delete fullLayout[p.parts[0]]._subplot.viewInitial['radialaxis.range']; + } + + // Annotations and images also need to convert to/from linearized coords + // Shapes do not need this :) + Registry.getComponentMethod('annotations', 'convertCoords')(gd, parentFull, vi, doextra); + Registry.getComponentMethod('images', 'convertCoords')(gd, parentFull, vi, doextra); + } else { + // any other type changes: the range from the previous type + // will not make sense, so autorange it. + doextra(ptrunk + '.autorange', true); + doextra(ptrunk + '.range', null); + } + nestedProperty(fullLayout, ptrunk + '._inputRange').set(null); + } else if(pleaf.match(AX_NAME_PATTERN)) { + var fullProp = nestedProperty(fullLayout, ai).get(); + var newType = (vi || {}).type; + + // This can potentially cause strange behavior if the autotype is not + // numeric (linear, because we don't auto-log) but the previous type + // was log. That's a very strange edge case though + if(!newType || newType === '-') newType = 'linear'; + Registry.getComponentMethod('annotations', 'convertCoords')(gd, fullProp, newType, doextra); + Registry.getComponentMethod('images', 'convertCoords')(gd, fullProp, newType, doextra); + } + + // alter gd.layout + + // collect array component edits for execution all together + // so we can ensure consistent behavior adding/removing items + // and order-independence for add/remove/edit all together in + // one relayout call + var containerArrayMatch = manageArrays.containerArrayMatch(ai); + if(containerArrayMatch) { + arrayStr = containerArrayMatch.array; + i = containerArrayMatch.index; + var propStr = containerArrayMatch.property; + var updateValObject = valObject || {editType: 'calc'}; + + if(i !== '' && propStr === '') { + // special handling of undoit if we're adding or removing an element + // ie 'annotations[2]' which can be {...} (add) or null, + // does not work when replacing the entire array + if(manageArrays.isAddVal(vi)) { + undoit[ai] = null; + } else if(manageArrays.isRemoveVal(vi)) { + undoit[ai] = (nestedProperty(layout, arrayStr).get() || [])[i]; + } else { + Lib.warn('unrecognized full object value', aobj); + } + } + editTypes.update(flags, updateValObject); + + // prepare the edits object we'll send to applyContainerArrayChanges + if(!arrayEdits[arrayStr]) arrayEdits[arrayStr] = {}; + var objEdits = arrayEdits[arrayStr][i]; + if(!objEdits) objEdits = arrayEdits[arrayStr][i] = {}; + objEdits[propStr] = vi; + + delete aobj[ai]; + } else if(pleaf === 'reverse') { + // handle axis reversal explicitly, as there's no 'reverse' attribute + + if(parentIn.range) parentIn.range.reverse(); + else { + doextra(ptrunk + '.autorange', true); + parentIn.range = [1, 0]; + } + + if(parentFull.autorange) flags.calc = true; + else flags.plot = true; + } else { + if((fullLayout._has('scatter-like') && fullLayout._has('regl')) && + (ai === 'dragmode' && + (vi === 'lasso' || vi === 'select') && + !(vOld === 'lasso' || vOld === 'select')) + ) { + flags.plot = true; + } else if(fullLayout._has('gl2d')) { + flags.plot = true; + } else if(valObject) editTypes.update(flags, valObject); + else flags.calc = true; + + p.set(vi); + } + } + + // now we've collected component edits - execute them all together + for(arrayStr in arrayEdits) { + var finished = manageArrays.applyContainerArrayChanges(gd, + layoutNP(layout, arrayStr), arrayEdits[arrayStr], flags, layoutNP); + if(!finished) flags.plot = true; + } + + // figure out if we need to recalculate axis constraints + var constraints = fullLayout._axisConstraintGroups || []; + for(axId in rangesAltered) { + for(i = 0; i < constraints.length; i++) { + var group = constraints[i]; + if(group[axId]) { + // Always recalc if we're changing constrained ranges. + // Otherwise it's possible to violate the constraints by + // specifying arbitrary ranges for all axes in the group. + // this way some ranges may expand beyond what's specified, + // as they do at first draw, to satisfy the constraints. + flags.calc = true; + for(var groupAxId in group) { + if(!rangesAltered[groupAxId]) { + Axes.getFromId(gd, groupAxId)._constraintShrinkable = true; + } + } + } + } + } + + // If the autosize changed or height or width was explicitly specified, + // this triggers a redraw + // TODO: do we really need special aobj.height/width handling here? + // couldn't editType do this? + if(updateAutosize(gd) || aobj.height || aobj.width) flags.plot = true; + + if(flags.plot || flags.calc) { + flags.layoutReplot = true; + } + + // now all attribute mods are done, as are + // redo and undo so we can save them + + return { + flags: flags, + rangesAltered: rangesAltered, + undoit: undoit, + redoit: redoit, + eventData: eventData + }; +} + +/* + * updateAutosize: we made a change, does it change the autosize result? + * puts the new size into fullLayout + * returns true if either height or width changed + */ +function updateAutosize(gd) { + var fullLayout = gd._fullLayout; + var oldWidth = fullLayout.width; + var oldHeight = fullLayout.height; + + // calculate autosizing + if(gd.layout.autosize) Plots.plotAutoSize(gd, gd.layout, fullLayout); + + return (fullLayout.width !== oldWidth) || (fullLayout.height !== oldHeight); +} + +/** + * update: update trace and layout attributes of an existing plot + * + * @param {String | HTMLDivElement} gd + * the id or DOM element of the graph container div + * @param {Object} traceUpdate + * attribute object `{astr1: val1, astr2: val2 ...}` + * corresponding to updates in the plot's traces + * @param {Object} layoutUpdate + * attribute object `{astr1: val1, astr2: val2 ...}` + * corresponding to updates in the plot's layout + * @param {Number[] | Number} [traces] + * integer or array of integers for the traces to alter (all if omitted) + * + */ +function update(gd, traceUpdate, layoutUpdate, _traces) { + gd = Lib.getGraphDiv(gd); + helpers.clearPromiseQueue(gd); + + if(gd.framework && gd.framework.isPolar) { + return Promise.resolve(gd); + } + + if(!Lib.isPlainObject(traceUpdate)) traceUpdate = {}; + if(!Lib.isPlainObject(layoutUpdate)) layoutUpdate = {}; + + if(Object.keys(traceUpdate).length) gd.changed = true; + if(Object.keys(layoutUpdate).length) gd.changed = true; + + var traces = helpers.coerceTraceIndices(gd, _traces); + + var restyleSpecs = _restyle(gd, Lib.extendFlat({}, traceUpdate), traces); + var restyleFlags = restyleSpecs.flags; + + var relayoutSpecs = _relayout(gd, Lib.extendFlat({}, layoutUpdate)); + var relayoutFlags = relayoutSpecs.flags; + + // clear calcdata and/or axis types if required + if(restyleFlags.calc || relayoutFlags.calc) gd.calcdata = undefined; + if(restyleFlags.clearAxisTypes) helpers.clearAxisTypes(gd, traces, layoutUpdate); + + // fill in redraw sequence + var seq = []; + + if(relayoutFlags.layoutReplot) { + // N.B. works fine when both + // relayoutFlags.layoutReplot and restyleFlags.fullReplot are true + seq.push(subroutines.layoutReplot); + } else if(restyleFlags.fullReplot) { + seq.push(exports.plot); + } else { + seq.push(Plots.previousPromises); + axRangeSupplyDefaultsByPass(gd, relayoutFlags, relayoutSpecs) || Plots.supplyDefaults(gd); + + if(restyleFlags.style) seq.push(subroutines.doTraceStyle); + if(restyleFlags.colorbars || relayoutFlags.colorbars) seq.push(subroutines.doColorBars); + if(relayoutFlags.legend) seq.push(subroutines.doLegend); + if(relayoutFlags.layoutstyle) seq.push(subroutines.layoutStyles); + if(relayoutFlags.axrange) addAxRangeSequence(seq, relayoutSpecs.rangesAltered); + if(relayoutFlags.ticks) seq.push(subroutines.doTicksRelayout); + if(relayoutFlags.modebar) seq.push(subroutines.doModeBar); + if(relayoutFlags.camera) seq.push(subroutines.doCamera); + + seq.push(emitAfterPlot); + } + + seq.push(Plots.rehover, Plots.redrag); + + Queue.add(gd, + update, [gd, restyleSpecs.undoit, relayoutSpecs.undoit, restyleSpecs.traces], + update, [gd, restyleSpecs.redoit, relayoutSpecs.redoit, restyleSpecs.traces] + ); + + var plotDone = Lib.syncOrAsync(seq, gd); + if(!plotDone || !plotDone.then) plotDone = Promise.resolve(gd); + + return plotDone.then(function() { + gd.emit('plotly_update', { + data: restyleSpecs.eventData, + layout: relayoutSpecs.eventData + }); + + return gd; + }); +} + +/* + * internal-use-only restyle/relayout/update variants that record the initial + * values in (fullLayout|fullTrace)._preGUI so changes can be persisted across + * Plotly.react data updates, dependent on uirevision attributes + */ +function guiEdit(func) { + return function wrappedEdit(gd) { + gd._fullLayout._guiEditing = true; + var p = func.apply(null, arguments); + gd._fullLayout._guiEditing = false; + return p; + }; +} + +// For connecting edited layout attributes to uirevision attrs +// If no `attr` we use `match[1] + '.uirevision'` +// Ordered by most common edits first, to minimize our search time +var layoutUIControlPatterns = [ + {pattern: /^hiddenlabels/, attr: 'legend.uirevision'}, + {pattern: /^((x|y)axis\d*)\.((auto)?range|title\.text)/}, + + // showspikes and modes include those nested inside scenes + {pattern: /axis\d*\.showspikes$/, attr: 'modebar.uirevision'}, + {pattern: /(hover|drag)mode$/, attr: 'modebar.uirevision'}, + + {pattern: /^(scene\d*)\.camera/}, + {pattern: /^(geo\d*)\.(projection|center)/}, + {pattern: /^(ternary\d*\.[abc]axis)\.(min|title\.text)$/}, + {pattern: /^(polar\d*\.radialaxis)\.((auto)?range|angle|title\.text)/}, + {pattern: /^(polar\d*\.angularaxis)\.rotation/}, + {pattern: /^(mapbox\d*)\.(center|zoom|bearing|pitch)/}, + + {pattern: /^legend\.(x|y)$/, attr: 'editrevision'}, + {pattern: /^(shapes|annotations)/, attr: 'editrevision'}, + {pattern: /^title\.text$/, attr: 'editrevision'} +]; + +// same for trace attributes: if `attr` is given it's in layout, +// or with no `attr` we use `trace.uirevision` +var traceUIControlPatterns = [ + {pattern: /^selectedpoints$/, attr: 'selectionrevision'}, + // "visible" includes trace.transforms[i].styles[j].value.visible + {pattern: /(^|value\.)visible$/, attr: 'legend.uirevision'}, + {pattern: /^dimensions\[\d+\]\.constraintrange/}, + {pattern: /^node\.(x|y|groups)/}, // for Sankey nodes + {pattern: /^level$/}, // for Sunburst & Treemap traces + + // below this you must be in editable: true mode + // TODO: I still put name and title with `trace.uirevision` + // reasonable or should these be `editrevision`? + // Also applies to axis titles up in the layout section + + // "name" also includes transform.styles + {pattern: /(^|value\.)name$/}, + // including nested colorbar attributes (ie marker.colorbar) + {pattern: /colorbar\.title\.text$/}, + {pattern: /colorbar\.(x|y)$/, attr: 'editrevision'} +]; + +function findUIPattern(key, patternSpecs) { + for(var i = 0; i < patternSpecs.length; i++) { + var spec = patternSpecs[i]; + var match = key.match(spec.pattern); + if(match) { + return {head: match[1], attr: spec.attr}; + } + } +} + +// We're finding the new uirevision before supplyDefaults, so do the +// inheritance manually. Note that only `undefined` inherits - other +// falsy values are returned. +function getNewRev(revAttr, container) { + var newRev = nestedProperty(container, revAttr).get(); + if(newRev !== undefined) return newRev; + + var parts = revAttr.split('.'); + parts.pop(); + while(parts.length > 1) { + parts.pop(); + newRev = nestedProperty(container, parts.join('.') + '.uirevision').get(); + if(newRev !== undefined) return newRev; + } + + return container.uirevision; +} + +function getFullTraceIndexFromUid(uid, fullData) { + for(var i = 0; i < fullData.length; i++) { + if(fullData[i]._fullInput.uid === uid) return i; + } + return -1; +} + +function getTraceIndexFromUid(uid, data, tracei) { + for(var i = 0; i < data.length; i++) { + if(data[i].uid === uid) return i; + } + // fall back on trace order, but only if user didn't provide a uid for that trace + return (!data[tracei] || data[tracei].uid) ? -1 : tracei; +} + +function valsMatch(v1, v2) { + var v1IsObj = Lib.isPlainObject(v1); + var v1IsArray = Array.isArray(v1); + if(v1IsObj || v1IsArray) { + return ( + (v1IsObj && Lib.isPlainObject(v2)) || + (v1IsArray && Array.isArray(v2)) + ) && JSON.stringify(v1) === JSON.stringify(v2); + } + return v1 === v2; +} + +function applyUIRevisions(data, layout, oldFullData, oldFullLayout) { + var layoutPreGUI = oldFullLayout._preGUI; + var key, revAttr, oldRev, newRev, match, preGUIVal, newNP, newVal; + var bothInheritAutorange = []; + var newRangeAccepted = {}; + for(key in layoutPreGUI) { + match = findUIPattern(key, layoutUIControlPatterns); + if(match) { + revAttr = match.attr || (match.head + '.uirevision'); + oldRev = nestedProperty(oldFullLayout, revAttr).get(); + newRev = oldRev && getNewRev(revAttr, layout); + if(newRev && (newRev === oldRev)) { + preGUIVal = layoutPreGUI[key]; + if(preGUIVal === null) preGUIVal = undefined; + newNP = nestedProperty(layout, key); + newVal = newNP.get(); + if(valsMatch(newVal, preGUIVal)) { + if(newVal === undefined && key.substr(key.length - 9) === 'autorange') { + bothInheritAutorange.push(key.substr(0, key.length - 10)); + } + newNP.set(undefinedToNull(nestedProperty(oldFullLayout, key).get())); + continue; + } + } + } else { + Lib.warn('unrecognized GUI edit: ' + key); + } + // if we got this far, the new value was accepted as the new starting + // point (either because it changed or revision changed) + // so remove it from _preGUI for next time. + delete layoutPreGUI[key]; + + if(key.substr(key.length - 8, 6) === 'range[') { + newRangeAccepted[key.substr(0, key.length - 9)] = 1; + } + } + + // Special logic for `autorange`, since it interacts with `range`: + // If the new figure's matching `range` was kept, and `autorange` + // wasn't supplied explicitly in either the original or the new figure, + // we shouldn't alter that - but we may just have done that, so fix it. + for(var i = 0; i < bothInheritAutorange.length; i++) { + var axAttr = bothInheritAutorange[i]; + if(newRangeAccepted[axAttr]) { + var newAx = nestedProperty(layout, axAttr).get(); + if(newAx) delete newAx.autorange; + } + } + + // Now traces - try to match them up by uid (in case we added/deleted in + // the middle), then fall back on index. + var allTracePreGUI = oldFullLayout._tracePreGUI; + for(var uid in allTracePreGUI) { + var tracePreGUI = allTracePreGUI[uid]; + var newTrace = null; + var fullInput; + for(key in tracePreGUI) { + // wait until we know we have preGUI values to look for traces + // but if we don't find both, stop looking at this uid + if(!newTrace) { + var fulli = getFullTraceIndexFromUid(uid, oldFullData); + if(fulli < 0) { + // Somehow we didn't even have this trace in oldFullData... + // I guess this could happen with `deleteTraces` or something + delete allTracePreGUI[uid]; + break; + } + var fullTrace = oldFullData[fulli]; + fullInput = fullTrace._fullInput; + + var newTracei = getTraceIndexFromUid(uid, data, fullInput.index); + if(newTracei < 0) { + // No match in new data + delete allTracePreGUI[uid]; + break; + } + newTrace = data[newTracei]; + } + + match = findUIPattern(key, traceUIControlPatterns); + if(match) { + if(match.attr) { + oldRev = nestedProperty(oldFullLayout, match.attr).get(); + newRev = oldRev && getNewRev(match.attr, layout); + } else { + oldRev = fullInput.uirevision; + // inheritance for trace.uirevision is simple, just layout.uirevision + newRev = newTrace.uirevision; + if(newRev === undefined) newRev = layout.uirevision; + } + + if(newRev && newRev === oldRev) { + preGUIVal = tracePreGUI[key]; + if(preGUIVal === null) preGUIVal = undefined; + newNP = nestedProperty(newTrace, key); + newVal = newNP.get(); + if(valsMatch(newVal, preGUIVal)) { + newNP.set(undefinedToNull(nestedProperty(fullInput, key).get())); + continue; + } + } + } else { + Lib.warn('unrecognized GUI edit: ' + key + ' in trace uid ' + uid); + } + delete tracePreGUI[key]; + } + } +} + +/** + * Plotly.react: + * A plot/update method that takes the full plot state (same API as plot/newPlot) + * and diffs to determine the minimal update pathway + * + * @param {string id or DOM element} gd + * the id or DOM element of the graph container div + * @param {array of objects} data + * array of traces, containing the data and display information for each trace + * @param {object} layout + * object describing the overall display of the plot, + * all the stuff that doesn't pertain to any individual trace + * @param {object} config + * configuration options (see ./plot_config.js for more info) + * + * OR + * + * @param {string id or DOM element} gd + * the id or DOM element of the graph container div + * @param {object} figure + * object containing `data`, `layout`, `config`, and `frames` members + * + */ +function react(gd, data, layout, config) { + var frames, plotDone; + + function addFrames() { return exports.addFrames(gd, frames); } + + gd = Lib.getGraphDiv(gd); + helpers.clearPromiseQueue(gd); + + var oldFullData = gd._fullData; + var oldFullLayout = gd._fullLayout; + + // you can use this as the initial draw as well as to update + if(!Lib.isPlotDiv(gd) || !oldFullData || !oldFullLayout) { + plotDone = exports.newPlot(gd, data, layout, config); + } else { + if(Lib.isPlainObject(data)) { + var obj = data; + data = obj.data; + layout = obj.layout; + config = obj.config; + frames = obj.frames; + } + + var configChanged = false; + // assume that if there's a config at all, we're reacting to it too, + // and completely replace the previous config + if(config) { + var oldConfig = Lib.extendDeep({}, gd._context); + gd._context = undefined; + setPlotContext(gd, config); + configChanged = diffConfig(oldConfig, gd._context); + } + + gd.data = data || []; + helpers.cleanData(gd.data); + gd.layout = layout || {}; + helpers.cleanLayout(gd.layout); + + applyUIRevisions(gd.data, gd.layout, oldFullData, oldFullLayout); + + // "true" skips updating calcdata and remapping arrays from calcTransforms, + // which supplyDefaults usually does at the end, but we may need to NOT do + // if the diff (which we haven't determined yet) says we'll recalc + Plots.supplyDefaults(gd, {skipUpdateCalc: true}); + + var newFullData = gd._fullData; + var newFullLayout = gd._fullLayout; + var immutable = newFullLayout.datarevision === undefined; + var transition = newFullLayout.transition; + + var relayoutFlags = diffLayout(gd, oldFullLayout, newFullLayout, immutable, transition); + var newDataRevision = relayoutFlags.newDataRevision; + var restyleFlags = diffData(gd, oldFullData, newFullData, immutable, transition, newDataRevision); + + // TODO: how to translate this part of relayout to Plotly.react? + // // Setting width or height to null must reset the graph's width / height + // // back to its initial value as computed during the first pass in Plots.plotAutoSize. + // // + // // To do so, we must manually set them back here using the _initialAutoSize cache. + // if(['width', 'height'].indexOf(ai) !== -1 && vi === null) { + // fullLayout[ai] = gd._initialAutoSize[ai]; + // } + + if(updateAutosize(gd)) relayoutFlags.layoutReplot = true; + + // clear calcdata if required + if(restyleFlags.calc || relayoutFlags.calc) gd.calcdata = undefined; + // otherwise do the calcdata updates and calcTransform array remaps that we skipped earlier + else Plots.supplyDefaultsUpdateCalc(gd.calcdata, newFullData); + + // Note: what restyle/relayout use impliedEdits and clearAxisTypes for + // must be handled by the user when using Plotly.react. + + // fill in redraw sequence + var seq = []; + + if(frames) { + gd._transitionData = {}; + Plots.createTransitionData(gd); + seq.push(addFrames); + } + + // Transition pathway, + // only used when 'transition' is set by user and + // when at least one animatable attribute has changed, + // N.B. config changed aren't animatable + if(newFullLayout.transition && !configChanged && (restyleFlags.anim || relayoutFlags.anim)) { + Plots.doCalcdata(gd); + subroutines.doAutoRangeAndConstraints(gd); + + seq.push(function() { + return Plots.transitionFromReact(gd, restyleFlags, relayoutFlags, oldFullLayout); + }); + } else if(restyleFlags.fullReplot || relayoutFlags.layoutReplot || configChanged) { + gd._fullLayout._skipDefaults = true; + seq.push(exports.plot); + } else { + for(var componentType in relayoutFlags.arrays) { + var indices = relayoutFlags.arrays[componentType]; + if(indices.length) { + var drawOne = Registry.getComponentMethod(componentType, 'drawOne'); + if(drawOne !== Lib.noop) { + for(var i = 0; i < indices.length; i++) { + drawOne(gd, indices[i]); + } + } else { + var draw = Registry.getComponentMethod(componentType, 'draw'); + if(draw === Lib.noop) { + throw new Error('cannot draw components: ' + componentType); + } + draw(gd); + } + } + } + + seq.push(Plots.previousPromises); + if(restyleFlags.style) seq.push(subroutines.doTraceStyle); + if(restyleFlags.colorbars || relayoutFlags.colorbars) seq.push(subroutines.doColorBars); + if(relayoutFlags.legend) seq.push(subroutines.doLegend); + if(relayoutFlags.layoutstyle) seq.push(subroutines.layoutStyles); + if(relayoutFlags.axrange) addAxRangeSequence(seq); + if(relayoutFlags.ticks) seq.push(subroutines.doTicksRelayout); + if(relayoutFlags.modebar) seq.push(subroutines.doModeBar); + if(relayoutFlags.camera) seq.push(subroutines.doCamera); + seq.push(emitAfterPlot); + } + + seq.push(Plots.rehover, Plots.redrag); + + plotDone = Lib.syncOrAsync(seq, gd); + if(!plotDone || !plotDone.then) plotDone = Promise.resolve(gd); + } + + return plotDone.then(function() { + gd.emit('plotly_react', { + data: data, + layout: layout + }); + + return gd; + }); +} + +function diffData(gd, oldFullData, newFullData, immutable, transition, newDataRevision) { + var sameTraceLength = oldFullData.length === newFullData.length; + + if(!transition && !sameTraceLength) { + return { + fullReplot: true, + calc: true + }; + } + + var flags = editTypes.traceFlags(); + flags.arrays = {}; + flags.nChanges = 0; + flags.nChangesAnim = 0; + + var i, trace; + + function getTraceValObject(parts) { + var out = PlotSchema.getTraceValObject(trace, parts); + if(!trace._module.animatable && out.anim) { + out.anim = false; + } + return out; + } + + var diffOpts = { + getValObject: getTraceValObject, + flags: flags, + immutable: immutable, + transition: transition, + newDataRevision: newDataRevision, + gd: gd + }; + + var seenUIDs = {}; + + for(i = 0; i < oldFullData.length; i++) { + if(newFullData[i]) { + trace = newFullData[i]._fullInput; + if(Plots.hasMakesDataTransform(trace)) trace = newFullData[i]; + if(seenUIDs[trace.uid]) continue; + seenUIDs[trace.uid] = 1; + + getDiffFlags(oldFullData[i]._fullInput, trace, [], diffOpts); + } + } + + if(flags.calc || flags.plot) { + flags.fullReplot = true; + } + + if(transition && flags.nChanges && flags.nChangesAnim) { + flags.anim = (flags.nChanges === flags.nChangesAnim) && sameTraceLength ? 'all' : 'some'; + } + + return flags; +} + +function diffLayout(gd, oldFullLayout, newFullLayout, immutable, transition) { + var flags = editTypes.layoutFlags(); + flags.arrays = {}; + flags.rangesAltered = {}; + flags.nChanges = 0; + flags.nChangesAnim = 0; + + function getLayoutValObject(parts) { + return PlotSchema.getLayoutValObject(newFullLayout, parts); + } + + var diffOpts = { + getValObject: getLayoutValObject, + flags: flags, + immutable: immutable, + transition: transition, + gd: gd + }; + + getDiffFlags(oldFullLayout, newFullLayout, [], diffOpts); + + if(flags.plot || flags.calc) { + flags.layoutReplot = true; + } + + if(transition && flags.nChanges && flags.nChangesAnim) { + flags.anim = flags.nChanges === flags.nChangesAnim ? 'all' : 'some'; + } + + return flags; +} + +function getDiffFlags(oldContainer, newContainer, outerparts, opts) { + var valObject, key, astr; + + var getValObject = opts.getValObject; + var flags = opts.flags; + var immutable = opts.immutable; + var inArray = opts.inArray; + var arrayIndex = opts.arrayIndex; + + function changed() { + var editType = valObject.editType; + if(inArray && editType.indexOf('arraydraw') !== -1) { + Lib.pushUnique(flags.arrays[inArray], arrayIndex); + return; + } + editTypes.update(flags, valObject); + + if(editType !== 'none') { + flags.nChanges++; + } + + // track animatable changes + if(opts.transition && valObject.anim) { + flags.nChangesAnim++; + } + + // track cartesian axes with altered ranges + if(AX_RANGE_RE.test(astr) || AX_AUTORANGE_RE.test(astr)) { + flags.rangesAltered[outerparts[0]] = 1; + } + + // clear _inputDomain on cartesian axes with altered domains + if(AX_DOMAIN_RE.test(astr)) { + nestedProperty(newContainer, '_inputDomain').set(null); + } + + // track datarevision changes + if(key === 'datarevision') { + flags.newDataRevision = 1; + } + } + + function valObjectCanBeDataArray(valObject) { + return valObject.valType === 'data_array' || valObject.arrayOk; + } + + for(key in oldContainer) { + // short-circuit based on previous calls or previous keys that already maximized the pathway + if(flags.calc && !opts.transition) return; + + var oldVal = oldContainer[key]; + var newVal = newContainer[key]; + var parts = outerparts.concat(key); + astr = parts.join('.'); + + if(key.charAt(0) === '_' || typeof oldVal === 'function' || oldVal === newVal) continue; + + // FIXME: ax.tick0 and dtick get filled in during plotting (except for geo subplots), + // and unlike other auto values they don't make it back into the input, + // so newContainer won't have them. + if((key === 'tick0' || key === 'dtick') && outerparts[0] !== 'geo') { + var tickMode = newContainer.tickmode; + if(tickMode === 'auto' || tickMode === 'array' || !tickMode) continue; + } + // FIXME: Similarly for axis ranges for 3D + // contourcarpet doesn't HAVE zmin/zmax, they're just auto-added. It needs them. + if(key === 'range' && newContainer.autorange) continue; + if((key === 'zmin' || key === 'zmax') && newContainer.type === 'contourcarpet') continue; + + valObject = getValObject(parts); + + // in case type changed, we may not even *have* a valObject. + if(!valObject) continue; + + if(valObject._compareAsJSON && JSON.stringify(oldVal) === JSON.stringify(newVal)) continue; + + var valType = valObject.valType; + var i; + + var canBeDataArray = valObjectCanBeDataArray(valObject); + var wasArray = Array.isArray(oldVal); + var nowArray = Array.isArray(newVal); + + // hack for traces that modify the data in supplyDefaults, like + // converting 1D to 2D arrays, which will always create new objects + if(wasArray && nowArray) { + var inputKey = '_input_' + key; + var oldValIn = oldContainer[inputKey]; + var newValIn = newContainer[inputKey]; + if(Array.isArray(oldValIn) && oldValIn === newValIn) continue; + } + + if(newVal === undefined) { + if(canBeDataArray && wasArray) flags.calc = true; + else changed(); + } else if(valObject._isLinkedToArray) { + var arrayEditIndices = []; + var extraIndices = false; + if(!inArray) flags.arrays[key] = arrayEditIndices; + + var minLen = Math.min(oldVal.length, newVal.length); + var maxLen = Math.max(oldVal.length, newVal.length); + if(minLen !== maxLen) { + if(valObject.editType === 'arraydraw') { + extraIndices = true; + } else { + changed(); + continue; + } + } + + for(i = 0; i < minLen; i++) { + getDiffFlags(oldVal[i], newVal[i], parts.concat(i), + // add array indices, but not if we're already in an array + Lib.extendFlat({inArray: key, arrayIndex: i}, opts)); + } + + // put this at the end so that we know our collected array indices are sorted + // but the check for length changes happens up front so we can short-circuit + // diffing if appropriate + if(extraIndices) { + for(i = minLen; i < maxLen; i++) { + arrayEditIndices.push(i); + } + } + } else if(!valType && Lib.isPlainObject(oldVal)) { + getDiffFlags(oldVal, newVal, parts, opts); + } else if(canBeDataArray) { + if(wasArray && nowArray) { + // don't try to diff two data arrays. If immutable we know the data changed, + // if not, assume it didn't and let `layout.datarevision` tell us if it did + if(immutable) { + flags.calc = true; + } + + // look for animatable attributes when the data changed + if(immutable || opts.newDataRevision) { + changed(); + } + } else if(wasArray !== nowArray) { + flags.calc = true; + } else changed(); + } else if(wasArray && nowArray) { + // info array, colorscale, 'any' - these are short, just stringify. + // I don't *think* that covers up any real differences post-validation, does it? + // otherwise we need to dive in 1 (info_array) or 2 (colorscale) levels and compare + // all elements. + if(oldVal.length !== newVal.length || String(oldVal) !== String(newVal)) { + changed(); + } + } else { + changed(); + } + } + + for(key in newContainer) { + if(!(key in oldContainer || key.charAt(0) === '_' || typeof newContainer[key] === 'function')) { + valObject = getValObject(outerparts.concat(key)); + + if(valObjectCanBeDataArray(valObject) && Array.isArray(newContainer[key])) { + flags.calc = true; + return; + } else changed(); + } + } +} + +/* + * simple diff for config - for now, just treat all changes as equivalent + */ +function diffConfig(oldConfig, newConfig) { + var key; + + for(key in oldConfig) { + if(key.charAt(0) === '_') continue; + var oldVal = oldConfig[key]; + var newVal = newConfig[key]; + if(oldVal !== newVal) { + if(Lib.isPlainObject(oldVal) && Lib.isPlainObject(newVal)) { + if(diffConfig(oldVal, newVal)) { + return true; + } + } else if(Array.isArray(oldVal) && Array.isArray(newVal)) { + if(oldVal.length !== newVal.length) { + return true; + } + for(var i = 0; i < oldVal.length; i++) { + if(oldVal[i] !== newVal[i]) { + if(Lib.isPlainObject(oldVal[i]) && Lib.isPlainObject(newVal[i])) { + if(diffConfig(oldVal[i], newVal[i])) { + return true; + } + } else { + return true; + } + } + } + } else { + return true; + } + } + } +} + +/** + * Animate to a frame, sequence of frame, frame group, or frame definition + * + * @param {string id or DOM element} gd + * the id or DOM element of the graph container div + * + * @param {string or object or array of strings or array of objects} frameOrGroupNameOrFrameList + * a single frame, array of frames, or group to which to animate. The intent is + * inferred by the type of the input. Valid inputs are: + * + * - string, e.g. 'groupname': animate all frames of a given `group` in the order + * in which they are defined via `Plotly.addFrames`. + * + * - array of strings, e.g. ['frame1', frame2']: a list of frames by name to which + * to animate in sequence + * + * - object: {data: ...}: a frame definition to which to animate. The frame is not + * and does not need to be added via `Plotly.addFrames`. It may contain any of + * the properties of a frame, including `data`, `layout`, and `traces`. The + * frame is used as provided and does not use the `baseframe` property. + * + * - array of objects, e.g. [{data: ...}, {data: ...}]: a list of frame objects, + * each following the same rules as a single `object`. + * + * @param {object} animationOpts + * configuration for the animation + */ +function animate(gd, frameOrGroupNameOrFrameList, animationOpts) { + gd = Lib.getGraphDiv(gd); + + if(!Lib.isPlotDiv(gd)) { + throw new Error( + 'This element is not a Plotly plot: ' + gd + '. It\'s likely that you\'ve failed ' + + 'to create a plot before animating it. For more details, see ' + + 'https://plot.ly/javascript/animations/' + ); + } + + var trans = gd._transitionData; + + // This is the queue of frames that will be animated as soon as possible. They + // are popped immediately upon the *start* of a transition: + if(!trans._frameQueue) { + trans._frameQueue = []; + } + + animationOpts = Plots.supplyAnimationDefaults(animationOpts); + var transitionOpts = animationOpts.transition; + var frameOpts = animationOpts.frame; + + // Since frames are popped immediately, an empty queue only means all frames have + // *started* to transition, not that the animation is complete. To solve that, + // track a separate counter that increments at the same time as frames are added + // to the queue, but decrements only when the transition is complete. + if(trans._frameWaitingCnt === undefined) { + trans._frameWaitingCnt = 0; + } + + function getTransitionOpts(i) { + if(Array.isArray(transitionOpts)) { + if(i >= transitionOpts.length) { + return transitionOpts[0]; + } else { + return transitionOpts[i]; + } + } else { + return transitionOpts; + } + } + + function getFrameOpts(i) { + if(Array.isArray(frameOpts)) { + if(i >= frameOpts.length) { + return frameOpts[0]; + } else { + return frameOpts[i]; + } + } else { + return frameOpts; + } + } + + // Execute a callback after the wrapper function has been called n times. + // This is used to defer the resolution until a transition has resovled *and* + // the frame has completed. If it's not done this way, then we get a race + // condition in which the animation might resolve before a transition is complete + // or vice versa. + function callbackOnNthTime(cb, n) { + var cnt = 0; + return function() { + if(cb && ++cnt === n) { + return cb(); + } + }; + } + + return new Promise(function(resolve, reject) { + function discardExistingFrames() { + if(trans._frameQueue.length === 0) { + return; + } + + while(trans._frameQueue.length) { + var next = trans._frameQueue.pop(); + if(next.onInterrupt) { + next.onInterrupt(); + } + } + + gd.emit('plotly_animationinterrupted', []); + } + + function queueFrames(frameList) { + if(frameList.length === 0) return; + + for(var i = 0; i < frameList.length; i++) { + var computedFrame; + + if(frameList[i].type === 'byname') { + // If it's a named frame, compute it: + computedFrame = Plots.computeFrame(gd, frameList[i].name); + } else { + // Otherwise we must have been given a simple object, so treat + // the input itself as the computed frame. + computedFrame = frameList[i].data; + } + + var frameOpts = getFrameOpts(i); + var transitionOpts = getTransitionOpts(i); + + // It doesn't make much sense for the transition duration to be greater than + // the frame duration, so limit it: + transitionOpts.duration = Math.min(transitionOpts.duration, frameOpts.duration); + + var nextFrame = { + frame: computedFrame, + name: frameList[i].name, + frameOpts: frameOpts, + transitionOpts: transitionOpts, + }; + if(i === frameList.length - 1) { + // The last frame in this .animate call stores the promise resolve + // and reject callbacks. This is how we ensure that the animation + // loop (which may exist as a result of a *different* .animate call) + // still resolves or rejecdts this .animate call's promise. once it's + // complete. + nextFrame.onComplete = callbackOnNthTime(resolve, 2); + nextFrame.onInterrupt = reject; + } + + trans._frameQueue.push(nextFrame); + } + + // Set it as never having transitioned to a frame. This will cause the animation + // loop to immediately transition to the next frame (which, for immediate mode, + // is the first frame in the list since all others would have been discarded + // below) + if(animationOpts.mode === 'immediate') { + trans._lastFrameAt = -Infinity; + } + + // Only it's not already running, start a RAF loop. This could be avoided in the + // case that there's only one frame, but it significantly complicated the logic + // and only sped things up by about 5% or so for a lorenz attractor simulation. + // It would be a fine thing to implement, but the benefit of that optimization + // doesn't seem worth the extra complexity. + if(!trans._animationRaf) { + beginAnimationLoop(); + } + } + + function stopAnimationLoop() { + gd.emit('plotly_animated'); + + // Be sure to unset also since it's how we know whether a loop is already running: + window.cancelAnimationFrame(trans._animationRaf); + trans._animationRaf = null; + } + + function nextFrame() { + if(trans._currentFrame && trans._currentFrame.onComplete) { + // Execute the callback and unset it to ensure it doesn't + // accidentally get called twice + trans._currentFrame.onComplete(); + } + + var newFrame = trans._currentFrame = trans._frameQueue.shift(); + + if(newFrame) { + // Since it's sometimes necessary to do deep digging into frame data, + // we'll consider it not 100% impossible for nulls or numbers to sneak through, + // so check when casting the name, just to be absolutely certain: + var stringName = newFrame.name ? newFrame.name.toString() : null; + gd._fullLayout._currentFrame = stringName; + + trans._lastFrameAt = Date.now(); + trans._timeToNext = newFrame.frameOpts.duration; + + // This is simply called and it's left to .transition to decide how to manage + // interrupting current transitions. That means we don't need to worry about + // how it resolves or what happens after this: + Plots.transition(gd, + newFrame.frame.data, + newFrame.frame.layout, + helpers.coerceTraceIndices(gd, newFrame.frame.traces), + newFrame.frameOpts, + newFrame.transitionOpts + ).then(function() { + if(newFrame.onComplete) { + newFrame.onComplete(); + } + }); + + gd.emit('plotly_animatingframe', { + name: stringName, + frame: newFrame.frame, + animation: { + frame: newFrame.frameOpts, + transition: newFrame.transitionOpts, + } + }); + } else { + // If there are no more frames, then stop the RAF loop: + stopAnimationLoop(); + } + } + + function beginAnimationLoop() { + gd.emit('plotly_animating'); + + // If no timer is running, then set last frame = long ago so that the next + // frame is immediately transitioned: + trans._lastFrameAt = -Infinity; + trans._timeToNext = 0; + trans._runningTransitions = 0; + trans._currentFrame = null; + + var doFrame = function() { + // This *must* be requested before nextFrame since nextFrame may decide + // to cancel it if there's nothing more to animated: + trans._animationRaf = window.requestAnimationFrame(doFrame); + + // Check if we're ready for a new frame: + if(Date.now() - trans._lastFrameAt > trans._timeToNext) { + nextFrame(); + } + }; + + doFrame(); + } + + // This is an animate-local counter that helps match up option input list + // items with the particular frame. + var configCounter = 0; + function setTransitionConfig(frame) { + if(Array.isArray(transitionOpts)) { + if(configCounter >= transitionOpts.length) { + frame.transitionOpts = transitionOpts[configCounter]; + } else { + frame.transitionOpts = transitionOpts[0]; + } + } else { + frame.transitionOpts = transitionOpts; + } + configCounter++; + return frame; + } + + // Disambiguate what's sort of frames have been received + var i, frame; + var frameList = []; + var allFrames = frameOrGroupNameOrFrameList === undefined || frameOrGroupNameOrFrameList === null; + var isFrameArray = Array.isArray(frameOrGroupNameOrFrameList); + var isSingleFrame = !allFrames && !isFrameArray && Lib.isPlainObject(frameOrGroupNameOrFrameList); + + if(isSingleFrame) { + // In this case, a simple object has been passed to animate. + frameList.push({ + type: 'object', + data: setTransitionConfig(Lib.extendFlat({}, frameOrGroupNameOrFrameList)) + }); + } else if(allFrames || ['string', 'number'].indexOf(typeof frameOrGroupNameOrFrameList) !== -1) { + // In this case, null or undefined has been passed so that we want to + // animate *all* currently defined frames + for(i = 0; i < trans._frames.length; i++) { + frame = trans._frames[i]; + + if(!frame) continue; + + if(allFrames || String(frame.group) === String(frameOrGroupNameOrFrameList)) { + frameList.push({ + type: 'byname', + name: String(frame.name), + data: setTransitionConfig({name: frame.name}) + }); + } + } + } else if(isFrameArray) { + for(i = 0; i < frameOrGroupNameOrFrameList.length; i++) { + var frameOrName = frameOrGroupNameOrFrameList[i]; + if(['number', 'string'].indexOf(typeof frameOrName) !== -1) { + frameOrName = String(frameOrName); + // In this case, there's an array and this frame is a string name: + frameList.push({ + type: 'byname', + name: frameOrName, + data: setTransitionConfig({name: frameOrName}) + }); + } else if(Lib.isPlainObject(frameOrName)) { + frameList.push({ + type: 'object', + data: setTransitionConfig(Lib.extendFlat({}, frameOrName)) + }); + } + } + } + + // Verify that all of these frames actually exist; return and reject if not: + for(i = 0; i < frameList.length; i++) { + frame = frameList[i]; + if(frame.type === 'byname' && !trans._frameHash[frame.data.name]) { + Lib.warn('animate failure: frame not found: "' + frame.data.name + '"'); + reject(); + return; + } + } + + // If the mode is either next or immediate, then all currently queued frames must + // be dumped and the corresponding .animate promises rejected. + if(['next', 'immediate'].indexOf(animationOpts.mode) !== -1) { + discardExistingFrames(); + } + + if(animationOpts.direction === 'reverse') { + frameList.reverse(); + } + + var currentFrame = gd._fullLayout._currentFrame; + if(currentFrame && animationOpts.fromcurrent) { + var idx = -1; + for(i = 0; i < frameList.length; i++) { + frame = frameList[i]; + if(frame.type === 'byname' && frame.name === currentFrame) { + idx = i; + break; + } + } + + if(idx > 0 && idx < frameList.length - 1) { + var filteredFrameList = []; + for(i = 0; i < frameList.length; i++) { + frame = frameList[i]; + if(frameList[i].type !== 'byname' || i > idx) { + filteredFrameList.push(frame); + } + } + frameList = filteredFrameList; + } + } + + if(frameList.length > 0) { + queueFrames(frameList); + } else { + // This is the case where there were simply no frames. It's a little strange + // since there's not much to do: + gd.emit('plotly_animated'); + resolve(); + } + }); +} + +/** + * Register new frames + * + * @param {string id or DOM element} gd + * the id or DOM element of the graph container div + * + * @param {array of objects} frameList + * list of frame definitions, in which each object includes any of: + * - name: {string} name of frame to add + * - data: {array of objects} trace data + * - layout {object} layout definition + * - traces {array} trace indices + * - baseframe {string} name of frame from which this frame gets defaults + * + * @param {array of integers} indices + * an array of integer indices matching the respective frames in `frameList`. If not + * provided, an index will be provided in serial order. If already used, the frame + * will be overwritten. + */ +function addFrames(gd, frameList, indices) { + gd = Lib.getGraphDiv(gd); + + if(frameList === null || frameList === undefined) { + return Promise.resolve(); + } + + if(!Lib.isPlotDiv(gd)) { + throw new Error( + 'This element is not a Plotly plot: ' + gd + '. It\'s likely that you\'ve failed ' + + 'to create a plot before adding frames. For more details, see ' + + 'https://plot.ly/javascript/animations/' + ); + } + + var i, frame, j, idx; + var _frames = gd._transitionData._frames; + var _frameHash = gd._transitionData._frameHash; + + + if(!Array.isArray(frameList)) { + throw new Error('addFrames failure: frameList must be an Array of frame definitions' + frameList); + } + + // Create a sorted list of insertions since we run into lots of problems if these + // aren't in ascending order of index: + // + // Strictly for sorting. Make sure this is guaranteed to never collide with any + // already-exisisting indices: + var bigIndex = _frames.length + frameList.length * 2; + + var insertions = []; + var _frameHashLocal = {}; + for(i = frameList.length - 1; i >= 0; i--) { + if(!Lib.isPlainObject(frameList[i])) continue; + + // The entire logic for checking for this type of name collision can be removed once we migrate to ES6 and + // use a Map instead of an Object instance, as Map keys aren't converted to strings. + var lookupName = frameList[i].name; + var name = (_frameHash[lookupName] || _frameHashLocal[lookupName] || {}).name; + var newName = frameList[i].name; + var collisionPresent = _frameHash[name] || _frameHashLocal[name]; + + if(name && newName && typeof newName === 'number' && collisionPresent && numericNameWarningCount < numericNameWarningCountLimit) { + numericNameWarningCount++; + + Lib.warn('addFrames: overwriting frame "' + (_frameHash[name] || _frameHashLocal[name]).name + + '" with a frame whose name of type "number" also equates to "' + + name + '". This is valid but may potentially lead to unexpected ' + + 'behavior since all plotly.js frame names are stored internally ' + + 'as strings.'); + + if(numericNameWarningCount === numericNameWarningCountLimit) { + Lib.warn('addFrames: This API call has yielded too many of these warnings. ' + + 'For the rest of this call, further warnings about numeric frame ' + + 'names will be suppressed.'); + } + } + + _frameHashLocal[lookupName] = {name: lookupName}; + + insertions.push({ + frame: Plots.supplyFrameDefaults(frameList[i]), + index: (indices && indices[i] !== undefined && indices[i] !== null) ? indices[i] : bigIndex + i + }); + } + + // Sort this, taking note that undefined insertions end up at the end: + insertions.sort(function(a, b) { + if(a.index > b.index) return -1; + if(a.index < b.index) return 1; + return 0; + }); + + var ops = []; + var revops = []; + var frameCount = _frames.length; + + for(i = insertions.length - 1; i >= 0; i--) { + frame = insertions[i].frame; + + if(typeof frame.name === 'number') { + Lib.warn('Warning: addFrames accepts frames with numeric names, but the numbers are' + + 'implicitly cast to strings'); + } + + if(!frame.name) { + // Repeatedly assign a default name, incrementing the counter each time until + // we get a name that's not in the hashed lookup table: + while(_frameHash[(frame.name = 'frame ' + gd._transitionData._counter++)]); + } + + if(_frameHash[frame.name]) { + // If frame is present, overwrite its definition: + for(j = 0; j < _frames.length; j++) { + if((_frames[j] || {}).name === frame.name) break; + } + ops.push({type: 'replace', index: j, value: frame}); + revops.unshift({type: 'replace', index: j, value: _frames[j]}); + } else { + // Otherwise insert it at the end of the list: + idx = Math.max(0, Math.min(insertions[i].index, frameCount)); + + ops.push({type: 'insert', index: idx, value: frame}); + revops.unshift({type: 'delete', index: idx}); + frameCount++; + } + } + + var undoFunc = Plots.modifyFrames; + var redoFunc = Plots.modifyFrames; + var undoArgs = [gd, revops]; + var redoArgs = [gd, ops]; + + if(Queue) Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); + + return Plots.modifyFrames(gd, ops); +} + +/** + * Delete frame + * + * @param {string id or DOM element} gd + * the id or DOM element of the graph container div + * + * @param {array of integers} frameList + * list of integer indices of frames to be deleted + */ +function deleteFrames(gd, frameList) { + gd = Lib.getGraphDiv(gd); + + if(!Lib.isPlotDiv(gd)) { + throw new Error('This element is not a Plotly plot: ' + gd); + } + + var i, idx; + var _frames = gd._transitionData._frames; + var ops = []; + var revops = []; + + if(!frameList) { + frameList = []; + for(i = 0; i < _frames.length; i++) { + frameList.push(i); + } + } + + frameList = frameList.slice(); + frameList.sort(); + + for(i = frameList.length - 1; i >= 0; i--) { + idx = frameList[i]; + ops.push({type: 'delete', index: idx}); + revops.unshift({type: 'insert', index: idx, value: _frames[idx]}); + } + + var undoFunc = Plots.modifyFrames; + var redoFunc = Plots.modifyFrames; + var undoArgs = [gd, revops]; + var redoArgs = [gd, ops]; + + if(Queue) Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); + + return Plots.modifyFrames(gd, ops); +} + +/** + * Purge a graph container div back to its initial pre-Plotly.plot state + * + * @param {string id or DOM element} gd + * the id or DOM element of the graph container div + */ +function purge(gd) { + gd = Lib.getGraphDiv(gd); + + var fullLayout = gd._fullLayout || {}; + var fullData = gd._fullData || []; + + // remove gl contexts + Plots.cleanPlot([], {}, fullData, fullLayout); + + // purge properties + Plots.purge(gd); + + // purge event emitter methods + Events.purge(gd); + + // remove plot container + if(fullLayout._container) fullLayout._container.remove(); + + // in contrast to Plotly.Plots.purge which does NOT clear _context! + delete gd._context; + + return gd; +} + +// ------------------------------------------------------- +// makePlotFramework: Create the plot container and axes +// ------------------------------------------------------- +function makePlotFramework(gd) { + var gd3 = d3.select(gd); + var fullLayout = gd._fullLayout; + + // Plot container + fullLayout._container = gd3.selectAll('.plot-container').data([0]); + fullLayout._container.enter().insert('div', ':first-child') + .classed('plot-container', true) + .classed('plotly', true); + + // Make the svg container + fullLayout._paperdiv = fullLayout._container.selectAll('.svg-container').data([0]); + fullLayout._paperdiv.enter().append('div') + .classed('svg-container', true) + .style('position', 'relative'); + + // Make the graph containers + // start fresh each time we get here, so we know the order comes out + // right, rather than enter/exit which can muck up the order + // TODO: sort out all the ordering so we don't have to + // explicitly delete anything + // FIXME: parcoords reuses this object, not the best pattern + fullLayout._glcontainer = fullLayout._paperdiv.selectAll('.gl-container') + .data([{}]); + + fullLayout._glcontainer.enter().append('div') + .classed('gl-container', true); + + fullLayout._paperdiv.selectAll('.main-svg').remove(); + fullLayout._paperdiv.select('.modebar-container').remove(); + + fullLayout._paper = fullLayout._paperdiv.insert('svg', ':first-child') + .classed('main-svg', true); + + fullLayout._toppaper = fullLayout._paperdiv.append('svg') + .classed('main-svg', true); + + fullLayout._modebardiv = fullLayout._paperdiv.append('div'); + + fullLayout._hoverpaper = fullLayout._paperdiv.append('svg') + .classed('main-svg', true); + + if(!fullLayout._uid) { + var otherUids = {}; + d3.selectAll('defs').each(function() { + if(this.id) otherUids[this.id.split('-')[1]] = 1; + }); + fullLayout._uid = Lib.randstr(otherUids); + } + + fullLayout._paperdiv.selectAll('.main-svg') + .attr(xmlnsNamespaces.svgAttrs); + + fullLayout._defs = fullLayout._paper.append('defs') + .attr('id', 'defs-' + fullLayout._uid); + + fullLayout._clips = fullLayout._defs.append('g') + .classed('clips', true); + + fullLayout._topdefs = fullLayout._toppaper.append('defs') + .attr('id', 'topdefs-' + fullLayout._uid); + + fullLayout._topclips = fullLayout._topdefs.append('g') + .classed('clips', true); + + fullLayout._bgLayer = fullLayout._paper.append('g') + .classed('bglayer', true); + + fullLayout._draggers = fullLayout._paper.append('g') + .classed('draglayer', true); + + // lower shape/image layer - note that this is behind + // all subplots data/grids but above the backgrounds + // except inset subplots, whose backgrounds are drawn + // inside their own group so that they appear above + // the data for the main subplot + // lower shapes and images which are fully referenced to + // a subplot still get drawn within the subplot's group + // so they will work correctly on insets + var layerBelow = fullLayout._paper.append('g') + .classed('layer-below', true); + fullLayout._imageLowerLayer = layerBelow.append('g') + .classed('imagelayer', true); + fullLayout._shapeLowerLayer = layerBelow.append('g') + .classed('shapelayer', true); + + // single cartesian layer for the whole plot + fullLayout._cartesianlayer = fullLayout._paper.append('g').classed('cartesianlayer', true); + + // single polar layer for the whole plot + fullLayout._polarlayer = fullLayout._paper.append('g').classed('polarlayer', true); + + // single ternary layer for the whole plot + fullLayout._ternarylayer = fullLayout._paper.append('g').classed('ternarylayer', true); + + // single geo layer for the whole plot + fullLayout._geolayer = fullLayout._paper.append('g').classed('geolayer', true); + + // single funnelarea layer for the whole plot + fullLayout._funnelarealayer = fullLayout._paper.append('g').classed('funnelarealayer', true); + + // single pie layer for the whole plot + fullLayout._pielayer = fullLayout._paper.append('g').classed('pielayer', true); + + // single treemap layer for the whole plot + fullLayout._treemaplayer = fullLayout._paper.append('g').classed('treemaplayer', true); + + // single sunburst layer for the whole plot + fullLayout._sunburstlayer = fullLayout._paper.append('g').classed('sunburstlayer', true); + + // single indicator layer for the whole plot + fullLayout._indicatorlayer = fullLayout._toppaper.append('g').classed('indicatorlayer', true); + + // fill in image server scrape-svg + fullLayout._glimages = fullLayout._paper.append('g').classed('glimages', true); + + // lastly upper shapes, info (legend, annotations) and hover layers go on top + // these are in a different svg element normally, but get collapsed into a single + // svg when exporting (after inserting 3D) + // upper shapes/images are only those drawn above the whole plot, including subplots + var layerAbove = fullLayout._toppaper.append('g') + .classed('layer-above', true); + fullLayout._imageUpperLayer = layerAbove.append('g') + .classed('imagelayer', true); + fullLayout._shapeUpperLayer = layerAbove.append('g') + .classed('shapelayer', true); + + fullLayout._infolayer = fullLayout._toppaper.append('g').classed('infolayer', true); + fullLayout._menulayer = fullLayout._toppaper.append('g').classed('menulayer', true); + fullLayout._zoomlayer = fullLayout._toppaper.append('g').classed('zoomlayer', true); + fullLayout._hoverlayer = fullLayout._hoverpaper.append('g').classed('hoverlayer', true); + + // Make the modebar container + fullLayout._modebardiv + .classed('modebar-container', true) + .style('position', 'absolute') + .style('top', '0px') + .style('right', '0px'); + + gd.emit('plotly_framework'); +} + +exports.animate = animate; +exports.addFrames = addFrames; +exports.deleteFrames = deleteFrames; + +exports.addTraces = addTraces; +exports.deleteTraces = deleteTraces; +exports.extendTraces = extendTraces; +exports.moveTraces = moveTraces; +exports.prependTraces = prependTraces; + +exports.newPlot = newPlot; +exports.plot = plot; +exports.purge = purge; + +exports.react = react; +exports.redraw = redraw; +exports.relayout = relayout; +exports.restyle = restyle; + +exports.setPlotConfig = setPlotConfig; + +exports.update = update; + +exports._guiRelayout = guiEdit(relayout); +exports._guiRestyle = guiEdit(restyle); +exports._guiUpdate = guiEdit(update); + +exports._storeDirectGUIEdit = _storeDirectGUIEdit; + +},{"../components/color":51,"../components/drawing":72,"../constants/xmlns_namespaces":150,"../lib":169,"../lib/events":163,"../lib/queue":183,"../lib/svg_text_utils":190,"../plots/cartesian/axes":213,"../plots/cartesian/constants":219,"../plots/cartesian/graph_interact":222,"../plots/cartesian/select":230,"../plots/plots":245,"../plots/polar/legacy":248,"../registry":258,"./edit_types":196,"./helpers":197,"./manage_arrays":199,"./plot_config":201,"./plot_schema":202,"./subroutines":204,"d3":16,"fast-isnumeric":18,"has-hover":20}],201:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +/** + * This will be transferred over to gd and overridden by + * config args to Plotly.plot. + * + * The defaults are the appropriate settings for plotly.js, + * so we get the right experience without any config argument. + * + * N.B. the config options are not coerced using Lib.coerce so keys + * like `valType` and `values` are only set for documentation purposes + * at the moment. + */ + +var configAttributes = { + staticPlot: { + valType: 'boolean', + dflt: false, + + }, + + plotlyServerURL: { + valType: 'string', + dflt: 'https://plot.ly', + + }, + + editable: { + valType: 'boolean', + dflt: false, + + }, + edits: { + annotationPosition: { + valType: 'boolean', + dflt: false, + + }, + annotationTail: { + valType: 'boolean', + dflt: false, + + }, + annotationText: { + valType: 'boolean', + dflt: false, + + }, + axisTitleText: { + valType: 'boolean', + dflt: false, + + }, + colorbarPosition: { + valType: 'boolean', + dflt: false, + + }, + colorbarTitleText: { + valType: 'boolean', + dflt: false, + + }, + legendPosition: { + valType: 'boolean', + dflt: false, + + }, + legendText: { + valType: 'boolean', + dflt: false, + + }, + shapePosition: { + valType: 'boolean', + dflt: false, + + }, + titleText: { + valType: 'boolean', + dflt: false, + + } + }, + + autosizable: { + valType: 'boolean', + dflt: false, + + }, + responsive: { + valType: 'boolean', + dflt: false, + + }, + fillFrame: { + valType: 'boolean', + dflt: false, + + }, + frameMargins: { + valType: 'number', + dflt: 0, + min: 0, + max: 0.5, + + }, + + scrollZoom: { + valType: 'flaglist', + flags: ['cartesian', 'gl3d', 'geo', 'mapbox'], + extras: [true, false], + dflt: 'gl3d+geo+mapbox', + + }, + doubleClick: { + valType: 'enumerated', + values: [false, 'reset', 'autosize', 'reset+autosize'], + dflt: 'reset+autosize', + + }, + doubleClickDelay: { + valType: 'number', + dflt: 300, + min: 0, + + }, + + showAxisDragHandles: { + valType: 'boolean', + dflt: true, + + }, + showAxisRangeEntryBoxes: { + valType: 'boolean', + dflt: true, + + }, + + showTips: { + valType: 'boolean', + dflt: true, + + }, + + showLink: { + valType: 'boolean', + dflt: false, + + }, + linkText: { + valType: 'string', + dflt: 'Edit chart', + noBlank: true, + + }, + sendData: { + valType: 'boolean', + dflt: true, + + }, + showSources: { + valType: 'any', + dflt: false, + + }, + + displayModeBar: { + valType: 'enumerated', + values: ['hover', true, false], + dflt: 'hover', + + }, + showSendToCloud: { + valType: 'boolean', + dflt: false, + + }, + showEditInChartStudio: { + valType: 'boolean', + dflt: false, + + }, + modeBarButtonsToRemove: { + valType: 'any', + dflt: [], + + }, + modeBarButtonsToAdd: { + valType: 'any', + dflt: [], + + }, + modeBarButtons: { + valType: 'any', + dflt: false, + + }, + toImageButtonOptions: { + valType: 'any', + dflt: {}, + + }, + displaylogo: { + valType: 'boolean', + dflt: true, + + }, + watermark: { + valType: 'boolean', + dflt: false, + + }, + + plotGlPixelRatio: { + valType: 'number', + dflt: 2, + min: 1, + max: 4, + + }, + + setBackground: { + valType: 'any', + dflt: 'transparent', + + }, + + topojsonURL: { + valType: 'string', + noBlank: true, + dflt: 'https://cdn.plot.ly/', + + }, + + mapboxAccessToken: { + valType: 'string', + dflt: null, + + }, + + logging: { + valType: 'boolean', + dflt: 1, + + }, + + queueLength: { + valType: 'integer', + min: 0, + dflt: 0, + + }, + + globalTransforms: { + valType: 'any', + dflt: [], + + }, + + locale: { + valType: 'string', + dflt: 'en-US', + + }, + + locales: { + valType: 'any', + dflt: {}, + + } +}; + +var dfltConfig = {}; + +function crawl(src, target) { + for(var k in src) { + var obj = src[k]; + if(obj.valType) { + target[k] = obj.dflt; + } else { + if(!target[k]) { + target[k] = {}; + } + crawl(obj, target[k]); + } + } +} + +crawl(configAttributes, dfltConfig); + +module.exports = { + configAttributes: configAttributes, + dfltConfig: dfltConfig +}; + +},{}],202:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Registry = _dereq_('../registry'); +var Lib = _dereq_('../lib'); + +var baseAttributes = _dereq_('../plots/attributes'); +var baseLayoutAttributes = _dereq_('../plots/layout_attributes'); +var frameAttributes = _dereq_('../plots/frame_attributes'); +var animationAttributes = _dereq_('../plots/animation_attributes'); +var configAttributes = _dereq_('./plot_config').configAttributes; + +// polar attributes are not part of the Registry yet +var polarAreaAttrs = _dereq_('../plots/polar/legacy/area_attributes'); +var polarAxisAttrs = _dereq_('../plots/polar/legacy/axis_attributes'); + +var editTypes = _dereq_('./edit_types'); + +var extendFlat = Lib.extendFlat; +var extendDeepAll = Lib.extendDeepAll; +var isPlainObject = Lib.isPlainObject; +var isArrayOrTypedArray = Lib.isArrayOrTypedArray; +var nestedProperty = Lib.nestedProperty; +var valObjectMeta = Lib.valObjectMeta; + +var IS_SUBPLOT_OBJ = '_isSubplotObj'; +var IS_LINKED_TO_ARRAY = '_isLinkedToArray'; +var ARRAY_ATTR_REGEXPS = '_arrayAttrRegexps'; +var DEPRECATED = '_deprecated'; +var UNDERSCORE_ATTRS = [IS_SUBPLOT_OBJ, IS_LINKED_TO_ARRAY, ARRAY_ATTR_REGEXPS, DEPRECATED]; + +exports.IS_SUBPLOT_OBJ = IS_SUBPLOT_OBJ; +exports.IS_LINKED_TO_ARRAY = IS_LINKED_TO_ARRAY; +exports.DEPRECATED = DEPRECATED; +exports.UNDERSCORE_ATTRS = UNDERSCORE_ATTRS; + +/** Outputs the full plotly.js plot schema + * + * @return {object} + * - defs + * - traces + * - layout + * - transforms + * - frames + * - animations + * - config + */ +exports.get = function() { + var traces = {}; + + Registry.allTypes.concat('area').forEach(function(type) { + traces[type] = getTraceAttributes(type); + }); + + var transforms = {}; + + Object.keys(Registry.transformsRegistry).forEach(function(type) { + transforms[type] = getTransformAttributes(type); + }); + + return { + defs: { + valObjects: valObjectMeta, + metaKeys: UNDERSCORE_ATTRS.concat(['description', 'role', 'editType', 'impliedEdits']), + editType: { + traces: editTypes.traces, + layout: editTypes.layout + }, + impliedEdits: { + + } + }, + + traces: traces, + layout: getLayoutAttributes(), + + transforms: transforms, + + frames: getFramesAttributes(), + animation: formatAttributes(animationAttributes), + + config: formatAttributes(configAttributes) + }; +}; + +/** + * Crawl the attribute tree, recursively calling a callback function + * + * @param {object} attrs + * The node of the attribute tree (e.g. the root) from which recursion originates + * @param {Function} callback + * A callback function with the signature: + * @callback callback + * @param {object} attr an attribute + * @param {String} attrName name string + * @param {object[]} attrs all the attributes + * @param {Number} level the recursion level, 0 at the root + * @param {String} fullAttrString full attribute name (ie 'marker.line') + * @param {Number} [specifiedLevel] + * The level in the tree, in order to let the callback function detect descend or backtrack, + * typically unsupplied (implied 0), just used by the self-recursive call. + * The necessity arises because the tree traversal is not controlled by callback return values. + * The decision to not use callback return values for controlling tree pruning arose from + * the goal of keeping the crawler backwards compatible. Observe that one of the pruning conditions + * precedes the callback call. + * @param {string} [attrString] + * the path to the current attribute, as an attribute string (ie 'marker.line') + * typically unsupplied, but you may supply it if you want to disambiguate which attrs tree you + * are starting from + * + * @return {object} transformOut + * copy of transformIn that contains attribute defaults + */ +exports.crawl = function(attrs, callback, specifiedLevel, attrString) { + var level = specifiedLevel || 0; + attrString = attrString || ''; + + Object.keys(attrs).forEach(function(attrName) { + var attr = attrs[attrName]; + + if(UNDERSCORE_ATTRS.indexOf(attrName) !== -1) return; + + var fullAttrString = (attrString ? attrString + '.' : '') + attrName; + callback(attr, attrName, attrs, level, fullAttrString); + + if(exports.isValObject(attr)) return; + + if(isPlainObject(attr) && attrName !== 'impliedEdits') { + exports.crawl(attr, callback, level + 1, fullAttrString); + } + }); +}; + +/** Is object a value object (or a container object)? + * + * @param {object} obj + * @return {boolean} + * returns true for a valid value object and + * false for tree nodes in the attribute hierarchy + */ +exports.isValObject = function(obj) { + return obj && obj.valType !== undefined; +}; + +/** + * Find all data array attributes in a given trace object - including + * `arrayOk` attributes. + * + * @param {object} trace + * full trace object that contains a reference to `_module.attributes` + * + * @return {array} arrayAttributes + * list of array attributes for the given trace + */ +exports.findArrayAttributes = function(trace) { + var arrayAttributes = []; + var stack = []; + var isArrayStack = []; + var baseContainer, baseAttrName; + + function callback(attr, attrName, attrs, level) { + stack = stack.slice(0, level).concat([attrName]); + isArrayStack = isArrayStack.slice(0, level).concat([attr && attr._isLinkedToArray]); + + var splittableAttr = ( + attr && + (attr.valType === 'data_array' || attr.arrayOk === true) && + !(stack[level - 1] === 'colorbar' && (attrName === 'ticktext' || attrName === 'tickvals')) + ); + + // Manually exclude 'colorbar.tickvals' and 'colorbar.ticktext' for now + // which are declared as `valType: 'data_array'` but scale independently of + // the coordinate arrays. + // + // Down the road, we might want to add a schema field (e.g `uncorrelatedArray: true`) + // to distinguish attributes of the likes. + + if(!splittableAttr) return; + + crawlIntoTrace(baseContainer, 0, ''); + } + + function crawlIntoTrace(container, i, astrPartial) { + var item = container[stack[i]]; + var newAstrPartial = astrPartial + stack[i]; + if(i === stack.length - 1) { + if(isArrayOrTypedArray(item)) { + arrayAttributes.push(baseAttrName + newAstrPartial); + } + } else { + if(isArrayStack[i]) { + if(Array.isArray(item)) { + for(var j = 0; j < item.length; j++) { + if(isPlainObject(item[j])) { + crawlIntoTrace(item[j], i + 1, newAstrPartial + '[' + j + '].'); + } + } + } + } else if(isPlainObject(item)) { + crawlIntoTrace(item, i + 1, newAstrPartial + '.'); + } + } + } + + baseContainer = trace; + baseAttrName = ''; + exports.crawl(baseAttributes, callback); + if(trace._module && trace._module.attributes) { + exports.crawl(trace._module.attributes, callback); + } + + var transforms = trace.transforms; + if(transforms) { + for(var i = 0; i < transforms.length; i++) { + var transform = transforms[i]; + var module = transform._module; + + if(module) { + baseAttrName = 'transforms[' + i + '].'; + baseContainer = transform; + + exports.crawl(module.attributes, callback); + } + } + } + + return arrayAttributes; +}; + +/* + * Find the valObject for one attribute in an existing trace + * + * @param {object} trace + * full trace object that contains a reference to `_module.attributes` + * @param {object} parts + * an array of parts, like ['transforms', 1, 'value'] + * typically from nestedProperty(...).parts + * + * @return {object|false} + * the valObject for this attribute, or the last found parent + * in some cases the innermost valObject will not exist, for example + * `valType: 'any'` attributes where we might set a part of the attribute. + * In that case, stop at the deepest valObject we *do* find. + */ +exports.getTraceValObject = function(trace, parts) { + var head = parts[0]; + var i = 1; // index to start recursing from + var moduleAttrs, valObject; + + if(head === 'transforms') { + if(parts.length === 1) { + return baseAttributes.transforms; + } + var transforms = trace.transforms; + if(!Array.isArray(transforms) || !transforms.length) return false; + var tNum = parts[1]; + if(!isIndex(tNum) || tNum >= transforms.length) { + return false; + } + moduleAttrs = (Registry.transformsRegistry[transforms[tNum].type] || {}).attributes; + valObject = moduleAttrs && moduleAttrs[parts[2]]; + i = 3; // start recursing only inside the transform + } else if(trace.type === 'area') { + valObject = polarAreaAttrs[head]; + } else { + // first look in the module for this trace + // components have already merged their trace attributes in here + var _module = trace._module; + if(!_module) _module = (Registry.modules[trace.type || baseAttributes.type.dflt] || {})._module; + if(!_module) return false; + + moduleAttrs = _module.attributes; + valObject = moduleAttrs && moduleAttrs[head]; + + // then look in the subplot attributes + if(!valObject) { + var subplotModule = _module.basePlotModule; + if(subplotModule && subplotModule.attributes) { + valObject = subplotModule.attributes[head]; + } + } + + // finally look in the global attributes + if(!valObject) valObject = baseAttributes[head]; + } + + return recurseIntoValObject(valObject, parts, i); +}; + +/* + * Find the valObject for one layout attribute + * + * @param {array} parts + * an array of parts, like ['annotations', 1, 'x'] + * typically from nestedProperty(...).parts + * + * @return {object|false} + * the valObject for this attribute, or the last found parent + * in some cases the innermost valObject will not exist, for example + * `valType: 'any'` attributes where we might set a part of the attribute. + * In that case, stop at the deepest valObject we *do* find. + */ +exports.getLayoutValObject = function(fullLayout, parts) { + var valObject = layoutHeadAttr(fullLayout, parts[0]); + + return recurseIntoValObject(valObject, parts, 1); +}; + +function layoutHeadAttr(fullLayout, head) { + var i, key, _module, attributes; + + // look for attributes of the subplot types used on the plot + var basePlotModules = fullLayout._basePlotModules; + if(basePlotModules) { + var out; + for(i = 0; i < basePlotModules.length; i++) { + _module = basePlotModules[i]; + if(_module.attrRegex && _module.attrRegex.test(head)) { + // if a module defines overrides, these take precedence + // initially this is to allow gl2d different editTypes from svg cartesian + if(_module.layoutAttrOverrides) return _module.layoutAttrOverrides; + + // otherwise take the first attributes we find + if(!out && _module.layoutAttributes) out = _module.layoutAttributes; + } + + // a module can also override the behavior of base (and component) module layout attrs + // again see gl2d for initial use case + var baseOverrides = _module.baseLayoutAttrOverrides; + if(baseOverrides && head in baseOverrides) return baseOverrides[head]; + } + if(out) return out; + } + + // look for layout attributes contributed by traces on the plot + var modules = fullLayout._modules; + if(modules) { + for(i = 0; i < modules.length; i++) { + attributes = modules[i].layoutAttributes; + if(attributes && head in attributes) { + return attributes[head]; + } + } + } + + /* + * Next look in components. + * Components that define a schema have already merged this into + * base and subplot attribute defs, so ignore these. + * Others (older style) all put all their attributes + * inside a container matching the module `name` + * eg `attributes` (array) or `legend` (object) + */ + for(key in Registry.componentsRegistry) { + _module = Registry.componentsRegistry[key]; + if(_module.name === 'colorscale' && head.indexOf('coloraxis') === 0) { + return _module.layoutAttributes[head]; + } else if(!_module.schema && (head === _module.name)) { + return _module.layoutAttributes; + } + } + + if(head in baseLayoutAttributes) return baseLayoutAttributes[head]; + + // Polar doesn't populate _modules or _basePlotModules + // just fall back on these when the others fail + if(head === 'radialaxis' || head === 'angularaxis') { + return polarAxisAttrs[head]; + } + return polarAxisAttrs.layout[head] || false; +} + +function recurseIntoValObject(valObject, parts, i) { + if(!valObject) return false; + + if(valObject._isLinkedToArray) { + // skip array index, abort if we try to dive into an array without an index + if(isIndex(parts[i])) i++; + else if(i < parts.length) return false; + } + + // now recurse as far as we can. Occasionally we have an attribute + // setting an internal part below what's in the schema; just return + // the innermost schema item we find. + for(; i < parts.length; i++) { + var newValObject = valObject[parts[i]]; + if(isPlainObject(newValObject)) valObject = newValObject; + else break; + + if(i === parts.length - 1) break; + + if(valObject._isLinkedToArray) { + i++; + if(!isIndex(parts[i])) return false; + } else if(valObject.valType === 'info_array') { + i++; + var index = parts[i]; + if(!isIndex(index)) return false; + + var items = valObject.items; + if(Array.isArray(items)) { + if(index >= items.length) return false; + if(valObject.dimensions === 2) { + i++; + if(parts.length === i) return valObject; + var index2 = parts[i]; + if(!isIndex(index2)) return false; + valObject = items[index][index2]; + } else valObject = items[index]; + } else { + valObject = items; + } + } + } + + return valObject; +} + +// note: this is different from Lib.isIndex, this one doesn't accept numeric +// strings, only actual numbers. +function isIndex(val) { + return val === Math.round(val) && val >= 0; +} + +function getTraceAttributes(type) { + var _module, basePlotModule; + + if(type === 'area') { + _module = { attributes: polarAreaAttrs }; + basePlotModule = {}; + } else { + _module = Registry.modules[type]._module, + basePlotModule = _module.basePlotModule; + } + + var attributes = {}; + + // make 'type' the first attribute in the object + attributes.type = null; + + var copyBaseAttributes = extendDeepAll({}, baseAttributes); + var copyModuleAttributes = extendDeepAll({}, _module.attributes); + + // prune global-level trace attributes that are already defined in a trace + exports.crawl(copyModuleAttributes, function(attr, attrName, attrs, level, fullAttrString) { + nestedProperty(copyBaseAttributes, fullAttrString).set(undefined); + // Prune undefined attributes + if(attr === undefined) nestedProperty(copyModuleAttributes, fullAttrString).set(undefined); + }); + + // base attributes (same for all trace types) + extendDeepAll(attributes, copyBaseAttributes); + + // prune-out base attributes based on trace module categories + if(Registry.traceIs(type, 'noOpacity')) { + delete attributes.opacity; + } + if(!Registry.traceIs(type, 'showLegend')) { + delete attributes.showlegend; + delete attributes.legendgroup; + } + if(Registry.traceIs(type, 'noHover')) { + delete attributes.hoverinfo; + delete attributes.hoverlabel; + } + if(!_module.selectPoints) { + delete attributes.selectedpoints; + } + + // module attributes + extendDeepAll(attributes, copyModuleAttributes); + + // subplot attributes + if(basePlotModule.attributes) { + extendDeepAll(attributes, basePlotModule.attributes); + } + + // 'type' gets overwritten by baseAttributes; reset it here + attributes.type = type; + + var out = { + meta: _module.meta || {}, + categories: _module.categories || {}, + animatable: Boolean(_module.animatable), + type: type, + attributes: formatAttributes(attributes), + }; + + // trace-specific layout attributes + if(_module.layoutAttributes) { + var layoutAttributes = {}; + + extendDeepAll(layoutAttributes, _module.layoutAttributes); + out.layoutAttributes = formatAttributes(layoutAttributes); + } + + // drop anim:true in non-animatable modules + if(!_module.animatable) { + exports.crawl(out, function(attr) { + if(exports.isValObject(attr) && 'anim' in attr) { + delete attr.anim; + } + }); + } + + return out; +} + +function getLayoutAttributes() { + var layoutAttributes = {}; + var key, _module; + + // global layout attributes + extendDeepAll(layoutAttributes, baseLayoutAttributes); + + // add base plot module layout attributes + for(key in Registry.subplotsRegistry) { + _module = Registry.subplotsRegistry[key]; + + if(!_module.layoutAttributes) continue; + + if(Array.isArray(_module.attr)) { + for(var i = 0; i < _module.attr.length; i++) { + handleBasePlotModule(layoutAttributes, _module, _module.attr[i]); + } + } else { + var astr = _module.attr === 'subplot' ? _module.name : _module.attr; + handleBasePlotModule(layoutAttributes, _module, astr); + } + } + + // polar layout attributes + layoutAttributes = assignPolarLayoutAttrs(layoutAttributes); + + // add registered components layout attributes + for(key in Registry.componentsRegistry) { + _module = Registry.componentsRegistry[key]; + var schema = _module.schema; + + if(schema && (schema.subplots || schema.layout)) { + /* + * Components with defined schema have already been merged in at register time + * but a few components define attributes that apply only to xaxis + * not yaxis (rangeselector, rangeslider) - delete from y schema. + * Note that the input attributes for xaxis/yaxis are the same object + * so it's not possible to only add them to xaxis from the start. + * If we ever have such asymmetry the other way, or anywhere else, + * we will need to extend both this code and mergeComponentAttrsToSubplot + * (which will not find yaxis only for example) + */ + var subplots = schema.subplots; + if(subplots && subplots.xaxis && !subplots.yaxis) { + for(var xkey in subplots.xaxis) { + delete layoutAttributes.yaxis[xkey]; + } + } + } else if(_module.name === 'colorscale') { + extendDeepAll(layoutAttributes, _module.layoutAttributes); + } else if(_module.layoutAttributes) { + // older style without schema need to be explicitly merged in now + insertAttrs(layoutAttributes, _module.layoutAttributes, _module.name); + } + } + + return { + layoutAttributes: formatAttributes(layoutAttributes) + }; +} + +function getTransformAttributes(type) { + var _module = Registry.transformsRegistry[type]; + var attributes = extendDeepAll({}, _module.attributes); + + // add registered components transform attributes + Object.keys(Registry.componentsRegistry).forEach(function(k) { + var _module = Registry.componentsRegistry[k]; + + if(_module.schema && _module.schema.transforms && _module.schema.transforms[type]) { + Object.keys(_module.schema.transforms[type]).forEach(function(v) { + insertAttrs(attributes, _module.schema.transforms[type][v], v); + }); + } + }); + + return { + attributes: formatAttributes(attributes) + }; +} + +function getFramesAttributes() { + var attrs = { + frames: extendDeepAll({}, frameAttributes) + }; + + formatAttributes(attrs); + + return attrs.frames; +} + +function formatAttributes(attrs) { + mergeValTypeAndRole(attrs); + formatArrayContainers(attrs); + stringify(attrs); + + return attrs; +} + +function mergeValTypeAndRole(attrs) { + function makeSrcAttr(attrName) { + return { + valType: 'string', + + + editType: 'none' + }; + } + + function callback(attr, attrName, attrs) { + if(exports.isValObject(attr)) { + if(attr.valType === 'data_array') { + // all 'data_array' attrs have role 'data' + attr.role = 'data'; + // all 'data_array' attrs have a corresponding 'src' attr + attrs[attrName + 'src'] = makeSrcAttr(attrName); + } else if(attr.arrayOk === true) { + // all 'arrayOk' attrs have a corresponding 'src' attr + attrs[attrName + 'src'] = makeSrcAttr(attrName); + } + } else if(isPlainObject(attr)) { + // all attrs container objects get role 'object' + attr.role = 'object'; + } + } + + exports.crawl(attrs, callback); +} + +function formatArrayContainers(attrs) { + function callback(attr, attrName, attrs) { + if(!attr) return; + + var itemName = attr[IS_LINKED_TO_ARRAY]; + + if(!itemName) return; + + delete attr[IS_LINKED_TO_ARRAY]; + + attrs[attrName] = { items: {} }; + attrs[attrName].items[itemName] = attr; + attrs[attrName].role = 'object'; + } + + exports.crawl(attrs, callback); +} + +// this can take around 10ms and should only be run from PlotSchema.get(), +// to ensure JSON.stringify(PlotSchema.get()) gives the intended result. +function stringify(attrs) { + function walk(attr) { + for(var k in attr) { + if(isPlainObject(attr[k])) { + walk(attr[k]); + } else if(Array.isArray(attr[k])) { + for(var i = 0; i < attr[k].length; i++) { + walk(attr[k][i]); + } + } else { + // as JSON.stringify(/test/) // => {} + if(attr[k] instanceof RegExp) { + attr[k] = attr[k].toString(); + } + } + } + } + + walk(attrs); +} + +function assignPolarLayoutAttrs(layoutAttributes) { + extendFlat(layoutAttributes, { + radialaxis: polarAxisAttrs.radialaxis, + angularaxis: polarAxisAttrs.angularaxis + }); + + extendFlat(layoutAttributes, polarAxisAttrs.layout); + + return layoutAttributes; +} + +function handleBasePlotModule(layoutAttributes, _module, astr) { + var np = nestedProperty(layoutAttributes, astr); + var attrs = extendDeepAll({}, _module.layoutAttributes); + + attrs[IS_SUBPLOT_OBJ] = true; + np.set(attrs); +} + +function insertAttrs(baseAttrs, newAttrs, astr) { + var np = nestedProperty(baseAttrs, astr); + + np.set(extendDeepAll(np.get() || {}, newAttrs)); +} + +},{"../lib":169,"../plots/animation_attributes":208,"../plots/attributes":210,"../plots/frame_attributes":240,"../plots/layout_attributes":243,"../plots/polar/legacy/area_attributes":246,"../plots/polar/legacy/axis_attributes":247,"../registry":258,"./edit_types":196,"./plot_config":201}],203:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Lib = _dereq_('../lib'); +var plotAttributes = _dereq_('../plots/attributes'); + +var TEMPLATEITEMNAME = 'templateitemname'; + +var templateAttrs = { + name: { + valType: 'string', + + editType: 'none', + + } +}; +templateAttrs[TEMPLATEITEMNAME] = { + valType: 'string', + + editType: 'calc', + +}; + +/** + * templatedArray: decorate an attributes object with templating (and array) + * properties. + * + * @param {string} name: the singular form of the array name. Sets + * `_isLinkedToArray` to this, so the schema knows to treat this as an array. + * @param {object} attrs: the item attributes. Since all callers are expected + * to be constructing this object on the spot, we mutate it here for + * performance, rather than extending a new object with it. + * + * @returns {object}: the decorated `attrs` object + */ +exports.templatedArray = function(name, attrs) { + attrs._isLinkedToArray = name; + attrs.name = templateAttrs.name; + attrs[TEMPLATEITEMNAME] = templateAttrs[TEMPLATEITEMNAME]; + return attrs; +}; + +/** + * traceTemplater: logic for matching traces to trace templates + * + * @param {object} dataTemplate: collection of {traceType: [{template}, ...]} + * ie each type the template applies to contains a list of template objects, + * to be provided cyclically to data traces of that type. + * + * @returns {object}: {newTrace}, a function: + * newTrace(traceIn): that takes the input traceIn, coerces its type, then + * uses that type to find the next template to apply. returns the output + * traceOut with template attached, ready to continue supplyDefaults. + */ +exports.traceTemplater = function(dataTemplate) { + var traceCounts = {}; + var traceType, typeTemplates; + + for(traceType in dataTemplate) { + typeTemplates = dataTemplate[traceType]; + if(Array.isArray(typeTemplates) && typeTemplates.length) { + traceCounts[traceType] = 0; + } + } + + function newTrace(traceIn) { + traceType = Lib.coerce(traceIn, {}, plotAttributes, 'type'); + var traceOut = {type: traceType, _template: null}; + if(traceType in traceCounts) { + typeTemplates = dataTemplate[traceType]; + // cycle through traces in the template set for this type + var typei = traceCounts[traceType] % typeTemplates.length; + traceCounts[traceType]++; + traceOut._template = typeTemplates[typei]; + } else { + // TODO: anything we should do for types missing from the template? + // try to apply some other type? Or just bail as we do here? + // Actually I think yes, we should apply other types; would be nice + // if all scatter* could inherit from each other, and if histogram + // could inherit from bar, etc... but how to specify this? And do we + // compose them, or if a type is present require it to be complete? + // Actually this could apply to layout too - 3D annotations + // inheriting from 2D, axes of different types inheriting from each + // other... + } + return traceOut; + } + + return { + newTrace: newTrace + // TODO: function to figure out what's left & what didn't work + }; +}; + +/** + * newContainer: Create a new sub-container inside `container` and propagate any + * applicable template to it. If there's no template, still propagates + * `undefined` so relinkPrivate will not retain an old template! + * + * @param {object} container: the outer container, should already have _template + * if there *is* a template for this plot + * @param {string} name: the key of the new container to make + * @param {string} baseName: if applicable, a base attribute to take the + * template from, ie for xaxis3 the base would be xaxis + * + * @returns {object}: an object for inclusion _full*, empty except for the + * appropriate template piece + */ +exports.newContainer = function(container, name, baseName) { + var template = container._template; + var part = template && (template[name] || (baseName && template[baseName])); + if(!Lib.isPlainObject(part)) part = null; + + var out = container[name] = {_template: part}; + return out; +}; + +/** + * arrayTemplater: special logic for templating both defaults and specific items + * in a container array (annotations etc) + * + * @param {object} container: the outer container, should already have _template + * if there *is* a template for this plot + * @param {string} name: the name of the array to template (ie 'annotations') + * will be used to find default ('annotationdefaults' object) and specific + * ('annotations' array) template specs. + * @param {string} inclusionAttr: the attribute determining this item's + * inclusion in the output, usually 'visible' or 'enabled' + * + * @returns {object}: {newItem, defaultItems}, both functions: + * newItem(itemIn): create an output item, bare except for the correct + * template and name(s), as the base for supplyDefaults + * defaultItems(): to be called after all newItem calls, return any + * specific template items that have not already beeen included, + * also as bare output items ready for supplyDefaults. + */ +exports.arrayTemplater = function(container, name, inclusionAttr) { + var template = container._template; + var defaultsTemplate = template && template[arrayDefaultKey(name)]; + var templateItems = template && template[name]; + if(!Array.isArray(templateItems) || !templateItems.length) { + templateItems = []; + } + + var usedNames = {}; + + function newItem(itemIn) { + // include name and templateitemname in the output object for ALL + // container array items. Note: you could potentially use different + // name and templateitemname, if you're using one template to make + // another template. templateitemname would be the name in the original + // template, and name is the new "subclassed" item name. + var out = {name: itemIn.name, _input: itemIn}; + var templateItemName = out[TEMPLATEITEMNAME] = itemIn[TEMPLATEITEMNAME]; + + // no itemname: use the default template + if(!validItemName(templateItemName)) { + out._template = defaultsTemplate; + return out; + } + + // look for an item matching this itemname + // note these do not inherit from the default template, only the item. + for(var i = 0; i < templateItems.length; i++) { + var templateItem = templateItems[i]; + if(templateItem.name === templateItemName) { + // Note: it's OK to use a template item more than once + // but using it at least once will stop it from generating + // a default item at the end. + usedNames[templateItemName] = 1; + out._template = templateItem; + return out; + } + } + + // Didn't find a matching template item, so since this item is intended + // to only be modifications it's most likely broken. Hide it unless + // it's explicitly marked visible - in which case it gets NO template, + // not even the default. + out[inclusionAttr] = itemIn[inclusionAttr] || false; + // special falsy value we can look for in validateTemplate + out._template = false; + return out; + } + + function defaultItems() { + var out = []; + for(var i = 0; i < templateItems.length; i++) { + var templateItem = templateItems[i]; + var name = templateItem.name; + // only allow named items to be added as defaults, + // and only allow each name once + if(validItemName(name) && !usedNames[name]) { + var outi = { + _template: templateItem, + name: name, + _input: {_templateitemname: name} + }; + outi[TEMPLATEITEMNAME] = templateItem[TEMPLATEITEMNAME]; + out.push(outi); + usedNames[name] = 1; + } + } + return out; + } + + return { + newItem: newItem, + defaultItems: defaultItems + }; +}; + +function validItemName(name) { + return name && typeof name === 'string'; +} + +function arrayDefaultKey(name) { + var lastChar = name.length - 1; + if(name.charAt(lastChar) !== 's') { + Lib.warn('bad argument to arrayDefaultKey: ' + name); + } + return name.substr(0, name.length - 1) + 'defaults'; +} +exports.arrayDefaultKey = arrayDefaultKey; + +/** + * arrayEditor: helper for editing array items that may have come from + * template defaults (in which case they will not exist in the input yet) + * + * @param {object} parentIn: the input container (eg gd.layout) + * @param {string} containerStr: the attribute string for the container inside + * `parentIn`. + * @param {object} itemOut: the _full* item (eg gd._fullLayout.annotations[0]) + * that we'll be editing. Assumed to have been created by `arrayTemplater`. + * + * @returns {object}: {modifyBase, modifyItem, getUpdateObj, applyUpdate}, all functions: + * modifyBase(attr, value): Add an update that's *not* related to the item. + * `attr` is the full attribute string. + * modifyItem(attr, value): Add an update to the item. `attr` is just the + * portion of the attribute string inside the item. + * getUpdateObj(): Get the final constructed update object, to use in + * `restyle` or `relayout`. Also resets the update object in case this + * update was canceled. + * applyUpdate(attr, value): optionally add an update `attr: value`, + * then apply it to `parent` which should be the parent of `containerIn`, + * ie the object to which `containerStr` is the attribute string. + */ +exports.arrayEditor = function(parentIn, containerStr, itemOut) { + var lengthIn = (Lib.nestedProperty(parentIn, containerStr).get() || []).length; + var index = itemOut._index; + // Check that we are indeed off the end of this container. + // Otherwise a devious user could put a key `_templateitemname` in their + // own input and break lots of things. + var templateItemName = (index >= lengthIn) && (itemOut._input || {})._templateitemname; + if(templateItemName) index = lengthIn; + var itemStr = containerStr + '[' + index + ']'; + + var update; + function resetUpdate() { + update = {}; + if(templateItemName) { + update[itemStr] = {}; + update[itemStr][TEMPLATEITEMNAME] = templateItemName; + } + } + resetUpdate(); + + function modifyBase(attr, value) { + update[attr] = value; + } + + function modifyItem(attr, value) { + if(templateItemName) { + // we're making a new object: edit that object + Lib.nestedProperty(update[itemStr], attr).set(value); + } else { + // we're editing an existing object: include *just* the edit + update[itemStr + '.' + attr] = value; + } + } + + function getUpdateObj() { + var updateOut = update; + resetUpdate(); + return updateOut; + } + + function applyUpdate(attr, value) { + if(attr) modifyItem(attr, value); + var updateToApply = getUpdateObj(); + for(var key in updateToApply) { + Lib.nestedProperty(parentIn, key).set(updateToApply[key]); + } + } + + return { + modifyBase: modifyBase, + modifyItem: modifyItem, + getUpdateObj: getUpdateObj, + applyUpdate: applyUpdate + }; +}; + +},{"../lib":169,"../plots/attributes":210}],204:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); +var Registry = _dereq_('../registry'); +var Plots = _dereq_('../plots/plots'); + +var Lib = _dereq_('../lib'); +var clearGlCanvases = _dereq_('../lib/clear_gl_canvases'); + +var Color = _dereq_('../components/color'); +var Drawing = _dereq_('../components/drawing'); +var Titles = _dereq_('../components/titles'); +var ModeBar = _dereq_('../components/modebar'); + +var Axes = _dereq_('../plots/cartesian/axes'); +var alignmentConstants = _dereq_('../constants/alignment'); +var axisConstraints = _dereq_('../plots/cartesian/constraints'); +var enforceAxisConstraints = axisConstraints.enforce; +var cleanAxisConstraints = axisConstraints.clean; +var doAutoRange = _dereq_('../plots/cartesian/autorange').doAutoRange; + +var SVG_TEXT_ANCHOR_START = 'start'; +var SVG_TEXT_ANCHOR_MIDDLE = 'middle'; +var SVG_TEXT_ANCHOR_END = 'end'; + +exports.layoutStyles = function(gd) { + return Lib.syncOrAsync([Plots.doAutoMargin, lsInner], gd); +}; + +function overlappingDomain(xDomain, yDomain, domains) { + for(var i = 0; i < domains.length; i++) { + var existingX = domains[i][0]; + var existingY = domains[i][1]; + + if(existingX[0] >= xDomain[1] || existingX[1] <= xDomain[0]) { + continue; + } + if(existingY[0] < yDomain[1] && existingY[1] > yDomain[0]) { + return true; + } + } + return false; +} + +function lsInner(gd) { + var fullLayout = gd._fullLayout; + var gs = fullLayout._size; + var pad = gs.p; + var axList = Axes.list(gd, '', true); + var i, subplot, plotinfo, ax, xa, ya; + + fullLayout._paperdiv.style({ + width: (gd._context.responsive && fullLayout.autosize && !gd._context._hasZeroWidth && !gd.layout.width) ? '100%' : fullLayout.width + 'px', + height: (gd._context.responsive && fullLayout.autosize && !gd._context._hasZeroHeight && !gd.layout.height) ? '100%' : fullLayout.height + 'px' + }) + .selectAll('.main-svg') + .call(Drawing.setSize, fullLayout.width, fullLayout.height); + gd._context.setBackground(gd, fullLayout.paper_bgcolor); + + exports.drawMainTitle(gd); + ModeBar.manage(gd); + + // _has('cartesian') means SVG specifically, not GL2D - but GL2D + // can still get here because it makes some of the SVG structure + // for shared features like selections. + if(!fullLayout._has('cartesian')) { + return Plots.previousPromises(gd); + } + + function getLinePosition(ax, counterAx, side) { + var lwHalf = ax._lw / 2; + + if(ax._id.charAt(0) === 'x') { + if(!counterAx) return gs.t + gs.h * (1 - (ax.position || 0)) + (lwHalf % 1); + else if(side === 'top') return counterAx._offset - pad - lwHalf; + return counterAx._offset + counterAx._length + pad + lwHalf; + } + + if(!counterAx) return gs.l + gs.w * (ax.position || 0) + (lwHalf % 1); + else if(side === 'right') return counterAx._offset + counterAx._length + pad + lwHalf; + return counterAx._offset - pad - lwHalf; + } + + // some preparation of axis position info + for(i = 0; i < axList.length; i++) { + ax = axList[i]; + + var counterAx = ax._anchorAxis; + + // clear axis line positions, to be set in the subplot loop below + ax._linepositions = {}; + + // stash crispRounded linewidth so we don't need to pass gd all over the place + ax._lw = Drawing.crispRound(gd, ax.linewidth, 1); + + // figure out the main axis line and main mirror line position. + // it's easier to follow the logic if we handle these separately from + // ax._linepositions, which are only used by mirror=allticks + // for non-main-subplot ticks, and mirror=all(ticks)? for zero line + // hiding logic + ax._mainLinePosition = getLinePosition(ax, counterAx, ax.side); + ax._mainMirrorPosition = (ax.mirror && counterAx) ? + getLinePosition(ax, counterAx, + alignmentConstants.OPPOSITE_SIDE[ax.side]) : null; + } + + // figure out which backgrounds we need to draw, + // and in which layers to put them + var lowerBackgroundIDs = []; + var backgroundIds = []; + var lowerDomains = []; + // no need to draw background when paper and plot color are the same color, + // activate mode just for large splom (which benefit the most from this + // optimization), but this could apply to all cartesian subplots. + var noNeedForBg = ( + Color.opacity(fullLayout.paper_bgcolor) === 1 && + Color.opacity(fullLayout.plot_bgcolor) === 1 && + fullLayout.paper_bgcolor === fullLayout.plot_bgcolor + ); + + for(subplot in fullLayout._plots) { + plotinfo = fullLayout._plots[subplot]; + + if(plotinfo.mainplot) { + // mainplot is a reference to the main plot this one is overlaid on + // so if it exists, this is an overlaid plot and we don't need to + // give it its own background + if(plotinfo.bg) { + plotinfo.bg.remove(); + } + plotinfo.bg = undefined; + } else { + var xDomain = plotinfo.xaxis.domain; + var yDomain = plotinfo.yaxis.domain; + var plotgroup = plotinfo.plotgroup; + + if(overlappingDomain(xDomain, yDomain, lowerDomains)) { + var pgNode = plotgroup.node(); + var plotgroupBg = plotinfo.bg = Lib.ensureSingle(plotgroup, 'rect', 'bg'); + pgNode.insertBefore(plotgroupBg.node(), pgNode.childNodes[0]); + backgroundIds.push(subplot); + } else { + plotgroup.select('rect.bg').remove(); + lowerDomains.push([xDomain, yDomain]); + if(!noNeedForBg) { + lowerBackgroundIDs.push(subplot); + backgroundIds.push(subplot); + } + } + } + } + + // now create all the lower-layer backgrounds at once now that + // we have the list of subplots that need them + var lowerBackgrounds = fullLayout._bgLayer.selectAll('.bg') + .data(lowerBackgroundIDs); + + lowerBackgrounds.enter().append('rect') + .classed('bg', true); + + lowerBackgrounds.exit().remove(); + + lowerBackgrounds.each(function(subplot) { + fullLayout._plots[subplot].bg = d3.select(this); + }); + + // style all backgrounds + for(i = 0; i < backgroundIds.length; i++) { + plotinfo = fullLayout._plots[backgroundIds[i]]; + xa = plotinfo.xaxis; + ya = plotinfo.yaxis; + + if(plotinfo.bg) { + plotinfo.bg + .call(Drawing.setRect, + xa._offset - pad, ya._offset - pad, + xa._length + 2 * pad, ya._length + 2 * pad) + .call(Color.fill, fullLayout.plot_bgcolor) + .style('stroke-width', 0); + } + } + + if(!fullLayout._hasOnlyLargeSploms) { + for(subplot in fullLayout._plots) { + plotinfo = fullLayout._plots[subplot]; + xa = plotinfo.xaxis; + ya = plotinfo.yaxis; + + // Clip so that data only shows up on the plot area. + var clipId = plotinfo.clipId = 'clip' + fullLayout._uid + subplot + 'plot'; + + var plotClip = Lib.ensureSingleById(fullLayout._clips, 'clipPath', clipId, function(s) { + s.classed('plotclip', true) + .append('rect'); + }); + + plotinfo.clipRect = plotClip.select('rect').attr({ + width: xa._length, + height: ya._length + }); + + Drawing.setTranslate(plotinfo.plot, xa._offset, ya._offset); + + var plotClipId; + var layerClipId; + + if(plotinfo._hasClipOnAxisFalse) { + plotClipId = null; + layerClipId = clipId; + } else { + plotClipId = clipId; + layerClipId = null; + } + + Drawing.setClipUrl(plotinfo.plot, plotClipId, gd); + + // stash layer clipId value (null or same as clipId) + // to DRY up Drawing.setClipUrl calls on trace-module and trace layers + // downstream + plotinfo.layerClipId = layerClipId; + } + } + + var xLinesXLeft, xLinesXRight, xLinesYBottom, xLinesYTop, + leftYLineWidth, rightYLineWidth; + var yLinesYBottom, yLinesYTop, yLinesXLeft, yLinesXRight, + connectYBottom, connectYTop; + var extraSubplot; + + function xLinePath(y) { + return 'M' + xLinesXLeft + ',' + y + 'H' + xLinesXRight; + } + + function xLinePathFree(y) { + return 'M' + xa._offset + ',' + y + 'h' + xa._length; + } + + function yLinePath(x) { + return 'M' + x + ',' + yLinesYTop + 'V' + yLinesYBottom; + } + + function yLinePathFree(x) { + return 'M' + x + ',' + ya._offset + 'v' + ya._length; + } + + function mainPath(ax, pathFn, pathFnFree) { + if(!ax.showline || subplot !== ax._mainSubplot) return ''; + if(!ax._anchorAxis) return pathFnFree(ax._mainLinePosition); + var out = pathFn(ax._mainLinePosition); + if(ax.mirror) out += pathFn(ax._mainMirrorPosition); + return out; + } + + for(subplot in fullLayout._plots) { + plotinfo = fullLayout._plots[subplot]; + xa = plotinfo.xaxis; + ya = plotinfo.yaxis; + + /* + * x lines get longer where they meet y lines, to make a crisp corner. + * The x lines get the padding (margin.pad) plus the y line width to + * fill up the corner nicely. Free x lines are excluded - they always + * span exactly the data area of the plot + * + * | XXXXX + * | XXXXX + * | + * +------ + * x1 + * ----- + * x2 + */ + var xPath = 'M0,0'; + if(shouldShowLinesOrTicks(xa, subplot)) { + leftYLineWidth = findCounterAxisLineWidth(xa, 'left', ya, axList); + xLinesXLeft = xa._offset - (leftYLineWidth ? (pad + leftYLineWidth) : 0); + rightYLineWidth = findCounterAxisLineWidth(xa, 'right', ya, axList); + xLinesXRight = xa._offset + xa._length + (rightYLineWidth ? (pad + rightYLineWidth) : 0); + xLinesYBottom = getLinePosition(xa, ya, 'bottom'); + xLinesYTop = getLinePosition(xa, ya, 'top'); + + // save axis line positions for extra ticks to reference + // each subplot that gets ticks from "allticks" gets an entry: + // [left or bottom, right or top] + extraSubplot = (!xa._anchorAxis || subplot !== xa._mainSubplot); + if(extraSubplot && (xa.mirror === 'allticks' || xa.mirror === 'all')) { + xa._linepositions[subplot] = [xLinesYBottom, xLinesYTop]; + } + + xPath = mainPath(xa, xLinePath, xLinePathFree); + if(extraSubplot && xa.showline && (xa.mirror === 'all' || xa.mirror === 'allticks')) { + xPath += xLinePath(xLinesYBottom) + xLinePath(xLinesYTop); + } + + plotinfo.xlines + .style('stroke-width', xa._lw + 'px') + .call(Color.stroke, xa.showline ? + xa.linecolor : 'rgba(0,0,0,0)'); + } + plotinfo.xlines.attr('d', xPath); + + /* + * y lines that meet x axes get longer only by margin.pad, because + * the x axes fill in the corner space. Free y axes, like free x axes, + * always span exactly the data area of the plot + * + * | | XXXX + * y2| y1| XXXX + * | | XXXX + * | + * +----- + */ + var yPath = 'M0,0'; + if(shouldShowLinesOrTicks(ya, subplot)) { + connectYBottom = findCounterAxisLineWidth(ya, 'bottom', xa, axList); + yLinesYBottom = ya._offset + ya._length + (connectYBottom ? pad : 0); + connectYTop = findCounterAxisLineWidth(ya, 'top', xa, axList); + yLinesYTop = ya._offset - (connectYTop ? pad : 0); + yLinesXLeft = getLinePosition(ya, xa, 'left'); + yLinesXRight = getLinePosition(ya, xa, 'right'); + + extraSubplot = (!ya._anchorAxis || subplot !== ya._mainSubplot); + if(extraSubplot && (ya.mirror === 'allticks' || ya.mirror === 'all')) { + ya._linepositions[subplot] = [yLinesXLeft, yLinesXRight]; + } + + yPath = mainPath(ya, yLinePath, yLinePathFree); + if(extraSubplot && ya.showline && (ya.mirror === 'all' || ya.mirror === 'allticks')) { + yPath += yLinePath(yLinesXLeft) + yLinePath(yLinesXRight); + } + + plotinfo.ylines + .style('stroke-width', ya._lw + 'px') + .call(Color.stroke, ya.showline ? + ya.linecolor : 'rgba(0,0,0,0)'); + } + plotinfo.ylines.attr('d', yPath); + } + + Axes.makeClipPaths(gd); + + return Plots.previousPromises(gd); +} + +function shouldShowLinesOrTicks(ax, subplot) { + return (ax.ticks || ax.showline) && + (subplot === ax._mainSubplot || ax.mirror === 'all' || ax.mirror === 'allticks'); +} + +/* + * should we draw a line on counterAx at this side of ax? + * It's assumed that counterAx is known to overlay the subplot we're working on + * but it may not be its main axis. + */ +function shouldShowLineThisSide(ax, side, counterAx) { + // does counterAx get a line at all? + if(!counterAx.showline || !counterAx._lw) return false; + + // are we drawing *all* lines for counterAx? + if(counterAx.mirror === 'all' || counterAx.mirror === 'allticks') return true; + + var anchorAx = counterAx._anchorAxis; + + // is this a free axis? free axes can only have a subplot side-line with all(ticks)? mirroring + if(!anchorAx) return false; + + // in order to handle cases where the user forgot to anchor this axis correctly + // (because its default anchor has the same domain on the relevant end) + // check whether the relevant position is the same. + var sideIndex = alignmentConstants.FROM_BL[side]; + if(counterAx.side === side) { + return anchorAx.domain[sideIndex] === ax.domain[sideIndex]; + } + return counterAx.mirror && anchorAx.domain[1 - sideIndex] === ax.domain[1 - sideIndex]; +} + +/* + * Is there another axis intersecting `side` end of `ax`? + * First look at `counterAx` (the axis for this subplot), + * then at all other potential counteraxes on or overlaying this subplot. + * Take the line width from the first one that has a line. + */ +function findCounterAxisLineWidth(ax, side, counterAx, axList) { + if(shouldShowLineThisSide(ax, side, counterAx)) { + return counterAx._lw; + } + for(var i = 0; i < axList.length; i++) { + var axi = axList[i]; + if(axi._mainAxis === counterAx._mainAxis && shouldShowLineThisSide(ax, side, axi)) { + return axi._lw; + } + } + return 0; +} + +exports.drawMainTitle = function(gd) { + var fullLayout = gd._fullLayout; + + var textAnchor = getMainTitleTextAnchor(fullLayout); + var dy = getMainTitleDy(fullLayout); + + Titles.draw(gd, 'gtitle', { + propContainer: fullLayout, + propName: 'title.text', + placeholder: fullLayout._dfltTitle.plot, + attributes: { + x: getMainTitleX(fullLayout, textAnchor), + y: getMainTitleY(fullLayout, dy), + 'text-anchor': textAnchor, + dy: dy + } + }); +}; + +function getMainTitleX(fullLayout, textAnchor) { + var title = fullLayout.title; + var gs = fullLayout._size; + var hPadShift = 0; + + if(textAnchor === SVG_TEXT_ANCHOR_START) { + hPadShift = title.pad.l; + } else if(textAnchor === SVG_TEXT_ANCHOR_END) { + hPadShift = -title.pad.r; + } + + switch(title.xref) { + case 'paper': + return gs.l + gs.w * title.x + hPadShift; + case 'container': + default: + return fullLayout.width * title.x + hPadShift; + } +} + +function getMainTitleY(fullLayout, dy) { + var title = fullLayout.title; + var gs = fullLayout._size; + var vPadShift = 0; + + if(dy === '0em' || !dy) { + vPadShift = -title.pad.b; + } else if(dy === alignmentConstants.CAP_SHIFT + 'em') { + vPadShift = title.pad.t; + } + + if(title.y === 'auto') { + return gs.t / 2; + } else { + switch(title.yref) { + case 'paper': + return gs.t + gs.h - gs.h * title.y + vPadShift; + case 'container': + default: + return fullLayout.height - fullLayout.height * title.y + vPadShift; + } + } +} + +function getMainTitleTextAnchor(fullLayout) { + var title = fullLayout.title; + + var textAnchor = SVG_TEXT_ANCHOR_MIDDLE; + if(Lib.isRightAnchor(title)) { + textAnchor = SVG_TEXT_ANCHOR_END; + } else if(Lib.isLeftAnchor(title)) { + textAnchor = SVG_TEXT_ANCHOR_START; + } + + return textAnchor; +} + +function getMainTitleDy(fullLayout) { + var title = fullLayout.title; + + var dy = '0em'; + if(Lib.isTopAnchor(title)) { + dy = alignmentConstants.CAP_SHIFT + 'em'; + } else if(Lib.isMiddleAnchor(title)) { + dy = alignmentConstants.MID_SHIFT + 'em'; + } + + return dy; +} + +exports.doTraceStyle = function(gd) { + var calcdata = gd.calcdata; + var editStyleCalls = []; + var i; + + for(i = 0; i < calcdata.length; i++) { + var cd = calcdata[i]; + var cd0 = cd[0] || {}; + var trace = cd0.trace || {}; + var _module = trace._module || {}; + + // See if we need to do arraysToCalcdata + // call it regardless of what change we made, in case + // supplyDefaults brought in an array that was already + // in gd.data but not in gd._fullData previously + var arraysToCalcdata = _module.arraysToCalcdata; + if(arraysToCalcdata) arraysToCalcdata(cd, trace); + + var editStyle = _module.editStyle; + if(editStyle) editStyleCalls.push({fn: editStyle, cd0: cd0}); + } + + if(editStyleCalls.length) { + for(i = 0; i < editStyleCalls.length; i++) { + var edit = editStyleCalls[i]; + edit.fn(gd, edit.cd0); + } + clearGlCanvases(gd); + exports.redrawReglTraces(gd); + } + + Plots.style(gd); + Registry.getComponentMethod('legend', 'draw')(gd); + + return Plots.previousPromises(gd); +}; + +exports.doColorBars = function(gd) { + Registry.getComponentMethod('colorbar', 'draw')(gd); + return Plots.previousPromises(gd); +}; + +// force plot() to redo the layout and replot with the modified layout +exports.layoutReplot = function(gd) { + var layout = gd.layout; + gd.layout = undefined; + return Registry.call('plot', gd, '', layout); +}; + +exports.doLegend = function(gd) { + Registry.getComponentMethod('legend', 'draw')(gd); + return Plots.previousPromises(gd); +}; + +exports.doTicksRelayout = function(gd) { + Axes.draw(gd, 'redraw'); + + if(gd._fullLayout._hasOnlyLargeSploms) { + Registry.subplotsRegistry.splom.updateGrid(gd); + clearGlCanvases(gd); + exports.redrawReglTraces(gd); + } + + exports.drawMainTitle(gd); + return Plots.previousPromises(gd); +}; + +exports.doModeBar = function(gd) { + var fullLayout = gd._fullLayout; + + ModeBar.manage(gd); + + for(var i = 0; i < fullLayout._basePlotModules.length; i++) { + var updateFx = fullLayout._basePlotModules[i].updateFx; + if(updateFx) updateFx(gd); + } + + return Plots.previousPromises(gd); +}; + +exports.doCamera = function(gd) { + var fullLayout = gd._fullLayout; + var sceneIds = fullLayout._subplots.gl3d; + + for(var i = 0; i < sceneIds.length; i++) { + var sceneLayout = fullLayout[sceneIds[i]]; + var scene = sceneLayout._scene; + + scene.setViewport(sceneLayout); + } +}; + +exports.drawData = function(gd) { + var fullLayout = gd._fullLayout; + + clearGlCanvases(gd); + + // loop over the base plot modules present on graph + var basePlotModules = fullLayout._basePlotModules; + for(var i = 0; i < basePlotModules.length; i++) { + basePlotModules[i].plot(gd); + } + + exports.redrawReglTraces(gd); + + // styling separate from drawing + Plots.style(gd); + + // draw components that can be drawn on axes, + // and that do not push the margins + Registry.getComponentMethod('shapes', 'draw')(gd); + Registry.getComponentMethod('annotations', 'draw')(gd); + Registry.getComponentMethod('images', 'draw')(gd); + + // Mark the first render as complete + fullLayout._replotting = false; + + return Plots.previousPromises(gd); +}; + +// Draw (or redraw) all regl-based traces in one go, +// useful during drag and selection where buffers of targeted traces are updated, +// but all traces need to be redrawn following clearGlCanvases. +// +// Note that _module.plot for regl trace does NOT draw things +// on the canvas, they only update the buffers. +// Drawing is perform here. +// +// TODO try adding per-subplot option using gl.SCISSOR_TEST for +// non-overlaying, disjoint subplots. +// +// TODO try to include parcoords in here. +// https://github.com/plotly/plotly.js/issues/3069 +exports.redrawReglTraces = function(gd) { + var fullLayout = gd._fullLayout; + + if(fullLayout._has('regl')) { + var fullData = gd._fullData; + var cartesianIds = []; + var polarIds = []; + var i, sp; + + if(fullLayout._hasOnlyLargeSploms) { + fullLayout._splomGrid.draw(); + } + + // N.B. + // - Loop over fullData (not _splomScenes) to preserve splom trace-to-trace ordering + // - Fill list if subplot ids (instead of fullLayout._subplots) to handle cases where all traces + // of a given module are `visible !== true` + for(i = 0; i < fullData.length; i++) { + var trace = fullData[i]; + + if(trace.visible === true && trace._length !== 0) { + if(trace.type === 'splom') { + fullLayout._splomScenes[trace.uid].draw(); + } else if(trace.type === 'scattergl') { + Lib.pushUnique(cartesianIds, trace.xaxis + trace.yaxis); + } else if(trace.type === 'scatterpolargl') { + Lib.pushUnique(polarIds, trace.subplot); + } + } + } + + for(i = 0; i < cartesianIds.length; i++) { + sp = fullLayout._plots[cartesianIds[i]]; + if(sp._scene) sp._scene.draw(); + } + + for(i = 0; i < polarIds.length; i++) { + sp = fullLayout[polarIds[i]]._subplot; + if(sp._scene) sp._scene.draw(); + } + } +}; + +exports.doAutoRangeAndConstraints = function(gd) { + var fullLayout = gd._fullLayout; + var axList = Axes.list(gd, '', true); + var matchGroups = fullLayout._axisMatchGroups || []; + var ax; + var axRng; + + for(var i = 0; i < axList.length; i++) { + ax = axList[i]; + cleanAxisConstraints(gd, ax); + doAutoRange(gd, ax); + } + + enforceAxisConstraints(gd); + + groupLoop: + for(var j = 0; j < matchGroups.length; j++) { + var group = matchGroups[j]; + var rng = null; + var id; + + for(id in group) { + ax = Axes.getFromId(gd, id); + if(ax.autorange === false) continue groupLoop; + + axRng = Lib.simpleMap(ax.range, ax.r2l); + if(rng) { + if(rng[0] < rng[1]) { + rng[0] = Math.min(rng[0], axRng[0]); + rng[1] = Math.max(rng[1], axRng[1]); + } else { + rng[0] = Math.max(rng[0], axRng[0]); + rng[1] = Math.min(rng[1], axRng[1]); + } + } else { + rng = axRng; + } + } + + for(id in group) { + ax = Axes.getFromId(gd, id); + ax.range = Lib.simpleMap(rng, ax.l2r); + ax._input.range = ax.range.slice(); + ax.setScale(); + } + } +}; + +// An initial paint must be completed before these components can be +// correctly sized and the whole plot re-margined. fullLayout._replotting must +// be set to false before these will work properly. +exports.finalDraw = function(gd) { + // TODO: rangesliders really belong in marginPushers but they need to be + // drawn after data - can we at least get the margin pushing part separated + // out and done earlier? + Registry.getComponentMethod('rangeslider', 'draw')(gd); + // TODO: rangeselector only needs to be here (in addition to drawMarginPushers) + // because the margins need to be fully determined before we can call + // autorange and update axis ranges (which rangeselector needs to know which + // button is active). Can we break out its automargin step from its draw step? + Registry.getComponentMethod('rangeselector', 'draw')(gd); +}; + +exports.drawMarginPushers = function(gd) { + Registry.getComponentMethod('legend', 'draw')(gd); + Registry.getComponentMethod('rangeselector', 'draw')(gd); + Registry.getComponentMethod('sliders', 'draw')(gd); + Registry.getComponentMethod('updatemenus', 'draw')(gd); + Registry.getComponentMethod('colorbar', 'draw')(gd); +}; + +},{"../components/color":51,"../components/drawing":72,"../components/modebar":109,"../components/titles":138,"../constants/alignment":145,"../lib":169,"../lib/clear_gl_canvases":158,"../plots/cartesian/autorange":212,"../plots/cartesian/axes":213,"../plots/cartesian/constraints":220,"../plots/plots":245,"../registry":258,"d3":16}],205:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Lib = _dereq_('../lib'); +var isPlainObject = Lib.isPlainObject; +var PlotSchema = _dereq_('./plot_schema'); +var Plots = _dereq_('../plots/plots'); +var plotAttributes = _dereq_('../plots/attributes'); +var Template = _dereq_('./plot_template'); +var dfltConfig = _dereq_('./plot_config').dfltConfig; + +/** + * Plotly.makeTemplate: create a template off an existing figure to reuse + * style attributes on other figures. + * + * Note: separated from the rest of templates because otherwise we get circular + * references due to PlotSchema. + * + * @param {object|DOM element|string} figure: The figure to base the template on + * should contain a trace array `figure.data` + * and a layout object `figure.layout` + * @returns {object} template: the extracted template - can then be used as + * `layout.template` in another figure. + */ +exports.makeTemplate = function(figure) { + figure = Lib.isPlainObject(figure) ? figure : Lib.getGraphDiv(figure); + figure = Lib.extendDeep({_context: dfltConfig}, {data: figure.data, layout: figure.layout}); + Plots.supplyDefaults(figure); + var data = figure.data || []; + var layout = figure.layout || {}; + // copy over a few items to help follow the schema + layout._basePlotModules = figure._fullLayout._basePlotModules; + layout._modules = figure._fullLayout._modules; + + var template = { + data: {}, + layout: {} + }; + + /* + * Note: we do NOT validate template values, we just take what's in the + * user inputs data and layout, not the validated values in fullData and + * fullLayout. Even if we were to validate here, there's no guarantee that + * these values would still be valid when applied to a new figure, which + * may contain different trace modes, different axes, etc. So it's + * important that when applying a template we still validate the template + * values, rather than just using them as defaults. + */ + + data.forEach(function(trace) { + // TODO: What if no style info is extracted for this trace. We may + // not want an empty object as the null value. + // TODO: allow transforms to contribute to templates? + // as it stands they are ignored, which may be for the best... + + var traceTemplate = {}; + walkStyleKeys(trace, traceTemplate, getTraceInfo.bind(null, trace)); + + var traceType = Lib.coerce(trace, {}, plotAttributes, 'type'); + var typeTemplates = template.data[traceType]; + if(!typeTemplates) typeTemplates = template.data[traceType] = []; + typeTemplates.push(traceTemplate); + }); + + walkStyleKeys(layout, template.layout, getLayoutInfo.bind(null, layout)); + + /* + * Compose the new template with an existing one to the same effect + * + * NOTE: there's a possibility of slightly different behavior: if the plot + * has an invalid value and the old template has a valid value for the same + * attribute, the plot will use the old template value but this routine + * will pull the invalid value (resulting in the original default). + * In the general case it's not possible to solve this with a single value, + * since valid options can be context-dependent. It could be solved with + * a *list* of values, but that would be huge complexity for little gain. + */ + delete template.layout.template; + var oldTemplate = layout.template; + if(isPlainObject(oldTemplate)) { + var oldLayoutTemplate = oldTemplate.layout; + + var i, traceType, oldTypeTemplates, oldTypeLen, typeTemplates, typeLen; + + if(isPlainObject(oldLayoutTemplate)) { + mergeTemplates(oldLayoutTemplate, template.layout); + } + var oldDataTemplate = oldTemplate.data; + if(isPlainObject(oldDataTemplate)) { + for(traceType in template.data) { + oldTypeTemplates = oldDataTemplate[traceType]; + if(Array.isArray(oldTypeTemplates)) { + typeTemplates = template.data[traceType]; + typeLen = typeTemplates.length; + oldTypeLen = oldTypeTemplates.length; + for(i = 0; i < typeLen; i++) { + mergeTemplates(oldTypeTemplates[i % oldTypeLen], typeTemplates[i]); + } + for(i = typeLen; i < oldTypeLen; i++) { + typeTemplates.push(Lib.extendDeep({}, oldTypeTemplates[i])); + } + } + } + for(traceType in oldDataTemplate) { + if(!(traceType in template.data)) { + template.data[traceType] = Lib.extendDeep([], oldDataTemplate[traceType]); + } + } + } + } + + return template; +}; + +function mergeTemplates(oldTemplate, newTemplate) { + // we don't care about speed here, just make sure we have a totally + // distinct object from the previous template + oldTemplate = Lib.extendDeep({}, oldTemplate); + + // sort keys so we always get annotationdefaults before annotations etc + // so arrayTemplater will work right + var oldKeys = Object.keys(oldTemplate).sort(); + var i, j; + + function mergeOne(oldVal, newVal, key) { + if(isPlainObject(newVal) && isPlainObject(oldVal)) { + mergeTemplates(oldVal, newVal); + } else if(Array.isArray(newVal) && Array.isArray(oldVal)) { + // Note: omitted `inclusionAttr` from arrayTemplater here, + // it's irrelevant as we only want the resulting `_template`. + var templater = Template.arrayTemplater({_template: oldTemplate}, key); + for(j = 0; j < newVal.length; j++) { + var item = newVal[j]; + var oldItem = templater.newItem(item)._template; + if(oldItem) mergeTemplates(oldItem, item); + } + var defaultItems = templater.defaultItems(); + for(j = 0; j < defaultItems.length; j++) newVal.push(defaultItems[j]._template); + + // templateitemname only applies to receiving plots + for(j = 0; j < newVal.length; j++) delete newVal[j].templateitemname; + } + } + + for(i = 0; i < oldKeys.length; i++) { + var key = oldKeys[i]; + var oldVal = oldTemplate[key]; + if(key in newTemplate) { + mergeOne(oldVal, newTemplate[key], key); + } else newTemplate[key] = oldVal; + + // if this is a base key from the old template (eg xaxis), look for + // extended keys (eg xaxis2) in the new template to merge into + if(getBaseKey(key) === key) { + for(var key2 in newTemplate) { + var baseKey2 = getBaseKey(key2); + if(key2 !== baseKey2 && baseKey2 === key && !(key2 in oldTemplate)) { + mergeOne(oldVal, newTemplate[key2], key); + } + } + } + } +} + +function getBaseKey(key) { + return key.replace(/[0-9]+$/, ''); +} + +function walkStyleKeys(parent, templateOut, getAttributeInfo, path, basePath) { + var pathAttr = basePath && getAttributeInfo(basePath); + for(var key in parent) { + var child = parent[key]; + var nextPath = getNextPath(parent, key, path); + var nextBasePath = getNextPath(parent, key, basePath); + var attr = getAttributeInfo(nextBasePath); + if(!attr) { + var baseKey = getBaseKey(key); + if(baseKey !== key) { + nextBasePath = getNextPath(parent, baseKey, basePath); + attr = getAttributeInfo(nextBasePath); + } + } + + // we'll get an attr if path starts with a valid part, then has an + // invalid ending. Make sure we got all the way to the end. + if(pathAttr && (pathAttr === attr)) continue; + + if(!attr || attr._noTemplating || + attr.valType === 'data_array' || + (attr.arrayOk && Array.isArray(child)) + ) { + continue; + } + + if(!attr.valType && isPlainObject(child)) { + walkStyleKeys(child, templateOut, getAttributeInfo, nextPath, nextBasePath); + } else if(attr._isLinkedToArray && Array.isArray(child)) { + var dfltDone = false; + var namedIndex = 0; + var usedNames = {}; + for(var i = 0; i < child.length; i++) { + var item = child[i]; + if(isPlainObject(item)) { + var name = item.name; + if(name) { + if(!usedNames[name]) { + // named array items: allow all attributes except data arrays + walkStyleKeys(item, templateOut, getAttributeInfo, + getNextPath(child, namedIndex, nextPath), + getNextPath(child, namedIndex, nextBasePath)); + namedIndex++; + usedNames[name] = 1; + } + } else if(!dfltDone) { + var dfltKey = Template.arrayDefaultKey(key); + var dfltPath = getNextPath(parent, dfltKey, path); + + // getAttributeInfo will fail if we try to use dfltKey directly. + // Instead put this item into the next array element, then + // pull it out and move it to dfltKey. + var pathInArray = getNextPath(child, namedIndex, nextPath); + walkStyleKeys(item, templateOut, getAttributeInfo, pathInArray, + getNextPath(child, namedIndex, nextBasePath)); + var itemPropInArray = Lib.nestedProperty(templateOut, pathInArray); + var dfltProp = Lib.nestedProperty(templateOut, dfltPath); + dfltProp.set(itemPropInArray.get()); + itemPropInArray.set(null); + + dfltDone = true; + } + } + } + } else { + var templateProp = Lib.nestedProperty(templateOut, nextPath); + templateProp.set(child); + } + } +} + +function getLayoutInfo(layout, path) { + return PlotSchema.getLayoutValObject( + layout, Lib.nestedProperty({}, path).parts + ); +} + +function getTraceInfo(trace, path) { + return PlotSchema.getTraceValObject( + trace, Lib.nestedProperty({}, path).parts + ); +} + +function getNextPath(parent, key, path) { + var nextPath; + if(!path) nextPath = key; + else if(Array.isArray(parent)) nextPath = path + '[' + key + ']'; + else nextPath = path + '.' + key; + + return nextPath; +} + +/** + * validateTemplate: Test for consistency between the given figure and + * a template, either already included in the figure or given separately. + * Note that not every issue we identify here is necessarily a problem, + * it depends on what you're using the template for. + * + * @param {object|DOM element} figure: the plot, with {data, layout} members, + * to test the template against + * @param {Optional(object)} template: the template, with its own {data, layout}, + * to test. If omitted, we will look for a template already attached as the + * plot's `layout.template` attribute. + * + * @returns {array} array of error objects each containing: + * - {string} code + * error code ('missing', 'unused', 'reused', 'noLayout', 'noData') + * - {string} msg + * a full readable description of the issue. + */ +exports.validateTemplate = function(figureIn, template) { + var figure = Lib.extendDeep({}, { + _context: dfltConfig, + data: figureIn.data, + layout: figureIn.layout + }); + var layout = figure.layout || {}; + if(!isPlainObject(template)) template = layout.template || {}; + var layoutTemplate = template.layout; + var dataTemplate = template.data; + var errorList = []; + + figure.layout = layout; + figure.layout.template = template; + Plots.supplyDefaults(figure); + + var fullLayout = figure._fullLayout; + var fullData = figure._fullData; + + var layoutPaths = {}; + function crawlLayoutForContainers(obj, paths) { + for(var key in obj) { + if(key.charAt(0) !== '_' && isPlainObject(obj[key])) { + var baseKey = getBaseKey(key); + var nextPaths = []; + var i; + for(i = 0; i < paths.length; i++) { + nextPaths.push(getNextPath(obj, key, paths[i])); + if(baseKey !== key) nextPaths.push(getNextPath(obj, baseKey, paths[i])); + } + for(i = 0; i < nextPaths.length; i++) { + layoutPaths[nextPaths[i]] = 1; + } + crawlLayoutForContainers(obj[key], nextPaths); + } + } + } + + function crawlLayoutTemplateForContainers(obj, path) { + for(var key in obj) { + if(key.indexOf('defaults') === -1 && isPlainObject(obj[key])) { + var nextPath = getNextPath(obj, key, path); + if(layoutPaths[nextPath]) { + crawlLayoutTemplateForContainers(obj[key], nextPath); + } else { + errorList.push({code: 'unused', path: nextPath}); + } + } + } + } + + if(!isPlainObject(layoutTemplate)) { + errorList.push({code: 'layout'}); + } else { + crawlLayoutForContainers(fullLayout, ['layout']); + crawlLayoutTemplateForContainers(layoutTemplate, 'layout'); + } + + if(!isPlainObject(dataTemplate)) { + errorList.push({code: 'data'}); + } else { + var typeCount = {}; + var traceType; + for(var i = 0; i < fullData.length; i++) { + var fullTrace = fullData[i]; + traceType = fullTrace.type; + typeCount[traceType] = (typeCount[traceType] || 0) + 1; + if(!fullTrace._fullInput._template) { + // this takes care of the case of traceType in the data but not + // the template + errorList.push({ + code: 'missing', + index: fullTrace._fullInput.index, + traceType: traceType + }); + } + } + for(traceType in dataTemplate) { + var templateCount = dataTemplate[traceType].length; + var dataCount = typeCount[traceType] || 0; + if(templateCount > dataCount) { + errorList.push({ + code: 'unused', + traceType: traceType, + templateCount: templateCount, + dataCount: dataCount + }); + } else if(dataCount > templateCount) { + errorList.push({ + code: 'reused', + traceType: traceType, + templateCount: templateCount, + dataCount: dataCount + }); + } + } + } + + // _template: false is when someone tried to modify an array item + // but there was no template with matching name + function crawlForMissingTemplates(obj, path) { + for(var key in obj) { + if(key.charAt(0) === '_') continue; + var val = obj[key]; + var nextPath = getNextPath(obj, key, path); + if(isPlainObject(val)) { + if(Array.isArray(obj) && val._template === false && val.templateitemname) { + errorList.push({ + code: 'missing', + path: nextPath, + templateitemname: val.templateitemname + }); + } + crawlForMissingTemplates(val, nextPath); + } else if(Array.isArray(val) && hasPlainObject(val)) { + crawlForMissingTemplates(val, nextPath); + } + } + } + crawlForMissingTemplates({data: fullData, layout: fullLayout}, ''); + + if(errorList.length) return errorList.map(format); +}; + +function hasPlainObject(arr) { + for(var i = 0; i < arr.length; i++) { + if(isPlainObject(arr[i])) return true; + } +} + +function format(opts) { + var msg; + switch(opts.code) { + case 'data': + msg = 'The template has no key data.'; + break; + case 'layout': + msg = 'The template has no key layout.'; + break; + case 'missing': + if(opts.path) { + msg = 'There are no templates for item ' + opts.path + + ' with name ' + opts.templateitemname; + } else { + msg = 'There are no templates for trace ' + opts.index + + ', of type ' + opts.traceType + '.'; + } + break; + case 'unused': + if(opts.path) { + msg = 'The template item at ' + opts.path + + ' was not used in constructing the plot.'; + } else if(opts.dataCount) { + msg = 'Some of the templates of type ' + opts.traceType + + ' were not used. The template has ' + opts.templateCount + + ' traces, the data only has ' + opts.dataCount + + ' of this type.'; + } else { + msg = 'The template has ' + opts.templateCount + + ' traces of type ' + opts.traceType + + ' but there are none in the data.'; + } + break; + case 'reused': + msg = 'Some of the templates of type ' + opts.traceType + + ' were used more than once. The template has ' + + opts.templateCount + ' traces, the data has ' + + opts.dataCount + ' of this type.'; + break; + } + opts.msg = msg; + + return opts; +} + +},{"../lib":169,"../plots/attributes":210,"../plots/plots":245,"./plot_config":201,"./plot_schema":202,"./plot_template":203}],206:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); + +var plotApi = _dereq_('./plot_api'); +var Lib = _dereq_('../lib'); + +var helpers = _dereq_('../snapshot/helpers'); +var toSVG = _dereq_('../snapshot/tosvg'); +var svgToImg = _dereq_('../snapshot/svgtoimg'); + +var attrs = { + format: { + valType: 'enumerated', + values: ['png', 'jpeg', 'webp', 'svg'], + dflt: 'png', + + }, + width: { + valType: 'number', + min: 1, + + }, + height: { + valType: 'number', + min: 1, + + }, + scale: { + valType: 'number', + min: 0, + dflt: 1, + + }, + setBackground: { + valType: 'any', + dflt: false, + + }, + imageDataOnly: { + valType: 'boolean', + dflt: false, + + } +}; + +/** Plotly.toImage + * + * @param {object | string | HTML div} gd + * can either be a data/layout/config object + * or an existing graph
+ * or an id to an existing graph
+ * @param {object} opts (see above) + * @return {promise} + */ +function toImage(gd, opts) { + opts = opts || {}; + + var data; + var layout; + var config; + var fullLayout; + + if(Lib.isPlainObject(gd)) { + data = gd.data || []; + layout = gd.layout || {}; + config = gd.config || {}; + fullLayout = {}; + } else { + gd = Lib.getGraphDiv(gd); + data = Lib.extendDeep([], gd.data); + layout = Lib.extendDeep({}, gd.layout); + config = gd._context; + fullLayout = gd._fullLayout || {}; + } + + function isImpliedOrValid(attr) { + return !(attr in opts) || Lib.validate(opts[attr], attrs[attr]); + } + + if((!isImpliedOrValid('width') && opts.width !== null) || + (!isImpliedOrValid('height') && opts.height !== null)) { + throw new Error('Height and width should be pixel values.'); + } + + if(!isImpliedOrValid('format')) { + throw new Error('Image format is not jpeg, png, svg or webp.'); + } + + var fullOpts = {}; + + function coerce(attr, dflt) { + return Lib.coerce(opts, fullOpts, attrs, attr, dflt); + } + + var format = coerce('format'); + var width = coerce('width'); + var height = coerce('height'); + var scale = coerce('scale'); + var setBackground = coerce('setBackground'); + var imageDataOnly = coerce('imageDataOnly'); + + // put the cloned div somewhere off screen before attaching to DOM + var clonedGd = document.createElement('div'); + clonedGd.style.position = 'absolute'; + clonedGd.style.left = '-5000px'; + document.body.appendChild(clonedGd); + + // extend layout with image options + var layoutImage = Lib.extendFlat({}, layout); + if(width) { + layoutImage.width = width; + } else if(opts.width === null && isNumeric(fullLayout.width)) { + layoutImage.width = fullLayout.width; + } + if(height) { + layoutImage.height = height; + } else if(opts.height === null && isNumeric(fullLayout.height)) { + layoutImage.height = fullLayout.height; + } + + // extend config for static plot + var configImage = Lib.extendFlat({}, config, { + _exportedPlot: true, + staticPlot: true, + setBackground: setBackground + }); + + var redrawFunc = helpers.getRedrawFunc(clonedGd); + + function wait() { + return new Promise(function(resolve) { + setTimeout(resolve, helpers.getDelay(clonedGd._fullLayout)); + }); + } + + function convert() { + return new Promise(function(resolve, reject) { + var svg = toSVG(clonedGd, format, scale); + var width = clonedGd._fullLayout.width; + var height = clonedGd._fullLayout.height; + + plotApi.purge(clonedGd); + document.body.removeChild(clonedGd); + + if(format === 'svg') { + if(imageDataOnly) { + return resolve(svg); + } else { + return resolve(helpers.encodeSVG(svg)); + } + } + + var canvas = document.createElement('canvas'); + canvas.id = Lib.randstr(); + + svgToImg({ + format: format, + width: width, + height: height, + scale: scale, + canvas: canvas, + svg: svg, + // ask svgToImg to return a Promise + // rather than EventEmitter + // leave EventEmitter for backward + // compatibility + promise: true + }) + .then(resolve) + .catch(reject); + }); + } + + function urlToImageData(url) { + if(imageDataOnly) { + return url.replace(helpers.IMAGE_URL_PREFIX, ''); + } else { + return url; + } + } + + return new Promise(function(resolve, reject) { + plotApi.plot(clonedGd, data, layoutImage, configImage) + .then(redrawFunc) + .then(wait) + .then(convert) + .then(function(url) { resolve(urlToImageData(url)); }) + .catch(function(err) { reject(err); }); + }); +} + +module.exports = toImage; + +},{"../lib":169,"../snapshot/helpers":262,"../snapshot/svgtoimg":264,"../snapshot/tosvg":266,"./plot_api":200,"fast-isnumeric":18}],207:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../lib'); +var Plots = _dereq_('../plots/plots'); +var PlotSchema = _dereq_('./plot_schema'); +var dfltConfig = _dereq_('./plot_config').dfltConfig; + +var isPlainObject = Lib.isPlainObject; +var isArray = Array.isArray; +var isArrayOrTypedArray = Lib.isArrayOrTypedArray; + +/** + * Validate a data array and layout object. + * + * @param {array} data + * @param {object} layout + * + * @return {array} array of error objects each containing: + * - {string} code + * error code ('object', 'array', 'schema', 'unused', 'invisible' or 'value') + * - {string} container + * container where the error occurs ('data' or 'layout') + * - {number} trace + * trace index of the 'data' container where the error occurs + * - {array} path + * nested path to the key that causes the error + * - {string} astr + * attribute string variant of 'path' compatible with Plotly.restyle and + * Plotly.relayout. + * - {string} msg + * error message (shown in console in logger config argument is enable) + */ +module.exports = function validate(data, layout) { + var schema = PlotSchema.get(); + var errorList = []; + var gd = {_context: Lib.extendFlat({}, dfltConfig)}; + + var dataIn, layoutIn; + + if(isArray(data)) { + gd.data = Lib.extendDeep([], data); + dataIn = data; + } else { + gd.data = []; + dataIn = []; + errorList.push(format('array', 'data')); + } + + if(isPlainObject(layout)) { + gd.layout = Lib.extendDeep({}, layout); + layoutIn = layout; + } else { + gd.layout = {}; + layoutIn = {}; + if(arguments.length > 1) { + errorList.push(format('object', 'layout')); + } + } + + // N.B. dataIn and layoutIn are in general not the same as + // gd.data and gd.layout after supplyDefaults as some attributes + // in gd.data and gd.layout (still) get mutated during this step. + + Plots.supplyDefaults(gd); + + var dataOut = gd._fullData; + var len = dataIn.length; + + for(var i = 0; i < len; i++) { + var traceIn = dataIn[i]; + var base = ['data', i]; + + if(!isPlainObject(traceIn)) { + errorList.push(format('object', base)); + continue; + } + + var traceOut = dataOut[i]; + var traceType = traceOut.type; + var traceSchema = schema.traces[traceType].attributes; + + // PlotSchema does something fancy with trace 'type', reset it here + // to make the trace schema compatible with Lib.validate. + traceSchema.type = { + valType: 'enumerated', + values: [traceType] + }; + + if(traceOut.visible === false && traceIn.visible !== false) { + errorList.push(format('invisible', base)); + } + + crawl(traceIn, traceOut, traceSchema, errorList, base); + + var transformsIn = traceIn.transforms; + var transformsOut = traceOut.transforms; + + if(transformsIn) { + if(!isArray(transformsIn)) { + errorList.push(format('array', base, ['transforms'])); + } + + base.push('transforms'); + + for(var j = 0; j < transformsIn.length; j++) { + var path = ['transforms', j]; + var transformType = transformsIn[j].type; + + if(!isPlainObject(transformsIn[j])) { + errorList.push(format('object', base, path)); + continue; + } + + var transformSchema = schema.transforms[transformType] ? + schema.transforms[transformType].attributes : + {}; + + // add 'type' to transform schema to validate the transform type + transformSchema.type = { + valType: 'enumerated', + values: Object.keys(schema.transforms) + }; + + crawl(transformsIn[j], transformsOut[j], transformSchema, errorList, base, path); + } + } + } + + var layoutOut = gd._fullLayout; + var layoutSchema = fillLayoutSchema(schema, dataOut); + + crawl(layoutIn, layoutOut, layoutSchema, errorList, 'layout'); + + // return undefined if no validation errors were found + return (errorList.length === 0) ? void(0) : errorList; +}; + +function crawl(objIn, objOut, schema, list, base, path) { + path = path || []; + + var keys = Object.keys(objIn); + + for(var i = 0; i < keys.length; i++) { + var k = keys[i]; + + // transforms are handled separately + if(k === 'transforms') continue; + + var p = path.slice(); + p.push(k); + + var valIn = objIn[k]; + var valOut = objOut[k]; + + var nestedSchema = getNestedSchema(schema, k); + var isInfoArray = (nestedSchema || {}).valType === 'info_array'; + var isColorscale = (nestedSchema || {}).valType === 'colorscale'; + var items = (nestedSchema || {}).items; + + if(!isInSchema(schema, k)) { + list.push(format('schema', base, p)); + } else if(isPlainObject(valIn) && isPlainObject(valOut)) { + crawl(valIn, valOut, nestedSchema, list, base, p); + } else if(isInfoArray && isArray(valIn)) { + if(valIn.length > valOut.length) { + list.push(format('unused', base, p.concat(valOut.length))); + } + var len = valOut.length; + var arrayItems = Array.isArray(items); + if(arrayItems) len = Math.min(len, items.length); + var m, n, item, valInPart, valOutPart; + if(nestedSchema.dimensions === 2) { + for(n = 0; n < len; n++) { + if(isArray(valIn[n])) { + if(valIn[n].length > valOut[n].length) { + list.push(format('unused', base, p.concat(n, valOut[n].length))); + } + var len2 = valOut[n].length; + for(m = 0; m < (arrayItems ? Math.min(len2, items[n].length) : len2); m++) { + item = arrayItems ? items[n][m] : items; + valInPart = valIn[n][m]; + valOutPart = valOut[n][m]; + if(!Lib.validate(valInPart, item)) { + list.push(format('value', base, p.concat(n, m), valInPart)); + } else if(valOutPart !== valInPart && valOutPart !== +valInPart) { + list.push(format('dynamic', base, p.concat(n, m), valInPart, valOutPart)); + } + } + } else { + list.push(format('array', base, p.concat(n), valIn[n])); + } + } + } else { + for(n = 0; n < len; n++) { + item = arrayItems ? items[n] : items; + valInPart = valIn[n]; + valOutPart = valOut[n]; + if(!Lib.validate(valInPart, item)) { + list.push(format('value', base, p.concat(n), valInPart)); + } else if(valOutPart !== valInPart && valOutPart !== +valInPart) { + list.push(format('dynamic', base, p.concat(n), valInPart, valOutPart)); + } + } + } + } else if(nestedSchema.items && !isInfoArray && isArray(valIn)) { + var _nestedSchema = items[Object.keys(items)[0]]; + var indexList = []; + + var j, _p; + + // loop over valOut items while keeping track of their + // corresponding input container index (given by _index) + for(j = 0; j < valOut.length; j++) { + var _index = valOut[j]._index || j; + + _p = p.slice(); + _p.push(_index); + + if(isPlainObject(valIn[_index]) && isPlainObject(valOut[j])) { + indexList.push(_index); + var valInj = valIn[_index]; + var valOutj = valOut[j]; + if(isPlainObject(valInj) && valInj.visible !== false && valOutj.visible === false) { + list.push(format('invisible', base, _p)); + } else crawl(valInj, valOutj, _nestedSchema, list, base, _p); + } + } + + // loop over valIn to determine where it went wrong for some items + for(j = 0; j < valIn.length; j++) { + _p = p.slice(); + _p.push(j); + + if(!isPlainObject(valIn[j])) { + list.push(format('object', base, _p, valIn[j])); + } else if(indexList.indexOf(j) === -1) { + list.push(format('unused', base, _p)); + } + } + } else if(!isPlainObject(valIn) && isPlainObject(valOut)) { + list.push(format('object', base, p, valIn)); + } else if(!isArrayOrTypedArray(valIn) && isArrayOrTypedArray(valOut) && !isInfoArray && !isColorscale) { + list.push(format('array', base, p, valIn)); + } else if(!(k in objOut)) { + list.push(format('unused', base, p, valIn)); + } else if(!Lib.validate(valIn, nestedSchema)) { + list.push(format('value', base, p, valIn)); + } else if(nestedSchema.valType === 'enumerated' && + ((nestedSchema.coerceNumber && valIn !== +valOut) || valIn !== valOut) + ) { + list.push(format('dynamic', base, p, valIn, valOut)); + } + } + + return list; +} + +// the 'full' layout schema depends on the traces types presents +function fillLayoutSchema(schema, dataOut) { + var layoutSchema = schema.layout.layoutAttributes; + + for(var i = 0; i < dataOut.length; i++) { + var traceOut = dataOut[i]; + var traceSchema = schema.traces[traceOut.type]; + var traceLayoutAttr = traceSchema.layoutAttributes; + + if(traceLayoutAttr) { + if(traceOut.subplot) { + Lib.extendFlat(layoutSchema[traceSchema.attributes.subplot.dflt], traceLayoutAttr); + } else { + Lib.extendFlat(layoutSchema, traceLayoutAttr); + } + } + } + + return layoutSchema; +} + +// validation error codes +var code2msgFunc = { + object: function(base, astr) { + var prefix; + + if(base === 'layout' && astr === '') prefix = 'The layout argument'; + else if(base[0] === 'data' && astr === '') { + prefix = 'Trace ' + base[1] + ' in the data argument'; + } else prefix = inBase(base) + 'key ' + astr; + + return prefix + ' must be linked to an object container'; + }, + array: function(base, astr) { + var prefix; + + if(base === 'data') prefix = 'The data argument'; + else prefix = inBase(base) + 'key ' + astr; + + return prefix + ' must be linked to an array container'; + }, + schema: function(base, astr) { + return inBase(base) + 'key ' + astr + ' is not part of the schema'; + }, + unused: function(base, astr, valIn) { + var target = isPlainObject(valIn) ? 'container' : 'key'; + + return inBase(base) + target + ' ' + astr + ' did not get coerced'; + }, + dynamic: function(base, astr, valIn, valOut) { + return [ + inBase(base) + 'key', + astr, + '(set to \'' + valIn + '\')', + 'got reset to', + '\'' + valOut + '\'', + 'during defaults.' + ].join(' '); + }, + invisible: function(base, astr) { + return ( + astr ? (inBase(base) + 'item ' + astr) : ('Trace ' + base[1]) + ) + ' got defaulted to be not visible'; + }, + value: function(base, astr, valIn) { + return [ + inBase(base) + 'key ' + astr, + 'is set to an invalid value (' + valIn + ')' + ].join(' '); + } +}; + +function inBase(base) { + if(isArray(base)) return 'In data trace ' + base[1] + ', '; + + return 'In ' + base + ', '; +} + +function format(code, base, path, valIn, valOut) { + path = path || ''; + + var container, trace; + + // container is either 'data' or 'layout + // trace is the trace index if 'data', null otherwise + + if(isArray(base)) { + container = base[0]; + trace = base[1]; + } else { + container = base; + trace = null; + } + + var astr = convertPathToAttributeString(path); + var msg = code2msgFunc[code](base, astr, valIn, valOut); + + // log to console if logger config option is enabled + Lib.log(msg); + + return { + code: code, + container: container, + trace: trace, + path: path, + astr: astr, + msg: msg + }; +} + +function isInSchema(schema, key) { + var parts = splitKey(key); + var keyMinusId = parts.keyMinusId; + var id = parts.id; + + if((keyMinusId in schema) && schema[keyMinusId]._isSubplotObj && id) { + return true; + } + + return (key in schema); +} + +function getNestedSchema(schema, key) { + if(key in schema) return schema[key]; + + var parts = splitKey(key); + + return schema[parts.keyMinusId]; +} + +var idRegex = Lib.counterRegex('([a-z]+)'); + +function splitKey(key) { + var idMatch = key.match(idRegex); + + return { + keyMinusId: idMatch && idMatch[1], + id: idMatch && idMatch[2] + }; +} + +function convertPathToAttributeString(path) { + if(!isArray(path)) return String(path); + + var astr = ''; + + for(var i = 0; i < path.length; i++) { + var p = path[i]; + + if(typeof p === 'number') { + astr = astr.substr(0, astr.length - 1) + '[' + p + ']'; + } else { + astr += p; + } + + if(i < path.length - 1) astr += '.'; + } + + return astr; +} + +},{"../lib":169,"../plots/plots":245,"./plot_config":201,"./plot_schema":202}],208:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + mode: { + valType: 'enumerated', + dflt: 'afterall', + + values: ['immediate', 'next', 'afterall'], + + }, + direction: { + valType: 'enumerated', + + values: ['forward', 'reverse'], + dflt: 'forward', + + }, + fromcurrent: { + valType: 'boolean', + dflt: false, + + + }, + frame: { + duration: { + valType: 'number', + + min: 0, + dflt: 500, + + }, + redraw: { + valType: 'boolean', + + dflt: true, + + }, + }, + transition: { + duration: { + valType: 'number', + + min: 0, + dflt: 500, + editType: 'none', + + }, + easing: { + valType: 'enumerated', + dflt: 'cubic-in-out', + values: [ + 'linear', + 'quad', + 'cubic', + 'sin', + 'exp', + 'circle', + 'elastic', + 'back', + 'bounce', + 'linear-in', + 'quad-in', + 'cubic-in', + 'sin-in', + 'exp-in', + 'circle-in', + 'elastic-in', + 'back-in', + 'bounce-in', + 'linear-out', + 'quad-out', + 'cubic-out', + 'sin-out', + 'exp-out', + 'circle-out', + 'elastic-out', + 'back-out', + 'bounce-out', + 'linear-in-out', + 'quad-in-out', + 'cubic-in-out', + 'sin-in-out', + 'exp-in-out', + 'circle-in-out', + 'elastic-in-out', + 'back-in-out', + 'bounce-in-out' + ], + + editType: 'none', + + }, + ordering: { + valType: 'enumerated', + values: ['layout first', 'traces first'], + dflt: 'layout first', + + editType: 'none', + + } + } +}; + +},{}],209:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../lib'); +var Template = _dereq_('../plot_api/plot_template'); + +/** Convenience wrapper for making array container logic DRY and consistent + * + * @param {object} parentObjIn + * user input object where the container in question is linked + * (i.e. either a user trace object or the user layout object) + * + * @param {object} parentObjOut + * full object where the coerced container will be linked + * (i.e. either a full trace object or the full layout object) + * + * @param {object} opts + * options object: + * - name {string} + * name of the key linking the container in question + * - inclusionAttr {string} + * name of the item attribute for inclusion/exclusion. Default is 'visible'. + * Since inclusion is true, use eg 'enabled' instead of 'disabled'. + * - handleItemDefaults {function} + * defaults method to be called on each item in the array container in question + * + * Its arguments are: + * - itemIn {object} item in user layout + * - itemOut {object} item in full layout + * - parentObj {object} (as in closure) + * - opts {object} (as in closure) + * N.B. + * + * - opts is passed to handleItemDefaults so it can also store + * links to supplementary data (e.g. fullData for layout components) + * + */ +module.exports = function handleArrayContainerDefaults(parentObjIn, parentObjOut, opts) { + var name = opts.name; + var inclusionAttr = opts.inclusionAttr || 'visible'; + + var previousContOut = parentObjOut[name]; + + var contIn = Lib.isArrayOrTypedArray(parentObjIn[name]) ? parentObjIn[name] : []; + var contOut = parentObjOut[name] = []; + var templater = Template.arrayTemplater(parentObjOut, name, inclusionAttr); + var i, itemOut; + + for(i = 0; i < contIn.length; i++) { + var itemIn = contIn[i]; + + if(!Lib.isPlainObject(itemIn)) { + itemOut = templater.newItem({}); + itemOut[inclusionAttr] = false; + } else { + itemOut = templater.newItem(itemIn); + } + + itemOut._index = i; + + if(itemOut[inclusionAttr] !== false) { + opts.handleItemDefaults(itemIn, itemOut, parentObjOut, opts); + } + + contOut.push(itemOut); + } + + var defaultItems = templater.defaultItems(); + for(i = 0; i < defaultItems.length; i++) { + itemOut = defaultItems[i]; + itemOut._index = contOut.length; + opts.handleItemDefaults({}, itemOut, parentObjOut, opts, {}); + contOut.push(itemOut); + } + + // in case this array gets its defaults rebuilt independent of the whole layout, + // relink the private keys just for this array. + if(Lib.isArrayOrTypedArray(previousContOut)) { + var len = Math.min(previousContOut.length, contOut.length); + for(i = 0; i < len; i++) { + Lib.relinkPrivateKeys(contOut[i], previousContOut[i]); + } + } + + return contOut; +}; + +},{"../lib":169,"../plot_api/plot_template":203}],210:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var fxAttrs = _dereq_('../components/fx/attributes'); + +module.exports = { + type: { + valType: 'enumerated', + + values: [], // listed dynamically + dflt: 'scatter', + editType: 'calc+clearAxisTypes', + _noTemplating: true // we handle this at a higher level + }, + visible: { + valType: 'enumerated', + values: [true, false, 'legendonly'], + + dflt: true, + editType: 'calc', + + }, + showlegend: { + valType: 'boolean', + + dflt: true, + editType: 'style', + + }, + legendgroup: { + valType: 'string', + + dflt: '', + editType: 'style', + + }, + opacity: { + valType: 'number', + + min: 0, + max: 1, + dflt: 1, + editType: 'style', + + }, + name: { + valType: 'string', + + editType: 'style', + + }, + uid: { + valType: 'string', + + editType: 'plot', + anim: true, + + }, + ids: { + valType: 'data_array', + editType: 'calc', + anim: true, + + }, + customdata: { + valType: 'data_array', + editType: 'calc', + + }, + meta: { + valType: 'any', + arrayOk: true, + + editType: 'plot', + + }, + + // N.B. these cannot be 'data_array' as they do not have the same length as + // other data arrays and arrayOk attributes in general + // + // Maybe add another valType: + // https://github.com/plotly/plotly.js/issues/1894 + selectedpoints: { + valType: 'any', + + editType: 'calc', + + }, + + hoverinfo: { + valType: 'flaglist', + + flags: ['x', 'y', 'z', 'text', 'name'], + extras: ['all', 'none', 'skip'], + arrayOk: true, + dflt: 'all', + editType: 'none', + + }, + hoverlabel: fxAttrs.hoverlabel, + stream: { + token: { + valType: 'string', + noBlank: true, + strict: true, + + editType: 'calc', + + }, + maxpoints: { + valType: 'number', + min: 0, + max: 10000, + dflt: 500, + + editType: 'calc', + + }, + editType: 'calc' + }, + transforms: { + _isLinkedToArray: 'transform', + editType: 'calc', + + }, + uirevision: { + valType: 'any', + + editType: 'none', + + } +}; + +},{"../components/fx/attributes":81}],211:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + + +module.exports = { + xaxis: { + valType: 'subplotid', + + dflt: 'x', + editType: 'calc+clearAxisTypes', + + }, + yaxis: { + valType: 'subplotid', + + dflt: 'y', + editType: 'calc+clearAxisTypes', + + } +}; + +},{}],212:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); + +var Lib = _dereq_('../../lib'); +var FP_SAFE = _dereq_('../../constants/numerical').FP_SAFE; +var Registry = _dereq_('../../registry'); + +module.exports = { + getAutoRange: getAutoRange, + makePadFn: makePadFn, + doAutoRange: doAutoRange, + findExtremes: findExtremes, + concatExtremes: concatExtremes +}; + +/** + * getAutoRange + * + * Collects all _extremes values corresponding to a given axis + * and computes its auto range. + * + * Note that getAutoRange uses return values from findExtremes. + * + * @param {object} gd: + * graph div object with filled-in fullData and fullLayout, in particular + * with filled-in '_extremes' containers: + * { + * val: calcdata value, + * pad: extra pixels beyond this value, + * extrapad: bool, does this point want 5% extra padding + * } + * @param {object} ax: + * full axis object, in particular with filled-in '_traceIndices' + * and '_annIndices' / '_shapeIndices' if applicable + * @return {array} + * an array of [min, max]. These are calcdata for log and category axes + * and data for linear and date axes. + * + * TODO: we want to change log to data as well, but it's hard to do this + * maintaining backward compatibility. category will always have to use calcdata + * though, because otherwise values between categories (or outside all categories) + * would be impossible. + */ +function getAutoRange(gd, ax) { + var i, j; + var newRange = []; + + var getPad = makePadFn(ax); + var extremes = concatExtremes(gd, ax); + var minArray = extremes.min; + var maxArray = extremes.max; + + if(minArray.length === 0 || maxArray.length === 0) { + return Lib.simpleMap(ax.range, ax.r2l); + } + + var minmin = minArray[0].val; + var maxmax = maxArray[0].val; + + for(i = 1; i < minArray.length; i++) { + if(minmin !== maxmax) break; + minmin = Math.min(minmin, minArray[i].val); + } + for(i = 1; i < maxArray.length; i++) { + if(minmin !== maxmax) break; + maxmax = Math.max(maxmax, maxArray[i].val); + } + + var axReverse = false; + + if(ax.range) { + var rng = Lib.simpleMap(ax.range, ax.r2l); + axReverse = rng[1] < rng[0]; + } + // one-time setting to easily reverse the axis + // when plotting from code + if(ax.autorange === 'reversed') { + axReverse = true; + ax.autorange = true; + } + + var rangeMode = ax.rangemode; + var toZero = rangeMode === 'tozero'; + var nonNegative = rangeMode === 'nonnegative'; + var axLen = ax._length; + // don't allow padding to reduce the data to < 10% of the length + var minSpan = axLen / 10; + + var mbest = 0; + var minpt, maxpt, minbest, maxbest, dp, dv; + + for(i = 0; i < minArray.length; i++) { + minpt = minArray[i]; + for(j = 0; j < maxArray.length; j++) { + maxpt = maxArray[j]; + dv = maxpt.val - minpt.val; + if(dv > 0) { + dp = axLen - getPad(minpt) - getPad(maxpt); + if(dp > minSpan) { + if(dv / dp > mbest) { + minbest = minpt; + maxbest = maxpt; + mbest = dv / dp; + } + } else if(dv / axLen > mbest) { + // in case of padding longer than the axis + // at least include the unpadded data values. + minbest = {val: minpt.val, pad: 0}; + maxbest = {val: maxpt.val, pad: 0}; + mbest = dv / axLen; + } + } + } + } + + function getMaxPad(prev, pt) { + return Math.max(prev, getPad(pt)); + } + + if(minmin === maxmax) { + var lower = minmin - 1; + var upper = minmin + 1; + if(toZero) { + if(minmin === 0) { + // The only value we have on this axis is 0, and we want to + // autorange so zero is one end. + // In principle this could be [0, 1] or [-1, 0] but usually + // 'tozero' pins 0 to the low end, so follow that. + newRange = [0, 1]; + } else { + var maxPad = (minmin > 0 ? maxArray : minArray).reduce(getMaxPad, 0); + // we're pushing a single value away from the edge due to its + // padding, with the other end clamped at zero + // 0.5 means don't push it farther than the center. + var rangeEnd = minmin / (1 - Math.min(0.5, maxPad / axLen)); + newRange = minmin > 0 ? [0, rangeEnd] : [rangeEnd, 0]; + } + } else if(nonNegative) { + newRange = [Math.max(0, lower), Math.max(1, upper)]; + } else { + newRange = [lower, upper]; + } + } else { + if(toZero) { + if(minbest.val >= 0) { + minbest = {val: 0, pad: 0}; + } + if(maxbest.val <= 0) { + maxbest = {val: 0, pad: 0}; + } + } else if(nonNegative) { + if(minbest.val - mbest * getPad(minbest) < 0) { + minbest = {val: 0, pad: 0}; + } + if(maxbest.val <= 0) { + maxbest = {val: 1, pad: 0}; + } + } + + // in case it changed again... + mbest = (maxbest.val - minbest.val) / + (axLen - getPad(minbest) - getPad(maxbest)); + + newRange = [ + minbest.val - mbest * getPad(minbest), + maxbest.val + mbest * getPad(maxbest) + ]; + } + + // maintain reversal + if(axReverse) newRange.reverse(); + + return Lib.simpleMap(newRange, ax.l2r || Number); +} + +/* + * calculate the pixel padding for ax._min and ax._max entries with + * optional extrapad as 5% of the total axis length + */ +function makePadFn(ax) { + // 5% padding for points that specify extrapad: true + var extrappad = ax._length / 20; + + // domain-constrained axes: base extrappad on the unconstrained + // domain so it's consistent as the domain changes + if((ax.constrain === 'domain') && ax._inputDomain) { + extrappad *= (ax._inputDomain[1] - ax._inputDomain[0]) / + (ax.domain[1] - ax.domain[0]); + } + + return function getPad(pt) { return pt.pad + (pt.extrapad ? extrappad : 0); }; +} + +function concatExtremes(gd, ax) { + var axId = ax._id; + var fullData = gd._fullData; + var fullLayout = gd._fullLayout; + var minArray = []; + var maxArray = []; + var i, j, d; + + function _concat(cont, indices) { + for(i = 0; i < indices.length; i++) { + var item = cont[indices[i]]; + var extremes = (item._extremes || {})[axId]; + if(item.visible === true && extremes) { + for(j = 0; j < extremes.min.length; j++) { + d = extremes.min[j]; + collapseMinArray(minArray, d.val, d.pad, {extrapad: d.extrapad}); + } + for(j = 0; j < extremes.max.length; j++) { + d = extremes.max[j]; + collapseMaxArray(maxArray, d.val, d.pad, {extrapad: d.extrapad}); + } + } + } + } + + _concat(fullData, ax._traceIndices); + _concat(fullLayout.annotations || [], ax._annIndices || []); + _concat(fullLayout.shapes || [], ax._shapeIndices || []); + + return {min: minArray, max: maxArray}; +} + +function doAutoRange(gd, ax) { + ax.setScale(); + + if(ax.autorange) { + ax.range = getAutoRange(gd, ax); + + ax._r = ax.range.slice(); + ax._rl = Lib.simpleMap(ax._r, ax.r2l); + + // doAutoRange will get called on fullLayout, + // but we want to report its results back to layout + + var axIn = ax._input; + + // before we edit _input, store preGUI values + var edits = {}; + edits[ax._attr + '.range'] = ax.range; + edits[ax._attr + '.autorange'] = ax.autorange; + Registry.call('_storeDirectGUIEdit', gd.layout, gd._fullLayout._preGUI, edits); + + axIn.range = ax.range.slice(); + axIn.autorange = ax.autorange; + } + + var anchorAx = ax._anchorAxis; + + if(anchorAx && anchorAx.rangeslider) { + var axeRangeOpts = anchorAx.rangeslider[ax._name]; + if(axeRangeOpts) { + if(axeRangeOpts.rangemode === 'auto') { + axeRangeOpts.range = getAutoRange(gd, ax); + } + } + anchorAx._input.rangeslider[ax._name] = Lib.extendFlat({}, axeRangeOpts); + } +} + +/** + * findExtremes + * + * Find min/max extremes of an array of coordinates on a given axis. + * + * Note that findExtremes is called during `calc`, when we don't yet know the axis + * length; all the inputs should be based solely on the trace data, nothing + * about the axis layout. + * + * Note that `ppad` and `vpad` as well as their asymmetric variants refer to + * the before and after padding of the passed `data` array, not to the whole axis. + * + * @param {object} ax: full axis object + * relies on + * - ax.type + * - ax._m (just its sign) + * - ax.d2l + * @param {array} data: + * array of numbers (i.e. already run though ax.d2c) + * @param {object} opts: + * available keys are: + * vpad: (number or number array) pad values (data value +-vpad) + * ppad: (number or number array) pad pixels (pixel location +-ppad) + * ppadplus, ppadminus, vpadplus, vpadminus: + * separate padding for each side, overrides symmetric + * padded: (boolean) add 5% padding to both ends + * (unless one end is overridden by tozero) + * tozero: (boolean) make sure to include zero if axis is linear, + * and make it a tight bound if possible + * vpadLinearized: (boolean) whether or not vpad (or vpadplus/vpadminus) + * is linearized (for log scale axes) + * + * @return {object} + * - min {array of objects} + * - max {array of objects} + * each object item has fields: + * - val {number} + * - pad {number} + * - extrappad {number} + * - opts {object}: a ref to the passed "options" object + */ +function findExtremes(ax, data, opts) { + if(!opts) opts = {}; + if(!ax._m) ax.setScale(); + + var minArray = []; + var maxArray = []; + + var len = data.length; + var extrapad = opts.padded || false; + var tozero = opts.tozero && (ax.type === 'linear' || ax.type === '-'); + var isLog = ax.type === 'log'; + var hasArrayOption = false; + var vpadLinearized = opts.vpadLinearized || false; + var i, v, di, dmin, dmax, ppadiplus, ppadiminus, vmin, vmax; + + function makePadAccessor(item) { + if(Array.isArray(item)) { + hasArrayOption = true; + return function(i) { return Math.max(Number(item[i]||0), 0); }; + } else { + var v = Math.max(Number(item||0), 0); + return function() { return v; }; + } + } + + var ppadplus = makePadAccessor((ax._m > 0 ? + opts.ppadplus : opts.ppadminus) || opts.ppad || 0); + var ppadminus = makePadAccessor((ax._m > 0 ? + opts.ppadminus : opts.ppadplus) || opts.ppad || 0); + var vpadplus = makePadAccessor(opts.vpadplus || opts.vpad); + var vpadminus = makePadAccessor(opts.vpadminus || opts.vpad); + + if(!hasArrayOption) { + // with no arrays other than `data` we don't need to consider + // every point, only the extreme data points + vmin = Infinity; + vmax = -Infinity; + + if(isLog) { + for(i = 0; i < len; i++) { + v = data[i]; + // data is not linearized yet so we still have to filter out negative logs + if(v < vmin && v > 0) vmin = v; + if(v > vmax && v < FP_SAFE) vmax = v; + } + } else { + for(i = 0; i < len; i++) { + v = data[i]; + if(v < vmin && v > -FP_SAFE) vmin = v; + if(v > vmax && v < FP_SAFE) vmax = v; + } + } + + data = [vmin, vmax]; + len = 2; + } + + var collapseOpts = {tozero: tozero, extrapad: extrapad}; + + function addItem(i) { + di = data[i]; + if(!isNumeric(di)) return; + ppadiplus = ppadplus(i); + ppadiminus = ppadminus(i); + + if(vpadLinearized) { + dmin = ax.c2l(di) - vpadminus(i); + dmax = ax.c2l(di) + vpadplus(i); + } else { + vmin = di - vpadminus(i); + vmax = di + vpadplus(i); + // special case for log axes: if vpad makes this object span + // more than an order of mag, clip it to one order. This is so + // we don't have non-positive errors or absurdly large lower + // range due to rounding errors + if(isLog && vmin < vmax / 10) vmin = vmax / 10; + + dmin = ax.c2l(vmin); + dmax = ax.c2l(vmax); + } + + if(tozero) { + dmin = Math.min(0, dmin); + dmax = Math.max(0, dmax); + } + if(goodNumber(dmin)) { + collapseMinArray(minArray, dmin, ppadiminus, collapseOpts); + } + if(goodNumber(dmax)) { + collapseMaxArray(maxArray, dmax, ppadiplus, collapseOpts); + } + } + + // For efficiency covering monotonic or near-monotonic data, + // check a few points at both ends first and then sweep + // through the middle + var iMax = Math.min(6, len); + for(i = 0; i < iMax; i++) addItem(i); + for(i = len - 1; i >= iMax; i--) addItem(i); + + return { + min: minArray, + max: maxArray, + opts: opts + }; +} + +function collapseMinArray(array, newVal, newPad, opts) { + collapseArray(array, newVal, newPad, opts, lessOrEqual); +} + +function collapseMaxArray(array, newVal, newPad, opts) { + collapseArray(array, newVal, newPad, opts, greaterOrEqual); +} + +/** + * collapseArray + * + * Takes items from 'array' and compares them to 'newVal', 'newPad'. + * + * @param {array} array: + * current set of min or max extremes + * @param {number} newVal: + * new value to compare against + * @param {number} newPad: + * pad value associated with 'newVal' + * @param {object} opts: + * - tozero {boolean} + * - extrapad {number} + * @param {function} atLeastAsExtreme: + * comparison function, use + * - lessOrEqual for min 'array' and + * - greaterOrEqual for max 'array' + * + * In practice, 'array' is either + * - 'extremes[ax._id].min' or + * - 'extremes[ax._id].max + * found in traces and layout items that affect autorange. + * + * Since we don't yet know the relationship between pixels and values + * (that's what we're trying to figure out!) AND we don't yet know how + * many pixels `extrapad` represents (it's going to be 5% of the length, + * but we don't want to have to redo calc just because length changed) + * two point must satisfy three criteria simultaneously for one to supersede the other: + * - at least as extreme a `val` + * - at least as big a `pad` + * - an unpadded point cannot supersede a padded point, but any other combination can + * + * Then: + * - If the item supersedes the new point, set includeThis false + * - If the new pt supersedes the item, delete it from 'array' + */ +function collapseArray(array, newVal, newPad, opts, atLeastAsExtreme) { + var tozero = opts.tozero; + var extrapad = opts.extrapad; + var includeThis = true; + + for(var j = 0; j < array.length && includeThis; j++) { + var v = array[j]; + if(atLeastAsExtreme(v.val, newVal) && v.pad >= newPad && (v.extrapad || !extrapad)) { + includeThis = false; + break; + } else if(atLeastAsExtreme(newVal, v.val) && v.pad <= newPad && (extrapad || !v.extrapad)) { + array.splice(j, 1); + j--; + } + } + if(includeThis) { + var clipAtZero = (tozero && newVal === 0); + array.push({ + val: newVal, + pad: clipAtZero ? 0 : newPad, + extrapad: clipAtZero ? false : extrapad + }); + } +} + +// In order to stop overflow errors, don't consider points +// too close to the limits of js floating point +function goodNumber(v) { + return isNumeric(v) && Math.abs(v) < FP_SAFE; +} + +function lessOrEqual(v0, v1) { return v0 <= v1; } +function greaterOrEqual(v0, v1) { return v0 >= v1; } + +},{"../../constants/numerical":149,"../../lib":169,"../../registry":258,"fast-isnumeric":18}],213:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); +var isNumeric = _dereq_('fast-isnumeric'); +var Plots = _dereq_('../../plots/plots'); + +var Registry = _dereq_('../../registry'); +var Lib = _dereq_('../../lib'); +var svgTextUtils = _dereq_('../../lib/svg_text_utils'); +var Titles = _dereq_('../../components/titles'); +var Color = _dereq_('../../components/color'); +var Drawing = _dereq_('../../components/drawing'); + +var axAttrs = _dereq_('./layout_attributes'); +var cleanTicks = _dereq_('./clean_ticks'); + +var constants = _dereq_('../../constants/numerical'); +var ONEAVGYEAR = constants.ONEAVGYEAR; +var ONEAVGMONTH = constants.ONEAVGMONTH; +var ONEDAY = constants.ONEDAY; +var ONEHOUR = constants.ONEHOUR; +var ONEMIN = constants.ONEMIN; +var ONESEC = constants.ONESEC; +var MINUS_SIGN = constants.MINUS_SIGN; +var BADNUM = constants.BADNUM; + +var alignmentConstants = _dereq_('../../constants/alignment'); +var MID_SHIFT = alignmentConstants.MID_SHIFT; +var CAP_SHIFT = alignmentConstants.CAP_SHIFT; +var LINE_SPACING = alignmentConstants.LINE_SPACING; +var OPPOSITE_SIDE = alignmentConstants.OPPOSITE_SIDE; + +var axes = module.exports = {}; + +axes.setConvert = _dereq_('./set_convert'); +var autoType = _dereq_('./axis_autotype'); + +var axisIds = _dereq_('./axis_ids'); +axes.id2name = axisIds.id2name; +axes.name2id = axisIds.name2id; +axes.cleanId = axisIds.cleanId; +axes.list = axisIds.list; +axes.listIds = axisIds.listIds; +axes.getFromId = axisIds.getFromId; +axes.getFromTrace = axisIds.getFromTrace; + +var autorange = _dereq_('./autorange'); +axes.getAutoRange = autorange.getAutoRange; +axes.findExtremes = autorange.findExtremes; + +/* + * find the list of possible axes to reference with an xref or yref attribute + * and coerce it to that list + * + * attr: the attribute we're generating a reference for. Should end in 'x' or 'y' + * but can be prefixed, like 'ax' for annotation's arrow x + * dflt: the default to coerce to, or blank to use the first axis (falling back on + * extraOption if there is no axis) + * extraOption: aside from existing axes with this letter, what non-axis value is allowed? + * Only required if it's different from `dflt` + */ +axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption) { + var axLetter = attr.charAt(attr.length - 1); + var axlist = gd._fullLayout._subplots[axLetter + 'axis']; + var refAttr = attr + 'ref'; + var attrDef = {}; + + if(!dflt) dflt = axlist[0] || extraOption; + if(!extraOption) extraOption = dflt; + + // data-ref annotations are not supported in gl2d yet + + attrDef[refAttr] = { + valType: 'enumerated', + values: axlist.concat(extraOption ? [extraOption] : []), + dflt: dflt + }; + + // xref, yref + return Lib.coerce(containerIn, containerOut, attrDef, refAttr); +}; + +/* + * coerce position attributes (range-type) that can be either on axes or absolute + * (paper or pixel) referenced. The biggest complication here is that we don't know + * before looking at the axis whether the value must be a number or not (it may be + * a date string), so we can't use the regular valType='number' machinery + * + * axRef (string): the axis this position is referenced to, or: + * paper: fraction of the plot area + * pixel: pixels relative to some starting position + * attr (string): the attribute in containerOut we are coercing + * dflt (number): the default position, as a fraction or pixels. If the attribute + * is to be axis-referenced, this will be converted to an axis data value + * + * Also cleans the values, since the attribute definition itself has to say + * valType: 'any' to handle date axes. This allows us to accept: + * - for category axes: category names, and convert them here into serial numbers. + * Note that this will NOT work for axis range endpoints, because we don't know + * the category list yet (it's set by ax.makeCalcdata during calc) + * but it works for component (note, shape, images) positions. + * - for date axes: JS Dates or milliseconds, and convert to date strings + * - for other types: coerce them to numbers + */ +axes.coercePosition = function(containerOut, gd, coerce, axRef, attr, dflt) { + var cleanPos, pos; + + if(axRef === 'paper' || axRef === 'pixel') { + cleanPos = Lib.ensureNumber; + pos = coerce(attr, dflt); + } else { + var ax = axes.getFromId(gd, axRef); + dflt = ax.fraction2r(dflt); + pos = coerce(attr, dflt); + cleanPos = ax.cleanPos; + } + + containerOut[attr] = cleanPos(pos); +}; + +axes.cleanPosition = function(pos, gd, axRef) { + var cleanPos = (axRef === 'paper' || axRef === 'pixel') ? + Lib.ensureNumber : + axes.getFromId(gd, axRef).cleanPos; + + return cleanPos(pos); +}; + +axes.redrawComponents = function(gd, axIds) { + axIds = axIds ? axIds : axes.listIds(gd); + + var fullLayout = gd._fullLayout; + + function _redrawOneComp(moduleName, methodName, stashName, shortCircuit) { + var method = Registry.getComponentMethod(moduleName, methodName); + var stash = {}; + + for(var i = 0; i < axIds.length; i++) { + var ax = fullLayout[axes.id2name(axIds[i])]; + var indices = ax[stashName]; + + for(var j = 0; j < indices.length; j++) { + var ind = indices[j]; + + if(!stash[ind]) { + method(gd, ind); + stash[ind] = 1; + // once is enough for images (which doesn't use the `i` arg anyway) + if(shortCircuit) return; + } + } + } + } + + // annotations and shapes 'draw' method is slow, + // use the finer-grained 'drawOne' method instead + _redrawOneComp('annotations', 'drawOne', '_annIndices'); + _redrawOneComp('shapes', 'drawOne', '_shapeIndices'); + _redrawOneComp('images', 'draw', '_imgIndices', true); +}; + +var getDataConversions = axes.getDataConversions = function(gd, trace, target, targetArray) { + var ax; + + // If target points to an axis, use the type we already have for that + // axis to find the data type. Otherwise use the values to autotype. + var d2cTarget = (target === 'x' || target === 'y' || target === 'z') ? + target : + targetArray; + + // In the case of an array target, make a mock data array + // and call supplyDefaults to the data type and + // setup the data-to-calc method. + if(Array.isArray(d2cTarget)) { + ax = { + type: autoType(targetArray), + _categories: [] + }; + axes.setConvert(ax); + + // build up ax._categories (usually done during ax.makeCalcdata() + if(ax.type === 'category') { + for(var i = 0; i < targetArray.length; i++) { + ax.d2c(targetArray[i]); + } + } + // TODO what to do for transforms? + } else { + ax = axes.getFromTrace(gd, trace, d2cTarget); + } + + // if 'target' has corresponding axis + // -> use setConvert method + if(ax) return {d2c: ax.d2c, c2d: ax.c2d}; + + // special case for 'ids' + // -> cast to String + if(d2cTarget === 'ids') return {d2c: toString, c2d: toString}; + + // otherwise (e.g. numeric-array of 'marker.color' or 'marker.size') + // -> cast to Number + + return {d2c: toNum, c2d: toNum}; +}; + +function toNum(v) { return +v; } +function toString(v) { return String(v); } + +axes.getDataToCoordFunc = function(gd, trace, target, targetArray) { + return getDataConversions(gd, trace, target, targetArray).d2c; +}; + +// get counteraxis letter for this axis (name or id) +// this can also be used as the id for default counter axis +axes.counterLetter = function(id) { + var axLetter = id.charAt(0); + if(axLetter === 'x') return 'y'; + if(axLetter === 'y') return 'x'; +}; + +// incorporate a new minimum difference and first tick into +// forced +// note that _forceTick0 is linearized, so needs to be turned into +// a range value for setting tick0 +axes.minDtick = function(ax, newDiff, newFirst, allow) { + // doesn't make sense to do forced min dTick on log or category axes, + // and the plot itself may decide to cancel (ie non-grouped bars) + if(['log', 'category', 'multicategory'].indexOf(ax.type) !== -1 || !allow) { + ax._minDtick = 0; + } else if(ax._minDtick === undefined) { + // undefined means there's nothing there yet + + ax._minDtick = newDiff; + ax._forceTick0 = newFirst; + } else if(ax._minDtick) { + if((ax._minDtick / newDiff + 1e-6) % 1 < 2e-6 && + // existing minDtick is an integer multiple of newDiff + // (within rounding err) + // and forceTick0 can be shifted to newFirst + + (((newFirst - ax._forceTick0) / newDiff % 1) + + 1.000001) % 1 < 2e-6) { + ax._minDtick = newDiff; + ax._forceTick0 = newFirst; + } else if((newDiff / ax._minDtick + 1e-6) % 1 > 2e-6 || + // if the converse is true (newDiff is a multiple of minDtick and + // newFirst can be shifted to forceTick0) then do nothing - same + // forcing stands. Otherwise, cancel forced minimum + + (((newFirst - ax._forceTick0) / ax._minDtick % 1) + + 1.000001) % 1 > 2e-6) { + ax._minDtick = 0; + } + } +}; + +// save a copy of the initial axis ranges in fullLayout +// use them in mode bar and dblclick events +axes.saveRangeInitial = function(gd, overwrite) { + var axList = axes.list(gd, '', true); + var hasOneAxisChanged = false; + + for(var i = 0; i < axList.length; i++) { + var ax = axList[i]; + var isNew = (ax._rangeInitial === undefined); + var hasChanged = isNew || !( + ax.range[0] === ax._rangeInitial[0] && + ax.range[1] === ax._rangeInitial[1] + ); + + if((isNew && ax.autorange === false) || (overwrite && hasChanged)) { + ax._rangeInitial = ax.range.slice(); + hasOneAxisChanged = true; + } + } + + return hasOneAxisChanged; +}; + +// save a copy of the initial spike visibility +axes.saveShowSpikeInitial = function(gd, overwrite) { + var axList = axes.list(gd, '', true); + var hasOneAxisChanged = false; + var allSpikesEnabled = 'on'; + + for(var i = 0; i < axList.length; i++) { + var ax = axList[i]; + var isNew = (ax._showSpikeInitial === undefined); + var hasChanged = isNew || !(ax.showspikes === ax._showspikes); + + if(isNew || (overwrite && hasChanged)) { + ax._showSpikeInitial = ax.showspikes; + hasOneAxisChanged = true; + } + + if(allSpikesEnabled === 'on' && !ax.showspikes) { + allSpikesEnabled = 'off'; + } + } + gd._fullLayout._cartesianSpikesEnabled = allSpikesEnabled; + return hasOneAxisChanged; +}; + +axes.autoBin = function(data, ax, nbins, is2d, calendar, size) { + var dataMin = Lib.aggNums(Math.min, null, data); + var dataMax = Lib.aggNums(Math.max, null, data); + + if(ax.type === 'category' || ax.type === 'multicategory') { + return { + start: dataMin - 0.5, + end: dataMax + 0.5, + size: Math.max(1, Math.round(size) || 1), + _dataSpan: dataMax - dataMin, + }; + } + + if(!calendar) calendar = ax.calendar; + + // piggyback off tick code to make "nice" bin sizes and edges + var dummyAx; + if(ax.type === 'log') { + dummyAx = { + type: 'linear', + range: [dataMin, dataMax] + }; + } else { + dummyAx = { + type: ax.type, + range: Lib.simpleMap([dataMin, dataMax], ax.c2r, 0, calendar), + calendar: calendar + }; + } + axes.setConvert(dummyAx); + + size = size && cleanTicks.dtick(size, dummyAx.type); + + if(size) { + dummyAx.dtick = size; + dummyAx.tick0 = cleanTicks.tick0(undefined, dummyAx.type, calendar); + } else { + var size0; + if(nbins) size0 = ((dataMax - dataMin) / nbins); + else { + // totally auto: scale off std deviation so the highest bin is + // somewhat taller than the total number of bins, but don't let + // the size get smaller than the 'nice' rounded down minimum + // difference between values + var distinctData = Lib.distinctVals(data); + var msexp = Math.pow(10, Math.floor( + Math.log(distinctData.minDiff) / Math.LN10)); + var minSize = msexp * Lib.roundUp( + distinctData.minDiff / msexp, [0.9, 1.9, 4.9, 9.9], true); + size0 = Math.max(minSize, 2 * Lib.stdev(data) / + Math.pow(data.length, is2d ? 0.25 : 0.4)); + + // fallback if ax.d2c output BADNUMs + // e.g. when user try to plot categorical bins + // on a layout.xaxis.type: 'linear' + if(!isNumeric(size0)) size0 = 1; + } + + axes.autoTicks(dummyAx, size0); + } + + var finalSize = dummyAx.dtick; + var binStart = axes.tickIncrement( + axes.tickFirst(dummyAx), finalSize, 'reverse', calendar); + var binEnd, bincount; + + // check for too many data points right at the edges of bins + // (>50% within 1% of bin edges) or all data points integral + // and offset the bins accordingly + if(typeof finalSize === 'number') { + binStart = autoShiftNumericBins(binStart, data, dummyAx, dataMin, dataMax); + + bincount = 1 + Math.floor((dataMax - binStart) / finalSize); + binEnd = binStart + bincount * finalSize; + } else { + // month ticks - should be the only nonlinear kind we have at this point. + // dtick (as supplied by axes.autoTick) only has nonlinear values on + // date and log axes, but even if you display a histogram on a log axis + // we bin it on a linear axis (which one could argue against, but that's + // a separate issue) + if(dummyAx.dtick.charAt(0) === 'M') { + binStart = autoShiftMonthBins(binStart, data, finalSize, dataMin, calendar); + } + + // calculate the endpoint for nonlinear ticks - you have to + // just increment until you're done + binEnd = binStart; + bincount = 0; + while(binEnd <= dataMax) { + binEnd = axes.tickIncrement(binEnd, finalSize, false, calendar); + bincount++; + } + } + + return { + start: ax.c2r(binStart, 0, calendar), + end: ax.c2r(binEnd, 0, calendar), + size: finalSize, + _dataSpan: dataMax - dataMin + }; +}; + + +function autoShiftNumericBins(binStart, data, ax, dataMin, dataMax) { + var edgecount = 0; + var midcount = 0; + var intcount = 0; + var blankCount = 0; + + function nearEdge(v) { + // is a value within 1% of a bin edge? + return (1 + (v - binStart) * 100 / ax.dtick) % 100 < 2; + } + + for(var i = 0; i < data.length; i++) { + if(data[i] % 1 === 0) intcount++; + else if(!isNumeric(data[i])) blankCount++; + + if(nearEdge(data[i])) edgecount++; + if(nearEdge(data[i] + ax.dtick / 2)) midcount++; + } + var dataCount = data.length - blankCount; + + if(intcount === dataCount && ax.type !== 'date') { + if(ax.dtick < 1) { + // all integers: if bin size is <1, it's because + // that was specifically requested (large nbins) + // so respect that... but center the bins containing + // integers on those integers + + binStart = dataMin - 0.5 * ax.dtick; + } else { + // otherwise start half an integer down regardless of + // the bin size, just enough to clear up endpoint + // ambiguity about which integers are in which bins. + + binStart -= 0.5; + if(binStart + ax.dtick < dataMin) binStart += ax.dtick; + } + } else if(midcount < dataCount * 0.1) { + if(edgecount > dataCount * 0.3 || + nearEdge(dataMin) || nearEdge(dataMax)) { + // lots of points at the edge, not many in the middle + // shift half a bin + var binshift = ax.dtick / 2; + binStart += (binStart + binshift < dataMin) ? binshift : -binshift; + } + } + return binStart; +} + + +function autoShiftMonthBins(binStart, data, dtick, dataMin, calendar) { + var stats = Lib.findExactDates(data, calendar); + // number of data points that needs to be an exact value + // to shift that increment to (near) the bin center + var threshold = 0.8; + + if(stats.exactDays > threshold) { + var numMonths = Number(dtick.substr(1)); + + if((stats.exactYears > threshold) && (numMonths % 12 === 0)) { + // The exact middle of a non-leap-year is 1.5 days into July + // so if we start the bins here, all but leap years will + // get hover-labeled as exact years. + binStart = axes.tickIncrement(binStart, 'M6', 'reverse') + ONEDAY * 1.5; + } else if(stats.exactMonths > threshold) { + // Months are not as clean, but if we shift half the *longest* + // month (31/2 days) then 31-day months will get labeled exactly + // and shorter months will get labeled with the correct month + // but shifted 12-36 hours into it. + binStart = axes.tickIncrement(binStart, 'M1', 'reverse') + ONEDAY * 15.5; + } else { + // Shifting half a day is exact, but since these are month bins it + // will always give a somewhat odd-looking label, until we do something + // smarter like showing the bin boundaries (or the bounds of the actual + // data in each bin) + binStart -= ONEDAY / 2; + } + var nextBinStart = axes.tickIncrement(binStart, dtick); + + if(nextBinStart <= dataMin) return nextBinStart; + } + return binStart; +} + +// ---------------------------------------------------- +// Ticks and grids +// ---------------------------------------------------- + +// ensure we have tick0, dtick, and tick rounding calculated +axes.prepTicks = function(ax) { + var rng = Lib.simpleMap(ax.range, ax.r2l); + + // calculate max number of (auto) ticks to display based on plot size + if(ax.tickmode === 'auto' || !ax.dtick) { + var nt = ax.nticks; + var minPx; + + if(!nt) { + if(ax.type === 'category' || ax.type === 'multicategory') { + minPx = ax.tickfont ? (ax.tickfont.size || 12) * 1.2 : 15; + nt = ax._length / minPx; + } else { + minPx = ax._id.charAt(0) === 'y' ? 40 : 80; + nt = Lib.constrain(ax._length / minPx, 4, 9) + 1; + } + + // radial axes span half their domain, + // multiply nticks value by two to get correct number of auto ticks. + if(ax._name === 'radialaxis') nt *= 2; + } + + // add a couple of extra digits for filling in ticks when we + // have explicit tickvals without tick text + if(ax.tickmode === 'array') nt *= 100; + + axes.autoTicks(ax, Math.abs(rng[1] - rng[0]) / nt); + // check for a forced minimum dtick + if(ax._minDtick > 0 && ax.dtick < ax._minDtick * 2) { + ax.dtick = ax._minDtick; + ax.tick0 = ax.l2r(ax._forceTick0); + } + } + + // check for missing tick0 + if(!ax.tick0) { + ax.tick0 = (ax.type === 'date') ? '2000-01-01' : 0; + } + + // ensure we don't try to make ticks below our minimum precision + // see https://github.com/plotly/plotly.js/issues/2892 + if(ax.type === 'date' && ax.dtick < 0.1) ax.dtick = 0.1; + + // now figure out rounding of tick values + autoTickRound(ax); +}; + +// calculate the ticks: text, values, positioning +// if ticks are set to automatic, determine the right values (tick0,dtick) +// in any case, set tickround to # of digits to round tick labels to, +// or codes to this effect for log and date scales +axes.calcTicks = function calcTicks(ax) { + axes.prepTicks(ax); + var rng = Lib.simpleMap(ax.range, ax.r2l); + + // now that we've figured out the auto values for formatting + // in case we're missing some ticktext, we can break out for array ticks + if(ax.tickmode === 'array') return arrayTicks(ax); + + // find the first tick + ax._tmin = axes.tickFirst(ax); + + // add a tiny bit so we get ticks which may have rounded out + var startTick = rng[0] * 1.0001 - rng[1] * 0.0001; + var endTick = rng[1] * 1.0001 - rng[0] * 0.0001; + // check for reversed axis + var axrev = (rng[1] < rng[0]); + + // No visible ticks? Quit. + // I've only seen this on category axes with all categories off the edge. + if((ax._tmin < startTick) !== axrev) return []; + + // return the full set of tick vals + var tickVals = []; + if(ax.type === 'category' || ax.type === 'multicategory') { + endTick = (axrev) ? Math.max(-0.5, endTick) : + Math.min(ax._categories.length - 0.5, endTick); + } + + var isDLog = (ax.type === 'log') && !(isNumeric(ax.dtick) || ax.dtick.charAt(0) === 'L'); + + var xPrevious = null; + var maxTicks = Math.max(1000, ax._length || 0); + for(var x = ax._tmin; + (axrev) ? (x >= endTick) : (x <= endTick); + x = axes.tickIncrement(x, ax.dtick, axrev, ax.calendar)) { + // prevent infinite loops - no more than one tick per pixel, + // and make sure each value is different from the previous + if(tickVals.length > maxTicks || x === xPrevious) break; + xPrevious = x; + + var minor = false; + if(isDLog && (x !== (x | 0))) { + minor = true; + } + + tickVals.push({ + minor: minor, + value: x + }); + } + + // If same angle over a full circle, the last tick vals is a duplicate. + // TODO must do something similar for angular date axes. + if(isAngular(ax) && Math.abs(rng[1] - rng[0]) === 360) { + tickVals.pop(); + } + + // save the last tick as well as first, so we can + // show the exponent only on the last one + ax._tmax = (tickVals[tickVals.length - 1] || {}).value; + + // for showing the rest of a date when the main tick label is only the + // latter part: ax._prevDateHead holds what we showed most recently. + // Start with it cleared and mark that we're in calcTicks (ie calculating a + // whole string of these so we should care what the previous date head was!) + ax._prevDateHead = ''; + ax._inCalcTicks = true; + + var ticksOut = new Array(tickVals.length); + for(var i = 0; i < tickVals.length; i++) { + ticksOut[i] = axes.tickText( + ax, + tickVals[i].value, + false, // hover + tickVals[i].minor // noSuffixPrefix + ); + } + + ax._inCalcTicks = false; + + return ticksOut; +}; + +function arrayTicks(ax) { + var vals = ax.tickvals; + var text = ax.ticktext; + var ticksOut = new Array(vals.length); + var rng = Lib.simpleMap(ax.range, ax.r2l); + var r0expanded = rng[0] * 1.0001 - rng[1] * 0.0001; + var r1expanded = rng[1] * 1.0001 - rng[0] * 0.0001; + var tickMin = Math.min(r0expanded, r1expanded); + var tickMax = Math.max(r0expanded, r1expanded); + var j = 0; + + // without a text array, just format the given values as any other ticks + // except with more precision to the numbers + if(!Array.isArray(text)) text = []; + + // make sure showing ticks doesn't accidentally add new categories + // TODO multicategory, if we allow ticktext / tickvals + var tickVal2l = ax.type === 'category' ? ax.d2l_noadd : ax.d2l; + + // array ticks on log axes always show the full number + // (if no explicit ticktext overrides it) + if(ax.type === 'log' && String(ax.dtick).charAt(0) !== 'L') { + ax.dtick = 'L' + Math.pow(10, Math.floor(Math.min(ax.range[0], ax.range[1])) - 1); + } + + for(var i = 0; i < vals.length; i++) { + var vali = tickVal2l(vals[i]); + if(vali > tickMin && vali < tickMax) { + if(text[i] === undefined) ticksOut[j] = axes.tickText(ax, vali); + else ticksOut[j] = tickTextObj(ax, vali, String(text[i])); + j++; + } + } + + if(j < vals.length) ticksOut.splice(j, vals.length - j); + + return ticksOut; +} + +var roundBase10 = [2, 5, 10]; +var roundBase24 = [1, 2, 3, 6, 12]; +var roundBase60 = [1, 2, 5, 10, 15, 30]; +// 2&3 day ticks are weird, but need something btwn 1&7 +var roundDays = [1, 2, 3, 7, 14]; +// approx. tick positions for log axes, showing all (1) and just 1, 2, 5 (2) +// these don't have to be exact, just close enough to round to the right value +var roundLog1 = [-0.046, 0, 0.301, 0.477, 0.602, 0.699, 0.778, 0.845, 0.903, 0.954, 1]; +var roundLog2 = [-0.301, 0, 0.301, 0.699, 1]; +// N.B. `thetaunit; 'radians' angular axes must be converted to degrees +var roundAngles = [15, 30, 45, 90, 180]; + +function roundDTick(roughDTick, base, roundingSet) { + return base * Lib.roundUp(roughDTick / base, roundingSet); +} + +// autoTicks: calculate best guess at pleasant ticks for this axis +// inputs: +// ax - an axis object +// roughDTick - rough tick spacing (to be turned into a nice round number) +// outputs (into ax): +// tick0: starting point for ticks (not necessarily on the graph) +// usually 0 for numeric (=10^0=1 for log) or jan 1, 2000 for dates +// dtick: the actual, nice round tick spacing, usually a little larger than roughDTick +// if the ticks are spaced linearly (linear scale, categories, +// log with only full powers, date ticks < month), +// this will just be a number +// months: M# +// years: M# where # is 12*number of years +// log with linear ticks: L# where # is the linear tick spacing +// log showing powers plus some intermediates: +// D1 shows all digits, D2 shows 2 and 5 +axes.autoTicks = function(ax, roughDTick) { + var base; + + function getBase(v) { + return Math.pow(v, Math.floor(Math.log(roughDTick) / Math.LN10)); + } + + if(ax.type === 'date') { + ax.tick0 = Lib.dateTick0(ax.calendar); + // the criteria below are all based on the rough spacing we calculate + // being > half of the final unit - so precalculate twice the rough val + var roughX2 = 2 * roughDTick; + + if(roughX2 > ONEAVGYEAR) { + roughDTick /= ONEAVGYEAR; + base = getBase(10); + ax.dtick = 'M' + (12 * roundDTick(roughDTick, base, roundBase10)); + } else if(roughX2 > ONEAVGMONTH) { + roughDTick /= ONEAVGMONTH; + ax.dtick = 'M' + roundDTick(roughDTick, 1, roundBase24); + } else if(roughX2 > ONEDAY) { + ax.dtick = roundDTick(roughDTick, ONEDAY, roundDays); + // get week ticks on sunday + // this will also move the base tick off 2000-01-01 if dtick is + // 2 or 3 days... but that's a weird enough case that we'll ignore it. + ax.tick0 = Lib.dateTick0(ax.calendar, true); + } else if(roughX2 > ONEHOUR) { + ax.dtick = roundDTick(roughDTick, ONEHOUR, roundBase24); + } else if(roughX2 > ONEMIN) { + ax.dtick = roundDTick(roughDTick, ONEMIN, roundBase60); + } else if(roughX2 > ONESEC) { + ax.dtick = roundDTick(roughDTick, ONESEC, roundBase60); + } else { + // milliseconds + base = getBase(10); + ax.dtick = roundDTick(roughDTick, base, roundBase10); + } + } else if(ax.type === 'log') { + ax.tick0 = 0; + var rng = Lib.simpleMap(ax.range, ax.r2l); + + if(roughDTick > 0.7) { + // only show powers of 10 + ax.dtick = Math.ceil(roughDTick); + } else if(Math.abs(rng[1] - rng[0]) < 1) { + // span is less than one power of 10 + var nt = 1.5 * Math.abs((rng[1] - rng[0]) / roughDTick); + + // ticks on a linear scale, labeled fully + roughDTick = Math.abs(Math.pow(10, rng[1]) - + Math.pow(10, rng[0])) / nt; + base = getBase(10); + ax.dtick = 'L' + roundDTick(roughDTick, base, roundBase10); + } else { + // include intermediates between powers of 10, + // labeled with small digits + // ax.dtick = "D2" (show 2 and 5) or "D1" (show all digits) + ax.dtick = (roughDTick > 0.3) ? 'D2' : 'D1'; + } + } else if(ax.type === 'category' || ax.type === 'multicategory') { + ax.tick0 = 0; + ax.dtick = Math.ceil(Math.max(roughDTick, 1)); + } else if(isAngular(ax)) { + ax.tick0 = 0; + base = 1; + ax.dtick = roundDTick(roughDTick, base, roundAngles); + } else { + // auto ticks always start at 0 + ax.tick0 = 0; + base = getBase(10); + ax.dtick = roundDTick(roughDTick, base, roundBase10); + } + + // prevent infinite loops + if(ax.dtick === 0) ax.dtick = 1; + + // TODO: this is from log axis histograms with autorange off + if(!isNumeric(ax.dtick) && typeof ax.dtick !== 'string') { + var olddtick = ax.dtick; + ax.dtick = 1; + throw 'ax.dtick error: ' + String(olddtick); + } +}; + +// after dtick is already known, find tickround = precision +// to display in tick labels +// for numeric ticks, integer # digits after . to round to +// for date ticks, the last date part to show (y,m,d,H,M,S) +// or an integer # digits past seconds +function autoTickRound(ax) { + var dtick = ax.dtick; + + ax._tickexponent = 0; + if(!isNumeric(dtick) && typeof dtick !== 'string') { + dtick = 1; + } + + if(ax.type === 'category' || ax.type === 'multicategory') { + ax._tickround = null; + } + if(ax.type === 'date') { + // If tick0 is unusual, give tickround a bit more information + // not necessarily *all* the information in tick0 though, if it's really odd + // minimal string length for tick0: 'd' is 10, 'M' is 16, 'S' is 19 + // take off a leading minus (year < 0) and i (intercalary month) so length is consistent + var tick0ms = ax.r2l(ax.tick0); + var tick0str = ax.l2r(tick0ms).replace(/(^-|i)/g, ''); + var tick0len = tick0str.length; + + if(String(dtick).charAt(0) === 'M') { + // any tick0 more specific than a year: alway show the full date + if(tick0len > 10 || tick0str.substr(5) !== '01-01') ax._tickround = 'd'; + // show the month unless ticks are full multiples of a year + else ax._tickround = (+(dtick.substr(1)) % 12 === 0) ? 'y' : 'm'; + } else if((dtick >= ONEDAY && tick0len <= 10) || (dtick >= ONEDAY * 15)) ax._tickround = 'd'; + else if((dtick >= ONEMIN && tick0len <= 16) || (dtick >= ONEHOUR)) ax._tickround = 'M'; + else if((dtick >= ONESEC && tick0len <= 19) || (dtick >= ONEMIN)) ax._tickround = 'S'; + else { + // tickround is a number of digits of fractional seconds + // of any two adjacent ticks, at least one will have the maximum fractional digits + // of all possible ticks - so take the max. length of tick0 and the next one + var tick1len = ax.l2r(tick0ms + dtick).replace(/^-/, '').length; + ax._tickround = Math.max(tick0len, tick1len) - 20; + + // We shouldn't get here... but in case there's a situation I'm + // not thinking of where tick0str and tick1str are identical or + // something, fall back on maximum precision + if(ax._tickround < 0) ax._tickround = 4; + } + } else if(isNumeric(dtick) || dtick.charAt(0) === 'L') { + // linear or log (except D1, D2) + var rng = ax.range.map(ax.r2d || Number); + if(!isNumeric(dtick)) dtick = Number(dtick.substr(1)); + // 2 digits past largest digit of dtick + ax._tickround = 2 - Math.floor(Math.log(dtick) / Math.LN10 + 0.01); + + var maxend = Math.max(Math.abs(rng[0]), Math.abs(rng[1])); + var rangeexp = Math.floor(Math.log(maxend) / Math.LN10 + 0.01); + if(Math.abs(rangeexp) > 3) { + if(isSIFormat(ax.exponentformat) && !beyondSI(rangeexp)) { + ax._tickexponent = 3 * Math.round((rangeexp - 1) / 3); + } else ax._tickexponent = rangeexp; + } + } else { + // D1 or D2 (log) + ax._tickround = null; + } +} + +// months and years don't have constant millisecond values +// (but a year is always 12 months so we only need months) +// log-scale ticks are also not consistently spaced, except +// for pure powers of 10 +// numeric ticks always have constant differences, other datetime ticks +// can all be calculated as constant number of milliseconds +axes.tickIncrement = function(x, dtick, axrev, calendar) { + var axSign = axrev ? -1 : 1; + + // includes linear, all dates smaller than month, and pure 10^n in log + if(isNumeric(dtick)) return x + axSign * dtick; + + // everything else is a string, one character plus a number + var tType = dtick.charAt(0); + var dtSigned = axSign * Number(dtick.substr(1)); + + // Dates: months (or years - see Lib.incrementMonth) + if(tType === 'M') return Lib.incrementMonth(x, dtSigned, calendar); + + // Log scales: Linear, Digits + else if(tType === 'L') return Math.log(Math.pow(10, x) + dtSigned) / Math.LN10; + + // log10 of 2,5,10, or all digits (logs just have to be + // close enough to round) + else if(tType === 'D') { + var tickset = (dtick === 'D2') ? roundLog2 : roundLog1; + var x2 = x + axSign * 0.01; + var frac = Lib.roundUp(Lib.mod(x2, 1), tickset, axrev); + + return Math.floor(x2) + + Math.log(d3.round(Math.pow(10, frac), 1)) / Math.LN10; + } else throw 'unrecognized dtick ' + String(dtick); +}; + +// calculate the first tick on an axis +axes.tickFirst = function(ax) { + var r2l = ax.r2l || Number; + var rng = Lib.simpleMap(ax.range, r2l); + var axrev = rng[1] < rng[0]; + var sRound = axrev ? Math.floor : Math.ceil; + // add a tiny extra bit to make sure we get ticks + // that may have been rounded out + var r0 = rng[0] * 1.0001 - rng[1] * 0.0001; + var dtick = ax.dtick; + var tick0 = r2l(ax.tick0); + + if(isNumeric(dtick)) { + var tmin = sRound((r0 - tick0) / dtick) * dtick + tick0; + + // make sure no ticks outside the category list + if(ax.type === 'category' || ax.type === 'multicategory') { + tmin = Lib.constrain(tmin, 0, ax._categories.length - 1); + } + return tmin; + } + + var tType = dtick.charAt(0); + var dtNum = Number(dtick.substr(1)); + + // Dates: months (or years) + if(tType === 'M') { + var cnt = 0; + var t0 = tick0; + var t1, mult, newDTick; + + // This algorithm should work for *any* nonlinear (but close to linear!) + // tick spacing. Limit to 10 iterations, for gregorian months it's normally <=3. + while(cnt < 10) { + t1 = axes.tickIncrement(t0, dtick, axrev, ax.calendar); + if((t1 - r0) * (t0 - r0) <= 0) { + // t1 and t0 are on opposite sides of r0! we've succeeded! + if(axrev) return Math.min(t0, t1); + return Math.max(t0, t1); + } + mult = (r0 - ((t0 + t1) / 2)) / (t1 - t0); + newDTick = tType + ((Math.abs(Math.round(mult)) || 1) * dtNum); + t0 = axes.tickIncrement(t0, newDTick, mult < 0 ? !axrev : axrev, ax.calendar); + cnt++; + } + Lib.error('tickFirst did not converge', ax); + return t0; + } else if(tType === 'L') { + // Log scales: Linear, Digits + + return Math.log(sRound( + (Math.pow(10, r0) - tick0) / dtNum) * dtNum + tick0) / Math.LN10; + } else if(tType === 'D') { + var tickset = (dtick === 'D2') ? roundLog2 : roundLog1; + var frac = Lib.roundUp(Lib.mod(r0, 1), tickset, axrev); + + return Math.floor(r0) + + Math.log(d3.round(Math.pow(10, frac), 1)) / Math.LN10; + } else throw 'unrecognized dtick ' + String(dtick); +}; + +// draw the text for one tick. +// px,py are the location on gd.paper +// prefix is there so the x axis ticks can be dropped a line +// ax is the axis layout, x is the tick value +// hover is a (truthy) flag for whether to show numbers with a bit +// more precision for hovertext +axes.tickText = function(ax, x, hover, noSuffixPrefix) { + var out = tickTextObj(ax, x); + var arrayMode = ax.tickmode === 'array'; + var extraPrecision = hover || arrayMode; + var axType = ax.type; + // TODO multicategory, if we allow ticktext / tickvals + var tickVal2l = axType === 'category' ? ax.d2l_noadd : ax.d2l; + var i; + + if(arrayMode && Array.isArray(ax.ticktext)) { + var rng = Lib.simpleMap(ax.range, ax.r2l); + var minDiff = Math.abs(rng[1] - rng[0]) / 10000; + + for(i = 0; i < ax.ticktext.length; i++) { + if(Math.abs(x - tickVal2l(ax.tickvals[i])) < minDiff) break; + } + if(i < ax.ticktext.length) { + out.text = String(ax.ticktext[i]); + return out; + } + } + + function isHidden(showAttr) { + if(showAttr === undefined) return true; + if(hover) return showAttr === 'none'; + + var firstOrLast = { + first: ax._tmin, + last: ax._tmax + }[showAttr]; + + return showAttr !== 'all' && x !== firstOrLast; + } + + var hideexp = hover ? + 'never' : + ax.exponentformat !== 'none' && isHidden(ax.showexponent) ? 'hide' : ''; + + if(axType === 'date') formatDate(ax, out, hover, extraPrecision); + else if(axType === 'log') formatLog(ax, out, hover, extraPrecision, hideexp); + else if(axType === 'category') formatCategory(ax, out); + else if(axType === 'multicategory') formatMultiCategory(ax, out, hover); + else if(isAngular(ax)) formatAngle(ax, out, hover, extraPrecision, hideexp); + else formatLinear(ax, out, hover, extraPrecision, hideexp); + + // add prefix and suffix + if(!noSuffixPrefix) { + if(ax.tickprefix && !isHidden(ax.showtickprefix)) out.text = ax.tickprefix + out.text; + if(ax.ticksuffix && !isHidden(ax.showticksuffix)) out.text += ax.ticksuffix; + } + + // Setup ticks and grid lines boundaries + // at 1/2 a 'category' to the left/bottom + if(ax.tickson === 'boundaries' || ax.showdividers) { + var inbounds = function(v) { + var p = ax.l2p(v); + return p >= 0 && p <= ax._length ? v : null; + }; + + out.xbnd = [ + inbounds(out.x - 0.5), + inbounds(out.x + ax.dtick - 0.5) + ]; + } + + return out; +}; + +/** + * create text for a hover label on this axis, with special handling of + * log axes (where negative values can't be displayed but can appear in hover text) + * + * @param {object} ax: the axis to format text for + * @param {number} val: calcdata value to format + * @param {Optional(number)} val2: a second value to display + * + * @returns {string} `val` formatted as a string appropriate to this axis, or + * `val` and `val2` as a range (ie ' - ') if `val2` is provided and + * it's different from `val`. + */ +axes.hoverLabelText = function(ax, val, val2) { + if(val2 !== BADNUM && val2 !== val) { + return axes.hoverLabelText(ax, val) + ' - ' + axes.hoverLabelText(ax, val2); + } + + var logOffScale = (ax.type === 'log' && val <= 0); + var tx = axes.tickText(ax, ax.c2l(logOffScale ? -val : val), 'hover').text; + + if(logOffScale) { + return val === 0 ? '0' : MINUS_SIGN + tx; + } + + // TODO: should we do something special if the axis calendar and + // the data calendar are different? Somehow display both dates with + // their system names? Right now it will just display in the axis calendar + // but users could add the other one as text. + return tx; +}; + +function tickTextObj(ax, x, text) { + var tf = ax.tickfont || {}; + + return { + x: x, + dx: 0, + dy: 0, + text: text || '', + fontSize: tf.size, + font: tf.family, + fontColor: tf.color + }; +} + +function formatDate(ax, out, hover, extraPrecision) { + var tr = ax._tickround; + var fmt = (hover && ax.hoverformat) || axes.getTickFormat(ax); + + if(extraPrecision) { + // second or sub-second precision: extra always shows max digits. + // for other fields, extra precision just adds one field. + if(isNumeric(tr)) tr = 4; + else tr = {y: 'm', m: 'd', d: 'M', M: 'S', S: 4}[tr]; + } + + var dateStr = Lib.formatDate(out.x, fmt, tr, ax._dateFormat, ax.calendar, ax._extraFormat); + var headStr; + + var splitIndex = dateStr.indexOf('\n'); + if(splitIndex !== -1) { + headStr = dateStr.substr(splitIndex + 1); + dateStr = dateStr.substr(0, splitIndex); + } + + if(extraPrecision) { + // if extraPrecision led to trailing zeros, strip them off + // actually, this can lead to removing even more zeros than + // in the original rounding, but that's fine because in these + // contexts uniformity is not so important (if there's even + // anything to be uniform with!) + + // can we remove the whole time part? + if(dateStr === '00:00:00' || dateStr === '00:00') { + dateStr = headStr; + headStr = ''; + } else if(dateStr.length === 8) { + // strip off seconds if they're zero (zero fractional seconds + // are already omitted) + // but we never remove minutes and leave just hours + dateStr = dateStr.replace(/:00$/, ''); + } + } + + if(headStr) { + if(hover) { + // hover puts it all on one line, so headPart works best up front + // except for year headPart: turn this into "Jan 1, 2000" etc. + if(tr === 'd') dateStr += ', ' + headStr; + else dateStr = headStr + (dateStr ? ', ' + dateStr : ''); + } else if(!ax._inCalcTicks || (headStr !== ax._prevDateHead)) { + dateStr += '
' + headStr; + ax._prevDateHead = headStr; + } + } + + out.text = dateStr; +} + +function formatLog(ax, out, hover, extraPrecision, hideexp) { + var dtick = ax.dtick; + var x = out.x; + var tickformat = ax.tickformat; + var dtChar0 = typeof dtick === 'string' && dtick.charAt(0); + + if(hideexp === 'never') { + // If this is a hover label, then we must *never* hide the exponent + // for the sake of display, which could give the wrong value by + // potentially many orders of magnitude. If hideexp was 'never', then + // it's now succeeded by preventing the other condition from automating + // this choice. Thus we can unset it so that the axis formatting takes + // precedence. + hideexp = ''; + } + + if(extraPrecision && (dtChar0 !== 'L')) { + dtick = 'L3'; + dtChar0 = 'L'; + } + + if(tickformat || (dtChar0 === 'L')) { + out.text = numFormat(Math.pow(10, x), ax, hideexp, extraPrecision); + } else if(isNumeric(dtick) || ((dtChar0 === 'D') && (Lib.mod(x + 0.01, 1) < 0.1))) { + var p = Math.round(x); + var absP = Math.abs(p); + var exponentFormat = ax.exponentformat; + if(exponentFormat === 'power' || (isSIFormat(exponentFormat) && beyondSI(p))) { + if(p === 0) out.text = 1; + else if(p === 1) out.text = '10'; + else out.text = '10' + (p > 1 ? '' : MINUS_SIGN) + absP + ''; + + out.fontSize *= 1.25; + } else if((exponentFormat === 'e' || exponentFormat === 'E') && absP > 2) { + out.text = '1' + exponentFormat + (p > 0 ? '+' : MINUS_SIGN) + absP; + } else { + out.text = numFormat(Math.pow(10, x), ax, '', 'fakehover'); + if(dtick === 'D1' && ax._id.charAt(0) === 'y') { + out.dy -= out.fontSize / 6; + } + } + } else if(dtChar0 === 'D') { + out.text = String(Math.round(Math.pow(10, Lib.mod(x, 1)))); + out.fontSize *= 0.75; + } else throw 'unrecognized dtick ' + String(dtick); + + // if 9's are printed on log scale, move the 10's away a bit + if(ax.dtick === 'D1') { + var firstChar = String(out.text).charAt(0); + if(firstChar === '0' || firstChar === '1') { + if(ax._id.charAt(0) === 'y') { + out.dx -= out.fontSize / 4; + } else { + out.dy += out.fontSize / 2; + out.dx += (ax.range[1] > ax.range[0] ? 1 : -1) * + out.fontSize * (x < 0 ? 0.5 : 0.25); + } + } + } +} + +function formatCategory(ax, out) { + var tt = ax._categories[Math.round(out.x)]; + if(tt === undefined) tt = ''; + out.text = String(tt); +} + +function formatMultiCategory(ax, out, hover) { + var v = Math.round(out.x); + var cats = ax._categories[v] || []; + var tt = cats[1] === undefined ? '' : String(cats[1]); + var tt2 = cats[0] === undefined ? '' : String(cats[0]); + + if(hover) { + // TODO is this what we want? + out.text = tt2 + ' - ' + tt; + } else { + // setup for secondary labels + out.text = tt; + out.text2 = tt2; + } +} + +function formatLinear(ax, out, hover, extraPrecision, hideexp) { + if(hideexp === 'never') { + // If this is a hover label, then we must *never* hide the exponent + // for the sake of display, which could give the wrong value by + // potentially many orders of magnitude. If hideexp was 'never', then + // it's now succeeded by preventing the other condition from automating + // this choice. Thus we can unset it so that the axis formatting takes + // precedence. + hideexp = ''; + } else if(ax.showexponent === 'all' && Math.abs(out.x / ax.dtick) < 1e-6) { + // don't add an exponent to zero if we're showing all exponents + // so the only reason you'd show an exponent on zero is if it's the + // ONLY tick to get an exponent (first or last) + hideexp = 'hide'; + } + out.text = numFormat(out.x, ax, hideexp, extraPrecision); +} + +function formatAngle(ax, out, hover, extraPrecision, hideexp) { + if(ax.thetaunit === 'radians' && !hover) { + var num = out.x / 180; + + if(num === 0) { + out.text = '0'; + } else { + var frac = num2frac(num); + + if(frac[1] >= 100) { + out.text = numFormat(Lib.deg2rad(out.x), ax, hideexp, extraPrecision); + } else { + var isNeg = out.x < 0; + + if(frac[1] === 1) { + if(frac[0] === 1) out.text = 'π'; + else out.text = frac[0] + 'π'; + } else { + out.text = [ + '', frac[0], '', + '⁄', + '', frac[1], '', + 'π' + ].join(''); + } + + if(isNeg) out.text = MINUS_SIGN + out.text; + } + } + } else { + out.text = numFormat(out.x, ax, hideexp, extraPrecision); + } +} + +// inspired by +// https://github.com/yisibl/num2fraction/blob/master/index.js +function num2frac(num) { + function almostEq(a, b) { + return Math.abs(a - b) <= 1e-6; + } + + function findGCD(a, b) { + return almostEq(b, 0) ? a : findGCD(b, a % b); + } + + function findPrecision(n) { + var e = 1; + while(!almostEq(Math.round(n * e) / e, n)) { + e *= 10; + } + return e; + } + + var precision = findPrecision(num); + var number = num * precision; + var gcd = Math.abs(findGCD(number, precision)); + + return [ + // numerator + Math.round(number / gcd), + // denominator + Math.round(precision / gcd) + ]; +} + +// format a number (tick value) according to the axis settings +// new, more reliable procedure than d3.round or similar: +// add half the rounding increment, then stringify and truncate +// also automatically switch to sci. notation +var SIPREFIXES = ['f', 'p', 'n', 'μ', 'm', '', 'k', 'M', 'G', 'T']; + +function isSIFormat(exponentFormat) { + return exponentFormat === 'SI' || exponentFormat === 'B'; +} + +// are we beyond the range of common SI prefixes? +// 10^-16 -> 1x10^-16 +// 10^-15 -> 1f +// ... +// 10^14 -> 100T +// 10^15 -> 1x10^15 +// 10^16 -> 1x10^16 +function beyondSI(exponent) { + return exponent > 14 || exponent < -15; +} + +function numFormat(v, ax, fmtoverride, hover) { + var isNeg = v < 0; + // max number of digits past decimal point to show + var tickRound = ax._tickround; + var exponentFormat = fmtoverride || ax.exponentformat || 'B'; + var exponent = ax._tickexponent; + var tickformat = axes.getTickFormat(ax); + var separatethousands = ax.separatethousands; + + // special case for hover: set exponent just for this value, and + // add a couple more digits of precision over tick labels + if(hover) { + // make a dummy axis obj to get the auto rounding and exponent + var ah = { + exponentformat: exponentFormat, + dtick: ax.showexponent === 'none' ? ax.dtick : + (isNumeric(v) ? Math.abs(v) || 1 : 1), + // if not showing any exponents, don't change the exponent + // from what we calculate + range: ax.showexponent === 'none' ? ax.range.map(ax.r2d) : [0, v || 1] + }; + autoTickRound(ah); + tickRound = (Number(ah._tickround) || 0) + 4; + exponent = ah._tickexponent; + if(ax.hoverformat) tickformat = ax.hoverformat; + } + + if(tickformat) return ax._numFormat(tickformat)(v).replace(/-/g, MINUS_SIGN); + + // 'epsilon' - rounding increment + var e = Math.pow(10, -tickRound) / 2; + + // exponentFormat codes: + // 'e' (1.2e+6, default) + // 'E' (1.2E+6) + // 'SI' (1.2M) + // 'B' (same as SI except 10^9=B not G) + // 'none' (1200000) + // 'power' (1.2x10^6) + // 'hide' (1.2, use 3rd argument=='hide' to eg + // only show exponent on last tick) + if(exponentFormat === 'none') exponent = 0; + + // take the sign out, put it back manually at the end + // - makes cases easier + v = Math.abs(v); + if(v < e) { + // 0 is just 0, but may get exponent if it's the last tick + v = '0'; + isNeg = false; + } else { + v += e; + // take out a common exponent, if any + if(exponent) { + v *= Math.pow(10, -exponent); + tickRound += exponent; + } + // round the mantissa + if(tickRound === 0) v = String(Math.floor(v)); + else if(tickRound < 0) { + v = String(Math.round(v)); + v = v.substr(0, v.length + tickRound); + for(var i = tickRound; i < 0; i++) v += '0'; + } else { + v = String(v); + var dp = v.indexOf('.') + 1; + if(dp) v = v.substr(0, dp + tickRound).replace(/\.?0+$/, ''); + } + // insert appropriate decimal point and thousands separator + v = Lib.numSeparate(v, ax._separators, separatethousands); + } + + // add exponent + if(exponent && exponentFormat !== 'hide') { + if(isSIFormat(exponentFormat) && beyondSI(exponent)) exponentFormat = 'power'; + + var signedExponent; + if(exponent < 0) signedExponent = MINUS_SIGN + -exponent; + else if(exponentFormat !== 'power') signedExponent = '+' + exponent; + else signedExponent = String(exponent); + + if(exponentFormat === 'e' || exponentFormat === 'E') { + v += exponentFormat + signedExponent; + } else if(exponentFormat === 'power') { + v += '×10' + signedExponent + ''; + } else if(exponentFormat === 'B' && exponent === 9) { + v += 'B'; + } else if(isSIFormat(exponentFormat)) { + v += SIPREFIXES[exponent / 3 + 5]; + } + } + + // put sign back in and return + // replace standard minus character (which is technically a hyphen) + // with a true minus sign + if(isNeg) return MINUS_SIGN + v; + return v; +} + +axes.getTickFormat = function(ax) { + var i; + + function convertToMs(dtick) { + return typeof dtick !== 'string' ? dtick : Number(dtick.replace('M', '')) * ONEAVGMONTH; + } + + function compareLogTicks(left, right) { + var priority = ['L', 'D']; + if(typeof left === typeof right) { + if(typeof left === 'number') { + return left - right; + } else { + var leftPriority = priority.indexOf(left.charAt(0)); + var rightPriority = priority.indexOf(right.charAt(0)); + if(leftPriority === rightPriority) { + return Number(left.replace(/(L|D)/g, '')) - Number(right.replace(/(L|D)/g, '')); + } else { + return leftPriority - rightPriority; + } + } + } else { + return typeof left === 'number' ? 1 : -1; + } + } + + function isProperStop(dtick, range, convert) { + var convertFn = convert || function(x) { return x;}; + var leftDtick = range[0]; + var rightDtick = range[1]; + return ((!leftDtick && typeof leftDtick !== 'number') || convertFn(leftDtick) <= convertFn(dtick)) && + ((!rightDtick && typeof rightDtick !== 'number') || convertFn(rightDtick) >= convertFn(dtick)); + } + + function isProperLogStop(dtick, range) { + var isLeftDtickNull = range[0] === null; + var isRightDtickNull = range[1] === null; + var isDtickInRangeLeft = compareLogTicks(dtick, range[0]) >= 0; + var isDtickInRangeRight = compareLogTicks(dtick, range[1]) <= 0; + return (isLeftDtickNull || isDtickInRangeLeft) && (isRightDtickNull || isDtickInRangeRight); + } + + var tickstop, stopi; + if(ax.tickformatstops && ax.tickformatstops.length > 0) { + switch(ax.type) { + case 'date': + case 'linear': { + for(i = 0; i < ax.tickformatstops.length; i++) { + stopi = ax.tickformatstops[i]; + if(stopi.enabled && isProperStop(ax.dtick, stopi.dtickrange, convertToMs)) { + tickstop = stopi; + break; + } + } + break; + } + case 'log': { + for(i = 0; i < ax.tickformatstops.length; i++) { + stopi = ax.tickformatstops[i]; + if(stopi.enabled && isProperLogStop(ax.dtick, stopi.dtickrange)) { + tickstop = stopi; + break; + } + } + break; + } + default: + } + } + return tickstop ? tickstop.value : ax.tickformat; +}; + +// getSubplots - extract all subplot IDs we need +// as an array of items like 'xy', 'x2y', 'x2y2'... +// sorted by x (x,x2,x3...) then y +// optionally restrict to only subplots containing axis object ax +// +// NOTE: this is currently only used OUTSIDE plotly.js (toolpanel, webapp) +// ideally we get rid of it there (or just copy this there) and remove it here +axes.getSubplots = function(gd, ax) { + var subplotObj = gd._fullLayout._subplots; + var allSubplots = subplotObj.cartesian.concat(subplotObj.gl2d || []); + + var out = ax ? axes.findSubplotsWithAxis(allSubplots, ax) : allSubplots; + + out.sort(function(a, b) { + var aParts = a.substr(1).split('y'); + var bParts = b.substr(1).split('y'); + + if(aParts[0] === bParts[0]) return +aParts[1] - +bParts[1]; + return +aParts[0] - +bParts[0]; + }); + + return out; +}; + +// find all subplots with axis 'ax' +// NOTE: this is only used in axes.getSubplots (only used outside plotly.js) and +// gl2d/convert (where it restricts axis subplots to only those with gl2d) +axes.findSubplotsWithAxis = function(subplots, ax) { + var axMatch = new RegExp( + (ax._id.charAt(0) === 'x') ? ('^' + ax._id + 'y') : (ax._id + '$') + ); + var subplotsWithAx = []; + + for(var i = 0; i < subplots.length; i++) { + var sp = subplots[i]; + if(axMatch.test(sp)) subplotsWithAx.push(sp); + } + + return subplotsWithAx; +}; + +// makeClipPaths: prepare clipPaths for all single axes and all possible xy pairings +axes.makeClipPaths = function(gd) { + var fullLayout = gd._fullLayout; + + // for more info: https://github.com/plotly/plotly.js/issues/2595 + if(fullLayout._hasOnlyLargeSploms) return; + + var fullWidth = {_offset: 0, _length: fullLayout.width, _id: ''}; + var fullHeight = {_offset: 0, _length: fullLayout.height, _id: ''}; + var xaList = axes.list(gd, 'x', true); + var yaList = axes.list(gd, 'y', true); + var clipList = []; + var i, j; + + for(i = 0; i < xaList.length; i++) { + clipList.push({x: xaList[i], y: fullHeight}); + for(j = 0; j < yaList.length; j++) { + if(i === 0) clipList.push({x: fullWidth, y: yaList[j]}); + clipList.push({x: xaList[i], y: yaList[j]}); + } + } + + // selectors don't work right with camelCase tags, + // have to use class instead + // https://groups.google.com/forum/#!topic/d3-js/6EpAzQ2gU9I + var axClips = fullLayout._clips.selectAll('.axesclip') + .data(clipList, function(d) { return d.x._id + d.y._id; }); + + axClips.enter().append('clipPath') + .classed('axesclip', true) + .attr('id', function(d) { return 'clip' + fullLayout._uid + d.x._id + d.y._id; }) + .append('rect'); + + axClips.exit().remove(); + + axClips.each(function(d) { + d3.select(this).select('rect').attr({ + x: d.x._offset || 0, + y: d.y._offset || 0, + width: d.x._length || 1, + height: d.y._length || 1 + }); + }); +}; + +/** + * Main multi-axis drawing routine! + * + * @param {DOM element} gd : graph div + * @param {string or array of strings} arg : polymorphic argument + * @param {object} opts: + * - @param {boolean} skipTitle : optional flag to skip axis title draw/update + * + * Signature 1: Axes.draw(gd, 'redraw') + * use this to clear and redraw all axes on graph + * + * Signature 2: Axes.draw(gd, '') + * use this to draw all axes on graph w/o the selectAll().remove() + * of the 'redraw' signature + * + * Signature 3: Axes.draw(gd, [axId, axId2, ...]) + * where the items are axis id string, + * use this to update multiple axes in one call + * + * N.B draw updates: + * - ax._r (stored range for use by zoom/pan) + * - ax._rl (stored linearized range for use by zoom/pan) + */ +axes.draw = function(gd, arg, opts) { + var fullLayout = gd._fullLayout; + + if(arg === 'redraw') { + fullLayout._paper.selectAll('g.subplot').each(function(d) { + var id = d[0]; + var plotinfo = fullLayout._plots[id]; + var xa = plotinfo.xaxis; + var ya = plotinfo.yaxis; + + plotinfo.xaxislayer.selectAll('.' + xa._id + 'tick').remove(); + plotinfo.yaxislayer.selectAll('.' + ya._id + 'tick').remove(); + plotinfo.xaxislayer.selectAll('.' + xa._id + 'tick2').remove(); + plotinfo.yaxislayer.selectAll('.' + ya._id + 'tick2').remove(); + plotinfo.xaxislayer.selectAll('.' + xa._id + 'divider').remove(); + plotinfo.yaxislayer.selectAll('.' + ya._id + 'divider').remove(); + + if(plotinfo.gridlayer) plotinfo.gridlayer.selectAll('path').remove(); + if(plotinfo.zerolinelayer) plotinfo.zerolinelayer.selectAll('path').remove(); + + fullLayout._infolayer.select('.g-' + xa._id + 'title').remove(); + fullLayout._infolayer.select('.g-' + ya._id + 'title').remove(); + }); + } + + var axList = (!arg || arg === 'redraw') ? axes.listIds(gd) : arg; + + return Lib.syncOrAsync(axList.map(function(axId) { + return function() { + if(!axId) return; + + var ax = axes.getFromId(gd, axId); + var axDone = axes.drawOne(gd, ax, opts); + + ax._r = ax.range.slice(); + ax._rl = Lib.simpleMap(ax._r, ax.r2l); + + return axDone; + }; + })); +}; + +/** + * Draw one cartesian axis + * + * @param {DOM element} gd + * @param {object} ax (full) axis object + * @param {object} opts + * - @param {boolean} skipTitle (set to true to skip axis title draw call) + * + * Depends on: + * - ax._mainSubplot (from linkSubplots) + * - ax._mainAxis + * - ax._anchorAxis + * - ax._subplotsWith + * - ax._counterDomainMin, ax._counterDomainMax (optionally, from linkSubplots) + * - ax._tickAngles (on redraw only, old value relinked during supplyDefaults) + * - ax._mainLinePosition (from lsInner) + * - ax._mainMirrorPosition + * - ax._linepositions + * + * Fills in: + * - ax._vals: + * - ax._gridVals: + * - ax._selections: + * - ax._tickAngles: + * - ax._depth (when required only): + * - and calls ax.setScale + */ +axes.drawOne = function(gd, ax, opts) { + opts = opts || {}; + + var i, sp, plotinfo; + + ax.setScale(); + + var fullLayout = gd._fullLayout; + var axId = ax._id; + var axLetter = axId.charAt(0); + var counterLetter = axes.counterLetter(axId); + var mainLinePosition = ax._mainLinePosition; + var mainMirrorPosition = ax._mainMirrorPosition; + var mainPlotinfo = fullLayout._plots[ax._mainSubplot]; + var mainAxLayer = mainPlotinfo[axLetter + 'axislayer']; + + var vals = ax._vals = axes.calcTicks(ax); + + // Add a couple of axis properties that should cause us to recreate + // elements. Used in d3 data function. + var axInfo = [ax.mirror, mainLinePosition, mainMirrorPosition].join('_'); + for(i = 0; i < vals.length; i++) { + vals[i].axInfo = axInfo; + } + + // stash selections to avoid DOM queries e.g. + // - stash tickLabels selection, so that drawTitle can use it to scoot title + ax._selections = {}; + // stash tick angle (including the computed 'auto' values) per tick-label class + // linkup 'previous' tick angles on redraws + if(ax._tickAngles) ax._prevTickAngles = ax._tickAngles; + ax._tickAngles = {}; + // measure [in px] between axis position and outward-most part of bounding box + // (touching either the tick label or ticks) + // depth can be expansive to compute, so we only do so when required + ax._depth = null; + + // calcLabelLevelBbox can be expensive, + // so make sure to not call it twice during the same Axes.drawOne call + // by stashing label-level bounding boxes per tick-label class + var llbboxes = {}; + function getLabelLevelBbox(suffix) { + var cls = axId + (suffix || 'tick'); + if(!llbboxes[cls]) llbboxes[cls] = calcLabelLevelBbox(ax, cls); + return llbboxes[cls]; + } + + if(!ax.visible) return; + + var transFn = axes.makeTransFn(ax); + var tickVals; + // We remove zero lines, grid lines, and inside ticks if they're within 1px of the end + // The key case here is removing zero lines when the axis bound is zero + var valsClipped; + + if(ax.tickson === 'boundaries') { + var boundaryVals = getBoundaryVals(ax, vals); + valsClipped = axes.clipEnds(ax, boundaryVals); + tickVals = ax.ticks === 'inside' ? valsClipped : boundaryVals; + } else { + valsClipped = axes.clipEnds(ax, vals); + tickVals = ax.ticks === 'inside' ? valsClipped : vals; + } + + var gridVals = ax._gridVals = valsClipped; + var dividerVals = getDividerVals(ax, vals); + + if(!fullLayout._hasOnlyLargeSploms) { + var subplotsWithAx = ax._subplotsWith; + + // keep track of which subplots (by main counter axis) we've already + // drawn grids for, so we don't overdraw overlaying subplots + var finishedGrids = {}; + + for(i = 0; i < subplotsWithAx.length; i++) { + sp = subplotsWithAx[i]; + plotinfo = fullLayout._plots[sp]; + + var counterAxis = plotinfo[counterLetter + 'axis']; + var mainCounterID = counterAxis._mainAxis._id; + if(finishedGrids[mainCounterID]) continue; + finishedGrids[mainCounterID] = 1; + + var gridPath = axLetter === 'x' ? + 'M0,' + counterAxis._offset + 'v' + counterAxis._length : + 'M' + counterAxis._offset + ',0h' + counterAxis._length; + + axes.drawGrid(gd, ax, { + vals: gridVals, + counterAxis: counterAxis, + layer: plotinfo.gridlayer.select('.' + axId), + path: gridPath, + transFn: transFn + }); + axes.drawZeroLine(gd, ax, { + counterAxis: counterAxis, + layer: plotinfo.zerolinelayer, + path: gridPath, + transFn: transFn + }); + } + } + + var tickSigns = axes.getTickSigns(ax); + var tickSubplots = []; + + if(ax.ticks) { + var mainTickPath = axes.makeTickPath(ax, mainLinePosition, tickSigns[2]); + var mirrorTickPath; + var fullTickPath; + if(ax._anchorAxis && ax.mirror && ax.mirror !== true) { + mirrorTickPath = axes.makeTickPath(ax, mainMirrorPosition, tickSigns[3]); + fullTickPath = mainTickPath + mirrorTickPath; + } else { + mirrorTickPath = ''; + fullTickPath = mainTickPath; + } + + var tickPath; + if(ax.showdividers && ax.ticks === 'outside' && ax.tickson === 'boundaries') { + var dividerLookup = {}; + for(i = 0; i < dividerVals.length; i++) { + dividerLookup[dividerVals[i].x] = 1; + } + tickPath = function(d) { + return dividerLookup[d.x] ? mirrorTickPath : fullTickPath; + }; + } else { + tickPath = fullTickPath; + } + + axes.drawTicks(gd, ax, { + vals: tickVals, + layer: mainAxLayer, + path: tickPath, + transFn: transFn + }); + + if(ax.mirror === 'allticks') { + tickSubplots = Object.keys(ax._linepositions || {}); + } + } + + for(i = 0; i < tickSubplots.length; i++) { + sp = tickSubplots[i]; + plotinfo = fullLayout._plots[sp]; + // [bottom or left, top or right], free and main are handled above + var linepositions = ax._linepositions[sp] || []; + var spTickPath = axes.makeTickPath(ax, linepositions[0], tickSigns[0]) + + axes.makeTickPath(ax, linepositions[1], tickSigns[1]); + + axes.drawTicks(gd, ax, { + vals: tickVals, + layer: plotinfo[axLetter + 'axislayer'], + path: spTickPath, + transFn: transFn + }); + } + + var seq = []; + + // tick labels - for now just the main labels. + // TODO: mirror labels, esp for subplots + + seq.push(function() { + return axes.drawLabels(gd, ax, { + vals: vals, + layer: mainAxLayer, + transFn: transFn, + labelFns: axes.makeLabelFns(ax, mainLinePosition) + }); + }); + + if(ax.type === 'multicategory') { + var pad = {x: 2, y: 10}[axLetter]; + + seq.push(function() { + var bboxKey = {x: 'height', y: 'width'}[axLetter]; + var standoff = getLabelLevelBbox()[bboxKey] + pad + + (ax._tickAngles[axId + 'tick'] ? ax.tickfont.size * LINE_SPACING : 0); + + return axes.drawLabels(gd, ax, { + vals: getSecondaryLabelVals(ax, vals), + layer: mainAxLayer, + cls: axId + 'tick2', + repositionOnUpdate: true, + secondary: true, + transFn: transFn, + labelFns: axes.makeLabelFns(ax, mainLinePosition + standoff * tickSigns[4]) + }); + }); + + seq.push(function() { + ax._depth = tickSigns[4] * (getLabelLevelBbox('tick2')[ax.side] - mainLinePosition); + + return drawDividers(gd, ax, { + vals: dividerVals, + layer: mainAxLayer, + path: axes.makeTickPath(ax, mainLinePosition, tickSigns[4], ax._depth), + transFn: transFn + }); + }); + } else if(ax.title.hasOwnProperty('standoff')) { + seq.push(function() { + ax._depth = tickSigns[4] * (getLabelLevelBbox()[ax.side] - mainLinePosition); + }); + } + + var hasRangeSlider = Registry.getComponentMethod('rangeslider', 'isVisible')(ax); + + seq.push(function() { + var s = ax.side.charAt(0); + var sMirror = OPPOSITE_SIDE[ax.side].charAt(0); + var pos = axes.getPxPosition(gd, ax); + var outsideTickLen = ax.ticks === 'outside' ? ax.ticklen : 0; + var llbbox; + + var push; + var mirrorPush; + var rangeSliderPush; + + if(ax.automargin || hasRangeSlider) { + if(ax.type === 'multicategory') { + llbbox = getLabelLevelBbox('tick2'); + } else { + llbbox = getLabelLevelBbox(); + if(axLetter === 'x' && s === 'b') { + ax._depth = Math.max(llbbox.width > 0 ? llbbox.bottom - pos : 0, outsideTickLen); + } + } + } + + if(ax.automargin) { + push = {x: 0, y: 0, r: 0, l: 0, t: 0, b: 0}; + var domainIndices = [0, 1]; + + if(axLetter === 'x') { + if(s === 'b') { + push[s] = ax._depth; + } else { + push[s] = ax._depth = Math.max(llbbox.width > 0 ? pos - llbbox.top : 0, outsideTickLen); + domainIndices.reverse(); + } + + if(llbbox.width > 0) { + var rExtra = llbbox.right - (ax._offset + ax._length); + if(rExtra > 0) { + push.xr = 1; + push.r = rExtra; + } + var lExtra = ax._offset - llbbox.left; + if(lExtra > 0) { + push.xl = 0; + push.l = lExtra; + } + } + } else { + if(s === 'l') { + push[s] = ax._depth = Math.max(llbbox.height > 0 ? pos - llbbox.left : 0, outsideTickLen); + } else { + push[s] = ax._depth = Math.max(llbbox.height > 0 ? llbbox.right - pos : 0, outsideTickLen); + domainIndices.reverse(); + } + + if(llbbox.height > 0) { + var bExtra = llbbox.bottom - (ax._offset + ax._length); + if(bExtra > 0) { + push.yb = 0; + push.b = bExtra; + } + var tExtra = ax._offset - llbbox.top; + if(tExtra > 0) { + push.yt = 1; + push.t = tExtra; + } + } + } + + push[counterLetter] = ax.anchor === 'free' ? + ax.position : + ax._anchorAxis.domain[domainIndices[0]]; + + if(ax.title.text !== fullLayout._dfltTitle[axLetter]) { + push[s] += approxTitleDepth(ax) + (ax.title.standoff || 0); + } + + if(ax.mirror && ax.anchor !== 'free') { + mirrorPush = {x: 0, y: 0, r: 0, l: 0, t: 0, b: 0}; + + mirrorPush[sMirror] = ax.linewidth; + if(ax.mirror && ax.mirror !== true) mirrorPush[sMirror] += outsideTickLen; + + if(ax.mirror === true || ax.mirror === 'ticks') { + mirrorPush[counterLetter] = ax._anchorAxis.domain[domainIndices[1]]; + } else if(ax.mirror === 'all' || ax.mirror === 'allticks') { + mirrorPush[counterLetter] = [ax._counterDomainMin, ax._counterDomainMax][domainIndices[1]]; + } + } + } + + if(hasRangeSlider) { + rangeSliderPush = Registry.getComponentMethod('rangeslider', 'autoMarginOpts')(gd, ax); + } + + Plots.autoMargin(gd, axAutoMarginID(ax), push); + Plots.autoMargin(gd, axMirrorAutoMarginID(ax), mirrorPush); + Plots.autoMargin(gd, rangeSliderAutoMarginID(ax), rangeSliderPush); + }); + + if(!opts.skipTitle && + !(hasRangeSlider && ax.side === 'bottom') + ) { + seq.push(function() { return drawTitle(gd, ax); }); + } + + return Lib.syncOrAsync(seq); +}; + +function getBoundaryVals(ax, vals) { + var out = []; + var i; + + // boundaryVals are never used for labels; + // no need to worry about the other tickTextObj keys + var _push = function(d, bndIndex) { + var xb = d.xbnd[bndIndex]; + if(xb !== null) { + out.push(Lib.extendFlat({}, d, {x: xb})); + } + }; + + if(vals.length) { + for(i = 0; i < vals.length; i++) { + _push(vals[i], 0); + } + _push(vals[i - 1], 1); + } + + return out; +} + +function getSecondaryLabelVals(ax, vals) { + var out = []; + var lookup = {}; + + for(var i = 0; i < vals.length; i++) { + var d = vals[i]; + if(lookup[d.text2]) { + lookup[d.text2].push(d.x); + } else { + lookup[d.text2] = [d.x]; + } + } + + for(var k in lookup) { + out.push(tickTextObj(ax, Lib.interp(lookup[k], 0.5), k)); + } + + return out; +} + +function getDividerVals(ax, vals) { + var out = []; + var i, current; + + // never used for labels; + // no need to worry about the other tickTextObj keys + var _push = function(d, bndIndex) { + var xb = d.xbnd[bndIndex]; + if(xb !== null) { + out.push(Lib.extendFlat({}, d, {x: xb})); + } + }; + + if(ax.showdividers && vals.length) { + for(i = 0; i < vals.length; i++) { + var d = vals[i]; + if(d.text2 !== current) { + _push(d, 0); + } + current = d.text2; + } + _push(vals[i - 1], 1); + } + + return out; +} + +function calcLabelLevelBbox(ax, cls) { + var top, bottom; + var left, right; + + if(ax._selections[cls].size()) { + top = Infinity; + bottom = -Infinity; + left = Infinity; + right = -Infinity; + ax._selections[cls].each(function() { + var thisLabel = selectTickLabel(this); + // Use parent node , to make Drawing.bBox + // retrieve a bbox computed with transform info + // + // To improve perf, it would be nice to use `thisLabel.node()` + // (like in fixLabelOverlaps) instead and use Axes.getPxPosition + // together with the makeLabelFns outputs and `tickangle` + // to compute one bbox per (tick value x tick style) + var bb = Drawing.bBox(thisLabel.node().parentNode); + top = Math.min(top, bb.top); + bottom = Math.max(bottom, bb.bottom); + left = Math.min(left, bb.left); + right = Math.max(right, bb.right); + }); + } else { + top = 0; + bottom = 0; + left = 0; + right = 0; + } + + return { + top: top, + bottom: bottom, + left: left, + right: right, + height: bottom - top, + width: right - left + }; +} + +/** + * Which direction do the 'ax.side' values, and free ticks go? + * + * @param {object} ax (full) axis object + * - {string} _id (starting with 'x' or 'y') + * - {string} side + * - {string} ticks + * @return {array} all entries are either -1 or 1 + * - [0]: sign for top/right ticks (i.e. negative SVG direction) + * - [1]: sign for bottom/left ticks (i.e. positive SVG direction) + * - [2]: sign for ticks corresponding to 'ax.side' + * - [3]: sign for ticks mirroring 'ax.side' + * - [4]: sign of arrow starting at axis pointing towards margin + */ +axes.getTickSigns = function(ax) { + var axLetter = ax._id.charAt(0); + var sideOpposite = {x: 'top', y: 'right'}[axLetter]; + var main = ax.side === sideOpposite ? 1 : -1; + var out = [-1, 1, main, -main]; + // then we flip if outside XOR y axis + if((ax.ticks !== 'inside') === (axLetter === 'x')) { + out = out.map(function(v) { return -v; }); + } + // independent of `ticks`; do not flip this one + if(ax.side) { + out.push({l: -1, t: -1, r: 1, b: 1}[ax.side.charAt(0)]); + } + return out; +}; + +/** + * Make axis translate transform function + * + * @param {object} ax (full) axis object + * - {string} _id + * - {number} _offset + * - {fn} l2p + * @return {fn} function of calcTicks items + */ +axes.makeTransFn = function(ax) { + var axLetter = ax._id.charAt(0); + var offset = ax._offset; + return axLetter === 'x' ? + function(d) { return 'translate(' + (offset + ax.l2p(d.x)) + ',0)'; } : + function(d) { return 'translate(0,' + (offset + ax.l2p(d.x)) + ')'; }; +}; + +/** + * Make axis tick path string + * + * @param {object} ax (full) axis object + * - {string} _id + * - {number} ticklen + * - {number} linewidth + * @param {number} shift along direction of ticklen + * @param {1 or -1} sgn tick sign + * @param {number (optional)} len tick length + * @return {string} + */ +axes.makeTickPath = function(ax, shift, sgn, len) { + len = len !== undefined ? len : ax.ticklen; + + var axLetter = ax._id.charAt(0); + var pad = (ax.linewidth || 1) / 2; + + return axLetter === 'x' ? + 'M0,' + (shift + pad * sgn) + 'v' + (len * sgn) : + 'M' + (shift + pad * sgn) + ',0h' + (len * sgn); +}; + +/** + * Make axis tick label x, y and anchor functions + * + * @param {object} ax (full) axis object + * - {string} _id + * - {string} ticks + * - {number} ticklen + * - {string} side + * - {number} linewidth + * - {number} tickfont.size + * - {boolean} showline + * @param {number} shift + * @param {number} angle [in degrees] ... + * @return {object} + * - {fn} xFn + * - {fn} yFn + * - {fn} anchorFn + * - {fn} heightFn + * - {number} labelStandoff (gap parallel to ticks) + * - {number} labelShift (gap perpendicular to ticks) + */ +axes.makeLabelFns = function(ax, shift, angle) { + var axLetter = ax._id.charAt(0); + var ticksOnOutsideLabels = ax.tickson !== 'boundaries' && ax.ticks === 'outside'; + + var labelStandoff = 0; + var labelShift = 0; + + if(ticksOnOutsideLabels) { + labelStandoff += ax.ticklen; + } + if(angle && ax.ticks === 'outside') { + var rad = Lib.deg2rad(angle); + labelStandoff = ax.ticklen * Math.cos(rad) + 1; + labelShift = ax.ticklen * Math.sin(rad); + } + if(ax.showticklabels && (ticksOnOutsideLabels || ax.showline)) { + labelStandoff += 0.2 * ax.tickfont.size; + } + labelStandoff += (ax.linewidth || 1) / 2; + + var out = { + labelStandoff: labelStandoff, + labelShift: labelShift + }; + + var x0, y0, ff, flipIt; + + if(axLetter === 'x') { + flipIt = ax.side === 'bottom' ? 1 : -1; + x0 = labelShift * flipIt; + y0 = shift + labelStandoff * flipIt; + ff = ax.side === 'bottom' ? 1 : -0.2; + + out.xFn = function(d) { return d.dx + x0; }; + out.yFn = function(d) { return d.dy + y0 + d.fontSize * ff; }; + out.anchorFn = function(d, a) { + if(!isNumeric(a) || a === 0 || a === 180) { + return 'middle'; + } + return (a * flipIt < 0) ? 'end' : 'start'; + }; + out.heightFn = function(d, a, h) { + return (a < -60 || a > 60) ? -0.5 * h : + ax.side === 'top' ? -h : + 0; + }; + } else if(axLetter === 'y') { + flipIt = ax.side === 'right' ? 1 : -1; + x0 = labelStandoff; + y0 = -labelShift * flipIt; + ff = Math.abs(ax.tickangle) === 90 ? 0.5 : 0; + + out.xFn = function(d) { return d.dx + shift + (x0 + d.fontSize * ff) * flipIt; }; + out.yFn = function(d) { return d.dy + y0 + d.fontSize * MID_SHIFT; }; + out.anchorFn = function(d, a) { + if(isNumeric(a) && Math.abs(a) === 90) { + return 'middle'; + } + return ax.side === 'right' ? 'start' : 'end'; + }; + out.heightFn = function(d, a, h) { + a *= ax.side === 'left' ? 1 : -1; + return a < -30 ? -h : + a < 30 ? -0.5 * h : + 0; + }; + } + + return out; +}; + +function tickDataFn(d) { + return [d.text, d.x, d.axInfo, d.font, d.fontSize, d.fontColor].join('_'); +} + +/** + * Draw axis ticks + * + * @param {DOM element} gd + * @param {object} ax (full) axis object + * - {string} _id + * - {string} ticks + * - {number} linewidth + * - {string} tickcolor + * @param {object} opts + * - {array of object} vals (calcTicks output-like) + * - {d3 selection} layer + * - {string or fn} path + * - {fn} transFn + * - {boolean} crisp (set to false to unset crisp-edge SVG rendering) + */ +axes.drawTicks = function(gd, ax, opts) { + opts = opts || {}; + + var cls = ax._id + 'tick'; + + var ticks = opts.layer.selectAll('path.' + cls) + .data(ax.ticks ? opts.vals : [], tickDataFn); + + ticks.exit().remove(); + + ticks.enter().append('path') + .classed(cls, 1) + .classed('ticks', 1) + .classed('crisp', opts.crisp !== false) + .call(Color.stroke, ax.tickcolor) + .style('stroke-width', Drawing.crispRound(gd, ax.tickwidth, 1) + 'px') + .attr('d', opts.path); + + ticks.attr('transform', opts.transFn); +}; + +/** + * Draw axis grid + * + * @param {DOM element} gd + * @param {object} ax (full) axis object + * - {string} _id + * - {boolean} showgrid + * - {string} gridcolor + * - {string} gridwidth + * - {boolean} zeroline + * - {string} type + * - {string} dtick + * @param {object} opts + * - {array of object} vals (calcTicks output-like) + * - {d3 selection} layer + * - {object} counterAxis (full axis object corresponding to counter axis) + * optional - only required if this axis supports zero lines + * - {string or fn} path + * - {fn} transFn + * - {boolean} crisp (set to false to unset crisp-edge SVG rendering) + */ +axes.drawGrid = function(gd, ax, opts) { + opts = opts || {}; + + var cls = ax._id + 'grid'; + var vals = opts.vals; + var counterAx = opts.counterAxis; + if(ax.showgrid === false) { + vals = []; + } else if(counterAx && axes.shouldShowZeroLine(gd, ax, counterAx)) { + var isArrayMode = ax.tickmode === 'array'; + for(var i = 0; i < vals.length; i++) { + var xi = vals[i].x; + if(isArrayMode ? !xi : (Math.abs(xi) < ax.dtick / 100)) { + vals = vals.slice(0, i).concat(vals.slice(i + 1)); + // In array mode you can in principle have multiple + // ticks at 0, so test them all. Otherwise once we found + // one we can stop. + if(isArrayMode) i--; + else break; + } + } + } + + var grid = opts.layer.selectAll('path.' + cls) + .data(vals, tickDataFn); + + grid.exit().remove(); + + grid.enter().append('path') + .classed(cls, 1) + .classed('crisp', opts.crisp !== false); + + ax._gw = Drawing.crispRound(gd, ax.gridwidth, 1); + + grid.attr('transform', opts.transFn) + .attr('d', opts.path) + .call(Color.stroke, ax.gridcolor || '#ddd') + .style('stroke-width', ax._gw + 'px'); + + if(typeof opts.path === 'function') grid.attr('d', opts.path); +}; + +/** + * Draw axis zero-line + * + * @param {DOM element} gd + * @param {object} ax (full) axis object + * - {string} _id + * - {boolean} zeroline + * - {number} zerolinewidth + * - {string} zerolinecolor + * - {number (optional)} _gridWidthCrispRound + * @param {object} opts + * - {d3 selection} layer + * - {object} counterAxis (full axis object corresponding to counter axis) + * - {string or fn} path + * - {fn} transFn + * - {boolean} crisp (set to false to unset crisp-edge SVG rendering) + */ +axes.drawZeroLine = function(gd, ax, opts) { + opts = opts || opts; + + var cls = ax._id + 'zl'; + var show = axes.shouldShowZeroLine(gd, ax, opts.counterAxis); + + var zl = opts.layer.selectAll('path.' + cls) + .data(show ? [{x: 0, id: ax._id}] : []); + + zl.exit().remove(); + + zl.enter().append('path') + .classed(cls, 1) + .classed('zl', 1) + .classed('crisp', opts.crisp !== false) + .each(function() { + // use the fact that only one element can enter to trigger a sort. + // If several zerolines enter at the same time we will sort once per, + // but generally this should be a minimal overhead. + opts.layer.selectAll('path').sort(function(da, db) { + return axisIds.idSort(da.id, db.id); + }); + }); + + zl.attr('transform', opts.transFn) + .attr('d', opts.path) + .call(Color.stroke, ax.zerolinecolor || Color.defaultLine) + .style('stroke-width', Drawing.crispRound(gd, ax.zerolinewidth, ax._gw || 1) + 'px'); +}; + +/** + * Draw axis tick labels + * + * @param {DOM element} gd + * @param {object} ax (full) axis object + * - {string} _id + * - {boolean} showticklabels + * - {number} tickangle + * - {object (optional)} _selections + * - {object} (optional)} _tickAngles + * - {object} (optional)} _prevTickAngles + * @param {object} opts + * - {array of object} vals (calcTicks output-like) + * - {d3 selection} layer + * - {string (optional)} cls (node className) + * - {boolean} repositionOnUpdate (set to true to reposition update selection) + * - {boolean} secondary + * - {fn} transFn + * - {object} labelFns + * + {fn} xFn + * + {fn} yFn + * + {fn} anchorFn + * + {fn} heightFn + */ +axes.drawLabels = function(gd, ax, opts) { + opts = opts || {}; + + var fullLayout = gd._fullLayout; + var axId = ax._id; + var axLetter = axId.charAt(0); + var cls = opts.cls || axId + 'tick'; + var vals = opts.vals; + var labelFns = opts.labelFns; + var tickAngle = opts.secondary ? 0 : ax.tickangle; + var prevAngle = (ax._prevTickAngles || {})[cls]; + + var tickLabels = opts.layer.selectAll('g.' + cls) + .data(ax.showticklabels ? vals : [], tickDataFn); + + var labelsReady = []; + + tickLabels.enter().append('g') + .classed(cls, 1) + .append('text') + // only so tex has predictable alignment that we can + // alter later + .attr('text-anchor', 'middle') + .each(function(d) { + var thisLabel = d3.select(this); + var newPromise = gd._promises.length; + + thisLabel + .call(svgTextUtils.positionText, labelFns.xFn(d), labelFns.yFn(d)) + .call(Drawing.font, d.font, d.fontSize, d.fontColor) + .text(d.text) + .call(svgTextUtils.convertToTspans, gd); + + if(gd._promises[newPromise]) { + // if we have an async label, we'll deal with that + // all here so take it out of gd._promises and + // instead position the label and promise this in + // labelsReady + labelsReady.push(gd._promises.pop().then(function() { + positionLabels(thisLabel, tickAngle); + })); + } else { + // sync label: just position it now. + positionLabels(thisLabel, tickAngle); + } + }); + + tickLabels.exit().remove(); + + if(opts.repositionOnUpdate) { + tickLabels.each(function(d) { + d3.select(this).select('text') + .call(svgTextUtils.positionText, labelFns.xFn(d), labelFns.yFn(d)); + }); + } + + function positionLabels(s, angle) { + s.each(function(d) { + var thisLabel = d3.select(this); + var mathjaxGroup = thisLabel.select('.text-math-group'); + var anchor = labelFns.anchorFn(d, angle); + + var transform = opts.transFn.call(thisLabel.node(), d) + + ((isNumeric(angle) && +angle !== 0) ? + (' rotate(' + angle + ',' + labelFns.xFn(d) + ',' + + (labelFns.yFn(d) - d.fontSize / 2) + ')') : + ''); + + // how much to shift a multi-line label to center it vertically. + var nLines = svgTextUtils.lineCount(thisLabel); + var lineHeight = LINE_SPACING * d.fontSize; + var anchorHeight = labelFns.heightFn(d, isNumeric(angle) ? +angle : 0, (nLines - 1) * lineHeight); + + if(anchorHeight) { + transform += ' translate(0, ' + anchorHeight + ')'; + } + + if(mathjaxGroup.empty()) { + thisLabel.select('text').attr({ + transform: transform, + 'text-anchor': anchor + }); + } else { + var mjWidth = Drawing.bBox(mathjaxGroup.node()).width; + var mjShift = mjWidth * {end: -0.5, start: 0.5}[anchor]; + mathjaxGroup.attr('transform', transform + (mjShift ? 'translate(' + mjShift + ',0)' : '')); + } + }); + } + + // make sure all labels are correctly positioned at their base angle + // the positionLabels call above is only for newly drawn labels. + // do this without waiting, using the last calculated angle to + // minimize flicker, then do it again when we know all labels are + // there, putting back the prescribed angle to check for overlaps. + positionLabels(tickLabels, (prevAngle + 1) ? prevAngle : tickAngle); + + function allLabelsReady() { + return labelsReady.length && Promise.all(labelsReady); + } + + var autoangle = null; + + function fixLabelOverlaps() { + positionLabels(tickLabels, tickAngle); + + // check for auto-angling if x labels overlap + // don't auto-angle at all for log axes with + // base and digit format + if(vals.length && axLetter === 'x' && !isNumeric(tickAngle) && + (ax.type !== 'log' || String(ax.dtick).charAt(0) !== 'D') + ) { + autoangle = 0; + + var maxFontSize = 0; + var lbbArray = []; + var i; + + tickLabels.each(function(d) { + maxFontSize = Math.max(maxFontSize, d.fontSize); + + var x = ax.l2p(d.x); + var thisLabel = selectTickLabel(this); + var bb = Drawing.bBox(thisLabel.node()); + + lbbArray.push({ + // ignore about y, just deal with x overlaps + top: 0, + bottom: 10, + height: 10, + left: x - bb.width / 2, + // impose a 2px gap + right: x + bb.width / 2 + 2, + width: bb.width + 2 + }); + }); + + if((ax.tickson === 'boundaries' || ax.showdividers) && !opts.secondary) { + var gap = 2; + if(ax.ticks) gap += ax.tickwidth / 2; + + // TODO should secondary labels also fall into this fix-overlap regime? + + for(i = 0; i < lbbArray.length; i++) { + var xbnd = vals[i].xbnd; + var lbb = lbbArray[i]; + if( + (xbnd[0] !== null && (lbb.left - ax.l2p(xbnd[0])) < gap) || + (xbnd[1] !== null && (ax.l2p(xbnd[1]) - lbb.right) < gap) + ) { + autoangle = 90; + break; + } + } + } else { + var vLen = vals.length; + var tickSpacing = Math.abs((vals[vLen - 1].x - vals[0].x) * ax._m) / (vLen - 1); + var rotate90 = (tickSpacing < maxFontSize * 2.5) || ax.type === 'multicategory'; + + // any overlap at all - set 30 degrees or 90 degrees + for(i = 0; i < lbbArray.length - 1; i++) { + if(Lib.bBoxIntersect(lbbArray[i], lbbArray[i + 1])) { + autoangle = rotate90 ? 90 : 30; + break; + } + } + } + + if(autoangle) { + positionLabels(tickLabels, autoangle); + } + } + } + + if(ax._selections) { + ax._selections[cls] = tickLabels; + } + + var seq = [allLabelsReady]; + + // N.B. during auto-margin redraws, if the axis fixed its label overlaps + // by rotating 90 degrees, do not attempt to re-fix its label overlaps + // as this can lead to infinite redraw loops! + if(ax.automargin && fullLayout._redrawFromAutoMarginCount && prevAngle === 90) { + autoangle = 90; + seq.push(function() { + positionLabels(tickLabels, prevAngle); + }); + } else { + seq.push(fixLabelOverlaps); + } + + // save current tick angle for future redraws + if(ax._tickAngles) { + seq.push(function() { + ax._tickAngles[cls] = autoangle === null ? + (isNumeric(tickAngle) ? tickAngle : 0) : + autoangle; + }); + } + + var done = Lib.syncOrAsync(seq); + if(done && done.then) gd._promises.push(done); + return done; +}; + +/** + * Draw axis dividers + * + * @param {DOM element} gd + * @param {object} ax (full) axis object + * - {string} _id + * - {string} showdividers + * - {number} dividerwidth + * - {string} dividercolor + * @param {object} opts + * - {array of object} vals (calcTicks output-like) + * - {d3 selection} layer + * - {fn} path + * - {fn} transFn + */ +function drawDividers(gd, ax, opts) { + var cls = ax._id + 'divider'; + var vals = opts.vals; + + var dividers = opts.layer.selectAll('path.' + cls) + .data(vals, tickDataFn); + + dividers.exit().remove(); + + dividers.enter().insert('path', ':first-child') + .classed(cls, 1) + .classed('crisp', 1) + .call(Color.stroke, ax.dividercolor) + .style('stroke-width', Drawing.crispRound(gd, ax.dividerwidth, 1) + 'px'); + + dividers + .attr('transform', opts.transFn) + .attr('d', opts.path); +} + +/** + * Get axis position in px, that is the distance for the graph's + * top (left) edge for x (y) axes. + * + * @param {DOM element} gd + * @param {object} ax (full) axis object + * - {string} _id + * - {string} side + * if anchored: + * - {object} _anchorAxis + * Otherwise: + * - {number} position + * @return {number} + */ +axes.getPxPosition = function(gd, ax) { + var gs = gd._fullLayout._size; + var axLetter = ax._id.charAt(0); + var side = ax.side; + var anchorAxis; + + if(ax.anchor !== 'free') { + anchorAxis = ax._anchorAxis; + } else if(axLetter === 'x') { + anchorAxis = { + _offset: gs.t + (1 - (ax.position || 0)) * gs.h, + _length: 0 + }; + } else if(axLetter === 'y') { + anchorAxis = { + _offset: gs.l + (ax.position || 0) * gs.w, + _length: 0 + }; + } + + if(side === 'top' || side === 'left') { + return anchorAxis._offset; + } else if(side === 'bottom' || side === 'right') { + return anchorAxis._offset + anchorAxis._length; + } +}; + +/** + * Approximate axis title depth (w/o computing its bounding box) + * + * @param {object} ax (full) axis object + * - {string} title.text + * - {number} title.font.size + * - {number} title.standoff + * @return {number} (in px) + */ +function approxTitleDepth(ax) { + var fontSize = ax.title.font.size; + var extraLines = (ax.title.text.match(svgTextUtils.BR_TAG_ALL) || []).length; + if(ax.title.hasOwnProperty('standoff')) { + return extraLines ? + fontSize * (CAP_SHIFT + (extraLines * LINE_SPACING)) : + fontSize * CAP_SHIFT; + } else { + return extraLines ? + fontSize * (extraLines + 1) * LINE_SPACING : + fontSize; + } +} + +/** + * Draw axis title, compute default standoff if necessary + * + * @param {DOM element} gd + * @param {object} ax (full) axis object + * - {string} _id + * - {string} _name + * - {string} side + * - {number} title.font.size + * - {object} _selections + * + * - {number} _depth + * - {number} title.standoff + * OR + * - {number} linewidth + * - {boolean} showticklabels + */ +function drawTitle(gd, ax) { + var fullLayout = gd._fullLayout; + var axId = ax._id; + var axLetter = axId.charAt(0); + var fontSize = ax.title.font.size; + + var titleStandoff; + + if(ax.title.hasOwnProperty('standoff')) { + titleStandoff = ax._depth + ax.title.standoff + approxTitleDepth(ax); + } else { + if(ax.type === 'multicategory') { + titleStandoff = ax._depth; + } else { + var offsetBase = 1.5; + titleStandoff = 10 + fontSize * offsetBase + (ax.linewidth ? ax.linewidth - 1 : 0); + } + + if(axLetter === 'x') { + titleStandoff += ax.side === 'top' ? + fontSize * (ax.showticklabels ? 1 : 0) : + fontSize * (ax.showticklabels ? 1.5 : 0.5); + } else { + titleStandoff += ax.side === 'right' ? + fontSize * (ax.showticklabels ? 1 : 0.5) : + fontSize * (ax.showticklabels ? 0.5 : 0); + } + } + + var pos = axes.getPxPosition(gd, ax); + var transform, x, y; + + if(axLetter === 'x') { + x = ax._offset + ax._length / 2; + y = (ax.side === 'top') ? pos - titleStandoff : pos + titleStandoff; + } else { + y = ax._offset + ax._length / 2; + x = (ax.side === 'right') ? pos + titleStandoff : pos - titleStandoff; + transform = {rotate: '-90', offset: 0}; + } + + var avoid; + + if(ax.type !== 'multicategory') { + var tickLabels = ax._selections[ax._id + 'tick']; + + avoid = { + selection: tickLabels, + side: ax.side + }; + + if(tickLabels && tickLabels.node() && tickLabels.node().parentNode) { + var translation = Drawing.getTranslate(tickLabels.node().parentNode); + avoid.offsetLeft = translation.x; + avoid.offsetTop = translation.y; + } + + if(ax.title.hasOwnProperty('standoff')) { + avoid.pad = 0; + } + } + + return Titles.draw(gd, axId + 'title', { + propContainer: ax, + propName: ax._name + '.title.text', + placeholder: fullLayout._dfltTitle[axLetter], + avoid: avoid, + transform: transform, + attributes: {x: x, y: y, 'text-anchor': 'middle'} + }); +} + +axes.shouldShowZeroLine = function(gd, ax, counterAxis) { + var rng = Lib.simpleMap(ax.range, ax.r2l); + return ( + (rng[0] * rng[1] <= 0) && + ax.zeroline && + (ax.type === 'linear' || ax.type === '-') && + ( + clipEnds(ax, 0) || + !anyCounterAxLineAtZero(gd, ax, counterAxis, rng) || + hasBarsOrFill(gd, ax) + ) + ); +}; + +axes.clipEnds = function(ax, vals) { + return vals.filter(function(d) { return clipEnds(ax, d.x); }); +}; + +function clipEnds(ax, l) { + var p = ax.l2p(l); + return (p > 1 && p < ax._length - 1); +} + +function anyCounterAxLineAtZero(gd, ax, counterAxis, rng) { + var mainCounterAxis = counterAxis._mainAxis; + if(!mainCounterAxis) return; + + var fullLayout = gd._fullLayout; + var axLetter = ax._id.charAt(0); + var counterLetter = axes.counterLetter(ax._id); + + var zeroPosition = ax._offset + ( + ((Math.abs(rng[0]) < Math.abs(rng[1])) === (axLetter === 'x')) ? + 0 : ax._length + ); + + function lineNearZero(ax2) { + if(!ax2.showline || !ax2.linewidth) return false; + var tolerance = Math.max((ax2.linewidth + ax.zerolinewidth) / 2, 1); + + function closeEnough(pos2) { + return typeof pos2 === 'number' && Math.abs(pos2 - zeroPosition) < tolerance; + } + + if(closeEnough(ax2._mainLinePosition) || closeEnough(ax2._mainMirrorPosition)) { + return true; + } + var linePositions = ax2._linepositions || {}; + for(var k in linePositions) { + if(closeEnough(linePositions[k][0]) || closeEnough(linePositions[k][1])) { + return true; + } + } + } + + var plotinfo = fullLayout._plots[counterAxis._mainSubplot]; + if(!(plotinfo.mainplotinfo || plotinfo).overlays.length) { + return lineNearZero(counterAxis, zeroPosition); + } + + var counterLetterAxes = axes.list(gd, counterLetter); + for(var i = 0; i < counterLetterAxes.length; i++) { + var counterAxis2 = counterLetterAxes[i]; + if( + counterAxis2._mainAxis === mainCounterAxis && + lineNearZero(counterAxis2, zeroPosition) + ) { + return true; + } + } +} + +function hasBarsOrFill(gd, ax) { + var fullData = gd._fullData; + var subplot = ax._mainSubplot; + var axLetter = ax._id.charAt(0); + + for(var i = 0; i < fullData.length; i++) { + var trace = fullData[i]; + + if(trace.visible === true && (trace.xaxis + trace.yaxis) === subplot) { + if( + Registry.traceIs(trace, 'bar-like') && + trace.orientation === {x: 'h', y: 'v'}[axLetter] + ) return true; + + if( + trace.fill && + trace.fill.charAt(trace.fill.length - 1) === axLetter + ) return true; + } + } + return false; +} + +function selectTickLabel(gTick) { + var s = d3.select(gTick); + var mj = s.select('.text-math-group'); + return mj.empty() ? s.select('text') : mj; +} + +/** + * Find all margin pushers for 2D axes and reserve them for later use + * Both label and rangeslider automargin calculations happen later so + * we need to explicitly allow their ids in order to not delete them. + * + * TODO: can we pull the actual automargin calls forward to avoid this hack? + * We're probably also doing multiple redraws in this case, would be faster + * if we can just do the whole calculation ahead of time and draw once. + */ +axes.allowAutoMargin = function(gd) { + var axList = axes.list(gd, '', true); + for(var i = 0; i < axList.length; i++) { + var ax = axList[i]; + if(ax.automargin) { + Plots.allowAutoMargin(gd, axAutoMarginID(ax)); + if(ax.mirror) { + Plots.allowAutoMargin(gd, axMirrorAutoMarginID(ax)); + } + } + if(Registry.getComponentMethod('rangeslider', 'isVisible')(ax)) { + Plots.allowAutoMargin(gd, rangeSliderAutoMarginID(ax)); + } + } +}; + +function axAutoMarginID(ax) { return ax._id + '.automargin'; } +function axMirrorAutoMarginID(ax) { return axAutoMarginID(ax) + '.mirror'; } +function rangeSliderAutoMarginID(ax) { return ax._id + '.rangeslider'; } + +// swap all the presentation attributes of the axes showing these traces +axes.swap = function(gd, traces) { + var axGroups = makeAxisGroups(gd, traces); + + for(var i = 0; i < axGroups.length; i++) { + swapAxisGroup(gd, axGroups[i].x, axGroups[i].y); + } +}; + +function makeAxisGroups(gd, traces) { + var groups = []; + var i, j; + + for(i = 0; i < traces.length; i++) { + var groupsi = []; + var xi = gd._fullData[traces[i]].xaxis; + var yi = gd._fullData[traces[i]].yaxis; + if(!xi || !yi) continue; // not a 2D cartesian trace? + + for(j = 0; j < groups.length; j++) { + if(groups[j].x.indexOf(xi) !== -1 || groups[j].y.indexOf(yi) !== -1) { + groupsi.push(j); + } + } + + if(!groupsi.length) { + groups.push({x: [xi], y: [yi]}); + continue; + } + + var group0 = groups[groupsi[0]]; + var groupj; + + if(groupsi.length > 1) { + for(j = 1; j < groupsi.length; j++) { + groupj = groups[groupsi[j]]; + mergeAxisGroups(group0.x, groupj.x); + mergeAxisGroups(group0.y, groupj.y); + } + } + mergeAxisGroups(group0.x, [xi]); + mergeAxisGroups(group0.y, [yi]); + } + + return groups; +} + +function mergeAxisGroups(intoSet, fromSet) { + for(var i = 0; i < fromSet.length; i++) { + if(intoSet.indexOf(fromSet[i]) === -1) intoSet.push(fromSet[i]); + } +} + +function swapAxisGroup(gd, xIds, yIds) { + var xFullAxes = []; + var yFullAxes = []; + var layout = gd.layout; + var i, j; + + for(i = 0; i < xIds.length; i++) xFullAxes.push(axes.getFromId(gd, xIds[i])); + for(i = 0; i < yIds.length; i++) yFullAxes.push(axes.getFromId(gd, yIds[i])); + + var allAxKeys = Object.keys(axAttrs); + + var noSwapAttrs = [ + 'anchor', 'domain', 'overlaying', 'position', 'side', 'tickangle', 'editType' + ]; + var numericTypes = ['linear', 'log']; + + for(i = 0; i < allAxKeys.length; i++) { + var keyi = allAxKeys[i]; + var xVal = xFullAxes[0][keyi]; + var yVal = yFullAxes[0][keyi]; + var allEqual = true; + var coerceLinearX = false; + var coerceLinearY = false; + if(keyi.charAt(0) === '_' || typeof xVal === 'function' || + noSwapAttrs.indexOf(keyi) !== -1) { + continue; + } + for(j = 1; j < xFullAxes.length && allEqual; j++) { + var xVali = xFullAxes[j][keyi]; + if(keyi === 'type' && numericTypes.indexOf(xVal) !== -1 && + numericTypes.indexOf(xVali) !== -1 && xVal !== xVali) { + // type is special - if we find a mixture of linear and log, + // coerce them all to linear on flipping + coerceLinearX = true; + } else if(xVali !== xVal) allEqual = false; + } + for(j = 1; j < yFullAxes.length && allEqual; j++) { + var yVali = yFullAxes[j][keyi]; + if(keyi === 'type' && numericTypes.indexOf(yVal) !== -1 && + numericTypes.indexOf(yVali) !== -1 && yVal !== yVali) { + // type is special - if we find a mixture of linear and log, + // coerce them all to linear on flipping + coerceLinearY = true; + } else if(yFullAxes[j][keyi] !== yVal) allEqual = false; + } + if(allEqual) { + if(coerceLinearX) layout[xFullAxes[0]._name].type = 'linear'; + if(coerceLinearY) layout[yFullAxes[0]._name].type = 'linear'; + swapAxisAttrs(layout, keyi, xFullAxes, yFullAxes, gd._fullLayout._dfltTitle); + } + } + + // now swap x&y for any annotations anchored to these x & y + for(i = 0; i < gd._fullLayout.annotations.length; i++) { + var ann = gd._fullLayout.annotations[i]; + if(xIds.indexOf(ann.xref) !== -1 && + yIds.indexOf(ann.yref) !== -1) { + Lib.swapAttrs(layout.annotations[i], ['?']); + } + } +} + +function swapAxisAttrs(layout, key, xFullAxes, yFullAxes, dfltTitle) { + // in case the value is the default for either axis, + // look at the first axis in each list and see if + // this key's value is undefined + var np = Lib.nestedProperty; + var xVal = np(layout[xFullAxes[0]._name], key).get(); + var yVal = np(layout[yFullAxes[0]._name], key).get(); + var i; + + if(key === 'title') { + // special handling of placeholder titles + if(xVal && xVal.text === dfltTitle.x) { + xVal.text = dfltTitle.y; + } + if(yVal && yVal.text === dfltTitle.y) { + yVal.text = dfltTitle.x; + } + } + + for(i = 0; i < xFullAxes.length; i++) { + np(layout, xFullAxes[i]._name + '.' + key).set(yVal); + } + for(i = 0; i < yFullAxes.length; i++) { + np(layout, yFullAxes[i]._name + '.' + key).set(xVal); + } +} + +function isAngular(ax) { + return ax._id === 'angularaxis'; +} + +},{"../../components/color":51,"../../components/drawing":72,"../../components/titles":138,"../../constants/alignment":145,"../../constants/numerical":149,"../../lib":169,"../../lib/svg_text_utils":190,"../../plots/plots":245,"../../registry":258,"./autorange":212,"./axis_autotype":214,"./axis_ids":216,"./clean_ticks":218,"./layout_attributes":225,"./set_convert":231,"d3":16,"fast-isnumeric":18}],214:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); + +var Lib = _dereq_('../../lib'); +var BADNUM = _dereq_('../../constants/numerical').BADNUM; + +module.exports = function autoType(array, calendar, opts) { + opts = opts || {}; + + if(!opts.noMultiCategory && multiCategory(array)) return 'multicategory'; + if(moreDates(array, calendar)) return 'date'; + if(category(array)) return 'category'; + if(linearOK(array)) return 'linear'; + else return '-'; +}; + +// is there at least one number in array? If not, we should leave +// ax.type empty so it can be autoset later +function linearOK(array) { + if(!array) return false; + + for(var i = 0; i < array.length; i++) { + if(isNumeric(array[i])) return true; + } + + return false; +} + +// does the array a have mostly dates rather than numbers? +// note: some values can be neither (such as blanks, text) +// 2- or 4-digit integers can be both, so require twice as many +// dates as non-dates, to exclude cases with mostly 2 & 4 digit +// numbers and a few dates +// as with categories, consider DISTINCT values only. +function moreDates(a, calendar) { + // test at most 1000 points, evenly spaced + var inc = Math.max(1, (a.length - 1) / 1000); + var dcnt = 0; + var ncnt = 0; + var seen = {}; + + for(var i = 0; i < a.length; i += inc) { + var ai = a[Math.round(i)]; + var stri = String(ai); + if(seen[stri]) continue; + seen[stri] = 1; + + if(Lib.isDateTime(ai, calendar)) dcnt += 1; + if(isNumeric(ai)) ncnt += 1; + } + + return (dcnt > ncnt * 2); +} + +// are the (x,y)-values in gd.data mostly text? +// require twice as many DISTINCT categories as distinct numbers +function category(a) { + // test at most 1000 points + var inc = Math.max(1, (a.length - 1) / 1000); + var curvenums = 0; + var curvecats = 0; + var seen = {}; + + for(var i = 0; i < a.length; i += inc) { + var ai = a[Math.round(i)]; + var stri = String(ai); + if(seen[stri]) continue; + seen[stri] = 1; + + if(typeof ai === 'boolean') curvecats++; + else if(Lib.cleanNumber(ai) !== BADNUM) curvenums++; + else if(typeof ai === 'string') curvecats++; + } + + return curvecats > curvenums * 2; +} + +// very-loose requirements for multicategory, +// trace modules that should never auto-type to multicategory +// should be declared with 'noMultiCategory' +function multiCategory(a) { + return Lib.isArrayOrTypedArray(a[0]) && Lib.isArrayOrTypedArray(a[1]); +} + +},{"../../constants/numerical":149,"../../lib":169,"fast-isnumeric":18}],215:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Registry = _dereq_('../../registry'); +var Lib = _dereq_('../../lib'); + +var layoutAttributes = _dereq_('./layout_attributes'); +var handleTickValueDefaults = _dereq_('./tick_value_defaults'); +var handleTickMarkDefaults = _dereq_('./tick_mark_defaults'); +var handleTickLabelDefaults = _dereq_('./tick_label_defaults'); +var handleCategoryOrderDefaults = _dereq_('./category_order_defaults'); +var handleLineGridDefaults = _dereq_('./line_grid_defaults'); +var setConvert = _dereq_('./set_convert'); + +/** + * options: object containing: + * + * letter: 'x' or 'y' + * title: name of the axis (ie 'Colorbar') to go in default title + * font: the default font to inherit + * outerTicks: boolean, should ticks default to outside? + * showGrid: boolean, should gridlines be shown by default? + * noHover: boolean, this axis doesn't support hover effects? + * noTickson: boolean, this axis doesn't support 'tickson' + * data: the plot data, used to manage categories + * bgColor: the plot background color, to calculate default gridline colors + * calendar: + * splomStash: + * visibleDflt: boolean + * reverseDflt: boolean + * automargin: boolean + */ +module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, options, layoutOut) { + var letter = options.letter; + var font = options.font || {}; + var splomStash = options.splomStash || {}; + + var visible = coerce('visible', !options.visibleDflt); + + var axType = containerOut.type; + + if(axType === 'date') { + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleDefaults'); + handleCalendarDefaults(containerIn, containerOut, 'calendar', options.calendar); + } + + setConvert(containerOut, layoutOut); + + var autorangeDflt = !containerOut.isValidRange(containerIn.range); + if(autorangeDflt && options.reverseDflt) autorangeDflt = 'reversed'; + var autoRange = coerce('autorange', autorangeDflt); + if(autoRange && (axType === 'linear' || axType === '-')) coerce('rangemode'); + + coerce('range'); + containerOut.cleanRange(); + + handleCategoryOrderDefaults(containerIn, containerOut, coerce, options); + + if(axType !== 'category' && !options.noHover) coerce('hoverformat'); + + var dfltColor = coerce('color'); + // if axis.color was provided, use it for fonts too; otherwise, + // inherit from global font color in case that was provided. + // Compare to dflt rather than to containerIn, so we can provide color via + // template too. + var dfltFontColor = (dfltColor !== layoutAttributes.color.dflt) ? dfltColor : font.color; + // try to get default title from splom trace, fallback to graph-wide value + var dfltTitle = splomStash.label || layoutOut._dfltTitle[letter]; + + handleTickLabelDefaults(containerIn, containerOut, coerce, axType, options, {pass: 1}); + if(!visible) return containerOut; + + coerce('title.text', dfltTitle); + Lib.coerceFont(coerce, 'title.font', { + family: font.family, + size: Math.round(font.size * 1.2), + color: dfltFontColor + }); + + handleTickValueDefaults(containerIn, containerOut, coerce, axType); + handleTickLabelDefaults(containerIn, containerOut, coerce, axType, options, {pass: 2}); + handleTickMarkDefaults(containerIn, containerOut, coerce, options); + handleLineGridDefaults(containerIn, containerOut, coerce, { + dfltColor: dfltColor, + bgColor: options.bgColor, + showGrid: options.showGrid, + attributes: layoutAttributes + }); + + if(containerOut.showline || containerOut.ticks) coerce('mirror'); + + if(options.automargin) coerce('automargin'); + + var isMultiCategory = containerOut.type === 'multicategory'; + + if(!options.noTickson && + (containerOut.type === 'category' || isMultiCategory) && + (containerOut.ticks || containerOut.showgrid) + ) { + var ticksonDflt; + if(isMultiCategory) ticksonDflt = 'boundaries'; + coerce('tickson', ticksonDflt); + } + + if(isMultiCategory) { + var showDividers = coerce('showdividers'); + if(showDividers) { + coerce('dividercolor'); + coerce('dividerwidth'); + } + } + + return containerOut; +}; + +},{"../../lib":169,"../../registry":258,"./category_order_defaults":217,"./layout_attributes":225,"./line_grid_defaults":227,"./set_convert":231,"./tick_label_defaults":232,"./tick_mark_defaults":233,"./tick_value_defaults":234}],216:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Registry = _dereq_('../../registry'); + +var constants = _dereq_('./constants'); + + +// convert between axis names (xaxis, xaxis2, etc, elements of gd.layout) +// and axis id's (x, x2, etc). Would probably have ditched 'xaxis' +// completely in favor of just 'x' if it weren't ingrained in the API etc. +exports.id2name = function id2name(id) { + if(typeof id !== 'string' || !id.match(constants.AX_ID_PATTERN)) return; + var axNum = id.substr(1); + if(axNum === '1') axNum = ''; + return id.charAt(0) + 'axis' + axNum; +}; + +exports.name2id = function name2id(name) { + if(!name.match(constants.AX_NAME_PATTERN)) return; + var axNum = name.substr(5); + if(axNum === '1') axNum = ''; + return name.charAt(0) + axNum; +}; + +exports.cleanId = function cleanId(id, axLetter) { + if(!id.match(constants.AX_ID_PATTERN)) return; + if(axLetter && id.charAt(0) !== axLetter) return; + + var axNum = id.substr(1).replace(/^0+/, ''); + if(axNum === '1') axNum = ''; + return id.charAt(0) + axNum; +}; + +// get all axis objects, as restricted in listNames +exports.list = function(gd, axLetter, only2d) { + var fullLayout = gd._fullLayout; + if(!fullLayout) return []; + + var idList = exports.listIds(gd, axLetter); + var out = new Array(idList.length); + var i; + + for(i = 0; i < idList.length; i++) { + var idi = idList[i]; + out[i] = fullLayout[idi.charAt(0) + 'axis' + idi.substr(1)]; + } + + if(!only2d) { + var sceneIds3D = fullLayout._subplots.gl3d || []; + + for(i = 0; i < sceneIds3D.length; i++) { + var scene = fullLayout[sceneIds3D[i]]; + + if(axLetter) out.push(scene[axLetter + 'axis']); + else out.push(scene.xaxis, scene.yaxis, scene.zaxis); + } + } + + return out; +}; + +// get all axis ids, optionally restricted by letter +// this only makes sense for 2d axes +exports.listIds = function(gd, axLetter) { + var fullLayout = gd._fullLayout; + if(!fullLayout) return []; + + var subplotLists = fullLayout._subplots; + if(axLetter) return subplotLists[axLetter + 'axis']; + return subplotLists.xaxis.concat(subplotLists.yaxis); +}; + +// get an axis object from its id 'x','x2' etc +// optionally, id can be a subplot (ie 'x2y3') and type gets x or y from it +exports.getFromId = function(gd, id, type) { + var fullLayout = gd._fullLayout; + + if(type === 'x') id = id.replace(/y[0-9]*/, ''); + else if(type === 'y') id = id.replace(/x[0-9]*/, ''); + + return fullLayout[exports.id2name(id)]; +}; + +// get an axis object of specified type from the containing trace +exports.getFromTrace = function(gd, fullTrace, type) { + var fullLayout = gd._fullLayout; + var ax = null; + + if(Registry.traceIs(fullTrace, 'gl3d')) { + var scene = fullTrace.scene; + if(scene.substr(0, 5) === 'scene') { + ax = fullLayout[scene][type + 'axis']; + } + } else { + ax = exports.getFromId(gd, fullTrace[type + 'axis'] || type); + } + + return ax; +}; + +// sort x, x2, x10, y, y2, y10... +exports.idSort = function(id1, id2) { + var letter1 = id1.charAt(0); + var letter2 = id2.charAt(0); + if(letter1 !== letter2) return letter1 > letter2 ? 1 : -1; + return +(id1.substr(1) || 1) - +(id2.substr(1) || 1); +}; + +exports.getAxisGroup = function getAxisGroup(fullLayout, axId) { + var matchGroups = fullLayout._axisMatchGroups; + + for(var i = 0; i < matchGroups.length; i++) { + var group = matchGroups[i]; + if(group[axId]) return 'g' + i; + } + return axId; +}; + +},{"../../registry":258,"./constants":219}],217:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +function findCategories(ax, opts) { + var dataAttr = opts.dataAttr || ax._id.charAt(0); + var lookup = {}; + var axData; + var i, j; + + if(opts.axData) { + // non-x/y case + axData = opts.axData; + } else { + // x/y case + axData = []; + for(i = 0; i < opts.data.length; i++) { + var trace = opts.data[i]; + if(trace[dataAttr + 'axis'] === ax._id) { + axData.push(trace); + } + } + } + + for(i = 0; i < axData.length; i++) { + var vals = axData[i][dataAttr]; + for(j = 0; j < vals.length; j++) { + var v = vals[j]; + if(v !== null && v !== undefined) { + lookup[v] = 1; + } + } + } + + return Object.keys(lookup); +} + +/** + * Fills in category* default and initial categories. + * + * @param {object} containerIn : input axis object + * @param {object} containerOut : full axis object + * @param {function} coerce : Lib.coerce fn wrapper + * @param {object} opts : + * - data {array} : (full) data trace + * OR + * - axData {array} : (full) data associated with axis being coerced here + * - dataAttr {string} : attribute name corresponding to coordinate array + */ +module.exports = function handleCategoryOrderDefaults(containerIn, containerOut, coerce, opts) { + if(containerOut.type !== 'category') return; + + var arrayIn = containerIn.categoryarray; + var isValidArray = (Array.isArray(arrayIn) && arrayIn.length > 0); + + // override default 'categoryorder' value when non-empty array is supplied + var orderDefault; + if(isValidArray) orderDefault = 'array'; + + var order = coerce('categoryorder', orderDefault); + var array; + + // coerce 'categoryarray' only in array order case + if(order === 'array') { + array = coerce('categoryarray'); + } + + // cannot set 'categoryorder' to 'array' with an invalid 'categoryarray' + if(!isValidArray && order === 'array') { + order = containerOut.categoryorder = 'trace'; + } + + // set up things for makeCalcdata + if(order === 'trace') { + containerOut._initialCategories = []; + } else if(order === 'array') { + containerOut._initialCategories = array.slice(); + } else { + array = findCategories(containerOut, opts).sort(); + if(order === 'category ascending') { + containerOut._initialCategories = array; + } else if(order === 'category descending') { + containerOut._initialCategories = array.reverse(); + } + } +}; + +},{}],218:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); +var Lib = _dereq_('../../lib'); +var ONEDAY = _dereq_('../../constants/numerical').ONEDAY; + +/** + * Return a validated dtick value for this axis + * + * @param {any} dtick: the candidate dtick. valid values are numbers and strings, + * and further constrained depending on the axis type. + * @param {string} axType: the axis type + */ +exports.dtick = function(dtick, axType) { + var isLog = axType === 'log'; + var isDate = axType === 'date'; + var isCat = axType === 'category'; + var dtickDflt = isDate ? ONEDAY : 1; + + if(!dtick) return dtickDflt; + + if(isNumeric(dtick)) { + dtick = Number(dtick); + if(dtick <= 0) return dtickDflt; + if(isCat) { + // category dtick must be positive integers + return Math.max(1, Math.round(dtick)); + } + if(isDate) { + // date dtick must be at least 0.1ms (our current precision) + return Math.max(0.1, dtick); + } + return dtick; + } + + if(typeof dtick !== 'string' || !(isDate || isLog)) { + return dtickDflt; + } + + var prefix = dtick.charAt(0); + var dtickNum = dtick.substr(1); + dtickNum = isNumeric(dtickNum) ? Number(dtickNum) : 0; + + if((dtickNum <= 0) || !( + // "M" gives ticks every (integer) n months + (isDate && prefix === 'M' && dtickNum === Math.round(dtickNum)) || + // "L" gives ticks linearly spaced in data (not in position) every (float) f + (isLog && prefix === 'L') || + // "D1" gives powers of 10 with all small digits between, "D2" gives only 2 and 5 + (isLog && prefix === 'D' && (dtickNum === 1 || dtickNum === 2)) + )) { + return dtickDflt; + } + + return dtick; +}; + +/** + * Return a validated tick0 for this axis + * + * @param {any} tick0: the candidate tick0. Valid values are numbers and strings, + * further constrained depending on the axis type + * @param {string} axType: the axis type + * @param {string} calendar: for date axes, the calendar to validate/convert with + * @param {any} dtick: an already valid dtick. Only used for D1 and D2 log dticks, + * which do not support tick0 at all. + */ +exports.tick0 = function(tick0, axType, calendar, dtick) { + if(axType === 'date') { + return Lib.cleanDate(tick0, Lib.dateTick0(calendar)); + } + if(dtick === 'D1' || dtick === 'D2') { + // D1 and D2 modes ignore tick0 entirely + return undefined; + } + // Aside from date axes, tick0 must be numeric + return isNumeric(tick0) ? Number(tick0) : 0; +}; + +},{"../../constants/numerical":149,"../../lib":169,"fast-isnumeric":18}],219:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; +var counterRegex = _dereq_('../../lib/regex').counter; + + +module.exports = { + + idRegex: { + x: counterRegex('x'), + y: counterRegex('y') + }, + + attrRegex: counterRegex('[xy]axis'), + + // axis match regular expression + xAxisMatch: counterRegex('xaxis'), + yAxisMatch: counterRegex('yaxis'), + + // pattern matching axis ids and names + // note that this is more permissive than counterRegex, as + // id2name, name2id, and cleanId accept "x1" etc + AX_ID_PATTERN: /^[xyz][0-9]*$/, + AX_NAME_PATTERN: /^[xyz]axis[0-9]*$/, + + // and for 2D subplots + SUBPLOT_PATTERN: /^x([0-9]*)y([0-9]*)$/, + + // pixels to move mouse before you stop clamping to starting point + MINDRAG: 8, + + // smallest dimension allowed for a select box + MINSELECT: 12, + + // smallest dimension allowed for a zoombox + MINZOOM: 20, + + // width of axis drag regions + DRAGGERSIZE: 20, + + // max pixels off straight before a lasso select line counts as bent + BENDPX: 1.5, + + // delay before a redraw (relayout) after smooth panning and zooming + REDRAWDELAY: 50, + + // throttling limit (ms) for selectPoints calls + SELECTDELAY: 100, + + // cache ID suffix for throttle + SELECTID: '-select', + + // last resort axis ranges for x and y axes if we have no data + DFLTRANGEX: [-1, 6], + DFLTRANGEY: [-1, 4], + + // Layers to keep trace types in the right order + // N.B. each 'unique' plot method must have its own layer + traceLayerClasses: [ + 'imagelayer', + 'heatmaplayer', + 'contourcarpetlayer', 'contourlayer', + 'funnellayer', 'waterfalllayer', 'barlayer', + 'carpetlayer', + 'violinlayer', + 'boxlayer', + 'ohlclayer', + 'scattercarpetlayer', 'scatterlayer' + ], + + clipOnAxisFalseQuery: [ + '.scatterlayer', + '.barlayer', + '.funnellayer', + '.waterfalllayer' + ], + + layerValue2layerClass: { + 'above traces': 'above', + 'below traces': 'below' + } +}; + +},{"../../lib/regex":184}],220:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var id2name = _dereq_('./axis_ids').id2name; +var scaleZoom = _dereq_('./scale_zoom'); +var makePadFn = _dereq_('./autorange').makePadFn; +var concatExtremes = _dereq_('./autorange').concatExtremes; + +var ALMOST_EQUAL = _dereq_('../../constants/numerical').ALMOST_EQUAL; +var FROM_BL = _dereq_('../../constants/alignment').FROM_BL; + +exports.handleConstraintDefaults = function(containerIn, containerOut, coerce, opts) { + var allAxisIds = opts.allAxisIds; + var layoutOut = opts.layoutOut; + var scaleanchorDflt = opts.scaleanchorDflt; + var constrainDflt = opts.constrainDflt; + var constraintGroups = layoutOut._axisConstraintGroups; + var matchGroups = layoutOut._axisMatchGroups; + var axId = containerOut._id; + var axLetter = axId.charAt(0); + var splomStash = ((layoutOut._splomAxes || {})[axLetter] || {})[axId] || {}; + var thisID = containerOut._id; + var letter = thisID.charAt(0); + + // coerce the constraint mechanics even if this axis has no scaleanchor + // because it may be the anchor of another axis. + var constrain = coerce('constrain', constrainDflt); + Lib.coerce(containerIn, containerOut, { + constraintoward: { + valType: 'enumerated', + values: letter === 'x' ? ['left', 'center', 'right'] : ['bottom', 'middle', 'top'], + dflt: letter === 'x' ? 'center' : 'middle' + } + }, 'constraintoward'); + + var matches, matchOpts; + + if((containerIn.matches || splomStash.matches) && !containerOut.fixedrange) { + matchOpts = getConstraintOpts(matchGroups, thisID, allAxisIds, layoutOut); + matches = Lib.coerce(containerIn, containerOut, { + matches: { + valType: 'enumerated', + values: matchOpts.linkableAxes || [], + dflt: splomStash.matches + } + }, 'matches'); + } + + // 'matches' wins over 'scaleanchor' (for now) + var scaleanchor, scaleOpts; + + if(!matches && + !(containerOut.fixedrange && constrain !== 'domain') && + (containerIn.scaleanchor || scaleanchorDflt) + ) { + scaleOpts = getConstraintOpts(constraintGroups, thisID, allAxisIds, layoutOut, constrain); + scaleanchor = Lib.coerce(containerIn, containerOut, { + scaleanchor: { + valType: 'enumerated', + values: scaleOpts.linkableAxes || [] + } + }, 'scaleanchor', scaleanchorDflt); + } + + if(matches) { + delete containerOut.constrain; + updateConstraintGroups(matchGroups, matchOpts.thisGroup, thisID, matches, 1); + } else if(allAxisIds.indexOf(containerIn.matches) !== -1) { + Lib.warn('ignored ' + containerOut._name + '.matches: "' + + containerIn.matches + '" to avoid either an infinite loop ' + + 'or because the target axis has fixed range.'); + } + + if(scaleanchor) { + var scaleratio = coerce('scaleratio'); + + // TODO: I suppose I could do attribute.min: Number.MIN_VALUE to avoid zero, + // but that seems hacky. Better way to say "must be a positive number"? + // Of course if you use several super-tiny values you could eventually + // force a product of these to zero and all hell would break loose... + // Likewise with super-huge values. + if(!scaleratio) scaleratio = containerOut.scaleratio = 1; + + updateConstraintGroups(constraintGroups, scaleOpts.thisGroup, thisID, scaleanchor, scaleratio); + } else if(allAxisIds.indexOf(containerIn.scaleanchor) !== -1) { + Lib.warn('ignored ' + containerOut._name + '.scaleanchor: "' + + containerIn.scaleanchor + '" to avoid either an infinite loop ' + + 'and possibly inconsistent scaleratios, or because the target ' + + 'axis has fixed range or this axis declares a *matches* constraint.'); + } +}; + +// If this axis is already part of a constraint group, we can't +// scaleanchor any other axis in that group, or we'd make a loop. +// Filter allAxisIds to enforce this, also matching axis types. +function getConstraintOpts(groups, thisID, allAxisIds, layoutOut, constrain) { + var doesNotConstrainRange = constrain !== 'range'; + var thisType = layoutOut[id2name(thisID)].type; + var i, j, idj, axj; + + var linkableAxes = []; + for(j = 0; j < allAxisIds.length; j++) { + idj = allAxisIds[j]; + if(idj === thisID) continue; + + axj = layoutOut[id2name(idj)]; + if(axj.type === thisType) { + if(!axj.fixedrange) { + linkableAxes.push(idj); + } else if(doesNotConstrainRange && axj.anchor) { + // allow domain constraints on subplots where + // BOTH axes have fixedrange:true and constrain:domain + var counterAxj = layoutOut[id2name(axj.anchor)]; + if(counterAxj.fixedrange) { + linkableAxes.push(idj); + } + } + } + } + + for(i = 0; i < groups.length; i++) { + if(groups[i][thisID]) { + var thisGroup = groups[i]; + + var linkableAxesNoLoops = []; + for(j = 0; j < linkableAxes.length; j++) { + idj = linkableAxes[j]; + if(!thisGroup[idj]) linkableAxesNoLoops.push(idj); + } + return {linkableAxes: linkableAxesNoLoops, thisGroup: thisGroup}; + } + } + + return {linkableAxes: linkableAxes, thisGroup: null}; +} + +/* + * Add this axis to the axis constraint groups, which is the collection + * of axes that are all constrained together on scale. + * + * constraintGroups: a list of objects. each object is + * {axis_id: scale_within_group}, where scale_within_group is + * only important relative to the rest of the group, and defines + * the relative scales between all axes in the group + * + * thisGroup: the group the current axis is already in + * thisID: the id if the current axis + * scaleanchor: the id of the axis to scale it with + * scaleratio: the ratio of this axis to the scaleanchor axis + */ +function updateConstraintGroups(constraintGroups, thisGroup, thisID, scaleanchor, scaleratio) { + var i, j, groupi, keyj, thisGroupIndex; + + if(thisGroup === null) { + thisGroup = {}; + thisGroup[thisID] = 1; + thisGroupIndex = constraintGroups.length; + constraintGroups.push(thisGroup); + } else { + thisGroupIndex = constraintGroups.indexOf(thisGroup); + } + + var thisGroupKeys = Object.keys(thisGroup); + + // we know that this axis isn't in any other groups, but we don't know + // about the scaleanchor axis. If it is, we need to merge the groups. + for(i = 0; i < constraintGroups.length; i++) { + groupi = constraintGroups[i]; + if(i !== thisGroupIndex && groupi[scaleanchor]) { + var baseScale = groupi[scaleanchor]; + for(j = 0; j < thisGroupKeys.length; j++) { + keyj = thisGroupKeys[j]; + groupi[keyj] = baseScale * scaleratio * thisGroup[keyj]; + } + constraintGroups.splice(thisGroupIndex, 1); + return; + } + } + + // otherwise, we insert the new scaleanchor axis as the base scale (1) + // in its group, and scale the rest of the group to it + if(scaleratio !== 1) { + for(j = 0; j < thisGroupKeys.length; j++) { + thisGroup[thisGroupKeys[j]] *= scaleratio; + } + } + thisGroup[scaleanchor] = 1; +} + +exports.enforce = function enforce(gd) { + var fullLayout = gd._fullLayout; + var constraintGroups = fullLayout._axisConstraintGroups || []; + + var i, j, axisID, ax, normScale, mode, factor; + + for(i = 0; i < constraintGroups.length; i++) { + var group = constraintGroups[i]; + var axisIDs = Object.keys(group); + + var minScale = Infinity; + var maxScale = 0; + // mostly matchScale will be the same as minScale + // ie we expand axis ranges to encompass *everything* + // that's currently in any of their ranges, but during + // autorange of a subset of axes we will ignore other + // axes for this purpose. + var matchScale = Infinity; + var normScales = {}; + var axes = {}; + var hasAnyDomainConstraint = false; + + // find the (normalized) scale of each axis in the group + for(j = 0; j < axisIDs.length; j++) { + axisID = axisIDs[j]; + axes[axisID] = ax = fullLayout[id2name(axisID)]; + + if(ax._inputDomain) ax.domain = ax._inputDomain.slice(); + else ax._inputDomain = ax.domain.slice(); + + if(!ax._inputRange) ax._inputRange = ax.range.slice(); + + // set axis scale here so we can use _m rather than + // having to calculate it from length and range + ax.setScale(); + + // abs: inverted scales still satisfy the constraint + normScales[axisID] = normScale = Math.abs(ax._m) / group[axisID]; + minScale = Math.min(minScale, normScale); + if(ax.constrain === 'domain' || !ax._constraintShrinkable) { + matchScale = Math.min(matchScale, normScale); + } + + // this has served its purpose, so remove it + delete ax._constraintShrinkable; + maxScale = Math.max(maxScale, normScale); + + if(ax.constrain === 'domain') hasAnyDomainConstraint = true; + } + + // Do we have a constraint mismatch? Give a small buffer for rounding errors + if(minScale > ALMOST_EQUAL * maxScale && !hasAnyDomainConstraint) continue; + + // now increase any ranges we need to until all normalized scales are equal + for(j = 0; j < axisIDs.length; j++) { + axisID = axisIDs[j]; + normScale = normScales[axisID]; + ax = axes[axisID]; + mode = ax.constrain; + + // even if the scale didn't change, if we're shrinking domain + // we need to recalculate in case `constraintoward` changed + if(normScale !== matchScale || mode === 'domain') { + factor = normScale / matchScale; + + if(mode === 'range') { + scaleZoom(ax, factor); + } else { + // mode === 'domain' + + var inputDomain = ax._inputDomain; + var domainShrunk = (ax.domain[1] - ax.domain[0]) / + (inputDomain[1] - inputDomain[0]); + var rangeShrunk = (ax.r2l(ax.range[1]) - ax.r2l(ax.range[0])) / + (ax.r2l(ax._inputRange[1]) - ax.r2l(ax._inputRange[0])); + + factor /= domainShrunk; + + if(factor * rangeShrunk < 1) { + // we've asked to magnify the axis more than we can just by + // enlarging the domain - so we need to constrict range + ax.domain = ax._input.domain = inputDomain.slice(); + scaleZoom(ax, factor); + continue; + } + + if(rangeShrunk < 1) { + // the range has previously been constricted by ^^, but we've + // switched to the domain-constricted regime, so reset range + ax.range = ax._input.range = ax._inputRange.slice(); + factor *= rangeShrunk; + } + + if(ax.autorange) { + /* + * range & factor may need to change because range was + * calculated for the larger scaling, so some pixel + * paddings may get cut off when we reduce the domain. + * + * This is easier than the regular autorange calculation + * because we already know the scaling `m`, but we still + * need to cut out impossible constraints (like + * annotations with super-long arrows). That's what + * outerMin/Max are for - if the expansion was going to + * go beyond the original domain, it must be impossible + */ + var rl0 = ax.r2l(ax.range[0]); + var rl1 = ax.r2l(ax.range[1]); + var rangeCenter = (rl0 + rl1) / 2; + var rangeMin = rangeCenter; + var rangeMax = rangeCenter; + var halfRange = Math.abs(rl1 - rangeCenter); + // extra tiny bit for rounding errors, in case we actually + // *are* expanding to the full domain + var outerMin = rangeCenter - halfRange * factor * 1.0001; + var outerMax = rangeCenter + halfRange * factor * 1.0001; + var getPad = makePadFn(ax); + + updateDomain(ax, factor); + var m = Math.abs(ax._m); + var extremes = concatExtremes(gd, ax); + var minArray = extremes.min; + var maxArray = extremes.max; + var newVal; + var k; + + for(k = 0; k < minArray.length; k++) { + newVal = minArray[k].val - getPad(minArray[k]) / m; + if(newVal > outerMin && newVal < rangeMin) { + rangeMin = newVal; + } + } + + for(k = 0; k < maxArray.length; k++) { + newVal = maxArray[k].val + getPad(maxArray[k]) / m; + if(newVal < outerMax && newVal > rangeMax) { + rangeMax = newVal; + } + } + + var domainExpand = (rangeMax - rangeMin) / (2 * halfRange); + factor /= domainExpand; + + rangeMin = ax.l2r(rangeMin); + rangeMax = ax.l2r(rangeMax); + ax.range = ax._input.range = (rl0 < rl1) ? + [rangeMin, rangeMax] : [rangeMax, rangeMin]; + } + + updateDomain(ax, factor); + } + } + } + } +}; + +// For use before autoranging, check if this axis was previously constrained +// by domain but no longer is +exports.clean = function clean(gd, ax) { + if(ax._inputDomain) { + var isConstrained = false; + var axId = ax._id; + var constraintGroups = gd._fullLayout._axisConstraintGroups; + for(var j = 0; j < constraintGroups.length; j++) { + if(constraintGroups[j][axId]) { + isConstrained = true; + break; + } + } + if(!isConstrained || ax.constrain !== 'domain') { + ax._input.domain = ax.domain = ax._inputDomain; + delete ax._inputDomain; + } + } +}; + +function updateDomain(ax, factor) { + var inputDomain = ax._inputDomain; + var centerFraction = FROM_BL[ax.constraintoward]; + var center = inputDomain[0] + (inputDomain[1] - inputDomain[0]) * centerFraction; + + ax.domain = ax._input.domain = [ + center + (inputDomain[0] - center) / factor, + center + (inputDomain[1] - center) / factor + ]; + ax.setScale(); +} + +},{"../../constants/alignment":145,"../../constants/numerical":149,"../../lib":169,"./autorange":212,"./axis_ids":216,"./scale_zoom":229}],221:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); +var tinycolor = _dereq_('tinycolor2'); +var supportsPassive = _dereq_('has-passive-events'); + +var Registry = _dereq_('../../registry'); +var Lib = _dereq_('../../lib'); +var svgTextUtils = _dereq_('../../lib/svg_text_utils'); +var Color = _dereq_('../../components/color'); +var Drawing = _dereq_('../../components/drawing'); +var Fx = _dereq_('../../components/fx'); +var Axes = _dereq_('./axes'); +var setCursor = _dereq_('../../lib/setcursor'); +var dragElement = _dereq_('../../components/dragelement'); +var FROM_TL = _dereq_('../../constants/alignment').FROM_TL; +var clearGlCanvases = _dereq_('../../lib/clear_gl_canvases'); +var redrawReglTraces = _dereq_('../../plot_api/subroutines').redrawReglTraces; + +var Plots = _dereq_('../plots'); + +var getFromId = _dereq_('./axis_ids').getFromId; +var prepSelect = _dereq_('./select').prepSelect; +var clearSelect = _dereq_('./select').clearSelect; +var selectOnClick = _dereq_('./select').selectOnClick; +var scaleZoom = _dereq_('./scale_zoom'); + +var constants = _dereq_('./constants'); +var MINDRAG = constants.MINDRAG; +var MINZOOM = constants.MINZOOM; + +// flag for showing "doubleclick to zoom out" only at the beginning +var SHOWZOOMOUTTIP = true; + +// dragBox: create an element to drag one or more axis ends +// inputs: +// plotinfo - which subplot are we making dragboxes on? +// x,y,w,h - left, top, width, height of the box +// ns - how does this drag the vertical axis? +// 'n' - top only +// 's' - bottom only +// 'ns' - top and bottom together, difference unchanged +// ew - same for horizontal axis +function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { + // mouseDown stores ms of first mousedown event in the last + // `gd._context.doubleClickDelay` ms on the drag bars + // numClicks stores how many mousedowns have been seen + // within `gd._context.doubleClickDelay` so we can check for click or doubleclick events + // dragged stores whether a drag has occurred, so we don't have to + // redraw unnecessarily, ie if no move bigger than MINDRAG or MINZOOM px + var zoomlayer = gd._fullLayout._zoomlayer; + var isMainDrag = (ns + ew === 'nsew'); + var singleEnd = (ns + ew).length === 1; + + // main subplot x and y (i.e. found in plotinfo - the main ones) + var xa0, ya0; + // {ax._id: ax} hash objects + var xaHash, yaHash; + // xaHash/yaHash values (arrays) + var xaxes, yaxes; + // main axis offsets + var xs, ys; + // main axis lengths + var pw, ph; + // contains keys 'xaHash', 'yaHash', 'xaxes', and 'yaxes' + // which are the x/y {ax._id: ax} hash objects and their values + // for linked axis relative to this subplot + var links; + // similar to `links` but for matching axes + var matches; + // set to ew/ns val when active, set to '' when inactive + var xActive, yActive; + // are all axes in this subplot are fixed? + var allFixedRanges; + // do we need to edit x/y ranges? + var editX, editY; + // graph-wide optimization flags + var hasScatterGl, hasSplom, hasSVG; + // collected changes to be made to the plot by relayout at the end + var updates; + + function recomputeAxisLists() { + xa0 = plotinfo.xaxis; + ya0 = plotinfo.yaxis; + pw = xa0._length; + ph = ya0._length; + xs = xa0._offset; + ys = ya0._offset; + + xaHash = {}; + xaHash[xa0._id] = xa0; + yaHash = {}; + yaHash[ya0._id] = ya0; + + // if we're dragging two axes at once, also drag overlays + if(ns && ew) { + var overlays = plotinfo.overlays; + for(var i = 0; i < overlays.length; i++) { + var xa = overlays[i].xaxis; + xaHash[xa._id] = xa; + var ya = overlays[i].yaxis; + yaHash[ya._id] = ya; + } + } + + xaxes = hashValues(xaHash); + yaxes = hashValues(yaHash); + xActive = isDirectionActive(xaxes, ew); + yActive = isDirectionActive(yaxes, ns); + allFixedRanges = !yActive && !xActive; + + links = calcLinks(gd, gd._fullLayout._axisConstraintGroups, xaHash, yaHash); + matches = calcLinks(gd, gd._fullLayout._axisMatchGroups, xaHash, yaHash); + editX = ew || links.isSubplotConstrained || matches.isSubplotConstrained; + editY = ns || links.isSubplotConstrained || matches.isSubplotConstrained; + + var fullLayout = gd._fullLayout; + hasScatterGl = fullLayout._has('scattergl'); + hasSplom = fullLayout._has('splom'); + hasSVG = fullLayout._has('svg'); + } + + recomputeAxisLists(); + + var cursor = getDragCursor(yActive + xActive, gd._fullLayout.dragmode, isMainDrag); + var dragger = makeRectDragger(plotinfo, ns + ew + 'drag', cursor, x, y, w, h); + + // still need to make the element if the axes are disabled + // but nuke its events (except for maindrag which needs them for hover) + // and stop there + if(allFixedRanges && !isMainDrag) { + dragger.onmousedown = null; + dragger.style.pointerEvents = 'none'; + return dragger; + } + + var dragOptions = { + element: dragger, + gd: gd, + plotinfo: plotinfo + }; + + dragOptions.prepFn = function(e, startX, startY) { + var dragModePrev = dragOptions.dragmode; + var dragModeNow = gd._fullLayout.dragmode; + if(dragModeNow !== dragModePrev) { + dragOptions.dragmode = dragModeNow; + } + + recomputeAxisLists(); + + if(!allFixedRanges) { + if(isMainDrag) { + // main dragger handles all drag modes, and changes + // to pan (or to zoom if it already is pan) on shift + if(e.shiftKey) { + if(dragModeNow === 'pan') dragModeNow = 'zoom'; + else if(!isSelectOrLasso(dragModeNow)) dragModeNow = 'pan'; + } else if(e.ctrlKey) { + dragModeNow = 'pan'; + } + } else { + // all other draggers just pan + dragModeNow = 'pan'; + } + } + + if(dragModeNow === 'lasso') dragOptions.minDrag = 1; + else dragOptions.minDrag = undefined; + + if(isSelectOrLasso(dragModeNow)) { + dragOptions.xaxes = xaxes; + dragOptions.yaxes = yaxes; + // this attaches moveFn, clickFn, doneFn on dragOptions + prepSelect(e, startX, startY, dragOptions, dragModeNow); + } else { + dragOptions.clickFn = clickFn; + if(isSelectOrLasso(dragModePrev)) { + // TODO Fix potential bug + // Note: clearing / resetting selection state only happens, when user + // triggers at least one interaction in pan/zoom mode. Otherwise, the + // select/lasso outlines are deleted (in plots.js.cleanPlot) but the selection + // cache isn't cleared. So when the user switches back to select/lasso and + // 'adds to a selection' with Shift, the "old", seemingly removed outlines + // are redrawn again because the selection cache still holds their coordinates. + // However, this isn't easily solved, since plots.js would need + // to have a reference to the dragOptions object (which holds the + // selection cache). + clearAndResetSelect(); + } + + if(!allFixedRanges) { + if(dragModeNow === 'zoom') { + dragOptions.moveFn = zoomMove; + dragOptions.doneFn = zoomDone; + + // zoomMove takes care of the threshold, but we need to + // minimize this so that constrained zoom boxes will flip + // orientation at the right place + dragOptions.minDrag = 1; + + zoomPrep(e, startX, startY); + } else if(dragModeNow === 'pan') { + dragOptions.moveFn = plotDrag; + dragOptions.doneFn = dragTail; + } + } + } + + gd._fullLayout._redrag = function() { + var dragDataNow = gd._dragdata; + + if(dragDataNow && dragDataNow.element === dragger) { + var dragModeNow = gd._fullLayout.dragmode; + + if(!isSelectOrLasso(dragModeNow)) { + recomputeAxisLists(); + updateSubplots([0, 0, pw, ph]); + dragOptions.moveFn(dragDataNow.dx, dragDataNow.dy); + } + + // TODO should we try to "re-select" under select/lasso modes? + // probably best to wait for https://github.com/plotly/plotly.js/issues/1851 + } + }; + }; + + function clearAndResetSelect() { + // clear selection polygon cache (if any) + dragOptions.plotinfo.selection = false; + // clear selection outlines + clearSelect(gd); + } + + function clickFn(numClicks, evt) { + var clickmode = gd._fullLayout.clickmode; + + removeZoombox(gd); + + if(numClicks === 2 && !singleEnd) doubleClick(); + + if(isMainDrag) { + if(clickmode.indexOf('select') > -1) { + selectOnClick(evt, gd, xaxes, yaxes, plotinfo.id, dragOptions); + } + + if(clickmode.indexOf('event') > -1) { + Fx.click(gd, evt, plotinfo.id); + } + } else if(numClicks === 1 && singleEnd) { + var ax = ns ? ya0 : xa0; + var end = (ns === 's' || ew === 'w') ? 0 : 1; + var attrStr = ax._name + '.range[' + end + ']'; + var initialText = getEndText(ax, end); + var hAlign = 'left'; + var vAlign = 'middle'; + + if(ax.fixedrange) return; + + if(ns) { + vAlign = (ns === 'n') ? 'top' : 'bottom'; + if(ax.side === 'right') hAlign = 'right'; + } else if(ew === 'e') hAlign = 'right'; + + if(gd._context.showAxisRangeEntryBoxes) { + d3.select(dragger) + .call(svgTextUtils.makeEditable, { + gd: gd, + immediate: true, + background: gd._fullLayout.paper_bgcolor, + text: String(initialText), + fill: ax.tickfont ? ax.tickfont.color : '#444', + horizontalAlign: hAlign, + verticalAlign: vAlign + }) + .on('edit', function(text) { + var v = ax.d2r(text); + if(v !== undefined) { + Registry.call('_guiRelayout', gd, attrStr, v); + } + }); + } + } + } + + dragElement.init(dragOptions); + + // x/y px position at start of drag + var x0, y0; + // bbox object of the zoombox + var box; + // luminance of bg behind zoombox + var lum; + // zoombox path outline + var path0; + // is zoombox dimmed (during drag) + var dimmed; + // 'x'-only, 'y' or 'xy' zooming + var zoomMode; + // zoombox d3 selection + var zb; + // zoombox corner d3 selection + var corners; + // zoom takes over minDrag, so it also has to take over gd._dragged + var zoomDragged; + + function zoomPrep(e, startX, startY) { + var dragBBox = dragger.getBoundingClientRect(); + x0 = startX - dragBBox.left; + y0 = startY - dragBBox.top; + box = {l: x0, r: x0, w: 0, t: y0, b: y0, h: 0}; + lum = gd._hmpixcount ? + (gd._hmlumcount / gd._hmpixcount) : + tinycolor(gd._fullLayout.plot_bgcolor).getLuminance(); + path0 = 'M0,0H' + pw + 'V' + ph + 'H0V0'; + dimmed = false; + zoomMode = 'xy'; + zoomDragged = false; + zb = makeZoombox(zoomlayer, lum, xs, ys, path0); + corners = makeCorners(zoomlayer, xs, ys); + } + + function zoomMove(dx0, dy0) { + if(gd._transitioningWithDuration) { + return false; + } + + var x1 = Math.max(0, Math.min(pw, dx0 + x0)); + var y1 = Math.max(0, Math.min(ph, dy0 + y0)); + var dx = Math.abs(x1 - x0); + var dy = Math.abs(y1 - y0); + + box.l = Math.min(x0, x1); + box.r = Math.max(x0, x1); + box.t = Math.min(y0, y1); + box.b = Math.max(y0, y1); + + function noZoom() { + zoomMode = ''; + box.r = box.l; + box.t = box.b; + corners.attr('d', 'M0,0Z'); + } + + if(links.isSubplotConstrained) { + if(dx > MINZOOM || dy > MINZOOM) { + zoomMode = 'xy'; + if(dx / pw > dy / ph) { + dy = dx * ph / pw; + if(y0 > y1) box.t = y0 - dy; + else box.b = y0 + dy; + } else { + dx = dy * pw / ph; + if(x0 > x1) box.l = x0 - dx; + else box.r = x0 + dx; + } + corners.attr('d', xyCorners(box)); + } else { + noZoom(); + } + } else if(matches.isSubplotConstrained) { + if(dx > MINZOOM || dy > MINZOOM) { + zoomMode = 'xy'; + + var r0 = Math.min(box.l / pw, (ph - box.b) / ph); + var r1 = Math.max(box.r / pw, (ph - box.t) / ph); + + box.l = r0 * pw; + box.r = r1 * pw; + box.b = (1 - r0) * ph; + box.t = (1 - r1) * ph; + corners.attr('d', xyCorners(box)); + } else { + noZoom(); + } + } else if(!yActive || dy < Math.min(Math.max(dx * 0.6, MINDRAG), MINZOOM)) { + // look for small drags in one direction or the other, + // and only drag the other axis + + if(dx < MINDRAG || !xActive) { + noZoom(); + } else { + box.t = 0; + box.b = ph; + zoomMode = 'x'; + corners.attr('d', xCorners(box, y0)); + } + } else if(!xActive || dx < Math.min(dy * 0.6, MINZOOM)) { + box.l = 0; + box.r = pw; + zoomMode = 'y'; + corners.attr('d', yCorners(box, x0)); + } else { + zoomMode = 'xy'; + corners.attr('d', xyCorners(box)); + } + box.w = box.r - box.l; + box.h = box.b - box.t; + + if(zoomMode) zoomDragged = true; + gd._dragged = zoomDragged; + + updateZoombox(zb, corners, box, path0, dimmed, lum); + computeZoomUpdates(); + gd.emit('plotly_relayouting', updates); + dimmed = true; + } + + function computeZoomUpdates() { + updates = {}; + + // TODO: edit linked axes in zoomAxRanges and in dragTail + if(zoomMode === 'xy' || zoomMode === 'x') { + zoomAxRanges(xaxes, box.l / pw, box.r / pw, updates, links.xaxes); + updateMatchedAxRange('x', updates); + } + if(zoomMode === 'xy' || zoomMode === 'y') { + zoomAxRanges(yaxes, (ph - box.b) / ph, (ph - box.t) / ph, updates, links.yaxes); + updateMatchedAxRange('y', updates); + } + } + + function zoomDone() { + computeZoomUpdates(); + removeZoombox(gd); + dragTail(); + showDoubleClickNotifier(gd); + } + + // scroll zoom, on all draggers except corners + var scrollViewBox = [0, 0, pw, ph]; + // wait a little after scrolling before redrawing + var redrawTimer = null; + var REDRAWDELAY = constants.REDRAWDELAY; + var mainplot = plotinfo.mainplot ? gd._fullLayout._plots[plotinfo.mainplot] : plotinfo; + + function zoomWheel(e) { + // deactivate mousewheel scrolling on embedded graphs + // devs can override this with layout._enablescrollzoom, + // but _ ensures this setting won't leave their page + if(!gd._context._scrollZoom.cartesian && !gd._fullLayout._enablescrollzoom) { + return; + } + + clearAndResetSelect(); + + // If a transition is in progress, then disable any behavior: + if(gd._transitioningWithDuration) { + e.preventDefault(); + e.stopPropagation(); + return; + } + + recomputeAxisLists(); + + clearTimeout(redrawTimer); + + var wheelDelta = -e.deltaY; + if(!isFinite(wheelDelta)) wheelDelta = e.wheelDelta / 10; + if(!isFinite(wheelDelta)) { + Lib.log('Did not find wheel motion attributes: ', e); + return; + } + + var zoom = Math.exp(-Math.min(Math.max(wheelDelta, -20), 20) / 200); + var gbb = mainplot.draglayer.select('.nsewdrag').node().getBoundingClientRect(); + var xfrac = (e.clientX - gbb.left) / gbb.width; + var yfrac = (gbb.bottom - e.clientY) / gbb.height; + var i; + + function zoomWheelOneAxis(ax, centerFraction, zoom) { + if(ax.fixedrange) return; + + var axRange = Lib.simpleMap(ax.range, ax.r2l); + var v0 = axRange[0] + (axRange[1] - axRange[0]) * centerFraction; + function doZoom(v) { return ax.l2r(v0 + (v - v0) * zoom); } + ax.range = axRange.map(doZoom); + } + + if(editX) { + // if we're only zooming this axis because of constraints, + // zoom it about the center + if(!ew) xfrac = 0.5; + + for(i = 0; i < xaxes.length; i++) { + zoomWheelOneAxis(xaxes[i], xfrac, zoom); + } + updateMatchedAxRange('x'); + + scrollViewBox[2] *= zoom; + scrollViewBox[0] += scrollViewBox[2] * xfrac * (1 / zoom - 1); + } + if(editY) { + if(!ns) yfrac = 0.5; + + for(i = 0; i < yaxes.length; i++) { + zoomWheelOneAxis(yaxes[i], yfrac, zoom); + } + updateMatchedAxRange('y'); + + scrollViewBox[3] *= zoom; + scrollViewBox[1] += scrollViewBox[3] * (1 - yfrac) * (1 / zoom - 1); + } + + // viewbox redraw at first + updateSubplots(scrollViewBox); + ticksAndAnnotations(); + + gd.emit('plotly_relayouting', updates); + + // then replot after a delay to make sure + // no more scrolling is coming + redrawTimer = setTimeout(function() { + scrollViewBox = [0, 0, pw, ph]; + dragTail(); + }, REDRAWDELAY); + + e.preventDefault(); + return; + } + + // everything but the corners gets wheel zoom + if(ns.length * ew.length !== 1) { + attachWheelEventHandler(dragger, zoomWheel); + } + + // plotDrag: move the plot in response to a drag + function plotDrag(dx, dy) { + // If a transition is in progress, then disable any behavior: + if(gd._transitioningWithDuration) { + return; + } + + // prevent axis drawing from monkeying with margins until we're done + gd._fullLayout._replotting = true; + + if(xActive === 'ew' || yActive === 'ns') { + if(xActive) { + dragAxList(xaxes, dx); + updateMatchedAxRange('x'); + } + if(yActive) { + dragAxList(yaxes, dy); + updateMatchedAxRange('y'); + } + updateSubplots([xActive ? -dx : 0, yActive ? -dy : 0, pw, ph]); + ticksAndAnnotations(); + gd.emit('plotly_relayouting', updates); + return; + } + + // dz: set a new value for one end (0 or 1) of an axis array axArray, + // and return a pixel shift for that end for the viewbox + // based on pixel drag distance d + // TODO: this makes (generally non-fatal) errors when you get + // near floating point limits + function dz(axArray, end, d) { + var otherEnd = 1 - end; + var movedAx; + var newLinearizedEnd; + for(var i = 0; i < axArray.length; i++) { + var axi = axArray[i]; + if(axi.fixedrange) continue; + movedAx = axi; + newLinearizedEnd = axi._rl[otherEnd] + + (axi._rl[end] - axi._rl[otherEnd]) / dZoom(d / axi._length); + var newEnd = axi.l2r(newLinearizedEnd); + + // if l2r comes back false or undefined, it means we've dragged off + // the end of valid ranges - so stop. + if(newEnd !== false && newEnd !== undefined) axi.range[end] = newEnd; + } + return movedAx._length * (movedAx._rl[end] - newLinearizedEnd) / + (movedAx._rl[end] - movedAx._rl[otherEnd]); + } + + if(links.isSubplotConstrained && xActive && yActive) { + // dragging a corner of a constrained subplot: + // respect the fixed corner, but harmonize dx and dy + var dxySign = ((xActive === 'w') === (yActive === 'n')) ? 1 : -1; + var dxyFraction = (dx / pw + dxySign * dy / ph) / 2; + dx = dxyFraction * pw; + dy = dxySign * dxyFraction * ph; + } + + if(xActive === 'w') dx = dz(xaxes, 0, dx); + else if(xActive === 'e') dx = dz(xaxes, 1, -dx); + else if(!xActive) dx = 0; + + if(yActive === 'n') dy = dz(yaxes, 1, dy); + else if(yActive === 's') dy = dz(yaxes, 0, -dy); + else if(!yActive) dy = 0; + + var xStart = (xActive === 'w') ? dx : 0; + var yStart = (yActive === 'n') ? dy : 0; + + if(links.isSubplotConstrained) { + var i; + if(!xActive && yActive.length === 1) { + // dragging one end of the y axis of a constrained subplot + // scale the other axis the same about its middle + for(i = 0; i < xaxes.length; i++) { + xaxes[i].range = xaxes[i]._r.slice(); + scaleZoom(xaxes[i], 1 - dy / ph); + } + dx = dy * pw / ph; + xStart = dx / 2; + } + if(!yActive && xActive.length === 1) { + for(i = 0; i < yaxes.length; i++) { + yaxes[i].range = yaxes[i]._r.slice(); + scaleZoom(yaxes[i], 1 - dx / pw); + } + dy = dx * ph / pw; + yStart = dy / 2; + } + } + + updateMatchedAxRange('x'); + updateMatchedAxRange('y'); + updateSubplots([xStart, yStart, pw - dx, ph - dy]); + ticksAndAnnotations(); + gd.emit('plotly_relayouting', updates); + } + + function updateMatchedAxRange(axLetter, out) { + var matchedAxes = matches.isSubplotConstrained ? + {x: yaxes, y: xaxes}[axLetter] : + matches[axLetter + 'axes']; + + var constrainedAxes = matches.isSubplotConstrained ? + {x: xaxes, y: yaxes}[axLetter] : + []; + + for(var i = 0; i < matchedAxes.length; i++) { + var ax = matchedAxes[i]; + var axId = ax._id; + var axId2 = matches.xLinks[axId] || matches.yLinks[axId]; + var ax2 = constrainedAxes[0] || xaHash[axId2] || yaHash[axId2]; + + if(ax2) { + if(out) { + // zoombox case - don't mutate 'range', just add keys in 'updates' + out[ax._name + '.range[0]'] = out[ax2._name + '.range[0]']; + out[ax._name + '.range[1]'] = out[ax2._name + '.range[1]']; + } else { + ax.range = ax2.range.slice(); + } + } + } + } + + // Draw ticks and annotations (and other components) when ranges change. + // Also records the ranges that have changed for use by update at the end. + function ticksAndAnnotations() { + var activeAxIds = []; + var i; + + function pushActiveAxIds(axList) { + for(i = 0; i < axList.length; i++) { + if(!axList[i].fixedrange) activeAxIds.push(axList[i]._id); + } + } + + if(editX) { + pushActiveAxIds(xaxes); + pushActiveAxIds(links.xaxes); + pushActiveAxIds(matches.xaxes); + } + if(editY) { + pushActiveAxIds(yaxes); + pushActiveAxIds(links.yaxes); + pushActiveAxIds(matches.yaxes); + } + + updates = {}; + for(i = 0; i < activeAxIds.length; i++) { + var axId = activeAxIds[i]; + var ax = getFromId(gd, axId); + Axes.drawOne(gd, ax, {skipTitle: true}); + updates[ax._name + '.range[0]'] = ax.range[0]; + updates[ax._name + '.range[1]'] = ax.range[1]; + } + + Axes.redrawComponents(gd, activeAxIds); + } + + function doubleClick() { + if(gd._transitioningWithDuration) return; + + var doubleClickConfig = gd._context.doubleClick; + + var axList = []; + if(xActive) axList = axList.concat(xaxes); + if(yActive) axList = axList.concat(yaxes); + if(matches.xaxes) axList = axList.concat(matches.xaxes); + if(matches.yaxes) axList = axList.concat(matches.yaxes); + + var attrs = {}; + var ax, i, rangeInitial; + + // For reset+autosize mode: + // If *any* of the main axes is not at its initial range + // (or autoranged, if we have no initial range, to match the logic in + // doubleClickConfig === 'reset' below), we reset. + // If they are *all* at their initial ranges, then we autosize. + if(doubleClickConfig === 'reset+autosize') { + doubleClickConfig = 'autosize'; + + for(i = 0; i < axList.length; i++) { + ax = axList[i]; + if((ax._rangeInitial && ( + ax.range[0] !== ax._rangeInitial[0] || + ax.range[1] !== ax._rangeInitial[1] + )) || + (!ax._rangeInitial && !ax.autorange) + ) { + doubleClickConfig = 'reset'; + break; + } + } + } + + if(doubleClickConfig === 'autosize') { + // don't set the linked axes here, so relayout marks them as shrinkable + // and we autosize just to the requested axis/axes + for(i = 0; i < axList.length; i++) { + ax = axList[i]; + if(!ax.fixedrange) attrs[ax._name + '.autorange'] = true; + } + } else if(doubleClickConfig === 'reset') { + // when we're resetting, reset all linked axes too, so we get back + // to the fully-auto-with-constraints situation + if(xActive || links.isSubplotConstrained) axList = axList.concat(links.xaxes); + if(yActive && !links.isSubplotConstrained) axList = axList.concat(links.yaxes); + + if(links.isSubplotConstrained) { + if(!xActive) axList = axList.concat(xaxes); + else if(!yActive) axList = axList.concat(yaxes); + } + + for(i = 0; i < axList.length; i++) { + ax = axList[i]; + + if(!ax.fixedrange) { + if(!ax._rangeInitial) { + attrs[ax._name + '.autorange'] = true; + } else { + rangeInitial = ax._rangeInitial; + attrs[ax._name + '.range[0]'] = rangeInitial[0]; + attrs[ax._name + '.range[1]'] = rangeInitial[1]; + } + } + } + } + + gd.emit('plotly_doubleclick', null); + Registry.call('_guiRelayout', gd, attrs); + } + + // dragTail - finish a drag event with a redraw + function dragTail() { + // put the subplot viewboxes back to default (Because we're going to) + // be repositioning the data in the relayout. But DON'T call + // ticksAndAnnotations again - it's unnecessary and would overwrite `updates` + updateSubplots([0, 0, pw, ph]); + + // since we may have been redrawing some things during the drag, we may have + // accumulated MathJax promises - wait for them before we relayout. + Lib.syncOrAsync([ + Plots.previousPromises, + function() { + gd._fullLayout._replotting = false; + Registry.call('_guiRelayout', gd, updates); + } + ], gd); + } + + // updateSubplots - find all plot viewboxes that should be + // affected by this drag, and update them. look for all plots + // sharing an affected axis (including the one being dragged), + // includes also scattergl and splom logic. + function updateSubplots(viewBox) { + var fullLayout = gd._fullLayout; + var plotinfos = fullLayout._plots; + var subplots = fullLayout._subplots.cartesian; + var i, sp, xa, ya; + + if(hasSplom) { + Registry.subplotsRegistry.splom.drag(gd); + } + + if(hasScatterGl) { + for(i = 0; i < subplots.length; i++) { + sp = plotinfos[subplots[i]]; + xa = sp.xaxis; + ya = sp.yaxis; + + if(sp._scene) { + var xrng = Lib.simpleMap(xa.range, xa.r2l); + var yrng = Lib.simpleMap(ya.range, ya.r2l); + sp._scene.update({range: [xrng[0], yrng[0], xrng[1], yrng[1]]}); + } + } + } + + if(hasSplom || hasScatterGl) { + clearGlCanvases(gd); + redrawReglTraces(gd); + } + + if(hasSVG) { + var xScaleFactor = viewBox[2] / xa0._length; + var yScaleFactor = viewBox[3] / ya0._length; + + for(i = 0; i < subplots.length; i++) { + sp = plotinfos[subplots[i]]; + xa = sp.xaxis; + ya = sp.yaxis; + + var editX2 = editX && !xa.fixedrange && xaHash[xa._id]; + var editY2 = editY && !ya.fixedrange && yaHash[ya._id]; + + var xScaleFactor2, yScaleFactor2; + var clipDx, clipDy; + + if(editX2) { + xScaleFactor2 = xScaleFactor; + clipDx = ew ? viewBox[0] : getShift(xa, xScaleFactor2); + } else if(matches.xaHash[xa._id]) { + xScaleFactor2 = xScaleFactor; + clipDx = viewBox[0] * xa._length / xa0._length; + } else if(matches.yaHash[xa._id]) { + xScaleFactor2 = yScaleFactor; + clipDx = yActive === 'ns' ? + -viewBox[1] * xa._length / ya0._length : + getShift(xa, xScaleFactor2, {n: 'top', s: 'bottom'}[yActive]); + } else { + xScaleFactor2 = getLinkedScaleFactor(xa, xScaleFactor, yScaleFactor); + clipDx = scaleAndGetShift(xa, xScaleFactor2); + } + + if(editY2) { + yScaleFactor2 = yScaleFactor; + clipDy = ns ? viewBox[1] : getShift(ya, yScaleFactor2); + } else if(matches.yaHash[ya._id]) { + yScaleFactor2 = yScaleFactor; + clipDy = viewBox[1] * ya._length / ya0._length; + } else if(matches.xaHash[ya._id]) { + yScaleFactor2 = xScaleFactor; + clipDy = xActive === 'ew' ? + -viewBox[0] * ya._length / xa0._length : + getShift(ya, yScaleFactor2, {e: 'right', w: 'left'}[xActive]); + } else { + yScaleFactor2 = getLinkedScaleFactor(ya, xScaleFactor, yScaleFactor); + clipDy = scaleAndGetShift(ya, yScaleFactor2); + } + + // don't scale at all if neither axis is scalable here + if(!xScaleFactor2 && !yScaleFactor2) { + continue; + } + + // but if only one is, reset the other axis scaling + if(!xScaleFactor2) xScaleFactor2 = 1; + if(!yScaleFactor2) yScaleFactor2 = 1; + + var plotDx = xa._offset - clipDx / xScaleFactor2; + var plotDy = ya._offset - clipDy / yScaleFactor2; + + // TODO could be more efficient here: + // setTranslate and setScale do a lot of extra work + // when working independently, should perhaps combine + // them into a single routine. + sp.clipRect + .call(Drawing.setTranslate, clipDx, clipDy) + .call(Drawing.setScale, xScaleFactor2, yScaleFactor2); + + sp.plot + .call(Drawing.setTranslate, plotDx, plotDy) + .call(Drawing.setScale, 1 / xScaleFactor2, 1 / yScaleFactor2); + + // apply an inverse scale to individual points to counteract + // the scale of the trace group. + // apply only when scale changes, as adjusting the scale of + // all the points can be expansive. + if(xScaleFactor2 !== sp.xScaleFactor || yScaleFactor2 !== sp.yScaleFactor) { + Drawing.setPointGroupScale(sp.zoomScalePts, xScaleFactor2, yScaleFactor2); + Drawing.setTextPointsScale(sp.zoomScaleTxt, xScaleFactor2, yScaleFactor2); + } + + Drawing.hideOutsideRangePoints(sp.clipOnAxisFalseTraces, sp); + + // update x/y scaleFactor stash + sp.xScaleFactor = xScaleFactor2; + sp.yScaleFactor = yScaleFactor2; + } + } + } + + // Find the appropriate scaling for this axis, if it's linked to the + // dragged axes by constraints. 0 is special, it means this axis shouldn't + // ever be scaled (will be converted to 1 if the other axis is scaled) + function getLinkedScaleFactor(ax, xScaleFactor, yScaleFactor) { + if(ax.fixedrange) return 0; + + if(editX && links.xaHash[ax._id]) { + return xScaleFactor; + } + if(editY && (links.isSubplotConstrained ? links.xaHash : links.yaHash)[ax._id]) { + return yScaleFactor; + } + return 0; + } + + function scaleAndGetShift(ax, scaleFactor) { + if(scaleFactor) { + ax.range = ax._r.slice(); + scaleZoom(ax, scaleFactor); + return getShift(ax, scaleFactor); + } + return 0; + } + + function getShift(ax, scaleFactor, from) { + return ax._length * (1 - scaleFactor) * FROM_TL[from || ax.constraintoward || 'middle']; + } + + return dragger; +} + +function makeDragger(plotinfo, nodeName, dragClass, cursor) { + var dragger3 = Lib.ensureSingle(plotinfo.draglayer, nodeName, dragClass, function(s) { + s.classed('drag', true) + .style({fill: 'transparent', 'stroke-width': 0}) + .attr('data-subplot', plotinfo.id); + }); + + dragger3.call(setCursor, cursor); + + return dragger3.node(); +} + +function makeRectDragger(plotinfo, dragClass, cursor, x, y, w, h) { + var dragger = makeDragger(plotinfo, 'rect', dragClass, cursor); + d3.select(dragger).call(Drawing.setRect, x, y, w, h); + return dragger; +} + +function isDirectionActive(axList, activeVal) { + for(var i = 0; i < axList.length; i++) { + if(!axList[i].fixedrange) return activeVal; + } + return ''; +} + +function getEndText(ax, end) { + var initialVal = ax.range[end]; + var diff = Math.abs(initialVal - ax.range[1 - end]); + var dig; + + // TODO: this should basically be ax.r2d but we're doing extra + // rounding here... can we clean up at all? + if(ax.type === 'date') { + return initialVal; + } else if(ax.type === 'log') { + dig = Math.ceil(Math.max(0, -Math.log(diff) / Math.LN10)) + 3; + return d3.format('.' + dig + 'g')(Math.pow(10, initialVal)); + } else { // linear numeric (or category... but just show numbers here) + dig = Math.floor(Math.log(Math.abs(initialVal)) / Math.LN10) - + Math.floor(Math.log(diff) / Math.LN10) + 4; + return d3.format('.' + String(dig) + 'g')(initialVal); + } +} + +function zoomAxRanges(axList, r0Fraction, r1Fraction, updates, linkedAxes) { + for(var i = 0; i < axList.length; i++) { + var axi = axList[i]; + if(axi.fixedrange) continue; + + var axRangeLinear0 = axi._rl[0]; + var axRangeLinearSpan = axi._rl[1] - axRangeLinear0; + updates[axi._name + '.range[0]'] = axi.l2r(axRangeLinear0 + axRangeLinearSpan * r0Fraction); + updates[axi._name + '.range[1]'] = axi.l2r(axRangeLinear0 + axRangeLinearSpan * r1Fraction); + } + + // zoom linked axes about their centers + if(linkedAxes && linkedAxes.length) { + var linkedR0Fraction = (r0Fraction + (1 - r1Fraction)) / 2; + zoomAxRanges(linkedAxes, linkedR0Fraction, 1 - linkedR0Fraction, updates, []); + } +} + +function dragAxList(axList, pix) { + for(var i = 0; i < axList.length; i++) { + var axi = axList[i]; + if(!axi.fixedrange) { + axi.range = [ + axi.l2r(axi._rl[0] - pix / axi._m), + axi.l2r(axi._rl[1] - pix / axi._m) + ]; + } + } +} + +// common transform for dragging one end of an axis +// d>0 is compressing scale (cursor is over the plot, +// the axis end should move with the cursor) +// d<0 is expanding (cursor is off the plot, axis end moves +// nonlinearly so you can expand far) +function dZoom(d) { + return 1 - ((d >= 0) ? Math.min(d, 0.9) : + 1 / (1 / Math.max(d, -0.3) + 3.222)); +} + +function getDragCursor(nsew, dragmode, isMainDrag) { + if(!nsew) return 'pointer'; + if(nsew === 'nsew') { + // in this case here, clear cursor and + // use the cursor style set on + if(isMainDrag) return ''; + if(dragmode === 'pan') return 'move'; + return 'crosshair'; + } + return nsew.toLowerCase() + '-resize'; +} + +function makeZoombox(zoomlayer, lum, xs, ys, path0) { + return zoomlayer.append('path') + .attr('class', 'zoombox') + .style({ + 'fill': lum > 0.2 ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0)', + 'stroke-width': 0 + }) + .attr('transform', 'translate(' + xs + ', ' + ys + ')') + .attr('d', path0 + 'Z'); +} + +function makeCorners(zoomlayer, xs, ys) { + return zoomlayer.append('path') + .attr('class', 'zoombox-corners') + .style({ + fill: Color.background, + stroke: Color.defaultLine, + 'stroke-width': 1, + opacity: 0 + }) + .attr('transform', 'translate(' + xs + ', ' + ys + ')') + .attr('d', 'M0,0Z'); +} + +function updateZoombox(zb, corners, box, path0, dimmed, lum) { + zb.attr('d', + path0 + 'M' + (box.l) + ',' + (box.t) + 'v' + (box.h) + + 'h' + (box.w) + 'v-' + (box.h) + 'h-' + (box.w) + 'Z'); + transitionZoombox(zb, corners, dimmed, lum); +} + +function transitionZoombox(zb, corners, dimmed, lum) { + if(!dimmed) { + zb.transition() + .style('fill', lum > 0.2 ? 'rgba(0,0,0,0.4)' : + 'rgba(255,255,255,0.3)') + .duration(200); + corners.transition() + .style('opacity', 1) + .duration(200); + } +} + +function removeZoombox(gd) { + d3.select(gd) + .selectAll('.zoombox,.js-zoombox-backdrop,.js-zoombox-menu,.zoombox-corners') + .remove(); +} + +function showDoubleClickNotifier(gd) { + if(SHOWZOOMOUTTIP && gd.data && gd._context.showTips) { + Lib.notifier(Lib._(gd, 'Double-click to zoom back out'), 'long'); + SHOWZOOMOUTTIP = false; + } +} + +function isSelectOrLasso(dragmode) { + return dragmode === 'lasso' || dragmode === 'select'; +} + +function xCorners(box, y0) { + return 'M' + + (box.l - 0.5) + ',' + (y0 - MINZOOM - 0.5) + + 'h-3v' + (2 * MINZOOM + 1) + 'h3ZM' + + (box.r + 0.5) + ',' + (y0 - MINZOOM - 0.5) + + 'h3v' + (2 * MINZOOM + 1) + 'h-3Z'; +} + +function yCorners(box, x0) { + return 'M' + + (x0 - MINZOOM - 0.5) + ',' + (box.t - 0.5) + + 'v-3h' + (2 * MINZOOM + 1) + 'v3ZM' + + (x0 - MINZOOM - 0.5) + ',' + (box.b + 0.5) + + 'v3h' + (2 * MINZOOM + 1) + 'v-3Z'; +} + +function xyCorners(box) { + var clen = Math.floor(Math.min(box.b - box.t, box.r - box.l, MINZOOM) / 2); + return 'M' + + (box.l - 3.5) + ',' + (box.t - 0.5 + clen) + 'h3v' + (-clen) + + 'h' + clen + 'v-3h-' + (clen + 3) + 'ZM' + + (box.r + 3.5) + ',' + (box.t - 0.5 + clen) + 'h-3v' + (-clen) + + 'h' + (-clen) + 'v-3h' + (clen + 3) + 'ZM' + + (box.r + 3.5) + ',' + (box.b + 0.5 - clen) + 'h-3v' + clen + + 'h' + (-clen) + 'v3h' + (clen + 3) + 'ZM' + + (box.l - 3.5) + ',' + (box.b + 0.5 - clen) + 'h3v' + clen + + 'h' + clen + 'v3h-' + (clen + 3) + 'Z'; +} + +function calcLinks(gd, groups, xaHash, yaHash) { + var isSubplotConstrained = false; + var xLinks = {}; + var yLinks = {}; + var xID, yID, xLinkID, yLinkID; + + for(var i = 0; i < groups.length; i++) { + var group = groups[i]; + // check if any of the x axes we're dragging is in this constraint group + for(xID in xaHash) { + if(group[xID]) { + // put the rest of these axes into xLinks, if we're not already + // dragging them, so we know to scale these axes automatically too + // to match the changes in the dragged x axes + for(xLinkID in group) { + if(!(xLinkID.charAt(0) === 'x' ? xaHash : yaHash)[xLinkID]) { + xLinks[xLinkID] = xID; + } + } + + // check if the x and y axes of THIS drag are linked + for(yID in yaHash) { + if(group[yID]) isSubplotConstrained = true; + } + } + } + + // now check if any of the y axes we're dragging is in this constraint group + // only look for outside links, as we've already checked for links within the dragger + for(yID in yaHash) { + if(group[yID]) { + for(yLinkID in group) { + if(!(yLinkID.charAt(0) === 'x' ? xaHash : yaHash)[yLinkID]) { + yLinks[yLinkID] = yID; + } + } + } + } + } + + if(isSubplotConstrained) { + // merge xLinks and yLinks if the subplot is constrained, + // since we'll always apply both anyway and the two will contain + // duplicates + Lib.extendFlat(xLinks, yLinks); + yLinks = {}; + } + + var xaHashLinked = {}; + var xaxesLinked = []; + for(xLinkID in xLinks) { + var xa = getFromId(gd, xLinkID); + xaxesLinked.push(xa); + xaHashLinked[xa._id] = xa; + } + + var yaHashLinked = {}; + var yaxesLinked = []; + for(yLinkID in yLinks) { + var ya = getFromId(gd, yLinkID); + yaxesLinked.push(ya); + yaHashLinked[ya._id] = ya; + } + + return { + xaHash: xaHashLinked, + yaHash: yaHashLinked, + xaxes: xaxesLinked, + yaxes: yaxesLinked, + xLinks: xLinks, + yLinks: yLinks, + isSubplotConstrained: isSubplotConstrained + }; +} + +// still seems to be some confusion about onwheel vs onmousewheel... +function attachWheelEventHandler(element, handler) { + if(!supportsPassive) { + if(element.onwheel !== undefined) element.onwheel = handler; + else if(element.onmousewheel !== undefined) element.onmousewheel = handler; + } else { + var wheelEventName = element.onwheel !== undefined ? 'wheel' : 'mousewheel'; + + if(element._onwheel) { + element.removeEventListener(wheelEventName, element._onwheel); + } + element._onwheel = handler; + + element.addEventListener(wheelEventName, handler, {passive: false}); + } +} + +function hashValues(hash) { + var out = []; + for(var k in hash) out.push(hash[k]); + return out; +} + +module.exports = { + makeDragBox: makeDragBox, + + makeDragger: makeDragger, + makeRectDragger: makeRectDragger, + makeZoombox: makeZoombox, + makeCorners: makeCorners, + + updateZoombox: updateZoombox, + xyCorners: xyCorners, + transitionZoombox: transitionZoombox, + removeZoombox: removeZoombox, + showDoubleClickNotifier: showDoubleClickNotifier, + + attachWheelEventHandler: attachWheelEventHandler +}; + +},{"../../components/color":51,"../../components/dragelement":69,"../../components/drawing":72,"../../components/fx":89,"../../constants/alignment":145,"../../lib":169,"../../lib/clear_gl_canvases":158,"../../lib/setcursor":188,"../../lib/svg_text_utils":190,"../../plot_api/subroutines":204,"../../registry":258,"../plots":245,"./axes":213,"./axis_ids":216,"./constants":219,"./scale_zoom":229,"./select":230,"d3":16,"has-passive-events":21,"tinycolor2":34}],222:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var d3 = _dereq_('d3'); + +var Fx = _dereq_('../../components/fx'); +var dragElement = _dereq_('../../components/dragelement'); +var setCursor = _dereq_('../../lib/setcursor'); + +var makeDragBox = _dereq_('./dragbox').makeDragBox; +var DRAGGERSIZE = _dereq_('./constants').DRAGGERSIZE; + +exports.initInteractions = function initInteractions(gd) { + var fullLayout = gd._fullLayout; + + if(gd._context.staticPlot) { + // this sweeps up more than just cartesian drag elements... + d3.select(gd).selectAll('.drag').remove(); + return; + } + + if(!fullLayout._has('cartesian') && !fullLayout._has('splom')) return; + + var subplots = Object.keys(fullLayout._plots || {}).sort(function(a, b) { + // sort overlays last, then by x axis number, then y axis number + if((fullLayout._plots[a].mainplot && true) === + (fullLayout._plots[b].mainplot && true)) { + var aParts = a.split('y'); + var bParts = b.split('y'); + return (aParts[0] === bParts[0]) ? + (Number(aParts[1] || 1) - Number(bParts[1] || 1)) : + (Number(aParts[0] || 1) - Number(bParts[0] || 1)); + } + return fullLayout._plots[a].mainplot ? 1 : -1; + }); + + subplots.forEach(function(subplot) { + var plotinfo = fullLayout._plots[subplot]; + var xa = plotinfo.xaxis; + var ya = plotinfo.yaxis; + + // main and corner draggers need not be repeated for + // overlaid subplots - these draggers drag them all + if(!plotinfo.mainplot) { + // main dragger goes over the grids and data, so we use its + // mousemove events for all data hover effects + var maindrag = makeDragBox(gd, plotinfo, xa._offset, ya._offset, + xa._length, ya._length, 'ns', 'ew'); + + maindrag.onmousemove = function(evt) { + // This is on `gd._fullLayout`, *not* fullLayout because the reference + // changes by the time this is called again. + gd._fullLayout._rehover = function() { + if((gd._fullLayout._hoversubplot === subplot) && gd._fullLayout._plots[subplot]) { + Fx.hover(gd, evt, subplot); + } + }; + + Fx.hover(gd, evt, subplot); + + // Note that we have *not* used the cached fullLayout variable here + // since that may be outdated when this is called as a callback later on + gd._fullLayout._lasthover = maindrag; + gd._fullLayout._hoversubplot = subplot; + }; + + /* + * IMPORTANT: + * We must check for the presence of the drag cover here. + * If we don't, a 'mouseout' event is triggered on the + * maindrag before each 'click' event, which has the effect + * of clearing the hoverdata; thus, cancelling the click event. + */ + maindrag.onmouseout = function(evt) { + if(gd._dragging) return; + + // When the mouse leaves this maindrag, unset the hovered subplot. + // This may cause problems if it leaves the subplot directly *onto* + // another subplot, but that's a tiny corner case at the moment. + gd._fullLayout._hoversubplot = null; + + dragElement.unhover(gd, evt); + }; + + // corner draggers + if(gd._context.showAxisDragHandles) { + makeDragBox(gd, plotinfo, xa._offset - DRAGGERSIZE, ya._offset - DRAGGERSIZE, + DRAGGERSIZE, DRAGGERSIZE, 'n', 'w'); + makeDragBox(gd, plotinfo, xa._offset + xa._length, ya._offset - DRAGGERSIZE, + DRAGGERSIZE, DRAGGERSIZE, 'n', 'e'); + makeDragBox(gd, plotinfo, xa._offset - DRAGGERSIZE, ya._offset + ya._length, + DRAGGERSIZE, DRAGGERSIZE, 's', 'w'); + makeDragBox(gd, plotinfo, xa._offset + xa._length, ya._offset + ya._length, + DRAGGERSIZE, DRAGGERSIZE, 's', 'e'); + } + } + if(gd._context.showAxisDragHandles) { + // x axis draggers - if you have overlaid plots, + // these drag each axis separately + if(subplot === xa._mainSubplot) { + // the y position of the main x axis line + var y0 = xa._mainLinePosition; + if(xa.side === 'top') y0 -= DRAGGERSIZE; + makeDragBox(gd, plotinfo, xa._offset + xa._length * 0.1, y0, + xa._length * 0.8, DRAGGERSIZE, '', 'ew'); + makeDragBox(gd, plotinfo, xa._offset, y0, + xa._length * 0.1, DRAGGERSIZE, '', 'w'); + makeDragBox(gd, plotinfo, xa._offset + xa._length * 0.9, y0, + xa._length * 0.1, DRAGGERSIZE, '', 'e'); + } + // y axis draggers + if(subplot === ya._mainSubplot) { + // the x position of the main y axis line + var x0 = ya._mainLinePosition; + if(ya.side !== 'right') x0 -= DRAGGERSIZE; + makeDragBox(gd, plotinfo, x0, ya._offset + ya._length * 0.1, + DRAGGERSIZE, ya._length * 0.8, 'ns', ''); + makeDragBox(gd, plotinfo, x0, ya._offset + ya._length * 0.9, + DRAGGERSIZE, ya._length * 0.1, 's', ''); + makeDragBox(gd, plotinfo, x0, ya._offset, + DRAGGERSIZE, ya._length * 0.1, 'n', ''); + } + } + }); + + // In case you mousemove over some hovertext, send it to Fx.hover too + // we do this so that we can put the hover text in front of everything, + // but still be able to interact with everything as if it isn't there + var hoverLayer = fullLayout._hoverlayer.node(); + + hoverLayer.onmousemove = function(evt) { + evt.target = gd._fullLayout._lasthover; + Fx.hover(gd, evt, fullLayout._hoversubplot); + }; + + hoverLayer.onclick = function(evt) { + evt.target = gd._fullLayout._lasthover; + Fx.click(gd, evt); + }; + + // also delegate mousedowns... TODO: does this actually work? + hoverLayer.onmousedown = function(evt) { + gd._fullLayout._lasthover.onmousedown(evt); + }; + + exports.updateFx(gd); +}; + +// Minimal set of update needed on 'modebar' edits. +// We only need to update the cursor style. +// +// Note that changing the axis configuration and/or the fixedrange attribute +// should trigger a full initInteractions. +exports.updateFx = function(gd) { + var fullLayout = gd._fullLayout; + var cursor = fullLayout.dragmode === 'pan' ? 'move' : 'crosshair'; + setCursor(fullLayout._draggers, cursor); +}; + +},{"../../components/dragelement":69,"../../components/fx":89,"../../lib/setcursor":188,"./constants":219,"./dragbox":221,"d3":16}],223:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Registry = _dereq_('../../registry'); +var Lib = _dereq_('../../lib'); + +/** + * Factory function for checking component arrays for subplot references. + * + * @param {string} containerArrayName: the top-level array in gd.layout to check + * If an item in this container is found that references a cartesian x and/or y axis, + * ensure cartesian is marked as a base plot module and record the axes (and subplot + * if both refs are axes) in gd._fullLayout + * + * @return {function}: with args layoutIn (gd.layout) and layoutOut (gd._fullLayout) + * as expected of a component includeBasePlot method + */ +module.exports = function makeIncludeComponents(containerArrayName) { + return function includeComponents(layoutIn, layoutOut) { + var array = layoutIn[containerArrayName]; + if(!Array.isArray(array)) return; + + var Cartesian = Registry.subplotsRegistry.cartesian; + var idRegex = Cartesian.idRegex; + var subplots = layoutOut._subplots; + var xaList = subplots.xaxis; + var yaList = subplots.yaxis; + var cartesianList = subplots.cartesian; + var hasCartesianOrGL2D = layoutOut._has('cartesian') || layoutOut._has('gl2d'); + + for(var i = 0; i < array.length; i++) { + var itemi = array[i]; + if(!Lib.isPlainObject(itemi)) continue; + + var xref = itemi.xref; + var yref = itemi.yref; + + var hasXref = idRegex.x.test(xref); + var hasYref = idRegex.y.test(yref); + if(hasXref || hasYref) { + if(!hasCartesianOrGL2D) Lib.pushUnique(layoutOut._basePlotModules, Cartesian); + + var newAxis = false; + if(hasXref && xaList.indexOf(xref) === -1) { + xaList.push(xref); + newAxis = true; + } + if(hasYref && yaList.indexOf(yref) === -1) { + yaList.push(yref); + newAxis = true; + } + + /* + * Notice the logic here: only add a subplot for a component if + * it's referencing both x and y axes AND it's creating a new axis + * so for example if your plot already has xy and x2y2, an annotation + * on x2y or xy2 will not create a new subplot. + */ + if(newAxis && hasXref && hasYref) { + cartesianList.push(xref + yref); + } + } + } + }; +}; + +},{"../../lib":169,"../../registry":258}],224:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var d3 = _dereq_('d3'); + +var Registry = _dereq_('../../registry'); +var Lib = _dereq_('../../lib'); +var Plots = _dereq_('../plots'); +var Drawing = _dereq_('../../components/drawing'); + +var getModuleCalcData = _dereq_('../get_data').getModuleCalcData; +var axisIds = _dereq_('./axis_ids'); +var constants = _dereq_('./constants'); +var xmlnsNamespaces = _dereq_('../../constants/xmlns_namespaces'); + +var ensureSingle = Lib.ensureSingle; + +function ensureSingleAndAddDatum(parent, nodeType, className) { + return Lib.ensureSingle(parent, nodeType, className, function(s) { + s.datum(className); + }); +} + +exports.name = 'cartesian'; + +exports.attr = ['xaxis', 'yaxis']; + +exports.idRoot = ['x', 'y']; + +exports.idRegex = constants.idRegex; + +exports.attrRegex = constants.attrRegex; + +exports.attributes = _dereq_('./attributes'); + +exports.layoutAttributes = _dereq_('./layout_attributes'); + +exports.supplyLayoutDefaults = _dereq_('./layout_defaults'); + +exports.transitionAxes = _dereq_('./transition_axes'); + +exports.finalizeSubplots = function(layoutIn, layoutOut) { + var subplots = layoutOut._subplots; + var xList = subplots.xaxis; + var yList = subplots.yaxis; + var spSVG = subplots.cartesian; + var spAll = spSVG.concat(subplots.gl2d || []); + var allX = {}; + var allY = {}; + var i, xi, yi; + + for(i = 0; i < spAll.length; i++) { + var parts = spAll[i].split('y'); + allX[parts[0]] = 1; + allY['y' + parts[1]] = 1; + } + + // check for x axes with no subplot, and make one from the anchor of that x axis + for(i = 0; i < xList.length; i++) { + xi = xList[i]; + if(!allX[xi]) { + yi = (layoutIn[axisIds.id2name(xi)] || {}).anchor; + if(!constants.idRegex.y.test(yi)) yi = 'y'; + spSVG.push(xi + yi); + spAll.push(xi + yi); + + if(!allY[yi]) { + allY[yi] = 1; + Lib.pushUnique(yList, yi); + } + } + } + + // same for y axes with no subplot + for(i = 0; i < yList.length; i++) { + yi = yList[i]; + if(!allY[yi]) { + xi = (layoutIn[axisIds.id2name(yi)] || {}).anchor; + if(!constants.idRegex.x.test(xi)) xi = 'x'; + spSVG.push(xi + yi); + spAll.push(xi + yi); + + if(!allX[xi]) { + allX[xi] = 1; + Lib.pushUnique(xList, xi); + } + } + } + + // finally, if we've gotten here we're supposed to show cartesian... + // so if there are NO subplots at all, make one from the first + // x & y axes in the input layout + if(!spAll.length) { + xi = ''; + yi = ''; + for(var ki in layoutIn) { + if(constants.attrRegex.test(ki)) { + var axLetter = ki.charAt(0); + if(axLetter === 'x') { + if(!xi || (+ki.substr(5) < +xi.substr(5))) { + xi = ki; + } + } else if(!yi || (+ki.substr(5) < +yi.substr(5))) { + yi = ki; + } + } + } + xi = xi ? axisIds.name2id(xi) : 'x'; + yi = yi ? axisIds.name2id(yi) : 'y'; + xList.push(xi); + yList.push(yi); + spSVG.push(xi + yi); + } +}; + +/** + * Cartesian.plot + * + * @param {DOM div | object} gd + * @param {array (optional)} traces + * array of traces indices to plot + * if undefined, plots all cartesian traces, + * @param {object} (optional) transitionOpts + * transition option object + * @param {function} (optional) makeOnCompleteCallback + * transition make callback function from Plots.transition + */ +exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) { + var fullLayout = gd._fullLayout; + var subplots = fullLayout._subplots.cartesian; + var calcdata = gd.calcdata; + var i; + + if(!Array.isArray(traces)) { + // If traces is not provided, then it's a complete replot and missing + // traces are removed + traces = []; + for(i = 0; i < calcdata.length; i++) traces.push(i); + } + + for(i = 0; i < subplots.length; i++) { + var subplot = subplots[i]; + var subplotInfo = fullLayout._plots[subplot]; + + // Get all calcdata for this subplot: + var cdSubplot = []; + var pcd; + + for(var j = 0; j < calcdata.length; j++) { + var cd = calcdata[j]; + var trace = cd[0].trace; + + // Skip trace if whitelist provided and it's not whitelisted: + // if (Array.isArray(traces) && traces.indexOf(i) === -1) continue; + if(trace.xaxis + trace.yaxis === subplot) { + // XXX: Should trace carpet dependencies. Only replot all carpet plots if the carpet + // axis has actually changed: + // + // If this trace is specifically requested, add it to the list: + if(traces.indexOf(trace.index) !== -1 || trace.carpet) { + // Okay, so example: traces 0, 1, and 2 have fill = tonext. You animate + // traces 0 and 2. Trace 1 also needs to be updated, otherwise its fill + // is outdated. So this retroactively adds the previous trace if the + // traces are interdependent. + if( + pcd && + pcd[0].trace.xaxis + pcd[0].trace.yaxis === subplot && + ['tonextx', 'tonexty', 'tonext'].indexOf(trace.fill) !== -1 && + cdSubplot.indexOf(pcd) === -1 + ) { + cdSubplot.push(pcd); + } + + cdSubplot.push(cd); + } + + // Track the previous trace on this subplot for the retroactive-add step + // above: + pcd = cd; + } + } + + plotOne(gd, subplotInfo, cdSubplot, transitionOpts, makeOnCompleteCallback); + } +}; + +function plotOne(gd, plotinfo, cdSubplot, transitionOpts, makeOnCompleteCallback) { + var traceLayerClasses = constants.traceLayerClasses; + var fullLayout = gd._fullLayout; + var modules = fullLayout._modules; + var _module, cdModuleAndOthers, cdModule; + + var layerData = []; + var zoomScaleQueryParts = []; + + for(var i = 0; i < modules.length; i++) { + _module = modules[i]; + var name = _module.name; + var categories = Registry.modules[name].categories; + + if(categories.svg) { + var className = (_module.layerName || name + 'layer'); + var plotMethod = _module.plot; + + // plot all visible traces of this type on this subplot at once + cdModuleAndOthers = getModuleCalcData(cdSubplot, plotMethod); + cdModule = cdModuleAndOthers[0]; + // don't need to search the found traces again - in fact we need to NOT + // so that if two modules share the same plotter we don't double-plot + cdSubplot = cdModuleAndOthers[1]; + + if(cdModule.length) { + layerData.push({ + i: traceLayerClasses.indexOf(className), + className: className, + plotMethod: plotMethod, + cdModule: cdModule + }); + } + + if(categories.zoomScale) { + zoomScaleQueryParts.push('.' + className); + } + } + } + + layerData.sort(function(a, b) { return a.i - b.i; }); + + var layers = plotinfo.plot.selectAll('g.mlayer') + .data(layerData, function(d) { return d.className; }); + + layers.enter().append('g') + .attr('class', function(d) { return d.className; }) + .classed('mlayer', true) + .classed('rangeplot', plotinfo.isRangePlot); + + layers.exit().remove(); + + layers.order(); + + layers.each(function(d) { + var sel = d3.select(this); + var className = d.className; + + d.plotMethod( + gd, plotinfo, d.cdModule, sel, + transitionOpts, makeOnCompleteCallback + ); + + // layers that allow `cliponaxis: false` + if(constants.clipOnAxisFalseQuery.indexOf('.' + className) === -1) { + Drawing.setClipUrl(sel, plotinfo.layerClipId, gd); + } + }); + + // call Scattergl.plot separately + if(fullLayout._has('scattergl')) { + _module = Registry.getModule('scattergl'); + cdModule = getModuleCalcData(cdSubplot, _module)[0]; + _module.plot(gd, plotinfo, cdModule); + } + + // stash "hot" selections for faster interaction on drag and scroll + if(!gd._context.staticPlot) { + if(plotinfo._hasClipOnAxisFalse) { + plotinfo.clipOnAxisFalseTraces = plotinfo.plot + .selectAll(constants.clipOnAxisFalseQuery.join(',')) + .selectAll('.trace'); + } + + if(zoomScaleQueryParts.length) { + var traces = plotinfo.plot + .selectAll(zoomScaleQueryParts.join(',')) + .selectAll('.trace'); + + plotinfo.zoomScalePts = traces.selectAll('path.point'); + plotinfo.zoomScaleTxt = traces.selectAll('.textpoint'); + } + } +} + +exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) { + var oldPlots = oldFullLayout._plots || {}; + var newPlots = newFullLayout._plots || {}; + var oldSubplotList = oldFullLayout._subplots || {}; + var plotinfo; + var i, k; + + // when going from a large splom graph to something else, + // we need to clear so that the new cartesian subplot + // can have the correct layer ordering + if(oldFullLayout._hasOnlyLargeSploms && !newFullLayout._hasOnlyLargeSploms) { + for(k in oldPlots) { + plotinfo = oldPlots[k]; + if(plotinfo.plotgroup) plotinfo.plotgroup.remove(); + } + } + + var hadGl = (oldFullLayout._has && oldFullLayout._has('gl')); + var hasGl = (newFullLayout._has && newFullLayout._has('gl')); + + if(hadGl && !hasGl) { + for(k in oldPlots) { + plotinfo = oldPlots[k]; + if(plotinfo._scene) plotinfo._scene.destroy(); + } + } + + // delete any titles we don't need anymore + // check if axis list has changed, and if so clear old titles + if(oldSubplotList.xaxis && oldSubplotList.yaxis) { + var oldAxIDs = axisIds.listIds({_fullLayout: oldFullLayout}); + for(i = 0; i < oldAxIDs.length; i++) { + var oldAxId = oldAxIDs[i]; + if(!newFullLayout[axisIds.id2name(oldAxId)]) { + oldFullLayout._infolayer.selectAll('.g-' + oldAxId + 'title').remove(); + } + } + } + + var hadCartesian = (oldFullLayout._has && oldFullLayout._has('cartesian')); + var hasCartesian = (newFullLayout._has && newFullLayout._has('cartesian')); + + if(hadCartesian && !hasCartesian) { + // if we've gotten rid of all cartesian traces, remove all the subplot svg items + + purgeSubplotLayers(oldFullLayout._cartesianlayer.selectAll('.subplot'), oldFullLayout); + oldFullLayout._defs.selectAll('.axesclip').remove(); + delete oldFullLayout._axisConstraintGroups; + } else if(oldSubplotList.cartesian) { + // otherwise look for subplots we need to remove + + for(i = 0; i < oldSubplotList.cartesian.length; i++) { + var oldSubplotId = oldSubplotList.cartesian[i]; + if(!newPlots[oldSubplotId]) { + var selector = '.' + oldSubplotId + ',.' + oldSubplotId + '-x,.' + oldSubplotId + '-y'; + oldFullLayout._cartesianlayer.selectAll(selector).remove(); + removeSubplotExtras(oldSubplotId, oldFullLayout); + } + } + } +}; + +exports.drawFramework = function(gd) { + var fullLayout = gd._fullLayout; + var subplotData = makeSubplotData(gd); + + var subplotLayers = fullLayout._cartesianlayer.selectAll('.subplot') + .data(subplotData, String); + + subplotLayers.enter().append('g') + .attr('class', function(d) { return 'subplot ' + d[0]; }); + + subplotLayers.order(); + + subplotLayers.exit() + .call(purgeSubplotLayers, fullLayout); + + subplotLayers.each(function(d) { + var id = d[0]; + var plotinfo = fullLayout._plots[id]; + + plotinfo.plotgroup = d3.select(this); + makeSubplotLayer(gd, plotinfo); + + // make separate drag layers for each subplot, + // but append them to paper rather than the plot groups, + // so they end up on top of the rest + plotinfo.draglayer = ensureSingle(fullLayout._draggers, 'g', id); + }); +}; + +exports.rangePlot = function(gd, plotinfo, cdSubplot) { + makeSubplotLayer(gd, plotinfo); + plotOne(gd, plotinfo, cdSubplot); + Plots.style(gd); +}; + +function makeSubplotData(gd) { + var fullLayout = gd._fullLayout; + var ids = fullLayout._subplots.cartesian; + var len = ids.length; + var i, j, id, plotinfo, xa, ya; + + // split 'regular' and 'overlaying' subplots + var regulars = []; + var overlays = []; + + for(i = 0; i < len; i++) { + id = ids[i]; + plotinfo = fullLayout._plots[id]; + xa = plotinfo.xaxis; + ya = plotinfo.yaxis; + + var xa2 = xa._mainAxis; + var ya2 = ya._mainAxis; + var mainplot = xa2._id + ya2._id; + var mainplotinfo = fullLayout._plots[mainplot]; + plotinfo.overlays = []; + + if(mainplot !== id && mainplotinfo) { + plotinfo.mainplot = mainplot; + plotinfo.mainplotinfo = mainplotinfo; + overlays.push(id); + } else { + plotinfo.mainplot = undefined; + plotinfo.mainPlotinfo = undefined; + regulars.push(id); + } + } + + // fill in list of overlaying subplots in 'main plot' + for(i = 0; i < overlays.length; i++) { + id = overlays[i]; + plotinfo = fullLayout._plots[id]; + plotinfo.mainplotinfo.overlays.push(plotinfo); + } + + // put 'regular' subplot data before 'overlaying' + var subplotIds = regulars.concat(overlays); + var subplotData = new Array(len); + + for(i = 0; i < len; i++) { + id = subplotIds[i]; + plotinfo = fullLayout._plots[id]; + xa = plotinfo.xaxis; + ya = plotinfo.yaxis; + + // use info about axis layer and overlaying pattern + // to clean what need to be cleaned up in exit selection + var d = [id, xa.layer, ya.layer, xa.overlaying || '', ya.overlaying || '']; + for(j = 0; j < plotinfo.overlays.length; j++) { + d.push(plotinfo.overlays[j].id); + } + subplotData[i] = d; + } + + return subplotData; +} + +function makeSubplotLayer(gd, plotinfo) { + var plotgroup = plotinfo.plotgroup; + var id = plotinfo.id; + var xLayer = constants.layerValue2layerClass[plotinfo.xaxis.layer]; + var yLayer = constants.layerValue2layerClass[plotinfo.yaxis.layer]; + var hasOnlyLargeSploms = gd._fullLayout._hasOnlyLargeSploms; + + if(!plotinfo.mainplot) { + if(hasOnlyLargeSploms) { + // TODO could do even better + // - we don't need plot (but we would have to mock it in lsInner + // and other places + // - we don't (x|y)lines and (x|y)axislayer for most subplots + // usually just the bottom x and left y axes. + plotinfo.xlines = ensureSingle(plotgroup, 'path', 'xlines-above'); + plotinfo.ylines = ensureSingle(plotgroup, 'path', 'ylines-above'); + plotinfo.xaxislayer = ensureSingle(plotgroup, 'g', 'xaxislayer-above'); + plotinfo.yaxislayer = ensureSingle(plotgroup, 'g', 'yaxislayer-above'); + } else { + var backLayer = ensureSingle(plotgroup, 'g', 'layer-subplot'); + plotinfo.shapelayer = ensureSingle(backLayer, 'g', 'shapelayer'); + plotinfo.imagelayer = ensureSingle(backLayer, 'g', 'imagelayer'); + + plotinfo.gridlayer = ensureSingle(plotgroup, 'g', 'gridlayer'); + plotinfo.zerolinelayer = ensureSingle(plotgroup, 'g', 'zerolinelayer'); + + ensureSingle(plotgroup, 'path', 'xlines-below'); + ensureSingle(plotgroup, 'path', 'ylines-below'); + plotinfo.overlinesBelow = ensureSingle(plotgroup, 'g', 'overlines-below'); + + ensureSingle(plotgroup, 'g', 'xaxislayer-below'); + ensureSingle(plotgroup, 'g', 'yaxislayer-below'); + plotinfo.overaxesBelow = ensureSingle(plotgroup, 'g', 'overaxes-below'); + + plotinfo.plot = ensureSingle(plotgroup, 'g', 'plot'); + plotinfo.overplot = ensureSingle(plotgroup, 'g', 'overplot'); + + plotinfo.xlines = ensureSingle(plotgroup, 'path', 'xlines-above'); + plotinfo.ylines = ensureSingle(plotgroup, 'path', 'ylines-above'); + plotinfo.overlinesAbove = ensureSingle(plotgroup, 'g', 'overlines-above'); + + ensureSingle(plotgroup, 'g', 'xaxislayer-above'); + ensureSingle(plotgroup, 'g', 'yaxislayer-above'); + plotinfo.overaxesAbove = ensureSingle(plotgroup, 'g', 'overaxes-above'); + + // set refs to correct layers as determined by 'axis.layer' + plotinfo.xlines = plotgroup.select('.xlines-' + xLayer); + plotinfo.ylines = plotgroup.select('.ylines-' + yLayer); + plotinfo.xaxislayer = plotgroup.select('.xaxislayer-' + xLayer); + plotinfo.yaxislayer = plotgroup.select('.yaxislayer-' + yLayer); + } + } else { + var mainplotinfo = plotinfo.mainplotinfo; + var mainplotgroup = mainplotinfo.plotgroup; + var xId = id + '-x'; + var yId = id + '-y'; + + // now make the components of overlaid subplots + // overlays don't have backgrounds, and append all + // their other components to the corresponding + // extra groups of their main plots. + + plotinfo.gridlayer = mainplotinfo.gridlayer; + plotinfo.zerolinelayer = mainplotinfo.zerolinelayer; + + ensureSingle(mainplotinfo.overlinesBelow, 'path', xId); + ensureSingle(mainplotinfo.overlinesBelow, 'path', yId); + ensureSingle(mainplotinfo.overaxesBelow, 'g', xId); + ensureSingle(mainplotinfo.overaxesBelow, 'g', yId); + + plotinfo.plot = ensureSingle(mainplotinfo.overplot, 'g', id); + + ensureSingle(mainplotinfo.overlinesAbove, 'path', xId); + ensureSingle(mainplotinfo.overlinesAbove, 'path', yId); + ensureSingle(mainplotinfo.overaxesAbove, 'g', xId); + ensureSingle(mainplotinfo.overaxesAbove, 'g', yId); + + // set refs to correct layers as determined by 'abovetraces' + plotinfo.xlines = mainplotgroup.select('.overlines-' + xLayer).select('.' + xId); + plotinfo.ylines = mainplotgroup.select('.overlines-' + yLayer).select('.' + yId); + plotinfo.xaxislayer = mainplotgroup.select('.overaxes-' + xLayer).select('.' + xId); + plotinfo.yaxislayer = mainplotgroup.select('.overaxes-' + yLayer).select('.' + yId); + } + + // common attributes for all subplots, overlays or not + + if(!hasOnlyLargeSploms) { + ensureSingleAndAddDatum(plotinfo.gridlayer, 'g', plotinfo.xaxis._id); + ensureSingleAndAddDatum(plotinfo.gridlayer, 'g', plotinfo.yaxis._id); + plotinfo.gridlayer.selectAll('g') + .map(function(d) { return d[0]; }) + .sort(axisIds.idSort); + } + + plotinfo.xlines + .style('fill', 'none') + .classed('crisp', true); + + plotinfo.ylines + .style('fill', 'none') + .classed('crisp', true); +} + +function purgeSubplotLayers(layers, fullLayout) { + if(!layers) return; + + var overlayIdsToRemove = {}; + + layers.each(function(d) { + var id = d[0]; + var plotgroup = d3.select(this); + + plotgroup.remove(); + removeSubplotExtras(id, fullLayout); + overlayIdsToRemove[id] = true; + + // do not remove individual axis s here + // as other subplots may need them + }); + + // must remove overlaid subplot trace layers 'manually' + + for(var k in fullLayout._plots) { + var subplotInfo = fullLayout._plots[k]; + var overlays = subplotInfo.overlays || []; + + for(var j = 0; j < overlays.length; j++) { + var overlayInfo = overlays[j]; + + if(overlayIdsToRemove[overlayInfo.id]) { + overlayInfo.plot.selectAll('.trace').remove(); + } + } + } +} + +function removeSubplotExtras(subplotId, fullLayout) { + fullLayout._draggers.selectAll('g.' + subplotId).remove(); + fullLayout._defs.select('#clip' + fullLayout._uid + subplotId + 'plot').remove(); +} + +exports.toSVG = function(gd) { + var imageRoot = gd._fullLayout._glimages; + var root = d3.select(gd).selectAll('.svg-container'); + var canvases = root.filter(function(d, i) {return i === root.size() - 1;}) + .selectAll('.gl-canvas-context, .gl-canvas-focus'); + + function canvasToImage() { + var canvas = this; + var imageData = canvas.toDataURL('image/png'); + var image = imageRoot.append('svg:image'); + + image.attr({ + xmlns: xmlnsNamespaces.svg, + 'xlink:href': imageData, + preserveAspectRatio: 'none', + x: 0, + y: 0, + width: canvas.width, + height: canvas.height + }); + } + + canvases.each(canvasToImage); +}; + +exports.updateFx = _dereq_('./graph_interact').updateFx; + +},{"../../components/drawing":72,"../../constants/xmlns_namespaces":150,"../../lib":169,"../../registry":258,"../get_data":241,"../plots":245,"./attributes":211,"./axis_ids":216,"./constants":219,"./graph_interact":222,"./layout_attributes":225,"./layout_defaults":226,"./transition_axes":235,"d3":16}],225:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var fontAttrs = _dereq_('../font_attributes'); +var colorAttrs = _dereq_('../../components/color/attributes'); +var dash = _dereq_('../../components/drawing/attributes').dash; +var extendFlat = _dereq_('../../lib/extend').extendFlat; +var templatedArray = _dereq_('../../plot_api/plot_template').templatedArray; + +var FORMAT_LINK = _dereq_('../../constants/docs').FORMAT_LINK; +var DATE_FORMAT_LINK = _dereq_('../../constants/docs').DATE_FORMAT_LINK; + +var constants = _dereq_('./constants'); + +module.exports = { + visible: { + valType: 'boolean', + + editType: 'plot', + + }, + color: { + valType: 'color', + dflt: colorAttrs.defaultLine, + + editType: 'ticks', + + }, + title: { + text: { + valType: 'string', + + editType: 'ticks', + + }, + font: fontAttrs({ + editType: 'ticks', + + }), + standoff: { + valType: 'number', + + min: 0, + editType: 'ticks', + + }, + editType: 'ticks' + }, + type: { + valType: 'enumerated', + // '-' means we haven't yet run autotype or couldn't find any data + // it gets turned into linear in gd._fullLayout but not copied back + // to gd.data like the others are. + values: ['-', 'linear', 'log', 'date', 'category', 'multicategory'], + dflt: '-', + + editType: 'calc', + // we forget when an axis has been autotyped, just writing the auto + // value back to the input - so it doesn't make sense to template this. + // Note: we do NOT prohibit this in `coerce`, so if someone enters a + // type in the template explicitly it will be honored as the default. + _noTemplating: true, + + }, + autorange: { + valType: 'enumerated', + values: [true, false, 'reversed'], + dflt: true, + + editType: 'axrange', + impliedEdits: {'range[0]': undefined, 'range[1]': undefined}, + + }, + rangemode: { + valType: 'enumerated', + values: ['normal', 'tozero', 'nonnegative'], + dflt: 'normal', + + editType: 'plot', + + }, + range: { + valType: 'info_array', + + items: [ + {valType: 'any', editType: 'axrange', impliedEdits: {'^autorange': false}, anim: true}, + {valType: 'any', editType: 'axrange', impliedEdits: {'^autorange': false}, anim: true} + ], + editType: 'axrange', + impliedEdits: {'autorange': false}, + anim: true, + + }, + fixedrange: { + valType: 'boolean', + dflt: false, + + editType: 'calc', + + }, + // scaleanchor: not used directly, just put here for reference + // values are any opposite-letter axis id + scaleanchor: { + valType: 'enumerated', + values: [ + constants.idRegex.x.toString(), + constants.idRegex.y.toString() + ], + + editType: 'plot', + + }, + scaleratio: { + valType: 'number', + min: 0, + dflt: 1, + + editType: 'plot', + + }, + constrain: { + valType: 'enumerated', + values: ['range', 'domain'], + dflt: 'range', + + editType: 'plot', + + }, + // constraintoward: not used directly, just put here for reference + constraintoward: { + valType: 'enumerated', + values: ['left', 'center', 'right', 'top', 'middle', 'bottom'], + + editType: 'plot', + + }, + matches: { + valType: 'enumerated', + values: [ + constants.idRegex.x.toString(), + constants.idRegex.y.toString() + ], + + editType: 'calc', + + }, + // ticks + tickmode: { + valType: 'enumerated', + values: ['auto', 'linear', 'array'], + + editType: 'ticks', + impliedEdits: {tick0: undefined, dtick: undefined}, + + }, + nticks: { + valType: 'integer', + min: 0, + dflt: 0, + + editType: 'ticks', + + }, + tick0: { + valType: 'any', + + editType: 'ticks', + impliedEdits: {tickmode: 'linear'}, + + }, + dtick: { + valType: 'any', + + editType: 'ticks', + impliedEdits: {tickmode: 'linear'}, + + }, + tickvals: { + valType: 'data_array', + editType: 'ticks', + + }, + ticktext: { + valType: 'data_array', + editType: 'ticks', + + }, + ticks: { + valType: 'enumerated', + values: ['outside', 'inside', ''], + + editType: 'ticks', + + }, + tickson: { + valType: 'enumerated', + values: ['labels', 'boundaries'], + + dflt: 'labels', + editType: 'ticks', + + }, + mirror: { + valType: 'enumerated', + values: [true, 'ticks', false, 'all', 'allticks'], + dflt: false, + + editType: 'ticks+layoutstyle', + + }, + ticklen: { + valType: 'number', + min: 0, + dflt: 5, + + editType: 'ticks', + + }, + tickwidth: { + valType: 'number', + min: 0, + dflt: 1, + + editType: 'ticks', + + }, + tickcolor: { + valType: 'color', + dflt: colorAttrs.defaultLine, + + editType: 'ticks', + + }, + showticklabels: { + valType: 'boolean', + dflt: true, + + editType: 'ticks', + + }, + automargin: { + valType: 'boolean', + dflt: false, + + editType: 'ticks', + + }, + showspikes: { + valType: 'boolean', + dflt: false, + + editType: 'modebar', + + }, + spikecolor: { + valType: 'color', + dflt: null, + + editType: 'none', + + }, + spikethickness: { + valType: 'number', + dflt: 3, + + editType: 'none', + + }, + spikedash: extendFlat({}, dash, {dflt: 'dash', editType: 'none'}), + spikemode: { + valType: 'flaglist', + flags: ['toaxis', 'across', 'marker'], + + dflt: 'toaxis', + editType: 'none', + + }, + spikesnap: { + valType: 'enumerated', + values: ['data', 'cursor'], + dflt: 'data', + + editType: 'none', + + }, + tickfont: fontAttrs({ + editType: 'ticks', + + }), + tickangle: { + valType: 'angle', + dflt: 'auto', + + editType: 'ticks', + + }, + tickprefix: { + valType: 'string', + dflt: '', + + editType: 'ticks', + + }, + showtickprefix: { + valType: 'enumerated', + values: ['all', 'first', 'last', 'none'], + dflt: 'all', + + editType: 'ticks', + + }, + ticksuffix: { + valType: 'string', + dflt: '', + + editType: 'ticks', + + }, + showticksuffix: { + valType: 'enumerated', + values: ['all', 'first', 'last', 'none'], + dflt: 'all', + + editType: 'ticks', + + }, + showexponent: { + valType: 'enumerated', + values: ['all', 'first', 'last', 'none'], + dflt: 'all', + + editType: 'ticks', + + }, + exponentformat: { + valType: 'enumerated', + values: ['none', 'e', 'E', 'power', 'SI', 'B'], + dflt: 'B', + + editType: 'ticks', + + }, + separatethousands: { + valType: 'boolean', + dflt: false, + + editType: 'ticks', + + }, + tickformat: { + valType: 'string', + dflt: '', + + editType: 'ticks', + + }, + tickformatstops: templatedArray('tickformatstop', { + enabled: { + valType: 'boolean', + + dflt: true, + editType: 'ticks', + + }, + dtickrange: { + valType: 'info_array', + + items: [ + {valType: 'any', editType: 'ticks'}, + {valType: 'any', editType: 'ticks'} + ], + editType: 'ticks', + + }, + value: { + valType: 'string', + dflt: '', + + editType: 'ticks', + + }, + editType: 'ticks' + }), + hoverformat: { + valType: 'string', + dflt: '', + + editType: 'none', + + }, + // lines and grids + showline: { + valType: 'boolean', + dflt: false, + + editType: 'ticks+layoutstyle', + + }, + linecolor: { + valType: 'color', + dflt: colorAttrs.defaultLine, + + editType: 'layoutstyle', + + }, + linewidth: { + valType: 'number', + min: 0, + dflt: 1, + + editType: 'ticks+layoutstyle', + + }, + showgrid: { + valType: 'boolean', + + editType: 'ticks', + + }, + gridcolor: { + valType: 'color', + dflt: colorAttrs.lightLine, + + editType: 'ticks', + + }, + gridwidth: { + valType: 'number', + min: 0, + dflt: 1, + + editType: 'ticks', + + }, + zeroline: { + valType: 'boolean', + + editType: 'ticks', + + }, + zerolinecolor: { + valType: 'color', + dflt: colorAttrs.defaultLine, + + editType: 'ticks', + + }, + zerolinewidth: { + valType: 'number', + dflt: 1, + + editType: 'ticks', + + }, + + showdividers: { + valType: 'boolean', + dflt: true, + + editType: 'ticks', + + }, + dividercolor: { + valType: 'color', + dflt: colorAttrs.defaultLine, + + editType: 'ticks', + + }, + dividerwidth: { + valType: 'number', + dflt: 1, + + editType: 'ticks', + + }, + // TODO dividerlen: that would override "to label base" length? + + // positioning attributes + // anchor: not used directly, just put here for reference + // values are any opposite-letter axis id + anchor: { + valType: 'enumerated', + values: [ + 'free', + constants.idRegex.x.toString(), + constants.idRegex.y.toString() + ], + + editType: 'plot', + + }, + // side: not used directly, as values depend on direction + // values are top, bottom for x axes, and left, right for y + side: { + valType: 'enumerated', + values: ['top', 'bottom', 'left', 'right'], + + editType: 'plot', + + }, + // overlaying: not used directly, just put here for reference + // values are false and any other same-letter axis id that's not + // itself overlaying anything + overlaying: { + valType: 'enumerated', + values: [ + 'free', + constants.idRegex.x.toString(), + constants.idRegex.y.toString() + ], + + editType: 'plot', + + }, + layer: { + valType: 'enumerated', + values: ['above traces', 'below traces'], + dflt: 'above traces', + + editType: 'plot', + + }, + domain: { + valType: 'info_array', + + items: [ + {valType: 'number', min: 0, max: 1, editType: 'plot'}, + {valType: 'number', min: 0, max: 1, editType: 'plot'} + ], + dflt: [0, 1], + editType: 'plot', + + }, + position: { + valType: 'number', + min: 0, + max: 1, + dflt: 0, + + editType: 'plot', + + }, + categoryorder: { + valType: 'enumerated', + values: [ + 'trace', 'category ascending', 'category descending', 'array', + 'total ascending', 'total descending', + 'min ascending', 'min descending', + 'max ascending', 'max descending', + 'sum ascending', 'sum descending', + 'mean ascending', 'mean descending', + 'median ascending', 'median descending' + ], + dflt: 'trace', + + editType: 'calc', + + }, + categoryarray: { + valType: 'data_array', + + editType: 'calc', + + }, + uirevision: { + valType: 'any', + + editType: 'none', + + }, + editType: 'calc', + + _deprecated: { + autotick: { + valType: 'boolean', + + editType: 'ticks', + + }, + title: { + valType: 'string', + + editType: 'ticks', + + }, + titlefont: fontAttrs({ + editType: 'ticks', + + }) + } +}; + +},{"../../components/color/attributes":50,"../../components/drawing/attributes":71,"../../constants/docs":146,"../../lib/extend":164,"../../plot_api/plot_template":203,"../font_attributes":239,"./constants":219}],226:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Lib = _dereq_('../../lib'); +var Color = _dereq_('../../components/color'); +var Template = _dereq_('../../plot_api/plot_template'); +var basePlotLayoutAttributes = _dereq_('../layout_attributes'); + +var layoutAttributes = _dereq_('./layout_attributes'); +var handleTypeDefaults = _dereq_('./type_defaults'); +var handleAxisDefaults = _dereq_('./axis_defaults'); +var handleConstraintDefaults = _dereq_('./constraints').handleConstraintDefaults; +var handlePositionDefaults = _dereq_('./position_defaults'); + +var axisIds = _dereq_('./axis_ids'); +var id2name = axisIds.id2name; +var name2id = axisIds.name2id; + +var Registry = _dereq_('../../registry'); +var traceIs = Registry.traceIs; +var getComponentMethod = Registry.getComponentMethod; + +function appendList(cont, k, item) { + if(Array.isArray(cont[k])) cont[k].push(item); + else cont[k] = [item]; +} + +module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { + var ax2traces = {}; + var xaMayHide = {}; + var yaMayHide = {}; + var xaMustDisplay = {}; + var yaMustDisplay = {}; + var yaMustNotReverse = {}; + var yaMayReverse = {}; + var axHasImage = {}; + var outerTicks = {}; + var noGrids = {}; + var i, j; + + // look for axes in the data + for(i = 0; i < fullData.length; i++) { + var trace = fullData[i]; + if(!traceIs(trace, 'cartesian') && !traceIs(trace, 'gl2d')) continue; + + var xaName; + if(trace.xaxis) { + xaName = id2name(trace.xaxis); + appendList(ax2traces, xaName, trace); + } else if(trace.xaxes) { + for(j = 0; j < trace.xaxes.length; j++) { + appendList(ax2traces, id2name(trace.xaxes[j]), trace); + } + } + + var yaName; + if(trace.yaxis) { + yaName = id2name(trace.yaxis); + appendList(ax2traces, yaName, trace); + } else if(trace.yaxes) { + for(j = 0; j < trace.yaxes.length; j++) { + appendList(ax2traces, id2name(trace.yaxes[j]), trace); + } + } + + // logic for funnels + if(trace.type === 'funnel') { + if(trace.orientation === 'h') { + if(xaName) xaMayHide[xaName] = true; + if(yaName) yaMayReverse[yaName] = true; + } else { + if(yaName) yaMayHide[yaName] = true; + } + } else if(trace.type === 'image') { + if(yaName) axHasImage[yaName] = true; + if(xaName) axHasImage[xaName] = true; + } else { + if(yaName) { + yaMustDisplay[yaName] = true; + yaMustNotReverse[yaName] = true; + } + + if(!traceIs(trace, 'carpet') || (trace.type === 'carpet' && !trace._cheater)) { + if(xaName) xaMustDisplay[xaName] = true; + } + } + + // Two things trigger axis visibility: + // 1. is not carpet + // 2. carpet that's not cheater + + // The above check for definitely-not-cheater is not adequate. This + // second list tracks which axes *could* be a cheater so that the + // full condition triggering hiding is: + // *could* be a cheater and *is not definitely visible* + if(trace.type === 'carpet' && trace._cheater) { + if(xaName) xaMayHide[xaName] = true; + } + + // check for default formatting tweaks + if(traceIs(trace, '2dMap')) { + outerTicks[xaName] = true; + outerTicks[yaName] = true; + } + + if(traceIs(trace, 'oriented')) { + var positionAxis = trace.orientation === 'h' ? yaName : xaName; + noGrids[positionAxis] = true; + } + } + + var subplots = layoutOut._subplots; + var xIds = subplots.xaxis; + var yIds = subplots.yaxis; + var xNames = Lib.simpleMap(xIds, id2name); + var yNames = Lib.simpleMap(yIds, id2name); + var axNames = xNames.concat(yNames); + + // plot_bgcolor only makes sense if there's a (2D) plot! + // TODO: bgcolor for each subplot, to inherit from the main one + var plotBgColor = Color.background; + if(xIds.length && yIds.length) { + plotBgColor = Lib.coerce(layoutIn, layoutOut, basePlotLayoutAttributes, 'plot_bgcolor'); + } + + var bgColor = Color.combine(plotBgColor, layoutOut.paper_bgcolor); + + var axName, axLetter, axLayoutIn, axLayoutOut; + + function coerce(attr, dflt) { + return Lib.coerce(axLayoutIn, axLayoutOut, layoutAttributes, attr, dflt); + } + + function coerce2(attr, dflt) { + return Lib.coerce2(axLayoutIn, axLayoutOut, layoutAttributes, attr, dflt); + } + + function getCounterAxes(axLetter) { + return (axLetter === 'x') ? yIds : xIds; + } + + var counterAxes = {x: getCounterAxes('x'), y: getCounterAxes('y')}; + var allAxisIds = counterAxes.x.concat(counterAxes.y); + + function getOverlayableAxes(axLetter, axName) { + var list = (axLetter === 'x') ? xNames : yNames; + var out = []; + + for(var j = 0; j < list.length; j++) { + var axName2 = list[j]; + + if(axName2 !== axName && !(layoutIn[axName2] || {}).overlaying) { + out.push(name2id(axName2)); + } + } + + return out; + } + + // first pass creates the containers, determines types, and handles most of the settings + for(i = 0; i < axNames.length; i++) { + axName = axNames[i]; + axLetter = axName.charAt(0); + + if(!Lib.isPlainObject(layoutIn[axName])) { + layoutIn[axName] = {}; + } + + axLayoutIn = layoutIn[axName]; + axLayoutOut = Template.newContainer(layoutOut, axName, axLetter + 'axis'); + + var traces = ax2traces[axName] || []; + axLayoutOut._traceIndices = traces.map(function(t) { return t._expandedIndex; }); + axLayoutOut._annIndices = []; + axLayoutOut._shapeIndices = []; + axLayoutOut._imgIndices = []; + axLayoutOut._subplotsWith = []; + axLayoutOut._counterAxes = []; + + // set up some private properties + axLayoutOut._name = axLayoutOut._attr = axName; + var id = axLayoutOut._id = name2id(axName); + + var overlayableAxes = getOverlayableAxes(axLetter, axName); + + var visibleDflt = + (axLetter === 'x' && !xaMustDisplay[axName] && xaMayHide[axName]) || + (axLetter === 'y' && !yaMustDisplay[axName] && yaMayHide[axName]); + + var reverseDflt = + (axLetter === 'y' && + ( + (!yaMustNotReverse[axName] && yaMayReverse[axName]) || + axHasImage[axName] + )); + + var defaultOptions = { + letter: axLetter, + font: layoutOut.font, + outerTicks: outerTicks[axName], + showGrid: !noGrids[axName], + data: traces, + bgColor: bgColor, + calendar: layoutOut.calendar, + automargin: true, + visibleDflt: visibleDflt, + reverseDflt: reverseDflt, + splomStash: ((layoutOut._splomAxes || {})[axLetter] || {})[id] + }; + + coerce('uirevision', layoutOut.uirevision); + + handleTypeDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions); + handleAxisDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions, layoutOut); + + var spikecolor = coerce2('spikecolor'); + var spikethickness = coerce2('spikethickness'); + var spikedash = coerce2('spikedash'); + var spikemode = coerce2('spikemode'); + var spikesnap = coerce2('spikesnap'); + var showSpikes = coerce('showspikes', !!spikecolor || !!spikethickness || !!spikedash || !!spikemode || !!spikesnap); + + if(!showSpikes) { + delete axLayoutOut.spikecolor; + delete axLayoutOut.spikethickness; + delete axLayoutOut.spikedash; + delete axLayoutOut.spikemode; + delete axLayoutOut.spikesnap; + } + + handlePositionDefaults(axLayoutIn, axLayoutOut, coerce, { + letter: axLetter, + counterAxes: counterAxes[axLetter], + overlayableAxes: overlayableAxes, + grid: layoutOut.grid + }); + + coerce('title.standoff'); + + axLayoutOut._input = axLayoutIn; + } + + // quick second pass for range slider and selector defaults + var rangeSliderDefaults = getComponentMethod('rangeslider', 'handleDefaults'); + var rangeSelectorDefaults = getComponentMethod('rangeselector', 'handleDefaults'); + + for(i = 0; i < xNames.length; i++) { + axName = xNames[i]; + axLayoutIn = layoutIn[axName]; + axLayoutOut = layoutOut[axName]; + + rangeSliderDefaults(layoutIn, layoutOut, axName); + + if(axLayoutOut.type === 'date') { + rangeSelectorDefaults( + axLayoutIn, + axLayoutOut, + layoutOut, + yNames, + axLayoutOut.calendar + ); + } + + coerce('fixedrange'); + } + + for(i = 0; i < yNames.length; i++) { + axName = yNames[i]; + axLayoutIn = layoutIn[axName]; + axLayoutOut = layoutOut[axName]; + + var anchoredAxis = layoutOut[id2name(axLayoutOut.anchor)]; + + var fixedRangeDflt = getComponentMethod('rangeslider', 'isVisible')(anchoredAxis); + + coerce('fixedrange', fixedRangeDflt); + } + + // Finally, handle scale constraints and matching axes. + // + // We need to do this after all axes have coerced both `type` + // (so we link only axes of the same type) and + // `fixedrange` (so we can avoid linking from OR TO a fixed axis). + + // sets of axes linked by `scaleanchor` along with the scaleratios compounded + // together, populated in handleConstraintDefaults + var constraintGroups = layoutOut._axisConstraintGroups = []; + // similar to _axisConstraintGroups, but for matching axes + var matchGroups = layoutOut._axisMatchGroups = []; + + for(i = 0; i < axNames.length; i++) { + axName = axNames[i]; + axLetter = axName.charAt(0); + axLayoutIn = layoutIn[axName]; + axLayoutOut = layoutOut[axName]; + + var scaleanchorDflt; + if(axLetter === 'y' && !axLayoutIn.hasOwnProperty('scaleanchor') && axHasImage[axName]) { + scaleanchorDflt = axLayoutOut.anchor; + } else {scaleanchorDflt = undefined;} + + var constrainDflt; + if(!axLayoutIn.hasOwnProperty('constrain') && axHasImage[axName]) { + constrainDflt = 'domain'; + } else {constrainDflt = undefined;} + + handleConstraintDefaults(axLayoutIn, axLayoutOut, coerce, { + allAxisIds: allAxisIds, + layoutOut: layoutOut, + scaleanchorDflt: scaleanchorDflt, + constrainDflt: constrainDflt + }); + } + + for(i = 0; i < matchGroups.length; i++) { + var group = matchGroups[i]; + var rng = null; + var autorange = null; + var axId; + + // find 'matching' range attrs + for(axId in group) { + axLayoutOut = layoutOut[id2name(axId)]; + if(!axLayoutOut.matches) { + rng = axLayoutOut.range; + autorange = axLayoutOut.autorange; + } + } + // if `ax.matches` values are reciprocal, + // pick values of first axis in group + if(rng === null || autorange === null) { + for(axId in group) { + axLayoutOut = layoutOut[id2name(axId)]; + rng = axLayoutOut.range; + autorange = axLayoutOut.autorange; + break; + } + } + // apply matching range attrs + for(axId in group) { + axLayoutOut = layoutOut[id2name(axId)]; + if(axLayoutOut.matches) { + axLayoutOut.range = rng.slice(); + axLayoutOut.autorange = autorange; + } + axLayoutOut._matchGroup = group; + } + + // remove matching axis from scaleanchor constraint groups (for now) + if(constraintGroups.length) { + for(axId in group) { + for(j = 0; j < constraintGroups.length; j++) { + var group2 = constraintGroups[j]; + for(var axId2 in group2) { + if(axId === axId2) { + Lib.warn('Axis ' + axId2 + ' is set with both ' + + 'a *scaleanchor* and *matches* constraint; ' + + 'ignoring the scale constraint.'); + + delete group2[axId2]; + if(Object.keys(group2).length < 2) { + constraintGroups.splice(j, 1); + } + } + } + } + } + } + } +}; + +},{"../../components/color":51,"../../lib":169,"../../plot_api/plot_template":203,"../../registry":258,"../layout_attributes":243,"./axis_defaults":215,"./axis_ids":216,"./constraints":220,"./layout_attributes":225,"./position_defaults":228,"./type_defaults":236}],227:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var colorMix = _dereq_('tinycolor2').mix; +var lightFraction = _dereq_('../../components/color/attributes').lightFraction; +var Lib = _dereq_('../../lib'); + +/** + * @param {object} opts : + * - dfltColor {string} : default axis color + * - bgColor {string} : combined subplot bg color + * - blend {number, optional} : blend percentage (to compute dflt grid color) + * - showLine {boolean} : show line by default + * - showGrid {boolean} : show grid by default + * - noZeroLine {boolean} : don't coerce zeroline* attributes + * - attributes {object} : attribute object associated with input containers + */ +module.exports = function handleLineGridDefaults(containerIn, containerOut, coerce, opts) { + opts = opts || {}; + + var dfltColor = opts.dfltColor; + + function coerce2(attr, dflt) { + return Lib.coerce2(containerIn, containerOut, opts.attributes, attr, dflt); + } + + var lineColor = coerce2('linecolor', dfltColor); + var lineWidth = coerce2('linewidth'); + var showLine = coerce('showline', opts.showLine || !!lineColor || !!lineWidth); + + if(!showLine) { + delete containerOut.linecolor; + delete containerOut.linewidth; + } + + var gridColorDflt = colorMix(dfltColor, opts.bgColor, opts.blend || lightFraction).toRgbString(); + var gridColor = coerce2('gridcolor', gridColorDflt); + var gridWidth = coerce2('gridwidth'); + var showGridLines = coerce('showgrid', opts.showGrid || !!gridColor || !!gridWidth); + + if(!showGridLines) { + delete containerOut.gridcolor; + delete containerOut.gridwidth; + } + + if(!opts.noZeroLine) { + var zeroLineColor = coerce2('zerolinecolor', dfltColor); + var zeroLineWidth = coerce2('zerolinewidth'); + var showZeroLine = coerce('zeroline', opts.showGrid || !!zeroLineColor || !!zeroLineWidth); + + if(!showZeroLine) { + delete containerOut.zerolinecolor; + delete containerOut.zerolinewidth; + } + } +}; + +},{"../../components/color/attributes":50,"../../lib":169,"tinycolor2":34}],228:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); + +var Lib = _dereq_('../../lib'); + + +module.exports = function handlePositionDefaults(containerIn, containerOut, coerce, options) { + var counterAxes = options.counterAxes || []; + var overlayableAxes = options.overlayableAxes || []; + var letter = options.letter; + var grid = options.grid; + + var dfltAnchor, dfltDomain, dfltSide, dfltPosition; + + if(grid) { + dfltDomain = grid._domains[letter][grid._axisMap[containerOut._id]]; + dfltAnchor = grid._anchors[containerOut._id]; + if(dfltDomain) { + dfltSide = grid[letter + 'side'].split(' ')[0]; + dfltPosition = grid.domain[letter][dfltSide === 'right' || dfltSide === 'top' ? 1 : 0]; + } + } + + // Even if there's a grid, this axis may not be in it - fall back on non-grid defaults + dfltDomain = dfltDomain || [0, 1]; + dfltAnchor = dfltAnchor || (isNumeric(containerIn.position) ? 'free' : (counterAxes[0] || 'free')); + dfltSide = dfltSide || (letter === 'x' ? 'bottom' : 'left'); + dfltPosition = dfltPosition || 0; + + var anchor = Lib.coerce(containerIn, containerOut, { + anchor: { + valType: 'enumerated', + values: ['free'].concat(counterAxes), + dflt: dfltAnchor + } + }, 'anchor'); + + if(anchor === 'free') coerce('position', dfltPosition); + + Lib.coerce(containerIn, containerOut, { + side: { + valType: 'enumerated', + values: letter === 'x' ? ['bottom', 'top'] : ['left', 'right'], + dflt: dfltSide + } + }, 'side'); + + var overlaying = false; + if(overlayableAxes.length) { + overlaying = Lib.coerce(containerIn, containerOut, { + overlaying: { + valType: 'enumerated', + values: [false].concat(overlayableAxes), + dflt: false + } + }, 'overlaying'); + } + + if(!overlaying) { + // TODO: right now I'm copying this domain over to overlaying axes + // in ax.setscale()... but this means we still need (imperfect) logic + // in the axes popover to hide domain for the overlaying axis. + // perhaps I should make a private version _domain that all axes get??? + var domain = coerce('domain', dfltDomain); + + // according to https://www.npmjs.com/package/canvas-size + // the minimum value of max canvas width across browsers and devices is 4096 + // which applied in the calculation below: + if(domain[0] > domain[1] - 1 / 4096) containerOut.domain = dfltDomain; + Lib.noneOrAll(containerIn.domain, containerOut.domain, dfltDomain); + } + + coerce('layer'); + + return containerOut; +}; + +},{"../../lib":169,"fast-isnumeric":18}],229:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var FROM_BL = _dereq_('../../constants/alignment').FROM_BL; + +module.exports = function scaleZoom(ax, factor, centerFraction) { + if(centerFraction === undefined) { + centerFraction = FROM_BL[ax.constraintoward || 'center']; + } + + var rangeLinear = [ax.r2l(ax.range[0]), ax.r2l(ax.range[1])]; + var center = rangeLinear[0] + (rangeLinear[1] - rangeLinear[0]) * centerFraction; + + ax.range = ax._input.range = [ + ax.l2r(center + (rangeLinear[0] - center) * factor), + ax.l2r(center + (rangeLinear[1] - center) * factor) + ]; +}; + +},{"../../constants/alignment":145}],230:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var polybool = _dereq_('polybooljs'); + +var Registry = _dereq_('../../registry'); +var Color = _dereq_('../../components/color'); +var Fx = _dereq_('../../components/fx'); + +var Lib = _dereq_('../../lib'); +var polygon = _dereq_('../../lib/polygon'); +var throttle = _dereq_('../../lib/throttle'); +var makeEventData = _dereq_('../../components/fx/helpers').makeEventData; +var getFromId = _dereq_('./axis_ids').getFromId; +var clearGlCanvases = _dereq_('../../lib/clear_gl_canvases'); + +var redrawReglTraces = _dereq_('../../plot_api/subroutines').redrawReglTraces; + +var constants = _dereq_('./constants'); +var MINSELECT = constants.MINSELECT; + +var filteredPolygon = polygon.filter; +var polygonTester = polygon.tester; + +function getAxId(ax) { return ax._id; } + +function prepSelect(e, startX, startY, dragOptions, mode) { + var gd = dragOptions.gd; + var fullLayout = gd._fullLayout; + var zoomLayer = fullLayout._zoomlayer; + var dragBBox = dragOptions.element.getBoundingClientRect(); + var plotinfo = dragOptions.plotinfo; + var xs = plotinfo.xaxis._offset; + var ys = plotinfo.yaxis._offset; + var x0 = startX - dragBBox.left; + var y0 = startY - dragBBox.top; + var x1 = x0; + var y1 = y0; + var path0 = 'M' + x0 + ',' + y0; + var pw = dragOptions.xaxes[0]._length; + var ph = dragOptions.yaxes[0]._length; + var allAxes = dragOptions.xaxes.concat(dragOptions.yaxes); + var subtract = e.altKey; + + var filterPoly, selectionTester, mergedPolygons, currentPolygon; + var i, searchInfo, eventData; + + coerceSelectionsCache(e, gd, dragOptions); + + if(mode === 'lasso') { + filterPoly = filteredPolygon([[x0, y0]], constants.BENDPX); + } + + var outlines = zoomLayer.selectAll('path.select-outline-' + plotinfo.id).data([1, 2]); + + outlines.enter() + .append('path') + .attr('class', function(d) { return 'select-outline select-outline-' + d + ' select-outline-' + plotinfo.id; }) + .attr('transform', 'translate(' + xs + ', ' + ys + ')') + .attr('d', path0 + 'Z'); + + var corners = zoomLayer.append('path') + .attr('class', 'zoombox-corners') + .style({ + fill: Color.background, + stroke: Color.defaultLine, + 'stroke-width': 1 + }) + .attr('transform', 'translate(' + xs + ', ' + ys + ')') + .attr('d', 'M0,0Z'); + + + var throttleID = fullLayout._uid + constants.SELECTID; + var selection = []; + + // find the traces to search for selection points + var searchTraces = determineSearchTraces(gd, dragOptions.xaxes, + dragOptions.yaxes, dragOptions.subplot); + + // in v2 (once log ranges are fixed), + // we'll be able to p2r here for all axis types + function p2r(ax, v) { + return ax.type === 'log' ? ax.p2d(v) : ax.p2r(v); + } + + function axValue(ax) { + var index = (ax._id.charAt(0) === 'y') ? 1 : 0; + return function(v) { return p2r(ax, v[index]); }; + } + + function ascending(a, b) { return a - b; } + + // allow subplots to override fillRangeItems routine + var fillRangeItems; + + if(plotinfo.fillRangeItems) { + fillRangeItems = plotinfo.fillRangeItems; + } else { + if(mode === 'select') { + fillRangeItems = function(eventData, poly) { + var ranges = eventData.range = {}; + + for(i = 0; i < allAxes.length; i++) { + var ax = allAxes[i]; + var axLetter = ax._id.charAt(0); + + ranges[ax._id] = [ + p2r(ax, poly[axLetter + 'min']), + p2r(ax, poly[axLetter + 'max']) + ].sort(ascending); + } + }; + } else { + fillRangeItems = function(eventData, poly, filterPoly) { + var dataPts = eventData.lassoPoints = {}; + + for(i = 0; i < allAxes.length; i++) { + var ax = allAxes[i]; + dataPts[ax._id] = filterPoly.filtered.map(axValue(ax)); + } + }; + } + } + + dragOptions.moveFn = function(dx0, dy0) { + x1 = Math.max(0, Math.min(pw, dx0 + x0)); + y1 = Math.max(0, Math.min(ph, dy0 + y0)); + + var dx = Math.abs(x1 - x0); + var dy = Math.abs(y1 - y0); + + if(mode === 'select') { + var direction = fullLayout.selectdirection; + + if(fullLayout.selectdirection === 'any') { + if(dy < Math.min(dx * 0.6, MINSELECT)) direction = 'h'; + else if(dx < Math.min(dy * 0.6, MINSELECT)) direction = 'v'; + else direction = 'd'; + } else { + direction = fullLayout.selectdirection; + } + + if(direction === 'h') { + // horizontal motion: make a vertical box + currentPolygon = [[x0, 0], [x0, ph], [x1, ph], [x1, 0]]; + currentPolygon.xmin = Math.min(x0, x1); + currentPolygon.xmax = Math.max(x0, x1); + currentPolygon.ymin = Math.min(0, ph); + currentPolygon.ymax = Math.max(0, ph); + // extras to guide users in keeping a straight selection + corners.attr('d', 'M' + currentPolygon.xmin + ',' + (y0 - MINSELECT) + + 'h-4v' + (2 * MINSELECT) + 'h4Z' + + 'M' + (currentPolygon.xmax - 1) + ',' + (y0 - MINSELECT) + + 'h4v' + (2 * MINSELECT) + 'h-4Z'); + } else if(direction === 'v') { + // vertical motion: make a horizontal box + currentPolygon = [[0, y0], [0, y1], [pw, y1], [pw, y0]]; + currentPolygon.xmin = Math.min(0, pw); + currentPolygon.xmax = Math.max(0, pw); + currentPolygon.ymin = Math.min(y0, y1); + currentPolygon.ymax = Math.max(y0, y1); + corners.attr('d', 'M' + (x0 - MINSELECT) + ',' + currentPolygon.ymin + + 'v-4h' + (2 * MINSELECT) + 'v4Z' + + 'M' + (x0 - MINSELECT) + ',' + (currentPolygon.ymax - 1) + + 'v4h' + (2 * MINSELECT) + 'v-4Z'); + } else if(direction === 'd') { + // diagonal motion + currentPolygon = [[x0, y0], [x0, y1], [x1, y1], [x1, y0]]; + currentPolygon.xmin = Math.min(x0, x1); + currentPolygon.xmax = Math.max(x0, x1); + currentPolygon.ymin = Math.min(y0, y1); + currentPolygon.ymax = Math.max(y0, y1); + corners.attr('d', 'M0,0Z'); + } + } else if(mode === 'lasso') { + filterPoly.addPt([x1, y1]); + currentPolygon = filterPoly.filtered; + } + + // create outline & tester + if(dragOptions.selectionDefs && dragOptions.selectionDefs.length) { + mergedPolygons = mergePolygons(dragOptions.mergedPolygons, currentPolygon, subtract); + currentPolygon.subtract = subtract; + selectionTester = multiTester(dragOptions.selectionDefs.concat([currentPolygon])); + } else { + mergedPolygons = [currentPolygon]; + selectionTester = polygonTester(currentPolygon); + } + + // draw selection + drawSelection(mergedPolygons, outlines); + + + throttle.throttle( + throttleID, + constants.SELECTDELAY, + function() { + selection = []; + + var thisSelection; + var traceSelections = []; + var traceSelection; + for(i = 0; i < searchTraces.length; i++) { + searchInfo = searchTraces[i]; + + traceSelection = searchInfo._module.selectPoints(searchInfo, selectionTester); + traceSelections.push(traceSelection); + + thisSelection = fillSelectionItem(traceSelection, searchInfo); + + if(selection.length) { + for(var j = 0; j < thisSelection.length; j++) { + selection.push(thisSelection[j]); + } + } else selection = thisSelection; + } + + eventData = {points: selection}; + updateSelectedState(gd, searchTraces, eventData); + fillRangeItems(eventData, currentPolygon, filterPoly); + dragOptions.gd.emit('plotly_selecting', eventData); + } + ); + }; + + dragOptions.clickFn = function(numClicks, evt) { + var clickmode = fullLayout.clickmode; + + corners.remove(); + + throttle.done(throttleID).then(function() { + throttle.clear(throttleID); + if(numClicks === 2) { + // clear selection on doubleclick + outlines.remove(); + for(i = 0; i < searchTraces.length; i++) { + searchInfo = searchTraces[i]; + searchInfo._module.selectPoints(searchInfo, false); + } + + updateSelectedState(gd, searchTraces); + + clearSelectionsCache(dragOptions); + + gd.emit('plotly_deselect', null); + } else { + if(clickmode.indexOf('select') > -1) { + selectOnClick(evt, gd, dragOptions.xaxes, dragOptions.yaxes, + dragOptions.subplot, dragOptions, outlines); + } + + if(clickmode === 'event') { + // TODO: remove in v2 - this was probably never intended to work as it does, + // but in case anyone depends on it we don't want to break it now. + // Note that click-to-select introduced pre v2 also emitts proper + // event data when clickmode is having 'select' in its flag list. + gd.emit('plotly_selected', undefined); + } + } + + Fx.click(gd, evt); + }).catch(Lib.error); + }; + + dragOptions.doneFn = function() { + corners.remove(); + + throttle.done(throttleID).then(function() { + throttle.clear(throttleID); + dragOptions.gd.emit('plotly_selected', eventData); + + if(currentPolygon && dragOptions.selectionDefs) { + // save last polygons + currentPolygon.subtract = subtract; + dragOptions.selectionDefs.push(currentPolygon); + + // we have to keep reference to arrays container + dragOptions.mergedPolygons.length = 0; + [].push.apply(dragOptions.mergedPolygons, mergedPolygons); + } + + if(dragOptions.doneFnCompleted) { + dragOptions.doneFnCompleted(selection); + } + }).catch(Lib.error); + }; +} + +function selectOnClick(evt, gd, xAxes, yAxes, subplot, dragOptions, polygonOutlines) { + var hoverData = gd._hoverdata; + var clickmode = gd._fullLayout.clickmode; + var sendEvents = clickmode.indexOf('event') > -1; + var selection = []; + var searchTraces, searchInfo, currentSelectionDef, selectionTester, traceSelection; + var thisTracesSelection, pointOrBinSelected, subtract, eventData, i; + + if(isHoverDataSet(hoverData)) { + coerceSelectionsCache(evt, gd, dragOptions); + searchTraces = determineSearchTraces(gd, xAxes, yAxes, subplot); + var clickedPtInfo = extractClickedPtInfo(hoverData, searchTraces); + var isBinnedTrace = clickedPtInfo.pointNumbers.length > 0; + + + // Note: potentially costly operation isPointOrBinSelected is + // called as late as possible through the use of an assignment + // in an if condition. + if(isBinnedTrace ? + isOnlyThisBinSelected(searchTraces, clickedPtInfo) : + isOnlyOnePointSelected(searchTraces) && + (pointOrBinSelected = isPointOrBinSelected(clickedPtInfo))) { + if(polygonOutlines) polygonOutlines.remove(); + for(i = 0; i < searchTraces.length; i++) { + searchInfo = searchTraces[i]; + searchInfo._module.selectPoints(searchInfo, false); + } + + updateSelectedState(gd, searchTraces); + + clearSelectionsCache(dragOptions); + + if(sendEvents) { + gd.emit('plotly_deselect', null); + } + } else { + subtract = evt.shiftKey && + (pointOrBinSelected !== undefined ? + pointOrBinSelected : + isPointOrBinSelected(clickedPtInfo)); + currentSelectionDef = newPointSelectionDef(clickedPtInfo.pointNumber, clickedPtInfo.searchInfo, subtract); + + var allSelectionDefs = dragOptions.selectionDefs.concat([currentSelectionDef]); + selectionTester = multiTester(allSelectionDefs); + + for(i = 0; i < searchTraces.length; i++) { + traceSelection = searchTraces[i]._module.selectPoints(searchTraces[i], selectionTester); + thisTracesSelection = fillSelectionItem(traceSelection, searchTraces[i]); + + if(selection.length) { + for(var j = 0; j < thisTracesSelection.length; j++) { + selection.push(thisTracesSelection[j]); + } + } else selection = thisTracesSelection; + } + + eventData = {points: selection}; + updateSelectedState(gd, searchTraces, eventData); + + if(currentSelectionDef && dragOptions) { + dragOptions.selectionDefs.push(currentSelectionDef); + } + + if(polygonOutlines) drawSelection(dragOptions.mergedPolygons, polygonOutlines); + + if(sendEvents) { + gd.emit('plotly_selected', eventData); + } + } + } +} + +/** + * Constructs a new point selection definition object. + */ +function newPointSelectionDef(pointNumber, searchInfo, subtract) { + return { + pointNumber: pointNumber, + searchInfo: searchInfo, + subtract: subtract + }; +} + +function isPointSelectionDef(o) { + return 'pointNumber' in o && 'searchInfo' in o; +} + +/* + * Constructs a new point number tester. + */ +function newPointNumTester(pointSelectionDef) { + return { + xmin: 0, + xmax: 0, + ymin: 0, + ymax: 0, + pts: [], + contains: function(pt, omitFirstEdge, pointNumber, searchInfo) { + var idxWantedTrace = pointSelectionDef.searchInfo.cd[0].trace._expandedIndex; + var idxActualTrace = searchInfo.cd[0].trace._expandedIndex; + return idxActualTrace === idxWantedTrace && + pointNumber === pointSelectionDef.pointNumber; + }, + isRect: false, + degenerate: false, + subtract: pointSelectionDef.subtract + }; +} + +/** + * Wraps multiple selection testers. + * + * @param {Array} list - An array of selection testers. + * + * @return a selection tester object with a contains function + * that can be called to evaluate a point against all wrapped + * selection testers that were passed in list. + */ +function multiTester(list) { + var testers = []; + var xmin = isPointSelectionDef(list[0]) ? 0 : list[0][0][0]; + var xmax = xmin; + var ymin = isPointSelectionDef(list[0]) ? 0 : list[0][0][1]; + var ymax = ymin; + + for(var i = 0; i < list.length; i++) { + if(isPointSelectionDef(list[i])) { + testers.push(newPointNumTester(list[i])); + } else { + var tester = polygon.tester(list[i]); + tester.subtract = list[i].subtract; + testers.push(tester); + xmin = Math.min(xmin, tester.xmin); + xmax = Math.max(xmax, tester.xmax); + ymin = Math.min(ymin, tester.ymin); + ymax = Math.max(ymax, tester.ymax); + } + } + + /** + * Tests if the given point is within this tester. + * + * @param {Array} pt - [0] is the x coordinate, [1] is the y coordinate of the point. + * @param {*} arg - An optional parameter to pass down to wrapped testers. + * @param {number} pointNumber - The point number of the point within the underlying data array. + * @param {number} searchInfo - An object identifying the trace the point is contained in. + * + * @return {boolean} true if point is considered to be selected, false otherwise. + */ + function contains(pt, arg, pointNumber, searchInfo) { + var contained = false; + for(var i = 0; i < testers.length; i++) { + if(testers[i].contains(pt, arg, pointNumber, searchInfo)) { + // if contained by subtract tester - exclude the point + contained = testers[i].subtract === false; + } + } + + return contained; + } + + return { + xmin: xmin, + xmax: xmax, + ymin: ymin, + ymax: ymax, + pts: [], + contains: contains, + isRect: false, + degenerate: false + }; +} + +function coerceSelectionsCache(evt, gd, dragOptions) { + var fullLayout = gd._fullLayout; + var plotinfo = dragOptions.plotinfo; + + var selectingOnSameSubplot = ( + fullLayout._lastSelectedSubplot && + fullLayout._lastSelectedSubplot === plotinfo.id + ); + var hasModifierKey = evt.shiftKey || evt.altKey; + + if(selectingOnSameSubplot && hasModifierKey && + (plotinfo.selection && plotinfo.selection.selectionDefs) && !dragOptions.selectionDefs) { + // take over selection definitions from prev mode, if any + dragOptions.selectionDefs = plotinfo.selection.selectionDefs; + dragOptions.mergedPolygons = plotinfo.selection.mergedPolygons; + } else if(!hasModifierKey || !plotinfo.selection) { + clearSelectionsCache(dragOptions); + } + + // clear selection outline when selecting a different subplot + if(!selectingOnSameSubplot) { + clearSelect(gd); + fullLayout._lastSelectedSubplot = plotinfo.id; + } +} + +function clearSelectionsCache(dragOptions) { + var plotinfo = dragOptions.plotinfo; + + plotinfo.selection = {}; + plotinfo.selection.selectionDefs = dragOptions.selectionDefs = []; + plotinfo.selection.mergedPolygons = dragOptions.mergedPolygons = []; +} + +function determineSearchTraces(gd, xAxes, yAxes, subplot) { + var searchTraces = []; + var xAxisIds = xAxes.map(getAxId); + var yAxisIds = yAxes.map(getAxId); + var cd, trace, i; + + for(i = 0; i < gd.calcdata.length; i++) { + cd = gd.calcdata[i]; + trace = cd[0].trace; + + if(trace.visible !== true || !trace._module || !trace._module.selectPoints) continue; + + if(subplot && (trace.subplot === subplot || trace.geo === subplot)) { + searchTraces.push(createSearchInfo(trace._module, cd, xAxes[0], yAxes[0])); + } else if( + trace.type === 'splom' && + // FIXME: make sure we don't have more than single axis for splom + trace._xaxes[xAxisIds[0]] && trace._yaxes[yAxisIds[0]] + ) { + var info = createSearchInfo(trace._module, cd, xAxes[0], yAxes[0]); + info.scene = gd._fullLayout._splomScenes[trace.uid]; + searchTraces.push(info); + } else if( + trace.type === 'sankey' + ) { + var sankeyInfo = createSearchInfo(trace._module, cd, xAxes[0], yAxes[0]); + searchTraces.push(sankeyInfo); + } else { + if(xAxisIds.indexOf(trace.xaxis) === -1) continue; + if(yAxisIds.indexOf(trace.yaxis) === -1) continue; + + searchTraces.push(createSearchInfo(trace._module, cd, + getFromId(gd, trace.xaxis), getFromId(gd, trace.yaxis))); + } + } + + return searchTraces; + + function createSearchInfo(module, calcData, xaxis, yaxis) { + return { + _module: module, + cd: calcData, + xaxis: xaxis, + yaxis: yaxis + }; + } +} + +function drawSelection(polygons, outlines) { + var paths = []; + var i, d; + + for(i = 0; i < polygons.length; i++) { + var ppts = polygons[i]; + paths.push(ppts.join('L') + 'L' + ppts[0]); + } + + d = polygons.length > 0 ? + 'M' + paths.join('M') + 'Z' : + 'M0,0Z'; + outlines.attr('d', d); +} + +function isHoverDataSet(hoverData) { + return hoverData && + Array.isArray(hoverData) && + hoverData[0].hoverOnBox !== true; +} + +function extractClickedPtInfo(hoverData, searchTraces) { + var hoverDatum = hoverData[0]; + var pointNumber = -1; + var pointNumbers = []; + var searchInfo, i; + + for(i = 0; i < searchTraces.length; i++) { + searchInfo = searchTraces[i]; + if(hoverDatum.fullData._expandedIndex === searchInfo.cd[0].trace._expandedIndex) { + // Special case for box (and violin) + if(hoverDatum.hoverOnBox === true) { + break; + } + + // Hint: in some traces like histogram, one graphical element + // doesn't correspond to one particular data point, but to + // bins of data points. Thus, hoverDatum can have a binNumber + // property instead of pointNumber. + if(hoverDatum.pointNumber !== undefined) { + pointNumber = hoverDatum.pointNumber; + } else if(hoverDatum.binNumber !== undefined) { + pointNumber = hoverDatum.binNumber; + pointNumbers = hoverDatum.pointNumbers; + } + + break; + } + } + + return { + pointNumber: pointNumber, + pointNumbers: pointNumbers, + searchInfo: searchInfo + }; +} + +function isPointOrBinSelected(clickedPtInfo) { + var trace = clickedPtInfo.searchInfo.cd[0].trace; + var ptNum = clickedPtInfo.pointNumber; + var ptNums = clickedPtInfo.pointNumbers; + var ptNumsSet = ptNums.length > 0; + + // When pointsNumbers is set (e.g. histogram's binning), + // it is assumed that when the first point of + // a bin is selected, all others are as well + var ptNumToTest = ptNumsSet ? ptNums[0] : ptNum; + + // TODO potential performance improvement + // Primarily we need this function to determine if a click adds + // or subtracts from a selection. + // In cases `trace.selectedpoints` is a huge array, indexOf + // might be slow. One remedy would be to introduce a hash somewhere. + return trace.selectedpoints ? trace.selectedpoints.indexOf(ptNumToTest) > -1 : false; +} + +function isOnlyThisBinSelected(searchTraces, clickedPtInfo) { + var tracesWithSelectedPts = []; + var searchInfo, trace, isSameTrace, i; + + for(i = 0; i < searchTraces.length; i++) { + searchInfo = searchTraces[i]; + if(searchInfo.cd[0].trace.selectedpoints && searchInfo.cd[0].trace.selectedpoints.length > 0) { + tracesWithSelectedPts.push(searchInfo); + } + } + + if(tracesWithSelectedPts.length === 1) { + isSameTrace = tracesWithSelectedPts[0] === clickedPtInfo.searchInfo; + if(isSameTrace) { + trace = clickedPtInfo.searchInfo.cd[0].trace; + if(trace.selectedpoints.length === clickedPtInfo.pointNumbers.length) { + for(i = 0; i < clickedPtInfo.pointNumbers.length; i++) { + if(trace.selectedpoints.indexOf(clickedPtInfo.pointNumbers[i]) < 0) { + return false; + } + } + return true; + } + } + } + + return false; +} + +function isOnlyOnePointSelected(searchTraces) { + var len = 0; + var searchInfo, trace, i; + + for(i = 0; i < searchTraces.length; i++) { + searchInfo = searchTraces[i]; + trace = searchInfo.cd[0].trace; + if(trace.selectedpoints) { + if(trace.selectedpoints.length > 1) return false; + + len += trace.selectedpoints.length; + if(len > 1) return false; + } + } + + return len === 1; +} + +function updateSelectedState(gd, searchTraces, eventData) { + var i, searchInfo, cd, trace; + + // before anything else, update preGUI if necessary + for(i = 0; i < searchTraces.length; i++) { + var fullInputTrace = searchTraces[i].cd[0].trace._fullInput; + var tracePreGUI = gd._fullLayout._tracePreGUI[fullInputTrace.uid] || {}; + if(tracePreGUI.selectedpoints === undefined) { + tracePreGUI.selectedpoints = fullInputTrace._input.selectedpoints || null; + } + } + + if(eventData) { + var pts = eventData.points || []; + + for(i = 0; i < searchTraces.length; i++) { + trace = searchTraces[i].cd[0].trace; + trace._input.selectedpoints = trace._fullInput.selectedpoints = []; + if(trace._fullInput !== trace) trace.selectedpoints = []; + } + + for(i = 0; i < pts.length; i++) { + var pt = pts[i]; + var data = pt.data; + var fullData = pt.fullData; + + if(pt.pointIndices) { + [].push.apply(data.selectedpoints, pt.pointIndices); + if(trace._fullInput !== trace) { + [].push.apply(fullData.selectedpoints, pt.pointIndices); + } + } else { + data.selectedpoints.push(pt.pointIndex); + if(trace._fullInput !== trace) { + fullData.selectedpoints.push(pt.pointIndex); + } + } + } + } else { + for(i = 0; i < searchTraces.length; i++) { + trace = searchTraces[i].cd[0].trace; + delete trace.selectedpoints; + delete trace._input.selectedpoints; + if(trace._fullInput !== trace) { + delete trace._fullInput.selectedpoints; + } + } + } + + var hasRegl = false; + + for(i = 0; i < searchTraces.length; i++) { + searchInfo = searchTraces[i]; + cd = searchInfo.cd; + trace = cd[0].trace; + + if(Registry.traceIs(trace, 'regl')) { + hasRegl = true; + } + + var _module = searchInfo._module; + var fn = _module.styleOnSelect || _module.style; + if(fn) { + fn(gd, cd, cd[0].node3); + if(cd[0].nodeRangePlot3) fn(gd, cd, cd[0].nodeRangePlot3); + } + } + + if(hasRegl) { + clearGlCanvases(gd); + redrawReglTraces(gd); + } +} + +function mergePolygons(list, poly, subtract) { + var res; + + if(subtract) { + res = polybool.difference({ + regions: list, + inverted: false + }, { + regions: [poly], + inverted: false + }); + + return res.regions; + } + + res = polybool.union({ + regions: list, + inverted: false + }, { + regions: [poly], + inverted: false + }); + + return res.regions; +} + +function fillSelectionItem(selection, searchInfo) { + if(Array.isArray(selection)) { + var cd = searchInfo.cd; + var trace = searchInfo.cd[0].trace; + + for(var i = 0; i < selection.length; i++) { + selection[i] = makeEventData(selection[i], trace, cd); + } + } + + return selection; +} + +// until we get around to persistent selections, remove the outline +// here. The selection itself will be removed when the plot redraws +// at the end. +function clearSelect(gd) { + var fullLayout = gd._fullLayout || {}; + var zoomlayer = fullLayout._zoomlayer; + if(zoomlayer) { + zoomlayer.selectAll('.select-outline').remove(); + } +} + +module.exports = { + prepSelect: prepSelect, + clearSelect: clearSelect, + selectOnClick: selectOnClick +}; + +},{"../../components/color":51,"../../components/fx":89,"../../components/fx/helpers":86,"../../lib":169,"../../lib/clear_gl_canvases":158,"../../lib/polygon":181,"../../lib/throttle":191,"../../plot_api/subroutines":204,"../../registry":258,"./axis_ids":216,"./constants":219,"polybooljs":25}],231:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); +var isNumeric = _dereq_('fast-isnumeric'); + +var Lib = _dereq_('../../lib'); +var cleanNumber = Lib.cleanNumber; +var ms2DateTime = Lib.ms2DateTime; +var dateTime2ms = Lib.dateTime2ms; +var ensureNumber = Lib.ensureNumber; +var isArrayOrTypedArray = Lib.isArrayOrTypedArray; + +var numConstants = _dereq_('../../constants/numerical'); +var FP_SAFE = numConstants.FP_SAFE; +var BADNUM = numConstants.BADNUM; +var LOG_CLIP = numConstants.LOG_CLIP; + +var constants = _dereq_('./constants'); +var axisIds = _dereq_('./axis_ids'); + +function fromLog(v) { + return Math.pow(10, v); +} + +function isValidCategory(v) { + return v !== null && v !== undefined; +} + +/** + * Define the conversion functions for an axis data is used in 5 ways: + * + * d: data, in whatever form it's provided + * c: calcdata: turned into numbers, but not linearized + * l: linearized - same as c except for log axes (and other nonlinear + * mappings later?) this is used when we need to know if it's + * *possible* to show some data on this axis, without caring about + * the current range + * p: pixel value - mapped to the screen with current size and zoom + * r: ranges, tick0, and annotation positions match one of the above + * but are handled differently for different types: + * - linear and date: data format (d) + * - category: calcdata format (c), and will stay that way because + * the data format has no continuous mapping + * - log: linearized (l) format + * TODO: in v2.0 we plan to change it to data format. At that point + * shapes will work the same way as ranges, tick0, and annotations + * so they can use this conversion too. + * + * Creates/updates these conversion functions, and a few more utilities + * like cleanRange, and makeCalcdata + * + * also clears the autotick constraints ._minDtick, ._forceTick0 + */ +module.exports = function setConvert(ax, fullLayout) { + fullLayout = fullLayout || {}; + + var axId = (ax._id || 'x'); + var axLetter = axId.charAt(0); + + function toLog(v, clip) { + if(v > 0) return Math.log(v) / Math.LN10; + + else if(v <= 0 && clip && ax.range && ax.range.length === 2) { + // clip NaN (ie past negative infinity) to LOG_CLIP axis + // length past the negative edge + var r0 = ax.range[0]; + var r1 = ax.range[1]; + return 0.5 * (r0 + r1 - 2 * LOG_CLIP * Math.abs(r0 - r1)); + } else return BADNUM; + } + + /* + * wrapped dateTime2ms that: + * - accepts ms numbers for backward compatibility + * - inserts a dummy arg so calendar is the 3rd arg (see notes below). + * - defaults to ax.calendar + */ + function dt2ms(v, _, calendar) { + // NOTE: Changed this behavior: previously we took any numeric value + // to be a ms, even if it was a string that could be a bare year. + // Now we convert it as a date if at all possible, and only try + // as (local) ms if that fails. + var ms = dateTime2ms(v, calendar || ax.calendar); + if(ms === BADNUM) { + if(isNumeric(v)) { + v = +v; + // keep track of tenths of ms, that `new Date` will drop + // same logic as in Lib.ms2DateTime + var msecTenths = Math.floor(Lib.mod(v + 0.05, 1) * 10); + var msRounded = Math.round(v - msecTenths / 10); + ms = dateTime2ms(new Date(msRounded)) + msecTenths / 10; + } else return BADNUM; + } + return ms; + } + + // wrapped ms2DateTime to insert default ax.calendar + function ms2dt(v, r, calendar) { + return ms2DateTime(v, r, calendar || ax.calendar); + } + + function getCategoryName(v) { + return ax._categories[Math.round(v)]; + } + + /* + * setCategoryIndex: return the index of category v, + * inserting it in the list if it's not already there + * + * this will enter the categories in the order it + * encounters them, ie all the categories from the + * first data set, then all the ones from the second + * that aren't in the first etc. + * + * it is assumed that this function is being invoked in the + * already sorted category order; otherwise there would be + * a disconnect between the array and the index returned + */ + function setCategoryIndex(v) { + if(isValidCategory(v)) { + if(ax._categoriesMap === undefined) { + ax._categoriesMap = {}; + } + + if(ax._categoriesMap[v] !== undefined) { + return ax._categoriesMap[v]; + } else { + ax._categories.push(typeof v === 'number' ? String(v) : v); + + var curLength = ax._categories.length - 1; + ax._categoriesMap[v] = curLength; + + return curLength; + } + } + return BADNUM; + } + + function setMultiCategoryIndex(arrayIn, len) { + var arrayOut = new Array(len); + + for(var i = 0; i < len; i++) { + var v0 = (arrayIn[0] || [])[i]; + var v1 = (arrayIn[1] || [])[i]; + arrayOut[i] = getCategoryIndex([v0, v1]); + } + + return arrayOut; + } + + function getCategoryIndex(v) { + if(ax._categoriesMap) { + return ax._categoriesMap[v]; + } + } + + function getCategoryPosition(v) { + // d2l/d2c variant that that won't add categories but will also + // allow numbers to be mapped to the linearized axis positions + var index = getCategoryIndex(v); + if(index !== undefined) return index; + if(isNumeric(v)) return +v; + } + + function l2p(v) { + if(!isNumeric(v)) return BADNUM; + + // include 2 fractional digits on pixel, for PDF zooming etc + return d3.round(ax._b + ax._m * v, 2); + } + + function p2l(px) { return (px - ax._b) / ax._m; } + + // conversions among c/l/p are fairly simple - do them together for all axis types + ax.c2l = (ax.type === 'log') ? toLog : ensureNumber; + ax.l2c = (ax.type === 'log') ? fromLog : ensureNumber; + + ax.l2p = l2p; + ax.p2l = p2l; + + ax.c2p = (ax.type === 'log') ? function(v, clip) { return l2p(toLog(v, clip)); } : l2p; + ax.p2c = (ax.type === 'log') ? function(px) { return fromLog(p2l(px)); } : p2l; + + /* + * now type-specific conversions for **ALL** other combinations + * they're all written out, instead of being combinations of each other, for + * both clarity and speed. + */ + if(['linear', '-'].indexOf(ax.type) !== -1) { + // all are data vals, but d and r need cleaning + ax.d2r = ax.r2d = ax.d2c = ax.r2c = ax.d2l = ax.r2l = cleanNumber; + ax.c2d = ax.c2r = ax.l2d = ax.l2r = ensureNumber; + + ax.d2p = ax.r2p = function(v) { return ax.l2p(cleanNumber(v)); }; + ax.p2d = ax.p2r = p2l; + + ax.cleanPos = ensureNumber; + } else if(ax.type === 'log') { + // d and c are data vals, r and l are logged (but d and r need cleaning) + ax.d2r = ax.d2l = function(v, clip) { return toLog(cleanNumber(v), clip); }; + ax.r2d = ax.r2c = function(v) { return fromLog(cleanNumber(v)); }; + + ax.d2c = ax.r2l = cleanNumber; + ax.c2d = ax.l2r = ensureNumber; + + ax.c2r = toLog; + ax.l2d = fromLog; + + ax.d2p = function(v, clip) { return ax.l2p(ax.d2r(v, clip)); }; + ax.p2d = function(px) { return fromLog(p2l(px)); }; + + ax.r2p = function(v) { return ax.l2p(cleanNumber(v)); }; + ax.p2r = p2l; + + ax.cleanPos = ensureNumber; + } else if(ax.type === 'date') { + // r and d are date strings, l and c are ms + + /* + * Any of these functions with r and d on either side, calendar is the + * **3rd** argument. log has reserved the second argument. + * + * Unless you need the special behavior of the second arg (ms2DateTime + * uses this to limit precision, toLog uses true to clip negatives + * to offscreen low rather than undefined), it's safe to pass 0. + */ + ax.d2r = ax.r2d = Lib.identity; + + ax.d2c = ax.r2c = ax.d2l = ax.r2l = dt2ms; + ax.c2d = ax.c2r = ax.l2d = ax.l2r = ms2dt; + + ax.d2p = ax.r2p = function(v, _, calendar) { return ax.l2p(dt2ms(v, 0, calendar)); }; + ax.p2d = ax.p2r = function(px, r, calendar) { return ms2dt(p2l(px), r, calendar); }; + + ax.cleanPos = function(v) { return Lib.cleanDate(v, BADNUM, ax.calendar); }; + } else if(ax.type === 'category') { + // d is categories (string) + // c and l are indices (numbers) + // r is categories or numbers + + ax.d2c = ax.d2l = setCategoryIndex; + ax.r2d = ax.c2d = ax.l2d = getCategoryName; + + ax.d2r = ax.d2l_noadd = getCategoryPosition; + + ax.r2c = function(v) { + var index = getCategoryPosition(v); + return index !== undefined ? index : ax.fraction2r(0.5); + }; + + ax.l2r = ax.c2r = ensureNumber; + ax.r2l = getCategoryPosition; + + ax.d2p = function(v) { return ax.l2p(ax.r2c(v)); }; + ax.p2d = function(px) { return getCategoryName(p2l(px)); }; + ax.r2p = ax.d2p; + ax.p2r = p2l; + + ax.cleanPos = function(v) { + if(typeof v === 'string' && v !== '') return v; + return ensureNumber(v); + }; + } else if(ax.type === 'multicategory') { + // N.B. multicategory axes don't define d2c and d2l, + // as 'data-to-calcdata' conversion needs to take into + // account all data array items as in ax.makeCalcdata. + + ax.r2d = ax.c2d = ax.l2d = getCategoryName; + ax.d2r = ax.d2l_noadd = getCategoryPosition; + + ax.r2c = function(v) { + var index = getCategoryPosition(v); + return index !== undefined ? index : ax.fraction2r(0.5); + }; + + ax.r2c_just_indices = getCategoryIndex; + + ax.l2r = ax.c2r = ensureNumber; + ax.r2l = getCategoryPosition; + + ax.d2p = function(v) { return ax.l2p(ax.r2c(v)); }; + ax.p2d = function(px) { return getCategoryName(p2l(px)); }; + ax.r2p = ax.d2p; + ax.p2r = p2l; + + ax.cleanPos = function(v) { + if(Array.isArray(v) || (typeof v === 'string' && v !== '')) return v; + return ensureNumber(v); + }; + + ax.setupMultiCategory = function(fullData) { + var traceIndices = ax._traceIndices; + var i, j; + + var matchGroups = fullLayout._axisMatchGroups; + if(matchGroups && matchGroups.length && ax._categories.length === 0) { + for(i = 0; i < matchGroups.length; i++) { + var group = matchGroups[i]; + if(group[axId]) { + for(var axId2 in group) { + if(axId2 !== axId) { + var ax2 = fullLayout[axisIds.id2name(axId2)]; + traceIndices = traceIndices.concat(ax2._traceIndices); + } + } + } + } + } + + // [ [cnt, {$cat: index}], for 1,2 ] + var seen = [[0, {}], [0, {}]]; + // [ [arrayIn[0][i], arrayIn[1][i]], for i .. N ] + var list = []; + + for(i = 0; i < traceIndices.length; i++) { + var trace = fullData[traceIndices[i]]; + + if(axLetter in trace) { + var arrayIn = trace[axLetter]; + var len = trace._length || Lib.minRowLength(arrayIn); + + if(isArrayOrTypedArray(arrayIn[0]) && isArrayOrTypedArray(arrayIn[1])) { + for(j = 0; j < len; j++) { + var v0 = arrayIn[0][j]; + var v1 = arrayIn[1][j]; + + if(isValidCategory(v0) && isValidCategory(v1)) { + list.push([v0, v1]); + + if(!(v0 in seen[0][1])) { + seen[0][1][v0] = seen[0][0]++; + } + if(!(v1 in seen[1][1])) { + seen[1][1][v1] = seen[1][0]++; + } + } + } + } + } + } + + list.sort(function(a, b) { + var ind0 = seen[0][1]; + var d = ind0[a[0]] - ind0[b[0]]; + if(d) return d; + + var ind1 = seen[1][1]; + return ind1[a[1]] - ind1[b[1]]; + }); + + for(i = 0; i < list.length; i++) { + setCategoryIndex(list[i]); + } + }; + } + + // find the range value at the specified (linear) fraction of the axis + ax.fraction2r = function(v) { + var rl0 = ax.r2l(ax.range[0]); + var rl1 = ax.r2l(ax.range[1]); + return ax.l2r(rl0 + v * (rl1 - rl0)); + }; + + // find the fraction of the range at the specified range value + ax.r2fraction = function(v) { + var rl0 = ax.r2l(ax.range[0]); + var rl1 = ax.r2l(ax.range[1]); + return (ax.r2l(v) - rl0) / (rl1 - rl0); + }; + + /* + * cleanRange: make sure range is a couplet of valid & distinct values + * keep numbers away from the limits of floating point numbers, + * and dates away from the ends of our date system (+/- 9999 years) + * + * optional param rangeAttr: operate on a different attribute, like + * ax._r, rather than ax.range + */ + ax.cleanRange = function(rangeAttr, opts) { + if(!opts) opts = {}; + if(!rangeAttr) rangeAttr = 'range'; + + var range = Lib.nestedProperty(ax, rangeAttr).get(); + var i, dflt; + + if(ax.type === 'date') dflt = Lib.dfltRange(ax.calendar); + else if(axLetter === 'y') dflt = constants.DFLTRANGEY; + else dflt = opts.dfltRange || constants.DFLTRANGEX; + + // make sure we don't later mutate the defaults + dflt = dflt.slice(); + + if(ax.rangemode === 'tozero' || ax.rangemode === 'nonnegative') { + dflt[0] = 0; + } + + if(!range || range.length !== 2) { + Lib.nestedProperty(ax, rangeAttr).set(dflt); + return; + } + + if(ax.type === 'date' && !ax.autorange) { + // check if milliseconds or js date objects are provided for range + // and convert to date strings + range[0] = Lib.cleanDate(range[0], BADNUM, ax.calendar); + range[1] = Lib.cleanDate(range[1], BADNUM, ax.calendar); + } + + for(i = 0; i < 2; i++) { + if(ax.type === 'date') { + if(!Lib.isDateTime(range[i], ax.calendar)) { + ax[rangeAttr] = dflt; + break; + } + + if(ax.r2l(range[0]) === ax.r2l(range[1])) { + // split by +/- 1 second + var linCenter = Lib.constrain(ax.r2l(range[0]), + Lib.MIN_MS + 1000, Lib.MAX_MS - 1000); + range[0] = ax.l2r(linCenter - 1000); + range[1] = ax.l2r(linCenter + 1000); + break; + } + } else { + if(!isNumeric(range[i])) { + if(isNumeric(range[1 - i])) { + range[i] = range[1 - i] * (i ? 10 : 0.1); + } else { + ax[rangeAttr] = dflt; + break; + } + } + + if(range[i] < -FP_SAFE) range[i] = -FP_SAFE; + else if(range[i] > FP_SAFE) range[i] = FP_SAFE; + + if(range[0] === range[1]) { + // somewhat arbitrary: split by 1 or 1ppm, whichever is bigger + var inc = Math.max(1, Math.abs(range[0] * 1e-6)); + range[0] -= inc; + range[1] += inc; + } + } + } + }; + + // set scaling to pixels + ax.setScale = function(usePrivateRange) { + var gs = fullLayout._size; + + // make sure we have a domain (pull it in from the axis + // this one is overlaying if necessary) + if(ax.overlaying) { + var ax2 = axisIds.getFromId({ _fullLayout: fullLayout }, ax.overlaying); + ax.domain = ax2.domain; + } + + // While transitions are occuring, occurring, we get a double-transform + // issue if we transform the drawn layer *and* use the new axis range to + // draw the data. This allows us to construct setConvert using the pre- + // interaction values of the range: + var rangeAttr = (usePrivateRange && ax._r) ? '_r' : 'range'; + var calendar = ax.calendar; + ax.cleanRange(rangeAttr); + + var rl0 = ax.r2l(ax[rangeAttr][0], calendar); + var rl1 = ax.r2l(ax[rangeAttr][1], calendar); + + if(axLetter === 'y') { + ax._offset = gs.t + (1 - ax.domain[1]) * gs.h; + ax._length = gs.h * (ax.domain[1] - ax.domain[0]); + ax._m = ax._length / (rl0 - rl1); + ax._b = -ax._m * rl1; + } else { + ax._offset = gs.l + ax.domain[0] * gs.w; + ax._length = gs.w * (ax.domain[1] - ax.domain[0]); + ax._m = ax._length / (rl1 - rl0); + ax._b = -ax._m * rl0; + } + + if(!isFinite(ax._m) || !isFinite(ax._b) || ax._length < 0) { + fullLayout._replotting = false; + throw new Error('Something went wrong with axis scaling'); + } + }; + + // makeCalcdata: takes an x or y array and converts it + // to a position on the axis object "ax" + // inputs: + // trace - a data object from gd.data + // axLetter - a string, either 'x' or 'y', for which item + // to convert (TODO: is this now always the same as + // the first letter of ax._id?) + // in case the expected data isn't there, make a list of + // integers based on the opposite data + ax.makeCalcdata = function(trace, axLetter) { + var arrayIn, arrayOut, i, len; + + var axType = ax.type; + var cal = axType === 'date' && trace[axLetter + 'calendar']; + + if(axLetter in trace) { + arrayIn = trace[axLetter]; + len = trace._length || Lib.minRowLength(arrayIn); + + if(Lib.isTypedArray(arrayIn) && (axType === 'linear' || axType === 'log')) { + if(len === arrayIn.length) { + return arrayIn; + } else if(arrayIn.subarray) { + return arrayIn.subarray(0, len); + } + } + + if(axType === 'multicategory') { + return setMultiCategoryIndex(arrayIn, len); + } + + arrayOut = new Array(len); + for(i = 0; i < len; i++) { + arrayOut[i] = ax.d2c(arrayIn[i], 0, cal); + } + } else { + var v0 = ((axLetter + '0') in trace) ? ax.d2c(trace[axLetter + '0'], 0, cal) : 0; + var dv = (trace['d' + axLetter]) ? Number(trace['d' + axLetter]) : 1; + + // the opposing data, for size if we have x and dx etc + arrayIn = trace[{x: 'y', y: 'x'}[axLetter]]; + len = trace._length || arrayIn.length; + arrayOut = new Array(len); + + for(i = 0; i < len; i++) { + arrayOut[i] = v0 + i * dv; + } + } + + return arrayOut; + }; + + ax.isValidRange = function(range) { + return ( + Array.isArray(range) && + range.length === 2 && + isNumeric(ax.r2l(range[0])) && + isNumeric(ax.r2l(range[1])) + ); + }; + + ax.isPtWithinRange = function(d, calendar) { + var coord = ax.c2l(d[axLetter], null, calendar); + var r0 = ax.r2l(ax.range[0]); + var r1 = ax.r2l(ax.range[1]); + + if(r0 < r1) { + return r0 <= coord && coord <= r1; + } else { + // Reversed axis case. + return r1 <= coord && coord <= r0; + } + }; + + // should skip if not category nor multicategory + ax.clearCalc = function() { + var emptyCategories = function() { + ax._categories = []; + ax._categoriesMap = {}; + }; + + var matchGroups = fullLayout._axisMatchGroups; + + if(matchGroups && matchGroups.length) { + var found = false; + + for(var i = 0; i < matchGroups.length; i++) { + var group = matchGroups[i]; + + if(group[axId]) { + found = true; + var categories = null; + var categoriesMap = null; + + for(var axId2 in group) { + var ax2 = fullLayout[axisIds.id2name(axId2)]; + if(ax2._categories) { + categories = ax2._categories; + categoriesMap = ax2._categoriesMap; + break; + } + } + + if(categories && categoriesMap) { + ax._categories = categories; + ax._categoriesMap = categoriesMap; + } else { + emptyCategories(); + } + break; + } + } + if(!found) emptyCategories(); + } else { + emptyCategories(); + } + + if(ax._initialCategories) { + for(var j = 0; j < ax._initialCategories.length; j++) { + setCategoryIndex(ax._initialCategories[j]); + } + } + }; + + // sort the axis (and all the matching ones) by _initialCategories + // returns the indices of the traces affected by the reordering + ax.sortByInitialCategories = function() { + var affectedTraces = []; + var emptyCategories = function() { + ax._categories = []; + ax._categoriesMap = {}; + }; + + emptyCategories(); + + if(ax._initialCategories) { + for(var j = 0; j < ax._initialCategories.length; j++) { + setCategoryIndex(ax._initialCategories[j]); + } + } + + affectedTraces = affectedTraces.concat(ax._traceIndices); + + // Propagate to matching axes + var group = ax._matchGroup; + for(var axId2 in group) { + if(axId === axId2) continue; + var ax2 = fullLayout[axisIds.id2name(axId2)]; + ax2._categories = ax._categories; + ax2._categoriesMap = ax._categoriesMap; + affectedTraces = affectedTraces.concat(ax2._traceIndices); + } + return affectedTraces; + }; + + // Propagate localization into the axis so that + // methods in Axes can use it w/o having to pass fullLayout + // Default (non-d3) number formatting uses separators directly + // dates and d3-formatted numbers use the d3 locale + // Fall back on default format for dummy axes that don't care about formatting + var locale = fullLayout._d3locale; + if(ax.type === 'date') { + ax._dateFormat = locale ? locale.timeFormat.utc : d3.time.format.utc; + ax._extraFormat = fullLayout._extraFormat; + } + // occasionally we need _numFormat to pass through + // even though it won't be needed by this axis + ax._separators = fullLayout.separators; + ax._numFormat = locale ? locale.numberFormat : d3.format; + + // and for bar charts and box plots: reset forced minimum tick spacing + delete ax._minDtick; + delete ax._forceTick0; +}; + +},{"../../constants/numerical":149,"../../lib":169,"./axis_ids":216,"./constants":219,"d3":16,"fast-isnumeric":18}],232:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Lib = _dereq_('../../lib'); +var layoutAttributes = _dereq_('./layout_attributes'); +var handleArrayContainerDefaults = _dereq_('../array_container_defaults'); + +module.exports = function handleTickLabelDefaults(containerIn, containerOut, coerce, axType, options, config) { + if(!config || config.pass === 1) { + handlePrefixSuffix(containerIn, containerOut, coerce, axType, options); + } + + if(!config || config.pass === 2) { + handleOtherDefaults(containerIn, containerOut, coerce, axType, options); + } +}; + +function handlePrefixSuffix(containerIn, containerOut, coerce, axType, options) { + var showAttrDflt = getShowAttrDflt(containerIn); + + var tickPrefix = coerce('tickprefix'); + if(tickPrefix) coerce('showtickprefix', showAttrDflt); + + var tickSuffix = coerce('ticksuffix', options.tickSuffixDflt); + if(tickSuffix) coerce('showticksuffix', showAttrDflt); +} + +function handleOtherDefaults(containerIn, containerOut, coerce, axType, options) { + var showAttrDflt = getShowAttrDflt(containerIn); + + var tickPrefix = coerce('tickprefix'); + if(tickPrefix) coerce('showtickprefix', showAttrDflt); + + var tickSuffix = coerce('ticksuffix', options.tickSuffixDflt); + if(tickSuffix) coerce('showticksuffix', showAttrDflt); + + var showTickLabels = coerce('showticklabels'); + if(showTickLabels) { + var font = options.font || {}; + var contColor = containerOut.color; + // as with titlefont.color, inherit axis.color only if one was + // explicitly provided + var dfltFontColor = (contColor && contColor !== layoutAttributes.color.dflt) ? + contColor : font.color; + Lib.coerceFont(coerce, 'tickfont', { + family: font.family, + size: font.size, + color: dfltFontColor + }); + coerce('tickangle'); + + if(axType !== 'category') { + var tickFormat = coerce('tickformat'); + var tickformatStops = containerIn.tickformatstops; + if(Array.isArray(tickformatStops) && tickformatStops.length) { + handleArrayContainerDefaults(containerIn, containerOut, { + name: 'tickformatstops', + inclusionAttr: 'enabled', + handleItemDefaults: tickformatstopDefaults + }); + } + if(!tickFormat && axType !== 'date') { + coerce('showexponent', showAttrDflt); + coerce('exponentformat'); + coerce('separatethousands'); + } + } + } +} + +/* + * Attributes 'showexponent', 'showtickprefix' and 'showticksuffix' + * share values. + * + * If only 1 attribute is set, + * the remaining attributes inherit that value. + * + * If 2 attributes are set to the same value, + * the remaining attribute inherits that value. + * + * If 2 attributes are set to different values, + * the remaining is set to its dflt value. + * + */ +function getShowAttrDflt(containerIn) { + var showAttrsAll = ['showexponent', 'showtickprefix', 'showticksuffix']; + var showAttrs = showAttrsAll.filter(function(a) { + return containerIn[a] !== undefined; + }); + var sameVal = function(a) { + return containerIn[a] === containerIn[showAttrs[0]]; + }; + + if(showAttrs.every(sameVal) || showAttrs.length === 1) { + return containerIn[showAttrs[0]]; + } +} + +function tickformatstopDefaults(valueIn, valueOut) { + function coerce(attr, dflt) { + return Lib.coerce(valueIn, valueOut, layoutAttributes.tickformatstops, attr, dflt); + } + + var enabled = coerce('enabled'); + if(enabled) { + coerce('dtickrange'); + coerce('value'); + } +} + +},{"../../lib":169,"../array_container_defaults":209,"./layout_attributes":225}],233:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Lib = _dereq_('../../lib'); + +var layoutAttributes = _dereq_('./layout_attributes'); + + +/** + * options: inherits outerTicks from axes.handleAxisDefaults + */ +module.exports = function handleTickDefaults(containerIn, containerOut, coerce, options) { + var tickLen = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'ticklen'); + var tickWidth = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'tickwidth'); + var tickColor = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'tickcolor', containerOut.color); + var showTicks = coerce('ticks', (options.outerTicks || tickLen || tickWidth || tickColor) ? 'outside' : ''); + + if(!showTicks) { + delete containerOut.ticklen; + delete containerOut.tickwidth; + delete containerOut.tickcolor; + } +}; + +},{"../../lib":169,"./layout_attributes":225}],234:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var cleanTicks = _dereq_('./clean_ticks'); + +module.exports = function handleTickValueDefaults(containerIn, containerOut, coerce, axType) { + var tickmode; + + if(containerIn.tickmode === 'array' && + (axType === 'log' || axType === 'date')) { + tickmode = containerOut.tickmode = 'auto'; + } else { + var tickmodeDefault = Array.isArray(containerIn.tickvals) ? 'array' : + containerIn.dtick ? 'linear' : + 'auto'; + tickmode = coerce('tickmode', tickmodeDefault); + } + + if(tickmode === 'auto') coerce('nticks'); + else if(tickmode === 'linear') { + // dtick is usually a positive number, but there are some + // special strings available for log or date axes + // tick0 also has special logic + var dtick = containerOut.dtick = cleanTicks.dtick( + containerIn.dtick, axType); + containerOut.tick0 = cleanTicks.tick0( + containerIn.tick0, axType, containerOut.calendar, dtick); + } else if(axType !== 'multicategory') { + var tickvals = coerce('tickvals'); + if(tickvals === undefined) containerOut.tickmode = 'auto'; + else coerce('ticktext'); + } +}; + +},{"./clean_ticks":218}],235:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); + +var Registry = _dereq_('../../registry'); +var Lib = _dereq_('../../lib'); +var Drawing = _dereq_('../../components/drawing'); +var Axes = _dereq_('./axes'); + +/** + * transitionAxes + * + * transition axes from one set of ranges to another, using a svg + * transformations, similar to during panning. + * + * @param {DOM element | object} gd + * @param {array} edits : array of 'edits', each item with + * - plotinfo {object} subplot object + * - xr0 {array} initial x-range + * - xr1 {array} end x-range + * - yr0 {array} initial y-range + * - yr1 {array} end y-range + * @param {object} transitionOpts + * @param {function} makeOnCompleteCallback + */ +module.exports = function transitionAxes(gd, edits, transitionOpts, makeOnCompleteCallback) { + var fullLayout = gd._fullLayout; + + // special case for redraw:false Plotly.animate that relies on this + // to update axis-referenced layout components + if(edits.length === 0) { + Axes.redrawComponents(gd); + return; + } + + function unsetSubplotTransform(subplot) { + var xa = subplot.xaxis; + var ya = subplot.yaxis; + + fullLayout._defs.select('#' + subplot.clipId + '> rect') + .call(Drawing.setTranslate, 0, 0) + .call(Drawing.setScale, 1, 1); + + subplot.plot + .call(Drawing.setTranslate, xa._offset, ya._offset) + .call(Drawing.setScale, 1, 1); + + var traceGroups = subplot.plot.selectAll('.scatterlayer .trace'); + + // This is specifically directed at scatter traces, applying an inverse + // scale to individual points to counteract the scale of the trace + // as a whole: + traceGroups.selectAll('.point') + .call(Drawing.setPointGroupScale, 1, 1); + traceGroups.selectAll('.textpoint') + .call(Drawing.setTextPointsScale, 1, 1); + traceGroups + .call(Drawing.hideOutsideRangePoints, subplot); + } + + function updateSubplot(edit, progress) { + var plotinfo = edit.plotinfo; + var xa = plotinfo.xaxis; + var ya = plotinfo.yaxis; + var xlen = xa._length; + var ylen = ya._length; + var editX = !!edit.xr1; + var editY = !!edit.yr1; + var viewBox = []; + + if(editX) { + var xr0 = Lib.simpleMap(edit.xr0, xa.r2l); + var xr1 = Lib.simpleMap(edit.xr1, xa.r2l); + var dx0 = xr0[1] - xr0[0]; + var dx1 = xr1[1] - xr1[0]; + viewBox[0] = (xr0[0] * (1 - progress) + progress * xr1[0] - xr0[0]) / (xr0[1] - xr0[0]) * xlen; + viewBox[2] = xlen * ((1 - progress) + progress * dx1 / dx0); + xa.range[0] = xa.l2r(xr0[0] * (1 - progress) + progress * xr1[0]); + xa.range[1] = xa.l2r(xr0[1] * (1 - progress) + progress * xr1[1]); + } else { + viewBox[0] = 0; + viewBox[2] = xlen; + } + + if(editY) { + var yr0 = Lib.simpleMap(edit.yr0, ya.r2l); + var yr1 = Lib.simpleMap(edit.yr1, ya.r2l); + var dy0 = yr0[1] - yr0[0]; + var dy1 = yr1[1] - yr1[0]; + viewBox[1] = (yr0[1] * (1 - progress) + progress * yr1[1] - yr0[1]) / (yr0[0] - yr0[1]) * ylen; + viewBox[3] = ylen * ((1 - progress) + progress * dy1 / dy0); + ya.range[0] = xa.l2r(yr0[0] * (1 - progress) + progress * yr1[0]); + ya.range[1] = ya.l2r(yr0[1] * (1 - progress) + progress * yr1[1]); + } else { + viewBox[1] = 0; + viewBox[3] = ylen; + } + + Axes.drawOne(gd, xa, {skipTitle: true}); + Axes.drawOne(gd, ya, {skipTitle: true}); + Axes.redrawComponents(gd, [xa._id, ya._id]); + + var xScaleFactor = editX ? xlen / viewBox[2] : 1; + var yScaleFactor = editY ? ylen / viewBox[3] : 1; + var clipDx = editX ? viewBox[0] : 0; + var clipDy = editY ? viewBox[1] : 0; + var fracDx = editX ? (viewBox[0] / viewBox[2] * xlen) : 0; + var fracDy = editY ? (viewBox[1] / viewBox[3] * ylen) : 0; + var plotDx = xa._offset - fracDx; + var plotDy = ya._offset - fracDy; + + plotinfo.clipRect + .call(Drawing.setTranslate, clipDx, clipDy) + .call(Drawing.setScale, 1 / xScaleFactor, 1 / yScaleFactor); + + plotinfo.plot + .call(Drawing.setTranslate, plotDx, plotDy) + .call(Drawing.setScale, xScaleFactor, yScaleFactor); + + // apply an inverse scale to individual points to counteract + // the scale of the trace group. + Drawing.setPointGroupScale(plotinfo.zoomScalePts, 1 / xScaleFactor, 1 / yScaleFactor); + Drawing.setTextPointsScale(plotinfo.zoomScaleTxt, 1 / xScaleFactor, 1 / yScaleFactor); + } + + var onComplete; + if(makeOnCompleteCallback) { + // This module makes the choice whether or not it notifies Plotly.transition + // about completion: + onComplete = makeOnCompleteCallback(); + } + + function transitionComplete() { + var aobj = {}; + + for(var i = 0; i < edits.length; i++) { + var edit = edits[i]; + var xa = edit.plotinfo.xaxis; + var ya = edit.plotinfo.yaxis; + if(edit.xr1) aobj[xa._name + '.range'] = edit.xr1.slice(); + if(edit.yr1) aobj[ya._name + '.range'] = edit.yr1.slice(); + } + + // Signal that this transition has completed: + onComplete && onComplete(); + + return Registry.call('relayout', gd, aobj).then(function() { + for(var i = 0; i < edits.length; i++) { + unsetSubplotTransform(edits[i].plotinfo); + } + }); + } + + function transitionInterrupt() { + var aobj = {}; + + for(var i = 0; i < edits.length; i++) { + var edit = edits[i]; + var xa = edit.plotinfo.xaxis; + var ya = edit.plotinfo.yaxis; + if(edit.xr0) aobj[xa._name + '.range'] = edit.xr0.slice(); + if(edit.yr0) aobj[ya._name + '.range'] = edit.yr0.slice(); + } + + return Registry.call('relayout', gd, aobj).then(function() { + for(var i = 0; i < edits.length; i++) { + unsetSubplotTransform(edits[i].plotinfo); + } + }); + } + + var t1, t2, raf; + var easeFn = d3.ease(transitionOpts.easing); + + gd._transitionData._interruptCallbacks.push(function() { + window.cancelAnimationFrame(raf); + raf = null; + return transitionInterrupt(); + }); + + function doFrame() { + t2 = Date.now(); + + var tInterp = Math.min(1, (t2 - t1) / transitionOpts.duration); + var progress = easeFn(tInterp); + + for(var i = 0; i < edits.length; i++) { + updateSubplot(edits[i], progress); + } + + if(t2 - t1 > transitionOpts.duration) { + transitionComplete(); + raf = window.cancelAnimationFrame(doFrame); + } else { + raf = window.requestAnimationFrame(doFrame); + } + } + + t1 = Date.now(); + raf = window.requestAnimationFrame(doFrame); + + return Promise.resolve(); +}; + +},{"../../components/drawing":72,"../../lib":169,"../../registry":258,"./axes":213,"d3":16}],236:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var traceIs = _dereq_('../../registry').traceIs; +var autoType = _dereq_('./axis_autotype'); + +/* + * data: the plot data to use in choosing auto type + * name: axis object name (ie 'xaxis') if one should be stored + */ +module.exports = function handleTypeDefaults(containerIn, containerOut, coerce, options) { + var axType = coerce('type', (options.splomStash || {}).type); + + if(axType === '-') { + setAutoType(containerOut, options.data); + + if(containerOut.type === '-') { + containerOut.type = 'linear'; + } else { + // copy autoType back to input axis + // note that if this object didn't exist + // in the input layout, we have to put it in + // this happens in the main supplyDefaults function + containerIn.type = containerOut.type; + } + } +}; + +function setAutoType(ax, data) { + // new logic: let people specify any type they want, + // only autotype if type is '-' + if(ax.type !== '-') return; + + var id = ax._id; + var axLetter = id.charAt(0); + + // support 3d + if(id.indexOf('scene') !== -1) id = axLetter; + + var d0 = getFirstNonEmptyTrace(data, id, axLetter); + if(!d0) return; + + // first check for histograms, as the count direction + // should always default to a linear axis + if(d0.type === 'histogram' && + axLetter === {v: 'y', h: 'x'}[d0.orientation || 'v']) { + ax.type = 'linear'; + return; + } + + var calAttr = axLetter + 'calendar'; + var calendar = d0[calAttr]; + var opts = {noMultiCategory: !traceIs(d0, 'cartesian') || traceIs(d0, 'noMultiCategory')}; + var i; + + // check all boxes on this x axis to see + // if they're dates, numbers, or categories + if(isBoxWithoutPositionCoords(d0, axLetter)) { + var posLetter = getBoxPosLetter(d0); + var boxPositions = []; + + for(i = 0; i < data.length; i++) { + var trace = data[i]; + if(!traceIs(trace, 'box-violin') || (trace[axLetter + 'axis'] || axLetter) !== id) continue; + + if(trace[posLetter] !== undefined) boxPositions.push(trace[posLetter][0]); + else if(trace.name !== undefined) boxPositions.push(trace.name); + else boxPositions.push('text'); + + if(trace[calAttr] !== calendar) calendar = undefined; + } + + ax.type = autoType(boxPositions, calendar, opts); + } else if(d0.type === 'splom') { + var dimensions = d0.dimensions; + var dim = dimensions[d0._axesDim[id]]; + if(dim.visible) ax.type = autoType(dim.values, calendar, opts); + } else { + ax.type = autoType(d0[axLetter] || [d0[axLetter + '0']], calendar, opts); + } +} + +function getFirstNonEmptyTrace(data, id, axLetter) { + for(var i = 0; i < data.length; i++) { + var trace = data[i]; + + if(trace.type === 'splom' && + trace._length > 0 && + (trace['_' + axLetter + 'axes'] || {})[id] + ) { + return trace; + } + + if((trace[axLetter + 'axis'] || axLetter) === id) { + if(isBoxWithoutPositionCoords(trace, axLetter)) { + return trace; + } else if((trace[axLetter] || []).length || trace[axLetter + '0']) { + return trace; + } + } + } +} + +function getBoxPosLetter(trace) { + return {v: 'x', h: 'y'}[trace.orientation || 'v']; +} + +function isBoxWithoutPositionCoords(trace, axLetter) { + var posLetter = getBoxPosLetter(trace); + var isBox = traceIs(trace, 'box-violin'); + var isCandlestick = traceIs(trace._fullInput || {}, 'candlestick'); + + return ( + isBox && + !isCandlestick && + axLetter === posLetter && + trace[posLetter] === undefined && + trace[posLetter + '0'] === undefined + ); +} + +},{"../../registry":258,"./axis_autotype":214}],237:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Registry = _dereq_('../registry'); +var Lib = _dereq_('../lib'); + +/* + * Create or update an observer. This function is designed to be + * idempotent so that it can be called over and over as the component + * updates, and will attach and detach listeners as needed. + * + * @param {optional object} container + * An object on which the observer is stored. This is the mechanism + * by which it is idempotent. If it already exists, another won't be + * added. Each time it's called, the value lookup table is updated. + * @param {array} commandList + * An array of commands, following either `buttons` of `updatemenus` + * or `steps` of `sliders`. + * @param {function} onchange + * A listener called when the value is changed. Receives data object + * with information about the new state. + */ +exports.manageCommandObserver = function(gd, container, commandList, onchange) { + var ret = {}; + var enabled = true; + + if(container && container._commandObserver) { + ret = container._commandObserver; + } + + if(!ret.cache) { + ret.cache = {}; + } + + // Either create or just recompute this: + ret.lookupTable = {}; + + var binding = exports.hasSimpleAPICommandBindings(gd, commandList, ret.lookupTable); + + if(container && container._commandObserver) { + if(!binding) { + // If container exists and there are no longer any bindings, + // remove existing: + if(container._commandObserver.remove) { + container._commandObserver.remove(); + container._commandObserver = null; + return ret; + } + } else { + // If container exists and there *are* bindings, then the lookup + // table should have been updated and check is already attached, + // so there's nothing to be done: + return ret; + } + } + + // Determine whether there's anything to do for this binding: + + if(binding) { + // Build the cache: + bindingValueHasChanged(gd, binding, ret.cache); + + ret.check = function check() { + if(!enabled) return; + + var update = bindingValueHasChanged(gd, binding, ret.cache); + + if(update.changed && onchange) { + // Disable checks for the duration of this command in order to avoid + // infinite loops: + if(ret.lookupTable[update.value] !== undefined) { + ret.disable(); + Promise.resolve(onchange({ + value: update.value, + type: binding.type, + prop: binding.prop, + traces: binding.traces, + index: ret.lookupTable[update.value] + })).then(ret.enable, ret.enable); + } + } + + return update.changed; + }; + + var checkEvents = [ + 'plotly_relayout', + 'plotly_redraw', + 'plotly_restyle', + 'plotly_update', + 'plotly_animatingframe', + 'plotly_afterplot' + ]; + + for(var i = 0; i < checkEvents.length; i++) { + gd._internalOn(checkEvents[i], ret.check); + } + + ret.remove = function() { + for(var i = 0; i < checkEvents.length; i++) { + gd._removeInternalListener(checkEvents[i], ret.check); + } + }; + } else { + // TODO: It'd be really neat to actually give a *reason* for this, but at least a warning + // is a start + Lib.log('Unable to automatically bind plot updates to API command'); + + ret.lookupTable = {}; + ret.remove = function() {}; + } + + ret.disable = function disable() { + enabled = false; + }; + + ret.enable = function enable() { + enabled = true; + }; + + if(container) { + container._commandObserver = ret; + } + + return ret; +}; + +/* + * This function checks to see if an array of objects containing + * method and args properties is compatible with automatic two-way + * binding. The criteria right now are that + * + * 1. multiple traces may be affected + * 2. only one property may be affected + * 3. the same property must be affected by all commands + */ +exports.hasSimpleAPICommandBindings = function(gd, commandList, bindingsByValue) { + var i; + var n = commandList.length; + + var refBinding; + + for(i = 0; i < n; i++) { + var binding; + var command = commandList[i]; + var method = command.method; + var args = command.args; + + if(!Array.isArray(args)) args = []; + + // If any command has no method, refuse to bind: + if(!method) { + return false; + } + var bindings = exports.computeAPICommandBindings(gd, method, args); + + // Right now, handle one and *only* one property being set: + if(bindings.length !== 1) { + return false; + } + + if(!refBinding) { + refBinding = bindings[0]; + if(Array.isArray(refBinding.traces)) { + refBinding.traces.sort(); + } + } else { + binding = bindings[0]; + if(binding.type !== refBinding.type) { + return false; + } + if(binding.prop !== refBinding.prop) { + return false; + } + if(Array.isArray(refBinding.traces)) { + if(Array.isArray(binding.traces)) { + binding.traces.sort(); + for(var j = 0; j < refBinding.traces.length; j++) { + if(refBinding.traces[j] !== binding.traces[j]) { + return false; + } + } + } else { + return false; + } + } else { + if(binding.prop !== refBinding.prop) { + return false; + } + } + } + + binding = bindings[0]; + var value = binding.value; + if(Array.isArray(value)) { + if(value.length === 1) { + value = value[0]; + } else { + return false; + } + } + if(bindingsByValue) { + bindingsByValue[value] = i; + } + } + + return refBinding; +}; + +function bindingValueHasChanged(gd, binding, cache) { + var container, value, obj; + var changed = false; + + if(binding.type === 'data') { + // If it's data, we need to get a trace. Based on the limited scope + // of what we cover, we can just take the first trace from the list, + // or otherwise just the first trace: + container = gd._fullData[binding.traces !== null ? binding.traces[0] : 0]; + } else if(binding.type === 'layout') { + container = gd._fullLayout; + } else { + return false; + } + + value = Lib.nestedProperty(container, binding.prop).get(); + + obj = cache[binding.type] = cache[binding.type] || {}; + + if(obj.hasOwnProperty(binding.prop)) { + if(obj[binding.prop] !== value) { + changed = true; + } + } + + obj[binding.prop] = value; + + return { + changed: changed, + value: value + }; +} + +/* + * Execute an API command. There's really not much to this; it just provides + * a common hook so that implementations don't need to be synchronized across + * multiple components with the ability to invoke API commands. + * + * @param {string} method + * The name of the plotly command to execute. Must be one of 'animate', + * 'restyle', 'relayout', 'update'. + * @param {array} args + * A list of arguments passed to the API command + */ +exports.executeAPICommand = function(gd, method, args) { + if(method === 'skip') return Promise.resolve(); + + var _method = Registry.apiMethodRegistry[method]; + var allArgs = [gd]; + if(!Array.isArray(args)) args = []; + + for(var i = 0; i < args.length; i++) { + allArgs.push(args[i]); + } + + return _method.apply(null, allArgs).catch(function(err) { + Lib.warn('API call to Plotly.' + method + ' rejected.', err); + return Promise.reject(err); + }); +}; + +exports.computeAPICommandBindings = function(gd, method, args) { + var bindings; + + if(!Array.isArray(args)) args = []; + + switch(method) { + case 'restyle': + bindings = computeDataBindings(gd, args); + break; + case 'relayout': + bindings = computeLayoutBindings(gd, args); + break; + case 'update': + bindings = computeDataBindings(gd, [args[0], args[2]]) + .concat(computeLayoutBindings(gd, [args[1]])); + break; + case 'animate': + bindings = computeAnimateBindings(gd, args); + break; + default: + // This is the case where intelligent logic about what affects + // this command is not implemented. It causes no ill effects. + // For example, addFrames simply won't bind to a control component. + bindings = []; + } + return bindings; +}; + +function computeAnimateBindings(gd, args) { + // We'll assume that the only relevant modification an animation + // makes that's meaningfully tracked is the frame: + if(Array.isArray(args[0]) && args[0].length === 1 && ['string', 'number'].indexOf(typeof args[0][0]) !== -1) { + return [{type: 'layout', prop: '_currentFrame', value: args[0][0].toString()}]; + } else { + return []; + } +} + +function computeLayoutBindings(gd, args) { + var bindings = []; + + var astr = args[0]; + var aobj = {}; + if(typeof astr === 'string') { + aobj[astr] = args[1]; + } else if(Lib.isPlainObject(astr)) { + aobj = astr; + } else { + return bindings; + } + + crawl(aobj, function(path, attrName, attr) { + bindings.push({type: 'layout', prop: path, value: attr}); + }, '', 0); + + return bindings; +} + +function computeDataBindings(gd, args) { + var traces, astr, val, aobj; + var bindings = []; + + // Logic copied from Plotly.restyle: + astr = args[0]; + val = args[1]; + traces = args[2]; + aobj = {}; + if(typeof astr === 'string') { + aobj[astr] = val; + } else if(Lib.isPlainObject(astr)) { + // the 3-arg form + aobj = astr; + + if(traces === undefined) { + traces = val; + } + } else { + return bindings; + } + + if(traces === undefined) { + // Explicitly assign this to null instead of undefined: + traces = null; + } + + crawl(aobj, function(path, attrName, _attr) { + var thisTraces; + var attr; + + if(Array.isArray(_attr)) { + attr = _attr.slice(); + + var nAttr = Math.min(attr.length, gd.data.length); + if(traces) { + nAttr = Math.min(nAttr, traces.length); + } + thisTraces = []; + for(var j = 0; j < nAttr; j++) { + thisTraces[j] = traces ? traces[j] : j; + } + } else { + attr = _attr; + thisTraces = traces ? traces.slice() : null; + } + + // Convert [7] to just 7 when traces is null: + if(thisTraces === null) { + if(Array.isArray(attr)) { + attr = attr[0]; + } + } else if(Array.isArray(thisTraces)) { + if(!Array.isArray(attr)) { + var tmp = attr; + attr = []; + for(var i = 0; i < thisTraces.length; i++) { + attr[i] = tmp; + } + } + attr.length = Math.min(thisTraces.length, attr.length); + } + + bindings.push({ + type: 'data', + prop: path, + traces: thisTraces, + value: attr + }); + }, '', 0); + + return bindings; +} + +function crawl(attrs, callback, path, depth) { + Object.keys(attrs).forEach(function(attrName) { + var attr = attrs[attrName]; + + if(attrName[0] === '_') return; + + var thisPath = path + (depth > 0 ? '.' : '') + attrName; + + if(Lib.isPlainObject(attr)) { + crawl(attr, callback, thisPath, depth + 1); + } else { + // Only execute the callback on leaf nodes: + callback(thisPath, attrName, attr); + } + }); +} + +},{"../lib":169,"../registry":258}],238:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var extendFlat = _dereq_('../lib/extend').extendFlat; + +/** + * Make a xy domain attribute group + * + * @param {object} opts + * @param {string} + * opts.name: name to be inserted in the default description + * @param {boolean} + * opts.trace: set to true for trace containers + * @param {string} + * opts.editType: editType for all pieces + * @param {boolean} + * opts.noGridCell: set to true to omit `row` and `column` + * + * @param {object} extra + * @param {string} + * extra.description: extra description. N.B we use + * a separate extra container to make it compatible with + * the compress_attributes transform. + * + * @return {object} attributes object containing {x,y} as specified + */ +exports.attributes = function(opts, extra) { + opts = opts || {}; + extra = extra || {}; + + var base = { + valType: 'info_array', + + editType: opts.editType, + items: [ + {valType: 'number', min: 0, max: 1, editType: opts.editType}, + {valType: 'number', min: 0, max: 1, editType: opts.editType} + ], + dflt: [0, 1] + }; + + var namePart = opts.name ? opts.name + ' ' : ''; + var contPart = opts.trace ? 'trace ' : 'subplot '; + var descPart = extra.description ? ' ' + extra.description : ''; + + var out = { + x: extendFlat({}, base, { + + }), + y: extendFlat({}, base, { + + }), + editType: opts.editType + }; + + if(!opts.noGridCell) { + out.row = { + valType: 'integer', + min: 0, + dflt: 0, + + editType: opts.editType, + + }; + out.column = { + valType: 'integer', + min: 0, + dflt: 0, + + editType: opts.editType, + + }; + } + + return out; +}; + +exports.defaults = function(containerOut, layout, coerce, dfltDomains) { + var dfltX = (dfltDomains && dfltDomains.x) || [0, 1]; + var dfltY = (dfltDomains && dfltDomains.y) || [0, 1]; + + var grid = layout.grid; + if(grid) { + var column = coerce('domain.column'); + if(column !== undefined) { + if(column < grid.columns) dfltX = grid._domains.x[column]; + else delete containerOut.domain.column; + } + + var row = coerce('domain.row'); + if(row !== undefined) { + if(row < grid.rows) dfltY = grid._domains.y[row]; + else delete containerOut.domain.row; + } + } + + var x = coerce('domain.x', dfltX); + var y = coerce('domain.y', dfltY); + + // don't accept bad input data + if(!(x[0] < x[1])) containerOut.domain.x = dfltX.slice(); + if(!(y[0] < y[1])) containerOut.domain.y = dfltY.slice(); +}; + +},{"../lib/extend":164}],239:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +/* + * make a font attribute group + * + * @param {object} opts + * @param {string} + * opts.description: where & how this font is used + * @param {optional bool} arrayOk: + * should each part (family, size, color) be arrayOk? default false. + * @param {string} editType: + * the editType for all pieces of this font + * @param {optional string} colorEditType: + * a separate editType just for color + * + * @return {object} attributes object containing {family, size, color} as specified + */ +module.exports = function(opts) { + var editType = opts.editType; + var colorEditType = opts.colorEditType; + if(colorEditType === undefined) colorEditType = editType; + var attrs = { + family: { + valType: 'string', + + noBlank: true, + strict: true, + editType: editType, + + }, + size: { + valType: 'number', + + min: 1, + editType: editType + }, + color: { + valType: 'color', + + editType: colorEditType + }, + editType: editType, + // blank strings so compress_attributes can remove + // TODO - that's uber hacky... better solution? + + }; + + if(opts.arrayOk) { + attrs.family.arrayOk = true; + attrs.size.arrayOk = true; + attrs.color.arrayOk = true; + } + + return attrs; +}; + +},{}],240:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + _isLinkedToArray: 'frames_entry', + + group: { + valType: 'string', + + + }, + name: { + valType: 'string', + + + }, + traces: { + valType: 'any', + + + }, + baseframe: { + valType: 'string', + + + }, + data: { + valType: 'any', + + + }, + layout: { + valType: 'any', + + + } +}; + +},{}],241:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Registry = _dereq_('../registry'); +var SUBPLOT_PATTERN = _dereq_('./cartesian/constants').SUBPLOT_PATTERN; + +/** + * Get calcdata trace(s) associated with a given subplot + * + * @param {array} calcData: as in gd.calcdata + * @param {string} type: subplot type + * @param {string} subplotId: subplot id to look for + * + * @return {array} array of calcdata traces + */ +exports.getSubplotCalcData = function(calcData, type, subplotId) { + var basePlotModule = Registry.subplotsRegistry[type]; + if(!basePlotModule) return []; + + var attr = basePlotModule.attr; + var subplotCalcData = []; + + for(var i = 0; i < calcData.length; i++) { + var calcTrace = calcData[i]; + var trace = calcTrace[0].trace; + + if(trace[attr] === subplotId) subplotCalcData.push(calcTrace); + } + + return subplotCalcData; +}; +/** + * Get calcdata trace(s) that can be plotted with a given module + * NOTE: this isn't necessarily just exactly matching trace type, + * if multiple trace types use the same plotting routine, they will be + * collected here. + * In order to not plot the same thing multiple times, we return two arrays, + * the calcdata we *will* plot with this module, and the ones we *won't* + * + * @param {array} calcdata: as in gd.calcdata + * @param {object|string|fn} arg1: + * the plotting module, or its name, or its plot method + * + * @return {array[array]} [foundCalcdata, remainingCalcdata] + */ +exports.getModuleCalcData = function(calcdata, arg1) { + var moduleCalcData = []; + var remainingCalcData = []; + + var plotMethod; + if(typeof arg1 === 'string') { + plotMethod = Registry.getModule(arg1).plot; + } else if(typeof arg1 === 'function') { + plotMethod = arg1; + } else { + plotMethod = arg1.plot; + } + if(!plotMethod) { + return [moduleCalcData, calcdata]; + } + + for(var i = 0; i < calcdata.length; i++) { + var cd = calcdata[i]; + var trace = cd[0].trace; + // N.B. + // - 'legendonly' traces do not make it past here + // - skip over 'visible' traces that got trimmed completely during calc transforms + if(trace.visible !== true || trace._length === 0) continue; + + // group calcdata trace not by 'module' (as the name of this function + // would suggest), but by 'module plot method' so that if some traces + // share the same module plot method (e.g. bar and histogram), we + // only call it one! + if(trace._module.plot === plotMethod) { + moduleCalcData.push(cd); + } else { + remainingCalcData.push(cd); + } + } + + return [moduleCalcData, remainingCalcData]; +}; + +/** + * Get the data trace(s) associated with a given subplot. + * + * @param {array} data plotly full data array. + * @param {string} type subplot type to look for. + * @param {string} subplotId subplot id to look for. + * + * @return {array} list of trace objects. + * + */ +exports.getSubplotData = function getSubplotData(data, type, subplotId) { + if(!Registry.subplotsRegistry[type]) return []; + + var attr = Registry.subplotsRegistry[type].attr; + var subplotData = []; + var trace, subplotX, subplotY; + + if(type === 'gl2d') { + var spmatch = subplotId.match(SUBPLOT_PATTERN); + subplotX = 'x' + spmatch[1]; + subplotY = 'y' + spmatch[2]; + } + + for(var i = 0; i < data.length; i++) { + trace = data[i]; + + if(type === 'gl2d' && Registry.traceIs(trace, 'gl2d')) { + if(trace[attr[0]] === subplotX && trace[attr[1]] === subplotY) { + subplotData.push(trace); + } + } else { + if(trace[attr] === subplotId) subplotData.push(trace); + } + } + + return subplotData; +}; + +},{"../registry":258,"./cartesian/constants":219}],242:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +function xformMatrix(m, v) { + var out = [0, 0, 0, 0]; + var i, j; + + for(i = 0; i < 4; ++i) { + for(j = 0; j < 4; ++j) { + out[j] += m[4 * i + j] * v[i]; + } + } + + return out; +} + +function project(camera, v) { + var p = xformMatrix(camera.projection, + xformMatrix(camera.view, + xformMatrix(camera.model, [v[0], v[1], v[2], 1]))); + return p; +} + +module.exports = project; + +},{}],243:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var fontAttrs = _dereq_('./font_attributes'); +var animationAttrs = _dereq_('./animation_attributes'); +var colorAttrs = _dereq_('../components/color/attributes'); +var padAttrs = _dereq_('./pad_attributes'); +var extendFlat = _dereq_('../lib/extend').extendFlat; + +var globalFont = fontAttrs({ + editType: 'calc', + +}); +globalFont.family.dflt = '"Open Sans", verdana, arial, sans-serif'; +globalFont.size.dflt = 12; +globalFont.color.dflt = colorAttrs.defaultLine; + +module.exports = { + font: globalFont, + title: { + text: { + valType: 'string', + + editType: 'layoutstyle', + + }, + font: fontAttrs({ + editType: 'layoutstyle', + + }), + xref: { + valType: 'enumerated', + dflt: 'container', + values: ['container', 'paper'], + + editType: 'layoutstyle', + + }, + yref: { + valType: 'enumerated', + dflt: 'container', + values: ['container', 'paper'], + + editType: 'layoutstyle', + + }, + x: { + valType: 'number', + min: 0, + max: 1, + dflt: 0.5, + + editType: 'layoutstyle', + + }, + y: { + valType: 'number', + min: 0, + max: 1, + dflt: 'auto', + + editType: 'layoutstyle', + + }, + xanchor: { + valType: 'enumerated', + dflt: 'auto', + values: ['auto', 'left', 'center', 'right'], + + editType: 'layoutstyle', + + }, + yanchor: { + valType: 'enumerated', + dflt: 'auto', + values: ['auto', 'top', 'middle', 'bottom'], + + editType: 'layoutstyle', + + }, + pad: extendFlat(padAttrs({editType: 'layoutstyle'}), { + + }), + editType: 'layoutstyle' + }, + autosize: { + valType: 'boolean', + + dflt: false, + // autosize, width, and height get special editType treatment in _relayout + // so we can handle noop resizes more efficiently + editType: 'none', + + }, + width: { + valType: 'number', + + min: 10, + dflt: 700, + editType: 'plot', + + }, + height: { + valType: 'number', + + min: 10, + dflt: 450, + editType: 'plot', + + }, + margin: { + l: { + valType: 'number', + + min: 0, + dflt: 80, + editType: 'plot', + + }, + r: { + valType: 'number', + + min: 0, + dflt: 80, + editType: 'plot', + + }, + t: { + valType: 'number', + + min: 0, + dflt: 100, + editType: 'plot', + + }, + b: { + valType: 'number', + + min: 0, + dflt: 80, + editType: 'plot', + + }, + pad: { + valType: 'number', + + min: 0, + dflt: 0, + editType: 'plot', + + }, + autoexpand: { + valType: 'boolean', + + dflt: true, + editType: 'plot', + + }, + editType: 'plot' + }, + paper_bgcolor: { + valType: 'color', + + dflt: colorAttrs.background, + editType: 'plot', + + }, + plot_bgcolor: { + // defined here, but set in cartesian.supplyLayoutDefaults + // because it needs to know if there are (2D) axes or not + valType: 'color', + + dflt: colorAttrs.background, + editType: 'layoutstyle', + + }, + separators: { + valType: 'string', + + editType: 'plot', + + }, + hidesources: { + valType: 'boolean', + + dflt: false, + editType: 'plot', + + }, + showlegend: { + // handled in legend.supplyLayoutDefaults + // but included here because it's not in the legend object + valType: 'boolean', + + editType: 'legend', + + }, + colorway: { + valType: 'colorlist', + dflt: colorAttrs.defaults, + + editType: 'calc', + + }, + datarevision: { + valType: 'any', + + editType: 'calc', + + }, + uirevision: { + valType: 'any', + + editType: 'none', + + }, + editrevision: { + valType: 'any', + + editType: 'none', + + }, + selectionrevision: { + valType: 'any', + + editType: 'none', + + }, + template: { + valType: 'any', + + editType: 'calc', + + }, + modebar: { + orientation: { + valType: 'enumerated', + values: ['v', 'h'], + dflt: 'h', + + editType: 'modebar', + + }, + bgcolor: { + valType: 'color', + + editType: 'modebar', + + }, + color: { + valType: 'color', + + editType: 'modebar', + + }, + activecolor: { + valType: 'color', + + editType: 'modebar', + + }, + uirevision: { + valType: 'any', + + editType: 'none', + + }, + editType: 'modebar' + }, + + meta: { + valType: 'any', + arrayOk: true, + + editType: 'plot', + + }, + + transition: extendFlat({}, animationAttrs.transition, { + + editType: 'none' + }), + _deprecated: { + title: { + valType: 'string', + + editType: 'layoutstyle', + + }, + titlefont: fontAttrs({ + editType: 'layoutstyle', + + }) + } +}; + +},{"../components/color/attributes":50,"../lib/extend":164,"./animation_attributes":208,"./font_attributes":239,"./pad_attributes":244}],244:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +/** + * Creates a set of padding attributes. + * + * @param {object} opts + * @param {string} editType: + * the editType for all pieces of this padding definition + * + * @return {object} attributes object containing {t, r, b, l} as specified + */ +module.exports = function(opts) { + var editType = opts.editType; + return { + t: { + valType: 'number', + dflt: 0, + + editType: editType, + + }, + r: { + valType: 'number', + dflt: 0, + + editType: editType, + + }, + b: { + valType: 'number', + dflt: 0, + + editType: editType, + + }, + l: { + valType: 'number', + dflt: 0, + + editType: editType, + + }, + editType: editType + }; +}; + +},{}],245:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); +var isNumeric = _dereq_('fast-isnumeric'); + +var Registry = _dereq_('../registry'); +var PlotSchema = _dereq_('../plot_api/plot_schema'); +var Template = _dereq_('../plot_api/plot_template'); +var Lib = _dereq_('../lib'); +var Color = _dereq_('../components/color'); +var BADNUM = _dereq_('../constants/numerical').BADNUM; + +var axisIDs = _dereq_('./cartesian/axis_ids'); + +var animationAttrs = _dereq_('./animation_attributes'); +var frameAttrs = _dereq_('./frame_attributes'); + +var getModuleCalcData = _dereq_('../plots/get_data').getModuleCalcData; + +var relinkPrivateKeys = Lib.relinkPrivateKeys; +var _ = Lib._; + +var plots = module.exports = {}; + +// Expose registry methods on Plots for backward-compatibility +Lib.extendFlat(plots, Registry); + +plots.attributes = _dereq_('./attributes'); +plots.attributes.type.values = plots.allTypes; +plots.fontAttrs = _dereq_('./font_attributes'); +plots.layoutAttributes = _dereq_('./layout_attributes'); + +// TODO make this a plot attribute? +plots.fontWeight = 'normal'; + +var transformsRegistry = plots.transformsRegistry; + +var commandModule = _dereq_('./command'); +plots.executeAPICommand = commandModule.executeAPICommand; +plots.computeAPICommandBindings = commandModule.computeAPICommandBindings; +plots.manageCommandObserver = commandModule.manageCommandObserver; +plots.hasSimpleAPICommandBindings = commandModule.hasSimpleAPICommandBindings; + +// in some cases the browser doesn't seem to know how big +// the text is at first, so it needs to draw it, +// then wait a little, then draw it again +plots.redrawText = function(gd) { + gd = Lib.getGraphDiv(gd); + + var fullLayout = gd._fullLayout || {}; + var hasPolar = fullLayout._has && fullLayout._has('polar'); + var hasLegacyPolar = !hasPolar && gd.data && gd.data[0] && gd.data[0].r; + + // do not work if polar is present + if(hasLegacyPolar) return; + + return new Promise(function(resolve) { + setTimeout(function() { + Registry.getComponentMethod('annotations', 'draw')(gd); + Registry.getComponentMethod('legend', 'draw')(gd); + Registry.getComponentMethod('colorbar', 'draw')(gd); + resolve(plots.previousPromises(gd)); + }, 300); + }); +}; + +// resize plot about the container size +plots.resize = function(gd) { + gd = Lib.getGraphDiv(gd); + + return new Promise(function(resolve, reject) { + if(!gd || Lib.isHidden(gd)) { + reject(new Error('Resize must be passed a displayed plot div element.')); + } + + if(gd._redrawTimer) clearTimeout(gd._redrawTimer); + + gd._redrawTimer = setTimeout(function() { + // return if there is nothing to resize or is hidden + if(!gd.layout || (gd.layout.width && gd.layout.height) || Lib.isHidden(gd)) { + resolve(gd); + return; + } + + delete gd.layout.width; + delete gd.layout.height; + + // autosizing doesn't count as a change that needs saving + var oldchanged = gd.changed; + + // nor should it be included in the undo queue + gd.autoplay = true; + + Registry.call('relayout', gd, {autosize: true}).then(function() { + gd.changed = oldchanged; + resolve(gd); + }); + }, 100); + }); +}; + + +// for use in Lib.syncOrAsync, check if there are any +// pending promises in this plot and wait for them +plots.previousPromises = function(gd) { + if((gd._promises || []).length) { + return Promise.all(gd._promises) + .then(function() { gd._promises = []; }); + } +}; + +/** + * Adds the 'Edit chart' link. + * Note that now Plotly.plot() calls this so it can regenerate whenever it replots + * + * Add source links to your graph inside the 'showSources' config argument. + */ +plots.addLinks = function(gd) { + // Do not do anything if showLink and showSources are not set to true in config + if(!gd._context.showLink && !gd._context.showSources) return; + + var fullLayout = gd._fullLayout; + + var linkContainer = Lib.ensureSingle(fullLayout._paper, 'text', 'js-plot-link-container', function(s) { + s.style({ + 'font-family': '"Open Sans", Arial, sans-serif', + 'font-size': '12px', + 'fill': Color.defaultLine, + 'pointer-events': 'all' + }) + .each(function() { + var links = d3.select(this); + links.append('tspan').classed('js-link-to-tool', true); + links.append('tspan').classed('js-link-spacer', true); + links.append('tspan').classed('js-sourcelinks', true); + }); + }); + + // The text node inside svg + var text = linkContainer.node(); + var attrs = {y: fullLayout._paper.attr('height') - 9}; + + // If text's width is bigger than the layout + // Check that text is a child node or document.body + // because otherwise IE/Edge might throw an exception + // when calling getComputedTextLength(). + // Apparently offsetParent is null for invisibles. + if(document.body.contains(text) && text.getComputedTextLength() >= (fullLayout.width - 20)) { + // Align the text at the left + attrs['text-anchor'] = 'start'; + attrs.x = 5; + } else { + // Align the text at the right + attrs['text-anchor'] = 'end'; + attrs.x = fullLayout._paper.attr('width') - 7; + } + + linkContainer.attr(attrs); + + var toolspan = linkContainer.select('.js-link-to-tool'); + var spacespan = linkContainer.select('.js-link-spacer'); + var sourcespan = linkContainer.select('.js-sourcelinks'); + + if(gd._context.showSources) gd._context.showSources(gd); + + // 'view in plotly' link for embedded plots + if(gd._context.showLink) positionPlayWithData(gd, toolspan); + + // separator if we have both sources and tool link + spacespan.text((toolspan.text() && sourcespan.text()) ? ' - ' : ''); +}; + +// note that now this function is only adding the brand in +// iframes and 3rd-party apps +function positionPlayWithData(gd, container) { + container.text(''); + var link = container.append('a') + .attr({ + 'xlink:xlink:href': '#', + 'class': 'link--impt link--embedview', + 'font-weight': 'bold' + }) + .text(gd._context.linkText + ' ' + String.fromCharCode(187)); + + if(gd._context.sendData) { + link.on('click', function() { + plots.sendDataToCloud(gd); + }); + } else { + var path = window.location.pathname.split('/'); + var query = window.location.search; + link.attr({ + 'xlink:xlink:show': 'new', + 'xlink:xlink:href': '/' + path[2].split('.')[0] + '/' + path[1] + query + }); + } +} + +plots.sendDataToCloud = function(gd) { + gd.emit('plotly_beforeexport'); + + var baseUrl = (window.PLOTLYENV || {}).BASE_URL || gd._context.plotlyServerURL; + + var hiddenformDiv = d3.select(gd) + .append('div') + .attr('id', 'hiddenform') + .style('display', 'none'); + + var hiddenform = hiddenformDiv + .append('form') + .attr({ + action: baseUrl + '/external', + method: 'post', + target: '_blank' + }); + + var hiddenformInput = hiddenform + .append('input') + .attr({ + type: 'text', + name: 'data' + }); + + hiddenformInput.node().value = plots.graphJson(gd, false, 'keepdata'); + hiddenform.node().submit(); + hiddenformDiv.remove(); + + gd.emit('plotly_afterexport'); + return false; +}; + +var d3FormatKeys = [ + 'days', 'shortDays', 'months', 'shortMonths', 'periods', + 'dateTime', 'date', 'time', + 'decimal', 'thousands', 'grouping', 'currency' +]; + +var extraFormatKeys = [ + 'year', 'month', 'dayMonth', 'dayMonthYear' +]; + +/* + * Fill in default values + * @param {DOM element} gd + * @param {object} opts + * @param {boolean} opts.skipUpdateCalc: normally if the existing gd.calcdata looks + * compatible with the new gd._fullData we finish by linking the new _fullData traces + * to the old gd.calcdata, so it's correctly set if we're not going to recalc. But also, + * if there are calcTransforms on the trace, we first remap data arrays from the old full + * trace into the new one. Use skipUpdateCalc to defer this (needed by Plotly.react) + * + * gd.data, gd.layout: + * are precisely what the user specified (except as modified by cleanData/cleanLayout), + * these fields shouldn't be modified (except for filling in some auto values) + * nor used directly after the supply defaults step. + * + * gd._fullData, gd._fullLayout: + * are complete descriptions of how to draw the plot, + * use these fields in all required computations. + * + * gd._fullLayout._modules + * is a list of all the trace modules required to draw the plot. + * + * gd._fullLayout._visibleModules + * subset of _modules, a list of modules corresponding to visible:true traces. + * + * gd._fullLayout._basePlotModules + * is a list of all the plot modules required to draw the plot. + * + * gd._fullLayout._transformModules + * is a list of all the transform modules invoked. + * + */ +plots.supplyDefaults = function(gd, opts) { + var skipUpdateCalc = opts && opts.skipUpdateCalc; + var oldFullLayout = gd._fullLayout || {}; + + if(oldFullLayout._skipDefaults) { + delete oldFullLayout._skipDefaults; + return; + } + + var newFullLayout = gd._fullLayout = {}; + var newLayout = gd.layout || {}; + + var oldFullData = gd._fullData || []; + var newFullData = gd._fullData = []; + var newData = gd.data || []; + + var oldCalcdata = gd.calcdata || []; + + var context = gd._context || {}; + + var i; + + // Create all the storage space for frames, but only if doesn't already exist + if(!gd._transitionData) plots.createTransitionData(gd); + + // So we only need to do this once (and since we have gd here) + // get the translated placeholder titles. + // These ones get used as default values so need to be known at supplyDefaults + // others keep their blank defaults but render the placeholder as desired later + // TODO: make these work the same way, only inserting the placeholder text at draw time? + // The challenge is that this has slightly different behavior right now in editable mode: + // using the placeholder as default makes this text permanently (but lightly) visible, + // but explicit '' for these titles gives you a placeholder that's hidden until you mouse + // over it - so you're not distracted by it if you really don't want a title, but if you do + // and you're new to plotly you may not be able to find it. + // When editable=false the two behave the same, no title is drawn. + newFullLayout._dfltTitle = { + plot: _(gd, 'Click to enter Plot title'), + x: _(gd, 'Click to enter X axis title'), + y: _(gd, 'Click to enter Y axis title'), + colorbar: _(gd, 'Click to enter Colorscale title'), + annotation: _(gd, 'new text') + }; + newFullLayout._traceWord = _(gd, 'trace'); + + var formatObj = getFormatObj(gd, d3FormatKeys); + + // stash the token from context so mapbox subplots can use it as default + newFullLayout._mapboxAccessToken = context.mapboxAccessToken; + + // first fill in what we can of layout without looking at data + // because fullData needs a few things from layout + if(oldFullLayout._initialAutoSizeIsDone) { + // coerce the updated layout while preserving width and height + var oldWidth = oldFullLayout.width; + var oldHeight = oldFullLayout.height; + + plots.supplyLayoutGlobalDefaults(newLayout, newFullLayout, formatObj); + + if(!newLayout.width) newFullLayout.width = oldWidth; + if(!newLayout.height) newFullLayout.height = oldHeight; + plots.sanitizeMargins(newFullLayout); + } else { + // coerce the updated layout and autosize if needed + plots.supplyLayoutGlobalDefaults(newLayout, newFullLayout, formatObj); + + var missingWidthOrHeight = (!newLayout.width || !newLayout.height); + var autosize = newFullLayout.autosize; + var autosizable = context.autosizable; + var initialAutoSize = missingWidthOrHeight && (autosize || autosizable); + + if(initialAutoSize) plots.plotAutoSize(gd, newLayout, newFullLayout); + else if(missingWidthOrHeight) plots.sanitizeMargins(newFullLayout); + + // for backwards-compatibility with Plotly v1.x.x + if(!autosize && missingWidthOrHeight) { + newLayout.width = newFullLayout.width; + newLayout.height = newFullLayout.height; + } + } + + newFullLayout._d3locale = getFormatter(formatObj, newFullLayout.separators); + newFullLayout._extraFormat = getFormatObj(gd, extraFormatKeys); + + newFullLayout._initialAutoSizeIsDone = true; + + // keep track of how many traces are inputted + newFullLayout._dataLength = newData.length; + + // clear the lists of trace and baseplot modules, and subplots + newFullLayout._modules = []; + newFullLayout._visibleModules = []; + newFullLayout._basePlotModules = []; + var subplots = newFullLayout._subplots = emptySubplotLists(); + + // initialize axis and subplot hash objects for splom-generated grids + var splomAxes = newFullLayout._splomAxes = {x: {}, y: {}}; + var splomSubplots = newFullLayout._splomSubplots = {}; + // initialize splom grid defaults + newFullLayout._splomGridDflt = {}; + + // for stacked area traces to share config across traces + newFullLayout._scatterStackOpts = {}; + // for the first scatter trace on each subplot (so it knows tonext->tozero) + newFullLayout._firstScatter = {}; + // for grouped bar/box/violin trace to share config across traces + newFullLayout._alignmentOpts = {}; + // track color axes referenced in the data + newFullLayout._colorAxes = {}; + + // for traces to request a default rangeslider on their x axes + // eg set `_requestRangeslider.x2 = true` for xaxis2 + newFullLayout._requestRangeslider = {}; + + // pull uids from old data to use as new defaults + newFullLayout._traceUids = getTraceUids(oldFullData, newData); + + // then do the data + newFullLayout._globalTransforms = (gd._context || {}).globalTransforms; + plots.supplyDataDefaults(newData, newFullData, newLayout, newFullLayout); + + // redo grid size defaults with info about splom x/y axes, + // and fill in generated cartesian axes and subplots + var splomXa = Object.keys(splomAxes.x); + var splomYa = Object.keys(splomAxes.y); + if(splomXa.length > 1 && splomYa.length > 1) { + Registry.getComponentMethod('grid', 'sizeDefaults')(newLayout, newFullLayout); + + for(i = 0; i < splomXa.length; i++) { + Lib.pushUnique(subplots.xaxis, splomXa[i]); + } + for(i = 0; i < splomYa.length; i++) { + Lib.pushUnique(subplots.yaxis, splomYa[i]); + } + for(var k in splomSubplots) { + Lib.pushUnique(subplots.cartesian, k); + } + } + + // attach helper method to check whether a plot type is present on graph + newFullLayout._has = plots._hasPlotType.bind(newFullLayout); + + if(oldFullData.length === newFullData.length) { + for(i = 0; i < newFullData.length; i++) { + relinkPrivateKeys(newFullData[i], oldFullData[i]); + } + } + + // finally, fill in the pieces of layout that may need to look at data + plots.supplyLayoutModuleDefaults(newLayout, newFullLayout, newFullData, gd._transitionData); + + // Special cases that introduce interactions between traces. + // This is after relinkPrivateKeys so we can use those in crossTraceDefaults + // and after layout module defaults, so we can use eg barmode + var _modules = newFullLayout._visibleModules; + var crossTraceDefaultsFuncs = []; + for(i = 0; i < _modules.length; i++) { + var funci = _modules[i].crossTraceDefaults; + // some trace types share crossTraceDefaults (ie histogram2d, histogram2dcontour) + if(funci) Lib.pushUnique(crossTraceDefaultsFuncs, funci); + } + for(i = 0; i < crossTraceDefaultsFuncs.length; i++) { + crossTraceDefaultsFuncs[i](newFullData, newFullLayout); + } + + // turn on flag to optimize large splom-only graphs + // mostly by omitting SVG layers during Cartesian.drawFramework + newFullLayout._hasOnlyLargeSploms = ( + newFullLayout._basePlotModules.length === 1 && + newFullLayout._basePlotModules[0].name === 'splom' && + splomXa.length > 15 && + splomYa.length > 15 && + newFullLayout.shapes.length === 0 && + newFullLayout.images.length === 0 + ); + + // TODO remove in v2.0.0 + // add has-plot-type refs to fullLayout for backward compatibility + newFullLayout._hasCartesian = newFullLayout._has('cartesian'); + newFullLayout._hasGeo = newFullLayout._has('geo'); + newFullLayout._hasGL3D = newFullLayout._has('gl3d'); + newFullLayout._hasGL2D = newFullLayout._has('gl2d'); + newFullLayout._hasTernary = newFullLayout._has('ternary'); + newFullLayout._hasPie = newFullLayout._has('pie'); + + // relink / initialize subplot axis objects + plots.linkSubplots(newFullData, newFullLayout, oldFullData, oldFullLayout); + + // clean subplots and other artifacts from previous plot calls + plots.cleanPlot(newFullData, newFullLayout, oldFullData, oldFullLayout); + + // clear selection outline until we implement persistent selection, + // don't clear them though when drag handlers (e.g. listening to + // `plotly_selecting`) update the graph. + // we should try to come up with a better solution when implementing + // https://github.com/plotly/plotly.js/issues/1851 + if(oldFullLayout._zoomlayer && !gd._dragging) { + oldFullLayout._zoomlayer.selectAll('.select-outline').remove(); + } + + + // fill in meta helpers + fillMetaTextHelpers(newFullData, newFullLayout); + + // relink functions and _ attributes to promote consistency between plots + relinkPrivateKeys(newFullLayout, oldFullLayout); + + // colorscale crossTraceDefaults needs newFullLayout with relinked keys + Registry.getComponentMethod('colorscale', 'crossTraceDefaults')(newFullData, newFullLayout); + + // For persisting GUI-driven changes in layout + // _preGUI and _tracePreGUI were already copied over in relinkPrivateKeys + if(!newFullLayout._preGUI) newFullLayout._preGUI = {}; + // track trace GUI changes by uid rather than by trace index + if(!newFullLayout._tracePreGUI) newFullLayout._tracePreGUI = {}; + var tracePreGUI = newFullLayout._tracePreGUI; + var uids = {}; + var uid; + for(uid in tracePreGUI) uids[uid] = 'old'; + for(i = 0; i < newFullData.length; i++) { + uid = newFullData[i]._fullInput.uid; + if(!uids[uid]) tracePreGUI[uid] = {}; + uids[uid] = 'new'; + } + for(uid in uids) { + if(uids[uid] === 'old') delete tracePreGUI[uid]; + } + + // set up containers for margin calculations + initMargins(newFullLayout); + + // collect and do some initial calculations for rangesliders + Registry.getComponentMethod('rangeslider', 'makeData')(newFullLayout); + + // update object references in calcdata + if(!skipUpdateCalc && oldCalcdata.length === newFullData.length) { + plots.supplyDefaultsUpdateCalc(oldCalcdata, newFullData); + } +}; + +plots.supplyDefaultsUpdateCalc = function(oldCalcdata, newFullData) { + for(var i = 0; i < newFullData.length; i++) { + var newTrace = newFullData[i]; + var cd0 = (oldCalcdata[i] || [])[0]; + if(cd0 && cd0.trace) { + var oldTrace = cd0.trace; + if(oldTrace._hasCalcTransform) { + var arrayAttrs = oldTrace._arrayAttrs; + var j, astr, oldArrayVal; + + for(j = 0; j < arrayAttrs.length; j++) { + astr = arrayAttrs[j]; + oldArrayVal = Lib.nestedProperty(oldTrace, astr).get().slice(); + Lib.nestedProperty(newTrace, astr).set(oldArrayVal); + } + } + cd0.trace = newTrace; + } + } +}; + +/** + * Create a list of uid strings satisfying (in this order of importance): + * 1. all unique, all strings + * 2. matches input uids if provided + * 3. matches previous data uids + */ +function getTraceUids(oldFullData, newData) { + var len = newData.length; + var oldFullInput = []; + var i, prevFullInput; + for(i = 0; i < oldFullData.length; i++) { + var thisFullInput = oldFullData[i]._fullInput; + if(thisFullInput !== prevFullInput) oldFullInput.push(thisFullInput); + prevFullInput = thisFullInput; + } + var oldLen = oldFullInput.length; + var out = new Array(len); + var seenUids = {}; + + function setUid(uid, i) { + out[i] = uid; + seenUids[uid] = 1; + } + + function tryUid(uid, i) { + if(uid && typeof uid === 'string' && !seenUids[uid]) { + setUid(uid, i); + return true; + } + } + + for(i = 0; i < len; i++) { + var newUid = newData[i].uid; + if(typeof newUid === 'number') newUid = String(newUid); + + if(tryUid(newUid, i)) continue; + if(i < oldLen && tryUid(oldFullInput[i].uid, i)) continue; + setUid(Lib.randstr(seenUids), i); + } + + return out; +} + +/** + * Make a container for collecting subplots we need to display. + * + * Finds all subplot types we need to enumerate once and caches it, + * but makes a new output object each time. + * Single-trace subplots (which have no `id`) such as pie, table, etc + * do not need to be collected because we just draw all visible traces. + */ +function emptySubplotLists() { + var collectableSubplotTypes = Registry.collectableSubplotTypes; + var out = {}; + var i, j; + + if(!collectableSubplotTypes) { + collectableSubplotTypes = []; + + var subplotsRegistry = Registry.subplotsRegistry; + + for(var subplotType in subplotsRegistry) { + var subplotModule = subplotsRegistry[subplotType]; + var subplotAttr = subplotModule.attr; + + if(subplotAttr) { + collectableSubplotTypes.push(subplotType); + + // special case, currently just for cartesian: + // we need to enumerate axes, not just subplots + if(Array.isArray(subplotAttr)) { + for(j = 0; j < subplotAttr.length; j++) { + Lib.pushUnique(collectableSubplotTypes, subplotAttr[j]); + } + } + } + } + } + + for(i = 0; i < collectableSubplotTypes.length; i++) { + out[collectableSubplotTypes[i]] = []; + } + return out; +} + +/** + * getFormatObj: use _context to get the format object from locale. + * Used to get d3.locale argument object and extraFormat argument object + * + * Regarding d3.locale argument : + * decimal and thousands can be overridden later by layout.separators + * grouping and currency are not presently used by our automatic number + * formatting system but can be used by custom formats. + * + * @returns {object} d3.locale format object + */ +function getFormatObj(gd, formatKeys) { + var locale = gd._context.locale; + if(!locale) locale === 'en-US'; + + var formatDone = false; + var formatObj = {}; + + function includeFormat(newFormat) { + var formatFinished = true; + for(var i = 0; i < formatKeys.length; i++) { + var formatKey = formatKeys[i]; + if(!formatObj[formatKey]) { + if(newFormat[formatKey]) { + formatObj[formatKey] = newFormat[formatKey]; + } else formatFinished = false; + } + } + if(formatFinished) formatDone = true; + } + + // same as localize, look for format parts in each format spec in the chain + for(var i = 0; i < 2; i++) { + var locales = gd._context.locales; + for(var j = 0; j < 2; j++) { + var formatj = (locales[locale] || {}).format; + if(formatj) { + includeFormat(formatj); + if(formatDone) break; + } + locales = Registry.localeRegistry; + } + + var baseLocale = locale.split('-')[0]; + if(formatDone || baseLocale === locale) break; + locale = baseLocale; + } + + // lastly pick out defaults from english (non-US, as DMY is so much more common) + if(!formatDone) includeFormat(Registry.localeRegistry.en.format); + + return formatObj; +} + +/** + * getFormatter: combine the final separators with the locale formatting object + * we pulled earlier to generate number and time formatters + * TODO: remove separators in v2, only use locale, so we don't need this step? + * + * @param {object} formatObj: d3.locale format object + * @param {string} separators: length-2 string to override decimal and thousands + * separators in number formatting + * + * @returns {object} {numberFormat, timeFormat} d3 formatter factory functions + * for numbers and time + */ +function getFormatter(formatObj, separators) { + formatObj.decimal = separators.charAt(0); + formatObj.thousands = separators.charAt(1); + + return d3.locale(formatObj); +} + +function fillMetaTextHelpers(newFullData, newFullLayout) { + var _meta; + var meta4data = []; + + if(newFullLayout.meta) { + _meta = newFullLayout._meta = { + meta: newFullLayout.meta, + layout: {meta: newFullLayout.meta} + }; + } + + for(var i = 0; i < newFullData.length; i++) { + var trace = newFullData[i]; + + if(trace.meta) { + meta4data[trace.index] = trace._meta = {meta: trace.meta}; + } else if(newFullLayout.meta) { + trace._meta = {meta: newFullLayout.meta}; + } + if(newFullLayout.meta) { + trace._meta.layout = {meta: newFullLayout.meta}; + } + } + + if(meta4data.length) { + if(!_meta) { + _meta = newFullLayout._meta = {}; + } + _meta.data = meta4data; + } +} + +// Create storage for all of the data related to frames and transitions: +plots.createTransitionData = function(gd) { + // Set up the default keyframe if it doesn't exist: + if(!gd._transitionData) { + gd._transitionData = {}; + } + + if(!gd._transitionData._frames) { + gd._transitionData._frames = []; + } + + if(!gd._transitionData._frameHash) { + gd._transitionData._frameHash = {}; + } + + if(!gd._transitionData._counter) { + gd._transitionData._counter = 0; + } + + if(!gd._transitionData._interruptCallbacks) { + gd._transitionData._interruptCallbacks = []; + } +}; + +// helper function to be bound to fullLayout to check +// whether a certain plot type is present on plot +// or trace has a category +plots._hasPlotType = function(category) { + var i; + + // check base plot modules + var basePlotModules = this._basePlotModules || []; + for(i = 0; i < basePlotModules.length; i++) { + if(basePlotModules[i].name === category) return true; + } + + // check trace modules (including non-visible:true) + var modules = this._modules || []; + for(i = 0; i < modules.length; i++) { + var name = modules[i].name; + if(name === category) return true; + // N.B. this is modules[i] along with 'categories' as a hash object + var _module = Registry.modules[name]; + if(_module && _module.categories[category]) return true; + } + + return false; +}; + +plots.cleanPlot = function(newFullData, newFullLayout, oldFullData, oldFullLayout) { + var i, j; + + var basePlotModules = oldFullLayout._basePlotModules || []; + for(i = 0; i < basePlotModules.length; i++) { + var _module = basePlotModules[i]; + + if(_module.clean) { + _module.clean(newFullData, newFullLayout, oldFullData, oldFullLayout); + } + } + + var hadGl = oldFullLayout._has && oldFullLayout._has('gl'); + var hasGl = newFullLayout._has && newFullLayout._has('gl'); + + if(hadGl && !hasGl) { + if(oldFullLayout._glcontainer !== undefined) { + oldFullLayout._glcontainer.selectAll('.gl-canvas').remove(); + oldFullLayout._glcontainer.selectAll('.no-webgl').remove(); + oldFullLayout._glcanvas = null; + } + } + + var hasInfoLayer = !!oldFullLayout._infolayer; + + oldLoop: + for(i = 0; i < oldFullData.length; i++) { + var oldTrace = oldFullData[i]; + var oldUid = oldTrace.uid; + + for(j = 0; j < newFullData.length; j++) { + var newTrace = newFullData[j]; + + if(oldUid === newTrace.uid) continue oldLoop; + } + + // clean old colorbars + if(hasInfoLayer) { + oldFullLayout._infolayer.select('.cb' + oldUid).remove(); + } + } +}; + +plots.linkSubplots = function(newFullData, newFullLayout, oldFullData, oldFullLayout) { + var i, j; + + var oldSubplots = oldFullLayout._plots || {}; + var newSubplots = newFullLayout._plots = {}; + var newSubplotList = newFullLayout._subplots; + + var mockGd = { + _fullData: newFullData, + _fullLayout: newFullLayout + }; + + var ids = newSubplotList.cartesian.concat(newSubplotList.gl2d || []); + + for(i = 0; i < ids.length; i++) { + var id = ids[i]; + var oldSubplot = oldSubplots[id]; + var xaxis = axisIDs.getFromId(mockGd, id, 'x'); + var yaxis = axisIDs.getFromId(mockGd, id, 'y'); + var plotinfo; + + // link or create subplot object + if(oldSubplot) { + plotinfo = newSubplots[id] = oldSubplot; + } else { + plotinfo = newSubplots[id] = {}; + plotinfo.id = id; + } + + // add these axis ids to each others' subplot lists + xaxis._counterAxes.push(yaxis._id); + yaxis._counterAxes.push(xaxis._id); + xaxis._subplotsWith.push(id); + yaxis._subplotsWith.push(id); + + // update x and y axis layout object refs + plotinfo.xaxis = xaxis; + plotinfo.yaxis = yaxis; + + // By default, we clip at the subplot level, + // but if one trace on a given subplot has *cliponaxis* set to false, + // we need to clip at the trace module layer level; + // find this out here, once of for all. + plotinfo._hasClipOnAxisFalse = false; + + for(j = 0; j < newFullData.length; j++) { + var trace = newFullData[j]; + + if( + trace.xaxis === plotinfo.xaxis._id && + trace.yaxis === plotinfo.yaxis._id && + trace.cliponaxis === false + ) { + plotinfo._hasClipOnAxisFalse = true; + break; + } + } + } + + // while we're at it, link overlaying axes to their main axes and + // anchored axes to the axes they're anchored to + var axList = axisIDs.list(mockGd, null, true); + var ax; + for(i = 0; i < axList.length; i++) { + ax = axList[i]; + var mainAx = null; + + if(ax.overlaying) { + mainAx = axisIDs.getFromId(mockGd, ax.overlaying); + + // you cannot overlay an axis that's already overlaying another + if(mainAx && mainAx.overlaying) { + ax.overlaying = false; + mainAx = null; + } + } + ax._mainAxis = mainAx || ax; + + /* + * For now force overlays to overlay completely... so they + * can drag together correctly and share backgrounds. + * Later perhaps we make separate axis domain and + * tick/line domain or something, so they can still share + * the (possibly larger) dragger and background but don't + * have to both be drawn over that whole domain + */ + if(mainAx) ax.domain = mainAx.domain.slice(); + + ax._anchorAxis = ax.anchor === 'free' ? + null : + axisIDs.getFromId(mockGd, ax.anchor); + } + + // finally, we can find the main subplot for each axis + // (on which the ticks & labels are drawn) + for(i = 0; i < axList.length; i++) { + ax = axList[i]; + ax._counterAxes.sort(axisIDs.idSort); + ax._subplotsWith.sort(Lib.subplotSort); + ax._mainSubplot = findMainSubplot(ax, newFullLayout); + + // find "full" domain span of counter axes, + // this loop can be costly, so only compute it when required + if(ax._counterAxes.length && ( + (ax.spikemode && ax.spikemode.indexOf('across') !== -1) || + (ax.automargin && ax.mirror && ax.anchor !== 'free') || + Registry.getComponentMethod('rangeslider', 'isVisible')(ax) + )) { + var min = 1; + var max = 0; + for(j = 0; j < ax._counterAxes.length; j++) { + var ax2 = axisIDs.getFromId(mockGd, ax._counterAxes[j]); + min = Math.min(min, ax2.domain[0]); + max = Math.max(max, ax2.domain[1]); + } + if(min < max) { + ax._counterDomainMin = min; + ax._counterDomainMax = max; + } + } + } +}; + +function findMainSubplot(ax, fullLayout) { + var mockGd = {_fullLayout: fullLayout}; + + var isX = ax._id.charAt(0) === 'x'; + var anchorAx = ax._mainAxis._anchorAxis; + var mainSubplotID = ''; + var nextBestMainSubplotID = ''; + var anchorID = ''; + + // First try the main ID with the anchor + if(anchorAx) { + anchorID = anchorAx._mainAxis._id; + mainSubplotID = isX ? (ax._id + anchorID) : (anchorID + ax._id); + } + + // Then look for a subplot with the counteraxis overlaying the anchor + // If that fails just use the first subplot including this axis + if(!mainSubplotID || !fullLayout._plots[mainSubplotID]) { + mainSubplotID = ''; + + var counterIDs = ax._counterAxes; + for(var j = 0; j < counterIDs.length; j++) { + var counterPart = counterIDs[j]; + var id = isX ? (ax._id + counterPart) : (counterPart + ax._id); + if(!nextBestMainSubplotID) nextBestMainSubplotID = id; + var counterAx = axisIDs.getFromId(mockGd, counterPart); + if(anchorID && counterAx.overlaying === anchorID) { + mainSubplotID = id; + break; + } + } + } + + return mainSubplotID || nextBestMainSubplotID; +} + +// This function clears any trace attributes with valType: color and +// no set dflt filed in the plot schema. This is needed because groupby (which +// is the only transform for which this currently applies) supplies parent +// trace defaults, then expanded trace defaults. The result is that `null` +// colors are default-supplied and inherited as a color instead of a null. +// The result is that expanded trace default colors have no effect, with +// the final result that groups are indistinguishable. This function clears +// those colors so that individual groupby groups get unique colors. +plots.clearExpandedTraceDefaultColors = function(trace) { + var colorAttrs, path, i; + + // This uses weird closure state in order to satisfy the linter rule + // that we can't create functions in a loop. + function locateColorAttrs(attr, attrName, attrs, level) { + path[level] = attrName; + path.length = level + 1; + if(attr.valType === 'color' && attr.dflt === undefined) { + colorAttrs.push(path.join('.')); + } + } + + path = []; + + // Get the cached colorAttrs: + colorAttrs = trace._module._colorAttrs; + + // Or else compute and cache the colorAttrs on the module: + if(!colorAttrs) { + trace._module._colorAttrs = colorAttrs = []; + PlotSchema.crawl( + trace._module.attributes, + locateColorAttrs + ); + } + + for(i = 0; i < colorAttrs.length; i++) { + var origprop = Lib.nestedProperty(trace, '_input.' + colorAttrs[i]); + + if(!origprop.get()) { + Lib.nestedProperty(trace, colorAttrs[i]).set(null); + } + } +}; + + +plots.supplyDataDefaults = function(dataIn, dataOut, layout, fullLayout) { + var modules = fullLayout._modules; + var visibleModules = fullLayout._visibleModules; + var basePlotModules = fullLayout._basePlotModules; + var cnt = 0; + var colorCnt = 0; + + var i, fullTrace, trace; + + fullLayout._transformModules = []; + + function pushModule(fullTrace) { + dataOut.push(fullTrace); + + var _module = fullTrace._module; + if(!_module) return; + + Lib.pushUnique(modules, _module); + if(fullTrace.visible === true) Lib.pushUnique(visibleModules, _module); + Lib.pushUnique(basePlotModules, fullTrace._module.basePlotModule); + cnt++; + + // TODO: do we really want color not to increment for explicitly invisible traces? + // This logic is weird, but matches previous behavior: traces that you explicitly + // set to visible:false do not increment the color, but traces WE determine to be + // empty or invalid (and thus set to visible:false) DO increment color. + // I kind of think we should just let all traces increment color, visible or not. + // see mock: axes-autotype-empty vs. a test of restyling visible: false that + // I can't find right now... + if(fullTrace._input.visible !== false) colorCnt++; + } + + var carpetIndex = {}; + var carpetDependents = []; + var dataTemplate = (layout.template || {}).data || {}; + var templater = Template.traceTemplater(dataTemplate); + + for(i = 0; i < dataIn.length; i++) { + trace = dataIn[i]; + + // reuse uid we may have pulled out of oldFullData + // Note: templater supplies trace type + fullTrace = templater.newTrace(trace); + fullTrace.uid = fullLayout._traceUids[i]; + plots.supplyTraceDefaults(trace, fullTrace, colorCnt, fullLayout, i); + + fullTrace.index = i; + fullTrace._input = trace; + fullTrace._expandedIndex = cnt; + + if(fullTrace.transforms && fullTrace.transforms.length) { + var sdInvisible = trace.visible !== false && fullTrace.visible === false; + + var expandedTraces = applyTransforms(fullTrace, dataOut, layout, fullLayout); + + for(var j = 0; j < expandedTraces.length; j++) { + var expandedTrace = expandedTraces[j]; + + // No further templating during transforms. + var fullExpandedTrace = { + _template: fullTrace._template, + type: fullTrace.type, + // set uid using parent uid and expanded index + // to promote consistency between update calls + uid: fullTrace.uid + j + }; + + // If the first supplyDefaults created `visible: false`, + // clear it before running supplyDefaults a second time, + // because sometimes there are items we still want to coerce + // inside trace modules before determining that the trace is + // again `visible: false`, for example partial visibilities + // in `splom` traces. + if(sdInvisible && expandedTrace.visible === false) { + delete expandedTrace.visible; + } + + plots.supplyTraceDefaults(expandedTrace, fullExpandedTrace, cnt, fullLayout, i); + + // relink private (i.e. underscore) keys expanded trace to full expanded trace so + // that transform supply-default methods can set _ keys for future use. + relinkPrivateKeys(fullExpandedTrace, expandedTrace); + + // add info about parent data trace + fullExpandedTrace.index = i; + fullExpandedTrace._input = trace; + fullExpandedTrace._fullInput = fullTrace; + + // add info about the expanded data + fullExpandedTrace._expandedIndex = cnt; + fullExpandedTrace._expandedInput = expandedTrace; + + pushModule(fullExpandedTrace); + } + } else { + // add identify refs for consistency with transformed traces + fullTrace._fullInput = fullTrace; + fullTrace._expandedInput = fullTrace; + + pushModule(fullTrace); + } + + if(Registry.traceIs(fullTrace, 'carpetAxis')) { + carpetIndex[fullTrace.carpet] = fullTrace; + } + + if(Registry.traceIs(fullTrace, 'carpetDependent')) { + carpetDependents.push(i); + } + } + + for(i = 0; i < carpetDependents.length; i++) { + fullTrace = dataOut[carpetDependents[i]]; + + if(!fullTrace.visible) continue; + + var carpetAxis = carpetIndex[fullTrace.carpet]; + fullTrace._carpet = carpetAxis; + + if(!carpetAxis || !carpetAxis.visible) { + fullTrace.visible = false; + continue; + } + + fullTrace.xaxis = carpetAxis.xaxis; + fullTrace.yaxis = carpetAxis.yaxis; + } +}; + +plots.supplyAnimationDefaults = function(opts) { + opts = opts || {}; + var i; + var optsOut = {}; + + function coerce(attr, dflt) { + return Lib.coerce(opts || {}, optsOut, animationAttrs, attr, dflt); + } + + coerce('mode'); + coerce('direction'); + coerce('fromcurrent'); + + if(Array.isArray(opts.frame)) { + optsOut.frame = []; + for(i = 0; i < opts.frame.length; i++) { + optsOut.frame[i] = plots.supplyAnimationFrameDefaults(opts.frame[i] || {}); + } + } else { + optsOut.frame = plots.supplyAnimationFrameDefaults(opts.frame || {}); + } + + if(Array.isArray(opts.transition)) { + optsOut.transition = []; + for(i = 0; i < opts.transition.length; i++) { + optsOut.transition[i] = plots.supplyAnimationTransitionDefaults(opts.transition[i] || {}); + } + } else { + optsOut.transition = plots.supplyAnimationTransitionDefaults(opts.transition || {}); + } + + return optsOut; +}; + +plots.supplyAnimationFrameDefaults = function(opts) { + var optsOut = {}; + + function coerce(attr, dflt) { + return Lib.coerce(opts || {}, optsOut, animationAttrs.frame, attr, dflt); + } + + coerce('duration'); + coerce('redraw'); + + return optsOut; +}; + +plots.supplyAnimationTransitionDefaults = function(opts) { + var optsOut = {}; + + function coerce(attr, dflt) { + return Lib.coerce(opts || {}, optsOut, animationAttrs.transition, attr, dflt); + } + + coerce('duration'); + coerce('easing'); + + return optsOut; +}; + +plots.supplyFrameDefaults = function(frameIn) { + var frameOut = {}; + + function coerce(attr, dflt) { + return Lib.coerce(frameIn, frameOut, frameAttrs, attr, dflt); + } + + coerce('group'); + coerce('name'); + coerce('traces'); + coerce('baseframe'); + coerce('data'); + coerce('layout'); + + return frameOut; +}; + +plots.supplyTraceDefaults = function(traceIn, traceOut, colorIndex, layout, traceInIndex) { + var colorway = layout.colorway || Color.defaults; + var defaultColor = colorway[colorIndex % colorway.length]; + + var i; + + function coerce(attr, dflt) { + return Lib.coerce(traceIn, traceOut, plots.attributes, attr, dflt); + } + + var visible = coerce('visible'); + + coerce('type'); + coerce('name', layout._traceWord + ' ' + traceInIndex); + + coerce('uirevision', layout.uirevision); + + // we want even invisible traces to make their would-be subplots visible + // so coerce the subplot id(s) now no matter what + var _module = plots.getModule(traceOut); + + traceOut._module = _module; + if(_module) { + var basePlotModule = _module.basePlotModule; + var subplotAttr = basePlotModule.attr; + var subplotAttrs = basePlotModule.attributes; + if(subplotAttr && subplotAttrs) { + var subplots = layout._subplots; + var subplotId = ''; + + // TODO - currently if we draw an empty gl2d subplot, it draws + // nothing then gets stuck and you can't get it back without newPlot + // sort this out in the regl refactor? but for now just drop empty gl2d subplots + if(basePlotModule.name !== 'gl2d' || visible) { + if(Array.isArray(subplotAttr)) { + for(i = 0; i < subplotAttr.length; i++) { + var attri = subplotAttr[i]; + var vali = Lib.coerce(traceIn, traceOut, subplotAttrs, attri); + + if(subplots[attri]) Lib.pushUnique(subplots[attri], vali); + subplotId += vali; + } + } else { + subplotId = Lib.coerce(traceIn, traceOut, subplotAttrs, subplotAttr); + } + + if(subplots[basePlotModule.name]) { + Lib.pushUnique(subplots[basePlotModule.name], subplotId); + } + } + } + } + + if(visible) { + coerce('customdata'); + coerce('ids'); + coerce('meta'); + + if(Registry.traceIs(traceOut, 'showLegend')) { + traceOut._dfltShowLegend = true; + coerce('showlegend'); + coerce('legendgroup'); + } else { + traceOut._dfltShowLegend = false; + } + + if(_module) { + _module.supplyDefaults(traceIn, traceOut, defaultColor, layout); + } + + if(!Registry.traceIs(traceOut, 'noOpacity')) { + coerce('opacity'); + } + + if(Registry.traceIs(traceOut, 'notLegendIsolatable')) { + // This clears out the legendonly state for traces like carpet that + // cannot be isolated in the legend + traceOut.visible = !!traceOut.visible; + } + + if(!Registry.traceIs(traceOut, 'noHover')) { + if(!traceOut.hovertemplate) Lib.coerceHoverinfo(traceIn, traceOut, layout); + + // parcats support hover, but not hoverlabel stylings (yet) + if(traceOut.type !== 'parcats') { + Registry.getComponentMethod('fx', 'supplyDefaults')(traceIn, traceOut, defaultColor, layout); + } + } + + if(_module && _module.selectPoints) { + coerce('selectedpoints'); + } + + plots.supplyTransformDefaults(traceIn, traceOut, layout); + } + + return traceOut; +}; + +/** + * hasMakesDataTransform: does this trace have a transform that makes its own + * data, either by grabbing it from somewhere else or by creating it from input + * parameters? If so, we should still keep going with supplyDefaults + * even if the trace is invisible, which may just be because it has no data yet. + */ +function hasMakesDataTransform(trace) { + var transforms = trace.transforms; + if(Array.isArray(transforms) && transforms.length) { + for(var i = 0; i < transforms.length; i++) { + var ti = transforms[i]; + var _module = ti._module || transformsRegistry[ti.type]; + if(_module && _module.makesData) return true; + } + } + return false; +} + +plots.hasMakesDataTransform = hasMakesDataTransform; + +plots.supplyTransformDefaults = function(traceIn, traceOut, layout) { + // For now we only allow transforms on 1D traces, ie those that specify a _length. + // If we were to implement 2D transforms, we'd need to have each transform + // describe its own applicability and disable itself when it doesn't apply. + // Also allow transforms that make their own data, but not in globalTransforms + if(!(traceOut._length || hasMakesDataTransform(traceIn))) return; + + var globalTransforms = layout._globalTransforms || []; + var transformModules = layout._transformModules || []; + + if(!Array.isArray(traceIn.transforms) && globalTransforms.length === 0) return; + + var containerIn = traceIn.transforms || []; + var transformList = globalTransforms.concat(containerIn); + var containerOut = traceOut.transforms = []; + + for(var i = 0; i < transformList.length; i++) { + var transformIn = transformList[i]; + var type = transformIn.type; + var _module = transformsRegistry[type]; + var transformOut; + + /* + * Supply defaults may run twice. First pass runs all supply defaults steps + * and adds the _module to any output transforms. + * If transforms exist another pass is run so that any generated traces also + * go through supply defaults. This has the effect of rerunning + * supplyTransformDefaults. If the transform does not have a `transform` + * function it could not have generated any new traces and the second stage + * is unnecessary. We detect this case with the following variables. + */ + var isFirstStage = !(transformIn._module && transformIn._module === _module); + var doLaterStages = _module && typeof _module.transform === 'function'; + + if(!_module) Lib.warn('Unrecognized transform type ' + type + '.'); + + if(_module && _module.supplyDefaults && (isFirstStage || doLaterStages)) { + transformOut = _module.supplyDefaults(transformIn, traceOut, layout, traceIn); + transformOut.type = type; + transformOut._module = _module; + + Lib.pushUnique(transformModules, _module); + } else { + transformOut = Lib.extendFlat({}, transformIn); + } + + containerOut.push(transformOut); + } +}; + +function applyTransforms(fullTrace, fullData, layout, fullLayout) { + var container = fullTrace.transforms; + var dataOut = [fullTrace]; + + for(var i = 0; i < container.length; i++) { + var transform = container[i]; + var _module = transformsRegistry[transform.type]; + + if(_module && _module.transform) { + dataOut = _module.transform(dataOut, { + transform: transform, + fullTrace: fullTrace, + fullData: fullData, + layout: layout, + fullLayout: fullLayout, + transformIndex: i + }); + } + } + + return dataOut; +} + +plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut, formatObj) { + function coerce(attr, dflt) { + return Lib.coerce(layoutIn, layoutOut, plots.layoutAttributes, attr, dflt); + } + + var template = layoutIn.template; + if(Lib.isPlainObject(template)) { + layoutOut.template = template; + layoutOut._template = template.layout; + layoutOut._dataTemplate = template.data; + } + + var globalFont = Lib.coerceFont(coerce, 'font'); + + coerce('title.text', layoutOut._dfltTitle.plot); + + Lib.coerceFont(coerce, 'title.font', { + family: globalFont.family, + size: Math.round(globalFont.size * 1.4), + color: globalFont.color + }); + + coerce('title.xref'); + coerce('title.yref'); + coerce('title.x'); + coerce('title.y'); + coerce('title.xanchor'); + coerce('title.yanchor'); + coerce('title.pad.t'); + coerce('title.pad.r'); + coerce('title.pad.b'); + coerce('title.pad.l'); + + // Make sure that autosize is defaulted to *true* + // on layouts with no set width and height for backward compatibly, + // in particular https://plot.ly/javascript/responsive-fluid-layout/ + // + // Before https://github.com/plotly/plotly.js/pull/635 , + // layouts with no set width and height were set temporary set to 'initial' + // to pass through the autosize routine + // + // This behavior is subject to change in v2. + coerce('autosize', !(layoutIn.width && layoutIn.height)); + + coerce('width'); + coerce('height'); + coerce('margin.l'); + coerce('margin.r'); + coerce('margin.t'); + coerce('margin.b'); + coerce('margin.pad'); + coerce('margin.autoexpand'); + + if(layoutIn.width && layoutIn.height) plots.sanitizeMargins(layoutOut); + + Registry.getComponentMethod('grid', 'sizeDefaults')(layoutIn, layoutOut); + + coerce('paper_bgcolor'); + + coerce('separators', formatObj.decimal + formatObj.thousands); + coerce('hidesources'); + + coerce('colorway'); + + coerce('datarevision'); + var uirevision = coerce('uirevision'); + coerce('editrevision', uirevision); + coerce('selectionrevision', uirevision); + + coerce('modebar.orientation'); + coerce('modebar.bgcolor', Color.addOpacity(layoutOut.paper_bgcolor, 0.5)); + var modebarDefaultColor = Color.contrast(Color.rgb(layoutOut.modebar.bgcolor)); + coerce('modebar.color', Color.addOpacity(modebarDefaultColor, 0.3)); + coerce('modebar.activecolor', Color.addOpacity(modebarDefaultColor, 0.7)); + coerce('modebar.uirevision', uirevision); + + coerce('meta'); + + // do not include defaults in fullLayout when users do not set transition + if(Lib.isPlainObject(layoutIn.transition)) { + coerce('transition.duration'); + coerce('transition.easing'); + coerce('transition.ordering'); + } + + Registry.getComponentMethod( + 'calendars', + 'handleDefaults' + )(layoutIn, layoutOut, 'calendar'); + + Registry.getComponentMethod( + 'fx', + 'supplyLayoutGlobalDefaults' + )(layoutIn, layoutOut, coerce); +}; + +plots.plotAutoSize = function plotAutoSize(gd, layout, fullLayout) { + var context = gd._context || {}; + var frameMargins = context.frameMargins; + var newWidth; + var newHeight; + + var isPlotDiv = Lib.isPlotDiv(gd); + + if(isPlotDiv) gd.emit('plotly_autosize'); + + // embedded in an iframe - just take the full iframe size + // if we get to this point, with no aspect ratio restrictions + if(context.fillFrame) { + newWidth = window.innerWidth; + newHeight = window.innerHeight; + + // somehow we get a few extra px height sometimes... + // just hide it + document.body.style.overflow = 'hidden'; + } else { + // plotly.js - let the developers do what they want, either + // provide height and width for the container div, + // specify size in layout, or take the defaults, + // but don't enforce any ratio restrictions + var computedStyle = isPlotDiv ? window.getComputedStyle(gd) : {}; + + newWidth = parseFloat(computedStyle.width) || parseFloat(computedStyle.maxWidth) || fullLayout.width; + newHeight = parseFloat(computedStyle.height) || parseFloat(computedStyle.maxHeight) || fullLayout.height; + + if(isNumeric(frameMargins) && frameMargins > 0) { + var factor = 1 - 2 * frameMargins; + newWidth = Math.round(factor * newWidth); + newHeight = Math.round(factor * newHeight); + } + } + + var minWidth = plots.layoutAttributes.width.min; + var minHeight = plots.layoutAttributes.height.min; + if(newWidth < minWidth) newWidth = minWidth; + if(newHeight < minHeight) newHeight = minHeight; + + var widthHasChanged = !layout.width && + (Math.abs(fullLayout.width - newWidth) > 1); + var heightHasChanged = !layout.height && + (Math.abs(fullLayout.height - newHeight) > 1); + + if(heightHasChanged || widthHasChanged) { + if(widthHasChanged) fullLayout.width = newWidth; + if(heightHasChanged) fullLayout.height = newHeight; + } + + // cache initial autosize value, used in relayout when + // width or height values are set to null + if(!gd._initialAutoSize) { + gd._initialAutoSize = { width: newWidth, height: newHeight }; + } + + plots.sanitizeMargins(fullLayout); +}; + +plots.supplyLayoutModuleDefaults = function(layoutIn, layoutOut, fullData, transitionData) { + var componentsRegistry = Registry.componentsRegistry; + var basePlotModules = layoutOut._basePlotModules; + var component, i, _module; + + var Cartesian = Registry.subplotsRegistry.cartesian; + + // check if any components need to add more base plot modules + // that weren't captured by traces + for(component in componentsRegistry) { + _module = componentsRegistry[component]; + + if(_module.includeBasePlot) { + _module.includeBasePlot(layoutIn, layoutOut); + } + } + + // make sure we *at least* have some cartesian axes + if(!basePlotModules.length) { + basePlotModules.push(Cartesian); + } + + // ensure all cartesian axes have at least one subplot + if(layoutOut._has('cartesian')) { + Registry.getComponentMethod('grid', 'contentDefaults')(layoutIn, layoutOut); + Cartesian.finalizeSubplots(layoutIn, layoutOut); + } + + // sort subplot lists + for(var subplotType in layoutOut._subplots) { + layoutOut._subplots[subplotType].sort(Lib.subplotSort); + } + + // base plot module layout defaults + for(i = 0; i < basePlotModules.length; i++) { + _module = basePlotModules[i]; + + // e.g. pie does not have a layout-defaults step + if(_module.supplyLayoutDefaults) { + _module.supplyLayoutDefaults(layoutIn, layoutOut, fullData); + } + } + + // trace module layout defaults + // use _modules rather than _visibleModules so that even + // legendonly traces can include settings - eg barmode, which affects + // legend.traceorder default value. + var modules = layoutOut._modules; + for(i = 0; i < modules.length; i++) { + _module = modules[i]; + + if(_module.supplyLayoutDefaults) { + _module.supplyLayoutDefaults(layoutIn, layoutOut, fullData); + } + } + + // transform module layout defaults + var transformModules = layoutOut._transformModules; + for(i = 0; i < transformModules.length; i++) { + _module = transformModules[i]; + + if(_module.supplyLayoutDefaults) { + _module.supplyLayoutDefaults(layoutIn, layoutOut, fullData, transitionData); + } + } + + for(component in componentsRegistry) { + _module = componentsRegistry[component]; + + if(_module.supplyLayoutDefaults) { + _module.supplyLayoutDefaults(layoutIn, layoutOut, fullData); + } + } +}; + +// Remove all plotly attributes from a div so it can be replotted fresh +// TODO: these really need to be encapsulated into a much smaller set... +plots.purge = function(gd) { + // note: we DO NOT remove _context because it doesn't change when we insert + // a new plot, and may have been set outside of our scope. + + var fullLayout = gd._fullLayout || {}; + if(fullLayout._glcontainer !== undefined) { + fullLayout._glcontainer.selectAll('.gl-canvas').remove(); + fullLayout._glcontainer.remove(); + fullLayout._glcanvas = null; + } + + // remove modebar + if(fullLayout._modeBar) fullLayout._modeBar.destroy(); + + if(gd._transitionData) { + // Ensure any dangling callbacks are simply dropped if the plot is purged. + // This is more or less only actually important for testing. + if(gd._transitionData._interruptCallbacks) { + gd._transitionData._interruptCallbacks.length = 0; + } + + if(gd._transitionData._animationRaf) { + window.cancelAnimationFrame(gd._transitionData._animationRaf); + } + } + + // remove any planned throttles + Lib.clearThrottle(); + + // remove responsive handler + Lib.clearResponsive(gd); + + // data and layout + delete gd.data; + delete gd.layout; + delete gd._fullData; + delete gd._fullLayout; + delete gd.calcdata; + delete gd.framework; + delete gd.empty; + + delete gd.fid; + + delete gd.undoqueue; // action queue + delete gd.undonum; + delete gd.autoplay; // are we doing an action that doesn't go in undo queue? + delete gd.changed; + + // these get recreated on Plotly.plot anyway, but just to be safe + // (and to have a record of them...) + delete gd._promises; + delete gd._redrawTimer; + delete gd._hmlumcount; + delete gd._hmpixcount; + delete gd._transitionData; + delete gd._transitioning; + delete gd._initialAutoSize; + delete gd._transitioningWithDuration; + + // created during certain events, that *should* clean them up + // themselves, but may not if there was an error + delete gd._dragging; + delete gd._dragged; + delete gd._dragdata; + delete gd._hoverdata; + delete gd._snapshotInProgress; + delete gd._editing; + delete gd._mouseDownTime; + delete gd._legendMouseDownTime; + + // remove all event listeners + if(gd.removeAllListeners) gd.removeAllListeners(); +}; + +plots.style = function(gd) { + var _modules = gd._fullLayout._visibleModules; + var styleModules = []; + var i; + + // some trace modules reuse the same style method, + // make sure to not unnecessary call them multiple times. + + for(i = 0; i < _modules.length; i++) { + var _module = _modules[i]; + if(_module.style) { + Lib.pushUnique(styleModules, _module.style); + } + } + + for(i = 0; i < styleModules.length; i++) { + styleModules[i](gd); + } +}; + +plots.sanitizeMargins = function(fullLayout) { + // polar doesn't do margins... + if(!fullLayout || !fullLayout.margin) return; + + var width = fullLayout.width; + var height = fullLayout.height; + var margin = fullLayout.margin; + var plotWidth = width - (margin.l + margin.r); + var plotHeight = height - (margin.t + margin.b); + var correction; + + // if margin.l + margin.r = 0 then plotWidth > 0 + // as width >= 10 by supplyDefaults + // similarly for margin.t + margin.b + + if(plotWidth < 0) { + correction = (width - 1) / (margin.l + margin.r); + margin.l = Math.floor(correction * margin.l); + margin.r = Math.floor(correction * margin.r); + } + + if(plotHeight < 0) { + correction = (height - 1) / (margin.t + margin.b); + margin.t = Math.floor(correction * margin.t); + margin.b = Math.floor(correction * margin.b); + } +}; + +plots.clearAutoMarginIds = function(gd) { + gd._fullLayout._pushmarginIds = {}; +}; + +plots.allowAutoMargin = function(gd, id) { + gd._fullLayout._pushmarginIds[id] = 1; +}; + +function initMargins(fullLayout) { + var margin = fullLayout.margin; + + if(!fullLayout._size) { + var gs = fullLayout._size = { + l: Math.round(margin.l), + r: Math.round(margin.r), + t: Math.round(margin.t), + b: Math.round(margin.b), + p: Math.round(margin.pad) + }; + gs.w = Math.round(fullLayout.width) - gs.l - gs.r; + gs.h = Math.round(fullLayout.height) - gs.t - gs.b; + } + if(!fullLayout._pushmargin) fullLayout._pushmargin = {}; + if(!fullLayout._pushmarginIds) fullLayout._pushmarginIds = {}; +} + +/** + * autoMargin: called by components that may need to expand the margins to + * be rendered on-plot. + * + * @param {DOM element} gd + * @param {string} id - an identifier unique (within this plot) to this object, + * so we can remove a previous margin expansion from the same object. + * @param {object} o - the margin requirements of this object, or omit to delete + * this entry (like if it's hidden). Keys are: + * x, y: plot fraction of the anchor point. + * xl, xr, yt, yb: if the object has an extent defined in plot fraction, + * you can specify both edges as plot fractions in each dimension + * l, r, t, b: the pixels to pad past the plot fraction x[l|r] and y[t|b] + * pad: extra pixels to add in all directions, default 12 (why?) + */ +plots.autoMargin = function(gd, id, o) { + var fullLayout = gd._fullLayout; + + var pushMargin = fullLayout._pushmargin; + var pushMarginIds = fullLayout._pushmarginIds; + + if(fullLayout.margin.autoexpand !== false) { + if(!o) { + delete pushMargin[id]; + delete pushMarginIds[id]; + } else { + var pad = o.pad; + if(pad === undefined) { + var margin = fullLayout.margin; + // if no explicit pad is given, use 12px unless there's a + // specified margin that's smaller than that + pad = Math.min(12, margin.l, margin.r, margin.t, margin.b); + } + + // if the item is too big, just give it enough automargin to + // make sure you can still grab it and bring it back + if(o.l + o.r > fullLayout.width * 0.5) { + Lib.log('Margin push', id, 'is too big in x, dropping'); + o.l = o.r = 0; + } + if(o.b + o.t > fullLayout.height * 0.5) { + Lib.log('Margin push', id, 'is too big in y, dropping'); + o.b = o.t = 0; + } + + var xl = o.xl !== undefined ? o.xl : o.x; + var xr = o.xr !== undefined ? o.xr : o.x; + var yt = o.yt !== undefined ? o.yt : o.y; + var yb = o.yb !== undefined ? o.yb : o.y; + + pushMargin[id] = { + l: {val: xl, size: o.l + pad}, + r: {val: xr, size: o.r + pad}, + b: {val: yb, size: o.b + pad}, + t: {val: yt, size: o.t + pad} + }; + pushMarginIds[id] = 1; + } + + if(!fullLayout._replotting) { + return plots.doAutoMargin(gd); + } + } +}; + +plots.doAutoMargin = function(gd) { + var fullLayout = gd._fullLayout; + if(!fullLayout._size) fullLayout._size = {}; + initMargins(fullLayout); + + var gs = fullLayout._size; + var margin = fullLayout.margin; + var oldMargins = Lib.extendFlat({}, gs); + + // adjust margins for outside components + // fullLayout.margin is the requested margin, + // fullLayout._size has margins and plotsize after adjustment + var ml = margin.l; + var mr = margin.r; + var mt = margin.t; + var mb = margin.b; + var width = fullLayout.width; + var height = fullLayout.height; + var pushMargin = fullLayout._pushmargin; + var pushMarginIds = fullLayout._pushmarginIds; + + if(fullLayout.margin.autoexpand !== false) { + for(var k in pushMargin) { + if(!pushMarginIds[k]) delete pushMargin[k]; + } + + // fill in the requested margins + pushMargin.base = { + l: {val: 0, size: ml}, + r: {val: 1, size: mr}, + t: {val: 1, size: mt}, + b: {val: 0, size: mb} + }; + + // now cycle through all the combinations of l and r + // (and t and b) to find the required margins + + for(var k1 in pushMargin) { + var pushleft = pushMargin[k1].l || {}; + var pushbottom = pushMargin[k1].b || {}; + var fl = pushleft.val; + var pl = pushleft.size; + var fb = pushbottom.val; + var pb = pushbottom.size; + + for(var k2 in pushMargin) { + if(isNumeric(pl) && pushMargin[k2].r) { + var fr = pushMargin[k2].r.val; + var pr = pushMargin[k2].r.size; + + if(fr > fl) { + var newL = (pl * fr + (pr - width) * fl) / (fr - fl); + var newR = (pr * (1 - fl) + (pl - width) * (1 - fr)) / (fr - fl); + if(newL >= 0 && newR >= 0 && width - (newL + newR) > 0 && newL + newR > ml + mr) { + ml = newL; + mr = newR; + } + } + } + + if(isNumeric(pb) && pushMargin[k2].t) { + var ft = pushMargin[k2].t.val; + var pt = pushMargin[k2].t.size; + + if(ft > fb) { + var newB = (pb * ft + (pt - height) * fb) / (ft - fb); + var newT = (pt * (1 - fb) + (pb - height) * (1 - ft)) / (ft - fb); + if(newB >= 0 && newT >= 0 && height - (newT + newB) > 0 && newB + newT > mb + mt) { + mb = newB; + mt = newT; + } + } + } + } + } + } + + gs.l = Math.round(ml); + gs.r = Math.round(mr); + gs.t = Math.round(mt); + gs.b = Math.round(mb); + gs.p = Math.round(margin.pad); + gs.w = Math.round(width) - gs.l - gs.r; + gs.h = Math.round(height) - gs.t - gs.b; + + // if things changed and we're not already redrawing, trigger a redraw + if(!fullLayout._replotting && plots.didMarginChange(oldMargins, gs)) { + if('_redrawFromAutoMarginCount' in fullLayout) { + fullLayout._redrawFromAutoMarginCount++; + } else { + fullLayout._redrawFromAutoMarginCount = 1; + } + + // Always allow at least one redraw and give each margin-push + // call 3 loops to converge. Of course, for most cases this way too many, + // but let's keep things on the safe side until we fix our + // auto-margin pipeline problems: + // https://github.com/plotly/plotly.js/issues/2704 + var maxNumberOfRedraws = 3 * (1 + Object.keys(pushMarginIds).length); + + if(fullLayout._redrawFromAutoMarginCount < maxNumberOfRedraws) { + return Registry.call('plot', gd); + } else { + Lib.warn('Too many auto-margin redraws.'); + } + } +}; + +var marginKeys = ['l', 'r', 't', 'b', 'p', 'w', 'h']; + +plots.didMarginChange = function(margin0, margin1) { + for(var i = 0; i < marginKeys.length; i++) { + var k = marginKeys[i]; + var m0 = margin0[k]; + var m1 = margin1[k]; + // use 1px tolerance in case we old/new differ only + // by rounding errors, which can lead to infinite loops + if(!isNumeric(m0) || Math.abs(m1 - m0) > 1) { + return true; + } + } + return false; +}; + +/** + * JSONify the graph data and layout + * + * This function needs to recurse because some src can be inside + * sub-objects. + * + * It also strips out functions and private (starts with _) elements. + * Therefore, we can add temporary things to data and layout that don't + * get saved. + * + * @param gd The graphDiv + * @param {Boolean} dataonly If true, don't return layout. + * @param {'keepref'|'keepdata'|'keepall'} [mode='keepref'] Filter what's kept + * keepref: remove data for which there's a src present + * eg if there's xsrc present (and xsrc is well-formed, + * ie has : and some chars before it), strip out x + * keepdata: remove all src tags, don't remove the data itself + * keepall: keep data and src + * @param {String} output If you specify 'object', the result will not be stringified + * @param {Boolean} useDefaults If truthy, use _fullLayout and _fullData + * @returns {Object|String} + */ +plots.graphJson = function(gd, dataonly, mode, output, useDefaults) { + // if the defaults aren't supplied yet, we need to do that... + if((useDefaults && dataonly && !gd._fullData) || + (useDefaults && !dataonly && !gd._fullLayout)) { + plots.supplyDefaults(gd); + } + + var data = (useDefaults) ? gd._fullData : gd.data; + var layout = (useDefaults) ? gd._fullLayout : gd.layout; + var frames = (gd._transitionData || {})._frames; + + function stripObj(d) { + if(typeof d === 'function') { + return null; + } + if(Lib.isPlainObject(d)) { + var o = {}; + var v, src; + for(v in d) { + // remove private elements and functions + // _ is for private, [ is a mistake ie [object Object] + if(typeof d[v] === 'function' || + ['_', '['].indexOf(v.charAt(0)) !== -1) { + continue; + } + + // look for src/data matches and remove the appropriate one + if(mode === 'keepdata') { + // keepdata: remove all ...src tags + if(v.substr(v.length - 3) === 'src') { + continue; + } + } else if(mode === 'keepstream') { + // keep sourced data if it's being streamed. + // similar to keepref, but if the 'stream' object exists + // in a trace, we will keep the data array. + src = d[v + 'src']; + if(typeof src === 'string' && src.indexOf(':') > 0) { + if(!Lib.isPlainObject(d.stream)) { + continue; + } + } + } else if(mode !== 'keepall') { + // keepref: remove sourced data but only + // if the source tag is well-formed + src = d[v + 'src']; + if(typeof src === 'string' && src.indexOf(':') > 0) { + continue; + } + } + + // OK, we're including this... recurse into it + o[v] = stripObj(d[v]); + } + return o; + } + + if(Array.isArray(d)) { + return d.map(stripObj); + } + + if(Lib.isTypedArray(d)) { + return Lib.simpleMap(d, Lib.identity); + } + + // convert native dates to date strings... + // mostly for external users exporting to plotly + if(Lib.isJSDate(d)) return Lib.ms2DateTimeLocal(+d); + + return d; + } + + var obj = { + data: (data || []).map(function(v) { + var d = stripObj(v); + // fit has some little arrays in it that don't contain data, + // just fit params and meta + if(dataonly) { delete d.fit; } + return d; + }) + }; + if(!dataonly) { obj.layout = stripObj(layout); } + + if(gd.framework && gd.framework.isPolar) obj = gd.framework.getConfig(); + + if(frames) obj.frames = stripObj(frames); + + return (output === 'object') ? obj : JSON.stringify(obj); +}; + +/** + * Modify a keyframe using a list of operations: + * + * @param {array of objects} operations + * Sequence of operations to be performed on the keyframes + */ +plots.modifyFrames = function(gd, operations) { + var i, op, frame; + var _frames = gd._transitionData._frames; + var _frameHash = gd._transitionData._frameHash; + + for(i = 0; i < operations.length; i++) { + op = operations[i]; + + switch(op.type) { + // No reason this couldn't exist, but is currently unused/untested: + /* case 'rename': + frame = _frames[op.index]; + delete _frameHash[frame.name]; + _frameHash[op.name] = frame; + frame.name = op.name; + break;*/ + case 'replace': + frame = op.value; + var oldName = (_frames[op.index] || {}).name; + var newName = frame.name; + _frames[op.index] = _frameHash[newName] = frame; + + if(newName !== oldName) { + // If name has changed in addition to replacement, then update + // the lookup table: + delete _frameHash[oldName]; + _frameHash[newName] = frame; + } + + break; + case 'insert': + frame = op.value; + _frameHash[frame.name] = frame; + _frames.splice(op.index, 0, frame); + break; + case 'delete': + frame = _frames[op.index]; + delete _frameHash[frame.name]; + _frames.splice(op.index, 1); + break; + } + } + + return Promise.resolve(); +}; + +/* + * Compute a keyframe. Merge a keyframe into its base frame(s) and + * expand properties. + * + * @param {object} frameLookup + * An object containing frames keyed by name (i.e. gd._transitionData._frameHash) + * @param {string} frame + * The name of the keyframe to be computed + * + * Returns: a new object with the merged content + */ +plots.computeFrame = function(gd, frameName) { + var frameLookup = gd._transitionData._frameHash; + var i, traceIndices, traceIndex, destIndex; + + // Null or undefined will fail on .toString(). We'll allow numbers since we + // make it clear frames must be given string names, but we'll allow numbers + // here since they're otherwise fine for looking up frames as long as they're + // properly cast to strings. We really just want to ensure here that this + // 1) doesn't fail, and + // 2) doens't give an incorrect answer (which String(frameName) would) + if(!frameName) { + throw new Error('computeFrame must be given a string frame name'); + } + + var framePtr = frameLookup[frameName.toString()]; + + // Return false if the name is invalid: + if(!framePtr) { + return false; + } + + var frameStack = [framePtr]; + var frameNameStack = [framePtr.name]; + + // Follow frame pointers: + while(framePtr.baseframe && (framePtr = frameLookup[framePtr.baseframe.toString()])) { + // Avoid infinite loops: + if(frameNameStack.indexOf(framePtr.name) !== -1) break; + + frameStack.push(framePtr); + frameNameStack.push(framePtr.name); + } + + // A new object for the merged result: + var result = {}; + + // Merge, starting with the last and ending with the desired frame: + while((framePtr = frameStack.pop())) { + if(framePtr.layout) { + result.layout = plots.extendLayout(result.layout, framePtr.layout); + } + + if(framePtr.data) { + if(!result.data) { + result.data = []; + } + traceIndices = framePtr.traces; + + if(!traceIndices) { + // If not defined, assume serial order starting at zero + traceIndices = []; + for(i = 0; i < framePtr.data.length; i++) { + traceIndices[i] = i; + } + } + + if(!result.traces) { + result.traces = []; + } + + for(i = 0; i < framePtr.data.length; i++) { + // Loop through this frames data, find out where it should go, + // and merge it! + traceIndex = traceIndices[i]; + if(traceIndex === undefined || traceIndex === null) { + continue; + } + + destIndex = result.traces.indexOf(traceIndex); + if(destIndex === -1) { + destIndex = result.data.length; + result.traces[destIndex] = traceIndex; + } + + result.data[destIndex] = plots.extendTrace(result.data[destIndex], framePtr.data[i]); + } + } + } + + return result; +}; + +/* + * Recompute the lookup table that maps frame name -> frame object. addFrames/ + * deleteFrames already manages this data one at a time, so the only time this + * is necessary is if you poke around manually in `gd._transitionData._frames` + * and create and haven't updated the lookup table. + */ +plots.recomputeFrameHash = function(gd) { + var hash = gd._transitionData._frameHash = {}; + var frames = gd._transitionData._frames; + for(var i = 0; i < frames.length; i++) { + var frame = frames[i]; + if(frame && frame.name) { + hash[frame.name] = frame; + } + } +}; + +/** + * Extend an object, treating container arrays very differently by extracting + * their contents and merging them separately. + * + * This exists so that we can extendDeepNoArrays and avoid stepping into data + * arrays without knowledge of the plot schema, but so that we may also manually + * recurse into known container arrays, such as transforms. + * + * See extendTrace and extendLayout below for usage. + */ +plots.extendObjectWithContainers = function(dest, src, containerPaths) { + var containerProp, containerVal, i, j, srcProp, destProp, srcContainer, destContainer; + var copy = Lib.extendDeepNoArrays({}, src || {}); + var expandedObj = Lib.expandObjectPaths(copy); + var containerObj = {}; + + // Step through and extract any container properties. Otherwise extendDeepNoArrays + // will clobber any existing properties with an empty array and then supplyDefaults + // will reset everything to defaults. + if(containerPaths && containerPaths.length) { + for(i = 0; i < containerPaths.length; i++) { + containerProp = Lib.nestedProperty(expandedObj, containerPaths[i]); + containerVal = containerProp.get(); + + if(containerVal === undefined) { + Lib.nestedProperty(containerObj, containerPaths[i]).set(null); + } else { + containerProp.set(null); + Lib.nestedProperty(containerObj, containerPaths[i]).set(containerVal); + } + } + } + + dest = Lib.extendDeepNoArrays(dest || {}, expandedObj); + + if(containerPaths && containerPaths.length) { + for(i = 0; i < containerPaths.length; i++) { + srcProp = Lib.nestedProperty(containerObj, containerPaths[i]); + srcContainer = srcProp.get(); + + if(!srcContainer) continue; + + destProp = Lib.nestedProperty(dest, containerPaths[i]); + destContainer = destProp.get(); + + if(!Array.isArray(destContainer)) { + destContainer = []; + destProp.set(destContainer); + } + + for(j = 0; j < srcContainer.length; j++) { + var srcObj = srcContainer[j]; + + if(srcObj === null) destContainer[j] = null; + else { + destContainer[j] = plots.extendObjectWithContainers(destContainer[j], srcObj); + } + } + + destProp.set(destContainer); + } + } + + return dest; +}; + +plots.dataArrayContainers = ['transforms', 'dimensions']; +plots.layoutArrayContainers = Registry.layoutArrayContainers; + +/* + * Extend a trace definition. This method: + * + * 1. directly transfers any array references + * 2. manually recurses into container arrays like transforms + * + * The result is the original object reference with the new contents merged in. + */ +plots.extendTrace = function(destTrace, srcTrace) { + return plots.extendObjectWithContainers(destTrace, srcTrace, plots.dataArrayContainers); +}; + +/* + * Extend a layout definition. This method: + * + * 1. directly transfers any array references (not critically important for + * layout since there aren't really data arrays) + * 2. manually recurses into container arrays like annotations + * + * The result is the original object reference with the new contents merged in. + */ +plots.extendLayout = function(destLayout, srcLayout) { + return plots.extendObjectWithContainers(destLayout, srcLayout, plots.layoutArrayContainers); +}; + +/** + * Transition to a set of new data and layout properties from Plotly.animate + * + * @param {DOM element} gd + * @param {Object[]} data + * an array of data objects following the normal Plotly data definition format + * @param {Object} layout + * a layout object, following normal Plotly layout format + * @param {Number[]} traces + * indices of the corresponding traces specified in `data` + * @param {Object} frameOpts + * options for the frame (i.e. whether to redraw post-transition) + * @param {Object} transitionOpts + * options for the transition + */ +plots.transition = function(gd, data, layout, traces, frameOpts, transitionOpts) { + var opts = {redraw: frameOpts.redraw}; + var transitionedTraces = {}; + var axEdits = []; + + opts.prepareFn = function() { + var dataLength = Array.isArray(data) ? data.length : 0; + var traceIndices = traces.slice(0, dataLength); + + for(var i = 0; i < traceIndices.length; i++) { + var traceIdx = traceIndices[i]; + var trace = gd._fullData[traceIdx]; + var _module = trace._module; + + // There's nothing to do if this module is not defined: + if(!_module) continue; + + // Don't register the trace as transitioned if it doesn't know what to do. + // If it *is* registered, it will receive a callback that it's responsible + // for calling in order to register the transition as having completed. + if(_module.animatable) { + var n = _module.basePlotModule.name; + if(!transitionedTraces[n]) transitionedTraces[n] = []; + transitionedTraces[n].push(traceIdx); + } + + gd.data[traceIndices[i]] = plots.extendTrace(gd.data[traceIndices[i]], data[i]); + } + + // Follow the same procedure. Clone it so we don't mangle the input, then + // expand any object paths so we can merge deep into gd.layout: + var layoutUpdate = Lib.expandObjectPaths(Lib.extendDeepNoArrays({}, layout)); + + // Before merging though, we need to modify the incoming layout. We only + // know how to *transition* layout ranges, so it's imperative that a new + // range not be sent to the layout before the transition has started. So + // we must remove the things we can transition: + var axisAttrRe = /^[xy]axis[0-9]*$/; + for(var attr in layoutUpdate) { + if(!axisAttrRe.test(attr)) continue; + delete layoutUpdate[attr].range; + } + + plots.extendLayout(gd.layout, layoutUpdate); + + // Supply defaults after applying the incoming properties. Note that any attempt + // to simplify this step and reduce the amount of work resulted in the reconstruction + // of essentially the whole supplyDefaults step, so that it seems sensible to just use + // supplyDefaults even though it's heavier than would otherwise be desired for + // transitions: + + // first delete calcdata so supplyDefaults knows a calc step is coming + delete gd.calcdata; + + plots.supplyDefaults(gd); + plots.doCalcdata(gd); + + var newLayout = Lib.expandObjectPaths(layout); + + if(newLayout) { + var subplots = gd._fullLayout._plots; + + for(var k in subplots) { + var plotinfo = subplots[k]; + var xa = plotinfo.xaxis; + var ya = plotinfo.yaxis; + var xr0 = xa.range.slice(); + var yr0 = ya.range.slice(); + + var xr1 = null; + var yr1 = null; + var editX = null; + var editY = null; + + if(Array.isArray(newLayout[xa._name + '.range'])) { + xr1 = newLayout[xa._name + '.range'].slice(); + } else if(Array.isArray((newLayout[xa._name] || {}).range)) { + xr1 = newLayout[xa._name].range.slice(); + } + if(Array.isArray(newLayout[ya._name + '.range'])) { + yr1 = newLayout[ya._name + '.range'].slice(); + } else if(Array.isArray((newLayout[ya._name] || {}).range)) { + yr1 = newLayout[ya._name].range.slice(); + } + + if(xr0 && xr1 && + (xa.r2l(xr0[0]) !== xa.r2l(xr1[0]) || xa.r2l(xr0[1]) !== xa.r2l(xr1[1])) + ) { + editX = {xr0: xr0, xr1: xr1}; + } + if(yr0 && yr1 && + (ya.r2l(yr0[0]) !== ya.r2l(yr1[0]) || ya.r2l(yr0[1]) !== ya.r2l(yr1[1])) + ) { + editY = {yr0: yr0, yr1: yr1}; + } + + if(editX || editY) { + axEdits.push(Lib.extendFlat({plotinfo: plotinfo}, editX, editY)); + } + } + } + + return Promise.resolve(); + }; + + opts.runFn = function(makeCallback) { + var traceTransitionOpts; + var basePlotModules = gd._fullLayout._basePlotModules; + var hasAxisTransition = axEdits.length; + var i; + + if(layout) { + for(i = 0; i < basePlotModules.length; i++) { + if(basePlotModules[i].transitionAxes) { + basePlotModules[i].transitionAxes(gd, axEdits, transitionOpts, makeCallback); + } + } + } + + // Here handle the exception that we refuse to animate scales and axes at the same + // time. In other words, if there's an axis transition, then set the data transition + // to instantaneous. + if(hasAxisTransition) { + traceTransitionOpts = Lib.extendFlat({}, transitionOpts); + traceTransitionOpts.duration = 0; + // This means do not transition cartesian traces, + // this happens on layout-only (e.g. axis range) animations + delete transitionedTraces.cartesian; + } else { + traceTransitionOpts = transitionOpts; + } + + // Note that we pass a callback to *create* the callback that must be invoked on completion. + // This is since not all traces know about transitions, so it greatly simplifies matters if + // the trace is responsible for creating a callback, if needed, and then executing it when + // the time is right. + for(var n in transitionedTraces) { + var traceIndices = transitionedTraces[n]; + var _module = gd._fullData[traceIndices[0]]._module; + _module.basePlotModule.plot(gd, traceIndices, traceTransitionOpts, makeCallback); + } + }; + + return _transition(gd, transitionOpts, opts); +}; + +/** + * Transition to a set of new data and layout properties from Plotly.react + * + * @param {DOM element} gd + * @param {object} restyleFlags + * - anim {'all'|'some'} + * @param {object} relayoutFlags + * - anim {'all'|'some'} + * @param {object} oldFullLayout : old (pre Plotly.react) fullLayout + */ +plots.transitionFromReact = function(gd, restyleFlags, relayoutFlags, oldFullLayout) { + var fullLayout = gd._fullLayout; + var transitionOpts = fullLayout.transition; + var opts = {}; + var axEdits = []; + + opts.prepareFn = function() { + var subplots = fullLayout._plots; + + // no need to redraw at end of transition, + // if all changes are animatable + opts.redraw = false; + if(restyleFlags.anim === 'some') opts.redraw = true; + if(relayoutFlags.anim === 'some') opts.redraw = true; + + for(var k in subplots) { + var plotinfo = subplots[k]; + var xa = plotinfo.xaxis; + var ya = plotinfo.yaxis; + var xr0 = oldFullLayout[xa._name].range.slice(); + var yr0 = oldFullLayout[ya._name].range.slice(); + var xr1 = xa.range.slice(); + var yr1 = ya.range.slice(); + + xa.setScale(); + ya.setScale(); + + var editX = null; + var editY = null; + + if(xa.r2l(xr0[0]) !== xa.r2l(xr1[0]) || xa.r2l(xr0[1]) !== xa.r2l(xr1[1])) { + editX = {xr0: xr0, xr1: xr1}; + } + if(ya.r2l(yr0[0]) !== ya.r2l(yr1[0]) || ya.r2l(yr0[1]) !== ya.r2l(yr1[1])) { + editY = {yr0: yr0, yr1: yr1}; + } + + if(editX || editY) { + axEdits.push(Lib.extendFlat({plotinfo: plotinfo}, editX, editY)); + } + } + + return Promise.resolve(); + }; + + opts.runFn = function(makeCallback) { + var fullData = gd._fullData; + var fullLayout = gd._fullLayout; + var basePlotModules = fullLayout._basePlotModules; + + var axisTransitionOpts; + var traceTransitionOpts; + var transitionedTraces; + + var allTraceIndices = []; + for(var i = 0; i < fullData.length; i++) { + allTraceIndices.push(i); + } + + function transitionAxes() { + for(var j = 0; j < basePlotModules.length; j++) { + if(basePlotModules[j].transitionAxes) { + basePlotModules[j].transitionAxes(gd, axEdits, axisTransitionOpts, makeCallback); + } + } + } + + function transitionTraces() { + for(var j = 0; j < basePlotModules.length; j++) { + basePlotModules[j].plot(gd, transitionedTraces, traceTransitionOpts, makeCallback); + } + } + + if(axEdits.length && restyleFlags.anim) { + if(transitionOpts.ordering === 'traces first') { + axisTransitionOpts = Lib.extendFlat({}, transitionOpts, {duration: 0}); + transitionedTraces = allTraceIndices; + traceTransitionOpts = transitionOpts; + setTimeout(transitionAxes, transitionOpts.duration); + transitionTraces(); + } else { + axisTransitionOpts = transitionOpts; + transitionedTraces = null; + traceTransitionOpts = Lib.extendFlat({}, transitionOpts, {duration: 0}); + setTimeout(transitionTraces, axisTransitionOpts.duration); + transitionAxes(); + } + } else if(axEdits.length) { + axisTransitionOpts = transitionOpts; + transitionAxes(); + } else if(restyleFlags.anim) { + transitionedTraces = allTraceIndices; + traceTransitionOpts = transitionOpts; + transitionTraces(); + } + }; + + return _transition(gd, transitionOpts, opts); +}; + +/** + * trace/layout transition wrapper that works + * for transitions initiated by Plotly.animate and Plotly.react. + * + * @param {DOM element} gd + * @param {object} transitionOpts + * @param {object} opts + * - redraw {boolean} + * - prepareFn {function} *should return a Promise* + * - runFn {function} ran inside executeTransitions + */ +function _transition(gd, transitionOpts, opts) { + var aborted = false; + + function executeCallbacks(list) { + var p = Promise.resolve(); + if(!list) return p; + while(list.length) { + p = p.then((list.shift())); + } + return p; + } + + function flushCallbacks(list) { + if(!list) return; + while(list.length) { + list.shift(); + } + } + + function executeTransitions() { + gd.emit('plotly_transitioning', []); + + return new Promise(function(resolve) { + // This flag is used to disabled things like autorange: + gd._transitioning = true; + + // When instantaneous updates are coming through quickly, it's too much to simply disable + // all interaction, so store this flag so we can disambiguate whether mouse interactions + // should be fully disabled or not: + if(transitionOpts.duration > 0) { + gd._transitioningWithDuration = true; + } + + // If another transition is triggered, this callback will be executed simply because it's + // in the interruptCallbacks queue. If this transition completes, it will instead flush + // that queue and forget about this callback. + gd._transitionData._interruptCallbacks.push(function() { + aborted = true; + }); + + if(opts.redraw) { + gd._transitionData._interruptCallbacks.push(function() { + return Registry.call('redraw', gd); + }); + } + + // Emit this and make sure it happens last: + gd._transitionData._interruptCallbacks.push(function() { + gd.emit('plotly_transitioninterrupted', []); + }); + + // Construct callbacks that are executed on transition end. This ensures the d3 transitions + // are *complete* before anything else is done. + var numCallbacks = 0; + var numCompleted = 0; + function makeCallback() { + numCallbacks++; + return function() { + numCompleted++; + // When all are complete, perform a redraw: + if(!aborted && numCompleted === numCallbacks) { + completeTransition(resolve); + } + }; + } + + opts.runFn(makeCallback); + + // If nothing else creates a callback, then this will trigger the completion in the next tick: + setTimeout(makeCallback()); + }); + } + + function completeTransition(callback) { + // This a simple workaround for tests which purge the graph before animations + // have completed. That's not a very common case, so this is the simplest + // fix. + if(!gd._transitionData) return; + + flushCallbacks(gd._transitionData._interruptCallbacks); + + return Promise.resolve().then(function() { + if(opts.redraw) { + return Registry.call('redraw', gd); + } + }).then(function() { + // Set transitioning false again once the redraw has occurred. This is used, for example, + // to prevent the trailing redraw from autoranging: + gd._transitioning = false; + gd._transitioningWithDuration = false; + + gd.emit('plotly_transitioned', []); + }).then(callback); + } + + function interruptPreviousTransitions() { + // Fail-safe against purged plot: + if(!gd._transitionData) return; + + // If a transition is interrupted, set this to false. At the moment, the only thing that would + // interrupt a transition is another transition, so that it will momentarily be set to true + // again, but this determines whether autorange or dragbox work, so it's for the sake of + // cleanliness: + gd._transitioning = false; + + return executeCallbacks(gd._transitionData._interruptCallbacks); + } + + var seq = [ + plots.previousPromises, + interruptPreviousTransitions, + opts.prepareFn, + plots.rehover, + executeTransitions + ]; + + var transitionStarting = Lib.syncOrAsync(seq, gd); + + if(!transitionStarting || !transitionStarting.then) { + transitionStarting = Promise.resolve(); + } + + return transitionStarting.then(function() { return gd; }); +} + +plots.doCalcdata = function(gd, traces) { + var axList = axisIDs.list(gd); + var fullData = gd._fullData; + var fullLayout = gd._fullLayout; + + var trace, _module, i, j; + + // XXX: Is this correct? Needs a closer look so that *some* traces can be recomputed without + // *all* needing doCalcdata: + var calcdata = new Array(fullData.length); + var oldCalcdata = (gd.calcdata || []).slice(); + gd.calcdata = calcdata; + + // extra helper variables + + // how many box/violins plots do we have (in case they're grouped) + fullLayout._numBoxes = 0; + fullLayout._numViolins = 0; + + // initialize violin per-scale-group stats container + fullLayout._violinScaleGroupStats = {}; + + // for calculating avg luminosity of heatmaps + gd._hmpixcount = 0; + gd._hmlumcount = 0; + + // for sharing colors across pies / sunbursts / treemap / funnelarea (and for legend) + fullLayout._piecolormap = {}; + fullLayout._sunburstcolormap = {}; + fullLayout._treemapcolormap = {}; + fullLayout._funnelareacolormap = {}; + + // If traces were specified and this trace was not included, + // then transfer it over from the old calcdata: + for(i = 0; i < fullData.length; i++) { + if(Array.isArray(traces) && traces.indexOf(i) === -1) { + calcdata[i] = oldCalcdata[i]; + continue; + } + } + + for(i = 0; i < fullData.length; i++) { + trace = fullData[i]; + + trace._arrayAttrs = PlotSchema.findArrayAttributes(trace); + + // keep track of trace extremes (for autorange) in here + trace._extremes = {}; + } + + // add polar axes to axis list + var polarIds = fullLayout._subplots.polar || []; + for(i = 0; i < polarIds.length; i++) { + axList.push( + fullLayout[polarIds[i]].radialaxis, + fullLayout[polarIds[i]].angularaxis + ); + } + + var hasCalcTransform = false; + + function transformCalci(i) { + trace = fullData[i]; + _module = trace._module; + + if(trace.visible === true && trace.transforms) { + // we need one round of trace module calc before + // the calc transform to 'fill in' the categories list + // used for example in the data-to-coordinate method + if(_module && _module.calc) { + var cdi = _module.calc(gd, trace); + + // must clear scene 'batches', so that 2nd + // _module.calc call starts from scratch + if(cdi[0] && cdi[0].t && cdi[0].t._scene) { + delete cdi[0].t._scene.dirty; + } + } + + for(j = 0; j < trace.transforms.length; j++) { + var transform = trace.transforms[j]; + + _module = transformsRegistry[transform.type]; + if(_module && _module.calcTransform) { + trace._hasCalcTransform = true; + hasCalcTransform = true; + _module.calcTransform(gd, trace, transform); + } + } + } + } + + function calci(i, isContainer) { + trace = fullData[i]; + _module = trace._module; + + if(!!_module.isContainer !== isContainer) return; + + var cd = []; + + if(trace.visible === true && trace._length !== 0) { + // clear existing ref in case it got relinked + delete trace._indexToPoints; + // keep ref of index-to-points map object of the *last* enabled transform, + // this index-to-points map object is required to determine the calcdata indices + // that correspond to input indices (e.g. from 'selectedpoints') + var transforms = trace.transforms || []; + for(j = transforms.length - 1; j >= 0; j--) { + if(transforms[j].enabled) { + trace._indexToPoints = transforms[j]._indexToPoints; + break; + } + } + + if(_module && _module.calc) { + cd = _module.calc(gd, trace); + } + } + + // Make sure there is a first point. + // + // This ensures there is a calcdata item for every trace, + // even if cartesian logic doesn't handle it (for things like legends). + if(!Array.isArray(cd) || !cd[0]) { + cd = [{x: BADNUM, y: BADNUM}]; + } + + // add the trace-wide properties to the first point, + // per point properties to every point + // t is the holder for trace-wide properties + if(!cd[0].t) cd[0].t = {}; + cd[0].trace = trace; + + calcdata[i] = cd; + } + + setupAxisCategories(axList, fullData); + + // 'transform' loop - must calc container traces first + // so that if their dependent traces can get transform properly + for(i = 0; i < fullData.length; i++) calci(i, true); + for(i = 0; i < fullData.length; i++) transformCalci(i); + + // clear stuff that should recomputed in 'regular' loop + if(hasCalcTransform) setupAxisCategories(axList, fullData); + + // 'regular' loop - make sure container traces (eg carpet) calc before + // contained traces (eg contourcarpet) + for(i = 0; i < fullData.length; i++) calci(i, true); + for(i = 0; i < fullData.length; i++) calci(i, false); + + doCrossTraceCalc(gd); + + // Sort axis categories per value if specified + var sorted = sortAxisCategoriesByValue(axList, gd); + if(sorted.length) { + // how many box/violins plots do we have (in case they're grouped) + fullLayout._numBoxes = 0; + fullLayout._numViolins = 0; + // If a sort operation was performed, run calc() again + for(i = 0; i < sorted.length; i++) calci(sorted[i], true); + for(i = 0; i < sorted.length; i++) calci(sorted[i], false); + doCrossTraceCalc(gd); + } + + Registry.getComponentMethod('fx', 'calc')(gd); + Registry.getComponentMethod('errorbars', 'calc')(gd); +}; + +var sortAxisCategoriesByValueRegex = /(total|sum|min|max|mean|median) (ascending|descending)/; + +function sortAxisCategoriesByValue(axList, gd) { + var affectedTraces = []; + var i, j, k, l, o; + + function zMapCategory(type, ax, value) { + var axLetter = ax._id.charAt(0); + if(type === 'histogram2dcontour') { + var counterAxLetter = ax._counterAxes[0]; + var counterAx = axisIDs.getFromId(gd, counterAxLetter); + + var xCategorical = axLetter === 'x' || (counterAxLetter === 'x' && counterAx.type === 'category'); + var yCategorical = axLetter === 'y' || (counterAxLetter === 'y' && counterAx.type === 'category'); + + return function(o, l) { + if(o === 0 || l === 0) return -1; // Skip first row and column + if(xCategorical && o === value[l].length - 1) return -1; + if(yCategorical && l === value.length - 1) return -1; + + return (axLetter === 'y' ? l : o) - 1; + }; + } else { + return function(o, l) { + return axLetter === 'y' ? l : o; + }; + } + } + + var aggFn = { + 'min': function(values) {return Lib.aggNums(Math.min, null, values);}, + 'max': function(values) {return Lib.aggNums(Math.max, null, values);}, + 'sum': function(values) {return Lib.aggNums(function(a, b) { return a + b;}, null, values);}, + 'total': function(values) {return Lib.aggNums(function(a, b) { return a + b;}, null, values);}, + 'mean': function(values) {return Lib.mean(values);}, + 'median': function(values) {return Lib.median(values);} + }; + + for(i = 0; i < axList.length; i++) { + var ax = axList[i]; + if(ax.type !== 'category') continue; + + // Order by value + var match = ax.categoryorder.match(sortAxisCategoriesByValueRegex); + if(match) { + var aggregator = match[1]; + var order = match[2]; + + // Store values associated with each category + var categoriesValue = []; + for(j = 0; j < ax._categories.length; j++) { + categoriesValue.push([ax._categories[j], []]); + } + + // Collect values across traces + for(j = 0; j < ax._traceIndices.length; j++) { + var traceIndex = ax._traceIndices[j]; + var fullTrace = gd._fullData[traceIndex]; + var axLetter = ax._id.charAt(0); + + // Skip over invisible traces + if(fullTrace.visible !== true) continue; + + var type = fullTrace.type; + if(Registry.traceIs(fullTrace, 'histogram')) { + delete fullTrace._xautoBinFinished; + delete fullTrace._yautoBinFinished; + } + + var cd = gd.calcdata[traceIndex]; + for(k = 0; k < cd.length; k++) { + var cdi = cd[k]; + var cat, catIndex, value; + + if(type === 'splom') { + // If `splom`, collect values across dimensions + // Find which dimension the current axis is representing + var currentDimensionIndex = fullTrace._axesDim[ax._id]; + + // Apply logic to associated x axis if it's defined + if(axLetter === 'y') { + var associatedXAxisID = fullTrace._diag[currentDimensionIndex][0]; + if(associatedXAxisID) ax = gd._fullLayout[axisIDs.id2name(associatedXAxisID)]; + } + + var categories = cdi.trace.dimensions[currentDimensionIndex].values; + for(l = 0; l < categories.length; l++) { + cat = categories[l]; + catIndex = ax._categoriesMap[cat]; + + // Collect associated values at index `l` over all other dimensions + for(o = 0; o < cdi.trace.dimensions.length; o++) { + if(o === currentDimensionIndex) continue; + var dimension = cdi.trace.dimensions[o]; + categoriesValue[catIndex][1].push(dimension.values[l]); + } + } + } else if(type === 'scattergl') { + // If `scattergl`, collect all values stashed under cdi.t + for(l = 0; l < cdi.t.x.length; l++) { + if(axLetter === 'x') { + cat = cdi.t.x[l]; + catIndex = cat; + value = cdi.t.y[l]; + } + + if(axLetter === 'y') { + cat = cdi.t.y[l]; + catIndex = cat; + value = cdi.t.x[l]; + } + categoriesValue[catIndex][1].push(value); + } + // must clear scene 'batches', so that 2nd + // _module.calc call starts from scratch + if(cdi.t && cdi.t._scene) { + delete cdi.t._scene.dirty; + } + } else if(cdi.hasOwnProperty('z')) { + // If 2dMap, collect values in `z` + value = cdi.z; + var mapping = zMapCategory(fullTrace.type, ax, value); + + for(l = 0; l < value.length; l++) { + for(o = 0; o < value[l].length; o++) { + catIndex = mapping(o, l); + if(catIndex + 1) categoriesValue[catIndex][1].push(value[l][o]); + } + } + } else { + // For all other 2d cartesian traces + if(axLetter === 'x') { + cat = cdi.p + 1 ? cdi.p : cdi.x; + value = cdi.s || cdi.v || cdi.y; + } else if(axLetter === 'y') { + cat = cdi.p + 1 ? cdi.p : cdi.y; + value = cdi.s || cdi.v || cdi.x; + } + if(!Array.isArray(value)) value = [value]; + for(l = 0; l < value.length; l++) { + categoriesValue[cat][1].push(value[l]); + } + } + } + } + + ax._categoriesValue = categoriesValue; + + var categoriesAggregatedValue = []; + for(j = 0; j < categoriesValue.length; j++) { + categoriesAggregatedValue.push([ + categoriesValue[j][0], + aggFn[aggregator](categoriesValue[j][1]) + ]); + } + + // Sort by aggregated value + categoriesAggregatedValue.sort(function(a, b) { + return a[1] - b[1]; + }); + + ax._categoriesAggregatedValue = categoriesAggregatedValue; + + // Set new category order + ax._initialCategories = categoriesAggregatedValue.map(function(c) { + return c[0]; + }); + + // Reverse if descending + if(order === 'descending') { + ax._initialCategories.reverse(); + } + + // Sort all matching axes + affectedTraces = affectedTraces.concat(ax.sortByInitialCategories()); + } + } + return affectedTraces; +} + +function setupAxisCategories(axList, fullData) { + for(var i = 0; i < axList.length; i++) { + var ax = axList[i]; + ax.clearCalc(); + if(ax.type === 'multicategory') { + ax.setupMultiCategory(fullData); + } + } +} + +function doCrossTraceCalc(gd) { + var fullLayout = gd._fullLayout; + var modules = fullLayout._visibleModules; + var hash = {}; + var i, j, k; + + // position and range calculations for traces that + // depend on each other ie bars (stacked or grouped) + // and boxes (grouped) push each other out of the way + + for(j = 0; j < modules.length; j++) { + var _module = modules[j]; + var fn = _module.crossTraceCalc; + if(fn) { + var spType = _module.basePlotModule.name; + if(hash[spType]) { + Lib.pushUnique(hash[spType], fn); + } else { + hash[spType] = [fn]; + } + } + } + + for(k in hash) { + var methods = hash[k]; + var subplots = fullLayout._subplots[k]; + + if(Array.isArray(subplots)) { + for(i = 0; i < subplots.length; i++) { + var sp = subplots[i]; + var spInfo = k === 'cartesian' ? + fullLayout._plots[sp] : + fullLayout[sp]; + + for(j = 0; j < methods.length; j++) { + methods[j](gd, spInfo, sp); + } + } + } else { + for(j = 0; j < methods.length; j++) { + methods[j](gd); + } + } + } +} + +plots.rehover = function(gd) { + if(gd._fullLayout._rehover) { + gd._fullLayout._rehover(); + } +}; + +plots.redrag = function(gd) { + if(gd._fullLayout._redrag) { + gd._fullLayout._redrag(); + } +}; + +plots.generalUpdatePerTraceModule = function(gd, subplot, subplotCalcData, subplotLayout) { + var traceHashOld = subplot.traceHash; + var traceHash = {}; + var i; + + // build up moduleName -> calcData hash + for(i = 0; i < subplotCalcData.length; i++) { + var calcTraces = subplotCalcData[i]; + var trace = calcTraces[0].trace; + + // skip over visible === false traces + // as they don't have `_module` ref + if(trace.visible) { + traceHash[trace.type] = traceHash[trace.type] || []; + traceHash[trace.type].push(calcTraces); + } + } + + // when a trace gets deleted, make sure that its module's + // plot method is called so that it is properly + // removed from the DOM. + for(var moduleNameOld in traceHashOld) { + if(!traceHash[moduleNameOld]) { + var fakeCalcTrace = traceHashOld[moduleNameOld][0]; + var fakeTrace = fakeCalcTrace[0].trace; + + fakeTrace.visible = false; + traceHash[moduleNameOld] = [fakeCalcTrace]; + } + } + + // call module plot method + for(var moduleName in traceHash) { + var moduleCalcData = traceHash[moduleName]; + var _module = moduleCalcData[0][0].trace._module; + + _module.plot(gd, subplot, Lib.filterVisible(moduleCalcData), subplotLayout); + } + + // update moduleName -> calcData hash + subplot.traceHash = traceHash; +}; + +plots.plotBasePlot = function(desiredType, gd, traces, transitionOpts, makeOnCompleteCallback) { + var _module = Registry.getModule(desiredType); + var cdmodule = getModuleCalcData(gd.calcdata, _module)[0]; + _module.plot(gd, cdmodule, transitionOpts, makeOnCompleteCallback); +}; + +plots.cleanBasePlot = function(desiredType, newFullData, newFullLayout, oldFullData, oldFullLayout) { + var had = (oldFullLayout._has && oldFullLayout._has(desiredType)); + var has = (newFullLayout._has && newFullLayout._has(desiredType)); + + if(had && !has) { + oldFullLayout['_' + desiredType + 'layer'].selectAll('g.trace').remove(); + } +}; + +},{"../components/color":51,"../constants/numerical":149,"../lib":169,"../plot_api/plot_schema":202,"../plot_api/plot_template":203,"../plots/get_data":241,"../registry":258,"./animation_attributes":208,"./attributes":210,"./cartesian/axis_ids":216,"./command":237,"./font_attributes":239,"./frame_attributes":240,"./layout_attributes":243,"d3":16,"fast-isnumeric":18}],246:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var scatterAttrs = _dereq_('../../../traces/scatter/attributes'); +var scatterMarkerAttrs = scatterAttrs.marker; +var extendFlat = _dereq_('../../../lib/extend').extendFlat; + +var deprecationWarning = [ + 'Area traces are deprecated!', + 'Please switch to the *barpolar* trace type.' +].join(' '); + +module.exports = { + r: extendFlat({}, scatterAttrs.r, { + + }), + t: extendFlat({}, scatterAttrs.t, { + + }), + marker: { + color: extendFlat({}, scatterMarkerAttrs.color, { + + }), + size: extendFlat({}, scatterMarkerAttrs.size, { + + }), + symbol: extendFlat({}, scatterMarkerAttrs.symbol, { + + }), + opacity: extendFlat({}, scatterMarkerAttrs.opacity, { + + }), + editType: 'calc' + } +}; + +},{"../../../lib/extend":164,"../../../traces/scatter/attributes":376}],247:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var axesAttrs = _dereq_('../../cartesian/layout_attributes'); +var extendFlat = _dereq_('../../../lib/extend').extendFlat; +var overrideAll = _dereq_('../../../plot_api/edit_types').overrideAll; + +var deprecationWarning = [ + 'Legacy polar charts are deprecated!', + 'Please switch to *polar* subplots.' +].join(' '); + +var domainAttr = extendFlat({}, axesAttrs.domain, { + +}); + +function mergeAttrs(axisName, nonCommonAttrs) { + var commonAttrs = { + showline: { + valType: 'boolean', + + + }, + showticklabels: { + valType: 'boolean', + + + }, + tickorientation: { + valType: 'enumerated', + values: ['horizontal', 'vertical'], + + + }, + ticklen: { + valType: 'number', + min: 0, + + + }, + tickcolor: { + valType: 'color', + + + }, + ticksuffix: { + valType: 'string', + + + }, + endpadding: { + valType: 'number', + + description: deprecationWarning, + }, + visible: { + valType: 'boolean', + + + } + }; + + return extendFlat({}, nonCommonAttrs, commonAttrs); +} + +module.exports = overrideAll({ + radialaxis: mergeAttrs('radial', { + range: { + valType: 'info_array', + + items: [ + { valType: 'number' }, + { valType: 'number' } + ], + + }, + domain: domainAttr, + orientation: { + valType: 'number', + + + } + }), + + angularaxis: mergeAttrs('angular', { + range: { + valType: 'info_array', + + items: [ + { valType: 'number', dflt: 0 }, + { valType: 'number', dflt: 360 } + ], + + }, + domain: domainAttr + }), + + // attributes that appear at layout root + layout: { + direction: { + valType: 'enumerated', + values: ['clockwise', 'counterclockwise'], + + + }, + orientation: { + valType: 'angle', + + + } + } +}, 'plot', 'nested'); + +},{"../../../lib/extend":164,"../../../plot_api/edit_types":196,"../../cartesian/layout_attributes":225}],248:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Polar = module.exports = _dereq_('./micropolar'); + +Polar.manager = _dereq_('./micropolar_manager'); + +},{"./micropolar":249,"./micropolar_manager":250}],249:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +var d3 = _dereq_('d3'); +var Lib = _dereq_('../../../lib'); +var extendDeepAll = Lib.extendDeepAll; +var MID_SHIFT = _dereq_('../../../constants/alignment').MID_SHIFT; + +var µ = module.exports = { version: '0.2.2' }; + +µ.Axis = function module() { + var config = { + data: [], + layout: {} + }, inputConfig = {}, liveConfig = {}; + var svg, container, dispatch = d3.dispatch('hover'), radialScale, angularScale; + var exports = {}; + function render(_container) { + container = _container || container; + var data = config.data; + var axisConfig = config.layout; + if (typeof container == 'string' || container.nodeName) container = d3.select(container); + container.datum(data).each(function(_data, _index) { + var dataOriginal = _data.slice(); + liveConfig = { + data: µ.util.cloneJson(dataOriginal), + layout: µ.util.cloneJson(axisConfig) + }; + var colorIndex = 0; + dataOriginal.forEach(function(d, i) { + if (!d.color) { + d.color = axisConfig.defaultColorRange[colorIndex]; + colorIndex = (colorIndex + 1) % axisConfig.defaultColorRange.length; + } + if (!d.strokeColor) { + d.strokeColor = d.geometry === 'LinePlot' ? d.color : d3.rgb(d.color).darker().toString(); + } + liveConfig.data[i].color = d.color; + liveConfig.data[i].strokeColor = d.strokeColor; + liveConfig.data[i].strokeDash = d.strokeDash; + liveConfig.data[i].strokeSize = d.strokeSize; + }); + var data = dataOriginal.filter(function(d, i) { + var visible = d.visible; + return typeof visible === 'undefined' || visible === true; + }); + var isStacked = false; + var dataWithGroupId = data.map(function(d, i) { + isStacked = isStacked || typeof d.groupId !== 'undefined'; + return d; + }); + if (isStacked) { + var grouped = d3.nest().key(function(d, i) { + return typeof d.groupId != 'undefined' ? d.groupId : 'unstacked'; + }).entries(dataWithGroupId); + var dataYStack = []; + var stacked = grouped.map(function(d, i) { + if (d.key === 'unstacked') return d.values; else { + var prevArray = d.values[0].r.map(function(d, i) { + return 0; + }); + d.values.forEach(function(d, i, a) { + d.yStack = [ prevArray ]; + dataYStack.push(prevArray); + prevArray = µ.util.sumArrays(d.r, prevArray); + }); + return d.values; + } + }); + data = d3.merge(stacked); + } + data.forEach(function(d, i) { + d.t = Array.isArray(d.t[0]) ? d.t : [ d.t ]; + d.r = Array.isArray(d.r[0]) ? d.r : [ d.r ]; + }); + var radius = Math.min(axisConfig.width - axisConfig.margin.left - axisConfig.margin.right, axisConfig.height - axisConfig.margin.top - axisConfig.margin.bottom) / 2; + radius = Math.max(10, radius); + var chartCenter = [ axisConfig.margin.left + radius, axisConfig.margin.top + radius ]; + var extent; + if (isStacked) { + var highestStackedValue = d3.max(µ.util.sumArrays(µ.util.arrayLast(data).r[0], µ.util.arrayLast(dataYStack))); + extent = [ 0, highestStackedValue ]; + } else extent = d3.extent(µ.util.flattenArray(data.map(function(d, i) { + return d.r; + }))); + if (axisConfig.radialAxis.domain != µ.DATAEXTENT) extent[0] = 0; + radialScale = d3.scale.linear().domain(axisConfig.radialAxis.domain != µ.DATAEXTENT && axisConfig.radialAxis.domain ? axisConfig.radialAxis.domain : extent).range([ 0, radius ]); + liveConfig.layout.radialAxis.domain = radialScale.domain(); + var angularDataMerged = µ.util.flattenArray(data.map(function(d, i) { + return d.t; + })); + var isOrdinal = typeof angularDataMerged[0] === 'string'; + var ticks; + if (isOrdinal) { + angularDataMerged = µ.util.deduplicate(angularDataMerged); + ticks = angularDataMerged.slice(); + angularDataMerged = d3.range(angularDataMerged.length); + data = data.map(function(d, i) { + var result = d; + d.t = [ angularDataMerged ]; + if (isStacked) result.yStack = d.yStack; + return result; + }); + } + var hasOnlyLineOrDotPlot = data.filter(function(d, i) { + return d.geometry === 'LinePlot' || d.geometry === 'DotPlot'; + }).length === data.length; + var needsEndSpacing = axisConfig.needsEndSpacing === null ? isOrdinal || !hasOnlyLineOrDotPlot : axisConfig.needsEndSpacing; + var useProvidedDomain = axisConfig.angularAxis.domain && axisConfig.angularAxis.domain != µ.DATAEXTENT && !isOrdinal && axisConfig.angularAxis.domain[0] >= 0; + var angularDomain = useProvidedDomain ? axisConfig.angularAxis.domain : d3.extent(angularDataMerged); + var angularDomainStep = Math.abs(angularDataMerged[1] - angularDataMerged[0]); + if (hasOnlyLineOrDotPlot && !isOrdinal) angularDomainStep = 0; + var angularDomainWithPadding = angularDomain.slice(); + if (needsEndSpacing && isOrdinal) angularDomainWithPadding[1] += angularDomainStep; + var tickCount = axisConfig.angularAxis.ticksCount || 4; + if (tickCount > 8) tickCount = tickCount / (tickCount / 8) + tickCount % 8; + if (axisConfig.angularAxis.ticksStep) { + tickCount = (angularDomainWithPadding[1] - angularDomainWithPadding[0]) / tickCount; + } + var angularTicksStep = axisConfig.angularAxis.ticksStep || (angularDomainWithPadding[1] - angularDomainWithPadding[0]) / (tickCount * (axisConfig.minorTicks + 1)); + if (ticks) angularTicksStep = Math.max(Math.round(angularTicksStep), 1); + if (!angularDomainWithPadding[2]) angularDomainWithPadding[2] = angularTicksStep; + var angularAxisRange = d3.range.apply(this, angularDomainWithPadding); + angularAxisRange = angularAxisRange.map(function(d, i) { + return parseFloat(d.toPrecision(12)); + }); + angularScale = d3.scale.linear().domain(angularDomainWithPadding.slice(0, 2)).range(axisConfig.direction === 'clockwise' ? [ 0, 360 ] : [ 360, 0 ]); + liveConfig.layout.angularAxis.domain = angularScale.domain(); + liveConfig.layout.angularAxis.endPadding = needsEndSpacing ? angularDomainStep : 0; + svg = d3.select(this).select('svg.chart-root'); + if (typeof svg === 'undefined' || svg.empty()) { + var skeleton = "' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '"; + var doc = new DOMParser().parseFromString(skeleton, 'application/xml'); + var newSvg = this.appendChild(this.ownerDocument.importNode(doc.documentElement, true)); + svg = d3.select(newSvg); + } + svg.select('.guides-group').style({ + 'pointer-events': 'none' + }); + svg.select('.angular.axis-group').style({ + 'pointer-events': 'none' + }); + svg.select('.radial.axis-group').style({ + 'pointer-events': 'none' + }); + var chartGroup = svg.select('.chart-group'); + var lineStyle = { + fill: 'none', + stroke: axisConfig.tickColor + }; + var fontStyle = { + 'font-size': axisConfig.font.size, + 'font-family': axisConfig.font.family, + fill: axisConfig.font.color, + 'text-shadow': [ '-1px 0px', '1px -1px', '-1px 1px', '1px 1px' ].map(function(d, i) { + return ' ' + d + ' 0 ' + axisConfig.font.outlineColor; + }).join(',') + }; + var legendContainer; + if (axisConfig.showLegend) { + legendContainer = svg.select('.legend-group').attr({ + transform: 'translate(' + [ radius, axisConfig.margin.top ] + ')' + }).style({ + display: 'block' + }); + var elements = data.map(function(d, i) { + var datumClone = µ.util.cloneJson(d); + datumClone.symbol = d.geometry === 'DotPlot' ? d.dotType || 'circle' : d.geometry != 'LinePlot' ? 'square' : 'line'; + datumClone.visibleInLegend = typeof d.visibleInLegend === 'undefined' || d.visibleInLegend; + datumClone.color = d.geometry === 'LinePlot' ? d.strokeColor : d.color; + return datumClone; + }); + + µ.Legend().config({ + data: data.map(function(d, i) { + return d.name || 'Element' + i; + }), + legendConfig: extendDeepAll({}, + µ.Legend.defaultConfig().legendConfig, + { + container: legendContainer, + elements: elements, + reverseOrder: axisConfig.legend.reverseOrder + } + ) + })(); + + var legendBBox = legendContainer.node().getBBox(); + radius = Math.min(axisConfig.width - legendBBox.width - axisConfig.margin.left - axisConfig.margin.right, axisConfig.height - axisConfig.margin.top - axisConfig.margin.bottom) / 2; + radius = Math.max(10, radius); + chartCenter = [ axisConfig.margin.left + radius, axisConfig.margin.top + radius ]; + radialScale.range([ 0, radius ]); + liveConfig.layout.radialAxis.domain = radialScale.domain(); + legendContainer.attr('transform', 'translate(' + [ chartCenter[0] + radius, chartCenter[1] - radius ] + ')'); + } else { + legendContainer = svg.select('.legend-group').style({ + display: 'none' + }); + } + svg.attr({ + width: axisConfig.width, + height: axisConfig.height + }).style({ + opacity: axisConfig.opacity + }); + chartGroup.attr('transform', 'translate(' + chartCenter + ')').style({ + cursor: 'crosshair' + }); + var centeringOffset = [ (axisConfig.width - (axisConfig.margin.left + axisConfig.margin.right + radius * 2 + (legendBBox ? legendBBox.width : 0))) / 2, (axisConfig.height - (axisConfig.margin.top + axisConfig.margin.bottom + radius * 2)) / 2 ]; + centeringOffset[0] = Math.max(0, centeringOffset[0]); + centeringOffset[1] = Math.max(0, centeringOffset[1]); + svg.select('.outer-group').attr('transform', 'translate(' + centeringOffset + ')'); + if (axisConfig.title && axisConfig.title.text) { + var title = svg.select('g.title-group text').style(fontStyle).text(axisConfig.title.text); + var titleBBox = title.node().getBBox(); + title.attr({ + x: chartCenter[0] - titleBBox.width / 2, + y: chartCenter[1] - radius - 20 + }); + } + var radialAxis = svg.select('.radial.axis-group'); + if (axisConfig.radialAxis.gridLinesVisible) { + var gridCircles = radialAxis.selectAll('circle.grid-circle').data(radialScale.ticks(5)); + gridCircles.enter().append('circle').attr({ + 'class': 'grid-circle' + }).style(lineStyle); + gridCircles.attr('r', radialScale); + gridCircles.exit().remove(); + } + radialAxis.select('circle.outside-circle').attr({ + r: radius + }).style(lineStyle); + var backgroundCircle = svg.select('circle.background-circle').attr({ + r: radius + }).style({ + fill: axisConfig.backgroundColor, + stroke: axisConfig.stroke + }); + function currentAngle(d, i) { + return angularScale(d) % 360 + axisConfig.orientation; + } + if (axisConfig.radialAxis.visible) { + var axis = d3.svg.axis().scale(radialScale).ticks(5).tickSize(5); + radialAxis.call(axis).attr({ + transform: 'rotate(' + axisConfig.radialAxis.orientation + ')' + }); + radialAxis.selectAll('.domain').style(lineStyle); + radialAxis.selectAll('g>text').text(function(d, i) { + return this.textContent + axisConfig.radialAxis.ticksSuffix; + }).style(fontStyle).style({ + 'text-anchor': 'start' + }).attr({ + x: 0, + y: 0, + dx: 0, + dy: 0, + transform: function(d, i) { + if (axisConfig.radialAxis.tickOrientation === 'horizontal') { + return 'rotate(' + -axisConfig.radialAxis.orientation + ') translate(' + [ 0, fontStyle['font-size'] ] + ')'; + } else return 'translate(' + [ 0, fontStyle['font-size'] ] + ')'; + } + }); + radialAxis.selectAll('g>line').style({ + stroke: 'black' + }); + } + var angularAxis = svg.select('.angular.axis-group').selectAll('g.angular-tick').data(angularAxisRange); + var angularAxisEnter = angularAxis.enter().append('g').classed('angular-tick', true); + angularAxis.attr({ + transform: function(d, i) { + return 'rotate(' + currentAngle(d, i) + ')'; + } + }).style({ + display: axisConfig.angularAxis.visible ? 'block' : 'none' + }); + angularAxis.exit().remove(); + angularAxisEnter.append('line').classed('grid-line', true).classed('major', function(d, i) { + return i % (axisConfig.minorTicks + 1) == 0; + }).classed('minor', function(d, i) { + return !(i % (axisConfig.minorTicks + 1) == 0); + }).style(lineStyle); + angularAxisEnter.selectAll('.minor').style({ + stroke: axisConfig.minorTickColor + }); + angularAxis.select('line.grid-line').attr({ + x1: axisConfig.tickLength ? radius - axisConfig.tickLength : 0, + x2: radius + }).style({ + display: axisConfig.angularAxis.gridLinesVisible ? 'block' : 'none' + }); + angularAxisEnter.append('text').classed('axis-text', true).style(fontStyle); + var ticksText = angularAxis.select('text.axis-text').attr({ + x: radius + axisConfig.labelOffset, + dy: MID_SHIFT + 'em', + transform: function(d, i) { + var angle = currentAngle(d, i); + var rad = radius + axisConfig.labelOffset; + var orient = axisConfig.angularAxis.tickOrientation; + if (orient == 'horizontal') return 'rotate(' + -angle + ' ' + rad + ' 0)'; else if (orient == 'radial') return angle < 270 && angle > 90 ? 'rotate(180 ' + rad + ' 0)' : null; else return 'rotate(' + (angle <= 180 && angle > 0 ? -90 : 90) + ' ' + rad + ' 0)'; + } + }).style({ + 'text-anchor': 'middle', + display: axisConfig.angularAxis.labelsVisible ? 'block' : 'none' + }).text(function(d, i) { + if (i % (axisConfig.minorTicks + 1) != 0) return ''; + if (ticks) { + return ticks[d] + axisConfig.angularAxis.ticksSuffix; + } else return d + axisConfig.angularAxis.ticksSuffix; + }).style(fontStyle); + if (axisConfig.angularAxis.rewriteTicks) ticksText.text(function(d, i) { + if (i % (axisConfig.minorTicks + 1) != 0) return ''; + return axisConfig.angularAxis.rewriteTicks(this.textContent, i); + }); + var rightmostTickEndX = d3.max(chartGroup.selectAll('.angular-tick text')[0].map(function(d, i) { + return d.getCTM().e + d.getBBox().width; + })); + legendContainer.attr({ + transform: 'translate(' + [ radius + rightmostTickEndX, axisConfig.margin.top ] + ')' + }); + var hasGeometry = svg.select('g.geometry-group').selectAll('g').size() > 0; + var geometryContainer = svg.select('g.geometry-group').selectAll('g.geometry').data(data); + geometryContainer.enter().append('g').attr({ + 'class': function(d, i) { + return 'geometry geometry' + i; + } + }); + geometryContainer.exit().remove(); + if (data[0] || hasGeometry) { + var geometryConfigs = []; + data.forEach(function(d, i) { + var geometryConfig = {}; + geometryConfig.radialScale = radialScale; + geometryConfig.angularScale = angularScale; + geometryConfig.container = geometryContainer.filter(function(dB, iB) { + return iB == i; + }); + geometryConfig.geometry = d.geometry; + geometryConfig.orientation = axisConfig.orientation; + geometryConfig.direction = axisConfig.direction; + geometryConfig.index = i; + geometryConfigs.push({ + data: d, + geometryConfig: geometryConfig + }); + }); + var geometryConfigsGrouped = d3.nest().key(function(d, i) { + return typeof d.data.groupId != 'undefined' || 'unstacked'; + }).entries(geometryConfigs); + var geometryConfigsGrouped2 = []; + geometryConfigsGrouped.forEach(function(d, i) { + if (d.key === 'unstacked') geometryConfigsGrouped2 = geometryConfigsGrouped2.concat(d.values.map(function(d, i) { + return [ d ]; + })); else geometryConfigsGrouped2.push(d.values); + }); + geometryConfigsGrouped2.forEach(function(d, i) { + var geometry; + if (Array.isArray(d)) geometry = d[0].geometryConfig.geometry; else geometry = d.geometryConfig.geometry; + var finalGeometryConfig = d.map(function(dB, iB) { + return extendDeepAll(µ[geometry].defaultConfig(), dB); + }); + µ[geometry]().config(finalGeometryConfig)(); + }); + } + var guides = svg.select('.guides-group'); + var tooltipContainer = svg.select('.tooltips-group'); + var angularTooltip = µ.tooltipPanel().config({ + container: tooltipContainer, + fontSize: 8 + })(); + var radialTooltip = µ.tooltipPanel().config({ + container: tooltipContainer, + fontSize: 8 + })(); + var geometryTooltip = µ.tooltipPanel().config({ + container: tooltipContainer, + hasTick: true + })(); + var angularValue, radialValue; + if (!isOrdinal) { + var angularGuideLine = guides.select('line').attr({ + x1: 0, + y1: 0, + y2: 0 + }).style({ + stroke: 'grey', + 'pointer-events': 'none' + }); + chartGroup.on('mousemove.angular-guide', function(d, i) { + var mouseAngle = µ.util.getMousePos(backgroundCircle).angle; + angularGuideLine.attr({ + x2: -radius, + transform: 'rotate(' + mouseAngle + ')' + }).style({ + opacity: .5 + }); + var angleWithOriginOffset = (mouseAngle + 180 + 360 - axisConfig.orientation) % 360; + angularValue = angularScale.invert(angleWithOriginOffset); + var pos = µ.util.convertToCartesian(radius + 12, mouseAngle + 180); + angularTooltip.text(µ.util.round(angularValue)).move([ pos[0] + chartCenter[0], pos[1] + chartCenter[1] ]); + }).on('mouseout.angular-guide', function(d, i) { + guides.select('line').style({ + opacity: 0 + }); + }); + } + var angularGuideCircle = guides.select('circle').style({ + stroke: 'grey', + fill: 'none' + }); + chartGroup.on('mousemove.radial-guide', function(d, i) { + var r = µ.util.getMousePos(backgroundCircle).radius; + angularGuideCircle.attr({ + r: r + }).style({ + opacity: .5 + }); + radialValue = radialScale.invert(µ.util.getMousePos(backgroundCircle).radius); + var pos = µ.util.convertToCartesian(r, axisConfig.radialAxis.orientation); + radialTooltip.text(µ.util.round(radialValue)).move([ pos[0] + chartCenter[0], pos[1] + chartCenter[1] ]); + }).on('mouseout.radial-guide', function(d, i) { + angularGuideCircle.style({ + opacity: 0 + }); + geometryTooltip.hide(); + angularTooltip.hide(); + radialTooltip.hide(); + }); + svg.selectAll('.geometry-group .mark').on('mouseover.tooltip', function(d, i) { + var el = d3.select(this); + var color = this.style.fill; + var newColor = 'black'; + var opacity = this.style.opacity || 1; + el.attr({ + 'data-opacity': opacity + }); + if (color && color !== 'none') { + el.attr({ + 'data-fill': color + }); + newColor = d3.hsl(color).darker().toString(); + el.style({ + fill: newColor, + opacity: 1 + }); + var textData = { + t: µ.util.round(d[0]), + r: µ.util.round(d[1]) + }; + if (isOrdinal) textData.t = ticks[d[0]]; + var text = 't: ' + textData.t + ', r: ' + textData.r; + var bbox = this.getBoundingClientRect(); + var svgBBox = svg.node().getBoundingClientRect(); + var pos = [ bbox.left + bbox.width / 2 - centeringOffset[0] - svgBBox.left, bbox.top + bbox.height / 2 - centeringOffset[1] - svgBBox.top ]; + geometryTooltip.config({ + color: newColor + }).text(text); + geometryTooltip.move(pos); + } else { + color = this.style.stroke || 'black'; + el.attr({ + 'data-stroke': color + }); + newColor = d3.hsl(color).darker().toString(); + el.style({ + stroke: newColor, + opacity: 1 + }); + } + }).on('mousemove.tooltip', function(d, i) { + if (d3.event.which != 0) return false; + if (d3.select(this).attr('data-fill')) geometryTooltip.show(); + }).on('mouseout.tooltip', function(d, i) { + geometryTooltip.hide(); + var el = d3.select(this); + var fillColor = el.attr('data-fill'); + if (fillColor) el.style({ + fill: fillColor, + opacity: el.attr('data-opacity') + }); else el.style({ + stroke: el.attr('data-stroke'), + opacity: el.attr('data-opacity') + }); + }); + }); + return exports; + } + exports.render = function(_container) { + render(_container); + return this; + }; + exports.config = function(_x) { + if (!arguments.length) return config; + var xClone = µ.util.cloneJson(_x); + xClone.data.forEach(function(d, i) { + if (!config.data[i]) config.data[i] = {}; + extendDeepAll(config.data[i], µ.Axis.defaultConfig().data[0]); + extendDeepAll(config.data[i], d); + }); + extendDeepAll(config.layout, µ.Axis.defaultConfig().layout); + extendDeepAll(config.layout, xClone.layout); + return this; + }; + exports.getLiveConfig = function() { + return liveConfig; + }; + exports.getinputConfig = function() { + return inputConfig; + }; + exports.radialScale = function(_x) { + return radialScale; + }; + exports.angularScale = function(_x) { + return angularScale; + }; + exports.svg = function() { + return svg; + }; + d3.rebind(exports, dispatch, 'on'); + return exports; +}; + +µ.Axis.defaultConfig = function(d, i) { + var config = { + data: [ { + t: [ 1, 2, 3, 4 ], + r: [ 10, 11, 12, 13 ], + name: 'Line1', + geometry: 'LinePlot', + color: null, + strokeDash: 'solid', + strokeColor: null, + strokeSize: '1', + visibleInLegend: true, + opacity: 1 + } ], + layout: { + defaultColorRange: d3.scale.category10().range(), + title: null, + height: 450, + width: 500, + margin: { + top: 40, + right: 40, + bottom: 40, + left: 40 + }, + font: { + size: 12, + color: 'gray', + outlineColor: 'white', + family: 'Tahoma, sans-serif' + }, + direction: 'clockwise', + orientation: 0, + labelOffset: 10, + radialAxis: { + domain: null, + orientation: -45, + ticksSuffix: '', + visible: true, + gridLinesVisible: true, + tickOrientation: 'horizontal', + rewriteTicks: null + }, + angularAxis: { + domain: [ 0, 360 ], + ticksSuffix: '', + visible: true, + gridLinesVisible: true, + labelsVisible: true, + tickOrientation: 'horizontal', + rewriteTicks: null, + ticksCount: null, + ticksStep: null + }, + minorTicks: 0, + tickLength: null, + tickColor: 'silver', + minorTickColor: '#eee', + backgroundColor: 'none', + needsEndSpacing: null, + showLegend: true, + legend: { + reverseOrder: false + }, + opacity: 1 + } + }; + return config; +}; + +µ.util = {}; + +µ.DATAEXTENT = 'dataExtent'; + +µ.AREA = 'AreaChart'; + +µ.LINE = 'LinePlot'; + +µ.DOT = 'DotPlot'; + +µ.BAR = 'BarChart'; + +µ.util._override = function(_objA, _objB) { + for (var x in _objA) if (x in _objB) _objB[x] = _objA[x]; +}; + +µ.util._extend = function(_objA, _objB) { + for (var x in _objA) _objB[x] = _objA[x]; +}; + +µ.util._rndSnd = function() { + return Math.random() * 2 - 1 + (Math.random() * 2 - 1) + (Math.random() * 2 - 1); +}; + +µ.util.dataFromEquation2 = function(_equation, _step) { + var step = _step || 6; + var data = d3.range(0, 360 + step, step).map(function(deg, index) { + var theta = deg * Math.PI / 180; + var radius = _equation(theta); + return [ deg, radius ]; + }); + return data; +}; + +µ.util.dataFromEquation = function(_equation, _step, _name) { + var step = _step || 6; + var t = [], r = []; + d3.range(0, 360 + step, step).forEach(function(deg, index) { + var theta = deg * Math.PI / 180; + var radius = _equation(theta); + t.push(deg); + r.push(radius); + }); + var result = { + t: t, + r: r + }; + if (_name) result.name = _name; + return result; +}; + +µ.util.ensureArray = function(_val, _count) { + if (typeof _val === 'undefined') return null; + var arr = [].concat(_val); + return d3.range(_count).map(function(d, i) { + return arr[i] || arr[0]; + }); +}; + +µ.util.fillArrays = function(_obj, _valueNames, _count) { + _valueNames.forEach(function(d, i) { + _obj[d] = µ.util.ensureArray(_obj[d], _count); + }); + return _obj; +}; + +µ.util.cloneJson = function(json) { + return JSON.parse(JSON.stringify(json)); +}; + +µ.util.validateKeys = function(obj, keys) { + if (typeof keys === 'string') keys = keys.split('.'); + var next = keys.shift(); + return obj[next] && (!keys.length || objHasKeys(obj[next], keys)); +}; + +µ.util.sumArrays = function(a, b) { + return d3.zip(a, b).map(function(d, i) { + return d3.sum(d); + }); +}; + +µ.util.arrayLast = function(a) { + return a[a.length - 1]; +}; + +µ.util.arrayEqual = function(a, b) { + var i = Math.max(a.length, b.length, 1); + while (i-- >= 0 && a[i] === b[i]) ; + return i === -2; +}; + +µ.util.flattenArray = function(arr) { + var r = []; + while (!µ.util.arrayEqual(r, arr)) { + r = arr; + arr = [].concat.apply([], arr); + } + return arr; +}; + +µ.util.deduplicate = function(arr) { + return arr.filter(function(v, i, a) { + return a.indexOf(v) == i; + }); +}; + +µ.util.convertToCartesian = function(radius, theta) { + var thetaRadians = theta * Math.PI / 180; + var x = radius * Math.cos(thetaRadians); + var y = radius * Math.sin(thetaRadians); + return [ x, y ]; +}; + +µ.util.round = function(_value, _digits) { + var digits = _digits || 2; + var mult = Math.pow(10, digits); + return Math.round(_value * mult) / mult; +}; + +µ.util.getMousePos = function(_referenceElement) { + var mousePos = d3.mouse(_referenceElement.node()); + var mouseX = mousePos[0]; + var mouseY = mousePos[1]; + var mouse = {}; + mouse.x = mouseX; + mouse.y = mouseY; + mouse.pos = mousePos; + mouse.angle = (Math.atan2(mouseY, mouseX) + Math.PI) * 180 / Math.PI; + mouse.radius = Math.sqrt(mouseX * mouseX + mouseY * mouseY); + return mouse; +}; + +µ.util.duplicatesCount = function(arr) { + var uniques = {}, val; + var dups = {}; + for (var i = 0, len = arr.length; i < len; i++) { + val = arr[i]; + if (val in uniques) { + uniques[val]++; + dups[val] = uniques[val]; + } else { + uniques[val] = 1; + } + } + return dups; +}; + +µ.util.duplicates = function(arr) { + return Object.keys(µ.util.duplicatesCount(arr)); +}; + +µ.util.translator = function(obj, sourceBranch, targetBranch, reverse) { + if (reverse) { + var targetBranchCopy = targetBranch.slice(); + targetBranch = sourceBranch; + sourceBranch = targetBranchCopy; + } + var value = sourceBranch.reduce(function(previousValue, currentValue) { + if (typeof previousValue != 'undefined') return previousValue[currentValue]; + }, obj); + if (typeof value === 'undefined') return; + sourceBranch.reduce(function(previousValue, currentValue, index) { + if (typeof previousValue == 'undefined') return; + if (index === sourceBranch.length - 1) delete previousValue[currentValue]; + return previousValue[currentValue]; + }, obj); + targetBranch.reduce(function(previousValue, currentValue, index) { + if (typeof previousValue[currentValue] === 'undefined') previousValue[currentValue] = {}; + if (index === targetBranch.length - 1) previousValue[currentValue] = value; + return previousValue[currentValue]; + }, obj); +}; + +µ.PolyChart = function module() { + var config = [ µ.PolyChart.defaultConfig() ]; + var dispatch = d3.dispatch('hover'); + var dashArray = { + solid: 'none', + dash: [ 5, 2 ], + dot: [ 2, 5 ] + }; + var colorScale; + function exports() { + var geometryConfig = config[0].geometryConfig; + var container = geometryConfig.container; + if (typeof container == 'string') container = d3.select(container); + container.datum(config).each(function(_config, _index) { + var isStack = !!_config[0].data.yStack; + var data = _config.map(function(d, i) { + if (isStack) return d3.zip(d.data.t[0], d.data.r[0], d.data.yStack[0]); else return d3.zip(d.data.t[0], d.data.r[0]); + }); + var angularScale = geometryConfig.angularScale; + var domainMin = geometryConfig.radialScale.domain()[0]; + var generator = {}; + generator.bar = function(d, i, pI) { + var dataConfig = _config[pI].data; + var h = geometryConfig.radialScale(d[1]) - geometryConfig.radialScale(0); + var stackTop = geometryConfig.radialScale(d[2] || 0); + var w = dataConfig.barWidth; + d3.select(this).attr({ + 'class': 'mark bar', + d: 'M' + [ [ h + stackTop, -w / 2 ], [ h + stackTop, w / 2 ], [ stackTop, w / 2 ], [ stackTop, -w / 2 ] ].join('L') + 'Z', + transform: function(d, i) { + return 'rotate(' + (geometryConfig.orientation + angularScale(d[0])) + ')'; + } + }); + }; + generator.dot = function(d, i, pI) { + var stackedData = d[2] ? [ d[0], d[1] + d[2] ] : d; + var symbol = d3.svg.symbol().size(_config[pI].data.dotSize).type(_config[pI].data.dotType)(d, i); + d3.select(this).attr({ + 'class': 'mark dot', + d: symbol, + transform: function(d, i) { + var coord = convertToCartesian(getPolarCoordinates(stackedData)); + return 'translate(' + [ coord.x, coord.y ] + ')'; + } + }); + }; + var line = d3.svg.line.radial().interpolate(_config[0].data.lineInterpolation).radius(function(d) { + return geometryConfig.radialScale(d[1]); + }).angle(function(d) { + return geometryConfig.angularScale(d[0]) * Math.PI / 180; + }); + generator.line = function(d, i, pI) { + var lineData = d[2] ? data[pI].map(function(d, i) { + return [ d[0], d[1] + d[2] ]; + }) : data[pI]; + d3.select(this).each(generator['dot']).style({ + opacity: function(dB, iB) { + return +_config[pI].data.dotVisible; + }, + fill: markStyle.stroke(d, i, pI) + }).attr({ + 'class': 'mark dot' + }); + if (i > 0) return; + var lineSelection = d3.select(this.parentNode).selectAll('path.line').data([ 0 ]); + lineSelection.enter().insert('path'); + lineSelection.attr({ + 'class': 'line', + d: line(lineData), + transform: function(dB, iB) { + return 'rotate(' + (geometryConfig.orientation + 90) + ')'; + }, + 'pointer-events': 'none' + }).style({ + fill: function(dB, iB) { + return markStyle.fill(d, i, pI); + }, + 'fill-opacity': 0, + stroke: function(dB, iB) { + return markStyle.stroke(d, i, pI); + }, + 'stroke-width': function(dB, iB) { + return markStyle['stroke-width'](d, i, pI); + }, + 'stroke-dasharray': function(dB, iB) { + return markStyle['stroke-dasharray'](d, i, pI); + }, + opacity: function(dB, iB) { + return markStyle.opacity(d, i, pI); + }, + display: function(dB, iB) { + return markStyle.display(d, i, pI); + } + }); + }; + var angularRange = geometryConfig.angularScale.range(); + var triangleAngle = Math.abs(angularRange[1] - angularRange[0]) / data[0].length * Math.PI / 180; + var arc = d3.svg.arc().startAngle(function(d) { + return -triangleAngle / 2; + }).endAngle(function(d) { + return triangleAngle / 2; + }).innerRadius(function(d) { + return geometryConfig.radialScale(domainMin + (d[2] || 0)); + }).outerRadius(function(d) { + return geometryConfig.radialScale(domainMin + (d[2] || 0)) + geometryConfig.radialScale(d[1]); + }); + generator.arc = function(d, i, pI) { + d3.select(this).attr({ + 'class': 'mark arc', + d: arc, + transform: function(d, i) { + return 'rotate(' + (geometryConfig.orientation + angularScale(d[0]) + 90) + ')'; + } + }); + }; + var markStyle = { + fill: function(d, i, pI) { + return _config[pI].data.color; + }, + stroke: function(d, i, pI) { + return _config[pI].data.strokeColor; + }, + 'stroke-width': function(d, i, pI) { + return _config[pI].data.strokeSize + 'px'; + }, + 'stroke-dasharray': function(d, i, pI) { + return dashArray[_config[pI].data.strokeDash]; + }, + opacity: function(d, i, pI) { + return _config[pI].data.opacity; + }, + display: function(d, i, pI) { + return typeof _config[pI].data.visible === 'undefined' || _config[pI].data.visible ? 'block' : 'none'; + } + }; + var geometryLayer = d3.select(this).selectAll('g.layer').data(data); + geometryLayer.enter().append('g').attr({ + 'class': 'layer' + }); + var geometry = geometryLayer.selectAll('path.mark').data(function(d, i) { + return d; + }); + geometry.enter().append('path').attr({ + 'class': 'mark' + }); + geometry.style(markStyle).each(generator[geometryConfig.geometryType]); + geometry.exit().remove(); + geometryLayer.exit().remove(); + function getPolarCoordinates(d, i) { + var r = geometryConfig.radialScale(d[1]); + var t = (geometryConfig.angularScale(d[0]) + geometryConfig.orientation) * Math.PI / 180; + return { + r: r, + t: t + }; + } + function convertToCartesian(polarCoordinates) { + var x = polarCoordinates.r * Math.cos(polarCoordinates.t); + var y = polarCoordinates.r * Math.sin(polarCoordinates.t); + return { + x: x, + y: y + }; + } + }); + } + exports.config = function(_x) { + if (!arguments.length) return config; + _x.forEach(function(d, i) { + if (!config[i]) config[i] = {}; + extendDeepAll(config[i], µ.PolyChart.defaultConfig()); + extendDeepAll(config[i], d); + }); + return this; + }; + exports.getColorScale = function() { + return colorScale; + }; + d3.rebind(exports, dispatch, 'on'); + return exports; +}; + +µ.PolyChart.defaultConfig = function() { + var config = { + data: { + name: 'geom1', + t: [ [ 1, 2, 3, 4 ] ], + r: [ [ 1, 2, 3, 4 ] ], + dotType: 'circle', + dotSize: 64, + dotVisible: false, + barWidth: 20, + color: '#ffa500', + strokeSize: 1, + strokeColor: 'silver', + strokeDash: 'solid', + opacity: 1, + index: 0, + visible: true, + visibleInLegend: true + }, + geometryConfig: { + geometry: 'LinePlot', + geometryType: 'arc', + direction: 'clockwise', + orientation: 0, + container: 'body', + radialScale: null, + angularScale: null, + colorScale: d3.scale.category20() + } + }; + return config; +}; + +µ.BarChart = function module() { + return µ.PolyChart(); +}; + +µ.BarChart.defaultConfig = function() { + var config = { + geometryConfig: { + geometryType: 'bar' + } + }; + return config; +}; + +µ.AreaChart = function module() { + return µ.PolyChart(); +}; + +µ.AreaChart.defaultConfig = function() { + var config = { + geometryConfig: { + geometryType: 'arc' + } + }; + return config; +}; + +µ.DotPlot = function module() { + return µ.PolyChart(); +}; + +µ.DotPlot.defaultConfig = function() { + var config = { + geometryConfig: { + geometryType: 'dot', + dotType: 'circle' + } + }; + return config; +}; + +µ.LinePlot = function module() { + return µ.PolyChart(); +}; + +µ.LinePlot.defaultConfig = function() { + var config = { + geometryConfig: { + geometryType: 'line' + } + }; + return config; +}; + +µ.Legend = function module() { + var config = µ.Legend.defaultConfig(); + var dispatch = d3.dispatch('hover'); + function exports() { + var legendConfig = config.legendConfig; + var flattenData = config.data.map(function(d, i) { + return [].concat(d).map(function(dB, iB) { + var element = extendDeepAll({}, legendConfig.elements[i]); + element.name = dB; + element.color = [].concat(legendConfig.elements[i].color)[iB]; + return element; + }); + }); + var data = d3.merge(flattenData); + data = data.filter(function(d, i) { + return legendConfig.elements[i] && (legendConfig.elements[i].visibleInLegend || typeof legendConfig.elements[i].visibleInLegend === 'undefined'); + }); + if (legendConfig.reverseOrder) data = data.reverse(); + var container = legendConfig.container; + if (typeof container == 'string' || container.nodeName) container = d3.select(container); + var colors = data.map(function(d, i) { + return d.color; + }); + var lineHeight = legendConfig.fontSize; + var isContinuous = legendConfig.isContinuous == null ? typeof data[0] === 'number' : legendConfig.isContinuous; + var height = isContinuous ? legendConfig.height : lineHeight * data.length; + var legendContainerGroup = container.classed('legend-group', true); + var svg = legendContainerGroup.selectAll('svg').data([ 0 ]); + var svgEnter = svg.enter().append('svg').attr({ + width: 300, + height: height + lineHeight, + xmlns: 'http://www.w3.org/2000/svg', + 'xmlns:xlink': 'http://www.w3.org/1999/xlink', + version: '1.1' + }); + svgEnter.append('g').classed('legend-axis', true); + svgEnter.append('g').classed('legend-marks', true); + var dataNumbered = d3.range(data.length); + var colorScale = d3.scale[isContinuous ? 'linear' : 'ordinal']().domain(dataNumbered).range(colors); + var dataScale = d3.scale[isContinuous ? 'linear' : 'ordinal']().domain(dataNumbered)[isContinuous ? 'range' : 'rangePoints']([ 0, height ]); + var shapeGenerator = function(_type, _size) { + var squareSize = _size * 3; + if (_type === 'line') { + return 'M' + [ [ -_size / 2, -_size / 12 ], [ _size / 2, -_size / 12 ], [ _size / 2, _size / 12 ], [ -_size / 2, _size / 12 ] ] + 'Z'; + } else if (d3.svg.symbolTypes.indexOf(_type) != -1) return d3.svg.symbol().type(_type).size(squareSize)(); else return d3.svg.symbol().type('square').size(squareSize)(); + }; + if (isContinuous) { + var gradient = svg.select('.legend-marks').append('defs').append('linearGradient').attr({ + id: 'grad1', + x1: '0%', + y1: '0%', + x2: '0%', + y2: '100%' + }).selectAll('stop').data(colors); + gradient.enter().append('stop'); + gradient.attr({ + offset: function(d, i) { + return i / (colors.length - 1) * 100 + '%'; + } + }).style({ + 'stop-color': function(d, i) { + return d; + } + }); + svg.append('rect').classed('legend-mark', true).attr({ + height: legendConfig.height, + width: legendConfig.colorBandWidth, + fill: 'url(#grad1)' + }); + } else { + var legendElement = svg.select('.legend-marks').selectAll('path.legend-mark').data(data); + legendElement.enter().append('path').classed('legend-mark', true); + legendElement.attr({ + transform: function(d, i) { + return 'translate(' + [ lineHeight / 2, dataScale(i) + lineHeight / 2 ] + ')'; + }, + d: function(d, i) { + var symbolType = d.symbol; + return shapeGenerator(symbolType, lineHeight); + }, + fill: function(d, i) { + return colorScale(i); + } + }); + legendElement.exit().remove(); + } + var legendAxis = d3.svg.axis().scale(dataScale).orient('right'); + var axis = svg.select('g.legend-axis').attr({ + transform: 'translate(' + [ isContinuous ? legendConfig.colorBandWidth : lineHeight, lineHeight / 2 ] + ')' + }).call(legendAxis); + axis.selectAll('.domain').style({ + fill: 'none', + stroke: 'none' + }); + axis.selectAll('line').style({ + fill: 'none', + stroke: isContinuous ? legendConfig.textColor : 'none' + }); + axis.selectAll('text').style({ + fill: legendConfig.textColor, + 'font-size': legendConfig.fontSize + }).text(function(d, i) { + return data[i].name; + }); + return exports; + } + exports.config = function(_x) { + if (!arguments.length) return config; + extendDeepAll(config, _x); + return this; + }; + d3.rebind(exports, dispatch, 'on'); + return exports; +}; + +µ.Legend.defaultConfig = function(d, i) { + var config = { + data: [ 'a', 'b', 'c' ], + legendConfig: { + elements: [ { + symbol: 'line', + color: 'red' + }, { + symbol: 'square', + color: 'yellow' + }, { + symbol: 'diamond', + color: 'limegreen' + } ], + height: 150, + colorBandWidth: 30, + fontSize: 12, + container: 'body', + isContinuous: null, + textColor: 'grey', + reverseOrder: false + } + }; + return config; +}; + +µ.tooltipPanel = function() { + var tooltipEl, tooltipTextEl, backgroundEl; + var config = { + container: null, + hasTick: false, + fontSize: 12, + color: 'white', + padding: 5 + }; + var id = 'tooltip-' + µ.tooltipPanel.uid++; + var tickSize = 10; + var exports = function() { + tooltipEl = config.container.selectAll('g.' + id).data([ 0 ]); + var tooltipEnter = tooltipEl.enter().append('g').classed(id, true).style({ + 'pointer-events': 'none', + display: 'none' + }); + backgroundEl = tooltipEnter.append('path').style({ + fill: 'white', + 'fill-opacity': .9 + }).attr({ + d: 'M0 0' + }); + tooltipTextEl = tooltipEnter.append('text').attr({ + dx: config.padding + tickSize, + dy: +config.fontSize * .3 + }); + return exports; + }; + exports.text = function(_text) { + var l = d3.hsl(config.color).l; + var strokeColor = l >= .5 ? '#aaa' : 'white'; + var fillColor = l >= .5 ? 'black' : 'white'; + var text = _text || ''; + tooltipTextEl.style({ + fill: fillColor, + 'font-size': config.fontSize + 'px' + }).text(text); + var padding = config.padding; + var bbox = tooltipTextEl.node().getBBox(); + var boxStyle = { + fill: config.color, + stroke: strokeColor, + 'stroke-width': '2px' + }; + var backGroundW = bbox.width + padding * 2 + tickSize; + var backGroundH = bbox.height + padding * 2; + backgroundEl.attr({ + d: 'M' + [ [ tickSize, -backGroundH / 2 ], [ tickSize, -backGroundH / 4 ], [ config.hasTick ? 0 : tickSize, 0 ], [ tickSize, backGroundH / 4 ], [ tickSize, backGroundH / 2 ], [ backGroundW, backGroundH / 2 ], [ backGroundW, -backGroundH / 2 ] ].join('L') + 'Z' + }).style(boxStyle); + tooltipEl.attr({ + transform: 'translate(' + [ tickSize, -backGroundH / 2 + padding * 2 ] + ')' + }); + tooltipEl.style({ + display: 'block' + }); + return exports; + }; + exports.move = function(_pos) { + if (!tooltipEl) return; + tooltipEl.attr({ + transform: 'translate(' + [ _pos[0], _pos[1] ] + ')' + }).style({ + display: 'block' + }); + return exports; + }; + exports.hide = function() { + if (!tooltipEl) return; + tooltipEl.style({ + display: 'none' + }); + return exports; + }; + exports.show = function() { + if (!tooltipEl) return; + tooltipEl.style({ + display: 'block' + }); + return exports; + }; + exports.config = function(_x) { + extendDeepAll(config, _x); + return exports; + }; + return exports; +}; + +µ.tooltipPanel.uid = 1; + +µ.adapter = {}; + +µ.adapter.plotly = function module() { + var exports = {}; + exports.convert = function(_inputConfig, reverse) { + var outputConfig = {}; + if (_inputConfig.data) { + outputConfig.data = _inputConfig.data.map(function(d, i) { + var r = extendDeepAll({}, d); + var toTranslate = [ + [ r, [ 'marker', 'color' ], [ 'color' ] ], + [ r, [ 'marker', 'opacity' ], [ 'opacity' ] ], + [ r, [ 'marker', 'line', 'color' ], [ 'strokeColor' ] ], + [ r, [ 'marker', 'line', 'dash' ], [ 'strokeDash' ] ], + [ r, [ 'marker', 'line', 'width' ], [ 'strokeSize' ] ], + [ r, [ 'marker', 'symbol' ], [ 'dotType' ] ], + [ r, [ 'marker', 'size' ], [ 'dotSize' ] ], + [ r, [ 'marker', 'barWidth' ], [ 'barWidth' ] ], + [ r, [ 'line', 'interpolation' ], [ 'lineInterpolation' ] ], + [ r, [ 'showlegend' ], [ 'visibleInLegend' ] ] + ]; + toTranslate.forEach(function(d, i) { + µ.util.translator.apply(null, d.concat(reverse)); + }); + + if (!reverse) delete r.marker; + if (reverse) delete r.groupId; + if (!reverse) { + if (r.type === 'scatter') { + if (r.mode === 'lines') r.geometry = 'LinePlot'; else if (r.mode === 'markers') r.geometry = 'DotPlot'; else if (r.mode === 'lines+markers') { + r.geometry = 'LinePlot'; + r.dotVisible = true; + } + } else if (r.type === 'area') r.geometry = 'AreaChart'; else if (r.type === 'bar') r.geometry = 'BarChart'; + delete r.mode; + delete r.type; + } else { + if (r.geometry === 'LinePlot') { + r.type = 'scatter'; + if (r.dotVisible === true) { + delete r.dotVisible; + r.mode = 'lines+markers'; + } else r.mode = 'lines'; + } else if (r.geometry === 'DotPlot') { + r.type = 'scatter'; + r.mode = 'markers'; + } else if (r.geometry === 'AreaChart') r.type = 'area'; else if (r.geometry === 'BarChart') r.type = 'bar'; + delete r.geometry; + } + return r; + }); + if (!reverse && _inputConfig.layout && _inputConfig.layout.barmode === 'stack') { + var duplicates = µ.util.duplicates(outputConfig.data.map(function(d, i) { + return d.geometry; + })); + outputConfig.data.forEach(function(d, i) { + var idx = duplicates.indexOf(d.geometry); + if (idx != -1) outputConfig.data[i].groupId = idx; + }); + } + } + if (_inputConfig.layout) { + var r = extendDeepAll({}, _inputConfig.layout); + var toTranslate = [ + [ r, [ 'plot_bgcolor' ], [ 'backgroundColor' ] ], + [ r, [ 'showlegend' ], [ 'showLegend' ] ], + [ r, [ 'radialaxis' ], [ 'radialAxis' ] ], + [ r, [ 'angularaxis' ], [ 'angularAxis' ] ], + [ r.angularaxis, [ 'showline' ], [ 'gridLinesVisible' ] ], + [ r.angularaxis, [ 'showticklabels' ], [ 'labelsVisible' ] ], + [ r.angularaxis, [ 'nticks' ], [ 'ticksCount' ] ], + [ r.angularaxis, [ 'tickorientation' ], [ 'tickOrientation' ] ], + [ r.angularaxis, [ 'ticksuffix' ], [ 'ticksSuffix' ] ], + [ r.angularaxis, [ 'range' ], [ 'domain' ] ], + [ r.angularaxis, [ 'endpadding' ], [ 'endPadding' ] ], + [ r.radialaxis, [ 'showline' ], [ 'gridLinesVisible' ] ], + [ r.radialaxis, [ 'tickorientation' ], [ 'tickOrientation' ] ], + [ r.radialaxis, [ 'ticksuffix' ], [ 'ticksSuffix' ] ], + [ r.radialaxis, [ 'range' ], [ 'domain' ] ], + [ r.angularAxis, [ 'showline' ], [ 'gridLinesVisible' ] ], + [ r.angularAxis, [ 'showticklabels' ], [ 'labelsVisible' ] ], + [ r.angularAxis, [ 'nticks' ], [ 'ticksCount' ] ], + [ r.angularAxis, [ 'tickorientation' ], [ 'tickOrientation' ] ], + [ r.angularAxis, [ 'ticksuffix' ], [ 'ticksSuffix' ] ], + [ r.angularAxis, [ 'range' ], [ 'domain' ] ], + [ r.angularAxis, [ 'endpadding' ], [ 'endPadding' ] ], + [ r.radialAxis, [ 'showline' ], [ 'gridLinesVisible' ] ], + [ r.radialAxis, [ 'tickorientation' ], [ 'tickOrientation' ] ], + [ r.radialAxis, [ 'ticksuffix' ], [ 'ticksSuffix' ] ], + [ r.radialAxis, [ 'range' ], [ 'domain' ] ], + [ r.font, [ 'outlinecolor' ], [ 'outlineColor' ] ], + [ r.legend, [ 'traceorder' ], [ 'reverseOrder' ] ], + [ r, [ 'labeloffset' ], [ 'labelOffset' ] ], + [ r, [ 'defaultcolorrange' ], [ 'defaultColorRange' ] ] + ]; + toTranslate.forEach(function(d, i) { + µ.util.translator.apply(null, d.concat(reverse)); + }); + + if (!reverse) { + if (r.angularAxis && typeof r.angularAxis.ticklen !== 'undefined') r.tickLength = r.angularAxis.ticklen; + if (r.angularAxis && typeof r.angularAxis.tickcolor !== 'undefined') r.tickColor = r.angularAxis.tickcolor; + } else { + if (typeof r.tickLength !== 'undefined') { + r.angularaxis.ticklen = r.tickLength; + delete r.tickLength; + } + if (r.tickColor) { + r.angularaxis.tickcolor = r.tickColor; + delete r.tickColor; + } + } + if (r.legend && typeof r.legend.reverseOrder != 'boolean') { + r.legend.reverseOrder = r.legend.reverseOrder != 'normal'; + } + if (r.legend && typeof r.legend.traceorder == 'boolean') { + r.legend.traceorder = r.legend.traceorder ? 'reversed' : 'normal'; + delete r.legend.reverseOrder; + } + if (r.margin && typeof r.margin.t != 'undefined') { + var source = [ 't', 'r', 'b', 'l', 'pad' ]; + var target = [ 'top', 'right', 'bottom', 'left', 'pad' ]; + var margin = {}; + d3.entries(r.margin).forEach(function(dB, iB) { + margin[target[source.indexOf(dB.key)]] = dB.value; + }); + r.margin = margin; + } + if (reverse) { + delete r.needsEndSpacing; + delete r.minorTickColor; + delete r.minorTicks; + delete r.angularaxis.ticksCount; + delete r.angularaxis.ticksCount; + delete r.angularaxis.ticksStep; + delete r.angularaxis.rewriteTicks; + delete r.angularaxis.nticks; + delete r.radialaxis.ticksCount; + delete r.radialaxis.ticksCount; + delete r.radialaxis.ticksStep; + delete r.radialaxis.rewriteTicks; + delete r.radialaxis.nticks; + } + outputConfig.layout = r; + } + return outputConfig; + }; + return exports; +}; + +},{"../../../constants/alignment":145,"../../../lib":169,"d3":16}],250:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +/* eslint-disable new-cap */ + +'use strict'; + +var d3 = _dereq_('d3'); +var Lib = _dereq_('../../../lib'); +var Color = _dereq_('../../../components/color'); + +var micropolar = _dereq_('./micropolar'); +var UndoManager = _dereq_('./undo_manager'); +var extendDeepAll = Lib.extendDeepAll; + +var manager = module.exports = {}; + +manager.framework = function(_gd) { + var config, previousConfigClone, plot, convertedInput, container; + var undoManager = new UndoManager(); + + function exports(_inputConfig, _container) { + if(_container) container = _container; + d3.select(d3.select(container).node().parentNode).selectAll('.svg-container>*:not(.chart-root)').remove(); + + config = (!config) ? + _inputConfig : + extendDeepAll(config, _inputConfig); + + if(!plot) plot = micropolar.Axis(); + convertedInput = micropolar.adapter.plotly().convert(config); + plot.config(convertedInput).render(container); + _gd.data = config.data; + _gd.layout = config.layout; + manager.fillLayout(_gd); + return config; + } + exports.isPolar = true; + exports.svg = function() { return plot.svg(); }; + exports.getConfig = function() { return config; }; + exports.getLiveConfig = function() { + return micropolar.adapter.plotly().convert(plot.getLiveConfig(), true); + }; + exports.getLiveScales = function() { return {t: plot.angularScale(), r: plot.radialScale()}; }; + exports.setUndoPoint = function() { + var that = this; + var configClone = micropolar.util.cloneJson(config); + (function(_configClone, _previousConfigClone) { + undoManager.add({ + undo: function() { + if(_previousConfigClone) that(_previousConfigClone); + }, + redo: function() { + that(_configClone); + } + }); + })(configClone, previousConfigClone); + previousConfigClone = micropolar.util.cloneJson(configClone); + }; + exports.undo = function() { undoManager.undo(); }; + exports.redo = function() { undoManager.redo(); }; + return exports; +}; + +manager.fillLayout = function(_gd) { + var container = d3.select(_gd).selectAll('.plot-container'); + var paperDiv = container.selectAll('.svg-container'); + var paper = _gd.framework && _gd.framework.svg && _gd.framework.svg(); + var dflts = { + width: 800, + height: 600, + paper_bgcolor: Color.background, + _container: container, + _paperdiv: paperDiv, + _paper: paper + }; + + _gd._fullLayout = extendDeepAll(dflts, _gd.layout); +}; + +},{"../../../components/color":51,"../../../lib":169,"./micropolar":249,"./undo_manager":251,"d3":16}],251:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +// Modified from https://github.com/ArthurClemens/Javascript-Undo-Manager +// Copyright (c) 2010-2013 Arthur Clemens, arthur@visiblearea.com +module.exports = function UndoManager() { + var undoCommands = []; + var index = -1; + var isExecuting = false; + var callback; + + function execute(command, action) { + if(!command) return this; + + isExecuting = true; + command[action](); + isExecuting = false; + + return this; + } + + return { + add: function(command) { + if(isExecuting) return this; + undoCommands.splice(index + 1, undoCommands.length - index); + undoCommands.push(command); + index = undoCommands.length - 1; + return this; + }, + setCallback: function(callbackFunc) { callback = callbackFunc; }, + undo: function() { + var command = undoCommands[index]; + if(!command) return this; + execute(command, 'undo'); + index -= 1; + if(callback) callback(command.undo); + return this; + }, + redo: function() { + var command = undoCommands[index + 1]; + if(!command) return this; + execute(command, 'redo'); + index += 1; + if(callback) callback(command.redo); + return this; + }, + clear: function() { + undoCommands = []; + index = -1; + }, + hasUndo: function() { return index !== -1; }, + hasRedo: function() { return index < (undoCommands.length - 1); }, + getCommands: function() { return undoCommands; }, + getPreviousCommand: function() { return undoCommands[index - 1]; }, + getIndex: function() { return index; } + }; +}; + +},{}],252:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Lib = _dereq_('../lib'); +var Template = _dereq_('../plot_api/plot_template'); +var handleDomainDefaults = _dereq_('./domain').defaults; + + +/** + * Find and supply defaults to all subplots of a given type + * This handles subplots that are contained within one container - so + * gl3d, geo, ternary... but not 2d axes which have separate x and y axes + * finds subplots, coerces their `domain` attributes, then calls the + * given handleDefaults function to fill in everything else. + * + * layoutIn: the complete user-supplied input layout + * layoutOut: the complete finished layout + * fullData: the finished data array, used only to find subplots + * opts: { + * type: subplot type string + * attributes: subplot attributes object + * partition: 'x' or 'y', which direction to divide domain space by default + * (default 'x', ie side-by-side subplots) + * TODO: this option is only here because 3D and geo made opposite + * choices in this regard previously and I didn't want to change it. + * Instead we should do: + * - something consistent + * - something more square (4 cuts 2x2, 5/6 cuts 2x3, etc.) + * - something that includes all subplot types in one arrangement, + * now that we can have them together! + * handleDefaults: function of (subplotLayoutIn, subplotLayoutOut, coerce, opts) + * this opts object is passed through to handleDefaults, so attach any + * additional items needed by this function here as well + * } + */ +module.exports = function handleSubplotDefaults(layoutIn, layoutOut, fullData, opts) { + var subplotType = opts.type; + var subplotAttributes = opts.attributes; + var handleDefaults = opts.handleDefaults; + var partition = opts.partition || 'x'; + + var ids = layoutOut._subplots[subplotType]; + var idsLength = ids.length; + + var baseId = idsLength && ids[0].replace(/\d+$/, ''); + + var subplotLayoutIn, subplotLayoutOut; + + function coerce(attr, dflt) { + return Lib.coerce(subplotLayoutIn, subplotLayoutOut, subplotAttributes, attr, dflt); + } + + for(var i = 0; i < idsLength; i++) { + var id = ids[i]; + + // ternary traces get a layout ternary for free! + if(layoutIn[id]) subplotLayoutIn = layoutIn[id]; + else subplotLayoutIn = layoutIn[id] = {}; + + subplotLayoutOut = Template.newContainer(layoutOut, id, baseId); + + // All subplot containers get a `uirevision` inheriting from the base. + // Currently all subplots containers have some user interaction + // attributes, but if we ever add one that doesn't, we would need an + // option to skip this step. + coerce('uirevision', layoutOut.uirevision); + + var dfltDomains = {}; + dfltDomains[partition] = [i / idsLength, (i + 1) / idsLength]; + handleDomainDefaults(subplotLayoutOut, layoutOut, coerce, dfltDomains); + + opts.id = id; + handleDefaults(subplotLayoutIn, subplotLayoutOut, coerce, opts); + } +}; + +},{"../lib":169,"../plot_api/plot_template":203,"./domain":238}],253:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var FORMAT_LINK = _dereq_('../constants/docs').FORMAT_LINK; +var DATE_FORMAT_LINK = _dereq_('../constants/docs').DATE_FORMAT_LINK; + +var templateFormatStringDescription = [ + 'Variables are inserted using %{variable}, for example "y: %{y}".', + 'Numbers are formatted using d3-format\'s syntax %{variable:d3-format}, for example "Price: %{y:$.2f}".', + FORMAT_LINK, + 'for details on the formatting syntax.', + 'Dates are formatted using d3-time-format\'s syntax %{variable|d3-time-format}, for example "Day: %{2019-01-01|%A}".', + DATE_FORMAT_LINK, + 'for details on the date formatting syntax.' +].join(' '); + +function describeVariables(extra) { + var descPart = extra.description ? ' ' + extra.description : ''; + var keys = extra.keys || []; + if(keys.length > 0) { + var quotedKeys = []; + for(var i = 0; i < keys.length; i++) { + quotedKeys[i] = '`' + keys[i] + '`'; + } + descPart = descPart + 'Finally, the template string has access to '; + if(keys.length === 1) { + descPart = 'variable ' + quotedKeys[0]; + } else { + descPart = 'variables ' + quotedKeys.slice(0, -1).join(', ') + ' and ' + quotedKeys.slice(-1) + '.'; + } + } + return descPart; +} + +exports.hovertemplateAttrs = function(opts, extra) { + opts = opts || {}; + extra = extra || {}; + + var descPart = describeVariables(extra); + + var hovertemplate = { + valType: 'string', + + dflt: '', + editType: opts.editType || 'none', + + }; + + if(opts.arrayOk !== false) { + hovertemplate.arrayOk = true; + } + + return hovertemplate; +}; + +exports.texttemplateAttrs = function(opts, extra) { + opts = opts || {}; + extra = extra || {}; + + var descPart = describeVariables(extra); + + var texttemplate = { + valType: 'string', + + dflt: '', + editType: opts.editType || 'calc', + + }; + + if(opts.arrayOk !== false) { + texttemplate.arrayOk = true; + } + return texttemplate; +}; + +},{"../constants/docs":146}],254:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Ternary = _dereq_('./ternary'); + +var getSubplotCalcData = _dereq_('../../plots/get_data').getSubplotCalcData; +var counterRegex = _dereq_('../../lib').counterRegex; +var TERNARY = 'ternary'; + +exports.name = TERNARY; + +var attr = exports.attr = 'subplot'; + +exports.idRoot = TERNARY; + +exports.idRegex = exports.attrRegex = counterRegex(TERNARY); + +var attributes = exports.attributes = {}; +attributes[attr] = { + valType: 'subplotid', + + dflt: 'ternary', + editType: 'calc', + +}; + +exports.layoutAttributes = _dereq_('./layout_attributes'); + +exports.supplyLayoutDefaults = _dereq_('./layout_defaults'); + +exports.plot = function plot(gd) { + var fullLayout = gd._fullLayout; + var calcData = gd.calcdata; + var ternaryIds = fullLayout._subplots[TERNARY]; + + for(var i = 0; i < ternaryIds.length; i++) { + var ternaryId = ternaryIds[i]; + var ternaryCalcData = getSubplotCalcData(calcData, TERNARY, ternaryId); + var ternary = fullLayout[ternaryId]._subplot; + + // If ternary is not instantiated, create one! + if(!ternary) { + ternary = new Ternary({ + id: ternaryId, + graphDiv: gd, + container: fullLayout._ternarylayer.node() + }, + fullLayout + ); + + fullLayout[ternaryId]._subplot = ternary; + } + + ternary.plot(ternaryCalcData, fullLayout, gd._promises); + } +}; + +exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) { + var oldTernaryKeys = oldFullLayout._subplots[TERNARY] || []; + + for(var i = 0; i < oldTernaryKeys.length; i++) { + var oldTernaryKey = oldTernaryKeys[i]; + var oldTernary = oldFullLayout[oldTernaryKey]._subplot; + + if(!newFullLayout[oldTernaryKey] && !!oldTernary) { + oldTernary.plotContainer.remove(); + oldTernary.clipDef.remove(); + oldTernary.clipDefRelative.remove(); + oldTernary.layers['a-title'].remove(); + oldTernary.layers['b-title'].remove(); + oldTernary.layers['c-title'].remove(); + } + } +}; + +},{"../../lib":169,"../../plots/get_data":241,"./layout_attributes":255,"./layout_defaults":256,"./ternary":257}],255:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var colorAttrs = _dereq_('../../components/color/attributes'); +var domainAttrs = _dereq_('../domain').attributes; +var axesAttrs = _dereq_('../cartesian/layout_attributes'); + +var overrideAll = _dereq_('../../plot_api/edit_types').overrideAll; +var extendFlat = _dereq_('../../lib/extend').extendFlat; + +var ternaryAxesAttrs = { + title: { + text: axesAttrs.title.text, + font: axesAttrs.title.font + // TODO does standoff here make sense? + }, + color: axesAttrs.color, + // ticks + tickmode: axesAttrs.tickmode, + nticks: extendFlat({}, axesAttrs.nticks, {dflt: 6, min: 1}), + tick0: axesAttrs.tick0, + dtick: axesAttrs.dtick, + tickvals: axesAttrs.tickvals, + ticktext: axesAttrs.ticktext, + ticks: axesAttrs.ticks, + ticklen: axesAttrs.ticklen, + tickwidth: axesAttrs.tickwidth, + tickcolor: axesAttrs.tickcolor, + showticklabels: axesAttrs.showticklabels, + showtickprefix: axesAttrs.showtickprefix, + tickprefix: axesAttrs.tickprefix, + showticksuffix: axesAttrs.showticksuffix, + ticksuffix: axesAttrs.ticksuffix, + showexponent: axesAttrs.showexponent, + exponentformat: axesAttrs.exponentformat, + separatethousands: axesAttrs.separatethousands, + tickfont: axesAttrs.tickfont, + tickangle: axesAttrs.tickangle, + tickformat: axesAttrs.tickformat, + tickformatstops: axesAttrs.tickformatstops, + hoverformat: axesAttrs.hoverformat, + // lines and grids + showline: extendFlat({}, axesAttrs.showline, {dflt: true}), + linecolor: axesAttrs.linecolor, + linewidth: axesAttrs.linewidth, + showgrid: extendFlat({}, axesAttrs.showgrid, {dflt: true}), + gridcolor: axesAttrs.gridcolor, + gridwidth: axesAttrs.gridwidth, + layer: axesAttrs.layer, + // range + min: { + valType: 'number', + dflt: 0, + + min: 0, + + }, + _deprecated: { + title: axesAttrs._deprecated.title, + titlefont: axesAttrs._deprecated.titlefont + } +}; + +var attrs = module.exports = overrideAll({ + domain: domainAttrs({name: 'ternary'}), + + bgcolor: { + valType: 'color', + + dflt: colorAttrs.background, + + }, + sum: { + valType: 'number', + + dflt: 1, + min: 0, + + }, + aaxis: ternaryAxesAttrs, + baxis: ternaryAxesAttrs, + caxis: ternaryAxesAttrs +}, 'plot', 'from-root'); + +// set uirevisions outside of `overrideAll` so we can get `editType: none` +attrs.uirevision = { + valType: 'any', + + editType: 'none', + +}; + +attrs.aaxis.uirevision = attrs.baxis.uirevision = attrs.caxis.uirevision = { + valType: 'any', + + editType: 'none', + +}; + +},{"../../components/color/attributes":50,"../../lib/extend":164,"../../plot_api/edit_types":196,"../cartesian/layout_attributes":225,"../domain":238}],256:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Color = _dereq_('../../components/color'); +var Template = _dereq_('../../plot_api/plot_template'); +var Lib = _dereq_('../../lib'); + +var handleSubplotDefaults = _dereq_('../subplot_defaults'); +var handleTickLabelDefaults = _dereq_('../cartesian/tick_label_defaults'); +var handleTickMarkDefaults = _dereq_('../cartesian/tick_mark_defaults'); +var handleTickValueDefaults = _dereq_('../cartesian/tick_value_defaults'); +var handleLineGridDefaults = _dereq_('../cartesian/line_grid_defaults'); +var layoutAttributes = _dereq_('./layout_attributes'); + +var axesNames = ['aaxis', 'baxis', 'caxis']; + +module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { + handleSubplotDefaults(layoutIn, layoutOut, fullData, { + type: 'ternary', + attributes: layoutAttributes, + handleDefaults: handleTernaryDefaults, + font: layoutOut.font, + paper_bgcolor: layoutOut.paper_bgcolor + }); +}; + +function handleTernaryDefaults(ternaryLayoutIn, ternaryLayoutOut, coerce, options) { + var bgColor = coerce('bgcolor'); + var sum = coerce('sum'); + options.bgColor = Color.combine(bgColor, options.paper_bgcolor); + var axName, containerIn, containerOut; + + // TODO: allow most (if not all) axis attributes to be set + // in the outer container and used as defaults in the individual axes? + + for(var j = 0; j < axesNames.length; j++) { + axName = axesNames[j]; + containerIn = ternaryLayoutIn[axName] || {}; + containerOut = Template.newContainer(ternaryLayoutOut, axName); + containerOut._name = axName; + + handleAxisDefaults(containerIn, containerOut, options, ternaryLayoutOut); + } + + // if the min values contradict each other, set them all to default (0) + // and delete *all* the inputs so the user doesn't get confused later by + // changing one and having them all change. + var aaxis = ternaryLayoutOut.aaxis; + var baxis = ternaryLayoutOut.baxis; + var caxis = ternaryLayoutOut.caxis; + if(aaxis.min + baxis.min + caxis.min >= sum) { + aaxis.min = 0; + baxis.min = 0; + caxis.min = 0; + if(ternaryLayoutIn.aaxis) delete ternaryLayoutIn.aaxis.min; + if(ternaryLayoutIn.baxis) delete ternaryLayoutIn.baxis.min; + if(ternaryLayoutIn.caxis) delete ternaryLayoutIn.caxis.min; + } +} + +function handleAxisDefaults(containerIn, containerOut, options, ternaryLayoutOut) { + var axAttrs = layoutAttributes[containerOut._name]; + + function coerce(attr, dflt) { + return Lib.coerce(containerIn, containerOut, axAttrs, attr, dflt); + } + + coerce('uirevision', ternaryLayoutOut.uirevision); + + containerOut.type = 'linear'; // no other types allowed for ternary + + var dfltColor = coerce('color'); + // if axis.color was provided, use it for fonts too; otherwise, + // inherit from global font color in case that was provided. + var dfltFontColor = (dfltColor !== axAttrs.color.dflt) ? dfltColor : options.font.color; + + var axName = containerOut._name; + var letterUpper = axName.charAt(0).toUpperCase(); + var dfltTitle = 'Component ' + letterUpper; + + var title = coerce('title.text', dfltTitle); + containerOut._hovertitle = title === dfltTitle ? title : letterUpper; + + Lib.coerceFont(coerce, 'title.font', { + family: options.font.family, + size: Math.round(options.font.size * 1.2), + color: dfltFontColor + }); + + // range is just set by 'min' - max is determined by the other axes mins + coerce('min'); + + handleTickValueDefaults(containerIn, containerOut, coerce, 'linear'); + handleTickLabelDefaults(containerIn, containerOut, coerce, 'linear', {}); + handleTickMarkDefaults(containerIn, containerOut, coerce, + { outerTicks: true }); + + var showTickLabels = coerce('showticklabels'); + if(showTickLabels) { + Lib.coerceFont(coerce, 'tickfont', { + family: options.font.family, + size: options.font.size, + color: dfltFontColor + }); + coerce('tickangle'); + coerce('tickformat'); + } + + handleLineGridDefaults(containerIn, containerOut, coerce, { + dfltColor: dfltColor, + bgColor: options.bgColor, + // default grid color is darker here (60%, vs cartesian default ~91%) + // because the grid is not square so the eye needs heavier cues to follow + blend: 60, + showLine: true, + showGrid: true, + noZeroLine: true, + attributes: axAttrs + }); + + coerce('hoverformat'); + coerce('layer'); +} + +},{"../../components/color":51,"../../lib":169,"../../plot_api/plot_template":203,"../cartesian/line_grid_defaults":227,"../cartesian/tick_label_defaults":232,"../cartesian/tick_mark_defaults":233,"../cartesian/tick_value_defaults":234,"../subplot_defaults":252,"./layout_attributes":255}],257:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var d3 = _dereq_('d3'); +var tinycolor = _dereq_('tinycolor2'); + +var Registry = _dereq_('../../registry'); +var Lib = _dereq_('../../lib'); +var _ = Lib._; +var Color = _dereq_('../../components/color'); +var Drawing = _dereq_('../../components/drawing'); +var setConvert = _dereq_('../cartesian/set_convert'); +var extendFlat = _dereq_('../../lib/extend').extendFlat; +var Plots = _dereq_('../plots'); +var Axes = _dereq_('../cartesian/axes'); +var dragElement = _dereq_('../../components/dragelement'); +var Fx = _dereq_('../../components/fx'); +var Titles = _dereq_('../../components/titles'); +var prepSelect = _dereq_('../cartesian/select').prepSelect; +var selectOnClick = _dereq_('../cartesian/select').selectOnClick; +var clearSelect = _dereq_('../cartesian/select').clearSelect; +var constants = _dereq_('../cartesian/constants'); + +function Ternary(options, fullLayout) { + this.id = options.id; + this.graphDiv = options.graphDiv; + this.init(fullLayout); + this.makeFramework(fullLayout); + + // unfortunately, we have to keep track of some axis tick settings + // as ternary subplots do not implement the 'ticks' editType + this.aTickLayout = null; + this.bTickLayout = null; + this.cTickLayout = null; +} + +module.exports = Ternary; + +var proto = Ternary.prototype; + +proto.init = function(fullLayout) { + this.container = fullLayout._ternarylayer; + this.defs = fullLayout._defs; + this.layoutId = fullLayout._uid; + this.traceHash = {}; + this.layers = {}; +}; + +proto.plot = function(ternaryCalcData, fullLayout) { + var _this = this; + var ternaryLayout = fullLayout[_this.id]; + var graphSize = fullLayout._size; + + _this._hasClipOnAxisFalse = false; + for(var i = 0; i < ternaryCalcData.length; i++) { + var trace = ternaryCalcData[i][0].trace; + + if(trace.cliponaxis === false) { + _this._hasClipOnAxisFalse = true; + break; + } + } + + _this.updateLayers(ternaryLayout); + _this.adjustLayout(ternaryLayout, graphSize); + Plots.generalUpdatePerTraceModule(_this.graphDiv, _this, ternaryCalcData, ternaryLayout); + _this.layers.plotbg.select('path').call(Color.fill, ternaryLayout.bgcolor); +}; + +proto.makeFramework = function(fullLayout) { + var _this = this; + var gd = _this.graphDiv; + var ternaryLayout = fullLayout[_this.id]; + + var clipId = _this.clipId = 'clip' + _this.layoutId + _this.id; + var clipIdRelative = _this.clipIdRelative = 'clip-relative' + _this.layoutId + _this.id; + + // clippath for this ternary subplot + _this.clipDef = Lib.ensureSingleById(fullLayout._clips, 'clipPath', clipId, function(s) { + s.append('path').attr('d', 'M0,0Z'); + }); + + // 'relative' clippath (i.e. no translation) for this ternary subplot + _this.clipDefRelative = Lib.ensureSingleById(fullLayout._clips, 'clipPath', clipIdRelative, function(s) { + s.append('path').attr('d', 'M0,0Z'); + }); + + // container for everything in this ternary subplot + _this.plotContainer = Lib.ensureSingle(_this.container, 'g', _this.id); + _this.updateLayers(ternaryLayout); + + Drawing.setClipUrl(_this.layers.backplot, clipId, gd); + Drawing.setClipUrl(_this.layers.grids, clipId, gd); +}; + +proto.updateLayers = function(ternaryLayout) { + var _this = this; + var layers = _this.layers; + + // inside that container, we have one container for the data, and + // one each for the three axes around it. + + var plotLayers = ['draglayer', 'plotbg', 'backplot', 'grids']; + + if(ternaryLayout.aaxis.layer === 'below traces') { + plotLayers.push('aaxis', 'aline'); + } + if(ternaryLayout.baxis.layer === 'below traces') { + plotLayers.push('baxis', 'bline'); + } + if(ternaryLayout.caxis.layer === 'below traces') { + plotLayers.push('caxis', 'cline'); + } + + plotLayers.push('frontplot'); + + if(ternaryLayout.aaxis.layer === 'above traces') { + plotLayers.push('aaxis', 'aline'); + } + if(ternaryLayout.baxis.layer === 'above traces') { + plotLayers.push('baxis', 'bline'); + } + if(ternaryLayout.caxis.layer === 'above traces') { + plotLayers.push('caxis', 'cline'); + } + + var toplevel = _this.plotContainer.selectAll('g.toplevel') + .data(plotLayers, String); + + var grids = ['agrid', 'bgrid', 'cgrid']; + + toplevel.enter().append('g') + .attr('class', function(d) { return 'toplevel ' + d; }) + .each(function(d) { + var s = d3.select(this); + layers[d] = s; + + // containers for different trace types. + // NOTE - this is different from cartesian, where all traces + // are in front of grids. Here I'm putting maps behind the grids + // so the grids will always be visible if they're requested. + // Perhaps we want that for cartesian too? + if(d === 'frontplot') { + s.append('g').classed('scatterlayer', true); + } else if(d === 'backplot') { + s.append('g').classed('maplayer', true); + } else if(d === 'plotbg') { + s.append('path').attr('d', 'M0,0Z'); + } else if(d === 'aline' || d === 'bline' || d === 'cline') { + s.append('path'); + } else if(d === 'grids') { + grids.forEach(function(d) { + layers[d] = s.append('g').classed('grid ' + d, true); + }); + } + }); + + toplevel.order(); +}; + +var whRatio = Math.sqrt(4 / 3); + +proto.adjustLayout = function(ternaryLayout, graphSize) { + var _this = this; + var domain = ternaryLayout.domain; + var xDomainCenter = (domain.x[0] + domain.x[1]) / 2; + var yDomainCenter = (domain.y[0] + domain.y[1]) / 2; + var xDomain = domain.x[1] - domain.x[0]; + var yDomain = domain.y[1] - domain.y[0]; + var wmax = xDomain * graphSize.w; + var hmax = yDomain * graphSize.h; + var sum = ternaryLayout.sum; + var amin = ternaryLayout.aaxis.min; + var bmin = ternaryLayout.baxis.min; + var cmin = ternaryLayout.caxis.min; + + var x0, y0, w, h, xDomainFinal, yDomainFinal; + + if(wmax > whRatio * hmax) { + h = hmax; + w = h * whRatio; + } else { + w = wmax; + h = w / whRatio; + } + + xDomainFinal = xDomain * w / wmax; + yDomainFinal = yDomain * h / hmax; + + x0 = graphSize.l + graphSize.w * xDomainCenter - w / 2; + y0 = graphSize.t + graphSize.h * (1 - yDomainCenter) - h / 2; + + _this.x0 = x0; + _this.y0 = y0; + _this.w = w; + _this.h = h; + _this.sum = sum; + + // set up the x and y axis objects we'll use to lay out the points + _this.xaxis = { + type: 'linear', + range: [amin + 2 * cmin - sum, sum - amin - 2 * bmin], + domain: [ + xDomainCenter - xDomainFinal / 2, + xDomainCenter + xDomainFinal / 2 + ], + _id: 'x' + }; + setConvert(_this.xaxis, _this.graphDiv._fullLayout); + _this.xaxis.setScale(); + _this.xaxis.isPtWithinRange = function(d) { + return ( + d.a >= _this.aaxis.range[0] && + d.a <= _this.aaxis.range[1] && + d.b >= _this.baxis.range[1] && + d.b <= _this.baxis.range[0] && + d.c >= _this.caxis.range[1] && + d.c <= _this.caxis.range[0] + ); + }; + + _this.yaxis = { + type: 'linear', + range: [amin, sum - bmin - cmin], + domain: [ + yDomainCenter - yDomainFinal / 2, + yDomainCenter + yDomainFinal / 2 + ], + _id: 'y' + }; + setConvert(_this.yaxis, _this.graphDiv._fullLayout); + _this.yaxis.setScale(); + _this.yaxis.isPtWithinRange = function() { return true; }; + + // set up the modified axes for tick drawing + var yDomain0 = _this.yaxis.domain[0]; + + // aaxis goes up the left side. Set it up as a y axis, but with + // fictitious angles and domain, but then rotate and translate + // it into place at the end + var aaxis = _this.aaxis = extendFlat({}, ternaryLayout.aaxis, { + range: [amin, sum - bmin - cmin], + side: 'left', + // tickangle = 'auto' means 0 anyway for a y axis, need to coerce to 0 here + // so we can shift by 30. + tickangle: (+ternaryLayout.aaxis.tickangle || 0) - 30, + domain: [yDomain0, yDomain0 + yDomainFinal * whRatio], + anchor: 'free', + position: 0, + _id: 'y', + _length: w + }); + setConvert(aaxis, _this.graphDiv._fullLayout); + aaxis.setScale(); + + // baxis goes across the bottom (backward). We can set it up as an x axis + // without any enclosing transformation. + var baxis = _this.baxis = extendFlat({}, ternaryLayout.baxis, { + range: [sum - amin - cmin, bmin], + side: 'bottom', + domain: _this.xaxis.domain, + anchor: 'free', + position: 0, + _id: 'x', + _length: w + }); + setConvert(baxis, _this.graphDiv._fullLayout); + baxis.setScale(); + + // caxis goes down the right side. Set it up as a y axis, with + // post-transformation similar to aaxis + var caxis = _this.caxis = extendFlat({}, ternaryLayout.caxis, { + range: [sum - amin - bmin, cmin], + side: 'right', + tickangle: (+ternaryLayout.caxis.tickangle || 0) + 30, + domain: [yDomain0, yDomain0 + yDomainFinal * whRatio], + anchor: 'free', + position: 0, + _id: 'y', + _length: w + }); + setConvert(caxis, _this.graphDiv._fullLayout); + caxis.setScale(); + + var triangleClip = 'M' + x0 + ',' + (y0 + h) + 'h' + w + 'l-' + (w / 2) + ',-' + h + 'Z'; + _this.clipDef.select('path').attr('d', triangleClip); + _this.layers.plotbg.select('path').attr('d', triangleClip); + + var triangleClipRelative = 'M0,' + h + 'h' + w + 'l-' + (w / 2) + ',-' + h + 'Z'; + _this.clipDefRelative.select('path').attr('d', triangleClipRelative); + + var plotTransform = 'translate(' + x0 + ',' + y0 + ')'; + _this.plotContainer.selectAll('.scatterlayer,.maplayer') + .attr('transform', plotTransform); + + _this.clipDefRelative.select('path').attr('transform', null); + + // TODO: shift axes to accommodate linewidth*sin(30) tick mark angle + + // TODO: there's probably an easier way to handle these translations/offsets now... + var bTransform = 'translate(' + (x0 - baxis._offset) + ',' + (y0 + h) + ')'; + + _this.layers.baxis.attr('transform', bTransform); + _this.layers.bgrid.attr('transform', bTransform); + + var aTransform = 'translate(' + (x0 + w / 2) + ',' + y0 + + ')rotate(30)translate(0,' + -aaxis._offset + ')'; + _this.layers.aaxis.attr('transform', aTransform); + _this.layers.agrid.attr('transform', aTransform); + + var cTransform = 'translate(' + (x0 + w / 2) + ',' + y0 + + ')rotate(-30)translate(0,' + -caxis._offset + ')'; + _this.layers.caxis.attr('transform', cTransform); + _this.layers.cgrid.attr('transform', cTransform); + + _this.drawAxes(true); + + _this.layers.aline.select('path') + .attr('d', aaxis.showline ? + 'M' + x0 + ',' + (y0 + h) + 'l' + (w / 2) + ',-' + h : 'M0,0') + .call(Color.stroke, aaxis.linecolor || '#000') + .style('stroke-width', (aaxis.linewidth || 0) + 'px'); + _this.layers.bline.select('path') + .attr('d', baxis.showline ? + 'M' + x0 + ',' + (y0 + h) + 'h' + w : 'M0,0') + .call(Color.stroke, baxis.linecolor || '#000') + .style('stroke-width', (baxis.linewidth || 0) + 'px'); + _this.layers.cline.select('path') + .attr('d', caxis.showline ? + 'M' + (x0 + w / 2) + ',' + y0 + 'l' + (w / 2) + ',' + h : 'M0,0') + .call(Color.stroke, caxis.linecolor || '#000') + .style('stroke-width', (caxis.linewidth || 0) + 'px'); + + if(!_this.graphDiv._context.staticPlot) { + _this.initInteractions(); + } + + Drawing.setClipUrl( + _this.layers.frontplot, + _this._hasClipOnAxisFalse ? null : _this.clipId, + _this.graphDiv + ); +}; + +proto.drawAxes = function(doTitles) { + var _this = this; + var gd = _this.graphDiv; + var titlesuffix = _this.id.substr(7) + 'title'; + var layers = _this.layers; + var aaxis = _this.aaxis; + var baxis = _this.baxis; + var caxis = _this.caxis; + + _this.drawAx(aaxis); + _this.drawAx(baxis); + _this.drawAx(caxis); + + if(doTitles) { + var apad = Math.max(aaxis.showticklabels ? aaxis.tickfont.size / 2 : 0, + (caxis.showticklabels ? caxis.tickfont.size * 0.75 : 0) + + (caxis.ticks === 'outside' ? caxis.ticklen * 0.87 : 0)); + var bpad = (baxis.showticklabels ? baxis.tickfont.size : 0) + + (baxis.ticks === 'outside' ? baxis.ticklen : 0) + 3; + + layers['a-title'] = Titles.draw(gd, 'a' + titlesuffix, { + propContainer: aaxis, + propName: _this.id + '.aaxis.title', + placeholder: _(gd, 'Click to enter Component A title'), + attributes: { + x: _this.x0 + _this.w / 2, + y: _this.y0 - aaxis.title.font.size / 3 - apad, + 'text-anchor': 'middle' + } + }); + layers['b-title'] = Titles.draw(gd, 'b' + titlesuffix, { + propContainer: baxis, + propName: _this.id + '.baxis.title', + placeholder: _(gd, 'Click to enter Component B title'), + attributes: { + x: _this.x0 - bpad, + y: _this.y0 + _this.h + baxis.title.font.size * 0.83 + bpad, + 'text-anchor': 'middle' + } + }); + layers['c-title'] = Titles.draw(gd, 'c' + titlesuffix, { + propContainer: caxis, + propName: _this.id + '.caxis.title', + placeholder: _(gd, 'Click to enter Component C title'), + attributes: { + x: _this.x0 + _this.w + bpad, + y: _this.y0 + _this.h + caxis.title.font.size * 0.83 + bpad, + 'text-anchor': 'middle' + } + }); + } +}; + +proto.drawAx = function(ax) { + var _this = this; + var gd = _this.graphDiv; + var axName = ax._name; + var axLetter = axName.charAt(0); + var axId = ax._id; + var axLayer = _this.layers[axName]; + var counterAngle = 30; + + var stashKey = axLetter + 'tickLayout'; + var newTickLayout = strTickLayout(ax); + if(_this[stashKey] !== newTickLayout) { + axLayer.selectAll('.' + axId + 'tick').remove(); + _this[stashKey] = newTickLayout; + } + + ax.setScale(); + + var vals = Axes.calcTicks(ax); + var valsClipped = Axes.clipEnds(ax, vals); + var transFn = Axes.makeTransFn(ax); + var tickSign = Axes.getTickSigns(ax)[2]; + + var caRad = Lib.deg2rad(counterAngle); + var pad = tickSign * (ax.linewidth || 1) / 2; + var len = tickSign * ax.ticklen; + var w = _this.w; + var h = _this.h; + + var tickPath = axLetter === 'b' ? + 'M0,' + pad + 'l' + (Math.sin(caRad) * len) + ',' + (Math.cos(caRad) * len) : + 'M' + pad + ',0l' + (Math.cos(caRad) * len) + ',' + (-Math.sin(caRad) * len); + + var gridPath = { + a: 'M0,0l' + h + ',-' + (w / 2), + b: 'M0,0l-' + (w / 2) + ',-' + h, + c: 'M0,0l-' + h + ',' + (w / 2) + }[axLetter]; + + Axes.drawTicks(gd, ax, { + vals: ax.ticks === 'inside' ? valsClipped : vals, + layer: axLayer, + path: tickPath, + transFn: transFn, + crisp: false + }); + + Axes.drawGrid(gd, ax, { + vals: valsClipped, + layer: _this.layers[axLetter + 'grid'], + path: gridPath, + transFn: transFn, + crisp: false + }); + + Axes.drawLabels(gd, ax, { + vals: vals, + layer: axLayer, + transFn: transFn, + labelFns: Axes.makeLabelFns(ax, 0, counterAngle) + }); +}; + +function strTickLayout(axLayout) { + return axLayout.ticks + String(axLayout.ticklen) + String(axLayout.showticklabels); +} + +// hard coded paths for zoom corners +// uses the same sizing as cartesian, length is MINZOOM/2, width is 3px +var CLEN = constants.MINZOOM / 2 + 0.87; +var BLPATH = 'm-0.87,.5h' + CLEN + 'v3h-' + (CLEN + 5.2) + + 'l' + (CLEN / 2 + 2.6) + ',-' + (CLEN * 0.87 + 4.5) + + 'l2.6,1.5l-' + (CLEN / 2) + ',' + (CLEN * 0.87) + 'Z'; +var BRPATH = 'm0.87,.5h-' + CLEN + 'v3h' + (CLEN + 5.2) + + 'l-' + (CLEN / 2 + 2.6) + ',-' + (CLEN * 0.87 + 4.5) + + 'l-2.6,1.5l' + (CLEN / 2) + ',' + (CLEN * 0.87) + 'Z'; +var TOPPATH = 'm0,1l' + (CLEN / 2) + ',' + (CLEN * 0.87) + + 'l2.6,-1.5l-' + (CLEN / 2 + 2.6) + ',-' + (CLEN * 0.87 + 4.5) + + 'l-' + (CLEN / 2 + 2.6) + ',' + (CLEN * 0.87 + 4.5) + + 'l2.6,1.5l' + (CLEN / 2) + ',-' + (CLEN * 0.87) + 'Z'; +var STARTMARKER = 'm0.5,0.5h5v-2h-5v-5h-2v5h-5v2h5v5h2Z'; + +// I guess this could be shared with cartesian... but for now it's separate. +var SHOWZOOMOUTTIP = true; + +proto.initInteractions = function() { + var _this = this; + var dragger = _this.layers.plotbg.select('path').node(); + var gd = _this.graphDiv; + var zoomLayer = gd._fullLayout._zoomlayer; + + // use plotbg for the main interactions + var dragOptions = { + element: dragger, + gd: gd, + plotinfo: { + id: _this.id, + xaxis: _this.xaxis, + yaxis: _this.yaxis + }, + subplot: _this.id, + prepFn: function(e, startX, startY) { + // these aren't available yet when initInteractions + // is called + dragOptions.xaxes = [_this.xaxis]; + dragOptions.yaxes = [_this.yaxis]; + var dragModeNow = gd._fullLayout.dragmode; + + if(dragModeNow === 'lasso') dragOptions.minDrag = 1; + else dragOptions.minDrag = undefined; + + if(dragModeNow === 'zoom') { + dragOptions.moveFn = zoomMove; + dragOptions.clickFn = clickZoomPan; + dragOptions.doneFn = zoomDone; + zoomPrep(e, startX, startY); + } else if(dragModeNow === 'pan') { + dragOptions.moveFn = plotDrag; + dragOptions.clickFn = clickZoomPan; + dragOptions.doneFn = dragDone; + panPrep(); + clearSelect(gd); + } else if(dragModeNow === 'select' || dragModeNow === 'lasso') { + prepSelect(e, startX, startY, dragOptions, dragModeNow); + } + } + }; + + var x0, y0, mins0, span0, mins, lum, path0, dimmed, zb, corners; + + function makeUpdate(_mins) { + var attrs = {}; + attrs[_this.id + '.aaxis.min'] = _mins.a; + attrs[_this.id + '.baxis.min'] = _mins.b; + attrs[_this.id + '.caxis.min'] = _mins.c; + return attrs; + } + + function clickZoomPan(numClicks, evt) { + var clickMode = gd._fullLayout.clickmode; + + removeZoombox(gd); + + if(numClicks === 2) { + gd.emit('plotly_doubleclick', null); + Registry.call('_guiRelayout', gd, makeUpdate({a: 0, b: 0, c: 0})); + } + + if(clickMode.indexOf('select') > -1 && numClicks === 1) { + selectOnClick(evt, gd, [_this.xaxis], [_this.yaxis], _this.id, dragOptions); + } + + if(clickMode.indexOf('event') > -1) { + Fx.click(gd, evt, _this.id); + } + } + + function zoomPrep(e, startX, startY) { + var dragBBox = dragger.getBoundingClientRect(); + x0 = startX - dragBBox.left; + y0 = startY - dragBBox.top; + mins0 = { + a: _this.aaxis.range[0], + b: _this.baxis.range[1], + c: _this.caxis.range[1] + }; + mins = mins0; + span0 = _this.aaxis.range[1] - mins0.a; + lum = tinycolor(_this.graphDiv._fullLayout[_this.id].bgcolor).getLuminance(); + path0 = 'M0,' + _this.h + 'L' + (_this.w / 2) + ', 0L' + _this.w + ',' + _this.h + 'Z'; + dimmed = false; + + zb = zoomLayer.append('path') + .attr('class', 'zoombox') + .attr('transform', 'translate(' + _this.x0 + ', ' + _this.y0 + ')') + .style({ + 'fill': lum > 0.2 ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0)', + 'stroke-width': 0 + }) + .attr('d', path0); + + corners = zoomLayer.append('path') + .attr('class', 'zoombox-corners') + .attr('transform', 'translate(' + _this.x0 + ', ' + _this.y0 + ')') + .style({ + fill: Color.background, + stroke: Color.defaultLine, + 'stroke-width': 1, + opacity: 0 + }) + .attr('d', 'M0,0Z'); + + clearSelect(gd); + } + + function getAFrac(x, y) { return 1 - (y / _this.h); } + function getBFrac(x, y) { return 1 - ((x + (_this.h - y) / Math.sqrt(3)) / _this.w); } + function getCFrac(x, y) { return ((x - (_this.h - y) / Math.sqrt(3)) / _this.w); } + + function zoomMove(dx0, dy0) { + var x1 = x0 + dx0; + var y1 = y0 + dy0; + var afrac = Math.max(0, Math.min(1, getAFrac(x0, y0), getAFrac(x1, y1))); + var bfrac = Math.max(0, Math.min(1, getBFrac(x0, y0), getBFrac(x1, y1))); + var cfrac = Math.max(0, Math.min(1, getCFrac(x0, y0), getCFrac(x1, y1))); + var xLeft = ((afrac / 2) + cfrac) * _this.w; + var xRight = (1 - (afrac / 2) - bfrac) * _this.w; + var xCenter = (xLeft + xRight) / 2; + var xSpan = xRight - xLeft; + var yBottom = (1 - afrac) * _this.h; + var yTop = yBottom - xSpan / whRatio; + + if(xSpan < constants.MINZOOM) { + mins = mins0; + zb.attr('d', path0); + corners.attr('d', 'M0,0Z'); + } else { + mins = { + a: mins0.a + afrac * span0, + b: mins0.b + bfrac * span0, + c: mins0.c + cfrac * span0 + }; + zb.attr('d', path0 + 'M' + xLeft + ',' + yBottom + + 'H' + xRight + 'L' + xCenter + ',' + yTop + + 'L' + xLeft + ',' + yBottom + 'Z'); + corners.attr('d', 'M' + x0 + ',' + y0 + STARTMARKER + + 'M' + xLeft + ',' + yBottom + BLPATH + + 'M' + xRight + ',' + yBottom + BRPATH + + 'M' + xCenter + ',' + yTop + TOPPATH); + } + + if(!dimmed) { + zb.transition() + .style('fill', lum > 0.2 ? 'rgba(0,0,0,0.4)' : + 'rgba(255,255,255,0.3)') + .duration(200); + corners.transition() + .style('opacity', 1) + .duration(200); + dimmed = true; + } + + gd.emit('plotly_relayouting', makeUpdate(mins)); + } + + function zoomDone() { + removeZoombox(gd); + + if(mins === mins0) return; + + Registry.call('_guiRelayout', gd, makeUpdate(mins)); + + if(SHOWZOOMOUTTIP && gd.data && gd._context.showTips) { + Lib.notifier(_(gd, 'Double-click to zoom back out'), 'long'); + SHOWZOOMOUTTIP = false; + } + } + + function panPrep() { + mins0 = { + a: _this.aaxis.range[0], + b: _this.baxis.range[1], + c: _this.caxis.range[1] + }; + mins = mins0; + } + + function plotDrag(dx, dy) { + var dxScaled = dx / _this.xaxis._m; + var dyScaled = dy / _this.yaxis._m; + mins = { + a: mins0.a - dyScaled, + b: mins0.b + (dxScaled + dyScaled) / 2, + c: mins0.c - (dxScaled - dyScaled) / 2 + }; + var minsorted = [mins.a, mins.b, mins.c].sort(); + var minindices = { + a: minsorted.indexOf(mins.a), + b: minsorted.indexOf(mins.b), + c: minsorted.indexOf(mins.c) + }; + if(minsorted[0] < 0) { + if(minsorted[1] + minsorted[0] / 2 < 0) { + minsorted[2] += minsorted[0] + minsorted[1]; + minsorted[0] = minsorted[1] = 0; + } else { + minsorted[2] += minsorted[0] / 2; + minsorted[1] += minsorted[0] / 2; + minsorted[0] = 0; + } + mins = { + a: minsorted[minindices.a], + b: minsorted[minindices.b], + c: minsorted[minindices.c] + }; + dy = (mins0.a - mins.a) * _this.yaxis._m; + dx = (mins0.c - mins.c - mins0.b + mins.b) * _this.xaxis._m; + } + + // move the data (translate, don't redraw) + var plotTransform = 'translate(' + (_this.x0 + dx) + ',' + (_this.y0 + dy) + ')'; + _this.plotContainer.selectAll('.scatterlayer,.maplayer') + .attr('transform', plotTransform); + + var plotTransform2 = 'translate(' + -dx + ',' + -dy + ')'; + _this.clipDefRelative.select('path').attr('transform', plotTransform2); + + // move the ticks + _this.aaxis.range = [mins.a, _this.sum - mins.b - mins.c]; + _this.baxis.range = [_this.sum - mins.a - mins.c, mins.b]; + _this.caxis.range = [_this.sum - mins.a - mins.b, mins.c]; + + _this.drawAxes(false); + + if(_this._hasClipOnAxisFalse) { + _this.plotContainer + .select('.scatterlayer').selectAll('.trace') + .call(Drawing.hideOutsideRangePoints, _this); + } + + gd.emit('plotly_relayouting', makeUpdate(mins)); + } + + function dragDone() { + Registry.call('_guiRelayout', gd, makeUpdate(mins)); + } + + // finally, set up hover and click + // these event handlers must already be set before dragElement.init + // so it can stash them and override them. + dragger.onmousemove = function(evt) { + Fx.hover(gd, evt, _this.id); + gd._fullLayout._lasthover = dragger; + gd._fullLayout._hoversubplot = _this.id; + }; + + dragger.onmouseout = function(evt) { + if(gd._dragging) return; + + dragElement.unhover(gd, evt); + }; + + dragElement.init(dragOptions); +}; + +function removeZoombox(gd) { + d3.select(gd) + .selectAll('.zoombox,.js-zoombox-backdrop,.js-zoombox-menu,.zoombox-corners') + .remove(); +} + +},{"../../components/color":51,"../../components/dragelement":69,"../../components/drawing":72,"../../components/fx":89,"../../components/titles":138,"../../lib":169,"../../lib/extend":164,"../../registry":258,"../cartesian/axes":213,"../cartesian/constants":219,"../cartesian/select":230,"../cartesian/set_convert":231,"../plots":245,"d3":16,"tinycolor2":34}],258:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Loggers = _dereq_('./lib/loggers'); +var noop = _dereq_('./lib/noop'); +var pushUnique = _dereq_('./lib/push_unique'); +var isPlainObject = _dereq_('./lib/is_plain_object'); +var addStyleRule = _dereq_('./lib/dom').addStyleRule; +var ExtendModule = _dereq_('./lib/extend'); + +var basePlotAttributes = _dereq_('./plots/attributes'); +var baseLayoutAttributes = _dereq_('./plots/layout_attributes'); + +var extendFlat = ExtendModule.extendFlat; +var extendDeepAll = ExtendModule.extendDeepAll; + +exports.modules = {}; +exports.allCategories = {}; +exports.allTypes = []; +exports.subplotsRegistry = {}; +exports.transformsRegistry = {}; +exports.componentsRegistry = {}; +exports.layoutArrayContainers = []; +exports.layoutArrayRegexes = []; +exports.traceLayoutAttributes = {}; +exports.localeRegistry = {}; +exports.apiMethodRegistry = {}; +exports.collectableSubplotTypes = null; + +/** + * Top-level register routine, exported as Plotly.register + * + * @param {object array or array of objects} _modules : + * module object or list of module object to register. + * + * A valid `moduleType: 'trace'` module has fields: + * - name {string} : the trace type + * - categories {array} : categories associated with this trace type, + * tested with Register.traceIs() + * - meta {object} : meta info (mostly for plot-schema) + * + * A valid `moduleType: 'locale'` module has fields: + * - name {string} : the locale name. Should be a 2-digit language string ('en', 'de') + * optionally with a country/region code ('en-GB', 'de-CH'). If a country + * code is used but the base language locale has not yet been supplied, + * we will use this locale for the base as well. + * - dictionary {object} : the dictionary mapping input strings to localized strings + * generally the keys should be the literal input strings, but + * if default translations are provided you can use any string as a key. + * - format {object} : a `d3.locale` format specifier for this locale + * any omitted keys we'll fall back on en-US. + * + * A valid `moduleType: 'transform'` module has fields: + * - name {string} : transform name + * - transform {function} : default-level transform function + * - calcTransform {function} : calc-level transform function + * - attributes {object} : transform attributes declarations + * - supplyDefaults {function} : attributes default-supply function + * + * A valid `moduleType: 'component'` module has fields: + * - name {string} : the component name, used it with Register.getComponentMethod() + * to employ component method. + * + * A valid `moduleType: 'apiMethod'` module has fields: + * - name {string} : the api method name. + * - fn {function} : the api method called with Register.call(); + * + */ +exports.register = function register(_modules) { + exports.collectableSubplotTypes = null; + + if(!_modules) { + throw new Error('No argument passed to Plotly.register.'); + } else if(_modules && !Array.isArray(_modules)) { + _modules = [_modules]; + } + + for(var i = 0; i < _modules.length; i++) { + var newModule = _modules[i]; + + if(!newModule) { + throw new Error('Invalid module was attempted to be registered!'); + } + + switch(newModule.moduleType) { + case 'trace': + registerTraceModule(newModule); + break; + case 'transform': + registerTransformModule(newModule); + break; + case 'component': + registerComponentModule(newModule); + break; + case 'locale': + registerLocale(newModule); + break; + case 'apiMethod': + var name = newModule.name; + exports.apiMethodRegistry[name] = newModule.fn; + break; + default: + throw new Error('Invalid module was attempted to be registered!'); + } + } +}; + +/** + * Get registered module using trace object or trace type + * + * @param {object||string} trace + * trace object with prop 'type' or trace type as a string + * @return {object} + * module object corresponding to trace type + */ +exports.getModule = function(trace) { + var _module = exports.modules[getTraceType(trace)]; + if(!_module) return false; + return _module._module; +}; + +/** + * Determine if this trace type is in a given category + * + * @param {object||string} traceType + * a trace (object) or trace type (string) + * @param {string} category + * category in question + * @return {boolean} + */ +exports.traceIs = function(traceType, category) { + traceType = getTraceType(traceType); + + // old plot.ly workspace hack, nothing to see here + if(traceType === 'various') return false; + + var _module = exports.modules[traceType]; + + if(!_module) { + if(traceType && traceType !== 'area') { + Loggers.log('Unrecognized trace type ' + traceType + '.'); + } + + _module = exports.modules[basePlotAttributes.type.dflt]; + } + + return !!_module.categories[category]; +}; + +/** + * Determine if this trace has a transform of the given type and return + * array of matching indices. + * + * @param {object} data + * a trace object (member of data or fullData) + * @param {string} type + * type of trace to test + * @return {array} + * array of matching indices. If none found, returns [] + */ +exports.getTransformIndices = function(data, type) { + var indices = []; + var transforms = data.transforms || []; + for(var i = 0; i < transforms.length; i++) { + if(transforms[i].type === type) { + indices.push(i); + } + } + return indices; +}; + +/** + * Determine if this trace has a transform of the given type + * + * @param {object} data + * a trace object (member of data or fullData) + * @param {string} type + * type of trace to test + * @return {boolean} + */ +exports.hasTransform = function(data, type) { + var transforms = data.transforms || []; + for(var i = 0; i < transforms.length; i++) { + if(transforms[i].type === type) { + return true; + } + } + return false; +}; + +/** + * Retrieve component module method. Falls back on noop if either the + * module or the method is missing, so the result can always be safely called + * + * @param {string} name + * name of component (as declared in component module) + * @param {string} method + * name of component module method + * @return {function} + */ +exports.getComponentMethod = function(name, method) { + var _module = exports.componentsRegistry[name]; + + if(!_module) return noop; + return _module[method] || noop; +}; + +/** + * Call registered api method. + * + * @param {string} name : api method name + * @param {...array} args : arguments passed to api method + * @return {any} : returns api method output + */ +exports.call = function() { + var name = arguments[0]; + var args = [].slice.call(arguments, 1); + return exports.apiMethodRegistry[name].apply(null, args); +}; + +function registerTraceModule(_module) { + var thisType = _module.name; + var categoriesIn = _module.categories; + var meta = _module.meta; + + if(exports.modules[thisType]) { + Loggers.log('Type ' + thisType + ' already registered'); + return; + } + + if(!exports.subplotsRegistry[_module.basePlotModule.name]) { + registerSubplot(_module.basePlotModule); + } + + var categoryObj = {}; + for(var i = 0; i < categoriesIn.length; i++) { + categoryObj[categoriesIn[i]] = true; + exports.allCategories[categoriesIn[i]] = true; + } + + exports.modules[thisType] = { + _module: _module, + categories: categoryObj + }; + + if(meta && Object.keys(meta).length) { + exports.modules[thisType].meta = meta; + } + + exports.allTypes.push(thisType); + + for(var componentName in exports.componentsRegistry) { + mergeComponentAttrsToTrace(componentName, thisType); + } + + /* + * Collect all trace layout attributes in one place for easier lookup later + * but don't merge them into the base schema as it would confuse the docs + * (at least after https://github.com/plotly/documentation/issues/202 gets done!) + */ + if(_module.layoutAttributes) { + extendFlat(exports.traceLayoutAttributes, _module.layoutAttributes); + } + + var basePlotModule = _module.basePlotModule; + var bpmName = basePlotModule.name; + + // add mapbox-gl CSS here to avoid console warning on instantiation + if(bpmName === 'mapbox') { + var styleRules = basePlotModule.constants.styleRules; + for(var k in styleRules) { + addStyleRule('.js-plotly-plot .plotly .mapboxgl-' + k, styleRules[k]); + } + } + + // if `plotly-geo-assets.js` is not included, + // add `PlotlyGeoAssets` global to stash references to all fetched + // topojson / geojson data + if((bpmName === 'geo' || bpmName === 'mapbox') && + (typeof window !== undefined && window.PlotlyGeoAssets === undefined) + ) { + window.PlotlyGeoAssets = {topojson: {}}; + } +} + +function registerSubplot(_module) { + var plotType = _module.name; + + if(exports.subplotsRegistry[plotType]) { + Loggers.log('Plot type ' + plotType + ' already registered.'); + return; + } + + // relayout array handling will look for component module methods with this + // name and won't find them because this is a subplot module... but that + // should be fine, it will just fall back on redrawing the plot. + findArrayRegexps(_module); + + // not sure what's best for the 'cartesian' type at this point + exports.subplotsRegistry[plotType] = _module; + + for(var componentName in exports.componentsRegistry) { + mergeComponentAttrsToSubplot(componentName, _module.name); + } +} + +function registerComponentModule(_module) { + if(typeof _module.name !== 'string') { + throw new Error('Component module *name* must be a string.'); + } + + var name = _module.name; + exports.componentsRegistry[name] = _module; + + if(_module.layoutAttributes) { + if(_module.layoutAttributes._isLinkedToArray) { + pushUnique(exports.layoutArrayContainers, name); + } + findArrayRegexps(_module); + } + + for(var traceType in exports.modules) { + mergeComponentAttrsToTrace(name, traceType); + } + + for(var subplotName in exports.subplotsRegistry) { + mergeComponentAttrsToSubplot(name, subplotName); + } + + for(var transformType in exports.transformsRegistry) { + mergeComponentAttrsToTransform(name, transformType); + } + + if(_module.schema && _module.schema.layout) { + extendDeepAll(baseLayoutAttributes, _module.schema.layout); + } +} + +function registerTransformModule(_module) { + if(typeof _module.name !== 'string') { + throw new Error('Transform module *name* must be a string.'); + } + + var prefix = 'Transform module ' + _module.name; + var hasTransform = typeof _module.transform === 'function'; + var hasCalcTransform = typeof _module.calcTransform === 'function'; + + if(!hasTransform && !hasCalcTransform) { + throw new Error(prefix + ' is missing a *transform* or *calcTransform* method.'); + } + if(hasTransform && hasCalcTransform) { + Loggers.log([ + prefix + ' has both a *transform* and *calcTransform* methods.', + 'Please note that all *transform* methods are executed', + 'before all *calcTransform* methods.' + ].join(' ')); + } + if(!isPlainObject(_module.attributes)) { + Loggers.log(prefix + ' registered without an *attributes* object.'); + } + if(typeof _module.supplyDefaults !== 'function') { + Loggers.log(prefix + ' registered without a *supplyDefaults* method.'); + } + + exports.transformsRegistry[_module.name] = _module; + + for(var componentName in exports.componentsRegistry) { + mergeComponentAttrsToTransform(componentName, _module.name); + } +} + +function registerLocale(_module) { + var locale = _module.name; + var baseLocale = locale.split('-')[0]; + + var newDict = _module.dictionary; + var newFormat = _module.format; + var hasDict = newDict && Object.keys(newDict).length; + var hasFormat = newFormat && Object.keys(newFormat).length; + + var locales = exports.localeRegistry; + + var localeObj = locales[locale]; + if(!localeObj) locales[locale] = localeObj = {}; + + // Should we use this dict for the base locale? + // In case we're overwriting a previous dict for this locale, check + // whether the base matches the full locale dict now. If we're not + // overwriting, locales[locale] is undefined so this just checks if + // baseLocale already had a dict or not. + // Same logic for dateFormats + if(baseLocale !== locale) { + var baseLocaleObj = locales[baseLocale]; + if(!baseLocaleObj) locales[baseLocale] = baseLocaleObj = {}; + + if(hasDict && baseLocaleObj.dictionary === localeObj.dictionary) { + baseLocaleObj.dictionary = newDict; + } + if(hasFormat && baseLocaleObj.format === localeObj.format) { + baseLocaleObj.format = newFormat; + } + } + + if(hasDict) localeObj.dictionary = newDict; + if(hasFormat) localeObj.format = newFormat; +} + +function findArrayRegexps(_module) { + if(_module.layoutAttributes) { + var arrayAttrRegexps = _module.layoutAttributes._arrayAttrRegexps; + if(arrayAttrRegexps) { + for(var i = 0; i < arrayAttrRegexps.length; i++) { + pushUnique(exports.layoutArrayRegexes, arrayAttrRegexps[i]); + } + } + } +} + +function mergeComponentAttrsToTrace(componentName, traceType) { + var componentSchema = exports.componentsRegistry[componentName].schema; + if(!componentSchema || !componentSchema.traces) return; + + var traceAttrs = componentSchema.traces[traceType]; + if(traceAttrs) { + extendDeepAll(exports.modules[traceType]._module.attributes, traceAttrs); + } +} + +function mergeComponentAttrsToTransform(componentName, transformType) { + var componentSchema = exports.componentsRegistry[componentName].schema; + if(!componentSchema || !componentSchema.transforms) return; + + var transformAttrs = componentSchema.transforms[transformType]; + if(transformAttrs) { + extendDeepAll(exports.transformsRegistry[transformType].attributes, transformAttrs); + } +} + +function mergeComponentAttrsToSubplot(componentName, subplotName) { + var componentSchema = exports.componentsRegistry[componentName].schema; + if(!componentSchema || !componentSchema.subplots) return; + + var subplotModule = exports.subplotsRegistry[subplotName]; + var subplotAttrs = subplotModule.layoutAttributes; + var subplotAttr = subplotModule.attr === 'subplot' ? subplotModule.name : subplotModule.attr; + if(Array.isArray(subplotAttr)) subplotAttr = subplotAttr[0]; + + var componentLayoutAttrs = componentSchema.subplots[subplotAttr]; + if(subplotAttrs && componentLayoutAttrs) { + extendDeepAll(subplotAttrs, componentLayoutAttrs); + } +} + +function getTraceType(traceType) { + if(typeof traceType === 'object') traceType = traceType.type; + return traceType; +} + +},{"./lib/dom":162,"./lib/extend":164,"./lib/is_plain_object":170,"./lib/loggers":173,"./lib/noop":178,"./lib/push_unique":182,"./plots/attributes":210,"./plots/layout_attributes":243}],259:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Registry = _dereq_('../registry'); +var Lib = _dereq_('../lib'); + +var extendFlat = Lib.extendFlat; +var extendDeep = Lib.extendDeep; + +// Put default plotTile layouts here +function cloneLayoutOverride(tileClass) { + var override; + + switch(tileClass) { + case 'themes__thumb': + override = { + autosize: true, + width: 150, + height: 150, + title: {text: ''}, + showlegend: false, + margin: {l: 5, r: 5, t: 5, b: 5, pad: 0}, + annotations: [] + }; + break; + + case 'thumbnail': + override = { + title: {text: ''}, + hidesources: true, + showlegend: false, + borderwidth: 0, + bordercolor: '', + margin: {l: 1, r: 1, t: 1, b: 1, pad: 0}, + annotations: [] + }; + break; + + default: + override = {}; + } + + + return override; +} + +function keyIsAxis(keyName) { + var types = ['xaxis', 'yaxis', 'zaxis']; + return (types.indexOf(keyName.slice(0, 5)) > -1); +} + + +module.exports = function clonePlot(graphObj, options) { + // Polar plot compatibility + if(graphObj.framework && graphObj.framework.isPolar) { + graphObj = graphObj.framework.getConfig(); + } + + var i; + var oldData = graphObj.data; + var oldLayout = graphObj.layout; + var newData = extendDeep([], oldData); + var newLayout = extendDeep({}, oldLayout, cloneLayoutOverride(options.tileClass)); + var context = graphObj._context || {}; + + if(options.width) newLayout.width = options.width; + if(options.height) newLayout.height = options.height; + + if(options.tileClass === 'thumbnail' || options.tileClass === 'themes__thumb') { + // kill annotations + newLayout.annotations = []; + var keys = Object.keys(newLayout); + + for(i = 0; i < keys.length; i++) { + if(keyIsAxis(keys[i])) { + newLayout[keys[i]].title = {text: ''}; + } + } + + // kill colorbar and pie labels + for(i = 0; i < newData.length; i++) { + var trace = newData[i]; + trace.showscale = false; + if(trace.marker) trace.marker.showscale = false; + if(Registry.traceIs(trace, 'pie-like')) trace.textposition = 'none'; + } + } + + if(Array.isArray(options.annotations)) { + for(i = 0; i < options.annotations.length; i++) { + newLayout.annotations.push(options.annotations[i]); + } + } + + // TODO: does this scene modification really belong here? + // If we still need it, can it move into the gl3d module? + var sceneIds = Object.keys(newLayout).filter(function(key) { + return key.match(/^scene\d*$/); + }); + if(sceneIds.length) { + var axesImageOverride = {}; + if(options.tileClass === 'thumbnail') { + axesImageOverride = { + title: {text: ''}, + showaxeslabels: false, + showticklabels: false, + linetickenable: false + }; + } + for(i = 0; i < sceneIds.length; i++) { + var scene = newLayout[sceneIds[i]]; + + if(!scene.xaxis) { + scene.xaxis = {}; + } + + if(!scene.yaxis) { + scene.yaxis = {}; + } + + if(!scene.zaxis) { + scene.zaxis = {}; + } + + extendFlat(scene.xaxis, axesImageOverride); + extendFlat(scene.yaxis, axesImageOverride); + extendFlat(scene.zaxis, axesImageOverride); + + // TODO what does this do? + scene._scene = null; + } + } + + var gd = document.createElement('div'); + if(options.tileClass) gd.className = options.tileClass; + + var plotTile = { + gd: gd, + td: gd, // for external (image server) compatibility + layout: newLayout, + data: newData, + config: { + staticPlot: (options.staticPlot === undefined) ? + true : + options.staticPlot, + plotGlPixelRatio: (options.plotGlPixelRatio === undefined) ? + 2 : + options.plotGlPixelRatio, + displaylogo: options.displaylogo || false, + showLink: options.showLink || false, + showTips: options.showTips || false, + mapboxAccessToken: context.mapboxAccessToken + } + }; + + if(options.setBackground !== 'transparent') { + plotTile.config.setBackground = options.setBackground || 'opaque'; + } + + // attaching the default Layout the gd, so you can grab it later + plotTile.gd.defaultLayout = cloneLayoutOverride(options.tileClass); + + return plotTile; +}; + +},{"../lib":169,"../registry":258}],260:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../lib'); + +var toImage = _dereq_('../plot_api/to_image'); + +var fileSaver = _dereq_('./filesaver'); +var helpers = _dereq_('./helpers'); + +/** + * Plotly.downloadImage + * + * @param {object | string | HTML div} gd + * can either be a data/layout/config object + * or an existing graph
+ * or an id to an existing graph
+ * @param {object} opts (see Plotly.toImage in ../plot_api/to_image) + * @return {promise} + */ +function downloadImage(gd, opts) { + var _gd; + if(!Lib.isPlainObject(gd)) _gd = Lib.getGraphDiv(gd); + + opts = opts || {}; + opts.format = opts.format || 'png'; + opts.imageDataOnly = true; + + return new Promise(function(resolve, reject) { + if(_gd && _gd._snapshotInProgress) { + reject(new Error('Snapshotting already in progress.')); + } + + // see comments within svgtoimg for additional + // discussion of problems with IE + // can now draw to canvas, but CORS tainted canvas + // does not allow toDataURL + // svg format will work though + if(Lib.isIE() && opts.format !== 'svg') { + reject(new Error(helpers.MSG_IE_BAD_FORMAT)); + } + + if(_gd) _gd._snapshotInProgress = true; + var promise = toImage(gd, opts); + + var filename = opts.filename || gd.fn || 'newplot'; + filename += '.' + opts.format; + + promise.then(function(result) { + if(_gd) _gd._snapshotInProgress = false; + return fileSaver(result, filename, opts.format); + }).then(function(name) { + resolve(name); + }).catch(function(err) { + if(_gd) _gd._snapshotInProgress = false; + reject(err); + }); + }); +} + +module.exports = downloadImage; + +},{"../lib":169,"../plot_api/to_image":206,"./filesaver":261,"./helpers":262}],261:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../lib'); +var helpers = _dereq_('./helpers'); + +/* +* substantial portions of this code from FileSaver.js +* https://github.com/eligrey/FileSaver.js +* License: https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md +* FileSaver.js +* A saveAs() FileSaver implementation. +* 1.1.20160328 +* +* By Eli Grey, http://eligrey.com +* License: MIT +* See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md +*/ +function fileSaver(url, name, format) { + var saveLink = document.createElement('a'); + var canUseSaveLink = 'download' in saveLink; + + var promise = new Promise(function(resolve, reject) { + var blob; + var objectUrl; + + if(Lib.isIE9orBelow()) { + reject(new Error('IE < 10 unsupported')); + } + + // Safari doesn't allow downloading of blob urls + if(Lib.isSafari()) { + var prefix = format === 'svg' ? ',' : ';base64,'; + helpers.octetStream(prefix + encodeURIComponent(url)); + return resolve(name); + } + + // IE 10+ (native saveAs) + if(Lib.isIE()) { + // At this point we are only dealing with a decoded SVG as + // a data URL (since IE only supports SVG) + blob = helpers.createBlob(url, 'svg'); + window.navigator.msSaveBlob(blob, name); + blob = null; + return resolve(name); + } + + if(canUseSaveLink) { + blob = helpers.createBlob(url, format); + objectUrl = helpers.createObjectURL(blob); + + saveLink.href = objectUrl; + saveLink.download = name; + document.body.appendChild(saveLink); + saveLink.click(); + + document.body.removeChild(saveLink); + helpers.revokeObjectURL(objectUrl); + blob = null; + + return resolve(name); + } + + reject(new Error('download error')); + }); + + return promise; +} + + +module.exports = fileSaver; + +},{"../lib":169,"./helpers":262}],262:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Registry = _dereq_('../registry'); + +exports.getDelay = function(fullLayout) { + if(!fullLayout._has) return 0; + + return ( + fullLayout._has('gl3d') || + fullLayout._has('gl2d') || + fullLayout._has('mapbox') + ) ? 500 : 0; +}; + +exports.getRedrawFunc = function(gd) { + return function() { + var fullLayout = gd._fullLayout || {}; + var hasPolar = fullLayout._has && fullLayout._has('polar'); + var hasLegacyPolar = !hasPolar && gd.data && gd.data[0] && gd.data[0].r; + + if(!hasLegacyPolar) { + Registry.getComponentMethod('colorbar', 'draw')(gd); + } + }; +}; + +exports.encodeSVG = function(svg) { + return 'data:image/svg+xml,' + encodeURIComponent(svg); +}; + +var DOM_URL = window.URL || window.webkitURL; + +exports.createObjectURL = function(blob) { + return DOM_URL.createObjectURL(blob); +}; + +exports.revokeObjectURL = function(url) { + return DOM_URL.revokeObjectURL(url); +}; + +exports.createBlob = function(url, format) { + if(format === 'svg') { + return new window.Blob([url], {type: 'image/svg+xml;charset=utf-8'}); + } else { + var binary = fixBinary(window.atob(url)); + return new window.Blob([binary], {type: 'image/' + format}); + } +}; + +exports.octetStream = function(s) { + document.location.href = 'data:application/octet-stream' + s; +}; + +// Taken from https://bl.ocks.org/nolanlawson/0eac306e4dac2114c752 +function fixBinary(b) { + var len = b.length; + var buf = new ArrayBuffer(len); + var arr = new Uint8Array(buf); + for(var i = 0; i < len; i++) { + arr[i] = b.charCodeAt(i); + } + return buf; +} + +exports.IMAGE_URL_PREFIX = /^data:image\/\w+;base64,/; + +exports.MSG_IE_BAD_FORMAT = 'Sorry IE does not support downloading from canvas. Try {format:\'svg\'} instead.'; + +},{"../registry":258}],263:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var helpers = _dereq_('./helpers'); + +var Snapshot = { + getDelay: helpers.getDelay, + getRedrawFunc: helpers.getRedrawFunc, + clone: _dereq_('./cloneplot'), + toSVG: _dereq_('./tosvg'), + svgToImg: _dereq_('./svgtoimg'), + toImage: _dereq_('./toimage'), + downloadImage: _dereq_('./download') +}; + +module.exports = Snapshot; + +},{"./cloneplot":259,"./download":260,"./helpers":262,"./svgtoimg":264,"./toimage":265,"./tosvg":266}],264:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../lib'); +var EventEmitter = _dereq_('events').EventEmitter; + +var helpers = _dereq_('./helpers'); + +function svgToImg(opts) { + var ev = opts.emitter || new EventEmitter(); + + var promise = new Promise(function(resolve, reject) { + var Image = window.Image; + var svg = opts.svg; + var format = opts.format || 'png'; + + // IE only support svg + if(Lib.isIE() && format !== 'svg') { + var ieSvgError = new Error(helpers.MSG_IE_BAD_FORMAT); + reject(ieSvgError); + // eventually remove the ev + // in favor of promises + if(!opts.promise) { + return ev.emit('error', ieSvgError); + } else { + return promise; + } + } + + var canvas = opts.canvas; + var scale = opts.scale || 1; + var w0 = opts.width || 300; + var h0 = opts.height || 150; + var w1 = scale * w0; + var h1 = scale * h0; + + var ctx = canvas.getContext('2d'); + var img = new Image(); + var svgBlob, url; + + if(format === 'svg' || Lib.isIE9orBelow() || Lib.isSafari()) { + url = helpers.encodeSVG(svg); + } else { + svgBlob = helpers.createBlob(svg, 'svg'); + url = helpers.createObjectURL(svgBlob); + } + + canvas.width = w1; + canvas.height = h1; + + img.onload = function() { + var imgData; + + svgBlob = null; + helpers.revokeObjectURL(url); + + // don't need to draw to canvas if svg + // save some time and also avoid failure on IE + if(format !== 'svg') { + ctx.drawImage(img, 0, 0, w1, h1); + } + + switch(format) { + case 'jpeg': + imgData = canvas.toDataURL('image/jpeg'); + break; + case 'png': + imgData = canvas.toDataURL('image/png'); + break; + case 'webp': + imgData = canvas.toDataURL('image/webp'); + break; + case 'svg': + imgData = url; + break; + default: + var errorMsg = 'Image format is not jpeg, png, svg or webp.'; + reject(new Error(errorMsg)); + // eventually remove the ev + // in favor of promises + if(!opts.promise) { + return ev.emit('error', errorMsg); + } + } + resolve(imgData); + // eventually remove the ev + // in favor of promises + if(!opts.promise) { + ev.emit('success', imgData); + } + }; + + img.onerror = function(err) { + svgBlob = null; + helpers.revokeObjectURL(url); + + reject(err); + // eventually remove the ev + // in favor of promises + if(!opts.promise) { + return ev.emit('error', err); + } + }; + + img.src = url; + }); + + // temporary for backward compatibility + // move to only Promise in 2.0.0 + // and eliminate the EventEmitter + if(opts.promise) { + return promise; + } + + return ev; +} + +module.exports = svgToImg; + +},{"../lib":169,"./helpers":262,"events":15}],265:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var EventEmitter = _dereq_('events').EventEmitter; + +var Registry = _dereq_('../registry'); +var Lib = _dereq_('../lib'); + +var helpers = _dereq_('./helpers'); +var clonePlot = _dereq_('./cloneplot'); +var toSVG = _dereq_('./tosvg'); +var svgToImg = _dereq_('./svgtoimg'); + +/** + * @param {object} gd figure Object + * @param {object} opts option object + * @param opts.format 'jpeg' | 'png' | 'webp' | 'svg' + */ +function toImage(gd, opts) { + // first clone the GD so we can operate in a clean environment + var ev = new EventEmitter(); + + var clone = clonePlot(gd, {format: 'png'}); + var clonedGd = clone.gd; + + // put the cloned div somewhere off screen before attaching to DOM + clonedGd.style.position = 'absolute'; + clonedGd.style.left = '-5000px'; + document.body.appendChild(clonedGd); + + function wait() { + var delay = helpers.getDelay(clonedGd._fullLayout); + + setTimeout(function() { + var svg = toSVG(clonedGd); + + var canvas = document.createElement('canvas'); + canvas.id = Lib.randstr(); + + ev = svgToImg({ + format: opts.format, + width: clonedGd._fullLayout.width, + height: clonedGd._fullLayout.height, + canvas: canvas, + emitter: ev, + svg: svg + }); + + ev.clean = function() { + if(clonedGd) document.body.removeChild(clonedGd); + }; + }, delay); + } + + var redrawFunc = helpers.getRedrawFunc(clonedGd); + + Registry.call('plot', clonedGd, clone.data, clone.layout, clone.config) + .then(redrawFunc) + .then(wait) + .catch(function(err) { + ev.emit('error', err); + }); + + + return ev; +} + +module.exports = toImage; + +},{"../lib":169,"../registry":258,"./cloneplot":259,"./helpers":262,"./svgtoimg":264,"./tosvg":266,"events":15}],266:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var d3 = _dereq_('d3'); + +var Lib = _dereq_('../lib'); +var Drawing = _dereq_('../components/drawing'); +var Color = _dereq_('../components/color'); + +var xmlnsNamespaces = _dereq_('../constants/xmlns_namespaces'); +var DOUBLEQUOTE_REGEX = /"/g; +var DUMMY_SUB = 'TOBESTRIPPED'; +var DUMMY_REGEX = new RegExp('("' + DUMMY_SUB + ')|(' + DUMMY_SUB + '")', 'g'); + +function htmlEntityDecode(s) { + var hiddenDiv = d3.select('body').append('div').style({display: 'none'}).html(''); + var replaced = s.replace(/(&[^;]*;)/gi, function(d) { + if(d === '<') { return '<'; } // special handling for brackets + if(d === '&rt;') { return '>'; } + if(d.indexOf('<') !== -1 || d.indexOf('>') !== -1) { return ''; } + return hiddenDiv.html(d).text(); // everything else, let the browser decode it to unicode + }); + hiddenDiv.remove(); + return replaced; +} + +function xmlEntityEncode(str) { + return str.replace(/&(?!\w+;|\#[0-9]+;| \#x[0-9A-F]+;)/g, '&'); +} + +module.exports = function toSVG(gd, format, scale) { + var fullLayout = gd._fullLayout; + var svg = fullLayout._paper; + var toppaper = fullLayout._toppaper; + var width = fullLayout.width; + var height = fullLayout.height; + var i; + + // make background color a rect in the svg, then revert after scraping + // all other alterations have been dealt with by properly preparing the svg + // in the first place... like setting cursors with css classes so we don't + // have to remove them, and providing the right namespaces in the svg to + // begin with + svg.insert('rect', ':first-child') + .call(Drawing.setRect, 0, 0, width, height) + .call(Color.fill, fullLayout.paper_bgcolor); + + // subplot-specific to-SVG methods + // which notably add the contents of the gl-container + // into the main svg node + var basePlotModules = fullLayout._basePlotModules || []; + for(i = 0; i < basePlotModules.length; i++) { + var _module = basePlotModules[i]; + + if(_module.toSVG) _module.toSVG(gd); + } + + // add top items above them assumes everything in toppaper is either + // a group or a defs, and if it's empty (like hoverlayer) we can ignore it. + if(toppaper) { + var nodes = toppaper.node().childNodes; + + // make copy of nodes as childNodes prop gets mutated in loop below + var topGroups = Array.prototype.slice.call(nodes); + + for(i = 0; i < topGroups.length; i++) { + var topGroup = topGroups[i]; + + if(topGroup.childNodes.length) svg.node().appendChild(topGroup); + } + } + + // remove draglayer for Adobe Illustrator compatibility + if(fullLayout._draggers) { + fullLayout._draggers.remove(); + } + + // in case the svg element had an explicit background color, remove this + // we want the rect to get the color so it's the right size; svg bg will + // fill whatever container it's displayed in regardless of plot size. + svg.node().style.background = ''; + + svg.selectAll('text') + .attr({'data-unformatted': null, 'data-math': null}) + .each(function() { + var txt = d3.select(this); + + // hidden text is pre-formatting mathjax, the browser ignores it + // but in a static plot it's useless and it can confuse batik + // we've tried to standardize on display:none but make sure we still + // catch visibility:hidden if it ever arises + if(this.style.visibility === 'hidden' || this.style.display === 'none') { + txt.remove(); + return; + } else { + // clear other visibility/display values to default + // to not potentially confuse non-browser SVG implementations + txt.style({visibility: null, display: null}); + } + + // Font family styles break things because of quotation marks, + // so we must remove them *after* the SVG DOM has been serialized + // to a string (browsers convert singles back) + var ff = this.style.fontFamily; + if(ff && ff.indexOf('"') !== -1) { + txt.style('font-family', ff.replace(DOUBLEQUOTE_REGEX, DUMMY_SUB)); + } + }); + + svg.selectAll('.point, .scatterpts, .legendfill>path, .legendlines>path, .cbfill').each(function() { + var pt = d3.select(this); + + // similar to font family styles above, + // we must remove " after the SVG DOM has been serialized + var fill = this.style.fill; + if(fill && fill.indexOf('url(') !== -1) { + pt.style('fill', fill.replace(DOUBLEQUOTE_REGEX, DUMMY_SUB)); + } + + var stroke = this.style.stroke; + if(stroke && stroke.indexOf('url(') !== -1) { + pt.style('stroke', stroke.replace(DOUBLEQUOTE_REGEX, DUMMY_SUB)); + } + }); + + if(format === 'pdf' || format === 'eps') { + // these formats make the extra line MathJax adds around symbols look super thick in some cases + // it looks better if this is removed entirely. + svg.selectAll('#MathJax_SVG_glyphs path') + .attr('stroke-width', 0); + } + + // fix for IE namespacing quirk? + // http://stackoverflow.com/questions/19610089/unwanted-namespaces-on-svg-markup-when-using-xmlserializer-in-javascript-with-ie + svg.node().setAttributeNS(xmlnsNamespaces.xmlns, 'xmlns', xmlnsNamespaces.svg); + svg.node().setAttributeNS(xmlnsNamespaces.xmlns, 'xmlns:xlink', xmlnsNamespaces.xlink); + + if(format === 'svg' && scale) { + svg.attr('width', scale * width); + svg.attr('height', scale * height); + svg.attr('viewBox', '0 0 ' + width + ' ' + height); + } + + var s = new window.XMLSerializer().serializeToString(svg.node()); + s = htmlEntityDecode(s); + s = xmlEntityEncode(s); + + // Fix quotations around font strings and gradient URLs + s = s.replace(DUMMY_REGEX, '\''); + + // IE is very strict, so we will need to clean + // svg with the following regex + // yes this is messy, but do not know a better way + // Even with this IE will not work due to tainted canvas + // see https://github.com/kangax/fabric.js/issues/1957 + // http://stackoverflow.com/questions/18112047/canvas-todataurl-working-in-all-browsers-except-ie10 + // Leave here just in case the CORS/tainted IE issue gets resolved + if(Lib.isIE()) { + // replace double quote with single quote + s = s.replace(/"/gi, '\''); + // url in svg are single quoted + // since we changed double to single + // we'll need to change these to double-quoted + s = s.replace(/(\('#)([^']*)('\))/gi, '(\"#$2\")'); + // font names with spaces will be escaped single-quoted + // we'll need to change these to double-quoted + s = s.replace(/(\\')/gi, '\"'); + } + + return s; +}; + +},{"../components/color":51,"../components/drawing":72,"../constants/xmlns_namespaces":150,"../lib":169,"d3":16}],267:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); + +// arrayOk attributes, merge them into calcdata array +module.exports = function arraysToCalcdata(cd, trace) { + for(var i = 0; i < cd.length; i++) cd[i].i = i; + + Lib.mergeArray(trace.text, cd, 'tx'); + Lib.mergeArray(trace.hovertext, cd, 'htx'); + + var marker = trace.marker; + if(marker) { + Lib.mergeArray(marker.opacity, cd, 'mo', true); + Lib.mergeArray(marker.color, cd, 'mc'); + + var markerLine = marker.line; + if(markerLine) { + Lib.mergeArray(markerLine.color, cd, 'mlc'); + Lib.mergeArrayCastPositive(markerLine.width, cd, 'mlw'); + } + } +}; + +},{"../../lib":169}],268:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var scatterAttrs = _dereq_('../scatter/attributes'); +var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs; +var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs; +var colorScaleAttrs = _dereq_('../../components/colorscale/attributes'); +var fontAttrs = _dereq_('../../plots/font_attributes'); +var constants = _dereq_('./constants'); + +var extendFlat = _dereq_('../../lib/extend').extendFlat; + +var textFontAttrs = fontAttrs({ + editType: 'calc', + arrayOk: true, + colorEditType: 'style', + +}); + +var scatterMarkerAttrs = scatterAttrs.marker; +var scatterMarkerLineAttrs = scatterMarkerAttrs.line; + +var markerLineWidth = extendFlat({}, + scatterMarkerLineAttrs.width, { dflt: 0 }); + +var markerLine = extendFlat({ + width: markerLineWidth, + editType: 'calc' +}, colorScaleAttrs('marker.line')); + +var marker = extendFlat({ + line: markerLine, + editType: 'calc' +}, colorScaleAttrs('marker'), { + opacity: { + valType: 'number', + arrayOk: true, + dflt: 1, + min: 0, + max: 1, + + editType: 'style', + + } +}); + +module.exports = { + x: scatterAttrs.x, + x0: scatterAttrs.x0, + dx: scatterAttrs.dx, + y: scatterAttrs.y, + y0: scatterAttrs.y0, + dy: scatterAttrs.dy, + + text: scatterAttrs.text, + texttemplate: texttemplateAttrs({editType: 'plot'}, { + keys: constants.eventDataKeys + }), + hovertext: scatterAttrs.hovertext, + hovertemplate: hovertemplateAttrs({}, { + keys: constants.eventDataKeys + }), + + textposition: { + valType: 'enumerated', + + values: ['inside', 'outside', 'auto', 'none'], + dflt: 'none', + arrayOk: true, + editType: 'calc', + + }, + + insidetextanchor: { + valType: 'enumerated', + values: ['end', 'middle', 'start'], + dflt: 'end', + + editType: 'plot', + + }, + + textangle: { + valType: 'angle', + dflt: 'auto', + + editType: 'plot', + + }, + + textfont: extendFlat({}, textFontAttrs, { + + }), + + insidetextfont: extendFlat({}, textFontAttrs, { + + }), + + outsidetextfont: extendFlat({}, textFontAttrs, { + + }), + + constraintext: { + valType: 'enumerated', + values: ['inside', 'outside', 'both', 'none'], + + dflt: 'both', + editType: 'calc', + + }, + + cliponaxis: extendFlat({}, scatterAttrs.cliponaxis, { + + }), + + orientation: { + valType: 'enumerated', + + values: ['v', 'h'], + editType: 'calc+clearAxisTypes', + + }, + + base: { + valType: 'any', + dflt: null, + arrayOk: true, + + editType: 'calc', + + }, + + offset: { + valType: 'number', + dflt: null, + arrayOk: true, + + editType: 'calc', + + }, + + width: { + valType: 'number', + dflt: null, + min: 0, + arrayOk: true, + + editType: 'calc', + + }, + + marker: marker, + + offsetgroup: { + valType: 'string', + + dflt: '', + editType: 'calc', + + }, + alignmentgroup: { + valType: 'string', + + dflt: '', + editType: 'calc', + + }, + + selected: { + marker: { + opacity: scatterAttrs.selected.marker.opacity, + color: scatterAttrs.selected.marker.color, + editType: 'style' + }, + textfont: scatterAttrs.selected.textfont, + editType: 'style' + }, + unselected: { + marker: { + opacity: scatterAttrs.unselected.marker.opacity, + color: scatterAttrs.unselected.marker.color, + editType: 'style' + }, + textfont: scatterAttrs.unselected.textfont, + editType: 'style' + }, + + r: scatterAttrs.r, + t: scatterAttrs.t, + + _deprecated: { + bardir: { + valType: 'enumerated', + + editType: 'calc', + values: ['v', 'h'], + + } + } +}; + +},{"../../components/colorscale/attributes":58,"../../lib/extend":164,"../../plots/font_attributes":239,"../../plots/template_attributes":253,"../scatter/attributes":376,"./constants":270}],269:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Axes = _dereq_('../../plots/cartesian/axes'); +var hasColorscale = _dereq_('../../components/colorscale/helpers').hasColorscale; +var colorscaleCalc = _dereq_('../../components/colorscale/calc'); +var arraysToCalcdata = _dereq_('./arrays_to_calcdata'); +var calcSelection = _dereq_('../scatter/calc_selection'); + +module.exports = function calc(gd, trace) { + var xa = Axes.getFromId(gd, trace.xaxis || 'x'); + var ya = Axes.getFromId(gd, trace.yaxis || 'y'); + var size, pos; + + if(trace.orientation === 'h') { + size = xa.makeCalcdata(trace, 'x'); + pos = ya.makeCalcdata(trace, 'y'); + } else { + size = ya.makeCalcdata(trace, 'y'); + pos = xa.makeCalcdata(trace, 'x'); + } + + // create the "calculated data" to plot + var serieslen = Math.min(pos.length, size.length); + var cd = new Array(serieslen); + + // set position and size + for(var i = 0; i < serieslen; i++) { + cd[i] = { p: pos[i], s: size[i] }; + + if(trace.ids) { + cd[i].id = String(trace.ids[i]); + } + } + + // auto-z and autocolorscale if applicable + if(hasColorscale(trace, 'marker')) { + colorscaleCalc(gd, trace, { + vals: trace.marker.color, + containerStr: 'marker', + cLetter: 'c' + }); + } + if(hasColorscale(trace, 'marker.line')) { + colorscaleCalc(gd, trace, { + vals: trace.marker.line.color, + containerStr: 'marker.line', + cLetter: 'c' + }); + } + + arraysToCalcdata(cd, trace); + calcSelection(cd, trace); + + return cd; +}; + +},{"../../components/colorscale/calc":59,"../../components/colorscale/helpers":62,"../../plots/cartesian/axes":213,"../scatter/calc_selection":378,"./arrays_to_calcdata":267}],270:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +module.exports = { + TEXTPAD: 3, // padding in pixels around text + eventDataKeys: [] +}; + +},{}],271:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); +var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray; +var BADNUM = _dereq_('../../constants/numerical').BADNUM; + +var Registry = _dereq_('../../registry'); +var Axes = _dereq_('../../plots/cartesian/axes'); +var getAxisGroup = _dereq_('../../plots/cartesian/axis_ids').getAxisGroup; +var Sieve = _dereq_('./sieve.js'); + +/* + * Bar chart stacking/grouping positioning and autoscaling calculations + * for each direction separately calculate the ranges and positions + * note that this handles histograms too + * now doing this one subplot at a time + */ + +function crossTraceCalc(gd, plotinfo) { + var xa = plotinfo.xaxis; + var ya = plotinfo.yaxis; + + var fullLayout = gd._fullLayout; + var fullTraces = gd._fullData; + var calcTraces = gd.calcdata; + var calcTracesHorz = []; + var calcTracesVert = []; + + for(var i = 0; i < fullTraces.length; i++) { + var fullTrace = fullTraces[i]; + if( + fullTrace.visible === true && + Registry.traceIs(fullTrace, 'bar') && + fullTrace.xaxis === xa._id && + fullTrace.yaxis === ya._id + ) { + if(fullTrace.orientation === 'h') { + calcTracesHorz.push(calcTraces[i]); + } else { + calcTracesVert.push(calcTraces[i]); + } + } + } + + var opts = { + mode: fullLayout.barmode, + norm: fullLayout.barnorm, + gap: fullLayout.bargap, + groupgap: fullLayout.bargroupgap + }; + + setGroupPositions(gd, xa, ya, calcTracesVert, opts); + setGroupPositions(gd, ya, xa, calcTracesHorz, opts); +} + +function setGroupPositions(gd, pa, sa, calcTraces, opts) { + if(!calcTraces.length) return; + + var excluded; + var included; + var i, calcTrace, fullTrace; + + initBase(sa, calcTraces); + + switch(opts.mode) { + case 'overlay': + setGroupPositionsInOverlayMode(pa, sa, calcTraces, opts); + break; + + case 'group': + // exclude from the group those traces for which the user set an offset + excluded = []; + included = []; + for(i = 0; i < calcTraces.length; i++) { + calcTrace = calcTraces[i]; + fullTrace = calcTrace[0].trace; + + if(fullTrace.offset === undefined) included.push(calcTrace); + else excluded.push(calcTrace); + } + + if(included.length) { + setGroupPositionsInGroupMode(gd, pa, sa, included, opts); + } + if(excluded.length) { + setGroupPositionsInOverlayMode(pa, sa, excluded, opts); + } + break; + + case 'stack': + case 'relative': + // exclude from the stack those traces for which the user set a base + excluded = []; + included = []; + for(i = 0; i < calcTraces.length; i++) { + calcTrace = calcTraces[i]; + fullTrace = calcTrace[0].trace; + + if(fullTrace.base === undefined) included.push(calcTrace); + else excluded.push(calcTrace); + } + + if(included.length) { + setGroupPositionsInStackOrRelativeMode(gd, pa, sa, included, opts); + } + if(excluded.length) { + setGroupPositionsInOverlayMode(pa, sa, excluded, opts); + } + break; + } + + collectExtents(calcTraces, pa); +} + +function initBase(sa, calcTraces) { + var i, j; + + for(i = 0; i < calcTraces.length; i++) { + var cd = calcTraces[i]; + var trace = cd[0].trace; + var base = (trace.type === 'funnel') ? trace._base : trace.base; + var b; + + // not sure if it really makes sense to have dates for bar size data... + // ideally if we want to make gantt charts or something we'd treat + // the actual size (trace.x or y) as time delta but base as absolute + // time. But included here for completeness. + var scalendar = trace.orientation === 'h' ? trace.xcalendar : trace.ycalendar; + + // 'base' on categorical axes makes no sense + var d2c = sa.type === 'category' || sa.type === 'multicategory' ? + function() { return null; } : + sa.d2c; + + if(isArrayOrTypedArray(base)) { + for(j = 0; j < Math.min(base.length, cd.length); j++) { + b = d2c(base[j], 0, scalendar); + if(isNumeric(b)) { + cd[j].b = +b; + cd[j].hasB = 1; + } else cd[j].b = 0; + } + for(; j < cd.length; j++) { + cd[j].b = 0; + } + } else { + b = d2c(base, 0, scalendar); + var hasBase = isNumeric(b); + b = hasBase ? b : 0; + for(j = 0; j < cd.length; j++) { + cd[j].b = b; + if(hasBase) cd[j].hasB = 1; + } + } + } +} + +function setGroupPositionsInOverlayMode(pa, sa, calcTraces, opts) { + // update position axis and set bar offsets and widths + for(var i = 0; i < calcTraces.length; i++) { + var calcTrace = calcTraces[i]; + + var sieve = new Sieve([calcTrace], { + sepNegVal: false, + overlapNoMerge: !opts.norm + }); + + // set bar offsets and widths, and update position axis + setOffsetAndWidth(pa, sieve, opts); + + // set bar bases and sizes, and update size axis + // + // (note that `setGroupPositionsInOverlayMode` handles the case barnorm + // is defined, because this function is also invoked for traces that + // can't be grouped or stacked) + if(opts.norm) { + sieveBars(sieve); + normalizeBars(sa, sieve, opts); + } else { + setBaseAndTop(sa, sieve); + } + } +} + +function setGroupPositionsInGroupMode(gd, pa, sa, calcTraces, opts) { + var sieve = new Sieve(calcTraces, { + sepNegVal: false, + overlapNoMerge: !opts.norm + }); + + // set bar offsets and widths, and update position axis + setOffsetAndWidthInGroupMode(gd, pa, sieve, opts); + + // relative-stack bars within the same trace that would otherwise + // be hidden + unhideBarsWithinTrace(sieve); + + // set bar bases and sizes, and update size axis + if(opts.norm) { + sieveBars(sieve); + normalizeBars(sa, sieve, opts); + } else { + setBaseAndTop(sa, sieve); + } +} + +function setGroupPositionsInStackOrRelativeMode(gd, pa, sa, calcTraces, opts) { + var sieve = new Sieve(calcTraces, { + sepNegVal: opts.mode === 'relative', + overlapNoMerge: !(opts.norm || opts.mode === 'stack' || opts.mode === 'relative') + }); + + // set bar offsets and widths, and update position axis + setOffsetAndWidth(pa, sieve, opts); + + // set bar bases and sizes, and update size axis + stackBars(sa, sieve, opts); + + // flag the outmost bar (for text display purposes) + for(var i = 0; i < calcTraces.length; i++) { + var calcTrace = calcTraces[i]; + + for(var j = 0; j < calcTrace.length; j++) { + var bar = calcTrace[j]; + + if(bar.s !== BADNUM) { + var isOutmostBar = ((bar.b + bar.s) === sieve.get(bar.p, bar.s)); + if(isOutmostBar) bar._outmost = true; + } + } + } + + // Note that marking the outmost bars has to be done + // before `normalizeBars` changes `bar.b` and `bar.s`. + if(opts.norm) normalizeBars(sa, sieve, opts); +} + +function setOffsetAndWidth(pa, sieve, opts) { + var minDiff = sieve.minDiff; + var calcTraces = sieve.traces; + + // set bar offsets and widths + var barGroupWidth = minDiff * (1 - opts.gap); + var barWidthPlusGap = barGroupWidth; + var barWidth = barWidthPlusGap * (1 - (opts.groupgap || 0)); + + // computer bar group center and bar offset + var offsetFromCenter = -barWidth / 2; + + for(var i = 0; i < calcTraces.length; i++) { + var calcTrace = calcTraces[i]; + var t = calcTrace[0].t; + + // store bar width and offset for this trace + t.barwidth = barWidth; + t.poffset = offsetFromCenter; + t.bargroupwidth = barGroupWidth; + t.bardelta = minDiff; + } + + // stack bars that only differ by rounding + sieve.binWidth = calcTraces[0][0].t.barwidth / 100; + + // if defined, apply trace offset and width + applyAttributes(sieve); + + // store the bar center in each calcdata item + setBarCenterAndWidth(pa, sieve); + + // update position axes + updatePositionAxis(pa, sieve); +} + +function setOffsetAndWidthInGroupMode(gd, pa, sieve, opts) { + var fullLayout = gd._fullLayout; + var positions = sieve.positions; + var distinctPositions = sieve.distinctPositions; + var minDiff = sieve.minDiff; + var calcTraces = sieve.traces; + var nTraces = calcTraces.length; + + // if there aren't any overlapping positions, + // let them have full width even if mode is group + var overlap = (positions.length !== distinctPositions.length); + var barGroupWidth = minDiff * (1 - opts.gap); + + var groupId = getAxisGroup(fullLayout, pa._id) + calcTraces[0][0].trace.orientation; + var alignmentGroups = fullLayout._alignmentOpts[groupId] || {}; + + for(var i = 0; i < nTraces; i++) { + var calcTrace = calcTraces[i]; + var trace = calcTrace[0].trace; + + var alignmentGroupOpts = alignmentGroups[trace.alignmentgroup] || {}; + var nOffsetGroups = Object.keys(alignmentGroupOpts.offsetGroups || {}).length; + + var barWidthPlusGap; + if(nOffsetGroups) { + barWidthPlusGap = barGroupWidth / nOffsetGroups; + } else { + barWidthPlusGap = overlap ? barGroupWidth / nTraces : barGroupWidth; + } + + var barWidth = barWidthPlusGap * (1 - (opts.groupgap || 0)); + + var offsetFromCenter; + if(nOffsetGroups) { + offsetFromCenter = ((2 * trace._offsetIndex + 1 - nOffsetGroups) * barWidthPlusGap - barWidth) / 2; + } else { + offsetFromCenter = overlap ? + ((2 * i + 1 - nTraces) * barWidthPlusGap - barWidth) / 2 : + -barWidth / 2; + } + + var t = calcTrace[0].t; + t.barwidth = barWidth; + t.poffset = offsetFromCenter; + t.bargroupwidth = barGroupWidth; + t.bardelta = minDiff; + } + + // stack bars that only differ by rounding + sieve.binWidth = calcTraces[0][0].t.barwidth / 100; + + // if defined, apply trace width + applyAttributes(sieve); + + // store the bar center in each calcdata item + setBarCenterAndWidth(pa, sieve); + + // update position axes + updatePositionAxis(pa, sieve, overlap); +} + +function applyAttributes(sieve) { + var calcTraces = sieve.traces; + var i, j; + + for(i = 0; i < calcTraces.length; i++) { + var calcTrace = calcTraces[i]; + var calcTrace0 = calcTrace[0]; + var fullTrace = calcTrace0.trace; + var t = calcTrace0.t; + var offset = fullTrace._offset || fullTrace.offset; + var initialPoffset = t.poffset; + var newPoffset; + + if(isArrayOrTypedArray(offset)) { + // if offset is an array, then clone it into t.poffset. + newPoffset = Array.prototype.slice.call(offset, 0, calcTrace.length); + + // guard against non-numeric items + for(j = 0; j < newPoffset.length; j++) { + if(!isNumeric(newPoffset[j])) { + newPoffset[j] = initialPoffset; + } + } + + // if the length of the array is too short, + // then extend it with the initial value of t.poffset + for(j = newPoffset.length; j < calcTrace.length; j++) { + newPoffset.push(initialPoffset); + } + + t.poffset = newPoffset; + } else if(offset !== undefined) { + t.poffset = offset; + } + + var width = fullTrace._width || fullTrace.width; + var initialBarwidth = t.barwidth; + + if(isArrayOrTypedArray(width)) { + // if width is an array, then clone it into t.barwidth. + var newBarwidth = Array.prototype.slice.call(width, 0, calcTrace.length); + + // guard against non-numeric items + for(j = 0; j < newBarwidth.length; j++) { + if(!isNumeric(newBarwidth[j])) newBarwidth[j] = initialBarwidth; + } + + // if the length of the array is too short, + // then extend it with the initial value of t.barwidth + for(j = newBarwidth.length; j < calcTrace.length; j++) { + newBarwidth.push(initialBarwidth); + } + + t.barwidth = newBarwidth; + + // if user didn't set offset, + // then correct t.poffset to ensure bars remain centered + if(offset === undefined) { + newPoffset = []; + for(j = 0; j < calcTrace.length; j++) { + newPoffset.push( + initialPoffset + (initialBarwidth - newBarwidth[j]) / 2 + ); + } + t.poffset = newPoffset; + } + } else if(width !== undefined) { + t.barwidth = width; + + // if user didn't set offset, + // then correct t.poffset to ensure bars remain centered + if(offset === undefined) { + t.poffset = initialPoffset + (initialBarwidth - width) / 2; + } + } + } +} + +function setBarCenterAndWidth(pa, sieve) { + var calcTraces = sieve.traces; + var pLetter = getAxisLetter(pa); + + for(var i = 0; i < calcTraces.length; i++) { + var calcTrace = calcTraces[i]; + var t = calcTrace[0].t; + var poffset = t.poffset; + var poffsetIsArray = Array.isArray(poffset); + var barwidth = t.barwidth; + var barwidthIsArray = Array.isArray(barwidth); + + for(var j = 0; j < calcTrace.length; j++) { + var calcBar = calcTrace[j]; + + // store the actual bar width and position, for use by hover + var width = calcBar.w = barwidthIsArray ? barwidth[j] : barwidth; + calcBar[pLetter] = calcBar.p + (poffsetIsArray ? poffset[j] : poffset) + width / 2; + } + } +} + +function updatePositionAxis(pa, sieve, allowMinDtick) { + var calcTraces = sieve.traces; + var minDiff = sieve.minDiff; + var vpad = minDiff / 2; + + Axes.minDtick(pa, sieve.minDiff, sieve.distinctPositions[0], allowMinDtick); + + for(var i = 0; i < calcTraces.length; i++) { + var calcTrace = calcTraces[i]; + var calcTrace0 = calcTrace[0]; + var fullTrace = calcTrace0.trace; + var pts = []; + var bar, l, r, j; + + for(j = 0; j < calcTrace.length; j++) { + bar = calcTrace[j]; + l = bar.p - vpad; + r = bar.p + vpad; + pts.push(l, r); + } + + if(fullTrace.width || fullTrace.offset) { + var t = calcTrace0.t; + var poffset = t.poffset; + var barwidth = t.barwidth; + var poffsetIsArray = Array.isArray(poffset); + var barwidthIsArray = Array.isArray(barwidth); + + for(j = 0; j < calcTrace.length; j++) { + bar = calcTrace[j]; + var calcBarOffset = poffsetIsArray ? poffset[j] : poffset; + var calcBarWidth = barwidthIsArray ? barwidth[j] : barwidth; + l = bar.p + calcBarOffset; + r = l + calcBarWidth; + pts.push(l, r); + } + } + + fullTrace._extremes[pa._id] = Axes.findExtremes(pa, pts, {padded: false}); + } +} + +// store these bar bases and tops in calcdata +// and make sure the size axis includes zero, +// along with the bases and tops of each bar. +function setBaseAndTop(sa, sieve) { + var calcTraces = sieve.traces; + var sLetter = getAxisLetter(sa); + + for(var i = 0; i < calcTraces.length; i++) { + var calcTrace = calcTraces[i]; + var fullTrace = calcTrace[0].trace; + var pts = []; + var allBaseAboveZero = true; + + for(var j = 0; j < calcTrace.length; j++) { + var bar = calcTrace[j]; + var base = bar.b; + var top = base + bar.s; + + bar[sLetter] = top; + pts.push(top); + if(bar.hasB) pts.push(base); + + if(!bar.hasB || !(bar.b > 0 && bar.s > 0)) { + allBaseAboveZero = false; + } + } + + fullTrace._extremes[sa._id] = Axes.findExtremes(sa, pts, { + tozero: !allBaseAboveZero, + padded: true + }); + } +} + +function stackBars(sa, sieve, opts) { + var sLetter = getAxisLetter(sa); + var calcTraces = sieve.traces; + var calcTrace; + var fullTrace; + var isFunnel; + var i, j; + var bar; + + for(i = 0; i < calcTraces.length; i++) { + calcTrace = calcTraces[i]; + fullTrace = calcTrace[0].trace; + + if(fullTrace.type === 'funnel') { + for(j = 0; j < calcTrace.length; j++) { + bar = calcTrace[j]; + + if(bar.s !== BADNUM) { + // create base of funnels + sieve.put(bar.p, -0.5 * bar.s); + } + } + } + } + + for(i = 0; i < calcTraces.length; i++) { + calcTrace = calcTraces[i]; + fullTrace = calcTrace[0].trace; + + isFunnel = (fullTrace.type === 'funnel'); + + var pts = []; + + for(j = 0; j < calcTrace.length; j++) { + bar = calcTrace[j]; + + if(bar.s !== BADNUM) { + // stack current bar and get previous sum + var value; + if(isFunnel) { + value = bar.s; + } else { + value = bar.s + bar.b; + } + + var base = sieve.put(bar.p, value); + + var top = base + value; + + // store the bar base and top in each calcdata item + bar.b = base; + bar[sLetter] = top; + + if(!opts.norm) { + pts.push(top); + if(bar.hasB) { + pts.push(base); + } + } + } + } + + // if barnorm is set, let normalizeBars update the axis range + if(!opts.norm) { + fullTrace._extremes[sa._id] = Axes.findExtremes(sa, pts, { + // N.B. we don't stack base with 'base', + // so set tozero:true always! + tozero: true, + padded: true + }); + } + } +} + +function sieveBars(sieve) { + var calcTraces = sieve.traces; + + for(var i = 0; i < calcTraces.length; i++) { + var calcTrace = calcTraces[i]; + + for(var j = 0; j < calcTrace.length; j++) { + var bar = calcTrace[j]; + + if(bar.s !== BADNUM) { + sieve.put(bar.p, bar.b + bar.s); + } + } + } +} + +function unhideBarsWithinTrace(sieve) { + var calcTraces = sieve.traces; + + for(var i = 0; i < calcTraces.length; i++) { + var calcTrace = calcTraces[i]; + var fullTrace = calcTrace[0].trace; + + if(fullTrace.base === undefined) { + var inTraceSieve = new Sieve([calcTrace], { + sepNegVal: true, + overlapNoMerge: true + }); + + for(var j = 0; j < calcTrace.length; j++) { + var bar = calcTrace[j]; + + if(bar.p !== BADNUM) { + // stack current bar and get previous sum + var base = inTraceSieve.put(bar.p, bar.b + bar.s); + + // if previous sum if non-zero, this means: + // multiple bars have same starting point are potentially hidden, + // shift them vertically so that all bars are visible by default + if(base) bar.b = base; + } + } + } + } +} + +// Note: +// +// normalizeBars requires that either sieveBars or stackBars has been +// previously invoked. +function normalizeBars(sa, sieve, opts) { + var calcTraces = sieve.traces; + var sLetter = getAxisLetter(sa); + var sTop = opts.norm === 'fraction' ? 1 : 100; + var sTiny = sTop / 1e9; // in case of rounding error in sum + var sMin = sa.l2c(sa.c2l(0)); + var sMax = opts.mode === 'stack' ? sTop : sMin; + + function needsPadding(v) { + return ( + isNumeric(sa.c2l(v)) && + ((v < sMin - sTiny) || (v > sMax + sTiny) || !isNumeric(sMin)) + ); + } + + for(var i = 0; i < calcTraces.length; i++) { + var calcTrace = calcTraces[i]; + var fullTrace = calcTrace[0].trace; + var pts = []; + var allBaseAboveZero = true; + var padded = false; + + for(var j = 0; j < calcTrace.length; j++) { + var bar = calcTrace[j]; + + if(bar.s !== BADNUM) { + var scale = Math.abs(sTop / sieve.get(bar.p, bar.s)); + bar.b *= scale; + bar.s *= scale; + + var base = bar.b; + var top = base + bar.s; + + bar[sLetter] = top; + pts.push(top); + padded = padded || needsPadding(top); + + if(bar.hasB) { + pts.push(base); + padded = padded || needsPadding(base); + } + + if(!bar.hasB || !(bar.b > 0 && bar.s > 0)) { + allBaseAboveZero = false; + } + } + } + + fullTrace._extremes[sa._id] = Axes.findExtremes(sa, pts, { + tozero: !allBaseAboveZero, + padded: padded + }); + } +} + +// find the full position span of bars at each position +// for use by hover, to ensure labels move in if bars are +// narrower than the space they're in. +// run once per trace group (subplot & direction) and +// the same mapping is attached to all calcdata traces +function collectExtents(calcTraces, pa) { + var pLetter = getAxisLetter(pa); + var extents = {}; + var i, j, cd; + + var pMin = Infinity; + var pMax = -Infinity; + + for(i = 0; i < calcTraces.length; i++) { + cd = calcTraces[i]; + for(j = 0; j < cd.length; j++) { + var p = cd[j].p; + if(isNumeric(p)) { + pMin = Math.min(pMin, p); + pMax = Math.max(pMax, p); + } + } + } + + // this is just for positioning of hover labels, and nobody will care if + // the label is 1px too far out; so round positions to 1/10K in case + // position values don't exactly match from trace to trace + var roundFactor = 10000 / (pMax - pMin); + var round = extents.round = function(p) { + return String(Math.round(roundFactor * (p - pMin))); + }; + + for(i = 0; i < calcTraces.length; i++) { + cd = calcTraces[i]; + cd[0].t.extents = extents; + + var poffset = cd[0].t.poffset; + var poffsetIsArray = Array.isArray(poffset); + + for(j = 0; j < cd.length; j++) { + var di = cd[j]; + var p0 = di[pLetter] - di.w / 2; + + if(isNumeric(p0)) { + var p1 = di[pLetter] + di.w / 2; + var pVal = round(di.p); + if(extents[pVal]) { + extents[pVal] = [Math.min(p0, extents[pVal][0]), Math.max(p1, extents[pVal][1])]; + } else { + extents[pVal] = [p0, p1]; + } + } + + di.p0 = di.p + (poffsetIsArray ? poffset[j] : poffset); + di.p1 = di.p0 + di.w; + di.s0 = di.b; + di.s1 = di.s0 + di.s; + } + } +} + +function getAxisLetter(ax) { + return ax._id.charAt(0); +} + +module.exports = { + crossTraceCalc: crossTraceCalc, + setGroupPositions: setGroupPositions +}; + +},{"../../constants/numerical":149,"../../lib":169,"../../plots/cartesian/axes":213,"../../plots/cartesian/axis_ids":216,"../../registry":258,"./sieve.js":280,"fast-isnumeric":18}],272:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var Color = _dereq_('../../components/color'); +var Registry = _dereq_('../../registry'); + +var handleXYDefaults = _dereq_('../scatter/xy_defaults'); +var handleStyleDefaults = _dereq_('./style_defaults'); +var getAxisGroup = _dereq_('../../plots/cartesian/axis_ids').getAxisGroup; +var attributes = _dereq_('./attributes'); + +var coerceFont = Lib.coerceFont; + +function supplyDefaults(traceIn, traceOut, defaultColor, layout) { + function coerce(attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); + } + + var len = handleXYDefaults(traceIn, traceOut, layout, coerce); + if(!len) { + traceOut.visible = false; + return; + } + + coerce('orientation', (traceOut.x && !traceOut.y) ? 'h' : 'v'); + coerce('base'); + coerce('offset'); + coerce('width'); + + coerce('text'); + coerce('hovertext'); + coerce('hovertemplate'); + + var textposition = coerce('textposition'); + handleText(traceIn, traceOut, layout, coerce, textposition, { + moduleHasSelected: true, + moduleHasUnselected: true, + moduleHasConstrain: true, + moduleHasCliponaxis: true, + moduleHasTextangle: true, + moduleHasInsideanchor: true + }); + + handleStyleDefaults(traceIn, traceOut, coerce, defaultColor, layout); + + var lineColor = (traceOut.marker.line || {}).color; + + // override defaultColor for error bars with defaultLine + var errorBarsSupplyDefaults = Registry.getComponentMethod('errorbars', 'supplyDefaults'); + errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, {axis: 'y'}); + errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, {axis: 'x', inherit: 'y'}); + + Lib.coerceSelectionMarkerOpacity(traceOut, coerce); +} + +function handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce) { + var orientation = traceOut.orientation; + // N.B. grouping is done across all trace types that support it + var posAxId = traceOut[{v: 'x', h: 'y'}[orientation] + 'axis']; + var groupId = getAxisGroup(fullLayout, posAxId) + orientation; + + var alignmentOpts = fullLayout._alignmentOpts || {}; + var alignmentgroup = coerce('alignmentgroup'); + + var alignmentGroups = alignmentOpts[groupId]; + if(!alignmentGroups) alignmentGroups = alignmentOpts[groupId] = {}; + + var alignmentGroupOpts = alignmentGroups[alignmentgroup]; + + if(alignmentGroupOpts) { + alignmentGroupOpts.traces.push(traceOut); + } else { + alignmentGroupOpts = alignmentGroups[alignmentgroup] = { + traces: [traceOut], + alignmentIndex: Object.keys(alignmentGroups).length, + offsetGroups: {} + }; + } + + var offsetgroup = coerce('offsetgroup'); + var offsetGroups = alignmentGroupOpts.offsetGroups; + var offsetGroupOpts = offsetGroups[offsetgroup]; + + if(offsetgroup) { + if(!offsetGroupOpts) { + offsetGroupOpts = offsetGroups[offsetgroup] = { + offsetIndex: Object.keys(offsetGroups).length + }; + } + + traceOut._offsetIndex = offsetGroupOpts.offsetIndex; + } +} + +function crossTraceDefaults(fullData, fullLayout) { + var traceIn, traceOut; + + function coerce(attr) { + return Lib.coerce(traceOut._input, traceOut, attributes, attr); + } + + if(fullLayout.barmode === 'group') { + for(var i = 0; i < fullData.length; i++) { + traceOut = fullData[i]; + + if(traceOut.type === 'bar') { + traceIn = traceOut._input; + handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce); + } + } + } +} + +function handleText(traceIn, traceOut, layout, coerce, textposition, opts) { + opts = opts || {}; + var moduleHasSelected = !(opts.moduleHasSelected === false); + var moduleHasUnselected = !(opts.moduleHasUnselected === false); + var moduleHasConstrain = !(opts.moduleHasConstrain === false); + var moduleHasCliponaxis = !(opts.moduleHasCliponaxis === false); + var moduleHasTextangle = !(opts.moduleHasTextangle === false); + var moduleHasInsideanchor = !(opts.moduleHasInsideanchor === false); + + var hasBoth = Array.isArray(textposition) || textposition === 'auto'; + var hasInside = hasBoth || textposition === 'inside'; + var hasOutside = hasBoth || textposition === 'outside'; + + if(hasInside || hasOutside) { + var dfltFont = coerceFont(coerce, 'textfont', layout.font); + + // Note that coercing `insidetextfont` is always needed – + // even if `textposition` is `outside` for each trace – since + // an outside label can become an inside one, for example because + // of a bar being stacked on top of it. + var insideTextFontDefault = Lib.extendFlat({}, dfltFont); + var isTraceTextfontColorSet = traceIn.textfont && traceIn.textfont.color; + var isColorInheritedFromLayoutFont = !isTraceTextfontColorSet; + if(isColorInheritedFromLayoutFont) { + delete insideTextFontDefault.color; + } + coerceFont(coerce, 'insidetextfont', insideTextFontDefault); + + if(hasOutside) coerceFont(coerce, 'outsidetextfont', dfltFont); + + + if(moduleHasSelected) coerce('selected.textfont.color'); + if(moduleHasUnselected) coerce('unselected.textfont.color'); + if(moduleHasConstrain) coerce('constraintext'); + if(moduleHasCliponaxis) coerce('cliponaxis'); + if(moduleHasTextangle) coerce('textangle'); + + coerce('texttemplate'); + } + + if(hasInside) { + if(moduleHasInsideanchor) coerce('insidetextanchor'); + } +} + +module.exports = { + supplyDefaults: supplyDefaults, + crossTraceDefaults: crossTraceDefaults, + handleGroupingDefaults: handleGroupingDefaults, + handleText: handleText +}; + +},{"../../components/color":51,"../../lib":169,"../../plots/cartesian/axis_ids":216,"../../registry":258,"../scatter/xy_defaults":401,"./attributes":268,"./style_defaults":282}],273:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); +var tinycolor = _dereq_('tinycolor2'); +var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray; + +exports.coerceString = function(attributeDefinition, value, defaultValue) { + if(typeof value === 'string') { + if(value || !attributeDefinition.noBlank) return value; + } else if(typeof value === 'number' || value === true) { + if(!attributeDefinition.strict) return String(value); + } + + return (defaultValue !== undefined) ? + defaultValue : + attributeDefinition.dflt; +}; + +exports.coerceNumber = function(attributeDefinition, value, defaultValue) { + if(isNumeric(value)) { + value = +value; + + var min = attributeDefinition.min; + var max = attributeDefinition.max; + var isOutOfBounds = (min !== undefined && value < min) || + (max !== undefined && value > max); + + if(!isOutOfBounds) return value; + } + + return (defaultValue !== undefined) ? + defaultValue : + attributeDefinition.dflt; +}; + +exports.coerceColor = function(attributeDefinition, value, defaultValue) { + if(tinycolor(value).isValid()) return value; + + return (defaultValue !== undefined) ? + defaultValue : + attributeDefinition.dflt; +}; + +exports.coerceEnumerated = function(attributeDefinition, value, defaultValue) { + if(attributeDefinition.coerceNumber) value = +value; + + if(attributeDefinition.values.indexOf(value) !== -1) return value; + + return (defaultValue !== undefined) ? + defaultValue : + attributeDefinition.dflt; +}; + +exports.getValue = function(arrayOrScalar, index) { + var value; + if(!Array.isArray(arrayOrScalar)) value = arrayOrScalar; + else if(index < arrayOrScalar.length) value = arrayOrScalar[index]; + return value; +}; + +exports.getLineWidth = function(trace, di) { + var w = + (0 < di.mlw) ? di.mlw : + !isArrayOrTypedArray(trace.marker.line.width) ? trace.marker.line.width : + 0; + + return w; +}; + +},{"../../lib":169,"fast-isnumeric":18,"tinycolor2":34}],274:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Fx = _dereq_('../../components/fx'); +var Registry = _dereq_('../../registry'); +var Color = _dereq_('../../components/color'); + +var fillText = _dereq_('../../lib').fillText; +var getLineWidth = _dereq_('./helpers').getLineWidth; + +function hoverPoints(pointData, xval, yval, hovermode) { + var barPointData = hoverOnBars(pointData, xval, yval, hovermode); + + if(barPointData) { + var cd = barPointData.cd; + var trace = cd[0].trace; + var di = cd[barPointData.index]; + + barPointData.color = getTraceColor(trace, di); + Registry.getComponentMethod('errorbars', 'hoverInfo')(di, trace, barPointData); + + return [barPointData]; + } +} + +function hoverOnBars(pointData, xval, yval, hovermode) { + var cd = pointData.cd; + var trace = cd[0].trace; + var t = cd[0].t; + var isClosest = (hovermode === 'closest'); + var isWaterfall = (trace.type === 'waterfall'); + var maxHoverDistance = pointData.maxHoverDistance; + var maxSpikeDistance = pointData.maxSpikeDistance; + + var posVal, sizeVal, posLetter, sizeLetter, dx, dy, pRangeCalc; + + function thisBarMinPos(di) { return di[posLetter] - di.w / 2; } + function thisBarMaxPos(di) { return di[posLetter] + di.w / 2; } + + var minPos = isClosest ? + thisBarMinPos : + function(di) { + /* + * In compare mode, accept a bar if you're on it *or* its group. + * Nearly always it's the group that matters, but in case the bar + * was explicitly set wider than its group we'd better accept the + * whole bar. + * + * use `bardelta` instead of `bargroupwidth` so we accept hover + * in the gap. That way hover doesn't flash on and off as you + * mouse over the plot in compare modes. + * In 'closest' mode though the flashing seems inevitable, + * without far more complex logic + */ + return Math.min(thisBarMinPos(di), di.p - t.bardelta / 2); + }; + + var maxPos = isClosest ? + thisBarMaxPos : + function(di) { + return Math.max(thisBarMaxPos(di), di.p + t.bardelta / 2); + }; + + function _positionFn(_minPos, _maxPos) { + // add a little to the pseudo-distance for wider bars, so that like scatter, + // if you are over two overlapping bars, the narrower one wins. + return Fx.inbox(_minPos - posVal, _maxPos - posVal, + maxHoverDistance + Math.min(1, Math.abs(_maxPos - _minPos) / pRangeCalc) - 1); + } + + function positionFn(di) { + return _positionFn(minPos(di), maxPos(di)); + } + + function thisBarPositionFn(di) { + return _positionFn(thisBarMinPos(di), thisBarMaxPos(di)); + } + + function sizeFn(di) { + var v = sizeVal; + var b = di.b; + var s = di[sizeLetter]; + + if(isWaterfall) { + s += Math.abs(di.rawS || 0); + } + + // add a gradient so hovering near the end of a + // bar makes it a little closer match + return Fx.inbox(b - v, s - v, maxHoverDistance + (s - v) / (s - b) - 1); + } + + if(trace.orientation === 'h') { + posVal = yval; + sizeVal = xval; + posLetter = 'y'; + sizeLetter = 'x'; + dx = sizeFn; + dy = positionFn; + } else { + posVal = xval; + sizeVal = yval; + posLetter = 'x'; + sizeLetter = 'y'; + dy = sizeFn; + dx = positionFn; + } + + var pa = pointData[posLetter + 'a']; + var sa = pointData[sizeLetter + 'a']; + + pRangeCalc = Math.abs(pa.r2c(pa.range[1]) - pa.r2c(pa.range[0])); + + function dxy(di) { return (dx(di) + dy(di)) / 2; } + var distfn = Fx.getDistanceFunction(hovermode, dx, dy, dxy); + Fx.getClosest(cd, distfn, pointData); + + // skip the rest (for this trace) if we didn't find a close point + if(pointData.index === false) return; + + // if we get here and we're not in 'closest' mode, push min/max pos back + // onto the group - even though that means occasionally the mouse will be + // over the hover label. + if(!isClosest) { + minPos = function(di) { + return Math.min(thisBarMinPos(di), di.p - t.bargroupwidth / 2); + }; + maxPos = function(di) { + return Math.max(thisBarMaxPos(di), di.p + t.bargroupwidth / 2); + }; + } + + // the closest data point + var index = pointData.index; + var di = cd[index]; + + var size = (trace.base) ? di.b + di.s : di.s; + pointData[sizeLetter + '0'] = pointData[sizeLetter + '1'] = sa.c2p(di[sizeLetter], true); + pointData[sizeLetter + 'LabelVal'] = size; + + var extent = t.extents[t.extents.round(di.p)]; + pointData[posLetter + '0'] = pa.c2p(isClosest ? minPos(di) : extent[0], true); + pointData[posLetter + '1'] = pa.c2p(isClosest ? maxPos(di) : extent[1], true); + pointData[posLetter + 'LabelVal'] = di.p; + + // spikelines always want "closest" distance regardless of hovermode + pointData.spikeDistance = (sizeFn(di) + thisBarPositionFn(di)) / 2 + maxSpikeDistance - maxHoverDistance; + // they also want to point to the data value, regardless of where the label goes + // in case of bars shifted within groups + pointData[posLetter + 'Spike'] = pa.c2p(di.p, true); + + fillText(di, trace, pointData); + pointData.hovertemplate = trace.hovertemplate; + + return pointData; +} + +function getTraceColor(trace, di) { + var mc = di.mcc || trace.marker.color; + var mlc = di.mlcc || trace.marker.line.color; + var mlw = getLineWidth(trace, di); + + if(Color.opacity(mc)) return mc; + else if(Color.opacity(mlc) && mlw) return mlc; +} + +module.exports = { + hoverPoints: hoverPoints, + hoverOnBars: hoverOnBars, + getTraceColor: getTraceColor +}; + +},{"../../components/color":51,"../../components/fx":89,"../../lib":169,"../../registry":258,"./helpers":273}],275:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + attributes: _dereq_('./attributes'), + layoutAttributes: _dereq_('./layout_attributes'), + supplyDefaults: _dereq_('./defaults').supplyDefaults, + crossTraceDefaults: _dereq_('./defaults').crossTraceDefaults, + supplyLayoutDefaults: _dereq_('./layout_defaults'), + calc: _dereq_('./calc'), + crossTraceCalc: _dereq_('./cross_trace_calc').crossTraceCalc, + colorbar: _dereq_('../scatter/marker_colorbar'), + arraysToCalcdata: _dereq_('./arrays_to_calcdata'), + plot: _dereq_('./plot').plot, + style: _dereq_('./style').style, + styleOnSelect: _dereq_('./style').styleOnSelect, + hoverPoints: _dereq_('./hover').hoverPoints, + selectPoints: _dereq_('./select'), + + moduleType: 'trace', + name: 'bar', + basePlotModule: _dereq_('../../plots/cartesian'), + categories: ['bar-like', 'cartesian', 'svg', 'bar', 'oriented', 'errorBarsOK', 'showLegend', 'zoomScale'], + animatable: true, + meta: { + + } +}; + +},{"../../plots/cartesian":224,"../scatter/marker_colorbar":393,"./arrays_to_calcdata":267,"./attributes":268,"./calc":269,"./cross_trace_calc":271,"./defaults":272,"./hover":274,"./layout_attributes":276,"./layout_defaults":277,"./plot":278,"./select":279,"./style":281}],276:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + + +module.exports = { + barmode: { + valType: 'enumerated', + values: ['stack', 'group', 'overlay', 'relative'], + dflt: 'group', + + editType: 'calc', + + }, + barnorm: { + valType: 'enumerated', + values: ['', 'fraction', 'percent'], + dflt: '', + + editType: 'calc', + + }, + bargap: { + valType: 'number', + min: 0, + max: 1, + + editType: 'calc', + + }, + bargroupgap: { + valType: 'number', + min: 0, + max: 1, + dflt: 0, + + editType: 'calc', + + } +}; + +},{}],277:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Registry = _dereq_('../../registry'); +var Axes = _dereq_('../../plots/cartesian/axes'); +var Lib = _dereq_('../../lib'); + +var layoutAttributes = _dereq_('./layout_attributes'); + +module.exports = function(layoutIn, layoutOut, fullData) { + function coerce(attr, dflt) { + return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt); + } + + var hasBars = false; + var shouldBeGapless = false; + var gappedAnyway = false; + var usedSubplots = {}; + + var mode = coerce('barmode'); + + for(var i = 0; i < fullData.length; i++) { + var trace = fullData[i]; + if(Registry.traceIs(trace, 'bar') && trace.visible) hasBars = true; + else continue; + + // if we have at least 2 grouped bar traces on the same subplot, + // we should default to a gap anyway, even if the data is histograms + if(mode === 'group') { + var subploti = trace.xaxis + trace.yaxis; + if(usedSubplots[subploti]) gappedAnyway = true; + usedSubplots[subploti] = true; + } + + if(trace.visible && trace.type === 'histogram') { + var pa = Axes.getFromId({_fullLayout: layoutOut}, + trace[trace.orientation === 'v' ? 'xaxis' : 'yaxis']); + if(pa.type !== 'category') shouldBeGapless = true; + } + } + + if(!hasBars) { + delete layoutOut.barmode; + return; + } + + if(mode !== 'overlay') coerce('barnorm'); + + coerce('bargap', (shouldBeGapless && !gappedAnyway) ? 0 : 0.2); + coerce('bargroupgap'); +}; + +},{"../../lib":169,"../../plots/cartesian/axes":213,"../../registry":258,"./layout_attributes":276}],278:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); +var isNumeric = _dereq_('fast-isnumeric'); + +var Lib = _dereq_('../../lib'); +var svgTextUtils = _dereq_('../../lib/svg_text_utils'); + +var Color = _dereq_('../../components/color'); +var Drawing = _dereq_('../../components/drawing'); +var Registry = _dereq_('../../registry'); +var tickText = _dereq_('../../plots/cartesian/axes').tickText; + +var style = _dereq_('./style'); +var helpers = _dereq_('./helpers'); +var constants = _dereq_('./constants'); +var attributes = _dereq_('./attributes'); + +var attributeText = attributes.text; +var attributeTextPosition = attributes.textposition; + +var appendArrayPointValue = _dereq_('../../components/fx/helpers').appendArrayPointValue; + +var TEXTPAD = constants.TEXTPAD; + +function keyFunc(d) {return d.id;} +function getKeyFunc(trace) { + if(trace.ids) { + return keyFunc; + } +} + +function dirSign(a, b) { + return (a < b) ? 1 : -1; +} + +function getXY(di, xa, ya, isHorizontal) { + var s = []; + var p = []; + + var sAxis = isHorizontal ? xa : ya; + var pAxis = isHorizontal ? ya : xa; + + s[0] = sAxis.c2p(di.s0, true); + p[0] = pAxis.c2p(di.p0, true); + + s[1] = sAxis.c2p(di.s1, true); + p[1] = pAxis.c2p(di.p1, true); + + return isHorizontal ? [s, p] : [p, s]; +} + +function transition(selection, opts, makeOnCompleteCallback) { + if(hasTransition(opts)) { + var onComplete; + if(makeOnCompleteCallback) { + onComplete = makeOnCompleteCallback(); + } + return selection + .transition() + .duration(opts.duration) + .ease(opts.easing) + .each('end', function() { onComplete && onComplete(); }) + .each('interrupt', function() { onComplete && onComplete(); }); + } else { + return selection; + } +} + +function hasTransition(transitionOpts) { + return transitionOpts && transitionOpts.duration > 0; +} + +function plot(gd, plotinfo, cdModule, traceLayer, opts, makeOnCompleteCallback) { + var xa = plotinfo.xaxis; + var ya = plotinfo.yaxis; + var fullLayout = gd._fullLayout; + + if(!opts) { + opts = { + mode: fullLayout.barmode, + norm: fullLayout.barmode, + gap: fullLayout.bargap, + groupgap: fullLayout.bargroupgap + }; + } + + var bartraces = Lib.makeTraceGroups(traceLayer, cdModule, 'trace bars').each(function(cd) { + var plotGroup = d3.select(this); + var trace = cd[0].trace; + var isWaterfall = (trace.type === 'waterfall'); + var isFunnel = (trace.type === 'funnel'); + var isBar = (trace.type === 'bar'); + var shouldDisplayZeros = (isBar || isFunnel); + + var adjustPixel = 0; + if(isWaterfall && trace.connector.visible && trace.connector.mode === 'between') { + adjustPixel = trace.connector.line.width / 2; + } + + var isHorizontal = (trace.orientation === 'h'); + + var pointGroup = Lib.ensureSingle(plotGroup, 'g', 'points'); + + var keyFunc = getKeyFunc(trace); + var bars = pointGroup.selectAll('g.point').data(Lib.identity, keyFunc); + + bars.enter().append('g') + .classed('point', true); + + bars.exit().remove(); + + bars.each(function(di, i) { + var bar = d3.select(this); + + // now display the bar + // clipped xf/yf (2nd arg true): non-positive + // log values go off-screen by plotwidth + // so you see them continue if you drag the plot + var xy = getXY(di, xa, ya, isHorizontal); + + var x0 = xy[0][0]; + var x1 = xy[0][1]; + var y0 = xy[1][0]; + var y1 = xy[1][1]; + + var isBlank = ( + x0 === x1 || + y0 === y1 || + !isNumeric(x0) || + !isNumeric(x1) || + !isNumeric(y0) || + !isNumeric(y1) + ); + // display zeros if line.width > 0 + if(isBlank && shouldDisplayZeros && helpers.getLineWidth(trace, di) && (isHorizontal ? x1 - x0 === 0 : y1 - y0 === 0)) { + isBlank = false; + } + di.isBlank = isBlank; + + if(isBlank && isHorizontal) x1 = x0; + if(isBlank && !isHorizontal) y1 = y0; + + // in waterfall mode `between` we need to adjust bar end points to match the connector width + if(adjustPixel && !isBlank) { + if(isHorizontal) { + x0 -= dirSign(x0, x1) * adjustPixel; + x1 += dirSign(x0, x1) * adjustPixel; + } else { + y0 -= dirSign(y0, y1) * adjustPixel; + y1 += dirSign(y0, y1) * adjustPixel; + } + } + + var lw; + var mc; + + if(trace.type === 'waterfall') { + if(!isBlank) { + var cont = trace[di.dir].marker; + lw = cont.line.width; + mc = cont.color; + } + } else { + lw = helpers.getLineWidth(trace, di); + mc = di.mc || trace.marker.color; + } + + var offset = d3.round((lw / 2) % 1, 2); + + function roundWithLine(v) { + // if there are explicit gaps, don't round, + // it can make the gaps look crappy + return (opts.gap === 0 && opts.groupgap === 0) ? + d3.round(Math.round(v) - offset, 2) : v; + } + + function expandToVisible(v, vc) { + // if it's not in danger of disappearing entirely, + // round more precisely + return Math.abs(v - vc) >= 2 ? roundWithLine(v) : + // but if it's very thin, expand it so it's + // necessarily visible, even if it might overlap + // its neighbor + (v > vc ? Math.ceil(v) : Math.floor(v)); + } + + if(!gd._context.staticPlot) { + // if bars are not fully opaque or they have a line + // around them, round to integer pixels, mainly for + // safari so we prevent overlaps from its expansive + // pixelation. if the bars ARE fully opaque and have + // no line, expand to a full pixel to make sure we + // can see them + + var op = Color.opacity(mc); + var fixpx = (op < 1 || lw > 0.01) ? roundWithLine : expandToVisible; + x0 = fixpx(x0, x1); + x1 = fixpx(x1, x0); + y0 = fixpx(y0, y1); + y1 = fixpx(y1, y0); + } + + var sel = transition(Lib.ensureSingle(bar, 'path'), opts, makeOnCompleteCallback); + sel + .style('vector-effect', 'non-scaling-stroke') + .attr('d', 'M' + x0 + ',' + y0 + 'V' + y1 + 'H' + x1 + 'V' + y0 + 'Z') + .call(Drawing.setClipUrl, plotinfo.layerClipId, gd); + + if(hasTransition(opts)) { + var styleFns = Drawing.makePointStyleFns(trace); + Drawing.singlePointStyle(di, sel, trace, styleFns, gd); + } + + appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, opts, makeOnCompleteCallback); + + if(plotinfo.layerClipId) { + Drawing.hideOutsideRangePoint(di, bar.select('text'), xa, ya, trace.xcalendar, trace.ycalendar); + } + }); + + // lastly, clip points groups of `cliponaxis !== false` traces + // on `plotinfo._hasClipOnAxisFalse === true` subplots + var hasClipOnAxisFalse = trace.cliponaxis === false; + Drawing.setClipUrl(plotGroup, hasClipOnAxisFalse ? null : plotinfo.layerClipId, gd); + }); + + // error bars are on the top + Registry.getComponentMethod('errorbars', 'plot')(gd, bartraces, plotinfo, opts); +} + +function appendBarText(gd, plotinfo, bar, calcTrace, i, x0, x1, y0, y1, opts, makeOnCompleteCallback) { + var xa = plotinfo.xaxis; + var ya = plotinfo.yaxis; + + var fullLayout = gd._fullLayout; + var textPosition; + + function appendTextNode(bar, text, textFont) { + var textSelection = Lib.ensureSingle(bar, 'text') + .text(text) + .attr({ + 'class': 'bartext bartext-' + textPosition, + 'text-anchor': 'middle', + // prohibit tex interpretation until we can handle + // tex and regular text together + 'data-notex': 1 + }) + .call(Drawing.font, textFont) + .call(svgTextUtils.convertToTspans, gd); + + return textSelection; + } + + // get trace attributes + var trace = calcTrace[0].trace; + var isHorizontal = (trace.orientation === 'h'); + + var text = getText(fullLayout, calcTrace, i, xa, ya); + textPosition = getTextPosition(trace, i); + + // compute text position + var inStackOrRelativeMode = + opts.mode === 'stack' || + opts.mode === 'relative'; + + var calcBar = calcTrace[i]; + var isOutmostBar = !inStackOrRelativeMode || calcBar._outmost; + + if(!text || + textPosition === 'none' || + ((calcBar.isBlank || x0 === x1 || y0 === y1) && ( + textPosition === 'auto' || + textPosition === 'inside'))) { + bar.select('text').remove(); + return; + } + + var layoutFont = fullLayout.font; + var barColor = style.getBarColor(calcTrace[i], trace); + var insideTextFont = style.getInsideTextFont(trace, i, layoutFont, barColor); + var outsideTextFont = style.getOutsideTextFont(trace, i, layoutFont); + + // Special case: don't use the c2p(v, true) value on log size axes, + // so that we can get correctly inside text scaling + var di = bar.datum(); + if(isHorizontal) { + if(xa.type === 'log' && di.s0 <= 0) { + if(xa.range[0] < xa.range[1]) { + x0 = 0; + } else { + x0 = xa._length; + } + } + } else { + if(ya.type === 'log' && di.s0 <= 0) { + if(ya.range[0] < ya.range[1]) { + y0 = ya._length; + } else { + y0 = 0; + } + } + } + + // padding excluded + var barWidth = Math.abs(x1 - x0) - 2 * TEXTPAD; + var barHeight = Math.abs(y1 - y0) - 2 * TEXTPAD; + + var textSelection; + var textBB; + var textWidth; + var textHeight; + + if(textPosition === 'outside') { + if(!isOutmostBar && !calcBar.hasB) textPosition = 'inside'; + } + + if(textPosition === 'auto') { + if(isOutmostBar) { + // draw text using insideTextFont and check if it fits inside bar + textPosition = 'inside'; + textSelection = appendTextNode(bar, text, insideTextFont); + + textBB = Drawing.bBox(textSelection.node()), + textWidth = textBB.width, + textHeight = textBB.height; + + var textHasSize = (textWidth > 0 && textHeight > 0); + var fitsInside = (textWidth <= barWidth && textHeight <= barHeight); + var fitsInsideIfRotated = (textWidth <= barHeight && textHeight <= barWidth); + var fitsInsideIfShrunk = (isHorizontal) ? + (barWidth >= textWidth * (barHeight / textHeight)) : + (barHeight >= textHeight * (barWidth / textWidth)); + + if(textHasSize && ( + fitsInside || + fitsInsideIfRotated || + fitsInsideIfShrunk) + ) { + textPosition = 'inside'; + } else { + textPosition = 'outside'; + textSelection.remove(); + textSelection = null; + } + } else { + textPosition = 'inside'; + } + } + + if(!textSelection) { + textSelection = appendTextNode(bar, text, + (textPosition === 'outside') ? + outsideTextFont : insideTextFont); + + var currentTransform = textSelection.attr('transform'); + textSelection.attr('transform', ''); + textBB = Drawing.bBox(textSelection.node()), + textWidth = textBB.width, + textHeight = textBB.height; + textSelection.attr('transform', currentTransform); + + if(textWidth <= 0 || textHeight <= 0) { + textSelection.remove(); + return; + } + } + + // compute text transform + var transform, constrained; + if(textPosition === 'outside') { + constrained = + trace.constraintext === 'both' || + trace.constraintext === 'outside'; + + transform = Lib.getTextTransform(toMoveOutsideBar(x0, x1, y0, y1, textBB, { + isHorizontal: isHorizontal, + constrained: constrained, + angle: trace.textangle + })); + } else { + constrained = + trace.constraintext === 'both' || + trace.constraintext === 'inside'; + + transform = Lib.getTextTransform(toMoveInsideBar(x0, x1, y0, y1, textBB, { + isHorizontal: isHorizontal, + constrained: constrained, + angle: trace.textangle, + anchor: trace.insidetextanchor + })); + } + + transition(textSelection, opts, makeOnCompleteCallback).attr('transform', transform); +} + +function getRotateFromAngle(angle) { + return (angle === 'auto') ? 0 : angle; +} + +function toMoveInsideBar(x0, x1, y0, y1, textBB, opts) { + var isHorizontal = !!opts.isHorizontal; + var constrained = !!opts.constrained; + var angle = opts.angle || 0; + var anchor = opts.anchor || 0; + + var textWidth = textBB.width; + var textHeight = textBB.height; + var lx = Math.abs(x1 - x0); + var ly = Math.abs(y1 - y0); + + var textpad = ( + lx > (2 * TEXTPAD) && + ly > (2 * TEXTPAD) + ) ? TEXTPAD : 0; + + lx -= 2 * textpad; + ly -= 2 * textpad; + + var autoRotate = (angle === 'auto'); + var isAutoRotated = false; + if(autoRotate && + !(textWidth <= lx && textHeight <= ly) && + (textWidth > lx || textHeight > ly) && ( + !(textWidth > ly || textHeight > lx) || + ((textWidth < textHeight) !== (lx < ly)) + )) { + isAutoRotated = true; + } + + if(isAutoRotated) { + // don't rotate yet only swap bar width with height + var tmp = ly; + ly = lx; + lx = tmp; + } + + var rotate = getRotateFromAngle(angle); + var absSin = Math.abs(Math.sin(Math.PI / 180 * rotate)); + var absCos = Math.abs(Math.cos(Math.PI / 180 * rotate)); + + // compute and apply text padding + var dx = Math.max(lx * absCos, ly * absSin); + var dy = Math.max(lx * absSin, ly * absCos); + + var scale = (constrained) ? + Math.min(dx / textWidth, dy / textHeight) : + Math.max(absCos, absSin); + + scale = Math.min(1, scale); + + // compute text and target positions + var targetX = (x0 + x1) / 2; + var targetY = (y0 + y1) / 2; + + if(anchor !== 'middle') { // case of 'start' or 'end' + var targetWidth = scale * (isHorizontal !== isAutoRotated ? textHeight : textWidth); + var targetHeight = scale * (isHorizontal !== isAutoRotated ? textWidth : textHeight); + textpad += 0.5 * (targetWidth * absSin + targetHeight * absCos); + + if(isHorizontal) { + textpad *= dirSign(x0, x1); + targetX = (anchor === 'start') ? x0 + textpad : x1 - textpad; + } else { + textpad *= dirSign(y0, y1); + targetY = (anchor === 'start') ? y0 + textpad : y1 - textpad; + } + } + + var textX = (textBB.left + textBB.right) / 2; + var textY = (textBB.top + textBB.bottom) / 2; + + // lastly apply auto rotation + if(isAutoRotated) rotate += 90; + + return { + textX: textX, + textY: textY, + targetX: targetX, + targetY: targetY, + scale: scale, + rotate: rotate + }; +} + +function toMoveOutsideBar(x0, x1, y0, y1, textBB, opts) { + var isHorizontal = !!opts.isHorizontal; + var constrained = !!opts.constrained; + var angle = opts.angle || 0; + + var textWidth = textBB.width; + var textHeight = textBB.height; + var lx = Math.abs(x1 - x0); + var ly = Math.abs(y1 - y0); + + var textpad; + // Keep the padding so the text doesn't sit right against + // the bars, but don't factor it into barWidth + if(isHorizontal) { + textpad = (ly > 2 * TEXTPAD) ? TEXTPAD : 0; + } else { + textpad = (lx > 2 * TEXTPAD) ? TEXTPAD : 0; + } + + // compute rotate and scale + var scale = 1; + if(constrained) { + scale = (isHorizontal) ? + Math.min(1, ly / textHeight) : + Math.min(1, lx / textWidth); + } + + var rotate = getRotateFromAngle(angle); + var absSin = Math.abs(Math.sin(Math.PI / 180 * rotate)); + var absCos = Math.abs(Math.cos(Math.PI / 180 * rotate)); + + // compute text and target positions + var targetWidth = scale * (isHorizontal ? textHeight : textWidth); + var targetHeight = scale * (isHorizontal ? textWidth : textHeight); + textpad += 0.5 * (targetWidth * absSin + targetHeight * absCos); + + var targetX = (x0 + x1) / 2; + var targetY = (y0 + y1) / 2; + + if(isHorizontal) { + targetX = x1 - textpad * dirSign(x1, x0); + } else { + targetY = y1 + textpad * dirSign(y0, y1); + } + + var textX = (textBB.left + textBB.right) / 2; + var textY = (textBB.top + textBB.bottom) / 2; + + return { + textX: textX, + textY: textY, + targetX: targetX, + targetY: targetY, + scale: scale, + rotate: rotate + }; +} + +function getText(fullLayout, calcTrace, index, xa, ya) { + var trace = calcTrace[0].trace; + var texttemplate = trace.texttemplate; + + var value; + if(texttemplate) { + value = calcTexttemplate(fullLayout, calcTrace, index, xa, ya); + } else if(trace.textinfo) { + value = calcTextinfo(calcTrace, index, xa, ya); + } else { + value = helpers.getValue(trace.text, index); + } + + return helpers.coerceString(attributeText, value); +} + +function getTextPosition(trace, index) { + var value = helpers.getValue(trace.textposition, index); + return helpers.coerceEnumerated(attributeTextPosition, value); +} + +function calcTexttemplate(fullLayout, calcTrace, index, xa, ya) { + var trace = calcTrace[0].trace; + var texttemplate = Lib.castOption(trace, index, 'texttemplate'); + if(!texttemplate) return ''; + var isHorizontal = (trace.orientation === 'h'); + var isWaterfall = (trace.type === 'waterfall'); + var isFunnel = (trace.type === 'funnel'); + + function formatLabel(u) { + var pAxis = isHorizontal ? ya : xa; + return tickText(pAxis, u, true).text; + } + + function formatNumber(v) { + var sAxis = isHorizontal ? xa : ya; + return tickText(sAxis, +v, true).text; + } + + var cdi = calcTrace[index]; + var obj = {}; + + obj.label = cdi.p; + obj.labelLabel = formatLabel(cdi.p); + + var tx = Lib.castOption(trace, cdi.i, 'text'); + if(tx === 0 || tx) obj.text = tx; + + obj.value = cdi.s; + obj.valueLabel = formatNumber(cdi.s); + + var pt = {}; + appendArrayPointValue(pt, trace, cdi.i); + + if(isWaterfall) { + obj.delta = +cdi.rawS || cdi.s; + obj.deltaLabel = formatNumber(obj.delta); + obj.final = cdi.v; + obj.finalLabel = formatNumber(obj.final); + obj.initial = obj.final - obj.delta; + obj.initialLabel = formatNumber(obj.initial); + } + + if(isFunnel) { + obj.value = cdi.s; + obj.valueLabel = formatNumber(obj.value); + + obj.percentInitial = cdi.begR; + obj.percentInitialLabel = Lib.formatPercent(cdi.begR); + obj.percentPrevious = cdi.difR; + obj.percentPreviousLabel = Lib.formatPercent(cdi.difR); + obj.percentTotal = cdi.sumR; + obj.percenTotalLabel = Lib.formatPercent(cdi.sumR); + } + + var customdata = Lib.castOption(trace, cdi.i, 'customdata'); + if(customdata) obj.customdata = customdata; + return Lib.texttemplateString(texttemplate, obj, fullLayout._d3locale, pt, obj, trace._meta || {}); +} + +function calcTextinfo(calcTrace, index, xa, ya) { + var trace = calcTrace[0].trace; + var isHorizontal = (trace.orientation === 'h'); + var isWaterfall = (trace.type === 'waterfall'); + var isFunnel = (trace.type === 'funnel'); + + function formatLabel(u) { + var pAxis = isHorizontal ? ya : xa; + return tickText(pAxis, u, true).text; + } + + function formatNumber(v) { + var sAxis = isHorizontal ? xa : ya; + return tickText(sAxis, +v, true).text; + } + + var textinfo = trace.textinfo; + var cdi = calcTrace[index]; + + var parts = textinfo.split('+'); + var text = []; + var tx; + + var hasFlag = function(flag) { return parts.indexOf(flag) !== -1; }; + + if(hasFlag('label')) { + text.push(formatLabel(calcTrace[index].p)); + } + + if(hasFlag('text')) { + tx = Lib.castOption(trace, cdi.i, 'text'); + if(tx === 0 || tx) text.push(tx); + } + + if(isWaterfall) { + var delta = +cdi.rawS || cdi.s; + var final = cdi.v; + var initial = final - delta; + + if(hasFlag('initial')) text.push(formatNumber(initial)); + if(hasFlag('delta')) text.push(formatNumber(delta)); + if(hasFlag('final')) text.push(formatNumber(final)); + } + + if(isFunnel) { + if(hasFlag('value')) text.push(formatNumber(cdi.s)); + + var nPercent = 0; + if(hasFlag('percent initial')) nPercent++; + if(hasFlag('percent previous')) nPercent++; + if(hasFlag('percent total')) nPercent++; + + var hasMultiplePercents = nPercent > 1; + + if(hasFlag('percent initial')) { + tx = Lib.formatPercent(cdi.begR); + if(hasMultiplePercents) tx += ' of initial'; + text.push(tx); + } + if(hasFlag('percent previous')) { + tx = Lib.formatPercent(cdi.difR); + if(hasMultiplePercents) tx += ' of previous'; + text.push(tx); + } + if(hasFlag('percent total')) { + tx = Lib.formatPercent(cdi.sumR); + if(hasMultiplePercents) tx += ' of total'; + text.push(tx); + } + } + + return text.join('
'); +} + +module.exports = { + plot: plot, + toMoveInsideBar: toMoveInsideBar, + toMoveOutsideBar: toMoveOutsideBar +}; + +},{"../../components/color":51,"../../components/drawing":72,"../../components/fx/helpers":86,"../../lib":169,"../../lib/svg_text_utils":190,"../../plots/cartesian/axes":213,"../../registry":258,"./attributes":268,"./constants":270,"./helpers":273,"./style":281,"d3":16,"fast-isnumeric":18}],279:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = function selectPoints(searchInfo, selectionTester) { + var cd = searchInfo.cd; + var xa = searchInfo.xaxis; + var ya = searchInfo.yaxis; + var trace = cd[0].trace; + var isFunnel = (trace.type === 'funnel'); + var isHorizontal = (trace.orientation === 'h'); + var selection = []; + var i; + + if(selectionTester === false) { + // clear selection + for(i = 0; i < cd.length; i++) { + cd[i].selected = 0; + } + } else { + for(i = 0; i < cd.length; i++) { + var di = cd[i]; + var ct = 'ct' in di ? di.ct : getCentroid(di, xa, ya, isHorizontal, isFunnel); + + if(selectionTester.contains(ct, false, i, searchInfo)) { + selection.push({ + pointNumber: i, + x: xa.c2d(di.x), + y: ya.c2d(di.y) + }); + di.selected = 1; + } else { + di.selected = 0; + } + } + } + + return selection; +}; + +function getCentroid(d, xa, ya, isHorizontal, isFunnel) { + var x0 = xa.c2p(isHorizontal ? d.s0 : d.p0, true); + var x1 = xa.c2p(isHorizontal ? d.s1 : d.p1, true); + var y0 = ya.c2p(isHorizontal ? d.p0 : d.s0, true); + var y1 = ya.c2p(isHorizontal ? d.p1 : d.s1, true); + + if(isFunnel) { + return [(x0 + x1) / 2, (y0 + y1) / 2]; + } else { + if(isHorizontal) { + return [x1, (y0 + y1) / 2]; + } else { + return [(x0 + x1) / 2, y1]; + } + } +} + +},{}],280:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = Sieve; + +var distinctVals = _dereq_('../../lib').distinctVals; +var BADNUM = _dereq_('../../constants/numerical').BADNUM; + +/** + * Helper class to sieve data from traces into bins + * + * @class + * + * @param {Array} traces +* Array of calculated traces + * @param {object} opts + * - @param {boolean} [sepNegVal] + * If true, then split data at the same position into a bar + * for positive values and another for negative values + * - @param {boolean} [overlapNoMerge] + * If true, then don't merge overlapping bars into a single bar + */ +function Sieve(traces, opts) { + this.traces = traces; + this.sepNegVal = opts.sepNegVal; + this.overlapNoMerge = opts.overlapNoMerge; + + // for single-bin histograms - see histogram/calc + var width1 = Infinity; + + var positions = []; + for(var i = 0; i < traces.length; i++) { + var trace = traces[i]; + for(var j = 0; j < trace.length; j++) { + var bar = trace[j]; + if(bar.p !== BADNUM) positions.push(bar.p); + } + if(trace[0] && trace[0].width1) { + width1 = Math.min(trace[0].width1, width1); + } + } + this.positions = positions; + + var dv = distinctVals(positions); + this.distinctPositions = dv.vals; + if(dv.vals.length === 1 && width1 !== Infinity) this.minDiff = width1; + else this.minDiff = Math.min(dv.minDiff, width1); + + this.binWidth = this.minDiff; + + this.bins = {}; +} + +/** + * Sieve datum + * + * @method + * @param {number} position + * @param {number} value + * @returns {number} Previous bin value + */ +Sieve.prototype.put = function put(position, value) { + var label = this.getLabel(position, value); + var oldValue = this.bins[label] || 0; + + this.bins[label] = oldValue + value; + + return oldValue; +}; + +/** + * Get current bin value for a given datum + * + * @method + * @param {number} position Position of datum + * @param {number} [value] Value of datum + * (required if this.sepNegVal is true) + * @returns {number} Current bin value + */ +Sieve.prototype.get = function get(position, value) { + var label = this.getLabel(position, value); + return this.bins[label] || 0; +}; + +/** + * Get bin label for a given datum + * + * @method + * @param {number} position Position of datum + * @param {number} [value] Value of datum + * (required if this.sepNegVal is true) + * @returns {string} Bin label + * (prefixed with a 'v' if value is negative and this.sepNegVal is + * true; otherwise prefixed with '^') + */ +Sieve.prototype.getLabel = function getLabel(position, value) { + var prefix = (value < 0 && this.sepNegVal) ? 'v' : '^'; + var label = (this.overlapNoMerge) ? + position : + Math.round(position / this.binWidth); + return prefix + label; +}; + +},{"../../constants/numerical":149,"../../lib":169}],281:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); +var Color = _dereq_('../../components/color'); +var Drawing = _dereq_('../../components/drawing'); +var Lib = _dereq_('../../lib'); +var Registry = _dereq_('../../registry'); + +var attributes = _dereq_('./attributes'); +var attributeTextFont = attributes.textfont; +var attributeInsideTextFont = attributes.insidetextfont; +var attributeOutsideTextFont = attributes.outsidetextfont; +var helpers = _dereq_('./helpers'); + +function style(gd) { + var s = d3.select(gd).selectAll('g.barlayer').selectAll('g.trace'); + var barcount = s.size(); + var fullLayout = gd._fullLayout; + + // trace styling + s.style('opacity', function(d) { return d[0].trace.opacity; }) + + // for gapless (either stacked or neighboring grouped) bars use + // crispEdges to turn off antialiasing so an artificial gap + // isn't introduced. + .each(function(d) { + if((fullLayout.barmode === 'stack' && barcount > 1) || + (fullLayout.bargap === 0 && + fullLayout.bargroupgap === 0 && + !d[0].trace.marker.line.width)) { + d3.select(this).attr('shape-rendering', 'crispEdges'); + } + }); + + s.selectAll('g.points').each(function(d) { + var sel = d3.select(this); + var trace = d[0].trace; + stylePoints(sel, trace, gd); + }); + + Registry.getComponentMethod('errorbars', 'style')(s); +} + +function stylePoints(sel, trace, gd) { + Drawing.pointStyle(sel.selectAll('path'), trace, gd); + styleTextPoints(sel, trace, gd); +} + +function styleTextPoints(sel, trace, gd) { + sel.selectAll('text').each(function(d) { + var tx = d3.select(this); + var font = determineFont(tx, d, trace, gd); + Drawing.font(tx, font); + }); +} + +function styleOnSelect(gd, cd, sel) { + var trace = cd[0].trace; + + if(trace.selectedpoints) { + stylePointsInSelectionMode(sel, trace, gd); + } else { + stylePoints(sel, trace, gd); + Registry.getComponentMethod('errorbars', 'style')(sel); + } +} + +function stylePointsInSelectionMode(s, trace, gd) { + Drawing.selectedPointStyle(s.selectAll('path'), trace); + styleTextInSelectionMode(s.selectAll('text'), trace, gd); +} + +function styleTextInSelectionMode(txs, trace, gd) { + txs.each(function(d) { + var tx = d3.select(this); + var font; + + if(d.selected) { + font = Lib.extendFlat({}, determineFont(tx, d, trace, gd)); + + var selectedFontColor = trace.selected.textfont && trace.selected.textfont.color; + if(selectedFontColor) { + font.color = selectedFontColor; + } + + Drawing.font(tx, font); + } else { + Drawing.selectedTextStyle(tx, trace); + } + }); +} + +function determineFont(tx, d, trace, gd) { + var layoutFont = gd._fullLayout.font; + var textFont = trace.textfont; + + if(tx.classed('bartext-inside')) { + var barColor = getBarColor(d, trace); + textFont = getInsideTextFont(trace, d.i, layoutFont, barColor); + } else if(tx.classed('bartext-outside')) { + textFont = getOutsideTextFont(trace, d.i, layoutFont); + } + + return textFont; +} + +function getTextFont(trace, index, defaultValue) { + return getFontValue( + attributeTextFont, trace.textfont, index, defaultValue); +} + +function getInsideTextFont(trace, index, layoutFont, barColor) { + var defaultFont = getTextFont(trace, index, layoutFont); + + var wouldFallBackToLayoutFont = + (trace._input.textfont === undefined || trace._input.textfont.color === undefined) || + (Array.isArray(trace.textfont.color) && trace.textfont.color[index] === undefined); + if(wouldFallBackToLayoutFont) { + defaultFont = { + color: Color.contrast(barColor), + family: defaultFont.family, + size: defaultFont.size + }; + } + + return getFontValue( + attributeInsideTextFont, trace.insidetextfont, index, defaultFont); +} + +function getOutsideTextFont(trace, index, layoutFont) { + var defaultFont = getTextFont(trace, index, layoutFont); + return getFontValue( + attributeOutsideTextFont, trace.outsidetextfont, index, defaultFont); +} + +function getFontValue(attributeDefinition, attributeValue, index, defaultValue) { + attributeValue = attributeValue || {}; + + var familyValue = helpers.getValue(attributeValue.family, index); + var sizeValue = helpers.getValue(attributeValue.size, index); + var colorValue = helpers.getValue(attributeValue.color, index); + + return { + family: helpers.coerceString( + attributeDefinition.family, familyValue, defaultValue.family), + size: helpers.coerceNumber( + attributeDefinition.size, sizeValue, defaultValue.size), + color: helpers.coerceColor( + attributeDefinition.color, colorValue, defaultValue.color) + }; +} + +function getBarColor(cd, trace) { + if(trace.type === 'waterfall') { + return trace[cd.dir].marker.color; + } + return cd.mc || trace.marker.color; +} + +module.exports = { + style: style, + styleTextPoints: styleTextPoints, + styleOnSelect: styleOnSelect, + getInsideTextFont: getInsideTextFont, + getOutsideTextFont: getOutsideTextFont, + getBarColor: getBarColor +}; + +},{"../../components/color":51,"../../components/drawing":72,"../../lib":169,"../../registry":258,"./attributes":268,"./helpers":273,"d3":16}],282:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Color = _dereq_('../../components/color'); +var hasColorscale = _dereq_('../../components/colorscale/helpers').hasColorscale; +var colorscaleDefaults = _dereq_('../../components/colorscale/defaults'); + +module.exports = function handleStyleDefaults(traceIn, traceOut, coerce, defaultColor, layout) { + coerce('marker.color', defaultColor); + + if(hasColorscale(traceIn, 'marker')) { + colorscaleDefaults( + traceIn, traceOut, layout, coerce, {prefix: 'marker.', cLetter: 'c'} + ); + } + + coerce('marker.line.color', Color.defaultLine); + + if(hasColorscale(traceIn, 'marker.line')) { + colorscaleDefaults( + traceIn, traceOut, layout, coerce, {prefix: 'marker.line.', cLetter: 'c'} + ); + } + + coerce('marker.line.width'); + coerce('marker.opacity'); + coerce('selected.marker.color'); + coerce('unselected.marker.color'); +}; + +},{"../../components/color":51,"../../components/colorscale/defaults":61,"../../components/colorscale/helpers":62}],283:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var scatterAttrs = _dereq_('../scatter/attributes'); +var barAttrs = _dereq_('../bar/attributes'); +var colorAttrs = _dereq_('../../components/color/attributes'); +var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs; +var extendFlat = _dereq_('../../lib/extend').extendFlat; + +var scatterMarkerAttrs = scatterAttrs.marker; +var scatterMarkerLineAttrs = scatterMarkerAttrs.line; + +module.exports = { + y: { + valType: 'data_array', + editType: 'calc+clearAxisTypes', + + }, + x: { + valType: 'data_array', + editType: 'calc+clearAxisTypes', + + }, + x0: { + valType: 'any', + + editType: 'calc+clearAxisTypes', + + }, + y0: { + valType: 'any', + + editType: 'calc+clearAxisTypes', + + }, + name: { + valType: 'string', + + editType: 'calc+clearAxisTypes', + + }, + text: extendFlat({}, scatterAttrs.text, { + + }), + hovertext: extendFlat({}, scatterAttrs.hovertext, { + + }), + hovertemplate: hovertemplateAttrs({ + + }), + whiskerwidth: { + valType: 'number', + min: 0, + max: 1, + dflt: 0.5, + + editType: 'calc', + + }, + notched: { + valType: 'boolean', + + editType: 'calc', + + }, + notchwidth: { + valType: 'number', + min: 0, + max: 0.5, + dflt: 0.25, + + editType: 'calc', + + }, + boxpoints: { + valType: 'enumerated', + values: ['all', 'outliers', 'suspectedoutliers', false], + dflt: 'outliers', + + editType: 'calc', + + }, + boxmean: { + valType: 'enumerated', + values: [true, 'sd', false], + dflt: false, + + editType: 'calc', + + }, + jitter: { + valType: 'number', + min: 0, + max: 1, + + editType: 'calc', + + }, + pointpos: { + valType: 'number', + min: -2, + max: 2, + + editType: 'calc', + + }, + orientation: { + valType: 'enumerated', + values: ['v', 'h'], + + editType: 'calc+clearAxisTypes', + + }, + + width: { + valType: 'number', + min: 0, + + dflt: 0, + editType: 'calc', + + }, + + marker: { + outliercolor: { + valType: 'color', + dflt: 'rgba(0, 0, 0, 0)', + + editType: 'style', + + }, + symbol: extendFlat({}, scatterMarkerAttrs.symbol, + {arrayOk: false, editType: 'plot'}), + opacity: extendFlat({}, scatterMarkerAttrs.opacity, + {arrayOk: false, dflt: 1, editType: 'style'}), + size: extendFlat({}, scatterMarkerAttrs.size, + {arrayOk: false, editType: 'calc'}), + color: extendFlat({}, scatterMarkerAttrs.color, + {arrayOk: false, editType: 'style'}), + line: { + color: extendFlat({}, scatterMarkerLineAttrs.color, + {arrayOk: false, dflt: colorAttrs.defaultLine, editType: 'style'} + ), + width: extendFlat({}, scatterMarkerLineAttrs.width, + {arrayOk: false, dflt: 0, editType: 'style'} + ), + outliercolor: { + valType: 'color', + + editType: 'style', + + }, + outlierwidth: { + valType: 'number', + min: 0, + dflt: 1, + + editType: 'style', + + }, + editType: 'style' + }, + editType: 'plot' + }, + line: { + color: { + valType: 'color', + + editType: 'style', + + }, + width: { + valType: 'number', + + min: 0, + dflt: 2, + editType: 'style', + + }, + editType: 'plot' + }, + fillcolor: scatterAttrs.fillcolor, + + offsetgroup: barAttrs.offsetgroup, + alignmentgroup: barAttrs.alignmentgroup, + + selected: { + marker: scatterAttrs.selected.marker, + editType: 'style' + }, + unselected: { + marker: scatterAttrs.unselected.marker, + editType: 'style' + }, + hoveron: { + valType: 'flaglist', + flags: ['boxes', 'points'], + dflt: 'boxes+points', + + editType: 'style', + + } +}; + +},{"../../components/color/attributes":50,"../../lib/extend":164,"../../plots/template_attributes":253,"../bar/attributes":268,"../scatter/attributes":376}],284:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); + +var Lib = _dereq_('../../lib'); +var _ = Lib._; +var Axes = _dereq_('../../plots/cartesian/axes'); + +// outlier definition based on http://www.physics.csbsju.edu/stats/box2.html +module.exports = function calc(gd, trace) { + var fullLayout = gd._fullLayout; + var xa = Axes.getFromId(gd, trace.xaxis || 'x'); + var ya = Axes.getFromId(gd, trace.yaxis || 'y'); + var cd = []; + + // N.B. violin reuses same Box.calc + var numKey = trace.type === 'violin' ? '_numViolins' : '_numBoxes'; + + var i; + var valAxis, valLetter; + var posAxis, posLetter; + + if(trace.orientation === 'h') { + valAxis = xa; + valLetter = 'x'; + posAxis = ya; + posLetter = 'y'; + } else { + valAxis = ya; + valLetter = 'y'; + posAxis = xa; + posLetter = 'x'; + } + + var val = valAxis.makeCalcdata(trace, valLetter); + var pos = getPos(trace, posLetter, posAxis, val, fullLayout[numKey]); + + var dv = Lib.distinctVals(pos); + var posDistinct = dv.vals; + var dPos = dv.minDiff / 2; + var posBins = makeBins(posDistinct, dPos); + + var pLen = posDistinct.length; + var ptsPerBin = initNestedArray(pLen); + + // bin pts info per position bins + for(i = 0; i < trace._length; i++) { + var v = val[i]; + if(!isNumeric(v)) continue; + + var n = Lib.findBin(pos[i], posBins); + if(n >= 0 && n < pLen) { + var pt = {v: v, i: i}; + arraysToCalcdata(pt, trace, i); + ptsPerBin[n].push(pt); + } + } + + var cdi; + var ptFilterFn = (trace.boxpoints || trace.points) === 'all' ? + Lib.identity : + function(pt) { return (pt.v < cdi.lf || pt.v > cdi.uf); }; + + // build calcdata trace items, one item per distinct position + for(i = 0; i < pLen; i++) { + if(ptsPerBin[i].length > 0) { + var pts = ptsPerBin[i].sort(sortByVal); + var boxVals = pts.map(extractVal); + var bvLen = boxVals.length; + + cdi = {}; + cdi.pos = posDistinct[i]; + cdi.pts = pts; + + // Sort categories by values + cdi[posLetter] = cdi.pos; + cdi[valLetter] = cdi.pts.map(function(pt) { return pt.v; }); + + cdi.min = boxVals[0]; + cdi.max = boxVals[bvLen - 1]; + cdi.mean = Lib.mean(boxVals, bvLen); + cdi.sd = Lib.stdev(boxVals, bvLen, cdi.mean); + + // first quartile + cdi.q1 = Lib.interp(boxVals, 0.25); + // median + cdi.med = Lib.interp(boxVals, 0.5); + // third quartile + cdi.q3 = Lib.interp(boxVals, 0.75); + + // lower and upper fences - last point inside + // 1.5 interquartile ranges from quartiles + cdi.lf = Math.min( + cdi.q1, + boxVals[Math.min( + Lib.findBin(2.5 * cdi.q1 - 1.5 * cdi.q3, boxVals, true) + 1, + bvLen - 1 + )] + ); + cdi.uf = Math.max( + cdi.q3, + boxVals[Math.max( + Lib.findBin(2.5 * cdi.q3 - 1.5 * cdi.q1, boxVals), + 0 + )] + ); + + // lower and upper outliers - 3 IQR out (don't clip to max/min, + // this is only for discriminating suspected & far outliers) + cdi.lo = 4 * cdi.q1 - 3 * cdi.q3; + cdi.uo = 4 * cdi.q3 - 3 * cdi.q1; + + // lower and upper notches ~95% Confidence Intervals for median + var iqr = cdi.q3 - cdi.q1; + var mci = 1.57 * iqr / Math.sqrt(bvLen); + cdi.ln = cdi.med - mci; + cdi.un = cdi.med + mci; + + cdi.pts2 = pts.filter(ptFilterFn); + + cd.push(cdi); + } + } + + calcSelection(cd, trace); + var extremes = Axes.findExtremes(valAxis, val, {padded: true}); + trace._extremes[valAxis._id] = extremes; + + if(cd.length > 0) { + cd[0].t = { + num: fullLayout[numKey], + dPos: dPos, + posLetter: posLetter, + valLetter: valLetter, + labels: { + med: _(gd, 'median:'), + min: _(gd, 'min:'), + q1: _(gd, 'q1:'), + q3: _(gd, 'q3:'), + max: _(gd, 'max:'), + mean: trace.boxmean === 'sd' ? _(gd, 'mean ± σ:') : _(gd, 'mean:'), + lf: _(gd, 'lower fence:'), + uf: _(gd, 'upper fence:') + } + }; + + fullLayout[numKey]++; + return cd; + } else { + return [{t: {empty: true}}]; + } +}; + +// In vertical (horizontal) box plots: +// if no x (y) data, use x0 (y0), or name +// so if you want one box +// per trace, set x0 (y0) to the x (y) value or category for this trace +// (or set x (y) to a constant array matching y (x)) +function getPos(trace, posLetter, posAxis, val, num) { + if(posLetter in trace) { + return posAxis.makeCalcdata(trace, posLetter); + } + + var pos0; + + if(posLetter + '0' in trace) { + pos0 = trace[posLetter + '0']; + } else if('name' in trace && ( + posAxis.type === 'category' || ( + isNumeric(trace.name) && + ['linear', 'log'].indexOf(posAxis.type) !== -1 + ) || ( + Lib.isDateTime(trace.name) && + posAxis.type === 'date' + ) + )) { + pos0 = trace.name; + } else { + pos0 = num; + } + + var pos0c = posAxis.type === 'multicategory' ? + posAxis.r2c_just_indices(pos0) : + posAxis.d2c(pos0, 0, trace[posLetter + 'calendar']); + + return val.map(function() { return pos0c; }); +} + +function makeBins(x, dx) { + var len = x.length; + var bins = new Array(len + 1); + + for(var i = 0; i < len; i++) { + bins[i] = x[i] - dx; + } + bins[len] = x[len - 1] + dx; + + return bins; +} + +function initNestedArray(len) { + var arr = new Array(len); + for(var i = 0; i < len; i++) { + arr[i] = []; + } + return arr; +} + +function arraysToCalcdata(pt, trace, i) { + var trace2calc = { + text: 'tx', + hovertext: 'htx' + }; + + for(var k in trace2calc) { + if(Array.isArray(trace[k])) { + pt[trace2calc[k]] = trace[k][i]; + } + } +} + +function calcSelection(cd, trace) { + if(Lib.isArrayOrTypedArray(trace.selectedpoints)) { + for(var i = 0; i < cd.length; i++) { + var pts = cd[i].pts || []; + var ptNumber2cdIndex = {}; + + for(var j = 0; j < pts.length; j++) { + ptNumber2cdIndex[pts[j].i] = j; + } + + Lib.tagSelected(pts, trace, ptNumber2cdIndex); + } + } +} + +function sortByVal(a, b) { return a.v - b.v; } + +function extractVal(o) { return o.v; } + +},{"../../lib":169,"../../plots/cartesian/axes":213,"fast-isnumeric":18}],285:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Axes = _dereq_('../../plots/cartesian/axes'); +var Lib = _dereq_('../../lib'); +var getAxisGroup = _dereq_('../../plots/cartesian/axis_ids').getAxisGroup; + +var orientations = ['v', 'h']; + +function crossTraceCalc(gd, plotinfo) { + var calcdata = gd.calcdata; + var xa = plotinfo.xaxis; + var ya = plotinfo.yaxis; + + for(var i = 0; i < orientations.length; i++) { + var orientation = orientations[i]; + var posAxis = orientation === 'h' ? ya : xa; + var boxList = []; + + // make list of boxes / candlesticks + // For backward compatibility, candlesticks are treated as if they *are* box traces here + for(var j = 0; j < calcdata.length; j++) { + var cd = calcdata[j]; + var t = cd[0].t; + var trace = cd[0].trace; + + if(trace.visible === true && + (trace.type === 'box' || trace.type === 'candlestick') && + !t.empty && + (trace.orientation || 'v') === orientation && + trace.xaxis === xa._id && + trace.yaxis === ya._id + ) { + boxList.push(j); + } + } + + setPositionOffset('box', gd, boxList, posAxis); + } +} + +function setPositionOffset(traceType, gd, boxList, posAxis) { + var calcdata = gd.calcdata; + var fullLayout = gd._fullLayout; + var axId = posAxis._id; + var axLetter = axId.charAt(0); + + var i, j, calcTrace; + var pointList = []; + var shownPts = 0; + + // make list of box points + for(i = 0; i < boxList.length; i++) { + calcTrace = calcdata[boxList[i]]; + for(j = 0; j < calcTrace.length; j++) { + pointList.push(posAxis.c2l(calcTrace[j].pos, true)); + shownPts += (calcTrace[j].pts2 || []).length; + } + } + + if(!pointList.length) return; + + // box plots - update dPos based on multiple traces + var boxdv = Lib.distinctVals(pointList); + var dPos0 = boxdv.minDiff / 2; + + // check for forced minimum dtick + Axes.minDtick(posAxis, boxdv.minDiff, boxdv.vals[0], true); + + var numKey = traceType === 'violin' ? '_numViolins' : '_numBoxes'; + var numTotal = fullLayout[numKey]; + var group = fullLayout[traceType + 'mode'] === 'group' && numTotal > 1; + var groupFraction = 1 - fullLayout[traceType + 'gap']; + var groupGapFraction = 1 - fullLayout[traceType + 'groupgap']; + + for(i = 0; i < boxList.length; i++) { + calcTrace = calcdata[boxList[i]]; + + var trace = calcTrace[0].trace; + var t = calcTrace[0].t; + var width = trace.width; + var side = trace.side; + + // position coordinate delta + var dPos; + // box half width; + var bdPos; + // box center offset + var bPos; + // half-width within which to accept hover for this box/violin + // always split the distance to the closest box/violin + var wHover; + + if(width) { + dPos = bdPos = wHover = width / 2; + bPos = 0; + } else { + dPos = dPos0; + + if(group) { + var groupId = getAxisGroup(fullLayout, posAxis._id) + trace.orientation; + var alignmentGroups = fullLayout._alignmentOpts[groupId] || {}; + var alignmentGroupOpts = alignmentGroups[trace.alignmentgroup] || {}; + var nOffsetGroups = Object.keys(alignmentGroupOpts.offsetGroups || {}).length; + var num = nOffsetGroups || numTotal; + var shift = nOffsetGroups ? trace._offsetIndex : t.num; + + bdPos = dPos * groupFraction * groupGapFraction / num; + bPos = 2 * dPos * (-0.5 + (shift + 0.5) / num) * groupFraction; + wHover = dPos * groupFraction / num; + } else { + bdPos = dPos * groupFraction * groupGapFraction; + bPos = 0; + wHover = dPos; + } + } + t.dPos = dPos; + t.bPos = bPos; + t.bdPos = bdPos; + t.wHover = wHover; + + // box/violin-only value-space push value + var pushplus; + var pushminus; + // edge of box/violin + var edge = bPos + bdPos; + var edgeplus; + var edgeminus; + // value-space padding + var vpadplus; + var vpadminus; + // pixel-space padding + var ppadplus; + var ppadminus; + // do we add 5% of both sides (more logic for points beyond box/violin below) + var padded = Boolean(width); + // does this trace show points? + var hasPts = (trace.boxpoints || trace.points) && (shownPts > 0); + + if(side === 'positive') { + pushplus = dPos * (width ? 1 : 0.5); + edgeplus = edge; + pushminus = edgeplus = bPos; + } else if(side === 'negative') { + pushplus = edgeplus = bPos; + pushminus = dPos * (width ? 1 : 0.5); + edgeminus = edge; + } else { + pushplus = pushminus = dPos; + edgeplus = edgeminus = edge; + } + + if(hasPts) { + var pointpos = trace.pointpos; + var jitter = trace.jitter; + var ms = trace.marker.size / 2; + + var pp = 0; + if((pointpos + jitter) >= 0) { + pp = edge * (pointpos + jitter); + if(pp > pushplus) { + // (++) beyond plus-value, use pp + padded = true; + ppadplus = ms; + vpadplus = pp; + } else if(pp > edgeplus) { + // (+), use push-value (it's bigger), but add px-pad + ppadplus = ms; + vpadplus = pushplus; + } + } + if(pp <= pushplus) { + // (->) fallback to push value + vpadplus = pushplus; + } + + var pm = 0; + if((pointpos - jitter) <= 0) { + pm = -edge * (pointpos - jitter); + if(pm > pushminus) { + // (--) beyond plus-value, use pp + padded = true; + ppadminus = ms; + vpadminus = pm; + } else if(pm > edgeminus) { + // (-), use push-value (it's bigger), but add px-pad + ppadminus = ms; + vpadminus = pushminus; + } + } + if(pm <= pushminus) { + // (<-) fallback to push value + vpadminus = pushminus; + } + } else { + vpadplus = pushplus; + vpadminus = pushminus; + } + + var pos = new Array(calcTrace.length); + for(j = 0; j < calcTrace.length; j++) { + pos[j] = calcTrace[j].pos; + } + + trace._extremes[axId] = Axes.findExtremes(posAxis, pos, { + padded: padded, + vpadminus: vpadminus, + vpadplus: vpadplus, + vpadLinearized: true, + // N.B. SVG px-space positive/negative + ppadminus: {x: ppadminus, y: ppadplus}[axLetter], + ppadplus: {x: ppadplus, y: ppadminus}[axLetter], + }); + } +} + +module.exports = { + crossTraceCalc: crossTraceCalc, + setPositionOffset: setPositionOffset +}; + +},{"../../lib":169,"../../plots/cartesian/axes":213,"../../plots/cartesian/axis_ids":216}],286:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var Registry = _dereq_('../../registry'); +var Color = _dereq_('../../components/color'); +var handleGroupingDefaults = _dereq_('../bar/defaults').handleGroupingDefaults; +var attributes = _dereq_('./attributes'); + +function supplyDefaults(traceIn, traceOut, defaultColor, layout) { + function coerce(attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); + } + + handleSampleDefaults(traceIn, traceOut, coerce, layout); + if(traceOut.visible === false) return; + + coerce('line.color', (traceIn.marker || {}).color || defaultColor); + coerce('line.width'); + coerce('fillcolor', Color.addOpacity(traceOut.line.color, 0.5)); + + coerce('whiskerwidth'); + coerce('boxmean'); + coerce('width'); + + var notched = coerce('notched', traceIn.notchwidth !== undefined); + if(notched) coerce('notchwidth'); + + handlePointsDefaults(traceIn, traceOut, coerce, {prefix: 'box'}); +} + +function handleSampleDefaults(traceIn, traceOut, coerce, layout) { + var y = coerce('y'); + var x = coerce('x'); + var hasX = x && x.length; + + var defaultOrientation, len; + + if(y && y.length) { + defaultOrientation = 'v'; + if(hasX) { + len = Math.min(Lib.minRowLength(x), Lib.minRowLength(y)); + } else { + coerce('x0'); + len = Lib.minRowLength(y); + } + } else if(hasX) { + defaultOrientation = 'h'; + coerce('y0'); + len = Lib.minRowLength(x); + } else { + traceOut.visible = false; + return; + } + traceOut._length = len; + + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); + handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout); + + coerce('orientation', defaultOrientation); +} + +function handlePointsDefaults(traceIn, traceOut, coerce, opts) { + var prefix = opts.prefix; + + var outlierColorDflt = Lib.coerce2(traceIn, traceOut, attributes, 'marker.outliercolor'); + var lineoutliercolor = coerce('marker.line.outliercolor'); + + var points = coerce( + prefix + 'points', + (outlierColorDflt || lineoutliercolor) ? 'suspectedoutliers' : undefined + ); + + if(points) { + coerce('jitter', points === 'all' ? 0.3 : 0); + coerce('pointpos', points === 'all' ? -1.5 : 0); + + coerce('marker.symbol'); + coerce('marker.opacity'); + coerce('marker.size'); + coerce('marker.color', traceOut.line.color); + coerce('marker.line.color'); + coerce('marker.line.width'); + + if(points === 'suspectedoutliers') { + coerce('marker.line.outliercolor', traceOut.marker.color); + coerce('marker.line.outlierwidth'); + } + + coerce('selected.marker.color'); + coerce('unselected.marker.color'); + coerce('selected.marker.size'); + coerce('unselected.marker.size'); + + coerce('text'); + coerce('hovertext'); + } else { + delete traceOut.marker; + } + + var hoveron = coerce('hoveron'); + if(hoveron === 'all' || hoveron.indexOf('points') !== -1) { + coerce('hovertemplate'); + } + + Lib.coerceSelectionMarkerOpacity(traceOut, coerce); +} + +function crossTraceDefaults(fullData, fullLayout) { + var traceIn, traceOut; + + function coerce(attr) { + return Lib.coerce(traceOut._input, traceOut, attributes, attr); + } + + for(var i = 0; i < fullData.length; i++) { + traceOut = fullData[i]; + var traceType = traceOut.type; + + if(traceType === 'box' || traceType === 'violin') { + traceIn = traceOut._input; + if(fullLayout[traceType + 'mode'] === 'group') { + handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce); + } + } + } +} + +module.exports = { + supplyDefaults: supplyDefaults, + crossTraceDefaults: crossTraceDefaults, + + handleSampleDefaults: handleSampleDefaults, + handlePointsDefaults: handlePointsDefaults +}; + +},{"../../components/color":51,"../../lib":169,"../../registry":258,"../bar/defaults":272,"./attributes":283}],287:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = function eventData(out, pt) { + // Note: hoverOnBox property is needed for click-to-select + // to ignore when a box was clicked. This is the reason box + // implements this custom eventData function. + if(pt.hoverOnBox) out.hoverOnBox = pt.hoverOnBox; + + if('xVal' in pt) out.x = pt.xVal; + if('yVal' in pt) out.y = pt.yVal; + if(pt.xa) out.xaxis = pt.xa; + if(pt.ya) out.yaxis = pt.ya; + + return out; +}; + +},{}],288:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Axes = _dereq_('../../plots/cartesian/axes'); +var Lib = _dereq_('../../lib'); +var Fx = _dereq_('../../components/fx'); +var Color = _dereq_('../../components/color'); +var fillText = Lib.fillText; + +function hoverPoints(pointData, xval, yval, hovermode) { + var cd = pointData.cd; + var trace = cd[0].trace; + var hoveron = trace.hoveron; + var closeBoxData = []; + var closePtData; + + if(hoveron.indexOf('boxes') !== -1) { + closeBoxData = closeBoxData.concat(hoverOnBoxes(pointData, xval, yval, hovermode)); + } + + if(hoveron.indexOf('points') !== -1) { + closePtData = hoverOnPoints(pointData, xval, yval); + } + + // If there's a point in range and hoveron has points, show the best single point only. + // If hoveron has boxes and there's no point in range (or hoveron doesn't have points), show the box stats. + if(hovermode === 'closest') { + if(closePtData) return [closePtData]; + return closeBoxData; + } + + // Otherwise in compare mode, allow a point AND the box stats to be labeled + // If there are multiple boxes in range (ie boxmode = 'overlay') we'll see stats for all of them. + if(closePtData) { + closeBoxData.push(closePtData); + return closeBoxData; + } + return closeBoxData; +} + +function hoverOnBoxes(pointData, xval, yval, hovermode) { + var cd = pointData.cd; + var xa = pointData.xa; + var ya = pointData.ya; + var trace = cd[0].trace; + var t = cd[0].t; + var isViolin = trace.type === 'violin'; + var closeBoxData = []; + + var pLetter, vLetter, pAxis, vAxis, vVal, pVal, dx, dy, dPos, + hoverPseudoDistance, spikePseudoDistance; + + var boxDelta = t.bdPos; + var boxDeltaPos, boxDeltaNeg; + var posAcceptance = t.wHover; + var shiftPos = function(di) { return pAxis.c2l(di.pos) + t.bPos - pAxis.c2l(pVal); }; + + if(isViolin && trace.side !== 'both') { + if(trace.side === 'positive') { + dPos = function(di) { + var pos = shiftPos(di); + return Fx.inbox(pos, pos + posAcceptance, hoverPseudoDistance); + }; + boxDeltaPos = boxDelta; + boxDeltaNeg = 0; + } + if(trace.side === 'negative') { + dPos = function(di) { + var pos = shiftPos(di); + return Fx.inbox(pos - posAcceptance, pos, hoverPseudoDistance); + }; + boxDeltaPos = 0; + boxDeltaNeg = boxDelta; + } + } else { + dPos = function(di) { + var pos = shiftPos(di); + return Fx.inbox(pos - posAcceptance, pos + posAcceptance, hoverPseudoDistance); + }; + boxDeltaPos = boxDeltaNeg = boxDelta; + } + + var dVal; + + if(isViolin) { + dVal = function(di) { + return Fx.inbox(di.span[0] - vVal, di.span[1] - vVal, hoverPseudoDistance); + }; + } else { + dVal = function(di) { + return Fx.inbox(di.min - vVal, di.max - vVal, hoverPseudoDistance); + }; + } + + if(trace.orientation === 'h') { + vVal = xval; + pVal = yval; + dx = dVal; + dy = dPos; + pLetter = 'y'; + pAxis = ya; + vLetter = 'x'; + vAxis = xa; + } else { + vVal = yval; + pVal = xval; + dx = dPos; + dy = dVal; + pLetter = 'x'; + pAxis = xa; + vLetter = 'y'; + vAxis = ya; + } + + // if two boxes are overlaying, let the narrowest one win + var pseudoDistance = Math.min(1, boxDelta / Math.abs(pAxis.r2c(pAxis.range[1]) - pAxis.r2c(pAxis.range[0]))); + hoverPseudoDistance = pointData.maxHoverDistance - pseudoDistance; + spikePseudoDistance = pointData.maxSpikeDistance - pseudoDistance; + + function dxy(di) { return (dx(di) + dy(di)) / 2; } + var distfn = Fx.getDistanceFunction(hovermode, dx, dy, dxy); + Fx.getClosest(cd, distfn, pointData); + + // skip the rest (for this trace) if we didn't find a close point + // and create the item(s) in closedata for this point + if(pointData.index === false) return []; + + var di = cd[pointData.index]; + var lc = trace.line.color; + var mc = (trace.marker || {}).color; + + if(Color.opacity(lc) && trace.line.width) pointData.color = lc; + else if(Color.opacity(mc) && trace.boxpoints) pointData.color = mc; + else pointData.color = trace.fillcolor; + + pointData[pLetter + '0'] = pAxis.c2p(di.pos + t.bPos - boxDeltaNeg, true); + pointData[pLetter + '1'] = pAxis.c2p(di.pos + t.bPos + boxDeltaPos, true); + + pointData[pLetter + 'LabelVal'] = di.pos; + + var spikePosAttr = pLetter + 'Spike'; + pointData.spikeDistance = dxy(di) * spikePseudoDistance / hoverPseudoDistance; + pointData[spikePosAttr] = pAxis.c2p(di.pos, true); + + // box plots: each "point" gets many labels + var usedVals = {}; + var attrs = ['med', 'q1', 'q3', 'min', 'max']; + + if(trace.boxmean || (trace.meanline || {}).visible) { + attrs.push('mean'); + } + if(trace.boxpoints || trace.points) { + attrs.push('lf', 'uf'); + } + + for(var i = 0; i < attrs.length; i++) { + var attr = attrs[i]; + + if(!(attr in di) || (di[attr] in usedVals)) continue; + usedVals[di[attr]] = true; + + // copy out to a new object for each value to label + var val = di[attr]; + var valPx = vAxis.c2p(val, true); + var pointData2 = Lib.extendFlat({}, pointData); + + pointData2.attr = attr; + pointData2[vLetter + '0'] = pointData2[vLetter + '1'] = valPx; + pointData2[vLetter + 'LabelVal'] = val; + pointData2[vLetter + 'Label'] = (t.labels ? t.labels[attr] + ' ' : '') + Axes.hoverLabelText(vAxis, val); + + // Note: introduced to be able to distinguish a + // clicked point from a box during click-to-select + pointData2.hoverOnBox = true; + + if(attr === 'mean' && ('sd' in di) && trace.boxmean === 'sd') { + pointData2[vLetter + 'err'] = di.sd; + } + + // only keep name and spikes on the first item (median) + pointData.name = ''; + pointData.spikeDistance = undefined; + pointData[spikePosAttr] = undefined; + + // no hovertemplate support yet + pointData2.hovertemplate = false; + + closeBoxData.push(pointData2); + } + + return closeBoxData; +} + +function hoverOnPoints(pointData, xval, yval) { + var cd = pointData.cd; + var xa = pointData.xa; + var ya = pointData.ya; + var trace = cd[0].trace; + var xPx = xa.c2p(xval); + var yPx = ya.c2p(yval); + var closePtData; + + var dx = function(di) { + var rad = Math.max(3, di.mrc || 0); + return Math.max(Math.abs(xa.c2p(di.x) - xPx) - rad, 1 - 3 / rad); + }; + var dy = function(di) { + var rad = Math.max(3, di.mrc || 0); + return Math.max(Math.abs(ya.c2p(di.y) - yPx) - rad, 1 - 3 / rad); + }; + var distfn = Fx.quadrature(dx, dy); + + // show one point per trace + var ijClosest = false; + var di, pt; + + for(var i = 0; i < cd.length; i++) { + di = cd[i]; + + for(var j = 0; j < (di.pts || []).length; j++) { + pt = di.pts[j]; + + var newDistance = distfn(pt); + if(newDistance <= pointData.distance) { + pointData.distance = newDistance; + ijClosest = [i, j]; + } + } + } + + if(!ijClosest) return false; + + di = cd[ijClosest[0]]; + pt = di.pts[ijClosest[1]]; + + var xc = xa.c2p(pt.x, true); + var yc = ya.c2p(pt.y, true); + var rad = pt.mrc || 1; + + closePtData = Lib.extendFlat({}, pointData, { + // corresponds to index in x/y input data array + index: pt.i, + color: (trace.marker || {}).color, + name: trace.name, + x0: xc - rad, + x1: xc + rad, + y0: yc - rad, + y1: yc + rad, + spikeDistance: pointData.distance, + hovertemplate: trace.hovertemplate + }); + + var pa; + if(trace.orientation === 'h') { + pa = ya; + closePtData.xLabelVal = pt.x; + closePtData.yLabelVal = di.pos; + } else { + pa = xa; + closePtData.xLabelVal = di.pos; + closePtData.yLabelVal = pt.y; + } + + var pLetter = pa._id.charAt(0); + closePtData[pLetter + 'Spike'] = pa.c2p(di.pos, true); + + fillText(pt, trace, closePtData); + + return closePtData; +} + +module.exports = { + hoverPoints: hoverPoints, + hoverOnBoxes: hoverOnBoxes, + hoverOnPoints: hoverOnPoints +}; + +},{"../../components/color":51,"../../components/fx":89,"../../lib":169,"../../plots/cartesian/axes":213}],289:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + attributes: _dereq_('./attributes'), + layoutAttributes: _dereq_('./layout_attributes'), + supplyDefaults: _dereq_('./defaults').supplyDefaults, + crossTraceDefaults: _dereq_('./defaults').crossTraceDefaults, + supplyLayoutDefaults: _dereq_('./layout_defaults').supplyLayoutDefaults, + calc: _dereq_('./calc'), + crossTraceCalc: _dereq_('./cross_trace_calc').crossTraceCalc, + plot: _dereq_('./plot').plot, + style: _dereq_('./style').style, + styleOnSelect: _dereq_('./style').styleOnSelect, + hoverPoints: _dereq_('./hover').hoverPoints, + eventData: _dereq_('./event_data'), + selectPoints: _dereq_('./select'), + + moduleType: 'trace', + name: 'box', + basePlotModule: _dereq_('../../plots/cartesian'), + categories: ['cartesian', 'svg', 'symbols', 'oriented', 'box-violin', 'showLegend', 'boxLayout', 'zoomScale'], + meta: { + + } +}; + +},{"../../plots/cartesian":224,"./attributes":283,"./calc":284,"./cross_trace_calc":285,"./defaults":286,"./event_data":287,"./hover":288,"./layout_attributes":290,"./layout_defaults":291,"./plot":292,"./select":293,"./style":294}],290:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + + +module.exports = { + boxmode: { + valType: 'enumerated', + values: ['group', 'overlay'], + dflt: 'overlay', + + editType: 'calc', + + }, + boxgap: { + valType: 'number', + min: 0, + max: 1, + dflt: 0.3, + + editType: 'calc', + + }, + boxgroupgap: { + valType: 'number', + min: 0, + max: 1, + dflt: 0.3, + + editType: 'calc', + + } +}; + +},{}],291:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Registry = _dereq_('../../registry'); +var Lib = _dereq_('../../lib'); +var layoutAttributes = _dereq_('./layout_attributes'); + +function _supply(layoutIn, layoutOut, fullData, coerce, traceType) { + var category = traceType + 'Layout'; + var hasTraceType = false; + + for(var i = 0; i < fullData.length; i++) { + var trace = fullData[i]; + + if(Registry.traceIs(trace, category)) { + hasTraceType = true; + break; + } + } + if(!hasTraceType) return; + + coerce(traceType + 'mode'); + coerce(traceType + 'gap'); + coerce(traceType + 'groupgap'); +} + +function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { + function coerce(attr, dflt) { + return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt); + } + _supply(layoutIn, layoutOut, fullData, coerce, 'box'); +} + +module.exports = { + supplyLayoutDefaults: supplyLayoutDefaults, + _supply: _supply +}; + +},{"../../lib":169,"../../registry":258,"./layout_attributes":290}],292:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); + +var Lib = _dereq_('../../lib'); +var Drawing = _dereq_('../../components/drawing'); + +// constants for dynamic jitter (ie less jitter for sparser points) +var JITTERCOUNT = 5; // points either side of this to include +var JITTERSPREAD = 0.01; // fraction of IQR to count as "dense" + +function plot(gd, plotinfo, cdbox, boxLayer) { + var xa = plotinfo.xaxis; + var ya = plotinfo.yaxis; + + Lib.makeTraceGroups(boxLayer, cdbox, 'trace boxes').each(function(cd) { + var plotGroup = d3.select(this); + var cd0 = cd[0]; + var t = cd0.t; + var trace = cd0.trace; + + // whisker width + t.wdPos = t.bdPos * trace.whiskerwidth; + + if(trace.visible !== true || t.empty) { + plotGroup.remove(); + return; + } + + var posAxis, valAxis; + + if(trace.orientation === 'h') { + posAxis = ya; + valAxis = xa; + } else { + posAxis = xa; + valAxis = ya; + } + + plotBoxAndWhiskers(plotGroup, {pos: posAxis, val: valAxis}, trace, t); + plotPoints(plotGroup, {x: xa, y: ya}, trace, t); + plotBoxMean(plotGroup, {pos: posAxis, val: valAxis}, trace, t); + }); +} + +function plotBoxAndWhiskers(sel, axes, trace, t) { + var posAxis = axes.pos; + var valAxis = axes.val; + var bPos = t.bPos; + var wdPos = t.wdPos || 0; + var bPosPxOffset = t.bPosPxOffset || 0; + var whiskerWidth = trace.whiskerwidth || 0; + var notched = trace.notched || false; + var nw = notched ? 1 - 2 * trace.notchwidth : 1; + + // to support for one-sided box + var bdPos0; + var bdPos1; + if(Array.isArray(t.bdPos)) { + bdPos0 = t.bdPos[0]; + bdPos1 = t.bdPos[1]; + } else { + bdPos0 = t.bdPos; + bdPos1 = t.bdPos; + } + + var paths = sel.selectAll('path.box').data(( + trace.type !== 'violin' || + trace.box.visible + ) ? Lib.identity : []); + + paths.enter().append('path') + .style('vector-effect', 'non-scaling-stroke') + .attr('class', 'box'); + + paths.exit().remove(); + + paths.each(function(d) { + if(d.empty) return 'M0,0Z'; + + var lcenter = posAxis.c2l(d.pos + bPos, true); + var posc = posAxis.l2p(lcenter) + bPosPxOffset; + var pos0 = posAxis.l2p(lcenter - bdPos0) + bPosPxOffset; + var pos1 = posAxis.l2p(lcenter + bdPos1) + bPosPxOffset; + var posw0 = posAxis.l2p(lcenter - wdPos) + bPosPxOffset; + var posw1 = posAxis.l2p(lcenter + wdPos) + bPosPxOffset; + var posm0 = posAxis.l2p(lcenter - bdPos0 * nw) + bPosPxOffset; + var posm1 = posAxis.l2p(lcenter + bdPos1 * nw) + bPosPxOffset; + var q1 = valAxis.c2p(d.q1, true); + var q3 = valAxis.c2p(d.q3, true); + // make sure median isn't identical to either of the + // quartiles, so we can see it + var m = Lib.constrain( + valAxis.c2p(d.med, true), + Math.min(q1, q3) + 1, Math.max(q1, q3) - 1 + ); + + // for compatibility with box, violin, and candlestick + // perhaps we should put this into cd0.t instead so it's more explicit, + // but what we have now is: + // - box always has d.lf, but boxpoints can be anything + // - violin has d.lf and should always use it (boxpoints is undefined) + // - candlestick has only min/max + var useExtremes = (d.lf === undefined) || (trace.boxpoints === false); + var lf = valAxis.c2p(useExtremes ? d.min : d.lf, true); + var uf = valAxis.c2p(useExtremes ? d.max : d.uf, true); + var ln = valAxis.c2p(d.ln, true); + var un = valAxis.c2p(d.un, true); + + if(trace.orientation === 'h') { + d3.select(this).attr('d', + 'M' + m + ',' + posm0 + 'V' + posm1 + // median line + 'M' + q1 + ',' + pos0 + 'V' + pos1 + // left edge + (notched ? 'H' + ln + 'L' + m + ',' + posm1 + 'L' + un + ',' + pos1 : '') + // top notched edge + 'H' + q3 + // end of the top edge + 'V' + pos0 + // right edge + (notched ? 'H' + un + 'L' + m + ',' + posm0 + 'L' + ln + ',' + pos0 : '') + // bottom notched edge + 'Z' + // end of the box + 'M' + q1 + ',' + posc + 'H' + lf + 'M' + q3 + ',' + posc + 'H' + uf + // whiskers + ((whiskerWidth === 0) ? '' : // whisker caps + 'M' + lf + ',' + posw0 + 'V' + posw1 + 'M' + uf + ',' + posw0 + 'V' + posw1)); + } else { + d3.select(this).attr('d', + 'M' + posm0 + ',' + m + 'H' + posm1 + // median line + 'M' + pos0 + ',' + q1 + 'H' + pos1 + // top of the box + (notched ? 'V' + ln + 'L' + posm1 + ',' + m + 'L' + pos1 + ',' + un : '') + // notched right edge + 'V' + q3 + // end of the right edge + 'H' + pos0 + // bottom of the box + (notched ? 'V' + un + 'L' + posm0 + ',' + m + 'L' + pos0 + ',' + ln : '') + // notched left edge + 'Z' + // end of the box + 'M' + posc + ',' + q1 + 'V' + lf + 'M' + posc + ',' + q3 + 'V' + uf + // whiskers + ((whiskerWidth === 0) ? '' : // whisker caps + 'M' + posw0 + ',' + lf + 'H' + posw1 + 'M' + posw0 + ',' + uf + 'H' + posw1)); + } + }); +} + +function plotPoints(sel, axes, trace, t) { + var xa = axes.x; + var ya = axes.y; + var bdPos = t.bdPos; + var bPos = t.bPos; + + // to support violin points + var mode = trace.boxpoints || trace.points; + + // repeatable pseudo-random number generator + Lib.seedPseudoRandom(); + + // since box plot points get an extra level of nesting, each + // box needs the trace styling info + var fn = function(d) { + d.forEach(function(v) { + v.t = t; + v.trace = trace; + }); + return d; + }; + + var gPoints = sel.selectAll('g.points') + .data(mode ? fn : []); + + gPoints.enter().append('g') + .attr('class', 'points'); + + gPoints.exit().remove(); + + var paths = gPoints.selectAll('path') + .data(function(d) { + var i; + var pts = d.pts2; + + // normally use IQR, but if this is 0 or too small, use max-min + var typicalSpread = Math.max((d.max - d.min) / 10, d.q3 - d.q1); + var minSpread = typicalSpread * 1e-9; + var spreadLimit = typicalSpread * JITTERSPREAD; + var jitterFactors = []; + var maxJitterFactor = 0; + var newJitter; + + // dynamic jitter + if(trace.jitter) { + if(typicalSpread === 0) { + // edge case of no spread at all: fall back to max jitter + maxJitterFactor = 1; + jitterFactors = new Array(pts.length); + for(i = 0; i < pts.length; i++) { + jitterFactors[i] = 1; + } + } else { + for(i = 0; i < pts.length; i++) { + var i0 = Math.max(0, i - JITTERCOUNT); + var pmin = pts[i0].v; + var i1 = Math.min(pts.length - 1, i + JITTERCOUNT); + var pmax = pts[i1].v; + + if(mode !== 'all') { + if(pts[i].v < d.lf) pmax = Math.min(pmax, d.lf); + else pmin = Math.max(pmin, d.uf); + } + + var jitterFactor = Math.sqrt(spreadLimit * (i1 - i0) / (pmax - pmin + minSpread)) || 0; + jitterFactor = Lib.constrain(Math.abs(jitterFactor), 0, 1); + + jitterFactors.push(jitterFactor); + maxJitterFactor = Math.max(jitterFactor, maxJitterFactor); + } + } + newJitter = trace.jitter * 2 / (maxJitterFactor || 1); + } + + // fills in 'x' and 'y' in calcdata 'pts' item + for(i = 0; i < pts.length; i++) { + var pt = pts[i]; + var v = pt.v; + + var jitterOffset = trace.jitter ? + (newJitter * jitterFactors[i] * (Lib.pseudoRandom() - 0.5)) : + 0; + + var posPx = d.pos + bPos + bdPos * (trace.pointpos + jitterOffset); + + if(trace.orientation === 'h') { + pt.y = posPx; + pt.x = v; + } else { + pt.x = posPx; + pt.y = v; + } + + // tag suspected outliers + if(mode === 'suspectedoutliers' && v < d.uo && v > d.lo) { + pt.so = true; + } + } + + return pts; + }); + + paths.enter().append('path') + .classed('point', true); + + paths.exit().remove(); + + paths.call(Drawing.translatePoints, xa, ya); +} + +function plotBoxMean(sel, axes, trace, t) { + var posAxis = axes.pos; + var valAxis = axes.val; + var bPos = t.bPos; + var bPosPxOffset = t.bPosPxOffset || 0; + + // to support violin mean lines + var mode = trace.boxmean || (trace.meanline || {}).visible; + + // to support for one-sided box + var bdPos0; + var bdPos1; + if(Array.isArray(t.bdPos)) { + bdPos0 = t.bdPos[0]; + bdPos1 = t.bdPos[1]; + } else { + bdPos0 = t.bdPos; + bdPos1 = t.bdPos; + } + + var paths = sel.selectAll('path.mean').data(( + (trace.type === 'box' && trace.boxmean) || + (trace.type === 'violin' && trace.box.visible && trace.meanline.visible) + ) ? Lib.identity : []); + + paths.enter().append('path') + .attr('class', 'mean') + .style({ + fill: 'none', + 'vector-effect': 'non-scaling-stroke' + }); + + paths.exit().remove(); + + paths.each(function(d) { + var lcenter = posAxis.c2l(d.pos + bPos, true); + var posc = posAxis.l2p(lcenter) + bPosPxOffset; + var pos0 = posAxis.l2p(lcenter - bdPos0) + bPosPxOffset; + var pos1 = posAxis.l2p(lcenter + bdPos1) + bPosPxOffset; + var m = valAxis.c2p(d.mean, true); + var sl = valAxis.c2p(d.mean - d.sd, true); + var sh = valAxis.c2p(d.mean + d.sd, true); + + if(trace.orientation === 'h') { + d3.select(this).attr('d', + 'M' + m + ',' + pos0 + 'V' + pos1 + + (mode === 'sd' ? + 'm0,0L' + sl + ',' + posc + 'L' + m + ',' + pos0 + 'L' + sh + ',' + posc + 'Z' : + '') + ); + } else { + d3.select(this).attr('d', + 'M' + pos0 + ',' + m + 'H' + pos1 + + (mode === 'sd' ? + 'm0,0L' + posc + ',' + sl + 'L' + pos0 + ',' + m + 'L' + posc + ',' + sh + 'Z' : + '') + ); + } + }); +} + +module.exports = { + plot: plot, + plotBoxAndWhiskers: plotBoxAndWhiskers, + plotPoints: plotPoints, + plotBoxMean: plotBoxMean +}; + +},{"../../components/drawing":72,"../../lib":169,"d3":16}],293:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = function selectPoints(searchInfo, selectionTester) { + var cd = searchInfo.cd; + var xa = searchInfo.xaxis; + var ya = searchInfo.yaxis; + var selection = []; + var i, j; + + if(selectionTester === false) { + for(i = 0; i < cd.length; i++) { + for(j = 0; j < (cd[i].pts || []).length; j++) { + // clear selection + cd[i].pts[j].selected = 0; + } + } + } else { + for(i = 0; i < cd.length; i++) { + for(j = 0; j < (cd[i].pts || []).length; j++) { + var pt = cd[i].pts[j]; + var x = xa.c2p(pt.x); + var y = ya.c2p(pt.y); + + if(selectionTester.contains([x, y], null, pt.i, searchInfo)) { + selection.push({ + pointNumber: pt.i, + x: xa.c2d(pt.x), + y: ya.c2d(pt.y) + }); + pt.selected = 1; + } else { + pt.selected = 0; + } + } + } + } + + return selection; +}; + +},{}],294:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); +var Color = _dereq_('../../components/color'); +var Drawing = _dereq_('../../components/drawing'); + +function style(gd, cd, sel) { + var s = sel ? sel : d3.select(gd).selectAll('g.trace.boxes'); + + s.style('opacity', function(d) { return d[0].trace.opacity; }); + + s.each(function(d) { + var el = d3.select(this); + var trace = d[0].trace; + var lineWidth = trace.line.width; + + function styleBox(boxSel, lineWidth, lineColor, fillColor) { + boxSel.style('stroke-width', lineWidth + 'px') + .call(Color.stroke, lineColor) + .call(Color.fill, fillColor); + } + + var allBoxes = el.selectAll('path.box'); + + if(trace.type === 'candlestick') { + allBoxes.each(function(boxData) { + if(boxData.empty) return; + + var thisBox = d3.select(this); + var container = trace[boxData.dir]; // dir = 'increasing' or 'decreasing' + styleBox(thisBox, container.line.width, container.line.color, container.fillcolor); + // TODO: custom selection style for candlesticks + thisBox.style('opacity', trace.selectedpoints && !boxData.selected ? 0.3 : 1); + }); + } else { + styleBox(allBoxes, lineWidth, trace.line.color, trace.fillcolor); + el.selectAll('path.mean') + .style({ + 'stroke-width': lineWidth, + 'stroke-dasharray': (2 * lineWidth) + 'px,' + lineWidth + 'px' + }) + .call(Color.stroke, trace.line.color); + + var pts = el.selectAll('path.point'); + Drawing.pointStyle(pts, trace, gd); + } + }); +} + +function styleOnSelect(gd, cd, sel) { + var trace = cd[0].trace; + var pts = sel.selectAll('path.point'); + + if(trace.selectedpoints) { + Drawing.selectedPointStyle(pts, trace); + } else { + Drawing.pointStyle(pts, trace, gd); + } +} + +module.exports = { + style: style, + styleOnSelect: styleOnSelect +}; + +},{"../../components/color":51,"../../components/drawing":72,"d3":16}],295:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var heatmapAttrs = _dereq_('../heatmap/attributes'); +var scatterAttrs = _dereq_('../scatter/attributes'); +var colorScaleAttrs = _dereq_('../../components/colorscale/attributes'); +var dash = _dereq_('../../components/drawing/attributes').dash; +var fontAttrs = _dereq_('../../plots/font_attributes'); +var extendFlat = _dereq_('../../lib/extend').extendFlat; + +var filterOps = _dereq_('../../constants/filter_ops'); +var COMPARISON_OPS2 = filterOps.COMPARISON_OPS2; +var INTERVAL_OPS = filterOps.INTERVAL_OPS; + +var FORMAT_LINK = _dereq_('../../constants/docs').FORMAT_LINK; + +var scatterLineAttrs = scatterAttrs.line; + +module.exports = extendFlat({ + z: heatmapAttrs.z, + x: heatmapAttrs.x, + x0: heatmapAttrs.x0, + dx: heatmapAttrs.dx, + y: heatmapAttrs.y, + y0: heatmapAttrs.y0, + dy: heatmapAttrs.dy, + text: heatmapAttrs.text, + hovertext: heatmapAttrs.hovertext, + transpose: heatmapAttrs.transpose, + xtype: heatmapAttrs.xtype, + ytype: heatmapAttrs.ytype, + zhoverformat: heatmapAttrs.zhoverformat, + hovertemplate: heatmapAttrs.hovertemplate, + hoverongaps: heatmapAttrs.hoverongaps, + connectgaps: extendFlat({}, heatmapAttrs.connectgaps, { + + }), + + fillcolor: { + valType: 'color', + + editType: 'calc', + + }, + + autocontour: { + valType: 'boolean', + dflt: true, + + editType: 'calc', + impliedEdits: { + 'contours.start': undefined, + 'contours.end': undefined, + 'contours.size': undefined + }, + + }, + ncontours: { + valType: 'integer', + dflt: 15, + min: 1, + + editType: 'calc', + + }, + + contours: { + type: { + valType: 'enumerated', + values: ['levels', 'constraint'], + dflt: 'levels', + + editType: 'calc', + + }, + start: { + valType: 'number', + dflt: null, + + editType: 'plot', + impliedEdits: {'^autocontour': false}, + + }, + end: { + valType: 'number', + dflt: null, + + editType: 'plot', + impliedEdits: {'^autocontour': false}, + + }, + size: { + valType: 'number', + dflt: null, + min: 0, + + editType: 'plot', + impliedEdits: {'^autocontour': false}, + + }, + coloring: { + valType: 'enumerated', + values: ['fill', 'heatmap', 'lines', 'none'], + dflt: 'fill', + + editType: 'calc', + + }, + showlines: { + valType: 'boolean', + dflt: true, + + editType: 'plot', + + }, + showlabels: { + valType: 'boolean', + dflt: false, + + editType: 'plot', + + }, + labelfont: fontAttrs({ + editType: 'plot', + colorEditType: 'style', + + }), + labelformat: { + valType: 'string', + dflt: '', + + editType: 'plot', + + }, + operation: { + valType: 'enumerated', + values: [].concat(COMPARISON_OPS2).concat(INTERVAL_OPS), + + dflt: '=', + editType: 'calc', + + }, + value: { + valType: 'any', + dflt: 0, + + editType: 'calc', + + }, + editType: 'calc', + impliedEdits: {'autocontour': false} + }, + + line: { + color: extendFlat({}, scatterLineAttrs.color, { + editType: 'style+colorbars', + + }), + width: { + valType: 'number', + min: 0, + + editType: 'style+colorbars', + + }, + dash: dash, + smoothing: extendFlat({}, scatterLineAttrs.smoothing, { + + }), + editType: 'plot' + } +}, + colorScaleAttrs('', { + cLetter: 'z', + autoColorDflt: false, + editTypeOverride: 'calc' + }) +); + +},{"../../components/colorscale/attributes":58,"../../components/drawing/attributes":71,"../../constants/docs":146,"../../constants/filter_ops":147,"../../lib/extend":164,"../../plots/font_attributes":239,"../heatmap/attributes":317,"../scatter/attributes":376}],296:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Colorscale = _dereq_('../../components/colorscale'); + +var heatmapCalc = _dereq_('../heatmap/calc'); +var setContours = _dereq_('./set_contours'); +var endPlus = _dereq_('./end_plus'); + +// most is the same as heatmap calc, then adjust it +// though a few things inside heatmap calc still look for +// contour maps, because the makeBoundArray calls are too entangled +module.exports = function calc(gd, trace) { + var cd = heatmapCalc(gd, trace); + + var zOut = cd[0].z; + setContours(trace, zOut); + + var contours = trace.contours; + var cOpts = Colorscale.extractOpts(trace); + var cVals; + + if(contours.coloring === 'heatmap' && cOpts.auto && trace.autocontour === false) { + var start = contours.start; + var end = endPlus(contours); + var cs = contours.size || 1; + var nc = Math.floor((end - start) / cs) + 1; + + if(!isFinite(cs)) { + cs = 1; + nc = 1; + } + + var min0 = start - cs / 2; + var max0 = min0 + nc * cs; + cVals = [min0, max0]; + } else { + cVals = zOut; + } + + Colorscale.calc(gd, trace, {vals: cVals, cLetter: 'z'}); + + return cd; +}; + +},{"../../components/colorscale":63,"../heatmap/calc":318,"./end_plus":306,"./set_contours":314}],297:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = function(pathinfo, contours) { + var pi0 = pathinfo[0]; + var z = pi0.z; + var i; + + switch(contours.type) { + case 'levels': + // Why (just) use z[0][0] and z[0][1]? + // + // N.B. using boundaryMin instead of edgeVal2 here makes the + // `contour_scatter` mock fail + var edgeVal2 = Math.min(z[0][0], z[0][1]); + + for(i = 0; i < pathinfo.length; i++) { + var pi = pathinfo[i]; + pi.prefixBoundary = !pi.edgepaths.length && + (edgeVal2 > pi.level || pi.starts.length && edgeVal2 === pi.level); + } + break; + case 'constraint': + // after convertToConstraints, pathinfo has length=0 + pi0.prefixBoundary = false; + + // joinAllPaths does enough already when edgepaths are present + if(pi0.edgepaths.length) return; + + var na = pi0.x.length; + var nb = pi0.y.length; + var boundaryMax = -Infinity; + var boundaryMin = Infinity; + + for(i = 0; i < nb; i++) { + boundaryMin = Math.min(boundaryMin, z[i][0]); + boundaryMin = Math.min(boundaryMin, z[i][na - 1]); + boundaryMax = Math.max(boundaryMax, z[i][0]); + boundaryMax = Math.max(boundaryMax, z[i][na - 1]); + } + for(i = 1; i < na - 1; i++) { + boundaryMin = Math.min(boundaryMin, z[0][i]); + boundaryMin = Math.min(boundaryMin, z[nb - 1][i]); + boundaryMax = Math.max(boundaryMax, z[0][i]); + boundaryMax = Math.max(boundaryMax, z[nb - 1][i]); + } + + var contoursValue = contours.value; + var v1, v2; + + switch(contours._operation) { + case '>': + if(contoursValue > boundaryMax) { + pi0.prefixBoundary = true; + } + break; + case '<': + if(contoursValue < boundaryMin || + (pi0.starts.length && contoursValue === boundaryMin)) { + pi0.prefixBoundary = true; + } + break; + case '[]': + v1 = Math.min(contoursValue[0], contoursValue[1]); + v2 = Math.max(contoursValue[0], contoursValue[1]); + if(v2 < boundaryMin || v1 > boundaryMax || + (pi0.starts.length && v2 === boundaryMin)) { + pi0.prefixBoundary = true; + } + break; + case '][': + v1 = Math.min(contoursValue[0], contoursValue[1]); + v2 = Math.max(contoursValue[0], contoursValue[1]); + if(v1 < boundaryMin && v2 > boundaryMax) { + pi0.prefixBoundary = true; + } + break; + } + break; + } +}; + +},{}],298:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var extractOpts = _dereq_('../../components/colorscale').extractOpts; +var makeColorMap = _dereq_('./make_color_map'); +var endPlus = _dereq_('./end_plus'); + +function calc(gd, trace, opts) { + var contours = trace.contours; + var line = trace.line; + var cs = contours.size || 1; + var coloring = contours.coloring; + var colorMap = makeColorMap(trace, {isColorbar: true}); + + if(coloring === 'heatmap') { + var cOpts = extractOpts(trace); + opts._fillgradient = trace.colorscale; + opts._zrange = [cOpts.min, cOpts.max]; + } else if(coloring === 'fill') { + opts._fillcolor = colorMap; + } + + opts._line = { + color: coloring === 'lines' ? colorMap : line.color, + width: contours.showlines !== false ? line.width : 0, + dash: line.dash + }; + + opts._levels = { + start: contours.start, + end: endPlus(contours), + size: cs + }; +} + +module.exports = { + min: 'zmin', + max: 'zmax', + calc: calc +}; + +},{"../../components/colorscale":63,"./end_plus":306,"./make_color_map":311}],299:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; +module.exports = { + // some constants to help with marching squares algorithm + // where does the path start for each index? + BOTTOMSTART: [1, 9, 13, 104, 713], + TOPSTART: [4, 6, 7, 104, 713], + LEFTSTART: [8, 12, 14, 208, 1114], + RIGHTSTART: [2, 3, 11, 208, 1114], + + // which way [dx,dy] do we leave a given index? + // saddles are already disambiguated + NEWDELTA: [ + null, [-1, 0], [0, -1], [-1, 0], + [1, 0], null, [0, -1], [-1, 0], + [0, 1], [0, 1], null, [0, 1], + [1, 0], [1, 0], [0, -1] + ], + + // for each saddle, the first index here is used + // for dx||dy<0, the second for dx||dy>0 + CHOOSESADDLE: { + 104: [4, 1], + 208: [2, 8], + 713: [7, 13], + 1114: [11, 14] + }, + + // after one index has been used for a saddle, which do we + // substitute to be used up later? + SADDLEREMAINDER: {1: 4, 2: 8, 4: 1, 7: 13, 8: 2, 11: 14, 13: 7, 14: 11}, + + // length of a contour, as a multiple of the plot area diagonal, per label + LABELDISTANCE: 2, + + // number of contour levels after which we start increasing the number of + // labels we draw. Many contours means they will generally be close + // together, so it will be harder to follow a long way to find a label + LABELINCREASE: 10, + + // minimum length of a contour line, as a multiple of the label length, + // at which we draw *any* labels + LABELMIN: 3, + + // max number of labels to draw on a single contour path, no matter how long + LABELMAX: 10, + + // constants for the label position cost function + LABELOPTIMIZER: { + // weight given to edge proximity + EDGECOST: 1, + // weight given to the angle off horizontal + ANGLECOST: 1, + // weight given to distance from already-placed labels + NEIGHBORCOST: 5, + // cost multiplier for labels on the same level + SAMELEVELFACTOR: 10, + // minimum distance (as a multiple of the label length) + // for labels on the same level + SAMELEVELDISTANCE: 5, + // maximum cost before we won't even place the label + MAXCOST: 100, + // number of evenly spaced points to look at in the first + // iteration of the search + INITIALSEARCHPOINTS: 10, + // number of binary search iterations after the initial wide search + ITERATIONS: 5 + } +}; + +},{}],300:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; +var isNumeric = _dereq_('fast-isnumeric'); + +var handleLabelDefaults = _dereq_('./label_defaults'); + +var Color = _dereq_('../../components/color'); +var addOpacity = Color.addOpacity; +var opacity = Color.opacity; + +var filterOps = _dereq_('../../constants/filter_ops'); +var CONSTRAINT_REDUCTION = filterOps.CONSTRAINT_REDUCTION; +var COMPARISON_OPS2 = filterOps.COMPARISON_OPS2; + +module.exports = function handleConstraintDefaults(traceIn, traceOut, coerce, layout, defaultColor, opts) { + var contours = traceOut.contours; + var showLines, lineColor, fillColor; + + var operation = coerce('contours.operation'); + contours._operation = CONSTRAINT_REDUCTION[operation]; + + handleConstraintValueDefaults(coerce, contours); + + if(operation === '=') { + showLines = contours.showlines = true; + } else { + showLines = coerce('contours.showlines'); + fillColor = coerce('fillcolor', addOpacity( + (traceIn.line || {}).color || defaultColor, 0.5 + )); + } + + if(showLines) { + var lineDfltColor = fillColor && opacity(fillColor) ? + addOpacity(traceOut.fillcolor, 1) : + defaultColor; + lineColor = coerce('line.color', lineDfltColor); + coerce('line.width', 2); + coerce('line.dash'); + } + + coerce('line.smoothing'); + + handleLabelDefaults(coerce, layout, lineColor, opts); +}; + +function handleConstraintValueDefaults(coerce, contours) { + var zvalue; + + if(COMPARISON_OPS2.indexOf(contours.operation) === -1) { + // Requires an array of two numbers: + coerce('contours.value', [0, 1]); + + if(!Array.isArray(contours.value)) { + if(isNumeric(contours.value)) { + zvalue = parseFloat(contours.value); + contours.value = [zvalue, zvalue + 1]; + } + } else if(contours.value.length > 2) { + contours.value = contours.value.slice(2); + } else if(contours.length === 0) { + contours.value = [0, 1]; + } else if(contours.length < 2) { + zvalue = parseFloat(contours.value[0]); + contours.value = [zvalue, zvalue + 1]; + } else { + contours.value = [ + parseFloat(contours.value[0]), + parseFloat(contours.value[1]) + ]; + } + } else { + // Requires a single scalar: + coerce('contours.value', 0); + + if(!isNumeric(contours.value)) { + if(Array.isArray(contours.value)) { + contours.value = parseFloat(contours.value[0]); + } else { + contours.value = 0; + } + } + } +} + +},{"../../components/color":51,"../../constants/filter_ops":147,"./label_defaults":310,"fast-isnumeric":18}],301:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var filterOps = _dereq_('../../constants/filter_ops'); +var isNumeric = _dereq_('fast-isnumeric'); + +// This syntax conforms to the existing filter transform syntax, but we don't care +// about open vs. closed intervals for simply drawing contours constraints: +module.exports = { + '[]': makeRangeSettings('[]'), + '][': makeRangeSettings(']['), + '>': makeInequalitySettings('>'), + '<': makeInequalitySettings('<'), + '=': makeInequalitySettings('=') +}; + +// This does not in any way shape or form support calendars. It's adapted from +// transforms/filter.js. +function coerceValue(operation, value) { + var hasArrayValue = Array.isArray(value); + + var coercedValue; + + function coerce(value) { + return isNumeric(value) ? (+value) : null; + } + + if(filterOps.COMPARISON_OPS2.indexOf(operation) !== -1) { + coercedValue = hasArrayValue ? coerce(value[0]) : coerce(value); + } else if(filterOps.INTERVAL_OPS.indexOf(operation) !== -1) { + coercedValue = hasArrayValue ? + [coerce(value[0]), coerce(value[1])] : + [coerce(value), coerce(value)]; + } else if(filterOps.SET_OPS.indexOf(operation) !== -1) { + coercedValue = hasArrayValue ? value.map(coerce) : [coerce(value)]; + } + + return coercedValue; +} + +// Returns a parabola scaled so that the min/max is either +/- 1 and zero at the two values +// provided. The data is mapped by this function when constructing intervals so that it's +// very easy to construct contours as normal. +function makeRangeSettings(operation) { + return function(value) { + value = coerceValue(operation, value); + + // Ensure proper ordering: + var min = Math.min(value[0], value[1]); + var max = Math.max(value[0], value[1]); + + return { + start: min, + end: max, + size: max - min + }; + }; +} + +function makeInequalitySettings(operation) { + return function(value) { + value = coerceValue(operation, value); + + return { + start: value, + end: Infinity, + size: Infinity + }; + }; +} + +},{"../../constants/filter_ops":147,"fast-isnumeric":18}],302:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = function handleContourDefaults(traceIn, traceOut, coerce, coerce2) { + var contourStart = coerce2('contours.start'); + var contourEnd = coerce2('contours.end'); + var missingEnd = (contourStart === false) || (contourEnd === false); + + // normally we only need size if autocontour is off. But contour.calc + // pushes its calculated contour size back to the input trace, so for + // things like restyle that can call supplyDefaults without calc + // after the initial draw, we can just reuse the previous calculation + var contourSize = coerce('contours.size'); + var autoContour; + + if(missingEnd) autoContour = traceOut.autocontour = true; + else autoContour = coerce('autocontour', false); + + if(autoContour || !contourSize) coerce('ncontours'); +}; + +},{}],303:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); + +// The contour extraction is great, except it totally fails for constraints because we +// need weird range loops and flipped contours instead of the usual format. This function +// does some weird manipulation of the extracted pathinfo data such that it magically +// draws contours correctly *as* constraints. +// +// ** I do not know which "weird range loops" the comment above is referring to. +module.exports = function(pathinfo, operation) { + var i, pi0, pi1; + + var op0 = function(arr) { return arr.reverse(); }; + var op1 = function(arr) { return arr; }; + + switch(operation) { + case '=': + case '<': + return pathinfo; + case '>': + if(pathinfo.length !== 1) { + Lib.warn('Contour data invalid for the specified inequality operation.'); + } + + // In this case there should be exactly one contour levels in pathinfo. + // We flip all of the data. This will draw the contour as closed. + pi0 = pathinfo[0]; + + for(i = 0; i < pi0.edgepaths.length; i++) { + pi0.edgepaths[i] = op0(pi0.edgepaths[i]); + } + for(i = 0; i < pi0.paths.length; i++) { + pi0.paths[i] = op0(pi0.paths[i]); + } + for(i = 0; i < pi0.starts.length; i++) { + pi0.starts[i] = op0(pi0.starts[i]); + } + + return pathinfo; + case '][': + var tmp = op0; + op0 = op1; + op1 = tmp; + // It's a nice rule, except this definitely *is* what's intended here. + /* eslint-disable: no-fallthrough */ + case '[]': + /* eslint-enable: no-fallthrough */ + if(pathinfo.length !== 2) { + Lib.warn('Contour data invalid for the specified inequality range operation.'); + } + + // In this case there should be exactly two contour levels in pathinfo. + // - We concatenate the info into one pathinfo. + // - We must also flip all of the data in the `[]` case. + // This will draw the contours as closed. + pi0 = copyPathinfo(pathinfo[0]); + pi1 = copyPathinfo(pathinfo[1]); + + for(i = 0; i < pi0.edgepaths.length; i++) { + pi0.edgepaths[i] = op0(pi0.edgepaths[i]); + } + for(i = 0; i < pi0.paths.length; i++) { + pi0.paths[i] = op0(pi0.paths[i]); + } + for(i = 0; i < pi0.starts.length; i++) { + pi0.starts[i] = op0(pi0.starts[i]); + } + + while(pi1.edgepaths.length) { + pi0.edgepaths.push(op1(pi1.edgepaths.shift())); + } + while(pi1.paths.length) { + pi0.paths.push(op1(pi1.paths.shift())); + } + while(pi1.starts.length) { + pi0.starts.push(op1(pi1.starts.shift())); + } + + return [pi0]; + } +}; + +function copyPathinfo(pi) { + return Lib.extendFlat({}, pi, { + edgepaths: Lib.extendDeep([], pi.edgepaths), + paths: Lib.extendDeep([], pi.paths), + starts: Lib.extendDeep([], pi.starts) + }); +} + +},{"../../lib":169}],304:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); + +var handleXYZDefaults = _dereq_('../heatmap/xyz_defaults'); +var handleConstraintDefaults = _dereq_('./constraint_defaults'); +var handleContoursDefaults = _dereq_('./contours_defaults'); +var handleStyleDefaults = _dereq_('./style_defaults'); +var attributes = _dereq_('./attributes'); + + +module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { + function coerce(attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); + } + + function coerce2(attr) { + return Lib.coerce2(traceIn, traceOut, attributes, attr); + } + + var len = handleXYZDefaults(traceIn, traceOut, coerce, layout); + if(!len) { + traceOut.visible = false; + return; + } + + coerce('text'); + coerce('hovertext'); + coerce('hovertemplate'); + coerce('hoverongaps'); + + var isConstraint = (coerce('contours.type') === 'constraint'); + coerce('connectgaps', Lib.isArray1D(traceOut.z)); + + if(isConstraint) { + handleConstraintDefaults(traceIn, traceOut, coerce, layout, defaultColor); + } else { + handleContoursDefaults(traceIn, traceOut, coerce, coerce2); + handleStyleDefaults(traceIn, traceOut, coerce, layout); + } +}; + +},{"../../lib":169,"../heatmap/xyz_defaults":331,"./attributes":295,"./constraint_defaults":300,"./contours_defaults":302,"./style_defaults":316}],305:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var constraintMapping = _dereq_('./constraint_mapping'); +var endPlus = _dereq_('./end_plus'); + +module.exports = function emptyPathinfo(contours, plotinfo, cd0) { + var contoursFinal = (contours.type === 'constraint') ? + constraintMapping[contours._operation](contours.value) : + contours; + + var cs = contoursFinal.size; + var pathinfo = []; + var end = endPlus(contoursFinal); + + var carpet = cd0.trace._carpetTrace; + + var basePathinfo = carpet ? { + // store axes so we can convert to px + xaxis: carpet.aaxis, + yaxis: carpet.baxis, + // full data arrays to use for interpolation + x: cd0.a, + y: cd0.b + } : { + xaxis: plotinfo.xaxis, + yaxis: plotinfo.yaxis, + x: cd0.x, + y: cd0.y + }; + + for(var ci = contoursFinal.start; ci < end; ci += cs) { + pathinfo.push(Lib.extendFlat({ + level: ci, + // all the cells with nontrivial marching index + crossings: {}, + // starting points on the edges of the lattice for each contour + starts: [], + // all unclosed paths (may have less items than starts, + // if a path is closed by rounding) + edgepaths: [], + // all closed paths + paths: [], + z: cd0.z, + smoothing: cd0.trace.line.smoothing + }, basePathinfo)); + + if(pathinfo.length > 1000) { + Lib.warn('Too many contours, clipping at 1000', contours); + break; + } + } + return pathinfo; +}; + +},{"../../lib":169,"./constraint_mapping":301,"./end_plus":306}],306:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +/* + * tiny helper to move the end of the contours a little to prevent + * losing the last contour to rounding errors + */ +module.exports = function endPlus(contours) { + return contours.end + contours.size / 1e6; +}; + +},{}],307:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var constants = _dereq_('./constants'); + +module.exports = function findAllPaths(pathinfo, xtol, ytol) { + var cnt, + startLoc, + i, + pi, + j; + + // Default just passes these values through as they were before: + xtol = xtol || 0.01; + ytol = ytol || 0.01; + + for(i = 0; i < pathinfo.length; i++) { + pi = pathinfo[i]; + + for(j = 0; j < pi.starts.length; j++) { + startLoc = pi.starts[j]; + makePath(pi, startLoc, 'edge', xtol, ytol); + } + + cnt = 0; + while(Object.keys(pi.crossings).length && cnt < 10000) { + cnt++; + startLoc = Object.keys(pi.crossings)[0].split(',').map(Number); + makePath(pi, startLoc, undefined, xtol, ytol); + } + if(cnt === 10000) Lib.log('Infinite loop in contour?'); + } +}; + +function equalPts(pt1, pt2, xtol, ytol) { + return Math.abs(pt1[0] - pt2[0]) < xtol && + Math.abs(pt1[1] - pt2[1]) < ytol; +} + +// distance in index units - uses the 3rd and 4th items in points +function ptDist(pt1, pt2) { + var dx = pt1[2] - pt2[2]; + var dy = pt1[3] - pt2[3]; + return Math.sqrt(dx * dx + dy * dy); +} + +function makePath(pi, loc, edgeflag, xtol, ytol) { + var locStr = loc.join(','); + var mi = pi.crossings[locStr]; + var marchStep = getStartStep(mi, edgeflag, loc); + // start by going backward a half step and finding the crossing point + var pts = [getInterpPx(pi, loc, [-marchStep[0], -marchStep[1]])]; + var m = pi.z.length; + var n = pi.z[0].length; + var startLoc = loc.slice(); + var startStep = marchStep.slice(); + var cnt; + + // now follow the path + for(cnt = 0; cnt < 10000; cnt++) { // just to avoid infinite loops + if(mi > 20) { + mi = constants.CHOOSESADDLE[mi][(marchStep[0] || marchStep[1]) < 0 ? 0 : 1]; + pi.crossings[locStr] = constants.SADDLEREMAINDER[mi]; + } else { + delete pi.crossings[locStr]; + } + + marchStep = constants.NEWDELTA[mi]; + if(!marchStep) { + Lib.log('Found bad marching index:', mi, loc, pi.level); + break; + } + + // find the crossing a half step forward, and then take the full step + pts.push(getInterpPx(pi, loc, marchStep)); + loc[0] += marchStep[0]; + loc[1] += marchStep[1]; + locStr = loc.join(','); + + // don't include the same point multiple times + if(equalPts(pts[pts.length - 1], pts[pts.length - 2], xtol, ytol)) pts.pop(); + + var atEdge = (marchStep[0] && (loc[0] < 0 || loc[0] > n - 2)) || + (marchStep[1] && (loc[1] < 0 || loc[1] > m - 2)); + + var closedLoop = loc[0] === startLoc[0] && loc[1] === startLoc[1] && + marchStep[0] === startStep[0] && marchStep[1] === startStep[1]; + + // have we completed a loop, or reached an edge? + if((closedLoop) || (edgeflag && atEdge)) break; + + mi = pi.crossings[locStr]; + } + + if(cnt === 10000) { + Lib.log('Infinite loop in contour?'); + } + var closedpath = equalPts(pts[0], pts[pts.length - 1], xtol, ytol); + var totaldist = 0; + var distThresholdFactor = 0.2 * pi.smoothing; + var alldists = []; + var cropstart = 0; + var distgroup, cnt2, cnt3, newpt, ptcnt, ptavg, thisdist, + i, j, edgepathi, edgepathj; + + /* + * Check for points that are too close together (<1/5 the average dist + * *in grid index units* (important for log axes and nonuniform grids), + * less if less smoothed) and just take the center (or avg of center 2). + * This cuts down on funny behavior when a point is very close to a + * contour level. + */ + for(cnt = 1; cnt < pts.length; cnt++) { + thisdist = ptDist(pts[cnt], pts[cnt - 1]); + totaldist += thisdist; + alldists.push(thisdist); + } + + var distThreshold = totaldist / alldists.length * distThresholdFactor; + + function getpt(i) { return pts[i % pts.length]; } + + for(cnt = pts.length - 2; cnt >= cropstart; cnt--) { + distgroup = alldists[cnt]; + if(distgroup < distThreshold) { + cnt3 = 0; + for(cnt2 = cnt - 1; cnt2 >= cropstart; cnt2--) { + if(distgroup + alldists[cnt2] < distThreshold) { + distgroup += alldists[cnt2]; + } else break; + } + + // closed path with close points wrapping around the boundary? + if(closedpath && cnt === pts.length - 2) { + for(cnt3 = 0; cnt3 < cnt2; cnt3++) { + if(distgroup + alldists[cnt3] < distThreshold) { + distgroup += alldists[cnt3]; + } else break; + } + } + ptcnt = cnt - cnt2 + cnt3 + 1; + ptavg = Math.floor((cnt + cnt2 + cnt3 + 2) / 2); + + // either endpoint included: keep the endpoint + if(!closedpath && cnt === pts.length - 2) newpt = pts[pts.length - 1]; + else if(!closedpath && cnt2 === -1) newpt = pts[0]; + + // odd # of points - just take the central one + else if(ptcnt % 2) newpt = getpt(ptavg); + + // even # of pts - average central two + else { + newpt = [(getpt(ptavg)[0] + getpt(ptavg + 1)[0]) / 2, + (getpt(ptavg)[1] + getpt(ptavg + 1)[1]) / 2]; + } + + pts.splice(cnt2 + 1, cnt - cnt2 + 1, newpt); + cnt = cnt2 + 1; + if(cnt3) cropstart = cnt3; + if(closedpath) { + if(cnt === pts.length - 2) pts[cnt3] = pts[pts.length - 1]; + else if(cnt === 0) pts[pts.length - 1] = pts[0]; + } + } + } + pts.splice(0, cropstart); + + // done with the index parts - remove them so path generation works right + // because it depends on only having [xpx, ypx] + for(cnt = 0; cnt < pts.length; cnt++) pts[cnt].length = 2; + + // don't return single-point paths (ie all points were the same + // so they got deleted?) + if(pts.length < 2) return; + else if(closedpath) { + pts.pop(); + pi.paths.push(pts); + } else { + if(!edgeflag) { + Lib.log('Unclosed interior contour?', + pi.level, startLoc.join(','), pts.join('L')); + } + + // edge path - does it start where an existing edge path ends, or vice versa? + var merged = false; + for(i = 0; i < pi.edgepaths.length; i++) { + edgepathi = pi.edgepaths[i]; + if(!merged && equalPts(edgepathi[0], pts[pts.length - 1], xtol, ytol)) { + pts.pop(); + merged = true; + + // now does it ALSO meet the end of another (or the same) path? + var doublemerged = false; + for(j = 0; j < pi.edgepaths.length; j++) { + edgepathj = pi.edgepaths[j]; + if(equalPts(edgepathj[edgepathj.length - 1], pts[0], xtol, ytol)) { + doublemerged = true; + pts.shift(); + pi.edgepaths.splice(i, 1); + if(j === i) { + // the path is now closed + pi.paths.push(pts.concat(edgepathj)); + } else { + if(j > i) j--; + pi.edgepaths[j] = edgepathj.concat(pts, edgepathi); + } + break; + } + } + if(!doublemerged) { + pi.edgepaths[i] = pts.concat(edgepathi); + } + } + } + for(i = 0; i < pi.edgepaths.length; i++) { + if(merged) break; + edgepathi = pi.edgepaths[i]; + if(equalPts(edgepathi[edgepathi.length - 1], pts[0], xtol, ytol)) { + pts.shift(); + pi.edgepaths[i] = edgepathi.concat(pts); + merged = true; + } + } + + if(!merged) pi.edgepaths.push(pts); + } +} + +// special function to get the marching step of the +// first point in the path (leading to loc) +function getStartStep(mi, edgeflag, loc) { + var dx = 0; + var dy = 0; + if(mi > 20 && edgeflag) { + // these saddles start at +/- x + if(mi === 208 || mi === 1114) { + // if we're starting at the left side, we must be going right + dx = loc[0] === 0 ? 1 : -1; + } else { + // if we're starting at the bottom, we must be going up + dy = loc[1] === 0 ? 1 : -1; + } + } else if(constants.BOTTOMSTART.indexOf(mi) !== -1) dy = 1; + else if(constants.LEFTSTART.indexOf(mi) !== -1) dx = 1; + else if(constants.TOPSTART.indexOf(mi) !== -1) dy = -1; + else dx = -1; + return [dx, dy]; +} + +/* + * Find the pixel coordinates of a particular crossing + * + * @param {object} pi: the pathinfo object at this level + * @param {array} loc: the grid index [x, y] of the crossing + * @param {array} step: the direction [dx, dy] we're moving on the grid + * + * @return {array} [xpx, ypx, xi, yi]: the first two are the pixel location, + * the next two are the interpolated grid indices, which we use for + * distance calculations to delete points that are too close together. + * This is important when the grid is nonuniform (and most dramatically when + * we're on log axes and include invalid (0 or negative) values. + * It's crucial to delete these extra two before turning an array of these + * points into a path, because those routines require length-2 points. + */ +function getInterpPx(pi, loc, step) { + var locx = loc[0] + Math.max(step[0], 0); + var locy = loc[1] + Math.max(step[1], 0); + var zxy = pi.z[locy][locx]; + var xa = pi.xaxis; + var ya = pi.yaxis; + + if(step[1]) { + var dx = (pi.level - zxy) / (pi.z[locy][locx + 1] - zxy); + + return [xa.c2p((1 - dx) * pi.x[locx] + dx * pi.x[locx + 1], true), + ya.c2p(pi.y[locy], true), + locx + dx, locy]; + } else { + var dy = (pi.level - zxy) / (pi.z[locy + 1][locx] - zxy); + return [xa.c2p(pi.x[locx], true), + ya.c2p((1 - dy) * pi.y[locy] + dy * pi.y[locy + 1], true), + locx, locy + dy]; + } +} + +},{"../../lib":169,"./constants":299}],308:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Color = _dereq_('../../components/color'); + +var heatmapHoverPoints = _dereq_('../heatmap/hover'); + +module.exports = function hoverPoints(pointData, xval, yval, hovermode, hoverLayer) { + var hoverData = heatmapHoverPoints(pointData, xval, yval, hovermode, hoverLayer, true); + + if(hoverData) { + hoverData.forEach(function(hoverPt) { + var trace = hoverPt.trace; + if(trace.contours.type === 'constraint') { + if(trace.fillcolor && Color.opacity(trace.fillcolor)) { + hoverPt.color = Color.addOpacity(trace.fillcolor, 1); + } else if(trace.contours.showlines && Color.opacity(trace.line.color)) { + hoverPt.color = Color.addOpacity(trace.line.color, 1); + } + } + }); + } + + return hoverData; +}; + +},{"../../components/color":51,"../heatmap/hover":324}],309:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + attributes: _dereq_('./attributes'), + supplyDefaults: _dereq_('./defaults'), + calc: _dereq_('./calc'), + plot: _dereq_('./plot').plot, + style: _dereq_('./style'), + colorbar: _dereq_('./colorbar'), + hoverPoints: _dereq_('./hover'), + + moduleType: 'trace', + name: 'contour', + basePlotModule: _dereq_('../../plots/cartesian'), + categories: ['cartesian', 'svg', '2dMap', 'contour', 'showLegend'], + meta: { + + } +}; + +},{"../../plots/cartesian":224,"./attributes":295,"./calc":296,"./colorbar":298,"./defaults":304,"./hover":308,"./plot":313,"./style":315}],310:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Lib = _dereq_('../../lib'); + +module.exports = function handleLabelDefaults(coerce, layout, lineColor, opts) { + if(!opts) opts = {}; + var showLabels = coerce('contours.showlabels'); + if(showLabels) { + var globalFont = layout.font; + Lib.coerceFont(coerce, 'contours.labelfont', { + family: globalFont.family, + size: globalFont.size, + color: lineColor + }); + coerce('contours.labelformat'); + } + + if(opts.hasHover !== false) coerce('zhoverformat'); +}; + +},{"../../lib":169}],311:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); + +var Colorscale = _dereq_('../../components/colorscale'); +var endPlus = _dereq_('./end_plus'); + +module.exports = function makeColorMap(trace) { + var contours = trace.contours; + var start = contours.start; + var end = endPlus(contours); + var cs = contours.size || 1; + var nc = Math.floor((end - start) / cs) + 1; + var extra = contours.coloring === 'lines' ? 0 : 1; + var cOpts = Colorscale.extractOpts(trace); + + if(!isFinite(cs)) { + cs = 1; + nc = 1; + } + + var scl = cOpts.reversescale ? + Colorscale.flipScale(cOpts.colorscale) : + cOpts.colorscale; + + var len = scl.length; + var domain = new Array(len); + var range = new Array(len); + + var si, i; + + if(contours.coloring === 'heatmap') { + var zmin0 = cOpts.min; + var zmax0 = cOpts.max; + + for(i = 0; i < len; i++) { + si = scl[i]; + domain[i] = si[0] * (zmax0 - zmin0) + zmin0; + range[i] = si[1]; + } + + // do the contours extend beyond the colorscale? + // if so, extend the colorscale with constants + var zRange = d3.extent([ + zmin0, + zmax0, + contours.start, + contours.start + cs * (nc - 1) + ]); + var zmin = zRange[zmin0 < zmax0 ? 0 : 1]; + var zmax = zRange[zmin0 < zmax0 ? 1 : 0]; + + if(zmin !== zmin0) { + domain.splice(0, 0, zmin); + range.splice(0, 0, range[0]); + } + + if(zmax !== zmax0) { + domain.push(zmax); + range.push(range[range.length - 1]); + } + } else { + for(i = 0; i < len; i++) { + si = scl[i]; + domain[i] = (si[0] * (nc + extra - 1) - (extra / 2)) * cs + start; + range[i] = si[1]; + } + } + + return Colorscale.makeColorScaleFunc( + {domain: domain, range: range}, + {noNumericCheck: true} + ); +}; + +},{"../../components/colorscale":63,"./end_plus":306,"d3":16}],312:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var constants = _dereq_('./constants'); + +// Calculate all the marching indices, for ALL levels at once. +// since we want to be exhaustive we'll check for contour crossings +// at every intersection, rather than just following a path +// TODO: shorten the inner loop to only the relevant levels +module.exports = function makeCrossings(pathinfo) { + var z = pathinfo[0].z; + var m = z.length; + var n = z[0].length; // we already made sure z isn't ragged in interp2d + var twoWide = m === 2 || n === 2; + var xi; + var yi; + var startIndices; + var ystartIndices; + var label; + var corners; + var mi; + var pi; + var i; + + for(yi = 0; yi < m - 1; yi++) { + ystartIndices = []; + if(yi === 0) ystartIndices = ystartIndices.concat(constants.BOTTOMSTART); + if(yi === m - 2) ystartIndices = ystartIndices.concat(constants.TOPSTART); + + for(xi = 0; xi < n - 1; xi++) { + startIndices = ystartIndices.slice(); + if(xi === 0) startIndices = startIndices.concat(constants.LEFTSTART); + if(xi === n - 2) startIndices = startIndices.concat(constants.RIGHTSTART); + + label = xi + ',' + yi; + corners = [[z[yi][xi], z[yi][xi + 1]], + [z[yi + 1][xi], z[yi + 1][xi + 1]]]; + for(i = 0; i < pathinfo.length; i++) { + pi = pathinfo[i]; + mi = getMarchingIndex(pi.level, corners); + if(!mi) continue; + + pi.crossings[label] = mi; + if(startIndices.indexOf(mi) !== -1) { + pi.starts.push([xi, yi]); + if(twoWide && startIndices.indexOf(mi, + startIndices.indexOf(mi) + 1) !== -1) { + // the same square has starts from opposite sides + // it's not possible to have starts on opposite edges + // of a corner, only a start and an end... + // but if the array is only two points wide (either way) + // you can have starts on opposite sides. + pi.starts.push([xi, yi]); + } + } + } + } + } +}; + +// modified marching squares algorithm, +// so we disambiguate the saddle points from the start +// and we ignore the cases with no crossings +// the index I'm using is based on: +// http://en.wikipedia.org/wiki/Marching_squares +// except that the saddles bifurcate and I represent them +// as the decimal combination of the two appropriate +// non-saddle indices +function getMarchingIndex(val, corners) { + var mi = (corners[0][0] > val ? 0 : 1) + + (corners[0][1] > val ? 0 : 2) + + (corners[1][1] > val ? 0 : 4) + + (corners[1][0] > val ? 0 : 8); + if(mi === 5 || mi === 10) { + var avg = (corners[0][0] + corners[0][1] + + corners[1][0] + corners[1][1]) / 4; + // two peaks with a big valley + if(val > avg) return (mi === 5) ? 713 : 1114; + // two valleys with a big ridge + return (mi === 5) ? 104 : 208; + } + return (mi === 15) ? 0 : mi; +} + +},{"./constants":299}],313:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var d3 = _dereq_('d3'); + +var Lib = _dereq_('../../lib'); +var Drawing = _dereq_('../../components/drawing'); +var Colorscale = _dereq_('../../components/colorscale'); +var svgTextUtils = _dereq_('../../lib/svg_text_utils'); +var Axes = _dereq_('../../plots/cartesian/axes'); +var setConvert = _dereq_('../../plots/cartesian/set_convert'); + +var heatmapPlot = _dereq_('../heatmap/plot'); +var makeCrossings = _dereq_('./make_crossings'); +var findAllPaths = _dereq_('./find_all_paths'); +var emptyPathinfo = _dereq_('./empty_pathinfo'); +var convertToConstraints = _dereq_('./convert_to_constraints'); +var closeBoundaries = _dereq_('./close_boundaries'); +var constants = _dereq_('./constants'); +var costConstants = constants.LABELOPTIMIZER; + +exports.plot = function plot(gd, plotinfo, cdcontours, contourLayer) { + var xa = plotinfo.xaxis; + var ya = plotinfo.yaxis; + + Lib.makeTraceGroups(contourLayer, cdcontours, 'contour').each(function(cd) { + var plotGroup = d3.select(this); + var cd0 = cd[0]; + var trace = cd0.trace; + var x = cd0.x; + var y = cd0.y; + var contours = trace.contours; + var pathinfo = emptyPathinfo(contours, plotinfo, cd0); + + // use a heatmap to fill - draw it behind the lines + var heatmapColoringLayer = Lib.ensureSingle(plotGroup, 'g', 'heatmapcoloring'); + var cdheatmaps = []; + if(contours.coloring === 'heatmap') { + cdheatmaps = [cd]; + } + heatmapPlot(gd, plotinfo, cdheatmaps, heatmapColoringLayer); + + makeCrossings(pathinfo); + findAllPaths(pathinfo); + + var leftedge = xa.c2p(x[0], true); + var rightedge = xa.c2p(x[x.length - 1], true); + var bottomedge = ya.c2p(y[0], true); + var topedge = ya.c2p(y[y.length - 1], true); + var perimeter = [ + [leftedge, topedge], + [rightedge, topedge], + [rightedge, bottomedge], + [leftedge, bottomedge] + ]; + + var fillPathinfo = pathinfo; + if(contours.type === 'constraint') { + // N.B. this also mutates pathinfo + fillPathinfo = convertToConstraints(pathinfo, contours._operation); + } + + // draw everything + makeBackground(plotGroup, perimeter, contours); + makeFills(plotGroup, fillPathinfo, perimeter, contours); + makeLinesAndLabels(plotGroup, pathinfo, gd, cd0, contours); + clipGaps(plotGroup, plotinfo, gd, cd0, perimeter); + }); +}; + +function makeBackground(plotgroup, perimeter, contours) { + var bggroup = Lib.ensureSingle(plotgroup, 'g', 'contourbg'); + + var bgfill = bggroup.selectAll('path') + .data(contours.coloring === 'fill' ? [0] : []); + bgfill.enter().append('path'); + bgfill.exit().remove(); + bgfill + .attr('d', 'M' + perimeter.join('L') + 'Z') + .style('stroke', 'none'); +} + +function makeFills(plotgroup, pathinfo, perimeter, contours) { + var hasFills = contours.coloring === 'fill' || (contours.type === 'constraint' && contours._operation !== '='); + var boundaryPath = 'M' + perimeter.join('L') + 'Z'; + + // fills prefixBoundary in pathinfo items + if(hasFills) { + closeBoundaries(pathinfo, contours); + } + + var fillgroup = Lib.ensureSingle(plotgroup, 'g', 'contourfill'); + + var fillitems = fillgroup.selectAll('path').data(hasFills ? pathinfo : []); + fillitems.enter().append('path'); + fillitems.exit().remove(); + fillitems.each(function(pi) { + // join all paths for this level together into a single path + // first follow clockwise around the perimeter to close any open paths + // if the whole perimeter is above this level, start with a path + // enclosing the whole thing. With all that, the parity should mean + // that we always fill everything above the contour, nothing below + var fullpath = (pi.prefixBoundary ? boundaryPath : '') + + joinAllPaths(pi, perimeter); + + if(!fullpath) { + d3.select(this).remove(); + } else { + d3.select(this) + .attr('d', fullpath) + .style('stroke', 'none'); + } + }); +} + +function joinAllPaths(pi, perimeter) { + var fullpath = ''; + var i = 0; + var startsleft = pi.edgepaths.map(function(v, i) { return i; }); + var newloop = true; + var endpt; + var newendpt; + var cnt; + var nexti; + var possiblei; + var addpath; + + function istop(pt) { return Math.abs(pt[1] - perimeter[0][1]) < 0.01; } + function isbottom(pt) { return Math.abs(pt[1] - perimeter[2][1]) < 0.01; } + function isleft(pt) { return Math.abs(pt[0] - perimeter[0][0]) < 0.01; } + function isright(pt) { return Math.abs(pt[0] - perimeter[2][0]) < 0.01; } + + while(startsleft.length) { + addpath = Drawing.smoothopen(pi.edgepaths[i], pi.smoothing); + fullpath += newloop ? addpath : addpath.replace(/^M/, 'L'); + startsleft.splice(startsleft.indexOf(i), 1); + endpt = pi.edgepaths[i][pi.edgepaths[i].length - 1]; + nexti = -1; + + // now loop through sides, moving our endpoint until we find a new start + for(cnt = 0; cnt < 4; cnt++) { // just to prevent infinite loops + if(!endpt) { + Lib.log('Missing end?', i, pi); + break; + } + + if(istop(endpt) && !isright(endpt)) newendpt = perimeter[1]; // right top + else if(isleft(endpt)) newendpt = perimeter[0]; // left top + else if(isbottom(endpt)) newendpt = perimeter[3]; // right bottom + else if(isright(endpt)) newendpt = perimeter[2]; // left bottom + + for(possiblei = 0; possiblei < pi.edgepaths.length; possiblei++) { + var ptNew = pi.edgepaths[possiblei][0]; + // is ptNew on the (horz. or vert.) segment from endpt to newendpt? + if(Math.abs(endpt[0] - newendpt[0]) < 0.01) { + if(Math.abs(endpt[0] - ptNew[0]) < 0.01 && + (ptNew[1] - endpt[1]) * (newendpt[1] - ptNew[1]) >= 0) { + newendpt = ptNew; + nexti = possiblei; + } + } else if(Math.abs(endpt[1] - newendpt[1]) < 0.01) { + if(Math.abs(endpt[1] - ptNew[1]) < 0.01 && + (ptNew[0] - endpt[0]) * (newendpt[0] - ptNew[0]) >= 0) { + newendpt = ptNew; + nexti = possiblei; + } + } else { + Lib.log('endpt to newendpt is not vert. or horz.', + endpt, newendpt, ptNew); + } + } + + endpt = newendpt; + + if(nexti >= 0) break; + fullpath += 'L' + newendpt; + } + + if(nexti === pi.edgepaths.length) { + Lib.log('unclosed perimeter path'); + break; + } + + i = nexti; + + // if we closed back on a loop we already included, + // close it and start a new loop + newloop = (startsleft.indexOf(i) === -1); + if(newloop) { + i = startsleft[0]; + fullpath += 'Z'; + } + } + + // finally add the interior paths + for(i = 0; i < pi.paths.length; i++) { + fullpath += Drawing.smoothclosed(pi.paths[i], pi.smoothing); + } + + return fullpath; +} + +function makeLinesAndLabels(plotgroup, pathinfo, gd, cd0, contours) { + var lineContainer = Lib.ensureSingle(plotgroup, 'g', 'contourlines'); + var showLines = contours.showlines !== false; + var showLabels = contours.showlabels; + var clipLinesForLabels = showLines && showLabels; + + // Even if we're not going to show lines, we need to create them + // if we're showing labels, because the fill paths include the perimeter + // so can't be used to position the labels correctly. + // In this case we'll remove the lines after making the labels. + var linegroup = exports.createLines(lineContainer, showLines || showLabels, pathinfo); + + var lineClip = exports.createLineClip(lineContainer, clipLinesForLabels, gd, cd0.trace.uid); + + var labelGroup = plotgroup.selectAll('g.contourlabels') + .data(showLabels ? [0] : []); + + labelGroup.exit().remove(); + + labelGroup.enter().append('g') + .classed('contourlabels', true); + + if(showLabels) { + var labelClipPathData = []; + var labelData = []; + + // invalidate the getTextLocation cache in case paths changed + Lib.clearLocationCache(); + + var contourFormat = exports.labelFormatter(gd, cd0); + + var dummyText = Drawing.tester.append('text') + .attr('data-notex', 1) + .call(Drawing.font, contours.labelfont); + + var xa = pathinfo[0].xaxis; + var ya = pathinfo[0].yaxis; + var xLen = xa._length; + var yLen = ya._length; + var xRng = xa.range; + var yRng = ya.range; + var xMin = Lib.aggNums(Math.min, null, cd0.x); + var xMax = Lib.aggNums(Math.max, null, cd0.x); + var yMin = Lib.aggNums(Math.min, null, cd0.y); + var yMax = Lib.aggNums(Math.max, null, cd0.y); + var x0 = Math.max(xa.c2p(xMin, true), 0); + var x1 = Math.min(xa.c2p(xMax, true), xLen); + var y0 = Math.max(ya.c2p(yMax, true), 0); + var y1 = Math.min(ya.c2p(yMin, true), yLen); + + // visible bounds of the contour trace (and the midpoints, to + // help with cost calculations) + var bounds = {}; + + if(xRng[0] < xRng[1]) { + bounds.left = x0; + bounds.right = x1; + } else { + bounds.left = x1; + bounds.right = x0; + } + + if(yRng[0] < yRng[1]) { + bounds.top = y0; + bounds.bottom = y1; + } else { + bounds.top = y1; + bounds.bottom = y0; + } + + bounds.middle = (bounds.top + bounds.bottom) / 2; + bounds.center = (bounds.left + bounds.right) / 2; + + labelClipPathData.push([ + [bounds.left, bounds.top], + [bounds.right, bounds.top], + [bounds.right, bounds.bottom], + [bounds.left, bounds.bottom] + ]); + + var plotDiagonal = Math.sqrt(xLen * xLen + yLen * yLen); + + // the path length to use to scale the number of labels to draw: + var normLength = constants.LABELDISTANCE * plotDiagonal / + Math.max(1, pathinfo.length / constants.LABELINCREASE); + + linegroup.each(function(d) { + var textOpts = exports.calcTextOpts(d.level, contourFormat, dummyText, gd); + + d3.select(this).selectAll('path').each(function() { + var path = this; + var pathBounds = Lib.getVisibleSegment(path, bounds, textOpts.height / 2); + if(!pathBounds) return; + + if(pathBounds.len < (textOpts.width + textOpts.height) * constants.LABELMIN) return; + + var maxLabels = Math.min(Math.ceil(pathBounds.len / normLength), + constants.LABELMAX); + + for(var i = 0; i < maxLabels; i++) { + var loc = exports.findBestTextLocation(path, pathBounds, textOpts, + labelData, bounds); + + if(!loc) break; + + exports.addLabelData(loc, textOpts, labelData, labelClipPathData); + } + }); + }); + + dummyText.remove(); + + exports.drawLabels(labelGroup, labelData, gd, lineClip, + clipLinesForLabels ? labelClipPathData : null); + } + + if(showLabels && !showLines) linegroup.remove(); +} + +exports.createLines = function(lineContainer, makeLines, pathinfo) { + var smoothing = pathinfo[0].smoothing; + + var linegroup = lineContainer.selectAll('g.contourlevel') + .data(makeLines ? pathinfo : []); + + linegroup.exit().remove(); + linegroup.enter().append('g') + .classed('contourlevel', true); + + if(makeLines) { + // pedgepaths / ppaths are used by contourcarpet, for the paths transformed from a/b to x/y + // edgepaths / paths are used by contour since it's in x/y from the start + var opencontourlines = linegroup.selectAll('path.openline') + .data(function(d) { return d.pedgepaths || d.edgepaths; }); + + opencontourlines.exit().remove(); + opencontourlines.enter().append('path') + .classed('openline', true); + + opencontourlines + .attr('d', function(d) { + return Drawing.smoothopen(d, smoothing); + }) + .style('stroke-miterlimit', 1) + .style('vector-effect', 'non-scaling-stroke'); + + var closedcontourlines = linegroup.selectAll('path.closedline') + .data(function(d) { return d.ppaths || d.paths; }); + + closedcontourlines.exit().remove(); + closedcontourlines.enter().append('path') + .classed('closedline', true); + + closedcontourlines + .attr('d', function(d) { + return Drawing.smoothclosed(d, smoothing); + }) + .style('stroke-miterlimit', 1) + .style('vector-effect', 'non-scaling-stroke'); + } + + return linegroup; +}; + +exports.createLineClip = function(lineContainer, clipLinesForLabels, gd, uid) { + var clips = gd._fullLayout._clips; + var clipId = clipLinesForLabels ? ('clipline' + uid) : null; + + var lineClip = clips.selectAll('#' + clipId) + .data(clipLinesForLabels ? [0] : []); + lineClip.exit().remove(); + + lineClip.enter().append('clipPath') + .classed('contourlineclip', true) + .attr('id', clipId); + + Drawing.setClipUrl(lineContainer, clipId, gd); + + return lineClip; +}; + +exports.labelFormatter = function(gd, cd0) { + var fullLayout = gd._fullLayout; + var trace = cd0.trace; + var contours = trace.contours; + + if(contours.labelformat) { + return fullLayout._d3locale.numberFormat(contours.labelformat); + } else { + var formatAxis; + var cOpts = Colorscale.extractOpts(trace); + if(cOpts && cOpts.colorbar && cOpts.colorbar._axis) { + formatAxis = cOpts.colorbar._axis; + } else { + formatAxis = { + type: 'linear', + _id: 'ycontour', + showexponent: 'all', + exponentformat: 'B' + }; + + if(contours.type === 'constraint') { + var value = contours.value; + if(Array.isArray(value)) { + formatAxis.range = [value[0], value[value.length - 1]]; + } else formatAxis.range = [value, value]; + } else { + formatAxis.range = [contours.start, contours.end]; + formatAxis.nticks = (contours.end - contours.start) / contours.size; + } + + if(formatAxis.range[0] === formatAxis.range[1]) { + formatAxis.range[1] += formatAxis.range[0] || 1; + } + if(!formatAxis.nticks) formatAxis.nticks = 1000; + + setConvert(formatAxis, fullLayout); + Axes.prepTicks(formatAxis); + formatAxis._tmin = null; + formatAxis._tmax = null; + } + return function(v) { + return Axes.tickText(formatAxis, v).text; + }; + } +}; + +exports.calcTextOpts = function(level, contourFormat, dummyText, gd) { + var text = contourFormat(level); + dummyText.text(text) + .call(svgTextUtils.convertToTspans, gd); + var bBox = Drawing.bBox(dummyText.node(), true); + + return { + text: text, + width: bBox.width, + height: bBox.height, + level: level, + dy: (bBox.top + bBox.bottom) / 2 + }; +}; + +exports.findBestTextLocation = function(path, pathBounds, textOpts, labelData, plotBounds) { + var textWidth = textOpts.width; + + var p0, dp, pMax, pMin, loc; + if(pathBounds.isClosed) { + dp = pathBounds.len / costConstants.INITIALSEARCHPOINTS; + p0 = pathBounds.min + dp / 2; + pMax = pathBounds.max; + } else { + dp = (pathBounds.len - textWidth) / (costConstants.INITIALSEARCHPOINTS + 1); + p0 = pathBounds.min + dp + textWidth / 2; + pMax = pathBounds.max - (dp + textWidth) / 2; + } + + var cost = Infinity; + for(var j = 0; j < costConstants.ITERATIONS; j++) { + for(var p = p0; p < pMax; p += dp) { + var newLocation = Lib.getTextLocation(path, pathBounds.total, p, textWidth); + var newCost = locationCost(newLocation, textOpts, labelData, plotBounds); + if(newCost < cost) { + cost = newCost; + loc = newLocation; + pMin = p; + } + } + if(cost > costConstants.MAXCOST * 2) break; + + // subsequent iterations just look half steps away from the + // best we found in the previous iteration + if(j) dp /= 2; + p0 = pMin - dp / 2; + pMax = p0 + dp * 1.5; + } + if(cost <= costConstants.MAXCOST) return loc; +}; + +/* + * locationCost: a cost function for label locations + * composed of three kinds of penalty: + * - for open paths, being close to the end of the path + * - the angle away from horizontal + * - being too close to already placed neighbors + */ +function locationCost(loc, textOpts, labelData, bounds) { + var halfWidth = textOpts.width / 2; + var halfHeight = textOpts.height / 2; + var x = loc.x; + var y = loc.y; + var theta = loc.theta; + var dx = Math.cos(theta) * halfWidth; + var dy = Math.sin(theta) * halfWidth; + + // cost for being near an edge + var normX = ((x > bounds.center) ? (bounds.right - x) : (x - bounds.left)) / + (dx + Math.abs(Math.sin(theta) * halfHeight)); + var normY = ((y > bounds.middle) ? (bounds.bottom - y) : (y - bounds.top)) / + (Math.abs(dy) + Math.cos(theta) * halfHeight); + if(normX < 1 || normY < 1) return Infinity; + var cost = costConstants.EDGECOST * (1 / (normX - 1) + 1 / (normY - 1)); + + // cost for not being horizontal + cost += costConstants.ANGLECOST * theta * theta; + + // cost for being close to other labels + var x1 = x - dx; + var y1 = y - dy; + var x2 = x + dx; + var y2 = y + dy; + for(var i = 0; i < labelData.length; i++) { + var labeli = labelData[i]; + var dxd = Math.cos(labeli.theta) * labeli.width / 2; + var dyd = Math.sin(labeli.theta) * labeli.width / 2; + var dist = Lib.segmentDistance( + x1, y1, + x2, y2, + labeli.x - dxd, labeli.y - dyd, + labeli.x + dxd, labeli.y + dyd + ) * 2 / (textOpts.height + labeli.height); + + var sameLevel = labeli.level === textOpts.level; + var distOffset = sameLevel ? costConstants.SAMELEVELDISTANCE : 1; + + if(dist <= distOffset) return Infinity; + + var distFactor = costConstants.NEIGHBORCOST * + (sameLevel ? costConstants.SAMELEVELFACTOR : 1); + + cost += distFactor / (dist - distOffset); + } + + return cost; +} + +exports.addLabelData = function(loc, textOpts, labelData, labelClipPathData) { + var halfWidth = textOpts.width / 2; + var halfHeight = textOpts.height / 2; + + var x = loc.x; + var y = loc.y; + var theta = loc.theta; + + var sin = Math.sin(theta); + var cos = Math.cos(theta); + var dxw = halfWidth * cos; + var dxh = halfHeight * sin; + var dyw = halfWidth * sin; + var dyh = -halfHeight * cos; + var bBoxPts = [ + [x - dxw - dxh, y - dyw - dyh], + [x + dxw - dxh, y + dyw - dyh], + [x + dxw + dxh, y + dyw + dyh], + [x - dxw + dxh, y - dyw + dyh], + ]; + + labelData.push({ + text: textOpts.text, + x: x, + y: y, + dy: textOpts.dy, + theta: theta, + level: textOpts.level, + width: textOpts.width, + height: textOpts.height + }); + + labelClipPathData.push(bBoxPts); +}; + +exports.drawLabels = function(labelGroup, labelData, gd, lineClip, labelClipPathData) { + var labels = labelGroup.selectAll('text') + .data(labelData, function(d) { + return d.text + ',' + d.x + ',' + d.y + ',' + d.theta; + }); + + labels.exit().remove(); + + labels.enter().append('text') + .attr({ + 'data-notex': 1, + 'text-anchor': 'middle' + }) + .each(function(d) { + var x = d.x + Math.sin(d.theta) * d.dy; + var y = d.y - Math.cos(d.theta) * d.dy; + d3.select(this) + .text(d.text) + .attr({ + x: x, + y: y, + transform: 'rotate(' + (180 * d.theta / Math.PI) + ' ' + x + ' ' + y + ')' + }) + .call(svgTextUtils.convertToTspans, gd); + }); + + if(labelClipPathData) { + var clipPath = ''; + for(var i = 0; i < labelClipPathData.length; i++) { + clipPath += 'M' + labelClipPathData[i].join('L') + 'Z'; + } + + var lineClipPath = Lib.ensureSingle(lineClip, 'path', ''); + lineClipPath.attr('d', clipPath); + } +}; + +function clipGaps(plotGroup, plotinfo, gd, cd0, perimeter) { + var trace = cd0.trace; + var clips = gd._fullLayout._clips; + var clipId = 'clip' + trace.uid; + + var clipPath = clips.selectAll('#' + clipId) + .data(trace.connectgaps ? [] : [0]); + clipPath.enter().append('clipPath') + .classed('contourclip', true) + .attr('id', clipId); + clipPath.exit().remove(); + + if(trace.connectgaps === false) { + var clipPathInfo = { + // fraction of the way from missing to present point + // to draw the boundary. + // if you make this 1 (or 1-epsilon) then a point in + // a sea of missing data will disappear entirely. + level: 0.9, + crossings: {}, + starts: [], + edgepaths: [], + paths: [], + xaxis: plotinfo.xaxis, + yaxis: plotinfo.yaxis, + x: cd0.x, + y: cd0.y, + // 0 = no data, 1 = data + z: makeClipMask(cd0), + smoothing: 0 + }; + + makeCrossings([clipPathInfo]); + findAllPaths([clipPathInfo]); + closeBoundaries([clipPathInfo], {type: 'levels'}); + + var path = Lib.ensureSingle(clipPath, 'path', ''); + path.attr('d', + (clipPathInfo.prefixBoundary ? 'M' + perimeter.join('L') + 'Z' : '') + + joinAllPaths(clipPathInfo, perimeter) + ); + } else clipId = null; + + Drawing.setClipUrl(plotGroup, clipId, gd); +} + +function makeClipMask(cd0) { + var empties = cd0.trace._emptypoints; + var z = []; + var m = cd0.z.length; + var n = cd0.z[0].length; + var i; + var row = []; + var emptyPoint; + + for(i = 0; i < n; i++) row.push(1); + for(i = 0; i < m; i++) z.push(row.slice()); + for(i = 0; i < empties.length; i++) { + emptyPoint = empties[i]; + z[emptyPoint[0]][emptyPoint[1]] = 0; + } + // save this mask to determine whether to show this data in hover + cd0.zmask = z; + return z; +} + +},{"../../components/colorscale":63,"../../components/drawing":72,"../../lib":169,"../../lib/svg_text_utils":190,"../../plots/cartesian/axes":213,"../../plots/cartesian/set_convert":231,"../heatmap/plot":328,"./close_boundaries":297,"./constants":299,"./convert_to_constraints":303,"./empty_pathinfo":305,"./find_all_paths":307,"./make_crossings":312,"d3":16}],314:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Axes = _dereq_('../../plots/cartesian/axes'); +var Lib = _dereq_('../../lib'); + +module.exports = function setContours(trace, vals) { + var contours = trace.contours; + + // check if we need to auto-choose contour levels + if(trace.autocontour) { + // N.B. do not try to use coloraxis cmin/cmax, + // these values here are meant to remain "per-trace" for now + var zmin = trace.zmin; + var zmax = trace.zmax; + if(trace.zauto || zmin === undefined) { + zmin = Lib.aggNums(Math.min, null, vals); + } + if(trace.zauto || zmax === undefined) { + zmax = Lib.aggNums(Math.max, null, vals); + } + + var dummyAx = autoContours(zmin, zmax, trace.ncontours); + contours.size = dummyAx.dtick; + contours.start = Axes.tickFirst(dummyAx); + dummyAx.range.reverse(); + contours.end = Axes.tickFirst(dummyAx); + + if(contours.start === zmin) contours.start += contours.size; + if(contours.end === zmax) contours.end -= contours.size; + + // if you set a small ncontours, *and* the ends are exactly on zmin/zmax + // there's an edge case where start > end now. Make sure there's at least + // one meaningful contour, put it midway between the crossed values + if(contours.start > contours.end) { + contours.start = contours.end = (contours.start + contours.end) / 2; + } + + // copy auto-contour info back to the source data. + // previously we copied the whole contours object back, but that had + // other info (coloring, showlines) that should be left to supplyDefaults + if(!trace._input.contours) trace._input.contours = {}; + Lib.extendFlat(trace._input.contours, { + start: contours.start, + end: contours.end, + size: contours.size + }); + trace._input.autocontour = true; + } else if(contours.type !== 'constraint') { + // sanity checks on manually-supplied start/end/size + var start = contours.start; + var end = contours.end; + var inputContours = trace._input.contours; + + if(start > end) { + contours.start = inputContours.start = end; + end = contours.end = inputContours.end = start; + start = contours.start; + } + + if(!(contours.size > 0)) { + var sizeOut; + if(start === end) sizeOut = 1; + else sizeOut = autoContours(start, end, trace.ncontours).dtick; + + inputContours.size = contours.size = sizeOut; + } + } +}; + + +/* + * autoContours: make a dummy axis object with dtick we can use + * as contours.size, and if needed we can use Axes.tickFirst + * with this axis object to calculate the start and end too + * + * start: the value to start the contours at + * end: the value to end at (must be > start) + * ncontours: max number of contours to make, like roughDTick + * + * returns: an axis object + */ +function autoContours(start, end, ncontours) { + var dummyAx = { + type: 'linear', + range: [start, end] + }; + + Axes.autoTicks( + dummyAx, + (end - start) / (ncontours || 15) + ); + + return dummyAx; +} + +},{"../../lib":169,"../../plots/cartesian/axes":213}],315:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var d3 = _dereq_('d3'); + +var Drawing = _dereq_('../../components/drawing'); +var heatmapStyle = _dereq_('../heatmap/style'); + +var makeColorMap = _dereq_('./make_color_map'); + + +module.exports = function style(gd) { + var contours = d3.select(gd).selectAll('g.contour'); + + contours.style('opacity', function(d) { + return d[0].trace.opacity; + }); + + contours.each(function(d) { + var c = d3.select(this); + var trace = d[0].trace; + var contours = trace.contours; + var line = trace.line; + var cs = contours.size || 1; + var start = contours.start; + + // for contourcarpet only - is this a constraint-type contour trace? + var isConstraintType = contours.type === 'constraint'; + var colorLines = !isConstraintType && contours.coloring === 'lines'; + var colorFills = !isConstraintType && contours.coloring === 'fill'; + + var colorMap = (colorLines || colorFills) ? makeColorMap(trace) : null; + + c.selectAll('g.contourlevel').each(function(d) { + d3.select(this).selectAll('path') + .call(Drawing.lineGroupStyle, + line.width, + colorLines ? colorMap(d.level) : line.color, + line.dash); + }); + + var labelFont = contours.labelfont; + c.selectAll('g.contourlabels text').each(function(d) { + Drawing.font(d3.select(this), { + family: labelFont.family, + size: labelFont.size, + color: labelFont.color || (colorLines ? colorMap(d.level) : line.color) + }); + }); + + if(isConstraintType) { + c.selectAll('g.contourfill path') + .style('fill', trace.fillcolor); + } else if(colorFills) { + var firstFill; + + c.selectAll('g.contourfill path') + .style('fill', function(d) { + if(firstFill === undefined) firstFill = d.level; + return colorMap(d.level + 0.5 * cs); + }); + + if(firstFill === undefined) firstFill = start; + + c.selectAll('g.contourbg path') + .style('fill', colorMap(firstFill - 0.5 * cs)); + } + }); + + heatmapStyle(gd); +}; + +},{"../../components/drawing":72,"../heatmap/style":329,"./make_color_map":311,"d3":16}],316:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var colorscaleDefaults = _dereq_('../../components/colorscale/defaults'); +var handleLabelDefaults = _dereq_('./label_defaults'); + + +module.exports = function handleStyleDefaults(traceIn, traceOut, coerce, layout, opts) { + var coloring = coerce('contours.coloring'); + + var showLines; + var lineColor = ''; + if(coloring === 'fill') showLines = coerce('contours.showlines'); + + if(showLines !== false) { + if(coloring !== 'lines') lineColor = coerce('line.color', '#000'); + coerce('line.width', 0.5); + coerce('line.dash'); + } + + if(coloring !== 'none') { + // plots/plots always coerces showlegend to true, but in this case + // we default to false and (by default) show a colorbar instead + if(traceIn.showlegend !== true) traceOut.showlegend = false; + traceOut._dfltShowLegend = false; + + colorscaleDefaults( + traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'} + ); + } + + coerce('line.smoothing'); + + handleLabelDefaults(coerce, layout, lineColor, opts); +}; + +},{"../../components/colorscale/defaults":61,"./label_defaults":310}],317:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var scatterAttrs = _dereq_('../scatter/attributes'); +var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs; +var colorScaleAttrs = _dereq_('../../components/colorscale/attributes'); +var FORMAT_LINK = _dereq_('../../constants/docs').FORMAT_LINK; + +var extendFlat = _dereq_('../../lib/extend').extendFlat; + +module.exports = extendFlat({ + z: { + valType: 'data_array', + editType: 'calc', + + }, + x: extendFlat({}, scatterAttrs.x, {impliedEdits: {xtype: 'array'}}), + x0: extendFlat({}, scatterAttrs.x0, {impliedEdits: {xtype: 'scaled'}}), + dx: extendFlat({}, scatterAttrs.dx, {impliedEdits: {xtype: 'scaled'}}), + y: extendFlat({}, scatterAttrs.y, {impliedEdits: {ytype: 'array'}}), + y0: extendFlat({}, scatterAttrs.y0, {impliedEdits: {ytype: 'scaled'}}), + dy: extendFlat({}, scatterAttrs.dy, {impliedEdits: {ytype: 'scaled'}}), + + text: { + valType: 'data_array', + editType: 'calc', + + }, + hovertext: { + valType: 'data_array', + editType: 'calc', + + }, + transpose: { + valType: 'boolean', + dflt: false, + + editType: 'calc', + + }, + xtype: { + valType: 'enumerated', + values: ['array', 'scaled'], + + editType: 'calc+clearAxisTypes', + + }, + ytype: { + valType: 'enumerated', + values: ['array', 'scaled'], + + editType: 'calc+clearAxisTypes', + + }, + zsmooth: { + valType: 'enumerated', + values: ['fast', 'best', false], + dflt: false, + + editType: 'calc', + + }, + hoverongaps: { + valType: 'boolean', + dflt: true, + + editType: 'none', + + }, + connectgaps: { + valType: 'boolean', + + editType: 'calc', + + }, + xgap: { + valType: 'number', + dflt: 0, + min: 0, + + editType: 'plot', + + }, + ygap: { + valType: 'number', + dflt: 0, + min: 0, + + editType: 'plot', + + }, + zhoverformat: { + valType: 'string', + dflt: '', + + editType: 'none', + + }, + hovertemplate: hovertemplateAttrs() +}, { + transforms: undefined +}, + colorScaleAttrs('', {cLetter: 'z', autoColorDflt: false}) +); + +},{"../../components/colorscale/attributes":58,"../../constants/docs":146,"../../lib/extend":164,"../../plots/template_attributes":253,"../scatter/attributes":376}],318:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Registry = _dereq_('../../registry'); +var Lib = _dereq_('../../lib'); +var Axes = _dereq_('../../plots/cartesian/axes'); + +var histogram2dCalc = _dereq_('../histogram2d/calc'); +var colorscaleCalc = _dereq_('../../components/colorscale/calc'); +var convertColumnData = _dereq_('./convert_column_xyz'); +var clean2dArray = _dereq_('./clean_2d_array'); +var interp2d = _dereq_('./interp2d'); +var findEmpties = _dereq_('./find_empties'); +var makeBoundArray = _dereq_('./make_bound_array'); + +module.exports = function calc(gd, trace) { + // prepare the raw data + // run makeCalcdata on x and y even for heatmaps, in case of category mappings + var xa = Axes.getFromId(gd, trace.xaxis || 'x'); + var ya = Axes.getFromId(gd, trace.yaxis || 'y'); + var isContour = Registry.traceIs(trace, 'contour'); + var isHist = Registry.traceIs(trace, 'histogram'); + var isGL2D = Registry.traceIs(trace, 'gl2d'); + var zsmooth = isContour ? 'best' : trace.zsmooth; + var x; + var x0; + var dx; + var y; + var y0; + var dy; + var z; + var i; + var binned; + + // cancel minimum tick spacings (only applies to bars and boxes) + xa._minDtick = 0; + ya._minDtick = 0; + + if(isHist) { + binned = histogram2dCalc(gd, trace); + x = binned.x; + x0 = binned.x0; + dx = binned.dx; + y = binned.y; + y0 = binned.y0; + dy = binned.dy; + z = binned.z; + } else { + var zIn = trace.z; + if(Lib.isArray1D(zIn)) { + convertColumnData(trace, xa, ya, 'x', 'y', ['z']); + x = trace._x; + y = trace._y; + zIn = trace._z; + } else { + x = trace._x = trace.x ? xa.makeCalcdata(trace, 'x') : []; + y = trace._y = trace.y ? ya.makeCalcdata(trace, 'y') : []; + } + + x0 = trace.x0; + dx = trace.dx; + y0 = trace.y0; + dy = trace.dy; + + z = clean2dArray(zIn, trace, xa, ya); + + if(isContour || trace.connectgaps) { + trace._emptypoints = findEmpties(z); + interp2d(z, trace._emptypoints); + } + } + + function noZsmooth(msg) { + zsmooth = trace._input.zsmooth = trace.zsmooth = false; + Lib.warn('cannot use zsmooth: "fast": ' + msg); + } + + // check whether we really can smooth (ie all boxes are about the same size) + if(zsmooth === 'fast') { + if(xa.type === 'log' || ya.type === 'log') { + noZsmooth('log axis found'); + } else if(!isHist) { + if(x.length) { + var avgdx = (x[x.length - 1] - x[0]) / (x.length - 1); + var maxErrX = Math.abs(avgdx / 100); + for(i = 0; i < x.length - 1; i++) { + if(Math.abs(x[i + 1] - x[i] - avgdx) > maxErrX) { + noZsmooth('x scale is not linear'); + break; + } + } + } + if(y.length && zsmooth === 'fast') { + var avgdy = (y[y.length - 1] - y[0]) / (y.length - 1); + var maxErrY = Math.abs(avgdy / 100); + for(i = 0; i < y.length - 1; i++) { + if(Math.abs(y[i + 1] - y[i] - avgdy) > maxErrY) { + noZsmooth('y scale is not linear'); + break; + } + } + } + } + } + + // create arrays of brick boundaries, to be used by autorange and heatmap.plot + var xlen = Lib.maxRowLength(z); + var xIn = trace.xtype === 'scaled' ? '' : x; + var xArray = makeBoundArray(trace, xIn, x0, dx, xlen, xa); + var yIn = trace.ytype === 'scaled' ? '' : y; + var yArray = makeBoundArray(trace, yIn, y0, dy, z.length, ya); + + // handled in gl2d convert step + if(!isGL2D) { + trace._extremes[xa._id] = Axes.findExtremes(xa, xArray); + trace._extremes[ya._id] = Axes.findExtremes(ya, yArray); + } + + var cd0 = { + x: xArray, + y: yArray, + z: z, + text: trace._text || trace.text, + hovertext: trace._hovertext || trace.hovertext + }; + + if(xIn && xIn.length === xArray.length - 1) cd0.xCenter = xIn; + if(yIn && yIn.length === yArray.length - 1) cd0.yCenter = yIn; + + if(isHist) { + cd0.xRanges = binned.xRanges; + cd0.yRanges = binned.yRanges; + cd0.pts = binned.pts; + } + + if(!isContour) { + colorscaleCalc(gd, trace, {vals: z, cLetter: 'z'}); + } + + if(isContour && trace.contours && trace.contours.coloring === 'heatmap') { + var dummyTrace = { + type: trace.type === 'contour' ? 'heatmap' : 'histogram2d', + xcalendar: trace.xcalendar, + ycalendar: trace.ycalendar + }; + cd0.xfill = makeBoundArray(dummyTrace, xIn, x0, dx, xlen, xa); + cd0.yfill = makeBoundArray(dummyTrace, yIn, y0, dy, z.length, ya); + } + + return [cd0]; +}; + +},{"../../components/colorscale/calc":59,"../../lib":169,"../../plots/cartesian/axes":213,"../../registry":258,"../histogram2d/calc":346,"./clean_2d_array":319,"./convert_column_xyz":321,"./find_empties":323,"./interp2d":326,"./make_bound_array":327}],319:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); +var Lib = _dereq_('../../lib'); +var BADNUM = _dereq_('../../constants/numerical').BADNUM; + +module.exports = function clean2dArray(zOld, trace, xa, ya) { + var rowlen, collen, getCollen, old2new, i, j; + + function cleanZvalue(v) { + if(!isNumeric(v)) return undefined; + return +v; + } + + if(trace && trace.transpose) { + rowlen = 0; + for(i = 0; i < zOld.length; i++) rowlen = Math.max(rowlen, zOld[i].length); + if(rowlen === 0) return false; + getCollen = function(zOld) { return zOld.length; }; + old2new = function(zOld, i, j) { return (zOld[j] || [])[i]; }; + } else { + rowlen = zOld.length; + getCollen = function(zOld, i) { return zOld[i].length; }; + old2new = function(zOld, i, j) { return (zOld[i] || [])[j]; }; + } + + var padOld2new = function(zOld, i, j) { + if(i === BADNUM || j === BADNUM) return BADNUM; + return old2new(zOld, i, j); + }; + + function axisMapping(ax) { + if(trace && trace.type !== 'carpet' && trace.type !== 'contourcarpet' && + ax && ax.type === 'category' && trace['_' + ax._id.charAt(0)].length) { + var axLetter = ax._id.charAt(0); + var axMapping = {}; + var traceCategories = trace['_' + axLetter + 'CategoryMap'] || trace[axLetter]; + for(i = 0; i < traceCategories.length; i++) { + axMapping[traceCategories[i]] = i; + } + return function(i) { + var ind = axMapping[ax._categories[i]]; + return ind + 1 ? ind : BADNUM; + }; + } else { + return Lib.identity; + } + } + + var xMap = axisMapping(xa); + var yMap = axisMapping(ya); + + if(ya && ya.type === 'category') rowlen = ya._categories.length; + var zNew = new Array(rowlen); + + for(i = 0; i < rowlen; i++) { + if(xa && xa.type === 'category') { + collen = xa._categories.length; + } else { + collen = getCollen(zOld, i); + } + zNew[i] = new Array(collen); + for(j = 0; j < collen; j++) zNew[i][j] = cleanZvalue(padOld2new(zOld, yMap(i), xMap(j))); + } + + return zNew; +}; + +},{"../../constants/numerical":149,"../../lib":169,"fast-isnumeric":18}],320:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + min: 'zmin', + max: 'zmax' +}; + +},{}],321:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Lib = _dereq_('../../lib'); +var BADNUM = _dereq_('../../constants/numerical').BADNUM; + +module.exports = function convertColumnData(trace, ax1, ax2, var1Name, var2Name, arrayVarNames) { + var colLen = trace._length; + var col1 = ax1.makeCalcdata(trace, var1Name); + var col2 = ax2.makeCalcdata(trace, var2Name); + var textCol = trace.text; + var hasColumnText = (textCol !== undefined && Lib.isArray1D(textCol)); + var hoverTextCol = trace.hovertext; + var hasColumnHoverText = (hoverTextCol !== undefined && Lib.isArray1D(hoverTextCol)); + var i, j; + + var col1dv = Lib.distinctVals(col1); + var col1vals = col1dv.vals; + var col2dv = Lib.distinctVals(col2); + var col2vals = col2dv.vals; + var newArrays = []; + var text; + var hovertext; + + for(i = 0; i < arrayVarNames.length; i++) { + newArrays[i] = Lib.init2dArray(col2vals.length, col1vals.length); + } + + if(hasColumnText) { + text = Lib.init2dArray(col2vals.length, col1vals.length); + } + if(hasColumnHoverText) { + hovertext = Lib.init2dArray(col2vals.length, col1vals.length); + } + + for(i = 0; i < colLen; i++) { + if(col1[i] !== BADNUM && col2[i] !== BADNUM) { + var i1 = Lib.findBin(col1[i] + col1dv.minDiff / 2, col1vals); + var i2 = Lib.findBin(col2[i] + col2dv.minDiff / 2, col2vals); + + for(j = 0; j < arrayVarNames.length; j++) { + var arrayVarName = arrayVarNames[j]; + var arrayVar = trace[arrayVarName]; + var newArray = newArrays[j]; + newArray[i2][i1] = arrayVar[i]; + } + + if(hasColumnText) text[i2][i1] = textCol[i]; + if(hasColumnHoverText) hovertext[i2][i1] = hoverTextCol[i]; + } + } + + trace['_' + var1Name] = col1vals; + trace['_' + var2Name] = col2vals; + for(j = 0; j < arrayVarNames.length; j++) { + trace['_' + arrayVarNames[j]] = newArrays[j]; + } + if(hasColumnText) trace._text = text; + if(hasColumnHoverText) trace._hovertext = hovertext; + + if(ax1 && ax1.type === 'category') { + trace['_' + var1Name + 'CategoryMap'] = col1vals.map(function(v) { return ax1._categories[v];}); + } + + if(ax2 && ax2.type === 'category') { + trace['_' + var2Name + 'CategoryMap'] = col2vals.map(function(v) { return ax2._categories[v];}); + } +}; + +},{"../../constants/numerical":149,"../../lib":169}],322:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Lib = _dereq_('../../lib'); + +var handleXYZDefaults = _dereq_('./xyz_defaults'); +var handleStyleDefaults = _dereq_('./style_defaults'); +var colorscaleDefaults = _dereq_('../../components/colorscale/defaults'); +var attributes = _dereq_('./attributes'); + + +module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { + function coerce(attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); + } + + var validData = handleXYZDefaults(traceIn, traceOut, coerce, layout); + if(!validData) { + traceOut.visible = false; + return; + } + + coerce('text'); + coerce('hovertext'); + coerce('hovertemplate'); + + handleStyleDefaults(traceIn, traceOut, coerce, layout); + + coerce('hoverongaps'); + coerce('connectgaps', Lib.isArray1D(traceOut.z) && (traceOut.zsmooth !== false)); + + colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'}); +}; + +},{"../../components/colorscale/defaults":61,"../../lib":169,"./attributes":317,"./style_defaults":330,"./xyz_defaults":331}],323:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var maxRowLength = _dereq_('../../lib').maxRowLength; + +/* Return a list of empty points in 2D array z + * each empty point z[i][j] gives an array [i, j, neighborCount] + * neighborCount is the count of 4 nearest neighbors that DO exist + * this is to give us an order of points to evaluate for interpolation. + * if no neighbors exist, we iteratively look for neighbors that HAVE + * neighbors, and add a fractional neighborCount + */ +module.exports = function findEmpties(z) { + var empties = []; + var neighborHash = {}; + var noNeighborList = []; + var nextRow = z[0]; + var row = []; + var blank = [0, 0, 0]; + var rowLength = maxRowLength(z); + var prevRow; + var i; + var j; + var thisPt; + var p; + var neighborCount; + var newNeighborHash; + var foundNewNeighbors; + + for(i = 0; i < z.length; i++) { + prevRow = row; + row = nextRow; + nextRow = z[i + 1] || []; + for(j = 0; j < rowLength; j++) { + if(row[j] === undefined) { + neighborCount = (row[j - 1] !== undefined ? 1 : 0) + + (row[j + 1] !== undefined ? 1 : 0) + + (prevRow[j] !== undefined ? 1 : 0) + + (nextRow[j] !== undefined ? 1 : 0); + + if(neighborCount) { + // for this purpose, don't count off-the-edge points + // as undefined neighbors + if(i === 0) neighborCount++; + if(j === 0) neighborCount++; + if(i === z.length - 1) neighborCount++; + if(j === row.length - 1) neighborCount++; + + // if all neighbors that could exist do, we don't + // need this for finding farther neighbors + if(neighborCount < 4) { + neighborHash[[i, j]] = [i, j, neighborCount]; + } + + empties.push([i, j, neighborCount]); + } else noNeighborList.push([i, j]); + } + } + } + + while(noNeighborList.length) { + newNeighborHash = {}; + foundNewNeighbors = false; + + // look for cells that now have neighbors but didn't before + for(p = noNeighborList.length - 1; p >= 0; p--) { + thisPt = noNeighborList[p]; + i = thisPt[0]; + j = thisPt[1]; + + neighborCount = ((neighborHash[[i - 1, j]] || blank)[2] + + (neighborHash[[i + 1, j]] || blank)[2] + + (neighborHash[[i, j - 1]] || blank)[2] + + (neighborHash[[i, j + 1]] || blank)[2]) / 20; + + if(neighborCount) { + newNeighborHash[thisPt] = [i, j, neighborCount]; + noNeighborList.splice(p, 1); + foundNewNeighbors = true; + } + } + + if(!foundNewNeighbors) { + throw 'findEmpties iterated with no new neighbors'; + } + + // put these new cells into the main neighbor list + for(thisPt in newNeighborHash) { + neighborHash[thisPt] = newNeighborHash[thisPt]; + empties.push(newNeighborHash[thisPt]); + } + } + + // sort the full list in descending order of neighbor count + return empties.sort(function(a, b) { return b[2] - a[2]; }); +}; + +},{"../../lib":169}],324:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Fx = _dereq_('../../components/fx'); +var Lib = _dereq_('../../lib'); +var Axes = _dereq_('../../plots/cartesian/axes'); +var extractOpts = _dereq_('../../components/colorscale').extractOpts; + +module.exports = function hoverPoints(pointData, xval, yval, hovermode, hoverLayer, contour) { + var cd0 = pointData.cd[0]; + var trace = cd0.trace; + var xa = pointData.xa; + var ya = pointData.ya; + var x = cd0.x; + var y = cd0.y; + var z = cd0.z; + var xc = cd0.xCenter; + var yc = cd0.yCenter; + var zmask = cd0.zmask; + var zhoverformat = trace.zhoverformat; + var x2 = x; + var y2 = y; + + var xl, yl, nx, ny; + + if(pointData.index !== false) { + try { + nx = Math.round(pointData.index[1]); + ny = Math.round(pointData.index[0]); + } catch(e) { + Lib.error('Error hovering on heatmap, ' + + 'pointNumber must be [row,col], found:', pointData.index); + return; + } + if(nx < 0 || nx >= z[0].length || ny < 0 || ny > z.length) { + return; + } + } else if(Fx.inbox(xval - x[0], xval - x[x.length - 1], 0) > 0 || + Fx.inbox(yval - y[0], yval - y[y.length - 1], 0) > 0) { + return; + } else { + if(contour) { + var i2; + x2 = [2 * x[0] - x[1]]; + + for(i2 = 1; i2 < x.length; i2++) { + x2.push((x[i2] + x[i2 - 1]) / 2); + } + x2.push([2 * x[x.length - 1] - x[x.length - 2]]); + + y2 = [2 * y[0] - y[1]]; + for(i2 = 1; i2 < y.length; i2++) { + y2.push((y[i2] + y[i2 - 1]) / 2); + } + y2.push([2 * y[y.length - 1] - y[y.length - 2]]); + } + nx = Math.max(0, Math.min(x2.length - 2, Lib.findBin(xval, x2))); + ny = Math.max(0, Math.min(y2.length - 2, Lib.findBin(yval, y2))); + } + + var x0 = xa.c2p(x[nx]); + var x1 = xa.c2p(x[nx + 1]); + var y0 = ya.c2p(y[ny]); + var y1 = ya.c2p(y[ny + 1]); + + if(contour) { + x1 = x0; + xl = x[nx]; + y1 = y0; + yl = y[ny]; + } else { + xl = xc ? xc[nx] : ((x[nx] + x[nx + 1]) / 2); + yl = yc ? yc[ny] : ((y[ny] + y[ny + 1]) / 2); + + if(xa && xa.type === 'category') xl = x[nx]; + if(ya && ya.type === 'category') yl = y[ny]; + + if(trace.zsmooth) { + x0 = x1 = xa.c2p(xl); + y0 = y1 = ya.c2p(yl); + } + } + + var zVal = z[ny][nx]; + if(zmask && !zmask[ny][nx]) zVal = undefined; + + if(zVal === undefined && !trace.hoverongaps) return; + + var text; + if(Array.isArray(cd0.hovertext) && Array.isArray(cd0.hovertext[ny])) { + text = cd0.hovertext[ny][nx]; + } else if(Array.isArray(cd0.text) && Array.isArray(cd0.text[ny])) { + text = cd0.text[ny][nx]; + } + + // dummy axis for formatting the z value + var cOpts = extractOpts(trace); + var dummyAx = { + type: 'linear', + range: [cOpts.min, cOpts.max], + hoverformat: zhoverformat, + _separators: xa._separators, + _numFormat: xa._numFormat + }; + var zLabel = Axes.tickText(dummyAx, zVal, 'hover').text; + + return [Lib.extendFlat(pointData, { + index: [ny, nx], + // never let a 2D override 1D type as closest point + distance: pointData.maxHoverDistance, + spikeDistance: pointData.maxSpikeDistance, + x0: x0, + x1: x1, + y0: y0, + y1: y1, + xLabelVal: xl, + yLabelVal: yl, + zLabelVal: zVal, + zLabel: zLabel, + text: text + })]; +}; + +},{"../../components/colorscale":63,"../../components/fx":89,"../../lib":169,"../../plots/cartesian/axes":213}],325:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + attributes: _dereq_('./attributes'), + supplyDefaults: _dereq_('./defaults'), + calc: _dereq_('./calc'), + plot: _dereq_('./plot'), + colorbar: _dereq_('./colorbar'), + style: _dereq_('./style'), + hoverPoints: _dereq_('./hover'), + + moduleType: 'trace', + name: 'heatmap', + basePlotModule: _dereq_('../../plots/cartesian'), + categories: ['cartesian', 'svg', '2dMap'], + meta: { + + } +}; + +},{"../../plots/cartesian":224,"./attributes":317,"./calc":318,"./colorbar":320,"./defaults":322,"./hover":324,"./plot":328,"./style":329}],326:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); + +var INTERPTHRESHOLD = 1e-2; +var NEIGHBORSHIFTS = [[-1, 0], [1, 0], [0, -1], [0, 1]]; + +function correctionOvershoot(maxFractionalChange) { + // start with less overshoot, until we know it's converging, + // then ramp up the overshoot for faster convergence + return 0.5 - 0.25 * Math.min(1, maxFractionalChange * 0.5); +} + +/* + * interp2d: Fill in missing data from a 2D array using an iterative + * poisson equation solver with zero-derivative BC at edges. + * Amazingly, this just amounts to repeatedly averaging all the existing + * nearest neighbors, at least if we don't take x/y scaling into account, + * which is the right approach here where x and y may not even have the + * same units. + * + * @param {array of arrays} z + * The 2D array to fill in. Will be mutated here. Assumed to already be + * cleaned, so all entries are numbers except gaps, which are `undefined`. + * @param {array of arrays} emptyPoints + * Each entry [i, j, neighborCount] for empty points z[i][j] and the number + * of neighbors that are *not* missing. Assumed to be sorted from most to + * least neighbors, as produced by heatmap/find_empties. + */ +module.exports = function interp2d(z, emptyPoints) { + var maxFractionalChange = 1; + var i; + + // one pass to fill in a starting value for all the empties + iterateInterp2d(z, emptyPoints); + + // we're don't need to iterate lone empties - remove them + for(i = 0; i < emptyPoints.length; i++) { + if(emptyPoints[i][2] < 4) break; + } + // but don't remove these points from the original array, + // we'll use them for masking, so make a copy. + emptyPoints = emptyPoints.slice(i); + + for(i = 0; i < 100 && maxFractionalChange > INTERPTHRESHOLD; i++) { + maxFractionalChange = iterateInterp2d(z, emptyPoints, + correctionOvershoot(maxFractionalChange)); + } + if(maxFractionalChange > INTERPTHRESHOLD) { + Lib.log('interp2d didn\'t converge quickly', maxFractionalChange); + } + + return z; +}; + +function iterateInterp2d(z, emptyPoints, overshoot) { + var maxFractionalChange = 0; + var thisPt; + var i; + var j; + var p; + var q; + var neighborShift; + var neighborRow; + var neighborVal; + var neighborCount; + var neighborSum; + var initialVal; + var minNeighbor; + var maxNeighbor; + + for(p = 0; p < emptyPoints.length; p++) { + thisPt = emptyPoints[p]; + i = thisPt[0]; + j = thisPt[1]; + initialVal = z[i][j]; + neighborSum = 0; + neighborCount = 0; + + for(q = 0; q < 4; q++) { + neighborShift = NEIGHBORSHIFTS[q]; + neighborRow = z[i + neighborShift[0]]; + if(!neighborRow) continue; + neighborVal = neighborRow[j + neighborShift[1]]; + if(neighborVal !== undefined) { + if(neighborSum === 0) { + minNeighbor = maxNeighbor = neighborVal; + } else { + minNeighbor = Math.min(minNeighbor, neighborVal); + maxNeighbor = Math.max(maxNeighbor, neighborVal); + } + neighborCount++; + neighborSum += neighborVal; + } + } + + if(neighborCount === 0) { + throw 'iterateInterp2d order is wrong: no defined neighbors'; + } + + // this is the laplace equation interpolation: + // each point is just the average of its neighbors + // note that this ignores differential x/y scaling + // which I think is the right approach, since we + // don't know what that scaling means + z[i][j] = neighborSum / neighborCount; + + if(initialVal === undefined) { + if(neighborCount < 4) maxFractionalChange = 1; + } else { + // we can make large empty regions converge faster + // if we overshoot the change vs the previous value + z[i][j] = (1 + overshoot) * z[i][j] - overshoot * initialVal; + + if(maxNeighbor > minNeighbor) { + maxFractionalChange = Math.max(maxFractionalChange, + Math.abs(z[i][j] - initialVal) / (maxNeighbor - minNeighbor)); + } + } + } + + return maxFractionalChange; +} + +},{"../../lib":169}],327:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Registry = _dereq_('../../registry'); +var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray; + +module.exports = function makeBoundArray(trace, arrayIn, v0In, dvIn, numbricks, ax) { + var arrayOut = []; + var isContour = Registry.traceIs(trace, 'contour'); + var isHist = Registry.traceIs(trace, 'histogram'); + var isGL2D = Registry.traceIs(trace, 'gl2d'); + var v0; + var dv; + var i; + + var isArrayOfTwoItemsOrMore = isArrayOrTypedArray(arrayIn) && arrayIn.length > 1; + + if(isArrayOfTwoItemsOrMore && !isHist && (ax.type !== 'category')) { + var len = arrayIn.length; + + // given vals are brick centers + // hopefully length === numbricks, but use this method even if too few are supplied + // and extend it linearly based on the last two points + if(len <= numbricks) { + // contour plots only want the centers + if(isContour || isGL2D) arrayOut = arrayIn.slice(0, numbricks); + else if(numbricks === 1) { + arrayOut = [arrayIn[0] - 0.5, arrayIn[0] + 0.5]; + } else { + arrayOut = [1.5 * arrayIn[0] - 0.5 * arrayIn[1]]; + + for(i = 1; i < len; i++) { + arrayOut.push((arrayIn[i - 1] + arrayIn[i]) * 0.5); + } + + arrayOut.push(1.5 * arrayIn[len - 1] - 0.5 * arrayIn[len - 2]); + } + + if(len < numbricks) { + var lastPt = arrayOut[arrayOut.length - 1]; + var delta = lastPt - arrayOut[arrayOut.length - 2]; + + for(i = len; i < numbricks; i++) { + lastPt += delta; + arrayOut.push(lastPt); + } + } + } else { + // hopefully length === numbricks+1, but do something regardless: + // given vals are brick boundaries + return isContour ? + arrayIn.slice(0, numbricks) : // we must be strict for contours + arrayIn.slice(0, numbricks + 1); + } + } else { + var calendar = trace[ax._id.charAt(0) + 'calendar']; + + if(isHist) { + v0 = ax.r2c(v0In, 0, calendar); + } else { + if(isArrayOrTypedArray(arrayIn) && arrayIn.length === 1) { + v0 = arrayIn[0]; + } else if(v0In === undefined) { + v0 = 0; + } else { + var fn = ax.type === 'log' ? ax.d2c : ax.r2c; + v0 = fn(v0In, 0, calendar); + } + } + + dv = dvIn || 1; + + for(i = (isContour || isGL2D) ? 0 : -0.5; i < numbricks; i++) { + arrayOut.push(v0 + dv * i); + } + } + + return arrayOut; +}; + +},{"../../lib":169,"../../registry":258}],328:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var d3 = _dereq_('d3'); +var tinycolor = _dereq_('tinycolor2'); + +var Registry = _dereq_('../../registry'); +var Lib = _dereq_('../../lib'); +var makeColorScaleFuncFromTrace = _dereq_('../../components/colorscale').makeColorScaleFuncFromTrace; +var xmlnsNamespaces = _dereq_('../../constants/xmlns_namespaces'); + +module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) { + var xa = plotinfo.xaxis; + var ya = plotinfo.yaxis; + + Lib.makeTraceGroups(heatmapLayer, cdheatmaps, 'hm').each(function(cd) { + var plotGroup = d3.select(this); + var cd0 = cd[0]; + var trace = cd0.trace; + + var z = cd0.z; + var x = cd0.x; + var y = cd0.y; + var xc = cd0.xCenter; + var yc = cd0.yCenter; + var isContour = Registry.traceIs(trace, 'contour'); + var zsmooth = isContour ? 'best' : trace.zsmooth; + + // get z dims + var m = z.length; + var n = Lib.maxRowLength(z); + var xrev = false; + var yrev = false; + + var left, right, temp, top, bottom, i; + + // TODO: if there are multiple overlapping categorical heatmaps, + // or if we allow category sorting, then the categories may not be + // sequential... may need to reorder and/or expand z + + // Get edges of png in pixels (xa.c2p() maps axes coordinates to pixel coordinates) + // figure out if either axis is reversed (y is usually reversed, in pixel coords) + // also clip the image to maximum 50% outside the visible plot area + // bigger image lets you pan more naturally, but slows performance. + // TODO: use low-resolution images outside the visible plot for panning + // these while loops find the first and last brick bounds that are defined + // (in case of log of a negative) + i = 0; + while(left === undefined && i < x.length - 1) { + left = xa.c2p(x[i]); + i++; + } + i = x.length - 1; + while(right === undefined && i > 0) { + right = xa.c2p(x[i]); + i--; + } + + if(right < left) { + temp = right; + right = left; + left = temp; + xrev = true; + } + + i = 0; + while(top === undefined && i < y.length - 1) { + top = ya.c2p(y[i]); + i++; + } + i = y.length - 1; + while(bottom === undefined && i > 0) { + bottom = ya.c2p(y[i]); + i--; + } + + if(bottom < top) { + temp = top; + top = bottom; + bottom = temp; + yrev = true; + } + + // for contours with heatmap fill, we generate the boundaries based on + // brick centers but then use the brick edges for drawing the bricks + if(isContour) { + xc = x; + yc = y; + x = cd0.xfill; + y = cd0.yfill; + } + + // make an image that goes at most half a screen off either side, to keep + // time reasonable when you zoom in. if zsmooth is true/fast, don't worry + // about this, because zooming doesn't increase number of pixels + // if zsmooth is best, don't include anything off screen because it takes too long + if(zsmooth !== 'fast') { + var extra = zsmooth === 'best' ? 0 : 0.5; + left = Math.max(-extra * xa._length, left); + right = Math.min((1 + extra) * xa._length, right); + top = Math.max(-extra * ya._length, top); + bottom = Math.min((1 + extra) * ya._length, bottom); + } + + var imageWidth = Math.round(right - left); + var imageHeight = Math.round(bottom - top); + + // setup image nodes + + // if image is entirely off-screen, don't even draw it + var isOffScreen = (imageWidth <= 0 || imageHeight <= 0); + + if(isOffScreen) { + var noImage = plotGroup.selectAll('image').data([]); + noImage.exit().remove(); + return; + } + + // generate image data + + var canvasW, canvasH; + if(zsmooth === 'fast') { + canvasW = n; + canvasH = m; + } else { + canvasW = imageWidth; + canvasH = imageHeight; + } + + var canvas = document.createElement('canvas'); + canvas.width = canvasW; + canvas.height = canvasH; + var context = canvas.getContext('2d'); + + var sclFunc = makeColorScaleFuncFromTrace(trace, {noNumericCheck: true, returnArray: true}); + + // map brick boundaries to image pixels + var xpx, + ypx; + if(zsmooth === 'fast') { + xpx = xrev ? + function(index) { return n - 1 - index; } : + Lib.identity; + ypx = yrev ? + function(index) { return m - 1 - index; } : + Lib.identity; + } else { + xpx = function(index) { + return Lib.constrain(Math.round(xa.c2p(x[index]) - left), + 0, imageWidth); + }; + ypx = function(index) { + return Lib.constrain(Math.round(ya.c2p(y[index]) - top), + 0, imageHeight); + }; + } + + // build the pixel map brick-by-brick + // cruise through z-matrix row-by-row + // build a brick at each z-matrix value + var yi = ypx(0); + var yb = [yi, yi]; + var xbi = xrev ? 0 : 1; + var ybi = yrev ? 0 : 1; + // for collecting an average luminosity of the heatmap + var pixcount = 0; + var rcount = 0; + var gcount = 0; + var bcount = 0; + + var xb, j, xi, v, row, c; + + function setColor(v, pixsize) { + if(v !== undefined) { + var c = sclFunc(v); + c[0] = Math.round(c[0]); + c[1] = Math.round(c[1]); + c[2] = Math.round(c[2]); + + pixcount += pixsize; + rcount += c[0] * pixsize; + gcount += c[1] * pixsize; + bcount += c[2] * pixsize; + return c; + } + return [0, 0, 0, 0]; + } + + function interpColor(r0, r1, xinterp, yinterp) { + var z00 = r0[xinterp.bin0]; + if(z00 === undefined) return setColor(undefined, 1); + + var z01 = r0[xinterp.bin1]; + var z10 = r1[xinterp.bin0]; + var z11 = r1[xinterp.bin1]; + var dx = (z01 - z00) || 0; + var dy = (z10 - z00) || 0; + var dxy; + + // the bilinear interpolation term needs different calculations + // for all the different permutations of missing data + // among the neighbors of the main point, to ensure + // continuity across brick boundaries. + if(z01 === undefined) { + if(z11 === undefined) dxy = 0; + else if(z10 === undefined) dxy = 2 * (z11 - z00); + else dxy = (2 * z11 - z10 - z00) * 2 / 3; + } else if(z11 === undefined) { + if(z10 === undefined) dxy = 0; + else dxy = (2 * z00 - z01 - z10) * 2 / 3; + } else if(z10 === undefined) dxy = (2 * z11 - z01 - z00) * 2 / 3; + else dxy = (z11 + z00 - z01 - z10); + + return setColor(z00 + xinterp.frac * dx + yinterp.frac * (dy + xinterp.frac * dxy)); + } + + if(zsmooth) { // best or fast, works fastest with imageData + var pxIndex = 0; + var pixels; + + try { + pixels = new Uint8Array(imageWidth * imageHeight * 4); + } catch(e) { + pixels = new Array(imageWidth * imageHeight * 4); + } + + if(zsmooth === 'best') { + var xForPx = xc || x; + var yForPx = yc || y; + var xPixArray = new Array(xForPx.length); + var yPixArray = new Array(yForPx.length); + var xinterpArray = new Array(imageWidth); + var findInterpX = xc ? findInterpFromCenters : findInterp; + var findInterpY = yc ? findInterpFromCenters : findInterp; + var yinterp, r0, r1; + + // first make arrays of x and y pixel locations of brick boundaries + for(i = 0; i < xForPx.length; i++) xPixArray[i] = Math.round(xa.c2p(xForPx[i]) - left); + for(i = 0; i < yForPx.length; i++) yPixArray[i] = Math.round(ya.c2p(yForPx[i]) - top); + + // then make arrays of interpolations + // (bin0=closest, bin1=next, frac=fractional dist.) + for(i = 0; i < imageWidth; i++) xinterpArray[i] = findInterpX(i, xPixArray); + + // now do the interpolations and fill the png + for(j = 0; j < imageHeight; j++) { + yinterp = findInterpY(j, yPixArray); + r0 = z[yinterp.bin0]; + r1 = z[yinterp.bin1]; + for(i = 0; i < imageWidth; i++, pxIndex += 4) { + c = interpColor(r0, r1, xinterpArray[i], yinterp); + putColor(pixels, pxIndex, c); + } + } + } else { // zsmooth = fast + for(j = 0; j < m; j++) { + row = z[j]; + yb = ypx(j); + for(i = 0; i < imageWidth; i++) { + c = setColor(row[i], 1); + pxIndex = (yb * imageWidth + xpx(i)) * 4; + putColor(pixels, pxIndex, c); + } + } + } + + var imageData = context.createImageData(imageWidth, imageHeight); + try { + imageData.data.set(pixels); + } catch(e) { + var pxArray = imageData.data; + var dlen = pxArray.length; + for(j = 0; j < dlen; j ++) { + pxArray[j] = pixels[j]; + } + } + + context.putImageData(imageData, 0, 0); + } else { // zsmooth = false -> filling potentially large bricks works fastest with fillRect + // gaps do not need to be exact integers, but if they *are* we will get + // cleaner edges by rounding at least one edge + var xGap = trace.xgap; + var yGap = trace.ygap; + var xGapLeft = Math.floor(xGap / 2); + var yGapTop = Math.floor(yGap / 2); + + for(j = 0; j < m; j++) { + row = z[j]; + yb.reverse(); + yb[ybi] = ypx(j + 1); + if(yb[0] === yb[1] || yb[0] === undefined || yb[1] === undefined) { + continue; + } + xi = xpx(0); + xb = [xi, xi]; + for(i = 0; i < n; i++) { + // build one color brick! + xb.reverse(); + xb[xbi] = xpx(i + 1); + if(xb[0] === xb[1] || xb[0] === undefined || xb[1] === undefined) { + continue; + } + v = row[i]; + c = setColor(v, (xb[1] - xb[0]) * (yb[1] - yb[0])); + context.fillStyle = 'rgba(' + c.join(',') + ')'; + + context.fillRect(xb[0] + xGapLeft, yb[0] + yGapTop, + xb[1] - xb[0] - xGap, yb[1] - yb[0] - yGap); + } + } + } + + rcount = Math.round(rcount / pixcount); + gcount = Math.round(gcount / pixcount); + bcount = Math.round(bcount / pixcount); + var avgColor = tinycolor('rgb(' + rcount + ',' + gcount + ',' + bcount + ')'); + + gd._hmpixcount = (gd._hmpixcount||0) + pixcount; + gd._hmlumcount = (gd._hmlumcount||0) + pixcount * avgColor.getLuminance(); + + var image3 = plotGroup.selectAll('image') + .data(cd); + + image3.enter().append('svg:image').attr({ + xmlns: xmlnsNamespaces.svg, + preserveAspectRatio: 'none' + }); + + image3.attr({ + height: imageHeight, + width: imageWidth, + x: left, + y: top, + 'xlink:href': canvas.toDataURL('image/png') + }); + }); +}; + +// get interpolated bin value. Returns {bin0:closest bin, frac:fractional dist to next, bin1:next bin} +function findInterp(pixel, pixArray) { + var maxBin = pixArray.length - 2; + var bin = Lib.constrain(Lib.findBin(pixel, pixArray), 0, maxBin); + var pix0 = pixArray[bin]; + var pix1 = pixArray[bin + 1]; + var interp = Lib.constrain(bin + (pixel - pix0) / (pix1 - pix0) - 0.5, 0, maxBin); + var bin0 = Math.round(interp); + var frac = Math.abs(interp - bin0); + + if(!interp || interp === maxBin || !frac) { + return { + bin0: bin0, + bin1: bin0, + frac: 0 + }; + } + return { + bin0: bin0, + frac: frac, + bin1: Math.round(bin0 + frac / (interp - bin0)) + }; +} + +function findInterpFromCenters(pixel, centerPixArray) { + var maxBin = centerPixArray.length - 1; + var bin = Lib.constrain(Lib.findBin(pixel, centerPixArray), 0, maxBin); + var pix0 = centerPixArray[bin]; + var pix1 = centerPixArray[bin + 1]; + var frac = ((pixel - pix0) / (pix1 - pix0)) || 0; + if(frac <= 0) { + return { + bin0: bin, + bin1: bin, + frac: 0 + }; + } + if(frac < 0.5) { + return { + bin0: bin, + bin1: bin + 1, + frac: frac + }; + } + return { + bin0: bin + 1, + bin1: bin, + frac: 1 - frac + }; +} + +function putColor(pixels, pxIndex, c) { + pixels[pxIndex] = c[0]; + pixels[pxIndex + 1] = c[1]; + pixels[pxIndex + 2] = c[2]; + pixels[pxIndex + 3] = Math.round(c[3] * 255); +} + +},{"../../components/colorscale":63,"../../constants/xmlns_namespaces":150,"../../lib":169,"../../registry":258,"d3":16,"tinycolor2":34}],329:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var d3 = _dereq_('d3'); + +module.exports = function style(gd) { + d3.select(gd).selectAll('.hm image') + .style('opacity', function(d) { + return d.trace.opacity; + }); +}; + +},{"d3":16}],330:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +module.exports = function handleStyleDefaults(traceIn, traceOut, coerce) { + var zsmooth = coerce('zsmooth'); + if(zsmooth === false) { + // ensure that xgap and ygap are coerced only when zsmooth allows them to have an effect. + coerce('xgap'); + coerce('ygap'); + } + + coerce('zhoverformat'); +}; + +},{}],331:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); +var Lib = _dereq_('../../lib'); + +var Registry = _dereq_('../../registry'); + +module.exports = function handleXYZDefaults(traceIn, traceOut, coerce, layout, xName, yName) { + var z = coerce('z'); + xName = xName || 'x'; + yName = yName || 'y'; + var x, y; + + if(z === undefined || !z.length) return 0; + + if(Lib.isArray1D(traceIn.z)) { + x = coerce(xName); + y = coerce(yName); + + var xlen = Lib.minRowLength(x); + var ylen = Lib.minRowLength(y); + + // column z must be accompanied by xName and yName arrays + if(xlen === 0 || ylen === 0) return 0; + + traceOut._length = Math.min(xlen, ylen, z.length); + } else { + x = coordDefaults(xName, coerce); + y = coordDefaults(yName, coerce); + + // TODO put z validation elsewhere + if(!isValidZ(z)) return 0; + + coerce('transpose'); + + traceOut._length = null; + } + + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); + handleCalendarDefaults(traceIn, traceOut, [xName, yName], layout); + + return true; +}; + +function coordDefaults(coordStr, coerce) { + var coord = coerce(coordStr); + var coordType = coord ? coerce(coordStr + 'type', 'array') : 'scaled'; + + if(coordType === 'scaled') { + coerce(coordStr + '0'); + coerce('d' + coordStr); + } + + return coord; +} + +function isValidZ(z) { + var allRowsAreArrays = true; + var oneRowIsFilled = false; + var hasOneNumber = false; + var zi; + + /* + * Without this step: + * + * hasOneNumber = false breaks contour but not heatmap + * allRowsAreArrays = false breaks contour but not heatmap + * oneRowIsFilled = false breaks both + */ + + for(var i = 0; i < z.length; i++) { + zi = z[i]; + if(!Lib.isArrayOrTypedArray(zi)) { + allRowsAreArrays = false; + break; + } + if(zi.length > 0) oneRowIsFilled = true; + for(var j = 0; j < zi.length; j++) { + if(isNumeric(zi[j])) { + hasOneNumber = true; + break; + } + } + } + + return (allRowsAreArrays && oneRowIsFilled && hasOneNumber); +} + +},{"../../lib":169,"../../registry":258,"fast-isnumeric":18}],332:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var barAttrs = _dereq_('../bar/attributes'); +var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs; +var makeBinAttrs = _dereq_('./bin_attributes'); +var constants = _dereq_('./constants'); +var extendFlat = _dereq_('../../lib/extend').extendFlat; + +module.exports = { + x: { + valType: 'data_array', + editType: 'calc+clearAxisTypes', + + }, + y: { + valType: 'data_array', + editType: 'calc+clearAxisTypes', + + }, + + text: extendFlat({}, barAttrs.text, { + + }), + hovertext: extendFlat({}, barAttrs.hovertext, { + + }), + orientation: barAttrs.orientation, + + histfunc: { + valType: 'enumerated', + values: ['count', 'sum', 'avg', 'min', 'max'], + + dflt: 'count', + editType: 'calc', + + }, + histnorm: { + valType: 'enumerated', + values: ['', 'percent', 'probability', 'density', 'probability density'], + dflt: '', + + editType: 'calc', + + }, + + cumulative: { + enabled: { + valType: 'boolean', + dflt: false, + + editType: 'calc', + + }, + + direction: { + valType: 'enumerated', + values: ['increasing', 'decreasing'], + dflt: 'increasing', + + editType: 'calc', + + }, + + currentbin: { + valType: 'enumerated', + values: ['include', 'exclude', 'half'], + dflt: 'include', + + editType: 'calc', + + }, + editType: 'calc' + }, + nbinsx: { + valType: 'integer', + min: 0, + dflt: 0, + + editType: 'calc', + + }, + xbins: makeBinAttrs('x', true), + + nbinsy: { + valType: 'integer', + min: 0, + dflt: 0, + + editType: 'calc', + + }, + ybins: makeBinAttrs('y', true), + autobinx: { + valType: 'boolean', + dflt: null, + + editType: 'calc', + + }, + autobiny: { + valType: 'boolean', + dflt: null, + + editType: 'calc', + + }, + + bingroup: { + valType: 'string', + + dflt: '', + editType: 'calc', + + }, + + hovertemplate: hovertemplateAttrs({}, { + keys: constants.eventDataKeys + }), + + marker: barAttrs.marker, + + offsetgroup: barAttrs.offsetgroup, + alignmentgroup: barAttrs.alignmentgroup, + + selected: barAttrs.selected, + unselected: barAttrs.unselected, + + _deprecated: { + bardir: barAttrs._deprecated.bardir + } +}; + +},{"../../lib/extend":164,"../../plots/template_attributes":253,"../bar/attributes":268,"./bin_attributes":334,"./constants":338}],333:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + + +module.exports = function doAvg(size, counts) { + var nMax = size.length; + var total = 0; + for(var i = 0; i < nMax; i++) { + if(counts[i]) { + size[i] /= counts[i]; + total += size[i]; + } else size[i] = null; + } + return total; +}; + +},{}],334:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = function makeBinAttrs(axLetter, match) { + return { + start: { + valType: 'any', // for date axes + + editType: 'calc', + + }, + end: { + valType: 'any', // for date axes + + editType: 'calc', + + }, + size: { + valType: 'any', // for date axes + + editType: 'calc', + + }, + editType: 'calc' + }; +}; + +},{}],335:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); + + +module.exports = { + count: function(n, i, size) { + size[n]++; + return 1; + }, + + sum: function(n, i, size, counterData) { + var v = counterData[i]; + if(isNumeric(v)) { + v = Number(v); + size[n] += v; + return v; + } + return 0; + }, + + avg: function(n, i, size, counterData, counts) { + var v = counterData[i]; + if(isNumeric(v)) { + v = Number(v); + size[n] += v; + counts[n]++; + } + return 0; + }, + + min: function(n, i, size, counterData) { + var v = counterData[i]; + if(isNumeric(v)) { + v = Number(v); + if(!isNumeric(size[n])) { + size[n] = v; + return v; + } else if(size[n] > v) { + var delta = v - size[n]; + size[n] = v; + return delta; + } + } + return 0; + }, + + max: function(n, i, size, counterData) { + var v = counterData[i]; + if(isNumeric(v)) { + v = Number(v); + if(!isNumeric(size[n])) { + size[n] = v; + return v; + } else if(size[n] < v) { + var delta = v - size[n]; + size[n] = v; + return delta; + } + } + return 0; + } +}; + +},{"fast-isnumeric":18}],336:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var numConstants = _dereq_('../../constants/numerical'); +var oneYear = numConstants.ONEAVGYEAR; +var oneMonth = numConstants.ONEAVGMONTH; +var oneDay = numConstants.ONEDAY; +var oneHour = numConstants.ONEHOUR; +var oneMin = numConstants.ONEMIN; +var oneSec = numConstants.ONESEC; +var tickIncrement = _dereq_('../../plots/cartesian/axes').tickIncrement; + + +/* + * make a function that will find rounded bin edges + * @param {number} leftGap: how far from the left edge of any bin is the closest data value? + * @param {number} rightGap: how far from the right edge of any bin is the closest data value? + * @param {Array[number]} binEdges: the actual edge values used in binning + * @param {object} pa: the position axis + * @param {string} calendar: the data calendar + * + * @return {function(v, isRightEdge)}: + * find the start (isRightEdge is falsy) or end (truthy) label value for a bin edge `v` + */ +module.exports = function getBinSpanLabelRound(leftGap, rightGap, binEdges, pa, calendar) { + // the rounding digit is the largest digit that changes in *all* of 4 regions: + // - inside the rightGap before binEdges[0] (shifted 10% to the left) + // - inside the leftGap after binEdges[0] (expanded by 10% of rightGap on each end) + // - same for binEdges[1] + var dv0 = -1.1 * rightGap; + var dv1 = -0.1 * rightGap; + var dv2 = leftGap - dv1; + var edge0 = binEdges[0]; + var edge1 = binEdges[1]; + var leftDigit = Math.min( + biggestDigitChanged(edge0 + dv1, edge0 + dv2, pa, calendar), + biggestDigitChanged(edge1 + dv1, edge1 + dv2, pa, calendar) + ); + var rightDigit = Math.min( + biggestDigitChanged(edge0 + dv0, edge0 + dv1, pa, calendar), + biggestDigitChanged(edge1 + dv0, edge1 + dv1, pa, calendar) + ); + + // normally we try to make the label for the right edge different from + // the left edge label, so it's unambiguous which bin gets data on the edge. + // but if this results in more than 3 extra digits (or for dates, more than + // 2 fields ie hr&min or min&sec, which is 3600x), it'll be more clutter than + // useful so keep the label cleaner instead + var digit, disambiguateEdges; + if(leftDigit > rightDigit && rightDigit < Math.abs(edge1 - edge0) / 4000) { + digit = leftDigit; + disambiguateEdges = false; + } else { + digit = Math.min(leftDigit, rightDigit); + disambiguateEdges = true; + } + + if(pa.type === 'date' && digit > oneDay) { + var dashExclude = (digit === oneYear) ? 1 : 6; + var increment = (digit === oneYear) ? 'M12' : 'M1'; + + return function(v, isRightEdge) { + var dateStr = pa.c2d(v, oneYear, calendar); + var dashPos = dateStr.indexOf('-', dashExclude); + if(dashPos > 0) dateStr = dateStr.substr(0, dashPos); + var roundedV = pa.d2c(dateStr, 0, calendar); + + if(roundedV < v) { + var nextV = tickIncrement(roundedV, increment, false, calendar); + if((roundedV + nextV) / 2 < v + leftGap) roundedV = nextV; + } + + if(isRightEdge && disambiguateEdges) { + return tickIncrement(roundedV, increment, true, calendar); + } + + return roundedV; + }; + } + + return function(v, isRightEdge) { + var roundedV = digit * Math.round(v / digit); + // if we rounded down and we could round up and still be < leftGap + // (or what leftGap values round to), do that + if(roundedV + (digit / 10) < v && roundedV + (digit * 0.9) < v + leftGap) { + roundedV += digit; + } + // finally for the right edge back off one digit - but only if we can do that + // and not clip off any data that's potentially in the bin + if(isRightEdge && disambiguateEdges) { + roundedV -= digit; + } + return roundedV; + }; +}; + +/* + * Find the largest digit that changes within a (calcdata) region [v1, v2] + * if dates, "digit" means date/time part when it's bigger than a second + * returns the unit value to round to this digit, eg 0.01 to round to hundredths, or + * 100 to round to hundreds. returns oneMonth or oneYear for month or year rounding, + * so that Math.min will work, rather than 'M1' and 'M12' + */ +function biggestDigitChanged(v1, v2, pa, calendar) { + // are we crossing zero? can't say anything. + // in principle this doesn't apply to dates but turns out this doesn't matter. + if(v1 * v2 <= 0) return Infinity; + + var dv = Math.abs(v2 - v1); + var isDate = pa.type === 'date'; + var digit = biggestGuaranteedDigitChanged(dv, isDate); + // see if a larger digit also changed + for(var i = 0; i < 10; i++) { + // numbers: next digit needs to be >10x but <100x then gets rounded down. + // dates: next digit can be as much as 60x (then rounded down) + var nextDigit = biggestGuaranteedDigitChanged(digit * 80, isDate); + // if we get to years, the chain stops + if(digit === nextDigit) break; + if(didDigitChange(nextDigit, v1, v2, isDate, pa, calendar)) digit = nextDigit; + else break; + } + return digit; +} + +/* + * Find the largest digit that *definitely* changes in a region [v, v + dv] for any v + * for nonuniform date regions (months/years) pick the largest + */ +function biggestGuaranteedDigitChanged(dv, isDate) { + if(isDate && dv > oneSec) { + // this is supposed to be the biggest *guaranteed* change + // so compare to the longest month and year across any calendar, + // and we'll iterate back up later + // note: does not support rounding larger than one year. We could add + // that if anyone wants it, but seems unusual and not strictly necessary. + if(dv > oneDay) { + if(dv > oneYear * 1.1) return oneYear; + if(dv > oneMonth * 1.1) return oneMonth; + return oneDay; + } + + if(dv > oneHour) return oneHour; + if(dv > oneMin) return oneMin; + return oneSec; + } + return Math.pow(10, Math.floor(Math.log(dv) / Math.LN10)); +} + +function didDigitChange(digit, v1, v2, isDate, pa, calendar) { + if(isDate && digit > oneDay) { + var dateParts1 = dateParts(v1, pa, calendar); + var dateParts2 = dateParts(v2, pa, calendar); + var parti = (digit === oneYear) ? 0 : 1; + return dateParts1[parti] !== dateParts2[parti]; + } + return Math.floor(v2 / digit) - Math.floor(v1 / digit) > 0.1; +} + +function dateParts(v, pa, calendar) { + var parts = pa.c2d(v, oneYear, calendar).split('-'); + if(parts[0] === '') { + parts.unshift(); + parts[0] = '-' + parts[0]; + } + return parts; +} + +},{"../../constants/numerical":149,"../../plots/cartesian/axes":213}],337:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); + +var Lib = _dereq_('../../lib'); +var Registry = _dereq_('../../registry'); +var Axes = _dereq_('../../plots/cartesian/axes'); + +var arraysToCalcdata = _dereq_('../bar/arrays_to_calcdata'); +var binFunctions = _dereq_('./bin_functions'); +var normFunctions = _dereq_('./norm_functions'); +var doAvg = _dereq_('./average'); +var getBinSpanLabelRound = _dereq_('./bin_label_vals'); + +function calc(gd, trace) { + var pos = []; + var size = []; + var pa = Axes.getFromId(gd, trace.orientation === 'h' ? trace.yaxis : trace.xaxis); + var mainData = trace.orientation === 'h' ? 'y' : 'x'; + var counterData = {x: 'y', y: 'x'}[mainData]; + var calendar = trace[mainData + 'calendar']; + var cumulativeSpec = trace.cumulative; + var i; + + var binsAndPos = calcAllAutoBins(gd, trace, pa, mainData); + var binSpec = binsAndPos[0]; + var pos0 = binsAndPos[1]; + + var nonuniformBins = typeof binSpec.size === 'string'; + var binEdges = []; + var bins = nonuniformBins ? binEdges : binSpec; + // make the empty bin array + var inc = []; + var counts = []; + var inputPoints = []; + var total = 0; + var norm = trace.histnorm; + var func = trace.histfunc; + var densityNorm = norm.indexOf('density') !== -1; + var i2, binEnd, n; + + if(cumulativeSpec.enabled && densityNorm) { + // we treat "cumulative" like it means "integral" if you use a density norm, + // which in the end means it's the same as without "density" + norm = norm.replace(/ ?density$/, ''); + densityNorm = false; + } + + var extremeFunc = func === 'max' || func === 'min'; + var sizeInit = extremeFunc ? null : 0; + var binFunc = binFunctions.count; + var normFunc = normFunctions[norm]; + var isAvg = false; + var pr2c = function(v) { return pa.r2c(v, 0, calendar); }; + var rawCounterData; + + if(Lib.isArrayOrTypedArray(trace[counterData]) && func !== 'count') { + rawCounterData = trace[counterData]; + isAvg = func === 'avg'; + binFunc = binFunctions[func]; + } + + // create the bins (and any extra arrays needed) + // assume more than 1e6 bins is an error, so we don't crash the browser + i = pr2c(binSpec.start); + + // decrease end a little in case of rounding errors + binEnd = pr2c(binSpec.end) + (i - Axes.tickIncrement(i, binSpec.size, false, calendar)) / 1e6; + + while(i < binEnd && pos.length < 1e6) { + i2 = Axes.tickIncrement(i, binSpec.size, false, calendar); + pos.push((i + i2) / 2); + size.push(sizeInit); + inputPoints.push([]); + // nonuniform bins (like months) we need to search, + // rather than straight calculate the bin we're in + binEdges.push(i); + // nonuniform bins also need nonuniform normalization factors + if(densityNorm) inc.push(1 / (i2 - i)); + if(isAvg) counts.push(0); + // break to avoid infinite loops + if(i2 <= i) break; + i = i2; + } + binEdges.push(i); + + // for date axes we need bin bounds to be calcdata. For nonuniform bins + // we already have this, but uniform with start/end/size they're still strings. + if(!nonuniformBins && pa.type === 'date') { + bins = { + start: pr2c(bins.start), + end: pr2c(bins.end), + size: bins.size + }; + } + + // bin the data + // and make histogram-specific pt-number-to-cd-index map object + var nMax = size.length; + var uniqueValsPerBin = true; + var leftGap = Infinity; + var rightGap = Infinity; + var ptNumber2cdIndex = {}; + for(i = 0; i < pos0.length; i++) { + var posi = pos0[i]; + n = Lib.findBin(posi, bins); + if(n >= 0 && n < nMax) { + total += binFunc(n, i, size, rawCounterData, counts); + if(uniqueValsPerBin && inputPoints[n].length && posi !== pos0[inputPoints[n][0]]) { + uniqueValsPerBin = false; + } + inputPoints[n].push(i); + ptNumber2cdIndex[i] = n; + + leftGap = Math.min(leftGap, posi - binEdges[n]); + rightGap = Math.min(rightGap, binEdges[n + 1] - posi); + } + } + + var roundFn; + if(!uniqueValsPerBin) { + roundFn = getBinSpanLabelRound(leftGap, rightGap, binEdges, pa, calendar); + } + + // average and/or normalize the data, if needed + if(isAvg) total = doAvg(size, counts); + if(normFunc) normFunc(size, total, inc); + + // after all normalization etc, now we can accumulate if desired + if(cumulativeSpec.enabled) cdf(size, cumulativeSpec.direction, cumulativeSpec.currentbin); + + var seriesLen = Math.min(pos.length, size.length); + var cd = []; + var firstNonzero = 0; + var lastNonzero = seriesLen - 1; + + // look for empty bins at the ends to remove, so autoscale omits them + for(i = 0; i < seriesLen; i++) { + if(size[i]) { + firstNonzero = i; + break; + } + } + for(i = seriesLen - 1; i >= firstNonzero; i--) { + if(size[i]) { + lastNonzero = i; + break; + } + } + + // create the "calculated data" to plot + for(i = firstNonzero; i <= lastNonzero; i++) { + if((isNumeric(pos[i]) && isNumeric(size[i]))) { + var cdi = { + p: pos[i], + s: size[i], + b: 0 + }; + + // setup hover and event data fields, + // N.B. pts and "hover" positions ph0/ph1 don't seem to make much sense + // for cumulative distributions + if(!cumulativeSpec.enabled) { + cdi.pts = inputPoints[i]; + if(uniqueValsPerBin) { + cdi.ph0 = cdi.ph1 = (inputPoints[i].length) ? pos0[inputPoints[i][0]] : pos[i]; + } else { + cdi.ph0 = roundFn(binEdges[i]); + cdi.ph1 = roundFn(binEdges[i + 1], true); + } + } + cd.push(cdi); + } + } + + if(cd.length === 1) { + // when we collapse to a single bin, calcdata no longer describes bin size + // so we need to explicitly specify it + cd[0].width1 = Axes.tickIncrement(cd[0].p, binSpec.size, false, calendar) - cd[0].p; + } + + arraysToCalcdata(cd, trace); + + if(Lib.isArrayOrTypedArray(trace.selectedpoints)) { + Lib.tagSelected(cd, trace, ptNumber2cdIndex); + } + + return cd; +} + +/* + * calcAllAutoBins: we want all histograms inside the same bingroup + * (see logic in Histogram.crossTraceDefaults) to share bin specs + * + * If the user has explicitly specified differing + * bin specs, there's nothing we can do, but if possible we will try to use the + * smallest bins of any of the auto values for all histograms inside the same + * bingroup. + */ +function calcAllAutoBins(gd, trace, pa, mainData, _overlayEdgeCase) { + var binAttr = mainData + 'bins'; + var fullLayout = gd._fullLayout; + var groupName = trace['_' + mainData + 'bingroup']; + var binOpts = fullLayout._histogramBinOpts[groupName]; + var isOverlay = fullLayout.barmode === 'overlay'; + var i, traces, tracei, calendar, pos0, autoVals, cumulativeSpec; + + var r2c = function(v) { return pa.r2c(v, 0, calendar); }; + var c2r = function(v) { return pa.c2r(v, 0, calendar); }; + + var cleanBound = pa.type === 'date' ? + function(v) { return (v || v === 0) ? Lib.cleanDate(v, null, calendar) : null; } : + function(v) { return isNumeric(v) ? Number(v) : null; }; + + function setBound(attr, bins, newBins) { + if(bins[attr + 'Found']) { + bins[attr] = cleanBound(bins[attr]); + if(bins[attr] === null) bins[attr] = newBins[attr]; + } else { + autoVals[attr] = bins[attr] = newBins[attr]; + Lib.nestedProperty(traces[0], binAttr + '.' + attr).set(newBins[attr]); + } + } + + // all but the first trace in this group has already been marked finished + // clear this flag, so next time we run calc we will run autobin again + if(trace['_' + mainData + 'autoBinFinished']) { + delete trace['_' + mainData + 'autoBinFinished']; + } else { + traces = binOpts.traces; + var allPos = []; + + // Note: we're including `legendonly` traces here for autobin purposes, + // so that showing & hiding from the legend won't affect bins. + // But this complicates things a bit since those traces don't `calc`, + // hence `isFirstVisible`. + var isFirstVisible = true; + var has2dMap = false; + var hasHist2dContour = false; + for(i = 0; i < traces.length; i++) { + tracei = traces[i]; + + if(tracei.visible) { + var mainDatai = binOpts.dirs[i]; + pos0 = tracei['_' + mainDatai + 'pos0'] = pa.makeCalcdata(tracei, mainDatai); + + allPos = Lib.concat(allPos, pos0); + delete tracei['_' + mainData + 'autoBinFinished']; + + if(trace.visible === true) { + if(isFirstVisible) { + isFirstVisible = false; + } else { + delete tracei._autoBin; + tracei['_' + mainData + 'autoBinFinished'] = 1; + } + if(Registry.traceIs(tracei, '2dMap')) { + has2dMap = true; + } + if(tracei.type === 'histogram2dcontour') { + hasHist2dContour = true; + } + } + } + } + + calendar = traces[0][mainData + 'calendar']; + var newBinSpec = Axes.autoBin(allPos, pa, binOpts.nbins, has2dMap, calendar, binOpts.sizeFound && binOpts.size); + + var autoBin = traces[0]._autoBin = {}; + autoVals = autoBin[binOpts.dirs[0]] = {}; + + if(hasHist2dContour) { + // the "true" 2nd argument reverses the tick direction (which we can't + // just do with a minus sign because of month bins) + if(!binOpts.size) { + newBinSpec.start = c2r(Axes.tickIncrement( + r2c(newBinSpec.start), newBinSpec.size, true, calendar)); + } + if(binOpts.end === undefined) { + newBinSpec.end = c2r(Axes.tickIncrement( + r2c(newBinSpec.end), newBinSpec.size, false, calendar)); + } + } + + // Edge case: single-valued histogram overlaying others + // Use them all together to calculate the bin size for the single-valued one + if(isOverlay && !Registry.traceIs(trace, '2dMap') && newBinSpec._dataSpan === 0 && + pa.type !== 'category' && pa.type !== 'multicategory') { + // Several single-valued histograms! Stop infinite recursion, + // just return an extra flag that tells handleSingleValueOverlays + // to sort out this trace too + if(_overlayEdgeCase) return [newBinSpec, pos0, true]; + + newBinSpec = handleSingleValueOverlays(gd, trace, pa, mainData, binAttr); + } + + // adjust for CDF edge cases + cumulativeSpec = tracei.cumulative || {}; + if(cumulativeSpec.enabled && (cumulativeSpec.currentbin !== 'include')) { + if(cumulativeSpec.direction === 'decreasing') { + newBinSpec.start = c2r(Axes.tickIncrement( + r2c(newBinSpec.start), newBinSpec.size, true, calendar)); + } else { + newBinSpec.end = c2r(Axes.tickIncrement( + r2c(newBinSpec.end), newBinSpec.size, false, calendar)); + } + } + + binOpts.size = newBinSpec.size; + if(!binOpts.sizeFound) { + autoVals.size = newBinSpec.size; + Lib.nestedProperty(traces[0], binAttr + '.size').set(newBinSpec.size); + } + + setBound('start', binOpts, newBinSpec); + setBound('end', binOpts, newBinSpec); + } + + pos0 = trace['_' + mainData + 'pos0']; + delete trace['_' + mainData + 'pos0']; + + // Each trace can specify its own start/end, or if omitted + // we ensure they're beyond the bounds of this trace's data, + // and we need to make sure start is aligned with the main start + var traceInputBins = trace._input[binAttr] || {}; + var traceBinOptsCalc = Lib.extendFlat({}, binOpts); + var mainStart = binOpts.start; + var startIn = pa.r2l(traceInputBins.start); + var hasStart = startIn !== undefined; + if((binOpts.startFound || hasStart) && startIn !== pa.r2l(mainStart)) { + // We have an explicit start to reconcile across traces + // if this trace has an explicit start, shift it down to a bin edge + // if another trace had an explicit start, shift it down to a + // bin edge past our data + var traceStart = hasStart ? + startIn : + Lib.aggNums(Math.min, null, pos0); + + var dummyAx = { + type: (pa.type === 'category' || pa.type === 'multicategory') ? 'linear' : pa.type, + r2l: pa.r2l, + dtick: binOpts.size, + tick0: mainStart, + calendar: calendar, + range: ([traceStart, Axes.tickIncrement(traceStart, binOpts.size, false, calendar)]).map(pa.l2r) + }; + var newStart = Axes.tickFirst(dummyAx); + if(newStart > pa.r2l(traceStart)) { + newStart = Axes.tickIncrement(newStart, binOpts.size, true, calendar); + } + traceBinOptsCalc.start = pa.l2r(newStart); + if(!hasStart) Lib.nestedProperty(trace, binAttr + '.start').set(traceBinOptsCalc.start); + } + + var mainEnd = binOpts.end; + var endIn = pa.r2l(traceInputBins.end); + var hasEnd = endIn !== undefined; + if((binOpts.endFound || hasEnd) && endIn !== pa.r2l(mainEnd)) { + // Reconciling an explicit end is easier, as it doesn't need to + // match bin edges + var traceEnd = hasEnd ? + endIn : + Lib.aggNums(Math.max, null, pos0); + + traceBinOptsCalc.end = pa.l2r(traceEnd); + if(!hasEnd) Lib.nestedProperty(trace, binAttr + '.start').set(traceBinOptsCalc.end); + } + + // Backward compatibility for one-time autobinning. + // autobin: true is handled in cleanData, but autobin: false + // needs to be here where we have determined the values. + var autoBinAttr = 'autobin' + mainData; + if(trace._input[autoBinAttr] === false) { + trace._input[binAttr] = Lib.extendFlat({}, trace[binAttr] || {}); + delete trace._input[autoBinAttr]; + delete trace[autoBinAttr]; + } + + return [traceBinOptsCalc, pos0]; +} + +/* + * Adjust single-value histograms in overlay mode to make as good a + * guess as we can at autobin values the user would like. + * + * Returns the binSpec for the trace that sparked all this + */ +function handleSingleValueOverlays(gd, trace, pa, mainData, binAttr) { + var fullLayout = gd._fullLayout; + var overlaidTraceGroup = getConnectedHistograms(gd, trace); + var pastThisTrace = false; + var minSize = Infinity; + var singleValuedTraces = [trace]; + var i, tracei, binOpts; + + // first collect all the: + // - min bin size from all multi-valued traces + // - single-valued traces + for(i = 0; i < overlaidTraceGroup.length; i++) { + tracei = overlaidTraceGroup[i]; + + if(tracei === trace) { + pastThisTrace = true; + } else if(!pastThisTrace) { + // This trace has already had its autobins calculated, so either: + // - it is part of a bingroup + // - it is NOT a single-valued trace + binOpts = fullLayout._histogramBinOpts[tracei['_' + mainData + 'bingroup']]; + minSize = Math.min(minSize, binOpts.size || tracei[binAttr].size); + } else { + var resulti = calcAllAutoBins(gd, tracei, pa, mainData, true); + var binSpeci = resulti[0]; + var isSingleValued = resulti[2]; + + // so we can use this result when we get to tracei in the normal + // course of events, mark it as done and put _pos0 back + tracei['_' + mainData + 'autoBinFinished'] = 1; + tracei['_' + mainData + 'pos0'] = resulti[1]; + + if(isSingleValued) { + singleValuedTraces.push(tracei); + } else { + minSize = Math.min(minSize, binSpeci.size); + } + } + } + + // find the real data values for each single-valued trace + // hunt through pos0 for the first valid value + var dataVals = new Array(singleValuedTraces.length); + for(i = 0; i < singleValuedTraces.length; i++) { + var pos0 = singleValuedTraces[i]['_' + mainData + 'pos0']; + for(var j = 0; j < pos0.length; j++) { + if(pos0[j] !== undefined) { + dataVals[i] = pos0[j]; + break; + } + } + } + + // are ALL traces are single-valued? use the min difference between + // all of their values (which defaults to 1 if there's still only one) + if(!isFinite(minSize)) { + minSize = Lib.distinctVals(dataVals).minDiff; + } + + // now apply the min size we found to all single-valued traces + for(i = 0; i < singleValuedTraces.length; i++) { + tracei = singleValuedTraces[i]; + var calendar = tracei[mainData + 'calendar']; + + var newBins = { + start: pa.c2r(dataVals[i] - minSize / 2, 0, calendar), + end: pa.c2r(dataVals[i] + minSize / 2, 0, calendar), + size: minSize + }; + + tracei._input[binAttr] = tracei[binAttr] = newBins; + + binOpts = fullLayout._histogramBinOpts[tracei['_' + mainData + 'bingroup']]; + if(binOpts) Lib.extendFlat(binOpts, newBins); + } + + return trace[binAttr]; +} + +/* + * Return an array of histograms that share axes and orientation. + * + * Only considers histograms. In principle we could include bars in a + * similar way to how we do manually binned histograms, though this + * would have tons of edge cases and value judgments to make. + */ +function getConnectedHistograms(gd, trace) { + var xid = trace.xaxis; + var yid = trace.yaxis; + var orientation = trace.orientation; + + var out = []; + var fullData = gd._fullData; + for(var i = 0; i < fullData.length; i++) { + var tracei = fullData[i]; + if(tracei.type === 'histogram' && + tracei.visible === true && + tracei.orientation === orientation && + tracei.xaxis === xid && tracei.yaxis === yid + ) { + out.push(tracei); + } + } + + return out; +} + +function cdf(size, direction, currentBin) { + var i, vi, prevSum; + + function firstHalfPoint(i) { + prevSum = size[i]; + size[i] /= 2; + } + + function nextHalfPoint(i) { + vi = size[i]; + size[i] = prevSum + vi / 2; + prevSum += vi; + } + + if(currentBin === 'half') { + if(direction === 'increasing') { + firstHalfPoint(0); + for(i = 1; i < size.length; i++) { + nextHalfPoint(i); + } + } else { + firstHalfPoint(size.length - 1); + for(i = size.length - 2; i >= 0; i--) { + nextHalfPoint(i); + } + } + } else if(direction === 'increasing') { + for(i = 1; i < size.length; i++) { + size[i] += size[i - 1]; + } + + // 'exclude' is identical to 'include' just shifted one bin over + if(currentBin === 'exclude') { + size.unshift(0); + size.pop(); + } + } else { + for(i = size.length - 2; i >= 0; i--) { + size[i] += size[i + 1]; + } + + if(currentBin === 'exclude') { + size.push(0); + size.shift(); + } + } +} + +module.exports = { + calc: calc, + calcAllAutoBins: calcAllAutoBins +}; + +},{"../../lib":169,"../../plots/cartesian/axes":213,"../../registry":258,"../bar/arrays_to_calcdata":267,"./average":333,"./bin_functions":335,"./bin_label_vals":336,"./norm_functions":344,"fast-isnumeric":18}],338:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +module.exports = { + eventDataKeys: ['binNumber'] +}; + +},{}],339:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var axisIds = _dereq_('../../plots/cartesian/axis_ids'); + +var traceIs = _dereq_('../../registry').traceIs; +var handleGroupingDefaults = _dereq_('../bar/defaults').handleGroupingDefaults; + +var nestedProperty = Lib.nestedProperty; +var getAxisGroup = axisIds.getAxisGroup; + +var BINATTRS = [ + {aStr: {x: 'xbins.start', y: 'ybins.start'}, name: 'start'}, + {aStr: {x: 'xbins.end', y: 'ybins.end'}, name: 'end'}, + {aStr: {x: 'xbins.size', y: 'ybins.size'}, name: 'size'}, + {aStr: {x: 'nbinsx', y: 'nbinsy'}, name: 'nbins'} +]; + +var BINDIRECTIONS = ['x', 'y']; + +// handle bin attrs and relink auto-determined values so fullData is complete +module.exports = function crossTraceDefaults(fullData, fullLayout) { + var allBinOpts = fullLayout._histogramBinOpts = {}; + var histTraces = []; + var mustMatchTracesLookup = {}; + var otherTracesList = []; + + var traceOut, traces, groupName, binDir; + var i, j, k; + + function coerce(attr, dflt) { + return Lib.coerce(traceOut._input, traceOut, traceOut._module.attributes, attr, dflt); + } + + function orientation2binDir(traceOut) { + return traceOut.orientation === 'v' ? 'x' : 'y'; + } + + function getAxisType(traceOut, binDir) { + var ax = axisIds.getFromTrace({_fullLayout: fullLayout}, traceOut, binDir); + return ax.type; + } + + function fillBinOpts(traceOut, groupName, binDir) { + // N.B. group traces that don't have a bingroup with themselves + var fallbackGroupName = traceOut.uid + '__' + binDir; + if(!groupName) groupName = fallbackGroupName; + + var axType = getAxisType(traceOut, binDir); + var calendar = traceOut[binDir + 'calendar']; + var binOpts = allBinOpts[groupName]; + var needsNewItem = true; + + if(binOpts) { + if(axType === binOpts.axType && calendar === binOpts.calendar) { + needsNewItem = false; + binOpts.traces.push(traceOut); + binOpts.dirs.push(binDir); + } else { + groupName = fallbackGroupName; + + if(axType !== binOpts.axType) { + Lib.warn([ + 'Attempted to group the bins of trace', traceOut.index, + 'set on a', 'type:' + axType, 'axis', + 'with bins on', 'type:' + binOpts.axType, 'axis.' + ].join(' ')); + } + if(calendar !== binOpts.calendar) { + // prohibit bingroup for traces using different calendar, + // there's probably a way to make this work, but skip for now + Lib.warn([ + 'Attempted to group the bins of trace', traceOut.index, + 'set with a', calendar, 'calendar', + 'with bins', + (binOpts.calendar ? 'on a ' + binOpts.calendar + ' calendar' : 'w/o a set calendar') + ].join(' ')); + } + } + } + + if(needsNewItem) { + allBinOpts[groupName] = { + traces: [traceOut], + dirs: [binDir], + axType: axType, + calendar: traceOut[binDir + 'calendar'] || '' + }; + } + traceOut['_' + binDir + 'bingroup'] = groupName; + } + + for(i = 0; i < fullData.length; i++) { + traceOut = fullData[i]; + + if(traceIs(traceOut, 'histogram')) { + histTraces.push(traceOut); + + // TODO: this shouldn't be relinked as it's only used within calc + // https://github.com/plotly/plotly.js/issues/749 + delete traceOut._xautoBinFinished; + delete traceOut._yautoBinFinished; + + // N.B. need to coerce *alignmentgroup* before *bingroup*, as traces + // in same alignmentgroup "have to match" + if(!traceIs(traceOut, '2dMap')) { + handleGroupingDefaults(traceOut._input, traceOut, fullLayout, coerce); + } + } + } + + var alignmentOpts = fullLayout._alignmentOpts || {}; + + // Look for traces that "have to match", that is: + // - 1d histogram traces on the same subplot with same orientation under barmode:stack, + // - 1d histogram traces on the same subplot with same orientation under barmode:group + // - 1d histogram traces on the same position axis with the same orientation + // and the same *alignmentgroup* (coerced under barmode:group) + // - Once `stackgroup` gets implemented (see https://github.com/plotly/plotly.js/issues/3614), + // traces within the same stackgroup will also "have to match" + for(i = 0; i < histTraces.length; i++) { + traceOut = histTraces[i]; + groupName = ''; + + if(!traceIs(traceOut, '2dMap')) { + binDir = orientation2binDir(traceOut); + + if(fullLayout.barmode === 'group' && traceOut.alignmentgroup) { + var pa = traceOut[binDir + 'axis']; + var aGroupId = getAxisGroup(fullLayout, pa) + traceOut.orientation; + if((alignmentOpts[aGroupId] || {})[traceOut.alignmentgroup]) { + groupName = aGroupId; + } + } + + if(!groupName && fullLayout.barmode !== 'overlay') { + groupName = ( + getAxisGroup(fullLayout, traceOut.xaxis) + + getAxisGroup(fullLayout, traceOut.yaxis) + + orientation2binDir(traceOut) + ); + } + } + + if(groupName) { + if(!mustMatchTracesLookup[groupName]) { + mustMatchTracesLookup[groupName] = []; + } + mustMatchTracesLookup[groupName].push(traceOut); + } else { + otherTracesList.push(traceOut); + } + } + + // Setup binOpts for traces that have to match, + // if the traces have a valid bingroup, use that + // if not use axis+binDir groupName + for(groupName in mustMatchTracesLookup) { + traces = mustMatchTracesLookup[groupName]; + + // no need to 'force' anything when a single + // trace is detected as "must match" + if(traces.length === 1) { + otherTracesList.push(traces[0]); + continue; + } + + var binGroupFound = false; + for(i = 0; i < traces.length; i++) { + traceOut = traces[i]; + binGroupFound = coerce('bingroup'); + break; + } + + groupName = binGroupFound || groupName; + + for(i = 0; i < traces.length; i++) { + traceOut = traces[i]; + var bingroupIn = traceOut._input.bingroup; + if(bingroupIn && bingroupIn !== groupName) { + Lib.warn([ + 'Trace', traceOut.index, 'must match', + 'within bingroup', groupName + '.', + 'Ignoring its bingroup:', bingroupIn, 'setting.' + ].join(' ')); + } + traceOut.bingroup = groupName; + + // N.B. no need to worry about 2dMap case + // (where both bin direction are set in each trace) + // as 2dMap trace never "have to match" + fillBinOpts(traceOut, groupName, orientation2binDir(traceOut)); + } + } + + // setup binOpts for traces that can but don't have to match, + // notice that these traces can be matched with traces that have to match + for(i = 0; i < otherTracesList.length; i++) { + traceOut = otherTracesList[i]; + + var binGroup = coerce('bingroup'); + + if(traceIs(traceOut, '2dMap')) { + for(k = 0; k < 2; k++) { + binDir = BINDIRECTIONS[k]; + var binGroupInDir = coerce(binDir + 'bingroup', + binGroup ? binGroup + '__' + binDir : null + ); + fillBinOpts(traceOut, binGroupInDir, binDir); + } + } else { + fillBinOpts(traceOut, binGroup, orientation2binDir(traceOut)); + } + } + + // coerce bin attrs! + for(groupName in allBinOpts) { + var binOpts = allBinOpts[groupName]; + traces = binOpts.traces; + + for(j = 0; j < BINATTRS.length; j++) { + var attrSpec = BINATTRS[j]; + var attr = attrSpec.name; + var aStr; + var autoVals; + + // nbins(x|y) is moot if we have a size. This depends on + // nbins coming after size in binAttrs. + if(attr === 'nbins' && binOpts.sizeFound) continue; + + for(i = 0; i < traces.length; i++) { + traceOut = traces[i]; + binDir = binOpts.dirs[i]; + aStr = attrSpec.aStr[binDir]; + + if(nestedProperty(traceOut._input, aStr).get() !== undefined) { + binOpts[attr] = coerce(aStr); + binOpts[attr + 'Found'] = true; + break; + } + + autoVals = (traceOut._autoBin || {})[binDir] || {}; + if(autoVals[attr]) { + // if this is the *first* autoval + nestedProperty(traceOut, aStr).set(autoVals[attr]); + } + } + + // start and end we need to coerce anyway, after having collected the + // first of each into binOpts, in case a trace wants to restrict its + // data to a certain range + if(attr === 'start' || attr === 'end') { + for(; i < traces.length; i++) { + traceOut = traces[i]; + if(traceOut['_' + binDir + 'bingroup']) { + autoVals = (traceOut._autoBin || {})[binDir] || {}; + coerce(aStr, autoVals[attr]); + } + } + } + + if(attr === 'nbins' && !binOpts.sizeFound && !binOpts.nbinsFound) { + traceOut = traces[0]; + binOpts[attr] = coerce(aStr); + } + } + } +}; + +},{"../../lib":169,"../../plots/cartesian/axis_ids":216,"../../registry":258,"../bar/defaults":272}],340:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Registry = _dereq_('../../registry'); +var Lib = _dereq_('../../lib'); +var Color = _dereq_('../../components/color'); + +var handleStyleDefaults = _dereq_('../bar/style_defaults'); +var attributes = _dereq_('./attributes'); + +module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { + function coerce(attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); + } + + var x = coerce('x'); + var y = coerce('y'); + + var cumulative = coerce('cumulative.enabled'); + if(cumulative) { + coerce('cumulative.direction'); + coerce('cumulative.currentbin'); + } + + coerce('text'); + coerce('hovertext'); + coerce('hovertemplate'); + + var orientation = coerce('orientation', (y && !x) ? 'h' : 'v'); + var sampleLetter = orientation === 'v' ? 'x' : 'y'; + var aggLetter = orientation === 'v' ? 'y' : 'x'; + + var len = (x && y) ? + Math.min(Lib.minRowLength(x) && Lib.minRowLength(y)) : + Lib.minRowLength(traceOut[sampleLetter] || []); + + if(!len) { + traceOut.visible = false; + return; + } + + traceOut._length = len; + + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); + handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout); + + var hasAggregationData = traceOut[aggLetter]; + if(hasAggregationData) coerce('histfunc'); + coerce('histnorm'); + + // Note: bin defaults are now handled in Histogram.crossTraceDefaults + // autobin(x|y) are only included here to appease Plotly.validate + coerce('autobin' + sampleLetter); + + handleStyleDefaults(traceIn, traceOut, coerce, defaultColor, layout); + + Lib.coerceSelectionMarkerOpacity(traceOut, coerce); + + var lineColor = (traceOut.marker.line || {}).color; + + // override defaultColor for error bars with defaultLine + var errorBarsSupplyDefaults = Registry.getComponentMethod('errorbars', 'supplyDefaults'); + errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, {axis: 'y'}); + errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, {axis: 'x', inherit: 'y'}); +}; + +},{"../../components/color":51,"../../lib":169,"../../registry":258,"../bar/style_defaults":282,"./attributes":332}],341:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = function eventData(out, pt, trace, cd, pointNumber) { + // standard cartesian event data + out.x = 'xVal' in pt ? pt.xVal : pt.x; + out.y = 'yVal' in pt ? pt.yVal : pt.y; + + // for 2d histograms + if('zLabelVal' in pt) out.z = pt.zLabelVal; + + if(pt.xa) out.xaxis = pt.xa; + if(pt.ya) out.yaxis = pt.ya; + + // specific to histogram - CDFs do not have pts (yet?) + if(!(trace.cumulative || {}).enabled) { + var pts = Array.isArray(pointNumber) ? + cd[0].pts[pointNumber[0]][pointNumber[1]] : + cd[pointNumber].pts; + + out.pointNumbers = pts; + out.binNumber = out.pointNumber; + delete out.pointNumber; + delete out.pointIndex; + + var pointIndices; + if(trace._indexToPoints) { + pointIndices = []; + for(var i = 0; i < pts.length; i++) { + pointIndices = pointIndices.concat(trace._indexToPoints[pts[i]]); + } + } else { + pointIndices = pts; + } + + out.pointIndices = pointIndices; + } + + return out; +}; + +},{}],342:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var barHover = _dereq_('../bar/hover').hoverPoints; +var hoverLabelText = _dereq_('../../plots/cartesian/axes').hoverLabelText; + +module.exports = function hoverPoints(pointData, xval, yval, hovermode) { + var pts = barHover(pointData, xval, yval, hovermode); + + if(!pts) return; + + pointData = pts[0]; + var di = pointData.cd[pointData.index]; + var trace = pointData.cd[0].trace; + + if(!trace.cumulative.enabled) { + var posLetter = trace.orientation === 'h' ? 'y' : 'x'; + + pointData[posLetter + 'Label'] = hoverLabelText(pointData[posLetter + 'a'], di.ph0, di.ph1); + } + + if(trace.hovermplate) pointData.hovertemplate = trace.hovertemplate; + + return pts; +}; + +},{"../../plots/cartesian/axes":213,"../bar/hover":274}],343:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +/** + * Histogram has its own attribute, defaults and calc steps, + * but uses bar's plot to display + * and bar's crossTraceCalc (formerly known as setPositions) for stacking and grouping + */ + +/** + * histogram errorBarsOK is debatable, but it's put in for backward compat. + * there are use cases for it - sqrt for a simple histogram works right now, + * constant and % work but they're not so meaningful. I guess it could be cool + * to allow quadrature combination of errors in summed histograms... + */ + +module.exports = { + attributes: _dereq_('./attributes'), + layoutAttributes: _dereq_('../bar/layout_attributes'), + supplyDefaults: _dereq_('./defaults'), + crossTraceDefaults: _dereq_('./cross_trace_defaults'), + supplyLayoutDefaults: _dereq_('../bar/layout_defaults'), + calc: _dereq_('./calc').calc, + crossTraceCalc: _dereq_('../bar/cross_trace_calc').crossTraceCalc, + plot: _dereq_('../bar/plot').plot, + layerName: 'barlayer', + style: _dereq_('../bar/style').style, + styleOnSelect: _dereq_('../bar/style').styleOnSelect, + colorbar: _dereq_('../scatter/marker_colorbar'), + hoverPoints: _dereq_('./hover'), + selectPoints: _dereq_('../bar/select'), + eventData: _dereq_('./event_data'), + + moduleType: 'trace', + name: 'histogram', + basePlotModule: _dereq_('../../plots/cartesian'), + categories: ['bar-like', 'cartesian', 'svg', 'bar', 'histogram', 'oriented', 'errorBarsOK', 'showLegend'], + meta: { + + } +}; + +},{"../../plots/cartesian":224,"../bar/cross_trace_calc":271,"../bar/layout_attributes":276,"../bar/layout_defaults":277,"../bar/plot":278,"../bar/select":279,"../bar/style":281,"../scatter/marker_colorbar":393,"./attributes":332,"./calc":337,"./cross_trace_defaults":339,"./defaults":340,"./event_data":341,"./hover":342}],344:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + + +module.exports = { + percent: function(size, total) { + var nMax = size.length; + var norm = 100 / total; + for(var n = 0; n < nMax; n++) size[n] *= norm; + }, + probability: function(size, total) { + var nMax = size.length; + for(var n = 0; n < nMax; n++) size[n] /= total; + }, + density: function(size, total, inc, yinc) { + var nMax = size.length; + yinc = yinc || 1; + for(var n = 0; n < nMax; n++) size[n] *= inc[n] * yinc; + }, + 'probability density': function(size, total, inc, yinc) { + var nMax = size.length; + if(yinc) total /= yinc; + for(var n = 0; n < nMax; n++) size[n] *= inc[n] / total; + } +}; + +},{}],345:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var histogramAttrs = _dereq_('../histogram/attributes'); +var makeBinAttrs = _dereq_('../histogram/bin_attributes'); +var heatmapAttrs = _dereq_('../heatmap/attributes'); +var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs; +var colorScaleAttrs = _dereq_('../../components/colorscale/attributes'); + +var extendFlat = _dereq_('../../lib/extend').extendFlat; + +module.exports = extendFlat( + { + x: histogramAttrs.x, + y: histogramAttrs.y, + + z: { + valType: 'data_array', + editType: 'calc', + + }, + marker: { + color: { + valType: 'data_array', + editType: 'calc', + + }, + editType: 'calc' + }, + + histnorm: histogramAttrs.histnorm, + histfunc: histogramAttrs.histfunc, + nbinsx: histogramAttrs.nbinsx, + xbins: makeBinAttrs('x'), + nbinsy: histogramAttrs.nbinsy, + ybins: makeBinAttrs('y'), + autobinx: histogramAttrs.autobinx, + autobiny: histogramAttrs.autobiny, + + bingroup: extendFlat({}, histogramAttrs.bingroup, { + + }), + xbingroup: extendFlat({}, histogramAttrs.bingroup, { + + }), + ybingroup: extendFlat({}, histogramAttrs.bingroup, { + + }), + + xgap: heatmapAttrs.xgap, + ygap: heatmapAttrs.ygap, + zsmooth: heatmapAttrs.zsmooth, + zhoverformat: heatmapAttrs.zhoverformat, + hovertemplate: hovertemplateAttrs({}, {keys: 'z'}) + }, + colorScaleAttrs('', {cLetter: 'z', autoColorDflt: false}) +); + +},{"../../components/colorscale/attributes":58,"../../lib/extend":164,"../../plots/template_attributes":253,"../heatmap/attributes":317,"../histogram/attributes":332,"../histogram/bin_attributes":334}],346:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var Axes = _dereq_('../../plots/cartesian/axes'); + +var binFunctions = _dereq_('../histogram/bin_functions'); +var normFunctions = _dereq_('../histogram/norm_functions'); +var doAvg = _dereq_('../histogram/average'); +var getBinSpanLabelRound = _dereq_('../histogram/bin_label_vals'); +var calcAllAutoBins = _dereq_('../histogram/calc').calcAllAutoBins; + +module.exports = function calc(gd, trace) { + var xa = Axes.getFromId(gd, trace.xaxis); + var ya = Axes.getFromId(gd, trace.yaxis); + + var xcalendar = trace.xcalendar; + var ycalendar = trace.ycalendar; + var xr2c = function(v) { return xa.r2c(v, 0, xcalendar); }; + var yr2c = function(v) { return ya.r2c(v, 0, ycalendar); }; + var xc2r = function(v) { return xa.c2r(v, 0, xcalendar); }; + var yc2r = function(v) { return ya.c2r(v, 0, ycalendar); }; + + var i, j, n, m; + + // calculate the bins + var xBinsAndPos = calcAllAutoBins(gd, trace, xa, 'x'); + var xBinSpec = xBinsAndPos[0]; + var xPos0 = xBinsAndPos[1]; + var yBinsAndPos = calcAllAutoBins(gd, trace, ya, 'y'); + var yBinSpec = yBinsAndPos[0]; + var yPos0 = yBinsAndPos[1]; + + var serieslen = trace._length; + if(xPos0.length > serieslen) xPos0.splice(serieslen, xPos0.length - serieslen); + if(yPos0.length > serieslen) yPos0.splice(serieslen, yPos0.length - serieslen); + + // make the empty bin array & scale the map + var z = []; + var onecol = []; + var zerocol = []; + var nonuniformBinsX = typeof xBinSpec.size === 'string'; + var nonuniformBinsY = typeof yBinSpec.size === 'string'; + var xEdges = []; + var yEdges = []; + var xbins = nonuniformBinsX ? xEdges : xBinSpec; + var ybins = nonuniformBinsY ? yEdges : yBinSpec; + var total = 0; + var counts = []; + var inputPoints = []; + var norm = trace.histnorm; + var func = trace.histfunc; + var densitynorm = norm.indexOf('density') !== -1; + var extremefunc = func === 'max' || func === 'min'; + var sizeinit = extremefunc ? null : 0; + var binfunc = binFunctions.count; + var normfunc = normFunctions[norm]; + var doavg = false; + var xinc = []; + var yinc = []; + + // set a binning function other than count? + // for binning functions: check first for 'z', + // then 'mc' in case we had a colored scatter plot + // and want to transfer these colors to the 2D histo + // TODO: axe this, make it the responsibility of the app changing type? or an impliedEdit? + var rawCounterData = ('z' in trace) ? + trace.z : + (('marker' in trace && Array.isArray(trace.marker.color)) ? + trace.marker.color : ''); + if(rawCounterData && func !== 'count') { + doavg = func === 'avg'; + binfunc = binFunctions[func]; + } + + // decrease end a little in case of rounding errors + var xBinSize = xBinSpec.size; + var xBinStart = xr2c(xBinSpec.start); + var xBinEnd = xr2c(xBinSpec.end) + + (xBinStart - Axes.tickIncrement(xBinStart, xBinSize, false, xcalendar)) / 1e6; + + for(i = xBinStart; i < xBinEnd; i = Axes.tickIncrement(i, xBinSize, false, xcalendar)) { + onecol.push(sizeinit); + xEdges.push(i); + if(doavg) zerocol.push(0); + } + xEdges.push(i); + + var nx = onecol.length; + var dx = (i - xBinStart) / nx; + var x0 = xc2r(xBinStart + dx / 2); + + var yBinSize = yBinSpec.size; + var yBinStart = yr2c(yBinSpec.start); + var yBinEnd = yr2c(yBinSpec.end) + + (yBinStart - Axes.tickIncrement(yBinStart, yBinSize, false, ycalendar)) / 1e6; + + for(i = yBinStart; i < yBinEnd; i = Axes.tickIncrement(i, yBinSize, false, ycalendar)) { + z.push(onecol.slice()); + yEdges.push(i); + var ipCol = new Array(nx); + for(j = 0; j < nx; j++) ipCol[j] = []; + inputPoints.push(ipCol); + if(doavg) counts.push(zerocol.slice()); + } + yEdges.push(i); + + var ny = z.length; + var dy = (i - yBinStart) / ny; + var y0 = yc2r(yBinStart + dy / 2); + + if(densitynorm) { + xinc = makeIncrements(onecol.length, xbins, dx, nonuniformBinsX); + yinc = makeIncrements(z.length, ybins, dy, nonuniformBinsY); + } + + // for date axes we need bin bounds to be calcdata. For nonuniform bins + // we already have this, but uniform with start/end/size they're still strings. + if(!nonuniformBinsX && xa.type === 'date') xbins = binsToCalc(xr2c, xbins); + if(!nonuniformBinsY && ya.type === 'date') ybins = binsToCalc(yr2c, ybins); + + // put data into bins + var uniqueValsPerX = true; + var uniqueValsPerY = true; + var xVals = new Array(nx); + var yVals = new Array(ny); + var xGapLow = Infinity; + var xGapHigh = Infinity; + var yGapLow = Infinity; + var yGapHigh = Infinity; + for(i = 0; i < serieslen; i++) { + var xi = xPos0[i]; + var yi = yPos0[i]; + n = Lib.findBin(xi, xbins); + m = Lib.findBin(yi, ybins); + if(n >= 0 && n < nx && m >= 0 && m < ny) { + total += binfunc(n, i, z[m], rawCounterData, counts[m]); + inputPoints[m][n].push(i); + + if(uniqueValsPerX) { + if(xVals[n] === undefined) xVals[n] = xi; + else if(xVals[n] !== xi) uniqueValsPerX = false; + } + if(uniqueValsPerY) { + if(yVals[m] === undefined) yVals[m] = yi; + else if(yVals[m] !== yi) uniqueValsPerY = false; + } + + xGapLow = Math.min(xGapLow, xi - xEdges[n]); + xGapHigh = Math.min(xGapHigh, xEdges[n + 1] - xi); + yGapLow = Math.min(yGapLow, yi - yEdges[m]); + yGapHigh = Math.min(yGapHigh, yEdges[m + 1] - yi); + } + } + // normalize, if needed + if(doavg) { + for(m = 0; m < ny; m++) total += doAvg(z[m], counts[m]); + } + if(normfunc) { + for(m = 0; m < ny; m++) normfunc(z[m], total, xinc, yinc[m]); + } + + return { + x: xPos0, + xRanges: getRanges(xEdges, uniqueValsPerX && xVals, xGapLow, xGapHigh, xa, xcalendar), + x0: x0, + dx: dx, + y: yPos0, + yRanges: getRanges(yEdges, uniqueValsPerY && yVals, yGapLow, yGapHigh, ya, ycalendar), + y0: y0, + dy: dy, + z: z, + pts: inputPoints + }; +}; + +function makeIncrements(len, bins, dv, nonuniform) { + var out = new Array(len); + var i; + if(nonuniform) { + for(i = 0; i < len; i++) out[i] = 1 / (bins[i + 1] - bins[i]); + } else { + var inc = 1 / dv; + for(i = 0; i < len; i++) out[i] = inc; + } + return out; +} + +function binsToCalc(r2c, bins) { + return { + start: r2c(bins.start), + end: r2c(bins.end), + size: bins.size + }; +} + +function getRanges(edges, uniqueVals, gapLow, gapHigh, ax, calendar) { + var i; + var len = edges.length - 1; + var out = new Array(len); + var roundFn = getBinSpanLabelRound(gapLow, gapHigh, edges, ax, calendar); + + for(i = 0; i < len; i++) { + var v = (uniqueVals || [])[i]; + out[i] = v === undefined ? + [roundFn(edges[i]), roundFn(edges[i + 1], true)] : + [v, v]; + } + return out; +} + +},{"../../lib":169,"../../plots/cartesian/axes":213,"../histogram/average":333,"../histogram/bin_functions":335,"../histogram/bin_label_vals":336,"../histogram/calc":337,"../histogram/norm_functions":344}],347:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Lib = _dereq_('../../lib'); + +var handleSampleDefaults = _dereq_('./sample_defaults'); +var handleStyleDefaults = _dereq_('../heatmap/style_defaults'); +var colorscaleDefaults = _dereq_('../../components/colorscale/defaults'); +var attributes = _dereq_('./attributes'); + + +module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { + function coerce(attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); + } + + handleSampleDefaults(traceIn, traceOut, coerce, layout); + if(traceOut.visible === false) return; + + handleStyleDefaults(traceIn, traceOut, coerce, layout); + colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'}); + coerce('hovertemplate'); +}; + +},{"../../components/colorscale/defaults":61,"../../lib":169,"../heatmap/style_defaults":330,"./attributes":345,"./sample_defaults":350}],348:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var heatmapHover = _dereq_('../heatmap/hover'); +var hoverLabelText = _dereq_('../../plots/cartesian/axes').hoverLabelText; + +module.exports = function hoverPoints(pointData, xval, yval, hovermode, hoverLayer, contour) { + var pts = heatmapHover(pointData, xval, yval, hovermode, hoverLayer, contour); + + if(!pts) return; + + pointData = pts[0]; + var indices = pointData.index; + var ny = indices[0]; + var nx = indices[1]; + var cd0 = pointData.cd[0]; + var xRange = cd0.xRanges[nx]; + var yRange = cd0.yRanges[ny]; + + pointData.xLabel = hoverLabelText(pointData.xa, xRange[0], xRange[1]); + pointData.yLabel = hoverLabelText(pointData.ya, yRange[0], yRange[1]); + + return pts; +}; + +},{"../../plots/cartesian/axes":213,"../heatmap/hover":324}],349:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + + attributes: _dereq_('./attributes'), + supplyDefaults: _dereq_('./defaults'), + crossTraceDefaults: _dereq_('../histogram/cross_trace_defaults'), + calc: _dereq_('../heatmap/calc'), + plot: _dereq_('../heatmap/plot'), + layerName: 'heatmaplayer', + colorbar: _dereq_('../heatmap/colorbar'), + style: _dereq_('../heatmap/style'), + hoverPoints: _dereq_('./hover'), + eventData: _dereq_('../histogram/event_data'), + + moduleType: 'trace', + name: 'histogram2d', + basePlotModule: _dereq_('../../plots/cartesian'), + categories: ['cartesian', 'svg', '2dMap', 'histogram'], + meta: { + + + } +}; + +},{"../../plots/cartesian":224,"../heatmap/calc":318,"../heatmap/colorbar":320,"../heatmap/plot":328,"../heatmap/style":329,"../histogram/cross_trace_defaults":339,"../histogram/event_data":341,"./attributes":345,"./defaults":347,"./hover":348}],350:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Registry = _dereq_('../../registry'); +var Lib = _dereq_('../../lib'); + +module.exports = function handleSampleDefaults(traceIn, traceOut, coerce, layout) { + var x = coerce('x'); + var y = coerce('y'); + var xlen = Lib.minRowLength(x); + var ylen = Lib.minRowLength(y); + + // we could try to accept x0 and dx, etc... + // but that's a pretty weird use case. + // for now require both x and y explicitly specified. + if(!xlen || !ylen) { + traceOut.visible = false; + return; + } + + traceOut._length = Math.min(xlen, ylen); + + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); + handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout); + + // if marker.color is an array, we can use it in aggregation instead of z + var hasAggregationData = coerce('z') || coerce('marker.color'); + + if(hasAggregationData) coerce('histfunc'); + coerce('histnorm'); + + // Note: bin defaults are now handled in Histogram2D.crossTraceDefaults + // autobin(x|y) are only included here to appease Plotly.validate + coerce('autobinx'); + coerce('autobiny'); +}; + +},{"../../lib":169,"../../registry":258}],351:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var histogram2dAttrs = _dereq_('../histogram2d/attributes'); +var contourAttrs = _dereq_('../contour/attributes'); +var colorScaleAttrs = _dereq_('../../components/colorscale/attributes'); + +var extendFlat = _dereq_('../../lib/extend').extendFlat; + +module.exports = extendFlat({ + x: histogram2dAttrs.x, + y: histogram2dAttrs.y, + z: histogram2dAttrs.z, + marker: histogram2dAttrs.marker, + + histnorm: histogram2dAttrs.histnorm, + histfunc: histogram2dAttrs.histfunc, + nbinsx: histogram2dAttrs.nbinsx, + xbins: histogram2dAttrs.xbins, + nbinsy: histogram2dAttrs.nbinsy, + ybins: histogram2dAttrs.ybins, + autobinx: histogram2dAttrs.autobinx, + autobiny: histogram2dAttrs.autobiny, + + bingroup: histogram2dAttrs.bingroup, + xbingroup: histogram2dAttrs.xbingroup, + ybingroup: histogram2dAttrs.ybingroup, + + autocontour: contourAttrs.autocontour, + ncontours: contourAttrs.ncontours, + contours: contourAttrs.contours, + line: { + color: contourAttrs.line.color, + width: extendFlat({}, contourAttrs.line.width, { + dflt: 0.5, + + }), + dash: contourAttrs.line.dash, + smoothing: contourAttrs.line.smoothing, + editType: 'plot' + }, + zhoverformat: histogram2dAttrs.zhoverformat, + hovertemplate: histogram2dAttrs.hovertemplate +}, + colorScaleAttrs('', { + cLetter: 'z', + editTypeOverride: 'calc' + }) +); + +},{"../../components/colorscale/attributes":58,"../../lib/extend":164,"../contour/attributes":295,"../histogram2d/attributes":345}],352:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Lib = _dereq_('../../lib'); + +var handleSampleDefaults = _dereq_('../histogram2d/sample_defaults'); +var handleContoursDefaults = _dereq_('../contour/contours_defaults'); +var handleStyleDefaults = _dereq_('../contour/style_defaults'); +var attributes = _dereq_('./attributes'); + + +module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { + function coerce(attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); + } + + function coerce2(attr) { + return Lib.coerce2(traceIn, traceOut, attributes, attr); + } + + handleSampleDefaults(traceIn, traceOut, coerce, layout); + if(traceOut.visible === false) return; + + handleContoursDefaults(traceIn, traceOut, coerce, coerce2); + handleStyleDefaults(traceIn, traceOut, coerce, layout); + coerce('hovertemplate'); +}; + +},{"../../lib":169,"../contour/contours_defaults":302,"../contour/style_defaults":316,"../histogram2d/sample_defaults":350,"./attributes":351}],353:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + attributes: _dereq_('./attributes'), + supplyDefaults: _dereq_('./defaults'), + crossTraceDefaults: _dereq_('../histogram/cross_trace_defaults'), + calc: _dereq_('../contour/calc'), + plot: _dereq_('../contour/plot').plot, + layerName: 'contourlayer', + style: _dereq_('../contour/style'), + colorbar: _dereq_('../contour/colorbar'), + hoverPoints: _dereq_('../contour/hover'), + + moduleType: 'trace', + name: 'histogram2dcontour', + basePlotModule: _dereq_('../../plots/cartesian'), + categories: ['cartesian', 'svg', '2dMap', 'contour', 'histogram', 'showLegend'], + meta: { + + + } +}; + +},{"../../plots/cartesian":224,"../contour/calc":296,"../contour/colorbar":298,"../contour/hover":308,"../contour/plot":313,"../contour/style":315,"../histogram/cross_trace_defaults":339,"./attributes":351,"./defaults":352}],354:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var plotAttrs = _dereq_('../../plots/attributes'); +var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs; +var extendFlat = _dereq_('../../lib/extend').extendFlat; +var colormodel = _dereq_('./constants').colormodel; + +var cm = ['rgb', 'rgba', 'hsl', 'hsla']; +var zminDesc = []; +var zmaxDesc = []; +for(var i = 0; i < cm.length; i++) { + zminDesc.push('For the `' + cm[i] + '` colormodel, it is [' + colormodel[cm[i]].min.join(', ') + '].'); + zmaxDesc.push('For the `' + cm[i] + '` colormodel, it is [' + colormodel[cm[i]].max.join(', ') + '].'); +} + +module.exports = extendFlat({ + z: { + valType: 'data_array', + + editType: 'calc', + + }, + colormodel: { + valType: 'enumerated', + values: cm, + dflt: 'rgb', + + editType: 'calc', + + }, + zmin: { + valType: 'info_array', + items: [ + {valType: 'number', editType: 'calc'}, + {valType: 'number', editType: 'calc'}, + {valType: 'number', editType: 'calc'}, + {valType: 'number', editType: 'calc'} + ], + + editType: 'calc', + + }, + zmax: { + valType: 'info_array', + items: [ + {valType: 'number', editType: 'calc'}, + {valType: 'number', editType: 'calc'}, + {valType: 'number', editType: 'calc'}, + {valType: 'number', editType: 'calc'} + ], + + editType: 'calc', + + }, + x0: { + valType: 'any', + dflt: 0, + + editType: 'calc+clearAxisTypes', + + }, + y0: { + valType: 'any', + dflt: 0, + + editType: 'calc+clearAxisTypes', + + }, + dx: { + valType: 'number', + dflt: 1, + + editType: 'calc', + + }, + dy: { + valType: 'number', + dflt: 1, + + editType: 'calc', + + }, + text: { + valType: 'data_array', + editType: 'plot', + + }, + hovertext: { + valType: 'data_array', + editType: 'plot', + + }, + hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { + flags: ['x', 'y', 'z', 'color', 'name', 'text'], + dflt: 'x+y+z+text+name' + }), + hovertemplate: hovertemplateAttrs({}, { + keys: ['z', 'color', 'colormodel'] + }), + + transforms: undefined +}); + +},{"../../lib/extend":164,"../../plots/attributes":210,"../../plots/template_attributes":253,"./constants":356}],355:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var constants = _dereq_('./constants'); +var isNumeric = _dereq_('fast-isnumeric'); +var Axes = _dereq_('../../plots/cartesian/axes'); +var maxRowLength = _dereq_('../../lib').maxRowLength; + +module.exports = function calc(gd, trace) { + var xa = Axes.getFromId(gd, trace.xaxis || 'x'); + var ya = Axes.getFromId(gd, trace.yaxis || 'y'); + + var x0 = xa.d2c(trace.x0) - trace.dx / 2; + var y0 = ya.d2c(trace.y0) - trace.dy / 2; + var h = trace.z.length; + var w = maxRowLength(trace.z); + + // Set axis range + var i; + var xrange = [x0, x0 + w * trace.dx]; + var yrange = [y0, y0 + h * trace.dy]; + if(xa && xa.type === 'log') for(i = 0; i < w; i++) xrange.push(x0 + i * trace.dx); + if(ya && ya.type === 'log') for(i = 0; i < h; i++) yrange.push(y0 + i * trace.dy); + trace._extremes[xa._id] = Axes.findExtremes(xa, xrange); + trace._extremes[ya._id] = Axes.findExtremes(ya, yrange); + trace._scaler = makeScaler(trace); + + var cd0 = { + x0: x0, + y0: y0, + z: trace.z, + w: w, + h: h + }; + return [cd0]; +}; + +function scale(zero, ratio, min, max) { + return function(c) { + return Lib.constrain((c - zero) * ratio, min, max); + }; +} + +function constrain(min, max) { + return function(c) { return Lib.constrain(c, min, max);}; +} + +// Generate a function to scale color components according to zmin/zmax and the colormodel +function makeScaler(trace) { + var colormodel = trace.colormodel; + var n = colormodel.length; + var cr = constants.colormodel[colormodel]; + + trace._sArray = []; + // Loop over all color components + for(var k = 0; k < n; k++) { + if(cr.min[k] !== trace.zmin[k] || cr.max[k] !== trace.zmax[k]) { + trace._sArray.push(scale( + trace.zmin[k], + (cr.max[k] - cr.min[k]) / (trace.zmax[k] - trace.zmin[k]), + cr.min[k], + cr.max[k] + )); + } else { + trace._sArray.push(constrain(cr.min[k], cr.max[k])); + } + } + + return function(pixel) { + var c = pixel.slice(0, n); + for(var k = 0; k < n; k++) { + var ck = c[k]; + if(!isNumeric(ck)) return false; + c[k] = trace._sArray[k](ck); + } + return c; + }; +} + +},{"../../lib":169,"../../plots/cartesian/axes":213,"./constants":356,"fast-isnumeric":18}],356:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + colormodel: { + rgb: { + min: [0, 0, 0], + max: [255, 255, 255], + fmt: function(c) {return c.slice(0, 3);}, + suffix: ['', '', ''] + }, + rgba: { + min: [0, 0, 0, 0], + max: [255, 255, 255, 1], + fmt: function(c) {return c.slice(0, 4);}, + suffix: ['', '', '', ''] + }, + hsl: { + min: [0, 0, 0], + max: [360, 100, 100], + fmt: function(c) { + var p = c.slice(0, 3); + p[1] = p[1] + '%'; + p[2] = p[2] + '%'; + return p; + }, + suffix: ['°', '%', '%'] + }, + hsla: { + min: [0, 0, 0, 0], + max: [360, 100, 100, 1], + fmt: function(c) { + var p = c.slice(0, 4); + p[1] = p[1] + '%'; + p[2] = p[2] + '%'; + return p; + }, + suffix: ['°', '%', '%', ''] + } + } +}; + +},{}],357:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var attributes = _dereq_('./attributes'); +var constants = _dereq_('./constants'); + +module.exports = function supplyDefaults(traceIn, traceOut) { + function coerce(attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); + } + var z = coerce('z'); + if(z === undefined || !z.length || !z[0] || !z[0].length) { + traceOut.visible = false; + return; + } + + coerce('x0'); + coerce('y0'); + coerce('dx'); + coerce('dy'); + var colormodel = coerce('colormodel'); + + coerce('zmin', constants.colormodel[colormodel].min); + coerce('zmax', constants.colormodel[colormodel].max); + + coerce('text'); + coerce('hovertext'); + coerce('hovertemplate'); + + traceOut._length = null; +}; + +},{"../../lib":169,"./attributes":354,"./constants":356}],358:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = function eventData(out, pt) { + if('xVal' in pt) out.x = pt.xVal; + if('yVal' in pt) out.y = pt.yVal; + if(pt.xa) out.xaxis = pt.xa; + if(pt.ya) out.yaxis = pt.ya; + out.color = pt.color; + out.colormodel = pt.trace.colormodel; + return out; +}; + +},{}],359:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Fx = _dereq_('../../components/fx'); +var Lib = _dereq_('../../lib'); +var constants = _dereq_('./constants'); + +module.exports = function hoverPoints(pointData, xval, yval) { + var cd0 = pointData.cd[0]; + var trace = cd0.trace; + var xa = pointData.xa; + var ya = pointData.ya; + + // Return early if not on image + if(Fx.inbox(xval - cd0.x0, xval - (cd0.x0 + cd0.w * trace.dx), 0) > 0 || + Fx.inbox(yval - cd0.y0, yval - (cd0.y0 + cd0.h * trace.dy), 0) > 0) { + return; + } + + // Find nearest pixel's index + var nx = Math.floor((xval - cd0.x0) / trace.dx); + var ny = Math.floor(Math.abs(yval - cd0.y0) / trace.dy); + + // return early if pixel is undefined + if(!cd0.z[ny][nx]) return; + + var hoverinfo = cd0.hi || trace.hoverinfo; + var fmtColor; + if(hoverinfo) { + var parts = hoverinfo.split('+'); + if(parts.indexOf('all') !== -1) parts = ['color']; + if(parts.indexOf('color') !== -1) fmtColor = true; + } + + var colormodel = trace.colormodel; + var dims = colormodel.length; + var c = trace._scaler(cd0.z[ny][nx]); + var s = constants.colormodel[colormodel].suffix; + + var colorstring = []; + if(trace.hovertemplate || fmtColor) { + colorstring.push('[' + [c[0] + s[0], c[1] + s[1], c[2] + s[2]].join(', ')); + if(dims === 4) colorstring.push(', ' + c[3] + s[3]); + colorstring.push(']'); + colorstring = colorstring.join(''); + pointData.extraText = colormodel.toUpperCase() + ': ' + colorstring; + } + + var text; + if(Array.isArray(trace.hovertext) && Array.isArray(trace.hovertext[ny])) { + text = trace.hovertext[ny][nx]; + } else if(Array.isArray(trace.text) && Array.isArray(trace.text[ny])) { + text = trace.text[ny][nx]; + } + + // TODO: for color model with 3 dims, display something useful for hovertemplate `%{color[3]}` + var py = ya.c2p(cd0.y0 + (ny + 0.5) * trace.dy); + var xVal = cd0.x0 + (nx + 0.5) * trace.dx; + var yVal = cd0.y0 + (ny + 0.5) * trace.dy; + var zLabel = '[' + cd0.z[ny][nx].slice(0, trace.colormodel.length).join(', ') + ']'; + return [Lib.extendFlat(pointData, { + index: [ny, nx], + x0: xa.c2p(cd0.x0 + nx * trace.dx), + x1: xa.c2p(cd0.x0 + (nx + 1) * trace.dx), + y0: py, + y1: py, + color: c, + xVal: xVal, + xLabelVal: xVal, + yVal: yVal, + yLabelVal: yVal, + zLabelVal: zLabel, + text: text, + hovertemplateLabels: { + 'zLabel': zLabel, + 'colorLabel': colorstring, + 'color[0]Label': c[0] + s[0], + 'color[1]Label': c[1] + s[1], + 'color[2]Label': c[2] + s[2], + 'color[3]Label': c[3] + s[3] + } + })]; +}; + +},{"../../components/fx":89,"../../lib":169,"./constants":356}],360:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + attributes: _dereq_('./attributes'), + supplyDefaults: _dereq_('./defaults'), + calc: _dereq_('./calc'), + plot: _dereq_('./plot'), + style: _dereq_('./style'), + hoverPoints: _dereq_('./hover'), + eventData: _dereq_('./event_data'), + + moduleType: 'trace', + name: 'image', + basePlotModule: _dereq_('../../plots/cartesian'), + categories: ['cartesian', 'svg', '2dMap', 'noSortingByValue'], + animatable: false, + meta: { + + } +}; + +},{"../../plots/cartesian":224,"./attributes":354,"./calc":355,"./defaults":357,"./event_data":358,"./hover":359,"./plot":361,"./style":362}],361:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); +var Lib = _dereq_('../../lib'); +var xmlnsNamespaces = _dereq_('../../constants/xmlns_namespaces'); +var constants = _dereq_('./constants'); + +module.exports = function plot(gd, plotinfo, cdimage, imageLayer) { + var xa = plotinfo.xaxis; + var ya = plotinfo.yaxis; + + Lib.makeTraceGroups(imageLayer, cdimage, 'im').each(function(cd) { + var plotGroup = d3.select(this); + var cd0 = cd[0]; + var trace = cd0.trace; + + var z = cd0.z; + var x0 = cd0.x0; + var y0 = cd0.y0; + var w = cd0.w; + var h = cd0.h; + var dx = trace.dx; + var dy = trace.dy; + + var left, right, temp, top, bottom, i; + // in case of log of a negative + i = 0; + while(left === undefined && i < w) { + left = xa.c2p(x0 + i * dx); + i++; + } + i = w; + while(right === undefined && i > 0) { + right = xa.c2p(x0 + i * dx); + i--; + } + i = 0; + while(top === undefined && i < h) { + top = ya.c2p(y0 + i * dy); + i++; + } + i = h; + while(bottom === undefined && i > 0) { + bottom = ya.c2p(y0 + i * dy); + i--; + } + + if(right < left) { + temp = right; + right = left; + left = temp; + } + + if(bottom < top) { + temp = top; + top = bottom; + bottom = temp; + } + + // Reduce image size when zoomed in to save memory + var extra = 0.5; // half the axis size + left = Math.max(-extra * xa._length, left); + right = Math.min((1 + extra) * xa._length, right); + top = Math.max(-extra * ya._length, top); + bottom = Math.min((1 + extra) * ya._length, bottom); + var imageWidth = Math.round(right - left); + var imageHeight = Math.round(bottom - top); + + // if image is entirely off-screen, don't even draw it + var isOffScreen = (imageWidth <= 0 || imageHeight <= 0); + if(isOffScreen) { + var noImage = plotGroup.selectAll('image').data([]); + noImage.exit().remove(); + return; + } + + // Draw each pixel + var canvas = document.createElement('canvas'); + canvas.width = imageWidth; + canvas.height = imageHeight; + var context = canvas.getContext('2d'); + + var ipx = function(i) {return Lib.constrain(Math.round(xa.c2p(x0 + i * dx) - left), 0, imageWidth);}; + var jpx = function(j) {return Lib.constrain(Math.round(ya.c2p(y0 + j * dy) - top), 0, imageHeight);}; + + var fmt = constants.colormodel[trace.colormodel].fmt; + var c; + for(i = 0; i < cd0.w; i++) { + var ipx0 = ipx(i); var ipx1 = ipx(i + 1); + if(ipx1 === ipx0 || isNaN(ipx1) || isNaN(ipx0)) continue; + for(var j = 0; j < cd0.h; j++) { + var jpx0 = jpx(j); var jpx1 = jpx(j + 1); + if(jpx1 === jpx0 || isNaN(jpx1) || isNaN(jpx0) || !z[j][i]) continue; + c = trace._scaler(z[j][i]); + if(c) { + context.fillStyle = trace.colormodel + '(' + fmt(c).join(',') + ')'; + } else { + // Return a transparent pixel + context.fillStyle = 'rgba(0,0,0,0)'; + } + context.fillRect(ipx0, jpx0, ipx1 - ipx0, jpx1 - jpx0); + } + } + + var image3 = plotGroup.selectAll('image') + .data(cd); + + image3.enter().append('svg:image').attr({ + xmlns: xmlnsNamespaces.svg, + preserveAspectRatio: 'none' + }); + + image3.attr({ + height: imageHeight, + width: imageWidth, + x: left, + y: top, + 'xlink:href': canvas.toDataURL('image/png') + }); + }); +}; + +},{"../../constants/xmlns_namespaces":150,"../../lib":169,"./constants":356,"d3":16}],362:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); + +module.exports = function style(gd) { + d3.select(gd).selectAll('.im image') + .style('opacity', function(d) { + return d.trace.opacity; + }); +}; + +},{"d3":16}],363:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var plotAttrs = _dereq_('../../plots/attributes'); +var domainAttrs = _dereq_('../../plots/domain').attributes; +var fontAttrs = _dereq_('../../plots/font_attributes'); +var colorAttrs = _dereq_('../../components/color/attributes'); +var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs; +var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs; + +var extendFlat = _dereq_('../../lib/extend').extendFlat; + +var textFontAttrs = fontAttrs({ + editType: 'plot', + arrayOk: true, + colorEditType: 'plot', + +}); + +module.exports = { + labels: { + valType: 'data_array', + editType: 'calc', + + }, + // equivalent of x0 and dx, if label is missing + label0: { + valType: 'number', + + dflt: 0, + editType: 'calc', + + }, + dlabel: { + valType: 'number', + + dflt: 1, + editType: 'calc', + + }, + + values: { + valType: 'data_array', + editType: 'calc', + + }, + + marker: { + colors: { + valType: 'data_array', // TODO 'color_array' ? + editType: 'calc', + + }, + + line: { + color: { + valType: 'color', + + dflt: colorAttrs.defaultLine, + arrayOk: true, + editType: 'style', + + }, + width: { + valType: 'number', + + min: 0, + dflt: 0, + arrayOk: true, + editType: 'style', + + }, + editType: 'calc' + }, + editType: 'calc' + }, + + text: { + valType: 'data_array', + editType: 'plot', + + }, + hovertext: { + valType: 'string', + + dflt: '', + arrayOk: true, + editType: 'style', + + }, + +// 'see eg:' +// 'https://www.e-education.psu.edu/natureofgeoinfo/sites/www.e-education.psu.edu.natureofgeoinfo/files/image/hisp_pies.gif', +// '(this example involves a map too - may someday be a whole trace type', +// 'of its own. but the point is the size of the whole pie is important.)' + scalegroup: { + valType: 'string', + + dflt: '', + editType: 'calc', + + }, + + // labels (legend is handled by plots.attributes.showlegend and layout.hiddenlabels) + textinfo: { + valType: 'flaglist', + + flags: ['label', 'text', 'value', 'percent'], + extras: ['none'], + editType: 'calc', + + }, + hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { + flags: ['label', 'text', 'value', 'percent', 'name'] + }), + hovertemplate: hovertemplateAttrs({}, { + keys: ['label', 'color', 'value', 'percent', 'text'] + }), + texttemplate: texttemplateAttrs({editType: 'plot'}, { + keys: ['label', 'color', 'value', 'percent', 'text'] + }), + textposition: { + valType: 'enumerated', + + values: ['inside', 'outside', 'auto', 'none'], + dflt: 'auto', + arrayOk: true, + editType: 'plot', + + }, + textfont: extendFlat({}, textFontAttrs, { + + }), + insidetextfont: extendFlat({}, textFontAttrs, { + + }), + outsidetextfont: extendFlat({}, textFontAttrs, { + + }), + automargin: { + valType: 'boolean', + dflt: false, + + editType: 'plot', + + }, + + title: { + text: { + valType: 'string', + dflt: '', + + editType: 'plot', + + }, + font: extendFlat({}, textFontAttrs, { + + }), + position: { + valType: 'enumerated', + values: [ + 'top left', 'top center', 'top right', + 'middle center', + 'bottom left', 'bottom center', 'bottom right' + ], + + editType: 'plot', + + }, + + editType: 'plot' + }, + + // position and shape + domain: domainAttrs({name: 'pie', trace: true, editType: 'calc'}), + + hole: { + valType: 'number', + + min: 0, + max: 1, + dflt: 0, + editType: 'calc', + + }, + + // ordering and direction + sort: { + valType: 'boolean', + + dflt: true, + editType: 'calc', + + }, + direction: { + /** + * there are two common conventions, both of which place the first + * (largest, if sorted) slice with its left edge at 12 o'clock but + * succeeding slices follow either cw or ccw from there. + * + * see http://visage.co/data-visualization-101-pie-charts/ + */ + valType: 'enumerated', + values: ['clockwise', 'counterclockwise'], + + dflt: 'counterclockwise', + editType: 'calc', + + }, + rotation: { + valType: 'number', + + min: -360, + max: 360, + dflt: 0, + editType: 'calc', + + }, + + pull: { + valType: 'number', + + min: 0, + max: 1, + dflt: 0, + arrayOk: true, + editType: 'calc', + + }, + + _deprecated: { + title: { + valType: 'string', + dflt: '', + + editType: 'calc', + + }, + titlefont: extendFlat({}, textFontAttrs, { + + }), + titleposition: { + valType: 'enumerated', + values: [ + 'top left', 'top center', 'top right', + 'middle center', + 'bottom left', 'bottom center', 'bottom right' + ], + + editType: 'calc', + + } + } +}; + +},{"../../components/color/attributes":50,"../../lib/extend":164,"../../plots/attributes":210,"../../plots/domain":238,"../../plots/font_attributes":239,"../../plots/template_attributes":253}],364:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var plots = _dereq_('../../plots/plots'); + +exports.name = 'pie'; + +exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) { + plots.plotBasePlot(exports.name, gd, traces, transitionOpts, makeOnCompleteCallback); +}; + +exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) { + plots.cleanBasePlot(exports.name, newFullData, newFullLayout, oldFullData, oldFullLayout); +}; + +},{"../../plots/plots":245}],365:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); +var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray; +var tinycolor = _dereq_('tinycolor2'); + +var Color = _dereq_('../../components/color'); + +var extendedColorWayList = {}; + +function calc(gd, trace) { + var cd = []; + + var fullLayout = gd._fullLayout; + var hiddenLabels = fullLayout.hiddenlabels || []; + + var labels = trace.labels; + var colors = trace.marker.colors || []; + var vals = trace.values; + var hasVals = isArrayOrTypedArray(vals) && vals.length; + + var i, pt; + + if(trace.dlabel) { + labels = new Array(vals.length); + for(i = 0; i < vals.length; i++) { + labels[i] = String(trace.label0 + i * trace.dlabel); + } + } + + var allThisTraceLabels = {}; + var pullColor = makePullColorFn(fullLayout['_' + trace.type + 'colormap']); + var seriesLen = (hasVals ? vals : labels).length; + var vTotal = 0; + var isAggregated = false; + + for(i = 0; i < seriesLen; i++) { + var v, label, hidden; + if(hasVals) { + v = vals[i]; + if(!isNumeric(v)) continue; + v = +v; + if(v < 0) continue; + } else v = 1; + + label = labels[i]; + if(label === undefined || label === '') label = i; + label = String(label); + + var thisLabelIndex = allThisTraceLabels[label]; + if(thisLabelIndex === undefined) { + allThisTraceLabels[label] = cd.length; + + hidden = hiddenLabels.indexOf(label) !== -1; + + if(!hidden) vTotal += v; + + cd.push({ + v: v, + label: label, + color: pullColor(colors[i], label), + i: i, + pts: [i], + hidden: hidden + }); + } else { + isAggregated = true; + + pt = cd[thisLabelIndex]; + pt.v += v; + pt.pts.push(i); + if(!pt.hidden) vTotal += v; + + if(pt.color === false && colors[i]) { + pt.color = pullColor(colors[i], label); + } + } + } + + var shouldSort = (trace.type === 'funnelarea') ? isAggregated : trace.sort; + if(shouldSort) cd.sort(function(a, b) { return b.v - a.v; }); + + // include the sum of all values in the first point + if(cd[0]) cd[0].vTotal = vTotal; + + return cd; +} + +function makePullColorFn(colorMap) { + return function pullColor(color, id) { + if(!color) return false; + + color = tinycolor(color); + if(!color.isValid()) return false; + + color = Color.addOpacity(color, color.getAlpha()); + if(!colorMap[id]) colorMap[id] = color; + + return color; + }; +} + +/* + * `calc` filled in (and collated) explicit colors. + * Now we need to propagate these explicit colors to other traces, + * and fill in default colors. + * This is done after sorting, so we pick defaults + * in the order slices will be displayed + */ +function crossTraceCalc(gd, plotinfo) { // TODO: should we name the second argument opts? + var desiredType = (plotinfo || {}).type; + if(!desiredType) desiredType = 'pie'; + + var fullLayout = gd._fullLayout; + var calcdata = gd.calcdata; + var colorWay = fullLayout[desiredType + 'colorway']; + var colorMap = fullLayout['_' + desiredType + 'colormap']; + + if(fullLayout['extend' + desiredType + 'colors']) { + colorWay = generateExtendedColors(colorWay, extendedColorWayList); + } + var dfltColorCount = 0; + + for(var i = 0; i < calcdata.length; i++) { + var cd = calcdata[i]; + var traceType = cd[0].trace.type; + if(traceType !== desiredType) continue; + + for(var j = 0; j < cd.length; j++) { + var pt = cd[j]; + if(pt.color === false) { + // have we seen this label and assigned a color to it in a previous trace? + if(colorMap[pt.label]) { + pt.color = colorMap[pt.label]; + } else { + colorMap[pt.label] = pt.color = colorWay[dfltColorCount % colorWay.length]; + dfltColorCount++; + } + } + } + } +} + +/** + * pick a default color from the main default set, augmented by + * itself lighter then darker before repeating + */ +function generateExtendedColors(colorList, extendedColorWays) { + var i; + var colorString = JSON.stringify(colorList); + var colors = extendedColorWays[colorString]; + if(!colors) { + colors = colorList.slice(); + + for(i = 0; i < colorList.length; i++) { + colors.push(tinycolor(colorList[i]).lighten(20).toHexString()); + } + + for(i = 0; i < colorList.length; i++) { + colors.push(tinycolor(colorList[i]).darken(20).toHexString()); + } + extendedColorWays[colorString] = colors; + } + + return colors; +} + +module.exports = { + calc: calc, + crossTraceCalc: crossTraceCalc, + + makePullColorFn: makePullColorFn, + generateExtendedColors: generateExtendedColors +}; + +},{"../../components/color":51,"../../lib":169,"fast-isnumeric":18,"tinycolor2":34}],366:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var attributes = _dereq_('./attributes'); +var handleDomainDefaults = _dereq_('../../plots/domain').defaults; +var handleText = _dereq_('../bar/defaults').handleText; + +module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { + function coerce(attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); + } + + var len; + var vals = coerce('values'); + var hasVals = Lib.isArrayOrTypedArray(vals); + var labels = coerce('labels'); + if(Array.isArray(labels)) { + len = labels.length; + if(hasVals) len = Math.min(len, vals.length); + } else if(hasVals) { + len = vals.length; + + coerce('label0'); + coerce('dlabel'); + } + + if(!len) { + traceOut.visible = false; + return; + } + traceOut._length = len; + + var lineWidth = coerce('marker.line.width'); + if(lineWidth) coerce('marker.line.color'); + + coerce('marker.colors'); + + coerce('scalegroup'); + // TODO: hole needs to be coerced to the same value within a scaleegroup + + var textData = coerce('text'); + var textTemplate = coerce('texttemplate'); + var textInfo; + if(!textTemplate) textInfo = coerce('textinfo', Array.isArray(textData) ? 'text+percent' : 'percent'); + + coerce('hovertext'); + coerce('hovertemplate'); + + if(textTemplate || (textInfo && textInfo !== 'none')) { + var textposition = coerce('textposition'); + handleText(traceIn, traceOut, layout, coerce, textposition, { + moduleHasSelected: false, + moduleHasUnselected: false, + moduleHasConstrain: false, + moduleHasCliponaxis: false, + moduleHasTextangle: false, + moduleHasInsideanchor: false + }); + + var hasBoth = Array.isArray(textposition) || textposition === 'auto'; + var hasOutside = hasBoth || textposition === 'outside'; + if(hasOutside) { + coerce('automargin'); + } + } + + handleDomainDefaults(traceOut, layout, coerce); + + var hole = coerce('hole'); + var title = coerce('title.text'); + if(title) { + var titlePosition = coerce('title.position', hole ? 'middle center' : 'top center'); + if(!hole && titlePosition === 'middle center') traceOut.title.position = 'top center'; + Lib.coerceFont(coerce, 'title.font', layout.font); + } + + coerce('sort'); + coerce('direction'); + coerce('rotation'); + coerce('pull'); +}; + +},{"../../lib":169,"../../plots/domain":238,"../bar/defaults":272,"./attributes":363}],367:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var appendArrayMultiPointValues = _dereq_('../../components/fx/helpers').appendArrayMultiPointValues; + +// Note: like other eventData routines, this creates the data for hover/unhover/click events +// but it has a different API and goes through a totally different pathway. +// So to ensure it doesn't get misused, it's not attached to the Pie module. +module.exports = function eventData(pt, trace) { + var out = { + curveNumber: trace.index, + pointNumbers: pt.pts, + data: trace._input, + fullData: trace, + label: pt.label, + color: pt.color, + value: pt.v, + percent: pt.percent, + text: pt.text, + + // pt.v (and pt.i below) for backward compatibility + v: pt.v + }; + + // Only include pointNumber if it's unambiguous + if(pt.pts.length === 1) out.pointNumber = out.i = pt.pts[0]; + + // Add extra data arrays to the output + // notice that this is the multi-point version ('s' on the end!) + // so added data will be arrays matching the pointNumbers array. + appendArrayMultiPointValues(out, trace, pt.pts); + + // don't include obsolete fields in new funnelarea traces + if(trace.type === 'funnelarea') { + delete out.v; + delete out.i; + } + + return out; +}; + +},{"../../components/fx/helpers":86}],368:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); + +exports.formatPiePercent = function formatPiePercent(v, separators) { + var vRounded = (v * 100).toPrecision(3); + if(vRounded.lastIndexOf('.') !== -1) { + vRounded = vRounded.replace(/[.]?0+$/, ''); + } + return Lib.numSeparate(vRounded, separators) + '%'; +}; + +exports.formatPieValue = function formatPieValue(v, separators) { + var vRounded = v.toPrecision(10); + if(vRounded.lastIndexOf('.') !== -1) { + vRounded = vRounded.replace(/[.]?0+$/, ''); + } + return Lib.numSeparate(vRounded, separators); +}; + +exports.getFirstFilled = function getFirstFilled(array, indices) { + if(!Array.isArray(array)) return; + for(var i = 0; i < indices.length; i++) { + var v = array[indices[i]]; + if(v || v === 0 || v === '') return v; + } +}; + +exports.castOption = function castOption(item, indices) { + if(Array.isArray(item)) return exports.getFirstFilled(item, indices); + else if(item) return item; +}; + +},{"../../lib":169}],369:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + attributes: _dereq_('./attributes'), + supplyDefaults: _dereq_('./defaults'), + supplyLayoutDefaults: _dereq_('./layout_defaults'), + layoutAttributes: _dereq_('./layout_attributes'), + + calc: _dereq_('./calc').calc, + crossTraceCalc: _dereq_('./calc').crossTraceCalc, + + plot: _dereq_('./plot').plot, + style: _dereq_('./style'), + styleOne: _dereq_('./style_one'), + + moduleType: 'trace', + name: 'pie', + basePlotModule: _dereq_('./base_plot'), + categories: ['pie-like', 'pie', 'showLegend'], + meta: { + + } +}; + +},{"./attributes":363,"./base_plot":364,"./calc":365,"./defaults":366,"./layout_attributes":370,"./layout_defaults":371,"./plot":372,"./style":373,"./style_one":374}],370:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + hiddenlabels: { + valType: 'data_array', + + editType: 'calc', + + }, + piecolorway: { + valType: 'colorlist', + + editType: 'calc', + + }, + extendpiecolors: { + valType: 'boolean', + dflt: true, + + editType: 'calc', + + } +}; + +},{}],371:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); + +var layoutAttributes = _dereq_('./layout_attributes'); + +module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) { + function coerce(attr, dflt) { + return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt); + } + + coerce('hiddenlabels'); + coerce('piecolorway', layoutOut.colorway); + coerce('extendpiecolors'); +}; + +},{"../../lib":169,"./layout_attributes":370}],372:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); + +var Plots = _dereq_('../../plots/plots'); +var Fx = _dereq_('../../components/fx'); +var Color = _dereq_('../../components/color'); +var Drawing = _dereq_('../../components/drawing'); +var Lib = _dereq_('../../lib'); +var svgTextUtils = _dereq_('../../lib/svg_text_utils'); + +var helpers = _dereq_('./helpers'); +var eventData = _dereq_('./event_data'); +var isValidTextValue = _dereq_('../../lib').isValidTextValue; + +function plot(gd, cdModule) { + var fullLayout = gd._fullLayout; + var gs = fullLayout._size; + + prerenderTitles(cdModule, gd); + layoutAreas(cdModule, gs); + + var plotGroups = Lib.makeTraceGroups(fullLayout._pielayer, cdModule, 'trace').each(function(cd) { + var plotGroup = d3.select(this); + var cd0 = cd[0]; + var trace = cd0.trace; + + setCoords(cd); + + // TODO: miter might look better but can sometimes cause problems + // maybe miter with a small-ish stroke-miterlimit? + plotGroup.attr('stroke-linejoin', 'round'); + + plotGroup.each(function() { + var slices = d3.select(this).selectAll('g.slice').data(cd); + + slices.enter().append('g') + .classed('slice', true); + slices.exit().remove(); + + var quadrants = [ + [[], []], // y<0: x<0, x>=0 + [[], []] // y>=0: x<0, x>=0 + ]; + var hasOutsideText = false; + + slices.each(function(pt) { + if(pt.hidden) { + d3.select(this).selectAll('path,g').remove(); + return; + } + + // to have consistent event data compared to other traces + pt.pointNumber = pt.i; + pt.curveNumber = trace.index; + + quadrants[pt.pxmid[1] < 0 ? 0 : 1][pt.pxmid[0] < 0 ? 0 : 1].push(pt); + + var cx = cd0.cx; + var cy = cd0.cy; + var sliceTop = d3.select(this); + var slicePath = sliceTop.selectAll('path.surface').data([pt]); + + slicePath.enter().append('path') + .classed('surface', true) + .style({'pointer-events': 'all'}); + + sliceTop.call(attachFxHandlers, gd, cd); + + if(trace.pull) { + var pull = +helpers.castOption(trace.pull, pt.pts) || 0; + if(pull > 0) { + cx += pull * pt.pxmid[0]; + cy += pull * pt.pxmid[1]; + } + } + + pt.cxFinal = cx; + pt.cyFinal = cy; + + function arc(start, finish, cw, scale) { + var dx = scale * (finish[0] - start[0]); + var dy = scale * (finish[1] - start[1]); + + return 'a' + + (scale * cd0.r) + ',' + (scale * cd0.r) + ' 0 ' + + pt.largeArc + (cw ? ' 1 ' : ' 0 ') + dx + ',' + dy; + } + + var hole = trace.hole; + if(pt.v === cd0.vTotal) { // 100% fails bcs arc start and end are identical + var outerCircle = 'M' + (cx + pt.px0[0]) + ',' + (cy + pt.px0[1]) + + arc(pt.px0, pt.pxmid, true, 1) + + arc(pt.pxmid, pt.px0, true, 1) + 'Z'; + if(hole) { + slicePath.attr('d', + 'M' + (cx + hole * pt.px0[0]) + ',' + (cy + hole * pt.px0[1]) + + arc(pt.px0, pt.pxmid, false, hole) + + arc(pt.pxmid, pt.px0, false, hole) + + 'Z' + outerCircle); + } else slicePath.attr('d', outerCircle); + } else { + var outerArc = arc(pt.px0, pt.px1, true, 1); + + if(hole) { + var rim = 1 - hole; + slicePath.attr('d', + 'M' + (cx + hole * pt.px1[0]) + ',' + (cy + hole * pt.px1[1]) + + arc(pt.px1, pt.px0, false, hole) + + 'l' + (rim * pt.px0[0]) + ',' + (rim * pt.px0[1]) + + outerArc + + 'Z'); + } else { + slicePath.attr('d', + 'M' + cx + ',' + cy + + 'l' + pt.px0[0] + ',' + pt.px0[1] + + outerArc + + 'Z'); + } + } + + // add text + formatSliceLabel(gd, pt, cd0); + var textPosition = helpers.castOption(trace.textposition, pt.pts); + var sliceTextGroup = sliceTop.selectAll('g.slicetext') + .data(pt.text && (textPosition !== 'none') ? [0] : []); + + sliceTextGroup.enter().append('g') + .classed('slicetext', true); + sliceTextGroup.exit().remove(); + + sliceTextGroup.each(function() { + var sliceText = Lib.ensureSingle(d3.select(this), 'text', '', function(s) { + // prohibit tex interpretation until we can handle + // tex and regular text together + s.attr('data-notex', 1); + }); + + sliceText.text(pt.text) + .attr({ + 'class': 'slicetext', + transform: '', + 'text-anchor': 'middle' + }) + .call(Drawing.font, textPosition === 'outside' ? + determineOutsideTextFont(trace, pt, gd._fullLayout.font) : + determineInsideTextFont(trace, pt, gd._fullLayout.font)) + .call(svgTextUtils.convertToTspans, gd); + + // position the text relative to the slice + var textBB = Drawing.bBox(sliceText.node()); + var transform; + + if(textPosition === 'outside') { + transform = transformOutsideText(textBB, pt); + } else { + transform = transformInsideText(textBB, pt, cd0); + if(textPosition === 'auto' && transform.scale < 1) { + sliceText.call(Drawing.font, trace.outsidetextfont); + if(trace.outsidetextfont.family !== trace.insidetextfont.family || + trace.outsidetextfont.size !== trace.insidetextfont.size) { + textBB = Drawing.bBox(sliceText.node()); + } + transform = transformOutsideText(textBB, pt); + } + } + + var translateX = cx + pt.pxmid[0] * transform.rCenter + (transform.x || 0); + var translateY = cy + pt.pxmid[1] * transform.rCenter + (transform.y || 0); + + // save some stuff to use later ensure no labels overlap + if(transform.outside) { + pt.yLabelMin = translateY - textBB.height / 2; + pt.yLabelMid = translateY; + pt.yLabelMax = translateY + textBB.height / 2; + pt.labelExtraX = 0; + pt.labelExtraY = 0; + hasOutsideText = true; + } + + sliceText.attr('transform', + 'translate(' + translateX + ',' + translateY + ')' + + (transform.scale < 1 ? ('scale(' + transform.scale + ')') : '') + + (transform.rotate ? ('rotate(' + transform.rotate + ')') : '') + + 'translate(' + + (-(textBB.left + textBB.right) / 2) + ',' + + (-(textBB.top + textBB.bottom) / 2) + + ')'); + }); + }); + + // add the title + var titleTextGroup = d3.select(this).selectAll('g.titletext') + .data(trace.title.text ? [0] : []); + + titleTextGroup.enter().append('g') + .classed('titletext', true); + titleTextGroup.exit().remove(); + + titleTextGroup.each(function() { + var titleText = Lib.ensureSingle(d3.select(this), 'text', '', function(s) { + // prohibit tex interpretation as above + s.attr('data-notex', 1); + }); + + var txt = trace.title.text; + if(trace._meta) { + txt = Lib.templateString(txt, trace._meta); + } + + titleText.text(txt) + .attr({ + 'class': 'titletext', + transform: '', + 'text-anchor': 'middle', + }) + .call(Drawing.font, trace.title.font) + .call(svgTextUtils.convertToTspans, gd); + + var transform; + + if(trace.title.position === 'middle center') { + transform = positionTitleInside(cd0); + } else { + transform = positionTitleOutside(cd0, gs); + } + + titleText.attr('transform', + 'translate(' + transform.x + ',' + transform.y + ')' + + (transform.scale < 1 ? ('scale(' + transform.scale + ')') : '') + + 'translate(' + transform.tx + ',' + transform.ty + ')'); + }); + + // now make sure no labels overlap (at least within one pie) + if(hasOutsideText) scootLabels(quadrants, trace); + + plotTextLines(slices, trace); + + if(hasOutsideText && trace.automargin) { + // TODO if we ever want to improve perf, + // we could reuse the textBB computed above together + // with the sliceText transform info + var traceBbox = Drawing.bBox(plotGroup.node()); + + var domain = trace.domain; + var vpw = gs.w * (domain.x[1] - domain.x[0]); + var vph = gs.h * (domain.y[1] - domain.y[0]); + var xgap = (0.5 * vpw - cd0.r) / gs.w; + var ygap = (0.5 * vph - cd0.r) / gs.h; + + Plots.autoMargin(gd, 'pie.' + trace.uid + '.automargin', { + xl: domain.x[0] - xgap, + xr: domain.x[1] + xgap, + yb: domain.y[0] - ygap, + yt: domain.y[1] + ygap, + l: Math.max(cd0.cx - cd0.r - traceBbox.left, 0), + r: Math.max(traceBbox.right - (cd0.cx + cd0.r), 0), + b: Math.max(traceBbox.bottom - (cd0.cy + cd0.r), 0), + t: Math.max(cd0.cy - cd0.r - traceBbox.top, 0), + pad: 5 + }); + } + }); + }); + + // This is for a bug in Chrome (as of 2015-07-22, and does not affect FF) + // if insidetextfont and outsidetextfont are different sizes, sometimes the size + // of an "em" gets taken from the wrong element at first so lines are + // spaced wrong. You just have to tell it to try again later and it gets fixed. + // I have no idea why we haven't seen this in other contexts. Also, sometimes + // it gets the initial draw correct but on redraw it gets confused. + setTimeout(function() { + plotGroups.selectAll('tspan').each(function() { + var s = d3.select(this); + if(s.attr('dy')) s.attr('dy', s.attr('dy')); + }); + }, 0); +} + +// TODO add support for transition +function plotTextLines(slices, trace) { + slices.each(function(pt) { + var sliceTop = d3.select(this); + + if(!pt.labelExtraX && !pt.labelExtraY) { + sliceTop.select('path.textline').remove(); + return; + } + + // first move the text to its new location + var sliceText = sliceTop.select('g.slicetext text'); + + sliceText.attr('transform', 'translate(' + pt.labelExtraX + ',' + pt.labelExtraY + ')' + + sliceText.attr('transform')); + + // then add a line to the new location + var lineStartX = pt.cxFinal + pt.pxmid[0]; + var lineStartY = pt.cyFinal + pt.pxmid[1]; + var textLinePath = 'M' + lineStartX + ',' + lineStartY; + var finalX = (pt.yLabelMax - pt.yLabelMin) * (pt.pxmid[0] < 0 ? -1 : 1) / 4; + + if(pt.labelExtraX) { + var yFromX = pt.labelExtraX * pt.pxmid[1] / pt.pxmid[0]; + var yNet = pt.yLabelMid + pt.labelExtraY - (pt.cyFinal + pt.pxmid[1]); + + if(Math.abs(yFromX) > Math.abs(yNet)) { + textLinePath += + 'l' + (yNet * pt.pxmid[0] / pt.pxmid[1]) + ',' + yNet + + 'H' + (lineStartX + pt.labelExtraX + finalX); + } else { + textLinePath += 'l' + pt.labelExtraX + ',' + yFromX + + 'v' + (yNet - yFromX) + + 'h' + finalX; + } + } else { + textLinePath += + 'V' + (pt.yLabelMid + pt.labelExtraY) + + 'h' + finalX; + } + + Lib.ensureSingle(sliceTop, 'path', 'textline') + .call(Color.stroke, trace.outsidetextfont.color) + .attr({ + 'stroke-width': Math.min(2, trace.outsidetextfont.size / 8), + d: textLinePath, + fill: 'none' + }); + }); +} + +function attachFxHandlers(sliceTop, gd, cd) { + var cd0 = cd[0]; + var trace = cd0.trace; + var cx = cd0.cx; + var cy = cd0.cy; + + // hover state vars + // have we drawn a hover label, so it should be cleared later + if(!('_hasHoverLabel' in trace)) trace._hasHoverLabel = false; + // have we emitted a hover event, so later an unhover event should be emitted + // note that click events do not depend on this - you can still get them + // with hovermode: false or if you were earlier dragging, then clicked + // in the same slice that you moused up in + if(!('_hasHoverEvent' in trace)) trace._hasHoverEvent = false; + + sliceTop.on('mouseover', function(pt) { + // in case fullLayout or fullData has changed without a replot + var fullLayout2 = gd._fullLayout; + var trace2 = gd._fullData[trace.index]; + + if(gd._dragging || fullLayout2.hovermode === false) return; + + var hoverinfo = trace2.hoverinfo; + if(Array.isArray(hoverinfo)) { + // super hacky: we need to pull out the *first* hoverinfo from + // pt.pts, then put it back into an array in a dummy trace + // and call castHoverinfo on that. + // TODO: do we want to have Fx.castHoverinfo somehow handle this? + // it already takes an array for index, for 2D, so this seems tricky. + hoverinfo = Fx.castHoverinfo({ + hoverinfo: [helpers.castOption(hoverinfo, pt.pts)], + _module: trace._module + }, fullLayout2, 0); + } + + if(hoverinfo === 'all') hoverinfo = 'label+text+value+percent+name'; + + // in case we dragged over the pie from another subplot, + // or if hover is turned off + if(trace2.hovertemplate || (hoverinfo !== 'none' && hoverinfo !== 'skip' && hoverinfo)) { + var rInscribed = pt.rInscribed || 0; + var hoverCenterX = cx + pt.pxmid[0] * (1 - rInscribed); + var hoverCenterY = cy + pt.pxmid[1] * (1 - rInscribed); + var separators = fullLayout2.separators; + var text = []; + + if(hoverinfo && hoverinfo.indexOf('label') !== -1) text.push(pt.label); + pt.text = helpers.castOption(trace2.hovertext || trace2.text, pt.pts); + if(hoverinfo && hoverinfo.indexOf('text') !== -1) { + var tx = pt.text; + if(Lib.isValidTextValue(tx)) text.push(tx); + } + pt.value = pt.v; + pt.valueLabel = helpers.formatPieValue(pt.v, separators); + if(hoverinfo && hoverinfo.indexOf('value') !== -1) text.push(pt.valueLabel); + pt.percent = pt.v / cd0.vTotal; + pt.percentLabel = helpers.formatPiePercent(pt.percent, separators); + if(hoverinfo && hoverinfo.indexOf('percent') !== -1) text.push(pt.percentLabel); + + var hoverLabel = trace2.hoverlabel; + var hoverFont = hoverLabel.font; + + Fx.loneHover({ + trace: trace, + x0: hoverCenterX - rInscribed * cd0.r, + x1: hoverCenterX + rInscribed * cd0.r, + y: hoverCenterY, + text: text.join('
'), + name: (trace2.hovertemplate || hoverinfo.indexOf('name') !== -1) ? trace2.name : undefined, + idealAlign: pt.pxmid[0] < 0 ? 'left' : 'right', + color: helpers.castOption(hoverLabel.bgcolor, pt.pts) || pt.color, + borderColor: helpers.castOption(hoverLabel.bordercolor, pt.pts), + fontFamily: helpers.castOption(hoverFont.family, pt.pts), + fontSize: helpers.castOption(hoverFont.size, pt.pts), + fontColor: helpers.castOption(hoverFont.color, pt.pts), + nameLength: helpers.castOption(hoverLabel.namelength, pt.pts), + textAlign: helpers.castOption(hoverLabel.align, pt.pts), + hovertemplate: helpers.castOption(trace2.hovertemplate, pt.pts), + hovertemplateLabels: pt, + eventData: [eventData(pt, trace2)] + }, { + container: fullLayout2._hoverlayer.node(), + outerContainer: fullLayout2._paper.node(), + gd: gd + }); + + trace._hasHoverLabel = true; + } + + trace._hasHoverEvent = true; + gd.emit('plotly_hover', { + points: [eventData(pt, trace2)], + event: d3.event + }); + }); + + sliceTop.on('mouseout', function(evt) { + var fullLayout2 = gd._fullLayout; + var trace2 = gd._fullData[trace.index]; + var pt = d3.select(this).datum(); + + if(trace._hasHoverEvent) { + evt.originalEvent = d3.event; + gd.emit('plotly_unhover', { + points: [eventData(pt, trace2)], + event: d3.event + }); + trace._hasHoverEvent = false; + } + + if(trace._hasHoverLabel) { + Fx.loneUnhover(fullLayout2._hoverlayer.node()); + trace._hasHoverLabel = false; + } + }); + + sliceTop.on('click', function(pt) { + // TODO: this does not support right-click. If we want to support it, we + // would likely need to change pie to use dragElement instead of straight + // mapbox event binding. Or perhaps better, make a simple wrapper with the + // right mousedown, mousemove, and mouseup handlers just for a left/right click + // mapbox would use this too. + var fullLayout2 = gd._fullLayout; + var trace2 = gd._fullData[trace.index]; + + if(gd._dragging || fullLayout2.hovermode === false) return; + + gd._hoverdata = [eventData(pt, trace2)]; + Fx.click(gd, d3.event); + }); +} + +function determineOutsideTextFont(trace, pt, layoutFont) { + var color = + helpers.castOption(trace.outsidetextfont.color, pt.pts) || + helpers.castOption(trace.textfont.color, pt.pts) || + layoutFont.color; + + var family = + helpers.castOption(trace.outsidetextfont.family, pt.pts) || + helpers.castOption(trace.textfont.family, pt.pts) || + layoutFont.family; + + var size = + helpers.castOption(trace.outsidetextfont.size, pt.pts) || + helpers.castOption(trace.textfont.size, pt.pts) || + layoutFont.size; + + return { + color: color, + family: family, + size: size + }; +} + +function determineInsideTextFont(trace, pt, layoutFont) { + var customColor = helpers.castOption(trace.insidetextfont.color, pt.pts); + if(!customColor && trace._input.textfont) { + // Why not simply using trace.textfont? Because if not set, it + // defaults to layout.font which has a default color. But if + // textfont.color and insidetextfont.color don't supply a value, + // a contrasting color shall be used. + customColor = helpers.castOption(trace._input.textfont.color, pt.pts); + } + + var family = + helpers.castOption(trace.insidetextfont.family, pt.pts) || + helpers.castOption(trace.textfont.family, pt.pts) || + layoutFont.family; + + var size = + helpers.castOption(trace.insidetextfont.size, pt.pts) || + helpers.castOption(trace.textfont.size, pt.pts) || + layoutFont.size; + + return { + color: customColor || Color.contrast(pt.color), + family: family, + size: size + }; +} + +function prerenderTitles(cdModule, gd) { + var cd0, trace; + + // Determine the width and height of the title for each pie. + for(var i = 0; i < cdModule.length; i++) { + cd0 = cdModule[i][0]; + trace = cd0.trace; + + if(trace.title.text) { + var txt = trace.title.text; + if(trace._meta) { + txt = Lib.templateString(txt, trace._meta); + } + + var dummyTitle = Drawing.tester.append('text') + .attr('data-notex', 1) + .text(txt) + .call(Drawing.font, trace.title.font) + .call(svgTextUtils.convertToTspans, gd); + var bBox = Drawing.bBox(dummyTitle.node(), true); + cd0.titleBox = { + width: bBox.width, + height: bBox.height, + }; + dummyTitle.remove(); + } + } +} + +function transformInsideText(textBB, pt, cd0) { + var textDiameter = Math.sqrt(textBB.width * textBB.width + textBB.height * textBB.height); + var textAspect = textBB.width / textBB.height; + var halfAngle = pt.halfangle; + var ring = pt.ring; + var rInscribed = pt.rInscribed; + var r = cd0.r || pt.rpx1; + + // max size text can be inserted inside without rotating it + // this inscribes the text rectangle in a circle, which is then inscribed + // in the slice, so it will be an underestimate, which some day we may want + // to improve so this case can get more use + var transform = { + scale: rInscribed * r * 2 / textDiameter, + + // and the center position and rotation in this case + rCenter: 1 - rInscribed, + rotate: 0 + }; + + if(transform.scale >= 1) return transform; + + // max size if text is rotated radially + var Qr = textAspect + 1 / (2 * Math.tan(halfAngle)); + var maxHalfHeightRotRadial = r * Math.min( + 1 / (Math.sqrt(Qr * Qr + 0.5) + Qr), + ring / (Math.sqrt(textAspect * textAspect + ring / 2) + textAspect) + ); + var radialTransform = { + scale: maxHalfHeightRotRadial * 2 / textBB.height, + rCenter: Math.cos(maxHalfHeightRotRadial / r) - + maxHalfHeightRotRadial * textAspect / r, + rotate: (180 / Math.PI * pt.midangle + 720) % 180 - 90 + }; + + // max size if text is rotated tangentially + var aspectInv = 1 / textAspect; + var Qt = aspectInv + 1 / (2 * Math.tan(halfAngle)); + var maxHalfWidthTangential = r * Math.min( + 1 / (Math.sqrt(Qt * Qt + 0.5) + Qt), + ring / (Math.sqrt(aspectInv * aspectInv + ring / 2) + aspectInv) + ); + var tangentialTransform = { + scale: maxHalfWidthTangential * 2 / textBB.width, + rCenter: Math.cos(maxHalfWidthTangential / r) - + maxHalfWidthTangential / textAspect / r, + rotate: (180 / Math.PI * pt.midangle + 810) % 180 - 90 + }; + // if we need a rotated transform, pick the biggest one + // even if both are bigger than 1 + var rotatedTransform = tangentialTransform.scale > radialTransform.scale ? + tangentialTransform : radialTransform; + + if(transform.scale < 1 && rotatedTransform.scale > transform.scale) return rotatedTransform; + return transform; +} + +function getInscribedRadiusFraction(pt, cd0) { + if(pt.v === cd0.vTotal && !cd0.trace.hole) return 1;// special case of 100% with no hole + + return Math.min(1 / (1 + 1 / Math.sin(pt.halfangle)), pt.ring / 2); +} + +function transformOutsideText(textBB, pt) { + var x = pt.pxmid[0]; + var y = pt.pxmid[1]; + var dx = textBB.width / 2; + var dy = textBB.height / 2; + + if(x < 0) dx *= -1; + if(y < 0) dy *= -1; + + return { + scale: 1, + rCenter: 1, + rotate: 0, + x: dx + Math.abs(dy) * (dx > 0 ? 1 : -1) / 2, + y: dy / (1 + x * x / (y * y)), + outside: true + }; +} + +function positionTitleInside(cd0) { + var textDiameter = + Math.sqrt(cd0.titleBox.width * cd0.titleBox.width + cd0.titleBox.height * cd0.titleBox.height); + return { + x: cd0.cx, + y: cd0.cy, + scale: cd0.trace.hole * cd0.r * 2 / textDiameter, + tx: 0, + ty: - cd0.titleBox.height / 2 + cd0.trace.title.font.size + }; +} + +function positionTitleOutside(cd0, plotSize) { + var scaleX = 1; + var scaleY = 1; + var maxPull; + + var trace = cd0.trace; + // position of the baseline point of the text box in the plot, before scaling. + // we anchored the text in the middle, so the baseline is on the bottom middle + // of the first line of text. + var topMiddle = { + x: cd0.cx, + y: cd0.cy + }; + // relative translation of the text box after scaling + var translate = { + tx: 0, + ty: 0 + }; + + // we reason below as if the baseline is the top middle point of the text box. + // so we must add the font size to approximate the y-coord. of the top. + // note that this correction must happen after scaling. + translate.ty += trace.title.font.size; + maxPull = getMaxPull(trace); + + if(trace.title.position.indexOf('top') !== -1) { + topMiddle.y -= (1 + maxPull) * cd0.r; + translate.ty -= cd0.titleBox.height; + } else if(trace.title.position.indexOf('bottom') !== -1) { + topMiddle.y += (1 + maxPull) * cd0.r; + } + + var rx = applyAspectRatio(cd0.r, cd0.trace.aspectratio); + + var maxWidth = plotSize.w * (trace.domain.x[1] - trace.domain.x[0]) / 2; + if(trace.title.position.indexOf('left') !== -1) { + // we start the text at the left edge of the pie + maxWidth = maxWidth + rx; + topMiddle.x -= (1 + maxPull) * rx; + translate.tx += cd0.titleBox.width / 2; + } else if(trace.title.position.indexOf('center') !== -1) { + maxWidth *= 2; + } else if(trace.title.position.indexOf('right') !== -1) { + maxWidth = maxWidth + rx; + topMiddle.x += (1 + maxPull) * rx; + translate.tx -= cd0.titleBox.width / 2; + } + scaleX = maxWidth / cd0.titleBox.width; + scaleY = getTitleSpace(cd0, plotSize) / cd0.titleBox.height; + return { + x: topMiddle.x, + y: topMiddle.y, + scale: Math.min(scaleX, scaleY), + tx: translate.tx, + ty: translate.ty + }; +} + +function applyAspectRatio(x, aspectratio) { + return x / ((aspectratio === undefined) ? 1 : aspectratio); +} + +function getTitleSpace(cd0, plotSize) { + var trace = cd0.trace; + var pieBoxHeight = plotSize.h * (trace.domain.y[1] - trace.domain.y[0]); + // use at most half of the plot for the title + return Math.min(cd0.titleBox.height, pieBoxHeight / 2); +} + +function getMaxPull(trace) { + var maxPull = trace.pull; + if(!maxPull) return 0; + + var j; + if(Array.isArray(maxPull)) { + maxPull = 0; + for(j = 0; j < trace.pull.length; j++) { + if(trace.pull[j] > maxPull) maxPull = trace.pull[j]; + } + } + return maxPull; +} + +function scootLabels(quadrants, trace) { + var xHalf, yHalf, equatorFirst, farthestX, farthestY, + xDiffSign, yDiffSign, thisQuad, oppositeQuad, + wholeSide, i, thisQuadOutside, firstOppositeOutsidePt; + + function topFirst(a, b) { return a.pxmid[1] - b.pxmid[1]; } + function bottomFirst(a, b) { return b.pxmid[1] - a.pxmid[1]; } + + function scootOneLabel(thisPt, prevPt) { + if(!prevPt) prevPt = {}; + + var prevOuterY = prevPt.labelExtraY + (yHalf ? prevPt.yLabelMax : prevPt.yLabelMin); + var thisInnerY = yHalf ? thisPt.yLabelMin : thisPt.yLabelMax; + var thisOuterY = yHalf ? thisPt.yLabelMax : thisPt.yLabelMin; + var thisSliceOuterY = thisPt.cyFinal + farthestY(thisPt.px0[1], thisPt.px1[1]); + var newExtraY = prevOuterY - thisInnerY; + + var xBuffer, i, otherPt, otherOuterY, otherOuterX, newExtraX; + + // make sure this label doesn't overlap other labels + // this *only* has us move these labels vertically + if(newExtraY * yDiffSign > 0) thisPt.labelExtraY = newExtraY; + + // make sure this label doesn't overlap any slices + if(!Array.isArray(trace.pull)) return; // this can only happen with array pulls + + for(i = 0; i < wholeSide.length; i++) { + otherPt = wholeSide[i]; + + // overlap can only happen if the other point is pulled more than this one + if(otherPt === thisPt || ( + (helpers.castOption(trace.pull, thisPt.pts) || 0) >= + (helpers.castOption(trace.pull, otherPt.pts) || 0)) + ) { + continue; + } + + if((thisPt.pxmid[1] - otherPt.pxmid[1]) * yDiffSign > 0) { + // closer to the equator - by construction all of these happen first + // move the text vertically to get away from these slices + otherOuterY = otherPt.cyFinal + farthestY(otherPt.px0[1], otherPt.px1[1]); + newExtraY = otherOuterY - thisInnerY - thisPt.labelExtraY; + + if(newExtraY * yDiffSign > 0) thisPt.labelExtraY += newExtraY; + } else if((thisOuterY + thisPt.labelExtraY - thisSliceOuterY) * yDiffSign > 0) { + // farther from the equator - happens after we've done all the + // vertical moving we're going to do + // move horizontally to get away from these more polar slices + + // if we're moving horz. based on a slice that's several slices away from this one + // then we need some extra space for the lines to labels between them + xBuffer = 3 * xDiffSign * Math.abs(i - wholeSide.indexOf(thisPt)); + + otherOuterX = otherPt.cxFinal + farthestX(otherPt.px0[0], otherPt.px1[0]); + newExtraX = otherOuterX + xBuffer - (thisPt.cxFinal + thisPt.pxmid[0]) - thisPt.labelExtraX; + + if(newExtraX * xDiffSign > 0) thisPt.labelExtraX += newExtraX; + } + } + } + + for(yHalf = 0; yHalf < 2; yHalf++) { + equatorFirst = yHalf ? topFirst : bottomFirst; + farthestY = yHalf ? Math.max : Math.min; + yDiffSign = yHalf ? 1 : -1; + + for(xHalf = 0; xHalf < 2; xHalf++) { + farthestX = xHalf ? Math.max : Math.min; + xDiffSign = xHalf ? 1 : -1; + + // first sort the array + // note this is a copy of cd, so cd itself doesn't get sorted + // but we can still modify points in place. + thisQuad = quadrants[yHalf][xHalf]; + thisQuad.sort(equatorFirst); + + oppositeQuad = quadrants[1 - yHalf][xHalf]; + wholeSide = oppositeQuad.concat(thisQuad); + + thisQuadOutside = []; + for(i = 0; i < thisQuad.length; i++) { + if(thisQuad[i].yLabelMid !== undefined) thisQuadOutside.push(thisQuad[i]); + } + + firstOppositeOutsidePt = false; + for(i = 0; yHalf && i < oppositeQuad.length; i++) { + if(oppositeQuad[i].yLabelMid !== undefined) { + firstOppositeOutsidePt = oppositeQuad[i]; + break; + } + } + + // each needs to avoid the previous + for(i = 0; i < thisQuadOutside.length; i++) { + var prevPt = i && thisQuadOutside[i - 1]; + // bottom half needs to avoid the first label of the top half + // top half we still need to call scootOneLabel on the first slice + // so we can avoid other slices, but we don't pass a prevPt + if(firstOppositeOutsidePt && !i) prevPt = firstOppositeOutsidePt; + scootOneLabel(thisQuadOutside[i], prevPt); + } + } + } +} + +function layoutAreas(cdModule, plotSize) { + var scaleGroups = []; + + // figure out the center and maximum radius + for(var i = 0; i < cdModule.length; i++) { + var cd0 = cdModule[i][0]; + var trace = cd0.trace; + + var domain = trace.domain; + var width = plotSize.w * (domain.x[1] - domain.x[0]); + var height = plotSize.h * (domain.y[1] - domain.y[0]); + // leave some space for the title, if it will be displayed outside + if(trace.title.text && trace.title.position !== 'middle center') { + height -= getTitleSpace(cd0, plotSize); + } + + var rx = width / 2; + var ry = height / 2; + if(trace.type === 'funnelarea' && !trace.scalegroup) { + ry /= trace.aspectratio; + } + + cd0.r = Math.min(rx, ry) / (1 + getMaxPull(trace)); + + cd0.cx = plotSize.l + plotSize.w * (trace.domain.x[1] + trace.domain.x[0]) / 2; + cd0.cy = plotSize.t + plotSize.h * (1 - trace.domain.y[0]) - height / 2; + if(trace.title.text && trace.title.position.indexOf('bottom') !== -1) { + cd0.cy -= getTitleSpace(cd0, plotSize); + } + + if(trace.scalegroup && scaleGroups.indexOf(trace.scalegroup) === -1) { + scaleGroups.push(trace.scalegroup); + } + } + + groupScale(cdModule, scaleGroups); +} + +function groupScale(cdModule, scaleGroups) { + var cd0, i, trace; + + // scale those that are grouped + for(var k = 0; k < scaleGroups.length; k++) { + var min = Infinity; + var g = scaleGroups[k]; + + for(i = 0; i < cdModule.length; i++) { + cd0 = cdModule[i][0]; + trace = cd0.trace; + + if(trace.scalegroup === g) { + var area; + if(trace.type === 'pie') { + area = cd0.r * cd0.r; + } else if(trace.type === 'funnelarea') { + var rx, ry; + + if(trace.aspectratio > 1) { + rx = cd0.r; + ry = rx / trace.aspectratio; + } else { + ry = cd0.r; + rx = ry * trace.aspectratio; + } + + rx *= (1 + trace.baseratio) / 2; + + area = rx * ry; + } + + min = Math.min(min, area / cd0.vTotal); + } + } + + for(i = 0; i < cdModule.length; i++) { + cd0 = cdModule[i][0]; + trace = cd0.trace; + if(trace.scalegroup === g) { + var v = min * cd0.vTotal; + if(trace.type === 'funnelarea') { + v /= (1 + trace.baseratio) / 2; + v /= trace.aspectratio; + } + + cd0.r = Math.sqrt(v); + } + } + } +} + +function setCoords(cd) { + var cd0 = cd[0]; + var trace = cd0.trace; + var currentAngle = trace.rotation * Math.PI / 180; + var angleFactor = 2 * Math.PI / cd0.vTotal; + var firstPt = 'px0'; + var lastPt = 'px1'; + + var i, cdi, currentCoords; + + if(trace.direction === 'counterclockwise') { + for(i = 0; i < cd.length; i++) { + if(!cd[i].hidden) break; // find the first non-hidden slice + } + if(i === cd.length) return; // all slices hidden + + currentAngle += angleFactor * cd[i].v; + angleFactor *= -1; + firstPt = 'px1'; + lastPt = 'px0'; + } + + function getCoords(angle) { + return [cd0.r * Math.sin(angle), -cd0.r * Math.cos(angle)]; + } + + currentCoords = getCoords(currentAngle); + + for(i = 0; i < cd.length; i++) { + cdi = cd[i]; + if(cdi.hidden) continue; + + cdi[firstPt] = currentCoords; + + currentAngle += angleFactor * cdi.v / 2; + cdi.pxmid = getCoords(currentAngle); + cdi.midangle = currentAngle; + + currentAngle += angleFactor * cdi.v / 2; + currentCoords = getCoords(currentAngle); + + cdi[lastPt] = currentCoords; + + cdi.largeArc = (cdi.v > cd0.vTotal / 2) ? 1 : 0; + + cdi.halfangle = Math.PI * Math.min(cdi.v / cd0.vTotal, 0.5); + cdi.ring = 1 - trace.hole; + cdi.rInscribed = getInscribedRadiusFraction(cdi, cd0); + } +} + +function formatSliceLabel(gd, pt, cd0) { + var fullLayout = gd._fullLayout; + var trace = cd0.trace; + // look for textemplate + var texttemplate = trace.texttemplate; + + // now insert text + var textinfo = trace.textinfo; + if(!texttemplate && textinfo && textinfo !== 'none') { + var parts = textinfo.split('+'); + var hasFlag = function(flag) { return parts.indexOf(flag) !== -1; }; + var hasLabel = hasFlag('label'); + var hasText = hasFlag('text'); + var hasValue = hasFlag('value'); + var hasPercent = hasFlag('percent'); + + var separators = fullLayout.separators; + var text; + + text = hasLabel ? [pt.label] : []; + if(hasText) { + var tx = helpers.getFirstFilled(trace.text, pt.pts); + if(isValidTextValue(tx)) text.push(tx); + } + if(hasValue) text.push(helpers.formatPieValue(pt.v, separators)); + if(hasPercent) text.push(helpers.formatPiePercent(pt.v / cd0.vTotal, separators)); + pt.text = text.join('
'); + } + + function makeTemplateVariables(pt) { + return { + label: pt.label, + value: pt.v, + valueLabel: helpers.formatPieValue(pt.v, fullLayout.separators), + percent: pt.v / cd0.vTotal, + percentLabel: helpers.formatPiePercent(pt.v / cd0.vTotal, fullLayout.separators), + color: pt.color, + text: pt.text, + customdata: Lib.castOption(trace, pt.i, 'customdata') + }; + } + + if(texttemplate) { + var txt = Lib.castOption(trace, pt.i, 'texttemplate'); + if(!txt) { + pt.text = ''; + } else { + var obj = makeTemplateVariables(pt); + var ptTx = helpers.getFirstFilled(trace.text, pt.pts); + if(isValidTextValue(ptTx) || ptTx === '') obj.text = ptTx; + pt.text = Lib.texttemplateString(txt, obj, gd._fullLayout._d3locale, obj, trace._meta || {}); + } + } +} +module.exports = { + plot: plot, + formatSliceLabel: formatSliceLabel, + transformInsideText: transformInsideText, + determineInsideTextFont: determineInsideTextFont, + positionTitleOutside: positionTitleOutside, + prerenderTitles: prerenderTitles, + layoutAreas: layoutAreas, + attachFxHandlers: attachFxHandlers, +}; + +},{"../../components/color":51,"../../components/drawing":72,"../../components/fx":89,"../../lib":169,"../../lib/svg_text_utils":190,"../../plots/plots":245,"./event_data":367,"./helpers":368,"d3":16}],373:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); + +var styleOne = _dereq_('./style_one'); + +module.exports = function style(gd) { + gd._fullLayout._pielayer.selectAll('.trace').each(function(cd) { + var cd0 = cd[0]; + var trace = cd0.trace; + var traceSelection = d3.select(this); + + traceSelection.style({opacity: trace.opacity}); + + traceSelection.selectAll('path.surface').each(function(pt) { + d3.select(this).call(styleOne, pt, trace); + }); + }); +}; + +},{"./style_one":374,"d3":16}],374:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Color = _dereq_('../../components/color'); +var castOption = _dereq_('./helpers').castOption; + +module.exports = function styleOne(s, pt, trace) { + var line = trace.marker.line; + var lineColor = castOption(line.color, pt.pts) || Color.defaultLine; + var lineWidth = castOption(line.width, pt.pts) || 0; + + s.style('stroke-width', lineWidth) + .call(Color.fill, pt.color) + .call(Color.stroke, lineColor); +}; + +},{"../../components/color":51,"./helpers":368}],375:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Lib = _dereq_('../../lib'); + + +// arrayOk attributes, merge them into calcdata array +module.exports = function arraysToCalcdata(cd, trace) { + // so each point knows which index it originally came from + for(var i = 0; i < cd.length; i++) cd[i].i = i; + + Lib.mergeArray(trace.text, cd, 'tx'); + Lib.mergeArray(trace.texttemplate, cd, 'txt'); + Lib.mergeArray(trace.hovertext, cd, 'htx'); + Lib.mergeArray(trace.customdata, cd, 'data'); + Lib.mergeArray(trace.textposition, cd, 'tp'); + if(trace.textfont) { + Lib.mergeArrayCastPositive(trace.textfont.size, cd, 'ts'); + Lib.mergeArray(trace.textfont.color, cd, 'tc'); + Lib.mergeArray(trace.textfont.family, cd, 'tf'); + } + + var marker = trace.marker; + if(marker) { + Lib.mergeArrayCastPositive(marker.size, cd, 'ms'); + Lib.mergeArrayCastPositive(marker.opacity, cd, 'mo'); + Lib.mergeArray(marker.symbol, cd, 'mx'); + Lib.mergeArray(marker.color, cd, 'mc'); + + var markerLine = marker.line; + if(marker.line) { + Lib.mergeArray(markerLine.color, cd, 'mlc'); + Lib.mergeArrayCastPositive(markerLine.width, cd, 'mlw'); + } + + var markerGradient = marker.gradient; + if(markerGradient && markerGradient.type !== 'none') { + Lib.mergeArray(markerGradient.type, cd, 'mgt'); + Lib.mergeArray(markerGradient.color, cd, 'mgc'); + } + } +}; + +},{"../../lib":169}],376:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs; +var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs; +var colorScaleAttrs = _dereq_('../../components/colorscale/attributes'); +var fontAttrs = _dereq_('../../plots/font_attributes'); +var dash = _dereq_('../../components/drawing/attributes').dash; + +var Drawing = _dereq_('../../components/drawing'); +var constants = _dereq_('./constants'); +var extendFlat = _dereq_('../../lib/extend').extendFlat; + +module.exports = { + x: { + valType: 'data_array', + editType: 'calc+clearAxisTypes', + anim: true, + + }, + x0: { + valType: 'any', + dflt: 0, + + editType: 'calc+clearAxisTypes', + anim: true, + + }, + dx: { + valType: 'number', + dflt: 1, + + editType: 'calc', + anim: true, + + }, + y: { + valType: 'data_array', + editType: 'calc+clearAxisTypes', + anim: true, + + }, + y0: { + valType: 'any', + dflt: 0, + + editType: 'calc+clearAxisTypes', + anim: true, + + }, + dy: { + valType: 'number', + dflt: 1, + + editType: 'calc', + anim: true, + + }, + + stackgroup: { + valType: 'string', + + dflt: '', + editType: 'calc', + + }, + orientation: { + valType: 'enumerated', + + values: ['v', 'h'], + editType: 'calc', + + }, + groupnorm: { + valType: 'enumerated', + values: ['', 'fraction', 'percent'], + dflt: '', + + editType: 'calc', + + }, + stackgaps: { + valType: 'enumerated', + values: ['infer zero', 'interpolate'], + dflt: 'infer zero', + + editType: 'calc', + + }, + + text: { + valType: 'string', + + dflt: '', + arrayOk: true, + editType: 'calc', + + }, + + texttemplate: texttemplateAttrs({}, { + + }), + hovertext: { + valType: 'string', + + dflt: '', + arrayOk: true, + editType: 'style', + + }, + mode: { + valType: 'flaglist', + flags: ['lines', 'markers', 'text'], + extras: ['none'], + + editType: 'calc', + + }, + hoveron: { + valType: 'flaglist', + flags: ['points', 'fills'], + + editType: 'style', + + }, + hovertemplate: hovertemplateAttrs({}, { + keys: constants.eventDataKeys + }), + line: { + color: { + valType: 'color', + + editType: 'style', + anim: true, + + }, + width: { + valType: 'number', + min: 0, + dflt: 2, + + editType: 'style', + anim: true, + + }, + shape: { + valType: 'enumerated', + values: ['linear', 'spline', 'hv', 'vh', 'hvh', 'vhv'], + dflt: 'linear', + + editType: 'plot', + + }, + smoothing: { + valType: 'number', + min: 0, + max: 1.3, + dflt: 1, + + editType: 'plot', + + }, + dash: extendFlat({}, dash, {editType: 'style'}), + simplify: { + valType: 'boolean', + dflt: true, + + editType: 'plot', + + }, + editType: 'plot' + }, + + connectgaps: { + valType: 'boolean', + dflt: false, + + editType: 'calc', + + }, + cliponaxis: { + valType: 'boolean', + dflt: true, + + editType: 'plot', + + }, + + fill: { + valType: 'enumerated', + values: ['none', 'tozeroy', 'tozerox', 'tonexty', 'tonextx', 'toself', 'tonext'], + + editType: 'calc', + + }, + fillcolor: { + valType: 'color', + + editType: 'style', + anim: true, + + }, + marker: extendFlat({ + symbol: { + valType: 'enumerated', + values: Drawing.symbolList, + dflt: 'circle', + arrayOk: true, + + editType: 'style', + + }, + opacity: { + valType: 'number', + min: 0, + max: 1, + arrayOk: true, + + editType: 'style', + anim: true, + + }, + size: { + valType: 'number', + min: 0, + dflt: 6, + arrayOk: true, + + editType: 'calc', + anim: true, + + }, + maxdisplayed: { + valType: 'number', + min: 0, + dflt: 0, + + editType: 'plot', + + }, + sizeref: { + valType: 'number', + dflt: 1, + + editType: 'calc', + + }, + sizemin: { + valType: 'number', + min: 0, + dflt: 0, + + editType: 'calc', + + }, + sizemode: { + valType: 'enumerated', + values: ['diameter', 'area'], + dflt: 'diameter', + + editType: 'calc', + + }, + + line: extendFlat({ + width: { + valType: 'number', + min: 0, + arrayOk: true, + + editType: 'style', + anim: true, + + }, + editType: 'calc' + }, + colorScaleAttrs('marker.line', {anim: true}) + ), + gradient: { + type: { + valType: 'enumerated', + values: ['radial', 'horizontal', 'vertical', 'none'], + arrayOk: true, + dflt: 'none', + + editType: 'calc', + + }, + color: { + valType: 'color', + arrayOk: true, + + editType: 'calc', + + }, + editType: 'calc' + }, + editType: 'calc' + }, + colorScaleAttrs('marker', {anim: true}) + ), + selected: { + marker: { + opacity: { + valType: 'number', + min: 0, + max: 1, + + editType: 'style', + + }, + color: { + valType: 'color', + + editType: 'style', + + }, + size: { + valType: 'number', + min: 0, + + editType: 'style', + + }, + editType: 'style' + }, + textfont: { + color: { + valType: 'color', + + editType: 'style', + + }, + editType: 'style' + }, + editType: 'style' + }, + unselected: { + marker: { + opacity: { + valType: 'number', + min: 0, + max: 1, + + editType: 'style', + + }, + color: { + valType: 'color', + + editType: 'style', + + }, + size: { + valType: 'number', + min: 0, + + editType: 'style', + + }, + editType: 'style' + }, + textfont: { + color: { + valType: 'color', + + editType: 'style', + + }, + editType: 'style' + }, + editType: 'style' + }, + + textposition: { + valType: 'enumerated', + values: [ + 'top left', 'top center', 'top right', + 'middle left', 'middle center', 'middle right', + 'bottom left', 'bottom center', 'bottom right' + ], + dflt: 'middle center', + arrayOk: true, + + editType: 'calc', + + }, + textfont: fontAttrs({ + editType: 'calc', + colorEditType: 'style', + arrayOk: true, + + }), + + r: { + valType: 'data_array', + editType: 'calc', + + }, + t: { + valType: 'data_array', + editType: 'calc', + + } +}; + +},{"../../components/colorscale/attributes":58,"../../components/drawing":72,"../../components/drawing/attributes":71,"../../lib/extend":164,"../../plots/font_attributes":239,"../../plots/template_attributes":253,"./constants":380}],377:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); +var Lib = _dereq_('../../lib'); + +var Axes = _dereq_('../../plots/cartesian/axes'); +var BADNUM = _dereq_('../../constants/numerical').BADNUM; + +var subTypes = _dereq_('./subtypes'); +var calcColorscale = _dereq_('./colorscale_calc'); +var arraysToCalcdata = _dereq_('./arrays_to_calcdata'); +var calcSelection = _dereq_('./calc_selection'); + +function calc(gd, trace) { + var fullLayout = gd._fullLayout; + var xa = Axes.getFromId(gd, trace.xaxis || 'x'); + var ya = Axes.getFromId(gd, trace.yaxis || 'y'); + var x = xa.makeCalcdata(trace, 'x'); + var y = ya.makeCalcdata(trace, 'y'); + var serieslen = trace._length; + var cd = new Array(serieslen); + var ids = trace.ids; + var stackGroupOpts = getStackOpts(trace, fullLayout, xa, ya); + var interpolateGaps = false; + var isV, i, j, k, interpolate, vali; + + setFirstScatter(fullLayout, trace); + + var xAttr = 'x'; + var yAttr = 'y'; + var posAttr; + if(stackGroupOpts) { + Lib.pushUnique(stackGroupOpts.traceIndices, trace._expandedIndex); + isV = stackGroupOpts.orientation === 'v'; + + // size, like we use for bar + if(isV) { + yAttr = 's'; + posAttr = 'x'; + } else { + xAttr = 's'; + posAttr = 'y'; + } + interpolate = stackGroupOpts.stackgaps === 'interpolate'; + } else { + var ppad = calcMarkerSize(trace, serieslen); + calcAxisExpansion(gd, trace, xa, ya, x, y, ppad); + } + + for(i = 0; i < serieslen; i++) { + var cdi = cd[i] = {}; + var xValid = isNumeric(x[i]); + var yValid = isNumeric(y[i]); + if(xValid && yValid) { + cdi[xAttr] = x[i]; + cdi[yAttr] = y[i]; + } else if(stackGroupOpts && (isV ? xValid : yValid)) { + // if we're stacking we need to hold on to all valid positions + // even with invalid sizes + + cdi[posAttr] = isV ? x[i] : y[i]; + cdi.gap = true; + if(interpolate) { + cdi.s = BADNUM; + interpolateGaps = true; + } else { + cdi.s = 0; + } + } else { + cdi[xAttr] = cdi[yAttr] = BADNUM; + } + + if(ids) { + cdi.id = String(ids[i]); + } + } + + arraysToCalcdata(cd, trace); + calcColorscale(gd, trace); + calcSelection(cd, trace); + + if(stackGroupOpts) { + // remove bad positions and sort + // note that original indices get added to cd in arraysToCalcdata + i = 0; + while(i < cd.length) { + if(cd[i][posAttr] === BADNUM) { + cd.splice(i, 1); + } else i++; + } + + Lib.sort(cd, function(a, b) { + return (a[posAttr] - b[posAttr]) || (a.i - b.i); + }); + + if(interpolateGaps) { + // first fill the beginning with constant from the first point + i = 0; + while(i < cd.length - 1 && cd[i].gap) { + i++; + } + vali = cd[i].s; + if(!vali) vali = cd[i].s = 0; // in case of no data AT ALL in this trace - use 0 + for(j = 0; j < i; j++) { + cd[j].s = vali; + } + // then fill the end with constant from the last point + k = cd.length - 1; + while(k > i && cd[k].gap) { + k--; + } + vali = cd[k].s; + for(j = cd.length - 1; j > k; j--) { + cd[j].s = vali; + } + // now interpolate internal gaps linearly + while(i < k) { + i++; + if(cd[i].gap) { + j = i + 1; + while(cd[j].gap) { + j++; + } + var pos0 = cd[i - 1][posAttr]; + var size0 = cd[i - 1].s; + var m = (cd[j].s - size0) / (cd[j][posAttr] - pos0); + while(i < j) { + cd[i].s = size0 + (cd[i][posAttr] - pos0) * m; + i++; + } + } + } + } + } + + return cd; +} + +function calcAxisExpansion(gd, trace, xa, ya, x, y, ppad) { + var serieslen = trace._length; + var fullLayout = gd._fullLayout; + var xId = xa._id; + var yId = ya._id; + var firstScatter = fullLayout._firstScatter[firstScatterGroup(trace)] === trace.uid; + var stackOrientation = (getStackOpts(trace, fullLayout, xa, ya) || {}).orientation; + var fill = trace.fill; + + // cancel minimum tick spacings (only applies to bars and boxes) + xa._minDtick = 0; + ya._minDtick = 0; + + // check whether bounds should be tight, padded, extended to zero... + // most cases both should be padded on both ends, so start with that. + var xOptions = {padded: true}; + var yOptions = {padded: true}; + + if(ppad) { + xOptions.ppad = yOptions.ppad = ppad; + } + + // TODO: text size + + var openEnded = serieslen < 2 || (x[0] !== x[serieslen - 1]) || (y[0] !== y[serieslen - 1]); + + if(openEnded && ( + (fill === 'tozerox') || + ((fill === 'tonextx') && (firstScatter || stackOrientation === 'h')) + )) { + // include zero (tight) and extremes (padded) if fill to zero + // (unless the shape is closed, then it's just filling the shape regardless) + + xOptions.tozero = true; + } else if(!(trace.error_y || {}).visible && ( + // if no error bars, markers or text, or fill to y=0 remove x padding + + (fill === 'tonexty' || fill === 'tozeroy') || + (!subTypes.hasMarkers(trace) && !subTypes.hasText(trace)) + )) { + xOptions.padded = false; + xOptions.ppad = 0; + } + + if(openEnded && ( + (fill === 'tozeroy') || + ((fill === 'tonexty') && (firstScatter || stackOrientation === 'v')) + )) { + // now check for y - rather different logic, though still mostly padded both ends + // include zero (tight) and extremes (padded) if fill to zero + // (unless the shape is closed, then it's just filling the shape regardless) + + yOptions.tozero = true; + } else if(fill === 'tonextx' || fill === 'tozerox') { + // tight y: any x fill + + yOptions.padded = false; + } + + // N.B. asymmetric splom traces call this with blank {} xa or ya + if(xId) trace._extremes[xId] = Axes.findExtremes(xa, x, xOptions); + if(yId) trace._extremes[yId] = Axes.findExtremes(ya, y, yOptions); +} + +function calcMarkerSize(trace, serieslen) { + if(!subTypes.hasMarkers(trace)) return; + + // Treat size like x or y arrays --- Run d2c + // this needs to go before ppad computation + var marker = trace.marker; + var sizeref = 1.6 * (trace.marker.sizeref || 1); + var markerTrans; + + if(trace.marker.sizemode === 'area') { + markerTrans = function(v) { + return Math.max(Math.sqrt((v || 0) / sizeref), 3); + }; + } else { + markerTrans = function(v) { + return Math.max((v || 0) / sizeref, 3); + }; + } + + if(Lib.isArrayOrTypedArray(marker.size)) { + // I tried auto-type but category and dates dont make much sense. + var ax = {type: 'linear'}; + Axes.setConvert(ax); + + var s = ax.makeCalcdata(trace.marker, 'size'); + + var sizeOut = new Array(serieslen); + for(var i = 0; i < serieslen; i++) { + sizeOut[i] = markerTrans(s[i]); + } + return sizeOut; + } else { + return markerTrans(marker.size); + } +} + +/** + * mark the first scatter trace for each subplot + * note that scatter and scattergl each get their own first trace + * note also that I'm doing this during calc rather than supplyDefaults + * so I don't need to worry about transforms, but if we ever do + * per-trace calc this will get confused. + */ +function setFirstScatter(fullLayout, trace) { + var group = firstScatterGroup(trace); + var firstScatter = fullLayout._firstScatter; + if(!firstScatter[group]) firstScatter[group] = trace.uid; +} + +function firstScatterGroup(trace) { + var stackGroup = trace.stackgroup; + return trace.xaxis + trace.yaxis + trace.type + + (stackGroup ? '-' + stackGroup : ''); +} + +function getStackOpts(trace, fullLayout, xa, ya) { + var stackGroup = trace.stackgroup; + if(!stackGroup) return; + var stackOpts = fullLayout._scatterStackOpts[xa._id + ya._id][stackGroup]; + var stackAx = stackOpts.orientation === 'v' ? ya : xa; + // Allow stacking only on numeric axes + // calc is a little late to be figuring this out, but during supplyDefaults + // we don't know the axis type yet + if(stackAx.type === 'linear' || stackAx.type === 'log') return stackOpts; +} + +module.exports = { + calc: calc, + calcMarkerSize: calcMarkerSize, + calcAxisExpansion: calcAxisExpansion, + setFirstScatter: setFirstScatter, + getStackOpts: getStackOpts +}; + +},{"../../constants/numerical":149,"../../lib":169,"../../plots/cartesian/axes":213,"./arrays_to_calcdata":375,"./calc_selection":378,"./colorscale_calc":379,"./subtypes":399,"fast-isnumeric":18}],378:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); + +module.exports = function calcSelection(cd, trace) { + if(Lib.isArrayOrTypedArray(trace.selectedpoints)) { + Lib.tagSelected(cd, trace); + } +}; + +},{"../../lib":169}],379:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var hasColorscale = _dereq_('../../components/colorscale/helpers').hasColorscale; +var calcColorscale = _dereq_('../../components/colorscale/calc'); + +var subTypes = _dereq_('./subtypes'); + +module.exports = function calcMarkerColorscale(gd, trace) { + if(subTypes.hasLines(trace) && hasColorscale(trace, 'line')) { + calcColorscale(gd, trace, { + vals: trace.line.color, + containerStr: 'line', + cLetter: 'c' + }); + } + + if(subTypes.hasMarkers(trace)) { + if(hasColorscale(trace, 'marker')) { + calcColorscale(gd, trace, { + vals: trace.marker.color, + containerStr: 'marker', + cLetter: 'c' + }); + } + if(hasColorscale(trace, 'marker.line')) { + calcColorscale(gd, trace, { + vals: trace.marker.line.color, + containerStr: 'marker.line', + cLetter: 'c' + }); + } + } +}; + +},{"../../components/colorscale/calc":59,"../../components/colorscale/helpers":62,"./subtypes":399}],380:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +module.exports = { + PTS_LINESONLY: 20, + + // fixed parameters of clustering and clipping algorithms + + // fraction of clustering tolerance "so close we don't even consider it a new point" + minTolerance: 0.2, + // how fast does clustering tolerance increase as you get away from the visible region + toleranceGrowth: 10, + + // number of viewport sizes away from the visible region + // at which we clip all lines to the perimeter + maxScreensAway: 20, + + eventDataKeys: [] +}; + +},{}],381:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var calc = _dereq_('./calc'); + +/* + * Scatter stacking & normalization calculations + * runs per subplot, and can handle multiple stacking groups + */ + +module.exports = function crossTraceCalc(gd, plotinfo) { + var xa = plotinfo.xaxis; + var ya = plotinfo.yaxis; + var subplot = xa._id + ya._id; + + var subplotStackOpts = gd._fullLayout._scatterStackOpts[subplot]; + if(!subplotStackOpts) return; + + var calcTraces = gd.calcdata; + + var i, j, k, i2, cd, cd0, posj, sumj, norm; + var groupOpts, interpolate, groupnorm, posAttr, valAttr; + var hasAnyBlanks; + + for(var stackGroup in subplotStackOpts) { + groupOpts = subplotStackOpts[stackGroup]; + var indices = groupOpts.traceIndices; + + // can get here with no indices if the stack axis is non-numeric + if(!indices.length) continue; + + interpolate = groupOpts.stackgaps === 'interpolate'; + groupnorm = groupOpts.groupnorm; + if(groupOpts.orientation === 'v') { + posAttr = 'x'; + valAttr = 'y'; + } else { + posAttr = 'y'; + valAttr = 'x'; + } + hasAnyBlanks = new Array(indices.length); + for(i = 0; i < hasAnyBlanks.length; i++) { + hasAnyBlanks[i] = false; + } + + // Collect the complete set of all positions across ALL traces. + // Start with the first trace, then interleave items from later traces + // as needed. + // Fill in mising items as we go. + cd0 = calcTraces[indices[0]]; + var allPositions = new Array(cd0.length); + for(i = 0; i < cd0.length; i++) { + allPositions[i] = cd0[i][posAttr]; + } + + for(i = 1; i < indices.length; i++) { + cd = calcTraces[indices[i]]; + + for(j = k = 0; j < cd.length; j++) { + posj = cd[j][posAttr]; + for(; posj > allPositions[k] && k < allPositions.length; k++) { + // the current trace is missing a position from some previous trace(s) + insertBlank(cd, j, allPositions[k], i, hasAnyBlanks, interpolate, posAttr); + j++; + } + if(posj !== allPositions[k]) { + // previous trace(s) are missing a position from the current trace + for(i2 = 0; i2 < i; i2++) { + insertBlank(calcTraces[indices[i2]], k, posj, i2, hasAnyBlanks, interpolate, posAttr); + } + allPositions.splice(k, 0, posj); + } + k++; + } + for(; k < allPositions.length; k++) { + insertBlank(cd, j, allPositions[k], i, hasAnyBlanks, interpolate, posAttr); + j++; + } + } + + var serieslen = allPositions.length; + + // stack (and normalize)! + for(j = 0; j < cd0.length; j++) { + sumj = cd0[j][valAttr] = cd0[j].s; + for(i = 1; i < indices.length; i++) { + cd = calcTraces[indices[i]]; + cd[0].trace._rawLength = cd[0].trace._length; + cd[0].trace._length = serieslen; + sumj += cd[j].s; + cd[j][valAttr] = sumj; + } + + if(groupnorm) { + norm = ((groupnorm === 'fraction') ? sumj : (sumj / 100)) || 1; + for(i = 0; i < indices.length; i++) { + var cdj = calcTraces[indices[i]][j]; + cdj[valAttr] /= norm; + cdj.sNorm = cdj.s / norm; + } + } + } + + // autorange + for(i = 0; i < indices.length; i++) { + cd = calcTraces[indices[i]]; + var trace = cd[0].trace; + var ppad = calc.calcMarkerSize(trace, trace._rawLength); + var arrayPad = Array.isArray(ppad); + if((ppad && hasAnyBlanks[i]) || arrayPad) { + var ppadRaw = ppad; + ppad = new Array(serieslen); + for(j = 0; j < serieslen; j++) { + ppad[j] = cd[j].gap ? 0 : (arrayPad ? ppadRaw[cd[j].i] : ppadRaw); + } + } + var x = new Array(serieslen); + var y = new Array(serieslen); + for(j = 0; j < serieslen; j++) { + x[j] = cd[j].x; + y[j] = cd[j].y; + } + calc.calcAxisExpansion(gd, trace, xa, ya, x, y, ppad); + + // while we're here (in a loop over all traces in the stack) + // record the orientation, so hover can find it easily + cd[0].t.orientation = groupOpts.orientation; + } + } +}; + +function insertBlank(calcTrace, index, position, traceIndex, hasAnyBlanks, interpolate, posAttr) { + hasAnyBlanks[traceIndex] = true; + var newEntry = { + i: null, + gap: true, + s: 0 + }; + newEntry[posAttr] = position; + calcTrace.splice(index, 0, newEntry); + // Even if we're not interpolating, if one trace has multiple + // values at the same position and this trace only has one value there, + // we just duplicate that one value rather than insert a zero. + // We also make it look like a real point - because it's ambiguous which + // one really is the real one! + if(index && position === calcTrace[index - 1][posAttr]) { + var prevEntry = calcTrace[index - 1]; + newEntry.s = prevEntry.s; + // TODO is it going to cause any problems to have multiple + // calcdata points with the same index? + newEntry.i = prevEntry.i; + newEntry.gap = prevEntry.gap; + } else if(interpolate) { + newEntry.s = getInterp(calcTrace, index, position, posAttr); + } + if(!index) { + // t and trace need to stay on the first cd entry + calcTrace[0].t = calcTrace[1].t; + calcTrace[0].trace = calcTrace[1].trace; + delete calcTrace[1].t; + delete calcTrace[1].trace; + } +} + +function getInterp(calcTrace, index, position, posAttr) { + var pt0 = calcTrace[index - 1]; + var pt1 = calcTrace[index + 1]; + if(!pt1) return pt0.s; + if(!pt0) return pt1.s; + return pt0.s + (pt1.s - pt0.s) * (position - pt0[posAttr]) / (pt1[posAttr] - pt0[posAttr]); +} + +},{"./calc":377}],382:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + + +// remove opacity for any trace that has a fill or is filled to +module.exports = function crossTraceDefaults(fullData) { + for(var i = 0; i < fullData.length; i++) { + var tracei = fullData[i]; + if(tracei.type !== 'scatter') continue; + + var filli = tracei.fill; + if(filli === 'none' || filli === 'toself') continue; + + tracei.opacity = undefined; + + if(filli === 'tonexty' || filli === 'tonextx') { + for(var j = i - 1; j >= 0; j--) { + var tracej = fullData[j]; + + if((tracej.type === 'scatter') && + (tracej.xaxis === tracei.xaxis) && + (tracej.yaxis === tracei.yaxis)) { + tracej.opacity = undefined; + break; + } + } + } + } +}; + +},{}],383:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var Registry = _dereq_('../../registry'); + +var attributes = _dereq_('./attributes'); +var constants = _dereq_('./constants'); +var subTypes = _dereq_('./subtypes'); +var handleXYDefaults = _dereq_('./xy_defaults'); +var handleStackDefaults = _dereq_('./stack_defaults'); +var handleMarkerDefaults = _dereq_('./marker_defaults'); +var handleLineDefaults = _dereq_('./line_defaults'); +var handleLineShapeDefaults = _dereq_('./line_shape_defaults'); +var handleTextDefaults = _dereq_('./text_defaults'); +var handleFillColorDefaults = _dereq_('./fillcolor_defaults'); + +module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { + function coerce(attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); + } + + var len = handleXYDefaults(traceIn, traceOut, layout, coerce); + if(!len) traceOut.visible = false; + + if(!traceOut.visible) return; + + var stackGroupOpts = handleStackDefaults(traceIn, traceOut, layout, coerce); + + var defaultMode = !stackGroupOpts && (len < constants.PTS_LINESONLY) ? + 'lines+markers' : 'lines'; + coerce('text'); + coerce('hovertext'); + coerce('mode', defaultMode); + + if(subTypes.hasLines(traceOut)) { + handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce); + handleLineShapeDefaults(traceIn, traceOut, coerce); + coerce('connectgaps'); + coerce('line.simplify'); + } + + if(subTypes.hasMarkers(traceOut)) { + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {gradient: true}); + } + + if(subTypes.hasText(traceOut)) { + coerce('texttemplate'); + handleTextDefaults(traceIn, traceOut, layout, coerce); + } + + var dfltHoverOn = []; + + if(subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) { + coerce('cliponaxis'); + coerce('marker.maxdisplayed'); + dfltHoverOn.push('points'); + } + + // It's possible for this default to be changed by a later trace. + // We handle that case in some hacky code inside handleStackDefaults. + coerce('fill', stackGroupOpts ? stackGroupOpts.fillDflt : 'none'); + if(traceOut.fill !== 'none') { + handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); + if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce); + } + + var lineColor = (traceOut.line || {}).color; + var markerColor = (traceOut.marker || {}).color; + + if(traceOut.fill === 'tonext' || traceOut.fill === 'toself') { + dfltHoverOn.push('fills'); + } + coerce('hoveron', dfltHoverOn.join('+') || 'points'); + if(traceOut.hoveron !== 'fills') coerce('hovertemplate'); + var errorBarsSupplyDefaults = Registry.getComponentMethod('errorbars', 'supplyDefaults'); + errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, {axis: 'y'}); + errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, {axis: 'x', inherit: 'y'}); + + Lib.coerceSelectionMarkerOpacity(traceOut, coerce); +}; + +},{"../../lib":169,"../../registry":258,"./attributes":376,"./constants":380,"./fillcolor_defaults":384,"./line_defaults":388,"./line_shape_defaults":390,"./marker_defaults":394,"./stack_defaults":397,"./subtypes":399,"./text_defaults":400,"./xy_defaults":401}],384:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Color = _dereq_('../../components/color'); +var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray; + +module.exports = function fillColorDefaults(traceIn, traceOut, defaultColor, coerce) { + var inheritColorFromMarker = false; + + if(traceOut.marker) { + // don't try to inherit a color array + var markerColor = traceOut.marker.color; + var markerLineColor = (traceOut.marker.line || {}).color; + + if(markerColor && !isArrayOrTypedArray(markerColor)) { + inheritColorFromMarker = markerColor; + } else if(markerLineColor && !isArrayOrTypedArray(markerLineColor)) { + inheritColorFromMarker = markerLineColor; + } + } + + coerce('fillcolor', Color.addOpacity( + (traceOut.line || {}).color || + inheritColorFromMarker || + defaultColor, 0.5 + )); +}; + +},{"../../components/color":51,"../../lib":169}],385:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Color = _dereq_('../../components/color'); +var subtypes = _dereq_('./subtypes'); + + +module.exports = function getTraceColor(trace, di) { + var lc, tc; + + // TODO: text modes + + if(trace.mode === 'lines') { + lc = trace.line.color; + return (lc && Color.opacity(lc)) ? + lc : trace.fillcolor; + } else if(trace.mode === 'none') { + return trace.fill ? trace.fillcolor : ''; + } else { + var mc = di.mcc || (trace.marker || {}).color; + var mlc = di.mlcc || ((trace.marker || {}).line || {}).color; + + tc = (mc && Color.opacity(mc)) ? mc : + (mlc && Color.opacity(mlc) && + (di.mlw || ((trace.marker || {}).line || {}).width)) ? mlc : ''; + + if(tc) { + // make sure the points aren't TOO transparent + if(Color.opacity(tc) < 0.3) { + return Color.addOpacity(tc, 0.3); + } else return tc; + } else { + lc = (trace.line || {}).color; + return (lc && Color.opacity(lc) && + subtypes.hasLines(trace) && trace.line.width) ? + lc : trace.fillcolor; + } + } +}; + +},{"../../components/color":51,"./subtypes":399}],386:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var Fx = _dereq_('../../components/fx'); +var Registry = _dereq_('../../registry'); +var getTraceColor = _dereq_('./get_trace_color'); +var Color = _dereq_('../../components/color'); +var fillText = Lib.fillText; + +module.exports = function hoverPoints(pointData, xval, yval, hovermode) { + var cd = pointData.cd; + var trace = cd[0].trace; + var xa = pointData.xa; + var ya = pointData.ya; + var xpx = xa.c2p(xval); + var ypx = ya.c2p(yval); + var pt = [xpx, ypx]; + var hoveron = trace.hoveron || ''; + var minRad = (trace.mode.indexOf('markers') !== -1) ? 3 : 0.5; + + // look for points to hover on first, then take fills only if we + // didn't find a point + if(hoveron.indexOf('points') !== -1) { + var dx = function(di) { + // dx and dy are used in compare modes - here we want to always + // prioritize the closest data point, at least as long as markers are + // the same size or nonexistent, but still try to prioritize small markers too. + var rad = Math.max(3, di.mrc || 0); + var kink = 1 - 1 / rad; + var dxRaw = Math.abs(xa.c2p(di.x) - xpx); + var d = (dxRaw < rad) ? (kink * dxRaw / rad) : (dxRaw - rad + kink); + return d; + }; + var dy = function(di) { + var rad = Math.max(3, di.mrc || 0); + var kink = 1 - 1 / rad; + var dyRaw = Math.abs(ya.c2p(di.y) - ypx); + return (dyRaw < rad) ? (kink * dyRaw / rad) : (dyRaw - rad + kink); + }; + var dxy = function(di) { + // scatter points: d.mrc is the calculated marker radius + // adjust the distance so if you're inside the marker it + // always will show up regardless of point size, but + // prioritize smaller points + var rad = Math.max(minRad, di.mrc || 0); + var dx = xa.c2p(di.x) - xpx; + var dy = ya.c2p(di.y) - ypx; + return Math.max(Math.sqrt(dx * dx + dy * dy) - rad, 1 - minRad / rad); + }; + var distfn = Fx.getDistanceFunction(hovermode, dx, dy, dxy); + + Fx.getClosest(cd, distfn, pointData); + + // skip the rest (for this trace) if we didn't find a close point + if(pointData.index !== false) { + // the closest data point + var di = cd[pointData.index]; + var xc = xa.c2p(di.x, true); + var yc = ya.c2p(di.y, true); + var rad = di.mrc || 1; + + // now we're done using the whole `calcdata` array, replace the + // index with the original index (in case of inserted point from + // stacked area) + pointData.index = di.i; + + var orientation = cd[0].t.orientation; + // TODO: for scatter and bar, option to show (sub)totals and + // raw data? Currently stacked and/or normalized bars just show + // the normalized individual sizes, so that's what I'm doing here + // for now. + var sizeVal = orientation && (di.sNorm || di.s); + var xLabelVal = (orientation === 'h') ? sizeVal : di.x; + var yLabelVal = (orientation === 'v') ? sizeVal : di.y; + + Lib.extendFlat(pointData, { + color: getTraceColor(trace, di), + + x0: xc - rad, + x1: xc + rad, + xLabelVal: xLabelVal, + + y0: yc - rad, + y1: yc + rad, + yLabelVal: yLabelVal, + + spikeDistance: dxy(di), + hovertemplate: trace.hovertemplate + }); + + fillText(di, trace, pointData); + Registry.getComponentMethod('errorbars', 'hoverInfo')(di, trace, pointData); + + return [pointData]; + } + } + + // even if hoveron is 'fills', only use it if we have polygons too + if(hoveron.indexOf('fills') !== -1 && trace._polygons) { + var polygons = trace._polygons; + var polygonsIn = []; + var inside = false; + var xmin = Infinity; + var xmax = -Infinity; + var ymin = Infinity; + var ymax = -Infinity; + + var i, j, polygon, pts, xCross, x0, x1, y0, y1; + + for(i = 0; i < polygons.length; i++) { + polygon = polygons[i]; + // TODO: this is not going to work right for curved edges, it will + // act as though they're straight. That's probably going to need + // the elements themselves to capture the events. Worth it? + if(polygon.contains(pt)) { + inside = !inside; + // TODO: need better than just the overall bounding box + polygonsIn.push(polygon); + ymin = Math.min(ymin, polygon.ymin); + ymax = Math.max(ymax, polygon.ymax); + } + } + + if(inside) { + // constrain ymin/max to the visible plot, so the label goes + // at the middle of the piece you can see + ymin = Math.max(ymin, 0); + ymax = Math.min(ymax, ya._length); + + // find the overall left-most and right-most points of the + // polygon(s) we're inside at their combined vertical midpoint. + // This is where we will draw the hover label. + // Note that this might not be the vertical midpoint of the + // whole trace, if it's disjoint. + var yAvg = (ymin + ymax) / 2; + for(i = 0; i < polygonsIn.length; i++) { + pts = polygonsIn[i].pts; + for(j = 1; j < pts.length; j++) { + y0 = pts[j - 1][1]; + y1 = pts[j][1]; + if((y0 > yAvg) !== (y1 >= yAvg)) { + x0 = pts[j - 1][0]; + x1 = pts[j][0]; + if(y1 - y0) { + xCross = x0 + (x1 - x0) * (yAvg - y0) / (y1 - y0); + xmin = Math.min(xmin, xCross); + xmax = Math.max(xmax, xCross); + } + } + } + } + + // constrain xmin/max to the visible plot now too + xmin = Math.max(xmin, 0); + xmax = Math.min(xmax, xa._length); + + // get only fill or line color for the hover color + var color = Color.defaultLine; + if(Color.opacity(trace.fillcolor)) color = trace.fillcolor; + else if(Color.opacity((trace.line || {}).color)) { + color = trace.line.color; + } + + Lib.extendFlat(pointData, { + // never let a 2D override 1D type as closest point + // also: no spikeDistance, it's not allowed for fills + distance: pointData.maxHoverDistance, + x0: xmin, + x1: xmax, + y0: yAvg, + y1: yAvg, + color: color, + hovertemplate: false + }); + + delete pointData.index; + + if(trace.text && !Array.isArray(trace.text)) { + pointData.text = String(trace.text); + } else pointData.text = trace.name; + + return [pointData]; + } + } +}; + +},{"../../components/color":51,"../../components/fx":89,"../../lib":169,"../../registry":258,"./get_trace_color":385}],387:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var subtypes = _dereq_('./subtypes'); + +module.exports = { + hasLines: subtypes.hasLines, + hasMarkers: subtypes.hasMarkers, + hasText: subtypes.hasText, + isBubble: subtypes.isBubble, + + attributes: _dereq_('./attributes'), + supplyDefaults: _dereq_('./defaults'), + crossTraceDefaults: _dereq_('./cross_trace_defaults'), + calc: _dereq_('./calc').calc, + crossTraceCalc: _dereq_('./cross_trace_calc'), + arraysToCalcdata: _dereq_('./arrays_to_calcdata'), + plot: _dereq_('./plot'), + colorbar: _dereq_('./marker_colorbar'), + style: _dereq_('./style').style, + styleOnSelect: _dereq_('./style').styleOnSelect, + hoverPoints: _dereq_('./hover'), + selectPoints: _dereq_('./select'), + animatable: true, + + moduleType: 'trace', + name: 'scatter', + basePlotModule: _dereq_('../../plots/cartesian'), + categories: [ + 'cartesian', 'svg', 'symbols', 'errorBarsOK', 'showLegend', 'scatter-like', + 'zoomScale' + ], + meta: { + + } +}; + +},{"../../plots/cartesian":224,"./arrays_to_calcdata":375,"./attributes":376,"./calc":377,"./cross_trace_calc":381,"./cross_trace_defaults":382,"./defaults":383,"./hover":386,"./marker_colorbar":393,"./plot":395,"./select":396,"./style":398,"./subtypes":399}],388:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray; +var hasColorscale = _dereq_('../../components/colorscale/helpers').hasColorscale; +var colorscaleDefaults = _dereq_('../../components/colorscale/defaults'); + +module.exports = function lineDefaults(traceIn, traceOut, defaultColor, layout, coerce, opts) { + var markerColor = (traceIn.marker || {}).color; + + coerce('line.color', defaultColor); + + if(hasColorscale(traceIn, 'line')) { + colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'line.', cLetter: 'c'}); + } else { + var lineColorDflt = (isArrayOrTypedArray(markerColor) ? false : markerColor) || defaultColor; + coerce('line.color', lineColorDflt); + } + + coerce('line.width'); + if(!(opts || {}).noDash) coerce('line.dash'); +}; + +},{"../../components/colorscale/defaults":61,"../../components/colorscale/helpers":62,"../../lib":169}],389:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var numConstants = _dereq_('../../constants/numerical'); +var BADNUM = numConstants.BADNUM; +var LOG_CLIP = numConstants.LOG_CLIP; +var LOG_CLIP_PLUS = LOG_CLIP + 0.5; +var LOG_CLIP_MINUS = LOG_CLIP - 0.5; +var Lib = _dereq_('../../lib'); +var segmentsIntersect = Lib.segmentsIntersect; +var constrain = Lib.constrain; +var constants = _dereq_('./constants'); + + +module.exports = function linePoints(d, opts) { + var xa = opts.xaxis; + var ya = opts.yaxis; + var xLog = xa.type === 'log'; + var yLog = ya.type === 'log'; + var xLen = xa._length; + var yLen = ya._length; + var connectGaps = opts.connectGaps; + var baseTolerance = opts.baseTolerance; + var shape = opts.shape; + var linear = shape === 'linear'; + var fill = opts.fill && opts.fill !== 'none'; + var segments = []; + var minTolerance = constants.minTolerance; + var len = d.length; + var pts = new Array(len); + var pti = 0; + + var i; + + // pt variables are pixel coordinates [x,y] of one point + // these four are the outputs of clustering on a line + var clusterStartPt, clusterEndPt, clusterHighPt, clusterLowPt; + + // "this" is the next point we're considering adding to the cluster + var thisPt; + + // did we encounter the high point first, then a low point, or vice versa? + var clusterHighFirst; + + // the first two points in the cluster determine its unit vector + // so the second is always in the "High" direction + var clusterUnitVector; + + // the pixel delta from clusterStartPt + var thisVector; + + // val variables are (signed) pixel distances along the cluster vector + var clusterRefDist, clusterHighVal, clusterLowVal, thisVal; + + // deviation variables are (signed) pixel distances normal to the cluster vector + var clusterMinDeviation, clusterMaxDeviation, thisDeviation; + + // turn one calcdata point into pixel coordinates + function getPt(index) { + var di = d[index]; + if(!di) return false; + var x = opts.linearized ? xa.l2p(di.x) : xa.c2p(di.x); + var y = opts.linearized ? ya.l2p(di.y) : ya.c2p(di.y); + + // if non-positive log values, set them VERY far off-screen + // so the line looks essentially straight from the previous point. + if(x === BADNUM) { + if(xLog) x = xa.c2p(di.x, true); + if(x === BADNUM) return false; + // If BOTH were bad log values, make the line follow a constant + // exponent rather than a constant slope + if(yLog && y === BADNUM) { + x *= Math.abs(xa._m * yLen * (xa._m > 0 ? LOG_CLIP_PLUS : LOG_CLIP_MINUS) / + (ya._m * xLen * (ya._m > 0 ? LOG_CLIP_PLUS : LOG_CLIP_MINUS))); + } + x *= 1000; + } + if(y === BADNUM) { + if(yLog) y = ya.c2p(di.y, true); + if(y === BADNUM) return false; + y *= 1000; + } + return [x, y]; + } + + function crossesViewport(xFrac0, yFrac0, xFrac1, yFrac1) { + var dx = xFrac1 - xFrac0; + var dy = yFrac1 - yFrac0; + var dx0 = 0.5 - xFrac0; + var dy0 = 0.5 - yFrac0; + var norm2 = dx * dx + dy * dy; + var dot = dx * dx0 + dy * dy0; + if(dot > 0 && dot < norm2) { + var cross = dx0 * dy - dy0 * dx; + if(cross * cross < norm2) return true; + } + } + + var latestXFrac, latestYFrac; + // if we're off-screen, increase tolerance over baseTolerance + function getTolerance(pt, nextPt) { + var xFrac = pt[0] / xLen; + var yFrac = pt[1] / yLen; + var offScreenFraction = Math.max(0, -xFrac, xFrac - 1, -yFrac, yFrac - 1); + if(offScreenFraction && (latestXFrac !== undefined) && + crossesViewport(xFrac, yFrac, latestXFrac, latestYFrac) + ) { + offScreenFraction = 0; + } + if(offScreenFraction && nextPt && + crossesViewport(xFrac, yFrac, nextPt[0] / xLen, nextPt[1] / yLen) + ) { + offScreenFraction = 0; + } + + return (1 + constants.toleranceGrowth * offScreenFraction) * baseTolerance; + } + + function ptDist(pt1, pt2) { + var dx = pt1[0] - pt2[0]; + var dy = pt1[1] - pt2[1]; + return Math.sqrt(dx * dx + dy * dy); + } + + // last bit of filtering: clip paths that are VERY far off-screen + // so we don't get near the browser's hard limit (+/- 2^29 px in Chrome and FF) + + var maxScreensAway = constants.maxScreensAway; + + // find the intersections between the segment from pt1 to pt2 + // and the large rectangle maxScreensAway around the viewport + // if one of pt1 and pt2 is inside and the other outside, there + // will be only one intersection. + // if both are outside there will be 0 or 2 intersections + // (or 1 if it's right at a corner - we'll treat that like 0) + // returns an array of intersection pts + var xEdge0 = -xLen * maxScreensAway; + var xEdge1 = xLen * (1 + maxScreensAway); + var yEdge0 = -yLen * maxScreensAway; + var yEdge1 = yLen * (1 + maxScreensAway); + var edges = [ + [xEdge0, yEdge0, xEdge1, yEdge0], + [xEdge1, yEdge0, xEdge1, yEdge1], + [xEdge1, yEdge1, xEdge0, yEdge1], + [xEdge0, yEdge1, xEdge0, yEdge0] + ]; + var xEdge, yEdge, lastXEdge, lastYEdge, lastFarPt, edgePt; + + // for linear line shape, edge intersections should be linearly interpolated + // spline uses this too, which isn't precisely correct but is actually pretty + // good, because Catmull-Rom weights far-away points less in creating the curvature + function getLinearEdgeIntersections(pt1, pt2) { + var out = []; + var ptCount = 0; + for(var i = 0; i < 4; i++) { + var edge = edges[i]; + var ptInt = segmentsIntersect( + pt1[0], pt1[1], pt2[0], pt2[1], + edge[0], edge[1], edge[2], edge[3] + ); + if(ptInt && (!ptCount || + Math.abs(ptInt.x - out[0][0]) > 1 || + Math.abs(ptInt.y - out[0][1]) > 1 + )) { + ptInt = [ptInt.x, ptInt.y]; + // if we have 2 intersections, make sure the closest one to pt1 comes first + if(ptCount && ptDist(ptInt, pt1) < ptDist(out[0], pt1)) out.unshift(ptInt); + else out.push(ptInt); + ptCount++; + } + } + return out; + } + + function onlyConstrainedPoint(pt) { + if(pt[0] < xEdge0 || pt[0] > xEdge1 || pt[1] < yEdge0 || pt[1] > yEdge1) { + return [constrain(pt[0], xEdge0, xEdge1), constrain(pt[1], yEdge0, yEdge1)]; + } + } + + function sameEdge(pt1, pt2) { + if(pt1[0] === pt2[0] && (pt1[0] === xEdge0 || pt1[0] === xEdge1)) return true; + if(pt1[1] === pt2[1] && (pt1[1] === yEdge0 || pt1[1] === yEdge1)) return true; + } + + // for line shapes hv and vh, movement in the two dimensions is decoupled, + // so all we need to do is constrain each dimension independently + function getHVEdgeIntersections(pt1, pt2) { + var out = []; + var ptInt1 = onlyConstrainedPoint(pt1); + var ptInt2 = onlyConstrainedPoint(pt2); + if(ptInt1 && ptInt2 && sameEdge(ptInt1, ptInt2)) return out; + + if(ptInt1) out.push(ptInt1); + if(ptInt2) out.push(ptInt2); + return out; + } + + // hvh and vhv we sometimes have to move one of the intersection points + // out BEYOND the clipping rect, by a maximum of a factor of 2, so that + // the midpoint line is drawn in the right place + function getABAEdgeIntersections(dim, limit0, limit1) { + return function(pt1, pt2) { + var ptInt1 = onlyConstrainedPoint(pt1); + var ptInt2 = onlyConstrainedPoint(pt2); + + var out = []; + if(ptInt1 && ptInt2 && sameEdge(ptInt1, ptInt2)) return out; + + if(ptInt1) out.push(ptInt1); + if(ptInt2) out.push(ptInt2); + + var midShift = 2 * Lib.constrain((pt1[dim] + pt2[dim]) / 2, limit0, limit1) - + ((ptInt1 || pt1)[dim] + (ptInt2 || pt2)[dim]); + if(midShift) { + var ptToAlter; + if(ptInt1 && ptInt2) { + ptToAlter = (midShift > 0 === ptInt1[dim] > ptInt2[dim]) ? ptInt1 : ptInt2; + } else ptToAlter = ptInt1 || ptInt2; + + ptToAlter[dim] += midShift; + } + + return out; + }; + } + + var getEdgeIntersections; + if(shape === 'linear' || shape === 'spline') { + getEdgeIntersections = getLinearEdgeIntersections; + } else if(shape === 'hv' || shape === 'vh') { + getEdgeIntersections = getHVEdgeIntersections; + } else if(shape === 'hvh') getEdgeIntersections = getABAEdgeIntersections(0, xEdge0, xEdge1); + else if(shape === 'vhv') getEdgeIntersections = getABAEdgeIntersections(1, yEdge0, yEdge1); + + // a segment pt1->pt2 entirely outside the nearby region: + // find the corner it gets closest to touching + function getClosestCorner(pt1, pt2) { + var dx = pt2[0] - pt1[0]; + var m = (pt2[1] - pt1[1]) / dx; + var b = (pt1[1] * pt2[0] - pt2[1] * pt1[0]) / dx; + + if(b > 0) return [m > 0 ? xEdge0 : xEdge1, yEdge1]; + else return [m > 0 ? xEdge1 : xEdge0, yEdge0]; + } + + function updateEdge(pt) { + var x = pt[0]; + var y = pt[1]; + var xSame = x === pts[pti - 1][0]; + var ySame = y === pts[pti - 1][1]; + // duplicate point? + if(xSame && ySame) return; + if(pti > 1) { + // backtracking along an edge? + var xSame2 = x === pts[pti - 2][0]; + var ySame2 = y === pts[pti - 2][1]; + if(xSame && (x === xEdge0 || x === xEdge1) && xSame2) { + if(ySame2) pti--; // backtracking exactly - drop prev pt and don't add + else pts[pti - 1] = pt; // not exact: replace the prev pt + } else if(ySame && (y === yEdge0 || y === yEdge1) && ySame2) { + if(xSame2) pti--; + else pts[pti - 1] = pt; + } else pts[pti++] = pt; + } else pts[pti++] = pt; + } + + function updateEdgesForReentry(pt) { + // if we're outside the nearby region and going back in, + // we may need to loop around a corner point + if(pts[pti - 1][0] !== pt[0] && pts[pti - 1][1] !== pt[1]) { + updateEdge([lastXEdge, lastYEdge]); + } + updateEdge(pt); + lastFarPt = null; + lastXEdge = lastYEdge = 0; + } + + function addPt(pt) { + latestXFrac = pt[0] / xLen; + latestYFrac = pt[1] / yLen; + // Are we more than maxScreensAway off-screen any direction? + // if so, clip to this box, but in such a way that on-screen + // drawing is unchanged + xEdge = (pt[0] < xEdge0) ? xEdge0 : (pt[0] > xEdge1) ? xEdge1 : 0; + yEdge = (pt[1] < yEdge0) ? yEdge0 : (pt[1] > yEdge1) ? yEdge1 : 0; + if(xEdge || yEdge) { + if(!pti) { + // to get fills right - if first point is far, push it toward the + // screen in whichever direction(s) are far + + pts[pti++] = [xEdge || pt[0], yEdge || pt[1]]; + } else if(lastFarPt) { + // both this point and the last are outside the nearby region + // check if we're crossing the nearby region + var intersections = getEdgeIntersections(lastFarPt, pt); + if(intersections.length > 1) { + updateEdgesForReentry(intersections[0]); + pts[pti++] = intersections[1]; + } + } else { + // we're leaving the nearby region - add the point where we left it + + edgePt = getEdgeIntersections(pts[pti - 1], pt)[0]; + pts[pti++] = edgePt; + } + + var lastPt = pts[pti - 1]; + if(xEdge && yEdge && (lastPt[0] !== xEdge || lastPt[1] !== yEdge)) { + // we've gone out beyond a new corner: add the corner too + // so that the next point will take the right winding + if(lastFarPt) { + if(lastXEdge !== xEdge && lastYEdge !== yEdge) { + if(lastXEdge && lastYEdge) { + // we've gone around to an opposite corner - we + // need to add the correct extra corner + // in order to get the right winding + updateEdge(getClosestCorner(lastFarPt, pt)); + } else { + // we're coming from a far edge - the extra corner + // we need is determined uniquely by the sectors + updateEdge([lastXEdge || xEdge, lastYEdge || yEdge]); + } + } else if(lastXEdge && lastYEdge) { + updateEdge([lastXEdge, lastYEdge]); + } + } + updateEdge([xEdge, yEdge]); + } else if((lastXEdge - xEdge) && (lastYEdge - yEdge)) { + // we're coming from an edge or far corner to an edge - again the + // extra corner we need is uniquely determined by the sectors + updateEdge([xEdge || lastXEdge, yEdge || lastYEdge]); + } + lastFarPt = pt; + lastXEdge = xEdge; + lastYEdge = yEdge; + } else { + if(lastFarPt) { + // this point is in range but the previous wasn't: add its entry pt first + updateEdgesForReentry(getEdgeIntersections(lastFarPt, pt)[0]); + } + + pts[pti++] = pt; + } + } + + // loop over ALL points in this trace + for(i = 0; i < len; i++) { + clusterStartPt = getPt(i); + if(!clusterStartPt) continue; + + pti = 0; + lastFarPt = null; + addPt(clusterStartPt); + + // loop over one segment of the trace + for(i++; i < len; i++) { + clusterHighPt = getPt(i); + if(!clusterHighPt) { + if(connectGaps) continue; + else break; + } + + // can't decimate if nonlinear line shape + // TODO: we *could* decimate [hv]{2,3} shapes if we restricted clusters to horz or vert again + // but spline would be verrry awkward to decimate + if(!linear || !opts.simplify) { + addPt(clusterHighPt); + continue; + } + + var nextPt = getPt(i + 1); + + clusterRefDist = ptDist(clusterHighPt, clusterStartPt); + + // #3147 - always include the very first and last points for fills + if(!(fill && (pti === 0 || pti === len - 1)) && + clusterRefDist < getTolerance(clusterHighPt, nextPt) * minTolerance) continue; + + clusterUnitVector = [ + (clusterHighPt[0] - clusterStartPt[0]) / clusterRefDist, + (clusterHighPt[1] - clusterStartPt[1]) / clusterRefDist + ]; + + clusterLowPt = clusterStartPt; + clusterHighVal = clusterRefDist; + clusterLowVal = clusterMinDeviation = clusterMaxDeviation = 0; + clusterHighFirst = false; + clusterEndPt = clusterHighPt; + + // loop over one cluster of points that collapse onto one line + for(i++; i < d.length; i++) { + thisPt = nextPt; + nextPt = getPt(i + 1); + if(!thisPt) { + if(connectGaps) continue; + else break; + } + thisVector = [ + thisPt[0] - clusterStartPt[0], + thisPt[1] - clusterStartPt[1] + ]; + // cross product (or dot with normal to the cluster vector) + thisDeviation = thisVector[0] * clusterUnitVector[1] - thisVector[1] * clusterUnitVector[0]; + clusterMinDeviation = Math.min(clusterMinDeviation, thisDeviation); + clusterMaxDeviation = Math.max(clusterMaxDeviation, thisDeviation); + + if(clusterMaxDeviation - clusterMinDeviation > getTolerance(thisPt, nextPt)) break; + + clusterEndPt = thisPt; + thisVal = thisVector[0] * clusterUnitVector[0] + thisVector[1] * clusterUnitVector[1]; + + if(thisVal > clusterHighVal) { + clusterHighVal = thisVal; + clusterHighPt = thisPt; + clusterHighFirst = false; + } else if(thisVal < clusterLowVal) { + clusterLowVal = thisVal; + clusterLowPt = thisPt; + clusterHighFirst = true; + } + } + + // insert this cluster into pts + // we've already inserted the start pt, now check if we have high and low pts + if(clusterHighFirst) { + addPt(clusterHighPt); + if(clusterEndPt !== clusterLowPt) addPt(clusterLowPt); + } else { + if(clusterLowPt !== clusterStartPt) addPt(clusterLowPt); + if(clusterEndPt !== clusterHighPt) addPt(clusterHighPt); + } + // and finally insert the end pt + addPt(clusterEndPt); + + // have we reached the end of this segment? + if(i >= d.length || !thisPt) break; + + // otherwise we have an out-of-cluster point to insert as next clusterStartPt + addPt(thisPt); + clusterStartPt = thisPt; + } + + // to get fills right - repeat what we did at the start + if(lastFarPt) updateEdge([lastXEdge || lastFarPt[0], lastYEdge || lastFarPt[1]]); + + segments.push(pts.slice(0, pti)); + } + + return segments; +}; + +},{"../../constants/numerical":149,"../../lib":169,"./constants":380}],390:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + + +// common to 'scatter' and 'scatterternary' +module.exports = function handleLineShapeDefaults(traceIn, traceOut, coerce) { + var shape = coerce('line.shape'); + if(shape === 'spline') coerce('line.smoothing'); +}; + +},{}],391:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var LINKEDFILLS = {tonextx: 1, tonexty: 1, tonext: 1}; + +module.exports = function linkTraces(gd, plotinfo, cdscatter) { + var trace, i, group, prevtrace, groupIndex; + + // first sort traces to keep stacks & filled-together groups together + var groupIndices = {}; + var needsSort = false; + var prevGroupIndex = -1; + var nextGroupIndex = 0; + var prevUnstackedGroupIndex = -1; + for(i = 0; i < cdscatter.length; i++) { + trace = cdscatter[i][0].trace; + group = trace.stackgroup || ''; + if(group) { + if(group in groupIndices) { + groupIndex = groupIndices[group]; + } else { + groupIndex = groupIndices[group] = nextGroupIndex; + nextGroupIndex++; + } + } else if(trace.fill in LINKEDFILLS && prevUnstackedGroupIndex >= 0) { + groupIndex = prevUnstackedGroupIndex; + } else { + groupIndex = prevUnstackedGroupIndex = nextGroupIndex; + nextGroupIndex++; + } + + if(groupIndex < prevGroupIndex) needsSort = true; + trace._groupIndex = prevGroupIndex = groupIndex; + } + + var cdscatterSorted = cdscatter.slice(); + if(needsSort) { + cdscatterSorted.sort(function(a, b) { + var traceA = a[0].trace; + var traceB = b[0].trace; + return (traceA._groupIndex - traceB._groupIndex) || + (traceA.index - traceB.index); + }); + } + + // now link traces to each other + var prevtraces = {}; + for(i = 0; i < cdscatterSorted.length; i++) { + trace = cdscatterSorted[i][0].trace; + group = trace.stackgroup || ''; + + // Note: The check which ensures all cdscatter here are for the same axis and + // are either cartesian or scatterternary has been removed. This code assumes + // the passed scattertraces have been filtered to the proper plot types and + // the proper subplots. + if(trace.visible === true) { + trace._nexttrace = null; + + if(trace.fill in LINKEDFILLS) { + prevtrace = prevtraces[group]; + trace._prevtrace = prevtrace || null; + + if(prevtrace) { + prevtrace._nexttrace = trace; + } + } + + trace._ownfill = (trace.fill && ( + trace.fill.substr(0, 6) === 'tozero' || + trace.fill === 'toself' || + (trace.fill.substr(0, 2) === 'to' && !trace._prevtrace) + )); + + prevtraces[group] = trace; + } else { + trace._prevtrace = trace._nexttrace = trace._ownfill = null; + } + } + + return cdscatterSorted; +}; + +},{}],392:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); + + +// used in the drawing step for 'scatter' and 'scattegeo' and +// in the convert step for 'scatter3d' +module.exports = function makeBubbleSizeFn(trace) { + var marker = trace.marker; + var sizeRef = marker.sizeref || 1; + var sizeMin = marker.sizemin || 0; + + // for bubble charts, allow scaling the provided value linearly + // and by area or diameter. + // Note this only applies to the array-value sizes + + var baseFn = (marker.sizemode === 'area') ? + function(v) { return Math.sqrt(v / sizeRef); } : + function(v) { return v / sizeRef; }; + + // TODO add support for position/negative bubbles? + // TODO add 'sizeoffset' attribute? + return function(v) { + var baseSize = baseFn(v / 2); + + // don't show non-numeric and negative sizes + return (isNumeric(baseSize) && (baseSize > 0)) ? + Math.max(baseSize, sizeMin) : + 0; + }; +}; + +},{"fast-isnumeric":18}],393:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +module.exports = { + container: 'marker', + min: 'cmin', + max: 'cmax' +}; + +},{}],394:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Color = _dereq_('../../components/color'); +var hasColorscale = _dereq_('../../components/colorscale/helpers').hasColorscale; +var colorscaleDefaults = _dereq_('../../components/colorscale/defaults'); + +var subTypes = _dereq_('./subtypes'); + +/* + * opts: object of flags to control features not all marker users support + * noLine: caller does not support marker lines + * gradient: caller supports gradients + * noSelect: caller does not support selected/unselected attribute containers + */ +module.exports = function markerDefaults(traceIn, traceOut, defaultColor, layout, coerce, opts) { + var isBubble = subTypes.isBubble(traceIn); + var lineColor = (traceIn.line || {}).color; + var defaultMLC; + + opts = opts || {}; + + // marker.color inherit from line.color (even if line.color is an array) + if(lineColor) defaultColor = lineColor; + + coerce('marker.symbol'); + coerce('marker.opacity', isBubble ? 0.7 : 1); + coerce('marker.size'); + + coerce('marker.color', defaultColor); + if(hasColorscale(traceIn, 'marker')) { + colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'marker.', cLetter: 'c'}); + } + + if(!opts.noSelect) { + coerce('selected.marker.color'); + coerce('unselected.marker.color'); + coerce('selected.marker.size'); + coerce('unselected.marker.size'); + } + + if(!opts.noLine) { + // if there's a line with a different color than the marker, use + // that line color as the default marker line color + // (except when it's an array) + // mostly this is for transparent markers to behave nicely + if(lineColor && !Array.isArray(lineColor) && (traceOut.marker.color !== lineColor)) { + defaultMLC = lineColor; + } else if(isBubble) defaultMLC = Color.background; + else defaultMLC = Color.defaultLine; + + coerce('marker.line.color', defaultMLC); + if(hasColorscale(traceIn, 'marker.line')) { + colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'marker.line.', cLetter: 'c'}); + } + + coerce('marker.line.width', isBubble ? 1 : 0); + } + + if(isBubble) { + coerce('marker.sizeref'); + coerce('marker.sizemin'); + coerce('marker.sizemode'); + } + + if(opts.gradient) { + var gradientType = coerce('marker.gradient.type'); + if(gradientType !== 'none') { + coerce('marker.gradient.color'); + } + } +}; + +},{"../../components/color":51,"../../components/colorscale/defaults":61,"../../components/colorscale/helpers":62,"./subtypes":399}],395:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var d3 = _dereq_('d3'); + +var Registry = _dereq_('../../registry'); +var Lib = _dereq_('../../lib'); +var ensureSingle = Lib.ensureSingle; +var identity = Lib.identity; +var Drawing = _dereq_('../../components/drawing'); + +var subTypes = _dereq_('./subtypes'); +var linePoints = _dereq_('./line_points'); +var linkTraces = _dereq_('./link_traces'); +var polygonTester = _dereq_('../../lib/polygon').tester; + +module.exports = function plot(gd, plotinfo, cdscatter, scatterLayer, transitionOpts, makeOnCompleteCallback) { + var join, onComplete; + + // If transition config is provided, then it is only a partial replot and traces not + // updated are removed. + var isFullReplot = !transitionOpts; + var hasTransition = !!transitionOpts && transitionOpts.duration > 0; + + // Link traces so the z-order of fill layers is correct + var cdscatterSorted = linkTraces(gd, plotinfo, cdscatter); + + join = scatterLayer.selectAll('g.trace') + .data(cdscatterSorted, function(d) { return d[0].trace.uid; }); + + // Append new traces: + join.enter().append('g') + .attr('class', function(d) { + return 'trace scatter trace' + d[0].trace.uid; + }) + .style('stroke-miterlimit', 2); + join.order(); + + createFills(gd, join, plotinfo); + + if(hasTransition) { + if(makeOnCompleteCallback) { + // If it was passed a callback to register completion, make a callback. If + // this is created, then it must be executed on completion, otherwise the + // pos-transition redraw will not execute: + onComplete = makeOnCompleteCallback(); + } + + var transition = d3.transition() + .duration(transitionOpts.duration) + .ease(transitionOpts.easing) + .each('end', function() { + onComplete && onComplete(); + }) + .each('interrupt', function() { + onComplete && onComplete(); + }); + + transition.each(function() { + // Must run the selection again since otherwise enters/updates get grouped together + // and these get executed out of order. Except we need them in order! + scatterLayer.selectAll('g.trace').each(function(d, i) { + plotOne(gd, i, plotinfo, d, cdscatterSorted, this, transitionOpts); + }); + }); + } else { + join.each(function(d, i) { + plotOne(gd, i, plotinfo, d, cdscatterSorted, this, transitionOpts); + }); + } + + if(isFullReplot) { + join.exit().remove(); + } + + // remove paths that didn't get used + scatterLayer.selectAll('path:not([d])').remove(); +}; + +function createFills(gd, traceJoin, plotinfo) { + traceJoin.each(function(d) { + var fills = ensureSingle(d3.select(this), 'g', 'fills'); + Drawing.setClipUrl(fills, plotinfo.layerClipId, gd); + + var trace = d[0].trace; + + var fillData = []; + if(trace._ownfill) fillData.push('_ownFill'); + if(trace._nexttrace) fillData.push('_nextFill'); + + var fillJoin = fills.selectAll('g').data(fillData, identity); + + fillJoin.enter().append('g'); + + fillJoin.exit() + .each(function(d) { trace[d] = null; }) + .remove(); + + fillJoin.order().each(function(d) { + // make a path element inside the fill group, just so + // we can give it its own data later on and the group can + // keep its simple '_*Fill' data + trace[d] = ensureSingle(d3.select(this), 'path', 'js-fill'); + }); + }); +} + +function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transitionOpts) { + var i; + + // Since this has been reorganized and we're executing this on individual traces, + // we need to pass it the full list of cdscatter as well as this trace's index (idx) + // since it does an internal n^2 loop over comparisons with other traces: + selectMarkers(gd, idx, plotinfo, cdscatter, cdscatterAll); + + var hasTransition = !!transitionOpts && transitionOpts.duration > 0; + + function transition(selection) { + return hasTransition ? selection.transition() : selection; + } + + var xa = plotinfo.xaxis; + var ya = plotinfo.yaxis; + + var trace = cdscatter[0].trace; + var line = trace.line; + var tr = d3.select(element); + + var errorBarGroup = ensureSingle(tr, 'g', 'errorbars'); + var lines = ensureSingle(tr, 'g', 'lines'); + var points = ensureSingle(tr, 'g', 'points'); + var text = ensureSingle(tr, 'g', 'text'); + + // error bars are at the bottom + Registry.getComponentMethod('errorbars', 'plot')(gd, errorBarGroup, plotinfo, transitionOpts); + + if(trace.visible !== true) return; + + transition(tr).style('opacity', trace.opacity); + + // BUILD LINES AND FILLS + var ownFillEl3, tonext; + var ownFillDir = trace.fill.charAt(trace.fill.length - 1); + if(ownFillDir !== 'x' && ownFillDir !== 'y') ownFillDir = ''; + + // store node for tweaking by selectPoints + cdscatter[0][plotinfo.isRangePlot ? 'nodeRangePlot3' : 'node3'] = tr; + + var prevRevpath = ''; + var prevPolygons = []; + var prevtrace = trace._prevtrace; + + if(prevtrace) { + prevRevpath = prevtrace._prevRevpath || ''; + tonext = prevtrace._nextFill; + prevPolygons = prevtrace._polygons; + } + + var thispath; + var thisrevpath; + // fullpath is all paths for this curve, joined together straight + // across gaps, for filling + var fullpath = ''; + // revpath is fullpath reversed, for fill-to-next + var revpath = ''; + // functions for converting a point array to a path + var pathfn, revpathbase, revpathfn; + // variables used before and after the data join + var pt0, lastSegment, pt1, thisPolygons; + + // initialize line join data / method + var segments = []; + var makeUpdate = Lib.noop; + + ownFillEl3 = trace._ownFill; + + if(subTypes.hasLines(trace) || trace.fill !== 'none') { + if(tonext) { + // This tells .style which trace to use for fill information: + tonext.datum(cdscatter); + } + + if(['hv', 'vh', 'hvh', 'vhv'].indexOf(line.shape) !== -1) { + pathfn = Drawing.steps(line.shape); + revpathbase = Drawing.steps( + line.shape.split('').reverse().join('') + ); + } else if(line.shape === 'spline') { + pathfn = revpathbase = function(pts) { + var pLast = pts[pts.length - 1]; + if(pts.length > 1 && pts[0][0] === pLast[0] && pts[0][1] === pLast[1]) { + // identical start and end points: treat it as a + // closed curve so we don't get a kink + return Drawing.smoothclosed(pts.slice(1), line.smoothing); + } else { + return Drawing.smoothopen(pts, line.smoothing); + } + }; + } else { + pathfn = revpathbase = function(pts) { + return 'M' + pts.join('L'); + }; + } + + revpathfn = function(pts) { + // note: this is destructive (reverses pts in place) so can't use pts after this + return revpathbase(pts.reverse()); + }; + + segments = linePoints(cdscatter, { + xaxis: xa, + yaxis: ya, + connectGaps: trace.connectgaps, + baseTolerance: Math.max(line.width || 1, 3) / 4, + shape: line.shape, + simplify: line.simplify, + fill: trace.fill + }); + + // since we already have the pixel segments here, use them to make + // polygons for hover on fill + // TODO: can we skip this if hoveron!=fills? That would mean we + // need to redraw when you change hoveron... + thisPolygons = trace._polygons = new Array(segments.length); + for(i = 0; i < segments.length; i++) { + trace._polygons[i] = polygonTester(segments[i]); + } + + if(segments.length) { + pt0 = segments[0][0]; + lastSegment = segments[segments.length - 1]; + pt1 = lastSegment[lastSegment.length - 1]; + } + + makeUpdate = function(isEnter) { + return function(pts) { + thispath = pathfn(pts); + thisrevpath = revpathfn(pts); + if(!fullpath) { + fullpath = thispath; + revpath = thisrevpath; + } else if(ownFillDir) { + fullpath += 'L' + thispath.substr(1); + revpath = thisrevpath + ('L' + revpath.substr(1)); + } else { + fullpath += 'Z' + thispath; + revpath = thisrevpath + 'Z' + revpath; + } + + if(subTypes.hasLines(trace) && pts.length > 1) { + var el = d3.select(this); + + // This makes the coloring work correctly: + el.datum(cdscatter); + + if(isEnter) { + transition(el.style('opacity', 0) + .attr('d', thispath) + .call(Drawing.lineGroupStyle)) + .style('opacity', 1); + } else { + var sel = transition(el); + sel.attr('d', thispath); + Drawing.singleLineStyle(cdscatter, sel); + } + } + }; + }; + } + + var lineJoin = lines.selectAll('.js-line').data(segments); + + transition(lineJoin.exit()) + .style('opacity', 0) + .remove(); + + lineJoin.each(makeUpdate(false)); + + lineJoin.enter().append('path') + .classed('js-line', true) + .style('vector-effect', 'non-scaling-stroke') + .call(Drawing.lineGroupStyle) + .each(makeUpdate(true)); + + Drawing.setClipUrl(lineJoin, plotinfo.layerClipId, gd); + + function clearFill(selection) { + transition(selection).attr('d', 'M0,0Z'); + } + + if(segments.length) { + if(ownFillEl3) { + ownFillEl3.datum(cdscatter); + if(pt0 && pt1) { + if(ownFillDir) { + if(ownFillDir === 'y') { + pt0[1] = pt1[1] = ya.c2p(0, true); + } else if(ownFillDir === 'x') { + pt0[0] = pt1[0] = xa.c2p(0, true); + } + + // fill to zero: full trace path, plus extension of + // the endpoints to the appropriate axis + // For the sake of animations, wrap the points around so that + // the points on the axes are the first two points. Otherwise + // animations get a little crazy if the number of points changes. + transition(ownFillEl3).attr('d', 'M' + pt1 + 'L' + pt0 + 'L' + fullpath.substr(1)) + .call(Drawing.singleFillStyle); + } else { + // fill to self: just join the path to itself + transition(ownFillEl3).attr('d', fullpath + 'Z') + .call(Drawing.singleFillStyle); + } + } + } else if(tonext) { + if(trace.fill.substr(0, 6) === 'tonext' && fullpath && prevRevpath) { + // fill to next: full trace path, plus the previous path reversed + if(trace.fill === 'tonext') { + // tonext: for use by concentric shapes, like manually constructed + // contours, we just add the two paths closed on themselves. + // This makes strange results if one path is *not* entirely + // inside the other, but then that is a strange usage. + transition(tonext).attr('d', fullpath + 'Z' + prevRevpath + 'Z') + .call(Drawing.singleFillStyle); + } else { + // tonextx/y: for now just connect endpoints with lines. This is + // the correct behavior if the endpoints are at the same value of + // y/x, but if they *aren't*, we should ideally do more complicated + // things depending on whether the new endpoint projects onto the + // existing curve or off the end of it + transition(tonext).attr('d', fullpath + 'L' + prevRevpath.substr(1) + 'Z') + .call(Drawing.singleFillStyle); + } + trace._polygons = trace._polygons.concat(prevPolygons); + } else { + clearFill(tonext); + trace._polygons = null; + } + } + trace._prevRevpath = revpath; + trace._prevPolygons = thisPolygons; + } else { + if(ownFillEl3) clearFill(ownFillEl3); + else if(tonext) clearFill(tonext); + trace._polygons = trace._prevRevpath = trace._prevPolygons = null; + } + + + function visFilter(d) { + return d.filter(function(v) { return !v.gap && v.vis; }); + } + + function visFilterWithGaps(d) { + return d.filter(function(v) { return v.vis; }); + } + + function gapFilter(d) { + return d.filter(function(v) { return !v.gap; }); + } + + function keyFunc(d) { + return d.id; + } + + // Returns a function if the trace is keyed, otherwise returns undefined + function getKeyFunc(trace) { + if(trace.ids) { + return keyFunc; + } + } + + function hideFilter() { + return false; + } + + function makePoints(points, text, cdscatter) { + var join, selection, hasNode; + + var trace = cdscatter[0].trace; + var showMarkers = subTypes.hasMarkers(trace); + var showText = subTypes.hasText(trace); + + var keyFunc = getKeyFunc(trace); + var markerFilter = hideFilter; + var textFilter = hideFilter; + + if(showMarkers || showText) { + var showFilter = identity; + // if we're stacking, "infer zero" gap mode gets markers in the + // gap points - because we've inferred a zero there - but other + // modes (currently "interpolate", later "interrupt" hopefully) + // we don't draw generated markers + var stackGroup = trace.stackgroup; + var isInferZero = stackGroup && ( + gd._fullLayout._scatterStackOpts[xa._id + ya._id][stackGroup].stackgaps === 'infer zero'); + if(trace.marker.maxdisplayed || trace._needsCull) { + showFilter = isInferZero ? visFilterWithGaps : visFilter; + } else if(stackGroup && !isInferZero) { + showFilter = gapFilter; + } + + if(showMarkers) markerFilter = showFilter; + if(showText) textFilter = showFilter; + } + + // marker points + + selection = points.selectAll('path.point'); + + join = selection.data(markerFilter, keyFunc); + + var enter = join.enter().append('path') + .classed('point', true); + + if(hasTransition) { + enter + .call(Drawing.pointStyle, trace, gd) + .call(Drawing.translatePoints, xa, ya) + .style('opacity', 0) + .transition() + .style('opacity', 1); + } + + join.order(); + + var styleFns; + if(showMarkers) { + styleFns = Drawing.makePointStyleFns(trace); + } + + join.each(function(d) { + var el = d3.select(this); + var sel = transition(el); + hasNode = Drawing.translatePoint(d, sel, xa, ya); + + if(hasNode) { + Drawing.singlePointStyle(d, sel, trace, styleFns, gd); + + if(plotinfo.layerClipId) { + Drawing.hideOutsideRangePoint(d, sel, xa, ya, trace.xcalendar, trace.ycalendar); + } + + if(trace.customdata) { + el.classed('plotly-customdata', d.data !== null && d.data !== undefined); + } + } else { + sel.remove(); + } + }); + + if(hasTransition) { + join.exit().transition() + .style('opacity', 0) + .remove(); + } else { + join.exit().remove(); + } + + // text points + selection = text.selectAll('g'); + join = selection.data(textFilter, keyFunc); + + // each text needs to go in its own 'g' in case + // it gets converted to mathjax + join.enter().append('g').classed('textpoint', true).append('text'); + + join.order(); + + join.each(function(d) { + var g = d3.select(this); + var sel = transition(g.select('text')); + hasNode = Drawing.translatePoint(d, sel, xa, ya); + + if(hasNode) { + if(plotinfo.layerClipId) { + Drawing.hideOutsideRangePoint(d, g, xa, ya, trace.xcalendar, trace.ycalendar); + } + } else { + g.remove(); + } + }); + + join.selectAll('text') + .call(Drawing.textPointStyle, trace, gd) + .each(function(d) { + // This just *has* to be totally custom becuase of SVG text positioning :( + // It's obviously copied from translatePoint; we just can't use that + var x = xa.c2p(d.x); + var y = ya.c2p(d.y); + + d3.select(this).selectAll('tspan.line').each(function() { + transition(d3.select(this)).attr({x: x, y: y}); + }); + }); + + join.exit().remove(); + } + + points.datum(cdscatter); + text.datum(cdscatter); + makePoints(points, text, cdscatter); + + // lastly, clip points groups of `cliponaxis !== false` traces + // on `plotinfo._hasClipOnAxisFalse === true` subplots + var hasClipOnAxisFalse = trace.cliponaxis === false; + var clipUrl = hasClipOnAxisFalse ? null : plotinfo.layerClipId; + Drawing.setClipUrl(points, clipUrl, gd); + Drawing.setClipUrl(text, clipUrl, gd); +} + +function selectMarkers(gd, idx, plotinfo, cdscatter, cdscatterAll) { + var xa = plotinfo.xaxis; + var ya = plotinfo.yaxis; + var xr = d3.extent(Lib.simpleMap(xa.range, xa.r2c)); + var yr = d3.extent(Lib.simpleMap(ya.range, ya.r2c)); + + var trace = cdscatter[0].trace; + if(!subTypes.hasMarkers(trace)) return; + // if marker.maxdisplayed is used, select a maximum of + // mnum markers to show, from the set that are in the viewport + var mnum = trace.marker.maxdisplayed; + + // TODO: remove some as we get away from the viewport? + if(mnum === 0) return; + + var cd = cdscatter.filter(function(v) { + return v.x >= xr[0] && v.x <= xr[1] && v.y >= yr[0] && v.y <= yr[1]; + }); + var inc = Math.ceil(cd.length / mnum); + var tnum = 0; + cdscatterAll.forEach(function(cdj, j) { + var tracei = cdj[0].trace; + if(subTypes.hasMarkers(tracei) && + tracei.marker.maxdisplayed > 0 && j < idx) { + tnum++; + } + }); + + // if multiple traces use maxdisplayed, stagger which markers we + // display this formula offsets successive traces by 1/3 of the + // increment, adding an extra small amount after each triplet so + // it's not quite periodic + var i0 = Math.round(tnum * inc / 3 + Math.floor(tnum / 3) * inc / 7.1); + + // for error bars: save in cd which markers to show + // so we don't have to repeat this + cdscatter.forEach(function(v) { delete v.vis; }); + cd.forEach(function(v, i) { + if(Math.round((i + i0) % inc) === 0) v.vis = true; + }); +} + +},{"../../components/drawing":72,"../../lib":169,"../../lib/polygon":181,"../../registry":258,"./line_points":389,"./link_traces":391,"./subtypes":399,"d3":16}],396:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var subtypes = _dereq_('./subtypes'); + +module.exports = function selectPoints(searchInfo, selectionTester) { + var cd = searchInfo.cd; + var xa = searchInfo.xaxis; + var ya = searchInfo.yaxis; + var selection = []; + var trace = cd[0].trace; + var i; + var di; + var x; + var y; + + var hasOnlyLines = (!subtypes.hasMarkers(trace) && !subtypes.hasText(trace)); + if(hasOnlyLines) return []; + + if(selectionTester === false) { // clear selection + for(i = 0; i < cd.length; i++) { + cd[i].selected = 0; + } + } else { + for(i = 0; i < cd.length; i++) { + di = cd[i]; + x = xa.c2p(di.x); + y = ya.c2p(di.y); + + if((di.i !== null) && selectionTester.contains([x, y], false, i, searchInfo)) { + selection.push({ + pointNumber: di.i, + x: xa.c2d(di.x), + y: ya.c2d(di.y) + }); + di.selected = 1; + } else { + di.selected = 0; + } + } + } + + return selection; +}; + +},{"./subtypes":399}],397:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var perStackAttrs = ['orientation', 'groupnorm', 'stackgaps']; + +module.exports = function handleStackDefaults(traceIn, traceOut, layout, coerce) { + var stackOpts = layout._scatterStackOpts; + + var stackGroup = coerce('stackgroup'); + if(stackGroup) { + // use independent stacking options per subplot + var subplot = traceOut.xaxis + traceOut.yaxis; + var subplotStackOpts = stackOpts[subplot]; + if(!subplotStackOpts) subplotStackOpts = stackOpts[subplot] = {}; + + var groupOpts = subplotStackOpts[stackGroup]; + var firstTrace = false; + if(groupOpts) { + groupOpts.traces.push(traceOut); + } else { + groupOpts = subplotStackOpts[stackGroup] = { + // keep track of trace indices for use during stacking calculations + // this will be filled in during `calc` and used during `crossTraceCalc` + // so it's OK if we don't recreate it during a non-calc edit + traceIndices: [], + // Hold on to the whole set of prior traces + // First one is most important, so we can clear defaults + // there if we find explicit values only in later traces. + // We're only going to *use* the values stored in groupOpts, + // but for the editor and validate we want things self-consistent + // The full set of traces is used only to fix `fill` default if + // we find `orientation: 'h'` beyond the first trace + traces: [traceOut] + }; + firstTrace = true; + } + // TODO: how is this going to work with groupby transforms? + // in principle it should be OK I guess, as long as explicit group styles + // don't override explicit base-trace styles? + + var dflts = { + orientation: (traceOut.x && !traceOut.y) ? 'h' : 'v' + }; + + for(var i = 0; i < perStackAttrs.length; i++) { + var attr = perStackAttrs[i]; + var attrFound = attr + 'Found'; + if(!groupOpts[attrFound]) { + var traceHasAttr = traceIn[attr] !== undefined; + var isOrientation = attr === 'orientation'; + if(traceHasAttr || firstTrace) { + groupOpts[attr] = coerce(attr, dflts[attr]); + + if(isOrientation) { + groupOpts.fillDflt = groupOpts[attr] === 'h' ? + 'tonextx' : 'tonexty'; + } + + if(traceHasAttr) { + // Note: this will show a value here even if it's invalid + // in which case it will revert to default. + groupOpts[attrFound] = true; + + // Note: only one trace in the stack will get a _fullData + // entry for a given stack-wide attribute. If no traces + // (or the first trace) specify that attribute, the + // first trace will get it. If the first trace does NOT + // specify it but some later trace does, then it gets + // removed from the first trace and only included in the + // one that specified it. This is mostly important for + // editors (that want to see the full values to know + // what settings are available) and Plotly.react diffing. + // Editors may want to use fullLayout._scatterStackOpts + // directly and make these settings available from all + // traces in the stack... then set the new value into + // the first trace, and clear all later traces. + if(!firstTrace) { + delete groupOpts.traces[0][attr]; + + // orientation can affect default fill of previous traces + if(isOrientation) { + for(var j = 0; j < groupOpts.traces.length - 1; j++) { + var trace2 = groupOpts.traces[j]; + if(trace2._input.fill !== trace2.fill) { + trace2.fill = groupOpts.fillDflt; + } + } + } + } + } + } + } + } + return groupOpts; + } +}; + +},{}],398:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var d3 = _dereq_('d3'); +var Drawing = _dereq_('../../components/drawing'); +var Registry = _dereq_('../../registry'); + +function style(gd) { + var s = d3.select(gd).selectAll('g.trace.scatter'); + + s.style('opacity', function(d) { + return d[0].trace.opacity; + }); + + s.selectAll('g.points').each(function(d) { + var sel = d3.select(this); + var trace = d.trace || d[0].trace; + stylePoints(sel, trace, gd); + }); + + s.selectAll('g.text').each(function(d) { + var sel = d3.select(this); + var trace = d.trace || d[0].trace; + styleText(sel, trace, gd); + }); + + s.selectAll('g.trace path.js-line') + .call(Drawing.lineGroupStyle); + + s.selectAll('g.trace path.js-fill') + .call(Drawing.fillGroupStyle); + + Registry.getComponentMethod('errorbars', 'style')(s); +} + +function stylePoints(sel, trace, gd) { + Drawing.pointStyle(sel.selectAll('path.point'), trace, gd); +} + +function styleText(sel, trace, gd) { + Drawing.textPointStyle(sel.selectAll('text'), trace, gd); +} + +function styleOnSelect(gd, cd, sel) { + var trace = cd[0].trace; + + if(trace.selectedpoints) { + Drawing.selectedPointStyle(sel.selectAll('path.point'), trace); + Drawing.selectedTextStyle(sel.selectAll('text'), trace); + } else { + stylePoints(sel, trace, gd); + styleText(sel, trace, gd); + } +} + +module.exports = { + style: style, + stylePoints: stylePoints, + styleText: styleText, + styleOnSelect: styleOnSelect +}; + +},{"../../components/drawing":72,"../../registry":258,"d3":16}],399:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Lib = _dereq_('../../lib'); + +module.exports = { + hasLines: function(trace) { + return trace.visible && trace.mode && + trace.mode.indexOf('lines') !== -1; + }, + + hasMarkers: function(trace) { + return trace.visible && ( + (trace.mode && trace.mode.indexOf('markers') !== -1) || + // until splom implements 'mode' + trace.type === 'splom' + ); + }, + + hasText: function(trace) { + return trace.visible && trace.mode && + trace.mode.indexOf('text') !== -1; + }, + + isBubble: function(trace) { + return Lib.isPlainObject(trace.marker) && + Lib.isArrayOrTypedArray(trace.marker.size); + } +}; + +},{"../../lib":169}],400:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Lib = _dereq_('../../lib'); + +/* + * opts: object of flags to control features not all text users support + * noSelect: caller does not support selected/unselected attribute containers + */ +module.exports = function(traceIn, traceOut, layout, coerce, opts) { + opts = opts || {}; + + coerce('textposition'); + Lib.coerceFont(coerce, 'textfont', layout.font); + + if(!opts.noSelect) { + coerce('selected.textfont.color'); + coerce('unselected.textfont.color'); + } +}; + +},{"../../lib":169}],401:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var Registry = _dereq_('../../registry'); + +module.exports = function handleXYDefaults(traceIn, traceOut, layout, coerce) { + var x = coerce('x'); + var y = coerce('y'); + var len; + + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); + handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout); + + if(x) { + var xlen = Lib.minRowLength(x); + if(y) { + len = Math.min(xlen, Lib.minRowLength(y)); + } else { + len = xlen; + coerce('y0'); + coerce('dy'); + } + } else { + if(!y) return 0; + + len = Lib.minRowLength(y); + coerce('x0'); + coerce('dx'); + } + + traceOut._length = len; + + return len; +}; + +},{"../../lib":169,"../../registry":258}],402:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs; +var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs; +var scatterAttrs = _dereq_('../scatter/attributes'); +var plotAttrs = _dereq_('../../plots/attributes'); +var colorScaleAttrs = _dereq_('../../components/colorscale/attributes'); +var dash = _dereq_('../../components/drawing/attributes').dash; + +var extendFlat = _dereq_('../../lib/extend').extendFlat; + +var scatterMarkerAttrs = scatterAttrs.marker; +var scatterLineAttrs = scatterAttrs.line; +var scatterMarkerLineAttrs = scatterMarkerAttrs.line; + +module.exports = { + a: { + valType: 'data_array', + editType: 'calc', + + }, + b: { + valType: 'data_array', + editType: 'calc', + + }, + c: { + valType: 'data_array', + editType: 'calc', + + }, + sum: { + valType: 'number', + + dflt: 0, + min: 0, + editType: 'calc', + + }, + mode: extendFlat({}, scatterAttrs.mode, {dflt: 'markers'}), + text: extendFlat({}, scatterAttrs.text, { + + }), + texttemplate: texttemplateAttrs({editType: 'plot'}, { + keys: ['a', 'b', 'c', 'text'] + }), + hovertext: extendFlat({}, scatterAttrs.hovertext, { + + }), + line: { + color: scatterLineAttrs.color, + width: scatterLineAttrs.width, + dash: dash, + shape: extendFlat({}, scatterLineAttrs.shape, + {values: ['linear', 'spline']}), + smoothing: scatterLineAttrs.smoothing, + editType: 'calc' + }, + connectgaps: scatterAttrs.connectgaps, + cliponaxis: scatterAttrs.cliponaxis, + fill: extendFlat({}, scatterAttrs.fill, { + values: ['none', 'toself', 'tonext'], + dflt: 'none', + + }), + fillcolor: scatterAttrs.fillcolor, + marker: extendFlat({ + symbol: scatterMarkerAttrs.symbol, + opacity: scatterMarkerAttrs.opacity, + maxdisplayed: scatterMarkerAttrs.maxdisplayed, + size: scatterMarkerAttrs.size, + sizeref: scatterMarkerAttrs.sizeref, + sizemin: scatterMarkerAttrs.sizemin, + sizemode: scatterMarkerAttrs.sizemode, + line: extendFlat({ + width: scatterMarkerLineAttrs.width, + editType: 'calc' + }, + colorScaleAttrs('marker.line') + ), + gradient: scatterMarkerAttrs.gradient, + editType: 'calc' + }, + colorScaleAttrs('marker') + ), + + textfont: scatterAttrs.textfont, + textposition: scatterAttrs.textposition, + + selected: scatterAttrs.selected, + unselected: scatterAttrs.unselected, + + hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { + flags: ['a', 'b', 'c', 'text', 'name'] + }), + hoveron: scatterAttrs.hoveron, + hovertemplate: hovertemplateAttrs(), +}; + +},{"../../components/colorscale/attributes":58,"../../components/drawing/attributes":71,"../../lib/extend":164,"../../plots/attributes":210,"../../plots/template_attributes":253,"../scatter/attributes":376}],403:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var isNumeric = _dereq_('fast-isnumeric'); + +var calcColorscale = _dereq_('../scatter/colorscale_calc'); +var arraysToCalcdata = _dereq_('../scatter/arrays_to_calcdata'); +var calcSelection = _dereq_('../scatter/calc_selection'); +var calcMarkerSize = _dereq_('../scatter/calc').calcMarkerSize; + +var dataArrays = ['a', 'b', 'c']; +var arraysToFill = {a: ['b', 'c'], b: ['a', 'c'], c: ['a', 'b']}; + +module.exports = function calc(gd, trace) { + var ternary = gd._fullLayout[trace.subplot]; + var displaySum = ternary.sum; + var normSum = trace.sum || displaySum; + var arrays = {a: trace.a, b: trace.b, c: trace.c}; + + var i, j, dataArray, newArray, fillArray1, fillArray2; + + // fill in one missing component + for(i = 0; i < dataArrays.length; i++) { + dataArray = dataArrays[i]; + if(arrays[dataArray]) continue; + + fillArray1 = arrays[arraysToFill[dataArray][0]]; + fillArray2 = arrays[arraysToFill[dataArray][1]]; + newArray = new Array(fillArray1.length); + for(j = 0; j < fillArray1.length; j++) { + newArray[j] = normSum - fillArray1[j] - fillArray2[j]; + } + arrays[dataArray] = newArray; + } + + // make the calcdata array + var serieslen = trace._length; + var cd = new Array(serieslen); + var a, b, c, norm, x, y; + for(i = 0; i < serieslen; i++) { + a = arrays.a[i]; + b = arrays.b[i]; + c = arrays.c[i]; + if(isNumeric(a) && isNumeric(b) && isNumeric(c)) { + a = +a; + b = +b; + c = +c; + norm = displaySum / (a + b + c); + if(norm !== 1) { + a *= norm; + b *= norm; + c *= norm; + } + // map a, b, c onto x and y where the full scale of y + // is [0, sum], and x is [-sum, sum] + // TODO: this makes `a` always the top, `b` the bottom left, + // and `c` the bottom right. Do we want options to rearrange + // these? + y = a; + x = c - b; + cd[i] = {x: x, y: y, a: a, b: b, c: c}; + } else cd[i] = {x: false, y: false}; + } + + calcMarkerSize(trace, serieslen); + calcColorscale(gd, trace); + arraysToCalcdata(cd, trace); + calcSelection(cd, trace); + + return cd; +}; + +},{"../scatter/arrays_to_calcdata":375,"../scatter/calc":377,"../scatter/calc_selection":378,"../scatter/colorscale_calc":379,"fast-isnumeric":18}],404:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Lib = _dereq_('../../lib'); + +var constants = _dereq_('../scatter/constants'); +var subTypes = _dereq_('../scatter/subtypes'); +var handleMarkerDefaults = _dereq_('../scatter/marker_defaults'); +var handleLineDefaults = _dereq_('../scatter/line_defaults'); +var handleLineShapeDefaults = _dereq_('../scatter/line_shape_defaults'); +var handleTextDefaults = _dereq_('../scatter/text_defaults'); +var handleFillColorDefaults = _dereq_('../scatter/fillcolor_defaults'); + +var attributes = _dereq_('./attributes'); + + +module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { + function coerce(attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); + } + + var a = coerce('a'); + var b = coerce('b'); + var c = coerce('c'); + var len; + + // allow any one array to be missing, len is the minimum length of those + // present. Note that after coerce data_array's are either Arrays (which + // are truthy even if empty) or undefined. As in scatter, an empty array + // is different from undefined, because it can signify that this data is + // not known yet but expected in the future + if(a) { + len = a.length; + if(b) { + len = Math.min(len, b.length); + if(c) len = Math.min(len, c.length); + } else if(c) len = Math.min(len, c.length); + else len = 0; + } else if(b && c) { + len = Math.min(b.length, c.length); + } + + if(!len) { + traceOut.visible = false; + return; + } + + traceOut._length = len; + + coerce('sum'); + + coerce('text'); + coerce('hovertext'); + if(traceOut.hoveron !== 'fills') coerce('hovertemplate'); + + var defaultMode = len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines'; + coerce('mode', defaultMode); + + if(subTypes.hasLines(traceOut)) { + handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce); + handleLineShapeDefaults(traceIn, traceOut, coerce); + coerce('connectgaps'); + } + + if(subTypes.hasMarkers(traceOut)) { + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {gradient: true}); + } + + if(subTypes.hasText(traceOut)) { + coerce('texttemplate'); + handleTextDefaults(traceIn, traceOut, layout, coerce); + } + + var dfltHoverOn = []; + + if(subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) { + coerce('cliponaxis'); + coerce('marker.maxdisplayed'); + dfltHoverOn.push('points'); + } + + coerce('fill'); + if(traceOut.fill !== 'none') { + handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); + if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce); + } + + if(traceOut.fill === 'tonext' || traceOut.fill === 'toself') { + dfltHoverOn.push('fills'); + } + coerce('hoveron', dfltHoverOn.join('+') || 'points'); + + Lib.coerceSelectionMarkerOpacity(traceOut, coerce); +}; + +},{"../../lib":169,"../scatter/constants":380,"../scatter/fillcolor_defaults":384,"../scatter/line_defaults":388,"../scatter/line_shape_defaults":390,"../scatter/marker_defaults":394,"../scatter/subtypes":399,"../scatter/text_defaults":400,"./attributes":402}],405:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = function eventData(out, pt, trace, cd, pointNumber) { + if(pt.xa) out.xaxis = pt.xa; + if(pt.ya) out.yaxis = pt.ya; + + if(cd[pointNumber]) { + var cdi = cd[pointNumber]; + + // N.B. These are the normalized coordinates. + out.a = cdi.a; + out.b = cdi.b; + out.c = cdi.c; + } else { + // for fill-hover only + out.a = pt.a; + out.b = pt.b; + out.c = pt.c; + } + + return out; +}; + +},{}],406:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var scatterHover = _dereq_('../scatter/hover'); +var Axes = _dereq_('../../plots/cartesian/axes'); + + +module.exports = function hoverPoints(pointData, xval, yval, hovermode) { + var scatterPointData = scatterHover(pointData, xval, yval, hovermode); + if(!scatterPointData || scatterPointData[0].index === false) return; + + var newPointData = scatterPointData[0]; + + // if hovering on a fill, we don't show any point data so the label is + // unchanged from what scatter gives us - except that it needs to + // be constrained to the trianglular plot area, not just the rectangular + // area defined by the synthetic x and y axes + // TODO: in some cases the vertical middle of the shape is not within + // the triangular viewport at all, so the label can become disconnected + // from the shape entirely. But calculating what portion of the shape + // is actually visible, as constrained by the diagonal axis lines, is not + // so easy and anyway we lost the information we would have needed to do + // this inside scatterHover. + if(newPointData.index === undefined) { + var yFracUp = 1 - (newPointData.y0 / pointData.ya._length); + var xLen = pointData.xa._length; + var xMin = xLen * yFracUp / 2; + var xMax = xLen - xMin; + newPointData.x0 = Math.max(Math.min(newPointData.x0, xMax), xMin); + newPointData.x1 = Math.max(Math.min(newPointData.x1, xMax), xMin); + return scatterPointData; + } + + var cdi = newPointData.cd[newPointData.index]; + + newPointData.a = cdi.a; + newPointData.b = cdi.b; + newPointData.c = cdi.c; + + newPointData.xLabelVal = undefined; + newPointData.yLabelVal = undefined; + + var ternary = newPointData.subplot; + newPointData.aLabel = Axes.tickText(ternary.aaxis, cdi.a, 'hover').text; + newPointData.bLabel = Axes.tickText(ternary.baxis, cdi.b, 'hover').text; + newPointData.cLabel = Axes.tickText(ternary.caxis, cdi.c, 'hover').text; + + var trace = newPointData.trace; + var hoverinfo = cdi.hi || trace.hoverinfo; + var text = []; + function textPart(ax, val) { + text.push(ax._hovertitle + ': ' + val); + } + if(!trace.hovertemplate) { + var parts = hoverinfo.split('+'); + if(parts.indexOf('all') !== -1) parts = ['a', 'b', 'c']; + if(parts.indexOf('a') !== -1) textPart(ternary.aaxis, newPointData.aLabel); + if(parts.indexOf('b') !== -1) textPart(ternary.baxis, newPointData.bLabel); + if(parts.indexOf('c') !== -1) textPart(ternary.caxis, newPointData.cLabel); + } + newPointData.extraText = text.join('
'); + newPointData.hovertemplate = trace.hovertemplate; + return scatterPointData; +}; + +},{"../../plots/cartesian/axes":213,"../scatter/hover":386}],407:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + attributes: _dereq_('./attributes'), + supplyDefaults: _dereq_('./defaults'), + colorbar: _dereq_('../scatter/marker_colorbar'), + calc: _dereq_('./calc'), + plot: _dereq_('./plot'), + style: _dereq_('../scatter/style').style, + styleOnSelect: _dereq_('../scatter/style').styleOnSelect, + hoverPoints: _dereq_('./hover'), + selectPoints: _dereq_('../scatter/select'), + eventData: _dereq_('./event_data'), + + moduleType: 'trace', + name: 'scatterternary', + basePlotModule: _dereq_('../../plots/ternary'), + categories: ['ternary', 'symbols', 'showLegend', 'scatter-like'], + meta: { + + + } +}; + +},{"../../plots/ternary":254,"../scatter/marker_colorbar":393,"../scatter/select":396,"../scatter/style":398,"./attributes":402,"./calc":403,"./defaults":404,"./event_data":405,"./hover":406,"./plot":408}],408:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var scatterPlot = _dereq_('../scatter/plot'); + +module.exports = function plot(gd, ternary, moduleCalcData) { + var plotContainer = ternary.plotContainer; + + // remove all nodes inside the scatter layer + plotContainer.select('.scatterlayer').selectAll('*').remove(); + + // mimic cartesian plotinfo + var plotinfo = { + xaxis: ternary.xaxis, + yaxis: ternary.yaxis, + plot: plotContainer, + layerClipId: ternary._hasClipOnAxisFalse ? ternary.clipIdRelative : null + }; + + var scatterLayer = ternary.layers.frontplot.select('g.scatterlayer'); + + scatterPlot(gd, plotinfo, moduleCalcData, scatterLayer); +}; + +},{"../scatter/plot":395}],409:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var boxAttrs = _dereq_('../box/attributes'); +var extendFlat = _dereq_('../../lib/extend').extendFlat; + +module.exports = { + y: boxAttrs.y, + x: boxAttrs.x, + x0: boxAttrs.x0, + y0: boxAttrs.y0, + name: extendFlat({}, boxAttrs.name, { + + }), + orientation: extendFlat({}, boxAttrs.orientation, { + + }), + + bandwidth: { + valType: 'number', + min: 0, + + editType: 'calc', + + }, + + scalegroup: { + valType: 'string', + + dflt: '', + editType: 'calc', + + }, + scalemode: { + valType: 'enumerated', + values: ['width', 'count'], + dflt: 'width', + + editType: 'calc', + + }, + + spanmode: { + valType: 'enumerated', + values: ['soft', 'hard', 'manual'], + dflt: 'soft', + + editType: 'calc', + + }, + span: { + valType: 'info_array', + items: [ + {valType: 'any', editType: 'calc'}, + {valType: 'any', editType: 'calc'} + ], + + editType: 'calc', + + }, + + line: { + color: { + valType: 'color', + + editType: 'style', + + }, + width: { + valType: 'number', + + min: 0, + dflt: 2, + editType: 'style', + + }, + editType: 'plot' + }, + fillcolor: boxAttrs.fillcolor, + + points: extendFlat({}, boxAttrs.boxpoints, { + + }), + jitter: extendFlat({}, boxAttrs.jitter, { + + }), + pointpos: extendFlat({}, boxAttrs.pointpos, { + + }), + + width: extendFlat({}, boxAttrs.width, { + + }), + + marker: boxAttrs.marker, + text: boxAttrs.text, + hovertext: boxAttrs.hovertext, + hovertemplate: boxAttrs.hovertemplate, + + box: { + visible: { + valType: 'boolean', + dflt: false, + + editType: 'plot', + + }, + width: { + valType: 'number', + min: 0, + max: 1, + dflt: 0.25, + + editType: 'plot', + + }, + fillcolor: { + valType: 'color', + + editType: 'style', + + }, + line: { + color: { + valType: 'color', + + editType: 'style', + + }, + width: { + valType: 'number', + min: 0, + + editType: 'style', + + }, + editType: 'style' + }, + editType: 'plot' + }, + + meanline: { + visible: { + valType: 'boolean', + dflt: false, + + editType: 'plot', + + }, + color: { + valType: 'color', + + editType: 'style', + + }, + width: { + valType: 'number', + min: 0, + + editType: 'style', + + }, + editType: 'plot' + }, + + side: { + valType: 'enumerated', + values: ['both', 'positive', 'negative'], + dflt: 'both', + + editType: 'calc', + + }, + + offsetgroup: boxAttrs.offsetgroup, + alignmentgroup: boxAttrs.alignmentgroup, + + selected: boxAttrs.selected, + unselected: boxAttrs.unselected, + + hoveron: { + valType: 'flaglist', + flags: ['violins', 'points', 'kde'], + dflt: 'violins+points+kde', + extras: ['all'], + + editType: 'style', + + } +}; + +},{"../../lib/extend":164,"../box/attributes":283}],410:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var Axes = _dereq_('../../plots/cartesian/axes'); +var boxCalc = _dereq_('../box/calc'); +var helpers = _dereq_('./helpers'); +var BADNUM = _dereq_('../../constants/numerical').BADNUM; + +module.exports = function calc(gd, trace) { + var cd = boxCalc(gd, trace); + + if(cd[0].t.empty) return cd; + + var fullLayout = gd._fullLayout; + var valAxis = Axes.getFromId( + gd, + trace[trace.orientation === 'h' ? 'xaxis' : 'yaxis'] + ); + + var spanMin = Infinity; + var spanMax = -Infinity; + var maxKDE = 0; + var maxCount = 0; + + for(var i = 0; i < cd.length; i++) { + var cdi = cd[i]; + var vals = cdi.pts.map(helpers.extractVal); + + var bandwidth = cdi.bandwidth = calcBandwidth(trace, cdi, vals); + var span = cdi.span = calcSpan(trace, cdi, valAxis, bandwidth); + + if(cdi.min === cdi.max && bandwidth === 0) { + // if span is zero and bandwidth is zero, we want a violin with zero width + span = cdi.span = [cdi.min, cdi.max]; + cdi.density = [{v: 1, t: span[0]}]; + cdi.bandwidth = bandwidth; + maxKDE = Math.max(maxKDE, 1); + } else { + // step that well covers the bandwidth and is multiple of span distance + var dist = span[1] - span[0]; + var n = Math.ceil(dist / (bandwidth / 3)); + var step = dist / n; + + if(!isFinite(step) || !isFinite(n)) { + Lib.error('Something went wrong with computing the violin span'); + cd[0].t.empty = true; + return cd; + } + + var kde = helpers.makeKDE(cdi, trace, vals); + cdi.density = new Array(n); + + for(var k = 0, t = span[0]; t < (span[1] + step / 2); k++, t += step) { + var v = kde(t); + cdi.density[k] = {v: v, t: t}; + maxKDE = Math.max(maxKDE, v); + } + } + + maxCount = Math.max(maxCount, vals.length); + spanMin = Math.min(spanMin, span[0]); + spanMax = Math.max(spanMax, span[1]); + } + + var extremes = Axes.findExtremes(valAxis, [spanMin, spanMax], {padded: true}); + trace._extremes[valAxis._id] = extremes; + + if(trace.width) { + cd[0].t.maxKDE = maxKDE; + } else { + var violinScaleGroupStats = fullLayout._violinScaleGroupStats; + var scaleGroup = trace.scalegroup; + var groupStats = violinScaleGroupStats[scaleGroup]; + + if(groupStats) { + groupStats.maxKDE = Math.max(groupStats.maxKDE, maxKDE); + groupStats.maxCount = Math.max(groupStats.maxCount, maxCount); + } else { + violinScaleGroupStats[scaleGroup] = { + maxKDE: maxKDE, + maxCount: maxCount + }; + } + } + + cd[0].t.labels.kde = Lib._(gd, 'kde:'); + + return cd; +}; + +// Default to Silveman's rule of thumb +// - https://stats.stackexchange.com/a/6671 +// - https://en.wikipedia.org/wiki/Kernel_density_estimation#A_rule-of-thumb_bandwidth_estimator +// - https://github.com/statsmodels/statsmodels/blob/master/statsmodels/nonparametric/bandwidths.py +function silvermanRule(len, ssd, iqr) { + var a = Math.min(ssd, iqr / 1.349); + return 1.059 * a * Math.pow(len, -0.2); +} + +function calcBandwidth(trace, cdi, vals) { + var span = cdi.max - cdi.min; + + // If span is zero + if(!span) { + if(trace.bandwidth) { + return trace.bandwidth; + } else { + // if span is zero and no bandwidth is specified + // it returns zero bandwidth which is a special case + return 0; + } + } + + // Limit how small the bandwidth can be. + // + // Silverman's rule of thumb can be "very" small + // when IQR does a poor job at describing the spread + // of the distribution. + // We also want to limit custom bandwidths + // to not blow up kde computations. + + if(trace.bandwidth) { + return Math.max(trace.bandwidth, span / 1e4); + } else { + var len = vals.length; + var ssd = Lib.stdev(vals, len - 1, cdi.mean); + return Math.max( + silvermanRule(len, ssd, cdi.q3 - cdi.q1), + span / 100 + ); + } +} + +function calcSpan(trace, cdi, valAxis, bandwidth) { + var spanmode = trace.spanmode; + var spanIn = trace.span || []; + var spanTight = [cdi.min, cdi.max]; + var spanLoose = [cdi.min - 2 * bandwidth, cdi.max + 2 * bandwidth]; + var spanOut; + + function calcSpanItem(index) { + var s = spanIn[index]; + var sc = valAxis.type === 'multicategory' ? + valAxis.r2c(s) : + valAxis.d2c(s, 0, trace[cdi.valLetter + 'calendar']); + return sc === BADNUM ? spanLoose[index] : sc; + } + + if(spanmode === 'soft') { + spanOut = spanLoose; + } else if(spanmode === 'hard') { + spanOut = spanTight; + } else { + spanOut = [calcSpanItem(0), calcSpanItem(1)]; + } + + // to reuse the equal-range-item block + var dummyAx = { + type: 'linear', + range: spanOut + }; + Axes.setConvert(dummyAx); + dummyAx.cleanRange(); + + return spanOut; +} + +},{"../../constants/numerical":149,"../../lib":169,"../../plots/cartesian/axes":213,"../box/calc":284,"./helpers":413}],411:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var setPositionOffset = _dereq_('../box/cross_trace_calc').setPositionOffset; +var orientations = ['v', 'h']; + +module.exports = function crossTraceCalc(gd, plotinfo) { + var calcdata = gd.calcdata; + var xa = plotinfo.xaxis; + var ya = plotinfo.yaxis; + + for(var i = 0; i < orientations.length; i++) { + var orientation = orientations[i]; + var posAxis = orientation === 'h' ? ya : xa; + var violinList = []; + + for(var j = 0; j < calcdata.length; j++) { + var cd = calcdata[j]; + var t = cd[0].t; + var trace = cd[0].trace; + + if(trace.visible === true && trace.type === 'violin' && + !t.empty && + trace.orientation === orientation && + trace.xaxis === xa._id && + trace.yaxis === ya._id + ) { + violinList.push(j); + } + } + + setPositionOffset('violin', gd, violinList, posAxis); + } +}; + +},{"../box/cross_trace_calc":285}],412:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var Color = _dereq_('../../components/color'); + +var boxDefaults = _dereq_('../box/defaults'); +var attributes = _dereq_('./attributes'); + +module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { + function coerce(attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); + } + function coerce2(attr, dflt) { + return Lib.coerce2(traceIn, traceOut, attributes, attr, dflt); + } + + boxDefaults.handleSampleDefaults(traceIn, traceOut, coerce, layout); + if(traceOut.visible === false) return; + + coerce('bandwidth'); + coerce('side'); + + var width = coerce('width'); + if(!width) { + coerce('scalegroup', traceOut.name); + coerce('scalemode'); + } + + var span = coerce('span'); + var spanmodeDflt; + if(Array.isArray(span)) spanmodeDflt = 'manual'; + coerce('spanmode', spanmodeDflt); + + var lineColor = coerce('line.color', (traceIn.marker || {}).color || defaultColor); + var lineWidth = coerce('line.width'); + var fillColor = coerce('fillcolor', Color.addOpacity(traceOut.line.color, 0.5)); + + boxDefaults.handlePointsDefaults(traceIn, traceOut, coerce, {prefix: ''}); + + var boxWidth = coerce2('box.width'); + var boxFillColor = coerce2('box.fillcolor', fillColor); + var boxLineColor = coerce2('box.line.color', lineColor); + var boxLineWidth = coerce2('box.line.width', lineWidth); + var boxVisible = coerce('box.visible', Boolean(boxWidth || boxFillColor || boxLineColor || boxLineWidth)); + if(!boxVisible) traceOut.box = {visible: false}; + + var meanLineColor = coerce2('meanline.color', lineColor); + var meanLineWidth = coerce2('meanline.width', lineWidth); + var meanLineVisible = coerce('meanline.visible', Boolean(meanLineColor || meanLineWidth)); + if(!meanLineVisible) traceOut.meanline = {visible: false}; +}; + +},{"../../components/color":51,"../../lib":169,"../box/defaults":286,"./attributes":409}],413:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); + +// Maybe add kernels more down the road, +// but note that the default `spanmode: 'soft'` bounds might have +// to become kernel-dependent +var kernels = { + gaussian: function(v) { + return (1 / Math.sqrt(2 * Math.PI)) * Math.exp(-0.5 * v * v); + } +}; + +exports.makeKDE = function(calcItem, trace, vals) { + var len = vals.length; + var kernel = kernels.gaussian; + var bandwidth = calcItem.bandwidth; + var factor = 1 / (len * bandwidth); + + // don't use Lib.aggNums to skip isNumeric checks + return function(x) { + var sum = 0; + for(var i = 0; i < len; i++) { + sum += kernel((x - vals[i]) / bandwidth); + } + return factor * sum; + }; +}; + +exports.getPositionOnKdePath = function(calcItem, trace, valuePx) { + var posLetter, valLetter; + + if(trace.orientation === 'h') { + posLetter = 'y'; + valLetter = 'x'; + } else { + posLetter = 'x'; + valLetter = 'y'; + } + + var pointOnPath = Lib.findPointOnPath( + calcItem.path, + valuePx, + valLetter, + {pathLength: calcItem.pathLength} + ); + + var posCenterPx = calcItem.posCenterPx; + var posOnPath0 = pointOnPath[posLetter]; + var posOnPath1 = trace.side === 'both' ? + 2 * posCenterPx - posOnPath0 : + posCenterPx; + + return [posOnPath0, posOnPath1]; +}; + +exports.getKdeValue = function(calcItem, trace, valueDist) { + var vals = calcItem.pts.map(exports.extractVal); + var kde = exports.makeKDE(calcItem, trace, vals); + return kde(valueDist) / calcItem.posDensityScale; +}; + +exports.extractVal = function(o) { return o.v; }; + +},{"../../lib":169}],414:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var Axes = _dereq_('../../plots/cartesian/axes'); +var boxHoverPoints = _dereq_('../box/hover'); +var helpers = _dereq_('./helpers'); + +module.exports = function hoverPoints(pointData, xval, yval, hovermode, hoverLayer) { + var cd = pointData.cd; + var trace = cd[0].trace; + var hoveron = trace.hoveron; + var hasHoveronViolins = hoveron.indexOf('violins') !== -1; + var hasHoveronKDE = hoveron.indexOf('kde') !== -1; + var closeData = []; + var closePtData; + var violinLineAttrs; + + if(hasHoveronViolins || hasHoveronKDE) { + var closeBoxData = boxHoverPoints.hoverOnBoxes(pointData, xval, yval, hovermode); + + if(hasHoveronKDE && closeBoxData.length > 0) { + var xa = pointData.xa; + var ya = pointData.ya; + var pLetter, vLetter, pAxis, vAxis, vVal; + + if(trace.orientation === 'h') { + vVal = xval; + pLetter = 'y'; + pAxis = ya; + vLetter = 'x'; + vAxis = xa; + } else { + vVal = yval; + pLetter = 'x'; + pAxis = xa; + vLetter = 'y'; + vAxis = ya; + } + + var di = cd[pointData.index]; + + if(vVal >= di.span[0] && vVal <= di.span[1]) { + var kdePointData = Lib.extendFlat({}, pointData); + var vValPx = vAxis.c2p(vVal, true); + var kdeVal = helpers.getKdeValue(di, trace, vVal); + var pOnPath = helpers.getPositionOnKdePath(di, trace, vValPx); + var paOffset = pAxis._offset; + var paLength = pAxis._length; + + kdePointData[pLetter + '0'] = pOnPath[0]; + kdePointData[pLetter + '1'] = pOnPath[1]; + kdePointData[vLetter + '0'] = kdePointData[vLetter + '1'] = vValPx; + kdePointData[vLetter + 'Label'] = vLetter + ': ' + Axes.hoverLabelText(vAxis, vVal) + ', ' + cd[0].t.labels.kde + ' ' + kdeVal.toFixed(3); + + // move the spike to the KDE point + kdePointData.spikeDistance = closeBoxData[0].spikeDistance; + var spikePosAttr = pLetter + 'Spike'; + kdePointData[spikePosAttr] = closeBoxData[0][spikePosAttr]; + closeBoxData[0].spikeDistance = undefined; + closeBoxData[0][spikePosAttr] = undefined; + + // no hovertemplate support yet + kdePointData.hovertemplate = false; + + closeData.push(kdePointData); + + violinLineAttrs = {stroke: pointData.color}; + violinLineAttrs[pLetter + '1'] = Lib.constrain(paOffset + pOnPath[0], paOffset, paOffset + paLength); + violinLineAttrs[pLetter + '2'] = Lib.constrain(paOffset + pOnPath[1], paOffset, paOffset + paLength); + violinLineAttrs[vLetter + '1'] = violinLineAttrs[vLetter + '2'] = vAxis._offset + vValPx; + } + } + + if(hasHoveronViolins) { + closeData = closeData.concat(closeBoxData); + } + } + + if(hoveron.indexOf('points') !== -1) { + closePtData = boxHoverPoints.hoverOnPoints(pointData, xval, yval); + } + + // update violin line (if any) + var violinLine = hoverLayer.selectAll('.violinline-' + trace.uid) + .data(violinLineAttrs ? [0] : []); + violinLine.enter().append('line') + .classed('violinline-' + trace.uid, true) + .attr('stroke-width', 1.5); + violinLine.exit().remove(); + violinLine.attr(violinLineAttrs); + + // same combine logic as box hoverPoints + if(hovermode === 'closest') { + if(closePtData) return [closePtData]; + return closeData; + } + if(closePtData) { + closeData.push(closePtData); + return closeData; + } + return closeData; +}; + +},{"../../lib":169,"../../plots/cartesian/axes":213,"../box/hover":288,"./helpers":413}],415:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + attributes: _dereq_('./attributes'), + layoutAttributes: _dereq_('./layout_attributes'), + supplyDefaults: _dereq_('./defaults'), + crossTraceDefaults: _dereq_('../box/defaults').crossTraceDefaults, + supplyLayoutDefaults: _dereq_('./layout_defaults'), + calc: _dereq_('./calc'), + crossTraceCalc: _dereq_('./cross_trace_calc'), + plot: _dereq_('./plot'), + style: _dereq_('./style'), + styleOnSelect: _dereq_('../scatter/style').styleOnSelect, + hoverPoints: _dereq_('./hover'), + selectPoints: _dereq_('../box/select'), + + moduleType: 'trace', + name: 'violin', + basePlotModule: _dereq_('../../plots/cartesian'), + categories: ['cartesian', 'svg', 'symbols', 'oriented', 'box-violin', 'showLegend', 'violinLayout', 'zoomScale'], + meta: { + + } +}; + +},{"../../plots/cartesian":224,"../box/defaults":286,"../box/select":293,"../scatter/style":398,"./attributes":409,"./calc":410,"./cross_trace_calc":411,"./defaults":412,"./hover":414,"./layout_attributes":416,"./layout_defaults":417,"./plot":418,"./style":419}],416:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var boxLayoutAttrs = _dereq_('../box/layout_attributes'); +var extendFlat = _dereq_('../../lib').extendFlat; + +module.exports = { + violinmode: extendFlat({}, boxLayoutAttrs.boxmode, { + + }), + violingap: extendFlat({}, boxLayoutAttrs.boxgap, { + + }), + violingroupgap: extendFlat({}, boxLayoutAttrs.boxgroupgap, { + + }) +}; + +},{"../../lib":169,"../box/layout_attributes":290}],417:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = _dereq_('../../lib'); +var layoutAttributes = _dereq_('./layout_attributes'); +var boxLayoutDefaults = _dereq_('../box/layout_defaults'); + +module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { + function coerce(attr, dflt) { + return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt); + } + boxLayoutDefaults._supply(layoutIn, layoutOut, fullData, coerce, 'violin'); +}; + +},{"../../lib":169,"../box/layout_defaults":291,"./layout_attributes":416}],418:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); +var Lib = _dereq_('../../lib'); +var Drawing = _dereq_('../../components/drawing'); + +var boxPlot = _dereq_('../box/plot'); +var linePoints = _dereq_('../scatter/line_points'); +var helpers = _dereq_('./helpers'); + +module.exports = function plot(gd, plotinfo, cdViolins, violinLayer) { + var fullLayout = gd._fullLayout; + var xa = plotinfo.xaxis; + var ya = plotinfo.yaxis; + + function makePath(pts) { + var segments = linePoints(pts, { + xaxis: xa, + yaxis: ya, + connectGaps: true, + baseTolerance: 0.75, + shape: 'spline', + simplify: true, + linearized: true + }); + return Drawing.smoothopen(segments[0], 1); + } + + Lib.makeTraceGroups(violinLayer, cdViolins, 'trace violins').each(function(cd) { + var plotGroup = d3.select(this); + var cd0 = cd[0]; + var t = cd0.t; + var trace = cd0.trace; + + if(trace.visible !== true || t.empty) { + plotGroup.remove(); + return; + } + + var bPos = t.bPos; + var bdPos = t.bdPos; + var valAxis = plotinfo[t.valLetter + 'axis']; + var posAxis = plotinfo[t.posLetter + 'axis']; + var hasBothSides = trace.side === 'both'; + var hasPositiveSide = hasBothSides || trace.side === 'positive'; + var hasNegativeSide = hasBothSides || trace.side === 'negative'; + + var violins = plotGroup.selectAll('path.violin').data(Lib.identity); + + violins.enter().append('path') + .style('vector-effect', 'non-scaling-stroke') + .attr('class', 'violin'); + + violins.exit().remove(); + + violins.each(function(d) { + var pathSel = d3.select(this); + var density = d.density; + var len = density.length; + var posCenter = posAxis.c2l(d.pos + bPos, true); + var posCenterPx = posAxis.l2p(posCenter); + + var scale; + if(trace.width) { + scale = t.maxKDE / bdPos; + } else { + var groupStats = fullLayout._violinScaleGroupStats[trace.scalegroup]; + scale = trace.scalemode === 'count' ? + (groupStats.maxKDE / bdPos) * (groupStats.maxCount / d.pts.length) : + groupStats.maxKDE / bdPos; + } + + var pathPos, pathNeg, path; + var i, k, pts, pt; + + if(hasPositiveSide) { + pts = new Array(len); + for(i = 0; i < len; i++) { + pt = pts[i] = {}; + pt[t.posLetter] = posCenter + (density[i].v / scale); + pt[t.valLetter] = valAxis.c2l(density[i].t, true); + } + pathPos = makePath(pts); + } + + if(hasNegativeSide) { + pts = new Array(len); + for(k = 0, i = len - 1; k < len; k++, i--) { + pt = pts[k] = {}; + pt[t.posLetter] = posCenter - (density[i].v / scale); + pt[t.valLetter] = valAxis.c2l(density[i].t, true); + } + pathNeg = makePath(pts); + } + + if(hasBothSides) { + path = pathPos + 'L' + pathNeg.substr(1) + 'Z'; + } else { + var startPt = [posCenterPx, valAxis.c2p(density[0].t)]; + var endPt = [posCenterPx, valAxis.c2p(density[len - 1].t)]; + + if(trace.orientation === 'h') { + startPt.reverse(); + endPt.reverse(); + } + + if(hasPositiveSide) { + path = 'M' + startPt + 'L' + pathPos.substr(1) + 'L' + endPt; + } else { + path = 'M' + endPt + 'L' + pathNeg.substr(1) + 'L' + startPt; + } + } + pathSel.attr('d', path); + + // save a few things used in getPositionOnKdePath, getKdeValue + // on hover and for meanline draw block below + d.posCenterPx = posCenterPx; + d.posDensityScale = scale * bdPos; + d.path = pathSel.node(); + d.pathLength = d.path.getTotalLength() / (hasBothSides ? 2 : 1); + }); + + var boxAttrs = trace.box; + var boxWidth = boxAttrs.width; + var boxLineWidth = (boxAttrs.line || {}).width; + var bdPosScaled; + var bPosPxOffset; + + if(hasBothSides) { + bdPosScaled = bdPos * boxWidth; + bPosPxOffset = 0; + } else if(hasPositiveSide) { + bdPosScaled = [0, bdPos * boxWidth / 2]; + bPosPxOffset = boxLineWidth * {x: 1, y: -1}[t.posLetter]; + } else { + bdPosScaled = [bdPos * boxWidth / 2, 0]; + bPosPxOffset = boxLineWidth * {x: -1, y: 1}[t.posLetter]; + } + + // inner box + boxPlot.plotBoxAndWhiskers(plotGroup, {pos: posAxis, val: valAxis}, trace, { + bPos: bPos, + bdPos: bdPosScaled, + bPosPxOffset: bPosPxOffset + }); + + // meanline insider box + boxPlot.plotBoxMean(plotGroup, {pos: posAxis, val: valAxis}, trace, { + bPos: bPos, + bdPos: bdPosScaled, + bPosPxOffset: bPosPxOffset + }); + + var fn; + if(!trace.box.visible && trace.meanline.visible) { + fn = Lib.identity; + } + + // N.B. use different class name than boxPlot.plotBoxMean, + // to avoid selectAll conflict + var meanPaths = plotGroup.selectAll('path.meanline').data(fn || []); + meanPaths.enter().append('path') + .attr('class', 'meanline') + .style('fill', 'none') + .style('vector-effect', 'non-scaling-stroke'); + meanPaths.exit().remove(); + meanPaths.each(function(d) { + var v = valAxis.c2p(d.mean, true); + var p = helpers.getPositionOnKdePath(d, trace, v); + + d3.select(this).attr('d', + trace.orientation === 'h' ? + 'M' + v + ',' + p[0] + 'V' + p[1] : + 'M' + p[0] + ',' + v + 'H' + p[1] + ); + }); + + boxPlot.plotPoints(plotGroup, {x: xa, y: ya}, trace, t); + }); +}; + +},{"../../components/drawing":72,"../../lib":169,"../box/plot":292,"../scatter/line_points":389,"./helpers":413,"d3":16}],419:[function(_dereq_,module,exports){ +/** +* Copyright 2012-2019, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = _dereq_('d3'); +var Color = _dereq_('../../components/color'); +var stylePoints = _dereq_('../scatter/style').stylePoints; + +module.exports = function style(gd) { + var s = d3.select(gd).selectAll('g.trace.violins'); + + s.style('opacity', function(d) { return d[0].trace.opacity; }); + + s.each(function(d) { + var trace = d[0].trace; + var sel = d3.select(this); + var box = trace.box || {}; + var boxLine = box.line || {}; + var meanline = trace.meanline || {}; + var meanLineWidth = meanline.width; + + sel.selectAll('path.violin') + .style('stroke-width', trace.line.width + 'px') + .call(Color.stroke, trace.line.color) + .call(Color.fill, trace.fillcolor); + + sel.selectAll('path.box') + .style('stroke-width', boxLine.width + 'px') + .call(Color.stroke, boxLine.color) + .call(Color.fill, box.fillcolor); + + var meanLineStyle = { + 'stroke-width': meanLineWidth + 'px', + 'stroke-dasharray': (2 * meanLineWidth) + 'px,' + meanLineWidth + 'px' + }; + + sel.selectAll('path.mean') + .style(meanLineStyle) + .call(Color.stroke, meanline.color); + + sel.selectAll('path.meanline') + .style(meanLineStyle) + .call(Color.stroke, meanline.color); + + stylePoints(sel, trace, gd); + }); +}; + +},{"../../components/color":51,"../scatter/style":398,"d3":16}]},{},[11])(11) +}); diff --git a/static/babybuddy/js/graph.a7dcc3322745.js.gz b/static/babybuddy/js/graph.a7dcc3322745.js.gz new file mode 100644 index 00000000..27c21c23 Binary files /dev/null and b/static/babybuddy/js/graph.a7dcc3322745.js.gz differ diff --git a/static/babybuddy/js/vendor.06e5507afbe2.js b/static/babybuddy/js/vendor.06e5507afbe2.js new file mode 100644 index 00000000..2d455009 --- /dev/null +++ b/static/babybuddy/js/vendor.06e5507afbe2.js @@ -0,0 +1,25042 @@ +/*! + * jQuery JavaScript Library v3.4.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2019-05-01T21:04Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var document = window.document; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var concat = arr.concat; + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + return typeof obj === "function" && typeof obj.nodeType !== "number"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + + + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.4.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }, + + // Support: Android <=4.0 only + // Make sure we trim BOM and NBSP + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + // Evaluates a script in a global context + globalEval: function( code, options ) { + DOMEval( code, { nonce: options && options.nonce } ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // Support: Android <=4.0 only + trim: function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.4 + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://js.foundation/ + * + * Date: 2019-04-08 + */ +(function( window ) { + +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rhtml = /HTML$/i, + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox<24 + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + // BMP codepoint + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { + + // ID selector + if ( (m = match[1]) ) { + + // Document context + if ( nodeType === 9 ) { + if ( (elem = context.getElementById( m )) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && (elem = newContext.getElementById( m )) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( (m = match[3]) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !nonnativeSelectorCache[ selector + " " ] && + (!rbuggyQSA || !rbuggyQSA.test( selector )) && + + // Support: IE 8 only + // Exclude object elements + (nodeType !== 1 || context.nodeName.toLowerCase() !== "object") ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && rdescend.test( selector ) ) { + + // Capture the context ID, setting it first if necessary + if ( (nid = context.getAttribute( "id" )) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", (nid = expando) ); + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[i] = "#" + nid + " " + toSelector( groups[i] ); + } + newSelector = groups.join( "," ); + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key + " " ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement("fieldset"); + + try { + return !!fn( el ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + var namespace = elem.namespaceURI, + docElem = (elem.ownerDocument || elem).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9-11, Edge + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + if ( preferredDoc !== document && + (subWindow = document.defaultView) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert(function( el ) { + el.className = "i"; + return !el.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function( el ) { + el.appendChild( document.createComment("") ); + return !el.getElementsByTagName("*").length; + }); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + }); + + // ID filter and find + if ( support.getById ) { + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode("id"); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( (elem = elems[i++]) ) { + node = elem.getAttributeNode("id"); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( el ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll("[msallowcapture^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push("~="); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push(".#.+[+~]"); + } + }); + + assert(function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement("input"); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll("[name=d]").length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll(":enabled").length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll(":disabled").length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( el ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + return -1; + } + if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + return a === document ? -1 : + b === document ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + if ( support.matchesSelector && documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch (e) { + nonnativeSelectorCache( expr, true ); + } + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return (sel + "").replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[6] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] ) { + match[2] = match[4] || match[5] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + // Use previously-cached element index if available + if ( useCache ) { + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + // Don't keep the element (issue #299) + input[0] = null; + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( (tokens = []) ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( (oldCache = uniqueCache[ key ]) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return (newCache[ 2 ] = oldCache[ 2 ]); + } else { + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + len = elems.length; + + if ( outermost ) { + outermostContext = context === document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + if ( !context && elem.ownerDocument !== document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context || document, xml) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( (selector = compiled.selector || selector) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert(function( el ) { + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement("fieldset") ) & 1; +}); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert(function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute("href") === "#" ; +}) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + }); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert(function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +}) ) { + addHandle( "value", function( elem, name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + }); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert(function( el ) { + return el.getAttribute("disabled") == null; +}) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + null; + } + }); +} + +return Sizzle; + +})( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +}; +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( typeof elem.contentDocument !== "undefined" ) { + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + master.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( master.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); + } + + return master.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + + +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (#9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + +var swap = function( elem, options, callback, args ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.apply( elem, args || [] ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // Support: IE <=9 only + option: [ 1, "" ], + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] +}; + +// Support: IE <=9 only +wrapMap.optgroup = wrapMap.option; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( toType( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; +} )(); + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = {}; + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + // Make a writable jQuery.Event from the native event object + var event = jQuery.event.fix( nativeEvent ); + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + return result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function( event ) { + var button = event.button; + + // Add which for key events + if ( event.which == null && rkeyEvent.test( event.type ) ) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { + if ( button & 1 ) { + return 1; + } + + if ( button & 2 ) { + return 3; + } + + if ( button & 4 ) { + return 2; + } + + return 0; + } + + return event.which; + } +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + delegateType: delegateType + }; +} ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + /* eslint-disable max-len */ + + // See https://github.com/eslint/eslint/issues/3229 + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi, + + /* eslint-enable */ + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.access( src ); + pdataCur = dataPriv.set( dest, pdataOld ); + events = pdataOld.events; + + if ( events ) { + delete pdataCur.handle; + pdataCur.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = concat.apply( [], args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + } ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html.replace( rxhtmlTag, "<$1>" ); + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +// Return a vendor-prefixed property or undefined +function vendorPropName( name ) { + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; + + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin + if ( box === "margin" ) { + delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { + + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + // Start with computed style + var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + // Support: IE 9-11 only + // Also use offsetWidth/offsetHeight for when box sizing is unreliable + // We use getClientRects() to check for hidden/disconnected. + // In those cases, the computed value can be trusted to be border-box + if ( ( !support.boxSizingReliable() && isBorderBox || + val === "auto" || + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue && type !== false ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( isValidValue ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = classesToArray( value ); + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +support.focusin = "onfocusin" in window; + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + var doc = this.ownerDocument || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = Date.now(); + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) { + xml = undefined; + } + + if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ) + .filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ) + .map( function( i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + + +jQuery._evalUrl = function( url, options ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options ); + } + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( "