使用 CSS 制作图表

Avatar of Robin Rendle
Robin Rendle

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

有很多方法可以制作数据的视觉表示:条形图、折线图、散点图、迷你图……更不用说在网上实现它们的多種方式了。在这篇文章中,我将探讨使用纯 CSS 方法来为数据设置样式。但是,在我们查看一些示例之前,我认为先简要回顾一下我们的设计目标是值得的。

制作图表指南

在网上开发图表有三个指南

  1. 可访问性:每个人都应该能够查看我们提供的某些数据格式,即使它只是一个乏味的 table(乏味比什么都没有好)。
  2. 易于开发:制作图表不应该不必要地复杂,而且我们当然希望避免未来的技术债务。
  3. 性能:我们需要确保用户不会花很多时间等待资产下载或元素绘制到屏幕上。

这些目标可能会根据制作的图表类型而改变,因为对于静态条形图来说,性能不会像 疯狂的交互式地图那样令人担忧。牢记这些指南,让我们来看几个例子。

CSS 条形图

有几种方法可以在 CSS 中制作简单的条形图。首先,我们将使用定义列表来存储我们的数据

<dl>
  <dt>A title of the graph</dt>
  <dd class="percentage"><span class="text"> Data 1: 20% </span></dd>
  <dd class="percentage"><span class="text"> Data 2: 50% </span></dd>
  <dd class="percentage"><span class="text"> Data 3: 30% </span></dd>
</dl>

我们将使用该跨度将每个 dd 的文本内容绝对定位到左侧。

为了制作视觉上代表数据的“条形”,我们将使用伪元素。为此,我们可以使用诸如 .percentage-20 之类的类更新标记,并在其伪元素上设置宽度

.percentage:after {
  content: "";
  display: block;
  background-color: #3d9970;
}
.percentage-20:after {
  width: 20%;
}
.percentage-30:after {
  width: 30%;
}

但我们不想手动写出所有这些类,因为数据将来可能会发生变化。我们可以编写一个 Sass 循环为我们创建所有这些类

@for $i from 1 through 100 {
  .percentage-#{$i}:after {
    $value: ($i * 1%);
    width: $value;
  }
}

这有点 令人讨厌,因为它会创建一个我们可能不会在最终实现中使用的许多类,但 有很多工具可以帮助我们在生产中整理这些类。

接下来,我们可以将那些自动生成的类添加到每个 .percentage 元素中,如下所示

<dl>
  <dt>A title of the graph</dt>
  <dd class="percentage percentage-7"><span class="text"> IE 11: 7% </span></dd>
  <dd class="percentage percentage-20"><span class="text"> Chrome: 20% </span></dd>
  <dd class="percentage percentage-2"><span class="text"> Android 4.4: 2% </span></dd>
</dl>

最后,我们可以使用 repeating-linear-gradient 向每个 .percentage 元素的背景添加规则,以帮助在比较这些值时提高可读性

.percentage {
  background: repeating-linear-gradient(
    to right,
    #ddd,
    #ddd 1px,
    #fff 1px,
    #fff 5%
  );
}

这种技术相对简单,但我忍不住认为这些信息应该始终默认情况下设置为 table。虽然我对以这种方式设置表格样式有点谨慎,但这当然并不意味着它是不可能的。例如,Eric Meyer 写过一篇关于这种技术的文章,并描述了如何定位挑剔的表格元素以使其像条形图一样行为。这是他为表格使用的原始标记

<table id="q-graph">
  <caption>Quarterly Results</caption>
  <thead>
    <tr>
      <th></th>
      <th class="sent">Invoiced</th>
      <th class="paid">Collected</th>
    </tr>
  </thead>
  <tbody>
    <tr class="qtr" id="q1">
      <th scope="row">Q1</th>
      <td class="sent bar"><p>$18,450.00</p></td>
      <td class="paid bar"><p>$16,500.00</p></td>
    </tr>
    <tr class="qtr" id="q2">
      <th scope="row">Q2</th>
      <td class="sent bar"><p>$34,340.72</p></td>
      <td class="paid bar"><p>$32,340.72</p></td>
    </tr>
    <tr class="qtr" id="q3">
      <th scope="row">Q3</th>
      <td class="sent bar"><p>$43,145.52</p></td>
      <td class="paid bar"><p>$32,225.52</p></td>
    </tr>
    <tr class="qtr" id="q4">
      <th scope="row">Q4</th>
      <td class="sent bar"><p>$18,415.96</p></td>
      <td class="paid bar"><p>$32,425.00</p></td>
    </tr>
  </tbody>
</table>

与我之前使用的示例不同,我使用 Sass 中的一组自动生成的辅助类来定义条形图的宽度,Eric 在 td 元素上使用内联样式,这些值是在服务器端或使用 JavaScript 计算的,而不是手动添加的。

下面的示例是我对 Eric 原始示例 的复制,我在其中对样式进行了一些更新

我真的很喜欢表格中的每一行都有一个标题,如 Q1、Q2 等——这感觉非常整洁,而不是依赖于定义列表来描述内容。它们易于定位,如果 CSS 由于某种原因无法加载,则会很好地回退到标准表格。

然而,这种方法的一个问题是它需要将每一行表格并排绝对定位,这意味着如果我们想添加更多数据,我们将需要做比简单更新标记更多的事情。这意味着它将来可能难以使用。

迷你图

当表示此类信息时,我们并不总是需要使用 table。当制作一系列迷你图时,情况可能就是这样,迷你图是坐在一行文本旁边的微型图表,可以帮助读者快速了解信息。 Wilson Miner 概述了这种方法 并确保事先关注信息的可用性

<figure>
  <ul class="sparklist">
    <li>
      <a href="http://www.example.com/fruits/apples/">Apples</a>
      <span class="sparkline">
        <span class="index"><span class="count" style="height: 27%;">(60,</span> </span>
        <span class="index"><span class="count" style="height: 97%;">220,</span> </span>
        <span class="index"><span class="count" style="height: 62%;">140,</span> </span>
        <span class="index"><span class="count" style="height: 35%;">80,</span> </span>
      </span>
    </li>
  </ul>
  <figcaption>Fruits eaten in the last 14 days by type</figcaption>
</figure>

我从 Wilson 使用的内容更新了原始标记,因为我认为这应该适合一个 figure,正如 MDN 上的文档 所述

通常 [一个图形]……是一张图片、一张插图、一个图表、一个代码片段或一个在主文本中引用的模式,但可以移动到另一个页面或附录中,而不会影响主流程。

对我来说,这比一个简单的 ul 元素更有意义。无论如何,这是它在没有 CSS 情况下看起来的样子

非常容易访问!接下来,我们可以向 sparkline 元素添加样式,并使用内联块将其定位到链接的右侧。同样,我们可以使用之前使用的 Sass 类生成器来设置每个条形的 height。但是,现在让我们保留那些内联样式

确保将鼠标悬停在列表中的每个条目上,以查看我添加的图表的扩展版本。虽然它在分解数据方面并没有特别有用,但它表明我们并没有局限于单一表示形式的图表;当使用简单的标记和 CSS 时,以这种轻松的方式操作这些可视化内容是一个巨大的优势。

CSS 饼图

Lea Verou 最近写了一篇关于制作饼图的 精彩文章。她提出的一种可能性是使用覆盖圆形的伪元素,并使用 transform: rotate() 将其略微移动。

另一种可能性是 SVG,它有许多优点,其中一些将在下一节列出。

如果浏览器支持 conic-gradient(),那将是一个 非常 令人信服的创建它们的方法。它们还没有完全支持,但 Lea 为它开发了一个 polyfill,效果非常好。以下是一个使用它的饼图演示,我们为 CSS-Tricks 上最近进行的民意调查中的一个制作了它

使用 CSS 制作图表的问题

  • 如果您使用 background 来设置元素的样式,那么它(可能)在网页打印时不可见。唯一的例外是,如果您在 WebKit 浏览器中使用 -webkit-print-color-adjust: exact;
  • 对设计的挑剔控制:例如,绝对定位表格行,将来可能会给开发人员带来痛苦。
  • 浏览器支持在某些情况下是一个繁琐的过程,确保所有设备都支持每个 CSS 属性可能很难测试。
  • 在伪元素上使用内联样式是不可能的,因此,如果您想使用 JavaScript 设置伪元素的样式,这会使事情变得更加复杂。
  • 使用 CSS 可以做任何事情,但是当我们制作 纯 CSS 折线图 时,我们可能应该花一点时间重新考虑这可能对代码库其他部分造成的影响。

总结

针对图表和图形的纯 CSS 和标记解决方案在一定程度上起作用,在许多情况下,它们可能是最安全的方案。但我认为探索表示数据的替代方案是值得的。

在本系列的下一篇文章中,我将探讨使用 SVG 和 JavaScript 制作图表的解决方案。

更多信息