Skip to content

Commit 811f925

Browse files
committed
Add drag scroll, move scroll code to draw, fix weird width bug
1 parent b45e846 commit 811f925

File tree

3 files changed

+126
-87
lines changed

3 files changed

+126
-87
lines changed

src/components/legend/constants.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
'use strict';
2+
3+
module.exports = {
4+
scrollBarWidth: 4,
5+
scrollBarHeight: 20,
6+
scrollBarColor: '#808BA4',
7+
scrollBarMargin: 4
8+
};

src/components/legend/index.js

+118-83
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ var styleOne = require('../../traces/pie/style_one');
2525

2626
var legend = module.exports = {};
2727

28+
var constants = require('./constants');
2829
legend.layoutAttributes = require('./attributes');
2930

3031
legend.supplyLayoutDefaults = function(layoutIn, layoutOut, fullData) {
@@ -294,12 +295,18 @@ legend.texts = function(context, td, d, i, traces){
294295
text.enter().append('text').classed('legendtext', true);
295296
text.attr({
296297
x: 40,
297-
y: 0
298+
y: 0,
299+
'data-unformatted': name
300+
})
301+
.style({
302+
'text-anchor': 'start',
303+
'-webkit-user-select': 'none',
304+
'-moz-user-select': 'none',
305+
'-ms-user-select': 'none',
306+
'user-select': 'none'
298307
})
299-
.style('text-anchor', 'start')
300308
.call(Drawing.font, fullLayout.legend.font)
301-
.text(name)
302-
.attr({'data-unformatted': name});
309+
.text(name);
303310

304311
function textLayout(s){
305312
Plotly.util.convertToTspans(s, function(){
@@ -464,9 +471,11 @@ legend.draw = function(td) {
464471
var scrollBar = legendsvg.selectAll('rect.scrollbar')
465472
.data([0]);
466473
scrollBar.enter().append('rect')
467-
.attr('class', 'scrollbar')
468-
.attr('rx', 20)
469-
.attr('ry', 2)
474+
.attr({
475+
'class': 'scrollbar',
476+
'rx': 20,
477+
'ry': 2
478+
})
470479
.call(Color.fill, '#808BA4');
471480

472481
var groups = scrollBox.selectAll('g.groups')
@@ -546,6 +555,78 @@ legend.draw = function(td) {
546555
// Position and size the legend
547556
legend.repositionLegend(td, traces);
548557

558+
// Scroll section must be executed after repositionLegend.
559+
// It requires the legend width, height, x and y to position the scrollbox
560+
// and these values are mutated in repositionLegend.
561+
var gs = fullLayout._size,
562+
lx = gs.l+gs.w*opts.x,
563+
ly = gs.t+gs.h*(1-opts.y);
564+
565+
// Deal with scrolling
566+
var plotHeight = fullLayout.height - fullLayout.margin.b,
567+
scrollPosition = scrollBox.attr('viewBox') ? scrollBox.attr('viewBox').split(' ')[1] : 0,
568+
scrollheight = Math.min(plotHeight - ly, opts.height);
569+
570+
bg.style({ width: opts.width, height: scrollheight });
571+
scrollBox.attr('viewBox', '0 ' + scrollPosition +' ' + opts.width + ' ' + scrollheight);
572+
573+
legendsvg.call(Drawing.setRect, lx, ly, opts.width, scrollheight);
574+
575+
if(td.firstRender && opts.height - scrollheight > 0 && !td._context.staticPlot){
576+
577+
legendsvg.node().addEventListener('wheel', function(e){
578+
e.preventDefault();
579+
scrollHandler(Math.round(e.deltaY / 15));
580+
});
581+
582+
scrollBar.node().addEventListener('mousedown', function(e) {
583+
e.preventDefault();
584+
585+
function mMove(e) {
586+
if(e.buttons === 1){
587+
scrollHandler(e.movementY);
588+
}
589+
}
590+
591+
function mUp() {
592+
scrollBar.node().removeEventListener('mousemove', mMove);
593+
window.removeEventListener('mouseup', mUp);
594+
}
595+
596+
window.addEventListener('mousemove', mMove);
597+
window.addEventListener('mouseup', mUp);
598+
});
599+
600+
// Move scrollbar to starting position on the first render
601+
scrollBar.call(
602+
Drawing.setRect,
603+
opts.width - (constants.scrollBarWidth + constants.scrollBarMargin),
604+
constants.scrollBarMargin,
605+
constants.scrollBarWidth,
606+
constants.scrollBarHeight
607+
);
608+
}
609+
610+
function scrollHandler(delta){
611+
612+
// Scale movement to simulate native scroll performance
613+
var viewBox = scrollBox.attr('viewBox').split(' '),
614+
scrollBarTrack = scrollheight - constants.scrollBarHeight - 2 * constants.scrollBarMargin,
615+
scrollBoxY = Lib.constrain(+viewBox[1] + delta, 0, Math.max(opts.height - scrollheight, 0)),
616+
scrollBarY = scrollBoxY / (opts.height - scrollheight) * scrollBarTrack + constants.scrollBarMargin;
617+
618+
viewBox[1] = scrollBoxY;
619+
620+
scrollBox.attr('viewBox', viewBox.join(' '));
621+
scrollBar.call(
622+
Drawing.setRect,
623+
opts.width - (constants.scrollBarWidth + constants.scrollBarMargin),
624+
scrollBarY,
625+
constants.scrollBarWidth,
626+
constants.scrollBarHeight
627+
);
628+
}
629+
549630
if(td._context.editable) {
550631
var xf,
551632
yf,
@@ -579,7 +660,7 @@ legend.draw = function(td) {
579660
},
580661
doneFn: function(dragged) {
581662
Fx.setCursor(legendsvg);
582-
if(dragged && xf!==undefined && yf!==undefined) {
663+
if(dragged && xf !== undefined && yf !== undefined) {
583664
Plotly.relayout(td, {'legend.x': xf, 'legend.y': yf});
584665
}
585666
}
@@ -591,12 +672,10 @@ legend.repositionLegend = function(td, traces){
591672
var fullLayout = td._fullLayout,
592673
gs = fullLayout._size,
593674
opts = fullLayout.legend,
594-
borderwidth = opts.borderwidth,
675+
borderwidth = opts.borderwidth;
595676

596-
// add the legend elements, keeping track of the
597-
// legend size (in px) as we go
598-
legendwidth = 0,
599-
legendheight = 0;
677+
opts.width = 0,
678+
opts.height = 0,
600679

601680
traces.each(function(d){
602681
var trace = d[0].trace,
@@ -605,7 +684,7 @@ legend.repositionLegend = function(td, traces){
605684
text = g.selectAll('.legendtext'),
606685
tspans = g.selectAll('.legendtext>tspan'),
607686
tHeight = opts.font.size * 1.3,
608-
tLines = tspans[0].length||1,
687+
tLines = tspans[0].length || 1,
609688
tWidth = text.node() && Drawing.bBox(text.node()).width,
610689
mathjaxGroup = g.select('g[class*=math-group]'),
611690
textY,
@@ -620,12 +699,12 @@ legend.repositionLegend = function(td, traces){
620699
var mathjaxBB = Drawing.bBox(mathjaxGroup.node());
621700
tHeight = mathjaxBB.height;
622701
tWidth = mathjaxBB.width;
623-
mathjaxGroup.attr('transform','translate(0,'+(tHeight/4)+')');
702+
mathjaxGroup.attr('transform','translate(0,' + (tHeight / 4) + ')');
624703
}
625704
else {
626705
// approximation to height offset to center the font
627706
// to avoid getBoundingClientRect
628-
textY = tHeight * (0.3 + (1-tLines)/2);
707+
textY = tHeight * (0.3 + (1-tLines) / 2);
629708
text.attr('y',textY);
630709
tspans.attr('y',textY);
631710
}
@@ -634,22 +713,23 @@ legend.repositionLegend = function(td, traces){
634713

635714
g.attr('transform',
636715
'translate(' + borderwidth + ',' +
637-
(5 + borderwidth + legendheight + tHeightFull/2) +
716+
(5 + borderwidth + opts.height + tHeightFull / 2) +
638717
')'
639718
);
640719
bg.attr({x: 0, y: -tHeightFull / 2, height: tHeightFull});
641720

642-
legendheight += tHeightFull;
643-
legendwidth = Math.max(legendwidth, tWidth||0);
721+
opts.height += tHeightFull;
722+
opts.width = Math.max(opts.width, tWidth || 0);
644723
});
645724

646-
if(isGrouped(opts)) legendheight += (opts._lgroupsLength-1) * opts.tracegroupgap;
647725

648-
traces.selectAll('.legendtoggle')
649-
.attr('width', (td._context.editable ? 0 : legendwidth) + 40);
726+
opts.width += 45 + borderwidth * 2;
727+
opts.height += 10 + borderwidth * 2;
728+
729+
if(isGrouped(opts)) opts.height += (opts._lgroupsLength-1) * opts.tracegroupgap;
650730

651-
legendwidth += 45+borderwidth*2;
652-
legendheight += 10+borderwidth*2;
731+
traces.selectAll('.legendtoggle')
732+
.attr('width', (td._context.editable ? 0 : opts.width) + 40);
653733

654734
// now position the legend. for both x,y the positions are recorded as
655735
// fractions of the plot area (left, bottom = 0,0). Outside the plot
@@ -661,83 +741,38 @@ legend.repositionLegend = function(td, traces){
661741
ly = gs.t+gs.h*(1-opts.y);
662742

663743
var xanchor = 'left';
664-
if(opts.xanchor==='right' || (opts.xanchor==='auto' && opts.x>=2/3)) {
665-
lx -= legendwidth;
744+
if(opts.xanchor === 'right' || (opts.xanchor === 'auto' && opts.x >= 2 / 3)) {
745+
lx -= opts.width;
666746
xanchor = 'right';
667747
}
668-
else if(opts.xanchor==='center' || (opts.xanchor==='auto' && opts.x>1/3)) {
669-
lx -= legendwidth/2;
748+
else if(opts.xanchor === 'center' || (opts.xanchor === 'auto' && opts.x > 1 / 3)) {
749+
lx -= opts.width / 2;
670750
xanchor = 'center';
671751
}
672752

673753
var yanchor = 'top';
674-
if(opts.yanchor==='bottom' || (opts.yanchor==='auto' && opts.y<=1/3)) {
675-
ly -= legendheight;
754+
if(opts.yanchor === 'bottom' || (opts.yanchor === 'auto' && opts.y <= 1 / 3)) {
755+
ly -= opts.width;
676756
yanchor = 'bottom';
677757
}
678-
else if(opts.yanchor==='middle' || (opts.yanchor==='auto' && opts.y<2/3)) {
679-
ly -= legendheight/2;
758+
else if(opts.yanchor === 'middle' || (opts.yanchor === 'auto' && opts.y < 2 / 3)) {
759+
ly -= opts.height / 2;
680760
yanchor = 'middle';
681761
}
682762

683763
// make sure we're only getting full pixels
684-
legendwidth = Math.ceil(legendwidth);
685-
legendheight = Math.ceil(legendheight);
764+
opts.width = Math.ceil(opts.width);
765+
opts.height = Math.ceil(opts.height);
686766
lx = Math.round(lx);
687767
ly = Math.round(ly);
688768

689-
690-
var legendsvg = fullLayout._infolayer.selectAll('svg.legend'),
691-
scrollBox = fullLayout._infolayer.selectAll('svg.legend .scrollbox'),
692-
scrollBar = fullLayout._infolayer.selectAll('svg.legend .scrollbar'),
693-
bg = fullLayout._infolayer.selectAll('svg.legend .bg');
694-
695-
var plotHeight = fullLayout.height - fullLayout.margin.t - fullLayout.margin.b,
696-
scrollheight = Math.min(plotHeight - ly, legendheight),
697-
scrollPosition = scrollBox.attr('viewBox')? scrollBox.attr('viewBox').split(' ')[1] : 0;
698-
699-
legendsvg.node().addEventListener('wheel', scrollHandler);
700-
legendsvg.call(Drawing.setRect, lx, ly, legendwidth, scrollheight);
701-
702-
bg.style({ width: legendwidth, height: scrollheight });
703-
704-
scrollBox.attr('viewBox', '0 ' + scrollPosition +' ' + legendwidth + ' ' + scrollheight);
705-
706-
if(td.firstRender) scrollBar.call(Drawing.setRect, legendwidth - 6, 10, 4, 20);
707-
708-
function scrollHandler(e){
709-
e.preventDefault();
710-
711-
// Scale movement to simulate native scroll performance
712-
var scrollDiff = e.deltaY / 25,
713-
viewBox = scrollBox.attr('viewBox').split(' ');
714-
715-
var scrollBoxY = constrain(0, Math.max(legendheight - scrollheight, 0), +viewBox[1] + scrollDiff),
716-
scrollBarY = scrollBoxY / legendheight * (scrollheight) + 10;
717-
718-
viewBox[1] = scrollBoxY;
719-
720-
scrollBox.attr('viewBox', viewBox.join(' '));
721-
scrollBar.call(Drawing.setRect, legendwidth - 6, scrollBarY, 4, 20);
722-
}
723-
724-
function constrain(min, max, c){
725-
if(c <= max && c >= min){
726-
return c;
727-
}else if(c > max){
728-
return max;
729-
}else{
730-
return min;
731-
}
732-
}
733-
734769
// lastly check if the margin auto-expand has changed
735770
Plots.autoMargin(td,'legend',{
736771
x: opts.x,
737772
y: opts.y,
738-
l: legendwidth * ({right:1, center:0.5}[xanchor]||0),
739-
r: legendwidth * ({left:1, center:0.5}[xanchor]||0),
740-
b: legendheight * ({top:1, middle:0.5}[yanchor]||0),
741-
t: legendheight * ({bottom:1, middle:0.5}[yanchor]||0)
773+
l: opts.width * ({right:1, center:0.5}[xanchor] || 0),
774+
r: opts.width * ({left:1, center:0.5}[xanchor] || 0),
775+
b: opts.height * ({top:1, middle:0.5}[yanchor] || 0),
776+
t: opts.height * ({bottom:1, middle:0.5}[yanchor] || 0)
742777
});
743778
};

src/lib/svg_text_utils.js

-4
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,6 @@ util.convertToTspans = function(_context, _callback){
8080
parent.selectAll('svg.' + svgClass).remove();
8181
parent.selectAll('g.' + svgClass + '-group').remove();
8282
_context.style({visibility: null});
83-
// for Plotly.Drawing.bBox: unlink text and all parents from its cached box
84-
for(var up = _context.node(); up && up.removeAttribute; up = up.parentNode) {
85-
up.removeAttribute('data-bb');
86-
}
8783

8884
function showText() {
8985
if(!parent.empty()){

0 commit comments

Comments
 (0)