legend labels and colors change to black dc.js d3.js - d3.js

I have a Composite Chart with two lines built with the following code:
function fakeGrouper(source_group) {
return {
all:function () {
var cumulate = 100;
var result = [];
return [{key: 0, value: cumulate}]
.concat(source_group.all().map(function(d) {
cumulate -= d.value;
return {key:d.key, value:cumulate};
}));
}
};
}
var recDim = cf1.dimension(dc.pluck('recidiefNa'));
var recGroup = recDim.group().reduceCount();
var RecGroup = fakeGrouper(recGroup);
var resDim = cf1.dimension(dc.pluck('residuNa'));
var resGroup = resDim.group().reduceCount();
var ResGroup = fakeGrouper(resGroup);
var scChart = dc.compositeChart("#scStepChart");
scChart
.width(600)
.height(400)
.x(d3.scale.linear().domain([0,52]))
.y(d3.scale.linear().domain([0,100]))
.clipPadding(10)
.brushOn(false)
.xAxisLabel("tijd in weken")
.yAxisLabel("percentage vrij van residu/recidief")
.legend(dc.legend().x(70).y(250).itemHeight(13).gap(5))
.compose([
dc.lineChart(scChart)
.dimension(recDim)
.group(RecGroup)
.renderDataPoints(true)
.interpolate("step-after")
,
dc.lineChart(scChart)
.dimension(resDim)
.group(ResGroup)
.colors(['#ff9933'])
.renderDataPoints(true)
.interpolate("step-after")
])
.xAxis().ticks(4);
scChart.render();
Which renders perfectly except for the fact that the legend has 0 as label for both chart. Check out my fiddle here: http://jsfiddle.net/8v9faput/
Now i read somewhere that you can solve this by changing the group from:
.group(GROUP)
to
.group(GROUP, LABELNAME)
However once i did this the second chart went black in the legend, and the lines are gone in the chart. as shown in this second fiddle. http://jsfiddle.net/ojdg3ny1/
Any ideas why and how to fix this? I think it might have something to do with me using a fake group.

You're passing the .colors() an array. You should pass it single value like so :
.colors('#ff9933')
So you chart function looks like this :
.legend(dc.legend().x(70).y(250).itemHeight(13).gap(5))
.compose([
dc.lineChart(scChart)
.dimension(recDim)
.group(RecGroup, "Recidief")
.colors('red')
.renderDataPoints(true)
.interpolate("step-after")
,
dc.lineChart(scChart)
.dimension(resDim)
.group(ResGroup, "Residu")
.colors('#ff9933')
.renderDataPoints(true)
.interpolate("step-after")
])
.xAxis().ticks(10)
scChart.render();
Updated fiddle : http://jsfiddle.net/reko91/8v9faput/1/

Related

dc.js add up only some values in numberDisplay

For the numberDisplay I only see examples in which all values are summed. But I would like to apply a filter so that I only see the values for a certain type.
In the example JSFIDDLE there is now a numberDisplay where all values are summed.
Is it also possible to only summarize the values of which for example the type is "cash"?
So in this case I would like to see the number 6 if nothing is selected.
Thank you in advance for your help.
HTML
<div id="demo1">
<h2>Markers with clustering, popups and single selection</h2>
<i>Renewable energy plants in Bulgaria in 2012</i>
<div class="lineChart"></div>
<div class="pie"></div>
<h5>I want here onlny the total number for type "cash" (nothing selected 6)</h5>
<div class="number_cash"></div>
</div>
<pre id="data">date type value
1/1/2020 cash 1
1/1/2020 tab 1
1/1/2020 visa 1
1/2/2020 cash 2
1/2/2020 tab 2
1/2/2020 visa 2
1/3/2020 cash 3
1/3/2020 tab 3
1/3/2020 visa 3
</pre>
JavaScript
function drawMarkerSelect(data) {
var xf = crossfilter(data);
const dateFormatSpecifier = '%m/%d/%Y';
const dateFormat = d3.timeFormat(dateFormatSpecifier);
const dateFormatParser = d3.timeParse(dateFormatSpecifier);
data.forEach(function(d) {
var tempDate = new Date(d.date);
d.date = tempDate;
})
var groupname = "marker-select";
var facilities = xf.dimension(function(d) {
return d.date;
});
var facilitiesGroup = facilities.group().reduceSum(d => +d.value);
var typeDimension = xf.dimension(function(d) {
return d.type;
});
var typeGroup = typeDimension.group().reduceSum(d => +d.value);
var dateDimension = xf.dimension(function(d) {
return d.date;
});
var dateGroup = dateDimension.group().reduceSum(d => +d.value);
var minDate = dateDimension.bottom(1)[0].date;
var maxDate = dateDimension.top(1)[0].date;
//numberDisplay cash
var all = xf.groupAll();
var ndGroup = all.reduceSum(function(d) {
return d.value; //6
});
//numbers
var number_cash = dc.numberDisplay("#demo1 .number_cash", groupname)
.group(ndGroup)
.valueAccessor(function(d) {
return d;
});
var pie = dc.pieChart("#demo1 .pie", groupname)
.dimension(typeDimension)
.group(typeGroup)
.width(200)
.height(200)
.renderLabel(true)
.renderTitle(true)
.ordering(function(p) {
return -p.value;
});
var lineChart = dc.lineChart("#demo1 .lineChart", groupname)
.width(450)
.height(200)
.margins({
top: 10,
bottom: 30,
right: 10,
left: 70
})
.dimension(dateDimension)
.group(dateGroup, "total spend")
.yAxisLabel("Transaction spend")
.renderHorizontalGridLines(true)
.renderArea(true)
.legend(dc.legend().x(1200).y(5).itemHeight(12).gap(5))
.x(d3.scaleTime().domain([minDate, maxDate]));
lineChart.yAxis().ticks(5);
lineChart.xAxis().ticks(4);
dc.renderAll(groupname);
}
const data = d3.tsvParse(d3.select('pre#data').text());
drawMarkerSelect(data);
CSS
pre#data {
display: none;
}
.marker-cluster-indiv {
background-color: #9ecae1;
}
.marker-cluster-indiv div {
background-color: #6baed6;
}
Crossfilter is a framework for mapping and reducing in JavaScript. The dimension and group key functions determine the mapping to group bins, and the group reduce functions determine how to add or remove a row of data to/from a bin.
When you use a groupAll, there is just one bin.
With that in mind, you can count cash rows at face value, and count other rows as zero, by writing your reduceSum function as
var ndGroup = all.reduceSum(function(d) {
return d.type === 'cash' ? d.value : 0;
});
Fork of your fiddle.

Time Series chart to filter multi line chart(rendering multi lines for multiple data items)

What is the right way to filter multi line chart using a time series chart as filter?
I need a time series chart for my focus chart that it is shown the image below. Whenever I brush on time series chart my focus chart needs to be filtered with respect to time series chart.
The time series chart needs to contain only X axis and time as its dimension and it should be interactive with focus chart with respect to time.
var totalNumber = null;
// ------ main chart function -------
function makeCompetitiveGraphs(error, keywords_data) {
errorHandle(error);
cleanedData = getCompositeChartData(keywords_data);
console.log("===", cleanedData);
minDate = moment.min(cleanedData.timeStamp);
maxDate = moment.max(cleanedData.timeStamp);
margins = { top: 27, right: 27, bottom: 36, left: 54 };
// create composite chart.
var composite = dc.compositeChart('#competitiveChart');
// create cross filter
var cf = crossfilter(cleanedData.keywordData);
// create dimensions.
var keywordDateDimension = cf.dimension(function (dp) { return dp.date;
});
var Group = keywordDateDimension.group();
// compose for key words
composeCharts = composeKeywords(dc, composite, keywordDateDimension);
// create chart.
composite
.width(width())
.height(height())
.transitionDuration(1000)
.x(d3.time.scale().domain([minDate, maxDate]))
.ordering(function (d) { return d.value; })
.elasticY(true)
.elasticX(true)
.margins(margins)
.legend(
dc.legend()
.x(1100)
.y(10)
.itemHeight(16)
.gap(8)
.horizontal(false)
)
.renderHorizontalGridLines(true)
.brushOn(false);
// compose the chart array.
composite.compose(composeCharts);
// render the chart
composite.render();
function getCompositeChartData(keywords) {
let momentTimeStamps = [];
let totalKeywordPerDay = [];
let allKeywords = [];
// clean data for d3js chart's
keywords.forEach((kob) => {
kob.sd.forEach((ob) => {
allKeywords.push({
name: kob.kn,
total: ob.value.total__,
date: new Date(moment(ob._id.mention_created_date_, "MMM-DD-YYYY-hh")._d),
});
momentTimeStamps.push(moment(moment(ob._id.mention_created_date_, "MMM-DD-
YYYY-hh")._d));
totalKeywordPerDay.push(ob.value.total__);
});
});
console.log("--------", allKeywords)
// apply date filter.
allKeywords = limiteDataToDateFilter(allKeywords);
return { "keywordData": allKeywords, "timeStamp": momentTimeStamps,
"totalKeywordPerDay": totalKeywordPerDay };
}
function limiteDataToDateFilter(allKeywords) {
cleanedDateWithDates = [];
allKeywords.forEach(element => {
if (moment(element.date).isAfter(moment().date(1).month(6)) &&
moment(element.date).isBefore(moment().date(30).month(8))) {
cleanedDateWithDates.push(element);
}
});
return cleanedDateWithDates;
}
function getReduce(keyword, keywordDateDimension) {
return keywordDateDimension.group().reduceSum(function (dp) {
return dp.name === keyword ? dp.total : 0;
});
}
function composeKeywords(dc, composite, keywordDateDimension) {
composeChartsData = []
keywordsParams.forEach(keyword => {
keyword.chart = dc.lineChart(composite)
.dimension(keywordDateDimension)
.colors(keyword.color)
.group(getReduce(keyword.word, keywordDateDimension), keyword.word)
.interpolate('basis')
composeChartsData.push(keyword.chart);
});
return composeChartsData;
}

Pie chart not updating properly when using brush in series chart

I have 1 pie chart and 1 series line chart with brush. Currently when I brushing the pie chart updates, but shows the same result regardless of the selected range. How to make brush work correctly?
const pieChart = dc.pieChart('#piechart');
const chart = dc.seriesChart('#chart');
const groupParameter = 'markdown';
const data = d3.csvParse(d3.select('pre#data').text());
data.forEach(d => {
d.week = +d.week;
d.markdown = +d.markdown;
});
const ndx = crossfilter(data);
const pieDimension = ndx.dimension(d => d.itemCategory);
const dimension = ndx.dimension(d => [d.itemCategory, d.week]);
const pieGroup = pieDimension.group().reduceSum(d => d[groupParameter]);
const group = dimension.group().reduceSum(d => d[groupParameter]);
pieChart
.width(768)
.height(480)
.dimension(pieDimension)
.group(pieGroup)
.legend(dc.legend());
chart
.width(768)
.height(480)
.chart(c => dc.lineChart(c).curve(d3.curveCardinal))
.x(d3.scaleLinear().domain([27, 38]))
.brushOn(true)
.yAxisLabel(groupParameter)
.yAxisPadding('5%')
.xAxisLabel('Week')
.elasticY(true)
.dimension(dimension)
.group(group)
.mouseZoomable(true)
.seriesAccessor(d => d.key[0])
.keyAccessor(d => d.key[1])
.valueAccessor(d => d.value)
.legend(
dc
.legend()
.itemHeight(13)
.gap(5)
.horizontal(1)
.legendWidth(140)
.itemWidth(70)
);
chart.margins().left += 100;
dc.renderAll();
https://jsfiddle.net/qwertypomy/L37d01e5/#&togetherjs=HF15j0M5pH
It doesn't look like brushing was ever properly implemented for series charts.
The issue is that the dimension key for series chart is a 2-dimensional array, but a normal RangedFilter is applied, which doesn't understand these keys.
You can manually apply a filter handler which looks at the right part of the key:
chart.filterHandler(function(dimensions, filters) {
if (filters.length === 0) {
dimension.filter(null);
} else {
var filter = dc.filters.RangedFilter(filters[0][0], filters[0][1]);
dimension.filterFunction(function(k) {
return filter.isFiltered(k[1]);
});
}
return filters;
});
Actually, I'm not sure if there is an elegant way to fix this. I've started an issue to track it: https://github.com/dc-js/dc.js/issues/1471
EDIT: This doesn't work unless it's applied to both the series chart and all of its children. Horribly inefficient, but like so:
function filterHandler(dimensions, filters) {
if (filters.length === 0) {
dimension.filter(null);
} else {
var filter = dc.filters.RangedFilter(filters[0][0], filters[0][1]);
dimension.filterFunction(function(k) {
return filter.isFiltered(k[1]);
});
console.log('all',all.value());
}
return filters;
}
chart
.chart(c => dc.lineChart(c).curve(d3.curveCardinal).filterHandler(filterHandler))
.filterHandler(filterHandler);
New version of fiddle, with dataCount to show it's working now: https://jsfiddle.net/gordonwoodhull/qga6z1yu/39/
We really need a generalized version of https://github.com/dc-js/dc.js/pull/682 to apply filters only once, in a cooperative way between multiple charts which share the same dimension.
Right now, this will apply the filter four times every time the brush changes!

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.

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.

Resources