I am in the process of making a simple set of boxplots on a graph, but for some reason the line elements are not showing up
Code is:
var margin = {
top: 10,
right: 30,
bottom: 30,
left: 40
},
width = 900 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var svgBox = 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 + ")");
var yScale = d3.scaleLinear()
.range([height, 0]);
var xScale = d3.scaleBand()
.rangeRound([0, width])
.paddingInner(0.05)
.align(0.1);
var center = 200
var width = 100
d3.csv("boxPlotData.csv", function(dataset) {
var max = d3.max(dataset, function(d) {
return +d.max;
});
yScale.domain([0, max])
xScale.domain(dataset.map(function(d) {
return d.borough;
}));
svgBox.append("g").call(d3.axisLeft(yScale));
svgBox.append("g").attr("transform", "translate(0," + height + ")").call(d3.axisBottom(xScale));
svgBox.selectAll("line").data(dataset).enter()
.append("line")
.attr("x1", d => xScale(d.borough) + width / 3.5)
.attr("x2", d => xScale(d.borough) + width / 3.5)
.attr("y1", d => yScale(+d.min))
.attr("y2", d => yScale(+d.max))
.attr("stroke", "black");
svgBox.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", d => xScale(d.borough) + width / 3.5)
.attr("y", d => yScale(+d.q3))
.attr("height", d => (yScale(+d.q1) - yScale(+d.q3)))
.attr("width", width)
.attr("stroke", "black")
.style("fill", "#69b3a2");
});
And my data is of the form
Data CSV
The code is entering the "rect" elements as expected, but the line components aren't showing up anywhere in the html?
The issue is you cannot just use svgBox.selectAll("line") because it will select the axis tick marks as well as the axis lines. Instead, I suggest add a class to your line with attr("class", "line") and use svgBox.selectAll(".line") to specifically select lines to append.
So the line append snippet should be:
svgBox.selectAll(".line")
.data(dataset)
.enter()
.append("line")
.attr("class", "line")
.attr("x1", d => xScale(d.borough) + width / 3.5)
.attr("x2", d => xScale(d.borough) + width / 3.5)
.attr("y1", d => yScale(+d.min))
.attr("y2", d => yScale(+d.max))
.attr("stroke", "black");
Here is the working snippet Block: https://bl.ocks.org/akulmehta/4b29fb357ea7f02a1b47b611e03a5468/
Related
I am trying to implement the following problem while learning d3.js for visualization.
Using the following titanic dataset:
Plot in scatterplot :
a)the male passengers using an SVG square (width 5, x and y - 2.5 )
b)the female passengers using a circle of radius 2.8
c) Have the survived column used as opacity such that the dead have opacity 0.25 and alive have opacity: 1;
fill-opacity:.1;
stroke: black;
Make the scatterplot axes, make the y axis to log scale, and add the passengers name on their mark (using the SVG title element).
I am implementing the following code to achieve my goals but, I have am not successful in displaying my graph.
Can anyone please help me.
The titanic dataset - here
And my code here:
// set the dimensions and margins of the graph
var margin = {
top: 20,
right: 30,
bottom: 30,
left: 40
},
width = 960 - margin.left - margin.right,
height = 500 - 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
d3.csv("https://gist.githubusercontent.com/michhar/2dfd2de0d4f8727f873422c5d959fff5/raw/fa71405126017e6a37bea592440b4bee94bf7b9e/titanic.csv", function(rawData) {
const data = rawData.map(function(d) {
return {
age: Number(d.age),
fare: Number(d.fare),
sex: d.sex,
survived: d.survived === "1",
name: d.name
};
});
// Add X axis
var x = d3.scaleLinear()
.domain([0, 80])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")");
// Add Y axis
var y = d3.scaleLog()
.domain([1e+0, 1e+3])
.range([height, 0]);
svg.append("g");
// Add dots
svg.append('g')
.selectAll("dot").select("female")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return x(d.age);
})
.attr("cy", function(d) {
return y(d.fare);
})
//.attr("r", 2.8)
.style("opacity", function(d) {
return d.survived ? "1" : "0.25";
})
.style("stroke", "black")
.style("fill-opacity", 0.1)
svg.append('g')
.selectAll("dot").select("male")
.data(data)
.enter()
.append("rect")
.attr("cx", function(d) {
return x(d.age);
})
.attr("cy", function(d) {
return y(d.fare);
})
//.attr("width", 5)
.style("opacity", function(d) {
return d.survived ? "1" : "0.25";
})
.style("stroke", "black")
.style("fill-opacity", 0.1)
.append("svg:title")
.text(function(d) {
return d.name
});
})
<script src="https://d3js.org/d3.v4.js"></script>
<div id="my_dataviz"></div>
can anyone please highlight where i am making mistake and help me please
You really, really need to read the manual, especially the SVG one. rect nodes don't have cx and cy, they have x and y, width, and height. And circle needs a radius r in order to be visible.
And you gave all the properties you read a lowercase starting letter. They need capitals. Look up a manual on debugging.
// set the dimensions and margins of the graph
var margin = {
top: 20,
right: 30,
bottom: 30,
left: 40
},
width = 960 - margin.left - margin.right,
height = 500 - 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
d3.csv("https://gist.githubusercontent.com/michhar/2dfd2de0d4f8727f873422c5d959fff5/raw/fa71405126017e6a37bea592440b4bee94bf7b9e/titanic.csv", function(rawData) {
const data = rawData.map(function(d) {
return {
age: Number(d.Age),
fare: Number(d.Fare),
sex: d.Sex,
survived: d.Survived === "1",
name: d.Name
};
});
// Add X axis
var x = d3.scaleLinear()
.domain([0, 80])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")");
// Add Y axis
var y = d3.scaleLog()
.domain([1e+0, 1e+3])
.range([height, 0]);
svg.append("g");
// Add dots
svg.append('g')
.selectAll("dot").select("female")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return x(d.age);
})
.attr("cy", function(d) {
return y(d.fare);
})
.attr("r", 2.8)
.style("opacity", function(d) {
return d.survived ? "1" : "0.25";
})
.style("stroke", "black")
.style("fill-opacity", 0.1)
svg.append('g')
.selectAll("dot").select("male")
.data(data)
.enter()
.append("rect")
.attr("x", function(d) {
return x(d.age);
})
.attr("y", function(d) {
return y(d.fare);
})
.attr("width", 5)
.attr("height", 5)
.style("opacity", function(d) {
return d.survived ? "1" : "0.25";
})
.style("stroke", "black")
.style("fill-opacity", 0.1)
.append("svg:title")
.text(function(d) {
return d.name
});
})
<script src="https://d3js.org/d3.v4.js"></script>
<div id="my_dataviz"></div>
Spent hours on this and still not really sure whats going wrong.
My plot is supposed to update based on a bunch of parameters the user selects. When the plot needs to add new data points the new points are not displayed correctly on the plot.
Check out the new plot:
With these parameters all the circles should be in a line. While the original "line" is in the correct location the new "line" does not match up with the grid.
Here is the function to make a new plot. This works fine, all the data points are where they should be.
export const newPlot = (Params) => {
d3.selectAll("svg").remove();
let margin = {top: 50, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
let x = d3.scaleLinear().range([0, width]);
let y = d3.scaleLinear().range([height, 0]);
let svg = d3.select('.plot').append("svg")
.attr('class', 'svgPlot')
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform","translate(" + margin.left + "," + margin.top + ")");
d3.json(`../assets/data/${Params.type}${Params.Year}.json`, (error, data) => {
if (error) throw error;
const refinedData = parametrize(data, Params);
refinedData.forEach((d) => {
d[Params.xSelect] = Number(d[Params.xSelect]);
d[Params.ySelect] = Number(d[Params.ySelect]);
});
let min = d3.min(refinedData,(d) => d[Params.xSelect]);
x.domain([(min - 2 <= 0 ? 0 : min - 2),
d3.max(refinedData,(d) => d[Params.xSelect])]);
y.domain([0, d3.max(refinedData,(d) => d[Params.ySelect])]);
svg.selectAll("circles")
.data(refinedData)
.enter().append("circle")
.attr('id', (d) => `${d.Player}`)
.attr("r", 5)
.attr("cx", (d) => x((d[Params.xSelect])) )
.attr("cy", (d) => y((d[Params.ySelect])) );
svg.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
svg.append("g")
.attr("class", "y-axis")
.call(d3.axisLeft(y));
svg.append('text')
.attr("class", "label")
.attr('id', 'xlabel')
.attr("transform","translate(" + (width - 20) + " ," + (height-5) + ")")
.style("fill", "white")
.style("text-anchor", "middle")
.text(`${Params.xSelect}`);
svg.append('text')
.attr("class", "label")
.attr('id', 'ylabel')
.attr("transform", "rotate(-90)")
.attr("y", 1)
.attr("x", (height/2 - 250))
.attr("dy", "1em")
.style("font-family", "sans-serif")
.style("fill", "white")
.style("text-anchor", "middle")
.text(`${Params.ySelect}`);
});
};
Here is the update function. Circles that are added are not in the correct location and are all offset by the same amount.
export const rePlot = (Params) => {
let margin = {top: 50, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
let xUp = d3.scaleLinear().range([0, width]);
let yUp = d3.scaleLinear().range([height, 0]);
let tooltip = d3.select("body").append("div")
.attr("class", "toolTip")
.style("display", "none");
let svg = d3.select('.svgPlot');
d3.json(`../assets/data/${Params.type}${Params.Year}.json`, (error, data) => {
if (error) throw error;
const refinedData = parametrize(data, Params);
refinedData.forEach((d) => {
d[Params.xSelect] = Number(d[Params.xSelect]);
d[Params.ySelect] = Number(d[Params.ySelect]);
});
let min = d3.min(refinedData,(d) => d[Params.xSelect]);
xUp.domain([(min - 2 <= 0 ? 0 : min - 2),
d3.max(refinedData,(d) => d[Params.xSelect])]);
yUp.domain([0, d3.max(refinedData,(d) => d[Params.ySelect])]);
svg.select('.x-axis')
.transition()
.duration(1000)
.call(d3.axisBottom(xUp));
svg.select('.y-axis')
.transition()
.duration(1000)
.call(d3.axisLeft(yUp));
svg.select('#xlabel')
.text(`${Params.xSelect}`);
svg.select('#ylabel')
.text(`${Params.ySelect}`);
let circle = svg.selectAll("circle")
.data(refinedData);
circle.exit()
.transition()
.remove();
circle.transition()
.duration(1000)
.attr("r", 5)
.attr("cx", (d) => xUp((d[Params.xSelect])) )
.attr("cy", (d) => yUp((d[Params.ySelect])) );
circle.enter().append("circle")
.attr('id', (d) => `${d.Player}`)
.attr("r", 5)
.attr("cx", (d) => xUp((d[Params.xSelect])) )
.attr("cy", (d) => yUp((d[Params.ySelect])) );
});
}
Your first set of circles gets appended to a group that is translated:
let svg = d3.select('.plot').append("svg")
.attr('class', 'svgPlot')
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform","translate(" + margin.left + "," + margin.top + ")");
In this case, the svg variable refers to a translated group. However, when you later reselect, you actually append to the root SVG element:
let svg = d3.select('.svgPlot');
This is the origin of the difference.
I am trying to draw a line in x-axis (bottom of bars in the chart) using the following script but it draws the on the top. What is the correct way of adding line on the bottom? Please help me to solve it.
var datasetBarChart = ${barList};
// set initial group value
var group = "All";
function datasetBarChosen(group) {
var ds = [];
for (x in datasetBarChart) {
if (datasetBarChart[x].group == group) {
ds.push(datasetBarChart[x]);
}
}
return ds;
}
function dsBarChartBasics() {
var margin = {top: 30, right: 5, bottom: 20, left: 50},
width = 1000 - margin.left - margin.right,
height = 450 - margin.top - margin.bottom,
colorBar = d3.scale.category20(),
barPadding = 1
;
return {
margin: margin,
width: width,
height: height,
colorBar: colorBar,
barPadding: barPadding
}
;
}
function dsBarChart() {
var firstDatasetBarChart = datasetBarChosen(group);
var basics = dsBarChartBasics();
var margin = basics.margin,
width = basics.width,
height = basics.height,
colorBar = basics.colorBar,
barPadding = basics.barPadding
;
var xScale = d3.scale.linear()
.domain([0, firstDatasetBarChart.length])
.range([0, width])
;
// Create linear y scale
// Purpose: No matter what the data is, the bar should fit into the svg area; bars should not
// get higher than the svg height. Hence incoming data needs to be scaled to fit into the svg area.
var yScale = d3.scale.linear()
// use the max funtion to derive end point of the domain (max value of the dataset)
// do not use the min value of the dataset as min of the domain as otherwise you will not see the first bar
.domain([0, d3.max(firstDatasetBarChart, function (d) {
return d.measure;
})])
// As coordinates are always defined from the top left corner, the y position of the bar
// is the svg height minus the data value. So you basically draw the bar starting from the top.
// To have the y position calculated by the range function
.range([height, 0])
;
//Create SVG element
var svg = d3.select("#barChart")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("id", "barChartPlot")
;
var plot = svg
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
;
var median = svg.append("line")
.attr("x2", width)
.attr("y2", (xScale/width))
.attr("stroke-width", 2)
.attr("stroke", "black");
plot.selectAll("rect")
.data(firstDatasetBarChart)
.enter()
.append("rect")
.attr("x", function (d, i) {
return xScale(i);
})
.attr("width", width / firstDatasetBarChart.length - barPadding)
.attr("y", function (d) {
return yScale(d.measure);
})
.attr("height", function (d) {
return height - yScale(d.measure);
})
.attr("fill", "lightgrey")
;
// Add y labels to plot
plot.selectAll("text")
.data(firstDatasetBarChart)
.enter()
.append("text")
.text(function (d) {
return formatAsInteger(d3.round(d.measure));
})
.attr("text-anchor", "middle")
// Set x position to the left edge of each bar plus half the bar width
.attr("x", function (d, i) {
return (i * (width / firstDatasetBarChart.length)) + ((width / firstDatasetBarChart.length - barPadding) / 2);
})
.attr("y", function (d) {
return yScale(d.measure) + 14;
})
.attr("class", "yAxis")
/* moved to CSS
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "white")
*/
;
// Add x labels to chart
var xLabels = svg
.append("g")
.attr("transform", "translate(" + margin.left + "," + (margin.top + height) + ")")
;
xLabels.selectAll("text.xAxis")
.data(firstDatasetBarChart)
.enter()
.append("text")
.text(function (d) {
return d.category;
})
.attr("text-anchor", "middle")
// Set x position to the left edge of each bar plus half the bar width
.attr("x", function (d, i) {
return (i * (width / firstDatasetBarChart.length)) + ((width / firstDatasetBarChart.length - barPadding) / 2);
})
.attr("y", 15)
.attr("class", "xAxis")
//.attr("style", "font-size: 12; font-family: Helvetica, sans-serif")
;
// Title
svg.append("text")
.attr("x", (width + margin.left + margin.right) / 2)
.attr("y", 15)
.attr("class", "title")
.attr("text-anchor", "middle")
.text Breakdown 2015")
;
}
dsBarChart();
script for the line;
var median = svg.append("line")
.attr("x2", width)
.attr("y2", (xScale/width))
.attr("stroke-width", 2)
.attr("stroke", "black");
I don't quite understand your y2 attribute. It looks like you want the line to render as <line x1="0" y1="{height}" x2="{width}" y2="{height}" />
Ideally you want to express this in terms of your scale functions so if they change you won't have to update this statement. The corresponding d3 call for that would be:
var median = svg.append("line")
.attr("stroke-width", 2)
.attr("stroke", "black")
.attr("x1", xScale.range()[0])
.attr("x2", xScale.range()[1])
.attr("y1", yScale.range()[0])
.attr("y2", yScale.range()[0]);
Also, I think something is up with the xScale/width calculation. xScale is a function. Though you should look into d3.svg.axis too
I want to assign two colors to two series in my data so that when I sort ascending or descending, the colors remain the same for each group.
The data is pulled programmatically from web map features. I thought I could get away with using an alternating function to assign colors but this doesn't work if I use sorting. I am trying to find a proper way to assign colors specifically to the series.
The data I am using has OL and NOL as two groups. You can see below how it is structured.
jsfiddle
relevant code:
var values = feature.properties;
var data = [
{name:"N11OL",value:values["N11OL"]},
{name:"N11NOL",value:values["N11NOL"]},
{name:"N21OL",value:values["N21OL"]},
{name:"N21NOL",value:values["N21NOL"]},
{name:"N22OL",value:values["N22OL"]},
{name:"N22NOL",value:values["N22NOL"]},
{name:"N23OL",value:values["N23OL"]},
{name:"N23NOL",value:values["N23NOL"]},
{name:"N31_33OL",value:values["N31_33OL"]},
{name:"N31_33NOL",value:values["N31_33NOL"]},
{name:"N41OL",value:values["N41OL"]},
{name:"N41NOL",value:values["N41NOL"]}
];
var Colors = ["#a6cee3", "#1f78b4"]
var margin = {top: 40, right: 2, bottom: 30, left: 180},
width = 400 - margin.left - margin.right,
height = 575 - margin.top - margin.bottom,
barHeight = height / data.length;
// Scale for X axis
var x = d3.scale.linear()
.domain([0, d3.max(data, function(d){return d.value;})])
.range([0, width]);
var y = d3.scale.ordinal()
.domain(["NAICS11", "NAICS21", "NAICS22", "NAICS23", "NAICS31-33", "NAICS41"])
.rangeRoundBands([0, height]);
//y axis
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.outerTickSize(0);
var svg = d3.select(div).select("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.classed("chart", true);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
var bar = svg.selectAll("g.bar")
.data(data)
.enter()
.append("g")
.attr("transform", function(d, i) { return "translate(0," + i * barHeight + ")"; });
bar.append("rect")
.attr("width", function(d){return x(d.value);})
.attr("fill", function(d, i) {return Colors[i % 2]; }) //Alternate colors
.attr("height", barHeight - 1);
bar.append("text")
.attr("class", "text")
.attr("x", function(d) { return x(d.value) - 3; })
.attr("y", barHeight / 2)
.attr("dy", ".35em")
.text(function(d) { return d.value; })
.attr("fill", "white")
.attr("font-family", "sans-serif")
.attr("font-size", "14px")
.attr("text-anchor", "end");
svg.append("text")
.attr("class", "title")
.attr("x", width/7)
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "middle")
.text("Employment by industry " + "(Total OL: " + feature.properties.IndOLTot + ")");
Starting at Line 241 in your jsFiddle
bar.append("rect")
.attr("width", function(d){return x(d.value);})
.attr("fill", function(d, i) {
if(d.name.indexOf("NOL") > -1) {
//return Colors[0];
return "red";
} else {
//return Colors[1];
return "black";
}
}) //Alternate colors
.attr("height", barHeight - 1);
This checks the name property for the substring "NOL". If the name contains "NOL" it uses the first color, if "NOL" is not found it uses the second color for a fill.
(I'm under the assumption that the series is determined by the name)
I have my data as follows:
var data = [[-5,7,10],[10,15,25],[-18,-14,-6]];
var margin = {top: 0, right: 30, bottom: 80, left: 30},
width = 180 - margin.left - margin.right,
height = 295 - margin.top - margin.bottom;
var x0 = Math.max(-d3.min(data[0]), d3.max(data[2]));
var x = d3.scale.linear()
.domain([-x0, x0])
.range([0, width]);
var y = d3.scale.ordinal()
.domain(d3.range(data.length))
.rangeRoundBands([0, height], .2);
var svg = d3.select("body").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 + ")");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.style("fill", "steelblue")
.attr("x", function(d, i) { return x(Math.min(0, d[0])); })
.attr("y", function(d, i) { return y(i); })
.attr("width", function(d, i) { return Math.abs(x(d[2]) - x(d[0])); })
.attr("height", y.rangeBand())
.append("rect");
//y axis
svg.append("g")
.attr("class", "y axis")
.append("line")
.attr("x1", x(0))
.attr("x2", x(0))
.attr("y1", 0)
.attr("y2", height+10);
What I want to do is create a bar chart where data[0] is the starting point, data[1] has a vertical line bisecting the bar and data[2] is the max value of the bar. So far, I think I'm covering the first two use cases correctly, but the third one fails. Any ideas?
http://jsfiddle.net/NPqEm/