|
This builds a chart from nothing: install, data, axes, interaction, a real-time trace, and an event handler for clicks on the data. No build toolchain is required.
If you already know ProEssentials on the desktop, skim. The JavaScript property names are the WinForms property names, and so are the event names. Step 10 puts the C# and the JavaScript side by side.
See ProEssentialsJS Overview for the object model and the four differences from .NET, Installation for what the package contains, and Deployment for what has to reach the browser.
1) Install
Clone the starter repository, which carries the library beside the page, and run it. There is nothing to install and nothing to configure.
|
git clone https://github.com/GigasoftInc/proessentials-js-starter.git
npm start
|
In a project of your own the library comes from npm instead, and the page is the same two tags pointed at node_modules. See Installation -- particularly the leading ./ on the import, which decides whether your editor offers completion.
The page has to be served, not opened from disk. A file:// page has no origin and a browser will not fetch a WebAssembly module without one. The starter includes a small server for exactly this reason.
No licence key is required. There is no activation, no domain registration and no phone home.
2) Give it somewhere to draw
One element. The chart sizes itself to it.
|
<div id="chart" style="width:800px; height:520px"></div>
|
3) Create the chart
The engine is WebAssembly, so it loads once and asynchronously. Everything after that is synchronous property setting.
Three objects, in order: the engine, the control that hosts it, and the property tree you set everything on.
|
import { attachApi, Enums as E } from './pe-api-graph.js';
// 1. The engine. Once per page, however many charts you draw. //
const m = await ProEssentials();
// 2. The control. This is the WinForms designer step, dropping a Pego on the form. //
const host = document.getElementById('chart');
const ctl = new PeControl(host, {
module: m,
kind: 'graph',
autoResize: true,
});
// 3. The property tree. //
const Pego1 = ctl.attach(attachApi);
|
autoResize is the web's Dock = Fill. The control watches its host element and re-fits when the browser window changes, so the CSS decides the size from then on.
kind selects the chart object, and the import on the first line has to match it: graph with pe-api-graph.js, and likewise sgraph, pie, pgraph and sgraph3d.
Why Pego1? That is what the object is called in every ProEssentials example, on every platform. Name it whatever you like, but if you keep the name our examples paste straight into your project.
4) Pass the data, and mind the order
ProEssentials wants properties in a specific order: Subsets, then Points, then the data, then everything else, and render() last.
Every data write is bounds checked against Subsets times Points. Set the data before you have told the chart how big it is and the writes are rejected, and the chart still draws using default data. This is the most common mistake on every platform. See Property Arrays.
|
// 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;
|
Note the indexing. C# writes PeData.Y[s, p]. JavaScript has no two-dimensional indexer, so it is PeData.Y[s][p]. That is the only shape difference in the whole API.
5) Titles, labels and axes
|
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";
|
Axis scaling is automatic by default. To pin it, and on a real-time chart you almost always should because an autoscaling axis refits on every frame:
|
Pego1.PeGrid.Configure.ManualScaleControlY = E.ManualScaleControl.MinMax;
Pego1.PeGrid.Configure.ManualMinY = 0;
Pego1.PeGrid.Configure.ManualMaxY = 100;
|
The Graph object plots equally spaced left to right, so it has no X axis scale. ManualScaleControlX, ManualMinX and ManualMaxX are Scientific Graph properties. See Graph Object's X Axis.
6) Choose how it draws
Enumerations live on E and carry the .NET enum names.
|
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;
|
Colours, And One Name To Get Right
QuickStyle writes a whole palette, so set it BEFORE any individual colour or it will overwrite what you chose.
|
Pego1.PeColor.QuickStyle = E.QuickStyle.LightShadow;
Pego1.PeColor.BitmapGradientMode = false;
// C#: Color.FromArgb(60, 0, 180, 0) //
Pego1.PeColor.SubsetColors[0] = PERGB(60, 0, 180, 0);
Pego1.PeColor.SubsetColors[1] = PERGB(180, 0, 0, 130);
// 1 is a sentinel meaning empty, not a colour //
Pego1.PeColor.Desk = 1;
|
It is PeColor.Desk, not PeColor.DeskColor. The flat .NET name is DeskColor; the grouped tree drops the prefix because the group is already PeColor. Writing PeColor.DeskColor = 1 does not fail -- it creates an ordinary JavaScript property on the object, changes nothing, and reports no error. If a colour will not take, check the name against the property tree before looking anywhere else.
The Rest Of The Picture
|
Pego1.PePlot.Option.GradientBars = 8;
Pego1.PePlot.SubsetLineTypes[0] = E.LineType.MediumSolid;
Pego1.PePlot.SubsetLineTypes[1] = E.LineType.MediumDash;
Pego1.PeLegend.Location = E.LegendLocation.Left;
Pego1.PeTable.Show = E.GraphPlusTable.Both;
Pego1.PeData.Precision = E.DataPrecision.NoDecimals;
Pego1.PeFont.FontSize = E.FontSize.Large;
Pego1.PeFont.Label.Bold = true;
|
7) Make it interactive
Zooming, scrollbars, the right click menu and the tracking cursor are built in. They are properties, not code you write.
|
// 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;
// The tracking value follows the cursor as a tooltip //
Pego1.PeUserInterface.Cursor.PromptLocation = E.CursorPromptLocation.ToolTip;
Pego1.PeUserInterface.Allow.FocalRect = false;
// Right-click menu entries //
Pego1.PeUserInterface.Menu.QuickStyle = true;
Pego1.PeUserInterface.Menu.CustomizeDialog = true;
|
8) Render it, then keep it moving
One call, at the end, after every property is set.
|
Pego1.PeFunction.ReinitializeResetImage(); // last, always //
|
For real time there are two shapes. Append adds new samples to a growing trace, and is the usual choice for a strip chart.
|
Pego1.PeData.Y.append(samples, samples.length);
Pego1.PeFunction.ReinitializeResetImage();
|
Replace hands the chart an entirely new dataset every frame, which is what a scope does on each sweep and what any application does when the data is filtered or recomputed before it is drawn. The example below replaces four subsets of 100,000 points, 400,000 in total, on every frame.
|
// 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);
Pego1.PeFunction.ReinitializeResetImage();
requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
|
Pin the axes rather than letting them autoscale, or the chart refits on every frame. On the Scientific Graph, also set PeData.ReuseDataX when only Y is changing, so an X series that did not move is not rebuilt. See ProEssentialsJS Real-Time Charts for the strip chart, the zero-copy path, and the point label limitation.
9) Respond to a click on a data point
Step 7 enabled data hot spots. This is the handler.
|
chart.PeDataHotSpot.add(function (sender, ev) {
alert('Subset ' + ev.subset + ', Point ' + ev.point +
' with a value of ' + Pego1.PeData.Y[ev.subset][ev.point]);
});
|
Click a bar. The engine hit tests on mouse down, fills the hot spot record, and raises the matching event with ev.subset and ev.point already decoded. You do not attach a click listener and you do not decode a type code. This is the same dispatch the desktop controls perform, so a C# hot spot handler ports across unchanged.
Events live on the control, not on the property surface, exactly as they do in .NET. That is chart here, not Pego1. Subscribing is additive: .add is C#'s += and .remove is -=, so a second handler adds rather than replacing the first.
A Graph carries 37 events, and an event a chart cannot raise is undefined rather than a handle that never fires. See ProEssentialsJS Events for the full surface, the 20 events a click raises and what each one carries, and ProEssentialsJS Deployment for the one file the .NET named events arrive with.
10) If you already know the desktop control
The same chart, twice.
C#, WinForms, WPF or 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, the browser:
|
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);
Pego1.PeFunction.ReinitializeResetImage();
|
Same property names, same order, same engine underneath. Five differences, and that is the list: the two-dimensional indexer, the enum prefix, .add for +=, the receiver for events, and the final call.
Transcribing existing desktop code? The last line runs verbatim. PeFunction.ReinitializeResetImage is on the JavaScript control too, along with Reinitialize, Reset, GetRectGraph and the rest of PeFunction. It is the same call as render(), which is the shorter spelling used in new browser code.
|