Public API - Custom Chart Designer

ActiveReportsJS provides a comprehensive API that enables developers to create reports programmatically. This example demonstrates how to generate a chart report at runtime, showcasing the flexibility and capabilities of the API. Through this approach, developers can customize report layouts, data sources, and formatting to suit their specific needs, creating dynamic and adaptable report solutions for a wide range of applications. While the example presented here uses Angular, the same technique can be easily adapted and applied to any front-end framework.

The demo is being dynamically compiled to support real-time code editing... For quicker access to features, switch to the "JavaScript" tab for a smoother experience! :)
data.ts
report.service.ts
app.component.html
app.component.ts
app.component.css
index.html
Copy to CodeMine
loading...
export enum ChartDataType { SalesOverTime = 'SalesOverTime', SalesPerChannel = 'SalesPerChannel', SalesPerCategory = 'SalesPerCategory', } export type DataItem = { value: string; label?: string; }; export const ChartPalettes: DataItem[] = [ { value: 'Office' }, { value: 'Light' }, { value: 'Dark' }, { value: 'Blue' }, { value: 'Orange' }, ]; export const ChartDataTypes: DataItem[] = [ { value: ChartDataType.SalesOverTime, label: 'Sales over time' }, { value: ChartDataType.SalesPerChannel, label: 'Sales per channel' }, { value: ChartDataType.SalesPerCategory, label: 'Sales per category' }, ]; export const TimePlotTypes: DataItem[] = [{ value: 'Line' }, { value: 'Area' }]; export const CategoryPlotTypes: DataItem[] = [ { value: 'Bar' }, { value: 'Column' }, ]; export const ChannelPlotTypes: DataItem[] = [ { value: 'Pie' }, { value: 'Donut' }, ]; export const getPlotTypes = (dataType: ChartDataType): DataItem[] => { switch (dataType) { case ChartDataType.SalesOverTime: return TimePlotTypes; case ChartDataType.SalesPerChannel: return ChannelPlotTypes; case ChartDataType.SalesPerCategory: return CategoryPlotTypes; default: return []; } };
export enum ChartDataType { SalesOverTime = 'SalesOverTime', SalesPerChannel = 'SalesPerChannel', SalesPerCategory = 'SalesPerCategory', } export type DataItem = { value: string; label?: string; }; export const ChartPalettes: DataItem[] = [ { value: 'Office' }, { value: 'Light' }, { value: 'Dark' }, { value: 'Blue' }, { value: 'Orange' }, ]; export const ChartDataTypes: DataItem[] = [ { value: ChartDataType.SalesOverTime, label: 'Sales over time' }, { value: ChartDataType.SalesPerChannel, label: 'Sales per channel' }, { value: ChartDataType.SalesPerCategory, label: 'Sales per category' }, ]; export const TimePlotTypes: DataItem[] = [{ value: 'Line' }, { value: 'Area' }]; export const CategoryPlotTypes: DataItem[] = [ { value: 'Bar' }, { value: 'Column' }, ]; export const ChannelPlotTypes: DataItem[] = [ { value: 'Pie' }, { value: 'Donut' }, ]; export const getPlotTypes = (dataType: ChartDataType): DataItem[] => { switch (dataType) { case ChartDataType.SalesOverTime: return TimePlotTypes; case ChartDataType.SalesPerChannel: return ChannelPlotTypes; case ChartDataType.SalesPerCategory: return CategoryPlotTypes; default: return []; } };
import { Injectable } from "@angular/core"; import { Rdl as ARJS } from "@mescius/activereportsjs/core"; import { ChartDataType } from "./data"; /** * Utility function that converts given number to a Length unit using inches as a unit of measure * @see https://developer.mescius.com/activereportsjs/docs/ReportAuthorGuide/Report-Items/Common-Properties#length */ function inches(val: number): string { return `${val}in`; } /** * Utility function that converts given number to a Length unit using points as a unit of measure * @see https://developer.mescius.com/activereportsjs/docs/ReportAuthorGuide/Report-Items/Common-Properties#length */ function points(val: number): string { return `${val}pt`; } /** * Utility function that converts given field name to an expression in Rdl format */ function fieldVal(fieldName: string): string { return `=Fields!${fieldName}.Value`; } function getSalesChannel(channelKey: number): string { switch (channelKey) { case 1: return "Store"; case 2: return "Online"; case 3: return "Catalog"; case 4: return "Reseller"; default: return "Unknown"; } } function getProductCategory(productKey: number): string { if (productKey < 116) return "Audio"; if (productKey < 338) return "TV and Video"; if (productKey < 944) return "Computers"; if (productKey < 1316) return "Cameras"; return "Cell phones"; } function getChartTitle(dataType: ChartDataType): string { switch (dataType) { case ChartDataType.SalesOverTime: return "Sales over time"; case ChartDataType.SalesPerChannel: return "Sales per channel"; case ChartDataType.SalesPerCategory: return "Sales per category"; default: return "Report"; } } @Injectable({ providedIn: "root", }) export class ReportService { constructor() {} /** * Creates a data source with data embedded into it */ private async getDataSource(): Promise<ARJS.DataSource> { const url = "https://demodata.mescius.io/contoso/odata/v1/FactSales?$filter=ProductKey+gt+337&$select=ProductKey,SalesAmount,ChannelKey,DateKey"; // use Web Api to retrieve the data from the demodata API const data = await fetch(url).then((res) => res.json()); // patch the data to resolve sales channel and product category data.value = data.value //.filter((dataItem) => dataItem.ChannelKey < 3) .map((dataItem: any) => { // Group date values by month and year const salesDate = new Date(dataItem.DateKey); salesDate.setDate(1); return { SalesAmount: dataItem.SalesAmount, SalesDate: salesDate, SalesChannel: getSalesChannel(dataItem.ChannelKey), ProductCategory: getProductCategory(dataItem.ProductKey), }; }); // construct the data source instance and embed the retrieved data // see https://developer.mescius.com/activereportsjs/docs/ReportAuthorGuide/Databinding#embedded-json-data-source const dataSource: ARJS.DataSource = { Name: "DataSource", ConnectionProperties: { DataProvider: "JSONEMBED", ConnectString: `jsondata=${JSON.stringify(data.value)}`, }, }; return dataSource; } /** * Creates a dataset that reads the data of the data source * see https://developer.mescius.com/activereportsjs/docs/ReportAuthorGuide/Databinding#data-set-configuration */ private getDataSet(): ARJS.DataSet { const dataSet: ARJS.DataSet = { Name: "SalesDataSet", Query: { CommandText: "$.*", DataSourceName: "DataSource", }, Fields: [ "SalesAmount", "SalesDate", "SalesChannel", "ProductCategory", ].map((f) => ({ Name: f, DataField: f, })), }; return dataSet; } private buildPiePlot(structure: ReportStructure): ARJS.DvChartPlot { const plot: ARJS.DvChartPlot = { PlotName: "Plot", PlotChartType: "Pie", Config: { InnerRadius: structure.plotType === "Donut" ? 0.2 : 0, Radial: true, AxisMode: "Radial", Text: { Template: "{PercentageCategory:p0}", TextPosition: "Center", Style: { FontSize: points(14), }, }, }, }; plot.Encodings = plot.Encodings || {}; // Values encoding for Pie plot defines values for pie parts plot.Encodings.Values = [ { Field: { Value: [fieldVal("SalesAmount")], }, Aggregate: "Sum", }, ]; plot.Encodings.Details = [ { Field: { Value: [fieldVal("SalesChannel")], }, Group: "Stack", }, ]; plot.Encodings.Color = { Field: { Value: [fieldVal("SalesChannel")] }, }; return plot; } private buildPiePlotArea(structure: ReportStructure): ARJS.DvChartPlotArea { const plotArea: ARJS.DvChartPlotArea = { Legends: [ { LegendType: "Color", Orientation: "Vertical", Position: "Right", }, ], Axes: [ { AxisType: "X", Position: "None", Plots: ["Plot"], }, { AxisType: "Y", Format: "p0", Position: "None", Scale: "Percentage", Plots: ["Plot"], }, ], }; return plotArea; } private buildColumnBarPlot(structure: ReportStructure): ARJS.DvChartPlot { const plot: ARJS.DvChartPlot = { PlotName: "Plot", PlotChartType: structure.plotType as any, Config: { SwapAxes: structure.plotType === "Bar", }, }; plot.Encodings = plot.Encodings || {}; // Category encoding for Bar & Column plots defines values for X axis plot.Encodings.Category = { Field: { Value: [fieldVal("ProductCategory")], }, }; // Values encoding for Line & Area plot defines values for Y axis plot.Encodings.Values = [ { Field: { Value: [fieldVal("SalesAmount")], }, Aggregate: "Sum", }, ]; if (structure.grouping) { plot.Encodings.Details = [ { Field: { Value: [fieldVal("SalesChannel")] }, Group: "Cluster", }, ]; plot.Encodings.Color = { Field: { Value: [fieldVal("SalesChannel")] }, }; } return plot; } private buildColumnBarPlotArea( structure: ReportStructure ): ARJS.DvChartPlotArea { const plotArea: ARJS.DvChartPlotArea = { Axes: [ { AxisType: "X", Plots: ["Plot"], // LabelAngle: -45, // Format: 'MM-YYYY', LabelStyle: { Color: "#1a1a1a", }, LineStyle: { Border: { Color: "#ccc", Width: points(2), Style: "Solid", }, }, }, { AxisType: "Y", Plots: ["Plot"], Format: "c0", LabelStyle: { Color: "#1a1a1a", }, MajorGrid: true, MajorGridStyle: { Border: { Color: "#ccc", Style: "Dotted", Width: points(0.25), }, }, }, ], }; if (structure.grouping) { plotArea.Legends = [ { LegendType: "Color", Orientation: "Vertical", Position: "Right", }, ]; } return plotArea; } private buildTimePlot(structure: ReportStructure): ARJS.DvChartPlot { const plot: ARJS.DvChartPlot = { PlotName: "Plot", PlotType: structure.plotType as any, }; plot.Encodings = plot.Encodings || {}; // Category encoding for Line & Area plot defines values for X axis plot.Encodings.Category = { Field: { Value: [fieldVal("SalesDate")], }, Sort: "Ascending", SortingField: fieldVal("SalesDate"), }; // Values encoding for Line & Area plot defines values for Y axis plot.Encodings.Values = [ { Field: { Value: [fieldVal("SalesAmount")], }, Aggregate: "Sum", }, ]; plot.Config = { LineAspect: "Spline", LineStyle: { Style: "Solid", Width: points(2), }, ShowNulls: "Connected", }; return plot; } private buildTimePlotArea(structure: ReportStructure): ARJS.DvChartPlotArea { const plotArea: ARJS.DvChartPlotArea = { Axes: [ { AxisType: "X", Plots: ["Plot"], LabelAngle: -45, Format: "MM-YYYY", LabelStyle: { Color: "#1a1a1a", }, LineStyle: { Border: { Color: "#ccc", Width: points(2), Style: "Solid", }, }, }, { AxisType: "Y", Plots: ["Plot"], Format: "c0", LabelStyle: { Color: "#1a1a1a", }, MajorGrid: true, MajorGridStyle: { Border: { Color: "#ccc", Style: "Dotted", Width: points(0.25), }, }, }, ], }; return plotArea; } private buildPlotArea(structure: ReportStructure): ARJS.DvChartPlotArea { switch (structure.dataType) { case ChartDataType.SalesOverTime: return this.buildTimePlotArea(structure); case ChartDataType.SalesPerCategory: return this.buildColumnBarPlotArea(structure); case ChartDataType.SalesPerChannel: return this.buildPiePlotArea(structure); } } private buildPlot(structure: ReportStructure): ARJS.DvChartPlot { switch (structure.dataType) { case ChartDataType.SalesOverTime: return this.buildTimePlot(structure); case ChartDataType.SalesPerCategory: return this.buildColumnBarPlot(structure); case ChartDataType.SalesPerChannel: return this.buildPiePlot(structure); } } public async generateReport( structure: ReportStructure ): Promise<ARJS.Report> { const chart: ARJS.DvChart = { Type: "dvchart", Name: "salesChart", Top: inches(0), Left: inches(0), Width: inches(7.5), Height: inches(6), DataSetName: "SalesDataSet", Palette: structure.palette as any, PlotArea: this.buildPlotArea(structure), Plots: [this.buildPlot(structure)], Header: { Title: getChartTitle(structure.dataType), TextStyle: { Color: "#3da7a8", FontSize: points(24), }, HAlign: "Center", VAlign: "Middle", Style: { PaddingTop: points(12), PaddingBottom: points(6), }, }, Bar: { Width: structure.dataType === ChartDataType.SalesPerCategory ? 0.5 : 1, }, }; // finally create a report with the chart in the body const report: ARJS.Report = { DataSources: [await this.getDataSource()], DataSets: [this.getDataSet()], Page: { TopMargin: inches(0.5), BottomMargin: inches(0.5), LeftMargin: inches(0.5), RightMargin: inches(0.5), PageWidth: inches(8.5), PageHeight: inches(11), }, Body: { ReportItems: [chart], Height: inches(6), }, Width: inches(7.5), }; //console.log(JSON.stringify(report)); return report; } } export interface ReportStructure { dataType: ChartDataType; plotType: string; palette: string; grouping: boolean; }
<div class="container" *ngIf="Mode == 'Design'"> <form [formGroup]="chartForm" (ngSubmit)="onSubmit()"> <div class="mb-3 mt-3 row"> <div class="col-md-6"> <label for="select-data-type" class="form-label" >Choose Data To Plot</label > <select formControlName="dataType" class="form-select" id="select-data-type" > <option *ngFor="let dt of DataTypes" [ngValue]="dt.value"> {{ dt.label || dt.value }} </option> </select> </div> <div class="col-md-6"> <label for="select-palette" class="form-label" >Select Chart Palette</label > <select formControlName="palette" class="form-select" id="select-palette" > <option *ngFor="let dt of ChartPalettes" [ngValue]="dt.value"> {{ dt.label || dt.value }} </option> </select> </div> </div> <div class="row mb-3 align-items-center"> <div class="col-md-6"> <label for="select-plot-type" class="form-label" >Choose Plot Type</label > <select formControlName="plotType" class="form-select" id="select-plot-type" > <option *ngFor="let pt of PlotTypes" [ngValue]="pt.value"> {{ pt.label || pt.value }} </option> </select> </div> <div class="col-md-6"> <div class="form-check"> <input type="checkbox" formControlName="grouping" id="isGroupingEnabled" class="form-check-input" /> <label for="isGroupingEnabled" class="form-check-label" >Group By Sales Channel</label > </div> </div> </div> <div class="row"> <div class="col-md-12"> <button type="submit" [disabled]="!chartForm.valid" class="btn btn-success btn-block" > Create Report </button> </div> </div> </form> </div> <div id="viewer-host" *ngIf="Mode == 'Preview'"> <gc-activereports-viewer (init)="onViewerInit()"> </gc-activereports-viewer> </div>
import { Component, ViewChild } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { ViewerComponent } from "@mescius/activereportsjs-angular"; import { ChartDataTypes, DataItem, getPlotTypes, ChartPalettes, ChartDataType, } from "./data"; import { ReportService } from "./report.service"; @Component({ selector: "app-root", templateUrl: "src/app.component.html", styleUrls: ["src/app.component.css"], }) export class AppComponent { private reportService = new ReportService(); private fb; constructor() { this.fb = new FormBuilder().nonNullable; this.chartForm = this.fb.group({ dataType: ["", Validators.required], plotType: [{ value: "", disabled: true }, Validators.required], grouping: [{ value: false, disabled: true }], palette: ["Office", Validators.required], }); } @ViewChild(ViewerComponent, { static: false }) reportViewer!: ViewerComponent; Mode: "Design" | "Preview" = "Design"; DataTypes: DataItem[] = ChartDataTypes; PlotTypes: DataItem[] = []; ChartPalettes = ChartPalettes; chartForm: any; ngAfterViewInit() { //console.log(this.chartForm.value); this.chartForm.get("dataType")?.valueChanges.subscribe((val: string) => { const chartDataType = val as ChartDataType; this.PlotTypes = getPlotTypes(chartDataType); this.chartForm.get("plotType")?.setValue(""); this.chartForm.get("plotType")?.enable(); this.chartForm.get("grouping")?.setValue(false); if (chartDataType === ChartDataType.SalesPerCategory) { this.chartForm.get("grouping")?.enable(); } else { this.chartForm.get("grouping")?.disable(); } }); } updateToolbar() { var designButton = { key: "$openDesigner", text: "Designer", iconCssClass: "mdi mdi-reply", enabled: true, action: () => { this.Mode = "Design"; }, }; this.reportViewer.toolbar.addItem(designButton); this.reportViewer.toolbar.updateLayout({ default: [ "$openDesigner", "$split", "$navigation", "$split", "$refresh", "$split", "$history", "$split", "$zoom", "$fullscreen", "$split", "$print", "$split", "$singlepagemode", "$continuousmode", "$galleymode", ], }); } onSubmit() { this.Mode = "Preview"; //console.log(this.chartForm.value); } onViewerInit() { this.updateToolbar(); const report = this.reportService.generateReport(this.chartForm.value); this.reportViewer.open("report", { ResourceLocator: { getResource(resource: string) { return report as any; }, }, }); } }
#viewer-host { width: 100%; height: 600px; }
(function (global) { System.config({ transpiler: "./plugin-typescript.js", typescriptOptions: { "target": "ES2022", "module": "system", "emitDecoratorMetadata": true, "experimentalDecorators": true, }, baseURL: "./node_modules/", meta: { typescript: { exports: "ts", }, "*.css": { loader: "systemjs-plugin-css" }, }, packageConfigPaths: [ "./node_modules/*/package.json", "./node_modules/@angular/*/package.json", "./node_modules/@mescius/*/package.json", ], map: { 'typescript': "typescript/lib/typescript.js", 'rxjs': "rxjs/dist/bundles/rxjs.umd.js", "rxjs/operators": "rxjs/dist/bundles/rxjs.umd.js", "@angular/core/primitives/signals": "@angular/core/fesm2022/primitives/signals.mjs", "@angular/common/http": "@angular/common/fesm2022/http.mjs", "@mescius/activereportsjs/core": "@mescius/activereportsjs/dist/ar-js-core.js", "@mescius/activereportsjs": "./activereports.js", "@mescius/activereportsjs/reportdesigner":"./activereports.js", "@mescius/ar-js-pagereport": "@mescius/activereportsjs/dist/ar-js-core.js", "@mescius/activereportsjs-i18n-ja": "@mescius/activereportsjs-i18n/dist/designer/ja-locale.js", "@mescius/activereportsjs-i18n-zh": "@mescius/activereportsjs-i18n/dist/designer/zh-locale.js", }, // packages tells the System loader how to load when no filename and/or no extension packages: { "./src": { defaultExtension: "ts", }, rxjs: { defaultExtension: "js", // "main": "bundles/rxjs.umd.min.js" }, node_modules: { defaultExtension: "js", }, "@angular/core": { defaultExtension: "mjs", main: "fesm2022/core.mjs", }, "@angular/platform-browser": { defaultExtension: "mjs", main: "fesm2022/platform-browser.mjs", }, "@angular/common": { defaultExtension: "mjs", main: "fesm2022/common.mjs", }, "@angular/compiler": { defaultExtension: "mjs", main: "fesm2022/compiler.mjs", }, "@angular/forms": { defaultExtension: "mjs", main: "fesm2022/forms.mjs", }, "@angular/localize": { defaultExtension: "mjs", main: "fesm2022/localize.mjs", }, "@angular/platform-browser-dynamic": { defaultExtension: "mjs", main: "fesm2022/platform-browser-dynamic.mjs", }, "@mescius/activereportsjs-angular": { defaultExtension: "mjs", main: "fesm2022/grapecity-activereports-angular.mjs", }, }, }); })(this);
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <title>MESCIUS ActiveReports for Javascript sample</title> <!-- angular sample --> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <!-- Polyfills --> <script src="node_modules/zone.js/fesm2015/zone.min.js"></script> <!-- SystemJS --> <script src="node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <link href="https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400&display=swap" rel="stylesheet" /> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous" /> <link href="//cdn.materialdesignicons.com/5.4.55/css/materialdesignicons.min.css" rel="stylesheet" /> <link rel="stylesheet" type="text/css" href="/activereportsjs/demos/arjs/styles/ar-js-ui.css" /> <link rel="stylesheet" type="text/css" href="/activereportsjs/demos/arjs/styles/ar-js-viewer.css" /> <script> System.import("./src/app.main"); </script> </head> <body> <app-root></app-root> </body> </html>