d3.js 项目 – 渲染问题

huangapple go评论55阅读模式
英文:

d3.js Project_Problem with Rendering

问题

以下是您要翻译的内容:

我有一个建立在 d3 库 v.7.6.1 上的项目。

它托管在 GitHub 页面上,在我的笔记本电脑上完全正常:
[![enter image description here][2]][2]

但在大多数其他设备上呈现不正确:
[![enter image description here][3]][3]

控制台上出现了`y`和`cy`属性的错误:
[![enter image description here][4]][4]

----------

我在这里复制了代码。它可能有什么问题?

```javascript
async function drawLineChart() {
  const pathToCsv = 'https://raw.githubusercontent.com/dsibi/portfolio/main/projects/line-graph-2/data/time_entries.csv';
  let rawDataset = await d3.dsv(";", pathToCsv);

  const record = {
    date: '',
    duration: ''
  };

  let dataset = [];

  for (let i = 0; i < rawDataset.length; i++) {
    let currRecord = Object.create(record);
    const [day, month, year] = rawDataset[i]['Start date'].split('.');
    currRecord.date = new Date(+year, +month - 1, +day);
    const [hours, minutes, seconds] = rawDataset[i]['Duration'].split(':');
    currRecord.duration = new Date(+year, +month - 1, +day, +hours, +minutes, +seconds);
    dataset.push(currRecord);
  }

  dataset.forEach(function(element) {
    let timeString = element.duration.toLocaleTimeString();
    let timeEl = timeString.split(':');
    element.durationSeconds = (+timeEl[0]) * 60 * 60 + (+timeEl[1]) * 60 + (+timeEl[2]);
  });

  var groupedDataset = [];
  dataset.reduce(function(res, value) {
    if (!res[value.date]) {
      res[value.date] = {
        date: value.date,
        totalDurationSeconds: 0
      };
      groupedDataset.push(res[value.date])
    }
    res[value.date].totalDurationSeconds += value.durationSeconds;
    return res;
  }, {});

  const xAccessor = d => d.date;
  const formatHours = d3.format(".2f");
  const yAccessor = d => +formatHours(d['totalDurationSeconds'] / 3600);
  const yAccessorLine = d => d['meanDurationHours'];
  let datasetWeeks = downsampleData(groupedDataset, xAccessor, yAccessor);
  const vacation = [{
    name: 'vacation',
    start: new Date('2022-06-16'),
    end: new Date('2022-06-26'),
  }, ];

  let dimensions = {
    width: window.innerWidth * 0.8,
    height: 400,
    margin: {
      top: 15,
      right: 40,
      bottom: 40,
      left: 40,
    },
  }
  dimensions.boundedWidth = dimensions.width - dimensions.margin.left - dimensions.margin.right
  dimensions.boundedHeight = dimensions.height - dimensions.margin.top - dimensions.margin.bottom

  // 3. 画布
  const wrapper = d3.select("#wrapper")
    .append("svg")
    .attr("width", dimensions.width)
    .attr("height", dimensions.height);

  const bounds = wrapper.append("g")
    .style("transform", `translate(${dimensions.margin.left}px, ${dimensions.margin.top}px)`);

  // 4. 比例尺
  const xScale = d3.scaleTime()
    .domain(d3.extent(groupedDataset, xAccessor))
    .range([0, dimensions.boundedWidth]);

  const yScale = d3.scaleLinear()
    .domain(d3.extent(groupedDataset, yAccessor))
    .range([dimensions.boundedHeight, 0])
    .nice();

  const meanHours = d3.mean(groupedDataset, yAccessor);

  bounds.append('line').attr('class', 'mean');

  const meanLine = bounds.select('.mean')
    .attr('x1', 0)
    .attr('x2', dimensions.boundedWidth)
    .attr('y1', yScale(meanHours))
    .attr('y2', yScale(meanHours));


  const xAxisGenerator = d3.axisBottom()
    .scale(xScale);

  const xAxis = bounds.append("g")
    .attr("class", "x-axis")
    .style("transform", `translateY(${dimensions.boundedHeight}px)`)
    .call(xAxisGenerator);

  // 5. 画数据
  // 点
  const dots = bounds.selectAll(".dot")
    .data(groupedDataset)
    .enter()
    .append("circle")
    .attr("cx", d => xScale(xAccessor(d)))
    .attr("cy", d => yScale(yAccessor(d)))
    .attr("r", 2)
    .attr("class", "dot");

  // 线
  const lineGenerator = d3.line()
    .x(function(d) {
      return xScale(xAccessor(d))
    })
    .y(d => yScale(yAccessorLine(d)))
    .curve(d3.curveCatmullRom.alpha(.5));

  const line = bounds.append("path")
    .attr("class", "line")
    .attr("d", lineGenerator(datasetWeeks))

  // 6. 画周边
  const yAxisGenerator = d3.axisLeft()
    .scale(yScale)
    .ticks(7);

  const yAxis = bounds.append("g")
    .attr("class", "y-axis")
    .call(yAxisGenerator);

};

drawLineChart();

function downsampleData(data, xAccessor, yAccessor) {
  const weeks = d3.timeWeeks(xAccessor(data[0]), xAccessor(data[data.length - 1]))

  return weeks.map((week, index) => {
    const weekEnd = weeks[index + 1] || new Date()
    const days = data.filter(d => xAccessor(d) > week && xAccessor(d) <= weekEnd)
    const meanTotalDurationHours = d3.mean(days, yAccessor)
    const meanDurationHours = meanTotalDurationHours === undefined ? 0 : d3.mean(days, yAccessor)
    return {
      date: week,
      meanDurationHours: meanDurationHours
    }
  })
};
.line {
  fill: none;
  stroke: #eb4511;
  stroke-width: 3;
}

.mean {
  stroke: #7d82b8;
  stroke-dasharray: 2px 4px;
}

.y-axis-label {
  fill: black;
  font-size: 1.4em;
  text-anchor: middle;
}

.x-axis-label {
  fill: black;
  font-size: 1.4em;
  text-anchor: middle;
}

.dot {
  fill: #9c9b98;
}

.mean_text,
.vacation_text {
  fill: #7d82b8;
  font-size: .8em;
  font-weight: 800;
  text-anchor: start;
}

.x-axis line,
.y-axis

<details>
<summary>英文:</summary>

I have a [project][1] built on [tag:d3] lib `v.7.6.1`. 

It&#39;s hosted on GitHub Pages and works totally Ok on my laptop:
[![enter image description here][2]][2]

But renders incorrectly on most of the other devices:
[![enter image description here][3]][3]

With error in console for `y` and `cy` attributes:
[![enter image description here][4]][4]


----------


I&#39;ve reproduced code here. What can be wrong with it?

&lt;!-- begin snippet: js hide: false console: true babel: false --&gt;

&lt;!-- language: lang-js --&gt;

    async function drawLineChart() {
      const pathToCsv = &#39;https://raw.githubusercontent.com/dsibi/portfolio/main/projects/line-graph-2/data/time_entries.csv&#39;;
      let rawDataset = await d3.dsv(&quot;;&quot;, pathToCsv);

      const record = {
        date: &#39;&#39;,
        duration: &#39;&#39;
      };

      let dataset = [];

      for (let i = 0; i &lt; rawDataset.length; i++) {
        let currRecord = Object.create(record);
        const [day, month, year] = rawDataset[i][&#39;Start date&#39;].split(&#39;.&#39;);
        currRecord.date = new Date(+year, +month - 1, +day);
        const [hours, minutes, seconds] = rawDataset[i][&#39;Duration&#39;].split(&#39;:&#39;);
        currRecord.duration = new Date(+year, +month - 1, +day, +hours, +minutes, +seconds);
        dataset.push(currRecord);
      }

      dataset.forEach(function(element) {
        let timeString = element.duration.toLocaleTimeString();
        let timeEl = timeString.split(&#39;:&#39;);
        element.durationSeconds = (+timeEl[0]) * 60 * 60 + (+timeEl[1]) * 60 + (+timeEl[2]);
      });

      var groupedDataset = [];
      dataset.reduce(function(res, value) {
        if (!res[value.date]) {
          res[value.date] = {
            date: value.date,
            totalDurationSeconds: 0
          };
          groupedDataset.push(res[value.date])
        }
        res[value.date].totalDurationSeconds += value.durationSeconds;
        return res;
      }, {});

      const xAccessor = d =&gt; d.date;
      const formatHours = d3.format(&quot;.2f&quot;);
      const yAccessor = d =&gt; +formatHours(d[&#39;totalDurationSeconds&#39;] / 3600);
      const yAccessorLine = d =&gt; d[&#39;meanDurationHours&#39;];
      let datasetWeeks = downsampleData(groupedDataset, xAccessor, yAccessor);
      const vacation = [{
        name: &#39;vacation&#39;,
        start: new Date(&#39;2022-06-16&#39;),
        end: new Date(&#39;2022-06-26&#39;),
      }, ];

      let dimensions = {
        width: window.innerWidth * 0.8,
        height: 400,
        margin: {
          top: 15,
          right: 40,
          bottom: 40,
          left: 40,
        },
      }
      dimensions.boundedWidth = dimensions.width - dimensions.margin.left - dimensions.margin.right
      dimensions.boundedHeight = dimensions.height - dimensions.margin.top - dimensions.margin.bottom

      // 3. Draw Canvas
      const wrapper = d3.select(&quot;#wrapper&quot;)
        .append(&quot;svg&quot;)
        .attr(&quot;width&quot;, dimensions.width)
        .attr(&quot;height&quot;, dimensions.height);

      const bounds = wrapper.append(&quot;g&quot;)
        .style(&quot;transform&quot;, `translate(${dimensions.margin.left}px, ${dimensions.margin.top}px)`);

      // 4. Scales
      const xScale = d3.scaleTime()
        .domain(d3.extent(groupedDataset, xAccessor))
        .range([0, dimensions.boundedWidth]);

      const yScale = d3.scaleLinear()
        .domain(d3.extent(groupedDataset, yAccessor))
        .range([dimensions.boundedHeight, 0])
        .nice();

      const meanHours = d3.mean(groupedDataset, yAccessor);

      bounds.append(&#39;line&#39;).attr(&#39;class&#39;, &#39;mean&#39;);

      const meanLine = bounds.select(&#39;.mean&#39;)
        .attr(&#39;x1&#39;, 0)
        .attr(&#39;x2&#39;, dimensions.boundedWidth)
        .attr(&#39;y1&#39;, yScale(meanHours))
        .attr(&#39;y2&#39;, yScale(meanHours));


      const xAxisGenerator = d3.axisBottom()
        .scale(xScale);

      const xAxis = bounds.append(&quot;g&quot;)
        .attr(&quot;class&quot;, &quot;x-axis&quot;)
        .style(&quot;transform&quot;, `translateY(${dimensions.boundedHeight}px)`)
        .call(xAxisGenerator);

      // 5. Draw Data
      //dots
      const dots = bounds.selectAll(&quot;.dot&quot;)
        .data(groupedDataset)
        .enter()
        .append(&quot;circle&quot;)
        .attr(&quot;cx&quot;, d =&gt; xScale(xAccessor(d)))
        .attr(&quot;cy&quot;, d =&gt; yScale(yAccessor(d)))
        .attr(&quot;r&quot;, 2)
        .attr(&quot;class&quot;, &quot;dot&quot;);

      //line
      const lineGenerator = d3.line()
        .x(function(d) {
          // console.log(xScale(xAccessor(d)))
          return xScale(xAccessor(d))
        })
        .y(d =&gt; yScale(yAccessorLine(d)))
        .curve(d3.curveCatmullRom.alpha(.5));
      // .curve(d3.curveMonotoneX);

      const line = bounds.append(&quot;path&quot;)
        .attr(&quot;class&quot;, &quot;line&quot;)
        .attr(&quot;d&quot;, lineGenerator(datasetWeeks))

      // 6. Draw Peripherals
      const yAxisGenerator = d3.axisLeft()
        .scale(yScale)
        .ticks(7);

      const yAxis = bounds.append(&quot;g&quot;)
        .attr(&quot;class&quot;, &quot;y-axis&quot;)
        .call(yAxisGenerator);

    };

    drawLineChart();

    function downsampleData(data, xAccessor, yAccessor) {
      const weeks = d3.timeWeeks(xAccessor(data[0]), xAccessor(data[data.length - 1]))

      return weeks.map((week, index) =&gt; {
        const weekEnd = weeks[index + 1] || new Date()
        const days = data.filter(d =&gt; xAccessor(d) &gt; week &amp;&amp; xAccessor(d) &lt;= weekEnd)
        const meanTotalDurationHours = d3.mean(days, yAccessor)
        const meanDurationHours = meanTotalDurationHours === undefined ? 0 : d3.mean(days, yAccessor)
        return {
          date: week,
          meanDurationHours: meanDurationHours
        }
      })
    };

&lt;!-- language: lang-css --&gt;

    .line {
      fill: none;
      stroke: #eb4511;
      stroke-width: 3;
    }

    .mean {
      stroke: #7d82b8;
      stroke-dasharray: 2px 4px;
    }

    .y-axis-label {
      fill: black;
      font-size: 1.4em;
      text-anchor: middle;
    }

    .x-axis-label {
      fill: black;
      font-size: 1.4em;
      text-anchor: middle;
    }

    .dot {
      fill: #9c9b98;
    }

    .mean_text,
    .vacation_text {
      fill: #7d82b8;
      font-size: .8em;
      font-weight: 800;
      text-anchor: start;
    }

    .x-axis line,
    .y-axis line,
    .domain {
      stroke: gray;
    }

    .tick text,
    .y-axis-label {
      fill: gray
    }

&lt;!-- language: lang-html --&gt;

    &lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/d3/7.6.1/d3.min.js&quot;&gt;&lt;/script&gt;
    &lt;html lang=&quot;en&quot;&gt;

    &lt;body&gt;
      &lt;div id=&quot;wrapper&quot;&gt;
      &lt;/div&gt;

    &lt;/body&gt;

    &lt;/html&gt;

&lt;!-- end snippet --&gt;


  [1]: https://dsibi.github.io/portfolio/projects/line-graph-2/line-graph-2.html
  [2]: https://i.stack.imgur.com/uMYq3.png
  [3]: https://i.stack.imgur.com/kwSAC.png
  [4]: https://i.stack.imgur.com/hHGgw.png

</details>


# 答案1
**得分**: 2

以下是代码部分的中文翻译

你的问题是由 `toLocaleTimeString()` 引起的

在以下部分添加 `console.log()` 
```javascript
dataset.forEach(function(element) {
    let timeString = element.duration.toLocaleTimeString();
    let timeEl = timeString.split(':');
    console.log(timeString, timeEl)
    element.durationSeconds = (+timeEl[0]) * 60 * 60 + (+timeEl[1]) * 60 + (+timeEl[2]);
});

桌面显示为:

00:19:34 (3) ['00', '19', '34']

但我的移动设备(使用 Chrome 远程检查器)显示为:

12:06:53 AM (3) ['12', '06', '53 AM']

所以当你执行 * 60 + (+timeEl[2]) 时,因为 53 AM 的原因,你会得到 NaN

你应该将时间逻辑更改为能够解析 24 小时和 12 小时制时间,例如使用:

以下是尝试修复此问题的部分翻译:

我使用了 new Date() 将其转换为日期对象。然后使用 valueOf() 获取Unix时间戳。现在我们只需将它们相减以获得 durationSeconds

dataset.forEach(function(element) {
    let secondsDiff = new Date(element.duration).valueOf() - new Date(element.date).valueOf();
    element.durationSeconds = secondsDiff;
});

这在我的笔记本电脑和移动设备上都有效。

更新后的代码片段如下:

// ...(代码截取)

希望这些翻译对你有帮助。如果有其他问题,请随时提出。

英文:

Your issue is caused by toLocaleTimeString().


When adding a console.log() to the following part:

dataset.forEach(function(element) {
let timeString = element.duration.toLocaleTimeString();
let timeEl = timeString.split(&#39;:&#39;);
console.log(timeString, timeEl)
element.durationSeconds = (+timeEl[0]) * 60 * 60 + (+timeEl[1]) * 60 + (+timeEl[2]);
});

Dekstop shows:

00:19:34 (3)&#160;[&#39;00&#39;, &#39;19&#39;, &#39;34&#39;]

But my mobile (using chrome remote inspector) shows:

12:06:53 AM (3)&#160;[&#39;12&#39;, &#39;06&#39;, &#39;53 AM&#39;]

So when you're doing * 60 + (+timeEl[2]) you're getting NaN due the 53 AM


You should change the time-logic to both be able to parse 24 and 12h format time, eg, using:

  • <https://stackoverflow.com/questions/55478610/how-to-use-tolocaletimestring-12-hours-time-without-am-pm-abbreviations>

Here's my attempt to fix this.

I've used new Date() to convert it to a date object. Then using valueOf() to get the unix timestamp. Now we just need to substract those from each-other to get the durationSeconds

dataset.forEach(function(element) {
    let secondsDiff = new Date(element.duration).valueOf() - new Date(element.date).valueOf();
    element.durationSeconds = secondsDiff;
});

This works both on my laptop as mobile:

Updated snippet:

<!-- begin snippet: js hide: true console: true babel: false -->

<!-- language: lang-js -->

async function drawLineChart() {
const pathToCsv = &#39;https://raw.githubusercontent.com/dsibi/portfolio/main/projects/line-graph-2/data/time_entries.csv&#39;;
let rawDataset = await d3.dsv(&quot;;&quot;, pathToCsv);
const record = {
date: &#39;&#39;,
duration: &#39;&#39;
};
let dataset = [];
for (let i = 0; i &lt; rawDataset.length; i++) {
let currRecord = Object.create(record);
const [day, month, year] = rawDataset[i][&#39;Start date&#39;].split(&#39;.&#39;);
currRecord.date = new Date(+year, +month - 1, +day);
const [hours, minutes, seconds] = rawDataset[i][&#39;Duration&#39;].split(&#39;:&#39;);
currRecord.duration = new Date(+year, +month - 1, +day, +hours, +minutes, +seconds);
dataset.push(currRecord);
}
dataset.forEach(function(element) {
let secondsDiff = new Date(element.duration).valueOf() - new Date(element.date).valueOf();
element.durationSeconds = secondsDiff;
});
var groupedDataset = [];
dataset.reduce(function(res, value) {
if (!res[value.date]) {
res[value.date] = {
date: value.date,
totalDurationSeconds: 0
};
groupedDataset.push(res[value.date])
}
res[value.date].totalDurationSeconds += value.durationSeconds;
return res;
}, {});
const xAccessor = d =&gt; d.date;
const formatHours = d3.format(&quot;.2f&quot;);
const yAccessor = d =&gt; +formatHours(d[&#39;totalDurationSeconds&#39;] / 3600);
const yAccessorLine = d =&gt; d[&#39;meanDurationHours&#39;];
let datasetWeeks = downsampleData(groupedDataset, xAccessor, yAccessor);
const vacation = [{
name: &#39;vacation&#39;,
start: new Date(&#39;2022-06-16&#39;),
end: new Date(&#39;2022-06-26&#39;),
}, ];
let dimensions = {
width: window.innerWidth * 0.8,
height: 400,
margin: {
top: 15,
right: 40,
bottom: 40,
left: 40,
},
}
dimensions.boundedWidth = dimensions.width - dimensions.margin.left - dimensions.margin.right
dimensions.boundedHeight = dimensions.height - dimensions.margin.top - dimensions.margin.bottom
// 3. Draw Canvas
const wrapper = d3.select(&quot;#wrapper&quot;)
.append(&quot;svg&quot;)
.attr(&quot;width&quot;, dimensions.width)
.attr(&quot;height&quot;, dimensions.height);
const bounds = wrapper.append(&quot;g&quot;)
.style(&quot;transform&quot;, `translate(${dimensions.margin.left}px, ${dimensions.margin.top}px)`);
// 4. Scales
const xScale = d3.scaleTime()
.domain(d3.extent(groupedDataset, xAccessor))
.range([0, dimensions.boundedWidth]);
const yScale = d3.scaleLinear()
.domain(d3.extent(groupedDataset, yAccessor))
.range([dimensions.boundedHeight, 0])
.nice();
const meanHours = d3.mean(groupedDataset, yAccessor);
bounds.append(&#39;line&#39;).attr(&#39;class&#39;, &#39;mean&#39;);
const meanLine = bounds.select(&#39;.mean&#39;)
.attr(&#39;x1&#39;, 0)
.attr(&#39;x2&#39;, dimensions.boundedWidth)
.attr(&#39;y1&#39;, yScale(meanHours))
.attr(&#39;y2&#39;, yScale(meanHours));
const xAxisGenerator = d3.axisBottom()
.scale(xScale);
const xAxis = bounds.append(&quot;g&quot;)
.attr(&quot;class&quot;, &quot;x-axis&quot;)
.style(&quot;transform&quot;, `translateY(${dimensions.boundedHeight}px)`)
.call(xAxisGenerator);
// 5. Draw Data
//dots
const dots = bounds.selectAll(&quot;.dot&quot;)
.data(groupedDataset)
.enter()
.append(&quot;circle&quot;)
.attr(&quot;cx&quot;, d =&gt; xScale(xAccessor(d)))
.attr(&quot;cy&quot;, d =&gt; yScale(yAccessor(d)))
.attr(&quot;r&quot;, 2)
.attr(&quot;class&quot;, &quot;dot&quot;);
//line
const lineGenerator = d3.line()
.x(function(d) {
// console.log(xScale(xAccessor(d)))
return xScale(xAccessor(d))
})
.y(d =&gt; yScale(yAccessorLine(d)))
.curve(d3.curveCatmullRom.alpha(.5));
// .curve(d3.curveMonotoneX);
const line = bounds.append(&quot;path&quot;)
.attr(&quot;class&quot;, &quot;line&quot;)
.attr(&quot;d&quot;, lineGenerator(datasetWeeks))
// 6. Draw Peripherals
const yAxisGenerator = d3.axisLeft()
.scale(yScale)
.ticks(7);
const yAxis = bounds.append(&quot;g&quot;)
.attr(&quot;class&quot;, &quot;y-axis&quot;)
.call(yAxisGenerator);
};
drawLineChart();
function downsampleData(data, xAccessor, yAccessor) {
const weeks = d3.timeWeeks(xAccessor(data[0]), xAccessor(data[data.length - 1]))
return weeks.map((week, index) =&gt; {
const weekEnd = weeks[index + 1] || new Date()
const days = data.filter(d =&gt; xAccessor(d) &gt; week &amp;&amp; xAccessor(d) &lt;= weekEnd)
const meanTotalDurationHours = d3.mean(days, yAccessor)
const meanDurationHours = meanTotalDurationHours === undefined ? 0 : d3.mean(days, yAccessor)
return {
date: week,
meanDurationHours: meanDurationHours
}
})
};

<!-- language: lang-css -->

.line {
fill: none;
stroke: #eb4511;
stroke-width: 3;
}
.mean {
stroke: #7d82b8;
stroke-dasharray: 2px 4px;
}
.y-axis-label {
fill: black;
font-size: 1.4em;
text-anchor: middle;
}
.x-axis-label {
fill: black;
font-size: 1.4em;
text-anchor: middle;
}
.dot {
fill: #9c9b98;
}
.mean_text,
.vacation_text {
fill: #7d82b8;
font-size: .8em;
font-weight: 800;
text-anchor: start;
}
.x-axis line,
.y-axis line,
.domain {
stroke: gray;
}
.tick text,
.y-axis-label {
fill: gray
}

<!-- language: lang-html -->

&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/d3/7.6.1/d3.min.js&quot;&gt;&lt;/script&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;body&gt;
&lt;div id=&quot;wrapper&quot;&gt;
&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;

<!-- end snippet -->

huangapple
  • 本文由 发表于 2023年2月16日 18:32:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/75470969.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定