Related
I'm trying to build a combo chart, i.e. vertical stack bar and a line chart together. I have built the graph but i want the value of each bar on top of the bar. I found certain code for text on top of single bar but not a clear answer for stacked bar. I have written down some code which is available below and I have commented it as // code i tried for text on top of each stack//. But that doesnt seem to work.
d3GroupBarChart(datas){
this.showData = datas
let textArray = [];
datas.forEach(element => {
element.stack.forEach(stack => {
textArray.push(stack)
});
});
if (datas === null || datas.length == 0) {
$(".sieir-chart").empty()
$('.sieir-chart').append(`<div class="no-card-data" >
<h5>No Data Available </h5>
</div>`)
return
}
$('.sieir-chart').html('')
var margin = { top: 20, right: 80, bottom: 100, left: 80 },
width = $('.group-bar-chart').width() - margin.left - margin.right,
height = 410 - margin.top - margin.bottom;
var svg: any = d3.select(".sieir-chart")
.append("svg")
.attr("viewBox", `0 0 ${$('.group-bar-chart').width()} 410`)
.attr("preserveAspectRatio", "xMinYMin meet")
var g = svg.append("g")
.attr("height", height)
.attr("transform",
"translate(" + (margin.left) + "," + (20) + ")");
var x: any = d3.scaleBand()
.range([0, width])
.domain(datas.map(function (d) { return d.group; }))
.padding(0.2);
var yMax = Math.max.apply(Math, datas.map(function (o) { return o.maxBarValue; }))
// Add Y axis
var y = d3.scaleLinear()
.domain([0, yMax])
.range([height, 0])
.nice();
var self = this;
var formatyAxis = d3.format('.0f');
g.append("g")
.style('font-weight', 'bold')
.call(d3.axisLeft(y).tickFormat(function (d: any) {
if (d % 1 === 0) {
return d.toLocaleString()
}
else {
return ''
}
}).ticks(5));
var y1Max = Math.max.apply(Math, datas.map(function (o) { return o.percentage; }))
var y1: any = d3.scaleLinear().range([height, 0]).domain([0, y1Max]);
var yAxisRight: any = d3.axisRight(y1).ticks(5)
// //this will make the y axis to the right
g.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + (width) + " ,0)")
.style('font-weight', 'bold')
.call(yAxisRight);
// // text label for the y axis
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - (margin.left - 100))
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.style("font-family", "poppins_regular")
.text("Logged User Count");
// text label for the y1 axis
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y1", 0 - (margin.right - 50))
.attr("x", 0 - (height / 2))
.attr("dy", width + 130)
.style("text-anchor", "middle")
.style("font-family", "poppins_regular")
.text("Duration in min");
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.selectAll(".tick text")
.attr("transform", "translate(-5,7)rotate(-15)")
.style("text-anchor", "middle")
.style("font-size", "11px")
.style('font-weight', 'bold')
.call(this.wrap, x.bandwidth())
var subgroups = ["Total Headcount","Onboarded resource count"];
var groups = d3.map(datas, function (d) { return (d['group']) }).keys();
// Another scale for subgroup position?
var xSubgroup = d3.scaleBand()
.domain(subgroups)
.range([0, x.bandwidth()])
.padding(0.05)
// color palette = one color per subgroup
var color = d3.scaleOrdinal()
.domain(subgroups)
.range(['#006287', '#F68721'])
var self = this;
datas.forEach(data => {
// console.log("data",data);
g.selectAll("mybar")
// Enter in data = loop group per group
.data(datas)
.enter()
.append("g")
.attr("class","bars")
.attr("transform", function (d) { return "translate(" + x(d.group) + ",0)"; })
.selectAll("rect")
.data(function (d) { return subgroups.map(function (key) { return { key: key,
value: d[key] }; }); })
.enter().append("rect")
.attr("x", function (d) { return xSubgroup(d.key); })
.attr("y", function (d) { return y(d.value); })
.attr("width", xSubgroup.bandwidth())
.attr("height", function (d) { return height - y(d.value); })
.attr("fill", function (d) { return color(d.key); })
.append("svg:title")
.text(function (d) {
return `${d['key']}:` + d.value;
})
//code i tried for text on top of each stack
g.selectAll(".text")
.data(data.stack)
.enter().append("text")
.attr("class", "barstext")
.attr("x", function (d) { console.log("d", d); return x(d.name); })
.attr("y", function (d) { return y(d.value); })
.text(function (d) { console.log("text", d); return (d.value); })
// // line chart
var averageline = d3.line()
.x(function (d, i) { return x(d['group']) + x.bandwidth() / 2; })
.y(function (d) { return y1(d['percentage']); })
.curve(d3.curveMonotoneX);
var path = g.append("path")
.attr("class", "line")
.style("fill", "none")
.style("stroke", "#58D68D")
.style("stroke-width", 2)
.attr("d", averageline(datas));
g.selectAll("myCircles")
.data(datas)
.enter()
.append("circle")
.attr("class", "dot")
.style("fill", "white")
.style("stroke", "#58D68D")
.style("stroke-width", 2)
.style('cursor', 'pointer')
.attr("cx", function (d, i) { return x(d['group']) + x.bandwidth() / 2; })
.attr("cy", function (d) { return y1(d['percentage']); })
.attr("r", 3)
.append("svg:title")
.text(function (d) {
return "Percentage: " + d.percentage;
})
})
}
dummy data
[
{
"group": "Digital Process Industries",
"Total Headcount": 12,
"Onboarded resource count": 1,
"percentage": 13,
"maxBarValue": 12,
"stack": [
{
"name": "Total Headcount",
"value": 12
},
{
"name": "Onboarded resource count",
"value": 1
}
]
},
{
"group": "Digital Discrete Industries",
"Total Headcount": 6,
"Onboarded resource count": 6,
"percentage": 33,
"maxBarValue": 6,
"stack": [
{
"name": "Total Headcount",
"value": 6
},
{
"name": "Onboarded resource count",
"value": 6
}
]
}]
You are pretty close with your current solution. There are two main things you need to do to get this working correctly:
If you are looping over your data already (datas.forEeach) there is no need to rebind to it in your databinding for the group offset. You should be binding to the individual data element instead (so bind to [data] instead).
Set the group you create off the data element to a variable and append both the rectangles for the bars and the text for the labels to that group rather than the svg. The reason for this is that it is already offset for the group (via the transform call) so you just have to worry about the subgroup x scale.
See this jsfiddle for a working version of your code. I added comments prepended with EDITED -- to all the lines I changed along with an explanation of what I did.
I am trying to draw a multi line chart with D3.js. The chart is drawn properly but the x-axis ticks are not aligned correctly with the data points circle.
Find the example code here: https://jsfiddle.net/gopal31795/qsLd7pc5/9/
I assume that there is some error in the code for creating the dots.
// Creating Dots on line
segment.selectAll("dot")
.data(function(d) {
return d.linedata;
})
.enter().append("circle")
.attr("r", 5)
.attr("cx", function(d) {
//return x(parseDate(new Date(d.mins))) + x.rangeBand() / 2;
return x(d.mins);
})
.attr("cy", function(d) {
return y(d.value);
})
.style("stroke", "white")
.style("fill", function(d) {
return color(this.parentNode.__data__.name);
})
.on("mouseenter", function(d) {
d3.select(this).transition().style("opacity", "0.25");
tooltip.html("<span style='color:" + color(this.parentNode.__data__.name) + ";'>" + this.parentNode.__data__.name + "</span>: " + (d.value + "s")).style("visibility", "visible").style("top", (event.pageY + 10) + "px").style("left", (event.pageX) + "px");
})
.on("mouseleave", function(d) {
d3.select(this).transition().style("opacity", "1");
tooltip.style("visibility", "hidden");
});
The problem in your code (which uses the old v3) is that you're using rangeRoundBands. That would be the correct choice if you had a bar chart, for instance.
However, since you're dealing with data points, you should use rangePoints or rangeRoundPoints, which:
Sets the output range from the specified continuous interval. The array interval contains two elements representing the minimum and maximum numeric value. This interval is subdivided into n evenly-spaced points, where n is the number of (unique) values in the input domain.
So, it should be:
var x = d3.scale.ordinal()
.rangeRoundPoints([0, width]);
Here is your code with that change:
var Data = [{
"name": "R",
"linedata": [{
"mins": 0,
"value": 1120
},
{
"mins": 2,
"value": 1040
},
{
"mins": 4,
"value": 1400
},
{
"mins": 6,
"value": 1500
}
]
},
{
"name": "E",
"linedata": [{
"mins": 0,
"value": 1220
},
{
"mins": 2,
"value": 1500
},
{
"mins": 4,
"value": 1610
},
{
"mins": 6,
"value": 1700
}
]
}
];
var margin = {
top: 20,
right: 90,
bottom: 35,
left: 90
},
width = $("#lineChart").width() - margin.left - margin.right,
height = $("#lineChart").width() * 0.3745 - margin.top - margin.bottom;
//var parseDate = d3.time.format("%d-%b");
var x = d3.scale.ordinal()
.rangeRoundPoints([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5);
var xData = Data[0].linedata.map(function(d) {
return d.mins;
});
var line = d3.svg.line()
.interpolate("linear")
.x(function(d) {
return x(d.mins);
})
.y(function(d) {
return y(d.value);
});
function transition(path) {
path.transition()
.duration(4000)
.attrTween("stroke-dasharray", tweenDash);
}
function tweenDash() {
var l = this.getTotalLength(),
i = d3.interpolateString("0," + l, l + "," + l);
return function(t) {
return i(t);
};
}
var svg = d3.select("#lineChart").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 + ")");
color.domain(Data.map(function(d) {
return d.name;
}));
x.domain(xData);
var valueMax = d3.max(Data, function(r) {
return d3.max(r.linedata, function(d) {
return d.value;
})
});
var valueMin = d3.min(Data, function(r) {
return d3.min(r.linedata, function(d) {
return d.value;
})
});
y.domain([valueMin, valueMax]);
//Drawing X Axis
svg.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("class", "xAxisText")
.attr("x", width)
.attr("dy", "-.41em")
.style("text-anchor", "end")
.style("fill", "white")
.text("Time(m)");
svg.append("g")
.attr("class", "y-axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.style("fill", "none")
.style("stroke", "white")
.style("stroke-width", 0.8)
.text("Duration(s)");
// Drawing Lines for each segments
var segment = svg.selectAll(".segment")
.data(Data)
.enter().append("g")
.attr("class", "segment");
segment.append("path")
.attr("class", "line")
.attr("id", function(d) {
return d.name;
})
.attr("visible", 1)
.call(transition)
//.delay(750)
//.duration(1000)
//.ease('linear')
.attr("d", function(d) {
return line(d.linedata);
})
.style("stroke", function(d) {
return color(d.name);
});
// Creating Dots on line
segment.selectAll("dot")
.data(function(d) {
return d.linedata;
})
.enter().append("circle")
.attr("r", 5)
.attr("cx", function(d) {
//return x(parseDate(new Date(d.mins))) + x.rangeBand() / 2;
return x(d.mins);
})
.attr("cy", function(d) {
return y(d.value);
})
.style("stroke", "white")
.style("fill", function(d) {
return color(this.parentNode.__data__.name);
})
.on("mouseenter", function(d) {
d3.select(this).transition().style("opacity", "0.25");
tooltip.html("<span style='color:" + color(this.parentNode.__data__.name) + ";'>" + this.parentNode.__data__.name + "</span>: " + (d.value + "s")).style("visibility", "visible").style("top", (event.pageY + 10) + "px").style("left", (event.pageX) + "px");
})
.on("mouseleave", function(d) {
d3.select(this).transition().style("opacity", "1");
tooltip.style("visibility", "hidden");
});
path {
fill: none;
stroke: black;
}
line {
stroke: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div class="card-body" id="lineChart">
</div>
Finally, as a tip: don't mix D3 and jQuery.
my code is working fine, but there is a issue with the first dot on the line. first dot always get the y=2 and x=1 position, but other dots are placed correctly. please help me to place the first dot in correct place.
graph:-
JSON data for the graph:-
var data = [{
"label": "Execution: 6 - defadmin#gmail.com",
"x": [1, 2, 3, 4, 5, 6],
"y": [2, 1, 1, 1, 1, 1],
"xAxisDisplayData": ["1", "2", "3", "4", "5", "6"]
}];
here is my code regarding the dot creation,
// Set the ranges
var x = d3.time.scale().range([0, innerwidth]);
var y = d3.scale.linear().range([innerheight, 0]);
// Scale the range of the data
x.domain(d3.extent(datasets[0]['x'], function (d, i) {
return datasets[0]['x'][i];
}));
y.domain([1, d3.max(datasets[0]['y'], function (d, i) {
return datasets[0]['y'][i];
})]);
// Add the scatterplot
svg.selectAll("dot")
.data(datasets[0]['x'])
.enter().append("circle")
.attr("r", 3.5)
.attr("cx", function (d, i) {
return x(datasets[0]['x'][i]);
})
.attr("cy", function (d, i) {
return y(datasets[0]['y'][i]);
});
UPDATE 1: full code
function createLineChart(data, number) {
// var data = [ { label: "Execution 1 - buddhika#gmail.com",
// x: [1,2,3,4,5,6],
// y: [2,1,1,1,1,1] }] ;
var widthForSVG;
var widthForChart;
if ((data[0]['x']).length < 13) {
widthForSVG = 1220;
widthForChart = 960;
} else {
widthForSVG = (((data[0]['x']).length - 12) * 80) + 1220;
widthForChart = (((data[0]['x']).length - 12) * 80) + 960;
}
var xy_chart = d3_xy_chart()
.width(widthForChart)
.height(500)
.xlabel("TCS")
.ylabel("STATUS");
// creating main svg
var svg = d3.select(".lineChartDiv" + number).append("svg")
.datum(data)
.call(xy_chart)
.attr("class", "lineChart" + number)
.attr('width', widthForSVG);
function d3_xy_chart() {
//1220px for 12 steps in svg
var width = widthForChart,
height = 480,
xlabel = "X Axis Label",
ylabel = "Y Axis Label";
function chart(selection, svg) {
var numberNUmber = 0;
selection.each(function (datasets) {
//
// Create the plot.
//
var margin = {top: 20, right: 80, bottom: 30, left: 50},
innerwidth = width - margin.left - margin.right,
innerheight = height - margin.top - margin.bottom;
// Set the ranges
var x_scale = d3.scale.linear()
.range([0, innerwidth])
.domain([d3.min(datasets, function (d) {
return d3.min(d.x);
}),
d3.max(datasets, function (d) {
return d3.max(d.x);
})]);
var y_scale = d3.scale.linear()
.range([innerheight, 0])
.domain([d3.min(datasets, function (d) {
return 1;
}),
d3.max(datasets, function (d) {
// d3.max(d.y)
return 3;
})]);
var color_scale = d3.scale.category10()
.domain(d3.range(datasets.length));
var x_axis = d3.svg.axis()
.scale(x_scale)
.orient("bottom")
.tickFormat(function (d, i) {
if (d % 1 == 0) {
return parseInt(datasets[0]['xAxisDisplayData'][i])
} else {
return " "
}
})
.ticks(d3.max(datasets, function (d) {
return d3.max(d.x);
}));
var y_axis = d3.svg.axis()
.scale(y_scale)
.orient("left")
.ticks(d3.max(datasets, function (d) {
return d3.max(d.y);
}))
.tickFormat(function (d, i) {
if (d == "1") {
return "NOT EXECUTED"
} else if (d == "2") {
return "FAILED"
} else if (d == "3") {
return "PASSED"
} else {
return " "
}
});
var x_grid = d3.svg.axis()
.scale(x_scale)
.orient("bottom")
.tickSize(-innerheight)
.ticks(d3.max(datasets, function (d) {
// d3.max(d.y)
return d3.max(d.x);
}))
.tickFormat("");
var y_grid = d3.svg.axis()
.scale(y_scale)
.orient("left")
.tickSize(-innerwidth)
.tickFormat("")
.ticks(d3.max(datasets, function (d) {
return d3.max(d.y);
}));
var draw_line = d3.svg.line()
.interpolate("linear")
.x(function (d) {
return x_scale(d[0]);
})
.y(function (d) {
return y_scale(d[1]);
});
var svg = d3.select(this)
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + 90 + "," + margin.top + ")");
svg.append("g")
.attr("class", "x grid")
.attr("transform", "translate(0," + innerheight + ")")
.call(x_grid);
svg.append("g")
.attr("class", "y grid")
.call(y_grid);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + innerheight + ")")
.call(x_axis)
.append("text")
.attr("dy", "-.71em")
.attr("x", innerwidth)
.style("text-anchor", "end")
.text(xlabel);
svg.append("g")
.attr("class", "y axis")
.call(y_axis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.style("text-anchor", "end")
.text(ylabel);
var data_lines = svg.selectAll(".d3_xy_chart_line")
.data(datasets.map(function (d) {
return d3.zip(d.x, d.y);
}))
.enter().append("g")
.attr("class", "d3_xy_chart_line");
data_lines.append("path")
.attr("class", "line")
.attr("d", function (d) {
return draw_line(d);
})
.attr("stroke", function (_, i) {
return color_scale(i);
});
data_lines.append("text")
.datum(function (d, i) {
return {name: datasets[i].label, final: d[d.length - 1]};
})
.attr("transform", function (d) {
return ( "translate(" + x_scale(d.final[0]) + "," +
y_scale(d.final[1]) + ")" );
})
.attr("x", 3)
.attr("dy", ".35em")
.attr("fill", function (_, i) {
return color_scale(i);
})
.text(function (d) {
return d.name;
});
// Set the ranges
var x = d3.time.scale().range([0, innerwidth]);
var y = d3.scale.linear().range([innerheight, 0]);
// Scale the range of the data
x.domain(d3.extent(datasets[0]['x']));
y.domain([1, d3.max(datasets[0]['y'])]);
svg.selectAll("dot")
.data(d3.zip(datasets[0].x, datasets[0].y))
.enter().append("circle")
.attr("r", 3.5)
.attr("cx", function (d) {
return x(d[0]);
})
.attr("cy", function (d) {
return y(d[1]);
});
});
}
chart.width = function (value) {
if (!arguments.length) return width;
width = value;
return chart;
};
chart.height = function (value) {
if (!arguments.length) return height;
height = value;
return chart;
};
chart.xlabel = function (value) {
if (!arguments.length) return xlabel;
xlabel = value;
return chart;
};
chart.ylabel = function (value) {
if (!arguments.length) return ylabel;
ylabel = value;
return chart;
};
return chart;
}
}
UPDATE 2:
html view of created circles-(check the first circle, it always has cx=0 and cy=0 cordinates.other circles are fine)
UPDATE 3: feddle
feddle
Your use of d3.extent() as well as d3.max() is flawed. The functions provided to these methods are just accessors; there is no parameter i for an actual iteration. They are meant as a means to actually access the relevant data of the array, which was passed in as the first parameter. Because you are already passing in the flat data arrays, both accessor function can be reduced to function (d) { return d; }. These might further be omitted because this is the default behavior. Your domain setup thus becomes:
// Scale the range of the data
x.domain(d3.extent(datasets[0]['x']));
y.domain([1, d3.max(datasets[0]['y'])]);
Personally, I would also rewrite your data binding logic to improve readability:
// Add the scatterplot
svg.selectAll("dot")
.data(d3.zip(datasets[0].x, datasets[0].y))
.enter().append("circle")
.attr("r", 3.5)
.attr("cx", function (d) {
return x(d[0]);
})
.attr("cy", function (d) {
return y(d[1]);
});
Instead of doing the cumbersome deep access into the datasets array each time you need those values, this uses d3.zip() to build a new array of arrays containing the points' coordinates, which is then bound to the selection. As you can see, this leaves you with clean code for setting your cx and cy attribute values.
Besides these technical shortcomings there is a logical glitch in setting up your y scale's domain—as indicated by Andrew's comment—, where you are doing
y.domain([1, d3.max(datasets[0]['y'])]);
In the dataset you provided the maximum value for y is 2, though. This way your domain will be set to [1, 2] leaving out the 3. With this domain the point will consequently be drawn at the origin. Because your y values are categories, this is obviously not what you want. To always draw the full range of categories, you could use static values to set up your scale's domain:
y.domain([1, 3]);
You are already doing it this way—in a rather awkward way, though—for your other scale y_scale, which is why the line is drawn correctly.
Of course, you could also decide to only draw the categories contained in the dataset, in which case you would keep the d3.max() in the domain, but you will then have to also do the same for your y_scale's domain.
Have a look at the following snippet for a working example. It contains the code from your JSFiddle having just the one line changed, where the y scale's domain is set up.
var data = [{
"label": "Execution: 6 - defadmin#gmail.com",
"x": [1, 2, 3, 4, 5, 6],
"y": [2, 1, 1, 1, 1, 1],
"xAxisDisplayData": ["1", "2", "3", "4", "5", "6"]
}];
var number = 1;
var widthForSVG;
var widthForChart;
if ((data[0]['x']).length < 13) {
widthForSVG = 1220;
widthForChart = 960;
} else {
widthForSVG = (((data[0]['x']).length - 12) * 80) + 1220;
widthForChart = (((data[0]['x']).length - 12) * 80) + 960;
}
var xy_chart = d3_xy_chart()
.width(widthForChart)
.height(500)
.xlabel("TCS")
.ylabel("STATUS");
// creating main svg
var svg = d3.select(".lineChartDiv1").append("svg")
.datum(data)
.call(xy_chart)
.attr("class", "lineChartDiv1")
.attr('width', widthForSVG);
function d3_xy_chart() {
var width = widthForChart,
height = 480,
xlabel = "X Axis Label",
ylabel = "Y Axis Label";
function chart(selection, svg) {
var numberNUmber = 0;
selection.each(function(datasets) {
//
// Create the plot.
//
var margin = {
top: 20,
right: 80,
bottom: 30,
left: 50
},
innerwidth = width - margin.left - margin.right,
innerheight = height - margin.top - margin.bottom;
// Set the ranges
var x_scale = d3.scale.linear()
.range([0, innerwidth])
.domain([d3.min(datasets, function(d) {
return d3.min(d.x);
}),
d3.max(datasets, function(d) {
return d3.max(d.x);
})
]);
var y_scale = d3.scale.linear()
.range([innerheight, 0])
.domain([d3.min(datasets, function(d) {
return 1;
}),
d3.max(datasets, function(d) {
// d3.max(d.y)
return 3;
})
]);
var color_scale = d3.scale.category10()
.domain(d3.range(datasets.length));
var x_axis = d3.svg.axis()
.scale(x_scale)
.orient("bottom")
.tickFormat(function(d, i) {
if (d % 1 == 0) {
return parseInt(datasets[0]['xAxisDisplayData'][i])
} else {
return " "
}
})
.ticks(d3.max(datasets, function(d) {
return d3.max(d.x);
}));
var y_axis = d3.svg.axis()
.scale(y_scale)
.orient("left")
.ticks(d3.max(datasets, function(d) {
return d3.max(d.y);
}))
.tickFormat(function(d, i) {
if (d == "1") {
return "NOT EXECUTED"
} else if (d == "2") {
return "FAILED"
} else if (d == "3") {
return "PASSED"
} else {
return " "
}
});
var x_grid = d3.svg.axis()
.scale(x_scale)
.orient("bottom")
.tickSize(-innerheight)
.ticks(d3.max(datasets, function(d) {
// d3.max(d.y)
return d3.max(d.x);
}))
.tickFormat("");
var y_grid = d3.svg.axis()
.scale(y_scale)
.orient("left")
.tickSize(-innerwidth)
.tickFormat("")
.ticks(d3.max(datasets, function(d) {
return d3.max(d.y);
}));
var draw_line = d3.svg.line()
.interpolate("linear")
.x(function(d) {
return x_scale(d[0]);
})
.y(function(d) {
return y_scale(d[1]);
});
var svg = d3.select(this)
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + 90 + "," + margin.top + ")");
svg.append("g")
.attr("class", "x grid")
.attr("transform", "translate(0," + innerheight + ")")
.call(x_grid);
svg.append("g")
.attr("class", "y grid")
.call(y_grid);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + innerheight + ")")
.call(x_axis)
.append("text")
.attr("dy", "-.71em")
.attr("x", innerwidth)
.style("text-anchor", "end")
.text(xlabel);
svg.append("g")
.attr("class", "y axis")
.call(y_axis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.style("text-anchor", "end")
.text(ylabel);
var data_lines = svg.selectAll(".d3_xy_chart_line")
.data(datasets.map(function(d) {
return d3.zip(d.x, d.y);
}))
.enter().append("g")
.attr("class", "d3_xy_chart_line");
data_lines.append("path")
.attr("class", "line")
.attr("d", function(d) {
return draw_line(d);
})
.attr("stroke", function(_, i) {
return color_scale(i);
});
data_lines.append("text")
.datum(function(d, i) {
return {
name: datasets[i].label,
final: d[d.length - 1]
};
})
.attr("transform", function(d) {
return ("translate(" + x_scale(d.final[0]) + "," +
y_scale(d.final[1]) + ")");
})
.attr("x", 3)
.attr("dy", ".35em")
.attr("fill", function(_, i) {
return color_scale(i);
})
.text(function(d) {
return d.name;
});
// Set the ranges
var x = d3.time.scale().range([0, innerwidth]);
var y = d3.scale.linear().range([innerheight, 0]);
// Scale the range of the data
x.domain(d3.extent(datasets[0]['x']));
y.domain([1, 3]);
// console.log(JSON.stringify(d3.extent(datasets[0]['x'])))
// console.log(JSON.stringify(d3.max(datasets[0]['y'])))
// Add the scatterplot
svg.selectAll("dot")
.data(d3.zip(datasets[0].x, datasets[0].y))
.enter().append("circle")
.attr("class", datasets[0]['label'])
.attr("r", 3.5)
.attr("cx", function(d) {
// console.log(JSON.stringify(d[0])+" XXXXXXXXXXx ")
return x(d[0]);
})
.attr("cy", function(d) {
//console.log(JSON.stringify(d[1])+" YYYYYYYyy ")
return y(d[1]);
});
});
}
chart.width = function(value) {
if (!arguments.length) return width;
width = value;
return chart;
};
chart.height = function(value) {
if (!arguments.length) return height;
height = value;
return chart;
};
chart.xlabel = function(value) {
if (!arguments.length) return xlabel;
xlabel = value;
return chart;
};
chart.ylabel = function(value) {
if (!arguments.length) return ylabel;
ylabel = value;
return chart;
};
return chart;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.grid path,
.grid line {
fill: none;
stroke: rgba(0, 0, 0, 0.25);
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.line {
fill: none;
stroke-width: 2.5px;
}
svg {
font: 10px sans-serif;
}
.area {
fill: lightgray;
clip-path: url(#clip);
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.brush .extent {
stroke: #fff;
fill-opacity: .125;
shape-rendering: crispEdges;
clip-path: url(#clip);
}
rect.pane {
cursor: move;
fill: none;
pointer-events: all;
}
<script src="https://d3js.org/d3.v3.js"></script>
<div class="row">
<div class="col-sm-12">
<div class="lineChartDiv1" style=" overflow-x: scroll">
</div>
</div>
</div>
Try this,
if(data[0]['y'][1]==1){
document.getElementsByClassName(data[0]['label'])[0].setAttribute('cx','0');
document.getElementsByClassName(data[0]['label'])[0].setAttribute('cy','225');
}else if(data[0]['y'][1]==2){
document.getElementsByClassName(data[0]['label'])[0].setAttribute('cx','0');
document.getElementsByClassName(data[0]['label'])[0].setAttribute('cy','450');
}else{
document.getElementsByClassName(data[0]['label'])[0].setAttribute('cx','0');
document.getElementsByClassName(data[0]['label'])[0].setAttribute('cy','0');
}
Hey I (Buddhika) improved your code below way, other wise it is useless :
var element = document.getElementsByClassName(data[0]['label']);
for (var i =0; i<element.length; i++){
if(data[0]['y'][0]==1 & data[0]['x'][i]==0){
document.getElementsByClassName(data[0]['label'])[i].setAttribute('cy','450');
}else if(data[0]['y'][0]==2 & data[0]['x'][i]==0){
document.getElementsByClassName(data[0]['label'])[i].setAttribute('cy','225');
}else if(data[0]['y'][0]==3 & data[0]['x'][i]==0){
document.getElementsByClassName(data[0]['label'])[i].setAttribute('cy','0');
}else if(data[0]['y'][i]==1){
document.getElementsByClassName(data[0]['label'])[i].setAttribute('cy','450');
}else if(data[0]['y'][i]==2){
document.getElementsByClassName(data[0]['label'])[i].setAttribute('cy','225');
}else if(data[0]['y'][i]==3){
document.getElementsByClassName(data[0]['label'])[i].setAttribute('cy','0');
}
}
I have a d3 line graph which works perfectly. It maps the number of telephone calls and emails received in the current month or current year. However, the only issue is that if phone calls were only received on one day of the month, then only a dot appears at that value. What i would like to happen is that for every day there is not a value then the value is treated as 0. I have had a look around google but i can only find examples of going the other way. Is anybody able to help?
My code as it stands is:
function buildCommunicationLineChart(data, placeholder, callback, type) {
var margin = { top: 20, right: 30, bottom: 40, left: 50 },
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
tooltipTextColour = "white";
var color = ["#FF9797", "#86BCFF", "#33FDC0", "#EFA9FE", "#7BCAE1", "#8C8CFF", "#80B584", "#C88E8E", "#DD597D", "#D8F0F8", "#DD597D", "#D6C485", "#990099", "#5B5BFF", "#1FCB4A", "#000000", "#00BFFF", "#BE81F7", "#BDBDBD", "#F79F81"];
if (type == "month") {
var x = d3.scale.linear()
.domain([1, 31])
.range([0, width]);
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function (d) {
return "<strong>Value:</strong> <span style='color:" + tooltipTextColour + "'>" + d.Value + "</span><br /><strong>Day of Month:</strong><span style='color:white'>" + d.xValue + "</span>";
});
}
else if (type == "year")
{
var x = d3.scale.linear()
.domain([1, 12])
.range([0, width]);
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function (d) {
return "<strong>Value:</strong> <span style='color:" + tooltipTextColour + "'>" + d.Value + "</span><br /><strong>Month of Year:</strong><span style='color:white'>" + d.xValue + "</span>";
});
}
var y = d3.scale.linear()
.domain([0, 60])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.tickSize(-height)
.tickPadding(10)
.tickSubdivide(true)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.tickPadding(10)
.tickSize(-width)
.tickSubdivide(true)
.orient("left");
var line = d3.svg.line()
.x(function (d) { var xTest = x(d.xValue); return x(d.xValue); })
.y(function (d) { var yTest = y(d.Value); return y(d.Value); });
var svg = placeholder.append("svg")
.attr("width", width + margin.left + margin.right + 50)
.attr("height", height + margin.top + margin.bottom)
.attr("class", "chart")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
if (type == "year") {
svg.append("g")
.attr("class", "x axis")
.append("text")
.attr("class", "axis-label")
.attr("transform", "none")
.attr("y", (-margin.left) + 530)
.attr("x", -height + 860)
.text('Month');
}
else if (type == "month") {
svg.append("g")
.attr("class", "x axis")
.append("text")
.attr("class", "axis-label")
.attr("transform", "none")
.attr("y", (-margin.left) + 525)
.attr("x", -height + 860)
.text('Day');
}
var methods = d3.entries(data);
y.domain([
d3.min(methods, function (c) { return d3.min(c.value.DataPoints, function (v) { return v.Value; }); }) -1,
d3.max(methods, function (c) { return d3.max(c.value.DataPoints, function (v) { return v.Value; }); }) +1
]);
svg.call(tip);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.attr("class", "y axis")
.append("text")
.attr("class", "axis-label")
.attr("transform", "rotate(-90)")
.attr("y", (-margin.left) + 15)
.attr("x", -height / 2)
.text('Communications');
var method = svg.selectAll('.method')
.data(methods)
.enter().append('g')
.attr('class', 'method')
.style('fill',function(d,i){
return color[i];
})
.style('stroke', function (d, i) {
return color[i];
});
var m = method.append('path')
.attr('class', function (d, i) { return 'line line-' + i; })
.attr('d', function (d) { return line(d.value.DataPoints); });
method.selectAll('circle')
.data(function (d) { return d.value.DataPoints; })
.enter().append("circle")
.attr('class', function (d, i) { return 'circle circle-' + i; })
.attr("cx", function (dd) { return x(dd.xValue); })
.attr("cy", function (dd) { return y(dd.Value); })
.attr("r", 3.5)
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
method.append('text')
.datum(function (d) { return { commType: d.value.Type, value: d.value.DataPoints[d.value.DataPoints.length - 1] }; })
.attr("transform", function (d) { return "translate(" + x(d.value.xValue) + "," + y(d.value.Value) + ")"; })
.attr('x', 5)
.attr('class',function(d,i){return 'text text-'+i;})
.attr('dy', '.15em')
.style('stroke','none')
.text(function (d) { return d.commType; });
if (callback) {
callback();
}
}
Many Thanks :)
EDIT
xVal is the day number of month number:
[{
"Type": "Email",
"DataPoints": [{
"xValue": 1,
"Value": 17
},
{
"xValue": 2,
"Value": 59
}]
},
{
"Type": "Phone",
"DataPoints": [{
"xValue": 1,
"Value": 1
}]
}]
I will assume some points:
You are receiving from your data source year's array of month's arrays (if you are receiving month by month you will have to make a Get request for each month, it's pretty easy, but I hope you have a route giving you data on year, would not be cool to do 12 http get for this result) data like:
[
{
month: "January",
data: [{
"Type": "Email",
"DataPoints":
[{
"xValue": 1,
"Value": 17
},
{
"xValue": 2,
"Value": 59
}]
},
{
"Type": "Phone",
"DataPoints": [{
"xValue": 1,
"Value": 1
}]
}]
}
},{
month: "February",
data: [...]
},
...
]
I will do that client side in Js:
Assuming each month is 31 days, i'm too lazy to think about doing it exactly but it shoudn't be that difficult, and xValue is the day of the month.
function completeMonths(data){
_.each(data, function(month){
_.each(month.data, function(typeSource){
for(var i = 1; i < 32; i++){
var dayOnList = _.some(typeSource.DataPoints, function(day){
return day.xValue == i;
});
if(!dayOnList)
typeSource.DataPoints.push({xValue: i, Value: 0});
}
typeSource.DataPoints = _.sortBy(typeSource.DataPoints, function(ite){
return ite.xValue;
});
});
});
return data;
}
Probably not the more optimized code, but I've tried it and it's working.
Actually I think it would be better to map the month's days present, and reject them from and 1 to 31 array.
Btw your Json isn't well formatted, you're missing a closing "]".
With JSON structured like:
[{
"Type": "Email",
"DataPoints": [{
"xValue": 1,
"Value": 17
},
{
"xValue": 2,
"Value": 59
}]
},
{
"Type": "Phone",
"DataPoints": [{
"xValue": 1,
"Value": 1
}]
}]
And assuming your DataPoints array is sorted by xValue and 31 days in the month:
var maxDaysInMonth = 31;
// for each dataset
dataset.forEach(function(d){
// loop our month
for (var i = 1; i <= maxDaysInMonth; i++){
// if there's no xValue at that location
if (!d.DataPoints.some(function(v){ return (v.xValue === i) })){
// add a zero in place
d.values.splice((i - 1), 0, {xValue: i, Value: 0});
}
}
});
I found a reference to the issue I am facing in this link
'nemesv' has provided with a solution to the actual issue.
var myApp = angular.module('app', []);
myApp.directive("lineChart", function() {
return {
restrict: 'E',
scope: {
data: '=',
id: '#'
},
link: function (scope, element, attrs) {
scope.$watch( 'data', function ( data ) {
d3.select("#"+attrs.id).select("svg").remove();
if (data) {
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = element[ 0 ].parentElement.offsetWidth - margin.left - margin.right,
height = element[ 0 ].parentElement.offsetHeight - margin.top - margin.bottom;
var parseDate = d3.time.format("%d-%b-%y").parse;
var bisectDate = d3.bisector(function(d) { return d[0]; }).left;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.innerTickSize(-height)
.ticks(4)
.outerTickSize(0)
.tickPadding(5)
.tickFormat(function(d) { return d3.time.format('%d/%m %H:%M')(new Date(d)); });
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.innerTickSize(-width)
.outerTickSize(0)
.tickPadding(10);
var line = d3.svg.line()
.x(function(d) { return x(d[0]); })
.y(function(d) { return y(d[1]); });
var svg = d3.select(element[0]).append("svg")
.attr("width", '100%')
.attr("height", '100%')
.attr('viewBox','0 0 '+ element[ 0 ].parentElement.offsetWidth +' '+ element[ 0 ].parentElement.offsetHeight )
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var minX = d3.min(data, function (item) { return d3.min(item.values, function (d) { return d[0]; }); });
var maxX = d3.max(data, function (item) { return d3.max(item.values, function (d) { return d[0]; }); });
var minY = d3.min(data, function (item) { return d3.min(item.values, function (d) { return d[1]; }); });
var maxY = d3.max(data, function (item) { return d3.max(item.values, function (d) { return d[1]; }); });
x.domain([minX, maxX]);
y.domain([0, maxY]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
var domaine = svg.selectAll(".domaine")
.data(data)
.enter().append("g")
.attr("class", "domaine");
domaine.append("path")
.attr("class", "line")
.attr("d", function (d) {
return line(d.values);
})
.style("stroke", function (d) {
return d.color;
});
var focus = svg.append("g")
.attr("class", "focus")
.style("display", "none");
for(var i=0;i<data.length;i++){
focus.append("g")
.attr("class", "focus"+i)
.append("circle")
.attr("r", 4.5);
svg.select(".focus"+i)
.append("text")
.attr("x", 9)
.attr("dy", ".35em");
}
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() { focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", mousemove);
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0]);
var series = data.map(function(e) {
var i = bisectDate(e.values, x0, 1),
d0 = e.values[i - 1],
d1 = e.values[i];
return x0 - d0[0] > d1[0] - x0 ? d1 : d0;
});
for(var i=0; i<series.length;i++){
var selectedFocus = svg.selectAll(".focus"+i);
selectedFocus.attr("transform", "translate(" + x(series[i][0]) + "," + y(series[i][1]) + ")");
selectedFocus.select("text").text(series[i][1]);
}
}
}
});
}
};
});
function MainCtrl($scope) {
$scope.lineData = [{"key": "users","color": "#16a085","values": [[1413814800000,4034.418],[1413815400000,5604.155000000001],[1413816000000,6343.079],[1413816600000,7308.226],[1413817200000,9841.185],[1413817800000,6571.891],[1413818400000,4660.6005000000005],[1413819000000,4555.4795],[1413819600000,5963.723],[1413820200000,9179.9595]]},{"key": "users 2","color": "#d95600","values": [[1413814800000,3168.183],[1413815400000,1530.8435],[1413816000000,2416.071],[1413816600000,1274.309],[1413817200000,1105.0445],[1413817800000,2086.0299999999997],[1413818400000,712.642],[1413819000000,1676.725],[1413819600000,3721.46],[1413820200000,2887.7975]]}];
}
Presently, if I hover over one line, I can see another dot hovering over the other line as well - i.e. mouseover is working simultaneously on both the lines. Could this mouseover/tooltip be done separately on each line i.e. when I hover over one line, focus circle will appear for only that line?