I am trying to add a regression line to a scatter plot. I am using the below code as an example for scatter plot.
http://bl.ocks.org/majetisiri/57da501b3182bd08d17402261c7187f7
I am appending the path to svg as explained here:
Plot regression line on a scatter plot from regression coefficients
But the regression line is not visible.
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<!-- Example based on http://bl.ocks.org/mbostock/3887118 -->
<!-- Tooltip example from http://www.d3noob.org/2013/01/adding-tooltips-to-d3js-graph.html -->
<style>
body {
font: 11px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.dot {
stroke: #000;
}
.tooltip {
position: absolute;
width: 200px;
height: 28px;
pointer-events: none;
}
h1 {
text-align: center;
}
h2 {
text-align: left;
}
</style>
<body>
<p><span><label for="y-axis">Select y-axis</label></span>
<select id="y-value">
<option value="FLFPR">Female LFPR</option>
<option value="lnGDP">Log GDP per capita</option>
<option value="Fertility">Fertility rate</option>
</select>
<p><span><label for="x-axis">Select x-axis</label></span>
<select id="x-value">
<option value="FLFPR">Female LFPR</option>
<option value="lnGDP">Log GDP per capita</option>
<option value="Fertility">Fertility rate</option>
</select>
<button onclick="setGraph()">submit</button>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
<script src="https://unpkg.com/d3-regression#1.2.3/dist/d3-regression.min.js"></script>
<script>
function drawGraph(xText, yText) {
$('svg').remove();
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
/*
* value accessor - returns the value to encode for a given data object.
* scale - maps value to a visual display encoding, such as a pixel position.
* map function - maps from data value to display value
* axis - sets up axis
*/
// setup x
var xValue = function(d) { return d[xText];}, // data -> value
xScale = d3.scale.linear().range([0, width]), // value -> display
xMap = function(d) { return xScale(xValue(d));}, // data -> display
xAxis = d3.svg.axis().scale(xScale).orient("bottom");
// setup y
var yValue = function(d) { return d[yText];}, // data -> value
yScale = d3.scale.linear().range([height, 0]), // value -> display
yMap = function(d) { return yScale(yValue(d));}, // data -> display
yAxis = d3.svg.axis().scale(yScale).orient("left");
// setup fill color
var cValue = function(d) { return d.IG;},
color = d3.scale.category20();
// add the graph canvas to the body of the webpage
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 + ")");
// add the tooltip area to the webpage
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// load data
d3.csv("scatter2.csv", function(error, data) {
// change string (from CSV) into number format
data.forEach(function(d) {
d[yText] = +d[yText];
d[xText] = +d[xText];
//console.log (d.School);
//console.dir (d);
});
// don't want dots overlapping axis, so add in buffer to data domain
xScale.domain([d3.min(data, xValue)-1, d3.max(data, xValue)+1]);
yScale.domain([d3.min(data, yValue)-1, d3.max(data, yValue)+1]);
// x-axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", -6)
.style("text-anchor", "end")
.text(xText);
// y-axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text(yText);
// draw dots
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", 6.6)
.attr("opacity", 0.9)
.style("stroke", function(d) { return color(cValue(d));})
.attr("cx", xMap)
.attr("cy", yMap)
.style("fill", function(d) { return color(cValue(d));})
.on("mouseover", function(d) {
tooltip.transition()
.duration(200)
.style("opacity", .9);
tooltip.html(d["Player"] + "<br/> " + d.School + "<br/>(" + xValue(d)
+ ", " + yValue(d) + ")")
.style("left", (d3.event.pageX + 10) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
});
// draw legend
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(10," + (i+7) * 20 + ")"; });
// draw legend colored rectangles
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
// draw legend text
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d;})
// get regression coefficients
regData = data.map(item => ({x: item[xText], y: item[yText]}));
res = drawRegressionLine(regData)
console.log("regression results")
console.log(res)
var line = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate("linear");
// var line = d3.svg.line()
// .x(function(d) { return xScale(d['x']); })
// .y(function(d) { return yScale(d['y']); });
// var lineFunction = d3.svg.line()
// .x(function(d) { return d.x; })
// .y(function(d) { return d.y; })
// .interpolate("linear");
var regLine = svg.append("path")
.datum(res)
.attr("d", line)
.style("stroke", "steelblue")
.style("stroke-width", "6px");
// var lineGraph = svg.append("path")
// .attr("d", line(res))
// .attr("stroke", "blue")
// .attr("stroke-width", 2)
// .attr("fill", "black");
});
}
// draw regression line
function drawRegressionLine(regData) {
console.log("beginning")
console.log("inside draw regression lilne")
linearRegression = d3.regressionLinear()
.x(d => d.x)
.y(d => d.y);
res = linearRegression(regData)
return res;
}
// drawGraph('Passing TD', 'Rushing TD');
function setGraph() {
console.log("inside set graph")
console.log($('#x-value').val())
drawGraph($('#x-value').val(), $('#y-value').val());
}
</script>
</body>
</html>
Please help me find what is wrong with the code.
It looks like you are not passing the your linear regression into your x and y scale.
Try:
var line = d3.svg.line()
.x(function(d) { return xScale(d[0]); })
.y(function(d) { return yScale(d[1]); })
.interpolate("linear");
Related
I have drawn the following chart with D3 Charting tool v4. I have attached the full code at the bottom of this post.
The red line is the target goal to be achieved. The following code block is drawing this line:
var targetGoalArr = [7];
svg.selectAll(".targetgoal")
.data(targetGoalArr)
.enter().append("line")
.attr("class", "targetgoal")
.attr("x1", 0)
.attr("x2", width)
.attr("y1", y)
.attr("y2", y)
.style("stroke", "#cc0000");
Now I need to label this line with the text Growth Target (7) to the right of it and in two lines. The label has to be broken in two lines as well!
The following screenshot shows the desired output.
How can I achieve the above?
One more thing I am not able to draw is the Y-Axis baseline. In my chart (with red line) I am creating the horizontal lines using a custom tick array. Here is the code:
function draw_yAxis_gridlines() {
return d3.axisLeft(y)
.tickValues(yTicks);
}
svg.append("g")
.attr("class", "grid axis")
.call(draw_yAxis_gridlines()
.tickSize(-width)
);
However, if I do not use custom ticks for Y-Axis, the baseline appears but I am missing the horizontal grid lines. I have to display both at the same time.
Here is my full code:
public function evd_unitary_growth_plan_chart( $data_str ){
ob_start(); ?>
<style> /* set the CSS */
.line {
fill: none;
stroke: steelblue;
stroke-width: 2px;
}
.grid line {
stroke: lightgrey;
stroke-opacity: 0.5;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
}
.axis {
font-size: 13px;
font-family: 'Roboto';
color: #808888;
}
</style>
<script type="text/javascript">
var h = 300;
var w = 750;
var barPadding = 2;
function barColor(data_month, current_month) {
if( parseInt(data_month) >= current_month)
return "#008600";
else
return "#c4c4c4";
}
// set the dimensions and margins of the graph
var margin = {top: 30, right: 20, bottom: 30, left: 40},
width = w - margin.left - margin.right,
height = h - margin.top - margin.bottom;
var data = <?php echo $data_str ?>;
// set the ranges
var x = d3.scaleBand().range([0, width]).padding(0.2);
var y = d3.scaleLinear().range([height, 0]);
var svg = d3.select("#ecbg_unitary").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 + ")");
// Scale the range of the data in the domains
x.domain(data.map(function(d) { return d.month; }));
var y_domain_upperBound = d3.max(data, function(d) { return d.points; });
y_domain_upperBound = Math.round(y_domain_upperBound / 10) * 10 + 10;
y.domain([0, y_domain_upperBound]);
// Create Y-Axis tick array to draw grid lines
var yTicks = [];
var tickInterval = 5;
for(var i = 0; i <= y_domain_upperBound; i = i + tickInterval) {
yTicks.push(i);
}
console.log(yTicks);
// gridlines in y axis function
function draw_yAxis_gridlines() {
return d3.axisLeft(y)
.tickValues(yTicks);
}
// Reference line - The red line
var targetGoalArr = [7];
svg.selectAll(".targetgoal")
.data(targetGoalArr)
.enter().append("line")
.attr("class", "targetgoal")
.attr("x1", 0)
.attr("x2", width)
.attr("y1", y)
.attr("y2", y)
.style("stroke", "#cc0000");
// append the rectangles for the bar chart
svg.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("x", function(d) {
return x(d.month);
})
.attr("width", x.bandwidth())
.attr("y", function(d) { return y(d.points); })
.attr("height", function(d) { return height - y(d.points); })
.attr("fill", function(d){return barColor(d.data_month_number, d.current_month_number)});
// column labels
svg.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function(d) {
return d.points;
})
.attr("text-anchor", "middle")
.attr("x", function(d, i) {
return x(d.month) + x.bandwidth() / 2;
})
.attr("y", function(d) {
return y(d.points) - 10;
})
.attr("font-family", "Roboto")
.attr("font-size", "13px")
.attr("font-weight", "bold")
.attr("fill", "#606668");
// add the x Axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// add the Y gridlines
svg.append("g")
.attr("class", "grid axis")
.call(draw_yAxis_gridlines()
.tickSize(-width)
);
</script>
<?php return ob_get_clean();
}
To add a label to your target line, you are best to create group (g) element, and then append a line and text element to it. The g element can be translated to the correct y position, so that the line and text can be positioned relatively to the g.
var targetGoalArr = [7];
var target = g.selectAll(".targetgoal")
.data(targetGoalArr)
.enter()
.append("g")
.attr("transform", function(d){
return "translate(0, " + y(d) +")"
})
target.append("line")
.attr("class", "targetgoal")
.attr("x1", 0)
.attr("x2", width)
.attr("y1", 0) //these can be omitted
.attr("y2", 0)
.style("stroke", "#cc0000");
target.append("text")
.text(function(d){ return "Target growth: " + d })
.attr("x", width)
.attr("y", "0.35em")
I am trying to follow this example here for a D3 stacked chart. I've tested it locally and it works fine.
I have adapted the code to match my csv dataset, but unfortunately I get issues with the calculation of y and height attributes:
Error: attribute y: Expected length, "NaN".
Error: attribute height: Expected length, "NaN".
Here is my adapted source code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Enterprise Elements Analysis - In/Out of Scope</title>
<script src="http://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<style type="text/css">
svg {
font: 10px sans-serif;
shape-rendering: crispEdges;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
}
path.domain {
stroke: none;
}
.y .tick line {
stroke: #ddd;
}
</style>
</head>
<body>
<script type="text/javascript">
// Our D3 code will go here
var ratData = [];
d3.csv("./etcounts.csv", function(d) {
return {
type: d.type,
in_scope: +d.in_scope,
out_scope: +d.out_scope
};
}, function(error, rows) {
data = rows;
console.log(data);
createVisualization();
});
function createVisualization() {
// Setup svg using with margins
var margin = {bottom: 75, left: 15, right: 85};
var w = 200 - margin.left - margin.right;
var h = 175 - margin.bottom;
// get length of Array
var arrayLength = data.length; // length of dataset
var x_axisLength = 100; // length of x-axis in our layout
var y_axisLength = 100; // length of y-axis in our layout
var svg = d3.select("body")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + ",10)");
// set up the properties for stack
var stack = d3.stack()
.keys(["In Scope", "Out Scope"])
.order(d3.stackOrderDescending)
.offset(d3.stackOffsetNone);
// transpose your data using stack
var series = stack(data);
// view the stack
console.log(series);
// setup the Y scale
var yScale = d3.scaleLinear()
.domain([0, d3.max(series, function(d) {
return d3.max(d, function(d) {
return d[1];
});
})])
.range([h, 0]);
// Set some colors into an array
var colors = ["#dfd6d6", "#d85f41"]; // choose colors
// Create groups for each series, rect elements for each segment
var groups = svg.selectAll("g.type")
.data(series)
.enter().append("g")
.attr("class", "type")
.style("fill", function(d, i) {
return colors[i]; // color the rectangles
});
// Create the rectangles
var rect = groups.selectAll("rect")
.data(function(d) {
return d;
})
.enter()
.append("rect")
.attr("x", function(d,i) {
return i * (x_axisLength/arrayLength) + 30; // Set x coordinate of rectangle to index of data value (i) *25
})
.attr("y", function(d) {
return yScale(d[1]); // set base of rectangle
})
.attr("height", function(d) {
return yScale(d[0]) - yScale(d[1]); // set height of rectangle
})
.attr("width", (x_axisLength/arrayLength) - 1) // set width of rectangle
.on("mouseover", function() {
tooltip.style("display", null); // hide tooltip
})
.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.data.city + ": " + (d[1] - d[0])); // populate tooltip
})
.on("mouseout", function() {
tooltip.style("display", "none");
});
// Draw legend
var legend = svg.selectAll(".legend")
.data(colors)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(" + i * 50 + ", 110)"; });
legend.append("rect")
.attr("x", w - 70)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d, i) {return colors.slice().reverse()[i];});
legend.append("text")
.attr("x", w - 49)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "start")
.text(function(d, i) {
switch (i) {
case 0: return "In";
case 1: return "Out";
}
});
// Prep the tooltip bits, initial display is hidden
var tooltip = svg.append("g")
.attr("class", "tooltip")
.style("display", "none");
tooltip.append("text")
.attr("x", 15)
.attr("dy", "1.2em")
.style("text-anchor", "middle")
.attr("font-size", "12px");
// Create y-axis
svg.append("line")
.attr("x1", 30)
.attr("y1", 0)
.attr("x2", 30)
.attr("y2", 100)
.attr("stroke-width", 2)
.attr("stroke", "black");
// y-axis label
svg.append("text")
.attr("class", "y label")
.attr("text-anchor", "middle")
.text("Elements")
.attr("transform", "translate(20, 50) rotate(-90)")
.attr("font-size", "14px")
.attr("font-family", "'Open Sans', sans-serif");
// Create x-axis
svg.append("line")
.attr("x1", 30)
.attr("y1", 100)
.attr("x2", 130)
.attr("y2", 100)
.attr("stroke-width", 2)
.attr("stroke", "black");
}
</script>
</body>
</html>
My Dataset (etcounts.csv) is here:
type,in_scope,out_scope
ERKRS,1,1
KKBER,6,5
KOKRS,1,31
BUKRS,78,143
VKORG,23,13
BWKEY,51,6
EKORG,5,6
WERKS,51,65
LGORT,9,180
SPART,9,3
VTWEG,2,0
PERSA,47,73
Unfortunately my D3/JS skills are not quite up to par, but I would appreciate any help. Thanks - John
Instead of
var stack = d3.stack()
.keys(["In Scope", "Out Scope"]) <-- there is no key as such
.order(d3.stackOrderDescending)
.offset(d3.stackOffsetNone);
it should have been:
var stack = d3.stack()
.keys(["in_scope", "out_scope"])
.order(d3.stackOrderDescending)
.offset(d3.stackOffsetNone);
Reason: there is no keys in your CSV "In Scope", "Out Scope"
It should have been "in_scope", "out_scope"
EDIT
For tool tip :
tooltip.select("text").text(d.data.city + ": " + (d[1] - d[0]));
should have been
tooltip.select("text").text(d.data.type + ": " + (d[1] - d[0]));
Reason: There is no data.city in your CSV.
working code here
I'm new to d3, but pretty familiar with the HighCharts api.
I've seen lots of examples of multiple d3 charts on the same page; but can't seem to find examples of one chart overlaying/sitting directly on top of another chart. Is this possible?
With HighCharts, you can define multiple chart types in the plotOptions config object. Is there something similar with d3? Or, how could you do this with d3?
I would effectively like to have a line graph on top of a bar chart. There will be different 'stages' according to the data, so some of the bar's could be inactive/empty.
Additionally, I need to display an indicator to show where the 'stage' is currently; and ensure that this is all responsive.
Example (rough mockup):
After researching d3 and looking for similar examples, I am thinking that maybe d3 isn't the best choice for this; maybe a custom CSS/JS/HTML solution (inside an angular app) would be better.
Any recommendations or pointers would be very appreciated.
Here's a quick mock-up started from this excellent bar chart example:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.point rect {
fill: steelblue;
}
.point circle {
fill: orange;
}
.point rect:hover {
fill: brown;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: orange
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 75, right: 20, bottom: 30, left: 40},
width = 600 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
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 + ")");
var data = "abcdefghijklmnopqrstuvwxyz".split("").map(function(d){
return {
letter: d,
bar: Math.random() * 10,
line: Math.random() * 10
};
})
x.domain(data.map(function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d3.max([d.bar, d.line]); })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text");
var points = svg.selectAll(".point")
.data(data)
.enter().append("g")
.attr("class", "point");
points.append('rect')
.attr("x", function(d) { return x(d.letter); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.bar); })
.attr("height", function(d) { return height - y(d.bar); });
points.append('circle')
.attr("r", 5)
.attr("cx", function(d){ return x(d.letter) + x.rangeBand() / 2; })
.attr("cy", function(d){ return y(d.line)});
var line = d3.svg.line()
.x(function(d) { return x(d.letter) + x.rangeBand() / 2; })
.y(function(d) { return y(d.line); });
svg.append("path")
.attr("class", "line")
.datum(data)
.attr("d", line);
var indicator = svg.append("g")
.attr("r", 5)
.attr("transform", "translate(" + (x("q") + x.rangeBand() / 2) + "," + -20 + ")");
indicator.append("circle")
.attr("r", 40)
.style("fill", "red");
indicator.append("text")
.text("!")
.style("fill", "white")
.style("text-anchor", "middle")
.style("alignment-baseline", "middle")
.style("font-size", 70);
indicator.append("line")
.attr("y1", 20)
.attr("y2", height + 20)
.attr("x1", 0)
.attr("x2", 0)
.style("stroke", "red")
.style("stroke-width", "4px");
</script>
New Solution Based on Comments
Given your input data, here's a new example. I went a bit overboard here, so please ask question on any confusing bits. I tried to comment it out:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
rect {
fill: steelblue;
}
circle {
fill: orange;
}
rect:hover {
fill: brown;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: orange
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = {
top: 75,
right: 20,
bottom: 30,
left: 40
},
width = 600 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
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 + ")");
// here's your data
var data =
{
'point1': [{
'value': 50
}, {
'value': 100
}, {
'value': 100
}, {
'value': 150
}],
'point2': [{
'value': 25
}, {
'value': 40
}, {
'value': 60
}],
'point3': [{
'value': 25
}]
};
// d3ify your data
// d3 likes arrays of objects, you have an object of objects
// so first make it an array
var barData = d3.entries(data);
// set x domain
x.domain(barData.map(function(d){ return d.key }));
// create lineData
var lineData = [];
barData.forEach(function(d0, i){
d0.mean = d3.mean(d0.value, function(d1){ return d1.value });
d0.max = d3.max(d0.value, function(d1){ return d1.value});
var N = d0.value.length,
// this is an inner scale
// that represents each bar
s = d3.scale.linear().range([
x(d0.key) + (x.rangeBand() / N) / 2,
x(d0.key) + x.rangeBand()
]).domain([
0, N
])
d0.value.forEach(function(d1, j){
lineData.push({
x: s(j), // this is the pixel position of x, it's jittered on the bar
y: d1.value // this is the user position of y
})
});
});
// set y domain
y.domain([0, d3.max(barData, function(d) {
return d.max;
})]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text");
// draw bars
var bars = svg.selectAll(".bar")
.data(barData)
.enter()
.append('rect')
.attr('class', 'bar')
.attr("x", function(d) {
return x(d.key);
})
.attr("width", x.rangeBand())
.attr("y", function(d) {
return y(d.mean);
})
.attr("height", function(d) {
return height - y(d.mean);
});
// add points
var points = svg.selectAll('point')
.data(lineData)
.enter()
.append('circle')
.attr('class', 'point')
.attr("r", 5)
.attr("cx", function(d) {
return d.x; // already pixel position
})
.attr("cy", function(d) {
return y(d.y)
});
var line = d3.svg.line()
.x(function(d) {
return d.x; // already pixel position
})
.y(function(d) {
return y(d.y);
});
svg.append("path")
.attr("class", "line")
.datum(lineData)
.attr("d", line);
var indicator = svg.append("g")
.attr("transform", "translate(" + (x("point2") + x.rangeBand() / 2) + "," + -20 + ")");
indicator.append("circle")
.attr("r", 40)
.style("fill", "red");
indicator.append("text")
.text("!")
.style("fill", "white")
.style("text-anchor", "middle")
.style("alignment-baseline", "middle")
.style("font-size", 70);
indicator.append("line")
.attr("y1", 20)
.attr("y2", height + 20)
.attr("x1", 0)
.attr("x2", 0)
.style("stroke", "red")
.style("stroke-width", "4px");
</script>
Happy d3ing!
I am trying to combine severeal D3.js examples based on example. I managed to get mouseover for each multiples chart in part working (values are not displayed at mouse pointer yet but via console.log). By checking those values I realized that my line paths at the upper two charts are off in relation to the Y-Axis, also causing the mouseover focus to be in the wrong place. I am new to D3, so I am still having trouble to pin down the problem beeing caused by domain/scale/axis etc. You can see the example here
This is my code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
margin: 0;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
//shape-rendering: crispEdges;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
.area {
//fill: #e7e7e7;
fill: transparent;
}
.overlay {
fill: none;
pointer-events: all;
}
.focus circle {
fill: none;
stroke: steelblue;
}
</style>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
var margin = {top: 8, right: 10, bottom: 20, left: 30},
width = 960 - margin.left - margin.right,
height = 138 - margin.top - margin.bottom;
var parseDate = d3.time.format("%b %Y").parse,
bisectDate = d3.bisector(function(d) { return d.date; }).left,
formatValue = d3.format(",.2f"),
formatCurrency = function(d) { return formatValue(d); };
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var area = d3.svg.area()
.x(function(d) { return x(d.date); })
.y0(height)
.y1(function(d) { return y(d.price); });
var line = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.price); });
var xAxis = d3.svg.axis()
.scale(x) // x is the d3.time.scale()
.orient("bottom") // the ticks go below the graph
.ticks(4); // specify the number of ticks
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(4);
d3.csv("stocks_chart2.csv", type, function(error, data) {
// Nest data by symbol.
var symbols = d3.nest()
.key(function(d) { return d.symbol; })
.entries(data);
// Compute the maximum price per symbol, needed for the y-domain.
symbols.forEach(function(s) {
s.maxPrice = d3.max(s.values, function(d) { return d.price; });
});
// Compute the minimum and maximum date across symbols.
// We assume values are sorted by date.
x.domain([
d3.min(symbols, function(s) { return s.values[0].date; }),
d3.max(symbols, function(s) { return s.values[s.values.length - 1].date; })
]);
// Add an SVG element for each symbol, with the desired dimensions and margin.
var svg = d3.select("body").selectAll("svg")
.data(symbols)
.enter().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 + ")");
// Add the area path elements. Note: the y-domain is set per element.
svg.append("path")
.attr("class", "area")
.attr("d", function(d) { y.domain([0, d.maxPrice]); return area(d.values); });
// Add the line path elements. Note: the y-domain is set per element.
svg.append("path")
.attr("class", "line")
.attr("d", function(d) { y.domain([0, d.maxPrice]); return line(d.values); });
// Add a small label for the symbol name.
svg.append("text")
.attr("x", width - 6)
.attr("y", height - 6)
.style("text-anchor", "end")
.text(function(d) { return d.key; });
svg.append('g') // create a <g> element
.attr('class', 'x axis') // specify classes
.attr("transform", "translate(0," + height + ")")
.call(xAxis); // let the axis do its thing
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Value");
var focus = svg.append("g")
.attr("class", "focus")
.style("display", "none");
focus.append("circle")
.attr("r", 4.5);
focus.append("text")
.attr("x", 9)
.attr("dy", ".35em");
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() { focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", mousemove);
function mousemove() {
var date, index;
date = x.invert(d3.mouse(this)[0]);
index = 0;
var focus = svg.selectAll(".focus");
focus.attr("transform", function(d) {
index = bisectDate(d.values, date, 0, d.values.length - 1);
console.log(index, d.values[index].symbol, d.values[index].date, d.values[index].price);
return "translate(" + x(d.values[index].date) + "," + y(d.values[index].price) + ")"
});
focus.selectAll("text", function(d) {
return formatCurrency(d.values[index].price);
});
}
});
function type(d) {
d.price = +d.price;
d.date = parseDate(d.date);
return d;
}
</script>
How do I assign the correct Y-Axis to each individual multiples chart causing the line path and mouseover values to be at the correct position? Any help would be greatly appreciated! Thank you!
This is an interesting problem. The example you link to uses a single y scale and yAxis for all 4 sub-plots. In your situation, though, your data has a very different domain for each sub-plot and when you add the dynamic mouse over a shared scale just won't work. So, my solution would be to create a different y scale and yAxis for each subplot.
...
// variable to hold our scales
var ys = {};
var area = d3.svg.area()
.x(function(d) {
return x(d.date);
})
.y0(height)
.y1(function(d) {
return ys[d.symbol](d.price); //<-- call the y function matched to our symbol
});
var line = d3.svg.line()
.x(function(d) {
return x(d.date);
})
.y(function(d, i) {
return ys[d.symbol](d.price); //<-- call the y scale function matched to our symbol
});
...
// for each symbol create our scale
symbols.forEach(function(s) {
var maxPrice = d3.max(s.values, function(d) {
return d.price;
});
ys[s.key] = d3.scale.linear() //<-- create a scale for each "symbol" (ie Sensor 1, etc...)
.range([height, 0])
.domain([0, maxPrice]);
});
...
// build 4 y axis
var axisGs = svg.append("g"); //<-- create a collection of axisGs
axisGs
.attr("class", "y axis")
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Value");
axisGs.each(function(d, i) { //<-- for each axisG create an axis with it's scale
var self = d3.select(this);
self.call(
d3.svg.axis()
.scale(ys[d.key])
.orient("left")
.ticks(4)
);
});
...
// adjust mouseover to use appropriate scale
focus.attr("transform", function(d) {
index = bisectDate(d.values, date, 0, d.values.length - 1);
console.log(index, d.values[index].symbol, d.values[index].date, d.values[index].price);
return "translate(" + x(d.values[index].date) + "," + ys[d.key](d.values[index].price) + ")"; //<-- finally in our mouse move use the appropriate scale
});
Fully working code here.
As far as best practices are concerned when you are dealing with n number of datasets you have to go for n number of y scales and their corresponding y axis. It is good for seperation of concern and keeps the visulization intact. here you can see the example.
http://grafitome.github.io/advanced-charts.html#(first chart)
I am using the D3 toolkit to create a stacked bar graph of some data. I want link the bars to a separate page with more detail about the underlying data for that bar. I can't figure out how to turn the bars into hyperlinks. The code below is based on a demo that I have been tinkering with. When I inspect the code in Google Chrome it looks correct but the bars aren't clickable. Tremendous thanks in advance for ideas and suggestions on this!
Source, attempting to add link to Google to each bar:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
}
.x.axis path {
display: none;
}
</style>
<body>
<script src="d3.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.rangeRound([height, 0]);
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".2s"));
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 + ")");
d3.csv("./data.csv", function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "State"; }));
data.forEach(function(d) {
var y0 = 0;
d.ages = color.domain().map(function(name) { return {name: name, y0: y0, y1: y0 += +d[name]}; });
d.total = d.ages[d.ages.length - 1].y1;
});
data.sort(function(a, b) { return b.total - a.total; });
x.domain(data.map(function(d) { return d.State; }));
y.domain([0, d3.max(data, function(d) { return d.total; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Population");
var state = svg.selectAll(".state")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("xlink:href", function(d) { return "http://www.google.com"; })
.attr("transform", function(d) { return "translate(" + x(d.State) + ",0)"; });
state.selectAll("rect")
.data(function(d) { return d.ages; })
.enter().append("rect")
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.y1); })
.attr("height", function(d) { return y(d.y0) - y(d.y1); })
.style("fill", function(d) { return color(d.name); });
state.append("a")
.attr("xlink:href", "http://www.google.com")
;
var legend = svg.selectAll(".legend")
.data(color.domain().slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
});
</script>
I'm not sure that you can add "a" html mark in SVG.
But you can add an event on each bar segment:
state.on('click',function(d){
... some great code ...
})