Related
lineFunction corresponds in this example with d3.svg.line(). Later, however, lineFunction is filled as a function with a parameter lineData, namely a list of points with x and y coordinates. How can I bypass lineFunction and include the dataset directly in d3.svg.line()?
My approach would be to call directly on d3.svg.line(lineData):
//The line SVG Path we draw
var lineGraph = svg.append("path")
.attr("d", d3.svg.line(lineData)
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate('linear'))
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("fill", "none");
But this does not make any sense, as long as this is no function, that accepts parameters. I've also looked into the D3 code base and found that the line function does accept an input:
export default function() {
var x = pointX,
// ...
function line(data) {
// ...
}
// ...
return line;
}
Here is a running example by Dimitar Danailov:
var width = 400;
var height = 400;
var svg = d3.select('body').append('svg');
svg.attr('width', width);
svg.attr('height', height);
//This is the accessor function we talked about above
var lineFunction = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate('linear');
//The data for our line
var lineData = [
{ "x": 1, "y": 5},
{ "x": 20, "y": 20},
{ "x": 40, "y": 10},
{ "x": 60, "y": 40},
{ "x": 80, "y": 5},
{ "x": 100, "y": 60}
];
//The line SVG Path we draw
var lineGraph = svg.append("path")
.attr("d", lineFunction(lineData))
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("fill", "none");
svg {
font-family: "Helvetica Neue", Helvetica;
}
.line {
fill: none;
stroke: #000;
stroke-width: 2px;
}
<script src="//d3js.org/d3.v3.min.js"></script>
Source: https://bl.ocks.org/dimitardanailov/6f0a451d4457b9fa7bf6e0dddcd0f468
Further examples: https://www.dashingd3js.com/svg-paths-and-d3js
What you can do is call d3.svg.line() after you've configured it, like:
var lineGraph = svg.append("path")
.attr("d", d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate('linear')(lineData))
So the revised snippet looks like this:
var width = 400;
var height = 400;
var svg = d3.select('body').append('svg');
svg.attr('width', width);
svg.attr('height', height);
//The data for our line
var lineData = [
{ "x": 1, "y": 5},
{ "x": 20, "y": 20},
{ "x": 40, "y": 10},
{ "x": 60, "y": 40},
{ "x": 80, "y": 5},
{ "x": 100, "y": 60}
];
//The line SVG Path we draw
var lineGraph = svg.append("path")
.attr("d", d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate('linear')(lineData))
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("fill", "none");
svg {
font-family: "Helvetica Neue", Helvetica;
}
.line {
fill: none;
stroke: #000;
stroke-width: 2px;
}
<script src="//d3js.org/d3.v3.min.js"></script>
Edit: Note that the question and answer uses d3.svg.line() which is from d3 v3. For higher versions you can use d3.line(), and omit the interpolate, like that mentioned below by #uzay95.
I am trying to get some data in my map, however I have the following error:
Uncaught TypeError: Cannot read property 'length' of undefined.
This is my code:
<!DOCTYPE html>
<meta charset="utf-8">
<style> /* set the CSS */
body { font: 12px Arial;}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
div.tooltip {
position: absolute;
text-align: center;
width: 60px;
height: 28px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
/* pointer-events: none; This line needs to be removed */
}
</style>
<body>
<!-- load the d3.js library -->
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script>
// Set the dimensions of the canvas / graph
var w = window.innerWidth,
h = window.innerHeight,
margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.time.format("%d-%b-%y").parse;
var formatTime = d3.time.format("%e %B");// Format tooltip date / time
// We're passing in a function in d3.max to tell it what we're maxing (x value)
var x = d3.scale.linear()
.domain([0, d3.max(data, function (d) { return d.x + 10; })])
.range([margin.left, w - margin.right]); // Set margins for x specific
// We're passing in a function in d3.max to tell it what we're maxing (y value)
var y = d3.scale.linear()
.domain([0, d3.max(data, function (d) { return d.y + 10; })])
.range([margin.top, h - margin.bottom]); // Set margins for y specific
// Add a X and Y Axis (Note: orient means the direction that ticks go, not position)
var xAxis = d3.svg.axis().scale(x).orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(5);
// Define the line
var valueline = d3.svg.line()
.x(function(d) { return x(d.x); })
.y(function(d) { return y(d.y); });
// Define 'div' for tooltips
var div = d3.select("body")
.append("div") // declare the tooltip div
.attr("class", "tooltip") // apply the 'tooltip' class
.style("opacity", 0); // set the opacity to nil
// Adds the svg canvas
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 + ")");
// Get the data
var datajson = '[ { x: 100, y: 110 }, { x: 83, y: 43 }, { x: 92, y: 28 }, { x: 49, y: 74 }, { x: 51, y: 10 }, { x: 25, y: 98 }, { x: 77, y: 30 }, { x: 20, y: 83 }, { x: 11, y: 63 }, { x: 4, y: 55 }, { x: 0, y: 0 }, { x: 85, y: 100 }, { x: 60, y: 40 }, { x: 70, y: 80 }, { x: 10, y: 20 }, { x: 40, y: 50 }, { x: 25, y: 31 } ]';
var data = JSON.parse(datajson);
data.forEach(function(d) {
d.x = d.x;
d.y = +d.y;
});
// Add the valueline path.
svg.append("path")
.attr("class", "line")
.attr("d", valueline(data));
// draw the scatterplot
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 5)
.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return y(d.y); })
// Tooltip stuff after this
.on("mouseover", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
div.transition()
.duration(200)
.style("opacity", .9);
div .html(
'<a href= "http://google.com">' + // The first <a> tag
d.x +
"</a>" + // closing </a> tag
"<br/>" + d.y)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
});
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
</script>
</body>
It goes wrong at line 59:
var y = d3.scale.linear()
.domain([0, d3.max(data, function (d) { return d.y + 10; })])
I am trying to plot line chart with points.
Firstly your JSON string should be like:
var datajson = '[ { "x": 100, "y": 110 },
{ "x": 83, "y": 43 }, { "x": 92, "y": 28 },
{ "x": 49, "y": 74 },
{ "x": 51, "y": 10 }, { "x": 25, "y": 98 },
{ "x": 77, "y": 30 }, { "x": 20, "y": 83 },
{ "x": 11, "y": 63 }, { "x": 4, "y": 55 },
{ "x": 0, "y": 0 }, { "x": 85, "y": 100 },
{ "x": 60, "y": 40 }, { "x": 70, "y": 80 },
{ "x": 10, "y": 0 }, { "x": 40, "y": 50 },
{ "x": 25, "y": 31 } ]';
Note the double codes(") on the key.
Next
You are setting the domain like:
var x = d3.scale.linear()
.domain([0, d3.max(data, function(d) {
return d.x + 10;
})])
.range([margin.left, w - margin.right]); //
But data is defined much below this line.
So please move the below lines above:
var data = JSON.parse(datajson);
data.forEach(function(d) {
d.x = d.x;
d.y = +d.y;
});
working code here
I would like to seek help how to set the starting point of a hexagon shape in progress animation. Based on my screenshot my issue is that my code is always start at first half of the hexagon shape.
I tried to changed my hexagon data but the issue still not in the correct starting point of the animation.
You can check my code below what's wrong. And thanks in advance.
<html>
<head>
<title>Demo</title>
</head>
<body>
<div id="animation"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.10/d3.min.js"></script>
<script>
/*https://codepen.io/MarcBT/pen/wcLCe/*/
var h = (Math.sqrt(3)/2.2),
radius = 100,
xp = 200,
yp = 200,
hexagonData = [
{ "x": (radius/2+xp) , "y": (-radius*h+yp)},
{ "x": -radius/2+xp, "y": -radius*h+yp},
{ "x": -radius+xp, "y": yp},
{ "x": -radius/2+xp, "y": radius*h+yp},
{ "x": radius/2+xp, "y": radius*h+yp},
{ "x": radius+xp, "y": yp},
];
drawHexagon =
d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate("cardinal-closed")
.tension(".1")
var svgContainer =
d3.select("body")
.append("svg")
.attr("width", 600)
.attr("height", 600);
var svgContainer = d3.select("#animation") //create container
.append("svg")
.attr("width", 1000)
.attr("height", 1000);
var path = svgContainer.append('path')
.attr('d', drawHexagon(hexagonData))
//.attr('d',line(pointData))
.attr('stroke', "#E2E2E1")
.attr('stroke-width', '4')
.attr('fill', 'none');
var totalLength = path.node().getTotalLength();
var percent = 100;
console.log(totalLength);
path
.attr("stroke-dasharray", totalLength + " " + totalLength)
.attr("stroke-dashoffset", totalLength)
//.transition()
//.duration(2000)
//.ease("linear")
.attr("stroke-dashoffset",0);
var path1 = svgContainer.append('path')
.attr('d', drawHexagon(hexagonData))
.attr('stroke', "green")
.attr('stroke-width', '4')
.attr('fill', 'none');
var totalLength = path1.node().getTotalLength();
var percent = -30;
//console.log(totalLength);
path1
.attr("stroke-dasharray", totalLength + " " + totalLength)
.attr("stroke-dashoffset",totalLength )
// .attr("fill", "rgba(255,0,0,0.4)")
.transition()
.duration(2000)
.attr("stroke-dashoffset", ((100 - percent)/100) *totalLength)
</script>
</body>
</html>
Here's a fiddle of what you are trying to do : https://jsfiddle.net/udsnbb5m/2/
The animation is starting at the first point of your hexagon. However your points aren't the vertices of your hexagon. You can change that like that:
d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate("cardinal-closed")
.tension("0.90") // Previously 0.1
Okay, the animation starts properly.
Now we have to change the hexagon orientation. You can modify directly the points coordinates. (That's what I did)
hexagonData = [
{ "x": xp, "y": -radius+yp}, //5
{ "x": -radius*h+xp , "y": -radius/2+yp}, //4
{ "x": (-radius*h+xp) , "y": (radius/2+yp)}, //3
{ "x": xp , "y": radius+yp}, //2
{ "x": radius*h+xp , "y": radius/2+yp}, //1
{ "x": radius*h+xp , "y": -radius/2+yp}, //6
];
Note that you probably can do it with this attribute:
.attr('transform','rotate(-45)');
I have drawn four svg rect diagram...with different colors....
I'm trying to add node inside of those svg rect...but I'm getting only the svg rect without node inside them...I know that I have done something wrong with data...
But I'm not able to figure them out..I'm just one month old to D3..please advice me on this....
If you run the code below you can see my mistake....
<!DOCTYPE html>
<html>
<head>
<script src="../D3/d3.min.js"></script>
</head>
<body>
<style>
</style>
<script type="text/javascript">
var width = 500,
height = 500;
var nodes = [
{ x: width / 3, y: height / 2 }
//{ x: 2 * width / 3, y: height / 1 },
//{ x: 3 * width / 3, y: height / 2 },
//{ x: 4 * width / 3, y: height / 2 }
];
var force = d3.layout.force()
.size([width, height])
.nodes(nodes)
var svgcontainer = d3.select("body")
.append("svg")
.attr("width", 1000)
.attr("height", 900);
var rectdata = [{ "x": 50, "y": 70, "width": 600, "height": 150,"rx":80,"ry":80,"fill":"skyblue"},
{ "x": 50, "y": 260, "width": 200, "height": 400, "rx": 80, "ry": 90, "fill": "palegreen" },
{ "x": 440, "y": 260, "width": 200, "height": 400, "rx": 80, "ry": 90, "fill": "orange" },
{ "x": 50, "y": 700, "width": 600, "height": 150, "rx": 80, "ry": 80, "fill": "brown" }];
var svgrect = svgcontainer.selectAll("rect").data(rectdata).enter().append("rect");
var node = svgcontainer.selectAll('.node')
.data(nodes)
.enter().append('rect')
.attr('class', 'node');
force.on('end', function () {
svgrect.attr("x", function (d, i) { return d.x; })
.attr("y", function (d, i) { return d.y; })
.attr("rx", function (d, i) { return d.rx; })
.attr("ry", function (d, i) { return d.ry; })
.attr("width", function (d, i) { return d.width; })
.attr("height", function (d, i) { return d.height; })
.attr("fill", function (d, i) { return d.fill; });
});
force.start();
</script>
</body>
</html>
You aren't giving the node any attributes. I take it you want a circle and not a rect :
var node = svgcontainer.selectAll('.node')
.data(nodes)
.enter().append('circle')
.attr('class', 'node')
.attr('x', function(d){ console.log(d); return d.x})
.attr('y', function(d){ return d.y})
.attr('r', 10)
.attr('transform', function(d){
return 'translate(' + d.x + ', ' + d.y + ')'
})
Notice the translate at the bottom. If you are using the force layout in D3, the tick function should take care of this, but it looks like you don't have one, so you have to insert it after you create the nodes.
If you didn't want circles and wanted rectangles then this should do :
var nodeRect = svgcontainer.selectAll('.nodeRect')
.data(nodes)
.enter().append('rect')
.attr('class', 'nodeRect')
.attr('x', function(d){ console.log(d); return d.x})
.attr('y', function(d){ return d.y})
.attr('width', 100)
.attr('height', 50)
.attr('transform', function(d){
return 'translate(' + d.x + ', ' + d.y + ')'
})
Working fiddle with both : https://jsfiddle.net/reko91/n13kqvw9/
var width = 500,
height = 500;
var nodes = [
{ x: width / 3, y: height / 2 }
//{ x: 2 * width / 3, y: height / 1 },
//{ x: 3 * width / 3, y: height / 2 },
//{ x: 4 * width / 3, y: height / 2 }
];
var force = d3.layout.force()
.size([width, height])
.nodes(nodes)
var svgcontainer = d3.select("body")
.append("svg")
.attr("width", 1000)
.attr("height", 900);
var rectdata = [{ "x": 50, "y": 70, "width": 600, "height": 150,"rx":80,"ry":80,"fill":"skyblue"},
{ "x": 50, "y": 260, "width": 200, "height": 400, "rx": 80, "ry": 90, "fill": "palegreen" },
{ "x": 440, "y": 260, "width": 200, "height": 400, "rx": 80, "ry": 90, "fill": "orange" },
{ "x": 50, "y": 700, "width": 600, "height": 150, "rx": 80, "ry": 80, "fill": "brown" }];
var svgrect = svgcontainer.selectAll("rect").data(rectdata).enter().append("rect");
var node = svgcontainer.selectAll('.node')
.data(nodes)
.enter().append('circle')
.attr('class', 'node')
.attr('x', function(d){ console.log(d); return d.x})
.attr('y', function(d){ return d.y})
.attr('r', 10)
.attr('transform', function(d){
return 'translate(' + d.x + ', ' + d.y + ')'
})
var nodeRect = svgcontainer.selectAll('.nodeRect')
.data(nodes)
.enter().append('rect')
.attr('class', 'nodeRect')
.attr('x', function(d){ console.log(d); return d.x})
.attr('y', function(d){ return d.y})
.attr('width', 100)
.attr('height', 50)
.attr('transform', function(d){
return 'translate(' + d.x + ', ' + d.y + ')'
})
force.on('end', function () {
svgrect.attr("x", function (d, i) { return d.x; })
.attr("y", function (d, i) { return d.y; })
.attr("rx", function (d, i) { return d.rx; })
.attr("ry", function (d, i) { return d.ry; })
.attr("width", function (d, i) { return d.width; })
.attr("height", function (d, i) { return d.height; })
.attr("fill", function (d, i) { return d.fill; });
});
force.start();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I have a graph for which I need a reference line everywhere the mouse-cursor is inside this graph. And this reference line will follow the mouse movements inside the graph.
But this doesn't seems to work fine. It works only on the axis and the ticks (.axis lines) of the axis. On debugging, I found that mouse event works fine when applied over SVG but not on the group, why so ?
Here is my code :
test.html
<html>
<head>
<script src="jquery.js">
</script>
<script src="d3.v2.js">
</script>
<script src="retest.js">
</script>
<style type="text/css">
.g_main {
cursor:pointer;
}
.axis path, .axis line {
stroke: #DBDBDB;
/*shape-rendering: crispEdges;
*/
}
.y g:first-child text {
display:none;
}
.y g:first-child line {
stroke: #989898 ;
stroke-width: 2.5px;
}
/*.x g:first-child line {
stroke: black ;
stroke-width: 2.5px;
}
*/
.y path {
stroke: #989898 ;
stroke-width: 2.5px;
}
</style>
</head>
<body>
<center>
<button id="reload" onclick="loadViz();">
load Graph
</button>
<div id="viz" class="viz">
</div>
</center>
<script>
loadViz();
</script>
</body>
</html>
retest.js
var series,
classifications,
minVal,
maxVal,
svgW = 600,
svgH = 600,
//w = 1200,
//h = 1200,
vizPadding = {
top: 120,
right: 30,
bottom: 120,
left: 50
},
yAxMin_PA = 0,
yAxMax_PA = 50,
xAxMin_PA = 2002,
xAxMax_PA = 2008,
areaStrokeColors = ['#FF6600', '#3366FF', '#B8860B', '#458B00', 'white'];
var loadViz = function () {
color = d3.scale.category10();
data = {
"lines": [{
"line": [{
"X": 2002,
"Y": 42
}, {
"X": 2003,
"Y": 45
},
{
"X": 2005,
"Y": 47
},
{
"X": 2007,
"Y": 41
}
]
}, {
"line": [{
"X": 2003,
"Y": 33
}, {
"X": 2005,
"Y": 38
}, {
"Y": 36,
"X": 2008
}
]
}, {
"line": [{
"X": 2004,
"Y": 13
}, {
"X": 2005,
"Y": 19
}, {
"X": 2008,
"Y": 21
}
]
}, {
"line": [{
"X": 2003,
"Y": 20
}, {
"X": 2005,
"Y": 27
}, {
"X": 2008,
"Y": 29
}
]
}
]
};
$("#viz").html("");
buildBase();
//setScales();
};
var buildBase = function () {
margin = {
top: 80,
right: 120,
bottom: 40,
left: 40
},
width = 960 - margin.left - margin.right,
height = 550 - margin.top - margin.bottom;
t2 = height + margin.top + margin.bottom;
x = d3.scale.linear()
.domain([xAxMin_PA, xAxMax_PA])
.range([0, width]);
y = d3.scale.linear()
.domain([yAxMin_PA, yAxMax_PA])
.range([height, 0]);
tickSizeToApplyX = 5;
tickSizeToApplyY = 10;
// Function to draw X-axis
xAxis = d3.svg.axis()
.scale(x)
.ticks(tickSizeToApplyX)
.tickSize(-height, 0, 0)
//.tickSize(10)
.orient("bottom")
.tickPadding(5);
// Function to draw Y-axis
yAxis = d3.svg.axis()
.scale(y)
.ticks(tickSizeToApplyY)
.tickSize(-width, 0, 0)
//.tickSize(0)
.orient("left")
.tickPadding(5);
// Define the line
var valueline = d3.svg.line()
.x(function (d) { /*console.log(d.X);*/
return x(d.X);
})
.y(function (d) { /*console.log(d.Y);*/
return y(d.Y);
});
// Define the line
var referline = d3.svg.line()
.x(function (dx) { /*console.log(d.X);*/
return dx;
})
.y(function (dy) { /*console.log(d.Y);*/
return dy;
});
// Append SVG into the html
var viz = d3.select("#viz")
.append("svg")
.attr("width", width + margin.left + margin.right + 10)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("class", "g_main")
.attr("transform", "translate(" + margin.left + "," + ((margin.top) - 30) + ")");
viz.on("mousemove", function () {
cx = d3.mouse(this)[0];
cy = d3.mouse(this)[1];
console.log("xx=>" + cx + "yy=>" + cy);
redrawline(cx, cy);
})
.on("mouseover", function () {
d3.selectAll('.line_over').style("display", "block");
})
.on("mouseout", function () {
d3.selectAll('.line_over').style("display", "none");
});
//console.log(this);
viz.append("line")
//d3.select("svg").append("line")
.attr("class", 'line_over')
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", x(xAxMax_PA))
.attr("y2", 0)
.style("stroke", "gray")
.attr("stroke-dasharray", ("5,5"))
.style("stroke-width", "1.5")
.style("display", "none");
// Draw X-axis
viz.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Draw Y-axis
viz.append("g")
.attr("class", function (d, i) {
return "y axis"
})
.call(yAxis);
function redrawline(cx, cy) {
d3.selectAll('.line_over')
.attr("x1", 0)
.attr("y1", cy)
.attr("x2", x(xAxMax_PA))
.attr("y2", cy)
.style("display", "block");
}
};
The g element is just an empty container which cannot capture click events (see documentation for pointer-events property for details).
However, mouse events do bubble up to it. Hence, the effect you desire can be achieved by first making sure that the g receives all pointer events:
.g_main {
// ..
pointer-events: all;
}
And then appending an invisible rectangle to it as a place to hover over:
viz.on("mousemove", function () {
cx = d3.mouse(this)[0];
cy = d3.mouse(this)[1];
redrawline(cx, cy);
})
.on("mouseover", function () {
d3.selectAll('.line_over').style("display", "block");
})
.on("mouseout", function () {
d3.selectAll('.line_over').style("display", "none");
})
.append('rect')
.attr('class', 'click-capture')
.style('visibility', 'hidden')
.attr('x', 0)
.attr('y', 0)
.attr('width', width)
.attr('height', height);
Working example: http://jsfiddle.net/H3W3k/
As for why they work when applied to the svg element (from the docs):
Note that the ‘svg’ element is not a graphics element, and in a Conforming SVG Stand-Alone File a rootmost ‘svg’ element will never be the target of pointer events, though events can bubble to this element. If a pointer event does not result in a positive hit-test on a graphics element, then it should evoke any user-agent-specific window behavior, such as a presenting a context menu or controls to allow zooming and panning of an SVG document fragment.