Selecting rows in dc.datatables.js - dc.js

I am trying to allow selection of a row in a datatable:
nasdaqTable /* dc.dataTable('.dc-data-table', 'chartGroup') */
.dimension(dateDimension)
// Data table does not use crossfilter group but rather a closure
// as a grouping function
.group(function (d) {
var format = d3.format('02d');
return d.dd.getFullYear() + '/' + format((d.dd.getMonth() + 1));
})
// (_optional_) max number of records to be shown, `default = 25`
.size(10)
// There are several ways to specify the columns; see the data-table documentation.
// This code demonstrates generating the column header automatically based on the columns.
.columns([
// Use the `d.date` field; capitalized automatically; specify sorting order
{
label: 'date',
type: 'date',
format: function(d) {
return d.date;
}
},
// Use `d.open`, `d.close`; default sorting order is numeric
'open',
'close',
{
// Specify a custom format for column 'Change' by using a label with a function.
label: 'Change',
format: function (d) {
return numberFormat(d.close - d.open);
}
},
// Use `d.volume`
'volume'
])
// (_optional_) sort using the given field, `default = function(d){return d;}`
.sortBy(function (d) {
return d.dd;
})
// (_optional_) sort order, `default = d3.ascending`
.order(d3.ascending)
// (_optional_) custom renderlet to post-process chart using [D3](http://d3js.org)
.on('renderlet', function (table) {
table.selectAll('.dc-table-group').classed('info', true);
});
This is the standard example drawn from https://github.com/dc-js/dc.datatables.js which integrates dc.js with datatables.js
However, I looked around for examples of implementation of rows being clickable and couldn't find any.
The goal I am trying to achieve is allow users to click the rows they would be interested in after playing around with the crossfilter dimensions and submitting them to a backend server.

Related

How to decide dimensions and groups in dc.js?

I am new to dc.js and facing issues in deciding dimensions and groups. I have data like this
this.data = [
{Type:'Type1', Day:1, Count: 20},
{Type:'Type2', Day:1, Count: 10},
{Type:'Type1', Day:2, Count: 30},
{Type:'Type2', Day:2, Count: 10}
]
I have to show a composite chart of two linecharts one for type Type1 and other for Type2. My x-axis will be Day. So one of my dimensions will be Day
var ndx = crossfilter(this.data);
var dayDim = ndx.dimension(function(d) { return d.Day; })
How the grouping will be done? If I do it on Count, the total count of a particular Day shows up which I don't want.
Your question isn't entirely clear, but it sounds like you want to group by both Type and Day
One way to do it is to use composite keys:
var typeDayDimension = ndx.dimension(function(d) {return [d.Type, d.Day]; }),
typeDayGroup = typeDayDimension.group().reduceSum(function(d) { return d.Count; });
Then you could use the series chart to generate two line charts inside a composite chart.
var chart = dc.seriesChart("#test");
chart
.width(768)
.height(480)
.chart(function(c) { return dc.lineChart(c); })
// ...
.dimension(typeDayDimension)
.group(typeDayGroup)
.seriesAccessor(function(d) {return d.key[0];})
.keyAccessor(function(d) {return +d.key[1];}) // convert to number
// ...
See the series chart example for more details.
Although what Gordon suggested is working perfectly fine, if you want to achieve the same result using composite chart then you can use group.reduce(add, remove, initial) method.
function reduceAdd(p, v) {
if (v.Type === "Type1") {
p.docCount += v.Count;
}
return p;
}
function reduceRemove(p, v) {
if (v.Type === "Type1") {
p.docCount -= v.Count;
}
return p;
}
function reduceInitial() {
return { docCount: 0 };
}
Here's an example: http://jsfiddle.net/curtisp/7frw79q6
Quoting Gordon:
Series chart is just a composite chart with the automatic splitting of the data and generation of the child charts.

How can I group data to be filtered without losing crossfilter functionality using dc.js?

I'm trying to learn d3 via dc.js and I'm quite stuck trying to figure out how to group the line chart with only the w15sec, w30sec,...,w1hr, names and values. When the filter is applied, I'd like it show only the max value for workouts that are within the filter parameters. Here is my jsfiddle.
I've got flat cycling data that looks like this:
var data = [{"TimeStamp":"2017-09-06T12:32:04.183","Duration":3459.518,"Distance":10261,"ActivityID":175508086,"AVGPower":305.5419317525,"w15sec":499.2666666667,"w30sec":479.3333333333,"w1min":470.2666666667,"w2min":441.9416666667,"w5min":417.5166666667,"w10min":410.5616666667,"w20min":342.3141666667,"w40min":306.2033333333,"w1hr":0.0},{"TimeStamp":"2017-09-08T12:07:27.033","Duration":2106.755,"Distance":3152,"ActivityID":175647595,"AVGPower":168.8485158649,"w15sec":375.8666666667,"w30sec":327.7333333333,"w1min":271.1833333333,"w2min":261.6083333333,"w5min":0.0,"w10min":0.0,"w20min":0.0,"w40min":0.0,"w1hr":0.0},{"TimeStamp":"2017-09-07T17:11:51.577","Duration":1792.025,"Distance":4245,"ActivityID":175670859,"AVGPower":244.2495803022,"w15sec":365.2,"w30sec":342.1333333333,"w1min":328.0333333333,"w2min":290.975,"w5min":276.0566666667,"w10min":268.8316666667,"w20min":246.8858333333,"w40min":0.0,"w1hr":0.0},{"TimeStamp":"2017-09-09T10:31:21.107","Duration":15927.885,"Distance":39408,"ActivityID":175971583,"AVGPower":255.0849193803,"w15sec":405.0666666667,"w30sec":389.8666666667,"w1min":367.6666666667,"w2min":350.3916666667,"w5min":318.93,"w10min":300.345,"w20min":281.9883333333,"w40min":259.4733333333,"w1hr":0.0}];
The goal is to have the chart on the right populated with the names of the categories (w15sec, w30sec,...,w1hr) as the dimensions and the values would be the max found in the activities for each category. It looks like a line chart going from high values (w15sec) to lower values (w1hr).
It should look something like this image.
Thanks so much for your help!
I think the best way to approach this is to use Reductio's multi-value group and maximum reducer to calculate the maximum for each window on your power curve in a single bucket, then create a fake group to make it appear that each of these windows is its own group "bucket".
You start by defining your dimension, some helper maps (you need to get onto a linear scale, so you need to convert your windows to numbers of seconds), and a helper dimension that you can use for filtering in the event that you want to do this:
var rmmDim = facts.dimension(function(d) {
return true;
});
var timeMap = {
"w15sec": 15,
"w30sec": 30,
"w1min": 60,
"w2min": 120,
"w5min": 300,
"w10min": 600,
"w20min": 1200,
"w40min": 2400,
"w1hr": 3600
}
var timeArray = ["w15sec","w30sec","w1min","w2min","w5min","w10min","w20min","w40min","w1hr"].map((d) => timeMap[d])
var rmmFilterDim = facts.dimension(function(d) {
return timeArray;
}, true)
You then create your group using Reductio, calculating the max for each window:
var rmmGroup = rmmDim.group();
var reducer = reductio()
reducer.value('w15sec')
.max((d) => { return d.w15sec; })
reducer.value('w30sec')
.max((d) => { return d.w30sec; })
reducer.value('w1min')
.max((d) => { return d.w1min; })
reducer.value('w2min')
.max((d) => { return d.w2min; })
reducer.value('w5min')
.max((d) => { return d.w5min; })
reducer.value('w10min')
.max((d) => { return d.w10min; })
reducer.value('w20min')
.max((d) => { return d.w20min; })
reducer.value('w40min')
.max((d) => { return d.w40min; })
reducer.value('w1hr')
.max((d) => { return d.w1hr; })
reducer(rmmGroup)
And finally you create your fake group. You need both top and all because the line chart requires them for some reason:
var fakeGroup = {
all: function() {
return ["w15sec","w30sec","w1min","w2min","w5min","w10min","w20min","w40min","w1hr"].map((d) => {
return {
key: timeMap[d],
value: rmmGroup.top(Infinity)[0].value[d]
}
})
},
top: function() {
return ["w15sec","w30sec","w1min","w2min","w5min","w10min","w20min","w40min","w1hr"].map((d) => {
return {
key: timeMap[d],
value: rmmGroup.top(Infinity)[0].value[d]
}
})
}
}
Then you can use this fake group in your power distribution chart:
PwrDistChart
.width(960)
.height(150)
.margins({
top: 10,
right: 10,
bottom: 20,
left: 40
})
.dimension(rmmFilterDim)
.group(fakeGroup)
.valueAccessor((d) => {
return d.value.max
})
.transitionDuration(500)
.x(d3.scale.linear().domain([0,3600]))
.elasticY(true)
Here is an updated version of the fiddle with all of this working: http://jsfiddle.net/fb3wsyck/5/

In d3 js how to append the additional text as tool tip on the circles

I am working on a d3 sample http://bost.ocks.org/mike/nations/:
I am trying to add title for circles with with the name as well as checkin details.
following is the modified code for the display year function (rest of the code almost no change,,,):
// Updates the display to show the specified year.
function displayYear(year) {
dot.data(interpolateData(year), key)
.call(position)
.sort(order);
dot.append("title").text(function(d) { return d.name});
dot.text(function(d) { return d.name + "~"+ d.checkins + d.Checkintimes; });
label.text(Math.round(year));
}
// Interpolates the dataset for the given (fractional) year.
function interpolateData(year) {
return nations.map(function(d) {
return {
name: d.name,
region: d.region,
checkins: interpolateValues(d.checkins, year),
teamsize: interpolateValues(d.teamsize, year),
Checkintimes: interpolateValues(d.Checkintimes, year)
};
});
}
However the same is not appearing as title in the circles. I just want to append the checkin detail with the circle.
My json file contains the following:
[
{
"name":"Search&Navigator",
"region":"IPScience",
"checkins":[[2000,100],[2001,200],[2002,300],[2003,275],[2004,222],[2005,280],[2006,281],[2007,400],[2008,55],[2009,300]],
"teamsize":[[2000,10],[2001,7],[2002,7],[2003,12],[2004,5],[2005,3],[2006,10],[2007,12],[2008,12],[2009,10]],
"Checkintimes":[[2000,40],[2001,50],[2002,60],[2003,50],[2004,40],[2005,30],[2006,30],[2007,35],[2008,30],[2009,30]]
}
]
Your variable dot doesn't contain a reference to the title element. Simply change the function that appends it to do what you want:
dot.append("title")
.text(function(d) { return d.name + "~"+ d.checkins + d.Checkintimes; });

Adding Filter in dc.js from another dataset

I'm pushing two datasets into the same page.
They're both coming from separate mongodb tables, but the records are linked by a primary key 'plankey'.
I want to add a filter from this graph to the one in the second dataset.
Main chart function:
function loadProjectData(productName) {
// $('#progress_dialog').show();
buildDataLoaded = false;
$.get('/getbuildresults.json?product=' + productName, function (data) {
stats = data;
if (stats != null && stats.length > 0) {
// Convert dates to real dates
stats.forEach(function (d) {
d.builddate = new Date(d.builddate);
});
// feed it through crossfilter
ndx = crossfilter(stats);
overall = ndx.dimension(function (d) {
return d3.time.month(d.builddate);
});
overallGroup = overall.group().reduce(buildReduceAdd, buildReduceRemove, buildReduceInitial);
//Test Types Graph Data Sorter
testTypesDimension = ndx.dimension(function (d) {
return d3.time.week(d.builddate)
})
}
overallChart = dc.compositeChart("#overall-timeline-chart")
.width(chartWidth) // (optional) define chart width, :default = 200
.height(250) // (optional) define chart height, :default = 200
.transitionDuration(500) // (optional) define chart transition duration, :default = 500
.margins({
top: 10,
right: 50,
bottom: 30,
left: 40
})
.dimension(failedTestDimension)
.group(failedTestDimensionGroup)
.elasticY(true)
.mouseZoomable(false)
.elasticX(false)
.renderHorizontalGridLines(true)
.x(d3.time.scale().domain(timelineDomain))
.round(d3.time.month.round)
.xUnits(d3.time.months)
.title(function (d) {
return "Value: " + d.value;
})
.renderTitle(true)
.brushOn(true);
// Loop through available plans and create chart for each and then compose
var charts = [];
var plans = buildGroup.all();
plans.forEach(function (plan) {
charts.push(dc.lineChart(overallChart).dimension(failedTestDimension).group(failedTestDimensionGroup)
.valueAccessor(function (d) {
return d.value.result[plan.key] ? d.value.result[plan.key].failed : null;
}));
});
overallChart.compose(charts);
Second graph function (this is where I want to add the filter from the above graph):
function loadTestResultsData() {
// $('#progress_dialog').show();
testDataLoaded = false;
$.get('/gettestresults.json', function(data) {
stats = data;
if (stats != null && stats.length > 0) {
// Convert dates to real dates
stats.forEach(function (d) {
d.rundate = new Date(d.rundate);
});
// feed it through crossfilter
support_ndx = crossfilter(stats);
//Support Cases Chart
// Dimension and Group for monthly support cases
supportCasesPerMonth = support_ndx.dimension(function(d){ return d.methodName });
supportCasesPerMonthGroup = supportCasesPerMonth.group();
buildTypesChart = dc.rowChart("#failed-tests-chart")
.width(750) // (optional) define chart width, :default = 200
.height(300) // (optional) define chart height, :default = 200
.group(supportCasesPerMonthGroup) // set group
.dimension(supportCasesPerMonth) // set dimension
// (optional) define margins
.margins({
top: 20,
left: 10,
right: 10,
bottom: 20
})
// (optional) define color array for slices
.colors(['#3182bd', '#6baed6', '#9ecae1', '#c6dbef', '#dadaeb'])
// (optional) set gap between rows, default is 5
.gap(7);
}
testDataLoaded = true;
dataLoaded();
});
};
You have two basic approaches. The first to be prefered.
1) Join the data first. I would suggest using something like queue
queue()
.defer(d3.json, '/getbuildresults.json?product=' + productName)
.defer(d3.json, '/gettestresults.json')
.await(ready);
function ready(error, products, stats) {
var productMap = {};
products.forEach(function (d) {
d.builddate = new Date(d.builddate);
productMap[d.plankey] = d;
});
stats.forEach(function (d) {
d.rundate = new Date(d.rundate);
$.extend(d, productMap[d.plankey]);
});
ndx = crossfilter(stats);
// build other dimensions/groups
// build charts
});
2) Your other option is to link the charts by using a trigger to filter on the plankey. So on both data sets, create a crossfilter linked dimension for plankey. Then, on the filter trigger from the second chart, see what plankeys have been set with something like
var keys = C2PlanKeysDim.all()
.filter(function(d){return d.value>0;})
.map(function(d){return d.key;});`
Then on chart 1, filter by the key on C1PlanKeysDim or whatever you call it, and trigger a chart redraw to take into account the filter.

How to draw line charts with complex data structures in d3

I have the following complex data structure:
[
Object {id: 15, targets: Array[2]},
Object {id: 26, targets: Array[2]},
Object {id: 39, targets: Array[2]}
]
'targets' is an array of objects. Each of them has this shape:
Object {idTarget: "target1", events: Array[315]}
Object {idTarget: "target2", events: Array[310]}
'events' is an array with the real values to plot.
So, each element has this shape:
Object {timestamp: 1373241642, value: 1801.335}
Now, with this structured dataset, I would like to create an svg group 'g' for each external object (I am referring to 15, 26 and 39) and inside each group I want to create two lines, one for each target, using the values in 'events'.
Having a flat dataset it's easy to proceed in the drawing following the pattern: select + data + enter + append, but I am having trouble with this complex dataset.
For example I don't even know how to assign a key function to start.
svg.selectAll('.element')
.data(data, function(d){ return d.id + ?})
I would like to have this kind of keys '15target1', '15target2', '26target1', '26target2' and so on.
Do you recommend to simplify the dataset giving up the possibility of having neat groups or there is a workaround here that lets me easily draw what I want?
Thanks.
You want nested selections for this. Your code would look something like this.
var gs = d3.selectAll("g").data(data, function(d) { return d.id; });
gs.enter().append("g");
var line = d3.svg.line().x(function(d) { return xscale(d.timestamp); })
.y(function(d) { return yscale(d.value); });
gs.selectAll("path")
.data(function(d) { return d.targets; }, function(d) { return d.idTarget; })
.enter().append("path")
.attr("d", function(d) { return line(d.events); });

Resources