Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simple scalable SVG graph with cartesian coordinate system

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?

like image 800
Ulrik Avatar asked Jun 26 '14 23:06

Ulrik


People also ask

How do you graph a Cartesian coordinate system?

How do you plot points? Given a point, (a, b), you plot the point in the Cartesian plane by finding the number a on the horizontal, or x-, axis; then you trace directly up or down from that spot, moving parallel to the vertical, or y-, axis, until you reach the height of the number b.

How do SVG coordinates work?

For all elements, SVG uses a coordinate system or grid system similar to the one used by canvas (and by a whole lot of other computer drawing routines). That is, the top left corner of the document is considered to be the point (0,0), or point of origin.


1 Answers

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

like image 192
Paul LeBeau Avatar answered Sep 30 '22 11:09

Paul LeBeau