Skip to content

Commit 97cb7bb

Browse files
committed
break up graph_obj.js:
- Plotly methods go in src/plot_api/plot_api.js - Plotly.Plots module goes in src/plots/plots/plots.js - break up attribute object in Plotly.Plots into seperate files - factor Plotly.Plots.titles into components/titles/
1 parent ce39cf5 commit 97cb7bb

File tree

6 files changed

+1663
-1647
lines changed

6 files changed

+1663
-1647
lines changed

src/components/titles/titles.js

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
'use strict';
2+
3+
var Plotly = require('../../plotly');
4+
var d3 = require('d3');
5+
var isNumeric = require('fast-isnumeric');
6+
7+
var plots = Plotly.Plots;
8+
9+
var Titles = module.exports = {};
10+
11+
// titles - (re)draw titles on the axes and plot
12+
// title can be 'xtitle', 'ytitle', 'gtitle',
13+
// or empty to draw all
14+
Titles.draw = function(gd, title) {
15+
if(!title) {
16+
Plotly.Axes.listIds(gd).forEach(function(axId) {
17+
Titles.draw(gd, axId + 'title');
18+
});
19+
Titles.draw(gd, 'gtitle');
20+
return;
21+
}
22+
23+
var fullLayout = gd._fullLayout,
24+
gs = fullLayout._size,
25+
axletter = title.charAt(0),
26+
colorbar = title.substr(1,2)==='cb';
27+
28+
var cbnum, cont, options;
29+
30+
if(colorbar) {
31+
var uid = title.substr(3).replace('title','');
32+
gd._fullData.some(function(trace, i) {
33+
if(trace.uid===uid) {
34+
cbnum = i;
35+
cont = gd.calcdata[i][0].t.cb.axis;
36+
return true;
37+
}
38+
});
39+
}
40+
else cont = fullLayout[Plotly.Axes.id2name(title.replace('title',''))] || fullLayout;
41+
42+
var prop = cont===fullLayout ? 'title' : cont._name+'.title',
43+
name = colorbar ? 'colorscale' :
44+
((cont._id||axletter).toUpperCase()+' axis'),
45+
font = cont.titlefont.family,
46+
fontSize = cont.titlefont.size,
47+
fontColor = cont.titlefont.color,
48+
x,
49+
y,
50+
transform='',
51+
attr = {},
52+
xa,
53+
ya,
54+
avoid = {
55+
selection:d3.select(gd).selectAll('g.'+cont._id+'tick'),
56+
side:cont.side
57+
},
58+
// multiples of fontsize to offset label from axis
59+
offsetBase = colorbar ? 0 : 1.5,
60+
avoidTransform;
61+
62+
// find the transform applied to the parents of the avoid selection
63+
// which doesn't get picked up by Plotly.Drawing.bBox
64+
if(colorbar) {
65+
avoid.offsetLeft = gs.l;
66+
avoid.offsetTop = gs.t;
67+
}
68+
else if(avoid.selection.size()) {
69+
avoidTransform = d3.select(avoid.selection.node().parentNode)
70+
.attr('transform')
71+
.match(/translate\(([-\.\d]+),([-\.\d]+)\)/);
72+
if(avoidTransform) {
73+
avoid.offsetLeft = +avoidTransform[1];
74+
avoid.offsetTop = +avoidTransform[2];
75+
}
76+
}
77+
78+
if(colorbar && cont.titleside) {
79+
// argh, we only make it here if the title is on top or bottom,
80+
// not right
81+
x = gs.l+cont.titlex*gs.w;
82+
y = gs.t+(1-cont.titley)*gs.h + ((cont.titleside==='top') ?
83+
3+fontSize*0.75 : - 3-fontSize*0.25);
84+
options = {x: x, y: y, 'text-anchor':'start'};
85+
avoid = {};
86+
87+
// convertToTspans rotates any 'y...' by 90 degrees...
88+
// TODO: need a better solution than this hack
89+
title = 'h'+title;
90+
}
91+
else if(axletter==='x'){
92+
xa = cont;
93+
ya = (xa.anchor==='free') ?
94+
{_offset:gs.t+(1-(xa.position||0))*gs.h, _length:0} :
95+
Plotly.Axes.getFromId(gd, xa.anchor);
96+
x = xa._offset+xa._length/2;
97+
y = ya._offset + ((xa.side==='top') ?
98+
-10 - fontSize*(offsetBase + (xa.showticklabels ? 1 : 0)) :
99+
ya._length + 10 +
100+
fontSize*(offsetBase + (xa.showticklabels ? 1.5 : 0.5)));
101+
options = {x: x, y: y, 'text-anchor': 'middle'};
102+
if(!avoid.side) { avoid.side = 'bottom'; }
103+
}
104+
else if(axletter==='y'){
105+
ya = cont;
106+
xa = (ya.anchor==='free') ?
107+
{_offset:gs.l+(ya.position||0)*gs.w, _length:0} :
108+
Plotly.Axes.getFromId(gd, ya.anchor);
109+
y = ya._offset+ya._length/2;
110+
x = xa._offset + ((ya.side==='right') ?
111+
xa._length + 10 +
112+
fontSize*(offsetBase + (ya.showticklabels ? 1 : 0.5)) :
113+
-10 - fontSize*(offsetBase + (ya.showticklabels ? 0.5 : 0)));
114+
attr = {center: 0};
115+
options = {x: x, y: y, 'text-anchor': 'middle'};
116+
transform = {rotate: '-90', offset: 0};
117+
if(!avoid.side) { avoid.side = 'left'; }
118+
}
119+
else{
120+
// plot title
121+
name = 'Plot';
122+
fontSize = fullLayout.titlefont.size;
123+
x = fullLayout.width/2;
124+
y = fullLayout._size.t/2;
125+
options = {x: x, y: y, 'text-anchor': 'middle'};
126+
avoid = {};
127+
}
128+
129+
var opacity = 1,
130+
isplaceholder = false,
131+
txt = cont.title.trim();
132+
if(txt === '') { opacity = 0; }
133+
if(txt.match(/Click to enter .+ title/)) {
134+
opacity = 0.2;
135+
isplaceholder = true;
136+
}
137+
138+
var group;
139+
if(colorbar) {
140+
group = d3.select(gd)
141+
.selectAll('.'+cont._id.substr(1)+' .cbtitle');
142+
// this class-to-rotate thing with convertToTspans is
143+
// getting hackier and hackier... delete groups with the
144+
// wrong class
145+
var otherClass = title.charAt(0)==='h' ?
146+
title.substr(1) : ('h'+title);
147+
group.selectAll('.'+otherClass+',.'+otherClass+'-math-group')
148+
.remove();
149+
}
150+
else {
151+
group = fullLayout._infolayer.selectAll('.g-'+title)
152+
.data([0]);
153+
group.enter().append('g')
154+
.classed('g-'+title, true);
155+
}
156+
157+
var el = group.selectAll('text')
158+
.data([0]);
159+
el.enter().append('text');
160+
el.text(txt)
161+
// this is hacky, but convertToTspans uses the class
162+
// to determine whether to rotate mathJax...
163+
// so we need to clear out any old class and put the
164+
// correct one (only relevant for colorbars, at least
165+
// for now) - ie don't use .classed
166+
.attr('class', title);
167+
168+
function titleLayout(titleEl){
169+
Plotly.Lib.syncOrAsync([drawTitle,scootTitle], titleEl);
170+
}
171+
172+
function drawTitle(titleEl) {
173+
titleEl.attr('transform', transform ?
174+
'rotate(' + [transform.rotate, options.x, options.y] +
175+
') translate(0, '+transform.offset+')' :
176+
null);
177+
titleEl.style({
178+
'font-family': font,
179+
'font-size': d3.round(fontSize,2)+'px',
180+
fill: Plotly.Color.rgb(fontColor),
181+
opacity: opacity*Plotly.Color.opacity(fontColor),
182+
'font-weight': plots.fontWeight
183+
})
184+
.attr(options)
185+
.call(Plotly.util.convertToTspans)
186+
.attr(options);
187+
titleEl.selectAll('tspan.line')
188+
.attr(options);
189+
return plots.previousPromises(gd);
190+
}
191+
192+
function scootTitle(titleElIn) {
193+
var titleGroup = d3.select(titleElIn.node().parentNode);
194+
195+
if(avoid && avoid.selection && avoid.side && txt){
196+
titleGroup.attr('transform',null);
197+
198+
// move toward avoid.side (= left, right, top, bottom) if needed
199+
// can include pad (pixels, default 2)
200+
var shift = 0,
201+
backside = {
202+
left: 'right',
203+
right: 'left',
204+
top: 'bottom',
205+
bottom: 'top'
206+
}[avoid.side],
207+
shiftSign = (['left','top'].indexOf(avoid.side)!==-1) ?
208+
-1 : 1,
209+
pad = isNumeric(avoid.pad) ? avoid.pad : 2,
210+
titlebb = Plotly.Drawing.bBox(titleGroup.node()),
211+
paperbb = {
212+
left: 0,
213+
top: 0,
214+
right: fullLayout.width,
215+
bottom: fullLayout.height
216+
},
217+
maxshift = colorbar ? fullLayout.width:
218+
(paperbb[avoid.side]-titlebb[avoid.side]) *
219+
((avoid.side==='left' || avoid.side==='top') ? -1 : 1);
220+
// Prevent the title going off the paper
221+
if(maxshift<0) shift = maxshift;
222+
else {
223+
// so we don't have to offset each avoided element,
224+
// give the title the opposite offset
225+
titlebb.left -= avoid.offsetLeft;
226+
titlebb.right -= avoid.offsetLeft;
227+
titlebb.top -= avoid.offsetTop;
228+
titlebb.bottom -= avoid.offsetTop;
229+
230+
// iterate over a set of elements (avoid.selection)
231+
// to avoid collisions with
232+
avoid.selection.each(function(){
233+
var avoidbb = Plotly.Drawing.bBox(this);
234+
235+
if(Plotly.Lib.bBoxIntersect(titlebb,avoidbb,pad)) {
236+
shift = Math.max(shift, shiftSign * (
237+
avoidbb[avoid.side] - titlebb[backside]) + pad);
238+
}
239+
});
240+
shift = Math.min(maxshift, shift);
241+
}
242+
if(shift>0 || maxshift<0) {
243+
var shiftTemplate = {
244+
left: [-shift, 0],
245+
right: [shift, 0],
246+
top: [0, -shift],
247+
bottom: [0, shift]
248+
}[avoid.side];
249+
titleGroup.attr('transform',
250+
'translate(' + shiftTemplate + ')');
251+
}
252+
}
253+
}
254+
255+
el.attr({'data-unformatted': txt})
256+
.call(titleLayout);
257+
258+
var placeholderText = 'Click to enter '+name.replace(/\d+/,'')+' title';
259+
260+
function setPlaceholder(){
261+
opacity = 0;
262+
isplaceholder = true;
263+
txt = placeholderText;
264+
fullLayout._infolayer.select('.'+title)
265+
.attr({'data-unformatted': txt})
266+
.text(txt)
267+
.on('mouseover.opacity',function(){
268+
d3.select(this).transition()
269+
.duration(100).style('opacity',1);
270+
})
271+
.on('mouseout.opacity',function(){
272+
d3.select(this).transition()
273+
.duration(1000).style('opacity',0);
274+
});
275+
}
276+
277+
if(gd._context.editable){
278+
if(!txt) setPlaceholder();
279+
280+
el.call(Plotly.util.makeEditable)
281+
.on('edit', function(text){
282+
if(colorbar) {
283+
var trace = gd._fullData[cbnum];
284+
if(plots.traceIs(trace, 'markerColorscale')) {
285+
Plotly.restyle(gd, 'marker.colorbar.title', text, cbnum);
286+
} else Plotly.restyle(gd, 'colorbar.title', text, cbnum);
287+
}
288+
else Plotly.relayout(gd,prop,text);
289+
})
290+
.on('cancel', function(){
291+
this.text(this.attr('data-unformatted'))
292+
.call(titleLayout);
293+
})
294+
.on('input', function(d){
295+
this.text(d || ' ').attr(options)
296+
.selectAll('tspan.line')
297+
.attr(options);
298+
});
299+
}
300+
else if(!txt || txt.match(/Click to enter .+ title/)) {
301+
el.remove();
302+
}
303+
el.classed('js-placeholder',isplaceholder);
304+
};

0 commit comments

Comments
 (0)