The lines on my chart are displaying with a weird black filled-in shape instead of an actual line. Any idea what i'm doing wrong? The data is parsed in from a csv. It's a multiline chart where each series is a column in the csv(columns are: date, cat1, cat2, cat3, cat4).
Aside - I got this to work in a different fashion by defining each valueline and appending to the path. But I ran into issues with scaling the axis. I eventually need to have four plots on my html page, each with different axis scales (for example log scale on the y axis). Therefore I'll need y = d3.scaleLog().domain(my domain).range(range) and then in d3.line.y return y(my value).
var margin = {top: 20, right: 20, bottom: 30, left: 50},
w = 960 - margin.left - margin.right,
h = 500 - margin.top - margin.bottom;
var padding =20;
var xScale = d3.scaleTime()
.domain([d3.min(dataset,function (d) { return d.date }),d3.max(dataset,function (d) { return d.date }) ]).range([padding, w - padding * 2])
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset,function (d) { return d.cat1 }) ]).range([h- padding, padding])
var xAxis = d3.axisBottom().scale(xScale);
var yAxis = d3.axisLeft().scale(yScale);
var svg1 = 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 line = d3.line()
.x(function(d) { return xScale(d['date']); })
.y(function(d) { return yScale(d['cat1']); });
var line2 = d3.line()
.x(function(d) { return xScale(d['date']); })
.y(function(d) { return yScale(d['cat2']); });
var line3 = d3.line()
.x(function(d) { return xScale(d['date']); })
.y(function(d) { return yScale(d['cat3']); });
var line4 = d3.line()
.x(function(d) { return xScale(d['date']); })
.y(function(d) { return yScale(d['cat4']); });
var path = svg1.append('path').attr('d', line(dataset));
var path2 = svg1.append('path').attr('d', line2(dataset));
var path3 = svg1.append('path').attr('d', line3(dataset));
var path4 = svg1.append('path').attr('d', line4(dataset));
//draw points
var selectCircle = svg1.selectAll(".circle")
.data(dataset)
selectCircle.enter().append("circle")
.attr("class", "circle")
.attr("r", 10)
.attr("cx", function(d) {
return xScale(d.date)
})
.attr("cy", function(d) {
return yScale(d.cat1)
})
.attr("fill", "#FFC300")
selectCircle.enter().append("circle")
.attr("class", "circle")
.attr("r", 10)
.attr("cx", function(d) {
return xScale(d.date)
})
.attr("cy", function(d) {
return yScale(d.cat2)
})
.attr("fill", "#FF5733")
selectCircle.enter().append("circle")
.attr("class", "circle")
.attr("r", 10)
.attr("cx", function(d) {
return xScale(d.date)
})
.attr("cy", function(d) {
return yScale(d.cat3)
})
.attr("fill", "#C70039")
selectCircle.enter().append("circle")
.attr("class", "circle")
.attr("r", 10)
.attr("cx", function(d) {
return xScale(d.date)
})
.attr("cy", function(d) {
return yScale(d.cat4)
})
.attr("fill", "#900C3F")
svg1.append("g").attr("class", "axis").attr("transform", "translate(0," + (h - padding) + ")").call(xAxis);
//draw Y axis
svg1.append("g").attr("class", "axis").attr("transform", "translate(" + padding + ",0)").call(yAxis);
// add label
svg1.append("text").attr("x", (w/2)).attr("y", h+30).attr("text-anchor", "middle").text("Year");
svg1.append("text").attr("x", padding).attr("y", padding-20).attr("text-anchor", "middle").text("# of Events");
//add title
svg1.append("text").attr("x", (w/2)).attr("y", padding).attr("text-anchor", "middle").text("Events per Year by Category");
Related
I am having trouble creating a chart with a simple line.
I'll put here my code and an image of how the line is getting off axis Y and X. I really have no idea why this is happening.
Chart:
HTML:
<div id="myChart"></div>
JavaScript:
Function to adjust my json only with the data I need:
var reduceVendedores = vendedores.reduce(function (allSales, sales) {
if (allSales.some(function (e) {
return e.vendnm === sales.vendnm;
})) {
allSales.filter(function (e) {
return e.vendnm === sales.vendnm
})[0].Vendas_Ano += sales.Vendas_Ano;
allSales.filter(function (e) {
return e.vendnm === sales.vendnm
})[0].Vendas_Ant += sales.Vendas_Ant
} else {
allSales.push({
vendnm: sales.vendnm,
Vendas_Ano: sales.Vendas_Ano,
Vendas_Ant: sales.Vendas_Ant
})
}
return allSales;
}, []);
Defining X and Y Axes of the Chart:
var margin = { top: 30, right: 30, bottom: 70, left: 60 },
width = 600 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
function getMax() {
return reduceVendedores.map(d => d.Vendas_Ano)
}
var svg = d3.select("#myChart")
.append("svg")
.attr("width", width)
.attr("height", height);
var xscale = d3.scaleBand()
.range([0, width - 100])
.domain(reduceVendedores.map(function (d) { return d.vendnm; }))
var yscale = d3.scaleLinear()
.domain([0, d3.max(getMax()) + 30000])
.range([height / 2, 0]);
var x_axis = d3.axisBottom().scale(xscale);
var y_axis = d3.axisLeft().scale(yscale);
svg.append("g")
.attr("transform", "translate(50, 10)")
.call(y_axis);
var xAxisTranslate = height / 2 + 10;
svg.append("g")
.attr("transform", "translate(50, " + xAxisTranslate + ")")
.call(d3.axisBottom(xscale))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end");
Defining the chart line:
svg.append("path")
.datum(reduceVendedores)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function (d) { return xscale(d.vendnm); })
.y(function (d) { return yscale(d.Vendas_Ano) })
)
You're not adjusting for the margins you gave to your axis with:
.attr("transform", "translate(50, 10)")
Try:
svg.append('g')
.attr('transform','translate(50,0)')
.append("path")
.datum(reduceVendedores)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function (d) { return xscale(d.vendnm); })
.y(function (d) { return yscale(d.Vendas_Ano) })
)
Typically you'd set your margin in svg, like:
var svg = d3.select("#myChart")
.append("svg")
.attr("width", width)
.attr("height", height)
.append('g')
.attr('transform','translate(' + margin.left + ',' + margin.top + ')')
But doing so know would ruin the alignment of your axis.
I'm building three line graphs in d3.js and I would like that when the viewer hovers over a section of the line graph, they are able to see the country, date and incidents. Currently all that is being returned is an undefined. any help would be immensely appreciated.
Here is my html:
<div id="callOut">
<div id="divCountry"></div>
<div id="divDate"></div>
<div id="divIncidents"></div>
</div>
Here are my helper functions:
function circleHover(chosen) {
if (modus == "incidents") {
d3.select("#callOut")
.style("top", "570px")
.style("left", "30px");
d3.select("#divCountry").html('Country: ' + chosen.country);
d3.select("#divDate").html('Date: ' + chosen.date);
d3.select("#divIncidents").html('Incidents: ' + chosen.incidents);
d3.select("#callOut")
.style("visibility","visible");
}
};
function showOne(d) {
var margin = {top: 80, right: 80, bottom: 80, left: 80},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var svg = d3.select("#svg").append("svg")
.attr("width", width)
.attr("height", height);
var incidentsData = svg.append("g");
incidentsData.selectAll(".incidentsData")
.data(incidentData)
.enter()
.append('incidentsData')
.attr('class', 'incidentsData');
var chosen = d;
var setOpacity = 0;
if (modus == "incidents") {
circleHover(chosen);
setOpacity = 0.1;
incidentsData.selectAll(".incidentsData")
.filter(function (d) { return eval("d.country") !=
eval("chosen.country");})
.style("fill-opacity", setOpacity);
}
}
};
Here is a snapshot of my incidentsData:
var incidentData =
[
{
"country": "Israel",
"date": 1971,
"incidents": 1,
"": ""
},
{
"country": "Israel",
"date": 1972,
"incidents": 15,
"": ""
},
Here is my totalIncidentsLineGraph:
function totalIncidentsLineGraph() {
modus = "incidents";
var margin = {top: 80, right: 80, bottom: 80, left: 80},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var svg = d3.select("#svg").append("svg")
.attr("width", width)
.attr("height", height);
var incidentsData = svg.append("g");
incidentsData.selectAll(".incidentsData")
.data(incidentData)
.enter()
.append('incidentsData')
.attr('class', 'incidentsData')
.attr('id', function(d) { return d.country, d.date,
d.incidents});
var parse = d3.time.format("%Y").parse;
var x = d3.time.scale().range([0, width]),
y = d3.scale.linear().range([height, 0]),
xAxis = d3.svg.axis().scale(x).tickSize(-height).tickSubdivide(true),
yAxis = d3.svg.axis().scale(y).ticks(4).orient("right");
var area = d3.svg.area()
.interpolate("monotone")
.x(function(d) { return x(d.date); })
.y0(height)
.y1(function(d) { return y(d.incidents); });
var line = d3.svg.line()
.interpolate("monotone")
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.incidents); });
function type(d) {
d.country = d.country;
d.date = parse(d.date);
d.incidents = +d.incidents
return d;
};
var data = d3.csv("static/data/israel/incidents_linechart.csv", type,
function(error, data) {
console.log(data);
var israel = data.filter(function(d) {
return d.country == "Israel";
});
var unitedStates = data.filter(function(d) {
return d.country == "United States";
});
var iran = data.filter(function(d) {
return d.country == "Iran";
});
x.domain([unitedStates[0].date, unitedStates[unitedStates.length -
1].date]);
y.domain([0, d3.max(egypt, function(d) { return d.incidents;
})]).nice();
var svg = d3.select("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top +
")")
svg.append("clipPath")
.attr("id", "clip")
.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")
.attr("transform", "translate(" + width + ",0)")
.call(yAxis);
svg.append("text")
.attr("id", "titleLine")
.attr("y", -50)
.attr("x", 250)
.attr("dy", ".35em")
.style("font-size", "12px")
.text("Total Incidents Amongst Countries: 1970 - 2017");
var colors = d3.scale.category10();
svg.selectAll('.line')
.data([israel, unitedStates, iran, saudiArabia, turkey, egypt, syria,
lebanon, jordan, palestine]) /// can add however many i want here
.enter()
.append('path')
.attr('class', 'line')
.style('stroke', function(d) {
return colors(Math.random() * 50);
})
.attr('clip-path', 'url(#clip)')
.attr('d', function(d) {
return line(d);
})
.on("mouseover", showOne)
.on("mouseout", showAll);
Right now I have a bubble chart that plots bubbles at different point with different radius. I want to be able to click a button and the re-populate the chart with new random bubbles.
I created a function update(), where I have tried putting d3.selectall("circles").remove() before appending new circles to it. But it only removes the circles after I click it.
<body>
<button class="btn" onclick="update()">Update</button>
<!-- load the d3.js library -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var r = d3.scaleLinear().range([10, 50]);
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 + ")");
var getdata = function() {
var dataset = []
for (var i = 0; i < 20; i++) {
var x = d3.randomUniform(-50,50)();
var y = d3.randomUniform(-50,50)();
var r = d3.randomUniform(-50,50)();
dataset.push({"x": x, "y": y,"r":r});
}
return dataset
}
var data = getdata()
data.forEach(function(d) {
d.x = +d.x;
d.y = +d.y;
d.r = +d.r;
});
x.domain([-50, 50]);
y.domain([-50, 50]);
r.domain([-50, 50]);
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", function(d) { return r(d.r); })
.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return y(d.y); })
.style("stroke", "black")
.style("fill", "none")
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
svg.append("g")
.call(d3.axisLeft(y));
function update(){
d3.selectAll("circle").remove()
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", function(d) { return r(d.r); })
.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return y(d.y); })
.style("stroke", "black")
.style("fill", "none")
}
You're actually deleting the bubble and redrawing the same.
You need to update your chart.
function update(){
d3.selectAll("circle").remove()
svg.selectAll("dot")
.data(getdata())
.enter().append("circle")
.attr("r", function(d) { return r(d.r); })
.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return y(d.y); })
.style("stroke", "black")
.style("fill", "none")
}
I just replaced .data(data) with .data(getdata())
I'm trying to generate a D3 Stream Graph similar to this beautiful example using some activity tracker data.
I've referenced this example - http://jsfiddle.net/NTJPB/1/light
My JSFiddle - http://jsfiddle.net/Nyquist212/00war1o6/ is telling me I have something wrong with my json format (despite trying to model it on the examples).
The core of my code looks something like this -
data.forEach(function(d){
parseDate = d3.time.format("%m/%d/%Y").parse;
d.date = parseDate(d.date);
d.value = Math.round(+d.value);
});
var maxval = d3.max(data, function(d){ return d.value; });
// Nest the json by key
var nest = d3.nest()
.key(function(d) { return d.key; })
.entries(data);
var margin = {top: 50, right: 50, bottom: 50, left: 50 },
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var color = d3.scale.linear()
.range(["#0A3430", "#1E5846", "#3E7E56", "#6BA55F", "#A4CA64", "#E8ED69"]);
var x = d3.scale.linear()
.range([0, width])
.domain([0, data[0].length]);
var y = d3.scale.linear()
.range([height, 0])
.domain([0, maxval]);
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 + ")");
var stack = d3.layout.stack()
.offset("wiggle");
var layers = stack([data]);
var area = d3.svg.area()
.interpolate('cardinal')
.x(function (d, i) { return x(i); })
.y0(function (d) { return y(d.y0);})
.y1(function (d) { return y(d.y0 + d.y);});
svg.selectAll(".layer")
.data(layers)
.enter().append("path")
.attr("class", "layer")
.attr("d", function (d) {return area(d);})
.style("fill", function (d, i) {
return color(i);
});
var layers = stack([nest]);
var area = d3.svg.area()
.interpolate('cardinal')
.x(function (d, i) { return x(i); })
.y0(function (d) { return y(d.y0);})
.y1(function (d) { return y(d.y0 + d.y);});
svg.selectAll(".layer")
.data(layers)
.enter().append("path")
.attr("class", "layer")
.attr("d", function (d) {return area(d);})
.style("fill", function (d, i) {
return color(i);
});
What is the preferred/desired json format I need to massage my data into? Is this even my problem?
Thanks
I refactored things and managed to get it working... amongst other things I think I was polluting my global variable namespace.
http://jsfiddle.net/Nyquist212/00war1o6/
margin = {top: 20, right: 20, bottom: 20, left: 30};
width = 950 - margin.left - margin.right;
height = 250 - margin.top - margin.bottom;
colorrange = ["#B30000", "#E34A33", "#FC8D59", "#FDBB84", "#FDD49E", "#FEF0D9"];
parseDate = d3.time.format("%m/%d/%Y").parse;
x = d3.time.scale().range([margin.left, width]);
y = d3.scale.linear().range([height, 0]);
z = d3.scale.ordinal().range(colorrange);
xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(d3.time.years);
yAxis = d3.svg.axis().scale(y);
nest = d3.nest()
.key(function(d) { return d.key; });
data.forEach(function(d) {
d.date = parseDate(d.date);
d.value= +d.value;
});
stack = d3.layout.stack()
.offset("silhouette")
.values(function(d) { return d.values; })
.x(function(d) { return d.date; })
.y(function(d) { return d.value; });
layers = stack(nest.entries(data));
area = d3.svg.area()
//.interpolate("cardinal")
.interpolate("basis")
.x(function(d) { return x(d.date); })
.y0(function(d) { return y(d.y0); })
.y1(function(d) { return y(d.y0 + d.y); });
svg = d3.select("#streamgraph").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 + ")");
layers = stack(nest.entries(data));
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);
svg.selectAll(".layer")
.data(layers)
.enter().append("path")
.attr("class", "layer")
.attr("d", function(d) { return area(d.values); })
.style("fill", function(d, i) { return z(i); });
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width + ", 0)")
.call(yAxis.orient("right"));
svg.append("g")
.attr("class", "y axis")
.call(yAxis.orient("left"));
svg.selectAll(".layer")
.attr("opacity", 1)
.on("mouseover", function(d, i) {
svg.selectAll(".layer").transition()
.duration(250)
.attr("opacity", function(d, j) {
return j != i ? 0.6 : 1;
})
})
I'm trying to build a multi series bar chart using d3 but running into problems due to the sparse nature of the dataset.
I want to force the x-axis to have a tick for every day, even if there is no data. The test data I have can have data points that are weeks apart so I'm expecting wide areas with no bars - which is fine.
I thought I could force the xAxis to use a set of predefined ticks using the tickValues array, but these leads to very strange display of overlaying the text for each day on top of days that do have some data.
I've included a screenshot of what I mean.
I get the feeling I'm supposed to do something when calculating the width of the bars but can't figure out what that might be.
Code:
var data = [];
var tickValues = [];
var max = _.max(chartData.tabular, function(assessment) { return assessment.dateUTC; });
var min = _.min(chartData.tabular, function(assessment) { return assessment.dateUTC; });
var iter = moment.twix(min.dateUTC, max.dateUTC).iterate("days");
while(iter.hasNext()){
var momentObj = iter.next();
var assessment = _.find(chartData.tabular, {'date': momentObj.format('DD/MM/YYYY')});
tickValues.push(momentObj.valueOf());
if(assessment != null){
if(assessment.type == 'calculated'){
data.push({date: momentObj.valueOf(), calculated: assessment.score, manual: null});
}
if(assessment.type == 'manual'){
data.push({date: momentObj.valueOf(), calculated: null, manual: assessment.score});
}
}
}
log(data);
var margin = {top: 20, right: 55, bottom: 30, left: 40},
width = $('#cahai-chart').width() - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.rangeRound([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickValues(tickValues)
.tickFormat(function(d){return d3.time.format('%d/%m/%y')(new Date(d))});
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var color = d3.scale.ordinal()
.range(["#001c9c","#101b4d","#475003","#9c8305","#d3c47c"]);
var svg = d3.select("#cahai-chart svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var labelVar = 'date';
var varNames = d3.keys(data[0]).filter(function (key) { return key !== labelVar;});
color.domain(varNames);
data.forEach(function (d) {
var y0 = 0;
d.mapping = varNames.map(function (name) {
return {
name: name,
label: d[labelVar],
y0: y0,
y1: y0 += +d[name]
};
});
d.total = d.mapping[d.mapping.length - 1].y1;
});
x.domain(data.map(function (d) { return d.date; }));
y.domain([0, d3.max(data, function (d) { return d.total; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
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")
.text("Score");
var selection = svg.selectAll(".series")
.data(data)
.enter().append("g")
.attr("class", "series")
.attr("transform", function (d) { return "translate(" + x(d.date) + ",0)"; });
selection.selectAll("rect")
.data(function (d) { return d.mapping; })
.enter().append("rect")
.attr("width", x.rangeBand())
.attr("y", function (d) { return y(d.y1); })
.attr("height", function (d) { return y(d.y0) - y(d.y1); })
.style("fill", function (d) { return color(d.name); })
.style("stroke", "grey")
.on("mouseover", function (d) { showPopover.call(this, d); })
.on("mouseout", function (d) { removePopovers(); })
var legend = svg.selectAll(".legend")
.data(varNames.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function (d, i) { return "translate(55," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 10)
.attr("width", 10)
.attr("height", 10)
.style("fill", color)
.style("stroke", "grey");
legend.append("text")
.attr("x", width - 12)
.attr("y", 6)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function (d) { return d; });
function removePopovers () {
$('.popover').each(function() {
$(this).remove();
});
}
function showPopover (d) {
$(this).popover({
title: d.name,
placement: 'auto top',
container: 'body',
trigger: 'manual',
html : true,
content: function() {
return "Date: " + d3.time.format('%d/%m/%y')(new Date(d.label)) +
"<br/>Score: " + d3.format(",")(d.value ? d.value: d.y1 - d.y0); }
});
$(this).popover('show')
}
An ordinal scale will always show as many ticks are there are values in the domain. You just need to pass the full array of dates as the domain.
Replace this line
x.domain(data.map(function (d) { return d.date; }));
with this
x.domain(tickValues);
It looks like you have everything else set up correctly, so this will space the bars out along the axis and make them slimmer.