1

Currently using d3 for the first time & mid tier knowledge of CSS.

Here is the code pen I will reference in this questions - and also as a stack snippet:

const padding = 30
const fakeData = [
  [0, 0],
  [100, 300],
  [300, 250],
  [500, 450]
]

function applyGraph(svg) {
  // Setup the axes
  const xScale = d3.scaleLinear()
    .range([0 + padding, 200 - padding])
    .domain([500, 0])
  const yScale = d3.scaleLinear()
    .range([0 + padding, 200 - padding])
    .domain([0, 500])
  
  const xAxis = d3.axisBottom(xScale).ticks(4)
  const yAxis = d3.axisLeft(yScale).ticks(4)
  svg.append('g')
    .attr('class', 'axis-rem')
    .attr('transform', `translate(0, ${200 - padding})`)
    .call(xAxis)
  svg.append('g')
    .attr('class', 'axis-rem')
    .attr('transform', `translate(${padding}, 0)`)
    .call(yAxis)
  
  // Plot some data
  const line = d3.line()
    .x(d => xScale(d[0]))
    .y(d => yScale(d[1]))
  
  svg.append('path')
    .data([fakeData])
    .attr('class', 'line-rem')
    .attr('fill', 'none')
    .attr('stroke', 'blue')
    .attr('d', line)
}

var g1 = d3.select("#graph1")
var g2 = d3.select("#graph2")
applyGraph(g1)
applyGraph(g2)
.line-rem {
   stroke-width: 0.4rem;
}

.axis-rem {
  font-size: 0.6rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.6.1/d3.min.js"></script>
<svg 
     id="graph1"
     viewBox="0 0 200 200"
     width="400" 
     height="400"/>
<svg 
     id="graph2"
     viewBox="0 0 200 200"
     width="200" 
     height="200"/>

I have a function (component) which renders a d3 graph based on some data. This function is reused in a number of places at different sizes / scales. My aim is to get the text and stroke width the same size regardless of the size the SVG is scaled to.

My initial thought was to use rem units as I have heard about them previously. My understanding is that this should make the size relative to the "root element". However even after using these units for stroke-width and font-size in the SVG, the text and the line appear to have different sizes.

Two questions:

  1. Why does this happen? Is it because the "root element" is reset to be the <svg> tag or something similar?
  2. How would I achieve this goal of making stroke-width and font-size (I would prefer not to hard code some px value in)?
Robin Mackenzie
  • 18,801
  • 7
  • 38
  • 56
CarterKF
  • 63
  • 2
  • 7
  • Your axis dimensions are fixed in `applyGraph` but your vary your svg dimensions (despite a constant `viewBox` size)... what are open to changing ? – Robin Mackenzie Jul 30 '22 at 11:54
  • @RobinMackenzie Thanks for the response! Very new to d3 and `viewBox`. I was assuming that the `scaleLinear().range(...)` was refering to the view box's dimensions. Do I have to change the `viewBox` to match the `height` and `width` on the `svg` component? I would be willing to have a variable sized `viewBox` (although not sure how that works at the moment) as eventually I want the graph to fill any horizontal space given. – CarterKF Jul 30 '22 at 17:33

1 Answers1

0

There are useful answers regarding use of width and height and viewbox here and here.

Your bigger chart is 'zoomed in' because the same 200x200 coordinate system is stretched over a larger SVG (400x400 vs 200x200).

If you dispense with the viewBox and change applyGraph to recognise the width and height of the svg then your charts will show the same font and stroke width:

const padding = 30
const fakeData = [
  [0, 0],
  [100, 300],
  [300, 250],
  [500, 450]
]

function applyGraph(svg) {
  // get dimensions of svg
  const svgWidth = +svg.attr("width");
  const svgHeight = +svg.attr("height");

  // Setup the axes
  const xScale = d3.scaleLinear()
    .range([0 + padding, svgWidth - padding])
    .domain([500, 0])
  const yScale = d3.scaleLinear()
    .range([0 + padding, svgHeight - padding])
    .domain([0, 500])
  
  const xAxis = d3.axisBottom(xScale).ticks(4)
  const yAxis = d3.axisLeft(yScale).ticks(4)
  svg.append('g')
    .attr('class', 'axis-rem')
    .attr('transform', `translate(0, ${svgHeight - padding})`)
    .call(xAxis)
  svg.append('g')
    .attr('class', 'axis-rem')
    .attr('transform', `translate(${padding}, 0)`)
    .call(yAxis)
  
  // Plot some data
  const line = d3.line()
    .x(d => xScale(d[0]))
    .y(d => yScale(d[1]))
  
  svg.append('path')
    .data([fakeData])
    .attr('class', 'line-rem')
    .attr('fill', 'none')
    .attr('stroke', 'blue')
    .attr('d', line)
}

var g1 = d3.select("#graph1")
var g2 = d3.select("#graph2")
applyGraph(g1)
applyGraph(g2)
.line-rem {
   stroke-width: 0.4rem;
}

.axis-rem {
  font-size: 0.6rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.6.1/d3.min.js"></script>
<svg 
     id="graph1"
     width="400" 
     height="400"/>
<svg 
     id="graph2"
     width="200" 
     height="200"/>
Robin Mackenzie
  • 18,801
  • 7
  • 38
  • 56