Skip to content

Scattergeo and choropleth click and hover events #215

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 4 commits into from
Jan 28, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion src/plots/geo/geo.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,15 @@ var topojsonFeature = require('topojson').feature;
function Geo(options, fullLayout) {

this.id = options.id;
this.graphDiv = options.graphDiv;
this.container = options.container;
this.topojsonURL = options.topojsonURL;

// add a few projection types to d3.geo,
// a subset of https://github.com/d3/d3-geo-projection
addProjectionsToD3();

this.showHover = fullLayout.hovermode==='closest';
this.showHover = (fullLayout.hovermode === 'closest');
this.hoverContainer = null;

this.topojsonName = null;
Expand Down
1 change: 1 addition & 0 deletions src/plots/geo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ exports.plot = function plotGeo(gd) {
if(geo === undefined) {
geo = new Geo({
id: geoId,
graphDiv: gd,
container: fullLayout._geocontainer.node(),
topojsonURL: gd._context.topojsonURL
},
Expand Down
77 changes: 48 additions & 29 deletions src/traces/choropleth/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,46 +59,49 @@ plotChoropleth.calcGeoJSON = function(trace, topojson) {

plotChoropleth.plot = function(geo, choroplethData, geoLayout) {
var framework = geo.framework,
topojson = geo.topojson,
gChoropleth = framework.select('g.choroplethlayer'),
gBaseLayer = framework.select('g.baselayer'),
gBaseLayerOverChoropleth = framework.select('g.baselayeroverchoropleth'),
baseLayersOverChoropleth = constants.baseLayersOverChoropleth,
layerName;

// TODO move to more d3-idiomatic pattern (that's work on replot)
// N.B. html('') does not work in IE11
gChoropleth.selectAll('*').remove();
gBaseLayerOverChoropleth.selectAll('*').remove();

var gChoroplethTraces = gChoropleth
.selectAll('g.trace.scatter')
.selectAll('g.trace.choropleth')
.data(choroplethData);

gChoroplethTraces.enter().append('g')
.attr('class', 'trace choropleth');
.attr('class', 'trace choropleth');

gChoroplethTraces.exit().remove();

gChoroplethTraces
.each(function(trace) {
if(trace.visible !== true) return;

var cdi = plotChoropleth.calcGeoJSON(trace, topojson),
cleanHoverLabelsFunc = makeCleanHoverLabelsFunc(geo, trace);
var cdi = plotChoropleth.calcGeoJSON(trace, geo.topojson),
cleanHoverLabelsFunc = makeCleanHoverLabelsFunc(geo, trace),
eventDataFunc = makeEventDataFunc(trace);

function handleMouseOver(d) {
function handleMouseOver(pt, ptIndex) {
if(!geo.showHover) return;

var xy = geo.projection(d.properties.ct);
cleanHoverLabelsFunc(d);
var xy = geo.projection(pt.properties.ct);
cleanHoverLabelsFunc(pt);

Plotly.Fx.loneHover({
x: xy[0],
y: xy[1],
name: d.nameLabel,
text: d.textLabel
name: pt.nameLabel,
text: pt.textLabel
}, {
container: geo.hoverContainer.node()
});

geo.graphDiv.emit('plotly_hover', eventDataFunc(pt, ptIndex));
Copy link
Contributor

Choose a reason for hiding this comment

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

🎉

}

function handleClick(pt, ptIndex) {
geo.graphDiv.emit('plotly_click', eventDataFunc(pt, ptIndex));
Copy link
Contributor

Choose a reason for hiding this comment

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

🎉

}

d3.select(this)
Expand All @@ -107,6 +110,7 @@ plotChoropleth.plot = function(geo, choroplethData, geoLayout) {
.enter().append('path')
.attr('class', 'choroplethlocation')
.on('mouseover', handleMouseOver)
.on('click', handleClick)
.on('mouseout', function() {
Plotly.Fx.loneUnhover(geo.hoverContainer);
})
Expand All @@ -118,6 +122,8 @@ plotChoropleth.plot = function(geo, choroplethData, geoLayout) {
});

// some baselayers are drawn over choropleth
gBaseLayerOverChoropleth.selectAll('*').remove();

for(var i = 0; i < baseLayersOverChoropleth.length; i++) {
layerName = baseLayersOverChoropleth[i];
gBaseLayer.select('g.' + layerName).remove();
Expand All @@ -140,11 +146,11 @@ plotChoropleth.style = function(geo) {
sclFunc = makeScaleFunction(scl, zmin, zmax);

s.selectAll('path.choroplethlocation')
.each(function(d) {
.each(function(pt) {
d3.select(this)
.attr('fill', function(d) { return sclFunc(d.z); })
.call(Color.stroke, d.mlc || markerLine.color)
.call(Drawing.dashLine, '', d.mlw || markerLine.width);
.attr('fill', function(pt) { return sclFunc(pt.z); })
.call(Color.stroke, pt.mlc || markerLine.color)
.call(Drawing.dashLine, '', pt.mlw || markerLine.width);
});
});
};
Expand All @@ -153,9 +159,9 @@ function makeCleanHoverLabelsFunc(geo, trace) {
var hoverinfo = trace.hoverinfo;

if(hoverinfo === 'none') {
return function cleanHoverLabelsFunc(d) {
delete d.nameLabel;
delete d.textLabel;
return function cleanHoverLabelsFunc(pt) {
delete pt.nameLabel;
delete pt.textLabel;
};
}

Expand All @@ -174,20 +180,33 @@ function makeCleanHoverLabelsFunc(geo, trace) {
return Plotly.Axes.tickText(axis, axis.c2l(val), 'hover').text;
}

return function cleanHoverLabelsFunc(d) {
return function cleanHoverLabelsFunc(pt) {
// put location id in name label container
// if name isn't part of hoverinfo
var thisText = [];

if(hasIdAsNameLabel) d.nameLabel = d.id;
if(hasIdAsNameLabel) pt.nameLabel = pt.id;
else {
if(hasName) d.nameLabel = trace.name;
if(hasLocation) thisText.push(d.id);
if(hasName) pt.nameLabel = trace.name;
if(hasLocation) thisText.push(pt.id);
}

if(hasZ) thisText.push(formatter(d.z));
if(hasText) thisText.push(d.tx);
if(hasZ) thisText.push(formatter(pt.z));
if(hasText) thisText.push(pt.tx);

pt.textLabel = thisText.join('<br>');
};
}

d.textLabel = thisText.join('<br>');
function makeEventDataFunc(trace) {
return function(pt, ptIndex) {
return {points: [{
data: trace._input,
fullData: trace,
curveNumber: trace.index,
pointNumber: ptIndex,
location: pt.id,
z: pt.z
}]};
};
}
83 changes: 53 additions & 30 deletions src/traces/scattergeo/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,19 +116,14 @@ function makeLineGeoJSON(trace) {
}

plotScatterGeo.plot = function(geo, scattergeoData) {
var gScatterGeo = geo.framework.select('g.scattergeolayer'),
topojson = geo.topojson;

// TODO move to more d3-idiomatic pattern (that's work on replot)
// N.B. html('') does not work in IE11
gScatterGeo.selectAll('*').remove();

var gScatterGeoTraces = gScatterGeo
.selectAll('g.trace.scatter')
var gScatterGeoTraces = geo.framework.select('.scattergeolayer')
.selectAll('g.trace.scattergeo')
.data(scattergeoData);

gScatterGeoTraces.enter().append('g')
.attr('class', 'trace scattergeo');
.attr('class', 'trace scattergeo');

gScatterGeoTraces.exit().remove();

// TODO add hover - how?
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't need this anymore 🎉

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh, we still need this unfortunately. scattergeo trace with mode 'lines' don't have any hover text at the moment.

gScatterGeoTraces
Expand All @@ -152,28 +147,37 @@ plotScatterGeo.plot = function(geo, scattergeoData) {
return;
}

var cdi = plotScatterGeo.calcGeoJSON(trace, topojson),
cleanHoverLabelsFunc = makeCleanHoverLabelsFunc(geo, trace);
var cdi = plotScatterGeo.calcGeoJSON(trace, geo.topojson),
cleanHoverLabelsFunc = makeCleanHoverLabelsFunc(geo, trace),
eventDataFunc = makeEventDataFunc(trace);

var hoverinfo = trace.hoverinfo,
hasNameLabel = (hoverinfo === 'all' ||
hoverinfo.indexOf('name') !== -1);
hasNameLabel = (
hoverinfo === 'all' ||
hoverinfo.indexOf('name') !== -1
);

function handleMouseOver(d) {
function handleMouseOver(pt, ptIndex) {
if(!geo.showHover) return;

var xy = geo.projection([d.lon, d.lat]);
cleanHoverLabelsFunc(d);
var xy = geo.projection([pt.lon, pt.lat]);
cleanHoverLabelsFunc(pt);

Fx.loneHover({
x: xy[0],
y: xy[1],
name: hasNameLabel ? trace.name : undefined,
text: d.textLabel,
color: d.mc || (trace.marker || {}).color
text: pt.textLabel,
color: pt.mc || (trace.marker || {}).color
}, {
container: geo.hoverContainer.node()
});

geo.graphDiv.emit('plotly_hover', eventDataFunc(pt, ptIndex));
}

function handleClick(pt, ptIndex) {
geo.graphDiv.emit('plotly_click', eventDataFunc(pt, ptIndex));
}

if(showMarkers) {
Expand All @@ -182,6 +186,7 @@ plotScatterGeo.plot = function(geo, scattergeoData) {
.enter().append('path')
.attr('class', 'point')
.on('mouseover', handleMouseOver)
.on('click', handleClick)
.on('mouseout', function() {
Fx.loneUnhover(geo.hoverContainer);
})
Expand Down Expand Up @@ -237,11 +242,13 @@ function makeCleanHoverLabelsFunc(geo, trace) {
}

var hoverinfoParts = (hoverinfo === 'all') ?
attributes.hoverinfo.flags :
hoverinfo.split('+');
attributes.hoverinfo.flags :
hoverinfo.split('+');

var hasLocation = (hoverinfoParts.indexOf('location') !== -1 &&
Array.isArray(trace.locations)),
var hasLocation = (
hoverinfoParts.indexOf('location') !== -1 &&
Array.isArray(trace.locations)
),
hasLon = (hoverinfoParts.indexOf('lon') !== -1),
hasLat = (hoverinfoParts.indexOf('lat') !== -1),
hasText = (hoverinfoParts.indexOf('text') !== -1);
Expand All @@ -251,18 +258,34 @@ function makeCleanHoverLabelsFunc(geo, trace) {
return Axes.tickText(axis, axis.c2l(val), 'hover').text + '\u00B0';
}

return function cleanHoverLabelsFunc(d) {
return function cleanHoverLabelsFunc(pt) {
var thisText = [];

if(hasLocation) thisText.push(d.location);
if(hasLocation) thisText.push(pt.location);
else if(hasLon && hasLat) {
thisText.push('(' + formatter(d.lon) + ', ' + formatter(d.lat) + ')');
thisText.push('(' + formatter(pt.lon) + ', ' + formatter(pt.lat) + ')');
}
else if(hasLon) thisText.push('lon: ' + formatter(d.lon));
else if(hasLat) thisText.push('lat: ' + formatter(d.lat));
else if(hasLon) thisText.push('lon: ' + formatter(pt.lon));
else if(hasLat) thisText.push('lat: ' + formatter(pt.lat));

if(hasText) thisText.push(pt.tx || trace.text);

if(hasText) thisText.push(d.tx || trace.text);
pt.textLabel = thisText.join('<br>');
};
}

d.textLabel = thisText.join('<br>');
function makeEventDataFunc(trace) {
var hasLocation = Array.isArray(trace.locations);

return function(pt, ptIndex) {
return {points: [{
data: trace._input,
fullData: trace,
curveNumber: trace.index,
pointNumber: ptIndex,
lon: pt.lon,
lat: pt.lat,
location: hasLocation ? pt.location : null
}]};
};
}
Loading