How to make columns wide (not pencil-thin) on dc BarChart with time x-axis - dc.js

I have a barchart set-up as follows:
function makeGraphs(recordsJson, factorText) {
// Clean data
var records = jQuery.parseJSON(recordsJson);
let parseDate = d3.timeParse("%Y-%m-%d");
records.forEach(function(d) {
d.date = parseDate(d.date);
d.factor = d.factor == true ? 1 : 0;
});
// Create a Crossfilter instance
var ndx = crossfilter(records);
// Define Dimensions
var dateDim = ndx.dimension(d => d.date);
// Group Data
var dateGroup = dateDim.group();
var factorGroup = dateDim.group().reduceSum(dc.pluck('factor'));
var all = ndx.groupAll();
// Define values (to be used in charts)
var minDate = dateDim.bottom(1)[0]["date"];
var maxDate = dateDim.top(1)[0]["date"];
// Chart
const timeChart = new dc.CompositeChart("#time_chart");
timeChart
.height(300)
.x(d3.scaleTime().domain([minDate,maxDate]))
.yAxisLabel("Number of Lines")
.legend(dc.legend().x(80).y(20).itemHeight(13).gap(5))
.renderHorizontalGridLines(true)
.compose([
new dc.LineChart(timeChart)
.dimension(dateDim)
.colors('blue')
.group(dateGroup, "Total")
.curve(d3.curveLinear),
new dc.BarChart(timeChart)
.dimension(dateDim)
.colors('red')
.centerBar(true)
.gap(1)
.alwaysUseRounding(true)
.group(factorGroup, factorText)
.xUnits(d3.timeDays)
])
.brushOn(false)
.render();
The barchart is always displayed with pencil-thin columns, representing correctly the count of 'factor' items in that date. This occurs no matter the size of day range I apply.
I have tried .xUnits(d3.timeWeeks) to see if it can be made to display better, to no avail (actually, it still displays daily totals, suggesting I need to construct an aggregate function). However, I really need daily counts.

As advised by Gordon, a CompositeChart needs to have its .xUnit property defined in the main chart section, not under one of its sub-chart types:
timeChart
.height(300)
.x(d3.scaleTime().domain([minDate,maxDate]))
.yAxisLabel("Number of Lines")
.legend(dc.legend().x(80).y(20).itemHeight(13).gap(5))
.renderHorizontalGridLines(true)
.xUnits(d3.timeDays)
.compose([
new dc.LineChart(timeChart)
.dimension(dateDim)
.colors('blue')
.group(dateGroup, "Total")
.curve(d3.curveLinear),
new dc.BarChart(timeChart)
.dimension(dateDim)
.colors('red')
.centerBar(true)
.gap(1)
.alwaysUseRounding(true)
.group(factorGroup, factorText)
])
.brushOn(false)
.render();
The bar-chart component then displays with a proper width.

Related

Adding a horizontal baseline to a SeriesChart in dc.js

So I have a SeriesChart with this code:
'use strict';
var sectorChart = new dc.SeriesChart('#test-chart');
d3.json('php/query.php?option=sectors').then(data => {
const dateFormatSpecifier = '%Y-%m-%d';
const dateFormat = d3.timeFormat(dateFormatSpecifier);
const dateFormatParser = d3.timeParse(dateFormatSpecifier);
const numberFormat = d3.format('.2f');
var minDate = new Date();
var maxDate = new Date();
var minRatio = .5
var maxRatio = .5
data.forEach(d => {
d.dd = dateFormatParser(d.date);
d.ratio = +d.ratio; // coerce to number
if (d.dd < minDate ) minDate = d.dd;
if (d.dd > maxDate ) maxDate = d.dd;
if (d.ratio < minRatio ) minRatio = d.ratio
if (d.ratio > maxRatio ) maxRatio = d.ratio
});
const ndx = crossfilter(data);
const all = ndx.groupAll();
// Dimension is an array of sector and date.
// Later we'll take the date for the y-axis
// and the sector for the labels
const dateDimension = ndx.dimension(d => [d.name, d.dd]);
// group by the average, the value we'll plot
var avgGroup = dateDimension.group().reduceSum(d => d.average);
sectorChart /* dc.lineChart('#monthly-move-chart', 'chartGroup') */
.width(990)
.height(500)
.chart(c => new dc.LineChart(c))
.x(d3.scaleTime().domain([minDate, maxDate]))
.y(d3.scaleLinear().domain([minRatio, maxRatio]))
.margins({top: 30, right: 10, bottom: 20, left: 40})
.brushOn(false)
.yAxisLabel("ratio avg 10")
.renderHorizontalGridLines(true)
.dimension(dateDimension)
.group(avgGroup)
.seriesAccessor(d => d.key[0])
.keyAccessor(d => d.key[1])
.valueAccessor(d => +d.value)
.legend(dc.legend().x(450).y(15).itemHeight(13).gap(5).horizontal(1).legendWidth(340).autoItemWidth(true));
dc.renderAll();
});
It works all right, but now I would like to add a black horizontal line at 0.5 value. I guess I could modify the query to return a fictious "name" to the dataset with all values at 0.5, but that won't allow me to control the color, and anyway I would like to know if there is a better way, not to mess with the returned data.
Edit: According to Gordon's info, I have transposed as good as I could the vertical line to a horizontal line. It has worked, with some eccentricities. One, the line starts at the left side of the viewport, not on the y axis as it should. Two, and more mysterious, the line is drawn at 0.51 instead of 0.50. I have created a fiddle if anybody wants to play with it.
Fiddle
Based on Gordon's solution I have written a function, with the idea not to burden the chart's definition with all that code. Not fully happy with it, particularly the use of random, but anyways, the world keeps turning and other things to do. In any case, I leave it here in case somebody find it useful.
/**
* Draws a fixed line horizontally or vertically in a dc.js chart
* To be used in the "pretransition" event
* #example
* .on('pretransition', function (chart) {
* dcUtilFixedLine(chart, true, 0.5, [{attr: 'stroke', value: 'red'}] )
* }
* #param (Chart} [chart] The chart provided by the triggered event
* #param (Boolean) [horizontal] True if the line is horizontal, false if vertical
* #param (Object) [value] Value of the point where the line is to be inserted,
* usually the type of the dimension if vertical or group if horizontal
* #param (Array) [attributes] Array of Objects with attr/value pairs)
* giving the attributes added to the line.
* It defaults to stroke = black, stroke-width = 1.
*/
function dcUtilFixedLine(chart, horizontal, value, attributes ) {
if (horizontal) {
var extra_data = [
{y: chart.y()(value) + chart.margins().top, x: chart.margins().left},
{y: chart.y()(value) + chart.margins().top, x: chart.margins().left + chart.effectiveWidth()}
]
} else {
var extra_data = [
{x: chart.x()(value) + chart.margins().left, y: chart.margins().top},
{x: chart.x()(value) + chart.margins().left, y: chart.margins().top + chart.effectiveHeight()}
]
}
var addedPath = 'extraLine' + Math.random().toString().substr(2, 8)
var line = d3.line().x(d => d.x ).y(d => d.y );
var chartBody = chart.select('g');
var path = chartBody.selectAll('path.'+addedPath).data([extra_data]);
path = path.enter()
.append('path')
.attr('class', addedPath)
.attr('id', 'oeLine')
.attr('stroke', 'black')
.merge(path);
attributes.forEach( attribute => path.attr(attribute.attr, attribute.value))
path.attr('d', line);
}
And forked and added it to the new fiddle
Although you can add artificial data series in order to display extra lines, it's often easier to "escape to D3", especially if the lines are static and don't move.
The row vertical line example shows how to do this.
The basic outline is:
use the pretransition event to draw something each time the chart is updated
create two x/y points of data to draw, using the chart's scales and margins to determine pixel coordinates
select a path with a unique class name (here .extra) and join the data to it
For a horizontal line, it could look like this:
.on('pretransition', function(chart) {
var y_horiz = 0.5;
var extra_data = [
{y: chart.y()(y_horiz) + chart.margins().top, x: chart.margins().left},
{y: chart.y()(y_horiz) + chart.margins().top, x: chart.margins().left + chart.effectiveWidth()}
];
var line = d3.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; });
var chartBody = chart.select('g');
var path = chartBody.selectAll('path.extra').data([extra_data]);
path = path.enter()
.append('path')
.attr('class', 'extra')
.attr('stroke', 'black')
.attr('id', 'oeLine')
.attr("stroke-width", 1)
.merge(path);
path.attr('d', line);
});
Each X coordinate is offset by chart.margins().left, each Y coordinate by chart.margins().top.
The width of the chart in pixels is chart.effectiveWidth() and the height in pixels is chart.effectiveHeight(). (These are just the .width() and .height() with margins subtracted.)
Fork of your fiddle
(Note: the example apparently has a typo, using oeExtra instead of extra in the join. This could cause multiple lines to be drawn when the chart is updated. The class in the select should match the class in the join.)

dc.js stacked bar chart, negative values messes up the layout

I need to figure out this bug, my dc.js chart works just fine, except when I add negative values to the stacked bar-chart. Instead of starting from y=0 it starts from the top of the stacked one and messes up the chart.
function grid (selector,data) {
var ndx = crossfilter(data),
all = ndx.groupAll();
var bar_bank = dc.barChart(selector + " .bank");
var bank = ndx.dimension(function(d) {
if (typeof d.bank == "undefined") return "";
return d.bank;
});
var bankGroupBuy = bank.group().reduceSum(function(d) {
return d.buy; });
var bankGroup = bank.group().reduceSum(function(d) {
return d.sell); });
bar_bank
.width(444)
.height(300)
.outerPadding(22)
.gap(1)
.x(d3.scaleBand())
.y(d3.scaleLinear())
.xUnits(dc.units.ordinal)
.brushOn(false)
.elasticY(true)
.yAxisLabel("#BARCHART")
.dimension(bank)
.group(bankGroupBuy)
.stack(bankGroupSell)
;
dc.renderAll();
}
This renders:
but when i
return -d.sell
This happens:
Any ideas how I can fix it? So the stacked negative one starts from 0 and goes down, and does not start from the top of the first one?
Thanks in advance!
This is my csv:
time,bank,buy,sell,AVG_vol_price
2019-02-11,AVA,26378,138177,1.688
2019-02-11,NON,19340,13500,1.683
2019-02-11,SFK,0,43,1.74
2019-02-11,SHB,11300,498,1.692
2019-02-11,SWB,101200,6000,1.689
2019-02-12,AVA,125612,138612,1.683
2019-02-12,ENS,5000,0,1.702

DC.js barChart not displaying with date axis

I'm trying to display a bar chart showing with a count of dates but the bars will not draw and I'm getting a NaN error in the console.
Please can someone tell me where I am going wrong?
var data1=[{budget:1,billed:1,fees:1,feeVariance:0,Documents:"URL",date:"01 February 2017"},
{budget:7,billed:6,fees:1,feeVariance:3,Documents:"URL",date:"01 February 2018"},
{budget:10,billed:1,fees:4,feeVariance:3,Documents:"URL",date:"01 May 2017"},
{budget:14,billed:2,fees:4,feeVariance:2,Documents:"URL",date:"15 May 2017"},
{budget:2,billed:1,fees:1,feeVariance:0,Documents:"URL",date:"02 June 2016"}];
var facts = crossfilter(data1);
var dateDimension = facts.dimension(function(d) { return new Date(d.date); });
var dateGroup = dateDimension.group().reduceCount(function(d){return new Date(d.date);});
var minDate = dateDimension.bottom(1)[0].date;
var maxDate = dateDimension.top(1)[0].date;
dc.barChart("#chart1")
.width(800)
.dimension(dateDimension)
.group(dateGroup)
.brushOn(false)
.x(d3.time.scale().domain([minDate,maxDate]))
.xUnits(d3.time.months);
dc.renderAll();
you need to firstly determine the date format so dc.js can understand it as a date rather than simply a string.
So firstly, create a variable to set the date format:
var dateFormat = d3.time.format('%d %B %Y');
then a function to make our date and month fields:
data1.forEach(function(d) {
d.date = dateFormat.parse(d.date);
d.month = d3.time.month(d.date);
});
Then change the dimension and group to use d.month
var dateDimension = facts.dimension(function(d) { return d.month; });
var dateGroup = dateDimension.group().reduceCount(function(d){return
d.month;});
This will group by month and provide a count/total by month also.
Here's the full code:
var dateFormat = d3.time.format('%d %B %Y');
var data1=[{budget:1,billed:1,fees:1,feeVariance:0,Documents:"URL",date:"01
February 2017"},
{budget:7,billed:6,fees:1,feeVariance:3,Documents:"URL",date:"01 February 2018"},
{budget:10,billed:1,fees:4,feeVariance:3,Documents:"URL",date:"01 May 2017"},
{budget:14,billed:2,fees:4,feeVariance:2,Documents:"URL",date:"15 May 2017"},
{budget:2,billed:1,fees:1,feeVariance:0,Documents:"URL",date:"02 June 2016"}];
var facts = crossfilter(data1);
data1.forEach(function(d) {
d.date = dateFormat.parse(d.date);
d.month = d3.time.month(d.date);
});
var dateDimension = facts.dimension(function(d) { return d.month; });
var dateGroup = dateDimension.group().reduceCount(function(d){return d.month;});
var minDate = dateDimension.bottom(1)[0].date;
var maxDate = dateDimension.top(1)[0].date;
dc.barChart("#chart1")
.width(800)
.dimension(dateDimension)
.group(dateGroup)
.brushOn(false)
.x(d3.time.scale().domain([minDate,maxDate]))
.xUnits(d3.time.months);
dc.renderAll();
I've added some extra properties to the chart to ensure all the bars are shown correctly:
.gap(2)
.outerPadding(5)
.centerBar(true)
.elasticX(true)
.xAxisPadding(20)
jsfiddle example:
jsfiddle

Bar-chart zoom confuses data filtering on DC chart

I have quite a long period of observations (tornadoes) 800-2016 (about 2000 inputs). To visualize them on one bar chart I use mouseZoomable (true) function for DC.js but it starts to mess filtering: User can't properly select interval on the chart, it slips all the time and seems as asynchronized.
Sorry I am very new to D3 and DC.js and couldn't find an answer.
Here is my code:
var ndx = crossfilter(records);
var YearDim = ndx.dimension(function(d) { return d["Year"]; });
var numRecordsByYear = YearDim.group();
var minYear = YearDim.bottom(1)[0]["Year"];
var maxYear = YearDim.top(1)[0]["Year"];
var timeChart = dc.barChart("#time-chart");
timeChart
.width(600)
.height(140)
.margins({top: 10, right: 50, bottom: 40, left: 40})
.dimension(YearDim)
.group(numRecordsByYear)
.transitionDuration(500)
.mouseZoomable(true)
.x(d3.time.scale().domain([minYear, maxYear]))
.zoomOutRestrict([true])
.elasticY(true)
Will be very gratefull for helping or suggesting another way to visualize long and irregular time interval.

Error with .compose using dc.js

I am trying to create a composite of 2 line charts with dc.js.
But I get this error everytime:
Uncaught TypeError: timeChart.width(...).height(...).x(...).elasticY(...).margins(...).dimension(...).compose is not a function
it is a time series where I want to plot netCommercialPosition and netCommercialPosition as two seperate line. It works when I stack them but not when i want to use .compose.
I have followed several examples such as:
Dual Y axis line chart in dc.js
http://dc-js.github.io/dc.js/examples/series.html
So hopefully I use the .compose element correctly
my data set is a json with the following structure:
[{"CFTC_Commodity_Code": 1, "Net_Commmercial_Position": -113520, "Report_Date_as_MM_DD_YYYY": "14/07/2015", "Net_Fund_Position": -12246, "Price": 583.5, "Net_ Commmercial_Position": 3877, " },{…}]
here is my code:
d3.json("/donorschoose/COT", function (error, dataset1){
var ymdFormat = d3.time.format("%d/%m/%Y");
dataset1.forEach(function(p) {
p.Report_Date_as_MM_DD_YYYY = ymdFormat.parse(p.Report_Date_as_MM_DD_YYYY);
});
var COTProjects = dataset1;
var ndx = crossfilter(COTProjects);
var all = ndx.groupAll();
FilterDimension = ndx.dimension(function (d) {
return d.CFTC_Commodity_Code;
});
var dateDim = ndx.dimension(function(d) { return d.Report_Date_as_MM_DD_YYYY; });
var Prices = dateDim.group().reduceSum(function(d) {return d.Price; });
var netFundPosition = dateDim.group().reduceSum(function(d) {return d.Net_Fund_Position; });
var netCommercialPosition = dateDim.group().reduceSum(function(d) {return d.Net_Commmercial_Position; });
var minDate = dateDim.bottom(1)[0]["Report_Date_as_MM_DD_YYYY"];
var maxDate = dateDim.top(1)[0]["Report_Date_as_MM_DD_YYYY"];
var timeChart = dc.lineChart("#time-chart");
actualValuesChart = dc.lineChart(timeChart)
.group(netFundPosition)
normValuesChart = dc.lineChart(timeChart)
.group(netCommercialPosition)
timeChart
.width(650)
.height(260)
.x(d3.time.scale().domain([minDate, maxDate]))
.elasticY(true)
.margins({top: 0, right: 5, bottom: 20, left: 40})
.dimension(dateDim)
.compose([actualValuesChart,normValuesChart])
.transitionDuration(500)
.yAxis().ticks(4);
.brushOn(false)
FilterDimension.filter(1)
dc.renderAll();
});
Any help appreciated
thanks in advance
I found the solution, my problem was that I was trying to call compose() from a lineChart object instead of a compositeChart object.
dc.js-s doc

Resources