Skip to content

Commit a60cebf

Browse files
authored
Merge pull request #2270 from plotly/contour-constraint
Constraint-type contours for regular contour (not on a carpet)
2 parents 28f7ea2 + db70bca commit a60cebf

36 files changed

+790
-609
lines changed

src/components/legend/helpers.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@
99

1010
'use strict';
1111

12-
var Registry = require('../../registry');
13-
14-
1512
exports.legendGetsTrace = function legendGetsTrace(trace) {
16-
return trace.visible && Registry.traceIs(trace, 'showLegend');
13+
// traceIs(trace, 'showLegend') is not sufficient anymore, due to contour(carpet)?
14+
// which are legend-eligible only if type: constraint. Otherwise, showlegend gets deleted.
15+
16+
// Note that we explicitly include showlegend: false, so a trace that *could* be
17+
// in the legend but is not shown still counts toward the two traces you need to
18+
// ensure the legend is shown by default, because this can still help disambiguate.
19+
return trace.visible && (trace.showlegend !== undefined);
1720
};
1821

1922
exports.isGrouped = function isGrouped(legendLayout) {

src/components/legend/style.js

+8-7
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,14 @@ module.exports = function style(s, gd) {
6060
.each(stylePoints);
6161

6262
function styleLines(d) {
63-
var trace = d[0].trace,
64-
showFill = trace.visible && trace.fill && trace.fill !== 'none',
65-
showLine = subTypes.hasLines(trace);
66-
67-
if(trace && trace._module && trace._module.name === 'contourcarpet') {
68-
showLine = trace.contours.showlines;
69-
showFill = trace.contours.coloring === 'fill';
63+
var trace = d[0].trace;
64+
var showFill = trace.visible && trace.fill && trace.fill !== 'none';
65+
var showLine = subTypes.hasLines(trace);
66+
var contours = trace.contours;
67+
68+
if(contours && contours.type === 'constraint') {
69+
showLine = contours.showlines;
70+
showFill = contours._operation !== '=';
7071
}
7172

7273
var fill = d3.select(this).select('.legendfill').selectAll('path')

src/constants/filter_ops.js

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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+
module.exports = {
12+
COMPARISON_OPS: ['=', '!=', '<', '>=', '>', '<='],
13+
COMPARISON_OPS2: ['=', '<', '>=', '>', '<='],
14+
INTERVAL_OPS: ['[]', '()', '[)', '(]', '][', ')(', '](', ')['],
15+
SET_OPS: ['{}', '}{'],
16+
CONSTRAINT_REDUCTION: {
17+
// for contour constraints, open/closed endpoints are equivalent
18+
'=': '=',
19+
20+
'<': '<',
21+
'<=': '<',
22+
23+
'>': '>',
24+
'>=': '>',
25+
26+
'[]': '[]',
27+
'()': '[]',
28+
'[)': '[]',
29+
'(]': '[]',
30+
31+
'][': '][',
32+
')(': '][',
33+
'](': '][',
34+
')[': ']['
35+
}
36+
};

src/traces/contour/attributes.js

+69
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ var dash = require('../../components/drawing/attributes').dash;
1616
var fontAttrs = require('../../plots/font_attributes');
1717
var extendFlat = require('../../lib/extend').extendFlat;
1818

19+
var filterOps = require('../../constants/filter_ops');
20+
var COMPARISON_OPS2 = filterOps.COMPARISON_OPS2;
21+
var INTERVAL_OPS = filterOps.INTERVAL_OPS;
22+
1923
var scatterLineAttrs = scatterAttrs.line;
2024

2125
module.exports = extendFlat({
@@ -34,6 +38,17 @@ module.exports = extendFlat({
3438

3539
connectgaps: heatmapAttrs.connectgaps,
3640

41+
fillcolor: {
42+
valType: 'color',
43+
role: 'style',
44+
editType: 'calc',
45+
description: [
46+
'Sets the fill color if `contours.type` is *constraint*.',
47+
'Defaults to a half-transparent variant of the line color,',
48+
'marker color, or marker line color, whichever is available.'
49+
].join(' ')
50+
},
51+
3752
autocontour: {
3853
valType: 'boolean',
3954
dflt: true,
@@ -67,6 +82,19 @@ module.exports = extendFlat({
6782
},
6883

6984
contours: {
85+
type: {
86+
valType: 'enumerated',
87+
values: ['levels', 'constraint'],
88+
dflt: 'levels',
89+
role: 'info',
90+
editType: 'calc',
91+
description: [
92+
'If `levels`, the data is represented as a contour plot with multiple',
93+
'levels displayed. If `constraint`, the data is represented as constraints',
94+
'with the invalid region shaded as specified by the `operation` and',
95+
'`value` parameters.'
96+
].join(' ')
97+
},
7098
start: {
7199
valType: 'number',
72100
dflt: null,
@@ -155,6 +183,47 @@ module.exports = extendFlat({
155183
'https://github.com/d3/d3-format/blob/master/README.md#locale_format.'
156184
].join(' ')
157185
},
186+
operation: {
187+
valType: 'enumerated',
188+
values: [].concat(COMPARISON_OPS2).concat(INTERVAL_OPS),
189+
role: 'info',
190+
dflt: '=',
191+
editType: 'calc',
192+
description: [
193+
'Sets the constraint operation.',
194+
195+
'*=* keeps regions equal to `value`',
196+
197+
'*<* and *<=* keep regions less than `value`',
198+
199+
'*>* and *>=* keep regions greater than `value`',
200+
201+
'*[]*, *()*, *[)*, and *(]* keep regions inside `value[0]` to `value[1]`',
202+
203+
'*][*, *)(*, *](*, *)[* keep regions outside `value[0]` to value[1]`',
204+
205+
'Open vs. closed intervals make no difference to constraint display, but',
206+
'all versions are allowed for consistency with filter transforms.'
207+
].join(' ')
208+
},
209+
value: {
210+
valType: 'any',
211+
dflt: 0,
212+
role: 'info',
213+
editType: 'calc',
214+
description: [
215+
'Sets the value or values of the constraint boundary.',
216+
217+
'When `operation` is set to one of the comparison values',
218+
'(' + COMPARISON_OPS2 + ')',
219+
'*value* is expected to be a number.',
220+
221+
'When `operation` is set to one of the interval values',
222+
'(' + INTERVAL_OPS + ')',
223+
'*value* is expected to be an array of two numbers where the first',
224+
'is the lower bound and the second is the upper bound.',
225+
].join(' ')
226+
},
158227
editType: 'calc',
159228
impliedEdits: {'autocontour': false}
160229
},

src/traces/contour/calc.js

+3-83
Original file line numberDiff line numberDiff line change
@@ -9,94 +9,14 @@
99

1010
'use strict';
1111

12-
var Axes = require('../../plots/cartesian/axes');
13-
var extendFlat = require('../../lib').extendFlat;
1412
var heatmapCalc = require('../heatmap/calc');
15-
13+
var setContours = require('./set_contours');
1614

1715
// most is the same as heatmap calc, then adjust it
1816
// though a few things inside heatmap calc still look for
1917
// contour maps, because the makeBoundArray calls are too entangled
2018
module.exports = function calc(gd, trace) {
21-
var cd = heatmapCalc(gd, trace),
22-
contours = trace.contours;
23-
24-
// check if we need to auto-choose contour levels
25-
if(trace.autocontour !== false) {
26-
var dummyAx = autoContours(trace.zmin, trace.zmax, trace.ncontours);
27-
28-
contours.size = dummyAx.dtick;
29-
30-
contours.start = Axes.tickFirst(dummyAx);
31-
dummyAx.range.reverse();
32-
contours.end = Axes.tickFirst(dummyAx);
33-
34-
if(contours.start === trace.zmin) contours.start += contours.size;
35-
if(contours.end === trace.zmax) contours.end -= contours.size;
36-
37-
// if you set a small ncontours, *and* the ends are exactly on zmin/zmax
38-
// there's an edge case where start > end now. Make sure there's at least
39-
// one meaningful contour, put it midway between the crossed values
40-
if(contours.start > contours.end) {
41-
contours.start = contours.end = (contours.start + contours.end) / 2;
42-
}
43-
44-
// copy auto-contour info back to the source data.
45-
// previously we copied the whole contours object back, but that had
46-
// other info (coloring, showlines) that should be left to supplyDefaults
47-
if(!trace._input.contours) trace._input.contours = {};
48-
extendFlat(trace._input.contours, {
49-
start: contours.start,
50-
end: contours.end,
51-
size: contours.size
52-
});
53-
trace._input.autocontour = true;
54-
}
55-
else {
56-
// sanity checks on manually-supplied start/end/size
57-
var start = contours.start,
58-
end = contours.end,
59-
inputContours = trace._input.contours;
60-
61-
if(start > end) {
62-
contours.start = inputContours.start = end;
63-
end = contours.end = inputContours.end = start;
64-
start = contours.start;
65-
}
66-
67-
if(!(contours.size > 0)) {
68-
var sizeOut;
69-
if(start === end) sizeOut = 1;
70-
else sizeOut = autoContours(start, end, trace.ncontours).dtick;
71-
72-
inputContours.size = contours.size = sizeOut;
73-
}
74-
}
75-
19+
var cd = heatmapCalc(gd, trace);
20+
setContours(trace);
7621
return cd;
7722
};
78-
79-
/*
80-
* autoContours: make a dummy axis object with dtick we can use
81-
* as contours.size, and if needed we can use Axes.tickFirst
82-
* with this axis object to calculate the start and end too
83-
*
84-
* start: the value to start the contours at
85-
* end: the value to end at (must be > start)
86-
* ncontours: max number of contours to make, like roughDTick
87-
*
88-
* returns: an axis object
89-
*/
90-
function autoContours(start, end, ncontours) {
91-
var dummyAx = {
92-
type: 'linear',
93-
range: [start, end]
94-
};
95-
96-
Axes.autoTicks(
97-
dummyAx,
98-
(end - start) / (ncontours || 15)
99-
);
100-
101-
return dummyAx;
102-
}

src/traces/contourcarpet/close_boundaries.js renamed to src/traces/contour/close_boundaries.js

+18-21
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
module.exports = function(pathinfo, operation, perimeter, trace) {
1212
// Abandon all hope, ye who enter here.
1313
var i, v1, v2;
14-
var na = trace.a.length;
15-
var nb = trace.b.length;
16-
var z = trace.z;
14+
var pi0 = pathinfo[0];
15+
var na = pi0.x.length;
16+
var nb = pi0.y.length;
17+
var z = pi0.z;
18+
var contours = trace.contours;
1719

1820
var boundaryMax = -Infinity;
1921
var boundaryMin = Infinity;
@@ -32,36 +34,31 @@ module.exports = function(pathinfo, operation, perimeter, trace) {
3234
boundaryMax = Math.max(boundaryMax, z[nb - 1][i]);
3335
}
3436

37+
pi0.prefixBoundary = false;
38+
3539
switch(operation) {
3640
case '>':
37-
case '>=':
38-
if(trace.contours.value > boundaryMax) {
39-
pathinfo[0].prefixBoundary = true;
41+
if(contours.value > boundaryMax) {
42+
pi0.prefixBoundary = true;
4043
}
4144
break;
4245
case '<':
43-
case '<=':
44-
if(trace.contours.value < boundaryMin) {
45-
pathinfo[0].prefixBoundary = true;
46+
if(contours.value < boundaryMin) {
47+
pi0.prefixBoundary = true;
4648
}
4749
break;
4850
case '[]':
49-
case '()':
50-
v1 = Math.min.apply(null, trace.contours.value);
51-
v2 = Math.max.apply(null, trace.contours.value);
52-
if(v2 < boundaryMin) {
53-
pathinfo[0].prefixBoundary = true;
54-
}
55-
if(v1 > boundaryMax) {
56-
pathinfo[0].prefixBoundary = true;
51+
v1 = Math.min.apply(null, contours.value);
52+
v2 = Math.max.apply(null, contours.value);
53+
if(v2 < boundaryMin || v1 > boundaryMax) {
54+
pi0.prefixBoundary = true;
5755
}
5856
break;
5957
case '][':
60-
case ')(':
61-
v1 = Math.min.apply(null, trace.contours.value);
62-
v2 = Math.max.apply(null, trace.contours.value);
58+
v1 = Math.min.apply(null, contours.value);
59+
v2 = Math.max.apply(null, contours.value);
6360
if(v1 < boundaryMin && v2 > boundaryMax) {
64-
pathinfo[0].prefixBoundary = true;
61+
pi0.prefixBoundary = true;
6562
}
6663
break;
6764
}

0 commit comments

Comments
 (0)