Custom ticks, values, and format - javascript

I'm trying to create two custom ticks on the far left and right side of my horizontal axis in D3.
The labels I want to use for my ticks do not come from any of my data — I just want to add in "Q1 2015" and "Q2 2015" in place of whatever it would normally pull in from my xScale.
Here's a fiddle and the code I'm using to construct my xAxis.
Right now there is nothing showing up on my axis for ticks — the data seems to be returning from the tickFormat function but it doesn't show up, so I'm confused at this point. I can't figure out where I am going wrong.
var xAxis = d3.svg.axis().orient("bottom")
// .ticks(2)
.tickValues([2015, 2016])
.tickFormat(function(d) {
console.log("Q1 " + d);
return "Q1" + d;
})
.outerTickSize(0);

The issue in here is that you are setting numeric tick values
.tickValues([2015, 2016])
which will basically try to create a tick with the 2015 and 2016 values.
Since your xAxis scale is set to use the following:
xScale.domain(d3.extent(data, function (d){ return d[xColumn]; }));
which was only returning a month from your data object, so the ticks were rendering but outside of your viewport due to the difference of values [monthValue] vs [yearValue].
If you are going to use a time scale you need to give a proper input to the domain of that scale.
// X Axis Scale
var xScale = d3.time.scale().range([0, innerWidth]);
// Axis Function
var xAxis = d3.svg.axis()
.orient("bottom")
.scale(xScale)
.tickFormat(function(d) { // Getting the value of the tick :)
return "Q1:" + mS[d.getMonth()];
});
/*
* Setting the domain of the x scale with a correct Date value
*
* We use nested[0].values which iterates over the values of just
* one hotel and the corresponding dates since the dates are the
* same for the remaining hotels.
*/
xScale.domain(d3.extent(nested[0].values, function (d){
var f = new Date('2012', (d[xColumn] - 1), '01');
return f;
}));
and dont forget to use the same approach in your area function.
var area = d3.svg.area()
.x(function(d) {
return xScale( new Date('2012', (d[xColumn] - 1), '01'))
})
.y0(function(d) { return yScale(d.y0); })
.y1(function(d) { return yScale(d.y0 + d.y); });
var mS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'June', 'July', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec'];
var outerWidth = 1000;
var outerHeight = 250;
var margin = { left: 55, top: 5, right: 100, bottom: 60 };
var xColumn = "month";
var yColumn = "tours";
var colorColumn = "name";
var areaColumn = colorColumn;
var xAxisLabelText = "Month";
var xAxisLabelOffset = 48;
var innerWidth = outerWidth - margin.left - margin.right;
var innerHeight = outerHeight - margin.top - margin.bottom;
var svg = d3.select("body").append("svg")
.attr("width", outerWidth)
.attr("height", outerHeight);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var xAxisG = g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + innerHeight + ")")
var xAxisLabel = xAxisG.append("text")
.style("text-anchor", "middle")
.attr("transform", "translate(" + (innerWidth / 2) + "," + xAxisLabelOffset + ")")
.attr("class", "label")
.text(xAxisLabelText);
var colorLegendG = svg.append("g")
.attr("class", "color-legend")
.attr("transform", "translate(403, 5)");
var xScale = d3.time.scale().range([0, innerWidth]);
var yScale = d3.scale.linear().range([innerHeight, 0]);
var colorScale = d3.scale.category10();
var xAxis = d3.svg.axis().orient("bottom").scale(xScale)
.tickFormat(function(d, i, e) {
return "Q1:" + mS[d.getMonth()];
});
var stack = d3.layout.stack()
.y(function (d){ return d[yColumn]; })
.values(function (d){ return d.values; });
var area = d3.svg.area()
.x(function(d) {
return xScale( new Date('2012', (d[xColumn] - 1), '01'))
})
.y0(function(d) { return yScale(d.y0); })
.y1(function(d) { return yScale(d.y0 + d.y); });
function render(data){
var nested = d3.nest()
.key(function (d){ return d[areaColumn]; })
.entries(data);
colorScale.domain(nested.map(function (d){ return d.key; }));
// Reversed the order here so the order matches between legend & areas.
var layers = stack(nested.reverse());
xScale.domain(d3.extent(nested[0].values, function (d){
var f = new Date('2012', (d[xColumn] - 1), '01');
return f;
}));
yScale.domain([0,
d3.max(layers, function (layer){
return d3.max(layer.values, function (d){
return d.y0 + d.y;
});
})
]);
var total = d3.sum(layers, function(layer) {
return d3.sum(layer.values, function(d) {
return d.tours;
});
});
var paths = g.selectAll(".chart-area").data(layers);
paths.enter().append("path").attr("class", "chart-line");
paths.exit().remove();
paths.attr("d", function (d){ return area(d.values); })
.attr("fill", function (d){ return colorScale(d.key); });
xAxisG.call(xAxis);
svg.append("g")
.attr("class", "legend")
.selectAll("text")
.data(layers)
.enter().append("text")
.text(function(d) { return (d.key) + ' (' + d.key + ')'; })
.attr('fill', function(d) { return colorScale(d.key); })
.attr('y', function(d, i) { return 20 * (i + 1); })
.attr('x', "0");
}
var toursByHotel = [
{
"name": "Marriott",
"month": 1,
"tours": 10
},
{
"name": "Marriott",
"month": 2,
"tours": 15
},
{
"name": "Marriott",
"month": 3,
"tours": 8
},
{
"name": "Marriott",
"month": 4,
"tours": 12
},
{
"name": "Marriott",
"month": 5,
"tours": 18
},
{
"name": "Marriott",
"month": 6,
"tours": 25
},
{
"name": "Marriott",
"month": 7,
"tours": 40
},
{
"name": "Marriott",
"month": 8,
"tours": 33
},
{
"name": "Marriott",
"month": 9,
"tours": 25
},
{
"name": "Marriott",
"month": 10,
"tours": 21
},
{
"name": "Marriott",
"month": 11,
"tours": 18
},
{
"name": "Marriott",
"month": 12,
"tours": 14
},
{
"name": "Springhill",
"month": 1,
"tours": 10
},
{
"name": "Springhill",
"month": 2,
"tours": 15
},
{
"name": "Springhill",
"month": 3,
"tours": 8
},
{
"name": "Springhill",
"month": 4,
"tours": 12
},
{
"name": "Springhill",
"month": 5,
"tours": 18
},
{
"name": "Springhill",
"month": 6,
"tours": 25
},
{
"name": "Springhill",
"month": 7,
"tours": 40
},
{
"name": "Springhill",
"month": 8,
"tours": 33
},
{
"name": "Springhill",
"month": 9,
"tours": 25
},
{
"name": "Springhill",
"month": 10,
"tours": 21
},
{
"name": "Springhill",
"month": 11,
"tours": 18
},
{
"name": "Springhill",
"month": 12,
"tours": 14
},
{
"name": "Residence",
"month": 1,
"tours": 10
},
{
"name": "Residence",
"month": 2,
"tours": 15
},
{
"name": "Residence",
"month": 3,
"tours": 8
},
{
"name": "Residence",
"month": 4,
"tours": 12
},
{
"name": "Residence",
"month": 5,
"tours": 18
},
{
"name": "Residence",
"month": 6,
"tours": 25
},
{
"name": "Residence",
"month": 7,
"tours": 40
},
{
"name": "Residence",
"month": 8,
"tours": 33
},
{
"name": "Residence",
"month": 9,
"tours": 25
},
{
"name": "Residence",
"month": 10,
"tours": 21
},
{
"name": "Residence",
"month": 11,
"tours": 18
},
{
"name": "Residence",
"month": 12,
"tours": 14
},
{
"name": "Courtyard",
"month": 1,
"tours": 10
},
{
"name": "Courtyard",
"month": 2,
"tours": 15
},
{
"name": "Courtyard",
"month": 3,
"tours": 8
},
{
"name": "Courtyard",
"month": 4,
"tours": 12
},
{
"name": "Courtyard",
"month": 5,
"tours": 18
},
{
"name": "Courtyard",
"month": 6,
"tours": 25
},
{
"name": "Courtyard",
"month": 7,
"tours": 40
},
{
"name": "Courtyard",
"month": 8,
"tours": 33
},
{
"name": "Courtyard",
"month": 9,
"tours": 25
},
{
"name": "Courtyard",
"month": 10,
"tours": 21
},
{
"name": "Courtyard",
"month": 11,
"tours": 18
},
{
"name": "Courtyard",
"month": 12,
"tours": 14
},
{
"name": "Renaissance",
"month": 1,
"tours": 10
},
{
"name": "Renaissance",
"month": 2,
"tours": 15
},
{
"name": "Renaissance",
"month": 3,
"tours": 8
},
{
"name": "Renaissance",
"month": 4,
"tours": 12
},
{
"name": "Renaissance",
"month": 5,
"tours": 18
},
{
"name": "Renaissance",
"month": 6,
"tours": 25
},
{
"name": "Renaissance",
"month": 7,
"tours": 40
},
{
"name": "Renaissance",
"month": 8,
"tours": 33
},
{
"name": "Renaissance",
"month": 9,
"tours": 25
},
{
"name": "Renaissance",
"month": 10,
"tours": 21
},
{
"name": "Renaissance",
"month": 11,
"tours": 18
},
{
"name": "Renaissance",
"month": 12,
"tours": 14
}
];
render(toursByHotel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Related

Selection from JSON

I recently started learning JavaScript and faced a task that I can't complete in any way, every time I get the wrong data that I need
There is an object that contains data on banking transactions, I need to make a selection and form a new object using filter, map or reduce:
We assume that the initial balance on the card = 0.
Output the TOP 3 months with the largest number of operations by month.
Formalize it as a task_1(arr) function, where arr is the source array with data
for all months.
Output format:
[
{ year: 2019, month: 11, opsCount: 27 },
{ year: 2019, month: 10, opsCount: 26 },
{ year: 2019, month: 8, opsCount: 24 }
]
Output statistics for the end of the specified month.
monthmonthBalance - own balance by month = The amount of all
deposits minus the amount of all debits
montWithdrawal – total cash withdrawal amount
withdrawalRate – the share of the total amount of debits from the total amount of
deposits per month.
rank – user status calculated by the formula:
Gold if withdrawalRate < 0.15.
Silver if withdrawalRate < 0.3.
Bronze in all other cases.
Formalize it as a task_2(year, month, arr) function, where year, month is the desired one
month, and arr is the original array with all the data by month.
Output format:
{
date: '2019-01-31’,
monthBalance: 3829,
monthWithrawal: 33800,
withdrawalRate: 0.3133
rank: 'Bronze’
}
Calculate statistics from task 2 for all months of the period.
Add a field to each month
totalBalance - cumulative balance = Own balance for the
month + Balance for the cumulative balance for the previous month.
Output format:
[
{
date: '2019-01-31’,
monthBalance: 3829,
totalBalance: 3829,
monthWithrawal: 33800,
withdrawalRate: 0.3133
rank: ’Bronze'
},
...
]
Formalize it as a task_3(arr) function, where arr is the source array with data.
JSON with data:
[
{ "year": 2019, "month": 1, "day": 1, "type": "replenishment", "amount": 79817 },
{ "year": 2019, "month": 1, "day": 3, "type": "payment", "amount": 11334 },
{ "year": 2019, "month": 1, "day": 5, "type": "withdrawal", "amount": 26700 },
{ "year": 2019, "month": 1, "day": 5, "type": "payment", "amount": 15475 },
{ "year": 2019, "month": 1, "day": 6, "type": "payment", "amount": 4818 },
{ "year": 2019, "month": 1, "day": 6, "type": "payment", "amount": 1893 },
{ "year": 2019, "month": 1, "day": 7, "type": "payment", "amount": 3844 },
{ "year": 2019, "month": 1, "day": 7, "type": "withdrawal", "amount": 3100 },
{ "year": 2019, "month": 1, "day": 7, "type": "payment", "amount": 3230 },
{ "year": 2019, "month": 1, "day": 7, "type": "payment", "amount": 2427 },
{ "year": 2019, "month": 1, "day": 9, "type": "replenishment", "amount": 15835 },
{ "year": 2019, "month": 1, "day": 10, "type": "payment", "amount": 9670 },
{ "year": 2019, "month": 1, "day": 11, "type": "payment", "amount": 582 },
{ "year": 2019, "month": 1, "day": 11, "type": "withdrawal", "amount": 1100 },
{ "year": 2019, "month": 1, "day": 11, "type": "replenishment", "amount": 5971 },
{ "year": 2019, "month": 1, "day": 12, "type": "payment", "amount": 173 },
{ "year": 2019, "month": 1, "day": 14, "type": "withdrawal", "amount": 1500 },
{ "year": 2019, "month": 1, "day": 14, "type": "payment", "amount": 3641 },
{ "year": 2019, "month": 1, "day": 16, "type": "payment", "amount": 4669 },
{ "year": 2019, "month": 1, "day": 18, "type": "payment", "amount": 2460 },
{ "year": 2019, "month": 1, "day": 19, "type": "payment", "amount": 1307 },
{ "year": 2019, "month": 1, "day": 20, "type": "withdrawal", "amount": 1400 },
{ "year": 2019, "month": 1, "day": 21, "type": "payment", "amount": 952 },
{ "year": 2019, "month": 1, "day": 21, "type": "payment", "amount": 561 },
{ "year": 2019, "month": 1, "day": 21, "type": "replenishment", "amount": 6236 },
{ "year": 2019, "month": 1, "day": 22, "type": "payment", "amount": 888 },
{ "year": 2019, "month": 1, "day": 22, "type": "payment", "amount": 2306 },
{ "year": 2019, "month": 2, "day": 1, "type": "replenishment", "amount": 84839 },
{ "year": 2019, "month": 2, "day": 1, "type": "withdrawal", "amount": 27700 },
{ "year": 2019, "month": 2, "day": 1, "type": "payment", "amount": 11145 },
{ "year": 2019, "month": 2, "day": 2, "type": "payment", "amount": 4075 },
{ "year": 2019, "month": 2, "day": 4, "type": "withdrawal", "amount": 10900 },
{ "year": 2019, "month": 2, "day": 6, "type": "payment", "amount": 10537 },
{ "year": 2019, "month": 2, "day": 6, "type": "payment", "amount": 6001 },
{ "year": 2019, "month": 2, "day": 7, "type": "withdrawal", "amount": 7300 },
{ "year": 2019, "month": 2, "day": 10, "type": "withdrawal", "amount": 1500 },
{ "year": 2019, "month": 2, "day": 10, "type": "payment", "amount": 3584 },
{ "year": 2019, "month": 2, "day": 11, "type": "payment", "amount": 701 },
{ "year": 2019, "month": 3, "day": 1, "type": "replenishment", "amount": 84771 },
{ "year": 2019, "month": 3, "day": 3, "type": "withdrawal", "amount": 22700 },
{ "year": 2019, "month": 3, "day": 5, "type": "payment", "amount": 12352 },
{ "year": 2019, "month": 3, "day": 8, "type": "payment", "amount": 2795 },
{ "year": 2019, "month": 3, "day": 11, "type": "withdrawal", "amount": 16600 },
{ "year": 2019, "month": 3, "day": 11, "type": "replenishment", "amount": 4141 },
{ "year": 2019, "month": 3, "day": 11, "type": "payment", "amount": 9854 },
{ "year": 2019, "month": 3, "day": 13, "type": "withdrawal", "amount": 1200 },
{ "year": 2019, "month": 3, "day": 14, "type": "payment", "amount": 11573 },
{ "year": 2019, "month": 3, "day": 14, "type": "payment", "amount": 5138 },
{ "year": 2019, "month": 3, "day": 15, "type": "payment", "amount": 731 },
{ "year": 2019, "month": 3, "day": 17, "type": "payment", "amount": 5053 },
{ "year": 2019, "month": 3, "day": 19, "type": "withdrawal", "amount": 400 },
{ "year": 2019, "month": 3, "day": 20, "type": "payment", "amount": 1745 },
{ "year": 2019, "month": 3, "day": 20, "type": "payment", "amount": 602 },
{ "year": 2019, "month": 3, "day": 21, "type": "payment", "amount": 178 },
{ "year": 2019, "month": 3, "day": 22, "type": "payment", "amount": 353 },
{ "year": 2019, "month": 3, "day": 23, "type": "payment", "amount": 837 },
{ "year": 2019, "month": 3, "day": 23, "type": "payment", "amount": 330 },
{ "year": 2019, "month": 3, "day": 23, "type": "payment", "amount": 799 },
{ "year": 2019, "month": 3, "day": 24, "type": "payment", "amount": 294 },
{ "year": 2019, "month": 3, "day": 24, "type": "payment", "amount": 260 },
{ "year": 2019, "month": 3, "day": 25, "type": "withdrawal", "amount": 200 },
{ "year": 2019, "month": 4, "day": 1, "type": "replenishment", "amount": 88656 },
{ "year": 2019, "month": 4, "day": 4, "type": "payment", "amount": 37852 },
{ "year": 2019, "month": 4, "day": 7, "type": "payment", "amount": 9365 },
{ "year": 2019, "month": 4, "day": 8, "type": "payment", "amount": 16701 },
{ "year": 2019, "month": 4, "day": 11, "type": "payment", "amount": 8979 },
{ "year": 2019, "month": 4, "day": 11, "type": "payment", "amount": 1971 },
{ "year": 2019, "month": 4, "day": 13, "type": "payment", "amount": 1261 },
{ "year": 2019, "month": 4, "day": 13, "type": "withdrawal", "amount": 800 },
{ "year": 2019, "month": 4, "day": 15, "type": "payment", "amount": 5553 },
{ "year": 2019, "month": 4, "day": 17, "type": "payment", "amount": 2593 },
{ "year": 2019, "month": 4, "day": 18, "type": "replenishment", "amount": 6915 },
{ "year": 2019, "month": 4, "day": 19, "type": "replenishment", "amount": 17647 },
{ "year": 2019, "month": 4, "day": 21, "type": "payment", "amount": 13814 },
{ "year": 2019, "month": 4, "day": 22, "type": "payment", "amount": 2707 },
{ "year": 2019, "month": 4, "day": 22, "type": "withdrawal", "amount": 1300 },
{ "year": 2019, "month": 4, "day": 22, "type": "withdrawal", "amount": 2900 },
{ "year": 2019, "month": 4, "day": 23, "type": "replenishment", "amount": 10709 },
{ "year": 2019, "month": 4, "day": 23, "type": "payment", "amount": 686 },
{ "year": 2019, "month": 4, "day": 23, "type": "withdrawal", "amount": 5100 },
{ "year": 2019, "month": 4, "day": 23, "type": "payment", "amount": 3830 },
{ "year": 2019, "month": 5, "day": 1, "type": "replenishment", "amount": 59877 },
{ "year": 2019, "month": 5, "day": 2, "type": "payment", "amount": 14095 },
{ "year": 2019, "month": 5, "day": 5, "type": "payment", "amount": 10858 },
{ "year": 2019, "month": 5, "day": 8, "type": "payment", "amount": 9412 },
{ "year": 2019, "month": 5, "day": 11, "type": "replenishment", "amount": 6892 },
{ "year": 2019, "month": 5, "day": 11, "type": "payment", "amount": 17541 },
{ "year": 2019, "month": 5, "day": 11, "type": "payment", "amount": 2666 },
{ "year": 2019, "month": 5, "day": 11, "type": "payment", "amount": 3935 },
{ "year": 2019, "month": 5, "day": 12, "type": "withdrawal", "amount": 2600 },
{ "year": 2019, "month": 5, "day": 14, "type": "payment", "amount": 2096 },
{ "year": 2019, "month": 5, "day": 14, "type": "replenishment", "amount": 2733 },
{ "year": 2019, "month": 5, "day": 15, "type": "replenishment", "amount": 538 },
{ "year": 2019, "month": 5, "day": 15, "type": "payment", "amount": 5324 },
{ "year": 2019, "month": 5, "day": 15, "type": "payment", "amount": 2490 },
{ "year": 2019, "month": 5, "day": 15, "type": "payment", "amount": 3510 },
{ "year": 2019, "month": 5, "day": 17, "type": "withdrawal", "amount": 300 },
{ "year": 2019, "month": 5, "day": 17, "type": "payment", "amount": 133 },
{ "year": 2019, "month": 6, "day": 1, "type": "replenishment", "amount": 89064 },
{ "year": 2019, "month": 6, "day": 2, "type": "payment", "amount": 7613 },
{ "year": 2019, "month": 6, "day": 2, "type": "payment", "amount": 33742 },
{ "year": 2019, "month": 6, "day": 5, "type": "withdrawal", "amount": 7200 },
{ "year": 2019, "month": 6, "day": 6, "type": "payment", "amount": 15125 },
{ "year": 2019, "month": 6, "day": 9, "type": "payment", "amount": 3379 },
{ "year": 2019, "month": 6, "day": 10, "type": "payment", "amount": 1260 },
{ "year": 2019, "month": 6, "day": 12, "type": "payment", "amount": 11066 },
{ "year": 2019, "month": 6, "day": 12, "type": "replenishment", "amount": 7050 },
{ "year": 2019, "month": 6, "day": 12, "type": "payment", "amount": 7531 },
{ "year": 2019, "month": 6, "day": 13, "type": "payment", "amount": 4776 },
{ "year": 2019, "month": 6, "day": 13, "type": "replenishment", "amount": 4456 },
{ "year": 2019, "month": 6, "day": 14, "type": "replenishment", "amount": 7998 },
{ "year": 2019, "month": 6, "day": 16, "type": "payment", "amount": 2437 },
{ "year": 2019, "month": 6, "day": 16, "type": "replenishment", "amount": 11729 },
{ "year": 2019, "month": 6, "day": 18, "type": "payment", "amount": 11216 },
{ "year": 2019, "month": 6, "day": 19, "type": "payment", "amount": 3420 },
{ "year": 2019, "month": 6, "day": 19, "type": "payment", "amount": 1339 },
{ "year": 2019, "month": 6, "day": 20, "type": "payment", "amount": 5578 },
{ "year": 2019, "month": 6, "day": 21, "type": "withdrawal", "amount": 1600 },
{ "year": 2019, "month": 6, "day": 21, "type": "withdrawal", "amount": 400 },
{ "year": 2019, "month": 7, "day": 1, "type": "replenishment", "amount": 51749 },
{ "year": 2019, "month": 7, "day": 2, "type": "payment", "amount": 2875 },
{ "year": 2019, "month": 7, "day": 2, "type": "payment", "amount": 10315 },
{ "year": 2019, "month": 7, "day": 5, "type": "payment", "amount": 18501 },
{ "year": 2019, "month": 7, "day": 5, "type": "payment", "amount": 12728 },
{ "year": 2019, "month": 7, "day": 7, "type": "payment", "amount": 4505 },
{ "year": 2019, "month": 7, "day": 8, "type": "payment", "amount": 2758 },
{ "year": 2019, "month": 7, "day": 8, "type": "payment", "amount": 60 },
{ "year": 2019, "month": 7, "day": 10, "type": "withdrawal", "amount": 1100 },
{ "year": 2019, "month": 7, "day": 12, "type": "withdrawal", "amount": 1000 },
{ "year": 2019, "month": 7, "day": 13, "type": "payment", "amount": 151 },
{ "year": 2019, "month": 8, "day": 1, "type": "replenishment", "amount": 85156 },
{ "year": 2019, "month": 8, "day": 1, "type": "payment", "amount": 33978 },
{ "year": 2019, "month": 8, "day": 2, "type": "payment", "amount": 6548 },
{ "year": 2019, "month": 8, "day": 3, "type": "payment", "amount": 5909 },
{ "year": 2019, "month": 8, "day": 6, "type": "payment", "amount": 2326 },
{ "year": 2019, "month": 8, "day": 6, "type": "payment", "amount": 17798 },
{ "year": 2019, "month": 8, "day": 9, "type": "replenishment", "amount": 10770 },
{ "year": 2019, "month": 8, "day": 10, "type": "withdrawal", "amount": 7400 },
{ "year": 2019, "month": 8, "day": 12, "type": "payment", "amount": 6065 },
{ "year": 2019, "month": 8, "day": 14, "type": "withdrawal", "amount": 900 },
{ "year": 2019, "month": 8, "day": 14, "type": "withdrawal", "amount": 1400 },
{ "year": 2019, "month": 8, "day": 14, "type": "payment", "amount": 4673 },
{ "year": 2019, "month": 8, "day": 15, "type": "payment", "amount": 960 },
{ "year": 2019, "month": 8, "day": 15, "type": "payment", "amount": 1085 },
{ "year": 2019, "month": 8, "day": 17, "type": "payment", "amount": 3723 },
{ "year": 2019, "month": 8, "day": 17, "type": "payment", "amount": 2522 },
{ "year": 2019, "month": 8, "day": 19, "type": "replenishment", "amount": 2496 },
{ "year": 2019, "month": 8, "day": 20, "type": "payment", "amount": 876 },
{ "year": 2019, "month": 8, "day": 20, "type": "payment", "amount": 2504 },
{ "year": 2019, "month": 8, "day": 21, "type": "payment", "amount": 826 },
{ "year": 2019, "month": 8, "day": 22, "type": "payment", "amount": 768 },
{ "year": 2019, "month": 8, "day": 23, "type": "withdrawal", "amount": 700 },
{ "year": 2019, "month": 8, "day": 23, "type": "payment", "amount": 190 },
{ "year": 2019, "month": 8, "day": 24, "type": "payment", "amount": 235 },
{ "year": 2019, "month": 9, "day": 1, "type": "replenishment", "amount": 95512 },
{ "year": 2019, "month": 9, "day": 3, "type": "payment", "amount": 26758 },
{ "year": 2019, "month": 9, "day": 3, "type": "replenishment", "amount": 8377 },
{ "year": 2019, "month": 9, "day": 4, "type": "payment", "amount": 30865 },
{ "year": 2019, "month": 9, "day": 4, "type": "withdrawal", "amount": 12800 },
{ "year": 2019, "month": 9, "day": 7, "type": "payment", "amount": 10518 },
{ "year": 2019, "month": 9, "day": 8, "type": "payment", "amount": 11007 },
{ "year": 2019, "month": 9, "day": 10, "type": "payment", "amount": 5613 },
{ "year": 2019, "month": 9, "day": 10, "type": "withdrawal", "amount": 1700 },
{ "year": 2019, "month": 9, "day": 12, "type": "payment", "amount": 2237 },
{ "year": 2019, "month": 9, "day": 14, "type": "payment", "amount": 885 },
{ "year": 2019, "month": 9, "day": 14, "type": "payment", "amount": 977 },
{ "year": 2019, "month": 9, "day": 15, "type": "payment", "amount": 766 },
{ "year": 2019, "month": 9, "day": 17, "type": "payment", "amount": 360 },
{ "year": 2019, "month": 9, "day": 18, "type": "payment", "amount": 116 },
{ "year": 2019, "month": 9, "day": 18, "type": "withdrawal", "amount": 200 },
{ "year": 2019, "month": 9, "day": 19, "type": "payment", "amount": 115 },
{ "year": 2019, "month": 9, "day": 20, "type": "payment", "amount": 50 },
{ "year": 2019, "month": 9, "day": 21, "type": "payment", "amount": 32 },
{ "year": 2019, "month": 10, "day": 1, "type": "replenishment", "amount": 90475 },
{ "year": 2019, "month": 10, "day": 1, "type": "replenishment", "amount": 8845 },
{ "year": 2019, "month": 10, "day": 2, "type": "payment", "amount": 7121 },
{ "year": 2019, "month": 10, "day": 3, "type": "payment", "amount": 27955 },
{ "year": 2019, "month": 10, "day": 3, "type": "payment", "amount": 23079 },
{ "year": 2019, "month": 10, "day": 4, "type": "payment", "amount": 5948 },
{ "year": 2019, "month": 10, "day": 7, "type": "withdrawal", "amount": 4400 },
{ "year": 2019, "month": 10, "day": 8, "type": "payment", "amount": 9677 },
{ "year": 2019, "month": 10, "day": 9, "type": "payment", "amount": 3912 },
{ "year": 2019, "month": 10, "day": 9, "type": "replenishment", "amount": 3870 },
{ "year": 2019, "month": 10, "day": 9, "type": "payment", "amount": 6949 },
{ "year": 2019, "month": 10, "day": 10, "type": "withdrawal", "amount": 3400 },
{ "year": 2019, "month": 10, "day": 10, "type": "replenishment", "amount": 7471 },
{ "year": 2019, "month": 10, "day": 10, "type": "payment", "amount": 5962 },
{ "year": 2019, "month": 10, "day": 10, "type": "payment", "amount": 4990 },
{ "year": 2019, "month": 10, "day": 10, "type": "withdrawal", "amount": 3000 },
{ "year": 2019, "month": 10, "day": 11, "type": "withdrawal", "amount": 200 },
{ "year": 2019, "month": 10, "day": 12, "type": "withdrawal", "amount": 1300 },
{ "year": 2019, "month": 10, "day": 13, "type": "payment", "amount": 986 },
{ "year": 2019, "month": 10, "day": 14, "type": "replenishment", "amount": 4225 },
{ "year": 2019, "month": 10, "day": 15, "type": "withdrawal", "amount": 900 },
{ "year": 2019, "month": 10, "day": 17, "type": "payment", "amount": 864 },
{ "year": 2019, "month": 10, "day": 17, "type": "withdrawal", "amount": 1000 },
{ "year": 2019, "month": 10, "day": 18, "type": "payment", "amount": 801 },
{ "year": 2019, "month": 10, "day": 19, "type": "withdrawal", "amount": 300 },
{ "year": 2019, "month": 10, "day": 20, "type": "payment", "amount": 530 },
{ "year": 2019, "month": 11, "day": 1, "type": "replenishment", "amount": 80285 },
{ "year": 2019, "month": 11, "day": 3, "type": "payment", "amount": 38155 },
{ "year": 2019, "month": 11, "day": 6, "type": "payment", "amount": 10260 },
{ "year": 2019, "month": 11, "day": 9, "type": "payment", "amount": 11013 },
{ "year": 2019, "month": 11, "day": 10, "type": "payment", "amount": 1232 },
{ "year": 2019, "month": 11, "day": 12, "type": "withdrawal", "amount": 5100 },
{ "year": 2019, "month": 11, "day": 12, "type": "payment", "amount": 1192 },
{ "year": 2019, "month": 11, "day": 13, "type": "withdrawal", "amount": 4500 },
{ "year": 2019, "month": 11, "day": 14, "type": "replenishment", "amount": 4304 },
{ "year": 2019, "month": 11, "day": 15, "type": "withdrawal", "amount": 700 },
{ "year": 2019, "month": 11, "day": 15, "type": "replenishment", "amount": 15857 },
{ "year": 2019, "month": 11, "day": 17, "type": "payment", "amount": 9134 },
{ "year": 2019, "month": 11, "day": 19, "type": "payment", "amount": 8090 },
{ "year": 2019, "month": 11, "day": 20, "type": "payment", "amount": 2117 },
{ "year": 2019, "month": 11, "day": 20, "type": "withdrawal", "amount": 2700 },
{ "year": 2019, "month": 11, "day": 21, "type": "withdrawal", "amount": 2200 },
{ "year": 2019, "month": 11, "day": 21, "type": "payment", "amount": 258 },
{ "year": 2019, "month": 11, "day": 21, "type": "withdrawal", "amount": 1200 },
{ "year": 2019, "month": 11, "day": 21, "type": "payment", "amount": 1966 },
{ "year": 2019, "month": 11, "day": 21, "type": "withdrawal", "amount": 200 },
{ "year": 2019, "month": 11, "day": 21, "type": "payment", "amount": 493 },
{ "year": 2019, "month": 11, "day": 21, "type": "payment", "amount": 396 },
{ "year": 2019, "month": 11, "day": 21, "type": "withdrawal", "amount": 200 },
{ "year": 2019, "month": 11, "day": 21, "type": "payment", "amount": 134 },
{ "year": 2019, "month": 11, "day": 22, "type": "replenishment", "amount": 4815 },
{ "year": 2019, "month": 11, "day": 22, "type": "withdrawal", "amount": 500 },
{ "year": 2019, "month": 11, "day": 23, "type": "payment", "amount": 1793 },
{ "year": 2019, "month": 12, "day": 1, "type": "replenishment", "amount": 93524 },
{ "year": 2019, "month": 12, "day": 2, "type": "payment", "amount": 44289 },
{ "year": 2019, "month": 12, "day": 2, "type": "payment", "amount": 7724 },
{ "year": 2019, "month": 12, "day": 4, "type": "payment", "amount": 9420 },
{ "year": 2019, "month": 12, "day": 4, "type": "withdrawal", "amount": 3200 },
{ "year": 2019, "month": 12, "day": 4, "type": "payment", "amount": 651 },
{ "year": 2019, "month": 12, "day": 6, "type": "payment", "amount": 9259 },
{ "year": 2019, "month": 12, "day": 6, "type": "withdrawal", "amount": 5700 },
{ "year": 2019, "month": 12, "day": 7, "type": "payment", "amount": 1298 },
{ "year": 2019, "month": 12, "day": 9, "type": "payment", "amount": 3108 },
{ "year": 2019, "month": 12, "day": 11, "type": "withdrawal", "amount": 4300 },
{ "year": 2019, "month": 12, "day": 13, "type": "withdrawal", "amount": 200 },
{ "year": 2019, "month": 12, "day": 13, "type": "replenishment", "amount": 9096 },
{ "year": 2019, "month": 12, "day": 14, "type": "payment", "amount": 7205 },
{ "year": 2019, "month": 12, "day": 16, "type": "payment", "amount": 658 },
{ "year": 2019, "month": 12, "day": 17, "type": "replenishment", "amount": 9654 }
]
First, for the task_1, I get an object that contains values by the key "month":
var arr1 = ops.map(function(item){
return item.month
})
Then I find the sum of the unique values in the resulting array:
quantity var = {};
for (var i = 0; i <arr1.length; i++){
quantity [arr1[i]] = 1 + (quantity [arr1[i]]|/ 0);
}
and I get the counts object in the following form:
counts
{1: 27, 2: 11, 3: 23, 4: 20, 5: 17, 6: 21, 7: 11, 8: 24, 9: 19, 10: 26, 11: 27, 12: 16}
After that, I sort to find the 3 maximum values:
function findMax 3(obj){
var res = [-1,-1,-1];
for (let key in obj){
res[3] = obj[key];
res.sort(function(a,b){return b-a});
}
res.pop();
return res;
}
console.log(findMax3(counts));
In the console I get the following:
[27, 27, 26]
but at the same time, I no longer know the index of the months to which these values relate
Thank you for your answers!
To solve these problems here is the hint "GroupBy using reduce"
As for task1
function task_1(arr) {
// Dictionary of Month to Object.
const groupedByMonth = arr.reduce(function (acc, currentValue) {
let groupKey = currentValue.month;
if (!acc[groupKey]) {
acc[groupKey] = {
year: currentValue.year,
month: currentValue.month,
opsCount: 0
};
}
acc[groupKey].opsCount += 1; // ++
return acc;
}, {});
// Sort by opsCount
function opsSort(a, b) { return b.opsCount - a.opsCount };
return Object
.values(groupedByMonth) // Array of Values
.sort(opsSort)
.slice(0, 3) // Top 3
}
Once you understand how task 1 is solved, task 2 becomes a bit simpler
Solution for Task 2
function getEndOfMonth(year, month) {
const date = new Date(year, month, 0);
let monthStr = "";
if (month < 10) {
monthStr += "0";
}
monthStr += month;
return year + "-" + monthStr + "-" + date.getDate();
}
function getRank(rate) {
if (rate < 0.15)
return 'Gold';
if (rate < 0.3)
return 'Gold';
return 'Bronze';
}
function task_2(arr) {
const groupedByMonth = arr.reduce(function (acc, currentValue) {
let groupKey = currentValue.month;
if (!acc[groupKey]) {
acc[groupKey] = {
date: getEndOfMonth(currentValue.year, currentValue.month),
monthWithrawal: 0,
totalDebits: 0,
totalDeposits: 0
};
}
// Based on type calculate value.
if (currentValue.type === "replenishment") {
acc[groupKey].totalDeposits += currentValue.amount;
} else if (currentValue.type === "payment") {
acc[groupKey].totalDebits += currentValue.amount;
} else if (currentValue.type === "withdrawal") {
acc[groupKey].monthWithrawal += currentValue.amount;
}
return acc;
}, {});
return Object
.values(groupedByMonth) // Array of Values
.map(function (ele) {
const withdrawalRate = ele.monthWithrawal / ele.totalDeposits;
return {
date: ele.date,
monthBalance: ele.totalDeposits - ele.totalDebits - ele.monthWithrawal,
monthWithrawal: ele.monthWithrawal,
withdrawalRate,
rank: getRank(withdrawalRate)
};
});
}
if you have solved task 2, task 3 is just adding reduce/map to task2's solution.
function task_3(arr) {
// Assuming that task_2 function is defined.
const task2 = task_2(arr);
// Can be Solved using reduce.
return task2
.map(function (currentValue, index, array) {
const tmp = currentValue;
tmp.totalBalance = tmp.monthBalance;
if (index > 0) { // Not the first element.
tmp.totalBalance += array[index - 1].totalBalance;
}
return tmp;
})
}
Task 1
const task_1 = (arr, top = 3) => {
const ops = arr.reduce((acc, {year, month}) => {
const accByMonth = acc.find((obj) => (obj.month === month));
if (accByMonth) {
accByMonth.opsCount +=1;
} else {
acc.push({ year, month, opsCount: 1 })
}
return acc;
}, []);
const sortedOps = ops.sort(
(obj1, obj2) => (obj2.opsCount - obj1.opsCount)
);
return sortedOps.slice(0, top);
}
console.log(task_1(data));
//[
// { year: 2019, month: 1, opsCount: 27 },
// { year: 2019, month: 11, opsCount: 27 },
// { year: 2019, month: 10, opsCount: 26 }
//]
Task 2
const getTotalByType = (arr, type) => arr
.filter((obj) => obj.type === type)
.reduce((sum, { amount }) => sum + amount, 0);
const getRank = (ratio) =>{
const ranks = [[0.15, 'Gold'], [0.3, 'Silver'], [Infinity, 'Bronze']]
return ranks.find(([ rankRatio ]) => ratio < rankRatio)[1];
}
const task_2 = (year, month, arr) => {
const filteredData = arr.filter((obj) => (obj.year === year) && (obj.month === month));
const maxDay = Math.max(...filteredData.map(({ day }) => day));
const date = new Date(year, month - 1, maxDay).toISOString().split('T')[0];
const monthReplenishment = getTotalByType(filteredData, 'replenishment');
const monthPayment = getTotalByType(filteredData, 'payment');
const monthWithdrawal = getTotalByType(filteredData, 'withdrawal');
const monthBalance = monthReplenishment - monthPayment - monthWithdrawal;
const withdrawalRate = parseFloat((monthWithdrawal / monthReplenishment).toFixed(4));
const rank = getRank(withdrawalRate);
return {
date,
monthBalance,
monthWithdrawal,
withdrawalRate,
rank,
}
};
console.log(task_2(2019, 7, data));
//{
// date: '2019-07-13',
// monthBalance: -2244,
// monthWithdrawal: 2100,
// withdrawalRate: 0.0406,
// rank: 'Gold'
//}
Task 3
const getPeriods = (arr) => arr.
reduce((acc, {year, month}) => {
const isPeriodInAcc = acc.some(
(obj) => (obj.month === month) && (obj.year === year)
);
if (!isPeriodInAcc) {
acc.push({ year, month })
}
return acc;
}, [])
const task_3 = (arr, initialBalance = 0) => {
const periods = getPeriods(arr);
let totalBalance = initialBalance;
return periods.map(({ year, month }) => {
const opsByPeriod = task_2(year, month, arr);
totalBalance += opsByPeriod.monthBalance;
return {...opsByPeriod, totalBalance}
});
}
console.log(task_3(data));
//[
// {
// date: '2019-01-22',
// monthBalance: 3829,
// monthWithdrawal: 33800,
// withdrawalRate: 0.3134,
// rank: 'Bronze',
// totalBalance: 3829
// },
// ...
// {...},
//]

D3 axis overlap

https://jsfiddle.net/q5vwgxgz/3/
var xAxis = d3.axisBottom(x)
.ticks(15)
.tickSizeInner(25)
So the issue is that labels of the X axis overlap, Mon 31 and Novemeber.
From what I can see the space between axes for those two dates is smaller and in result the labels overlap.
Can I solve this somehow or it's a library issue?
The project where that chart is used in has dateselectors for start and end date and also a dropdown for date granularity(daily, monthly, yearly) which means that I can't use a fixed number of ticks and specific format.
.ticks(15)
is used here just for the demo, in the project it's like this
Math.min(data.length, Math.floor(width / 100)) // 100 being the 'min' tick width
One solution (out of many) is specifically setting the tick format...
.tickFormat(function(d) {
return d3.timeFormat("%b %d")(d)
})
And spreading the ticks, for instance using timeDay:
.ticks(d3.timeDay.every(4))
Here is your code with those changes:
var data = [{
"Date": "2016-09-30",
"Jobs": 0,
}, {
"Date": "2016-10-01",
"Jobs": 0,
}, {
"Date": "2016-10-02",
"Jobs": 1,
}, {
"Date": "2016-10-03",
"Jobs": 0,
}, {
"Date": "2016-10-04",
"Jobs": 3,
}, {
"Date": "2016-10-05",
"Jobs": 0,
}, {
"Date": "2016-10-06",
"Jobs": 2,
}, {
"Date": "2016-10-07",
"Jobs": 0,
}, {
"Date": "2016-10-08",
"Jobs": 0,
}, {
"Date": "2016-10-09",
"Jobs": 4,
}, {
"Date": "2016-10-10",
"Jobs": 6,
}, {
"Date": "2016-10-11",
"Jobs": 1,
}, {
"Date": "2016-10-12",
"Jobs": 2,
}, {
"Date": "2016-10-13",
"Jobs": 0,
}, {
"Date": "2016-10-14",
"Jobs": 0,
}, {
"Date": "2016-10-15",
"Jobs": 0,
}, {
"Date": "2016-10-16",
"Jobs": 3,
}, {
"Date": "2016-10-17",
"Jobs": 2,
}, {
"Date": "2016-10-18",
"Jobs": 3,
}, {
"Date": "2016-10-19",
"Jobs": 3,
}, {
"Date": "2016-10-20",
"Jobs": 1,
}, {
"Date": "2016-10-21",
"Jobs": 0,
}, {
"Date": "2016-10-22",
"Jobs": 0,
}, {
"Date": "2016-10-23",
"Jobs": 4,
}, {
"Date": "2016-10-24",
"Jobs": 2,
}, {
"Date": "2016-10-25",
"Jobs": 6,
}, {
"Date": "2016-10-26",
"Jobs": 1,
}, {
"Date": "2016-10-27",
"Jobs": 1,
}, {
"Date": "2016-10-28",
"Jobs": 0,
}, {
"Date": "2016-10-29",
"Jobs": 3,
}, {
"Date": "2016-10-30",
"Jobs": 5,
}, {
"Date": "2016-10-31",
"Jobs": 0,
}, {
"Date": "2016-11-01",
"Jobs": 3,
}, {
"Date": "2016-11-02",
"Jobs": 1,
}, {
"Date": "2016-11-03",
"Jobs": 0,
}, {
"Date": "2016-11-04",
"Jobs": 0,
}, {
"Date": "2016-11-05",
"Jobs": 1,
}, {
"Date": "2016-11-06",
"Jobs": 4,
}, {
"Date": "2016-11-07",
"Jobs": 5,
}, {
"Date": "2016-11-08",
"Jobs": 6,
}, {
"Date": "2016-11-09",
"Jobs": 2,
}, {
"Date": "2016-11-10",
"Jobs": 1,
}, {
"Date": "2016-11-11",
"Jobs": 0,
}, {
"Date": "2016-11-12",
"Jobs": 2,
}, {
"Date": "2016-11-13",
"Jobs": 2,
}, {
"Date": "2016-11-14",
"Jobs": 1,
}, {
"Date": "2016-11-15",
"Jobs": 3,
}, {
"Date": "2016-11-16",
"Jobs": 0,
}, {
"Date": "2016-11-17",
"Jobs": 1,
}, {
"Date": "2016-11-18",
"Jobs": 0,
}, {
"Date": "2016-11-19",
"Jobs": 0,
}, {
"Date": "2016-11-20",
"Jobs": 1,
}, {
"Date": "2016-11-21",
"Jobs": 2,
}, {
"Date": "2016-11-22",
"Jobs": 2,
}, {
"Date": "2016-11-23",
"Jobs": 4,
}, {
"Date": "2016-11-24",
"Jobs": 0,
}, {
"Date": "2016-11-25",
"Jobs": 0,
}]
var parseTime = d3.timeParse("%Y-%m-%d");
data.forEach(function(d) {
d.Date = parseTime(d.Date);
});
var svg = d3.select("svg"),
margin = {
top: 20,
right: 20,
bottom: 50,
left: 50
},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleTime()
.rangeRound([0, width]);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var line = d3.line()
.x(function(d) {
return x(d.Date);
})
.y(function(d) {
return y(d.Jobs);
});
x.domain(d3.extent(data, function(d) {
return d.Date;
}));
y.domain(d3.extent(data, function(d) {
return d.Jobs;
}));
var xAxis = d3.axisBottom(x)
.ticks(d3.timeDay.every(4))
.tickFormat(function(d) {
return d3.timeFormat("%b %d")(d)
})
.tickSizeInner(25)
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.select(".domain")
.remove();
g.append("g")
.call(d3.axisLeft(y))
.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("Jobs");
g.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", line);
<svg width="1200" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
However, have in mind that in D3 time scales were not created having customizations in mind. Their behaviour is dynamic. Therefore, You'll have to fix each specific problem using ad hoc solutions.
Or you may rotate the label for ticks by 90 degrees:
d3.selectAll(".x-axis .tick text")
.attr("transform", "translate(-30, 50)rotate(-90)")
working code here

organizing objects by name

Take the array of objects below "toursByHotels" — I want to take this data and get the sum of tours for each object that has the same name (the hotel's name, ie. Marriott, etc).
Is there an easier way in d3 than having to create 5 new arrays with the same names, then sum the tours of those?
var toursByHotel = [
{
"name": "Marriott",
"month": 1,
"tours": 10
},
{
"name": "Marriott",
"month": 2,
"tours": 15
},
{
"name": "Marriott",
"month": 3,
"tours": 8
},
{
"name": "Marriott",
"month": 4,
"tours": 12
},
{
"name": "Marriott",
"month": 5,
"tours": 18
},
{
"name": "Marriott",
"month": 6,
"tours": 25
},
{
"name": "Marriott",
"month": 7,
"tours": 40
},
{
"name": "Marriott",
"month": 8,
"tours": 33
},
{
"name": "Marriott",
"month": 9,
"tours": 25
},
{
"name": "Marriott",
"month": 10,
"tours": 21
},
{
"name": "Marriott",
"month": 11,
"tours": 18
},
{
"name": "Marriott",
"month": 12,
"tours": 14
},
{
"name": "Springhill",
"month": 1,
"tours": 10
},
{
"name": "Springhill",
"month": 2,
"tours": 15
},
{
"name": "Springhill",
"month": 3,
"tours": 8
},
{
"name": "Springhill",
"month": 4,
"tours": 12
},
{
"name": "Springhill",
"month": 5,
"tours": 18
},
{
"name": "Springhill",
"month": 6,
"tours": 25
},
{
"name": "Springhill",
"month": 7,
"tours": 40
},
{
"name": "Springhill",
"month": 8,
"tours": 33
},
{
"name": "Springhill",
"month": 9,
"tours": 25
},
{
"name": "Springhill",
"month": 10,
"tours": 21
},
{
"name": "Springhill",
"month": 11,
"tours": 18
},
{
"name": "Springhill",
"month": 12,
"tours": 14
},
{
"name": "Residence",
"month": 1,
"tours": 10
},
{
"name": "Residence",
"month": 2,
"tours": 15
},
{
"name": "Residence",
"month": 3,
"tours": 8
},
{
"name": "Residence",
"month": 4,
"tours": 12
},
{
"name": "Residence",
"month": 5,
"tours": 18
},
{
"name": "Residence",
"month": 6,
"tours": 25
},
{
"name": "Residence",
"month": 7,
"tours": 40
},
{
"name": "Residence",
"month": 8,
"tours": 33
},
{
"name": "Residence",
"month": 9,
"tours": 25
},
{
"name": "Residence",
"month": 10,
"tours": 21
},
{
"name": "Residence",
"month": 11,
"tours": 18
},
{
"name": "Residence",
"month": 12,
"tours": 14
},
{
"name": "Courtyard",
"month": 1,
"tours": 10
},
{
"name": "Courtyard",
"month": 2,
"tours": 15
},
{
"name": "Courtyard",
"month": 3,
"tours": 8
},
{
"name": "Courtyard",
"month": 4,
"tours": 12
},
{
"name": "Courtyard",
"month": 5,
"tours": 18
},
{
"name": "Courtyard",
"month": 6,
"tours": 25
},
{
"name": "Courtyard",
"month": 7,
"tours": 40
},
{
"name": "Courtyard",
"month": 8,
"tours": 33
},
{
"name": "Courtyard",
"month": 9,
"tours": 25
},
{
"name": "Courtyard",
"month": 10,
"tours": 21
},
{
"name": "Courtyard",
"month": 11,
"tours": 18
},
{
"name": "Courtyard",
"month": 12,
"tours": 14
},
{
"name": "Renaissance",
"month": 1,
"tours": 10
},
{
"name": "Renaissance",
"month": 2,
"tours": 15
},
{
"name": "Renaissance",
"month": 3,
"tours": 8
},
{
"name": "Renaissance",
"month": 4,
"tours": 12
},
{
"name": "Renaissance",
"month": 5,
"tours": 18
},
{
"name": "Renaissance",
"month": 6,
"tours": 25
},
{
"name": "Renaissance",
"month": 7,
"tours": 40
},
{
"name": "Renaissance",
"month": 8,
"tours": 33
},
{
"name": "Renaissance",
"month": 9,
"tours": 25
},
{
"name": "Renaissance",
"month": 10,
"tours": 21
},
{
"name": "Renaissance",
"month": 11,
"tours": 18
},
{
"name": "Renaissance",
"month": 12,
"tours": 14
}
];
You can use reduce and return object where each key is hotel name and value is sum of tours. DEMO
data = data.reduce(function(obj, e) {
obj[e.name] = (obj[e.name] || 0) + e.tours;
return obj;
}, {});
console.log(data)
If you want to get sum and percentage for each hotel you can do it like this DEMO
var result = {}
var total = data.reduce((a, b) => {return a + b.tours }, 0);
data.forEach(function(e) {
if(!this[e.name]) {
this[e.name] = {sum: e.tours, percentage: 0}
result[e.name] = this[e.name];
}
this[e.name].sum += e.tours;
this[e.name].percentage = (this[e.name].sum / total)*100;
}, {});
console.log(result)
Using reduce would be the best way to do this:
Working Example
// lets keep track of the total here:
var total = 0;
var totals = a.reduce(function(curr, next) {
curr[next.name] = (curr[next.name] || 0) + next.tours;
// increment total here:
total += next.tours;
return curr;
}, {});
for (var hotel in totals) {
var obj = {};
obj.name = totals[hotel];
obj.percentage = (totals[hotel] / total) * 100 + '%';
totals[hotel] = obj;
}
MDN docs on .reduce

d3.js: How to change a nodes' representation in a force-layout graph

I am trying to expand this force layout example by changing a nodes' shape from circle to rectangle when it is clicked. So I don't want to change any data but just want to replace the corresponding SVG element.
One of my approaches looked like this:
node.on("click", function() {
this.remove();
svg.selectAll(".node").data(graph.nodes).enter().append("rect")
.attr("class", "node")
.attr("width", 5).attr("height", 5)
.style("fill", function(d) { return color(d.group); });
});
So I removed the SVG element from the DOM and rebound the data, adding a rectangle for the now missing node.
Unfortunately this does not work (the force layout does not set any properties on the new element) and I have no idea if this a reasonable approach at all.
Any ideas how to do this properly?
Try this way.
node.on("click", function(d) {
var size = d.weight * 2 + 12;
d3.select(this).select("circle").remove();
d3.select(this).append("rect")
.attr("x", -(size / 2))
.attr("y", -(size / 2))
.attr("height", size)
.attr("width", size)
.style("fill", function(d) {
return color(1 / d.rating);
});
});
Working code snippet -
var graph = {
"nodes": [{
"name": "1",
"rating": 90,
"id": 2951
}, {
"name": "2",
"rating": 80,
"id": 654654
}, {
"name": "3",
"rating": 80,
"id": 6546544
}, {
"name": "4",
"rating": 1,
"id": 68987978
}, {
"name": "5",
"rating": 1,
"id": 9878933
}, {
"name": "6",
"rating": 1,
"id": 6161
}, {
"name": "7",
"rating": 1,
"id": 64654
}, {
"name": "8",
"rating": 20,
"id": 354654
}, {
"name": "9",
"rating": 50,
"id": 8494
}, {
"name": "10",
"rating": 1,
"id": 6846874
}, {
"name": "11",
"rating": 1,
"id": 5487
}, {
"name": "12",
"rating": 80,
"id": "parfum_kenzo"
}, {
"name": "13",
"rating": 1,
"id": 65465465
}, {
"name": "14",
"rating": 90,
"id": "jungle_de_kenzo"
}, {
"name": "15",
"rating": 20,
"id": 313514
}, {
"name": "16",
"rating": 40,
"id": 36543614
}, {
"name": "17",
"rating": 100,
"id": "Yann_YA645"
}, {
"name": "18",
"rating": 1,
"id": 97413
}, {
"name": "19",
"rating": 1,
"id": 97414
}, {
"name": "20",
"rating": 100,
"id": 976431231
}, {
"name": "21",
"rating": 1,
"id": 9416
}, {
"name": "22",
"rating": 1,
"id": 998949
}, {
"name": "23",
"rating": 100,
"id": 984941
}, {
"name": "24",
"rating": 100,
"id": "99843"
}, {
"name": "25",
"rating": 1,
"id": 94915
}, {
"name": "26",
"rating": 1,
"id": 913134
}, {
"name": "27",
"rating": 1,
"id": 9134371
}],
"links": [{
"source": 6,
"target": 5,
"value": 6,
"label": "publishedOn"
}, {
"source": 8,
"target": 5,
"value": 6,
"label": "publishedOn"
}, {
"source": 7,
"target": 1,
"value": 4,
"label": "containsKeyword"
}, {
"source": 8,
"target": 10,
"value": 3,
"label": "containsKeyword"
}, {
"source": 7,
"target": 14,
"value": 4,
"label": "publishedBy"
}, {
"source": 8,
"target": 15,
"value": 6,
"label": "publishedBy"
}, {
"source": 9,
"target": 1,
"value": 6,
"label": "depicts"
}, {
"source": 10,
"target": 1,
"value": 6,
"label": "depicts"
}, {
"source": 16,
"target": 1,
"value": 6,
"label": "manageWebsite"
}, {
"source": 16,
"target": 2,
"value": 5,
"label": "manageWebsite"
}, {
"source": 16,
"target": 3,
"value": 6,
"label": "manageWebsite"
}, {
"source": 16,
"target": 4,
"value": 6,
"label": "manageWebsite"
}, {
"source": 19,
"target": 18,
"value": 2,
"label": "postedOn"
}, {
"source": 18,
"target": 1,
"value": 6,
"label": "childOf"
}, {
"source": 17,
"target": 19,
"value": 8,
"label": "describes"
}, {
"source": 18,
"target": 11,
"value": 6,
"label": "containsKeyword"
}, {
"source": 17,
"target": 13,
"value": 3,
"label": "containsKeyword"
}, {
"source": 20,
"target": 13,
"value": 3,
"label": "containsKeyword"
}, {
"source": 20,
"target": 21,
"value": 3,
"label": "postedOn"
}, {
"source": 22,
"target": 20,
"value": 3,
"label": "postedOn"
}, {
"source": 23,
"target": 21,
"value": 3,
"label": "manageWebsite"
}, {
"source": 23,
"target": 24,
"value": 3,
"label": "manageWebsite"
}, {
"source": 23,
"target": 25,
"value": 3,
"label": "manageWebsite"
}, {
"source": 23,
"target": 26,
"value": 3,
"label": "manageWebsite"
}]
}
var margin = {
top: -5,
right: -5,
bottom: -5,
left: -5
};
var width = 500 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-200)
.linkDistance(50)
.size([width + margin.left + margin.right, height + margin.top + margin.bottom]);
var zoom = d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
var drag = d3.behavior.drag()
.origin(function(d) {
return d;
})
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
var svg = d3.select("#map").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.right + ")")
.call(zoom);
var rect = svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all");
var container = svg.append("g");
//d3.json('http://blt909.free.fr/wd/map2.json', function(error, graph) {
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = container.append("g")
.attr("class", "links")
.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) {
return Math.sqrt(d.value);
});
var node = container.append("g")
.attr("class", "nodes")
.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.call(drag);
node.append("circle")
.attr("r", function(d) {
return d.weight * 2 + 12;
})
.style("fill", function(d) {
return color(1 / d.rating);
});
force.on("tick", function() {
link.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
});
var linkedByIndex = {};
graph.links.forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index];
}
node.on("click", function(d) {
var size = d.weight * 2 + 12;
d3.select(this).select("circle").remove();
d3.select(this).append("rect")
.attr("x", -(size / 2))
.attr("y", -(size / 2))
.attr("height", size)
.attr("width", size)
.style("fill", function(d) {
return color(1 / d.rating);
});
});
function dottype(d) {
d.x = +d.x;
d.y = +d.y;
return d;
}
function zoomed() {
container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
force.start();
}
function dragged(d) {
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("dragging", false);
}
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.node-active {
stroke: #555;
stroke-width: 1.5px;
}
.link {
stroke: #555;
stroke-opacity: .3;
}
.link-active {
stroke-opacity: 1;
}
.overlay {
fill: none;
pointer-events: all;
}
#map {
border: 2px #555 dashed;
width: 500px;
height: 400px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<body>
<div id="map"></div>
</body>
You've updated the dom elements, but not the array used by the force layout algorithm, so do this as the last line in the on click function:
graph.nodes = svg.selectAll(".node");
(You might also find without a data key function that random nodes get changed to rectangles rather than the one you clicked)

Content of d3.js chart overflows margins when using brush

I'm having trouble with a d3.js bar chart. The full code and working demo are here: https://jsfiddle.net/monotasker/yurqkm3n/. The issue is that when I use the brush on the lower (context) chart, the upper (focus) chart overflows its margins.
Here is the function that the brush activates:
function brushed() {
time.domain(brush.empty() ? navTime.domain() : brush.extent())
.range([0, (width)]);
x.domain(max_extent_in_days(time))
.rangeBands([margin.left, (width)], 0.1, 0);
focus.selectAll('.bar.stack')
.attr('transform', function(d) {return "translate(" + time(d.date) + ",0)"; })
.attr('width', x.rangeBand());
focus.selectAll('.rect')
.attr('width', x.rangeBand());
focus.selectAll('.line').attr('d', line);
focus.select(".x.axis").call(x_axis);
};
One oddity about the chart is that there are two scales that together control the x axis: "time" and "x". The "time" scale is used for positioning the content on the x axis, but the "x" scale (which converts "time" to an ordinal scale) is used for calculating bar widths. I don't know whether this approach is somehow the source of the problem.
Any help is greatly appreciated.
As in this example where Mr. Bostock applies a clip path to the area path, you need to apply one to the bars. The easiest way is to put them in their own g then apply the clip-path to that:
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
...
// Plot the stacked bars
var focus_bar = focus
.append("g") //<-- g just for the bars to be clipped
.attr("class","barClipper")
.style('clip-path', 'url(#clip)') //<-- apply clipping
.selectAll('.g')
.data(data.answer_counts)
.enter().append('g')
.attr('class', 'g bar stack')
.attr('transform', function(d) {
return "translate(" + x(d.date) + ",0)"
});
Full code:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
<style>
#milestones_attempts_combo .context {
/*fill: #efefef; */
}
#milestones_attempts_combo .focus .right,
#milestones_attempts_combo .context .right {
fill: #0064cd;
}
#milestones_attempts_combo .focus .wrong,
#milestones_attempts_combo .context .wrong {
fill: #cd001d;
}
#milestones_attempts_combo .y-axis-label,
#milestones_attempts_combo .y2-axis-label {
fill: #aaa;
font-size: 120%;
}
#milestones_attempts_combo .focus-line,
#milestones_attempts_combo .context-line {
stroke: orange;
stroke-width: 2;
fill-opacity: 0;
}
.brush .extent {
stroke: #efefef;
fill: #666;
fill-opacity: .125;
shape-rendering: crispEdges;
}
.stats-container {
width: 700px;
margin: 0 auto;
}
.chart .axis line,
.chart .axis path,
.chart .tick line {
fill: none;
stroke: #aaa;
shape-rendering: crispEdges;
}
</style>
</head>
<body>
<div id="milestones_attempts_combo"></div>
<script>
(function() {
var myjsondata = {
"answer_counts": [{
"date": "2012-09-13",
"ys": [{
"y1": 9,
"y0": 0,
"class": "right"
}, {
"y1": 12,
"y0": 9,
"class": "wrong"
}],
"total": 12
}, {
"date": "2012-09-16",
"ys": [{
"y1": 16,
"y0": 0,
"class": "right"
}, {
"y1": 17,
"y0": 16,
"class": "wrong"
}],
"total": 17
}, {
"date": "2012-09-17",
"ys": [{
"y1": 12,
"y0": 0,
"class": "right"
}, {
"y1": 14,
"y0": 12,
"class": "wrong"
}],
"total": 14
}, {
"date": "2012-09-19",
"ys": [{
"y1": 2,
"y0": 0,
"class": "right"
}, {
"y1": 2,
"y0": 2,
"class": "wrong"
}],
"total": 2
}, {
"date": "2012-09-20",
"ys": [{
"y1": 12,
"y0": 0,
"class": "right"
}, {
"y1": 16,
"y0": 12,
"class": "wrong"
}],
"total": 16
}, {
"date": "2012-09-21",
"ys": [{
"y1": 20,
"y0": 0,
"class": "right"
}, {
"y1": 22,
"y0": 20,
"class": "wrong"
}],
"total": 22
}, {
"date": "2012-09-22",
"ys": [{
"y1": 1,
"y0": 0,
"class": "right"
}, {
"y1": 1,
"y0": 1,
"class": "wrong"
}],
"total": 1
}, {
"date": "2012-09-23",
"ys": [{
"y1": 10,
"y0": 0,
"class": "right"
}, {
"y1": 12,
"y0": 10,
"class": "wrong"
}],
"total": 12
}, {
"date": "2012-09-24",
"ys": [{
"y1": 9,
"y0": 0,
"class": "right"
}, {
"y1": 9,
"y0": 9,
"class": "wrong"
}],
"total": 9
}, {
"date": "2012-09-25",
"ys": [{
"y1": 2,
"y0": 0,
"class": "right"
}, {
"y1": 4,
"y0": 2,
"class": "wrong"
}],
"total": 4
}, {
"date": "2012-09-29",
"ys": [{
"y1": 26,
"y0": 0,
"class": "right"
}, {
"y1": 37,
"y0": 26,
"class": "wrong"
}],
"total": 37
}, {
"date": "2012-10-01",
"ys": [{
"y1": 44,
"y0": 0,
"class": "right"
}, {
"y1": 44,
"y0": 44,
"class": "wrong"
}],
"total": 44
}, {
"date": "2012-10-02",
"ys": [{
"y1": 2,
"y0": 0,
"class": "right"
}, {
"y1": 2,
"y0": 2,
"class": "wrong"
}],
"total": 2
}, {
"date": "2012-10-03",
"ys": [{
"y1": 13,
"y0": 0,
"class": "right"
}, {
"y1": 13,
"y0": 13,
"class": "wrong"
}],
"total": 13
}, {
"date": "2012-10-05",
"ys": [{
"y1": 47,
"y0": 0,
"class": "right"
}, {
"y1": 47,
"y0": 47,
"class": "wrong"
}],
"total": 47
}, {
"date": "2012-10-08",
"ys": [{
"y1": 17,
"y0": 0,
"class": "right"
}, {
"y1": 17,
"y0": 17,
"class": "wrong"
}],
"total": 17
}, {
"date": "2012-10-09",
"ys": [{
"y1": 19,
"y0": 0,
"class": "right"
}, {
"y1": 20,
"y0": 19,
"class": "wrong"
}],
"total": 20
}, {
"date": "2012-10-10",
"ys": [{
"y1": 31,
"y0": 0,
"class": "right"
}, {
"y1": 31,
"y0": 31,
"class": "wrong"
}],
"total": 31
}, {
"date": "2012-10-11",
"ys": [{
"y1": 6,
"y0": 0,
"class": "right"
}, {
"y1": 6,
"y0": 6,
"class": "wrong"
}],
"total": 6
}, {
"date": "2012-10-14",
"ys": [{
"y1": 6,
"y0": 0,
"class": "right"
}, {
"y1": 6,
"y0": 6,
"class": "wrong"
}],
"total": 6
}, {
"date": "2012-10-19",
"ys": [{
"y1": 30,
"y0": 0,
"class": "right"
}, {
"y1": 32,
"y0": 30,
"class": "wrong"
}],
"total": 32
}, {
"date": "2012-10-20",
"ys": [{
"y1": 20,
"y0": 0,
"class": "right"
}, {
"y1": 22,
"y0": 20,
"class": "wrong"
}],
"total": 22
}, {
"date": "2012-10-23",
"ys": [{
"y1": 20,
"y0": 0,
"class": "right"
}, {
"y1": 20,
"y0": 20,
"class": "wrong"
}],
"total": 20
}, {
"date": "2012-10-24",
"ys": [{
"y1": 20,
"y0": 0,
"class": "right"
}, {
"y1": 21,
"y0": 20,
"class": "wrong"
}],
"total": 21
}, {
"date": "2012-10-29",
"ys": [{
"y1": 22,
"y0": 0,
"class": "right"
}, {
"y1": 22,
"y0": 22,
"class": "wrong"
}],
"total": 22
}, {
"date": "2012-11-01",
"ys": [{
"y1": 1,
"y0": 0,
"class": "right"
}, {
"y1": 1,
"y0": 1,
"class": "wrong"
}],
"total": 1
}, {
"date": "2012-11-02",
"ys": [{
"y1": 26,
"y0": 0,
"class": "right"
}, {
"y1": 29,
"y0": 26,
"class": "wrong"
}],
"total": 29
}, {
"date": "2012-11-05",
"ys": [{
"y1": 23,
"y0": 0,
"class": "right"
}, {
"y1": 27,
"y0": 23,
"class": "wrong"
}],
"total": 27
}, {
"date": "2012-11-06",
"ys": [{
"y1": 10,
"y0": 0,
"class": "right"
}, {
"y1": 11,
"y0": 10,
"class": "wrong"
}],
"total": 11
}, {
"date": "2012-11-07",
"ys": [{
"y1": 15,
"y0": 0,
"class": "right"
}, {
"y1": 18,
"y0": 15,
"class": "wrong"
}],
"total": 18
}, {
"date": "2012-11-09",
"ys": [{
"y1": 26,
"y0": 0,
"class": "right"
}, {
"y1": 31,
"y0": 26,
"class": "wrong"
}],
"total": 31
}, {
"date": "2012-11-10",
"ys": [{
"y1": 2,
"y0": 0,
"class": "right"
}, {
"y1": 2,
"y0": 2,
"class": "wrong"
}],
"total": 2
}, {
"date": "2012-11-14",
"ys": [{
"y1": 15,
"y0": 0,
"class": "right"
}, {
"y1": 17,
"y0": 15,
"class": "wrong"
}],
"total": 17
}, {
"date": "2012-11-16",
"ys": [{
"y1": 20,
"y0": 0,
"class": "right"
}, {
"y1": 23,
"y0": 20,
"class": "wrong"
}],
"total": 23
}, {
"date": "2012-11-18",
"ys": [{
"y1": 2,
"y0": 0,
"class": "right"
}, {
"y1": 2,
"y0": 2,
"class": "wrong"
}],
"total": 2
}, {
"date": "2012-11-19",
"ys": [{
"y1": 23,
"y0": 0,
"class": "right"
}, {
"y1": 26,
"y0": 23,
"class": "wrong"
}],
"total": 26
}, {
"date": "2012-11-21",
"ys": [{
"y1": 2,
"y0": 0,
"class": "right"
}, {
"y1": 3,
"y0": 2,
"class": "wrong"
}],
"total": 3
}, {
"date": "2012-11-23",
"ys": [{
"y1": 9,
"y0": 0,
"class": "right"
}, {
"y1": 10,
"y0": 9,
"class": "wrong"
}],
"total": 10
}, {
"date": "2012-11-26",
"ys": [{
"y1": 2,
"y0": 0,
"class": "right"
}, {
"y1": 2,
"y0": 2,
"class": "wrong"
}],
"total": 2
}, {
"date": "2012-11-27",
"ys": [{
"y1": 25,
"y0": 0,
"class": "right"
}, {
"y1": 26,
"y0": 25,
"class": "wrong"
}],
"total": 26
}, {
"date": "2012-11-28",
"ys": [{
"y1": 73,
"y0": 0,
"class": "right"
}, {
"y1": 75,
"y0": 73,
"class": "wrong"
}],
"total": 75
}, {
"date": "2012-12-02",
"ys": [{
"y1": 19,
"y0": 0,
"class": "right"
}, {
"y1": 20,
"y0": 19,
"class": "wrong"
}],
"total": 20
}, {
"date": "2012-12-05",
"ys": [{
"y1": 1,
"y0": 0,
"class": "right"
}, {
"y1": 1,
"y0": 1,
"class": "wrong"
}],
"total": 1
}, {
"date": "2013-01-11",
"ys": [{
"y1": 2,
"y0": 0,
"class": "right"
}, {
"y1": 3,
"y0": 2,
"class": "wrong"
}],
"total": 3
}, {
"date": "2013-01-16",
"ys": [{
"y1": 8,
"y0": 0,
"class": "right"
}, {
"y1": 8,
"y0": 8,
"class": "wrong"
}],
"total": 8
}],
"badge_set_reached": [{
"date": "2014-05-24",
"set": 3
}, {
"date": "2014-09-29",
"set": 5
}, {
"date": "2014-11-10",
"set": 6
}, {
"date": "2014-08-29",
"set": 7
}, {
"date": "2015-08-12",
"set": 9
}, {
"date": "2016-01-09",
"set": 9
}]
}
var data = myjsondata;
// preprocess data to get usable date objects
var parse_date = d3.time.format('%Y-%m-%d').parse
for (var i in data) {
var myObj = data[i]
for (var j in myObj) {
myObj[j].date = parse_date(myObj[j].date);
}
}
console.log(data);
// set variables
var margin = {
left: 60,
right: 60,
top: 10,
bottom: 140
},
navMargin = {
top: 300,
right: 60,
bottom: 40,
left: 60
},
height = 400 - margin.top - margin.bottom,
width = 800 - margin.left - margin.right,
navWidth = width, // for context band
navHeight = 400 - navMargin.top - navMargin.bottom,
max_total_counts = d3.max(data.answer_counts, function(d) {
return d.total;
}),
max_extent_dates = d3.extent(data.answer_counts, function(d) {
return d.date;
});
max_extent_in_days = function(timescale) {
return d3.time.days(timescale.domain()[0], d3.time.day.offset(timescale.domain()[1], 1))
};
// scales
var y = d3.scale.linear().domain([0, max_total_counts]).rangeRound([height, 0]),
navY = d3.scale.linear().domain([0, max_total_counts]).rangeRound([navHeight, 0]),
time = d3.time.scale().domain(max_extent_dates).range([0, width]),
navTime = d3.time.scale().domain(max_extent_dates).range([0, width]),
x = d3.scale.ordinal().domain(max_extent_in_days(time)).rangeBands([0, width], 0.1, 0), // used to calculate bar widths
navX = d3.scale.ordinal().domain(max_extent_in_days(navTime)).rangeBands([0, width], 0.1, 0),
y2 = d3.scale.linear().domain([0, d3.max(data.badge_set_reached, function(d) {
return d.set
})]).rangeRound([height, 0]);
navY2 = d3.scale.linear().domain([0, d3.max(data.badge_set_reached, function(d) {
return d.set
})]).rangeRound([navHeight, 0]);
// axes
var x_axis = d3.svg.axis().scale(time).orient('bottom') // .tickFormat(d3.time.format('%Y-%m-%d'))
.outerTickSize(0), // at start and end of axis line
nav_x_axis = d3.svg.axis().scale(navTime).orient('bottom') // .tickFormat(d3.time.format('%Y-%m-%d'))
.outerTickSize(0), // at start and end of axis line
y_axis = d3.svg.axis().scale(y).orient('left').tickFormat(d3.format('d'));
y2_axis = d3.svg.axis().scale(y2).orient('right').tickFormat(d3.format('d'));
// add brush
var brush = d3.svg.brush()
.x(navTime)
.on("brush", brushed);
// svg context
var svg = d3.select("#milestones_attempts_combo")
.append('svg')
.attr('class', 'chart')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var focus = svg.append('g')
.attr('class', 'focus')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var context = svg.append('g')
.attr('class', 'context')
.attr('transform', 'translate(' + navMargin.left + ',' + navMargin.top + ')');
// Plot the stacked bars
var focus_bar = focus
.append("g")
.attr("class","barClipper")
.style('clip-path', 'url(#clip)')
.selectAll('.g')
.data(data.answer_counts)
.enter().append('g')
.attr('class', 'g bar stack')
.attr('transform', function(d) {
return "translate(" + x(d.date) + ",0)"
});
var focus_rects = focus_bar.selectAll('rect')
.data(function(d) {
return d.ys;
})
.enter().append('rect')
.attr('width', x.rangeBand())
.attr('height', function(d) {
return y(d.y0) - y(d.y1);
})
.attr('y', function(d) {
return y(d.y1);
})
.attr('class', function(d) {
return 'rect ' + d['class'];
});
var context_bar = context.selectAll('.g')
.data(data.answer_counts)
.enter().append('g')
.attr('class', 'g')
.attr('transform', function(d) {
return "translate(" + navTime(d.date) + ",0)"
});
var context_rects = context_bar.selectAll('rect')
.data(function(d) {
return d.ys;
})
.enter().append('rect')
.attr('width', navX.rangeBand())
.attr('height', function(d) {
return navY(d.y0) - navY(d.y1);
})
.attr('y', function(d) {
return navY(d.y1);
})
.attr('class', function(d) {
return 'rect ' + d['class'];
});
// Plot lines
var line = d3.svg.line()
.x(function(d) {
return time(d.date)
})
.y(function(d) {
return y2(d.set)
})
.interpolate('step-after');
var focus_line = focus.append('path')
.datum(data.badge_set_reached)
.attr('class', 'line focus-line')
.attr('d', line);
var line2 = d3.svg.line()
.x(function(d) {
return time(d.date)
})
.y(function(d) {
return navY2(d.set)
})
.interpolate('step-after');
var context_line = context.append('path')
.datum(data.badge_set_reached)
.attr('class', 'line context-line')
.attr('d', line2);
// Plot axes
focus.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0, ' + height + ')')
.call(x_axis)
.selectAll('text')
.style('text-anchor', 'end')
// .attr('transform', 'rotate(-45)')
// .attr('dx', '-.5em')
// .attr('dy', '.5em');
focus.append('g')
.attr('class', 'y axis')
.attr('transform', 'translate(0, 0)')
.call(y_axis);
focus.append('g')
.attr('class', 'y2 axis')
.attr('transform', 'translate(' + width + ', 0)')
.call(y2_axis);
context.append('g')
.attr('class', 'navX axis')
.attr('transform', 'translate(0, ' + navHeight + ')')
.call(nav_x_axis);
// Label axes
svg.append('text')
.attr('class', 'label y-axis-label')
.attr('transform', 'rotate(-90)')
.attr('x', 0 - ((height + margin.top) / 2))
.attr('y', 0)
.attr('dy', 20)
.style('text-anchor', 'middle')
.text('Paths Attempted');
svg.append('text')
.attr('class', 'label y2-axis-label')
.attr('transform', 'rotate(+90)')
.attr('y', 0 - (width + margin.left + margin.right))
.attr('x', 0 + ((height + margin.top) / 2))
.attr('dy', '2em')
.style('text-anchor', 'middle')
.text('Badge Set Reached');
// Add brush to svg
context.append('g')
.attr('class', 'x brush')
.call(brush)
.selectAll("rect")
.attr("y", -6)
.attr("height", navHeight + 7);
function brushed() {
time.domain(brush.empty() ? navTime.domain() : brush.extent())
.range([0, (width)]);
x.domain(max_extent_in_days(time))
.rangeBands([margin.left, (width)], 0.1, 0);
focus.selectAll('.bar.stack')
.attr('transform', function(d) {
return "translate(" + time(d.date) + ",0)";
})
.attr('width', x.rangeBand());
focus.selectAll('.rect')
.attr('width', x.rangeBand());
focus.selectAll('.line').attr('d', line);
focus.select(".x.axis").call(x_axis);
};
})();
</script>
</body>
</html>

Categories