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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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: "height", y: "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: 'height', y: 'width'}[axLetter];
- var standoff = getLabelLevelBbox()[bboxKey] + pad +
- (ax._tickAngles[axId + 'tick'] ? ax.tickfont.size * LINE_SPACING : 0);
-
- return axes.drawLabels(gd, ax, {
- vals: getSecondaryLabelVals(ax, vals),
- layer: mainAxLayer,
- cls: axId + 'tick2',
- repositionOnUpdate: true,
- secondary: true,
- transFn: 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-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);
+ 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});