ProEssentialsJS v11 入门教程

从 npm 安装到可交互的实时图表 - 按照实际构建的顺序,一步一步来

JavaScript chart walkthrough
WebAssembly chart tutorial
npm install javascript chart
javascript chart example code
ProEssentialsJS chart
javascript real time chart tutorial
canvas2d chart getting started
javascript charting library walkthrough

ProEssentialsJS v11 Walkthrough
Your first chart, step by step

本教程将从零开始构建一个真正可用的图表:安装、数据、坐标轴、交互、一条实时更新的曲线,以及一个响应数据点击的事件处理程序。大约需要十分钟,如果您不想引入构建工具链,也完全不必。

如果您已经在桌面端用过 ProEssentials,可以快速略读。 JavaScript 的属性名就是 WinForms 的属性名,事件名同样如此。第 10 步把 C# 与 JavaScript 并排放在一起,您可以看到改动有多小。

更想直接读成品代码?打开在线示例浏览器 — 每个示例都在图表旁显示自身的源码。

1) 安装

有两种入门方式,而您所写的页面在两种方式下完全相同,不同的只是路径。

克隆 starter 仓库 — 让图表出现在屏幕上的最短路径。库就提交在页面旁边,因此没有任何东西需要解析,也没有任何东西需要安装:

git clone https://github.com/GigasoftInc/proessentials-js-starter
npm start

这就是全部步骤。该包没有依赖,它定义的那一个脚本会启动一个小型静态服务器,原因见下文。

或者从 npm 安装,在您自己的项目中:

npm install proessentials

无论哪种方式,页面都是两个脚本标签。 第一个是引擎,一个传统脚本。第二个是您自己的代码,以模块方式加载。

<script src="proessentials.iife.js"></script>
<script type="module" src="app.js"></script>

app.js 中,您导入所需图表的属性接口。这是在您自己的 JavaScript 中的一次 import,而不是又一个 include:

import { attachApi, Enums as E } from './pe-api-graph.js';

它无法合并为一个标签,这并不是打包上的缺陷:属性接口是真正的 ES 模块,而传统脚本无法容纳模块。同时提供两种形式的图表库,也都在同一处分开。从 npm 安装时,这两个路径改为指向 node_modules/proessentials/dist/请保留开头的 ./,因为开头的 / 也是合法 URL,在浏览器中运行完全正常,只是编辑器的补全会悄无声息地全部失效。

此外无需任何配置。 不需要打包器,不需要 import map,不需要 jsconfig.json,也不需要复制资源。库会相对于自身的脚本 URL 而非页面来解析它自己的 WebAssembly 二进制文件和内置位图,因此无论库放在哪里都能找到。编辑器补全能工作也是同样的道理:TypeScript 定义就放在它所描述的模块旁边。

请通过服务器提供页面,不要从磁盘直接打开。 file:// 形式的 URL 没有源(origin),而浏览器不会为没有源的页面获取 WebAssembly 模块 — 于是页面保持空白,但页面本身并没有任何问题。任何静态服务器都可以,上面的 npm start 就是其中之一。这一点常让从「可以直接打开」的库转过来的人受阻:没有模块、也没有任何东西需要获取的普通脚本,自然可以顺利打开。而这个需要获取一个 2.9 MB 的引擎,如果您尝试,页面会明确告诉您。

运行以上任何内容都不需要许可密钥。 没有激活,没有域名注册,也没有回传。

2) 给它一个绘制的位置

一个元素即可。图表会自动适配它的尺寸。

<div id="chart" style="width:800px; height:520px"></div>

3) 创建图表

引擎是 WebAssembly,因此只加载一次,且是异步的。之后的一切都是同步的属性设置。

// 1. The engine. Once per page, however many charts you go on to make //
const m = await ProEssentials();

// 2. The control. This is the WinForms designer step: dropping a Pego
// on the form. autoResize is the web Dock = Fill //
const ctl = new PeControl(document.getElementById('chart'), {
  module: m,
  kind: 'graph',
  autoResize: true,
});

// 3. The property tree //
const Pego1 = ctl.attach(attachApi);

三个对象,各司其职。 m 是引擎,之后您不会再碰它。ctl 是控件,画布、菜单、滚动条和事件都归它所有。Pego1 是属性接口,本教程余下的内容都在这里进行。

为什么叫 Pego1 因为在每个平台上的每一个 ProEssentials 示例里,这个对象三十年来一直叫这个名字。您可以随意命名,但如果保留这个名字,我们的示例和 AI 助手的输出都能直接粘贴进您的项目。

4) 传入数据 — 并留意顺序

这是唯一值得读两遍的一步。 ProEssentials 要求按特定顺序设置属性:

先 Subsets,再 Points,再数据,再其他全部,最后 render()

原因在于,每一次数据写入都会对 Subsets × Points 做边界检查。如果在告诉图表尺寸之前就设置数据,写入会被拒绝 — 而图表仍会用默认数据绘制出来,看上去完全说得通。这是所有平台上最常见的一个错误,一旦知道就很容易避开。

// 1. Shape first. Subsets = rows, Points = columns //
Pego1.PeData.Subsets = 2;
Pego1.PeData.Points = 6;

// 2. Then the data //
Pego1.PeData.Y[0][0] = 10; Pego1.PeData.Y[0][1] = 30;
Pego1.PeData.Y[0][2] = 20; Pego1.PeData.Y[0][3] = 40;
Pego1.PeData.Y[0][4] = 30; Pego1.PeData.Y[0][5] = 50;
Pego1.PeData.Y[1][0] = 15; Pego1.PeData.Y[1][1] = 63;
Pego1.PeData.Y[1][2] = 74; Pego1.PeData.Y[1][3] = 54;
Pego1.PeData.Y[1][4] = 25; Pego1.PeData.Y[1][5] = 34;

注意下标写法。 C# 写作 PeData.Y[s, p];JavaScript 没有二维下标,因此写作 PeData.Y[s][p]。这是整套 API 中唯一形式上的差别。

5) 标题、标签与坐标轴

Pego1.PeString.MainTitle = "Units Sold per Month";
Pego1.PeString.SubTitle = "";
Pego1.PeString.YAxisLabel = "Units Sold";

Pego1.PeString.PointLabels[0] = "Jan";
Pego1.PeString.PointLabels[1] = "Feb";
Pego1.PeString.PointLabels[2] = "Mar";
Pego1.PeString.PointLabels[3] = "Apr";
Pego1.PeString.PointLabels[4] = "May";
Pego1.PeString.PointLabels[5] = "Jun";

Pego1.PeString.SubsetLabels[0] = "Texas";
Pego1.PeString.SubsetLabels[1] = "Florida";

坐标轴默认自动缩放。当您需要固定它时 — 在实时图表上几乎总是需要,因为自动缩放的坐标轴每一帧都会重新适配,曲线看上去像在呼吸 — 请切换为手动控制:

Pego1.PeGrid.Configure.ManualScaleControlY = E.ManualScaleControl.MinMax;
Pego1.PeGrid.Configure.ManualMinY = 0;
Pego1.PeGrid.Configure.ManualMaxY = 100;

Graph 对象只缩放 Y 轴。 它把数据点从左到右等间距绘制,因此没有需要固定的 X 刻度,也没有 ManualMinX。如果您的 X 值本身带有含义,例如时间戳或不规则的采样间隔,那属于 Scientific Graph,它以相同的名称提供完整的 X 一组属性。整套 API 始终一致;两者都可以在示例浏览器中查看。

6) 选择绘制方式

枚举位于 E 之下,名称与 .NET 枚举完全相同。

Pego1.PePlot.Method = E.GraphPlottingMethod.Bar;
Pego1.PeGrid.LineControl = E.GridLineControl.Both;
Pego1.PeGrid.Style = E.GridStyle.Dot;
Pego1.PePlot.Option.BarGlassEffect = true;
Pego1.PePlot.DataShadows = E.DataShadows.Shadows;

7) 让它具备交互能力

缩放、滚动条、右键菜单和跟踪光标全部内置。它们是属性,不是您需要编写的代码。

// Left-drag draws a zoom box; z or the popup menu undoes it //
Pego1.PeUserInterface.Allow.Zooming = E.AllowZooming.HorzAndVert;

// Follows the data under the pointer and shows the value //
Pego1.PeUserInterface.Cursor.PromptTracking = true;

// Middle-button drag to pan //
Pego1.PeUserInterface.Scrollbar.MouseDraggingX = true;
Pego1.PeUserInterface.Scrollbar.MouseDraggingY = true;

// This enables data hot spots. Step 9 writes the handler //
Pego1.PeUserInterface.HotSpot.Data = true;

8) 绘制 — 然后让它动起来

一次调用,放在最后,在所有属性都设置完之后。

ctl.render(); // last, always //

实时更新有两种形态,值得先弄清您需要哪一种。

追加 — 向不断增长的曲线添加新采样点。开销低,也是走纸式图表的通常选择。

替换 — 每一帧都交给图表一份全新的数据集。示波器每次扫描就是这样做的,任何在绘制前对数据做过滤或重算的应用程序也是如此。这是更难的情形,也正是这个引擎为之而生的情形:下面的示例在每一帧替换四个各 100,000 点的 Subset — 合计 400,000 点。

// requestAnimationFrame, not setInterval -- ask for what the display
// will take and never more, or the callbacks queue up //
const tick = () => {
  Pego1.PeData.Y.load(block, 4, 100000); // values, subsets, points //
  ctl.render();
  requestAnimationFrame(tick);
};
requestAnimationFrame(tick);

有两个值得养成的习惯。 固定坐标轴,不要让它自动缩放,否则图表每一帧都会重新适配,曲线看起来像在呼吸。以及用 load() 把整个类型化数组一次性交给引擎,而不是逐点赋值 — 这样只跨越一次边界。

在 X 序列真实存在的 Scientific Graph 上还有第三种:当只有 Y 发生变化时设置 PeData.ReuseDataX = true,引擎就不会重建一条并未变动的 X 序列。Graph 对象没有 X 序列,因此这里不适用。

9) 响应数据点上的点击

第 7 步启用了数据热点。下面就是处理程序。

ctl.PeDataHotSpot.add((sender, ev) => {
  alert("Subset " + ev.subset + ", Point " + ev.point +
      " with a value of " + Pego1.PeData.Y[ev.subset][ev.point]);
});

点击一根柱子。引擎会在鼠标按下时做命中测试,填好热点记录,并触发对应的事件,其中 ev.subsetev.point 都已经解析完毕。您不需要挂接点击监听器,也不需要自行解析类型码。

.add 就是 C# 的 +=.remove 就是 -= JavaScript 没有可以重载的运算符,因此事件以方法的形式提供。它们和 .NET 原版一样是多播的:第二次 .add 会再添加一个处理程序,而不是替换第一个。每个句柄还带有 .count.clear()

一个 Graph 带有 37 个事件,拼写与 .NET 完全一致 — PeSubsetHotSpotPeGraphHotSpotPeMainTitlePeZoomInPeCursorMoved 等等。某个控件无法触发的事件是直接不存在,而不是留一个失效的入口:没有可订阅的句柄,因此一个永远不会触发的订阅会在写下它的那一行抛出 TypeError,而不是在运行时悄无声息。

注意接收方。 属性在 Pego1 上,事件在 ctl(也就是控件本身)上。在 WinForms 中两者都在 pego1 上。Web 端把事件留在控件上,这样即使页面从未绑定属性接口,也依然能收到事件 — 一个您只用来点击的图表,完全不需要调用 attach()

10) 如果您已经熟悉桌面控件

同一个图表,写两遍。这就是这套 API 的全部主张,展示比解释更省事。

C# — WinForms、WPF 或 WinUI:

pego1.PeData.Subsets = 2;
pego1.PeData.Points = 6;
pego1.PeData.Y[0, 0] = 10;
pego1.PeString.MainTitle = "Units Sold per Month";
pego1.PePlot.Method = GraphPlottingMethod.Bar;
pego1.PeUserInterface.HotSpot.Data = true;
pego1.PeDataHotSpot += OnDataHotSpot;
pego1.PeFunction.ReinitializeResetImage();

JavaScript — 浏览器:

Pego1.PeData.Subsets = 2;
Pego1.PeData.Points = 6;
Pego1.PeData.Y[0][0] = 10;
Pego1.PeString.MainTitle = "Units Sold per Month";
Pego1.PePlot.Method = E.GraphPlottingMethod.Bar;
Pego1.PeUserInterface.HotSpot.Data = true;
ctl.PeDataHotSpot.add(onDataHotSpot);
ctl.render();

相同的属性名,相同的顺序,底层相同的引擎。差别有五处,就这么多:二维下标、枚举前缀、用 .add 代替 +=、事件的接收方,以及最后那次调用。您对 ProEssentials 的了解全部可以迁移,我们的 AI 助手所掌握的同样如此 — 因为它是基于同一套属性模型作答的。

正在迁移已有的桌面代码? 最后一行可以原样运行:Pego1.PeFunction.ReinitializeResetImage() 在 JavaScript 控件上同样存在,ReinitializeResetGetRectGraph 以及 PeFunction 的其余部分也都在。它与 render() 是同一次调用,后者是我们在新的浏览器代码中使用的较短写法。

接下来可以看什么
  • 在线示例浏览器 — 与我们桌面演示相同的示例集合,每个都附带源码。
  • PE-Query — 直接询问您需要的属性,得到的答案是对照已编译引擎验证过的,而不是从文档里搜出来的。
  • API 浏览器 — 每个控件的每一个属性。
  • 横向对比 — 与 Highcharts、SciChart.js、LightningChart JS 等的对比。
  • 联系我们 — 支持免费、无限制,并且由编写引擎的人亲自回答。