Diagram adjustments

25 views
Skip to first unread message

Jan Bracker

unread,
Aug 16, 2013, 9:53:49 AM8/16/13
to Diagrams Disscussion List, Tim Docker, Brent Yorgey
Hello everybody,

currently in Charts [0] we have a weird bug where rendering through diagrams using cairo works perfectly, while using SVG everything renders upside down. Not being able to pinpoint the source of this problem I tested some stuff:


By standard the backends do an adjustment to all diagrams that scales them to fill the maximum amount of space and centers them on the drawing plane. When using diagrams as a rendering backend for Charts we don't want that. The cairo backend offers an options to bypass this behavior (line 25) and by that allows absolute positioning on the drawing plane. My current hack to get around this is to draw invisible lines that mark the outlines of the drawing plane I want (line 44-48 and 42). Is there a better way to do this? (I guess Envelops help on this?)

Aside of a specific solution. Should the possibility to bypass these adjustments be added to the SVG backend or maybe removed from the Cairo backend? I think it is kind of weird they can behave that differently. Also you do not need that flag as there probably is a solution to the absolute positioning that is way more elegant then switching it on or off per backend, which in the long run leads to weird behavior when switching backends (as happened to us).

Greets
Jan

John Lato

unread,
Aug 17, 2013, 10:19:00 AM8/17/13
to Jan Bracker, diagrams...@googlegroups.com, Tim Docker, Brent Yorgey

Not a solution, but the reason that Cairo allows for bypassing the adjustment is because I needed that for supporting gtk.  i felt like my original implementation was quite hacky, but I think Brent cleaned it up a bit.

Personally I think the possibility for bypassing adjustments should be added to all backends.  The problem with auto scaling is that it makes it impossible to match query positions with canvas locations because you don't know the applied adjustment. If there were an alternative though, that would probably be ok.

--
You received this message because you are subscribed to the Google Groups "diagrams-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to diagrams-discu...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Brent Yorgey

unread,
Aug 19, 2013, 10:23:44 PM8/19/13
to Jan Bracker, Diagrams Disscussion List, Tim Docker
Hi Jan,

On Fri, Aug 16, 2013 at 03:53:49PM +0200, Jan Bracker wrote:
> Hello everybody,
>
> currently in Charts [0] we have a weird bug where rendering through
> diagrams using cairo works perfectly, while using SVG everything renders
> upside down. Not being able to pinpoint the source of this problem I tested
> some stuff:
>
> http://lpaste.net/91897
>
> By standard the backends do an adjustment to all diagrams that scales them
> to fill the maximum amount of space and centers them on the drawing plane.
> When using diagrams as a rendering backend for Charts we don't want that.
> The cairo backend offers an options to bypass this behavior (line 25) and
> by that allows absolute positioning on the drawing plane. My current hack
> to get around this is to draw invisible lines that mark the outlines of the
> drawing plane I want (line 44-48 and 42). Is there a better way to do this?
> (I guess Envelops help on this?)

First of all, let me just mention that yes, there is a better way to
do this, using the 'view' function:

http://hackage.haskell.org/packages/archive/diagrams-lib/0.7/doc/html/Diagrams-TwoD-Combinators.html#v:view

It directly sets the envelope without having to use invisible lines.

In any case, after staring at your test code a bit, I think I now see
what is going on. Here's the story. You might want to get some
popcorn first.

In diagrams, the positive y-axis points *up* (because this is
standard mathematical practice, and as much as possible I want
diagrams to be semantically elegant and abstracted from concrete
representation details). In many graphics systems such as cairo and
SVG, the positive y-axis points *down*. So both the cairo and SVG
backends perform a 'reflectX' operation before handing off a diagram
to the adjustDia2D function.

Now, when the cairo bypass option selected, it actually *does NOT do
the reflection*, in addition to not calling adjustDia2D. The point of
the bypass option is to have the logical diagram coordinates
correspond to the cairo device coordinates, so you can e.g. easily
match up mouse clicks from a GTK window to your diagram. I actually
hadn't thought about this a whole lot up until now, but this is very
weird --- constructing a diagram in an upside-down coordinate system
means that e.g. (===) and vcat will appear to produce upside-down
results, and rotations will go in the wrong direction. In fact I
wonder how John dealt with these issues in his application! I rather
think we ought to get rid of the bypass option, and I have some ideas
for a nice way to still support John's use case. But that's for
another email.

Here's my interpretation of what happened: since Chart was based on
cairo, it uses cairo's y-axis-points-down coordinate system. (I notice
in your test that you say the line p2 (50, 50 ) ~~ p2 (450, 50) is
"above" the line p2 (50, 100) ~~ p2 (450, 100), but typically in
diagrams it would be the reverse!) Since you already have explicit
coordinates for everything calculated by Chart, you just draw all the
paths in their correct location directly, so you never noticed that
(===), vcat, rotate, etc. would have given strange results, because
you don't use them (?). You then enabled the cairo bypass option which
just so happened to NOT flip the diagram over, giving you the proper
behavior with cairo. However, the SVG backend does do a flip, so the
produced diagram is "upside down"... or perhaps it's the one which is
right side up, and you have been drawing upside down diagrams the
whole time. ;) It all depends on your point of view.

Here's my suggestion: use the 'view' function to get the desired area,
not the bypass, and then flip your diagram with reflectY at the very
end, before handing it to any backend. (Yes, this means reflectY will
be called twice in a row. That's life.)

-Brent

Jan Bracker

unread,
Aug 20, 2013, 8:45:36 AM8/20/13
to Jan Bracker, Diagrams Disscussion List, Tim Docker

Hi Brent,

First of all, let me just mention that yes, there is a better way to
do this, using the 'view' function:

  http://hackage.haskell.org/packages/archive/diagrams-lib/0.7/doc/html/Diagrams-TwoD-Combinators.html#v:view

It directly sets the envelope without having to use invisible lines.

Thank you, exactly what I was looking for! I fixed the bug for the tests and here on my machine the SVG and PNG files produced through the diagrams backend look the same.

I introduced the "runBackendR" function to handle this [0]. To use the view function properly you need to know the width and height of the result image, so "runBackendR" takes these values as additional arguments. "R" because it takes a renderable. The standard "runBackend" function [1] does not correct the output yet, because it does not get the width and height information necessary for applying "view".

I am a little bit torn about whether to add those parameters and by that prevent possible users from the pain of adjusting things manually and keeping the "runBackend" function look the same as in the cairo backend. I guess it is more worth it to add the parameters and make the function easier to use. Thoughts?
 
In any case, after staring at your test code a bit, I think I now see
what is going on.  Here's the story.  You might want to get some
popcorn first.

In diagrams, the positive y-axis points *up* (because this is
standard mathematical practice, and as much as possible I want
diagrams to be semantically elegant and abstracted from concrete
representation details).  In many graphics systems such as cairo and
SVG, the positive y-axis points *down*.  So both the cairo and SVG
backends perform a 'reflectX' operation before handing off a diagram
to the adjustDia2D function.

Yeah, this was what I actually tried to explain in my mail.
 
Now, when the cairo bypass option selected, it actually *does NOT do
the reflection*, in addition to not calling adjustDia2D.  The point of
the bypass option is to have the logical diagram coordinates
correspond to the cairo device coordinates, so you can e.g. easily
match up mouse clicks from a GTK window to your diagram.  I actually
hadn't thought about this a whole lot up until now, but this is very
weird --- constructing a diagram in an upside-down coordinate system
means that e.g. (===) and vcat will appear to produce upside-down
results, and rotations will go in the wrong direction.  In fact I
wonder how John dealt with these issues in his application!  I rather
think we ought to get rid of the bypass option, and I have some ideas
for a nice way to still support John's use case.  But that's for
another email.

Isn't the "view" function a nice way to do this?
 
Here's my interpretation of what happened: since Chart was based on
cairo, it uses cairo's y-axis-points-down coordinate system. (I notice
in your test that you say the line p2 (50, 50 ) ~~ p2 (450, 50) is
"above" the line p2 (50, 100) ~~ p2 (450, 100), but typically in
diagrams it would be the reverse!)  Since you already have explicit
coordinates for everything calculated by Chart, you just draw all the
paths in their correct location directly, so you never noticed that
(===), vcat, rotate, etc. would have given strange results, because
you don't use them (?).  You then enabled the cairo bypass option which
just so happened to NOT flip the diagram over, giving you the proper
behavior with cairo.  However, the SVG backend does do a flip, so the
produced diagram is "upside down"... or perhaps it's the one which is
right side up, and you have been drawing upside down diagrams the
whole time. ;) It all depends on your point of view.

Here's my suggestion: use the 'view' function to get the desired area,
not the bypass, and then flip your diagram with reflectY at the very
end, before handing it to any backend. (Yes, this means reflectY will
be called twice in a row.  That's life.)

Brent Yorgey

unread,
Aug 20, 2013, 12:13:33 PM8/20/13
to Jan Bracker, Diagrams Disscussion List, Tim Docker
On Tue, Aug 20, 2013 at 02:45:36PM +0200, Jan Bracker wrote:
> Hi Brent,
>
> First of all, let me just mention that yes, there is a better way to
> > do this, using the 'view' function:
> >
> >
> > http://hackage.haskell.org/packages/archive/diagrams-lib/0.7/doc/html/Diagrams-TwoD-Combinators.html#v:view
> >
> > It directly sets the envelope without having to use invisible lines.
> >
>
> Thank you, exactly what I was looking for! I fixed the bug for the tests
> and here on my machine the SVG and PNG files produced through the diagrams
> backend look the same.

Great!

> I introduced the "runBackendR" function to handle this [0]. To use the view
> function properly you need to know the width and height of the result
> image, so "runBackendR" takes these values as additional arguments. "R"
> because it takes a renderable. The standard "runBackend" function [1] does
> not correct the output yet, because it does not get the width and height
> information necessary for applying "view".
>
> I am a little bit torn about whether to add those parameters and by that
> prevent possible users from the pain of adjusting things manually and
> keeping the "runBackend" function look the same as in the cairo backend. I
> guess it is more worth it to add the parameters and make the function
> easier to use. Thoughts?

I don't think I understand. Why cannot runBackend figure out the
correct width and height to use? Also, what does the default image
output by diagrams look like, vs. what it should look like (after
using the view function)? Is it just adding extra space around the
edges?

> > Now, when the cairo bypass option selected, it actually *does NOT do
> > the reflection*, in addition to not calling adjustDia2D. The point of
> > the bypass option is to have the logical diagram coordinates
> > correspond to the cairo device coordinates, so you can e.g. easily
> > match up mouse clicks from a GTK window to your diagram. I actually
> > hadn't thought about this a whole lot up until now, but this is very
> > weird --- constructing a diagram in an upside-down coordinate system
> > means that e.g. (===) and vcat will appear to produce upside-down
> > results, and rotations will go in the wrong direction. In fact I
> > wonder how John dealt with these issues in his application! I rather
> > think we ought to get rid of the bypass option, and I have some ideas
> > for a nice way to still support John's use case. But that's for
> > another email.
> >
>
> Isn't the "view" function a nice way to do this?

No, view doesn't help. Say I make a circle with radius 1, and ask it
to be drawn in a GTK window of size 400x400. Now e.g. the center of
the circle has "logical" coordinates (0,0) but device coordinates
(200,200). If the user clicks their mouse in the center of the
circle, the GTK application will tell me that there was a click at
(200,200) and I need a way to compute that this corresponds to the
origin in my diagram. Using the 'view' function would affect how the
circle is positioned and scaled, but it has no effect on the fact that
logically the circle still has a radius of 1 unit and has its center
at the origin, whereas the device coordinates are something entirely
different. The view function does not change the coordinate system of
a diagram, it only modifies the envelope, which in turn affects how it
is positioned and scaled.

-Brent

Jan Bracker

unread,
Aug 20, 2013, 12:30:55 PM8/20/13
to Jan Bracker, Diagrams Disscussion List, Tim Docker
I don't think I understand.  Why cannot runBackend figure out the
correct width and height to use?  Also, what does the default image
output by diagrams look like, vs. what it should look like (after
using the view function)? Is it just adding extra space around the
edges?

Well, some times, we have a "Renderable" (the ones from Charts not from Diagrams) and those fit things to the size they are told. So you need to tell them what size you want t render. That's why you need the size for "runBackendR" and because of some spacing at the border.

For a standard "ChartBackend" as rendered by "runBackend" you may place a circle of radius 10 at location (50, 50). Now according to our semantics we should at least see 40 pixels of space above and to the left of that circle (y-axis point downwards). I would not know how to get that information from a diagrams. Also you should see an amount of space beneath and to the right of it, but how should you know the amount of space if you do not know the size of the drawing plane?
Ah, I see.

Jan 
Reply all
Reply to author
Forward
0 new messages