-
Notifications
You must be signed in to change notification settings - Fork 715
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
feat: re-type vx/scale with new functionalities #766
Conversation
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 know it's WIP, just leaving some general comments reading through
scale.clamp(clamp); | ||
applyInterpolate(scale, config); | ||
applyRound(scale, config); | ||
applyZero(scale, config); |
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.
Since these are mutable functions, is call order significant here? Does applyInterpolate()
have to be called before applyRound()
? If so, we might want to think of a way to enforce this. I don't expect it to a problem as it is lib code, but could be an easily introduced bug that could slip through a review.
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.
Thank you for the comments. I think the order of operations is a valid point. I could set up a proxy function and only allow a single apply call with list of operations through this proxy to ensure order. Not sure if that is an overkill.
Since these are mutable functions, is call order significant here?
The ones that really have to be in order are domain > nice > zero
.
Does
applyInterpolate()
have to be called beforeapplyRound()
?
Not necessary. They should be mutually exclusive. applyInterpolate()
is in fact applyColorInterpolate
. (This is a new functionality for vx
. I could also drop it. Previously I don't think vx
was accepting interpolate
.)
Users can either do rounding which will set interpolateRound
when a scale output is number.
{ round: true }
or
when a scale output is color and they want to set color interpolation to HCL
{ interpolate: 'hcl' }
It is still not super clean and the typing may allow something like this to be specified. Although both effect cannot happen at the same time.
{ interpolate: 'hcl', round: true }
Alternatives
- I could drop the
interpolate
feature for now. - Stricter typing to allow only either
{ interpolate }
or{ round }
. Can be complicated. - Make
{ round: true }
become{ interpolate: 'round' }
.
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.
One idea instead of proxy (can copy & paste in browser console):
const log = (str) => (scale, config) => console.log(str);
const operations = {
domain: log('domain'),
nice: log('nice'),
interpolate: log('interpolate'),
round: (scale, config) => config.interpolate ? console.warn(`can't round and interpolate`) : console.log('round'),
zero: log('zero')
};
const order = Object.keys(operations);
const intersection = (a,b) => a.filter(v => b.includes(v));
function scaleOperator(...ops) {
const orderedOps = intersection(order, ops);
return (scale, config) => {
return orderedOps.forEach(op => operations[op](scale, config))
}
}
// order doesn't matter
const applyOperator1 = scaleOperator('round', 'zero', 'domain', 'nice');
const applyOperator2 = scaleOperator('round', 'zero', 'domain', 'interpolate');
// in createScale({ scale, config })
applyOperator1(/**scale*/ {}, /**config*/ {});
applyOperator2(/**scale*/ {}, /**config*/ { round: true, interpolate: true });
output 1:
=> domain
=> nice
=> round
=> zero
output 2:
=> domain
=> interpolate
=> WARN: can't round and interpolate
=> zero
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 refactor with this idea. The individual scale files now look very clean.
@hshoff @williaster One question I would love thoughts here is about { rangeRound: xxx } is basically syntactic sugar for { range: xxx, round: true } which makes sense for when it was D3 function // for continuous scales
scale.rangeRound(xxx) = scale.range(xxx).interpolate(interpolateRound);
// for point or band scales
scale.rangeRound(xxx) = scale.range(xxx).round(true); but when we are dealing with a config as input, should we provide single way to define // all of these will pass the current typescript type check unless we add more union types
{ rangeRound: xxx, round: false }
{ range: xxx, rangeRound: xxx }
{ range: xxx, rangeRound: xxx, round: false } I am proposing we drop support for |
Another question is the addition of // @ts-ignore
scale.type = 'log'; |
These are definitely not ideal, I believe they were added at one point to enable checking type, but I couldn't fine any references with search. @hshoff do you remember where they were used? If they are no longer used I think we should remove them, and even if they are it'd be great to find another approach. |
This makes sense to me based on all of the considerations. If the types are clear about this, then the docs should pick them up and also be clear. If we get issues from users who are confused about it from |
+1
My guess is I was going to use them in Legends or Axis before the TypeScript re-write. Probably ok to remove them as a breaking change. |
|
||
return scale; | ||
}; | ||
} |
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.
nice solution
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.
Had a couple of small suggestions, mostly just impressed! Amazing job 💯
|
||
// Actual implementation | ||
|
||
function createScale< |
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 will be amazing for XYChart
🤩
year: utcYear, | ||
}; | ||
|
||
export default function applyNice< |
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 amazing
}); | ||
it('set unknown', () => { | ||
const scale = scaleOrdinal({ domain: ['noodle', 'burger'], unknown: 'green' }); | ||
expect(scale('sandwich')).toEqual('green'); |
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.
💯
💥 Breaking Changes
rangeRound
field in the input ofscaleLinear()
,scaleLog()
,scalePoint()
,scalePower()
,scaleSqrt()
,scaleTime()
andscaleUtc()
.Instead of
Do this instead
Deprecate
ticks
andtickFormat
in the input ofscaleQuantize()
. It was not really doing anything anyway as bothscale.ticks()
andscale.tickFormat()
do not mutate the scale.Remove
scale.type
field that was attached to the d3 scales.🚀 Enhancements
New functions
createScale()
: A factory function for all scalesinferScaleType()
: Given a D3 scale, return a string that tells its type.New fields for the scale configs
scaleLinear
interpolate
,zero
scaleLog
interpolate
scalePower
interpolate
,zero
scaleSqrt
interpolate
,zero
scaleSymlog
clamp
,nice
,zero
scaleTime
clamp
,interpolate
,nice*
scaleUtc
clamp
,interpolate
,nice*
zero
: Adjustdomain
(if necessary) to include0
interpolate
: Take a string of color interpolator names or parameter object and set the interpolator function for the scale.nice*
: This is an existing field, but for time scales, it now accepts more thanboolean
.so you can specify nicing to round by every 15 minutes like this
Other improvement
Stronger type-safety overall with new types for all scales and scale configs.
Updated
updateScale(scale, config)
with type-safety. No moreany
. Also type-check to make sure thescale
andconfig
are compatible.New type exports
📝 Documentation
🐛 Bug Fix
🏠 Internal
vx/scale
package has 100% test coverage.