3

The SVG example currently visually looks perfect except I want to achieve the same result without padding.

The padding's purpose is to force the red/blue lines 40px to the right to make space for the axis whilst also stopping it from overflowing on the right side of the graph.

I'm trying to remove the padding, but then I need to find a new way to shift JUST the "lines" 40px to the right without using x="40px" width="calc(100% - 40px)" because this syntax also isn't compatible with Sharp, or older browsers.

Is there any way to remove the padding and shift the lines 40px right whilst also constraining it within the box model of the SVG?

(final note: I don't want to use 'responsive' units in any logic that moves the lines)

Line graph showing padding

JSFiddle of line graph: https://jsfiddle.net/Lefsqo3j/14/

<div style="height:148px;width:300px;overflow:hidden;resize:both">
    <svg overflow="visible" style="padding:24px 0px 24px 40px" height="100%" width="100%">
            <svg role="img" viewBox="0 0 300 100" preserveAspectRatio="none">
                <svg viewBox="0 0 300 100" preserveAspectRatio="none">
                    <line y1="0" y2="100" x1="0" x2="0" stroke-width="0.2" stroke="black"></line>
                    <line y1="0" y2="100" x1="75" x2="75" stroke-width="0.2" stroke="black"></line>
                    <line y1="0" y2="100" x1="150" x2="150" stroke-width="0.2" stroke="black"></line>
                    <line y1="0" y2="100" x1="225" x2="225" stroke-width="0.2" stroke="black"></line>
                    <line y1="0" y2="100" x1="300" x2="300" stroke-width="0.2" stroke="black"></line>
                    <line x1="0" x2="300" y1="0" y2="0" stroke-width="0.2" stroke="black"></line>
                    <line x1="0" x2="300" y1="25" y2="25" stroke-width="0.2" stroke="black"></line>
                    <line x1="0" x2="300" y1="50" y2="50" stroke-width="0.2" stroke="black"></line>
                    <line x1="0" x2="300" y1="75" y2="75" stroke-width="0.2" stroke="black"></line>
                    <line x1="0" x2="300" y1="100" y2="100" stroke-width="0.2" stroke="black"></line>
                </svg>
            </svg>
            <svg x="100%" overflow="visible">
                <text text-anchor="end" dy="-8px">
                    <tspan fill="red"> ⬤</tspan>
                    Line A<tspan fill="blue"> ⬤</tspan>
                    Line B
                </text>
            </svg>
            <svg viewBox="0 0 300 100" preserveAspectRatio="none">
                <path
                    d="M 0 75 M 0 75  L 75 50 L 150 15 L 225 67 L 300 67 L 300 67"
                    stroke-width="3"
                    stroke="red"
                    fill="transparent"
                    vector-effect="non-scaling-stroke"
                ></path>
                <path
                    d="M 0 92 M 0 92  L 75 72 L 150 80 L 225 50 L 300 50 L 300 50"
                    stroke-width="3"
                    stroke="blue"
                    fill="transparent"
                ></path>
            </svg>
            <svg overflow="visible">
                <text x="0%" text-anchor="end" y="0%">
                    40
                </text>
                <text x="0%" text-anchor="end" y="25%">
                    30
                </text>
                <text x="0%" text-anchor="end" y="50%">
                    20
                </text>
                <text x="0%" text-anchor="end" y="75%">
                    10
                </text>
                <text x="0%" text-anchor="end" y="100%">
                    0
                </text>
                <g style="transform: translateX(0%);">
                    <text y="100%" dy="16px">
                        5
                    </text>
                </g>
                <g style="transform: translateX(25%);">
                    <text y="100%" dy="16px">
                        10
                    </text>
                </g>
                <g style="transform: translateX(50%); ">
                    <text y="100%" dy="16px">
                        15
                    </text>
                </g>
                <g style="transform: translateX(75%); ">
                    <text y="100%" dy="16px">
                        20
                    </text>
                </g>
                <g text-anchor="end" style="transform: translateX(100%);">
                    <text y="100%" dy="16px">
                        25
                    </text>
                </g>
            </svg>
        </svg>
</div>
Shanon Jackson
  • 5,873
  • 1
  • 19
  • 39
Prism
  • 31
  • 2
  • Is it possible to wrap all the contents of the parent `svg` in `g`? – 7-zete-7 Feb 04 '22 at 20:39
  • Yes, any structural changes can be made to the HTML structure aslong as it conforms to the SVG specification (I.E can't wrap the very top parent graph in a div) – Shanon Jackson Feb 04 '22 at 21:18

1 Answers1

1

I've modified the SVG and brought the content inside the content box. Dimensions scale uniformly maintaining aspect ratio.

<div style="height:148px;
    width:300px;
    overflow:hidden;
    resize:both; 
    background-color: wheat;
    ">

  <svg class="chart" overflow="visible">
      <style>
        .chart {
          width: 100%;
          height: 100%;
          font-size: 14px;
          padding-bottom: 24px;
        }

        /* background color for entire chart */
        .chart-background {
          fill: rgb(230, 249, 255);
        }

        /* styling for only the graph part */
        .graph {
          background-color: transparent;
        }

        /* grid lines style  */
        .chart .grid>line {
          stroke-width: 0.5;
          stroke: green;
        }

        .legend {
          background-color: wheat;
        }

        .labels {
          background-color: transparent !important;
        }

        .back {
          background-color: wheat;
        }

      </style>
      <rect class="chart-background" height="100%" width="100%" />
      <svg viewBox="0 0 340 140" x="40" y="24" preserveAspectRatio="none">
        <g class="grid">

          <line y1="0" y2="100" x1="0" x2="0"></line>
          <line y1="0" y2="100" x1="75" x2="75"></line>
          <line y1="0" y2="100" x1="150" x2="150"></line>
          <line y1="0" y2="100" x1="225" x2="225"></line>
          <line y1="0" y2="100" x1="300" x2="300"></line>

          <line x1="0" x2="300" y1="0" y2="0"></line>
          <line x1="0" x2="300" y1="25" y2="25"></line>
          <line x1="0" x2="300" y1="50" y2="50"></line>
          <line x1="0" x2="300" y1="75" y2="75"></line>
          <line x1="0" x2="300" y1="100" y2="100"></line>
        </g>
      </svg>

  <svg>
        <g class="legend">
          <text x="100%" text-anchor="end" dy="16">
            <tspan fill="red"> ⬤</tspan>Line A<tspan fill="blue"> ⬤</tspan>Line B
          </text>
        </g>
      </svg>

  <svg overflow="visible" x="40" y="24" height="70%" width="88%" style="margin-bottom: 24px;">
        <g class="labels" style="font-size: 12px;">
          <text x="0%" text-anchor="end" y="0%">40</text>
          <text x="0%" text-anchor="end" y="25%">30</text>
          <text x="0%" text-anchor="end" y="50%">20</text>
          <text x="0%" text-anchor="end" y="75%">10</text>
          <text x="0%" text-anchor="end" y="100%">0</text>

          <text y="100%" dy="16px">5</text>
          <text y="100%" dy="16px" x="22%">10</text>
          <text y="100%" dy="16px" x="48%">15</text>
          <text y="100%" dy="16px" x="74%">20</text>
          <text y="100%" dy="16px" dx="-20px" x="100%">25</text>
        </g>
      </svg>

  <svg viewBox="0 0 340 140" x="40" y="24" preserveAspectRatio="none">
        <g class="plot">
          <path d="M 0 75 M 0 75  L 75 50 L 150 15 L 225 67 L 300 67 L 300 67" stroke-width="3" stroke="red"
            fill="transparent" vector-effect="non-scaling-stroke"></path>
          <path d="M 0 92 M 0 92  L 75 72 L 150 80 L 225 50 L 300 50 L 300 50" stroke-width="3" stroke="blue"
            fill="transparent"></path>
        </g>
      </svg>
  </svg>
</div>

Sharpjs:

const svgBuffer = Buffer.from(svgImage);

const image = await sharp(svgBuffer, {})
   .resize({ width: 500, fit: sharp.fit.inside, position: 'centre' })
   .toFile('e:/svg-image.png');

enter image description here


Old answer
We can wrap the original svg content in a new <svg> tag. And with custom styles the original svg can be positioned such that overflow will be visible:

const sharp = require('sharp');

async function svgToImage(svgImage) {
 try {
  const wrapped = `<svg id='wrapper' width="600" height="400">
    <style>
      #wrapper > svg{
       transform: scale(.9) translate(25px, 25px) ;
      }
    </style>
    ${svgImage}
  </svg>`;

  const svgBuffer = Buffer.from(wrapped);
  //const image = await sharp(svgBuffer, {}).resize(500).toFile('e:/svg-image.png');

  const image = await sharp({
   create: {
    width: 600,
    height: 400,
    channels: 4,
    background: { r: 220, g: 255, b: 220, alpha: 1 },
   },
  })
   .composite([{ input: svgBuffer, top: 0, left: 0 }])
   .png()
   .toFile('e:/svg-image.png');
 } catch (error) {
  console.log(error);
 }
}

const svgImage = `
  <svg overflow="visible" style="padding:24px 0px 24px 40px">
  <svg role="img" viewBox="0 0 300 100" preserveAspectRatio="none">
      <svg viewBox="0 0 300 100" preserveAspectRatio="none">
          <line y1="0" y2="100" x1="0" x2="0" stroke-width="0.2" stroke="black"></line>
          <line y1="0" y2="100" x1="75" x2="75" stroke-width="0.2" stroke="black"></line>
          <line y1="0" y2="100" x1="150" x2="150" stroke-width="0.2" stroke="black"></line>
          <line y1="0" y2="100" x1="225" x2="225" stroke-width="0.2" stroke="black"></line>
          <line y1="0" y2="100" x1="300" x2="300" stroke-width="0.2" stroke="black"></line>
          <line x1="0" x2="300" y1="0" y2="0" stroke-width="0.2" stroke="black"></line>
          <line x1="0" x2="300" y1="25" y2="25" stroke-width="0.2" stroke="black"></line>
          <line x1="0" x2="300" y1="50" y2="50" stroke-width="0.2" stroke="black"></line>
          <line x1="0" x2="300" y1="75" y2="75" stroke-width="0.2" stroke="black"></line>
          <line x1="0" x2="300" y1="100" y2="100" stroke-width="0.2" stroke="black"></line>
      </svg>
  </svg>
  <svg x="100%" overflow="visible">
      <text text-anchor="end" dy="-8px">
          <tspan fill="red"> ⬤</tspan>
          Line A<tspan fill="blue"> ⬤</tspan>
          Line B
      </text>
  </svg>
  <svg viewBox="0 0 300 100" preserveAspectRatio="none">
      <path
          d="M 0 75 M 0 75  L 75 50 L 150 15 L 225 67 L 300 67 L 300 67"
          stroke-width="3"
          stroke="red"
          fill="transparent"
          vector-effect="non-scaling-stroke"
      ></path>
      <path
          d="M 0 92 M 0 92  L 75 72 L 150 80 L 225 50 L 300 50 L 300 50"
          stroke-width="3"
          stroke="blue"
          fill="transparent"
      ></path>
  </svg>
  <svg overflow="visible">
      <text x="0%" text-anchor="end" y="0%">40</text>
      <text x="0%" text-anchor="end" y="25%">30</text>
      <text x="0%" text-anchor="end" y="50%">20</text>
      <text x="0%" text-anchor="end" y="75%">10</text>
      <text x="0%" text-anchor="end" y="100%">0</text>
      
      <text y="100%" dy="16px">5</text>
      <text y="100%" dy="16px" x="25%">10</text>
      <text y="100%" dy="16px" x="50%">15</text>
      <text y="100%" dy="16px" x="75%">20</text>
      <text y="100%" dy="16px" x="100%">25</text>
  </svg>
</svg>`;

svgToImage(svgImage);

Output:
enter image description here



On x axis the labels didn't behave because of bug and bug related to transform. So I had to modify the x label tags.

the Hutt
  • 16,980
  • 2
  • 14
  • 44
  • After reviewing everything here, this isn't quite right sorry. "scale(0.9)" is a responsive unit. These graphs need to be perfectly responsive in where there's always 40px of space allocated on the left, no more no less. Also I really want to avoid any kind of overflow because these graphs are also rendered into webpages and I don't want to breach the boundaries of the box model. – Shanon Jackson Feb 06 '22 at 00:15
  • Are you generating these graphs(svgs) yourself? I thought they were third party and you had no control. You need to remove `dy="-8px"` negative position on legend and `text-anchor="end"` from y axis. Plot the components so that they don't go over boundaries. Need more information on where are these graphs coming from and how they are being generated. – the Hutt Feb 06 '22 at 03:02
  • 1
    The graph is our own inhouse library so we have full control to change any and all HTML – Shanon Jackson Feb 06 '22 at 07:06
  • 1
    They why not build the Svg without overflows in the first place? – the Hutt Feb 06 '22 at 07:07
  • Don't quite understand what you mean, but it currently DOESN"T overflow and looks visually perfect we just want to achieve the same result without using padding. I.E The padding forces the "lines (red/blue)" to the right 40px which is what we want, we just don't want to use padding to achieve it – Shanon Jackson Feb 06 '22 at 07:08
  • Ohh I get it now. I've changed the SVG(see the updated answer). Now it looks ok in HTML as well as Sharpjs. Let me know if it is ok. – the Hutt Feb 06 '22 at 12:50
  • Sorry its so close; The underlying responsivity has changed which wasn't mentioned in the original post. The reason we're using SVG is to be both horizontally and vertically responsive. To convey this I've added "resize:both" to a wrapper around the original SVG the wrapper here is irrelevant to the solution however is used to convey how the current graph responds to different height/width container if you drag the resize control around. You'll notice that when resized horizontally it fills all the horizontal space. In the solution this behavior no longer works. – Shanon Jackson Feb 06 '22 at 20:51
  • I've made it resizable(see updated answer). Not able to use `calc()` is a huge drawback. The bottom and right spaces can't be perfectly covered. – the Hutt Feb 07 '22 at 09:16