Is there a more complete tutorial or guide to creating charts with dc.js than what is offered in their documentation? I'm trying to create a simple line chart with 2 stacked levels. I'm making use of the following csv:
I want the WasteDate to be on the x-axis and the WasteData to be on the y-axis. Further I want one layer to be of the WasteFunction Minimisation and the other to be of the WasteFunction Disposal. This should give me something like the following (very roughly):
Now, as I understand it, I need to create a dimension for the x-axis using crossfilter and then a filtered dimension for my 2 stacks.
The dimension for the x-axis will be the dates:
// dimension by month
var Date_dim = ndx.dimension(function (d) {
return d.WasteDate;
});
// Get min/max date for x-axis
var minDate = Date_dim.bottom(1)[0].WasteDate;
var maxDate = Date_dim.top(1)[0].WasteDate;
Then I need to create a dimension for the y-axis, then filter it for each of my stacks?
// WasteType dimension
var WasteFunction_dim = ndx.dimension(function (d) {
return d.WasteFunction;
});
// Minimisation Filter
var WasteFunction_Disposal = WasteFunction_dim.filter("Disposal");
// Disposal Filter
var WasteFunction_Minimisation = WasteFunction_dim.filter("Minimisation");
Then I should be able to use these to setup the chart:
moveChart
.renderArea(true)
.width(900)
.height(200)
.dimension(Date_dim)
.group(WasteFunction_Minimisation, 'Minimisation')
.stack(WasteFunction_Disposal, 'Disposal')
.x(d3.time.scale().domain([minDate, maxDate]));
Now, I can't get passed this error on the RenderAll() function:
The full code:
< script type = "text/javascript" >
$(document).ready(function() {
var moveChart = dc.lineChart('#monthly-move-chart');
d3.csv('minimisation-vs-disposal.csv', function(data) {
/* format the csv file a bit */
var dateFormat = d3.time.format('%d/%M/%Y');
var numberFormat = d3.format('.2f');
data.forEach(function(d) {
d.dd = dateFormat.parse(d.WasteDate);
d.WasteData = +d.WasteData // coerce to number
});
// Cross Filter instance
var ndx = crossfilter(data);
var all = ndx.groupAll();
// dimension by month
var Date_dim = ndx.dimension(function(d) {
return d.WasteDate;
});
// Get min/max date for x-axis
var minDate = Date_dim.bottom(1)[0].WasteDate;
var maxDate = Date_dim.top(1)[0].WasteDate;
// Waste Data dimension
var WasteData_dim = ndx.dimension(function(d) {
return d.WasteData;
});
// WasteType dimension
var WasteFunction_dim = ndx.dimension(function(d) {
return d.WasteFunction;
});
// Minimisation Filter
var WasteFunction_Disposal = WasteFunction_dim.filter("Disposal");
// Disposal Filter
var WasteFunction_Minimisation = WasteFunction_dim.filter("Minimisation");
moveChart
.renderArea(true)
.width(900)
.height(200)
.transitionDuration(1000)
.dimension(Date_dim)
.group(WasteFunction_Minimisation, 'Minimisation')
.stack(WasteFunction_Disposal, 'Disposal')
.x(d3.time.scale().domain([minDate, maxDate]));
dc.renderAll();
});
});
< /script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="monthly-move-chart">
<strong>Waste minimisation chart</strong>
</div>
It's true, dc.js does not have much documentation. Someone could write a book but it hasn't happened. People mostly rely on examples to get started, and the annotated stock example is a good first read.
The biggest problem in your code is that those are not crossfilter groups. You really need to learn the crossfilter concepts to use dc.js effectively. Crossfilter has very strong documentation, but it's also very dense and you'll have to read it a few times.
Feel free to join us on the dc.js user group if you want to talk it through to get a better understanding. It does take a while to get the idea but it's worth it!
Related
I am using google charts to display a stacked column chart. I am using entity framework and linq queries to gather my data from the db.
The problems I am having is:
that it will not order the chart. I have ordered the chart but the x-axis remains un-ordered. Can this be done through the linq query or could I do it in the script?
Currently it only displays x-axis values for data that I have. Example is on the x-axis I have month number but it only displays marks for data I have eg. 1,4,5,6. Is there a way to include from 1-12 although there is no data for that particular month number?
Code:
#region Total Hours Per Month sick
var querythpshols = (from r in db.HolidayRequestForms
where (r.StartDate) >= dateAndTime
group r by r.MonthOfHoliday into g
select new { Value = g.Key, Count = g.Sum(h => h.HoursTaken)});
var resultthpshols = querythpshols.ToList();
var datachartthpshols = new object[resultthpshols.Count];
int G = 0;
foreach (var i in resultthpshols)
{
datachartthpshols[G] = new object[] { i.Value.ToString(), i.Count };
G++;
}
string datathpshols = JsonConvert.SerializeObject(datachartthpshols, Formatting.None);
ViewBag.datajthpshols = new HtmlString(datathpshols);
#endregion
#region Total Hours Per Month
var querythpshols1 = (from r in db.HolidayRequestForms
where (r.StartDate) <= dateAndTime
group r by r.MonthOfHoliday into g
select new { Value = g.Key, Count1 = g.Sum(r => r.HoursTaken) })
;
var resultthpshols1 = querythpshols1.ToList();
var datachartthpshols1 = new object[resultthpshols1.Count];
int P = 0;
foreach (var i in resultthpshols1)
{
datachartthpshols1[P] = new object[] { i.Value.ToString(), i.Count1 };
P++;
}
string datathpshols1 = JsonConvert.SerializeObject(datachartthpshols1, Formatting.None);
ViewBag.datajthpshols1 = new HtmlString(datathpshols1);
#endregion
Script:
#*TOTAL HOURS PER MONTH CHART*#
<scipt>
<script>
var datathpshols = '#ViewBag.datajthpshols';
var datassthpshols = JSON.parse(datathpshols);
var datathpshols1 = '#ViewBag.datajthpshols1';
var datassthpshols1 = JSON.parse(datathpshols1);
</script>
<script type="text/javascript">
// Load the Visualization API and the corechart package.
google.charts.load('current', { 'packages': ['corechart'] });
// Set a callback to run when the Google Visualization API is loaded.
google.charts.setOnLoadCallback(drawChartA);
// Callback that creates and populates a data table,
// instantiates the pie chart, passes in the data and
// draws it.
function drawChartA() {
// Create the data table.
var data1 = new google.visualization.DataTable();
data1.addColumn('string', 'Value');
data1.addColumn('number', 'Holiday Hours Booked');
data1.addRows(datassthpshols);
var data2 = new google.visualization.DataTable();
data2.addColumn('string', 'Value');
data2.addColumn('number', 'Holiday Hours Taken');
data2.addRows(datassthpshols1);
var joinedData = google.visualization.data.join(data1, data2, 'full', [[0, 0]], [1], [1]);
// Set chart options
var options = {
'title': 'Holiday Hours Taken Per Month',
'width': 600,
'height': 350,
'hAxis': { title: 'Month Number' },
'vAxis': { title: 'Holiday Hours Taken' },
'is3D': true,
'isStacked': true,
'legend': 'right'
};
// Instantiate and draw our chart, passing in some options.
var chart = new google.visualization.ColumnChart(document.getElementById('chartTHPShols_div'));
chart.draw(joinedData, options);
}
</script>
1) Use data table method --> sort -- to order the x-axis.
joinedData.sort([{column: 0}]);
2) strings produce a discrete axis, and will only display the data available. numbers produce a continuous axis, and provide much more flexibility for the axis ticks. probably the most simplest solution would be to use a data view to convert the x-axis to numbers. (use the data view to draw the chart)
var joinedData = google.visualization.data.join(data1, data2, 'full', [[0, 0]], [1], [1]);
var dataView = new google.visualization.DataView(joinedData);
dataView.setColumns([{
calc: function (dt, row) {
return parseFloat(dt.getValue(row, 0));
},
label: joinedData.getColumnLabel(0),
type: 'number'
}, 1, 2]);
chart.draw(dataView, options);
I am using pdfMake to generate table reports. Some of the reports are very wide and dont fit on a standard page width, even in landscape mode. Currently, pdfMake is cutting off the table content when it overflows past the page margin.
I would like to page break the table when it is too wide, much like when the rows overflow to the next page.
Is this possible using pdfMake?
Can using pageBreakBefore callback function help for this?
Thank you
Yes, this is possible with pdfMake, even though not currently a feature.
To achieve this, you can just break overflowing columns into another table. We can put all the tables in an array, then just set them in the docDefinition as follows.
Any common attributes you want in the tables can be defined in the Template constructor.
for (var i = 0; i < tables.length;i++) {
docDefinition.content[i] = new Template();
docDefinition.content[i].table.body = tables[i];
docDefinition.content[i].table.widths = widths[i];
if (i > 0) {
docDefinition.content[i].pageBreak = 'before';
}
}
function Template(){
this.table = {
dontBreakRows: true
};
//zebra stripes layout
this.layout = {
fillColor: function (row, node, col) {
return (row % 2 === 0) ? '#CCCCCC' : null;
}
}
}
How do we determine if a column will overflow? We have two options:
If you are using bootstrap datatables, you can use the "width" attribute in the html.
pdfmake calculates the actual width, but you may have to dig around in pdfmake.js.
Then, just loop through, adding widths until you exceed your limit (my limit was for 8pt font). You can do this for THs then save those column splits and use those for the TDs.
If the final page is just barely overflowing, we don't want the final page to have just one column, we want each page to have roughly the same width. We calculate the number of pages needed, then find the desired break point from there. To link the pages together more easily, you can add a row number column at the beginning of each table.
var colSplits = [];
var tables = new Array();
function parseTHs(colSplits, tables) {
var colSum = 0;
var pageSize = 1120-7*rows.toString().length;
var paddingDiff = 11.9;
var columns = 0;
var prevSum;
var i = 0;
var width = $(".dataTables_scrollHeadInner > table").width();
var pages = Math.ceil(width/pageSize);
console.log("pages: "+pages);
var desiredBreakPoint = width/pages;
console.log("spread: "+desiredBreakPoint);
var limit = pageSize;
var row = ['#'];
var percent = '%';
widths.push(percent);
$(".dataTables_scrollHeadInner > table > thead > tr:first > th").each(function() {
prevSum = colSum;
colSum += $(this).outerWidth()-paddingDiff;
//if adding column brings us farther away from the desiredBreakPoint than before, kick it to next table
if (colSum > limit || desiredBreakPoint-colSum > desiredBreakPoint-prevSum) {
tables[i] = [row];
row = ['#'];
widths.push(percent);
colSplits.push(columns);
i++;
desiredBreakPoint += width/pages;
limit = prevSum+pageSize;
}
row.push({text: $(this).text(), style:'header'});
widths.push(percent);
columns++;
});
//add the final set of columns
tables[i] = [row];
}
function parseTDs(colSplits, tables) {
var currentRow = 0;
$("#"+tableId+" > tbody > tr").each(function() {
var i = 0;
var row = [currentRow+1];
var currentColumn = 0;
var split = colSplits[i];
$(this).find("td").each(function() {
if (currentColumn === split) {
tables[i].push(row);
row = [currentRow+1];
i++;
split = colSplits[i];
}
row.push({text: $(this).text()});
currentColumn++;
});
//add the final set of columns
tables[i].push(row);
currentRow++;
});
}
parseTHs(colSplits, tables);
parseTDs(colSplits, tables);
Note: If you want the columns to fill all the available page, there's a good implementation for that at this link.
I just added '%' for the widths and added that code to pdfmake.js.
Hope this helps!
Just add dontBreakRows property in your table object like this
table: {
dontBreakRows: true,
widths: [30,75,48,48,48,48,48,115],
body: []
}
Also, you can make the page wider and change the page orientation as landscape.
pageSize: "A2",
pageOrientation: "landscape",
I want to make stacked bar chart where each portion has a width that encodes one value (say "Change" in the data below) and a height that encodes another value ("Share")
In some ways this is like a histogram with different bin sizes. There are a few "histogram" questions but none seem to address this. Plot Histograms in Highcharts
So given data like this:
Category Share Price Change
Apples 14.35 0.1314192423
Horseradish 46.168 0.1761474117
Figs 2.871 0.018874249
Tomatoes 13.954 0.0106121298
Mangoes 7.264 0.1217297011
Raisins 5.738 0.0206787136
Eggplant 6.31 0.0110160732
Other produce 3.344 0.0945377722
I can make a stacked bar that captures the "share" column in widths:
And another that captures the "change" column in heights:
And I can use an image editor to combine those into this histogram-like beast:
Which really captures that horseradish is a huge deal. So my question is, can I do that within Highcharts?
You can realise that by using snippet.
(function (H) {
var seriesTypes = H.seriesTypes,
each = H.each,
extendClass = H.extendClass,
defaultPlotOptions = H.getOptions().plotOptions,
merge = H.merge;
defaultPlotOptions.marimekko = merge(defaultPlotOptions.column, {
pointPadding: 0,
groupPadding: 0
});
seriesTypes.marimekko = extendClass(seriesTypes.column, {
type: 'marimekko',
pointArrayMap: ['y', 'z'],
parallelArrays: ['x', 'y', 'z'],
processData: function () {
var series = this;
this.totalZ = 0;
this.relZ = [];
seriesTypes.column.prototype.processData.call(this);
each(this.zData, function (z, i) {
series.relZ[i] = series.totalZ;
series.totalZ += z;
});
},
translate: function () {
var series = this,
totalZ = series.totalZ,
xAxis = series.xAxis;
seriesTypes.column.prototype.translate.call(this);
// Distort the points to reflect z dimension
each(this.points, function (point, i) {
var shapeArgs = point.shapeArgs,
relZ = series.relZ[i];
shapeArgs.x *= (relZ / totalZ) / (shapeArgs.x / xAxis.len);
shapeArgs.width *= (point.z / totalZ) / (series.pointRange / series.xAxis.max);
});
}
});
}(Highcharts));
Example: http://jsfiddle.net/highcharts/75oucp3b/
I'm trying to convert the following options in Highcharts to multiple series with multiple drilldowns. The problem is that I've changed the loop to progress over the points so as to add both drilldown series. However, in the loop I've written, it seems to be the case that after one go through the loop, the points array is overwritten with nulls, which makes the whole thing irrelevant.
I'm a beginner with the options, and after having spent quite a bit of time on it I can't crack it. An explanation and a solution would be an ideal answer to the question: "How do I do the following with multiple drilldowns?" It's all asynchronous requests on datasets.
I'm starting with
options.chart = options.chart || {};
options.chart.events = options.chart.events || {};
var dd = options.chart.events.drilldown || function(e) {};
options.chart.events.drilldown = function(e) {
var chart = this;
chart._drilldowns = chart._drilldowns || {};
var series = chart._drilldowns[e.point.drilldown];
if (series) {
e.seriesOptions = series;
chart.addSeriesAsDrilldown(e.point, series);
dd(e);
}
if (!e.seriesOptions) {
chart.showLoading('Fetching data...');
$.getJSON(
'%(url)s?' + analytics.get_form_data(),
function(drilldowns) {
chart.hideLoading();
chart._drilldowns = drilldowns;
var series = drilldowns[e.point.drilldown];
chart.addSeriesAsDrilldown(e.point, series);
e.seriesOptions = series;
dd(e);
}
);
}
};
''' % {'url': self.get_drilldown_url()}
and I've tried to change the second part to:
...
function(drilldowns) {
chart.hideLoading();
chart._drilldowns = drilldowns;
e.points.forEach(function(value, key){
var series = drilldowns[value.drilldown];
chart.addSeriesAsDrilldown(value, series);
})
e.seriesOptions = series;
dd(e);
}
);
}
};
But I don't get both my series drilling down. I actually get a Property xAxis of null is not allowed error, as when I go through the loop the second time, the set of points has been changed to null.
UPDATE
We eventually fixed (actually a collegue did!) using promises. We engineered it to wait until the first async request for data was retrieved, and then called addSingleSeriesAsDrilldown on each one until we hit the last one (derived off the chart state) at which point we called addSeriesAsDrilldown which does the applyDrilldown as part of the code.
It's not the part of official API, but this way you can get multiple drilldowns: http://jsfiddle.net/p2xw9416/
In the lowest level of AJAX requests (if you have multiple of them), add each series as single object:
chart.hideLoading();
chart.addSingleSeriesAsDrilldown(e.point, series_1);
chart.addSingleSeriesAsDrilldown(e.point, series_2);
chart.applyDrilldown(); // update && animate
I have an area chart with negative values. Nothing insanely different from the example they give, but there's one twist: I'd like to keep zero centered on the Y axis.
I know this can be achieved by setting the yAxis.max to some value n and yAxis.min to −n, with n representing the absolute value of either the peak of the chart or the trough, whichever is larger (as in this fiddle). However, my data is dynamic, so I don't know ahead of time what n needs to be.
I'm relatively new to Highcharts, so it's possible I'm missing a way to do this through configuration and let Highcharts take care of it for me, but it's looking like I'll need to use Javascript to manually adjust the y axis myself when the page loads, and as new data comes in.
Is there an easy, configuration-driven way to keep zero centered on the Y axis?
I ended up finding a way to do this through configuration after digging even further into the Highcharts API. Each axis has a configuration option called tickPositioner for which you provide a function which returns an array. This array contains the exact values where you want ticks to appear on the axis. Here is my new tickPositioner configuration, which places five ticks on my Y axis, with zero neatly in the middle and the max at both extremes :
yAxis: {
tickPositioner: function () {
var maxDeviation = Math.ceil(Math.max(Math.abs(this.dataMax), Math.abs(this.dataMin)));
var halfMaxDeviation = Math.ceil(maxDeviation / 2);
return [-maxDeviation, -halfMaxDeviation, 0, halfMaxDeviation, maxDeviation];
},
...
}
I know this is an old post, but thought I would post my solution anyway (which is inspired from the one macserv suggested above in the accepted answer) as it may help others who are looking for a similar solution:
tickPositioner: function (min, max) {
var maxDeviation = Math.ceil(Math.max(Math.abs(this.dataMax), Math.abs(this.dataMin)));
return this.getLinearTickPositions(this.tickInterval, -maxDeviation, maxDeviation);
}
You can do this with the getExtremes and setExtremes methods
http://api.highcharts.com/highcharts#Axis.getExtremes%28%29
http://api.highcharts.com/highcharts#Axis.setExtremes%28%29
example:
http://jsfiddle.net/jlbriggs/j3NTM/1/
var ext = chart.yAxis[0].getExtremes();
Here is my solution. The nice thing about this is that you can maintain the tickInterval.
tickPositioner(min, max) {
let { tickPositions, tickInterval } = this;
tickPositions = _.map(tickPositions, (tickPos) => Math.abs(tickPos));
tickPositions = tickPositions.sort((a, b) => (b - a));
const maxTickPosition = _.first(tickPositions);
let minTickPosition = maxTickPosition * -1;
let newTickPositions = [];
while (minTickPosition <= maxTickPosition) {
newTickPositions.push(minTickPosition);
minTickPosition += tickInterval;
}
return newTickPositions;
}
Just in case someone is searching,
One option more. I ended up in a similar situation. Follows my solution:
tickPositioner: function () {
var dataMin,
dataMax = this.dataMax;
var positivePositions = [], negativePositions = [];
if(this.dataMin<0) dataMin = this.dataMin*-1;
if(this.dataMax<0) dataMax = this.dataMax*-1;
for (var i = 0; i <= (dataMin)+10; i+=10) {
negativePositions.push(i*-1)
}
negativePositions.reverse().pop();
for (var i = 0; i <= (dataMax)+10; i+=10) {
positivePositions.push(i)
}
return negativePositions.concat(positivePositions);
},
http://jsfiddle.net/j3NTM/21/
It is an old question but recently I have had the same problem, and here is my solution which might be generalized:
const TICK_PRECISION = 2;
const AXIS_MAX_EXPAND_RATE = 1.2;
function setAxisTicks(axis, tickCount) {
// first you calc the max from the data, then multiply with 1.1 or 1.2
// which can expand the max a little, in order to leave some space from the bottom/top to the max value.
// toPrecision decide the significant number.
let maxDeviation = (Math.max(Math.abs(axis.dataMax), Math.abs(axis.dataMin)) * AXIS_MAX_EXPAND_RATE).toPrecision(TICK_PRECISION);
// in case it is not a whole number
let wholeMaxDeviation = maxDeviation * 10 ** TICK_PRECISION;
// halfCount will be the tick counts on each side of 0
let halfCount = Math.floor(tickCount / 2);
// look for the nearest larger number which can mod the halfCount
while (wholeMaxDeviation % halfCount != 0) {
wholeMaxDeviation++;
}
// calc the unit tick amount, remember to divide by the precision
let unitTick = (wholeMaxDeviation / halfCount) / 10 ** TICK_PRECISION;
// finally get all ticks
let tickPositions = [];
for (let i = -halfCount; i <= halfCount; i++) {
// there are problems with the precision when multiply a float, make sure no anything like 1.6666666667 in your result
let tick = parseFloat((unitTick * i).toFixed(TICK_PRECISION));
tickPositions.push(tick);
}
return tickPositions;
}
So in your chart axis tickPositioner you may add :
tickPositioner: function () {
return setAxisTicks(this, 7);
},