diff --git a/src/plots/geo/geo.js b/src/plots/geo/geo.js
index 2f08fafcbfb..2472ab9956e 100644
--- a/src/plots/geo/geo.js
+++ b/src/plots/geo/geo.js
@@ -33,6 +33,7 @@ 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;
@@ -40,7 +41,7 @@ function Geo(options, fullLayout) {
// 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;
diff --git a/src/plots/geo/index.js b/src/plots/geo/index.js
index 772db2ed0c1..cd4aabd81ef 100644
--- a/src/plots/geo/index.js
+++ b/src/plots/geo/index.js
@@ -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
},
diff --git a/src/traces/choropleth/plot.js b/src/traces/choropleth/plot.js
index db29ecb0444..5f5573485d1 100644
--- a/src/traces/choropleth/plot.js
+++ b/src/traces/choropleth/plot.js
@@ -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));
+ }
+
+ function handleClick(pt, ptIndex) {
+ geo.graphDiv.emit('plotly_click', eventDataFunc(pt, ptIndex));
}
d3.select(this)
@@ -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);
})
@@ -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();
@@ -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);
});
});
};
@@ -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;
};
}
@@ -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('
');
+ };
+}
- d.textLabel = thisText.join('
');
+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
+ }]};
};
}
diff --git a/src/traces/scattergeo/plot.js b/src/traces/scattergeo/plot.js
index 6f8691f4bee..eee16c1d477 100644
--- a/src/traces/scattergeo/plot.js
+++ b/src/traces/scattergeo/plot.js
@@ -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?
gScatterGeoTraces
@@ -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) {
@@ -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);
})
@@ -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);
@@ -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('
');
+ };
+}
- d.textLabel = thisText.join('
');
+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
+ }]};
};
}
diff --git a/test/jasmine/tests/geo_interact_test.js b/test/jasmine/tests/geo_interact_test.js
index e59f5b62ef6..2c811b33b5e 100644
--- a/test/jasmine/tests/geo_interact_test.js
+++ b/test/jasmine/tests/geo_interact_test.js
@@ -14,14 +14,24 @@ describe('Test geo interactions', function() {
describe('mock geo_first.json', function() {
var mock = require('@mocks/geo_first.json');
+ var gd;
+
+ function mouseEventScatterGeo(type) {
+ mouseEvent(type, 300, 235);
+ }
+
+ function mouseEventChoropleth(type) {
+ mouseEvent(type, 400, 160);
+ }
beforeEach(function(done) {
- Plotly.plot(createGraphDiv(), mock.data, mock.layout).then(done);
+ gd = createGraphDiv();
+ Plotly.plot(gd, mock.data, mock.layout).then(done);
});
- describe('scattegeo hover labels', function() {
+ describe('scattergeo hover labels', function() {
beforeEach(function() {
- mouseEvent('mouseover', 300, 235);
+ mouseEventScatterGeo('mouseover');
});
it('should show one hover text group', function() {
@@ -41,9 +51,63 @@ describe('Test geo interactions', function() {
});
});
+ describe('scattergeo hover events', function() {
+ var ptData;
+
+ beforeEach(function() {
+ gd.on('plotly_hover', function(eventData) {
+ ptData = eventData.points[0];
+ });
+
+ mouseEventScatterGeo('mouseover');
+ });
+
+ it('should contain the correct fields', function() {
+ expect(Object.keys(ptData)).toEqual([
+ 'data', 'fullData', 'curveNumber', 'pointNumber',
+ 'lon', 'lat', 'location'
+ ]);
+ });
+
+ it('should show the correct point data', function() {
+ expect(ptData.lon).toEqual(0);
+ expect(ptData.lat).toEqual(0);
+ expect(ptData.location).toBe(null);
+ expect(ptData.curveNumber).toEqual(0);
+ expect(ptData.pointNumber).toEqual(0);
+ });
+ });
+
+ describe('scattergeo click events', function() {
+ var ptData;
+
+ beforeEach(function() {
+ gd.on('plotly_click', function(eventData) {
+ ptData = eventData.points[0];
+ });
+
+ mouseEventScatterGeo('click');
+ });
+
+ it('should contain the correct fields', function() {
+ expect(Object.keys(ptData)).toEqual([
+ 'data', 'fullData', 'curveNumber', 'pointNumber',
+ 'lon', 'lat', 'location'
+ ]);
+ });
+
+ it('should show the correct point data', function() {
+ expect(ptData.lon).toEqual(0);
+ expect(ptData.lat).toEqual(0);
+ expect(ptData.location).toBe(null);
+ expect(ptData.curveNumber).toEqual(0);
+ expect(ptData.pointNumber).toEqual(0);
+ });
+ });
+
describe('choropleth hover labels', function() {
beforeEach(function() {
- mouseEvent('mouseover', 400, 160);
+ mouseEventChoropleth('mouseover');
});
it('should show one hover text group', function() {
@@ -64,5 +128,57 @@ describe('Test geo interactions', function() {
});
});
+ describe('choropleth hover events', function() {
+ var ptData;
+
+ beforeEach(function() {
+ gd.on('plotly_hover', function(eventData) {
+ ptData = eventData.points[0];
+ });
+
+ mouseEventChoropleth('mouseover');
+ });
+
+ it('should contain the correct fields', function() {
+ expect(Object.keys(ptData)).toEqual([
+ 'data', 'fullData', 'curveNumber', 'pointNumber',
+ 'location', 'z'
+ ]);
+ });
+
+ it('should show the correct point data', function() {
+ expect(ptData.location).toBe('RUS');
+ expect(ptData.z).toEqual(10);
+ expect(ptData.curveNumber).toEqual(1);
+ expect(ptData.pointNumber).toEqual(2);
+ });
+ });
+
+ describe('choropleth click events', function() {
+ var ptData;
+
+ beforeEach(function() {
+ gd.on('plotly_click', function(eventData) {
+ ptData = eventData.points[0];
+ });
+
+ mouseEventChoropleth('click');
+ });
+
+ it('should contain the correct fields', function() {
+ expect(Object.keys(ptData)).toEqual([
+ 'data', 'fullData', 'curveNumber', 'pointNumber',
+ 'location', 'z'
+ ]);
+ });
+
+ it('should show the correct point data', function() {
+ expect(ptData.location).toBe('RUS');
+ expect(ptData.z).toEqual(10);
+ expect(ptData.curveNumber).toEqual(1);
+ expect(ptData.pointNumber).toEqual(2);
+ });
+ });
+
});
});