如何将 id 和名称从 rails 传递给 d3

How to pass ids and names to d3 from rails

提问人:ASing 提问时间:8/1/2023 更新时间:8/26/2023 访问量:54

问:

与任何合理化的数据库一样,ID、名称和数据存储在单独的表中。当前脚本(如下)在图例中显示 ID,但我想传入并显示名称。问题是如何合并来自两个表的数据(一个带有名称,一个带有 ID),以便我可以将它们传递给 d3 图表以在图例和/或标签中显示数据和名称。我怀疑控制器中需要有一个哈希值,但我不确定如何。任何帮助将不胜感激。谢谢

class MacroSeries < ApplicationRecord
    has_many :macro_values
end

class MacroValue < ApplicationRecord
    belongs_to :macro_series
end

  • macro_series
    • 编号
    • series_name
  • macro_values
    • 编号
    • macro_series_id
    • 日期
    • 价值

控制器

@income_equality1 = MacroSeries.where(series_code: ['WFRBSTP1300', 'WFRBSB50215'])
@mv1 = MacroValue.where(macro_series_id: @income_equality1, geography_id: @geography_id)

视图

<%= render 'macro_values/macro_values_chart', chart_num: 1, chart_data1: @mv1, chart_type: 'area_stacked', x_value: 'date', y_value: 'value', cat1: 'macro_series_id' %>

D3 图表

    // INPUT
    // Data
    // Assign the json data to a variable
    // Number of records
    var last = <%= last %>;
    var data = <%= raw(chart_data1.last(last).to_json) %>;

    var x_value = <%= raw(x_value.to_json) %>;
    var date_format = <%= raw(date_format.to_json) %>;
    var x_label = <%= raw(x_label.to_json) %>;
    var x_format = <%= raw(x_format.to_json) %>;

    var y_value = <%= raw(y_value.to_json) %>;
    var y_label = <%= raw(y_label.to_json) %>;
    var y_format = <%= raw(y_format.to_json) %>;

    var z_value = <%= raw(z_value.to_json) %>; 
    var cat1 = <%= raw(cat1.to_json) %>;
    var cat2 = <%= raw(cat2.to_json) %>;
    
    var chart_num = <%= raw(chart_num.to_json) %>;
    var chart_type = <%= raw(chart_type.to_json) %>;
    var color = <%= raw(color.to_json) %>;
    var legend_names = <%= raw(legend.to_json) %>;

    // Chart specifics
    // Colors
    var series_colors = ["#af3700", "#eb8f00", "#006000", "#005490", "#660066", "#600000"];
    //var colors = d3.schemeTableau10; // array of colors for z

    // Dimensions and margins
    var margin = {top: 20, right: 30, bottom: 100, left: 40},
                width = <%= width %>,
                height = <%= height %>,
                xRange = [margin.left, width - margin.right],  // [left, right]
                yRange = [height - margin.bottom, margin.top]; // [bottom, top]
      
    // Append the SVG object to the page
    var svg = d3.select("#<%= 'chart_'+raw(chart_type)+raw(chart_data1.last.id) %>")
                .append("svg")
                .attr("width", width)
                .attr("height", height)
                .attr("viewBox", [0, 0, width, height])
                .attr("style", "max-width: 100%; height: auto; height: intrinsic;")
                .style("-webkit-tap-highlight-color", "transparent")
                .on("touchstart", event => event.preventDefault());// add event listener

    // Determine the series that need to be stacked
    var series = d3.stack()
                    .keys(d3.union(data.map(d => d[cat1])))                                     // distinct series keys, in input order
                    .value(([, D], key) => D.get(key)[y_value])                                 // get value for each series key and stack
                    (d3.index(data, d => d3.timeParse(date_format)(d[x_value]), d => d[cat1])); // group by stack then series key

    // Prepare the scales for positional and color encodings
    var x = d3.scaleUtc()
                .domain(d3.extent(data, d => d3.timeParse(date_format)(d[x_value])))
                .range([margin.left, width - margin.right]);

    var y = d3.scaleLinear()
                .domain([0, d3.max(series, d => d3.max(d, d => d[1]))])
                .rangeRound([height - margin.bottom, margin.top]).nice();

    var color = d3.scaleOrdinal()
                    .domain(series.map(d => d.key))
                    .range(series_colors);

    // Construct an area shape
    var area = d3.area()
                    .x(d => x(d.data[0]))
                    .y0(d => y(d[0]))
                    .y1(d => y(d[1]));

    // Add the y-axis, remove the domain line, add grid lines and a label
    svg.append("g")
        .attr("transform", `translate(${margin.left},0)`)
        .call(d3.axisLeft(y).ticks(height / 80))
        .call(g => g.select(".domain").remove())
        .call(g => g.selectAll(".tick line").clone()
            .attr("x2", width - margin.left - margin.right)
            .attr("stroke-opacity", 0.1))
        .call(g => g.append("text")
            .attr("x", -margin.left)
            .attr("y", margin.top)
            .attr("fill", "currentColor")
            .attr("text-anchor", "start")
            .text(y_label));

    // Append the x axis
    svg.append("g")
        .attr("transform", `translate(0,${height - margin.bottom})`)
        .call(d3.axisBottom(x).tickSizeOuter(0));

    // Append a path for each series
    svg.append("g")
        .selectAll()
        .data(series)
        .join("path")
        .attr("fill", d => color(d.key))
        .attr("d", area)
        .append("title")
        .text(d => d.key);

    // HIGHLIGHT GROUP

    // What to do when one group is hovered
    var highlight = function(event, d) {
                // reduce opacity of all groups
                d3.selectAll(".myArea").style("opacity", .1)
                // expect the one that is hovered
                d3.select("."+d).style("opacity", 1)
    }

    // And when it is not hovered anymore
    var noHighlight = function(event, d) {
        d3.selectAll(".myArea").style("opacity", 1)
    }

    // LEGEND

    var legendHolder = svg.append('g')
                            // translate the holder to the right side of the graph
                            //.attr('transform', "translate(" + (margin.left) + ",0)");
                            .attr("transform", `translate(10,${height - margin.bottom + 30})`)

    var legend = legendHolder.selectAll(".legend")
                             .data(series)
                             .enter().append("g")
                             .attr("class", "legend")
                             .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });

    var size = 18
    legend.append("rect")
            .attr("x", 10)
            .attr("width", size)
            .attr("height", size)
            .style("fill", d => color(d.key))
            .on("mouseover", highlight)
            .on("mouseleave", noHighlight);

    legend.append("text")
            .attr("x", margin.left)
            .attr("y", size / 2)
            .attr("dy", ".35em")
            .style("fill", d => color(d.key))
            .text(function(d, i) { return series[i].key; })
            .on("mouseover", highlight)
            .on("mouseleave", noHighlight);
JSON Ruby-on-Rails D3.js 哈希图

评论

0赞 kwerle 8/2/2023
我不确定你在问什么。您想知道如何将数据传递到 d3(d3 问题)或如何从控制器中获取数据以传递给 d3(rails 问题)..还是两者兼而有之?
0赞 ASing 8/2/2023
@kwerle,我能够从macros_values表/模型传递数据并绘制这些数据。但是,与该表中的macros_series_id关联的名称位于macro_series表中。如何从表中提取名称并将其与要绘制macro_series数据一起传递,以便在标签和图例中显示名称而不是 ID?先谢谢你

答:

0赞 ASing 8/26/2023 #1

执行此操作的方法是将控制器中的两个表联接起来,或者使用

  1. SQL算法
    MacroValue
      .joins("INNER JOIN macro_series ON macro_values.macro_series_id = macro_series.id")
      .where(:macro_values => {:macro_series_id => @unemployment)
      .select( 'date', 'value', 'series_name')
  1. Rails 函数 .joins 或 .includes
    MacroValue
       .joins(:macro_series)
       .where(macro_series_id: @unemployment)
       .select( 'date', 'value', 'series_name' )

然后,只需在 D3 中引用 select 语句中的字段即可。

这些帖子很有帮助: