Simple scalable SVG graph with cartesian coordinate system
Asked Answered
J

1

7

I am totally new to SVG, so please bear with me. I read a lot of articles on the subject, and everyone is pointing to solutions like d3.js, which is, in my opinion, way to complex for the simple task that I have.

I need to make a graph with a Cartesian coordinate system, where (0,0) is at the lower left corner. It needs to have width, height and data within expressed in percentages, so everything scales with the page.

So, here is my code (to make things simple, only part of the graph is there):

<style>
 .grid {stroke: white; stroke-width: 1; stroke-dasharray: 1 2}
 .label{font-family: courier new; fill: white; font-size: 14px}
 .data {stroke: white; stroke-width: 1}
</style>

<svg width="100%" height="100%">
 <g class="x grid">
  <line x1="0%"   x2="0%"   y1="80%" y2="100%"></line>
  <line x1="10%"  x2="10%"  y1="80%" y2="100%"></line>
  <line x1="20%"  x2="20%"  y1="80%" y2="100%"></line>
 </g>
 <g class="y grid">
  <line x1="0%" x2="20%" y1="80%"  y2="80%" ></line>
  <line x1="0%" x2="20%" y1="90%"  y2="90%" ></line>
  <line x1="0%" x2="20%" y1="100%" y2="100%"></line>
 </g>
 <g class="x label">
  <text x="10%"  y="100%"> 1 minute </text>
  <text x="20%"  y="100%"> 2 minutes</text>
 </g>
 <g class="y label">
  <text x="0%" y="80%"> 20% </text>
  <text x="0%" y="90%"> 10% </text>
 </g>
 <g class="data">
  <line x1="0%"  x2="10%"  y1="85%"  y2="92%"  ></line>
  <line x1="10%" x2="20%"  y1="92%"  y2="88%"  ></line>
 </g>
</svg>

I wanted to use polygon and path for the data, so I can fill the area below the curve, but it doesn't like the percentages as values. Someone suggested using viewbox to translate percentages to pixels, and then use pixels, but that messes up my grid. I would also like to have (0,0) at the lower left corner, so that my CGI doesn't have to do math on all the points that it needs to display. I tried transform="translate(0,100) scale(1,-1)" but that doesn't work with percentages. I also tried transform="rotate(270)" but when you reduce window width, graph height is reduced...

So, can someone kick-start me here, and help me set up a fluid, resizable graph with the origin in the lower left corner and colored area below the curve?

Jesuitism answered 26/6, 2014 at 23:21 Comment(0)
B
12

You'll need to use a viewBox because, as you discovered, the transform components don't take percentages. With a viewBox, you will still be able to use percentages for your coordinates. However you will need to pick a viewBox that has an aspect ratio similar to your final graph. Otherwise objects on your page may get squashed or stretched undesirably.

<svg width="100%" height="100%" viewBox="0 0 500 500">
  <g id="cartesian" transform="translate(0,500) scale(1,-1)">
    <g class="data">
      <line x1="0%"  y1="75%" x2="50%"  y2="40%" ></line>
      <line x1="50%" y1="40%" x2="100%" y2="60%"></line>
    </g>
  </g>
</svg>

Unfortunately flipping the coordinate system has side effects. If flips all objects including text, which you can see if we add some:

<svg width="100%" height="100%" viewBox="0 0 500 500">
  <g id="cartesian" transform="translate(0,500) scale(1,-1)">
    <g class="data">
      <line x1="0%"  y1="75%" x2="50%"  y2="40%" ></line>
      <line x1="50%" y1="40%" x2="100%" y2="60%"></line>
    </g>
    <g class="y label">
      <text x="0%" y="50%"> 10% </text>
      <text x="0%" y="90%"> 20% </text>
    </g>
  </g>
</svg>

Demo here

So you need to solve this by flipping the text back up the right way again.

<svg width="100%" height="100%" viewBox="0 0 500 500">
  <g id="cartesian" transform="translate(0,500) scale(1,-1)">
    <g class="data">
      <line x1="0%"  y1="75%" x2="50%"  y2="40%" ></line>
      <line x1="50%" y1="40%" x2="100%" y2="60%"></line>
    </g>
    <g class="y label">
      <text x="0%" y="50%" font-size="16"
            transform="translate(0,500) scale(1,-1)"> 10% </text>
      <text x="0%" y="90%" font-size="16"
            transform="translate(0,900) scale(1,-1)"> 20% </text>
    </g>
  </g>
</svg>

Unfortunately, as you can see, this messes with our ability to cleanly position the labels with percent coords. If we want to use percent coords on the <text> elements, we have to adjust the transform for each label.

Probably the best solution to this problem is to put all your labels in a <defs> and reference them with <use>. That way we can flip them up the right way and position them with percent coords.

<svg width="100%" height="100%" viewBox="0 0 500 500">
  <defs>
    <text id="label1" font-size="16" transform="scale(1,-1)"> 10% </text>
    <text id="label2" font-size="16" transform="scale(1,-1)"> 20% </text>
  </defs>
  <g id="cartesian" transform="translate(0,500) scale(1,-1)">
    <g class="data">
      <line x1="0%"  y1="75%" x2="50%"  y2="40%" ></line>
      <line x1="50%" y1="40%" x2="100%" y2="60%"></line>
    </g>
    <g class="y label">
        <use xlink:href="#label1" x="0%" y="50%"/>
        <use xlink:href="#label2" x="0%" y="90%"/>
    </g>
  </g>
</svg>

Demo here

Behling answered 27/6, 2014 at 6:27 Comment(4)
Thank you, your answer was very helpful. I figured out that, if grid and labels are static, they do not need to be within the Cartesian g, it makes code simpler and cleaner. But there is one more thing that I would like to fix but I don't know how. I don't need to maintain aspect ratio. I want the graph to fill the page (or parent div) and be flexible. Is that possible to achieve with viewport?Jesuitism
Yes. Use <svg width="100%" height="100%" preserveAspectRatio="none">.Behling
Almost there (i was trying to do it by myself but it seems I need one last peace of the puzzle) - is there any way in which I can make labels preserve the ratio?Jesuitism
Unfortunately no. You will need to not let the parent container aspect ratio get too different from the SVG aspect ratio.Behling

© 2022 - 2024 — McMap. All rights reserved.