-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Better hover for choropleth traces #1401
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
Changes from 1 commit
bdbb3d6
ec6c1b1
8c29dfb
b461dc1
919fe42
c32d445
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/** | ||
* Copyright 2012-2017, 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 = function eventData(out, pt) { | ||
out.location = pt.location; | ||
out.z = pt.z; | ||
|
||
return out; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
/** | ||
* Copyright 2012-2017, 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 Axes = require('../../plots/cartesian/axes'); | ||
var attributes = require('./attributes'); | ||
|
||
module.exports = function hoverPoints(pointData) { | ||
var cd = pointData.cd; | ||
var trace = cd[0].trace; | ||
var geo = pointData.subplot; | ||
|
||
// set on choropleth paths 'mouseover' | ||
var pt = geo.choroplethHoverPt; | ||
|
||
if(!pt) return; | ||
|
||
var centroid = geo.projection(pt.properties.ct); | ||
|
||
pointData.x0 = pointData.x1 = centroid[0]; | ||
pointData.y0 = pointData.y1 = centroid[1]; | ||
|
||
pointData.index = pt.index; | ||
pointData.location = pt.id; | ||
pointData.z = pt.z; | ||
|
||
makeHoverInfo(pointData, trace, pt, geo.mockAxis); | ||
|
||
return [pointData]; | ||
}; | ||
|
||
function makeHoverInfo(pointData, trace, pt, axis) { | ||
var hoverinfo = trace.hoverinfo; | ||
|
||
var parts = (hoverinfo === 'all') ? | ||
attributes.hoverinfo.flags : | ||
hoverinfo.split('+'); | ||
|
||
var hasName = (parts.indexOf('name') !== -1), | ||
hasLocation = (parts.indexOf('location') !== -1), | ||
hasZ = (parts.indexOf('z') !== -1), | ||
hasText = (parts.indexOf('text') !== -1), | ||
hasIdAsNameLabel = !hasName && hasLocation; | ||
|
||
var text = []; | ||
|
||
function formatter(val) { | ||
return Axes.tickText(axis, axis.c2l(val), 'hover').text; | ||
} | ||
|
||
if(hasIdAsNameLabel) pointData.nameOverride = pt.id; | ||
else { | ||
if(hasName) pointData.nameOverride = trace.name; | ||
if(hasLocation) text.push(pt.id); | ||
} | ||
|
||
if(hasZ) text.push(formatter(pt.z)); | ||
if(hasText) text.push(pt.tx); | ||
|
||
pointData.extraText = text.join('<br>'); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,8 +11,6 @@ | |
|
||
var d3 = require('d3'); | ||
|
||
var Axes = require('../../plots/cartesian/axes'); | ||
var Fx = require('../../plots/cartesian/graph_interact'); | ||
var Color = require('../../components/color'); | ||
var Drawing = require('../../components/drawing'); | ||
var Colorscale = require('../../components/colorscale'); | ||
|
@@ -22,42 +20,8 @@ var locationToFeature = require('../../lib/geo_location_utils').locationToFeatur | |
var arrayToCalcItem = require('../../lib/array_to_calc_item'); | ||
|
||
var constants = require('../../plots/geo/constants'); | ||
var attributes = require('./attributes'); | ||
|
||
var plotChoropleth = module.exports = {}; | ||
|
||
|
||
plotChoropleth.calcGeoJSON = function(trace, topojson) { | ||
var cdi = [], | ||
locations = trace.locations, | ||
len = locations.length, | ||
features = getTopojsonFeatures(trace, topojson), | ||
markerLine = (trace.marker || {}).line || {}; | ||
|
||
var feature; | ||
|
||
for(var i = 0; i < len; i++) { | ||
feature = locationToFeature(trace.locationmode, locations[i], features); | ||
|
||
if(!feature) continue; // filter the blank features here | ||
|
||
// 'data_array' attributes | ||
feature.z = trace.z[i]; | ||
if(trace.text !== undefined) feature.tx = trace.text[i]; | ||
|
||
// 'arrayOk' attributes | ||
arrayToCalcItem(markerLine.color, feature, 'mlc', i); | ||
arrayToCalcItem(markerLine.width, feature, 'mlw', i); | ||
|
||
cdi.push(feature); | ||
} | ||
|
||
if(cdi.length > 0) cdi[0].trace = trace; | ||
|
||
return cdi; | ||
}; | ||
|
||
plotChoropleth.plot = function(geo, calcData, geoLayout) { | ||
module.exports = function plot(geo, calcData, geoLayout) { | ||
|
||
function keyFunc(d) { return d[0].trace.uid; } | ||
|
||
|
@@ -79,54 +43,20 @@ plotChoropleth.plot = function(geo, calcData, geoLayout) { | |
|
||
gChoroplethTraces.each(function(calcTrace) { | ||
var trace = calcTrace[0].trace, | ||
cdi = plotChoropleth.calcGeoJSON(trace, geo.topojson), | ||
cleanHoverLabelsFunc = makeCleanHoverLabelsFunc(geo, trace), | ||
eventDataFunc = makeEventDataFunc(trace); | ||
|
||
// keep ref to event data in this scope for plotly_unhover | ||
var eventData = null; | ||
|
||
function handleMouseOver(pt, ptIndex) { | ||
if(!geo.showHover) return; | ||
|
||
var xy = geo.projection(pt.properties.ct); | ||
cleanHoverLabelsFunc(pt); | ||
|
||
Fx.loneHover({ | ||
x: xy[0], | ||
y: xy[1], | ||
name: pt.nameLabel, | ||
text: pt.textLabel | ||
}, { | ||
container: geo.hoverContainer.node() | ||
}); | ||
|
||
eventData = eventDataFunc(pt, ptIndex); | ||
cdi = calcGeoJSON(trace, geo.topojson); | ||
|
||
geo.graphDiv.emit('plotly_hover', eventData); | ||
} | ||
|
||
function handleClick(pt, ptIndex) { | ||
geo.graphDiv.emit('plotly_click', eventDataFunc(pt, ptIndex)); | ||
} | ||
|
||
var paths = d3.select(this).selectAll('path.choroplethlocation') | ||
.data(cdi); | ||
var paths = d3.select(this) | ||
.selectAll('path.choroplethlocation') | ||
.data(cdi); | ||
|
||
paths.enter().append('path') | ||
.classed('choroplethlocation', true) | ||
.on('mouseover', handleMouseOver) | ||
.on('click', handleClick) | ||
.on('mouseout', function() { | ||
Fx.loneUnhover(geo.hoverContainer); | ||
|
||
geo.graphDiv.emit('plotly_unhover', eventData); | ||
}) | ||
.on('mousedown', function() { | ||
// to simulate the 'zoomon' event | ||
Fx.loneUnhover(geo.hoverContainer); | ||
.on('mouseover', function(pt) { | ||
geo.choroplethHoverPt = pt; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Alternatively, we could have looped over all the features and use something like |
||
}) | ||
.on('mouseup', handleMouseOver); // ~ 'zoomend' | ||
.on('mouseout', function() { | ||
geo.choroplethHoverPt = null; | ||
}); | ||
|
||
paths.exit().remove(); | ||
}); | ||
|
@@ -141,87 +71,62 @@ plotChoropleth.plot = function(geo, calcData, geoLayout) { | |
geo.styleLayer(gBaseLayerOverChoropleth, layerName, geoLayout); | ||
} | ||
|
||
plotChoropleth.style(geo); | ||
style(geo); | ||
}; | ||
|
||
plotChoropleth.style = function(geo) { | ||
geo.framework.selectAll('g.trace.choropleth') | ||
.each(function(calcTrace) { | ||
var trace = calcTrace[0].trace, | ||
s = d3.select(this), | ||
marker = trace.marker || {}, | ||
markerLine = marker.line || {}; | ||
|
||
var sclFunc = Colorscale.makeColorScaleFunc( | ||
Colorscale.extractScale( | ||
trace.colorscale, | ||
trace.zmin, | ||
trace.zmax | ||
) | ||
); | ||
|
||
s.selectAll('path.choroplethlocation') | ||
.each(function(pt) { | ||
d3.select(this) | ||
.attr('fill', function(pt) { return sclFunc(pt.z); }) | ||
.call(Color.stroke, pt.mlc || markerLine.color) | ||
.call(Drawing.dashLine, '', pt.mlw || markerLine.width || 0); | ||
}); | ||
}); | ||
}; | ||
function calcGeoJSON(trace, topojson) { | ||
var cdi = [], | ||
locations = trace.locations, | ||
len = locations.length, | ||
features = getTopojsonFeatures(trace, topojson), | ||
markerLine = (trace.marker || {}).line || {}; | ||
|
||
function makeCleanHoverLabelsFunc(geo, trace) { | ||
var hoverinfo = trace.hoverinfo; | ||
var feature; | ||
|
||
if(hoverinfo === 'none' || hoverinfo === 'skip') { | ||
return function cleanHoverLabelsFunc(pt) { | ||
delete pt.nameLabel; | ||
delete pt.textLabel; | ||
}; | ||
} | ||
for(var i = 0; i < len; i++) { | ||
feature = locationToFeature(trace.locationmode, locations[i], features); | ||
|
||
var hoverinfoParts = (hoverinfo === 'all') ? | ||
attributes.hoverinfo.flags : | ||
hoverinfo.split('+'); | ||
if(!feature) continue; // filter the blank features here | ||
|
||
var hasName = (hoverinfoParts.indexOf('name') !== -1), | ||
hasLocation = (hoverinfoParts.indexOf('location') !== -1), | ||
hasZ = (hoverinfoParts.indexOf('z') !== -1), | ||
hasText = (hoverinfoParts.indexOf('text') !== -1), | ||
hasIdAsNameLabel = !hasName && hasLocation; | ||
// 'data_array' attributes | ||
feature.z = trace.z[i]; | ||
if(trace.text !== undefined) feature.tx = trace.text[i]; | ||
|
||
function formatter(val) { | ||
var axis = geo.mockAxis; | ||
return Axes.tickText(axis, axis.c2l(val), 'hover').text; | ||
} | ||
// 'arrayOk' attributes | ||
arrayToCalcItem(markerLine.color, feature, 'mlc', i); | ||
arrayToCalcItem(markerLine.width, feature, 'mlw', i); | ||
|
||
return function cleanHoverLabelsFunc(pt) { | ||
// put location id in name label container | ||
// if name isn't part of hoverinfo | ||
var thisText = []; | ||
// for event data | ||
feature.index = i; | ||
|
||
if(hasIdAsNameLabel) pt.nameLabel = pt.id; | ||
else { | ||
if(hasName) pt.nameLabel = trace.name; | ||
if(hasLocation) thisText.push(pt.id); | ||
} | ||
cdi.push(feature); | ||
} | ||
|
||
if(hasZ) thisText.push(formatter(pt.z)); | ||
if(hasText) thisText.push(pt.tx); | ||
if(cdi.length > 0) cdi[0].trace = trace; | ||
|
||
pt.textLabel = thisText.join('<br>'); | ||
}; | ||
return cdi; | ||
} | ||
|
||
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 | ||
}]}; | ||
}; | ||
function style(geo) { | ||
geo.framework.selectAll('g.trace.choropleth').each(function(calcTrace) { | ||
var trace = calcTrace[0].trace, | ||
s = d3.select(this), | ||
marker = trace.marker || {}, | ||
markerLine = marker.line || {}; | ||
|
||
var sclFunc = Colorscale.makeColorScaleFunc( | ||
Colorscale.extractScale( | ||
trace.colorscale, | ||
trace.zmin, | ||
trace.zmax | ||
) | ||
); | ||
|
||
s.selectAll('path.choroplethlocation').each(function(pt) { | ||
d3.select(this) | ||
.attr('fill', function(pt) { return sclFunc(pt.z); }) | ||
.call(Color.stroke, pt.mlc || markerLine.color) | ||
.call(Drawing.dashLine, '', pt.mlw || markerLine.width || 0); | ||
}); | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see ec6c1b1