-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
Copy pathindex.js
255 lines (224 loc) · 9.12 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
/**
* Copyright 2012-2018, 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 d3 = require('d3');
var isNumeric = require('fast-isnumeric');
var Plotly = require('../../plotly');
var Plots = require('../../plots/plots');
var Lib = require('../../lib');
var Drawing = require('../drawing');
var Color = require('../color');
var svgTextUtils = require('../../lib/svg_text_utils');
var interactConstants = require('../../constants/interactions');
var Titles = module.exports = {};
var numStripRE = / [XY][0-9]* /;
/**
* Titles - (re)draw titles on the axes and plot:
* @param {DOM element} gd - the graphDiv
* @param {string} titleClass - the css class of this title
* @param {object} options - how and what to draw
* propContainer - the layout object containing `title` and `titlefont`
* attributes that apply to this title
* propName - the full name of the title property (for Plotly.relayout)
* [traceIndex] - include only if this property applies to one trace
* (such as a colorbar title) - then editing pipes to Plotly.restyle
* instead of Plotly.relayout
* placeholder - placeholder text for an empty editable title
* [avoid] {object} - include if this title should move to avoid other elements
* selection - d3 selection of elements to avoid
* side - which direction to move if there is a conflict
* [offsetLeft] - if these elements are subject to a translation
* wrt the title element
* [offsetTop]
* attributes {object} - position and alignment attributes
* x - pixels
* y - pixels
* text-anchor - start|middle|end
* transform {object} - how to transform the title after positioning
* rotate - degrees
* offset - shift up/down in the rotated frame (unused?)
* containerGroup - if an svg <g> element already exists to hold this
* title, include here. Otherwise it will go in fullLayout._infolayer
*
* @return {selection} d3 selection of title container group
*/
Titles.draw = function(gd, titleClass, options) {
var cont = options.propContainer;
var prop = options.propName;
var placeholder = options.placeholder;
var traceIndex = options.traceIndex;
var avoid = options.avoid || {};
var attributes = options.attributes;
var transform = options.transform;
var group = options.containerGroup;
var fullLayout = gd._fullLayout;
var titlefont = cont.titlefont || {};
var font = titlefont.family;
var fontSize = titlefont.size;
var fontColor = titlefont.color;
var opacity = 1;
var isplaceholder = false;
var txt = (cont.title || '').trim();
// only make this title editable if we positively identify its property
// as one that has editing enabled.
var editAttr;
if(prop === 'title') editAttr = 'titleText';
else if(prop.indexOf('axis') !== -1) editAttr = 'axisTitleText';
else if(prop.indexOf('colorbar' !== -1)) editAttr = 'colorbarTitleText';
var editable = gd._context.edits[editAttr];
if(txt === '') opacity = 0;
// look for placeholder text while stripping out numbers from eg X2, Y3
// this is just for backward compatibility with the old version that had
// "Click to enter X2 title" and may have gotten saved in some old plots,
// we don't want this to show up when these are displayed.
else if(txt.replace(numStripRE, ' % ') === placeholder.replace(numStripRE, ' % ')) {
opacity = 0.2;
isplaceholder = true;
if(!editable) txt = '';
}
var elShouldExist = txt || editable;
if(!group) {
group = fullLayout._infolayer.selectAll('.g-' + titleClass)
.data([0]);
group.enter().append('g')
.classed('g-' + titleClass, true);
}
var el = group.selectAll('text')
.data(elShouldExist ? [0] : []);
el.enter().append('text');
el.text(txt)
// this is hacky, but convertToTspans uses the class
// to determine whether to rotate mathJax...
// so we need to clear out any old class and put the
// correct one (only relevant for colorbars, at least
// for now) - ie don't use .classed
.attr('class', titleClass);
el.exit().remove();
if(!elShouldExist) return group;
function titleLayout(titleEl) {
Lib.syncOrAsync([drawTitle, scootTitle], titleEl);
}
function drawTitle(titleEl) {
var transformVal;
if(transform) {
transformVal = '';
if(transform.rotate) {
transformVal += 'rotate(' + [transform.rotate, attributes.x, attributes.y] + ')';
}
if(transform.offset) {
transformVal += 'translate(0, ' + transform.offset + ')';
}
} else {
transformVal = null;
}
titleEl.attr('transform', transformVal);
titleEl.style({
'font-family': font,
'font-size': d3.round(fontSize, 2) + 'px',
fill: Color.rgb(fontColor),
opacity: opacity * Color.opacity(fontColor),
'font-weight': Plots.fontWeight
})
.attr(attributes)
.call(svgTextUtils.convertToTspans, gd);
return Plots.previousPromises(gd);
}
function scootTitle(titleElIn) {
var titleGroup = d3.select(titleElIn.node().parentNode);
if(avoid && avoid.selection && avoid.side && txt) {
titleGroup.attr('transform', null);
// move toward avoid.side (= left, right, top, bottom) if needed
// can include pad (pixels, default 2)
var shift = 0;
var backside = {
left: 'right',
right: 'left',
top: 'bottom',
bottom: 'top'
}[avoid.side];
var shiftSign = (['left', 'top'].indexOf(avoid.side) !== -1) ?
-1 : 1;
var pad = isNumeric(avoid.pad) ? avoid.pad : 2;
var titlebb = Drawing.bBox(titleGroup.node());
var paperbb = {
left: 0,
top: 0,
right: fullLayout.width,
bottom: fullLayout.height
};
var maxshift = avoid.maxShift || (
(paperbb[avoid.side] - titlebb[avoid.side]) *
((avoid.side === 'left' || avoid.side === 'top') ? -1 : 1));
// Prevent the title going off the paper
if(maxshift < 0) shift = maxshift;
else {
// so we don't have to offset each avoided element,
// give the title the opposite offset
var offsetLeft = avoid.offsetLeft || 0;
var offsetTop = avoid.offsetTop || 0;
titlebb.left -= offsetLeft;
titlebb.right -= offsetLeft;
titlebb.top -= offsetTop;
titlebb.bottom -= offsetTop;
// iterate over a set of elements (avoid.selection)
// to avoid collisions with
avoid.selection.each(function() {
var avoidbb = Drawing.bBox(this);
if(Lib.bBoxIntersect(titlebb, avoidbb, pad)) {
shift = Math.max(shift, shiftSign * (
avoidbb[avoid.side] - titlebb[backside]) + pad);
}
});
shift = Math.min(maxshift, shift);
}
if(shift > 0 || maxshift < 0) {
var shiftTemplate = {
left: [-shift, 0],
right: [shift, 0],
top: [0, -shift],
bottom: [0, shift]
}[avoid.side];
titleGroup.attr('transform',
'translate(' + shiftTemplate + ')');
}
}
}
el.call(titleLayout);
function setPlaceholder() {
opacity = 0;
isplaceholder = true;
el.text(placeholder)
.on('mouseover.opacity', function() {
d3.select(this).transition()
.duration(interactConstants.SHOW_PLACEHOLDER).style('opacity', 1);
})
.on('mouseout.opacity', function() {
d3.select(this).transition()
.duration(interactConstants.HIDE_PLACEHOLDER).style('opacity', 0);
});
}
if(editable) {
if(!txt) setPlaceholder();
else el.on('.opacity', null);
el.call(svgTextUtils.makeEditable, {gd: gd})
.on('edit', function(text) {
if(traceIndex !== undefined) Plotly.restyle(gd, prop, text, traceIndex);
else Plotly.relayout(gd, prop, text);
})
.on('cancel', function() {
this.text(this.attr('data-unformatted'))
.call(titleLayout);
})
.on('input', function(d) {
this.text(d || ' ')
.call(svgTextUtils.positionText, attributes.x, attributes.y);
});
}
el.classed('js-placeholder', isplaceholder);
return group;
};