简体   繁体   English

d3 用于说明折线图中的缺失数据

[英]d3 to account for missing data in line chart

I am using d3.js to create a data-driven path .我正在使用d3.js创建一个数据驱动的path But the path is not accounting for missing data.但是path没有考虑丢失的数据。

For example,例如,

 const src = [{ "Region": "East Asia & Pacific", "Name": "Australia", "Year": 1997, "Value": 15.540540540540499 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 1998, "Value": 15.540540540540499 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 1999, "Value": 22.4489795918367 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2000, "Value": 22.972972972973 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2002, "Value": 25.3333333333333 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2003, "Value": 25.3333333333333 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2004, "Value": 24.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2005, "Value": 24.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2006, "Value": 24.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2007, "Value": 26.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2008, "Value": 26.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2009, "Value": 27.3333333333333 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2010, "Value": 24.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2011, "Value": 24.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2012, "Value": 24.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2013, "Value": 26 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2014, "Value": 26 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2015, "Value": 26.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2016, "Value": 28.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2017, "Value": 28.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2018, "Value": 28.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2019, "Value": 30.463576158940398 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2020, "Value": 30.463576158940398 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2021, "Value": 31.125827814569501 } ] //////////////////////////////////////////////////////////// //////////////////////// 1 CREATE SVG //////////////////// //////////////////////////////////////////////////////////// const width = 1280; const height = 720; const svgns = 'http://www.w3.org/2000/svg' const svg = d3.select('svg') svg //.attr('xmlns', svgns) .attr('viewBox', `0 0 ${width} ${height}`) //---create a rect covering viewBox --- to be deleted later svg.append('rect') .attr('class', 'vBoxRect') .attr('width', `${width}`) .attr('height', `${height}`) .attr('fill', 'none') .attr('stroke', 'red') //////////////////////////////////////////////////////////// //////////////////////// 2 CREATE BOUND //////////////////// //////////////////////////////////////////////////////////// const padding = { top: 70, bottom: 50, left: 70, right: 190 } const boundHeight = height - padding.top - padding.bottom; const boundWidth = width - padding.right - padding.left; //create BOUND rect -- to be deleted later svg.append('rect') .attr('class', 'boundRect') .attr('x', `${padding.left}`) .attr('y', `${padding.top}`) .attr('width', `${boundWidth}`) .attr('height', `${boundHeight}`) .attr('fill', 'none') .attr('stroke', 'green') //create bound element const bound = svg.append('g') .attr('class', 'bound') //specify transform, must be .style and not .attr, px needs to be mentioned .style('transform', `translate(${padding.left}px,${padding.top}px)`) //////////////////////////////////////////////////////////// //////////////////////// 3 CREATE SCALE //////////////////// //////////////////////////////////////////////////////////// const scaleX = d3.scaleLinear() .range([0, boundWidth]) .domain(d3.extent(src, d => d.Year)) const scaleY = d3.scaleLinear() .range([boundHeight, 0]) .domain(d3.extent(src, d => d.Value)) //////////////////////////////////////////////////////////// //////////////////////// 4 CREATE AXIS //////////////////// //////////////////////////////////////////////////////////// bound.append('g').attr('class', 'yAxis') .append('g').attr('class', 'yAxisDetail') .call(d3.axisLeft(scaleY) ) const data2 = src.map(d => d.Year) const count = [...new Set(data2)].length - 1 const minYear = Math.min([...src]) bound.append('g') .attr('class', 'xAxis') .append('g') .attr('class', 'xAxisBottom') .style('transform', `translateY(${boundHeight}px)`) .call(d3.axisBottom(scaleX).ticks(count).tickFormat(d3.format("d"))) //////////////////////////////////////////////////////////// //////////////////////// 5 CREATE PATH //////////////////// //////////////////////////////////////////////////////////// const line = d3.line() .x(d => scaleX(d.Year)) .y(d => scaleY(d.Value)) bound.append('g') .attr('class', 'valLine') .append('path') .attr('d', line(src)) .attr('stroke', 'black') .attr('fill', 'none')
 <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script> <body> <div id="container" class="svg-container"></div> <svg> </svg> </body> <script type="text/javascript"> </script> </html>

There is no data for Year=2001 .没有Year=2001的数据。 Yet, d3 is generating a path between 2001 and 2002 .然而, d3 正在生成20012002之间的路径。 How can I ask d3 to not generate anything between that.我怎么能要求d3在这之间不产生任何东西。

I am hoping for a gap in path between that period.我希望在那个时期之间有一个差距。

Update更新

I came across the defined syntax and tried it like the following witht the current data which did not work.我遇到了定义的语法,并在当前数据不起作用的情况下尝试了以下方法。

const line = d3.line() 
.defined(d=>  d.Year !== 2001 ) 
.x(d => scaleX(d.Year))
.y(d => scaleY(d.Value))

However, when I wrangled my data to this然而,当我把我的数据争吵到这个

src.push(   {
        "Region": "East Asia & Pacific",
        "Name": "Australia",
        "Year": 2001,
        "Value": null
    })
    
    src.sort((a,b)=>a.Year-b.Year);

and tried the below, it worked并尝试了以下方法,它有效

const line = d3.line() 
.defined(d=>  d.Value !== null )    
.x(d => scaleX(d.Year))
.y(d => scaleY(d.Value))

What am I trying to understand here is,in order for defined to work我想在这里理解的是,为了定义工作

do I need to wrangle my data first in a way so that, it contains each X-axis year and corresponding Y-axis values (null for the missing year)?我是否需要首先以某种方式整理我的数据,以便它包含每个 X 轴年份和相应的 Y 轴值(缺失年份为空)?

or is there a way for defined to figure out the missing years without needing the author to wrangle the data?或者有没有一种方法可以在不需要作者争论数据的情况下找出丢失的年份

 const src = [{ "Region": "East Asia & Pacific", "Name": "Australia", "Year": 1997, "Value": 15.540540540540499 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 1998, "Value": 15.540540540540499 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 1999, "Value": 22.4489795918367 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2000, "Value": 22.972972972973 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2002, "Value": 25.3333333333333 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2003, "Value": 25.3333333333333 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2004, "Value": 24.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2005, "Value": 24.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2006, "Value": 24.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2007, "Value": 26.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2008, "Value": 26.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2009, "Value": 27.3333333333333 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2010, "Value": 24.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2011, "Value": 24.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2012, "Value": 24.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2013, "Value": 26 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2014, "Value": 26 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2015, "Value": 26.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2016, "Value": 28.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2017, "Value": 28.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2018, "Value": 28.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2019, "Value": 30.463576158940398 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2020, "Value": 30.463576158940398 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2021, "Value": 31.125827814569501 } ] // src.push( { // "Region": "East Asia & Pacific", // "Name": "Australia", // "Year": 2001, // "Value": null // }) // src.sort((a,b)=>a.Year-b.Year); //////////////////////////////////////////////////////////// //////////////////////// 1 CREATE SVG //////////////////// //////////////////////////////////////////////////////////// const width = 1280; const height = 720; const svgns = 'http://www.w3.org/2000/svg' const svg = d3.select('svg') svg //.attr('xmlns', svgns) .attr('viewBox', `0 0 ${width} ${height}`) //---create a rect covering viewBox --- to be deleted later svg.append('rect') .attr('class', 'vBoxRect') .attr('width', `${width}`) .attr('height', `${height}`) .attr('fill', 'none') .attr('stroke', 'red') //////////////////////////////////////////////////////////// //////////////////////// 2 CREATE BOUND //////////////////// //////////////////////////////////////////////////////////// const padding = { top: 70, bottom: 50, left: 70, right: 190 } const boundHeight = height - padding.top - padding.bottom; const boundWidth = width - padding.right - padding.left; //create BOUND rect -- to be deleted later svg.append('rect') .attr('class', 'boundRect') .attr('x', `${padding.left}`) .attr('y', `${padding.top}`) .attr('width', `${boundWidth}`) .attr('height', `${boundHeight}`) .attr('fill', 'none') .attr('stroke', 'green') //create bound element const bound = svg.append('g') .attr('class', 'bound') //specify transform, must be .style and not .attr, px needs to be mentioned .style('transform', `translate(${padding.left}px,${padding.top}px)`) //////////////////////////////////////////////////////////// //////////////////////// 3 CREATE SCALE //////////////////// //////////////////////////////////////////////////////////// const scaleX = d3.scaleLinear() .range([0, boundWidth]) .domain(d3.extent(src, d => d.Year)) const scaleY = d3.scaleLinear() .range([boundHeight, 0]) .domain(d3.extent(src, d => d.Value)) //////////////////////////////////////////////////////////// //////////////////////// 4 CREATE AXIS //////////////////// //////////////////////////////////////////////////////////// bound.append('g').attr('class', 'yAxis') .append('g').attr('class', 'yAxisDetail') .call(d3.axisLeft(scaleY)) const data2 = src.map(d => d.Year) const count = [...new Set(data2)].length - 1 const minYear = Math.min(...src.map(d => d.Year)) for (let i = minYear; i <= minYear + count + 1; i++) { const len = src.filter(a => a.Year == i).length; (len == 1) ? null: src.push({ "Region": "East Asia & Pacific", "Name": "Australia", "Year": i, "Value": null }); }; src.sort((a, b) => a.Year - b.Year); bound.append('g') .attr('class', 'xAxis') .append('g') .attr('class', 'xAxisBottom') .style('transform', `translateY(${boundHeight}px)`) .call(d3.axisBottom(scaleX).ticks(count).tickFormat(d3.format("d"))) .call(a => a.selectAll('rect') //this is required to check if the tooltip is working as desired .data(d3.selectAll(".xAxisBottom>.tick:not(:last-child)")) .join('rect') .attr('x', '0') .attr('y', '0') .attr('height', `${boundHeight}`) .attr('width', `${scaleX(1998)}`) .attr('class', (d, i) => { return `bgRect${i}` }) .attr('fill', 'none') .style('stroke', 'black') .style('stroke-opacity', '0.1') .attr('transform', (d, i) => { const attributeX = d.getAttribute('transform').match(/(\d+\.\d+)(?=\,)|(\d+)(?=\,)/gm) const attributeY = boundHeight * -1; return `translate(${attributeX} ${attributeY})` }) ) //////////////////////////////////////////////////////////// //////////////////////// 5 CREATE PATH //////////////////// //////////////////////////////////////////////////////////// const line = d3.line() .defined(d => d.Value !== null) .x(d => scaleX(d.Year)) .y(d => scaleY(d.Value)) bound.append('g') .attr('class', 'valLine') .append('path') .attr('d', line(src)) .attr('stroke', 'black') .attr('fill', 'none')
 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script> <body> <div id="container" class="svg-container"></div> <svg> </svg> </body> <script type="text/javascript"> </script> </html>

I can always dynamically add the missing values, if that is the only way to make defined generates the gap in the path.我总是可以动态添加缺失值,如果这是使定义生成路径中的间隙的唯一方法。

const count = [...new Set(data2)].length - 1
const minYear = Math.min(...src.map(d => d.Year))

for (let i = minYear; i <= minYear + count + 1; i++) {
    const len = src.filter(a => a.Year == i).length;
    (len == 1) ? null: src.push({
        "Region": "East Asia & Pacific",
        "Name": "Australia",
        "Year": i,
        "Value": null
    });
};

You just need to add the data points (years) you want to skip, using a value later verified by line.defined .您只需使用稍后由line.defined验证的值添加要跳过的数据点(年)。 You can do this programatically, but here I'm just hardcoding the year 2001 with null as value.您可以以编程方式执行此操作,但在这里我只是将 2001 年硬编码为null作为值。

Then, your line generator can be:然后,您的线路生成器可以是:

const line = d3.line()
    .x(d => scaleX(d.Year))
    .y(d => scaleY(d.Value))
    .defined(d => d.Value)

Since null is falsy, I'm just checking if Value is truthy.由于null是假的,我只是在检查Value是否为真。

Here's your code with that change:这是您进行更改的代码:

 const src = [{ "Region": "East Asia & Pacific", "Name": "Australia", "Year": 1997, "Value": 15.540540540540499 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 1998, "Value": 15.540540540540499 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 1999, "Value": 22.4489795918367 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2000, "Value": 22.972972972973 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2001, "Value": null }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2002, "Value": 25.3333333333333 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2003, "Value": 25.3333333333333 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2004, "Value": 24.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2005, "Value": 24.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2006, "Value": 24.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2007, "Value": 26.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2008, "Value": 26.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2009, "Value": 27.3333333333333 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2010, "Value": 24.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2011, "Value": 24.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2012, "Value": 24.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2013, "Value": 26 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2014, "Value": 26 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2015, "Value": 26.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2016, "Value": 28.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2017, "Value": 28.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2018, "Value": 28.6666666666667 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2019, "Value": 30.463576158940398 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2020, "Value": 30.463576158940398 }, { "Region": "East Asia & Pacific", "Name": "Australia", "Year": 2021, "Value": 31.125827814569501 } ] //////////////////////////////////////////////////////////// //////////////////////// 1 CREATE SVG //////////////////// //////////////////////////////////////////////////////////// const width = 1280; const height = 720; const svgns = 'http://www.w3.org/2000/svg' const svg = d3.select('svg') svg //.attr('xmlns', svgns) .attr('viewBox', `0 0 ${width} ${height}`) //---create a rect covering viewBox --- to be deleted later svg.append('rect') .attr('class', 'vBoxRect') .attr('width', `${width}`) .attr('height', `${height}`) .attr('fill', 'none') .attr('stroke', 'red') //////////////////////////////////////////////////////////// //////////////////////// 2 CREATE BOUND //////////////////// //////////////////////////////////////////////////////////// const padding = { top: 70, bottom: 50, left: 70, right: 190 } const boundHeight = height - padding.top - padding.bottom; const boundWidth = width - padding.right - padding.left; //create BOUND rect -- to be deleted later svg.append('rect') .attr('class', 'boundRect') .attr('x', `${padding.left}`) .attr('y', `${padding.top}`) .attr('width', `${boundWidth}`) .attr('height', `${boundHeight}`) .attr('fill', 'none') .attr('stroke', 'green') //create bound element const bound = svg.append('g') .attr('class', 'bound') //specify transform, must be .style and not .attr, px needs to be mentioned .style('transform', `translate(${padding.left}px,${padding.top}px)`) //////////////////////////////////////////////////////////// //////////////////////// 3 CREATE SCALE //////////////////// //////////////////////////////////////////////////////////// const scaleX = d3.scaleLinear() .range([0, boundWidth]) .domain(d3.extent(src, d => d.Year)) const scaleY = d3.scaleLinear() .range([boundHeight, 0]) .domain(d3.extent(src, d => d.Value)) //////////////////////////////////////////////////////////// //////////////////////// 4 CREATE AXIS //////////////////// //////////////////////////////////////////////////////////// bound.append('g').attr('class', 'yAxis') .append('g').attr('class', 'yAxisDetail') .call(d3.axisLeft(scaleY)) const data2 = src.map(d => d.Year) const count = [...new Set(data2)].length - 1 const minYear = Math.min([...src]) bound.append('g') .attr('class', 'xAxis') .append('g') .attr('class', 'xAxisBottom') .style('transform', `translateY(${boundHeight}px)`) .call(d3.axisBottom(scaleX).ticks(count).tickFormat(d3.format("d"))) //////////////////////////////////////////////////////////// //////////////////////// 5 CREATE PATH //////////////////// //////////////////////////////////////////////////////////// const line = d3.line() .x(d => scaleX(d.Year)) .y(d => scaleY(d.Value)) .defined(d => d.Value) bound.append('g') .attr('class', 'valLine') .append('path') .attr('d', line(src)) .attr('stroke', 'black') .attr('fill', 'none')
 <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script> <body> <div id="container" class="svg-container"></div> <svg> </svg> </body> <script type="text/javascript"> </script> </html>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM