以下内容是 Nick Moreton 的客座文章。我必须说,我发现这个想法相当有趣。我知道我喜欢在 HTML、SVG 和 CSS 中工作,所以当 Nick 分享我们可以用它来构建结构,并直接使用数据来设置图表样式时,这吸引了我。然后知道,因为您使用的是 Angular,图表可以随着数据的变化而自动更改……这太酷了。
当我开始玩 AngularJS 时,我发现它可以直接在标记中获取数据并使用它,这为创建数据可视化提供了一种非常快速简单的方案。
在本教程中,我将使用内联 CSS 和 SVG 创建三种不同类型的图表。
为什么选择 Angular?
如果您以前从 JavaScript 或 jQuery 中写入过 DOM,您会知道您的代码有多快就会变得杂乱无章,特别是如果您使用多个变量。Angular 允许直接在标记中使用数据,从而使代码清晰、易于阅读和理解。
还有一些很棒的可视化库,但它们自带许多默认样式。通过使用 Angular,可视化完全不受约束,并且会立即拾取您的样式。
我并不是说这是创建数据可视化的最佳方法,但它确实吸引了我!
设置我们的 Angular 应用程序
基本应用程序设置
首先,我们需要设置一个 Angular 应用程序和一个控制器来容纳我们的功能和数据。
(function(){
var app = angular.module('graphApp',[]);
app.controller('graphController', function($scope){
// Code goes here!
});
})();
默认选项
接下来,我们设置一些默认变量,绑定到控制器 $scope,我们将使用它来控制图表的尺寸,以及 X 轴和 Y 轴的标签。
$scope.width = 600;
$scope.height = 400;
$scope.yAxis = "Sales";
$scope.xAxis = "2014"
数据
然后我们添加我们的数据,以 JSON 格式编写,绑定到控制器的 $scope。
$scope.data = [
{
label: 'January',
value: 36
},
{
label: 'February',
value: 54
},
// .... and so on .....
{
label: 'November',
value: 252
},
{
label: 'December',
value: 342
}
];
查找最大值
最后,我们编写一个循环来遍历数据以查找最大值并将其设置为变量。我们将在以后使用它来定位可视化中的元素。
$scope.max = 0;
var arrLength = $scope.data.length;
for (var i = 0; i < arrLength; i++) {
// Find Maximum X Axis Value
if ($scope.data[i].value > $scope.max)
$scope.max = $scope.data[i].value;
}
这就是我们 JavaScript 的全部内容。实际上,这都是关于设置数据和变量以便在以后的标记中使用。
设置标记、模板和 CSS
现在我们需要设置可视化应用程序标记和 CSS。
<div ng-app="graphApp">
<div ng-controller="graphController as graph">
<div class="graph" style="width:{{width}}px; height:{{height}}px;" >
<div class="y" style="width:{{height}}px;">{{yAxis}}</div>
<div class="x">{{xAxis}}</div>
</div>
</div>
</div>
在 HTML 中,我们可以开始将数据直接从 JavaScript 拉入标记中,既作为内容(例如 X 轴和 Y 轴标签),也作为内联样式来控制图表的宽度和高度。
.chart {
border-left: 1px solid black;
border-bottom: 1px solid black;
margin: 60px auto;
position: relative;
}
.y {
position: absolute;
transform: rotate(-90deg);
transform-origin: bottom left;
bottom: 0;
padding: 5px;
}
.x {
position: absolute;
top: 100%;
width: 100%;
padding: 5px;
}
柱状图
好的,为了在图表上创建数据,我们将使用 Angular 的 ng-repeat 函数。这将遍历我们的数据数组,并为每个条目输出我们包装在 ng-repeat 中的任何标记。
首先,我们将创建一个柱状图。首先,我为 bar 类设置了一些默认 CSS,它将位置设置为绝对值,并添加背景颜色。
.bar {
background: blue;
position: absolute;
bottom: 0;
}
然后,使用 ng-repeat,为每个条目创建一个带有 bar 类的 <div>,我们将写入
<div ng-repeat="bar in data" class="bar"></div>
此代码位于我们的 <div class="graph"></div> 中。
现在,我们可以使用我们的 Angular 数据(以及一些数学!) 在一些内联 CSS 中来控制每个柱形图的高度和宽度。
<div ng-repeat="bar in data" class="bar" style="height:{{bar.value / max * height}}px; width:{{width / data.length - 5}}px;"></div>
高度是值除以最大值(在我们 Angular 应用程序中设置的),乘以图表的总高度。这样可以确保数据中的最大值将占用图表的全部高度。
宽度是总宽度除以条目数量,并减去 5 像素,以便在我们将柱形图放置在 X 轴上时创建一些间距。
最后,我们需要使用 left CSS 属性将柱形图定位在 X 轴上。
<div ng-repeat="bar in data" class="bar" style="height:{{bar.value / max * height}}px; width:{{width / data.length - 5}}px; left:{{$index / data.length * width}}px;"></div>
在这里,我们使用 $index,一个 Angular 变量,它将从 0 开始,并在每个后续柱形图中增加。我们将索引除以条目总数,并将结果乘以图表的总宽度。这样会将第一个柱形图放置在 0 处,然后将其余柱形图等间隔地放置在图表上。
值得注意的是,如果您希望图表具有流畅性,则可以乘以 100 并使用 % 作为单位,而不是像素。
就是这样 - 我们的柱状图现在已经创建完成,您可以开始使用 CSS 进行美化了!
完整的最终图表代码如下所示
<div class="chart" style="width:{{width}}px; height:{{height}}px;">
<!-- Labels -->
<div class="y" style="width:{{height}}px;">{{yAxis}}</div>
<div class="x">{{xAxis}}</div>
<!-- Data -->
<div ng-repeat="bar in data" class="bar" style="height:{{bar.value / max * height}}px; width:{{width / data.length - 5}}px; left:{{$index / data.length * width}}px;"></div>
</div>
查看 CodePen 上 Nick Moreton (@nickmoreton) 的 1fe35094c4c1d447bdf59c5588167274。
点状图
点状图的方法非常类似。
同样,我为点设置了一些默认 CSS
.dot {
background: blue;
width: 10px;
height: 10px;
border-radius: 50%;
position: absolute;
}
对于点,我们不需要担心宽度或高度,我们只需使用相同的数据和数学方法从左侧和底部定位点即可。
唯一的区别是我们为 $index 添加了 0.5,这样点将位于分配给它的空间的中心。
<div ng-repeat="dot in data" class="dot" style="bottom:{{dot.value / max * height}}px; left:{{($index + 1) / data.length * width}}px;"></div>
完整的图表如下所示
<div class="chart" style="width:{{width}}px; height:{{height}}px;">
<!-- Labels -->
<div class="y" style="width:{{height}}px;">{{yAxis}}</div>
<div class="x">{{xAxis}}</div>
<!-- Data -->
<div ng-repeat="dot in data" class="dot" style="bottom:{{dot.value / max * height}}px; left:{{($index + 0.5) / data.length * width}}px;"></div>
</div>
查看 CodePen 上 Nick Moreton (@nickmoreton) 的 0ee74365fd766311a52aa0d129c22ea8。
SVG 线状图
除了内联 CSS,我们还可以在 SVG 数据中使用 Angular 数据值。
此 CSS 为
svg {
position: absolute;
transform: rotateX(180deg);
left: 0;
}
line {
stroke:red;
stroke-width:3px;
}
我正在旋转 SVG,因为默认情况下它会从顶部处理值,我们需要将其翻转以从底部获取值。
首先,我们需要创建一个跨越图表整个区域的 SVG,然后在其中对 line 元素使用 ng-repeat。
每个 line 元素都需要 X 轴 (x1, x2) 和 Y 轴 (y1, y2) 上的起点和终点。
X 轴非常简单 - 我们遵循与之前相同的系统,以便每条线在图表上等间隔地分布,从 0 开始使用 $index,并在下一条线开始的地方结束,使用 $index + 1。
对于 Y 轴,这就是 $index 变量发挥作用的地方,因为它允许我们从数组中的前一个或下一个条目中选择值。
每条线的初始 Y 点值来自使用 `data[$index - 1].value` 的上一条数据条目,然后我们应用与之前类似的数学运算。对于第二个 Y 点,我们可以直接从条目中调用直线值。
这听起来可能很复杂(我可以向你保证,想通这一点有点让人头疼!),但希望这个解释加上下面的代码能帮助你理解它!
<svg style="width:{{width}}px; height:{{height}}px;">
<line ng-repeat="line in data" x1="{{$index / data.length * width}}" y1="{{data[$index - 1].value / max * height}}" x2="{{($index + 1) / data.length * width}}" y2="{{line.value / max * height}}">
</line>
</svg>
最终的图表代码如下所示
<div class="chart" style="width:{{width}}px; height:{{height}}px;">
<!-- Labels -->
<div class="y" style="width:{{height}}px;">{{yAxis}}</div>
<div class="x">{{xAxis}}</div>
<!-- Data -->
<svg style="width:{{width}}px; height:{{height}}px;">
<line ng-repeat="line in data"
x1="{{$index / data.length * width }}"
y1="{{data[$index - 1].value / max * height}}"
x2="{{($index + 1) / data.length * width}}"
y2="{{line.value / max * height}}">
</line>
</svg>
</div>
参见 Pen a3e89703d7671fa81d5c2ceb20f7b150 by Nick Moreton (@nickmoreton) on CodePen.
下一步做什么?
你也可以在类名中使用 `$index` 变量,例如 `<element class="classname{{$index}}">`,这允许对每个条形图、点或线进行精细控制 - 我们可以使用它来为这些可视化效果制作动画。
我制作了一个带有动画的完整点/线图表示例,以及使用我们数据中每个条目中的标签的 CSS 工具提示。你可以在 这里 查看它。
我还创建了一个包含所有这些示例的 Pen。
我希望本教程对你有所帮助,我很乐意听到你使用 Angular 创建的任何可视化效果!
我一直都在想从哪里开始我的 Angular 学习之旅,而这看起来是一个不错的起点,因为我正在开发一个需要这种图表的功能的 ERP。如果能看到一些饼图就好了,但我猜这需要一些 SVG,你可能认为这对于一个简单的帖子来说过于繁琐了。
对 AngularJs 的精彩介绍!我从一开始就喜欢 AngularJs。我已经使用它制作了许多交互式网页应用程序,使网页应用程序的某些部分易于交互。你可以在 Codepen 上查看一些示例:http://codepen.io/netsi1964/tag/angularjs/
/Sten
Sten,做得很好…
非常有趣,而“有趣”的意思是“太棒了”!当我想要进行数据可视化时,我总是直接使用 D3(即使是在 Angular 应用程序中进行数据可视化时也是如此)。很明显,我一直忽略了 SVG 就像其他一切一样,只是 HTML,而 Angular 在扩展 HTML 方面做得非常棒。在 SVG 声明中看到 Angular 结构让我的极客感动了。感谢这篇文章!
我同意。J
我一直想深入研究 SVG 等,但我总是犹豫在项目中使用它,因为 IE 存在问题。我知道在大多数情况下都有备选方案,但我无法想到针对这种情况的备选方案。有什么文章或想法可以克服这个小问题吗?
@Eric,根据“Can I Use”网站(https://caniuse.cn/#search=svg),你实际上不应该犹豫使用 SVG。全球 93.83% 的浏览器都支持 SVG。
当你写“Data:…” 时,那不是 JSON,而是 JavaScript 对象。
是的,但在现实世界中,它很可能是一个 JSON 提要。
我正在进行一个使用 Angular 的项目,这个项目就在 SVG 的中间。很酷的是,你可以从数据模型中删除一个元素,对应的图形元素也会消失,而无需任何其他代码。
不过我遇到了一个问题,请注意 Chrome 在看到带有不熟悉标记和指令的 SVG 时会发脾气。(Firefox 也会,但程度较轻)。它似乎仍然可以正常工作,但我不知道如何解决这个问题。
我喜欢这些演示在 IE 中从未正常工作。我并没有指责开发人员。我指责浏览器。我出于工作目的使用 IE。不要评判我 :]
不错的教程
我建议使用 `ng-style` 指令,而不是使用 `style` + 插值。类似这样
+1 用于 `ng-style`。为了补充你的示例,我建议使用“min-height”而不是“height”在条形图上使用 CSS 过渡来制作动画。
谢谢大家
是的,`ng-style` 看起来非常适合这种情况,我一定会记在心上。
关于使用过渡,Chris 发布了一篇关于对内联样式进行动画处理的 非常酷的技巧,它可以作用于高度或任何其他合适的属性,我以前在类似的情况下使用过它。
现在是更大的挑战:饼图!万岁,三角学。
它在处理大型数据集时的表现如何?我喜欢这种声明式方法(与 d3 相比),但担心任何中等大小的数据集的 `$watch` 数量会导致性能问题。
我认为在这种情况下,`$watch` 不是最佳方法。最好以某种方式提出一个“脏”标志或类似的东西。因为当数据发生变化时,你通常会在 Angular 能够通过 `$watch` 捕获它之前就知道它。当它确实发生变化时,你可以发出一个事件,触发重新加载数据并重新渲染图表。
问题在于,Angular 为任何 {{expr}} 表达式和 ng-repeat/if/show/class/style 属性创建隐式 `$watch`。这也是使用 ng-repeat 创建大型数据表格会导致性能问题的原因 - 每个 ng-repeated 行都会创建一个隐式 `$watch`。更多 `$watch` = 应用程序更慢,因为每个 `$watch` 至少在每次消化周期中都会被评估一次。
我们在 Next Big Sound 遇到了类似的问题,我们的解决方案之一是使用一次性绑定(也称为绑定一次)与 `watchCollection()`。查看 http://stackoverflow.com/questions/23903389/do-bindings-nested-inside-of-a-lazy-one-time-ng-repeat-binding-bind-just-once
大多数人可能永远不会遇到这种性能问题,但知道它总归是件好事。我喜欢你的方法!
Alexandros Marinos 撰写了一篇关于基于 Angular 的声明式数据可视化方法的深入文章。如果你觉得这篇文章很有趣,这篇文章是一个很好的后续内容:http://alexandros.resin.io/angular-d3-svg/
尽管 AngularJS 的学习曲线很陡峭,需要花费相当长的时间才能理解其背后的概念(如果你还没有熟悉这些概念),但一旦你完成了学习,我相信你会喜欢它,并且你会在创建交互式和模块化数据可视化效果的背景下看到它的价值。