D3 Updating stacked bar chart
up vote
1
down vote
favorite
In my data there can be different number of rectangles in each stack.
Here is a picture:
When pressing the change bars layout the chart looks like this:
Since I might get bar chart stacked vertically or horizontally and different number of stacks I wasn't able use D3 stack function.
and had to do most of the calculation myself.
2 more important points:
my code is inside vuejs framework.
the variable
stackedXorY
is 1 or 0, when it's 1 the stack is horizontal
when it's 0 the stack is vertical.
A couple of questions:
Is there a way to use the d3 stack or some other method to create this kind of chart?
I noticed that when I update the chart (in
handleChartLayout
function)
each selected stage holds the y property, even though the data itself
(tasks) and stages in it don't hold it. How is that possible? It seems to me I have an issue but not sure.
Here is the an example of the data:
{
"id":"ACra92XfQ696H7BUCCYT",
"isTaskFinished":true,
"taskName":"t1",
"initTaskTime":1536427448023,
"endTaskTime":1536427471408,
"taskStages":[
{
"initTime":1536427447023,
"timeStamps":[
1536427449994,
1536427453242,
1536427456115
],
"totalTime":12503
},
{
"initTime":1536427460526,
"timeStamps":[
1536427465433,
1536427470250
],
"totalTime":10882
}
],
"taskTotalTime":23385
}
here is the code:
<template>
<div>
<svg
ref="stacked-svg-tasks"
id="stacked-svg-tasks"
:width="svgWidth"
:height="svgHeight"
></svg>
</div>
</template>
<script>
import * as d3 from 'd3';
export default {
name: 'TasksStackedBarChart',
props: ['tasks', 'svgWidth', 'svgHeight', 'stackedXorY'],
data() {
return {
X_AXIS_HEIGHT: 20,
Y_AXIS_WIDTH: 50,
svgMargin: { top: 20 },
barChartGroup: {
width: 0,
height: 0,
x: 0,
y: 0,
},
GbT: 50, // gap between tasks
stageWidth: 0,
toalStages: 0,
xPosOfTasks: ,
yScale: null,
yScaleDomainEnd: 0,
};
},
created() {
this.barChartGroup.width = this.svgWidth - this.Y_AXIS_WIDTH;
this.barChartGroup.height = this.svgHeight - this.X_AXIS_HEIGHT;
this.barChartGroup.x = this.Y_AXIS_WIDTH;
this.barChartGroup.y = this.X_AXIS_HEIGHT;
this.toalStages = this.tasks.map(v => v.taskStages.length).reduce((s, v) => s + v, 0);
this.stageWidth = this.getStageWidth();
this.xPosOfTasks = this.getXPosOfTask(this.GbT, this.tasks, this.stageWidth, this.stackedXorY);
this.yScaleDomainEnd = this.getLongestTask();
this.yScale = this.getScaleForTask(this.barChartGroup.height, this.yScaleDomainEnd);
},
mounted() {
this.chartBuilder();
},
methods: {
getScaleForTask(rangeEnd, domainEnd) {
const calcRangeEnd = rangeEnd - this.svgMargin.top - this.X_AXIS_HEIGHT;
return d3.scaleLinear()
.range([0, calcRangeEnd])
.domain([0, domainEnd]);
},
getLongestTask() {
return d3.max(this.tasks.map(t => t.taskTotalTime));
},
getStageWidth() { // will keep using this. to see if it's better to work like this or i should pass parameters
const numOfChartBars = this.stackedXorY ? this.toalStages : this.tasks.length;
return Math.floor((this.svgWidth - (this.GbT * (this.tasks.length + 1))) / numOfChartBars);
},
getPosOfStage(stage, stageIndex) {
const x = stageIndex * this.stageWidth * this.stackedXorY;
// this sets the bars at the bottom than this.yScale(s.y) moves them accordingly
const y = this.barChartGroup.height - this.yScale(stage.totalTime) - ((1 - this.stackedXorY) * this.yScale(stage.y));
return `translate(${x}, ${y})`;
},
getXPosOfTask(gapBetweenTasks, tasks, widthOfBar, stackedYorX) {
const newXPosOfTasks = [0]; // [gapBetweenTasks + 50];
for (let c = 0; c < tasks.length - 1; c += 1) {
const barsInTask = stackedYorX ? tasks[c].taskStages.length : 1;
const taskXPos = gapBetweenTasks + (barsInTask * widthOfBar) + newXPosOfTasks[c];
newXPosOfTasks.push(taskXPos);
}
return newXPosOfTasks;
},
cumulativeTimeOfStages(taskStages) {
return taskStages.reduce((sums, curItem) => {
const newSum = sums[sums.length - 1] + curItem.totalTime;
sums.push(newSum);
return sums;
}, [0]);
},
reconstructedStageData(taskStages) {
const cumulativeSums = this.cumulativeTimeOfStages(taskStages);
return taskStages.map((stage, i) => ({ y: cumulativeSums[i], ...stage }));
},
chartBuilder() {
// clearing svg
d3.selectAll('#stacked-svg-tasks').selectAll('g').remove();
const svg = d3.select('#stacked-svg-tasks');
// adding Y axis
const yAxisScale = d3
.scaleTime()
.domain([this.yScaleDomainEnd, 0])
.range([0, this.svgHeight - this.X_AXIS_HEIGHT - this.svgMargin.top]);
const yAxis = d3.axisLeft().scale(yAxisScale);
const barChartGroup = svg
.selectAll('g')
.data([this.tasks])
.enter()
.append('g')
.attr('class', 'bar-chart-group')
.attr('transform', `translate(${this.barChartGroup.x},${this.barChartGroup.y})`);
const taskGroups = barChartGroup
.selectAll('g')
.data(t => t)
.enter()
.append('g')
.attr('class', (t, i) => `bar${i}`)
.attr('transform', (t, i) => `translate(${this.xPosOfTasks[i]},0)`);
const stageGroups = taskGroups
.selectAll('g')
.data(t => this.reconstructedStageData(t.taskStages))
.enter()
.append('g')
.attr('transform', (s, i) => this.getPosOfStage(s, i))
.append('rect')
.attr('width', this.stageWidth)
.attr('height', s => this.yScale(s.totalTime))
.attr('fill', (d, i) => (i % 2 === 0 ? '#66ccff' : '#99ff66'))
.attr('style', 'stroke:rgb(150,150,150);stroke-width:2');
},
handleChartLayout() {
const svg = d3.select('#stacked-svg-tasks');
this.tasks.forEach((task, taskIndex) => {
svg
.selectAll(`.bar${taskIndex}`)
.data([task])
.transition()
.duration(750)
.attr('transform', `translate(${this.xPosOfTasks[taskIndex]}, 0)`)
.selectAll('g')
.attr('transform', (s, i) => this.getPosOfStage(s, i))
.selectAll('rect')
.attr('width', this.stageWidth);
});
},
},
watch: {
stackedXorY() {
this.stageWidth = this.getStageWidth();
this.xPosOfTasks = this.getXPosOfTask(this.GbT, this.tasks, this.stageWidth, this.stackedXorY);
this.handleChartLayout();
},
},
};
</script>
javascript ecmascript-6 data-visualization d3.js vue.js
add a comment |
up vote
1
down vote
favorite
In my data there can be different number of rectangles in each stack.
Here is a picture:
When pressing the change bars layout the chart looks like this:
Since I might get bar chart stacked vertically or horizontally and different number of stacks I wasn't able use D3 stack function.
and had to do most of the calculation myself.
2 more important points:
my code is inside vuejs framework.
the variable
stackedXorY
is 1 or 0, when it's 1 the stack is horizontal
when it's 0 the stack is vertical.
A couple of questions:
Is there a way to use the d3 stack or some other method to create this kind of chart?
I noticed that when I update the chart (in
handleChartLayout
function)
each selected stage holds the y property, even though the data itself
(tasks) and stages in it don't hold it. How is that possible? It seems to me I have an issue but not sure.
Here is the an example of the data:
{
"id":"ACra92XfQ696H7BUCCYT",
"isTaskFinished":true,
"taskName":"t1",
"initTaskTime":1536427448023,
"endTaskTime":1536427471408,
"taskStages":[
{
"initTime":1536427447023,
"timeStamps":[
1536427449994,
1536427453242,
1536427456115
],
"totalTime":12503
},
{
"initTime":1536427460526,
"timeStamps":[
1536427465433,
1536427470250
],
"totalTime":10882
}
],
"taskTotalTime":23385
}
here is the code:
<template>
<div>
<svg
ref="stacked-svg-tasks"
id="stacked-svg-tasks"
:width="svgWidth"
:height="svgHeight"
></svg>
</div>
</template>
<script>
import * as d3 from 'd3';
export default {
name: 'TasksStackedBarChart',
props: ['tasks', 'svgWidth', 'svgHeight', 'stackedXorY'],
data() {
return {
X_AXIS_HEIGHT: 20,
Y_AXIS_WIDTH: 50,
svgMargin: { top: 20 },
barChartGroup: {
width: 0,
height: 0,
x: 0,
y: 0,
},
GbT: 50, // gap between tasks
stageWidth: 0,
toalStages: 0,
xPosOfTasks: ,
yScale: null,
yScaleDomainEnd: 0,
};
},
created() {
this.barChartGroup.width = this.svgWidth - this.Y_AXIS_WIDTH;
this.barChartGroup.height = this.svgHeight - this.X_AXIS_HEIGHT;
this.barChartGroup.x = this.Y_AXIS_WIDTH;
this.barChartGroup.y = this.X_AXIS_HEIGHT;
this.toalStages = this.tasks.map(v => v.taskStages.length).reduce((s, v) => s + v, 0);
this.stageWidth = this.getStageWidth();
this.xPosOfTasks = this.getXPosOfTask(this.GbT, this.tasks, this.stageWidth, this.stackedXorY);
this.yScaleDomainEnd = this.getLongestTask();
this.yScale = this.getScaleForTask(this.barChartGroup.height, this.yScaleDomainEnd);
},
mounted() {
this.chartBuilder();
},
methods: {
getScaleForTask(rangeEnd, domainEnd) {
const calcRangeEnd = rangeEnd - this.svgMargin.top - this.X_AXIS_HEIGHT;
return d3.scaleLinear()
.range([0, calcRangeEnd])
.domain([0, domainEnd]);
},
getLongestTask() {
return d3.max(this.tasks.map(t => t.taskTotalTime));
},
getStageWidth() { // will keep using this. to see if it's better to work like this or i should pass parameters
const numOfChartBars = this.stackedXorY ? this.toalStages : this.tasks.length;
return Math.floor((this.svgWidth - (this.GbT * (this.tasks.length + 1))) / numOfChartBars);
},
getPosOfStage(stage, stageIndex) {
const x = stageIndex * this.stageWidth * this.stackedXorY;
// this sets the bars at the bottom than this.yScale(s.y) moves them accordingly
const y = this.barChartGroup.height - this.yScale(stage.totalTime) - ((1 - this.stackedXorY) * this.yScale(stage.y));
return `translate(${x}, ${y})`;
},
getXPosOfTask(gapBetweenTasks, tasks, widthOfBar, stackedYorX) {
const newXPosOfTasks = [0]; // [gapBetweenTasks + 50];
for (let c = 0; c < tasks.length - 1; c += 1) {
const barsInTask = stackedYorX ? tasks[c].taskStages.length : 1;
const taskXPos = gapBetweenTasks + (barsInTask * widthOfBar) + newXPosOfTasks[c];
newXPosOfTasks.push(taskXPos);
}
return newXPosOfTasks;
},
cumulativeTimeOfStages(taskStages) {
return taskStages.reduce((sums, curItem) => {
const newSum = sums[sums.length - 1] + curItem.totalTime;
sums.push(newSum);
return sums;
}, [0]);
},
reconstructedStageData(taskStages) {
const cumulativeSums = this.cumulativeTimeOfStages(taskStages);
return taskStages.map((stage, i) => ({ y: cumulativeSums[i], ...stage }));
},
chartBuilder() {
// clearing svg
d3.selectAll('#stacked-svg-tasks').selectAll('g').remove();
const svg = d3.select('#stacked-svg-tasks');
// adding Y axis
const yAxisScale = d3
.scaleTime()
.domain([this.yScaleDomainEnd, 0])
.range([0, this.svgHeight - this.X_AXIS_HEIGHT - this.svgMargin.top]);
const yAxis = d3.axisLeft().scale(yAxisScale);
const barChartGroup = svg
.selectAll('g')
.data([this.tasks])
.enter()
.append('g')
.attr('class', 'bar-chart-group')
.attr('transform', `translate(${this.barChartGroup.x},${this.barChartGroup.y})`);
const taskGroups = barChartGroup
.selectAll('g')
.data(t => t)
.enter()
.append('g')
.attr('class', (t, i) => `bar${i}`)
.attr('transform', (t, i) => `translate(${this.xPosOfTasks[i]},0)`);
const stageGroups = taskGroups
.selectAll('g')
.data(t => this.reconstructedStageData(t.taskStages))
.enter()
.append('g')
.attr('transform', (s, i) => this.getPosOfStage(s, i))
.append('rect')
.attr('width', this.stageWidth)
.attr('height', s => this.yScale(s.totalTime))
.attr('fill', (d, i) => (i % 2 === 0 ? '#66ccff' : '#99ff66'))
.attr('style', 'stroke:rgb(150,150,150);stroke-width:2');
},
handleChartLayout() {
const svg = d3.select('#stacked-svg-tasks');
this.tasks.forEach((task, taskIndex) => {
svg
.selectAll(`.bar${taskIndex}`)
.data([task])
.transition()
.duration(750)
.attr('transform', `translate(${this.xPosOfTasks[taskIndex]}, 0)`)
.selectAll('g')
.attr('transform', (s, i) => this.getPosOfStage(s, i))
.selectAll('rect')
.attr('width', this.stageWidth);
});
},
},
watch: {
stackedXorY() {
this.stageWidth = this.getStageWidth();
this.xPosOfTasks = this.getXPosOfTask(this.GbT, this.tasks, this.stageWidth, this.stackedXorY);
this.handleChartLayout();
},
},
};
</script>
javascript ecmascript-6 data-visualization d3.js vue.js
add a comment |
up vote
1
down vote
favorite
up vote
1
down vote
favorite
In my data there can be different number of rectangles in each stack.
Here is a picture:
When pressing the change bars layout the chart looks like this:
Since I might get bar chart stacked vertically or horizontally and different number of stacks I wasn't able use D3 stack function.
and had to do most of the calculation myself.
2 more important points:
my code is inside vuejs framework.
the variable
stackedXorY
is 1 or 0, when it's 1 the stack is horizontal
when it's 0 the stack is vertical.
A couple of questions:
Is there a way to use the d3 stack or some other method to create this kind of chart?
I noticed that when I update the chart (in
handleChartLayout
function)
each selected stage holds the y property, even though the data itself
(tasks) and stages in it don't hold it. How is that possible? It seems to me I have an issue but not sure.
Here is the an example of the data:
{
"id":"ACra92XfQ696H7BUCCYT",
"isTaskFinished":true,
"taskName":"t1",
"initTaskTime":1536427448023,
"endTaskTime":1536427471408,
"taskStages":[
{
"initTime":1536427447023,
"timeStamps":[
1536427449994,
1536427453242,
1536427456115
],
"totalTime":12503
},
{
"initTime":1536427460526,
"timeStamps":[
1536427465433,
1536427470250
],
"totalTime":10882
}
],
"taskTotalTime":23385
}
here is the code:
<template>
<div>
<svg
ref="stacked-svg-tasks"
id="stacked-svg-tasks"
:width="svgWidth"
:height="svgHeight"
></svg>
</div>
</template>
<script>
import * as d3 from 'd3';
export default {
name: 'TasksStackedBarChart',
props: ['tasks', 'svgWidth', 'svgHeight', 'stackedXorY'],
data() {
return {
X_AXIS_HEIGHT: 20,
Y_AXIS_WIDTH: 50,
svgMargin: { top: 20 },
barChartGroup: {
width: 0,
height: 0,
x: 0,
y: 0,
},
GbT: 50, // gap between tasks
stageWidth: 0,
toalStages: 0,
xPosOfTasks: ,
yScale: null,
yScaleDomainEnd: 0,
};
},
created() {
this.barChartGroup.width = this.svgWidth - this.Y_AXIS_WIDTH;
this.barChartGroup.height = this.svgHeight - this.X_AXIS_HEIGHT;
this.barChartGroup.x = this.Y_AXIS_WIDTH;
this.barChartGroup.y = this.X_AXIS_HEIGHT;
this.toalStages = this.tasks.map(v => v.taskStages.length).reduce((s, v) => s + v, 0);
this.stageWidth = this.getStageWidth();
this.xPosOfTasks = this.getXPosOfTask(this.GbT, this.tasks, this.stageWidth, this.stackedXorY);
this.yScaleDomainEnd = this.getLongestTask();
this.yScale = this.getScaleForTask(this.barChartGroup.height, this.yScaleDomainEnd);
},
mounted() {
this.chartBuilder();
},
methods: {
getScaleForTask(rangeEnd, domainEnd) {
const calcRangeEnd = rangeEnd - this.svgMargin.top - this.X_AXIS_HEIGHT;
return d3.scaleLinear()
.range([0, calcRangeEnd])
.domain([0, domainEnd]);
},
getLongestTask() {
return d3.max(this.tasks.map(t => t.taskTotalTime));
},
getStageWidth() { // will keep using this. to see if it's better to work like this or i should pass parameters
const numOfChartBars = this.stackedXorY ? this.toalStages : this.tasks.length;
return Math.floor((this.svgWidth - (this.GbT * (this.tasks.length + 1))) / numOfChartBars);
},
getPosOfStage(stage, stageIndex) {
const x = stageIndex * this.stageWidth * this.stackedXorY;
// this sets the bars at the bottom than this.yScale(s.y) moves them accordingly
const y = this.barChartGroup.height - this.yScale(stage.totalTime) - ((1 - this.stackedXorY) * this.yScale(stage.y));
return `translate(${x}, ${y})`;
},
getXPosOfTask(gapBetweenTasks, tasks, widthOfBar, stackedYorX) {
const newXPosOfTasks = [0]; // [gapBetweenTasks + 50];
for (let c = 0; c < tasks.length - 1; c += 1) {
const barsInTask = stackedYorX ? tasks[c].taskStages.length : 1;
const taskXPos = gapBetweenTasks + (barsInTask * widthOfBar) + newXPosOfTasks[c];
newXPosOfTasks.push(taskXPos);
}
return newXPosOfTasks;
},
cumulativeTimeOfStages(taskStages) {
return taskStages.reduce((sums, curItem) => {
const newSum = sums[sums.length - 1] + curItem.totalTime;
sums.push(newSum);
return sums;
}, [0]);
},
reconstructedStageData(taskStages) {
const cumulativeSums = this.cumulativeTimeOfStages(taskStages);
return taskStages.map((stage, i) => ({ y: cumulativeSums[i], ...stage }));
},
chartBuilder() {
// clearing svg
d3.selectAll('#stacked-svg-tasks').selectAll('g').remove();
const svg = d3.select('#stacked-svg-tasks');
// adding Y axis
const yAxisScale = d3
.scaleTime()
.domain([this.yScaleDomainEnd, 0])
.range([0, this.svgHeight - this.X_AXIS_HEIGHT - this.svgMargin.top]);
const yAxis = d3.axisLeft().scale(yAxisScale);
const barChartGroup = svg
.selectAll('g')
.data([this.tasks])
.enter()
.append('g')
.attr('class', 'bar-chart-group')
.attr('transform', `translate(${this.barChartGroup.x},${this.barChartGroup.y})`);
const taskGroups = barChartGroup
.selectAll('g')
.data(t => t)
.enter()
.append('g')
.attr('class', (t, i) => `bar${i}`)
.attr('transform', (t, i) => `translate(${this.xPosOfTasks[i]},0)`);
const stageGroups = taskGroups
.selectAll('g')
.data(t => this.reconstructedStageData(t.taskStages))
.enter()
.append('g')
.attr('transform', (s, i) => this.getPosOfStage(s, i))
.append('rect')
.attr('width', this.stageWidth)
.attr('height', s => this.yScale(s.totalTime))
.attr('fill', (d, i) => (i % 2 === 0 ? '#66ccff' : '#99ff66'))
.attr('style', 'stroke:rgb(150,150,150);stroke-width:2');
},
handleChartLayout() {
const svg = d3.select('#stacked-svg-tasks');
this.tasks.forEach((task, taskIndex) => {
svg
.selectAll(`.bar${taskIndex}`)
.data([task])
.transition()
.duration(750)
.attr('transform', `translate(${this.xPosOfTasks[taskIndex]}, 0)`)
.selectAll('g')
.attr('transform', (s, i) => this.getPosOfStage(s, i))
.selectAll('rect')
.attr('width', this.stageWidth);
});
},
},
watch: {
stackedXorY() {
this.stageWidth = this.getStageWidth();
this.xPosOfTasks = this.getXPosOfTask(this.GbT, this.tasks, this.stageWidth, this.stackedXorY);
this.handleChartLayout();
},
},
};
</script>
javascript ecmascript-6 data-visualization d3.js vue.js
In my data there can be different number of rectangles in each stack.
Here is a picture:
When pressing the change bars layout the chart looks like this:
Since I might get bar chart stacked vertically or horizontally and different number of stacks I wasn't able use D3 stack function.
and had to do most of the calculation myself.
2 more important points:
my code is inside vuejs framework.
the variable
stackedXorY
is 1 or 0, when it's 1 the stack is horizontal
when it's 0 the stack is vertical.
A couple of questions:
Is there a way to use the d3 stack or some other method to create this kind of chart?
I noticed that when I update the chart (in
handleChartLayout
function)
each selected stage holds the y property, even though the data itself
(tasks) and stages in it don't hold it. How is that possible? It seems to me I have an issue but not sure.
Here is the an example of the data:
{
"id":"ACra92XfQ696H7BUCCYT",
"isTaskFinished":true,
"taskName":"t1",
"initTaskTime":1536427448023,
"endTaskTime":1536427471408,
"taskStages":[
{
"initTime":1536427447023,
"timeStamps":[
1536427449994,
1536427453242,
1536427456115
],
"totalTime":12503
},
{
"initTime":1536427460526,
"timeStamps":[
1536427465433,
1536427470250
],
"totalTime":10882
}
],
"taskTotalTime":23385
}
here is the code:
<template>
<div>
<svg
ref="stacked-svg-tasks"
id="stacked-svg-tasks"
:width="svgWidth"
:height="svgHeight"
></svg>
</div>
</template>
<script>
import * as d3 from 'd3';
export default {
name: 'TasksStackedBarChart',
props: ['tasks', 'svgWidth', 'svgHeight', 'stackedXorY'],
data() {
return {
X_AXIS_HEIGHT: 20,
Y_AXIS_WIDTH: 50,
svgMargin: { top: 20 },
barChartGroup: {
width: 0,
height: 0,
x: 0,
y: 0,
},
GbT: 50, // gap between tasks
stageWidth: 0,
toalStages: 0,
xPosOfTasks: ,
yScale: null,
yScaleDomainEnd: 0,
};
},
created() {
this.barChartGroup.width = this.svgWidth - this.Y_AXIS_WIDTH;
this.barChartGroup.height = this.svgHeight - this.X_AXIS_HEIGHT;
this.barChartGroup.x = this.Y_AXIS_WIDTH;
this.barChartGroup.y = this.X_AXIS_HEIGHT;
this.toalStages = this.tasks.map(v => v.taskStages.length).reduce((s, v) => s + v, 0);
this.stageWidth = this.getStageWidth();
this.xPosOfTasks = this.getXPosOfTask(this.GbT, this.tasks, this.stageWidth, this.stackedXorY);
this.yScaleDomainEnd = this.getLongestTask();
this.yScale = this.getScaleForTask(this.barChartGroup.height, this.yScaleDomainEnd);
},
mounted() {
this.chartBuilder();
},
methods: {
getScaleForTask(rangeEnd, domainEnd) {
const calcRangeEnd = rangeEnd - this.svgMargin.top - this.X_AXIS_HEIGHT;
return d3.scaleLinear()
.range([0, calcRangeEnd])
.domain([0, domainEnd]);
},
getLongestTask() {
return d3.max(this.tasks.map(t => t.taskTotalTime));
},
getStageWidth() { // will keep using this. to see if it's better to work like this or i should pass parameters
const numOfChartBars = this.stackedXorY ? this.toalStages : this.tasks.length;
return Math.floor((this.svgWidth - (this.GbT * (this.tasks.length + 1))) / numOfChartBars);
},
getPosOfStage(stage, stageIndex) {
const x = stageIndex * this.stageWidth * this.stackedXorY;
// this sets the bars at the bottom than this.yScale(s.y) moves them accordingly
const y = this.barChartGroup.height - this.yScale(stage.totalTime) - ((1 - this.stackedXorY) * this.yScale(stage.y));
return `translate(${x}, ${y})`;
},
getXPosOfTask(gapBetweenTasks, tasks, widthOfBar, stackedYorX) {
const newXPosOfTasks = [0]; // [gapBetweenTasks + 50];
for (let c = 0; c < tasks.length - 1; c += 1) {
const barsInTask = stackedYorX ? tasks[c].taskStages.length : 1;
const taskXPos = gapBetweenTasks + (barsInTask * widthOfBar) + newXPosOfTasks[c];
newXPosOfTasks.push(taskXPos);
}
return newXPosOfTasks;
},
cumulativeTimeOfStages(taskStages) {
return taskStages.reduce((sums, curItem) => {
const newSum = sums[sums.length - 1] + curItem.totalTime;
sums.push(newSum);
return sums;
}, [0]);
},
reconstructedStageData(taskStages) {
const cumulativeSums = this.cumulativeTimeOfStages(taskStages);
return taskStages.map((stage, i) => ({ y: cumulativeSums[i], ...stage }));
},
chartBuilder() {
// clearing svg
d3.selectAll('#stacked-svg-tasks').selectAll('g').remove();
const svg = d3.select('#stacked-svg-tasks');
// adding Y axis
const yAxisScale = d3
.scaleTime()
.domain([this.yScaleDomainEnd, 0])
.range([0, this.svgHeight - this.X_AXIS_HEIGHT - this.svgMargin.top]);
const yAxis = d3.axisLeft().scale(yAxisScale);
const barChartGroup = svg
.selectAll('g')
.data([this.tasks])
.enter()
.append('g')
.attr('class', 'bar-chart-group')
.attr('transform', `translate(${this.barChartGroup.x},${this.barChartGroup.y})`);
const taskGroups = barChartGroup
.selectAll('g')
.data(t => t)
.enter()
.append('g')
.attr('class', (t, i) => `bar${i}`)
.attr('transform', (t, i) => `translate(${this.xPosOfTasks[i]},0)`);
const stageGroups = taskGroups
.selectAll('g')
.data(t => this.reconstructedStageData(t.taskStages))
.enter()
.append('g')
.attr('transform', (s, i) => this.getPosOfStage(s, i))
.append('rect')
.attr('width', this.stageWidth)
.attr('height', s => this.yScale(s.totalTime))
.attr('fill', (d, i) => (i % 2 === 0 ? '#66ccff' : '#99ff66'))
.attr('style', 'stroke:rgb(150,150,150);stroke-width:2');
},
handleChartLayout() {
const svg = d3.select('#stacked-svg-tasks');
this.tasks.forEach((task, taskIndex) => {
svg
.selectAll(`.bar${taskIndex}`)
.data([task])
.transition()
.duration(750)
.attr('transform', `translate(${this.xPosOfTasks[taskIndex]}, 0)`)
.selectAll('g')
.attr('transform', (s, i) => this.getPosOfStage(s, i))
.selectAll('rect')
.attr('width', this.stageWidth);
});
},
},
watch: {
stackedXorY() {
this.stageWidth = this.getStageWidth();
this.xPosOfTasks = this.getXPosOfTask(this.GbT, this.tasks, this.stageWidth, this.stackedXorY);
this.handleChartLayout();
},
},
};
</script>
javascript ecmascript-6 data-visualization d3.js vue.js
javascript ecmascript-6 data-visualization d3.js vue.js
edited 10 hours ago
Sᴀᴍ Onᴇᴌᴀ
7,67561748
7,67561748
asked Sep 29 at 16:06
user4602966
92
92
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
up vote
0
down vote
Avoid the Toll cost of crossing the DOM bridge
...think of DOM as a piece of land and JavaScript (meaning ECMAScript) as another piece of land, both connected with a toll bridge
-quote from John Hrvatin, Microsoft, MIX09, in this talk Building High Performance Web Applications and Sites at 29:38, also included in the O'reilly Javascript book by Nicholas C Zakas Pg 36, also mentioned in this post
In your code I see d3.select('#stacked-svg-tasks')
two times, as well as d3.selectAll('#stacked-svg-tasks')
. The last expression obviously works but the difference between .select()
and .selectAll()
is that the former uses document.querySelector()
whereas the latter uses document.querySelectorAll()
, which returns a NodeList instead of a single element - somewhat overkill for getting an element using the id attribute.
In the d3 documentation for .select()
we see the following:
If the selector is not a string, instead selects the specified node; this is useful if you already have a reference to a node1
Because you are using VueJS and have ref="stacked-svg-tasks"
on the <svg>
tag in the template, you can use $refs instead of querying the DOM each time to select that element.
So lines like this
const svg = d3.select("stacked-svg-tasks");
Could be updated like this:
const svg = d3.select(this.$refs['stacked-svg-tasks']);
I would recommend switching to camelCase or at least something without the hyphens -
<svg
ref="stackedSvgTasks"
Then the dot notation (instead of bracket notation) can be used like below:
const svg = d3.select(this.$refs.stackedSvgTasks);
Additionally, that reference svg
could be stored when the component is mounted (e.g. in the mounted method
) and re-used in other methods.
Beyond that, the code looks good. I like the use of const
by default for variables and only using let
for the iterator variable c
.
1https://github.com/d3/d3-selection#select
add a comment |
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
0
down vote
Avoid the Toll cost of crossing the DOM bridge
...think of DOM as a piece of land and JavaScript (meaning ECMAScript) as another piece of land, both connected with a toll bridge
-quote from John Hrvatin, Microsoft, MIX09, in this talk Building High Performance Web Applications and Sites at 29:38, also included in the O'reilly Javascript book by Nicholas C Zakas Pg 36, also mentioned in this post
In your code I see d3.select('#stacked-svg-tasks')
two times, as well as d3.selectAll('#stacked-svg-tasks')
. The last expression obviously works but the difference between .select()
and .selectAll()
is that the former uses document.querySelector()
whereas the latter uses document.querySelectorAll()
, which returns a NodeList instead of a single element - somewhat overkill for getting an element using the id attribute.
In the d3 documentation for .select()
we see the following:
If the selector is not a string, instead selects the specified node; this is useful if you already have a reference to a node1
Because you are using VueJS and have ref="stacked-svg-tasks"
on the <svg>
tag in the template, you can use $refs instead of querying the DOM each time to select that element.
So lines like this
const svg = d3.select("stacked-svg-tasks");
Could be updated like this:
const svg = d3.select(this.$refs['stacked-svg-tasks']);
I would recommend switching to camelCase or at least something without the hyphens -
<svg
ref="stackedSvgTasks"
Then the dot notation (instead of bracket notation) can be used like below:
const svg = d3.select(this.$refs.stackedSvgTasks);
Additionally, that reference svg
could be stored when the component is mounted (e.g. in the mounted method
) and re-used in other methods.
Beyond that, the code looks good. I like the use of const
by default for variables and only using let
for the iterator variable c
.
1https://github.com/d3/d3-selection#select
add a comment |
up vote
0
down vote
Avoid the Toll cost of crossing the DOM bridge
...think of DOM as a piece of land and JavaScript (meaning ECMAScript) as another piece of land, both connected with a toll bridge
-quote from John Hrvatin, Microsoft, MIX09, in this talk Building High Performance Web Applications and Sites at 29:38, also included in the O'reilly Javascript book by Nicholas C Zakas Pg 36, also mentioned in this post
In your code I see d3.select('#stacked-svg-tasks')
two times, as well as d3.selectAll('#stacked-svg-tasks')
. The last expression obviously works but the difference between .select()
and .selectAll()
is that the former uses document.querySelector()
whereas the latter uses document.querySelectorAll()
, which returns a NodeList instead of a single element - somewhat overkill for getting an element using the id attribute.
In the d3 documentation for .select()
we see the following:
If the selector is not a string, instead selects the specified node; this is useful if you already have a reference to a node1
Because you are using VueJS and have ref="stacked-svg-tasks"
on the <svg>
tag in the template, you can use $refs instead of querying the DOM each time to select that element.
So lines like this
const svg = d3.select("stacked-svg-tasks");
Could be updated like this:
const svg = d3.select(this.$refs['stacked-svg-tasks']);
I would recommend switching to camelCase or at least something without the hyphens -
<svg
ref="stackedSvgTasks"
Then the dot notation (instead of bracket notation) can be used like below:
const svg = d3.select(this.$refs.stackedSvgTasks);
Additionally, that reference svg
could be stored when the component is mounted (e.g. in the mounted method
) and re-used in other methods.
Beyond that, the code looks good. I like the use of const
by default for variables and only using let
for the iterator variable c
.
1https://github.com/d3/d3-selection#select
add a comment |
up vote
0
down vote
up vote
0
down vote
Avoid the Toll cost of crossing the DOM bridge
...think of DOM as a piece of land and JavaScript (meaning ECMAScript) as another piece of land, both connected with a toll bridge
-quote from John Hrvatin, Microsoft, MIX09, in this talk Building High Performance Web Applications and Sites at 29:38, also included in the O'reilly Javascript book by Nicholas C Zakas Pg 36, also mentioned in this post
In your code I see d3.select('#stacked-svg-tasks')
two times, as well as d3.selectAll('#stacked-svg-tasks')
. The last expression obviously works but the difference between .select()
and .selectAll()
is that the former uses document.querySelector()
whereas the latter uses document.querySelectorAll()
, which returns a NodeList instead of a single element - somewhat overkill for getting an element using the id attribute.
In the d3 documentation for .select()
we see the following:
If the selector is not a string, instead selects the specified node; this is useful if you already have a reference to a node1
Because you are using VueJS and have ref="stacked-svg-tasks"
on the <svg>
tag in the template, you can use $refs instead of querying the DOM each time to select that element.
So lines like this
const svg = d3.select("stacked-svg-tasks");
Could be updated like this:
const svg = d3.select(this.$refs['stacked-svg-tasks']);
I would recommend switching to camelCase or at least something without the hyphens -
<svg
ref="stackedSvgTasks"
Then the dot notation (instead of bracket notation) can be used like below:
const svg = d3.select(this.$refs.stackedSvgTasks);
Additionally, that reference svg
could be stored when the component is mounted (e.g. in the mounted method
) and re-used in other methods.
Beyond that, the code looks good. I like the use of const
by default for variables and only using let
for the iterator variable c
.
1https://github.com/d3/d3-selection#select
Avoid the Toll cost of crossing the DOM bridge
...think of DOM as a piece of land and JavaScript (meaning ECMAScript) as another piece of land, both connected with a toll bridge
-quote from John Hrvatin, Microsoft, MIX09, in this talk Building High Performance Web Applications and Sites at 29:38, also included in the O'reilly Javascript book by Nicholas C Zakas Pg 36, also mentioned in this post
In your code I see d3.select('#stacked-svg-tasks')
two times, as well as d3.selectAll('#stacked-svg-tasks')
. The last expression obviously works but the difference between .select()
and .selectAll()
is that the former uses document.querySelector()
whereas the latter uses document.querySelectorAll()
, which returns a NodeList instead of a single element - somewhat overkill for getting an element using the id attribute.
In the d3 documentation for .select()
we see the following:
If the selector is not a string, instead selects the specified node; this is useful if you already have a reference to a node1
Because you are using VueJS and have ref="stacked-svg-tasks"
on the <svg>
tag in the template, you can use $refs instead of querying the DOM each time to select that element.
So lines like this
const svg = d3.select("stacked-svg-tasks");
Could be updated like this:
const svg = d3.select(this.$refs['stacked-svg-tasks']);
I would recommend switching to camelCase or at least something without the hyphens -
<svg
ref="stackedSvgTasks"
Then the dot notation (instead of bracket notation) can be used like below:
const svg = d3.select(this.$refs.stackedSvgTasks);
Additionally, that reference svg
could be stored when the component is mounted (e.g. in the mounted method
) and re-used in other methods.
Beyond that, the code looks good. I like the use of const
by default for variables and only using let
for the iterator variable c
.
1https://github.com/d3/d3-selection#select
edited 9 hours ago
answered 10 hours ago
Sᴀᴍ Onᴇᴌᴀ
7,67561748
7,67561748
add a comment |
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
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f204586%2fd3-updating-stacked-bar-chart%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
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
Required, but never shown
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
Required, but never shown
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
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown