I basically have some nested data like so:
const shapeGroups = [
{
title: 'shapeGroup_01',
color: 'blue',
shapes: [
{
shape:'rect',
width: 30,
height: 100,
x: 250,
y: 450
}
]
},
{
title: 'shapeGroup_01',
color: 'blue',
shapes: [
{
shape:'rect',
width: 10,
height: 40,
x: 350,
y:50
}
]
}
]
What I'm interested in doing is setting the color in each rect as it's defined in the shapeGroup's color property.
const shapeGroups = [{
title: 'shapeGroup_01',
color: 'blue',
shapes: [{
shape: 'rect',
width: 30,
height: 100,
x: 250,
y: 450
}]
},
{
title: 'shapeGroup_01',
color: 'blue',
shapes: [{
shape: 'rect',
width: 10,
height: 40,
x: 350,
y: 50
}]
}
]
const stage = d3.select('#stageContainer')
.append('svg')
.attr('id', '#stage')
.attr('width', 1000)
.attr('height', 1000)
const groups = stage
.selectAll('.group')
.data(shapeGroups)
const groupEnter = groups
.enter()
.append('g')
.attr('class', 'group');
const getGroup = group => group.shapes;
const createShape = shape => document.createElementNS(d3.namespaces.svg, shape.shape)
groupEnter
.selectAll('.shape')
.data(getGroup)
.enter()
.append(createShape)
.attr('fill', 'red') // I want the color as defined in the current group
.attr('width', d => d.width)
.attr('height', d => d.height)
.attr('x', d => d.x)
.attr('y', d => d.y)
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="stageContainer"></div>
In other words, I need a way to either set color at the group level, or somehow access the parent datum object when I'm appending individual .shapes.
As you can read in my answer here, you cannot access the parent's datum or its index from inside a selection in D3 v4/v5.
A solution here is getting the datum of the parentNode:
.attr('fill', (_, i, n) => d3.select(n[i].parentNode).datum().color)
Here is your code with that change (I'm making the two rectangles with different colours and the SVG smaller, for better visualising it):
const shapeGroups = [{
title: 'shapeGroup_01',
color: 'blue',
shapes: [{
shape: 'rect',
width: 30,
height: 100,
x: 250,
y: 50
}]
},
{
title: 'shapeGroup_01',
color: 'green',
shapes: [{
shape: 'rect',
width: 10,
height: 40,
x: 350,
y: 20
}]
}
];
const stage = d3.select('body')
.append('svg')
.attr('width', 400)
.attr('height', 400)
const groups = stage
.selectAll('.group')
.data(shapeGroups)
const groupEnter = groups
.enter()
.append('g')
.attr('class', 'group');
const getGroup = group => group.shapes;
const createShape = shape => document.createElementNS(d3.namespaces.svg, shape.shape)
groupEnter
.selectAll('.shape')
.data(getGroup)
.enter()
.append(createShape)
.attr('fill', (_, i, n) => d3.select(n[i].parentNode).datum().color)
.attr('width', d => d.width)
.attr('height', d => d.height)
.attr('x', d => d.x)
.attr('y', d => d.y)
<script src="https://d3js.org/d3.v5.min.js"></script>
However, the simplest solution by far is just setting the fill to the parent selection itself:
const groupEnter = groups
.enter()
.append('g')
.attr('class', 'group')
.attr('fill', d => d.color);
Here is the demo:
const shapeGroups = [{
title: 'shapeGroup_01',
color: 'blue',
shapes: [{
shape: 'rect',
width: 30,
height: 100,
x: 250,
y: 50
}]
},
{
title: 'shapeGroup_01',
color: 'green',
shapes: [{
shape: 'rect',
width: 10,
height: 40,
x: 350,
y: 20
}]
}
];
const stage = d3.select('body')
.append('svg')
.attr('width', 400)
.attr('height', 400)
const groups = stage
.selectAll('.group')
.data(shapeGroups)
const groupEnter = groups
.enter()
.append('g')
.attr('class', 'group')
.attr('fill', d=>d.color);
const getGroup = group => group.shapes;
const createShape = shape => document.createElementNS(d3.namespaces.svg, shape.shape)
groupEnter
.selectAll('.shape')
.data(getGroup)
.enter()
.append(createShape)
.attr('width', d => d.width)
.attr('height', d => d.height)
.attr('x', d => d.x)
.attr('y', d => d.y)
<script src="https://d3js.org/d3.v5.min.js"></script>
Related
I am trying to make a horizontal stacked bar chart, starting with this code snippet, updating to d3 v7. Instead of getting a neatly stacked bar chart, each subsequent bar in a stack is getting offset vertically down from where it should be. When I inspect the yScale value, I get the expected value, so I'm extra-confused about this behavior.
I'd include just the relevant piece of the puzzle, but I honestly don't know where my problem is -- am I appending to the wrong 'g' element? Using enter() on the wrong piece of data?
<script src="https://d3js.org/d3.v7.min.js"></script>
<body>
<div id="bar_chart">
<script>
var data = [{
dep_time: "5:30",
risk: 100,
details: [{
time: 19,
source: 'Drive'
},
{
time: 10,
source: 'Margin'
},
{
time: 42,
source: 'Full'
},
{
time: 35,
source: 'Crossing'
},
{
time: 23,
source: 'Drive'
}
]
},
{
dep_time: "6:20",
risk: 80,
details: [{
time: 25,
source: 'Drive'
},
{
time: 1,
source: 'Margin'
},
{
time: 38,
source: 'Full'
},
{
time: 35,
source: 'Crossing'
},
{
time: 25,
source: 'Drive'
}
]
},
{
dep_time: "7:10",
risk: 5,
details: [{
time: 8,
source: 'Drive'
},
{
time: 28,
source: 'Margin'
},
{
time: 38,
source: 'Full'
},
{
time: 35,
source: 'Crossing'
},
{
time: 18,
source: 'Drive'
}
]
}
];
var chartContainer = '.chart-container';
var units = [];
var xMax = 0;
data.forEach(function(s) {
var total = 0;
s.details.forEach(function(s) {
s["x0"] = total; //Abs left
s["x"] = s.time; //Width
s["x1"] = total + s.time; //Abs right
total = total + s.time;
if (total > xMax) xMax = total;
});
s["y"] = s.dep_time;
units.push(s.dep_time);
});
//Need it to look like: newdata = [(Drive) [19, 25, 32.] Margin [0, 1, 28]. Full [42, 38, 38]. Crossing [35, 35, 35]. Drive [23, 25, 18].]
//So it's a row in the array for each column of data.
//re-arrange the data so it makes more sense to d3 (and less sense to any sane human)
var newdata = [];
for (var i = 0; i < data[0].details.length; i++) {
var row = [];
data.forEach(function(s) {
row.push({
x: s.details[i].x,
y: s.dep_time,
x0: s.details[i].x0
});
});
newdata.push(row);
}
console.log("newdata");
console.log(newdata);
var margins = {
left: 50,
bottom: 50,
top: 25,
right: 25
};
var sizes = {
width: 500,
height: 150
};
var width = sizes.width - margins.left - margins.right;
var height = sizes.height - margins.bottom - margins.top;
var svg = d3.select("#bar_chart")
.append('svg')
.attr('width', width + margins.left + margins.right)
.attr('height', height + margins.bottom)
.append('g')
.attr('transform', 'translate(' + margins.left + ', ' + margins.top + ")");
var yScale = d3.scaleBand()
.domain(units)
.rangeRound([0, height]);
var yAxis = d3.axisLeft(yScale);
var yAxisG = svg.append("g")
.attr("transform", "translate(0,0)")
.attr("id", "yaxis")
.call(yAxis);
const xScale = d3.scaleLinear()
.domain([0, xMax])
.range([0, width]);
var xAxis = d3.axisBottom(xScale);
var xAxisG = svg.append("g")
.attr("transform", "translate(0, " + height + ")")
.attr("id", "xaxis")
.call(xAxis
.ticks(8));
var bar_colors = ['red', 'purple', 'green', 'lightblue', 'yellow'];
var colors = function(i) {
return bar_colors[i];
}
var groups = svg.selectAll('g')
.data(newdata)
//.exit()
.append('g')
.style('fill', function(d, i) {
console.log("d");
console.log(d);
//console.log("i"); console.log(i);
return colors(i);
});
groups.selectAll('rect')
.data(function(d) {
//console.log(d);
return d;
})
.enter()
.append('rect')
.attr('x', function(d) {
//console.log("x0"); console.log(d.x0);
return xScale(d.x0);
})
.attr('y', function(d, i) {
//console.log(yScale(d.y));
//console.log(i);
return yScale(d.y);
})
.attr('height', 10) //function (d) {return yScale.rangeBand();})
.attr('width', function(d) {
return xScale(d.x);
});
</script>
</div>
</body>
You are appending the rectangles to existing translated groups (the axes) because of this:
var groups = svg.selectAll("g")
Instead, select nothing (and also remember to enter the selection):
var groups = svg.selectAll(null)
Here's your code with that change:
<script src="https://d3js.org/d3.v7.min.js"></script>
<body>
<div id="bar_chart">
<script>
var data = [{
dep_time: "5:30",
risk: 100,
details: [{
time: 19,
source: 'Drive'
},
{
time: 10,
source: 'Margin'
},
{
time: 42,
source: 'Full'
},
{
time: 35,
source: 'Crossing'
},
{
time: 23,
source: 'Drive'
}
]
},
{
dep_time: "6:20",
risk: 80,
details: [{
time: 25,
source: 'Drive'
},
{
time: 1,
source: 'Margin'
},
{
time: 38,
source: 'Full'
},
{
time: 35,
source: 'Crossing'
},
{
time: 25,
source: 'Drive'
}
]
},
{
dep_time: "7:10",
risk: 5,
details: [{
time: 8,
source: 'Drive'
},
{
time: 28,
source: 'Margin'
},
{
time: 38,
source: 'Full'
},
{
time: 35,
source: 'Crossing'
},
{
time: 18,
source: 'Drive'
}
]
}
];
var chartContainer = '.chart-container';
var units = [];
var xMax = 0;
data.forEach(function(s) {
var total = 0;
s.details.forEach(function(s) {
s["x0"] = total; //Abs left
s["x"] = s.time; //Width
s["x1"] = total + s.time; //Abs right
total = total + s.time;
if (total > xMax) xMax = total;
});
s["y"] = s.dep_time;
units.push(s.dep_time);
});
//Need it to look like: newdata = [(Drive) [19, 25, 32.] Margin [0, 1, 28]. Full [42, 38, 38]. Crossing [35, 35, 35]. Drive [23, 25, 18].]
//So it's a row in the array for each column of data.
//re-arrange the data so it makes more sense to d3 (and less sense to any sane human)
var newdata = [];
for (var i = 0; i < data[0].details.length; i++) {
var row = [];
data.forEach(function(s) {
row.push({
x: s.details[i].x,
y: s.dep_time,
x0: s.details[i].x0
});
});
newdata.push(row);
}
var margins = {
left: 50,
bottom: 50,
top: 25,
right: 25
};
var sizes = {
width: 500,
height: 150
};
var width = sizes.width - margins.left - margins.right;
var height = sizes.height - margins.bottom - margins.top;
var svg = d3.select("#bar_chart")
.append('svg')
.attr('width', width + margins.left + margins.right)
.attr('height', height + margins.bottom)
.append('g')
.attr('transform', 'translate(' + margins.left + ', ' + margins.top + ")");
var yScale = d3.scaleBand()
.domain(units)
.rangeRound([0, height]);
var yAxis = d3.axisLeft(yScale);
var yAxisG = svg.append("g")
.attr("transform", "translate(0,0)")
.attr("id", "yaxis")
.call(yAxis);
const xScale = d3.scaleLinear()
.domain([0, xMax])
.range([0, width]);
var xAxis = d3.axisBottom(xScale);
var xAxisG = svg.append("g")
.attr("transform", "translate(0, " + height + ")")
.attr("id", "xaxis")
.call(xAxis
.ticks(8));
var bar_colors = ['red', 'purple', 'green', 'lightblue', 'yellow'];
var colors = function(i) {
return bar_colors[i];
}
var groups = svg.selectAll(null)
.data(newdata)
.enter()
.append('g')
.style('fill', function(d, i) {
return colors(i);
});
groups.selectAll('rect')
.data(function(d) {
//console.log(d);
return d;
})
.enter()
.append('rect')
.attr('x', function(d) {
return xScale(d.x0);
})
.attr('y', function(d, i) {
return yScale(d.y);
})
.attr('height', 10) //function (d) {return yScale.rangeBand();})
.attr('width', function(d) {
return xScale(d.x);
});
</script>
</div>
</body>
How do I achieve this with D3? desired output
It's easy to have two layers of pie charts https://embed.plnkr.co/plunk/2p0zmp
Or to use d3 network with graph and nodes, http://using-d3js.com/05_08_links.html
but how could I overlay the concept of "nodes" and "links" onto these arcs of a piechart?
What kind of data structure is preferred?
{
nodes: [
{
layer: 1,
data: [
{name: A },
{name: B },
{name: C },
{name: D }
]
},
{
layer: 2,
data: [
{name: E },
{name: F },
{name: G }
]
}
],
links: [{ source: 'B', target: 'E'}, { source: 'D', target: 'F'}]
}
This gets pretty close with what you're looking for. You can use some additional arc generators and arc.centroid() to get the positions for the start and ends of the links. Then you can use a link generator to draw the links. One drawback of this is that the links can overlap the nodes.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="https://d3js.org/d3.v7.js"></script>
</head>
<body>
<div id="chart"></div>
<script>
/*
set up
*/
const width = 700;
const height = 400;
const svg = d3.select('#chart')
.append('svg')
.attr('width', width)
.attr('height', height);
const g = svg.append('g')
.attr('transform', `translate(${width / 2},${height})`);
/*
data
*/
const level1Nodes = [
{ name: 'A', value: 50, level: 1, color: 'RoyalBlue' },
{ name: 'B', value: 50, level: 1, color: 'DarkOrange' },
{ name: 'C', value: 50, level: 1, color: 'DarkOrange' },
{ name: 'D', value: 30, level: 1, color: 'Gold' }
];
const level2Nodes = [
{ name: 'E', value: 75, level: 2, color: 'RoyalBlue' },
{ name: 'F', value: 75, level: 2, color: 'DarkOrange' },
{ name: 'G', value: 30, level: 2, color: 'RoyalBlue' },
];
const links = [
{ source: 'B', target: 'E'},
{ source: 'D', target: 'F'}
];
/*
pie generator
*/
const pie = d3.pie()
.value(d => d.value)
.startAngle(-Math.PI / 2)
.endAngle(Math.PI / 2)
.padAngle(Math.PI / 45);
// calculate the angles for the slices of the nodes
const slices = [
...pie(level1Nodes),
...pie(level2Nodes)
];
/*
arcs
*/
const level1InnerRadius = 130;
const level1OuterRadius = 200;
const level2InnerRadius = 270;
const level2OuterRadius = 340;
// for drawing the nodes
const level1Arc = d3.arc()
.innerRadius(level1InnerRadius)
.outerRadius(level1OuterRadius);
const level2Arc = d3.arc()
.innerRadius(level2InnerRadius)
.outerRadius(level2OuterRadius);
const levelToArc = new Map([
[1, level1Arc],
[2, level2Arc]
]);
// for positioning the links along the outside
// of the level 1 nodes and the inside of the
// level 2 nodes
const level1OuterArc = d3.arc()
.innerRadius(level1OuterRadius)
.outerRadius(level1OuterRadius);
const level2InnerArc = d3.arc()
.innerRadius(level2InnerRadius)
.outerRadius(level2InnerRadius);
/*
calculating position of links
*/
// Map from the name of a node to the data for its arc
const nameToSlice = d3.index(slices, d => d.data.name);
// get the start and end positions for each link
const linkPositions = links.map(({source, target}) => ({
source: level1OuterArc.centroid(nameToSlice.get(source)),
target: level2InnerArc.centroid(nameToSlice.get(target)),
}));
/*
drawing
*/
// nodes
g.append('g')
.selectAll('path')
.data(slices)
.join('path')
.attr('d', d => levelToArc.get(d.data.level)(d))
.attr('fill', d => d.data.color);
// node labels
const labelsGroup = g.append('g')
.attr('font-family', 'sans-serif')
.attr('font-weight', 'bold')
.attr('font-size', 30)
.selectAll('text')
.data(slices)
.join('text')
.attr('dominant-baseline', 'middle')
.attr('text-anchor', 'middle')
.attr('transform', d => `translate(${levelToArc.get(d.data.level).centroid(d)})`)
.text(d => d.data.name);
// links
g.append('g')
.selectAll('path')
.data(linkPositions)
.join('path')
.attr('d', d3.linkVertical())
.attr('fill', 'none')
.attr('stroke', 'DarkBlue')
.attr('stroke-width', 2);
// circles at the end of links
g.append('g')
.selectAll('circle')
.data(linkPositions.map(({source, target}) => [source, target]).flat())
.join('circle')
.attr('r', 5)
.attr('fill', 'DarkBlue')
.attr('transform', d => `translate(${d})`);
</script>
</body>
</html>
I trying to create subtransitions using transition.each(), but the transitions created in the each callback does not inherit the transition settings from the parent transition; the following code maintains the 8000 duration under v3, but not under v6.
let dataset = [{
x: 50,
y: 50,
color: 'red'
},
{
x: 100,
y: 50,
color: 'black'
},
{
x: 100,
y: 100,
color: 'blue'
},
{
x: 50,
y: 100,
color: 'yellow'
}
]
let svg = d3.select("body").append("p").append("svg")
.attr("width", 200)
.attr("height", 200)
let circles = svg.selectAll("circle").data(dataset)
.enter()
.append("circle")
.attr("r", 10)
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("fill", d => d.color)
parent_transition = d3.select({}).transition()
.duration(8000)
parent_transition.each(() => { // does not inherit duration
circles.transition()
.attr("cx", (d, i) => dataset[(i + 1) % 4].x)
.attr("cy", (d, i) => dataset[(i + 1) % 4].y)
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
How should I do the equivalent in v6?
This was a deliberate change made more than 4 years ago, when D3 v4 was released. According to the changelog:
Transitions created this way inherit timing from the closest ancestor element, and thus are synchronized even when the referenced transition has variable timing such as a staggered delay. This method replaces the deeply magical behavior of transition.each in 3.x; in 4.0, transition.each is identical to selection.each. (emphasis mine)
That said, it's not exactly clear what's your goal here. One possible solution is using named transitions:
let dataset = [{
x: 50,
y: 50,
color: 'red'
},
{
x: 100,
y: 50,
color: 'black'
},
{
x: 100,
y: 100,
color: 'blue'
},
{
x: 50,
y: 100,
color: 'yellow'
}
]
let svg = d3.select("body").append("p").append("svg")
.attr("width", 200)
.attr("height", 200)
let circles = svg.selectAll("circle").data(dataset)
.enter()
.append("circle")
.attr("r", 10)
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("fill", d => d.color)
const parent_transition = d3.transition()
.duration(8000)
circles.transition(parent_transition)
.attr("cx", (d, i) => dataset[(i + 1) % 4].x)
.attr("cy", (d, i) => dataset[(i + 1) % 4].y);
<script src="https://d3js.org/d3.v6.min.js"></script>
Below is my code. I made a circle with 5 arcs and now I want to add text to each arc such that: http://bl.ocks.org/nbremer/raw/b603c3e0f7a74794da87/
// declarations
const svgSize = {
width: 1000,
height: 800
};
// setup
let svg = d3
.select('body')
.append('svg')
.attr('width', svgSize.width)
.attr('height', svgSize.height)
.append('g')
.attr('transform', 'translate(' + svgSize.width / 2 + ',' + svgSize.height / 2 + ')');
// drawing
let arcGenerator = d3.arc()
.innerRadius(296)
.outerRadius(300);
let arcData = [
{ startAngle: 0, endAngle: 0.2 },
{ startAngle: 0.2, endAngle: 0.6 },
{ startAngle: 0.6, endAngle: 1.4 },
{ startAngle: 1.4, endAngle: 3 },
{ startAngle: 3, endAngle: 2 * Math.PI }
];
d3.select('g')
.selectAll('path')
.data(arcData)
.enter()
.append('path')
.attr('d', arcGenerator);
d3.select('g')
.selectAll('path')
.data(arcData)
.enter()
.append('path')
.attr("id", (d, i) => { return "uniqueId_" + i; })
.attr('d', arcGenerator);
let monthData = [{ month: 'Jan' }, { month: 'Feb' }, { month: 'Mar' }, { month: 'Apr' }, { month: 'May' },]
// append the month names to each slice
svg.selectAll(".monthText")
.data(monthData)
.enter().append("text")
.attr("class", "monthText")
.attr("x", 10) // move the text from the start angle of the arc
.attr("dy", -10) // move the text down
.append("textPath")
.attr("xlink:href", function (d, i) { return "#uniqueId_" + i; })
.text(function (d) { return d.month; });
This question builds on this question.
Using d3.js/dc.js, I have three (or more) charts. All have the same x-axis (a date series), so the nth datapoint on any chart will correspond exactly to the nth datapoint on the x-axis of the other charts.
When the user clicks on a dot point in one chart, I need to get the "y" data from the same point on the other 2+ charts and return an array or object or string with the chartID/y-datum from the other charts, something like this:
{"chart1":"30","chart2":"50","chart3":"10"}
Here is an example borrowed from Gerardo Furtado's answer to the above-referenced question. How would I modify Gerardo's example to return the datapoints from each chart?
var data = [{x:20, y:30},
{x:30, y:60},
{x:40, y:40},
{x:50, y:90},
{x:60, y:20},
{x:70, y:90},
{x:80, y:90},
{x:90, y:10}];
draw("#svg1");
draw("#svg2");
draw("#svg3");
function draw(selector){
var width = 250,
height = 250;
var svg = d3.select(selector)
.append("svg")
.attr("width", width)
.attr("height", height);
var xScale = d3.scaleLinear()
.domain([0, 100])
.range([30, width - 10]);
var yScale = d3.scaleLinear()
.domain([0,100])
.range([height - 30, 10]);
var circles = svg.selectAll("foo")
.data(data)
.enter()
.append("circle");
circles.attr("r", 10)
.attr("fill", "teal")
.attr("cx", d=>xScale(d.x))
.attr("cy", d=>yScale(d.y));
var xAxis = d3.axisBottom(xScale);
var yAxis = d3.axisLeft(yScale);
svg.append("g").attr("transform", "translate(0,220)")
.attr("class", "xAxis")
.call(xAxis);
svg.append("g")
.attr("transform", "translate(30,0)")
.attr("class", "yAxis")
.call(yAxis);
}
d3.selectAll("circle").on("mouseover", function(){
var thisDatum = d3.select(this).datum();
d3.selectAll("circle").filter(d=>d.x == thisDatum.x && d.y == thisDatum.y).attr("fill", "firebrick");
}).on("mouseout", function(){
d3.selectAll("circle").attr("fill", "teal")
})
#svg1 {
float: left;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="svg1"></div>
<div id="svg2"></div>
<div id="svg3"></div>
As you have several different data sets, I'll modify the answer I wrote in your previous question so we can have different y values.
First, let't put all data in an object. That way, we can access the different data sets later:
var dataObject = {
data1: [{
x: 10,
y: 30
}, ...
}],
data2: [{
x: 10,
y: 70
}, ...
}],
data3: [{
x: 10,
y: 10
}, ...
}]
};
Then, we call the draw function:
draw("#svg1", dataObject.data1);
draw("#svg2", dataObject.data2);
draw("#svg3", dataObject.data3);
So, to get what you want, in the mouseover...
d3.selectAll("circle").on("mouseover", function() {
var thisDatum = d3.select(this).datum();
findPoints(thisDatum);
})
We call this function:
function findPoints(datum) {
var myObject = {};
for (var i = 1; i < 4; i++) {
myObject["chart" + i] = dataObject["data" + i].filter(e => e.x === datum.x)[0].y;
}
console.log(myObject)//use return instead of console.log
}
Here is the demo:
var dataObject = {
data1: [{
x: 10,
y: 30
}, {
x: 20,
y: 60
}, {
x: 30,
y: 40
}, {
x: 40,
y: 90
}, {
x: 50,
y: 20
}, {
x: 60,
y: 90
}, {
x: 70,
y: 90
}, {
x: 80,
y: 10
}],
data2: [{
x: 10,
y: 70
}, {
x: 20,
y: 60
}, {
x: 30,
y: 80
}, {
x: 40,
y: 10
}, {
x: 50,
y: 10
}, {
x: 60,
y: 20
}, {
x: 70,
y: 10
}, {
x: 80,
y: 90
}],
data3: [{
x: 10,
y: 10
}, {
x: 20,
y: 20
}, {
x: 30,
y: 40
}, {
x: 40,
y: 90
}, {
x: 50,
y: 80
}, {
x: 60,
y: 70
}, {
x: 70,
y: 50
}, {
x: 80,
y: 50
}]
};
draw("#svg1", dataObject.data1);
draw("#svg2", dataObject.data2);
draw("#svg3", dataObject.data3);
function draw(selector, data) {
var width = 200,
height = 100;
var svg = d3.select(selector)
.append("svg")
.attr("width", width)
.attr("height", height);
var xScale = d3.scaleLinear()
.domain([0, 100])
.range([30, width - 10]);
var yScale = d3.scaleLinear()
.domain([0, 100])
.range([height - 30, 10]);
var circles = svg.selectAll("foo")
.data(data)
.enter()
.append("circle");
circles.attr("r", 5)
.attr("fill", "palegreen")
.attr("cx", d => xScale(d.x))
.attr("cy", d => yScale(d.y));
var xAxis = d3.axisBottom(xScale);
var yAxis = d3.axisLeft(yScale).ticks(2);
svg.append("g").attr("transform", "translate(0,70)")
.attr("class", "xAxis")
.call(xAxis);
svg.append("g")
.attr("transform", "translate(30,0)")
.attr("class", "yAxis")
.call(yAxis);
}
d3.selectAll("circle").on("mouseover", function() {
var thisDatum = d3.select(this).datum();
findPoints(thisDatum);
d3.selectAll("circle").filter(d => d.x == thisDatum.x).attr("fill", "firebrick");
}).on("mouseout", function() {
d3.selectAll("circle").attr("fill", "palegreen")
})
function findPoints(datum) {
var myObject = {};
for (var i = 1; i < 4; i++) {
myObject["chart" + i] = dataObject["data" + i].filter(e => e.x === datum.x)[0].y;
}
console.log(JSON.stringify(myObject))
}
#svg1, #svg2 {
float: left;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="svg1"></div>
<div id="svg2"></div>
<div id="svg3"></div>