I am currently trying to create a legend for my graph in D3 version 4. I've seen other examples where you can preset the legend with static text but I want for the legend labels to be loaded dynamically from the csv data which I am using. Here is my code:
HTML:
<div class ="graphics"> </div>
Calling the graph with JS:
var margin = {top:20, right: 20, bottom: 30, left:50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseTime = d3.timeParse("%d/%m/%Y %H:%M");
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var valueline = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.channelA); });
var valuelineTwo = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.channelB);});
//apend the svg object o the body
var svg = d3.select(".graphics").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 + ")");
//GETtheDATA
d3.csv("data.csv", function(error, data){
if (error) throw error;
data.forEach(function(d){
d.date = parseTime(d.date);
d.channelA = + d.channelA;
d.channelB = + d.channelB;
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) {return Math.max(d.channelA, d.channelB);})]);
svg.append("path")
.data([data])
.attr("class", "line")
.style("stroke", "FECB00")
.attr("d", valueline);
svg.append("path")
.data([data])
.attr("class", "line")
.attr("d", valuelineTwo);
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
svg.append("g")
.call(d3.axisLeft(y));
});
Trying to create the Legend:
var legendRectSize = 18;
var legendSpacing = 4;
var legend = svg.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize + legendSpacing;
var offset = height * color.domain().length / 2;
var horz = -2 * legendRectSize;
var vert = i * height - offset;
return 'translate(' + horz + ',' + vert + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function(d) { return d; });
I am also trying to draw a rectangle around the legend which I am not sure how to do and I cannot find any relevant examples:
Previous questions which I've checked: Example 1, Example 2, Example 3
Here is an image of how much graph looks:
Related
I'm working on an interactive graph project in django using d3js.
I'm trying to create a scatter graph in d3js that displays a tooltip when the cursor hovers over a data node.
However, I don't know how to get the name that corresponds to the pre-defined data when the cursor hovers over it.
The scatter data I'm using is
x : scatter x-coordinate
y : scatter y-coordinate
name : a unique name for the data
I want to get the value of this name key during this hover.
How can I reference the value of the original data corresponding to the data node with the key etc.?
Thank you.
My code is as follows.
<div id="dc-graph"></div>
// data from python (django)
var graph_data = JSON.parse('{{ graph|safe }}');
var num = graph_data.num;
var width = graph_data.width;
var height = graph_data.height;
var margin = graph_data.margin;
var svgWidth = width + margin.left + margin.right;
var svgHeight = height + margin.top + margin.bottom;
var data = [];
for (let i = 0; i < num; i++){
const adata = {x: graph_data.x[i], y: graph_data.y[i], name: graph_data.name[i]};
data.push(adata);
}
// svg
var svg = d3.select("#dc-graph")
.append("svg")
.attr("class", "dc-graph-svg")
.attr('width', svgWidth)
.attr('height', svgHeight);
// tooptip
var tooltip = d3.select("#dc-graph")
.append("div")
.attr("id", "dc-tooltip")
// axis
var xScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d){return d.x;})])
.range([0, width]);
var yScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d){return d.y;})])
.range([height, 0]);
var axisx = d3.axisBottom(xScale);
var axisy = d3.axisLeft(yScale);
svg.append("g")
.attr("class", "x_axis")
.attr("transform", "translate(" + margin.left + "," + (height + margin.top) + ")")
.call(axisx);
svg.append("g")
.attr("class", "y_axis")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(axisy);
// plot
svg.append("g")
.selectAll("circle")
.attr("class", "sc-scatter")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
console.log(d.x);
return xScale(d.x);
})
.attr("cy", function(d) {
console.log(d.y);
return yScale(d.y);
})
.attr("fill", "SkyBlue")
.attr("r", 4)
.on("mouseover", function(d) {
tooltip
.style("visibility", "visible")
.html("name : " + d.name + "<br>x: " + d.x + "y: " + d.y);
})
.on("mousemove", function(evnet, d) {
const[x, y] = d3.pointer(evnet);
tooltip
.style("top", (y - 20) + "px")
.style("left", (x + 10) + "px");
})
.on("mouseout", function(d) {
tooltip.style("visibility", "hidden");
});
I try to follow as following link to put labels in the groups bar chart, but it does not show up.
Anyone know what's going on my text label?
http://plnkr.co/edit/9lAiAXwet1bCOYL58lWN?p=preview&preview
Append text to Grouped Bar Chart in d3js v4
// set the dimensions and margins of the graph
var margin = { top: 10, right: 30, bottom: 40, left: 50 },
width = 700 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
const dataUrl = "https://raw.githubusercontent.com/yushinglui/IV/main/time_distance_status_v2.csv"
//fetch the data
d3.csv(dataUrl)
.then((data) => {
// append the svg object to the body of the page
var svg = d3.select("#graph-2")
.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 + ")")
// List of subgroups = header of the csv files = soil condition here
var subgroups = data.columns.slice(1)
// List of groups = species here = value of the first column called group -> I show them on the X axis
var groups = d3.map(data, function (d) { return (d.startTime) })
// Add X axis
var x = d3.scaleBand()
.domain(groups)
.range([0, width])
.padding([0.2])
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickSize(0));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 20])
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// Another scale for subgroup position?
var xSubgroup = d3.scaleBand()
.domain(subgroups)
.range([0, x.bandwidth()])
.padding([0.05])
// color palette = one color per subgroup
var color = d3.scaleOrdinal()
.domain(subgroups)
.range(['#98abc5', '#8a89a6'])
// Show the bars
svg.append("g")
.selectAll("g")
// Enter in data = loop group per group
.data(data)
.enter()
.append("g")
.attr("transform", function (d) { return "translate(" + x(d.startTime) + ",0)"; })
.selectAll("rect")
.data(function (d) { return subgroups.map(function (key) { return { key: key, value: d[key] }; }); })
.enter()
.append("rect")
.attr("x", function (d) { return xSubgroup(d.key); })
.attr("y", function (d) { return y(d.value); })
.attr("width", xSubgroup.bandwidth())
.attr("height", function (d) { return height - y(d.value); })
.attr("fill", function (d) { return color(d.key); })
//axis labels
svg.append('text')
.attr('x', - (height / 2))
.attr('y', width - 650)
.attr('transform', 'rotate(-90)')
.attr('text-anchor', 'middle')
.style("font-size", "17px")
.text('Average Distance');
svg.append('text')
.attr('x', 300)
.attr('y', width - 240)
.attr('transform', 'rotate()')
.attr('text-anchor', 'middle')
.style("font-size", "17px")
.text('Start Time');
// legend
svg.append("circle").attr("cx", 200).attr("cy", 20).attr("r", 6).style("fill", "#98abc5")
svg.append("circle").attr("cx", 300).attr("cy", 20).attr("r", 6).style("fill", "#8a89a6")
svg.append("text").attr("x", 220).attr("y", 20).text("Present").style("font-size", "15px").attr("alignment-baseline", "middle")
svg.append("text").attr("x", 320).attr("y", 20).text("Absent").style("font-size", "15px").attr("alignment-baseline", "middle")
//text labels on bars
svg.append("g")
.selectAll("g")
// Enter in data = loop group per group
.data(data)
.enter()
.append("g")
.attr("transform", function (d) { return "translate(" + x(d.startTime) + ",0)"; })
.selectAll("text")
.data(function (d) {
return [d['P'], d['ABS']];
})
.enter()
.append("text")
.attr("fill", "black")
.text(function (d) {
return formatCount(d)
})
.attr("transform", function (d, i) {
var x0 = xSubgroup.bandwidth() * i + 11,
y0 = y(d) + 8;
return "translate(" + x0 + "," + y0 + ") rotate(90)";
})
});
try this...and if possible please provide code snippet....
svg.append("text")
.attr("fill", "black")
.text(function (d) {
console.log( formatCount(d) )
return formatCount(d)
})
.attr("transform", function (d, i) {
var x0 = xSubgroup.bandwidth() * i + 11,
y0 = y(d) + 8;
return "translate(" + x0 + "," + y0 + ") rotate(90)";
})
I have an issue with D3 scatterplot where the data are not correctly plot
(plotted to 1 horizontal line rather than a scattered plot, the actual data is also scattered)
and the x-axis not able to show up.
// set the dimensions and margins of the graph
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// parse the date / time
var parseTime = d3.timeParse("%Y-%m-%dT%H:%M:%S.%L%Z");
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
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
data = data.rows;
// format the data
data.forEach(function(d) {
var momentTemp = moment(d[0]).format("YYYY-MM-DDTHH:mm:ss.SSSZ");
var parseTemp = parseTime(momentTemp);
d.date = parseTemp;
d.close += d[1];
});
// 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;
})]);
var xValue = function(d) {
return d.date;
}
var yValue = function(d) {
return d.close;
}
// Add the scatterplot
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 1.5)
.attr("cx", function(d) {
return x(d.date);
})
.attr("cy", function(d) {
return y(d.close);
});
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
svg.append("g")
.attr("transform", "translate(" + margin.left + " ,0)")
.call(d3.axisLeft(y));
That happens when your data is invalid. Are you sure the field close is correct? Your data calls the column ratio. I made some sample data and everything works:
// set the dimensions and margins of the graph
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// parse the date / time
var parseTime = d3.timeParse("%Y-%m-%dT%H:%M:%S.%L%Z");
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
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 = new Array(100)
.fill(undefined)
.map(function(d, i) {
return {
date: new Date(Number(new Date("01/01/2000")) + i * 24 * 60 * 60 * 1000),
close: Math.random(),
};
});
// 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;
})]);
var xValue = function(d) {
return d.date;
}
var yValue = function(d) {
return d.close;
}
// Add the scatterplot
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 1.5)
.attr("cx", function(d) {
return x(d.date);
})
.attr("cy", function(d) {
return y(d.close);
});
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
svg.append("g")
.attr("transform", "translate(" + margin.left + " ,0)")
.call(d3.axisLeft(y));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
I am using D3.js to create a simple line graph.
<div>
<h6>Price Over Time</h6>
<div id="priceOverTimeChart"></div>
</div>
// Set the dimensions of the canvas / graph
var margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 800 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.time.format("%d-%b-%y").parse;
// Set the ranges
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(10);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(10);
// Define the line
var valueline = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
// Adds the svg canvas
var svg = d3.select("#priceOverTimeChart")
.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
d3.csv("data.csv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
// 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);
});
The data for the line graph uses the following data format:
26-Apr-12,0.048
25-Apr-12,0.048
24-Apr-12,0.048
I would like to add an optional string to each record so it looks like:
26-Apr-12,0.048, "product 1 launch",
25-Apr-12,0.048, "product 2",
24-Apr-12,0.048, "product 3"
26-Apr-12,0.048, null
25-Apr-12,0.048, null
24-Apr-12,0.048, null
The graph would then look something like this with the labels on it:
Graph with optional labels
How can I accomplish this? Thanks in advance!
Appending texts to the corresponding x, y position will do the trick.
Please refer this working JS Fiddle
svg.append("g").selectAll("text")
.data(data)
.enter()
.append("text")
.attr("x", function(d) { return x(d.date) - paddingForText })
.attr("y", function(d) { return y(d.close) + paddingForText })
.attr("fill", "red")
.text(function(d) { return d.notes });
I am writing a code for D3 multiline chart. The 2 lines appear properly along with the tooltip for which I am using focus element on mouseover. here is the code:
var margin = {top: 50, right: 140, bottom: 50, left: 80},
width = 1000 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y").parse,
bisectDate = d3.bisector(function(d) { return d.date; }).left,
formatValue = d3.format(",.2f");
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(15);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(10);
var voronoi = d3.geom.voronoi()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.value); })
.clipExtent([[-margin.left, -margin.top], [width + margin.right, height + margin.bottom]]);
// var valueline1 = d3.svg.line()
// .x(function(d) { return x(d.date); })
// .y(function(d) { return y(d.california_energy_production); });
var valueline2 = d3.svg.line()
// .interpolate("basis")
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.uv); });
var valueline3 = d3.svg.line()
// .interpolate("basis")
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.tv); });
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("class", "graph-svg-component")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
tmp = null;
// Get the data
var data = [{"uv":"1904","date":"1434391200000","tv":"1000"},{"uv":"5174","date":"1434393900000","tv":"334"},{"uv":"4840","date":"1434394200000","tv":"3432"},{"uv":"11237","date":"1434394500000","tv":"3243"},{"uv":"14456","date":"1434394800000","tv":"1223"},{"uv":"5363","date":"1434397500000","tv":"554"},{"uv":"11641","date":"1434397800000","tv":"3244"},{"uv":"11414","date":"1434398100000","tv":"6767"},{"uv":"13041","date":"1434398400000","tv":"76765"},{"uv":"12111","date":"1434402300000","tv":"5546"},{"uv":"368","date":"1434402600000","tv":"6767"},{"uv":"14476","date":"1434402900000","tv":"5464"},{"uv":"6357","date":"1434403200000","tv":"4323"},{"uv":"1037","date":"1434403500000","tv":"6565"}];
var flatData = [];
data.forEach(function(d) {
d.date = d.date;
// d.california_energy_production = +d.california_energy_production;
d.uv = +d.uv;
d.tv = +d.tv
flatData.push({date: d.date, value: d.uv, key: "uv"});
flatData.push({date: d.date, value: d.tv, key: "tv"});
});
// 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 Math.max( d.uv, d.tv); })]);
// Add the valueline path.
// svg.append("path")
// .attr("class", "line")
// .attr("d", valueline1(data));
svg.append("path")
.attr("class", "line uv")
.style("stroke","blue")
.attr("d", valueline2(data));
svg.append("path")
.attr("class", "line tv")
.style("stroke","yellow")
.attr("d", valueline3(data));
svg.append("g") // Add the X Axis
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g") // Add the Y Axis
.attr("class", "y axis")
.call(yAxis);
var focus = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(-100,-100)");
focus.append("circle")
.attr("r", 4.5);
focus.append("text");
svg.append("text")
// .attr("class", "sources")
.attr("transform", "translate(" + (width+3) + "," + y(10) + ")")
.attr("dy", ".35em")
.attr("text-anchor", "start")
.style("fill", "#898989");
svg.append("text")
// .attr("class", "sources")
.attr("transform", "translate(" + (width+3) + "," + y(10) + ")")
.attr("text-anchor", "start")
.style("fill", "#898989");
var voronoiGroup = svg.append("g")
.attr("class", "voronoi");
voronoiGroup.selectAll("path")
.data(voronoi(flatData))
.enter().append("path")
.attr("d", function(d) { return "M" + d.join("L") + "Z"; })
.datum(function(d) { return d.point; })
.on("mouseover", mouseover)
.on("mouseout", mouseout);
function mouseover(d) {
console.log(d);
d3.select("."+d.key).classed("line-hover", true);
focus.attr("transform", "translate(" + x(d.date) + "," + y(d.value) + ")");
focus.style("position: absoloute");
focus.attrb("x","20px");
focus.select("text").text(d.date);
}
function mouseout(d) {
d3.select("."+d.key).classed("line-hover", false);
focus.attr("transform", "translate(-100,-100)");
}
In this case the text on mouseover is appearing around the focus element circle. What I want is that the mouseover text should be positioned in an absolute position inside the chart, say, in top left corner of the chart and not near the focus circle. Can this be done with focus element?
I got it to work. Instead of appending the focus text with the info, I am appending the svg text. I added one id attribute to this svg text and on mouse out, I'm removing the svg id.