Update pattern for subplots

Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
1
down vote
favorite
I'm working on a d3 project that has multiple sub-plots with updating data. I have a working example below, but I think I might be over complicating the update() pattern. I didn't include an exit() because the data is consistent (there's always the same amount of divisions, teams and values).
d3 experts: does this look right to you? What would you modify to make it more performant / maintainable? Should I invoke a function for each component of the chart? e.g. a function for building the division charts, a function for building the team charts, a function for building the bars, etc.
I'm interested in learning how to write clean maintainable d3 code, so please pass along any resources if you have any.
const dataset1 = [
"division": "division 1", "team": "team 1", "value": 5,
"division": "division 1", "team": "team 1", "value": 7,
"division": "division 1", "team": "team 1", "value": 9,
"division": "division 1", "team": "team 2", "value": 2,
"division": "division 1", "team": "team 2", "value": 1,
"division": "division 1", "team": "team 2", "value": 1,
"division": "division 1", "team": "team 3", "value": 3,
"division": "division 1", "team": "team 3", "value": 1,
"division": "division 1", "team": "team 3", "value": 7,
"division": "division 2", "team": "team 1", "value": 2,
"division": "division 2", "team": "team 1", "value": 7,
"division": "division 2", "team": "team 1", "value": 3,
"division": "division 2", "team": "team 2", "value": 6,
"division": "division 2", "team": "team 2", "value": 3,
"division": "division 2", "team": "team 2", "value": 2,
"division": "division 2", "team": "team 3", "value": 3,
"division": "division 2", "team": "team 3", "value": 9,
"division": "division 2", "team": "team 3", "value": 5,
]
const dataset2 = [
"division": "division 1", "team": "team 1", "value": 1,
"division": "division 1", "team": "team 1", "value": 4,
"division": "division 1", "team": "team 1", "value": 3,
"division": "division 1", "team": "team 2", "value": 6,
"division": "division 1", "team": "team 2", "value": 2,
"division": "division 1", "team": "team 2", "value": 9,
"division": "division 1", "team": "team 3", "value": 5,
"division": "division 1", "team": "team 3", "value": 2,
"division": "division 1", "team": "team 3", "value": 3,
"division": "division 2", "team": "team 1", "value": 8,
"division": "division 2", "team": "team 1", "value": 1,
"division": "division 2", "team": "team 1", "value": 2,
"division": "division 2", "team": "team 2", "value": 5,
"division": "division 2", "team": "team 2", "value": 3,
"division": "division 2", "team": "team 2", "value": 7,
"division": "division 2", "team": "team 3", "value": 4,
"division": "division 2", "team": "team 3", "value": 3,
"division": "division 2", "team": "team 3", "value": 8,
]
function updateChart(data)
// Divisions
var divisions = d3.nest().key(d=>d.division).entries(data);
var divisionCharts = d3.select("body").selectAll("svg").data(divisions, d=>d.key);
var divisionChartsEnter = divisionCharts.enter().append("svg").attr("width", 200).attr("height", 75);
// Teams
var teamCharts = divisionCharts.merge(divisionChartsEnter).selectAll("svg")
.data(d => d3.nest().key(d=>d.team).entries(d.values), d=>d.key)
var teamChartsEnter = teamCharts.enter().append("svg").attr("x", (d,i) => i*50);
// Values for each team
var bars = teamCharts.merge(teamChartsEnter).selectAll("rect").data(d => d.values);
var barsEnter = bars.enter()
.append("rect")
.attr("x", (d,i)=>i*12)
.attr("width", 10)
bars.merge(barsEnter)
.transition()
.attr("height", d=>d.value*10)
// Toggle dataset
currDataset = data == dataset1 ? dataset2 : dataset1;
let currDataset = dataset1;
updateChart(currDataset);
d3.select("#updateData").on("click", () => updateChart(currDataset))<p><button id="updateData">Update Data</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>javascript d3.js
add a comment |Â
up vote
1
down vote
favorite
I'm working on a d3 project that has multiple sub-plots with updating data. I have a working example below, but I think I might be over complicating the update() pattern. I didn't include an exit() because the data is consistent (there's always the same amount of divisions, teams and values).
d3 experts: does this look right to you? What would you modify to make it more performant / maintainable? Should I invoke a function for each component of the chart? e.g. a function for building the division charts, a function for building the team charts, a function for building the bars, etc.
I'm interested in learning how to write clean maintainable d3 code, so please pass along any resources if you have any.
const dataset1 = [
"division": "division 1", "team": "team 1", "value": 5,
"division": "division 1", "team": "team 1", "value": 7,
"division": "division 1", "team": "team 1", "value": 9,
"division": "division 1", "team": "team 2", "value": 2,
"division": "division 1", "team": "team 2", "value": 1,
"division": "division 1", "team": "team 2", "value": 1,
"division": "division 1", "team": "team 3", "value": 3,
"division": "division 1", "team": "team 3", "value": 1,
"division": "division 1", "team": "team 3", "value": 7,
"division": "division 2", "team": "team 1", "value": 2,
"division": "division 2", "team": "team 1", "value": 7,
"division": "division 2", "team": "team 1", "value": 3,
"division": "division 2", "team": "team 2", "value": 6,
"division": "division 2", "team": "team 2", "value": 3,
"division": "division 2", "team": "team 2", "value": 2,
"division": "division 2", "team": "team 3", "value": 3,
"division": "division 2", "team": "team 3", "value": 9,
"division": "division 2", "team": "team 3", "value": 5,
]
const dataset2 = [
"division": "division 1", "team": "team 1", "value": 1,
"division": "division 1", "team": "team 1", "value": 4,
"division": "division 1", "team": "team 1", "value": 3,
"division": "division 1", "team": "team 2", "value": 6,
"division": "division 1", "team": "team 2", "value": 2,
"division": "division 1", "team": "team 2", "value": 9,
"division": "division 1", "team": "team 3", "value": 5,
"division": "division 1", "team": "team 3", "value": 2,
"division": "division 1", "team": "team 3", "value": 3,
"division": "division 2", "team": "team 1", "value": 8,
"division": "division 2", "team": "team 1", "value": 1,
"division": "division 2", "team": "team 1", "value": 2,
"division": "division 2", "team": "team 2", "value": 5,
"division": "division 2", "team": "team 2", "value": 3,
"division": "division 2", "team": "team 2", "value": 7,
"division": "division 2", "team": "team 3", "value": 4,
"division": "division 2", "team": "team 3", "value": 3,
"division": "division 2", "team": "team 3", "value": 8,
]
function updateChart(data)
// Divisions
var divisions = d3.nest().key(d=>d.division).entries(data);
var divisionCharts = d3.select("body").selectAll("svg").data(divisions, d=>d.key);
var divisionChartsEnter = divisionCharts.enter().append("svg").attr("width", 200).attr("height", 75);
// Teams
var teamCharts = divisionCharts.merge(divisionChartsEnter).selectAll("svg")
.data(d => d3.nest().key(d=>d.team).entries(d.values), d=>d.key)
var teamChartsEnter = teamCharts.enter().append("svg").attr("x", (d,i) => i*50);
// Values for each team
var bars = teamCharts.merge(teamChartsEnter).selectAll("rect").data(d => d.values);
var barsEnter = bars.enter()
.append("rect")
.attr("x", (d,i)=>i*12)
.attr("width", 10)
bars.merge(barsEnter)
.transition()
.attr("height", d=>d.value*10)
// Toggle dataset
currDataset = data == dataset1 ? dataset2 : dataset1;
let currDataset = dataset1;
updateChart(currDataset);
d3.select("#updateData").on("click", () => updateChart(currDataset))<p><button id="updateData">Update Data</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>javascript d3.js
add a comment |Â
up vote
1
down vote
favorite
up vote
1
down vote
favorite
I'm working on a d3 project that has multiple sub-plots with updating data. I have a working example below, but I think I might be over complicating the update() pattern. I didn't include an exit() because the data is consistent (there's always the same amount of divisions, teams and values).
d3 experts: does this look right to you? What would you modify to make it more performant / maintainable? Should I invoke a function for each component of the chart? e.g. a function for building the division charts, a function for building the team charts, a function for building the bars, etc.
I'm interested in learning how to write clean maintainable d3 code, so please pass along any resources if you have any.
const dataset1 = [
"division": "division 1", "team": "team 1", "value": 5,
"division": "division 1", "team": "team 1", "value": 7,
"division": "division 1", "team": "team 1", "value": 9,
"division": "division 1", "team": "team 2", "value": 2,
"division": "division 1", "team": "team 2", "value": 1,
"division": "division 1", "team": "team 2", "value": 1,
"division": "division 1", "team": "team 3", "value": 3,
"division": "division 1", "team": "team 3", "value": 1,
"division": "division 1", "team": "team 3", "value": 7,
"division": "division 2", "team": "team 1", "value": 2,
"division": "division 2", "team": "team 1", "value": 7,
"division": "division 2", "team": "team 1", "value": 3,
"division": "division 2", "team": "team 2", "value": 6,
"division": "division 2", "team": "team 2", "value": 3,
"division": "division 2", "team": "team 2", "value": 2,
"division": "division 2", "team": "team 3", "value": 3,
"division": "division 2", "team": "team 3", "value": 9,
"division": "division 2", "team": "team 3", "value": 5,
]
const dataset2 = [
"division": "division 1", "team": "team 1", "value": 1,
"division": "division 1", "team": "team 1", "value": 4,
"division": "division 1", "team": "team 1", "value": 3,
"division": "division 1", "team": "team 2", "value": 6,
"division": "division 1", "team": "team 2", "value": 2,
"division": "division 1", "team": "team 2", "value": 9,
"division": "division 1", "team": "team 3", "value": 5,
"division": "division 1", "team": "team 3", "value": 2,
"division": "division 1", "team": "team 3", "value": 3,
"division": "division 2", "team": "team 1", "value": 8,
"division": "division 2", "team": "team 1", "value": 1,
"division": "division 2", "team": "team 1", "value": 2,
"division": "division 2", "team": "team 2", "value": 5,
"division": "division 2", "team": "team 2", "value": 3,
"division": "division 2", "team": "team 2", "value": 7,
"division": "division 2", "team": "team 3", "value": 4,
"division": "division 2", "team": "team 3", "value": 3,
"division": "division 2", "team": "team 3", "value": 8,
]
function updateChart(data)
// Divisions
var divisions = d3.nest().key(d=>d.division).entries(data);
var divisionCharts = d3.select("body").selectAll("svg").data(divisions, d=>d.key);
var divisionChartsEnter = divisionCharts.enter().append("svg").attr("width", 200).attr("height", 75);
// Teams
var teamCharts = divisionCharts.merge(divisionChartsEnter).selectAll("svg")
.data(d => d3.nest().key(d=>d.team).entries(d.values), d=>d.key)
var teamChartsEnter = teamCharts.enter().append("svg").attr("x", (d,i) => i*50);
// Values for each team
var bars = teamCharts.merge(teamChartsEnter).selectAll("rect").data(d => d.values);
var barsEnter = bars.enter()
.append("rect")
.attr("x", (d,i)=>i*12)
.attr("width", 10)
bars.merge(barsEnter)
.transition()
.attr("height", d=>d.value*10)
// Toggle dataset
currDataset = data == dataset1 ? dataset2 : dataset1;
let currDataset = dataset1;
updateChart(currDataset);
d3.select("#updateData").on("click", () => updateChart(currDataset))<p><button id="updateData">Update Data</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>javascript d3.js
I'm working on a d3 project that has multiple sub-plots with updating data. I have a working example below, but I think I might be over complicating the update() pattern. I didn't include an exit() because the data is consistent (there's always the same amount of divisions, teams and values).
d3 experts: does this look right to you? What would you modify to make it more performant / maintainable? Should I invoke a function for each component of the chart? e.g. a function for building the division charts, a function for building the team charts, a function for building the bars, etc.
I'm interested in learning how to write clean maintainable d3 code, so please pass along any resources if you have any.
const dataset1 = [
"division": "division 1", "team": "team 1", "value": 5,
"division": "division 1", "team": "team 1", "value": 7,
"division": "division 1", "team": "team 1", "value": 9,
"division": "division 1", "team": "team 2", "value": 2,
"division": "division 1", "team": "team 2", "value": 1,
"division": "division 1", "team": "team 2", "value": 1,
"division": "division 1", "team": "team 3", "value": 3,
"division": "division 1", "team": "team 3", "value": 1,
"division": "division 1", "team": "team 3", "value": 7,
"division": "division 2", "team": "team 1", "value": 2,
"division": "division 2", "team": "team 1", "value": 7,
"division": "division 2", "team": "team 1", "value": 3,
"division": "division 2", "team": "team 2", "value": 6,
"division": "division 2", "team": "team 2", "value": 3,
"division": "division 2", "team": "team 2", "value": 2,
"division": "division 2", "team": "team 3", "value": 3,
"division": "division 2", "team": "team 3", "value": 9,
"division": "division 2", "team": "team 3", "value": 5,
]
const dataset2 = [
"division": "division 1", "team": "team 1", "value": 1,
"division": "division 1", "team": "team 1", "value": 4,
"division": "division 1", "team": "team 1", "value": 3,
"division": "division 1", "team": "team 2", "value": 6,
"division": "division 1", "team": "team 2", "value": 2,
"division": "division 1", "team": "team 2", "value": 9,
"division": "division 1", "team": "team 3", "value": 5,
"division": "division 1", "team": "team 3", "value": 2,
"division": "division 1", "team": "team 3", "value": 3,
"division": "division 2", "team": "team 1", "value": 8,
"division": "division 2", "team": "team 1", "value": 1,
"division": "division 2", "team": "team 1", "value": 2,
"division": "division 2", "team": "team 2", "value": 5,
"division": "division 2", "team": "team 2", "value": 3,
"division": "division 2", "team": "team 2", "value": 7,
"division": "division 2", "team": "team 3", "value": 4,
"division": "division 2", "team": "team 3", "value": 3,
"division": "division 2", "team": "team 3", "value": 8,
]
function updateChart(data)
// Divisions
var divisions = d3.nest().key(d=>d.division).entries(data);
var divisionCharts = d3.select("body").selectAll("svg").data(divisions, d=>d.key);
var divisionChartsEnter = divisionCharts.enter().append("svg").attr("width", 200).attr("height", 75);
// Teams
var teamCharts = divisionCharts.merge(divisionChartsEnter).selectAll("svg")
.data(d => d3.nest().key(d=>d.team).entries(d.values), d=>d.key)
var teamChartsEnter = teamCharts.enter().append("svg").attr("x", (d,i) => i*50);
// Values for each team
var bars = teamCharts.merge(teamChartsEnter).selectAll("rect").data(d => d.values);
var barsEnter = bars.enter()
.append("rect")
.attr("x", (d,i)=>i*12)
.attr("width", 10)
bars.merge(barsEnter)
.transition()
.attr("height", d=>d.value*10)
// Toggle dataset
currDataset = data == dataset1 ? dataset2 : dataset1;
let currDataset = dataset1;
updateChart(currDataset);
d3.select("#updateData").on("click", () => updateChart(currDataset))<p><button id="updateData">Update Data</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>const dataset1 = [
"division": "division 1", "team": "team 1", "value": 5,
"division": "division 1", "team": "team 1", "value": 7,
"division": "division 1", "team": "team 1", "value": 9,
"division": "division 1", "team": "team 2", "value": 2,
"division": "division 1", "team": "team 2", "value": 1,
"division": "division 1", "team": "team 2", "value": 1,
"division": "division 1", "team": "team 3", "value": 3,
"division": "division 1", "team": "team 3", "value": 1,
"division": "division 1", "team": "team 3", "value": 7,
"division": "division 2", "team": "team 1", "value": 2,
"division": "division 2", "team": "team 1", "value": 7,
"division": "division 2", "team": "team 1", "value": 3,
"division": "division 2", "team": "team 2", "value": 6,
"division": "division 2", "team": "team 2", "value": 3,
"division": "division 2", "team": "team 2", "value": 2,
"division": "division 2", "team": "team 3", "value": 3,
"division": "division 2", "team": "team 3", "value": 9,
"division": "division 2", "team": "team 3", "value": 5,
]
const dataset2 = [
"division": "division 1", "team": "team 1", "value": 1,
"division": "division 1", "team": "team 1", "value": 4,
"division": "division 1", "team": "team 1", "value": 3,
"division": "division 1", "team": "team 2", "value": 6,
"division": "division 1", "team": "team 2", "value": 2,
"division": "division 1", "team": "team 2", "value": 9,
"division": "division 1", "team": "team 3", "value": 5,
"division": "division 1", "team": "team 3", "value": 2,
"division": "division 1", "team": "team 3", "value": 3,
"division": "division 2", "team": "team 1", "value": 8,
"division": "division 2", "team": "team 1", "value": 1,
"division": "division 2", "team": "team 1", "value": 2,
"division": "division 2", "team": "team 2", "value": 5,
"division": "division 2", "team": "team 2", "value": 3,
"division": "division 2", "team": "team 2", "value": 7,
"division": "division 2", "team": "team 3", "value": 4,
"division": "division 2", "team": "team 3", "value": 3,
"division": "division 2", "team": "team 3", "value": 8,
]
function updateChart(data)
// Divisions
var divisions = d3.nest().key(d=>d.division).entries(data);
var divisionCharts = d3.select("body").selectAll("svg").data(divisions, d=>d.key);
var divisionChartsEnter = divisionCharts.enter().append("svg").attr("width", 200).attr("height", 75);
// Teams
var teamCharts = divisionCharts.merge(divisionChartsEnter).selectAll("svg")
.data(d => d3.nest().key(d=>d.team).entries(d.values), d=>d.key)
var teamChartsEnter = teamCharts.enter().append("svg").attr("x", (d,i) => i*50);
// Values for each team
var bars = teamCharts.merge(teamChartsEnter).selectAll("rect").data(d => d.values);
var barsEnter = bars.enter()
.append("rect")
.attr("x", (d,i)=>i*12)
.attr("width", 10)
bars.merge(barsEnter)
.transition()
.attr("height", d=>d.value*10)
// Toggle dataset
currDataset = data == dataset1 ? dataset2 : dataset1;
let currDataset = dataset1;
updateChart(currDataset);
d3.select("#updateData").on("click", () => updateChart(currDataset))<p><button id="updateData">Update Data</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>const dataset1 = [
"division": "division 1", "team": "team 1", "value": 5,
"division": "division 1", "team": "team 1", "value": 7,
"division": "division 1", "team": "team 1", "value": 9,
"division": "division 1", "team": "team 2", "value": 2,
"division": "division 1", "team": "team 2", "value": 1,
"division": "division 1", "team": "team 2", "value": 1,
"division": "division 1", "team": "team 3", "value": 3,
"division": "division 1", "team": "team 3", "value": 1,
"division": "division 1", "team": "team 3", "value": 7,
"division": "division 2", "team": "team 1", "value": 2,
"division": "division 2", "team": "team 1", "value": 7,
"division": "division 2", "team": "team 1", "value": 3,
"division": "division 2", "team": "team 2", "value": 6,
"division": "division 2", "team": "team 2", "value": 3,
"division": "division 2", "team": "team 2", "value": 2,
"division": "division 2", "team": "team 3", "value": 3,
"division": "division 2", "team": "team 3", "value": 9,
"division": "division 2", "team": "team 3", "value": 5,
]
const dataset2 = [
"division": "division 1", "team": "team 1", "value": 1,
"division": "division 1", "team": "team 1", "value": 4,
"division": "division 1", "team": "team 1", "value": 3,
"division": "division 1", "team": "team 2", "value": 6,
"division": "division 1", "team": "team 2", "value": 2,
"division": "division 1", "team": "team 2", "value": 9,
"division": "division 1", "team": "team 3", "value": 5,
"division": "division 1", "team": "team 3", "value": 2,
"division": "division 1", "team": "team 3", "value": 3,
"division": "division 2", "team": "team 1", "value": 8,
"division": "division 2", "team": "team 1", "value": 1,
"division": "division 2", "team": "team 1", "value": 2,
"division": "division 2", "team": "team 2", "value": 5,
"division": "division 2", "team": "team 2", "value": 3,
"division": "division 2", "team": "team 2", "value": 7,
"division": "division 2", "team": "team 3", "value": 4,
"division": "division 2", "team": "team 3", "value": 3,
"division": "division 2", "team": "team 3", "value": 8,
]
function updateChart(data)
// Divisions
var divisions = d3.nest().key(d=>d.division).entries(data);
var divisionCharts = d3.select("body").selectAll("svg").data(divisions, d=>d.key);
var divisionChartsEnter = divisionCharts.enter().append("svg").attr("width", 200).attr("height", 75);
// Teams
var teamCharts = divisionCharts.merge(divisionChartsEnter).selectAll("svg")
.data(d => d3.nest().key(d=>d.team).entries(d.values), d=>d.key)
var teamChartsEnter = teamCharts.enter().append("svg").attr("x", (d,i) => i*50);
// Values for each team
var bars = teamCharts.merge(teamChartsEnter).selectAll("rect").data(d => d.values);
var barsEnter = bars.enter()
.append("rect")
.attr("x", (d,i)=>i*12)
.attr("width", 10)
bars.merge(barsEnter)
.transition()
.attr("height", d=>d.value*10)
// Toggle dataset
currDataset = data == dataset1 ? dataset2 : dataset1;
let currDataset = dataset1;
updateChart(currDataset);
d3.select("#updateData").on("click", () => updateChart(currDataset))<p><button id="updateData">Update Data</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>javascript d3.js
edited Jul 19 at 3:36
Stephen Rauch
3,49951430
3,49951430
asked Jul 19 at 2:26
Jeff Appareti
82
82
add a comment |Â
add a comment |Â
1 Answer
1
active
oldest
votes
up vote
1
down vote
accepted
Overall, your update function is correct. You're appending several SVGs to the HTML, which is a good and very common approach when creating small multiples. Also, you have a correct (almost, see below) understanding of the update pattern and the merge() method.
Major issues
1. Merging the selections
As I said, you get the principle of merging selections right, but some details are wrong. For instance, you don't need the following selectAll in the merged selection.
So, they should be something like this (using the first one as example):
var divisions = d3.nest()
.key(d => d.division)
.entries(data);
var divisionCharts = container.selectAll("svg")
.data(divisions, d => d.key);
var divisionChartsEnter = divisionCharts.enter()
.append("svg")
.attr("width", width)
.attr("height", height);
divisionCharts = divisionCharts.merge(divisionChartsEnter);
And do the same for the other merged selections.
2. Appending SVGs inside SVGs
As stated above, appending multiple SVGs is a very common approach when creating small multiples. However, inside each SVG you're creating nested SVGs, one for each team... not only this is uncommon and unnecessary, but also can lead to some problems. The common approach here is creating groups.
So, instead of SVGs, let's groups (<g>) for each team:
var teamCharts = divisionCharts.selectAll("g")
.data(d => d3.nest()
.key(d => d.team)
.entries(d.values), d => d.key);
var teamChartsEnter = teamCharts.enter()
.append("g")
.attr("transform", (d) => "translate(" + xScaleTeam(d.key) + ",0)");
teamCharts = teamCharts.merge(teamChartsEnter);
Since you're appending groups you have to translate them, instead of setting the x attribute.
3. Use scales!
This is a very important advice: use scales in a D3 code. The way you're doing right now (using absolute values and indices for positioning and size) is not correct and, the most important, it will be a hell to debug/maintain. Actually, if you look at the snippets above, you'll see that I'm already using scales on them.
So, create proper scales. In your case:
var xScaleTeam = d3.scaleBand()
.range([0, width])
.domain([...new Set(dataset1.map(function(d)
return d.team
))])
.paddingOuter(0.4);
var xScaleBars = d3.scaleBand()
.range([0, xScaleTeam.bandwidth()])
.domain(d3.range(3))
.paddingOuter(0.4);
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max([d3.max(dataset1, function(d)
return d.value
), d3.max(dataset2, function(d)
return d.value
)])]);
xScaleTeam is the outer x scale for each small multiple, xScaleBars is the inner x scale (for each team) and yScale is, obviously, the y scale.
4. Bars going up
Maybe I'm wrong, but it seems to me that you want the bar chart going up, as almost all bar charts around. That being the case, just do:
bars.attr("y", d => yScale(d.value))
.attr("height", d => height - yScale(d.value))
Minor issues
Don't append the SVGs directly to the body. Instead of that, select a specific element, like a
<div>withcontainerid:var container = d3.select("#container");This code works just fine if you reference the new v5 version, instead of v4 (which you're using right now). Therefore, change to v5.
Don't use magic numbers. For instance, define the width and height:
var width = 200,
height = 75;Move the toggle logic to outside the
updatefunction: that way, you have a function that depends only on the arguments it receives.As a further advice: color the bars by team. It is simple as:
var color = d3.scaleOrdinal(d3.schemeCategory10);And then:
teamChartsEnter.style("fill", (d)=> color(d.key));
Demo
Here is your code with all those changes:
const dataset1 = [
"division": "division 1",
"team": "team 1",
"value": 5
,
"division": "division 1",
"team": "team 1",
"value": 7
,
"division": "division 1",
"team": "team 1",
"value": 9
,
"division": "division 1",
"team": "team 2",
"value": 2
,
"division": "division 1",
"team": "team 2",
"value": 1
,
"division": "division 1",
"team": "team 2",
"value": 1
,
"division": "division 1",
"team": "team 3",
"value": 3
,
"division": "division 1",
"team": "team 3",
"value": 1
,
"division": "division 1",
"team": "team 3",
"value": 7
,
"division": "division 2",
"team": "team 1",
"value": 2
,
"division": "division 2",
"team": "team 1",
"value": 7
,
"division": "division 2",
"team": "team 1",
"value": 3
,
"division": "division 2",
"team": "team 2",
"value": 6
,
"division": "division 2",
"team": "team 2",
"value": 3
,
"division": "division 2",
"team": "team 2",
"value": 2
,
"division": "division 2",
"team": "team 3",
"value": 3
,
"division": "division 2",
"team": "team 3",
"value": 9
,
"division": "division 2",
"team": "team 3",
"value": 5
,
]
const dataset2 = [
"division": "division 1",
"team": "team 1",
"value": 1
,
"division": "division 1",
"team": "team 1",
"value": 4
,
"division": "division 1",
"team": "team 1",
"value": 3
,
"division": "division 1",
"team": "team 2",
"value": 6
,
"division": "division 1",
"team": "team 2",
"value": 2
,
"division": "division 1",
"team": "team 2",
"value": 9
,
"division": "division 1",
"team": "team 3",
"value": 5
,
"division": "division 1",
"team": "team 3",
"value": 2
,
"division": "division 1",
"team": "team 3",
"value": 3
,
"division": "division 2",
"team": "team 1",
"value": 8
,
"division": "division 2",
"team": "team 1",
"value": 1
,
"division": "division 2",
"team": "team 1",
"value": 2
,
"division": "division 2",
"team": "team 2",
"value": 5
,
"division": "division 2",
"team": "team 2",
"value": 3
,
"division": "division 2",
"team": "team 2",
"value": 7
,
"division": "division 2",
"team": "team 3",
"value": 4
,
"division": "division 2",
"team": "team 3",
"value": 3
,
"division": "division 2",
"team": "team 3",
"value": 8
,
];
var width = 200,
height = 75;
var container = d3.select("#container");
var color = d3.scaleOrdinal(d3.schemeCategory10);
var xScaleTeam = d3.scaleBand()
.range([0, width])
.domain([...new Set(dataset1.map(function(d)
return d.team
))])
.paddingOuter(0.4);
var xScaleBars = d3.scaleBand()
.range([0, xScaleTeam.bandwidth()])
.domain(d3.range(3))
.paddingOuter(0.4);
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max([d3.max(dataset1, function(d)
return d.value
), d3.max(dataset2, function(d)
return d.value
)])]);
function updateChart(data)
// Divisions
var divisions = d3.nest().key(d => d.division).entries(data);
var divisionCharts = container.selectAll("svg").data(divisions, d => d.key);
var divisionChartsEnter = divisionCharts.enter().append("svg").attr("width", width).attr("height", height);
divisionCharts = divisionCharts.merge(divisionChartsEnter);
// Teams
var teamCharts = divisionCharts.selectAll("g")
.data(d => d3.nest().key(d => d.team).entries(d.values), d => d.key);
var teamChartsEnter = teamCharts.enter().append("g").attr("transform", (d) => "translate(" + xScaleTeam(d.key) + ",0)")
.style("fill", (d) => color(d.key));
teamCharts = teamCharts.merge(teamChartsEnter);
// Values for each team
var bars = teamCharts.selectAll("rect").data(d => d.values);
var barsEnter = bars.enter()
.append("rect")
.attr("x", (d, i) => xScaleBars(i))
.attr("width", 10)
.attr("y", height)
bars.merge(barsEnter)
.transition()
.attr("y", d => yScale(d.value))
.attr("height", d => height - yScale(d.value))
let currDataset = dataset1;
updateChart(currDataset);
d3.select("#updateData").on("click", () =>
currDataset = currDataset === dataset1 ? dataset2 : dataset1;
updateChart(currDataset);
)<p><button id="updateData">Update Data</button>
<div id="container"></div>
<script src="https://d3js.org/d3.v5.min.js"></script>
Thank you so much! This is very helpful. Glad to know that I'm (mostly) on the right track. I didn't know about the problems with nestedsvgs so I'll usegs instead. Another question: is there any advantage to nesting the data separately like the sample code? Or would it be better to double nest it like this:var dataNested = d3.nest().key(d => d.division).key(d => d.team)entries(data);
â Jeff Appareti
Jul 19 at 22:15
It makes very little difference. But for readability I'd do two separate nests.
â Gerardo Furtado
Jul 19 at 22:47
add a comment |Â
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
1
down vote
accepted
Overall, your update function is correct. You're appending several SVGs to the HTML, which is a good and very common approach when creating small multiples. Also, you have a correct (almost, see below) understanding of the update pattern and the merge() method.
Major issues
1. Merging the selections
As I said, you get the principle of merging selections right, but some details are wrong. For instance, you don't need the following selectAll in the merged selection.
So, they should be something like this (using the first one as example):
var divisions = d3.nest()
.key(d => d.division)
.entries(data);
var divisionCharts = container.selectAll("svg")
.data(divisions, d => d.key);
var divisionChartsEnter = divisionCharts.enter()
.append("svg")
.attr("width", width)
.attr("height", height);
divisionCharts = divisionCharts.merge(divisionChartsEnter);
And do the same for the other merged selections.
2. Appending SVGs inside SVGs
As stated above, appending multiple SVGs is a very common approach when creating small multiples. However, inside each SVG you're creating nested SVGs, one for each team... not only this is uncommon and unnecessary, but also can lead to some problems. The common approach here is creating groups.
So, instead of SVGs, let's groups (<g>) for each team:
var teamCharts = divisionCharts.selectAll("g")
.data(d => d3.nest()
.key(d => d.team)
.entries(d.values), d => d.key);
var teamChartsEnter = teamCharts.enter()
.append("g")
.attr("transform", (d) => "translate(" + xScaleTeam(d.key) + ",0)");
teamCharts = teamCharts.merge(teamChartsEnter);
Since you're appending groups you have to translate them, instead of setting the x attribute.
3. Use scales!
This is a very important advice: use scales in a D3 code. The way you're doing right now (using absolute values and indices for positioning and size) is not correct and, the most important, it will be a hell to debug/maintain. Actually, if you look at the snippets above, you'll see that I'm already using scales on them.
So, create proper scales. In your case:
var xScaleTeam = d3.scaleBand()
.range([0, width])
.domain([...new Set(dataset1.map(function(d)
return d.team
))])
.paddingOuter(0.4);
var xScaleBars = d3.scaleBand()
.range([0, xScaleTeam.bandwidth()])
.domain(d3.range(3))
.paddingOuter(0.4);
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max([d3.max(dataset1, function(d)
return d.value
), d3.max(dataset2, function(d)
return d.value
)])]);
xScaleTeam is the outer x scale for each small multiple, xScaleBars is the inner x scale (for each team) and yScale is, obviously, the y scale.
4. Bars going up
Maybe I'm wrong, but it seems to me that you want the bar chart going up, as almost all bar charts around. That being the case, just do:
bars.attr("y", d => yScale(d.value))
.attr("height", d => height - yScale(d.value))
Minor issues
Don't append the SVGs directly to the body. Instead of that, select a specific element, like a
<div>withcontainerid:var container = d3.select("#container");This code works just fine if you reference the new v5 version, instead of v4 (which you're using right now). Therefore, change to v5.
Don't use magic numbers. For instance, define the width and height:
var width = 200,
height = 75;Move the toggle logic to outside the
updatefunction: that way, you have a function that depends only on the arguments it receives.As a further advice: color the bars by team. It is simple as:
var color = d3.scaleOrdinal(d3.schemeCategory10);And then:
teamChartsEnter.style("fill", (d)=> color(d.key));
Demo
Here is your code with all those changes:
const dataset1 = [
"division": "division 1",
"team": "team 1",
"value": 5
,
"division": "division 1",
"team": "team 1",
"value": 7
,
"division": "division 1",
"team": "team 1",
"value": 9
,
"division": "division 1",
"team": "team 2",
"value": 2
,
"division": "division 1",
"team": "team 2",
"value": 1
,
"division": "division 1",
"team": "team 2",
"value": 1
,
"division": "division 1",
"team": "team 3",
"value": 3
,
"division": "division 1",
"team": "team 3",
"value": 1
,
"division": "division 1",
"team": "team 3",
"value": 7
,
"division": "division 2",
"team": "team 1",
"value": 2
,
"division": "division 2",
"team": "team 1",
"value": 7
,
"division": "division 2",
"team": "team 1",
"value": 3
,
"division": "division 2",
"team": "team 2",
"value": 6
,
"division": "division 2",
"team": "team 2",
"value": 3
,
"division": "division 2",
"team": "team 2",
"value": 2
,
"division": "division 2",
"team": "team 3",
"value": 3
,
"division": "division 2",
"team": "team 3",
"value": 9
,
"division": "division 2",
"team": "team 3",
"value": 5
,
]
const dataset2 = [
"division": "division 1",
"team": "team 1",
"value": 1
,
"division": "division 1",
"team": "team 1",
"value": 4
,
"division": "division 1",
"team": "team 1",
"value": 3
,
"division": "division 1",
"team": "team 2",
"value": 6
,
"division": "division 1",
"team": "team 2",
"value": 2
,
"division": "division 1",
"team": "team 2",
"value": 9
,
"division": "division 1",
"team": "team 3",
"value": 5
,
"division": "division 1",
"team": "team 3",
"value": 2
,
"division": "division 1",
"team": "team 3",
"value": 3
,
"division": "division 2",
"team": "team 1",
"value": 8
,
"division": "division 2",
"team": "team 1",
"value": 1
,
"division": "division 2",
"team": "team 1",
"value": 2
,
"division": "division 2",
"team": "team 2",
"value": 5
,
"division": "division 2",
"team": "team 2",
"value": 3
,
"division": "division 2",
"team": "team 2",
"value": 7
,
"division": "division 2",
"team": "team 3",
"value": 4
,
"division": "division 2",
"team": "team 3",
"value": 3
,
"division": "division 2",
"team": "team 3",
"value": 8
,
];
var width = 200,
height = 75;
var container = d3.select("#container");
var color = d3.scaleOrdinal(d3.schemeCategory10);
var xScaleTeam = d3.scaleBand()
.range([0, width])
.domain([...new Set(dataset1.map(function(d)
return d.team
))])
.paddingOuter(0.4);
var xScaleBars = d3.scaleBand()
.range([0, xScaleTeam.bandwidth()])
.domain(d3.range(3))
.paddingOuter(0.4);
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max([d3.max(dataset1, function(d)
return d.value
), d3.max(dataset2, function(d)
return d.value
)])]);
function updateChart(data)
// Divisions
var divisions = d3.nest().key(d => d.division).entries(data);
var divisionCharts = container.selectAll("svg").data(divisions, d => d.key);
var divisionChartsEnter = divisionCharts.enter().append("svg").attr("width", width).attr("height", height);
divisionCharts = divisionCharts.merge(divisionChartsEnter);
// Teams
var teamCharts = divisionCharts.selectAll("g")
.data(d => d3.nest().key(d => d.team).entries(d.values), d => d.key);
var teamChartsEnter = teamCharts.enter().append("g").attr("transform", (d) => "translate(" + xScaleTeam(d.key) + ",0)")
.style("fill", (d) => color(d.key));
teamCharts = teamCharts.merge(teamChartsEnter);
// Values for each team
var bars = teamCharts.selectAll("rect").data(d => d.values);
var barsEnter = bars.enter()
.append("rect")
.attr("x", (d, i) => xScaleBars(i))
.attr("width", 10)
.attr("y", height)
bars.merge(barsEnter)
.transition()
.attr("y", d => yScale(d.value))
.attr("height", d => height - yScale(d.value))
let currDataset = dataset1;
updateChart(currDataset);
d3.select("#updateData").on("click", () =>
currDataset = currDataset === dataset1 ? dataset2 : dataset1;
updateChart(currDataset);
)<p><button id="updateData">Update Data</button>
<div id="container"></div>
<script src="https://d3js.org/d3.v5.min.js"></script>
Thank you so much! This is very helpful. Glad to know that I'm (mostly) on the right track. I didn't know about the problems with nestedsvgs so I'll usegs instead. Another question: is there any advantage to nesting the data separately like the sample code? Or would it be better to double nest it like this:var dataNested = d3.nest().key(d => d.division).key(d => d.team)entries(data);
â Jeff Appareti
Jul 19 at 22:15
It makes very little difference. But for readability I'd do two separate nests.
â Gerardo Furtado
Jul 19 at 22:47
add a comment |Â
up vote
1
down vote
accepted
Overall, your update function is correct. You're appending several SVGs to the HTML, which is a good and very common approach when creating small multiples. Also, you have a correct (almost, see below) understanding of the update pattern and the merge() method.
Major issues
1. Merging the selections
As I said, you get the principle of merging selections right, but some details are wrong. For instance, you don't need the following selectAll in the merged selection.
So, they should be something like this (using the first one as example):
var divisions = d3.nest()
.key(d => d.division)
.entries(data);
var divisionCharts = container.selectAll("svg")
.data(divisions, d => d.key);
var divisionChartsEnter = divisionCharts.enter()
.append("svg")
.attr("width", width)
.attr("height", height);
divisionCharts = divisionCharts.merge(divisionChartsEnter);
And do the same for the other merged selections.
2. Appending SVGs inside SVGs
As stated above, appending multiple SVGs is a very common approach when creating small multiples. However, inside each SVG you're creating nested SVGs, one for each team... not only this is uncommon and unnecessary, but also can lead to some problems. The common approach here is creating groups.
So, instead of SVGs, let's groups (<g>) for each team:
var teamCharts = divisionCharts.selectAll("g")
.data(d => d3.nest()
.key(d => d.team)
.entries(d.values), d => d.key);
var teamChartsEnter = teamCharts.enter()
.append("g")
.attr("transform", (d) => "translate(" + xScaleTeam(d.key) + ",0)");
teamCharts = teamCharts.merge(teamChartsEnter);
Since you're appending groups you have to translate them, instead of setting the x attribute.
3. Use scales!
This is a very important advice: use scales in a D3 code. The way you're doing right now (using absolute values and indices for positioning and size) is not correct and, the most important, it will be a hell to debug/maintain. Actually, if you look at the snippets above, you'll see that I'm already using scales on them.
So, create proper scales. In your case:
var xScaleTeam = d3.scaleBand()
.range([0, width])
.domain([...new Set(dataset1.map(function(d)
return d.team
))])
.paddingOuter(0.4);
var xScaleBars = d3.scaleBand()
.range([0, xScaleTeam.bandwidth()])
.domain(d3.range(3))
.paddingOuter(0.4);
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max([d3.max(dataset1, function(d)
return d.value
), d3.max(dataset2, function(d)
return d.value
)])]);
xScaleTeam is the outer x scale for each small multiple, xScaleBars is the inner x scale (for each team) and yScale is, obviously, the y scale.
4. Bars going up
Maybe I'm wrong, but it seems to me that you want the bar chart going up, as almost all bar charts around. That being the case, just do:
bars.attr("y", d => yScale(d.value))
.attr("height", d => height - yScale(d.value))
Minor issues
Don't append the SVGs directly to the body. Instead of that, select a specific element, like a
<div>withcontainerid:var container = d3.select("#container");This code works just fine if you reference the new v5 version, instead of v4 (which you're using right now). Therefore, change to v5.
Don't use magic numbers. For instance, define the width and height:
var width = 200,
height = 75;Move the toggle logic to outside the
updatefunction: that way, you have a function that depends only on the arguments it receives.As a further advice: color the bars by team. It is simple as:
var color = d3.scaleOrdinal(d3.schemeCategory10);And then:
teamChartsEnter.style("fill", (d)=> color(d.key));
Demo
Here is your code with all those changes:
const dataset1 = [
"division": "division 1",
"team": "team 1",
"value": 5
,
"division": "division 1",
"team": "team 1",
"value": 7
,
"division": "division 1",
"team": "team 1",
"value": 9
,
"division": "division 1",
"team": "team 2",
"value": 2
,
"division": "division 1",
"team": "team 2",
"value": 1
,
"division": "division 1",
"team": "team 2",
"value": 1
,
"division": "division 1",
"team": "team 3",
"value": 3
,
"division": "division 1",
"team": "team 3",
"value": 1
,
"division": "division 1",
"team": "team 3",
"value": 7
,
"division": "division 2",
"team": "team 1",
"value": 2
,
"division": "division 2",
"team": "team 1",
"value": 7
,
"division": "division 2",
"team": "team 1",
"value": 3
,
"division": "division 2",
"team": "team 2",
"value": 6
,
"division": "division 2",
"team": "team 2",
"value": 3
,
"division": "division 2",
"team": "team 2",
"value": 2
,
"division": "division 2",
"team": "team 3",
"value": 3
,
"division": "division 2",
"team": "team 3",
"value": 9
,
"division": "division 2",
"team": "team 3",
"value": 5
,
]
const dataset2 = [
"division": "division 1",
"team": "team 1",
"value": 1
,
"division": "division 1",
"team": "team 1",
"value": 4
,
"division": "division 1",
"team": "team 1",
"value": 3
,
"division": "division 1",
"team": "team 2",
"value": 6
,
"division": "division 1",
"team": "team 2",
"value": 2
,
"division": "division 1",
"team": "team 2",
"value": 9
,
"division": "division 1",
"team": "team 3",
"value": 5
,
"division": "division 1",
"team": "team 3",
"value": 2
,
"division": "division 1",
"team": "team 3",
"value": 3
,
"division": "division 2",
"team": "team 1",
"value": 8
,
"division": "division 2",
"team": "team 1",
"value": 1
,
"division": "division 2",
"team": "team 1",
"value": 2
,
"division": "division 2",
"team": "team 2",
"value": 5
,
"division": "division 2",
"team": "team 2",
"value": 3
,
"division": "division 2",
"team": "team 2",
"value": 7
,
"division": "division 2",
"team": "team 3",
"value": 4
,
"division": "division 2",
"team": "team 3",
"value": 3
,
"division": "division 2",
"team": "team 3",
"value": 8
,
];
var width = 200,
height = 75;
var container = d3.select("#container");
var color = d3.scaleOrdinal(d3.schemeCategory10);
var xScaleTeam = d3.scaleBand()
.range([0, width])
.domain([...new Set(dataset1.map(function(d)
return d.team
))])
.paddingOuter(0.4);
var xScaleBars = d3.scaleBand()
.range([0, xScaleTeam.bandwidth()])
.domain(d3.range(3))
.paddingOuter(0.4);
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max([d3.max(dataset1, function(d)
return d.value
), d3.max(dataset2, function(d)
return d.value
)])]);
function updateChart(data)
// Divisions
var divisions = d3.nest().key(d => d.division).entries(data);
var divisionCharts = container.selectAll("svg").data(divisions, d => d.key);
var divisionChartsEnter = divisionCharts.enter().append("svg").attr("width", width).attr("height", height);
divisionCharts = divisionCharts.merge(divisionChartsEnter);
// Teams
var teamCharts = divisionCharts.selectAll("g")
.data(d => d3.nest().key(d => d.team).entries(d.values), d => d.key);
var teamChartsEnter = teamCharts.enter().append("g").attr("transform", (d) => "translate(" + xScaleTeam(d.key) + ",0)")
.style("fill", (d) => color(d.key));
teamCharts = teamCharts.merge(teamChartsEnter);
// Values for each team
var bars = teamCharts.selectAll("rect").data(d => d.values);
var barsEnter = bars.enter()
.append("rect")
.attr("x", (d, i) => xScaleBars(i))
.attr("width", 10)
.attr("y", height)
bars.merge(barsEnter)
.transition()
.attr("y", d => yScale(d.value))
.attr("height", d => height - yScale(d.value))
let currDataset = dataset1;
updateChart(currDataset);
d3.select("#updateData").on("click", () =>
currDataset = currDataset === dataset1 ? dataset2 : dataset1;
updateChart(currDataset);
)<p><button id="updateData">Update Data</button>
<div id="container"></div>
<script src="https://d3js.org/d3.v5.min.js"></script>
Thank you so much! This is very helpful. Glad to know that I'm (mostly) on the right track. I didn't know about the problems with nestedsvgs so I'll usegs instead. Another question: is there any advantage to nesting the data separately like the sample code? Or would it be better to double nest it like this:var dataNested = d3.nest().key(d => d.division).key(d => d.team)entries(data);
â Jeff Appareti
Jul 19 at 22:15
It makes very little difference. But for readability I'd do two separate nests.
â Gerardo Furtado
Jul 19 at 22:47
add a comment |Â
up vote
1
down vote
accepted
up vote
1
down vote
accepted
Overall, your update function is correct. You're appending several SVGs to the HTML, which is a good and very common approach when creating small multiples. Also, you have a correct (almost, see below) understanding of the update pattern and the merge() method.
Major issues
1. Merging the selections
As I said, you get the principle of merging selections right, but some details are wrong. For instance, you don't need the following selectAll in the merged selection.
So, they should be something like this (using the first one as example):
var divisions = d3.nest()
.key(d => d.division)
.entries(data);
var divisionCharts = container.selectAll("svg")
.data(divisions, d => d.key);
var divisionChartsEnter = divisionCharts.enter()
.append("svg")
.attr("width", width)
.attr("height", height);
divisionCharts = divisionCharts.merge(divisionChartsEnter);
And do the same for the other merged selections.
2. Appending SVGs inside SVGs
As stated above, appending multiple SVGs is a very common approach when creating small multiples. However, inside each SVG you're creating nested SVGs, one for each team... not only this is uncommon and unnecessary, but also can lead to some problems. The common approach here is creating groups.
So, instead of SVGs, let's groups (<g>) for each team:
var teamCharts = divisionCharts.selectAll("g")
.data(d => d3.nest()
.key(d => d.team)
.entries(d.values), d => d.key);
var teamChartsEnter = teamCharts.enter()
.append("g")
.attr("transform", (d) => "translate(" + xScaleTeam(d.key) + ",0)");
teamCharts = teamCharts.merge(teamChartsEnter);
Since you're appending groups you have to translate them, instead of setting the x attribute.
3. Use scales!
This is a very important advice: use scales in a D3 code. The way you're doing right now (using absolute values and indices for positioning and size) is not correct and, the most important, it will be a hell to debug/maintain. Actually, if you look at the snippets above, you'll see that I'm already using scales on them.
So, create proper scales. In your case:
var xScaleTeam = d3.scaleBand()
.range([0, width])
.domain([...new Set(dataset1.map(function(d)
return d.team
))])
.paddingOuter(0.4);
var xScaleBars = d3.scaleBand()
.range([0, xScaleTeam.bandwidth()])
.domain(d3.range(3))
.paddingOuter(0.4);
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max([d3.max(dataset1, function(d)
return d.value
), d3.max(dataset2, function(d)
return d.value
)])]);
xScaleTeam is the outer x scale for each small multiple, xScaleBars is the inner x scale (for each team) and yScale is, obviously, the y scale.
4. Bars going up
Maybe I'm wrong, but it seems to me that you want the bar chart going up, as almost all bar charts around. That being the case, just do:
bars.attr("y", d => yScale(d.value))
.attr("height", d => height - yScale(d.value))
Minor issues
Don't append the SVGs directly to the body. Instead of that, select a specific element, like a
<div>withcontainerid:var container = d3.select("#container");This code works just fine if you reference the new v5 version, instead of v4 (which you're using right now). Therefore, change to v5.
Don't use magic numbers. For instance, define the width and height:
var width = 200,
height = 75;Move the toggle logic to outside the
updatefunction: that way, you have a function that depends only on the arguments it receives.As a further advice: color the bars by team. It is simple as:
var color = d3.scaleOrdinal(d3.schemeCategory10);And then:
teamChartsEnter.style("fill", (d)=> color(d.key));
Demo
Here is your code with all those changes:
const dataset1 = [
"division": "division 1",
"team": "team 1",
"value": 5
,
"division": "division 1",
"team": "team 1",
"value": 7
,
"division": "division 1",
"team": "team 1",
"value": 9
,
"division": "division 1",
"team": "team 2",
"value": 2
,
"division": "division 1",
"team": "team 2",
"value": 1
,
"division": "division 1",
"team": "team 2",
"value": 1
,
"division": "division 1",
"team": "team 3",
"value": 3
,
"division": "division 1",
"team": "team 3",
"value": 1
,
"division": "division 1",
"team": "team 3",
"value": 7
,
"division": "division 2",
"team": "team 1",
"value": 2
,
"division": "division 2",
"team": "team 1",
"value": 7
,
"division": "division 2",
"team": "team 1",
"value": 3
,
"division": "division 2",
"team": "team 2",
"value": 6
,
"division": "division 2",
"team": "team 2",
"value": 3
,
"division": "division 2",
"team": "team 2",
"value": 2
,
"division": "division 2",
"team": "team 3",
"value": 3
,
"division": "division 2",
"team": "team 3",
"value": 9
,
"division": "division 2",
"team": "team 3",
"value": 5
,
]
const dataset2 = [
"division": "division 1",
"team": "team 1",
"value": 1
,
"division": "division 1",
"team": "team 1",
"value": 4
,
"division": "division 1",
"team": "team 1",
"value": 3
,
"division": "division 1",
"team": "team 2",
"value": 6
,
"division": "division 1",
"team": "team 2",
"value": 2
,
"division": "division 1",
"team": "team 2",
"value": 9
,
"division": "division 1",
"team": "team 3",
"value": 5
,
"division": "division 1",
"team": "team 3",
"value": 2
,
"division": "division 1",
"team": "team 3",
"value": 3
,
"division": "division 2",
"team": "team 1",
"value": 8
,
"division": "division 2",
"team": "team 1",
"value": 1
,
"division": "division 2",
"team": "team 1",
"value": 2
,
"division": "division 2",
"team": "team 2",
"value": 5
,
"division": "division 2",
"team": "team 2",
"value": 3
,
"division": "division 2",
"team": "team 2",
"value": 7
,
"division": "division 2",
"team": "team 3",
"value": 4
,
"division": "division 2",
"team": "team 3",
"value": 3
,
"division": "division 2",
"team": "team 3",
"value": 8
,
];
var width = 200,
height = 75;
var container = d3.select("#container");
var color = d3.scaleOrdinal(d3.schemeCategory10);
var xScaleTeam = d3.scaleBand()
.range([0, width])
.domain([...new Set(dataset1.map(function(d)
return d.team
))])
.paddingOuter(0.4);
var xScaleBars = d3.scaleBand()
.range([0, xScaleTeam.bandwidth()])
.domain(d3.range(3))
.paddingOuter(0.4);
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max([d3.max(dataset1, function(d)
return d.value
), d3.max(dataset2, function(d)
return d.value
)])]);
function updateChart(data)
// Divisions
var divisions = d3.nest().key(d => d.division).entries(data);
var divisionCharts = container.selectAll("svg").data(divisions, d => d.key);
var divisionChartsEnter = divisionCharts.enter().append("svg").attr("width", width).attr("height", height);
divisionCharts = divisionCharts.merge(divisionChartsEnter);
// Teams
var teamCharts = divisionCharts.selectAll("g")
.data(d => d3.nest().key(d => d.team).entries(d.values), d => d.key);
var teamChartsEnter = teamCharts.enter().append("g").attr("transform", (d) => "translate(" + xScaleTeam(d.key) + ",0)")
.style("fill", (d) => color(d.key));
teamCharts = teamCharts.merge(teamChartsEnter);
// Values for each team
var bars = teamCharts.selectAll("rect").data(d => d.values);
var barsEnter = bars.enter()
.append("rect")
.attr("x", (d, i) => xScaleBars(i))
.attr("width", 10)
.attr("y", height)
bars.merge(barsEnter)
.transition()
.attr("y", d => yScale(d.value))
.attr("height", d => height - yScale(d.value))
let currDataset = dataset1;
updateChart(currDataset);
d3.select("#updateData").on("click", () =>
currDataset = currDataset === dataset1 ? dataset2 : dataset1;
updateChart(currDataset);
)<p><button id="updateData">Update Data</button>
<div id="container"></div>
<script src="https://d3js.org/d3.v5.min.js"></script>Overall, your update function is correct. You're appending several SVGs to the HTML, which is a good and very common approach when creating small multiples. Also, you have a correct (almost, see below) understanding of the update pattern and the merge() method.
Major issues
1. Merging the selections
As I said, you get the principle of merging selections right, but some details are wrong. For instance, you don't need the following selectAll in the merged selection.
So, they should be something like this (using the first one as example):
var divisions = d3.nest()
.key(d => d.division)
.entries(data);
var divisionCharts = container.selectAll("svg")
.data(divisions, d => d.key);
var divisionChartsEnter = divisionCharts.enter()
.append("svg")
.attr("width", width)
.attr("height", height);
divisionCharts = divisionCharts.merge(divisionChartsEnter);
And do the same for the other merged selections.
2. Appending SVGs inside SVGs
As stated above, appending multiple SVGs is a very common approach when creating small multiples. However, inside each SVG you're creating nested SVGs, one for each team... not only this is uncommon and unnecessary, but also can lead to some problems. The common approach here is creating groups.
So, instead of SVGs, let's groups (<g>) for each team:
var teamCharts = divisionCharts.selectAll("g")
.data(d => d3.nest()
.key(d => d.team)
.entries(d.values), d => d.key);
var teamChartsEnter = teamCharts.enter()
.append("g")
.attr("transform", (d) => "translate(" + xScaleTeam(d.key) + ",0)");
teamCharts = teamCharts.merge(teamChartsEnter);
Since you're appending groups you have to translate them, instead of setting the x attribute.
3. Use scales!
This is a very important advice: use scales in a D3 code. The way you're doing right now (using absolute values and indices for positioning and size) is not correct and, the most important, it will be a hell to debug/maintain. Actually, if you look at the snippets above, you'll see that I'm already using scales on them.
So, create proper scales. In your case:
var xScaleTeam = d3.scaleBand()
.range([0, width])
.domain([...new Set(dataset1.map(function(d)
return d.team
))])
.paddingOuter(0.4);
var xScaleBars = d3.scaleBand()
.range([0, xScaleTeam.bandwidth()])
.domain(d3.range(3))
.paddingOuter(0.4);
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max([d3.max(dataset1, function(d)
return d.value
), d3.max(dataset2, function(d)
return d.value
)])]);
xScaleTeam is the outer x scale for each small multiple, xScaleBars is the inner x scale (for each team) and yScale is, obviously, the y scale.
4. Bars going up
Maybe I'm wrong, but it seems to me that you want the bar chart going up, as almost all bar charts around. That being the case, just do:
bars.attr("y", d => yScale(d.value))
.attr("height", d => height - yScale(d.value))
Minor issues
Don't append the SVGs directly to the body. Instead of that, select a specific element, like a
<div>withcontainerid:var container = d3.select("#container");This code works just fine if you reference the new v5 version, instead of v4 (which you're using right now). Therefore, change to v5.
Don't use magic numbers. For instance, define the width and height:
var width = 200,
height = 75;Move the toggle logic to outside the
updatefunction: that way, you have a function that depends only on the arguments it receives.As a further advice: color the bars by team. It is simple as:
var color = d3.scaleOrdinal(d3.schemeCategory10);And then:
teamChartsEnter.style("fill", (d)=> color(d.key));
Demo
Here is your code with all those changes:
const dataset1 = [
"division": "division 1",
"team": "team 1",
"value": 5
,
"division": "division 1",
"team": "team 1",
"value": 7
,
"division": "division 1",
"team": "team 1",
"value": 9
,
"division": "division 1",
"team": "team 2",
"value": 2
,
"division": "division 1",
"team": "team 2",
"value": 1
,
"division": "division 1",
"team": "team 2",
"value": 1
,
"division": "division 1",
"team": "team 3",
"value": 3
,
"division": "division 1",
"team": "team 3",
"value": 1
,
"division": "division 1",
"team": "team 3",
"value": 7
,
"division": "division 2",
"team": "team 1",
"value": 2
,
"division": "division 2",
"team": "team 1",
"value": 7
,
"division": "division 2",
"team": "team 1",
"value": 3
,
"division": "division 2",
"team": "team 2",
"value": 6
,
"division": "division 2",
"team": "team 2",
"value": 3
,
"division": "division 2",
"team": "team 2",
"value": 2
,
"division": "division 2",
"team": "team 3",
"value": 3
,
"division": "division 2",
"team": "team 3",
"value": 9
,
"division": "division 2",
"team": "team 3",
"value": 5
,
]
const dataset2 = [
"division": "division 1",
"team": "team 1",
"value": 1
,
"division": "division 1",
"team": "team 1",
"value": 4
,
"division": "division 1",
"team": "team 1",
"value": 3
,
"division": "division 1",
"team": "team 2",
"value": 6
,
"division": "division 1",
"team": "team 2",
"value": 2
,
"division": "division 1",
"team": "team 2",
"value": 9
,
"division": "division 1",
"team": "team 3",
"value": 5
,
"division": "division 1",
"team": "team 3",
"value": 2
,
"division": "division 1",
"team": "team 3",
"value": 3
,
"division": "division 2",
"team": "team 1",
"value": 8
,
"division": "division 2",
"team": "team 1",
"value": 1
,
"division": "division 2",
"team": "team 1",
"value": 2
,
"division": "division 2",
"team": "team 2",
"value": 5
,
"division": "division 2",
"team": "team 2",
"value": 3
,
"division": "division 2",
"team": "team 2",
"value": 7
,
"division": "division 2",
"team": "team 3",
"value": 4
,
"division": "division 2",
"team": "team 3",
"value": 3
,
"division": "division 2",
"team": "team 3",
"value": 8
,
];
var width = 200,
height = 75;
var container = d3.select("#container");
var color = d3.scaleOrdinal(d3.schemeCategory10);
var xScaleTeam = d3.scaleBand()
.range([0, width])
.domain([...new Set(dataset1.map(function(d)
return d.team
))])
.paddingOuter(0.4);
var xScaleBars = d3.scaleBand()
.range([0, xScaleTeam.bandwidth()])
.domain(d3.range(3))
.paddingOuter(0.4);
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max([d3.max(dataset1, function(d)
return d.value
), d3.max(dataset2, function(d)
return d.value
)])]);
function updateChart(data)
// Divisions
var divisions = d3.nest().key(d => d.division).entries(data);
var divisionCharts = container.selectAll("svg").data(divisions, d => d.key);
var divisionChartsEnter = divisionCharts.enter().append("svg").attr("width", width).attr("height", height);
divisionCharts = divisionCharts.merge(divisionChartsEnter);
// Teams
var teamCharts = divisionCharts.selectAll("g")
.data(d => d3.nest().key(d => d.team).entries(d.values), d => d.key);
var teamChartsEnter = teamCharts.enter().append("g").attr("transform", (d) => "translate(" + xScaleTeam(d.key) + ",0)")
.style("fill", (d) => color(d.key));
teamCharts = teamCharts.merge(teamChartsEnter);
// Values for each team
var bars = teamCharts.selectAll("rect").data(d => d.values);
var barsEnter = bars.enter()
.append("rect")
.attr("x", (d, i) => xScaleBars(i))
.attr("width", 10)
.attr("y", height)
bars.merge(barsEnter)
.transition()
.attr("y", d => yScale(d.value))
.attr("height", d => height - yScale(d.value))
let currDataset = dataset1;
updateChart(currDataset);
d3.select("#updateData").on("click", () =>
currDataset = currDataset === dataset1 ? dataset2 : dataset1;
updateChart(currDataset);
)<p><button id="updateData">Update Data</button>
<div id="container"></div>
<script src="https://d3js.org/d3.v5.min.js"></script>const dataset1 = [
"division": "division 1",
"team": "team 1",
"value": 5
,
"division": "division 1",
"team": "team 1",
"value": 7
,
"division": "division 1",
"team": "team 1",
"value": 9
,
"division": "division 1",
"team": "team 2",
"value": 2
,
"division": "division 1",
"team": "team 2",
"value": 1
,
"division": "division 1",
"team": "team 2",
"value": 1
,
"division": "division 1",
"team": "team 3",
"value": 3
,
"division": "division 1",
"team": "team 3",
"value": 1
,
"division": "division 1",
"team": "team 3",
"value": 7
,
"division": "division 2",
"team": "team 1",
"value": 2
,
"division": "division 2",
"team": "team 1",
"value": 7
,
"division": "division 2",
"team": "team 1",
"value": 3
,
"division": "division 2",
"team": "team 2",
"value": 6
,
"division": "division 2",
"team": "team 2",
"value": 3
,
"division": "division 2",
"team": "team 2",
"value": 2
,
"division": "division 2",
"team": "team 3",
"value": 3
,
"division": "division 2",
"team": "team 3",
"value": 9
,
"division": "division 2",
"team": "team 3",
"value": 5
,
]
const dataset2 = [
"division": "division 1",
"team": "team 1",
"value": 1
,
"division": "division 1",
"team": "team 1",
"value": 4
,
"division": "division 1",
"team": "team 1",
"value": 3
,
"division": "division 1",
"team": "team 2",
"value": 6
,
"division": "division 1",
"team": "team 2",
"value": 2
,
"division": "division 1",
"team": "team 2",
"value": 9
,
"division": "division 1",
"team": "team 3",
"value": 5
,
"division": "division 1",
"team": "team 3",
"value": 2
,
"division": "division 1",
"team": "team 3",
"value": 3
,
"division": "division 2",
"team": "team 1",
"value": 8
,
"division": "division 2",
"team": "team 1",
"value": 1
,
"division": "division 2",
"team": "team 1",
"value": 2
,
"division": "division 2",
"team": "team 2",
"value": 5
,
"division": "division 2",
"team": "team 2",
"value": 3
,
"division": "division 2",
"team": "team 2",
"value": 7
,
"division": "division 2",
"team": "team 3",
"value": 4
,
"division": "division 2",
"team": "team 3",
"value": 3
,
"division": "division 2",
"team": "team 3",
"value": 8
,
];
var width = 200,
height = 75;
var container = d3.select("#container");
var color = d3.scaleOrdinal(d3.schemeCategory10);
var xScaleTeam = d3.scaleBand()
.range([0, width])
.domain([...new Set(dataset1.map(function(d)
return d.team
))])
.paddingOuter(0.4);
var xScaleBars = d3.scaleBand()
.range([0, xScaleTeam.bandwidth()])
.domain(d3.range(3))
.paddingOuter(0.4);
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max([d3.max(dataset1, function(d)
return d.value
), d3.max(dataset2, function(d)
return d.value
)])]);
function updateChart(data)
// Divisions
var divisions = d3.nest().key(d => d.division).entries(data);
var divisionCharts = container.selectAll("svg").data(divisions, d => d.key);
var divisionChartsEnter = divisionCharts.enter().append("svg").attr("width", width).attr("height", height);
divisionCharts = divisionCharts.merge(divisionChartsEnter);
// Teams
var teamCharts = divisionCharts.selectAll("g")
.data(d => d3.nest().key(d => d.team).entries(d.values), d => d.key);
var teamChartsEnter = teamCharts.enter().append("g").attr("transform", (d) => "translate(" + xScaleTeam(d.key) + ",0)")
.style("fill", (d) => color(d.key));
teamCharts = teamCharts.merge(teamChartsEnter);
// Values for each team
var bars = teamCharts.selectAll("rect").data(d => d.values);
var barsEnter = bars.enter()
.append("rect")
.attr("x", (d, i) => xScaleBars(i))
.attr("width", 10)
.attr("y", height)
bars.merge(barsEnter)
.transition()
.attr("y", d => yScale(d.value))
.attr("height", d => height - yScale(d.value))
let currDataset = dataset1;
updateChart(currDataset);
d3.select("#updateData").on("click", () =>
currDataset = currDataset === dataset1 ? dataset2 : dataset1;
updateChart(currDataset);
)<p><button id="updateData">Update Data</button>
<div id="container"></div>
<script src="https://d3js.org/d3.v5.min.js"></script>const dataset1 = [
"division": "division 1",
"team": "team 1",
"value": 5
,
"division": "division 1",
"team": "team 1",
"value": 7
,
"division": "division 1",
"team": "team 1",
"value": 9
,
"division": "division 1",
"team": "team 2",
"value": 2
,
"division": "division 1",
"team": "team 2",
"value": 1
,
"division": "division 1",
"team": "team 2",
"value": 1
,
"division": "division 1",
"team": "team 3",
"value": 3
,
"division": "division 1",
"team": "team 3",
"value": 1
,
"division": "division 1",
"team": "team 3",
"value": 7
,
"division": "division 2",
"team": "team 1",
"value": 2
,
"division": "division 2",
"team": "team 1",
"value": 7
,
"division": "division 2",
"team": "team 1",
"value": 3
,
"division": "division 2",
"team": "team 2",
"value": 6
,
"division": "division 2",
"team": "team 2",
"value": 3
,
"division": "division 2",
"team": "team 2",
"value": 2
,
"division": "division 2",
"team": "team 3",
"value": 3
,
"division": "division 2",
"team": "team 3",
"value": 9
,
"division": "division 2",
"team": "team 3",
"value": 5
,
]
const dataset2 = [
"division": "division 1",
"team": "team 1",
"value": 1
,
"division": "division 1",
"team": "team 1",
"value": 4
,
"division": "division 1",
"team": "team 1",
"value": 3
,
"division": "division 1",
"team": "team 2",
"value": 6
,
"division": "division 1",
"team": "team 2",
"value": 2
,
"division": "division 1",
"team": "team 2",
"value": 9
,
"division": "division 1",
"team": "team 3",
"value": 5
,
"division": "division 1",
"team": "team 3",
"value": 2
,
"division": "division 1",
"team": "team 3",
"value": 3
,
"division": "division 2",
"team": "team 1",
"value": 8
,
"division": "division 2",
"team": "team 1",
"value": 1
,
"division": "division 2",
"team": "team 1",
"value": 2
,
"division": "division 2",
"team": "team 2",
"value": 5
,
"division": "division 2",
"team": "team 2",
"value": 3
,
"division": "division 2",
"team": "team 2",
"value": 7
,
"division": "division 2",
"team": "team 3",
"value": 4
,
"division": "division 2",
"team": "team 3",
"value": 3
,
"division": "division 2",
"team": "team 3",
"value": 8
,
];
var width = 200,
height = 75;
var container = d3.select("#container");
var color = d3.scaleOrdinal(d3.schemeCategory10);
var xScaleTeam = d3.scaleBand()
.range([0, width])
.domain([...new Set(dataset1.map(function(d)
return d.team
))])
.paddingOuter(0.4);
var xScaleBars = d3.scaleBand()
.range([0, xScaleTeam.bandwidth()])
.domain(d3.range(3))
.paddingOuter(0.4);
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max([d3.max(dataset1, function(d)
return d.value
), d3.max(dataset2, function(d)
return d.value
)])]);
function updateChart(data)
// Divisions
var divisions = d3.nest().key(d => d.division).entries(data);
var divisionCharts = container.selectAll("svg").data(divisions, d => d.key);
var divisionChartsEnter = divisionCharts.enter().append("svg").attr("width", width).attr("height", height);
divisionCharts = divisionCharts.merge(divisionChartsEnter);
// Teams
var teamCharts = divisionCharts.selectAll("g")
.data(d => d3.nest().key(d => d.team).entries(d.values), d => d.key);
var teamChartsEnter = teamCharts.enter().append("g").attr("transform", (d) => "translate(" + xScaleTeam(d.key) + ",0)")
.style("fill", (d) => color(d.key));
teamCharts = teamCharts.merge(teamChartsEnter);
// Values for each team
var bars = teamCharts.selectAll("rect").data(d => d.values);
var barsEnter = bars.enter()
.append("rect")
.attr("x", (d, i) => xScaleBars(i))
.attr("width", 10)
.attr("y", height)
bars.merge(barsEnter)
.transition()
.attr("y", d => yScale(d.value))
.attr("height", d => height - yScale(d.value))
let currDataset = dataset1;
updateChart(currDataset);
d3.select("#updateData").on("click", () =>
currDataset = currDataset === dataset1 ? dataset2 : dataset1;
updateChart(currDataset);
)<p><button id="updateData">Update Data</button>
<div id="container"></div>
<script src="https://d3js.org/d3.v5.min.js"></script>edited Jul 21 at 0:10
answered Jul 19 at 3:23
Gerardo Furtado
1,1342420
1,1342420
Thank you so much! This is very helpful. Glad to know that I'm (mostly) on the right track. I didn't know about the problems with nestedsvgs so I'll usegs instead. Another question: is there any advantage to nesting the data separately like the sample code? Or would it be better to double nest it like this:var dataNested = d3.nest().key(d => d.division).key(d => d.team)entries(data);
â Jeff Appareti
Jul 19 at 22:15
It makes very little difference. But for readability I'd do two separate nests.
â Gerardo Furtado
Jul 19 at 22:47
add a comment |Â
Thank you so much! This is very helpful. Glad to know that I'm (mostly) on the right track. I didn't know about the problems with nestedsvgs so I'll usegs instead. Another question: is there any advantage to nesting the data separately like the sample code? Or would it be better to double nest it like this:var dataNested = d3.nest().key(d => d.division).key(d => d.team)entries(data);
â Jeff Appareti
Jul 19 at 22:15
It makes very little difference. But for readability I'd do two separate nests.
â Gerardo Furtado
Jul 19 at 22:47
Thank you so much! This is very helpful. Glad to know that I'm (mostly) on the right track. I didn't know about the problems with nested
svgs so I'll use gs instead. Another question: is there any advantage to nesting the data separately like the sample code? Or would it be better to double nest it like this: var dataNested = d3.nest().key(d => d.division).key(d => d.team)entries(data);â Jeff Appareti
Jul 19 at 22:15
Thank you so much! This is very helpful. Glad to know that I'm (mostly) on the right track. I didn't know about the problems with nested
svgs so I'll use gs instead. Another question: is there any advantage to nesting the data separately like the sample code? Or would it be better to double nest it like this: var dataNested = d3.nest().key(d => d.division).key(d => d.team)entries(data);â Jeff Appareti
Jul 19 at 22:15
It makes very little difference. But for readability I'd do two separate nests.
â Gerardo Furtado
Jul 19 at 22:47
It makes very little difference. But for readability I'd do two separate nests.
â Gerardo Furtado
Jul 19 at 22:47
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f199793%2fupdate-pattern-for-subplots%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password