Draw a grid without labels and axis using d3 - d3.js

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>

Related

d3 - y-axis with log scale - ticks getting overlapped

I am trying to use log scale for y-axis on my line chart.
Here is my code:
var yScale_for_axis = d3.scaleLog().domain([1,d3.max(vals)]).range ([height,0]);
g.append("g")
.call(d3.axisLeft(yScale_for_axis).tickFormat( d3.format(".1e"));
The ticks are getting overlapped with each other. Heres how it looks:
What should I do to make it look like this?
Look at the snippet - it seems to be working. Maybe it's tick formatting which makes ticks overlap:
const width = 400, height = 500;
// Append SVG
const svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
// Create scale
const scale = d3.scaleLog()
.domain([1, 5000])
.range([20, height - 20]);
// Add scales to axis
const yAxis = d3.axisLeft()
.scale(scale);
//Append group and insert axis
svg.append("g")
.attr('transform', 'translate(150, 0)')
.call(yAxis);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

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.

D3 line graph show positive and negative numbers

I'm working with D3 to create a line-graph. This graph is available here jsfiddle.
I'm trying to draw lines manually to represent certain data-point-values. I've tried to add comments to most of the lines in the code, so hopefully you can follow along.
My problem is that I cannot seem to draw negative numbers in a good way, if i do, then the graph-data-lines are misaligned. So my question is: How can i scale my graph so that I can show both negative and positive numbers? In this case, the graph should go from 2 to -2 based on the max/min values i've set.
currently. I'm scaling my graph like this
//
// Setup y scale
//
var y = d3.scale.linear()
.domain([0, max])
.range([height, 0]);
//
// Setup x scale
//
var x = d3.time.scale()
.domain(d3.extent(data, dateFn))
.range([0, width]);
In my mind, doing .domain([-2,max]) would be sufficient, but that seems to make things worse.
Also, my lines do not seem to match what the data-lines are saying. In the jsfiddle, the green line is set at 1. But the data-lines whose value are 1, are not on that green line.
So, this is pretty much a scale question i guess.
Visual (picasso-esc) representation of what the graph should look like if it worked.
As you want your y domain to be [-2, 2] as opposed to be driven by the data, you can remove a lot of setup and helper functions from your drawGraph function.
After drawing your graph, you can simply loop through the yLines array, and draw a line for each with the specified color, at the specified val according to your yScale.
Update: EDITED: As you will be supplied the values of nominal, upperTolerance, lowerTolerance, innerUpperTolerance, innerLowerTolerance from your endpoint (and they don't need to be calculated from the data on the client side), just feed those values into your data-driven yScale to draw the coloured lines.
Below I have just used the values 1, 1.8, -1.8, but you will receive values that will be more meaningfully tied to your data.
// Setup
const yLines = [{
val: 1,
color: 'green'
},
{
val: 1.8,
color: 'yellow'
},
{
val: -1.8,
color: 'red'
}
]
const margin = {
top: 10,
right: 80,
bottom: 60,
left: 20
};
const strokeWidth = 3;
const pointRadius = 4;
const svgWidth = 600;
const svgHeight = 600;
const width = svgWidth - margin.left - margin.right;
const height = svgHeight - margin.top - margin.bottom;
const stroke = '#2990ea'; // blue
const areaFill = 'rgba(41,144,234,0.1)'; // lighter blue
const format = d3.time.format("%b %e %Y");
const valueFn = function(d) {
return d.value
};
const dateFn = function(d) {
return format.parse(d.name)
};
// select the div and append svg to it
const graph = d3.select('#chart').append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.style('overflow', 'visible');
const transformGroup = graph.append('g')
.attr('tranform', `translate(${margin.left}, ${margin.right})`)
// Make a group for yLines
const extraLines = transformGroup.append('g')
.attr('class', 'extra-lines')
// Generate some dummy data
const getData = function() {
let JSONData = [];
for (var i = 0; i < 30; i++) {
JSONData.push({
"name": moment().add(i, 'days').format('MMM D YYYY'),
"value": Math.floor(Math.random() * (Math.floor(Math.random() * 20))) - 10
})
}
return JSONData.slice()
}
const drawGraph = function(data) {
console.log(data)
// Setup y scale
const y = d3.scale.linear()
.domain(d3.extent(data.map((d) => d.value)))
.range([height, 0]);
// Setup y axis
const yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10)
.tickSize(0, 0, 0)
// append group & call yAxis
transformGroup.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + margin.left + ",0)")
.call(yAxis);
// Draw extra coloured lines from yLines array
extraLines.selectAll('.extra-line')
.data(yLines)
.enter()
.append('line')
.attr('class', 'extra-line')
.attr('x1', margin.left)
.attr('x2', svgWidth - margin.right)
.attr('stroke', d => d.color)
.attr('y1', d => y(+d.val))
.attr('y2', d => y(+d.val))
.attr('stroke-width', strokeWidth)
.attr('opacity', 0.5)
// Setup x scale
const x = d3.time.scale()
.domain(d3.extent(data, dateFn))
.range([0, width])
// function for filling area under chart
const area = d3.svg.area()
.x(d => x(format.parse(d.name)))
.y0(height)
.y1(d => y(d.value))
// function for drawing line
const line = d3.svg.line()
.x(d => x(format.parse(d.name)))
.y(d => y(d.value))
const lineStart = d3.svg.line()
.x(d => x(format.parse(d.name)))
.y(d => y(0))
// make the line
transformGroup.append('path')
.attr('stroke', stroke)
.attr('stroke-width', strokeWidth)
.attr('fill', 'none')
.attr('transform', `translate(${margin.left}, ${margin.top})`)
.attr('d', lineStart(data))
.attr('d', line(data))
// fill area under the graph
transformGroup.append("path")
.datum(data)
.attr("class", "area")
.attr('fill', areaFill)
.attr('transform', `translate(${margin.left}, ${margin.top})`)
.attr('d', lineStart(data))
.attr("d", area)
}
drawGraph(getData())
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
<div id="chart" style="margin: 0 auto;"></div>

Simple bar chart on d3 JS

I'm new to this library and I don't understand why the rect bars have a negative value. I'm using d3-node (d3 v4) with jsdom to render svg from server.
This is what I tried :
var data = [
{
"name" : "Nom Prénom",
"value1" : 40,
"value2" : 100,
"value3" : 10,
"total" : 150 //a changer
}
];
var margin = {top: 20, right: 160, bottom: 35, left: 30};
var width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var svg = this.d3n
.createSVG(GRAPH_WIDTH - MARGINS.left - MARGINS.right - LEGEND_PANEL.width, GRAPH_HEIGHT - MARGINS.top - MARGINS.bottom)
.append('g');
var stack = d3.stack().keys(["value1","value2","value3"]).order(d3.stackOrderNone).offset(d3.stackOffsetNone);
var dataSet = stack(data);
//set x, y and colors
var x = d3.scaleBand()
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var colors = ["b33040", "#d25c4d", "#f2b447", "#d9d574"];
//define and draw axis
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(x).ticks(5).tickFormat(function(d){ return d;});
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Create groups for each series, rects for each segment
var groups = svg.selectAll("g.cost")
.data(dataSet)
.enter().append("g")
.attr("class", "cost")
.style("fill", function(d, i) { return colors[i]; });
var rect = groups.selectAll("rect")
.data(function(d) { return d; })
.enter()
.append("rect")
.attr("x", function(d) {
return x(d[0]);
})
.attr("y", function(d) { return y(d[0] + d[1]); })
.attr("height", function(d) { return y(d[0]); })
.attr("width", x.bandwidth())
.on("mouseover", function() { tooltip.style("display", null); })
.on("mouseout", function() { tooltip.style("display", "none"); })
.on("mousemove", function(d) {
var xPosition = d3.mouse(this)[0] - 15;
var yPosition = d3.mouse(this)[1] - 25;
tooltip.attr("transform", "translate(" + xPosition + "," + yPosition + ")");
tooltip.select("text").text(d.y);
});
And the svg looks like this
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="250"><g><g class="y axis" fill="none" font-size="10" font-family="sans-serif" text-anchor="end"><path class="domain" stroke="#000" d="M-6,0.5H0.5V770.5H-6"></path></g><g class="x axis" transform="translate(0,445)" fill="none" font-size="10" font-family="sans-serif" text-anchor="middle"><path class="domain" stroke="#000" d="M0.5,6V0.5H770.5V6"></path></g><g class="cost" style="fill: b33040;"><rect y="-17355" height="445" width="770"></rect></g><g class="cost" style="fill: #d25c4d;"><rect y="-79655" height="-17355" width="770"></rect></g><g class="cost" style="fill: #f2b447;"><rect y="-128605" height="-61855" width="770"></rect></g></g></svg>
It's kinda hard to see some v4 tutorials, it seems there is a lot of changes between v3 and v4. Could you please explain in which part this is wrong ?
Thanks a lot
First of all, looks like there are some issues with height and width:
var width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
Gives us width = 770 and height = 445. But
.createSVG(GRAPH_WIDTH - MARGINS.left - MARGINS.right - LEGEND_PANEL.width, GRAPH_HEIGHT - MARGINS.top - MARGINS.bottom)
ends up with <svg width="360" height="250">.
Then, you're getting negative height and y because of missing domain part in your y.scaleLinear declaration:
var y = d3.scaleLinear()
.range([height, 0]);
It should be something like:
var y = d3.scaleLinear()
.domain([0, 150])
.range([height, 0])
D3 scaleLinear() returns function which translates every point from your data dimension (in this case it's [0, 150], where 0 and 150 min and max possible values in your data) to its visual coordinate, which is then used for drawing. I.e. it maps [0, 150] -> [445, 0].
To get a better understanding, I suggest reading the official ds-scale docs. There are also three tutorials are mentioned in the first paragraph.
Even if you found a v3 tutorial, it might be worth to read it, the ideas remain the same, just keep the changelog page open to verify how to write the same code in v4. The biggest collection of tutorials is listed here.

best way to get available dimensions for svg in 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;

Resources