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:
enter image description here



When pressing the change bars layout the chart looks like this:



enter image description here



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:




  1. my code is inside vuejs framework.


  2. 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:




  1. Is there a way to use the d3 stack or some other method to create this kind of chart?


  2. 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>









share|improve this question




























    up vote
    1
    down vote

    favorite












    In my data there can be different number of rectangles in each stack.
    Here is a picture:
    enter image description here



    When pressing the change bars layout the chart looks like this:



    enter image description here



    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:




    1. my code is inside vuejs framework.


    2. 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:




    1. Is there a way to use the d3 stack or some other method to create this kind of chart?


    2. 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>









    share|improve this question


























      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:
      enter image description here



      When pressing the change bars layout the chart looks like this:



      enter image description here



      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:




      1. my code is inside vuejs framework.


      2. 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:




      1. Is there a way to use the d3 stack or some other method to create this kind of chart?


      2. 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>









      share|improve this question















      In my data there can be different number of rectangles in each stack.
      Here is a picture:
      enter image description here



      When pressing the change bars layout the chart looks like this:



      enter image description here



      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:




      1. my code is inside vuejs framework.


      2. 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:




      1. Is there a way to use the d3 stack or some other method to create this kind of chart?


      2. 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






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited 10 hours ago









      Sᴀᴍ Onᴇᴌᴀ

      7,67561748




      7,67561748










      asked Sep 29 at 16:06









      user4602966

      92




      92






















          1 Answer
          1






          active

          oldest

          votes

















          up vote
          0
          down vote













          Avoid the Toll cost of crossing the DOM bridge



          bridge toll




          ...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






          share|improve this answer























            Your Answer





            StackExchange.ifUsing("editor", function () {
            return StackExchange.using("mathjaxEditing", function () {
            StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
            StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
            });
            });
            }, "mathjax-editing");

            StackExchange.ifUsing("editor", function () {
            StackExchange.using("externalEditor", function () {
            StackExchange.using("snippets", function () {
            StackExchange.snippets.init();
            });
            });
            }, "code-snippets");

            StackExchange.ready(function() {
            var channelOptions = {
            tags: "".split(" "),
            id: "196"
            };
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function() {
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled) {
            StackExchange.using("snippets", function() {
            createEditor();
            });
            }
            else {
            createEditor();
            }
            });

            function createEditor() {
            StackExchange.prepareEditor({
            heartbeatType: 'answer',
            convertImagesToLinks: false,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: null,
            bindNavPrevention: true,
            postfix: "",
            imageUploader: {
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            },
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            });


            }
            });














             

            draft saved


            draft discarded


















            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

























            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



            bridge toll




            ...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






            share|improve this answer



























              up vote
              0
              down vote













              Avoid the Toll cost of crossing the DOM bridge



              bridge toll




              ...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






              share|improve this answer

























                up vote
                0
                down vote










                up vote
                0
                down vote









                Avoid the Toll cost of crossing the DOM bridge



                bridge toll




                ...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






                share|improve this answer














                Avoid the Toll cost of crossing the DOM bridge



                bridge toll




                ...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







                share|improve this answer














                share|improve this answer



                share|improve this answer








                edited 9 hours ago

























                answered 10 hours ago









                Sᴀᴍ Onᴇᴌᴀ

                7,67561748




                7,67561748






























                     

                    draft saved


                    draft discarded



















































                     


                    draft saved


                    draft discarded














                    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





















































                    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







                    Popular posts from this blog

                    404 Error Contact Form 7 ajax form submitting

                    How to know if a Active Directory user can login interactively

                    TypeError: fit_transform() missing 1 required positional argument: 'X'