HighCharts4--学习指南-全-
HighCharts4 学习指南(全)
原文:
zh.annas-archive.org/md5/66c0e35f19a805eeed24dc912ad49b35译者:飞龙
前言
《学习 Highcharts 4》 的目标是成为从各个角度填补 Highcharts 空白的指南。本书是为希望了解 Highcharts 的网络开发者所写。本书具有以下特点:
-
这是一份逐步指南,指导如何将基本外观的图表构建成可展示的图表。
-
书中包含大量使用真实数据的示例,涵盖了所有 Highcharts 系列类型——线/样条图、柱状图、饼图、环形图、散点图、气泡图、面积范围、柱状图范围、仪表、实心仪表、金字塔图、箱线图、蜘蛛图、瀑布图、漏斗图、误差条、热图和 3D 图表。
-
包含了在线参考手册和演示中尚未涉及的主题领域,例如图表布局结构、颜色渐变,以及对于气泡系列中的 sizeBy、柱状系列中的 groupZPadding 等一些特定选项的深入解释,以及如何修改或创建插件。
-
涵盖了使用 Highcharts API 和事件处理创建动态和交互式图表的应用程序。
-
也涵盖了如何将 Highcharts 与移动框架(如 jQuery Mobile)和富互联网应用程序框架(如 Ext JS)集成的应用程序。
-
涵盖了演示如何在服务器端运行 Highcharts 以自动化图表生成并导出其图形输出的应用程序。
-
使用最新的在线服务 Highcharts Cloud,你将学习如何将图表嵌入到文档中。
-
你还将学习 Highcharts 插件的架构以及如何创建插件。
本书不是参考手册,因为 Highcharts 团队已经提供了详尽的在线参考,并且每个配置都附有 jsFiddle 演示。本书也不旨在成为图表设计指南或使用 Highcharts 进行编程设计的教程。
简而言之,这本书向你展示了你可以用 Highcharts 做些什么。
本书涵盖的内容
第二版增加了四个新章节,重写了其中一个章节,并在一些现有章节中增加了新内容。前版的所有内容都进行了技术修订。因此,新版本大约包含 50% 的新材料。
由于本书包含大量示例,因此不可能包含每个示例的所有源代码。对于逐步教程,代码是逐步列出的。如果你想尝试样本代码,强烈建议你从 Packt Publishing 网站下载代码,或访问 joekuan.org/Learning_Highcharts 以查看在线演示。
第一章,网络图表,描述了从 HTML 诞生到最新的 HTML5 标准使用 SVG 和 canvas 技术的网络图表的发展历程。本章还简要概述了使用 HTML5 标准的图表软件市场,并讨论了为什么 Highcharts 比其他产品更好。
第二章, Highcharts 配置,介绍了图表组件中的常见配置选项,并提供了大量示例,解释了图表布局的工作原理。
第三章, 线形、面积和散点图,展示了如何绘制简单的线形图、面积图和散点图,以及如何绘制包含所有三种系列类型的海报式图表。
第四章, 柱状图和条形图,展示了柱状图和条形图以及各种衍生图表,如堆叠图、百分比图、镜像图、分组图、重叠图、镜像堆叠图和水平仪表图。
第五章, 饼图,展示了如何构建各种图表,从简单的饼图到多系列图表,例如图表中的多个饼图和同心圆环饼图,即甜甜圈图。本章还探讨了如何使用特定选项创建开放的甜甜圈图。
第六章, 仪表、极坐标和范围图,是构建双指针速度计的逐步指南,以及创建简单的实心仪表图的制作。它还展示了极坐标图的特点及其与笛卡尔图的相似性。它还说明了在面积图和柱状图范围图上使用范围数据。
第七章, 气泡图、箱线图和误差线图,解释了气泡图的特点及其特定选项。本章建立了一种逐步的方法来创建类似于现实生活体育图表的气泡图,将相同的练习应用于环境数据的箱线图,并提供了使用赛车数据创建误差线图的教程。
第八章, 瀑布图、漏斗图、金字塔图和热力图,说明了如何配置瀑布图和漏斗图,并使用钻取功能将两个图表链接起来。然后有一个关于金字塔图和从财务手册中重建它们的教程。接着,介绍了热力图,并通过体验多个系列选项展示了不同的输出结果。
第九章, 3D 图表,讨论了 Highcharts 中的 3D 图表真正意味着什么,并展示了 3D 图表的概念,例如柱状图、饼图和散点系列。它通过实验说明了具体的 3D 选项,并从信息图表中重建了一个 3D 图表。本章还涵盖了如何通过鼠标操作创建交互式的 3D 方向。
第十章,Highcharts API,解释了 Highcharts API 的使用,并通过使用股票市场演示来绘制动态图表来展示这一点。本章讨论了使用不同方法更新系列,并分析了各种浏览器上每种方法的性能以及 Highcharts 的可扩展性。
第十一章,Highcharts 事件,解释了 Highcharts 事件,并通过与投资组合应用程序演示中的图表的各种用户交互来演示它们。
第十二章,Highcharts 和 jQuery Mobile,是一个关于 jQuery Mobile 框架的简短教程,展示了如何通过创建一个浏览奥运奖牌表的移动 Web 应用程序来将其与 Highcharts 集成。本章还涵盖了使用基于触摸和旋转事件与 Highcharts 的使用。
第十三章,Highcharts 和 Ext JS,是 Sencha 的 Ext JS 的简要介绍,描述了在具有 Highcharts 的应用程序中可能使用的组件。它还展示了如何在 Ext JS 应用程序中通过使用模块和 Highcharts 扩展来绘制 Highcharts 图表。
第十四章,服务器端 Highcharts,讨论了 Highcharts 的基于服务器端解决方案如何使用 PhantomJS 进行演变。给出了对 PhantomJS 的快速介绍,并进行了逐步实验,以创建一个使用 Highcharts 的服务器端解决方案。本章还演示了如何使用 Highcharts 官方服务器端脚本。
第十五章,Highcharts 在线服务和插件,是关于导出服务器服务的快速介绍,并讨论了最近的一个重要云发展:Highcharts Cloud。本章带您了解它提供的内容以及如何使用它,从对 Highcharts 和 JavaScript 编程没有任何先验知识的基本水平到高级用户水平。本章还演示了插件的架构,并展示了如何组合多个插件来创建新的用户体验。
阅读本书所需的条件
阅读者在以下领域应具备基本的网络开发知识:
-
HTML 文档的结构及其语法
-
Ajax
由于本书全部关于 Highcharts,而 Highcharts 是用 JavaScript 开发的,因此读者应熟悉该语言的中级水平。一些示例使用 jQuery 作为快速访问文档元素并将方法绑定到事件的方式。因此,对 jQuery 的基本了解就足够了。对 jQuery UI 的了解也将是一个优势,因为它在第十章、Highcharts API和第十一章、Highcharts 事件中都有轻微的使用。
本书面向的对象
本书是为以下网络开发者编写的:
-
希望学习如何将图形图表集成到他们的 Web 应用程序中
-
希望将他们的 Adobe Flash 图表迁移到 HTML5 JavaScript 解决方案
-
想通过示例了解更多关于 Highcharts 的信息
约定
在这本书中,您将找到许多用于区分不同类型信息的文本样式。以下是一些这些样式的示例及其含义的解释。
文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名应如下显示:“在这个演示中,updatePie函数在几个地方被调用,例如删除一个系列、当图例复选框被选中时,等等。”
代码块应如下设置:
// Bring up the modify dialog box
click: function(evt) {
// Store the clicked pie slice
// detail into the dialog box
$('#updateName').text(evt.point.name);
$('#percentage').val(evt.point.y);
$('#dialog-form').dialog("option",
"pieSlice", evt.point);
当我们希望将您的注意力引向代码块中的特定部分时,相关的行或项目将以粗体显示:
<div data-role="content">
<ul data-role="listview" data-inset="true">
<li><a href="./gold.html"
data-ajax="false" >Top 10 countries by gold</a></li>
<li><a href="./medals.html"
data-ajax="false" >Top 10 countries by medals</a></li>
<li><a href="#">A-Z countries</a></li>
<li><a href="#">A-Z olympians</a></li>
</ul>
任何命令行输入或输出都应如下编写:
java -jar batik-rasterizer.jar /tmp/chart.svg
新术语和重要词汇将以粗体显示。屏幕上看到的单词,例如在菜单或对话框中,在文本中应如下显示:“顺序是这样的,任天堂类别的子类别值在艺电的子类别数据之前,等等。”
注意
警告或重要注意事项将以如下框的形式出现。
小贴士
小技巧和窍门如下所示。
读者反馈
读者反馈始终欢迎。请告诉我们您对这本书的看法——您喜欢或不喜欢什么。读者反馈对我们来说很重要,因为它帮助我们开发出您真正能从中获得最大价值的标题。
要发送给我们一般性的反馈,只需发送电子邮件至<feedback@packtpub.com>,并在邮件的主题中提及书籍的标题。
如果您在某个主题上具有专业知识,并且您对撰写或为书籍做出贡献感兴趣,请参阅我们的作者指南www.packtpub.com/authors。
客户支持
现在您已经是 Packt 书籍的骄傲拥有者,我们有一些事情可以帮助您从您的购买中获得最大收益。
下载示例代码
您可以从www.packtpub.com的账户下载您购买的所有 Packt 出版物的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。
错误清单
尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果您在我们的书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问 www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站或添加到该标题的勘误部分下的现有勘误列表中。
要查看之前提交的勘误,请访问 www.packtpub.com/books/content/support,并在搜索字段中输入书籍名称。所需信息将出现在勘误部分下。
盗版
互联网上对版权材料的盗版是一个跨所有媒体的持续问题。在 Packt,我们非常重视保护我们的版权和许可证。如果您在互联网上发现任何形式的我们作品的非法副本,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。
请通过 <copyright@packtpub.com> 联系我们,并提供涉嫌盗版材料的链接。
我们感谢您在保护我们的作者和我们为您提供有价值内容的能力方面的帮助。
询问
如果您对本书的任何方面有问题,您可以通过 <questions@packtpub.com> 联系我们,我们将尽力解决问题。
第一章 网络图表
在本章中,你将了解网络图表的一般背景。这包括网络图表在 Ajax 和 HTML5 成为新标准之前是如何制作的简要历史。将简要讨论 JavaScript 编程的最新进展。然后,介绍 SVG 支持和新的 HTML5 功能 canvas,这是 JavaScript 图表的主要推动力,并进行演示。接下来,将快速介绍市场上可用的其他 JavaScript 绘图包。最后,我们将介绍 Highcharts,并解释使用 Highcharts 相对于其他产品的优势。在本章中,我们将涵盖以下主题:
-
网络图表的简要历史
-
JavaScript 和 HTML5 的兴起
-
市场上的 JavaScript 图表
-
为什么选择 Highcharts?
网络图表的简要历史
在深入研究 Highcharts 之前,值得提及的是,网络图表是如何从纯 HTML 和服务器端技术发展到当前客户端技术的。
HTML 图像映射(服务器端技术)
这种技术自 HTML 的早期就开始使用,当时服务器端操作是主要驱动力。图表只是由网络服务器生成的 HTML 图像。在 PHP 等服务器端脚本语言出现之前,最常见的方法之一是使用通用网关接口(CGI),它执行绘图程序(如 gnuplot)以输出图像。后来,当 PHP 变得流行时,使用了 GD 图形模块进行绘图。使用这种技术的产品之一是JpGraph。
以下是如何在 HTML 页面中包含图表图像的示例:
<img src="img/pie_chart.php" border=0 align="left">
图表脚本文件 pie_chart.php 嵌入 HTML img 标签中。当页面加载时,浏览器看到 img src 属性并发送一个对 pie_chart.php 的 HTTP 请求。对于网络浏览器来说,它不知道.php 文件是否是一个图像文件。当带有 PHP 支持的服务器接收到请求时,它识别.php 扩展名并执行 PHP 脚本。以下是一个简化的 JpGraph 示例;脚本输出图像内容并将其作为 HTTP 响应流回,就像正常图像内容被发送回一样:
// Create new graph
$graph = new Graph(350, 250);
// Add data points in array of x-axis and y-axis values
$p1 = new LinePlot($datay,$datax);
$graph->Add($p1);
// Output line chart in image format back to the client
$graph->Stroke();
此外,这项技术与 HTML map 标签结合,用于图表导航,因此当用户点击图表的某个区域,例如饼图中的一个切片时,它可以加载一个包含另一个图表的新页面。
这种技术有以下优点:
-
非常适合自动化任务,例如带有图形附件的定时报告或电子邮件警报。
-
不需要 JavaScript。它是健壮的,纯 HTML,且对客户端影响较小。
它有以下缺点:
-
服务器端的工作负载更多
-
纯 HTML 和有限的技术——无法在图表上放置更多交互,也无法在动画上放置任何交互
Java 小程序(客户端)和 servlet(服务器端)
Java 小程序使网络浏览器能够执行多平台 Java 字节码,以实现 HTML 无法做到的事情,例如图形显示、动画和高级用户交互。这是第一种将传统基于服务器的操作扩展到客户端的技术。要在 HTML 页面中包含 Java 小程序,需要使用 HTML 的applet(已弃用)或object标签,并且需要在浏览器中安装 Java 插件。
以下是在object标签内包含 Java 小程序的示例。由于 Java 在 Internet Explorer 中与其他浏览器运行的环境不同,因此使用了针对 IE 的条件注释:
<!--[if !IE]> Non Internet Explorer way of loading applet -->
<object classid="Java:chart.class" type="application/x-java-applet"
height="300" width="550" >
<!--<![endif] Internet way of loading applet -->
<object classid="clsid:8AD9C840..." codebase="/classes/">
<param name="code" value="chart.class" />
</object>
<!--[if !IE]> -->
</object>
<!--<![endif]-->
通常,Java 2D 图表产品是由 Java 抽象窗口工具包(AWT)的java.awt.Graphics2D类和java.awt.geom包构建的。然后,主要的图表库允许用户通过扩展Applet类在浏览器中使用它,或者通过扩展Servlet类在服务器端运行它。
Java 产品的例子是JFreeChart。它包含 2D 和 3D 解决方案,并且免费供非营利使用。JFreeChart 可以作为 applet、servlet 或独立应用程序运行。以下展示了在 applet 内绘制数据点的部分代码:
public class AppletGraph extends JApplet {
// Create X and Y axis plot dataset and populate
// with data.
XYPlot xyPlot = new XYPlot();
xyPlot.setDataset(defaultXYDataset);
CombinedDomainXYPlot combinedDomainXYPlot =
new CombinedDomainXYPlot();
combinedDomainXYPlot.add(xyPlot);
// Create a jFreeChart object with the dataset
JFreeChart jFreeChart = new JFreeChart(combinedDomainXYPlot);
// Put the jFreeChart in a chartPanel
ChartPanel chartPanel = new ChartPanel(jFreeChart);
chartPanel.setPreferredSize(new Dimension(900,600));
// Add the chart panel into the display
getContentPane().add(chartPanel);
}
要在服务器端运行图表应用程序,需要一个 servlet 容器,例如 Apache Tomcat。标准的web.xml文件被定义为将 URL 绑定到 servlet:
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="server_charts" version="2.4"
xsi:schemaLocation="...">
<servlet>
<servlet-name>PieChartServlet</servlet-name>
<servlet-class>charts.PieChartServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>PieChartServlet</servlet-name>
<url-pattern>/servlets/piechart</url-pattern>
</servlet-mapping>
</web-app>
当 Servlet 容器,例如 Tomcat,接收到 URL 为http://localhost/servlets/piechart的 HTTP 请求时,它将请求解析为一个 servlet 应用程序。然后,Web 服务器执行图表 servlet,将输出格式化为图像,并将图像内容作为 HTTP 响应返回。
这种技术有以下优点:
-
高级图形、动画和用户界面
-
可重用核心代码以支持不同的部署选项:客户端、服务器端或独立应用程序
它有以下缺点:
-
小程序安全问题
-
如果插件崩溃,可能会导致浏览器挂起或崩溃
-
非常占用 CPU
-
需要 Java 插件
-
长启动时间
-
标准化问题
Adobe Shockwave Flash (客户端)
Flash 因其能够在网页浏览器中提供音频、图形、动画和视频功能而被广泛使用。浏览器需要安装 Adobe Flash Player 插件。至于绘图,在 HTML5 标准流行之前,这种技术是常见的选择(因为其他选项不多)。
采用这种技术的绘图软件自带其自己的导出Shockwave Flash(SWF)文件。这些 SWF 文件包含基于压缩矢量的图形和编译后的 ActionScript 指令来创建图表。为了使 Flash Player 能够显示图表,必须从 HTML 页面加载 SWF 文件。为此,需要一个 HTML object标签。该标签由软件的 JavaScript 例程内部创建并注入到文档的 DOM 中。
在这个 object 标签内包含用于绘制图表的尺寸和 SWF 路径信息。图表变量数据也包含在这个标签内。因此,一旦浏览器看到具有特定参数的 object 标签,它就会调用已安装的 Flash Player 来处理 SWF 文件和参数。为了将图表的绘图数据从服务器端传递到客户端 Flash Player,flashVars 被嵌入到具有数据类型的 param 标签中。以下是一个来自 Yahoo YUI 2 的示例:
<object id="yuiswf1" type="..." data="charts.swf" width="100%" height="100%">
<param name="allowscriptaccess" value="always">
<param name="flashVars" value="param1=value1¶m2=value2">
</object>
该技术具有以下优点:
- 精美的图形和动画,丰富的用户交互
它有以下缺点:
-
如果插件崩溃,它可能会挂起或崩溃浏览器
-
非常占用 CPU 资源
-
长时间启动
-
标准化问题
JavaScript 和 HTML5 的兴起
JavaScript 的作用已经从几个简单的客户端程序转变为创建和管理网络用户界面的主导语言。随着函数对象、原型和闭包的引入,编程技术已经转向面向对象。这得益于像 Douglas Crockford 这样的先驱者,他通过他的书 JavaScript: The Good Parts,O'Reilly Media / Yahoo Press 负责将语言转化为教育,使 JavaScript 成为一种更好的语言。其他人包括 Sam Stephenson,Prototype JavaScript 库的创建者(www.prototypejs.org),以及 John Resig,JQuery 库的创建者(jquery.com),他们把 JavaScript 带入了一个构建更复杂前端网络软件的框架。
本书范围之外,无法介绍这种新的编程风格。读者应了解 jQuery 和 CSS 选择器语法的基础,这些语法在部分章节中使用。读者还应熟悉书中描述的先进 JavaScript 脚本,如 JavaScript: The Good Parts,O'Reilly Media / Yahoo Press,包括原型、闭包、继承和函数对象。
HTML5(SVG 和 Canvas)
在本节中,介绍了两种 HTML5 技术:SVG 和 Canvas,以及相应的示例。
SVG
HTML5 是迄今为止 HTML 标准的最大进步。该标准的采用率正在快速增长(也得益于苹果移动设备,它们停止支持 Adobe Flash)。再次强调,本书范围之外,无法涵盖它们。然而,与网络图表最相关的是 可缩放矢量图形(SVG)。SVG 是一种 XML 格式,用于描述基于矢量的图形,由路径、文本、形状、颜色等组件组成。这项技术与 PostScript 类似,但 PostScript 是一种基于堆栈的语言。正如其名称所暗示的,SVG 的一个主要优点是它是一种无损技术(与 PostScript 相同):放大图像时不会出现任何像素化效果。减小图像尺寸不会导致原始内容丢失。
此外,SVG 可以使用同步多媒体集成语言(SMIL)进行脚本化动画和事件处理。除了 IE 之外,这项 SVG 技术被所有主流浏览器支持,caniuse.com/#feat=svg-smil。
以下是一个简单的 SVG 代码示例,展示了两个点之间的单条曲线:
<svg version="1.1">
<path id="curveAB" d="M 100 350 q 150 -300 300 0" stroke="blue" stroke-width="5" fill="none" />
<!-- Mark relevant points -->
<g stroke="black" stroke-width="3" fill="black">
<circle id="pointA" cx="100" cy="350" r="3" />
<circle id="pointB" cx="400" cy="350" r="3" />
</g>
<!-- Label the points -->
<g font-size="30" font="sans-serif" fill="black" stroke="none" text-anchor="middle">
<text x="100" y="350" dx="-30">A</text>
<text x="400" y="350" dx="30">B</text>
</g>
</svg>
前面的 SVG 代码按照以下步骤执行:
-
使用
id="curveAB"绘制路径,并使用数据 (d)。首先,将M移动到绝对坐标 (100,350),然后从当前位置绘制一条贝塞尔二次曲线到 (150,-300),并最终到达 (300,0)。 -
将两个圆形元素——
"pointA"和"pointB"——分别以中心坐标 (100,350) 和 (400,350) 以及3像素的半径组合在一起。然后使用黑色填充两个圆。 -
将两个文本元素
A和B组合在一起,起始位置分别为 (100,350) 和 (400,350),使用无衬线字体黑色显示,然后分别在 x 轴(dx)上左右移动30像素。
以下是从 SVG 脚本生成的最终图形:

Canvas
Canvas 是另一个新的 HTML5 标准,被一些 JavaScript 图表软件包所使用。Canvas 的目的正如其名所示;你在 canvas 标签上声明一个绘图区域,然后使用新的 JavaScript API 以像素为单位绘制线条和形状。这是两种技术的显著区别:Canvas 基于像素,而 SVG 基于矢量。Canvas 没有内置的动画例程,因此使用定时序列的 API 调用来模拟动画。此外,它没有事件处理支持,因此开发者需要手动将事件处理程序附加到画布上的某些区域。复杂的图表动画可能更难实现。
以下是一个 Canvas 代码示例,它实现了与前面 SVG 曲线相同的效果:
<canvas id="myCanvas" width="500" height="300" style="border:1px solid #d3d3d3;">Canvas tag not supported</canvas>
<script type="text/javascript">
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
// Draw the quadratic curve from Point A to B
ctx.beginPath();
ctx.moveTo(100, 250);
ctx.quadraticCurveTo(250, 0, 400, 250);
ctx.strokeStyle="blue";
ctx.lineWidth=5;
ctx.stroke();
// Draw a black circle attached to the start of the curve
ctx.fillStyle="black";
ctx.strokeStyle="black";
ctx.lineWidth=3;
ctx.beginPath();
ctx.arc(100,250,3, 0, 2* Math.PI);
ctx.stroke();
ctx.fill();
// Draw a black circle attached to the end of the curve
ctx.beginPath();
ctx.arc(400,250,3, 0, 2* Math.PI);
ctx.stroke();
ctx.fill();
// Display 'A' and 'B' text next to the points
ctx.font="30px 'sans-serif'";
ctx.textAlign="center";
ctx.fillText("A", 70, 250);
ctx.fillText("B", 430, 250);
</script>
如您所见,Canvas 和 SVG 都可以完成相同的工作,但 Canvas 需要更多的指令:

与 SVG 中的连续路径描述不同,这里调用了一系列 JavaScript 绘图方法。前面的 Canvas 代码遵循以下步骤来绘制曲线:
-
在前面的示例中,它首先调用
beginPath在画布上开始一个路径,然后发出moveTo调用来将路径起点移动到坐标 (100, 250) 而不创建线条。quadraticCurveTo方法从moveTo位置创建曲线路径到曲线顶部和终点,终点是 (400, 250)。一旦路径设置好,我们在调用stroke方法绘制路径上的线条之前设置描边属性。 -
我们调用
beginPath来重置为一个新的路径进行绘制,并调用arc方法在曲线路径的起始和结束坐标处创建一个微小的圆形路径。一旦我们设置了描边和填充样式,我们就调用fill和stroke例程来填充它为黑色,作为坐标标记。 -
最后,我们设置了字体属性,并通过调用
fillText并在坐标标记附近的位置创建文本A和B。
简而言之,Canvas 采用多个属性设置程序,而不是像 SVG 那样使用带有多个属性的单一标签。SVG 主要是声明性的,而 Canvas 强制执行命令式编程方法。
市场上的 JavaScript 图表
用 SVG 或 Canvas 手动编码编程图表是不可想象的。幸运的是,市场上提供了许多不同的图表库。讨论每一个都是不可能的。
小贴士
由于缺乏竞争优势,我们省略了本书第一版中提到的一些产品。
图表库是开源的,但其中一些在基本图表方面并不全面,例如饼图、折线图和条形图,看起来相当不完整。在这里,我们讨论了一些商业和开源产品,包括所有基本图表和一些附加功能。其中一些仍然支持 Flash 插件,这是向后兼容的一个选项,因为 SVG 和 canvas 在旧浏览器中不受支持。尽管其中一些产品对商业开发不是免费的,这是可以理解的,但它们的价格非常合理。
注意
查看code.google.com/p/explorercanvas/。许多库使用这个附加组件来模拟 IE 9 之前的 Canvas。
amCharts
amCharts 提供了一套完整的 2D 和 3D 图表,以及其他有趣的图表,如雷达图、气泡图、蜡烛图和极坐标图。所有图表看起来都很漂亮,并支持动画。amCharts 对商业使用是免费的,但在图表的左上角会显示一个信用标签。图表的展示效果很好,动画可以完全自定义。与 Highcharts 相比,默认的动画似乎有些过度。
Ext JS 5 图表
Ext JS 是由专注于 Web 应用开发的先驱公司 Sencha 开发的一个非常流行的 Ajax 应用框架。与使用 YUI 2 Flash 图表库的前一代 Ext JS 3 不同,Ext JS 4 自带一个纯 JavaScript 图表库。随着市场趋势逐渐远离 Adobe Flash,Sencha 推出了自家的图表库。Ext JS 4 涵盖了所有基本的 2D 图表以及仪表盘和雷达图,所有图表都支持动画。对于开源和非商业用途,许可证是免费的,但商业开发需要开发者许可证。Ext JS 图表的一个巨大优势是,与一套全面的 UI 组件集成,例如对于具有存储框架的图表,使用编辑器显示/更新图表和数据表都非常简单。
在 Ext JS 5 中,图表库已经被完全重构。与 Ext JS 4 相比,外观是一个巨大的进步:图表看起来更加专业,与其他竞争对手处于同一水平。尽管 Ext JS 5 的图表布局、颜色和动画可能不如 Highcharts 时尚和流畅,但它们仍然非常接近,并且仍然受到好评。
数据驱动文档
数据驱动文档(D3)是最广泛使用的图表库。它由 Bostock 等人创建,是在他们之前对 Protovis 的学术研究工作上的重大改进。
注意
Bostock, Michael; Ogievetsky, Vadim; Heer, Jeffrey (October 2011), D3: 数据驱动文档, IEEE Transactions on Visualization and Computer Graphics, IEEE 出版社。
D3 的原则非常独特,因为它专注于文档元素中的转换。换句话说,它是一个装饰性框架,通过众多 API 来操作选定的元素,称为选择。这些 API 允许用户将选择与图表数据连接,并像 CSS 一样样式化它们,或者应用特定的效果。这种方法使得 D3 能够创建出其他产品无法生产的各种令人印象深刻的图表,并带有动画效果。它是绘制特定科学图表或需要复杂数据可视化展示的图表的理想工具,例如层次边束图。
该软件可用于商业用途且免费,已经吸引了大量用户基础和用户社区的贡献,尤其是来自学术界的贡献。
由于其高度可编程和相对低级的编程方法,它需要更陡峭的学习曲线,可能不会吸引那些不太技术性的用户或寻求在生产级别上获得图表解决方案的开发者。至于在 D3 中构建 3D 图表,这可能是一项具有挑战性的任务。尽管 D3 在性能、控制和展示方面表现优异,但它并非适合所有人。
FusionCharts
FusionCharts 可能是看起来最令人印象深刻的工具之一,并且在市场上提供了最全面的图表系列。它不仅提供了一系列有趣的 2D 图表(雷达图、仪表盘、地图和蜡烛图)作为独立产品,而且还提供了完全交互式的 3D 图表。所有的图表动画都制作得非常专业。FusionCharts 可以在两种模式下运行:Flash 或 JavaScript。尽管 FusionCharts 的价格较高,但它提供了带有可旋转、动画效果的列、饼图、漏斗图和金字塔图系列的最佳外观图表。
Raphaël
Raphaël 是另一个免费的图形库。它支持早期浏览器中的 SVG 和 VML。它设计成一个通用的图形库,处理 SVG 元素,但也可以用作图表解决方案。该软件提供 API 来创建类似于 Canvas 的对象,即 paper,用户可以在其中创建基本形状或 SVG 线元素。API 结构在某种程度上与 D3 相似,因为它可以将数据绑定到元素并操作它们,但方法较为原始,而 D3 是一个以数据为中心的解决方案。其文档基础,用户社区较小。与 D3 一样,编程一个可展示的图表比 Highcharts 需要更多努力,并且不是 3D 图表解决方案的理想选择。
为什么选择 Highcharts?
Highcharts 在市场上提供了非常吸引人和专业-looking 的 2D/3D 图表。这是一个通过关注细节而脱颖而出的产品,不仅体现在展示方面,还包括后面描述的其他领域。它是由挪威公司 Highsoft AS 开发的,由 Torstein Hønsi 创建和成立,并于 2009 年底发布。Highcharts 不是他们的第一个产品,但无疑是他们最畅销的产品。
Highcharts 和 JavaScript 框架
虽然 Highcharts 是用 JavaScript 框架库构建的,但其实现方式使其不依赖于特定的框架。Highcharts 附带适配器,使其接口对框架可插拔。
因此,Highcharts 可以与 MooTools、Prototype 或 jQuery JavaScript 框架集成。对于使用纯 JavaScript 编写的用户,Highcharts 还提供了一个独立的框架。这使用户能够在不影响他们已经开发的产品的情况下,或者允许他们选择最适合他们项目的框架。在 jQuery 中开发他们的网络图表应用的用户,只需在 Highcharts 之前加载 jQuery 库即可。
在 MooTools 环境中使用 Highcharts,用户只需执行以下操作:
<script src="img/mootools-yui-compressed.js"></script>
<script type="text/javascript"
src="img/mootools-adapter.js"></script>
<script type="text/javascript"
src="img/highcharts.js"></script>
在 Prototype 下使用 Highcharts,用户需要执行以下操作:
<script src="img/prototype.js"></script>
<script type="text/javascript"
src="img/prototype-adapter.js"></script>
<script type="text/javascript"
src="img/highcharts.js"></script>
展示
Highcharts 在视觉效果和感觉上取得了正确的平衡。图表本身视觉上令人愉悦,同时风格简洁。默认的颜色选择令人舒适,没有强烈的对比,并且不会相互冲突,得益于微妙的阴影和白色边框效果。文本和坐标轴的颜色都不是黑色或任何深色,这有助于将观众的注意力集中在彩色数据展示上。以下是一个 Highcharts 表示的示例:

Highcharts 中的所有动画(初始、更新、工具提示)都经过精心调整:平滑且逐渐减速。多系列饼图的初始动画是最令人印象深刻的,这是一个 Highcharts 明显优于其他图表的领域。其他图表的动画过于机械,过多,有时甚至令人反感:

工具提示和图例(包括内边和外边)的圆角以及简单的边框不会与观众的注意力竞争,并且很好地融入图表中。以下是一个工具提示示例:

以下是一个带有两个系列的图例示例:

简而言之,Highcharts 中的每个元素都不会与其他元素竞争观众的注意力,因此它们平均分担负载并协同工作作为图表。
许可证
Highcharts 提供免费的非商业许可证以及商业许可证。个人和非营利目的的免费许可证是Creative Commons – Attribution Noncommercial 3.0。Highcharts 为不同的目的提供不同类型的商业许可证。他们有一个一次性单网站许可证,幸运的是,还有一个开发者许可证。对于网络开发产品,由于以下原因,开发者许可证比按网站使用单位收费或非常昂贵的 OEM 许可证是一个更好的模型:
-
软件公司更容易在其开发计划中计算出数学问题
-
关于销售了多少副本的担忧较少,以免违反许可证
与往常一样,开发者许可证不会自动授予无限期使用 Highcharts 的权利。许可证仅允许在许可证购买日期起一年内无限期使用所有发布的版本。此后,如果开发者决定使用新版本,则需要全新的许可证。此外,OEM 许可证可以协商任何条件,报价通常基于项目中的开发人员数量和部署数量。
简单的 API 模型
Highcharts 具有非常简单的 API 模型。要创建图表,构造函数 API 期望一个包含所有必要设置的指定对象。要动态更新现有图表,Highcharts 提供了一组小的 API。配置属性在第二章中详细描述,Highcharts 配置。API 调用在第十章中讨论,Highcharts API。
文档
Highcharts 的在线文档是真正优于其他领域的部分之一。它不仅仅是一个简单的文档页面,将所有定义和示例都堆放在一起。这是一个经过深思熟虑的文档页面。
文档页面的左侧以对象结构组织,正如您创建图表时传递给它一样。您可以根据 JavaScript 控制台中的方式进一步展开和折叠对象的属性。这有助于用户通过自然使用来熟悉产品:

文档中经过深思熟虑的部分在右侧,有属性的说明。每个定义都附带一个描述和每个设置的在线演示,链接到 jsFiddle 网站:

这个即时的 jsFiddle 示例邀请用户探索不同的属性值并观察它们对图表的影响,因此整个文档浏览过程变得非常有效且流畅。
开放性(带有用户声音的功能请求)
Highcharts 决定每个主要版本新功能的一个重要方式是通过用户的反馈(这在开源项目中并不罕见,但 Highcharts 在这个领域优于其他项目)。用户可以提交新的功能请求并对它们进行投票。然后公司审查得票最多的功能请求,并制定包括新功能在内的开发计划。该计划的细节随后发布在 Highcharts 网站上。
此外,Highcharts 还托管在 GitHub 上,这是一个在线公共源代码控制服务,允许 JavaScript 开发者贡献并克隆他们自己的版本:

Highcharts – 快速教程
在本节中,你将了解如何实现你的第一个 Highcharts 图表。假设我们想从我们的本地 Web 服务器运行 Highcharts,首先从 Highcharts 网站下载最新版本:www.highcharts.com/download。
或者,我们可以通过以下方式通过内容分发网络(CDN)加载库:
<script type="text/javascript"
src="img/highcharts.js"></script>
对于调试,highcharts.src.js 也可以从 CDN 获取。
目录结构
当你解压下载的 ZIP 文件时,你应该在 Highcharts-4.x.x 顶级目录下看到以下目录结构:

以下每个目录包含的内容及其用途:
-
index.html:这是演示 HTML 页面,与 Highcharts 网站上的演示页面相同,这样你就可以离线实验 Highcharts。 -
examples:这包含所有示例的源文件。 -
graphics:这包含示例中使用的图像文件。 -
gfx:这包含一个图像文件,用于为仅支持 VML 的浏览器生成径向渐变效果。 -
exporting-server:这个目录包含三个主要组件:一个在线导出服务器的 Java 实现、一个用于在服务端运行 Highcharts 的 PhantomJs 工具包,以及一个使用 Batik 服务的服务器脚本。这个目录对需要设置自己的内部导出服务或作为服务器进程运行 Highcharts 的用户很有用。参见第十四章,服务器端 Highcharts 和 第十五章,Highcharts 在线服务和插件。 -
js:这是 Highcharts 代码的主要目录。每个 JavaScript 文件名有两个后缀:.src.js,其中包含带有注释的源代码,以及.js,这是 JavaScript 源文件的压缩版本。highcharts.js文件包含核心功能以及基本的图表实现,highcharts-3d.js是 3D 图表的扩展,而highcharts.more.js包含额外的系列,如极坐标、仪表、气泡、范围、瀑布、误差条和箱线图。Highcharts-all.js允许用户在他们的应用程序中提供所有图表系列。 -
adapters:此目录包含默认的适配器 standalone-framework,以源代码和压缩格式提供。 -
modules:自上一版本以来,Highcharts 创建了许多插件模块,如 solid-gauge、funnel、exporting、drilldown、canvas-tools 等。此目录包含这些插件。注意
第三方工具 canvg 支持安卓 2.x 版本,因为原生浏览器没有 SVG 支持,但可以显示 canvas。
-
themes:这包含一组预构建的 JavaScript 文件,其中包含背景颜色、字体样式、坐标轴布局等设置。用户可以在他们的图表中加载这些文件之一以实现不同的样式。
您需要做的就是将顶层 Highcharts-4.x.x/js 目录移动到您的 Web 服务器文档根目录中。
要使用 Highcharts,您需要在您的 HTML 文件中包含 Highcharts-4.x.x/js/highcharts.js 库。以下是一个示例,展示了公共网站浏览器使用的百分比。该示例使用最小配置设置,以便您快速开始。以下示例的上半部分:
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=utf-8">
<title>Highcharts First Example</title>
<script src="img/jquery.min.js"></script>
<script type="text/javascript"
src="img/highcharts.js"></script>
我们使用 Google 公共库服务在加载 Highcharts 库之前加载 jQuery 库版本 1.8.2。在撰写本文时,最新的 jQuery 版本是 2.1.1,而 Highcharts 对 jQuery 的系统要求是 1.8.2。
示例的第二部分是主要的 Highcharts 代码,如下所示:
<script type="text/javascript">
var chart;
$(document).ready(function() {
Chart = new Highcharts.Chart({
chart: {
renderTo: 'container',
type: 'spline'
},
title: {
text: 'Web browsers statistics'
},
subtitle: {
text: 'From 2008 to present'
},
xAxis: {
categories: [ 'Jan 2008', 'Feb', .... ],
tickInterval: 3
},
yAxis: {
title: {
text: 'Percentage %'
},
min: 0
},
plotOptions: {
series: {
lineWidth: 2
}
},
series: [{
name: 'Internet Explorer',
data: [54.7, 54.7, 53.9, 54.8, 54.4, ... ]
}, {
name: 'FireFox',
data: [36.4, 36.5, 37.0, 39.1, 39.8, ... ]
}, {
// Chrome started until late 2008
name: 'Chrome',
data: [ null, null, null, null, null, null,
null, null, 3.1, 3.0, 3.1, 3.6, ... ]
}, {
name: 'Safari',
data: [ 1.9, 2.0, 2.1, 2.2, 2.4, 2.6, ... ]
}, {
name: 'Opera',
data: [ 1.4, 1.4, 1.4, 1.4, 1.5, 1.7, ... ]
}]
});
});
</script>
</head>
<body>
<div>
<!-- Highcharts rendering takes place inside this DIV -->
<div id="container"></div>
</div>
</body>
</html>
通过一个包含所有属性和系列数据的对象指定符创建样条图。一旦创建 chart 对象,图表就会在浏览器中显示。在这个对象指定符中,有与图表结构相对应的主要组件:
var chart = new HighCharts.Chart({
chart: {
...
},
title: '...'
...
});
renderTo 选项指示 Highcharts 在具有 'container' ID 值的 HTML <div> 元素上显示图表,该 ID 在 HTML <body> 部分中定义。type 选项设置为默认的展示类型 'spline',适用于任何系列数据,如下所示:
chart: {
renderTo: 'container',
type: 'spline'
}
接下来是设置 title 和 subtitle,它们出现在图表顶部的中央:
title: {
text: 'Web browsers ... '
},
subtitle: {
text: 'From 2008 to present'
},
xAxis属性的categories选项包含每个数据点的 x 轴标签数组。由于图表至少有 50 个数据点,打印每个 x 轴标签将使文本重叠。旋转标签仍然会使轴看起来非常拥挤。最佳折衷方案是每打印第三个标签(tickIntervals: 3),这样标签之间的间距就很好。
为了简化,我们在xAxis.categories中使用 50 个条目来表示时间。然而,我们将在下一章看到一种更优化的、更合理的方式来显示日期和时间数据:
xAxis: {
categories: [ 'Jan 2008', 'Feb', .... ],
tickInterval: 3
},
yAxis中的选项是分配 y 轴的标题并设置可能的最小值为零;否则,Highcharts 将在 y 轴上显示负百分比范围,这对于这个数据集来说是不想要的:
yAxis: {
title: {
text: 'Percentage %'
},
min: 0
},
plotOptions属性用于控制每个系列如何显示,根据其类型(线、饼、柱状等)。plotOptions.series选项是应用于所有系列类型的通用配置,而不是在series数组内部定义每个设置。在这个例子中,每个系列的默认linewidth设置为 2 像素宽,如下所示:
plotOptions: {
series: {
lineWidth: 2
}
},
series属性是整个配置对象的核心,它定义了所有系列数据。它是一个series对象的数组。series对象可以通过多种方式指定。在这个例子中,名称是出现在图表图例和工具提示中的系列名称。数据是一个 y 轴值的数组,其长度与xAxis.categories数组相同,以形成(x,y)数据点:
series: [{
name: 'Internet Explorer',
data: [54.7, 54.7, 53.9, 54.8, 54.4, ... ]
}, {
name: 'FireFox',
data: [36.4, 36.5, 37.0, 39.1, 39.8, ... ]
}, {
提示
下载示例代码
您可以从您在www.packtpub.com的账户中下载示例代码文件,以获取您购买的所有 Packt Publishing 书籍。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。
以下截图显示了在 Safari 浏览器上最终 Highcharts 应该看起来是什么样子:

以下截图显示了在 Internet Explorer 11 浏览器上应该看起来是什么样子:

以下截图显示了在 Chrome 浏览器上应该看起来是什么样子:

以下截图显示了在 Firefox 浏览器上应该看起来是什么样子:

摘要
网络图表自 HTML 的早期以来就已经存在,从服务器端技术发展到客户端。在此期间,采用了几种解决方案来克服 HTML 的不足。现在,随着功能丰富的 HTML5 的出现,网络图表又回到了 HTML,这次它是为了永远,得益于 JavaScript。
本章提到了许多 JavaScript 图表产品。在这些产品中,Highcharts 凭借其独特的图形风格和流畅的用户交互脱颖而出。我们还通过一个简单的图表示例来体验创建我们第一个 Highcharts 图表是多么快捷和简单。
在下一章中,我们将更深入地探讨 Highcharts 配置对象,并附带更多示例。配置对象是产品的核心部分,其结构作为所有图表的共同原型。
第二章:高级图表配置
所有 Highcharts 图表共享相同的配置结构,对我们来说熟悉核心组件至关重要。然而,在本书中不可能涵盖所有配置。在本章中,我们将探索最常用的功能属性,并通过示例进行演示。我们将学习 Highcharts 如何管理布局,然后探讨如何配置坐标轴、指定单系列和多系列数据,接着查看 JavaScript 和 HTML 中格式化和样式化工具提示。之后,我们将了解如何使用各种类型的动画来润色我们的图表,并应用颜色渐变。最后,我们将探索drilldown交互功能。在本章中,我们将涵盖以下主题:
-
理解 Highcharts 布局
-
使用坐标轴框定图表
-
重新审视系列配置
-
设置工具提示样式
-
动画图表
-
使用渐变扩展颜色
-
使用
drilldown系列构建图表
配置结构
在 Highcharts 配置对象中,顶层组件代表图表的骨架结构。以下是在本章中涵盖的主要组件列表:
-
chart:这包含顶级图表属性(如布局、尺寸、事件、动画和用户交互)的配置 -
series:这是一个系列对象的数组(包含数据和特定选项),用于单系列和多系列,其中系列数据可以以多种方式指定 -
xAxis/yAxis/zAxis:这包含所有轴属性(如标签、样式、范围、间隔、绘图线、绘图带和背景)的配置 -
tooltip:这包含系列数据工具提示的布局和格式样式配置 -
drilldown:这包含用于钻取系列及其与主系列关联的 ID 字段的配置 -
title/subtitle:这包含图表标题和副标题的布局和样式配置 -
legend:这包含图表图例的布局和格式样式配置 -
plotOptions:这包含所有绘图选项,例如显示、动画和用户交互,适用于常见系列和特定系列类型 -
exporting:这包含控制打印和导出功能的布局和功能的配置
有关所有配置的参考信息,请访问api.highcharts.com。
理解 Highcharts 的布局
在我们开始学习 Highcharts 布局如何工作之前,首先理解一些基本概念是至关重要的。为了做到这一点,让我们首先回顾第一章中使用的图表示例 Web Charts,并设置一些可见的边框。首先,设置绘图区域的边框。为此,我们可以在chart部分设置plotBorderWidth和plotBorderColor的选项,如下所示:
chart: {
renderTo: 'container',
type: 'spline',
plotBorderWidth: 1,
plotBorderColor: '#3F4044'
},
第二个边框被设置在 Highcharts 容器周围。接下来,我们通过添加额外的设置扩展前面的 chart 部分:
chart: {
renderTo: 'container',
....
borderColor: '#a1a1a1',
borderWidth: 2,
borderRadius: 3
},
这将容器边框颜色设置为 2 像素的宽度,并具有 3 像素的圆角。
如我们所见,容器周围有一个边框,这是 Highcharts 显示不能超过的边界:

默认情况下,Highcharts 显示有三个不同的区域:空间、标签和绘图区域。绘图区域是包含所有绘图图形的内矩形区域。标签区域是标题、副标题、坐标轴标题、图例和版权信息等标签所在区域,位于绘图区域周围,因此它位于绘图区域边缘和空间区域内边缘之间。空间区域是容器边框和标签区域外边缘之间的区域。
以下截图显示了三种不同类型的区域。插入了一条灰色虚线来表示空间和标签区域之间的边界。

每个图表标签位置可以在以下两种布局之一中进行操作:
-
自动布局:Highcharts 会根据标签区域中标签的位置自动调整绘图区域的大小,因此绘图区域根本不会与标签元素重叠。自动布局是配置的最简单方式,但控制较少。这是定位图表元素的默认方式。
-
固定布局:没有标签区域的概念。图表标签在固定位置指定,因此它在绘图区域上具有浮动效果。换句话说,绘图区域边不会自动调整以适应相邻的标签位置。这使用户能够完全控制图表的显示方式。
空间区域控制了 Highcharts 在每一边的显示偏移。只要图表边距没有定义,增加或减少空间区域会对自动和固定布局中的绘图区域测量产生全局影响。
图表边距和间距设置
在本节中,我们将了解图表边距和间距设置如何影响整体布局。图表边距可以通过 margin、marginTop、marginLeft、marginRight 和 marginBottom 属性进行配置,并且默认情况下它们是禁用的。设置图表边距会对绘图区域产生全局影响,因此没有任何标签位置或图表间距配置可以影响绘图区域的大小。因此,所有图表元素相对于绘图区域都处于固定布局模式。margin 选项是一个包含四个边距值的数组,每个方向都覆盖了一个值,与 CSS 中的相同,从北开始,按顺时针方向进行。此外,margin 选项的优先级低于任何方向性的 margin 选项,无论它们在 chart 部分中的顺序如何。
间距配置默认启用,每侧有一个固定值。这些可以在 chart 部分通过属性名称 spacing、spacingTop、spacingLeft、spacingBottom 和 spacingRight 进行配置。
在本例中,我们将增加或减少图表每侧的 margin 或 spacing 属性,并观察其效果。以下是一些图表设置:
chart: {
renderTo: 'container',
type: ...
marginTop: 10,
marginRight: 0,
spacingLeft: 30,
spacingBottom: 0
},
以下截图显示了图表的外观:

marginTop 属性将图表区域的顶部边界固定在容器边界的 10 像素处。它还将任何标签元素的顶部边界更改为固定布局,因此图表标题和副标题浮在图表区域上方。spacingLeft 属性增加左侧的间距区域,因此将 y 轴标题推得更远。由于它是自动布局(未声明 marginLeft),它还将图表区域的西边界推入。将 marginRight 设置为 0 将覆盖图表右侧的所有默认间距并将其更改为固定布局模式。最后,将 spacingBottom 设置为 0 使图例接触容器的底部条,因此它也将图表区域向下拉伸。这是因为即使 spacingBottom 设置为 0,底部边缘仍然是自动布局。
图表标签属性
图表标签如 xAxis.title、yAxis.title、legend、title、subtitle 和 credits 具有共同的属性名称,如下所示:
-
align: 这用于标签的水平对齐。可能的关键字是'left','center', 和'right'。至于坐标轴标题,它是'low','middle', 和'high'。 -
floating: 这是为了在图表区域给标签位置一个浮动效果。将此设置为true将导致标签位置对相邻图表区域的边界没有影响。 -
margin: 这是标签与相邻图表区域边界的边距设置。只有某些标签类型有此设置。 -
verticalAlign: 这用于标签的垂直对齐。关键字有'top','middle', 和'bottom'。 -
x: 这用于相对于对齐的横向定位。 -
y: 这用于相对于对齐的纵向定位。
至于标签的 x 和 y 定位,它们不用于图表内的绝对定位。它们是为标签对齐进行精细调整而设计的。以下图表显示了坐标方向,其中中心代表标签位置:

我们可以通过一个简单的align和y位置设置的示例来实验这些属性,通过将标题和副标题并排放置。标题通过将align设置为'left'向左移动,而副标题的对齐设置为'right'。为了使两个标题出现在同一行,我们将副标题的y位置更改为15,这与标题的默认y值相同:
title: {
text: 'Web browsers ...',
align: 'left'
},
subtitle: {
text: 'From 2008 to present',
align: 'right',
y: 15
},
以下是一个截图,显示了两个标题在同一行对齐:

在以下子节中,我们将实验每个标签元素的对齐变化如何影响绘图区域的布局行为。
标题和副标题对齐
标题和副标题具有相同的布局属性,唯一的区别是默认值和标题有margin设置。指定任何值的verticalAlign都会从默认的自动布局更改为固定布局(它内部将floating切换为true)。然而,手动将副标题的floating属性设置为false并不会切换回自动布局。以下是一个自动布局中的title和固定布局中的subtitle的示例:
title: {
text: 'Web browsers statistics'
},
subtitle: {
text: 'From 2008 to present',
verticalAlign: 'top',
y: 60
},
副标题的verticalAlign属性设置为'top',这会将布局切换到固定布局,并将y偏移增加到60。y偏移将副标题的位置进一步向下推。由于绘图区域不再与副标题处于自动布局关系,绘图区域的上边框会高于副标题。然而,绘图区域仍然与标题处于自动布局关系,因此标题仍然位于绘图区域上方:

图例对齐
图例对于verticalAlign和align属性有不同的行为。除了将对齐设置为'center'外,verticalAlign和align中的所有其他设置都保持自动定位。以下是一个图例位于图表右侧的示例。verticalAlign属性被切换到图表的中间,其中水平align被设置为'right':
legend: {
align: 'right',
verticalAlign: 'middle',
layout: 'vertical'
},
将layout属性设置为'vertical',这样会导致图例框内的项目以垂直方式显示。正如我们所见,绘图区域会自动调整大小以适应图例框:

注意,在新版本中,图例框周围的边框装饰已被禁用。要显示图例框周围的圆角边框,我们可以使用以下方式添加borderWidth和borderRadius选项:
legend: {
align: 'right',
verticalAlign: 'middle',
layout: 'vertical',
borderWidth: 1,
borderRadius: 3
},
这里是具有圆角边框的图例框:

轴标题对齐
轴标题不使用 verticalAlign。相反,它们使用 align 设置,可以是 'low'、'middle' 或 'high'。标题的 margin 值是轴标题与轴线之间的距离。以下是一个示例,展示了 y 轴标题水平旋转(而不是默认的垂直旋转)并显示在轴线上方而不是旁边的情况。我们还使用 y 属性来微调标题位置:
yAxis: {
title: {
text: 'Percentage %',
rotation: 0,
y: -15,
margin: -70,
align: 'high'
},
min: 0
},
以下是一个图表右上角的截图,显示标题在 y 轴顶部水平对齐。或者,我们可以使用 offset 选项代替 margin 来达到相同的效果。

信用对齐
信用与其他标签元素略有不同。它只支持 align、verticalAlign、x 和 y 属性在 credits.position 属性中(credits: { position: … } 的缩写),并且也不受任何间距设置的影响。假设我们有一个没有图例的图表,并且我们必须将信用移动到图表的左下角区域,以下代码片段显示了如何操作:
legend: {
enabled: false
},
credits: {
position: {
align: 'left'
},
text: 'Joe Kuan',
href: 'http://joekuan.wordpress.com'
},
然而,信用文本超出了图表的边缘,如下面的截图所示:

即使我们将 credits 标签通过 x 定位移动到右侧,标签仍然与 x 轴间隔标签有点太近。我们可以引入额外的 spacingBottom 在两个标签之间留出间隙,如下所示:
chart: {
spacingBottom: 30,
....
},
credits: {
position: {
align: 'left',
x: 20,
y: -7
},
},
....
以下是一个截图,显示了经过最终调整的信用:

尝试自动布局
在本节中,我们将更详细地检查自动布局功能。为了简化示例,我们将从只有图表标题且没有任何图表间距设置开始:
chart: {
renderTo: 'container',
// border and plotBorder settings
borderWidth: 2,
.....
},
title: {
text: 'Web browsers statistics,
},
从前面的示例中,图表标题应出现在容器和绘图区域边框之间,如预期所示:

标题与容器顶部边框之间的空间具有间距区域(默认高度为 10 像素)的默认设置 spacingTop。标题与绘图区域顶部边框之间的间隙是 title.margin 的默认设置,高度为 15 像素。
通过在 chart 部分设置 spacingTop 为 0,图表标题向上移动到容器顶部边框旁边。因此,绘图区域的大小自动向上扩展,如下所示:

然后,我们将 title.margin 设置为 0;绘图区域边框向上移动,因此绘图区域的高度进一步增加,如下所示:

如您可能注意到的,顶部边框和图表标题之间仍然有少量像素的间隙。这实际上是由于标题的 y 位置设置的默认值,为 15 像素,足够大,可以容纳默认标题字体大小。
以下是将容器和绘图区域之间的所有空间设置为 0 的图表配置:
chart: {
renderTo: 'container',
// border and plotBorder settings
.....
spacingTop: 0
},
title: {
text: null,
margin: 0,
y: 0
}
如果我们将 title.y 设置为 0,绘图区域顶部边缘和顶部容器边缘之间的所有间隙都会关闭。以下是在图表左上角的最终截图,以显示效果。图表标题不再可见,因为它已经被移到了容器上方:

有趣的是,如果我们反向工作到第一个示例,绘图区域顶部和容器顶部之间的默认距离计算如下:
spacingTop + title.margin + title.y = 10 + 15 + 15 = 40
因此,更改这三个变量中的任何一个都会自动调整从顶部容器栏开始的绘图区域。这些偏移变量在自动布局中实际上都有其自己的目的。间距是容器和图表内容之间的间隙;因此,如果我们想在网页上与其他元素间隔显示图表,应使用间距元素。同样,如果我们想为标签元素使用特定的字体大小,我们应该考虑调整 y 偏移。因此,标签仍然保持一定的距离,不会干扰图表中的其他组件。
尝试固定布局
在上一节中,我们学习了绘图区域如何动态调整自身。在本节中,我们将看到如何手动定位图表标签。首先,我们将从 尝试自动布局 部分的开头示例代码开始,将图表标题的 verticalAlign 设置为 'bottom',如下所示:
chart: {
renderTo: 'container',
// border and plotBorder settings .....
},
title: {
text: 'Web browsers statistics',
verticalAlign: 'bottom'
},
图表标题被移动到图表的底部,紧邻容器的下边框。请注意,此设置已将标题更改为浮动模式;更重要的是,图例仍然保持在绘图区域的默认自动布局中:

请注意,我们尚未指定 spacingBottom,当应用于图表时,它的高度默认值为 15 像素。这意味着标题和容器底部边框之间应该有一个间隙,但并未显示。这是因为 title.y 位置相对于间距的默认值为 15 像素。根据 图表标签属性 部分的图示,这个正 y 值将标题推向底部边框;这补偿了由 spacingBottom 创建的空间。
让我们这次对 y 偏移位置进行更大的更改,以显示 verticalAlign 是浮在绘图区域之上的:
title: {
text: 'Web browsers statistics',
verticalAlign: 'bottom',
y: -90
},
负 y 值将标题向上移动,如下所示:

现在标题与绘图区域重叠。为了证明图例在绘图区域方面仍然是自动布局,这里我们更改了图例的 y 位置和 margin 设置,即轴标签的距离:
legend: {
margin: 70,
y: -10
},
这已经推高了绘图区域的底部。然而,图表标题仍然保持固定布局,并且在应用新的图例设置后,其位置在图表内没有任何改变,如下面的截图所示:

到目前为止,我们应该对如何定位标签元素及其与绘图区域相关的布局策略有了更好的理解。
使用轴来框定图表
在本节中,我们将从功能区域的角度探讨 Highcharts 中轴的配置。我们将从一个简单的折线图开始,逐渐向图表应用更多选项以展示其效果。
访问轴数据类型
指定图表数据的方式有两种:分类和系列数据。为了显示具有特定名称的区间,我们应该使用 categories 字段,它期望一个字符串数组。分类数组中的每个条目都与系列数据数组相关联。或者,轴区间值嵌入在系列数据数组内部。然后,Highcharts 提取两个轴的系列数据,解释数据类型,并适当地格式化和标记值。
以下是一个简单的示例,展示了分类的使用:
chart: {
renderTo: 'container',
height: 250,
spacingRight: 20
},
title: {
text: 'Market Data: Nasdaq 100'
},
subtitle: {
text: 'May 11, 2012'
},
xAxis: {
categories: [ '9:30 am', '10:00 am', '10:30 am',
'11:00 am', '11:30 am', '12:00 pm',
'12:30 pm', '1:00 pm', '1:30 pm',
'2:00 pm', '2:30 pm', '3:00 pm',
'3:30 pm', '4:00 pm' ],
labels: {
step: 3
}
},
yAxis: {
title: {
text: null
}
},
legend: {
enabled: false
},
credits: {
enabled: false
},
series: [{
name: 'Nasdaq',
color: '#4572A7',
data: [ 2606.01, 2622.08, 2636.03, 2637.78, 2639.15,
2637.09, 2633.38, 2632.23, 2632.33, 2632.59,
2630.34, 2626.89, 2624.59, 2615.98 ]
}]
前面的代码片段生成的图表如下截图所示:

分类字段中的第一个名称对应于数据数组中的第一个值,9:30 am,2606.01,等等。
或者,我们可以在系列数据内部指定时间值,并使用 x 轴的 type 属性来格式化时间。type 属性支持 'linear'(默认值)、'logarithmic' 或 'datetime'。'datetime' 设置会自动将系列数据中的时间解释为可读形式。此外,我们可以使用 dateTimeLabelFormats 属性来预定义时间单位的自定义格式。该选项还可以接受多个时间单位格式。这是当我们事先不知道系列数据中的时间跨度有多长时的情况,因此结果图中的每个单位可以是每小时、每天等等。以下示例展示了如何使用预定义的小时和分钟格式指定图表。格式字符串的语法基于 PHP 的 strftime 函数:
xAxis: {
type: 'datetime',
// Format 24 hour time to AM/PM
dateTimeLabelFormats: {
hour: '%I:%M %P',
minute: '%I %M'
}
},
series: [{
name: 'Nasdaq',
color: '#4572A7',
data: [ [ Date.UTC(2012, 4, 11, 9, 30), 2606.01 ],
[ Date.UTC(2012, 4, 11, 10), 2622.08 ],
[ Date.UTC(2012, 4, 11, 10, 30), 2636.03 ],
.....
]
}]
注意,x 轴使用的是 12 小时制时间格式,如下面的截图所示:

相反,我们可以定义 xAxis.labels.formatter 属性的格式处理程序以实现类似的效果。Highcharts 提供了一个实用程序,Highcharts.dateFormat,它将毫秒时间戳转换为可读格式。在以下代码片段中,我们使用 dateFormat 和 this.value 定义了 formatter 函数。关键字 this 是轴的间隔对象,而 this.value 是间隔实例的 UTC 时间值:
xAxis: {
type: 'datetime',
labels: {
formatter: function() {
return Highcharts.dateFormat('%I:%M %P', this.value);
}
}
},
由于我们的数据点的时值是固定间隔的,它们也可以以缩略版的形式排列。我们只需要定义时间点的起始点 pointStart 和它们之间的常规间隔 pointInterval(以毫秒为单位):
series: [{
name: 'Nasdaq',
color: '#4572A7',
pointStart: Date.UTC(2012, 4, 11, 9, 30),
pointInterval: 30 * 60 * 1000,
data: [ 2606.01, 2622.08, 2636.03, 2637.78,
2639.15, 2637.09, 2633.38, 2632.23,
2632.33, 2632.59, 2630.34, 2626.89,
2624.59, 2615.98 ]
}]
调整间隔和背景
在上一节中,我们学习了如何使用轴类别和系列数据数组。在本节中,我们将看到如何格式化间隔线和背景样式,以产生更清晰的图表。
我们将继续之前的示例。首先,让我们在 y 轴上创建一些间隔线。在图表中,间隔自动设置为 20。然而,将间隔线的数量加倍会更清晰。为此,只需将 tickInterval 值分配为 10。然后,我们使用 minorTickInterval 在间隔之间放置另一条线以指示半间隔。为了区分间隔线和半间隔线,我们将半间隔线 minorGridLineDashStyle 设置为虚线和点划线样式。
注意
Highcharts 中有近十种线型设置可用,从 'Solid' 到 'LongDashDotDot'。读者可以参考在线手册以获取可能的值。
以下是为创建新设置的第一步:
yAxis: {
title: {
text: null
},
tickInterval: 10,
minorTickInterval: 5,
minorGridLineColor: '#ADADAD',
minorGridLineDashStyle: 'dashdot'
}
间隔线应该看起来像下面的截图:

为了使图表看起来更加美观,我们使用 alternateGridColor 添加了带有阴影的条纹效果。然后,我们将间隔线颜色 gridLineColor 改变为与条纹相似的范围。以下代码片段被添加到 yAxis 配置中:
gridLineColor: '#8AB8E6',
alternateGridColor: {
linearGradient: {
x1: 0, y1: 1,
x2: 1, y2: 1
},
stops: [ [0, '#FAFCFF' ],
[0.5, '#F5FAFF'] ,
[0.8, '#E0F0FF'] ,
[1, '#D6EBFF'] ]
}
我们将在本章后面讨论颜色渐变。以下是有新阴影背景的图表:

下一步是给 y 轴线应用更专业的样式。我们将使用 lineWidth 属性在 y 轴上画线,并使用以下代码片段在间隔线上添加一些测量标记:
lineWidth: 2,
lineColor: '#92A8CD',
tickWidth: 3,
tickLength: 6,
tickColor: '#92A8CD',
minorTickLength: 3,
minorTickWidth: 1,
minorTickColor: '#D8D8D8'
tickWidth 和 tickLength 属性在每条间隔线的起始处添加了小标记的效果。我们在间隔标记和坐标线上都应用了相同的颜色。然后,我们将 minorTickLength 和 minorTickWidth 添加到半间隔线中,尺寸更小。这样就在坐标线上产生了一个很好的测量标记效果,如下面的截图所示:

现在,我们对xAxis配置应用类似的润色,如下:
xAxis: {
type: 'datetime',
labels: {
formatter: function() {
return Highcharts.dateFormat('%I:%M %P', this.value);
},
},
gridLineDashStyle: 'dot',
gridLineWidth: 1,
tickInterval: 60 * 60 * 1000,
lineWidth: 2,
lineColor: '#92A8CD',
tickWidth: 3,
tickLength: 6,
tickColor: '#92A8CD',
},
我们将x轴的间隔线设置为小时格式,并将线型切换为虚线。然后,我们应用与y轴相同的颜色、粗细和间隔刻度。以下是为截图:

然而,在x轴线上存在一些缺陷。首先,x轴线和y轴线的交汇点没有正确对齐。其次,x轴上的间隔标签与间隔刻度相接触。最后,第一个数据点的一部分被 y 轴线覆盖。以下是一个放大后的截图,显示了这些问题:

解决坐标轴线对齐问题的有两种方法,如下:
-
将绘图区域向 x 轴方向移动 1 像素。这可以通过将
xAxis的offset属性设置为1来实现。 -
将x轴线的宽度增加到 3 像素,与y轴的刻度间隔宽度相同。
至于x轴标签,我们可以通过将y偏移值引入labels设置来简单地解决这个问题。
最后,为了避免第一个数据点接触 y 轴线,我们可以在x轴上施加minPadding。这样做是在轴的最小值处添加填充空间,即第一个点。minPadding值基于图表宽度的比例。在这种情况下,将属性设置为0.02相当于将x轴向右移动 5 像素(250 px * 0.02)。以下是为改善图表的附加设置:
xAxis: {
....
labels: {
formatter: ...,
y: 17
},
.....
minPadding: 0.02,
offset: 1
}
以下截图显示问题已得到解决:

如我们所见,Highcharts 提供了一套全面的、具有高度灵活性的可配置变量。
使用折线和折带
在本节中,我们将看到如何使用 Highcharts 在轴上放置线条或带状区域。我们将继续使用上一节中的示例。让我们画几条线来指示 y 轴上一天的最高和最低指数点。plotLines字段接受一个对象配置数组,用于每个折线。plotLines没有宽度和颜色默认值,因此我们需要明确指定它们才能看到线条。以下是为折线提供的代码片段:
yAxis: {
... ,
plotLines: [{
value: 2606.01,
width: 2,
color: '#821740',
label: {
text: 'Lowest: 2606.01',
style: {
color: '#898989'
}
}
}, {
value: 2639.15,
width: 2,
color: '#4A9338',
label: {
text: 'Highest: 2639.15',
style: {
color: '#898989'
}
}
}]
}
以下截图显示了它应该看起来像什么:

我们可以稍微改善图表的外观。首先,顶部折线的文本标签不应紧挨着最高点。其次,底部线的标签应被系列线和间隔线远程覆盖,如下:

为了解决这些问题,我们可以将绘图线的 zIndex 赋值为 1,这将文本标签置于区间线之上。我们还设置了标签的 x 位置以将文本移至点旁边。以下是新更改:
plotLines: [{
... ,
label: {
... ,
x: 25
},
zIndex: 1
}, {
... ,
label: {
... ,
x: 130
},
zIndex: 1
}]
以下图表显示标签已被从绘图线移开,并移至区间线上:

现在,我们将更改前面的示例,使用绘图带区域显示市场开盘价和收盘价之间的指数变化。绘图带配置与绘图线非常相似,除了它使用 to 和 from 属性,并且 color 属性接受渐变设置或颜色代码。我们创建了一个带有三角形文本符号和值的绘图带,以表示正收盘价。我们不是使用 x 和 y 属性来微调标签位置,而是使用 align 选项将文本调整到绘图区域的中心(替换上述示例中的 plotLines 设置):
plotBands: [{
from: 2606.01,
to: 2615.98,
label: {
text: '▲ 9.97 (0.38%)',
align: 'center',
style: {
color: '#007A3D'
}
},
zIndex: 1,
color: {
linearGradient: {
x1: 0, y1: 1,
x2: 1, y2: 1
},
stops: [ [0, '#EBFAEB' ],
[0.5, '#C2F0C2'] ,
[0.8, '#ADEBAD'] ,
[1, '#99E699']
]
}
}]
注意
三角形是一个 alt-code 字符;按住左 Alt 键,在数字键盘上输入 30。有关更多详细信息,请参阅 www.alt-codes.net。
这产生了一个图表,其中绿色绘图带突出显示了市场中的正收盘价,如下面的截图所示:

扩展到多个轴
之前,我们跑过了大多数的轴配置。在这里,我们探讨如何使用多个轴,这些轴只是一个包含轴配置的对象数组。
继续上一个股票市场示例,假设我们现在想包括另一个市场指数,道琼斯,以及纳斯达克。然而,这两个指数在性质上不同,所以它们的值范围差异很大。首先,让我们通过显示具有共同 y 轴的两个指数来检查结果。我们更改标题,移除 y 轴上的固定间隔设置,并包含另一个系列的数据:
chart: ... ,
title: {
text: 'Market Data: Nasdaq & Dow Jones'
},
subtitle: ... ,
xAxis: ... ,
credits: ... ,
yAxis: {
title: {
text: null
},
minorGridLineColor: '#D8D8D8',
minorGridLineDashStyle: 'dashdot',
gridLineColor: '#8AB8E6',
alternateGridColor: {
linearGradient: {
x1: 0, y1: 1,
x2: 1, y2: 1
},
stops: [ [0, '#FAFCFF' ],
[0.5, '#F5FAFF'] ,
[0.8, '#E0F0FF'] ,
[1, '#D6EBFF'] ]
},
lineWidth: 2,
lineColor: '#92A8CD',
tickWidth: 3,
tickLength: 6,
tickColor: '#92A8CD',
minorTickLength: 3,
minorTickWidth: 1,
minorTickColor: '#D8D8D8'
},
series: [{
name: 'Nasdaq',
color: '#4572A7',
data: [ [ Date.UTC(2012, 4, 11, 9, 30), 2606.01 ],
[ Date.UTC(2012, 4, 11, 10), 2622.08 ],
[ Date.UTC(2012, 4, 11, 10, 30), 2636.03 ],
...
]
}, {
name: 'Dow Jones',
color: '#AA4643',
data: [ [ Date.UTC(2012, 4, 11, 9, 30), 12598.32 ],
[ Date.UTC(2012, 4, 11, 10), 12538.61 ],
[ Date.UTC(2012, 4, 11, 10, 30), 12549.89 ],
...
]
}]
以下图表显示了两个市场指数:

如预期的那样,指数在一天中发生的变化已经通过价值的大幅差异进行了标准化。两条线看起来大致是直的,这错误地暗示指数几乎没有变化。
现在我们来探讨将两个指数分别放在单独的 y 轴上。我们应该移除 y 轴上的任何背景装饰,因为我们现在在同一背景上共享不同的数据范围。
以下是对 yAxis 的新设置:
yAxis: [{
title: {
text: 'Nasdaq'
},
}, {
title: {
text: 'Dow Jones'
},
opposite: true
}],
现在 yAxis 是一个轴配置数组。数组中的第一个条目是纳斯达克,第二个是道琼斯。这次,我们显示轴标题以区分它们。opposite 属性是将道琼斯 y 轴放置在图表的另一侧以提高清晰度。否则,两个 y 轴都出现在左侧。
下一步是将 y 轴数组中的索引与系列数据数组对齐,如下所示:
series: [{
name: 'Nasdaq',
color: '#4572A7',
yAxis: 0,
data: [ ... ]
}, {
name: 'Dow Jones',
color: '#AA4643',
yAxis: 1,
data: [ ... ]
}]
我们可以清楚地看到新图表中指数的移动,如下所示:

此外,我们可以通过将系列与坐标轴线条进行颜色匹配来改进最终视图。Highcharts.getOptions().colors属性包含系列默认颜色列表,因此我们使用前两个条目作为我们的索引。另一个改进是设置x轴的maxPadding,因为新的 y 轴线条覆盖了x轴高端部分的数据点:
xAxis: {
... ,
minPadding: 0.02,
maxPadding: 0.02
},
yAxis: [{
title: {
text: 'Nasdaq'
},
lineWidth: 2,
lineColor: '#4572A7',
tickWidth: 3,
tickLength: 6,
tickColor: '#4572A7'
}, {
title: {
text: 'Dow Jones'
},
opposite: true,
lineWidth: 2,
lineColor: '#AA4643',
tickWidth: 3,
tickLength: 6,
tickColor: '#AA4643'
}],
下面的截图显示了图表改进后的外观:

我们可以通过向yAxis和series数组中添加条目来扩展前面的示例,并拥有超过两个轴,同时将两者映射在一起。下面的截图显示了一个 4 轴折线图:

重新审视系列配置
到目前为止,我们应该已经了解了series属性的作用。在本节中,我们将更详细地探讨它。
series属性是包含数据-系列特定选项的系列配置对象数组。它允许我们指定单系列数据和多系列数据。系列对象的目的就是通知 Highcharts 数据的格式以及数据在图表中的呈现方式。
图表中的所有数据值都是通过data字段指定的。data字段非常灵活,可以采用多种形式的数组,如下所示:
-
数值
-
一个包含x和y值的数组
-
一个具有描述数据点属性的点对象
前两个选项已在访问轴数据类型部分中进行了探讨。在本节中,我们将探讨第三个选项。让我们使用单系列纳斯达克示例,并通过数值和对象的混合来指定系列数据:
series: [{
name: 'Nasdaq',
pointStart: Date.UTC(2012, 4, 11, 9, 30),
pointInterval: 30 * 60 * 1000,
data: [{
// First data point
y: 2606.01,
marker: {
symbol: 'url(./sun.png)'
}
}, 2622.08, 2636.03, 2637.78,
{
// Highest data point
y: 2639.15,
dataLabels: {
enabled: true
},
marker: {
fillColor: '#33CC33',
radius: 5
}
}, 2637.09, 2633.38, 2632.23, 2632.33,
2632.59, 2630.34, 2626.89, 2624.59,
{
// Last data point
y: 2615.98,
marker: {
symbol: 'url(./moon.png)'
}
}]
}]
第一个和最后一个数据点是具有y轴值和图像文件的对象,用于表示市场的开盘和收盘。最高数据点配置了不同的颜色和数据标签。数据点的大小也设置为略大于默认值。其余的数据数组只是数值,如下面的截图所示:

探索 PlotOptions
plotOptions对象是 Highcharts 中支持的所有系列类型配置对象的包装对象。这些配置具有如plotOptions.line.lineWidth这样的属性,这些属性与其他系列类型通用,以及如plotOptions.pie.center这样的配置,它仅针对饼图系列类型。在具体的系列中,有plotOptions.series,它用于整个系列共享的通用绘图选项。
前面的 plotOptions 对象可以在 plotOptions.series、plotOptions.{series-type} 和系列配置之间形成一个优先级链。例如,series[x].shadow(其中 series[x].type 是 'pie')的优先级高于 plotOptions.pie.shadow,而 plotOptions.pie.shadow 的优先级又高于 plotOptions.series.shadow。
这样做的目的是图表由多个不同的系列类型组成。例如,在一个包含多个柱状系列和单个线条系列的图表中,柱状图和线条的共同属性可以在 plotOptions.series.* 中定义,而 plotOptions.column 和 plotOptions.line 则持有它们自己的特定属性值。此外,plotOptions.{series-type}.* 中的属性可以被系列数组中指定的相同系列类型进一步覆盖。
下面的表格是配置优先级的参考。高级别的配置优先级较低,这意味着链中较低级别的配置可以覆盖链中较高级别的配置。对于系列数组,如果 series[x].type 或默认系列类型值与 plotOptions 中的系列类型相同,则优先级有效:
chart.type
series[x].type
plotOptions.series.{seriesProperty}
plotOptions.{series-type}.{seriesProperty}
series[x].{seriesProperty}
plotOptions.points.events.*
series[x].data[y].events.*
plotOptions.series.marker.*
series[x].data[y].marker.*
plotOptions 对象包含控制图表中系列类型呈现方式的属性——例如倒置图表、系列颜色、堆叠柱状图、用户与系列的交互等。当我们研究每种图表类型时,所有这些选项都将被详细说明。同时,我们将通过一个月度纳斯达克图来探索 plotOptions 的概念。该图有五种不同的系列数据类型:开盘价、收盘价、最高价、最低价和成交量。通常,这些数据用于绘制每日股票图表(OHLCV)。我们将它们压缩到单个图表中,目的是为了演示 plotOptions。

下面的代码是生成前面图表的图表配置:
chart: {
renderTo: 'container',
height: 250,
spacingRight: 30
},
title: {
text: 'Market Data: Nasdaq 100'
},
subtitle: {
text: '2011 - 2012'
},
xAxis: {
categories: [ 'Jan', 'Feb', 'Mar', 'Apr',
'May', 'Jun', 'Jul', 'Aug',
'Sep', 'Oct', 'Nov', 'Dec' ],
labels: {
y: 17
},
gridLineDashStyle: 'dot',
gridLineWidth: 1,
lineWidth: 2,
lineColor: '#92A8CD',
tickWidth: 3,
tickLength: 6,
tickColor: '#92A8CD',
minPadding: 0.04,
offset: 1
},
yAxis: [{
title: {
text: 'Nasdaq index'
},
min: 2000,
minorGridLineColor: '#D8D8D8',
minorGridLineDashStyle: 'dashdot',
gridLineColor: '#8AB8E6',
alternateGridColor: {
linearGradient: {
x1: 0, y1: 1,
x2: 1, y2: 1
},
stops: [ [0, '#FAFCFF' ],
[0.5, '#F5FAFF'] ,
[0.8, '#E0F0FF'] ,
[1, '#D6EBFF'] ]
},
lineWidth: 2,
lineColor: '#92A8CD',
tickWidth: 3,
tickLength: 6,
tickColor: '#92A8CD'
}, {
title: {
text: 'Volume'
},
lineWidth: 2,
lineColor: '#3D96AE',
tickWidth: 3,
tickLength: 6,
tickColor: '#3D96AE',
opposite: true
}],
credits: {
enabled: false
},
plotOptions: {
column: {
stacking: 'normal'
},
line: {
zIndex: 2,
marker: {
radius: 3,
lineColor: '#D9D9D9',
lineWidth: 1
},
dashStyle: 'ShortDot'
}
},
series: [{
name: 'Monthly High',
// Use stacking column chart - values on
// top of monthly low to simulate monthly
// high
data: [ 98.31, 118.08, 142.55, 160.68, ... ],
type: 'column',
color: '#4572A7'
}, {
name: 'Monthly Low',
data: [ 2237.73, 2285.44, 2217.43, ... ],
type: 'column',
color: '#AA4643'
}, {
name: 'Open (Start of the month)',
data: [ 2238.66, 2298.37, 2359.78, ... ],
color: '#89A54E'
}, {
name: 'Close (End of the month)',
data: [ 2336.04, 2350.99, 2338.99, ... ],
color: '#80699B'
}, {
name: 'Volume',
data: [ 1630203800, 1944674700, 2121923300, ... ],
yAxis: 1,
type: 'column',
stacking: null,
color: '#3D96AE'
}]
}
虽然图表看起来稍微复杂一些,但我们将逐步分析代码。首先,在yAxis数组中有两个条目:第一个是为纳斯达克指数;第二个 y 轴,显示在右侧(opposite: true),是用于成交量。在系列数组中,第一个和第二个系列被指定为柱状图系列类型(type: 'column'),这覆盖了默认的系列类型'line'。然后,在plotOptions.column中定义了stacking选项为'normal',这将在月度最高价上堆叠月度最低价柱(深蓝色和黑色柱)。严格来说,堆叠柱状图用于显示属于同一类别的数据比率。为了演示plotOptions,我们使用了堆叠柱状图来显示月度交易的上下限。为此,我们取月度最高价和最低价的差值,并将这些差值替换回月度最高价系列。因此,在代码中,我们可以看到月度最高价系列中的数据值要小得多。
第三个和第四个系列是市场开盘和收盘指数。两者都采用默认的线系列类型,并继承自plotOptions.line中定义的选项。zIndex选项被分配为2,以将两个线系列叠加在第五个成交量系列之上;否则,两条线都将被成交量柱覆盖。marker对象配置是为了减少默认的数据点大小,因为整个图表已经通过柱状图和线条压缩。
最后一个柱状图系列是成交量,系列中的stacking选项被手动设置为null,这覆盖了从plotOptions.column继承的选项。这将系列重置为非堆叠选项,显示为单独的柱状图。最后,将yAxis索引选项设置为与成交量系列的 y 轴对齐(yAxis: 1)。
提示框样式
Highcharts 中的提示框是通过tooltip.enabled布尔选项启用的,默认值为true。在 Highcharts 4 中,提示框框的默认形状已更改为 callout。以下展示了新的提示框样式:

对于旧式的提示框形状,我们可以将tooltip.shape选项设置为square,我们将在下面的练习中使用它。
提示框的内容格式是灵活的,可以通过回调处理程序或 HTML 样式来定义。我们将从上一节中的示例继续。由于图表中包含多行和多列,我们可以首先启用十字准线提示框来帮助我们将数据点对齐到轴上。crosshairs配置可以接受一个布尔值来激活该功能,或者是一个对象样式来定义十字准线线的样式。以下是一个代码片段,用于设置带有灰色彩色和虚线样式的 x 轴和 y 轴配置数组的十字准线:
tooltip : {
shape: 'square',
crosshairs: [{
color: '#5D5D5D',
dashStyle: 'dash',
width: 2
}, {
color: '#5D5D5D',
dashStyle: 'dash',
width: 2
}]
},
注意
再次,dashStyle 选项使用与 Highcharts 中相同的常用线型值。请参阅十字线参考手册以获取所有可能的值。
以下截图显示了在市场收盘系列中悬停数据点时的视图。我们看到指针旁边出现了一个工具提示框,以及两个轴的灰色十字线:

在 HTML 中格式化工具提示
Highcharts 提供了如 headerFormat、pointFormat 和 footerFormat 等模板选项,通过特定的模板变量(或宏)来构建工具提示。这些特定的变量是系列和点,我们可以在模板中使用它们的属性,例如 point.x、point.y、series.name 和 series.color。例如,默认的工具提示设置使用 pointFormat,其默认值为以下代码片段:
<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b><br/>
Highcharts 会将前面的表达式内部转换为 SVG 文本标记,因此只能支持 HTML 语法的一个子集,即 <b>、<br>、<strong>、<em>、<i>、<span>、<href> 以及 CSS 中的字体样式属性。然而,如果我们想要在润色内容方面有更大的灵活性,并且能够包含图像文件,我们需要使用 useHTML 选项来实现完整的 HTML 工具提示。此选项允许我们执行以下操作:
-
在工具提示中使用其他 HTML 标签,如
<img> -
在实际的 HTML 内容中创建工具提示,使其位于 SVG 标记之外
在这里,我们可以在工具提示内格式化一个 HTML 表格。我们将使用 headerFormat 创建一个用于类别的表头列,并使用底部边框将表头与数据分开。然后,我们将使用 pointFormat 设置一个图标图像以及系列名称和数据。图像文件基于 series.index 宏,因此不同的系列有不同的图像图标。我们使用 series.color 宏以与图表中相同的颜色突出显示系列名称,并使用 series.data 宏来应用系列值:
tooltip : {
useHTML: true,
shape: 'square',
headerFormat: '<table><thead><tr>' +
'<th style="border-bottom: 2px solid #6678b1; color: #039" ' +
'colspan=2 >{point.key}</th></tr></thead><tbody>',
pointFormat: '<tr><td style="color: {series.color}">' +
'<img src="img/series_{series.index}.png" ' +
'style="vertical-align:text-bottom; margin-right: 5px" >' +
'{series.name}: </td><td style="text-align: right; color: #669;">' +
'<b>{point.y}</b></td></tr>',
footerFormat: '</tbody></table>'
},
当我们悬停在数据点上时,模板变量 point 会内部替换为悬停的点对象,系列则替换为包含数据点的 series 对象。
以下是新工具提示的截图。系列名称旁边的图标表示市场收盘:

使用回调处理程序
或者,我们可以通过 JavaScript 中的回调处理程序来实现工具提示。工具提示处理程序通过 formatter 选项声明。模板选项和处理程序之间的主要区别在于,我们可以通过设置条件和返回布尔值 false 来禁用某些点的工具提示显示,而对于模板选项,我们则不能这样做。在回调示例中,我们使用 this.series 和 this.point 变量来获取系列名称和悬停数据点的值。
以下是一个处理程序的示例:
formatter: function() {
return '<span style="color:#039;font-weight:bold">' +
this.point.category +
'</span><br/><span style="color:' +
this.series.color + '">' + this.series.name +
'</span>: <span style="color:#669;font-weight:bold">' +
this.point.y + '</span>';
}
之前的手动代码返回一个包含系列名称、类别和值的 SVG 文本提示框,如下截图所示:

应用多系列提示框
另一个灵活的提示框功能是允许所有系列数据在同一提示框内显示。这通过在一次操作中查找多个系列数据来简化用户交互。要启用此功能,我们需要将 shared 选项设置为 true。
我们将继续使用之前的示例来展示多系列提示框。以下是新提示框代码:
shared: true,
useHTML: true,
shape: 'square',
headerFormat: '<table><thead><tr><th colspan=2 >' +
'{point.key}</th></tr></thead><tbody>',pointFormat: '<tr><td style="color: {series.color}">' +
'{series.name}: </td>' +
'<td style="text-align: right; color: #669;"> ' +
'<b>{point.y}</b></td></tr>',footerFormat: '</tbody></table>'
之前的代码片段将生成以下截图:

如前所述,我们将使用月最高值和月最低值系列来绘制堆叠柱状图,这些柱状图实际上是用于在同一类别内绘制数据的。因此,月最高值系列的提示框显示了之前放入的减去值。为了在提示框中纠正这一点,我们可以使用处理程序为月最高值系列应用不同的属性,如下所示:
shared: true,
shape: 'square',
formatter: function() {
return '<span style="color:#039;font-weight:bold">' +
this.x + '</span><br/>' +
this.points.map(function(point, idx) {return '<span style="color:' + point.series.color +
'">' + point.series.name +
'</span>: <span style="color:#669;font-weight:bold">' +
Highcharts.numberFormat((idx == 0) ? point.total : point.y) + '</span>';}).join('<br/>');
}
point.total 是差异总和与月最低值系列值的总和。以下截图显示了新的更正后的月最高值:

动画图表
Highcharts 中有两种类型的动画:初始动画和更新动画。初始动画是在系列数据准备就绪且图表显示时发生的动画。更新动画发生在初始动画之后,当系列数据或图表结构的任何部分发生变化时。
初始动画配置可以通过 plotOptions.series.animation 或 plotOptions.{series-type}.animation 来指定,而更新动画则是通过 chart.animation 属性来配置。
所有 Highcharts 动画都使用 jQuery 实现。animation 属性可以是布尔值或一组选项。对于布尔值,它是 true。Highcharts 可以使用 jQuery 进行摆动动画。这些是选项:
-
duration:这是完成动画所需的时间,以毫秒为单位。 -
easing:这是 jQuery 提供的动画类型。通过导入 jQuery UI 插件可以扩展动画的多样性。一个良好的参考可以在plugindetector.com/demo/easing-jquery-ui/找到。
在这里,我们继续上一节中的示例。我们将应用动画设置到 plotOptions.column 和 plotOptions.line,如下所示:
plotOptions: {
column: {
... ,
animation: {
duration: 2000,
easing: 'swing'
}
},
line: {
.... ,
animation: {
duration: 3000,
easing: 'linear'
}
}
},
动画速度被调整得慢得多,因此我们可以看到线性动画和摆动动画之间的区别。线系列沿着 x 轴以线性速度出现,而柱状系列以线性速度向上扩展,然后在接近显示末尾时急剧减速。以下是一个显示正在进行线性动画的截图:

使用渐变扩展颜色
Highcharts 不仅支持单色值,还允许复杂的颜色渐变定义。在 Highcharts 中,颜色渐变基于 SVG 线性颜色渐变标准,由以下两组信息组成:
-
linearGradient:这为由两组 x 和 y 坐标组成的颜色光谱提供了渐变方向;比例值介于 0 和 1 之间,或者以百分比表示 -
stops:这给出了一组要填充到光谱中的颜色,以及它们在渐变方向中的比例位置
我们可以使用之前的股市示例,只使用成交量序列,并将 yAxis alternateGridColor 重新定义为以下内容:
yAxis: [{
title: { text: 'Nasdaq index' },
....
alternateGridColor: {
linearGradient: [ 10, 250, 400, 250 ],
stops: [
[ 0, 'red' ],
[ 0.2, 'orange' ],
[ 0.5, 'yellow' ] ,
[ 0.8, 'green' ] ,
[ 1, 'lime' ] ]
}
linearGradient 是一个按 x1, y1, x2, y2 顺序排列的坐标值数组。这些值可以是绝对坐标、百分比字符串或介于 0 和 1 之间的比例值。区别在于,在坐标值中定义的颜色可能会受到图表大小的影响,而百分比和比例值则避免了这一点。
注意
绝对位置渐变的数组语法已被弃用,因为它在 SVG 和 VML 中工作方式不同,它也无法很好地适应不同大小的图表。
stops 属性有一个元组数组:第一个值是介于 0 到 1 之间的偏移量,第二个值是颜色定义。偏移量和颜色值定义了颜色在光谱中的位置。例如,[0, 'red' ] 和 [0.2, 'orange' ] 表示从红色开始,逐渐在水平方向上改变颜色到橙色,位置在 x = 80 (0.2 * 400),然后在 x = 80 处从橙色变为黄色,以此类推。以下是多色渐变的截图:

如我们所见,红色和橙色没有出现在图表上,因为渐变是基于坐标的。因此,根据图表的大小,y 轴的位置超过了此示例中的红色和橙色坐标。或者,我们可以按以下方式指定 linearGradient 的百分比:
linearGradient: [ '20%', 250, '90%', 250 ]
这意味着 linearGradient 从图表宽度的 20% 开始延伸到 90%,这样颜色带就不会局限于图表的大小。以下截图显示了新的 linearGradient 设置的效果:

图表背景现在具有完整的颜色光谱。至于指定 0 到 1 之间的比例值,linearGradient 必须以对象样式定义,否则值将被视为坐标。请注意,比例值仅指代绘图区域的比例,而不是整个图表。
linearGradient: { x1: 0, y1: 0, x2: 1, y2: 0 }
上一行代码是设置水平渐变的另一种方法。

以下代码行调整了垂直渐变:
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 }
它在垂直方向上产生渐变背景。我们还分别设置了'Jan'和'Jul'数据点作为具有垂直方向线性阴影的点对象:

此外,我们可以操作 Highcharts 标准颜色,在系列图中触发颜色渐变。这种方法来自一个在 Highcharts 论坛上实验 3D 图表外观的帖子。在绘制图表之前,我们需要用渐变色覆盖默认系列颜色。以下代码片段将第一个系列颜色替换为水平蓝色渐变阴影。请注意,此示例中的渐变比例值是指系列柱子的宽度:
$(document).ready(function() {
Highcharts.getOptions().colors[0] = {
linearGradient: { x1: 0, y1: 0, x2: 1, y2: 0 },
stops: [ [ 0, '#4572A7' ],
[ 0.7, '#CCFFFF' ],
[ 1, '#4572A7' ] ]
};
var chart = new Highcharts.Chart({ ...
下面的截图是一个带有颜色阴影的柱状图:

使用钻取功能缩放数据
Highcharts 提供了一个简单的方法,在两个数据系列之间进行交互式缩放数据。这个功能作为一个独立的模块实现。要使用这个功能,用户需要包含以下 JavaScript:
<script type="text/javascript"
src="img/drilldown.js"></script>
建立交互式钻取图有两种方式:同步和异步。前一种方法要求用户提前提供顶层和详细级别的数据,并在配置中安排这两级数据。这两级数据都指定为标准的 Highcharts series配置,除了放大系列位于drilldown属性内。为了连接这两级,顶层系列必须提供series.data[x].drilldown选项,与详细级别的drilldown.series.[y].id选项匹配。
让我们回顾一下网络浏览器统计示例,并将数据绘制在柱状图中。我们将在本书的后面部分更详细地介绍柱状系列。以下是最顶层数据的series配置:
series: [{
type: 'column',
name: 'Web browsers',
colorByPoint: true,
data: [{
name: 'Chrome', y: 55.8, drilldown: 'chrome'
}, {
name: 'Firefox', y: 26.8, drilldown: 'firefox',
}, {
name: 'Internet Explorer', y: 9, drilldown: 'ie'
}, {
name: 'Safari', y: 3.8
}]
}],
每个柱子都使用drilldown选项定义并分配一个特定的名称。请注意,并非所有数据点都必须可缩放。前面的例子演示了没有drilldown属性的Safari列。
为了对应每个drilldown分配,配置了一个系列数据数组(详细级别)以匹配值。以下显示了钻取数据的设置:
drilldown: {
series: [{
id: 'chrome',
name: 'Chrome',
data: [{
name: 'C 32', y: 1
}, {
name: 'C 31', y: 49
}, {
T.
}]
}, {
id: 'firefox',
name: 'Firfox',
data: [{
name: 'FF 26', y: 6.9
}, {
name: 'FF 25', y: 13.3
}, {
w.
}]
….
}]
}
这产生了以下截图:

如我们所见,x 轴上的类别标签以深色并带有下划线(默认样式)装饰。它们是可点击的,与非钻取列(Safari)相比看起来不同。当我们点击 Firefox 列或标签时,列会动画放大并缩入多个列,同时在右上角有一个返回按钮,如下面的截图所示:

让我们用更多样式修改图表。drilldown 选项中包含一些智能化的数据标签属性,如 activeAxisLabelStyle 和 activeDataLabelStyle,它们接受 CSS 样式的配置对象。在以下代码中,我们使用超链接、蓝色和下划线等功能来润色类别标签。此外,我们将光标样式更改为浏览器特定的放大图标:
小贴士
webkit-zoom-in 是 Chrome 和 Safari 浏览器特定的光标名称;moz-zoom-in/out 是 Firefox 的。
plotOptions: {
column: {
dataLabels: {
enabled: true
}
}
},
drilldown: {
activeAxisLabelStyle: {
textDecoration: 'none',
cursor: '-webkit-zoom-in',
color: 'blue'
},
activeDataLabelStyle: {
cursor: '-webkit-zoom-in',
color: 'blue',
fontStyle: 'italic'
},
series: [{
....
}]
}
以下截图显示了新的文本标签样式以及新的类别标签上的新光标图标:

Highcharts 的另一个巨大灵活性在于,顶层和详细级别的图表并不限于同一类型的系列。钻取系列可以是任何最适合展示详细数据的系列。这可以通过在详细级别分配不同的系列 type 来实现。以下是对示例的进一步代码更新,其中将 Internet Explorer 列放大并展示为饼图(或环形图)。我们还为饼图设置了数据标签,以显示版本名称和百分比值:
plotOptions: {
column: {
....
},
pie: {
dataLabels: {
enabled: true,
format: '{point.name} - {y}%'
}
}
},
drilldown: {
activeAxisLabelStyle: {
....
},
activeDataLabelStyle: {
....
},
series: [{
id: 'ie',
name: 'Internet Explorer',
type: 'pie',
innerSize: '30%',
data: [{
....
}],
}, {
....
}]
}
以下是对环形图的截图:

前面的环形图略有误导性,因为所有数据标签的总值为 9%,但环形图的展示给人一种总值为 100% 的印象。为了澄清 9% 是总值,我们可以利用 Highcharts 渲染器引擎(我们将在第五章饼图中进一步探讨)在环的中间显示一个 SVG 文本框。然而,drilldown 选项仅允许我们声明系列特定的选项。此外,我们希望当点击 'Internet Explorer' 列时,9% 的文本框才出现。
实现这一点的技巧是使用 Highcharts 事件,这将在第十一章 Highcharts 事件中进一步探讨。在这里,我们使用特定于 drilldown 操作的事件。基本思路是,当 drilldown 事件被触发时,我们检查被点击的数据点是否为 Internet Explorer。如果是,那么我们使用图表的渲染器引擎创建一个文本框。在 drillup 事件(当点击 Back to … 按钮时触发)中,如果存在文本框,则将其移除:
chart = new Highcharts.Chart({
chart: {
renderTo: 'container',
events: {
drilldown: function(event) {
// According to Highcharts documentation,
// the clicked data point is embedded inside
// the event object
var point = event.point;
var chart = point.series.chart;
var renderer = chart.renderer;
if (point.name == 'Internet Explorer') {
// Create SVG text box from
// Highcharts Renderer
// chart center position based on
// it's dimension
pTxt = renderer.text('9%',
(chart.plotWidth / 2) +
chart.plotLeft - 43,
(chart.plotHeight / 2) +
chart.plotTop + 25)
.css({
fontSize: '45px',
// Google Font
fontFamily: 'Old Standard TT',
fontStyle: 'italic',
color: 'blue'
}).add();
}
}, // drilldown
drillup: function() {
pTxt && (pTxt = pTxt.destroy());
}
}
这是带有文本框居中的精炼图表:

摘要
在本章中,我们讨论并实验了主要的配置组件,并展示了示例。到目前为止,我们应该对我们已经覆盖的内容感到舒适,并准备好以更详细的方式绘制一些基本图表。
在下一章中,我们将探讨 Highcharts 支持的折线图、面积图和散点图。我们将应用本章学到的配置,并探索特定系列的风格选项,以艺术风格绘制图表。
第三章:折线图、面积图和散点图
在本章中,我们将学习折线图、面积图和散点图,并更详细地探讨它们的绘图选项。我们还将学习如何创建堆叠面积图和投影图。然后,我们将尝试以稍微更艺术化的风格绘制图表。这样做的原因是给我们一个机会利用各种绘图选项。在本章中,我们将涵盖以下内容:
-
介绍折线图
-
绘制面积图
-
突出显示和提高基准水平
-
混合线形和面积系列
-
结合散点图和面积系列
介绍折线图
首先,让我们从一个单系列折线图开始。我们将使用由世界银行组织提供的许多数据集之一。以下是一个创建简单折线图的代码片段,该图显示了日本过去三十年 65 岁及以上人口的比例:
var chart = new Highcharts.Chart({
chart: {
renderTo: 'container'
},
title: {
text: 'Population ages 65 and over (% of total)'
},
credits: {
position: {
align: 'left',
x: 20
},
text: 'Data from The World Bank'
},
yAxis: {
title: {
text: 'Percentage %'
}
},
xAxis: {
categories: ['1980', '1981', '1982', ... ],
labels: {
step: 5
}
},
series: [{
name: 'Japan - 65 and over',
data: [ 9, 9, 9, 10, 10, 10, 10 ... ]
}]
});
以下是一个简单图表的显示:

在类别中,我们不是手动指定年份数字作为字符串,而是可以在series配置中使用pointStart选项来启动第一个点的x轴值。因此,我们有空的xAxis配置和series配置,如下所示:
xAxis: {
},
series: [{
pointStart: 1980,
name: 'Japan - 65 and over',
data: [ 9, 9, 9, 10, 10, 10, 10 ... ]
}]
扩展到多系列折线图
我们可以包括更多的线系列,并通过将线宽增加到6像素宽来强调日本系列,如下所示:
series: [{
lineWidth: 6,
name: 'Japan',
data: [ 9, 9, 9, 10, 10, 10, 10 ... ]
}, {
Name: 'Singapore',
data: [ 5, 5, 5, 5, ... ]
}, {
// Norway, Brazil, and South Africa series data...
...
}]
通过使那条线更粗,日本人口折线系列在图表中成为焦点,如下面的截图所示:

让我们继续到一个更复杂的折线图。为了演示倒置的折线图,我们使用chart.inverted选项将 y 轴和 x 轴翻转成相反的方向。然后,我们更改轴的线颜色以匹配上一章中的系列颜色。我们还禁用了所有系列的标记点,并最终将第二个系列与 y 轴数组上的第二个条目对齐,如下所示:
chart: {
renderTo: 'container',
inverted: true
},
yAxis: [{
title: {
text: 'Percentage %'
},
lineWidth: 2,
lineColor: '#4572A7'
}, {
title: {
text: 'Age'
},
opposite: true,
lineWidth: 2,
lineColor: '#AA4643'
}],
plotOptions: {
series: {
marker: {
enabled: false
}
}
},
xAxis: {
categories: [ '1980', '1981', '1982', ...,
'2009', '2010' ],
labels: {
step: 5
}
},
series: [{
name: 'Japan - 65 and over',
type: 'spline',
data: [ 9, 9, 9, ... ]
}, {
name: 'Japan - Life Expectancy',
yAxis: 1,
data: [ 76, 76, 77, ... ]
}]
以下是一个带有双 y 轴的倒置图表:

由于通常的时间标签被交换到y轴上,数据趋势难以理解,因此图表的数据表示可能看起来有些奇怪。inverted选项通常用于以非连续形式和条形格式显示数据。如果我们从图中解释数据,1990 年有 12%的人口年龄在 65 岁或以上,预期寿命为 79 岁。
将plotOptions.series.marker.enabled设置为false将关闭所有数据点标记。如果我们想显示特定系列的点标记,我们可以全局关闭标记然后为单个系列打开标记,或者反过来:
plotOptions: {
series: {
marker: {
enabled: false
}
}
},
series: [{
marker: {
enabled: true
},
name: 'Japan - 65 and over',
type: 'spline',
data: [ 9, 9, 9, ... ]
}, {
以下图表显示,只有 65 岁以上的系列有数据点标记:

突出显示负值并提高基准水平
有时,我们可能希望用不同的颜色突出显示正负区域。在这种情况下,我们可以使用系列选项negativeColor指定负值的系列颜色。让我们创建一个包含正负数据的简单通货膨胀数据示例。以下是系列配置:
plotOptions: {
line: {
negativeColor: 'red'
}
},
series: [{
type: 'line',
color: '#0D3377',
marker: {
enabled: false
},
pointStart: 2004,
data:[ 2.9, 2.8, 2.4, 3.3, 4.7,
2.3, 1.1, 1.0, -0.3, -2.1
]
}]
我们将负通货膨胀值指定为红色,并在线条系列中禁用标记。线条系列的颜色由另一种颜色定义,即蓝色,用于正值。以下是以不同颜色显示系列的图表:

让我们创建另一个稍微复杂一点的例子,其中我们根据主题定义负值区域。我们绘制另一个通货膨胀图表,但基于欧洲中央银行(ECB)对健康通货膨胀 2%的定义。任何低于该水平的都被认为对经济不健康,因此我们将低于该阈值的颜色设置为红色。除了颜色阈值外,我们还在 y 轴上设置了一条折线,以指示截止水平。以下是我们设置图表的第一个尝试:
yAxis: {
title: { text: null },
min: 0,
max: 4,
plotLines: [{
value: 2,
width: 3,
color: '#6FA031',
zIndex: 1,
label: {
text: 'ECB....',
....
}
}]
},
xAxis: { type: 'datetime' },
plotOptions: {
line: { lineWidth: 3 }
},
series: [{
type: 'line',
name: 'EU Inflation (harmonized), year-over-year (%)',
color: '#0D3377',
marker: { enabled: false },
data:[
[ Date.UTC(2011, 8, 1), 3.3 ],
[ Date.UTC(2011, 9, 1), 3.3 ],
[ Date.UTC(2011, 10, 1), 3.3 ],
....
我们在 y 轴上设置了值为 2 的绿色折线,宽度为 3 像素。zIndex选项是为了避免间隔线出现在折线上方。对于膨胀线系列,我们禁用了标记,并将线宽设置为 3 像素宽。以下是没有阈值处理的初始尝试:

让我们将阈值水平应用于线条系列。y 轴上默认的负色值在 0 处。对于这个特定的例子,负色的基准水平是 2。为了将基准水平提高到 2,我们设置了threshold属性以及negativeColor选项:
plotOptions: {
line: {
lineWidth: 3,
negativeColor: 'red',
threshold: 2
}
},
前面的修改将部分线条系列变为红色,以表示警告:

绘制面积图
在本节中,我们将使用我们的第一个示例并将其转变为一个更时尚的图表(基于 Kristin Clute 设计的风能海报):面积样条图。面积样条图是通过面积图和样条图的组合属性生成的。主要数据线以样条曲线的形式绘制,线下方的区域以类似颜色填充,具有渐变和不透明样式:

首先,我们希望使图表更容易让观众查找当前趋势的值,因此我们将 y 轴值移动到图表的右侧,这样它们将最接近最近的一年:
yAxis: { ....
opposite: true
}
下一步是移除间隔线,并在 y 轴上有一条细轴线:
yAxis: { ....
gridLineWidth: 0,
lineWidth: 1,
}
然后,我们使用百分比符号简化 y 轴标题,并将其对齐到轴的顶部:
yAxis: { ....
title: {
text: '(%)',
rotation: 0,
x: 10,
y: 5,
align: 'high'
},
}
至于 x 轴,我们用红色加粗轴线,并移除间隔刻度:
xAxis: { ....
lineColor: '#CC2929',
lineWidth: 4,
tickWidth: 0,
offset: 2
}
对于图表标题,我们将标题移动到图表的右侧,增加图表和标题之间的边距,并为标题采用不同的字体:
title: {
text: 'Population ages 65 and over (% of total) - Japan ',
margin: 40,
align: 'right',
style: {
fontFamily: 'palatino'
}
}
之后,我们将修改整个系列的展示,所以我们首先将chart.type属性从'line'更改为'areaspline'。请注意,在这个series对象内部设置属性将覆盖在plotOptions.areaspline等plotOptions.series中定义的相同属性。
由于到目前为止图中只有一个系列,因此没有必要显示图例框。我们可以使用showInLegend属性来禁用它。然后,我们使用渐变色智能地处理面积部分,并使用较深的颜色处理样条线:
series: [{
showInLegend: false,
lineColor: '#145252',
fillColor: {
linearGradient: {
x1: 0, y1: 0,
x2: 0, y2: 1
},
stops:[ [ 0.0, '#248F8F' ] ,
[ 0.7, '#70DBDB' ],
[ 1.0, '#EBFAFA' ] ]
},
data: [ ... ]
}]
之后,我们在沿线引入了一些数据标签,以表明老龄人口排名随时间增加。我们使用对应于 1995 年和 2010 年的系列数据数组中的值,然后将数值条目转换为数据点对象。由于我们只想显示这两年的点标记,我们在plotOptions.series.marker.enabled中全局关闭标记,并在点对象内部单独设置标记,并伴随样式设置:
plotOptions: {
series: {
marker: {
enabled: false
}
}
},
series: [{ ...,
data:[ 9, 9, 9, ...,
{ marker: {
radius: 2,
lineColor: '#CC2929',
lineWidth: 2,
fillColor: '#CC2929',
enabled: true
},
y: 14
}, 15, 15, 16, ... ]
}]
我们首先在数据标签周围设置了一个圆角边框(borderRadius),其边框颜色(borderColor)与 x 轴相同。然后,我们使用x和y选项对数据标签位置进行精细调整。最后,我们更改了数据标签格式器的默认实现。我们不再返回点值,而是打印国家排名:
series: [{ ...,
data:[ 9, 9, 9, ...,
{ marker: {
...
},
dataLabels: {
enabled: true,
borderRadius: 3,
borderColor: '#CC2929',
borderWidth: 1,
y: -23,
formatter: function() {
return "Rank: 15th";
}
},
y: 14
}, 15, 15, 16, ... ]
}]
最后的修饰是给图表添加灰色背景,并为spacingBottom添加额外空间。spacingBottom的额外空间是为了避免信用标签和 x 轴标签过于接近,因为我们已经禁用了图例框:
chart: {
renderTo: 'container',
spacingBottom: 30,
backgroundColor: '#EAEAEA'
},
当所有这些配置组合在一起时,它会产生本节开头截图所示的图表。
混合线形和面积系列
在本节中,我们将探索不同的图表,包括线形和面积系列,如下所示:
-
创建一个投影图,其中一条趋势线与两种不同线型的系列连接
-
绘制一个带有另一个步进线系列的区域样条图
-
探索一个堆叠的面积样条图,其中两个面积样条系列堆叠在一起
模拟一个投影图
投影图具有与实际数据部分相对应的样条面积,并使用投影数据以虚线形式继续。为此,我们将数据分为两个系列,一个用于实际数据,另一个用于投影数据。以下是从 2015 年到 2024 年的数据系列配置代码。这些数据基于国家人口和社会安全研究所的报告(www.ipss.go.jp/pp-newest/e/ppfj02/ppfj02.pdf):
series: [{
name: 'project data',
type: 'spline',
showInLegend: false,
lineColor: '#145252',
dashStyle: 'Dash',
data: [ [ 2015, 26 ], [ 2016, 26.5 ],
... [ 2024, 28.5 ] ]
}]
未来系列配置为虚线样式的样条线,图例框被禁用,因为我们想显示两个系列作为同一系列的一部分。然后我们将未来(第二个)系列的颜色设置为与第一个系列相同。最后部分是构建系列数据。由于我们使用pointStart属性指定了 x 轴时间数据,我们需要在 2014 年之后对齐投影数据。我们可以使用两种方法来指定时间数据,如下所示:
-
在第二个系列数据数组中插入空值,以与实际数据系列对齐
-
在
tuples中指定第二个系列数据,这是一个包含时间和投影数据的数组
我们将使用第二种方法,因为系列表示更简单。以下仅为未来数据系列的截图:

实际数据系列与绘制面积图部分开头截图中的图表完全相同,只是没有点标记和数据标签装饰。下一步是将两个系列合并,如下所示:
series: [{
name: 'real data',
type: 'areaspline',
....
}, {
name: 'project data',
type: 'spline',
....
}]
由于两个系列数据之间没有重叠,它们产生了一个平滑的投影图:

对比样条线和步骤线
在本节中,我们将以步骤形式绘制一个面积样条图系列和另一个线条系列。步骤线根据系列数据的变化在垂直和水平方向上穿越。它通常用于表示离散数据:没有连续/渐进运动的数据。
为了显示步骤线,我们将从第一个面积样条图示例继续。首先,我们需要通过移除禁用的showInLegend设置来启用图例,并在系列数据中移除dataLabels。
接下来是在图表中包含一个新的系列——0 至 14 岁——并使用默认的线条类型。然后,我们将线条样式稍作调整,变为不同的步骤。以下是为两个系列配置的以下内容:
series: [{
name: 'Ages 65 and over',
type: 'areaspline',
lineColor: '#145252',
pointStart: 1980,
fillColor: {
....
},
data: [ 9, 9, 9, 10, ...., 23 ]
}, {
name: 'Ages 0 to 14',
// default type is line series
step: true,
pointStart: 1980,
data: [ 24, 23, 23, 23, 22, 22, 21,
20, 20, 19, 18, 18, 17, 17, 16, 16, 16,
15, 15, 15, 15, 14, 14, 14, 14, 14, 14,
14, 14, 13, 13 ]
}]
以下截图显示了线条步骤样式中的第二个系列:

扩展到堆叠面积图
在本节中,我们将把两个序列都转换为面积样条图,并将它们堆叠在一起以创建堆叠面积图。由于数据序列堆叠在一起,我们可以观察到各个序列的数量作为单独的、成比例的和总数量。
让我们将第二个序列更改为另一种 'areaspline' 类型:
name: 'Ages 0 to 14',
type: 'areaspline',
pointStart: 1980,
data: [ 24, 23, 23, ... ]
将 stacking 选项设置为 areaspline 的默认设置 'normal',如下所示:
plotOptions: {
areaspline: {
stacking: 'normal'
}
}
这样就设置了两个堆叠在彼此之上的面积图。通过这样做,我们可以从数据中观察到,两个年龄组大致相互补偿,总共约占总体人口的 33%,而 65 岁及以上 的群体在后期阶段逐渐落后:

假设我们有三个面积样条序列,我们只想堆叠其中的两个(尽管在柱状图中这样做更清晰,而不是在面积样条图中)。如第二章中 探索 PlotOptions 部分所述,Highcharts 配置,我们可以在 plotOptions.series 中设置 stacking 选项为 'normal',并在第三个序列配置中手动关闭 stacking。以下是与另一个序列的序列配置:
plotOptions: {
series: {
marker: {
enabled: false
},
stacking: 'normal'
}
},
series: [{
name: 'Ages 65 and over',
....
}, {
name: 'Ages 0 to 14',
....
}, {
name: 'Ages 15 to 64',
type: 'areaspline',
pointStart: 1980,
stacking: null,
data: [ 67, 67, 68, 68, .... ]
}]
这创建了一个包含第三个序列 15 至 64 岁 覆盖其他两个堆叠序列的面积样条图,如下面的截图所示:

绘制具有缺失数据的图表
如果一个序列有缺失数据,那么 Highcharts 的默认行为是将序列显示为折线图。有一个选项——connectNulls——允许即使在存在缺失数据的情况下,序列线也可以继续。此选项的默认值是 false。让我们通过设置两个具有空数据点的样条序列来检查默认行为。我们还启用了数据点标记,以便我们可以清楚地查看缺失的数据点:
series: [{
name: 'Ages 65 and over',
connectNulls: true,
....,
// Missing data from 2004 - 2009
data: [ 9, 9, 9, ..., null, null, null, 22, 23 ]
}, {
name: 'Ages 0 to 14',
....,
// Missing data from 1989 - 1994
data: [ 24, 23, 23, ..., 19, null, null, ..., 13 ]
}]
以下是一个具有不同样式的样条序列表示缺失点的图表:

如我们所见,0 至 14 岁 的序列有一个明显的断裂线,而 65 岁及以上 是通过将 connectNulls 设置为 true 来配置的,这使用样条曲线连接了缺失的点。如果未启用数据点标记,我们就无法注意到差异。
然而,我们应该谨慎使用此选项,并且绝对不应该与 stacking 选项一起启用。假设我们有一个包含两个序列的堆叠面积图,并且只有 0 至 14 岁 的序列中存在缺失数据,这是底部序列。对于缺失数据的默认行为将使图表看起来如下截图所示:

虽然底部系列确实显示了断裂部分,但堆叠图总体上仍然正确。顶部系列的相同区域回到单系列值,总体百分比仍然保持不变。
当我们将connectNulls选项设置为true时,问题出现了,我们没有意识到系列中存在缺失数据。这导致图表不一致,如下所示:

底部系列覆盖了顶部系列留下的孔,这与堆叠图的总体百分比相矛盾。
结合散点图和面积图
Highcharts 还支持散点图,使我们能够从大量数据样本中绘制数据趋势。在这里,我们将以不同的方式使用散点系列,这使得我们的图表有点像海报图表。
首先,我们将使用'Ages 0 to 14'数据的一个子集,并将系列设置为scatter类型:
name: 'Ages 0 to 14',
type: 'scatter',
data: [ [ 1982, 23 ], [ 1989, 19 ],
[ 2007, 14 ], [ 2004, 14 ],
[ 1997, 15 ], [ 2002, 14 ],
[ 2009, 13 ], [ 2010, 13 ] ]
然后,我们将为scatter系列启用数据标签,并确保marker形状始终为'circle',如下所示:
plotOptions: {
scatter: {
marker: {
symbol: 'circle'
},
dataLabels: {
enabled: true
}
}
}
上述代码片段给出了以下图表:

提示
Highcharts 提供了一系列的标记符号,并允许用户提供自己的标记图标(见第二章,Highcharts 配置)。支持的符号列表包括:圆形、正方形、菱形、三角形和向下三角形。
以艺术风格润色图表
下一步是将每个散点点格式化为具有radius属性的气泡样式,并手动设置数据标签字体大小与百分比值成比例。
注意
我们使用散点系列而不是气泡系列的原因是,本章的大部分内容是为第一版编写的;此图表是用早期版本的 Highcharts 创建的,该版本不支持气泡系列。
然后,使用verticalAlign属性调整标签,使其位于放大后的散点点中心。散点点的各种大小要求我们以不同的属性呈现每个数据点。因此,我们需要将系列数据定义更改为点对象配置数组,例如:
plotOptions: {
scatter: {
marker: {
symbol: 'circle'
},
dataLabels: {
enabled: true,
verticalAlign: 'middle'
}
}
},
data: {
dataLabels: {
style: {
fontSize: '25px'
}
},
marker: { radius: 31 },
y: 23,
x: 1982
}, {
dataLabels: {
style: {
fontSize: '22px'
}
},
marker: { radius: 23 },
y: 19,
x: 1989
}, .....
以下截图显示了一个数据点序列的图表,从大标记大小和字体开始,然后根据它们的百分比值逐渐变小:
![以艺术风格润色图表
现在,前一个图表有两个问题。首先,散点系列颜色(默认的第二系列颜色)与标记内的灰色文本标签冲突,使得它们难以阅读。
为了解决这个问题,我们将散点系列的颜色改为较浅的颜色,并使用渐变设置:
color: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [ [ 0, '#FF944D' ],
[ 1, '#FFC299' ] ]
},
然后,在plotOptions中给散点点一个更深的轮廓,如下所示:
plotOptions: {
scatter: {
marker: {
symbol: 'circle',
lineColor: '#E65C00',
lineWidth: 1
},
其次,数据点被轴的范围结束所阻挡。可以通过在两个轴中引入额外的填充空间来解决这个问题:
yAxis: {
.....,
maxPadding: 0.09
},
xAxis: {
.....,
maxPadding: 0.02
}
下面的图形是新的外观:

对于下一部分,我们将放置一个标志和一些装饰性文本。将图像导入图表有两种方式——plotBackgroundImage 选项或 renderer.image API 调用。plotBackgroundImage 选项将整个图像带入图表背景,这不是我们想要做的。renderer.image 方法提供了更多关于图像位置和大小的控制。以下是图表创建后的调用:
var chart = new Highcharts.Chart({
...
});
chart.renderer.image('logo.png', 240, 10, 187, 92).add();
logo.png 是标志图像文件的 URL 路径。接下来的两个参数是图表中图像显示的 x 和 y 位置(从 0 开始,其中 0 是左上角)。最后的两个参数是图像文件的宽度和高度。image 调用基本上返回一个 element 对象,随后 .add 调用将返回的图像对象放入渲染器中。
至于装饰性文本,它是一个带有白色粗体文本的不同大小的红色圆圈。它们都是通过渲染器创建的。在下面的代码片段中,第一个渲染器调用是用来创建一个带有 x 和 y 位置的红色圆圈和半径大小的圆圈。然后使用 attr 方法立即设置 SVG 属性,该方法配置了透明度和轮廓的较暗颜色。接下来的三个渲染器调用是用来在红色圆圈内创建文本,并使用 css 方法设置字体大小、样式和颜色。我们将在第十章 Highcharts API 中重新访问 chart.renderer:
// Red circle at the back
chart.renderer.circle(220, 65, 45).attr({
fill: '#FF7575',
'fill-opacity': 0.6,
stroke: '#B24747',
'stroke-width': 1
}).add();
// Large percentage text with special font
chart.renderer.text('37.5%', 182, 63).css({
fontWeight: 'bold',
color: '#FFFFFF',
fontSize: '30px',
fontFamily: 'palatino'
}).add();
// Align subject in the circle
chart.renderer.text('65 and over', 184, 82).css({
'fontWeight': 'bold',
}).add();
chart.renderer.text('by 2050', 193, 96).css({
'fontWeight': 'bold',
}).add();
最后,我们将图例框移动到图表的顶部。为了将图例定位在绘图区域内,我们需要将 floating 属性设置为 true,这将强制图例进入固定布局模式。然后,我们移除默认的边框线并将图例项列表设置为垂直方向:
legend: {
floating: true,
verticalAlign: 'top',
align: 'center',
x: 130,
y: 40,
borderWidth: 0,
layout: 'vertical'
},
下面的图形是我们带有装饰的最终图形:

摘要
在本章中,我们探讨了线形图、面积图和散点图的用法。我们看到了 Highcharts 可以提供多少灵活性来制作类似海报的图表。
在下一章中,我们将学习如何使用它们的绘图选项绘制柱状图和条形图。
第四章:条形图和柱状图
在本章中,我们将从学习柱状图及其绘图选项开始。然后,我们将应用更高级的选项来堆叠和组合柱状图。之后,我们将通过相同的示例来学习条形图。然后,我们将学习如何润色条形图,并应用技巧将条形图转换为镜像和仪表图表。最后,我们将多个图表的网页组合在一起作为总结练习。在本章中,我们将涵盖以下内容:
-
介绍柱状图
-
堆叠和组合柱状图
-
调整柱状图颜色和数据标签
-
介绍条形图
-
构建镜像图表
-
将单个条形图转换为水平仪表图
-
将图表粘合在一起
注意
本章的大部分内容与第一版相同。所有示例都已使用最新版本的 Highcharts 运行。除了不同的配色方案外,生成的图表看起来与本章中展示的截图相同。
介绍柱状图
柱状图和条形图之间的区别微不足道。柱状图中的数据是垂直排列的,而条形图中的数据是水平排列的。柱状图和条形图通常用于在 x 轴上具有类别的数据绘图。在本节中,我们将演示如何绘制柱状图。我们将使用的数据来自美国专利和商标局。
在以下代码片段之后的图形显示了过去 10 年中英国授予的专利数量柱状图。以下是该图表配置代码:
chart: {
renderTo: 'container',
type: 'column',
borderWidth: 1
},
title: {
text: 'Number of Patents Granted',
},
credits: {
position: {
align: 'left',
x: 20
},
href: 'http://www.uspto.gov',
text: 'Source: U.S. Patent & Trademark Office'
},
xAxis: {
categories: [
'2001', '2002', '2003', '2004', '2005',
'2006', '2007', '2008', '2009', '2010',
'2011' ]
},
yAxis: {
title: {
text: 'No. of Patents'
}
},
series: [{
name: 'UK',
data: [ 4351, 4190, 4028, 3895, 3553,
4323, 4029, 3834, 4009, 5038, 4924 ]
}]
下面的结果是前面代码片段得到的结果:
注意
这份数据可以在专利技术监测团队发布的在线报告 所有专利,所有类型报告 中找到,报告链接为www.uspto.gov/web/offices/ac/ido/oeip/taf/apat.htm。

让我们再添加一个系列,法国:
series: [{
name: 'UK',
data: [ 4351, 4190, 4028, .... ]
}, {
name: 'France',
data: [ 4456, 4421, 4126, 3686, 3106,
3856, 3720, 3813, 3805, 5100, 5022 ]
}]
下面的图表显示了两个系列并排对齐:

重叠柱状图
展示多序列柱状图的另一种方式是重叠柱状图。这种展示方式的主要原因是为了避免图表中类别过多时柱状图变得过于细长和拥挤。结果,观察值和比较它们变得困难。重叠柱状图在各个类别之间提供了更多的空间,因此每个柱状图仍然可以保持其宽度。
我们可以使用填充选项使两个序列部分重叠,如下所示:
plotOptions: {
series: {
pointPadding: -0.2,
groupPadding: 0.3
}
},
列(以及条形)之间的默认填充设置是 0.2,这是每个类别宽度的分数值。在这个例子中,我们将pointPadding设置为负值,这意味着,而不是在相邻列之间有填充距离,我们将列放在一起以重叠。groupPadding是组值相对于每个类别宽度的距离,因此 2005 年和 2006 年英国和法国列之间的距离。在这个例子中,我们将它设置为 0.3 以确保列不会自动变宽,因为重叠会在每个组之间产生更多空间。以下是对重叠列的截图:

堆叠和分组柱状图
与将列并排对齐不同,我们可以将列堆叠在一起。虽然这会使观察每个列的值稍微困难一些,但我们可以立即观察到每个类别的总值以及系列之间的比率变化。当有多个系列时,堆叠列的另一个强大功能是选择性地对它们进行分组。这可以在多个堆叠系列组之间产生一种比例感。
让我们从英国、德国、日本和韩国开始一个新的柱状图。

与其他国家相比,日本获得的专利数量超出了常规。让我们使用以下系列配置将多个系列分组和堆叠到欧洲和亚洲:
plotOptions: {
column: {
stacking: 'normal'
}
},
series: [{
name: 'UK',
data: [ 4351, 4190, 4028, .... ],
stack: 'Europe'
}, {
name: 'Germany',
data: [ 11894, 11957, 12140, ... ],
stack: 'Europe'
}, {
name: 'S.Korea',
data: [ 3763, 4009, 4132, ... ],
stack: 'Asia'
}, {
name: 'Japan',
data: [ 34890, 36339, 37248, ... ],
stack: 'Asia'
}]
我们在plotOptions中声明列stacking为'normal',然后为每个柱形系列分配一个堆叠组名,'Europe'和'Asia',这产生了以下图表:

如我们所见,图表将四个垂直条形减少到两个,每个柱形包含两个系列。第一个垂直条形是'Europe'组,第二个是'Asia'。
混合堆叠和单列
在前面的章节中,我们承认了分组和堆叠多个系列的好处。也有时候,多个系列可以属于一个组,但也有一些单独的系列属于它们自己的组。Highcharts 提供了混合堆叠和分组系列与单系列灵活性的功能。
让我们看看混合堆叠柱形和单列的例子。首先,我们移除每个系列的堆叠组分配,因为所有柱形系列的默认行为是保持堆叠在一起。然后,我们引入一个新的柱形系列,美国,并在系列配置中手动声明stacking选项为null以覆盖默认的plotOptions设置:
plotOptions: {
column: {
stacking: 'normal'
}
},
series: [{
name: 'UK',
data: [ 4351, 4190, 4028, .... ]
}, {
name: 'Germany',
data: [ 11894, 11957, 12140, ... ]
}, {
name: 'S.Korea',
data: [ 3763, 4009, 4132, ... ]
}, {
name: 'Japan',
data: [ 34890, 36339, 37248, ... ]
}, {
name: 'US',
data: [ 98655, 97125, 98590, ... ],
stacking: null
}
}]
新的系列数组产生了以下图表:

前四个系列,英国、德国、韩国和日本,被堆叠在一起作为一个单独的列,而美国则作为一个单独的列显示。通过将系列堆叠在一起,我们可以很容易地观察到其他四个国家的专利总数加起来还不到美国专利总数的三分之二(美国的专利数量几乎是英国的四倍)。
比较堆叠百分比中的列
或者,我们可以通过将值归一化为百分比并将它们堆叠在一起来查看每个国家在列中的比较情况。这可以通过移除美国系列中的手动堆叠设置并将全局列堆叠设置为'percent'来实现:
plotOptions: {
column: {
stacking: 'percent'
}
}
所有系列都被放入一个单独的列中,并且它们的值被归一化为百分比,如下面的截图所示:

调整列颜色和数据标签
让我们再做一个图表;这次我们将绘制获得专利的前十位国家。以下生成图表的代码:
chart: {
renderTo: 'container',
type: 'column',
borderWidth: 1
},
title: {
text: 'Number of Patents Granted in 2011'
},
credits: { ... },
xAxis: {
categories: [
'United States', 'Japan',
'South Korea', 'Germany', 'Taiwan',
'Canada', 'France', 'United Kingdom',
'China', 'Italy' ]
},
yAxis: {
title: {
text: 'No. of Patents'
}
},
series: [{
showInLegend: false,
data: [ 121261, 48256, 13239, 12968, 9907,
5754, 5022, 4924, 3786, 2333 ]
}]
以下代码片段生成了以下图表:

在前面的图表中,我们希望更改几个区域。首先,国家名称中存在换行。为了避免这种情况,我们可以将 x 轴标签旋转,如下所示:
xAxis: {
categories: [
'United States', 'Japan',
'South Korea', ... ],
labels: {
rotation: -45,
align: 'right'
}
},
其次,与来自其他国家的值相比,'美国'的大值已经超出了刻度,因此我们无法轻易识别它们的值。为了解决这个问题,我们可以在 y 轴上应用对数刻度,如下所示:
yAxis: {
title: ... ,
type: 'logarithmic'
},
最后,我们希望沿着列打印值标签,并为每一列用不同的颜色装饰图表,如下所示:
plotOptions: {
column: {
colorByPoint: true,
dataLabels: {
enabled: true,
rotation: -90,
y: 25,
color: '#F4F4F4',
formatter: function() {
return
Highcharts.numberFormat(this.y, 0);
},
x: 10,
style: {
fontWeight: 'bold'
}
}
}
},
下面的图表显示了所有的改进:

介绍条形图
在 Highcharts 中,有两种指定条形图的方法——将系列type设置为'bar'或将chart.inverted选项设置为true与列系列(从条形切换到列也是如此)。在柱状图和条形图之间切换只是简单地交换 y 轴和 x 轴之间的显示方向;所有的标签旋转仍然保持不变。此外,实际的配置仍然保留在 x 轴和 y 轴上。为了演示这一点,我们将使用之前的示例,并将inverted选项设置为true,如下所示:
chart: {
.... ,
type: 'column',
inverted: true
},
以下代码片段生成了一个条形图,如下所示:

国家名称的旋转和对数轴标签的旋转仍然保持不变。事实上,现在值标签混乱在一起,类别名称与条形没有正确对齐。下一步是将标签方向重置以恢复图表的可读性。我们将简单地交换 y 轴的标签设置到 x 轴:
xAxis: {
categories: [ 'United States',
'Japan', 'South Korea', ... ]
},
yAxis: {
.... ,
labels: {
rotation: -45,
align: 'right'
}
},
然后,我们将通过移除旋转选项并重新调整 x 和 y 位置以对齐柱状图内部来重置默认的列dataLabel设置:
plotOptions: {
column: {
..... ,
dataLabels: {
enabled: true,
color: '#F4F4F4',
x: -50,
y: 0,
formatter: ....,
style: ...
}
}
以下是一个具有固定数据标签的图表:

给柱状图一个更简单的样子
在这里,我们将轴简化到最基本、最裸露的展示。我们移除整个 y 轴,并调整柱状图上方的类别名称。为了移除 y 轴,我们将使用以下代码片段:
yAxis: {
title: {
text: null
},
labels: {
enabled: false
},
gridLineWidth: 0,
type: 'logarithmic'
},
然后,我们将国家标签移动到柱状图上方。这伴随着移除轴线线和间隔刻度线,然后更改标签对齐及其 x 和 y 位置,如下所示:
xAxis: {
categories: [ 'United States', 'Japan',
'South Korea', ... ],
lineWidth: 0,
tickLength: 0,
labels: {
align: 'left',
x: 0,
y: -13,
style: {
fontWeight: 'bold'
}
}
},
由于我们将标签对齐改为在柱状图上方,柱状图的水平位置(绘图区域)已移至图表的左侧,以取代旧标签的位置。因此,我们需要增加左侧的间距,以避免图表看起来过于拥挤。最后,我们在绘图区域添加一个背景图像,chartBg.png,以填充空余空间,如下所示:
chart: {
renderTo: 'container',
type: 'column',
spacingLeft: 20,
plotBackgroundImage: 'chartBg.png',
inverted: true
},
title: {
text: null
},
以下截图显示了我们的柱状图的新简单外观:

构建镜像图
使用镜像图是另一种比较两个柱状系列的方法。而不是将两个系列对齐为相邻的列,镜像图将它们对齐为相对的柱状。有时,这被用作展示两个系列之间趋势的首选方式。
在 Highcharts 中,我们可以利用堆积柱状图并将其稍作修改以形成一个镜像图,用于比较两组数据水平并排。为此,让我们从新的数据系列专利授予开始,该系列显示了过去十年英国和中国在专利授予数量上的比较。
我们配置图表的方式实际上是一个堆积柱状图,其中一组数据为正值,另一组数据则手动转换为负值,使得零值轴位于图表中间。然后,我们将柱状图反转成柱状图,并将负值范围标记为正值。为了演示这个概念,让我们首先创建一个堆积柱状图,其中包含正数和自制的负数范围,如下所示:
chart: {
renderTo: 'container',
type: 'column',
borderWidth: 1
},
title: {
text: 'Number of Patents Granted'
},
credits: { ... },
xAxis: {
categories: [ '2001', '2002', '2003', ... ]
},
yAxis: {
title: {
text: 'No. of Patents'
}
},
plotOptions: {
series: {
stacking: 'normal'
}
},
series: [{
name: 'UK',
data: [ 4351, 4190, 4028, ... ]
}, {
name: 'China',
data: [ -265, -391, -424, ... ]
}]
以下截图显示了 y 轴中间的零值的堆积柱状图:

然后,我们将配置更改为一个柱状图,每个侧面都显示两个 x 轴,范围相同。最后一步是定义 y 轴标签的formatter函数,将负标签转换为正标签,如下所示:
chart: {
.... ,
type: 'bar'
},
xAxis: [{
categories: [ '2001', '2002', '2003', ... ]
}, {
categories: [ '2001', '2002', '2003', ... ],
opposite: true,
linkedTo: 0
}],
yAxis: {
.... ,
labels: {
formatter: function() {
return
Highcharts.numberFormat(Math.abs(this.value), 0);
}
}
},
以下是比较过去十年英国和中国授予的专利数量的最终柱状图:

扩展到堆积镜像图
我们还可以将列示例中的相同原则应用于堆叠和分组系列图表。我们不是将两组堆叠的柱状图并排放置,而是可以将所有系列堆叠在一起,用零值来分隔两组。以下截图演示了在柱状图中比较欧洲和亚洲堆叠组的情况:

韩国和日本的系列数据堆叠在左侧(负侧),而英国和德国的数据则分组在右侧(正侧)。在生成前面的图表时,唯一棘手的部分是如何输出数据标签框。
首先,韩国和日本的系列数据被手动设置为负值。其次,由于韩国和英国都是各自组的外部系列,我们为这些系列启用了数据标签。以下代码片段显示了系列数组的配置:
series: [{
name: 'UK',
data: [ 4351, 4190, 4028, ... ],
dataLabels : {
enabled: true,
backgroundColor: '#FFFFFF',
x: 40,
formatter: function() {
return
Highcharts.numberFormat(Math.abs(this.total), 0);
},
style: {
fontWeight: 'bold'
}
}
}, {
name: 'Germany',
data: [ 11894, 11957, 12140, ... ]
}, {
name: 'S.Korea',
data: [ -3763, -4009, -4132, ... ],
dataLabels : {
enabled: true,
x: -48,
backgroundColor: '#FFFFFF',
formatter: function() {
return
Highcharts.numberFormat(Math.abs(this.total), 0);
},
style: {
fontWeight: 'bold'
}
}
}, {
name: 'Japan',
data: [ -34890, -36339, -37248, ... ]
}]
注意,formatter 函数的定义使用的是 this.total 而不是 this.y,因为我们使用外部系列的位置来打印组的总价值。数据标签的白色背景设置是为了避免与 y 轴间隔线发生冲突。
将单个柱状图转换为水平仪表图
仪表图通常用作当前阈值水平的指示器,意味着 y 轴的极端值是固定的。另一个特点是 x 轴上的单值(一维)是当前时间。
接下来,我们将学习如何将单柱图表转换为仪表级图表。基本思路是将绘图区域缩小到与柱状图相同的大小。这意味着我们必须固定绘图区域和柱状图的大小,不考虑容器尺寸。为此,我们将chart.width和chart.height设置为某些值。然后,我们用边框和背景色装饰绘图区域,使其类似于仪表的容器:
chart: {
renderTo: 'container',
type: 'bar',
plotBorderWidth: 2,
plotBackgroundColor: '#D6D6EB',
plotBorderColor: '#D8D8D8',
plotShadow: true,
spacingBottom: 43,
width: 350,
height: 120
},
然后我们关闭 y 轴标题,并在百分比内设置一个常规间隔,如下所示:
xAxis: {
categories: [ 'US' ],
tickLength: 0
},
yAxis: {
title: {
text: null
},
labels: {
y: 20
},
min: 0,
max: 100,
tickInterval: 20,
minorTickInterval: 10,
tickWidth: 1,
tickLength: 8,
minorTickLength: 5,
minorTickWidth: 1,
minorGridLineWidth: 0
},
最后的部分是配置柱状图系列,使柱状图的宽度完美地适应绘图区域。其余的系列配置是对柱状图应用 SVG 渐变效果,如下所示:
series: [{
borderColor: '#7070B8',
borderRadius: 3,
borderWidth: 1,
color: {
linearGradient:
{ x1: 0, y1: 0, x2: 1, y2: 0 },
stops: [ [ 0, '#D6D6EB' ],
[ 0.3, '#5C5CAD' ],
[ 0.45, '#5C5C9C' ],
[ 0.55, '#5C5C9C' ],
[ 0.7, '#5C5CAD' ],
[ 1, '#D6D6EB'] ]
},
pointWidth: 50,
data: [ 48.9 ]
}]
注意
SVG 支持多个停止渐变,但 VML 不支持。对于 VML 浏览器,如 Internet Explorer 8,停止渐变的数量应限制为两个。
以下是仪表图的最终精炼外观:

将图表拼接在一起
在本节中,我们正在构建一个包含多种图表的页面。主图表显示在左侧面板中,三个迷你图表按从上到下的顺序显示在右侧面板中。布局是通过 HTML div 框和 CSS 样式实现的。
左侧图表是从我们之前讨论的多色柱状图示例中提取的。在迷你图表中,所有坐标线和标签都被禁用了。
最上面的第一个迷你图表是一个包含两个系列且只对每个系列的最后一个点启用了dataLabels的折线图:数据数组中的最后一个点是一个数据对象。标签颜色被设置为与系列相同的颜色。然后,在 y 轴的 50%值标记处插入了plotLine。以下是一个系列配置的示例:
pointStart: 2001,
marker: {
enabled: false
},
data: [ 53.6, 52.7, 52.7, 51.9, 52.4,
52.1, 51.2, 49.7, 49.5, 49.6,
{ y: 48.9,
name: 'US',
dataLabels: {
color: '#4572A7',
enabled: true,
x: -10,
y: 14,
formatter: function() {
return
this.point.name + ": " + this.y + '%';
}
}
}]
第二个迷你图表是一个简单的条形图,数据标签位于类别之外。数据标签的样式被设置为更大的粗体字体。
最后一个迷你图表基本上是一个散点图,其中每个系列只有一个点,这样每个系列都可以出现在右侧图例中。此外,我们将每个系列的 x 值设置为零,这样我们就可以有不同的数据点大小,并且可以堆叠在一起。以下是一个散点图配置的示例:
zIndex: 1,
legendIndex: 0,
color: {
linearGradient:
{ x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [ [ 0, '#FF6600' ],
[ 0.6, '#FFB280' ] ]
},
name: 'America - 49%',
marker: {
symbol: 'circle',
lineColor: '#B24700',
lineWidth: 1
},
data: [
{ x: 0, y: 49, name: 'America',
marker: { radius: 74 }
} ]
以下是将这些多个图表并排显示的截图:

摘要
在本章中,我们学习了如何使用柱状图和条形图。我们利用它们的选项来实现列和条形的各种展示配置,以便于比较数据集。我们还学习了不同图表外观的高级配置,例如镜像和仪表图。
在下一章中,我们将探讨饼图系列。
第五章。饼图
在本章中,你将学习如何绘制饼图并探索它们的各种选项。然后我们将检查如何在图表中放置多个饼。之后,我们将找出如何创建环形图。然后我们将通过绘制包含我们迄今为止所学的所有系列类型的图表来结束本章——柱状图、折线图和饼图。在本章中,我们将涵盖以下主题:
-
理解图表、饼图和系列之间的关系
-
绘制简单的饼图 – 单个系列
-
在图表中绘制多个饼 – 多个系列
-
准备环形图 – 多个系列
-
使用多种系列类型构建图表
-
理解
startAngle和endAngle选项 -
创建简化版的股票选择轮图表
理解图表、饼图和系列之间的关系
饼图绘制简单:它们没有轴需要配置,只需要带有类别的数据。通常,术语饼图指的是只有一个饼系列图表。在 Highcharts 中,一个图表可以处理多个饼系列。在这种情况下,一个图表可以显示多个饼:每个饼都与一系列数据相关联。而不是显示多个饼,Highcharts 可以显示一个环形图,它基本上是一个有多个同心圆叠加的饼图。每个同心圆是一个饼系列,类似于堆叠饼图。我们首先将学习如何绘制单个饼的图表,然后在本章后面我们将探讨在单独的饼和环形图中使用多个饼系列进行绘制。
绘制简单的饼图 – 单个系列
在本章中,我们将使用由vgchartz提供的视频游戏数据(www.vgchartz.com)。以下是一个饼图配置,数据是根据出版商基于 2011 年销量前 100 的游戏销售数量。Wii Sports 被从数据集中去除,因为它与 Wii 游戏机捆绑免费:
chart: {
renderTo: 'container',
type: 'pie',
borderWidth: 1,
borderRadius: 5
},
title: {
text: 'Number of Software Games Sold in 2011 Grouped by Publishers',
},
credits: {
...
},
series: [{
data: [ [ 'Nintendo', 54030288 ],
[ 'Electronic Arts', 31367739 ],
... ]
}]
这里是一个简单的饼图截图,第一个数据点(任天堂)从 12 点钟位置开始。除非我们使用startAngle选项指定不同的起始点,否则第一片总是从 12 点钟位置开始,我们将在本章后面探讨这一点。

配置带有切割部分的饼图
我们可以将之前的饼图改进,包括标签中的值和换行一些出版商的长名称。以下是为饼系列配置的代码。allowPointSelect选项允许用户通过点击数据点与图表进行交互。至于饼系列,这用于从饼图中切割掉一个部分(见以下截图)。slicedOffset选项用于调整从饼图中切割部分的距离。对于换行标签,我们将标签样式dataLabels.style.width设置为 140 像素宽:
plotOptions: {
pie: {
slicedOffset: 20,
allowPointSelect: true,
dataLabels: {
style: {
width: '140px'
},
formatter: function() {
var str = this.point.name + ': ' +
Highcharts.numberFormat(this.y, 0);
return str;
}
}
}
},
此外,我们希望在初始显示中切掉最大的部分;其标签以粗体字体显示。为此,我们需要将最大的数据点转换为对象配置,如下面的截图所示。然后我们将sliced属性放入对象中,并将默认值false更改为true,这迫使切片从中心切掉。此外,我们通过将fontWeight选项分配给dataLabels来覆盖默认设置:
series: [{
data: [ {
name: 'Nintendo',
y: 54030288,
sliced: true,
dataLabels: {
style: {
fontWeight: 'bold'
}
}
}, [ 'Electronic Arts', 31367739 ],
[ 'Activision', 30230170 ], .... ]
}]
下面的图表显示了经过精炼的标签:

如前所述,slicedOffset选项也将切掉的部分推得更远,默认距离为 10 像素。slicedOffset选项适用于所有切掉的部分,这意味着我们无法控制单独分开的部分的距离。还值得注意的是,由于这个原因,连接线(切片和数据标签之间的线条)变得弯曲。在下一个示例中,我们演示了sliced属性可以应用于我们想要的任意多个数据点,并移除slicedOffset选项以恢复默认设置以显示差异。以下图表通过重复数据对象设置(Nintendo)为其他两个点,展示了三个分开的切片:

注意到连接线又变回了平滑的线条。然而,对于sliced选项,还有一个有趣的行为。对于默认设置为false的sliced选项的切片,只有一个可以被切掉。例如,用户点击其他部分,它会从图表中移开。然后,点击Activision将切片切掉,其他部分会向中心移动,而三个配置为sliced: true的部分保持它们分开的位置。换句话说,将sliced选项设置为true,这使得它的状态可以独立于设置为false的其他部分。
将图例应用到饼图中
到目前为止,图表包含大量数字,很难真正理解一个部分比另一个部分大多少。我们可以打印所有标签的百分比。让我们将所有出版商名称放入图例框中,并在每个切片中打印百分比值。
绘图配置重新定义为以下内容。要启用图例框,我们将showInLegend设置为true。然后,我们将数据标签的字体颜色和样式分别设置为粗体和白色,并稍微修改formatter函数以使用仅适用于饼图系列的this.percentage变量。distance选项是数据标签与饼图外边缘之间的距离。正值将数据标签移出边缘,负值将数据标签移向相反方向:
plotOptions: {
pie: {
showInLegend: true,
dataLabels: {
distance: -24,
color: 'white',
style: {
fontWeight: 'bold'
},
formatter: function() {
return Highcharts.numberFormat(this.percentage) + '%';
}
}
}
},
然后,对于图例框,由于图例项较多,我们添加了一些填充,并将图例框设置得更靠近饼图,如下所示:
legend: {
align: 'right',
layout: 'vertical',
verticalAlign: 'middle',
itemMarginBottom: 4,
itemMarginTop: 4,
x: -40
},
以下是对图表的另一种展示:

在图表中绘制多个饼图 – 多个系列
使用饼图,我们可以通过并排显示另一个饼图来比较数据,从而提供更多信息。这可以通过在系列数组中简单地指定两个系列配置来完成。
我们可以继续使用左侧图表的先前示例,并从相同的数据集中创建一个新的类别系列,但这次是按平台分组。以下是这样做的系列配置:
plotOptions:{
pie: {
....,
size: '75%'
}
},
series: [{
center: [ '25%', '50%' ],
data: [ [ 'Nintendo', 54030288 ],
[ 'Electronic Arts', 31367739 ],
.... ]
}, {
center: [ '75%', '50%' ],
dataLabels: {
formatter: function() {
var str = this.point.name + ': ' + Highcharts.numberFormat(this.percentage, 0) + '%';
return str;
}
},
data: [ [ 'Xbox', 80627548 ],
[ 'PS3', 64788830 ],
.... ]
}]
如我们所见,我们使用了一个新的选项center来定位饼图。该选项包含一个包含两个百分比值的数组——第一个是 x 位置与整个容器宽度的比例,而第二个百分比值是 y 比例。默认值是['50%', '50%'],位于容器中间。在这个例子中,我们将第一个百分比值指定为'25%'和'75%',分别位于左右两半的中间。我们使用plotOptions.pie.size将饼图系列的大小设置为 75%,这确保了两个饼图大小相同,否则左侧的饼图将显得更小。
在第二系列中,我们将选择显示带有百分比数据标签的饼图,而不是单位值。以下是一个双饼图的截图:

表面上看,这与在单个<div>标签中绘制两个独立的饼图没有太大区别,除了共享相同的标题。主要好处是我们可以在同一图表下结合不同的系列类型展示。例如,假设我们想在多个列系列组上方直接展示饼图系列的比例分布。我们将在本章后面学习如何做到这一点。
准备环形图 – 多个系列
Highcharts 提供了另一种类型的饼图,即环形图。它具有钻取类别以创建子类别的效果,是查看更详细数据的便捷方式。这种钻取效果可以应用于多个级别。在本节中,我们将创建一个简单的环形图,它有一个与内部类别(发行商)对齐的外环子类别(游戏标题)。
为了简化,我们只将使用前三个游戏发行商作为内部饼图。以下是环形图的系列数组配置:
series: [{
name: 'Publishers',
dataLabels : {
distance: -70,
color: 'white',
formatter: function() {
return this.point.name + ':<br/> ' +
Highcharts.numberFormat(this.y / 1000000, 2);
},
style: {
fontWeight: 'bold'
}
},
data: [ [ 'Nintendo', 54030288 ],
[ 'Electronic Arts', 31367739 ],
[ 'Activision', 30230170 ] ]
}, {
name: 'Titles',
innerSize: '60%',
dataLabels: {
formatter: function() {
var str = '<b>' + this.point.name + '</b>: ' +
Highcharts.numberFormat(this.y / 1000000, 2);
return str;
}
},
data: [ // Nintendo
{ name: 'Pokemon B&W', y: 8541422,
color: colorBrightness("#4572A7",
0.05) },
{ name: 'Mario Kart', y: 5349103,
color: colorBrightness('#4572A7',
0.1) },
....
// EA
{ name: 'Battlefield 3', y: 11178806,
color: colorBrightness('#AA4643',
0.05) },
....
// Activision
{ name: 'COD: Modern Warfare 3',
y: 23981182,
color: colorBrightness('#89A54E',
0.1) },
....
}]
}]
首先,我们有两个系列——内部饼图系列,或出版商,以及外部环系列,或标题。标题系列包含所有子类别的数据,并且与出版商系列对齐。顺序是这样的,任天堂类别的子类别值在艺电的子类别数据之前,依此类推(参见标题系列中的数据数组顺序)。
子类别系列中的每个数据点都被声明为一个数据点对象,以便将颜色分配到与它们的主要类别相似的范围。这可以通过遵循 Highcharts 演示来调整颜色亮度:
color: Highcharts.Color(color).brighten(brightness).get()
基本上,这个功能是使用主要类别的颜色值来创建一个Color对象,然后通过亮度参数调整颜色代码。这个参数是从子类别值的比率中得出的。我们将这个示例重写成一个名为colorBrightness的函数,并在图表配置中调用它:
function colorBrightness(color, brightness) {
return
Highcharts.Color(color).brighten(brightness).get();
}
下一个部分是指定哪个系列进入内部饼图,哪个进入外部环。外部系列使用innerSize选项,标题创建一个内部圆圈。因此,标题系列形成一个环形的饼图。innerSize选项的值可以是像素或绘图区域大小的百分比值。
最后的部分是用数据标签装饰图表。显然,我们希望将内部图表的数据标签定位在内部饼图的上方,因此我们将dataLabels.distance选项赋值为负值。我们不是打印长值,而是定义formatter将它们转换为百万单位。
下面的显示是饼图的展示:

注意,在饼图中心放置饼图不是强制性的。这只是本例的展示风格。我们可以有多个同心环。下面的图表与前面提到的示例完全相同,只是为出版商的内部系列添加了innerSize选项:

我们甚至可以通过引入第三个系列来进一步复杂化饼图。我们在三个层次上绘制以下图表。代码简单地从示例中扩展了另一个系列并包含了更多数据。源代码和演示可在joekuan.org/Learning_Highcharts/找到。两个外部系列使用innerSize选项。由于内部饼图将变得甚至更小,并且没有足够的空间放置标签,因此我们通过showInLegend选项启用最内部系列的图例框。

构建多系列类型的图表
到目前为止,我们已经学习了线形、柱形和饼图系列类型。现在是时候将这些不同的系列展示方式汇总到一个图表中。在本节中,我们将使用 2008 年至 2011 年的年度数据来绘制三种不同类型的系列:柱形、线形和饼图。柱形类型表示每种游戏机每年的销量。饼图系列显示了每个供应商每年销售的年度游戏机数量。最后一个是样条图系列,它揭示了每年所有游戏机总共发布了多少新游戏标题。
为了确保整个图表使用相同的颜色方案来表示每种游戏机类型,我们必须手动为饼图和柱形系列中的每个数据点分配一个颜色代码:
var wiiColor = '#BBBBBB',
x360Color = '#89A54E',
ps3Color = '#4572A7',
splineColor = '#FF66CC';
然后,我们以更酷的方式装饰图表。首先,我们给图表添加一个深色背景和颜色渐变:
var chart = new Highcharts.Chart({
chart: {
renderTo: 'container',
borderWidth: 1,
spacingTop: 40,
backgroundColor: {
linearGradient: { x1: 0, y1: 0,
x2: 0, y2: 1 },
stops: [ [ 0, '#0A0A0A' ],
[ 1, '#303030' ] ]
}
},
subtitle: {
floating: true,
text: 'Number of consoles sold (in millions)',
y: -5
},
然后,我们需要将柱形向右移动,以便为(稍后描述的)要放置在右上角的照片留出足够的空间:
xAxis: {
minPadding: 0.2,
tickInterval: 1,
labels: {
formatter: function() {
return this.value;
},
style: {
color: '#CFCFCF'
}
}
},
下一个任务是为饼图留出足够的空间,以便将它们放置在柱形上方。这可以通过在两个 y 轴上引入maxPadding选项来实现:
yAxis: [{
title: {
text: 'Number of games sold',
align: 'low',
style: {
color: '#CFCFCF'
}
},
labels: {
style: {
color: '#CFCFCF'
}
},
maxPadding: 0.5
}, {
title: {
text: 'Number of games released',
style: {
color: splineColor
}
},
labels: {
style: {
color: splineColor
}
},
maxPadding: 0.5,
opposite: true
}],
每个饼图系列单独显示,并与其对应的柱形顶部以及年份类别对齐。这是通过调整系列数组中饼图的center选项来实现的。我们还想减小饼图系列的显示尺寸,因为图表中还有其他类型的系列需要共享。我们将使用size选项并设置百分比值。百分比值是饼图系列的直径与绘图区域大小的比例:
series:[{
type: 'pie',
name: 'Hardware 2011',
size: '25%',
center: [ '88%', '5%' ],
data: [{ name: 'PS3', y: 14128407,
color: ps3Color },
{ name: 'X360', y: 13808365,
color: x360Color },
{ name: 'Wii', y: 11567105,
color: wiiColor } ],
.....
样条图系列被定义为对应于相反的 y 轴。为了使系列与第二个轴明显关联,我们应用相同的颜色方案来绘制线条、轴标题和标签:
{ name: "Game released",
type: 'spline',
showInLegend: false,
lineWidth: 3,
yAxis: 1,
color: splineColor,
pointStart: 2008,
pointInterval: 1,
data: [ 1170, 2076, 1551, 1378 ]
},
我们使用renderer.image方法将图像插入到图表中,并确保图像具有更高的zIndex,这样轴线就不会位于图像的顶部。我们不是包含一个 PNG 图像,而是使用 SVG 图像。这样,图像在图表缩放时保持清晰,避免了像素化效果:
chart.renderer.image('./pacman.svg', 0,
0, 200, 200).attr({
'zIndex': 10
}).add();
以下是在图表中添加了 Pac-Man SVG 图像的最终图形,以给图表增添游戏主题:

创建股票选择轮
股票选择轮图表是由投资者智能([www.investorsintelligence.co.uk/wheel/](http://www.investorsintelligence.co.uk/wheel/))设计的一种金融图表,该图表提供了一种交互式的方式来可视化市场指数中主要股票的整体和详细表现。以下是该图表的截图:

基本上,这是一个开口的环形图。每个切片代表一家蓝筹股公司,所有切片的宽度都相等。根据股价表现分数,切片被分为不同的颜色带(从红色到橙色、绿色和蓝色)。当用户将鼠标悬停在某个公司名称上时,环形图缺口处会出现一个更大的切片,并显示该公司的详细业绩信息。尽管这个令人印象深刻的图表是用 Adobe Flash 实现的,但我们还是想尝试看看 Highcharts 是否能在本节中产生一个类似的外观。我们将专注于环形图,并将中间图表留作你稍后的练习。
理解 startAngle 和 endAngle
在我们开始绘制股票选择轮图表之前,我们需要知道如何创建一个开口的环形图。startAngle 和 endAngle 选项可以使饼图在任何大小和方向上分裂。然而,在绘制实际图表之前,我们需要熟悉这些选项。默认情况下,第一个切片的起始位置在 12 点钟,这被认为是 startAngle 选项的 0 度。startAngle 选项从原点开始,顺时针移动到 endAngle。让我们制作一个简单的饼图,将 startAngle 和 endAngle 分别设置为 30 和 270:
plotOptions: {
pie: {
startAngle: 30,
endAngle: 270
}
},
上述代码生成了以下图表。请注意,尽管系列数据的饼图面积变小了,但数据仍然会按比例调整:

为了在饼图的右侧显示一个间隙,endAngle 选项需要稍作不同的处理。为了使 endAngle 选项最终位于 startAngle 选项之上,即 endAngle 选项绕过原点并在 0 度处通过,endAngle 的值必须超过 360,而不是再次从 0 度开始。让我们将 startAngle 和 endAngle 的值分别设置为 120 和 420,并将 innerSize 值增加以形成一个环形图。以下是新的配置:
plotOptions: {
pie: {
startAngle: 120,
endAngle: 420,
innerSize: 110,
....
}
}
我们可以看到 endAngle 选项超过了原点,并在 startAngle 选项之上结束。请看以下截图:

创建股票符号的切片
到目前为止,我们已经成功地创建了股票选择轮图表的一般形状。下一个任务是给每个切片填充等大小的财务数据标签。而不是像原始图表那样使用 FTSE 指数的一百只股票,我们将使用道琼斯工业平均指数,它由 30 只主要股票组成。首先,我们生成一个按百分比变化降序排列的股票符号数据数组:
var data = [{
name: 'MCD 0.96%',
y: 1,
dataLabels: { rotation: 35 },
color: colorBrightness('#365D97', ratio1)
}, {
name: 'HD 0.86%',
y: 1,
dataLabels: { rotation: 45 },
color: colorBrightness('#365D97', ratio2)
}, {
....
}]
我们将每个份额符号的 y 轴值设置为 1,以便使所有切片的大小相同。然后,我们根据这些股份的百分比变化与整体最大值和最小值之间的比率来评估正负渐变色变化。然后,我们将这个比率值应用到我们之前讨论过的colorBrightness方法上,以实现渐变效果。
至于标签旋转,因为我们事先知道每个份额符号的位置和应该应用的旋转角度,我们可以根据公司的份额符号位置从startAngle计算标签旋转。dataLabel的朝向与startAngle不同,它以 3 点钟位置为 0 度(因为我们水平阅读文本)。以startAngle为 120 度,我们可以轻松计算出第一个切片的dataLabel旋转为 30 度(我们再增加 5 度以便dataLabel位于切片的中间),每个切片比前一个切片多旋转 10 度。以下说明了这种计算的逻辑:

我们接下来的任务是移除连接线并将默认的dataLabel位置(位于切片外部)移动到饼图的中心。我们通过将connectorWidth值设置为零并将distance选项设置为负值来实现这一点,这样就可以将dataLabel拖向图表的中心,使得标签位于切片内部。以下是plotOptions.pie配置:
plotOptions: {
pie: {
size: '100%',
startAngle: 120,
endAngle: 420,
innerSize: 110,
dataLabels: {
connectorWidth: 0,
distance: -40,
color: 'white',
y: 15
}
},
以下截图显示了旋转标签以实现股票选择轮效果的切片方向:

使用 Highcharts 的渲染器创建形状
这个实验最后缺失的部分是在用户将指针悬停在切片上时显示一个显示份额符号详细信息的弧线。为了做到这一点,我们使用了 Highcharts 渲染器引擎,它提供了一套 API 来在图表中绘制各种形状和文本。在这个练习中,我们调用arc方法来创建一个动态切片并将其定位在间隙内。
由于这是由用户操作触发的,因此该过程必须在事件处理程序内部执行。以下是事件处理程序代码:
var mouseOver = function(event) {
// 'this' keyword in this handler represents
// the triggered object, i.e. the data point
// being hovered over
var point = this;
// retrieve the renderer object from the
// chart hierarchy
var renderer = point.series.chart.renderer;
// Initialise the SVG elements group
group = renderer.g().add();
// Calculate the x and y coordinate of the
// pie center from the plot dimension
var arcX = c.plotWidth/2 - c.plotLeft;
var arcY = c.plotHeight/2 + c.plotTop;
// Create and display the SVG arc with the
// same color hovered slice and add the arc
// into the group
detailArc =
renderer.arc(arcX, arcY, 230, 70, -0.3, 0.3)
.css({
fill: point.options.color
}).add(group);
// Create and display the text box with stock
// detail and add into the group
detailBox = renderer.text(sharePrice,
arcX + 80, arcY - 5)
.css({
color: 'white',
fontWeight: 'bold',
fontSize: '15px'
}).add(group);
};
plotOptions: {
pie: {
....
point: {
events: {
mouseOver: mouseOver,
mouseOut: function(event) {
group && (group = group.destroy());
}
}
在鼠标悬停事件中,我们首先根据 Highcharts API 文档从处理程序内部的'this'关键字中提取触发数据点对象。从那里,我们可以向下传播以恢复renderer对象。然后,我们使用渲染器的g()调用创建一个组对象,然后调用该对象的方法add将其插入到图表中。这个组处理对象使我们能够后来将多个 SVG 元素组合成一个组,以便更容易处理。
我们随后调用弧线方法来生成一个带有图表的 x 和 y 中心位置的切片,该位置是通过评估图表的绘图维度得到的。我们还指定了innerRadius参数,并赋予一个非零值(70),以便使弧线呈现出甜甜圈图表的切片效果。然后,我们使用css将调用与点对象相同的颜色链式调用,并将add方法链接到组上。接着,我们在新弧线内部创建一个包含股票详情的文本框;调用以相同的方式通过css和add链接到同一个组。
对于鼠标移出事件,我们可以利用组对象。我们不需要逐个删除每个元素,只需在组对象上调用destroy方法即可。以下截图显示了新切片的显示效果:

因此,我们创建了一个类似 Highcharts 的股票选择轮,这证明了 Highcharts 的多功能性。然而,为了简化,这个练习省略了一些复杂性,这影响了最终的外观。实际上,如果我们足够努力地使用 Highcharts,我们可以完成一个看起来甚至更接近原始图表的图表。以下是一个这样的例子,其中每个切片都有几个颜色带和颜色渐变:

在线演示可以在joekuan.org/demos/ftse_donut找到。
摘要
在本章中,我们学习了如何绘制饼图及其变体甜甜圈图。我们还绘制了一个包含我们迄今为止所学所有系列类型的图表。
在下一章中,我们将探讨负责创建动态图表的 Highcharts API,例如使用 AJAX 查询更新图表内容、访问 Highcharts 对象中的组件、将图表导出为 SVG 等等。
第六章。量表、极坐标和范围图表
在本章中,我们将逐步学习如何创建量表图表。量表图表与其他 Highcharts 图表非常不同。我们将通过绘制类似于双表盘菲亚特 500 速度表的图表来探索新设置。之后,我们将回顾极坐标图表的结构及其与其他图表的相似性。然后,我们将继续探讨如何通过使用前一章的示例来创建范围图表。最后,我们将使用量表图表逐步调整径向渐变以达到预期效果。在本章中,我们将涵盖以下主题:
-
绘制速度表量表图表
-
展示一个简单的实心量表图表
-
将折线图转换为极坐标/雷达图
-
使用市场指数数据绘制范围图表
-
在量表图中使用径向渐变
加载量表、极坐标和范围图表
为了使用任何量表、极坐标和范围类型图表,我们首先需要包含一个额外的文件,即包中提供的highcharts-more.js:
<script type="text/javascript"
src="img/highcharts.js"></script>
<script type="text/javascript"
src="img/highcharts-more.js"></script>
绘制速度表量表图表
量表图与其他 Highcharts 图表的结构非常不同。例如,量表图的核心是由一个面板组成的。面板是一个圆形绘图区域,用于布局图表内容。我们可以调整面板的大小、位置和背景。一旦面板布局完成,我们就可以在上面放置轴。Highcharts 支持图表内的多个面板,因此我们可以在图表内显示多个量表(即多个量表系列)。量表系列由两个特定组件组成——一个枢轴和一个表盘。
在量表图中,另一个显著的区别是系列实际上是单维数据:一个单一值。因此,这种图表中只有一个轴,即 y 轴。yAxis属性的使用方式与其他系列类型图表相同,可以是linear、datetime或logarithmic刻度,并且它也响应tickInterval选项等。
绘制双表盘图表——菲亚特 500 速度表
到目前为止,我们已经提到了构成量表图表的所有部分及其关系。有许多可用的选项可以与量表图表一起使用。为了充分利用它们,我们将逐步学习如何按照菲亚特 500 速度表的设计构建一个复杂的量表图表,如下所示:

速度表是由两个叠在一起的表盘组装而成的。外层表盘有两个轴——英里/小时和千米/小时。内层表盘是转速表,具有不同的刻度和样式。另一个不常见的特性是,两个表盘的机身部分都隐藏在下面:只显示顶部指针部分。量表中心有一个 LED 屏幕显示行程信息。尽管所有这些独特特性,Highcharts 仍然提供了足够的灵活性来组装一个看起来非常相似的图表。
绘制仪表图面板
首先,让我们看看在 Highcharts 中面板的作用。为了做到这一点,我们应该先从构建一个单独的表盘速度表开始。以下是一个带有单个面板和单个轴的仪表图的图表配置代码:
chart: {
renderTo: 'container'
},
title: { text: 'Fiat 500 Speedometer' },
pane: [{
startAngle: -120,
endAngle: 120,
size: 300,
backgroundColor: '#E4E3DF'
}],
yAxis: [{
min: 0,
max: 140,
labels: {
rotation: 'auto'
}
}],
series: [{
type: 'gauge',
data: [ 0 ]
}]
上述代码片段生成了以下仪表图:

目前,这看起来根本不像菲亚特 500 的速度表,但我们将看到图表逐渐演变。配置声明了一个从-120 度到 120 度的圆形绘图区域的面板,y 轴水平放置,而 0 度位于 12 点钟位置。rotation选项通常采用数值度值;'auto'是特殊关键字,用于启用 y 轴标签自动旋转,以便与面板角度对齐。表盘下面的这个小方块是默认的数据标签,显示系列中的当前值。
设置面板背景
仪表图支持比单一背景颜色更高级的背景设置,正如我们在上一个示例中看到的那样。相反,我们可以在接受不同背景设置数组的pane选项内部指定另一个属性background。每个设置都可以声明为一个内环,其中定义了innerRadius和outerRadius值,或者作为一个只有outerRadius选项的圆形背景。两种选项都分配了相对于面板大小的百分比值。在这里,我们将多个背景设置到面板中,如下所示:
chart: {
type: 'gauge',
....
},
title: { .... },
series: [{
name: 'Speed',
data: [ 0 ],
dial: { backgroundColor: '#FA3421' }
}],
pane: [{
startAngle: -120,
endAngle: 120,
size: 300,
background: [{
backgroundColor: {
radialGradient: {
cx: 0.5,
cy: 0.6,
r: 1.0
},
stops: [
[0.3, '#A7A9A4'],
[0.45, '#DDD'],
[0.7, '#EBEDEA'],
]
},
innerRadius: '72%',
outerRadius: '105%'
}, {
// BG color in between speed and rpm
backgroundColor: '#38392F',
outerRadius: '72%',
innerRadius: '67%'
}, {
// BG color for rpm
.....
}]
如我们所见,定义了几个背景,包括内仪表的背景:转速表盘。其中一些背景是环形,最后一个是一个圆形背景。此外,针的颜色series.dial.backgroundColor默认设置为黑色。我们最初将表盘颜色设置为红色,这样我们仍然可以看到黑色背景下的针,这也更接近现实生活中的例子。在本节稍后,我们将探讨塑造和着色表盘和枢轴的细节。至于具有radialGradient功能的背景,我们将在本章稍后进行考察。

管理不同刻度的轴
下一个任务是为 km/h 刻度创建一个次要的y轴,我们将把这个新轴设置在当前显示轴的下方。我们按照以下方式插入新的轴配置:
yAxis: [{
min: 0,
max: 140,
labels: {
rotation: 'auto'
}
}, {
min: 0,
max: 220,
tickPosition: 'outside',
minorTickPosition: 'outside',
offset: -40,
labels: {
distance: 5,
rotation: 'auto'
}
}],
新轴的刻度范围从0到220,我们使用具有负值的offset选项,这使轴的线条向面板中心推进。此外,tickPosition和minorTickPosition都设置为'outside'。这改变了间隔刻度的方向,与默认设置相反。现在两个轴都面向对方,这使得它们类似于以下图示:

然而,出现了一个问题,因为顶部轴的刻度已经被打乱:它不再在 0 到 140 之间。这是因为次要轴的默认行为是对多个轴之间的间隔进行对齐。为了解决这个问题,我们必须将chart.alignTicks选项设置为false。之后,问题得到解决,两个轴都按照预期布局:

扩展到多个窗格
由于量规由两个刻度盘组成,我们需要为第二个刻度盘添加一个额外的窗格。以下是对窗格的配置:
pane: [{
// First pane for speed dial
startAngle: -120,
endAngle: 120,
size: 300,
background: [{
backgroundColor: {
radialGradient: {
.....
}]
}, {
// Second pane for rpm dial
startAngle: -120,
endAngle: 120,
size: 200
}]
第二个窗格的绘图区域从与第一个窗格相同的角度开始和结束,但尺寸更小。由于我们没有使用center选项在图表内定位任何窗格,内窗格自动放置在外窗格的中心。下一步是创建另一个轴,转速轴,它在 4.5 和 6 之间的值之间有一个红色区域标记。然后,我们将所有轴绑定到它们的窗格,如下所示:
yAxis: [{
// axis for rpm - pane 1
min: 0,
max: 6,
labels: {
rotation: 'auto',
formatter: function() {
if (this.value >= 4.5) {
return '<span style="color:' +
'#A41E09">' + this.value +
"</span>";
}
return this.value;
}
},
plotBands: [{
from: 4.5,
to: 6,
color: '#A41E09',
innerRadius: '94%'
}],
pane: 1
}, {
// axis for mph - pane 0
min: 0,
max: 140,
.....
pane: 0
}, {
// axis for km/h - pane 0
min: 0,
max: 220,
....
pane: 0
}]
对于转速轴,我们使用labels.formatter来标记高转速区域的字体颜色,并为轴创建一个绘图带。innerRadius选项用于控制红色区域看起来有多厚。下一个任务是创建一个新的量规系列,即新窗格的第二个刻度盘。由于图表包含两个不同的刻度盘,我们需要使刻度盘的运动相对于一个轴,因此我们将yAxis选项分配给绑定系列到轴。此外,我们将新系列的初始值设置为4,只是为了演示两个刻度盘是如何构建的,而不是相互叠加,如下所示:
series: [{
type: 'gauge',
name: 'Speed',
data: [ 0 ],
yAxis: 0
}, {
type: 'gauge',
name: 'RPM',
data: [ 4 ],
yAxis: 2
}]
在所有这些额外更改之后,内部刻度盘的新外观如下:

在下一部分,我们将学习如何设置刻度针的外观和感觉。
量规系列 – 刻度盘和枢轴
有一些特定于量规系列的属性,即plotOptions.gauge.dial和plotOptions.gauge.pivot。dial选项控制针本身的外观和感觉,而pivot是连接到刻度盘的量规中心的小圆圈对象。
首先,我们想要改变刻度盘的颜色和厚度,如下所示:
series: [{
type: 'gauge',
name: 'Speed',
....
dial: {
backgroundColor: '#FA3421',
baseLength: '90%',
baseWidth: 7,
topWidth: 3,
borderColor: '#B17964',
borderWidth: 1
}
}, {
type: 'gauge',
name: 'RPM',
....
dial: {
backgroundColor: '#FA3421',
baseLength: '90%',
baseWidth: 7,
topWidth: 3,
borderColor: '#631210',
borderWidth: 1
}
}]
上述代码片段产生以下输出:

首先,我们通过将baseWidth选项设置为针尖宽度为7像素,针尖末端为3像素来加宽针。然后,我们不是让针逐渐变细到针尖的末端,而是将baseLength选项设置为'90%',这是针在刻度盘上开始变细到尖点的位置。
如我们所见,表盘仍然不太对劲,因为它们不够长,无法达到轴线条,如前一个屏幕截图所示。其次,其余的表盘主体没有被覆盖。我们可以通过调整rearLength选项来解决这个问题。以下是对系列设置的修正:
series: [{
type: 'gauge',
name: 'Speed',
.....
dial: {
.....
radius: '100%',
rearLength: '-74%'
},
pivot: { radius: 0 }
}, {
type: 'gauge',
name: 'RPM',
.....
dial: {
.....
radius: '100%',
rearLength: '-74%'
},
pivot: { radius: 0 }
}]
技巧在于,我们不像大多数仪表图那样使用正值,而是输入一个负值以产生覆盖效果。最后,我们通过指定radius值为0来移除枢轴。以下是对表盘的最终调整:

用字体和颜色润色图表
下一步是应用轴选项来调整刻度间隔的颜色和大小。轴标签使用来自 Google 网络字体服务的字体(有关 Google 网络字体,请参阅www.google.com/fonts)。然后,我们调整字体大小和颜色,使其与屏幕截图中的显示一致。Google 网络字体提供了众多选择,并且应用它们有简单的说明。以下是将 Squada One 字体嵌入 HTML 文件<head>部分的示例:
<link href='http://fonts.googleapis.com/css?family=Squada One'
rel='stylesheet' type='text/css'>
我们将新导入的字体应用到 y 轴标题和标签上,如下例所示:
yAxis: {
// axis for rpm - pane 1
min: 0,
max: 6,
labels: {
style: {
fontFamily: 'Squada One',
fontSize: 22
....
}
}
这显著提高了仪表的外观,如下所示:

绘制实心仪表图
Highcharts 提供了另一种类型的仪表图,即实心仪表图,其呈现方式不同。图表背景不是表盘,而是填充了另一种颜色以指示水平。以下是从 Highcharts 在线演示中提取的示例:

制作实心仪表图的原理与仪表图相同,包括面板、y 轴和系列数据,但不需要设置表盘。以下是我们第一次尝试:
chart: {
renderTo: 'container',
type: 'solidgauge'
},
title: ....,
pane: {
size: '90%',
background: {
innerRadius: "70%",
outerRadius: "100%"
}
},
// the value axis
yAxis: {
min: 0,
max: 200,
title: {
text: 'Speed'
}
},
series: [{
data: [40],
dataLabels: {
enabled: false
}
}]
我们从指定innerRadius和outerRadius选项的环形面板开始。然后,我们将 y 轴范围分配到背景面板上,并将初始值设置为 40 以显示仪表水平。以下是我们第一次实心仪表的结果:

如我们所见,面板从 12 点钟位置开始和结束,面板的一部分被蓝色填充作为实心仪表表盘。让我们使轴顺时针从 9 点钟到 3 点钟移动,并配置间隔以在颜色仪表上可见。此外,我们将增加间隔的厚度,并且只允许显示第一个和最后一个间隔标签。以下是修改后的配置:
chart: .... ,
title: .... ,
pane: {
startAngle: -90,
endAngle: 90,
....
},
yAxis: {
lineColor: '#8D8D8D',
tickColor: '#8D8D8D',
intervalWidth: 2,
zIndex: 4,
minorTickInterval: null,
tickInterval: 10,
labels: {
step: 20,
y: 16
},
....
},
series: ....
startAngle和endAngle选项是面板上标签的有效起始和结束位置,因此-90 度和 90 度分别是 9 点钟和 3 点钟的位置。接下来,lineColor(外边界上的轴线颜色)和tickColor都应用了较深的颜色,因此通过结合zIndex选项,间隔以不同的仪表颜色级别变得可见。
以下是修改后图表的输出:

接下来,我们移除背景面板的下半部分,留下半圆形形状,并使用阴影(在本章后面详细描述)配置背景。然后,我们将颜色仪表的大小调整为与背景面板相同的宽度。我们提供了一组颜色带,用于不同的速度值,这样 Highcharts 将根据值自动将仪表水平线从一种颜色带切换到另一种颜色带:
chart: ...
title: ...
pane: {
background: {
shape: 'arc',
borderWidth: 2,
borderColor: '#8D8D8D',
backgroundColor: {
radialGradient: {
cx: 0.5,
cy: 0.7,
r: 0.9
},
stops: [
[ 0.3, '#CCC' ],
[ 0.6, '#E8E8E8' ]
]
},
}
},
yAxis: {
... ,
stops: [
[ 0, '#4673ac' ], // blue
[ 0.2, '#79c04f' ], // green
[ 0.4, '#ffcc00'], // yellow
[ 0.6, '#ff6600'], // orange
[ 0.8, '#ff5050' ], // red
[ 1.0, '#cc0000' ]
],
},
plotOptions: {
solidgauge: {
innerRadius: '71%'
}
},
series: [{
data: [100],
}]
shape选项,'arc',将面板转换为弧形。y 轴上的颜色带通过stops选项提供,该选项是一个包含比例(y 轴范围的比率)和颜色值的数组。为了使仪表水平与面板对齐,我们将plotOptions.solidgauge的innerRadius值设置为略小于面板的innerRadius值,这样仪表水平线的移动就不会覆盖面板的内边框。我们将系列值设置为 100 以显示仪表水平线显示不同颜色的如下:

现在我们图表的下半部分有额外的空间,我们可以简单地向下移动图表并使用以下方式装饰它:
pane: {
.... ,
center: [ '50%', '65%' ]
},
plotOptions: {
solidgauge: {
innerRadius: '71%',
dataLabels: {
y: 5,
borderWidth: 0,
useHTML: true
}
}
},
series: [{
data: [100],
dataLabels: {
y: -60,
format: '<div style="text-align:center">' +
'<span style="font-size:35px;color:black">{y}</span><br/>' +
'<span style="font-size:16px;color:silver">km/h</span></div>'
}
}]
我们使用center选项将图表的垂直位置设置为绘图区域高度的 65%。最后,我们启用dataLabels以在 HTML 中以文本装饰的形式显示:

将折线图转换为极坐标/雷达图
极坐标图(或雷达图)通常用于发现数据趋势。它们与线型和柱状图有一些不同。尽管它们可能看起来像饼图,但它们与它们没有任何共同之处。实际上,极坐标图是传统二维图表的圆形表示。为了以另一种方式可视化,它是一个折叠的线或柱状图,以圆形方式放置,x 轴的两端相遇。
再次强调,为了绘制极坐标图,我们需要包含 highcharts-more.js。以下截图展示了极坐标图的结构:

在原则上几乎没有差异,Highcharts 的配置也是如此。让我们使用我们第一个浏览器使用情况图表的例子,在 第一章 网络图表 中,将其转换为雷达图。回忆起浏览器折线图,我们做了以下操作:

要将折线图转换为极坐标图,我们只需将 chart.polar 选项设置为 true,这样就将正交的 x 和 y 坐标转换为极坐标系。为了使新的极坐标图更容易阅读,我们将 x 轴标签的 rotation 值设置为 'auto',如下所示:
chart: {
....,
polar: true
},
....,
xAxis: {
....,
labels: { rotation: 'auto' }
},
以下为折线图的极坐标版本:

如我们所见,极坐标图的一个特点是它以与传统图表不同的方式揭示数据趋势。从顺时针方向看,对于上升趋势(Chrome),数据线“螺旋上升”,而对于下降趋势(Internet Explorer),数据线“螺旋下降”,而 Firefox 的数据线没有太多移动。至于 Safari 和 Opera,这些系列基本上是丢失的,除非我们放大图表容器,这在实际操作中是不切实际的。另一个特点是系列中的最后一个和第一个数据点连接在一起。因此,Firefox 系列显示一个闭合的环,而 Internet Explorer 系列(Chrome 系列没有连接,因为 Chrome 浏览器直到 2008 年晚些时候才发布,所以在系列开头有空值)有一个突然的跳跃。
为了纠正这种行为,我们可以在每个系列数据数组的末尾添加一个空值,以打破连续性,如下面的截图所示:

Highcharts 支持在 y 轴网格线上进行多边形插值,而不是圆形极坐标图。这意味着网格线被拉直,整个图表就像一个蜘蛛网。
为了说明,我们将 x 轴和 y 轴线条的宽度设置为 0,从而从图表中移除了圆形轮廓。然后,我们在 y 轴上设置了一个特殊选项 gridLineInterpolation,将其设置为 polygon。最后,我们将 x 轴的 tickmarkPlacement 选项从默认值 between 改为 'on'。这样,x 轴上的间隔刻度就会与每个类别的开始对齐。以下代码片段总结了我们需要做出的更改:
xAxis: {
categories: [ ..... ],
tickmarkPlacement: 'on',
labels: {
rotation: 'auto',
},
lineWidth: 0,
plotBands: [{
from: 10,
to: 11,
color: '#FF0000'
}]
},
yAxis: {
.....,
gridLineInterpolation: 'polygon',
lineWidth: 0,
min: 0
},
为了演示蜘蛛网形状,我们将从之前的图表中删除大部分数据样本(或者,您也可以放大图表容器以保留所有数据)。我们还将添加一些网格线装饰和一个 x 轴绘图带(11 月 – 12 月),只是为了显示其他轴选项仍然可以应用于极坐标图:

使用市场指数数据绘制范围图
范围图实际上是线形和柱形图,用于展示一系列数据范围。范围类型的系列可以是arearange、areasplinerange和columnrange。这些系列期望在数据选项中有一个包含三个数据点的数组,x,y 最小值,y 最大值,或者在xAxis.cateogries已经指定的情况下,是一个包含 y 最小值和 y 最大值的数组。
让我们用过去的例子来看看我们是否可以改进范围图。在第二章中,Highcharts 配置,我们有一个包含纳斯达克 100 月度数据的五系列图表:开盘价、收盘价、最高价、最低价和成交量,如下面的截图所示:

使用新的范围系列,我们排序系列数据,并将月最高价和月最低价列合并为一个柱形范围系列,将开盘价和最低价列合并为一个区域样条线范围系列,如下所示:
series: [{
type: 'columnrange',
name: 'High & Low',
data: [ [ 2237.73, 2336.04 ],
[ 2285.44, 2403.52 ],
[ 2217.43, 2359.98 ], ...... ]
}, {
type: 'areasplinerange',
name: 'Open & Close',
// This array of data is pre-sorted,
// not in Open, Close order.
data: [ [ 2238.66, 2336.04 ],
[ 2298.37, 2350.99 ],
[ 2338.99, 2359.78 ], ...... ]
}, {
name: 'Volume',
......
下面的截图显示了范围图版本:

新图表看起来更容易阅读,图形也更紧凑。值得注意的是,对于柱形范围系列,必须保持范围在最小值到最大值的顺序。至于区域样条线和区域范围系列类型,我们仍然可以绘制范围系列,即使在没有事先排序的情况下也可以。
例如,高低范围系列必须按照系列名称的自然含义,以最小值和最大值的顺序排列。然而,对于开盘价与收盘价范围系列来说,情况并非如此,因为我们不知道哪一个是开盘价或收盘价。如果我们按照从开盘到收盘的顺序,而不是从 y 最小值到 y 最大值的顺序,来绘制开盘价与收盘价区域范围系列,那么区域范围将显示不同,如下面的截图所示:

如我们所见,区域范围系列中存在扭曲的部分;这些交叉是由数据对中的反向顺序引起的。然而,我们不知道开盘价是否高于收盘价,反之亦然。如果我们只想知道开盘价与收盘价系列之间的范围有多宽,那么前面的区域范围图就达到了目的。通过将它们作为单独的系列,就不会出现这样的问题。总的来说,这是在绘制具有模糊意义的范围系列数据时的微妙差异。
在仪表图上使用径向渐变
径向渐变设置基于 SVG。正如其名所示,径向渐变是以圆形方向向外辐射的颜色阴影。因此,它需要三个属性来定义渐变圆——cx、cy 和 r。渐变圆是阴影的最外层圆,这样阴影就不会超出这个范围。
所有渐变位置都是相对于其包含元素的比例值在零到一之间定义的。cx 和 cy 选项位于最外层圆的 x、y 中心位置,而 r 是最外层圆的半径。如果 r 是 0.5,这意味着渐变半径是其元素直径的一半,与包含面板大小相同。换句话说,渐变从中心开始,一直延伸到仪表的边缘。stop 偏移选项与线性渐变的工作方式相同:第一个参数是渐变圆中停止阴影的比率位置。这控制了颜色之间的阴影强度。间隔越短,颜色之间的对比度越高。
让我们探索如何设置颜色渐变。以下是一个没有颜色渐变的情绪波动检测器:

我们将使用以下设置将径向渐变应用于前面的图表:
background: [{
backgroundColor: {
radialGradient: {
cx: 0.5,
cy: 0.5,
r: 0.5
},
stops: [
[ 0, '#CCD5DE' ],
[ 1, '#002E59' ]
]
}
}]
我们将 cx、cy 和 r 设置为 0.5,以便渐变从中心位置开始阴影,一直延伸到圆的边缘,如下所示:

如我们所见,前面的图表显示了从中心均匀辐射的白色阴影。让我们改变一些参数并看看效果:
backgroundColor: {
radialGradient: {
cx: 0.5,
cy: 0.7,
r: 0.25
},
stops: [
[ 0.15, '#CCD5DE' ],
[ 0.85, '#002E59' ]
]
}
在这里,我们将渐变圆的大小改为仪表大小的一半,并将圆向下移动。明亮的颜色直到达到渐变圆大小的 15% 才开始阴影,因此在中间有一个明显的白色块,阴影在圆的 85% 处停止:

在 SVG 辐射渐变中,还有两个其他选项,fx 和 fy,它们用于设置阴影的焦点位置;它们也被称为内圆设置。
注意
fx 和 fy 选项可能与 Chrome 和 Safari 浏览器不兼容。在撰写本文时,只有 Firefox 和 Internet Explorer 浏览器与这里显示的图像正常工作。
让我们实验一下焦点如何影响阴影效果:
backgroundColor: {
radialGradient: {
cx: 0.5,
cy: 0.7,
r: 0.25,
fx: 0.6,
fy: 1.0
},
stops: [
[ 0.15, '#CCD5DE' ],
[ 0.85, '#002E59' ]
]
}
以下代码片段产生以下输出:

我们可以观察到fx和fy选项将亮色从渐变圆的底部开始,略微向右移动。这使得阴影变得更加有方向性。让我们通过将亮斑位置移动到仪表图表的中心来做出最终的改变。此外,我们将焦点方向与良好标签对齐:
background: [{
backgroundColor: {
radialGradient: {
cx: 0.32,
cy: 0.38,
r: 0.25,
fx: 1.3,
fy: 0.95
},
stops: [
[ 0.1, '#CCD5DE' ],
[ 0.9, '#002E59' ]
]
}]
最后,我们可以通过将亮面移动到我们想要的位置来完成图表,如下所示:

注意
fx和fy选项仅适用于 SVG,而旧版本的 Internet Explorer(8.0 或更早版本)使用 VML 则不支持。
摘要
在本章中,我们学习了仪表、极坐标和范围图表。一个详尽的分步演示展示了如何通过利用大多数仪表选项来绘制一个复杂的速度计。我们还演示了极坐标、柱状图和折线图在原理和配置方面的差异。我们使用范围图表来改进过去章节的示例,并研究它们在图表中引入的细微差异。最后,我们探讨了如何通过逐步调整选项来定义径向渐变。
在下一章中,我们将通过从在线体育图表中重新创建它来学习气泡图表的结构。然后,我们将简要讨论箱线图表的特性,通过将蜘蛛图表中的数据转换而来,接着展示使用误差线图表呈现的 F1 赛车数据。
第七章:气泡图、箱线图和误差线图
在本章中,我们将通过首先研究如何从各种系列选项中确定气泡大小,然后通过复制现实生活中的图表来熟悉它。之后,我们将研究箱线图的结构,并讨论将过度拥挤的蜘蛛图转换为箱线图的论文。我们将使用它作为练习,以熟悉箱线图。最后,我们将转向误差线系列,了解其结构,并将其应用于一些统计数据。
本章假设您对统计学有一些基本知识,例如平均值、百分位数、标准误差和标准差。对于需要复习这些主题的读者,有大量的在线材料涵盖了它们。或者,Deborah J. Rumsey 编著的《统计学傻瓜指南》提供了很好的解释,并涵盖了一些基本图表,如箱线图。
在本章中,我们将涵盖以下主题:
-
气泡大小是如何确定的
-
在逐步复制现实生活中的图表时,气泡图选项
-
在重新绘制蜘蛛图数据时,箱线图结构和系列选项
-
带有现实数据的误差线图
气泡图
气泡系列是散点系列的扩展,其中每个数据点的大小都是可变的。它通常用于在二维图表上显示三维数据,气泡大小反映了 z 值之间的比例。
理解气泡大小是如何确定的
在 Highcharts 中,气泡大小是通过将最小的 z 值关联到 plotOptions.bubble.minSize 选项,将最大的 z 值关联到 plotOptions.bubble.maxSize 来决定的。默认情况下,minSize 被设置为直径 8 像素,而 maxSize 被设置为绘图区域大小的 20%,这是宽度和高度的较小值。
另外还有一个选项,sizeBy,它也会影响气泡大小。sizeBy 选项接受字符串值:'width' 或 'area'。宽度值表示气泡宽度由其在系列中的 z 值比例决定,与 minSize 和 maxSize 范围成比例。至于 'area',气泡的大小通过取 z 值比例的平方根来缩放(有关 'area' 实现的更多描述,请参阅 en.wikipedia.org/wiki/Bubble_chart)。此选项是为那些在比较圆圈大小时有不同的感知的观众设计的。为了演示这个概念,让我们以 Highcharts 在线 API 文档中的 sizeBy 示例 (jsfiddle.net/ZqTTQ/) 为例。以下是示例中的代码片段:
plotOptions: {
series: { minSize: 8, maxSize: 40 }
},
series: [{
data: [ [1, 1, 1], [2, 2, 2],
[3, 3, 3], [4, 4, 4], [5, 5, 5] ],
sizeBy: 'area',
name: 'Size by area'
}, {
data: [ [1, 1, 1], [2, 2, 2],
[3, 3, 3], [4, 4, 4], [5, 5, 5] ],
sizeBy: 'width',
name: 'Size by width'
}]
设置了两组具有不同 sizeBy 方案的气泡。最小和最大气泡大小分别设置为 8 和 40 像素宽。以下是该示例的屏幕输出:

两个系列具有完全相同的 x、y 和 z 值,因此它们在极端值(1 和 5)处具有相同的气泡大小。对于中间值 2 到 4,按面积大小的气泡开始时比按宽度大小的气泡面积大,并且两种方案逐渐缩小到相同的大小。以下是一个表格,显示了每种方法在不同 z 值下的最终大小值,而括号内的关联值是系列中的 z 值比率:
| Z-值 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|
| 按宽度大小(比率) | 8 (0) | 16 (0.25) | 24 (0.5) | 32 (0.75) | 40 (1) |
| 按面积大小(比率) | 8 (0) | 24 (0.5) | 31 (0.71) | 36 (0.87) | 40 (1) |
让我们看看在这两种方法中气泡大小是如何计算的。按宽度大小的比率计算为(Z - Zmin) / (Zmax - Zmin)。因此,对于 z 值 3,比率计算为(3 - 1) / (5 - 1) = 0.5。要评估按面积大小方案的比率,只需取按宽度大小比率的平方根。在这种情况下,对于 z 值 3,计算结果为√0.5 ≈ 0.71。然后,我们将比率值转换为基于minSize和maxSize范围的像素数。按宽度大小的 z 值 3 计算如下:
比率 * (maxSize - minSize) + minSize = 0.5 * (40 - 8) + 8 = 24
重现现实生活中的图表
在本节中,我们将通过复制一个现实生活中的例子(MLB 球员图表:fivethirtyeight.com/datalab/has-mike-trout-already-peaked/)来检查气泡系列选项。以下是一名棒球运动员里程碑的气泡图:

首先,我们有两种方式可以在系列中列出数据点(这些值是从图表的最佳估计中得出的)。传统的方式是一个包含 x、y 和 z 值的数组,其中 x 是本例中从 21 岁开始的年龄值:
series: [{
type: 'bubble',
data: [ [ 21, 100, 1 ],
[ 22, 50, 5 ],
....
或者,我们可以简单地使用pointStart选项作为初始年龄值,并省略其余部分:
series: [{
type: 'bubble',
pointStart: 21,
data: [ [ 100, 1 ],
[ 50, 5 ],
....
然后,我们定义背景颜色、轴标题,并将y轴标题旋转到图表的顶部。以下是我们第一次尝试的结果:

如我们所见,有一些区域并不完全正确。让我们首先修复气泡大小和颜色。与原始图表相比,前一个图表对于较高值有更大的气泡大小,气泡应该是实心红色。我们更新plotOptions.bubble系列如下:
plotOptions: {
bubble: {
minSize: 9,
maxSize: 30,
color: 'red'
}
},
这将气泡大小视角调整为更接近原始图表:

下一步是固定 y 轴范围,因为我们希望它仅在 0 到 100 之间。因此,我们将以下配置应用于yAxis选项:
yAxis: {
endOnTick: false,
startOnTick: false,
labels: {
formatter: function() {
return (this.value === 100) ?
this.value + ' %' : this.value;
}
}
},
通过将endOnTick和startOnTick选项设置为false,我们移除了两端额外的间隔。标签格式化器仅在100间隔处打印%符号。以下图表显示了y轴上的改进:

下一个改进是将x轴移动到零值级别,并将x轴细化成偶数间隔。我们还启用了每个主要间隔的网格线,并增加了轴线的宽度,使其类似于原始图表:
xAxis: {
tickInterval: 2,
offset: -27,
gridLineColor: '#d1d1d1',
gridLineWidth: 1,
lineWidth: 2,
lineColor: '#7E7F7E',
labels: {
y: 28
},
minPadding: 0.04,
maxPadding: 0.15,
title: ....
},
tickInterval属性设置标签间隔为偶数,offset选项将x轴级别向上推,与零值对齐。通过将gridLineWidth选项设置为非零值来启用间隔线。在原始图表中,x轴两端有额外的空间用于数据标签。我们可以通过将minPadding和maxPadding都设置为比例值来实现这一点。通过增加y值的属性,将x轴标签向下推得更远。以下截图显示了改进:

最终的改进是将数据标签放在第一个和倒数第二个数据点旁边。为了为特定点启用数据标签,我们将特定的点值转换为具有dataLabels选项的对象配置,如下所示:
series: {
pointStart: 21,
data: [{
y: 100,
z: 1,
name: 'Alex Rodriguez <br>in Seattle at age 21',
dataLabels: {
enabled: true,
align: 'right',
verticalAlign: 'middle',
format: '{point.name}',
color: 'black',
x: 15
}
}, ....
我们使用name属性作为数据标签内容,并将format选项设置为指向name属性。我们还将标签定位在数据点的右侧,并分配标签颜色。

我们将同样的技巧应用于其他标签;以下是我们的气泡图的最终草案:

剩下的部分是气泡大小图例。不幸的是,Highcharts 目前不提供此类功能。然而,这可以通过使用图表的渲染器引擎来创建圆形和文本标签来完成。我们将把这个作为读者的练习。
从技术角度讲,我们可以用散点系列创建相同的图表,每个数据点在对象配置中指定,并将预先计算的 z 值比例分配给 data.marker.radius 选项。
理解箱线图
箱线图是一种技术图表,它以分布形状来展示数据样本。在我们可以创建箱线图之前,我们需要了解其基本结构和概念。以下图表展示了箱线图的结构:

为了找出百分位数,首先需要对整个数据样本进行排序。基本上,箱线图由顶部和底部须线值、第一(Q1)和第三(Q3)四分位数以及中位数组成。四分位数 Q1 代表 50 百分位数和最小数据之间的中位数。四分位数 Q3 以类似的方式工作,但与最大数据相关。对于完美正态分布的数据,箱线图中的每个部分之间将具有相等的距离。
严格来说,还有其他类型的箱线图,它们在须线百分位数覆盖的程度上有所不同。有些使用 1.5 倍的四分位距的定义,即 1.5 * (Q3 - Q1),或者标准差。目的是隔离异常数据并将它们作为单独的点绘制出来,这些点可以与箱线图一起放入散点数据点中。在这里,我们使用最简单的箱线图形式:最大和最小数据点分别被视为顶部和底部须线。
绘制箱线图
为了创建箱线图,我们需要加载一个额外的库,highcharts-more.js:
<script src="img/highcharts-more.js"></script>
Highcharts 提供了一系列选项来塑造和样式化箱线图系列,例如线宽、样式和颜色,这些在以下代码片段中显示:
plotOptions: {
boxplot: {
lineWidth: 2,
fillColor: '#808080',
medianColor: '#FFFFFF',
medianWidth: 2,
stemColor: "#808080",
stemDashStyle: 'dashdot',
stemWidth: 1,
whiskerColor: '#808080',
whiskerWidth: 2,
whiskerLength: '120%'
}
},
lineWidth 选项是箱线图的整体线宽,而 fillColor 用于箱体内部的颜色。median 选项指的是箱体内的水平中位数线,而 stem 选项用于四分位数和须线之间的线。whiskerLength 选项是对应于四分位数箱体宽度的比例。在这个例子中,我们将增大 whiskerLength 选项以方便可视化,因为图表中包含了许多箱线图。
箱线图的系列数据值以升序数组形式列出,因此从底部到顶部的须线。以下是一个系列数据的示例:
series: [{
type: 'boxplot',
data: [
[16.855, 19.287, 26.537, 31.368, 33.035 ],
[16.139, 18.668, 25.33, 30.632, 32.385 ],
[12.589, 15.536, 23.5495, 28.960, 30.848 ],
[13.395, 16.399, 22.078, 27.013, 29.146 ],
....
]
}]
理解箱线图数据
在我们深入探讨一个真实数据示例之前,值得参考一篇优秀的文章(junkcharts.typepad.com/junk_charts/2014/04/an-overused-chart-why-it-fails-and-how-to-fix-it.html),这篇文章由营销分析及数据可视化专家 Kaiser Fund 撰写,他也是大数据处理方面的书籍作者之一。在文章中,Kaiser 从一部名为 Arctic Death Spiral 的视频(youtu.be/20pjigmWwiw)中提出对蜘蛛图的观察,如下所述:

视频演示了北极海冰体积(每年每个系列每月的数据)以惊人的速度螺旋向中心移动。他辩称,使用蜘蛛图并不能公正地展示数据中的重要信息。为了总结他的论点:
-
在圆形图表中,读者很难理解真实的下降趋势尺度。
-
与圆形运动相比,人类更自然地以水平进展的方式来感知时间序列数据。
-
如果一年内月度数据的波动幅度大于其他年份,我们将有多个线系列相互交叉。结果,我们得到的是一大堆难以理解的图表,就像意大利面一样。
为了解决这个问题,Kaiser 建议箱线图是最好的选择。他不是将 12 个多个系列线挤在一起,而是使用箱线图来表示年度数据分布。每年的 12 个月数据都进行了排序,只有中位数、四分位数和极端值被替换到箱线图中。尽管由于数据较少而丢失了一些细节,但在此情况下,时间趋势的范围和尺度得到了更好的表示。
以下是在 Highcharts 中的最终箱线图展示:

箱线图提示框
由于箱线图系列包含各种值,因此系列有不同的属性名称—low、q1、median、q3、high—来指代它们。以下是一个 tooltip.formatter 的示例:
chart: {
....
},
....,
tooltip: {
formatter: function() {
return "In year: " + this.x + ", <br>" +
"low: " + this.point.low + ", <br>" +
"Q1: " + this.point.q1 + ", <br>" +
"median: " + this.point.median + ", <br>" +
"Q3: " + this.point.q3 + ", <br>" +
"high: " + this.point.high;
}
},
series: [{
....
}]
注意,formatter 应该添加到主选项对象的 tooltip 属性中,而不是 series 对象中。以下是箱线图提示框的样式:

误差线图
误差线图是另一种技术图表,它显示了标准误差,即标准差除以样本大小的平方根。这意味着随着样本大小的增加,样本均值的变化会减小。误差线系列具有与箱线图相似的色彩和样式选项,但仅适用于须和茎:
plotOptions: {
errorbar: {
stemColor: "#808080",
stemDashStyle: 'dashdot',
stemWidth: 2,
whiskerColor: '#808080',
whiskerWidth: 2,
whiskerLength: '20%'
}
},
同样,这也适用于提示框格式化器,其中 low 和 high 指的是误差棒的两侧。至于系列数据选项,它接受一个包含下限和上限值的元组的数组:
series: [{
type: 'column',
data: ....
}, {
name: 'error range',
type: 'errorbar',
data: [
[ 22.76, 23.404 ],
[ 25.316, 29.976 ],
为了展示误差线,我们使用了来自每个赛道的所有车队在www.formula1.com/results/season/2013的 F1 停站时间。我们将每个赛道的平均值绘制在柱状系列中。然后我们计算标准误差并将结果应用于平均值。以下是误差线图表的截图:

注意,当在柱状图中显示误差线系列时,误差线系列必须在系列数组中先于柱状系列指定。否则,误差线的一半会被柱状线遮挡,如下例所示:

摘要
在本章中,我们学习了气泡图以及气泡大小是如何确定的。我们还通过重新创建一个现实生活中的图表来测试了气泡图系列。我们考察了箱线图的原则和结构,并练习了如何将数据样本转换为百分位数数据并将布局转换为箱线图。最后,我们通过使用一些统计数据来研究误差线图。
在下一章中,我们将研究瀑布图、漏斗图、金字塔图和热力图图表的特性,并对其进行实验。
第八章:瀑布图、漏斗图、金字塔图和热力图图
在本章中,我们通过使用简单的销售和会计数据,探索了一些不太常见的图表,例如瀑布图和漏斗图。我们实验了如何将这两个图表与相关信息链接起来。然后,我们继续研究如何绘制金字塔图,并通过模仿一份合适的商业报告中的金融金字塔图来评估 Highcharts 的灵活性。最后,我们以逐步的方法研究热力图图,并使用现实生活中的统计数据调整各种特定选项。在本章中,我们将涵盖以下主题:
-
构建瀑布图
-
制作漏斗图
-
将瀑布图和漏斗图结合使用
-
绘制商业金字塔图
-
探索带有通货膨胀数据的热力图图
-
尝试热力图图的数据类和 nullColor 选项
构建瀑布图
瀑布图是一种柱状图类型,其中正负值沿x轴类别累积。相邻列的起始和结束对齐在同一水平上,列的上升或下降取决于值。它们主要用于在较高层次上展示现金流。
要使用瀑布图,必须包含highcharts-more.js库:
<script type="text/javascript"
src="img/highcharts-more.js"></script>
让我们从简单的瀑布图开始,其中包含一些收入和支出的值。以下系列配置如下:
series: [{
type: 'waterfall',
upColor: Highcharts.getOptions().colors[0],
color: '#E64545',
data: [{
name: 'Product Sales',
y: 63700
}, {
name: 'Renew Contracts',
y: 27000
}, {
name: 'Total Revenue',
isIntermediateSum: true,
color: '#4F5F70'
}, {
name: 'Expenses',
y: -43000
}, {
name: 'Net Profit',
isSum: true,
color: Highcharts.getOptions().colors[1]
}]
}]
首先,我们将系列type指定为waterfall,然后为upColor选项分配一个颜色值(蓝色),这将设置任何正值列的默认颜色。在下一行,我们声明另一个颜色值(红色),这是用于负值列的颜色。数据点条目与普通柱状图相同,除了表示到目前为止的总和的列。这些列使用isIntermediateSum和isSum布尔选项指定,没有y轴值。瀑布图将显示这些为累积列。我们使用另一种颜色指定累积列,因为它们既不是收入也不是支出。
最后,我们需要启用数据标签以显示价值流动,这是瀑布图的主要目的。以下是与数据标签样式和格式相关的plotOptions设置:
plotOptions: {
series: {
borderWidth: 0,
dataLabels: {
enabled: true,
style: {
fontWeight: 'bold'
},
color: 'white',
formatter: function() {
return Highcharts.numberFormat(this.y / 1000, 1, '.') + ' k';
}
}
}
},
下面是瀑布图的截图:

制作水平瀑布图
假设我们需要绘制一个稍微复杂一些的瀑布图,因为我们有更多关于收入、支出和支出计划的图表。此外,我们还需要显示累积收入的多个阶段。与传统瀑布图中的垂直列不同,我们声明了inverted选项来将列切换为水平。由于列更加紧凑,数据标签可能无法放在列内。因此,我们将数据标签放在列的末尾。以下是变更的总结:
chart: {
renderTo: 'container',
inverted: true
},
....,
plotOptions: {
series: {
borderWidth: 0,
dataLabels: {
enabled: true,
inside: false,
style: {
fontWeight: 'bold'
},
color: '#E64545',
formatter: ....
}
}
},
series: [{
type: 'waterfall',
upColor: Highcharts.getOptions().colors[0],
color: '#E64545',
name: 'Q1 Revenue',
data: [{
name: 'Product A Sales',
y: 63700,
dataLabels: {
color: Highcharts.getOptions().colors[0]
}
}, {
name: 'Product B Sales',
y: 33800,
dataLabels: {
color: Highcharts.getOptions().colors[0]
}
}, {
// Product C & Renew Maintenance
....
}, {
name: 'Total Revenue',
isIntermediateSum: true,
color: '#4F5F70',
dataLabels: {
color: '#4F5F70'
}
}, {
name: 'Staff Cost',
y: -83000
}, {
// Staff Cost, Office Rental and Tax
....
}, {
name: 'Net Profit',
isSum: true,
color: Highcharts.getOptions().colors[1],
dataLabels: {
color: Highcharts.getOptions().colors[1]
}
}]
以下是新外观的瀑布图:

构建漏斗图
如其名所示,漏斗图的形状就像漏斗。漏斗图是另一种以递减方式展示数据流动的方式。例如,它可以用来展示从销售线索数量、演示和会议到实际销售的阶段,或者通过考试、面试和接受实际工作邀请的求职者数量。
漏斗图作为一个独立的模块打包,需要包含以下 JavaScript 文件:
<script type="text/javascript" src="img/funnel.js"></script>
在这个漏斗图示例中,我们将从网站访问开始,追踪到产品销售。以下是系列配置:
series: [{
type: 'funnel',
width: '60%',
height: '80%',
neckWidth: '20%',
dataLabels: {
format: '{point.name} - {y}',
color: '#222'
},
title: {
text: "Product from Web Visits to Sales"
},
data: [{
name: 'Website visits',
y: 29844
}, {
name: 'Product downloads',
y: 9891
}, {
....
}]
}]
除了系列类型选项外,Highcharts 简单地以漏斗形状配置绘制一组数据点。我们使用neckWidth选项来控制漏斗狭窄部分的宽度,作为绘图区域的比率。同样适用于width和height选项。以下是说明这一点的屏幕截图:

结合瀑布图和漏斗图
到目前为止,我们已经探讨了瀑布图和漏斗图。这两种图表都已在销售数据中使用。在本节中,我们将结合先前的瀑布图和漏斗图示例,以便当客户点击水平条(比如说产品 B 销售)时,瀑布图将缩放到漏斗图以显示产品的潜在比率。为了实现这一点,我们使用钻取功能(在第二章中描述和演示,Highcharts 配置)。
首先,我们设置一个带有钻取标识符的数据点,例如productB。然后,我们将前一个示例中的整个漏斗系列配置导入钻取选项中,匹配id值。最后,我们将钻取回退按钮移至屏幕底部。最终更改应如下所示:
series: [{
type: 'waterfall',
....
data: [{
....
}, {
name: 'Product B Sales',
y: 33800,
drilldown: 'productB',
....
}]
}],
drilldown: {
drillUpButton: {
position: {
verticalAlign: 'bottom',
align: 'center',
y: -20
}
},
series: {
type: 'funnel',
id: 'productB',
现在,我们有一个完全交互式的图表,从收入分解瀑布图到产品前景漏斗图。以下是一个带有钻取功能的瀑布图:

绘制商业金字塔图
金字塔图是漏斗图的倒数,但形状为金字塔,通常用于表示数据的自上而下的层次排序。由于这是漏斗图模块的一部分,需要funnel.js。
数据条目默认顺序是系列数据数组中的最后一个条目显示在金字塔的顶部。这可以通过将reverse选项切换为 false 来纠正。让我们用一个现实生活中的例子来做这个练习。以下是从 2013 年瑞士信贷全球财富数据手册中摘取的全球财富金字塔图表的图片:

如我们所见,在每一边以及中间都有数据标签,看起来非常时尚。让我们尝试一下重现这个图表:
title: {
text: "The global wealth pyramid",
align: 'left',
x: 0,
style: {
fontSize: '13px',
fontWeight: 'bold'
}
},
credits: {
text: 'Source: James Davies, ....',
position: {
align: 'left',
verticalAlign: 'top',
y: 40,
x: 10
},
style: {
fontSize: '9px'
}
},
series: [{
type: 'pyramid',
width: '70%',
reversed: true,
dataLabels: {
enabled: true,
format: '<b>{point.name}</b> ({point.y:,.1f}%)',
},
data: [{
name: '3,207 m',
y: 68.7,
color: 'rgb(159, 192, 190)'
}, {
name: '1,066 m',
y: 22.9,
color: 'rgb(140, 161, 191)'
}, {
name: '361 m',
y: 7.7,
color: 'rgb(159, 165, 157)'
}, {
name: '32 m',
y: 0.7,
color: 'rgb(24, 52, 101)'
}]
}]
首先,我们将标题和版权信息移动到左上角。然后,我们将颜色和百分比值复制到系列中,这样就得到了以下图表:

与原始图表相比,有两个主要问题:每一层的比例是错误的,数据标签要么缺失,要么位置不正确。正如我们所见,现实是富裕人口实际上比原始图表显示的要小得多,有趣的是只有一个像素。在下一节中,我们将看到我们可以将 Highcharts 推进多远,以制作财务图表。
绘制高级金字塔图表
让我们面对现实。原始图表中的层并没有反映它们的真实百分比,这在之前的图表中已经证明。因此,我们需要调整比例值,使它们与原始图表相似。其次,在金字塔图表中,数据标签的重新定位是有限的。将数据标签移动到金字塔每一层中心的唯一方法是不启用连接线,并逐渐调整plotOptions.pyramid.dataLabels中的distance选项。然而,这仅允许我们每个数据层有一个标签,并且我们只能将相同的定位设置应用于所有标签。
那么,我们如何将额外的数据标签放在每一层的两侧以及金字塔底部的标题呢?答案是(经过数小时的尝试和错误)使用多个 y 轴和plotLines。想法是拥有三个 y 轴,将数据标签放在金字塔的左侧、中心和右侧。然后,我们隐藏所有 y 轴的间隔线和它们的标签。我们将 y 轴标题放置在轴线的底部,不旋转,我们只为左侧(财富)和右侧(总财富)轴旋转。
我们禁用了剩余的 y 轴标题。技巧是使用 x 轴标题代替中心标签(成人数量),因为它位于金字塔的顶部。以下是配置这些轴的代码片段:
chart: {
renderTo: 'container',
// Force axes to show the title
showAxes: true,
....
},
xAxis: {
title: {
text: "Number of adults (percentage of world population)"
},
// Only want the title, not the line
lineWidth: 0
},
yAxis: {
// Left Y-axis
title: {
align: 'low',
text: 'Wealth',
rotation: 0,
x: 30
},
// Don't show the numbers
labels: {
enabled: false
},
gridLineWidth: 0,
min: 0,
max: 100,
reversed: true,
....
}, {
// Center Y-axis
title: {
text: ''
},
// Same setting to hide the labels and lines
....
}, {
// Right Y-axis
opposite: true,
title: {
align: 'low',
text: 'Total Wealth <br>(percent of world)',
rotation: 0,
x: -70
},
// Same setting to hide the labels and lines
....
以下截图展示了金字塔图表底部对齐的标题:

在 Highcharts 中,热力图模块作为 Highmaps 扩展的一部分发布,它可以作为 Highmaps 的一部分使用,也可以作为 Highcharts 的一个模块。为了将热力图作为 Highcharts 模块加载,它包括以下库:
<script src="img/heatmap.js"></script>
首先,月份被绘制在 y 轴上,而 x 轴持有国家名称。因此,我们需要反转图表:
chart: {
renderTo: 'container',
type: 'heatmap',
inverted: true
},
下一步是将 x 轴和 y 轴都设置为具有特定标签的分类轴。然后,我们设置 x 轴没有间隔线。至于 y 轴,我们将标签定位在单元格的顶部,通过将相反的选项设置为 true 并包含一个偏移距离,以确保轴线不会太靠近:
xAxis: {
tickWidth: 0,
// Only show a subset of countries
categories: [
'United States', 'Japan', 'United Kingdom',
'Venezuela', 'Singapore', 'Switzerland', 'China'
]
},
yAxis: {
title: { text: null },
opposite: true,
offset: 8,
categories: [ 'Aug 2010', 'Sept', 'Oct', 'Nov', 'Dec', ...
我们还需要配置一个额外的轴,即colorAxis(参见api.highcharts.com/highmaps),这是热力图图表和 Highmaps 特有的。colorAxis选项类似于x/yAxis,与它共享许多选项。主要区别在于colorAxis是在颜色与值之间的映射定义。有两种方式来定义颜色映射:离散和线性颜色范围。
在本例中,我们演示了如何定义多个线性颜色范围。正如我们所见,通货膨胀示例具有非对称的颜色刻度,因此范围从-6.6%到 36%,但请注意,-0.1%到 0.1%之间的颜色是灰色。为了更接近地模仿颜色刻度,我们使用stops选项来定义离散光谱的片段。stops选项接受一个包含比例范围和颜色的元组数组,我们将通货膨胀和颜色值从示例转换为多个比例(我们选择从-1%到 30%的范围,因为子集样本的原因):
colorAxis: {
min: -0.9,
max: 30,
stops: [
[0, '#1E579F'],
// -6.6
[0.085, '#467CBA'],
// -6
[0.1, '#487EBB'],
// -2
[0.2, '#618EC4'],
// -1
[0.225, '#7199CA'],
// -0.2
[0.245, '#9CB4D9'],
// Around 0
[0.25, '#C1C1C1'],
// Around 0.2
[0.256, '#ECACA8'],
// Around 10
[0.5, '#D02335'],
// Around 20
[0.75, '#972531'],
[1.0, '#93212E']
],
labels: {
enabled: true
}
}
此外,我们按照原始图表复制了标题(左上角)、颜色刻度图例(右上角)和致谢(右下角),以下为配置信息:
title: {
text: "Feeling the Heat: Global Inflation",
align: 'left',
style: {
fontSize: '14px'
}
},
subtitle: {
text: "From Aug 2010 - Aug 2011",
align: 'left',
style: {
fontSize: '12px'
}
},
legend: {
align: 'right',
verticalAlign: 'top',
floating: true,
x: -60,
y: -5
},
credits: {
text: 'Sources: CEIC Data; national statistical ....',
position: {
y: -30
}
},
最后一步是定义三维数据和开启dataLabels选项:
series: [{
dataLabels: {
enabled: true,
color: 'white'
},
// Country Category Index, Month/Year Index, Inflation
data: [
// US
[ 0, 0, 1.1 ],
[ 0, 1, 1.1 ],
....,
// Japan
[ 1, 0, -0.9 ],
[ 1, 1, -0.6 ],
这是显示效果:

在热图中尝试使用 dataClasses 和 nullColor 选项
定义颜色轴的另一种方法是让特定范围的值与颜色相关联。让我们绘制另一个热图图表。在本例中,我们重构了一个从kindofnormal.com/truthfacts获取的图表,如下所示:

为了重新创建前面的图表,我们首先使用倒置的热图来模拟它作为一个条形图,但条本身是由颜色逐渐变化的单元格组成。我们将每个块视为 y 轴值的单位,并且每个两个间隔与一个颜色值相关联。因此,y 轴的范围在 0 到 8 之间。以下是修剪后的配置:
yAxis: {
title: { text: null },
gridLineWidth: 0,
minorTickWidth: 1,
max: 8,
min: 0,
offset: 8,
labels: {
style: { .... },
formatter: ....,
然后,我们使用dataClasses选项指定colorAxis,将值范围分为四个颜色组:
colorAxis: {
dataClasses: [{
color: '#2D5C18',
from: 0,
to: 2
}, {
color: '#3B761D',
from: 2,
to: 4
}, {
color: '#70AD28',
from: 4,
to: 6
}, {
color: '#81C02E',
from: 6,
to: 8
}]
},
为了使条形看起来由多个块组成,我们将边框宽度和边框颜色设置为与图表背景颜色相同:
plotOptions: {
heatmap: {
nullColor: '#D2E4B4',
borderWidth: 1,
borderColor: '#D2E4B4',
},
},
注意到有一个nullColor选项;这是为了设置具有 null 值的点的颜色。我们将 null 数据点分配与背景相同的颜色。我们将在稍后看到这种 null 颜色在热图中能做什么。
在热图中,与柱状图不同,我们可以指定列之间的距离和分组。要在类别之间有间隔,唯一的方法是伪造它,因此一个带有空标题的类别:
xAxis: {
tickWidth: 0,
categories: [ 'Food', '', 'Water', '', 'Sleep',
'', 'Internet' ],
lineWidth: 0,
....,
},
由于我们正在模拟一个颜色值变化与 y 轴值相关的条形图,z 值与 y 值相同。以下是食品和水类别的系列数据配置:
series: [{
data: [ [ 0, 7, 7 ], [ 0, 6, 6 ], [ 0, 5, 5 ], [ 0, 4, 4 ],
[ 0, 3, 3 ], [ 0, 2, 2 ], [ 0, 1, 1 ], [ 0, 0, 0 ],
[ 2, 0, 0 ], [ 2, 1, 1 ], [ 2, 2, 2 ], [ 2, 3, 3 ],
[ 2, 4, 4 ], [ 2, 5, 5 ],
第一组数据点在倒置的 x 轴上为零值,这是类别——食品的索引。该组有八个数据点的完整范围。接下来是六个数据点的组(由于虚拟类别,x 轴上的值为二),这对应于水类别中的六个块。
为了演示nullColor的工作原理,我们不是像原始图表那样显示一个单元格块,而是稍微改变一下,使其具有分数单位的单元格。在睡眠和互联网类别中,我们将值分别改为 1.5 和 0.25。显示不是完整块的单元格的热力图的小技巧是使用nullColor选项,即分配给 null 的单元值的一部分,null 值的颜色与图表背景颜色相同,以“隐藏”其余的单元:
[ 4, 0, 0 ],
[ 4, 1, 1 ],
[ 4, 1.5, null ],
[ 6, 0, 0 ],
[ 6, 0.25, null ]
这里是 Highcharts 中复制的截图:

摘要
在本章中,我们学习了如何使用虚构的销售数据绘制瀑布图和漏斗图。我们通过从财务报告中重建金字塔图来熟悉并测试了 Highcharts 的灵活性。我们检查了热力图图表的构建,并通过不同的示例研究了颜色轴属性。
在下一章中,我们将探讨那个备受期待的高图表功能——3D 图表。我们将探讨如何在图表上应用 3D 方向,并绘制一系列 3D 系列图表的画廊。
第九章。3D 图表
3D 图表一直是 Highcharts 中最受期待和最希望拥有的功能。这是一个在用户社区中引起激烈争论的话题。有些用户同意使用 3D 图表,例如在销售和营销报告中或信息图表海报中。反对者认为,3D 图表在数据可视化和比较的准确性方面具有误导性,因此应将开发资源集中在其他地方。至于 Highcharts,它选择了支持的方向。
本章重点介绍 3D 配置的基础知识,并展示了一系列以 3D 形式呈现的图表,以及每个支持系列中的几个特定选项。在本章中,我们将学习:
-
高 charts 中的 3D 图表是什么以及不是什么
-
尝试 3D 图表的方向:
alpha、beta、depth和viewDistance -
配置 3D 图表背景
-
在 3D 图表中绘制柱状图、饼图、环形图和散点图系列
-
使用 3D 图表导航
高 charts 3D 图表是什么以及不是什么
在撰写本文时,3D 图表功能是将 2D 图表以真实 3D 演示的形式显示出来,即我们可以从不同维度旋转和倾斜图表,并调整视图距离。3D 功能目前仅适用于柱状图、饼图和散点图系列。
3D 图表无法做到的是构建一个真正的三维、x、y和z轴图表,除了散点图;我们甚至会在散点图中看到一些不足之处。由于这是 3D 图表的第一个主要版本,这种限制可能很快就会消失。
尝试 3D 图表的方向
要启用 3D 功能,我们首先需要包含以下扩展:
<script
src="img/highcharts-3d.js"></script>
在 Highcharts 中,存在两个级别的 3D 选项:图表和系列。图表级别的选项位于chart.options3d中,主要处理绘图区域的朝向和框架,而系列的三维选项仍然位于常规的plotOptions区域,例如plotOptions.column.depth。
目前,Highcharts 支持两个旋转轴:水平和垂直,分别对应于chart.options3d中的alpha和beta选项。这些选项的值以度为单位。以下图表通过箭头说明了随着度值增加和减少图表旋转的方向:

Alpha 和 beta 方向
在前面的图表中,我们展示了方向的指向。让我们尝试一个柱状图。我们将使用来自第四章的图表,条形图和柱状图,看起来如下:

然后我们按照以下方式向图表添加 3D 选项:
chart: {
renderTo: 'container',
type: 'column',
borderWidth: 1,
options3d: {
alpha: 0,
beta: 0,
enabled: true
}
},
我们将图表切换到 3D 显示模式,启用选项,并在两个轴上没有旋转,从而产生以下图表:

注意,在没有旋转的情况下,图表看起来几乎与原始图表相同,正如预期的那样。当我们仔细观察时,柱子实际上是构建在 3D 中,具有深度。让我们将 alpha 轴设置为 30 度,将 beta 轴保持为零:

当 alpha 轴增加时,我们可以更清楚地看到 3D 结构,因为我们正倾斜着从上到下查看图表。让我们将 alpha 轴重置为零,并将 beta 轴设置为 30 度:

图表在垂直主轴上水平旋转。请注意,一旦图表在 beta 轴上旋转,标题和 y 轴之间会出现一个很大的间隙。我们可以通过使用 x 和 y 选项定位标题来消除这个间隙:
yAxis: {
title: {
text: 'No. of Patents',
x: 35,
y: -80
}
},
让我们将两个旋转都设置为 30:

注意,当我们旋转 3D 图表时,绘图区域可能会与其他组件(如图表标题和每侧的间距)发生干扰。在这种情况下,我们需要使用边距设置来手动适当地调整图表,这取决于大小和视图角度。以下是一个示例:
var chart = new Highcharts.Chart({
chart: {
....,
marginTop: 105,
marginLeft: 35,
spacingBottom: 25
}
....
深度和视图距离
除了 alpha 和 beta 选项之外,我们还可以使用 depth 选项来控制数据显示与 3D 背景之间的距离。让我们将 depth 选项设置为与 3D 柱子的默认深度完全相同,即 25 像素深。我们还指定 viewDistance 选项为默认值 100(在撰写本文时,更改深度值也会更改 viewDistance 值),如下所示:
options3d: {
alpha: 30,
beta: 30,
enabled: true,
viewDistance: 100,
depth: 25
}
如我们所见,柱子背靠背景框架:

让我们将 viewDistance 选项更改为 0 以获得更近的视角:

配置 3D 图表背景
而不是使用白色背景和深色标签,让我们用不同的设置替换围绕绘图区域的侧面、底部和背面框架。在 Highcharts 中,我们可以指定这些框架的颜色和厚度,如下所示:
options3d: {
....,
frame: {
back: {
color: '#A3A3C2',
size: 4
},
bottom: {
color: '#DBB8FF',
size: 10
},
side: {
color: '#8099E6',
size: 2
}
}
}
同时,我们也更改了数据标签颜色为白色,这与新的背景颜色形成很好的对比:

注意,如果我们将背景图像应用于 3D 图表,Highcharts 不会自动将图像沿背景倾斜。
在 3D 图表中绘制柱状图、饼图、环形图和散点图系列
在本节中,我们将绘制一个画廊,展示当前支持用于 3D 展示的柱状图、饼图、环形图和散点图系列。我们还将检查每个系列特定的 3D 选项。这里使用的一些示例来自前面的章节。
堆叠和多个系列中的 3D 柱子
让我们从嵌入 options3d 设置的多系列堆叠柱状图开始:
options3d: {
alpha: 10,
beta: 30,
enabled: true
}
这是一个多系列分组和堆叠的 3D 柱状图的样子:

柱深和 Z 内边距
从前面的图表中可以看出,英国/德国堆叠列的侧面被韩国/日本列覆盖。假设我们想显示英国/德国列的一部分侧面。为了做到这一点,我们可以使用plotOptions.column.depth选项减少韩国/日本列的厚度,例如:
series: [{
name: 'UK',
data: ....,
stack: 'Europe'
}, {
name: 'Germany',
data: ....,
stack: 'Europe'
}, {
name: 'S.Korea',
data: ....,
stack: 'Asia',
depth: 12
}, {
name: 'Japan',
data: ....,
stack: 'Asia',
depth: 12
}]
在这里,我们将韩国/日本列的厚度减少到 12 像素,大约是默认值的一半。以下图表显示了一组比另一组更细的列:

注意,即使我们减少了一组列的深度,英国/德国列的侧面仍然被遮挡。这是因为 Highcharts 默认情况下将 3D 柱状图的表面沿彼此对齐。为了改变这种行为,我们使用groupZPadding选项。为了理解groupZPadding选项的概念,最好用多个系列 3D 柱状图的俯视图来展示:

groupZPadding选项是前视图和柱状图面之间的距离。为了使两个系列列的背面对齐,我们需要增加具有减少深度的系列 B 的groupZPadding值,即:
B 列深 + B 组 Z 内边距 = A 列深 + A 组 Z 内边距
我们添加以下groupZPadding选项(3D 柱状图默认深度为 25 像素,默认groupZPadding为 1 像素):
series: [{
name: 'UK',
data: ....,
stack: 'Europe',
}, {
name: 'Germany',
data: ....,
stack: 'Europe',
}, {
name: 'S.Korea',
data: ....,
stack: 'Asia',
depth: 12,
groupZPadding: 14
}, {
name: 'Japan',
data: ....,
stack: 'Asia',
depth: 12,
groupZPadding: 14
}]
如预期,韩国/日本列被推后,显示了其他系列列的侧面:

绘制信息图表 3D 柱状图
让我们看看我们是否可以使用 Highcharts 绘制信息图表风格的 3D 图表。在本节中,我们将使用 Arno Ghelfi 设计的并在《Wired》杂志上发表的图表,即《任何价格下的极客精神》(见starno.com/client/wired/#geekiness-at-any-price-wired)。以下是信息图表海报的一部分:

虽然图表看起来难以理解,但我们将专注于使用 Highcharts 重现这些特殊 3D 风格柱状图之一。从之前的练习中,我们知道我们可以设置具有增加深度或具有减少groupZPadding值的列,以达到相同的效果。这是因为深度和groupZPadding选项来自plotOptions.column配置,该配置是基于每个系列设计的。因此,我们需要将每个数据项作为单独的系列放置,以便具有不同深度和groupZPadding值的列,如下所示:
series: [{
data: [ 1500 ],
depth: 5,
groupZPadding: 95
}, {
data: [ 1300 ],
depth: 10,
groupZPadding: 90
}, {
data: [ 1100 ],
depth: 15,
groupZPadding: 85
}, {
其次,示例图表中的柱子之间没有间隔。我们可以通过将groupPadding和pointPadding选项的填充空间设置为零来实现相同的效果。然后,我们使用颜色选项将所有柱子的颜色设置为相同。没有间隔且柱子颜色相同会使它们看起来无法区分。幸运的是,3D 柱状图还有一个特定的选项,即edgeColor,它是用于柱子边缘的颜色。以下是配置的结果:
plotOptions: {
column: {
pointPadding: 0,
groupPadding: 0,
color: '#C5542D',
edgeColor: '#953A20'
}
},
这里是我们尝试使用 Highcharts 创建信息图表风格图表的尝试:

绘制 3D 饼图和环形图
绘制 3D 饼图和环形图遵循与柱状图相同的原理。只需包含用于方向的options3d配置即可。为了控制饼图的厚度,我们使用plotOptions.pie.depth以类似的方式。
让我们使用一些实时数据来绘制 3D 饼图和环形图。首先,我们从 Highcharts 在线演示(www.highcharts.com/demo/pie-gradient)中借用一些代码,该代码将 Highcharts 标准系列颜色转换为一些渐变风格的颜色。这自动使图表更具吸引力,并带有轻微的阴影:
Highcharts.getOptions().colors =
Highcharts.map(Highcharts.getOptions().colors, function(color)
{
return {
radialGradient: { cx: 0.5, cy: 0.3, r: 0.7 },
stops: [
[0, color],
[1, Highcharts.Color(color).
brighten(-0.3).get('rgb')] // darken
]
};
});
在这个饼图中,我们将饼图的depth和alpha旋转选项分别设置为 50 和 55:
chart: {
....,
options3d: {
alpha: 55,
beta: 0,
enabled: true
}
},
....,
plotOptions: {
pie: {
center: [ '50%', '45%' ],
depth: 50,
slicedOffset: 40,
startAngle: 30,
dataLabels: {
....
},
size: "120%"
}
},
series: [{
type: 'pie',
data: [ {
name: 'Sleep',
y: 8.74,
sliced: true,
}, {
name: 'Working and work-related activities',
y: 3.46
}, {
....
}]
}]
如我们所见,depth选项产生了一个相当厚的 3D 饼图,并具有很好的颜色阴影:

在下一个饼图中,我们将所有部分设置为通过sliced选项设置为true来分隔,并应用方向:
chart: {
....
options3d: {
alpha: 50,
beta: 40,
enabled: true
}
},
....,
plotOptions: {
pie: {
depth: 40,
center: [ '50%', '44%' ],
slicedOffset: 15,
innerSize: '50%',
startAngle: 270,
size: "110%",
....
}
},
series: [{
type: 'pie',
data: [{
name: 'Swiss & UK',
y: 790,
sliced: true,
// color, dataLabel decorations
....
}, {
name: 'Australia, U.S & South Africa',
y: 401,
....
}, {
....
}]
}]
这里是一个所有切片都相互分离的 3D 环形图:

3D 散点图
Highcharts 支持真实的三维散点系列,尽管它们在视觉上相当模糊。尽管如此,我们可以使用zAxis选项来定义第三个轴的范围。图表没有像其他图表那样在 z 轴上显示任何细节。让我们用一些三维数据构建图表。为了绘制三维散点系列,除了options3d配置之外,我们还需要在三元组数组中定义数据系列:
series: [{
name: 'China',
data: [ [ 2000, 23.32, 20.91 ],
[ 2001, 22.6, 20.48 ],
[ 2002, 25.13, 22.56 ],
....,
在多个三维散点系列中,它看起来是这样的:

如我们所见,在三维空间中悬挂的数据点很难理解。请注意,Highcharts 目前没有在 z 轴上显示任何间隔。让我们添加一些导航来辅助数据可视化:
var orgColors = Highcharts.getOptions().colors;
// Apply color shading to Highcharts.getOptions().colors
....
chart: ....,
....,
tooltip: {
crosshairs: [ { width: 2, color: '#B84DDB' },
{ width: 2, color: '#B84DDB' },
{ width: 2, color: '#B84DDB' } ],
formatter: function() {
var color = orgColors[this.series.index];
return '<span style="color:' + color + '">\u25CF</span> ' +
this.series.name + ' - In <b>' + this.point.x +
'</b>: Exports: <b>' + this.point.y +
'%</b>, Imports: <b>' + this.point.z + '%</b><br/>';
},
shape: 'square',
positioner: function(width, height, point) {
var x = chart.plotLeft + chart.plotWidth - width + 20;
var y = 40;
return { x: x, y: y };
}
}
我们将所有维度的工具提示更改为十字准线。为了避免工具提示遮挡十字准线线,我们使用positioner选项将工具提示位置固定在图表的右上角。接下来,我们使用formatter来设置工具提示内容。我们从 Highcharts 在线演示(www.highcharts.com/demo/3d-scatter-draggable)借用工具提示格式化代码;\u25CF是一个点 Unicode 符号,用作具有系列颜色的项目符号。由于工具提示远离悬停点,将工具提示设置为矩形形状而不是默认的气球样式(Highcharts 中的callout)会更少引起混淆。以下截图显示了带有悬停十字准线的图表:

如我们所见,即使我们在 z 维度指定了十字准线,也没有十字准线线沿轴投射。
使用 3D 图表进行导航
在本节中,我们将探讨如何与 3D 图表进行交互。在第二章中,Highcharts 配置,我们已经探讨了钻取功能,我们将在 3D 图表中简要回顾它。3D 图表特有的另一个交互功能是令人印象深刻的点击并拖动功能。
钻取 3D 图表
让我们将之前的钻取示例转换为 3D 图表。首先,我们添加options3d选项(和其他定位选项):
options3d: {
enabled: true,
alpha: 25,
beta: 30,
depth: 30,
viewDistance: 100
},
这里,我们有我们的顶级 3D 图表:

下一步是改进我们的 3D 钻取饼图,使其具有不同的方向。由于顶层列已经配置为 3D,钻取饼图(在drilldown.series选项中定义,具有匹配的id值)也将相应地配置。然而,我们不会注意到 3D 中的饼图,因为默认深度是 0。因此,我们需要使用depth选项设置饼图的厚度:
plotOptions: {
pie: {
depth: 30,
....,
为了将钻取图表设置为不同的旋转,我们在drilldown事件回调中更改图表选项:
chart: {
renderTo: 'container',
....,
events: {
drilldown: function(e) {
....
if (e.point.name == 'Internet Explorer') {
// Create the '9%' string in the center of
// the donut chart
pTxt = chart.renderer.text('9%',
(chart.plotWidth / 2) + chart.plotLeft - 25,
(chart.plotHeight / 2) + chart.plotTop + 25).
css({
// font size, color and family
....,
'-webkit-transform':
'perspective(600) rotateY(50deg)'
}).add();
chart.options.chart.options3d.alpha = 0;
chart.options.chart.options3d.beta = 40;
}
},
drillup: function() {
// Revert to original orientation
chart.options.chart.options3d.alpha = 25;
chart.options.chart.options3d.beta = 30;
....
}
}
当从Internet Explorer列触发drilldown事件,它缩放到一个环形图时,我们将图表的新alpha和beta方向分别设置为 0 和 40。我们使用 CSS3 -webkit-transform设置在9%符号上,使其看起来与环形图具有相同的旋转。最后,我们在drillup回调中重置图表到其原始方向,该回调在用户点击返回到…按钮时触发。以下是缩放后的环形图的显示:

点击并拖动 3D 图表
与 3D 图表的另一个令人印象深刻的交互功能是点击并拖动功能,我们可以使用它将图表拖动到任何方向。这种交互实际上来自 Highcharts 3D 散点图在线演示(www.highcharts.com/demo/3d-scatter-draggable)。在这里,我们将探讨这是如何实现的。在我们这样做之前,让我们重用我们的信息图表示例,并将点击并拖动 jQuery 演示代码复制到这个练习中。以下是对点击并拖动代码的轻微修改:
$(function () {
$(document).ready(function() {
document.title = "Highcharts " + Highcharts.version;
var chart = new Highcharts.Chart({
....
});
// Add mouse events for rotation
$(chart.container).bind('mousedown.hc touchstart.hc',
function (e) {
e = chart.pointer.normalize(e);
var posX = e.pageX,
posY = e.pageY,
alpha = chart.options.chart.options3d.alpha,
beta = chart.options.chart.options3d.beta,
newAlpha,
newBeta,
sensitivity = 5; // lower is more sensitive
$(document).bind({
'mousemove.hc touchdrag.hc':
function (e) {
// Run beta
newBeta =
beta + (posX - e.pageX) / sensitivity;
newBeta =
Math.min(100, Math.max(-100,
newBeta));
chart.options.chart.options3d.beta =
newBeta;
// Run alpha
newAlpha =
alpha + (e.pageY - posY) / sensitivity;
newAlpha = Math.min(100, Math.max(-100,
newAlpha));
chart.options.chart.options3d.alpha =
newAlpha;
// Update the alpha, beta and viewDistance
// value in subtitle continuously
var subtitle = "alpha: " +
Highcharts.numberFormat(newAlpha, 1) +
", beta: " +
Highcharts.numberFormat(newBeta, 1) +
", viewDistance: " +
Highcharts.numberFormat(
chart.options.chart.options3d.viewDistance, 1);
chart.setTitle(null,
{ text: subtitle }, false);
chart.redraw(false);
},
'mouseup touchend': function () {
$(document).unbind('.hc');
}
});
});
图表容器的元素绑定在mousedown和touchstart事件上。以'.hc'结尾的事件名称意味着事件处理程序被分组到同一个命名空间'.hc'。这后来被用来解绑在命名空间下声明的所有事件处理程序。
因此,当用户在图表容器中执行mousedown或touchstart事件时,它将执行处理程序。该函数首先为跨浏览器事件兼容性标准化事件对象。然后,它记录mousedown事件下的当前指针位置值(pageX,pageY)以及alpha和beta值。进一步在mousedown或touchstart事件下,我们在同一个'.hc'命名空间中绑定一个额外的处理程序,带有mousemove和touchdrag事件。换句话说,这意味着在mousedown或touchstart动作下,移动鼠标和通过触摸拖动将绑定第二个处理程序到容器。
第二个处理程序的实现是通过比较当前移动坐标与mousedown处理程序中记录的初始位置来计算 x 和 y 方向上的移动。然后,它将滚动距离转换为方向,并更新新的alpha和beta值。Math.max和Math.min表达式是为了将alpha和beta限制在-100 和 100 之间的范围。请注意,代码并没有限制我们只能使用事件对象中的pageX/Y。我们可以使用其他类似属性,如screenX/Y或clientX/Y,只要两个处理程序都引用相同的。
最后,我们调用chart.draw(false)以新的方向重新绘制图表,但不进行动画。没有动画的原因是,移动处理程序在滚动动作和动画中被频繁调用,这将需要额外的开销,降低显示的响应性。
以下截图展示了点击并拖动操作后的图表:

鼠标滚轮滚动和视图距离
我们可以通过引入另一个交互,即mousewheel,来提高用户体验,使图表的视图距离响应mousewheel动作。为了实现跨浏览器的兼容性解决方案,我们使用了 Brandon Aaron 的 jQuery mousewheel 插件(github.com/brandonaaron/jquery-mousewheel/)。
以下是对处理程序的代码:
// Add mouse events for zooming in and out view distance
$(chart.container).bind('mousewheel',
function (e) {
e = chart.pointer.normalize(e);
var sensitivity = 10; // lower is more sensitive
var distance =
chart.options.chart.options3d.viewDistance;
distance += e.deltaY / sensitivity;
distance = Math.min(100, Math.max(1, distance));
chart.options.chart.options3d.viewDistance = distance;
var subtitle = "alpha: " +
Highcharts.numberFormat(
chart.options.chart.options3d.alpha, 1) +
", beta: " +
Highcharts.numberFormat(
chart.options.chart.options3d.beta, 1) +
", viewDistance: " +
Highcharts.numberFormat(distance, 1);
chart.setTitle(null, { text: subtitle }, false);
chart.redraw(false);
});
以类似的方式,deltaY 是鼠标滚轮滚动值,我们将此变化应用于 viewDistance 选项。以下是当我们应用点击拖动和鼠标滚轮滚动操作时的结果:

摘要
在本章中,我们学习了如何创建 3D 柱状图、散点图和饼图,以及如何操作每个系列特有的方向和配置。我们通过制作 3D 图表画廊来测试这些选项。除此之外,我们还探讨了 Highcharts 中 3D 图表的多种用户交互方式。
在下一章中,我们将探讨 Highcharts API,这些 API 负责创建动态图表,例如使用 Ajax 查询更新图表内容,访问 Highcharts 对象中的组件,以及将图表导出为 SVG 格式。
第十章. Highcharts API
Highcharts 提供了一组小型 API,专为动态交互式绘图而设计。为了理解 API 的工作原理,我们首先需要熟悉图表的内部对象以及它们在图表内部的组织方式。在本章中,我们将学习图表类模型以及如何通过引用对象调用 API。然后,我们使用 PHP、jQuery 和 jQuery UI 构建一个简单的股价应用程序来演示 Highcharts API 的使用。之后,我们将关注四种不同的更新系列的方法。我们通过实验所有系列更新方法,目的是构建一个应用程序来展示视觉效果的差异,以及它们之间 CPU 性能的差异。最后,我们调查使用流行的网络浏览器更新系列时的性能,从不同大小的数据集的角度来看。在本章中,我们将涵盖以下主题:
-
理解 Highcharts 类模型
-
使用 Ajax 获取数据并通过
Chart.addSeries显示新的系列 -
使用同时进行的 Ajax 调用显示多个系列
-
使用
Chart.getSVG将 SVG 数据格式化为图像文件 -
使用
Chart.renderer方法 -
探索更新系列的不同方法及其性能
-
使用大数据集实验 Highcharts 性能
理解 Highcharts 类模型
Highcharts 类之间的关系非常简单且明显。一个图表由五个不同的类组成——Chart、Axis、Series、Point 和 Renderer。其中一些类包含一组低级组件和一个对象属性,用于回引用到高级所有者组件。例如,Point 类有一个 series 属性指向所有者 Series 类。每个类也有一组用于管理和显示其自身层的方法。在本章中,我们将重点关注动态修改图表的 API 集合。以下类图描述了这些类之间的关系:

Chart 类是表示整个图表对象的顶层类。它包含操作整个图表的方法调用——例如,将图表导出为 SVG 或其他图像格式以及设置图表的维度。Chart 类具有多个 Axis 和 Series 对象数组;也就是说,一个图表可以有一个或多个 x 轴、y 轴和系列。Renderer 类是一个实用类,每个图表都有一个一对一的关系,并为基于 SVG 和 VML 的浏览器提供绘制的一个通用接口。
Series 类包含一个 Point 对象数组。该类具有指向 Chart 和 Axis 对象的回引用属性(参见前一个图中的虚线)并提供管理其 Point 对象列表的函数。在 Series 类中,yAxis 和 xAxis 属性是必要的,因为图表可以拥有多个轴。
Point 类只是一个包含 x 和 y 值以及对其序列对象的反向引用的简单对象(见虚线)。API 用于管理图表中的数据点。
Highcharts 构造函数 – Highcharts.Chart
不言而喻,API 中最重要的方法是 Highcharts.Chart 方法,我们之前已经看到了很多关于这个构造函数的用法。然而,这个构造函数调用还有更多内容。Highcharts.Chart 创建并返回一个图表对象,但它还有一个名为 callback 的第二个可选参数:
Chart(Object options, [ Function callback ])
当图表创建并渲染时,会调用回调函数。在函数内部,我们可以调用组件方法或访问图表对象内的属性。新创建的图表对象通过唯一的回调函数参数传递。我们还可以在回调函数内部使用 this 关键字,它也指向图表对象。我们可以在 chart.events.load 处理程序内部声明代码,而不是使用 Highcharts.Chart 回调参数,这将在下一章中探讨。
在 Highcharts 组件中导航
为了使用 Highcharts API,我们必须在类层次结构中导航到正确的对象。有几种方法可以在图表对象内部进行遍历:通过图表层次结构模型,直接使用 Chart.get 方法检索组件,或者两者的组合。
使用对象层次结构
假设图表对象已创建,如下所示:
<script type="text/javascript">
$(document).ready(function() {
var chart = new Highcharts.Chart({
chart: {
renderTo: "container"
},
yAxis: [{
min: 10,
max: 30
}, {
min: 40,
max: 60
}],
series: [{
data: [ 10, 20 ]
}, {
data: [ 50, 70 ],
yAxis: 1
}],
subtitle: {
text: "Experiment Highcharts APIs"
}
});
}, function() {
...
});
</script>
然后,我们可以通过索引 0 从图表中访问序列对象的第一个位置。在回调处理程序内部,我们使用 this 关键字如下引用图表对象:
var series = this.series[0];
假设配置中有两个 y 轴。要检索第二个 y 轴,我们可以这样做:
var yAxis = this.yAxis[1];
要从第二个序列中检索第三个数据点对象,请输入以下内容:
var point = this.series[1].data[2];
假设页面上创建了多个图表,可以通过 Highcharts 命名空间访问图表对象:
var chart = Highcharts.charts[0];
我们还可以通过 container 选项检索图表的容器元素:
var container = chart.container;
要检查创建的图表的选项结构,请使用 options 属性:
// Get the chart subtitle
var subtitle = chart.options.subtitle.text;
使用 Chart.get 方法
我们可以直接使用 Chart.get 方法检索组件,而不是沿着对象层次结构向下遍历(get 方法仅在图表级别可用,不在每个组件类中)。通过 ID 分配组件将允许您使用 get 方法直接访问它们,而不是遍历对象的层次结构节点。配置使用选项键 id,因此此值必须是唯一的。
假设我们已使用以下配置代码创建了一个图表:
xAxis: {
id: 'xAxis',
categories: [ ... ]
},
series: [{
name: 'UK',
id: 'uk',
data: [ 4351, 4190,
{ y: 4028, id: 'thirdPoint' },
... ]
}]
我们可以按以下方式检索组件:
var series = this.get('uk');
var point = this.get('thirdPoint');
var xAxis = this.get('xAxis');
如果之前没有配置 id 选项,我们可以使用 JavaScript filter 方法根据属性搜索项目:
this.series.filter(function(elt) {return elt.name == 'uk';})[0];
使用对象层次结构和 Chart.get 方法
为图表中的每个组件定义id选项很麻烦。作为替代,我们可以使用两种方法中的任何一种来遍历组件,如下所示:
var point = this.get('uk').data[2];
使用 Highcharts API
在本节中,我们将使用 jQuery、jQuery UI 和 Highcharts 构建一个示例,以探索每个组件的 API。从现在开始的所有示例代码都将使用对象层次结构来访问图表组件,即chart.series[0].data[0]。这里使用的用户界面外观非常简洁,远非完美,因为这项练习的主要目的是检查 Highcharts API。
首先,让我们看看这个用户界面的用法;然后我们将剖析代码以了解操作是如何执行的。以下是在本节中创建的最终 UI 截图:

这是一个简单的 Web 前端,用于绘制过去 30 天的股票数据图表。顶部是一组按钮,用于设置股票符号、获取股票价格和通过下载或电子邮件检索图表图像。添加到列表按钮用于直接将股票符号添加到列表中,而无需获取股票价格和绘制数据。绘制所有按钮用于从符号列表中同时启动多个股票价格查询,并在所有结果到达时绘制数据。或者,添加并绘制是一个快速选项,用于绘制单个股票符号。
下半部分包含一个我们已经创建的图表。图表以空数据和带有标题的轴(将showAxes选项设置为true)显示。整个想法是重用现有的图表,而不是每次新结果到来时都重新创建一个新的图表对象。因此,当图表被销毁和创建时没有闪烁效果,它看起来像是一个平滑的更新动画。这也提供了更好的性能,无需运行额外的代码来重新生成图表对象。
此示例也可在www.joekuan.org/Learning_Highcharts/Chapter_10/Example_1.html在线获取。
图表配置
以下是用作示例的图表配置。一旦执行股票查询,服务器端脚本将请求过去 30 天的股票价格,并将结果格式化为以毫秒为单位的日单位。因此,x轴被配置为以每日为基础的datetime类型:
var chart = new Highcharts.Chart({
chart: {
renderTo: 'container',
showAxes: true,
borderWidth: 1
},
title: { text: 'Last 30 days stock price' },
credits: { text: 'Learning Highcharts' },
xAxis: {
type: 'datetime',
tickInterval: 24 * 3600 * 1000,
dateTimeLabelFormats: { day: '%Y-%m-%d' },
title: {
text: 'Date',
align: 'high'
},
labels: {
rotation: -45,
align : 'center',
step: 2,
y: 40,
x: -20
}
},
yAxis: {
title: { text: 'Price ($)' }
},
plotOptions: {
line: { allowPointSelect: true }
}
});
使用 Ajax 获取数据并在 Chart.addSeries 中显示新的系列
让我们检查添加并绘制按钮背后的动作,如下所示 HTML 语法:
Stock Symbol: <input type=text id='symbol' />
<input type='button' value='Add to the list' id='addStockList' /> <input type='button' value='Add & Plot' id='plotStock'>
....
Added symbols list:
<ol id='stocklist'>
</ol>
按钮动作的 jQuery 代码如下:
$('#plotStock').button().click(
function(evt) {
// Get the input stock symbol, empty the
// list andinsert the new symbol into the list
$('#stocklist').empty();
var symbol = $('#symbol').val();
$('#stocklist').append($("<li/>").append(symbol));
// Kick off the loading screen
chart.showLoading("Getting stock data ....");
// Launch the stock query
$.getJSON('./stockQuery.php?symbol=' +
symbol.toLowerCase(),
function(stockData) {
// parse JSON response here
.....
}
);
}
);
之前的代码定义了添加 & 绘制按钮的点击事件的处理器。首先,它会清空股票符号列表框中所有 ID 为stocklist的条目。然后,它从输入字段symbol中检索股票符号值并将其添加到列表中。下一步是通过调用chart.showLoading方法在图表上启动一个加载信息屏幕。以下截图显示了加载信息屏幕:

下一个调用是启动一个 jQuery Ajax 调用,$.getJSON,以查询股票价格。stockQuery.php服务器脚本(当然,可以使用任何其他服务器端语言)执行两个任务:它将符号解析为组织的全名,从另一个网站(ichart.finance.yahoo.com/table.csv?s=BATS.L)查询过去的股票价格数据,然后将数据打包成行并编码成 JSON 格式。以下是在stockQuery.php文件中的代码:
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// Get the stock symbol name
curl_setopt($ch, CURLOPT_URL, "http://download.finance.yahoo.com/d/quotes.csv?s={$_GET['symbol']}&f=n");
$result = curl_exec($ch);
$name = trim(trim($result), '"');
// Get from now to 30 days ago
$now = time();
$toDate = localtime($now, true);
$toDate['tm_year'] += 1900;
$fromDate = localtime($now - (86400 * 30), true);
$fromDate['tm_year'] += 1900;
$dateParams = "a={$fromDate['tm_mon']}&b={$fromDate['tm_mday']}&c={$fromDate['tm_year']}" ."&d={$toDate['tm_mday']}&e={$toDate['tm_mday']}&f={$toDate['tm_year']}";
curl_setopt($ch, CURLOPT_URL, "http://ichart.finance.yahoo.com/table.csv?s={$_GET['symbol']}&{$dateParams}&g=d");
$result = curl_exec($ch);
curl_close($ch);
// Remove the header row
$lines = explode("\n", $result);
array_shift($lines);
$stockResult['rows'] = array();
// Parse the result into dates and close value
foreach((array) $lines as $ln) {
if (!strlen(trim($ln))) {
continue;
}
list($date, $o, $h, $l, $c, $v, $ac) =
explode(",", $ln, 7);
list($year, $month, $day) = explode('-', $date, 3);
$tm = mktime(12, 0, 0, $month, $day, $year);
$stockResult['rows'][] =
array('date' => $tm * 1000,
'price' => floatval($c));
}
$stockResult['name'] = $name;
echo json_encode($stockResult);
?>
以下是从服务器端以 JSON 格式返回的结果:
{"rows":[ {"date":1348138800000,"price":698.7},
{"date":1348225200000,"price":700.09},
... ],
"name": "Apple Inc."
}
一旦 JSON 结果到达,数据就会被传递到getJSON处理器的定义中,并解析成一个行数组。以下是对处理器代码的详细说明:
$.getJSON('./stockQuery.php?symbol=' +
symbol.toLowerCase(),
function(stockData) {
// Remove all the chart existing series
while (chart.series.length) {
chart.series[0].remove();
}
// Construct series data and add the series
var seriesData = [];
$.each(stockData.rows,
function(idx, data) {
seriesData.push([ data.date, data.price ]);
}
);
var seriesOpts = {
name: stockData.name + ' - (' + symbol +')',
data: seriesData,
// This is to stop Highcharts rotating
// the color and data point symbol for
// the series
color: chart.options.colors[0],
marker: {
symbol: chart.options.symbols[0]
}
};
chart.hideLoading();
chart.addSeries(seriesOpts);
}
);
首先,我们通过调用Series.remove移除图表中显示的所有现有系列。然后,我们构建一个包含日期(UTC 时间)和价格的数据数组的系列选项。然后,我们通过调用Chart.hideLoading移除加载屏幕,并使用Chart.addSeries方法显示一个新的系列。唯一的小问题是,当系列被重新插入时,系列的默认颜色和点标记会改变;当系列被移除并重新添加到图表中时,chart.options.colors和chart.options.symbols中的内部索引会增加。我们可以显式设置系列颜色和点符号来解决这个问题。
或者,我们可以调用Series.setData以实现相同的结果,但是一旦分配了系列(主题)的名称并创建了系列,就不允许更改。因此,我们在这个例子中坚持使用Chart.addSeries和Series.remove。
以下是一个单个股票查询的截图:

使用同时进行的 Ajax 调用显示多个系列
下一个部分是探索如何同时启动多个 Ajax 查询,并在所有结果都返回后一起绘制系列。实现方式基本上与绘制单个股票查询相同,只是我们在收集结果的同时构建系列数组选项,并在最后一个结果到达时才绘制它们:
// Query all the stocks simultaneously and
// plot multipleseries in one go
$('#plotAll').button().click(
function(evt) {
// Kick off the loading screen
chart.showLoading("Getting multiple stock data ....");
// Get the list of stock symbols and launch
// the query foreach symbol
var total = $('#stocklist').children().length;
// start Ajax request for each of the items separately
$.each($('#stocklist').children(),
function(idx, item) {
var symbol = $(item).text();
$.getJSON('./stockQuery.php?symbol=' +
symbol.toLowerCase(),
function(stockData) {
// data arrives, buildup the series array
$.each(stockData.rows,
function(idx, data) {
$.histStock.push([ data.date,
data.price ]);
}
);
seriesOpts.push({
name: stockData.name + ' - (' +
symbol +')',
data: $.histStock,
// This is to stop Highcharts
// rotating the colorfor the series
color: chart.options.colors[idx],
marker: {
symbol: chart.options.symbols[idx]
}
});
// Plot the series if this result
// is the last one
if (seriesOpts.length == total) {
// Remove all the chart existing series
while (chart.series.length) {
chart.series[0].remove()
}
chart.hideLoading();
$.each(seriesOpts,
function(idx, hcOption) {
chart.addSeries(hcOption,
false);
}
);
chart.redraw();
} // else – do nothing,
// not all results came yet
} // function(stockData)
); // getJSON
}); // $.each($('#stocklist')
}); // on('click'
Chart.addSeries的第二个布尔参数redraw被传递为false。相反,我们通过一个单一的调用Chart.redraw来最终化所有更新,以节省 CPU 时间。以下是多个股票查询的截图:

使用 Chart.getSVG 提取 SVG 数据
在本节中,我们将学习如何提取图表输出并通过电子邮件或文件下载来交付。虽然我们可以依赖导出模块并调用exportChart方法将图表导出为所需的图像格式,但了解从格式化原始 SVG 内容到创建图像文件的全过程将更有益。之后,只需在服务器端调用不同的实用工具来交付图像文件即可。
要从显示的图表中提取 SVG 下面的数据,需要调用getSVG方法,该方法在导出模块加载时可用。此方法类似于exportChart:它接受chartOptions参数,该参数用于将配置应用到导出图表输出。
这是处理下载和电子邮件按钮的客户端 jQuery 代码。
在这里,我们使用protocol变量来指定图表的动作,并且两个按钮都调用定义的通用函数deliverChart:
// Export chart into SVG and deliver it to the server
function deliverChart(chart, protocol, target) {
// First extracts the SVG markup content from the
// displayed chart
var svg = chart.getSVG();
// Send the whole SVG to the server and url
$.post('./deliverChart.php', {
svg: svg,
protocol: protocol,
target: target
},
function(result) {
var message = null;
var title = null;
switch (protocol) {
// Create a dialog box to show the
// sent status
case 'mailto':
message = result.success ?
'The mail has been sent successfully' :
result.message;
title = 'Email Chart';
break;
// Uses hidden frame to download the
// image file created on the server side
case 'file':
// Only popup a message if error occurs
if (result.success) {
$('#hidden_iframe').attr("src",
"dlChart.php");
} else {
message = result.message;
title = 'Download Chart';
}
break;
}
if (message) {
var msgDialog = $('#dialog');
msgDialog.dialog({ autoOpen: false,
modal: true, title: title});
msgDialog.text(message);
msgDialog.dialog('open');
}
}, 'json');
}
deliverChart方法首先调用 Highcharts API 的getSVG来提取 SVG 内容,然后使用 SVG 数据和动作参数启动一个POST调用。当$.post返回任务状态值时,它会显示一个消息对话框。至于下载图表,我们在任务状态值成功返回时创建一个隐藏的<iframe>来下载图表图像文件。
以下是将 SVG 内容转换为并交付导出文件的简单服务器端脚本:
<?php
$svg = $_POST['svg'];
$protocol = $_POST['protocol'];
$target = $_POST['target'];
function returnError($output) {
$result['success'] = false;
$result['error'] = implode("<BR/>", $output);
echo json_encode($result);
exit(1);
}
// Format the svg into an image file
file_put_contents("/tmp/chart.svg", $svg);
$cmd = "convert /tmp/chart.svg /tmp/chart.png";
exec($cmd, $output, $rc);
if ($rc) {
returnError($output);
}
// Deliver the chart image file according to the url
if ($protocol == 'mailto') {
$cmd = "EMAIL='{$target}' mutt -s 'Here is the chart' -a /tmp/chart.png -- {$protocol}:{$target} <<.
Hope you like the chart
.";
exec($cmd, $output, $rc);
if ($rc) {
returnError($output);
}
$result['success'] = true;
} else if ($protocol == 'file') {
$result['success'] = true;
}
echo json_encode($result);
?>
注意
网络服务器运行在 Linux 平台(Ubuntu 12.04)上。至于电子邮件操作,我们使用两个命令行工具来帮助我们。第一个是一个快速图像转换工具convert,它是ImageMagick包的一部分(更多详情请访问www.imagemagick.org/script/index.php)。在脚本中,我们将从POST参数中保存 SVG 数据到一个文件,然后运行 convert 工具将其格式化为 PNG 图像。convert 工具支持许多其他图像格式,并附带了许多高级功能。或者,我们可以使用 Batik 通过以下命令进行直接的转换:
java -jar batik-rasterizer.jar /tmp/chart.svg
给定的命令还可以将 SVG 文件转换为/tmp/chart.png并自动输出。为了快速实现电子邮件功能,我们将启动一个电子邮件工具mutt(更多详情请访问www.mutt.org),而不是使用 PHP 邮件扩展。一旦 PNG 图像文件创建完成,我们使用 mutt 将其作为附件发送,并使用 heredoc 指定消息正文。
小贴士
heredoc 是在 Unix 命令行中快速输入带有换行和空格的字符串的一种方法。请参阅 en.wikipedia.org/wiki/Here_document。
以下是被发送的电子邮件的截图:

以下是我电子邮件账户中收到的附件电子邮件的截图:

选择数据点和添加绘图线
接下来是实现 显示范围 复选框和 显示点值 按钮。显示范围 选项在图表的最高点和最低点显示绘图线,而 显示点值 选项在选中一个点时在左下角显示一个包含值的框。以下截图展示了如何在图表中同时启用这两个功能:

注意
虽然对于 显示点值 复选框来说,显示选中的点显示更为自然,但这将成为每个点选择事件的回调实现。相反,我们在这里使用一个按钮,这样我们就可以直接调用 Chart.getSelectedPoints 方法。
使用 Axis.getExtremes 和 Axis.addPlotLine
Axis.getExtremes 方法不仅返回轴当前显示的最小和最大范围,还包括数据点的最高和最低值。在这里,我们使用该方法与 Axis.addPlotLine 函数结合,在 y 轴上添加一对绘图线。addPlotLine 例程期望一个绘图线配置。
在这个例子中,我们指定了一个数据标签以及一个 id 名称,这样我们就可以在未勾选 显示范围 选项或需要用新值重新显示绘图线时,同时移除高点和低点的线条。以下是为 显示范围 动作编写的代码:
// Show the highest and lowest range in the plotlines.
var showRange = function(chart, checked) {
if (!chart.series || !chart.series.length) {
return;
}
// Checked or not checked, we still need to remove
// any existing plot lines first
chart.yAxis[0].removePlotLine('highest');
chart.yAxis[0].removePlotLine('lowest');
if (!checked) {
return;
}
// Checked - get the highest & lowest points
var extremes = chart.yAxis[0].getExtremes();
// Create plot lines for the highest & lowest points
chart.yAxis[0].addPlotLine({
width: 2,
label: {
text: extremes.dataMax,
enabled: true,
y: -7
},
value: extremes.dataMax,
id: 'highest',
zIndex: 2,
dashStyle: 'dashed',
color: '#33D685'
});
chart.yAxis[0].addPlotLine({
width: 2,
label: {
text: extremes.dataMin,
enabled: true,
y: 13
},
value: extremes.dataMin,
zIndex: 2,
id: 'lowest',
dashStyle: 'dashed',
color: '#FF7373'
});
};
使用 Chart.getSelectedPoints 和 Chart.renderer 方法
显示点值 按钮利用 Chart.getSelectedPoints 方法检索当前选中的数据点。请注意,此方法需要首先启用系列选项 allowPointSelect。一旦选中数据点并点击 显示点值 按钮,我们使用 Chart.renderer 方法提供的函数来绘制一个类似工具提示的框,显示选中的值。我们可以使用 Renderer.path 或 Renderer.rect 方法来绘制圆角框,然后使用 Renderer.text 方法来绘制数据值。
小贴士
Highcharts 也支持多数据点选择,可以通过按住 Ctrl 键并单击鼠标左键来实现。
此外,我们使用 Renderer.g 例程将 SVG 框和值字符串组合在一起,并将生成的组元素添加到图表中。这样做的原因是,我们可以通过删除整个旧组对象而不是每个单独的元素来重新显示带有新值的框:
$('#showPoint').button().click(function(evt) {
// Remove the point info box if exists
chart.infoBox && (chart.infoBox =
chart.infoBox.destroy());
// Display the point value box if a data point
// is selected
var selectedPoint = chart.getSelectedPoints();
var r = chart.renderer;
if (selectedPoint.length) {
chart.infoBox = r.g();
r.rect(20, 255, 150, 30, 3).attr({
stroke: chart.options.colors[0],
'stroke-width': 2,
fill: 'white'
}).add(chart.infoBox);
// Convert selected point UTC value to date string
var tm = new Date(selectedPoint[0].x);
tm = tm.getFullYear() + '-' +
(tm.getMonth() + 1) + '-' + tm.getDate();
r.text(tm + ': ' + selectedPoint[0].y,
28, 275).add(chart.infoBox);
chart.infoBox.add();
}
});
Highcharts 的Renderer类还提供了其他方法来在图表上绘制简单的 SVG 形状,例如arc、circle、image、rect、text、g和path。对于更复杂的形状,我们可以使用path方法,它接受 SVG 路径语法,并在 VML 路径上有有限的兼容性。此外,Renderer类可以独立于图表使用——也就是说,我们可以在创建图表之前调用Renderer类的各种方法,并将 SVG 内容添加到 HTML 元素中:
var renderer = new Highcharts.Renderer($('#container')[0], 200, 100);
这创建了一个Renderer对象,它允许我们在container元素内部创建 SVG 元素,其面积为 200 像素宽和 100 像素高。
探索系列更新
系列更新是在图表中执行的最频繁的任务之一。在本节中,我们将以高清晰度研究它。在 Highcharts 中,有几种更新系列的方法。通常,我们可以从系列或数据点级别更新系列。然后,更新方法本身可以是实际更改值,也可以是重新插入它。我们将讨论每种方法,并创建一个综合示例来实验所有技术。
为了比较每种方法,我们将继续使用股票市场数据,但这次我们将改变用户界面以启用回放历史股票价格。以下是示例操作的截图:

如我们所见,有多个选择框可供选择:要回放多少年的历史股票价格,每次迭代中要更新多少个数据点,以及每次更新之间的等待时间有多长。最重要的是,我们可以选择应该使用哪种系列更新方法;观察它们在整个回放过程中的行为差异很有趣,特别是观察它们的行为差异。这个演示也发布在我的网站上,www.joekuan.org/Learning_Highcharts/Chapter_10/Example_2.html。我强烈推荐读者尝试一下。在我们深入研究每种更新方法之前,让我们找出如何构建这个连续系列更新过程。
连续系列更新
一旦我们输入股票代码并选择要回放多少年的股票价格,我们就可以点击加载数据按钮来检索价格数据。一旦数据到达,就会弹出一个带有开始按钮的确认对话框来启动过程。以下是为开始按钮的动作代码:
// Create a named space to store the current user
// input field values and the timeout id
$.histStock = {};
$('#Start').button().click(function() {
chart.showLoading("Loading stock price ... ");
// Remove old timeout if exists
$.histStock.timeoutID &&
clearTimeout($.histStock.timeoutID);
var symbol =
encodeURIComponent($('#symbol').val().toLowerCase());
var years = encodeURIComponent($('#years').val());
// Remember current user settings and initialise values
// for the run
$.histStock = {
// First loop start at the beginning
offset: 0,
// Number of data pts to display in each iteration
numPoints: 30,
// How long to wait in each iteration
wait: parseInt($('#updateMs').val(), 10),
// Which Highcharts method to update the series
method: $('#update').val(),
// How many data points to update in each iteration
update: parseInt($('#updatePoints').val(), 10)
};
// Clean up old data points from the last run
chart.series.length && chart.series[0].setData([]);
// Start Ajax query to get the stock history
$.getJSON('./histStock.php?symbol=' + symbol +
'&years=' + years,
function(stockData) {
// Got the whole period of historical stock data
$.histStock.name = stockData.name;
$.histStock.data = stockData.rows;
chart.hideLoading();
// Start the chart refresh
refreshSeries();
}
);
})
我们首先在 jQuery 命名空间下创建一个变量histStock,该变量在演示的各个部分中被访问。histStock变量包含当前用户的输入和刷新任务的引用。任何来自用户界面的更改都会更新$.histStock,因此系列更新会相应地响应。
基本上,当点击开始按钮时,我们初始化$.histStock变量,并使用股票符号和年数参数启动一个 Ajax 查询。然后,当查询返回股票价格数据时,我们将结果存储到变量中。我们随后调用refreshSeries,它通过定时器例程调用自身。以下代码是该方法的简化版本:
var refreshSeries = function() {
var i = 0, j;
// Update the series data according to each approach
switch ($.histStock.method) {
case 'setData':
....
break;
case 'renewSeries':
....
break;
case 'update':
....
break;
case 'addPoint':
....
break;
}
// Shift the offset for the next update
$.histStock.offset += $.histStock.update;
// Update the jQuery UI progress bar
....
// Finished
if (i == $.histStock.data.length) {
return;
}
// Setup for the next loop
$.histStock.timeoutID =
setTimeout(refreshSeries, $.histStock.wait);
};
在refreshSeries内部,它检查$.histStock变量内的设置,并根据用户的选择更新系列。一旦更新完成,我们就增加offset值,这是将股票结果数据复制到图表的起始位置。如果计数器变量i达到股票数据的末尾,它将简单地退出方法。否则,它将调用 JavaScript 定时器函数来设置下一个循环。下一个目标是回顾每种更新方法是如何执行的。
测试各种 Highcharts 方法的性能
更新系列数据有四种技术:Series.setData、Series.remove/Chart.addSeries、Point.update和Series.addPoint。我们使用资源监视器工具测量所有四种技术在 CPU 和内存使用方面的性能。每种方法在回放过去一年的股票价格时都会被计时,每次更新之间有 0.5 秒的等待时间。我们重复进行了两次相同的运行,并记录了平均值。实验在 Firefox、Chrome、Internet Explorer 8 和 11 以及 Safari 等浏览器上进行了重复。尽管 IE 8 不支持 SVG,只支持 VML,但在实验中使用它很重要,因为 Highcharts 的实现与 IE 8 兼容。我们立刻注意到,在 IE8 上显示的相同图表不如 SVG 吸引人。
注意
整个实验在一个安装了 Windows 7 Ultimate 的 PC 上运行,硬件配置为 4GB RAM Core 2 Duo 3.06 GHz,并配备了 Intel G41 显卡芯片组。
浏览器版本包括 Firefox 31.0、Chrome 36.0.1985、IE11 11.0.9600、Safari 5.1.7 以及 IE8 8.0.6001。Safari 可能不是一个真正的性能指标,因为它对于一个 PC 平台来说相对较旧。
由于微软已经停止支持,现在在 Windows 7 上安装/运行 IE8 不再可能。虽然我们可以在 IE11 上设置用户代理为 IE8 并执行实验,但这并不能真正反映 IE8 的性能。因此,我们设置了一个运行 Windows XP 且硬件相同的系统,上面安装了 IE8。
在接下来的章节中,将解释每个系列更新方法,并展示不同浏览器之间的性能比较。读者不应将结果作为浏览器一般性能的指南,因为浏览器的一般性能是通过在多个领域的众多测试中得出的。我们在这里实验的是 Highcharts 在各个浏览器上针对 SVG 动画的性能。
注意
注意,与上一版中展示的结果相比,结果有所不同。这主要是因为使用了更新版本的 Highcharts:我们在这次实验中使用的是 4.0.3,而上一版中记录的是 2.2.24。
使用 Series.setData 应用新数据集
我们可以使用Series.setData方法将一组新数据应用到现有的系列上:
setData (Array<Mixed> data, [Boolean redraw])
数据可以是一维数据的数组,x 和 y 值对的数组,或者数据点对象的数组。请注意,这种方法是所有方法中最简单的一种,并且根本不提供任何动画效果。以下是我们如何在示例中使用setData函数:
case 'setData':
var data = [];
// Building up the data array in the series option
for (i = $.histStock.offset, j = 0;
i < $.histStock.data.length &&
j < $.histStock.numPoints; i++, j++) {
data.push([
$.histStock.data[i].date,
$.histStock.data[i].price ]);
}
if (!chart.series.length) {
// Insert the very first series
chart.addSeries({
name: $.histStock.name,
data: data
});
} else {
// Just update the series with
// the new data array
chart.series[0].setData(data, true);
}
break;
图表中出现了两组动画:x轴标签从图表中心移动,以及系列中的数据点。尽管系列滚动得很平滑,但x轴标签的移动看起来太快,变得断断续续。以下图表显示了使用setData方法在浏览器之间性能比较:

在 CPU 使用方面,除了运行在 VML 而不是 SVG 上的 IE8 之外,存在一些细微的差异。IE8 消耗了更高的 CPU 使用率,并且完成时间更长。在整个实验过程中,动画都出现了滞后。在所有浏览器中,Safari 的表现略好。在所有浏览器中,Firefox 的内存占用最大,而 IE 11 的内存占用最小。也许一个轻微的惊喜是,Safari 的性能优于 Firefox,并且也非常接近 Chrome。
使用 Series.remove 和 Chart.addSeries 重新插入带有新数据的系列
或者,我们可以使用Series.remove方法删除整个系列,然后用数据和系列选项重建系列,最后使用Chart.addSeries重新插入一个新的系列。这种方法的一个缺点是,默认颜色和点符号的内部索引会增加,正如我们在早期示例中发现的那样。我们可以通过指定颜色和标记选项来补偿这一点。以下是addSeries方法的代码:
case 'renewSeries':
var data = [];
for (i = $.histStock.offset, j = 0;
i < $.histStock.data.length &&
j < $.histStock.numPoints; i++, j++) {
data.push([ $.histStock.data[i].date,
$.histStock.data[i].price ]);
}
// Remove all the existing series
if (chart.series.length) {
chart.series[0].remove();
}
// Re-insert a new series with new data
chart.addSeries({
name: $.histStock.name,
data: data,
color: chart.options.colors[0],
marker: {
symbol: chart.options.symbols[0]
}
});
break;
在这个实验中,我们使用每半秒的刷新率,这比默认动画的时间跨度要短。因此,系列更新看起来没有多少动画,就像在setData中一样,显得不规则。然而,如果我们把刷新率改为 3 秒或更长,那么我们就可以看到每次更新时系列从左侧到右侧的重绘。与其它方法不同,x 轴标签的更新没有任何动画:

以下图表显示了使用addSeries方法在浏览器之间性能比较:

由于这种方法似乎动画最少,因此所有浏览器的 CPU 使用率水平相对较低,内存使用也是如此。正如预期的那样,IE8 消耗的资源最多。其次是 IE11,其性能与 Chrome 大致相当。最不寻常的结果是,当动画较少时,Firefox 的 CPU 使用量比 Chrome 和 Safari 都要少得多。我们将在稍后的部分进一步调查这一点。
使用 Point.update 更新数据点
我们可以使用 Point.update 方法更新单个数据点。更新方法具有与 setData 相似的原型,它接受单个值、x 和 y 值的数组或数据点对象。每次更新调用都可以带动画或不带动画地重新绘制到图表中:
update ([Mixed options], [Boolean redraw], [Mixed animation])
这里是如何使用 Point.update 方法的:我们遍历每个点对象并调用其成员函数。为了节省 CPU 时间,我们将 redraw 参数设置为 false,并在最后一个数据点更新后调用 Chart.redraw:
case 'update':
// Note: Series can be already existed
// at start if we click 'Stop' and 'Start'
// again
if (!chart.series.length ||
!chart.series[0].points.length) {
// Build up the first series
var data = [];
for (i = $.histStock.offset, j = 0;
i < $.histStock.data.length &&
j < $.histStock.numPoints; i++, j++) {
data.push([
$.histStock.data[i].date,
$.histStock.data[i].price ]);
}
if (!chart.series.length) {
chart.addSeries({
name: $.histStock.name,
data: data
});
} else {
chart.series[0].setData(data);
}
} else {
// Updating each point
for (i = $.histStock.offset, j = 0;
i < $.histStock.data.length &&
j < $.histStock.numPoints; i++, j++) {
chart.series[0].points[j].update([
$.histStock.data[i].date,
$.histStock.data[i].price ],
false);
}
chart.redraw();
}
break;
Point.update 方法垂直动画每个数据点。随着图表的逐步更新,整体上产生波浪效果。与 setData 方法类似,标签以对角线方式接近 x 轴线。以下图表显示了 Point.update 方法在各个浏览器中的性能比较:

由于动画与 setData 方法几乎相同,因此前面图表中显示的性能与 setData 性能实验的结果非常接近。
使用 Point.remove 和 Series.addPoint 移除和添加数据点
我们不需要逐个更新每个数据点,可以使用 Point.remove 来从 series.data 数组中移除数据点,并使用 Series.addPoint 将新的数据点重新添加到系列中:
remove ([Boolean redraw], [Mixed animation])
addPoint (Object options, [Boolean redraw], [Boolean shift],
[Mixed animation])
对于时间序列数据,我们可以使用 addPoint 并将 shift 参数设置为 true,这将自动移动系列点数组:
case 'addPoint':
// Note: Series can be already existed at
// start if we click 'Stop' and 'Start' again
if (!chart.series.length ||
!chart.series[0].points.length) {
// Build up the first series
var data = [];
for (i = $.histStock.offset, j = 0;
i < $.histStock.data.length &&
j < $.histStock.numPoints; i++, j++) {
data.push([
$.histStock.data[i].date,
$.histStock.data[i].price ]);
}
if (!chart.series.length) {
chart.addSeries({
name: $.histStock.name,
data: data
});
} else {
chart.series[0].setData(data);
}
// This is different, we don't redraw
// any old points
$.histStock.offset = i;
} else {
// Only updating the new data point
for (i = $.histStock.offset, j = 0;
i < $.histStock.data.length &&
j < $.histStock.update; i++, j++) {
chart.series[0].addPoint([
$.histStock.data[i].date,
$.histStock.data[i].price ],
false, true );
}
chart.redraw();
}
break;
以下图表显示了 addPoint 方法在各个浏览器中的性能比较:

在 CPU 和内存使用方面,addPoint 方法、setData 和 update 几乎没有区别。
探索浏览器上的 SVG 动画性能
到目前为止,我们已经看到 CPU 使用率随着动画的增加而增加。然而,尚未回答的问题是为什么 Safari 的 CPU 消耗低于 Chrome 和 Firefox。在测试机器上运行了多个浏览器基准测试套件,以确认普遍共识,即 Firefox 和 Chrome 浏览器在整体性能上优于 Safari。
注意
所有浏览器都使用 SunSpider www.webkit.org/perf/sunspider/sunspider.html、谷歌的 V8 基准测试套件 octane-benchmark.googlecode.com/svn/latest/index.html 和 Peacekeeper peacekeeper.futuremark.com/ 进行基准测试。
尽管如此,Safari 在一个特定领域比其他浏览器有更好的性能:SVG 动画;这一点在我们之前的实验中已经体现出来。在这里,我们使用由 Cameron Adams 编写的基准测试,专门用于测量每秒帧数中的弹跳粒子 SVG 动画。这个测试(HTML5 与 Flash:动画基准测试 [www.themaninblue.com/writing/perspective/2010/03/22/](http://www.themaninblue.com/writing/perspective/2010/03/22/))最初是为了比较各种 HTML5 动画技术与 Flash 的。在这里,我们使用 Chrome 和 Safari 浏览器运行 SVG 测试。以下是在 500 个粒子测试下运行的 Safari 截图:

对于 Chrome,测试运行在大约 165 FPS。我们在两个浏览器上使用不同数量的粒子重复评估。以下图表总结了 SVG 动画方面的性能差异:

如我们所见,Safari 在粒子数少于 3,000 个时能够管理更高的帧率。之后,Safari 的性能开始与 Chrome 并行下降。Firefox 的帧率始终较低,并且与其他浏览器相比帧率下降得相当多。
这引出了另一个不可避免的问题:既然两个浏览器都运行在相同的 webkit 代码库上,为什么会有如此大的差异?很难确定差异所在。然而,这两个产品之间少数几个差异之一是 JavaScript 引擎,这可能会影响该区域,或者可能是由于 webkit 版本之间的微小差异造成的。此外,jsperf.com 上也运行了其他特定的 SVG 性能测试,其中 Safari 再次比 Chrome 得分高。
在下一节中,我们将看到 Highcharts 的性能如何与数据大小相对应。
比较 Highcharts 在大型数据集上的性能
我们的最终测试是观察 Highcharts 在大型数据集上的表现。在这个实验中,我们将绘制不同数据大小的散点系列,并观察显示数据所需的时间。我们选择使用散点系列,因为当存在包含数万个样本的非常大的数据集时,用户很可能会在图表上仅绘制数据点。以下是说明我们如何操作的简化代码:
var data = [];
// Adjust for each experiment
var num = 0;
if (location.match(/num=/)) {
var numParam = location.match(/num=([^&]+)/)[1];
num = parseInt(numParam, 10);
}
for (var i = 0; i < num; i ++) {
data.push([ Math.random() * 1000000000, Math.random() * 50000 ]);
}
var start = new Date().getTime();
var chart = new Highcharts.Chart({
chart: {
renderTo: 'container',
showAxes: true,
animation: false,
....
},
series: [{
type: 'scatter',
data: data
}],
tooltips: {
enabled: false
},
plotOptions: {
series: {
turboThreshold: 1,
animation: false,
enableMouseTracking: false
}
}
}, function() {
var stop = new Date().getTime();
// Update the time taken label
$('#time').append(((stop - start) / 1000) + " sec");
});
页面通过 URL 参数加载,以指定数据集大小。我们在创建图表对象之前开始计时,并在构造函数中的 Chart 方法回调处理程序处停止。我们使用在之前的基准测试实验中使用的每个浏览器重复具有相同数据集大小的实验。一旦页面在浏览器上加载,数据集就会随机生成。然后,在图表对象构建之前开始计时,并在 Highcharts 中的回调方法处停止。当图表最终显示在屏幕上时,执行图表函数。以下截图显示了在 Safari 浏览器上显示 3,000 个数据点所需的时间:

下面的图表说明了不同数据集大小下 Highcharts 在不同浏览器上的性能。线条越低,显示数据点数量所需的时间越少,这表明性能越好:

没有浏览器在显示大数据集时特别耗时。这显示了 Highcharts 令人印象深刻的可扩展性能。即使是所有测试中始终较慢的 IE 11,也只需 5.3 秒就能渲染 50,000 个数据点。至于较小的数据集,计时几乎没有差异。Firefox 和 Safari 的结果非常相似,尽管 Firefox 略好。这是因为散点系列中没有动画,Firefox 通常在没有 SVG 动画的情况下表现更好。这也支持我们在最不活跃的 addSeries 实验中的发现,其中 Firefox 需要更少的 CPU 使用量。至于 Chrome,其性能大致位于 Safari/Firefox 和 IE 11 之间。
从所有这些实验中,我们可以得出结论,Safari 在 Windows PC 上使用 Highcharts 时表现最佳,尤其是在图表中有大量 SVG 动画时。如果图表是静态的,那么 Firefox 会提供最佳性能。IE 11 在浏览器中具有最佳的内存利用率,但运行速度较慢。Chrome 实现了稳定的中间性能。我们必须强调,SVG 只是性能测试的许多领域之一,Chrome 在基准分数中优于其他浏览器。
摘要
在本章中,我们学习了 Highcharts API,从类模型到将其应用于应用程序。然后我们对 Highcharts 中更新图表系列的不同技术进行了全面研究,并进行了实验以分析它们的性能差异。最后,本章通过分析不同大小的大数据集在渲染数据点时的不同网页浏览器的速度来结束。
在下一章中,我们将探讨 Highcharts 事件处理,这与 Highcharts API 密切相关。
第十一章。Highcharts 事件
在上一章中,我们学习了 Highcharts API。在本章中,我们将学习 Highcharts 事件处理。我们将从介绍 Highcharts 支持的事件集开始本章。然后,我们将构建两个网络应用程序来涵盖大多数事件;每个应用程序探索不同的事件集。尽管这些应用程序远非完美,并且有大量的改进空间,但唯一目的是演示 Highcharts 事件的工作原理。在本章中,我们将涵盖以下主题:
-
使用图表
load事件启动 Ajax 查询 -
使用图表
redraw事件激活用户界面 -
使用点
select和unselect事件选择和取消选择数据点 -
使用图表选择事件缩放所选区域
-
使用点
mouseover和mouseout事件悬停在数据点上 -
使用图表
click事件创建绘图线 -
使用系列
click事件启动对话框 -
使用系列
checkboxClick事件启动饼图 -
使用
click、update和remove事件编辑饼图
介绍 Highcharts 事件
到目前为止,我们已经了解了大部分 Highcharts 的配置,但还有一个领域尚未涉及:事件处理。Highcharts 在多个区域提供了一套事件选项,例如图表事件、系列事件和轴基本事件;它们通过 API 调用和用户与图表的交互触发。
Highcharts 事件可以通过创建图表时通过对象配置指定,也可以通过接受对象配置的 API 指定,例如 Chart.addSeries、Axis.addPlotLine 和 Axis.addPlotBand。
事件处理程序通过事件对象传递,该对象包含鼠标信息和与事件操作相关的特定动作数据。例如,event.xAxis[0] 和 event.yAxis[0] 存储在 chart.events.click 处理程序的参数中。在事件函数内部,可以使用 'this' 关键字,它指向基于事件函数的 Highcharts 组件。例如,chart.events.click 中的 'this' 关键字指向 chart 对象,而 plotOptions.series.events.click 中的 'this' 关键字指向被点击的 series 对象。
以下是一个 Highcharts 事件的列表:
-
chart.events:addSeries、click、load、redraw、selection、drilldown和drillup -
plotOptions.<series-type>.events:click、checkboxClick、hide、mouseover、mouseout、show、afterAnimate和legendItemClick注意
或者,我们可以在系列数组中为特定系列指定事件选项,例如:
series[ { events: click: function { ... }, ... } ]。 -
plotOptions.<series-type>.point.events:click、mouseover、mouseout、remove、select、unselect、update和legendItemClick注意
我们可以为特定系列定义点事件,如下所示:
series[ { point : { events: { click: function() { ... } }, ... } } ]。对于在系列中的特定数据点定义事件,我们可以如下指定:
series[ { data: [ { events: { click: function() { ... } } ], ... } ]。 -
x/yAxis.events:setExtremes和afterSetExtremes -
x/yAxis.plotBands[x].events和x/yAxis.plotLines[x].events:click、mouseover、mousemove和mouseout
Highcharts 在线文档提供了全面的参考和大量的迷你示例;强烈建议您查阅。重复相同的练习没有太大意义。相反,我们将构建两个稍微大一点的示例,以利用大多数 Highcharts 事件,并展示这些事件如何在应用程序中协同工作。由于完整的示例代码太长,无法在本章中列出,因此只编辑并展示了相关部分。
完整的演示和源代码可以在www.joekuan.org/Learning_Highcharts/Chapter_11/chart1.html找到。
投资组合历史示例
此应用程序扩展了上一章中的历史股票图表,并添加了额外的投资组合功能。前端使用 jQuery 和 jQuery UI 实现,以下事件在本示例中得到了覆盖:
-
chart.events:click、load、redraw和selection -
plotOptions.series.points.events:mouseover、mouseout、select和unselect -
xAxis/yAxis.plotLines.events:mouseover和click
以下为演示的启动屏幕,其中组件已标注:

应用程序包含一对时间序列图表。底部图表是显示整个历史价格走势并指向公司股票购买和卖出时间的顶级图表。顶部图表是详细图表,当在底部图表中创建选区时,它会放大以显示更详细的细节。
一旦在浏览器中加载了网络应用程序,就会创建两个图表。顶级图表配置了一个加载事件,该事件会自动从网络服务器请求股票历史价格和投资组合历史。
以下截图显示了自动加载顶级图表后的图形:

在顶级图表上方有圆形和三角形的数据点。这些表示交易历史。B符号表示股票被购买时,而S符号表示股票被卖出时。顶级图表下方的信息是截至当前日期的股票投资组合详情。
如果我们点击这些交易历史点之一,投资组合详情部分将更新以反映所选日期的投资历史。此外,当我们选择一个区域时,它会在详细图表中放大并显示股价走势。事件处理中涉及其他功能,我们将在后面的章节中讨论它们。
顶级图表
以下是为顶级图表(底部图表显示整个历史价格走势)的配置代码,我们将chart对象存储在myApp命名空间中,如下所示:
$.myApp.topChart = new Highcharts.Chart({
chart: {
zoomType: 'x',
spacingRight: 15,
renderTo: 'top-container',
events: {
// Load the default stock symbol of
// the portfolio
load: function() { .... },
// The top level time series have
// been redrawn, enable the portfolio
// select box
redraw: function() { .... },
// Selection - get all the data points from
// the selection and populate into the
// detail chart
selection: function(evt) { .... },
}
},
title: { text: null },
yAxis: {
title: { text: null },
gridLineWidth: 0,
labels: { enabled: false }
},
tooltip: { enabled: false },
xAxis: {
title: { text: null },
type: 'datetime'
},
series: [ ... ],
legend: { enabled: false },
credits: { enabled: false }
});
在此配置中有很多事情在进行。图表定义时大部分功能都被禁用,例如图例、标题、工具提示和y轴标签。更重要的是,图表配置了zoomType选项,这使得图表可以沿x轴方向缩放;因此,我们可以使用select事件。系列数组由多个系列组成,这些系列也包含事件配置。
构建顶级图表的系列配置
在系列数组中,定义了多个系列,包括开盘价和收盘价、买入和卖出交易日期,以及一个用于跟踪详细图表中鼠标移动的隐藏系列:
series: [{
// Past closed price series
type: 'areaspline',
marker: { enabled: false },
enableMouseTracking: false
}, {
// This is the open price series and never shown
// in the bottom chart. We use it to copy this
// to the detail chart
visible: false
}, {
// Series for date and price when shares
// are bought
type: 'scatter',
allowPointSelect: true,
color: $.myApp.boughtColor,
dataLabels: {
enabled: true,
formatter: function() { return 'B'; }
},
point: {
events: { .... }
}
}, {
// Series for date and price when shares are sold
type: 'scatter',
allowPointSelect: true,
color: $.myApp.soldColor,
dataLabels: {
enabled: true,
formatter: function() { return 'S'; }
},
point: {
events: { .... }
}
}, {
// This is the tracker series to show a single
// data point of where the mouse is hovered on
// the detail chart
type: 'scatter',
color: '#AA4643'
}]
第一系列是历史股价系列,配置时没有数据点标记。第二系列是隐藏的,并在详细图表中作为历史开盘价数据的占位符。第三(买入)和第四(卖出)系列是散点系列,揭示了股票交易的具体日期。这两个系列都设置了allowPointSelect选项,因此我们可以在point.events选项中定义select和unselect事件。最后一个系列也是一个散点系列,使用mouseover和mouseout事件来反映详细图表中的鼠标移动;我们将在稍后看到这些是如何实现的。
使用图表加载事件启动 Ajax 查询
如前所述,一旦顶级图表创建并加载到浏览器中,它就可以从服务器获取数据。以下是为该图表的load事件处理器定义:
chart: {
events: {
load: function() {
// Load the default stock symbol of
// the portfolio
var symbol = $('#symbol').val();
$('#symbol').attr('disabled', true);
loadPortfolio(symbol);
},
我们首先从我的投资组合选择框中检索值,并在查询期间禁用选择框。然后,我们调用一个预定义的函数loadPortfolio。该方法执行多个任务,如下所示:
-
使用 Ajax 调用
$.getJSON来加载过去的股价和投资组合数据。 -
设置处理返回的 Ajax 结果的处理器,以进一步执行以下步骤:
-
隐藏图表加载遮罩。
-
解包返回的数据,并使用
Series.setData方法将其填充到系列数据中。 -
更新投资组合详情部分的数据,以显示截至当前日期的投资价值。
-
使用图表重绘事件激活用户界面
一旦顶级图表填充了数据,我们就可以在页面上启用我的投资组合选择框。为此,我们可以依赖于redraw事件,该事件由步骤 2 中的子步骤 2 中的Series.setData调用触发:
redraw: function() {
$('#symbol').attr('disabled', false);
},
使用点选择和取消选择事件选择和取消选择数据点
购买和销售系列具有相同的事件处理;它们之间的唯一区别是颜色和点标记形状。想法是,当用户点击这些系列中的数据点时,投资详情部分会更新以显示交易日期的股票投资详情。以下截图显示了选择第一次购买交易点后的效果:

为了保持数据点被选中,我们将使用 allowPointSelect 选项,它允许我们定义 select 和 unselect 事件。以下是购买和销售系列的事件配置:
point: {
events: {
select: function() {
updatePortfolio(this.x);
},
unselect: function() {
// Only default back to current time
// portfolio detail when unselecting
// itself
var selectPt =
$.myApp.topChart.getSelectedPoints();
if (selectPt[0].x == this.x) {
updatePortfolio(new Date().getTime());
}
}
}
}
基本上,select 事件处理器调用一个预定义的函数,updatePortfolio,根据所选数据点的时间:this.x 更新 投资详情 部分。处理器中的 'this' 关键字指的是所选点对象,其中 x 是时间值。
取消选择数据点将调用 unselect 事件处理器。前面的实现意味着,如果未选择的数据点 (this.x) 与之前选择的数据点相同,那么这表明用户取消选择了相同的数据点,因此我们希望显示当前日期的投资详情。否则,它将不执行任何操作,因为这表示用户已选择另一个交易数据点;因此,将使用不同日期调用另一个 select 事件。
使用图表选择事件放大所选区域
selection 事件在顶层图表和详细图表之间形成桥梁。当我们选择顶层图表中的区域时,所选区域会被突出显示,详细图表中的数据会被放大。这个动作会触发 selection 事件,以下是对事件处理器的简化代码:
selection: function(evt) {
// Get the xAxis selection
var selectStart = Math.round(evt.xAxis[0].min);
var selectEnd = Math.round(evt.xAxis[0].max);
// We use plotBand to paint the selected area
// to simulate a selected area
this.xAxis[0].removePlotBand('selected');
this.xAxis[0].addPlotBand({
color: 'rgba(69, 114, 167, 0.25)',
id: 'selected',
from: selectStart,
to: selectEnd
});
for (var i = 0;
i < this.series[0].data.length; i++) {
var pt = this.series[0].data[i];
if (pt.x >= selectStart &&
pt.x <= selectEnd) {
selectedData.push([pt.x, pt.y]);
}
if (pt.x > selectEnd) {
break;
}
}
// Update the detail serie
var dSeries = $.myApp.detailChart.series[0];
dSeries.setData(selectedData, false);
....
// Update the detail chart title & subtitle
$.myApp.detailChart.setTitle({
text: $.myApp.stockName + " (" +
$.myApp.stockSymbol + ")",
style: { fontFamily: 'palatino, serif',
fontWeight: 'bold' }
}, {
text: Highcharts.dateFormat('%e %b %y',
selectStart) + ' -- ' +
Highcharts.dateFormat('%e %b %y',
selectEnd),
style: { fontFamily: 'palatino, serif' }
});
$.myApp.detailChart.redraw();
return false;
}
处理器代码中包含几个步骤。首先,我们从处理器参数中提取所选范围值——evt.xAxis[0].min 和 evt.xAxis[0].max。下一步是使所选区域在顶层图表中保持突出显示。为此,我们使用 this.xAxis[0].addPlotBand 在相同区域创建一个绘图带以模拟选择。
'this' 关键字指的是顶层图表对象。接下来的任务是赋予一个固定的 id,以便我们可以移除旧的选择并突出显示新的选择。此外,绘图带应该与图表上被拖动的选择具有相同的颜色。我们所需做的只是将绘图带颜色赋值为 chart.selectionMarkerFill 选项的默认值。
之后,我们将所选范围内的数据复制到一个数组中,并使用 Series.setData 将其传递给详细图表。由于我们多次调用了 setData 方法,因此将 redraw 选项设置为 false 以节省资源,然后调用 redraw 方法。
最后,最重要的步骤是在函数末尾返回false。返回false布尔值告诉 Highcharts 在做出选择后不要采取默认操作。否则,整个顶级图表将被重新绘制并拉伸(或者,我们也可以调用event.preventDefault())。
以下截图放大并显示另一个图表的详细信息:

详细图表
详细图表只是一个折线图,显示了从顶级图表中选择的区域。图表配置了一个固定在上左角的工具提示和我们将要讨论的一些事件:
$.myApp.detailChart = new Highcharts.Chart({
chart: {
showAxes: true,
renderTo: 'detail-container',
events: {
click: function(evt) {
// Only allow to prompt stop order
// dialog if the chart contains future
// time
....
}
},
},
title: {
margin: 10,
text: null
},
credits: { enabled: false },
legend: {
enabled: true,
floating: true,
verticalAlign: 'top',
align: 'right'
},
series: [ ... ],
// Fixed location tooltip in the top left
tooltip: {
shared: true,
positioner: function() {
return { x: 10, y: 10 }
},
// Include 52 week high and low
formatter: function() { .... }
},
yAxis: {
title: { text: 'Price' }
},
xAxis: { type: 'datetime' }
});
以下是一个截图,显示了悬停的数据点以及显示在上左角的工具提示:

构建详细图表的系列配置
详细图表中配置了两个系列。主要关注的是第一个系列,即股票收盘价。该系列没有数据点标记,并将'crosshair'作为cursor选项,正如我们在前面的截图中所见。此外,为创建顶级图表中追踪系列标记的数据点定义了mouseout和mouseover事件。我们将在下一节中讨论这些事件。系列数组定义如下:
series: [{
marker: {
enabled: false,
states: {
hover: { enabled: true }
}
},
cursor: 'crosshair',
point: {
events: {
mouseOver: function() { ... },
mouseOut: function() { ... }
}
},
stickyTracking: false,
showInLegend: false
}, {
name: 'Open Price',
marker: { enabled: false },
visible: false
}],
使用鼠标悬停和鼠标移出事件悬停在数据点上
当我们在详细图表的系列上移动鼠标指针时,该移动也会在所选区域内的顶级图表中反映出来。以下截图显示了在顶级图表中显示的追踪点(倒三角形):

倒三角形表示我们在顶级图表中浏览的位置。为此,我们将在详细图表系列中设置mouseOut和mouseOver点事件选项,如下所示:
point: {
events: {
mouseOver: function() {
var series = $.myApp.topChart.series[4];
series.setData([]);
series.addPoint([this.x, this.y]);
},
mouseOut: function() {
var series = $.myApp.topChart.series[4];
series.setData([]);
}
}
},
在mouseOver处理程序内部,'this'关键字指向悬停的数据点对象,而x和y属性指向时间和价格值。由于顶级图表和详细图表在 x 和 y 轴上共享相同的数据类型,我们只需简单地将一个数据点添加到顶级图表的追踪系列中。至于mouseOut事件,我们通过清空数据数组来重置系列。
应用图表点击事件
在本节中,我们将应用图表点击事件来为投资组合创建一个止损订单。止损订单是一个投资术语,指的是在未来指定日期/时间范围内,当股票达到价格阈值时卖出或买入股票。它通常用于限制损失或保护利润。
注意,顶层图表的右侧有一个空白区域。实际上,这是故意创建的,用于从当前日期开始的下一个 30 天范围。让我们突出显示该区域,以便未来日期出现在详细图表中:

如我们所见,详细图表中的线条系列在触及当前日期时立即停止。如果我们点击详细图表中未来日期的区域,将出现一个创建停止订单对话框。然后,图表上点击的 x,y 位置被转换为日期和价格,这些值随后被填充到对话框中。以下是对话框的截图:

如果需要,可以进一步调整到期日期和价格字段。一旦点击保存订单按钮,就会创建一个停止订单,并生成一对 x 和 y 绘图线来标记图表。以下是在图表上显示两个停止订单的截图:

让我们看看所有这些操作如何从代码中推导出来。首先,基于页面上声明的 HTML 表单创建 jQuery UI 对话框:
<div id='dialog'>
<form>
<fieldset>
<label for="expire">Expire at</label>
<input type=text name="expire" id="expire" size=9 ><br/><br/>
<select name='stopOrder' id='stopOrder'>
<option value='buy' selected>Buy</option>
<option value='sell'>Sell</option>
</select>
<label for="shares">no. of shares</label>
<input type="text" name="shares" id="shares" value="" size=7 class="text ui-widget-content ui-corner-all" />,
<label for="price">when market price reaches (in pences)</label>
<input type="text" name="price" id="price" value="" size=7 class="text ui-widget-content ui-corner-all" />
</fieldset>
</form>
</div>
然后定义详细图表的click事件处理程序,如下所示:
click: function(evt) {
// Only allow to prompt stop order dialog
// if the chart contains future time
if (!$.myApp.detailChart.futureDate) {
return;
}
// Based on what we click on the time, set
// input field inside the dialog
$('#expire').val(
Highcharts.dateFormat("%m/%d/%y",
evt.xAxis[0].value));
$('#price').val(
Highcharts.numberFormat(
evt.yAxis[0].value, 2));
// Display the form to setup stop order
$('#dialog').dialog("open");
}
第一个保护条件是查看详细图表中是否包含任何未来日期。如果存在未来日期,那么它将从click事件中提取 x 和 y 值,并将它们分配到表单输入字段中。之后,它调用 jQuery UI 对话框方法,在对话框中布局 HTML 表单,并显示它。
以下代码片段显示了如何定义 jQuery UI 对话框及其操作按钮。代码已编辑以提高可读性:
// Initiate stop order dialog
$( "#dialog" ).dialog({
// Dialog startup configuration –
// dimension, modal, title, etc
.... ,
buttons: [{
text: "Save Order",
click: function() {
// Check whether this dialog is called
// with a stop order id. If not, then
// assign a new stop order id
// Assign the dialog fields into an
// object - 'order'
....
// Store the stop order
$.myApp.stopOrders[id] = order;
// Remove plotlines if already exist.
// This can happen if we modify a stop
// order point
var xAxis = $.myApp.detailChart.xAxis[0];
xAxis.removePlotLine(id);
var yAxis = $.myApp.detailChart.yAxis[0];
yAxis.removePlotLine(id);
// Setup events handling for both
// x & y axis plotlines
var events = {
// Change the mouse cursor to pointer
// when the mouse is hovered above
// the plotlines
mouseover: function() { ... },
// Launch modify dialog when
// click on a plotline
click: function(evt) { ... }
};
// Create the plot lines for the stop
// order
xAxis.addPlotLine({
value: order.expire,
width: 2,
events: events,
color: (order.stopOrder == 'buy') ? $.myApp.boughtColor : $.myApp.soldColor,
id: id,
// Over both line series and
// plot line
zIndex: 3
});
yAxis.addPlotLine({
value: order.price,
width: 2,
color: (order.stopOrder == 'buy') ? $.myApp.boughtColor : $.myApp.soldColor,
id: id,
zIndex: 3,
events: events,
label: {
text: ((order.stopOrder == 'buy') ? 'SO-B by (' : 'SO-S by (') + Highcharts.dateFormat("%e %b %Y", parseInt(order.expire)) + ') @ ' + order.price,
align: 'right'
}
});
$('#dialog').dialog("close");
}
}, {
text: "Cancel",
click: function() {
$('#dialog').dialog("close");
}
}]
});
对话框设置代码稍微复杂一些。在保存订单按钮的处理程序中,它执行以下几个任务:
-
它从对话框中提取输入值。
-
它检查对话框是否以特定的停止订单
id打开。如果没有,则分配一个新的停止订单id,并将带有id的值存储到$.myApp.stopOrders中。 -
如果我们修改现有的停止订单,它会删除任何与
id匹配的现有绘图线。 -
它为 x 轴和 y 轴的绘图线设置
click和mouseover事件处理程序。 -
它在详细图表中使用在第 4 步中构建的事件定义创建 x 和 y 绘图线。
一个包含停止订单的场景是,用户可能在条件满足之前想要更改或删除一个停止订单。因此,在第 4 步中,绘图线上的click事件的目的就是弹出修改对话框。此外,我们希望在鼠标悬停在绘图线上时将鼠标光标更改为指针,以显示它是可点击的。
使用鼠标悬停事件更改绘图线上的鼠标光标
要更改绘图线上的鼠标光标,我们定义了如下mouseover事件处理程序:
mouseover: function() {
$.each(this.axis.plotLinesAndBands,
function(idx, plot) {
if (plot.id == id) {
plot.svgElem.element.style.cursor =
'pointer';
return false;
}
}
);
},
this关键字包含一个轴对象,悬停的绘图线属于该轴。由于每个轴可以有多个绘图线,我们需要遍历轴对象中plotLinesAndBands属性内的绘图线和绘图带数组。一旦通过匹配id找到目标绘图线,我们将深入内部元素并设置光标样式为'pointer'。以下截图显示了鼠标光标悬停在绘图线上:

使用点击事件设置绘图线动作
绘图线的click事件启动修改停止订单对话框以进行停止订单:
// Click on the prompt line
click: function(evt) {
// Retrieves the stop order object stored in
// $.myApp.stopOrders
$('#dialog').dialog("option",
"stopOrderId", id);
var stopOrder = $.myApp.stopOrders[id];
// Put the settings into the stop order form
$('#dialog').dialog("option", "title",
"Modify Stop Order");
$('#price').val(
Highcharts.numberFormat(
stopOrder.price, 2));
$('#stopOrder').val(stopOrder.stopOrder);
$('#shares').val(stopOrder.shares);
$('#expire').val(
Highcharts.dateFormat("%m/%d/%y",
stopOrder.expire));
// Add a remove button inside the dialog
var buttons =
$('#dialog').dialog("option", "buttons");
buttons.push({
text: 'Remove Order',
click: function() {
// Remove plot line and stop order
// settings
delete $.myApp.stopOrders[id];
var xAxis =
$.myApp.detailChart.xAxis[0];
xAxis.removePlotLine(id);
var yAxis =
$.myApp.detailChart.yAxis[0];
yAxis.removePlotLine(id);
// Set the dialog to original state
resetDialog();
$('#dialog').dialog("close");
}
});
$('#dialog').dialog("option",
"buttons", buttons);
$('#dialog').dialog("open");
}
click事件处理程序简单地检索停止订单设置,并将值放入修改停止订单对话框中。在启动对话框之前,将一个移除订单按钮添加到对话框中,按钮处理程序调用removePlotLine,并带有绘图线id。以下是对创建停止订单对话框的截图:

股票增长图表示例
我们接下来的示例(对于在线演示,请参阅joekuan.org/Learning_Highcharts/Chapter_11/chart2.html)是为了演示以下事件:
-
chart.events:addSeries -
plotOptions.series.events:click,checkboxClick, 和legendItemClick -
plotOptions.series.point.events:update和remove
假设我们想要根据过去的股票增长表现来制定一个长期投资组合。演示中包含了一个从两个系列开始的图表,即“投资组合”和“平均增长”,以及一个输入股票代码的表单。基本上,在这个演示中,我们输入一个股票代码,然后一个股票增长线系列就被插入到图表中。
因此,我们可以绘制多个股票收益趋势,并调整它们在投资组合中的比例,以观察平均线和投资组合线的表现。以下截图显示了初始屏幕:

从显示的股票系列中绘制平均系列
让我们查询两只股票,并点击平均图例以启用系列:

如预期的那样,平均线绘制在两条股票线之间。假设未来的增长与过去相似,如果我们对投资组合中的两只股票进行等额投资,平均线将预测未来的增长。让我们在图表上添加另一个股票代码:

新的增长线产生更高的收益,因此平均线自动调整并移动到顶部第二行。让我们看看它是如何实现的。以下是对应的图表配置代码:
$.myChart = new Highcharts.Chart({
chart: {
renderTo: 'container',
showAxes: true,
events: {
addSeries: function() { ... }
}
},
series: [{
visible: false,
name: 'Portfolio',
color: $.colorRange.shift(),
marker: { enabled: false },
events: {
legendItemClick: function(evt) { ... }
}
}, {
name: 'Average',
events: {
legendItemClick: function(evt) { ... }
},
color: $.colorRange.shift(),
visible: false,
marker: { enabled: false }
}, {
visible: false,
type: 'pie',
point: {
events: {
click: function(evt) { ... },
update: function(evt) { ... },
remove: function(evt) { ... }
}
},
center: [ '13%', '5%' ],
size: '30%',
dataLabels: { enabled: false }
}],
title: { text: 'Stocks Growth' },
credits: { enabled: false },
legend: {
enabled: true,
align: 'right',
layout: 'vertical',
verticalAlign: 'top'
},
yAxis: {
title: { text: 'Growth (%)' }
},
xAxis: { type: 'datetime' }
});
图表包含三个系列:投资组合、平均和用于编辑投资组合分布的饼图系列。
当我们点击带有股票代码的添加按钮时,调用showLoading方法在图表前放置一个加载遮罩,然后与服务器建立 Ajax 连接以查询股票收益数据。我们通过调用addSeries函数将新系列插入图表来实现 Ajax 处理程序。
一旦触发addSeries事件,这意味着数据已经返回并准备好绘图。在这种情况下,我们可以禁用图表加载遮罩,如下所示:
chart: {
.... ,
events: {
addSeries: function() {
this.hideLoading();
}
},
.... ,
以下是实现添加按钮动作的代码:
$('#add').button().on('click',
function() {
var symbol = $('#symbol').val().toLowerCase();
$.myChart.showLoading();
$.getJSON('./stockGrowth.php?symbol=' + symbol +
'&years=' + $.numOfYears,
function(stockData) {
// Build up the series data array
var seriesData = [];
if (!stockData.rows.length) {
return;
}
$.symbols.push({
symbol: symbol,
name: stockData.name
});
$.each(stockData.rows,
function(idx, data) {
seriesData.push([
data.date * 1000,
data.growth ]);
});
$.myChart.addSeries({
events: {
// Remove the stock series
click: { ... },
// Include the stock into portfolio
checkboxClick: { ... }
},
data: seriesData,
name: stockData.name,
marker: { enabled: false },
stickyTracking: false,
showCheckbox: true,
// Because we can add/remove series,
// we need to make sure the chosen
// color used in the visible series
color: $.colorRange.shift()
}, false);
updateAvg(false);
$.myChart.redraw();
} // function (stockData)
); //getJSON
});
我们从 Ajax 返回的数据中构建一个系列配置对象。在这个新的系列配置中,我们将图例项旁边的复选框的showCheckbox选项设置为true。配置中还添加了几个事件,click和checkboxClick,稍后讨论。
在调用addSeries方法之后,我们接着调用一个预定义的例程updateAvg,它只重新计算并重新绘制显示的平均线。
从前面的平均系列事件定义中回忆,我们使用legendItemClick事件来捕捉在图例框中点击平均系列的情况:
series: {
...
}, {
name: 'Average',
events: {
legendItemClick: function(evt) {
if (!this.visible) {
updateAvg();
}
}
},
.....
前面的代码意味着,如果平均系列当前不在可见状态,那么在处理程序返回后,系列将变为可见。因此,它计算平均值并显示系列。
使用系列点击事件启动对话框
我们不是通过点击图例项来启用或禁用股票收益线,而是可能想要完全移除系列线。在这种情况下,我们使用click事件来完成,如下所示:
$.myChart.addSeries({
events: {
// Launch a confirm dialog box to delete
// the series
click: function() {
// Save the clicked series into the dialog
$("#dialog-confirm").dialog("option",
"seriesIdx", this.index);
$("#dialog-confirm").dialog("option",
"seriesName", this.name);
$("#removeName").text(this.name);
$("#dialog-confirm").dialog("open");
},
// Include the stock into portfolio
checkboxClick: function(evt) { ... }
},
....
});
点击动作启动一个确认对话框,用于从图表中移除系列。我们在对话框中存储被点击的系列('this'关键字)信息。移除按钮的处理程序使用这些数据来移除系列,并在显示的情况下重新计算平均系列。以下截图显示了这一过程:

由于增长线系列配置了showCheckbox选项,我们可以定义checkboxClick事件,当复选框被勾选时启动饼图:
checkboxClick: function(evt) {
updatePie(this, evt.checked);
}
在此演示中,updatePie 函数在多个地方被调用,例如,在删除系列、图例复选框被选中时等。以下是代码的简短版本:
var updatePie = function(seriesObj, checked) {
var index = seriesObj.index;
// Loop through the stock series. If checkbox
// checked, then compute the equal distribution
// percentage for the pie series data
for (i = $.pfloIdx + 1;
i < $.myChart.series.length; i++) {
var insert = (i == index) ? checked : $.myChart.series[i].selected;
if (insert) {
data.push({
name: $.myChart.series[i].name,
y: parseFloat((100 / count).toFixed(2)),
color: $.myChart.series[i].color
});
}
}
// Update the pie chart series
$.myChart.series[$.pfloIdx].setData(data, false);
$.myChart.series[$.pfloIdx].show();
};
上述代码片段基本上遍历股票系列数组,并检查它是否被选中。如果是,则以均匀分布的方式将股票包含在饼图系列中。然后,如果有一个或多个条目,将显示饼图。
使用数据点的点击、更新和删除事件编辑饼图的切片
投资组合中所有股票的分布可能不会均匀。因此,我们可以通过修改饼图内部的部分来增强示例。当点击饼图的某个切片时,会弹出一个对话框。这允许我们调整或删除投资组合内的部分。以下截图显示了这一点:

更新对话框中的更新投资组合按钮使用 Point.update 方法更新饼图切片,而删除按钮调用 Point.remove 方法。这两个调用分别触发 update 和 remove 事件。在这里,我们在饼图中定义了数据点的 click、update 和 remove 事件:
series: {
....
},
visible: false,
type: 'pie',
point: {
events: {
// Bring up the modify dialog box
click: function(evt) {
// Store the clicked pie slice
// detail into the dialog box
$('#updateName').text(evt.point.name);
$('#percentage').val(evt.point.y);
$('#dialog-form').dialog("option",
"pieSlice", evt.point);
$('#dialog-form').dialog("open");
},
// Once the Update button is clicked,
// the pie slice portion is updated
// Hence, this event is triggered and the
// portfolio series is updated
update: function(evt) {
updatePortfolio();
},
// Pie slice is removed, unselect the series
// in the legend checkbox and update the
// portfolio series
remove: function(evt) {
var series = nameToSeries(this.name);
series && series.select(false);
updatePortfolio();
}
}
}
click 事件函数将点击的切片(点对象)存储在修改对话框中并启动它。在对话框内部,更新和删除按钮的按钮处理程序随后提取这些存储的点对象,调用饼图,并使用对象的更新或删除方法来反映显示的饼图中的更改。这随后触发点 update 或 remove 事件处理程序并调用预定义的函数 updatePortfolio,该函数重新计算包含股票的新分布。因此,让我们将过去最佳表现股票的分布更新为 80% 的比例,其他两种股票各 10%。投资组合系列会自动从 update 事件中调整自身,如下面的截图所示:

理解页面初始化
在本节中,我们将探讨为什么我们不使用传统的 DOM-ready 方法来为移动页面运行初始化代码。假设一个页面的内容需要某种初始化,那么使用传统的 DOM-ready 方法 $.ready 可能会有负面影响。这是因为 $.ready 方法会在文档中所有 DOM 加载完毕后立即运行。换句话说,如果初始化代码在 DOM ready 处理器内部,我们就无法控制其运行的时间。
然而,jQM 提供了一个特定的事件,pageinit,用于处理这种情况。我们只需要在 <div data-role='page'> 标记内分配一个 id 值,然后为该 id 值定义 pageinit 事件处理器。每当一个页面即将被初始化以显示时,这个事件就会被触发。请注意,$.ready 方法仍然会被调用,但我们只是在 jQM 中不使用它。为了演示这个概念,让我们使用之前的带有额外 $.ready 调用的多页面示例:
<script type="text/javascript">
$(document).on('pageinit', '#main_page', function() {
alert('jQuery Mobile: pageinit for Main page');
});
$(document).on('pageinit', '#config', function() {
alert('jQuery Mobile: pageinit for Config page');
});
$(document).ready(function() {
alert('jQuery ready');
});
</script>
</head>
<body>
<!-- MAIN PAGE -->
<div data-role="page" id='main_page'>
<div data-role="header">
<h1>jQuery Mobile</h1>
<a href="#config" data-rel='dialog'
data-icon="gear"
class='ui-btn-right'>Options</a>
</div><!-- /header -->
<div data-role="content" id=''>
</div>
</div><!-- /page -->
<!-- CONFIG PAGE -->
<div data-role="page" id='config' >
<div data-role="header">
<h1>Config</h1>
</div><!-- /header -->
<div data-role="content">
<a href="" data-role="button"
data-rel="back" >Cancel</a>
</div>
</div><!-- /page -->
在这个例子中定义了两个移动页面:main_page 和 config。每个移动页面都与它的 pageinit 事件处理器相关联。使用 $.ready 方法,我们可以观察与其他 pageinit 事件的调用顺序。当我们第一次将文档加载到浏览器中时,我们看到以下截图:

记住,jQM 总是显示 HTML 体中的第一个页面。这意味着main_page的pageinit事件在main_page的 DOM 完全加载并初始化用于显示时立即触发。同样重要的是要理解,在执行点,后续config页面的 DOM 尚未加载。当我们触摸确定按钮时,执行继续,并加载config页面的 DOM。因此,文档中的所有 DOM 都已加载,然后调用$.ready方法;它显示了以下截图中的第二个警告消息:

当我们触摸确定按钮时,警告框消失,控制权返回到浏览器。现在,如果我们触摸右上角的选项按钮,config对话框页面被初始化并在屏幕上显示。因此,调用config页面的pageinit处理程序:

移动页面之间的链接
jQM 的第二个重要概念是如何将移动页面链接在一起。理解这个概念可以帮助我们设计具有流畅用户体验的 Web 移动应用程序。在 jQM 中,有两种方式可以加载外部移动页面:HTTP 和 Ajax。根据我们如何设置data-属性,它解释href值并决定加载移动页面的方式。默认情况下,除了第一个文档加载是正常的 HTTP 传输外,移动页面通过 Ajax 加载。
以下块图解释了如何在文档中管理多个移动页面块:

当一个移动页面调用另一个移动页面时,jQM 框架基本上解析href值。由于这是一个锚点引用,它表示这是一个内部移动页面块。框架通过匹配id值从当前 DOM 中定位页面块。然后它初始化并渲染页面,这也会触发页面 B 的pageinit事件,如前一个块图所示。
假设我们有两个独立的 HTML 文档,其中一个页面上的按钮引用了另一个文档。以下块图描述了该场景:

在此情况下,我们添加一个属性,data-ajax="false"(为了简化 JavaScript 代码的管理),告诉 jQM 这个按钮需要一个文档加载而不是后台 Ajax 加载。这很重要,因为否则<script>标签内的pageinit处理代码(或任何 JavaScript 文件)将不会为新移动页面B.html加载。
注意
JavaScript 代码可以嵌入到移动页面块内的<script>标签中并执行。这种方法的缺点是它需要更多的代码管理,因为每个页面块都有自己的pageinit处理代码。
有一种加载多个文档中 Ajax 的替代方法,但我们将在这里省略。这已经足够实现一个简单的移动网络应用。读者可以从 jQuery Mobile 文档中了解更多信息。
高级图表在触摸屏环境中的使用
Highcharts 的好处是它可以在桌面浏览器和移动网络环境中完美运行,无需更改任何代码。唯一需要考虑的部分是事件处理,因为移动设备都是基于触摸屏的,这意味着鼠标光标是不可见的。
在 Highcharts 中,所有鼠标悬停事件仍然可以在触摸设备上触发,尽管鼠标光标没有显示。例如,如果我们定义了一个具有 mouseOut、mouseOver 和 click 事件处理的系列。如果我们触摸该系列,mouseOver 和 click 事件都会被触发。然而,如果我们触摸另一个系列导致之前选中的系列被取消选中,第一个系列的 mouseOut 事件就会被触发。不用说,事件的顺序会与真实的光标设备不同。一般来说,我们应该避免在基于触摸屏的设备中使用任何鼠标悬停事件。
在下一节中,我们将学习如何将 jQM 与 Highcharts 集成,包括如何将触摸事件应用于图表,如何使用图表 click 事件启动另一个图表和移动页面等。
使用奥运奖牌表应用整合 Highcharts 和 jQuery Mobile
在本节中,我们将构建一个用于浏览 2012 年奥运奖牌表结果的移动应用。此应用仅在 iPhone 和 iPad 上进行了测试。启动屏幕提供了四个菜单来查找结果,如下面的截图所示:

初始页面由指向其他页面的四个超链接列表组成,如下面的代码所示:
<head>
<!-- CDN load of Highcharts, jQuery and jQM -->
....
</head>
<body>
<div data-role="page">
<div data-role="header">
<h1>London Olympic 2012 </h1>
</div><!-- /header -->
<div data-role="content">
<ul data-role="listview" data-inset="true">
<li><a href="./gold.html"
data-ajax="false" >Top 10 countries by gold</a></li>
<li><a href="./medals.html"
data-ajax="false" >Top 10 countries by medals</a></li>
<li><a href="#">A-Z countries</a></li>
<li><a href="#">A-Z olympians</a></li>
</ul>
</div>
</div><!-- /page -->
</body>
因此,当点击金牌前 10 名国家按钮时,gold.html 文件是通过 HTTP 加载的(不是 Ajax),因为我们定义了 data-ajax="false"。由于这是一个 HTTP 加载,整个页面 gold.html 以及 <script> 标签中正在执行的所有内容都将加载到浏览器中。
加载金牌页面
以下为 gold.html 文件的内容:
<head>
<!-- CDN load of Highcharts, jQuery and jQM -->
....
<script type="text/javascript" src="img/common.js"></script>
<script type="text/javascript" src="img/gold.js"></script>
</head>
<body>
<div data-role="page" id='gold_chart'>
<div data-role="header">
<a href="olympic.html" data-icon="home"
data-iconpos="notext">Home</a>
<h1>London Olympic 2012 - Top 10 countries by gold</h1>
<a href="#options" data-rel='dialog'
data-icon="gear" id='options'>Options</a>
</div><!-- /header -->
<div data-role="content">
<div id='chart_container'></div>
</div>
</div><!-- /page -->
<!-- options dialog -->
<div data-role="page" id='options' >
....
</div>
</body>
由于整个 HTML 文档都是通过 HTTP 加载到浏览器中的,因此 common.js 和 gold.js 文件也被加载(参见 joekuan.org/Learning_Highcharts/Chapter_12/)。common.js 文件包含在演示中共享的通用代码,例如设备检测、方向检测、图表创建等。gold.js 文件包含 gold.html 文件中所有移动页面的 pageinit 处理器代码。作为移动页面块,gold_chart 是文档中首先定义的块,因此它会被自动加载并渲染到显示中;因此,gold_chart 页面块的 pageinit 事件被触发。
检测设备属性
对于检测移动设备,技术范围从 navigator.userAgent 选项的字符串匹配、jQuery.support、jQuery.browser(已弃用)、CSS 媒体查询,到第三方插件如 Modernizer(详情请见 modernizr.com/)。然而,并没有一个标准的做法。或许是因为兼容性检查的多样化需求。本书的范围并不包括对每种技术的优缺点的讨论。对于这个演示,我们只对屏幕尺寸的差异感兴趣;也就是说,如果显示空间更大(例如平板设备),则我们在图表中显示完整的国家名称,而不是用于较小设备的国家代码。我们假设以下技术足以区分手机和平板设备:
function getDevice() {
return ($(window).width() > 320) ? "tablet" : "phone";
}
$(window).width 属性返回设备以像素为单位的宽度,无论设备方向如何。至于获取当前设备方向,我们有以下方法:
function getOrientation() {
return (window.innerHeight/window.innerWidth) > 1 ?
'portrait' : 'landscape';
}
在移动设备上绘制 Highcharts 图表
以下是为 gold_chart 移动页面编写的 pageinit 处理器代码:
$(document).on('pageinit', '#gold_chart',
function() {
var device = getDevice();
// current orientation
var orientation = getOrientation();
$.olympicApp = $.olympicApp || {};
// Setup the point click events for all the
// country medal charts – used by plotGoldChart method
var pointEvt = {
events: {
click: function(evt) { ... }
}
};
// Switch between column and pie chart
$('#chart_container').on('swipeleft',
function(evt) { ... } );
$('#chart_container').on('swiperight',
function(evt) { ... } );
// Switch between column and bar chart on
// smaller display
$(document).on('orientationchange',
function(evt) { ... } );
// General method for plotting gold medal chart
// Used by dialog box also to toggle chart options
// such as column stacking, labels etc
$.olympicApp.plotGoldChart = function(chart, options) {
.....
};
// Create and display Highcharts for gold medal chart
$.olympicApp.goldChart = createChart({
device: device,
orientation: orientation,
load: $.olympicApp.plotGoldChart,
type: (orientation == 'landscape') ?
'bar' : 'column',
// legend and title settings specific
....
});
}
);
后续将讨论触摸事件,如 swipeleft、swiperight 和 orientationchange。当用户在金牌图表中的国家条上点击时,事件处理器 pointEvt 会进一步深入到另一个图表。我们也会稍后探讨这种交互。首先让我们关注代码的最后部分,这部分用于创建图表。createChart 方法是一个通用流程,用于创建具有所有图表移动页面共享的常见选项的 Highcharts 图表。例如,renderTo 选项始终设置为 chart_container,它位于 data-role='content' 属性内部。以下代码展示了 createChart 的实现:
// Main routine for creating chart
function createChart(options) {
// Based on the device display and current orientation
// Work out the spacing options, labels orientation
return new Highcharts.Chart({
chart: {
renderTo: 'chart_container',
type: options.type,
events: {
load: function(chart) {
// Execute the page general plot routine
options.load &&
options.load(chart, options);
}
},
spacingLeft: ....,
....
},
title: { text: options.title },
xAxis: {
labels: ....
},
....
});
}
注意,在 options 参数中没有定义系列,并且 options.load 属性被设置为在图表创建并加载到浏览器中时调用 plotGoldChart 函数。以下代码片段是 plotGoldChart 函数的一部分:
// chart is the target chart object to apply new settings,
// options is an object containing the new settings
$.olympicApp.plotGoldChart =
function(chart, options) {
// Get the top 10 countries with the
// most gold medals
$.getJSON('./gold_10.json',
function(result) {
var device = getDevice();
// Remove any series in the chart if exists
....
// If display pie chart,
// then we only plot the gold medals series
if (options && options.type == 'pie') {
var goldOpt = {
data: [],
point: pointEvt,
type: 'pie',
dataLabels: { ... }
};
$.each(result.rows,
function(idx, data) {
goldOpt.data.push({
// If device is phone,
// set short country labels
// otherwise full names
name: (device === 'phone') ?
data.code : data.country,
y: data.gold,
color: pieGoldColors[idx]
});
});
chart.addSeries(goldOpt, false);
// Disable option button for pie chart
$('#options').addClass('ui-disabled');
} else {
// Sorting out chart option - stacking,
// dataLabels if specified in the option
// parameters
var dataLabel = (options &&
options.dataLabel) ? {
enabled: true,
rotation:
(getOrientation() == 'landscape') ?
0 : -45,
color: '#404040'
} : {
enabled: false
} ;
var stacking = (options &&
options.stacking) || null;
var bronzeOpt = {
data: [], name: 'Bronze',
color: '#BE9275',
stacking: stacking,
dataLabels: dataLabel,
point: pointEvt
};
var silverOpt = {
data: [], name: 'Silver',
color: '#B5B5B5',
stacking: stacking,
dataLabels: dataLabel,
point: pointEvt
};
var goldOpt = {
data: [],
name: 'Gold',
color: '#FFB400',
point: pointEvt,
stacking: stacking,
dataLabels: dataLabel
};
var category = [];
$.each(result.rows,
function(idx, data) {
// Display country code on phone
// otherwise name
category.push((device === 'phone') ?
data.code : data.country);
goldOpt.data.push(data.gold);
silverOpt.data.push(data.silver);
bronzeOpt.data.push(data.bronze);
});
chart.xAxis[0].setCategories(category);
chart.addSeries(bronzeOpt, false);
chart.addSeries(silverOpt, false);
chart.addSeries(goldOpt, false);
// Enable the option button for the
// column chart
$('#options').removeClass('ui-disabled');
}
chart.redraw();
}); // function(result)
}; // function(chart, …
plotGoldChart方法是一个通用例程,用于将一个系列绘制到现有图表中。options参数是一个配置对象,包含要应用到图表中的新设置。首先,该函数调用 Ajax 调用gold_10.json以获取获得最多金牌的前 10 个国家。以下是 JSON 格式的结果:
{"rows": [{
"country":"United States","gold":46,"silver":29,"bronze":29,
"total":104,"code":"USA"
},{
"country":"China","gold":38,"silver":27,"bronze":23,
"total":88,"code":"CHN"
},{
"country":"Great Britain & N.Ireland",
"gold":29,"silver":17,"bronze":19, "total":65,"code":"GBR"
},{
"country":"Russia Federation",
"gold":24,"silver":26,"bronze":32,"total":82,"code":"RUS"
}, {
....
}]
当结果返回后,处理函数会检查options参数中的系列类型、设备方向以及其他字段(来自config对话框的堆叠和数据标签,我们将在后面讨论)。然后,根据设置创建图表。如果type属性是column,则创建三个名为Gold、Silver和Bronze的柱状系列,并配置了点click事件。如果type值是pie,则创建一个金牌的单一饼状系列,颜色逐渐变化并带有数据标签。
因此,当gold_chart页面首次加载时,会创建并显示一个柱状图。以下截图显示了初始的纵向模式柱状图:

如果我们触摸图例项以显示银牌和铜牌的数量,图表看起来如下截图所示:

使用 jQuery Mobile 对话框切换图形选项
右上角的选项按钮仅在当前显示的图表是柱状图时启用。它启动一个用于切换堆叠列和数据标签的选项对话框。以下代码是用于对话框的移动页面:
<div data-role="page" id='options' >
<div data-role="header">
<h1>Config</h1>
</div><!-- /header -->
<div data-role="content">
<label for="stacking">Stacking:</label>
<select name="stacking" id="stacking"
data-role="slider">
<option selected="selected">Off</option>
<option>On</option>
</select>
<label for="dataLabel">Show Values:</label>
<select name="dataLabel" id="dataLabel"
data-role="slider">
<option selected="selected">Off</option>
<option>On</option>
</select>
<a href="#" data-role="button"
data-rel="back" id='updateChart' >Update</a>
<a href="#" data-role="button"
data-rel="back" >Cancel</a>
</div>
</div><!-- /page -->
jQM 中的<select>标记被渲染为具有data-role='slider'属性的滑块开关,而超链接被渲染为具有data-role='button'属性的对话框按钮。以下截图显示了对话框页面:

同样,我们为对话框页面编写了pageinit处理程序以初始化更新按钮动作:
$(document).on('pageinit', '#options',
function() {
var myApp = $.olympicApp;
$('#updateChart').click(function() {
var stacking =
($('#stacking').val() === 'Off') ?
null: 'normal';
var dataLabel =
!($('#dataLabel').val() == 'off');
myApp.plotGoldChart(myApp.goldChart, {
stacking: stacking,
dataLabel: dataLabel
});
});
});
实际上,按钮的动作代码非常简单。因为我们用data-rel='back'属性定义了更新按钮,所以当我们点击按钮时,对话框就会关闭,我们返回到上一页。<select>输入框的选项值会被传递给plotGoldChart例程以重新绘制当前图表。以下是一个仅开启显示值的截图:

以下截图显示了一个同时开启了堆叠和数据标签的柱状图:

通过 swipeleft 手势事件更改图形展示
在这里,我们通过向图表添加swipeleft事件来增强用户体验。我们试图实现的是在现有的柱状图上从右侧向左侧施加滑动动作。这个动作将柱状图切换为具有相同数据集的饼图,反之亦然,使用swiperight动作:
// Switch to pie chart
$('#chart_container').on('swipeleft',
function(evt) {
var myApp = $.olympicApp;
if (myApp.goldChart.series[0].type == 'column') {
myApp.plotGoldChart(myApp.goldChart, {
type: 'pie'
});
}
});
// Switch back to default column chart
$('#chart_container').on('swiperight',
function(evt) {
var myApp = $.olympicApp;
if (myApp.goldChart.series[0].type == 'pie') {
myApp.plotGoldChart(myApp.goldChart);
}
});
处理程序内的守卫条件是为了停止以相同展示方式重绘图表。以下是swipeleft动作后的视图:

使用 orientationchange 事件切换图形方向
假设我们在横屏位置使用触摸手机设备查看柱状图。如果我们旋转设备,图表将自动调整大小,但 y 轴的刻度会被压缩。因此,在比较每个国家的表现时,清晰度会降低。为了克服这个问题,我们使用另一个 jQuery Mobile 事件orientationchange,该事件在设备旋转时触发。以下是处理程序的实现:
var device = ($(window).width() > 750) ? 'tablet' : 'phone';
// Switch between vertical and horizontal bar
$(document).on('orientationchange',
function(evt) {
// We only do this for phone device because
// the display is narrow
// Tablet device have enough space, the
// chart will look fine in both orientations
if (device == 'phone') {
var myApp = $.olympicApp;
var orientation = getOrientation();
// I have to destroy the chart and recreate
// to get the inverted axes and legend box
// relocated
myApp.goldChart.destroy();
// create the chart optimized for horizontal
// view and invert the axis.
myApp.goldChart = createChart({
device: device,
orientation: orientation,
inverted: (orientation === 'landscape'),
load: myApp.plotGoldChart,
legend: ....,
});
// Hide the address bar
window.scrollTo(0,1);
}
}
);
我们将图表的inverted选项设置为true以交换 x 轴和 y 轴,并将图例定位在右下角。在配置中还为图表的load事件设置了一个方法。最终,生成了一个倒置的图表,如下面的截图所示:

以下是从平板设备上拍摄的金牌和银牌图表的截图:

plotGoldChart方法检测到更大的显示区域,并使用setCategories方法渲染图表(使用完整的国家名称而不是 JSON 结果中的国家代码)。
使用点点击事件深入数据
到目前为止,我们只对按金牌数量排序的前几名国家进行了调整。让我们看看我们如何使用 Highcharts 事件在 jQuery Mobile 中导航其他图表。回到chart_page的pageinit处理程序代码,我们声明了pointEvt变量,它是金牌图表中所有系列共享的click事件处理程序。以下是对该事件的代码:
var pointEvt = {
events: {
click: function(evt) {
document.location.href = './sport.html?country='
// Country code or name
+ encodeURIComponent(this.category) +
// Medal color
'&color=' + this.series.name;
}
}
};
此事件由触摸柱状图中的柱或饼图中的切片触发。因此,它加载了一个新的文档页面(sport.html),其中包含柱状图。文档页面的 URL 在处理程序内部构建,使用所选国家代码和奖牌颜色作为参数。该页面的 HTML 内容列在下一节中。this关键字指的是被点击的数据点(即国家柱)。柱状图显示了所选国家赢得奖牌的体育项目列表,以及奖牌颜色。以下截图显示了英国和北爱尔兰赢得金牌的体育项目列表图:

在新页面内部,它使用与金牌国家图表类似的代码来生成前面屏幕截图所示的图表。唯一的区别是它嵌入了点click回调。我们将在下一节中看到。
使用点点击事件构建动态内容对话框
现在我们知道了哪些运动在奥运会上获得了金牌,但我们还想进一步找出获奖者是谁。让我们触摸图表中的田径条目。出现一个对话框,并列出运动员的缩略图、他们的名字、照片以及他们的比赛信息,如下面的屏幕截图所示:

注意,前一个屏幕截图显示的对话框不是静态的 HTML 页面。这是通过一个点click事件构建的,并从结果动态构建对话框内容。问题是,为了在 jQM 中启动对话框页面,我们需要在页面上某个地方有一个按钮来开始。技巧是创建一个 jQM 隐藏按钮和一个程序,从事件处理器内部调用点击动作。以下代码是隐藏按钮和对话框页面的 HTML(sport.html):
<!-- hidden dialog -->
<a id='hiddenDialog' href="#atheletes_dialog"
data-rel="dialog" data-transition="pop"
style='display:none;'></a>
<!-- medalists page -->
<div data-role="page" id='atheletes_dialog' >
<div data-role="header">
<h1></h1>
</div><!-- /header -->
<div data-role="content">
<ul data-role="listview" data-inset="true"
id='atheletes_list' >
</ul>
</div>
<a href="" data-role="button" data-rel="back">Cancel</a>
</div><!-- /page -->
以下是对体育图表的click处理器的实现:
point: {
events: {
click: function(evt) {
var params = {
country : urlParams.country,
color : urlParams.color.toLowerCase(),
sport : this.category
};
// Set the title for dialog
$('#atheletes_dialog h1').text(this.category +
" (" + urlParams.country + ") - " +
urlParams.color + " medalists");
// Simulate a button click to launch the
// list view dialog
$('#hiddenDialog').click();
// Launch ajax query, append the result into
// a list and launch the dialog
$.getJSON('./olympic.php', params,
function(result) {
$("#atheletes_list").empty();
$.each(result.rows,
function(idx, athelete) {
// Formatting the image, name, and
// the sport event
var content = "<li><img src='" +
athelete.image + "' />" + "<h3>" +
athelete.name + "</h3><p><strong>"
+ athelete.event +
"</strong></p><p>" +
athelete.desc + "</p></li>";
$("#atheletes_list").append(content);
});
// Need this to apply the format to
// the new entry
$('#atheletes_list').listview('refresh');
}); // getJSON
}
}
首先,我们为即将发布的对话框页面准备标题。然后,我们触发一个动作点击到隐藏按钮,调用如下:
$('#hiddenDialog').click();
这反过来又生成一个点击事件来启动对话框页面。然后,我们发出一个 Ajax 查询,获取当前选定国家、奖牌颜色和运动的获奖者列表。服务器页面olympic.php包含每个国家的奥运结果。它根据 URL 参数对结果进行排序,并以 JSON 格式格式化排序后的列表。然后我们将 JSON 结果中的每个项目转换为并插入到<ul>列表atheletes_list中。
将手势变化(捏合动作)事件应用于饼图
到目前为止,我们只探索了涉及单个触摸点的动作。我们的下一个目标是学习如何使用多指应用更高级的动作事件。最常见的一种动作是捏合放大/缩小。iOS 的 Safari 浏览器通过 gesturestart、gesturechange 和 gestureend 事件支持这种动作。当有两个或更多手指触摸屏幕时,会触发 gesturestart 事件。然后,当手指在屏幕上移动时,会触发 gesturechange 事件。当手指离开屏幕时,会生成 gestureend 事件。在将控制权返回给事件处理器时,如果动作被识别,事件对象中的某个属性将被更新。例如,事件对象中的 scale 属性在捏合放大时设置为大于 1.0,在捏合缩小时设置为小于 1.0。有关 GestureEvent 类的参考,请参阅 developer.apple.com/library/mac/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html。
在本节中,我们将捏合动作应用于饼图。对于捏合放大的动作,我们将饼图转换为带有额外信息的环形图——反之亦然,对于捏合缩小的动作,将环形图转换回饼图。首先,让我们启动一个新的图表,按奖牌数量排名前 10 的国家,这是前端菜单的第二项。以下截图是图表的输出:

当我们执行捏合放大的动作时,图表将重新绘制,如下面的截图所示:

外环显示了每个国家每种颜色奖牌的比例。此外,原始饼图的数据标签向内移动,为外环腾出空间。让我们看看 gesturechange 事件是如何实现的。以下是在 pageinit 处理器内部的代码:
$('#chart_container').on('gesturechange',
function(evt) {
evt = evt.originalEvent || evt;
var myApp = $.olympicApp;
if (evt.scale > 1) {
// Pinch open - from pie chart to
// donut chart
if (!myApp.medalChart.series[1].visible) {
myApp.medalChart.destroy();
myApp.medalChart = createChart({
orientation: getOrientation(),
device: device,
outerRing: true,
load: myApp.plotMedalChart
});
}
} else if (myApp.medalChart.series[1].visible) {
// Pinch close
myApp.medalChart.destroy();
myApp.medalChart = createChart({
orientation: getOrientation(),
device: device,
load: myApp.plotMedalChart
});
}
});
我们将手势事件绑定到图表容器。每当在屏幕上执行多指手势操作时,例如捏合或旋转动作,都会调用此事件处理器。为了确保这是一个捏合动作,我们需要查看由 jQuery 层包装的浏览器生成的原始事件。我们将检查是否设置了 scale 属性,并决定它是捏合放大还是缩小,然后根据需要重新创建饼图或环形图。
摘要
本章的目标是在移动触摸设备上部署 Highcharts 图表。为了做到这一点,我们使用了移动网页开发框架 jQuery Mobile。我们简要介绍了框架的概念。然后,我们探讨了如何将 Highcharts 与 jQuery Mobile 集成。
然后,我们演示了一个移动应用程序,以展示 2012 年奥运会的奖牌榜结果。我们使用 jQuery Mobile 对话页面构建了一个图表菜单,然后展示了如何使用单点触控、多点触控和方向事件在图表之间导航。我们还展示了如何使用 Highcharts 的点击事件动态构建对话页面。
在下一章中,我们将学习如何将 Highcharts 与 ExtJs 结合使用,这是一个非常强大且流行的 富互联网应用 (RIA) 框架,用于构建桌面风格的应用程序。
第十三章. Highcharts 和 Ext JS
本章从 Sencha 的 Ext JS 介绍开始。由于 Ext JS 框架涵盖了广泛的功能,它包含了一个庞大的类集合。因此,我们将给出一个小集合的快速指南,特别是对于可能用于 Highcharts 的用户界面组件。然后,我们将学习我们为 Ext JS 提供的 Highcharts 扩展以及如何在 Ext JS 中创建 Highcharts 图表。我们还将了解扩展提供的一小部分 API。之后,我们将使用网络数据构建一个简单的应用程序来演示 Ext JS 组件如何与 Highcharts 交互。最后,我们将简要查看一个与 Highcharts 一起工作的商业 Ext JS 应用程序。在本章中,我们将涵盖以下主题:
-
介绍并快速讲解 Sencha Ext JS 类
-
介绍 Ext JS 的 Highcharts 扩展
-
展示如何将扩展的现有 Highcharts 配置进行转换
-
为扩展准备 Ext JS JsonStore 对象
-
描述扩展模块提供的 API
-
说明如何使用 Highcharts 扩展创建 Ext JS 应用程序
Sencha Ext JS 的简要介绍
Sencha 的 Ext JS 是市场上最全面的 富互联网应用(RIA)框架之一。RIA 框架可以生成一个像桌面应用程序一样表现的前端网页。Ext JS 支持许多功能,如代理存储、图表、管理 SVG、标签页、工具栏、多种不同的表单输入等。还有其他流行的 RIA 框架,例如基于 Java 的 Google Web Toolkit(GWT)和基于 Python 的 Dojo。这两个框架都可以通过第三方贡献的软件与 Highcharts 集成。
注意
请参阅 第三方实现 部分的 www.highcharts.com/download,以获取其他开发者贡献的软件完整列表。
Highcharts 扩展最初由 Daniel Kloosterman 为 Ext JS 2+ 编写,作为一个适配器,因为当时它不支持任何图表。在 Ext JS 3 中,它开始采用 YUI 图表库作为图表解决方案。然而,这些图表缺乏功能和样式,主要缺点是它们需要 Flash 才能运行。自从 Ext JS 3.1 以来,我一直维护 Highcharts 扩展,并添加了诸如支持饼图和增强一些 API 等功能。
虽然 Ext JS 4 自带图表库,但一些用户仍然更喜欢 Highcharts 而不是 Ext JS 4 图表,因为 Highcharts 在样式和灵活性方面更胜一筹。此外,Ext JS 4 可以与版本 3 代码并行运行,因此增强扩展以原生支持 Ext JS 4 是很有必要的,我已经实现了这一点。扩展实现始终遵循原始方法,即尽可能保留 Highcharts 配置的使用。
在撰写本文时,Ext JS 5 刚刚发布,从 Ext JS 4 到 Ext JS 5 的变化并不像从 Ext JS 3 到 Ext JS 4 那样剧烈。Highcharts 扩展已被更新,以完全兼容 Ext JS 4 和 5。在本章中,我们将专注于使用 Ext JS 5。所有示例都简单地来自上一版,该版基于 Ext JS 4,并且已更新以与 Ext JS 5 一起工作。
在线有演示 joekuan.org/demos/Highcharts_Sencha/desktop.extjs5/,并且可以从 github.com/JoeKuan/Highcharts_Sencha/ 下载扩展。
与 jQuery UI 不同,Ext JS 应用程序是用纯 JavaScript 编程的,无需与 HTML 标记协作或调整特定的 CSS 类(严格来说,有时需要与 HTML 和 CSS 进行接口,但这并不常见,而且只是小量)。这使得程序员能够专注于用单一语言开发整个 Web 应用程序,并专注于应用程序逻辑。这也推动服务器端开发仅限于数据操作,与一些使用服务器端语言与 HTML 和 CSS 一起提供客户端页面的方法不同。
从技术上讲,JavaScript 没有类:函数本身就是一个对象。Ext JS 框架通过类方法提供对其组件的访问,这些组件以分层的方式组织。在本章中,我们将使用“类”一词来指代 Ext JS 类。
Ext JS 组件快速浏览
Ext JS 中有大量的类,本书的范围超出了介绍它们的范围。Sencha 提供了三种类型的在线文档,在质量和数量方面:参考手册、教程(书面和视频)以及工作演示。强烈建议您花充足的时间审查这些材料。在本节中,对一些组件进行了非常简要的介绍,特别是那些可能需要与 Highcharts 接口的部分。但这章绝对不足以让您开始使用 Ext JS 进行编程,但应该足以让您有一个大致的概念。
实现和加载 Ext JS 代码
一个 Ext JS 应用程序始终可以分成多个 JavaScript 文件,但它们应该始终从一个 HTML 文件开始。以下代码片段演示了如何从一个 HTML 文件启动 Ext JS:
<html>
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=UTF-8">
<title>Highcharts for Ext JS 5</title>
// At the time of writing, there is no CDN for ExtJs 5
// Download http://www.sencha.com/products/extjs/download/ext-js-5.0.0/3164
<link rel="stylesheet" type="text/css"
href="/extjs5/packages/ext-theme-classic/build/resources/ext-theme-classic-all.css" />
</head>
<body></body>
<script type="text/javascript" src="img/ext-all.js"></script>
<script type='text/javascript'>
Ext.require('Ext.window.Window');
Ext.onReady(function() {
// application startup code goes here
....
});
</script>
</html>
Ext JS 5 包含了各种主题。前面的示例演示了如何加载可用的主题之一。在示例中,我们将应用不同的主题来展示 Ext JS 5 的外观和感觉。脚本文件 ext-all.js 以压缩格式包含了所有 Ext JS 类。
注意事项
Ext JS 有构建自定义类文件的设施,以减少生产部署的加载。我们将这个留给你们去探索。
Ext.require用于加载应用程序中使用的特定类。Ext.onReady是 DOM 就绪方法,与应用程序启动代码在函数内部开始运行的$.ready jQuery 方法相同。
创建和访问 Ext JS 组件
在 Ext JS 的所有类中,我们应该首先讨论Ext.Component,它是 Ext JS 用户界面组件的基类。根据组件的特性,其中一些组件,如Panel、Window、FieldSet和RadioGroup可以包含多个组件,因为它们是通过另一个类Container继承的。我们将在稍后更详细地查看Container。
要创建一个 Ext JS 对象,我们使用Ext.create方法,它接受两个参数。第一个参数是类路径的字符串表示,例如'Ext.window.Window',或者一个别名,例如'widget.window'。第二个参数是对象指定符,包含实例化类的初始值:
var win = Ext.create('Ext.window.Window', {
title: 'Ext JS Window',
layout: 'fit',
items: [{
xtype: 'textarea',
id: 'textbox',
value: 'Lorem ipsum dolor sit amet, ... '
}]
});
win.show();
前面的代码片段用于创建一个窗口小部件,其内容是通过items选项定义的。Window类是从Container类派生出来的,它继承了items选项以包含其他组件。当窗口最终创建并准备好渲染时,它会遍历items数组中的每个对象指定符,并创建每个组件。
xtype选项是 Ext 特定的类型,它有一个简短的唯一名称来表示组件的类路径。在 Ext JS 中,所有界面组件都有自己的xtype名称(这指的是Ext.Component手册)。xtype选项通常用于方便在容器内创建组件,而不是使用带有完整路径名的Ext.create。
id字段是为了给一个组件赋予一个唯一的 ID 名称。其目的是在任何程序内部直接访问一个组件。要检索具有 ID 值的组件,我们可以执行以下代码行:
var tb = Ext.getCmp('textbox');
或者,我们可以使用itemId选项来分配一个唯一的名称。区别在于 ID 必须对整个应用程序全局唯一,而itemId只需要在父容器内部唯一,以避免在应用程序的其他地方发生名称冲突。要访问具有itemId值的组件,我们需要从直接父容器调用getComponent,如下所示:
var tb = win.getComponent('textbox');
此外,我们可以从顶级一直链式调用到所需的组件,如下所示:
var val =
win.getComponent('panel').getComponent('textbox').getValue();
带有itemId定义的'textbox'组件是在父容器'panel'内部构建的,该容器位于窗口对象内部。尽管getCmp方法提供了直接、简单的访问组件的方式,但它通常应该避免作为最佳实践的一部分,因为它的性能较慢,如果意外使用了重复的 ID,可能会产生不期望的效果。
注意
为了避免长样本代码,我们在一些演示中使用了getCmp调用。
注意,Sencha 还提供了方便的组件导航,up和down方法通过 CSS 样式选择器搜索目标组件。以下是一个示例:
var val = win.down('#textbox').getValue();
如我们所见,前面的表达式更加简化且直接。down方法基本上是向下遍历到其子组件,等等,直到遇到第一个符合条件组件。在这种情况下,匹配表达式'#textbox'表示一个itemId被指定为文本框的组件。可以使用许多不同的搜索表达式,另一个例子是down('textarea'),表示搜索第一个具有xtype值为 textarea 的子组件。
使用布局和视口
如我们之前提到的,某些类型的组件具有包含其他组件的能力,因为它们是从Container类扩展而来的。Container类的另一个特性是在包含组件之间安排布局;布局策略通过layout选项指定。大约有十几种布局策略:其中'anchor'、'border'和'fit'是最常用的(card布局也经常使用,但通过标签面板)。border布局在 GUI 编程中被广泛使用。布局被精细地划分为'north'、'east'、'south'、'west'和'center'区域。
当开发需要利用整个浏览器空间的应用程序时,我们通常使用与border布局结合的Viewport类。Viewport是一种特殊的容器,其大小会自动绑定到浏览器。以下是一个使用视口的简单示例:
var viewport = Ext.create('Ext.container.Viewport', {
layout: 'border',
defaults: {
frame: true
},
items: [{
region: 'north',
html: '<h1>North</h1>'
}, {
region: 'east',
html: '<h1>East</h1>',
width: '15%'
}, {
region: 'south',
html: '<h1>South</h1>'
}, {
region: 'west',
html: '<h1>West</h1>',
width: '20%'
}, {
region: 'center',
html: '<h1>Center</h1>'
}]
});
以下截图显示了灰色主题中的border布局:

面板
Panel是一个基本的容器组件,通常用作布局格式的构建块,然后与更多的面板或组件结合使用。另一种一般用途是将Panel类扩展为特殊用途的面板类型,例如在线门户演示中的PortalPanel。最广泛使用的面板类型是GridPanel、FormPanel和TabPanel。
网格面板
GridPanel用于以表格格式显示数据,并附带许多有用的功能,例如拖放列排序、列排序、灵活的数据渲染、启用或禁用列显示功能等。GridPanel还可以与不同的插件一起使用,例如行编辑器,允许用户即时编辑字段值。该类提供了一套大量的事件设置,可以与其他组件建立顺畅的协调。尽管如此,最紧密耦合的组件是存储对象,我们将在后面的部分进行演示。
表单面板
FormPanel 是一个用于容纳表单样式字段输入组件的面板,即左侧是标签,右侧是输入,以及按钮数组。Ext JS 提供了大量的表单输入,例如日期时间字段、组合框、数字字段、滑块等。在 FormPanel 层下面,有一个 BasicForm 组件,它通过存储的 Record 类为字段验证、表单提交和加载服务做出贡献,用于添加和编辑条目。
以下是一个 FormPanel 的截图,展示了各种输入:

TabPanel
TabPanel,正如其名称所暗示的,是一个与标签关联的面板。它支持动态创建和删除标签以及在不同标签之间滚动。以下代码片段展示了如何创建一个标签面板:
items:[{
xtype: 'tabpanel',
items: [{
title: 'Tab 1',
xtype: 'form',
items: [{
.....
}]
}, {
title: 'Tab 2',
....
}]
}]
以下是一个带有滚动功能的标签面板内标签的截图:

Window
Window 是一种特殊的面板类型,它不绑定到任何父容器,并在应用程序中自由浮动。它提供了许多在普通桌面窗口中找到的功能,例如调整大小和最大化/最小化,还提供了添加工具栏、页脚栏和按钮的选项。稍后,我们将通过一个示例来查看 Window 面板的实际应用。
Ajax
Ext JS 框架提供自己的方法,Ajax.request,用于发起 Ajax 查询。当返回的 JSON 数据不需要转换为表格行和字段列时,使用此方法。该方法通常与 Ext.decode 配合使用,将返回的 JSON 格式字符串转换为 JavaScript 对象,并直接访问对象内部的各个字段。
以下代码片段展示了发起一个 Ajax 查询的示例:
Ext.Ajax.request({
url: 'getData.php ',
params: { id: 1 },
success: function(response) {
// Decode JSON response from the server
var result = Ext.decode(response.responseText);
if (result && result.success) {
....
} else {
....
}
}
});
Store 和 JsonStore
Store 是一种用于模型数据的通用存储类。有几个类是从 Store 派生出来的,但 Highcharts 最重要的是 JsonStore。它是一个代理缓存的存储类,负责发起 Ajax 查询并将返回的 JSON 数据解包成模型数据。JsonStore 类通常用于访问位于服务器端的数据库数据。存储对象可以与多个组件绑定,例如,一个 JsonStore 对象可以绑定到一个网格面板和一个柱状图。在网格面板中点击列排序方向可以改变 JsonStore 中的行顺序,从而影响图表中显示的列顺序。换句话说,Store 类充当一个骨架,以使多个组件能够轻松且系统地协同工作。
需要注意的是,Store 类中的加载方法是异步的。如果我们想在数据加载后执行某个操作,应将事件处理程序分配给加载事件。可以通过 listeners.click 通过对象指定器或 store.on 方法指定操作。
JsonStore 和 GridPanel 的示例
到目前为止,已经介绍了一些 Ext JS 组件;我们应该看看它们是如何协同工作的。让我们构建一个简单的窗口应用程序,其中包含一个表格(GridPanel),显示从服务器返回的主机列表及其下载使用情况。假设我们从服务器返回的数据行是以 JSON 格式:
{ "data": [
{ "host" : "192.168.200.145", "download" : 126633683 },
{ "host" : "192.168.200.99" , "download" : 55840235 },
{ "host" : "192.168.200.148", "download" : 54382673 },
...
] }
首先,我们定义数据模型以与 JSON 数据相对应。为了简化,我们可以将所有演示代码放在 Ext.onReady 中,而不是单独的 JavaScript 文件中:
Ext.onReady(function() {
Ext.define('NetworkData', {
extend: 'Ext.data.Model',
fields: [
{name: 'host', type: 'string'},
{name: 'download', type: 'int'}
]
});
});
注意
接受服务器返回的字段名不是强制的。Ext.data.Model 提供了 mapping 选项,可以将备用字段名映射到客户端使用。
下一步是定义一个 JsonStore 对象,包含 URL、连接类型和数据格式类型。我们将 JsonStore 对象绑定到前面代码片段中定义的 NetworkData 数据模型:
var netStore = Ext.create('Ext.data.JsonStore', {
autoLoad: true,
model: 'NetworkData',
proxy: {
type: 'ajax',
url: './getNetTraffic.php',
reader: {
type: 'json',
idProperty: 'host',
rootProperty: 'data'
}
}
});
idProperty 用于定义如果未提供默认的 'id' 字段名,哪个字段被视为 ID,以便 Store.getById 等方法可以正常工作。root 选项告诉读取器(JsonReader),哪个属性名包含来自服务器的 JSON 响应中的行数据数组。接下来的任务是构建一个 Window 面板,使用 GridPanel 类,如下所示:
var win = Ext.create('Ext.window.Window', {
title: 'Network Traffic',
layout: 'fit',
items: [{
xtype: 'grid',
height: 170,
width: 270,
store: netStore,
columns: [{
header: 'IP Address',
dataIndex: 'host',
width: 150
}, {
header: 'Download',
dataIndex: 'download'
}]
}]
}).show();
我们指示网格面板绑定到 netStore 对象,并定义要显示的列列表。然后,我们通过 dataIndex 选项将每个列与存储的数据字段匹配。以下是一个窗口(清晰主题)截图,其中包含网格面板的一部分:

Highcharts 扩展
在本节中,我们将探讨在 Ext JS 中创建 Highcharts 组件是多么简单。我们通过从现有的 Highcharts 配置中导入来实现这一点。让我们从上一个 JsonStore 示例继续,并将其纳入扩展中。
第 1 步 - 移除一些 Highcharts 选项
假设我们已经有了一个工作独立的 Highcharts 配置,如下所示:
var myConfig = {
chart: {
renderTo: 'container',
width: 350,
height: 300,
....
},
series: [{
type: 'column',
data: [ 126633683, 55840235, .... ]
}],
xAxis: {
categories: [ "192.168.200.145",
"192.168.200.99", ... ],
....
},
yAxis: { .... },
title: { .... },
....
};
第一步是移除扩展将内部处理的全部字段并将它们传递给 Highcharts。因此,我们需要移除 chart.renderTo 和维度选项。我们还需要移除 chart.series 数组,因为最终 JsonStore 将是图形数据的来源。我们还想移除 chart.xAxis.categories,因为它包含图形数据。
第 2 步 - 转换为 Highcharts 扩展配置
下一步是构建一个新的配置,用于扩展旧的 Highcharts 配置。让我们从一个新的配置对象 myNewConfig 开始,包含大小属性:
var myNewConfig = {
width: 350,
height: 300
};
下一步是创建一个新的选项 chartConfig,这是扩展所必需的。我们将 myConfig 对象中剩余的属性移向 chartConfig。以下代码片段显示了新的配置应该是什么样子:
// ExtJS component config for Highcharts extension
var myNewConfig = {
width: 450,
height: 350,
chartConfig: {
// Trimmed Highcharts configuration here
chart: { .... },
xAxis: { .... },
yAxis: { .... },
title: { .... },
....
}
};
步骤 3 – 通过映射 JsonStore 数据模型构建系列选项
回顾存储对象的 data 模型,我们有以下代码片段:
fields: [
{ name: 'host', type: 'string' },
{ name: 'download', type: 'int' }
]
下一个任务是构建一个与 JsonStore 数据模型匹配的系列数组。新的系列数组结构与 Highcharts 选项中的结构类似。我们还需要在对象配置中链接存储对象。最终,选项对象应类似于以下代码片段:
var myNewConfig = {
width: 450,
height: 350,
store: netStore,
series: [{
name: 'Network Traffic',
type: 'column',
// construct the series data out of the
// 'download' field from the return Json data
dataIndex: 'download'
}],
// construct the x-axis categories data from
// 'host' field from the return Json data
xField: 'host',
chartConfig: {
....
}
};
dataIndex 选项用于将 JsonStore 中的 y 值映射到系列数据数组中。由于 'host' 字段是字符串类型的数据,它被用作类别。因此,我们指定 xField 选项在系列数组外部,与系列共享。
步骤 4 – 创建 Highcharts 扩展
最后一步是将所有内容组合起来,在 Ext JS 中显示图表。我们首先创建一个 Highcharts 组件,并将其放入 Ext JS 容器对象中,如下所示:
var hcChart = Ext.create('Chart.ux.Highcharts', myNewConfig);
var win = Ext.create('widget.window', {
title: 'Network Traffic',
layout: 'fit',
items: [ hcChart ]
}).show();
或者,我们也可以通过使用 xtype 在一个配置中创建整个结构,如下所示:
var win = Ext.create('widget.window', {
title: 'Network Traffic',
layout: 'fit',
items: [{
xtype: 'highchart',
itemId: 'highchart',
height: 350,
width: 450,
store: netStore,
series: [{ .... }],
xField: 'host',
chartConfig: {
chart: { .... },
xAxis: { .... },
yAxis: { .... },
....
}]
}).show();
以下截图显示了 Ext JS 窗口(经典主题)内的 Highcharts 图表:

注意
为了在启动时显示数据,必须通过将 autoLoad 选项设置为 true 或在程序开始时手动调用 Store.load 方法来实例化 JsonStore。
在 Highcharts 扩展中传递特定系列的选项
如果我们需要传递特定系列的选项,例如颜色、数据点装饰等,那么我们只需以通常在 Highcharts 中做的方式,将它们放入系列配置中:
.....
store: netStore,
series: [{
name: 'Network Traffic',
type: 'column',
dataIndex: 'download',
color: '#A47D7C'
}],
在创建系列的同时,扩展将复制这些选项。
将数据模型转换为 Highcharts 系列
在上一个示例中,我们学习了如何将简单的数据模型从 Ext JS 存储映射到 Highcharts。然而,有几种声明数据映射的方法,每种方法根据场景的不同有不同的影响,尤其是在多个系列的情况下。
X 轴类别数据和 y 轴数值
这是最简单且可能是最常见的情况。每个系列在 y 轴上有数值,并在类别间共享数据。由于历史原因,dataIndex 选项也可以替换为另一个选项名称 yField,它具有更高的优先级,并且两者行为完全相同:
series: [{
name: 'Upload',
type: 'column',
yField: 'upload'
}, {
name: 'Download',
type: 'column',
yField: 'download'
}],
// 'Monday', 'Tuesday', 'Wednesday' ....
xField: 'day'
x 轴和 y 轴的数值
另一种情况是,x 轴和 y 轴都由数值组成。有两种不同的方式来指定数据映射。首先,每个系列持有 y 轴值并共享共同的 x 轴值。在这种情况下,系列指定方式与上一个示例相同:
series: [{
name: 'Upload',
type: 'line',
yField: 'upload'
}, {
name: 'Download',
type: 'line',
yField: 'download'
}],
// Time in UTC
xField: 'time'
另一种情况是,每个系列都持有自己的 x 和 y 值对,如下所示:
series: [{
name: 'Upload',
type: 'line',
yField: 'upload',
xField: 'upload_time'
}, {
name: 'Download',
type: 'line',
yField: 'download',
xField: 'download_time'
}]
这两种设置的差异在于,第一种配置最终在图表中产生两个线系列,数据点沿 x 轴对齐,而后者则没有,存储数据模型也有所不同。
从存储数据执行预处理
假设我们需要在绘制图表之前对服务器数据进行预处理。我们可以通过覆盖系列配置中的模板方法来实现这一点。
在扩展代码中,每个系列实际上是从一个 Serie 类实例化的。这个类定义了一个标准方法,名为 getData,用于从存储中检索数据。让我们看看 getData 的原始实现:
getData : function(record, index) {
var yField = this.yField || this.dataIndex,
xField = this.xField,
point = {
data : record.data,
y : record.data[yField]
};
if (xField)
point.x = record.data[xField];
return point;
},
注意
这个扩展中的类和方法被原始作者命名为包含单词 "Serie"。
基本上,每次从 JsonStore 返回的每一行都会调用 getData 方法。该方法传递两个参数。第一个参数是一个 Ext JS Record 对象,它是一个数据行的对象表示。第二个参数是记录在存储中的索引值。在 Record 对象内部,data 选项在创建存储对象时根据模型定义持有值。
如我们所见,getData 的简单实现是根据 xField、yField 和 dataIndex 的值访问 record.data,并将其格式化为 Highcharts Point 配置。我们可以根据我们声明系列时的数据转换需求来覆盖此方法。让我们继续例子:假设服务器以 JSON 字符串的形式返回数据:
{"data":[
{"host":"192.168.200.145","download":126633683,
"upload":104069233},
{"host":"192.168.200.99","download":55840235,
"upload":104069233},
{"host":"192.168.200.148","download":54382673,
"upload":19565468},
....
JsonStore 将前面的数据解释为具有以下模型定义的行:
fields: [
{name: 'host', type: 'string'},
{name: 'download', type: 'int'},
{name: 'upload', type: 'int'}
]
我们需要绘制一个柱状图,其中每个条形是上传和下载字段的总量,因此我们为系列定义了如下的 getData 方法。请注意,我们不再需要声明 yField 或 dataIndex,因为此特定系列的 getData 方法已经处理了字段映射:
series: [{
name: 'Total Usage',
type: 'column',
getData: function(record, index) {
return {
data: record.data,
y: record.data.upload +
record.data.download
};
}
}],
xField: 'host',
....
绘制饼图
绘制饼图与线图、柱状图和散点图略有不同。饼图系列由数据值组成,其中每个值来自一个类别。因此,该模块有两个特定的选项名称,分别为 categorieField 和 dataField,分别用于类别和数据。要绘制饼图,系列需要指定以下内容:
series: [{
type: 'pie',
categorieField: 'host',
dataField: 'upload'
}]
PieSeries 类的 getData 方法随后将存储中的映射数据转换为 Point 对象,并将值分配给 name 和 y 字段。
绘制环形图
让我们提醒自己,环形图实际上是一个包含两个系列的饼图,其中内饼图的数据是外饼图的子类别。换句话说,内系列中的每个切片总是其外部分的总和。因此,从 JsonStore 返回的数据必须设计成可以通过字段名将这些数据分组到子类别中。在这种情况下,JSON 数据应该返回如下所示:
{ "data": [
{ "host" : "192.168.200.145", "bytes" : 126633683,
"direction" : "download"},
{ "host" : "192.168.200.145", "bytes" : 104069233,
"direction" : "upload"},
{ "host" : "192.168.200.99", "bytes" : 55840235,
"direction" : "download"},
{ "host" : "192.168.200.99", "bytes" : 104069233,
"direction" : "upload"},
....
] }
然后,我们使用一个额外的布尔选项 totalDataField,对于内饼图系列,表示我们想要使用 dataField 来扫描每个 "host" 类别的总值。对于外系列,我们只需将其定义为正常的饼图系列,但将 "direction" 和 "bytes" 分别定义为 categorieField 和 dataField。以下是为环形图定义的系列:
series: [{
// Inner pie
type: 'pie',
categorieField: 'host',
dataField: 'bytes',
totalDataField: true,
size: '60%',
....
}, {
// Outer pie
type: 'pie',
categorieField: 'direction',
dataField: 'bytes',
innerSize: '60%',
....
}]
以下截图显示了 Ext JS(aria 主题)中的环形图的外观:

在扩展内部,PieSeries 类的 getData 方法的实现与其他系列类型显著不同,以便处理饼图和环形图的数据。因此,不建议覆盖此方法。稍后,我们将看到如何使用此模块绘制饼图和环形图。
模块 API
Highcharts 扩展包含一组小的 API。其中大部分是辅助函数,用于修改 Ext JS 层中的系列。至于 Highcharts 原生 API,可以通过扩展组件内部的 chart 属性来调用,例如:
win.down('#highchart').chart.getSVG({ ... });
在前面的代码行中,'highchart' 是创建图表组件时的 itemId 值。down 方法是 Ext JS 使用 CSS 选择样式遍历层次组件的便捷方式。
如前所述,chartConfig 选项包含所有 Highcharts 配置。一旦创建图表组件,它就会在组件内部保存 chartConfig。因此,chartConfig 属性包含创建图表的所有初始配置。稍后,我们将看到这个 chartConfig 属性如何在 API 调用中发挥作用。
addSeries
addSeries 方法将一个或多个系列添加到图表中。添加的系列也存储在 chartConfig.series 数组中,如下所示:
addSeries : function(Array series, [Boolean append])
系列参数是一个系列配置对象的数组。addSeries 不仅允许使用 xField、yField 和 dataIndex 选项进行系列配置,还支持使用数据数组进行系列配置,因此它不会通过存储对象提取数据。以下是如何以不同方式使用 addSeries 的示例:
Ext.getComponent('highchart').addSeries([{
name: 'Upload',
yField: 'upload'
}], true);
Ext.getComponent('highchart').addSeries([{
name: 'Random',
type: 'column',
data: [ 524524435, 434324423, 43436454, 47376432 ]
}], true);
可选的 append 参数将系列参数设置为替换当前显示的系列或将系列添加到图表中。默认值为 false。
removeSerie 和 removeAllSeries
removeSerie 方法从图表中移除单个系列,而 removeAllSeries 方法移除为图表定义的所有系列。这两种方法也会移除 chartConfig.series 中的系列配置,如下所示:
removeSerie : function(Number idx, [Boolean redraw])
removeAllSeries : function()
idx 参数是系列数组中的索引值。可选的 redraw 参数设置在移除系列后是否重新绘制图表。默认值为 true。
setTitle 和 setSubTitle
setTitle 和 setSubTitle 都会更改当前图表标题以及 chartConfig 中的标题设置,如下所示:
setSubTitle : function(String title)
setTitle: function(String title)
draw
到目前为止,我们提到了 chartConfig,但还没有真正解释它在模块中的功能。draw 方法实际上销毁了内部 Highcharts 对象,并根据当前 chartConfig 内的设置重新创建图表。假设我们已创建了一个图表组件,但想更改一些显示属性。我们修改 chartConfig(Highcharts 配置)内的属性,并调用此方法来重新创建内部 Highcharts 对象:
draw: function()
虽然我们可以通过内部 chart 选项调用 Highcharts 的原生 API 而不销毁和重新创建图表,但并非所有 Highcharts 元素都可以通过 API 调用来更改,例如系列颜色、图例布局、列堆叠选项、反转图表轴等等。
因此,此方法使扩展组件能够在不重新创建组件本身的情况下,通过任何配置更改刷新内部图表。因此,它通过不在父容器中移除并重新插入一个新组件来增强 Ext JS 应用程序。此外,父容器中的布局也不会被打乱。
事件处理和导出模块
为扩展指定图表事件处理器与我们在 Highcharts 中通常声明事件处理器的方式完全相同。由于现在它处于 Ext JS 和 jQuery 环境下,实现可以使用 Ext JS 和 jQuery 方法。
Highcharts 导出图表模块不受扩展的影响。导出设置只是绕过此扩展并直接工作。
使用 Highcharts 扩展示例
在本节中,我们将构建一个更大的示例,其中包含其他类型的面板和图表。应用程序使用视口显示两个区域——'center' 区域是一个包含三个标签的标签面板,每个标签对应不同类型的网络数据图表,而 'west' 区域显示当前显示图表的表格数据。第一个标签中的图表是 带宽利用率,表示通过网络的数据速率。
以下截图显示了应用程序的前端屏幕(neptune 主题):

工具栏中的昨天按钮是一个切换按钮,它会在同一图表上触发一个额外的系列昨天。在左侧表格中还会显示一个名为昨天的额外数据列,如下面的截图所示:

昨天按钮处理程序内部使用addSeries和removeSeries方法来切换昨天系列。以下是其实现:
toggleHandler: function(item, pressed) {
// Retrieve the chart extension component
var chart = Ext.getCmp('chart1').chart;
if (pressed && chart.series.length == 1) {
Ext.getCmp('chart1').addSeries([{
name: 'Yesterday',
yField: 'yesterday'
}], true);
// Display yesterday column in the grid panel
....
} else if (!pressed && chart.series.length == 2) {
Ext.getCmp('chart1').removeSerie(1);
// Hide yesterday column in the grid panel
....
}
}
让我们转到第二个标签页,它是一个柱状图,显示了主机列表及其上行和下行方向的网络使用情况,如下所示:

当我们点击堆叠柱状图按钮时,两个系列的条形会堆叠在一起,而不是相邻对齐,如下所示:

这是通过修改扩展的chartConfig属性中的列stacking选项,并使用模块的draw方法重新创建整个图表来实现的:
toggleHandler: function(item, pressed) {
var chart2 = Ext.getCmp('chart2');
chart2.chartConfig.plotOptions.column.stacking =
(pressed) ? 'normal' : null;
chart2.draw();
}
注意,我们在创建图表时在chartConfig内部声明了默认的stacking选项,这样我们就可以在稍后的处理代码中直接修改该属性:
chartConfig: {
.... ,
plotOptions: {
column: { stacking: null }
},
......
最后一个标签页是最近 7 天网络使用情况,它有一个饼图显示了最近七天的网络使用情况,如下面的截图所示:

让我们详细看看这个饼图是如何实现的。JsonStore被调整为以下格式返回数据:
{"data": [
{"date": "Mon 13/08", "type": "wan",
"bytes": 92959786, "color": "#8187ff" },
{"date": "Mon 13/08", "type": "lan",
"bytes": 438238992, "color": "#E066A3" },
{"date": "Tue 14/08", "type": "wan",
"bytes": 241585530, "color": "#8187ff" },
{"date":"Tue 14/08", "type": "lan",
"bytes": 773479723, "color": "#E066A3" },
.....
然后,我们定义标签页内容,如下所示:
items:[{
xtype: 'highchart',
id: 'chart3',
store: summStore,
series: [{
type: 'pie',
name: 'Total',
categorieField: 'date',
dataField: 'bytes',
totalDataField: true,
size: '60%',
showInLegend: true,
dataLabels: { enabled: true }
}],
chartConfig: {
chart: {...},
title: { text: null },
legend: { enabled: false }
}
}]
系列被设置为内部系列,因此使用了totalDataField和dataField选项来获取"lan"和"wan"的总字节数,作为每个'host'的切片值。如果我们点击显示流量类型按钮,那么饼图就会变成一个环形图,如下面的截图所示:

第一张饼图中的原始数据标签被替换为图例框内的项目。一个外部系列以固定的颜色方案显示,以展示局域网和广域网的流量部分。以下是为显示流量类型按钮的按钮处理代码:
toggleHandler: function(item, pressed) {
var config = Ext.getCmp('chart3').chartConfig;
if (pressed) {
Ext.getCmp('chart3').addSeries([{
type: 'pie',
center: [ '50%', '45%' ],
categorieField: 'type',
dataField: 'bytes',
colorField: 'color',
innerSize: '50%',
dataLabels: {
distance: 20,
formatter: function() {
if (this.point.x <= 1) {
return this.point.name.toUpperCase();
}
return null;
}
},
size: '60%'
}], true);
config.legend.enabled = true;
config.series[0].dataLabels.enabled = false;
config.series[0].size = '50%';
config.series[0].center = [ '50%', '45%' ];
} else {
Ext.getCmp('chart3').removeSerie(1);
config.legend.enabled = false;
config.series[0].dataLabels.enabled = true;
config.series[0].size = '60%';
config.series[0].center = [ '50%', '50%' ];
}
Ext.getCmp('chart3').draw();
}
如果切换按钮被启用,那么我们通过 addSeries 方法添加一个外部的饼图系列(带有 innerSize 选项)。此外,我们根据流量 'type' 对外系列进行相应对齐,因此 categorieField 和 dataField 被分配给 'type' 和 'bytes'。由于需要更多信息来显示第二个系列,我们将内部系列的大小设置为更小,以获得更多空间。为了只显示外部系列中的前两个数据标签,我们实现了 dataLabels.formatter 来在 this.point.x 为 0 和 1 时打印标签。之后,我们在 formatter 函数中返回 null 来禁用数据标签。最后,使用 draw 方法来反映所有更改。
通过点击数据点显示上下文菜单
对于交互式应用,允许用户通过点击数据点来启动特定操作将非常有用。为了做到这一点,我们需要处理 Highcharts 的点击事件。在这里,我们创建了一个简单的菜单,用于显示选定点与系列平均值之间的差异。以下是示例代码:
point: {
events: {
click: function(evt) {
var menu =
Ext.create('Ext.menu.Menu', {
items: [{
text: 'Compare to Average Usage',
scope: this,
handler: function() {
var series = this.series,
yVal = this, avg = 0, msg = '';
Ext.each(this.series.data, function(point) {
avg += point.y;
});
avg /= this.series.data.length;
if (yVal > avg) {
msg =
Highcharts.numberFormat(yVal - avg) +
" above average (" +
Highcharts.numberFormat(avg) + ")";
} else {
msg =
Highcharts.numberFormat(avg - yVal) +
" below average (" +
Highcharts.numberFormat(avg) + ")";
}
Ext.Msg.alert('Info', msg);
}
}] // items:
});
menu.showAt(evt.point.pageX, evt.point.pageY);
}
}
}
首先,我们创建一个简单的 Ext JS Menu 对象,带有菜单项 与平均使用情况比较。click 处理器使用鼠标事件参数 evt 被调用,然后我们获取鼠标指针位置,pageX 和 pageY,并将其传递给菜单对象。结果,在点击数据点后,Ext JS 菜单出现在指针旁边。
在 click 事件处理器中的 'this' 关键字指的是选定的点对象。然后我们使用 scope 选项将 Highcharts 点对象传递给菜单处理器层。在处理器内部,'this' 关键字变成了数据点对象,而不是 Ext JS 菜单项。我们提取系列数据来计算平均值,并计算与选定点值的差异。然后,我们显示带有该值的消息。以下是该菜单的截图:

带有 Highcharts 的商业 RIA – Profiler
到目前为止,我们已经展示了如何在 Ext JS 框架中应用 Highcharts。然而,这个演示本身对于一个 RIA 产品来说似乎有点过于封闭。在本节中,我们将快速浏览一个商业应用,Profiler,这是由 iTrinegy 开发的一个用于分析公司网络场景的工具。由于其业务性质,此类应用需要一系列诊断图表。整个应用被设计成多个门户,用于监控来自多个地点的网络流量。用户可以从利用率图表向下钻取到按 IP 地址排序的上行链路使用情况图表,修改过滤器属性以显示多个系列中的相关数据,等等。
为了微调分析参数并提供门户界面,需要一个提供动态和校准用户界面的框架。因此,Ext JS 是一个合适的候选者,因为它提供了一套丰富的专业外观的小部件组件,并且它的跨浏览器支持使得构建复杂的 RIA 软件变得可管理。以下是通过特定参数启动带宽利用率报告图的界面:

Highcharts 事件可以轻松地与 Ext JS 组件绑定,从而实现全交互式导航风格。例如,如果利用率图上出现一个峰值,用户可以点击峰值数据点或突出显示特定时间范围内的区域,然后会出现一个包含网络图选择的上下文菜单。这一操作意味着我们可以将选定的时区添加到累积的过滤器中,并导航到特定的图表。以下是一个上下文菜单的截图,它出现在图表之一中:

如果我们通过再次选择相同的图表,即利用率,这意味着我们想在选定的时区内更详细地放大。这并不使用 Highcharts 的默认缩放操作,它只是拉伸图表系列并重新绘制坐标轴。实际上,它启动了另一个带有选定时间的 Ajax 查询,并返回更细粒度的图表数据,因此可以进一步诊断图中的峰值。换句话说,该应用程序使用户能够通过一系列不同的图表进行视觉筛选。同时,用户逐渐在不同维度上细化过滤器。这个过程以迅速、直观和有效的方式将问题剖析到根本原因。
摘要
在本章中,我们学习了 Ext JS 的基础知识,它是一个用于构建富互联网应用(RIAs)的框架。我们快速介绍了可能用于与 Highcharts 扩展结合使用的十几个 Ext JS 组件。然后,我们以逐步的方式探讨了如何从现有的 Highcharts 配置中创建 Highcharts 组件。我们研究了扩展模块提供的少量 API,并使用网络使用数据构建了一个简单的应用程序。最后,我们简要地看了看 Highcharts 和 Ext JS 在商业网络分析应用中的应用。
在下一章中,我们将探讨如何在服务器端运行 Highcharts。
第十四章:服务器端 Highcharts
第一版《学习 Highcharts》涵盖了在服务器端运行 Highcharts 的多种方法。从那时起,这个领域已经取得了显著的发展。结果是,Highcharts 采用了 PhantomJS(无头 webkit)作为服务器解决方案,以及 PhantomJS/Batik 作为 Java 服务器实现。我们还将探讨如何使用 PhantomJS 创建我们自己的 Highcharts 服务器进程,以及如何使用 Highcharts 发布的官方服务器端脚本。
在本章中,我们将涵盖以下主题:
-
为什么我们想在服务器端运行 Highcharts
-
为什么 Highcharts 采用了 PhantomJS 和 Batik
-
PhantomJS 和 Batik 的基本知识
-
创建我们自己的 PhantomJS 脚本来导出图表
-
如何在命令行和服务器模式下使用 Highcharts 服务器端脚本
在服务器端运行 Highcharts
在服务器端运行 Highcharts 的主要原因是为了允许基于客户端的图形应用程序在服务器端自动化和访问。在某些情况下,我们希望在前端生成图表,同时在后端交付带有图表的自动化报告。为了保持一致性和开发成本,我们希望在两端生成相同风格的图表。以下是我们可能希望在服务器端生成图表的其他场景:
-
应用程序需要在服务器端运行一个计划任务。它生成带有图表的常规总结报告(例如,服务水平协议报告),并自动将报告通过电子邮件发送给客户或具有管理角色的用户。
-
数据的本质意味着它需要很长时间来计算图表。相反,用户将参数发送到服务器以生成图表。一旦完成,图表设置被保存,然后用户被通知查看从预计算的 JSON 设置中实时 Highcharts 图表。
-
应用程序涉及大量重复数据,这些数据仅保留一定时期,例如自动生成并存储为图像格式的数据趋势图表,以供记录。
服务器端 Highcharts
在本书的第一版中,我们提到了一些可以用于在服务器端纯服务器端生成图表图像的技术。在这些技术中,PhantomJS 是最突出的。简而言之,它是一个能够运行服务器端 JavaScript 的独立程序。除此之外,它易于使用,设置简单,且可编程且健壮。
另一种方法是使用 Rhino,这是一种 JavaScript 引擎的 Java 实现,可以在服务器端运行 JavaScript,以便 Highcharts 可以在服务器端运行并将图表导出为 SVG 文件。然后,SVG 文件被转发到 Batik,这是一个基于 Java 的通用 SVG 工具包,从 SVG 生成图像文件。
从那时起,Highcharts 对不同的方法进行了广泛实验,并得出结论,将 PhantomJS 纳入解决方案是未来的方向。这个决定有几个原因。首先,与 PhantomJS 相比,Rhino 存在渲染问题,这使得 PhantomJS 成为更好的选择。此外,PhantomJS 还可以导出图像,尽管当数据点的数量增加到约 1,500 时,它在渲染图表方面存在可扩展性问题。还考虑了图像转换器 ImageMagick,但它也存在特定的性能和可靠性问题。有关发现详情,请参阅 www.highcharts.com/component/content/article/2-articles/news/52-serverside-generated-charts#phantom_usage。
对于需要在 Java 中实现的服务器端解决方案,Batik 是格式化 SVG 的更自然选择,而 PhantomJS 则用于运行 Highcharts 以处理 SVG 内容。至于非 Java 方法,PhantomJS 本身就足够驱动整个服务器端解决方案。
Batik – SVG 工具包
Batik 是 Apache 基金会项目的一部分,xmlgraphics.apache.org/batik/。其目的是提供一项网络服务,用于查看、生成和转换 SVG 数据。例如,Highcharts 使用第三方软件将 SVG 数据转换为图像格式。当用户点击导出按钮时,Highcharts 内部将图表的 SVG 数据和用户选择的图像格式请求转发给 Batik。
然后,Batik 接收 SVG 数据并将其转换为所需的图像格式。以下图表总结了正常 Highcharts 图表如何使用 Batik 的导出服务:

要安装 Batik,请从 xmlgraphics.apache.org/batik/download.html#binary 下载最新发行版,并按照安装说明进行操作。对于 Ubuntu,只需执行以下操作:
apt-get install libbatik-java
在整个 Batik 软件包中,我们只需要图像转换组件,即 batik-rasterizer.jar 文件。要从 SVG 转码为 PNG 文件,可以使用以下命令:
java -jar batik-rasterizer.jar chart.svg
之前的命令将转换 chart.svg 并在同一目录下创建 chart.png。
PhantomJS (无头 WebKit)
WebKit 基本上是驱动 Safari 和 Google Chrome 等浏览器的后端引擎。它实现了 HTML5 中的几乎所有功能,除了浏览器的用户界面。PhantomJS(位于 phantomjs.org/,由 Ariya Hidayat 创建和维护)是一个无头 WebKit,这意味着 WebKit 引擎可以作为独立程序运行。它在许多方面都很有用,其中之一就是服务器端 SVG 渲染。
创建简单的 PhantomJS 脚本
虽然 Highcharts 发布了一个 PhantomJS 脚本以在服务器端导出图表,但了解 PhantomJS 的概念及其工作原理仍然很有价值。假设我们已经有了一个运行中的 Web 服务器和 PhantomJS。要从命令行在 PhantomJS 上运行 HTML 页面,请运行以下命令:
phantomjs loadPage.js
loadPage.js页面可以像这样简单:
var page = require('webpage').create();
page.onError = function(msg, trace) {
console.error(msg);
phantom.exit(1);
};
page.onConsoleMessage = function(msg) {
console.log(msg);
};
page.open('http://localhost/mychart.html', function(status) {
if (status === 'success') {
console.log('page loaded');
phantom.exit();
}
});
在 PhantomJS 进程中,它首先加载webpage模块并创建一个page对象。
提示
这只是一个简短的示例,用于说明。对于正确处理错误消息的方法,请参阅 PhantomJS API 文档。
page.onError和page.onConsoleMessage方法通过console.log将页面错误和输出消息重定向到终端输出。请注意,这里的console.log是指我们的终端控制台。如果页面内部调用console.log,它将仅保留在页面对象的生命周期内,除非定义了page.onConsoleMessage来重定向这些消息。
上述脚本仅将 HTML 页面打开到webpage对象中,然后终止,这并不特别有用。
创建我们自己的服务器端 Highcharts 脚本
让我们以稍微更高级的方式使用 PhantomJS。在 PhantomJS 中,我们不需要依赖 Web 服务器来提供服务页面。相反,我们本地加载一个 Highcharts 页面文件,并包含另一个 JSON 文件中的系列数据。然后,我们将结果渲染到图像文件中。因此,以下是我们在命令行上运行服务器端脚本的方式:
phantomjs renderChart.js chart.html data.json chart.png
chart.html页面只是一个我们通常会创建的简单 Highcharts 页面。在这个练习中,我们将系列数据保留为变量seriesData。以下显示了如何构建chart.html页面:
<html>
<head>
<meta> ....
<script src='..../jquery.min.js'></script>
<script src='..../Highcharts.js'></script>
<script type='text/javascript'>
$(function () {
$(document).ready(function() {
chart = new Highcharts.Chart({
chart: {
....
},
plotOptions: {
....
},
....,
series: [{
name: 'Nasdaq',
data: seriesData
}]
});
});
});
</script>
</head>
<body>
<div id="container" ></div>
</body>
</html>
然后,data.json只是一个简单的 JSON 文件,包含x和y系列数据的数组。以下是其中的一些内容:
[[1336728600000,2606.01],[1336730400000,2622.08],
[1336732200000,2636.03],[1336734000000,2637.78],
[1336735800000,2639.15],[1336737600000,2637.09],
....
对于 PhantomJS 文件renderChart.js,令人惊讶的是,我们只需要添加很少的额外代码(粗体突出显示)就能达到结果:
var page = require('webpage').create(),
system = require('system'),
fs = require('fs');
// Convert temporary file with series data – data.json
var jsonData = fs.read(system.args[2]);
fs.write('/tmp/data.js', 'var seriesData = ' + jsonData + ';');
page.onError = function(msg, trace) {
console.error(msg);
phantom.exit(1);
}
page.onConsoleMessage = function(msg) {
console.log(msg);
};
// initializes the seriesData variable before loading the script
page.onInitialized = function() {
page.injectJs('/tmp/data.js');
};
// load chart.html
page.open(system.args[1], function(status) {
if (status === 'success') {
// output to chart.png
page.render(system.args[3]);
phantom.exit();
}
});
我们首先加载system和fs模块,这些模块在本例中用于选择命令行参数并在 JSON 文件上处理文件 I/O。脚本基本上读取(fs.read)JSON 文件的内容,将其转换为 JavaScript 表达式,并将其保存(fs.write)到文件中。然后,我们为页面对象定义onInitialized事件处理器,该事件在 URL 加载之前触发。因此,我们在页面对象加载chart.html页面之前插入(injectJs)seriesData的 JavaScript 表达式。一旦页面加载,我们就将页面内容导出(page.render)到图像文件中。
注意,生成的图像文件并不完全正确,因为折线系列实际上缺失了。然而,如果我们更仔细地观察图像,实际上线条已经开始绘制(请参见以下截图):

这是因为图表默认的动画设置。在我们通过将 plotOptions.series.animation 选项设置为 false 关闭初始动画后,线条系列就会出现:

运行 Highcharts 服务器脚本
到目前为止,我们的脚本示例在功能和错误检查功能方面还不足,远非完美。尽管如此,我们可以看到创建自己的服务器端 Highcharts 进程以生成图像是多么容易。在本节中,你将学习如何使用 Highcharts 的官方服务器端脚本,它具有更多功能,可用于不同场景。
服务器脚本使用
自版本 3 以来,Highcharts 与服务器端脚本 highcharts_convert.js 一起打包,该脚本位于 exporting-server/phantomjs 目录中。该脚本可以作为命令行或监听服务器运行。
小贴士
对于用法和参数的完整描述,请参阅 github.com/highslide-software/highcharts.com/tree/master/exporting-server/phantomjs。
将脚本作为独立命令运行
这是 highcharts_convert.js 的典型命令行格式:
phantomjs highcharts-convert.js -infile file
-outfile chart1.png | -type ( png | jpg | pdf | svg ) -tmpdir dir
[-scale num | -width pixels ] [ -constr (Highcharts | Highstocks) ]
[-callback script.js ]
以下是在前一个命令中使用的参数列表:
-
-infile:这是脚本的输入源,可以是 JSON 格式的图表配置(通用用法)或 SVG 文件。服务器脚本会自动检测内容类型,并按所需格式处理和导出图表/内容。 -
-outfile,-type,-tmpdir:指定输出格式的两种方式是通过-type或-outfile。使用-outfile参数时,脚本将从扩展名推导图像格式。或者,使用-type,例如,输入png,将格式化为 PNG 图像文件,并使用-tmpdir将输出文件保存到特定位置。 -
-scale,-width:有两个可选参数用于调整输出图像大小,通过-scale或-width。正如其名所示,一个是通过缩放调整大小,另一个是通过绝对大小调整。 -
-constr:-constr参数用于指示脚本是否将图表导出为 Highcharts 或 Highstock 图表(另一个用于金融图表的产品)。 -
-callback:-callback参数用于在图表加载后和导出之前执行额外的 JavaScript 代码。
让我们将之前的图表配置文件应用到这个命令行中。此外,我们还将使用回调参数在图表上叠加水印,SAMPLE。
首先,我们将整个图表配置对象及其系列数据保存到文件中:
{ chart: {
renderTo: 'container',
height: 250,
spacingRight: 30,
animation: false
},
. . . .
}
然后,我们创建一个回调脚本,以下代码用于添加水印,watermark.js:
function(chart) {
chart.renderer.text('SAMPLE', 220, 200).
attr({
rotation: -30
}).
css({
color: '#D0D0D0',
fontSize: '50px',
fontWeight: 'bold',
opacity: 0.8
}).
add();
}
最后,我们运行以下命令:
phantomjs highcharts-convert.js -infile options.json -outfile chart.png -width 550 -callback watermark.js
命令在运行时会生成输出:
Highcharts.options.parsed
Highcharts.cb.parsed
Highcharts.customCode.parsed
/tmp/chart.png
它还生成以下截图:

以监听服务器运行脚本
要将脚本作为监听 HTTP 请求的服务器运行,我们使用以下命令启动脚本:
phantomjs highcharts-convert.js -host address -port num
-type ( png | svg | jpg | pdf )
让我们使用以下命令启动一个 Highcharts 导出服务器:
phantomjs highcharts-convert.js -host 127.0.0.1 -port 9413 -type png
这将启动一个只监听本地端口 9413 的服务器,以下消息输出到屏幕:
OK, PhantomJS is ready.
向监听服务器传递选项
一旦服务器进程准备就绪,我们就可以开始发送嵌入 Highcharts 配置数据中的 POST 请求。请求中使用的 Highcharts 选项与我们在命令行中使用的相同。让我们重用上一个练习中的配置,并将它们打包成一个 POST 请求。
首先,我们需要将整个图表配置“字符串化”为一个值,作为 infile 选项的值。接下来,我们以同样的方式处理回调方法。然后,我们将其余的选项放入一个 JSON 格式,并保存到一个名为 post.json 的文件中:
{ "infile" : " { chart: { .... }, series { .... } } " ,
"callback" : "function(chart) { .... } ",
"scale" : 1.2
}
下一个任务是把这个数据打包成一个 POST 查询。由于本章的目的是服务器端处理,我们应该以命令行风格操作。因此,我们使用 curl 工具来创建一个 POST 请求。以下命令可以完成这项工作:
curl -X POST -H "Content-Type: application/json" -d @post.json http://localhost:9413/ | base64 -d > /tmp/chart.png
之前的 curl 命令是用来创建一个带有 JSON 内容类型的 POST 请求。-d @ 参数通知 curl 命令哪个文件包含 POST 数据。由于 HTTP 是一个 ASCII 协议,结果二进制图像数据的响应以 base-64 编码返回。因此,我们需要将 POST 响应数据管道到另一个实用程序,base64,以解码数据并将其写入文件。
摘要
在本章中,我们描述了在服务器端运行 Highcharts 的目的,并学习了 Highcharts 在服务器上选择使用的技术。你学习了 PhantomJS 的基础知识以及 Batik 的作用。你扩展了对 PhantomJS 的理解,以创建自己的 Highcharts 服务器端脚本。除此之外,我们还实验了如何以单命令行和服务器模式运行 Highcharts 发布的官方 PhantomJS 脚本。
在下一章中,我们将简要了解 Highcharts 提供的在线服务,并探索一些 Highcharts 插件。
第十五章. Highcharts 在线服务和插件
在上一章中,你学习了如何在服务器端运行 Highcharts。这使得 Highcharts 能够将其范围扩展到在线服务。我们将在本章中访问这些服务,并探讨我们可以从它们中获得哪些好处。此外,我们还将探讨如何通过插件扩展 Highcharts。在本章中,我们将涵盖以下主题:
-
服务 export.highcharts.com 提供的内容
-
使用新云服务——cloud.highcharts.com 创建在线图表的逐步练习
-
高 charts 插件是什么
-
两个插件示例 - 回归和可拖动点
-
通过互操作插件创建新的用户体验
-
如何编写插件 – 扩展现有方法、导出新方法以及处理事件
Highcharts 导出服务器 – export.highcharts.com
在上一章中,我们探讨了在服务器端运行 Highcharts。然而,一些用户可能不想设置自己的服务器操作。这时,export.highcharts.com 就派上用场了。最初,它仅用于设置导出模块,以便在互联网上运行 Highcharts 的用户可以自由导出他们的图表。后来,URL 扩展以支持在线服务。这使得用户可以输入自己的 Highcharts 配置并下载生成的图表图像。
以下内容是 export.highcharts.com 网页的一部分:

如我们所见,用户输入确实对应于服务器端脚本的参数,即我们在上一章中提到的 highcharts_convert.js。Web 界面和服务器过程都是用 Java 实现的,它们将用户的选择传递给 PhantomJS/highcharts_convert.js 进程,并将其导出为 SVG。一旦 Java 服务器收到 SVG 结果,它就会启动 Batik 将其格式化为图像文件。整个网络服务解决方案的源代码位于 exporting-server/java/highcharts-export 目录中。
在线导出服务的缺点是它不是所见即所得(WYSIWYG),因此使用起来可能不太直观。因此,诞生了一个具有更丰富用户体验的新网络服务——Highcharts 云服务。我们将在下一节中体验一下,看看它带来了哪些不同。
Highcharts 云服务
在本节中,我们将回顾由 Highcharts 团队开发的一个全新的在线图表服务——Highcharts 云服务 (cloud.highcharts.com)。以下截图显示了初始欢迎屏幕:

Highcharts 云服务是扩展产品线的一个重大里程碑。它旨在为用户提供:
-
即使没有任何 JavaScript 或 Highcharts 知识,也能创建 HTML5 图表(在 SIMPLE 模式下)
-
无需在 Web 服务器和 Highcharts 上安装和设置,即可交互式地原型化图表
-
使用简单的超链接将图表嵌入在线文章、应用程序或网页中
-
将他们的图表存储在云中而不是本地
-
与其他人轻松分享他们的图表
以下是从云服务创建的图表的新闻网站截图:

让我们尝试使用云服务创建我们的第一个图表。Web 界面基于向导,对任何非技术用户来说都是直观的。以下是云服务的初始屏幕:

界面中分为三个主要部分:左侧向导面板、右上角的结果图表面板和右下角的系列数据编辑器。在左侧向导面板的顶部,显示了我们当前所处的阶段。在第一个阶段(导入),我们可以将 CSV 数据粘贴到文本区域并点击上传,或者通过右下角的编辑器手动输入系列数据。
在前一张截图中,我们已经在左侧面板中粘贴了一些数据。当我们点击上传并继续按钮时,应用程序进入第 2 阶段(模板)。以下是截图:

首先,我们可以看到右上角面板更新了默认的折线系列,而右下角的编辑器面板填充了系列数据。尽管右上角的图表没有显示任何有意义的内容,但在稍后的配置阶段将会变得清晰。在此阶段,如果我们需要,可以进一步在编辑器面板中编辑系列数据。让我们在左侧面板中选择一个系列,3D 柱状图,这将立即更新右上角的图表:

在右上角面板中显示了一个 3D 柱状图,但坐标轴和图表标题仍然不正确。我们可以点击继续自定义按钮(如前一张截图所示)或点击自定义进入下一阶段,调整图表中的每个组件:

如我们所见,图表中有不同的区域我们可以选择进行配置。在这个例子中,我们已更改了图表标题、坐标轴类型、标题和标签格式。请注意,在自定义阶段,下面显示了三个标签。这使用户可以选择如何更新图表。简单是最基本的,适用于没有编程经验的技术用户,或者用于快速简单的更改。高级模式适用于熟悉 Highcharts 选项的用户。用户界面是以名称和值样式进行的简单属性更新。代码级别适用于希望为图表编写 JavaScript 代码的用户,例如事件处理程序。以下截图显示了高级和代码用户界面:

一旦我们对最终的图表满意,我们可以点击 继续分享 以生成图表的链接。
Highcharts 插件
Highcharts 可以通过插件进行扩展,这些插件允许我们在不干扰代码核心层的情况下添加功能,并且易于共享。网上有一个插件库,由 Highcharts 员工和其他用户贡献,位于 www.highcharts.com/plugin-registry。开发插件功能的一个显著优势是我们可以选择和选择插件功能,并从中构建一个压缩的 JavaScript 库。实际上,我们已经在下载页面上的 Highcharts 库中做到了类似的事情。
在本节中,我们将浏览一些你可能觉得有用的插件。
回归绘图插件
当我们创建一个包含大量数据点的散点图时,通常值得叠加回归线。当然,我们可以通过手动添加线系列来实现这一点。然而,我们仍然需要编写回归分析的代码。包含一个插件会方便得多。Ignacio Vazquez 创建的 Highcharts 回归插件做得很好。首先,我们包含该插件:
<script src="img/highcharts-regression.js"> </script>
然后,我们像往常一样创建我们的散点图。由于我们包含了回归插件,它提供了额外的回归选项:
series: [{
regression: true ,
regressionSettings: {
type: 'linear',
color: 'rgba(223, 83, 83, .9)'
},
name: 'Female',
color: 'rgba(223, 83, 83, .5)',
data: [ [161.2, 51.6], [167.5, 59.0],
....
这里是演示中的图表(www.highcharts.com/plugin-registry/single/22/Highcharts%20regression):

可拖动点插件
这里是 Torstein 另一个引人注目的插件,它允许图表查看者拖动任何系列数据点。我们使用以下行导入该插件:
<script src="img/draggable-points.js"></script>
此插件引入了两个新的点事件,drag 和 drop,我们可以通过 plotOptions.series.point.events 选项(或数据点对象中的 events 选项)来定义处理程序。以下是演示中的示例代码:
events: {
drag: function(e) {
// Update new data value in 'drag' div box
$('#drag').html(
'Dragging <b>' + this.series.name + '</b>, <b>' +
this.category + '</b> to <b>' +
Highcharts.numberFormat(e.newY, 2) + '</b>'
);
},
drop: function() {
$('#drop').html(
'In <b>' + this.series.name + '</b>, <b>' +
this.category + '</b> was set to <b>' +
Highcharts.numberFormat(this.y, 2) + '</b>'
);
}
}
当我们选择一个数据点并移动鼠标时,会触发拖动事件,并且演示代码将更新图表下方的文本框,如下面的截图所示。该插件提供了几个新选项来控制我们如何拖放数据点。以下是一个使用示例:
series: [{
data: [0, 71.5, 106.4, .... ],
draggableY: true,
dragMinY: 0,
type: 'column',
minPointLength: 2
}, {
data: [0, 71.5, 106.4, ....],
draggableY: true,
dragMinY: 0,
type: 'column',
minPointLength: 2
}, {
布尔选项 draggableX/Y 通知数据点可以拖动的方向。此外,可以通过 dragMinX/Y 和 dragMaxX/Y 选项限制拖动范围。以下截图显示了正在拖动的柱状图:

通过组合插件创建新的效果
到目前为止,我们已经看到了两个独立插件的效果。是时候通过加载这两个插件并组合它们的效果来创建新的用户体验了。想法是创建一个带有可移动数据点的回归图,这样当我们拖动一个数据点时,回归线会自动实时调整。在这样做的时候,我们需要稍微修改回归插件代码。以下是原始代码的一部分:
(function (H) {
H.wrap(H.Chart.prototype, 'init', function (proceed) {
....
for (i = 0 ; i < series.length ; i++){
var s = series[i];
if ( s.regression && !s.rendered ) {
// Create regression series option
var extraSerie = {
....
};
// Compute regression based on the type
if (regressionType == "linear") {
regression = _linear(s.data) ;
extraSerie.type = "line";
}
}
}
// Append to series configuration array
....
});
function _linear(data) {
....
}
....
})(Highcharts);
基本上,在图表创建和渲染之前,插件会扫描系列数据,计算回归结果,并将结果格式化为线系列选项。为此,回归实现被包含在 Chart 类的 init 方法中,该方法在创建 Chart 对象时被调用。为了扩展 Highcharts 中的现有功能,我们在对象的原型中的方法上调用 wrap 函数。换句话说,当创建 Chart 对象时,它将调用 init 函数,该函数执行堆叠在内部的每个函数(闭包)。我们将在稍后进一步研究这个主题。
为了在运行时更新回归线,我们需要从插件外部调用 _linear 的能力。以下是添加新方法 updateRegression 的新修改的伪代码:
(function (H) {
H.wrap(H.Chart.prototype, 'init', function (proceed) {
....
});
H.Chart.prototype.updateRegression = function(point) {
// Get the series from the dragged data point
var series = point.series;
var chart = series.chart;
// Get the regression series associated
// with this data series
var regressSeries = chart.series[series.regressIdx];
// Recompute based on the regression type and
// update the series
if (series.regressionType == "linear") {
regression = _linear(series.data) ;
}
regressSeries.update(regression, true);
};
function _linear(data) {
....
}
....
})(Highcharts);
现在我们有一个具有可访问方法 updateRegression 的回归插件,可以调用内部作用域函数 _linear。有了这个新的插件函数,我们可以将功能与可拖拽插件导出的 drag 事件链接起来:
plotOptions: {
series: {
point: {
events: {
drag: function(e) {
// Pass the dragged data point to the
// regression plugin and update the
// regression line
Highcharts.charts[0]
.updateRegression(e.target);
}
}
}
}
}
为了更清楚地观察新的效果,我们使用了一组更小的散点图。以下是包含两个插件选项的系列配置:
series: [{
regression: true ,
regressionSettings: {
type: 'linear',
color: 'rgba(223, 83, 83, .9)'
},
draggableX: true,
draggableY: true,
name: 'Female',
color: 'rgba(223, 83, 83, .5)',
data: [ [161.2, 51.6], [167.5, 59.0], [159.5, 49.2],
[157.0, 63.0], [155.8, 53.6], [170.0, 59.0],
[159.1, 47.6], [166.0, 69.8], [176.2, 66.8],
....
在配置中,我们有在 x 和 y 方向上都可以拖动的散点图,回归类型是线性的。让我们加载我们新的改进图表。以下是初始屏幕:

让我们假设一种过度活跃、未经批准的减肥药“鼠标按下”已经进入市场,它有一些未报告的副作用。不幸的人会迅速增加,而真正不幸的人的身高会下降。以下是新结果的结果:

当我们在最右侧的数据点的这些权重上按下鼠标时,回归线会实时响应,并更新左上角的图例框。
创建插件的指南
一些用户创建插件是因为某些任务无法通过 API 完成,而且这个任务足够通用,对其他图表用户也有益。然而,没有标准的 API 来创建插件;开发者必须亲自动手,利用他们对 Highcharts 代码的了解。尽管如此,我们可以从现有的插件中概括出一些指导方针。
在自调用匿名函数中实现插件
总是在一个自调用的匿名函数中实现插件,并将 Highcharts 作为参数。自调用的匿名函数是 JavaScript 中相当常见的技术。所有 Highcharts 插件都是用这种方式实现的。以下代码展示了示例:
(function (Highcharts) {
....
// what happens in anonymous function,
// stays in anonymous function
function hangoverIn(place) {
if (place === 'home') {
return 'phew';
} else if (place === 'hospital') {
return 'ouch';
} else if (place === 'vegas') {
return 'aaahhhhh!!';
}
}
})(Highcharts);
插件中声明的所有命名函数和变量都无法从外部访问,因为它们是在自调用的匿名函数的作用域内声明的(闭包和模块模式)。因此,实现对外部是私有的,除非我们在 Highcharts 命名空间中分配属性。
使用 Highcharts.wrap 来扩展现有函数
根据插件任务,某些插件需要扩展现有函数。例如,回归插件调用 H.wrap 来扩展 init 函数,该函数由 Chart 构造函数调用。请参见以下代码:
(function (H) {
H.wrap(H.Chart.prototype, 'init',
function (proceed) {
// Plugin specific code for processing
// chart configuration
....
// Must include this instruction for wrap
// method to work
proceed.apply(this,
Array.prototype.slice.call(arguments, 1));
}
);
})(Highcharts);
Highcharts.wrap 是插件中常用的一种函数。wrap 函数的工作方式是用包含先前实现 proceed 的新函数体覆盖 init 函数。当我们用匿名函数中的新插件代码扩展方法时,我们必须接受 proceed 参数,它代表父函数体。在我们自己的插件代码之前或之后,我们必须对相同的参数调用 proceed.apply,以完成执行链。
作为参考,我们始终可以扩展特定系列的方法,例如 Highcharts.seriesTypes.column.prototype,其中 seriesTypes 是一个包含所有系列类的对象。或者,如果插件需要为所有系列设置,我们可以调用 Highcharts.Series.prototype 上的 wrap 方法代替(所有系列类都是从 Highcharts.Series 扩展的)。
使用原型来暴露插件方法
有时我们可能需要为插件导出特定的方法。为此,我们应始终在 prototype 属性内部定义新方法,例如:
(function (H) {
H.Chart.prototype.pluginMethod = function(x, y) {
....
};
})(Highcharts);
这是因为在匿名函数内声明的任何代码都无法从外部访问。因此,创建可调用方法的唯一方法是将对象绑定到匿名函数传递的对象上,在这种情况下是顶级 Highcharts 对象。prototype 属性是 JavaScript 中从对象继承属性和方法的标准方式。在 prototype 属性中附加方法的原因是我们不知道开发者会如何使用插件。例如,他们可能会决定创建一个新的 Chart 对象并调用插件方法。在这种情况下,插件代码仍然可以工作。
定义一个新的事件处理器
插件的一种动作类型是定义新事件,正如我们在可拖动插件中看到的那样。这里的问题是:我们需要访问图表元素来绑定事件处理器,也就是说,在图表渲染之后。然而,init 类方法是在图表渲染之前执行的。这就是Highcharts.Chart.prototype.callbacks 数组发挥作用的地方。这是一个设计用来存储需要访问元素的外部函数的地方。例如,导出模块使用这个数组来在图表中插入按钮。以下是一些设置事件的伪代码:
(function (H) {
Highcharts.Chart.prototype.callbacks.push(function (chart) {
// A DOM element, e.g. chart, container, document,
var element = chart.container;
// DOM event type: mousedown, touchstart, etc
var eventName = 'mousedown';
// We can call fireEvent to fire a new event
// defined in this plugin
fireEvent(element, newEvent, evtArgs,
function(args) {
....
});
// Plugin event
function handler(e) {
// Plugin code
...
}
// Bind specific event to the defined handler
Highcharts.addEvent(element, eventName, handler);
});
})(Highcharts);
Highcharts 有两个与事件相关的方法:addEvent 和 fireEvent。前者方法是将事件绑定到图表元素并带有处理器,而 fireEvent 则在元素上触发事件。
之前的代码基本上创建了一个匿名函数,该函数组织所有事件设置,例如定义处理器并将它们绑定到元素上。该函数必须接受 chart 作为唯一参数。最后,我们将该函数追加到 callbacks 数组中。该函数将在图表完全渲染后自动执行。
摘要
在本章中,我们参观了 Highcharts 在线导出服务器和新的云服务。我们对云服务进行了简短的游览,并展示了我们如何在线创建图表,而无需任何先前的 JavaScript 和 Highcharts 知识。另一个讨论的主题是 Highcharts 插件,我们在其中尝试了两个插件:回归和可拖动数据点。然后我们演示了如何修改一个插件,使其两个插件能够协同工作,提供新的用户体验。
到目前为止,我所有关于 Highcharts 的知识和经验都包含在这本日志中。在三年内完成两版对我来说比预期的要困难得多。因此,我的 Highcharts 之旅已经结束,我可以把时间重新投入到它应该始终在的地方,家庭。对于您购买和阅读这本书,我表示由衷的感谢。我的目标是向您展示 Highcharts 的动态性和令人印象深刻之处,我希望我已经实现了这一点,并以一个高调的结尾结束了这本书。


浙公网安备 33010602011771号