I have a graph drawn with d3 with the x and y scales defined as follows:
x = d3.scaleLinear().domain([30, 150]).range([0, width])
y = d3.scaleLinear().domain([0, 100]).range([height, 0])
I need to draw a vertical line after 25% of data points. So I have my code like this:
svg.append('line')
.attr('x1', lowerLimit)
.attr('y1', 0)
.attr('x2', lowerLimit)
.attr('y2', height)
.style('stroke', 'red')
My problem is I am not sure how to set the lowerLimit x value to be 25% of all the x-values in the scale. Can someone help please? Thanks in advance!
Well, your question is not clear. When you say:
I need to draw a vertical line after 25% of data points
You're talking about the first quartile, which is impossible to calculate without the data, and not only that, but which changes for every different data set.
If you are indeed talking about the first quartile, this is what you have to do:
Given an data array called data, you can use d3.quantile:
var firstQuartile = d3.quantile(data, 0.25);
In the following demo, I'm plotting 100 dots, and calculating the 25 percentile (first quartile) regarding the property x:
var lowerLimit = d3.quantile(data, 0.25, function(d) {
return d.x
});
The console shows the value. Check the demo:
var data = d3.range(100).map(() => ({
x: Math.random() * 120 + 30,
y: Math.random() * 100
}));
var w = 500,
h = 160;
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
x = d3.scaleLinear().domain([30, 150]).range([20, w - 20]);
y = d3.scaleLinear().domain([0, 100]).range([h - 20, 20]);
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
var circles = svg.selectAll("circles")
.data(data)
.enter()
.append("circle")
.attr("r", 2)
.attr("fill", "teal")
.attr("cx", d => x(d.x))
.attr("cy", d => y(d.y));
var data = data.sort((a, b) => d3.ascending(a.x, b.x))
var lowerLimit = d3.quantile(data, 0.25, function(d) {
return d.x
});
console.log(lowerLimit);
svg.append('line')
.attr('x1', x(lowerLimit))
.attr('y1', 20)
.attr('x2', x(lowerLimit))
.attr('y2', h - 20)
.style('stroke', 'red')
svg.append("g")
.attr("transform", "translate(0," + (h - 20) + ")")
.call(xAxis);
svg.append("g")
.attr("transform", "translate(20,0)")
.call(yAxis);
<script src="https://d3js.org/d3.v4.min.js"></script>
However, if you're talking about "getting the value that is 25% of the domain", the answer is easy. You can, for instance, create a function:
function findLimit(percentage) {
return x(x.domain()[0] + (x.domain()[1] - x.domain()[0]) * percentage / 100);
};
And pass the value of that function to lowerLimit:
var lowerLimit = findLimit(25);
Check this demo, drawing a line at 25% of the x axis:
var data = d3.range(100).map(() => ({
x: Math.random() * 120 + 30,
y: Math.random() * 100
}));
var w = 500,
h = 200;
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
x = d3.scaleLinear().domain([30, 150]).range([20, w - 20]);
y = d3.scaleLinear().domain([0, 100]).range([h - 20, 20]);
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
var circles = svg.selectAll("circles")
.data(data)
.enter()
.append("circle")
.attr("r", 2)
.attr("fill", "teal")
.attr("cx", d => x(d.x))
.attr("cy", d => y(d.y));
var data = data.sort((a, b) => d3.ascending(a.x, b.x))
var lowerLimit = d3.quantile(data, 0.25, function(d) {
return d.x
});
console.log(lowerLimit);
svg.append('line')
.attr('x1', x(lowerLimit))
.attr('y1', 20)
.attr('x2', x(lowerLimit))
.attr('y2', h - 20)
.style('stroke', 'red')
svg.append("g")
.attr("transform", "translate(0," + (h - 20) + ")")
.call(xAxis);
svg.append("g")
.attr("transform", "translate(20,0)")
.call(yAxis);
<script src="https://d3js.org/d3.v4.min.js"></script>
Related
I'm really having trouble with D3 and need some help changing my existing barchart to be a grouped barchart The barchart is being used within a tooltip and currently looks like:
Each colour represents a sector of industry (pink = retail, teal = groceries...etc).
I need to change the bar chart so that it compares the percentage change in each industry with the world average percentage change in this industry.
At the moment the bar chart is being created from an array of data. I also have an array with the world percentage values.
So imagine:
countryData = [10,-20,-30,-63,-23,20],
worldData = [23,-40,-23,-42,-23,40]
Where index 0 = retail sector, index 1 = grocery sector, etc.
I need to plot a grouped barchart comparing each sector to the world average (show the world average in red). This is a bit tricky to explain so I drew it for you (...excuse the shoddy drawing).
Please can someone help me change my existing tooltip?
Here's the current code. If you want to simulate the data values changing.
If you want to scrap my existing code that's fine.
.on('mouseover', ({ properties }) => {
// get county data
const mobilityData = covid.data[properties[key]] || {};
const {
retailAverage,
groceryAverage,
parksAverage,
transitAverage,
workplaceAverage,
residentialAverage,
} = getAverage(covid1);
let avgArray = [retailAverage, groceryAverage, parksAverage, transitAverage, workplaceAverage, retailAverage];
let categoriesNames = ["Retail", "Grocery", "Parks", "Transit", "Workplaces", "Residential"];
// create tooltip
div = d3.select('body')
.append('div')
.attr('class', 'tooltip')
.style('opacity', 0);
div.html(properties[key]);
div.transition()
.duration(200)
.style('opacity', 0.9);
// calculate bar graph data for tooltip
const barData = [];
Object.keys(mobilityData).forEach((industry) => {
const stringMinusPercentage = mobilityData[industry].slice(0, -1);
barData.push(+stringMinusPercentage); // changing it to an integer value, from string
});
//combine the two lists for the combined bar graph
var combinedList = [];
for(var i = 0; i < barData.length; i++) {
const stringMinusPercentage2 = +(avgArray[i].slice(0, -1));
const object = {category: categoriesNames[i], country: barData[i], world: stringMinusPercentage2}
combinedList.push(object); //Push object into list
}
console.log(combinedList);
// barData = barData.sort(function (a, b) { return a - b; });
// sort into ascending ^ keeping this in case we need it later
const height2 = 220;
const width2 = 250;
const margin = {
left: 50, right: 10, top: 20, bottom: 15,
};
// create bar chart svg
const svgA = div.append('svg')
.attr('height', height2)
.attr('width', width2)
.style('border', '1px solid')
.append('g')
// apply the margins:
.attr('transform', `translate(${[`${margin.left},${margin.top}`]})`);
const barWidth = 30; // Width of the bars
// plot area is height - vertical margins.
const chartHeight = height2 - margin.top - margin.left;
// set the scale:
const yScale = d3.scaleLinear()
.domain([-100, 100])
.range([chartHeight, 0]);
// draw some rectangles:
svgA
.selectAll('rect')
.data(barData)
.enter()
.append('rect')
.attr('x', (d, i) => i * barWidth)
.attr('y', (d) => {
if (d < 0) {
return yScale(0); // if the value is under zero, the top of the bar is at yScale(0);
}
return yScale(d); // otherwise the rectangle top is above yScale(0) at yScale(d);
})
.attr('height', (d) => Math.abs(yScale(0) - yScale(d))) // the height of the rectangle is the difference between the scale value and yScale(0);
.attr('width', barWidth)
.style('fill', (d, i) => colours[i % 6]) // colour the bars depending on index
.style('stroke', 'black')
.style('stroke-width', '1px');
// Labelling the Y axis
const yAxis = d3.axisLeft(yScale);
svgA.append('text')
.attr('class', 'y label')
.attr('text-anchor', 'end')
.attr('x', -15)
.attr('y', -25)
.attr('dy', '-.75em')
.attr('transform', 'rotate(-90)')
.text('Percentage Change (%)');
svgA.append('g')
.call(yAxis);
})
.on('mouseout', () => {
div.style('opacity', 0);
div.remove();
})
.on('mousemove', () => div
.style('top', `${d3.event.pageY - 140}px`)
.style('left', `${d3.event.pageX + 15}px`));
svg.append('g')
.attr('transform', 'translate(25,25)')
.call(colorLegend, {
colorScale,
circleRadius: 10,
spacing: 30,
textOffset: 20,
});
};
drawMap(svg1, geoJson1, geoPath1, covid1, key1, 'impact1');
drawMap(svg2, geoJson2, geoPath2, covid2, key2, 'impact2');
};
In short I would suggest you to use two Band Scales for x axis. I've attached a code snippet showing the solution.
Enjoy ;)
//Assuming the following data final format
var finalData = [
{
"groupKey": "Retail",
"sectorValue": 70,
"worldValue": 60
},
{
"groupKey": "Grocery",
"sectorValue": 90,
"worldValue": 90
},
{
"groupKey": "other",
"sectorValue": -20,
"worldValue": 30
}
];
var colorRange = d3.scaleOrdinal().range(["#00BCD4", "#FFC400", "#ECEFF1"]);
var subGroupKeys = ["sectorValue", "worldValue"];
var svg = d3.select("svg");
var margin = {top: 20, right: 20, bottom: 30, left: 40};
var width = +svg.attr("width") - margin.left - margin.right;
var height = +svg.attr("height") - margin.top - margin.bottom;
var container = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// The scale spacing the groups, your "sectors":
var x0 = d3.scaleBand()
.domain(finalData.map(d => d.groupKey))
.rangeRound([0, width])
.paddingInner(0.1);
// The scale for spacing each group's bar, your "sector bar":
var x1 = d3.scaleBand()
.domain(subGroupKeys)
.rangeRound([0, x0.bandwidth()])
.padding(0.05);
var yScale = d3.scaleLinear()
.domain([-100, 100])
.rangeRound([height, 0]);
//and then you will need to append both, groups and bars
var groups = container.append('g')
.selectAll('g')
.data(finalData, d => d.groupKey)
.join("g")
.attr("transform", (d) => "translate(" + x0(d.groupKey) + ",0)");
//define groups bars, one per sub group
var bars = groups
.selectAll("rect")
.data(d => subGroupKeys.map(key => ({ key, value: d[key], groupKey: d.groupKey })), (d) => "" + d.groupKey + "_" + d.key)
.join("rect")
.attr("fill", d => colorRange(d.key))
.attr("x", d => x1(d.key))
.attr("width", (d) => x1.bandwidth())
.attr('y', (d) => Math.min(yScale(0), yScale(d.value)))
.attr('height', (d) => Math.abs(yScale(0) - yScale(d.value)));
//append x axis
container.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x0));
//append y axis
container.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale))
.append("text")
.attr("x", 2)
.attr("y", yScale(yScale.ticks().pop()) + 0.5)
.attr("dy", "0.32em")
.attr("fill", "#000")
.attr("font-weight", "bold")
.attr("text-anchor", "start")
.text("Values");
<script src="https://d3js.org/d3.v7.min.js"></script>
<svg width="600" height="400"></svg>
I am using this example to make scatter plot:
https://www.d3-graph-gallery.com/graph/boxplot_show_individual_points.html
Now this example uses jitter to randomize x position of the dots for demonstration purpose, but my goal is to make these dots in that way so they don't collide and to be in the same row if there is collision.
Best example of what I am trying to do (visually) is some sort of beeswarm where data is represented like in this fiddle:
https://jsfiddle.net/n444k759/4/
Snippet of first example:
// set the dimensions and margins of the graph
var margin = {top: 10, right: 30, bottom: 30, left: 40},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
// Read the data and compute summary statistics for each specie
d3.csv("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/iris.csv", function(data) {
// Compute quartiles, median, inter quantile range min and max --> these info are then used to draw the box.
var sumstat = d3.nest() // nest function allows to group the calculation per level of a factor
.key(function(d) { return d.Species;})
.rollup(function(d) {
q1 = d3.quantile(d.map(function(g) { return g.Sepal_Length;}).sort(d3.ascending),.25)
median = d3.quantile(d.map(function(g) { return g.Sepal_Length;}).sort(d3.ascending),.5)
q3 = d3.quantile(d.map(function(g) { return g.Sepal_Length;}).sort(d3.ascending),.75)
interQuantileRange = q3 - q1
min = q1 - 1.5 * interQuantileRange
max = q3 + 1.5 * interQuantileRange
return({q1: q1, median: median, q3: q3, interQuantileRange: interQuantileRange, min: min, max: max})
})
.entries(data)
// Show the X scale
var x = d3.scaleBand()
.range([ 0, width ])
.domain(["setosa", "versicolor", "virginica"])
.paddingInner(1)
.paddingOuter(.5)
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
// Show the Y scale
var y = d3.scaleLinear()
.domain([3,9])
.range([height, 0])
svg.append("g").call(d3.axisLeft(y))
// Show the main vertical line
svg
.selectAll("vertLines")
.data(sumstat)
.enter()
.append("line")
.attr("x1", function(d){return(x(d.key))})
.attr("x2", function(d){return(x(d.key))})
.attr("y1", function(d){return(y(d.value.min))})
.attr("y2", function(d){return(y(d.value.max))})
.attr("stroke", "black")
.style("width", 40)
// rectangle for the main box
var boxWidth = 100
svg
.selectAll("boxes")
.data(sumstat)
.enter()
.append("rect")
.attr("x", function(d){return(x(d.key)-boxWidth/2)})
.attr("y", function(d){return(y(d.value.q3))})
.attr("height", function(d){return(y(d.value.q1)-y(d.value.q3))})
.attr("width", boxWidth )
.attr("stroke", "black")
.style("fill", "#69b3a2")
// Show the median
svg
.selectAll("medianLines")
.data(sumstat)
.enter()
.append("line")
.attr("x1", function(d){return(x(d.key)-boxWidth/2) })
.attr("x2", function(d){return(x(d.key)+boxWidth/2) })
.attr("y1", function(d){return(y(d.value.median))})
.attr("y2", function(d){return(y(d.value.median))})
.attr("stroke", "black")
.style("width", 80)
var simulation = d3.forceSimulation(data)
.force("x", d3.forceX(function(d) { return x(d.Species); }))
// .force("y", d3.forceX(function(d) { return y(d.Sepal_lenght) }))
.force("collide", d3.forceCollide()
.strength(1)
.radius(4+1))
.stop();
for (var i = 0; i < data.length; ++i) simulation.tick();
// Add individual points with jitter
var jitterWidth = 50
svg
.selectAll("points")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d){return( d.x )})
.attr("cy", function(d){return(y(d.Sepal_Length))})
.attr("r", 4)
.style("fill", "white")
.attr("stroke", "black")
})
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
I tried to make something like this:
var simulation = d3.forceSimulation(data)
.force("x", d3.forceX(function(d) { return x(d.Species); }))
.force("collide", d3.forceCollide(4)
.strength(1)
.radius(4+1))
.stop();
for (var i = 0; i < 120; ++i) simulation.tick();
// Append circle points
svg.selectAll(".point")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d){
return(x(d.x))
})
.attr("cy", function(d){
return(y(d.y))
})
.attr("r", 4)
.attr("fill", "white")
.attr("stroke", "black")
but it does not even prevent collision and I am a bit confused with it.
I also tried to modify plot from this example:
http://bl.ocks.org/asielen/92929960988a8935d907e39e60ea8417
where beeswarm looks exactly what I need to achieve. But this code is way too expanded as it is made to fit the purpose of reusable charts and I can't track what exact formula is used to achieve this:
Any help would be great..
Thanks
Here's a quick example which combines the ideas of your beeswarm example with your initial boxplot. I've commented the tricky parts below:
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<script>
// set the dimensions and margins of the graph
var margin = {
top: 10,
right: 30,
bottom: 30,
left: 40
},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
// Read the data and compute summary statistics for each specie
d3.csv("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/iris.csv", function(data) {
// Compute quartiles, median, inter quantile range min and max --> these info are then used to draw the box.
var sumstat = d3.nest() // nest function allows to group the calculation per level of a factor
.key(function(d) {
return d.Species;
})
.rollup(function(d) {
q1 = d3.quantile(d.map(function(g) {
return g.Sepal_Length;
}).sort(d3.ascending), .25)
median = d3.quantile(d.map(function(g) {
return g.Sepal_Length;
}).sort(d3.ascending), .5)
q3 = d3.quantile(d.map(function(g) {
return g.Sepal_Length;
}).sort(d3.ascending), .75)
interQuantileRange = q3 - q1
min = q1 - 1.5 * interQuantileRange
max = q3 + 1.5 * interQuantileRange
return ({
q1: q1,
median: median,
q3: q3,
interQuantileRange: interQuantileRange,
min: min,
max: max
})
})
.entries(data)
// Show the X scale
var x = d3.scaleBand()
.range([0, width])
.domain(["setosa", "versicolor", "virginica"])
.paddingInner(1)
.paddingOuter(.5)
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
// Show the Y scale
var y = d3.scaleLinear()
.domain([3, 9])
.range([height, 0])
svg.append("g").call(d3.axisLeft(y))
// Show the main vertical line
svg
.selectAll("vertLines")
.data(sumstat)
.enter()
.append("line")
.attr("x1", function(d) {
return (x(d.key))
})
.attr("x2", function(d) {
return (x(d.key))
})
.attr("y1", function(d) {
return (y(d.value.min))
})
.attr("y2", function(d) {
return (y(d.value.max))
})
.attr("stroke", "black")
.style("width", 40)
// rectangle for the main box
var boxWidth = 100
svg
.selectAll("boxes")
.data(sumstat)
.enter()
.append("rect")
.attr("x", function(d) {
return (x(d.key) - boxWidth / 2)
})
.attr("y", function(d) {
return (y(d.value.q3))
})
.attr("height", function(d) {
return (y(d.value.q1) - y(d.value.q3))
})
.attr("width", boxWidth)
.attr("stroke", "black")
.style("fill", "#69b3a2")
// Show the median
svg
.selectAll("medianLines")
.data(sumstat)
.enter()
.append("line")
.attr("x1", function(d) {
return (x(d.key) - boxWidth / 2)
})
.attr("x2", function(d) {
return (x(d.key) + boxWidth / 2)
})
.attr("y1", function(d) {
return (y(d.value.median))
})
.attr("y2", function(d) {
return (y(d.value.median))
})
.attr("stroke", "black")
.style("width", 80)
var r = 8;
// create a scale that'll return a discreet value
// so that close y values fall in a line
var yPtScale = y.copy()
.range([Math.floor(y.range()[0] / r), 0])
.interpolate(d3.interpolateRound)
.domain(y.domain());
// bucket the data
var ptsObj = {};
data.forEach(function(d,i) {
var yBucket = yPtScale(d.Sepal_Length);
if (!ptsObj[d.Species]){
ptsObj[d.Species] = {};
}
if (!ptsObj[d.Species][yBucket]){
ptsObj[d.Species][yBucket] = [];
}
ptsObj[d.Species][yBucket].push({
cy: yPtScale(d.Sepal_Length) * r,
cx: x(d.Species)
});
});
// determine the x position
for (var x in ptsObj){
for (var row in ptsObj[x]) {
var v = ptsObj[x][row], // array of points
m = v[0].cx, // mid-point
l = m - (((v.length / 2) * r) - r/2); // left most position based on count of points in the bucket
v.forEach(function(d,i){
d.cx = l + (r * i); // x position
});
}
}
// flatten the data structure
var flatData = Object.values(ptsObj)
.map(function(d){return Object.values(d)})
.flat(2);
svg
.selectAll("points")
.data(flatData)
.enter()
.append("circle")
.attr("cx", function(d) {
return d.cx;
})
.attr("cy", function(d) {
return d.cy;
})
.attr("r", 4)
.style("fill", "white")
.attr("stroke", "black")
})
</script>
</body>
</html>
I'm having a problem getting the axes to actually show on my bar graph, so far without any luck as I just can't seem to wrap my head around what's wrong. Is it something to do with the scaling?
Is the axis rendering but being cut out of the svg by the bars?
<script type="text/javascript">
//Width and height
var w = 850;
var h = 650;
var barpadding = 20;
var dataset = [40, 99];
//Create SVG element
var svg = d3.select(".wrapper")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create scale functions
var x = d3.scaleBand()
.range([0, w])
.padding(0.1);
var y = d3.scaleLinear()
.range([h, 0]);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function (d, i) {
return i * (w / dataset.length);
})
.attr("y", function (d) {
return h - (d * 4);
})
.attr("width", w / dataset.length - barpadding)
.attr("height", function (d) {
return d * 4;
})
.attr("fill", "dodgerblue");
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function (d) {
return d;
})
.attr("text-anchor", "middle")
.attr("x", function (d, i) {
return i * (w / dataset.length) + (w / dataset.length - barpadding) / 2;
})
.attr("y", function (d) {
return h - (d * 4) + 14;
})
.attr("font-family", "sans-serif")
.attr("font-size", "18px")
.attr("fill", "black");
d3.select(".axis")
.call(d3.axisBottom(x));
</script>
For showing the axis, you'll have to append a <g> element first. After that, since the axes are always generated at the origin (0,0), you'll have to translate it down and, only then, calling the axis:
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + someValue + ")")
.call(d3.axisBottom(x));
I normally provide a working demo, but this time I'll skip it, because your code has some problems:
It lacks the margins for the axis
It lacks the domains of the scales
It position the bars using magic numbers, not the scales
But, if you want to see your code with the axis 20px above the bottom, this is how it looks like: https://jsfiddle.net/qcnako3g/
I'm trying to plot this continuous function with D3 js version 4 but i'm having many problems with arrays. this is my javascript code:
var x = d3.range(-4., 4.1, 0.1)
var fnorm = x => (1. / Math.sqrt(2 * Math.PI)) * Math.exp(-0.5 * Math.pow(x, 2))
var y = new Array()
for (var i = 0 ; i < x.length ; i++) {
y[i] = fnorm(x[i])
}
var dataset = []
for (var j = 0; j < x.length; j++) {
dataset[j] = []
dataset[j][0] = x[j]
dataset[j][1] = y[j]
}
console.log(dataset[0])
console.log(dataset[1])
var w = 500
var h = 500
var padding = 50
var xScale = d3.scaleLinear()
.domain([d3.min(x, function(d) { return d }), d3.max(x, function(d) { return d })])
.range([padding, w - padding * 2])
var yScale = d3.scaleLinear()
.domain([0, 0.4])
.range([h - padding, padding])
function mycanvas() {
var svg = d3.select('body')
.append('svg')
.attr('width', w)
.attr('height', h)
svg.append('rect')
.attr('width', '100%')
.attr('height', '100%')
.attr('fill', 'blue')
// Define the axis
var xAxis = d3.axisBottom().scale(xScale).ticks(9)
var yAxis = d3.axisLeft().scale(yScale).ticks(9)
// Create the axis
svg.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(0,' + (h - padding) + ')')
.call(xAxis)
svg.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(' + padding + ',0)')
.call(yAxis)
svg.selectAll('line')
.data(dataset)
.enter()
.append('line')
.attr('x1', function(d) {
return xScale(d[0])
})
.attr('y1', function(d) {
return yScale(d[1])
})
.attr('x2', function(d) {
return xScale(d[1])
})
.attr('y2', function(d) {
return yScale(d[0])
})
.attr('stroke', 'white')
}
function main() {
mycanvas()
}
window.onload = main
<script src="https://d3js.org/d3.v4.min.js"></script>
The main problem is when i try to plot with svg line i don't know how to fix the right way to call and apply dataset array with x1,y1,x2,y2
You could use d3.line to produce a path from dataset:
var line = d3.line()
.x(function(d) { return xScale(d[0]);})
.y(function(d) { return yScale(d[1]);});
svg.append("path")
.attr("d", line(dataset))
.attr("stroke", "white")
.attr("fill", "none");
Demo: https://jsfiddle.net/LukaszWiktor/kkxe5sbc/
I would like to add a line on my horizontal bar chart something like the image, the line should represent 270 on x-axis in this case, but I get the error invalid path attribute. Here is the plunker code:
var info = [
{name: "Walnuts", value:206},
{name: "Almonds", value:332}
];
/* Set chart dimensions */
var width = 960,
height = 500,
margin = {top:10, right:10, bottom:20, left:60};
//subtract margins
width = width - margin.left - margin.right;
height = height - margin.top - margin.bottom;
//sort data from highest to lowest
info = info.sort(function(a, b){ return b.value - a.value; });
//Sets the y scale from 0 to the maximum data element
var max_n = 0;
var category = []
for (var d in info) {
max_n = Math.max(info[d].value, max_n);
category.push(info[d].name)
}
var dx = width / max_n;
var dy = height / info.length;
var y = d3.scale.ordinal()
.domain(category)
.rangeRoundBands([0, height], .1);
var x = d3.scale.linear()
.range([0, width]);
var yAxis = d3.svg.axis()
.scale(y)
.orient('left')
var svg = d3.select("#chart")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr('preserveAspectRatio', 'xMidYMin')
.attr("viewBox", '0 0 ' + parseInt(width + margin.left + margin.right) + ' ' + parseInt(height + margin.top + margin.bottom))
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.selectAll(".bar")
.data(info)
.enter()
.append("rect")
.attr("class", function(d, i) {return "bar" + d.name;})
.attr("x", function(d, i) {return 0;})
.attr("y", function(d, i) {return dy*i;})
.attr("width", function(d, i) {return dx*d.value})
.attr("height", dy)
.attr("fill", function(d, i){
if(d.name == 'Walnuts') {return 'red'} else {return 'green'}
});
var y_xis = svg.append('g')
.attr('id','yaxis')
.call(yAxis);
var lineEnd = 270;
var line = d3.svg.line()
line
.x(function(d, i) {
return x(d.value) + i; })
.y(function(d, i) { return lineEnd; });
svg.append("path")
.datum(info)
.attr("class", "line")
.attr("d", line);
You don't need d3.svg.line() here. Just create a simple line:
var lineEnd = 270;
var line = svg.append("line")
.attr("x1", lineEnd)
.attr("x2", lineEnd)
.attr("y1", 0)
.attr("y2", height)
.attr("stroke-width", 8)
.attr("stroke", "black")
.attr("stroke-dasharray", "8,8");
This is the plunker: http://plnkr.co/edit/dOhZjRvBHzFqWFByerKH?p=preview
PS: This is not 270 on x-axis, this is simply 270px on the SVG. Right now you cannot use x as a scale because there is no domain. Set a domain for x and use it to set the width of your bars.
First, get rid of this:
var max_n = 0;
var category = []
for (var d in info) {
max_n = Math.max(info[d].value, max_n);
category.push(info[d].name)
}
var dx = width / max_n;
var dy = height / info.length;
Now, set the scales:
var y = d3.scale.ordinal()
.domain(info.map(function(d){ return d.name}))
.rangeRoundBands([0, height], .1);
var x = d3.scale.linear()
.range([0, width])
.domain([0, d3.max(info, function(d){return d.value})])
And then use these scales for your bars:
.attr("x", 0)
.attr("y", function(d){ return y(d.name)})
.attr("width", function(d) {return x(d.value)})
.attr("height", y.rangeBand())
With all these corrected, now we can use 270 in the scale:
var line = svg.append("line")
.attr("x1", function(){ return x(lineEnd)})
.attr("x2", function(){ return x(lineEnd)})
.attr("y1", 0)
.attr("y2", height)
.attr("stroke-width", 6)
.attr("stroke", "black")
.attr("stroke-dasharray", "8,8")
Here is the updated plunker: http://plnkr.co/edit/gtPA12qSf9mBoAY6MeDd?p=preview