Skip to content

De-circularize Titles and Axes #235

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 2, 2016
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/components/colorbar/draw.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ var Fx = require('../../plots/cartesian/graph_interact');
var Lib = require('../../lib');
var Drawing = require('../drawing');
var Color = require('../color');
var Titles = require('../titles');

var handleAxisDefaults = require('../../plots/cartesian/axis_defaults');
var handleAxisPositionDefaults = require('../../plots/cartesian/position_defaults');
Expand Down Expand Up @@ -253,10 +254,10 @@ module.exports = function draw(gd, id) {

cbAxisOut._axislayer = container.select('.cbaxis');
var titleHeight = 0;
if(['top','bottom'].indexOf(opts.titleside)!==-1) {
if(['top', 'bottom'].indexOf(opts.titleside) !==- 1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the space was a bit too eager there.

Should be if(['top', 'bottom'].indexOf(opts.titleside) !== -1) {

// draw the title so we know how much room it needs
// when we squish the axis
Plotly.Titles.draw(gd, cbAxisOut._id + 'title');
Titles.draw(gd, cbAxisOut._id + 'title');
}

function drawAxis(){
Expand Down
162 changes: 83 additions & 79 deletions src/components/titles/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,48 +9,47 @@

'use strict';

var Plotly = require('../../plotly');
var d3 = require('d3');
var isNumeric = require('fast-isnumeric');

var plots = Plotly.Plots;
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 axisIds = require('../../plots/cartesian/axis_ids');


var Titles = module.exports = {};

// titles - (re)draw titles on the axes and plot
// title can be 'xtitle', 'ytitle', 'gtitle',
// or empty to draw all
/**
* Titles - (re)draw titles on the axes and plot:
* title can be 'xtitle', 'ytitle', 'gtitle'
*/
Titles.draw = function(gd, title) {
if(!title) {
Plotly.Axes.listIds(gd).forEach(function(axId) {
Titles.draw(gd, axId + 'title');
});
Titles.draw(gd, 'gtitle');
return;
}

var fullLayout = gd._fullLayout,
gs = fullLayout._size,
axletter = title.charAt(0),
colorbar = title.substr(1,2)==='cb';
colorbar = (title.substr(1, 2) === 'cb');

var cbnum, cont, options;

if(colorbar) {
var uid = title.substr(3).replace('title','');
var uid = title.substr(3).replace('title', '');
gd._fullData.some(function(trace, i) {
if(trace.uid===uid) {
if(trace.uid === uid) {
cbnum = i;
cont = gd.calcdata[i][0].t.cb.axis;
return true;
}
});
}
else cont = fullLayout[Plotly.Axes.id2name(title.replace('title',''))] || fullLayout;
else cont = fullLayout[axisIds.id2name(title.replace('title', ''))] || fullLayout;

var prop = cont===fullLayout ? 'title' : cont._name+'.title',
var prop = (cont === fullLayout) ? 'title' : cont._name+'.title',
name = colorbar ? 'colorscale' :
((cont._id||axletter).toUpperCase()+' axis'),
((cont._id || axletter).toUpperCase()+' axis'),
font = cont.titlefont.family,
fontSize = cont.titlefont.size,
fontColor = cont.titlefont.color,
Expand All @@ -68,7 +67,7 @@ Titles.draw = function(gd, title) {
avoidTransform;

// find the transform applied to the parents of the avoid selection
// which doesn't get picked up by Plotly.Drawing.bBox
// which doesn't get picked up by Drawing.bBox
if(colorbar) {
avoid.offsetLeft = gs.l;
avoid.offsetTop = gs.t;
Expand All @@ -86,57 +85,61 @@ Titles.draw = function(gd, title) {
if(colorbar && cont.titleside) {
// argh, we only make it here if the title is on top or bottom,
// not right
x = gs.l+cont.titlex*gs.w;
y = gs.t+(1-cont.titley)*gs.h + ((cont.titleside==='top') ?
3+fontSize*0.75 : - 3-fontSize*0.25);
options = {x: x, y: y, 'text-anchor':'start'};
x = gs.l + cont.titlex * gs.w;
y = gs.t + (1 - cont.titley) * gs.h + ((cont.titleside === 'top') ?
3 + fontSize * 0.75 : - 3 - fontSize * 0.25);
options = {x: x, y: y, 'text-anchor': 'start'};
avoid = {};

// convertToTspans rotates any 'y...' by 90 degrees...
// TODO: need a better solution than this hack
title = 'h'+title;
title = 'h' + title;
}
else if(axletter==='x'){
else if(axletter === 'x'){
xa = cont;
ya = (xa.anchor==='free') ?
{_offset:gs.t+(1-(xa.position||0))*gs.h, _length:0} :
Plotly.Axes.getFromId(gd, xa.anchor);
x = xa._offset+xa._length/2;
y = ya._offset + ((xa.side==='top') ?
ya = (xa.anchor === 'free') ?
{_offset: gs.t + (1 - (xa.position || 0)) * gs.h, _length: 0} :
axisIds.getFromId(gd, xa.anchor);

x = xa._offset + xa._length / 2;
y = ya._offset + ((xa.side === 'top') ?
-10 - fontSize*(offsetBase + (xa.showticklabels ? 1 : 0)) :
ya._length + 10 +
fontSize*(offsetBase + (xa.showticklabels ? 1.5 : 0.5)));

options = {x: x, y: y, 'text-anchor': 'middle'};
if(!avoid.side) { avoid.side = 'bottom'; }
if(!avoid.side) avoid.side = 'bottom';
}
else if(axletter==='y'){
else if(axletter === 'y'){
ya = cont;
xa = (ya.anchor==='free') ?
{_offset:gs.l+(ya.position||0)*gs.w, _length:0} :
Plotly.Axes.getFromId(gd, ya.anchor);
y = ya._offset+ya._length/2;
x = xa._offset + ((ya.side==='right') ?
xa = (ya.anchor === 'free') ?
{_offset: gs.l + (ya.position || 0) * gs.w, _length: 0} :
axisIds.getFromId(gd, ya.anchor);

y = ya._offset + ya._length / 2;
x = xa._offset + ((ya.side === 'right') ?
xa._length + 10 +
fontSize*(offsetBase + (ya.showticklabels ? 1 : 0.5)) :
-10 - fontSize*(offsetBase + (ya.showticklabels ? 0.5 : 0)));

options = {x: x, y: y, 'text-anchor': 'middle'};
transform = {rotate: '-90', offset: 0};
if(!avoid.side) { avoid.side = 'left'; }
if(!avoid.side) avoid.side = 'left';
}
else{
else {
// plot title
name = 'Plot';
fontSize = fullLayout.titlefont.size;
x = fullLayout.width/2;
y = fullLayout._size.t/2;
x = fullLayout.width / 2;
y = fullLayout._size.t / 2;
options = {x: x, y: y, 'text-anchor': 'middle'};
avoid = {};
}

var opacity = 1,
isplaceholder = false,
txt = cont.title.trim();
if(txt === '') { opacity = 0; }
if(txt === '') opacity = 0;
if(txt.match(/Click to enter .+ title/)) {
opacity = 0.2;
isplaceholder = true;
Expand All @@ -145,20 +148,20 @@ Titles.draw = function(gd, title) {
var group;
if(colorbar) {
group = d3.select(gd)
.selectAll('.'+cont._id.substr(1)+' .cbtitle');
.selectAll('.' + cont._id.substr(1) + ' .cbtitle');
// this class-to-rotate thing with convertToTspans is
// getting hackier and hackier... delete groups with the
// wrong class
var otherClass = title.charAt(0)==='h' ?
title.substr(1) : ('h'+title);
group.selectAll('.'+otherClass+',.'+otherClass+'-math-group')
var otherClass = title.charAt(0) === 'h' ?
title.substr(1) : ('h' + title);
group.selectAll('.' + otherClass + ',.' + otherClass + '-math-group')
.remove();
}
else {
group = fullLayout._infolayer.selectAll('.g-'+title)
group = fullLayout._infolayer.selectAll('.g-' + title)
.data([0]);
group.enter().append('g')
.classed('g-'+title, true);
.classed('g-' + title, true);
}

var el = group.selectAll('text')
Expand All @@ -173,36 +176,36 @@ Titles.draw = function(gd, title) {
.attr('class', title);

function titleLayout(titleEl){
Plotly.Lib.syncOrAsync([drawTitle,scootTitle], titleEl);
Lib.syncOrAsync([drawTitle,scootTitle], titleEl);
}

function drawTitle(titleEl) {
titleEl.attr('transform', transform ?
'rotate(' + [transform.rotate, options.x, options.y] +
') translate(0, '+transform.offset+')' :
') translate(0, ' + transform.offset + ')' :
null);

titleEl.style({
'font-family': font,
'font-size': d3.round(fontSize,2)+'px',
fill: Plotly.Color.rgb(fontColor),
opacity: opacity*Plotly.Color.opacity(fontColor),
'font-weight': plots.fontWeight
'font-size': d3.round(fontSize,2) + 'px',
fill: Color.rgb(fontColor),
opacity: opacity * Color.opacity(fontColor),
'font-weight': Plots.fontWeight
})
.attr(options)
.call(Plotly.util.convertToTspans)
.call(svgTextUtils.convertToTspans)
.attr(options);

titleEl.selectAll('tspan.line')
.attr(options);
return plots.previousPromises(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);
titleGroup.attr('transform', null);

// move toward avoid.side (= left, right, top, bottom) if needed
// can include pad (pixels, default 2)
Expand All @@ -213,10 +216,10 @@ Titles.draw = function(gd, title) {
top: 'bottom',
bottom: 'top'
}[avoid.side],
shiftSign = (['left','top'].indexOf(avoid.side)!==-1) ?
shiftSign = (['left','top'].indexOf(avoid.side) !== -1) ?
-1 : 1,
pad = isNumeric(avoid.pad) ? avoid.pad : 2,
titlebb = Plotly.Drawing.bBox(titleGroup.node()),
titlebb = Drawing.bBox(titleGroup.node()),
paperbb = {
left: 0,
top: 0,
Expand All @@ -225,9 +228,9 @@ Titles.draw = function(gd, title) {
},
maxshift = colorbar ? fullLayout.width:
(paperbb[avoid.side]-titlebb[avoid.side]) *
((avoid.side==='left' || avoid.side==='top') ? -1 : 1);
((avoid.side === 'left' || avoid.side === 'top') ? -1 : 1);
// Prevent the title going off the paper
if(maxshift<0) shift = maxshift;
if(maxshift < 0) shift = maxshift;
else {
// so we don't have to offset each avoided element,
// give the title the opposite offset
Expand All @@ -239,16 +242,16 @@ Titles.draw = function(gd, title) {
// iterate over a set of elements (avoid.selection)
// to avoid collisions with
avoid.selection.each(function(){
var avoidbb = Plotly.Drawing.bBox(this);
var avoidbb = Drawing.bBox(this);

if(Plotly.Lib.bBoxIntersect(titlebb,avoidbb,pad)) {
if(Lib.bBoxIntersect(titlebb,avoidbb,pad)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's space out these args.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good 👀

shift = Math.max(shift, shiftSign * (
avoidbb[avoid.side] - titlebb[backside]) + pad);
}
});
shift = Math.min(maxshift, shift);
}
if(shift>0 || maxshift<0) {
if(shift > 0 || maxshift < 0) {
var shiftTemplate = {
left: [-shift, 0],
right: [shift, 0],
Expand All @@ -264,43 +267,44 @@ Titles.draw = function(gd, title) {
el.attr({'data-unformatted': txt})
.call(titleLayout);

var placeholderText = 'Click to enter '+name.replace(/\d+/,'')+' title';
var placeholderText = 'Click to enter ' + name.replace(/\d+/, '') + ' title';

function setPlaceholder(){
function setPlaceholder() {
opacity = 0;
isplaceholder = true;
txt = placeholderText;
fullLayout._infolayer.select('.'+title)
fullLayout._infolayer.select('.' + title)
.attr({'data-unformatted': txt})
.text(txt)
.on('mouseover.opacity',function(){
.on('mouseover.opacity', function() {
d3.select(this).transition()
.duration(100).style('opacity',1);
.duration(100).style('opacity', 1);
})
.on('mouseout.opacity',function(){
.on('mouseout.opacity',function() {
d3.select(this).transition()
.duration(1000).style('opacity',0);
.duration(1000).style('opacity', 0);
});
}

if(gd._context.editable){
if(!txt) setPlaceholder();

el.call(Plotly.util.makeEditable)
.on('edit', function(text){
el.call(svgTextUtils.makeEditable)
.on('edit', function(text) {
if(colorbar) {
var trace = gd._fullData[cbnum];
if(plots.traceIs(trace, 'markerColorscale')) {
if(Plots.traceIs(trace, 'markerColorscale')) {
Plotly.restyle(gd, 'marker.colorbar.title', text, cbnum);
} else Plotly.restyle(gd, 'colorbar.title', text, cbnum);
}
else Plotly.restyle(gd, 'colorbar.title', text, cbnum);
}
else Plotly.relayout(gd,prop,text);
})
.on('cancel', function(){
.on('cancel', function() {
this.text(this.attr('data-unformatted'))
.call(titleLayout);
})
.on('input', function(d){
.on('input', function(d) {
this.text(d || ' ').attr(options)
.selectAll('tspan.line')
.attr(options);
Expand All @@ -309,5 +313,5 @@ Titles.draw = function(gd, title) {
else if(!txt || txt.match(/Click to enter .+ title/)) {
el.remove();
}
el.classed('js-placeholder',isplaceholder);
el.classed('js-placeholder', isplaceholder);
};
2 changes: 0 additions & 2 deletions src/plotly.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ exports.Colorbar = require('./components/colorbar');
exports.ErrorBars = require('./components/errorbars');
exports.Annotations = require('./components/annotations');
exports.Shapes = require('./components/shapes');
exports.Titles = require('./components/titles');
exports.Legend = require('./components/legend');
exports.ModeBar = require('./components/modebar');

Expand All @@ -59,7 +58,6 @@ exports.register = function register(_modules) {
_modules = [_modules];
}


for(var i = 0; i < _modules.length; i++){
var newModule = _modules[i];

Expand Down
Loading