-
Notifications
You must be signed in to change notification settings - Fork 11
Implement CubeHelix sampling function #31
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
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'm not particularly happy with name ColorScaleBuilder
, we might move it with corresponding functions in separate module too.
src/Color.purs
Outdated
radians = pi / 180.0 | ||
|
||
cubehelixMix :: Number -> Mixer -- TODO fix alpha | ||
cubehelixMix gama (HSLA ah' as' al' _) (HSLA bh' bs' bl' _) = |
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.
Let's maybe add this as a comment in the code?
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, should be gamma
instead of gama
.
src/Color.purs
Outdated
al = al' | ||
bl = bl' - al | ||
|
||
-- NOTE not sure why isNan check is needed so I have not ported it |
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 will remove this comments, as still not sure why it's needed 🗡
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, let's remove the NaN checks. I think it's only needed if the Color
could possibly have NaN
values internally..
src/Color.purs
Outdated
-- NOTE not sure why isNan check is needed so I have not ported it | ||
-- if (isNaN(bs)) bs = 0, as = isNaN(as) ? b.s : as; | ||
-- if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; | ||
in \t -> |
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 it should be much better in performance, what's your thoughts?
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.
Possibly. Good idea.
src/Color/Scale.purs
Outdated
@@ -53,19 +64,26 @@ stopColor :: ColorStop -> Color | |||
stopColor (ColorStop c _) = c | |||
|
|||
-- | A color scale. | |||
data ColorScale = ColorScale ColorSpace Color (List ColorStop) Color | |||
data ColorScaleBuilder = ColorScaleBuilder Color (List ColorStop) Color |
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.
Exporting this is very useful so users could write their own helper functions, without waiting for their PR to be merged. Before I needed to define my own type for it see this
Also if we have separate type which does not contain ColorSpace
then we can implement combineScale :: Number → Scale → Scale → Scale
which i did in the link above. If the Scale contained colorSpace then we would have to discard one of them, which I think will not be a good thing to do.
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.
What about just naming this ColorStops
or ColorStopList
?
src/Color/Scale.purs
Outdated
uniformScale mode b middle e = colorScale mode b stops e | ||
uniformScale mode b middle e = ColorScale mode $ uniformScale' b middle e | ||
|
||
uniformScale' :: forall f. Foldable f => Color -> f Color -> Color -> ColorScaleBuilder |
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 '
functions might be useful when someone is working on ColorScaleBuilder and might not care about ColorSpace. Like I did 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.
Okay. Can we please add a docstring?
Stack overflows on |
Thank you for working on this. Let me know when it's ready to review. |
It's ready for 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 working on this! There's a few things I'd like to discuss first, before merging this - please see the inline-comments.
src/Color.purs
Outdated
where h' = if h == 360.0 then h else h `modPos` 360.0 | ||
s' = clamp 0.0 1.0 s | ||
hsla h s l a = HSLA h s' l' a' | ||
where s' = clamp 0.0 1.0 s |
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 don't really like that we have to change this:
- It forces us to insert
clipHSLHue
calls in multiple places (see below) - Worse, it forces us to remember that we have to call
clipHSLHue
when using hue values (in the future)
Why is this necessary?
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.
it's needed for coubehelix mix of colors.
for example if we want gradient from volor eith hue 350 to color with hue 370.
without this change we will get gradient from 350 down to 10 where wi will have length of 340 in hue, not up to 370 where length will be 20. and the distinction between 370 and 10 might be useful for other sampling functions too
to not forget cliping we could use toHSLA and do not patern match on constructor, or sormthing else.
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.
you can revert this part and see that coubhelix gradient will look like
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 see, okay.
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 have just pushed a refacto of this issue.
let me know if you like the approach, or if you can think of different approach to issue?
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.
In this case, I rather prefer the previous approach, to be honest. With both hue
and hueOriginal
as fields, we have to keep track of always keeping both in sync. Sorry 😄
Thinking about this a bit more, I guess it's okay to allow for angles outside of [0°, 360°]
.
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've reverted most of the changes but made this change, so we are not forced to remember clipping Hue:
data Color = HSLA Hue Number Number Number
newtype Hue = UnclippedHue Number
clipHue :: Hue -> Number
clipHue (UnclippedHue x) = x `modPos` 360.0
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.
👍
src/Color.purs
Outdated
@@ -500,10 +505,13 @@ interpolateAngle fraction a b = interpolate fraction shortest.from shortest.to | |||
dist { from, to } = abs (to - from) | |||
shortest = unsafePartial (fromJust (minimumBy (comparing dist) paths)) | |||
|
|||
|
|||
type Mixer = Color -> Color -> Number -> Color |
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'm fine with the type synonym, but I'd like to add a short doc-string (.. a function that interpolates between two colors..).
src/Color.purs
Outdated
@@ -538,6 +546,32 @@ mix Lab c1 c2 frac = lab | |||
f = toLab c1 | |||
t = toLab c2 | |||
|
|||
radians :: Radians |
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 would move this definition inside cubehelixMix
, if it is not needed anywhere else.
src/Color.purs
Outdated
radians = pi / 180.0 | ||
|
||
cubehelixMix :: Number -> Mixer -- TODO fix alpha | ||
cubehelixMix gama (HSLA ah' as' al' _) (HSLA bh' bs' bl' _) = |
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.
Let's maybe add this as a comment in the code?
src/Color.purs
Outdated
radians :: Radians | ||
radians = pi / 180.0 | ||
|
||
cubehelixMix :: Number -> Mixer -- TODO fix alpha |
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.
Minor comment: I'd prefer mixCubehelix
.
src/Color/Scale.purs
Outdated
uniformScale mode b middle e = colorScale mode b stops e | ||
uniformScale mode b middle e = ColorScale mode $ uniformScale' b middle e | ||
|
||
uniformScale' :: forall f. Foldable f => Color -> f Color -> Color -> ColorScaleBuilder |
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.
Okay. Can we please add a docstring?
src/Color/Scale.purs
Outdated
sample :: Sampler ColorScale | ||
sample (ColorScale mode scale) = mkSimpleSampler (mix mode) scale | ||
|
||
type Sampler a = a -> Number -> Color |
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.
IMHO: this type synonym just adds mental overhead when reading the type definitions below.
src/Color/Scale.purs
Outdated
-- | Get the color at a specific point on the color scale (number between 0 and | ||
-- | 1). If the number is smaller than 0, the color at 0 is returned. If the | ||
-- | number is larger than 1, the color at 1 is returned. | ||
sample :: ColorScale -> Number -> Color | ||
sample scale@(ColorScale mode b middle e) x | ||
mkSimpleSampler mixer (ColorScaleBuilder b middle e) 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.
Why is there a docstring between type declaration and function implementation?
src/Color/Scale.purs
Outdated
colors scale = colors' (sample scale) | ||
|
||
-- | Takes sampling function and returns A list of colors that is sampled from | ||
-- | a color scale. The number of colors can be specified. |
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.
Does colors'
have to be a top-level function?
src/Color/Scale.purs
Outdated
|
||
cssColorStops scale@(ColorScale _ b middle e) = cssColorStops csRGB | ||
cssColorStopsSample :: Sampler ColorScaleBuilder -> Int -> ColorScaleBuilder -> String |
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.
What are these for? Can you please add some comments/documentation?
mix HSL c1 c2 frac = hsla | ||
(interpolateAngle frac f.h t.h) | ||
(interpolateAngle frac f.h t.h) -- NOTE here we might benefit from using uncliped Hue? |
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 see use of UnclippedHue 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.
Yes, good idea!
|
||
-- | Mix two colors by [Dave Green's `cubehelix'](http://www.mrao.cam.ac.uk/~dag/CUBEHELIX/) interpolation between them. | ||
-- | Takes gamma correction value as an argument and retunrs Interpolator. | ||
-- | For more details see: [d3-plugins/cubehelix](https://github.com/d3/d3-plugins/tree/40f8b3b91e67719f58408732d7ddae94cafa559a/cubehelix#interpolateCubehelix) |
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 links like this work in pursuit?
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 so.
I have added comments to new functions, let me know if you spot some typos or would like different wording. |
I don't have any time today but I'll try to review it in full tomorrow. Thank you very much. Do you know why the tests started failing now? Why did the stackoverflow only appear now? |
Not sure, when I was adding the test which is now failing it was raising stackoverflow at first and because of that I used smaller numbers. |
I just found out that it's allowed to have hue be out of range [0,360] https://www.w3.org/TR/css3-color/#hsl-color
This makes me think that we should remove clipping from Besides |
I came to If we try same thing I see it as creating But I don't quite see how to implement, will need to dig into implementation here is a:
if you try to look at that image with grayscale enabled with on our PC/mobile you will see that lightness is monotonic. This is Color picker in CobeHelix space. |
Thanks for fixing those
Yeah, good point. Thanks for the reference.
Maybe it's actually best if we return unclipped values everywhere. Do we need the clipped values at some point? 😄
Cool. Thanks for looking into this. Sounds like a good option to me (adding it as a ColorSpace). |
We need to clip for About So to merge this we just need to decide what should we don on clipping. |
as of spec it's fine https://www.w3.org/TR/css3-color/#hsl-color
I think it's perfectly fine the way you did it right now, thank you very much. |
I have added I've also added the |
nice! |
Let me know when you release new version :P |
Done. Thanks again! |
I've also added a small interactive cubehelix experiment at the bottom of the page: (similar to https://www.mrao.cam.ac.uk/~dag/CUBEHELIX/cubetry.html) |
sweet 🍓 |
fix #30