Skip to content
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

test(xy): scale type improvements #1381

Merged
merged 30 commits into from
Sep 16, 2021
Merged

Conversation

monfera
Copy link
Contributor

@monfera monfera commented Sep 16, 2021

Summary

As a first step for comprehensive type safety, this PR increases TS type safety to the central Cartesian functions/methods/classes and lots of utility and render function dependencies through turning the Scale generic on its domain

Details

There were a couple of strategically placed anys in the Scale interface, and elsewhere. Though low in number, their propagating effect meant that most of the xy code doesn't really enjoy type safety, there can always be a scale induced any leak, so only very specific functions could be considered type safe.

The current PR is a start of work, focusing on Scale.domain, Scale.ticks, and its positive effects on Scale methods and lots of other functions. While there's still a lot of dirt swept under the carpet (just grep as Scale<) it's way more explicit, and these assertions can be solved one by one.

So, statically a lot more places where we assert or say any, but it's more explicit, closer to the as yet unresolved bits.

There's also a place where the D3 scale is called with a type that's not enabled ([undefined] domain); it's now marked explicitly.

Issues

Under the umbrella of #1303

Checklist

  • The proper chart type label has been added (e.g. :xy, :partition)
  • The proper feature labels have been added (e.g. :interactions, :axis)
  • All related issues have been linked (i.e. closes #123, fixes #123)
  • Unit tests have been added or updated to match the most common scenarios

@monfera monfera added the wip work in progress label Sep 16, 2021
@monfera monfera marked this pull request as draft September 16, 2021 09:19
@monfera monfera marked this pull request as ready for review September 16, 2021 16:37
@monfera
Copy link
Contributor Author

monfera commented Sep 16, 2021

test this

@monfera monfera changed the title chore: test test(xy): scale type improvements Sep 16, 2021
@monfera monfera removed the wip work in progress label Sep 16, 2021
@monfera monfera added :refactor Code refactor :test Missing or to be fixed test :xy Bar/Line/Area chart related code quality labels Sep 16, 2021
@monfera monfera added the :data Data/series/scales related issue label Sep 16, 2021
@monfera monfera mentioned this pull request Sep 16, 2021
19 tasks
Copy link
Member

@markov00 markov00 left a comment

Choose a reason for hiding this comment

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

I haven't gone too far, but in general this are the current assumptions for cartesian:
yScale is always Scale<number>
xScale is always Scale<number|string>, can be restricted if required but most of the time I don't need to know if the value I'm sending to the scale is a number or a string, I just need to know that the output of the scale is a number

Comment on lines 167 to 170
const padding = (xScale.step - (xScale as ScaleBand<string | number>).originalBandwidth) / 2;
annotationValueXPosition -= padding;
} else {
annotationValueXPosition += xScale.originalBandwidth / 2;
annotationValueXPosition += (xScale as ScaleBand<string | number>).originalBandwidth / 2;
Copy link
Member

Choose a reason for hiding this comment

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

I think you can remove this assertion because isBandScale already define xScale as ScaleBand

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, will check! I haven't added assertions where there was no complaint, but it's possible that a later commit made an earlier assertion unnecessary, indeed a good idea to revisit all

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed, I only later turned the type guard function generic, now it no longer complains there 👯‍♀️

Copy link
Contributor Author

Choose a reason for hiding this comment

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

const dimensions = computeRectAnnotationDimensions(
annotationSpec,
yScales,
xScale as Scale<number>,
Copy link
Member

Choose a reason for hiding this comment

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

why you are restricting this to number only if the passed one could be both?

Copy link
Member

Choose a reason for hiding this comment

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

You can change computeRectAnnotationDimensions to accept Scale<unknown> because internally you are calling isBandScale that assert the type for you

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Re the question, it's because it's in a conditional branch, and I didn't want to add potentially unduly restrictive types in the other, unrelated branch in the first round of improvements

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can't change computeRectAnnotationDimensions to accept Scale<unknown> because calling limitValueToDomainRange will have a red squiggly mark (limitValueToDomainRange performs a subtraction of domain values, so it must be numeric) and for the same reason it can't be Scale<number | string> either

Basically the computeRectAnnotationDimensions only seems to work with numeric X, though I haven't tested the runtime, it was enough for me to see the subtraction. Though the subtraction only happens if the domain length is non-zero, I assumed it's always the case

Copy link
Contributor Author

Choose a reason for hiding this comment

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

By extension, the entire computeRectAnnotationDimensions only works with a numeric X scale, Marco or Nick lmk if it's not the case

@@ -64,7 +64,7 @@ export function computeRectAnnotationDimensions(
let xAndWidth: { x: number; width: number } | null = null;

if (isBandScale(xScale)) {
xAndWidth = scaleXonBandScale(xScale, x0, x1);
xAndWidth = scaleXonBandScale(xScale as ScaleBand<number>, x0, x1);
Copy link
Member

Choose a reason for hiding this comment

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

this one also seems unnecessary, see my previous comment in file /xy_chart/annotations/utils.ts

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, thanks for the catch, I need to revisit these (the assertion was done before the function param types were tightened)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@@ -31,8 +31,8 @@ import {
export function renderArea(
shift: number,
dataSeries: DataSeries,
xScale: Scale,
yScale: Scale,
xScale: Scale<number>,
Copy link
Member

Choose a reason for hiding this comment

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

this should be string|number as every other xScale
The scale is used in the pathGenerator.x function with the data coming from dataSeries.data where the x can be both string or number DataSeriesDatum.x

Copy link
Contributor Author

Choose a reason for hiding this comment

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

xScale? are we ever linking ordinals with a line? I tightened it to number because:

  • it unconditionally calls renderPoints which expects numeric
  • I wasn't aware of using lines for ordinals
  • tests let it pass

Just made a quick attempt to relax the xScale param of renderPoints to also allow strings and it didn't complain, so I can relax it here too, though no idea why I didn't get a complaint for my exclusion of the strings, will look into it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@@ -21,8 +21,8 @@ import { MarkSizeOptions } from './utils';
export function renderBubble(
shift: number,
dataSeries: DataSeries,
xScale: Scale,
yScale: Scale,
xScale: Scale<number>,
Copy link
Member

Choose a reason for hiding this comment

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

number|string

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@@ -30,8 +30,8 @@ import {
export function renderLine(
shift: number,
dataSeries: DataSeries,
xScale: Scale,
yScale: Scale,
xScale: Scale<number>,
Copy link
Member

Choose a reason for hiding this comment

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

number|string

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@@ -29,8 +29,8 @@ import {
export function renderPoints(
shift: number,
dataSeries: DataSeries,
xScale: Scale,
yScale: Scale,
xScale: Scale<number>,
Copy link
Member

Choose a reason for hiding this comment

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

number|string

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks Marco, will revisit these!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

const defaultDomain = domain.length === 0 ? [undefined] : domain;
return new ScaleBand(defaultDomain, [0, maxRange], undefined, singlePanelSmallMultiple ? 0 : padding);
return new ScaleBand(
domain.length > 0 ? domain : [(undefined as unknown) as number],
Copy link
Contributor Author

@monfera monfera Sep 16, 2021

Choose a reason for hiding this comment

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

Yes we've already been cheating to comply with the D3 scale typedef file and the D3 doc, but now we're clear about it. I'll try to solve it in a next PR, it's only partially addressed in this one

}
return false;
const nextLastDrag = nextProps.lastDrag;
return nextLastDrag !== null && (prevLastDrag === null || prevLastDrag.end.time !== nextLastDrag.end.time);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Replacing a hairy control structure and making the logical conditions less redundant

@monfera monfera merged commit cd201de into elastic:master Sep 16, 2021
@nickofthyme
Copy link
Collaborator

🎉 This PR is included in version 37.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@nickofthyme nickofthyme added the released Issue released publicly label Oct 5, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
code quality :data Data/series/scales related issue :refactor Code refactor released Issue released publicly :test Missing or to be fixed test :xy Bar/Line/Area chart related
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants