I have multiple svg groups (each containing a circle and text) which I am dragging via d3-drag from an initial position. I have a rectangular hit zone that I only want one of these draggable groups in at a time. So whenever two groups are in the hit zone, I would like the first group that was in the hit zone to fade away and reappear in its initial position.
I have tried doing this via a function which translates the group back to its initial position by finding the current position of the circle shape and translating like:
translate(${-current_x}, ${-current_y})
This does translate the group back to the (0,0) position, so I have to offset by its initial position. I do this by setting the initial x and y values of the circle shape as attributes in the circle element and incorporating these into the translation:
translate(${-current_x + initial_x}, ${-current_y + initial_y})
Here is a block of my attempt:
https://bl.ocks.org/interwebjill/fb9b0d648df769ed72aeb2755d3ff7d5
And here it is in snippet form:
const circleRadius = 40;
const variables = ['one', 'two', 'three', 'four'];
const inZone = [];
// DOM elements
const svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500)
const dragDockGroup = svg.append('g')
.attr('id', 'draggables-dock');
const dock = dragDockGroup.selectAll('g')
.data(variables)
.enter().append("g")
.attr("id", (d, i) => `dock-${variables[i]}`);
dock.append("circle")
.attr("cx", (d, i) => circleRadius * (2 * i + 1))
.attr("cy", circleRadius)
.attr("r", circleRadius)
.style("stroke", "none")
.style("fill", "palegoldenrod");
dock.append("text")
.attr("x", (d, i) => circleRadius * (2 * i + 1))
.attr("y", circleRadius)
.attr("text-anchor", "middle")
.style("fill", "white")
.text((d, i) => variables[i]);
const draggablesGroup = svg.append('g')
.attr('id', 'draggables');
const draggables = draggablesGroup.selectAll('g')
.data(variables)
.enter().append("g")
.attr("id", (d, i) => variables[i])
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded));
draggables.append('circle')
.attr("cx", (d, i) => circleRadius * (2 * i + 1))
.attr("cy", circleRadius)
.attr("initial_x", (d, i) => circleRadius * (2 * i + 1))
.attr("initial_y", circleRadius)
.attr("r", circleRadius)
.style("stroke", "orange")
.style("fill", "yellowgreen");
draggables.append("text")
.attr("x", (d, i) => circleRadius * (2 * i + 1))
.attr("y", circleRadius)
.attr("text-anchor", "middle")
.style("fill", "white")
.text((d, i) => variables[i]);
svg.append('rect')
.attr("x", 960/2)
.attr("y", 0)
.attr("width", 100)
.attr("height", 500/2)
.attr("fill-opacity", 0)
.style("stroke", "#848276")
.attr("id", "hitZone");
// functions
function dragStarted() {
d3.select(this).raise().classed("active", true);
}
function dragged() {
d3.select(this).select("text").attr("x", d3.event.x).attr("y", d3.event.y);
d3.select(this).select("circle").attr("cx", d3.event.x).attr("cy", d3.event.y);
}
function dragEnded() {
d3.select(this).classed("active", false);
d3.select(this).lower();
let hit = d3.select(document.elementFromPoint(d3.event.sourceEvent.clientX, d3.event.sourceEvent.clientY)).attr("id");
if (hit == "hitZone") {
inZone.push(this.id);
if (inZone.length > 1) {
let resetVar = inZone.shift();
resetCircle(resetVar);
}
}
d3.select(this).raise();
}
function resetCircle(resetVar) {
let current_x = d3.select(`#${resetVar}`)
.select('circle')
.attr('cx');
let current_y = d3.select(`#${resetVar}`)
.select('circle')
.attr('cy');
let initial_x = d3.select(`#${resetVar}`)
.select('circle')
.attr('initial_x');
let initial_y = d3.select(`#${resetVar}`)
.select('circle')
.attr('initial_y');
d3.select(`#${resetVar}`)
.transition()
.duration(2000)
.style('opacity', 0)
.transition()
.duration(2000)
.attr('transform', `translate(${-current_x}, ${-current_y})`)
.transition()
.duration(2000)
.style('opacity', 1);
}
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
<script src="https://d3js.org/d3.v5.min.js"></script>
Here are the problems:
While using translate(${-current_x}, ${-current_y}) works, when I try using translate(${-current_x + initial_x}, ${-current_y + initial_y}), the translation uses very large negative numbers (for example, translate(-52640, -4640)).
While using translate(${-current_x}, ${-current_y}) works, when I try to drag this translated group again, the group immediately repeats the previous translate(${-current_x}, ${-current_y})
Your code runs into difficulties because you are positioning both the g elements and the children text and circles.
Circles and text are originally positioned by x/y attributes:
draggables.append('circle')
.attr("cx", (d, i) => circleRadius * (2 * i + 1))
.attr("cy", circleRadius)
draggables.append("text")
.attr("x", (d, i) => circleRadius * (2 * i + 1))
.attr("y", circleRadius)
Drag events move the circles and text here:
d3.select(this).select("text").attr("x", d3.event.x).attr("y", d3.event.y);
d3.select(this).select("circle").attr("cx", d3.event.x).attr("cy", d3.event.y);
And then we reset the circles and text by trying to offset the parent g with a transform:
d3.select(`#${resetVar}`).attr('transform', `translate(${-current_x}, ${-current_y})`)
Where current_x and current_y are the current x,y values for the circles and text. We have also stored the initial x,y values for the text, but altogether, this becomes a more convoluted then necessary as we have two competing sets of positioning coordinates.
This can be simplified a fair amount. Instead of positioning both the text and the circles, simply apply a transform to the parent g holding both the circle and the text. Then when we drag we update the transform, and when we finish, we reset the transform.
Now we have no modification of x,y/cx,cy attributes and transforms for positioning the elements relative to one another. No offsets and the parent g's transform will always represent the position of the circle and the text.
Below I keep track of the original transform with the datum (not an element attribute) - normally I would use a property of the datum, but you have non-object data, so I just replace the datum with the original transform:
const circleRadius = 40;
const variables = ['one', 'two', 'three', 'four'];
const inZone = [];
// DOM elements
const svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500)
const dragDockGroup = svg.append('g')
.attr('id', 'draggables-dock');
// Immovable placemarkers:
const dock = dragDockGroup.selectAll('g')
.data(variables)
.enter().append("g")
.attr("id", (d, i) => `dock-${variables[i]}`);
dock.append("circle")
.attr("cx", (d, i) => circleRadius * (2 * i + 1))
.attr("cy", circleRadius)
.attr("r", circleRadius)
.style("stroke", "none")
.style("fill", "palegoldenrod");
dock.append("text")
.attr("x", (d, i) => circleRadius * (2 * i + 1))
.attr("y", circleRadius)
.attr("text-anchor", "middle")
.style("fill", "white")
.text((d, i) => variables[i]);
// Dragables
const draggablesGroup = svg.append('g')
.attr('id', 'draggables');
const draggables = draggablesGroup.selectAll('g')
.data(variables)
.enter()
.append("g")
.datum(function(d,i) {
return "translate("+[circleRadius * (2 * i + 1),circleRadius]+")";
})
.attr("transform", (d,i) => d)
.attr("id", (d, i) => variables[i])
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded));
draggables.append('circle')
.attr("r", circleRadius)
.style("stroke", "orange")
.style("fill", "yellowgreen");
draggables.append("text")
.attr("text-anchor", "middle")
.style("fill", "white")
.text((d, i) => variables[i]);
svg.append('rect')
.attr("x", 960/2)
.attr("y", 0)
.attr("width", 100)
.attr("height", 500/2)
.attr("fill-opacity", 0)
.style("stroke", "#848276")
.attr("id", "hitZone");
// functions
function dragStarted() {
d3.select(this).raise();
}
function dragged() {
d3.select(this).attr("transform","translate("+[d3.event.x,d3.event.y]+")")
}
function dragEnded() {
d3.select(this).lower();
let hit = d3.select(document.elementFromPoint(d3.event.sourceEvent.clientX, d3.event.sourceEvent.clientY)).attr("id");
if (hit == "hitZone") {
inZone.push(this.id);
if (inZone.length > 1) {
let resetVar = inZone.shift();
resetCircle(resetVar);
}
}
d3.select(this).raise();
}
function resetCircle(resetVar) {
d3.select(`#${resetVar}`)
.transition()
.duration(500)
.style('opacity', 0)
.transition()
.duration(500)
.attr("transform", (d,i) => d)
.transition()
.duration(500)
.style('opacity', 1);
}
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
<script src="https://d3js.org/d3.v5.min.js"></script>
Related
I want tooltips to appear next to my mouse when it hovers over a node. I tried solutions I found on SO, but so far, only got this solution by Boxun to work, although it's not quite what I had in mind (D3.js: Position tooltips using element position, not mouse position?).
I was wondering why in my listener function,
.on('mousemove', function(d) {})
, the functions
Tooltips
.style("left", d3.mouse(this)[0])
.style("top", (d3.mouse(this)[1]))
or
Tooltips
.style("left", d3.event.pageX + 'px')
.style("top", d3.event.pageY + 'px')
shows up on top of the svg instead of where my mouse is.
From reading the answers to the link above, I think I have to transform my coordinates somehow, but I was not able to get that to work.
Here, I am using d3.event.pageX and my mouse is over cherry node.
import * as d3_base from "d3";
import * as d3_dag from "d3-dag";
const d3 = Object.assign({}, d3_base, d3_dag);
drawDAG({
graph: [
["apples", "banana"],
["cherry", "tomato"],
["cherry", "avocado"],
["squash", "banana"],
["lychee", "cherry"],
["dragonfruit", "mango"],
["tomato", "mango"]
]
})
async function drawDAG(response) {
loadDag(response['graph'])
.then(layoutAndDraw())
.catch(console.error.bind(console));
}
async function loadDag(source) {
const [key, reader] = ["zherebko", d3_dag.dagConnect().linkData(() => ({}))]
return reader(source);
}
function layoutAndDraw() {
const width = 800;
const height = 800;
const d3 = Object.assign({}, d3_base, d3_dag);
function sugiyama(dag) {
const layout = d3.sugiyama()
.size([width, height])
.layering(d3.layeringSimplex())
.decross(d3.decrossOpt())
.coord(d3.coordVert());
layout(dag);
draw(dag);
}
return sugiyama;
function draw(dag) {
// Create a tooltip
const Tooltip = d3.select("root")
.append("div")
.attr("class", "tooltip")
.style('position', 'absolute')
.style("opacity", 0)
.style("background-color", "black")
.style("padding", "5px")
.style('text-align', 'center')
.style('width', 60)
.style('height', 30)
.style('border-radius', 10)
.style('color', 'white')
// This code only handles rendering
const nodeRadius = 100;
const svgSelection = d3.select("root")
.append("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", `${-nodeRadius} ${-nodeRadius} ${width + 2 * nodeRadius} ${height + 2 * nodeRadius}`);
const defs = svgSelection.append('defs');
const steps = dag.size();
const interp = d3.interpolateRainbow;
const colorMap = {};
dag.each((node, i) => {
colorMap[node.id] = interp(i / steps);
});
// How to draw edges
const line = d3.line()
.curve(d3.curveCatmullRom)
.x(d => d.x)
.y(d => d.y);
// Plot edges
svgSelection.append('g')
.selectAll('path')
.data(dag.links())
.enter()
.append('path')
.attr('d', ({
data
}) => line(data.points))
.attr('fill', 'none')
.attr('stroke-width', 3)
.attr('stroke', ({
source,
target
}) => {
const gradId = `${source.id}-${target.id}`;
const grad = defs.append('linearGradient')
.attr('id', gradId)
.attr('gradientUnits', 'userSpaceOnUse')
.attr('x1', source.x)
.attr('x2', target.x)
.attr('y1', source.y)
.attr('y2', target.y);
grad.append('stop').attr('offset', '0%').attr('stop-color', colorMap[source.id]);
grad.append('stop').attr('offset', '100%').attr('stop-color', colorMap[target.id]);
return `url(#${gradId})`;
});
// Select nodes
const nodes = svgSelection.append('g')
.selectAll('g')
.data(dag.descendants())
.enter()
.append('g')
.attr('width', 100)
.attr('height', 100)
.attr('transform', ({
x,
y
}) => `translate(${x}, ${y})`)
.on('mouseover', function(d) {
Tooltip
.style('opacity', .8)
.text(d.id)
})
.on('mouseout', function(d) {
Tooltip
.style('opacity', 0)
})
.on('mousemove', function(d) {
var matrix = this.getScreenCTM()
.translate(+this.getAttribute("cx"), +this.getAttribute("cy"));
Tooltip
.html(d.id)
.style("left", (window.pageXOffset + matrix.e - 50) + "px")
.style("top", (window.pageYOffset + matrix.f - 60) + "px");
})
// Plot node circles
nodes.append('rect')
.attr('y', -30)
.attr('x', (d) => {
return -(d.id.length * 15 / 2)
})
.attr('rx', 10)
.attr('ry', 10)
.attr('width', (d) => {
return d.id.length * 15;
})
.attr('height', (d) => 60)
.attr('fill', n => colorMap[n.id])
// Add text to nodes
nodes.append('text')
.text(d => {
let id = '';
d.id.replace(/_/g, ' ').split(' ').forEach(str => {
if (str !== 'qb')
id += str.charAt(0).toUpperCase() + str.substring(1) + '\n';
});
return id;
})
.attr('font-size', 25)
.attr('font-weight', 'bold')
.attr('font-family', 'sans-serif')
.attr('text-anchor', 'middle')
.attr('alignment-baseline', 'middle')
.attr('fill', 'white')
.attr();
}
}
You can try using this instead, this will make sure that the tooltip is displayed on the exact mouse position.
d3.event.offsetY
This question already has an answer here:
reverse how vertical bar chart is drawn
(1 answer)
Closed 2 years ago.
This is a d3 bar graph.
I am trying to animate bars grow from bottom to upwards. how to achieve it?
also, how to make labels ie text of bars stay at the top of the bars and move along with them?
currently, bars are starting at top and going down to zero.
d3.select("body")
.append("h2")
.text("BAR GRAPH");
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
const w = 500;
const h = 120;
const svg = d3
.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var bars = svg
.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d, i) => i * 30)
.attr("y", (d, i) => h - 3 * d)
.attr("width", 25)
.attr("height", (d, i) => 3 * d)
.attr("fill", "navy");
bars
.transition()
.duration(400)
.attr("y", h)
.attr("height", (d, i) => 3 * d);
svg
.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(d => d)
.attr("x", (d, i) => i * 30)
.attr("y", (d, i) => h - 3 * d - 3)
.style("font-size", "25px")
.style("fill", "red")
.append("title")
.text(d => d);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Your code was almost on point for the bars, you just need to change the order of the affectation to the y attribute of bars and set the height attribute of bars to 0 at the start.
I just modified the three lines in your example and marked them with comments to show what to change to achieve your desired effect.
To make the labels follow the bars you can just add a transition on them with the same duration ! I added some lines in the last block and modified one to achieve the effect.
Hope it helps :)
d3.select("body")
.append("h2")
.text("BAR GRAPH");
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
const w = 500;
const h = 120;
const svg = d3
.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var bars = svg
.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d, i) => i * 30)
.attr("y", h) // modified here
.attr("width", 25)
.attr("height", 0) // modified here
.attr("fill", "navy");
bars
.transition()
.duration(400)
.attr("y", (d, i) => h - 3 * d) // modified here
.attr("height", (d, i) => 3 * d);
// modified a bit here
var labels = svg
.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(d => d)
.attr("x", (d, i) => i * 30)
.attr("y", h) // modified here
.style("font-size", "25px")
.style("fill", "red");
labels
.append("title")
.text(d => d);
labels
.transition() // added here
.duration(400) // added here
.attr("y", (d, i) => h - 3 * d - 3) // added here
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
I have a bubble chart in which I make bubbles in the following way:
var circles = svg.selectAll(null)
.data(data)
.enter()
.append("circle")
.attr("cx", width / 2)
.attr("cy", height / 2)
.attr("opacity", 0.3)
.attr("r", 20)
.style("fill", function(d){
if(+d.student_percentile <= 40){
return "red";
}
else if(+d.student_percentile > 40 && +d.student_percentile <= 70){
return "yellow";
}
else{
return "green";
}
})
.attr("cx", function(d) {
return xscale(+d.student_percentile);
})
.attr("cy", function(d) {
return yscale(+d.rank);
})
.on('mouseover', function(d, i) {
d3.select(this)
.transition()
.duration(1000)
.ease(d3.easeBounce)
.attr("r", 32)
.style("fill", "orange")
.style("cursor", "pointer")
.attr("text-anchor", "middle");
texts.filter(function(e) {
return +e.rank === +d.rank;
})
.attr("font-size", "20px");
}
)
.on('mouseout', function(d, i) {
d3.select(this).transition()
.style("opacity", 0.3)
.attr("r", 20)
.style("fill", "blue")
.style("cursor", "default");
texts.filter(function(e) {
return e.rank === d.rank;
})
.transition()
.duration(1000)
.ease(d3.easeBounce)
.attr("font-size", "10px")
});
I have given colors red, yellow, green to the bubbles based on the student percentile. On mouseover, I change the color of bubble to 'orange'. Now the issue is, on mouseout, currently I am making colors of bubbles as 'blue' but I want to assign the same color to them as they had before mouseover, i.e., red/green/yellow. How do I find out what color, the bubbles had?
One way is to obviously check the percentile of student and then give color based on that(like I have initially assigned green/yellow/red colors), but is there any direct way of finding the actual color of bubble?
Thanks in advance!
There are several ways for doing this.
Solution 1:
The most obvious one is declaring a variable...
var previous;
... to which you assign to the colour of the element on the mouseover...
previous = d3.select(this).style("fill");
... and reuse in the mouseout:
d3.select(this).style("fill", previous)
Here is a demo:
var svg = d3.select("svg");
var colors = d3.scaleOrdinal(d3.schemeCategory10);
var previous;
var circles = svg.selectAll(null)
.data(d3.range(5))
.enter()
.append("circle")
.attr("cy", 75)
.attr("cx", function(d, i) {
return 50 + 50 * i
})
.attr("r", 20)
.style("fill", function(d, i) {
return colors(i)
})
.on("mouseover", function() {
previous = d3.select(this).style("fill");
d3.select(this).style("fill", "#222");
}).on("mouseout", function() {
d3.select(this).style("fill", previous)
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
Solution 2:
However, D3 has a nice feature, called local variables. You simply have to define the local...
var local = d3.local();
..., set it on the mouseover:
local.set(this, d3.select(this).style("fill"));
And then, get its value on the mouseout:
d3.select(this).style("fill", local.get(this));
Here is the demo:
var svg = d3.select("svg");
var colors = d3.scaleOrdinal(d3.schemeCategory10);
var local = d3.local();
var circles = svg.selectAll(null)
.data(d3.range(5))
.enter()
.append("circle")
.attr("cy", 75)
.attr("cx", function(d, i) {
return 50 + 50 * i
})
.attr("r", 20)
.style("fill", function(d, i) {
return colors(i)
})
.on("mouseover", function() {
local.set(this, d3.select(this).style("fill"));
d3.select(this).style("fill", "#222");
}).on("mouseout", function() {
d3.select(this).style("fill", local.get(this));
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
Solution 3:
Since DDD (also known as D3) means data-driven documents, you can use the bound datum to get the previous colour.
First, you set it (in my demo, using the colors scale):
.style("fill", function(d, i) {
return d.fill = colors(i);
})
And then you use it in the mouseout. Check the demo:
var svg = d3.select("svg");
var colors = d3.scaleOrdinal(d3.schemeCategory10);
var circles = svg.selectAll(null)
.data(d3.range(5).map(function(d) {
return {
x: d
}
}))
.enter()
.append("circle")
.attr("cy", 75)
.attr("cx", function(d) {
return 50 + 50 * d.x
})
.attr("r", 20)
.style("fill", function(d, i) {
return d.fill = colors(i);
})
.on("mouseover", function() {
d3.select(this).style("fill", "#222");
}).on("mouseout", function(d) {
d3.select(this).style("fill", d.fill);
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
For using this solution #3, the element's datum has to be an object.
PS: drop that bunch of if...else for setting the style of the bubbles. Use a scale instead.
I am trying to append images on rectangles and images locations are present in my array named arrFileUrl. Nodes of this color are white and I want to append these images on each of the generated rectangles. How can I do this?
var arrFileUrl = [], arrBrightness = [], arrPattern = [], arrSize = [];
var width = 1260,
height = 1200;
var fill = d3.scale.category10();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.csv("data/Images.csv", function(error, data) {
data.forEach(function(d) {
arrFileUrl.push(d['FingerImageName']);
});
var nodes = d3.range(arrSize.length).map(function(i) {
return {index: i};
});
var force = d3.layout.force()
.nodes(nodes)
.gravity(0.05)
.charge(-1700)
.friction(0.5)
.size([width, height])
.on("tick", tick)
.start();
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("rect")
.attr("class", "node")
.attr("width", 120)
.attr("height", 160)
.style("fill", "#fff")
.style("stroke", "black")
.call(force.drag);
node.append("image")
.attr("xlink:href", "https://github.com/favicon.ico")
.attr("x", 16)
.attr("y", 16)
.attr("width", 100)
.attr("height", 120);
svg.style("opacity", 1e-6)
.transition()
.duration(1000)
.style("opacity", 1);
function tick(e) {
// Push different nodes in different directions for clustering.
var k = 6 * e.alpha;
nodes.forEach(function(o, i) {
o.y += i & 1 ? k : -k;
o.x += i & 2 ? k : -k;
});
node.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
}
});
Do it this way:
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g");//make groups
//add rectangle to the group
node.append("rect")
.attr("class", "node")
.attr("width", 120)
.attr("height", 160)
.style("fill", "#fff")
.style("stroke", "black")
.call(force.drag);
//add image to the group
node.append("image")
.attr("xlink:href", "https://github.com/favicon.ico")
.attr("x", 16)
.attr("y", 16)
.attr("width", 100)
.attr("height", 120);
Tick function translate the full group
function tick(e) {
// Push different nodes in different directions for clustering.
var k = 6 * e.alpha;
nodes.forEach(function(o, i) {
o.y += i & 1 ? k : -k;
o.x += i & 2 ? k : -k;
});
//do transform
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
}
Problem in your code:
You were appending the image inside the rectangle DOM that is reason why image is not visible.
In tick function you are moving the x and y alone and not of the image, the approach should have been moving both of them in a group and translating the group as done above.
I have a list of n associative arrays.
[{'path': 'somepath', 'relevant': [7, 8, 9]}, {'path': 'anotherpath', 'relevant': [9], ...}
Within a large SVG, I want to: a) create rects ("buckets") whose dimensions are proportional to the lengths of their 'relevant' sublists, and b) create rects ("pieces"), for each of the elements in the sublists, placed "inside" their respective buckets.
After reading Mike Bostock's response to a similar question, I'm sure that I need to use group ("g") elements to group the pieces together. I can get the code below to produce the DOM tree that I want, but I'm stumped on how to code the y values of the pieces. At the point where I need the value, D3 is iterating over the subarrays. How can I get the index of the current subarray that I'm iterating over from inside it when i no longer points to the index within the larger array?
var piece_bukcets = svg.selectAll("g.piece_bucket")
.data(files)
.enter()
.append("g")
.attr("class", "piece_bucket")
.attr("id", function (d, i) { return ("piece_bucket" + i) })
.append("rect")
.attr("y", function (d, i) { return (i * 60) + 60; })
.attr("class", "bar")
.attr("x", 50)
.attr("width", function (d) {
return 10 * d["relevant"].length;
})
.attr("height", 20)
.attr("fill", "red")
.attr("opacity", 0.2)
var pieces = svg.selectAll("g.piece_bucket")
.selectAll("rect.piece")
.data( function (d) { return d["relevant"]; })
.enter()
.append("rect")
.attr("class", "piece")
.attr("id", function (d) { return ("piece" + d) })
.attr("y", ????) // <<-- How do I get the y value of d's parent?
.attr("x", function (d, i) { return i * 10; })
.attr("height", 10)
.attr("width", 10)
.attr("fill", "black");
Is there a method on d available to find the index of the node it's currently inside? In this case, is there a method I can call on a "piece" to find the index of its parent "bucket"?
You can use the secret third argument to the function:
.attr("y", function(d, i, j) {
// j is the i of the parent
return (j * 60) + 60;
})
There's a simpler way however. You can simply translate the g element and everything you add to it will fall into place.
var piece_buckets = svg.selectAll("g.piece_bucket")
.data(files)
.enter()
.append("g")
.attr("class", "piece_bucket")
.attr("transform", function(d, i) {
return "translate(0," + ((i*60) + 60) + ")";
})
.attr("id", function (d, i) { return ("piece_bucket" + i) });
piece_buckets.append("rect")
.attr("class", "bar")
.attr("x", 50)
.attr("width", function (d) {
return 10 * d["relevant"].length;
})
.attr("height", 20)
.attr("fill", "red")
.attr("opacity", 0.2);
var pieces = piece_buckets.selectAll("rect.piece")
.data(function (d) { return d["relevant"]; })
.enter()
.append("rect")
.attr("class", "piece")
.attr("id", function (d) { return ("piece" + d); })
.attr("x", function (d, i) { return i * 10; })
.attr("height", 10)
.attr("width", 10)
.attr("fill", "black");