Skip to content
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

Better hover for choropleth traces #1401

Merged
merged 6 commits into from
Feb 27, 2017
Merged
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
9 changes: 3 additions & 6 deletions src/plot_api/plot_api.js
Original file line number Diff line number Diff line change
@@ -2762,11 +2762,6 @@ function makePlotFramework(gd) {
fullLayout._glcontainer.enter().append('div')
.classed('gl-container', true);

fullLayout._geocontainer = fullLayout._paperdiv.selectAll('.geo-container')
.data([0]);
fullLayout._geocontainer.enter().append('div')
.classed('geo-container', true);

fullLayout._paperdiv.selectAll('.main-svg').remove();

fullLayout._paper = fullLayout._paperdiv.insert('svg', ':first-child')
Copy link
Contributor Author

Choose a reason for hiding this comment

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

An artefact from the first iteration back in July 2015 - where I found that Fx.lonehover worked best off a plain <div> (fast-forward today, this not longer the case).

@@ -2810,6 +2805,9 @@ function makePlotFramework(gd) {
// single ternary layer for the whole plot
fullLayout._ternarylayer = fullLayout._paper.append('g').classed('ternarylayer', true);

// single geo layer for the whole plot
fullLayout._geolayer = fullLayout._paper.append('g').classed('geolayer', true);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now geo lies side-by-side with other SVG subplot types 🎉


// upper shape layer
// (only for shapes to be drawn above the whole plot, including subplots)
var layerAbove = fullLayout._paper.append('g')
@@ -2824,7 +2822,6 @@ function makePlotFramework(gd) {

// fill in image server scrape-svg
fullLayout._glimages = fullLayout._paper.append('g').classed('glimages', true);
fullLayout._geoimages = fullLayout._paper.append('g').classed('geoimages', true);

// lastly info (legend, annotations) and hover layers go on top
// these are in a different svg element normally, but get collapsed into a single
6 changes: 0 additions & 6 deletions src/plot_api/subroutines.js
Original file line number Diff line number Diff line change
@@ -306,12 +306,6 @@ exports.doModeBar = function(gd) {
// no need to do this for gl2d subplots,
// Plots.linkSubplots takes care of it all.

subplotIds = Plots.getSubplotIds(fullLayout, 'geo');
for(i = 0; i < subplotIds.length; i++) {
var geo = fullLayout[subplotIds[i]]._subplot;
geo.updateFx(fullLayout.hovermode);
}

return Plots.previousPromises(gd);
};

2 changes: 2 additions & 0 deletions src/plots/cartesian/graph_interact.js
Original file line number Diff line number Diff line change
@@ -974,6 +974,8 @@ function createHoverText(hoverData, opts) {
contrastColor = tinycolor(traceColor).getBrightness() > 128 ?
'#000' : Color.background;

// to get custom 'name' labels pass cleanPoint
if(d.nameOverride !== undefined) d.name = d.nameOverride;

if(d.name && d.zLabelVal === undefined) {
// strip out our pseudo-html elements from d.name (if it exists at all)
94 changes: 30 additions & 64 deletions src/plots/geo/geo.js
Original file line number Diff line number Diff line change
@@ -25,22 +25,19 @@ var createGeoZoom = require('./zoom');
var createGeoZoomReset = require('./zoom_reset');
var constants = require('./constants');

var xmlnsNamespaces = require('../../constants/xmlns_namespaces');
var topojsonUtils = require('../../lib/topojson_utils');
var topojsonFeature = require('topojson-client').feature;

// add a few projection types to d3.geo
addProjectionsToD3(d3);


function Geo(options, fullLayout) {
function Geo(options) {
this.id = options.id;
this.graphDiv = options.graphDiv;
this.container = options.container;
this.topojsonURL = options.topojsonURL;

this.hoverContainer = null;

this.topojsonName = null;
this.topojson = null;

@@ -54,11 +51,7 @@ function Geo(options, fullLayout) {
this.zoom = null;
this.zoomReset = null;

this.xaxis = null;
this.yaxis = null;

this.makeFramework();
this.updateFx(fullLayout.hovermode);

this.traceHash = {};
}
@@ -178,15 +171,6 @@ proto.onceTopojsonIsLoaded = function(geoCalcData, geoLayout) {
this.render();
};

proto.updateFx = function(hovermode) {
this.showHover = (hovermode !== false);

// TODO should more strict, any layout.hovermode other
// then false will make all geo subplot display hover text.
// Instead each geo should have its own geo.hovermode
// to control hover visibility independently of other subplots.
};

proto.makeProjection = function(geoLayout) {
var projLayout = geoLayout.projection,
projType = projLayout.type,
@@ -232,38 +216,30 @@ proto.makePath = function() {
this.path = d3.geo.path().projection(this.projection);
};

/*
* <div this.container>
* <div this.geoDiv>
* <svg this.hoverContainer>
* <svg this.framework>
*/
proto.makeFramework = function() {
var geoDiv = this.geoDiv = d3.select(this.container).append('div');
geoDiv
.attr('id', this.id)
.style('position', 'absolute');

// only choropleth traces use this,
// scattergeo traces use Fx.hover and fullLayout._hoverlayer
var hoverContainer = this.hoverContainer = geoDiv.append('svg');
hoverContainer
.attr(xmlnsNamespaces.svgAttrs)
.style({
'position': 'absolute',
'z-index': 20,
'pointer-events': 'none'
});

var framework = this.framework = geoDiv.append('svg');
var fullLayout = this.graphDiv._fullLayout;
var clipId = 'clip' + fullLayout._uid + this.id;

var defGroup = fullLayout._defs.selectAll('g.clips')
.data([0]);
defGroup.enter().append('g')
.classed('clips', true);

var clipDef = this.clipDef = defGroup.selectAll('#' + clipId)
.data([0]);

clipDef.enter().append('clipPath').attr('id', clipId)
.append('rect');

var framework = this.framework = d3.select(this.container).append('g');

framework
.attr(xmlnsNamespaces.svgAttrs)
.attr({
'position': 'absolute',
'preserveAspectRatio': 'none'
});
.attr('class', 'geo ' + this.id)
.style('pointer-events', 'all')
.call(Drawing.setClipUrl, clipId);

framework.append('g').attr('class', 'bglayer')
framework.append('g')
.attr('class', 'bglayer')
.append('rect');

framework.append('g').attr('class', 'baselayer');
@@ -274,8 +250,6 @@ proto.makeFramework = function() {
// N.B. disable dblclick zoom default
framework.on('dblclick.zoom', null);

// TODO use clip paths instead of nested SVG

this.xaxis = { _id: 'x' };
this.yaxis = { _id: 'y' };
};
@@ -286,28 +260,20 @@ proto.adjustLayout = function(geoLayout, graphSize) {
var left = graphSize.l + graphSize.w * domain.x[0] + geoLayout._marginX,
top = graphSize.t + graphSize.h * (1 - domain.y[1]) + geoLayout._marginY;

this.geoDiv.style({
left: left + 'px',
top: top + 'px',
width: geoLayout._width + 'px',
height: geoLayout._height + 'px'
});
Drawing.setTranslate(this.framework, left, top);

this.hoverContainer.attr({
var dimsAttrs = {
x: 0,
y: 0,
width: geoLayout._width,
height: geoLayout._height
});
};

this.framework.attr({
width: geoLayout._width,
height: geoLayout._height
});
this.clipDef.select('rect')
.attr(dimsAttrs);

this.framework.select('.bglayer').select('rect')
.attr({
width: geoLayout._width,
height: geoLayout._height
})
.attr(dimsAttrs)
.call(Color.fill, geoLayout.bgcolor);

this.xaxis._offset = left;
34 changes: 4 additions & 30 deletions src/plots/geo/index.js
Original file line number Diff line number Diff line change
@@ -48,16 +48,13 @@ exports.plot = function plotGeo(gd) {
geoCalcData = Plots.getSubplotCalcData(calcData, 'geo', geoId),
geo = fullLayout[geoId]._subplot;

// If geo is not instantiated, create one!
if(!geo) {
geo = new Geo({
id: geoId,
graphDiv: gd,
container: fullLayout._geocontainer.node(),
container: fullLayout._geolayer.node(),
topojsonURL: gd._context.topojsonURL
},
fullLayout
);
});

fullLayout[geoId]._subplot = geo;
}
@@ -74,31 +71,8 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout)
var oldGeo = oldFullLayout[oldGeoKey]._subplot;

if(!newFullLayout[oldGeoKey] && !!oldGeo) {
oldGeo.geoDiv.remove();
oldGeo.framework.remove();
oldGeo.clipDef.remove();
}
}
};

exports.toSVG = function(gd) {
var fullLayout = gd._fullLayout,
geoIds = Plots.getSubplotIds(fullLayout, 'geo'),
size = fullLayout._size;

for(var i = 0; i < geoIds.length; i++) {
var geoLayout = fullLayout[geoIds[i]],
domain = geoLayout.domain,
geoFramework = geoLayout._subplot.framework;

geoFramework.attr('style', null);
geoFramework
.attr({
x: size.l + size.w * domain.x[0] + geoLayout._marginX,
y: size.t + size.h * (1 - domain.y[1]) + geoLayout._marginY,
width: geoLayout._width,
height: geoLayout._height
});

fullLayout._geoimages.node()
.appendChild(geoFramework.node());
}
};
10 changes: 2 additions & 8 deletions src/plots/geo/zoom_reset.js
Original file line number Diff line number Diff line change
@@ -9,9 +9,7 @@

'use strict';

var Fx = require('../cartesian/graph_interact');

function createGeoZoomReset(geo, geoLayout) {
module.exports = function createGeoZoomReset(geo, geoLayout) {
var projection = geo.projection,
zoom = geo.zoom;

@@ -22,12 +20,8 @@ function createGeoZoomReset(geo, geoLayout) {
zoom.scale(projection.scale());
zoom.translate(projection.translate());

Fx.loneUnhover(geo.hoverContainer);

geo.render();
};

return zoomReset;
}

module.exports = createGeoZoomReset;
};
17 changes: 17 additions & 0 deletions src/traces/choropleth/event_data.js
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;
};
68 changes: 68 additions & 0 deletions src/traces/choropleth/hover.js
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;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

see ec6c1b1

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>');
}
7 changes: 3 additions & 4 deletions src/traces/choropleth/index.js
Original file line number Diff line number Diff line change
@@ -15,10 +15,9 @@ Choropleth.attributes = require('./attributes');
Choropleth.supplyDefaults = require('./defaults');
Choropleth.colorbar = require('../heatmap/colorbar');
Choropleth.calc = require('./calc');
Choropleth.plot = require('./plot').plot;

// add dummy hover handler to skip Fx.hover w/o warnings
Choropleth.hoverPoints = function() {};
Choropleth.plot = require('./plot');
Choropleth.hoverPoints = require('./hover');
Choropleth.eventData = require('./event_data');

Choropleth.moduleType = 'trace';
Choropleth.name = 'choropleth';
Loading