Skip to content

Commit d97a659

Browse files
authoredJul 19, 2017
Merge pull request #1895 from plotly/edit-parts
Granular options for on-chart editing
2 parents e3eba8a + 04d49dd commit d97a659

File tree

8 files changed

+225
-112
lines changed

8 files changed

+225
-112
lines changed
 

‎src/components/annotations/draw.js

+9-4
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ function drawOne(gd, index) {
8585
function drawRaw(gd, options, index, subplotId, xa, ya) {
8686
var fullLayout = gd._fullLayout;
8787
var gs = gd._fullLayout._size;
88+
var edits = gd._context.edits;
89+
8890
var className;
8991
var annbase;
9092

@@ -128,8 +130,11 @@ function drawRaw(gd, options, index, subplotId, xa, ya) {
128130
var annTextGroup = annGroup.append('g')
129131
.classed('annotation-text-g', true);
130132

133+
var editTextPosition = edits[options.showarrow ? 'annotationTail' : 'annotationPosition'];
134+
var textEvents = options.captureevents || edits.annotationText || editTextPosition;
135+
131136
var annTextGroupInner = annTextGroup.append('g')
132-
.style('pointer-events', options.captureevents ? 'all' : null)
137+
.style('pointer-events', textEvents ? 'all' : null)
133138
.call(setCursor, 'default')
134139
.on('click', function() {
135140
gd._dragging = false;
@@ -519,7 +524,7 @@ function drawRaw(gd, options, index, subplotId, xa, ya) {
519524

520525
// the arrow dragger is a small square right at the head, then a line to the tail,
521526
// all expanded by a stroke width of 6px plus the arrow line width
522-
if(gd._context.editable && arrow.node().parentNode && !subplotId) {
527+
if(edits.annotationPosition && arrow.node().parentNode && !subplotId) {
523528
var arrowDragHeadX = headX;
524529
var arrowDragHeadY = headY;
525530
if(options.standoff) {
@@ -601,7 +606,7 @@ function drawRaw(gd, options, index, subplotId, xa, ya) {
601606
if(options.showarrow) drawArrow(0, 0);
602607

603608
// user dragging the annotation (text, not arrow)
604-
if(gd._context.editable) {
609+
if(editTextPosition) {
605610
var update,
606611
baseTextTransform;
607612

@@ -679,7 +684,7 @@ function drawRaw(gd, options, index, subplotId, xa, ya) {
679684
}
680685
}
681686

682-
if(gd._context.editable) {
687+
if(edits.annotationText) {
683688
annText.call(svgTextUtils.makeEditable, {delegate: annTextGroupInner, gd: gd})
684689
.call(textLayout)
685690
.on('edit', function(_text) {

‎src/components/colorbar/draw.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,7 @@ module.exports = function draw(gd, id) {
550550
if(cbDone && cbDone.then) (gd._promises || []).push(cbDone);
551551

552552
// dragging...
553-
if(gd._context.editable) {
553+
if(gd._context.edits.colorbarPosition) {
554554
var t0,
555555
xf,
556556
yf;

‎src/components/legend/draw.js

+27-59
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ module.exports = function draw(gd) {
312312
});
313313
}
314314

315-
if(gd._context.editable) {
315+
if(gd._context.edits.legendPosition) {
316316
var xf, yf, x0, y0;
317317

318318
legend.classed('cursor-move', true);
@@ -385,7 +385,7 @@ function drawTexts(g, gd) {
385385
});
386386
}
387387

388-
if(gd._context.editable && !isPie) {
388+
if(gd._context.edits.legendText && !isPie) {
389389
text.call(svgTextUtils.makeEditable, {gd: gd})
390390
.call(textLayout)
391391
.on('edit', function(text) {
@@ -597,10 +597,15 @@ function computeTextDimensions(g, gd) {
597597
}
598598

599599
function computeLegendDimensions(gd, groups, traces) {
600-
var fullLayout = gd._fullLayout,
601-
opts = fullLayout.legend,
602-
borderwidth = opts.borderwidth,
603-
isGrouped = helpers.isGrouped(opts);
600+
var fullLayout = gd._fullLayout;
601+
var opts = fullLayout.legend;
602+
var borderwidth = opts.borderwidth;
603+
var isGrouped = helpers.isGrouped(opts);
604+
605+
var extraWidth = 0;
606+
607+
opts.width = 0;
608+
opts.height = 0;
604609

605610
if(helpers.isVertical(opts)) {
606611
if(isGrouped) {
@@ -609,9 +614,6 @@ function computeLegendDimensions(gd, groups, traces) {
609614
});
610615
}
611616

612-
opts.width = 0;
613-
opts.height = 0;
614-
615617
traces.each(function(d) {
616618
var legendItem = d[0],
617619
textHeight = legendItem.height,
@@ -632,26 +634,9 @@ function computeLegendDimensions(gd, groups, traces) {
632634
opts.height += (opts._lgroupsLength - 1) * opts.tracegroupgap;
633635
}
634636

635-
// make sure we're only getting full pixels
636-
opts.width = Math.ceil(opts.width);
637-
opts.height = Math.ceil(opts.height);
638-
639-
traces.each(function(d) {
640-
var legendItem = d[0],
641-
bg = d3.select(this).select('.legendtoggle');
642-
643-
bg.call(Drawing.setRect,
644-
0,
645-
-legendItem.height / 2,
646-
(gd._context.editable ? 0 : opts.width) + 40,
647-
legendItem.height
648-
);
649-
});
637+
extraWidth = 40;
650638
}
651639
else if(isGrouped) {
652-
opts.width = 0;
653-
opts.height = 0;
654-
655640
var groupXOffsets = [opts.width],
656641
groupData = groups.data();
657642

@@ -692,26 +677,8 @@ function computeLegendDimensions(gd, groups, traces) {
692677

693678
opts.height += 10 + borderwidth * 2;
694679
opts.width += borderwidth * 2;
695-
696-
// make sure we're only getting full pixels
697-
opts.width = Math.ceil(opts.width);
698-
opts.height = Math.ceil(opts.height);
699-
700-
traces.each(function(d) {
701-
var legendItem = d[0],
702-
bg = d3.select(this).select('.legendtoggle');
703-
704-
bg.call(Drawing.setRect,
705-
0,
706-
-legendItem.height / 2,
707-
(gd._context.editable ? 0 : opts.width),
708-
legendItem.height
709-
);
710-
});
711680
}
712681
else {
713-
opts.width = 0;
714-
opts.height = 0;
715682
var rowHeight = 0,
716683
maxTraceHeight = 0,
717684
maxTraceWidth = 0,
@@ -750,22 +717,23 @@ function computeLegendDimensions(gd, groups, traces) {
750717
opts.width += borderwidth * 2;
751718
opts.height += 10 + borderwidth * 2;
752719

753-
// make sure we're only getting full pixels
754-
opts.width = Math.ceil(opts.width);
755-
opts.height = Math.ceil(opts.height);
720+
}
756721

757-
traces.each(function(d) {
758-
var legendItem = d[0],
759-
bg = d3.select(this).select('.legendtoggle');
722+
// make sure we're only getting full pixels
723+
opts.width = Math.ceil(opts.width);
724+
opts.height = Math.ceil(opts.height);
760725

761-
bg.call(Drawing.setRect,
762-
0,
763-
-legendItem.height / 2,
764-
(gd._context.editable ? 0 : opts.width),
765-
legendItem.height
766-
);
767-
});
768-
}
726+
traces.each(function(d) {
727+
var legendItem = d[0],
728+
bg = d3.select(this).select('.legendtoggle');
729+
730+
bg.call(Drawing.setRect,
731+
0,
732+
-legendItem.height / 2,
733+
(gd._context.edits.legendText ? 0 : opts.width) + extraWidth,
734+
legendItem.height
735+
);
736+
});
769737
}
770738

771739
function expandMargin(gd) {

‎src/components/shapes/draw.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ function drawOne(gd, index) {
114114
null
115115
);
116116

117-
if(gd._context.editable) setupDragElement(gd, path, options, index);
117+
if(gd._context.edits.shapePosition) setupDragElement(gd, path, options, index);
118118
}
119119
}
120120

‎src/components/titles/index.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,14 @@ Titles.draw = function(gd, titleClass, options) {
7070
var opacity = 1;
7171
var isplaceholder = false;
7272
var txt = cont.title.trim();
73-
var editable = gd._context.editable;
73+
74+
// only make this title editable if we positively identify its property
75+
// as one that has editing enabled.
76+
var editAttr;
77+
if(prop === 'title') editAttr = 'titleText';
78+
else if(prop.indexOf('axis') !== -1) editAttr = 'axisTitleText';
79+
else if(prop.indexOf('colorbar' !== -1)) editAttr = 'colorbarTitleText';
80+
var editable = gd._context.edits[editAttr];
7481

7582
if(txt === '') opacity = 0;
7683
if(txt.match(PLACEHOLDER_RE)) {

‎src/plot_api/plot_api.js

+33-4
Original file line numberDiff line numberDiff line change
@@ -397,28 +397,57 @@ function opaqueSetBackground(gd, bgColor) {
397397
}
398398

399399
function setPlotContext(gd, config) {
400-
if(!gd._context) gd._context = Lib.extendFlat({}, Plotly.defaultConfig);
400+
if(!gd._context) gd._context = Lib.extendDeep({}, Plotly.defaultConfig);
401401
var context = gd._context;
402402

403+
var i, keys, key;
404+
403405
if(config) {
404-
Object.keys(config).forEach(function(key) {
406+
keys = Object.keys(config);
407+
for(i = 0; i < keys.length; i++) {
408+
key = keys[i];
409+
if(key === 'editable' || key === 'edits') continue;
405410
if(key in context) {
406411
if(key === 'setBackground' && config[key] === 'opaque') {
407412
context[key] = opaqueSetBackground;
408413
}
409414
else context[key] = config[key];
410415
}
411-
});
416+
}
412417

413418
// map plot3dPixelRatio to plotGlPixelRatio for backward compatibility
414419
if(config.plot3dPixelRatio && !context.plotGlPixelRatio) {
415420
context.plotGlPixelRatio = context.plot3dPixelRatio;
416421
}
422+
423+
// now deal with editable and edits - first editable overrides
424+
// everything, then edits refines
425+
var editable = config.editable;
426+
if(editable !== undefined) {
427+
// we're not going to *use* context.editable, we're only going to
428+
// use context.edits... but keep it for the record
429+
context.editable = editable;
430+
431+
keys = Object.keys(context.edits);
432+
for(i = 0; i < keys.length; i++) {
433+
context.edits[keys[i]] = editable;
434+
}
435+
}
436+
if(config.edits) {
437+
keys = Object.keys(config.edits);
438+
for(i = 0; i < keys.length; i++) {
439+
key = keys[i];
440+
if(key in context.edits) {
441+
context.edits[key] = config.edits[key];
442+
}
443+
}
444+
}
417445
}
418446

419447
// staticPlot forces a bunch of others:
420448
if(context.staticPlot) {
421449
context.editable = false;
450+
context.edits = {};
422451
context.autosizable = false;
423452
context.scrollZoom = false;
424453
context.doubleClick = false;
@@ -487,7 +516,7 @@ function plotPolar(gd, data, layout) {
487516
var title = polarPlotSVG.select('.title-group text')
488517
.call(titleLayout);
489518

490-
if(gd._context.editable) {
519+
if(gd._context.edits.titleText) {
491520
if(!txt || txt === placeholderText) {
492521
opacity = 0.2;
493522
// placeholder is not going through convertToTspans

‎src/plot_api/plot_config.js

+20-1
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,27 @@ module.exports = {
2323
// no interactivity, for export or image generation
2424
staticPlot: false,
2525

26-
// we can edit titles, move annotations, etc
26+
// we can edit titles, move annotations, etc - sets all pieces of `edits`
27+
// unless a separate `edits` config item overrides individual parts
2728
editable: false,
29+
edits: {
30+
// annotationPosition: the main anchor of the annotation, which is the
31+
// text (if no arrow) or the arrow (which drags the whole thing leaving
32+
// the arrow length & direction unchanged)
33+
annotationPosition: false,
34+
// just for annotations with arrows, change the length and direction of the arrow
35+
annotationTail: false,
36+
annotationText: false,
37+
axisTitleText: false,
38+
colorbarPosition: false,
39+
colorbarTitleText: false,
40+
legendPosition: false,
41+
// edit the trace name fields from the legend
42+
legendText: false,
43+
shapePosition: false,
44+
// the global `layout.title`
45+
titleText: false
46+
},
2847

2948
// DO autosize once regardless of layout.autosize
3049
// (use default width or height values otherwise)

0 commit comments

Comments
 (0)