I'm trying to color the circles per a .csv data, column "COLOR". The "COLOR" includes "red", "yellow" , "green" -but right now the colors are not transferring... just the default black...
var col = function(d) {return d.COLOR};
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", 15)
.attr("cx", xMap)
.attr("cy", yMap)
.style("fill", col)
.style("opacity", ".5")
.on("mouseover", function(d) {
tooltip.transition()
.duration(200)
.style("opacity", .9);
tooltip.html(d["Cereal Name"] + "<br/> (" + xValue(d)
+ ", " + yValue(d) + ")")
.style("left", (d3.event.pageX + 5) + "px")
.style("top", (d3.event.pageY - 28) + "px");
data.forEach(function(d) {
d.POPULATION = +d.POPULATION;
d.REVENUE = +d.REVENUE;
d.COLOR = +d.COLOR;
Your COLOR values in your CSV contain quotation marks " which will be part of the parsed strings in data. Therefore, you end up with attribute values like fill=""yellow"" which is not valid. Hence, the black default color.
One way around this might be to get rid of the quotation marks in the CSV itself. If this is not feasible, you might adjust your color accessor function col to something like the following:
var col = function(d) {return d.COLOR.substring(1, d.COLOR.length - 1)};
Have a look at this working demo:
var data = [{ COLOR: '"yellow"'}];
var col = function(d) { return d.COLOR.substring(1, d.COLOR.length - 1); };
d3.select("body").append("svg")
.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", 100)
.attr("cy", 100)
.attr("r", 10)
.attr("fill", col);
<script src="https://d3js.org/d3.v4.js"></script>
Related
I am trying to plot a network graph using networkD3 in R. I wanted to make some changes to the display so that the text labels (which appears when mouseover) can be easily read.
Please refer to the link here for an example. Note: Jump to the d3ForceNetwork plot.
As seen in the example, the labels are hard to read due to its colour and it often gets obstructed by the surrounding nodes. I have been messing around with the JS file and managed to change the text label color to black. However, having no knowledge of JS or CSS (I can't even tell the difference between the 2 actually), I have no idea how I can change the stack order such that the text labels will always be displayed above any other objects.
Can anyone advise me on how I can achieve the desired outcome?
Below is the full JS file:
HTMLWidgets.widget({
name: "forceNetwork",
type: "output",
initialize: function(el, width, height) {
d3.select(el).append("svg")
.attr("width", width)
.attr("height", height);
return d3.layout.force();
},
resize: function(el, width, height, force) {
d3.select(el).select("svg")
.attr("width", width)
.attr("height", height);
force.size([width, height]).resume();
},
renderValue: function(el, x, force) {
// Compute the node radius using the javascript math expression specified
function nodeSize(d) {
if(options.nodesize){
return eval(options.radiusCalculation);
}else{
return 6}
}
// alias options
var options = x.options;
// convert links and nodes data frames to d3 friendly format
var links = HTMLWidgets.dataframeToD3(x.links);
var nodes = HTMLWidgets.dataframeToD3(x.nodes);
// get the width and height
var width = el.offsetWidth;
var height = el.offsetHeight;
var color = eval(options.colourScale);
// set this up even if zoom = F
var zoom = d3.behavior.zoom();
// create d3 force layout
force
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(options.linkDistance)
.charge(options.charge)
.on("tick", tick)
.start();
// thanks http://plnkr.co/edit/cxLlvIlmo1Y6vJyPs6N9?p=preview
// http://stackoverflow.com/questions/22924253/adding-pan-zoom-to-d3js-force-directed
var drag = force.drag()
.on("dragstart", dragstart)
// allow force drag to work with pan/zoom drag
function dragstart(d) {
d3.event.sourceEvent.preventDefault();
d3.event.sourceEvent.stopPropagation();
}
// select the svg element and remove existing children
var svg = d3.select(el).select("svg");
svg.selectAll("*").remove();
// add two g layers; the first will be zoom target if zoom = T
// fine to have two g layers even if zoom = F
svg = svg
.append("g").attr("class","zoom-layer")
.append("g")
// add zooming if requested
if (options.zoom) {
function redraw() {
d3.select(el).select(".zoom-layer").attr("transform",
"translate(" + d3.event.translate + ")"+
" scale(" + d3.event.scale + ")");
}
zoom.on("zoom", redraw)
d3.select(el).select("svg")
.attr("pointer-events", "all")
.call(zoom);
} else {
zoom.on("zoom", null);
}
// draw links
var link = svg.selectAll(".link")
.data(force.links())
.enter().append("line")
.attr("class", "link")
.style("stroke", function(d) { return d.colour ; })
//.style("stroke", options.linkColour)
.style("opacity", options.opacity)
.style("stroke-width", eval("(" + options.linkWidth + ")"))
.on("mouseover", function(d) {
d3.select(this)
.style("opacity", 1);
})
.on("mouseout", function(d) {
d3.select(this)
.style("opacity", options.opacity);
});
// draw nodes
var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.style("fill", function(d) { return color(d.group); })
.style("opacity", options.opacity)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", click)
.call(force.drag);
node.append("circle")
.attr("r", function(d){return nodeSize(d);})
.style("stroke", "#fff")
.style("opacity", options.opacity)
.style("stroke-width", "1.5px");
node.append("svg:text")
.attr("class", "nodetext")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name })
.style("font", options.fontSize + "px " + options.fontFamily)
.style("opacity", options.opacityNoHover)
.style("pointer-events", "none");
function tick() {
node.attr("transform", function(d) {
if(options.bounded){ // adds bounding box
d.x = Math.max(nodeSize(d), Math.min(width - nodeSize(d), d.x));
d.y = Math.max(nodeSize(d), Math.min(height - nodeSize(d), d.y));
}
return "translate(" + d.x + "," + d.y + ")"});
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
}
function mouseover() {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", function(d){return nodeSize(d)+5;});
d3.select(this).select("text").transition()
.duration(750)
.attr("x", 13)
.style("stroke-width", ".5px")
.style("font", options.clickTextSize + "px ")
.style('fill', 'black')
.style('position','relative')
.style("opacity", 1);
}
function mouseout() {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", function(d){return nodeSize(d);});
d3.select(this).select("text").transition()
.duration(1250)
.attr("x", 0)
.style("font", options.fontSize + "px ")
.style("opacity", options.opacityNoHover);
}
function click(d) {
return eval(options.clickAction)
}
// add legend option
if(options.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 = legendRectSize;
var vert = i * height+4;
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)
.style('fill', 'darkOrange')
.text(function(d) { return d; });
}
// make font-family consistent across all elements
d3.select(el).selectAll('text').style('font-family', options.fontFamily);
},
});
I suspect I need to make some changes to the code over here:
function mouseover() {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", function(d){return nodeSize(d)+5;});
d3.select(this).select("text").transition()
.duration(750)
.attr("x", 13)
.style("stroke-width", ".5px")
.style("font", options.clickTextSize + "px ")
.style('fill', 'black')
.style("opacity", 1);
}
You need to resort the node groups holding the circles and text so the currently mouseover'ed one is the last in that group, and thus the last one drawn so it appears on top of the others. See the first answer here -->
Updating SVG Element Z-Index With D3
In your case, if your data doesn't have an id field you may have to use 'name' instead as below (adapted to use the mouseover function you've got):
function mouseover(d) {
d3.selectAll("g.node").sort(function (a, b) {
if (a.name != d.name) return -1; // a is not the hovered element, send "a" to the back
else return 1; // a is the hovered element, bring "a" to the front (by making it last)
});
// your code continues
The pain might be that you have to do this edit for every d3 graph generated by this R script, unless you can edit the R code/package itself. (or you could suggest it to the package author as an enhancement.)
I have a scatter plot similar to: http://plnkr.co/edit/MkZcXJPS7hrcWh3M0MZ1?p=preview
I want to give a tooltip on mouse hover for every combination. The tooltip code that i have currently does like:
var tooltip = d3.select("body").append("div") // tooltip code
.attr("class", "tooltip")
.style("opacity", 0);
var circles = svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", 3.5)
.attr("cx", function(d) { return x(d.petalWidth); })
.attr("cy", function(d) { return y(d.petalLength); })
.style("fill", function(d) { return color(d.species); })
.on("mouseover", function(d) {
tooltip.transition()
.duration(200)
.style("opacity", 1.0);
tooltip.html(d.petalLength+", "+d.petalWidth)
.style("left", (d3.event.pageX + 5) + "px")
.style("top", (d3.event.pageY - 18) + "px");
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
});
This will fail to return the correct tooltip for combinations other than petalWidth and d.petalLength.
Is there any way of knowing which combination has been selected and the associated numerical value for the combination?
To do this:
First store the tool-tip info in a new variable(displayX/displayY) like this:
.attr("cx", function(d) {
d.displayX = d.petalWidth;//so displayX holds the x info
return x(d.petalWidth);
})
.attr("cy", function(d) {
d.displayY = d.petalLength;//so displayY holds the y info
return y(d.petalLength);
})
When you set the combo reset the variables accordingly.
svg.selectAll(".dot").transition().attr("cy", function(d) {
d.displayY = d[yAxy];//reset the variable displayY
return y(d[yAxy]);
});
Same for
svg.selectAll(".dot").transition().attr("cx", function(d) {
d.displayX = d[xAxy];//reset the variable displayX
return x(d[xAxy]);
});
Now in the tool tip mouse hover use variable(displayX/displayY)
.on("mouseover", function(d) {
tooltip.transition()
.duration(200)
.style("opacity", 1.0);
tooltip.html(d.displayY + ", " + d.displayX)//use displayX and displayY
.style("left", (d3.event.pageX + 5) + "px")
.style("top", (d3.event.pageY - 18) + "px");
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
});
working code here
Hope this helps!
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 am making a d3 graph and trying to put a border around my rect elements. The rect elements are appended to a cell and the text elements are appended to the same cell. Thus if I change the stroke in the rect I lose all the text for some reason, and if I change the stroke in the cell the borders and fonts change too.
This is a portion of my code for drawing the graph.
this.svg = d3.select("#body").append("div")
.attr("class", "chart")
.style("position", "relative")
.style("width", (this.w +this.marginTree.left+this.marginTree.right) + "px")
.style("height", (this.h + this.marginTree.top + this.marginTree.bottom) + "px")
.style("left", this.marginTree.left +"px")
.style("top", this.marginTree.top + "px")
.append("svg:svg")
.attr("width", this.w)
.attr("height", this.h)
.append("svg:g")
.attr("transform", "translate(.5,.5)");
this.node = this.root = this.nestedJson;
var nodes = this.treemap.nodes(this.root)
.filter(function(d) { return !d.children; });
this.tip = d3.tip()
.attr('class', 'd3-tip')
.html(function(d) {
return "<span style='color:white'>" + (d.name+",\n "+d.size) + "</span>";
})
this.svg.call(this.tip);
var cell = this.svg.selectAll("g")
.data(nodes)
.enter().append("svg:g")
.attr("class", "cell")
.call(this.position)
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.on("click", function(d) { return this.zoom(this.node == d.parent ? this.root : d.parent); })
.style("border",'black');
var borderPath = this.svg.append("rect")
.attr("x", this.marginTree.left)
.attr("y", this.marginTree.top)
.attr("height", this.h - this.marginTree.top - this.marginTree.bottom )
.attr("width", this.w - this.marginTree.left - this.marginTree.right)
.style("stroke", 'darkgrey')
.style("fill", "none")
.style("stroke-width", '3px');
cell.append("svg:rect")
.attr("id", function(d,i) { return "rect-" + (i+1); })
.attr("class","highlighting2")
.attr("title", function(d) {return (d.name+", "+d.size);})
.attr("data-original-title", function(d) {return (d.name+",\n "+d.size);})
.attr("width", function(d) { return d.dx - 1; })
.attr("height", function(d) { return d.dy ; })
.on('mouseover', this.tip.show)
.on('mouseout', this.tip.hide)
.style("fill", function(d) {return coloring(d.color);});
cell.append("svg:text")
.attr("class", "treemap-text nameTexts")
.attr("id", function(d,i) { return "name-" + (i+1); })
.attr("x", cellMargin)
.attr("y", function(d) { return parseInt($('.treemap-text').css('font-size'))+cellMargin; })
.text(function(d) {return (d.name);});
cell.append("svg:text")
.attr("class", "treemap-text sizeTexts")
.attr("id", function(d,i) { return "size-" + (i+1); })
.attr("x", cellMargin)
.attr("y", function(d) { return 2*parseInt($('.treemap-text').css('font-size'))+2*cellMargin; })
.text(function(d) {return (d.size);});
Additionally, I thought about creating lines and drawing four lines around each rect element, but was wondering if there is an easier way. Thanks.
I didn't check fully through your source, it would also be helpful to work with jsbin, codepen, jsfiddle or other online platforms to show your problem.
Actually I think you just have misinterpreted the SVG presentation attributes and their styling with CSS. For SVG elements only SVG presentation attributes are valid in CSS. This means there is no border property as you have it in your code. Also note that for <text> elements the fill color is the font-body color and the stroke is the outline of the font. Consider that stroke and fill are inherited down to child element which means that if you have a rectangle with a stroke style and some containing text element that they will have the stroke applied as outline and you'd need to override the styles there.
Hope you can solve your issue.
Cheers
Gion
I am wondering why doing the following is not possible:
div.text(function(d) {return " bought " + d.USD;})
It works fine for the either red or green circles earlier in the code:
.style("fill", function(d) {if (d.USD <= 0) {return "green"}
else { return "red" };})
Here is the extract of my code responsible for drawing the circles and adding a tooltip:
// Draw circle around values
svg.selectAll("dot")
.data(bitstamp_data)
.enter().append("circle")
.attr("r", function(d) { return Math.sqrt(Math.abs(d.USD)) - 5; })
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.Price); })
.style("fill-opacity", 0.7)
.attr("stroke", "black")
// Show different colour depending on buying or selling USD based on positive or negative d.USD
.style("fill", function(d) {
if (d.USD <= 0) {return "green"}
else { return "red" }
;})
// Tooltip section
.on("mouseover", function(d) {
div.transition()
.duration(400)
.style("opacity", .9);
div.text(function(d) {return " bought " + d.USD
;})
.style("left", (d3.event.pageX + 15) + "px")
.attr("stroke", "black")
.style("top", (d3.event.pageY - 28) + "px");
})