date sorting inside table using dc.js and d3 - d3.js

I'm trying to sort data based on date inside a table. However the dates are not sorted properly. Any guidance would be appreciated.
Pasted some parts of my code:
var dateFormat = d3.time.format("%Y-%m-%d");
var formatDate = d3.time.format('%d/%m/%Y');
jiraalertproject.forEach(function(d) {
d["date"] = dateFormat.parse(d["date"]);
var dataTable = dc.dataTable("#dc-table-graph");
dataTable
.width(1000)
.height(600)
.dimension(dateDim)
.group(function(d) { return "You will get the Ticket details with scroll option:"
})
.size(10)
.columns([
function(d) { return formatDate(d.date); },
function(d) { return d.ticket; },
function(d) { return d.component; },
function(d) { return d.summary; }
])
.sortBy(function(d){ return +formatDate(d.date); })
.order(d3.descending);

Related

aspx page on Sharepoint error: Function Expected only when rendering datatable

I have been building a reporting page in sharepoint with dc and crossfilter.
Right now on my page, I have 5 pie charts that render with no problem. However, when I tried to add a dc datatable to the page to show results of the charts as they are filtered, I get a javascript error on "resultsChart.render();"
Because no errors are given when I render each of the pie charts, I assume this to mean that something is off with the datatable object, or that I cannot call render() on that object (whatever it thinks it is).
Here are the relevant pieces of my code:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.5.0/d3.min.js" type="text/javascript">
<script src="https://cdnjs.cloudflare.com/ajax/libs/datatables/1.10.19/js/jquery.dataTables.min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crossfilter/1.3.12/crossfilter.min.js" type="text/javascript">
<script src="https://cdnjs.cloudflare.com/ajax/libs/dc/3.0.6/dc.min.js" type="text/javascript">
//connect to sharepoint site (change this URL to redirect)
var siteUrl = 'path';
var masterData = [];
//retrieve list data from above sharepoint site based on List Name
function retrieveListItems() {
var clientContext = new SP.ClientContext(siteUrl);
var oList = clientContext.get_web().get_lists().getByTitle('Upcoming');
var camlQuery = new SP.CamlQuery();
camlQuery.set_viewXml("<View><Query></Query></View>");
this.collListItem = oList.getItems(camlQuery);
clientContext.load(collListItem);
clientContext.executeQueryAsync(
Function.createDelegate(this, this.onQuerySucceeded),
Function.createDelegate(this, this.onQueryFailed)
);
}
//on query success
function onQuerySucceeded(sender, args) {
var listItemEnumerator = collListItem.getEnumerator();
while (listItemEnumerator.moveNext()) {
var data = {};
var oListItem = listItemEnumerator.get_current();
//set field keys on array objects
data.project = oListItem.get_item('Project_x0020_Type');
data.stoplight = oListItem.get_item('Stoplight');
data.appmgr = oListItem.get_item('AIT_x0020_App_x0020_Manager');
data.compdate = oListItem.get_item('Completion_x0020_Due_x0020_Date');
data.ait = oListItem.get_item('AIT_x0020_Number');
data.lob = oListItem.get_item('Business_x0020_Area');
data.sublob = oListItem.get_item('Business_x0020_Sub_x0020_Area');
masterData.push(data);
}//end while
var projectChart = dc.pieChart("#project", "project");
var stoplightChart = dc.pieChart("#stoplight", "stoplight");
var appmgrChart = dc.pieChart("#appmgr", "appmgr");
var lobChart = dc.pieChart("#lob", "lob");
var sublobChart = dc.pieChart("#sublob", "sublob");
var resultChart = dc.dataTable("#result_table", "result");
var ndx = crossfilter(masterData),
projectType = ndx.dimension(function(d) { return d.project;}),
stoplight = ndx.dimension(function(d) { return d.stoplight;}),
appMgr = ndx.dimension(function(d) { return d.appmgr;}),
compdate = ndx.dimension(function(d) { return d.compdate;}),
lob = ndx.dimension(function(d) { return d.lob;}),
sublob = ndx.dimension(function(d) { return d.sublob;})
projectTypeGroup = projectType.group();
stoplightGroup = stoplight.group(),
appMgrGroup = appMgr.group(),
compDateGroup = compdate.group(),
lobGroup = lob.group(),
sublobGroup = sublob.group();
projectChart
.dimension(projectType)
.group(projectTypeGroup)
.width(200)
.height(200)
.innerRadius(75)
stoplightChart
.dimension(stoplight)
.group(stoplightGroup)
.width(200)
.height(200)
.innerRadius(75)
appmgrChart
.dimension(appMgr)
.group(appMgrGroup)
.width(200)
.height(200)
.innerRadius(75)
lobChart
.dimension(lob)
.group(lobGroup)
.width(300)
.height(300)
.innerRadius(117)
sublobChart
.dimension(sublob)
.group(sublobGroup)
.width(200)
.height(200)
.innerRadius(75)
resultChart
.dimension(compdate)
.group(compDateGroup)
.columns([
function(d) { return d.ait},
function(d) { return d.project},
function(d) { return d.stoplight},
function(d) { return d.compdate}
])
.size(100);
projectChart.render();
stoplightChart.render();
appmgrChart.render();
lobChart.render();
sublobChart.render();
resultChart.render();
}
function onQueryFailed(sender, args) {
alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
}
SP.SOD.executeOrDelayUntilScriptLoaded(retrieveListItems, 'sp.js');
</script>
Any and all help is extremely appreciated!
I figured it out. According to the dc.dataTable documentation, you cannot use a crossfilter group as the .group attribute on a datatable. Instead, you must explicitly use a function there.
So it should be
resultChart
.dimension(compdate)
.group(function(d) { return d.compdate;})
.columns([
function(d) { return d.ait},
function(d) { return d.project},
function(d) { return d.stoplight},
function(d) { return d.compdate}
])
.size(100);
Instead of
resultChart
.dimension(compdate)
.group(compDateGroup)
.columns([
function(d) { return d.ait},
function(d) { return d.project},
function(d) { return d.stoplight},
function(d) { return d.compdate}
])
.size(100);

Dynamic filtering with D3

I'm quite new to D3 and coding in general. I'm trying to set up a bar chart which includes/excludes data depending on a checkbox. I have a set of product groups and countries which I want to toggle in/out of the total represented by the bar. The output should be one bar per product.
My full data set has many more products, product groups and countries so it is not viable to create a key-value pair for each potential combination of checkboxes. Instead I would like to create a function that re-evaluates the checkboxes and re-filters the data and updates the rollup when a checkbox is changed.
I'm not sure where this function should sit in my code or what it should look like... This is what I'm working with at the moment:
var data = data.filter(function(d) {
if (document.getElementById("nz_button").checked) {
return d.country == 'NZ'
}
if (document.getElementById("au_button").checked) {
return d.country == 'AU'
}
if (document.getElementById("us_button").checked) {
return d.country == 'US'
}
})
// to see how many distinct groups there are and sum volume
var products = d3.nest()
.key(function(d) {
return d.product
})
.rollup(function(leaves) {
var sum = 0;
leaves.forEach(function(d) {
sum += d.volume;
})
return sum
})
.entries(data);
Full code: http://plnkr.co/edit/qezdwMLt48RPc8KH17hS?p=preview
Maybe I should be working with selections and re-running the nest/rollup when required?
Any help appreciated. Thanks :)
You can move the full code which makes the graph in a new function like this:
function makeDataGraph(data) {//function to make the graph.
//
// FILTER
//
var data = data.filter(function(d) {
if (document.getElementById("au_button").checked) {
return d.country == 'AU'
}
if (document.getElementById("us_button").checked) {
return d.country == 'US'
}
if (document.getElementById("nz_button").checked) {
return d.country == 'NZ'
}
})
// to see how many distinct groups there are and sum volume
var products = d3.nest()
.key(function(d) {
return d.product
})
.rollup(function(leaves) {
var sum = 0;
leaves.forEach(function(d) {
sum += d.volume;
})
return sum
})
.entries(data);
// sorting on descending total
console.log(products);
products.sort(function(a, b) {
return b.values - a.values
})
var max = d3.max(products, function(d) {
return d.values;
});
var xscale = d3.scale.linear()
.domain([0, max])
.range([0, 600])
var svg = d3.select("svg");
//
// Still needs to be cleaned up \/ \/
//
var rects = svg.selectAll("rect.product")
.data(products)
rects.exit().remove();
rects.enter().append("rect").classed("product", true)
rects.attr({
x: 200,
y: function(d, i) {
return 100 + i * 50
},
width: function(d, i) {
return xscale(d.values)
},
height: 50
}).on("click", function(d, i) {
console.log(i, d);
})
var labels = svg.selectAll("text.label")
.data(products)
labels.exit().remove();
labels.enter().append("text").classed("label", true)
labels.attr({
x: 195,
y: function(d, i) {
return 128 + i * 50
},
"text-anchor": "end",
"alignment-baseline": "middle"
}).text(function(d) {
return d.key || "N/A"
})
var volume = svg.selectAll("text.volume")
.data(products);
volume.exit().remove();
volume.enter().append("text").classed("volume", true)
volume.attr({
x: function(d, i) {
return 205 + xscale(d.values)
},
y: function(d, i) {
return 128 + i * 50
},
"text-anchor": "start",
"alignment-baseline": "middle"
}).text(function(d) {
return d.values || "N/A"
})
}
Remember to do rects.exit().remove(); so that when the data is changed on click of the checkbox, rectangles related to old data is removed.
Now you can call this function from the click event and also afterloading the tsv like this:
d3.tsv("data.tsv", function(err, udata) {
var udata = udata.map(process);
console.log("udata", udata);
var data = udata // making new var to preserve unfiltered data
makeDataGraph(data);//call the function to make graph
function handleClick() { // event handler...
makeDataGraph(data)
}
//add listener to all check boxes.
d3.selectAll(".filter_button").on("click", handleClick);
});
working code here

How to override a function in NVD3 library?

My goal is to override the function used by NVD3 for the content of a tooltip.
I found the function that provides this behaviour: contentGenerator.
Below here the code:
var contentGenerator = function(d) {
if (content != null) {
return content;
}
if (d == null) {
return '';
}
var table = d3.select(document.createElement("table"));
var theadEnter = table.selectAll("thead")
.data([d])
.enter().append("thead");
theadEnter.append("tr")
.append("td")
.attr("colspan",3)
.append("strong")
.classed("x-value",true)
.html(headerFormatter(d.value));
var tbodyEnter = table.selectAll("tbody")
.data([d])
.enter().append("tbody");
var trowEnter = tbodyEnter.selectAll("tr")
.data(function(p) { return p.series})
.enter()
.append("tr")
.classed("highlight", function(p) { return p.highlight});
trowEnter.append("td")
.classed("legend-color-guide",true)
.append("div")
.style("background-color", function(p) { return p.color});
trowEnter.append("td")
.classed("key",true)
.html(function(p) {return p.key});
trowEnter.append("td")
.classed("value",true)
.html(function(p,i) { return valueFormatter(p.value,i) });
trowEnter.selectAll("td").each(function(p) {
if (p.highlight) {
var opacityScale = d3.scale.linear().domain([0,1]).range(["#fff",p.color]);
var opacity = 0.6;
d3.select(this)
.style("border-bottom-color", opacityScale(opacity))
.style("border-top-color", opacityScale(opacity))
;
}
});
var html = table.node().outerHTML;
if (d.footer == undefined)
html += "<div class='footer'>" + d.footer + "</div>";
return html;
};
Now, I'd like to put inside my chart section the function to override this method.
var chart = nv.models.lineChart()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.useInteractiveGuideline(false)
.tooltipContent(function (key, x, y, e, graph) {
return contentGenerator
});
How Can I achieve this goal?
I wanted to customise:
1) In the header add a simple text
2) Add a footer

dc.js dataTable - how to use a pre-defined group

I would like to load a dc dataTable with dimensional data that has been grouped and filtered. Is this possible? I am assuming it is but the examples I have seen so far have simple groups coded at the time of table construction and I can't get it to work any other way!
var facts = crossfilter(data);
var testTable = dc.dataTable('#testtable');
var providerDim = facts.dimension(function (d) {
if (d.report_by_3 !== null) {
return d.report_by_3;
}
});
var MH003Group = providerDim.group().reduceSum(function (d) {
if (d.indicator_code === 'MH003') {
return (d.observed / d.expected);
} else {
return null;
}
});
var MH003Group2 = {
all: function () {
return MH003Group.all().filter(function (d) {
return d.value !== null;
});
}
};
dataTable.width(960)
.height(8000)
.dimension(providerDim)
// .group(MH003Group2)
.group(function (d) {
return 'data';
})
.size(10)
.columns([function (d) {return d.indicator_code;},
function (d) {return d.report_by_1;},
function (d) {return d.report_by_3;},
function (d) {return (d.observed / d.expected);}
]);
.sortBy(function (d) {return d.report_by_1;})
.order(d3.ascending);
In the code above I would like to display filtered rows in the dimension using the pre-defined grouping rather than all the rows of data in the dimension which is what is currently happening.

Find the maximum value in an object where the keys are arrays?

Using D3, how can I find the maximum value in an object where all the values are arrays of floats?
var data = {
"point1": {
"level1": [...],
"level2": [...]
},
...
};
I read Mike's answer on finding the max of a nested array, but I'm not sure what to do here since this is an object of arrays.
Currently I'm doing this, which is awful:
var c1Max = d3.max(data.point1.level1, function(d) { return d.value; });
var c2Max = d3.max(data.point1.level2, function(d) { return d.value; });
var c3Max = d3.max(data.point2.level1, function(d) { return d.value; });
var c4Max = d3.max(data.point2.level2, function(d) { return d.value; });
var c5Max = d3.max(data.point3.level1, function(d) { return d.value; });
var c6Max = d3.max(data.point3.level2, function(d) { return d.value; });
var max = d3.max([c1Max, c2Max, c3Max, c4Max, c5Max, c6Max]);
Maybe I should iterate over all the keys? Or maybe I could flatten the data object somehow? Or maybe there's a nicer way.
You can use d3.entries to convert the object into something you can iterate over:
var max = d3.max(d3.entries(data), function(d) {
return d3.max(d3.entries(d.value), function(e) {
return d3.max(e.value);
});
});
In this particular case (keys not required) you can of course also use d3.values, which makes the above code a bit shorter.
To make it work with the structure in your jsfiddle, you need this code:
var max1 = d3.max(d3.entries(data), function(d) {
return d3.max(d3.entries(d.value), function(e) {
return d3.max(e.value, function(f) { return f.value; });
});
});

Resources