Skip to content

Persistent point selection #2135

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 46 commits into from
Nov 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
8b60b4e
Mirror point selection in graph data
rreusser Oct 24, 2017
b209a55
Partial stateful select
rreusser Oct 25, 2017
2a04371
Merge branch 'master' into persistent-point-selection
etpinard Nov 2, 2017
6903d31
remove we won't do in this PR
etpinard Nov 2, 2017
3ef323f
mv 'selectedpoints' & 'selectedids' to global trace attrs/defaults
etpinard Nov 2, 2017
1efe144
improve updateSelectedState
etpinard Nov 2, 2017
9f3f58d
introduce new on-select update strategy:
etpinard Nov 2, 2017
782bc66
persistent selections for 'scatter' traces
etpinard Nov 2, 2017
ec0578b
persistent selection for 'scattercarpet' and 'scatteternary'
etpinard Nov 2, 2017
1bd832e
persistent selections for 'box' and 'violin' traces
etpinard Nov 2, 2017
10c5b7b
persistent selection for 'scattergeo'
etpinard Nov 2, 2017
eeace5b
persistent selections for 'choropleth'
etpinard Nov 2, 2017
bf039ab
resolve #1862 - add `marker.opacity` to bar (& histogram) traces
etpinard Nov 2, 2017
5cfadbd
persistent selection for 'bar' & 'histogram' traces
etpinard Nov 2, 2017
f54a08a
persistent selections for 'scattermapbox'
etpinard Nov 2, 2017
d4d1728
don't coerce selected/unselected in scatter3d traces
etpinard Nov 2, 2017
eeaca74
do not implement persistent selections in scattergl traces (for now)
etpinard Nov 2, 2017
5ca8b36
improve point-selection mock
etpinard Nov 2, 2017
4e83ef7
fixup bar/histogram tests
etpinard Nov 2, 2017
67cefad
fixup bar path selector
etpinard Nov 3, 2017
10a2fa9
add selected.marker.size support
etpinard Nov 3, 2017
2fcdb7c
Merge branch 'master' into persistent-point-selection
etpinard Nov 13, 2017
54cc67b
make hover AND select use same event data pipeline
etpinard Nov 15, 2017
638d03d
adapt Histogram.eventData so that it works with selection pts
etpinard Nov 15, 2017
b30fab4
add Scattercarpet.eventData method
etpinard Nov 15, 2017
949b311
add ScatterTernary.eventData method
etpinard Nov 15, 2017
315e632
add Lib.tagSelected made to make selectedpoints work w/ transforms
etpinard Nov 15, 2017
9b79b3f
add support for selection of aggregations
etpinard Nov 15, 2017
9210278
update point-selection baseline
etpinard Nov 15, 2017
261387d
update event-data jasmine tests (mostly adding pointIndex)
etpinard Nov 15, 2017
1859256
add Lib.coerceSelectionMarkerOpacity
etpinard Nov 15, 2017
d3cdd6f
update Drawing.selectedPointStyle
etpinard Nov 16, 2017
451778b
speed up is-pt-index-valid check
etpinard Nov 16, 2017
c8d715b
emit 'normalized' a/b/c coords in scatterternary event data
etpinard Nov 16, 2017
7543dc6
move ptNumber2cdIndex computation to main binning loop
etpinard Nov 16, 2017
a95e47b
Merge pull request #2163 from plotly/persistent-point-selection-compa…
etpinard Nov 16, 2017
9d86a2f
:hocho: selectedids (for now)
etpinard Nov 16, 2017
6437081
add delay in bar select test (to pass on CI)
etpinard Nov 16, 2017
9bbf55b
assert gd.data[i].selectedpoints in per-trace-type select tests
etpinard Nov 16, 2017
9ccccd3
(fixup) Lib.coerceSelectionMarkerOpacity for all selectable traces
etpinard Nov 17, 2017
3d2049c
:lock: down [un]selected styles in mocks
etpinard Nov 17, 2017
a7f06fa
(fixup) rm selectedpoints from scattercarpet for select test
etpinard Nov 17, 2017
80eee22
add selections persist test
etpinard Nov 17, 2017
aff9dbf
fixup selectedpoints -> calcdata for box/violin
etpinard Nov 17, 2017
ab7b8f1
update baseline (box/violin selectedpoints ordering)
etpinard Nov 17, 2017
88fb812
aj-proof [un]selected logic
etpinard Nov 20, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 154 additions & 34 deletions src/components/drawing/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ var svgTextUtils = require('../../lib/svg_text_utils');
var xmlnsNamespaces = require('../../constants/xmlns_namespaces');
var alignment = require('../../constants/alignment');
var LINE_SPACING = alignment.LINE_SPACING;
var DESELECTDIM = require('../../constants/interactions').DESELECTDIM;

var subTypes = require('../../traces/scatter/subtypes');
var makeBubbleSizeFn = require('../../traces/scatter/make_bubble_size_func');
Expand Down Expand Up @@ -247,18 +248,22 @@ drawing.symbolNumber = function(v) {
return Math.floor(Math.max(v, 0));
};

function makePointPath(symbolNumber, r) {
var base = symbolNumber % 100;
return drawing.symbolFuncs[base](r) + (symbolNumber >= 200 ? DOTPATH : '');
}

function singlePointStyle(d, sel, trace, markerScale, lineScale, marker, markerLine, gd) {
// only scatter & box plots get marker path and opacity
// bars, histograms don't
if(Registry.traceIs(trace, 'symbols')) {
var sizeFn = makeBubbleSizeFn(trace);

sel.attr('d', function(d) {
var r;

// handle multi-trace graph edit case
if(d.ms === 'various' || marker.size === 'various') r = 3;
else {
if(d.ms === 'various' || marker.size === 'various') {
r = 3;
} else {
r = subTypes.isBubble(trace) ?
sizeFn(d.ms) : (marker.size || 6) / 2;
}
Expand All @@ -267,21 +272,20 @@ function singlePointStyle(d, sel, trace, markerScale, lineScale, marker, markerL
d.mrc = r;

// turn the symbol into a sanitized number
var x = drawing.symbolNumber(d.mx || marker.symbol) || 0,
xBase = x % 100;
var x = drawing.symbolNumber(d.mx || marker.symbol) || 0;

// save if this marker is open
// because that impacts how to handle colors
d.om = x % 200 >= 100;

return drawing.symbolFuncs[xBase](r) +
(x >= 200 ? DOTPATH : '');
})
.style('opacity', function(d) {
return (d.mo + 1 || marker.opacity + 1) - 1;
return makePointPath(x, r);
});
}

sel.style('opacity', function(d) {
return (d.mo + 1 || marker.opacity + 1) - 1;
});

var perPointGradient = false;

// 'so' is suspected outliers, for box plots
Expand Down Expand Up @@ -409,7 +413,6 @@ drawing.singlePointStyle = function(d, sel, trace, markerScale, lineScale, gd) {
var marker = trace.marker;

singlePointStyle(d, sel, trace, markerScale, lineScale, marker, marker.line, gd);

};

drawing.pointStyle = function(s, trace, gd) {
Expand All @@ -426,6 +429,84 @@ drawing.pointStyle = function(s, trace, gd) {
});
};

drawing.selectedPointStyle = function(s, trace) {
if(!s.size() || !trace.selectedpoints) return;

var selectedAttrs = trace.selected || {};
var unselectedAttrs = trace.unselected || {};

var marker = trace.marker || {};
var selectedMarker = selectedAttrs.marker || {};
var unselectedMarker = unselectedAttrs.marker || {};

var mo = marker.opacity;
var smo = selectedMarker.opacity;
var usmo = unselectedMarker.opacity;
var smoIsDefined = smo !== undefined;
var usmoIsDefined = usmo !== undefined;

s.each(function(d) {
var pt = d3.select(this);
var dmo = d.mo;
var dmoIsDefined = dmo !== undefined;
var mo2;

if(dmoIsDefined || smoIsDefined || usmoIsDefined) {
if(d.selected) {
if(smoIsDefined) mo2 = smo;
} else {
if(usmoIsDefined) mo2 = usmo;
else mo2 = DESELECTDIM * (dmoIsDefined ? dmo : mo);
}
}

if(mo2 !== undefined) pt.style('opacity', mo2);
});

var smc = selectedMarker.color;
var usmc = unselectedMarker.color;

if(smc || usmc) {
s.each(function(d) {
var pt = d3.select(this);
var mc2;

if(d.selected) {
if(smc) mc2 = smc;
} else {
if(usmc) mc2 = usmc;
}

if(mc2) Color.fill(pt, mc2);
});
}

var sms = selectedMarker.size;
var usms = unselectedMarker.size;
var smsIsDefined = sms !== undefined;
var usmsIsDefined = usms !== undefined;

if(Registry.traceIs(trace, 'symbols') && (smsIsDefined || usmsIsDefined)) {
s.each(function(d) {
var pt = d3.select(this);
var mrc = d.mrc;
var mx = d.mx || marker.symbol || 0;
var mrc2;

if(d.selected) {
mrc2 = (smsIsDefined) ? sms / 2 : mrc;
} else {
mrc2 = (usmsIsDefined) ? usms / 2 : mrc;
}

pt.attr('d', makePointPath(drawing.symbolNumber(mx), mrc2));

// save for selectedTextStyle
d.mrc2 = mrc2;
});
}
};

drawing.tryColorscale = function(marker, prefix) {
var cont = prefix ? Lib.nestedProperty(marker, prefix).get() : marker,
scl = cont.colorscale,
Expand All @@ -439,8 +520,39 @@ drawing.tryColorscale = function(marker, prefix) {
else return Lib.identity;
};

// draw text at points
var TEXTOFFSETSIGN = {start: 1, end: -1, middle: 0, bottom: 1, top: -1};

function textPointPosition(s, textPosition, fontSize, markerRadius) {
var group = d3.select(s.node().parentNode);

var v = textPosition.indexOf('top') !== -1 ?
'top' :
textPosition.indexOf('bottom') !== -1 ? 'bottom' : 'middle';
var h = textPosition.indexOf('left') !== -1 ?
'end' :
textPosition.indexOf('right') !== -1 ? 'start' : 'middle';

// if markers are shown, offset a little more than
// the nominal marker size
// ie 2/1.6 * nominal, bcs some markers are a bit bigger
var r = markerRadius ? markerRadius / 0.8 + 1 : 0;

var numLines = (svgTextUtils.lineCount(s) - 1) * LINE_SPACING + 1;
var dx = TEXTOFFSETSIGN[h] * r;
var dy = fontSize * 0.75 + TEXTOFFSETSIGN[v] * r +
(TEXTOFFSETSIGN[v] - 1) * numLines * fontSize / 2;

// fix the overall text group position
s.attr('text-anchor', h);
group.attr('transform', 'translate(' + dx + ',' + dy + ')');
}

function extracTextFontSize(d, trace) {
var fontSize = d.ts || trace.textfont.size;
return (isNumeric(fontSize) && fontSize > 0) ? fontSize : 0;
}

// draw text at points
drawing.textPointStyle = function(s, trace, gd) {
s.each(function(d) {
var p = d3.select(this);
Expand All @@ -451,35 +563,43 @@ drawing.textPointStyle = function(s, trace, gd) {
return;
}

var pos = d.tp || trace.textposition,
v = pos.indexOf('top') !== -1 ? 'top' :
pos.indexOf('bottom') !== -1 ? 'bottom' : 'middle',
h = pos.indexOf('left') !== -1 ? 'end' :
pos.indexOf('right') !== -1 ? 'start' : 'middle',
fontSize = d.ts || trace.textfont.size,
// if markers are shown, offset a little more than
// the nominal marker size
// ie 2/1.6 * nominal, bcs some markers are a bit bigger
r = d.mrc ? (d.mrc / 0.8 + 1) : 0;

fontSize = (isNumeric(fontSize) && fontSize > 0) ? fontSize : 0;
var pos = d.tp || trace.textposition;
var fontSize = extracTextFontSize(d, trace);

p.call(drawing.font,
d.tf || trace.textfont.family,
fontSize,
d.tc || trace.textfont.color)
.attr('text-anchor', h)
.text(text)
.call(svgTextUtils.convertToTspans, gd);
.call(svgTextUtils.convertToTspans, gd)
.call(textPointPosition, pos, fontSize, d.mrc);
});
};

var pgroup = d3.select(this.parentNode);
var numLines = (svgTextUtils.lineCount(p) - 1) * LINE_SPACING + 1;
var dx = TEXTOFFSETSIGN[h] * r;
var dy = fontSize * 0.75 + TEXTOFFSETSIGN[v] * r +
(TEXTOFFSETSIGN[v] - 1) * numLines * fontSize / 2;
drawing.selectedTextStyle = function(s, trace) {
if(!s.size() || !trace.selectedpoints) return;

var selectedAttrs = trace.selected || {};
var unselectedAttrs = trace.unselected || {};

s.each(function(d) {
var tx = d3.select(this);
var tc = d.tc || trace.textfont.color;
var tp = d.tp || trace.textposition;
var fontSize = extracTextFontSize(d, trace);
var stc = (selectedAttrs.textfont || {}).color;
var utc = (unselectedAttrs.textfont || {}).color;
var tc2;

if(d.selected) {
if(stc) tc2 = stc;
} else {
if(utc) tc2 = utc;
else if(!stc) tc2 = Color.addOpacity(tc, DESELECTDIM);
}

// fix the overall text group position
pgroup.attr('transform', 'translate(' + dx + ',' + dy + ')');
if(tc2) Color.fill(tx, tc2);
textPointPosition(tx, tp, fontSize, d.mrc2 || d.mrc);
});
};

Expand Down
57 changes: 57 additions & 0 deletions src/components/fx/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,63 @@ exports.quadrature = function quadrature(dx, dy) {
};
};

/** Fill event data point object for hover and selection.
* Invokes _module.eventData if present.
*
* N.B. note that point 'index' corresponds to input data array index
* whereas 'number' is its post-transform version.
*
* If the hovered/selected pt corresponds to an multiple input points
* (e.g. for histogram and transformed traces), 'pointNumbers` and 'pointIndices'
* are include in the event data.
*
* @param {object} pt
* @param {object} trace
* @param {object} cd
* @return {object}
*/
exports.makeEventData = function makeEventData(pt, trace, cd) {
// hover uses 'index', select uses 'pointNumber'
var pointNumber = 'index' in pt ? pt.index : pt.pointNumber;

var out = {
data: trace._input,
fullData: trace,
curveNumber: trace.index,
pointNumber: pointNumber
};

if(trace._indexToPoints) {
var pointIndices = trace._indexToPoints[pointNumber];

if(pointIndices.length === 1) {
out.pointIndex = pointIndices[0];
} else {
out.pointIndices = pointIndices;
}
} else {
out.pointIndex = pointNumber;
}

if(trace._module.eventData) {
out = trace._module.eventData(out, pt, trace, cd, pointNumber);
} else {
if('xVal' in pt) out.x = pt.xVal;
else if('x' in pt) out.x = pt.x;

if('yVal' in pt) out.y = pt.yVal;
else if('y' in pt) out.y = pt.y;

if(pt.xa) out.xaxis = pt.xa;
if(pt.ya) out.yaxis = pt.ya;
if(pt.zLabelVal !== undefined) out.z = pt.zLabelVal;
}

exports.appendArrayPointValue(out, trace, pointNumber);

return out;
};

/** Appends values inside array attributes corresponding to given point number
*
* @param {object} pointData : point data object (gets mutated here)
Expand Down
21 changes: 1 addition & 20 deletions src/components/fx/hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -417,26 +417,7 @@ function _hover(gd, evt, subplot, noHoverEvent) {
// other people and send it to the event
for(itemnum = 0; itemnum < hoverData.length; itemnum++) {
var pt = hoverData[itemnum];

var out = {
data: pt.trace._input,
fullData: pt.trace,
curveNumber: pt.trace.index,
pointNumber: pt.index
};

if(pt.trace._module.eventData) out = pt.trace._module.eventData(out, pt);
else {
out.x = pt.xVal;
out.y = pt.yVal;
out.xaxis = pt.xa;
out.yaxis = pt.ya;

if(pt.zLabelVal !== undefined) out.z = pt.zLabelVal;
}

helpers.appendArrayPointValue(out, pt.trace, pt.index);
newhoverdata.push(out);
newhoverdata.push(helpers.makeEventData(pt, pt.trace, pt.cd));
}

gd._hoverdata = newhoverdata;
Expand Down
Loading