Skip to content

Commit 50a1e9b

Browse files
committed
Support updating titles through relayout and update [882]
1 parent a7e3a9d commit 50a1e9b

File tree

4 files changed

+174
-21
lines changed

4 files changed

+174
-21
lines changed

src/plot_api/plot_api.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1686,6 +1686,28 @@ function _restyle(gd, aobj, traces) {
16861686
};
16871687
}
16881688

1689+
/**
1690+
* Maps deprecated layout "string attributes" to
1691+
* "string attributes" of the current API to ensure backwards compatibility.
1692+
*
1693+
* E.g. Maps {'xaxis.title': 'A chart'} to {'xaxis.title.text': 'A chart'}
1694+
*
1695+
* @param layoutObj
1696+
*/
1697+
function mapDeprecatedLayoutAttributeStrings(layoutObj) {
1698+
if(layoutObj.title && !Lib.isPlainObject(layoutObj.title)) {
1699+
layoutObj.title = {text: layoutObj.title};
1700+
}
1701+
if(layoutObj['xaxis.title'] !== undefined) {
1702+
layoutObj['xaxis.title.text'] = layoutObj['xaxis.title'];
1703+
delete layoutObj['xaxis.title'];
1704+
}
1705+
if(layoutObj['yaxis.title'] !== undefined) {
1706+
layoutObj['yaxis.title.text'] = layoutObj['yaxis.title'];
1707+
delete layoutObj['yaxis.title'];
1708+
}
1709+
}
1710+
16891711
/**
16901712
* relayout: update layout attributes of an existing plot
16911713
*
@@ -1726,6 +1748,8 @@ exports.relayout = function relayout(gd, astr, val) {
17261748

17271749
if(Object.keys(aobj).length) gd.changed = true;
17281750

1751+
mapDeprecatedLayoutAttributeStrings(aobj);
1752+
17291753
var specs = _relayout(gd, aobj);
17301754
var flags = specs.flags;
17311755

@@ -2203,6 +2227,8 @@ exports.update = function update(gd, traceUpdate, layoutUpdate, _traces) {
22032227
var restyleSpecs = _restyle(gd, Lib.extendFlat({}, traceUpdate), traces);
22042228
var restyleFlags = restyleSpecs.flags;
22052229

2230+
mapDeprecatedLayoutAttributeStrings(layoutUpdate);
2231+
22062232
var relayoutSpecs = _relayout(gd, Lib.extendFlat({}, layoutUpdate));
22072233
var relayoutFlags = relayoutSpecs.flags;
22082234

src/plots/cartesian/layout_attributes.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ module.exports = {
4646
role: 'info',
4747
editType: 'ticks',
4848
description: 'Sets the title of this axis.'
49-
}
49+
},
50+
editType: 'ticks'
5051
},
5152
titlefont: fontAttrs({
5253
editType: 'ticks',

src/plots/layout_attributes.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ module.exports = {
3333
description: [
3434
'Sets the plot\'s title.'
3535
].join(' ')
36-
}
36+
},
37+
editType: 'layoutstyle'
3738
},
3839
titlefont: fontAttrs({
3940
editType: 'layoutstyle',

test/jasmine/tests/titles_test.js

Lines changed: 144 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ var d3 = require('d3');
22

33
var Plotly = require('@lib/index');
44
var interactConstants = require('@src/constants/interactions');
5+
var Lib = require('@src/lib');
56

67
var createGraphDiv = require('../assets/create_graph_div');
78
var destroyGraphDiv = require('../assets/destroy_graph_div');
89
var mouseEvent = require('../assets/mouse_event');
910

10-
1111
describe('Plot title', function() {
1212
'use strict';
1313

@@ -28,39 +28,164 @@ describe('Plot title', function() {
2828
it('is centered horizontally and vertically above the plot by default', function() {
2929
Plotly.plot(gd, data, layout);
3030

31-
expectDefaultCenteredPosition();
31+
expectDefaultCenteredPosition(gd);
3232
});
3333

3434
it('can still be defined as `layout.title` to ensure backwards-compatibility', function() {
3535
Plotly.plot(gd, data, {title: 'Plotly line chart'});
3636

37-
expect(titleSel().text()).toBe('Plotly line chart');
38-
expectDefaultCenteredPosition();
37+
expectTitle('Plotly line chart');
38+
expectDefaultCenteredPosition(gd);
3939
});
4040

41-
function expectDefaultCenteredPosition() {
42-
var containerBB = gd.getBoundingClientRect();
41+
it('can be updated via `relayout`', function(done) {
42+
Plotly.plot(gd, data, {title: 'Plotly line chart'})
43+
.then(expectTitleFn('Plotly line chart'))
44+
.then(function() {
45+
return Plotly.relayout(gd, {title: {text: 'Some other title'}});
46+
})
47+
.then(expectTitleFn('Some other title'))
48+
.catch(fail)
49+
.then(done);
50+
});
4351

44-
expect(titleX()).toBe(containerBB.width / 2);
45-
expect(titleY()).toBe(gd._fullLayout.margin.t / 2);
46-
}
52+
it('preserves alignment when text is updated via `Plotly.relayout` using an attribute string', function() {
53+
// TODO implement once alignment is implemented
54+
});
4755

48-
function titleX() {
49-
return Number.parseFloat(titleSel().attr('x'));
50-
}
56+
it('preserves alignment when text is updated via `Plotly.update` using an attribute string', function() {
57+
// TODO implement once alignment is implemented
58+
});
59+
60+
it('discards alignment when text is updated via `Plotly.relayout` by passing a new title object', function() {
61+
// TODO implement once alignment is implemented
62+
});
63+
64+
it('discards alignment when text is updated via `Plotly.update` by passing a new title object', function() {
65+
// TODO implement once alignment is implemented
66+
});
67+
});
68+
69+
describe('Titles can be updated', function() {
70+
'use strict';
71+
72+
var data = [{x: [1, 2, 3], y: [1, 2, 3]}];
73+
var NEW_TITLE = 'Weight over years';
74+
var NEW_XTITLE = 'Age in years';
75+
var NEW_YTITLE = 'Average weight';
76+
var gd;
77+
78+
beforeEach(function() {
79+
var layout = {
80+
title: {text: 'Plotly line chart'},
81+
xaxis: {title: {text: 'Age'}},
82+
yaxis: {title: {text: 'Weight'}}
83+
};
84+
gd = createGraphDiv();
85+
Plotly.plot(gd, data, Lib.extendDeep({}, layout));
86+
87+
expectTitles('Plotly line chart', 'Age', 'Weight');
88+
});
5189

52-
function titleY() {
53-
return Number.parseFloat(titleSel().attr('y'));
90+
afterEach(destroyGraphDiv);
91+
92+
[
93+
{
94+
desc: 'by replacing the entire title objects',
95+
update: {
96+
title: {text: NEW_TITLE},
97+
xaxis: {title: {text: NEW_XTITLE}},
98+
yaxis: {title: {text: NEW_YTITLE}}
99+
}
100+
},
101+
{
102+
desc: 'by using attribute strings',
103+
update: {
104+
'title.text': NEW_TITLE,
105+
'xaxis.title.text': NEW_XTITLE,
106+
'yaxis.title.text': NEW_YTITLE
107+
}
108+
},
109+
{
110+
desc: 'despite passing title only as a string (backwards-compatibility)',
111+
update: {
112+
title: NEW_TITLE,
113+
xaxis: {title: NEW_XTITLE},
114+
yaxis: {title: NEW_YTITLE}
115+
}
116+
},
117+
{
118+
desc: 'despite passing title only as a string using string attributes ' +
119+
'(backwards-compatibility)',
120+
update: {
121+
'title': NEW_TITLE,
122+
'xaxis.title': NEW_XTITLE,
123+
'yaxis.title': NEW_YTITLE
124+
}
125+
}
126+
].forEach(function(testCase) {
127+
it('via `Plotly.relayout` ' + testCase.desc, function() {
128+
Plotly.relayout(gd, testCase.update);
129+
130+
expectChangedTitles();
131+
});
132+
133+
it('via `Plotly.update` ' + testCase.desc, function() {
134+
Plotly.update(gd, {}, testCase.update);
135+
136+
expectChangedTitles();
137+
});
138+
});
139+
140+
function expectChangedTitles() {
141+
expectTitles(NEW_TITLE, NEW_XTITLE, NEW_YTITLE);
54142
}
55143

56-
function titleSel() {
57-
var titleSel = d3.select('.infolayer .g-gtitle .gtitle');
58-
expect(titleSel.empty()).toBe(false, 'Title element missing');
59-
return titleSel;
144+
function expectTitles(expTitle, expXTitle, expYTitle) {
145+
expectTitle(expTitle);
146+
147+
var xTitleSel = d3.select('.xtitle');
148+
expect(xTitleSel.empty()).toBe(false, 'X-axis title element missing');
149+
expect(xTitleSel.text()).toBe(expXTitle);
150+
151+
var yTitleSel = d3.select('.ytitle');
152+
expect(yTitleSel.empty()).toBe(false, 'Y-axis title element missing');
153+
expect(yTitleSel.text()).toBe(expYTitle);
60154
}
61155
});
62156

63-
describe('editable titles', function() {
157+
function expectTitle(expTitle) {
158+
expectTitleFn(expTitle)();
159+
}
160+
161+
function expectTitleFn(expTitle) {
162+
return function() {
163+
expect(titleSel().text()).toBe(expTitle);
164+
};
165+
}
166+
167+
function expectDefaultCenteredPosition(gd) {
168+
var containerBB = gd.getBoundingClientRect();
169+
170+
expect(titleX()).toBe(containerBB.width / 2);
171+
expect(titleY()).toBe(gd._fullLayout.margin.t / 2);
172+
}
173+
174+
function titleX() {
175+
return Number.parseFloat(titleSel().attr('x'));
176+
}
177+
178+
function titleY() {
179+
return Number.parseFloat(titleSel().attr('y'));
180+
}
181+
182+
function titleSel() {
183+
var titleSel = d3.select('.infolayer .g-gtitle .gtitle');
184+
expect(titleSel.empty()).toBe(false, 'Title element missing');
185+
return titleSel;
186+
}
187+
188+
describe('Editable titles', function() {
64189
'use strict';
65190

66191
var data = [{x: [1, 2, 3], y: [1, 2, 3]}];

0 commit comments

Comments
 (0)