如何使用 CSS 创建面积图

Avatar of Rami Yushuvaev
Rami Yushuvaev

DigitalOcean 为您的旅程每个阶段提供云产品。立即开始使用 价值 200 美元的免费积分!

您可能知道一些使用纯 CSS 创建图表的方法。其中一些在 CSS-Tricks 上有介绍,还有许多其他方法可以在 CodePen 上找到,但我没有看到很多“面积图”的例子(想象一个底部区域填充的折线图),尤其是 HTML 和 CSS 结合使用的例子。在本文中,我们将使用语义和可访问的 HTML 基础来实现这一点。

A red area chart against a dark gray background.

让我们从 HTML 开始

为了简化操作,我们将使用 <ul> 标签作为包装器,并使用 <li> 元素来表示单个数据项。您可以在项目中使用任何其他 HTML 标签,具体取决于您的需求。

<ul class="area-chart">
  <li> 40% </li>
  <li> 80% </li>
  <li> 60% </li>
  <li> 100% </li>
  <li> 30% </li>
</ul>

CSS 无法检索内部 HTML 文本,因此我们将使用 CSS 自定义属性将数据传递给我们的 CSS。每个数据项将具有 --start--end 自定义属性。

<ul class="area-chart">
  <li style="--start: 0.1; --end: 0.4;"> 40% </li>
  <li style="--start: 0.4; --end: 0.8;"> 80% </li>
  <li style="--start: 0.8; --end: 0.6;"> 60% </li>
  <li style="--start: 0.6; --end: 1.0;"> 100% </li>
  <li style="--start: 1.0; --end: 0.3;"> 30% </li>
</ul>

我们需要考虑以下几点…

在进入样式设计之前,我们应该考虑一些设计原则。

  • 单位数据: 我们将在 HTML 中使用无单位数据(即没有 pxemrem% 或任何其他单位)。--start--end 自定义属性将是 0 到 1 之间的数字。
  • 列宽: 我们不会为每个 <li> 元素设置固定的 width。我们也不会使用 %,因为我们不知道有多少个项目。每列的宽度将基于主包装器宽度除以数据项总数。在本例中,即 <ul> 元素的宽度除以 <li> 元素的数量。
  • 可访问性: 每个 <li> 中的值是可选的,只有 --start--end 自定义属性是必需的。不过,最好包含某种文本或值,以便屏幕阅读器和其他辅助技术能够描述内容。

现在,让我们开始设计样式!

让我们先从一般布局样式开始。图表包装器元素是一个弹性容器,按行显示项目,并拉伸每个子元素,以填充整个区域。

.area-chart {
  /* Reset */
  margin: 0;
  padding: 0;
  border: 0;

  /* Dimensions */
  width: 100%;
  max-width: var(--chart-width, 100%);
  height: var(--chart-height, 300px);

  /* Layout */
  display: flex;
  justify-content: stretch;
  align-items: stretch;
  flex-direction: row;
}

如果面积图包装器是一个列表,我们应该删除列表样式,以便获得更多样式灵活性。

ul.area-chart,
ol.area-chart {
  list-style: none;
}

此代码对整个图表中的所有列进行样式设置。对于条形图,这很简单:我们使用 background-colorheight 为每列设置样式。对于面积图,我们将使用 clip-path 属性来设置应该显示的区域。

首先,我们设置每列的样式

.area-chart > * {
  /* Even size items */
  flex-grow: 1;
  flex-shrink: 1;
  flex-basis: 0;

  /* Color */
  background: var(--color, rgba(240, 50, 50, .75));
}

为了创建一个覆盖整个区域的矩形,我们将使用 clip-path 属性及其 polygon() 函数,该函数包含区域的坐标。目前,这实际上没有任何作用,因为多边形覆盖了所有内容。

.area-chart > * {
  clip-path: polygon(
    0% 0%,     /* top left */
    100% 0%,   /* top right */
    100% 100%, /* bottom right */
    0% 100%    /* bottom left */
  );
}

现在,最棒的部分来了!

为了只显示列的一部分,我们将其剪切以创建类似面积图的效果。为了只显示我们想要的区域,我们在 clip-path 多边形中使用 --start--end 自定义属性。

.area-chart > * {
  clip-path: polygon(
    0% calc(100% * (1 - var(--start))),
    100% calc(100% * (1 - var(--end))),
    100% 100%,
    0% 100%
  );
}

说真的,这行 CSS 代码完成了所有工作。以下是我们的成果。

使用多个数据集

现在我们已经了解了基础知识,让我们创建一个具有多个数据集的面积图。面积图通常测量多个数据集,其效果是对数据进行分层比较。

这种图表需要多个子元素,因此我们将用 <table> 替换我们的 <ul> 方法。

<table class="area-chart">
  <tbody>
    <tr>
      <td> 40% </td>
      <td> 80% </td>
    </tr>
    <tr>
      <td> 60% </td>
      <td> 100% </td>
    </tr>
  </tbody>
</table>

表格是可访问的,并且对搜索引擎友好。如果样式表由于某种原因未加载,所有数据仍然可见。

同样,我们将使用 --start--end 自定义属性,其值介于 0 和 1 之间。

<table class="area-chart">
  <tbody>
    <tr>
      <td style="--start: 0; --end: 0.4;"> 40% </td>
      <td style="--start: 0; --end: 0.8;"> 80% </td>
    </tr>
    <tr>
      <td style="--start: 0.4; --end: 0.6;"> 60% </td>
      <td style="--start: 0.8; --end: 1.0;"> 100% </td>
    </tr>
  </tbody>
</table>

因此,首先我们将为包装元素(我们的表格)设置一般布局样式,我们已经为其添加了 .area-chart 类。

.area-chart {
  /* Reset */
  margin: 0;
  padding: 0;
  border: 0;

  /* Dimensions */
  width: 100%;
  max-width: var(--chart-width, 600px);
  height: var(--chart-height, 300px);
}

接下来,我们将使 <tbody> 元素成为弹性容器,按行显示 <tr> 项目,并使其大小一致。

.area-chart tbody {
  width: 100%;
  height: var(--chart-height, 300px);

  /* Layout */
  display: flex;
  justify-content: stretch;
  align-items: stretch;
  flex-direction: row;
}
.area-chart tr {
  /* Even size items */
  flex-grow: 1;
  flex-shrink: 1;
  flex-basis: 0;
}

现在,我们需要使 <td> 元素相互覆盖,每个元素位于另一个元素的顶部,这样我们就能够获得分层效果。每个 <td> 元素都覆盖了包含它的 <tr> 元素的整个区域。

.area-chart tr {
  position: relative;
}
.area-chart td {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

让我们将 clip-path: polygon() 的神奇力量应用起来!我们只显示 --start--end 自定义属性之间的区域,这些属性的值仍然是 0 到 1 之间的数字。

.area-chart td {
  clip-path: polygon(
    0% calc(100% * (1 - var(--start))),
    100% calc(100% * (1 - var(--end))),
    100% 100%,
    0% 100%
  );
}

现在,让我们为每个元素添加颜色。

.area-chart td {
  background: var(--color);
}
.area-chart td:nth-of-type(1) {
  --color: rgba(240, 50, 50, 0.75);
}
.area-chart td:nth-of-type(2) {
  --color: rgba(255, 180, 50, 0.75);
}
.area-chart td:nth-of-type(3) {
  --color: rgba(255, 220, 90, 0.75);
}

重要的是使用带有不透明度的颜色来获得更好的效果,这就是我们使用 rgba() 值的原因。如果您习惯使用 hsla(),也可以在这里使用它。

就是这样!

总结

无论我们在图表中添加多少 HTML 元素,基于弹性的布局都能确保所有项目的大小一致。这样,我们只需要设置包装图表元素的宽度,项目就会相应调整以实现响应式布局。

我们已经介绍了一种使用纯 CSS 创建面积图的技术。对于高级用例,您可以查看我的新的开源数据可视化框架 ChartsCSS.org。查看 面积图 部分,了解如何使用不同的方向、轴,甚至反转顺序(无需更改 HTML 标记)等方式自定义面积图,还有更多功能!