Trying to order dates on heatMap after formatting the dates - d3.js

This is how the xAxis looks after formatting the dates:
Without formatTimeMonth() and only parseTime() the xAxis if ordered correctly.
Formating dates:
var parseTime = d3.timeParse("%m/%d/%Y");
var formatTimeMonth = d3.timeFormat("%b/%Y");
data.forEach(function (d) {
d.Month = formatTimeMonth(parseTime(d.Month));
});
HeatMap:
heatMap
.width(900)
.height(800)
.dimension(dimension)
.group(FTEMonthGroup)
.margins({ left: 200, top: 30, right: 10, bottom: 35 })
.keyAccessor(function (d) { return d.key[0]; })
.valueAccessor(function (d) { return d.key[1]; })
.colorAccessor(function (d) { return +d.value.color; })
.title(function (d) {
return "Manager: " + d.key[1] + "\n" +
"FTE: " + d.value.toolTip + "\n" +
"Date: " + d.key[0] + "";
})
.on('renderlet', function (chart) {
chart.selectAll("g.cols.axis text")
.attr("transform", function () {
var coord = this.getBBox();
var x = coord.x + (coord.width / 2),
y = coord.y + (coord.height / 2);
return "rotate(-45 " + x + " " + y + ")"
})
.style("text-anchor", "right");
});
heatMap.colorCalculator(function (d, i) {
return d.value.color === null ?
'#ccc' : heatMap.colors()(d.value.color);
});

The issue here is that you are specifying the group key as a string. Crossfilter will apply "natural ordering" to keys in groups, so your keys are in alphabetical order.
The best way to format your ticks without changing the order is to revert your keys to dates or date-strings and then use colsLabel:
heatMap.colsLabel(d => formatTimeMonth(parseTime(d.Month)));
This is the equivalent of using
chart.xAxis().tickFormat(...)
on most of the other charts.
(The heatmap is one of a few charts that does not use d3-axis. I don't know if the original author ran into troubles with using the standard component - my guess is that they just found it easier to reinvent the wheel.)

Related

How to get day of week on left side of D3 calendar heat map

I'm trying to create a calendar heatmap with D3, very similar to the Github contribution calendar.
I can't get the day of week to align correctly. It seems to repeat for every month and doesn't have correct margins or alignment. I only want the days to display once, on the left side of the calendar.
Just like this:
Here is what mine looks like:
Here is my code:
<style>
#calendar {
margin: 20px;
}
.month {
margin-right: 8px;
}
.month-name {
font-size: 85%;
fill: #777;
font-family: Muli, san-serif;
}
.day.hover {
stroke: #6d6E70;
stroke-width: 2;
}
.day.focus {
stroke: #ffff33;
stroke-width: 2;
}
</style>
<div style="text-align:center;" id="calendar"></div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script>
function drawCalendar(dateData){
var weeksInMonth = function(month){
var m = d3.timeMonth.floor(month)
return d3.timeWeeks(d3.timeWeek.floor(m), d3.timeMonth.offset(m,1)).length;
}
//var minDate = new Date(2018, 12, 31);
var minDate = d3.min(dateData, function(d) { return new Date(2018, 12, 1 ) });
//var minDate = d3.min(dateData, function(d) { return new Date(d.day) });
console.log(minDate);
//var maxDate = new Date(2019, 11, 30);
var maxDate = d3.max(dateData, function(d) { return new Date(2019, 11, 30 ) });
console.log(maxDate);
var cellMargin = 2,
calY=10,//offset of calendar in each group
xOffset=-5,
dayName = ['Su','Mo','Tu','We','Th','Fr','Sa'],
cellSize = 20;
var day = d3.timeFormat("%w"),
week = d3.timeFormat("%U"),
format = d3.timeFormat("%Y-%m-%d"),
titleFormat = d3.utcFormat("%a, %d-%b"),
monthName = d3.timeFormat("%B"),
months= d3.timeMonth.range(d3.timeMonth.floor(minDate), maxDate);
var svg = d3.select("#calendar").selectAll("svg")
.data(months)
.enter().append("svg")
.attr("class", "month")
.attr("height", ((cellSize * 7) + (cellMargin * 8) + 20) ) // the 20 is for the month labels
.attr("width", function(d) {
var columns = weeksInMonth(d);
return ((cellSize * columns) + (cellMargin * (columns + 1)));
})
.append("g")
svg.append("text")
.attr("class", "month-name")
.attr("y", (cellSize * 7) + (cellMargin * 8) + 15 )
.attr("x", function(d) {
var columns = weeksInMonth(d);
return (((cellSize * columns) + (cellMargin * (columns + 1))) / 2);
})
.attr("text-anchor", "middle")
.text(function(d) { return monthName(d); })
//create day labels
var days = ['Su','Mo','Tu','We','Th','Fr','Sa'];
var dayLabels=svg.append("g").attr("id","dayLabels")
days.forEach(function(d,i) {
dayLabels.append("text")
.attr("class","dayLabel")
.attr("x",xOffset)
.attr("y",function(d) { return calY+(i * cellSize); })
.text(d);
})
var rect = svg.selectAll("rect.day")
.data(function(d, i) { return d3.timeDays(d, new Date(d.getFullYear(), d.getMonth()+1, 1)); })
.enter().append("rect")
.attr("class", "day")
.attr("width", cellSize)
.attr("height", cellSize)
.attr("rx", 3).attr("ry", 3) // rounded corners
.attr("fill", '#eaeaea') // default light grey fill
.attr("y", function(d) { return (day(d) * cellSize) + (day(d) * cellMargin) + cellMargin; })
.attr("x", function(d) { return ((week(d) - week(new Date(d.getFullYear(),d.getMonth(),1))) * cellSize) + ((week(d) - week(new Date(d.getFullYear(),d.getMonth(),1))) * cellMargin) + cellMargin ; })
.on("mouseover", function(d) {
d3.select(this).classed('hover', true);
})
.on("mouseout", function(d) {
d3.select(this).classed('hover', false);
})
.datum(format);
rect.append("title")
.text(function(d) { return titleFormat(new Date(d)); });
var lookup = d3.nest()
.key(function(d) { return d.day; })
.rollup(function(leaves) {
return d3.sum(leaves, function(d){ return parseInt(d.count); });
})
.object(dateData);
var scale = d3.scaleLinear()
.domain(d3.extent(dateData, function(d) { return parseInt(d.count); }))
.range([0.2,1]); // the interpolate used for color expects a number in the range [0,1] but i don't want the lightest part of the color scheme
rect.filter(function(d) { return d in lookup; })
.style("fill", function(d) { return d3.interpolateYlGn(scale(lookup[d])); })
.select("title")
.text(function(d) { return titleFormat(new Date(d)) + ": " + lookup[d]; });
}
d3.csv("dates.csv", function(response){
drawCalendar(response);
})
</script>
There is also an input csv file that contains the following values:
day,count
2019-05-12,171
2019-06-17,139
2019-05-02,556
2019-04-10,1
2019-05-04,485
2019-03-27,1
2019-05-26,42
2019-05-25,337
2019-05-23,267
2019-05-05,569
2019-03-31,32
2019-03-25,128
2019-05-13,221
2019-03-30,26
2019-03-15,3
2019-04-24,10
2019-04-27,312
2019-03-20,99
2019-05-10,358
2019-04-01,15
2019-05-11,199
2019-07-06,744
2019-05-08,23
2019-03-28,98
2019-03-29,64
2019-04-30,152
2019-03-21,148
2019-03-19,20
2019-05-07,69
2019-04-29,431
2019-04-25,330
2019-04-28,353
2019-04-18,9
2019-01-10,1
2019-01-09,2
2019-03-26,21
2019-05-27,18
2019-04-19,10
2019-04-06,1
2019-04-12,214
2019-05-03,536
2019-07-03,3
2019-06-16,1
2019-03-24,138
2019-04-26,351
2019-04-23,14
2019-05-01,19
2019-07-05,523
2019-05-22,3
2019-05-09,430
2019-05-24,472
2019-04-11,172
2019-03-17,7
2019-05-14,10
2019-05-06,449
2019-07-04,295
2019-05-15,12
2019-03-23,216
2019-03-18,47
2019-03-22,179
Typically you allow for a margin in your SVG, something like this:
const margin = { top: 10, right: 20, bottom: 10, left: 5 }
const svg = d3
.select('#chart')
.append('svg')
.attr('width', 900 + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
Basically you create an SVG element that is bigger than your drawing area, and then you move (translate) the chart in by the margins. Then your axis can appear in the margin

Is there any way to retrieve the chart object by its id in dc.js

I am add rowcharts according the user input, append needed div elements, then add rowchart to this element, but I do not know how to retrieve this chart when when handle the label, when I use dc.rowChart("#rowchart_" + checkedValues[i]), it worked but it will throw me error, about the dimisions, and also I cannot set the reset link for the chart. My question is that:
Is there ant way to retrieve the chart object?
Appreciate any help!!! Thanks!
for(var i = 0; i < checkedValues.length; i++){
$(".rowCharts").append("<div id='rowchart_" + checkedValues[i] + "' class='col-sm-12 col-md-12 col-lg-5'></div>");
dc.rowChart("#rowchart_" + checkedValues[i])
.width(400)
.height(300)
.margins({ top: 20, left: 10, right: 10, bottom: 20 })
.dimension(ndx.dimension(function (d) { return d[checkedValues[i]]; }))
.group(ndx.dimension(function (d) { return d[checkedValues[i]]; }).group())
// .label(function (d) {
// if (dc.rowChart("#rowchart_" + checkedValues[i]).hasFilter() && !dc.rowChart("#rowchart_" + checkedValues[i]).hasFilter(d.key)) {
// return d.key + '(0%)';
// }
// var label = d.key;
// if (all.value()) {
// label += '(' + (d.value / all.value() * 100).toFixed(2) + '%)';
// }
// return label;
// })
.title(function (d) { return d.value; })
.on('filtered.monitor', chartFilteredCallback)
.elasticX(true)
.rowsCap(10)
.ordering(function (d) { return -d.value })
.xAxis().ticks(5);
}
I found the answer. I made an array to store the charts.
rowCharts = [];
for(var i = 0; i < checkedValues.length; i++){
var chartId = "rowchart_" + checkedValues[i];
$(".rowCharts").append("<div id='" + chartId + "' class='col-sm-12 col-md-12 col-lg-5'></div>");
var chart = dc.rowChart("#" + chartId);
var dimension = ndx.dimension(function (d) { return d[checkedValues[i]]; });
var group = dimension.group();
rowCharts.push(chart);
chart
.width(400)
.height(300)
.margins({ top: 20, left: 10, right: 10, bottom: 20 })
.dimension(dimension)
.group(group)
.label(function (d) {
if (chart.hasFilter() && !chart.hasFilter(d.key)) {
return d.key + '(0%)';
}
var label = d.key;
if (all.value()) {
label += '(' + (d.value / all.value() * 100).toFixed(2) + '%)';
}
return label;
})
.title(function (d) { return d.value; })
.on('filtered.monitor', chartFilteredCallback)
.elasticX(true)
.rowsCap(10)
.ordering(function (d) { return -d.value })
.xAxis().ticks(5);
$("#" + chartId).html('<strong>' + checkedValues[i] + '</strong><span class="reset" style="display: none;">Selected: <span class="filter"></span></span><a class="reset" href="javascript:redrawAll(rowCharts[' + i + ']);" style="display: none;"> reset</a>');
}

Unexpected d3 v4 tree behaviour

The following d3.js (v4) interactive tree layout I've put together as a proof of concept for a user interface project is not behaving as expected. This is my first d3.js visualisation and I'm still getting my head around all the concepts.
Essentially, clicking any yellow node should generate two yellow child nodes (& links). This works fine when following a left to right, top to bottom click sequence, otherwise it displays unexpected behaviour.
It's probably easiest to run you through an example, so here's a snippet:
var data = {
source: {
type: 'dataSource',
name: 'Data Source',
silos: [
{ name: 'Silo 1', selected: true },
{ name: 'Silo 2', selected: false },
{ name: 'Silo 3', selected: false }
],
union: {
type: 'union',
name: 'Union',
count: null,
cardinalities: [
{ type: 'cardinality', positive: false, name: 'Falsey', count: 40, cardinalities: [] },
{ type: 'cardinality', positive: true, name: 'Truthy', count: 60, cardinalities: [] }
]
}
}
}
// global variables
var containerPadding = 20;
var container = d3.select('#container').style('padding', containerPadding + 'px'); // contains the structured search svg
var svg = container.select('svg'); // the canvas that displays the structured search
var group = svg.append('g'); // contains the tree elements (nodes & links)
var nodeWidth = 40, nodeHeight = 30, nodeCornerRadius = 3, verticalNodeSeparation = 150, transitionDuration = 600;
var tree = d3.tree().nodeSize([nodeWidth, nodeHeight]);
var source;
function nodeClicked(d) {
source = d;
switch (d.data.type) {
case 'dataSource':
// todo: show the data source popup and update the selected values
d.data.silos[0].selected = !d.data.silos[0].selected;
break;
default:
// todo: show the operation popup and update the selected values
if (d.data.cardinalities && d.data.cardinalities.length) {
d.data.cardinalities.splice(-2, 2);
}
else {
d.data.cardinalities.push({ type: 'cardinality', positive: false, name: 'F ' + (new Date()).getSeconds(), count: 40, cardinalities: [] });
d.data.cardinalities.push({ type: 'cardinality', positive: true, name: 'T ' + (new Date()).getSeconds(), count: 60, cardinalities: [] });
}
break;
}
render();
}
function renderLink(source, destination) {
var x = destination.x + nodeWidth / 2;
var y = destination.y;
var px = source.x + nodeWidth / 2;
var py = source.y + nodeHeight;
return 'M' + x + ',' + y
+ 'C' + x + ',' + (y + py) / 2
+ ' ' + x + ',' + (y + py) / 2
+ ' ' + px + ',' + py;
}
function render() {
// map the data source to a heirarchy that d3.tree requires
// d3.tree instance needs the data structured in a specific way to generate the required layout of nodes & links (lines)
var hierarchy = d3.hierarchy(data.source, function (d) {
switch (d.type) {
case 'dataSource':
return d.silos.some(function (e) { return e.selected; }) ? [d.union] : undefined;
default:
return d.cardinalities;
}
});
// set the layout parameters (all required for resizing)
var containerBoundingRect = container.node().getBoundingClientRect();
var width = containerBoundingRect.width - containerPadding * 2;
var height = verticalNodeSeparation * hierarchy.height;
svg.transition().duration(transitionDuration).attr('width', width).attr('height', height + nodeHeight);
tree.size([width - nodeWidth, height]);
// tree() assigns the (x, y) coords, depth, etc, to the nodes in the hierarchy
tree(hierarchy);
// get the descendants
var descendants = hierarchy.descendants();
// store previous position for transitioning
descendants.forEach(function (d) {
d.x0 = d.x;
d.y0 = d.y;
});
// ensure source is set when rendering for the first time (hierarch is the root, same as descendants[0])
source = source || hierarchy;
// render nodes
var nodesUpdate = group.selectAll('.node').data(descendants);
var nodesEnter = nodesUpdate.enter()
.append('g')
.attr('class', 'node')
.attr('transform', 'translate(' + source.x0 + ',' + source.y0 + ')')
.style('opacity', 0)
.on('click', nodeClicked);
nodesEnter.append('rect')
.attr('rx', nodeCornerRadius)
.attr('width', nodeWidth)
.attr('height', nodeHeight)
.attr('class', function (d) { return 'box ' + d.data.type; });
nodesEnter.append('text')
.attr('dx', nodeWidth / 2 + 5)
.attr('dy', function (d) { return d.parent ? -5 : nodeHeight + 15; })
.text(function (d) { return d.data.name; });
nodesUpdate
.merge(nodesEnter)
.transition().duration(transitionDuration)
.attr('transform', function (d) { return 'translate(' + d.x + ',' + d.y + ')'; })
.style('opacity', 1);
nodesUpdate.exit().transition().duration(transitionDuration)
.attr('transform', function (d) { return 'translate(' + source.x + ',' + source.y + ')'; })
.style('opacity', 0)
.remove();
// render links
var linksUpdate = group.selectAll('.link').data(descendants.slice(1));
var linksEnter = linksUpdate.enter()
.append('path')
.attr('class', 'link')
.classed('falsey', function (d) { return d.data.positive === false })
.classed('truthy', function (d) { return d.data.positive === true })
.attr('d', function (d) { var o = { x: source.x0, y: source.y0 }; return renderLink(o, o); })
.style('opacity', 0);
linksUpdate
.merge(linksEnter)
.transition().duration(transitionDuration)
.attr('d', function (d) { return renderLink({ x: d.parent.x, y: d.parent.y }, d); })
.style('opacity', 1);
linksUpdate.exit()
.transition().duration(transitionDuration)
.attr('d', function (d) { var o = { x: source.x, y: source.y }; return renderLink(o, o); })
.style('opacity', 0)
.remove();
}
window.addEventListener('resize', render); // todo: use requestAnimationFrame (RAF) for this
render();
.link {
fill:none;
stroke:#555;
stroke-opacity:0.4;
stroke-width:1.5px
}
.truthy {
stroke:green
}
.falsey {
stroke:red
}
.box {
stroke:black;
stroke-width:1;
cursor:pointer
}
.dataSource {
fill:blue
}
.union {
fill:orange
}
.cardinality {
fill:yellow
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="container" style="background-color:gray">
<svg style="background-color:#fff" width="0" height="0"></svg>
</div>
If you click on the Falsey node then the Truthy node, you'll see two child nodes appear beneath each, as expected. However, if you click on the Truthy node first, when you then click the Falsey node, you'll see that the Truthy child nodes move under Falsey, and the Falsey child nodes move under Truthy. Plus, the child nodes beneath Falsey and Truthy are actually the same two nodes, even though the underlying data is different.
I've confirmed that the data object is correctly structured after creating the children. From what I can see, the d3.hierarchy() and d3.tree() methods are working correctly, so I'm assuming that there's an issue with the way I'm constructing the selections.
Hopefully someone can spot the problem.
A second issue that may be related to the first is: Clicking Falsey or Truthy a second time should cause the child nodes (& links) to transition back to the parent node, but it does not track the parent's position. Hopefully someone can spot the issue here too.
Thanks!
It seems to me that you need a key function when you join your data:
If a key function is not specified, then the first datum in data is assigned to the first selected element, the second datum to the second selected element, and so on. A key function may be specified to control which datum is assigned to which element, replacing the default join-by-index.
So, this should be your data binding selection:
var nodesUpdate = group.selectAll('.node')
.data(descendants, function(d){ return d.data.name});
Check the snippet:
var data = {
source: {
type: 'dataSource',
name: 'Data Source',
silos: [
{ name: 'Silo 1', selected: true },
{ name: 'Silo 2', selected: false },
{ name: 'Silo 3', selected: false }
],
union: {
type: 'union',
name: 'Union',
count: null,
cardinalities: [
{ type: 'cardinality', positive: false, name: 'Falsey', count: 40, cardinalities: [] },
{ type: 'cardinality', positive: true, name: 'Truthy', count: 60, cardinalities: [] }
]
}
}
}
// global variables
var containerPadding = 20;
var container = d3.select('#container').style('padding', containerPadding + 'px'); // contains the structured search svg
var svg = container.select('svg'); // the canvas that displays the structured search
var group = svg.append('g'); // contains the tree elements (nodes & links)
var nodeWidth = 40, nodeHeight = 30, nodeCornerRadius = 3, verticalNodeSeparation = 150, transitionDuration = 600;
var tree = d3.tree().nodeSize([nodeWidth, nodeHeight]);
var source;
function nodeClicked(d) {
source = d;
switch (d.data.type) {
case 'dataSource':
// todo: show the data source popup and update the selected values
d.data.silos[0].selected = !d.data.silos[0].selected;
break;
default:
// todo: show the operation popup and update the selected values
if (d.data.cardinalities && d.data.cardinalities.length) {
d.data.cardinalities.splice(-2, 2);
}
else {
d.data.cardinalities.push({ type: 'cardinality', positive: false, name: 'F ' + (new Date()).getSeconds(), count: 40, cardinalities: [] });
d.data.cardinalities.push({ type: 'cardinality', positive: true, name: 'T ' + (new Date()).getSeconds(), count: 60, cardinalities: [] });
}
break;
}
render();
}
function renderLink(source, destination) {
var x = destination.x + nodeWidth / 2;
var y = destination.y;
var px = source.x + nodeWidth / 2;
var py = source.y + nodeHeight;
return 'M' + x + ',' + y
+ 'C' + x + ',' + (y + py) / 2
+ ' ' + x + ',' + (y + py) / 2
+ ' ' + px + ',' + py;
}
function render() {
// map the data source to a heirarchy that d3.tree requires
// d3.tree instance needs the data structured in a specific way to generate the required layout of nodes & links (lines)
var hierarchy = d3.hierarchy(data.source, function (d) {
switch (d.type) {
case 'dataSource':
return d.silos.some(function (e) { return e.selected; }) ? [d.union] : undefined;
default:
return d.cardinalities;
}
});
// set the layout parameters (all required for resizing)
var containerBoundingRect = container.node().getBoundingClientRect();
var width = containerBoundingRect.width - containerPadding * 2;
var height = verticalNodeSeparation * hierarchy.height;
svg.transition().duration(transitionDuration).attr('width', width).attr('height', height + nodeHeight);
tree.size([width - nodeWidth, height]);
// tree() assigns the (x, y) coords, depth, etc, to the nodes in the hierarchy
tree(hierarchy);
// get the descendants
var descendants = hierarchy.descendants();
// store previous position for transitioning
descendants.forEach(function (d) {
d.x0 = d.x;
d.y0 = d.y;
});
// ensure source is set when rendering for the first time (hierarch is the root, same as descendants[0])
source = source || hierarchy;
// render nodes
var nodesUpdate = group.selectAll('.node').data(descendants, function(d){ return d.data.name});
var nodesEnter = nodesUpdate.enter()
.append('g')
.attr('class', 'node')
.attr('transform', 'translate(' + source.x0 + ',' + source.y0 + ')')
.style('opacity', 0)
.on('click', nodeClicked);
nodesEnter.append('rect')
.attr('rx', nodeCornerRadius)
.attr('width', nodeWidth)
.attr('height', nodeHeight)
.attr('class', function (d) { return 'box ' + d.data.type; });
nodesEnter.append('text')
.attr('dx', nodeWidth / 2 + 5)
.attr('dy', function (d) { return d.parent ? -5 : nodeHeight + 15; })
.text(function (d) { return d.data.name; });
nodesUpdate
.merge(nodesEnter)
.transition().duration(transitionDuration)
.attr('transform', function (d) { return 'translate(' + d.x + ',' + d.y + ')'; })
.style('opacity', 1);
nodesUpdate.exit().transition().duration(transitionDuration)
.attr('transform', function (d) { return 'translate(' + source.x + ',' + source.y + ')'; })
.style('opacity', 0)
.remove();
// render links
var linksUpdate = group.selectAll('.link').data(descendants.slice(1));
var linksEnter = linksUpdate.enter()
.append('path')
.attr('class', 'link')
.classed('falsey', function (d) { return d.data.positive === false })
.classed('truthy', function (d) { return d.data.positive === true })
.attr('d', function (d) { var o = { x: source.x0, y: source.y0 }; return renderLink(o, o); })
.style('opacity', 0);
linksUpdate
.merge(linksEnter)
.transition().duration(transitionDuration)
.attr('d', function (d) { return renderLink({ x: d.parent.x, y: d.parent.y }, d); })
.style('opacity', 1);
linksUpdate.exit()
.transition().duration(transitionDuration)
.attr('d', function (d) { var o = { x: source.x, y: source.y }; return renderLink(o, o); })
.style('opacity', 0)
.remove();
}
window.addEventListener('resize', render); // todo: use requestAnimationFrame (RAF) for this
render();
.link {
fill:none;
stroke:#555;
stroke-opacity:0.4;
stroke-width:1.5px
}
.truthy {
stroke:green
}
.falsey {
stroke:red
}
.box {
stroke:black;
stroke-width:1;
cursor:pointer
}
.dataSource {
fill:blue
}
.union {
fill:orange
}
.cardinality {
fill:yellow
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="container" style="background-color:gray">
<svg style="background-color:#fff" width="0" height="0"></svg>
</div>

The bar in the d3 graph is going out of range and i am not able to select it using a brush

I have created a d3 graph to show the event count per day so the xaxis shows date and y axis shows count. I have also included brush event to filter data accordingly. but the last bar is shown outof range and i am not able to select it using brush. Now how can i make it look proper. I am adding an image of it and code too ... Everything is coming fine except the selection. please help me out. thanku :)
var data = document.getElementById('eventCountList').innerHTML;
flights = JSON.parse(data);
// Various formatters.
var formatNumber = d3.format(",d"), formatChange = d3.format("+,d"), formatDate = d3.time
.format("%B %d, %Y"), formatTime = d3.time
.format("%I:%M:%S %p");
// A nest operator, for grouping the flight list.
var nestByDate = d3.nest().key(function(d) {
return d3.time.day(d.date);
});
// A little coercion, since the CSV is untyped.
flights.forEach(function(d, i) {
d.index = i;
console.log(d.eventTimeGmt);
d.date = parseDate(String(d.eventTimeGmt));
});
// Create the crossfilter for the relevant dimensions and groups.
var flight = crossfilter(flights), all = flight.groupAll(), date = flight
.dimension(function(d) {
return d.date;
}), dates = date.group(d3.time.day), second = flight
.dimension(function(d) {
return d.date.getSeconds();
}), seconds = second.group(Math.floor), minute = flight
.dimension(function(d) {
return d.date.getMinutes() + d.date.getSeconds() / 60;
}), minutes = minute.group(Math.floor), hour = flight
.dimension(function(d) {
return d.date.getHours() + d.date.getMinutes() / 60;
}), hours = hour.group(Math.floor);
var charts = [
barChart().dimension(second).group(seconds).x(
d3.scale.linear().domain([ 0, 60 ]).rangeRound(
[ 0, 10 * 60 ])),
barChart().dimension(minute).group(minutes).x(
d3.scale.linear().domain([ 0, 60 ]).rangeRound(
[ 0, 10 * 60 ])),
barChart().dimension(hour).group(hours).x(
d3.scale.linear().domain([ 0, 24 ]).rangeRound(
[ 0, 10 * 60 ])),
barChart()
.dimension(date)
.group(dates)
.round(d3.time.hour.round)
.x(
d3.time
.scale()
.domain(
[
new Date(
flights[flights.length - 1].eventTimeGmt
.substring(
0,
4),
flights[flights.length - 1].eventTimeGmt
.substring(
5,
7) - 1,
flights[flights.length - 1].eventTimeGmt
.substring(
8,
10)),
new Date(
flights[0].eventTimeGmt
.substring(
0,
4),
flights[0].eventTimeGmt
.substring(
5,
7) - 1,
flights[0].eventTimeGmt
.substring(
8,
10)) ])
.rangeRound([ 0, 10 * 60 ])) /*.filter(
[ new Date(2001, 1, 1), new Date(2001, 2, 1) ]) */
];
// Given our array of charts, which we assume are in the same order as the
// .chart elements in the DOM, bind the charts to the DOM and render them.
// We also listen to the chart's brush events to update the display.
var chart = d3.selectAll(".chart").data(charts).each(
function(chart) {
chart.on("brush", renderAll).on("brushend", renderAll);
});
// Render the initial lists.
var list = d3.selectAll(".list").data([ flightList ]);
// Render the total.
d3.selectAll("#total").text(formatNumber(flight.size()));
renderAll();
// Renders the specified chart or list.
function render(method) {
d3.select(this).call(method);
}
// Whenever the brush moves, re-rendering everything.
function renderAll() {
chart.each(render);
list.each(render);
d3.select("#active").text(formatNumber(all.value()));
}
// Like d3.time.format, but faster
//2015-12-28 11:18:32
function parseDate(d) {
return new Date(d.substring(0, 4), d.substring(5, 7) - 1, d
.substring(8, 10), d.substring(11, 13), d.substring(14,
16), d.substring(17, 19));
}
window.filter = function(filters) {
filters.forEach(function(d, i) {
charts[i].filter(d);
});
renderAll();
};
window.reset = function(i) {
charts[i].filter(null);
window.poll.start();
renderAll();
};
function flightList(div) {
var flightsByDate = nestByDate.entries(date.top(40));
div.each(function() {
var date = d3.select(this).selectAll(".date").data(
flightsByDate, function(d) {
return d.key;
});
date.enter().append("div").attr("class", "date").append(
"div").attr("class", "day").text(function(d) {
return formatDate(d.values[0].date);
});
date.exit().remove();
var flight = date.order().selectAll(".flight").data(
function(d) {
return d.values;
}, function(d) {
return d.index;
});
var flightEnter = flight.enter().append("div").attr(
"class", "flight");
flightEnter.append("div").attr("class", "time").text(
function(d) {
return formatTime(d.date);
});
flightEnter.append("div").attr("class", "sourceAddress")
.text(function(d) {
return d.sourceAddress;
});
flightEnter.append("div").attr("class", "destAddress")
.text(function(d) {
return d.destAddress;
});
flight.exit().remove();
flight.order();
});
}
function barChart() {
if (!barChart.id)
barChart.id = 0;
var margin = {
top : 10,
right : 10,
bottom : 20,
left : 40
}, x, y = d3.scale.linear().range([ 100, 0 ]), id = barChart.id++, axis = d3.svg
.axis().orient("bottom"), yAxisLeft = d3.svg.axis()
.scale(y).orient("left"), brush = d3.svg.brush(), brushDirty, dimension, group, round;
function chart(div) {
var width = x.range()[1], height = y.range()[0];
y.domain([ 0, group.top(1)[0].value ]);
div.each(function() {
var div = d3.select(this), g = div.select("g");
// Create the skeletal chart.
if (g.empty()) {
div.select(".title").append("a").attr("href",
"javascript:reset(" + id + ")").attr(
"class", "reset").text("reset").style(
"display", "none");
g = div.append("svg").attr("width",
width + margin.left + margin.right).attr(
"height",
height + margin.top + margin.bottom)
.append("g").attr(
"transform",
"translate(" + margin.left + ","
+ margin.top + ")");
g.append("clipPath").attr("id", "clip-" + id)
.append("rect").attr("width", width).attr(
"height", height);
g.selectAll(".bar").data(
[ "background", "foreground" ]).enter()
.append("path").attr("class", function(d) {
return d + " bar";
}).datum(group.all());
g.selectAll(".foreground.bar").attr("clip-path",
"url(#clip-" + id + ")");
g.append("g").attr("class", "axis").attr(
"transform", "translate(0," + height + ")")
.call(axis);
g.append("g").attr("class", "y axis").attr(
"transform", "translate(" + 0 + ")", 0)
.call(yAxisLeft);
g.append("text").attr(
"transform",
"rotate(-90 " + margin.left / 1.8 + " "
+ height / 2 + ")").style(
"text-anchor", "start")
.style("fill", "red")
.text("no. of events ");
/*
g.append("text")
.attr("transform", "translate(" + (width / 2) + " ," + (height + margin.bottom) + ")")
.style("text-anchor", "middle")
.text("Date"); */
/* g.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 – margin.left)
.attr("x",0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("no. of events"); */
// Initialize the brush component with pretty resize handles.
var gBrush = g.append("g").attr("class", "brush")
.call(brush);
gBrush.selectAll("rect").attr("height", height);
gBrush.selectAll(".resize").append("path").attr(
"d", resizePath);
}
// Only redraw the brush if set externally.
if (brushDirty) {
brushDirty = false;
g.selectAll(".brush").call(brush);
div.select(".title a").style("display",
brush.empty() ? "none" : null);
if (brush.empty()) {
g.selectAll("#clip-" + id + " rect").attr("x",
0).attr("width", width);
} else {
var extent = brush.extent();
g.selectAll("#clip-" + id + " rect").attr("x",
x(extent[0])).attr("width",
x(extent[1]) - x(extent[0]));
}
}
g.selectAll(".bar").attr("d", barPath);
});
function barPath(groups) {
var path = [], i = -1, n = groups.length, d;
while (++i < n) {
d = groups[i];
path.push("M", x(d.key), ",", height, "V",
y(d.value), "h9V", height);
}
return path.join("");
}
function resizePath(d) {
var e = +(d == "e"), x = e ? 1 : -1, y = height / 3;
return "M" + (.5 * x) + "," + y + "A6,6 0 0 " + e + " "
+ (6.5 * x) + "," + (y + 6) + "V" + (2 * y - 6)
+ "A6,6 0 0 " + e + " " + (.5 * x) + ","
+ (2 * y) + "Z" + "M" + (2.5 * x) + ","
+ (y + 8) + "V" + (2 * y - 8) + "M" + (4.5 * x)
+ "," + (y + 8) + "V" + (2 * y - 8);
}
}
brush.on("brushstart.chart", function() {
window.poll.stop();
var div = d3.select(this.parentNode.parentNode.parentNode);
div.select(".title a").style("display", null);
});
brush.on("brush.chart",
function() {
var g = d3.select(this.parentNode), extent = brush
.extent();
if (round)
g.select(".brush").call(
brush
.extent(extent = extent
.map(round)))
.selectAll(".resize").style("display",
null);
g.select("#clip-" + id + " rect").attr("x",
x(extent[0])).attr("width",
x(extent[1]) - x(extent[0]));
dimension.filterRange(extent);
});
brush.on("brushend.chart", function() {
if (brush.empty()) {
var div = d3
.select(this.parentNode.parentNode.parentNode);
div.select(".title a").style("display", "none");
div.select("#clip-" + id + " rect").attr("x", null)
.attr("width", "120%");
dimension.filterAll();
}
});
chart.margin = function(_) {
if (!arguments.length)
return margin;
margin = _;
return chart;
};
chart.x = function(_) {
if (!arguments.length)
return x;
x = _;
axis.scale(x);
brush.x(x);
return chart;
};
chart.y = function(_) {
if (!arguments.length)
return y;
y = _;
yAxisLeft.scale(y);
return chart;
};
chart.dimension = function(_) {
if (!arguments.length)
return dimension;
dimension = _;
return chart;
};
chart.filter = function(_) {
if (_) {
brush.extent(_);
dimension.filterRange(_);
} else {
brush.clear();
dimension.filterAll();
}
brushDirty = true;
return chart;
};
chart.group = function(_) {
if (!arguments.length)
return group;
group = _;
return chart;
};
chart.round = function(_) {
if (!arguments.length)
return round;
round = _;
return chart;
};
return d3.rebind(chart, brush, "on");
}
};
I have added a day as an offset. that solved my problem.below is my code that solved my problem.....
d3.time.day.offset(new Date(flights[0].eventTimeGmt.substring(0,4)
,flights[0].eventTimeGmt.substring(5,7)-1,
flights[0].eventTimeGmt.substring(8,10)), 1)

Not able to render/plot Composite chart dc.js

This is a piece of a code in which I am able to get data correctly.But the issue is i am not able to plot chart.
Please refer this link to find chart image which I am getting
// This is where the composite chart code is starting.
CompositeChart: function (chartConfig, chartManager, meta) {
var data = meta.data;
var panelDetails = this.panelDetails(chartConfig.chartName);
var chart = dc.compositeChart(panelDetails.id);
var yAxisGroup = [];
if (chartConfig.xDomainType == "Date" && !(data[0][meta.header[chartConfig.x]] instanceof Date)) {
var dateFormater = d3.time.format("%Y-%m-%d");
data.forEach(function (d, i) {
d[meta.header[chartConfig.x]] = dateFormater.parse(d[meta.header[chartConfig.x]]);
});
}
var xAxis = chartManager.cf.dimension(function (d) {
var val = d[meta.header[chartConfig.x]];
var result = (chartManager.dimValues[chartConfig.x]) ? chartManager.dimValues[chartConfig.x][val] : val;
return result;
});
yAxisGroup = (chartConfig.y);
var temp = yAxisGroup[0];
var temp1 = yAxisGroup[1];
var yDim1 = xAxis.group().reduceSum(function (d) {
var val = d[meta.header[temp]];
var result = (chartManager.dimValues[temp]) ? chartManager.dimValues[temp][val] : val;
return result;
});
var yDim2 = xAxis.group().reduceSum(function (d) {
var val = d[meta.header[temp1]];
var result = (chartManager.dimValues[temp1]) ? chartManager.dimValues[temp1][val] : val;
return result;
});
chart
.width(panelDetails.width)
.height(panelDetails.height)
.margins({
top: 35,
right: 55,
bottom: 30,
left: 45
})
.renderHorizontalGridLines(true)
.shareColors(true)
.compose([
dc.lineChart(chart)
.dimension(xAxis)
.dashStyle([2, 2])
.group(yDim1),
dc.lineChart(chart)
.dimension(xAxis)
.dashStyle([5, 5])
.group(yDim2)
])
.brushOn(false);
this.addXDomain(chartConfig, meta, chart, xAxis);
chart.render();
this.chartStore[chartConfig.chartName] = chart;
},
When I debug the data is fine. I am able to print it on console.But no luck on Chart
I am sharing data of X-Axis while grouping Y-Axis data. Any Solutions?
Note: Console is showing no error or exception.
Can any body help me find the parent chart? Guess that is the problem but I am not sure I need to pass the parent chat in compose function.
Fiddle Link
It's hard to know for sure, but you might try putting your xaxis on the composite chart rather than each composed chart.
Here is a working chart that I've built:
var actuals = dc.barChart(compositeChart)
.gap(65)
.group(group)
.valueAccessor(function (d) {
return d.value.Actual;
});
var budgets = dc.barChart(compositeChart)
.gap(65)
.group(group)
.valueAccessor(function (d) {
return d.value.Budget;
});
compositeChart
.width(1000)
.height(300)
.margins({top: 20, right: 45, bottom: 40, left: 50})
.dimension(monthDimension)
.group(group)
.elasticY(true)
.x(d3.time.scale().domain(timeExtent))
.xUnits(d3.time.months)
.round(d3.time.month.round)
.renderHorizontalGridLines(true)
.compose([budgets, actuals])
.brushOn(true)
.title(function (d) {
return [
"Engagement: " + d.value.engagement,
"Amount:" + d.value.amount
].join("\n");
})
.renderlet(function (chart) {
chart.selectAll("g._1").attr("transform", "translate(" + 20 + ", 0)");
chart.selectAll("g._0").attr("transform", "translate(" + 1 + ", 0)");
});
Note that the budget and actual charts do not define an x or xUnits method. This is left to the composite chart. Not sure that whether this is required or not, but I imagine you need to define x for the composite chart.

Resources