为 D3 可视化创建平均 Y 线?

Creating an average Y line for D3 visualization?

我正在尝试创建可视化来帮助与我一起工作的学生了解拟合度量,例如 R 平方。对于 R 平方,我想在图表上同时包含回归线和 Y 均值线。 (我的最终目标是在点之间有线 and/or 代表 ESS、TSS 和 SSR,学生可以点击查看或不查看)。

我的回归线工作正常,但是当我尝试添加我的平均线时,它以一个奇怪的起点和终点结束,并且明显不是平均线 (4.4) 的平线。我的控制台中也出现以下错误:

Error: Invalid value for <path> attribute d="M112,235.71428571428572L194,119.14285714285712L276,NaNL358,NaNL440,NaN"

对应代码行:

.attr({

在我的 avg.append("path") 中用于我的 avgline(至少,我认为这就是我在那里指定的原因):

svg.append("path")
                .datum(avgdataset)
                .attr({
                d: avgline,
                stroke: "green",
                "stroke-width": 1,
                fill: "none",
                "stroke-dasharray": "5,5",
                });

我试过如何指定 avgline 直到没完没了(这种尝试通常最终不会产生任何行)。我也尝试过使用数据而不是数据,但无济于事。我可能犯了一个非常基本的错误,因为我是 javascript 和 D3 的新手。

这是我迄今为止的所有代码,将其放在上下文中:

//Width and height
var w = 500;
var h = 300;
var padding = 30;

var dataset = [
  [1, 1],
  [2, 5],
  [3, 4],
  [4, 7],
  [5, 5]
];

//Create scale functions
var xScale = d3.scale.linear()
  .domain([0, d3.max(dataset, function(d) {
    return d[0];
  })])
  .range([padding, w - padding * 2]);

var yScale = d3.scale.linear()
  .domain([0, d3.max(dataset, function(d) {
    return d[1];
  })])
  .range([h - padding, padding]);

var rScale = d3.scale.linear()
  .domain([0, d3.max(dataset, function(d) {
    return d[1];
  })])
  .range([2, 5]);

//Define X axis
var xAxis = d3.svg.axis()
  .scale(xScale)
  .orient("bottom")
  .ticks(5);

//Define Y axis
var yAxis = d3.svg.axis()
  .scale(yScale)
  .orient("left")
  .ticks(5);

//Create SVG element
var svg = d3.select("body")
  .append("svg")
  .attr("width", w)
  .attr("height", h);

//Create circles
svg.selectAll("circle")
  .data(dataset)
  .enter()
  .append("circle")
  .attr("cx", function(d) {
    return xScale(d[0]);
  })
  .attr("cy", function(d) {
    return yScale(d[1]);
  })
  .attr("r", 4)
  .append("svg:title")
  .text(function(d) {
    return d[0] + "," + d[1];
  });;

//average stuff
var sum = 0,
  average;
for (var i = 0; i < dataset.length; i++) {
  sum += dataset[i][1];
}
average = sum / dataset.length;

console.log(average);

var avgdataset = [
  [1, average],
  [2, average],
  [3, average],
  [4, average],
  [5, average]
];

console.log(avgdataset);
document.write(avgdataset);

//Create labels
/*svg.selectAll("text")
     .data(dataset)
     .enter()
     .append("text")
     .text(function(d) {
       return d[0] + "," + d[1];
     })
     .attr("x", function(d) {
       return xScale(d[0]);
     })
     .attr("y", function(d) {
       return yScale(d[1]);
     })
     .attr("font-family", "sans-serif")
     .attr("font-size", "11px")
     .attr("fill", "red");
  */
//Create X axis
svg.append("g")
  .attr("class", "axis")
  .attr("transform", "translate(0," + (h - padding) + ")")
  .call(xAxis);

//Create Y axis
svg.append("g")
  .attr("class", "axis")
  .attr("transform", "translate(" + padding + ",0)")
  .call(yAxis);

var lr = ss.linear_regression().data(dataset).line();
var forecast_x = 20
console.log(lr)
var lrline = d3.svg.line()
  .x(function(d, i) {
    return xScale(i);
  })
  .y(function(d, i) {
    return yScale(lr(i));
  });

svg.append("path")
  .datum(Array(dataset.length * forecast_x))
  .attr({
    d: lrline,
    stroke: "black",
    "stroke-width": 1,
    fill: "none",
    "stroke-dasharray": "5,5",
  });

var avgline = d3.svg.line()
  //.x(function(d, i) { return xScale(i); })
  //.y(function(d, i) { return yScale(avgdataset(i)); });
  .x(function(d, i) {
    return xScale(d[0]);
  })
  .y(function(d, i) {
    return yScale(d[i]);
  });


svg.append("path")
  .datum(avgdataset)
  .attr({
    d: avgline,
    stroke: "green",
    "stroke-width": 1,
    fill: "none",
    "stroke-dasharray": "5,5",
  });


//to get the m and b for the equation line
var mvalue = ss.linear_regression().data(dataset).m();
console.log(mvalue);

var bvalue = ss.linear_regression().data(dataset).b();
console.log(bvalue);

//equation written out
svg.append("text")
  .text("Y= " + mvalue + "x + " + bvalue)
  .attr("class", "text-label")
  .attr("x", 60)
  .attr("y", 30);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://raw.github.com/tmcw/simple-statistics/master/src/simple_statistics.js"></script>

我想你差不多明白了,只是 avgdataset 不是一个函数而是一个数组。

只需替换

var avgline = d3.svg.line()
  //.x(function(d, i) { return xScale(i); })
  //.y(function(d, i) { return yScale(avgdataset(i)); });
  .x(function(d, i) {
    return xScale(d[0]);
  })
  .y(function(d, i) {
    return yScale(d[i]);
  });

var avgline = d3.svg.line()
    .x(function(d, i) { return xScale(i); })
    .y(function(d, i) { return yScale(avgdataset[i][1]); });

    //Width and height
    var w = 500;
    var h = 300;
    var padding = 30;

    var dataset = [[1, 1], [2, 5], [3, 4], [4, 7], [5, 5]];

    //Create scale functions
    var xScale = d3.scale.linear()
                         .domain([0, d3.max(dataset, function(d) { return d[0]; })])
                         .range([padding, w - padding * 2]);

    var yScale = d3.scale.linear()
                         .domain([0, d3.max(dataset, function(d) { return d[1]; })])
                         .range([h - padding, padding]);

    var rScale = d3.scale.linear()
                         .domain([0, d3.max(dataset, function(d) { return d[1]; })])
                         .range([2, 5]);

    //Define X axis
    var xAxis = d3.svg.axis()
                      .scale(xScale)
                      .orient("bottom")
                      .ticks(5);

    //Define Y axis
    var yAxis = d3.svg.axis()
                      .scale(yScale)
                      .orient("left")
                      .ticks(5);

    //Create SVG element
    var svg = d3.select("body")
                .append("svg")
                .attr("width", w)
                .attr("height", h);

    //Create circles
    svg.selectAll("circle")
       .data(dataset)
       .enter()
       .append("circle")
       .attr("cx", function(d) {
            return xScale(d[0]);
       })
       .attr("cy", function(d) {
            return yScale(d[1]);
       })
       .attr("r", 4
       )
       .append("svg:title")
       .text(function(d){return d[0] + "," + d[1];});;

    //average stuff
    var sum = 0, average;
    for (var i = 0; i < dataset.length; i++) {
        sum += dataset[i][1];
    }
    average = sum / dataset.length;

    console.log(average);       

    var avgdataset = [[1, average], [2, average], [3, average], [4, average], [5, average]];

    console.log(avgdataset);
    document.write(avgdataset);

    //Create labels
    /*svg.selectAll("text")
       .data(dataset)
       .enter()
       .append("text")
       .text(function(d) {
            return d[0] + "," + d[1];
       })
       .attr("x", function(d) {
            return xScale(d[0]);
       })
       .attr("y", function(d) {
            return yScale(d[1]);
       })
       .attr("font-family", "sans-serif")
       .attr("font-size", "11px")
       .attr("fill", "red");
    */
    //Create X axis
    svg.append("g")
        .attr("class", "axis")
        .attr("transform", "translate(0," + (h - padding) + ")")
        .call(xAxis);

    //Create Y axis
    svg.append("g")
        .attr("class", "axis")
        .attr("transform", "translate(" + padding + ",0)")
        .call(yAxis);

    var lr = ss.linear_regression().data(dataset).line();
    var forecast_x = 20
    console.log(lr)
    var lrline = d3.svg.line()
        .x(function(d, i) { return xScale(i); })
        .y(function(d, i) { return yScale(lr(i)); });

    svg.append("path")
        .datum(Array(dataset.length*forecast_x))
        .attr({
        d: lrline,
        stroke: "black",
        "stroke-width": 1,
        fill: "none",
        "stroke-dasharray": "5,5",
        });

    var avgline = d3.svg.line()
        .x(function(d, i) { return xScale(i); })
        .y(function(d, i) { return yScale(avgdataset[i][1]); });


    svg.append("path")
        .datum(avgdataset)
        .attr({
        d: avgline,
        stroke: "green",
        "stroke-width": 1,
        fill: "none",
        "stroke-dasharray": "5,5",
        });


    //to get the m and b for the equation line
    var mvalue = ss.linear_regression().data(dataset).m();
    console.log(mvalue);

    var bvalue = ss.linear_regression().data(dataset).b();
    console.log(bvalue);

    //equation written out
    svg.append("text")
        .text("Y= " + mvalue + "x + " + bvalue)
        .attr("class", "text-label")
        .attr("x", 60)
        .attr("y", 30);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://raw.github.com/tmcw/simple-statistics/master/src/simple_statistics.js"></script> 

与 Kaiido 类似,在我看来 avgline 功能是问题所在。您正在传递一个数组数组,而 xy 没有访问数组的正确部分。我使用过的大多数示例都传递了一个对象数组,例如:

var data = [ {x: 1, y: 4.4}, {x:2, y:4.4}, etc];

如果您构建这样的对象,您可以简单地将其传递给 avgline,然后它可以优雅地访问数据的正确部分,例如:

var avgline = d3.svg.line() //changed x and y function to reflect changed data
    .x(function(d, i) {
        return xScale(d.x);
    })
    .y(function(d, i) {
        return yScale(d.y);
    });

这样做有很多好处。例如,您可以确保所有数据都对应于此结构,然后您只需要一个行构造函数而不是两个。