Skip to content

Commit da0e59e

Browse files
authored
Merge pull request #1461 from rpaskowitz/dropline
Cartesian dropline support
2 parents e650a7c + 5925049 commit da0e59e

27 files changed

+623
-135
lines changed

build/ploticon.js

+6
Original file line numberDiff line numberDiff line change
@@ -114,5 +114,11 @@ module.exports = {
114114
'path': 'm0 850l0-143 143 0 0 143-143 0z m286 0l0-143 143 0 0 143-143 0z m285 0l0-143 143 0 0 143-143 0z m286 0l0-143 143 0 0 143-143 0z m-857-286l0-143 143 0 0 143-143 0z m857 0l0-143 143 0 0 143-143 0z m-857-285l0-143 143 0 0 143-143 0z m857 0l0-143 143 0 0 143-143 0z m-857-286l0-143 143 0 0 143-143 0z m286 0l0-143 143 0 0 143-143 0z m285 0l0-143 143 0 0 143-143 0z m286 0l0-143 143 0 0 143-143 0z',
115115
'ascent': 850,
116116
'descent': -150
117+
},
118+
'spikeline': {
119+
'width': 1000,
120+
'path': 'M512 409c0-57-46-104-103-104-57 0-104 47-104 104 0 57 47 103 104 103 57 0 103-46 103-103z m-327-39l92 0 0 92-92 0z m-185 0l92 0 0 92-92 0z m370-186l92 0 0 93-92 0z m0-184l92 0 0 92-92 0z',
121+
'ascent': 850,
122+
'descent': -150
117123
}
118124
};

src/components/dragelement/unhover.js

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ unhover.raw = function unhoverRaw(gd, evt) {
4141
}
4242

4343
fullLayout._hoverlayer.selectAll('g').remove();
44+
fullLayout._hoverlayer.selectAll('line').remove();
45+
fullLayout._hoverlayer.selectAll('circle').remove();
4446
gd._hoverdata = undefined;
4547

4648
if(evt.target && oldhoverdata) {

src/components/drawing/attributes.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Copyright 2012-2017, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
10+
'use strict';
11+
12+
exports.dash = {
13+
valType: 'string',
14+
// string type usually doesn't take values... this one should really be
15+
// a special type or at least a special coercion function, from the GUI
16+
// you only get these values but elsewhere the user can supply a list of
17+
// dash lengths in px, and it will be honored
18+
values: ['solid', 'dot', 'dash', 'longdash', 'dashdot', 'longdashdot'],
19+
dflt: 'solid',
20+
role: 'style',
21+
description: [
22+
'Sets the dash style of lines. Set to a dash type string',
23+
'(*solid*, *dot*, *dash*, *longdash*, *dashdot*, or *longdashdot*)',
24+
'or a dash length list in px (eg *5px,10px,2px,2px*).'
25+
].join(' ')
26+
};

src/components/drawing/index.js

+12-4
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,17 @@ drawing.lineGroupStyle = function(s, lw, lc, ld) {
113113

114114
drawing.dashLine = function(s, dash, lineWidth) {
115115
lineWidth = +lineWidth || 0;
116+
117+
dash = drawing.dashStyle(dash, lineWidth);
118+
119+
s.style({
120+
'stroke-dasharray': dash,
121+
'stroke-width': lineWidth + 'px'
122+
});
123+
};
124+
125+
drawing.dashStyle = function(dash, lineWidth) {
126+
lineWidth = +lineWidth || 1;
116127
var dlw = Math.max(lineWidth, 3);
117128

118129
if(dash === 'solid') dash = '';
@@ -127,10 +138,7 @@ drawing.dashLine = function(s, dash, lineWidth) {
127138
}
128139
// otherwise user wrote the dasharray themselves - leave it be
129140

130-
s.style({
131-
'stroke-dasharray': dash,
132-
'stroke-width': lineWidth + 'px'
133-
});
141+
return dash;
134142
};
135143

136144
drawing.fillGroupStyle = function(s) {

src/components/modebar/buttons.js

+61-5
Original file line numberDiff line numberDiff line change
@@ -177,17 +177,20 @@ function handleCartesian(gd, ev) {
177177
astr = button.getAttribute('data-attr'),
178178
val = button.getAttribute('data-val') || true,
179179
fullLayout = gd._fullLayout,
180-
aobj = {};
180+
aobj = {},
181+
axList = Axes.list(gd, null, true),
182+
ax,
183+
allEnabled = 'on',
184+
i;
181185

182186
if(astr === 'zoom') {
183187
var mag = (val === 'in') ? 0.5 : 2,
184188
r0 = (1 + mag) / 2,
185-
r1 = (1 - mag) / 2,
186-
axList = Axes.list(gd, null, true);
189+
r1 = (1 - mag) / 2;
187190

188-
var ax, axName;
191+
var axName;
189192

190-
for(var i = 0; i < axList.length; i++) {
193+
for(i = 0; i < axList.length; i++) {
191194
ax = axList[i];
192195

193196
if(!ax.fixedrange) {
@@ -202,6 +205,12 @@ function handleCartesian(gd, ev) {
202205
aobj[axName + '.range[0]'] = rangeInitial[0];
203206
aobj[axName + '.range[1]'] = rangeInitial[1];
204207
}
208+
if(ax._showSpikeInitial !== undefined) {
209+
aobj[axName + '.showspikes'] = ax._showSpikeInitial;
210+
if(allEnabled === 'on' && !ax._showSpikeInitial) {
211+
allEnabled = 'off';
212+
}
213+
}
205214
}
206215
else {
207216
var rangeNow = [
@@ -219,12 +228,24 @@ function handleCartesian(gd, ev) {
219228
}
220229
}
221230
}
231+
fullLayout._cartesianSpikesEnabled = allEnabled;
222232
}
223233
else {
224234
// if ALL traces have orientation 'h', 'hovermode': 'x' otherwise: 'y'
225235
if(astr === 'hovermode' && (val === 'x' || val === 'y')) {
226236
val = fullLayout._isHoriz ? 'y' : 'x';
227237
button.setAttribute('data-val', val);
238+
if(val !== 'closest') {
239+
fullLayout._cartesianSpikesEnabled = 'off';
240+
}
241+
} else if(astr === 'hovermode' && val === 'closest') {
242+
for(i = 0; i < axList.length; i++) {
243+
ax = axList[i];
244+
if(allEnabled === 'on' && !ax.showspikes) {
245+
allEnabled = 'off';
246+
}
247+
}
248+
fullLayout._cartesianSpikesEnabled = allEnabled;
228249
}
229250

230251
aobj[astr] = val;
@@ -518,3 +539,38 @@ modeBarButtons.resetViews = {
518539
// geo subplots.
519540
}
520541
};
542+
543+
modeBarButtons.toggleSpikelines = {
544+
name: 'toggleSpikelines',
545+
title: 'Toggle Spike Lines',
546+
icon: Icons.spikeline,
547+
attr: '_cartesianSpikesEnabled',
548+
val: 'on',
549+
click: function(gd) {
550+
var fullLayout = gd._fullLayout;
551+
552+
fullLayout._cartesianSpikesEnabled = fullLayout.hovermode === 'closest' ?
553+
(fullLayout._cartesianSpikesEnabled === 'on' ? 'off' : 'on') : 'on';
554+
555+
var aobj = setSpikelineVisibility(gd);
556+
557+
aobj.hovermode = 'closest';
558+
Plotly.relayout(gd, aobj);
559+
}
560+
};
561+
562+
function setSpikelineVisibility(gd) {
563+
var fullLayout = gd._fullLayout,
564+
axList = Axes.list(gd, null, true),
565+
ax,
566+
axName,
567+
aobj = {};
568+
569+
for(var i = 0; i < axList.length; i++) {
570+
ax = axList[i];
571+
axName = ax._name;
572+
aobj[axName + '.showspikes'] = fullLayout._cartesianSpikesEnabled === 'on' ? true : false;
573+
}
574+
575+
return aobj;
576+
}

src/components/modebar/manage.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ function getButtonGroups(gd, buttonsToRemove, buttonsToAdd) {
138138
addGroup(['hoverClosestGl2d']);
139139
}
140140
else if(hasCartesian) {
141-
addGroup(['hoverClosestCartesian', 'hoverCompareCartesian']);
141+
addGroup(['toggleSpikelines', 'hoverClosestCartesian', 'hoverCompareCartesian']);
142142
}
143143
else if(hasPie) {
144144
addGroup(['hoverClosestPie']);

src/components/modebar/modebar.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ proto.createButton = function(config) {
149149
button.setAttribute('data-toggle', config.toggle || false);
150150
if(config.toggle) d3.select(button).classed('active', true);
151151

152-
button.appendChild(this.createIcon(config.icon || Icons.question));
152+
button.appendChild(this.createIcon(config.icon || Icons.question, config.name));
153153
button.setAttribute('data-gravity', config.gravity || 'n');
154154

155155
return button;
@@ -162,7 +162,7 @@ proto.createButton = function(config) {
162162
* @Param {string} thisIcon.path
163163
* @Return {HTMLelement}
164164
*/
165-
proto.createIcon = function(thisIcon) {
165+
proto.createIcon = function(thisIcon, name) {
166166
var iconHeight = thisIcon.ascent - thisIcon.descent,
167167
svgNS = 'http://www.w3.org/2000/svg',
168168
icon = document.createElementNS(svgNS, 'svg'),
@@ -172,8 +172,12 @@ proto.createIcon = function(thisIcon) {
172172
icon.setAttribute('width', (thisIcon.width / iconHeight) + 'em');
173173
icon.setAttribute('viewBox', [0, 0, thisIcon.width, iconHeight].join(' '));
174174

175+
var transform = name === 'toggleSpikelines' ?
176+
'matrix(1.5 0 0 -1.5 0 ' + thisIcon.ascent + ')' :
177+
'matrix(1 0 0 -1 0 ' + thisIcon.ascent + ')';
178+
175179
path.setAttribute('d', thisIcon.path);
176-
path.setAttribute('transform', 'matrix(1 0 0 -1 0 ' + thisIcon.ascent + ')');
180+
path.setAttribute('transform', transform);
177181
icon.appendChild(path);
178182

179183
return icon;

src/components/shapes/attributes.js

+3-4
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@
99
'use strict';
1010

1111
var annAttrs = require('../annotations/attributes');
12-
var scatterAttrs = require('../../traces/scatter/attributes');
12+
var scatterLineAttrs = require('../../traces/scatter/attributes').line;
13+
var dash = require('../drawing/attributes').dash;
1314
var extendFlat = require('../../lib/extend').extendFlat;
1415

15-
var scatterLineAttrs = scatterAttrs.line;
16-
1716
module.exports = {
1817
_isLinkedToArray: 'shape',
1918

@@ -151,7 +150,7 @@ module.exports = {
151150
line: {
152151
color: scatterLineAttrs.color,
153152
width: scatterLineAttrs.width,
154-
dash: scatterLineAttrs.dash,
153+
dash: dash,
155154
role: 'info'
156155
},
157156
fillcolor: {

src/fonts/ploticon/ploticon.svg

+2-1
Loading

src/plot_api/plot_api.js

+10-5
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ Plotly.plot = function(gd, data, layout, config) {
153153
makePlotFramework(gd);
154154
}
155155

156+
// save initial show spikes once per graph
157+
if(graphWasEmpty) Plotly.Axes.saveShowSpikeInitial(gd);
158+
156159
// prepare the data and find the autorange
157160

158161
// generate calcdata, if we need to
@@ -2149,14 +2152,16 @@ function _relayout(gd, aobj) {
21492152
flags.doticks = flags.dolayoutstyle = true;
21502153
}
21512154
/*
2152-
* hovermode and dragmode don't need any redrawing, since they just
2153-
* affect reaction to user input, everything else, assume full replot.
2155+
* hovermode, dragmode, and spikes don't need any redrawing, since they just
2156+
* affect reaction to user input. Everything else, assume full replot.
21542157
* height, width, autosize get dealt with below. Except for the case of
21552158
* of subplots - scenes - which require scene.updateFx to be called.
21562159
*/
2157-
else if(['hovermode', 'dragmode'].indexOf(ai) !== -1) flags.domodebar = true;
2158-
else if(['hovermode', 'dragmode', 'height',
2159-
'width', 'autosize'].indexOf(ai) === -1) {
2160+
else if(['hovermode', 'dragmode'].indexOf(ai) !== -1 ||
2161+
ai.indexOf('spike') !== -1) {
2162+
flags.domodebar = true;
2163+
}
2164+
else if(['height', 'width', 'autosize'].indexOf(ai) === -1) {
21602165
flags.doplot = true;
21612166
}
21622167

0 commit comments

Comments
 (0)