drawsvg icon indicating copy to clipboard operation
drawsvg copied to clipboard

reversed Y coordinate interacts badly with translate

Open mgritter opened this issue 5 years ago • 3 comments

drawSvg, as mentioned in https://github.com/cduck/drawSvg/issues/11, reverses the direction of the Y coordinate so that increasing values are upwards (like Cartesian coordinates), rather than downwards (like screen position.)

Unfortunately, this means that translate() operations have to explicitly take this into account. Here's an example:

import drawSvg as draw

d = draw.Drawing( 200, 200, (0,0) )
d.append( draw.Circle( 100, 100, 50, fill="none", stroke="black" ) )
g = draw.Group( transform="translate(100,100)" )
g.append( draw.Circle( 0, 0, 40, fill="none", stroke="black" ) )
d.append( g )
d.saveSvg( "example-transform.svg" )

If everything was using the same coordinate system, this should result in two concentric circles, but instead the output is:

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
     width="200" height="200" viewBox="0 -200 200 200">
<defs>
</defs>
<circle cx="100" cy="-100" r="50" fill="none" stroke="black" />
<g transform="translate(100,100)">
<circle cx="0" cy="0" r="40" fill="none" stroke="black" />
</g>
</svg>

example-transform

Is there a preferred way of specifying translations and other transforms? I didn't find one in the code or examples.

Could the library parse transform attributes and rewrite them to the preferred coordinate system?

Failing that, the documentation should be more clear about the flipped coordinate system and what will and won't work.

mgritter avatar Jun 05 '20 02:06 mgritter

Thanks for your input. There currently isn't a preferred way to specify transforms. I like your idea of changing each transform to the coordinate system but I haven't done this because it's a backwards compatibility issue at this point.

Adding scale(1,-1) before and after the transform is probably the best way to manually do this: "scale(1,-1) translate(100,100) scale(1,-1)"

At a temporary solution, here is a monkey patch you can use to get the right behavior:

import drawSvg as draw

# Monkey patch drawSvg so the transform argument uses the same coordinate system
# as the rest of the library
old_init = draw.DrawingBasicElement.__init__
def fix_init(self, **kwargs):
    old_init(self, **kwargs)
    transform = self.args.get('transform')
    if transform is not None:
        self.args['transform'] = 'scale(1,-1) {} scale(1,-1)'.format(transform)
draw.DrawingBasicElement.__init__ = fix_init

d = draw.Drawing(...

cduck avatar Jun 05 '20 20:06 cduck

Hello. Could you tell me the reason for the try: y = -y-height except ... bellow ? There are others on Circle and Ellipse elements regarding y arg.

class Rectangle(DrawingBasicElement):
    ''' A rectangle

        Additional keyword arguments are output as additional arguments to the
        SVG node e.g. fill="red", stroke="#ff4477", stroke_width=2. '''
    TAG_NAME = 'rect'
    def __init__(self, x, y, width, height, **kwargs):
        try:
            y = -y-height
        except TypeError:
            pass
        super().__init__(x=x, y=y, width=width, height=height,
            **kwargs)

With this try except, to draw properly I must pass y as string, like:

draw.Rectangle( x = 10, y = '0', width = 30, height = 200, fill = '#ff0000' )

Is this correct ?

Best regards, Mário.

popolinneto avatar Jun 25 '20 20:06 popolinneto

That's not the intended way to flip the y-coordinate. See #11 for a better way.

The reason for the try/except was to allow values with units like "50%" to be used.

cduck avatar Jun 25 '20 20:06 cduck

Hitting this myself - drawSvg has such great potential, there is no equivalent for it - I am doing web tools / web dev and screen coordinates with top, left at 0,0 are natural to think in.

#11 did not work as it flips text

I am using shapely https://shapely.readthedocs.io/en/stable/manual.html to perform the transformations

from shapely import Point, affinity, get_x, get_y

def to_pt(d, x, y):
    p = Point(x, y)
    pt = affinity.translate(p, -d.width / 2, -d.height / 2)
    pt = affinity.scale(pt, 1, -1, 1, (0, 0))
    return (get_x(pt), get_y(pt))

example

screen_x = 123
screen_y = 123

(d_x, d_y) = to_pt(d, screen_x, screen_y)

# where d_x and d_y are the screen_x/screen_y in drawSvg's own coordinate system - center of screen with flipped `y`

I leave this here for the next soul to find help. Do still note that the height must still be specified as negative though to match.

Maybe next version of awesome drawSvg would have a configurable coordinate system. It is a great lib, keep-it up!

iongion avatar Dec 20 '22 22:12 iongion