d3js Multiple Stacked Bar Chart Line Cursor Appearing in Wrong Chart - javascript

I am in the middle of creating two related stacked bar charts. Both charts must include a line cursor. The cursor for the top chart works fine, however no line cursor gets displayed in the bottom chart. Worse, when I mouse over the bottom chart, the line cursor within the top chart moves. What needs to be fixed a for line cursor to appear within the bottom chart and either not to affect the top chart or be synchronized with the one in the top chart?
Note: before saying that I am not respecting the "Do Not Repeat Yourself Principle" in my code, note that Kent Beck says: "First, get it to work, then get it right, finally get it to work fast". Also, feel free to skip the data creation section.
Here is my code:
var app = {};
app.allBarsDatasets = [
{
"xAxisTickValue": "10-1",
"barValue": 17
},
{
"xAxisTickValue": "10-2",
"barValue": 17
},
{
"xAxisTickValue": "10-3",
"barValue": 17
}
];
app.allBarsDatasets2 = [
[
{
"xAxisTickValue": "10-1",
"barValue": 10
},
{
"xAxisTickValue": "10-2",
"barValue": 6
},
{
"xAxisTickValue": "10-3",
"barValue": 7
}
],
[
{
"xAxisTickValue": "10-1",
"barValue": 6
},
{
"xAxisTickValue": "10-2",
"barValue": 8
},
{
"xAxisTickValue": "10-3",
"barValue": 10
}
]
];
app.allLinesDatasets =
{
"points": [
{
"x": 1,
"y": 10
},
{
"x": 2,
"y": 8
},
{
"x": 3,
"y": 14
}
],
"color": "blue"
};
app.busStopsWaitTimes = {
"1": {
"days": {
"we": {
"10-1": [
17,
14,
14,
4,
8,
13,
11,
3,
2,
14,
14,
8,
9,
1,
9,
9,
9,
17,
1,
20
],
"10-2": [
13,
12,
3,
5,
18,
14,
17,
5,
9,
12,
19,
3,
8,
9,
20,
3,
14,
5,
7,
13
],
"10-3": [
18,
8,
8,
7,
10,
20,
16,
17,
6,
13,
5,
11,
11,
14,
18,
17,
11,
17,
4,
3
]
}
},
"name": "Adderley"
}
};
app.populateBusStopsWaitSelectionForm = function () {
let stopOptions = `<option value="">Select a stop</option>`;
$.each(app.busStopsWaitTimes, function (idx, stop) {
stopOptions += `<option value={"stopId":${idx}}>${stop.name}</option>`;
});
$("#busStopAnalysis_Stops").html(stopOptions);
}
app.populateBusStopsWaitSelectionForm();
$("#busStopAnalysis_Stops").change(function() {
let values = $("#busStopAnalysis_Stops").val();
if (values !== "") {
values = JSON.parse(values);
let daysOptions = `<option value="">Select a day</option>`;
if ("we" in app.busStopsWaitTimes[values.stopId].days) {
daysOptions += `<option value={"dayKey":"we"}>Wednesday</option>`
}
$("#busStopAnalysis_Days").html(daysOptions);
} else {
$("#busStopAnalysis_Days").html("<option>Please select a route</option>");
}
});
$("#drawBusStopAnalysisChart").on("click", function (evt) {
evt.preventDefault();
const stopInfo = JSON.parse($("#busStopAnalysis_Stops").val());
const dayInfo = JSON.parse($("#busStopAnalysis_Days").val());
if (stopInfo !== "" || dayInfo !== "") {
const allBarsDatasets = [];
const allBarsDatasets2 = [[],[]]
const allLinesdatasets = [];
const linePoints = [];
let i = 1;
$.each(app.busStopsWaitTimes[stopInfo.stopId]["days"][dayInfo.dayKey], function (idx, timeslot) {
timeslot.sort(function (a,b) {
return a - b;
});
let Percentile25th = timeslot[parseInt(timeslot.length / 4)];
let Percentile50th = timeslot[parseInt(timeslot.length / 2)];
let Percentile75th = timeslot[parseInt((timeslot.length / 4) * 3)];
allBarsDatasets.push({
xAxisTickValue: idx,
barValue: Percentile75th
});
allBarsDatasets2[0].push({
xAxisTickValue: idx,
barValue: Percentile25th
});
allBarsDatasets2[1].push({
xAxisTickValue: idx,
barValue: Percentile75th - Percentile25th
});
linePoints.push({x : i, y : Percentile50th});
i++;
});
allLinesdatasets.push({points:linePoints,color:"blue"});
app.drawBusStopAnalysisOneDayChart(allBarsDatasets, allBarsDatasets2, allLinesdatasets);
}
});
app.drawBusStopAnalysisOneDayChart = function (allBarsDatasets, allBarsDatasets2, allLinesdatasets) {
app.allLinesdatasets = allLinesdatasets;
$("#busStopAnalysis_OneDayChart").html("");
var barColor = '#384a60';
// calculate total frequency by state for all segment.
// var fD = app.allBarsDatasets.map(function(d){return [d.xAxisTickValue,d.barValue];});
var fD = allBarsDatasets.map(function(d){return [d.xAxisTickValue,d.barValue];});
var margin = {top: 20, right: 100, bottom: 30, left: 100},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var padding = 100;
//create svg for histogram.
var svg = d3.select("#busStopAnalysis_OneDayChart").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 + ")");
// create function for x-axis mapping.
var x = d3.scale.ordinal().rangeRoundBands([0, width], 0.1, 0)
.domain(fD.map(function(d) { return d[0]; }));
// Add x-axis to the histogram svg.
svg.append("g").attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.svg.axis()
.scale(x)
.orient("bottom")
.innerTickSize(-height)
.outerTickSize(0)
.tickPadding(10));
// create function for y-axis mapping.
var yMin = 0;
var yMax = d3.max(fD.map(function(d) { return d[1]; }));
var y = d3.scale.linear().range([height, 0])
.domain([0, d3.max(fD, function(d) { return d[1]; })]);
var yScaleGridLines = d3.scale.linear()
.domain([0, yMax])
.range([height, 0]);
var yAxisGridLines = d3.svg.axis()
.scale(yScaleGridLines)
.orient("left")
.innerTickSize(-width)
.outerTickSize(0)
.tickPadding(10);
svg.append("g")
.attr("class", "y axis")
.call(yAxisGridLines);
// You would think d3 draws bar by bar but it draws level by level
// therefore you need to create stacks which are sub-arrays whose contents
// are arrays of elements at the same level
// to achieve that
// call stack,
// call map and iterate over each array
// call map iterate over all elements within an array while creating points based on values to visualize
var layers = d3.layout.stack() (
allBarsDatasets2.map(
function(barDataset) {
return barDataset.map(
function(d) {
return {x: d.xAxisTickValue, y:d.barValue};
}
)
}
)
);
var z = d3.scale.category10();
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) {
var x;
if (i === 0) {
x = "transparent";
} else {
// x = z(i);
x = "#686868";
}
return x;
});
var mouseG = d3.select("#busStopAnalysis_Charts").append("g")
.attr("class", "mouse-over-effects");
// this is the vertical line
svg.append("path")
.attr("class", "mouse-line")
.style("stroke", "black")
.style("stroke-width", "1px")
.style("opacity", "1");
var tooltip = d3.select("#busStopAnalysis_Charts")
.append('div')
.attr('id', 'tooltip');
$("#tooltip").css('display', 'none');
layer.selectAll("rect")
.data(function (d) { return d; })
.enter().append("rect")
.attr("x", function (d) { return x(d.x); })
.attr("y", function (d) { return y(d.y + d.y0); })
.attr("height", function (d) { return y(d.y0) - y(d.y + d.y0); })
.attr("width", x.rangeBand() - 1)
.on("mousemove", function (d) {
var mouse = d3.mouse(this);
// move the vertical line
d3.select(".mouse-line")
.attr("d", function() {
var d = "M" + mouse[0] + "," + height;
d += " " + mouse[0] + "," + 0;
return d;
});
})
.on("mouseover", function (d) {
var mouse = d3.mouse(this);
console.log("first chart");
console.log(mouse);
d3.select("#tooltip")
.style("left", mouse[0] + "px")
.style("top", mouse[1] + "px")
.style("width", "auto")
.style("height", "auto")
.html("Day: " + $("#busStopAnalysis_Days option:selected").text() + "<br>Time Range: " + d.x + "<br>Avg Wait: " + d.y);
$("#tooltip").css("display", "");
app.drawAllDaysStopAnalysisChart(d);
})
.on("mouseout", function() {
$("#tooltip").css("display", "none");
})
.on("click", function (d) {
app.drawAllDaysStopAnalysisChart(d);
});
// Beginning of line things drawing
// Add min and max x and y borrowed from weird lines
var xMin = app.allLinesdatasets.reduce(function(pv,cv){
var currentXMin = cv.points.reduce(function(pv,cv){
return Math.min(pv,cv.x);
},100)
return Math.min(pv,currentXMin);
},100);
var xMax = app.allLinesdatasets.reduce(function(pv,cv){
var currentXMax = cv.points.reduce(function(pv,cv){
return Math.max(pv,cv.x);
},0)
return Math.max(pv,currentXMax);
},0);
var yMin = app.allLinesdatasets.reduce(function(pv,cv){
var currentYMin = cv.points.reduce(function(pv,cv){
return Math.min(pv,cv.y);
},100)
return Math.min(pv,currentYMin);
},100);
var yMax = app.allLinesdatasets.reduce(function(pv,cv){
var currentYMax = cv.points.reduce(function(pv,cv){
return Math.max(pv,cv.y);
},0)
return Math.max(pv,currentYMax);
},0);
var yScaleGridLines = d3.scale.linear()
.domain([0, yMax])
.range([height, 0]);
var yAxisGridLines = d3.svg.axis()
.scale(yScaleGridLines)
.orient("left")
.innerTickSize(-width)
.outerTickSize(0)
.tickPadding(10);
var xScaleGridLines = {};
xScaleGridLines = d3.scale.linear()
.domain([xMin, xMax])
.range([0, width]);
var xAxisGridLines = d3.svg.axis()
.scale(xScaleGridLines)
.orient("bottom")
.innerTickSize(-height)
.outerTickSize(0)
.tickPadding(10);
var lineGridLines = d3.svg.line()
.interpolate('step-after')
.x(function(d) { return xScaleGridLines(d.x); })
.y(function(d) { return yScaleGridLines(d.y); });
$.each(app.allLinesdatasets, function (idx, dataset) {
svg.append("path")
.data([dataset.points])
.attr("class", "line")
.attr("d", lineGridLines)
.style("stroke", function(){
// return dataset.color;
return "#FF9900";
});
});
}
/********************************************************************
* Append and Draw Second Chart
********************************************************************/
app.drawAllDaysStopAnalysisChart = function drawAllDateStopAnalysis (timeRange) {
const stopInfo = JSON.parse($("#busStopAnalysis_Stops").val());
const allBarsDatasets = [];
const allBarsDatasets2 = [[],[]]
const allLinesdatasets = [];
const linePoints = [];
if (stopInfo !== "" || dayInfo !== "") {
let i = 1;
$.each(app.busStopsWaitTimes[stopInfo.stopId]["days"], function (idx, day) {
const stopRangeWaitTimeInfo = day[timeRange.x];
stopRangeWaitTimeInfo.sort(function (a,b) {
return a - b;
});
let Percentile25th = stopRangeWaitTimeInfo[parseInt(stopRangeWaitTimeInfo.length / 4)];
let Percentile50th = stopRangeWaitTimeInfo[parseInt(stopRangeWaitTimeInfo.length / 2)];
let Percentile75th = stopRangeWaitTimeInfo[parseInt((stopRangeWaitTimeInfo.length / 4) * 3)];
allBarsDatasets.push({
xAxisTickValue: idx,
barValue: Percentile75th
});
allBarsDatasets2[0].push({
xAxisTickValue: idx,
barValue: Percentile25th
});
allBarsDatasets2[1].push({
xAxisTickValue: idx,
barValue: Percentile75th - Percentile25th
});
linePoints.push({x : i, y : Percentile50th});
i++;
});
allLinesdatasets.push({points:linePoints,color:"orange"});
}
$("#busStopAnalysis_AllDaysChart").html("");
var barColor = '#384a60';
var fD = allBarsDatasets.map(function(d){return [d.xAxisTickValue,d.barValue];});
var margin = {top: 20, right: 100, bottom: 30, left: 100},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var padding = 100;
//create svg for histogram.
var svg = d3.select("#busStopAnalysis_AllDaysChart").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 + ")");
// create function for x-axis mapping.
var x = d3.scale.ordinal().rangeRoundBands([0, width], 0.1, 0)
.domain(fD.map(function(d) { return d[0]; }));
// Add x-axis to the histogram svg.
svg.append("g").attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.svg.axis()
.scale(x)
.orient("bottom")
.innerTickSize(-height)
.outerTickSize(0)
.tickPadding(10));
// create function for y-axis mapping.
var yMin = 0;
var yMax = d3.max(fD.map(function(d) { return d[1]; }));
var y = d3.scale.linear().range([height, 0])
.domain([0, d3.max(fD, function(d) { return d[1]; })]);
var yScaleGridLines = d3.scale.linear()
.domain([yMin, yMax])
.range([height, 0]);
var yAxisGridLines = d3.svg.axis()
.scale(yScaleGridLines)
.orient("left")
.innerTickSize(-width)
.outerTickSize(0)
.tickPadding(10);
svg.append("g")
.attr("class", "y axis")
.call(yAxisGridLines);
var layers = d3.layout.stack() (
allBarsDatasets2.map(
function(barDataset) {
return barDataset.map(
function(d) {
return {x: d.xAxisTickValue, y:d.barValue};
}
)
}
)
);
var z = d3.scale.category10();
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) {
var x;
if (i === 0) {
x = "transparent";
} else {
x = "#FF9900";
}
return x;
});
// append a g for all the mouse over nonsense
var mouseG = svg.append("g")
.attr("class", "mouse-over-effects");
// this is the vertical line
svg.append("path")
.attr("class", "mouse-line")
.style("stroke", "grey")
.style("stroke-width", "1px")
.style("opacity", "1");
var tooltip = d3.select("#tooltip");
$("#tooltip").css('display', 'none');
layer.selectAll("rect")
.data(function (d) { return d; })
.enter().append("rect")
.attr("x", function (d) { return x(d.x); })
.attr("y", function (d) { return y(d.y + d.y0); })
.attr("height", function (d) { return y(d.y0) - y(d.y + d.y0); })
.attr("width", x.rangeBand() - 1)
.on("mousemove", function (d) {
var mouse = d3.mouse(this);
// move the vertical line
d3.select(".mouse-line")
.attr("d", function() {
var d = "M" + mouse[0] + "," + height;
d += " " + mouse[0] + "," + 0;
return d;
});
})
.on("mouseover", function (d) {
var mouse = d3.mouse(this);
console.log("second chart");
console.log(mouse);
tooltip.style("left", mouse[0] + "px")
.style("top", mouse[1] + "px")
.style("width", "auto")
.style("height", "auto")
.html("Day: " + $("#busStopAnalysis_Days option:selected").text() + "<br>Time Range: " + d.x + "<br>Avg Wait: " + d.y);
$("#tooltip").css("display", "");
})
.on("mouseout", function() {
$("#tooltip").css("display", "none");
});
// Beginning of line things drawing
// Add min and max x and y borrowed from weird lines
var xMin = app.allLinesdatasets.reduce(function(pv,cv){
var currentXMin = cv.points.reduce(function(pv,cv){
return Math.min(pv,cv.x);
},100)
return Math.min(pv,currentXMin);
},100);
var xMax = allLinesdatasets.reduce(function(pv,cv){
var currentXMax = cv.points.reduce(function(pv,cv){
return Math.max(pv,cv.x);
},0)
return Math.max(pv,currentXMax);
},0);
var yMin = allLinesdatasets.reduce(function(pv,cv){
var currentYMin = cv.points.reduce(function(pv,cv){
return Math.min(pv,cv.y);
},100)
return Math.min(pv,currentYMin);
},100);
var yMax = allLinesdatasets.reduce(function(pv,cv){
var currentYMax = cv.points.reduce(function(pv,cv){
return Math.max(pv,cv.y);
},0)
return Math.max(pv,currentYMax);
},0);
var yScaleGridLines = d3.scale.linear()
.domain([0, yMax])
.range([height, 0]);
var yAxisGridLines = d3.svg.axis()
.scale(yScaleGridLines)
.orient("left")
.innerTickSize(-width)
.outerTickSize(0)
.tickPadding(10);
var xScaleGridLines = {};
xScaleGridLines = d3.scale.linear()
.domain([xMin, xMax])
.range([0, width]);
var xAxisGridLines = d3.svg.axis()
.scale(xScaleGridLines)
.orient("bottom")
.innerTickSize(-height)
.outerTickSize(0)
.tickPadding(10);
var lineGridLines = d3.svg.line()
.interpolate('step-after')
.x(function(d) { return xScaleGridLines(d.x); })
.y(function(d) { return yScaleGridLines(d.y); });
$.each(app.allLinesdatasets, function (idx, dataset) {
svg.append("path")
.data([dataset.points])
.attr("class", "line")
.attr("d", lineGridLines)
.style("stroke", function(){
// return dataset.color;
return "#FF3300";
});
});
}
#busStopAnalysis_Charts .axis path,
#busStopAnalysis_Charts .axis line{
fill: none;
stroke: black;
}
#busStopAnalysis_Charts .line{
fill: none;
stroke: blue;
stroke-width: 2px;
}
#busStopAnalysis_Charts .tick text{
font-size: 12px;
}
#busStopAnalysis_Charts .tick line{
opacity: 0.2;
}
#busStopAnalysis_Charts #tooltip {
position: absolute;
text-align: center;
color: white;
padding: 10px 10px 10px 10px;
display: inline-block;
font: 12px sans-serif;
background-color: #384a60;
border: 3px solid #2f3e50;
-webkit-border-radius: 30px;
-moz-border-radius: 30px;
border-radius: 30px;
-webkit-box-shadow: 2px 2px 4px #888;
-moz-box-shadow: 2px 2px 4px #888;
box-shadow: 2px 2px 4px #888;
}
#busStopAnalysis_Charts #tooltip.hidden {
display: none;
}
#busStopAnalysis_Charts #tooltip p {
margin: 0;
font-family: sans-serif;
font-size: 16px;
line-height: 20px;
}
<div id="busStopAnalysisChartArea_Form">
<div id="busStopAnalysisChartArea_Form_TableRow">
<div id="busStopAnalysisChartArea_Form_Stop">
<label for="family" class="control-label"></label>
<select class="form-control dataset-column" style="width:auto;" id="busStopAnalysis_Stops"></select>
</div>
<div id="busStopAnalysisChartArea_Form_Days">
<label for="family" class="control-label"></label>
<div>
<select class="form-control dataset-column" style="width:auto;float:left;" id="busStopAnalysis_Days"></select>
draw the chart
</div>
</div>
</div>
</div>
<div id="busStopAnalysis_Charts">
<div id="busStopAnalysis_OneDayChart"></div>
<div id="busStopAnalysis_AllDaysChart"></div>
<div>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>

The issue was I was appending the line cursor to both chart as a class with the same name. I guess d3 had trouble identifying which one I was trying to animate so it always picked the first one.
The fix was to replace the classes with IDs as illustrated in the code snippet below.
var app = {};
app.allBarsDatasets = [
{
"xAxisTickValue": "10-1",
"barValue": 17
},
{
"xAxisTickValue": "10-2",
"barValue": 17
},
{
"xAxisTickValue": "10-3",
"barValue": 17
}
];
app.allBarsDatasets2 = [
[
{
"xAxisTickValue": "10-1",
"barValue": 10
},
{
"xAxisTickValue": "10-2",
"barValue": 6
},
{
"xAxisTickValue": "10-3",
"barValue": 7
}
],
[
{
"xAxisTickValue": "10-1",
"barValue": 6
},
{
"xAxisTickValue": "10-2",
"barValue": 8
},
{
"xAxisTickValue": "10-3",
"barValue": 10
}
]
];
app.allLinesDatasets =
{
"points": [
{
"x": 1,
"y": 10
},
{
"x": 2,
"y": 8
},
{
"x": 3,
"y": 14
}
],
"color": "blue"
};
app.busStopsWaitTimes = {
"1": {
"days": {
"we": {
"10-1": [
17,
14,
14,
4,
8,
13,
11,
3,
2,
14,
14,
8,
9,
1,
9,
9,
9,
17,
1,
20
],
"10-2": [
13,
12,
3,
5,
18,
14,
17,
5,
9,
12,
19,
3,
8,
9,
20,
3,
14,
5,
7,
13
],
"10-3": [
18,
8,
8,
7,
10,
20,
16,
17,
6,
13,
5,
11,
11,
14,
18,
17,
11,
17,
4,
3
]
}
},
"name": "Adderley"
}
};
app.populateBusStopsWaitSelectionForm = function () {
let stopOptions = `<option value="">Select a stop</option>`;
$.each(app.busStopsWaitTimes, function (idx, stop) {
stopOptions += `<option value={"stopId":${idx}}>${stop.name}</option>`;
});
$("#busStopAnalysis_Stops").html(stopOptions);
}
app.populateBusStopsWaitSelectionForm();
$("#busStopAnalysis_Stops").change(function() {
let values = $("#busStopAnalysis_Stops").val();
if (values !== "") {
values = JSON.parse(values);
let daysOptions = `<option value="">Select a day</option>`;
if ("we" in app.busStopsWaitTimes[values.stopId].days) {
daysOptions += `<option value={"dayKey":"we"}>Wednesday</option>`
}
$("#busStopAnalysis_Days").html(daysOptions);
} else {
$("#busStopAnalysis_Days").html("<option>Please select a route</option>");
}
});
$("#drawBusStopAnalysisChart").on("click", function (evt) {
evt.preventDefault();
const stopInfo = JSON.parse($("#busStopAnalysis_Stops").val());
const dayInfo = JSON.parse($("#busStopAnalysis_Days").val());
if (stopInfo !== "" || dayInfo !== "") {
const allBarsDatasets = [];
const allBarsDatasets2 = [[],[]]
const allLinesdatasets = [];
const linePoints = [];
let i = 1;
$.each(app.busStopsWaitTimes[stopInfo.stopId]["days"][dayInfo.dayKey], function (idx, timeslot) {
timeslot.sort(function (a,b) {
return a - b;
});
let Percentile25th = timeslot[parseInt(timeslot.length / 4)];
let Percentile50th = timeslot[parseInt(timeslot.length / 2)];
let Percentile75th = timeslot[parseInt((timeslot.length / 4) * 3)];
allBarsDatasets.push({
xAxisTickValue: idx,
barValue: Percentile75th
});
allBarsDatasets2[0].push({
xAxisTickValue: idx,
barValue: Percentile25th
});
allBarsDatasets2[1].push({
xAxisTickValue: idx,
barValue: Percentile75th - Percentile25th
});
linePoints.push({x : i, y : Percentile50th});
i++;
});
allLinesdatasets.push({points:linePoints,color:"blue"});
app.drawBusStopAnalysisOneDayChart(allBarsDatasets, allBarsDatasets2, allLinesdatasets);
}
});
app.drawBusStopAnalysisOneDayChart = function (allBarsDatasets, allBarsDatasets2, allLinesdatasets) {
app.allLinesdatasets = allLinesdatasets;
$("#busStopAnalysis_OneDayChart").html("");
var barColor = '#384a60';
// calculate total frequency by state for all segment.
// var fD = app.allBarsDatasets.map(function(d){return [d.xAxisTickValue,d.barValue];});
var fD = allBarsDatasets.map(function(d){return [d.xAxisTickValue,d.barValue];});
var margin = {top: 20, right: 100, bottom: 30, left: 100},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var padding = 100;
//create svg for histogram.
var svg = d3.select("#busStopAnalysis_OneDayChart").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 + ")");
// create function for x-axis mapping.
var x = d3.scale.ordinal().rangeRoundBands([0, width], 0.1, 0)
.domain(fD.map(function(d) { return d[0]; }));
// Add x-axis to the histogram svg.
svg.append("g").attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.svg.axis()
.scale(x)
.orient("bottom")
.innerTickSize(-height)
.outerTickSize(0)
.tickPadding(10));
// create function for y-axis mapping.
var yMin = 0;
var yMax = d3.max(fD.map(function(d) { return d[1]; }));
var y = d3.scale.linear().range([height, 0])
.domain([0, d3.max(fD, function(d) { return d[1]; })]);
var yScaleGridLines = d3.scale.linear()
.domain([0, yMax])
.range([height, 0]);
var yAxisGridLines = d3.svg.axis()
.scale(yScaleGridLines)
.orient("left")
.innerTickSize(-width)
.outerTickSize(0)
.tickPadding(10);
svg.append("g")
.attr("class", "y axis")
.call(yAxisGridLines);
// You would think d3 draws bar by bar but it draws level by level
// therefore you need to create stacks which are sub-arrays whose contents
// are arrays of elements at the same level
// to achieve that
// call stack,
// call map and iterate over each array
// call map iterate over all elements within an array while creating points based on values to visualize
var layers = d3.layout.stack() (
allBarsDatasets2.map(
function(barDataset) {
return barDataset.map(
function(d) {
return {x: d.xAxisTickValue, y:d.barValue};
}
)
}
)
);
var z = d3.scale.category10();
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) {
var x;
if (i === 0) {
x = "transparent";
} else {
x = "#686868";
}
return x;
});
var mouseG = d3.select("#busStopAnalysis_Charts").append("g")
.attr("id", "mouse-over-effects-one-day-chart");
const lineCursorId = "mouse-line-one-day-chart";
// this is the vertical line
svg.append("path")
.attr("id", lineCursorId)
.style("stroke", "black")
.style("stroke-width", "1px")
.style("opacity", "1");
layer.selectAll("rect")
.data(function (d) { return d; })
.enter().append("rect")
.attr("x", function (d) { return x(d.x); })
.attr("y", function (d) { return y(d.y + d.y0); })
.attr("height", function (d) { return y(d.y0) - y(d.y + d.y0); })
.attr("width", x.rangeBand() - 1)
.on("mousemove", function (d) {
var mouse = d3.mouse(this);
app.drawStopAnalysisLineCursor(mouse, "#" + lineCursorId, height);
})
.on("click", function (d) {
app.drawAllDaysStopAnalysisChart(d);
});
// Beginning of line things drawing
// Add min and max x and y borrowed from weird lines
var xMin = app.allLinesdatasets.reduce(function(pv,cv){
var currentXMin = cv.points.reduce(function(pv,cv){
return Math.min(pv,cv.x);
},100)
return Math.min(pv,currentXMin);
},100);
var xMax = app.allLinesdatasets.reduce(function(pv,cv){
var currentXMax = cv.points.reduce(function(pv,cv){
return Math.max(pv,cv.x);
},0)
return Math.max(pv,currentXMax);
},0);
var yMin = app.allLinesdatasets.reduce(function(pv,cv){
var currentYMin = cv.points.reduce(function(pv,cv){
return Math.min(pv,cv.y);
},100)
return Math.min(pv,currentYMin);
},100);
var yMax = app.allLinesdatasets.reduce(function(pv,cv){
var currentYMax = cv.points.reduce(function(pv,cv){
return Math.max(pv,cv.y);
},0)
return Math.max(pv,currentYMax);
},0);
var yScaleGridLines = d3.scale.linear()
.domain([0, yMax])
.range([height, 0]);
var yAxisGridLines = d3.svg.axis()
.scale(yScaleGridLines)
.orient("left")
.innerTickSize(-width)
.outerTickSize(0)
.tickPadding(10);
var xScaleGridLines = {};
xScaleGridLines = d3.scale.linear()
.domain([xMin, xMax])
.range([0, width]);
var xAxisGridLines = d3.svg.axis()
.scale(xScaleGridLines)
.orient("bottom")
.innerTickSize(-height)
.outerTickSize(0)
.tickPadding(10);
var lineGridLines = d3.svg.line()
.interpolate('step-after')
.x(function(d) { return xScaleGridLines(d.x); })
.y(function(d) { return yScaleGridLines(d.y); });
$.each(app.allLinesdatasets, function (idx, dataset) {
svg.append("path")
.data([dataset.points])
.attr("class", "line")
.attr("d", lineGridLines)
.style("stroke", function(){
// return dataset.color;
return "#FF9900";
});
});
}
/********************************************************************
* Append and Draw Second Chart
********************************************************************/
app.drawAllDaysStopAnalysisChart = function drawAllDateStopAnalysis (timeRange) {
const stopInfo = JSON.parse($("#busStopAnalysis_Stops").val());
const allBarsDatasets = [];
const allBarsDatasets2 = [[],[]]
const allLinesdatasets = [];
const linePoints = [];
if (stopInfo !== "" || dayInfo !== "") {
let i = 1;
$.each(app.busStopsWaitTimes[stopInfo.stopId]["days"], function (idx, day) {
const stopRangeWaitTimeInfo = day[timeRange.x];
stopRangeWaitTimeInfo.sort(function (a,b) {
return a - b;
});
let Percentile25th = stopRangeWaitTimeInfo[parseInt(stopRangeWaitTimeInfo.length / 4)];
let Percentile50th = stopRangeWaitTimeInfo[parseInt(stopRangeWaitTimeInfo.length / 2)];
let Percentile75th = stopRangeWaitTimeInfo[parseInt((stopRangeWaitTimeInfo.length / 4) * 3)];
allBarsDatasets.push({
xAxisTickValue: idx,
barValue: Percentile75th
});
allBarsDatasets2[0].push({
xAxisTickValue: idx,
barValue: Percentile25th
});
allBarsDatasets2[1].push({
xAxisTickValue: idx,
barValue: Percentile75th - Percentile25th
});
linePoints.push({x : i, y : Percentile50th});
i++;
});
allLinesdatasets.push({points:linePoints,color:"orange"});
}
$("#busStopAnalysis_AllDaysChart").html("");
var barColor = '#384a60';
var fD = allBarsDatasets.map(function(d){return [d.xAxisTickValue,d.barValue];});
var margin = {top: 20, right: 100, bottom: 30, left: 100},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var padding = 100;
//create svg for histogram.
var svg = d3.select("#busStopAnalysis_AllDaysChart").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 + ")");
// create function for x-axis mapping.
var x = d3.scale.ordinal().rangeRoundBands([0, width], 0.1, 0)
.domain(fD.map(function(d) { return d[0]; }));
// Add x-axis to the histogram svg.
svg.append("g").attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.svg.axis()
.scale(x)
.orient("bottom")
.innerTickSize(-height)
.outerTickSize(0)
.tickPadding(10));
// create function for y-axis mapping.
var yMin = 0;
var yMax = d3.max(fD.map(function(d) { return d[1]; }));
var y = d3.scale.linear().range([height, 0])
.domain([0, d3.max(fD, function(d) { return d[1]; })]);
var yScaleGridLines = d3.scale.linear()
.domain([yMin, yMax])
.range([height, 0]);
var yAxisGridLines = d3.svg.axis()
.scale(yScaleGridLines)
.orient("left")
.innerTickSize(-width)
.outerTickSize(0)
.tickPadding(10);
svg.append("g")
.attr("class", "y axis")
.call(yAxisGridLines);
var layers = d3.layout.stack() (
allBarsDatasets2.map(
function(barDataset) {
return barDataset.map(
function(d) {
return {x: d.xAxisTickValue, y:d.barValue};
}
)
}
)
);
var z = d3.scale.category10();
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) {
var x;
if (i === 0) {
x = "transparent";
} else {
x = "#FF9900";
}
return x;
});
// append a g for all the mouse over nonsense
var mouseG = svg.append("g")
.attr("id", "mouse-over-effects-all-day-chart");
let lineCursorId = "mouse-line-all-day-chart";
// this is the vertical line
svg.append("path")
.attr("id", lineCursorId)
.style("stroke", "grey")
.style("stroke-width", "1px")
.style("opacity", "1");
layer.selectAll("rect")
.data(function (d) { return d; })
.enter().append("rect")
.attr("x", function (d) { return x(d.x); })
.attr("y", function (d) { return y(d.y + d.y0); })
.attr("height", function (d) { return y(d.y0) - y(d.y + d.y0); })
.attr("width", x.rangeBand() - 1)
.on("mousemove", function (d) {
var mouse = d3.mouse(this);
app.drawStopAnalysisLineCursor(mouse, "#" + lineCursorId, height)
});
// Beginning of line things drawing
// Add min and max x and y borrowed from weird lines
var xMin = app.allLinesdatasets.reduce(function(pv,cv){
var currentXMin = cv.points.reduce(function(pv,cv){
return Math.min(pv,cv.x);
},100)
return Math.min(pv,currentXMin);
},100);
var xMax = allLinesdatasets.reduce(function(pv,cv){
var currentXMax = cv.points.reduce(function(pv,cv){
return Math.max(pv,cv.x);
},0)
return Math.max(pv,currentXMax);
},0);
var yMin = allLinesdatasets.reduce(function(pv,cv){
var currentYMin = cv.points.reduce(function(pv,cv){
return Math.min(pv,cv.y);
},100)
return Math.min(pv,currentYMin);
},100);
var yMax = allLinesdatasets.reduce(function(pv,cv){
var currentYMax = cv.points.reduce(function(pv,cv){
return Math.max(pv,cv.y);
},0)
return Math.max(pv,currentYMax);
},0);
var yScaleGridLines = d3.scale.linear()
.domain([0, yMax])
.range([height, 0]);
var yAxisGridLines = d3.svg.axis()
.scale(yScaleGridLines)
.orient("left")
.innerTickSize(-width)
.outerTickSize(0)
.tickPadding(10);
var xScaleGridLines = {};
xScaleGridLines = d3.scale.linear()
.domain([xMin, xMax])
.range([0, width]);
var xAxisGridLines = d3.svg.axis()
.scale(xScaleGridLines)
.orient("bottom")
.innerTickSize(-height)
.outerTickSize(0)
.tickPadding(10);
var lineGridLines = d3.svg.line()
.interpolate('step-after')
.x(function(d) { return xScaleGridLines(d.x); })
.y(function(d) { return yScaleGridLines(d.y); });
$.each(app.allLinesdatasets, function (idx, dataset) {
svg.append("path")
.data([dataset.points])
.attr("class", "line")
.attr("d", lineGridLines)
.style("stroke", function(){
return "#FF3300";
});
});
}
app.drawStopAnalysisLineCursor = function (mouse, mouseLineId, height) {
// move the vertical line
d3.select(mouseLineId)
.attr("d", function() {
var d = "M" + (mouse[0] -2) + "," + height;
d += " " + (mouse[0] - 2) + "," + 0;
return d;
});
}
#busStopAnalysis_Charts .axis path,
#busStopAnalysis_Charts .axis line{
fill: none;
stroke: black;
}
#busStopAnalysis_Charts .line{
fill: none;
stroke: blue;
stroke-width: 2px;
}
#busStopAnalysis_Charts .tick text{
font-size: 12px;
}
#busStopAnalysis_Charts .tick line{
opacity: 0.2;
}
<div id="busStopAnalysisChartArea_Form">
<div id="busStopAnalysisChartArea_Form_TableRow">
<div id="busStopAnalysisChartArea_Form_Stop">
<label for="family" class="control-label"></label>
<select class="form-control dataset-column" style="width:auto;" id="busStopAnalysis_Stops"></select>
</div>
<div id="busStopAnalysisChartArea_Form_Days">
<label for="family" class="control-label"></label>
<div>
<select class="form-control dataset-column" style="width:auto;float:left;" id="busStopAnalysis_Days"></select>
draw the chart
</div>
</div>
</div>
</div>
<div id="busStopAnalysis_Charts">
<div id="busStopAnalysis_OneDayChart"></div>
<div id="busStopAnalysis_AllDaysChart"></div>
</div>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>

Related

How to plot data points on multi-line chart with multiple y axes

I am trying to add data points to my line chart with multiple y axes. Click here for my fiddle.
//after restructuring dataset array
var data = [{
data: [{
x: 0,
y: 0
}, {
x: 10,
y: 10
}, {
x: 20,
y: 20
}, {
x: 30,
y: 30
}, {
x: 40,
y: 40
}],
yAxis: 0,
}, {
data: [{
x: 0,
y: 0
}, {
x: 10,
y: 200
}, {
x: 20,
y: 300
}, {
x: 30,
y: 400
}, {
x: 40,
y: 500
}],
yAxis: 1,
}];
const margin = {
left: 20,
right: 20,
top: 20,
bottom: 80
};
const svg = d3.select('svg');
svg.selectAll("*").remove();
const width = 200 - margin.left - margin.right;
const height = 200 - margin.top - margin.bottom;
const g = svg.append('g').attr('transform', `translate(${80},${margin.top})`);
//************* Axes and Gridlines ***************
const xAxisG = g.append('g');
const yAxisG = g.append('g');
xAxisG.append('text')
.attr('class', 'axis-label')
.attr('x', width / 3)
.attr('y', -10)
.style('fill', 'black')
.text(function(d) {
return "X Axis";
});
yAxisG.append('text')
.attr('class', 'axis-label')
.attr('id', 'yAxisLabel0')
.attr('x', -height / 2)
.attr('y', -15)
.attr('transform', `rotate(-90)`)
.style('text-anchor', 'middle')
.style('fill', 'black')
.text(function(d) {
return "Y Axis 1";
});
// interpolator for X axis -- inner plot region
var x = d3.scaleLinear()
.domain([0, d3.max(xValueArray)])
.range([0, width])
.nice();
var yScale = new Array();
for (var i = 0; i < 2; i++) {
// interpolator for Y axis -- inner plot region
var y = d3.scaleLinear()
.domain([0, d3.max(arr[i])])
.range([0, height])
.nice();
yScale.push(y);
}
const xAxis = d3.axisTop()
.scale(x)
.ticks(5)
.tickPadding(2)
.tickSize(-height)
const yAxis = d3.axisLeft()
.scale(yScale[0])
.ticks(5)
.tickPadding(2)
.tickSize(-width);
yAxisArray = new Array();
yAxisArray.push(yAxis);
for (var i = 1; i < 2; i++) {
var yAxisSecondary = d3.axisLeft()
.scale(yScale[i])
.ticks(5)
yAxisArray.push(yAxisSecondary);
}
svg.append("g")
.attr("class", "x axis")
.attr("transform", `translate(80,${height-80})`)
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("id", "ySecAxis0")
.attr("transform", "translate(80,20)")
.call(yAxis);
var translation = 50;
var textTranslation = 0;
var yLabelArray = ["Y Axis 1", "Y Axis 2"];
//loop starts from 1 as primary y axis is already plotted
for (var i = 1; i < 2; i++) {
svg.append("g")
.attr("transform", "translate(" + translation + "," + 20 + ")")
.attr("id", "ySecAxis" + i)
.call(yAxisArray[i]);
yAxisG.append('text')
.attr('x', -height / 2)
.attr('y', -60)
.attr('transform', `rotate(-90)`)
.attr("id", "yAxisLabel" + i)
.style('text-anchor', 'middle')
.style('fill', 'black')
.text(yLabelArray[i]);
translation -= 40;
textTranslation += 40;
}
//************* Lines and Data Points ***************
var colors = ["blue", "red"];
var thisScale;
var line = d3.line()
.x(d => x(d.x))
.y(d => thisScale(d.y))
.curve(d3.curveLinear);
var paths = g.selectAll("foo")
.data(data)
.enter()
.append("path");
paths.attr("stroke", function (d,i){return colors[i]})
.attr("d", d => {
thisScale = yScale[d.yAxis]
return line(d.data);
})
.attr("stroke-width", 2)
.attr("id", function (d,i){return "line" + i})
.attr("fill", "none");
var points = g.selectAll("dot")
.data(data)
.enter()
.append("circle");
points.attr("cx", function(d) { return x(d.x)} )
.attr("cy", function(d,i) { return yScale[i](d.y); } )
.attr("r", 3)
.attr("class", function (d,i){return "blackDot" + i})
.attr("clip-path", "url(#clip)")
Right now the console log is showing these errors: Error: attribute cx: Expected length, "NaN". Error: attribute cy: Expected length, "NaN". It seems like I am not attributing the correct cx and cy to points, but I can't figure out what I am doing wrongly. Any help is greatly appreciated!
Your data structure is an array of objects, each one containing an inner array with the real coordinates for the circles. Therefore, that single enter selection will not work.
With minimal refactoring, my solution here is appending groups according to the objects, and then, for each one, appending circles according to the inner arrays. For that cumbersome yScale to work you cannot rely on the circle's indices anymore, so I'm using a local variable here:
var pointsGroup = g.selectAll(null)
.data(data)
.enter()
.append("g")
.attr("fill", function(d, i) {
local.set(this, yScale[i])
return colors[i];
});
var points = pointsGroup.selectAll(null)
.data(function(d) {
return d.data
})
.enter()
.append("circle")
.attr("cx", function(d) {
return x(d.x)
})
.attr("cy", function(d, i) {
return local.get(this)(d.y);
})
//etc...
Here is the code with those changes:
var local = d3.local();
var xValueArray = [0, 10, 20, 30, 40];
var arr = [
[0, 10, 20, 30, 40],
[0, 200, 300, 400, 500]
];
var dataset = [
[{
x: 0,
y: 0
}, {
x: 10,
y: 10
}, {
x: 20,
y: 20
}, {
x: 30,
y: 30
}, {
x: 40,
y: 40
}],
[{
x: 0,
y: 0
}, {
x: 10,
y: 200
}, {
x: 20,
y: 300
}, {
x: 30,
y: 400
}, {
x: 40,
y: 500
}]
];
var data = [];
for (var i = 0; i < 2; i++) {
data.push({
"data": dataset[i],
"yAxis": i
})
}
console.log(data);
//after restructuring dataset array
var data = [{
data: [{
x: 0,
y: 0
}, {
x: 10,
y: 10
}, {
x: 20,
y: 20
}, {
x: 30,
y: 30
}, {
x: 40,
y: 40
}],
yAxis: 0,
}, {
data: [{
x: 0,
y: 0
}, {
x: 10,
y: 200
}, {
x: 20,
y: 300
}, {
x: 30,
y: 400
}, {
x: 40,
y: 500
}],
yAxis: 1,
}];
const margin = {
left: 20,
right: 20,
top: 20,
bottom: 80
};
const svg = d3.select('svg');
svg.selectAll("*").remove();
const width = 200 - margin.left - margin.right;
const height = 200 - margin.top - margin.bottom;
const g = svg.append('g').attr('transform', `translate(${80},${margin.top})`);
//************* Axes and Gridlines ***************
const xAxisG = g.append('g');
const yAxisG = g.append('g');
xAxisG.append('text')
.attr('class', 'axis-label')
.attr('x', width / 3)
.attr('y', -10)
.style('fill', 'black')
.text(function(d) {
return "X Axis";
});
yAxisG.append('text')
.attr('class', 'axis-label')
.attr('id', 'yAxisLabel0')
.attr('x', -height / 2)
.attr('y', -15)
.attr('transform', `rotate(-90)`)
.style('text-anchor', 'middle')
.style('fill', 'black')
.text(function(d) {
return "Y Axis 1";
});
// interpolator for X axis -- inner plot region
var x = d3.scaleLinear()
.domain([0, d3.max(xValueArray)])
.range([0, width])
.nice();
var yScale = new Array();
for (var i = 0; i < 2; i++) {
// interpolator for Y axis -- inner plot region
var y = d3.scaleLinear()
.domain([0, d3.max(arr[i])])
.range([0, height])
.nice();
yScale.push(y);
}
const xAxis = d3.axisTop()
.scale(x)
.ticks(5)
.tickPadding(2)
.tickSize(-height)
const yAxis = d3.axisLeft()
.scale(yScale[0])
.ticks(5)
.tickPadding(2)
.tickSize(-width);
yAxisArray = new Array();
yAxisArray.push(yAxis);
for (var i = 1; i < 2; i++) {
var yAxisSecondary = d3.axisLeft()
.scale(yScale[i])
.ticks(5)
yAxisArray.push(yAxisSecondary);
}
svg.append("g")
.attr("class", "x axis")
.attr("transform", `translate(80,${height-80})`)
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("id", "ySecAxis0")
.attr("transform", "translate(80,20)")
.call(yAxis);
var translation = 50;
var textTranslation = 0;
var yLabelArray = ["Y Axis 1", "Y Axis 2"];
//loop starts from 1 as primary y axis is already plotted
for (var i = 1; i < 2; i++) {
svg.append("g")
.attr("transform", "translate(" + translation + "," + 20 + ")")
.attr("id", "ySecAxis" + i)
.call(yAxisArray[i]);
yAxisG.append('text')
.attr('x', -height / 2)
.attr('y', -60)
.attr('transform', `rotate(-90)`)
.attr("id", "yAxisLabel" + i)
.style('text-anchor', 'middle')
.style('fill', 'black')
.text(yLabelArray[i]);
translation -= 40;
textTranslation += 40;
}
//************* Mouseover ***************
var tooltip = d3.select("body")
.append("div")
.style("opacity", 0)
.attr("class", "tooltip")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "1px")
.style("border-radius", "5px")
.style("padding", "10px")
.style("position", "absolute")
var mouseover = function(d) {
tooltip
.html("x: " + d.x + "<br/>" + "y: " + d.y)
.style("opacity", 1)
.style("left", (d3.mouse(this)[0] + 90) + "px")
.style("top", (d3.mouse(this)[1]) + "px")
}
// A function that change this tooltip when the leaves a point: just need to set opacity to 0 again
var mouseleave = function(d) {
tooltip
.transition()
.duration(200)
.style("opacity", 0)
}
//************* Lines and Data Points ***************
var colors = ["blue", "red"];
var thisScale;
var line = d3.line()
.x(d => x(d.x))
.y(d => thisScale(d.y))
.curve(d3.curveLinear);
var paths = g.selectAll("foo")
.data(data)
.enter()
.append("path");
paths.attr("stroke", function(d, i) {
return colors[i]
})
.attr("d", d => {
thisScale = yScale[d.yAxis]
return line(d.data);
})
.attr("stroke-width", 2)
.attr("id", function(d, i) {
return "line" + i
})
.attr("fill", "none");
var pointsGroup = g.selectAll(null)
.data(data)
.enter()
.append("g")
.attr("fill", function(d, i) {
local.set(this, yScale[i])
return colors[i];
});
var points = pointsGroup.selectAll(null)
.data(function(d) {
return d.data
})
.enter()
.append("circle")
.attr("cx", function(d) {
return x(d.x)
})
.attr("cy", function(d, i) {
return local.get(this)(d.y);
})
.attr("r", 3)
.attr("class", function(d, i) {
return "blackDot" + i
})
.attr("clip-path", "url(#clip)")
.on("mouseover", mouseover)
.on("mouseleave", mouseleave)
//plot lines (hard-coding)
/*var lineFunction1 = d3.line()
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return yScale[0](d.y);
})
.curve(d3.curveLinear);
var lineFunction2 = d3.line()
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return yScale[1](d.y);
})
.curve(d3.curveLinear);
var path1 = g.append("path")
.attr("class", "path" + 0)
.attr("id", "line" + 0)
.attr("d", lineFunction1(data[0]))
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("fill", "none")
.attr("clip-path", "url(#clip)");
var path2 = g.append("path")
.attr("class", "path" + 1)
.attr("id", "line" + 1)
.attr("d", lineFunction2(data[1]))
.attr("stroke", "red")
.attr("stroke-width", 2)
.attr("fill", "none")
.attr("clip-path", "url(#clip)");*/
//plot lines and points using for loop
/*for (var i = 0; i < 2; i++) {
var lineFunction = d3.line()
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return yScale[i](d.y);
})
.curve(d3.curveLinear);
var paths = g.append("path")
.attr("class", "path" + i)
.attr("id", "line" + i)
.attr("d", lineFunction(data[i]))
.attr("stroke", colors[i])
.attr("stroke-width", 2)
.attr("fill", "none")
.attr("clip-path", "url(#clip)")
//plot a circle at each data point
g.selectAll(".dot")
.data(data[i])
.enter().append("circle")
.attr("cx", function(d) { return x(d.x)} )
.attr("cy", function(d) { return yScale[i](d.y); } )
.attr("r", 3)
.attr("class", "blackDot" + i)
.attr("clip-path", "url(#clip)")
.on("mouseover", mouseover)
.on("mouseleave", mouseleave)
}*/
//************* Legend ***************
var legend = svg.selectAll(".legend")
.data(data)
.enter().append("g")
legend.append("rect")
.attr("x", width + 65)
.attr("y", function(d, i) {
return 30 + i * 20;
})
.attr("width", 18)
.attr("height", 4)
.style("fill", function(d, i) {
return colors[i];
})
legend.append("text")
.attr("x", width + 60)
.attr("y", function(d, i) {
return 30 + i * 20;
})
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d, i) {
return "Value" + (i + 1);
})
.on("click", function(d, i) {
// Determine if current line is visible
let opacity = d3.select("#line" + i).style("opacity");
let newOpacity;
if (opacity == 0) {
newOpacity = 1;
} else {
newOpacity = 0
}
d3.select("#line" + i).style("opacity", newOpacity);
d3.selectAll(".blackDot" + i).style("opacity", newOpacity);
d3.select("#ySecAxis" + i).style("opacity", newOpacity);
d3.select("#yAxisLabel" + i).style("opacity", newOpacity);
});
//************* Zoom & Brush***************
const margin2 = {
left: 80,
right: 0,
top: 80,
bottom: 0
};
const height2 = height - margin2.top - margin2.bottom;
var xZoom = d3.scaleLinear().range([0, width]);
var yZoom = d3.scaleLinear().range([height2, 0]);
var xAxis2 = d3.axisTop(xZoom);
var brush = d3.brushX()
.extent([
[0, 0],
[width, height2]
])
.on("brush end", brushed);
var zoom = d3.zoom()
.scaleExtent([1, Infinity])
.translateExtent([
[0, 0],
[width, height]
])
.extent([
[0, 0],
[width, height]
])
.on("zoom", zoomed);
var clip = svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("width", width)
.attr("height", height)
.attr("x", 0)
.attr("y", 0);
xZoom.domain(x.domain());
yZoom.domain(y.domain());
var context = svg.append("g")
.attr("class", "context")
.attr("transform", "translate(" + margin2.left + "," + 125 + ")");
context.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height2 + ")")
.call(xAxis2);
context.append("g")
.attr("class", "brush")
.call(brush)
.call(brush.move, x.range());
function brushed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return;
var s = d3.event.selection || xZoom.range();
x.domain(s.map(xZoom.invert, xZoom));
svg.select(".x.axis").call(xAxis);
//svg.select(".path0").attr("d", lineFunction1(data[0]));
//svg.select(".path1").attr("d", lineFunction2(data[1]));
for (var i = 0; i < 2; i++) {
//svg.select(".path" + i).attr("d", lineFunction(data[i]));
g.selectAll(".blackDot" + i)
.attr("cx", function(d) {
return x(d.x);
})
.attr("cy", function(d) {
return yScale[i](d.y);
})
.attr("r", 3)
}
}
function zoomed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return;
var t = d3.event.transform;
x.domain(t.rescaleX(xZoom).domain());
svg.select(".x.axis").transiton(t).call(xAxis);
//svg.select(".path0").transiton(t).attr("d", lineFunction1(data[0]));
//svg.select(".path1").transiton(t).attr("d", lineFunction2(data[1]));
for (var i = 0; i < 2; i++) {
//svg.select(".path" + i).attr("d", lineFunction(data[i]));
g.selectAll(".blackDot" + i)
.attr("cx", function(d) {
return x(d.x);
})
.attr("cy", function(d) {
return yScale[i](d.y);
})
.attr("r", 3)
}
}
.xy_chart {
position: relative;
left: 70px;
top: 100px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg class="xy_chart"></svg>
Pay attention to the fact that one of the circles has an incorrect cy value. So, I'd suggest you to change your y scale approach.

Dynamically scale data in an interactive chart, in which the data gets altered?

I have made an interactive bar chart, where the bars can be dragged up and down to adjust the data.
As the bar is dragged beyond the current max/min of the y-axis domain, the y-axis scales accordingly. However, I cannot get the rest of the bars to scale accordingly (i.e: if I increase one bar's value to a new extreme, the other bars should shrink along with the new scale)
I have a JS Fiddle here with everything that works so far.
// canvas properties
var margin =
{
top: 40,
bottom: 40,
right: 30,
left: 50
}
var w = 960 - margin.left - margin.right,
h = 500 - margin.top - margin.bottom;
// initiating axes
var x = d3.scale.ordinal()
.rangeRoundBands([0, w], 0.1);
var y = d3.scale.linear()
.range([h, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(0)
.tickPadding(6);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var zeroline = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(0)
.tickFormat('')
var svg = d3.select("body").append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var newValue;
var data = [
{name: "A", value: -15},
{name: "B", value: -20},
{name: "C", value: -22},
{name: "D", value: -18},
{name: "E", value: 2},
{name: "F", value: 6},
{name: "G", value: 26},
{name: "H", value: 18}
];
function type(d)
{
d.value = +d.value;
return d;
}
function generateChart(error, data)
{
/* ========== Parse Data & Create Axes ========== */
// create a new property called y (needed for d3.events)
var data = data.map(function (d, i)
{
return {
name: d.name,
value: d.value,
y: d.value
}
});
var max = d3.max(data, function (d) { return d.y; });
var min = -max;
y.domain([min, max]).nice();
x.domain(data.map(function (d) { return d.name; }));
var zz = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (h / 2) + ")")
.call(zeroline);
var xx = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (h + 20) + ")")
.call(xAxis);
var yy = svg.append("g")
.attr("class", "y axis")
.call(yAxis);
/* ========== Drag Behaviour for Rectangles ========== */
var drag = d3.behavior.drag()
.on("drag", resize);
/* ========== Create Rectangles ========== */
var DataBar = svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", function (d) { return "bar bar--" + (d.value < 0 ? "negative" : "positive"); })
.attr("id", function (d) { return (d.value < 0 ? "negative" : "positive"); })
.attr("x", function (d) { return x(d.name); })
.attr("y", function (d) { return y(Math.max(0, d.value)); })
.attr("width", x.rangeBand())
.attr("height", function (d) { return Math.abs(y(d.value) - y(0)); })
.attr("cursor", "ns-resize")
.call(drag);
/* ========== Drag Functions ========== */
function resize(d)
{
if (d3.select(this)[0][0].id == 'positive')
{
d.y = d3.event.y;
if (y.invert(d.y) >= 0) // positive -> postive
{
var barHeight = -(d.y - y(0));
var bar = d3.select(this);
bar.attr("y", function (d) { return d.y; })
.attr("height", barHeight)
.style("fill", "steelblue");
}
else if (y.invert(d.y) < 0) // positive -> negative
{
var barHeight = Math.abs((d.y) - y(0))
var dragy = d3.event.y
barHeight += dragy - (d.y);
var bar = d3.select(this)
bar.attr("height", barHeight)
.attr("y", y(0))
.style("fill", "darkorange");
}
newValue = y.invert(d.y);
}
else if (d3.select(this)[0][0].id == 'negative')
{
var barHeight = Math.abs(y(d.y) - y(0))
var dragy = d3.event.y
if (y.invert(dragy) < 0) // negative -> negative
{
barHeight += dragy - y(d.y);
var bar = d3.select(this)
bar.attr("height", barHeight)
.attr("y", y(0))
.style("fill", "darkorange");
}
else if (y.invert(dragy) >= 0) // negative -> positive
{
var barHeight = -(dragy - y(0));
var bar = d3.select(this);
bar.attr("y", function (d) { return dragy; })
.attr("height", barHeight)
.style("fill", "steelblue");
}
//newValue = y.invert(dragy);
}
var max = d3.max(data, function (d) { return d.value; });
var min = -max;
var update = [];
if (newValue > max)// || newValue < min)
{
y.domain([-newValue, newValue]).nice();
yy.call(yAxis)
}
}
}
generateChart('error!', data)
(Quick note: the y-axis rescaling only works with the initial blue bars at the moment.)
Add the following block of code after the if (newValue > max) { ... } block:
var selectedObjectName = d3.select(this).data()[0].name;
svg.selectAll("rect.bar").filter(function (d){
return (d.name != selectedObjectName);})
.attr("height", function (d) { return Math.abs(y(d.value) - y(0));})
.attr("y", function (d) { return y(Math.max(0, d.value)); });
The idea is to select all the rectangles, filter out the currently selected one, and re-adjust the height and y coordinate of the remaining rectangles. Fiddle

I want to change the domain in th below code in d3

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 !

make the series d3 graph chart

I trying to display a d3 linechart. I've a problem - I cannot stop the date from repeating. How can I stop the date keep repeating? I only want to show two columns(17/12/2013 and 18/12/2013) based on the JSON data reflected below. Or what do I need to do so the first tickmark would show 17/12/2013 and the last one would show 18/12/2013?
[
{
"key": "Excited",
"values": [ [1387212490000, 0], [1387298890000 , 10] ]
},
{
"key": "Sad",
"values": [ [1387212490000, 20], [1387298890000 , 50] ]
},
{
"key": "Angry",
"values": [ [1387212490000, 30], [1387298890000 , 30] ]
},
{
"key": "Happy",
"values": [ [1387212490000, 40], [1387298890000 , 70] ]
}
]
Below is the JS script
$(document).ready(function() {
d3.json('sales.json', function(data) {
nv.addGraph(function() {
var chart = nv.models.lineChart().x(function(d) {
return d[0]
}).y(function(d) {
return d[1]
}).color(d3.scale.category10().range())
.useInteractiveGuideline(true);
chart.xAxis.tickFormat(function(d) {
return d3.time.format('%d/%m/%Y')(new Date(d))
});
//chart.xScale(d3.time.scale());
d3.select('#nvd3 svg').datum(data).transition().duration(500).call(chart);
nv.utils.windowResize(chart.update);
return chart;
});
});
});
You didn't show enough code so it may be difficult to debug...
Anyway, try this and I'm working on an example to prove it...
chart.xAxis
.tickFormat(function(d) {
return d3.time.format('%d/%m/%Y')(new Date(d))
})
.ticks(d3.time.days, 1)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
var margin = {top: 20, right: 40, bottom: 30, left: 20},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
barWidth = Math.floor(width / 19) - 1;
var x = d3.scale.linear()
.range([barWidth / 2, width - barWidth / 2]);
var y = d3.scale.linear()
.range([height, 0]);
var yAxis = d3.svg.axis()
.scale(y)
.orient("right")
.tickSize(-width)
.tickFormat(function(d) { return Math.round(d / 1e6) + "M"; });
// An SVG element with a bottom-right origin.
var svg = d3.select("body").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 + ")");
// A sliding container to hold the bars by birthyear.
var birthyears = svg.append("g")
.attr("class", "birthyears");
// A label for the current year.
var title = svg.append("text")
.attr("class", "title")
.attr("dy", ".71em")
.text(2000);
d3.csv("population.csv", function(error, data) {
// Convert strings to numbers.
data.forEach(function(d) {
d.people = +d.people;
d.year = +d.year;
d.age = +d.age;
});
// Compute the extent of the data set in age and years.
var age1 = d3.max(data, function(d) { return d.age; }),
year0 = d3.min(data, function(d) { return d.year; }),
year1 = d3.max(data, function(d) { return d.year; }),
year = year1;
// Update the scale domains.
x.domain([year1 - age1, year1]);
y.domain([0, d3.max(data, function(d) { return d.people; })]);
// Produce a map from year and birthyear to [male, female].
data = d3.nest()
.key(function(d) { return d.year; })
.key(function(d) { return d.year - d.age; })
.rollup(function(v) { return v.map(function(d) { return d.people; }); })
.map(data);
// Add an axis to show the population values.
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width + ",0)")
.call(yAxis)
.selectAll("g")
.filter(function(value) { return !value; })
.classed("zero", true);
// Add labeled rects for each birthyear (so that no enter or exit is required).
var birthyear = birthyears.selectAll(".birthyear")
.data(d3.range(year0 - age1, year1 + 1, 5))
.enter().append("g")
.attr("class", "birthyear")
.attr("transform", function(birthyear) { return "translate(" + x(birthyear) + ",0)"; });
birthyear.selectAll("rect")
.data(function(birthyear) { return data[year][birthyear] || [0, 0]; })
.enter().append("rect")
.attr("x", -barWidth / 2)
.attr("width", barWidth)
.attr("y", y)
.attr("height", function(value) { return height - y(value); });
// Add labels to show birthyear.
birthyear.append("text")
.attr("y", height - 4)
.text(function(birthyear) { return birthyear; });
// Add labels to show age (separate; not animated).
svg.selectAll(".age")
.data(d3.range(0, age1 + 1, 5))
.enter().append("text")
.attr("class", "age")
.attr("x", function(age) { return x(year - age); })
.attr("y", height + 4)
.attr("dy", ".71em")
.text(function(age) { return age; });
// Allow the arrow keys to change the displayed year.
window.focus();
d3.select(window).on("keydown", function() {
switch (d3.event.keyCode) {
case 37: year = Math.max(year0, year - 10); break;
case 39: year = Math.min(year1, year + 10); break;
}
update();
});
function update() {
if (!(year in data)) return;
title.text(year);
birthyears.transition()
.duration(750)
.attr("transform", "translate(" + (x(year1) - x(year)) + ",0)");
birthyear.selectAll("rect")
.data(function(birthyear) { return data[year][birthyear] || [0, 0]; })
.transition()
.duration(750)
.attr("y", y)
.attr("height", function(value) { return height - y(value); });
}
});
svg {
font: 10px sans-serif;
}
.y.axis path {
display: none;
}
.y.axis line {
stroke: #fff;
stroke-opacity: .2;
shape-rendering: crispEdges;
}
.y.axis .zero line {
stroke: #000;
stroke-opacity: 1;
}
.title {
font: 300 78px Helvetica Neue;
fill: #666;
}
.birthyear,
.age {
text-anchor: middle;
}
.birthyear {
fill: #fff;
}
rect {
fill-opacity: .6;
fill: #e377c2;
}
rect:first-child {
fill: #1f77b4;
}

Adding several y axes with zoom / pan in D3js

I'm working on a horizontal line chart in d3js that displays several lines based on json input. It has zooming and panning, but also need to display a y-axis for each of the drawn lines. In my case, three.
First off, is this bad practice? Should I stack all three on one side, or should I keep two on the left, one on the right or any other combination?
I've tried following this tutorial, but that did really just create more mess and confusing code.
I was hoping someone could guide me in the direction of how to add the additional y axes and how I could have them work with the zooming and panning as well, like the one I have now.
Here's my current view:
And here's my code:
<script>
var margin = { top: 20, right: 80, bottom: 20, left: 40 },
width = ($("#trendcontainer").width() - 50) - margin.left - margin.right,
height = 650 - margin.top - margin.bottom;
var svg;
var format = d3.time.format("%Y-%m-%dT%H:%M:%S").parse;
var x = d3.time.scale()
.range([0, width]);
var y0 = d3.scale.linear()
.range([height, 0]);
var y1 = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(-height);
// TODO: Rename axis to instrument name (i.e 'depth')
var yAxis0 = d3.svg.axis()
.scale(y0)
.orient("left")
.tickSize(-width);
var yAxis1 = d3.svg.axis()
.scale(y1)
.orient("right")
.tickSize(-width);
var line = d3.svg.line()
.interpolate("basis")
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y0(d.value);
});
d3.json('#Url.Action("DataBlob", "Trend", new {id = Model.Unit.UnitId, runId = Request.Params["runId"]})', function(error, tmparray) {
var json = JSON.parse(tmparray);
$('#processing').hide();
color.domain(d3.keys(json[0]).filter(function(key) {
return key !== "Time" && key !== "Id";
}));
json.forEach(function(d) {
var date = format(d.Time);
d.Time = date;
});
var instruments = color.domain().map(function(name) {
return {
name: name,
values: json.map(function(d) {
return {
date: d.Time,
value: +d[name]
};
})
};
});
x.domain(d3.extent(json, function(d) {
return d.Time;
}));
y0.domain([
d3.min(instruments, function (c) {
if (c.name == "Depth") {
return d3.min(c.values, function (v) {
return v.value;
});
}
//return d3.min(c.values, function (v) {
// return v.value;
//});
}),
d3.max(instruments, function(c) {
return d3.max(c.values, function(v) {
return v.value;
});
})
]);
y1.domain([
d3.min(instruments, function (c) {
console.log("In y1.domain c is: " + c);
if (c.name == "Weight") {
return d3.min(c.values, function (v) {
return v.value;
});
}
//return d3.min(c.values, function (v) {
// return v.value;
//});
}),
d3.max(instruments, function(c) {
return d3.max(c.values, function(v) {
return v.value;
});
})
]);
var zoom = d3.behavior.zoom()
.x(x)
.y(y0)
.scaleExtent([1, 10])
.on("zoom", zoomed);
svg = d3.select(".panel-body").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 + ")")
.call(zoom)
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
svg.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis0);
svg.append("g")
.attr("class", "y axis")
.call(yAxis1);
var instrument = svg.selectAll(".instrument")
.data(instruments)
.enter().append("g")
.attr("class", "instrument");
instrument.append("path")
.attr("class", "line")
.attr("d", function(d) {
return line(d.values);
})
.style("stroke", function(d) {
return color(d.name);
});
instrument.append("text")
.datum(function(d) {
return {
name: d.name,
value: d.values[d.values.length - 1]
};
})
.attr("transform", function(d) {
return "translate(" + x(d.value.date) + "," + y0(d.value.value) + ")";
})
.attr("x", 3)
.attr("dy", ".35em")
.text(function(d) {
return d.name;
});
});
function zoomed() {
svg.select(".x.axis").call(xAxis);
svg.select(".y.axis").call(yAxis0);
svg.select(".x.grid")
.call(make_x_axis()
.tickSize(-height, 0, 0)
.tickFormat(""));
svg.select(".y.grid")
.call(make_y_axis()
.tickSize(-width, 0, 0)
.tickFormat(""));
svg.selectAll(".line")
.attr("d", function(d) { return line(d.values); });
};
var make_x_axis = function() {
return d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5);
};
var make_y_axis = function() {
return d3.svg.axis()
.scale(y0)
.orient("left")
.ticks(5);
};
</script>
Finally, here's what I'm trying to achieve (This component is way too slow, and does not handle large datasets well):
Finally ended up at a solution, with some kind assistance from #LarsKotthoff. Also added multiple axes zoom, based on this post.
<script>
/* d3 vars */
var x;
var y1;
var y2;
var y3;
var graph;
var m = [];
var w;
var h;
/* d3 axes */
var xAxis;
var yAxisLeft;
var yAxisLeftLeft;
var yAxisRight;
/* d3 lines */
var line1;
var line2;
var line3;
/* d3 zoom */
var zoom;
var zoomLeftLeft;
var zoomRight;
/* Data */
var speed = [];
var depth = [];
var weight = [];
var timestamp = [];
var url = '#Url.Action("DataBlob", "Trend", new {id = Model.Unit.UnitId, runId = Request.Params["runId"]})';
var data = $.getJSON(url, null, function(data) {
var list = JSON.parse(data);
var format = d3.time.format("%Y-%m-%dT%H:%M:%S").parse;
list.forEach(function(d) {
speed.push(d.Speed);
depth.push(d.Depth);
weight.push(d.Weight);
var date = format(d.Time);
d.Time = date;
timestamp.push(d.Time);
});
m = [10, 80, 30, 100]; // margins: top, right, bottom, left
w = $("#trendcontainer").width() - m[1] - m[3]; // width
h = 550 - m[0] - m[2]; // height
x = d3.time.scale().domain(d3.extent(timestamp, function (d) {
return d;
})).range([0, w]);
y1 = d3.scale.linear().domain([0, d3.max(speed)]).range([h, 0]);
y2 = d3.scale.linear().domain([0, d3.max(depth)]).range([h, 0]);
y3 = d3.scale.linear().domain([0, d3.max(weight)]).range([h, 0]);
line1 = d3.svg.line()
.interpolate("basis")
.x(function (d, i) {
return x(timestamp[i]);
})
.y(function (d) {
return y1(d);
});
line2 = d3.svg.line()
.interpolate("basis")
.x(function (d, i) {
return x(timestamp[i]);
})
.y(function (d) {
return y2(d);
});
line3 = d3.svg.line()
.interpolate("basis")
.x(function (d, i) {
return x(timestamp[i]);
})
.y(function (d) {
return y3(d);
});
zoom = d3.behavior.zoom()
.x(x)
.y(y1)
.scaleExtent([1, 10])
.on("zoom", zoomed);
zoomLeftLeft = d3.behavior.zoom()
.x(x)
.y(y3)
.scaleExtent([1, 10]);
zoomRight = d3.behavior.zoom()
.x(x)
.y(y2)
.scaleExtent([1, 10]);
// Add an SVG element with the desired dimensions and margin.
graph = d3.select(".panel-body").append("svg:svg")
.attr("width", w + m[1] + m[3])
.attr("height", h + m[0] + m[2])
.call(zoom)
.append("svg:g")
.attr("transform", "translate(" + m[3] + "," + m[0] + ")");
// create xAxis
xAxis = d3.svg.axis().scale(x).tickSize(-h).tickSubdivide(false);
// Add the x-axis.
graph.append("svg:g")
.attr("class", "x axis")
.attr("transform", "translate(0," + h + ")")
.call(xAxis);
// create left yAxis
yAxisLeft = d3.svg.axis().scale(y1).ticks(10).orient("left");
// Add the y-axis to the left
graph.append("svg:g")
.attr("class", "y axis axisLeft")
.attr("transform", "translate(-15,0)")
.call(yAxisLeft);
// create leftleft yAxis
yAxisLeftLeft = d3.svg.axis().scale(y3).ticks(10).orient("left");
// Add the y-axis to the left
graph.append("svg:g")
.attr("class", "y axis axisLeftLeft")
.attr("transform", "translate(-50,0)")
.call(yAxisLeftLeft);
// create right yAxis
yAxisRight = d3.svg.axis().scale(y2).ticks(10).orient("right");
// Add the y-axis to the right
graph.append("svg:g")
.attr("class", "y axis axisRight")
.attr("transform", "translate(" + (w + 15) + ",0)")
.call(yAxisRight);
// add lines
// do this AFTER the axes above so that the line is above the tick-lines
graph.append("svg:path").attr("d", line1(speed)).attr("class", "y1");
graph.append("svg:path").attr("d", line2(depth)).attr("class", "y2");
graph.append("svg:path").attr("d", line3(weight)).attr("class", "y3");
});
function zoomed() {
zoomRight.scale(zoom.scale()).translate(zoom.translate());
zoomLeftLeft.scale(zoom.scale()).translate(zoom.translate());
graph.select(".x.axis").call(xAxis);
graph.select(".y.axisLeft").call(yAxisLeft);
graph.select(".y.axisLeftLeft").call(yAxisLeftLeft);
graph.select(".y.axisRight").call(yAxisRight);
graph.select(".x.grid")
.call(make_x_axis()
.tickFormat(""));
graph.select(".y.axis")
.call(make_y_axis()
.tickSize(5, 0, 0));
graph.selectAll(".y1")
.attr("d", line1(speed));
graph.selectAll(".y2")
.attr("d", line2(depth));
graph.selectAll(".y3")
.attr("d", line3(weight));
};
var make_x_axis = function () {
return d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5);
};
var make_y_axis = function () {
return d3.svg.axis()
.scale(y1)
.orient("left")
.ticks(5);
};
</script>

Categories

Resources