D3更新条形图VUE.js

D3更新条形图VUE.js,vue.js,d3.js,methods,bar-chart,data-visualization,Vue.js,D3.js,Methods,Bar Chart,Data Visualization,嘿,我对Vue中的d3图表有问题。除了更新数据外,其他一切都很好 我使用道具传递数据,当数据改变我的图表而不是更新时,它会在当前图表的正下方创建另一个图表 我想做的是保持当前图形,并在数据发生变化时仅更新条形图 <script> export default { props: { arr: { type: Array }, colors: { type: Array } }, w

嘿,我对Vue中的d3图表有问题。除了更新数据外,其他一切都很好

我使用道具传递数据,当数据改变我的图表而不是更新时,它会在当前图表的正下方创建另一个图表

我想做的是保持当前图形,并在数据发生变化时仅更新条形图

<script>
export default {
    props: {
      arr: {
          type: Array
      }, 
      colors: {
          type: Array
      }
  },
  watch: {
    arr: {
      immediate: true,
      handler(val) {
        setTimeout(() => {
          this.barCharts(val)
        }, 100);
      }
    }
  },
  methods: {
    barCharts(data) {
      let margin = { top: 40, right: 20, bottom: 50, left: 60 };
      let width = 650
      let height = 240; 

      let color = d3.scaleOrdinal().range(this.colors);
      let t = d3.transition().duration(750);

      let g = d3
        .select("#barChart")
        .append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", "100%")
        .append("g")
        .attr("transform", `translate(${margin.left}, ${margin.top})`);

      let xAxisGroup = g
        .append("g")
        .attr("class", "x axis")
        .attr("transform", `translate(0, ${height})`);

      let yAxisGroup = g.append("g").attr("class", "y axis");

      // X Scale
      let x = d3
        .scaleBand()
        .range([0, width])
        .padding(0.2);

      // Y Scale
      let y = d3.scaleLinear().range([height, 0]);

      // axis
      x.domain(data.map(d => d.name));
      y.domain([
        0,
        d3.max(data, function(d) {
          return d.value;
        })
      ]);

      g.append("g")
        .attr("transform", "translate(0," + height + ")")
        .call(d3.axisBottom(x))
        .attr("class", "axis")
        .selectAll("text")
        .style("text-anchor", "end")
        .attr("dx", "-.8em")
        .attr("dy", ".15em")
        .attr("transform", "rotate(-30)");

      g.append("g")
        .call(d3.axisLeft(y))
        .attr("class", "axis");

      // draw bars
      let rects = g.selectAll("rect").data(data);

      rects
        .transition(t)
        .attr("y", function(d) {
          return y(d.value);
        })
        .attr("x", function(d) {
          return x(d.name);
        })
        .attr("height", function(d) {
          return height - y(d.value);
        })
        .attr("width", x.bandwidth())
        .attr("fill", (d, i) => color(i));

      rects
        .enter()
        .append("rect")
        .attr("x", function(d) {
          return x(d.name);
        })
        .attr("width", x.bandwidth())
        .attr("fill", (d, i) => color(i))
        .attr("y", y(0))
        .attr("height", 0)
        .transition(t)
        .attr("y", function(d) {
          return y(d.value);
        })
        .attr("height", function(d) {
          return height - y(d.value);
        });

    }
  }
};
}
</script>

导出默认值{
道具:{
arr:{
类型:数组
}, 
颜色:{
类型:数组
}
},
观察:{
arr:{
立即:是的,
处理程序(val){
设置超时(()=>{
这是条形图(val)
}, 100);
}
}
},
方法:{
条形图(数据){
让边距={顶部:40,右侧:20,底部:50,左侧:60};
宽度=650
设高度=240;
设color=d3.scaleOrdinal().range(this.colors);
设t=d3.transition().duration(750);
设g=d3
.选择(“条形图”)
.append(“svg”)
.attr(“宽度”,宽度+边距。左侧+边距。右侧)
.attr(“高度”、“100%”)
.附加(“g”)
.attr(“transform”,`translate(${margin.left},${margin.top})`);
设xAxisGroup=g
.附加(“g”)
.attr(“类”、“x轴”)
.attr(“transform”,`translate(0,${height})`);
设yAxisGroup=g.append(“g”).attr(“class”,“y轴”);
//X刻度
设x=d3
.scaleBand()
.范围([0,宽度])
.填充(0.2);
//Y刻度
设y=d3.scaleLinear().range([height,0]);
//轴心
x、 域(data.map(d=>d.name));
y、 领域([
0,
d3.最大值(数据、函数(d){
返回d值;
})
]);
g、 附加(“g”)
.attr(“变换”、“平移(0)”、“高度+”)
.call(d3.axisBottom(x))
.attr(“类”、“轴”)
.selectAll(“文本”)
.style(“文本锚定”、“结束”)
.attr(“dx”,“-.8em”)
.attr(“dy”,“.15em”)
.attr(“变换”、“旋转(-30)”);
g、 附加(“g”)
.呼叫(d3.左(y))
.attr(“类”、“轴”);
//拉杆
设rects=g.selectAll(“rect”).data(数据);
直肠
.过渡(t)
.attr(“y”,函数(d){
返回y(d值);
})
.attr(“x”,函数(d){
返回x(d.name);
})
.attr(“高度”,功能(d){
返回高度-y(d值);
})
.attr(“宽度”,x.带宽())
.attr(“填充”(d,i)=>颜色(i));
直肠
.输入()
.append(“rect”)
.attr(“x”,函数(d){
返回x(d.name);
})
.attr(“宽度”,x.带宽())
.attr(“填充”(d,i)=>颜色(i))
.attr(“y”,y(0))
.attr(“高度”,0)
.过渡(t)
.attr(“y”,函数(d){
返回y(d值);
})
.attr(“高度”,功能(d){
返回高度-y(d值);
});
}
}
};
}
我是否可以实现另一个函数,例如update,在该函数内部调用,以仅更新bard


提前谢谢

生成新的条形图是因为在
barCharts
方法中将新的SVG元素添加到目标中。每次调用它时,它都会附加一个带有更新数据的新SVG,从而导致出现重复图表的问题

由于Vue有自己的呈现和更新DOM元素的方法,所以不应该使用d3来实现这一点,因为这会影响Vue的反应性和清理(这意味着可能会导致内存泄漏或元素未被删除)

我的建议是在vue
中定义
和其他元素,并使用v-bind指令将条形图值传递给vue呈现的SVG元素。例如,您可以使用
d3 scale
中的方法在组件中创建
x
y
barColor
方法/计算属性。然后可以将每个条的结果绑定到模板中的正确属性,例如
或类似属性

让我知道这是否足以让你上路,否则我今天晚些时候可以给你写一个完整的例子。我还推荐这些结合vue和d3的视频,它们对我很有洞察力:


    • 生成一个新的条形图是因为在
      条形图方法中将一个新的SVG元素附加到目标中。每次调用它时,它都会附加一个带有更新数据的新SVG,从而导致出现重复图表的问题

      由于Vue有自己的呈现和更新DOM元素的方法,所以不应该使用d3来实现这一点,因为这会影响Vue的反应性和清理(这意味着可能会导致内存泄漏或元素未被删除)

      我的建议是在vue
      中定义
      和其他元素,并使用v-bind指令将条形图值传递给vue呈现的SVG元素。例如,您可以使用
      d3 scale
      中的方法在组件中创建
      x
      y
      barColor
      方法/计算属性。然后可以将每个条的结果绑定到模板中的正确属性,例如
      或类似属性

      让我知道这是否足以让你上路,否则我今天晚些时候可以给你写一个完整的例子。我还推荐这些结合vue和d3的视频,它们对我很有洞察力:


      根据你的建议,我已经改变了这一点,我可以承认这非常棒!!它工作得很好,只是有一个过渡问题。我不太确定我应该把它放在哪里。。。在输入时,我希望将y从0转换为条形图的高度,然后在数据更改时进行另一次转换

      再次感谢您的帮助

      <template>
        <svg class="barChart" :width="width" height="340">
          <g>
          <!-- axis bottom -->
             <g class="x-axis" fill="none" :transform="`translate(0, ${height})`">
              <path stroke="currentColor" :d="`M0.5,6V0.5H${width}.5V6`"></path>
              <g
                class="tick"
                opacity="1"
                font-size="10"
                font-family="sans-serif"
                text-anchor="middle"
                v-for="(bar, index) in bars"
                :key="index"
                :transform="`translate(${bar.x + bar.width / 2}, 0)`"
              >
                <line stroke="currentColor" y2="6"></line>
                <text fill="currentColor" y="9" dy="0.71em">{{ bar.xLabel }}</text>
              </g>
            </g>
      
            <!-- y-axis -->
            <g class="y-axis" fill="none" :transform="`translate(0, 0)`">
              <path
                class="domain"
                stroke="currentColor"
                :d="`M0.5,${height}.5H0.5V0.5H-6`"
              ></path>
              <g
                class="tick"
                opacity="1"  
                font-size="10"
                font-family="sans-serif"
                text-anchor="end"
                v-for="(tick, index) in yTicks"
                :key="index"
                :transform="`translate(0, ${y(tick) + 0.5})`"
              >
                <line stroke="currentColor" x2="-6"></line>
                <text fill="currentColor" x="-9" dy="0.32em">{{ tick }}</text>
              </g>
            </g>
            <!-- bars -->
            <g class="bars" fill="none">
              <rect 
                v-for="(bar, index) in bars"
                :fill="color(bar.xLabel)"
                :key="index"
                :height="bar.height"
                :width="bar.width"
                :x="bar.x"
                :y="bar.y"
              ></rect>
            </g>
          </g>
        </svg>
      </template>
      <script>
      import * as d3 from "d3";
      import { scaleBand, scaleLinear } from 'd3-scale'
      export default { 
        props: {
          dataset: {
            type: Array
          }, 
          colors: {
            type: Array
          }
        },
        data() {
          return {
            height: 240,
            width: 650
          }
        },
        computed: {
          color() {
            return d3.scaleOrdinal().range(this.colors); 
          },
          yTicks() {
            return this.y.ticks(5);
          },
          x() {
            return scaleBand()
            .range([0, this.width])
            .padding(0.3)
            .domain(this.dataset.map(e => e[0]));
          },
      
          y() {
            let values = this.dataset.map(e => e[1]);
            return scaleLinear()
            .range([this.height, 0])
            .domain([0, Math.max(...values)]);
          },
          bars() {
            let bars = this.dataset.map(d => {
              return {
                xLabel: d[0],
                x: this.x(d[0]),
                y: this.y(d[1]),
                width: this.x.bandwidth(),
                height: this.height - this.y(d[1])
              };
            });
            return bars;
          }
        },
      }
      </script>
      
      
      {