Skip to content

Instantly share code, notes, and snippets.

@veltman
Last active February 8, 2021 12:27
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save veltman/5cd1ba0b3c623e7b5146 to your computer and use it in GitHub Desktop.
Save veltman/5cd1ba0b3c623e7b5146 to your computer and use it in GitHub Desktop.
Responsive SVG with sticky text

Keeping text size sticky while making an SVG responsive with viewBox.

  1. Set the SVG's viewBox to its original width and height, and its width to 100%, so it fills its container.
  2. On resize, find out the SVG's new width.
  3. Update the SVG's height to preserve the aspect ratio.
  4. Use transform to scale up the text by the inverse. For example, if the new width is 2/3 of the original, make the text 3/2 scale.

Instead of keeping the text size fixed, you could constrain it to some other range so that it shrinks but more slowly than the rest of the SVG. For example:

var textScaleFactor = d3.scale.linear()
  .domain([300,600]) // expected limits of SVG width
  .range([1.5,1]); // when SVG is 1/2 width, text will be 2/3 size

See also: Hannah Fairfield's connected scatterplot on gas prices

Display the source blob
Display the rendered blob
Raw
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="960" height="440" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs xmlns="http://www.w3.org/1999/xhtml"><style type="text/css"><![CDATA[
* { margin: 0px; padding: 0px; font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; font-size: 14px; line-height: normal; font-family: Helvetica, Arial, sans-serif; }
path, line { fill: none; stroke: rgb(0, 0, 0); }
.axis path, .axis line { shape-rendering: crispEdges; stroke: rgb(204, 204, 204); }
.axis path { display: none; }
.connection { stroke-width: 2px; }
circle { fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 1px; }
div.controls { padding-top: 0.5em; text-align: center; }
div.button { border: 1px solid rgb(204, 204, 204); color: rgb(51, 51, 51); padding: 0.5em 1em; line-height: 140%; vertical-align: middle; cursor: pointer; text-align: center; display: inline-block; background-color: rgb(255, 255, 255); }
g.year text { font-size: 12px; letter-spacing: 0.03em; }
div.button:hover, div.button:focus, div.button:active, div.button.active { border-color: rgb(173, 173, 173); background-color: rgb(235, 235, 235); }
div.button:active, div.button.active { color: rgb(0, 0, 0); outline: 0px; box-shadow: rgba(0, 0, 0, 0.121569) 0px 3px 5px inset; }
div.outer { width: 960px; margin: 0px auto; }]]></style></defs><g transform="translate(30,10)"><g class="x axis" transform="translate(0,390)"><g class="tick" transform="translate(50,0)" style="opacity: 1;"><line y2="-390" x2="0"/><text dy=".71em" y="3" x="0" style="text-anchor: middle;">3,000 mi.</text></g><g class="tick" transform="translate(150,0)" style="opacity: 1;"><line y2="-390" x2="0"/><text dy=".71em" y="3" x="0" style="text-anchor: middle;">4,000 mi.</text></g><g class="tick" transform="translate(250,0)" style="opacity: 1;"><line y2="-390" x2="0"/><text dy=".71em" y="3" x="0" style="text-anchor: middle;">5,000 mi.</text></g><g class="tick" transform="translate(350,0)" style="opacity: 1;"><line y2="-390" x2="0"/><text dy=".71em" y="3" x="0" style="text-anchor: middle;">6,000 mi.</text></g><g class="tick" transform="translate(450,0)" style="opacity: 1;"><line y2="-390" x2="0"/><text dy=".71em" y="3" x="0" style="text-anchor: middle;">7,000 mi.</text></g><g class="tick" transform="translate(550,0)" style="opacity: 1;"><line y2="-390" x2="0"/><text dy=".71em" y="3" x="0" style="text-anchor: middle;">8,000 mi.</text></g><g class="tick" transform="translate(650,0)" style="opacity: 1;"><line y2="-390" x2="0"/><text dy=".71em" y="3" x="0" style="text-anchor: middle;">9,000 mi.</text></g><g class="tick" transform="translate(750,0)" style="opacity: 1;"><line y2="-390" x2="0"/><text dy=".71em" y="3" x="0" style="text-anchor: middle;">10,000 mi.</text></g><path class="domain" d="M0,-390V0H800V-390"/></g><g class="y axis" transform="translate(800)"><g class="tick" transform="translate(0,340.59985980804487)" style="opacity: 1;"><line x2="-800" y2="0"/><text dy=".32em" x="3" y="0" style="text-anchor: start;">$1.50</text></g><g class="tick" transform="translate(0,282.18654516697217)" style="opacity: 1;"><line x2="-800" y2="0"/><text dy=".32em" x="3" y="0" style="text-anchor: start;">$2.00</text></g><g class="tick" transform="translate(0,223.77323052589952)" style="opacity: 1;"><line x2="-800" y2="0"/><text dy=".32em" x="3" y="0" style="text-anchor: start;">$2.50</text></g><g class="tick" transform="translate(0,165.3599158848269)" style="opacity: 1;"><line x2="-800" y2="0"/><text dy=".32em" x="3" y="0" style="text-anchor: start;">$3.00</text></g><g class="tick" transform="translate(0,106.94660124375426)" style="opacity: 1;"><line x2="-800" y2="0"/><text dy=".32em" x="3" y="0" style="text-anchor: start;">$3.50</text></g><g class="tick" transform="translate(0,48.5332866026816)" style="opacity: 1;"><line x2="-800" y2="0"/><text dy=".32em" x="3" y="0" style="text-anchor: start;">$4.00 / gal</text></g><path class="domain" d="M-800,0H0V390H-800"/></g><path class="connection" d="M34.513922119675314,204.45462202998314L50.9402611653637,208.33078611755096L67.08399641216701,226.54831230454008L75.97401987423386,230.99452164867034L89.87935588550464,219.7065304342088L94.70790136146134,217.7234351879803L114.99825994149069,215.57923742658434L121.71801848837158,211.86163894545203L125.03692927946317,210.77263017838115L130.05819058328643,224.95847508318414L143.90511461903301,226.99996602302454L147.82885545018758,225.29789697494328L151.44539963723196,230.98756792745652L161.03425630171552,236.5732649777433L175.51328961691644,241.11854889696636L191.03566216121507,244.66333928192498L206.92149326616288,241.9447782348138L221.05077729363694,242.71460153132753L235.12657933547825,240.9670322893521L256.1476671366143,248.05201976818125L273.8834613187826,253.62736840218494L291.19104340732326,261.40495463495057L317.66211929473775,267.3054287437443L350.1952747832223,277.0196219719028L369.6581144147736,274.1886572758459L348.79377104543994,217.43568210090623L364.73553484754376,224.40535379326076L393.1898296918748,217.44159679338148L416.1055349195541,216.49527853141183L443.9852899490393,239.31117222042488L429.44710897006485,179.8917968544597L424.17667123902936,106.08075145610125L427.795287823005,104.24108158146777L438.50016648218144,148.7587913319018L456.9480140583415,175.73179810799323L479.4687463632977,196.99350868796753L495.9642508804944,208.47154788836116L514.107095376725,280.941851234374L542.939220303481,282.8839055306713L578.6176904017163,290.50286309183105L599.4018346755829,279.45242518167555L609.0408727625352,258.3529295547595L608.5824653091158,273.01436813143744L626.0336814694534,281.2930670969812L633.4989516024918,291.3636205048179L645.9926437626203,296.7813601457542L659.8834391718744,297.193477635095L677.9257758861728,288.83605778737547L692.0256619435556,293.4108546468443L708.7212811595509,326.680647820475L720.1958034944989,313.1738619096943L729.7492125909005,264.8443037013908L737.2710520344224,276.78540799829113L749.0821631539071,294.34021617866836L752.6537261219949,269.6692067667234L768.4300012221772,234.33394963102916L768.2906849168227,184.79835955699804L766.7417034428242,154.40481165236508L762.1882889776424,135.79635405717823L734.1382383759629,89.80846572780284L720.0391720510675,206.30822132682374L714.957978671186,156.26382988223193L701.0793270837232,76.02525730428762L695.1451274662542,70.81659230593793L694.1851661711698,90.48417483218849"/><g class="year" transform="translate(34.513922119675314 204.45462202998314)"><circle r="5"/><text dy="-0.6em" dx="-0.6em" text-anchor="end">1949</text></g><g class="year" transform="translate(50.9402611653637 208.33078611755096)"><circle r="5"/></g><g class="year" transform="translate(67.08399641216701 226.54831230454008)"><circle r="5"/></g><g class="year" transform="translate(75.97401987423386 230.99452164867034)"><circle r="5"/></g><g class="year" transform="translate(89.87935588550464 219.7065304342088)"><circle r="5"/></g><g class="year" transform="translate(94.70790136146134 217.7234351879803)"><circle r="5"/></g><g class="year" transform="translate(114.99825994149069 215.57923742658434)"><circle r="5"/></g><g class="year" transform="translate(121.71801848837158 211.86163894545203)"><circle r="5"/></g><g class="year" transform="translate(125.03692927946317 210.77263017838115)"><circle r="5"/></g><g class="year" transform="translate(130.05819058328643 224.95847508318414)"><circle r="5"/></g><g class="year" transform="translate(143.90511461903301 226.99996602302454)"><circle r="5"/></g><g class="year" transform="translate(147.82885545018758 225.29789697494328)"><circle r="5"/></g><g class="year" transform="translate(151.44539963723196 230.98756792745652)"><circle r="5"/></g><g class="year" transform="translate(161.03425630171552 236.5732649777433)"><circle r="5"/></g><g class="year" transform="translate(175.51328961691644 241.11854889696636)"><circle r="5"/></g><g class="year" transform="translate(191.03566216121507 244.66333928192498)"><circle r="5"/></g><g class="year" transform="translate(206.92149326616288 241.9447782348138)"><circle r="5"/></g><g class="year" transform="translate(221.05077729363694 242.71460153132753)"><circle r="5"/></g><g class="year" transform="translate(235.12657933547825 240.9670322893521)"><circle r="5"/></g><g class="year" transform="translate(256.1476671366143 248.05201976818125)"><circle r="5"/></g><g class="year" transform="translate(273.8834613187826 253.62736840218494)"><circle r="5"/></g><g class="year" transform="translate(291.19104340732326 261.40495463495057)"><circle r="5"/></g><g class="year" transform="translate(317.66211929473775 267.3054287437443)"><circle r="5"/></g><g class="year" transform="translate(350.1952747832223 277.0196219719028)"><circle r="5"/></g><g class="year" transform="translate(369.6581144147736 274.1886572758459)"><circle r="5"/></g><g class="year" transform="translate(348.79377104543994 217.43568210090623)"><circle r="5"/><text dy="-0.6em" dx="-0.6em" text-anchor="end">1974</text></g><g class="year" transform="translate(364.73553484754376 224.40535379326076)"><circle r="5"/></g><g class="year" transform="translate(393.1898296918748 217.44159679338148)"><circle r="5"/></g><g class="year" transform="translate(416.1055349195541 216.49527853141183)"><circle r="5"/></g><g class="year" transform="translate(443.9852899490393 239.31117222042488)"><circle r="5"/></g><g class="year" transform="translate(429.44710897006485 179.8917968544597)"><circle r="5"/></g><g class="year" transform="translate(424.17667123902936 106.08075145610125)"><circle r="5"/><text dy="-0.6em" dx="-0.6em" text-anchor="end">1980</text></g><g class="year" transform="translate(427.795287823005 104.24108158146777)"><circle r="5"/></g><g class="year" transform="translate(438.50016648218144 148.7587913319018)"><circle r="5"/></g><g class="year" transform="translate(456.9480140583415 175.73179810799323)"><circle r="5"/></g><g class="year" transform="translate(479.4687463632977 196.99350868796753)"><circle r="5"/></g><g class="year" transform="translate(495.9642508804944 208.47154788836116)"><circle r="5"/></g><g class="year" transform="translate(514.107095376725 280.941851234374)"><circle r="5"/></g><g class="year" transform="translate(542.939220303481 282.8839055306713)"><circle r="5"/></g><g class="year" transform="translate(578.6176904017163 290.50286309183105)"><circle r="5"/></g><g class="year" transform="translate(599.4018346755829 279.45242518167555)"><circle r="5"/></g><g class="year" transform="translate(609.0408727625352 258.3529295547595)"><circle r="5"/><text dy="-0.6em" dx="-0.6em" text-anchor="end">1990</text></g><g class="year" transform="translate(608.5824653091158 273.01436813143744)"><circle r="5"/></g><g class="year" transform="translate(626.0336814694534 281.2930670969812)"><circle r="5"/></g><g class="year" transform="translate(633.4989516024918 291.3636205048179)"><circle r="5"/></g><g class="year" transform="translate(645.9926437626203 296.7813601457542)"><circle r="5"/></g><g class="year" transform="translate(659.8834391718744 297.193477635095)"><circle r="5"/></g><g class="year" transform="translate(677.9257758861728 288.83605778737547)"><circle r="5"/></g><g class="year" transform="translate(692.0256619435556 293.4108546468443)"><circle r="5"/></g><g class="year" transform="translate(708.7212811595509 326.680647820475)"><circle r="5"/></g><g class="year" transform="translate(720.1958034944989 313.1738619096943)"><circle r="5"/></g><g class="year" transform="translate(729.7492125909005 264.8443037013908)"><circle r="5"/><text dy="-0.6em" dx="-0.6em" text-anchor="end">2000</text></g><g class="year" transform="translate(737.2710520344224 276.78540799829113)"><circle r="5"/></g><g class="year" transform="translate(749.0821631539071 294.34021617866836)"><circle r="5"/></g><g class="year" transform="translate(752.6537261219949 269.6692067667234)"><circle r="5"/></g><g class="year" transform="translate(768.4300012221772 234.33394963102916)"><circle r="5"/></g><g class="year" transform="translate(768.2906849168227 184.79835955699804)"><circle r="5"/></g><g class="year" transform="translate(766.7417034428242 154.40481165236508)"><circle r="5"/></g><g class="year" transform="translate(762.1882889776424 135.79635405717823)"><circle r="5"/></g><g class="year" transform="translate(734.1382383759629 89.80846572780284)"><circle r="5"/><text dy="-0.6em" dx="0.45em" text-anchor="start">2008</text></g><g class="year" transform="translate(720.0391720510675 206.30822132682374)"><circle r="5"/></g><g class="year" transform="translate(714.957978671186 156.26382988223193)"><circle r="5"/></g><g class="year" transform="translate(701.0793270837232 76.02525730428762)"><circle r="5"/></g><g class="year" transform="translate(695.1451274662542 70.81659230593793)"><circle r="5"/></g><g class="year" transform="translate(694.1851661711698 90.48417483218849)"><circle r="5"/><text dy="-0.6em" dx="-0.6em" text-anchor="end">2013</text></g></g></svg>
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.14/d3.min.js"></script>
<script>
var width = 960,
height = 440;
var container = d3.select("body").append("div")
.style("width",width);
d3.text("gas.svg",function(err,raw){
container.html(raw);
var svg = d3.select("svg")
.attr("width","100%")
.attr("height",height)
.attr("preserveAspectRatio", "xMidYMid meet")
.attr("viewBox", "0 0 " + width + " " + height);
var text = svg.selectAll("text");
var grid = svg.selectAll("line");
simulateResize();
// Simulate a window resize
// In real life you would skip this and use e.g.:
// window.onresize = resized;
function simulateResize(expand) {
container.transition()
.duration(4000)
.style("width",(expand ? width : width / 1.5) + "px")
.tween("simulate",function(){
return resized;
})
.each("end",function(){
simulateResize(!expand);
});
}
function resized() {
var scale = width / container.node().getBoundingClientRect().width;
// Update SVG height to maintain aspect ratio
svg.attr("height",height / scale);
// Fix grid size at 1px too
grid.style("stroke-width",scale + "px");
// Increase text scale proportional to overall scale reduction
// e.g. 3/4 of the original width -> scale text by 4/3
text.attr("transform","scale(" + scale + " " + scale + ")");
}
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment