-
-
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
Contour line labels #1815
Contour line labels #1815
Conversation
no optimization at all yet
yeah, this needs major refactoring to be more useful for cases like this
src/traces/contour/plot.js
Outdated
function makeLines(plotgroup, pathinfo, contours) { | ||
var TRAILING_ZEROS = /\.?0+$/; | ||
|
||
function makeLinesAndLabels(plotgroup, pathinfo, gd, cd0, contours, perimeter) { |
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.
Except for the innermost join content, it looks like mostly d3 operations so is probably okay, but this function comes out to 240 lines, which seems a bit on the high side.
This looks FABULOUS! From these examples I don't think we would need more control on the positioning. |
src/traces/contour/plot.js
Outdated
|
||
var maxLabels = Math.min(Math.ceil(pathLen / normLength), | ||
constants.LABELMAX); | ||
var dp, p0, pMax, minCost, location, pMin; |
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.
Just a small note of caution that my syntax highlighter picks up location
since window.location
is special, though it's 100% fine 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.
Ah good call, should be ok but I'll change it to avoid any confusion.
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.
Only danger (I think) is if var
declaration gets removed. Linter may or may not catch it. So really not dangerous at all… almost certainly 🔫
@rreusser what plot did you see those labels on tiny loops? There is a minimum of 3x the label length but perhaps we need to increase that. |
@alexcjohnson zoomed in section on: http://rickyreusser.com/plotly-mock-viewer/#contour_transposed Also, I've been thinking about carpet plots. Since plotting is pretty trace-specific, I think it might have to be duplicated, though it's probably possible to factor out the code since there's nothing really contour-specific. I'll try to throw together a POC (though probably not much work required beyond that). |
awesome, that'll help with your previous comment too 🍰 |
using just width had some adverse effects with really short labels
src/traces/contour/plot.js
Outdated
@@ -423,7 +423,7 @@ function makeLinesAndLabels(plotgroup, pathinfo, gd, cd0, contours, perimeter) { | |||
|
|||
var isOpen = d3.select(this).classed('openline'); | |||
|
|||
if(pathLen < textWidth * constants.LABELMIN) return; | |||
if(pathLen < (textWidth + textHeight) * constants.LABELMIN) return; |
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 didn't increase LABELMIN
, instead included both width and height of the label. That way when you have long labels you'll still get them on about the same length paths as before, but short labels will require a bit longer paths than they did previously:
Note that this isn't in the cost function, because that controls label location on the path but only weakly affects whether the path gets a label at all - ie if it pushes past MAXCOST
'Has only an effect if `contours.coloring` is set to *fill*.' | ||
].join(' ') | ||
}, | ||
showlines: contourContourAttrs.showlines, |
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.
🌴 thanks!
src/traces/contour/attributes.js
Outdated
'Determines whether to label the contour lines with their values.' | ||
].join(' ') | ||
}, | ||
font: extendFlat({}, fontAttrs, { |
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'd vote for labelfont
here.
cc @cldougl
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.
+1 for labelfont
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.
-> labelFont
in 2116a12
@@ -62,7 +64,11 @@ exports.get = function() { | |||
return { | |||
defs: { | |||
valObjects: Lib.valObjects, | |||
metaKeys: UNDERSCORE_ATTRS.concat(['description', 'role']) | |||
metaKeys: UNDERSCORE_ATTRS.concat(['description', 'role']), | |||
editTypes: { |
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. Sure, might as well list those things.
- so we might be able to reuse them for contourcarpet - and test getVisibleSegment
minor change to the algorithm probably due to cleaner open/closed criterion
this makes contours.coloring='lines' work with carpet also fixes some unusual bugs like if the first contour is not shown that were fixed long ago for contour
@@ -15,7 +15,7 @@ ContourCarpet.supplyDefaults = require('./defaults'); | |||
ContourCarpet.colorbar = require('../contour/colorbar'); | |||
ContourCarpet.calc = require('./calc'); | |||
ContourCarpet.plot = require('./plot'); | |||
ContourCarpet.style = require('./style'); |
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.
continuing to 🌴 contour/contourcarpet... this also fixes some bugs such as:
- contourcarpet failed with
contours.coloring='lines'
- if the first contour wasn't shown (if you give an explicit contour start value lower than what the data would be) the colors would have been wrong. For contour this was fixed in Contour bug #1309 (the key is to use
d.level
rather thani
in coloring the contour lines/fills) and tested incontour_edge_cases
.
@etpinard do you think it's sufficient to rely on the contour tests for contourcarpet too - since it's using the same code now - or would you like me to add contourcarpet tests of these cases?
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.
do you think it's sufficient to rely
We have plenty of carpet mocks, maybe you could update one of them with contours.coloring='lines'
?
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.
hmm, as far as I can tell only cheater_contour
is not constraint-type, but perhaps I can extend that one to cover these cases. Sure.
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.
0ff2dc4
This mock on master:
and now:
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.
This is great. I'd have to dig in further to refresh my memory, but I struggled a bit to wrangle the behavior under control for both constraints and contours since it felt like they had slightly different semantics. Like constraints tend to be a single color while contours have multiple colors. But if it passes the existing image tests and fixes other behaviors, then I'm 100% content.
src/traces/contour/style.js
Outdated
|
||
var colorMap = makeColorMap(trace); | ||
// for contourcarpet only - is this a constraint-type contour trace? |
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.
A bit funny to have contourcarpet only
code in contour.style
- but perhaps someday we would like to support constraint-type coloring for regular contour plots too?
@@ -15,7 +15,7 @@ ContourCarpet.supplyDefaults = require('./defaults'); | |||
ContourCarpet.colorbar = require('../contour/colorbar'); | |||
ContourCarpet.calc = require('./calc'); | |||
ContourCarpet.plot = require('./plot'); | |||
ContourCarpet.style = require('./style'); | |||
ContourCarpet.style = require('../contour/style'); |
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 🎉
@@ -21,6 +21,8 @@ module.exports = function supplyDefaults(traceIn, traceOut, dfltColor, fullLayou | |||
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); | |||
} | |||
|
|||
traceOut._clipPathId = 'clip' + traceOut.uid + 'carpet'; |
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.
@rreusser FYI - it turns out that we weren't applying the clip path to mocks that have the data trace before the carpet trace because the ID was cached during the plot step. I moved _clipPathId
up to supplyDefaults
to get around that. It's barely visible in the test images, but that's why they all changed (all the ones that put the data before the carpet anyway).
Also two more changes in here:
_clipPathId
rather thanclipPathId
- if we're attaching something to the trace (or layout) that's not an attribute, it should either be a function or it should start with an underscore.- use
Drawing.setClipUrl
rather than directly setting theclip-path
attribute. This is to handle pages with a<base>
tag, which is obnoxious but important if it's present - which happens most notably in angular 😑
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.
Oh wow… Good catch! Yeah, I can imagine that's a subtle half-border-width-effect. 👍
same as contour - it's *probably* ok without this but I'm a little worried about some restyle call changing element order unexpectedly.
also a bugfix to findBestTextLocation - surprised that didn't break anything before!
@@ -37,6 +38,6 @@ module.exports = function plot(gd, plotinfoproxy, data) { | |||
// separately to all scattercarpet traces, but that would require | |||
// lots of reorganization of scatter traces that is otherwise not | |||
// necessary. That makes this a potential optimization. | |||
node.attr('clip-path', 'url(#clip' + carpet.uid + 'carpet)'); | |||
Drawing.setClipUrl(node, carpet._clipPathId); |
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. That means carpet traces will now be functional in Angular 1.0 see #509
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.
yeah, as mentioned in #1815 (comment)
Tried to figure out if we have a way to enforce that... only allow attr('clip-path', ...)
inside Drawing.setClipUrl
- like something to add to strict-d3
? Seemed pretty invasive and hacky though.
Amazing PR! This will surely make the daunting contour code more-easily reusable ( 💃 |
@@ -487,15 +487,15 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { | |||
// to get the parity of the number of intersections. | |||
if(edges.reduce(function(a, x) { | |||
return a ^ | |||
!!lineIntersect(headX, headY, headX + 1e6, headY + 1e6, | |||
!!Lib.segmentsIntersect(headX, headY, headX + 1e6, headY + 1e6, |
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.
Not part of this PR and I'm sure no meaningful performance hit, but is reduce
necessary here? Seems like
for (i = 0; i < edges.length; i++) {
if (!Lib.segmentsIntersect(...)) return;
}
would be more concise and readable and would bail out early when it can.
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.
Unless that equates to an all
condition where you actually need to check all. Not 100% sure. The ^
is a bit confusing.
@@ -487,15 +487,15 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { | |||
// to get the parity of the number of intersections. | |||
if(edges.reduce(function(a, x) { | |||
return a ^ | |||
!!lineIntersect(headX, headY, headX + 1e6, headY + 1e6, | |||
!!Lib.segmentsIntersect(headX, headY, headX + 1e6, headY + 1e6, | |||
x[0], x[1], x[2], x[3]); | |||
}, false)) { | |||
// no line or arrow - so quit drawArrow now | |||
return; | |||
} | |||
|
|||
edges.forEach(function(x) { |
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.
Is forEach
avoidable here?
* for this element beforehand. | ||
* | ||
* @return {object}: a plain object containing the width, height, left, right, | ||
* top, and bottom of `node` |
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.
❤️ 📚
*/ | ||
drawing.savedBBoxes = {}; | ||
var savedBBoxesCount = 0; | ||
var maxSavedBBoxes = 10000; | ||
|
||
drawing.bBox = function(node, hash) { | ||
drawing.bBox = function(node, inTester, hash) { |
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.
👍 technically an internal API used elsewhere, but a quick grep I can't find any cases where the argument signature change is invalid.
// ignore the case where they are colinear | ||
if(det === 0) return null; | ||
var t = (b * f - c * e) / det, | ||
u = (b * d - a * e) / det; |
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 I were really going to nitpick, I'd suggest comparing bounding boxes first and here multiplying through by det
to eliminate division, but this is probably not a bottleneck…
@@ -36,6 +37,7 @@ module.exports = extendFlat({}, { | |||
valType: 'boolean', | |||
dflt: true, | |||
role: 'style', | |||
editType: 'docalc', |
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.
😮
Now I understand. Nice.
|
||
|
||
module.exports = function plot(gd, plotinfo, cdcontours) { | ||
exports.plot = function plot(gd, plotinfo, cdcontours) { |
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.
Named for 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.
Reuse in carpet. Got it.
return lineClip; | ||
}; | ||
|
||
exports.labelFormatter = function(contours, colorbar, fullLayout) { |
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.
Interesting. Does this have any more general applicability? I feel like piggybacking on this is a common need, and I struggled to figure out just which parts and in what sequence it should be done.
}; | ||
}; | ||
|
||
exports.findBestTextLocation = function(path, pathBounds, textOpts, labelData, plotBounds) { |
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.
❤️ ❤️ ❤️ Reusable for carpet?
@@ -15,7 +15,7 @@ ContourCarpet.supplyDefaults = require('./defaults'); | |||
ContourCarpet.colorbar = require('../contour/colorbar'); | |||
ContourCarpet.calc = require('./calc'); | |||
ContourCarpet.plot = require('./plot'); | |||
ContourCarpet.style = require('./style'); |
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.
This is great. I'd have to dig in further to refresh my memory, but I struggled a bit to wrangle the behavior under control for both constraints and contours since it felt like they had slightly different semantics. Like constraints tend to be a single color while contours have multiple colors. But if it passes the existing image tests and fixes other behaviors, then I'm 100% content.
Looks great to me! Thrilled for carpet to get a close second look. One nitpick is that 💃 |
Hmm, interesting - I suppose we could either add some extra space ahead of a leading |
... or implement #1597 |
Fixes #1395
TODO:
supplyDefaults
work without them)cc @etpinard