React Chart Control Samples (#763)
* Removed xml2js references * Initial version * Created dynamic data callable * Cleaned up code and comments * Added chartinator.
|
@ -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
|
|
@ -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
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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" />
|
After Width: | Height: | Size: 103 KiB |
After Width: | Height: | Size: 4.6 MiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 8.2 MiB |
After Width: | Height: | Size: 1008 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 2.4 MiB |
After Width: | Height: | Size: 3.8 MiB |
After Width: | Height: | Size: 780 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 45 KiB |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 2.6 MiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 139 KiB |
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||||
|
"deployCdnPath": "temp/deploy"
|
||||||
|
}
|
|
@ -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 -->"
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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/"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||||
|
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||||
|
}
|
|
@ -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);
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -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[]>;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './IChartDataProvider';
|
||||||
|
export * from './MockChartDataProvider';
|
|
@ -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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './GitHubService';
|
||||||
|
export * from './IGitHubService.types';
|
|
@ -0,0 +1,6 @@
|
||||||
|
export interface IListField {
|
||||||
|
Id: string;
|
||||||
|
Title: string;
|
||||||
|
InternalName: string;
|
||||||
|
TypeAsString: string;
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
export interface IListItem {
|
||||||
|
Id: string;
|
||||||
|
Label: string;
|
||||||
|
Value: number;
|
||||||
|
YValue?: number;
|
||||||
|
RValue?: number;
|
||||||
|
}
|
|
@ -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>>;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
export * from './IListField';
|
||||||
|
export * from './IListItem';
|
||||||
|
export * from './IListService';
|
||||||
|
export * from './ListService';
|
||||||
|
export * from './MockListService';
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
|
@ -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
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
export interface IAccessibleTableProps {
|
||||||
|
summary: string;
|
||||||
|
caption: string;
|
||||||
|
title: string;
|
||||||
|
datasetlabel: string;
|
||||||
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
||||||
|
});
|
|
@ -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;
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
|
@ -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'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
export interface IAreaChartDemoProps {
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAreaChartDemoState {
|
||||||
|
dataSet: number[];
|
||||||
|
fill: false | 'origin' | 'start' | 'end';
|
||||||
|
curved: boolean;
|
||||||
|
}
|
|
@ -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/"
|
||||||
|
}
|
||||||
|
});
|
|
@ -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;
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
|
@ -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'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
export interface IBarChartDemoProps {
|
||||||
|
// no props
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IBarChartDemoState {
|
||||||
|
// no state
|
||||||
|
}
|
|
@ -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/"
|
||||||
|
}
|
||||||
|
});
|
|
@ -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;
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
|
@ -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'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
export interface IBarChartStaticProps {
|
||||||
|
|
||||||
|
}
|
|
@ -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/"
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,9 @@
|
||||||
|
declare interface IBarChartStaticWebPartStrings {
|
||||||
|
WebPartDescription: string;
|
||||||
|
MoreInfoLinkUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'BarChartStaticWebPartStrings' {
|
||||||
|
const strings: IBarChartStaticWebPartStrings;
|
||||||
|
export = strings;
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
|
@ -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'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
export interface IBubbleChartDemoProps {
|
||||||
|
|
||||||
|
}
|
|
@ -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/"
|
||||||
|
}
|
||||||
|
});
|
|
@ -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;
|
||||||
|
}
|
|
@ -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": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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');
|
After Width: | Height: | Size: 559 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1013 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1023 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1015 B |
After Width: | Height: | Size: 989 B |