Open ghost opened 5 years ago
I don't think this is typically supported by graphics packages and I think it is because it can be computationally a bit expensive and maybe difficult to get reliable results. That said, I think diagrams would be a great place to include this feature, though I'm not sure the best way to express it or if it could be supported by all backends. Here is an example of someone achieving this with SVG and JavaScript: https://bl.ocks.org/mbostock/4163057
@fryguybob
I've also been thinking about working around by dividing into a lot of small segments and interpolating their colors, I'm surprised this approach looks so good. I just tried it with diagrams and the immediate problem is that I can't find any way to sample points on spline. There are trailPoints
and trailVertices
, but I can't specify some delta or number of points I want to get.
There is a way to sample points using tracing like in showTrace'
:
https://hackage.haskell.org/package/diagrams-lib-1.4.2.3/docs/src/Diagrams.TwoD.Model.html#showTrace%27
But the problem is that points are out of order and they are not equally distributed on the spline.
@ivxvm I think you want atParam
and arcLengthToParam
both from Diagrams.Parametric
.
I've implemented similar approach with diagrams and I'm very satisfied with results. It allows not only gradients, but variation of any style parameters along the trails. Here's an example of decreasing-width-decreasing-opacity: https://imgur.com/GZRmteT
Code is a mess, can be improved for sure:
morphingSpline :: Int -> (Double -> _ -> _) -> [P2 Double] -> Diagram B
morphingSpline numIterations styleFn points =
mconcat $ map render $ getZipList $ (,,) <$> ZipList points' <*> ZipList (tail points') <*> ZipList ticks
where
spline = cubicSpline False points :: Trail _ _
points' = map (\t -> spline `atParam` t) ticks
ticks = [0 :: Double, 1 / fromIntegral numIterations .. 1]
render (s, e, t) = strokeLocTrail (fromSegments [straight (e - s)] `at` P s) # styleFn t
styleFn t = opacity (1 - t) . lw (local ((1 - t) / 3))
diagram = morphingSpline 1000 styleFn points # bgFrame 1 white
Ripple texture is a side effect of aliasing (or something else), gonna figure out how to fix it. Not an issue with default line width: https://imgur.com/ym7oJyG
You can simplify your code by using section. I was hoping it might also help with the ripple effect, but it doesn't.
edit: you can fix the ripple texture by overlapping segments (tail became drop 2, fixed below)
morphingSpline :: _ => Int -> (Double -> _ -> _) -> [P2 Double] -> Diagram b
morphingSpline numIterations styleFn points =
mconcat $ zipWith render ticks (drop 2 ticks)
where
spline = cubicSpline False points :: Located (Trail _ _)
ticks = [0, 1 / fromIntegral numIterations .. 1]
render s e = stroke (section spline s e) # styleFn s
@bacchanalia
Thanks, gotta wrap my head around section
.
For some reason this gives me weird results (looks cool tho):
https://imgur.com/abQZSpt (with drop 2 ticks
)
https://imgur.com/p4pezKW (with tail ticks
)
Edit:
I've found out setting lineCap LineCapSquare
fixes the ripple, but it still looks wrong.
Opacity is incorrect because of the overlapping.
In this case, spline should already be half transparent in the middle:
https://imgur.com/46cpuRN
lineCap LineCapSquare works for basically the same reason as dropping ticks works: the line cap is thick and therefore causes overlap. The pattern still shows up to a lesser extent with drop 2 ticks
because the overlap isn't sufficient. You can increase the overlap but have finer control by increasing 2. Increasing the overlap with drop causes not enough opacity at the beginning, so I found changing the first arg to replicate n 0 ++ ticks
is better. You still need to to turn down the opacity to compensate for the overlap.
morphingSpline :: _ => Int -> (Double -> _ -> _) -> [P2 Double] -> Diagram b
morphingSpline numIterations styleFn points =
mconcat $ zipWith render (replicate 10 0 ++ ticks) ticks
where
spline = cubicSpline False points :: Located (Trail _ _)
ticks = [0, 1 / fromIntegral numIterations .. 1]
render s e = stroke (section spline s e) # styleFn s
points = map p2 [(0,0), (200,300), (500,-200), (-400,100), (0,300)]
-- using opacity/5 for n = 10 as a guess
styleFn t = opacity ((1 - t)/5) . lw (local (500*(1 - t) / 3))
diagram = morphingSpline 1000 styleFn points # pad 1.3 # bg white
@bacchanalia Running your code gives me this. What backend do you use? I'm using Rasterific-0.7.4.2, diagrams-rasterific-1.4.1.1, diagrams-lib-1.4.2.3, diagrams-core-1.4.1.1.
Edit: Ah, I just noticed https://github.com/diagrams/diagrams-lib/issues/322, it's probably not in any released version yet (I've used stack with lts-13.12). Thank you, that looks great!
Right, I should have mentioned that section was fixed in HEAD, but the fix hasn't been released yet. Sorry about that.
@bacchanalia Could you please check if this works fine for you?
morphingSpline :: _ => Int -> (Double -> _ -> _) -> [P2 Double] -> Diagram b
morphingSpline numIterations styleFn points = mconcat $ zipWith render ticks (tail ticks) -- (replicate 10 0 ++ ticks) ticks
where
spline = cubicSpline False points :: Located (Trail _ _)
ticks = [0, 1 / fromIntegral numIterations .. 1]
render s e = stroke (section spline s e) # styleFn s
points = map p2 [(0,0), (200,300), (500,-200), (-400,100), (0,300)]
styleFn t = id -- opacity ((1 - t)/5) . lw (local (500*(1 - t) / 3))
diagram = morphingSpline 1000 styleFn points # pad 1.3 # bg white
@ivxvm
Is there any general way to specify gradient along the 1d position on spline? For example, this one I made using
cubicSpline
, it consists of 6 points: https://imgur.com/MOqOKDNGradient is:
linear = mkLinearGradient (mkStops [(black, 0, 1), (white, 1, 1)]) 0 1 GradPad
What I want to achieve is that spline gradually becomes white along the way from the start to the end, so that point A is visible and B is not.