diff --git a/.circleci/config.yml b/.circleci/config.yml
index 1f31df70a85..6d2821ddeb7 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,4 +1,8 @@
+# This config is equivalent to both the '.circleci/extended/orb-free.yml' and the base '.circleci/config.yml'
version: 2.1
+
+# Orbs are reusable packages of CircleCI configuration that you may share across projects, enabling you to create encapsulated, parameterized commands, jobs, and executors that can be used across multiple projects.
+# See: https://circleci.com/docs/2.0/orb-intro/
orbs:
browser-tools: circleci/browser-tools@1.2.2
@@ -190,7 +194,7 @@ jobs:
working_directory: ~/plotly.js
steps:
- browser-tools/install-browser-tools: &browser-versions
- firefox-version: '81.0'
+ firefox-version: "81.0"
install-chrome: false
install-chromedriver: false
- attach_workspace:
@@ -209,7 +213,7 @@ jobs:
working_directory: ~/plotly.js
steps:
- browser-tools/install-browser-tools: &browser-versions
- firefox-version: '82.0'
+ firefox-version: "82.0"
install-chrome: false
install-chromedriver: false
- attach_workspace:
@@ -347,8 +351,8 @@ jobs:
- run:
name: Install poppler-utils to have pdftops for exporting eps
command: |
- sudo apt-get update --allow-releaseinfo-change
- sudo apt-get install poppler-utils
+ sudo apt-get update --allow-releaseinfo-change
+ sudo apt-get install poppler-utils
- run:
name: Create svg, jpg, jpeg, webp, pdf and eps files
command: python3 test/image/make_exports.py
@@ -467,8 +471,8 @@ jobs:
command: npm run no-new-func
workflows:
- version: 2
- build-and-test:
+ sample: # This is the name of the workflow, feel free to change it to better match your workflow.
+ # Inside the workflow, you define the jobs you want to run.
jobs:
- install-and-cibuild-node12
diff --git a/delete.txt b/delete.txt
new file mode 100644
index 00000000000..f6c4fd068ea
--- /dev/null
+++ b/delete.txt
@@ -0,0 +1 @@
+delete me
\ No newline at end of file
diff --git a/devtools/test_dashboard/index - boxplot.html b/devtools/test_dashboard/index - boxplot.html
new file mode 100644
index 00000000000..dc9c10d469e
--- /dev/null
+++ b/devtools/test_dashboard/index - boxplot.html
@@ -0,0 +1,82 @@
+
+
+
+ Plotly.js Devtools
+
+
+
+ Plotly.js Devtools
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/devtools/test_dashboard/index copy 2.html b/devtools/test_dashboard/index copy 2.html
new file mode 100644
index 00000000000..e4596963f0a
--- /dev/null
+++ b/devtools/test_dashboard/index copy 2.html
@@ -0,0 +1,171 @@
+
+
+
+ Plotly.js Devtools
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/devtools/test_dashboard/index copy.html b/devtools/test_dashboard/index copy.html
new file mode 100644
index 00000000000..bbe0c94afa3
--- /dev/null
+++ b/devtools/test_dashboard/index copy.html
@@ -0,0 +1,163 @@
+
+
+
+ Plotly.js Devtools
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/devtools/test_dashboard/index.html b/devtools/test_dashboard/index.html
index 0c0c9e89760..13f8a3c48d3 100644
--- a/devtools/test_dashboard/index.html
+++ b/devtools/test_dashboard/index.html
@@ -1,27 +1,526 @@
-
- Plotly.js Devtools
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ Plotly.js Devtools
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
index e6dd94e1092..7ee763f4bd7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7886,9 +7886,9 @@
}
},
"node_modules/jasmine-core": {
- "version": "3.5.0",
- "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.5.0.tgz",
- "integrity": "sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA==",
+ "version": "3.99.1",
+ "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.99.1.tgz",
+ "integrity": "sha512-Hu1dmuoGcZ7AfyynN3LsfruwMbxMALMka+YtZeGoLuDEySVmVAPaonkNoBRIw/ectu8b9tVQCJNgp4a4knp+tg==",
"dev": true
},
"node_modules/jest-worker": {
@@ -14405,8 +14405,7 @@
"@mapbox/mapbox-gl-supported": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-1.5.0.tgz",
- "integrity": "sha512-/PT1P6DNf7vjEEiPkVIRJkvibbqWtqnyGaBz3nfRdcxclNSnSdaLU5tfAgcD7I8Yt5i+L19s406YLl1koLnLbg==",
- "requires": {}
+ "integrity": "sha512-/PT1P6DNf7vjEEiPkVIRJkvibbqWtqnyGaBz3nfRdcxclNSnSdaLU5tfAgcD7I8Yt5i+L19s406YLl1koLnLbg=="
},
"@mapbox/node-pre-gyp": {
"version": "1.0.8",
@@ -14992,8 +14991,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz",
"integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"acorn-globals": {
"version": "6.0.0",
@@ -15017,8 +15015,7 @@
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"acorn-walk": {
"version": "7.1.1",
@@ -19073,9 +19070,9 @@
}
},
"jasmine-core": {
- "version": "3.5.0",
- "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.5.0.tgz",
- "integrity": "sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA==",
+ "version": "3.99.1",
+ "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.99.1.tgz",
+ "integrity": "sha512-Hu1dmuoGcZ7AfyynN3LsfruwMbxMALMka+YtZeGoLuDEySVmVAPaonkNoBRIw/ectu8b9tVQCJNgp4a4knp+tg==",
"dev": true
},
"jest-worker": {
@@ -19333,8 +19330,7 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/karma-jasmine-spec-tags/-/karma-jasmine-spec-tags-1.3.0.tgz",
"integrity": "sha512-J1iAZtcEcK/pCkaYsU0VbNJae+3Awz/3p1dhWnKgvcB4FkbBK1TIOi4qswJ6HAmmcdJZgudffXpslcNQJZH9sw==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"karma-spec-reporter": {
"version": "0.0.36",
@@ -21761,8 +21757,7 @@
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "devOptional": true
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"source-map-js": {
"version": "1.0.2",
@@ -21960,11 +21955,6 @@
"fs-extra": "^10.0.0"
}
},
- "string_decoder": {
- "version": "0.10.31",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
- "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
- },
"string-split-by": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/string-split-by/-/string-split-by-1.0.0.tgz",
diff --git a/src/lib/sort_traces.js b/src/lib/sort_traces.js
new file mode 100644
index 00000000000..9e4985e84ae
--- /dev/null
+++ b/src/lib/sort_traces.js
@@ -0,0 +1,144 @@
+'use strict';
+
+function zipArrays(arrays) {
+ var zipped = [];
+ arrays[0].forEach(function(e, i) {
+ var row = [];
+ arrays.forEach(function(arr) {
+ row.push(arr[i]);
+ });
+ zipped.push(row);
+ });
+ return zipped;
+}
+
+function sortObjecstByKey(a, b, key) {
+ if(a[key] === b[key]) return 0;
+ if(a[key] < b[key]) return -1; + return 1; +} + +function matrixToObjectList(matrix, cols) { + var zipped = zipArrays(matrix); + + var objList = []; + + zipped.forEach(function(row) { + var objRow = {}; + cols.forEach(function(col, idx) { + objRow[col] = row[idx]; + }); + objRow.y = row[row.length - 1]; + objList.push(objRow); + }); + return objList; +} + +exports.matrixToObjectList = matrixToObjectList; + +function sortObjectList(cols, objList) { + var sortedObjectList = objList.map(function(e) { + return e; + }); + cols.slice().reverse().forEach(function(key) { + sortedObjectList = sortedObjectList.sort(function(a, b) { + return sortObjecstByKey(a, b, key); + }); + }); + return sortedObjectList; +} + +exports.sortObjectList = sortObjectList; + +function objectListToList(objectList) { + var list = []; + objectList.forEach(function(item) { + list.push(Object.values(item)); + }); + return list; +} + +exports.objectListToList = objectListToList; + +function sortedMatrix(list, removeNull) { + var xs = []; + var y = []; + + list.slice().forEach(function(item) { + var val = item.pop(); + + if(removeNull & item.includes(null)) { + return; + } + + y.push(val); + xs.push(item); + }); + + return [xs, y]; +} + +exports.sortedMatrix = sortedMatrix; + +function squareMatrix(matrix) { + var width = matrix[0].length; + var height = matrix.length; + + if(width === height) { + return matrix; + } + + var newMatrix = []; + + if(width> height) {
+ for(var rw = 0; rw < height; rw++) { + newMatrix.push(matrix[rw].slice()); + } + for(var i = height; i < width; i++) { + newMatrix.push(Array(width)); + } + } else { + for(var row = 0; row < height; row++) { + var rowExpansion = Array(height - width); + var rowSlice = matrix[row].slice(); + Array.prototype.push.apply(rowSlice, rowExpansion); + newMatrix.push(rowSlice); + } + } + return newMatrix; +} + +exports.squareMatrix = squareMatrix; + +function transpose(matrix) { + var height = matrix.length; + var width = matrix[0].length; + + var squaredMatrix = squareMatrix(matrix); + + var newMatrix = []; + + // prevent inplace change and mantain the main diagonal + for(var rw = 0; rw < squaredMatrix.length; rw++) { + newMatrix.push(squaredMatrix[rw].slice()); + } + + for(var i = 0; i < newMatrix.length; i++) { + for(var j = 0; j < i; j++) { + newMatrix = newMatrix.slice(); + var temp = newMatrix[i][j]; + newMatrix[i][j] = newMatrix[j][i]; + newMatrix[j][i] = temp; + } + } + if(width> height) {
+ for(var row = 0; row < newMatrix.length; row++) { + newMatrix[row] = newMatrix[row].slice(0, height); + } + } else { + newMatrix = newMatrix.slice(0, width); + } + return newMatrix; +} + +exports.transpose = transpose; diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index dfdb0e5166d..1506cfa3cff 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -1,21 +1,21 @@ -'use strict'; +"use strict"; -var d3 = require('@plotly/d3'); -var isNumeric = require('fast-isnumeric'); -var Plots = require('../../plots/plots'); +var d3 = require("@plotly/d3"); +var isNumeric = require("fast-isnumeric"); +var Plots = require("../../plots/plots"); -var Registry = require('../../registry'); -var Lib = require('../../lib'); +var Registry = require("../../registry"); +var Lib = require("../../lib"); var strTranslate = Lib.strTranslate; -var svgTextUtils = require('../../lib/svg_text_utils'); -var Titles = require('../../components/titles'); -var Color = require('../../components/color'); -var Drawing = require('../../components/drawing'); +var svgTextUtils = require("../../lib/svg_text_utils"); +var Titles = require("../../components/titles"); +var Color = require("../../components/color"); +var Drawing = require("../../components/drawing"); -var axAttrs = require('./layout_attributes'); -var cleanTicks = require('./clean_ticks'); +var axAttrs = require("./layout_attributes"); +var cleanTicks = require("./clean_ticks"); -var constants = require('../../constants/numerical'); +var constants = require("../../constants/numerical"); var ONEMAXYEAR = constants.ONEMAXYEAR; var ONEAVGYEAR = constants.ONEAVGYEAR; var ONEMINYEAR = constants.ONEMINYEAR; @@ -34,21 +34,21 @@ var ONESEC = constants.ONESEC; var MINUS_SIGN = constants.MINUS_SIGN; var BADNUM = constants.BADNUM; -var ZERO_PATH = { K: 'zeroline' }; -var GRID_PATH = { K: 'gridline', L: 'path' }; -var MINORGRID_PATH = { K: 'minor-gridline', L: 'path' }; -var TICK_PATH = { K: 'tick', L: 'path' }; -var TICK_TEXT = { K: 'tick', L: 'text' }; +var ZERO_PATH = { K: "zeroline" }; +var GRID_PATH = { K: "gridline", L: "path" }; +var MINORGRID_PATH = { K: "minor-gridline", L: "path" }; +var TICK_PATH = { K: "tick", L: "path" }; +var TICK_TEXT = { K: "tick", L: "text" }; var MARGIN_MAPPING = { - width: ['x', 'r', 'l', 'xl', 'xr'], - height: ['y', 't', 'b', 'yt', 'yb'], - right: ['r', 'xr'], - left: ['l', 'xl'], - top: ['t', 'yt'], - bottom: ['b', 'yb'] + width: ["x", "r", "l", "xl", "xr"], + height: ["y", "t", "b", "yt", "yb"], + right: ["r", "xr"], + left: ["l", "xl"], + top: ["t", "yt"], + bottom: ["b", "yb"], }; -var alignmentConstants = require('../../constants/alignment'); +var alignmentConstants = require("../../constants/alignment"); var MID_SHIFT = alignmentConstants.MID_SHIFT; var CAP_SHIFT = alignmentConstants.CAP_SHIFT; var LINE_SPACING = alignmentConstants.LINE_SPACING; @@ -56,12 +56,12 @@ var OPPOSITE_SIDE = alignmentConstants.OPPOSITE_SIDE; var TEXTPAD = 3; -var axes = module.exports = {}; +var axes = (module.exports = {}); -axes.setConvert = require('./set_convert'); -var autoType = require('./axis_autotype'); +axes.setConvert = require("./set_convert"); +var autoType = require("./axis_autotype"); -var axisIds = require('./axis_ids'); +var axisIds = require("./axis_ids"); var idSort = axisIds.idSort; var isLinked = axisIds.isLinked; @@ -74,17 +74,14 @@ axes.listIds = axisIds.listIds; axes.getFromId = axisIds.getFromId; axes.getFromTrace = axisIds.getFromTrace; -var autorange = require('./autorange'); +var autorange = require("./autorange"); axes.getAutoRange = autorange.getAutoRange; axes.findExtremes = autorange.findExtremes; var epsilon = 0.0001; function expandRange(range) { - var delta = (range[1] - range[0]) * epsilon; - return [ - range[0] - delta, - range[1] + delta - ]; + var delta = (range[1] - range[0]) * epsilon; + return [range[0] - delta, range[1] + delta]; } /* @@ -98,28 +95,46 @@ function expandRange(range) { * 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] || (typeof extraOption === 'string' ? extraOption : extraOption[0]); - if(!extraOption) extraOption = dflt; - axlist = axlist.concat(axlist.map(function(x) { return x + ' domain'; })); - - // data-ref annotations are not supported in gl2d yet - - attrDef[refAttr] = { - valType: 'enumerated', - values: axlist.concat(extraOption ? - (typeof extraOption === 'string' ? [extraOption] : extraOption) : - []), - dflt: dflt - }; - - // xref, yref - return Lib.coerce(containerIn, containerOut, attrDef, refAttr); +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] || + (typeof extraOption === "string" ? extraOption : extraOption[0]); + if (!extraOption) extraOption = dflt; + axlist = axlist.concat( + axlist.map(function (x) { + return x + " domain"; + }) + ); + + // data-ref annotations are not supported in gl2d yet + + attrDef[refAttr] = { + valType: "enumerated", + values: axlist.concat( + extraOption + ? typeof extraOption === "string" + ? [extraOption] + : extraOption + : [] + ), + dflt: dflt, + }; + + // xref, yref + return Lib.coerce(containerIn, containerOut, attrDef, refAttr); }; /* @@ -130,11 +145,21 @@ axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption * ar: the axis reference string * */ -axes.getRefType = function(ar) { - if(ar === undefined) { return ar; } - if(ar === 'paper') { return 'paper'; } - if(ar === 'pixel') { return 'pixel'; } - if(/( domain)$/.test(ar)) { return 'domain'; } else { return 'range'; } +axes.getRefType = function (ar) { + if (ar === undefined) { + return ar; + } + if (ar === "paper") { + return "paper"; + } + if (ar === "pixel") { + return "pixel"; + } + if (/( domain)$/.test(ar)) { + return "domain"; + } else { + return "range"; + } }; /* @@ -159,397 +184,425 @@ axes.getRefType = function(ar) { * - 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; - var axRefType = axes.getRefType(axRef); - if(axRefType !== 'range') { - 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.coercePosition = function (containerOut, gd, coerce, axRef, attr, dflt) { + var cleanPos, pos; + var axRefType = axes.getRefType(axRef); + if (axRefType !== "range") { + 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; +axes.cleanPosition = function (pos, gd, axRef) { + var cleanPos = + axRef === "paper" || axRef === "pixel" + ? Lib.ensureNumber + : axes.getFromId(gd, axRef).cleanPos; - return cleanPos(pos); + return cleanPos(pos); }; -axes.redrawComponents = function(gd, axIds) { - axIds = axIds ? axIds : axes.listIds(gd); +axes.redrawComponents = function (gd, axIds) { + axIds = axIds ? axIds : axes.listIds(gd); - var fullLayout = gd._fullLayout; + var fullLayout = gd._fullLayout; - function _redrawOneComp(moduleName, methodName, stashName, shortCircuit) { - var method = Registry.getComponentMethod(moduleName, methodName); - var stash = {}; + 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 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]; + 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; - } - } + 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); - _redrawOneComp('selections', 'drawOne', '_selectionIndices'); + // 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); + _redrawOneComp("selections", "drawOne", "_selectionIndices"); }; -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, undefined, { - autotypenumbers: gd._fullLayout.autotypenumbers - }), - _categories: [] - }; - axes.setConvert(ax); +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, undefined, { + autotypenumbers: gd._fullLayout.autotypenumbers, + }), + _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); + // 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};
+ // 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};
+ // 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
+ // otherwise (e.g. numeric-array of 'marker.color' or 'marker.size')
+ // -> cast to Number
- return {d2c: toNum, c2d: toNum};
-};
+ return { d2c: toNum, c2d: toNum };
+});
-function toNum(v) { return +v; }
-function toString(v) { return String(v); }
+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;
+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';
+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;
- }
+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._rangeInitial0 === undefined && - ax._rangeInitial1 === undefined; - - var hasChanged = isNew || ( - ax.range[0] !== ax._rangeInitial0 || - ax.range[1] !== ax._rangeInitial1 - ); - - var autorange = ax.autorange; - if((isNew && autorange !== true) || (overwrite && hasChanged)) { - ax._rangeInitial0 = (autorange === 'min' || autorange === 'max reversed') ? undefined : ax.range[0]; - ax._rangeInitial1 = (autorange === 'max' || autorange === 'min reversed') ? undefined : ax.range[1]; - ax._autorangeInitial = autorange; - hasOneAxisChanged = true; - } - } - - return hasOneAxisChanged; +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._rangeInitial0 === undefined && ax._rangeInitial1 === undefined; + + var hasChanged = + isNew || + ax.range[0] !== ax._rangeInitial0 || + ax.range[1] !== ax._rangeInitial1; + + var autorange = ax.autorange; + if ((isNew && autorange !== true) || (overwrite && hasChanged)) { + ax._rangeInitial0 = + autorange === "min" || autorange === "max reversed" + ? undefined + : ax.range[0]; + ax._rangeInitial1 = + autorange === "max" || autorange === "min reversed" + ? undefined + : ax.range[1]; + ax._autorangeInitial = autorange; + 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, - }; - } +axes.saveShowSpikeInitial = function (gd, overwrite) { + var axList = axes.list(gd, "", true); + var hasOneAxisChanged = false; + var allSpikesEnabled = "on"; - if(!calendar) calendar = ax.calendar; + for (var i = 0; i < axList.length; i++) { + var ax = axList[i]; + var isNew = ax._showSpikeInitial === undefined; + var hasChanged = isNew || !(ax.showspikes === ax._showspikes); - // 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 - }; + if (isNew || (overwrite && hasChanged)) { + ax._showSpikeInitial = ax.showspikes; + hasOneAxisChanged = true; } - 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); + if (allSpikesEnabled === "on" && !ax.showspikes) { + allSpikesEnabled = "off"; } + } + gd._fullLayout._cartesianSpikesEnabled = allSpikesEnabled; + return hasOneAxisChanged; +}; - var finalSize = dummyAx.dtick; - var binStart = axes.tickIncrement( - axes.tickFirst(dummyAx), finalSize, 'reverse', calendar); - var binEnd, bincount; +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); - // 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);
+ 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,
+ };
+ }
- 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);
- }
+ if (!calendar) calendar = ax.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 + // 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; + 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 -= HALFDAY;
- }
- var nextBinStart = axes.tickIncrement(binStart, dtick);
-
- if(nextBinStart <= dataMin) return nextBinStart; + 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 -= HALFDAY;
}
- return binStart;
+ var nextBinStart = axes.tickIncrement(binStart, dtick);
+
+ if (nextBinStart <= dataMin) return nextBinStart; + } + return binStart; } // ---------------------------------------------------- @@ -557,346 +610,352 @@ function autoShiftMonthBins(binStart, data, dtick, dataMin, calendar) { // ---------------------------------------------------- // ensure we have minor tick0 and dtick calculated -axes.prepMinorTicks = function(mockAx, ax, opts) { - if(!ax.minor.dtick) { - delete mockAx.dtick; - var hasMajor = ax.dtick && isNumeric(ax._tmin); - var mockMinorRange; - if(hasMajor) { - var tick2 = axes.tickIncrement(ax._tmin, ax.dtick, true); - // mock range a tiny bit smaller than one major tick interval - mockMinorRange = [ax._tmin, tick2 * 0.99 + ax._tmin * 0.01]; +axes.prepMinorTicks = function (mockAx, ax, opts) { + if (!ax.minor.dtick) { + delete mockAx.dtick; + var hasMajor = ax.dtick && isNumeric(ax._tmin); + var mockMinorRange; + if (hasMajor) { + var tick2 = axes.tickIncrement(ax._tmin, ax.dtick, true); + // mock range a tiny bit smaller than one major tick interval + mockMinorRange = [ax._tmin, tick2 * 0.99 + ax._tmin * 0.01]; + } else { + var rl = Lib.simpleMap(ax.range, ax.r2l); + // If we don't have a major dtick, the concept of minor ticks is a little + // ambiguous - just take a stab and say minor.nticks should span 1/5 the axis + mockMinorRange = [rl[0], 0.8 * rl[0] + 0.2 * rl[1]]; + } + mockAx.range = Lib.simpleMap(mockMinorRange, ax.l2r); + mockAx._isMinor = true; + + axes.prepTicks(mockAx, opts); + + if (hasMajor) { + var numericMajor = isNumeric(ax.dtick); + var numericMinor = isNumeric(mockAx.dtick); + var majorNum = numericMajor ? ax.dtick : +ax.dtick.substring(1); + var minorNum = numericMinor ? mockAx.dtick : +mockAx.dtick.substring(1); + if (numericMajor && numericMinor) { + if (!isMultiple(majorNum, minorNum)) { + // give up on minor ticks - outside the below exceptions, + // this can only happen if minor.nticks is smaller than two jumps + // in the auto-tick scale and the first jump is not an even multiple + // (5 -> 2 or for dates 3 ->2, 15 -> 10 etc) or if you provided
+ // an explicit dtick, in which case it's fine to give up,
+ // you can provide an explicit minor.dtick.
+ if (majorNum === 2 * ONEWEEK && minorNum === 3 * ONEDAY) {
+ mockAx.dtick = ONEWEEK;
+ } else if (majorNum === ONEWEEK && !(ax._input.minor || {}).nticks) {
+ // minor.nticks defaults to 5, but in this one case we want 7,
+ // so the minor ticks show on all days of the week
+ mockAx.dtick = ONEDAY;
+ } else if (isClose(majorNum / minorNum, 2.5)) {
+ // 5*10^n -> 2*10^n and you've set nticks < 5 + // quarters are pretty common, we don't do this by default as it + // would add an extra digit to display, but minor has no labels + mockAx.dtick = majorNum / 2; + } else { + mockAx.dtick = majorNum; + } + } else if (majorNum === 2 * ONEWEEK && minorNum === 2 * ONEDAY) { + // this is a weird one: we don't want to automatically choose + // 2-day minor ticks for 2-week major, even though it IS an even multiple, + // because people would expect to see the weeks clearly + mockAx.dtick = ONEWEEK; + } + } else if (String(ax.dtick).charAt(0) === "M") { + if (numericMinor) { + mockAx.dtick = "M1"; } else { - var rl = Lib.simpleMap(ax.range, ax.r2l); - // If we don't have a major dtick, the concept of minor ticks is a little - // ambiguous - just take a stab and say minor.nticks should span 1/5 the axis - mockMinorRange = [rl[0], 0.8 * rl[0] + 0.2 * rl[1]]; + if (!isMultiple(majorNum, minorNum)) { + // unless you provided an explicit ax.dtick (in which case + // it's OK for us to give up, you can provide an explicit + // minor.dtick too), this can only happen with: + // minor.nticks < 3 and dtick === M3, or + // minor.nticks < 5 and dtick === 5 * 10^n years + // so in all cases we just give up. + mockAx.dtick = ax.dtick; + } else if (majorNum>= 12 && minorNum === 2) {
+ // another special carve-out: for year major ticks, don't show
+ // 2-month minor ticks, bump to quarters
+ mockAx.dtick = "M3";
+ }
}
- mockAx.range = Lib.simpleMap(mockMinorRange, ax.l2r);
- mockAx._isMinor = true;
-
- axes.prepTicks(mockAx, opts);
-
- if(hasMajor) {
- var numericMajor = isNumeric(ax.dtick);
- var numericMinor = isNumeric(mockAx.dtick);
- var majorNum = numericMajor ? ax.dtick : +ax.dtick.substring(1);
- var minorNum = numericMinor ? mockAx.dtick : +mockAx.dtick.substring(1);
- if(numericMajor && numericMinor) {
- if(!isMultiple(majorNum, minorNum)) {
- // give up on minor ticks - outside the below exceptions,
- // this can only happen if minor.nticks is smaller than two jumps
- // in the auto-tick scale and the first jump is not an even multiple
- // (5 -> 2 or for dates 3 ->2, 15 -> 10 etc) or if you provided
- // an explicit dtick, in which case it's fine to give up,
- // you can provide an explicit minor.dtick.
- if((majorNum === 2 * ONEWEEK) && (minorNum === 3 * ONEDAY)) {
- mockAx.dtick = ONEWEEK;
- } else if(majorNum === ONEWEEK && !(ax._input.minor || {}).nticks) {
- // minor.nticks defaults to 5, but in this one case we want 7,
- // so the minor ticks show on all days of the week
- mockAx.dtick = ONEDAY;
- } else if(isClose(majorNum / minorNum, 2.5)) {
- // 5*10^n -> 2*10^n and you've set nticks < 5 - // quarters are pretty common, we don't do this by default as it - // would add an extra digit to display, but minor has no labels - mockAx.dtick = majorNum / 2; - } else { - mockAx.dtick = majorNum; - } - } else if(majorNum === 2 * ONEWEEK && minorNum === 2 * ONEDAY) { - // this is a weird one: we don't want to automatically choose - // 2-day minor ticks for 2-week major, even though it IS an even multiple, - // because people would expect to see the weeks clearly - mockAx.dtick = ONEWEEK; - } - } else if(String(ax.dtick).charAt(0) === 'M') { - if(numericMinor) { - mockAx.dtick = 'M1'; - } else { - if(!isMultiple(majorNum, minorNum)) { - // unless you provided an explicit ax.dtick (in which case - // it's OK for us to give up, you can provide an explicit - // minor.dtick too), this can only happen with: - // minor.nticks < 3 and dtick === M3, or - // minor.nticks < 5 and dtick === 5 * 10^n years - // so in all cases we just give up. - mockAx.dtick = ax.dtick; - } else if((majorNum>= 12) && (minorNum === 2)) {
- // another special carve-out: for year major ticks, don't show
- // 2-month minor ticks, bump to quarters
- mockAx.dtick = 'M3';
- }
- }
- } else if(String(mockAx.dtick).charAt(0) === 'L') {
- if(String(ax.dtick).charAt(0) === 'L') {
- if(!isMultiple(majorNum, minorNum)) {
- mockAx.dtick = isClose(majorNum / minorNum, 2.5) ? (ax.dtick / 2) : ax.dtick;
- }
- } else {
- mockAx.dtick = 'D1';
- }
- } else if(mockAx.dtick === 'D2' && +ax.dtick> 1) {
- // the D2 log axis tick spacing is confusing for unlabeled minor ticks if
- // the major dtick is more than one order of magnitude.
- mockAx.dtick = 1;
- }
+ } else if (String(mockAx.dtick).charAt(0) === "L") {
+ if (String(ax.dtick).charAt(0) === "L") {
+ if (!isMultiple(majorNum, minorNum)) {
+ mockAx.dtick = isClose(majorNum / minorNum, 2.5)
+ ? ax.dtick / 2
+ : ax.dtick;
+ }
+ } else {
+ mockAx.dtick = "D1";
}
- // put back the original range, to use to find the full set of minor ticks
- mockAx.range = ax.range;
- }
- if(ax.minor._tick0Init === undefined) {
- // ensure identical tick0
- mockAx.tick0 = ax.tick0;
- }
+ } else if (mockAx.dtick === "D2" && +ax.dtick> 1) {
+ // the D2 log axis tick spacing is confusing for unlabeled minor ticks if
+ // the major dtick is more than one order of magnitude.
+ mockAx.dtick = 1;
+ }
+ }
+ // put back the original range, to use to find the full set of minor ticks
+ mockAx.range = ax.range;
+ }
+ if (ax.minor._tick0Init === undefined) {
+ // ensure identical tick0
+ mockAx.tick0 = ax.tick0;
+ }
};
function isMultiple(bigger, smaller) {
- return Math.abs((bigger / smaller + 0.5) % 1 - 0.5) < 0.001; + return Math.abs(((bigger / smaller + 0.5) % 1) - 0.5) < 0.001; } function isClose(a, b) { - return Math.abs((a / b) - 1) < 0.001; + return Math.abs(a / b - 1) < 0.001; } // ensure we have tick0, dtick, and tick rounding calculated -axes.prepTicks = function(ax, opts) { - var rng = Lib.simpleMap(ax.range, ax.r2l, undefined, undefined, opts); - - // 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 ? Lib.bigFont(ax.tickfont.size || 12) : 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; - } +axes.prepTicks = function (ax, opts) { + var rng = Lib.simpleMap(ax.range, ax.r2l, undefined, undefined, opts); - if(!(ax.minor && ax.minor.tickmode !== 'array')) { - // add a couple of extra digits for filling in ticks when we - // have explicit tickvals without tick text - if(ax.tickmode === 'array') nt *= 100; - } + // calculate max number of (auto) ticks to display based on plot size + if (ax.tickmode === "auto" || !ax.dtick) { + var nt = ax.nticks; + var minPx; - ax._roughDTick = Math.abs(rng[1] - rng[0]) / nt; - axes.autoTicks(ax, ax._roughDTick); + if (!nt) { + if (ax.type === "category" || ax.type === "multicategory") { + minPx = ax.tickfont ? Lib.bigFont(ax.tickfont.size || 12) : 15; + nt = ax._length / minPx; + } else { + minPx = ax._id.charAt(0) === "y" ? 40 : 80; + nt = Lib.constrain(ax._length / minPx, 4, 9) + 1; + } - // 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); - } + // radial axes span half their domain, + // multiply nticks value by two to get correct number of auto ticks. + if (ax._name === "radialaxis") nt *= 2; } - if(ax.ticklabelmode === 'period') { - adjustPeriodDelta(ax); + if (!(ax.minor && ax.minor.tickmode !== "array")) { + // add a couple of extra digits for filling in ticks when we + // have explicit tickvals without tick text + if (ax.tickmode === "array") nt *= 100; } - // check for missing tick0 - if(!ax.tick0) { - ax.tick0 = (ax.type === 'date') ? '2000-01-01' : 0; + ax._roughDTick = Math.abs(rng[1] - rng[0]) / nt; + axes.autoTicks(ax, ax._roughDTick); + + // 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); } + } + + if (ax.ticklabelmode === "period") { + adjustPeriodDelta(ax); + } + + // 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; + // 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); + // now figure out rounding of tick values + autoTickRound(ax); }; function nMonths(dtick) { - return +(dtick.substring(1)); + return +dtick.substring(1); } -function adjustPeriodDelta(ax) { // adjusts ax.dtick and sets ax._definedDelta - var definedDelta; - - function mDate() { - return !( - isNumeric(ax.dtick) || - ax.dtick.charAt(0) !== 'M' - ); - } - var isMDate = mDate(); - var tickformat = axes.getTickFormat(ax); - if(tickformat) { - var noDtick = ax._dtickInit !== ax.dtick; - if( - !(/%[fLQsSMX]/.test(tickformat)) - // %f: microseconds as a decimal number [000000, 999999] - // %L: milliseconds as a decimal number [000, 999] - // %Q: milliseconds since UNIX epoch - // %s: seconds since UNIX epoch - // %S: second as a decimal number [00,61] - // %M: minute as a decimal number [00,59] - // %X: the locale’s time, such as %-I:%M:%S %p - ) { - if( - /%[HI]/.test(tickformat) - // %H: hour (24-hour clock) as a decimal number [00,23] - // %I: hour (12-hour clock) as a decimal number [01,12] - ) { - definedDelta = ONEHOUR; - if(noDtick && !isMDate && ax.dtick < ONEHOUR) ax.dtick = ONEHOUR; - } else if( - /%p/.test(tickformat) // %p: either AM or PM - ) { - definedDelta = HALFDAY; - if(noDtick && !isMDate && ax.dtick < HALFDAY) ax.dtick = HALFDAY; - } else if( - /%[Aadejuwx]/.test(tickformat) - // %A: full weekday name - // %a: abbreviated weekday name - // %d: zero-padded day of the month as a decimal number [01,31] - // %e: space-padded day of the month as a decimal number [ 1,31] - // %j: day of the year as a decimal number [001,366] - // %u: Monday-based (ISO 8601) weekday as a decimal number [1,7] - // %w: Sunday-based weekday as a decimal number [0,6] - // %x: the locale’s date, such as %-m/%-d/%Y - ) { - definedDelta = ONEDAY; - if(noDtick && !isMDate && ax.dtick < ONEDAY) ax.dtick = ONEDAY; - } else if( - /%[UVW]/.test(tickformat) - // %U: Sunday-based week of the year as a decimal number [00,53] - // %V: ISO 8601 week of the year as a decimal number [01, 53] - // %W: Monday-based week of the year as a decimal number [00,53] - ) { - definedDelta = ONEWEEK; - if(noDtick && !isMDate && ax.dtick < ONEWEEK) ax.dtick = ONEWEEK; - } else if( - /%[Bbm]/.test(tickformat) - // %B: full month name - // %b: abbreviated month name - // %m: month as a decimal number [01,12] - ) { - definedDelta = ONEAVGMONTH; - if(noDtick && ( - isMDate ? nMonths(ax.dtick) < 1 : ax.dtick < ONEMINMONTH) - ) ax.dtick = 'M1'; - } else if( - /%[q]/.test(tickformat) - // %q: quarter of the year as a decimal number [1,4] - ) { - definedDelta = ONEAVGQUARTER; - if(noDtick && ( - isMDate ? nMonths(ax.dtick) < 3 : ax.dtick < ONEMINQUARTER) - ) ax.dtick = 'M3'; - } else if( - /%[Yy]/.test(tickformat) - // %Y: year with century as a decimal number, such as 1999 - // %y: year without century as a decimal number [00,99] - ) { - definedDelta = ONEAVGYEAR; - if(noDtick && ( - isMDate ? nMonths(ax.dtick) < 12 : ax.dtick < ONEMINYEAR) - ) ax.dtick = 'M12'; - } - } +function adjustPeriodDelta(ax) { + // adjusts ax.dtick and sets ax._definedDelta + var definedDelta; + + function mDate() { + return !(isNumeric(ax.dtick) || ax.dtick.charAt(0) !== "M"); + } + var isMDate = mDate(); + var tickformat = axes.getTickFormat(ax); + if (tickformat) { + var noDtick = ax._dtickInit !== ax.dtick; + if ( + !/%[fLQsSMX]/.test(tickformat) + // %f: microseconds as a decimal number [000000, 999999] + // %L: milliseconds as a decimal number [000, 999] + // %Q: milliseconds since UNIX epoch + // %s: seconds since UNIX epoch + // %S: second as a decimal number [00,61] + // %M: minute as a decimal number [00,59] + // %X: the locale’s time, such as %-I:%M:%S %p + ) { + if ( + /%[HI]/.test(tickformat) + // %H: hour (24-hour clock) as a decimal number [00,23] + // %I: hour (12-hour clock) as a decimal number [01,12] + ) { + definedDelta = ONEHOUR; + if (noDtick && !isMDate && ax.dtick < ONEHOUR) ax.dtick = ONEHOUR; + } else if ( + /%p/.test(tickformat) // %p: either AM or PM + ) { + definedDelta = HALFDAY; + if (noDtick && !isMDate && ax.dtick < HALFDAY) ax.dtick = HALFDAY; + } else if ( + /%[Aadejuwx]/.test(tickformat) + // %A: full weekday name + // %a: abbreviated weekday name + // %d: zero-padded day of the month as a decimal number [01,31] + // %e: space-padded day of the month as a decimal number [ 1,31] + // %j: day of the year as a decimal number [001,366] + // %u: Monday-based (ISO 8601) weekday as a decimal number [1,7] + // %w: Sunday-based weekday as a decimal number [0,6] + // %x: the locale’s date, such as %-m/%-d/%Y + ) { + definedDelta = ONEDAY; + if (noDtick && !isMDate && ax.dtick < ONEDAY) ax.dtick = ONEDAY; + } else if ( + /%[UVW]/.test(tickformat) + // %U: Sunday-based week of the year as a decimal number [00,53] + // %V: ISO 8601 week of the year as a decimal number [01, 53] + // %W: Monday-based week of the year as a decimal number [00,53] + ) { + definedDelta = ONEWEEK; + if (noDtick && !isMDate && ax.dtick < ONEWEEK) ax.dtick = ONEWEEK; + } else if ( + /%[Bbm]/.test(tickformat) + // %B: full month name + // %b: abbreviated month name + // %m: month as a decimal number [01,12] + ) { + definedDelta = ONEAVGMONTH; + if ( + noDtick && + (isMDate ? nMonths(ax.dtick) < 1 : ax.dtick < ONEMINMONTH) + ) + ax.dtick = "M1"; + } else if ( + /%[q]/.test(tickformat) + // %q: quarter of the year as a decimal number [1,4] + ) { + definedDelta = ONEAVGQUARTER; + if ( + noDtick && + (isMDate ? nMonths(ax.dtick) < 3 : ax.dtick < ONEMINQUARTER) + ) + ax.dtick = "M3"; + } else if ( + /%[Yy]/.test(tickformat) + // %Y: year with century as a decimal number, such as 1999 + // %y: year without century as a decimal number [00,99] + ) { + definedDelta = ONEAVGYEAR; + if ( + noDtick && + (isMDate ? nMonths(ax.dtick) < 12 : ax.dtick < ONEMINYEAR) + ) + ax.dtick = "M12"; + } } + } - isMDate = mDate(); - if(isMDate && ax.tick0 === ax._dowTick0) { - // discard Sunday/Monday tweaks - ax.tick0 = ax._rawTick0; - } + isMDate = mDate(); + if (isMDate && ax.tick0 === ax._dowTick0) { + // discard Sunday/Monday tweaks + ax.tick0 = ax._rawTick0; + } - ax._definedDelta = definedDelta; + ax._definedDelta = definedDelta; } function positionPeriodTicks(tickVals, ax, definedDelta) { - for(var i = 0; i < tickVals.length; i++) { - var v = tickVals[i].value; - - var a = i; - var b = i + 1; - if(i < tickVals.length - 1) { - a = i; - b = i + 1; - } else if(i> 0) {
- a = i - 1;
- b = i;
- } else {
- a = i;
- b = i;
- }
-
- var A = tickVals[a].value;
- var B = tickVals[b].value;
- var actualDelta = Math.abs(B - A);
- var delta = definedDelta || actualDelta;
- var periodLength = 0;
-
- if(delta>= ONEMINYEAR) {
- if(actualDelta>= ONEMINYEAR && actualDelta <= ONEMAXYEAR) { - periodLength = actualDelta; - } else { - periodLength = ONEAVGYEAR; - } - } else if(definedDelta === ONEAVGQUARTER && delta>= ONEMINQUARTER) {
- if(actualDelta>= ONEMINQUARTER && actualDelta <= ONEMAXQUARTER) { - periodLength = actualDelta; - } else { - periodLength = ONEAVGQUARTER; - } - } else if(delta>= ONEMINMONTH) {
- if(actualDelta>= ONEMINMONTH && actualDelta <= ONEMAXMONTH) { - periodLength = actualDelta; - } else { - periodLength = ONEAVGMONTH; - } - } else if(definedDelta === ONEWEEK && delta>= ONEWEEK) {
- periodLength = ONEWEEK;
- } else if(delta>= ONEDAY) {
- periodLength = ONEDAY;
- } else if(definedDelta === HALFDAY && delta>= HALFDAY) {
- periodLength = HALFDAY;
- } else if(definedDelta === ONEHOUR && delta>= ONEHOUR) {
- periodLength = ONEHOUR;
- }
-
- var inBetween;
- if(periodLength>= actualDelta) {
- // ensure new label positions remain between ticks
- periodLength = actualDelta;
- inBetween = true;
- }
-
- var endPeriod = v + periodLength;
- if(ax.rangebreaks && periodLength> 0) {
- var nAll = 84; // highly divisible 7 * 12
- var n = 0;
- for(var c = 0; c < nAll; c++) { - var r = (c + 0.5) / nAll; - if(ax.maskBreaks(v * (1 - r) + r * endPeriod) !== BADNUM) n++; - } - periodLength *= n / nAll; - - if(!periodLength) { - tickVals[i].drop = true; - } - - if(inBetween && actualDelta> ONEWEEK) periodLength = actualDelta; // center monthly & longer periods
- }
-
- if(
- periodLength> 0 || // not instant
- i === 0 // taking care first tick added
- ) {
- tickVals[i].periodX = v + periodLength / 2;
- }
+ for (var i = 0; i < tickVals.length; i++) { + var v = tickVals[i].value; + + var a = i; + var b = i + 1; + if (i < tickVals.length - 1) { + a = i; + b = i + 1; + } else if (i> 0) {
+ a = i - 1;
+ b = i;
+ } else {
+ a = i;
+ b = i;
+ }
+
+ var A = tickVals[a].value;
+ var B = tickVals[b].value;
+ var actualDelta = Math.abs(B - A);
+ var delta = definedDelta || actualDelta;
+ var periodLength = 0;
+
+ if (delta>= ONEMINYEAR) {
+ if (actualDelta>= ONEMINYEAR && actualDelta <= ONEMAXYEAR) { + periodLength = actualDelta; + } else { + periodLength = ONEAVGYEAR; + } + } else if (definedDelta === ONEAVGQUARTER && delta>= ONEMINQUARTER) {
+ if (actualDelta>= ONEMINQUARTER && actualDelta <= ONEMAXQUARTER) { + periodLength = actualDelta; + } else { + periodLength = ONEAVGQUARTER; + } + } else if (delta>= ONEMINMONTH) {
+ if (actualDelta>= ONEMINMONTH && actualDelta <= ONEMAXMONTH) { + periodLength = actualDelta; + } else { + periodLength = ONEAVGMONTH; + } + } else if (definedDelta === ONEWEEK && delta>= ONEWEEK) {
+ periodLength = ONEWEEK;
+ } else if (delta>= ONEDAY) {
+ periodLength = ONEDAY;
+ } else if (definedDelta === HALFDAY && delta>= HALFDAY) {
+ periodLength = HALFDAY;
+ } else if (definedDelta === ONEHOUR && delta>= ONEHOUR) {
+ periodLength = ONEHOUR;
+ }
+
+ var inBetween;
+ if (periodLength>= actualDelta) {
+ // ensure new label positions remain between ticks
+ periodLength = actualDelta;
+ inBetween = true;
+ }
+
+ var endPeriod = v + periodLength;
+ if (ax.rangebreaks && periodLength> 0) {
+ var nAll = 84; // highly divisible 7 * 12
+ var n = 0;
+ for (var c = 0; c < nAll; c++) { + var r = (c + 0.5) / nAll; + if (ax.maskBreaks(v * (1 - r) + r * endPeriod) !== BADNUM) n++; + } + periodLength *= n / nAll; + + if (!periodLength) { + tickVals[i].drop = true; + } + + if (inBetween && actualDelta> ONEWEEK) periodLength = actualDelta; // center monthly & longer periods
+ }
+
+ if (
+ periodLength> 0 || // not instant
+ i === 0 // taking care first tick added
+ ) {
+ tickVals[i].periodX = v + periodLength / 2;
}
+ }
}
// calculate the ticks: text, values, positioning
@@ -904,412 +963,411 @@ function positionPeriodTicks(tickVals, ax, definedDelta) {
// 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, opts) {
- var type = ax.type;
- var calendar = ax.calendar;
- var ticklabelstep = ax.ticklabelstep;
- var isPeriod = ax.ticklabelmode === 'period';
-
- var rng = Lib.simpleMap(ax.range, ax.r2l, undefined, undefined, opts);
- var axrev = (rng[1] < rng[0]); - var minRange = Math.min(rng[0], rng[1]); - var maxRange = Math.max(rng[0], rng[1]); + var type = ax.type; + var calendar = ax.calendar; + var ticklabelstep = ax.ticklabelstep; + var isPeriod = ax.ticklabelmode === "period"; - var maxTicks = Math.max(1000, ax._length || 0); + var rng = Lib.simpleMap(ax.range, ax.r2l, undefined, undefined, opts); + var axrev = rng[1] < rng[0]; + var minRange = Math.min(rng[0], rng[1]); + var maxRange = Math.max(rng[0], rng[1]); - var ticksOut = []; - var minorTicks = []; + var maxTicks = Math.max(1000, ax._length || 0); - var tickVals = []; - var minorTickVals = []; + var ticksOut = []; + var minorTicks = []; - var hasMinor = ax.minor && (ax.minor.ticks || ax.minor.showgrid); + var tickVals = []; + var minorTickVals = []; - // calc major first - for(var major = 1; major>= (hasMinor ? 0 : 1); major--) {
- var isMinor = !major;
+ var hasMinor = ax.minor && (ax.minor.ticks || ax.minor.showgrid);
- if(major) {
- ax._dtickInit = ax.dtick;
- ax._tick0Init = ax.tick0;
- } else {
- ax.minor._dtickInit = ax.minor.dtick;
- ax.minor._tick0Init = ax.minor.tick0;
- }
-
- var mockAx = major ? ax : Lib.extendFlat({}, ax, ax.minor);
-
- if(isMinor) {
- axes.prepMinorTicks(mockAx, ax, opts);
- } else {
- axes.prepTicks(mockAx, opts);
- }
+ // calc major first
+ for (var major = 1; major>= (hasMinor ? 0 : 1); major--) {
+ var isMinor = !major;
- // 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(mockAx.tickmode === 'array') {
- if(major) {
- tickVals = [];
- ticksOut = arrayTicks(ax);
- } else {
- minorTickVals = [];
- minorTicks = arrayTicks(ax);
- }
- continue;
- }
-
- // fill tickVals based on overlaying axis
- if(mockAx.tickmode === 'sync') {
- tickVals = [];
- ticksOut = syncTicks(ax);
- continue;
- }
-
- // add a tiny bit so we get ticks which may have rounded out
- var exRng = expandRange(rng);
- var startTick = exRng[0];
- var endTick = exRng[1];
-
- var numDtick = isNumeric(mockAx.dtick);
- var isDLog = (type === 'log') && !(numDtick || mockAx.dtick.charAt(0) === 'L');
+ if (major) {
+ ax._dtickInit = ax.dtick;
+ ax._tick0Init = ax.tick0;
+ } else {
+ ax.minor._dtickInit = ax.minor.dtick;
+ ax.minor._tick0Init = ax.minor.tick0;
+ }
- // find the first tick
- var x0 = axes.tickFirst(mockAx, opts);
+ var mockAx = major ? ax : Lib.extendFlat({}, ax, ax.minor);
- if(major) {
- ax._tmin = x0;
+ if (isMinor) {
+ axes.prepMinorTicks(mockAx, ax, opts);
+ } else {
+ axes.prepTicks(mockAx, opts);
+ }
- // No visible ticks? Quit.
- // I've only seen this on category axes with all categories off the edge.
- if((x0 < startTick) !== axrev) break; + // 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 (mockAx.tickmode === "array") { + if (major) { + tickVals = []; + ticksOut = arrayTicks(ax); + } else { + minorTickVals = []; + minorTicks = arrayTicks(ax); + } + continue; + } - // return the full set of tick vals - if(type === 'category' || type === 'multicategory') { - endTick = (axrev) ? Math.max(-0.5, endTick) : - Math.min(ax._categories.length - 0.5, endTick); - } - } + // fill tickVals based on overlaying axis + if (mockAx.tickmode === "sync") { + tickVals = []; + ticksOut = syncTicks(ax); + continue; + } - var prevX = null; - var x = x0; - var majorId; - - if(major) { - // ids for ticklabelstep - var _dTick; - if(numDtick) { - _dTick = ax.dtick; - } else { - if(type === 'date') { - if(typeof ax.dtick === 'string' && ax.dtick.charAt(0) === 'M') { - _dTick = ONEAVGMONTH * ax.dtick.substring(1); - } - } else { - _dTick = ax._roughDTick; - } - } - - majorId = Math.round(( - ax.r2l(x) - - ax.r2l(ax.tick0) - ) / _dTick) - 1; + // add a tiny bit so we get ticks which may have rounded out + var exRng = expandRange(rng); + var startTick = exRng[0]; + var endTick = exRng[1]; + + var numDtick = isNumeric(mockAx.dtick); + var isDLog = + type === "log" && !(numDtick || mockAx.dtick.charAt(0) === "L"); + + // find the first tick + var x0 = axes.tickFirst(mockAx, opts); + + if (major) { + ax._tmin = x0; + + // No visible ticks? Quit. + // I've only seen this on category axes with all categories off the edge. + if (x0 < startTick !== axrev) break; + + // return the full set of tick vals + if (type === "category" || type === "multicategory") { + endTick = axrev + ? Math.max(-0.5, endTick) + : Math.min(ax._categories.length - 0.5, endTick); + } + } + + var prevX = null; + var x = x0; + var majorId; + + if (major) { + // ids for ticklabelstep + var _dTick; + if (numDtick) { + _dTick = ax.dtick; + } else { + if (type === "date") { + if (typeof ax.dtick === "string" && ax.dtick.charAt(0) === "M") { + _dTick = ONEAVGMONTH * ax.dtick.substring(1); + } + } else { + _dTick = ax._roughDTick; } + } - var dtick = mockAx.dtick; + majorId = Math.round((ax.r2l(x) - ax.r2l(ax.tick0)) / _dTick) - 1; + } - if(mockAx.rangebreaks && mockAx._tick0Init !== mockAx.tick0) { - // adjust tick0 - x = moveOutsideBreak(x, ax); - if(!axrev) { - x = axes.tickIncrement(x, dtick, !axrev, calendar); - } - } + var dtick = mockAx.dtick; - if(major && isPeriod) { - // add one item to label period before tick0 - x = axes.tickIncrement(x, dtick, !axrev, calendar); - majorId--; - } + if (mockAx.rangebreaks && mockAx._tick0Init !== mockAx.tick0) { + // adjust tick0 + x = moveOutsideBreak(x, ax); + if (!axrev) { + x = axes.tickIncrement(x, dtick, !axrev, calendar); + } + } - for(; - axrev ? - (x>= endTick) :
- (x <= endTick); - x = axes.tickIncrement( - x, - dtick, - axrev, - calendar - ) - ) { - if(major) majorId++; - - if(mockAx.rangebreaks) { - if(!axrev) { - if(x < startTick) continue; - if(mockAx.maskBreaks(x) === BADNUM && moveOutsideBreak(x, mockAx)>= maxRange) break;
- }
- }
-
- // 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 === prevX) break;
- prevX = x;
-
- var obj = { value: x };
-
- if(major) {
- if(isDLog && (x !== (x | 0))) {
- obj.simpleLabel = true;
- }
-
- if(ticklabelstep> 1 && majorId % ticklabelstep) {
- obj.skipLabel = true;
- }
-
- tickVals.push(obj);
- } else {
- obj.minor = true;
-
- minorTickVals.push(obj);
- }
- }
+ if (major && isPeriod) {
+ // add one item to label period before tick0
+ x = axes.tickIncrement(x, dtick, !axrev, calendar);
+ majorId--;
}
- if(hasMinor) {
- var canOverlap =
- (ax.minor.ticks === 'inside' && ax.ticks === 'outside') ||
- (ax.minor.ticks === 'outside' && ax.ticks === 'inside');
-
- if(!canOverlap) {
- // remove duplicate minors
-
- var majorValues = tickVals.map(function(d) { return d.value; });
-
- var list = [];
- for(var k = 0; k < minorTickVals.length; k++) { - var T = minorTickVals[k]; - var v = T.value; - if(majorValues.indexOf(v) !== -1) { - continue; - } - var found = false; - for(var q = 0; !found && (q < tickVals.length); q++) { - if( - // add 10e6 to eliminate problematic digits - 10e6 + tickVals[q].value === - 10e6 + v - ) { - found = true; - } - } - if(!found) list.push(T); - } - minorTickVals = list; + for ( + ; + axrev ? x>= endTick : x <= endTick; + x = axes.tickIncrement(x, dtick, axrev, calendar) + ) { + if (major) majorId++; + + if (mockAx.rangebreaks) { + if (!axrev) { + if (x < startTick) continue; + if ( + mockAx.maskBreaks(x) === BADNUM && + moveOutsideBreak(x, mockAx)>= maxRange
+ )
+ break;
}
- }
+ }
- if(isPeriod) positionPeriodTicks(tickVals, ax, ax._definedDelta);
+ // 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 === prevX) break;
+ prevX = x;
- var i;
- if(ax.rangebreaks) {
- var flip = ax._id.charAt(0) === 'y';
+ var obj = { value: x };
- var fontSize = 1; // one pixel minimum
- if(ax.tickmode === 'auto') {
- fontSize = ax.tickfont ? ax.tickfont.size : 12;
+ if (major) {
+ if (isDLog && x !== (x | 0)) {
+ obj.simpleLabel = true;
}
- var prevL = NaN;
- for(i = tickVals.length - 1; i> -1; i--) {
- if(tickVals[i].drop) {
- tickVals.splice(i, 1);
- continue;
- }
-
- tickVals[i].value = moveOutsideBreak(tickVals[i].value, ax);
-
- // avoid overlaps
- var l = ax.c2p(tickVals[i].value);
- if(flip ?
- (prevL> l - fontSize) :
- (prevL < l + fontSize) - ) { // ensure one pixel minimum - tickVals.splice(axrev ? i + 1 : i, 1); - } else { - prevL = l; - } + if (ticklabelstep> 1 && majorId % ticklabelstep) {
+ obj.skipLabel = true;
}
- }
- // 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();
- }
+ tickVals.push(obj);
+ } else {
+ obj.minor = true;
- // 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 lastVisibleHead;
- var hideLabel = function(tick) {
- tick.text = '';
- ax._prevDateHead = lastVisibleHead;
- };
+ minorTickVals.push(obj);
+ }
+ }
+ }
- tickVals = tickVals.concat(minorTickVals);
+ if (hasMinor) {
+ var canOverlap =
+ (ax.minor.ticks === "inside" && ax.ticks === "outside") ||
+ (ax.minor.ticks === "outside" && ax.ticks === "inside");
- var t, p;
- for(i = 0; i < tickVals.length; i++) { - var _minor = tickVals[i].minor; - var _value = tickVals[i].value; + if (!canOverlap) { + // remove duplicate minors - if(_minor) { - minorTicks.push({ - x: _value, - minor: true - }); - } else { - lastVisibleHead = ax._prevDateHead; + var majorValues = tickVals.map(function (d) { + return d.value; + }); - t = axes.tickText( - ax, - _value, - false, // hover - tickVals[i].simpleLabel // noSuffixPrefix - ); - - p = tickVals[i].periodX; - if(p !== undefined) { - t.periodX = p; - if(p> maxRange || p < minRange) { // hide label if outside the range - if(p> maxRange) t.periodX = maxRange;
- if(p < minRange) t.periodX = minRange; - - hideLabel(t); - } - } + var list = []; + for (var k = 0; k < minorTickVals.length; k++) { + var T = minorTickVals[k]; + var v = T.value; + if (majorValues.indexOf(v) !== -1) { + continue; + } + var found = false; + for (var q = 0; !found && q < tickVals.length; q++) { + if ( + // add 10e6 to eliminate problematic digits + 10e6 + tickVals[q].value === + 10e6 + v + ) { + found = true; + } + } + if (!found) list.push(T); + } + minorTickVals = list; + } + } + + if (isPeriod) positionPeriodTicks(tickVals, ax, ax._definedDelta); + + var i; + if (ax.rangebreaks) { + var flip = ax._id.charAt(0) === "y"; + + var fontSize = 1; // one pixel minimum + if (ax.tickmode === "auto") { + fontSize = ax.tickfont ? ax.tickfont.size : 12; + } + + var prevL = NaN; + for (i = tickVals.length - 1; i> -1; i--) {
+ if (tickVals[i].drop) {
+ tickVals.splice(i, 1);
+ continue;
+ }
+
+ tickVals[i].value = moveOutsideBreak(tickVals[i].value, ax);
+
+ // avoid overlaps
+ var l = ax.c2p(tickVals[i].value);
+ if (flip ? prevL> l - fontSize : prevL < l + fontSize) { + // ensure one pixel minimum + tickVals.splice(axrev ? i + 1 : i, 1); + } else { + prevL = l; + } + } + } + + // 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 lastVisibleHead; + var hideLabel = function (tick) { + tick.text = ""; + ax._prevDateHead = lastVisibleHead; + }; + + tickVals = tickVals.concat(minorTickVals); + + var t, p; + for (i = 0; i < tickVals.length; i++) { + var _minor = tickVals[i].minor; + var _value = tickVals[i].value; + + if (_minor) { + minorTicks.push({ + x: _value, + minor: true, + }); + } else { + lastVisibleHead = ax._prevDateHead; + + t = axes.tickText( + ax, + _value, + false, // hover + tickVals[i].simpleLabel // noSuffixPrefix + ); + + p = tickVals[i].periodX; + if (p !== undefined) { + t.periodX = p; + if (p> maxRange || p < minRange) { + // hide label if outside the range + if (p> maxRange) t.periodX = maxRange;
+ if (p < minRange) t.periodX = minRange; + + hideLabel(t); + } + } - if(tickVals[i].skipLabel) { - hideLabel(t); - } + if (tickVals[i].skipLabel) { + hideLabel(t); + } - ticksOut.push(t); - } + ticksOut.push(t); } - ticksOut = ticksOut.concat(minorTicks); + } + ticksOut = ticksOut.concat(minorTicks); - ax._inCalcTicks = false; + ax._inCalcTicks = false; - if(isPeriod && ticksOut.length) { - // drop very first tick that we added to handle period - ticksOut[0].noTick = true; - } + if (isPeriod && ticksOut.length) { + // drop very first tick that we added to handle period + ticksOut[0].noTick = true; + } - return ticksOut; + return ticksOut; }; function filterRangeBreaks(ax, ticksOut) { - if(ax.rangebreaks) { - // remove ticks falling inside rangebreaks - ticksOut = ticksOut.filter(function(d) { - return ax.maskBreaks(d.x) !== BADNUM; - }); - } + if (ax.rangebreaks) { + // remove ticks falling inside rangebreaks + ticksOut = ticksOut.filter(function (d) { + return ax.maskBreaks(d.x) !== BADNUM; + }); + } - return ticksOut; + return ticksOut; } function syncTicks(ax) { - // get the overlaying axis - var baseAxis = ax._mainAxis; - - var ticksOut = []; - if(baseAxis._vals) { - for(var i = 0; i < baseAxis._vals.length; i++) { - // filter vals with noTick flag - if(baseAxis._vals[i].noTick) { - continue; - } - - // get the position of the every tick - var pos = baseAxis.l2p(baseAxis._vals[i].x); - - // get the tick for the current axis based on position - var vali = ax.p2l(pos); - var obj = axes.tickText(ax, vali); - - // assign minor ticks - if(baseAxis._vals[i].minor) { - obj.minor = true; - obj.text = ''; - } - - ticksOut.push(obj); - } - } + // get the overlaying axis + var baseAxis = ax._mainAxis; - ticksOut = filterRangeBreaks(ax, ticksOut); + var ticksOut = []; + if (baseAxis._vals) { + for (var i = 0; i < baseAxis._vals.length; i++) { + // filter vals with noTick flag + if (baseAxis._vals[i].noTick) { + continue; + } - return ticksOut; -} + // get the position of the every tick + var pos = baseAxis.l2p(baseAxis._vals[i].x); -function arrayTicks(ax) { - var rng = Lib.simpleMap(ax.range, ax.r2l); - var exRng = expandRange(rng); - var tickMin = Math.min(exRng[0], exRng[1]); - var tickMax = Math.max(exRng[0], exRng[1]); + // get the tick for the current axis based on position + var vali = ax.p2l(pos); + var obj = axes.tickText(ax, vali); - // 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; + // assign minor ticks + if (baseAxis._vals[i].minor) { + obj.minor = true; + obj.text = ""; + } - // 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); + ticksOut.push(obj); } + } - var ticksOut = []; - for(var isMinor = 0; isMinor <= 1; isMinor++) { - if(isMinor && !ax.minor) continue; - var vals = !isMinor ? ax.tickvals : ax.minor.tickvals; - var text = !isMinor ? ax.ticktext : []; + ticksOut = filterRangeBreaks(ax, ticksOut); - if(!vals) continue; - - - // 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 = []; - - for(var i = 0; i < vals.length; i++) { - var vali = tickVal2l(vals[i]); - if(vali> tickMin && vali < tickMax) { - var obj = text[i] === undefined ? - axes.tickText(ax, vali) : - tickTextObj(ax, vali, String(text[i])); - - if(isMinor) { - obj.minor = true; - obj.text = ''; - } + return ticksOut; +} - ticksOut.push(obj); - } +function arrayTicks(ax) { + var rng = Lib.simpleMap(ax.range, ax.r2l); + var exRng = expandRange(rng); + var tickMin = Math.min(exRng[0], exRng[1]); + var tickMax = Math.max(exRng[0], exRng[1]); + + // 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); + } + + var ticksOut = []; + for (var isMinor = 0; isMinor <= 1; isMinor++) { + if (isMinor && !ax.minor) continue; + var vals = !isMinor ? ax.tickvals : ax.minor.tickvals; + var text = !isMinor ? ax.ticktext : []; + + if (!vals) continue; + + // 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 = []; + + for (var i = 0; i < vals.length; i++) { + var vali = tickVal2l(vals[i]); + if (vali> tickMin && vali < tickMax) { + var obj = + text[i] === undefined + ? axes.tickText(ax, vali) + : tickTextObj(ax, vali, String(text[i])); + + if (isMinor) { + obj.minor = true; + obj.text = ""; } + + ticksOut.push(obj); + } } + } - ticksOut = filterRangeBreaks(ax, ticksOut); + ticksOut = filterRangeBreaks(ax, ticksOut); - return ticksOut; + return ticksOut; } var roundBase10 = [2, 5, 10]; @@ -1319,13 +1377,15 @@ var roundBase60 = [1, 2, 5, 10, 15, 30]; 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 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); + return base * Lib.roundUp(roughDTick / base, roundingSet); } // autoTicks: calculate best guess at pleasant ticks for this axis @@ -1344,106 +1404,109 @@ function roundDTick(roughDTick, base, roundingSet) { // 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, isMinor) { - 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, 0); - - // 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, ax._hasDayOfWeekBreaks ? [1, 2, 7, 14] : roundDays);
- if(!isMinor) {
- // 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.
- var tickformat = axes.getTickFormat(ax);
- var isPeriod = ax.ticklabelmode === 'period';
- if(isPeriod) ax._rawTick0 = ax.tick0;
-
- if(/%[uVW]/.test(tickformat)) {
- ax.tick0 = Lib.dateTick0(ax.calendar, 2); // Monday
- } else {
- ax.tick0 = Lib.dateTick0(ax.calendar, 1); // Sunday
- }
-
- if(isPeriod) ax._dowTick0 = ax.tick0;
- }
- } 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);
+axes.autoTicks = function (ax, roughDTick, isMinor) {
+ 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, 0);
+
+ // 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,
+ ax._hasDayOfWeekBreaks ? [1, 2, 7, 14] : roundDays
+ );
+ if (!isMinor) {
+ // 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.
+ var tickformat = axes.getTickFormat(ax);
+ var isPeriod = ax.ticklabelmode === "period";
+ if (isPeriod) ax._rawTick0 = ax.tick0;
+
+ if (/%[uVW]/.test(tickformat)) {
+ ax.tick0 = Lib.dateTick0(ax.calendar, 2); // Monday
} 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(ax._isMinor) {
- // Log axes by default get MORE than nTicks based on the metrics below
- // But for minor ticks we don't want this increase, we already have
- // the major ticks.
- roughDTick *= 1.5;
+ ax.tick0 = Lib.dateTick0(ax.calendar, 1); // Sunday
}
- 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);
+ if (isPeriod) ax._dowTick0 = ax.tick0;
+ }
+ } 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 (ax._isMinor) {
+ // Log axes by default get MORE than nTicks based on the metrics below
+ // But for minor ticks we don't want this increase, we already have
+ // the major ticks.
+ roughDTick *= 1.5;
+ }
+ 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
@@ -1452,64 +1515,67 @@ axes.autoTicks = function(ax, roughDTick, isMinor) {
// 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); - var minexponent = ax.minexponent === undefined ? 3 : ax.minexponent; - if(Math.abs(rangeexp)> minexponent) {
- 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;
- }
+ 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); + var minexponent = ax.minexponent === undefined ? 3 : ax.minexponent; + if (Math.abs(rangeexp)> minexponent) {
+ 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
@@ -1518,95 +1584,104 @@ function autoTickRound(ax) {
// 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;
+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 Lib.increment(x, axSign * dtick);
+ // includes linear, all dates smaller than month, and pure 10^n in log
+ if (isNumeric(dtick)) return Lib.increment(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));
+ // 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);
+ // Dates: months (or years - see Lib.incrementMonth)
+ if (tType === "M") return Lib.incrementMonth(x, dtSigned, calendar);
- // Log scales: Linear, Digits
- if(tType === 'L') return Math.log(Math.pow(10, x) + dtSigned) / Math.LN10;
+ // Log scales: Linear, Digits
+ 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)
- 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);
+ // log10 of 2,5,10, or all digits (logs just have to be
+ // close enough to round)
+ 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;
- }
+ return (
+ Math.floor(x2) + Math.log(d3.round(Math.pow(10, frac), 1)) / Math.LN10
+ );
+ }
- throw 'unrecognized dtick ' + String(dtick);
+ throw "unrecognized dtick " + String(dtick);
};
// calculate the first tick on an axis
-axes.tickFirst = function(ax, opts) {
- var r2l = ax.r2l || Number;
- var rng = Lib.simpleMap(ax.range, r2l, undefined, undefined, opts);
- 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 = expandRange(rng)[0]; - 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; - } +axes.tickFirst = function (ax, opts) { + var r2l = ax.r2l || Number; + var rng = Lib.simpleMap(ax.range, r2l, undefined, undefined, opts); + 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 = expandRange(rng)[0]; + 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 - 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); + 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. @@ -1615,77 +1690,78 @@ axes.tickFirst = function(ax, opts) { // 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]) - (ax._lBreaks || 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; - } - - if(ax.labelalias && ax.labelalias.hasOwnProperty(out.text)) { - var t = ax.labelalias[out.text]; - if(typeof t === 'string') out.text = t; - } - - // 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; - }; +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]) - (ax._lBreaks || 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; + } + + if (ax.labelalias && ax.labelalias.hasOwnProperty(out.text)) { + var t = ax.labelalias[out.text]; + if (typeof t === "string") out.text = t; + } + + // 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) - ]; - } + out.xbnd = [inbounds(out.x - 0.5), inbounds(out.x + ax.dtick - 0.5)]; + } - return out; + return out; }; /** @@ -1700,291 +1776,325 @@ axes.tickText = function(ax, x, hover, noSuffixPrefix) { * first value and second value as a range (ie ' - ') if the second value is provided and
* it's different from the first value.
*/
-axes.hoverLabelText = function(ax, values, hoverformat) {
- if(hoverformat) ax = Lib.extendFlat({}, ax, {hoverformat: hoverformat});
-
- var val = Array.isArray(values) ? values[0] : values;
- var val2 = Array.isArray(values) ? values[1] : undefined;
- if(val2 !== undefined && val2 !== val) {
- return (
- axes.hoverLabelText(ax, val, hoverformat) + ' - ' +
- axes.hoverLabelText(ax, val2, hoverformat)
- );
- }
+axes.hoverLabelText = function (ax, values, hoverformat) {
+ if (hoverformat) ax = Lib.extendFlat({}, ax, { hoverformat: hoverformat });
+
+ var val = Array.isArray(values) ? values[0] : values;
+ var val2 = Array.isArray(values) ? values[1] : undefined;
+ if (val2 !== undefined && val2 !== val) {
+ return (
+ axes.hoverLabelText(ax, val, hoverformat) +
+ " - " +
+ axes.hoverLabelText(ax, val2, hoverformat)
+ );
+ }
- var logOffScale = (ax.type === 'log' && val <= 0); - var tx = axes.tickText(ax, ax.c2l(logOffScale ? -val : val), 'hover').text; + 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; - } + 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; + // 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 - }; + 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); - - // Only apply extra precision if no explicit format was provided. - extraPrecision = !fmt && extraPrecision; - - 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(headStr !== undefined && (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 || - ax._prevDateHead !== headStr - ) { - ax._prevDateHead = headStr; - dateStr += ' ' + headStr;
- } else {
- var isInside = insideTicklabelposition(ax);
- var side = ax._trueSide || ax.side; // polar mocks the side of the radial axis
- if(
- (!isInside && side === 'top') ||
- (isInside && side === 'bottom')
- ) {
- dateStr += ' ';
- }
- }
+ var tr = ax._tickround;
+ var fmt = (hover && ax.hoverformat) || axes.getTickFormat(ax);
+
+ // Only apply extra precision if no explicit format was provided.
+ extraPrecision = !fmt && extraPrecision;
+
+ 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 (
+ headStr !== undefined &&
+ (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 || ax._prevDateHead !== headStr) {
+ ax._prevDateHead = headStr;
+ dateStr += " " + headStr;
+ } else {
+ var isInside = insideTicklabelposition(ax);
+ var side = ax._trueSide || ax.side; // polar mocks the side of the radial axis
+ if ((!isInside && side === "top") || (isInside && side === "bottom")) {
+ dateStr += " ";
}
+ }
}
+ }
- out.text = dateStr;
+ 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';
- }
+ 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 + " ";
- 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); - } - } - } + 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); + 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; - } + var v = Math.round(out.x); + var cats = + ax._categories[v].map(function (cat) { + return cat; + }) || []; + var texts = cats + .slice() + .reverse() + .map(function (cat) { + return cat === undefined ? "" : String(cat); + }); + + if (hover) { + // TODO is this what we want? + var hoverText = ""; + cats.forEach(function (text, index) { + text = String(text); + if (index < texts.length - 1) { + hoverText = hoverText + " " + text + " - "; + } else { + hoverText = hoverText + " " + text; + } + }); + + out.text = hoverText; + } else { + // setup for secondary labels + out.text = texts[0]; + out.texts = texts; + } } 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); + 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 (ax.thetaunit === "radians" && !hover) { + var num = out.x / 180; - if(num === 0) { - out.text = '0'; + 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 { - 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;
- }
+ out.text = [
+ "",
+ frac[0],
+ " ",
+ "⁄",
+ "",
+ frac[1],
+ " ",
+ "π",
+ ].join("");
}
- } else {
- out.text = numFormat(out.x, ax, hideexp, extraPrecision);
+
+ 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) - ]; + 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']; +var SIPREFIXES = ["f", "p", "n", "μ", "m", "", "k", "M", "G", "T"]; function isSIFormat(exponentFormat) { - return exponentFormat === 'SI' || exponentFormat === 'B'; + return exponentFormat === "SI" || exponentFormat === "B"; } // are we beyond the range of common SI prefixes? @@ -1995,179 +2105,203 @@ function isSIFormat(exponentFormat) { // 10^15 -> 1x10^15
// 10^16 -> 1x10^16
function beyondSI(exponent) {
- return exponent> 14 || exponent < -15; + 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, - minexponent: ax.minexponent, - 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; + 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, + minexponent: ax.minexponent, + 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 += 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;
+ 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;
- }
- }
+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 typeof left === 'number' ? 1 : -1;
+ return leftPriority - rightPriority;
}
- }
-
- 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); - } + } + } 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))
+ );
+ }
- 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: + 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; + } + return tickstop ? tickstop.value : ax.tickformat; }; // getSubplots - extract all subplot IDs we need @@ -2177,83 +2311,92 @@ axes.getTickFormat = function(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 || []); +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; + 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'); + 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]; - }); + if (aParts[0] === bParts[0]) return +aParts[1] - +bParts[1]; + return +aParts[0] - +bParts[0]; + }); - return out; + 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; +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 - }); +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, + }); + }); }; /** @@ -2279,80 +2422,88 @@ axes.makeClipPaths = function(gd) { * - 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]; - if(plotinfo) { - 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.minorGridlayer) plotinfo.minorGridlayer.selectAll('path').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; - - var fullAxList = axes.list(gd); - // Get the list of the overlaying axis for all 'shift' axes - var overlayingShiftedAx = fullAxList.filter(function(ax) { - return ax.autoshift; - }).map(function(ax) { - return ax.overlaying; +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]; + if (plotinfo) { + 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.minorGridlayer) + plotinfo.minorGridlayer.selectAll("path").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; + + var fullAxList = axes.list(gd); + // Get the list of the overlaying axis for all 'shift' axes + var overlayingShiftedAx = fullAxList + .filter(function (ax) { + return ax.autoshift; + }) + .map(function (ax) { + return ax.overlaying; }); - // order axes that have dependency to other axes - axList.map(function(axId) { - var ax = axes.getFromId(gd, axId); + // order axes that have dependency to other axes + axList.map(function (axId) { + var ax = axes.getFromId(gd, axId); - if(ax.tickmode === 'sync' && ax.overlaying) { - var overlayingIndex = axList.findIndex(function(axis) {return axis === ax.overlaying;}); + if (ax.tickmode === "sync" && ax.overlaying) { + var overlayingIndex = axList.findIndex(function (axis) { + return axis === ax.overlaying; + }); - if(overlayingIndex>= 0) {
- axList.unshift(axList.splice(overlayingIndex, 1).shift());
- }
- }
- });
+ if (overlayingIndex>= 0) {
+ axList.unshift(axList.splice(overlayingIndex, 1).shift());
+ }
+ }
+ });
- var axShifts = {false: {left: 0, right: 0}};
+ var axShifts = { false: { left: 0, right: 0 } };
- return Lib.syncOrAsync(axList.map(function(axId) {
- return function() {
- if(!axId) return;
+ return Lib.syncOrAsync(
+ axList.map(function (axId) {
+ return function () {
+ if (!axId) return;
- var ax = axes.getFromId(gd, axId);
+ var ax = axes.getFromId(gd, axId);
- if(!opts) opts = {};
- opts.axShifts = axShifts;
- opts.overlayingShiftedAx = overlayingShiftedAx;
+ if (!opts) opts = {};
+ opts.axShifts = axShifts;
+ opts.overlayingShiftedAx = overlayingShiftedAx;
- var axDone = axes.drawOne(gd, ax, opts);
+ var axDone = axes.drawOne(gd, ax, opts);
- if(ax._shiftPusher) {
- incrementShift(ax, ax._fullDepth || 0, axShifts, true);
- }
- ax._r = ax.range.slice();
- ax._rl = Lib.simpleMap(ax._r, ax.r2l);
+ if (ax._shiftPusher) {
+ incrementShift(ax, ax._fullDepth || 0, axShifts, true);
+ }
+ ax._r = ax.range.slice();
+ ax._rl = Lib.simpleMap(ax._r, ax.r2l);
- return axDone;
- };
- }));
+ return axDone;
+ };
+ })
+ );
};
/**
@@ -2382,535 +2533,647 @@ axes.draw = function(gd, arg, opts) {
* - ax._depth (when required only):
* - and calls ax.setScale
*/
-axes.drawOne = function(gd, ax, opts) {
- opts = opts || {};
-
- var axShifts = opts.axShifts || {};
- var overlayingShiftedAx = opts.overlayingShiftedAx || [];
-
- 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 mainPlotinfo = fullLayout._plots[ax._mainSubplot];
-
- // this happens when updating matched group with 'missing' axes
- if(!mainPlotinfo) return;
-
- ax._shiftPusher = ax.autoshift ||
- overlayingShiftedAx.indexOf(ax._id) !== -1 ||
- overlayingShiftedAx.indexOf(ax.overlaying) !== -1;
- // An axis is also shifted by 1/2 of its own linewidth and inside tick length if applicable
- // as well as its manually specified `shift` val if we're in the context of `autoshift`
- if(ax._shiftPusher & ax.anchor === 'free') {
- var selfPush = (ax.linewidth / 2 || 0);
- if(ax.ticks === 'inside') {
- selfPush += ax.ticklen;
- }
- incrementShift(ax, selfPush, axShifts, true);
- incrementShift(ax, (ax.shift || 0), axShifts, false);
- }
-
- // Somewhat inelegant way of making sure that the shift value is only updated when the
- // Axes.DrawOne() function is called from the right context. An issue when redrawing the
- // axis as result of using the dragbox, for example.
- if(opts.skipTitle !== true || ax._shift === undefined) ax._shift = setShiftVal(ax, axShifts);
-
- var mainAxLayer = mainPlotinfo[axLetter + 'axislayer'];
- var mainLinePosition = ax._mainLinePosition;
- var mainLinePositionShift = mainLinePosition += ax._shift;
- var mainMirrorPosition = ax._mainMirrorPosition;
-
- 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, mainLinePositionShift, 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 transTickFn = axes.makeTransTickFn(ax); - var transTickLabelFn = axes.makeTransTickLabelFn(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; - - var insideTicks = ax.ticks === 'inside'; - var outsideTicks = ax.ticks === 'outside'; +axes.drawOne = function (gd, ax, opts) { + opts = opts || {}; + + var axShifts = opts.axShifts || {}; + var overlayingShiftedAx = opts.overlayingShiftedAx || []; + + 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 mainPlotinfo = fullLayout._plots[ax._mainSubplot]; + + // this happens when updating matched group with 'missing' axes + if (!mainPlotinfo) return; + + ax._shiftPusher = + ax.autoshift || + overlayingShiftedAx.indexOf(ax._id) !== -1 || + overlayingShiftedAx.indexOf(ax.overlaying) !== -1; + // An axis is also shifted by 1/2 of its own linewidth and inside tick length if applicable + // as well as its manually specified `shift` val if we're in the context of `autoshift` + if (ax._shiftPusher & (ax.anchor === "free")) { + var selfPush = ax.linewidth / 2 || 0; + if (ax.ticks === "inside") { + selfPush += ax.ticklen; + } + incrementShift(ax, selfPush, axShifts, true); + incrementShift(ax, ax.shift || 0, axShifts, false); + } + + // Somewhat inelegant way of making sure that the shift value is only updated when the + // Axes.DrawOne() function is called from the right context. An issue when redrawing the + // axis as result of using the dragbox, for example. + if (opts.skipTitle !== true || ax._shift === undefined) + ax._shift = setShiftVal(ax, axShifts); + + var mainAxLayer = mainPlotinfo[axLetter + "axislayer"]; + var mainLinePosition = ax._mainLinePosition; + var mainLinePositionShift = (mainLinePosition += ax._shift); + var mainMirrorPosition = ax._mainMirrorPosition; + + 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, mainLinePositionShift, 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 transTickFn = axes.makeTransTickFn(ax); + var transTickLabelFn = axes.makeTransTickLabelFn(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; + + var insideTicks = ax.ticks === "inside"; + var outsideTicks = ax.ticks === "outside"; + + if (ax.tickson === "boundaries") { + var boundaryVals = getBoundaryVals(ax, vals); + valsClipped = axes.clipEnds(ax, boundaryVals); + tickVals = insideTicks ? valsClipped : boundaryVals; + } else { + valsClipped = axes.clipEnds(ax, vals); + tickVals = + insideTicks && ax.ticklabelmode !== "period" ? 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), + minorLayer: plotinfo.minorGridlayer.select("." + axId), + path: gridPath, + transFn: transTickFn, + }); + axes.drawZeroLine(gd, ax, { + counterAxis: counterAxis, + layer: plotinfo.zerolinelayer, + path: gridPath, + transFn: transTickFn, + }); + } + } + + var tickPath; + + var majorTickSigns = axes.getTickSigns(ax); + var minorTickSigns = axes.getTickSigns(ax, "minor"); + + if (ax.ticks || (ax.minor && ax.minor.ticks)) { + var majorTickPath = axes.makeTickPath( + ax, + mainLinePositionShift, + majorTickSigns[2] + ); + var minorTickPath = axes.makeTickPath( + ax, + mainLinePositionShift, + minorTickSigns[2], + { minor: true } + ); - if(ax.tickson === 'boundaries') { - var boundaryVals = getBoundaryVals(ax, vals); - valsClipped = axes.clipEnds(ax, boundaryVals); - tickVals = insideTicks ? valsClipped : boundaryVals; + var mirrorMajorTickPath; + var mirrorMinorTickPath; + + var fullMajorTickPath; + var fullMinorTickPath; + + if (ax._anchorAxis && ax.mirror && ax.mirror !== true) { + mirrorMajorTickPath = axes.makeTickPath( + ax, + mainMirrorPosition, + majorTickSigns[3] + ); + mirrorMinorTickPath = axes.makeTickPath( + ax, + mainMirrorPosition, + minorTickSigns[3], + { minor: true } + ); + + fullMajorTickPath = majorTickPath + mirrorMajorTickPath; + fullMinorTickPath = minorTickPath + mirrorMinorTickPath; } else { - valsClipped = axes.clipEnds(ax, vals); - tickVals = (insideTicks && ax.ticklabelmode !== 'period') ? 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; + mirrorMajorTickPath = ""; + mirrorMinorTickPath = ""; + fullMajorTickPath = majorTickPath; + fullMinorTickPath = minorTickPath; + } + + if (ax.showdividers && outsideTicks && ax.tickson === "boundaries") { + var dividerLookup = {}; + for (i = 0; i < dividerVals.length; i++) { + dividerLookup[dividerVals[i].x] = 1; + } + tickPath = function (d) { + return dividerLookup[d.x] ? mirrorMajorTickPath : fullMajorTickPath; + }; + } else { + tickPath = function (d) { + return d.minor ? fullMinorTickPath : fullMajorTickPath; + }; + } + } + + axes.drawTicks(gd, ax, { + vals: tickVals, + layer: mainAxLayer, + path: tickPath, + transFn: transTickFn, + }); + + if (ax.mirror === "allticks") { + var 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 p0 = linepositions[0]; + var p1 = linepositions[1]; + var isMinor = linepositions[2]; + + var spTickPath = + axes.makeTickPath( + ax, + p0, + isMinor ? majorTickSigns[0] : minorTickSigns[0], + { minor: isMinor } + ) + + axes.makeTickPath( + ax, + p1, + isMinor ? majorTickSigns[1] : minorTickSigns[1], + { minor: isMinor } + ); - axes.drawGrid(gd, ax, { - vals: gridVals, - counterAxis: counterAxis, - layer: plotinfo.gridlayer.select('.' + axId), - minorLayer: plotinfo.minorGridlayer.select('.' + axId), - path: gridPath, - transFn: transTickFn - }); - axes.drawZeroLine(gd, ax, { - counterAxis: counterAxis, - layer: plotinfo.zerolinelayer, - path: gridPath, - transFn: transTickFn - }); - } + axes.drawTicks(gd, ax, { + vals: tickVals, + layer: plotinfo[axLetter + "axislayer"], + path: spTickPath, + transFn: transTickFn, + }); } + } - var tickPath; + var seq = []; - var majorTickSigns = axes.getTickSigns(ax); - var minorTickSigns = axes.getTickSigns(ax, 'minor'); + // tick labels - for now just the main labels. + // TODO: mirror labels, esp for subplots - if(ax.ticks || (ax.minor && ax.minor.ticks)) { - var majorTickPath = axes.makeTickPath(ax, mainLinePositionShift, majorTickSigns[2]); - var minorTickPath = axes.makeTickPath(ax, mainLinePositionShift, minorTickSigns[2], { minor: true }); - - var mirrorMajorTickPath; - var mirrorMinorTickPath; - - var fullMajorTickPath; - var fullMinorTickPath; - - if(ax._anchorAxis && ax.mirror && ax.mirror !== true) { - mirrorMajorTickPath = axes.makeTickPath(ax, mainMirrorPosition, majorTickSigns[3]); - mirrorMinorTickPath = axes.makeTickPath(ax, mainMirrorPosition, minorTickSigns[3], { minor: true }); - - fullMajorTickPath = majorTickPath + mirrorMajorTickPath; - fullMinorTickPath = minorTickPath + mirrorMinorTickPath; - } else { - mirrorMajorTickPath = ''; - mirrorMinorTickPath = ''; - fullMajorTickPath = majorTickPath; - fullMinorTickPath = minorTickPath; - } - - if(ax.showdividers && outsideTicks && ax.tickson === 'boundaries') { - var dividerLookup = {}; - for(i = 0; i < dividerVals.length; i++) { - dividerLookup[dividerVals[i].x] = 1; - } - tickPath = function(d) { - return dividerLookup[d.x] ? mirrorMajorTickPath : fullMajorTickPath; - }; - } else { - tickPath = function(d) { - return d.minor ? fullMinorTickPath : fullMajorTickPath; - }; - } - } - - axes.drawTicks(gd, ax, { - vals: tickVals, - layer: mainAxLayer, - path: tickPath, - transFn: transTickFn + seq.push(function () { + return axes.drawLabels(gd, ax, { + vals: vals, + layer: mainAxLayer, + plotinfo: plotinfo, + transFn: transTickLabelFn, + labelFns: axes.makeLabelFns(ax, mainLinePositionShift), }); + }); + + var tickNames = ["tick"]; + + if (ax.type === "multicategory") { + ax.levels + .slice() + .reverse() + .slice(0, ax.levelNr - 1) + .forEach(function (_lvl) { + var pad = { x: 0 * _lvl, y: 10 }[axLetter]; + + var tickName = "tick" + String(_lvl); + tickNames.push(tickName); + + seq.push(function () { + var bboxKey = { x: "width" }[axLetter]; + var standoff = + _lvl * getLabelLevelBbox()[bboxKey] + + pad + + (ax._tickAngles[axId + "tick"] + ? ax.tickfont.size * LINE_SPACING + : 0); + + return axes.drawLabels(gd, ax, { + vals: getSecondaryLabelVals(ax, vals, _lvl), + layer: mainAxLayer, + cls: axId + tickName, + repositionOnUpdate: true, + secondary: true, + transFn: transTickFn, + labelFns: axes.makeLabelFns( + ax, + mainLinePosition + standoff * majorTickSigns[4] + ), + }); + }); + }); - if(ax.mirror === 'allticks') { - var 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 p0 = linepositions[0]; - var p1 = linepositions[1]; - var isMinor = linepositions[2]; - - var spTickPath = - axes.makeTickPath(ax, p0, - isMinor ? majorTickSigns[0] : minorTickSigns[0], - { minor: isMinor } - ) + - axes.makeTickPath(ax, p1, - isMinor ? majorTickSigns[1] : minorTickSigns[1], - { minor: isMinor } - ); - - axes.drawTicks(gd, ax, { - vals: tickVals, - layer: plotinfo[axLetter + 'axislayer'], - path: spTickPath, - transFn: transTickFn - }); - } - } - - var seq = []; + tickNames = tickNames.sort(); - // tick labels - for now just the main labels. - // TODO: mirror labels, esp for subplots + ax.levels.forEach(function (_lvl, idx) { + seq.push(function () { + ax._depth = + majorTickSigns[4] * + (getLabelLevelBbox(tickNames.slice()[_lvl])[ax.side] - + mainLinePosition); - seq.push(function() { - return axes.drawLabels(gd, ax, { - vals: vals, - layer: mainAxLayer, - plotinfo: plotinfo, - transFn: transTickLabelFn, - labelFns: axes.makeLabelFns(ax, mainLinePositionShift) + var levelDividers = dividerVals.slice().filter(function (divider) { + return divider.level === idx; }); - }); - if(ax.type === 'multicategory') { - var pad = {x: 2, y: 10}[axLetter]; - - seq.push(function() { - var bboxKey = {x: '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: transTickFn, - labelFns: axes.makeLabelFns(ax, mainLinePositionShift + standoff * majorTickSigns[4]) - }); + return drawDividers(gd, ax, { + vals: levelDividers, + layer: mainAxLayer, + path: axes.makeTickPath(ax, mainLinePosition, majorTickSigns[4], { + len: ax._depth, + }), + transFn: transTickFn, + level: _lvl, }); + }); + }); + } else if (ax.title.hasOwnProperty("standoff")) { + seq.push(function () { + ax._depth = + majorTickSigns[4] * + (getLabelLevelBbox()[ax.side] - mainLinePositionShift); + }); + } - seq.push(function() { - ax._depth = majorTickSigns[4] * (getLabelLevelBbox('tick2')[ax.side] - mainLinePositionShift); + var hasRangeSlider = Registry.getComponentMethod( + "rangeslider", + "isVisible" + )(ax); - return drawDividers(gd, ax, { - vals: dividerVals, - layer: mainAxLayer, - path: axes.makeTickPath(ax, mainLinePositionShift, majorTickSigns[4], { len: ax._depth }), - transFn: transTickFn - }); - }); - } else if(ax.title.hasOwnProperty('standoff')) { - seq.push(function() { - ax._depth = majorTickSigns[4] * (getLabelLevelBbox()[ax.side] - mainLinePositionShift); - }); - } + if (!opts.skipTitle && !(hasRangeSlider && ax.side === "bottom")) { + seq.push(function () { + return drawTitle(gd, ax); + }); + } - 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 = outsideTicks ? ax.ticklen : 0; + var llbbox; + + var push; + var mirrorPush; + var rangeSliderPush; + + if (ax.automargin || hasRangeSlider || ax._shiftPusher) { + if (ax.type === "multicategory") { + // hardcoded tick name, breakes only with plotly.py. Not sure if this is the right selection + // llbbox = getLabelLevelBbox('tick2'); + llbbox = getLabelLevelBbox("tick" + String(ax.levelNr - 1)); + } else { + llbbox = getLabelLevelBbox(); + if (axLetter === "x" && s === "b") { + ax._depth = Math.max( + llbbox.width> 0 ? llbbox.bottom - pos : 0,
+ outsideTickLen
+ );
+ }
+ }
+ }
+
+ var axDepth = 0;
+ var titleDepth = 0;
+ if (ax._shiftPusher) {
+ axDepth = Math.max(
+ outsideTickLen,
+ llbbox.height> 0
+ ? s === "l"
+ ? pos - llbbox.left
+ : llbbox.right - pos
+ : 0
+ );
+ if (ax.title.text !== fullLayout._dfltTitle[axLetter]) {
+ titleDepth = (ax._titleStandoff || 0) + (ax._titleScoot || 0);
+ if (s === "l") {
+ titleDepth += approxTitleDepth(ax);
+ }
+ }
- if(!opts.skipTitle &&
- !(hasRangeSlider && ax.side === 'bottom')
- ) {
- seq.push(function() { return drawTitle(gd, ax); });
+ ax._fullDepth = Math.max(axDepth, titleDepth);
}
- 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 = outsideTicks ? ax.ticklen : 0;
- var llbbox;
-
- var push;
- var mirrorPush;
- var rangeSliderPush;
-
- if(ax.automargin || hasRangeSlider || ax._shiftPusher) {
- 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];
+ var shift = typeof ax._shift === "number" ? ax._shift : 0;
+ 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();
}
- var axDepth = 0;
- var titleDepth = 0;
- if(ax._shiftPusher) {
- axDepth = Math.max(
- outsideTickLen,
- llbbox.height> 0 ? (s === 'l' ? pos - llbbox.left : llbbox.right - pos) : 0
- );
- if(ax.title.text !== fullLayout._dfltTitle[axLetter]) {
- titleDepth = (ax._titleStandoff || 0) + (ax._titleScoot || 0);
- if(s === 'l') {
- titleDepth += approxTitleDepth(ax);
- }
- }
-
- ax._fullDepth = Math.max(axDepth, titleDepth);
+ 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") {
+ ax._depth = Math.max(
+ llbbox.height> 0 ? pos - llbbox.left : 0,
+ outsideTickLen
+ );
+ push[s] = ax._depth - shift;
+ } else {
+ ax._depth = Math.max(
+ llbbox.height> 0 ? llbbox.right - pos : 0,
+ outsideTickLen
+ );
+ push[s] = ax._depth + shift;
+ domainIndices.reverse();
}
- if(ax.automargin) {
- push = {x: 0, y: 0, r: 0, l: 0, t: 0, b: 0};
- var domainIndices = [0, 1];
- var shift = typeof ax._shift === 'number' ? ax._shift : 0;
- 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') {
- ax._depth = Math.max(llbbox.height> 0 ? pos - llbbox.left : 0, outsideTickLen);
- push[s] = ax._depth - shift;
- } else {
- ax._depth = Math.max(llbbox.height> 0 ? llbbox.right - pos : 0, outsideTickLen);
- push[s] = ax._depth + shift;
- 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 (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;
+ }
}
- if(hasRangeSlider) {
- rangeSliderPush = Registry.getComponentMethod('rangeslider', 'autoMarginOpts')(gd, ax);
+ }
+
+ 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);
+ }
- if(typeof ax.automargin === 'string') {
- filterPush(push, ax.automargin);
- filterPush(mirrorPush, ax.automargin);
- }
+ if (typeof ax.automargin === "string") {
+ filterPush(push, ax.automargin);
+ filterPush(mirrorPush, ax.automargin);
+ }
- Plots.autoMargin(gd, axAutoMarginID(ax), push);
- Plots.autoMargin(gd, axMirrorAutoMarginID(ax), mirrorPush);
- Plots.autoMargin(gd, rangeSliderAutoMarginID(ax), rangeSliderPush);
- });
+ Plots.autoMargin(gd, axAutoMarginID(ax), push);
+ Plots.autoMargin(gd, axMirrorAutoMarginID(ax), mirrorPush);
+ Plots.autoMargin(gd, rangeSliderAutoMarginID(ax), rangeSliderPush);
+ });
- return Lib.syncOrAsync(seq);
+ return Lib.syncOrAsync(seq);
};
function filterPush(push, automargin) {
- if(!push) return;
-
- var keepMargin = Object.keys(MARGIN_MAPPING).reduce(function(data, nextKey) {
- if(automargin.indexOf(nextKey) !== -1) {
- MARGIN_MAPPING[nextKey].forEach(function(key) { data[key] = 1;});
- }
- return data;
- }, {});
- Object.keys(push).forEach(function(key) {
- if(!keepMargin[key]) {
- if(key.length === 1) push[key] = 0;
- else delete push[key];
- }
- });
+ if (!push) return;
+
+ var keepMargin = Object.keys(MARGIN_MAPPING).reduce(function (data, nextKey) {
+ if (automargin.indexOf(nextKey) !== -1) {
+ MARGIN_MAPPING[nextKey].forEach(function (key) {
+ data[key] = 1;
+ });
+ }
+ return data;
+ }, {});
+ Object.keys(push).forEach(function (key) {
+ if (!keepMargin[key]) {
+ if (key.length === 1) push[key] = 0;
+ else delete push[key];
+ }
+ });
}
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}));
- }
- };
+ var out = [];
+ var i;
- if(vals.length) {
- for(i = 0; i < vals.length; i++) { - _push(vals[i], 0); - } - _push(vals[i - 1], 1); + // 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 })); } + }; - return out; -} + if (vals.length) { + for (i = 0; i < vals.length; i++) { + _push(vals[i], 0); + } + _push(vals[i - 1], 1); + } -function getSecondaryLabelVals(ax, vals) { - var out = []; - var lookup = {}; + return out; +} - 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]; - } +function getSecondaryLabelVals(ax, vals, level) { + var out = []; + var lookup = {}; + var appearences = {}; + var current; + var currentParent = null; + var parent = null; + + for (var i = 0; i < vals.length; i++) { + var d = vals[i]; + var text = d.texts[level]; + parent = d.texts[level + 1]; + if (lookup[text]) { + if ((d.texts[level] === current) & (parent === currentParent)) { + lookup[text][appearences[text]].push(d.x); + } else { + appearences[text] = appearences[text] + 1; + lookup[text].push([d.x]); + } + } else { + appearences[text] = 0; + lookup[text] = [[d.x]]; } + current = d.texts[level]; + currentParent = d.texts[level + 1]; + } - for(var k in lookup) { - out.push(tickTextObj(ax, Lib.interp(lookup[k], 0.5), k)); - } + Object.keys(lookup).forEach(function (key) { + lookup[key].forEach(function (pos) { + out.push(tickTextObj(ax, Lib.interp(pos, 0.5), key)); + }); + }); - return out; + return out; } function getDividerVals(ax, vals) { - var out = []; - var i, current; - - var reversed = (vals.length && vals[vals.length - 1].x < vals[0].x); - - // 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, reversed ? 1 : 0); - } - current = d.text2; + var out = []; + var i, current; + + var reversed = vals.length && vals[vals.length - 1].x < vals[0].x; + + // never used for labels; + // no need to worry about the other tickTextObj keys + var _push = function (d, bndIndex, level) { + var xb = d.xbnd[bndIndex]; + if (xb !== null) { + var _out = Lib.extendFlat({}, d, { x: xb }); + _out.level = level; + out.push(_out); + } + }; + + if (ax.showdividers && vals.length) { + ax.levels.forEach(function (_lvl) { + current = undefined; + for (i = 0; i < vals.length; i++) { + var d = vals[i]; + if (d.texts[_lvl] !== current) { + _push(d, reversed ? 1 : 0, _lvl); } - _push(vals[i - 1], reversed ? 0 : 1); - } - - return out; + current = d.texts[_lvl]; + // text2 + } + _push(vals[i - 1], reversed ? 0 : 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
- };
+ 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,
+ };
}
/**
@@ -2927,22 +3190,24 @@ function calcLabelLevelBbox(ax, cls) {
* - [3]: sign for ticks mirroring 'ax.side'
* - [4]: sign of arrow starting at axis pointing towards margin
*/
-axes.getTickSigns = function(ax, minor) {
- 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
-
- var ticks = minor ? (ax.minor || {}).ticks : ax.ticks;
- if((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;
+axes.getTickSigns = function (ax, minor) {
+ 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
+
+ var ticks = minor ? (ax.minor || {}).ticks : ax.ticks;
+ if ((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;
};
/**
@@ -2954,80 +3219,75 @@ axes.getTickSigns = function(ax, minor) {
* - {fn} l2p
* @return {fn} function of calcTicks items
*/
-axes.makeTransTickFn = function(ax) {
- return ax._id.charAt(0) === 'x' ?
- function(d) { return strTranslate(ax._offset + ax.l2p(d.x), 0); } :
- function(d) { return strTranslate(0, ax._offset + ax.l2p(d.x)); };
+axes.makeTransTickFn = function (ax) {
+ return ax._id.charAt(0) === "x"
+ ? function (d) {
+ return strTranslate(ax._offset + ax.l2p(d.x), 0);
+ }
+ : function (d) {
+ return strTranslate(0, ax._offset + ax.l2p(d.x));
+ };
};
-axes.makeTransTickLabelFn = function(ax) {
- var uv = getTickLabelUV(ax);
- var u = uv[0];
- var v = uv[1];
-
- return ax._id.charAt(0) === 'x' ?
- function(d) {
- return strTranslate(
- u + ax._offset + ax.l2p(getPosX(d)),
- v
- );
- } :
- function(d) {
- return strTranslate(
- v,
- u + ax._offset + ax.l2p(getPosX(d))
- );
- };
+axes.makeTransTickLabelFn = function (ax) {
+ var uv = getTickLabelUV(ax);
+ var u = uv[0];
+ var v = uv[1];
+
+ return ax._id.charAt(0) === "x"
+ ? function (d) {
+ return strTranslate(u + ax._offset + ax.l2p(getPosX(d)), v);
+ }
+ : function (d) {
+ return strTranslate(v, u + ax._offset + ax.l2p(getPosX(d)));
+ };
};
function getPosX(d) {
- return d.periodX !== undefined ? d.periodX : d.x;
+ return d.periodX !== undefined ? d.periodX : d.x;
}
// u is a shift along the axis,
// v is a shift perpendicular to the axis
function getTickLabelUV(ax) {
- var ticklabelposition = ax.ticklabelposition || '';
- var has = function(str) {
- return ticklabelposition.indexOf(str) !== -1;
- };
-
- var isTop = has('top');
- var isLeft = has('left');
- var isRight = has('right');
- var isBottom = has('bottom');
- var isInside = has('inside');
-
- var isAligned = isBottom || isLeft || isTop || isRight;
-
- // early return
- if(!isAligned && !isInside) return [0, 0];
-
- var side = ax.side;
-
- var u = isAligned ? (ax.tickwidth || 0) / 2 : 0;
- var v = TEXTPAD;
-
- var fontSize = ax.tickfont ? ax.tickfont.size : 12;
- if(isBottom || isTop) {
- u += fontSize * CAP_SHIFT;
- v += (ax.linewidth || 0) / 2;
- }
- if(isLeft || isRight) {
- u += (ax.linewidth || 0) / 2;
- v += TEXTPAD;
- }
- if(isInside && side === 'top') {
- v -= fontSize * (1 - CAP_SHIFT);
- }
-
- if(isLeft || isTop) u = -u;
- if(side === 'bottom' || side === 'right') v = -v;
-
- return [
- isAligned ? u : 0,
- isInside ? v : 0
- ];
+ var ticklabelposition = ax.ticklabelposition || "";
+ var has = function (str) {
+ return ticklabelposition.indexOf(str) !== -1;
+ };
+
+ var isTop = has("top");
+ var isLeft = has("left");
+ var isRight = has("right");
+ var isBottom = has("bottom");
+ var isInside = has("inside");
+
+ var isAligned = isBottom || isLeft || isTop || isRight;
+
+ // early return
+ if (!isAligned && !isInside) return [0, 0];
+
+ var side = ax.side;
+
+ var u = isAligned ? (ax.tickwidth || 0) / 2 : 0;
+ var v = TEXTPAD;
+
+ var fontSize = ax.tickfont ? ax.tickfont.size : 12;
+ if (isBottom || isTop) {
+ u += fontSize * CAP_SHIFT;
+ v += (ax.linewidth || 0) / 2;
+ }
+ if (isLeft || isRight) {
+ u += (ax.linewidth || 0) / 2;
+ v += TEXTPAD;
+ }
+ if (isInside && side === "top") {
+ v -= fontSize * (1 - CAP_SHIFT);
+ }
+
+ if (isLeft || isTop) u = -u;
+ if (side === "bottom" || side === "right") v = -v;
+
+ return [isAligned ? u : 0, isInside ? v : 0];
}
/**
@@ -3043,20 +3303,20 @@ function getTickLabelUV(ax) {
* - {number (optional)} len tick length
* @return {string}
*/
-axes.makeTickPath = function(ax, shift, sgn, opts) {
- if(!opts) opts = {};
- var minor = opts.minor;
- if(minor && !ax.minor) return '';
+axes.makeTickPath = function (ax, shift, sgn, opts) {
+ if (!opts) opts = {};
+ var minor = opts.minor;
+ if (minor && !ax.minor) return "";
- var len = opts.len !== undefined ? opts.len :
- minor ? ax.minor.ticklen : ax.ticklen;
+ var len =
+ opts.len !== undefined ? opts.len : minor ? ax.minor.ticklen : ax.ticklen;
- var axLetter = ax._id.charAt(0);
- var pad = (ax.linewidth || 1) / 2;
+ 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);
+ return axLetter === "x"
+ ? "M0," + (shift + pad * sgn) + "v" + len * sgn
+ : "M" + (shift + pad * sgn) + ",0h" + len * sgn;
};
/**
@@ -3080,159 +3340,169 @@ axes.makeTickPath = function(ax, shift, sgn, opts) {
* - {number} labelStandoff (gap parallel to ticks)
* - {number} labelShift (gap perpendicular to ticks)
*/
-axes.makeLabelFns = function(ax, shift, angle) {
- var ticklabelposition = ax.ticklabelposition || '';
- var has = function(str) {
- return ticklabelposition.indexOf(str) !== -1;
- };
-
- var isTop = has('top');
- var isLeft = has('left');
- var isRight = has('right');
- var isBottom = has('bottom');
- var isAligned = isBottom || isLeft || isTop || isRight;
-
- var insideTickLabels = has('inside');
- var labelsOverTicks =
- (ticklabelposition === 'inside' && ax.ticks === 'inside') ||
- (!insideTickLabels && ax.ticks === 'outside' && ax.tickson !== 'boundaries');
-
- var labelStandoff = 0;
- var labelShift = 0;
-
- var tickLen = labelsOverTicks ? ax.ticklen : 0;
- if(insideTickLabels) {
- tickLen *= -1;
- } else if(isAligned) {
- tickLen = 0;
- }
-
- if(labelsOverTicks) {
- labelStandoff += tickLen;
- if(angle) {
- var rad = Lib.deg2rad(angle);
- labelStandoff = tickLen * Math.cos(rad) + 1;
- labelShift = tickLen * Math.sin(rad);
+axes.makeLabelFns = function (ax, shift, angle) {
+ var ticklabelposition = ax.ticklabelposition || "";
+ var has = function (str) {
+ return ticklabelposition.indexOf(str) !== -1;
+ };
+
+ var isTop = has("top");
+ var isLeft = has("left");
+ var isRight = has("right");
+ var isBottom = has("bottom");
+ var isAligned = isBottom || isLeft || isTop || isRight;
+
+ var insideTickLabels = has("inside");
+ var labelsOverTicks =
+ (ticklabelposition === "inside" && ax.ticks === "inside") ||
+ (!insideTickLabels &&
+ ax.ticks === "outside" &&
+ ax.tickson !== "boundaries");
+
+ var labelStandoff = 0;
+ var labelShift = 0;
+
+ var tickLen = labelsOverTicks ? ax.ticklen : 0;
+ if (insideTickLabels) {
+ tickLen *= -1;
+ } else if (isAligned) {
+ tickLen = 0;
+ }
+
+ if (labelsOverTicks) {
+ labelStandoff += tickLen;
+ if (angle) {
+ var rad = Lib.deg2rad(angle);
+ labelStandoff = tickLen * Math.cos(rad) + 1;
+ labelShift = tickLen * Math.sin(rad);
+ }
+ }
+
+ if (ax.showticklabels && (labelsOverTicks || ax.showline)) {
+ labelStandoff += 0.2 * ax.tickfont.size;
+ }
+ labelStandoff += ((ax.linewidth || 1) / 2) * (insideTickLabels ? -1 : 1);
+
+ var out = {
+ labelStandoff: labelStandoff,
+ labelShift: labelShift,
+ };
+
+ var x0, y0, ff, flipIt;
+ var xQ = 0;
+
+ var side = ax.side;
+ var axLetter = ax._id.charAt(0);
+ var tickangle = ax.tickangle;
+ var endSide;
+ if (axLetter === "x") {
+ endSide =
+ (!insideTickLabels && side === "bottom") ||
+ (insideTickLabels && side === "top");
+
+ flipIt = endSide ? 1 : -1;
+ if (insideTickLabels) flipIt *= -1;
+
+ x0 = labelShift * flipIt;
+ y0 = shift + labelStandoff * flipIt;
+ ff = endSide ? 1 : -0.2;
+ if (Math.abs(tickangle) === 90) {
+ if (insideTickLabels) {
+ ff += MID_SHIFT;
+ } else {
+ if (tickangle === -90 && side === "bottom") {
+ ff = CAP_SHIFT;
+ } else if (tickangle === 90 && side === "top") {
+ ff = MID_SHIFT;
+ } else {
+ ff = 0.5;
}
- }
+ }
- if(ax.showticklabels && (labelsOverTicks || ax.showline)) {
- labelStandoff += 0.2 * ax.tickfont.size;
+ xQ = (MID_SHIFT / 2) * (tickangle / 90);
}
- labelStandoff += (ax.linewidth || 1) / 2 * (insideTickLabels ? -1 : 1);
- var out = {
- labelStandoff: labelStandoff,
- labelShift: labelShift
+ out.xFn = function (d) {
+ return d.dx + x0 + xQ * d.fontSize;
};
+ out.yFn = function (d) {
+ return d.dy + y0 + d.fontSize * ff;
+ };
+ out.anchorFn = function (d, a) {
+ if (isAligned) {
+ if (isLeft) return "end";
+ if (isRight) return "start";
+ }
- var x0, y0, ff, flipIt;
- var xQ = 0;
-
- var side = ax.side;
- var axLetter = ax._id.charAt(0);
- var tickangle = ax.tickangle;
- var endSide;
- if(axLetter === 'x') {
- endSide =
- (!insideTickLabels && side === 'bottom') ||
- (insideTickLabels && side === 'top');
-
- flipIt = endSide ? 1 : -1;
- if(insideTickLabels) flipIt *= -1;
-
- x0 = labelShift * flipIt;
- y0 = shift + labelStandoff * flipIt;
- ff = endSide ? 1 : -0.2;
- if(Math.abs(tickangle) === 90) {
- if(insideTickLabels) {
- ff += MID_SHIFT;
- } else {
- if(tickangle === -90 && side === 'bottom') {
- ff = CAP_SHIFT;
- } else if(tickangle === 90 && side === 'top') {
- ff = MID_SHIFT;
- } else {
- ff = 0.5;
- }
- }
-
- xQ = (MID_SHIFT / 2) * (tickangle / 90);
- }
-
- out.xFn = function(d) { return d.dx + x0 + xQ * d.fontSize; };
- out.yFn = function(d) { return d.dy + y0 + d.fontSize * ff; };
- out.anchorFn = function(d, a) {
- if(isAligned) {
- if(isLeft) return 'end';
- if(isRight) return 'start';
- }
-
- if(!isNumeric(a) || a === 0 || a === 180) {
- return 'middle';
- }
-
- return ((a * flipIt < 0) !== insideTickLabels) ? 'end' : 'start'; - }; - out.heightFn = function(d, a, h) { - return (a < -60 || a> 60) ? -0.5 * h :
- ((ax.side === 'top') !== insideTickLabels) ? -h :
- 0;
- };
- } else if(axLetter === 'y') {
- endSide =
- (!insideTickLabels && side === 'left') ||
- (insideTickLabels && side === 'right');
-
- flipIt = endSide ? 1 : -1;
- if(insideTickLabels) flipIt *= -1;
+ if (!isNumeric(a) || a === 0 || a === 180) {
+ return "middle";
+ }
- x0 = labelStandoff;
- y0 = labelShift * flipIt;
+ return a * flipIt < 0 !== insideTickLabels ? "end" : "start"; + }; + out.heightFn = function (d, a, h) { + return a < -60 || a> 60
+ ? -0.5 * h
+ : (ax.side === "top") !== insideTickLabels
+ ? -h
+ : 0;
+ };
+ } else if (axLetter === "y") {
+ endSide =
+ (!insideTickLabels && side === "left") ||
+ (insideTickLabels && side === "right");
+
+ flipIt = endSide ? 1 : -1;
+ if (insideTickLabels) flipIt *= -1;
+
+ x0 = labelStandoff;
+ y0 = labelShift * flipIt;
+ ff = 0;
+ if (!insideTickLabels && Math.abs(tickangle) === 90) {
+ if (
+ (tickangle === -90 && side === "left") ||
+ (tickangle === 90 && side === "right")
+ ) {
+ ff = CAP_SHIFT;
+ } else {
+ ff = 0.5;
+ }
+ }
+
+ if (insideTickLabels) {
+ var ang = isNumeric(tickangle) ? +tickangle : 0;
+ if (ang !== 0) {
+ var rA = Lib.deg2rad(ang);
+ xQ = Math.abs(Math.sin(rA)) * CAP_SHIFT * flipIt;
ff = 0;
- if(!insideTickLabels && Math.abs(tickangle) === 90) {
- if(
- (tickangle === -90 && side === 'left') ||
- (tickangle === 90 && side === 'right')
- ) {
- ff = CAP_SHIFT;
- } else {
- ff = 0.5;
- }
- }
-
- if(insideTickLabels) {
- var ang = isNumeric(tickangle) ? +tickangle : 0;
- if(ang !== 0) {
- var rA = Lib.deg2rad(ang);
- xQ = Math.abs(Math.sin(rA)) * CAP_SHIFT * flipIt;
- ff = 0;
- }
- }
+ }
+ }
- out.xFn = function(d) { return d.dx + shift - (x0 + d.fontSize * ff) * flipIt + xQ * d.fontSize; };
- 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';
- }
+ out.xFn = function (d) {
+ return d.dx + shift - (x0 + d.fontSize * ff) * flipIt + xQ * d.fontSize;
+ };
+ 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 endSide ? 'end' : 'start';
- };
- out.heightFn = function(d, a, h) {
- if(ax.side === 'right') a *= -1;
+ return endSide ? "end" : "start";
+ };
+ out.heightFn = function (d, a, h) {
+ if (ax.side === "right") a *= -1;
- return a < -30 ? -h : - a < 30 ? -0.5 * h : - 0; - }; - } + return a < -30 ? -h : a < 30 ? -0.5 * h : 0; + }; + } - return out; + return out; }; function tickDataFn(d) { - return [d.text, d.x, d.axInfo, d.font, d.fontSize, d.fontColor].join('_'); + return [d.text, d.x, d.axInfo, d.font, d.fontSize, d.fontColor].join("_"); } /** @@ -3251,48 +3521,57 @@ function tickDataFn(d) { * - {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 vals = [] - .concat(ax.minor && ax.minor.ticks ? - // minor vals - opts.vals.filter(function(d) { return d.minor && !d.noTick; }) : - [] - ) - .concat(ax.ticks ? - // major vals - opts.vals.filter(function(d) { return !d.minor && !d.noTick; }) : - [] - ); +axes.drawTicks = function (gd, ax, opts) { + opts = opts || {}; + + var cls = ax._id + "tick"; + + var vals = [] + .concat( + ax.minor && ax.minor.ticks + ? // minor vals + opts.vals.filter(function (d) { + return d.minor && !d.noTick; + }) + : [] + ) + .concat( + ax.ticks + ? // major vals + opts.vals.filter(function (d) { + return !d.minor && !d.noTick; + }) + : [] + ); - var ticks = opts.layer.selectAll('path.' + cls) - .data(vals, tickDataFn); - - ticks.exit().remove(); - - ticks.enter().append('path') - .classed(cls, 1) - .classed('ticks', 1) - .classed('crisp', opts.crisp !== false) - .each(function(d) { - return Color.stroke(d3.select(this), d.minor ? ax.minor.tickcolor : ax.tickcolor); - }) - .style('stroke-px'; - }) - .attr('d', opts.path) - .style('display', null); // visible - - hideCounterAxisInsideTickLabels(ax, [TICK_PATH]); - - ticks.attr('transform', opts.transFn); + var ticks = opts.layer.selectAll("path." + cls).data(vals, tickDataFn); + + ticks.exit().remove(); + + ticks + .enter() + .append("path") + .classed(cls, 1) + .classed("ticks", 1) + .classed("crisp", opts.crisp !== false) + .each(function (d) { + return Color.stroke( + d3.select(this), + d.minor ? ax.minor.tickcolor : ax.tickcolor + ); + }) + .style("stroke-width", function (d) { + return ( + Drawing.crispRound(gd, d.minor ? ax.minor.tickwidth : ax.tickwidth, 1) + + "px" + ); + }) + .attr("d", opts.path) + .style("display", null); // visible + + hideCounterAxisInsideTickLabels(ax, [TICK_PATH]); + + ticks.attr("transform", opts.transFn); }; /** @@ -3317,80 +3596,90 @@ axes.drawTicks = function(gd, ax, opts) { * - {fn} transFn * - {boolean} crisp (set to false to unset crisp-edge SVG rendering) */ -axes.drawGrid = function(gd, ax, opts) { - opts = opts || {}; - - if(ax.tickmode === 'sync') { - // for tickmode sync we use the overlaying axis grid - return; - } - - var cls = ax._id + 'grid'; - - var hasMinor = ax.minor && ax.minor.showgrid; - var minorVals = hasMinor ? opts.vals.filter(function(d) { return d.minor; }) : []; - var majorVals = ax.showgrid ? opts.vals.filter(function(d) { return !d.minor; }) : []; - - var counterAx = opts.counterAxis; - if(counterAx && axes.shouldShowZeroLine(gd, ax, counterAx)) { - var isArrayMode = ax.tickmode === 'array'; - for(var i = 0; i < majorVals.length; i++) { - var xi = majorVals[i].x; - if(isArrayMode ? !xi : (Math.abs(xi) < ax.dtick / 100)) { - majorVals = majorVals.slice(0, i).concat(majorVals.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; - } - } - } +axes.drawGrid = function (gd, ax, opts) { + opts = opts || {}; + + if (ax.tickmode === "sync") { + // for tickmode sync we use the overlaying axis grid + return; + } + + var cls = ax._id + "grid"; + + var hasMinor = ax.minor && ax.minor.showgrid; + var minorVals = hasMinor + ? opts.vals.filter(function (d) { + return d.minor; + }) + : []; + var majorVals = ax.showgrid + ? opts.vals.filter(function (d) { + return !d.minor; + }) + : []; + + var counterAx = opts.counterAxis; + if (counterAx && axes.shouldShowZeroLine(gd, ax, counterAx)) { + var isArrayMode = ax.tickmode === "array"; + for (var i = 0; i < majorVals.length; i++) { + var xi = majorVals[i].x; + if (isArrayMode ? !xi : Math.abs(xi) < ax.dtick / 100) { + majorVals = majorVals.slice(0, i).concat(majorVals.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; + } + } + } + + ax._gw = Drawing.crispRound(gd, ax.gridwidth, 1); + + var wMinor = !hasMinor ? 0 : Drawing.crispRound(gd, ax.minor.gridwidth, 1); + + var majorLayer = opts.layer; + var minorLayer = opts.minorLayer; + for (var major = 1; major>= 0; major--) {
+ var layer = major ? majorLayer : minorLayer;
+ if (!layer) continue;
+
+ var grid = layer
+ .selectAll("path." + cls)
+ .data(major ? majorVals : minorVals, tickDataFn);
+
+ grid.exit().remove();
+
+ grid
+ .enter()
+ .append("path")
+ .classed(cls, 1)
+ .classed("crisp", opts.crisp !== false);
+
+ grid
+ .attr("transform", opts.transFn)
+ .attr("d", opts.path)
+ .each(function (d) {
+ return Color.stroke(
+ d3.select(this),
+ d.minor ? ax.minor.gridcolor : ax.gridcolor || "#ddd"
+ );
+ })
+ .style("stroke-dasharray", function (d) {
+ return Drawing.dashStyle(
+ d.minor ? ax.minor.griddash : ax.griddash,
+ d.minor ? ax.minor.gridwidth : ax.gridwidth
+ );
+ })
+ .style("stroke-width", function (d) {
+ return (d.minor ? wMinor : ax._gw) + "px";
+ })
+ .style("display", null); // visible
- ax._gw =
- Drawing.crispRound(gd, ax.gridwidth, 1);
-
- var wMinor = !hasMinor ? 0 :
- Drawing.crispRound(gd, ax.minor.gridwidth, 1);
-
- var majorLayer = opts.layer;
- var minorLayer = opts.minorLayer;
- for(var major = 1; major>= 0; major--) {
- var layer = major ? majorLayer : minorLayer;
- if(!layer) continue;
-
- var grid = layer.selectAll('path.' + cls)
- .data(major ? majorVals : minorVals, tickDataFn);
-
- grid.exit().remove();
-
- grid.enter().append('path')
- .classed(cls, 1)
- .classed('crisp', opts.crisp !== false);
-
- grid.attr('transform', opts.transFn)
- .attr('d', opts.path)
- .each(function(d) {
- return Color.stroke(d3.select(this), d.minor ?
- ax.minor.gridcolor :
- (ax.gridcolor || '#ddd')
- );
- })
- .style('stroke-dasharray', function(d) {
- return Drawing.dashStyle(
- d.minor ? ax.minor.griddash : ax.griddash,
- d.minor ? ax.minor.gridwidth : ax.gridwidth
- );
- })
- .style('stroke-width', function(d) {
- return (d.minor ? wMinor : ax._gw) + 'px';
- })
- .style('display', null); // visible
-
- if(typeof opts.path === 'function') grid.attr('d', opts.path);
- }
+ if (typeof opts.path === "function") grid.attr("d", opts.path);
+ }
- hideCounterAxisInsideTickLabels(ax, [GRID_PATH, MINORGRID_PATH]);
+ hideCounterAxisInsideTickLabels(ax, [GRID_PATH, MINORGRID_PATH]);
};
/**
@@ -3410,37 +3699,42 @@ axes.drawGrid = function(gd, ax, opts) {
* - {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 idSort(da.id, db.id);
- });
- });
+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 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')
- .style('display', null); // visible
+ 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"
+ )
+ .style("display", null); // visible
- hideCounterAxisInsideTickLabels(ax, [ZERO_PATH]);
+ hideCounterAxisInsideTickLabels(ax, [ZERO_PATH]);
};
/**
@@ -3467,467 +3761,499 @@ axes.drawZeroLine = function(gd, ax, opts) {
* + {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.filter(function(d) { return d.text; });
-
- 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);
- }
- });
-
- hideCounterAxisInsideTickLabels(ax, [TICK_TEXT]);
+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.filter(function (d) {
+ return d.text;
+ });
+
+ 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();
+ hideCounterAxisInsideTickLabels(ax, [TICK_TEXT]);
- if(opts.repositionOnUpdate) {
- tickLabels.each(function(d) {
- d3.select(this).select('text')
- .call(svgTextUtils.positionText, labelFns.xFn(d), labelFns.yFn(d));
- });
- }
+ tickLabels.exit().remove();
- 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 += strTranslate(0, anchorHeight);
- }
-
- if(mathjaxGroup.empty()) {
- var thisText = thisLabel.select('text');
- thisText.attr({
- transform: transform,
- 'text-anchor': anchor
- });
-
- thisText.style('opacity', 1); // visible
-
- if(ax._adjustTickLabelsOverflow) {
- ax._adjustTickLabelsOverflow();
- }
- } else {
- var mjWidth = Drawing.bBox(mathjaxGroup.node()).width;
- var mjShift = mjWidth * {end: -0.5, start: 0.5}[anchor];
- mathjaxGroup.attr('transform', transform + strTranslate(mjShift, 0));
- }
+ 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 += strTranslate(0, anchorHeight);
+ }
+
+ if (mathjaxGroup.empty()) {
+ var thisText = thisLabel.select("text");
+ thisText.attr({
+ transform: transform,
+ "text-anchor": anchor,
});
- }
-
- ax._adjustTickLabelsOverflow = function() {
- var ticklabeloverflow = ax.ticklabeloverflow;
- if(!ticklabeloverflow || ticklabeloverflow === 'allow') return;
- var hideOverflow = ticklabeloverflow.indexOf('hide') !== -1;
+ thisText.style("opacity", 1); // visible
- var isX = ax._id.charAt(0) === 'x';
- // div positions
- var p0 = 0;
- var p1 = isX ?
- gd._fullLayout.width :
- gd._fullLayout.height;
-
- if(ticklabeloverflow.indexOf('domain') !== -1) {
- // domain positions
- var rl = Lib.simpleMap(ax.range, ax.r2l);
- p0 = ax.l2p(rl[0]) + ax._offset;
- p1 = ax.l2p(rl[1]) + ax._offset;
+ if (ax._adjustTickLabelsOverflow) {
+ ax._adjustTickLabelsOverflow();
}
+ } else {
+ var mjWidth = Drawing.bBox(mathjaxGroup.node()).width;
+ var mjShift = mjWidth * { end: -0.5, start: 0.5 }[anchor];
+ mathjaxGroup.attr("transform", transform + strTranslate(mjShift, 0));
+ }
+ });
+ }
- var min = Math.min(p0, p1);
- var max = Math.max(p0, p1);
-
- var side = ax.side;
-
- var visibleLabelMin = Infinity;
- var visibleLabelMax = -Infinity;
-
- tickLabels.each(function(d) {
- var thisLabel = d3.select(this);
- var mathjaxGroup = thisLabel.select('.text-math-group');
-
- if(mathjaxGroup.empty()) {
- var bb = Drawing.bBox(thisLabel.node());
- var adjust = 0;
- if(isX) {
- if(bb.right> max) adjust = 1;
- else if(bb.left < min) adjust = 1; - } else { - if(bb.bottom> max) adjust = 1;
- else if(bb.top + (ax.tickangle ? 0 : d.fontSize / 4) < min) adjust = 1; - } - - var t = thisLabel.select('text'); - if(adjust) { - if(hideOverflow) t.style('opacity', 0); // hidden - } else { - t.style('opacity', 1); // visible - - if(side === 'bottom' || side === 'right') { - visibleLabelMin = Math.min(visibleLabelMin, isX ? bb.top : bb.left); - } else { - visibleLabelMin = -Infinity; - } - - if(side === 'top' || side === 'left') { - visibleLabelMax = Math.max(visibleLabelMax, isX ? bb.bottom : bb.right); - } else { - visibleLabelMax = Infinity; - } - } - } // TODO: hide mathjax? - }); + ax._adjustTickLabelsOverflow = function () { + var ticklabeloverflow = ax.ticklabeloverflow; + if (!ticklabeloverflow || ticklabeloverflow === "allow") return; - for(var subplot in fullLayout._plots) { - var plotinfo = fullLayout._plots[subplot]; - if(ax._id !== plotinfo.xaxis._id && ax._id !== plotinfo.yaxis._id) continue; - var anchorAx = isX ? plotinfo.yaxis : plotinfo.xaxis; - if(anchorAx) { - anchorAx['_visibleLabelMin_' + ax._id] = visibleLabelMin; - anchorAx['_visibleLabelMax_' + ax._id] = visibleLabelMax; - } - } - }; + var hideOverflow = ticklabeloverflow.indexOf("hide") !== -1; - ax._hideCounterAxisInsideTickLabels = function(partialOpts) { - var isX = ax._id.charAt(0) === 'x'; + var isX = ax._id.charAt(0) === "x"; + // div positions + var p0 = 0; + var p1 = isX ? gd._fullLayout.width : gd._fullLayout.height; - var anchoredAxes = []; - for(var subplot in fullLayout._plots) { - var plotinfo = fullLayout._plots[subplot]; - if(ax._id !== plotinfo.xaxis._id && ax._id !== plotinfo.yaxis._id) continue; - anchoredAxes.push(isX ? plotinfo.yaxis : plotinfo.xaxis); - } + if (ticklabeloverflow.indexOf("domain") !== -1) { + // domain positions + var rl = Lib.simpleMap(ax.range, ax.r2l); + p0 = ax.l2p(rl[0]) + ax._offset; + p1 = ax.l2p(rl[1]) + ax._offset; + } - anchoredAxes.forEach(function(anchorAx, idx) { - if(anchorAx && insideTicklabelposition(anchorAx)) { - (partialOpts || [ - ZERO_PATH, - MINORGRID_PATH, - GRID_PATH, - TICK_PATH, - TICK_TEXT - ]).forEach(function(e) { - var isPeriodLabel = - e.K === 'tick' && - e.L === 'text' && - ax.ticklabelmode === 'period'; - - var mainPlotinfo = fullLayout._plots[ax._mainSubplot]; - - var sel; - if(e.K === ZERO_PATH.K) sel = mainPlotinfo.zerolinelayer.selectAll('.' + ax._id + 'zl'); - else if(e.K === MINORGRID_PATH.K) sel = mainPlotinfo.minorGridlayer.selectAll('.' + ax._id); - else if(e.K === GRID_PATH.K) sel = mainPlotinfo.gridlayer.selectAll('.' + ax._id); - else sel = mainPlotinfo[ax._id.charAt(0) + 'axislayer']; - - sel.each(function() { - var w = d3.select(this); - if(e.L) w = w.selectAll(e.L); - - w.each(function(d) { - var q = ax.l2p( - isPeriodLabel ? getPosX(d) : d.x - ) + ax._offset; - - var t = d3.select(this); - if( - q < ax['_visibleLabelMax_' + anchorAx._id] && - q> ax['_visibleLabelMin_' + anchorAx._id]
- ) {
- t.style('display', 'none'); // hidden
- } else if(e.K === 'tick' && !idx) {
- t.style('display', null); // visible
- }
- });
- });
- });
- }
- });
- };
+ var min = Math.min(p0, p1);
+ var max = Math.max(p0, p1);
- // 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);
+ var side = ax.side;
- function allLabelsReady() {
- return labelsReady.length && Promise.all(labelsReady);
- }
+ var visibleLabelMin = Infinity;
+ var visibleLabelMax = -Infinity;
- 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
- });
- });
+ tickLabels.each(function (d) {
+ var thisLabel = d3.select(this);
+ var mathjaxGroup = thisLabel.select(".text-math-group");
- 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 ticklabelposition = ax.ticklabelposition || ''; - var has = function(str) { - return ticklabelposition.indexOf(str) !== -1; - }; - var isTop = has('top'); - var isLeft = has('left'); - var isRight = has('right'); - var isBottom = has('bottom'); - var isAligned = isBottom || isLeft || isTop || isRight; - var pad = !isAligned ? 0 : - (ax.tickwidth || 0) + 2 * TEXTPAD; - - var rotate90 = (tickSpacing < maxFontSize * 2.5) || ax.type === 'multicategory' || ax._name === 'realaxis'; - - // 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], pad)) { - autoangle = rotate90 ? 90 : 30; - break; - } - } - } - - if(autoangle) { - positionLabels(tickLabels, autoangle); - } + if (mathjaxGroup.empty()) { + var bb = Drawing.bBox(thisLabel.node()); + var adjust = 0; + if (isX) { + if (bb.right> max) adjust = 1;
+ else if (bb.left < min) adjust = 1; + } else { + if (bb.bottom> max) adjust = 1;
+ else if (bb.top + (ax.tickangle ? 0 : d.fontSize / 4) < min) + adjust = 1; } - } - - 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); - } + var t = thisLabel.select("text"); + if (adjust) { + if (hideOverflow) t.style("opacity", 0); // hidden + } else { + t.style("opacity", 1); // visible + + if (side === "bottom" || side === "right") { + visibleLabelMin = Math.min(visibleLabelMin, isX ? bb.top : bb.left); + } else { + visibleLabelMin = -Infinity; + } + + if (side === "top" || side === "left") { + visibleLabelMax = Math.max( + visibleLabelMax, + isX ? bb.bottom : bb.right + ); + } else { + visibleLabelMax = Infinity; + } + } + } // TODO: hide mathjax? + }); - // save current tick angle for future redraws - if(ax._tickAngles) { - seq.push(function() { - ax._tickAngles[cls] = autoangle === null ? - (isNumeric(tickAngle) ? tickAngle : 0) : - autoangle; - }); + for (var subplot in fullLayout._plots) { + var plotinfo = fullLayout._plots[subplot]; + if (ax._id !== plotinfo.xaxis._id && ax._id !== plotinfo.yaxis._id) + continue; + var anchorAx = isX ? plotinfo.yaxis : plotinfo.xaxis; + if (anchorAx) { + anchorAx["_visibleLabelMin_" + ax._id] = visibleLabelMin; + anchorAx["_visibleLabelMax_" + ax._id] = visibleLabelMax; + } } + }; - var computeTickLabelBoundingBoxes = function() { - var labelsMaxW = 0; - var labelsMaxH = 0; - tickLabels.each(function(d, i) { - var thisLabel = selectTickLabel(this); - var mathjaxGroup = thisLabel.select('.text-math-group'); - - if(mathjaxGroup.empty()) { - var bb; + ax._hideCounterAxisInsideTickLabels = function (partialOpts) { + var isX = ax._id.charAt(0) === "x"; - if(ax._vals[i]) { - bb = ax._vals[i].bb || Drawing.bBox(thisLabel.node()); - ax._vals[i].bb = bb; - } + var anchoredAxes = []; + for (var subplot in fullLayout._plots) { + var plotinfo = fullLayout._plots[subplot]; + if (ax._id !== plotinfo.xaxis._id && ax._id !== plotinfo.yaxis._id) + continue; + anchoredAxes.push(isX ? plotinfo.yaxis : plotinfo.xaxis); + } - labelsMaxW = Math.max(labelsMaxW, bb.width); - labelsMaxH = Math.max(labelsMaxH, bb.height); - } + anchoredAxes.forEach(function (anchorAx, idx) { + if (anchorAx && insideTicklabelposition(anchorAx)) { + ( + partialOpts || [ + ZERO_PATH, + MINORGRID_PATH, + GRID_PATH, + TICK_PATH, + TICK_TEXT, + ] + ).forEach(function (e) { + var isPeriodLabel = + e.K === "tick" && e.L === "text" && ax.ticklabelmode === "period"; + + var mainPlotinfo = fullLayout._plots[ax._mainSubplot]; + + var sel; + if (e.K === ZERO_PATH.K) + sel = mainPlotinfo.zerolinelayer.selectAll("." + ax._id + "zl"); + else if (e.K === MINORGRID_PATH.K) + sel = mainPlotinfo.minorGridlayer.selectAll("." + ax._id); + else if (e.K === GRID_PATH.K) + sel = mainPlotinfo.gridlayer.selectAll("." + ax._id); + else sel = mainPlotinfo[ax._id.charAt(0) + "axislayer"]; + + sel.each(function () { + var w = d3.select(this); + if (e.L) w = w.selectAll(e.L); + + w.each(function (d) { + var q = ax.l2p(isPeriodLabel ? getPosX(d) : d.x) + ax._offset; + + var t = d3.select(this); + if ( + q < ax["_visibleLabelMax_" + anchorAx._id] && + q> ax["_visibleLabelMin_" + anchorAx._id]
+ ) {
+ t.style("display", "none"); // hidden
+ } else if (e.K === "tick" && !idx) {
+ t.style("display", null); // visible
+ }
+ });
+ });
});
-
- return {
- labelsMaxW: labelsMaxW,
- labelsMaxH: labelsMaxH
- };
- };
-
- var anchorAx = ax._anchorAxis;
- if(
- anchorAx && (anchorAx.autorange || anchorAx.insiderange) &&
- insideTicklabelposition(ax) &&
- !isLinked(fullLayout, ax._id)
+ }
+ });
+ };
+
+ // 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")
) {
- if(!fullLayout._insideTickLabelsUpdaterange) {
- fullLayout._insideTickLabelsUpdaterange = {};
+ 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; + } } - - if(anchorAx.autorange) { - fullLayout._insideTickLabelsUpdaterange[anchorAx._name + '.autorange'] = anchorAx.autorange; - - seq.push(computeTickLabelBoundingBoxes); + } else { + var vLen = vals.length; + var tickSpacing = + Math.abs((vals[vLen - 1].x - vals[0].x) * ax._m) / (vLen - 1); + + var ticklabelposition = ax.ticklabelposition || ""; + var has = function (str) { + return ticklabelposition.indexOf(str) !== -1; + }; + var isTop = has("top"); + var isLeft = has("left"); + var isRight = has("right"); + var isBottom = has("bottom"); + var isAligned = isBottom || isLeft || isTop || isRight; + var pad = !isAligned ? 0 : (ax.tickwidth || 0) + 2 * TEXTPAD; + + var rotate90 = + tickSpacing < maxFontSize * 2.5 || + ax.type === "multicategory" || + ax._name === "realaxis"; + + // 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], pad)) { + 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; + }); + } - if(anchorAx.insiderange) { - var BBs = computeTickLabelBoundingBoxes(); - var move = ax._id.charAt(0) === 'y' ? - BBs.labelsMaxW : - BBs.labelsMaxH; - - move += 2 * TEXTPAD; - - if(ax.ticklabelposition === 'inside') { - move += ax.ticklen || 0; - } - - var sgn = (ax.side === 'right' || ax.side === 'top') ? 1 : -1; - var index = sgn === 1 ? 1 : 0; - var otherIndex = sgn === 1 ? 0 : 1; - - var newRange = []; - newRange[otherIndex] = anchorAx.range[otherIndex]; - - var p0 = anchorAx.d2p(anchorAx.range[index]); - var p1 = anchorAx.d2p(anchorAx.range[otherIndex]); - var dist = Math.abs(p1 - p0); - if(dist - move> 0) {
- dist -= move;
- move *= 1 + move / dist;
- } else {
- move = 0;
- }
-
- if(ax._id.charAt(0) !== 'y') move = -move;
+ var computeTickLabelBoundingBoxes = function () {
+ var labelsMaxW = 0;
+ var labelsMaxH = 0;
+ tickLabels.each(function (d, i) {
+ var thisLabel = selectTickLabel(this);
+ var mathjaxGroup = thisLabel.select(".text-math-group");
- newRange[index] = anchorAx.p2d(
- anchorAx.d2p(anchorAx.range[index]) +
- sgn * move
- );
+ if (mathjaxGroup.empty()) {
+ var bb;
- // handle partial ranges in insiderange
- if(
- anchorAx.autorange === 'min' ||
- anchorAx.autorange === 'max reversed'
- ) {
- newRange[0] = null;
-
- anchorAx._rangeInitial0 = undefined;
- anchorAx._rangeInitial1 = undefined;
- } else if(
- anchorAx.autorange === 'max' ||
- anchorAx.autorange === 'min reversed'
- ) {
- newRange[1] = null;
-
- anchorAx._rangeInitial0 = undefined;
- anchorAx._rangeInitial1 = undefined;
- }
-
- fullLayout._insideTickLabelsUpdaterange[anchorAx._name + '.range'] = newRange;
+ if (ax._vals[i]) {
+ bb = ax._vals[i].bb || Drawing.bBox(thisLabel.node());
+ ax._vals[i].bb = bb;
}
- }
- var done = Lib.syncOrAsync(seq);
- if(done && done.then) gd._promises.push(done);
- return done;
+ labelsMaxW = Math.max(labelsMaxW, bb.width);
+ labelsMaxH = Math.max(labelsMaxH, bb.height);
+ }
+ });
+
+ return {
+ labelsMaxW: labelsMaxW,
+ labelsMaxH: labelsMaxH,
+ };
+ };
+
+ var anchorAx = ax._anchorAxis;
+ if (
+ anchorAx &&
+ (anchorAx.autorange || anchorAx.insiderange) &&
+ insideTicklabelposition(ax) &&
+ !isLinked(fullLayout, ax._id)
+ ) {
+ if (!fullLayout._insideTickLabelsUpdaterange) {
+ fullLayout._insideTickLabelsUpdaterange = {};
+ }
+
+ if (anchorAx.autorange) {
+ fullLayout._insideTickLabelsUpdaterange[anchorAx._name + ".autorange"] =
+ anchorAx.autorange;
+
+ seq.push(computeTickLabelBoundingBoxes);
+ }
+
+ if (anchorAx.insiderange) {
+ var BBs = computeTickLabelBoundingBoxes();
+ var move = ax._id.charAt(0) === "y" ? BBs.labelsMaxW : BBs.labelsMaxH;
+
+ move += 2 * TEXTPAD;
+
+ if (ax.ticklabelposition === "inside") {
+ move += ax.ticklen || 0;
+ }
+
+ var sgn = ax.side === "right" || ax.side === "top" ? 1 : -1;
+ var index = sgn === 1 ? 1 : 0;
+ var otherIndex = sgn === 1 ? 0 : 1;
+
+ var newRange = [];
+ newRange[otherIndex] = anchorAx.range[otherIndex];
+
+ var p0 = anchorAx.d2p(anchorAx.range[index]);
+ var p1 = anchorAx.d2p(anchorAx.range[otherIndex]);
+ var dist = Math.abs(p1 - p0);
+ if (dist - move> 0) {
+ dist -= move;
+ move *= 1 + move / dist;
+ } else {
+ move = 0;
+ }
+
+ if (ax._id.charAt(0) !== "y") move = -move;
+
+ newRange[index] = anchorAx.p2d(
+ anchorAx.d2p(anchorAx.range[index]) + sgn * move
+ );
+
+ // handle partial ranges in insiderange
+ if (
+ anchorAx.autorange === "min" ||
+ anchorAx.autorange === "max reversed"
+ ) {
+ newRange[0] = null;
+
+ anchorAx._rangeInitial0 = undefined;
+ anchorAx._rangeInitial1 = undefined;
+ } else if (
+ anchorAx.autorange === "max" ||
+ anchorAx.autorange === "min reversed"
+ ) {
+ newRange[1] = null;
+
+ anchorAx._rangeInitial0 = undefined;
+ anchorAx._rangeInitial1 = undefined;
+ }
+
+ fullLayout._insideTickLabelsUpdaterange[anchorAx._name + ".range"] =
+ newRange;
+ }
+ }
+
+ var done = Lib.syncOrAsync(seq);
+ if (done && done.then) gd._promises.push(done);
+ return done;
};
/**
@@ -3946,23 +4272,28 @@ axes.drawLabels = function(gd, ax, opts) {
* - {fn} transFn
*/
function drawDividers(gd, ax, opts) {
- var cls = ax._id + 'divider';
- var vals = opts.vals;
+ var cls = ax._id + "divider";
+ var vals = opts.vals;
- var dividers = opts.layer.selectAll('path.' + cls)
- .data(vals, tickDataFn);
+ var dividers = opts.layer.selectAll("path." + cls).data(vals, tickDataFn);
+ if (ax.type === "multicategory") {
+ if (opts.level === 0) {
+ dividers.exit().remove();
+ }
+ } else {
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
+ .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);
+ dividers.attr("transform", opts.transFn).attr("d", opts.path);
}
/**
@@ -3979,31 +4310,31 @@ function drawDividers(gd, ax, opts) {
* - {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 + ax._shift,
- _length: 0
- };
- }
+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 + ax._shift,
+ _length: 0,
+ };
+ }
- if(side === 'top' || side === 'left') {
- return anchorAxis._offset;
- } else if(side === 'bottom' || side === 'right') {
- return anchorAxis._offset + anchorAxis._length;
- }
+ if (side === "top" || side === "left") {
+ return anchorAxis._offset;
+ } else if (side === "bottom" || side === "right") {
+ return anchorAxis._offset + anchorAxis._length;
+ }
};
/**
@@ -4016,17 +4347,15 @@ axes.getPxPosition = function(gd, ax) {
* @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;
- }
+ 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;
+ }
}
/**
@@ -4047,188 +4376,198 @@ function approxTitleDepth(ax) {
* - {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);
+ 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 {
+ var isInside = insideTicklabelposition(ax);
+
+ if (ax.type === "multicategory") {
+ titleStandoff = ax._depth;
} else {
- var isInside = insideTicklabelposition(ax);
-
- if(ax.type === 'multicategory') {
- titleStandoff = ax._depth;
- } else {
- var offsetBase = 1.5 * fontSize;
- if(isInside) {
- offsetBase = 0.5 * fontSize;
- if(ax.ticks === 'outside') {
- offsetBase += ax.ticklen;
- }
- }
- titleStandoff = 10 + offsetBase + (ax.linewidth ? ax.linewidth - 1 : 0);
- }
-
- if(!isInside) {
- 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 offsetBase = 1.5 * fontSize;
+ if (isInside) {
+ offsetBase = 0.5 * fontSize;
+ if (ax.ticks === "outside") {
+ offsetBase += ax.ticklen;
}
- }
-
- var pos = axes.getPxPosition(gd, ax);
- var transform, x, y;
+ }
+ titleStandoff = 10 + offsetBase + (ax.linewidth ? ax.linewidth - 1 : 0);
+ }
+
+ if (!isInside) {
+ 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(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};
+ if (tickLabels && tickLabels.node() && tickLabels.node().parentNode) {
+ var translation = Drawing.getTranslate(tickLabels.node().parentNode);
+ avoid.offsetLeft = translation.x;
+ avoid.offsetTop = translation.y;
}
- 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;
- }
+ if (ax.title.hasOwnProperty("standoff")) {
+ avoid.pad = 0;
}
+ }
- ax._titleStandoff = titleStandoff;
+ ax._titleStandoff = titleStandoff;
- 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'}
- });
+ 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 === '-') && - !(ax.rangebreaks && ax.maskBreaks(0) === BADNUM) && - ( - clipEnds(ax, 0) || - !anyCounterAxLineAtZero(gd, ax, counterAxis, rng) || - hasBarsOrFill(gd, ax) - ) - ); +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 === "-") && + !(ax.rangebreaks && ax.maskBreaks(0) === BADNUM) && + (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); }); +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); + 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 - ); + var mainCounterAxis = counterAxis._mainAxis; + if (!mainCounterAxis) return; - function lineNearZero(ax2) { - if(!ax2.showline || !ax2.linewidth) return false; - var tolerance = Math.max((ax2.linewidth + ax.zerolinewidth) / 2, 1); + var fullLayout = gd._fullLayout; + var axLetter = ax._id.charAt(0); + var counterLetter = axes.counterLetter(ax._id); - function closeEnough(pos2) { - return typeof pos2 === 'number' && Math.abs(pos2 - zeroPosition) < tolerance; - } + var zeroPosition = + ax._offset + + (Math.abs(rng[0]) < Math.abs(rng[1]) === (axLetter === "x") + ? 0 + : ax._length); - 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; - } - } - } + function lineNearZero(ax2) { + if (!ax2.showline || !ax2.linewidth) return false; + var tolerance = Math.max((ax2.linewidth + ax.zerolinewidth) / 2, 1); - var plotinfo = fullLayout._plots[counterAxis._mainSubplot]; - if(!(plotinfo.mainplotinfo || plotinfo).overlays.length) { - return lineNearZero(counterAxis, zeroPosition); + function closeEnough(pos2) { + return ( + typeof pos2 === "number" && Math.abs(pos2 - zeroPosition) < tolerance + ); } - 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; - } + 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; - } + 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; + } + return false; } function selectTickLabel(gTick) { - var s = d3.select(gTick); - var mj = s.select('.text-math-group'); - return mj.empty() ? s.select('text') : mj; + var s = d3.select(gTick); + var mj = s.select(".text-math-group"); + return mj.empty() ? s.select("text") : mj; } /** @@ -4240,215 +4579,245 @@ function selectTickLabel(gTick) { * 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)); - } - } +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'; } +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); +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); - } + 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); - } - } + var groups = []; + var i, j; - if(!groupsi.length) { - groups.push({x: [xi], y: [yi]}); - continue; - } + 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? - var group0 = groups[groupsi[0]]; - var groupj; + 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> 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]); + 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; + return groups; } function mergeAxisGroups(intoSet, fromSet) { - for(var i = 0; i < fromSet.length; i++) { - if(intoSet.indexOf(fromSet[i]) === -1) intoSet.push(fromSet[i]); - } + 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], ['?']); - } - } + 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); - } + // 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'; + return ax._id === "angularaxis"; } function moveOutsideBreak(v, ax) { - var len = ax._rangebreaks.length; - for(var k = 0; k < len; k++) { - var brk = ax._rangebreaks[k]; - if(v>= brk.min && v < brk.max) { - return brk.max; - } - } - return v; + var len = ax._rangebreaks.length; + for (var k = 0; k < len; k++) { + var brk = ax._rangebreaks[k]; + if (v>= brk.min && v < brk.max) {
+ return brk.max;
+ }
+ }
+ return v;
}
function insideTicklabelposition(ax) {
- return ((ax.ticklabelposition || '').indexOf('inside') !== -1);
+ return (ax.ticklabelposition || "").indexOf("inside") !== -1;
}
function hideCounterAxisInsideTickLabels(ax, opts) {
- if(insideTicklabelposition(ax._anchorAxis || {})) {
- if(ax._hideCounterAxisInsideTickLabels) {
- ax._hideCounterAxisInsideTickLabels(opts);
- }
+ if (insideTicklabelposition(ax._anchorAxis || {})) {
+ if (ax._hideCounterAxisInsideTickLabels) {
+ ax._hideCounterAxisInsideTickLabels(opts);
}
+ }
}
function incrementShift(ax, shiftVal, axShifts, normalize) {
- // Need to set 'overlay' for anchored axis
- var overlay = ((ax.anchor !== 'free') && ((ax.overlaying === undefined) || (ax.overlaying === false))) ? ax._id : ax.overlaying;
- var shiftValAdj;
- if(normalize) {
- shiftValAdj = ax.side === 'right' ? shiftVal : -shiftVal;
- } else {
- shiftValAdj = shiftVal;
- }
- if(!(overlay in axShifts)) {
- axShifts[overlay] = {};
- }
- if(!(ax.side in axShifts[overlay])) {
- axShifts[overlay][ax.side] = 0;
- }
- axShifts[overlay][ax.side] += shiftValAdj;
+ // Need to set 'overlay' for anchored axis
+ var overlay =
+ ax.anchor !== "free" &&
+ (ax.overlaying === undefined || ax.overlaying === false)
+ ? ax._id
+ : ax.overlaying;
+ var shiftValAdj;
+ if (normalize) {
+ shiftValAdj = ax.side === "right" ? shiftVal : -shiftVal;
+ } else {
+ shiftValAdj = shiftVal;
+ }
+ if (!(overlay in axShifts)) {
+ axShifts[overlay] = {};
+ }
+ if (!(ax.side in axShifts[overlay])) {
+ axShifts[overlay][ax.side] = 0;
+ }
+ axShifts[overlay][ax.side] += shiftValAdj;
}
function setShiftVal(ax, axShifts) {
- return ax.autoshift ?
- axShifts[ax.overlaying][ax.side] :
- (ax.shift || 0);
+ return ax.autoshift ? axShifts[ax.overlaying][ax.side] : ax.shift || 0;
}
diff --git a/src/plots/cartesian/set_convert.js b/src/plots/cartesian/set_convert.js
index 14f17f9c2aa..8c167206b5d 100644
--- a/src/plots/cartesian/set_convert.js
+++ b/src/plots/cartesian/set_convert.js
@@ -156,9 +156,13 @@ module.exports = function setConvert(ax, fullLayout) {
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]);
+ var vs = [];
+ for(var j = 0; j < ax.levelNr; j++) {
+ vs.push((arrayIn[j] || [])[i]);
+ }
+ // var v0 = (arrayIn[0] || [])[i];
+ // var v1 = (arrayIn[1] || [])[i];
+ arrayOut[i] = getCategoryIndex(vs);
}
return arrayOut;
@@ -333,6 +337,7 @@ module.exports = function setConvert(ax, fullLayout) {
// 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.
+ var sortLib = require('../../lib/sort_traces');
ax.r2d = ax.c2d = ax.l2d = getCategoryName;
ax.d2r = ax.d2l_noadd = getCategoryPosition;
@@ -357,9 +362,14 @@ module.exports = function setConvert(ax, fullLayout) {
return ensureNumber(v);
};
- ax.setupMultiCategory = function(fullData) {
+ ax.setupMultiCategory = function(gd) {
+ var fullData = gd._fullData;
+ // axes_test should set up category maps correctly for multicategory axes
+ if(!fullData) {
+ fullData = gd;
+ }
var traceIndices = ax._traceIndices;
- var i, j;
+ var i;
var group = ax._matchGroup;
if(group && ax._categories.length === 0) {
@@ -371,49 +381,99 @@ module.exports = function setConvert(ax, fullLayout) {
}
}
- // [ [cnt, {$cat: index}], for 1,2 ]
- var seen = [[0, {}], [0, {}]];
- // [ [arrayIn[0][i], arrayIn[1][i]], for i .. N ]
- var list = [];
-
+ var axLabels = [];
+ var fullObjectList = [];
+ var cols = [];
+ // Don't think that the trace should be drawn at all if the lengths don't match. Removing the arrays length check. It is better to fail loudly than silently.
for(i = 0; i < traceIndices.length; i++) {
var trace = fullData[traceIndices[i]];
+ cols = [];
+
+ for(var k = 0; k < fullData[traceIndices[0]][axLetter].length; k++) {
+ cols.push('col' + k.toString());
+ }
+ if(cols.length < 2) {
+ return;
+ }
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]++;
- }
+ if(isArrayOrTypedArray(arrayIn[0])) {
+ var arrays = arrayIn.map(function(x) {
+ return x;
+ });
+ var valLetter;
+ if(trace.type === 'ohlc' | trace.type === 'candlestick') {
+ var t = trace;
+ var valsTransform = sortLib.transpose([t.open, t.high, t.low, t.close]);
+ arrays.push(valsTransform);
+ } else if(trace.z) {
+ if(axLetter === 'x') {
+ arrays.push(sortLib.transpose(trace.z));
+ } else {
+ arrays.push(trace.z);
}
+ valLetter = 'z';
+ } else if(axLetter === 'y' && trace.x) {
+ arrays.push(trace.x);
+ valLetter = 'x';
+ } else if(trace.y) {
+ arrays.push(trace.y);
+ valLetter = 'y';
+ } else {
+ var nullArray = arrayIn[0].map(function() {return null;});
+ arrays.push(nullArray);
+ }
+ var objList = sortLib.matrixToObjectList(arrays, cols);
+
+ Array.prototype.push.apply(fullObjectList, objList);
+
+ // convert the trace data from list to object and sort (backwards, stable sort)
+ var sortedObjectList = sortLib.sortObjectList(cols, objList);
+ var matrix = sortLib.objectListToList(sortedObjectList);
+ var sortedMatrix = sortLib.sortedMatrix(matrix);
+
+ axLabels = sortedMatrix[0].slice();
+ var axVals = sortedMatrix[1];
+
+ if(valLetter === 'z' & axLetter === 'x') {
+ axVals = sortLib.transpose(axVals);
+ }
+
+ if(trace.type === 'ohlc' | trace.type === 'candlestick') {
+ var sortedValsTransform = sortLib.transpose(axVals);
+ gd._fullData[i].open = sortedValsTransform[0];
+ gd._fullData[i].high = sortedValsTransform[1];
+ gd._fullData[i].low = sortedValsTransform[2];
+ gd._fullData[i].close = sortedValsTransform[3];
+ }
+ // Could/should set sorted y axis values for each trace as the sorted values are already available.
+ // Need write access to gd._fullData, bad? Should probably be done right at newPlot, or on setting gd._fullData
+
+ var transposedAxLabels = sortLib.transpose(axLabels);
+ if(gd._fullData) {
+ gd._fullData[i][axLetter] = transposedAxLabels;
+ }
+ if(valLetter) {
+ gd._fullData[i][valLetter] = axVals;
}
}
}
}
- list.sort(function(a, b) {
- var ind0 = seen[0][1];
- var d = ind0[a[0]] - ind0[b[0]];
- if(d) return d;
+ if(axLabels.length) {
+ ax.levelNr = axLabels[0].length;
+ ax.levels = axLabels[0].map(function(_, idx) {return idx;});
- var ind1 = seen[1][1];
- return ind1[a[1]] - ind1[b[1]];
- });
+ var fullSortedObjectList = sortLib.sortObjectList(cols, fullObjectList.slice());
+ var fullList = sortLib.objectListToList(fullSortedObjectList);
+ var fullSortedMatrix = sortLib.sortedMatrix(fullList, true);
- for(i = 0; i < list.length; i++) {
- setCategoryIndex(list[i]);
+ var fullXs = fullSortedMatrix[0].slice();
+
+ for(i = 0; i < fullXs.length; i++) {
+ setCategoryIndex(fullXs[i]);
+ }
}
};
}
diff --git a/src/plots/plots.js b/src/plots/plots.js
index 5a6b5e0466c..b32d05bd9e1 100644
--- a/src/plots/plots.js
+++ b/src/plots/plots.js
@@ -3136,7 +3136,7 @@ plots.doCalcdata = function(gd, traces) {
calcdata[i] = cd;
}
- setupAxisCategories(axList, fullData, fullLayout);
+ setupAxisCategories(axList, gd, fullLayout);
// 'transform' loop - must calc container traces first
// so that if their dependent traces can get transform properly
@@ -3144,7 +3144,7 @@ plots.doCalcdata = function(gd, traces) {
for(i = 0; i < fullData.length; i++) transformCalci(i);
// clear stuff that should recomputed in 'regular' loop
- if(hasCalcTransform) setupAxisCategories(axList, fullData, fullLayout);
+ if(hasCalcTransform) setupAxisCategories(axList, gd, fullLayout);
// 'regular' loop - make sure container traces (eg carpet) calc before
// contained traces (eg contourcarpet)
@@ -3352,13 +3352,13 @@ function sortAxisCategoriesByValue(axList, gd) {
return affectedTraces;
}
-function setupAxisCategories(axList, fullData, fullLayout) {
+function setupAxisCategories(axList, gd, fullLayout) {
var axLookup = {};
function setupOne(ax) {
ax.clearCalc();
if(ax.type === 'multicategory') {
- ax.setupMultiCategory(fullData);
+ ax.setupMultiCategory(gd);
}
axLookup[ax._id] = 1;
diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js
index 165b90912f5..63ce83e9b53 100644
--- a/test/jasmine/tests/hover_label_test.js
+++ b/test/jasmine/tests/hover_label_test.js
@@ -6010,6 +6010,7 @@ describe('hovermode: (x|y)unified', function() {
var mockOhlc = require('../../image/mocks/finance_multicategory.json');
var mockCopy = Lib.extendDeep({}, mockOhlc);
mockCopy.layout.hovermode = 'x unified';
+
Plotly.newPlot(gd, mockCopy)
.then(function(gd) {
_hover(gd, {curveNumber: 0, pointNumber: 0});