Pass div id to javascript function to show multiple d3 charts on a page

58 Views Asked by At

I am trying to display multiple d3 charts on a page using Rails 7 using different div (or svg) ids:

view.html.erb

<%= content_tag "svg", class: 'chart', id: raw(chart_type)+'_'+raw(chart_num), data: {chart_type: chart_type, chart_num: chart_num} do %>
<script>
    line('#line_1');
</script>
<% end %>

javascript\component\charts\line.js

import * as d3 from "d3";

function line(chart_id) {

   const svg = d3.select('#line_1')
        .attr('width', 400)
        .attr('height', 400)
        .style('background-color', 'black');

    svg.append(chart_id)
        .style("stroke", "lightgreen")
        .style("stroke-width", 10)
        .attr("x1", 0)
        .attr("y1", 0)
        .attr("x2", 200)
        .attr("y2", 200);
}

importmap. pin_all_from "app/javascript/components", under: "components"

application.js import "components/charts/line"

I have tried the suggestions in this posts: How to get a Rails variable into JavaScript file (D3) mainly because the div/svg id is the data that needs to be passed. Also, I know how to do this with in-line javascript but I can't figure out how to pass the div id as a variable to the chart as a function.

Any help would be greatly appreciated.

1

There are 1 best solutions below

2
Alex On BEST ANSWER

I'm not exactly clear what you're asking here, but I see a couple of obvious issues. Because we're working with modules, line function is not global, it has to be exported/imported where needed or it has to be explicitly made global.

import * as d3 from "d3";

function line (chart_id) {
  const svg = d3.select(chart_id)
    .attr('width', 400)
    .attr('height', 400)
    .style('background-color', 'black');

  svg.append("line")
    .style("stroke", "lightgreen")
    .style("stroke-width", 10)
    .attr("x1", 0)
    .attr("y1", 0)
    .attr("x2", 200)
    .attr("y2", 200);
}

window.line = line;

// or like this 
//
// window.line = function (chart_id) { 
//   ...
// }

Another issue is plain script tags get loaded before module scripts, so line function would not be defined yet:

<script type="module">
  line("#line_1");
</script>

You might want to consider making a Stimulus controller instead of making inline scripts and adding global functions:
https://stimulus.hotwired.dev/

$ bin/rails stimulus chart
// app/javascript/controllers/chart_controller.js

import { Controller } from "@hotwired/stimulus"
import * as d3 from "d3";

// Connects to data-controller="chart"
export default class extends Controller {
  // https://stimulus.hotwired.dev/reference/values
  static values = {
    type: String,
    num: Number,
  }

  connect() {
    // this.typeValue
    // this.numValue

    this.line();
  }

  line() {
    const svg = d3.select(this.element)
      .attr('width', 400)
      .attr('height', 400)
      .style('background-color', 'black');

    svg.append("line")
      .style("stroke", "lightgreen")
      .style("stroke-width", 10)
      .attr("x1", 0)
      .attr("y1", 0)
      .attr("x2", 200)
      .attr("y2", 200);
  }
}
# app/views/home/index.html.erb

<%= tag.svg data: {
  controller: "chart",
  chart_type_value: "line",
  chart_num_value: 1
} %>