fgp-graph is a chart lib based on Dygraphs
npm install @future-grid/fgp-graph!fgp-graph
1. Typescript
``javascript`
let graphDiv: HTMLDivElement = document.getElementById("graphArea") as HTMLDivElement;
let fgpGraph = new FgpGraph(graphDiv, [vsConfig]);
fgpGraph.initGraph();
`
2. React
javascript`
let fgpGraph = new FgpGraph(document.getElementById('graphArea'), [
vdConfig,
vsConfig
]);
fgpGraph.initGraph();
`
3. Angular2
`
coming soon~
This graph is interactive as same as dygraphs, but some changes are made to meet the needs of futuregrid customer requirements. You can scroll up and down on both left and right axis to scale the y-ranges. You can also zoom-in & zoom-out on graph in the same way. Panning on those area has same effect.
This graph is highly configurable. You can put mulitple "VIEW"(datasource) to this graph and swap them with the "dropdown" easily. You can download data and save current graph as a image. There are "callbacks" can be used to monitoring the date window and highlighting(selection) events.
If you're using npm and a bundler like webpack, browserify or rollup, you can install fgp-graph via:
```
npm install --save @future-grid/fgp-graph`
and use it via:javascript
import FgpGraph from '@future-grid/fgp-graph';
//...... your code ......//
let fgpGraph = new FgpGraph(document.getElementById('graphArea'), [
vdConfig,
vsConfig
]);
fgpGraph.initGraph();
//...... your code ......//
`
Check out the React demo for more details.
javascript
const vdConfig: ViewConfig = {
name: "device view",
connectSeparatedPoints: true,
graphConfig: {
hideHeader: {views: false, intervals: false, toolbar: false, series: false},
// hideHeader: false,
features: {
zoom: true,
scroll: true,
rangeBar: {show: true, format: 'DD MMM YYYY h:mm a'},
legend: this.formatters.legendForAllSeries,
exports: [GraphExports.Data, GraphExports.Image],
rangeLocked: true // lock or unlock range bar
},
entities: [
{id: "substation1", type: "substation", name: "substation1"},
],
rangeEntity: {id: "substation1", type: "substation", name: "substation1"},
rangeCollection: {
label: 'substation_day',
name: 'substation_interval_day',
interval: 86400000,
series: [
{label: "Avg", type: 'line', exp: "data.avgConsumptionVah"}
]
},
collections: [
{
label: 'substation_raw',
name: 'substation_interval',
interval: 3600000,
markLines: [{value: 256, label: '256', color: '#FF0000'}, {
value: 248,
label: '248',
color: '#FF0000'
}],
series: [
{
label: "Avg",
type: 'line',
exp: "data.avgConsumptionVah",
yIndex: 'left',
color: '#058902',
visibility: false
},
{
label: "Max",
type: 'line',
exp: "data.maxConsumptionVah",
yIndex: 'left',
color: '#d80808'
},
{
label: "Min",
type: 'line',
exp: "data.minConsumptionVah",
yIndex: 'left',
color: '#210aa8',
extraConfig: {name: "helloword"}
}
],
threshold: {min: 0, max: (1000 60 60 24 10)}, // 0 ~ 10 days
yLabel: 'voltage',
y2Label: 'voltage',
initScales: {left: {min: 245, max: 260}},
fill: false
}, {
label: 'substation_day',
name: 'substation_interval_day',
interval: 86400000,
// markLines: [{value: 255, label: '255', color: '#FF0000'}, {value: 235, label: '235', color: '#FF0000'}],
series: [
{label: "Avg", type: 'line', exp: "data.avgConsumptionVah", yIndex: 'left'},
{
label: "Max",
type: 'line',
exp: "data.maxConsumptionVah",
yIndex: 'left',
color: '#ff0000'
},
// {
// label: "Min",
// type: 'dots',
// exp: "data.minConsumptionVah",
// yIndex: 'left',
// extraConfig: {any: "anything"}
// }
],
threshold: {min: (1000 60 60 24 10), max: (1000 60 60 24 7 52 10)}, // 7 days ~ 3 weeks
yLabel: 'voltage',
y2Label: 'voltage',
initScales: {left: {min: 230, max: 260}},
fill: false
}
],
filters: {
"buttons": [
{
label: "All"
, func: () => {
return ["Min", "Max", "Avg"];
}
},
{
label: "Min"
, func: (): Array => {
return ["Min"];
}
},
{
label: "Max"
, func: () => {
return ["Max"];
}
},
{
label: "Avg"
, func: () => {
return ["Avg"];
}
},
{
label: "Colors",
type: FilterType.COLORS,
func: (labels?: Array) => {
let colors: Array = [];
// generate colors
if (labels) {
labels.forEach(element => {
colors.push("#FF0000");
}); }
return colors;
}
},
{
label: "reset Colors",
type: FilterType.COLORS,
func: (labels?: Array) => {
return [];
}
}
]
}
},
dataService: this.dataService,
show: true,
ranges: [
{name: "10 mins", value: 1000 60 10},
{name: "half an hour", value: 1000 60 30},
{name: "1 hours", value: 1000 60 60},
{name: "2 hours", value: 1000 60 60 * 2},
{name: "1 day", value: 1000 60 60 * 24},
{name: "7 days", value: 604800000, show: true},
{name: "1 month", value: 2592000000}
],
initRange: {
start: moment("2019-11-01").add(0, 'days').startOf('day').valueOf(),
end: moment("2019-12-01").subtract(0, 'days').endOf('day').valueOf()
},
interaction: {
callback: {
highlightCallback: (datetime, series, points) => {
// console.debug("selected series: ", series);
},
syncDateWindow: (dateWindow) => {
// console.debug(moment(dateWindow[0]), moment(dateWindow[1]));
},
dbClickCallback: (series) => {
// console.debug("dbl callback");
}
}
},
timezone: 'Australia/Perth',
highlightSeriesBackgroundAlpha: 1
// timezone: 'Pacific/Auckland'
};
let fgpGraph = new FgpGraph(document.getElementById('graphArea'), [
vdConfig
]);
`
$3
#### Display
###### name
All views configuration has a name and you will find it in the "views" dropdownd list. It's a key of view and should be exclusive.
type: string
default: none
###### timezone
You should let the graphs know what timezone that data blongs to. You can find the right timezone for your data by googlingtype: string
default: none
`
Australia/Melbourne Pacific/Auckland
`
###### show
fgp-graph will find the last view config with "show: true", then show it first.
type: boolean
default: false
###### ranges
array of configuration to let you change datewindow easier.
`json
[{ name: "7 days", value: 604800000, show: true },{ name: "1 month", value: 2592000000 }]
`
+ name
show this name in range dropdown list
type: string
default: none
+ value
gap in milliseconds
type: number
default: none
+ show
tells graph which one should show first
type: boolean
default: false
###### initRange
this is another way to tell graph what timewindow you want to show first. graph will ignore "ranges" configuration with this attribute.
`json
{start: moment().subtract(10, 'days').startOf('day').valueOf(),end: moment().add(1, 'days').valueOf()}
`
+ start
type: number(timestamp)
default: none
+ end
type: number(timestamp)
default: none
#### Graph
###### features
+ zoom
enable/disable zoom
type: boolean
default: false+ scroll
enable/disable scroll
type: boolean
default: false
+ rangebar
show/hide rangebar
type: boolean
default: false
+ legend
legend formatter, there are 2 formatters included in @future-grid/fgp-graph/lib/extras/formatters
type: function(data):htmlstr
default: none
provider: legendForAllSeries(device view) legendForSingleSeries(children view)
+ exports
enable/disable export buttons
type: Array
default: none
provider: GraphExports.Data and GraphExports.Image
###### Entities
array of devices
+ id
device name from futuregrid platform
+ type
device type from futuregrid platform
+ name
device description(extension) from futuregrid platform
sample:
`json
[{ id: "meter1", type: "meter", name: "meter1" },{ id: "meter2", type: "meter", name: "meter2" }]
`
###### RangeEntity
single device and should always be the parent of "entities"+ id
device id from futuregrid platform, you can put device name here
+ type
device type from futuregrid platform
+ name
device name from futuregrid platform
sample:
`json
{ id: "substation1", type: "substation", name: "substation1" }
`###### RangeCollection
this "collection" is a futuregrid interval configuration. Graph will call "dataservice" to get the frist and last record and render the rangebar with that timewindow. But most time insted of last record with current datetime.
+ label
label of the "interval", different or same to interval name
type: string
default: none
+ name
name of the "interval"
type: string
default: none
+ interval
interval in milliseconds
type: numnber
default: none
+ series
Array of series configuration, but only one needed. we just need one line in the rangebar.
+ label
show this label when hover the line
+ type
"line", "bar" or any other types supported by graph. Right now only "line" worked.
+ exp
expression for data calculation.
`
"data.avgConsumptionVah * 10"
`
"data." is a prefix for the attribute from data###### Collection
this "collection" is similar to RangeCollection, but shown on main graph. you can put multiple collections there and graph will swap them base on "threshold"
+ label
label of the "interval", different or same to interval name
type: string
default: none
+ name
name of the "interval"
type: string
default: none
+ interval
interval in milliseconds
type: numnber
default: none
+ series
Array of series configuration, but only one needed.
`json
[{ label: "Voltage", type: 'line', exp: "data.voltage", yIndex: 'left' }]
`
+ label
show this label when hover the line
+ type
"line", "bar" or any other types supported by graph. Right now only "line" worked.
+ exp
expression for data calculation.
`
"data.avgConsumptionVah * 10"
`
"data." is a prefix for the attribute from data
+ yIndex
left(0) or right(1) axes+ threshold
Tells graph to show it in a particular datetime range.
`json
{ min: (1000 60 60 24 10), max: (1000 60 60 24 7 52 10) }
`
+ min
type: number(timestamp)
default: none
+ max
type: number(timestamp)
default: none
+ initScales
value ranges for left and right Y-Axes. leave it empty the graph will calculate with max and min from value, then add 10% offset to min and max.
`json
{ left: { min: 245, max: 260 }, right: { min:10, max:30 } }
`
+ left
+ min
type: number
default: none
+ max
type: number
default: none
+ right
+ min
type: number
default: none
+ max
type: number
default: none
+ yLabel
label of y-axes
type: string
default: none
#### Datasource
You need to provide a datasource base on "DataHandler" interface. There are two methods with "Promise" return value.
+ fetchFirstNLast
`javascript
fetchdata(ids: Array, interval: string, range: { start: number; end: number }, fields?: Array): Promise }>>;
`
+ fetchdata
`javascript
fetchFirstNLast(ids: Array, interval: string, fields?: Array): Promise>;
` All date that you provided should have the attributes in series coniguration and "timestamp" attribute as timeseries field.
check the react demo for more details
#### Interactions
Every time you change the datetime range or highlighting series on graph, the graph will call "callback" that you provided. You can do what you want, just like highlight something on map or redirect to another page.
+ callback
+ highlighCallback
`js
(datetime, series, points) => {
// console.debug("selected series: ", series);
}
`
+ datetime
timestamp of the points
+ series
series name
+ points
points value
+ clickCallback
`js
(series) => {
// console.debug("choosed series: ", series);
}
`
+ dbClickCallback
same as clickCallback, just in case sometimes we need 2 different event to highlight on map and pop window for another page
`js
(series) => {
// console.debug("choosed series: ", series);
}
`
#### Event Listeners
There are 2 listeners let you know when "ViewConfig" & "Interval" changed. Such as you can use "viewChangeListener" to create a child graph.
+ viewChangeListener
`js
onViewChange = (g: FgpGraph, view: ViewConfig): void => {
console.log("view changed!", view.name);
const mainGraph = g;
if("device view" === view.name){
// add new child graph
this.setState({
childrenGraph: []
});
} else {
// add new child graph
this.setState({
childrenGraph: [{
id: '' + Math.random() * 1000,
viewConfigs: this.childViewConfigs,
onReady: (div: HTMLDivElement, g: FgpGraph) => {
mainGraph.setChildren([g]);
}
}]
});
}
};
`
Graph instance and viewConfig object will send back as parameters. + intervalChangeListener
`js
onIntervalChange = (g: FgpGraph, interval: { name: string; value: number; show?: boolean }): void => {
console.log('interval changed!', interval);
};
```