小型多折线图的独立刻度

Independent scales for small multiple line chart

Link to the notebook.

我正在 Observable 上使用 d3.v5 制作一个小型多折线图,数据集的结构如下:

对于可视化,y 尺度从域的 values 数组中获取 num。有几行具有唯一的键值,我想用它们来产生小的倍数。上图显示了第一个键。

可视化小倍数后,我注意到所有折线图都使用相同的 y 刻度,这不是我想要做的。这是我目前拥有的:

  const y_scale = d3
    .scaleLinear()
    .domain([0, d3.max(series, d => d3.max(d.values, m => m.num))])
    .range([width/2, width/2 - start_y - margin.bottom]);

有没有办法调整域,使每个图表根据自己的 num 值有自己的比例?

编辑 1: 笔记本 link 添加到顶部

这里惯用的 D3 解决方案将使用 local variables。但是,有几种不同的工作选择。

为了使用局部变量,我们首先声明它们...

const localScale = d3.local();
const localLine = d3.local();

然后,我们在"enter"选择中设置不同的比例:

var enter = my_group
    .enter()
    .append("g")
    .attr("class", "chart_group")
    .each(function(d) {
        const yScale = localScale.set(this, d3
            .scaleLinear()
            .domain([0, d3.max(d.values, d => d.num)])
            .range([panel_width / 2, panel_width / 2 - start_y - margin]));

        localLine.set(this, d3
            .line()
            .x(d => x_scale(d.date))
            .y(d => yScale(d.num)));
    });

最后,我们得到了那些尺度:

sub_group
    .select(".chart_line")
    .attr("d", function(d) {
        return localLine.get(this)(d)
    })

这是整个单元格,copy/paste这个在你的笔记本里,替换你的单元格:

chart = {
    const panels_per_row = 4;
    const panel_width = (width - margin * 8) / panels_per_row;
    const height =
        margin + (panel_width + margin) * (parseInt(my_data.length / 2) + 1);

    const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);
    const start_x = 2;
    const start_y = panel_width / 3 + margin;

    const x_scale = d3
        .scaleBand()
        .domain(d3.set(series[0].values, d => d.date).values())
        .range([0, panel_width]);
    const localScale = d3.local();
    const localLine = d3.local();

    //join
    var my_group = svg.selectAll('.chart_group').data(series, d => d.key);

    //exit and remove
    my_group.exit().remove();
    //enter new groups
    var enter = my_group
        .enter()
        .append("g")
        .attr("class", "chart_group")
        .each(function(d) {
            const yScale = localScale.set(this, d3
                .scaleLinear()
                .domain([0, d3.max(d.values, d => d.num)])
                .range([panel_width / 2, panel_width / 2 - start_y - margin]));

            localLine.set(this, d3
                .line()
                .x(d => x_scale(d.date))
                .y(d => yScale(d.num)));
        });

    //append elements to new group
    enter.append("rect").attr("class", "group_rect");
    enter.append("text").attr("class", "group_text");
    enter.append("g").attr("class", "sub_chart_group");

    //merge
    my_group = my_group.merge(enter);

    position_group_elements(my_group);

    //join
    var sub_group = my_group
        .select(".sub_chart_group")
        .selectAll('.sub_chart_elements_group')
        .data(d => [d.values]); // data is wrapped in an array because this is a line/area chart

    //exit and remove
    sub_group.exit().remove();
    //enter new groups
    var sub_enter = sub_group
        .enter()
        .append("g")
        .attr("class", "sub_chart_elements_group");

    //append elements to new group
    sub_enter.append("path").attr("class", "chart_line");

    //merge
    sub_group = sub_group.merge(sub_enter);

    sub_group
        .select(".chart_line")
        .attr("d", function(d) {
            return localLine.get(this)(d)
        })
        .attr("fill", "none")
        .attr("stroke", "black")
        .attr("stroke-width", 1)
        .attr("transform", "translate(" + start_x + "," + start_y + ")");

    function position_group_elements(my_group) {
        //position rectangle
        my_group
            .select(".group_rect")
            .attr("x", function(d, i) {
                //two groups per row so
                var position = i % panels_per_row;
                d.x_pos = position * (panel_width + margin) + margin;
                d.y_pos =
                    parseInt(i / panels_per_row) * (panel_width + margin) + margin;
                return d.x_pos;
            })
            .attr("y", d => d.y_pos)
            .attr("fill", "#eee")
            .attr("stroke", "#aaa")
            .attr("stroke-width", 1)
            .attr("width", panel_width)
            .attr("height", panel_width);

        //then position sub groups
        my_group
            .select(".sub_chart_group")
            .attr("id", d => d.key)
            .attr("transform", d => "translate(" + d.x_pos + "," + d.y_pos + ")");
    }

    return svg.node();
}