React Chart Control Samples (#763)

* Removed xml2js references

* Initial version

* Created dynamic data callable

* Cleaned up code and comments

* Added chartinator.
This commit is contained in:
Hugo Bernier 2019-01-22 09:08:28 -05:00 committed by Vesa Juvonen
parent 780348ff39
commit 3f30d811f5
258 changed files with 29055 additions and 84 deletions

View File

@ -0,0 +1,25 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# change these settings to your own preference
indent_style = space
indent_size = 2
# we recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[{package,bower}.json]
indent_style = space
indent_size = 2

32
samples/react-chartcontrol/.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
# Logs
logs
*.log
npm-debug.log*
# Dependency directories
node_modules
# Build generated files
dist
lib
solution
temp
*.sppkg
# Coverage directory used by tools like istanbul
coverage
# OSX
.DS_Store
# Visual Studio files
.ntvs_analysis.dat
.vs
bin
obj
# Resx Generated Code
*.resx.ts
# Styles Generated Code
*.scss.ts

View File

@ -0,0 +1,11 @@
{
"@microsoft/generator-sharepoint": {
"isCreatingSolution": false,
"environment": "spo",
"version": "1.7.1",
"libraryName": "react-chartcontrol",
"libraryId": "fb9665fb-4455-4b7f-99ab-2195920871fd",
"packageManager": "npm",
"componentType": "webpart"
}
}

View File

@ -0,0 +1,224 @@
## React Chart Control Samples
## Summary
This sample contains several web parts that demonstrate how to use the ChartControl from @pnp/spfx-controls-react.
![The list of web parts](./assets/WebPartList.png)
The web parts in this sample are not intended to be used in production -- they simply demonstrate how you would use all the capabilities of the ChartControl.
> **NOTE:** If you are looking for a ready-to-use web part, please look into [joelfmrodrigues' cool react-modern-charts sample](https://github.com/SharePoint/sp-dev-fx-webparts/tree/master/samples/react-modern-charts), which was built without the ChartControl.
## About the samples
We created the ChartControl by popular request of [@pnp/spfx-controls-react](https://sharepoint.github.io/sp-dev-fx-controls-react/) users who wanted a way to easily insert [Chart.js](https://www.chartjs.org/) charts into their solutions.
To keep things as easy as possible, the ChartControl supports (most) of the Chart.js functionality (yes, even the use of plugins!).
The samples in this solution mostly use code found from the Chart.js code samples -- with little or no conversion. Whereever possible, we localized strings, moved colors and styles to the web part's SCSS, and used Office Fabric UI components instead of regular buttons.
Each sample retrieves data and passes it to the ChartControl using one of the 3 methods:
* Static data passed in the control's properties
* Dynamic data passed through the web part's state
* Dynamic data passed directly to the ChartControl, without state.
Feel free to use any method you wish for your own web parts.
## Web Part List
### Accessible Chart
![Accessible Chart](./assets/AccessibleChart.png)
Demonstrates the accessibility features of the Chart Control.
This sample shows the table that is automatically generated by the Chart Control.
### Area Chart
![Area Chart](./assets/AreaChart.gif)
This sample shows how you can render an area chart and configure the chart to use the `fill` option, as well as whether to use straight lines or curves.
It also shows how to render gradient fills.
### Bar Chart -- Static data
![Bar Chart with Static Data](./assets/BarChartStatic.png)
Uses the most basic features of the ChartControl; It uses static data and does not provide any optional parameters.
The ChartControl automatic generates chart colors that mimic the colors you would find in Office.
The control also automatically picks up the environment's theme and changes the chart's background color, lines, and fonts to match the theme.
For accessibility purposes, the ChartControl also renders a hidden table that contains a summary of the data which will be available for those who use a screen reader.
### Bar Chart
![Bar Chart](./assets/BarChart.png)
Another bar chart, but this one loads data asynchronously from a (fake) service.
While the sample service simply returns an array of numbers, you could replace it with your own code that reads from SharePoint, or any other service.
### Bubble Chart
![Bubble Chart](./assets/BubbleChart.gif)
This sample demonstrates the use of data elements with a X, Y, and R value to create a bubble chart.
You can even pop the bubbles, which is a fun way to demonstrate event handlers.
### Chartinator
![Chartinator](./assets/Chartinator.gif)
This is a Chart Control version of the SharePoint Quick Charts. It supports every chart type and offers many configuration options.
This sample also demonstrates how to create conditional property pane groups and custom property pane controls.
### Combo Chart
![Combo Chart](./assets/ComboBar.png)
Demonstrates how to create combination charts with multiple datasets.
### Custom Plugin
![Chart with Custom Plugin](./assets/CustomPlugin.png)
If you can't find a Chart.js plugin that already exists, and you want to create your own, simply implement the `IChartPlugin` interface.
This example takes sample plugin code to create a custom plugin.
The concept for the chart's look and feel came from a [StackOverflow question](https://stackoverflow.com/questions/45446153/chartjs-round-borders-on-a-doughnut-chart-with-multiple-datasets) from fsenna.
The custom plugin code is inspired from a [JSFiddle](http://jsfiddle.net/tgyxmkLj/1/) written by user8296539 in response to fsenna's question.
### Doughnut with Patterns
![Doughnut with Patterns](./assets/DoughnutPatterns.gif)
This web part demonstrates how to use the ChartControl to render a donut chart with patterns instead of colors.
It is inspired by the [patternomaly sample](https://github.com/ashiguruma/patternomaly/blob/master/examples/optional.html) referred by the [Chart.js documentation](https://www.chartjs.org/docs/latest/general/colors.html).
### Dynamic Data Callable
![Dynamic Data Callable](./assets/DynamicDataCallable.gif)
I wanted to use real data, but didn't want to deal with creating data on SharePoint, so I created a web part that calls the GitHub REST APIs and shows contributors for a given repository.
The sample is also a dynamic data provider. It demonstrates how to respond to mouse events (clicking on a segment of the donut chart will send the data to another web part).
Use in combination with the **Dynamic Data Consumer** web part.
### Dynamics Data Consumer
![Dynamic Data Consumer](./assets/DynamicDataConsumer.gif)
This demonstrates how to render a chart by receiving events from a dynamic data connection.
It also demonstrates how to render a line chart that looks like the Office 365 Admin dashboards.
### Horizontal Bar
![Horizontal Bar](./assets/HorizontalBarChart.png)
This sample shows how to render a horizontal bar chart. It also demonstrates how to create stacked bars.
### Line Chart
![Line Chart](./assets/LineChart.png)
This sample demonstrates how to render line charts.
### Pie Chart
![Pie Chart](./assets/PieChart.png)
Demonstrates how to create a pie chart.
### Polar Chart
![Polar Chart](./assets/PolarChart.png)
Creates a polar area chart. Also adds ability to add and remove data.
### Radar Chart
![Radar Chart](./assets/RadarChart.png)
Renders a multi-dataset radar chart.
### Real-Time Chart
![Real-Time](./assets/RealTime.gif)
This sample uses an existing plugin to create a scrolling/real-time chart.
This sample also demonstrates how to use time series for the X axis.
### Scatter Chart
![Scatter Chart](./assets/ScatterChart.png)
This sample shows how to use X and Y coordinate data elements in a scatter chart.
## Used SharePoint Framework Version
![SPFx v1.7.1](https://img.shields.io/badge/SPFx-1.7.1-green.svg)
## Applies to
* [SharePoint Framework](https:/dev.office.com/sharepoint)
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
## Prerequisites
There are no pre-requisites to use these samples.
## Solution
Solution|Author(s)
--------|---------
react-chart-control | Hugo Bernier ([Tahoe Ninjas](http://tahoeninjas.blog), @bernierh)
## Version history
Version|Date|Comments
-------|----|--------
1.0|January, 2019|Initial release
## Disclaimer
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
---
## Minimal Path to Awesome
* Clone this repository
* in the command line run:
* `npm install`
* `gulp serve`
* Insert the one of the webs part on a page
## Features
This Web Part sample pack illustrates the following concepts on top of the SharePoint Framework:
* Using the @pnp/spfx-controls-react ChartControl
* Creating static charts
* Creating dynamic charts using state
* Creating dynamic charts by calling the ChartControl's update method
* Responding to chart events
* Connecting a chart to dynamic data
* Rendering conditional property pane groups
* Rendering conditional property pane fields
* Creating custom property pane controls
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-chart-control" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1008 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

View File

@ -0,0 +1,173 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"bar-chart-demo-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/barChartDemo/BarChartDemoWebPart.js",
"manifest": "./src/webparts/barChartDemo/BarChartDemoWebPart.manifest.json"
}
]
},
"bar-chart-static-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/barChartStatic/BarChartStaticWebPart.js",
"manifest": "./src/webparts/barChartStatic/BarChartStaticWebPart.manifest.json"
}
]
},
"donut-patterns-demo-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/donutPatternsDemo/DonutPatternsDemoWebPart.js",
"manifest": "./src/webparts/donutPatternsDemo/DonutPatternsDemoWebPart.manifest.json"
}
]
},
"line-chart-demo-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/lineChartDemo/LineChartDemoWebPart.js",
"manifest": "./src/webparts/lineChartDemo/LineChartDemoWebPart.manifest.json"
}
]
},
"horizontal-bar-demo-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/horizontalBarDemo/HorizontalBarDemoWebPart.js",
"manifest": "./src/webparts/horizontalBarDemo/HorizontalBarDemoWebPart.manifest.json"
}
]
},
"radar-demo-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/radarDemo/RadarDemoWebPart.js",
"manifest": "./src/webparts/radarDemo/RadarDemoWebPart.manifest.json"
}
]
},
"pie-chart-demo-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/pieChartDemo/PieChartDemoWebPart.js",
"manifest": "./src/webparts/pieChartDemo/PieChartDemoWebPart.manifest.json"
}
]
},
"polar-area-demo-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/polarAreaDemo/PolarAreaDemoWebPart.js",
"manifest": "./src/webparts/polarAreaDemo/PolarAreaDemoWebPart.manifest.json"
}
]
},
"bubble-chart-demo-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/bubbleChartDemo/BubbleChartDemoWebPart.js",
"manifest": "./src/webparts/bubbleChartDemo/BubbleChartDemoWebPart.manifest.json"
}
]
},
"scatter-chart-demo-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/scatterChartDemo/ScatterChartDemoWebPart.js",
"manifest": "./src/webparts/scatterChartDemo/ScatterChartDemoWebPart.manifest.json"
}
]
},
"area-chart-demo-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/areaChartDemo/AreaChartDemoWebPart.js",
"manifest": "./src/webparts/areaChartDemo/AreaChartDemoWebPart.manifest.json"
}
]
},
"combo-chart-demo-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/comboChartDemo/ComboChartDemoWebPart.js",
"manifest": "./src/webparts/comboChartDemo/ComboChartDemoWebPart.manifest.json"
}
]
},
"custom-plugin-demo-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/customPluginDemo/CustomPluginDemoWebPart.js",
"manifest": "./src/webparts/customPluginDemo/CustomPluginDemoWebPart.manifest.json"
}
]
},
"realtime-plugin-demo-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/realtimePluginDemo/RealtimePluginDemoWebPart.js",
"manifest": "./src/webparts/realtimePluginDemo/RealtimePluginDemoWebPart.manifest.json"
}
]
},
"accessible-table-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/accessibleTable/AccessibleTableWebPart.js",
"manifest": "./src/webparts/accessibleTable/AccessibleTableWebPart.manifest.json"
}
]
},
"dynamic-data-callable-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/dynamicDataCallable/DynamicDataCallableWebPart.js",
"manifest": "./src/webparts/dynamicDataCallable/DynamicDataCallableWebPart.manifest.json"
}
]
},
"dynamic-data-consumer-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/dynamicDataConsumer/DynamicDataConsumerWebPart.js",
"manifest": "./src/webparts/dynamicDataConsumer/DynamicDataConsumerWebPart.manifest.json"
}
]
},
"chartinator-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/chartinator/ChartinatorWebPart.js",
"manifest": "./src/webparts/chartinator/ChartinatorWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js",
"PropertyControlStrings": "node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js",
"BarChartDemoWebPartStrings": "lib/webparts/barChartDemo/loc/{locale}.js",
"BarChartStaticWebPartStrings": "lib/webparts/barChartStatic/loc/{locale}.js",
"DonutPatternsDemoWebPartStrings": "lib/webparts/donutPatternsDemo/loc/{locale}.js",
"LineChartDemoWebPartStrings": "lib/webparts/lineChartDemo/loc/{locale}.js",
"HorizontalBarDemoWebPartStrings": "lib/webparts/horizontalBarDemo/loc/{locale}.js",
"RadarDemoWebPartStrings": "lib/webparts/radarDemo/loc/{locale}.js",
"PieChartDemoWebPartStrings": "lib/webparts/pieChartDemo/loc/{locale}.js",
"PolarAreaDemoWebPartStrings": "lib/webparts/polarAreaDemo/loc/{locale}.js",
"BubbleChartDemoWebPartStrings": "lib/webparts/bubbleChartDemo/loc/{locale}.js",
"ScatterChartDemoWebPartStrings": "lib/webparts/scatterChartDemo/loc/{locale}.js",
"AreaChartDemoWebPartStrings": "lib/webparts/areaChartDemo/loc/{locale}.js",
"ComboChartDemoWebPartStrings": "lib/webparts/comboChartDemo/loc/{locale}.js",
"CustomPluginDemoWebPartStrings": "lib/webparts/customPluginDemo/loc/{locale}.js",
"RealtimePluginDemoWebPartStrings": "lib/webparts/realtimePluginDemo/loc/{locale}.js",
"AccessibleTableWebPartStrings": "lib/webparts/accessibleTable/loc/{locale}.js",
"DynamicDataCallableWebPartStrings": "lib/webparts/dynamicDataCallable/loc/{locale}.js",
"DynamicDataConsumerWebPartStrings": "lib/webparts/dynamicDataConsumer/loc/{locale}.js",
"ChartinatorWebPartStrings": "lib/webparts/chartinator/loc/{locale}.js"
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
"deployCdnPath": "temp/deploy"
}

View File

@ -0,0 +1,7 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
"workingDir": "./temp/deploy/",
"account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "react-chartcontrol",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,12 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "react-chartcontrol-client-side-solution",
"id": "fb9665fb-4455-4b7f-99ab-2195920871fd",
"version": "1.0.0.0",
"includeClientSideAssets": true
},
"paths": {
"zippedPackage": "solution/react-chartcontrol.sppkg"
}
}

View File

@ -0,0 +1,10 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
"port": 4321,
"https": true,
"initialPage": "https://localhost:5432/workbench",
"api": {
"port": 5432,
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
"cdnBasePath": "<!-- PATH TO CDN -->"
}

View File

@ -0,0 +1,7 @@
'use strict';
const gulp = require('gulp');
const build = require('@microsoft/sp-build-web');
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
build.initialize(gulp);

18370
samples/react-chartcontrol/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
{
"name": "react-chartcontrol",
"version": "0.0.1",
"private": true,
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
},
"dependencies": {
"@microsoft/sp-core-library": "1.7.1",
"@microsoft/sp-lodash-subset": "1.7.1",
"@microsoft/sp-office-ui-fabric-core": "1.7.1",
"@microsoft/sp-webpart-base": "1.7.1",
"@pnp/common": "^1.2.8",
"@pnp/logging": "^1.2.8",
"@pnp/odata": "^1.2.8",
"@pnp/sp": "^1.2.8",
"@pnp/spfx-controls-react": "1.11.0",
"@pnp/spfx-property-controls": "^1.13.1",
"@types/es6-promise": "0.0.33",
"@types/react": "16.4.2",
"@types/react-dom": "16.0.5",
"@types/webpack-env": "1.13.1",
"chartjs-plugin-streaming": "^1.7.1",
"color": "^3.1.0",
"moment": "^2.23.0",
"patternomaly": "^1.3.2",
"react": "16.3.2",
"react-dom": "16.3.2"
},
"resolutions": {
"@types/react": "16.4.2"
},
"devDependencies": {
"@microsoft/sp-build-web": "1.7.1",
"@microsoft/sp-tslint-rules": "1.7.1",
"@microsoft/sp-module-interfaces": "1.7.1",
"@microsoft/sp-webpart-workbench": "1.7.1",
"gulp": "~3.9.1",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"ajv": "~5.2.2"
}
}

View File

@ -0,0 +1 @@
// A file is required to be in the root of the /src directory by the TypeScript compiler

View File

@ -0,0 +1,12 @@
/**
* Provides sample chart data. All returned results are randomized.
* Any resemblance to winning lottery numbers is purely coincidental.
*/
export default interface IChartDataProvider {
getMultiBubbleArrays(numDatasets: number, length: number): Promise<Array<Chart.ChartPoint[]>>;
getMultiDataset(numDatasets: number, length: number): Promise<Array<number[]>>;
getNumberArray(length: number, waitduration?: number): Promise<number[]>;
getPointArray(length: number): Promise<Chart.ChartPoint[]>;
getScatterArray(length: number): Promise<Chart.ChartPoint[]>;
getSignedNumberArray(length: number): Promise<number[]>;
}

View File

@ -0,0 +1,146 @@
import IChartDataProvider from "./IChartDataProvider";
const FAKE_DELAY: number = 500;
/**
* Returns an array of chart points (x,y)
*/
export default class MockChartDataProvider implements IChartDataProvider {
public getSignedNumberArray(length: number): Promise<number[]> {
return new Promise<number[]>((resolve) => {
// pretend we're getting the data from a service
setTimeout(() => {
let numArray: number[] = [];
for (let index = 0; index < length; index++) {
numArray.push(MockChartDataProvider.getRandomSignedNumber());
}
resolve(numArray);
}, FAKE_DELAY);
});
}
/**
* Returns a multi-dataset array of points for a bubble chart
* @param numDatasets How many datesets to generate
* @param length How long should each dataset be?
*/
public getMultiBubbleArrays(numDatasets: number, length: number): Promise<Array<Chart.ChartPoint[]>> {
return new Promise<Array<Chart.ChartPoint[]>>((resolve) => {
// pretend we're getting the data from a service
setTimeout(() => {
let dataSetArray: Array<Chart.ChartPoint[]> = [];
for (let dsIndex = 0; dsIndex < numDatasets; dsIndex++) {
let bubbleArray: Chart.ChartPoint[] = [];
for (let index = 0; index < length; index++) {
bubbleArray.push({
x: MockChartDataProvider.getRandomNumber(),
y: MockChartDataProvider.getRandomNumber(),
r: Math.abs(MockChartDataProvider.getRandomNumber()) / 5
});
}
dataSetArray.push(bubbleArray);
}
resolve(dataSetArray);
}, FAKE_DELAY);
});
}
/**
* Returns a multi-dataset array of numbers for use in a chart
* @param numDatasets The number of datasets you would like
* @param length The length of each dataset
*/
public getMultiDataset(numDatasets: number, length: number): Promise<Array<number[]>> {
return new Promise<Array<number[]>>((resolve) => {
// pretend we're getting the data from a service
setTimeout(() => {
let dataSetArray: Array<number[]> = [];
for (let dsIndex = 0; dsIndex < numDatasets; dsIndex++) {
let numArray: number[] = [];
for (let index = 0; index < length; index++) {
numArray.push(MockChartDataProvider.getRandomNumber());
}
dataSetArray.push(numArray);
}
resolve(dataSetArray);
}, FAKE_DELAY);
});
}
/**
* Gets an array of points to render on a chart requiring X, Y coordinates
* @param length Length of the dataset to generate
*/
public getPointArray(length: number): Promise<Chart.ChartPoint[]> {
return new Promise<Chart.ChartPoint[]>((resolve) => {
// pretend we're getting the data from a service
setTimeout(() => {
let numArray: Chart.ChartPoint[] = [];
for (let index = 0; index < length; index++) {
numArray.push(
{
x: MockChartDataProvider.getRandomNumber(),
y: MockChartDataProvider.getRandomNumber(),
}
);
}
resolve(numArray);
}, FAKE_DELAY);
});
}
/**
* Gets an array of points for a scatter chart
* @param length Length of the dataset to generate
*/
public getScatterArray(length: number): Promise<Chart.ChartPoint[]> {
return new Promise<Chart.ChartPoint[]>((resolve) => {
// pretend we're getting the data from a service
setTimeout(() => {
let numArray: Chart.ChartPoint[] = [];
for (let index = 0; index < length; index++) {
numArray.push(
{
x: MockChartDataProvider.getRandomNumber(),
y: MockChartDataProvider.getRandomSignedNumber(),
}
);
}
resolve(numArray);
}, FAKE_DELAY);
});
}
/**
* Returns an array of numbers
*/
public getNumberArray(length: number, waitduration?: number): Promise<number[]> {
return new Promise<number[]>((resolve) => {
// pretend we're getting the data from a service
setTimeout(() => {
let numArray: number[] = [];
for (let index = 0; index < length; index++) {
numArray.push(MockChartDataProvider.getRandomNumber());
}
resolve(numArray);
}, waitduration && FAKE_DELAY);
});
}
/**
* Returns a random number between 1-100.
*/
public static getRandomNumber(): number {
return Math.round(Math.random() * 101);
}
/**
* Returns a random signed number between -100 and 100
* This method is used for charts with signed numbers,
* such as line charts.
*/
public static getRandomSignedNumber(): number {
return Math.round(Math.random() * 201) - 100;
}
}

View File

@ -0,0 +1,2 @@
export * from './IChartDataProvider';
export * from './MockChartDataProvider';

View File

@ -0,0 +1,46 @@
import { IGitHubService, IGitHubContributor, IAuthorCommit } from "./IGitHubService.types";
import { HttpClient, HttpClientResponse } from '@microsoft/sp-http';
export default class GitHubService implements IGitHubService {
/**
* Gets a list of github commits in a repo for a single contributor
* @param client the HttpClient object that will make the call
* @param repoOwner the repo owner (i.e.: the first slash after github.com. E.g.: https://github.com/SharePoint/sp-dev-docs = sharepoint)
* @param repo the repo name (i.e.: the second slash after github.com. E.g.: https://github.com/SharePoint/sp-dev-docs- = sp-dev-docs)
* @param alias the GitHub contributor alias. e.g.: hugoabernier
*
* NOTE: GitHub only allows a certain number of API calls without authentication. If you plan on using this
* more extensively, you may want to add authentication, by adding an Http headers:
* "cache-control": "no-cache") -- no necessary, but useful
* "Authorization" "token f11111f1100bb1ec1111ad11c1111ed1d1a1111a");
*/
public getCommits(client: HttpClient, repoOwner: string, repo: string, alias: string): Promise<IAuthorCommit[]> {
const requestUrl: string = `https://api.github.com/repos/${repoOwner}/${repo}/commits?author=${alias}`;
// call the GitHub API
return client.fetch(requestUrl,
HttpClient.configurations.v1, {}).then((response: HttpClientResponse) => response.json())
.then((commits: IAuthorCommit[]): IAuthorCommit[] => {
return commits;
});
}
/**
* Gets a list of contributors in a repo
* @param client the HttpClient
* @param repoOwner the repo owner (i.e.: the first slash after github.com. E.g.: https://github.com/SharePoint/sp-dev-docs = sharepoint)
* @param repo the repo name (i.e.: the second slash after github.com. E.g.: https://github.com/SharePoint/sp-dev-docs- = sp-dev-docs)
*
* See above notes regarding daily limits on GitHub API
*/
public getContributors(client: HttpClient, repoOwner: string, repo: string): Promise<IGitHubContributor[]> {
// note that for simplicity, we don't escape strings or verify that they are valid.
const requestUrl: string = `https://api.github.com/repos/${repoOwner}/${repo}/contributors`;
// call the GitHub API
return client.fetch(requestUrl,
HttpClient.configurations.v1, {}).then((response: HttpClientResponse) => response.json())
.then((contributors: IGitHubContributor[]): IGitHubContributor[] => {
return contributors;
});
}
}

View File

@ -0,0 +1,138 @@
import { HttpClient } from '@microsoft/sp-http';
/**
* Exposes GitHub API calls
*/
export interface IGitHubService {
getCommits(client: HttpClient, repoOwner: string, repo: string, alias: string): Promise<IAuthorCommit[]>;
getContributors(client: HttpClient, repoOwner: string, repo: string): Promise<IGitHubContributor[]>;
}
// The majority of the interfaces in this file were auto-generated from JSON.
// I don't use everything, but I left everything here in case you'd like to
// use this code for your own purpose.
export interface IGitHubContributor {
avatar_url: string;
contributions: number;
events_url: string;
followers_url: string;
following_url: string;
gists_url: string;
gravatar_id: string;
html_url: string;
id: number;
login: string;
node_id: string;
organizations_url: string;
received_events_url: string;
repos_url: string;
site_admin: boolean;
starred_url: string;
subscriptions_url: string;
type: string;
url: string;
}
export interface IContributor {
alias: string;
repoOwner: string;
repo: string;
}
export interface Author {
date: Date;
email: string;
name: string;
}
export interface Committer {
date: Date;
email: string;
name: string;
}
export interface Tree {
sha: string;
url: string;
}
export interface Verification {
payload?: any;
reason: string;
signature?: any;
verified: boolean;
}
export interface Commit {
author: Author;
comment_count: number;
committer: Committer;
message: string;
tree: Tree;
url: string;
verification: Verification;
}
export interface Author2 {
avatar_url: string;
events_url: string;
followers_url: string;
following_url: string;
gists_url: string;
gravatar_id: string;
html_url: string;
id: number;
login: string;
node_id: string;
organizations_url: string;
received_events_url: string;
repos_url: string;
site_admin: boolean;
starred_url: string;
subscriptions_url: string;
type: string;
url: string;
}
export interface Committer2 {
avatar_url: string;
events_url: string;
followers_url: string;
following_url: string;
gists_url: string;
gravatar_id: string;
html_url: string;
id: number;
login: string;
node_id: string;
organizations_url: string;
received_events_url: string;
repos_url: string;
site_admin: boolean;
starred_url: string;
subscriptions_url: string;
type: string;
url: string;
}
export interface Parent {
html_url: string;
sha: string;
url: string;
}
/**
* Describes a commit
*/
export interface IAuthorCommit {
author: Author2;
comments_url: string;
commit: Commit;
committer: Committer2;
html_url: string;
node_id: string;
parents: Parent[];
sha: string;
url: string;
}

View File

@ -0,0 +1,2 @@
export * from './GitHubService';
export * from './IGitHubService.types';

View File

@ -0,0 +1,6 @@
export interface IListField {
Id: string;
Title: string;
InternalName: string;
TypeAsString: string;
}

View File

@ -0,0 +1,7 @@
export interface IListItem {
Id: string;
Label: string;
Value: number;
YValue?: number;
RValue?: number;
}

View File

@ -0,0 +1,7 @@
import { IListField } from "./IListField";
import { IListItem } from "./IListItem";
export interface IListService {
getFields(listId: string): Promise<Array<IListField>>;
getListItems(listId: string, labelField: string, valueField: string, yValueField?: string, rValueField?: string): Promise<Array<IListItem>>;
}

View File

@ -0,0 +1,57 @@
import { IListService } from "./IListService";
import { WebPartContext } from "@microsoft/sp-webpart-base";
import { sp } from "@pnp/sp";
import { IListField } from "./IListField";
import { IListItem } from "./IListItem";
export class ListService implements IListService {
private _context: WebPartContext;
/**
*
*/
constructor(context: WebPartContext) {
this._context = context;
}
public getFields = (listId: string): Promise<Array<IListField>> => {
sp.setup({
spfxContext: this._context
});
return sp.web.lists.getById(listId).fields.filter('ReadOnlyField eq false and Hidden eq false')
.select("Id", "Title", "InternalName", "TypeAsString").get();
}
public getListItems(listId: string, labelField: string, valueField: string, yValueField?: string, rValueField?: string): Promise<Array<IListItem>> {
sp.setup({
spfxContext: this._context
});
// build the list of fields we need
let fields: string[] = ["Id", labelField, valueField];
// Add the y value if necessary
if (yValueField) {
fields.push(yValueField);
}
// Add a R value if necessary
if (rValueField) {
fields.push(rValueField);
}
return sp.web.lists.getById(listId).items.select(...fields).getAll().then((rows: any[]) => {
return rows.map((item: any) => {
let listItem: IListItem = {
Id: item.Id,
Label: item[labelField],
Value: valueField && item[valueField],
YValue: yValueField && item[yValueField],
RValue: rValueField && item[rValueField]
};
return listItem;
});
});
}
}

View File

@ -0,0 +1,96 @@
import { IListService } from "./IListService";
import { IListField } from "./IListField";
import { IListItem } from "./IListItem";
import { ChartType } from '@pnp/spfx-controls-react/lib/ChartControl';
import MockChartDataProvider from "../ChartDataProvider/MockChartDataProvider";
import IChartDataProvider from "../ChartDataProvider/IChartDataProvider";
const DATA_COUNT: number = 7;
export class MockListService implements IListService {
private _chartType: ChartType;
private _labels: string[];
constructor(chartType: ChartType, labels: string[]) {
this._chartType = chartType;
this._labels = labels;
}
public getFields(_listId: string): Promise<IListField[]> {
throw new Error("Method not implemented for Mock service.");
}
public getListItems(_listId: string, _labelField: string, _valueField: string, _yValueField?: string, _rValueField?: string): Promise<Array<IListItem>> {
if (this._chartType === ChartType.Bubble) {
return this._getSampleBubbleData();
}
if (this._chartType === ChartType.Scatter) {
return this._getSampleScatterData();
}
return this._getSampleArrayData();
}
private _getSampleArrayData = (): Promise<Array<IListItem>> => {
return new Promise<Array<IListItem>>((resolve, _reject) => {
// we're using a mock service that returns random numbers.
const dataProvider: IChartDataProvider = new MockChartDataProvider();
dataProvider.getNumberArray(DATA_COUNT, 500).then((dataSet: number[]) => {
const listRows: Array<IListItem> = dataSet.map((value: number, index: number) => {
const listRow: IListItem = {
Id: `row_${index}`,
Label: this._labels[index],
Value: value
};
return listRow;
});
resolve(listRows);
});
});
}
private _getSampleBubbleData(): Promise<Array<IListItem>> {
return new Promise<Array<IListItem>>((resolve, _reject) => {
// we're using a mock service that returns random numbers.
const dataProvider: IChartDataProvider = new MockChartDataProvider();
dataProvider.getMultiBubbleArrays(1, DATA_COUNT).then((dataSet: Array<Chart.ChartPoint[]>) => {
const listRows: Array<IListItem> = dataSet[0].map((value: Chart.ChartPoint, index: number) => {
const listRow: IListItem = {
Id: `row_${index}`,
Label: this._labels[index],
Value: value.x as number,
YValue: value.y as number,
RValue: value.r
};
return listRow;
});
resolve(listRows);
});
});
}
private _getSampleScatterData(): Promise<Array<IListItem>> {
return new Promise<Array<IListItem>>((resolve, _reject) => {
// we're using a mock service that returns random numbers.
const dataProvider: IChartDataProvider = new MockChartDataProvider();
dataProvider.getScatterArray(DATA_COUNT).then((dataSet: Chart.ChartPoint[]) => {
const listRows: Array<IListItem> = dataSet.map((value: Chart.ChartPoint, index: number) => {
const listRow: IListItem = {
Id: `row_${index}`,
Label: this._labels[index],
Value: value.x as number,
YValue: value.y as number
};
return listRow;
});
resolve(listRows);
});
});
}
}

View File

@ -0,0 +1,5 @@
export * from './IListField';
export * from './IListItem';
export * from './IListService';
export * from './ListService';
export * from './MockListService';

View File

@ -0,0 +1,24 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "afb76932-d38c-482c-ade4-649d5b65ad99",
"alias": "AccessibleTableWebPart",
"componentType": "WebPart",
"version": "*",
"manifestVersion": 2,
"requiresCustomScript": false,
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70",
"group": { "default": "Other" },
"title": { "default": "Accessible Table" },
"description": { "default": "Shows how to use the accessibility features of the PnP ChartControl to render an accessible table" },
"iconImageUrl": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAcCAYAAAATFf3WAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAD6AAAA+gBtXtSawAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAIdSURBVFiF5dc5aBZBGMbxn0aMt+SQxCMEBDWNB0lh8MDCxsZGo4JgGkubgDairSAIFqKVqRURCxERFUQURVErUVARtPKoYhpRQ2Ixm3ybZffLfsuuKXzghZnZ2Yf/zuy8M8N/oNN4GYuVZZrPK8GjG32x+vwSPKc0t0yzKlQUcAF2YnmJLKlqFLALF/EVj9BZOlFCjfyDQziDRRWxpCov4FacRXOFLKnKO8XPsRvfMY5h9ONdRVxTmmkE9+EPbuGJMJIdAvCsayG+YAwn6vS7jIlYdJcJUW+KB4VV2oRzwuj9c9UDPBwrP1X+tG6WIyNkATZjW6x+JaPfBrQn2jZiXaJtMVoScRD3o3LDahfSymR0ZfQbElZ1/B/8JowOrMWHxPNkvMGaIpB5dUxYSBP4iJ6ovTeCrQc3Ge+F7bMy7RVSUVtU34WRnHC/MJBlPCejvRNvY/UBPMgJu0RISzONyCbhwLEf9xoFJBwIOqLyeRzPCZjUDmyPymOR14Tw0Z/xoqDvtAQ8gtaCPndiPneLwqSpL2b8GqsL+qzA48jnQDloNd3AbSwr8G6/Wp5txikVnOBbhK2OcNcYxjWsqvPOclwQ8uNNYdFUrjY8VJvyn7iKo1ga9ekRPmDU9DTySskXqTRtwSfpeaw36jOY8uwH9lQNN6lWXMLvDMAjsbZxXFfxFpalbpwUTjijanfiQ3gm7N3rZwMsS00zdymuv/ffiLs8snLxAAAAAElFTkSuQmCC",
"properties": {
"title": "Top Contributors",
"datasetlabel": "Number of commits",
"summary": "This is a text representation of the chart above"
}
}]
}

View File

@ -0,0 +1,92 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-webpart-base';
import * as strings from 'AccessibleTableWebPartStrings';
import AccessibleTable from './components/AccessibleTable';
import { IAccessibleTableProps } from './components/IAccessibleTable.types';
import { CalloutTriggers } from '@pnp/spfx-property-controls/lib/PropertyFieldHeader';
import { PropertyFieldTextWithCallout } from '@pnp/spfx-property-controls/lib/PropertyFieldTextWithCallout';
export interface IAccessibleTableWebPartProps {
summary: string;
caption: string;
title: string;
datasetlabel: string;
}
export default class AccessibleTableWebPart extends BaseClientSideWebPart<IAccessibleTableWebPartProps> {
public render(): void {
const element: React.ReactElement<IAccessibleTableProps> = React.createElement(
AccessibleTable,
{
summary: this.properties.summary,
caption: this.properties.caption,
title: this.properties.title,
datasetlabel: this.properties.datasetlabel
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.ChartSettingsGroupName,
groupFields: [
PropertyPaneTextField('title', {
label: strings.TitleFieldLabel
}),
PropertyPaneTextField('datasetlabel', {
label: strings.DatasetFieldLabel
})
]
},
{
groupName: strings.AccessibilitySettingsGroupName,
groupFields: [
PropertyFieldTextWithCallout('caption', {
calloutTrigger: CalloutTriggers.Hover,
key: 'captionFieldId',
label: strings.CaptionFieldLabel,
calloutContent: React.createElement('span', {}, strings.CaptionFieldDescription),
calloutWidth: 150,
value: this.properties.caption
}),
PropertyFieldTextWithCallout('summary', {
calloutTrigger: CalloutTriggers.Hover,
key: 'summaryFieldId',
label: strings.SummaryFieldLabel,
calloutContent: React.createElement('span', {}, strings.SummaryFieldDescription),
calloutWidth: 150,
value: this.properties.summary
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,42 @@
@import "~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss";
.accessibleTable {
.shomMeTheTable {
position: unset !important;
left: unset !important;
top: unset !important;
width: unset !important;
height: unset !important;
display: block !important;
color: $ms-color-neutralSecondary;
background-color: $ms-color-neutralLighter;
text-align: left;
padding: 20px;
margin-top: 20px;
& > table,
th,
td {
border: 1px solid black;
border-collapse: collapse;
}
& > table {
margin-left: auto;
margin-right: auto;
width: 100%;
& th {
border-bottom: 1px double black;
}
& > caption {
font-size: $ms-font-size-xl;
font-weight: $ms-font-weight-semibold;
& span {
font-size: $ms-font-size-16;
}
}
}
}
}

View File

@ -0,0 +1,59 @@
import * as React from 'react';
import styles from './AccessibleTable.module.scss';
import { IAccessibleTableProps } from './IAccessibleTable.types';
import * as strings from 'AccessibleTableWebPartStrings';
import { escape } from '@microsoft/sp-lodash-subset';
import { MessageBar } from 'office-ui-fabric-react/lib/MessageBar';
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
import { ChartControl, ChartType, OFFICE_COLORFUL1, PaletteGenerator } from '@pnp/spfx-controls-react/lib/ChartControl';
import MockChartDataProvider from '../../../services/ChartDataProvider/MockChartDataProvider';
export default class AccessibleTable extends React.Component<IAccessibleTableProps, {}> {
public render(): React.ReactElement<IAccessibleTableProps> {
return (
<div className={styles.accessibleTable}>
<MessageBar>
{strings.TableWarning}
</MessageBar>
<ChartControl
type={ChartType.Bar}
data={{labels: strings.DataLabels,
datasets: [
{
backgroundColor: PaletteGenerator.alpha(OFFICE_COLORFUL1, 0.2),
borderColor: OFFICE_COLORFUL1,
label: escape(this.props.datasetlabel),
data: [
MockChartDataProvider.getRandomNumber(),
MockChartDataProvider.getRandomNumber(),
MockChartDataProvider.getRandomNumber(),
MockChartDataProvider.getRandomNumber(),
MockChartDataProvider.getRandomNumber(),
MockChartDataProvider.getRandomNumber(),
],
borderWidth: 1
}
]}}
loadingtemplate={() => <Spinner size={SpinnerSize.large} label={strings.Loading} ariaLive="assertive" />}
options={{
title: {
display: true,
text: escape(this.props.title),
}
}}
accessibility={
{
className: styles.shomMeTheTable,
summary: this.props.summary,
caption: this.props.caption !== '' ? this.props.caption : this.props.title
}
}
/>
</div>
);
}
}

View File

@ -0,0 +1,7 @@
export interface IAccessibleTableProps {
summary: string;
caption: string;
title: string;
datasetlabel: string;
}

View File

@ -0,0 +1,25 @@
define([], function() {
return {
"TableWarning": "The table shown below is usually invisible except to users with a screen reader. We have made it visible for this example.",
"ChartTitle": "Top Contributors",
"DatasetLabel": "Number of Commits",
"DataLabels": [
'David',
'Mikael',
'Simon-Pierre',
'Velin',
'Vesa',
'Waldek'
],
"PropertyPaneDescription": "Use the options below to configure the accessiblity settings of the chart control.",
"AccessibilitySettingsGroupName": "Accessibility Settings",
"SummaryFieldLabel": "Summary",
"SummaryFieldDescription": "The summary is used to describe the chart. The summary appears at a 'summary' attribute of the table, which is read by screen readers, but isn't visible otherwsie.",
"CaptionFieldLabel": "Caption",
"CaptionFieldDescription": "If not provided, the accessible table will use the chart's title",
"DatasetFieldLabel": "Dataset label",
"TitleFieldLabel": "Chart title",
"ChartSettingsGroupName": "Chart Settings",
"Loading": "Loading"
}
});

View File

@ -0,0 +1,21 @@
declare interface IAccessibleTableWebPartStrings {
DatasetFieldLabel: string;
TitleFieldLabel: string;
ChartSettingsGroupName: any;
CaptionFieldLabel: string;
TableWarning: string;
ChartTitle: string;
PropertyPaneDescription: string;
AccessibilitySettingsGroupName: string;
SummaryFieldLabel: string;
SummaryFieldDescription: string;
DatasetLabel: string;
DataLabels: string[];
CaptionFieldDescription: string;
Loading: string;
}
declare module 'AccessibleTableWebPartStrings' {
const strings: IAccessibleTableWebPartStrings;
export = strings;
}

View File

@ -0,0 +1,21 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "fcba1920-e685-428a-8637-ab15883a2d0b",
"alias": "AreaChartDemoWebPart",
"componentType": "WebPart",
"version": "*",
"manifestVersion": 2,
"requiresCustomScript": false,
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70",
"group": { "default": "Other" },
"title": { "default": "Area Chart" },
"description": { "default": "Demonstrates how to use an area chart" },
"officeFabricIconFontName": "AreaChart",
"properties": {
"description": "Area Chart"
}
}]
}

View File

@ -0,0 +1,57 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
} from '@microsoft/sp-webpart-base';
import { PropertyPaneWebPartInformation } from '@pnp/spfx-property-controls/lib/PropertyPaneWebPartInformation';
import * as strings from 'AreaChartDemoWebPartStrings';
import AreaChartDemo from './components/AreaChartDemo';
import { IAreaChartDemoProps } from './components/IAreaChartDemo.types';
export interface IAreaChartDemoWebPartProps {
description: string;
}
export default class AreaChartDemoWebPart extends BaseClientSideWebPart<IAreaChartDemoWebPartProps> {
public render(): void {
const element: React.ReactElement<IAreaChartDemoProps > = React.createElement(
AreaChartDemo,
{
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
groups: [
{
groupFields: [
PropertyPaneWebPartInformation({
description: strings.WebPartDescription,
moreInfoLink: strings.MoreInfoLinkUrl,
key: 'webPartInfoId'
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,10 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.areaChartDemo {
color:inherit;
}
:export {
backgroundColor: rgba(255, 99, 132, 0.2);
borderColor: rgba(255,99,132,1);
}

View File

@ -0,0 +1,294 @@
import * as React from 'react';
import styles from './AreaChartDemo.module.scss';
import { IAreaChartDemoProps, IAreaChartDemoState } from './IAreaChartDemo.types';
import * as strings from 'AreaChartDemoWebPartStrings';
// used to add a chart control
import { ChartControl, ChartType } from '@pnp/spfx-controls-react/lib/ChartControl';
// used to retrieve (fake) data from a (fake) service
import MockChartDataProvider from '../../../services/ChartDataProvider/MockChartDataProvider';
// used to render the toolbar above the chart
import {
CommandBar,
IContextualMenuItem,
DirectionalHint
} from 'office-ui-fabric-react';
/**
In chart.js, there are really no "area" chart types; They're just line charts with a 'fill'
value.
This demo shows the following:
- Render a line chart with random data
- Store data in the state and randomize it
- Generate a gradient for a background color
- Change the fill value
*/
export default class AreaChartDemo extends React.Component<IAreaChartDemoProps, IAreaChartDemoState> {
/**
* The chart element
*/
private _chartElem: ChartControl = undefined;
/**
* Always start showing 'loading' and with an empty dataset
* @param props
*/
constructor(props: IAreaChartDemoProps) {
super(props);
this.state = {
dataSet: [
MockChartDataProvider.getRandomSignedNumber(),
MockChartDataProvider.getRandomSignedNumber(),
MockChartDataProvider.getRandomSignedNumber(),
MockChartDataProvider.getRandomSignedNumber(),
MockChartDataProvider.getRandomSignedNumber(),
MockChartDataProvider.getRandomSignedNumber(),
MockChartDataProvider.getRandomSignedNumber(),
MockChartDataProvider.getRandomSignedNumber(),
],
fill: 'origin',
curved: false
};
}
/**
* Renders the command bar and the chart
*/
public render(): React.ReactElement<IAreaChartDemoProps> {
const {
dataSet,
fill,
curved
} = this.state;
const data: Chart.ChartData = {
labels:
[
'January', 'February', 'March', 'April', 'May', 'June', 'July'
],
datasets: [
{
label: 'My First Dataset',
fill: fill,
lineTension: curved ? 0.4 : 0.000001,
backgroundColor: styles.backgroundColor, // 20% opacity red
borderColor: styles.borderColor, // opaque red
borderWidth: 1,
data: dataSet
}
]
};
// set the options
const options: Chart.ChartOptions = {
legend: {
display: false,
},
title: {
display: true,
text: 'My First Area Chart'
}
};
return (
<div className={styles.areaChartDemo}>
{this._renderCommandBar()}
<ChartControl
type={ChartType.Line}
ref={this._linkElement}
data={data}
options={options}
/>
</div>
);
}
public componentDidMount(): void {
this._applyGradientFill();
}
public componentDidUpdate(prevProps: IAreaChartDemoProps, prevState: IAreaChartDemoState): void {
this._applyGradientFill();
}
/**
* Renders the command bar control.
*/
private _renderCommandBar(): JSX.Element {
return (
<CommandBar
isSearchBoxVisible={false}
items={[
{
key: 'randomizeData',
name: strings.RandomizeCommandLabel,
iconProps: {
iconName: 'Refresh'
},
ariaLabel: strings.RandomizeCommandLabel,
onClick: () => { this._handleRandomizeData(); },
['data-automation-id']: 'randomizeData'
},
{
key: 'fill',
iconProps: {
iconName: 'BucketColor'
},
subMenuProps: {
directionalHint: DirectionalHint.bottomCenter,
items: [
{
key: 'fillNegative',
name: 'false',
canCheck: true,
isChecked: this.state.fill === false,
onClick: (ev: React.MouseEvent<HTMLElement>, item: IContextualMenuItem) => { this._handleToggleFill(ev, item); }
},
{
key: 'fillOrigin',
name: 'origin',
canCheck: true,
isChecked: this.state.fill === 'origin',
onClick: (ev: React.MouseEvent<HTMLElement>, item: IContextualMenuItem) => { this._handleToggleFill(ev, item); }
},
{
key: 'fillStart',
name: 'start',
canCheck: true,
isChecked: this.state.fill === 'start',
onClick: (ev: React.MouseEvent<HTMLElement>, item: IContextualMenuItem) => { this._handleToggleFill(ev, item); }
},
{
key: 'fillEnd',
name: 'end',
canCheck: true,
isChecked: this.state.fill === 'end',
onClick: (ev: React.MouseEvent<HTMLElement>, item: IContextualMenuItem) => { this._handleToggleFill(ev, item); }
}
]
},
name: this.state.fill === false ? 'fill: false' : `fill: '${this.state.fill}'`,
canCheck: false,
split: true,
},
{
key: 'alignment',
iconProps: {
iconName: 'Line'
},
subMenuProps: {
directionalHint: DirectionalHint.bottomLeftEdge,
items: [
{
key: 'smoothOff',
name: strings.NotSmooth,
canCheck: true,
isChecked: this.state.curved === false,
onClick: (ev: React.MouseEvent<HTMLElement>, item: IContextualMenuItem) => { this._handleToggleSmooth(ev, item); }
},
{
key: 'smoothOn',
name: strings.Smooth,
canCheck: true,
isChecked: this.state.curved === true,
onClick: (ev: React.MouseEvent<HTMLElement>, item: IContextualMenuItem) => { this._handleToggleSmooth(ev, item); }
},
]
},
name: strings.LineTypeCommandLabel,
canCheck: false,
split: true,
}
]}
/>
);
}
/**
* Called when user clicks on Randomize Data.
* Reloads the entire dataset with newly retrieved randomized numbers
*/
private _handleRandomizeData = () => {
this.setState({
dataSet: [
MockChartDataProvider.getRandomSignedNumber(),
MockChartDataProvider.getRandomSignedNumber(),
MockChartDataProvider.getRandomSignedNumber(),
MockChartDataProvider.getRandomSignedNumber(),
MockChartDataProvider.getRandomSignedNumber(),
MockChartDataProvider.getRandomSignedNumber(),
MockChartDataProvider.getRandomSignedNumber(),
MockChartDataProvider.getRandomSignedNumber(),
],
});
}
/**
* Changes the line type from straight to curved, and vice-versa
*/
private _handleToggleSmooth = (ev: React.MouseEvent<HTMLElement>, item: IContextualMenuItem) => {
ev!.preventDefault();
// Should it be curved -- or smooth
const isSmooth: boolean = item.key === 'smoothOn';
this.setState({
curved: isSmooth
}, () => {
this.forceUpdate();
});
}
/**
* Changes the type of fill
*/
private _handleToggleFill = (ev: React.MouseEvent<HTMLElement>, item: IContextualMenuItem) => {
ev!.preventDefault();
let fillType: false | 'origin' | 'start' | 'end' = undefined;
switch (item.key) {
case 'fillOrigin':
fillType = 'origin';
break;
case 'fillStart':
fillType = 'start';
break;
case 'fillEnd':
fillType = 'end';
break;
default:
fillType = false;
}
this.setState({
fill: fillType
}, () => {
this.forceUpdate();
});
}
/**
* Links a reference to the chart so that we can
* refer to it later and change its data
*/
// tslint:disable-next-line no-any
private _linkElement = (e: any) => {
this._chartElem = e;
}
private _applyGradientFill() {
const canvas: HTMLCanvasElement = this._chartElem.getCanvas();
const ctx = canvas.getContext('2d');
let gradientFill = ctx.createLinearGradient(0, 0, 0, canvas.height);
gradientFill.addColorStop(0, 'rgba(255, 99, 132, 0.5)');
gradientFill.addColorStop(1, 'rgba(255, 255, 255, 0)');
let data: Chart.ChartDataSets = this._chartElem.getChart().data.datasets[0];
data.backgroundColor = gradientFill;
this._chartElem.update();
}
}

View File

@ -0,0 +1,8 @@
export interface IAreaChartDemoProps {
}
export interface IAreaChartDemoState {
dataSet: number[];
fill: false | 'origin' | 'start' | 'end';
curved: boolean;
}

View File

@ -0,0 +1,14 @@
define([], function() {
return {
"ChartTitle": "Area Chart",
"RandomizeCommandLabel": "Randomize data",
"LineTypeCommandLabel": "Line type",
"Smooth": "Curved",
"NotSmooth": "Straight",
"ChartLabes": [
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug'
],
"WebPartDescription": `<p>This web part demonstrates how to use the PnP <strong>ChartControl</strong> to render an <a href="https://www.chartjs.org/docs/latest/charts/area.html" target="_blank">area chart</a> as per the <a href="https://github.com/chartjs/Chart.js/blob/master/samples/charts/area/line-boundaries.html" target="_blank">Chart.js sample</a></p>`,
"MoreInfoLinkUrl": "https://sharepoint.github.io/sp-dev-fx-controls-react/controls/ChartControl/"
}
});

View File

@ -0,0 +1,15 @@
declare interface IAreaChartDemoWebPartStrings {
ChartTitle: string;
RandomizeCommandLabel: string;
ChartLabels: string[];
LineTypeCommandLabel: string;
Smooth: string;
NotSmooth: string;
WebPartDescription: string;
MoreInfoLinkUrl: string;
}
declare module 'AreaChartDemoWebPartStrings' {
const strings: IAreaChartDemoWebPartStrings;
export = strings;
}

View File

@ -0,0 +1,22 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "74f7231a-c3f1-4ef5-94e7-3d2dd97cb39b",
"alias": "BarChartDemoWebPart",
"componentType": "WebPart",
"version": "*",
"manifestVersion": 2,
"requiresCustomScript": false,
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70",
"group": { "default": "Other" },
"title": { "default": "Bar Chart" },
"description": { "default": "Demonstrates how to create a bar chart with the least possible amount of code" },
"officeFabricIconFontName": "BarChartVertical",
"properties": {
"description": "BarChartDemo"
}
}]
}

View File

@ -0,0 +1,63 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import * as strings from 'BarChartDemoWebPartStrings';
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
} from '@microsoft/sp-webpart-base';
import { PropertyPaneWebPartInformation } from '@pnp/spfx-property-controls/lib/PropertyPaneWebPartInformation';
import BarChartDemo from './components/BarChartDemo';
import { IBarChartDemoProps } from './components/IBarChartDemo.types';
export interface IBarChartDemoWebPartProps {
description: string;
}
/**
* This web part retrieves data asynchronously and renders a bar chart once loaded
* It mimics a "real-life" scenario by loading (random) data asynchronously
* and rendering the chart once the data has been retrieved.
* To keep the demo simple, we don't specify custom colors.
*/
export default class BarChartDemoWebPart extends BaseClientSideWebPart<IBarChartDemoWebPartProps> {
public render(): void {
const element: React.ReactElement<IBarChartDemoProps > = React.createElement(
BarChartDemo,
{
// there are no properties to pass for this demo
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
groups: [
{
groupFields: [
PropertyPaneWebPartInformation({
description: strings.WebPartDescription,
moreInfoLink: strings.MoreInfoLinkUrl,
key: 'webPartInfoId'
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,21 @@
@import "~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss";
.barChartDemo {
color: inherit; // so that SCSS doesn't complain about an empty class
}
:export {
background1: rgba(255, 99, 132, 0.2);
background2: rgba(54, 162, 235, 0.2);
background3: rgba(255, 206, 86, 0.2);
background4: rgba(75, 192, 192, 0.2);
background5: rgba(153, 102, 255, 0.2);
background6: rgba(255, 159, 64, 0.2);
border1: rgba(255, 99, 132, 1);
border2: rgba(54, 162, 235, 1);
border3: rgba(255, 206, 86, 1);
border4: rgba(75, 192, 192, 1);
border5: rgba(153, 102, 255, 1);
border6: rgba(255, 159, 64, 1);
}

View File

@ -0,0 +1,91 @@
import * as React from 'react';
import styles from './BarChartDemo.module.scss';
import * as strings from 'BarChartDemoWebPartStrings';
import { IBarChartDemoProps, IBarChartDemoState } from './IBarChartDemo.types';
import MockChartDataProvider from '../../../services/ChartDataProvider/MockChartDataProvider';
import IChartDataProvider from '../../../services/ChartDataProvider/IChartDataProvider';
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
import { ChartControl, ChartType } from '@pnp/spfx-controls-react/lib/ChartControl';
const DATA_LENGTH: number = 7;
/**
* This demo shows how to easily load data from an asynchronous service
* and display the results, rendering a "please wait" message while
* data is loading.
*/
export default class BarChartDemo extends React.Component<IBarChartDemoProps, IBarChartDemoState> {
/**
* Renders the "Loading" spinner if the state is currently loading,
* or the chart once data is loladed
*/
public render(): React.ReactElement<IBarChartDemoProps> {
return (
<div className={styles.barChartDemo}>
<ChartControl
type={ChartType.Bar}
datapromise={this._loadAsyncData()}
loadingtemplate={() => <Spinner size={SpinnerSize.large} label={strings.PleaseWait} ariaLive="assertive" />}
options={{
scales:
{
yAxes:
[{
ticks:
{
beginAtZero: true // optional, but makes the chart start at zero instead of the minimum value
}
}]
},
// animation is totally unecessary -- it just makes this demo pretty
animation: {
easing: 'easeInOutBack'
}
}}
/>
</div>
);
}
/**
* Loads data from a service.
* This is where you would replace for your own code
*/
private _loadAsyncData(): Promise<Chart.ChartData> {
return new Promise<Chart.ChartData>((resolve, reject) => {
// we're using a mock service that returns random numbers.
const dataProvider: IChartDataProvider = new MockChartDataProvider();
dataProvider.getNumberArray(DATA_LENGTH, 2000).then((dataSet: number[]) => {
const data: Chart.ChartData =
{
labels: strings.ChartLabels,
datasets: [
{
label: strings.DataSetLabel,
data: dataSet,
backgroundColor: [
styles.background1,
styles.background2,
styles.background3,
styles.background4,
styles.background5,
styles.background6
],
borderColor: [
styles.border1,
styles.border2,
styles.border3,
styles.border4,
styles.border5,
styles.border6
],
borderWidth: 1
}
]
};
resolve(data);
});
});
}
}

View File

@ -0,0 +1,7 @@
export interface IBarChartDemoProps {
// no props
}
export interface IBarChartDemoState {
// no state
}

View File

@ -0,0 +1,11 @@
define([], function() {
return {
"DataSetLabel": "Cat Videos",
"ChartLabels": [
'January', 'February', 'March', 'April', 'May', 'June', 'July'
],
"PleaseWait" : "Counting cat videos per month...",
"WebPartDescription": `<p>This web part demonstrates how to use the PnP <strong>ChartControl</strong> to render a bar chart using data from asynchronous service.</p><p>It uses the sample bar chart found in <a href="https://www.chartjs.org/docs/latest/charts/bar.html" target="_blank">Chart.js's documentation</a> from Chart.js</p>`,
"MoreInfoLinkUrl": "https://sharepoint.github.io/sp-dev-fx-controls-react/controls/ChartControl/"
}
});

View File

@ -0,0 +1,12 @@
declare interface IBarChartDemoWebPartStrings {
DataSetLabel: string;
ChartLabels: string[];
WebPartDescription: string;
MoreInfoLinkUrl: string;
PleaseWait: string;
}
declare module 'BarChartDemoWebPartStrings' {
const strings: IBarChartDemoWebPartStrings;
export = strings;
}

View File

@ -0,0 +1,22 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "8459e5ba-c215-4887-a23b-563af4733ddc",
"alias": "BarChartStaticWebPart",
"componentType": "WebPart",
"version": "*",
"manifestVersion": 2,
"requiresCustomScript": false,
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70",
"group": { "default": "Other" },
"title": { "default": "Bar Chart - Static Data" },
"description": { "default": "Shows the easiest way to display static data." },
"iconImageUrl": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAcCAYAAAATFf3WAAABG2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIi8+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+Gkqr6gAAAYFpQ0NQc1JHQiBJRUM2MTk2Ni0yLjEAACiRdZG/S0JRFMc/aqGUYVBDUIOENWlYQdTSoJQF1aAGWS368keg9nhPCWkNWoWCqKVfQ/0FtQbNQVAUQTQ0NRe1lLzOy8CIPJdzz+d+7z2He88FazSr5PQGP+TyBS0cCrjnYvNu+xNOHFjpxB5XdHU6Mh6lrr3fYjHjtc+sVf/cv9a8lNQVsDiERxVVKwhPCE+tFlSTt4TblUx8SfhE2KvJBYVvTD1R5WeT01X+NFmLhoNgbRV2p39x4hcrGS0nLC/Hk8sWlZ/7mC9xJvOzEYnd4l3ohAkRwM0kYwQZop8RmYfwMUCfrKiT7//On2FFchWZVUpoLJMmQwGvqEWpnpSYEj0pI0vJ7P/fvuqpwYFqdWcAGh8N47UH7JtQKRvGx4FhVA7B9gDn+Vr+yj4Mv4lermmePXCtw+lFTUtsw9kGdNyrcS3+LdnErakUvBxDSwzarqBpodqzn32O7iC6Jl91CTu70CvnXYtfBnVnugxnM5UAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAGoSURBVFiFzZY/bsIwFId/pj1AxVgppBITqlTWKoOdDpSBSyC2DohWIorY4CQ5BXsSJEaUTKw5Qm/gDhDEHyfYxHH5JCvRs579ybFfDNw5D4LYE4D3/fuvQRdpXAAcwPx/NXY81jj2EMDLWSzcN2nqFmSCeKgySEOHyRU+APzcmlznCuaEVZJlVjDE7tAct3mVSVVQ+cQRgLQukSJUBF0A3zV5FFJ1Dw6hoZSUoUOwcikpQ1eZqVRKytBVZkJN41xgolBXwqjgYrF4zd89z+OUUk4p5Z7n8aIco4KO49gA4Ps+t20bcRyTOI5Jq9WC7/tCSRO/ugOdTscCgPV6jdVqRfL4eDwmlFKhoLEV7Ha74JxzAGg0LqclhFzEAEOCs9msnaYpsSzrWTXXiGCv13sDgCAIOGOMJ0kCxhg/bnksCIKTT62yBwtP2rW+zWYDAJMsyxBFEQAcnsdEUQTXdU9iMoKJIJap9DWbzbbEPEJkBMtuMFJ9o9FIWuicMkEXmi6my+XyczAYOLfklgkyiG8qymy325tzRYIJdrcTbfT7/a/pdKpzyPvhD3sccj6XxnsRAAAAAElFTkSuQmCC",
"properties": {
"description": "BarChartStatic"
}
}]
}

View File

@ -0,0 +1,57 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
} from '@microsoft/sp-webpart-base';
import { PropertyPaneWebPartInformation } from '@pnp/spfx-property-controls/lib/PropertyPaneWebPartInformation';
import * as strings from 'BarChartStaticWebPartStrings';
import BarChartStatic from './components/BarChartStatic';
import { IBarChartStaticProps } from './components/IBarChartStaticProps';
export interface IBarChartStaticWebPartProps {
description: string;
}
export default class BarChartStaticWebPart extends BaseClientSideWebPart<IBarChartStaticWebPartProps> {
public render(): void {
const element: React.ReactElement<IBarChartStaticProps > = React.createElement(
BarChartStatic,
{
// no properties to pass
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
groups: [
{
groupFields: [
PropertyPaneWebPartInformation({
description: strings.WebPartDescription,
moreInfoLink: strings.MoreInfoLinkUrl,
key: 'webPartInfoId'
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,74 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.barChartStatic {
.container {
max-width: 700px;
margin: 0px auto;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.row {
@include ms-Grid-row;
@include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px;
}
.column {
@include ms-Grid-col;
@include ms-lg10;
@include ms-xl8;
@include ms-xlPush2;
@include ms-lgPush1;
}
.title {
@include ms-font-xl;
@include ms-fontColor-white;
}
.subTitle {
@include ms-font-l;
@include ms-fontColor-white;
}
.description {
@include ms-font-l;
@include ms-fontColor-white;
}
.button {
// Our button
text-decoration: none;
height: 32px;
// Primary Button
min-width: 80px;
background-color: $ms-color-themePrimary;
border-color: $ms-color-themePrimary;
color: $ms-color-white;
// Basic Button
outline: transparent;
position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
-webkit-font-smoothing: antialiased;
font-size: $ms-font-size-m;
font-weight: $ms-font-weight-regular;
border-width: 0;
text-align: center;
cursor: pointer;
display: inline-block;
padding: 0 16px;
.label {
font-weight: $ms-font-weight-semibold;
font-size: $ms-font-size-m;
height: 32px;
line-height: 32px;
margin: 0 4px;
vertical-align: top;
display: inline-block;
}
}
}

View File

@ -0,0 +1,43 @@
import * as React from 'react';
import styles from './BarChartStatic.module.scss';
import { IBarChartStaticProps } from './IBarChartStaticProps';
import { ChartControl, ChartType } from '@pnp/spfx-controls-react/lib/ChartControl';
/**
* Renders a static bar chart.
* This demo is intended to demonstrate how to take a sample from Chart.js
* and use it with the ChartControl.
* You shouldn't hard-code the data or the text, but we're trying to keep
* this sample as simple as possible.
* @see https://www.chartjs.org/docs/latest/ for the original sample code.
*/
export default class BarChartStatic extends React.Component<IBarChartStaticProps, {}> {
public render(): React.ReactElement<IBarChartStaticProps> {
return (
<div className={styles.barChartStatic}>
<ChartControl
data={
{
// Please localize strings in real life!
labels: [
'David',
'Mikael',
'Simon-Pierre',
'Velin',
'Vesa',
'Waldek'
], // any resemblance to real people's names is purely coincidental
datasets: [
{
label: '# of Votes', // Please localize strings in real life!
data: [12, 19, 3, 5, 2, 3], // You should really get the data from somewhere... not hard coded
borderWidth: 1
}
]
}}
type={ChartType.Bar} />
</div>
);
}
}

View File

@ -0,0 +1,3 @@
export interface IBarChartStaticProps {
}

View File

@ -0,0 +1,13 @@
define([], function() {
return {
"WebPartDescription": `<p>This web part shows how to use the PnP <strong>ChartControl</strong> to render a bar chart in its simplest form.</p><p>We took the sample code from <a href="https://www.chartjs.org/docs/latest/" target="_blank">Chart.js</a> and simplified it by:
<ul>
<li>Keeping the hard-coded (in real life, you would want to retrieve the data from somewhere)
<li>We did not localize any strings (in real life, you should localize your strings)
<li>We did not change the colors (the ChartComponent automatically provides colors if none are provided)
</ul>
</p>
`,
"MoreInfoLinkUrl": "https://sharepoint.github.io/sp-dev-fx-controls-react/controls/ChartControl/"
}
});

View File

@ -0,0 +1,9 @@
declare interface IBarChartStaticWebPartStrings {
WebPartDescription: string;
MoreInfoLinkUrl: string;
}
declare module 'BarChartStaticWebPartStrings' {
const strings: IBarChartStaticWebPartStrings;
export = strings;
}

View File

@ -0,0 +1,23 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "dd21a307-fd9f-4fef-8d9f-d8abcb83b814",
"alias": "BubbleChartDemoWebPart",
"componentType": "WebPart",
"version": "*",
"manifestVersion": 2,
"requiresCustomScript": false,
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70",
"group": { "default": "Other" },
"title": { "default": "Bubble Chart" },
"description": { "default": "Demonstrates how to use bubble charts" },
"iconImageUrl": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAcCAYAAAATFf3WAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAO3QAADt0BKvL0UAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAARcSURBVFiF7Zh/SKN1HMffcw43vXM2/9C5sJhyxoyJNg1s5jkOpEKzKeIho4H/mDgcYnj+ERSoRafpdQYFlwaHYDjxSJyxzCRNGUuW4hy7cj8YrtJukT8677z89Iduzcdn3q7YKOgNDzzf7+fD5/N6ns/3+X6+PBycVg2A1wFkA+Cw2KMhAvA9gKsAxkINTIDnAcwAuA7gp5ig/aV0ADoAFwHMh3N6H8BqjIDYtArgWuhEHMPhMQAHgUF/f/+1lpaWOYvFIo0BHI5zi0InmIAn5HK5FAaD4dnd3V15VLEeQTcBWAMDi8UinZ2dfSWG+a3HDEHFn+VdWFjoBOCMJtHDdGaJ/w36H/Cf6r8NSESSlZWV6zab7W0iEsYK6iwFtxkiSpuamvohPT2dsrOzaXFx8Tsi4kc5/6ltJiygw+G4IpPJCEeNnKqqqu7t7+9fijVg2BLz+XyfRCIJtj2ZTPZ7QkLCnSjCsYp5mrkJ4GkA+UTEtVqtUxMTE0UbGxuJNpsNqampXpVK9bFer+8+K6jRaMw3m811Ozs75+VyuUWr1Q5FyGPF0YFBE87hRKvzer2iiooKN47LDIDS0tLuDQ4OXg4XYGhoSJ2fn/9zwD85OfmPzs7OTx4BMLI1CAAGg+FVLpcbhAtcOp0u7Bupr6//lukvEokOxsbGIunpka9BAEhMTNzn8Xin5rlc7v3A/ebmptJut3+5vr7+GhGl2u32p5j+fr8/3mazKSIAPCVWQCLiarXaWz09PW81NDTcDrXl5OT8Vlxc/Glg7PF4buTl5anMZvMbAM6JxWLWD0ksFm/9HUDW04zL5XrcZDK95PP54nNzcwdbW1uFKysraTweD+Xl5bba2tqZgG9CQsKeQqGARCJ5AGC7tLR03GQytRwcBDcAVFdXr6pUqqGQ+PyRkZHO9fV1qUKhMDU2Nn4YKXBwDer1+jfVavXI5ORkY1JSUnA9ZWVl3Xc4HMEyzs3NXV5YWPh8a2vrIgAQUXxvb+87arV6ubKy0tnc3Dw+PT0tC03S3d19NS4ujgBQRkbG3dHR0ReOTZGdB4mI39HRka1UKj8TCoVeoVB4sLe3xwOAlJSUuwKBYBsAhoeHX9ZoNDfcbndiU1PTeQDPcTicBwCuHF8AgIGBgRPxnU5n1uHhIQDA5/PxvV5vPoApNhbWNbi0tPTk/Px8hd/vv6RUKud0Ot1HRUVF2yUlJXfq6urey8zM9AGAx+MpcLvdiQCwtrZ2gYgEjAeNI6JTL6GwsPALiUSyDwBlZWVeuVx+i42DTcESO53OJ4go+AkTkYiIkkOdZ2ZmcjQazXJBQcFuV1fXB8xg7e3txra2tq/ZEhkMhhf7+vo6GOV/aIl/BcADAKlU6gk1cDgcPzOJSqVyENEzAFI4HM4vTLtAIFgmonNsgDU1NUYARsY0D8CJPMxWVwLgKwADAH5kCxxFiQE0AygF8E1gku3XhhpHvz4uhLFHQwTgNoB3AYyHGv4Ec4GuEtcWUi8AAAAASUVORK5CYII=",
"properties": {
"description": "Bubble Chart"
}
}]
}

View File

@ -0,0 +1,57 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
} from '@microsoft/sp-webpart-base';
import { PropertyPaneWebPartInformation } from '@pnp/spfx-property-controls/lib/PropertyPaneWebPartInformation';
import * as strings from 'BubbleChartDemoWebPartStrings';
import BubbleChartDemo from './components/BubbleChartDemo';
import { IBubbleChartDemoProps } from './components/IBubbleChartDemo.types';
export interface IBubbleChartDemoWebPartProps {
description: string;
}
export default class BubbleChartDemoWebPart extends BaseClientSideWebPart<IBubbleChartDemoWebPartProps> {
public render(): void {
const element: React.ReactElement<IBubbleChartDemoProps > = React.createElement(
BubbleChartDemo,
{
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
groups: [
{
groupFields: [
PropertyPaneWebPartInformation({
description: strings.WebPartDescription,
moreInfoLink: strings.MoreInfoLinkUrl,
key: 'webPartInfoId'
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,13 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.bubbleChartDemo {
color: inherit;
}
:export {
color1: rgb(255, 99, 132);
color2: rgb(255, 159, 64);
color3: rgb(255, 205, 86);
color4: rgb(75, 192, 192);
color5: rgb(54, 162, 235);
}

View File

@ -0,0 +1,243 @@
import * as React from 'react';
import styles from './BubbleChartDemo.module.scss';
import { IBubbleChartDemoProps } from './IBubbleChartDemo.types';
import * as strings from 'BubbleChartDemoWebPartStrings';
import * as Color from 'color';
// used to add a chart control
import { ChartControl, ChartType, PaletteGenerator } from '@pnp/spfx-controls-react/lib/ChartControl';
// used to retrieve (fake) data from a (fake) service
import IChartDataProvider from '../../../services/ChartDataProvider/IChartDataProvider';
import MockChartDataProvider from '../../../services/ChartDataProvider/MockChartDataProvider';
// used to render the toolbar above the chart
import {
CommandBar
} from 'office-ui-fabric-react';
/**
* Define the chart colors:
* Red,
* Orange,
* Yellow,
* Green,
* Blue
*/
const chartColors: string[] = [
styles.color1,
styles.color2,
styles.color3,
styles.color4,
styles.color5];
const chartBackgroundColors: string[] = PaletteGenerator.alpha(chartColors, 0.2) as string[];
const DATA_COUNT: number = 16;
const DATSET_LENGTH: number = 2;
const MAX_BORDERWIDTH: number = 8;
export default class BubbleChartDemo extends React.Component<IBubbleChartDemoProps, {}> {
/**
* The chart element
*/
private _chartElem: ChartControl = undefined;
public render(): React.ReactElement<IBubbleChartDemoProps> {
return (
<div className={styles.bubbleChartDemo}>
{this._renderCommandBar()}
<ChartControl
type={ChartType.Bubble}
ref={this._linkElement}
datapromise={this._loadAsyncData()}
options={
{
aspectRatio: 1,
legend: {
display: false
},
tooltips: {
enabled: false
},
animation: {
duration: 3000 // The chart.js demo code was 10000 -- too slow for me
},
responsive: true,
title: {
display: true,
text: strings.ChartTitle
}
}
}
onClick={(event: MouseEvent, _unused: {}[]) => {
const eventItem = this._chartElem.getElementAtEvent(event);
// don't do anything if we didn't click on a bubble
if (eventItem[0] === undefined) {
return;
}
// get the data item passed from the event
const datasetIndex: number = eventItem![0]!['_datasetIndex'];
const itemIndex: number = eventItem![0]!['_index'];
// pop that bubble!
if (datasetIndex !== undefined && itemIndex !== undefined) {
// I wish I could make a 'pop' sound
// get the chart's data
const { data } = this._chartElem.getChart();
// remove the data item that was clicked
data.datasets[datasetIndex]!.data!.splice(itemIndex, 1);
// update that chart!
this._chartElem.update();
}
}}
/>
</div>
);
}
/**
* Renders the command bar control.
*/
private _renderCommandBar(): JSX.Element {
return (
<CommandBar
isSearchBoxVisible={false}
items={[
{
key: 'randomizeData',
name: strings.RandomizeCommandLabel,
iconProps: {
iconName: 'Refresh'
},
ariaLabel: strings.RandomizeCommandLabel,
onClick: () => { this._handleRandomizeData(); },
['data-automation-id']: 'randomizeData'
},
{
key: 'addData',
name: strings.AddDatasetCommandLabel,
iconProps: {
iconName: 'Table'
},
onClick: () => { this._handleAddDataset(); },
['data-automation-id']: 'addDataset'
},
{
key: 'removeData',
name: strings.RemoveDatasetCommandLabel,
icon: 'DeleteTable',
onClick: () => { this._handleRemoveDataset(); },
['data-automation-id']: 'removeData'
},
]}
/>
);
}
/**
* Links a reference to the chart so that we can
* refer to it later and change its data
*/
// tslint:disable-next-line no-any
private _linkElement = (e: any) => {
this._chartElem = e;
}
/**
* Loads data from a service.
* This is where you would replace for your own code
*/
private _loadAsyncData(): Promise<Chart.ChartData> {
return new Promise<Chart.ChartData>((resolve, reject) => {
const dataProvider: IChartDataProvider = new MockChartDataProvider();
dataProvider
.getMultiBubbleArrays(DATSET_LENGTH, DATA_COUNT) // we only need 5 data elements for this demo
.then((bubbleArrays: Array<Chart.ChartPoint[]>) => {
const data: Chart.ChartData = {
datasets: [{
label: strings.DataSet1Label,
backgroundColor: chartBackgroundColors[0],
borderColor: chartColors[0],
hoverBackgroundColor: chartColors[0],
borderWidth: 1,
data: bubbleArrays[0]
}, {
label: strings.DataSet2Label,
backgroundColor: chartBackgroundColors[1],
borderColor: chartColors[1],
hoverBackgroundColor: chartColors[1],
borderWidth: 2,
data: bubbleArrays[1]
}]
};
resolve(data);
});
});
}
/**
* Called when user clicks on Randomize Data.
* Reloads the entire dataset with newly retrieved randomized numbers
*/
private _handleRandomizeData = () => {
const { data } = this._chartElem.getChart();
data.datasets.forEach((dataset) => {
// get the data as array of IBubblePoint
dataset.data = this._generateData();
});
this._chartElem.update();
}
/**
* Handles requests to add a new dataset.
*/
private _handleAddDataset = () => {
// get the chart's data
const { data } = this._chartElem.getChart();
data.datasets.push({
backgroundColor: chartBackgroundColors[data.datasets.length % chartColors.length],
borderColor: chartColors[data.datasets.length % chartColors.length],
hoverBackgroundColor: chartColors[data.datasets.length % chartColors.length],
data: this._generateData(),
borderWidth: Math.min(Math.max(1, data.datasets.length + 1), MAX_BORDERWIDTH)
});
// update that chart
this._chartElem.update();
}
/**
* Removes the oldset dataset
*/
private _handleRemoveDataset = () => {
const { data } = this._chartElem.getChart();
data.datasets.shift();
// update that chart
this._chartElem.update();
}
private _generateData(): Chart.ChartPoint[] {
const data: Chart.ChartPoint[] = [];
for (let i = 0; i < DATA_COUNT; ++i) {
data.push({
x: MockChartDataProvider.getRandomNumber(),
y: MockChartDataProvider.getRandomNumber(),
r: Math.abs(MockChartDataProvider.getRandomNumber()) / 5
});
}
return data;
}
}

View File

@ -0,0 +1,3 @@
export interface IBubbleChartDemoProps {
}

View File

@ -0,0 +1,13 @@
define([], function() {
return {
"ChartTitle": "Bubble Chart",
"DataSet1Label": "My first dataset",
"DataSet2Label": "My second dataset",
"RandomizeCommandLabel": "Randomize data",
"AddDatasetCommandLabel": "Add dataset",
"RemoveDatasetCommandLabel": "Remove dataset",
"WebPartDescription": `<p>This web part demonstrates how to use the PnP <strong>ChartControl</strong> to render a bubble chart as per the <a href="https://github.com/chartjs/Chart.js/blob/master/samples/charts/bubble.html" target="_blank">Chart.js sample</a></p>
<p>Note that this example uses points that provide an <strong>x</strong> and <strong>y</strong> coordinate, along with a <strong>r</strong> value, for the bubble's radius.`,
"MoreInfoLinkUrl": "https://sharepoint.github.io/sp-dev-fx-controls-react/controls/ChartControl/"
}
});

View File

@ -0,0 +1,15 @@
declare interface IBubbleChartDemoWebPartStrings {
ChartTitle: string;
DataSet1Label: string;
DataSet2Label: string;
RandomizeCommandLabel: string;
AddDatasetCommandLabel: string;
RemoveDatasetCommandLabel: string;
WebPartDescription: string;
MoreInfoLinkUrl: string;
}
declare module 'BubbleChartDemoWebPartStrings' {
const strings: IBubbleChartDemoWebPartStrings;
export = strings;
}

View File

@ -0,0 +1,29 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "604dd344-80a9-4efb-9cd5-7caddc5ac653",
"alias": "ChartinatorWebPart",
"componentType": "WebPart",
"version": "*",
"manifestVersion": 2,
"requiresCustomScript": false,
"preconfiguredEntries": [
{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70",
"group": {
"default": "Other"
},
"title": {
"default": "Chartinator"
},
"description": {
"default": "Easily create and configure a chart to show your data visually."
},
"officeFabricIconFontName": "Chart",
"properties": {
"description": "Chartinator",
"dataSourceType": 0,
"data": []
}
}
]
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,64 @@
import { INumberChartData, IBubbleChartData, IScatterChartData } from './controls/PropertyFieldRepeatingData';
import { ChartType, ChartPalette } from '@pnp/spfx-controls-react/lib/ChartControl';
import { DashType } from './controls/PropertyPaneDashSelector/components/DashSelector.types';
import { DataSourceType, EasingType, CapType, JoinType } from './components/Chartinator.types';
/**
* There are a LOT of options to store with this web part.
* I don't normally recommend doing this, but the goal of this
* sample is to demonstrate each chart setting individually.
*/
export interface IChartinatorWebPartProps {
animateRotate: boolean;
animateScale: boolean;
animationDuration: number;
animationEasing: EasingType;
borderCapStyle: CapType;
borderColor: string;
borderDash: DashType;
borderJoinStyle: JoinType;
borderWidth: number;
bottomPadding: number;
chartPalette: ChartPalette;
chartRotation: number;
chartType: ChartType;
circumference: number;
cutoutPercentage: number;
data: Array<INumberChartData | IBubbleChartData | IScatterChartData>;
dataLabelField: string;
dataRValueField: string;
dataSetName: string;
dataSourceListId: string;
dataSourceType: DataSourceType;
dataValueField: string;
dataYValueField: string;
leftPadding: number;
legendPosition: Chart.PositionType | 'none';
legendReversed: boolean;
lineCurved: boolean;
lineFill: string;
lineShowLine: boolean;
lineStepped: boolean;
offsetGridLines: boolean;
pointRadius: number;
pointRotation: number;
pointStyle: Chart.PointStyle;
rightPadding: number;
title: string;
tooltipEnabled: boolean;
tooltipIntersect: boolean;
tooltipMode: Chart.InteractionMode;
tooltipPosition: string;
topPadding: number;
xAxisLabelEnabled: boolean;
xAxisLabelText: string;
xAxisShowGridlines: boolean;
yAxisBeginAtZero: boolean;
yAxisLabelEnabled: boolean;
yAxisLabelText: string;
yAxisMax: number;
yAxisMaxTicksLimit: number;
yAxisMin: number;
yAxisStepSize: number;
yAxisShowGridlines: boolean;
}

View File

@ -0,0 +1,64 @@
// legend icons
export const LegendLeft: string = require('./assets/LegendLeft.png');
export const LegendRight: string = require('./assets/LegendRight.png');
export const LegendTop: string = require('./assets/LegendTop.png');
export const LegendBottom: string = require('./assets/LegendBottom.png');
export const LegendNone: string = require('./assets/LegendNone.png');
// chart icons
export const ChartIcons = {
"Bubble": require('./assets/BubbleChart.png'),
"PolarArea": require('./assets/PolarArea.png'),
"Radar": require('./assets/RadarChart.png'),
"Scatter": require('./assets/ScatterChart.png'),
};
export const PointStyleCircle: string = require('./assets/PointStyleCircle.png');
export const PointStyleCross: string = require('./assets/PointStyleCross.png');
export const PointStyleCrossRot: string = require('./assets/PointStyleCrossRot.png');
export const PointStyleDash: string = require('./assets/PointStyleDash.png');
export const PointStyleLine: string = require('./assets/PointStyleLine.png');
export const PointStyleRect: string = require('./assets/PointStyleRect.png');
export const PointStyleRectRounded: string = require('./assets/PointStyleRectRounded.png');
export const PointStyleRectRot: string = require('./assets/PointStyleRectRot.png');
export const PointStyleStar: string = require('./assets/PointStyleStar.png');
export const PointStyleTriangle: string = require('./assets/PointStyleTriangle.png');
// easing icons
export const Linear: string = require('./assets/Linear.png');
export const EaseInQuad: string = require('./assets/EaseInQuad.png');
export const EaseOutQuad: string = require('./assets/EaseOutQuad.png');
export const EaseInOutQuad: string = require('./assets/EaseInOutQuad.png');
export const EaseInCubic: string = require('./assets/EaseInCubic.png');
export const EaseOutCubic: string = require('./assets/EaseOutCubic.png');
export const EaseInOutCubic: string = require('./assets/EaseInOutCubic.png');
export const EaseInQuart: string = require('./assets/EaseInQuart.png');
export const EaseOutQuart: string = require('./assets/EaseOutQuart.png');
export const EaseInOutQuart: string = require('./assets/EaseInOutQuart.png');
export const EaseInQuint: string = require('./assets/EaseInQuint.png');
export const EaseOutQuint: string = require('./assets/EaseOutQuint.png');
export const EaseInOutQuint: string = require('./assets/EaseInOutQuint.png');
export const EaseInSine: string = require('./assets/EaseInSine.png');
export const EaseOutSine: string = require('./assets/EaseOutSine.png');
export const EaseInOutSine: string = require('./assets/EaseInOutSine.png');
export const EaseInExpo: string = require('./assets/EaseInExpo.png');
export const EaseOutExpo: string = require('./assets/EaseOutExpo.png');
export const EaseInOutExpo: string = require('./assets/EaseInOutExpo.png');
export const EaseInCirc: string = require('./assets/EaseInCirc.png');
export const EaseOutCirc: string = require('./assets/EaseOutCirc.png');
export const EaseInOutCirc: string = require('./assets/EaseInOutCirc.png');
export const EaseInElastic: string = require('./assets/EaseInElastic.png');
export const EaseOutElastic: string = require('./assets/EaseOutElastic.png');
export const EaseInOutElastic: string = require('./assets/EaseInOutElastic.png');
export const EaseInBack: string = require('./assets/EaseInBack.png');
export const EaseOutBack: string = require('./assets/EaseOutBack.png');
export const EaseInOutBack: string = require('./assets/EaseInOutBack.png');
export const EaseInBounce: string = require('./assets/EaseInBounce.png');
export const EaseOutBounce: string = require('./assets/EaseOutBounce.png');
export const EaseInOutBounce: string = require('./assets/EaseInOutBounce.png');
// fill icons
export const FillNone: string = require('./assets/FillNone.png');
export const FillStart: string = require('./assets/FillStart.png');
export const FillEnd: string = require('./assets/FillEnd.png');
export const FillOrigin: string = require('./assets/FillOrigin.png');

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1013 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1023 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1015 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 989 B

Some files were not shown because too many files have changed in this diff Show More