Skip to content

Constraint-type contours for regular contour (not on a carpet) #2270

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jan 30, 2018
11 changes: 7 additions & 4 deletions src/components/legend/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@

'use strict';

var Registry = require('../../registry');


exports.legendGetsTrace = function legendGetsTrace(trace) {
return trace.visible && Registry.traceIs(trace, 'showLegend');
// traceIs(trace, 'showLegend') is not sufficient anymore, due to contour(carpet)?
// which are legend-eligible only if type: constraint. Otherwise, showlegend gets deleted.

// Note that we explicitly include showlegend: false, so a trace that *could* be
// in the legend but is not shown still counts toward the two traces you need to
// ensure the legend is shown by default, because this can still help disambiguate.
return trace.visible && (trace.showlegend !== undefined);
};

exports.isGrouped = function isGrouped(legendLayout) {
Expand Down
15 changes: 8 additions & 7 deletions src/components/legend/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,14 @@ module.exports = function style(s, gd) {
.each(stylePoints);

function styleLines(d) {
var trace = d[0].trace,
showFill = trace.visible && trace.fill && trace.fill !== 'none',
showLine = subTypes.hasLines(trace);

if(trace && trace._module && trace._module.name === 'contourcarpet') {
showLine = trace.contours.showlines;
showFill = trace.contours.coloring === 'fill';
var trace = d[0].trace;
var showFill = trace.visible && trace.fill && trace.fill !== 'none';
var showLine = subTypes.hasLines(trace);
var contours = trace.contours;

if(contours && contours.type === 'constraint') {
showLine = contours.showlines;
showFill = contours._operation !== '=';
}

var fill = d3.select(this).select('.legendfill').selectAll('path')
Expand Down
36 changes: 36 additions & 0 deletions src/constants/filter_ops.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Copyright 2012-2018, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

module.exports = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

COMPARISON_OPS: ['=', '!=', '<', '>=', '>', '<='],
COMPARISON_OPS2: ['=', '<', '>=', '>', '<='],
INTERVAL_OPS: ['[]', '()', '[)', '(]', '][', ')(', '](', ')['],
SET_OPS: ['{}', '}{'],
CONSTRAINT_REDUCTION: {
// for contour constraints, open/closed endpoints are equivalent
'=': '=',

'<': '<',
'<=': '<',

'>': '>',
'>=': '>',

'[]': '[]',
'()': '[]',
'[)': '[]',
'(]': '[]',

'][': '][',
')(': '][',
'](': '][',
')[': ']['
}
};
75 changes: 75 additions & 0 deletions src/traces/contour/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ var dash = require('../../components/drawing/attributes').dash;
var fontAttrs = require('../../plots/font_attributes');
var extendFlat = require('../../lib/extend').extendFlat;

var filterOps = require('../../constants/filter_ops');
var COMPARISON_OPS2 = filterOps.COMPARISON_OPS2;
var INTERVAL_OPS = filterOps.INTERVAL_OPS;

var scatterLineAttrs = scatterAttrs.line;

module.exports = extendFlat({
Expand All @@ -34,6 +38,17 @@ module.exports = extendFlat({

connectgaps: heatmapAttrs.connectgaps,

fillcolor: {
valType: 'color',
role: 'style',
editType: 'calc',
description: [
'Sets the fill color if `contours.type` is *constraint*.',
'Defaults to a half-transparent variant of the line color,',
'marker color, or marker line color, whichever is available.'
].join(' ')
},

autocontour: {
valType: 'boolean',
dflt: true,
Expand Down Expand Up @@ -67,6 +82,19 @@ module.exports = extendFlat({
},

contours: {
type: {
valType: 'enumerated',
values: ['levels', 'constraint'],
dflt: 'levels',
role: 'info',
editType: 'calc',
description: [
'If `levels`, the data is represented as a contour plot with multiple',
'levels displayed. If `constraint`, the data is represented as constraints',
'with the invalid region shaded as specified by the `operation` and',
'`value` parameters.'
].join(' ')
},
start: {
valType: 'number',
dflt: null,
Expand Down Expand Up @@ -155,6 +183,53 @@ module.exports = extendFlat({
'https://github.com/d3/d3-format/blob/master/README.md#locale_format.'
].join(' ')
},
operation: {
valType: 'enumerated',
values: [].concat(COMPARISON_OPS2).concat(INTERVAL_OPS),
role: 'info',
dflt: '=',
editType: 'calc',
description: [
'Sets the filter operation.',

'*=* keeps items equal to `value`',

'*<* and *<=* keep items less than `value`',

'*>* and *>=* keep items greater than `value`',

'*[]*, *()*, *[)*, and *(]* keep items inside `value[0]` to `value[1]`',

'*][*, *)(*, *](*, *)[* keep items outside `value[0]` to value[1]`',

'Open vs. closed intervals make no difference to constraint display, but',
'all versions are allowed for consistency with filter transforms.'
].join(' ')
},
value: {
valType: 'any',
dflt: 0,
role: 'info',
editType: 'calc',
description: [
'Sets the value or values by which to filter by.',

'Values are expected to be in the same type as the data linked',
'to *target*.',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I think this was copied from transforms/filter.js, target makes no sense here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch - 7c7a011


'When `operation` is set to one of the comparison values',
'(' + COMPARISON_OPS2 + ')',
'*value* is expected to be a number or a string.',

'When `operation` is set to one of the interval value',
'(' + INTERVAL_OPS + ')',
'*value* is expected to be 2-item array where the first item',
'is the lower bound and the second item is the upper bound.',

'Open vs. closed intervals make no difference to constraint display, but',
'all versions are allowed for consistency with filter transforms.'
].join(' ')
},
editType: 'calc',
impliedEdits: {'autocontour': false}
},
Expand Down
86 changes: 3 additions & 83 deletions src/traces/contour/calc.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,94 +9,14 @@

'use strict';

var Axes = require('../../plots/cartesian/axes');
var extendFlat = require('../../lib').extendFlat;
var heatmapCalc = require('../heatmap/calc');

var setContours = require('./set_contours');

// most is the same as heatmap calc, then adjust it
// though a few things inside heatmap calc still look for
// contour maps, because the makeBoundArray calls are too entangled
module.exports = function calc(gd, trace) {
var cd = heatmapCalc(gd, trace),
contours = trace.contours;

// check if we need to auto-choose contour levels
if(trace.autocontour !== false) {
var dummyAx = autoContours(trace.zmin, trace.zmax, trace.ncontours);

contours.size = dummyAx.dtick;

contours.start = Axes.tickFirst(dummyAx);
dummyAx.range.reverse();
contours.end = Axes.tickFirst(dummyAx);

if(contours.start === trace.zmin) contours.start += contours.size;
if(contours.end === trace.zmax) contours.end -= contours.size;

// if you set a small ncontours, *and* the ends are exactly on zmin/zmax
// there's an edge case where start > end now. Make sure there's at least
// one meaningful contour, put it midway between the crossed values
if(contours.start > contours.end) {
contours.start = contours.end = (contours.start + contours.end) / 2;
}

// copy auto-contour info back to the source data.
// previously we copied the whole contours object back, but that had
// other info (coloring, showlines) that should be left to supplyDefaults
if(!trace._input.contours) trace._input.contours = {};
extendFlat(trace._input.contours, {
start: contours.start,
end: contours.end,
size: contours.size
});
trace._input.autocontour = true;
}
else {
// sanity checks on manually-supplied start/end/size
var start = contours.start,
end = contours.end,
inputContours = trace._input.contours;

if(start > end) {
contours.start = inputContours.start = end;
end = contours.end = inputContours.end = start;
start = contours.start;
}

if(!(contours.size > 0)) {
var sizeOut;
if(start === end) sizeOut = 1;
else sizeOut = autoContours(start, end, trace.ncontours).dtick;

inputContours.size = contours.size = sizeOut;
}
}

var cd = heatmapCalc(gd, trace);
setContours(trace);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lovely ♻️

return cd;
};

/*
* autoContours: make a dummy axis object with dtick we can use
* as contours.size, and if needed we can use Axes.tickFirst
* with this axis object to calculate the start and end too
*
* start: the value to start the contours at
* end: the value to end at (must be > start)
* ncontours: max number of contours to make, like roughDTick
*
* returns: an axis object
*/
function autoContours(start, end, ncontours) {
var dummyAx = {
type: 'linear',
range: [start, end]
};

Axes.autoTicks(
dummyAx,
(end - start) / (ncontours || 15)
);

return dummyAx;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
module.exports = function(pathinfo, operation, perimeter, trace) {
// Abandon all hope, ye who enter here.
var i, v1, v2;
var na = trace.a.length;
var nb = trace.b.length;
var z = trace.z;
var pi0 = pathinfo[0];
var na = pi0.x.length;
var nb = pi0.y.length;
var z = pi0.z;
var contours = trace.contours;

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

pi0.prefixBoundary = false;

switch(operation) {
case '>':
case '>=':
if(trace.contours.value > boundaryMax) {
pathinfo[0].prefixBoundary = true;
if(contours.value > boundaryMax) {
pi0.prefixBoundary = true;
}
break;
case '<':
case '<=':
if(trace.contours.value < boundaryMin) {
pathinfo[0].prefixBoundary = true;
if(contours.value < boundaryMin) {
pi0.prefixBoundary = true;
}
break;
case '[]':
case '()':
v1 = Math.min.apply(null, trace.contours.value);
v2 = Math.max.apply(null, trace.contours.value);
if(v2 < boundaryMin) {
pathinfo[0].prefixBoundary = true;
}
if(v1 > boundaryMax) {
pathinfo[0].prefixBoundary = true;
v1 = Math.min.apply(null, contours.value);
v2 = Math.max.apply(null, contours.value);
if(v2 < boundaryMin || v1 > boundaryMax) {
pi0.prefixBoundary = true;
}
break;
case '][':
case ')(':
v1 = Math.min.apply(null, trace.contours.value);
v2 = Math.max.apply(null, trace.contours.value);
v1 = Math.min.apply(null, contours.value);
v2 = Math.max.apply(null, contours.value);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we use Lib.aggNums here in case contours.value has some non-numeric values?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleConstraintValueDefaults already cleans this up. Not sure if I actually agree with all the choices made there, but it doesn't look to me as though non-numeric values can get through.

if(v1 < boundaryMin && v2 > boundaryMax) {
pathinfo[0].prefixBoundary = true;
pi0.prefixBoundary = true;
}
break;
}
Expand Down
50 changes: 50 additions & 0 deletions src/traces/contour/constraint_defaults.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Copyright 2012-2018, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/


'use strict';

var handleConstraintValueDefaults = require('./constraint_value_defaults');
var handleLabelDefaults = require('./label_defaults');
var Color = require('../../components/color');
var addOpacity = Color.addOpacity;
var opacity = Color.opacity;
var CONSTRAINT_REDUCTION = require('../../constants/filter_ops').CONSTRAINT_REDUCTION;

module.exports = function handleConstraintDefaults(traceIn, traceOut, coerce, layout, defaultColor, opts) {
var contours = traceOut.contours;
var showLines, lineColor, fillColor;

var operation = coerce('contours.operation');
contours._operation = CONSTRAINT_REDUCTION[operation];

handleConstraintValueDefaults(coerce, contours);

if(operation === '=') {
showLines = contours.showlines = true;
}
else {
showLines = coerce('contours.showlines');
fillColor = coerce('fillcolor', addOpacity(
(traceIn.line || {}).color || defaultColor, 0.5
));
}

if(showLines) {
var lineDfltColor = fillColor && opacity(fillColor) ?
addOpacity(traceOut.fillcolor, 1) :
defaultColor;
lineColor = coerce('line.color', lineDfltColor);
coerce('line.width', 2);
coerce('line.dash');
}

coerce('line.smoothing');

handleLabelDefaults(coerce, layout, lineColor, opts);
};
Loading