Solution 1 :

Maybe I didn’t understand fully but I think it is possible to do it like this:

Demo: https://codepen.io/Alexander9111/pen/eYNGQPz:

body {
  background-color: #e9ecef;
}

.chart {
  /* background: #eee; */
  /* background: white; */
  border: 1px solid black;
  padding: 3px;
}

.chart div {
  width: 0;
  transition: all 1s ease-out;
  -moz-transition: all 1s ease-out;
  -webkit-transition: all 1s ease-out;
}

.chart div {
  font: 10px sans-serif;
  /* background-color: steelblue; */
  background-color: #262262;
  text-align: right;
  padding: 3px;
  margin: 5px;
  color: white;
  /* box-shadow: 2px 2px 2px #666; */
}

.bar {
  /* fill: #262262; */
}

canvas {
  display: block;
  width: 100%;
  visibility: hidden;
}

svg {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
}

.graph {
  width: 100%;
  position: relative;
}
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css"
  integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">
<div id="container" class="container-fluid">
  <div id="jumbotron" class="jumbotron">
    <h3>SVG Chart Example</h3>
    <div class="row">
      <div class="col-lg-6 col-xl-4" style="padding: 10px 10px;">
        <div class="card">
          <h4 class="card-header">Horizontal Bars</h4>
          <div class="card-body">
            <div class="graph">
              <canvas width="600" height="400"></canvas>
              <svg viewBox="0 0 600 400" preserveAspectRatio="xMinYMin">
                <g class="chart_area" transform="translate(50,0)">
                  <g class="chart_data">
                    <rect class="bar" x="0.7142857142857143" y="8" fill="rgb(110, 64, 170)" fill-opacity="0.6"
                      width="107.14285714285714" height="54"></rect>
                    <rect class="bar" x="0.7142857142857143" y="68" fill="rgb(238, 67, 149)" fill-opacity="0.6"
                      width="357.14285714285717" height="54"></rect>
                    <rect class="bar" x="0.7142857142857143" y="128" fill="rgb(255, 140, 56)" fill-opacity="0.6"
                      width="214.28571428571428" height="54"></rect>
                    <rect class="bar" x="0.7142857142857143" y="188" fill="rgb(175, 240, 91)" fill-opacity="0.6"
                      width="500" height="54"></rect>
                    <rect class="bar" x="0.7142857142857143" y="248" fill="rgb(40, 234, 141)" fill-opacity="0.6"
                      width="285.7142857142857" height="54"></rect>
                    <rect class="bar" x="0.7142857142857143" y="308" fill="rgb(47, 150, 224)" fill-opacity="0.6"
                      width="339.2857142857143" height="54"></rect><text class="label" y="35" font-size="12"
                      text-anchor="left" alignment-baseline="middle" x="112.14285714285714">300</text><text
                      class="label" y="95" font-size="12" text-anchor="left" alignment-baseline="middle"
                      x="362.14285714285717">1000</text><text class="label" y="155" font-size="12" text-anchor="left"
                      alignment-baseline="middle" x="219.28571428571428">600</text><text class="label" y="215"
                      font-size="12" text-anchor="left" alignment-baseline="middle" x="505">1400</text><text
                      class="label" y="275" font-size="12" text-anchor="left" alignment-baseline="middle"
                      x="290.7142857142857">800</text><text class="label" y="335" font-size="12" text-anchor="left"
                      alignment-baseline="middle" x="344.2857142857143">950</text>
                  </g>
                  <g class="x axis" font-size="10" transform="translate(0,370)" fill="none" font-family="sans-serif"
                    text-anchor="middle">
                    <path class="domain" stroke="currentColor" d="M0.5,6V0.5H500.5V6"></path>
                    <g class="tick" opacity="1" transform="translate(0.5,0)">
                      <line stroke="currentColor" y2="6"></line><text fill="currentColor" y="9" dy="0.71em">0</text>
                    </g>
                    <g class="tick" opacity="1" transform="translate(71.92857142857143,0)">
                      <line stroke="currentColor" y2="6"></line><text fill="currentColor" y="9" dy="0.71em">200</text>
                    </g>
                    <g class="tick" opacity="1" transform="translate(143.35714285714286,0)">
                      <line stroke="currentColor" y2="6"></line><text fill="currentColor" y="9" dy="0.71em">400</text>
                    </g>
                    <g class="tick" opacity="1" transform="translate(214.78571428571428,0)">
                      <line stroke="currentColor" y2="6"></line><text fill="currentColor" y="9" dy="0.71em">600</text>
                    </g>
                    <g class="tick" opacity="1" transform="translate(286.2142857142857,0)">
                      <line stroke="currentColor" y2="6"></line><text fill="currentColor" y="9" dy="0.71em">800</text>
                    </g>
                    <g class="tick" opacity="1" transform="translate(357.64285714285717,0)">
                      <line stroke="currentColor" y2="6"></line><text fill="currentColor" y="9" dy="0.71em">1,000</text>
                    </g>
                    <g class="tick" opacity="1" transform="translate(429.07142857142856,0)">
                      <line stroke="currentColor" y2="6"></line><text fill="currentColor" y="9" dy="0.71em">1,200</text>
                    </g>
                    <g class="tick" opacity="1" transform="translate(500.5,0)">
                      <line stroke="currentColor" y2="6"></line><text fill="currentColor" y="9" dy="0.71em">1,400</text>
                    </g>
                  </g>
                  <g class="y axis" font-size="10" fill="none" font-family="sans-serif" text-anchor="end">
                    <path class="domain" stroke="currentColor" d="M-6,0.5H0.5V370.5H-6"></path>
                    <g class="tick" opacity="1" transform="translate(0,35.5)">
                      <line stroke="currentColor" x2="-6"></line><text fill="currentColor" x="-9" dy="0.32em">Jan</text>
                    </g>
                    <g class="tick" opacity="1" transform="translate(0,95.5)">
                      <line stroke="currentColor" x2="-6"></line><text fill="currentColor" x="-9" dy="0.32em">Feb</text>
                    </g>
                    <g class="tick" opacity="1" transform="translate(0,155.5)">
                      <line stroke="currentColor" x2="-6"></line><text fill="currentColor" x="-9" dy="0.32em">Mar</text>
                    </g>
                    <g class="tick" opacity="1" transform="translate(0,215.5)">
                      <line stroke="currentColor" x2="-6"></line><text fill="currentColor" x="-9" dy="0.32em">Apr</text>
                    </g>
                    <g class="tick" opacity="1" transform="translate(0,275.5)">
                      <line stroke="currentColor" x2="-6"></line><text fill="currentColor" x="-9" dy="0.32em">May</text>
                    </g>
                    <g class="tick" opacity="1" transform="translate(0,335.5)">
                      <line stroke="currentColor" x2="-6"></line><text fill="currentColor" x="-9" dy="0.32em">Jun</text>
                    </g>
                  </g>
                </g>
              </svg></div>
            </h-bars-chart>
            <h5 class="card-title">Special title treatment</h5>
            <p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
          </div>
        </div>
      </div>
    </div>
  </div>

Note that the important lines are:

CSS (see svg fills its parent element as width 100%):

svg {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
}

Then I have the svg using viewbox and it always fills its parent div.

So you could animate this parent element’s (here the div’s) size and it should follow etc. always filling width 100%

HTML simplified example:

<div class="card-body">
    <div class="graph">
        <canvas width="600" height="400"></canvas>
        <svg viewBox="0 0 600 400" preserveAspectRatio="xMinYMin">
    </div>
</div>

Note I use a blank canvas just because otherwise in IE it doesn’t work as expected.

CSS:

canvas {
  display: block;
  width: 100%;
  visibility: hidden;
}

Demo:

enter image description here

Update- 2nd iteration

After comments from the OP, I came up with a different example:

#holder {
  background: #bfbfbf;
  height: 98vh;
  width: 98vw;
}
svg {
  border: 2px solid blue;
  max-height: 98vh;
}
<div id="holder">
  <svg viewBox="0 0 600 400" preserveAspectRatio="xMinYMin">
    <circle r="100" cx="300" cy="200" />
    <circle r="10" cx="10" cy="10" />
    <circle r="10" cx="10" cy="390" />
    <circle r="10" cx="590" cy="10" />
    <circle r="10" cx="590" cy="390" />
    <rect x="0" y="0" width="600" height="400" stroke="black" stroke-width="2" fill="none" />
</div>

Demo here:

enter image description here

Note: the svg element’s border is blue and the rect element’s border is black, which is the set to max of the viewport, here you can see that the svg is sometimes wider than the viewport but the svg viewport is aligned/anchored top left. If you change the svg element’s preserve aspect ratio to this:

<svg viewBox="0 0 600 400" preserveAspectRatio="xMaxYMax">

Then you see the viewport aligned to the bottom right instead.

Demo also at https://codepen.io/Alexander9111/pen/rNVpWBp

UPDATE – 3rd iteration

#holder {
  background: #bfbfbf;
  height: 65vh;
  width: 65vw;
}
svg {
  border: 2px solid blue;
  max-height: 80%;
}
#buttonContainer {
    text-align: center;
}
<div id="holder">
  <svg viewBox="0 0 600 400" preserveAspectRatio="xMinYMin">
    <circle r="100" cx="300" cy="200" />
    <circle r="10" cx="10" cy="10" />
    <circle r="10" cx="10" cy="390" />
    <circle r="10" cx="590" cy="10" />
    <circle r="10" cx="590" cy="390" />
    <rect x="0" y="0" width="600" height="400" stroke="black" stroke-width="2" fill="none" />
    <!--  <foreignObject x="0" y="330" width="600" height="150">
      <div id="buttonContainer">
        <button style="height: 60px">88</button>
        <button style="height: 60px">888</button>
      </div>
    </foreignObject> -->
  <svg/> 
  <div id="buttonContainer">
    <button style="height: 10vh">88</button>
    <button style="height: 10vh">888</button>
  </div>
</div>

Demo and here(https://codepen.io/Alexander9111/pen/rNVpWBp):

enter image description here

Note here the div with id=”holder” can be any width and height.

Then the svg css is set to max 100% height of parent element:

svg {
  border: 2px solid blue;
  max-height: 100%;
}

Problem :

I’m making a simple HTML layout with an SVG displayed above a row of buttons:

<!DOCTYPE html>
<html>
	<head>
		<style type="text/css">
			#playgroundForTest
			{
				width: 500px;
				height: 260px;

				border-width: 3px;
				border-color: gray;
				border-style: solid;
			}

			#buttonContainer
			{
				display: flex;
				flex-direction: row;
				justify-content: center;
			}

			#mainContainer
			{
				display: flex;
				flex-direction: column;
				align-items: center;
			}

			svg
			{
				height: 200px;
			}
		</style>
	</head>
	<body>
		<div id="playgroundForTest">
			<div id="mainContainer">
				<svg viewBox="0 0 1000 1000">
					<circle cx="500" cy="500" r="450" fill="#22f" />
					<circle cx="50" cy="50" r="50" fill="#88f" />
					<circle cx="70" cy="930" r="70" fill="#bbf" />
					<circle cx="910" cy="910" r="90" fill="#44f" />
					<circle cx="900" cy="100" r="100" fill="#a0a0ff" />
				</svg>
				<div id="buttonContainer">
					<button style="height: 60px">88</button>
					<button style="height: 60px">888</button>
				</div>
			</div>
		</div>
	</body>
</html>

At any given moment, the gray box around it (“playgroundForTest”) has a fixed size with an arbitrary value. I want the SVG to stretch as that size changes, like in this animation:

<!DOCTYPE html>
<html>
	<head>
		<style type="text/css">
			@keyframes playgroundPulse
			{
				from
				{
					height: 260px;
				}

				to
				{
					height: 410px;
				}
			}

			@keyframes svgPulse
			{
				from
				{
					height: 200px;
				}

				to
				{
					height: 350px;
				}
			}

			#playgroundForTest
			{
				width: 500px;
				height: 260px;

				border-width: 3px;
				border-color: gray;
				border-style: solid;

				animation-duration: 1s;
				animation-name: playgroundPulse;
				animation-iteration-count: infinite;
				animation-direction: alternate;
			}

			#buttonContainer
			{
				display: flex;
				flex-direction: row;
				justify-content: center;
			}

			#mainContainer
			{
				display: flex;
				flex-direction: column;
				align-items: center;
			}

			svg
			{
				height: 200px;

				animation-duration: 1s;
				animation-name: svgPulse;
				animation-iteration-count: infinite;
				animation-direction: alternate;
			}
		</style>
	</head>
	<body>
		<div id="playgroundForTest">
			<div id="mainContainer">
				<svg viewBox="0 0 1000 1000">
					<circle cx="500" cy="500" r="450" fill="#22f" />
					<circle cx="50" cy="50" r="50" fill="#88f" />
					<circle cx="70" cy="930" r="70" fill="#bbf" />
					<circle cx="910" cy="910" r="90" fill="#44f" />
					<circle cx="900" cy="100" r="100" fill="#a0a0ff" />
				</svg>
				<div id="buttonContainer">
					<button style="height: 60px">88</button>
					<button style="height: 60px">888</button>
				</div>
			</div>
		</div>
	</body>
</html>

I also want it to neatly fit itself to width, as in this hacked-together animation:

<!DOCTYPE html>
<html>
	<head>
		<style type="text/css">
			@keyframes playgroundPulse
			{
				from
				{
					width: 250px;
				}

				to
				{
					width: 450px;
				}
			}

			@keyframes svgPulse
			{
				from
				{
					top: 140px;
					width: 250px;
				}

				to
				{
					top: 40px;
					width: 450px;
				}
			}

			@keyframes buttonPulse
			{
				from
				{
					top: 277px;
				}

				to
				{
					top: 77px;
				}
			}

			#playgroundForTest
			{
				width: 250px;
				height: 590px;

				border-width: 3px;
				border-color: gray;
				border-style: solid;

				animation-duration: 1s;
				animation-name: playgroundPulse;
				animation-iteration-count: infinite;
				animation-direction: alternate;
			}

			#buttonContainer
			{
				display: flex;
				flex-direction: row;
				justify-content: center;

				position: relative;

				animation-duration: 1s;
				animation-name: buttonPulse;
				animation-iteration-count: infinite;
				animation-direction: alternate;
			}

			svg
			{
				position: relative;
				width: 250px;

				animation-duration: 1s;
				animation-name: svgPulse;
				animation-iteration-count: infinite;
				animation-direction: alternate;
			}
		</style>
	</head>
	<body>
		<div id="playgroundForTest">
			<div id="mainContainer">
				<svg viewBox="0 0 1000 1000">
					<circle cx="500" cy="500" r="450" fill="#22f" />
					<circle cx="50" cy="50" r="50" fill="#88f" />
					<circle cx="70" cy="930" r="70" fill="#bbf" />
					<circle cx="910" cy="910" r="90" fill="#44f" />
					<circle cx="900" cy="100" r="100" fill="#a0a0ff" />
				</svg>
				<div id="buttonContainer">
					<button style="height: 60px">88</button>
					<button style="height: 60px">888</button>
				</div>
			</div>
		</div>
	</body>
</html>

Ideally I would also like to be able to explicitly and easily set the alignments of the SVG and the button row along the vertical axis, so as to be able to produce variants such as this:

<!DOCTYPE html>
<html>
	<head>
		<style type="text/css">
			@keyframes playgroundPulse
			{
				from
				{
					width: 250px;
				}

				to
				{
					width: 450px;
				}
			}

			@keyframes svgPulse
			{
				from
				{
					top: 0px;
					width: 250px;
				}

				to
				{
					top: 0px;
					width: 450px;
				}
			}

			#playgroundForTest
			{
				width: 250px;
				height: 590px;

				border-width: 3px;
				border-color: gray;
				border-style: solid;

				animation-duration: 1s;
				animation-name: playgroundPulse;
				animation-iteration-count: infinite;
				animation-direction: alternate;
			}

			#buttonContainer
			{
				display: flex;
				flex-direction: row;
				justify-content: center;
			}

			svg
			{
				position: relative;
				width: 250px;

				animation-duration: 1s;
				animation-name: svgPulse;
				animation-iteration-count: infinite;
				animation-direction: alternate;
			}
		</style>
	</head>
	<body>
		<div id="playgroundForTest">
			<div id="mainContainer">
				<svg viewBox="0 0 1000 1000">
					<circle cx="500" cy="500" r="450" fill="#22f" />
					<circle cx="50" cy="50" r="50" fill="#88f" />
					<circle cx="70" cy="930" r="70" fill="#bbf" />
					<circle cx="910" cy="910" r="90" fill="#44f" />
					<circle cx="900" cy="100" r="100" fill="#a0a0ff" />
				</svg>
				<div id="buttonContainer">
					<button style="height: 60px">88</button>
					<button style="height: 60px">888</button>
				</div>
			</div>
		</div>
	</body>
</html>

I want this all done “elegantly” – that is, without JavaScript, hardcoded distance values (with the possible exception of “100%”), goofy hacks involving padding-top or whatever, or anything else ugly. I assume this means I’d only be using flexbox and grid layouts. But I can’t seem to manage it.

Is there a way to do this? It seems like flexbox and/or grid should be powerful enough, but I keep failing to make it work… If it’s not possible then I’ll give up on some constraints and maybe make something ugly, but for now I’m asking how to do it nicely.

Comments

Comment posted by mjwach

In this example the SVG appears to be matching the overall viewing area’s width (sometimes), and never its height. I am asking for it to match the width OR the height, depending on which choice allows the full SVG to be shown. In the first two snippets in my question, the height-matching behavior is shown. Your example does not show that behavior for me.

Comment posted by codepen.io/Alexander9111/pen/rNVpWBp

See my updated answer and demo here:

Comment posted by mjwach

This achieves the basic effect, though it leaves out the row of buttons. But it isn’t a general solution. It appears to rely on using vh or vw units for both the containing div and the svg. This gives the container a dependency on the size of the overall viewing area. But I ask for the container’s size to be arbitrary – it could be px. If there were some kind of “ph” or “pw” unit that’d evaluate to 1% of the PARENT’s dimension, then maybe that could be used, though it’d still be unclear how to fit in a row of fixed-height buttons without leaving a gap or an overlap between them and the SVG.

Comment posted by mjwach

The core of your example seems to be the “max-height” setting. When asking I didn’t yet know about max-height; that attribute is definitely helpful in at least working around this problem. A workaround is probably good enough for my actual project. I am not making the most important webpage in history here. I will remain curious about a perfect answer though. (But maybe one doesn’t exist under current HTML/CSS/SVG standards.)

Comment posted by Alex L

See my updated answer and codepen. You can set the svg to max-height: 100% and this is a % of the parent element. This gives you the effect you are looking for I believe. The parent div can be set to any size and it should work. If my answer is helpful and solves your problem, please consider to up-vote and/or mark as correct.

By