Search code examples
htmlcsssvgstrokegauge

How to make border of stroke SVG?


I am making gauge component using SVG. Two strokes are used to display two states at once. My current result is like this. enter image description here

enter image description here

And want to make like this. To block the end of the hatched part. enter image description here I don't know how to close this end... Can <filter> and :fill be used together?

 .circle-front {
      stroke-dasharray: 120;
      stroke: #333;
      stroke-width: 5.5px;
      transform: translate(2px, 5px) rotate(148deg);
      transform-origin: center;
    }

    .circle-back {
      stroke-dasharray: 120;
      stroke-dashoffset: 39;
      stroke: rgba(255, 255, 255, 0.2);
      transform: translate(2px, 5px) rotate(148deg);
      stroke-width: 5;
      transform-origin: center;   
    }

    .circle-oblique {
      stroke-dasharray: 120;
      /* stroke: #333; */
      stroke-linejoin: round;

      stroke-width: 4.5px;
      transform: translate(2px, 5px) rotate(148deg);
      transform-origin: center;
    }
<!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <svg
          width="110"
          height="110"
          viewBox="0 0 60 54"
          fill="none"
          xmlns="http://www.w3.org/2000/svg"
        >
          <g filter="url(#filter0_d_711_531)">
            <circle cx="29.7727" cy="30.2273" r="19" className="circle-back" />
          </g>
          <circle
            cx="29.7727"
            cy="30.2273"
            r="19"
            stroke="url(#vertical-stripe-2)"
            className="circle-oblique"
            strokeDashoffset="90"
          />
          <defs>
            {" "}
            <pattern
              id="vertical-stripe-2"
              patternUnits="userSpaceOnUse"
              width="2"
              height="2"
            >
              {" "}
              <image
                xlinkHref="data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxMCcgaGVpZ2h0PScxMCc+CiAgPHJlY3Qgd2lkdGg9JzEwJyBoZWlnaHQ9JzEwJyBmaWxsPSd3aGl0ZScgLz4KICA8cmVjdCB4PScwJyB5PScwJyB3aWR0aD0nMicgaGVpZ2h0PScxMCcgZmlsbD0nYmxhY2snIC8+Cjwvc3ZnPg=="
                x="0"
                y="0"
                width="2"
                height="2"
              >
                {" "}
              </image>{" "}
            </pattern>{" "}
          </defs>
          <circle
            cx="29.7727"
            cy="30.2273"
            r="19"
            className="circle-front"
            strokeDashoffset="43"
          />
          <defs>
            <filter id="filter0_d_711_531">
              <feFlood floodOpacity="0" result="BackgroundImageFix" />
              <feColorMatrix
                in="SourceAlpha"
                type="matrix"
                values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 7 0"
              />
              <feOffset />
              <feGaussianBlur stdDeviation="0.3" />
              <feComposite in2="hardAlpha" operator="out" />
              <feColorMatrix
                type="matrix"
                values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 0"
              />
            </filter>
          </defs>
        </svg>
    </body>
    </html>


Solution

  • I don't like the use of filters if they are not really needed for pixel-oriented operations: they are hard to understand and performance-heavy. All they seem to do in your case is draw a border, which is a vector operation. Here is a solution that draws paths.

    For the same reason, this uses vector-based <pattern> content instead of an image.

    (Your code example is neither executable nor self-explaining, so I am ignoring it.) From the example pictures you provided it seems the border is to surround the total area of the gauge, no matter what the displayed values are. That means it can be drawn once without any reference to dynamic data. Then the lines dividing the areas representing your values are just short perpendicular lines bisecting the gauge area. To get them dynamically in the correct position is a matter of rotating them. The areas themselves are drawn separately, below the borders.

    I have provided the dynamic data as CSS variables here, but it should be easy to insert them via some JS scripting, just replace the CSS properties that reference them with an appropriate way to insert values, probably in presentation attributes.

    svg {
      height: 100vh;
      --val1: 0.4;
      --val2: 0.6;
    }
    symbol {
      overflow: visible;
    }
    .area {
      fill:none;
      stroke-width: 15;
    }
    .stroke {
      fill:none;
      stroke: #333;
      stroke-width: 2;
      stroke-linejoin: round;
    }
    #area-1 {
      stroke: #333;
      stroke-dasharray: calc(var(--val1) * 100px) 100px;
    }
    #limit-1 {
      transform: rotate(calc(45deg + var(--val1) * 270deg));
    }
    #area-2 {
      stroke: url(#hatch);
      stroke-dasharray: calc(var(--val2) * 100px) 100px;
    }
    #limit-2 {
      transform: rotate(calc(45deg + var(--val2) * 270deg));
    }
    <svg viewBox="-60 -60 120 120">
      <symbol id="limit">
        <path d="M 0,41 V 54" />
      </symbol>
      <pattern id="hatch" patternUnits="userSpaceOnUse" width="16" height="8">
        <path class="stroke" d="M -2,-6 18,4 M -2,2 18,12 M -2,10 18,20" />
      </pattern>
      <!-- data areas in reverse order -->
      <path id="area-2" class="area" d="M -33.588,33.587 A 47.5,47.5 0 1 1 33.588,33.588" pathLength="100" />
      <path id="area-1" class="area" d="M -33.588,33.587 A 47.5,47.5 0 1 1 33.588,33.588" pathLength="100" />
      <g class="stroke">
        <!-- static outer border -->
        <path d="M -38.184,38.184 A 54,54 0 1 1 38.184,38.184 L 28.991,28.991 A 41,41 0 1 0 -28.991,28.991 Z" />
        <!-- bisecting data lines, cited from a template -->
        <use id="limit-1" href="#limit" />
        <use id="limit-2" href="#limit" />
      </g>
    </svg>