@future-grid/fgp-graph
TypeScript icon, indicating that this package has built-in type declarations

20.6.2-RC15 • Public • Published

fgp-graph

fgp-graph bases on dygraphs and uses typescript as development langulage. It allows future-grid customer to explore their timeseries aggregations data sets. Here's how the fgp-graphs works:

fgp-graph

  1. Typescript
    let graphDiv: HTMLDivElement = document.getElementById("graphArea") as HTMLDivElement;
    let fgpGraph = new FgpGraph(graphDiv, [vsConfig]);
    fgpGraph.initGraph();
  1. React
    let fgpGraph = new FgpGraph(document.getElementById('graphArea'), [
          vdConfig,
          vsConfig
    ]);
    fgpGraph.initGraph();
  1. 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.

Geeting Started

Once you`ve got ready to put it in your project, please check the demos or check out our github wiki.

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:

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.

Options Reference

If you are a dygrpahs user, please don't put dygraphs options in fgp-graph. fgp-graph has its own options and different formats.

Usage

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<string> => {
                                return ["Min"];
                            }
                        },
                        {
                            label: "Max"
                            , func: () => {
                                return ["Max"];
                            }
                        },
                        {
                            label: "Avg"
                            , func: () => {
                                return ["Avg"];
                            }
                        },
                        {
                            label: "Colors",
                            type: FilterType.COLORS,
                            func: (labels?: Array<string>) => {
                                let colors: Array<string> = [];
                                // generate colors
                                if (labels) {
                                    labels.forEach(element => {
                                        colors.push("#FF0000");
                                    });

                                }
                                return colors;
                            }
                        },
                        {
                            label: "reset Colors",
                            type: FilterType.COLORS,
                            func: (labels?: Array<string>) => {
                                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
    ]);

ViewConfig

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 googling

type: 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.

[{ 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.

{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:

[{ 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:

{ 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.

    [{ 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.

    { 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.

    { 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

    fetchdata(ids: Array<string>, interval: string, range: { start: number; end: number }, fields?: Array<string>): Promise<Array<{ id: string, data: Array<any> }>>;
  • fetchdata

    fetchFirstNLast(ids: Array<string>, interval: string, fields?: Array<string>): Promise<Array<{ id: string, data: { first: any, last: any } }>>;

    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
      (datetime, series, points) => {
        // console.debug("selected series: ", series);
      }
      • datetime timestamp of the points
      • series series name
      • points points value
    • clickCallback
      (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
      (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

    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

    onIntervalChange = (g: FgpGraph, interval: { name: string; value: number; show?: boolean }): void => {
            console.log('interval changed!', interval);
        };

API

Please goto "docs" folder for more details.

Readme

Keywords

Package Sidebar

Install

npm i @future-grid/fgp-graph

Weekly Downloads

3

Version

20.6.2-RC15

License

Apache-2.0

Unpacked Size

3.15 MB

Total Files

81

Last publish

Collaborators

  • oobles
  • naveenbajaj
  • eric.wang
  • chizzele
  • kranners
  • fg-jenkins