Proper data structure for d3 nodes - json

I want to build force-directed graph using d3. Each nodes of the graph is a character from Marvel comics (e.g. Spider-man). If the characters appear in one comic there should be a link between the nodes which represent them.
I have a characters_by_comics.json file which contains array of object the following type:
{
"comic_id": 2,
"characters": [
1009220,
1010776,
1009378,
1010363,
1009215,
1009471,
1009718
]
},
I also have characters.json which contains all info about each character including their character_id. In addition, comics.json contains the title of every comic and its comic_id.
The graph is going to be quiet huge. That will affect user experience in a negative way. So, to prevent this I added some information about each character, such as race, gender, alive, etc. Using this information then I am planning to:
Create different filters that can be applied to the graph. For example, if I apply only female filter, only the nodes representing female characters should be visible
Create different types of links: not only appear in one comic, but also belongs to one team, for example.
My question is how can I transform the data I have so it is easy to create nodes and links from it using d3?

You don't include a snippet of characters.json, but that is your node data. Sounds like it might already formatted correctly. It would need to be something like this:
var characters = [
{
id: 1009220,
name: 'Ms Marvel'
}, {
id: 1010776,
name: 'Spiderman'
}
....
];
Now the real question becomes, how do we get your link data from your characters_by_comics.json? Assuming a structure like this:
var charactersByComic = [{
"comic_id": 2,
"characters": [
1009221,
1010776,
...
]
}, {
"comic_id": 3,
"characters": [
1009221,
1009220,
1009379,
...
]
}];
You could do it like:
var linkData = {}; // object to hold data
charactersByComic.forEach(d0 => { // loop the master data
d0.characters.forEach(d1 => { // outer loop of characters
d0.characters.forEach(d2 => { // inner loop of characters
var key = d1 + "|" + d2; // create unique key to prevent duplicate relationshipts
if (d1 !== d2 && !linkData[key]) { // if its not the same character, and they don't already have a relationship
linkData[key] = { // add it
source: d1,
target: d2
};
}
});
});
});
linkData = Object.values(linkData); // take just the array of relationships
This would produce our desired link structure:
[
{"source":1009221,"target":1010776},
{"source":1009221,"target":1009378},
...
]
Now we can jam this whole thing into a force directed graph:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
</style>
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="200" height="200"></svg>
<script>
var characters = [{
id: 1009220,
name: 'Ms Marvel'
}, {
id: 1010776,
name: 'Spiderman'
}, {
id: 1009378
}, {
id: 1010363
}, {
id: 1009215
}, {
id: 1009471
}, {
id: 1009718
}, {
id: 1009221
}, {
id: 1010777
}, {
id: 1009379
}, {
id: 1010361
}, {
id: 1009212
}, {
id: 1009474
}, {
id: 1009715
}];
var charactersByComic = [{
"comic_id": 2,
"characters": [
1009221,
1010776,
1009378,
1010363,
1009215,
1009471,
1009718,
1010777
]
}, {
"comic_id": 3,
"characters": [
1009221,
1009220,
1009379,
1010361,
1009212,
1009474,
1009715,
1010777
]
}];
var linkData = {};
charactersByComic.forEach(d0 => {
d0.characters.forEach(d1 => {
d0.characters.forEach(d2 => {
var key = d1 + "|" + d2;
if (d1 !== d2 && !linkData[key]) {
linkData[key] = {
source: d1,
target: d2
};
}
});
});
});
linkData = Object.values(linkData);
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
console.log(d)
return d.id;
}))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(linkData)
.enter().append("line")
.attr("stroke-width", 1);
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(characters)
.enter()
.append("circle")
.attr("r", 5)
.attr("fill", "steelblue");
node.append("title")
.text(function(d) { return d.id; });
simulation
.nodes(characters)
.on("tick", ticked);
simulation.force("link")
.links(linkData);
console.log(simulation)
function ticked() {
link
.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
node
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
}
</script>

Related

Why are there connections between these points in chartjs?

I have an angular apllication I which I display a chart. The chart is filled with data from a database. The y axis displays sensordata and the x axis displays a timestamp. The timestamp is also a column in the database. When I start the app there are connections between some points that I don´t want and that don´t make sense! Why is that so?
ngOnInit(): void {
var chartOptions = {
plugins: {
legend: {
display: false
}
},
responsive: true,
maintainAspectRatio: false,
animation: {duration: 0}
};
var tempChart = new Chart('tempChart', {
type: 'line',
data: {
labels: [],
datasets: [{
data: [0],
borderWidth: 1
}]
},
options: chartOptions
});
setInterval(() => {
// API request
this.service.getMesswerte().subscribe({
next: data => {
this.chartData = data;
this.fillChartData(this.chartData);
}
})
}, 1000)
setInterval(() => {
tempChart.data.datasets[0].data = this.tempArray;
tempChart.update();
}, 1000)
}
private fillChartData(chartData: Object)
{
this.objects = Object.values(chartData);
for(let row in this.objects)
{
this.tempObject = {
x: this.objects[row].timestamp,
y: this.objects[row].airtemp
}
this.tempArray.push(this.tempObject);
}
}

How to set up links and tooltip in d3 force directed layout

I am trying to put together a force directed layout in d3 but am having some problems getting my links in. I am trying to create links that connect from each of my nodes in the json array to static svg nodes that I created. Do I need to have a json file that includes both links and nodes? Or can I reference them separately. What am I missing in my code to make this happen?
Here's the json file for nodes:
https://api.myjson.com/bins/1rnhq
And here's the json file for links:
var links = [
{ source: academiaNode, target: 0 },
{ source: academiaNode, target: 1 },
{ source: academiaNode, target: 2 },
{ source: academiaNode, target: 3 },
{ source: academiaNode, target: 4 },
{ source: academiaNode, target: 5 },
{ source: academiaNode, target: 6 },
{ source: academiaNode, target: 7 },
{ source: ngoNode, target: 8 },
{ source: ngoNode, target: 9 },
{ source: ngoNode, target: 10 },
{ source: ngoNode, target: 11 },
{ source: ngoNode, target: 12 },
{ source: ngoNode, target: 13 },
{ source: ngoNode, target: 14 },
{ source: ngoNode, target: 15 },
{ source: ngoNode, target: 16 },
{ source: commonwealthNode, target: 17 },
{ source: commonwealthNode, target: 18 },
{ source: commonwealthNode, target: 19 },
{ source: commonwealthNode, target: 20 },
{ source: commonwealthNode, target: 21 },
{ source: commonwealthNode, target: 22 },
{ source: commonwealthNode, target: 23 },
{ source: commonwealthNode, target: 24 },
{ source: commonwealthNode, target: 25 },
{ source: academiaNode, target: 26 },
{ source: commonwealthNode, target: 27 },
{ source: legislatureNode, target: 28 },
{ source: legislatureNode, target: 29 },
{ source: legislatureNode, target: 30 },
{ source: legislatureNode, target: 31 },
{ source: federalNode, target: 32 },
{ source: federalNode, target: 33 },
{ source: federalNode, target: 34 },
{ source: federalNode, target: 35 },
{ source: federalNode, target: 36 },
{ source: federalNode, target: 37 },
{ source: federalNode, target: 38 },
{ source: federalNode, target: 39 },
{ source: federalNode, target: 40 },
{ source: federalNode, target: 41 },
{ source: federalNode, target: 42 },
{ source: federalNode, target: 43 },
{ source: federalNode, target: 44 },
{ source: militaryNode, target: 45 },
{ source: militaryNode, target: 46 }
];
I'm also trying to set up a click function on my nodes so that an svg rectangle expands out from the left of the screen and displays the description of each node. You can see the description I am trying to display in the nodes json file. Here's the d3 code I'm working with to power the visualization.
<script type= "text/javascript">
var w = 1000,
h = 650;
var svg = d3.select("body").append("svg")
.attr("height", 0)
.attr("width", 0)
.style("border", "1px solid black");
var data; // a global
var force = d3.layout.force()
.size([w, h])
.linkDistance([150])
.charge([-1050])
.gravity(0.5)
.on("tick", tick);
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
var academiaNode = svg.append("circle")
.attr("cx", 125)
.attr("cy", 300)
.attr("r", 10)
.attr("fill", "lightblue");
var ngoNode = svg.append("circle")
.attr("cx", 225)
.attr("cy", 300)
.attr("r", 10)
.attr("fill", "lightblue");
var federalNode = svg.append("circle")
.attr("cx", 325)
.attr("cy", 300)
.attr("r", 10)
.attr("fill", "lightblue")
var commonwealthNode = svg.append("circle")
.attr("cx", 425)
.attr("cy", 300)
.attr("r", 10)
.attr("fill", "lightblue");
var legislatureNode = svg.append("circle")
.attr("cx", 525)
.attr("cy", 300)
.attr("r", 10)
.attr("fill", "lightblue");
var commonwealthNode = svg.append("circle")
.attr("cx", 625)
.attr("cy", 300)
.attr("r", 10)
.attr("fill", "lightblue");
var circles = svg.selectAll(".node");
d3.json("https://api.myjson.com/bins/1rnhq", function(error, json) {
if (error) return console.warn(error);
data = json;
console.log(data);
force.nodes(data).links()
.start();
// Update nodes.
circles = circles.data(data);
circles.exit().remove();
var nodeEnter = circles.enter().append("g")
.attr("class", "node")
.style("fill", "#000")
.style("opacity", 0.75)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", click)
.call(force.drag);
nodeEnter.append("circle")
.attr("r", function(d) { return d.sector == "Academia" ? 1:5 });
nodeEnter.attr("cursor", "pointer");
// Update links
var links = svg.selectAll(".link")
.data(data.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", "1px");
links.exit().remove();
function mouseover() {
d3.select(this).select("circle").transition()
.duration(250)
.attr('r', 10);
}
function mouseout() {
d3.select(this).select("circle").transition()
.duration(250)
.attr('r', 5);
}
nodeEnter.append("text")
.attr("text-anchor", "middle")
.style("font-size", ".75em")
.attr("dy", "-0.85em").text(function (d) { return d.name });
var tooltip = svg.append("rect")
.attr("x", 1000)
.attr("y", 0)
.attr("width", 900)
.attr("height", 700)
.attr("opacity", 0.85);
function click() {
d3.select(tooltip).transition()
.duration(450)
.attr("x", 650)
};
});
function tick() {
links.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
circles.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
};
</script>
The default for the links is: source: index_number, target: index_number
You should have in the source and target the same: or the index number or just an id (which has to be also in the json for the nodes), or a name, ..
So if you don't connect your links with the index_number then you have to add something like this:
var nodeById = d3.map();
graph.nodes.forEach(function(node) {
nodeById.set(node.id, node);
});
graph.links.forEach(function(link) {
link.source = nodeById.get(link.source);
link.target = nodeById.get(link.target);
});
Here in that example i connected the likes with the node.id.
My json file looked like this:
{"nodes": [
{
"id":9053,
"name":"0009053",
"category":"AP"
}
,{
"id":9136,
"name":"Curon Venosta",
"category":"TCS"
}
,{
"id":9116,
"name":"Dummy",
"category":"EQP"
}
,{
"id":9286,
"name":"T",
"category":"EQ"
}
],
"links": [
{
"source":9136,
"target":9053
}
,{
"source":9116,
"target":9136
}
,{
"source":9286,
"target":9116
}
]}
See also this: http://bl.ocks.org/mbostock/533daf20348023dfdd76
Hope this helps you a bit..

Get current day values from database to plot on highcharts

Hello i am designing a webserver which shows me a power consumption graph from a mysql database. I am a beginner and have written a code with some help that uses ajax calls to fetch the data from the database and plot it on the highcharts. Problem is that I get all the historic values saved in my database but I want only the values of current day to be plotted on the chart. This is my js code:
Highcharts.setOptions({
global: {
useUTC: false
}
});
$('#container').highcharts({
chart: {
type: 'area',
animation: Highcharts.svg, // don't animate in old IE
marginRight: 10,
},
title: {
text: ''
},
xAxis: {
type: 'datetime',
tickPixelInterval: 100
},
yAxis: {
title: {
text: 'POWER'
},
plotLines: [{
value: 0,
width: 1,
color: '#808080'
}]
},
tooltip: {
formatter: function () {
return '<b>' + this.series.name + '</b><br/>' +
Highcharts.dateFormat('%d-%m-%Y %H:%M:%S', this.x) + '<br/>' +
Highcharts.numberFormat(this.y);
}
},
legend: {
enabled: true
},
exporting: {
enabled: false
},
series: [{
name: 'Power Consumption',
data: (function () {
var data = [], i, t, d;
var request = {};
request['EndPoint'] = "arduino/charts/analog/fetch";
$.ajax({
type: "POST",
url: "../Server/fyp-processor.php",
data: JSON.stringify(request),
dataType: "json",
async: false,
success: function (result) {
if(result.Code == "1008" && (result.Data != null || result.Data != undefined)) {
for(i = 0; i < result.Data.Historic.length; i++) {
t = result.Data.Historic[i].DateTime.split(/[- :]/);
d = new Date(t[0], t[1]-1, t[2], t[3], t[4], t[5]);
data.push({
x: d,
y: parseInt(result.Data.Historic[i].Value)
});
}
}
}
});
return data;
}())
}]
});
It calls a fyp-processor php script that contains this code:
else if($postData['EndPoint'] == 'arduino/charts/analog/fetch') {
#result set
$resultSet = null;
#get all historic analog chart data
$results = $db->charts();
if(count($results) > 0) {
foreach($results as $result) {
$resultSet['Historic'][] = $result;
}
}
I would be glad if anyone could help me out to get only current DAY's data and also what changes I can do to it then to get current month and current week's data too.

Read in csv data for multiBarHorizontalChart in NVD3?

I'd like to visualise data in a "multiBarHorizontalChart" in NVD3 and read the data from a .csv file.
I'm struggling to get the data in the right format. According to NVD3.org the format for multiBarHorizontalChart has to be:
[
{
"key": "Series 1",
"color": "#d67777",
"values": [
{
"label" : "Group A" ,
"value" : -1.8746444827653
} ,
{
"label" : "Group B" ,
"value" : -8.0961543492239
}
]
},
The code that I'm using is below. Could someone tell me what I'm doing wrong?
d3.csv("File.csv", function (error, csv) {
if (error) return console.log("there was an error loading the csv: " + error);
console.log("there are " + csv.length + " elements in my csv set");
var mmm = ["pre_ineq","post_ineq"];
for (var i = 0; i < mmm.length; i++) {
myall[i].values.label = csv.map(function(d) { return [ d["label"] ]; });
myall[i].values.value = csv.map(function(d) { return [ +d[mmm[i]] ]; });
//or? myall[i].values = csv.map(function(d) { return [ label=d["label"], +d[mmm[i]] ]; });
};
var chart;
nv.addGraph(function() {
var chart = nv.models.multiBarHorizontalChart()
.x(function(d) { return d.label })
.y(function(d) { return d.value })
.margin({top: 30, right: 20, bottom: 50, left: 175})
.showValues(true)
.tooltips(false)
.showControls(false);
chart.yAxis
.tickFormat(d3.format(',.2f'));
d3.select('#chart1')
.datum(myall)
.transition().duration(500)
.call(chart);
nv.utils.windowResize(chart.update);
return chart;
});
});
Thanks a lot!
Guess you should do something like
for (var i = 0; i < mmm.length; i++) {
myall[i].values = csv.map(function(d) {
return { "label": d["label"], "value": +d[mmm[i]] };
});
};
But it's not tested.

Highcharts bargraph from json data in angularJS

I have a highcharts bargraph whose values are received from json whose format is as follows:
"bargraph":
[
{
"categories": "['S', 'M', 'T', 'W', 'T', 'F']",
"series1": "[800, 1100, 80, 1800, 1600, 2000]",
"series2": "[800, 1100, 80, 1800, 1200, 800]"
}
]
How can i embed those values for my bargraph in angularJS
HTML Code:
<div id="bargraph" bargraph={{bargraph}}><\div>
Directive:
angular.module('example').directive('bargraph', function () {
element.highcharts({
xAxis: [
{
categories: [] //embed categories value here
},
series: [
{
name: 'series1',
data: [] //Embed series1 data here
},
{
name: 'series2',
data: [] //Embed series2 data here
}
]
})
})
Please provide a suitable way to embed the data from json.
Here is a directive i copied and pasted from my webapp it is how i render highcharts using a directive NOTE: not all of this directive is applicable to you and some of it is specific to what i needed but you get the idea.
lrApp.directive('chart', function () {
return {
restrict: 'E',
template: '<div></div>',
transclude: true,
replace: true,
link: function (scope, element, attrs) {
var chart = null;
var chartsDefaults = {
chart: {
renderTo: element[0],
type: attrs.type || null,
height: attrs.height || null,
width: attrs.width || null,
},
colors: scope.$eval(attrs.colors) || null,
title: {
style: {
display: 'none'
}
},
xAxis: {
//categories: ['{"-7 days"|date_format}','{"-6 days"|date_format}','{"-5 days"|date_format}','{"-4 days"|date_format}', '{"-3 days"|date_format}', '{"-2 days"|date_format}', '{"-1 day"|date_format}', '{$smarty.now|date_format}'],
categories: scope.$eval(attrs.dates) || null,
gridLineDashStyle: 'ShortDot',
gridLineColor: "#C0C0C0",
gridLineWidth: 1,
labels: {
y: 27
}
},
yAxis: {
title: {
text: null
},
min: 0,
gridLineDashStyle: 'ShortDot',
gridLineColor: "#C0C0C0",
gridLineWidth: 1
},
credits: {
enabled: false
},
legend: {
enabled: false
},
plotOptions: {
series: {
shadow: false,
lineWidth: 3
}
},
tooltip: {
formatter: function () {
return '<b>' + this.series.name + '</b><br/>' +
this.x + ': ' + this.y + '</b>';
}
}
};
//Update when charts data changes
scope.$watch(attrs.value, function (newVal, oldVal) {
if (!newVal.length) return;
// We need deep copy in order to NOT override original chart object.
// This allows us to override chart data member and still the keep
// our original renderTo will be the same
var deepCopy = true;
var newSettings = {};
chartsDefaults.series = newVal;
chartsDefaults.colors = scope.$eval(attrs.colors);
chartsDefaults.xAxis.categories = scope.$eval(attrs.dates);
console.log(chartsDefaults);
chart = new Highcharts.Chart(chartsDefaults);
});
}
}
});
and this is how it used it obviously you would change "line" to bar:
<chart value="stats.sets" dates="stats.days" colors="stats.colors" type="line"></chart>