Skip to content

Commit 07d73df

Browse files
authored
Merge pull request #3300 from plotly/multicategory
Multicategory axis type
2 parents 1fdfc73 + 4b82426 commit 07d73df

File tree

76 files changed

+2251
-577
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+2251
-577
lines changed

src/components/legend/defaults.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ module.exports = function legendDefaults(layoutIn, layoutOut, fullData) {
8686
coerce('orientation');
8787
if(containerOut.orientation === 'h') {
8888
var xaxis = layoutIn.xaxis;
89-
if(xaxis && xaxis.rangeslider && xaxis.rangeslider.visible) {
89+
if(Registry.getComponentMethod('rangeslider', 'isVisible')(xaxis)) {
9090
defaultX = 0;
9191
defaultXAnchor = 'left';
9292
defaultY = 1.1;

src/components/rangeslider/draw.js

+25-53
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,21 @@ var Color = require('../color');
1919
var Titles = require('../titles');
2020

2121
var Cartesian = require('../../plots/cartesian');
22-
var Axes = require('../../plots/cartesian/axes');
22+
var axisIDs = require('../../plots/cartesian/axis_ids');
2323

2424
var dragElement = require('../dragelement');
2525
var setCursor = require('../../lib/setcursor');
2626

2727
var constants = require('./constants');
2828

2929
module.exports = function(gd) {
30-
var fullLayout = gd._fullLayout,
31-
rangeSliderData = makeRangeSliderData(fullLayout);
30+
var fullLayout = gd._fullLayout;
31+
var rangeSliderData = fullLayout._rangeSliderData;
32+
for(var i = 0; i < rangeSliderData.length; i++) {
33+
var opts = rangeSliderData[i][constants.name];
34+
// fullLayout._uid may not exist when we call makeData
35+
opts._clipId = opts._id + '-' + fullLayout._uid;
36+
}
3237

3338
/*
3439
* <g container />
@@ -55,10 +60,6 @@ module.exports = function(gd) {
5560
.selectAll('g.' + constants.containerClassName)
5661
.data(rangeSliderData, keyFunction);
5762

58-
rangeSliders.enter().append('g')
59-
.classed(constants.containerClassName, true)
60-
.attr('pointer-events', 'all');
61-
6263
// remove exiting sliders and their corresponding clip paths
6364
rangeSliders.exit().each(function(axisOpts) {
6465
var opts = axisOpts[constants.name];
@@ -68,12 +69,16 @@ module.exports = function(gd) {
6869
// return early if no range slider is visible
6970
if(rangeSliderData.length === 0) return;
7071

72+
rangeSliders.enter().append('g')
73+
.classed(constants.containerClassName, true)
74+
.attr('pointer-events', 'all');
75+
7176
// for all present range sliders
7277
rangeSliders.each(function(axisOpts) {
73-
var rangeSlider = d3.select(this),
74-
opts = axisOpts[constants.name],
75-
oppAxisOpts = fullLayout[Axes.id2name(axisOpts.anchor)],
76-
oppAxisRangeOpts = opts[Axes.id2name(axisOpts.anchor)];
78+
var rangeSlider = d3.select(this);
79+
var opts = axisOpts[constants.name];
80+
var oppAxisOpts = fullLayout[axisIDs.id2name(axisOpts.anchor)];
81+
var oppAxisRangeOpts = opts[axisIDs.id2name(axisOpts.anchor)];
7782

7883
// update range
7984
// Expand slider range to the axis range
@@ -97,19 +102,9 @@ module.exports = function(gd) {
97102
var domain = axisOpts.domain;
98103
var tickHeight = (axisOpts._boundingBox || {}).height || 0;
99104

100-
var oppBottom = Infinity;
101-
var subplotData = Axes.getSubplots(gd, axisOpts);
102-
for(var i = 0; i < subplotData.length; i++) {
103-
var oppAxis = Axes.getFromId(gd, subplotData[i].substr(subplotData[i].indexOf('y')));
104-
oppBottom = Math.min(oppBottom, oppAxis.domain[0]);
105-
}
106-
107-
opts._id = constants.name + axisOpts._id;
108-
opts._clipId = opts._id + '-' + fullLayout._uid;
105+
var oppBottom = opts._oppBottom;
109106

110107
opts._width = graphSize.w * (domain[1] - domain[0]);
111-
opts._height = (fullLayout.height - margin.b - margin.t) * opts.thickness;
112-
opts._offsetShift = Math.floor(opts.borderwidth / 2);
113108

114109
var x = Math.round(margin.l + (graphSize.w * domain[0]));
115110

@@ -177,36 +172,9 @@ module.exports = function(gd) {
177172
}
178173
});
179174
}
180-
181-
// update margins
182-
Plots.autoMargin(gd, opts._id, {
183-
x: domain[0],
184-
y: oppBottom,
185-
l: 0,
186-
r: 0,
187-
t: 0,
188-
b: opts._height + margin.b + tickHeight,
189-
pad: constants.extraPad + opts._offsetShift * 2
190-
});
191175
});
192176
};
193177

194-
function makeRangeSliderData(fullLayout) {
195-
var axes = Axes.list({ _fullLayout: fullLayout }, 'x', true),
196-
name = constants.name,
197-
out = [];
198-
199-
if(fullLayout._has('gl2d')) return out;
200-
201-
for(var i = 0; i < axes.length; i++) {
202-
var ax = axes[i];
203-
204-
if(ax[name] && ax[name].visible) out.push(ax);
205-
}
206-
207-
return out;
208-
}
209-
210178
function setupDragElement(rangeSlider, gd, axisOpts, opts) {
211179
var slideBox = rangeSlider.select('rect.' + constants.slideBoxClassName).node(),
212180
grabAreaMin = rangeSlider.select('rect.' + constants.grabAreaMinClassName).node(),
@@ -393,11 +361,10 @@ function addClipPath(rangeSlider, gd, axisOpts, opts) {
393361
}
394362

395363
function drawRangePlot(rangeSlider, gd, axisOpts, opts) {
396-
var subplotData = Axes.getSubplots(gd, axisOpts),
397-
calcData = gd.calcdata;
364+
var calcData = gd.calcdata;
398365

399366
var rangePlots = rangeSlider.selectAll('g.' + constants.rangePlotClassName)
400-
.data(subplotData, Lib.identity);
367+
.data(axisOpts._subplotsWith, Lib.identity);
401368

402369
rangePlots.enter().append('g')
403370
.attr('class', function(id) { return constants.rangePlotClassName + ' ' + id; })
@@ -413,7 +380,7 @@ function drawRangePlot(rangeSlider, gd, axisOpts, opts) {
413380
var plotgroup = d3.select(this),
414381
isMainPlot = (i === 0);
415382

416-
var oppAxisOpts = Axes.getFromId(gd, id, 'y'),
383+
var oppAxisOpts = axisIDs.getFromId(gd, id, 'y'),
417384
oppAxisName = oppAxisOpts._name,
418385
oppAxisRangeOpts = opts[oppAxisName];
419386

@@ -445,6 +412,11 @@ function drawRangePlot(rangeSlider, gd, axisOpts, opts) {
445412
var xa = mockFigure._fullLayout.xaxis;
446413
var ya = mockFigure._fullLayout[oppAxisName];
447414

415+
xa.clearCalc();
416+
xa.setScale();
417+
ya.clearCalc();
418+
ya.setScale();
419+
448420
var plotinfo = {
449421
id: id,
450422
plotgroup: plotgroup,

src/components/rangeslider/helpers.js

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* Copyright 2012-2018, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var axisIDs = require('../../plots/cartesian/axis_ids');
12+
var constants = require('./constants');
13+
var name = constants.name;
14+
15+
function isVisible(ax) {
16+
var rangeSlider = ax && ax[name];
17+
return rangeSlider && rangeSlider.visible;
18+
}
19+
exports.isVisible = isVisible;
20+
21+
exports.makeData = function(fullLayout) {
22+
var axes = axisIDs.list({ _fullLayout: fullLayout }, 'x', true);
23+
var margin = fullLayout.margin;
24+
var rangeSliderData = [];
25+
26+
if(!fullLayout._has('gl2d')) {
27+
for(var i = 0; i < axes.length; i++) {
28+
var ax = axes[i];
29+
30+
if(isVisible(ax)) {
31+
rangeSliderData.push(ax);
32+
33+
var opts = ax[name];
34+
opts._id = name + ax._id;
35+
opts._height = (fullLayout.height - margin.b - margin.t) * opts.thickness;
36+
opts._offsetShift = Math.floor(opts.borderwidth / 2);
37+
}
38+
}
39+
}
40+
41+
fullLayout._rangeSliderData = rangeSliderData;
42+
};
43+
44+
exports.autoMarginOpts = function(gd, ax) {
45+
var opts = ax[name];
46+
47+
var oppBottom = Infinity;
48+
var counterAxes = ax._counterAxes;
49+
for(var j = 0; j < counterAxes.length; j++) {
50+
var counterId = counterAxes[j];
51+
var oppAxis = axisIDs.getFromId(gd, counterId);
52+
oppBottom = Math.min(oppBottom, oppAxis.domain[0]);
53+
}
54+
opts._oppBottom = oppBottom;
55+
56+
var tickHeight = (ax.side === 'bottom' && ax._boundingBox.height) || 0;
57+
58+
return {
59+
x: 0,
60+
y: oppBottom,
61+
l: 0,
62+
r: 0,
63+
t: 0,
64+
b: opts._height + gd._fullLayout.margin.b + tickHeight,
65+
pad: constants.extraPad + opts._offsetShift * 2
66+
};
67+
};

src/components/rangeslider/index.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
var Lib = require('../../lib');
1212
var attrs = require('./attributes');
1313
var oppAxisAttrs = require('./oppaxis_attributes');
14+
var helpers = require('./helpers');
1415

1516
module.exports = {
1617
moduleType: 'component',
@@ -29,5 +30,8 @@ module.exports = {
2930
layoutAttributes: require('./attributes'),
3031
handleDefaults: require('./defaults'),
3132
calcAutorange: require('./calc_autorange'),
32-
draw: require('./draw')
33+
draw: require('./draw'),
34+
isVisible: helpers.isVisible,
35+
makeData: helpers.makeData,
36+
autoMarginOpts: helpers.autoMarginOpts
3337
};

src/components/shapes/calc_autorange.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
* LICENSE file in the root directory of this source tree.
77
*/
88

9-
109
'use strict';
1110

1211
var Lib = require('../../lib');
@@ -84,7 +83,7 @@ function calcPaddingOptions(lineWidth, sizeMode, v0, v1, path, isYAxis) {
8483
}
8584

8685
function shapeBounds(ax, v0, v1, path, paramsToUse) {
87-
var convertVal = (ax.type === 'category') ? ax.r2c : ax.d2c;
86+
var convertVal = (ax.type === 'category' || ax.type === 'multicategory') ? ax.r2c : ax.d2c;
8887

8988
if(v0 !== undefined) return [convertVal(v0), convertVal(v1)];
9089
if(!path) return;

src/lib/array.js

+23
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,26 @@ exports.concat = function() {
132132
}
133133
return out;
134134
};
135+
136+
exports.maxRowLength = function(z) {
137+
return _rowLength(z, Math.max, 0);
138+
};
139+
140+
exports.minRowLength = function(z) {
141+
return _rowLength(z, Math.min, Infinity);
142+
};
143+
144+
function _rowLength(z, fn, len0) {
145+
if(isArrayOrTypedArray(z)) {
146+
if(isArrayOrTypedArray(z[0])) {
147+
var len = len0;
148+
for(var i = 0; i < z.length; i++) {
149+
len = fn(len, z[i].length);
150+
}
151+
return len;
152+
} else {
153+
return z.length;
154+
}
155+
}
156+
return 0;
157+
}

src/lib/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ lib.isArrayOrTypedArray = arrayModule.isArrayOrTypedArray;
3131
lib.isArray1D = arrayModule.isArray1D;
3232
lib.ensureArray = arrayModule.ensureArray;
3333
lib.concat = arrayModule.concat;
34+
lib.maxRowLength = arrayModule.maxRowLength;
35+
lib.minRowLength = arrayModule.minRowLength;
3436

3537
var modModule = require('./mod');
3638
lib.mod = modModule.mod;

src/plot_api/plot_api.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -332,8 +332,7 @@ exports.plot = function(gd, data, layout, config) {
332332
return Lib.syncOrAsync([
333333
Registry.getComponentMethod('shapes', 'calcAutorange'),
334334
Registry.getComponentMethod('annotations', 'calcAutorange'),
335-
doAutoRangeAndConstraints,
336-
Registry.getComponentMethod('rangeslider', 'calcAutorange')
335+
doAutoRangeAndConstraints
337336
], gd);
338337
}
339338

@@ -345,6 +344,11 @@ exports.plot = function(gd, data, layout, config) {
345344
// store initial ranges *after* enforcing constraints, otherwise
346345
// we will never look like we're at the initial ranges
347346
if(graphWasEmpty) Axes.saveRangeInitial(gd);
347+
348+
// this one is different from shapes/annotations calcAutorange
349+
// the others incorporate those components into ax._extremes,
350+
// this one actually sets the ranges in rangesliders.
351+
Registry.getComponentMethod('rangeslider', 'calcAutorange')(gd);
348352
}
349353

350354
// draw ticks, titles, and calculate axis scaling (._b, ._m)

0 commit comments

Comments
 (0)