I started the D3.js challenge on FreeCodeCamp, the problem is that I solved it with the chart but it only gives me a display on the rectum, only one with the width and height that it I put, I'll show the code below.
The entire code on
<script>
//set d3
var w = 1000, h = 500;
var padding = 50;
var svg = d3.select('body')
.append('svg')
.attr('width', w)
.attr('height', h)
//title
svg.append('text')
.attr('x', w / 2)
.attr('y', 50)
.text('United States GDP')
fetch('https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/GDP-data.json')
.then((result)=>result.json())
.then((data)=>{
var the_data = data['data']
//get vals
var get_max = d3.max(data['data'])
var get_mix = d3.min(data['data'])
//for x
var max_x = Number(get_max[0].split('-')[0])
var min_x = Number(get_mix[0].split('-')[0])
//for y
var max_y = get_max[1]
var min_y = get_mix[1]
var xScale = d3.scaleLinear()
.domain([min_x, max_x])
.range([padding, w-padding])
var yScale = d3.scaleLinear()
.domain([min_y, max_y])
.range([h-padding, padding])
//the_chars
for(var i in the_data){
var get_year = Number(the_data[i][0].split('-')[0])
the_data[i][0] = get_year
}
svg.selectAll('rect')
.data(the_data)
.enter()
.append('rect')
.attr("x", (d) => { xScale(d[0]) })
.attr('y', (d)=>{ yScale(d[1]) })
.attr("width", 200)
.attr("height", 20)
//axis
const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale);
//display axis
svg.append("g")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
svg.append('g')
.attr('transform', 'translate(' + padding + ', 0)')
.call(yAxis)
})
Now, what I need to do to display the charts!
I mention that the script tags are embedded in the body
Problem: Arrow functions without a return value. Solution: Instead use an explicit or an implicit return.
.attr("x", (d) => { xScale(d[0]) }) // returns undefined
.attr("x", (d) => xScale(d[0])) // implicit return
.attr("x", (d) => { return xScale(d[0]) }) // explicit return
Problem: Fixed height value. Solution Evaluate the height of each based on the GDP value (d[1]) instead.
.attr('height', 20) // fixed height
.attr('height', d => yScale(min_y) - yScale(d[1]))
// subtract from min range to account for padding and inverted y coordinates in SVG
Full solution in this codepen
I'm having trouble adjusting if/else terminology to adjust for changes from d3.nest (v5) to d3.groups (v6 onward).
I've adjusted from
.data(function(d) { return d.values; })
to
.data(([, values ]) => values)
without any issues. However, the code I'm working off for a source has an if/else statement that is causing me some trouble. I need to adjust the following and I'm at a loss (selectLegis is a filtered dataset):
.data(selectLegis, function(d){
return d ? d.key : this.key;
})
What are d and this.key in the new nomenclature and how do I rewrite the code here?
Edit: added sample data below
year, state, wvalue, lvalue,
1980, AL, 0.4, Example1,
1980, AL, 0.3, Example2,
1984, AL, 0.2, Example1,
1984, AL, 0.7, Example2
Edit: reproducible example below
var margin = { top: 50, right: 50, bottom: 50, left: 50 }
var h = 500 - margin.top - margin.bottom
var w = 700 - margin.left - margin.right
var formatDecimal = d3.format('.2')
d3.csv('15/data.csv').then(function (data) {
// format the data
data.forEach(function(d) {
d.year = +d.year;
d.state = d.state;
d.wvalue = +d.wvalue;
d.lvalue = d.lvalue
});
// Scales
var x = d3.scaleLinear()
.range([0,w])
var y = d3.scaleLinear()
.range([h,0])
y.domain([
d3.min([0,d3.min(data,function (d) { return d.wvalue })]),
d3.max([0,d3.max(data,function (d) { return d.wvalue })])
]);
x.domain([1968, 2016])
// Define the line
var valueLine = d3.line()
.x(function(d) { return x(d.year); })
.y(function(d) { return y(d.wvalue); })
// Create the svg canvas in the "d3block" div
var svg = d3.select("#d3block")
.append("svg")
.style("width", w + margin.left + margin.right + "px")
.style("height", h + margin.top + margin.bottom + "px")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform","translate(" + margin.left + "," + margin.top + ")")
.attr("class", "svg");
//nest variable
var nest = d3.groups(data,
d => d.state, d => d.lvalue)
// X-axis
var xAxis = d3.axisBottom()
.scale(x)
.tickFormat(formatDecimal)
.ticks(7)
// Y-axis
var yAxis = d3.axisLeft()
.scale(y)
.tickFormat(formatDecimal)
.ticks(5)
// Create a dropdown
var legisMenu = d3.select("#legisDropdown")
legisMenu
.append("select")
.selectAll("option")
.data(nest)
.enter()
.append("option")
.attr("value", ([key, ]) => key)
.text(([key, ]) => key)
// Function to create the initial graph
var initialGraph = function(legis){
// Filter the data to include only state of interest
var selectLegis = nest.filter(([key, ]) => key == legis)
var selectLegisGroups = svg.selectAll(".legisGroups")
.data(selectLegis, function(d){
return d ? d.key : this.key;
})
.enter()
.append("g")
.attr("class", "legisGroups")
var initialPath = selectLegisGroups.selectAll(".line")
.data(([, values]) => values)
.enter()
.append("path")
initialPath
.attr("d", d => valueLine(d))
.attr("class", "line")
}
// Create initial graph
initialGraph("Alabama")
// Update the data
var updateGraph = function(legis){
// Filter the data to include only state of interest
var selectLegis = nest.filter(([key, ]) => key == legis)
// Select all of the grouped elements and update the data
var selectLegisGroups = svg.selectAll(".legisGroups")
.data(selectLegis)
// Select all the lines and transition to new positions
selectLegisGroups.selectAll("path.line")
.data(([, values]) => values)
.transition()
.duration(1000)
.attr("d", valueLine(([, values ]) => values))
}
// Run update function when dropdown selection changes
legisMenu.on('change', function(){
// Find which state was selected from the dropdown
var selectedLegis = d3.select(this)
.select("select")
.property("value")
// Run update function with the selected state
updateGraph(selectedLegis)
});
// X-axis
svg.append('g')
.attr('class','axis')
.attr('id','xAxis')
.attr('transform', 'translate(0,' + h + ')')
.call(xAxis)
.append('text') // X-axis Label
.attr('id','xAxisLabel')
.attr('fill','black')
.attr('y',-10)
.attr('x',w)
.attr('dy','.71em')
.style('text-anchor','end')
.text('')
// Y-axis
svg.append('g')
.attr('class','axis')
.attr('id','yAxis')
.call(yAxis)
.append('text') // y-axis Label
.attr('id', 'yAxisLabel')
.attr('fill', 'black')
.attr('transform','rotate(-90)')
.attr('x',0)
.attr('y',5)
.attr('dy','.71em')
.style('text-anchor','end')
.text('wvalue')
})
altocumulus was correct that the issue was how the data were being manipulated/called. I did more digging and found the answer here.
I had to replace the initial path attribute .attr("d", valueLine(([, values]) => values)) with .attr('d', (d) => valueLine(Array.from(d.values())[1])). I also had to replace the code further down within the updateGraph function under selectLegisGroups .attr for it to update properly.
The problem
I am trying to get a stacked bar graph in D3 (v5) to have individually coloured bar for different groups (that I can do, Fig 1), with each stack a different colour (depending on the Fig 2).
I can't find a way to get the stack colouring (i.e. I want different shades of the Group colour to vary with the different stack height) example in Fig 3 (except I'd like the different groups to be different colours i.e. not repeating as they are here).
In the code examples I have provided there are two sets of data. A simple set, to help play with the data:
Animal,Group,A,B,C,D
Dog,Domestic,10,10,20,5
Cat,Domestic,20,5,10,10
Mouse,Pest,75,5,35,0
Lion,Africa,5,5,30,25
Elephant,Africa,15,15,20,20
Whale,Marine,35,20,10,45
Shark,Marine,45,55,0, 60
Fish,Marine,20, 5,30,10
And a bigger set that I am actually trying to use.
Here is the bl.ocks.org code that I'm trying to develop:
// set the dimensions and margins of the graph
const margin = {
top: 90,
right: 20,
bottom: 30,
left: 40
},
width = 960 - margin.left - margin.right,
height = 960 - margin.top - margin.bottom;
const svg = d3.select("#my_dataviz")
.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 + ")");
const y = d3.scaleBand()
.range([0, height])
.padding(0.1);
const x = d3.scaleLinear()
.range([0, width]);
const z = d3.scaleOrdinal()
.range(["none", "lightsteelblue", "steelblue", "darksteelblue"]);
d3.csv("https://gist.githubusercontent.com/JimMaltby/844ca313589e488b249b86ead0d621a9/raw/f328ad6291ffd3c9767a2dbdba5ce8ade59a5dfa/TimeBarDummyFormat.csv", d3.autoType, (d, i, columns) => {
var i = 3;
var t = 0;
for (; i < columns.length; ++i)
t += d[columns[i]] = +d[columns[i]];
d.total = t;
return d;
}
).then(function(data) {
const keys = data.columns.slice(3); // takes the column names, ignoring the first 3 items = ["EarlyMin","EarlyAll", "LateAll", "LateMax"]
// List of groups = species here = value of the first column called group -> I show them on the X axis
const Groups = d3.map(data, d => d.Group);
y.domain(data.map(d => d.Ser));
x.domain([2000, d3.max(data, d => d.total)]).nice();
z.domain(keys);
svg.append("g")
.selectAll("g")
.data(d3.stack().keys(keys)(data))
.enter().append("g")
.attr("fill", d => z(d.key)) //Color is assigned here because you want everyone for the series to be the same color
.selectAll("rect")
.data(d => d)
.enter()
.append("rect")
.attr("y", d => y(d.data.Ser))
.attr("x", d => x(d[0]))
.attr("height", y.bandwidth())
.attr("width", d => x(d[1]) - x(d[0]));
svg.append("g")
.attr("transform", "translate(0,0)")
.call(d3.axisTop(x));
svg.append("g")
.call(d3.axisLeft(y));
});
.bar {
fill: rgb(70, 131, 180);
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="my_dataviz"></div>
JSFiddle:
https://jsfiddle.net/yq7bkvdL/
What I’ve tried
I feel like I am just missing something simple, but I am a coding noob and my coding is pretty rudimentary so I can't work it out.
I think I am either placing the fill attr in the wrong place. Or it's that I don't understand how to select the key in the nested/hierarchical data of d3.stack.
I have tried various things, all with no success:
1. Array of colours
I have tried writing a function to create an array of the colours, by iterating (with forEach) over the "key" and the "Group" values/names and concatenate them to create an array that I can use with the d3 Scale (ordinal) to select the correct colour. For example with the first dataset it would create an array ColoursID [DomesticA, DomesticB, DomesticC, DomesticD,PestA, PestB...] which then matches up to the colours in ColourS ["grey", "lightgreen", "green", "darkgreen", "yellow", ...]
Below is the attempt to do this, plus various other explorations commented out.
// List of groups = species here = value of the first column called group -> I show them on the X axis
const stack = d3.stack().keys(stackKeys)(data);
//const Groups = d3.map(data, d => d.Group);
//const ColourID = d3.map(data, d => d.Group && d.key);
// const stackID = stack.data // //stack[3].key = "D" // [2][6].data.Group = "Marine"
// const Test1 = Object.entries(stack).forEach(d => d.key);
const stackB = stack.forEach(function(d,i,j){
//var a = Object.keys(d)//= list of 3rd Level keys "0,..7,key,index"
//var a = Object.keys(d).forEach((key) => key) "undefined"
//var a = d.key //= "D" for all
d.forEach(function(d,i){
//var a = d.keys // = "function keys{} ([native code])"
//var a = Object.keys(d)
//var a = Object.keys(d) //= list of 2nd Level keys "0,1,data"
var b = data[i]["Group"]
d.forEach(function(d){
//var a = [d]["key"] // = "undefined"
//var a = Object.keys(d).forEach((key) => d[key]) // = "undefined"
var a = Object.keys(d) //= ""
// var a = d.keys //= "undefined"
data[i]["colourID"] = b + " a" + "-b " + a //d.key
})
})
});
console.log(stack)
svg.append("g")
.selectAll("g")
.data(stack)
.enter().append("g")
//.attr("fill", d => z(d.data.Group) ) //Color is assigned here because you want everyone for the series to be the same color
.selectAll("rect")
.data(d => d)
.enter().append("rect")
.attr("fill", d => colourZ(d.data.colourID)) //Color is assigned here because you want each Group to be a different colour **how do you vary the blocks?**
.attr("y", d => y(d.data.Animal) ) //uses the Column of data Animal to seperate bars
.attr("x", d=> x(d[0]) ) //
.attr("height", y.bandwidth() ) //
.attr("width", d=> x(d[1]) - x(d[0])); //
VizHub code: https://vizhub.com/JimMaltby/373f1dbb42ad453787dc0055dee7db81?file=index.js
2. Create a second colour scale:
I used the advice in here (d3.js-adding different colors to one bar in stacked bar chart), adding an if function to select a different colour scale, by adding this code:
//-----------------------------BARS------------------------------//
// append the rectangles for the bar chart
svg.append("g")
.selectAll("g")
.data(stack)
.enter().append("g")
//Color is assigned here because you want everyone for the series to be the same color
.selectAll("rect")
.data(d => d)
.enter().append("rect")
.attr("fill", d =>
d.data.Group == "Infrastructure"
? z2(d.key)
: z(d.key))
//.attr("class", "bar")
.attr("y", d => y(d.data.Ser) ) //Vert2Hor **x to y** **x(d.data.Ser) to y(d.data.Ser)**
.attr("x", d=> x(d[0]) ) //Vert2Hor **y to x** **y(d[1]) to x(d[0])**
.attr("height", y.bandwidth() ) //Vert2Hor **"width" to "height"** **x.bandwidth to y.bandwidth**
.attr("width", d=> x(d[1]) - x(d[0])); //Vert2Hor **"height" to "width"** **y(d[0]) - y(d[1]) to x(d[1]) - x(d[0])**
VizHub code
3. A big IF function in fill.
If this is the solution I would appreciate some advice on
a. making it work, then b. having a more efficient way of doing it
Here again it seems I am struggling to select the "key" of the "stack" data array. You'll note that I have been trying different ways to select the key in the code here, with no success :(.
.attr("fill", function(d,i, j) {
if (d.data.Group === "Domestic") {
if (d.key === "A") { return "none"}
else if (d.key === "B") { return "lightblue"}
else if (d.key === "C") { return "blue"}
else if (d.key === "D") { return "darkblue"}
else { return "forestgreen"}
}
else if (d.data.Group === "Pest") {
if (d.key === "A") { return "yellow"}
else if (d.key === "B") { return "lightorange"}
else if (d.key === "C") { return "orange"}
else if (d.key === "D") { return "darkorange"}
else { return "Brown"} //"yellow", "lightorange", "orange", ""
}
else if (d.data.Group === "Africa") {
if (Object.keys(root.data) === 1) { return "grey"}
else if (d.key === "B") { return "lightred"}
else if (d.key === "C") { return "red"}
else if (d.key === "D") { return "darkred"}
else { return "pink"}
}
else if (d.data.Group == "Marine") {
if (stackKeys == "A") { return "lightgrey"}
else if (stackKeys[d] == "B") { return "lightblue"}
else if (stackKeys[i] == "C") { return "blue"}
else if (stackKeys[3] == "D") { return "darkblue"}
else { return "steelblue"}
}
else { return "black" }
;})
Code in Viz Hub
If you want to vary the bar colours slightly if the bars are of smaller length, you can use fill-opacity and keep the fill the same! This way, the colours are less pronounced and lighter if the value is lighter.
Just create a new scale opacity with range [0.3, 1]. I chose 0.3 because 0 opacity means the bar is invisible, and you generally don't want that. I added a separate value d.height to denote the entire visible height of the bar, which is separate from start (but equivalent to d.Min + d.All + d.Max). Now, just apply the attribute to every bar and you're done.
You can choose to set the range to [0, 1] and not use d3.extent for the domain - that will probably lead to similar results, though there are some differences which you can spot with a thought experiment.
Right now the fill-opacity attribute is set on every bar. So the bars in the same stack have the same fill-opacity value. Note that this is entirely optional, though, and you can also apply distinct values.
// set the dimensions and margins of the graph
const margin = {
top: 90,
right: 20,
bottom: 30,
left: 40
},
width = 960 - margin.left - margin.right,
height = 960 - margin.top - margin.bottom;
const svg = d3.select("#my_dataviz")
.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 + ")");
const y = d3.scaleBand()
.range([0, height])
.padding(0.1);
const x = d3.scaleLinear()
.range([0, width]);
const z = d3.scaleOrdinal()
.range(["none", "lightsteelblue", "steelblue", "darksteelblue"]);
const opacity = d3.scaleLinear()
.range([0.3, 1]);
d3.csv("https://gist.githubusercontent.com/JimMaltby/844ca313589e488b249b86ead0d621a9/raw/f328ad6291ffd3c9767a2dbdba5ce8ade59a5dfa/TimeBarDummyFormat.csv", d3.autoType, (d, i, columns) => {
var i = 3;
var t = 0;
for (; i < columns.length; ++i)
t += d[columns[i]] = +d[columns[i]];
d.total = t;
d.height = d.total - d.Start;
return d;
}
).then(function(data) {
const keys = data.columns.slice(3); // takes the column names, ignoring the first 3 items = ["EarlyMin","EarlyAll", "LateAll", "LateMax"]
// List of groups = species here = value of the first column called group -> I show them on the X axis
const Groups = d3.map(data, d => d.Group);
y.domain(data.map(d => d.Ser));
x.domain([2000, d3.max(data, d => d.total)]).nice();
z.domain(keys);
opacity.domain(d3.extent(data, d => d.height));
console.log(opacity.domain());
svg.append("g")
.selectAll("g")
.data(d3.stack().keys(keys)(data))
.enter().append("g")
.attr("fill", d => z(d.key))
.selectAll("rect")
.data(d => d)
.enter()
.append("rect")
.attr("y", d => y(d.data.Ser))
.attr("x", d => x(d[0]))
.attr("height", y.bandwidth())
.attr("width", d => x(d[1]) - x(d[0]))
.attr("fill-opacity", d => opacity(d.data.height));
svg.append("g")
.attr("transform", "translate(0,0)")
.call(d3.axisTop(x));
svg.append("g")
.call(d3.axisLeft(y));
});
.bar {
fill: rgb(70, 131, 180);
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="my_dataviz"></div>
Edit: knowing that you want to colour the bars by group, I would use the same logic, but make a few adjustments:
For one, I switched z to deal with fill-opacity (which I still use to accentuate the different groups), and use a new ordinal scale group for the colours. The key to that scale is simply the pre-existing field d.Group.
// set the dimensions and margins of the graph
const margin = {
top: 90,
right: 20,
bottom: 30,
left: 40
},
width = 960 - margin.left - margin.right,
height = 960 - margin.top - margin.bottom;
const svg = d3.select("#my_dataviz")
.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 + ")");
const y = d3.scaleBand()
.range([0, height])
.padding(0.1);
const x = d3.scaleLinear()
.range([0, width]);
const z = d3.scaleOrdinal()
.range([0.25, 0.5, 0.75, 1]);
const group = d3.scaleOrdinal()
.range(["darkgreen", "darkred", "steelblue", "purple"]);
d3.csv("https://gist.githubusercontent.com/JimMaltby/844ca313589e488b249b86ead0d621a9/raw/f328ad6291ffd3c9767a2dbdba5ce8ade59a5dfa/TimeBarDummyFormat.csv", d3.autoType, (d, i, columns) => {
var i = 3;
var t = 0;
for (; i < columns.length; ++i)
t += d[columns[i]] = +d[columns[i]];
d.total = t;
return d;
}
).then(function(data) {
const keys = data.columns.slice(3); // takes the column names, ignoring the first 3 items = ["EarlyMin","EarlyAll", "LateAll", "LateMax"]
y.domain(data.map(d => d.Ser));
x.domain([2000, d3.max(data, d => d.total)]).nice();
z.domain(keys);
group.domain(data.map(d => d.Group));
svg.append("g")
.selectAll("g")
.data(d3.stack().keys(keys)(data))
.enter().append("g")
.attr("fill-opacity", d => z(d.key))
.selectAll("rect")
.data(d => d)
.enter()
.append("rect")
.attr("y", d => y(d.data.Ser))
.attr("x", d => x(d[0]))
.attr("height", y.bandwidth())
.attr("width", d => x(d[1]) - x(d[0]))
.attr("fill", d => group(d.data.Group));
svg.append("g")
.attr("transform", "translate(0,0)")
.call(d3.axisTop(x));
svg.append("g")
.call(d3.axisLeft(y));
});
.bar {
fill: rgb(70, 131, 180);
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="my_dataviz"></div>
Edit 2: if you want to specify the colours yourself, I would use a map of keys to colours:
// set the dimensions and margins of the graph
const margin = {
top: 90,
right: 20,
bottom: 30,
left: 40
},
width = 960 - margin.left - margin.right,
height = 960 - margin.top - margin.bottom;
const svg = d3.select("#my_dataviz")
.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 + ")");
const y = d3.scaleBand()
.range([0, height])
.padding(0.1);
const x = d3.scaleLinear()
.range([0, width]);
const z = d3.scaleOrdinal()
.range([0.25, 0.5, 0.75, 1]);
const group = d3.scaleOrdinal()
.range([
{ Start: "none", Min: "lightgreen", All: "green", Max: "darkgreen" },
{ Start: "none", Min: "indianred", All: "red", Max: "darkred" },
{ Start: "none", Min: "lightsteelblue", All: "steelblue", Max: "darksteelblue" }
]);
d3.csv("https://gist.githubusercontent.com/JimMaltby/844ca313589e488b249b86ead0d621a9/raw/f328ad6291ffd3c9767a2dbdba5ce8ade59a5dfa/TimeBarDummyFormat.csv", d3.autoType, (d, i, columns) => {
var i = 3;
var t = 0;
for (; i < columns.length; ++i)
t += d[columns[i]] = +d[columns[i]];
d.total = t;
return d;
}
).then(function(data) {
const keys = data.columns.slice(3); // takes the column names, ignoring the first 3 items = ["EarlyMin","EarlyAll", "LateAll", "LateMax"]
y.domain(data.map(d => d.Ser));
x.domain([2000, d3.max(data, d => d.total)]).nice();
z.domain(keys);
group.domain(data.map(d => d.Group));
svg.append("g")
.selectAll("g")
.data(d3.stack().keys(keys)(data))
.enter().append("g")
.each(function(e) {
d3.select(this)
.selectAll("rect")
.data(d => d)
.enter()
.append("rect")
.attr("y", d => y(d.data.Ser))
.attr("x", d => x(d[0]))
.attr("height", y.bandwidth())
.attr("width", d => x(d[1]) - x(d[0]))
.attr("fill", d => group(d.data.Group)[e.key]);
});
svg.append("g")
.attr("transform", "translate(0,0)")
.call(d3.axisTop(x));
svg.append("g")
.call(d3.axisLeft(y));
});
.bar {
fill: rgb(70, 131, 180);
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="my_dataviz"></div>
My making progress with my graph, it seems to mostly work. However I'm struggling to put my data values on the x axis. In this case, there should be 3 x axis labels, (test1, test2,test3).
// Data
var dataset = [{
name: "test1",
y: 0.1
},
{
name: "test2",
y: 0.6
},
{
name: "test3",
y: 0.9
}
];
It seems to just label it by how many entries there are (0,1,2) rather than using the name. What I tried was changing this:
var line = d3.line()
.x(function(d, i) {
return xScale(i);
To this (which I must admit was a bit of a guess).
var line = d3.line()
.x(function(d, i) {
return xScale(d.name);
Unfortunately that didn't work and I'm not sure what I can try next. Here is the full code if that helps.
http://jsfiddle.net/spadez/cfz3g4w2/
You are using the wrong scale for your x data. You have discrete data and want an ordinal scale.
var xScale = d3.scalePoint()
.domain(dataset.map(d => d.name)) // input is an array of names
.range([0, width]); // output
Running code:
// Data
var dataset = [{
name: "test1",
y: 0.1
},
{
name: "test2",
y: 0.6
},
{
name: "test3",
y: 0.9
}
];
// Count number of datapoints
var n = Object.keys(dataset).length;
// Find max of the data points for Y axis
var mymax = Math.max.apply(Math, dataset.map(function(o) {
return o.y;
}));
// 2. Use the margin convention practice
var margin = {
top: 50,
right: 50,
bottom: 50,
left: 50
},
width = window.innerWidth - margin.left - margin.right;
height = window.innerHeight - margin.top - margin.bottom;
// 5. X scale will use the index of our data
var xScale = d3.scalePoint()
.domain(dataset.map(d => d.name)) // input
.range([0, width]); // output
// 6. Y scale will use the randomly generate number
var yScale = d3.scaleLinear()
.domain([0, mymax]) // input
.range([height, 0]); // output
// 7. d3's line generator
var line = d3.line()
.x(function(d, i) {
return xScale(d.name);
}) // set the x values for the line generator
.y(function(d) {
return yScale(d.y);
}) // set the y values for the line generator
.curve(d3.curveMonotoneX) // apply smoothing to the line
// 1. Add the SVG to the page and employ #2
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 + ")");
// 3. Call the x axis in a group tag
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale)); // Create an axis component with d3.axisBottom
// 4. Call the y axis in a group tag
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale)); // Create an axis component with d3.axisLeft
// 9. Append the path, bind the data, and call the line generator
svg.append("path")
.datum(dataset) // 10. Binds data to the line
.attr("class", "line") // Assign a class for styling
.attr("d", line); // 11. Calls the line generator
// 12. Appends a circle for each datapoint
svg.selectAll(".dot")
.data(dataset)
.enter().append("circle") // Uses the enter().append() method
.attr("class", "dot") // Assign a class for styling
.attr("cx", function(d, i) {
return xScale(d.name)
})
.attr("cy", function(d) {
return yScale(d.y)
})
.attr("r", 5)
.on("mouseover", function(a, b, c) {
console.log(a)
this.attr('class', 'focus')
})
.on("mouseout", function() {})
.on("mousemove", mousemove);
var focus = svg.append("g")
.attr("class", "focus")
.style("display", "none");
focus.append("circle")
.attr("r", 4.5);
focus.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]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.date > d1.date - x0 ? d1 : d0;
focus.attr("transform", "translate(" + x(d.date) + "," + y(d.close) + ")");
focus.select("text").text(d);
}
.line {
fill: none;
stroke: #ffab00;
stroke-width: 3;
}
.overlay {
fill: none;
pointer-events: all;
}
/* Style the dots by assigning a fill and stroke */
.dot {
fill: #ffab00;
stroke: #fff;
}
.focus circle {
fill: none;
stroke: steelblue;
}
<!-- Load in the d3 library -->
<script src="https://d3js.org/d3.v5.min.js"></script>
I have a bar chart see plunker the problem is that I would like to move the y-axis ticks to be at the middle left side of the rects but they appear on the top and end. and I cannot seem to move them without destroying the chart.
my code
var info = [{
name: "Walnuts",
value: 546546
}, {
name: "Almonds",
value: 456455
}
];
/* Set chart dimensions */
var width = 960,
height = 500,
margin = {
top: 10,
right: 10,
bottom: 20,
left: 60
};
//subtract margins
width = width - margin.left - margin.right;
height = height - margin.top - margin.bottom;
//sort data from highest to lowest
info = info.sort(function(a, b) {
return b.value - a.value;
});
//Sets the y scale from 0 to the maximum data element
var max_n = 0;
var category = []
for (var d in info) {
max_n = Math.max(info[d].value, max_n);
category.push(info[d].name)
}
var dx = width / max_n;
var dy = height / info.length;
var y = d3.scale.ordinal()
.domain(category)
.range([0, height]);
var yAxis = d3.svg.axis()
.scale(y)
.orient('left')
var svg = d3.select("#chart")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr('preserveAspectRatio', 'xMidYMin')
.attr("viewBox", '0 0 ' + parseInt(width + margin.left + margin.right) + ' ' + parseInt(height + margin.top + margin.bottom))
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.selectAll(".bar")
.data(info)
.enter()
.append("rect")
.attr("class", function(d, i) {
return "bar" + d.name;
})
.attr("x", function(d, i) {
return 0;
})
.attr("y", function(d, i) {
return dy * i;
})
.attr("width", function(d, i) {
return dx * d.value
})
.attr("height", dy)
.attr("fill", function(d, i) {
if (d.name == 'Walnuts') {
return 'red'
} else {
return 'green'
}
});
var y_xis = svg.append('g')
.attr('id', 'yaxis')
.call(yAxis);
You are using range in y axis like this:
var y = d3.scale.ordinal()
.domain(category)
.range([0, height]);
You should be using 'rangeRoundBands' since the y scale is ordinal
var y = d3.scale.ordinal()
.domain(category)
.rangeRoundBands([0, height], .1);
working code here
For d3 versions like v4/v5.
Defining height as the graph/plot height, and max as the maximum value of y.
import { parseSvg } from 'd3-interpolate/src/transform/parse'
const yScale = d3
.scaleLinear()
.domain([0, max])
.rangeRound([height, 0])
const yAxis = d3.axisLeft(yScale)
svg
.append('g')
.call(yAxis)
.selectAll('.tick')
.each(function(data) {
const tick = d3.select(this)
const { translateX, translateY } = parseSvg(tick.attr('transform'))
tick.attr(
'transform',
translate(translateX, translateY + height / (2 * max))
)
})
Recently I needed something very very similar and I solved this with a call with selecting all text elements in the selection and moving their dy upwards. I will give an example with OP's code:
var y_xis = svg.append('g')
.attr('id','yaxis')
.call(yAxis)
.call(selection => selection
.selectAll('text')
.attr('dy', '-110') // this moves the text labels upwards
.attr('x', '110')); // this does the same job but horizontally