Reputation: 3051
I would like to put circles
at each point
of the x
and y
coordinates, as I show in this image:
how can I do it?
// Set the dimensions of the canvas / graph
var margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
// Set the ranges
var x = d3.scale.ordinal().domain(["OCT 2020",
"SEP 2020",
"AGO 2020",
"JUL 2020",
"MAY 2020",
"ABR 2020",
"MAR 2020",
"FEB 2020",
"ENE 2020"])
.rangePoints([0, width]);
var y = d3.scale.linear().range([height, 0]);
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(100);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(10);
// Define the line
var valueline = d3.svg.line().interpolate("basis")
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
// 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
let data=[
{date: "OCT 2020", close: 57370},
{date: "SEP 2020", close: 60100},
{date: "AGO 2020", close: 62530},
{date: "JUL 2020", close: 68840},
{date: "MAY 2020", close: 91470},
{date: "ABR 2020", close: 54130},
{date: "MAR 2020", close: 57960},
{date: "FEB 2020", close: 55720},
{date: "ENE 2020", close: 54360}
]
// Scale the range of the data
// x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.close; })]);
// Add the valueline path.
svg.append("path")
.attr("class", "line")
.attr("d", valueline(data));
// 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);
let circle=svg.append("circle").attr("r",2).attr("x",0).attr("y",0)
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;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
this is my code live:
http://plnkr.co/edit/ZSHvIygi7Wgp0nBc?open=lib%2Fscript.js
Upvotes: 0
Views: 83
Reputation: 108512
Just create a selection, bind your data and enter your circles:
svg.selectAll(".points")
.data(data)
.enter()
.append("circle")
.attr("r", 4)
.attr("cx", function(d){
return x(d.date);
})
.attr("cy", function(d){
return y(d.close);
});
<!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;
}
</style>
<body>
<!-- load the d3.js library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script>
// Set the dimensions of the canvas / graph
var margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
// Set the ranges
var x = d3.scale.ordinal().domain(["OCT 2020",
"SEP 2020",
"AGO 2020",
"JUL 2020",
"MAY 2020",
"ABR 2020",
"MAR 2020",
"FEB 2020",
"ENE 2020"])
.rangePoints([0, width]);
var y = d3.scale.linear().range([height, 0]);
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(100);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(10);
// Define the line
var valueline = d3.svg.line().interpolate("basis")
.x(function(d) { console.log(x(d.date),"ddd",d); return x(d.date); })
.y(function(d) { console.log(y(d.close)); return y(d.close); });
// 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
let data=[
{date: "OCT 2020", close: 57370},
{date: "SEP 2020", close: 60100},
{date: "AGO 2020", close: 62530},
{date: "JUL 2020", close: 68840},
{date: "MAY 2020", close: 91470},
{date: "ABR 2020", close: 54130},
{date: "MAR 2020", close: 57960},
{date: "FEB 2020", close: 55720},
{date: "ENE 2020", close: 54360}
]
// Scale the range of the data
// x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.close; })]);
// Add the valueline path.
svg.append("path")
.attr("class", "line")
.attr("d", valueline(data));
// 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);
svg.selectAll(".points")
.data(data)
.enter()
.append("circle")
.attr("r", 4)
.attr("cx", function(d){
return x(d.date);
})
.attr("cy", function(d){
return y(d.close);
});
</script>
</body>
But when you do this, you are going to find that the circles don't fall directly on the curve. This is because your curve is an interpolated basis spline (a curve fit to the data). If you want points directly on the curve, you'll need to extrapolate their position from the curve itself.
Borrowing from my own answer here, we can walk the curve and use getPointAtLength
to find the y position on the curve matching the x.
<!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;
}
</style>
<body>
<!-- load the d3.js library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script>
// Set the dimensions of the canvas / graph
var margin = {
top: 30,
right: 20,
bottom: 30,
left: 50
},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
// Set the ranges
var x = d3.scale.ordinal().domain(["OCT 2020",
"SEP 2020",
"AGO 2020",
"JUL 2020",
"MAY 2020",
"ABR 2020",
"MAR 2020",
"FEB 2020",
"ENE 2020"
])
.rangePoints([0, width]);
var y = d3.scale.linear().range([height, 0]);
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(100);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(10);
// Define the line
var valueline = d3.svg.line().interpolate("basis")
.x(function(d) {
console.log(x(d.date), "ddd", d);
return x(d.date);
})
.y(function(d) {
console.log(y(d.close));
return y(d.close);
});
// 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
let data = [{
date: "OCT 2020",
close: 57370
},
{
date: "SEP 2020",
close: 60100
},
{
date: "AGO 2020",
close: 62530
},
{
date: "JUL 2020",
close: 68840
},
{
date: "MAY 2020",
close: 91470
},
{
date: "ABR 2020",
close: 54130
},
{
date: "MAR 2020",
close: 57960
},
{
date: "FEB 2020",
close: 55720
},
{
date: "ENE 2020",
close: 54360
}
]
// Scale the range of the data
// x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) {
return d.close;
})]);
// Add the valueline path.
var path = svg.append("path")
.attr("class", "line")
.attr("d", valueline(data));
// 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);
svg.selectAll(".points")
.data(data)
.enter()
.append("circle")
.attr("r", 4)
.attr("cx", function(d) {
return x(d.date);
})
.attr("cy", function(d) {
return y(d.close);
});
svg.selectAll(".pointsFit")
.data(data)
.enter()
.append("circle")
.attr("r", 4)
.attr("cx", function(d) {
return x(d.date);
})
.attr("cy", function(d) {
return yValueForX(d.date);
})
.attr("fill", "red");
function yValueForX(xCor) {
var xp = x(xCor),
pathEl = path.node(),
pathLength = pathEl.getTotalLength();
var beginning = xp,
end = pathLength,
target;
while (true) {
target = Math.floor((beginning + end) / 2);
pos = pathEl.getPointAtLength(target);
if ((target === end || target === beginning) && pos.x !== xp) {
break;
}
if (pos.x > xp) end = target;
else if (pos.x < xp) beginning = target;
else break; //position found
}
return pos.y;
}
</script>
</body>
Producing this:
Upvotes: 4