2

I'm looking for a simple way to make the coordinate system in a given svg element start from the top right corner, instead of the top left. This means the X axis is flipped, thus increasing the x attribute of an element renders it further to the left, and increasing the y attribute renders it further to the bottom as usual.

I've played around with scale and viewBox, however:

  • scale almost solves the problem, but it doesn't really work for my use case because it also flips the text I've got rendered
  • viewBox doesn't seem work with height="100%" and width="100%". For my use case I don't think I can hard code the height and width of the SVG because I need it to be usable across many different resolutions and screen sizes.

This question says it solves the same problem for the Y axis with a matrix transformation. I looked around and tried to calculate the equivalent for the X axis, but with no success.

Here is an example of what I'm trying to achieve:

<svg style="border: 1px black solid;" height="100%" width="100%">
  <g>
    <g>
      <rect fill="#F0BC40" width="70" height="12" x="0" y="30"></rect>
      <text fill="black" font-size="10px" text-anchor="middle" x="35" y="29">7</text>
    </g>
    <g>
      <rect fill="orange" width="50" height="12" x="72" y="30"></rect>
      <text fill="black" font-size="10px" text-anchor="middle" x="97" y="29">5</text>
    </g>
    <g>
      <rect fill="orange" width="40" height="12" x="124" y="30"></rect>
      <text fill="black" font-size="10px" text-anchor="middle" x="144" y="29">4</text>
    </g>
    <g>
      <rect fill="red" width="50" height="12" x="166" y="30"></rect>
      <text fill="black" font-size="10px" text-anchor="middle" x="191" y="29">5</text>
    </g>
      <rect fill="#52575E" width="2" height="16" x="70" y="28"></rect>
      <rect fill="#52575E" width="2" height="16" x="122" y="28"></rect>
      <rect fill="#52575E" width="2" height="16" x="164" y="28"></rect>
    </g>
  </svg>

As you can see I'd like this stacked bar to be rendered from the right, with the red bar being the furthest to the left (so essentially the stacked bar would be flipped)

Also I'm doing this in Elm, so I can't access the DOM to check widths, heights or coordinates of elements (I'm calculating everything in a functional way).

If anyone could help me achieve this I'd be greatly thankful.

Sasha Fonseca
  • 2,257
  • 23
  • 41

2 Answers2

2

The way I would think about this is drawing your bars from x="0" to the left, and then setting the viewBox with a negative x value and a width that lets it end at x="0".

For the text elements, add a negative sign to the x value. For the rects, set the x value as x -> -x - width.

Define a viewBox such that the lowest x value is still inside, or whatever is appropriate.

<svg style="border: 1px black solid;" height="100%" width="100%" viewBox="-500 0 500 100">
  <g>
    <g>
      <rect fill="#F0BC40" width="70" height="12" x="-70" y="30"></rect>
      <text fill="black" font-size="10px" text-anchor="middle" x="-35" y="29">7</text>
    </g>
    <g>
      <rect fill="orange" width="50" height="12" x="-122" y="30"></rect>
      <text fill="black" font-size="10px" text-anchor="middle" x="-97" y="29">5</text>
    </g>
    <g>
      <rect fill="orange" width="40" height="12" x="-164" y="30"></rect>
      <text fill="black" font-size="10px" text-anchor="middle" x="-144" y="29">4</text>
    </g>
    <g>
      <rect fill="red" width="50" height="12" x="-216" y="30"></rect>
      <text fill="black" font-size="10px" text-anchor="middle" x="-191" y="29">5</text>
    </g>
    <rect fill="#52575E" width="2" height="16" x="-72" y="28"></rect>
    <rect fill="#52575E" width="2" height="16" x="-124" y="28"></rect>
    <rect fill="#52575E" width="2" height="16" x="-166" y="28"></rect>
  </g>
</svg>

This will scale the text and the bars; if you need to avoid that, there is a trick. You can surround the content with two <svg> elements and use the inner one to move everything 100% to the right. overflow="visible" (or style="overflow:visible") makes sure the content is visible although it is formally outside the viewport of the inner <svg>.

<svg style="border: 1px black solid;" height="100%" width="100%">
  <svg x="100%" overflow="visible">
    <g>
      <g>
        <rect fill="#F0BC40" width="70" height="12" x="-70" y="30"></rect>
        <text fill="black" font-size="10px" text-anchor="middle" x="-35" y="29">7</text>
      </g>
      <g>
        <rect fill="orange" width="50" height="12" x="-122" y="30"></rect>
        <text fill="black" font-size="10px" text-anchor="middle" x="-97" y="29">5</text>
      </g>
      <g>
        <rect fill="orange" width="40" height="12" x="-164" y="30"></rect>
        <text fill="black" font-size="10px" text-anchor="middle" x="-144" y="29">4</text>
      </g>
      <g>
        <rect fill="red" width="50" height="12" x="-216" y="30"></rect>
        <text fill="black" font-size="10px" text-anchor="middle" x="-191" y="29">5</text>
      </g>
      <rect fill="#52575E" width="2" height="16" x="-72" y="28"></rect>
      <rect fill="#52575E" width="2" height="16" x="-124" y="28"></rect>
      <rect fill="#52575E" width="2" height="16" x="-166" y="28"></rect>
    </g>
  </svg>
</svg>
ccprog
  • 20,308
  • 4
  • 27
  • 44
1

As you said, scale "almost works". You can use scale again to unflip the text. Use nested transforms to get the flipping style working correctly with horizontal text placement. If you want to switch back to the unflipped version just change the -1 in the scale to a 1 (or get rid of the transform in the flipping style).

<head>
  <style TYPE="text/css">
  <!--
  .flipped {
    transform: scale(-1,1);
  }
  -->
  </style> 
</head>
<svg class=flipped style="border: 1px black solid;" height="100%" width="100%">
  <g>
    <g>
      <rect fill="#F0BC40" width="70" height="12" x="0" y="30"></rect>
      <g transform="translate(35,29)">
        <g class=flipped >
          <text fill="black" font-size="10px" text-anchor="middle" >7</text>
        </g>
      </g>
    </g>
    <g>
      <rect fill="orange" width="50" height="12" x="72" y="30"></rect>
      <g transform="translate(97,29)">
        <g class=flipped >
          <text fill="black" font-size="10px" text-anchor="middle" >5</text>
        </g>
      </g>
    </g>
    <g>
      <rect fill="orange" width="40" height="12" x="124" y="30"></rect>
      <g transform="translate(144,29)">
        <g class=flipped >
          <text fill="black" font-size="10px" text-anchor="middle" >4</text>
        </g>
      </g>
    </g>
    <g>
      <rect fill="red" width="50" height="12" x="166" y="30"></rect>
      <g transform="translate(191,29)">
        <g class=flipped >
          <text fill="black" font-size="10px" text-anchor="middle" >5</text>
        </g>
      </g>
    </g>
    <rect fill="#52575E" width="2" height="16" x="70" y="28"></rect>
    <rect fill="#52575E" width="2" height="16" x="122" y="28"></rect>
    <rect fill="#52575E" width="2" height="16" x="164" y="28"></rect>
  </g>
</svg>
Dave Compton
  • 1,421
  • 1
  • 11
  • 18
  • Your answer seems pretty good, but I just can't figure out why the need of the `translate` and how you calculate which values will be needed? Is there a way I can calculate that in my code without relying on DOM inspection? – Sasha Fonseca Sep 30 '18 at 10:24
  • The values used for translation are the same values that you had originally used for text positioning. So you can calculate them in exactly the same way - in this case, above the middle of the appropriate rectangle. What is happening is that the untranslated text first gets flipped at x=0 ( which results in mirror image text), then translated to sit above the appropriate rectangular bar. :Lastly, the whole thing gets flipped again which puts the bars (and text) on the right side of the svg image ( which is what you wanted in the first place). The twice-flipped text appears normally. – Dave Compton Sep 30 '18 at 12:48
  • One reason this worked out nicely is that the text is "middle" anchored which means that the text-flipping transform will flip around the *middle* of the text. Left becomes right, right becomes left, middle stays middle and the whole thing is flipped – Dave Compton Sep 30 '18 at 13:37
  • Also note that the values used for text placement are no longer in the text specification. If they had been, the text would have been positioned *before* being mirrored and the flipping would have made positive x values into negative values and the text would have flipped out of the image – Dave Compton Sep 30 '18 at 14:03
  • Thanks for your input Dave. I started by experimenting with @ccprog answer and it worked, but yours looks valid too! – Sasha Fonseca Oct 01 '18 at 13:11