Consider the following code:
var dataAsCsv = `date,col_1,col_2
11/1/2012,1977652,1802851
12/1/2012,1128739,948687
1/1/2013,1201944,1514667
2/1/2013,1863148,1834006
3/1/2013,1314851,1906060
4/1/2013,1283943,1978702
5/1/2013,1127964,1195606
6/1/2013,1773254,977214
7/1/2013,1929574,1127450
8/1/2013,1980411,1808161
9/1/2013,1405691,1182788
10/1/2013,1336790,937890
11/1/2013,1851053,1358400
12/1/2013,1472623,1214610
1/1/2014,1155116,1757052
2/1/2014,1571611,1935038
3/1/2014,1898348,1320348
4/1/2014,1444838,1934789
5/1/2014,1235087,950194
6/1/2014,1272040,1580656
7/1/2014,980781,1680164
8/1/2014,1391291,1115999
9/1/2014,1211125,1542148
10/1/2014,1020824,1782795
11/1/2014,1685081,926612
12/1/2014,1469254,1767071
1/1/2015,1168523,935897
2/1/2015,1602610,1450541
3/1/2015,1830278,1354876
4/1/2015,1275158,1412555
5/1/2015,1560961,1839718
6/1/2015,949948,1587130
7/1/2015,1413765,1494446
8/1/2015,1166141,1305105
9/1/2015,958975,1202219
10/1/2015,902696,1023987
11/1/2015,961441,1865628
12/1/2015,1363145,1954046
1/1/2016,1862878,1470741
2/1/2016,1723891,1042760
3/1/2016,1906747,1169012
4/1/2016,1963364,1927063
5/1/2016,1899735,1936915
6/1/2016,1300369,1430697
7/1/2016,1777108,1401210
8/1/2016,1597045,1566763
9/1/2016,1558287,1140057
10/1/2016,1965665,1953595
11/1/2016,1800438,937551
12/1/2016,1689152,1221895
1/1/2017,1607824,1963282
2/1/2017,1878431,1415658
3/1/2017,1730296,1947106
4/1/2017,1956756,1696780
5/1/2017,1746673,1662892
6/1/2017,989702,1537646
7/1/2017,1098812,1592064
8/1/2017,1861973,1892987
9/1/2017,1129596,1406514
10/1/2017,1528632,1725020
11/1/2017,925850,1795575`;
var margin = {
top: 50,
right: 20,
bottom: 50,
left: 80
},
width = 1400 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// parse the date / time
// look at the .csv in Notepad! DO NOT LOOK AT EXCEL!
var parseDate = d3.timeParse("%m/%d/%Y");
var sessions_desktop_col = "#CE1126",
sessions_mobile_col = "#00B6D0";
var x = d3.scaleTime()
.range([0, width - margin.left - margin.right]);
var y = d3.scaleLinear().range([height, 0]);
var z = d3.scaleOrdinal()
.range([sessions_desktop_col, sessions_mobile_col]); // red and blue
var xMonthAxis = d3.axisBottom(x)
.ticks(d3.timeMonth.every(1))
.tickFormat(d3.timeFormat("%b")); // label every month
var xYearAxis = d3.axisBottom(x)
.ticks(d3.timeYear.every(1))
.tickFormat(d3.timeFormat("%Y")); // label every year
var yAxis = d3.axisLeft(y).tickFormat(d3.format('.2s')).tickSize(-width + margin.left + margin.right);
var formatNum = d3.format(",");
var barPad = 8;
var data = d3.csvParse(dataAsCsv, function(d, i, columns) {
for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]];
d.total = t;
return d;
});
data.forEach(function(d) {
d.date = parseDate(d.date);
});
var keys = data.columns.slice(1);
data.sort(function(a, b) {
return b.date - a.date;
});
x.domain(d3.extent(data, function(d) {
return d.date
}));
var max = x.domain()[1];
var min = x.domain()[0];
var datePlusOneMonth = d3.timeDay.offset(d3.timeMonth.offset(max, 1), -1); // last day of current month: move up one month, back one day
x.domain([min, datePlusOneMonth]);
y.domain([0, d3.max(data, function(d) {
return d.total;
})]).nice();
z.domain(keys);
// x-axis
var monthAxis = g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xMonthAxis);
var defaultSliderValue = 25;
// calculate offset date based on initial slider value
var offsetDate = defaultSliderValue ? d3.timeDay.offset(d3.timeMonth.offset((new Date(datePlusOneMonth.getFullYear(), datePlusOneMonth.getMonth() - defaultSliderValue)), 1), -1) : min
// set x domain and re-render xAxis
x.domain([offsetDate, datePlusOneMonth]);
g.select('.x.axis').call(xMonthAxis);
var filteredData = data.filter(function(d) {
return d.date >= offsetDate;
});
barWidth = (width - margin.right - margin.left) / (filteredData.length + barPad);
// the bars
g.append("g").classed('bars', true)
.selectAll("g")
.data(d3.stack().keys(keys)(filteredData))
.enter().append("g")
.attr('class', function(d) {
return d.key;
})
.attr("fill", function(d) {
return z(d.key);
})
.selectAll("rect")
.data(function(d) {
return d;
})
.enter()
.append("rect")
.attr("x", function(d) {
return x(d.data.date);
})
.attr("y", function(d) {
return y(d[1]);
})
.attr("height", function(d) {
return y(d[0]) - y(d[1]);
})
.attr("width", barWidth)
.on("mousemove", function(d) {
//Get this bar's x/y values, then augment for the tooltip
var xPosition = parseFloat(d3.mouse(this)[0]) + 88;
var yPosition = parseFloat(d3.mouse(this)[1]) + 50;
var value = d.data[d3.select(this.parentNode).attr('class')]; // differentiating between col1 and col2 values
var totalCount = d.data["total"];
var valuePercent = value / totalCount;
var valueClass = d3.select(this.parentNode).attr('class').replace("col_1", "Column 1").replace("col_2", "Column 2");
//Update the tooltip position and value
d3.select("#tooltip")
.style("left", xPosition + "px")
.style("top", yPosition + "px")
d3.select("#tooltip")
.select("#count")
.text(formatNum(value)); // return the value
d3.select("#tooltip")
.select("#totalCount")
.text(formatNum(totalCount)); // return the value
d3.select("#tooltip")
.select("#percent")
.text((valuePercent * 100).toFixed(1) + "%"); // return the value
d3.select("#tooltip")
.select("#month")
.text(d3.timeFormat("%B %Y")(d.data.date)); // return the value
d3.select("#tooltip")
.select("#sessionLabel")
.text(valueClass); // return the class
//Show the tooltip
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function() {
//Hide the tooltip
d3.select("#tooltip").classed("hidden", true);
});
g.selectAll("bars")
.attr("transform", "translate(0,300)");
const firstDataYear = x.domain()[0];
if (firstDataYear.getMonth() == 11) { // When .getmonth() == 11, this shows two years overlapping with each other, making the label look ugly
const firstDataYearOffset = d3.timeDay.offset(firstDataYear, 1);
var tickValues = x.ticks().filter(function(d) {
return !d.getMonth()
});
if (tickValues.length && firstDataYearOffset.getFullYear() !== tickValues[1].getFullYear()) {
tickValues = [firstDataYearOffset].concat(tickValues);
} else {
tickValues = [firstDataYearOffset];
}
} else {
var tickValues = x.ticks().filter(function(d) {
return !d.getMonth()
});
if (tickValues.length && firstDataYear.getFullYear() !== tickValues[0].getFullYear()) {
tickValues = [firstDataYear].concat(tickValues);
} else {
tickValues = [firstDataYear];
}
}
xYearAxis.tickValues(tickValues);
var yearAxis = g.append("g")
.attr("class", "yearaxis axis")
.attr("transform", "translate(0," + (height + 25) + ")")
.call(xYearAxis);
var valueAxis = g.append("g")
.attr("class", "y axis grid")
.call(yAxis);
monthAxis.selectAll("g").select("text")
.attr("transform", "translate(" + barWidth / 2 + ",0)");
var options = d3.keys(data[0]).filter(function(key) {
return key !== "date";
}).reverse();
var legend = svg.selectAll(".legend")
.data(options.slice().filter(function(type) {
return type != "total"
}))
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(0," + i * 20 + ")";
});
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.attr('class', function(d) {
return d;
})
.style("fill", z)
.on("mouseover", function(d) {
if (d == 'col_2') {
d3.selectAll(".col_2").style("fill", '#008394');
}
if (d == 'col_1') {
d3.selectAll(".col_1").style("fill", '#80061b');
};
})
.on("mouseout", function() {
d3.selectAll(".col_2").style("fill", col_2_col);
d3.selectAll(".col_1").style("fill", col_1_col);
});
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) {
return capitalizeFirstLetter(d.replace("col_1", "Column 1").replace("col_2", "Column 2"));
})
.on("mouseover", function(d) {
if (d == 'col_2') {
d3.selectAll(".col_2").style("fill", '#008394');
}
if (d == 'col_1') {
d3.selectAll(".col_1").style("fill", '#80061b');
};
})
.on("mouseout", function() {
d3.selectAll(".col_2").style("fill", col_2_col);
d3.selectAll(".col_1").style("fill", col_1_col);
});;
// Initialize jQuery slider
$('div#month-slider').slider({
min: 1,
max: 25,
value: defaultSliderValue,
create: function() {
// add value to the handle on slider creation
$(this).find('.ui-slider-handle').html($(this).slider("value"));
},
slide: function(e, ui) {
// change values on slider handle and label based on slider value
$(e.target).find('.ui-slider-handle').html(ui.value);
// calculate offset date based on slider value
var offsetDate = ui.value ? d3.timeDay.offset(d3.timeMonth.offset((new Date(datePlusOneMonth.getFullYear(), datePlusOneMonth.getMonth() - ui.value)), 1), -1) : min
// set x domain and re-render xAxis
x.domain([offsetDate, datePlusOneMonth]);
g.select('.x.axis').call(xMonthAxis);
const firstDataYear = x.domain()[0];
if (firstDataYear.getMonth() == 11) { // When .getmonth() == 11, this shows two years overlapping with each other, making the label look ugly
const firstDataYearOffset = d3.timeDay.offset(firstDataYear, 1);
var tickValues = x.ticks().filter(function(d) {
return !d.getMonth()
});
if (tickValues.length == 2 && firstDataYearOffset.getFullYear() !== tickValues[1].getFullYear()) {
tickValues = [firstDataYearOffset].concat(tickValues);
} else {
tickValues = [firstDataYearOffset];
}
} else {
var tickValues = x.ticks().filter(function(d) {
return !d.getMonth()
});
if (tickValues.length && firstDataYear.getFullYear() !== tickValues[0].getFullYear()) {
tickValues = [firstDataYear].concat(tickValues);
} else {
tickValues = [firstDataYear];
}
}
xYearAxis.tickValues(tickValues);
g.select('.yearaxis.axis').call(xYearAxis);
// calculate filtered data based on new offset date, set y axis domain and re-render y axis
var filteredData = data.filter(function(d) {
return d.date >= offsetDate;
});
y.domain([0, d3.max(filteredData, function(d) {
return d.total;
})]).nice();
g.select('.y.axis').transition().duration(200).call(yAxis);
// re-render the bars based on new filtered data
// the bars
var bars = g.select("g.bars")
.selectAll("g")
.data(d3.stack().keys(keys)(filteredData));
var barRects = bars.enter().append("g").merge(bars)
.attr('class', function(d) {
return d.key;
})
.attr("fill", function(d) {
return z(d.key);
})
.selectAll("rect")
.data(function(d) {
return d;
});
barRects.exit().remove();
barRects.enter()
.append("rect");
barWidth = (width - margin.right - margin.left) / (filteredData.length + barPad);
monthAxis.selectAll("g").select("text")
.attr("transform", "translate(" + barWidth / 2 + ",0)");
g.select("g.bars").selectAll('g rect')
.attr("x", function(d) {
return x(d.data.date);
})
.attr("y", function(d) {
return y(d[1]);
})
.attr("height", function(d) {
return y(d[0]) - y(d[1]);
})
.attr("width", barWidth)
.on("mousemove", function(d) {
//Get this bar's x/y values, then augment for the tooltip
var xPosition = parseFloat(d3.mouse(this)[0]) + 88;
var yPosition = parseFloat(d3.mouse(this)[1]) + 50;
var value = d.data[d3.select(this.parentNode).attr('class')]; // differentiating between col1 and col2 values
var totalCount = d.data["total"];
var valuePercent = value / totalCount;
var valueClass = d3.select(this.parentNode).attr('class').replace("col_1", "Column 1").replace("col_2", "Column 2");
//Update the tooltip position and value
d3.select("#tooltip")
.style("left", xPosition + "px")
.style("top", yPosition + "px")
d3.select("#tooltip")
.select("#count")
.text(formatNum(value)); // return the value
d3.select("#tooltip")
.select("#totalCount")
.text(formatNum(totalCount)); // return the value
d3.select("#tooltip")
.select("#percent")
.text((valuePercent * 100).toFixed(1) + "%"); // return the value
d3.select("#tooltip")
.select("#month")
.text(d3.timeFormat("%B %Y")(d.data.date)); // return the value
d3.select("#tooltip")
.select("#sessionLabel")
.text(valueClass); // return the class
//Show the tooltip
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function() {
//Hide the tooltip
d3.select("#tooltip").classed("hidden", true);
});
}
});
#tooltip {
position: absolute;
width: 290px;
z-index: 2;
height: auto;
padding: 10px;
background-color: white;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
-webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
-moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
pointer-events: none;
}
.legend {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 60%;
}
#tooltip.hidden {
display: none;
}
#tooltip p {
margin: 0;
font-family: sans-serif;
font-size: 16px;
line-height: 20px;
}
g[class="col_1"] rect:hover {
fill: #80061b;
}
g[class="col_2"] rect:hover {
fill: #008394;
}
div.slider-container {
margin: 20px 20px 20px 80px;
}
div#month-slider {
width: 50%;
margin: 0px 0px 0px 330px;
background: #e3eff4;
}
div#month-slider .ui-slider .ui-slider-handle {
width: 25%;
}
div#month-slider .ui-slider-handle {
text-align: center;
}
.title {
font-size: 30px;
font-weight: bold;
}
.grid line {
stroke: lightgrey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
pointer-events: none;
}
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.0/jquery-ui.min.js" integrity="sha256-eGE6blurk5sHj+rmkfsGYeKyZx3M4bG+ZlFyA7Kns7E=" crossorigin="anonymous"></script>
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/1.7.0/d3-legend.min.js"></script>
<body>
<div id="tooltip" class="hidden">
<p><strong>Month: </strong><span id="month"></span>
<p>
<p><strong><span id="sessionLabel"></span>: </strong><span id="count"></span> (<span id="percent"></span>)</p>
<p><strong>Total: </strong><span id="totalCount"></span></p>
</div>
<div class="slider-container" style='inline-block;'>
<span style='margin-left:6em;display:inline-block;'><strong>Number of Months Displayed:</strong></span>
<div id="month-slider" style='inline-block;'></div>
</div>
The main problem I have with this is that the slider label and the slider are not vertically aligned:
It looks like that the main problem behind this is the width of the #month-slider:
My thought would be that the text would need to be in #month-slider somehow, to the left of the physical slider. I'm not sure how to do this, though. As you can see in the HTML code above, I physically put the "Number of Months Displayed" text in the slide-container div.
One solution would to be to simply put position:relative;top:15px; in the span tag.
Although it should be used sparingly in situations like this as it can easly overlap with other elements in your code forcing you to change z-index layer.
Here's the code:
var dataAsCsv = `date,col_1,col_2
11/1/2012,1977652,1802851
12/1/2012,1128739,948687
1/1/2013,1201944,1514667
2/1/2013,1863148,1834006
3/1/2013,1314851,1906060
4/1/2013,1283943,1978702
5/1/2013,1127964,1195606
6/1/2013,1773254,977214
7/1/2013,1929574,1127450
8/1/2013,1980411,1808161
9/1/2013,1405691,1182788
10/1/2013,1336790,937890
11/1/2013,1851053,1358400
12/1/2013,1472623,1214610
1/1/2014,1155116,1757052
2/1/2014,1571611,1935038
3/1/2014,1898348,1320348
4/1/2014,1444838,1934789
5/1/2014,1235087,950194
6/1/2014,1272040,1580656
7/1/2014,980781,1680164
8/1/2014,1391291,1115999
9/1/2014,1211125,1542148
10/1/2014,1020824,1782795
11/1/2014,1685081,926612
12/1/2014,1469254,1767071
1/1/2015,1168523,935897
2/1/2015,1602610,1450541
3/1/2015,1830278,1354876
4/1/2015,1275158,1412555
5/1/2015,1560961,1839718
6/1/2015,949948,1587130
7/1/2015,1413765,1494446
8/1/2015,1166141,1305105
9/1/2015,958975,1202219
10/1/2015,902696,1023987
11/1/2015,961441,1865628
12/1/2015,1363145,1954046
1/1/2016,1862878,1470741
2/1/2016,1723891,1042760
3/1/2016,1906747,1169012
4/1/2016,1963364,1927063
5/1/2016,1899735,1936915
6/1/2016,1300369,1430697
7/1/2016,1777108,1401210
8/1/2016,1597045,1566763
9/1/2016,1558287,1140057
10/1/2016,1965665,1953595
11/1/2016,1800438,937551
12/1/2016,1689152,1221895
1/1/2017,1607824,1963282
2/1/2017,1878431,1415658
3/1/2017,1730296,1947106
4/1/2017,1956756,1696780
5/1/2017,1746673,1662892
6/1/2017,989702,1537646
7/1/2017,1098812,1592064
8/1/2017,1861973,1892987
9/1/2017,1129596,1406514
10/1/2017,1528632,1725020
11/1/2017,925850,1795575`;
var margin = {top: 50, right: 20, bottom: 50, left: 80},
width = 1400 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// parse the date / time
// look at the .csv in Notepad! DO NOT LOOK AT EXCEL!
var parseDate = d3.timeParse("%m/%d/%Y");
var sessions_desktop_col = "#CE1126",
sessions_mobile_col = "#00B6D0";
var x = d3.scaleTime()
.range([0, width - margin.left - margin.right]);
var y = d3.scaleLinear().range([height, 0]);
var z = d3.scaleOrdinal()
.range([sessions_desktop_col, sessions_mobile_col]); // red and blue
var xMonthAxis = d3.axisBottom(x)
.ticks(d3.timeMonth.every(1))
.tickFormat(d3.timeFormat("%b")); // label every month
var xYearAxis = d3.axisBottom(x)
.ticks(d3.timeYear.every(1))
.tickFormat(d3.timeFormat("%Y")); // label every year
var yAxis = d3.axisLeft(y).tickFormat(d3.format('.2s')).tickSize(-width+margin.left+margin.right);
var formatNum = d3.format(",");
var barPad = 8;
var data = d3.csvParse(dataAsCsv, function(d, i, columns) {
for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]];
d.total = t;
return d;
});
data.forEach(function(d) {
d.date = parseDate(d.date);
});
var keys = data.columns.slice(1);
data.sort(function(a, b) { return b.date - a.date; });
x.domain(d3.extent( data, function(d){ return d.date }) );
var max = x.domain()[1];
var min = x.domain()[0];
var datePlusOneMonth = d3.timeDay.offset(d3.timeMonth.offset(max, 1), -1); // last day of current month: move up one month, back one day
x.domain([min,datePlusOneMonth]);
y.domain([0, d3.max(data, function(d) { return d.total; })]).nice();
z.domain(keys);
// x-axis
var monthAxis = g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xMonthAxis);
var defaultSliderValue = 25;
// calculate offset date based on initial slider value
var offsetDate = defaultSliderValue ? d3.timeDay.offset(d3.timeMonth.offset((new Date(datePlusOneMonth.getFullYear(), datePlusOneMonth.getMonth()-defaultSliderValue)), 1), -1) : min
// set x domain and re-render xAxis
x.domain([offsetDate, datePlusOneMonth]);
g.select('.x.axis').call(xMonthAxis);
var filteredData = data.filter(function(d) { return d.date >= offsetDate; });
barWidth = (width - margin.right- margin.left)/(filteredData.length+barPad);
// the bars
g.append("g").classed('bars', true)
.selectAll("g")
.data(d3.stack().keys(keys)(filteredData))
.enter().append("g")
.attr('class', function(d) { return d.key; })
.attr("fill", function(d) { return z(d.key); })
.selectAll("rect")
.data(function(d) { return d; })
.enter()
.append("rect")
.attr("x", function(d) { return x(d.data.date); })
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[1]); })
.attr("width", barWidth)
.on("mousemove", function(d) {
//Get this bar's x/y values, then augment for the tooltip
var xPosition = parseFloat(d3.mouse(this)[0]) + 88;
var yPosition = parseFloat(d3.mouse(this)[1]) + 50;
var value = d.data[d3.select(this.parentNode).attr('class')]; // differentiating between col1 and col2 values
var totalCount = d.data["total"];
var valuePercent = value/totalCount;
var valueClass = d3.select(this.parentNode).attr('class').replace("col_1", "Column 1").replace("col_2", "Column 2");
//Update the tooltip position and value
d3.select("#tooltip")
.style("left", xPosition + "px")
.style("top", yPosition + "px")
d3.select("#tooltip")
.select("#count")
.text(formatNum(value)); // return the value
d3.select("#tooltip")
.select("#totalCount")
.text(formatNum(totalCount)); // return the value
d3.select("#tooltip")
.select("#percent")
.text((valuePercent * 100).toFixed(1) + "%"); // return the value
d3.select("#tooltip")
.select("#month")
.text(d3.timeFormat("%B %Y")(d.data.date)); // return the value
d3.select("#tooltip")
.select("#sessionLabel")
.text(valueClass); // return the class
//Show the tooltip
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function() {
//Hide the tooltip
d3.select("#tooltip").classed("hidden", true);
});
g.selectAll("bars")
.attr("transform", "translate(0,300)");
const firstDataYear = x.domain()[0];
if (firstDataYear.getMonth() == 11){ // When .getmonth() == 11, this shows two years overlapping with each other, making the label look ugly
const firstDataYearOffset = d3.timeDay.offset(firstDataYear, 1);
var tickValues = x.ticks().filter(function(d) { return !d.getMonth()});
if(tickValues.length && firstDataYearOffset.getFullYear() !== tickValues[1].getFullYear()) {
tickValues = [firstDataYearOffset].concat(tickValues);
} else {
tickValues = [firstDataYearOffset];
}
} else {
var tickValues = x.ticks().filter(function(d) { return !d.getMonth()});
if(tickValues.length && firstDataYear.getFullYear() !== tickValues[0].getFullYear()) {
tickValues = [firstDataYear].concat(tickValues);
} else {
tickValues = [firstDataYear];
}
}
xYearAxis.tickValues(tickValues);
var yearAxis = g.append("g")
.attr("class", "yearaxis axis")
.attr("transform", "translate(0," + (height + 25) + ")")
.call(xYearAxis);
var valueAxis = g.append("g")
.attr("class", "y axis grid")
.call(yAxis);
monthAxis.selectAll("g").select("text")
.attr("transform","translate(" + barWidth/2 + ",0)");
var options = d3.keys(data[0]).filter(function(key) { return key !== "date"; }).reverse();
var legend = svg.selectAll(".legend")
.data(options.slice().filter(function(type){ return type != "total"}))
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.attr('class', function(d) { return d; })
.style("fill", z)
.on("mouseover", function(d){
if (d == 'col_2') {
d3.selectAll(".col_2").style("fill", '#008394');
}
if (d == 'col_1') {
d3.selectAll(".col_1").style("fill", '#80061b');
};
})
.on("mouseout", function(){
d3.selectAll(".col_2").style("fill", col_2_col);
d3.selectAll(".col_1").style("fill", col_1_col);
});
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return capitalizeFirstLetter(d.replace("col_1", "Column 1").replace("col_2", "Column 2")); })
.on("mouseover", function(d){
if (d == 'col_2') {
d3.selectAll(".col_2").style("fill", '#008394');
}
if (d == 'col_1') {
d3.selectAll(".col_1").style("fill", '#80061b');
};
})
.on("mouseout", function(){
d3.selectAll(".col_2").style("fill", col_2_col);
d3.selectAll(".col_1").style("fill", col_1_col);
});;
// Initialize jQuery slider
$('div#month-slider').slider({
min: 1,
max: 25,
value: defaultSliderValue,
create: function() {
// add value to the handle on slider creation
$(this).find('.ui-slider-handle').html($( this ).slider( "value" ));
},
slide: function(e, ui) {
// change values on slider handle and label based on slider value
$(e.target).find('.ui-slider-handle').html(ui.value);
// calculate offset date based on slider value
var offsetDate = ui.value ? d3.timeDay.offset(d3.timeMonth.offset((new Date(datePlusOneMonth.getFullYear(), datePlusOneMonth.getMonth()-ui.value)), 1), -1) : min
// set x domain and re-render xAxis
x.domain([offsetDate, datePlusOneMonth]);
g.select('.x.axis').call(xMonthAxis);
const firstDataYear = x.domain()[0];
if (firstDataYear.getMonth() == 11){ // When .getmonth() == 11, this shows two years overlapping with each other, making the label look ugly
const firstDataYearOffset = d3.timeDay.offset(firstDataYear, 1);
var tickValues = x.ticks().filter(function(d) { return !d.getMonth()});
if(tickValues.length == 2 && firstDataYearOffset.getFullYear() !== tickValues[1].getFullYear()) {
tickValues = [firstDataYearOffset].concat(tickValues);
} else {
tickValues = [firstDataYearOffset];
}
} else {
var tickValues = x.ticks().filter(function(d) { return !d.getMonth()});
if(tickValues.length && firstDataYear.getFullYear() !== tickValues[0].getFullYear()) {
tickValues = [firstDataYear].concat(tickValues);
} else {
tickValues = [firstDataYear];
}
}
xYearAxis.tickValues(tickValues);
g.select('.yearaxis.axis').call(xYearAxis);
// calculate filtered data based on new offset date, set y axis domain and re-render y axis
var filteredData = data.filter(function(d) { return d.date >= offsetDate; });
y.domain([0, d3.max(filteredData, function(d) { return d.total; })]).nice();
g.select('.y.axis').transition().duration(200).call(yAxis);
// re-render the bars based on new filtered data
// the bars
var bars = g.select("g.bars")
.selectAll("g")
.data(d3.stack().keys(keys)(filteredData));
var barRects = bars.enter().append("g").merge(bars)
.attr('class', function(d) { return d.key; })
.attr("fill", function(d) { return z(d.key); })
.selectAll("rect")
.data(function(d) { return d; });
barRects.exit().remove();
barRects.enter()
.append("rect");
barWidth = (width - margin.right- margin.left)/(filteredData.length+barPad);
monthAxis.selectAll("g").select("text")
.attr("transform","translate(" + barWidth/2 + ",0)");
g.select("g.bars").selectAll('g rect')
.attr("x", function(d) { return x(d.data.date); })
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[1]); })
.attr("width", barWidth)
.on("mousemove", function(d) {
//Get this bar's x/y values, then augment for the tooltip
var xPosition = parseFloat(d3.mouse(this)[0]) + 88;
var yPosition = parseFloat(d3.mouse(this)[1]) + 50;
var value = d.data[d3.select(this.parentNode).attr('class')]; // differentiating between col1 and col2 values
var totalCount = d.data["total"];
var valuePercent = value/totalCount;
var valueClass = d3.select(this.parentNode).attr('class').replace("col_1", "Column 1").replace("col_2", "Column 2");
//Update the tooltip position and value
d3.select("#tooltip")
.style("left", xPosition + "px")
.style("top", yPosition + "px")
d3.select("#tooltip")
.select("#count")
.text(formatNum(value)); // return the value
d3.select("#tooltip")
.select("#totalCount")
.text(formatNum(totalCount)); // return the value
d3.select("#tooltip")
.select("#percent")
.text((valuePercent * 100).toFixed(1) + "%"); // return the value
d3.select("#tooltip")
.select("#month")
.text(d3.timeFormat("%B %Y")(d.data.date)); // return the value
d3.select("#tooltip")
.select("#sessionLabel")
.text(valueClass); // return the class
//Show the tooltip
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function() {
//Hide the tooltip
d3.select("#tooltip").classed("hidden", true);
});
}
});
#tooltip {
position: absolute;
width: 290px;
z-index: 2;
height: auto;
padding: 10px;
background-color: white;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
-webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
-moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
pointer-events: none;
}
.legend {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 60%;
}
#tooltip.hidden {
display: none;
}
#tooltip p {
margin: 0;
font-family: sans-serif;
font-size: 16px;
line-height: 20px;
}
g[class="col_1"] rect:hover {
fill:#80061b;
}
g[class="col_2"] rect:hover {
fill:#008394;
}
div.slider-container {
margin: 20px 20px 20px 80px;
}
div#month-slider {
width: 50%;
margin: 0px 0px 0px 330px;
background:#e3eff4;
}
div#month-slider .ui-slider .ui-slider-handle {
width: 25%;
}
div#month-slider .ui-slider-handle {
text-align: center;
}
.title {
font-size: 30px;
font-weight: bold;
}
.grid line {
stroke: lightgrey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
pointer-events: none;
}
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script
src="https://code.jquery.com/ui/1.12.0/jquery-ui.min.js"
integrity="sha256-eGE6blurk5sHj+rmkfsGYeKyZx3M4bG+ZlFyA7Kns7E="
crossorigin="anonymous"></script>
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/1.7.0/d3-legend.min.js"></script>
<body>
<div id="tooltip" class="hidden">
<p><strong>Month: </strong><span id="month"></span><p>
<p><strong><span id="sessionLabel"></span>: </strong><span id="count"></span> (<span id="percent"></span>)</p>
<p><strong>Total: </strong><span id="totalCount"></span></p>
</div>
<div class="slider-container" style='inline-block;'>
<span style='margin-left:6em;display:inline-block;position:relative;top:15px;'><strong>Number of Months Displayed:</strong></span><div id="month-slider" style='inline-block;'></div>
</div>
Related
I have created an horizontal grouped stacked chart using D3. Everything seemed perfect until I narrowed down the values to two. In the snippet below, if you replace the json_data with this:
var json_data = {"headers":["Month","Country","Number"],"rows":[["2018-05-01 00:00:00.0","France",7],["2018-05-01 00:00:00.0","Germany",19],["2018-05-01 00:00:00.0","Italy",35],["2018-05-01 00:00:00.0","Spain",40],["2018-05-01 00:00:00.0","UK",23],["2018-04-01 00:00:00.0","France",14],["2018-04-01 00:00:00.0","Germany",21],["2018-04-01 00:00:00.0","Italy",37],["2018-04-01 00:00:00.0","Spain",32],["2018-04-01 00:00:00.0","UK",129]
]};
everything works fine and the chart looks responsive:
However, considering that in my snippet I have two values (UK, Germany), the bars overlap. I tried playing with this line:
console.log(d3.scale.ordinal().rangeBands([height, 0], 0.2) );
but I can't think of a way to make the bars responsive no matter what the number of values is.
Snippet:
/* ----- Data ----- */
var json_data = {"headers":["Month","Country","Number"],"rows":[["2018-05-01 00:00:00.0","Germany",19],["2018-05-01 00:00:00.0","United Kingdom",23],["2018-04-01 00:00:00.0","Germany",21],["2018-04-01 00:00:00.0","United Kingdom",129]
]};
var dataRows = json_data.rows;
/* ----- !Data ----- */
/* ----- Functions ----- */
//Create dictionary function (transformed JSON)
createDict = (data) => {
let groups = data.reduce((acc, arr) => {
if (acc.hasOwnProperty(arr[1])) {
acc[arr[1]].push(arr);
} else {
acc[arr[1]] = [arr];
}
return acc;
}, {});
let results = [];
for (let g in groups) {
let obj = {Value: g};
let a = groups[g][0];
let b = groups[g][1];
if (a[0] <= b[0]) {
obj.num = a[2];
obj.num2 = b[2];
} else {
obj.num = b[2];
obj.num2 = a[2];
}
results.push(obj);
}
return results;
}
//Returns unique values of a specific object of a JSON string
uniqueValues = (data,objectNum) => {
var uniqueValues = [];
data.forEach(function(item) {
var value = item[objectNum];
if (uniqueValues.indexOf(value) !== -1)
return false;
uniqueValues.push(value);
});
return uniqueValues;
}
//Chart creation function
createChart = (data) => {
//Margin conventions
console.log(data)
var margin = {top: 10, right: 50, bottom: 20, left: 70};
var widther = window.outerWidth;
var width = widther - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
//Appends the svg to the chart-container div
var svg = d3.select(".g-chart").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 + ")");
//Creates the xScale
var xScale = d3.scale.linear()
.range([0,width]);
//Creates the yScale
var y0 = d3.scale.ordinal()
.rangeBands([height, 0], 0.2)
.domain(uniqueValues);
console.log(d3.scale.ordinal().rangeBands([height, 0], 0.2) );
//.domain(["Spain", "UK", "Germany", "France", "Italy"]);
//Defines the y axis styles
var yAxis = d3.svg.axis()
.scale(y0)
.orient("left");
//Defines the y axis styles
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.tickFormat(function(d) {return d; })
//Change axis values for percentage
//.tickFormat(function(d) {return d + "%"; })
.tickSize(height)
.ticks(numTicks(width));
//FORMAT data
data.forEach(function(d) {
d.num = +d.num;
});
//Sets the max for the xScale
var maxX = d3.max(data, function(d) { return d.num; });
//Defines the xScale max
xScale.domain([0, maxX ]);
//Appends the y axis
var yAxisGroup = svg.append("g")
.attr("class", "y axis")
.call(yAxis);
//Appends the x axis
var xAxisGroup = svg.append("g")
.attr("class", "x axis")
.call(xAxis);
//Binds the data to the bars
var categoryGroup = svg.selectAll(".g-category-group")
.data(data)
.enter()
.append("g")
.attr("class", "g-category-group")
.attr("transform", function(d) {
return "translate(0," + y0(d.Value) + ")";
});
//Appends first bar
var bars = categoryGroup.append("rect")
.attr("width", function(d) { return xScale(d.num); })
.attr("height", y0.rangeBand()/2.5 )
.attr("class", "g-num")
.attr("transform", "translate(0,4)");
//Appends second bar
var bars2 = categoryGroup.append("rect")
.attr("width", function(d) { return xScale(d.num2); })
.attr("height", y0.rangeBand()/2.5 )
.attr("class", "g-num2")
.attr("transform", "translate(0,29)");
//Binds data to labels
var labelGroup = svg.selectAll("g-num")
.data(data)
.enter()
.append("g")
.attr("class", "g-label-group")
.attr("transform", function(d) {
return "translate(0," + y0(d.Value) + ")";
});
//Appends first bar labels
var barLabels = labelGroup.append("text")
.text(function(d) {return d.num;})
.attr("x", function(d) { return xScale(d.num) - 20; })
.attr("y", y0.rangeBand()/2.65 )
.attr("class", "g-labels");
//Appends second bar labels
var barLabels2 = labelGroup.append("text")
.text(function(d) {return d.num2;})
.attr("x", function(d) { return xScale(d.num2) - 20; })
.attr("y", y0.rangeBand()/1.25 )
.attr("class", "g-labels");
//Appends chart source
d3.select(".g-source-bold")
.text("SOURCE: ")
.attr("class", "g-source-bold");
d3.select(".g-source-reg")
.text("Chart source info goes here")
.attr("class", "g-source-reg");
//RESPONSIVENESS
d3.select(window).on("resize", resized);
function resized() {
//new margin
var newMargin = {top: 10, right: 80, bottom: 20, left: 50};
//Get the width of the window
var w = d3.select(".g-chart").node().clientWidth;
console.log("resized", w);
//Change the width of the svg
d3.select("svg")
.attr("width", w);
//Change the xScale
xScale
.range([0, w - newMargin.right]);
//Update the bars
bars
.attr("width", function(d) { return xScale(d.num); });
//Update the second bars
bars2
.attr("width", function(d) { return xScale(d.num2); });
//Updates bar labels
barLabels
.attr("x", function(d) { return xScale(d.num) - 20; })
.attr("y", y0.rangeBand()/2.65 )
//Updates second bar labels
barLabels2
.attr("x", function(d) { return xScale(d.num2) - 20; })
.attr("y", y0.rangeBand()/1.25 )
//Updates xAxis
xAxisGroup
.call(xAxis);
//Updates ticks
xAxis
.scale(xScale)
.ticks(numTicks(w));
};
//}
//Determines number of ticks base on width
function numTicks(widther) {
if (widther <= 400) {
return 4
console.log("return 4")
}
else {
return 10
console.log("return 5")
}
}
}
/* ----- !Functions ----- */
/* ----- Main ----- */
var data = createDict(dataRows);
//Calculate unique Values
var uniqueValues = uniqueValues(dataRows,1);
createChart(data);
/* ----- !Main ----- */
/*css to go here*/
#import url(https://fonts.googleapis.com/css?family=Karla);
body {
font-family: 'Karla', sans-serif;
font-size: 12px;
}
.g-hed {
text-align: left;
text-transform: uppercase;
font-weight: bold;
font-size:22px;
margin: 3px 0;
}
.g-source-bold {
text-align: left;
font-size:10px;
font-weight: bold;
}
.g-source {
margin: 10px 0;
}
.g-source-bold {
text-align: left;
font-size:10px;
}
.g-intro {
font-size: 16px;
margin: 0px 0px 10px 0px;
}
.g-num {
fill:#124;
}
.g-num2 {
fill:#ccc;
}
.g-labels {
fill: white;
font-weight: bold;
font-size: 13px;
}
.axis line {
fill: none;
stroke: #ccc;
stroke-dasharray: 2px 3px;
shape-rendering: crispEdges;
stroke-width: 1px;
}
.axis text {
font-size: 13px;
pointer-events: none;
fill: #7e7e7e;
}
.domain {
display: none;
}
.y.axis text {
text-anchor: end !important;
font-size:14px;
fill: #000000;
}
.y.axis line {
display: none;
}
.g-baseline line {
stroke:#000;
stroke-width: 1px;
stroke-dasharray:none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js" charset="utf-8"></script>
<body>
<h5 class="g-hed"></h5>
<p class="g-intro"></p>
<div class="g-chart"></div>
<div class="g-source"><span class="g-source-bold"></span><span class="g-source-reg"></span></div>
</div>
</body>
Your problem is with the offset of the bars from each other; if you hard-code a value, e.g. using translate(0,29), you are going to be in trouble when the chart data changes, and that makes the bars change size, too. To prevent that happening, set the translate value relative to the size of the bar:
//Appends first bar
var bars = categoryGroup.append("rect")
.attr("width", function(d) { return xScale(d.num); })
.attr("height", y0.rangeBand()/2.5 )
.attr("class", "g-num")
var bars2 = categoryGroup.append("rect")
.attr("width", function(d) { return xScale(d.num2); })
.attr("height", y0.rangeBand()/2.5 )
.attr("class", "g-num2")
.attr("transform", "translate(0," + ( y0.rangeBand()/2.5 ) + ")");
This way, no matter how many or how few bars you have in the chart, the offset of bars2 will always be the same size as the height of the bars, i.e. y0.rangeBand()/2.5.
I'd advise you to standardise the bar label position in a similar manner, but add in a fixed y offset using the dy attribute:
//Appends first bar labels
var barLabels = labelGroup.append("text")
.text(function(d) {return d.num;})
.attr("x", function(d) { return xScale(d.num) - 20; })
.attr("y", y0.rangeBand()/2.5 ) // note: use 2.5, rather than 2.65
.attr('dy', '-0.35em') // fixed y offset, set relative to the text size
.attr("class", "g-labels");
//Appends second bar labels
var barLabels2 = labelGroup.append("text")
.text(function(d) {return d.num2;})
.attr("x", function(d) { return xScale(d.num2) - 20; })
.attr("y", y0.rangeBand()/1.25 )
.attr('dy', '-0.35em') // fixed y offset
.attr("class", "g-labels");
Here's your full example:
/* ----- Data ----- */
var json_data = {"headers":["Month","Country","Number"],"rows":[["2018-05-01 00:00:00.0","Germany",19],["2018-05-01 00:00:00.0","United Kingdom",23],["2018-04-01 00:00:00.0","Germany",21],["2018-04-01 00:00:00.0","United Kingdom",129] ]};
var dataRows = json_data.rows;
/* ----- !Data ----- */
/* ----- Functions ----- */
//Create dictionary function (transformed JSON)
createDict = (data) => {
let groups = data.reduce((acc, arr) => {
if (acc.hasOwnProperty(arr[1])) {
acc[arr[1]].push(arr);
} else {
acc[arr[1]] = [arr];
}
return acc;
}, {});
let results = [];
for (let g in groups) {
let obj = {Value: g};
let a = groups[g][0];
let b = groups[g][1];
if (a[0] <= b[0]) {
obj.num = a[2];
obj.num2 = b[2];
} else {
obj.num = b[2];
obj.num2 = a[2];
}
results.push(obj);
}
return results;
}
//Returns unique values of a specific object of a JSON string
uniqueValues = (data,objectNum) => {
var uniqueValues = [];
data.forEach(function(item) {
var value = item[objectNum];
if (uniqueValues.indexOf(value) !== -1)
return false;
uniqueValues.push(value);
});
return uniqueValues;
}
//Chart creation function
createChart = (data) => {
//Margin conventions
console.log(data)
var margin = {top: 10, right: 50, bottom: 20, left: 70};
var widther = window.outerWidth;
var width = widther - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
//Appends the svg to the chart-container div
var svg = d3.select(".g-chart").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 + ")");
//Creates the xScale
var xScale = d3.scale.linear()
.range([0,width]);
//Creates the yScale
var y0 = d3.scale.ordinal()
.rangeBands([height, 0], 0.2)
.domain(uniqueValues);
console.log(d3.scale.ordinal().rangeBands([height, 0], 0.2) );
//.domain(["Spain", "UK", "Germany", "France", "Italy"]);
//Defines the y axis styles
var yAxis = d3.svg.axis()
.scale(y0)
.orient("left");
//Defines the y axis styles
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.tickFormat(function(d) {return d; })
//Change axis values for percentage
//.tickFormat(function(d) {return d + "%"; })
.tickSize(height)
.ticks(numTicks(width));
//FORMAT data
data.forEach(function(d) {
d.num = +d.num;
});
//Sets the max for the xScale
var maxX = d3.max(data, function(d) { return d.num; });
//Defines the xScale max
xScale.domain([0, maxX ]);
//Appends the y axis
var yAxisGroup = svg.append("g")
.attr("class", "y axis")
.call(yAxis);
//Appends the x axis
var xAxisGroup = svg.append("g")
.attr("class", "x axis")
.call(xAxis);
//Binds the data to the bars
var categoryGroup = svg.selectAll(".g-category-group")
.data(data)
.enter()
.append("g")
.attr("class", "g-category-group")
.attr("transform", function(d) {
return "translate(0," + y0(d.Value) + ")";
});
//Appends first bar
var bars = categoryGroup.append("rect")
.attr("width", function(d) { return xScale(d.num); })
.attr("height", y0.rangeBand()/2.5 )
.attr("class", "g-num")
// .attr("transform", "translate(0,4)");
//Appends second bar
var bars2 = categoryGroup.append("rect")
.attr("width", function(d) { return xScale(d.num2); })
.attr("height", y0.rangeBand()/2.5 )
.attr("class", "g-num2")
.attr("transform", "translate(0," + ( y0.rangeBand()/2.5 ) + ")");
//Binds data to labels
var labelGroup = svg.selectAll("g-num")
.data(data)
.enter()
.append("g")
.attr("class", "g-label-group")
.attr("transform", function(d) {
return "translate(0," + y0(d.Value) + ")";
});
//Appends first bar labels
var barLabels = labelGroup.append("text")
.text(function(d) {return d.num;})
.attr("x", function(d) { return xScale(d.num) - 20; })
.attr("y", y0.rangeBand()/2.5 )
.attr('dy', '-0.35em')
.attr("class", "g-labels");
//Appends second bar labels
var barLabels2 = labelGroup.append("text")
.text(function(d) {return d.num2;})
.attr("x", function(d) { return xScale(d.num2) - 20; })
.attr("y", y0.rangeBand()/1.25 )
.attr('dy', '-0.35em')
.attr("class", "g-labels");
//Appends chart source
d3.select(".g-source-bold")
.text("SOURCE: ")
.attr("class", "g-source-bold");
d3.select(".g-source-reg")
.text("Chart source info goes here")
.attr("class", "g-source-reg");
//RESPONSIVENESS
d3.select(window).on("resize", resized);
function resized() {
//new margin
var newMargin = {top: 10, right: 80, bottom: 20, left: 50};
//Get the width of the window
var w = d3.select(".g-chart").node().clientWidth;
console.log("resized", w);
//Change the width of the svg
d3.select("svg")
.attr("width", w);
//Change the xScale
xScale
.range([0, w - newMargin.right]);
//Update the bars
bars
.attr("width", function(d) { return xScale(d.num); });
//Update the second bars
bars2
.attr("width", function(d) { return xScale(d.num2); });
//Updates bar labels
barLabels
.attr("x", function(d) { return xScale(d.num) - 20; })
.attr("y", y0.rangeBand()/2.65 )
//Updates second bar labels
barLabels2
.attr("x", function(d) { return xScale(d.num2) - 20; })
.attr("y", y0.rangeBand()/1.25 )
//Updates xAxis
xAxisGroup
.call(xAxis);
//Updates ticks
xAxis
.scale(xScale)
.ticks(numTicks(w));
};
//}
//Determines number of ticks base on width
function numTicks(widther) {
if (widther <= 400) {
return 4
console.log("return 4")
}
else {
return 10
console.log("return 5")
}
}
}
/* ----- !Functions ----- */
/* ----- Main ----- */
var data = createDict(dataRows);
//Calculate unique Values
var uniqueValues = uniqueValues(dataRows,1);
createChart(data);
/* ----- !Main ----- */
/*css to go here*/
#import url(https://fonts.googleapis.com/css?family=Karla);
body {
font-family: 'Karla', sans-serif;
font-size: 12px;
}
.g-hed {
text-align: left;
text-transform: uppercase;
font-weight: bold;
font-size:22px;
margin: 3px 0;
}
.g-source-bold {
text-align: left;
font-size:10px;
font-weight: bold;
}
.g-source {
margin: 10px 0;
}
.g-source-bold {
text-align: left;
font-size:10px;
}
.g-intro {
font-size: 16px;
margin: 0px 0px 10px 0px;
}
.g-num {
fill:#124;
}
.g-num2 {
fill:#ccc;
}
.g-labels {
fill: white;
font-weight: bold;
font-size: 13px;
}
.axis line {
fill: none;
stroke: #ccc;
stroke-dasharray: 2px 3px;
shape-rendering: crispEdges;
stroke-width: 1px;
}
.axis text {
font-size: 13px;
pointer-events: none;
fill: #7e7e7e;
}
.domain {
display: none;
}
.y.axis text {
text-anchor: end !important;
font-size:14px;
fill: #000000;
}
.y.axis line {
display: none;
}
.g-baseline line {
stroke:#000;
stroke-width: 1px;
stroke-dasharray:none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js" charset="utf-8"></script>
<body>
<h5 class="g-hed"></h5>
<p class="g-intro"></p>
<div class="g-chart"></div>
<div class="g-source"><span class="g-source-bold"></span><span class="g-source-reg"></span></div>
</div>
</body>
I am creating a brushable bar chart, in the Y axis I need to have grouped chart with brushable content, whenever I am moving the brush, the graph is not getting refreshing.
Used the below code for creating the additional bar,
var bar2 = bar.enter().append("rect")
.attr("class", "bar2")
.attr("id","lesser")
.style("fill", "#ff7f0e")
.attr("y", function(d,i) { return main_yScale(d.country); })
.attr("height", main_yScale.rangeBand()/2)
.attr("x", 0)
.transition().duration(50)
.attr("width", function(d) { return main_xScale(d.result); });
I have tried in http://jsfiddle.net/mouneshp777/7xp10awb/1/
I am not able to fix the issue.
Thanks in Advance
The main problem is the way you use the join-pattern (selectAll("abc").data(mylist).enter()). Read the docs again to see the details.
why create a zoom when you don't use it. I have removed it from the example
every thing regarding tool tips is commented
do not calculate a new domain for X on each brush move, it gives the impression that the values change because the bar changes size.
don't define multiple elements with the same id (greater / lesser). Use a class.
define the fill with an attribute or with a style but not both and using different colors makes it even harder to determine which color you want/will be used.
why animate the width - 50ms is so short nobody will notice
what is the use of scroll()?
maybe an idea to port the chart over to d3v5
var data = [], svg, defs,gBrush, brush, main_xScale, mini_xScale, main_yScale,
mini_yScale,main_yZoom, main_xAxis, main_yAxis, mini_width, textScale;
init();
function init() {
for (var i = 1; i < 30; i++) {
var my_object = {};
my_object.key = i;
my_object.country = "Label"+i;
my_object.gtLabel = "greater";
my_object.value = Math.floor(Math.random() * 600);
my_object.ltLabel = "Lesser";
my_object.result = Math.floor(Math.random() * 300);
data.push(my_object);
}
// var zoomer = d3.behavior.zoom()
// .on("zoom", null);
var main_margin = {top: 10, right: 10, bottom: 30, left: 100},
main_width = 450 - main_margin.left - main_margin.right,
main_height = 250 - main_margin.top - main_margin.bottom;
var mini_margin = {top: 10, right: 10, bottom: 30, left: 10},
mini_height = 250 - mini_margin.top - mini_margin.bottom;
mini_width = 100 - mini_margin.left - mini_margin.right;
svg = d3.select("body").append("svg")
.attr("class", "svgWrapper")
.attr("width", main_width + main_margin.left + main_margin.right + mini_width + mini_margin.left + mini_margin.right)
.attr("height", main_height + main_margin.top + main_margin.bottom);
// .call(zoomer)
// .on("wheel.zoom", scroll)
// .on("mousedown.zoom", null)
// .on("touchstart.zoom", null)
// .on("touchmove.zoom", null)
// .on("touchend.zoom", null);
var mainGroup = svg.append("g")
.attr("class","mainGroupWrapper")
.attr("transform","translate(180,10)")
.append("g")
.attr("clip-path", "url(#clip)")
.style("clip-path", "url(#clip)")
.attr("class","mainGroup");
var miniGroup = svg.append("g")
.attr("class","miniGroup")
.attr("transform","translate(135,10)");
var brushGroup = svg.append("g")
.attr("class","brushGroup")
.attr("transform","translate(135,10)");
main_xScale = d3.scale.linear().range([0, main_width]);
mini_xScale = d3.scale.linear().range([0, mini_width]);
main_yScale = d3.scale.ordinal().rangeBands([0, main_height], 0.4, 0);
mini_yScale = d3.scale.ordinal().rangeBands([0, mini_height], 0.4, 0);
main_yZoom = d3.scale.linear()
.range([0, main_height])
.domain([0, main_height]);
main_xAxis = d3.svg.axis()
.scale(main_xScale)
.orient("bottom")
.tickFormat(d3.format(".2s"));
d3.select(".mainGroupWrapper")
.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + 0 + "," + (main_height + 5) + ")");
svg.append("text")
.attr("transform", "translate(" + (main_width / 2) + " ," + (main_height + (main_margin.bottom -60) ) +")")
.attr("dy", ".71em")
.attr("class", "x axis")
.attr("stroke-width",1)
.style("font-size","15px")
.text("");
main_yAxis = d3.svg.axis()
.scale(main_yScale)
.orient("left").tickSize(5);
mainGroup.append("g")
.attr("class", "y axis")
.attr("transform", "translate(-48,0)");
main_xScale.domain([0, d3.max(data, function(d) { return d.value; })]);
mini_xScale.domain([0, d3.max(data, function(d) { return d.value; })]);
main_yScale.domain(data.map(function(d) { return d.country; }));
mini_yScale.domain(data.map(function(d) { return d.country; }));
d3.select(".mainGroup").select(".y.axis").call(main_yAxis);
textScale = d3.scale.linear()
.domain([25,50])
.range([12,6])
.clamp(true);
var brushExtent = 15;// Math.max( 1, Math.min( 20, Math.round(data.length*0.2)));
brush = d3.svg.brush()
.y(mini_yScale)
.extent([mini_yScale(data[0].country), mini_yScale(data[brushExtent].country)])
.on("brush", brushmove);
gBrush = d3.select(".brushGroup").append("g")
.attr("class", "brush")
.call(brush);
gBrush.selectAll(".resize")
.append("line")
.attr("x2", 40);
gBrush.selectAll("rect")
.attr("width", 40);
gBrush.select(".background")
.on("mousedown.brush", brushcenter)
.on("touchstart.brush", brushcenter);
defs = svg.append("defs")
defs.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("x", -main_margin.left)
.attr("width", main_width + main_margin.left)
.attr("height", main_height);
var mini_bar = d3.select(".miniGroup").selectAll(".bar")
.data(data, function(d) { return d.key; });
mini_bar
.attr("width", function(d) { return (mini_xScale(d.value)/2.2); })
.attr("y", function(d,i) { return mini_yScale(d.country); })
.attr("height", mini_yScale.rangeBand());
mini_bar.enter().append("rect")
.attr("class", "bar")
.attr("x", 0)
.attr("width", function(d) { return mini_xScale(d.value/2.2); })
.attr("y", function(d,i) { return mini_yScale(d.country); })
.attr("height", mini_yScale.rangeBand())
.style("fill", "url(#gradient-rainbow-mini)");
mini_bar.exit()
.remove();
gBrush.call(brush.event);
}
function update() {
// var divTooltip = svg.append("div").attr("class", "toolTip");
if (d3.select(".mainGroup").select(".bar2.greater").empty()) {
var bar = d3.select(".mainGroup").selectAll(null)
.data(data, function(d) { return d.key; });
bar.enter().append("rect")
.attr("class", "bar2 greater")
.attr("fill", "#1f77b4")
.attr("x", 0);
bar.enter().append("rect")
.attr("class", "bar2 lesser")
.attr("fill", "#ff7f0e")
.attr("x", 0);
}
d3.selectAll(".bar2.greater")
.attr("y", function(d) { return main_yScale(d.country) + main_yScale.rangeBand()/2; })
.attr("width", function(d) { return main_xScale(d.value); })
.attr("height", main_yScale.rangeBand()/2);
d3.selectAll(".bar2.lesser")
.attr("y", function(d,i) { return main_yScale(d.country); })
.attr("width", function(d) { return main_xScale(d.result); })
.attr("height", main_yScale.rangeBand()/2);
// bar
// .attr("y", function(d,i) { return main_yScale(d.country); })
// .attr("height", main_yScale.rangeBand())
// .attr("x", 0)
// .transition().duration(50)
// .attr("width", function(d) { return main_xScale(d.value); });
// var bar1= bar.enter().append("rect")
// .attr("class", "bar2")
// // .attr("id","greater")
// // .style("fill", "#1f77b4")
// // .attr("fill", function(d,i) { return "#000" })
// .attr("fill", "#1f77b4")
// .attr("y", function(d,i) { return main_yScale(d.country) + main_yScale.rangeBand()/2; })
// .attr("height", main_yScale.rangeBand()/2)
// .attr("x", 0)
// .transition().duration(50)
// .attr("width", function(d) { return main_xScale(d.value); });
// // console.log(bar1);
// var bar2 = bar.enter().append("rect")
// .attr("class", "bar2")
// // .attr("id","lesser")
// // .style("fill", "#ff7f0e")
// .attr("fill", "#ff7f0e")
// .attr("y", function(d,i) { return main_yScale(d.country); })
// .attr("height", main_yScale.rangeBand()/2)
// .attr("x", 0)
// .transition().duration(50)
// .attr("width", function(d) { return main_xScale(d.result); });
// console.log(bar2);
// var dwellTimeSecsEntered = $("#dwellTimeSecs").val();
// var lessValue = "value";
// var greaterValues = "result";
// var tip = d3.tip()
// .attr('class', 'd3-tip')
// .offset([10, 75])
// .html(function(d) {
// return "<strong>"+d.country+ " </strong><br>" +
// ""+lessValue+" :<span style='color:black'>" + d.result + "</span><br>"+greaterValues+": <span style='color:black'>" + d.value + "</span><br>";
// });
// bar.on('mouseover', tip.show)
// .on('mouseout', tip.hide);
// svg.call(tip);
// bar.exit()
// .remove();
}
function brushmove() {
var extent = brush.extent();
var selected = mini_yScale.domain()
.filter(function(d) { return (extent[0] - mini_yScale.rangeBand() + 1e-2 <= mini_yScale(d)) && (mini_yScale(d) <= extent[1] - 1e-2); });
d3.select(".miniGroup").selectAll(".bar")
.style("fill", "lightGrey");
d3.selectAll(".y.axis text")
.style("font-size", textScale(selected.length));
var originalRange = main_yZoom.range();
main_yZoom.domain( extent );
main_yScale.domain(data.map(function(d) { return d.country; }));
main_yScale.rangeBands( [ main_yZoom(originalRange[0]), main_yZoom(originalRange[1]) ], 0.4, 0);
d3.select(".mainGroup")
.select(".y.axis")
.call(main_yAxis);
// keep x-axis at the same scale independet of selected brush range
// var newMaxXScale = d3.max(data, function(d) { return selected.indexOf(d.country) > -1 ? d.value : 0; });
// main_xScale.domain([0, newMaxXScale]);
// can be moved to the init() call
d3.select(".mainGroupWrapper")
.select(".x.axis")
.transition().duration(50)
.call(main_xAxis);
update();
}
function brushcenter() {
var target = d3.event.target,
extent = brush.extent(),
size = extent[1] - extent[0],
range = mini_yScale.range(),
y0 = d3.min(range) + size / 2,
y1 = d3.max(range) + mini_yScale.rangeBand() - size / 2,
center = Math.max( y0, Math.min( y1, d3.mouse(target)[1] ) );
d3.event.stopPropagation();
gBrush
.call(brush.extent([center - size / 2, center + size / 2]))
.call(brush.event);
}
function scroll() {
var extent = brush.extent(),
size = extent[1] - extent[0],
range = mini_yScale.range(),
y0 = d3.min(range),
y1 = d3.max(range) + mini_yScale.rangeBand(),
dy = d3.event.deltaY,
topSection;
if ( extent[0] - dy < y0 ) { topSection = y0; }
else if ( extent[1] - dy > y1 ) { topSection = y1 - size; }
else { topSection = extent[0] - dy; }
d3.event.stopPropagation();
d3.event.preventDefault();
gBrush
.call(brush.extent([ topSection, topSection + size ]))
.call(brush.event);
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: block;
}
body {
font-size: 10px;
font-family: 'Open Sans', sans-serif;
font-weight: 400;
text-align: center;
}
#title {
font-size: 20px;
padding-bottom: 10px;
padding-top: 20px;
font-weight: 300;
}
#explanation {
font-size: 12px;
max-width: 620px;
margin: 0 auto;
padding-top: 10px;
color: #ababab;
font-weight: 300;
}
.brush .extent {
fill-opacity: .125;
shape-rendering: crispEdges;
}
.resize {
display: inline !important; /* show when empty */
fill: #7A7A7A;
fill-opacity: 1;
stroke: #7A7A7A;
stroke-width: 2px;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<body>
</body>
Hopefully Mike Bostock comes swinging in to my rescue on this one.
(EDIT: With the help of Andrea Crawford's answer below this is the working version)
JS Fiddle: https://jsfiddle.net/dfcarter/kd0dkrL8/
I have written a chart in d3 to allow on the fly user changes (colors, selected data, etc)
Well it is mainly working but when you remove the lower ordinals it stops centering on the graph and starts to creep downward.
Looks Fine - removing the first 3 seems fine
Looks Unacceptable - removing the Last 3 and the chart is toward the bottom of the graph area
The Core of the D3 for reference:
(The Fiddle works correctly I don't see why the external reference to spectrum is not working so look at the fiddle)
var $pop = $('#my_custom_menu'),
notHov = 1; // Hover flag
$pop.hover(function() {
notHov ^= 1;
}); // Toggle flag on hover
$(document).on('mouseup keyup', function(e) {
if (notHov || e.which == 27) $pop.fadeOut();
});
/////// CALL POPUP
$('.my_custom_menu').click(function() {
$pop.stop().fadeIn();
});
function updateStream(color) {
var label1 = d3.selectAll(".layer")
.filter(function(d, i) {
return i === clickIndex;
})
.style("fill", color);
}
$("#showPalette").spectrum({
showPalette: true,
palette: [
['black', 'white', 'blanchedalmond'],
['rgb(255, 128, 0);', 'hsv 100 70 50', 'lightyellow']
],
change: updateStream
});
var clickIndex = 0;
$("#textDisplayName").keyup(function(event) {
if (event.keyCode == 13) {
//alert(eval($("#stream").val) + " , " + eval($("#textDisplayname").val));
var newValue = document.getElementById("textDisplayName").value;
var label1 = d3.selectAll("text")
.filter(function(d, i) {
return i === clickIndex;
})
.text(newValue);
}
});
chart("data.csv", "blue");
var datearray = [];
var colorrange = [];
function csv(url, callback) {
d3.text(url, function(text) {
callback(text && d3.csv.parse(text));
});
}
function drawgrid() {
}
function chart(csvpath, color) {
if (color == "blue") {
colorrange = ["#045A8D", "#2B8CBE", "#74A9CF", "#A6BDDB", "#D0D1E6", "#F1EEF6"];
} else if (color == "pink") {
colorrange = ["#980043", "#DD1C77", "#DF65B0", "#C994C7", "#D4B9DA", "#F1EEF6"];
} else if (color == "orange") {
colorrange = ["#B30000", "#E34A33", "#FC8D59", "#FDBB84", "#FDD49E", "#FEF0D9"];
}
strokecolor = colorrange[0];
var format = d3.time.format("%m/%d/%y");
var margin = {
top: 20,
right: 40,
bottom: 30,
left: 30
};
var width = document.body.clientWidth - margin.left - margin.right - 200;
var height = 400 - margin.top - margin.bottom;
var tooltip = d3.select("body")
.append("div")
.attr("class", "remove")
.style("position", "absolute")
.style("z-index", "20")
.style("visibility", "hidden")
.style("top", "30px")
.style("left", "55px");
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height - 10, 0]);
var getColor = d3.scale.ordinal()
.range(colorrange);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(d3.time.weeks);
var yAxis = d3.svg.axis()
.scale(y);
var yAxisr = d3.svg.axis()
.scale(y);
var stack = d3.layout.stack()
.offset("silhouette")
.values(function(d) {
return d.values;
})
.x(function(d) {
return d.date;
})
.y(function(d) {
return d.value;
});
var nest = d3.nest()
.key(function(d) {
return d.key;
});
var area = d3.svg.area()
.interpolate("cardinal")
.x(function(d) {
return x(d.date);
})
.y0(function(d) {
return y(d.y0);
})
.y1(function(d) {
return y(d.y0 + d.y);
});
var svg = d3.select(".chart").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 + ")");
function randomData() {
return Math.random() * 9;
}
var dates = ["07/08/2016", "07/09/2016", "07/10/2016", "07/11/2016", "07/12/2016", "07/13/2016", "07/14/2016", "07/15/2016", "07/16/2016", "07/17/2016", "07/18/2016"];
var numberOfSeries = 7,
numberOfDataPoint = 7,
data = [];
for (var i = 0; i < numberOfSeries; ++i) {
for (var j = 0; j < numberOfDataPoint; ++j) {
data.push({
key: i,
value: randomData(),
date: Date.parse(dates[j])
});
}
}
var layerData = nest.entries(data);
var checkholder = d3.select("#checkboxHolder")
var legend = checkholder.selectAll(".legend")
.data(layerData)
.enter().append("div")
legend.append("input")
.attr("type", "checkbox")
.attr("id", function(d, i) {
return "check" + i
});
legend.append("label")
.attr("for", function(d, i) {
return "check" + i
})
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.html(function(d) {
return d.key;
});
legend.on('change', function(d) {
d.disabled = !d.disabled;
d3.transition().duration(600).each(redraw);
});
var xaxisnode = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
var yaxisnode = svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width + ", 0)")
.call(yAxis.orient("right"));
//svg.append("g")
// .attr("class", "y axis")
// .call(yAxis.orient("left"));
function redraw() {
var activeLayers = layerData.filter(function(d1) {
return !d1.disabled;
});
var layers = stack(activeLayers);
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([0, d3.max(data, function(d) {
if (!d.disabled) return d.y0 + d.y;
})]);
svg.selectAll(".layergroup").remove();
var layergroup = svg.selectAll(".layergroup")
.data(layers)
.enter().append("g")
.attr("class", "layergroup");
var paths = layergroup.append("path")
.attr("class", "layer")
.attr("d", function(d) {
return area(d.values);
})
.style("fill", function(d, i) {
return getColor(i);
});
layergroup.append("text")
.datum(function(d) {
return {
name: d.key,
value: d.values[d.values.length - 1]
};
})
.attr("transform", function(d) {
return "translate(" + x(d.value.date) + "," + y(d.value.y0 + d.value.y / 2) + ")";
})
.attr("x", -6)
.attr("dy", ".35em")
.attr("class", "pathLabel")
.text(function(d) {
return d.name;
});
//var clickIndex = 0;
//paths.on("click", function (d, i) {
// clickIndex = i;
// svg.selectAll("text").filter(function (d, i) { return i === clickIndex }).attr("transform", function (d) { var coords = d3.mouse(svg.node()); return "translate(" + coords[0] + "," + coords[1] + ")"; })
//});
xaxisnode.call(xAxis);
yaxisnode.call(yAxis.orient("right"));
const drag = d3.behavior.drag()
.origin(function(d) {
return d;
})
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended)
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
}
var dragIndex = 0;
function dragged(d, i) {
dragindex = i;
const elem = svg.selectAll(".pathLabel").filter(function(d, i) {
return i === dragindex;
});
elem.attr('transform', function(d) {
var coords = d3.mouse(svg.node());
return "translate(" + coords[0] + "," + coords[1] + ")";
});
//elem.attr('y', +elem.attr('y') + d3.event.dy)
}
function dragended(d) {}
svg.selectAll(".layer")
.attr("opacity", 1)
.on("mouseover", function(d, i) {
svg.selectAll(".layer").transition()
.duration(250)
.attr("opacity", function(d, j) {
return j != i ? 0.6 : 1;
})
})
.on("mousemove", function(d, i) {
mousex = d3.mouse(this);
mousex = mousex[0];
var invertedx = x.invert(mousex);
invertedx = invertedx.getMonth() + invertedx.getDate();
var selected = (d.values);
for (var k = 0; k < selected.length; k++) {
datearray[k] = selected[k].date
datearray[k] = datearray[k].getMonth() + datearray[k].getDate();
}
mousedate = datearray.indexOf(invertedx);
pro = d.values[mousedate].value;
mouse = d3.mouse(d3.select("#letable").node());
d3.select(this)
.classed("hover", true)
.attr("stroke", strokecolor)
.attr("stroke-width", "0.5px"),
tooltip.html("<p>" + d.key + "<br>" + pro + "</p>").style("visibility", "visible").style("left", (mouse[0] + 50) + "px").style("top", (mouse[1] + 30) + "px");
})
.on("mouseout", function(d, i) {
svg.selectAll(".layer")
.transition()
.duration(250)
.attr("opacity", "1");
d3.select(this)
.classed("hover", false)
.attr("stroke-width", "0px"), tooltip.html("<p>" + d.key + "<br>" + pro + "</p>").style("visibility", "hidden");
})
.on("contextmenu", function(data, index) {
var position = d3.mouse(this);
d3.select('#my_custom_menu')
.style('position', 'absolute')
.style('left', position[0] + "px")
.style('top', position[1] + "px")
.style('display', 'block');
d3.event.preventDefault();
//d3.select('#stream')
// .attr("value", index);
clickIndex = index;
$('#textDisplayName')
.val(data.key);
}).call(drag);
//d3.select('svg')
// .selectAll('text')
// .data(labels)
// .enter()
// .append('text')
// .text(d => d)
// .attr('fill', 'green')
// .attr('x', (d, i) => 10 + i * 30)
// .attr('y', (d, i) => 15 + i * 30)
// .call(drag)
var vertical = d3.select(".chart")
.append("div")
.attr("class", "remove")
.style("position", "absolute")
.style("z-index", "19")
.style("width", "1px")
.style("height", "380px")
.style("top", "10px")
.style("bottom", "30px")
.style("left", "0px")
.style("background", "#ccc");
d3.select(".chart")
.on("mousemove", function() {
mousex = d3.mouse(d3.select("#letable").node());
mousex = mousex[0] + 5;
vertical.style("left", mousex + "px")
})
.on("mouseover", function() {
mousex = d3.mouse(d3.select("#letable").node());
mousex = mousex[0] + 5;
vertical.style("left", mousex + "px")
});
}
redraw();
}
body {
font: 10px "segoe ui";
}
.chart {
background: #fff;
}
p {
font: 12px "segoe ui";
}
.axis path,
.axis line {
fill: none;
stroke: #CCC;
stroke-width: 2px;
shape-rendering: crispEdges;
}
.pathLabel {
font: bold 16px "Segoe UI";
color: black;
text-shadow: 0 0 3px #FFF;
}
button {
position: absolute;
right: 50px;
top: 10px;
}
#my_custom_menu {
display: none;
padding: 15px 25px;
width: 220px;
background: #fff;
border-radius: 4px;
box-shadow: 0 3px 3px -2px #024;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/2.6.0/d3.min.js"></script>
<!DOCTYPE html>
<body>
<script src="https://bgrins.github.io/spectrum/spectrum.js"></script>
<script src="https://d3js.org/d3.v2.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<link href="https://bgrins.github.io/spectrum/spectrum.css" rel="stylesheet" />
<table id="letable">
<tr>
<td>
<div id="checkboxHolder" style="width:200px;height:400px;overflow:auto;">
</div>
</td>
<td>
<div class="chart" style="width:700px;height:400px">
</div>
</td>
</tr>
</table>
<div id="my_custom_menu">
<input id="textDisplayName" type="text" />
<br />
<input id="showPalette" type="text" />
<br />
<input type="hidden" id="stream" value="" />
</div>
</body>
I'm not an expert on D3 so you may still want to wait for Mr. Bostock, but I think your issue is that inside your redraw function, you have this code:
y.domain([0, d3.max(data, function(d) {
if (!d.disabled) return d.y0 + d.y;
})]);
but if you look at the value of d, it doesn't actually have a disabled property.
You could add a disabled property to the values at the same time you add it to the parent:
legend.on('change', function(d) {
d.disabled = !d.disabled;
$.each(d.values, function(){
this.disabled = d.disabled;
});
d3.transition().duration(600).each(redraw);
});
There's probably a more elegant way to do that, but it seems to fix the issue.
can you resolve this problem.
I am not able to rotate in X-axis values. can you please check below examples. Now x-axis text is coming horizontally but we wants Vertical alignment.
In my requirement is rotate -60 or -90 only. in "Model 1 , Module 2, Module 3" values i needs to rotate.
var margin = {top: 20, right: 30, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
padding = 0.3;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], padding);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(function(d) { return dollarFormatter(d); });
var chart = d3.select(".chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//d3.csv("data.csv", type, function(error, data) {
var data = [{ name :"Module 1",value : 20 },{ name :"Module 2",value :15},{ name :"Module 3 ",value :45},
{ name :"Final Count ",value :200}];
//console.log(data);
// Transform data (i.e., finding cumulative values and total) for easier charting
var cumulative = 0;
for (var i = 0; i < data.length; i++) {
data[i].start = cumulative;
cumulative += data[i].value;
data[i].end = cumulative;
data[i].class = ( data[i].value >= 0 ) ? 'positive' : 'negative'
}
data.push({
name: 'Total',
end: cumulative,
start: 0,
class: 'total'
});
x.domain(data.map(function(d) { return d.name; }));
y.domain([0, d3.max(data, function(d) { return d.end; })]);
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
chart.append("g")
.attr("class", "y axis")
.call(yAxis);
var bar = chart.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("class", function(d) { return "bar " + d.class })
.attr("transform", function(d) { return "translate(" + x(d.name) + ",0)"; });
bar.append("rect")
.attr("y", function(d) { return y( Math.max(d.start, d.end) ); })
.attr("height", function(d) { return Math.abs( y(d.start) - y(d.end) ); })
.attr("width", x.rangeBand());
bar.append("text")
.attr("x", x.rangeBand() / 2)
.attr("y", function(d) { return y(d.end) + 5; })
.attr("dy", function(d) { return ((d.class=='negative') ? '-' : '') + ".75em" })
.text(function(d) { return dollarFormatter(d.end - d.start);});
bar.filter(function(d) { return d.class != "total" }).append("line")
.attr("class", "connector")
.attr("x1", x.rangeBand() + 5 )
.attr("y1", function(d) { return y(d.end) } )
.attr("x2", x.rangeBand() / ( 1 - padding) - 5 )
.attr("y2", function(d) { return y(d.end) } )
//});
function type(d) {
d.value = +d.value;
return d;
}
function dollarFormatter(n) {
n = Math.round(n);
var result = n;
if (Math.abs(n) > 1000) {
result = Math.round(n/1000) + 'K';
}
return result;
}
.bar.total rect {
fill: steelblue;
}
.bar.positive rect {
fill: darkolivegreen;
}
.bar.negative rect {
fill: crimson;
}
.bar line.connector {
stroke: grey;
stroke-dasharray: 3;
}
.bar text {
fill: white;
font: 10px sans-serif;
text-anchor: middle;
}
.axis text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
<script src="http://d3js.org/d3.v3.min.js"></script>
<svg class="chart"></svg>
To rotate only the first 3 ticks ("module 1", "module 2" and "module 3"):
var ticks = d3.selectAll(".x.axis text").each(function(d, i) {
if (i < 3) {
d3.select(this).attr("y", 0)
d3.select(this).attr("x", 10)
d3.select(this).attr("dy", ".35em")
d3.select(this).attr("transform", "rotate(90)")
d3.select(this).style("text-anchor", "start");
}
});
Check the demo:
var margin = {
top: 20,
right: 30,
bottom: 60,
left: 40
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
padding = 0.3;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], padding);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(function(d) {
return dollarFormatter(d);
});
var chart = d3.select(".chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//d3.csv("data.csv", type, function(error, data) {
var data = [{
name: "Module 1",
value: 20
}, {
name: "Module 2",
value: 15
}, {
name: "Module 3 ",
value: 45
}, {
name: "Final Count ",
value: 200
}];
//console.log(data);
// Transform data (i.e., finding cumulative values and total) for easier charting
var cumulative = 0;
for (var i = 0; i < data.length; i++) {
data[i].start = cumulative;
cumulative += data[i].value;
data[i].end = cumulative;
data[i].class = (data[i].value >= 0) ? 'positive' : 'negative'
}
data.push({
name: 'Total',
end: cumulative,
start: 0,
class: 'total'
});
x.domain(data.map(function(d) {
return d.name;
}));
y.domain([0, d3.max(data, function(d) {
return d.end;
})]);
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
var ticks = d3.selectAll(".x.axis text").each(function(d, i) {
if (i < 3) {
d3.select(this).attr("y", 0)
d3.select(this).attr("x", 10)
d3.select(this).attr("dy", ".35em")
d3.select(this).attr("transform", "rotate(90)")
d3.select(this).style("text-anchor", "start");
}
});
chart.append("g")
.attr("class", "y axis")
.call(yAxis);
var bar = chart.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("class", function(d) {
return "bar " + d.class
})
.attr("transform", function(d) {
return "translate(" + x(d.name) + ",0)";
});
bar.append("rect")
.attr("y", function(d) {
return y(Math.max(d.start, d.end));
})
.attr("height", function(d) {
return Math.abs(y(d.start) - y(d.end));
})
.attr("width", x.rangeBand());
bar.append("text")
.attr("x", x.rangeBand() / 2)
.attr("y", function(d) {
return y(d.end) + 5;
})
.attr("dy", function(d) {
return ((d.class == 'negative') ? '-' : '') + ".75em"
})
.text(function(d) {
return dollarFormatter(d.end - d.start);
});
bar.filter(function(d) {
return d.class != "total"
}).append("line")
.attr("class", "connector")
.attr("x1", x.rangeBand() + 5)
.attr("y1", function(d) {
return y(d.end)
})
.attr("x2", x.rangeBand() / (1 - padding) - 5)
.attr("y2", function(d) {
return y(d.end)
})
//});
function type(d) {
d.value = +d.value;
return d;
}
function dollarFormatter(n) {
n = Math.round(n);
var result = n;
if (Math.abs(n) > 1000) {
result = Math.round(n / 1000) + 'K';
}
return result;
}
.bar.total rect {
fill: steelblue;
}
.bar.positive rect {
fill: darkolivegreen;
}
.bar.negative rect {
fill: crimson;
}
.bar line.connector {
stroke: grey;
stroke-dasharray: 3;
}
.bar text {
fill: white;
font: 10px sans-serif;
text-anchor: middle;
}
.axis text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
<script src="http://d3js.org/d3.v3.min.js"></script>
<svg class="chart"></svg>
var margin = {top: 20, right: 30, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
padding = 0.3;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], padding);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(function(d) { return percentage(d); })
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0)
.attr("align","middle");
var chart = d3.select(".chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
data = [
{"name":"Product Revenue","value":420000},
{"name":"Services Revenue","value":210000},
{"name":"Employee Revenue","value":190000},
{"name":"Fixed Costs","value":-170000},
{"name":"Variable Costs","value":-140000}
];
//function to find all the positive values
var positive_val = data.filter(function(d) { return d.value > 0; });
console.log(JSON.stringify(positive_val));
//function to calculate the sum of all the positive values
var maxSum = positive_val.reduce(function(sum, d) {
return sum + d.value;
}, 0);
console.log("The maximum sum is "+maxSum);
//to calculate the new Domain by adding 120
var yaxisRange=maxSum+120;
console.log("The y axis sum is "+yaxisRange);
var newDomain=percentage(yaxisRange);
console.log(newDomain);
var newDomain = newDomain.replace(/[!##$%^&*]/g, "");
console.log(newDomain);
// Transform data (i.e., finding cumulative values and total)
var cumulative = 0;
for (var i = 0; i < data.length; i++) {
data[i].start = cumulative;
cumulative += data[i].value;
data[i].end = cumulative;
data[i].class = ( data[i].value >= 0 ) ? 'positive' : 'negative'
}
data.push({
name: 'Total',
end: cumulative,
start: 0,
class: 'total',
value: cumulative
});
x.domain(data.map(function(d) { return d.name; }));
y.domain([0, d3.max(data, function(d) { return d.end; })]);
//WHen i try to use this as my new domain,the bar increase the height
//y.domain([0,newDomain]);
debugger;
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
chart.append("g")
.attr("class", "y axis")
.call(yAxis);
var bar = chart.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("class", function(d) { return "bar " + d.class })
.attr("transform", function(d) { return "translate(" + x(d.name) + ",0)"; });
bar.append("rect")
//.attr("y", function(d) { return y(d.value); })
.attr("y", function(d) { return y( Math.max(d.start, d.end) ); })
.attr("height", function(d) { return Math.abs( y(d.start) - y(d.end) ); })
//function to draw the tooltip
.attr("width", x.rangeBand()).on("mouseover", function(d) {
// to find the parent node,to calculate the x position
var parentG = d3.select(this.parentNode);
var barPos = parseFloat(parentG.attr('transform').split("(")[1]);
var xPosition = barPos+x.rangeBand()/2;
//to find the y position
var yPosition = parseFloat(d3.select(this).attr("y"))+ Math.abs( y(d.start) - y(d.end))/2;
tooltip.transition()
.duration(200)
.style("opacity", .9);
tooltip.html(d.name + "<br/>" + percentage(d.value))
.style("left", xPosition + "px")
.style("top", yPosition + "px");
}).on("mouseout", function(d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
});
bar.append("text")
.attr("x", x.rangeBand() / 2)
.attr("y", function(d) { return y(d.end) + 5; })
.attr("dy", function(d) { return ((d.class=='negative') ? '-' : '') + ".75em" })
.text(function(d) { return percentage(d.end - d.start);});
bar.filter(function(d) { return d.class != "total" }).append("line")
.attr("class", "connector")
.attr("x1", x.rangeBand() + 5 )
.attr("y1", function(d) { return y(d.end) } )
.attr("x2", x.rangeBand() / ( 1 - padding) - 5 )
.attr("y2", function(d) { return y(d.end) } )
function type(d) {
d.value = +d.value;
return d;
}
function percentage(n) {
n = Math.round(n);
var result = n;
if (Math.abs(n) > 100) {
result = Math.round(n/100) + '%';
}
return result;
}
-Here is the updated fiddle http://jsfiddle.net/7mkq4k8k/21/
-I want to make the yaxis label increase .for eg 9000,9500.I have calculated the newDomian.
-If i try to add this domain,my chart doesnt get drawn properly.The height of the bars increase ,and the due to this the rest of the bars are not drawn.Please help me in this issue.
So the chart you initially draw is based on this domain :
y.domain([0, d3.max(data, function (d) {
return d.end;
})]);
Try to console.log(d3.max(data, function (d) {return d.end;})) and you will find it returns 820000, which is the maximum of your cumulative calculation. That means your chart is drawn with a domain from 0 to 820000.
Now let's talk about your newDomain. You're taking the percentage of your maxSum, which means your newDomain is equal to 8201. So now you're trying to draw your chart from 0 to 8201.
But your bars height is calculated like this :
Math.abs(y(d.start) - y(d.end)), which means you are calculating ranges from y(0) to y(820000) (d.end is max equal to 820000).
y(820000) doesn't fit, as you specified with your domain that it could max go to y(8201). That's why your bars are reaching over the very top of your chart, because the domain you're giving doesn't correspond the numbers inside :
y(this number is too big and doesn't fit because it is not between 0 and newDomain).
How to solve this ?
You define your domain correctly, removing the percentage line
//function to calculate the sum of all the positive values
var maxSum = positive_val.reduce(function (sum, d) {
return sum + d.value;
}, 0);
console.log("The maximum sum is " + maxSum);
//to calculate the new Domain by adding 520000 (big number to show you it works)
var newDomain = maxSum + 520000;
console.log(newDomain); //1340000
y.domain([0,newDomain]);
Working snippet below :
var margin = {
top: 20,
right: 30,
bottom: 30,
left: 40
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
padding = 0.3;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], padding);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(function (d) {
return percentage(d);
})
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0)
.attr("align", "middle");
var chart = d3.select(".chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
data = [{
"name": "Product Revenue",
"value": 420000
}, {
"name": "Services Revenue",
"value": 210000
}, {
"name": "Employee Revenue",
"value": 190000
}, {
"name": "Fixed Costs",
"value": -170000
}, {
"name": "Variable Costs",
"value": -140000
}
];
//function to find all the positive values
var positive_val = data.filter(function (d) {
return d.value > 0;
});
console.log(JSON.stringify(positive_val));
//function to calculate the sum of all the positive values
var maxSum = positive_val.reduce(function (sum, d) {
return sum + d.value;
}, 0);
console.log("The maximum sum is " + maxSum);
//to calculate the new Domain by adding 120
var newDomain = maxSum + 520000;
console.log(newDomain);
// Transform data (i.e., finding cumulative values and total)
var cumulative = 0;
for (var i = 0; i < data.length; i++) {
data[i].start = cumulative;
cumulative += data[i].value;
data[i].end = cumulative;
data[i].class = (data[i].value >= 0) ? 'positive' : 'negative'
}
data.push({
name: 'Total',
end: cumulative,
start: 0,
class: 'total',
value: cumulative
});
x.domain(data.map(function (d) {
return d.name;
}));
console.log(d3.max(data, function (d) {
return d.end;
}));
/*y.domain([0, d3.max(data, function (d) {
return d.end;
})]);*/
//WHen i try to use this as my new domain,the bar increase the height
y.domain([0,newDomain]);
debugger;
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
chart.append("g")
.attr("class", "y axis")
.call(yAxis);
var bar = chart.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("class", function (d) {
return "bar " + d.class
})
.attr("transform", function (d) {
return "translate(" + x(d.name) + ",0)";
});
bar.append("rect")
//.attr("y", function(d) { return y(d.value); })
.attr("y", function (d) {
return y(Math.max(d.start, d.end));
})
.attr("height", function (d) {
return Math.abs(y(d.start) - y(d.end));
})
//function to draw the tooltip
.attr("width", x.rangeBand()).on("mouseover", function (d) {
// to find the parent node,to calculate the x position
var parentG = d3.select(this.parentNode);
var barPos = parseFloat(parentG.attr('transform').split("(")[1]);
var xPosition = barPos + x.rangeBand() / 2;
//to find the y position
var yPosition = parseFloat(d3.select(this).attr("y")) + Math.abs(y(d.start) - y(d.end)) / 2;
tooltip.transition()
.duration(200)
.style("opacity", .9);
tooltip.html(d.name + "<br/>" + percentage(d.value))
.style("left", xPosition + "px")
.style("top", yPosition + "px");
}).on("mouseout", function (d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
});
bar.append("text")
.attr("x", x.rangeBand() / 2)
.attr("y", function (d) {
return y(d.end) + 5;
})
.attr("dy", function (d) {
return ((d.class == 'negative') ? '-' : '') + ".75em"
})
.text(function (d) {
return percentage(d.end - d.start);
});
bar.filter(function (d) {
return d.class != "total"
}).append("line")
.attr("class", "connector")
.attr("x1", x.rangeBand() + 5)
.attr("y1", function (d) {
return y(d.end)
})
.attr("x2", x.rangeBand() / (1 - padding) - 5)
.attr("y2", function (d) {
return y(d.end)
})
function type(d) {
d.value = +d.value;
return d;
}
function percentage(n) {
n = Math.round(n);
var result = n;
if (Math.abs(n) > 100) {
result = Math.round(n / 100) + '%';
}
return result;
}
.bar.total rect {
fill: steelblue;
}
.bar:hover rect {
fill:orange;
}
.bar.positive rect {
fill: darkolivegreen;
}
.bar:hover rect {
fill:orange;
}
.bar.negative rect {
fill: crimson;
}
.bar:hover rect {
fill:orange;
}
.bar line.connector {
stroke: grey;
stroke-dasharray: 3;
}
.bar text {
fill: white;
font: 12px sans-serif;
text-anchor: middle;
}
.axis text {
font: 10px sans-serif;
}
.axis path, .axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
div.tooltip {
position:absolute;
text-align: center;
padding: 2px;
font: 12px sans-serif;
background: #33CC00;
border: 0px;
border-radius: 8px;
pointer-events: none;
width: 90px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg class="chart"></svg>
Hope this helps !