Beeswarm plot with force - add links to nodes - d3.js

I've created a Beeswarm plot with d3v4 and d3.forceSimulation, and the points are where I want them to be:
var data = [
{ country: "Algeria", amount: 22, year: 2000 },
{ country: "Argentina", amount: 49, year: 1990 },
{ country: "Armenia", amount: 3, year: 1990 },
{ country: "Australia", amount: 9, year: 2010 },
{ country: "Austria", amount: 1, year: 2010 },
{ country: "Bahamas", amount: 5, year: 2018 },
{ country: "Bahrain", amount: 22, year: 2018 },
{ country: "Belarus", amount: 9, year: 2010 },
{ country: "Belgium", amount: 46, year: 2018 },
{ country: "Brazil", amount: 79, year: 1990 },
{ country: "Canada", amount: 12, year: 2000 },
{ country: "China", amount: 26, year: 2018 },
{ country: "Colombia", amount: 9, year: 2010 },
{ country: "Croatia", amount: 8, year: 2000 },
{ country: "Cuba", amount: 14, year: 1990 },
{ country: "Czech Republic", amount: 11, year: 2018 },
{ country: "Denmark", amount: 125, year: 2010 },
{ country: "Canada", amount: 124, year: 2018 },
{ country: "Bahrain", amount: 39, year: 2010 },
{ country: "Estonia", amount: 141, year: 2018 },
{ country: "Ethiopia", amount: 38, year: 1990 },
{ country: "France", amount: 4, year: 2018 },
{ country: "Germany", amount: 15, year: 2000 },
{ country: "Greece", amount: 16, year: 2010 },
{ country: "Grenada", amount: 241, year: 2010 },
{ country: "Hungary", amount: 135, year: 1990 },
{ country: "India", amount: 22, year: 1990 },
{ country: "Indonesia", amount: 31, year: 1990 },
{ country: "Iran", amount: 88, year: 2010 },
{ country: "Ireland", amount: 12, year: 2018 },
{ country: "Italy", amount: 128, year: 2000 },
{ country: "Jamaica", amount: 1, year: 2018 },
{ country: "Japan", amount: 41, year: 1990 },
{ country: "Jordan", amount: 137, year: 2010 },
{ country: "Iran", amount: 13, year: 1990 },
{ country: "Malaysia", amount: 25, year: 2018 },
{ country: "Mexico", amount: 59, year: 2010 },
{ country: "Moldova", amount: 71, year: 2000 },
{ country: "Mongolia", amount: 22, year: 2018 },
{ country: "Morocco", amount: 131, year: 1990 },
{ country: "Netherlands", amount: 129, year: 2018 },
{ country: "New Zealand", amount: 148, year: 2018 },
{ country: "Niger", amount: 1, year: 2010 },
{ country: "Nigeria", amount: 41, year: 1990 },
{ country: "Norway", amount: 14, year: 2010 },
{ country: "Philippines", amount: 15, year: 2018 },
{ country: "Poland", amount: 12, year: 2010 },
{ country: "Portugal", amount: 31, year: 2000 },
{ country: "Puerto Rico", amount: 51, year: 2000 },
{ country: "Romania", amount: 15, year: 2000 },
{ country: "Serbia", amount: 18, year: 2000 },
{ country: "South Africa", amount: 14, year: 2010 },
{ country: "Sweden", amount: 11, year: 2018 },
{ country: "Switzerland", amount: 7, year: 2010 },
{ country: "Thailand", amount: 61, year: 2018 },
{ country: "Trinidad and Tobago", amount: 12, year: 2018 },
{ country: "Tunisia", amount: 34, year: 2010 },
{ country: "Turkey", amount: 28, year: 2010 },
{ country: "Ukraine", amount: 11, year: 2010 },
{ country: "Uzbekistan", amount: 123, year: 2018 },
{ country: "Venezuela", amount: 23, year: 2018 },
{ country: "Iran", amount: 13, year: 2018 }
];
var width = 1000,
height = 500;
var svg = d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height);
var x = d3.scaleLinear()
.range([95, 650]);
var y = d3.scaleLinear()
.range([100, 450]);
data.forEach(d => {
d.amount = +d.amount;
});
var sort = data.sort((a, b) => d3.descending(a, b));
y.domain(d3.extent(data, function(d) {
return d.amount;
}));
x.domain(d3.extent(data, function(d) {
return d.year;
}));
var simulation = d3.forceSimulation(data)
.force("x", d3.forceX(function(d) {
return x(d.year);
}).strength(3))
.force("y", d3.forceY(function(d) {
return y(d.amount)
}).strength(2))
.force("collide", d3.forceCollide(7).strength(7))
.stop();
for (var i = 0; i < data.length * 2; ++i) simulation.tick();
var circles = svg.selectAll(".circles")
.data(data);
var circlesEnter = circles.enter()
.append("circle");
circlesEnter.attr("r", 4)
.attr("cx", function(d) {
return d.x
})
.attr("cy", function(d) {
return d.y
})
.attr("fill", function(d) {
if (d.country == "Iran") {
return "#FF0044"
} else if (d.country == "Canada") {
return "#00A9E9"
} else if (d.country == "Bahrain") {
return "#6BF4C6"
} else {
return '#333'
}
})
.attr('class', function(d) {
return d.amount + ' ' + d.year + ' ' + d.country
})
// connector lines
var byCountry = d3.nest()
.key(function(d) {
return d.country;
})
.entries(data);
var countryNames = d3.values(byCountry).map(function(d) {
return d.values.map(function(v) {
return v.country;
}).join(', ');
});
for (i = 0; i < countryNames.length; i++) {
eaco = countryNames[i].split(',')[0]
const filterByCountry = (country, data) => item => item.country === country
connectData = data.filter(filterByCountry(eaco))
var linesGroup = svg.append("g")
.attr("class", "connectors");
var linec = d3.line()
.x(function(d) {
return x(d.year)
})
.y(function(d) {
return y(d.amount)
})
// using below as the points does not work
// .x(function(d) { return x(d.x)})
// .y(function(d) { return y(d.y)})
var lineGraph = linesGroup.selectAll('.connect')
.data(connectData)
.enter()
.append("path")
.attr('class', function(d) {
return d.amount + ' ' + d.year + ' ' + d.country
})
.attr("d", linec(connectData))
.attr("stroke", function(d) {
if (d.country == "Iran") {
return "#FF0044"
} else if (d.country == "Canada") {
return "#00A9E9"
} else if (d.country == "Bahrain") {
return "#6BF4C6"
} else {
return '#333'
}
})
.attr("stroke-width", 1)
.attr("fill", "none")
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<body><div id="chart"></div></body>
The x axis shows years.
The y axis shows amount.
Each point is a country.
I'd like to add connecting lines between points that are the same country over different years. I've color-coded these to clarify.
The problem is, I can't get the x/y to match up with the points based on the force. I commented out what I thought would work. Any ideas?

Once circles are created, it is possible to retrieve their exact coordinates. With these circle coordinates, it becomes very easy to set the extremities of the lines:
var data = [
{ country: "Algeria", amount: 22, year: 2000 },
{ country: "Argentina", amount: 49, year: 1990 },
{ country: "Armenia", amount: 3, year: 1990 },
{ country: "Australia", amount: 9, year: 2010 },
{ country: "Austria", amount: 1, year: 2010 },
{ country: "Bahamas", amount: 5, year: 2018 },
{ country: "Bahrain", amount: 22, year: 2018 },
{ country: "Belarus", amount: 9, year: 2010 },
{ country: "Belgium", amount: 46, year: 2018 },
{ country: "Brazil", amount: 79, year: 1990 },
{ country: "Canada", amount: 12, year: 2000 },
{ country: "China", amount: 26, year: 2018 },
{ country: "Colombia", amount: 9, year: 2010 },
{ country: "Croatia", amount: 8, year: 2000 },
{ country: "Cuba", amount: 14, year: 1990 },
{ country: "Czech Republic", amount: 11, year: 2018 },
{ country: "Denmark", amount: 125, year: 2010 },
{ country: "Canada", amount: 124, year: 2018 },
{ country: "Bahrain", amount: 39, year: 2010 },
{ country: "Estonia", amount: 141, year: 2018 },
{ country: "Ethiopia", amount: 38, year: 1990 },
{ country: "France", amount: 4, year: 2018 },
{ country: "Germany", amount: 15, year: 2000 },
{ country: "Greece", amount: 16, year: 2010 },
{ country: "Grenada", amount: 241, year: 2010 },
{ country: "Hungary", amount: 135, year: 1990 },
{ country: "India", amount: 22, year: 1990 },
{ country: "Indonesia", amount: 31, year: 1990 },
{ country: "Iran", amount: 88, year: 2010 },
{ country: "Ireland", amount: 12, year: 2018 },
{ country: "Italy", amount: 128, year: 2000 },
{ country: "Jamaica", amount: 1, year: 2018 },
{ country: "Japan", amount: 41, year: 1990 },
{ country: "Jordan", amount: 137, year: 2010 },
{ country: "Iran", amount: 13, year: 1990 },
{ country: "Malaysia", amount: 25, year: 2018 },
{ country: "Mexico", amount: 59, year: 2010 },
{ country: "Moldova", amount: 71, year: 2000 },
{ country: "Mongolia", amount: 22, year: 2018 },
{ country: "Morocco", amount: 131, year: 1990 },
{ country: "Netherlands", amount: 129, year: 2018 },
{ country: "New Zealand", amount: 148, year: 2018 },
{ country: "Niger", amount: 1, year: 2010 },
{ country: "Nigeria", amount: 41, year: 1990 },
{ country: "Norway", amount: 14, year: 2010 },
{ country: "Philippines", amount: 15, year: 2018 },
{ country: "Poland", amount: 12, year: 2010 },
{ country: "Portugal", amount: 31, year: 2000 },
{ country: "Puerto Rico", amount: 51, year: 2000 },
{ country: "Romania", amount: 15, year: 2000 },
{ country: "Serbia", amount: 18, year: 2000 },
{ country: "South Africa", amount: 14, year: 2010 },
{ country: "Sweden", amount: 11, year: 2018 },
{ country: "Switzerland", amount: 7, year: 2010 },
{ country: "Thailand", amount: 61, year: 2018 },
{ country: "Trinidad and Tobago", amount: 12, year: 2018 },
{ country: "Tunisia", amount: 34, year: 2010 },
{ country: "Turkey", amount: 28, year: 2010 },
{ country: "Ukraine", amount: 11, year: 2010 },
{ country: "Uzbekistan", amount: 123, year: 2018 },
{ country: "Venezuela", amount: 23, year: 2018 },
{ country: "Iran", amount: 13, year: 2018 }
];
var width = 1000,
height = 500;
var svg = d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height);
var x = d3.scaleLinear()
.range([95, 650]);
var y = d3.scaleLinear()
.range([100, 450]);
data.forEach(d => {
d.amount = +d.amount;
});
var sort = data.sort((a, b) => d3.descending(a, b));
y.domain(d3.extent(data, function(d) {
return d.amount;
}));
x.domain(d3.extent(data, function(d) {
return d.year;
}));
var simulation = d3.forceSimulation(data)
.force("x", d3.forceX(function(d) {
return x(d.year);
}).strength(3))
.force("y", d3.forceY(function(d) {
return y(d.amount)
}).strength(2))
.force("collide", d3.forceCollide(7).strength(7))
.stop();
for (var i = 0; i < data.length * 2; ++i) simulation.tick();
var circles = svg.selectAll(".circles").data(data);
var circlesEnter = circles.enter().append("circle");
circlesEnter.attr("r", 4)
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("fill", function(d) {
if (d.country == "Iran") {
return "#FF0044"
} else if (d.country == "Canada") {
return "#00A9E9"
} else if (d.country == "Bahrain") {
return "#6BF4C6"
} else {
return '#333'
}
})
.attr('class', function(d) {
return d.country + '-' + d.year + '-' + d.amount
});
// connector lines
var byCountry = d3.nest().key(function(d) { return d.country; }).entries(data);
var countryNames = d3.values(byCountry).map(function(d) {
return d.values.map(function(v) {
return v.country;
}).join(', ');
});
for (i = 0; i < countryNames.length; i++) {
eaco = countryNames[i].split(',')[0]
const filterByCountry = (country, data) => item => item.country === country
connectData = data.filter(filterByCountry(eaco))
if (connectData.length >= 2) {
var linesGroup = svg.append("g")
.attr("class", "connectors");
var linec = d3.line()
.x(d => d3.select("circle." + d.country + "-" + d.year + "-" + d.amount).attr("cx"))
.y(d => d3.select("circle." + d.country + "-" + d.year + "-" + d.amount).attr("cy"));
linesGroup
.datum(connectData)
.append("path")
.attr('class', d => d[0].amount + '-' + d[0].year + '-' + d[0].country)
.attr("d", linec)
.attr("stroke", function(d) {
if (d[0].country == "Iran") return "#FF0044";
else if (d[0].country == "Canada") return "#00A9E9";
else if (d[0].country == "Bahrain") return "#6BF4C6";
else return '#333';
})
.attr("stroke-width", 1)
.attr("fill", "none");
}
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<body><div id="chart"></div></body>
This way, lines extremities exactly match the coordinates of the circles.
To do so, we can select the circles corresponding to the extremities of the line in question using:
d3.select("circle." + d.country + "-" + d.year + "-" + d.amount)
on which we can retrieve the cx and cy attributes (x and y position of the circle):
d3.select("circle." + d.country + "-" + d.year + "-" + d.amount).attr("cx")
to finally create the line with these coordinates:
var linec = d3.line()
.x(d => d3.select("circle." + d.country + "-" + d.year + "-" + d.amount).attr("cx"))
.y(d => d3.select("circle." + d.country + "-" + d.year + "-" + d.amount).attr("cy"));
Side notes:
Notice how I modified the classes you used to name circles. As the class names contained spaces, we couldn't select them; I used - instead. In addition we apparently can't select a class which starts with a number, so I changed the class names to start with the country instead of the amount.
Since, here, classes are used to uniquely define circles and lines, it would probably make more sense to set ids rather than classes.
As noticed by #Gerardo, each line is actually created several times (once per circle; such that a line joining 3 circles would be created 3 times). Here is one possible way you can fix that:
var linec = d3.line()
.x(d => d3.select("circle." + d.country + "-" + d.year + "-" + d.amount).attr("cx"))
.y(d => d3.select("circle." + d.country + "-" + d.year + "-" + d.amount).attr("cy"));
linesGroup.datum(connectData).append("path").attr("d", linec)...

Related

Change default behaviour of legend in Amchart 5

I'm trying to make this chart (https://www.amcharts.com/demos/reversed-value-axis/) in angular and need help in changing default behaviour of legend like if i click on any legend only that axis should be show other will hide unlike now if we click on any legend, that will hide.
is there any possible way to do it?
Default Chart
If click on one legend
Yes, of course...
To do that, you need a collection of all your series. You also need hide() and appear() methods.
Here is a possible implementation:
am5.ready(() => {
let root = am5.Root.new("chartdiv");
root.setThemes([
am5themes_Animated.new(root)
]);
let chart = root.container.children.push(am5xy.XYChart.new(root, {
layout: root.verticalLayout
}));
let data = [
{
year: "1930",
italy: 1,
germany: 5,
uk: 3
},
{
year: "1934",
italy: 1,
germany: 2,
uk: 6
},
{
year: "1938",
italy: 2,
germany: 3,
uk: 1
},
{
year: "1950",
italy: 3,
germany: 4,
uk: 1
},
{
year: "1954",
italy: 5,
germany: 1,
uk: 2
},
{
year: "1958",
italy: 3,
germany: 2,
uk: 1
},
{
year: "1962",
italy: 1,
germany: 2,
uk: 3
},
{
year: "1966",
italy: 2,
germany: 1,
uk: 5
},
{
year: "1970",
italy: 3,
germany: 5,
uk: 2
},
{
year: "1974",
italy: 4,
germany: 3,
uk: 6
},
{
year: "1978",
italy: 1,
germany: 2,
uk: 4
}
];
let xAxis = chart.xAxes.push(am5xy.CategoryAxis.new(root, {
categoryField: "year",
renderer: am5xy.AxisRendererX.new(root, {})
}));
xAxis.data.setAll(data);
let yAxis = chart.yAxes.push(am5xy.ValueAxis.new(root, {
renderer: am5xy.AxisRendererY.new(root, {
inversed: true
})
}));
function createSeries(name, field) {
let series = chart.series.push(am5xy.LineSeries.new(root, {
name,
xAxis,
yAxis,
categoryXField: "year",
valueYField: field
}));
series.bullets.push(() => {
return am5.Bullet.new(root, {
sprite: am5.Circle.new(root, {
radius: 5,
fill: series.get("fill")
})
});
});
series.data.setAll(data);
return series;
}
// The interesting part starts here!
let allSeries = [];
allSeries.push(createSeries("Italy", "italy"));
allSeries.push(createSeries("Germany", "germany"));
allSeries.push(createSeries("UK", "uk"));
let legend = chart.children.push(am5.Legend.new(root, {
centerX: am5.p50,
x: am5.p50
}));
legend.itemContainers.template.events.on("click", () => {
for (let series of allSeries) {
series.hide();
}
});
legend.data.setAll(chart.series.values);
document.getElementById("reset").addEventListener("click", () => {
for (let series of allSeries) {
series.appear();
}
});
});
#chartdiv {
width: 100%;
height: 350px;
}
#reset {
width: 50%;
display: block;
margin: auto;
cursor: pointer;
}
<script src="https://cdn.amcharts.com/lib/5/index.js"></script>
<script src="https://cdn.amcharts.com/lib/5/xy.js"></script>
<script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script>
<div id="chartdiv"></div>
<button type="button" id="reset">Reset</button>

add value (nunber) per same year from an array of objects

const db1 = [ { year: "2000", state: "Rio", month: "November", number: 18 }, { year: "2002", state: "Perambuco", month: "February", number: 64 }, { year: "2001", state: "Mato Grasso", month: "March", number: 112 }, { year: "2003", state: "Roraima", month: "January", number: 547 }, { year: "2002", state: "Maranhoo", month: "July", number: 4 }, { year: "2003", state: "Rio", month: "March", number: 9 }, { year: "2000", state: "Roraima", month: "October", number: 25 }, { year: "2001", state: "Paraiba", month: "January", number: 11 }, ];
I have the following array of objects. from db1 I want to filter out only the keys (year, number) and add the number (values) per same respective year ex:
{ year: "2002", number: 68 }, { year: "2000", number: 43 }, { year: "2003", number: 556 }, { year: "2001", number: 123 },
any feedback is appreciated.
const sumArrayVals = (db1) => { return db1.reduce((a, b) => { Object.keys(b).map((c) => (a[c] = (a[c] || 0) + b[c])); return a; }); };)
took a bit but got it.
const sumPerYear = db1.reduce((acc, cur) => {
// increment or initialize to cur.number
acc[cur.year] = acc[cur.year] + cur.number || cur.number;
return acc;
}, {});

D3v5 Sankey Diagram add Drag&Drop

I have a Sankey Diagram where I show connections between source and target sites.
Now I want to add a drag&drop functionallity to this chart, but I have difficulties as I did use transform/translate until now. The current Sankey Version works with [[x0,y0],[x1,y1]] coords and x+width/y+height and not with translate anymore.
I found an example with drag & drop using translate/transform here: https://bl.ocks.org/d3noob/013054e8d7807dff76247b81b0e29030
This is my current example
const {
DOM
} = new observablehq.Library;
var units = "Links";
var margin = {
top: 10,
right: 10,
bottom: 10,
left: 10
},
width = 900 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
// the function for moving the nodes
function dragmove(d) {
console.log(d3.event);
/*???
sankey.update(graph);
*/
}
const d3color = d3.scaleOrdinal(d3.schemeCategory10);
const color = function(name) {
return d3color(name.replace(/ .*/, ""));
}
var format = function(d) {
const f = d3.format(",.0f");
return f(d);
}
// append the svg canvas to the page
var svg = d3.select("#chart")
//.attr("viewBox", [0, 0, width, height]);;
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
// Set the sankey diagram properties
const sankey = d3.sankey()
.nodeAlign(d3.sankeyJustify)
.nodeWidth(15)
.nodePadding(10)
.extent([
[margin.top, margin.left],
[width - margin.right, height - margin.bottom]
]);
const data = {
links: [{
source: 1,
target: 0,
value: 1
},
{
source: 3,
target: 2,
value: 24
},
{
source: 5,
target: 4,
value: 1
},
{
source: 5,
target: 6,
value: 1
},
{
source: 7,
target: 6,
value: 2
},
{
source: 9,
target: 8,
value: 2
},
{
source: 11,
target: 10,
value: 32
},
{
source: 11,
target: 4,
value: 32
},
{
source: 11,
target: 12,
value: 32
},
{
source: 11,
target: 13,
value: 32
},
{
source: 11,
target: 14,
value: 30
},
{
source: 11,
target: 15,
value: 32
},
{
source: 11,
target: 16,
value: 32
},
{
source: 17,
target: 6,
value: 3
},
{
source: 18,
target: 4,
value: 1
},
{
source: 19,
target: 2,
value: 62
},
{
source: 20,
target: 6,
value: 7
},
{
source: 21,
target: 6,
value: 1
},
{
source: 23,
target: 22,
value: 7
},
{
source: 23,
target: 4,
value: 18
},
{
source: 23,
target: 6,
value: 15
},
{
source: 23,
target: 24,
value: 2
},
{
source: 25,
target: 6,
value: 1
},
{
source: 26,
target: 4,
value: 1
},
{
source: 26,
target: 6,
value: 1
},
{
source: 28,
target: 27,
value: 24
},
{
source: 28,
target: 29,
value: 1
},
{
source: 28,
target: 2,
value: 24
},
{
source: 28,
target: 30,
value: 24
},
{
source: 28,
target: 10,
value: 24
},
{
source: 28,
target: 12,
value: 10
},
{
source: 28,
target: 31,
value: 12
},
{
source: 28,
target: 32,
value: 9
},
{
source: 28,
target: 33,
value: 24
},
{
source: 28,
target: 14,
value: 22
},
{
source: 34,
target: 6,
value: 1
},
{
source: 35,
target: 24,
value: 1
},
{
source: 36,
target: 27,
value: 12
},
{
source: 36,
target: 37,
value: 12
},
{
source: 36,
target: 14,
value: 12
},
{
source: 38,
target: 4,
value: 3
},
{
source: 38,
target: 6,
value: 1
},
{
source: 38,
target: 39,
value: 1
},
{
source: 40,
target: 22,
value: 8
},
{
source: 40,
target: 4,
value: 6
},
{
source: 40,
target: 41,
value: 1
},
{
source: 40,
target: 42,
value: 6
},
{
source: 43,
target: 30,
value: 21
},
{
source: 44,
target: 6,
value: 1
},
{
source: 45,
target: 10,
value: 1
},
{
source: 45,
target: 4,
value: 5
},
{
source: 45,
target: 42,
value: 2
},
{
source: 45,
target: 6,
value: 2
},
{
source: 46,
target: 4,
value: 3
},
{
source: 46,
target: 6,
value: 1
},
{
source: 47,
target: 4,
value: 7
},
{
source: 47,
target: 41,
value: 9
},
{
source: 47,
target: 42,
value: 6
},
{
source: 47,
target: 6,
value: 6
},
{
source: 48,
target: 6,
value: 1
},
{
source: 49,
target: 4,
value: 3
},
{
source: 49,
target: 6,
value: 13
},
{
source: 49,
target: 24,
value: 1
},
{
source: 50,
target: 4,
value: 1
},
{
source: 51,
target: 4,
value: 1
},
{
source: 51,
target: 41,
value: 6
},
{
source: 51,
target: 42,
value: 3
},
{
source: 51,
target: 6,
value: 11
},
{
source: 52,
target: 4,
value: 3
},
{
source: 52,
target: 6,
value: 1
},
{
source: 53,
target: 4,
value: 1
},
{
source: 53,
target: 39,
value: 1
},
{
source: 54,
target: 22,
value: 2
},
{
source: 54,
target: 41,
value: 7
},
{
source: 56,
target: 55,
value: 3
},
{
source: 57,
target: 27,
value: 4
},
{
source: 57,
target: 30,
value: 4
},
{
source: 57,
target: 10,
value: 4
},
{
source: 57,
target: 4,
value: 4
},
{
source: 57,
target: 58,
value: 4
},
{
source: 59,
target: 4,
value: 3
},
{
source: 59,
target: 60,
value: 2
},
{
source: 59,
target: 41,
value: 5
},
{
source: 59,
target: 6,
value: 5
},
{
source: 59,
target: 24,
value: 1
},
{
source: 61,
target: 4,
value: 1
},
{
source: 61,
target: 6,
value: 1
},
{
source: 62,
target: 6,
value: 1
},
{
source: 63,
target: 41,
value: 1
},
{
source: 63,
target: 6,
value: 1
},
{
source: 64,
target: 4,
value: 1
},
{
source: 65,
target: 6,
value: 1
}
],
nodes: [{
value: 1,
name: 'site1'
},
{
value: 1,
name: 'site2'
},
{
value: 110,
name: 'site3'
},
{
value: 24,
name: 'site4'
},
{
value: 95,
name: 'site5'
},
{
value: 2,
name: 'site6'
},
{
value: 78,
name: 'site7'
},
{
value: 2,
name: 'site8'
},
{
value: 2,
name: 'site9'
},
{
value: 2,
name: 'site10'
},
{
value: 61,
name: 'site11'
},
{
value: 222,
name: 'site12'
},
{
value: 42,
name: 'site13'
},
{
value: 32,
name: 'site14'
},
{
value: 64,
name: 'site15'
},
{
value: 32,
name: 'site16'
},
{
value: 32,
name: 'site17'
},
{
value: 3,
name: 'site18'
},
{
value: 1,
name: 'site19'
},
{
value: 62,
name: 'site20'
},
{
value: 7,
name: 'site21'
},
{
value: 1,
name: 'site22'
},
{
value: 17,
name: 'site23'
},
{
value: 42,
name: 'site24'
},
{
value: 5,
name: 'site25'
},
{
value: 1,
name: 'site26'
},
{
value: 2,
name: 'site27'
},
{
value: 40,
name: 'site28'
},
{
value: 174,
name: 'site29'
},
{
value: 1,
name: 'site30'
},
{
value: 49,
name: 'site31'
},
{
value: 12,
name: 'site32'
},
{
value: 9,
name: 'site33'
},
{
value: 24,
name: 'site34'
},
{
value: 1,
name: 'site35'
},
{
value: 1,
name: 'site36'
},
{
value: 36,
name: 'site37'
},
{
value: 12,
name: 'site38'
},
{
value: 5,
name: 'site39'
},
{
value: 2,
name: 'site40'
},
{
value: 21,
name: 'site41'
},
{
value: 29,
name: 'site42'
},
{
value: 17,
name: 'site43'
},
{
value: 21,
name: 'site44'
},
{
value: 1,
name: 'site45'
},
{
value: 10,
name: 'site46'
},
{
value: 4,
name: 'site47'
},
{
value: 28,
name: 'site48'
},
{
value: 1,
name: 'site49'
},
{
value: 17,
name: 'site50'
},
{
value: 1,
name: 'site51'
},
{
value: 21,
name: 'site52'
},
{
value: 4,
name: 'site53'
},
{
value: 2,
name: 'site54'
},
{
value: 9,
name: 'site55'
},
{
value: 3,
name: 'site56'
},
{
value: 3,
name: 'site57'
},
{
value: 20,
name: 'site58'
},
{
value: 4,
name: 'site59'
},
{
value: 16,
name: 'site60'
},
{
value: 2,
name: 'site61'
},
{
value: 2,
name: 'site62'
},
{
value: 1,
name: 'site63'
},
{
value: 2,
name: 'site64'
},
{
value: 1,
name: 'site65'
},
{
value: 1,
name: 'site66'
}
]
};
const graph = sankey(data);
const node = svg.append("g")
.attr("stroke", "#000")
.selectAll("rect")
.data(graph.nodes)
.join("rect")
.attr("class", "node")
.attr("x", d => d.x0)
.attr("y", d => d.y0)
.attr("height", d => d.y1 - d.y0)
.attr("width", d => d.x1 - d.x0)
.attr("fill", d => d.color = color(d.name))
.style("stroke", d => d3.rgb(d.color).darker(2))
.call(d3.drag()
.subject(function(d) { return d; })
.on("start", function() { this.parentNode.appendChild(this); })
.on("drag", dragmove));
node.append("title")
.text(d => d.name + ': ' + format(d.value))
const link = svg.append("g")
.attr("fill", "none")
.selectAll("g")
.data(graph.links)
.join("g")
.style("mix-blend-mode", "multiply")
.attr("class", "link");
const gradient = link.append("linearGradient")
.attr("id", d => (d.uid = DOM.uid("link")).id)
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", d => d.source.x1)
.attr("x2", d => d.target.x0);
gradient.append("stop")
.attr("offset", "0%")
.attr("stop-color", d => color(d.source.name));
gradient.append("stop")
.attr("offset", "100%")
.attr("stop-color", d => color(d.target.name));
link.append("path")
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke", d => d.uid)
.attr("stroke-width", d => Math.max(1, d.width));
link.append("title")
.text(d => d.source.name + ' → ' + d.target.name + ': ' + format(d.value));
svg.append("g")
.style("font", "10px sans-serif")
.selectAll("text")
.data(graph.nodes)
.join("text")
.attr("x", d => d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6)
.attr("y", d => (d.y1 + d.y0) / 2)
.attr("dy", "0.35em")
.attr("text-anchor", d => d.x0 < width / 2 ? "start" : "end")
.text(d => d.name);
/*
// the function for moving the nodes
function dragmove(d) {
d3.select(this).attr("transform",
"translate(" + d.x + "," + (
d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))
) + ")");
sankey.relayout();
link.attr("d", d3.sankeyLinkHorizontal());
}*/
rect.node {
/*cursor: move; */
fill-opacity: .9;
shape-rendering: crispEdges;
}
.node title {
pointer-events: none;
text-shadow: 0 1px 0 #fff;
}
.link {
fill: none;
stroke: #000;
stroke-opacity: .2;
}
.link:hover {
stroke-opacity: .5;
}
<body>
<div>
<svg id='chart'></svg>
</div>
</body>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://unpkg.com/d3-sankey#0.12.3/dist/d3-sankey.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/#observablehq/stdlib"></script>
How can drag & drop work here?

How to create multi color vertical bar chart in d3.js (version 4 and up) using specific json data?

i am working the typescript project
i will expect the multicolored bar Chart
the Json data is:
const OutputData = [ { Date: '01/01/18', TotalPercentage: 40, Data: [{ Percentage: 40, color: 'red' }] },
{ Date: '02/01/18', TotalPercentage: 60, Data: [{ Percentage: 20, color: 'green' }, { Percentage: 20, color: 'red' }, { Percentage: 20, color: 'Yellow' }] },
{ Date: '03/01/18', TotalPercentage: 75, Data: [{ Percentage: 35, color: 'blue' }, { Percentage: 10, color: 'green' }, { Percentage: 30, color: 'red' }] },
{ Date: '04/01/18', TotalPercentage: 80, Data: [{ Percentage: 30, color: 'green' }, { Percentage: 50, color: 'red' }] },
{ Date: '05/01/18', TotalPercentage: 95, Data: [{ Percentage: 95, color: 'red' }] },
{ Date: '06/01/18', TotalPercentage: 100, Data: [{ Percentage: 70, color: 'blue' }, { Percentage: 30, color: 'red' }] } ];
Expected Output is
this Code working good including angular project
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<div>
<svg id="stacked"></svg>
</div>
</body>
<script src="http://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<script>
const ExpectedData = [ { Date: '01', Percentage: 5, Data: [{ Percentage: 2, color: 'lightgray' }, { Percentage: 3, color: 'silver' }] },
{ Date: '02', Percentage: 6, Data: [{ Percentage: 3, color: 'silver' }, { Percentage: 3, color: 'lightgray' }] },
{ Date: '03', Percentage: 8, Data: [{ Percentage: 4, color: 'lightgray' }, { Percentage: 4, color: 'silver' }] },
{ Date: '04', Percentage: 10, Data: [{ Percentage: 5, color: 'silver' }, { Percentage: 5, color: 'lightgray' }] },
{ Date: '05', Percentage: 12, Data: [{ Percentage: 6, color: 'lightgray' }, { Percentage: 6, color: 'silver' }] },
{ Date: '06', Percentage: 14, Data: [{ Percentage: 7, color: 'silver' }, { Percentage: 7, color: 'lightgray' }] },
{ Date: '07', Percentage: 16, Data: [{ Percentage: 8, color: 'lightgray' }, { Percentage: 8, color: 'silver' }] },
{ Date: '08', Percentage: 18, Data: [{ Percentage: 9, color: 'silver' }, { Percentage: 9, color: 'lightgray' }] },
{ Date: '09', Percentage: 20, Data: [{ Percentage: 10, color: 'lightgray' }, { Percentage: 10, color: 'silver' }] },
{ Date: '10', Percentage: 22, Data: [{ Percentage: 11, color: 'silver' }, { Percentage: 11, color: 'lightgray' }] },
{ Date: '11', Percentage: 24, Data: [{ Percentage: 12, color: 'lightgray' }, { Percentage: 12, color: 'silver' }] } ];
const height = 300;
const width = 550;
const x = d3.scaleBand()
.domain(ExpectedData.map(function(d) { return d.Date; }))
.rangeRound([0, width])
.padding(0.3)
.align(0.3);
const y = d3.scaleLinear()
.domain([0, d3.max(ExpectedData, function(d) { return d.Percentage; })]).nice()
.rangeRound([height, 0]);
const svg = d3.select('#stacked').style('Width', '600px').style('height', '450px').style('overflow-x', 'auto');
const g = svg.append('g').attr('transform', 'translate(40, 40)');
g.selectAll('.group')
.data(ExpectedData)
.attr('class', 'group')
.enter().append('g')
.each(function(d, i) {
d.Data.map( (obj, j, arr) => {
d3.select(this)
.append('rect')
.attr('class', 'bar')
.attr('data-index', j)
.attr('x', function(e) { return x(d.Date); })
.attr('width', x.bandwidth())
.style('fill', function(e) { return obj.color; })
.attr('y', function(e) { let sum = 0;
arr.map((obj_1, k) => {
if (k < j) { sum = sum + obj_1.Percentage; }
});
return y(obj.Percentage + sum);
})
.attr('height', function(e) { return height - y(obj.Percentage); });
});
});
g.append('g')
.attr('class', 'axis axis--x')
.attr('transform', 'translate(0,' + height + ')')
.call(d3.axisBottom(x));
g.append('g')
.attr('class', 'axis axis--y')
.call(d3.axisLeft(y));
g.selectAll('rect')
.on('mouseover', function(d) { d3.select(this).style('fill', 'gray'); })
.on('mouseout', function(d, i, j) { const index = j[i].attributes['data-index'].nodeValue;
d3.select(this).style('fill', d.Data[index].color);
});
</script>
</body>
</html>

dc.js scatter plot filtering dimension from selection of control

I am attempting to filter a dimension on a scatter and the plots rendered via a control (select box) and I am having trouble getting this to work. Perhaps I am thinking because the dimension returns an array [key, value, value] and I am trying to filter using a text value from the control.
<div id="chart-scatter"></div>
<select id="selection">
<option value="BranchA">Branch A</option>
<option value="BranchB">Branch B</option>
<option value="BranchC">Branch C</option>
<option value="BranchD">Branch D</option>
</select>
var transactions = [
{
accountType: 9,
amount: 284,
serviceName: "BranchE"
},
{
accountType: 7,
amount: 716,
serviceName: "BranchE"
},
{
accountType: 5,
amount: 899,
serviceName: "BranchD"
},
{
accountType: 8,
amount: 781,
serviceName: "BranchD"
},
{
accountType: 5,
amount: 295,
serviceName: "BranchA"
},
{
accountType: 9,
amount: 770,
serviceName: "BranchB"
},
{
accountType: 9,
amount: 195,
serviceName: "BranchE"
},
{
accountType: 5,
amount: 335,
serviceName: "BranchF"
},
{
accountType: 10,
amount: 74,
serviceName: "BranchF"
},
{
accountType: 10,
amount: 223,
serviceName: "BranchC"
},
{
accountType: 5,
amount: 290,
serviceName: "BranchD"
},
{
accountType: 10,
amount: 485,
serviceName: "BranchA"
},
{
accountType: 7,
amount: 364,
serviceName: "BranchE"
},
{
accountType: 9,
amount: 509,
serviceName: "BranchB"
},
{
accountType: 8,
amount: 74,
serviceName: "BranchC"
},
{
accountType: 9,
amount: 442,
serviceName: "BranchE"
}
];
filter = crossfilter(transactions);
dim = filter.dimension(function(d) {
return [d.accountType, d.amount, d.serviceName];
});
grp = dim.group();
scatterChart = dc.scatterPlot("#chart-scatter");
scatterChart
.width(380)
.height(200)
.margins({
top: 10,
right: 20,
bottom: 30,
left: 40
})
.dimension(dim)
.group(grp)
.x(d3.scale.linear().domain([4., 11.]))
.y(d3.scale.linear().domain([0., 1000.]))
.renderHorizontalGridLines(true)
.renderVerticalGridLines(true)
.symbolSize(30)
.highlightedSize(8)
.colorAccessor(function(d) {
return d.key[2];
})
.colors(d3.scale.ordinal()
.domain(['BranchA', 'BranchB', 'BranchC','BranchD','BranchE', 'BranchF'])
.range(["#fa3701", "#339933", "#bbbbbb","#aaaaaa","#999999","#888888"])
);
d3.select("#selection").on('change', function(){
dim.filter($("#selection").val())
scatterChart.redraw();
dc.redrawAll();
})
dc.renderAll();
from examples I have found this seems like the approach in various places, however none are really for a scatter that I can find and I am wondering what the difference would be given the dim = array
JSFiddle
A group does not observe filters on its own dimension (https://github.com/crossfilter/crossfilter/wiki/Crossfilter-Gotchas#a-group-does-not-observe-its-dimensions-filters). Because you are filtering on the same dimension the group is defined on, the filter has no affect on the group.
Define a 2nd dimension for the service name and put a default filter in place:
serviceDim = filter.dimension(function(d) {
return "" + d.serviceName;
})
serviceDim.filter('BranchA')
Then update your change function as follows:
d3.select("#selection").on('change', function(){
serviceDim.filter($("#selection").val())
//scatterChart.redraw();
dc.redrawAll();
})
Here is an updated JSFiddle: https://jsfiddle.net/esjewett/uhvh23b0/

Resources