-
Notifications
You must be signed in to change notification settings - Fork 185
Auto mark: never render rect; move zero-ness to autoSpec #1368
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
Conversation
src/marks/auto.js
Outdated
@@ -75,19 +75,43 @@ export function autoSpec(data, options) { | |||
: null; | |||
} | |||
|
|||
// TODO: should we just always materialize in 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.
No, we should materialize only when necessary; this should be moved into conditional checks below. (But there’s no rush to do that now while we’re still figuring out the heuristic.)
Co-authored-by: Mike Bostock <[email protected]>
…into toph/never-rect
The heatmap examples are now fixed by checking Binning / grouping should never change the zero-ness of anything, right? In a histogram, the binning is on the other dimension from the one with the meaningful zero. In this PR we now have two places where we assert zero-ness: before inferring the mark type, and after. I'm trying to think through whether that makes sense. Before, we check only if a zero reducer has been applied. After, we check if we've picked a mark that has to "stand on" a baseline. That feels sorta reasonable? For the autoBarZero test (the simple bar chart of alphabet), here's a demo of an alternate heuristic that prefers bars when there's one data point for each value in an ordinal domain. (But really it should probably depend on that and zero-ness.) https://observablehq.com/d/ac53225f9c7e967b |
Paired with Mike. To move the zero-ness determination into autoSpec (which would move it above the determination of mark and transform implementations), we started down the road of mirroring the logic for deciding the mark and transform implementations, since zero-ness depends on that. But that started to feel like a boondoggle; we're going to refactor a bit, but the sequence of the logic will stay more similar to how it is now. Here's my current understanding of how it should work:
And here's a sketch of the refactoring. The public interfaces for autoSpec and auto won't change, but the bulk of the logic — formerly all held in auto, then split into auto and autoSpec — will be re-consolidated in a new private function, autoImpl. // expose information about what would be done; don't instantiate
export function autoSpec(data, options) {
const {x, y, fx, fy, color, size, mark} = autoImpl(data, options);
return {x, y, fx, fy, color, size, mark};
}
// decide mark and transform implementations and anything that was undefined in options;
// everything but instantiating
function autoImpl(data, options) {
// most of the auto logic gets re-consolidated here
return {...options, markImpl, markOptions, transformImpl, transformOptions}
}
// actually instantiate; just the last few lines of today's auto
export function auto(data, options) {
const {markImpl, markOptions, transformImpl, transformOptions, xZero, yZero, fx, fy, colorMode}
= autoImpl(data, options)
return marks(/* ... */);
} |
// Greedily materialize columns for type inference; we’ll need them anyway to | ||
// plot! Note that we don’t apply any type inference to the fx and fy | ||
// channels, if present; these are always ordinal (at least for now). | ||
const {x, y, color, size} = options; | ||
const X = materializeValue(data, x); | ||
const Y = materializeValue(data, y); | ||
const C = materializeValue(data, color); | ||
const S = materializeValue(data, size); |
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.
moved here from top of auto, which it was passing into autoSpec
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 we still want to do this materialization (also) inside of auto, so that it doesn’t need to be done twice (the second time being when we call auto.plot).
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.
Ooh right, we're not passing these materialized values back out of autoImpl… and we don't want to, because autoSpec shouldn't return the materialized values. So I guess auto should greedily materialize and autoImpl shouldn't bc autoSpec should return something cleaner if possible?
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 was wrong; it doesn’t materialize twice because autoImpl returns the full markOptions with the already-materialized values. I think we’re good as-is. Nice!
src/marks/auto.js
Outdated
if (xZero === undefined) | ||
xZero = X && transform !== binX && (mark === barX || mark === areaX || mark === rectX || mark === ruleY); | ||
xZero = | ||
X && | ||
transform !== bin && | ||
transform !== binX && | ||
(markImpl === barX || markImpl === areaX || markImpl === rectX || markImpl === ruleY); |
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 added the transform !== bin
check for both xZero and yZero, which I think achieves the same thing as checking !colorReduce && !sizeReduce
in the earlier implementation, i.e. it fixes the heatmap case.
@@ -211,23 +164,78 @@ export function auto(data, options) { | |||
if (transform) { | |||
if (transform === bin || transform === binX) markOptions.x = {value: X, ...xOptions}; | |||
if (transform === bin || transform === binY) markOptions.y = {value: Y, ...yOptions}; | |||
markOptions = transform(transformOptions, markOptions); |
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.
Moved down into auto bc it's instantiating
src/marks/auto.js
Outdated
x: { | ||
value: xValue ?? null, | ||
reduce: xReduce ?? null, | ||
zero: xZero ?? false, |
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.
Might as well coerce the input to a boolean here, in case the user passed in something like 0
?
zero: xZero ?? false, | |
zero: !!xZero, |
src/marks/auto.js
Outdated
y: { | ||
value: yValue ?? null, | ||
reduce: yReduce ?? null, | ||
zero: yZero ?? false, |
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.
Same.
zero: yZero ?? false, | |
zero: !!yZero, |
src/marks/auto.js
Outdated
colorMode | ||
} = autoImpl(data, options); | ||
|
||
if (transform) markOptions = transform(transformOptions, markOptions); |
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.
We could fold this into autoImpl and then autoImpl still only needs to return markImpl and markOptions—not also transformImpl and transformOptions. Not necessary, though… 🤔
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 i wasn't sure if there was some explicit benefit to not instantiating — like, if it could potentially have side effects, or be slower. also might depend on whether we'd want autoSpec to be able to report any info on transforms?
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.
Nice work! 👏👏
To fix the autoMatrix test we just need this tiny change: --- a/test/plot.js
+++ b/test/plot.js
@@ -71,7 +71,7 @@ function reindexStyle(root) {
const parent = style.parentNode;
const uid = parent.getAttribute("class");
for (const child of [parent, ...parent.querySelectorAll("[class]")]) {
- child.setAttribute("class", child.getAttribute("class").replace(new RegExp(`\\b${uid}\\b`, "g"), name));
+ child.setAttribute("class", child.getAttribute("class")?.replace(new RegExp(`\\b${uid}\\b`, "g"), name));
}
style.textContent = style.textContent.replace(new RegExp(`[.]${uid}`, "g"), `.${name}`);
} |
src/marks/auto.js
Outdated
transform !== bin && | ||
transform !== binX && | ||
!(transformImpl === bin || transformImpl === binX) && |
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 the point of this change that they're mutually exclusive and !(a || b)
can short-circuit faster than !a && !b
or something?
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 just found it more readable by grouping the related checks.
Oooh, easy, thanks Fil! I'm gonna make that a separate PR because I also wanna think about which plots should actually be in the matrix and whether it makes sense to have that kind of redundancy. (Feels a little weird that the matrix is useful for looking at the live results, but doesn't add any coverage for the automated testing.) And thanks Mike for lots of good lil edits! 🙏 |
I don’t think we should add the redundant matrix test. That was just an idea for debugging this problem. |
…q#1368) * Auto mark: never render rect; move zero-ness to autoSpec * update test artifacts * fix some tests; only set zero on a dimension if that dimension is defined * Update src/marks/auto.js Co-authored-by: Mike Bostock <[email protected]> * dont set zero-ness if colorReduce * fix some tests * prettier * just committing the state after pairing so i have it * revert auto file * re-fix the original motivating bugs, i think * autoImpl * rm autoplot matrix test bc it didnt work with test runner * transformImpl; coerce zero; sort imports; const * normalize mark option --------- Co-authored-by: Mike Bostock <[email protected]>
Still pretty rough… head's spinning a bit from all the combinations.
Fixes #1340 by preferring rectY over rect… but that messes with our zero-ness heuristic, which depended on the inferred mark implementation. Moved that logic to autoSpec but haven't fixed it in the heatmap case yet.
Fixes #1365 by partially reverting 6eab4f2 's changes to how we decide the mark type.
To-do:
Questions:
Current broken tests
Definitely gotta fix the heatmaps. The alphabet example I'm not sure about; maybe people should just have to specify bar there. The mean zero going back to being a line feels OK.