I am new with d3 and json. I am trying to construct a horizontal gantt chart. Earlier I have acieved the same using inline arrays which i stored in var dataset. But now I have replaced the arrays with json object array .
[ {
"process" :"process-name 1",
"stage" : "stage name 1",
"activities": [
{
"activity_name": "waiting",
"start": new Date('2012-12-10T06:45+05:30'),
"end": new Date('2012-10-10T08:45+05:30'),
},
{
"activity_name": "processing",
"start": new Date('2012-10-11T05:45+05:30'),
"end": new Date('2012-10-11T06:45+05:30'),
},
{
"activity_name": "pending",
"start": new Date('2012-10-12T11:45+05:30'),
"end": new Date('2012-10-13T12:45+05:30'),
}
]
},
{
"process" :"process-name 2",
"stage" : "stage name 2",
"activities": [
{
"activity_name": "waiting",
"start": new Date('2012-10-22T06:45+05:30'),
"end": new Date('2012-10-23T08:45+05:30'),
},
{
"activity_name": "processing",
"start": new Date('2012-10-25T05:45+05:30'),
"end": new Date('2012-10-25T06:45+05:30'),
},
{
"activity_name": "pending",
"start": new Date('2012-10-27T11:45+05:30'),
"end": new Date('2012-10-27T12:45+05:30'),
}
]
}
];
The d3 part of the code to draw the rectangle is this :
console.log(dataset);
var height = 600;
var width = 500;
var x = d3.time.scale().domain([new Date(2011, 0, 1,23,33,00), new Date(2013, 0, 1, 23, 59)]).range([0, width]);
var svg = d3.selectAll("body")
.append("svg")
.attr("width",width)
.attr("height",height)
.attr("shape-rendering","crispEdges");
temp = svg.selectAll("body")
.data(dataset) // LINE 1
.enter()
temp.append("rect")
.attr("width", 4)
.attr("height",12)
.attr("x", function(d) {return x(d.activities[0].start)}) // LINE 2
.attr("y",function(d,i){return i*10;})
.attr("fill","steelblue");
I have following questions :
I want to access START date inside activities but with the code lines (marked LINE 1 and LINE 2) I have written it is giving only 2 rectangles for first start of each activity . WHY?
In LINE 1 can I use dataset.actitvies instead of just dataset? I tried using that , gave me this error :
Uncaught TypeError: Cannot read property '0' of undefined
Because the length of dataset is 2, so when you join it with your rects you're only going to get 2 rects.
You can use dataset.activities, but you would change LINE 2 to
.attr("x", function(d) {return x(d.start)})
because you've changed the join so that d now refers to the activities. However, if you do that, you're just going to get three rectangles -- the activities for one of your projects.
If you're going to use nested data, you either need to use a nested selection or you need to flatten the data first by adding all activities to a simple array.
var data = [];
for (var i = 0, len = dataset.length; i < len; i++) {
data = data.concat(dataset[i].activities);
}
A nested selection would look something like this:
projects = svg.selectAll("g")
.data(dataset)
.enter().append("g")
.attr("transform", function (d, i) { return "translate(0," + (i * 10) + ")"; }),
rects = projects.selectAll("rect")
.data(function (d) { return d.activities; })
.enter().append("rect")
.attr("height", 12)
.attr("width", 4)
.attr("x", function (d) { return x(d.start); })
EDIT: here's a jsfiddle
Related
Using the latest Flare build originally built by prefuse, I am trying to get an indent field like the one in Layouts seen here. I am working with a list of objects that I pull from Google Firebase. While I can put them on a graph just fine and compare one and other values I can't find instructions on the different layouts. I am about to study the docs but I wanted to know if there was anything else out there I could reference.
Edit:
http://flare.prefuse.org/api/flare/vis/operator/layout/Layout.html I have found the general layouts here. However I only am able to show 1 or two circles unless I do AxisLayout.
For reference, my data pulled from firebase is something like this.
An array of objects.
Each object has properties name, sales, date, active and such.
I want it to act like the example above and show the item name in each circle. Then when the user clicks the circle he is able to show the properties of the item.
Edit: I was able to find an article on it, but after hours of constructing my data around his set format, I am not able to mock it entirely.
http://simon.oconnorlamb.com/ria/2012/03/visualising-data-with-flare/
Edit: To go into detail:
When I pull from my json list from Google Firebase I parse it so that it puts the items in referencable categories. All of which go into an array to mock the tutorial I linked above. I am trying to mock the structure as close as possible.
private function handleDataRead(e:DatabaseEvent):void
{
var trueDataArray:Array = new Array();
//Extract manufacturers.
var manufacturers:Vector.<String> = new Vector.<String>();
for each (var item:Object in e.data)
{
if (manufacturers.indexOf(item.Manufacturer) == -1)
{
manufacturers.push(item.Manufacturer);
//Example: {type:'Manufacturer',id:'0',name:'Company A'}
trueDataArray.push( {type:'manufacturer',
id:manufacturers.indexOf(item.Manufacturer).toString(),
name:item.Manufacturer});
}
}
//Extract Item Name
var itemNames:Vector.<String> = new Vector.<String>();
for each (var item:Object in e.data)
{
if (itemNames.indexOf(item.ItemName) == -1)
{
itemNames.push(item.ItemName);
var idValue:String = new String(itemNames.indexOf(item.ItemName) + (manufacturers.length - 1) +1);
trueDataArray.push( {type:'item',
id:idValue,
name:item.ItemName,
manufacturer:manufacturers.indexOf(item.Manufacturer).toString()} );
}
}
//Extract property 1
var mlCount:Vector.<int> = new Vector.<int>();
for each (var item:Object in e.data)
{
if (item.hasOwnProperty("ML"))
{
if (mlCount.indexOf(item.ML) == -1)
{
mlCount.push(item.ML);
var idValue:String = new String(mlCount.indexOf(item.ML) + (itemNames.length - 1) + (manufacturers.length - 1) +1);
trueDataArray.push({type:'mL',
id:idValue,
name:(item.ML as int).toString(),
item:itemNames.indexOf(item.ItemName).toString()});
}
}
}
//Extract another property
var mgCount:Vector.<int> = new Vector.<int>();
for each (var item:Object in e.data)
{
if (item.hasOwnProperty("MG"))
{
if (mgCount.indexOf(item.MG) == -1)
{
mgCount.push(item.MG);
var idValue:String = new String(mgCount.indexOf(item.MG) + mlCount.indexOf(item.ML) + (itemNames.length - 1) + (manufacturers.length - 1) +1);
trueDataArray.push({type:'mG',
id:idValue,
name:(item.MG as int).toString(),
mL:mlCount.indexOf(item.ML).toString()});
}
}
}
The result looks like this.
[
{
"name":"Company A",
"type":"manufacturer",
"id":"0"
},
{
"name":"Company B",
"type":"manufacturer",
"id":"1"
},
{
"name":"Company C",
"type":"manufacturer",
"id":"2"
},
{
"name":"Company D",
"type":"manufacturer",
"id":"3"
},
{
"name":"Company E",
"type":"manufacturer",
"id":"4"
},
{
"type":"manufacturer",
"id":"5"
},
... //So on
{
"manufacturer":"0",
"name":"Item Name 1",
"type":"item",
"id":"18"
},
{
"manufacturer":"0",
"name":"Item Name 2",
"type":"item",
"id":"19"
},
{
"manufacturer":"0",
"name":"Item Name 3",
"type":"item",
"id":"20"
},
{
"manufacturer":"0",
"name":"Item Name 4",
"type":"item",
"id":"21"
...//So on
{
"name":"60",
"item":"0",
"type":"mL",
"id":"195"
},
{
"name":"100",
"item":"5",
"type":"mL",
"id":"196"
},
{
"name":"120",
"item":"36",
"type":"mL",
"id":"197"
},
{
"name":"30",
"item":"100",
"type":"mL",
"id":"198"
}
...//and so forth
]
When I pass this to my function to create the nodes and edges (that I again based off the blog in the link above)
public function buildTree(arr:Array):Data
{
var d:Data = new Data(true);
//Keyed lookup for easy edge addition in step 2
var nodeLookup:Object = {};
var row:Object;
var ns:NodeSprite;
//Step 1: Add all rows of data;
for each(row in arr){
ns = d.addNode(row);
nodeLookup[row.id] = ns;
}
//Step 2: Add edges
for each(ns in d.nodes){
if(ns.data.hasOwnProperty('manufacturer')){
d.addEdgeFor(nodeLookup[ns.data.manufacturer],ns);
}
else if(ns.data.hasOwnProperty('item')){
d.addEdgeFor(nodeLookup[ns.data.item],ns);
}
else if(ns.data.hasOwnProperty('mL')){
d.addEdgeFor(nodeLookup[ns.data.mL],ns);
}
}
return d;
}
and construct it
data = buildTree(trueDataArray);
sourceTree = new ItemVisualisation(data);
sourceTree.bounds = new Rectangle(10, 10, 550, 550);
sourceTree.x = 20;
sourceTree.y = 20;
addChild(sourceTree);
sourceTree.operators.add(new IndentedTreeLayout());
sourceTree.operators.add(new ShapeEncoder("data.type"));
sourceTree.operators.add(new ColorEncoder("data.type", Data.NODES, "lineColor", ScaleType.CATEGORIES));
sourceTree.data.nodes.setProperties({fillColor:0, lineWidth:2});
sourceTree.update();
I get the following..
I almost have this down but I don't know what I am doing wrong. Everything seems to be as it should in relation.
Edit: It seems that the nodes may be linking properly with each other however this is not the layout I desire. I cannot get any other layouts to work either.
trueDataArray.push({type:'root', id:0, name:'rootname'});
I was able to solve this problem by binding everything to 1 node. I was having some trouble listing anything beyond two steps but that is beyond the requirement of my project.
I am trying to create an animated pie chart using a JSON file to store the data.
My JSON file is like this:
{"data":[
{"ap": [
{"floorratio": [
{"floor":"Basement", "ratio": 0.20},
{"floor":"Ground", "ratio": 0.20},
{"floor":"First Floor", "ratio": 0.15},
{"floor":"Second Floor", "ratio": 0.20},
{"floor":"Third Floor", "ratio": 0.25}
]}
]},
{"ap": [
{"floorratio": [
{"floor":"Basement", "ratio": 0.10},
{"floor":"Ground", "ratio": 0.30},
{"floor":"First Floor", "ratio": 0.10},
{"floor":"Second Floor", "ratio": 0.15},
{"floor":"Third Floor", "ratio": 0.35}
]}
]}
]}
and my HTML code:
<!doctype html>
<html>
<head>
<title>Pie Chart Test</title>
<script src="d3.min.js"></script>
</head>
<style>
body {
font: 10px sans-serif;
}
.arc path {
stroke: #fff;
}
</style>
<body>
<label><input type="radio" name="dataset" value="0" checked> 1</label>
<label><input type="radio" name="dataset" value="1"> 2</label>
<script>
var width = 960,
height = 500,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var pie = d3.layout.pie()
.sort(null)
.value(function (d) { return d.ratio;});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var nodeSelected;
d3.json("APCategories.json", function (error, data) {
//initialise data
floorRatio = data.data[0].ap[0].floorratio;
var g = svg.selectAll(".arc")
.data(pie(floorRatio))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function (d) {
return color(d.data.floor);
})
.each(function(d) {this._current = d;});
g.append("text")
.attr("transform", function (d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function (d) {
return d.data.floor;
});
//showing initial pie chart code ends here
function changePieChart() {
nodeSelected = this.value;
newFloorRatio = data.data[nodeSelected].ap[0].floorratio;
g = g.data(pie(newFloorRatio));
g.transition().duration(750).attrTween("d", arcTween);
}
function radioSelectedChange() {
nodeSelected = this.value;
changePieChart();
}
d3.selectAll("input")
.on("change", changePieChart);
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
});
</script>
</body>
</html>
So at the moment I am only loading the first set of data because I am calling
floorRatio = data.data[0].ap[0].floorratio
but what I want to be able to do is to have something more along the lines of:
floorRatio = data.data[i].ap[0].floorratio
I have tried to do this in the changePieChart function, however I think the issue lies with the initialising of the data and I am not quite sure of the correct way to do it with JSON files.
Note: My json file is only a small example of a much larger version.
I want to create one pie chart that updates/changes but am unsure how to with JSON data.
What is the correct way of loading all of the data and then utilising it?
You will have to write a function like forEach to browse through each element inside json data. Look at this plnkr link. d will have the object i.e in this example (data.data) and i will hold the index of each object.
d3.json("json/APCategories.json", function (error, data) {
//initialise data
//floorRatio = data.data[0].ap[0].floorratio;
(data.data).forEach(function(d,i) {
console.log(d.ap[i].floorratio);
// d.data = +d.data;
floorRatio = d.ap[i].floorratio;
console.log(floorRatio);
});
EDITED :
I believe you want to draw multiple pie charts for on number of "ap" objects see this link for working demo.
This link will show svg object which will update when selection changes.
Im having some trouble filtering using d3 based on a passed in value
Im using JSON data and want to filter the data thats returned into the table based on a value
"companyid": "151590",
"receivingCompany": "1182316",
"value": "145129619",
"date": "22/01/2012",
"int": "13"
Here is my D3
d3.json('transactions.json', function (error,transactions) {
function tabulate(data, columns) {
var table = d3.select('div#transaction-table').append('table').attr("class", function (d) {return 'table table-hover table-striped'})
var thead = table.append('thead')
var tbody = table.append('tbody');
var titles = [ "Borrowing Company", "Lending Company", "Amount (USD)", "Inception (DD/MM/YY)", "Interest (%)" ];
// append the header row
thead.append('tr')
.selectAll('th')
.data(titles).enter()
.append('th')
.text(function (titles) { return titles; });
// create a row for each object in the data
var rows = tbody.selectAll('tr')
.data(data)
.enter()
.append('tr');
// create a cell in each row for each column
var cells = rows.selectAll('td')
.data(function (row) {
return columns.map(function (column) {
return {column: column, value: row[column]};
});
})
.enter()
.append('td')
.text(function (d) { return d.value; });
return table;
}
// render the table(s)
tabulate(transactions, ['companyid', 'receivingCompany', 'value' , 'date'])
});
I want to filter the data returned based on the companyid.
.filter(function(a) { return a.companyid == 137 })
This what ive found when searching online but this doesnt seem to work it just returns all the fields but only with "137" as a value and not the data that refers to 137.
Thanks
I have a json file that has a large "Charts" section with many different charts of different names. Using Dimple.JS I'm trying to build charts directly from the json, but I can't figure out how to get the x and y data in correctly. I want to plot x[0], y[0]
{ "Name": "test", "Charts": [
{
"chartName": "Idle Times",
"disk": 0,
"OpCode": "Read",
"xAxis": [0,100,200,300,400,500],
"yAxis": [337487,8565,11419,9704,7598]
},
{
"chartName": "Idle Times",
"disk": 0,
"OpCode": "Read",
"xAxis": [0,100,200,300,400,500],
"yAxis": [337487,8565,11419,9704,7598]
}]
}
Here is where the chart is being built, this builds a chart, but with just one point. When you hover over the chart it shows the x-axis array, and the y-axis array, but it's only charting the first numbers.
var data = $rootScope._idleTimes.items;
console.log(data);
var svg = dimple.newSvg("#idle-times-chart-container", 1000, 500);
var chart = new dimple.chart(svg, data);
chart.addCategoryAxis("x", "xAxis");
var y = chart.addCategoryAxis("y", "yAxis");
y.tickFormat = "%";
chart.addSeries("OpCode", dimple.plot.bar);
var myLegend = chart.addLegend(950, 50, 60, 100, "Right");
chart.draw();
chart.legends = [];
svg.selectAll("title_text")
.data([" "])
.enter()
.append("text")
.attr("x", 499)
.attr("y", function (d, i) { return 90 + i * 14; })
.style("font-family", "sans-serif")
.style("font-size", "10px")
.style("color", "Black")
.text(function (d) { return d; });
var filterValues = dimple.getUniqueValues(data, "OpCode");
myLegend.shapes.selectAll("rect")
.on("click", function (e) {
var hide = false;
var newFilters = [];
filterValues.forEach(function (f) {
if (f === e.aggField.slice(-1)[0]) {
hide = true;
} else {
newFilters.push(f);
}
});
if (hide) {
d3.select(this).style("opacity", 0.2);
} else {
newFilters.push(e.aggField.slice(-1)[0]);
d3.select(this).style("opacity", 0.8);
}
filterValues = newFilters;
chart.data = dimple.filterData(data, "OpCode", filterValues);
chart.draw(1000);
});
My question is, do I need to reformat the json file so there is one record per line? Something like this:
{"chartName":"Idle Times", "disk":0, "OpCode":"Read","xAxis":0, "yAxis":1234},
{"chartName":"Idle Times", "disk":0, "OpCode":"Read","xAxis":100, "yAxis":1234},
{"chartName":"Idle Times", "disk":0, "OpCode":"Read","xAxis":200, "yAxis":1234},
{"chartName":"Idle Times", "disk":0, "OpCode":"Read","xAxis":300, "yAxis":1234},
{"chartName":"Idle Times", "disk":0, "OpCode":"Read","xAxis":400, "yAxis":1234},
{"chartName":"Idle Times", "disk":0, "OpCode":"Read","xAxis":500, "yAxis":1234},
{"chartName":"Idle Times", "disk":0, "OpCode":"Read","xAxis":600, "yAxis":1234}
Or is there a way with angular, dimple, or some other javascript to use my current json files?
Thanks in advance for your help!
I'm afraid if you want to use dimple you will need to reformat the data exactly as you describe. Either in your source, or at runtime depending on your requirements. You may also want to add some kind of identifier to disaggregate your bubbles by. A bubble is drawn for every distinct value of the dimension(s) specified in the first parameter in the addSeries method so in this case you will only get 1 bubble per OpCode value, which is probably not what you want. Alternatively you could pass the x and y values in:
chart.addSeries(["xAxis", "yAxis", "OpCode"], dimple.plot.bubble);
This will work as long as there are no duplicate pairs of x, y and OpCode values in your data.
I have a large JSON file with values similar to this example:
[
{"date": "12/27/2012", "temp": 23, "pH": ".4"},
{"date": "12/27/2012", "temp": 23, "pH": ".7"},
{"date": "12/27/2012", "temp": 23, "pH": ".2"},
{"date": "12/27/2012", "temp": 23}
]
There will be instances where the pH value will not exist. I need to keep track on the page of how many instances of "temp" and "pH" there are. I don't have a problem doing this with a JavaScript loop, but I'd also like to see if I can do it with crossfilter.
Basically, is there something similar to groupAll().reduceCount().value(), that counts just the individual instances of the strings?
Here's a sample of the code inside the d3.json() block:
var tempDim = xFilter.dimension(function(d) {return d.temp;});
var tempGroup = tempDim.group().reduceCount(function(d) {return d.temp;});
var tempCount = tempDim.groupAll().reduceCount().value();
console.log("tempCount :"+tempCount); // 4
var tempSum = tempDim.groupAll().reduceSum(function(d) {return d.temp;}).value();
console.log("tempSum :"+tempSum); // 92
//pH
var phDim = xFilter.dimension(function(d) {return d.pH;});
var phGroup = phDim.group().reduceCount(function(d) {return d.pH;});
var pHCount = phDim.groupAll().reduceCount().value();
console.log("pHCount :"+pHCount); // Equal to 4, looking for 3
Won't this get you the number of distinct temps and phs?
var tempDim = xFilter.dimension(function(d) {return d.temp;});
var tempGroup = tempDim.group();
console.log(tempGroup.top(Infinity).length);
var phDim = xFilter.dimension(function(d) {return d.pH;});
var phGroup = phDim.group()
console.log(phGroup.top(Infinity).length);
Or, if you need a count of the number of values whether or not they're distinct, you could use a custom reduce function, something like:
tempDim.groupAll().reduce(
function(p,v) { return (v.temp !== undefined) ? p+1 : 0; },
function(p,v) { return (v.temp !== undefined) ? p-1 : 0; },
function() { return 0; })\
Then you can generalize using the reusable reduce function pattern.