diagrams / diagrams-lib

Diagrams standard library
https://diagrams.github.io/
Other
138 stars 62 forks source link

how can I apply a different color on some diagrams? #294

Closed Javran closed 7 years ago

Javran commented 7 years ago

The following code results in a red circle, rather than a green one (any backend should work),

main :: IO ()
main = mainWith (circle 1 # fc red # fc green :: Diagram B)

I don't recall seeing anything saying I can't apply color on same diagram twice, and the second application (i.e. # fc green) should have precedence.

cchalmers commented 7 years ago

Unfortunately this isn't possible at the moment, the only way to combine an attribute is by using the semigroup structure and FillTexture uses Last. You could write your own fill attribute that uses First which would do what you want but then the backend wouldn't know what to do with it.

Once we have a style traversal, replacing a fill colour could be done with something like:

myGreenCircle = circle 1 # fc red & styles . _fillTexture ?= solid green

I've got a working version in my rewrite but it isn't possible to do a valid style traversal with the current DUALTree internals. So it might be a while before this makes it to the release version.

Javran commented 7 years ago

@cchalmers I'm a bit confused here, what does the use of Last suppose to do? I think for Maybe or Option it's taking the last non-empty one appended, but for circle 1 # fc red # fc green, or fc green (fc red $ circle 1) it sounds more intuitive to me that fc green is the last.

cchalmers commented 7 years ago

A diagram is really a tree with annotations, whenever you apply an attribute it's applied to the root of a the tree. So your diagram would look something like

fc green
   |
 fc red
   |
circle 1

When a diagram is rendered you start from the top of the tree and accumulate the style (and transformation). Once you reach the leaf primitive (the Path given by circle 1 in this case) you render it with the accumulated style (and transformation). In this case the accumulated style would be

Last green <> Last red = red

Another way you can think about it is by writing it as

x # applyStyle a # applyStyle b = (applyStyle b . applyStyle a) x

And since applyStyle is a Monoid homomorphism

applyStyle b . applyStyle a = applyStyle (b <> a)
byorgey commented 7 years ago

Just to be clear, this was a deliberate choice: you say "the second application should have precedence", but I think a good argument can be made that sometimes you really want the first application to have precedence, and that in particular this is a more sensible default. In particular, it means you can specify colors for some parts of a diagram and leave colors unspecified on other parts; then applying a color to the entire diagram has the effect of setting the color of only the parts with no color specified, instead of simply making the entire diagram the new color. The manual does mention this in a few places (e.g. "In general, inner attributes (that is, attributes applied earlier) override outer ones."), though I don't blame you for missing it.

Ultimately, I agree the right solution will be to have style traversals. For now, if you want to allow later overriding of colors you could do something like

myCircle :: (Diagram B -> Diagram B) -> Diagram B
myCircle styleFun = circle 1 # styleFun # fc red

... later ...

myCircle id  -- circle with default (red) style
myCircle (fc green)  -- circle with overridden color

It's not necessarily ideal but it works.

Javran commented 7 years ago

thanks for the explanation, this behavior now makes sense to me. I'm still new to diagrams and incorrectly assumed when you modifies an attribute the old one gets overwritten - haven't got to the point of being introduced about the notion of inner and outer attributes.

depending on the usage I think there are plenty of workarounds, @byorgey has just gave one, but I'm good with just keeping track of color somewhere else and apply it later. as in my use case color is probably the only thing that could require a change later.

byorgey commented 7 years ago

Yeah, separately keeping track of the color, updating it in whatever way you want, and then applying it at the very end is another option. It's also not ideal since you have to manually keep track of which colors go with which diagrams --- but in simple enough cases this can work fine.

I'll close this issue for now---feel free to re-open if there's anything more to discuss.