I am using d3.js to create text elements on svg canvas. And I'm trying to change the style of the text. But somehow it doesn't seem to work. Only the x-y coordinate can be changed successfully, but other attributes like font size and color wouldn't change. I've also tried using .attr instead of .style. Nothing happened. Can anyone help? Really appreciate it!!
//create canvas
var canvas = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 500);
//text data
var textSpec = [{"content" : "testing", "font-family" : "sans-serif","font-size" : "80px", "color" : "red" , "x" : 100 , "y" : 15}];
//text display
var text = canvas.selectAll("text")
.data(textSpec)
.enter()
.append("text")
.attr("x", function(d) { return d.x;})
.attr("y", function(d) { return d.y;})
.text(function(d) { return d.content})
.style("font-family", function(d) { return d.font-family;})
.style("font-size", function(d) { return d.font-size;})
.style("stroke", red)
.style('fill', function(d) { return d.color;});
There is error reported:
Uncaught ReferenceError: family is not defined
because d.font-family is not treated as property font-family of variable d but as property font of variable d minus family.
Also, red is used like variable which is not defined.
You have to change the code to:
...
.style("font-family", function(d) { return d['font-family'];})
.style("font-size", function(d) { return d['font-size'];})
.style("stroke", 'red')
.style('fill', function(d) { return d.color;});
Related
I am trying to replicate this example of a multiline chart with dots. My data is basically the same, where I have an object with name and values in the first level, and then a couple of values in the second level inside values. For the most part, my code works, but for some reason, the j index in the anonymous function for the fill returns an array of repeated circle instead of returning the parent of the current element. I believe this may have something to do with the way I created the svg and selected the elements, but I can't figure it out. Below is an excerpt of my code that shows how I created the svg, the line path and the circles.
var svgb = d3.select("body")
.append("svg")
.attr("id","svg-b")
.attr("width", width)
.attr("height", height)
var gameb = svgb.selectAll(".gameb")
.data(games)
.enter()
.append("g")
.attr("class", "gameb");
gameb.append("path")
.attr("class", "line")
.attr("d", function(d) {return line_count(d.values); })
.style("stroke", function(d) { return color(d.name); })
.style("fill", "none");
gameb.selectAll("circle")
.data(function(d) {return d.values;})
.enter()
.append("circle")
.attr("cx", function(d) {return x(d.date);})
.attr("cy", function(d) {return y_count(d.count);})
.attr("r", 3)
.style("fill", function(d,i,j) {console.log(j)
return color(games[j].name);});
j (or more accurately, the third parameter) will always be the nodes in the selection (the array of circles here), not the parent. If you want the parent datum you can use:
.attr("fill", function() {
let parent = this.parentNode;
let datum = d3.select(parent).datum();
return color(datum.name);
})
Note that using ()=> instead of function() will change the this context and the above will not work.
However, rather than coloring each circle independently, you could use a or the parent g to color the circles too:
gameb.append("g")
.style("fill", function(d) { return color(d.name); })
.selectAll("circle")
.data(function(d) {return d.values;})
.enter()
.append("circle")
.attr("cx", function(d) {return x(d.date);})
.attr("cy", function(d) {return y_count(d.count);})
.attr("r", 3);
Here we add an intermediate g (though we could use the original parent with a few additional modifications), apply a fill color to it, and then the parent g will color the children circles for us. The datum is passed on to this new g behind the scenes.
you can find something like below in the jsFiddle demo included in this question, and i am wondering how may I assign images into each node.
var graph = {
"nodes":[
{"name":"1","rating":90,"id":2951},
]
}
I have jsFiddle Demo in this link : http://jsfiddle.net/JSDavi/qvco2Ljy/
Update 1 :
node.append("circle")
.attr("r", function(d) { return d.weight * 2+ 12; })
.style("fill", function(d) { return color(1/d.rating); });
node.append("image")
.attr("xlink:href", d=> d.url)
//people icon's location (x,y)
.attr("x", function(d) { return d.weight * 2+ 12; })
.attr("y", function(d) { return d.height * 2+ 12; })
//people icon's size
.attr("width", width/10)
.attr("height", height/10);
The codes above is how I set the circle and images
but they never stick together.
this is my updated demo : http://jsfiddle.net/qvco2Ljy/116/
For each object, set the URL of the image:
{name: "1", rating: 90, id: 2951, url: "someUrl"},
Then, instead of appending circles, append an image
node.append("image")
.attr("xlink:href", d=> d.url)
.attr("x", -width/2)
.attr("y", -height/2)
.attr("width", width)
.attr("height", height);
Where width and height are the corresponding image width and height.
PS: have in mind that I'm simply answering your question ("how may I assign images into each node?"). If giving up the circles is not an option for you, you'll have to use a pattern.
I am new to D3.js and am trying to build rectangles that represent all nodes from an XML file. So far so good but I want interactivity with each of the rectangles I draw and to be able to capture the nodes that have been touched for further processing. So let's say I click on a rectangle, I can make it react by doing an onclick event (like increasing the font size) but I can't seem to retrieve some of the info. I'd like to create an array with the text of each item that was clicked on.
Here's the code for one instance of the rectangle.
d3.select("#chart")
.append("svg")
.attr("width", 600)
.attr("height", 2000)
.style("background", "#93A1A1")
d3.select("svg")
.append("rect").attr("x", 50)
.attr("y", 25)
.attr("height", 20)
.attr("width", 200)
.attr("title", "resourceDef")
.style("fill", "#CB4B19")
d3.select("svg")
.append("text")
.attr("x", 55)
.attr("y", 37)
.attr("font-size", 11)
.attr("font-family", "sans-serif")
.text("resourceDef")
.on("mouseover", function(d) {
tempText = this.text;
alert(tempText);
d3.select(this)
.attr("font-size", 15)})
.on("mouseout", function(d) {
d3.select(this)
.attr("font-size", 11)})
I can grab style info by using but not the title and I can't find that info anywhere. Thanks for your help, I know it's a long question with probably a simple answer.
You can attach a mouse over event on the rectangle DOM by doing something like this:
d3.select("svg")
.append("rect").attr("x", 50)
.attr("y", 25)
.attr("height", 20)
.attr("width", 200)
.attr("title", "resourceDef")
.style("fill", "#CB4B19")
.on("click", function (d) {
var t = d3.select(this).attr("title");
//pushing the title into the array.
clickedTitles.push(t);
console.log(t);
});
You can get the attribute of a DOM(in your case tite) by doing something like this:
.on("click", function (d) {
var t = d3.select(this).attr("title");
clickedTitles.push(t);
console.log(t)
})
You can store the clicked rectangles title in an array like this:
//create an array
var clickedTitles = [];
//in your click function push the title into the array
clickedTitles.push(t);
//use the clickedTitles where ever you need in the code
Full code is here.
I've brought some of my code to match D3 standards. But now I'm having issues with my update function working. I'm trying to follow the General Update Pattern.
When I run the function, I get an error in the console: "TypeError: gbars.enter(...).attr is not a function"
function updateBars()
{
xScale.domain(d3.range(dataset.length)).rangeRoundBands([0, w], 0.05);
yScale.domain([0, d3.max(dataset, function(d) { return (d.local > d.global) ? d.local : d.global;})]);
var gbars = svg.selectAll("rect.global")
.data(dataset);
gbars.enter()
.attr("x", w)
.attr("y", function(d) {return h - yScale(d.global); })
.attr("width", xScale.rangeBand())
.attr("height", function(d) {return yScale(d.global);});
gbars.transition()
.duration(500)
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d.global);
})
.attr("width", xScale.rangeBand())
.attr("height", function(d) {
return yScale(d.global);
});
gbars.exit()
.transition()
.duration(500)
.attr("x", -xScale.rangeBand())
.remove();
}
I understand, I probably have a lot of errors with the update function in general, but it's hard to troubleshoot when hung-up at the beginning.
My goal for the update will be to remove/add series from the chart. They can choose to display Global, Local, or both (default). I'd actually prefer that when they hover over the legend it shows only that series. On mouseout, it would go back to default.
Working Fiddle here.
You need to say what you want to do for new nodes. Yes it's almost always append but you still have to tell it:
gbars.enter()
.append("rect")
.attr("class", "global")
.attr("x", w)
Other than that error, your structure for general update pattern looks correct on the surface.
I've been looking for a way to have my legend control my chart animation (similar to NVD3 examples). I've run into a problem though - nested selections.
var legend = svg.append("g")
.attr("class", "legend")
.attr("transform", "translate(70,10)")
;
var legendRect = legend.selectAll('rect').data(colors);
legendRect.enter()
.append("rect")
.attr("x", w - 65)
.attr("width", 10)
.attr("height", 10)
.attr("y", function(d, i) {
return i * 20;
})
.style("stroke", function(d) {
return d[1];
})
.style("fill", function(d) {
return d[1];
});
I'm using a bit of a hack to do my animation. Basically setting style to display: none.
I want to be able to click on the rectangles and call the function. But putting a mouseover or onclick within legendRect doesn't work. The bars to animate are not children of the legend. How can I call the function, or chain my function to my legend?
function updateBars(opts) {
var gbars = svg.selectAll("rect.global");
var lbars = svg.selectAll("rect.local");
if (opts === "global") {
gbars.style("display", "block") ;
lbars.style("display", "none");
gbars.transition()
.duration(500)
.attr("width", xScale.rangeBand());
};
if (opts === "local") {
lbars.style("display", "block")
;
gbars.style("display", "none");
lbars.transition()
.duration(500)
.attr("x", 1 / -xScale.rangeBand())
.attr("width", xScale.rangeBand());
};
}
My other obstacle is changing the fill color on click. I want it to almost imitate a checkbox, so clicking (to deselect) would turn the fill white. I tried something similar as .on("click",(".style" ("fill", "white"))). But that is incorrect.
Here is my fiddle. For some reason, the function isn't updating things on Fiddle. It works on my localhost though. Not sure the problem with that.
I'm not completely sure I understand you correctly, but if your first question is how to change element X when clicking on element Y, you need something along the lines of:
legendRect.on("click", function() {
gbars.transition()
.duration(500)
.style("display", "block")
// etc...
}
As for changing the fill on click, try:
gbars.on("click", function() {
d3.select(this)
.attr("fill", "white");
}