best way to get available dimensions for svg in d3.js - d3.js

In this jsbin I am getting the available width like this:
getDimensions() {
const minWidth = window.innerWidth;
const margin = {
top: 20,
right: 50,
left: 50,
bottom: 50
};
const width = minWidth - margin.left - margin.right;
const height = window.innerHeight - margin.top - margin.bottom;
return {
margin,
width,
height
};
}
I then use this to set the width of my svg element:
const el = this.refs.sine;
const dimensions = this.getDimensions();
const xScale = d3.scale.linear()
.domain([0, 20])
.range([0, dimensions.width]);
const yScale = d3.scale.linear()
.domain([0, 20])
.range([dimensions.height, 0]);
const svg = d3.select(el).append("svg")
.attr("width", dimensions.width)
.attr("height", dimensions.height);
It grabs the available width and height in desktop but in mobile it does not.
Can anyone suggest a better way of having a responsive layout for svg?

Calculate the parent width/height of the chart id in this case el or you can directly calculate the width/height of body. Below is the example to do so using jquery:
var containerWidth = $("#el").parent().width();
var containerheight = $("#el").parent().height();
var margin = {top: 15, right: 30, bottom: 40, left:40},
width = containerWidth- margin.left - margin.right,
height = containerheight - margin.top - margin.bottom;

Related

Draw a grid without labels and axis using d3

I would like to create a vertical grid using D3.
I know I could use d3 axis methods (like axisBottom, axisLeft, ...) and set tickSize to manage the grid lines size but the axis generators create not only the lines but also the axis and the labels and I don't need them.
For example, this is what I could draw using axisBottom:
const container = d3.select('svg')
container.append('g').attr('class', 'vertical-grid')
const height = 100
const numberOfTicks = 5
const xScale = d3.scaleLinear()
.domain([0, 100])
.range([0, 200])
const xGridGenerator = d3.axisBottom(xScale)
.tickSize(height)
.ticks(numberOfTicks)
container
.select('.vertical-grid')
.attr('transform', `translate(${0}, ${0})`)
.call(xGridGenerator)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="app">
<svg></svg>
</div>
This is what I would like to draw:
I'm interested only in the red and blue lines, no labels, no axes.
I have the g container, the two scales and the number of ticks.
How can I do that?
Honestly I don't know how to start
You have a good start. A simple way to do the customization is to modify the axes' elements (labels, line colors, etc.) after you've added them. For example:
xAxis.selectAll('text').remove()
xAxis.selectAll('line').attr('stroke', 'blue')
Here's a complete example that renders a version of what you want (though the width/height are a little off because you'd want to have some margin):
const height = 200
const width = 200
const margin = { top: 10, bottom: 10, left: 10, right: 10 }
const container = d3.select('svg')
.attr('height', height)
.attr('width', width)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`)
container.append('g').attr('class', 'vertical-grid')
container.append('g').attr('class', 'horizontal-grid')
const numberOfTicks = { x: 8, y: 4 }
const xScale = d3.scaleLinear()
.domain([0, 100])
.range([0, width - margin.left - margin.right ])
const xGridGenerator = d3.axisBottom(xScale)
.tickSize(height - margin.top - margin.bottom)
.ticks(numberOfTicks.x)
const xAxis = container
.select('.vertical-grid')
.attr('transform', `translate(${0}, ${0})`)
.call(xGridGenerator)
const yScale = d3.scaleLinear()
.domain([0, 100])
.range([0, height - margin.top - margin.bottom ])
const yGridGenerator = d3.axisRight(yScale)
.tickSize(width - margin.left - margin.right)
.ticks(numberOfTicks.y)
const yAxis = container
.select('.horizontal-grid')
.attr('transform', `translate(${0}, ${0})`)
.call(yGridGenerator)
// Customize
xAxis.selectAll('text').remove()
xAxis.selectAll('line').attr('stroke', 'blue')
yAxis.selectAll('text').remove()
yAxis.selectAll('line').attr('stroke', 'red')
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="app">
<svg></svg>
</div>

Why is appended svg not positioned at 0,0?

I have a scale created with D3 and then I pull in an svg sprite using d3.xml. Why is the sprite not in the top left hand corner of the svg before translation? is there a way to ensure the sprite is appended too the exact top left of the SVG?
d3.csv('planets.csv').then(function(data){
var margin = { top: window.innerHeight * 0.3, left: 170, bottom: window.innerHeight * 0.3, right: 50 };
var height = (window.innerHeight - margin.top - margin.bottom)*0.5;
var chartWidth = window.innerWidth - margin.left - margin.right;
var maxDist = d3.max(data, function(d) { return d.distance; });
var xScale = d3.scaleLinear().domain([0, maxDist]).range([0, chartWidth]);
var xAxisTop = d3.axisTop(xScale);
var svg = d3.select('#vis')
.append('svg')
.attr('width', chartWidth + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('class', 'chart')
.attr('transform', 'translate(' + margin.left + ', '+ margin.top + ')');
var xAxisDrawTop = svg.insert('g',':first-child')
.attr('class','x_axis_top')
.call(xAxisTop);
var xAxisDrawTopTorchContainer = d3.select('.x_axis_top').append('g').attr('class','rocket_container')
d3.xml('torch.svg').then(torch=>{
torchSprite = torch.documentElement;
xAxisDrawTopTorchContainer.node().append(torchSprite);
})
The solution was to avoid creating a document fragment by using:
d3.select('relevant-g element').append('image').attr('type','image/svg+xml').attr('xlink:href','torch.svg');
this ensures the appended sprite is mapped to the coordinates 0,0 of the existing SVG in the DOM.

Use d3 to create InnerWidth and InnerHeight so grid isn't on the border

The graph that I am trying to make is right on the border, I tried to include inner margins so I can have space in the outside margins, but it wont work. Any suggestions?
I am using d3.js
Thank you
// D3 gives us an api to allow us to create axises
// d3.axisTop()
// d3.axisRight()
// d3.axisBottom()
// d3.axisLeft()
var data= [80, 100, 56, 120, 180, 30, 40, 120, 160];
var data1= [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
var margin = {top:20, right: 20, bottom: 20, left: 20};
var InnerWidth = 500, InnerHeight = 300;
var svg = d3.select('svg') //applying the width and high of the svg box
.attr("width", InnerWidth)
.attr("height", InnerHeight);
var xScale = d3.scaleLinear()
.domain([0, d3.max(data)]) //takes in integers between 0 to max integer
.range([0, 400]); //represents the length of the x-axis
var yScale = d3.scaleLinear()
.domain([0, d3.max(data1)]) //this the range of the data from 0 to max
.range([InnerHeight, 0]); //represents the length of the x-axis
var x_axis = d3.axisBottom().scale(xScale); //call for x-axis
var y_axis = d3.axisLeft().scale(yScale); //call for y-axis
svg.append("g") //group element
.attr("transform", "translate(50, 0)") //takes the y-axis and transforms it(shift it)
.call(y_axis);
var xAxisTranslate = InnerHeight - 20;
svg.append("g")
.attr("transform", "translate(50, " + xAxisTranslate +")")
.call(x_axis);
svg.append("g")
.attr("transform","translate(${margin.left},${margin.top})");
<html>
<head>
<link rel="stylesheet" href="index.css">
<title>Learn D3.js</title>
</head>
<body>
<h1>Bar Chart using D3.js</h1>
<svg></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="index.js"></script>
</body>
</html>
Please try to replace the width and height of your SVG element with the viewBox like so:
var svg = d3.select('svg')
//.attr("width", InnerWidth)
//.attr("height", InnerHeight);
.attr("viewBox", `${-margin.right} ${-margin.top} ${InnerWidth + 2*margin.right} ${InnerHeight + 2*margin.top}`);
I hope this helps.

Making SVG container 100% width and height of parent container in D3 v4 (instead of by pixels)

I have a parent container (div.barChartContainer) whose height and width are calculated from the viewport, ex: width: calc(100vh - 200px). The SVG container is appended to the div.barChartContainer element.
I am struggling with how to get the SVG element to be 100% width and height of its parent element, hoping I could get some help. For now I have static pixel amounts (where it currently renders properly, but is non responsive).
Thanks!
var margin = {top: 20, right: 20, bottom: 30, left: 80},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// set the ranges
var y = d3.scaleBand()
.range([height, 0])
.padding(0.4);
var x = d3.scaleLinear()
.range([0, width]);
var svg = d3.select(".barChartContainer").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
The solution was to comment out/remove the width and height attribute values and instead add the following two lines:
//- .attr("width", width + margin.left + margin.right)
//- .attr("height", height + margin.top + margin.bottom)
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", "0 0 960 500")
Check the svg is drawn with parent height and width.
Ref: https://bl.ocks.org/curran/3a68b0c81991e2e94b19
var parentDiv = document.getElementById("parentDiv");
var svg = d3.select(parentDiv).append("svg");
function redraw(show) {
// Extract the width and height that was computed by CSS.
var width = parentDiv.clientWidth;
var height = parentDiv.clientHeight;
// Use the extracted size to set the size of an SVG element.
svg
.attr("width", width)
.attr("height", height);
if (show === false) {
svg.style('visibility', 'hidden');
} else {
svg.style('visibility', 'visible');
}
svg.attr("style", "outline: thin solid red;")
.append("circle")
.attr("cx", 30)
.attr("cy", 30)
.attr("r", 20);
}
// Draw for the first time to initialize.
redraw(false);
// Redraw after sometime
setTimeout(redraw, 1000);
#parentDiv {
height: calc(100vh - 100px); /** output container is small for display */
width: calc(100vw - 100px);
display: block;
border: 1px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.10/d3.min.js"></script>
<div id="parentDiv"></div>

How to limit animated axis labels position in d3?

I'm implementing a chart using d3 that has a sliding x axis. Demo
When axis labels approach the edges, they fade out/in.
However, the labels animate into the left and right margins of the svg (the gray area):
How could I avoid the labels to be rendered on the svg margins?
const timeWindow = 10000;
const transitionDuration = 3000;
const xScaleDomain = (now = new Date()) =>
[now - timeWindow, now];
const totalWidth = 500;
const totalHeight = 200;
const margin = {
top: 30,
right: 50,
bottom: 30,
left: 50
};
const width = totalWidth - margin.left - margin.right;
const height = totalHeight - margin.top - margin.bottom;
const svg = d3.select('.chart')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight)
.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`)
svg
.append('rect')
.attr('width', width)
.attr('height', height);
// Add x axis
const xScale = d3.scaleTime()
.domain(xScaleDomain(new Date() - transitionDuration))
.range([0, width]);
const xAxis = d3.axisBottom(xScale);
const xAxisSelection = svg
.append('g')
.attr('transform', `translate(0, ${height})`)
.call(xAxis);
// Animate
const animate = () => {
xScale.domain(xScaleDomain());
xAxisSelection
.transition()
.duration(transitionDuration)
.ease(d3.easeLinear)
.call(xAxis)
.on('end', animate);
};
animate();
svg {
margin: 30px;
background-color: #ccc;
}
rect {
fill: #fff;
outline: 1px dashed #ddd;
}
<script src="https://unpkg.com/d3#4.4.1/build/d3.js"></script>
<div class="chart"></div>
You can clip-path it:
svg.append('defs')
.append('clipPath')
.attr('id','myClip')
.append('rect')
.attr('width', width)
.attr('height', totalHeight);
...
const xAxisSelection = svg
.append('g')
.attr('clip-path', 'url(#myClip)')
...
Full Code:
const timeWindow = 10000;
const transitionDuration = 3000;
const xScaleDomain = (now = new Date()) =>
[now - timeWindow, now];
const totalWidth = 500;
const totalHeight = 200;
const margin = {
top: 30,
right: 50,
bottom: 30,
left: 50
};
const width = totalWidth - margin.left - margin.right;
const height = totalHeight - margin.top - margin.bottom;
const svg = d3.select('.chart')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight)
.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`)
svg.append('defs')
.append('clipPath')
.attr('id','myClip')
.append('rect')
.attr('width', width)
.attr('height', totalHeight);
// Add x axis
const xScale = d3.scaleTime()
.domain(xScaleDomain(new Date() - transitionDuration))
.range([0, width]);
const xAxis = d3.axisBottom(xScale);
const xAxisSelection = svg
.append('g')
.attr('clip-path', 'url(#myClip)')
.attr('transform', `translate(0, ${height})`)
.call(xAxis);
// Animate
const animate = () => {
xScale.domain(xScaleDomain());
xAxisSelection
.transition()
.duration(transitionDuration)
.ease(d3.easeLinear)
.call(xAxis)
.on('end', animate);
};
animate();
svg {
margin: 30px;
background-color: #ccc;
}
rect {
fill: #fff;
outline: 1px dashed #ddd;
}
<script src="https://unpkg.com/d3#4.4.1/build/d3.js"></script>
<div class="chart"></div>

Resources