Shifting SVG by fixed number of pixels

Viewed 153

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>

1 Answers

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.

Related