Can anyone tell me what I am doing wrong? I am getting an error.
Uncaught (in promise) TypeError: xScale.bandwidth is not a function
at barChart (bar_chart.js:53:27)
at bar_chart.js:84:5
I am trying to create a bar graph of this data.
year,total_ghg
2000,661.97
2001,665.72
2002,660.75
2003,583.65
2004,635.5
2005,598.44
2006,646.91
2007,646.46
2008,617.09
2009,633.8
2010,601.14
2011,644.74
2012,643.12
2013,555.26
2014,566.21
2015,566.47
2016,577.32
2017,623.08
2018,619.26
my js
var dataset;
function barChart(dataset) {
//declaring Varibales
var margin = {top:50, right:50, bottom:50, left:50};
var width = 500-margin.left-margin.right;
var height = 500-margin.top-margin.bottom;
//creating svg
var svg = d3
.select("#barchart")
.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 + ")");
//setting up scales
var xScale = d3
.scaleTime()
.domain([
d3.min(dataset, function (d) {
return d.year;
}),
d3.max(dataset, function (d) {
return d.year;
}),
])
.range([0,width]);
var yScale = d3.scaleLinear()
.domain([0,d3.max(dataset, function (d) {
return d.total_ghg;
})
])
.range([height,0]);
// Plotting axis
svg.append("g").attr("transform", "translate(0," + height + ")").call(d3.axisBottom(xScale));
svg.append("g").call(d3.axisLeft(yScale));
//Set up groups
svg.selectAll("mybar")
.enter()
.data(dataset).append("rect")
.attr("x", function(d) {
return xScale(d.year);
})
.attr('y', function(d) {
return yScale(d.total_ghg);
})
.attr("width", xScale.rangeBand())
.attr('height', function(d) {
return height - yScale(d.total_ghg);
})
.attr("fill", "#004DA5")
.on("mouseover", function(event, d) {
d3.select(this).attr("fill", "orange");
var xPosition = parseFloat(d3.select(this).attr("x")) + xScale.bandwidth() / 2 - 5;
var yPosition = parseFloat(d3.select(this).attr("y")) + 20;
svg.append("text")
.attr("id", "tooltip")
.attr("x", xPosition)
.attr("y", yPosition)
.text(d);
}).on("mouseout", function(d) {
d3.select("#tooltip").remove();
d3.select(this)
.attr("fill", "#004DA5")
});
}
function init() {
d3.csv("src/data/total_ghp.csv", function (d) {
// + : force the year and month to be typed as a number instead of string
return {
year: d3.timeParse("%Y")(d.year),
total_ghg: +d.total_ghg,
};
}).then(function (data) {
dataset = data;
barChart(dataset);
});
}
window.addEventListener("load", init);
Any suggestions Please
What I have tried
ScaleOrdinal
rangeBandRounds
RangeBand()
instead of bandwidth
and a few more things like using a different d3 script same error in every scenario
Your xScale uses scaleTime, which is meant for charts where the axis represents time (i.e. line charts). For bar charts you should use scaleBand instead which does have the bandwidth function:
const xScale = d3
.scaleBand()
.domain([
d3.min(dataset, (d) => d.year),
d3.max(dataset, (d) = > d.year),
])
.range([0, width]);
More information on scaleBand can be found here: https://observablehq.com/#d3/d3-scaleband
There are some other mistakes in your code that prevent your bars from rendering:
Replace scaleTime with scaleBand for xScale
Replace xScale.rangeBand() with xScale.bandwidth()
Move .enter() to come after .data(dataset)
Related
I probably should be sticking with d3 v4 or lower it seems. Anyway, I'm trying to create a typical Area Chart to display stock data. But I'm running into a problem with a mouseover function where I try to get the nearest data point based off mouse location. Similar to what happens when you hover on a graph like on google or yahoo finance. But the main issue is a problem with my X variable I guess.
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var bisect = d3.bisector(function (d) {
return d.date;
}).left;
// Create the circle that travels along the curve of chart
var area = d3
.area()
.x(function (d) {
return x(d.date);
})
.y0(height)
.y1(function (d) {
return y(d.close);
});
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 focus = svg
.append("g")
.append("circle")
.style("fill", "none")
.attr("stroke", "black")
.attr("r", 8.5)
.style("opacity", 0);
function mouseover() {
focus.style("opacity", 1);
}
// format the data
var stockDateObj = {};
for (const [key, value] of Object.entries(data)) {
var stockDateObj = {};
stockDateObj.date = parseTime(key);
stockDateObj.close = Number(value["4. close"]);
setD3Data.push(stockDateObj);
}
setD3Data = setD3Data.sort(function (x, y) {
return d3.ascending(x.date, y.date);
});
this.setState({ currD3Data: setD3Data });
x.domain(
d3.extent(this.state.currD3Data, function (d) {
return d.date;
})
);
y.domain([
0,
d3.max(this.state.currD3Data, function (d) {
return d.close;
}),
]);
function mousemove(e) {
// recover coordinate we need
console.log(d3.pointer(e, svg.node()));
var x0 = x.invert(d3.pointer(e, svg.node()));
console.log(x0);
var i = bisect(this.state.currD3Data, x0, 1);
console.log(i);
var selectedData = this.state.currD3Data[i];
console.log(selectedData);
focus.attr("cx", x(selectedData.date)).attr("cy", y(selectedData.close));
}
function mouseout() {
focus.style("opacity", 0);
}
mouseover = mouseover.bind(this);
mousemove = mousemove.bind(this);
mouseout = mouseout.bind(this);
svg
.append("rect")
.style("fill", "none")
.style("pointer-events", "all")
.attr("width", width)
.attr("height", height)
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseout", mouseout);
The data above shows the creation of the x domain and the svg element that handles the mouseover functionality.
I don't really know what else to do considering I'm using d3 v6.
But this my error when i console.log(x0):
Invalid Date
I have created the stacked bar chart by using d3.js.In that I would like to display a single bar with different colors to highlight the data for particular x axis value like below.
The script i have used to plot stacked chart is below:
// Set the dimensions of the canvas / graph
var svg = d3.select("#svgID"),
margin = {top: 80, right: 140, bottom: 100, left: 100},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var padding = -100;
//set the ranges
var x = d3.scaleBand()
.rangeRound([0, width])
.paddingInner(0.20)
.align(0.1);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var z = d3.scaleOrdinal()
.range(["#008000", "#C00000", "#404040", "#4d4d4d"]);
var data = $("#svgID").data("values");
var keys = ["Pass", "Fail", "Average", "Worst"];
var legendKeysbar = ["Pass", "Fail", "Average", "Worst"];
var legendColorsbar = d3.scaleOrdinal()
.range(["#008000", "#C00000", "#404040", "#4d4d4d"]);
// Scale the range of the data
x.domain(data.map(function (d) {
return d.year;
}));
y.domain([0, d3.max(data, function (d) {
return d.total;
})]).nice();
z.domain(keys);
// add the Y gridlines
g.append("g").selectAll(".hline").data(y.ticks(10)).enter()
.append("svg:line")
.attr("x1", 0)
.attr("y1", function(d){ return y(d);})
.attr("x2", width)
.attr("y2", function(d){ return y(d);})
.style("stroke", "white")
.style("stroke-width", 1);
// append the rectangles for the bar chart
g.append("g")
.selectAll("g")
.data(d3.stack().keys(keys)(data))
.enter().append("g")
.attr("fill", function (d) {
return z(d.key);
})
.selectAll("rect")
.data(function (d) {
return d;
})
.enter().append("rect")
.attr("x", function (d) {
return x(d.data.year);
})
.attr("y", function (d) {
return y(d[1]);
})
.attr("height", function (d) {
return y(d[0]) - y(d[1]);
})
Can you help me to update colors for single bar? is that possible by d3.js
Create a second color scale, then in the method where you assign color, perform a check to determine which color scale to use, e.g.,:
var z2 = d3.scaleOrdinal().range(<your color array here>)
...
.attr("fill", function (d) {
return d.data.year === "Dec" ? z2(d.key) : z(d.key);
})
Current situation: I already have a small multiple visualization for my data. What it represents is the stress intensity over time for six different days. It plots the graphs correctly. Now I wanted to add dots on the existing graph if the person smoked at that time. I am reading a csv file which consists of date, time, stress level and whether the person smoked or not (so 1 if they did and -1 if they didn't). I am using d3 v4.
This is what I am currently getting but the red dots are obviously in the wrong spot because they are showing up places I don't even have data.
What I wanted was for the red dots to be on the graph and represent the times the user smoked.
Code:
<script>
var margin = {top: 8, right: 10, bottom: 2, left: 10},
width = 1160 - margin.left - margin.right,
height = 100 - margin.top - margin.bottom;
var parseDate = d3.timeParse("%H:%M:%S");
var x = d3.scaleTime()
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var area = d3.area()
.x(function (d) {
return x(d.time);
})
.y0(height)
.y1(function (d) {
return y(d.stress);
});
var line = d3.line()
.x(function (d) {
return x(d.time);
})
.y(function (d) {
return y(d.stress);
});
d3.csv("6000smokedData3.csv", type, function (error, data) {
// Nest data by date.
var dates = d3.nest()
.key(function (d) {
return d.date;
})
.entries(data);
// Compute the maximum stress per date, needed for the y-domain.
dates.forEach(function (s) {
s.maxPrice = d3.max(s.values, function (d) {
return d.stress;
});
});
// Compute the minimum and maximum time across dates.
// We assume values are sorted by time.
x.domain([
d3.min(dates, function (s) {
return s.values[0].time;
}),
d3.max(dates, function (s) {
return s.values[s.values.length - 1].time;
})
]);
// Add an SVG element for each date, with the desired dimensions and margin.
var svg = d3.select("body").selectAll("svg")
.data(dates)
.enter().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 + ")");
//Add the scatterplot
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 4)
.style("fill", function (d) {
return "red";
})
.attr("cx", function (d) {
if (d.smoked == 1) {
return x(d.time);
}
})
.attr("cy", function (d) {
if (d.smoked == 1) {
return y(d.stress);
}
});
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
svg.append("g")
.call(d3.axisLeft(y));
// Add the area path elements. Note: the y-domain is set per element.
svg.append("path")
.attr("class", "area")
.attr("d", function (d) {
y.domain([0, d.maxPrice]);
return area(d.values);
});
// Add the line path elements. Note: the y-domain is set per element.
svg.append("path")
.attr("class", "line")
.attr("d", function (d) {
y.domain([0, d.maxPrice]);
return line(d.values);
});
// Add a small label for the date name.
svg.append("text")
.attr("x", width - 6)
.attr("y", height - 6)
.style("text-anchor", "end")
.text(function (d) {
return d.key;
});
});
function type(d) {
d.stress = +d.stress;
d.time = parseDate(d.time);
d.smoked = +d.smoked;
return d;
}
</script>
Few lines of csv file:
date,time,stress,smoked
2014-08-04,11:24:28,0.026191,-1
2014-08-04,11:24:29,0.026183,-1
2014-08-04,11:24:30,0.031845,-1
2014-08-04,11:24:31,0.01235,-1
Thank you
You're drawing the dots before you set the y scale for each element. I usually like to make small multiples inside of an each loop to avoid tricky things like. It looks like the y axis is also off - they should be different on each plot.
I have a scatter plot with D3 and I'm trying to add circles to a selection based on changing data. I'm passing the data selection down to two functions: render and update. The render function is the initial render and update has the enter() and exit() methods. I can easily add the initial data set and get the circles no longer in the data set to exit. I'm using d.id as the d3 placeholder.
The problem: when I try to enter() the added data points, nothing happens. I've checked the length of the new data selection, and it's larger than the pre-existing. On the DOM, the smaller data set remains (the circles that were already there), but no new circles enter, even though the data set has changed.
I've looked through lots of tutorials regarding data joins, and I think I've appropriately called my enter() and exit() methods. What gives?
Here is my code:
var container = angular.element(document.querySelector('.chart-container'))[0];
var margin = {
top: container.clientHeight / 12,
right: container.clientWidth / 14,
bottom: container.clientHeight / 10,
left: container.clientWidth / 11
};
var w = container.clientWidth - margin.left - margin.right;
var h = container.clientHeight - margin.top - margin.bottom;
// ******** **************** ******** //
// ******** INITIAL RENDER ******** //
function render(input) {
console.log(Object.keys(input).length);
var xScale = d3.scale.linear()
.domain([0, d3.max(input, function(d) { return d["ctc"]; })])
.range([0, w])
.nice();
var yScale = d3.scale.linear()
.domain([0, d3.max(input, function(d) { return d["ttc"]; })])
.range([h, 0])
.nice();
var rScale = d3.scale.linear()
.domain([0, d3.max(input, function(d) { return d["effective"]; })])
.range([2, 15]);
// *********** //
// SVG ELEMENT //
var svg = d3.select('.chart-container')
.append('svg')
.attr('class', 'scatter')
.attr('viewBox', '0, 0, ' + Math.round(w + margin.left + margin.right) + ', ' + Math.round(h + margin.top + margin.bottom))
.attr('preserveAspectRatio', 'xMinYMin')
// used to center element and make use of margins
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// add circles in group
var circles = svg.append('g')
.attr('class','circles')
.attr('clip-path','url(#chart-area)');
// add individual circles
var circle = circles.selectAll('circle')
.data(input, function(d) {return d.id;})
.enter()
.append('circle')
.attr('class', 'circle')
.attr('cx', function(d) { return xScale(d["ctc"]); })
.attr('cy', function(d) { return yScale(d["ttc"]); })
.attr('r', function(d) { return rScale(d["effective"]); })
.attr('fill', function(d, i) { return d["effective"]; })
.on('mouseover', function(d) {
tooltip.style('visibility', 'visible');
return tooltip.text(d["technology"]);
})
.on("mousemove", function(){ return tooltip.style("top",
(d3.event.pageY-10)+"px").style("left",(d3.event.pageX+10)+"px");})
.on("mouseout", function(){return tooltip.style("visibility", "hidden");})
// append clip path
svg.append('clipPath')
.attr('id','chart-area')
.append('rect')
.attr('class', 'rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', w)
.attr('height', h);
};
// ******** **************** ******** //
// ******** UPDATE ******** //
function update(updateObject) {
var input = updateObject;
var xScale = d3.scale.linear()
.domain([0, d3.max(input, function(d) { return d["ctc"]; })])
.range([0, w])
.nice();
var yScale = d3.scale.linear()
.domain([0, d3.max(input, function(d) { return d["ttc"]; })])
.range([h, 0])
.nice();
var rScale = d3.scale.linear()
.domain([0, d3.max(input, function(d) { return d["effective"]; })])
.range([2, 15]);
var svg = d3.select('svg')
.data(input)
.attr('viewBox', '0, 0, ' + Math.round(w + margin.left + margin.right) + ', ' + Math.round(h + margin.top + margin.bottom))
.attr('preserveAspectRatio', 'xMinYMin')
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// BIND TO DATA
var circles = d3.selectAll('circle')
.data(input, function(d) { return d.id; });
// Circles Enter
circles.enter()
.insert('svg:circle')
.attr('class', 'circle')
.attr('cx', function(d) { return xScale(d["ctc"]); })
.attr('cy', function(d) { return yScale(d["ttc"]); })
.attr('r', function(d) { return rScale(d["effective"]); });
/*
.on('mouseover', function(d) {
tooltip.style('visibility', 'visible');
return tooltip.text(d["technology"]);
})
.on("mousemove", function(){ return tooltip.style("top",
(d3.event.pageY-10)+"px").style("left",(d3.event.pageX+10)+"px");})
.on("mouseout", function(){return tooltip.style("visibility", "hidden");})
*/
// UPDATE
circles.transition()
.duration(1000)
.attr('cx', function(d) { return xScale(d["ctc"]); })
.attr('cy', function(d) { return yScale(d["ttc"]); })
.attr('r', function(d) { return rScale(d["effective"]); });
// EXIT
circles.exit()
.transition()
.duration(500)
.attr('r', 0)
.style('opacity', 0)
.style('fill', 'gray')
.remove();
}
Update
here is a codepen for testing: http://codepen.io/himmel/pen/JdNJMM
The problem with the code is, that you are trying to use an object as data. d3.selection.data() takes an array, not an object. See the d3 wiki for more information on the data() function.
I have created an updated version of your codepen. I changed the data to an array and applied the correct conventional margin. Moreover I simplified the code by removing the double initialization of scales and the svg element.
I am currently modifying the following example to produce a multi horizontal relative stack chart. The following example is a "single" stack chart example.
http://jsfiddle.net/zDkWP/
Here is my version however I get NaN X and Width values and I'm looking to find out why. Thank you.
Within the SVG the structure is as follows for each of the : -
g[ g [rect, rect, rect]], g[ g [rect, rect, rect]]
Here is my code: http://jsfiddle.net/scottietom/7c3vb4e9/
var dataset = [
[
[{x: 0,y: 100}],[{x: 0,y: 30}],[{x: 0,y: 50}]],
[[{x: 0,y: 100}],[{x: 0,y: 30}],[{x: 0,y: 50}]]
];
//Set up stack method
var stack = d3.layout.stack();
//Data, stacked
for (i=0; i<dataset.length; i++) {
stack(dataset[i]);
}
//Set up scales
var xScale = d3.scale.linear()
.domain([0, d3.max(dataset, function (d) {
return d3.max(d, function (d) {
return d.y0 + d.y;
});
})])
.range([0, w]);
//Easy colors accessible via a 10-step ordinal scale
var colors = d3.scale.category10();
//or make your own colour palet
var color = d3.scale.ordinal()
.range(["#1459D9", "#148DD9", "#87ceeb", "#daa520"]);
//Create SVG element
var svg = d3.select(".pathanalysis_diagram")
.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 + ")");
// Add a group for each row of data
var groups = svg.selectAll("g")
.data(dataset)
.enter()
.append("g")
.style("fill", function (d, i) {
return color(i);
});
// Add a rect for each data value
var rects = groups.selectAll("rect")
.data(function (d) {
return d;
})
.enter()
.append("rect")
.attr("x", function (d) {
return xScale(d.y0);
})
.attr("y", 50)
.attr("height", 50)
.attr("width", function (d) {
return xScale(d.y);
});
Here's a JSFiddle with your solution: http://jsfiddle.net/ee2todev/z73u6mro/
I called the nested dataset arrayOfDatasets so the changes become more clear.
First you have to call stack() for each of the datasets:
arrayOfDatasets.forEach(function (dataset) {stack(dataset); });
Then you have to retrieve the max for xScale:
var xScale = d3.scale.linear()
.domain([0, d3.max(arrayOfDatasets, function (dataset) {
return d3.max(dataset, function (d) {
return d3.max(d, function (d) {
return d.y0 + d.y;
});
})
})])
.range([0, w]);
Finally, you just have to access the data properly and translate the datasets so they don't overlap:
var groupOfGroups = svg.selectAll("g.toplevel")
.data(arrayOfDatasets)
.enter()
.append("g")
.attr("class", "toplevel")
.attr("transform", function(d, i ) {return "translate(0, " + (-i * 55) + ")"; });
// Add a group for each row of data
var groups = groupOfGroups.selectAll("g.dataset")
.data(function(d) {return d; })
.enter()
.append("g")
.attr("class", "dataset")
.style("fill", function (d, i) {
return color(i);
});
You might still store your datasets differently since it's not clear why you have the x values in them.