Related
I am building a data visualization project utilizing the d3 library. I have created a legend and am trying to match up text labels with that legend.
To elaborate further, I have 10 rect objects created and colored per each line of my graph. I want text to appear adjacent to each rect object corresponding with the line's color.
My Problem
-Right now, an array containing all words that correspond to each line appears adjacent to the top rect object. And that's it.
I think it could be because I grouped my data using the d3.nest function. Also, I noticed only one text element is created in the HTML. Can anyone take a look and tell me what I'm doing wrong?
JS Code
const margin = { top: 20, right: 30, bottom: 30, left: 0 },
width = 1000 - margin.left - margin.right;
height = 600 - margin.top - margin.bottom;
// maybe a translate line
// document.body.append(svg);
const div_block = document.getElementById("main-div");
// console.log(div_block);
const svg = d3
.select("svg")
.attr("width", width + margin.left + margin.right) // viewport size
.attr("height", height + margin.top + margin.bottom) // viewport size
.append("g")
.attr("transform", "translate(40, 20)"); // center g in svg
// load csv
d3.csv("breitbartData.csv").then((data) => {
// convert Count column values to numbers
data.forEach((d) => {
d.Count = +d.Count;
d.Date = new Date(d.Date);
});
// group the data with the word as the key
const words = d3
.nest()
.key(function (d) {
return d.Word;
})
.entries(data);
// create x scale
const x = d3
.scaleTime() // creaters linear scale for time
.domain(
d3.extent(
data,
// d3.extent returns [min, max]
(d) => d.Date
)
)
.range([margin.left - -30, width - margin.right]);
// x axis
svg
.append("g")
.attr("class", "x-axis")
.style("transform", `translate(-3px, 522px)`)
.call(d3.axisBottom(x))
.append("text")
.attr("class", "axis-label-x")
.attr("x", "55%")
.attr("dy", "4em")
// .attr("dy", "20%")
.style("fill", "black")
.text("Months");
// create y scale
const y = d3
.scaleLinear()
.domain([0, d3.max(data, (d) => d.Count)])
.range([height - margin.bottom, margin.top]);
// y axis
svg
.append("g")
.attr("class", "y-axis")
.style("transform", `translate(27px, 0px)`)
.call(d3.axisLeft(y));
// line colors
const line_colors = words.map(function (d) {
return d.key; // list of words
});
const color = d3
.scaleOrdinal()
.domain(line_colors)
.range([
"#e41a1c",
"#377eb8",
"#4daf4a",
"#984ea3",
"#ff7f00",
"#ffff33",
"#a65628",
"#f781bf",
"#999999",
"#872ff8",
]); //https://observablehq.com/#d3/d3-scaleordinal
// craete legend variable
const legend = svg
.append("g")
.attr("class", "legend")
.attr("height", 100)
.attr("width", 100)
.attr("transform", "translate(-20, 50)");
// create legend shapes and locations
legend
.selectAll("rect")
.data(words)
.enter()
.append("rect")
.attr("x", width + 65)
.attr("y", function (d, i) {
return i * 20;
})
.attr("width", 10)
.attr("height", 10)
.style("fill", function (d) {
return color(d.key);
});
// create legend labels
legend
.append("text")
.attr("x", width + 85)
.attr("y", function (d, i) {
return i * 20 + 9;
})
// .attr("dy", "0.32em")
.text(
words.map(function (d, i) {
return d.key; // list of words
})
);
// returning an array as text
// });
svg
.selectAll(".line")
.data(words)
.enter()
.append("path")
.attr("fill", "none")
.attr("stroke", function (d) {
return color(d.key);
})
.attr("stroke-width", 1.5)
.attr("d", function (d) {
return d3
.line()
.x(function (d) {
return x(d.Date);
})
.y(function (d) {
return y(d.Count);
})(d.values);
});
});
Image of the problem:
P.S. I cannot add a JSfiddle because I am hosting this page on a web server, as that is the only way chrome can read in my CSV containing the data.
My Temporary Solution
function leg_labels() {
let the_word = "";
let num = 0;
for (i = 0; i < words.length; i++) {
the_word = words[i].key;
num += 50;
d3.selectAll(".legend")
.append("text")
.attr("x", width + 85)
.attr("y", function (d, i) {
return i + num;
})
// .attr("dy", "0.32em")
.text(the_word);
}
}
leg_labels();
Problem
Your problem has to do with this code
legend
.append("text")
.attr("x", width + 85)
.attr("y", function (d, i) {
return i * 20 + 9;
})
// .attr("dy", "0.32em")
.text(
words.map(function (d, i) {
return d.key; // list of words
})
);
You are appending only a single text element and in the text function you are returning the complete array of words, which is why all words are shown.
Solution
Create a corresponding text element for each legend rectangle and provide the correct word. There are multiple ways to go about it.
You could use foreignObject to append HTML inside your SVG, which is very helpful for text, but for single words, plain SVG might be enough.
I advise to use a g element for each legend item. This makes positioning a lot easier, as you only need to position the rectangle and text relative to the group, not to the whole chart.
Here is my example:
let legendGroups = legend
.selectAll("g.legend-item")
.data(words)
.enter()
.append("g")
.attr("class", "legend-item")
.attr("transform", function(d, i) {
return `translate(${width + 65}px, ${i * 20}px)`;
});
legendGroups
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 10)
.attr("height", 10)
.style("fill", function (d) {
return color(d.key);
});
legendGroups
.append("text")
.attr("x", 20)
.attr("y", 9)
.text(function(d, i) { return words[i].key; });
This should work as expected.
Please note the use of groups for easier positioning.
I have a d3 line chart with a tooltip, I am facing a problem with a tooltip.
I have functionality, on click of points/circle I am appending rect to g, which is adding on top of the existing rect which has the tooltip functionality.
My tooltip is not coming at selected(rect) Graph Point.
g.append("rect")
.attr("class", "overlay")
.attr("id", "firstLayer")
.attr("width", width)
.attr("height", height)
.on("mouseover", function(d) {
focus.style("display", null);
div
.transition()
.duration(200)
.style("opacity", 0.9);
})
.on("click", function(d, index) {
let newXScale = newX ? newX : xScale;
if (rect) rect.remove();
rect = g
.append("rect")
.attr("x", newXScale(d.startTime) - 12.5)
.attr("y", 0)
.attr("width", 24)
.attr("height", height + 5)
.attr("data", d.startTime)
.style("fill", "steelblue")
.style("opacity", 0.5);
if (clickLine) clickLine.remove();
clickLine = g
.append("line")
.attr("x1", newXScale(d.startTime))
.attr("y1", yScale(yDomain[0]))
.attr("x2", newXScale(d.startTime))
.attr("y2", yScale(yDomain[1]))
.attr("class", "focusLine")
.style("opacity", 0.5);
})
rect element is coming on top of the gm on hover of that tooltip is not coming, any suggestions on how to fix it ?
On mouse hover -
At selected Graph Point -
CodeSandbox link below -
https://codesandbox.io/s/damp-dawn-82hxc
Please guide me what can be changed.
on click of the circle you are appending another rectangle to g, which is adding on top of the existing rect which has the tool tip functionality
Note: d3 js adds layer/shape on top of another which basically overrides the existing layer/shape functionality with the new layer/shape if they are in the same position
To avoid that we have to draw the layers depends on their intended purpose and position.
Solution for the above problem
append background rects for circle you want to create with opacity: 0
g.selectAll(".faaa")
.data(data)
.enter()
.append("rect")
.attr("class", "faaa")
.attr("id", d => "rect_" + d.id)
.attr("x", d => xScale(d.startTime) - 12.5)
.attr("y", 0)
.attr("width", 24)
.attr("height", height + 5)
.attr("data", d => d)
.style("fill", "steelblue")
.style("opacity", 0);
append firstLayer rect which has the tooltip functionality so the background rect won't break the tooltip functionality
g.append("rect")
.attr("class", "overlay")
.attr("id", "firstLayer")
.attr("width", width)
.attr("height", height)
.on("mouseover", function(d) {
focus.style("display", null);
div
.transition()
.duration(200)
.style("opacity", 0.9);
})
.on("mouseout", function() {
focus.style("display", "none");
div
.transition()
.duration(300)
.style("opacity", 0);
})
.on("mousemove", function() {
var mouse = d3.mouse(this);
var mouseDate = xScale.invert(mouse[0]);
var i = bisectDate(data, mouseDate); // returns the index to the current data item
var d0 = data[i - 1];
var d1 = data[i];
let d;
// work out which date value is closest to the mouse
if (typeof d1 !== "undefined") {
d = mouseDate - d0.startTime > d1.startTime - mouseDate ? d1 : d0;
} else {
d = d0;
}
div
.html(
`<span>${parseDate(d.startTime)}</span>
<span> Changes : ${d.magnitude} % </span>`
)
.style("left", d3.event.pageX + "px")
.style("top", d3.event.pageY - 28 + "px");
var x = xScale(d.startTime);
var y = yScale(d.magnitude);
focus
.select("#focusCircle")
.attr("cx", x)
.attr("cy", y);
focus
.select("#focusLineX")
.attr("x1", x)
.attr("y1", yScale(yDomain[0]))
.attr("x2", x)
.attr("y2", yScale(yDomain[1]));
focus
.select("#focusLineY")
.attr("x1", xScale(xDomain[0]))
.attr("y1", y)
.attr("x2", xScale(xDomain[1]))
.attr("y2", y);
});
append circle and add click functionality then change the opacity to highlight the background rect
g.selectAll(".foo")
.data(data)
.enter()
.append("circle")
.attr("id", d => d.id)
.attr("class", "foo")
.attr("data", d => d)
.attr("cx", function(d) {
return xScale(d.startTime);
})
.attr("cy", function(d) {
return yScale(d.magnitude);
})
.attr("r", function(d) {
return 6;
})
.on("click", function(d) {
// change the opacity here
d3.select("#rect_" + d.id).style("opacity", 0.5);
})
.attr("class", "circle");
Hope this solves the above problem...
I am trying to create a scrollable list. I have looked at other tutorials on here but it does not seem to be working. Essentially I am appending an SVG to a div. Inside this SVG is a D3JS stacked Bar Graph. to the right of this bar graph I am appending a 'g' element with an svg inside. I have set a height for this right SVG. Inside this I have populated a list that would extend beyond the height of the SVG. I have set the CSS for this svg to 'overflow-y: scroll'.
In spite of all of this I can not get this svg to scroll. Instead it just grows to the size of the list and extends past to intended bounds. Please See code below.
var barSVG = d3.select("#documents_reviewed_bar_chart").append("svg")
.classed('barChart-docs-reviewed', true)
.attr('id', 'barSVG')
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr('id', 'gElement')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var count = 0;
var graph = barSVG.append('g')
.attr('id', 'graphElement')
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "Date"; }));
data.forEach(function(d) {
var myDate = d.Date; //add to stock code
var y0 = 0;
d.people = color.domain().map(function(name) { return {myDate:myDate, name: name, y0: y0, y1: y0 += +d[name]}; });
d.total = d.people[d.people.length - 1].y1;
count = isNaN(d.total) ? count : count + d.total
});
x.domain(data.map(function(d) { return d.Date; }));
y.domain([0, d3.max(data, function(d) { return d.total; })]);
graph.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-65)" )
.style("cursor", "pointer")
.on('click', renderHorizontalChart);
graph.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");
graph.append('text')
.text('Total: ' + count)
.attr('x', 20)
.attr('y', -10)
var state = graph.selectAll(".state")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + "0" + ",0)"; });
//.attr("transform", function(d) { return "translate(" + x(d.Date) + ",0)"; })
state.selectAll("rect")
.data(function(d) {
return d.people;
})
.enter().append("rect")
.attr("width", x.rangeBand())
.attr("y", height)
.attr("x",function(d) { //add to stock code
return x(d.myDate)
})
.attr("height", 0 )
.style("fill", function(d) { return color(d.name); })
.transition()
.duration(1000)
.attr("y", function(d) { return y(d.y1); })
.attr("height", function(d) { return y(d.y0) - y(d.y1); })
.attr("class", function(d) {
classLabel = d.name.replace(/,\s/g, ''); //remove spaces
return "class" + classLabel;
});
state.selectAll("rect")
.on("mouseover", function(d){
var delta = d.y1 - d.y0;
var xPos = parseFloat(d3.select(this).attr("x"));
var yPos = parseFloat(d3.select(this).attr("y"));
var height = parseFloat(d3.select(this).attr("height"))
d3.select(this).attr("stroke","black").attr("stroke-width",2);
tooltip.style("visibility", "visible");
tooltip.style("top", (event.pageY-10)+"px").style("left",(event.pageX+10)+"px");
tooltip.style('background', 'black')
tooltip.style('color', 'white')
tooltip.style('border-radius', '3px')
tooltip.style('padding', '5px')
tooltip.style('opacity', '0.8')
tooltip.style('font-size', '10px;')
tooltip.text(d.name +": "+ delta)
})
.on("mouseout",function(){
tooltip.style("visibility", "hidden");
graph.select(".tooltip").remove();
d3.select(this).attr("stroke","pink").attr("stroke-width",0.2);
})
var itemsAmount = 0
var rightSVG = barSVG.append('svg').classed('rightSVG', true)
.attr('height', '390')
.attr('id', 'rightSVG')
var legendSVG = rightSVG.append('svg').classed('legendSVG', true)
.attr('id', 'legendSVG')
var legend = legendSVG.selectAll(".legend")
.data(color.domain().slice().reverse())
.enter().append("g")
//.attr("class", "legend")
.attr("class", function (d) {
itemsAmount = itemsAmount + 1
legendClassArray.push(d.replace(/,\s/g, '')); //remove spaces
return "legend";
})
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
//reverse order to match order in which bars are stacked
legendClassArray = legendClassArray.reverse();
legend.append("rect")
.attr("x", width - 0)
.attr("width", 18)
.attr("height", 18)
.style("fill", color)
.attr("id", function (d, i) {
return "id#" + d.replace(/,\s/g, '');
})
.on("mouseover",function(){
if (active_link === "0") d3.select(this).style("cursor", "pointer");
else {
if (active_link.split("class").pop() === this.id.split("id#").pop()) {
d3.select(this).style("cursor", "pointer");
} else d3.select(this).style("cursor", "auto");
}
})
.on("click",function(d){
if (!this.id.includes('active')) { //nothing selected, turn on this selection
d3.select(this)
.attr('id', function(){
return this.id + 'active'
})
.style("stroke", "black")
.style("stroke-width", 2);
active_link = this.id.split("id#").pop();
plotSingle(this);
} else { //deactivate
d3.select(this)
.classed("active", false)
.attr('id', function() {
return this.id.replace('active', '')
})
.style("stroke", "none")
.style("stroke-width", 0);
plotSingle(this);
}
});
legend.append("text")
.attr("x", width - 6)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
legendSVG.append("text")
.classed('queryButton', true)
.attr("x", width - 6)
.attr("y", height)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text('Run Query')
.on('click', function(){
if (newArr.length > 0) {
d3.select('#barSVG').remove();
runScript(newArr)
}
});
legendSVG.append("text")
.classed('queryButton', true)
.attr("x", width - 6)
.attr("y", height + 18)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text('Reset')
The Specific SVG that I want to be scrollable is the 'rightSVG'.
As you can see in the image, the names are cut off. There should be a scrollable legend where I am able to see 29 data items.
Also, I have added the below CSS:
#documents_reviewed_bar_chart, #gElement{
max-height: 390;
overflow-y: scroll;
}
Short answer: you can't have a scrollable SVG inside another SVG. The overflow-y: scroll applies to HTML elements, not to SVG elements.
Alternative (and hacky) answer: technically, what you want is possible, but you'll have to wrap your inner SVG in an HTML element, which has to be inside a foreignObject.
This alternative is suboptimal, makes little sense and doesn't work on IE. However, just for the sake of curiosity, this is how you can do it:
var outerSvg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 200)
.style("background-color", "darkkhaki");
var foreign = outerSvg.append("foreignObject")
.attr("x", 300)
.attr("y", 10)
.attr("width", 150)
.attr("height", 180)
.append("xhtml:div")
.style("max-height", "180px")
.style("overflow-y", "scroll");
var innerSvg = foreign.append("svg")
.attr("width", 133)
.attr("height", 1000)
.style("background-color", "powderblue");
var color = d3.scaleOrdinal(d3.schemeCategory20);
var texts = innerSvg.selectAll("foo")
.data(d3.range(65))
.enter()
.append("text")
.attr("x", 40)
.attr("y", (d,i)=> 20 + 15*i)
.text("foo bar baz")
var rects = innerSvg.selectAll("foo")
.data(d3.range(65))
.enter()
.append("rect")
.attr("x", 10)
.attr("y", (d,i)=> 8 + 15*i)
.attr("width", 20)
.attr("height", 13)
.attr("fill", (d,i)=>color(i));
<script src="https://d3js.org/d3.v4.min.js"></script>
The outer SVG is light brown (or khaki). The inner SVG, at the right, is blue, and it's inside a <div> with overflow-y: scroll;.
I am creating the legends with triangle shapes. One is "Yes", the other one is "No". By running the code below, it generate two triangles but they are overlapping. I am trying to seperate them by using this line of code .attr("y", function(d,i) {return 50+i*40;}) but seems like it doesn't work.
Can anyone tell me how to fix it? Thanks!
Click here! This is an html sreenshot for this part of script
var legendname = ["Yes","No"];
var legend = svg.selectAll(".legend")
.data(legendname)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(" + (w + 150) + "," + (m.t - 30) + ")";
});
legend.append("path")
.attr("d", d3.svg.symbol().type("triangle-up").size(128))
*** .attr("y", function(d,i) {return 50+i*40;})
.style('fill', function(d) {return color(d);});
legend.append("text")
.attr("y", function(d,i) {return 50+i*20;})
.attr("x", 30)
.text(function(d) { return d; })
You will have to update the translate y attribute of groups instead of the paths. And also there is no need for extra calculations for y attributes of texts and paths then.
.attr("transform", function(d, i) {
return "translate(" + (w + 150) + "," + (30+i*40) + ")";
});
Working Code Snippet:
var w=40; //Sample chart width
var color = d3.scale.category20c();
var svg = d3.select("body").append("svg").attr({ height: 500, width: 400 });
var legendname = ["Yes", "No"];
var legend = svg.selectAll(".legend")
.data(legendname)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(" + (w + 150) + "," + (30+i*40) + ")";
});
legend.append("path")
.attr("d", d3.svg.symbol().type("triangle-up").size(128))
.style('fill', function(d,i) {
return color(i);
});
legend.append("text")
.attr("dx",10)
.attr("dy",".4em")
.text(function(d) {
return d;
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I'm trying to add a legend to a d3 scatterplot matrix (using this example as a template: http://bl.ocks.org/mbostock/4063663), and while the scatterplot itself is displaying as expected, I have been unable to successfully add a legend. The code for the plot and one of the attempts at adding a legend are below:
var width = 960,
size = 150,
padding = 19.5;
var x = d3.scale.linear()
.range([padding / 2, size - padding / 2]);
var y = d3.scale.linear()
.range([size - padding / 2, padding / 2]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5);
var color = d3.scale.category10();
d3.csv(datafilename, function(error, dataset) {
var domainByTrait = {},
traits = d3.keys(dataset[0]).filter(function(d) { return d !== "class"; }),
n = traits.length;
traits.forEach(function(trait) {
domainByTrait[trait] = d3.extent(dataset, function(d) { return d[trait]; });
});
xAxis.tickSize(size * n);
yAxis.tickSize(-size * n);
var brush = d3.svg.brush()
.x(x)
.y(y)
.on("brushstart", brushstart)
.on("brush", brushmove)
.on("brushend", brushend);
var svg = d3.select("#visualizationDiv").append("svg")
.attr("width", size * n + padding)
.attr("height", size * n + padding)
.append("g")
.attr("transform", "translate(" + padding + "," + padding / 2 + ")");
svg.selectAll(".x.axis")
.data(traits)
.enter().append("g")
.attr("class", "x axis")
.attr("transform", function(d, i) { return "translate(" + (n - i - 1) * size + ",0)"; })
.each(function(d) { x.domain(domainByTrait[d]); d3.select(this).call(xAxis); });
svg.selectAll(".y.axis")
.data(traits)
.enter().append("g")
.attr("class", "y axis")
.attr("transform", function(d, i) { return "translate(0," + i * size + ")"; })
.each(function(d) { y.domain(domainByTrait[d]); d3.select(this).call(yAxis); });
var cell = svg.selectAll(".cell")
.data(cross(traits, traits))
.enter().append("g")
.attr("class", "cell")
.attr("transform", function(d) { return "translate(" + (n - d.i - 1) * size + "," + d.j * size + ")"; })
.each(plot);
// Titles for the diagonal.
cell.filter(function(d) { return d.i === d.j; }).append("text")
.attr("x", padding)
.attr("y", padding)
.attr("dy", ".71em")
.text(function(d) { return d.x; });
cell.call(brush);
function plot(p) {
var cell = d3.select(this);
x.domain(domainByTrait[p.x]);
y.domain(domainByTrait[p.y]);
cell.append("rect")
.attr("class", "frame")
.attr("x", padding / 2)
.attr("y", padding / 2)
.attr("width", size - padding)
.attr("height", size - padding);
cell.selectAll("circle")
.data(dataset)
.enter().append("circle")
.attr("cx", function(d) { return x(d[p.x]); })
.attr("cy", function(d) { return y(d[p.y]); })
.attr("r", 3)
.style("fill", function(d) { return color(d.class); });
}
var brushCell;
// Clear the previously-active brush, if any.
function brushstart(p) {
if (brushCell !== this) {
d3.select(brushCell).call(brush.clear());
x.domain(domainByTrait[p.x]);
y.domain(domainByTrait[p.y]);
brushCell = this;
}
}
// Highlight the selected circles.
function brushmove(p) {
var e = brush.extent();
svg.selectAll("circle").classed("hidden", function(d) {
return e[0][0] > d[p.x] || d[p.x] > e[1][0]
|| e[0][1] > d[p.y] || d[p.y] > e[1][1];
});
}
// If the brush is empty, select all circles.
function brushend() {
if (brush.empty()) svg.selectAll(".hidden").classed("hidden", false);
}
function cross(a, b) {
var c = [], n = a.length, m = b.length, i, j;
for (i = -1; ++i < n;) for (j = -1; ++j < m;) c.push({x: a[i], i: i, y: b[j], j: j});
return c;
}
d3.select(self.frameElement).style("height", size * n + padding + 20 + "px");
// add legend
var legend = svg.append("g")
.attr("class", "legend")
.attr("height", 100)
.attr("width", 100)
.attr('transform', 'translate(-20,50)');
legend.selectAll('rect')
.data(dataset)
.enter()
.append("rect")
.attr("x", width - 65)
.attr("y", function(d, i){ return i * 20;})
.attr("width", 10)
.attr("height", 10)
.style("fill", function(d) { return color(d.class); });
legend.selectAll('text')
.data(dataset)
.enter()
.append("text")
.attr("x", width - 52)
.attr("y", function(d, i){ return i * 20 + 9;})
.text(function(d) { return d.class; });
});
Among my other unsuccessful attempts at adding a legend are
var legend = svg.selectAll("g")
.data(dataset)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 28)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d) { return color(d.class); });
legend.append("text")
.attr("x", width - 34)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d.class; });
and
var legend = svg.selectAll('g').data(dataset)
.enter()
.append('g')
.attr("class", "legend");
legend.append("rect")
.attr("x", width - 45)
.attr("y", 25)
.attr("height", 50)
.attr("width", 50)
.each(function(d, i) {
var g = d3.select(this);
g.append("rect")
.attr("x", width - 65)
.attr("y", i*25)
.attr("width", 10)
.attr("height", 10)
.style("fill", function(d) { return color(d.class); });
g.append("text")
.attr("x", width - 50)
.attr("y", i * 25 + 8)
.attr("height",30)
.attr("width",100)
.style("fill", function(d) { return color(d.class); })
.text(function(d) { return d.class; });
all based on examples I've found on the web. None of these approaches seem to be working - I must be missing something here. Any insights or suggestions would be greatly appreciated.
The problem is right at the beginning:
var legend = svg.selectAll('g').data(dataset)
.enter()
.append('g')
.attr("class", "legend");
The selectAll('g') is going to select one of the groups already in your diagram, and then nothing will happen because enter() indicates that everything from there on (including the value that gets saved to the legend variable) only applies to groups that don't exist yet.
I'm pretty sure this legend code is supposed to be run from within its own <g> element. That way, it won't interfere with the rest of your graph.
var legendGroup = svg.append('g')
.attr('class', 'legend')
.attr('transform', /* translate as appropriate */);
var legendEntry = legendGroup.selectAll('g')
.data(dataset);
//create one legend entry for each series in the dataset array
//if that's not what you want, create an array that has one
//value for every entry you want in the legend
legendEntry.enter().append("g")
.attr("class", "legend-entry")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
//shift each entry down by approx 1 line (20px)
legendEntry.append("rect") //add a square to each entry
/* and so on */