-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Fix axis line width, length, and positioning for coupled subplots #1854
Conversation
no updated baseline yet because the behavior is broken
no functionality changes here, just simplifying and linting
@etpinard this is ready for a look. I ended up changing nearly all the baseline images, so let me explain (and please look closely at the new images, let me know if you think these changes are positive AND acceptable in terms of compatibility).
And an aesthetic question for you: Notice in mock 20 and the x line and y line comments about how we handle free axes: if a free axis is aligned with an opposite-letter anchored axis, I opted to extend its line out the same way as if both axes are anchored - but perhaps it would be better to leave free axes always ending at the edge of the subplot? That would mean for example that the green, yellow, purple, and brown axes in 20 would be shortened to the same span as the blue and red axes. |
src/plot_api/subroutines.js
Outdated
var ya = Plotly.Axes.getFromId(gd, subplot, 'y'); | ||
var xDomain = xa.domain; | ||
var yDomain = ya.domain; | ||
var xDomain = plotinfo.xaxis.domain; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing. Note that references in plotinfo
in are updated linkSubplots
called during supplyDefaults.
src/plot_api/subroutines.js
Outdated
var axList = Plotly.Axes.list(gd); | ||
var hasSVGCartesian = fullLayout._has('cartesian'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To note, this is needed as gl2d now uses cartesian (SVG layers) subplots to handle selections.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah right - I figured out that that's the reason, which is why I added SVG
to the var name, but perhaps I should add the full explanation in a comment. Will become important when we start using SVG axes with GL2D plots @dfcreative
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
comment added 6d41a37
When I first saw the title of that issue "Axis labels on very tall graph don't reach end of axis" I thought it might be related to the axis line being (sometimes) longer than the subplot (and therefore you can't have ticks/labels all the way to the end) - see my "aesthetic question" in #1854 (comment). That's one way in which it actually isn't a purely aesthetic consideration, you could argue that axis lines for free axes should map exactly to the extent of the plot area, to help the viewer map far-away data points onto the axis. I think I buy that argument actually, so I'd be inclined to make this change. Any objection? |
src/plot_api/subroutines.js
Outdated
var ylw = Drawing.crispRound(gd, ya.linewidth, 1); | ||
|
||
function findMainAxis(ax) { | ||
return ax.overlaying ? Plotly.Axes.getFromId(gd, ax.overlaying) : ax; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non- ⛔ : might be nice to stash references to overlaying and anchor axes in plotinfo
during makeSubplotData
and 🔪 those Axes.getFromId
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good idea. Actually makeSubplotData
didn't really seem the place for this, it duplicated a lot of effort that should have just been done once per axis. I put this all into plots.linkSubplots
. 274526a
src/plot_api/subroutines.js
Outdated
return ax.overlaying ? Plotly.Axes.getFromId(gd, ax.overlaying) : ax; | ||
} | ||
|
||
function findCounterAxes(ax) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be too much to ask to move those new find*
functions outside of the subplotSelection.each
scope? I don't think compilers can optimize that still meaning these functions are redefined for every subplot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, I actually meant to un-nest these, thanks for reminding me. b31e60d
src/plots/cartesian/axes.js
Outdated
@@ -1792,7 +1795,9 @@ axes.doTicks = function(gd, axid, skipTitle) { | |||
} | |||
else { | |||
flipit = (axside === 'right') ? 1 : -1; | |||
labely = function(d) { return d.dy + d.fontSize / 2 - labelShift * flipit; }; | |||
labely = function(d) { | |||
return d.dy + d.fontSize * 0.35 - labelShift * flipit; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non- ⛔ (might be better suited for #1434 - the part where we proposed making it configurable) but this 0.35
would look really nice side-by-side LINE_SPACING
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hah, I found the 0.35 value empirically, but polar had the same one, and it turns out there's a rational basis for exactly this value. 16bae9c
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indeed! just out of curiosity, would alignment-baseline: central
be an alternative, or there's something that would be lost with it? (tweenability comes to mind, smooth with pixels but not nice with discrete levels, though probably tweening isn't applicable here)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would
alignment-baseline: central
be an alternative
Quite possibly! I recall some baseline properties that I tried to use in the past not having sufficient cross-browser support, so we would need to test it. Also I'm not sure how this would work with pseudo-html (subscript/superscript, changing font sizes, multiple lines...) so I'm not going to do it now but we could investigate later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you run into such a thing I'm interested as the upcoming table
uses it. IIRC dominant-baseline
had some inconsistencies but don't now recall an issue with alignment-baseline
. We'll see, I just bought a machine for real-life testing which as MS Edge on it.
'M0,0') | ||
.attr('stroke-width', ylw + 'px') | ||
.style('stroke-width', ylw + 'px') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. Is .attr('stroke-width', /* */)
a thing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, a lot of properties in SVG you can use either .attr
or .style
, with slightly different rules but also very different precedence - see https://css-tricks.com/presentation-attributes-vs-inline-styles/ - attributes are about the lowest, while inline styles are nearly the highest.
@monfera and I had a discussion about this a few days ago, I think for our purposes .style
should be preferred.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ha, interesting. It's something we might to standardize in strict-d3.js
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think stroke-width
as an attribute doesn't need the 'px'
but also, the question kept brewing in my mind and I like the (user-experienced) flexibility of using attributes where possible, as it's easy to restyle them. With D3
the typical usage is inline styling of the element, which is for our current context, the highest priority, ie. the user would have a hard time overriding it (there are two main ways: 1) user reselects with D3 and changes the inline style; 2) user specifies !important
styling in the style sheet. So, using attr
would be more convenient for users if they choose to restyle via CSS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... also, there's the thing that Microsoft browsers (IE, Edge) don't have full CSS support, so if the decision is to go with style
as much as we can, then we still must use attr
for transform
and possibly some more presentation attributes, ie. some style
->attr
switch may need to happen during MS testing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, transform
should stay as an attr
for MS support. I think even some things like x
and y
that we've always provided as attributes can be styles. I think my own proclivity is to treat positioning as attributes and everything else as style, but I'm sure I've broken that rule. Might be worthwhile to create a list of property names we use and whether each should be used as a style or attribute? And then yes, we could enforce that in strict-d3
.
I'm sympathetic to the idea of giving users more control, but in the end I think it would be a mistake to encourage overriding internals of the plot with CSS - we should push people to use the plot JSON instead - for three reasons:
- Portability: with outside CSS the figure JSON no longer fully describes the plot. Even if you don't care about sharing a plot to another server, you might care about the image server or - depending on how the user applied their styling - even in-browser downloading could be affected.
- Accidental overrides: we use a lot of generic class names like
.point
,.xtitle
, that could get mangled easily if the page does something else with those classes. - Forward compatibility: obviously anyone can peek inside our elements and find selectors for whatever elements they'd like to style, but we've never published this as part of the API, and I don't want to give up the right to refactor our structures and rename classes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes these points are all true. We don't currently go out of our way to dissuade scenegraph class based styling - we don't generate mangled or one-off class names or otherwise actively dissuade external styling. Also on the forum, SO and gh questions sometimes our answer is that a particular wish isn't supported but can be done with a d3.selectAll('whatever').style(...)
and our scenegraph class is human friendly so I over-inferred 😄 That and the upside of not having to have heuristics for when to switch to attr
. Btw. as a developer either way is fine as long as there's a guideline like what you mentioned above @alexcjohnson
Not sure if the emerging linter solution already covers it, I think this is the list of attributes (ie. all presentation attributes) that can go either way.
Unfortunately it's quite Venn-diagrammey and common stuff like x
, dx
, r
etc. ie. drivers of the most basic geometry are not on it so even in the future, those things won't be able to use CSS transitions/animations. (Maybe interesting is that the SMIL deprecation note got retracted.)
if(showfreex) { freefinished.push(xa._id); } | ||
if(showfreey) { freefinished.push(ya._id); } | ||
if(showFreeX) freeFinished[xa._id] = 1; | ||
if(showFreeY) freeFinished[ya._id] = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Smart. I usually set keys like this as true
. But 1
is truthy and three characters shorter!
src/plot_api/subroutines.js
Outdated
* free x lines are not excluded - they don't necessarily *meet* the | ||
* y lines, but it still looks good if the x line reaches to the ends | ||
* of the y lines, especially in the case of a free axis parallel to | ||
* an anchored axis, like this: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree 100%. Thanks for documenting.
src/plot_api/subroutines.js
Outdated
* y lines do not get longer when they meet x axes, because the | ||
* x axis already filled that space and we don't want to double-fill. | ||
* BUT they get longer if they're free axes, for the same reason as | ||
* we do not exclude x axes: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mock 20
looks amazing.
Hmm. By extent of the plot area, you mean spanning exactly |
domain, but yeah - the idea is to have it exactly match the x or y pixel range on which we display data. |
@alexcjohnson here's what mpl does: Looks like free axes do span the whole axis domain regardless of their sister axis line width. All in all, we'll probably need some new attribute governing this behavior in the future. I'll let you decide the best default value for now. |
We could probably use these in other places too, to reduce getFromId calls
And for those you (like me), that had issues understanding the implications of
Here's the effect as of eb48a9d when relayout'ing |
I tried this out, and I think it both looks good and best represents the data. 739c998 Also I included |
@@ -295,7 +296,7 @@ var µ = module.exports = { version: '0.2.2' }; | |||
angularAxisEnter.append('text').classed('axis-text', true).style(fontStyle); | |||
var ticksText = angularAxis.select('text.axis-text').attr({ | |||
x: radius + axisConfig.labelOffset, | |||
dy: '.35em', | |||
dy: MID_SHIFT + 'em', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🏆 for patching the polar code.
test/image/mocks/20.json
Outdated
@@ -291,6 +291,6 @@ | |||
}, | |||
"width": 750, | |||
"height": 600, | |||
"margin": {"r": 200, "l": 50, "t": 50, "b": 50} | |||
"margin": {"r": 200, "l": 50, "t": 50, "b": 50, "pad": 5} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🏆 for bringing margin.pad
back to life.
// while we're at it, link overlaying axes to their main axes and | ||
// anchored axes to the axes they're anchored to | ||
var axList = Plotly.Axes.list(mockGd, null, true); | ||
for(i = 0; i < axList.length; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Much better. I'd glad linkSubplots
is getting more attention. Updating all those refs early during supplyDefaults
sounds like the way to go.
One small non-:no_entry: comment, that we could easily address later: Axes.list
is now called twice during Plots.supplyDefaults
here and after doAutoMargin
here. Maybe we could call it once and stash the result in fullLayout
instead?
💃 solid work. 😄 to see that
Make sure to either close #1434 and open up a new issue about making tick label alignment configurable OR update the #1434 issue title accordingly. |
@etpinard this is for the first bug we came across this afternoon, but I'm not going to fix it until you sort out the second one (re:
joinLayer
) because I expect the symptoms are likely to change with that fix - although I don't expect it will be fixed. Turns out that there are a bunch of related issues with the wrong line widths getting applied to different lines, and the wrong positioning of ticks & labels regardless of which width is correct. The mock changes below make all the axis lines semi-transparent so we can tell if ticks and counteraxes abut (as intended) or overlap.Also to note (will be fixed in this PR): axis lines currently get
stroke-width
set both as a style and an attribute - with different values 🙀 . I'll remove this duplication and standardize on style.