tdpu
tdpu

Reputation: 708

How do you create a reusable Vue component with d3?

I'm trying to create a Chart.vue component based on D3. I need to be able to add multiple instances to a single page and keep each one separate.

I've tried to assign an ID generated with uuid to the div wrapping my component in the template:

<template>
  <div :id=this.id>
    <svg></svg>
  </div>
</template>

The ID is created when the component is created.

<script>
import * as d3 from "d3";
import { v4 as uuidv4 } from "uuid";

export default {
  ...
  created () {
    this.id = uuidv4()
  }, 
  ...

The chart is re-rendered when there is an update to the data passed in as props from the parent App.vue. To select the "correct" <svg> element that is owned by a particular instance of Chart I use the unique this.id in my renderChart method:

  methods: {
    renderChart(chart_data) {
      const svg_width = 1000;
      const svg_height = 600;
  
      const svg = d3
        .select("#" + this.id)
        .select("svg")
        .attr("width", svg_width)
        .attr("height", svg_height);
      ...

Proceeding to add all the axes, data, etc.

If I add two such components to my App.vue template:

<template>
  <div id="app">
    <form action="#" @submit.prevent="getIssues">
      <div class="form-group">
        <input
          type="text"
          placeholder="owner/repo Name"
          v-model="repository"
          class="col-md-2 col-md-offset-5"
        >
      </div>
    </form>
    <Chart :issues="issues" />
    <Chart :issues="issues" />
  </div>
</template>

I see that they are added to the DOM with some uuid that's been created. When the data is updated and the renderChart function executes, both components get a copy of the "issues" data, but I only see one chart being created.

I'm quite novice with JavaScript, Vue and D3, so perhaps going about this the wrong way, but it seems like this should work?

Any help is appreciated.

Upvotes: 0

Views: 774

Answers (1)

tdpu
tdpu

Reputation: 708

Well, I seem to have found a solution, although I don't fully understand it, and I'm not sure why the initial approach didn't work (note: original approach did seem to work sometimes, but the behaviour was unpredictable).

To solve, I pass a unique ID to the component from the parent template as a prop and add it as the Chart component <div> id.

In App.vue:

<template>
  <div id="app">
    <form action="#" @submit.prevent="getIssues">
      <div class="form-group">
        <input
          type="text"
          placeholder="owner/repo Name"
          v-model="repository"
          class="col-md-2 col-md-offset-5"
        >
      </div>
    </form>
    <Chart id="chart1" :issues="issues" />
    <Chart id="chart2" :issues="issues" />        
  </div>
</template>

Now I need to add id in the props of the Chart.vue and set a variable in the data() section.

<template>
  <div :id=chart_id>
    <svg></svg>
  </div>
</template>

<script>
import * as d3 from "d3";

export default {
  name: 'Chart',
  props: ["issues", "id"],
  
  data() {
    return {
      chart: null,
      chart_id: this.id
    };
  },
  ...

I'm not sure why the uuid approach didn't work, but this seems more robust.

Upvotes: 0

Related Questions