DEV Community

Yao
Yao

Posted on

利用 svg 实现折线图

在web开发中数据可视化在企业中后台是很重要的一部分,常见的图表有折线图、柱状图、饼图...等等。开发者为了效率和效果会使用第三方图表库,例如:echartsantVD3,这些图表库的底层实现包括 canvas、SVG、webGL,接下来我会使用 svg 来实现一个简单的折线图。

创建折线图组件

创建一个名为 <LineChart /> 的新组件并将文件命名为 line-chart.js。

组件接受一个名为 data 的 props。

import React, { useRef, useState } from 'react';
import PropTypes from 'prop-types';

const LineChart = ({ data }) => {
  return null;
};

LineChart.propTypes = {
  /** The shape of the data  */
  data: PropTypes.arrayOf(
    PropTypes.shape({
      total: PropTypes.number.isRequired,
      date: PropTypes.string.isRequired
    })
  ).isRequired
};

export default LineChart;
Enter fullscreen mode Exit fullscreen mode

设置这线图

这步设置折线图不同部分的变量

import React from 'react';
import PropTypes from 'prop-types';

const LineChart = ({ data }) => {
  const chartWidth = 1200; // SVG 视图框的宽度
  const chartHeight = 600; // SVG 视图框的高度
  const offsetY = 40; // 用于定位刻度
  const paddingX = 50; // 折线图周围的左右内边距
  const paddingY = 90; // 折线图周围的上下内边距
  const maxY = Math.max(...data.map((item) => item.total)); // 数据数组中的最大总值
  const guides = [...Array(16).keys()]; // 一个空数组,用于确定指南的数量

  return null;
};

LineChart.propTypes = {
  /** The shape of the data  */
  data: PropTypes.arrayOf(
    PropTypes.shape({
      total: PropTypes.number.isRequired,
      date: PropTypes.string.isRequired
    })
  ).isRequired
};

export default LineChart;
Enter fullscreen mode Exit fullscreen mode

properties 是使用来自 data prop 的值和上一步中定义的变量的组合创建的。每个返回值都用于折线图的不同部分。

const LineChart = ({ data }) => {
  // ...

  const properties = data.map((property, index) => {
    const { total, date } = property;
    const x = (index / data.length) * (chartWidth - paddingX)  paddingX / 2;
    const y = chartHeight - offsetY - (total / maxY) * (chartHeight - (paddingY + offsetY)) - paddingY + offsetY;
    return {
      total: total,
      date: date,
      x: x,
      y: y
    };
  });

  return null;
};
Enter fullscreen mode Exit fullscreen mode

这里要查看的两个重要值是 x 和 y。这些变量是通过组合前面定义的一些变量和 .map 中的索引值来创建的。

创建 X 坐标

x 坐标用于创建标记的位置、值和折线点的 x 值。

const x = (index / data.length) * (chartWidth - paddingX) + paddingX / 2;
Enter fullscreen mode Exit fullscreen mode

x 坐标是通过使用 .map 中的索引值并将其除以在 data prop 上传递的数据的长度来创建的。将其乘以 chartWidth 并减去 paddingX 值可确保 x 坐标值永远不会超过图表宽度的范围。

创建 Y 坐标

y 坐标用于创建标记的位置、值和折线点的 y 值。

const y = chartHeight - offsetY - (total / maxY) * (chartHeight - (paddingY + offsetY)) - paddingY + offsetY;
Enter fullscreen mode Exit fullscreen mode

y 坐标是通过将总数除以 maxY 值再乘以 chartHeight 减去 paddingY 值来创建的。作为一个额外的步骤,减去 paddingY 加上 offsetY 值,这为图表底部的刻度创建了一些额外的空间。

创建点数组

SVG Polyline 元素可用于创建由点连接的线。要为折线创建点,您可以使用属性数组中的 x 和 y 值,并将它们作为 x、y 位置的数组返回。

  const points = properties.map((point) => {
    const { x, y } = point;
    return `${x},${y}`;
  });
Enter fullscreen mode Exit fullscreen mode

点数组将返回类似于下面的内容。数组中的每个索引都包含 x 和 y 坐标。

[
  '25,25', '47,232', '69,40', '91,235' ...
]
Enter fullscreen mode Exit fullscreen mode

创建 SVG

创建一个新的 元素,定义 viewBox 属性并将角色设置为presentation。

const LineChart = ({ data }) => {
   // ... 
  return (
   <svg viewBox={`0 0 ${chartWidth} ${chartHeight}`} role="presentation">

   </svg>
  );
};
Enter fullscreen mode Exit fullscreen mode

创建折线

创建一个新的 <polyline /> 元素,将填充设置为 none 并添加相关 class来设置样式。还可以使用属性定义strokeWidth。使用 points 属性,可以传递 points 数组返回的值。

<polyline fill="none" className="stroke-gray-400" strokeWidth={2} points={points} />
Enter fullscreen mode Exit fullscreen mode

创建标记和值

使用 properties 数组的返回值,现在可以添加和定位 svg 元素以显示数据数组中的总数。

  return (
    <svg viewBox={`0 0 ${chartWidth} ${chartHeight}`} role="presentation">
      <polyline fill="none" className="stroke-gray-400" strokeWidth={2} points={points} />
      {properties.map((property, index) => {
        const { total, date, x, y } = property;

        return (
          <g key={index}>
            <circle className="stroke-gray-400 fill-white" cx={x} cy={y} r={12} strokeWidth={2} />
            <text x={x} y={y + 2.8} textAnchor="middle" fontSize={8} className="font-bold fill-gray-800 select-none">
              {total}
            </text>
          </g>
        );
      })}
    </svg>
  );
Enter fullscreen mode Exit fullscreen mode

元素使用 x 和 y 属性使用 cx 和 cy 属性定位,并使用 r 属性指定半径为 12。您可以添加自己的类名和 strokeWidth 的值以实现所需的外观。

元素也使用 x 和 y 属性使用 x 和 y 属性定位。 元素接受子元素,因此添加 total 属性以显示它。我在 y 中添加了一个额外的 2.8,以确保文本在 中垂直居中。可以添加自己的类名或属性来获得所需的外观。

创建刻度

return (
          <g key={index}>
            ...
            <g transform={`translate(${x} ${chartHeight - (paddingY - offsetY)})`}>
              <text transform="rotate(45)" textAnchor="start" transformorigin="50% 50%" fontSize={10} className="fill-gray-800 select-none">
                {new Date(date).toLocaleDateString(undefined, { year: '2-digit', month: 'numeric', day: 'numeric' })}
              </text>
            </g>
          </g>
        );
Enter fullscreen mode Exit fullscreen mode

刻度的创建方式略有不同, 元素被包裹在 中。

这是为了创建一个新的坐标系,以便在对文本应用旋转时,其顶部/左侧位置是根据 而不是 元素的顶部和左侧位置计算的。

元素不支持 x 或 y 属性,因此您将使用 transform 属性并为 x 和 y 位置提供平移值。我还减去了 paddingY 和 offsetY 值来正确定位刻度。

创建指南

指南是最后添加的元素;但是这次你将迭代guides数组,而不是遍历properties数组。
这只是一个空数组,索引用于为 React 提供键。 y 坐标的创建方式与以前类似,其中创建了一个比率以确保 y 位置保持在边界内并且不与刻度重叠。

const LineChart = ({ data }) => {
  return (
    <svg viewBox={`0 0 ${chartWidth} ${chartHeight}`} role="presentation">
       ...
      {guides.map((_, index) => {
        const ratio = index / guides.length;
        const y = chartHeight - paddingY - chartHeight * ratio;

        return <polyline key={index} className="stroke-gray-200" fill="none" strokeWidth={1} points={`${paddingX / 2},${y} ${chartWidth - paddingX / 2},${y}`} />;
      })}
       ...
    </svg>
  );
};
Enter fullscreen mode Exit fullscreen mode

使用从属性数组返回的值,可以添加任意数量的不同 SVG 元素,以帮助显示来自不同类型数据的不同值。

x 和 y 属性应该是您所需要的。虽然创建它们有点棘手,但它们为您想添加到图表中的任何新元素创建了边界。

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay