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>