I have problem with adding the axis to stem plot. I'm using D3.js in version 3.
I draw my own stems that consist of circles and lines.
I have two scenarios:
1.
1.1 First I add the stems
1.2 Then I add axis
The Y-axis is plotted on the stem. On the following image the magenta line covers green one (I want the opposite, stem should cover axis)
2
2.1 First I add axis
2.2 Then I add the stems
The lines of stems are not plotted.
I need someone to explain me why the lines are not drawn.
File js/stem-functions.js
var svgParams = {
// Graph field
graphWidth : 200,
graphHeight : 120,
// Margins
leftPadding : 30,
rightPadding : 10,
upPadding : 15,
downPadding : 25,
}
function setParametersSvg() {
// Size of the SVG object
this.svgWidth = this.graphWidth + this.leftPadding + this.rightPadding;
this.svgHeight = this.graphHeight + this.upPadding + this.downPadding;
}
setParametersSvg.apply(svgParams);
// Create Scale functions
var xScale = (function ustawSkaleX(minX, maxX, svgParam) {
var xSc = d3.scale.linear()
.domain([minX, maxX])
.range([svgParam.leftPadding, svgParam.leftPadding + svgParam.graphWidth]);
return xSc;
} (0, 5, svgParams) );
var yScale = (function ustawSkaleY(minY, maxY, svgParam) {
var ySc = d3.scale.linear()
.domain([minY, maxY])
.range([svgParam.upPadding + svgParam.graphHeight, svgParam.upPadding]);
return ySc;
} (0, 0.5, svgParams) );
function addAxis(svg, svgParam, xScale, yScale) {
// Functions drawing axis X
var xAxis = d3.svg.axis();
xAxis.scale(xScale) // Scale function for X
.orient("bottom") // Location of label
.ticks(7); // Ticks
// Add group
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (svgParam.svgHeight - svgParam.downPadding) +")")
.call(xAxis);
// Functions drawing axis Y
var yAxis = d3.svg.axis();
yAxis.scale(yScale) // Scale function for Y
.orient("left") // Location of label
.ticks(4); // Ticks
// Add group
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + (svgParam.leftPadding) + ", 0)")
.call(yAxis);
}
function addStems(svg, dataset, xScale, yScale) {
var circles = svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("fill", "green")
.attr("cx", function(d) { return xScale(d.x); } )
.attr("cy", function(d) { return yScale(d.n); } )
.attr("r", 4);
var lines = svg.selectAll("line")
.data(dataset)
.enter()
.append("line")
.attr("class", "stem-line")
.attr("stroke", "green")
.attr("stroke-width", "1")
.attr("x1", function(d) { return xScale(d.x); } )
.attr("x2", function(d) { return xScale(d.x); } )
.attr("y1", function(d) { return yScale(0); } )
.attr("y2", function(d) { return yScale(d.n); } );
}
File js/stem-examples.js
// Data set
var p = [
{ x: 0, n: 0.15 },
{ x: 1, n: 0.25 },
{ x: 2, n: 0.40 },
{ x: 3, n: 0.15 },
{ x: 4, n: 0.05 }
];
console.log('probabilities ', p);
d3.select("body").append("h4").html("Call Stems printing before Axis printing");
// Stems before Axis => Axis is over stem
var svg1 = d3.select("body")
.append("svg")
.attr("width", svgParams.svgWidth)
.attr("height", svgParams.svgHeight);
addStems(svg1, p, xScale, yScale);
addAxis(svg1, svgParams, xScale, yScale);
d3.select("body").append("br");
d3.select("body").append("h4").html("Call Axis printing before Stems printing");
// Axis before Stems => stem lines are gone (why?)
var svg2 = d3.select("body")
.append("svg")
.attr("width", svgParams.svgWidth)
.attr("height", svgParams.svgHeight);
addAxis(svg2, svgParams, xScale, yScale);
addStems(svg2, p, xScale, yScale);
File css/styl.js
svg { border: teal 1px solid; }
.axis path, .axis line {
fill: none;
stroke: magenta;
stroke-width: 1;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 12px;
fill: DarkViolet;
}
File index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Stem plot with axis</title>
<link rel="stylesheet" href="css/styl.css">
</head>
<body>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://d3js.org/d3.v3.min.js"></script> <!-- V3 -->
<!-- TODO: try version 4 of D3.js --> <!-- V4 -->
<script src="js/stem-functions.js"></script>
<script src="js/stem-examples.js"></script>
</body>
</html>
Appending the axis first and then the stems is the correct approach. The problem is not that.
The problem is that, when you do this...
var lines = svg.selectAll("line")
.data(dataset)
.enter()
.append("line")
//etc...
... you are selecting lines that already exist in that SVG, and binding data to them.
Solution
Do this:
var lines = svg.selectAll(null)
.data(dataset)
.enter()
.append("line")
//etc...
To understand why I'm selecting null, have a look at this question/answer of mine here: Selecting null: what is the reason of using 'selectAll(null)' in D3.js?
Related
<!DOCTYPE html>
<html lang="en">
<head>
<h1> Amount of money spent on gas in a week vs Distance from work(miles)<h1/>
<meta charset="utf-8">
<title>D3: Labels removed</title>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="http://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script>
<style type="text/css">
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 11px;
}
.d3-tip {
line-height: 1;
font-weight: bold;
padding: 12px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
border-radius: 2px;
}
</style>
</head>
<body>
<script type="text/javascript">
//Width and height
var w = 1000;
var h = 610;
var padding = 50;
//Static dataset
var dataset = [
[63, 45], [60, 43], [10, 12], [95, 60], [30, 15]
];
/*
//Dynamic, random dataset
var dataset = []; //Initialize empty array
var numDataPoints = 50; //Number of dummy data points to create
var xRange = Math.random() * 1000; //Max range of new x values
var yRange = Math.random() * 1000; //Max range of new y values
for (var i = 0; i < numDataPoints; i++) { //Loop numDataPoints times
var newNumber1 = Math.floor(Math.random() * xRange); //New random integer
var newNumber2 = Math.floor(Math.random() * yRange); //New random integer
dataset.push([newNumber1, newNumber2]); //Add new number to array
}
*/
//Create scale functions
var xScale = d3.scale.linear()
.domain([0, 100]) // This is what is written on the Axis: from 0 to 100
.range([0, w]);
var yScale = d3.scale.linear()
.domain([0, 100 ]) // This is what is written on the Axis: from 0 to 100
.range([h, 0]);
var rScale = d3.scale.linear()
.domain([5, 5])
.range([5, 5]);
//Define X axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(5);
//Define Y axis
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create circles
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.attr("r", function(d) {
return rScale(d[1]);
});
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<strong>dataset:</strong> <span style='color:red'>" + d.dataset + "</span>";})
svg.selectAll("circle")
svg.selectAll("circle")
.data(dataset)
.enter().append("rect")
.attr("class", "circle")
.attr("x", function(d) { return x(d.dataset); })
.attr("width", 0)
.attr("y", function(d) { return y(d.dataset); })
.attr("height", function(d) { return height - y(d.dataset); })
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
//X AND Y text
svg.append("text")
.attr("class", "y label")
.attr("text-anchor", "end")
.attr("x", -175)
.attr("y", 12)
.attr("transform", "rotate(-90)")
.text("Distance from work (Miles) ")
.attr("font-size",'12pt')
.attr("font-weight","bold");
svg.append("text")
.attr("class", "y label")
.attr("text-anchor", "end")
.attr("x", 700)
.attr("y", 600)
.text("Amount of money spent on gas in a week ")
.attr("font-size",'12pt')
.attr("font-weight","bold");
/*
//Create labels
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d[0] + "," + d[1];
})
.attr("x", function(d) {
return xScale(d[0]);
})
.attr("y", function(d) {
return yScale(d[1]);
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "red");
*/
//Create X axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
//Create Y axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + padding + ",0)")
.call(yAxis);
function type(d) {
d.dataset = +d.dataset;
return d;
}
</script>
</body>
</html>
Hello im trying to add a tool tip to my circles. When I run the code I dont get an error in my console which makes me think that it works but it does not. I think im missing something simple but I am in general not sure. Would really appreciate any help on this issue. the var.tip part is where I think the biggest problem is at
There are a few problems in the code:
the tooltip plugin should be bound to the data visualization, as documented in the quick usage instructions:
/* Invoke the tip in the context of your visualization */
vis.call(tip)
In your case, svg.call(tip) should be executed before the creation of the circles.
For some reason, the mouseover and mouseout event listeners are attached to some rectangles (classed circle), instead of being attached to the circles.
The tooltips are filled using d.dataset, which is inexistent, hence undefined. Assuming that you want to display the x, y coordinates, d may be used instead.
The snippet below illustrates the fixes.
P.S. You may want to use a more recent version of d3 and of the tooltip plugin, in order to benefit from latest bug fixes and other enhancements.
//Width and height
var w = 1000;
var h = 610;
var padding = 50;
//Static dataset
var dataset = [
[63, 45], [60, 43], [10, 12], [95, 60], [30, 15]
];
//Create scale functions
var xScale = d3.scale.linear()
.domain([0, 100]) // This is what is written on the Axis: from 0 to 100
.range([0, w]);
var yScale = d3.scale.linear()
.domain([0, 100 ]) // This is what is written on the Axis: from 0 to 100
.range([h, 0]);
var rScale = d3.scale.linear()
.domain([5, 5])
.range([5, 5]);
//Define X axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(5);
//Define Y axis
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var tip = d3.tip()
//.attr('class', 'd3-tip')
//.offset([-10, 0])
.html(function(d) {
return "<strong>dataset:</strong> <span style='color:red'>" + d + "</span>";})
svg.call(tip)
//Create circles
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.attr("r", function(d) {
return rScale(d[1]);
})
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
svg.selectAll("circle")
.data(dataset)
.enter().append("rect")
.attr("class", "circle")
.attr("x", function(d) { return x(d.dataset); })
.attr("width", 0)
.attr("y", function(d) { return y(d.dataset); })
.attr("height", function(d) { return height - y(d.dataset); })
//X AND Y text
svg.append("text")
.attr("class", "y label")
.attr("text-anchor", "end")
.attr("x", -175)
.attr("y", 12)
.attr("transform", "rotate(-90)")
.text("Distance from work (Miles) ")
.attr("font-size",'12pt')
.attr("font-weight","bold");
svg.append("text")
.attr("class", "y label")
.attr("text-anchor", "end")
.attr("x", 700)
.attr("y", 600)
.text("Amount of money spent on gas in a week ")
.attr("font-size",'12pt')
.attr("font-weight","bold");
//Create X axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
//Create Y axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + padding + ",0)")
.call(yAxis);
function type(d) {
d.dataset = +d.dataset;
return d;
}
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 11px;
}
.d3-tip {
line-height: 1;
font-weight: bold;
padding: 12px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
border-radius: 2px;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="http://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script>
My making progress with my graph, it seems to mostly work. However I'm struggling to put my data values on the x axis. In this case, there should be 3 x axis labels, (test1, test2,test3).
// Data
var dataset = [{
name: "test1",
y: 0.1
},
{
name: "test2",
y: 0.6
},
{
name: "test3",
y: 0.9
}
];
It seems to just label it by how many entries there are (0,1,2) rather than using the name. What I tried was changing this:
var line = d3.line()
.x(function(d, i) {
return xScale(i);
To this (which I must admit was a bit of a guess).
var line = d3.line()
.x(function(d, i) {
return xScale(d.name);
Unfortunately that didn't work and I'm not sure what I can try next. Here is the full code if that helps.
http://jsfiddle.net/spadez/cfz3g4w2/
You are using the wrong scale for your x data. You have discrete data and want an ordinal scale.
var xScale = d3.scalePoint()
.domain(dataset.map(d => d.name)) // input is an array of names
.range([0, width]); // output
Running code:
// Data
var dataset = [{
name: "test1",
y: 0.1
},
{
name: "test2",
y: 0.6
},
{
name: "test3",
y: 0.9
}
];
// Count number of datapoints
var n = Object.keys(dataset).length;
// Find max of the data points for Y axis
var mymax = Math.max.apply(Math, dataset.map(function(o) {
return o.y;
}));
// 2. Use the margin convention practice
var margin = {
top: 50,
right: 50,
bottom: 50,
left: 50
},
width = window.innerWidth - margin.left - margin.right;
height = window.innerHeight - margin.top - margin.bottom;
// 5. X scale will use the index of our data
var xScale = d3.scalePoint()
.domain(dataset.map(d => d.name)) // input
.range([0, width]); // output
// 6. Y scale will use the randomly generate number
var yScale = d3.scaleLinear()
.domain([0, mymax]) // input
.range([height, 0]); // output
// 7. d3's line generator
var line = d3.line()
.x(function(d, i) {
return xScale(d.name);
}) // set the x values for the line generator
.y(function(d) {
return yScale(d.y);
}) // set the y values for the line generator
.curve(d3.curveMonotoneX) // apply smoothing to the line
// 1. Add the SVG to the page and employ #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 + ")");
// 3. Call the x axis in a group tag
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale)); // Create an axis component with d3.axisBottom
// 4. Call the y axis in a group tag
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale)); // Create an axis component with d3.axisLeft
// 9. Append the path, bind the data, and call the line generator
svg.append("path")
.datum(dataset) // 10. Binds data to the line
.attr("class", "line") // Assign a class for styling
.attr("d", line); // 11. Calls the line generator
// 12. Appends a circle for each datapoint
svg.selectAll(".dot")
.data(dataset)
.enter().append("circle") // Uses the enter().append() method
.attr("class", "dot") // Assign a class for styling
.attr("cx", function(d, i) {
return xScale(d.name)
})
.attr("cy", function(d) {
return yScale(d.y)
})
.attr("r", 5)
.on("mouseover", function(a, b, c) {
console.log(a)
this.attr('class', 'focus')
})
.on("mouseout", function() {})
.on("mousemove", mousemove);
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 x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.date > d1.date - x0 ? d1 : d0;
focus.attr("transform", "translate(" + x(d.date) + "," + y(d.close) + ")");
focus.select("text").text(d);
}
.line {
fill: none;
stroke: #ffab00;
stroke-width: 3;
}
.overlay {
fill: none;
pointer-events: all;
}
/* Style the dots by assigning a fill and stroke */
.dot {
fill: #ffab00;
stroke: #fff;
}
.focus circle {
fill: none;
stroke: steelblue;
}
<!-- Load in the d3 library -->
<script src="https://d3js.org/d3.v5.min.js"></script>
My data points and the values in the scaleBand y axis are not aligned. I am not able to align them properly, when I read the documentation, saw that by default the alignment is 0.5 and that's why my data points are plotted between the two points in the axis. But I tried to override the alignment my giving the alignment as 0, but there seems to be no change.
The following is my code.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title>D3 v4 - linechart</title>
<style>
#graph {
width: 900px;
height: 500px;
}
.tick line {
stroke-dasharray: 2 2 ;
stroke: #ccc;
}
.y path{
fill: none;
stroke: none;
}
</style>
</head>
<body>
<div id="graph"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.1.1/d3.min.js"></script>
<script>
!(function(){
"use strict"
var width,height
var chartWidth, chartHeight
var margin
var svg = d3.select("#graph").append("svg")
var axisLayer = svg.append("g").classed("axisLayer", true)
var chartLayer = svg.append("g").classed("chartLayer", true)
var xScale = d3.scaleLinear()
var yScale = d3.scaleBand()
var align = 0
//d3.tsv("data1.tsv", cast, main)
d3.json("http://localhost/d32.json",cast)
//データの方変換
function cast(data) {
console.log("got it");
data.forEach(function(data) {
console.log(data.Letter);
data.Letter = data.Letter;
data.Freq = +data.Freq;
});
main(data);
}
function main(data) {
console.log("in main");
setSize(data)
drawAxis()
drawChart(data)
}
function setSize(data) {
width = document.querySelector("#graph").clientWidth
height = document.querySelector("#graph").clientHeight
margin = {top:40, left:100, bottom:40, right:0 }
chartWidth = width - (margin.left+margin.right+8)
chartHeight = height - (margin.top+margin.bottom)
svg.attr("width", width).attr("height", height)
axisLayer.attr("width", width).attr("height", height)
chartLayer
.attr("width", chartWidth)
.attr("height", chartHeight)
.attr("transform", "translate("+[margin.left, margin.top]+")")
xScale.domain([0, d3.max(data, function(d) { return d.Freq; })]).range([0,chartWidth])
yScale.domain(data.map(function(d) { return d.Letter; })).range([chartHeight, 0]).align(1)
}
function drawChart(data) {
console.log("in drawChart");
var t = d3.transition()
.duration(8000)
.ease(d3.easeLinear)
.on("start", function(d){ console.log("transiton start") })
.on("end", function(d){ console.log("transiton end") })
var lineGen = d3.line()
.x(function(d) { return xScale(d.Freq) })
.y(function(d) { return yScale(d.Letter) })
.curve(d3.curveStepAfter)
var line = chartLayer.selectAll(".line")
.data([data])
line.enter().append("path").classed("line", true)
.merge(line)
.attr("d", lineGen)
.attr("fill", "none")
.attr("stroke", "blue")
.attr("stroke-width","2px")
.attr("stroke-dasharray", function(d){ return this.getTotalLength() })
.attr("stroke-dashoffset", function(d){ return this.getTotalLength() })
chartLayer.selectAll(".line").transition(t)
.attr("stroke-dashoffset", 0)
chartLayer.selectAll("circle").classed("circle",true)
.data(data)
.enter().append("circle")
.attr("class", "circle")
.attr("fill","none")
.attr("stroke","black")
.attr("cx", function(d) { return xScale(d.Freq); })
.attr("cy", function(d) { return yScale(d.Letter); })
.attr("r", 4);
chartLayer.selectAll(".logo").transition(t)
.attr("stroke-dashoffset", 0)
}
function drawAxis(){
var yAxis = d3.axisLeft(yScale)
.tickSizeInner(-chartWidth)
axisLayer.append("g")
.attr("transform", "translate("+[margin.left, margin.top]+")")
.attr("class", "axis y")
.call(yAxis);
var xAxis = d3.axisBottom(xScale)
axisLayer.append("g")
.attr("class", "axis x")
.attr("transform", "translate("+[margin.left, chartHeight+margin.top]+")")
.call(xAxis);
}
}());
</script>
</body>
</html>
The output is shown here:
The band scale is the wrong tool in this situation. The main reason is that a band scale has an associated bandwidth.
You can tweak the paddingInner and paddingOuter values of the band scale to give you the expected result. However, the easiest solution is using a point scale instead. Point scales:
...are a variant of band scales with the bandwidth fixed to zero. (emphasis mine)
So, it should be:
var yScale = d3.scalePoint()
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've got a little line graph written in d3, and I would like it to behave in such a way that one svg line path is visible when the graph is opened, and then when a user clicks a button, I would like the existing svg line path to smoothly transition into a line path representing a new and slightly different dataset.
I can transition regular attributes just fine, but it is transitioning the "d" attribute that has me stumped. The code is here
var w = 500;
var h = 300;
var padding = 30;
var dataset = [
[1.3, 2015],
[2.1, 2036.25],
[3.4, 2057.5],
[3.5, 2057.5],
[4, 2100]
];
//Create scale functions
var yScale = d3.scale.linear()
.domain([0, 4])
.range([h - padding, padding]);
var xScale = d3.scale.linear()
.domain([2015, 2100])
.range([padding, w - padding * 2]);
//Define X axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
//Define Y axis
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var lineFunc = d3.svg.line()
.x(function(d) {
return xScale(d[1]);
})
.y(function(d) {
return yScale(d[0]);
})
.interpolate('linear');
svg.append('svg:path')
.transition()
.attr('d', lineFunc(dataset))
.attr('class', 'dataline');
//Create X axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
//Create Y axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + padding + ",0)")
.call(yAxis);
//create event listener and data reset functions
d3.select("button")
.on("click", function() {
dataset = [
[1, 2015],
[2.05, 2036.25],
[3.25, 2057.5],
[3.3, 2057.5],
[3.7, 2100]
];
var dynamoBar = svg.append('svg:path')
.transition()
.attr("d", lineFunc(dataset))
.attr("class", "dataline");
})
.axis path,
.axis line {
fill: black;
stroke: none;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 11px;
}
.dataline {
stroke: purple;
stroke-width: 1;
fill: none;
}
<button id="button">Try it</button>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
When you select the line to transition with new data (after the button click), you're creating a new path:
var dynamoBar = svg.append('svg:path')...
Instead, select the existing path:
var dynamoBar = svg.select('.dataline')...
See this fiddle: http://jsfiddle.net/henbox/L2hehcry/