I am using D3 to plot a rectangle for each object in an array, the height of the rectangle being dependant on the 'Size' property of the object. These rectangles are stacked on top of each other. I currently set the y position by summing the 'Size' of each subsequent rect that gets plotted - but this seems wrong - and I was wondering if there was a better way to do this, such as accessing the 'y' attribute of the previous item (and how?) or another way...
This is what the essence of my code looks like. There is a link to the fiddle below.
var cumY = 0;
var blocks1 = sampleSVG.selectAll("rect")
.data(fpp)
.enter()
.append("rect")
.sort(SortBySize)
.style("stroke", "gray")
.style("opacity", blockOpacity)
.style("fill", function (d) {return d.Colour})
.attr("width", 80)
.attr("height", function (d) {return d.Size})
.attr("x", 5)
.attr("y", function (d, i) {
var thisY = cumY;
cumY += d.Size;
// perhaps I could just return something like d.Size + previousItem.GetAttribute("y") ???
return thisY;
});
http://jsfiddle.net/ninjaPixel/bvER3/
This is tricky do! You're right that keeping track of the cumulative height 'seems wrong' - it works now but it isn't very idiomatic d3 and will get pretty messy once you start trying to do something more complicated.
I would try using d3's built in stack-layout which was created solve this problem. You might want to start working off of this example and posting an updated fiddle if you get stuck. Good luck!
Related
I am working on a d3 scatter plot where an area of the chart will be circled (a Youden Plot). Based on available samples, I have been able to add zoom to both my data points and my axis. However, I am unable to get the circle to zoom correctly.
I suspect that I need to set up some kind of scale (scaleSqrt, possibly), but I am struggling to find documentation on this that is written at a beginner level.
My current circle code is very straightforward
var circle = drawCircle();
function drawCircle() {
return svg
.append('g')
.attr('class', 'scatter-group')
.append('circle')
.attr("r", 75 )
.attr('cx', 200 + margin.left) //suspect this needs to be related to a scale
.attr('cy', 200 + margin.top) //suspect this needs to be related to
.attr('r', 75)//suspect this needs to be related to a scale
.attr('stroke', 'red')
.attr('stroke-width', 3)
.style('fill', 'none')
}
As is the zoomed function
function zoomed() {
var new_xScale = d3.event.transform.rescaleX(xScale);
var new_yScale = d3.event.transform.rescaleY(yScale);
// update axes
gX.call(xAxis.scale(new_xScale));
gY.call(yAxis.scale(new_yScale));
//redraw data ppints
points.data(data)
.attr('cx', function(d) {return new_xScale(d.x)})
.attr('cy', function(d) {return new_yScale(d.y)});
//redraw circle
}
My work in progress is available in this fiddle . Can someone possible point me in the right direction?
I believe this will get you most of the way there. You need to update your circle attributes in the zoomed function along with the other elements:
function zoomed() {
var new_xScale = d3.event.transform.rescaleX(xScale);
var new_yScale = d3.event.transform.rescaleY(yScale);
// update axes
gX.call(xAxis.scale(new_xScale));
gY.call(yAxis.scale(new_yScale));
//redraw data ppints
points.data(data)
.attr('cx', function(d) {return new_xScale(d.x)})
.attr('cy', function(d) {return new_yScale(d.y)});
// The new part:
// the transform
let trans = d3.event.transform
// the approximate domain value of the circle 'cx' for converting later
let cx_domain = xScale.invert(200 + margin.left)
// the approximate domain value of the circle 'cy' for converting later
let cy_domain = yScale.invert(200 + margin.top)
// the circle
let circ = d3.select('.scatter-group circle')
// the radius
let rad = 75
// reset the circle 'cx' and 'cy' according to the transform
circ
.attr('cx',function(d) { return new_xScale(cx_domain)})
.attr('cy',function(d) { return new_yScale(cy_domain)})
// reset the radius by the scaling factor
.attr('r', function(d) { return rad*trans.k })
}
See this fiddle
You'll notice the circle does not scale or move at quite the same rate as the scatter dots. This is possibly because of the use of the invert function, because the conversion from range to domain and back to range is imperfect. This issue is documented
For a valid value y in the range, continuous(continuous.invert(y)) approximately equals y; similarly, for a valid value x in the domain, continuous.invert(continuous(x)) approximately equals x. The scale and its inverse may not be exact due to the limitations of floating point precision.
Your original idea to assign dynamic values to cx, cy and r will likely compensate for this, because you can then avoid the inversion.
i'm working in projet of data visualization using D3JS.
i have to put some information inside a rectangle, and i did that using wrap function to split the content, no i'm looking to put each line (tspan) inside a rectangle. and i don't know how to do that, any help is appreciated for me.enter image description here
the small rectangles will contain informations. does any one have an example how to that. thakns
there!
Actually, what you need to do is set right position for groups.
var data = [0,1,2,3,4]; // example data
var selection = svg.selectAll('g').data(data).enter();
// set position for rects and texts using transform-translate
var groups = selection.append('g')
.attr('transform', function(d, i) { return 'translate(0,' + i * 35 + ')' });
groups.append('rect')
.attr('width', 100)
.attr('height', 30)
.attr('fill', 'white')
.attr('stroke', 'black'); // all rects
groups.append('text')
.text((d) => d)
.attr('font-size', '10px')
.attr('y', 15); // all texts
And an additional link for you, maybe it would be helpful.
I want to create a kind of infographic where I can represent percentages intuitively using a kind of fill logic.
Example
For the sake of simplicity let's just assume intervals of 25%. For the task of 75% of households, there would be four houses in total and 3 of them would be filled in. The remaining house would remain fill:'none'.
I had something in mind like:
It would be in SVG form.
The only way I can think of to achieve this is pre-draw the houses as a collective image and link the file like:
var fileMap = { 50:'fifty.svg', 75:'seventy-five.svg'};
But this doesn't seem to be very modular, and it doesn't utilize d3 hardly.
Question: Is it possible/feasible to create a simple 25% interval conditional fill using d3 compatible logic? What would my .data() call expect? It has to be an array, maybe a binary:
var data = [1,1,1,0] //75%;
Maybe there's a better way altogether, but that's the best I have got.
"I want to create a kind of infographic where I can represent percentages intuitively using a kind of fill logic"... The technical name for this is pictogram.
For creating a pictogram you don't need anything special, you can use a common enter selection. So, given your data...
var data = [1,1,1,0]
... we will create one house for each array element...
var house = svg.selectAll(null)
.data(data)
.enter()
.append("path")
... and fill them according to the datum:
.style("fill", function(d){
return d ? "blue" : "white"
})
Here is a basic demo:
var d = "m787.67 1599.58l148.83 157.74 124.02-131.45v630.95h396.87 198.44 396.87v-630.95l124.02 131.45 148.83-157.74-768.94-814.97-768.94 814.97m1066.6-709.82v78.868l198.44 210.32v-289.18h-198.44z";
var svg = d3.select("svg");
var data = [1, 1, 1, 0];
var house = svg.selectAll(null)
.data(data)
.enter()
.append("path")
.attr("d", d)
.attr("transform", function(_, i) {
return "translate(" + (i * 70) + ",100) matrix(.04 0 0 .03-4.159-50.852)"
})
.style("stroke", "black")
.style("stroke-width", "50px")
.style("fill", function(d) {
return d ? "blue" : "white"
})
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>
In my d3 force directed scatter plot i try to make points disappear and re-appear by clicking on a legend key. After clicking the legend key, i would like the remaining points to regroup and not to stay fixed in the same position, leaving blank spaces (screenshots). When clicking again on the legend, they should fly in again.
I tried to remove the fill of the circles upon clicking on a legend key, which is working, but obviouly does not make the force do its work..
My code on blockbuilder.org: http://blockbuilder.org/dwoltjer/04a84646720e1f82c16536d5ef9848e8
You can treat the filtered data as new data and apply the update, enter and exit pattern:
var node = svg.selectAll(".dot")
.data(data);
node.exit().remove();
node.enter().append("circle")
.attr("class", "dot")
.attr("r", radius)
......
The click event for legend:
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color)
.on("click", function (d) {
visible[d] = !visible[d];
var newdata = data.filter(function(e) { return visible[e.bank];});
DrawNode(newdata);
});
Here is the update blocks
Simply deleting the nodes should be enough to make the force rearrange itself and group the nodes again. But, you will want to save the nodes to bring them back (possibly using a temporary array).
However, if you want the nodes to fly off screen (and back on), then what I'd do (using V4) is move the nodes to a new forcePoint that's way off screen:
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color)
.on("click", function (d) {
node.filter(function () {
return this.dataset.bank === d;
})
position
.force('x', d3.forceX(width/2).strength(20))
.force('y', d3.forceY(height*2).strength(20));//should be twice the height of the svg, so way off the y axis. Or whichever direction you choose.
});
I'm just starting with d3js and I wanted to know if it's possible to create a scatterplot with custom icons for the data points similar to this method for Forced Layout?
I don't want to use d3.svg.symbol() as I want to use a custom icon with my company logo to mark each data point.
My final goal is to translate a point (with a custom icon) along a horizontal axis depending on the x value. I've searched high and low on how to do this with d3js but have had no luck.
To use an icon instead of a symbol, just swap out the path element (that the symbol generator is called on) with an image element.
Given a data set, D, with elements like {src: http.myImageURL, x: 10, y : 20} it would look something like this:
var svg = d3.select('body').append('svg');
svg.append('g').selectAll('.myPoint')
.data(D)
.enter()
.append('image')
.attr("xlink:href", function(d){ return d.src })
.attr("x", function(d){ return d.x })
.attr("y", function(d){ return d.y })
.attr("width", 16)
.attr("height", 16);