UNPKG

@future-grid/fgp-graph

Version:

fgp-graph is a chart lib based on Dygraphs

369 lines (327 loc) 16.1 kB
import Dygraph from 'dygraphs'; import moment from 'moment-timezone'; import {GraphConstant} from '../metadata/configurations'; export class Formatters { /** *show graph timestamp with this timezone * @type {string} * @memberof Formatters */ public timezone: string; public dateformat?: string; private TICK_PLACEMENT: any[]; private SHORT_SPACINGS: any[]; /** *Creates an instance of Formatters. * @param {string} timezone show graph timestamp with this timezone * @memberof Formatters */ constructor(timezone: string) { this.timezone = timezone; const DATEFIELD_Y = 0; const DATEFIELD_M = 1; const DATEFIELD_D = 2; const DATEFIELD_HH = 3; const DATEFIELD_MM = 4; const DATEFIELD_SS = 5; const DATEFIELD_MS = 6; const NUM_DATEFIELDS = 7; this.TICK_PLACEMENT = []; this.TICK_PLACEMENT[GraphConstant.SECONDLY] = {datefield: DATEFIELD_SS, step: 1, spacing: 1000 * 1}; this.TICK_PLACEMENT[GraphConstant.TWO_SECONDLY] = {datefield: DATEFIELD_SS, step: 2, spacing: 1000 * 2}; this.TICK_PLACEMENT[GraphConstant.FIVE_SECONDLY] = {datefield: DATEFIELD_SS, step: 5, spacing: 1000 * 5}; this.TICK_PLACEMENT[GraphConstant.TEN_SECONDLY] = {datefield: DATEFIELD_SS, step: 10, spacing: 1000 * 10}; this.TICK_PLACEMENT[GraphConstant.THIRTY_SECONDLY] = {datefield: DATEFIELD_SS, step: 30, spacing: 1000 * 30}; this.TICK_PLACEMENT[GraphConstant.MINUTELY] = {datefield: DATEFIELD_MM, step: 1, spacing: 1000 * 60}; this.TICK_PLACEMENT[GraphConstant.TWO_MINUTELY] = {datefield: DATEFIELD_MM, step: 2, spacing: 1000 * 60 * 2}; this.TICK_PLACEMENT[GraphConstant.FIVE_MINUTELY] = {datefield: DATEFIELD_MM, step: 5, spacing: 1000 * 60 * 5}; this.TICK_PLACEMENT[GraphConstant.TEN_MINUTELY] = {datefield: DATEFIELD_MM, step: 10, spacing: 1000 * 60 * 10}; this.TICK_PLACEMENT[GraphConstant.THIRTY_MINUTELY] = { datefield: DATEFIELD_MM, step: 30, spacing: 1000 * 60 * 30 }; this.TICK_PLACEMENT[GraphConstant.HOURLY] = {datefield: DATEFIELD_HH, step: 1, spacing: 1000 * 3600}; this.TICK_PLACEMENT[GraphConstant.TWO_HOURLY] = {datefield: DATEFIELD_HH, step: 2, spacing: 1000 * 3600 * 2}; this.TICK_PLACEMENT[GraphConstant.SIX_HOURLY] = {datefield: DATEFIELD_HH, step: 6, spacing: 1000 * 3600 * 6}; this.TICK_PLACEMENT[GraphConstant.DAILY] = {datefield: DATEFIELD_D, step: 1, spacing: 1000 * 86400}; this.TICK_PLACEMENT[GraphConstant.TWO_DAILY] = {datefield: DATEFIELD_D, step: 2, spacing: 1000 * 86400 * 2}; this.TICK_PLACEMENT[GraphConstant.WEEKLY] = {datefield: DATEFIELD_D, step: 7, spacing: 1000 * 604800}; this.TICK_PLACEMENT[GraphConstant.MONTHLY] = {datefield: DATEFIELD_M, step: 1, spacing: 1000 * 7200 * 365.2524}; // 1e3 * 60 * 60 * 24 * 365.2524 / 12 this.TICK_PLACEMENT[GraphConstant.QUARTERLY] = { datefield: DATEFIELD_M, step: 3, spacing: 1000 * 21600 * 365.2524 }; // 1e3 * 60 * 60 * 24 * 365.2524 / 4 this.TICK_PLACEMENT[GraphConstant.BIANNUAL] = { datefield: DATEFIELD_M, step: 6, spacing: 1000 * 43200 * 365.2524 }; // 1e3 * 60 * 60 * 24 * 365.2524 / 2 this.TICK_PLACEMENT[GraphConstant.ANNUAL] = {datefield: DATEFIELD_Y, step: 1, spacing: 1000 * 86400 * 365.2524}; // 1e3 * 60 * 60 * 24 * 365.2524 * 1 this.TICK_PLACEMENT[GraphConstant.DECADAL] = { datefield: DATEFIELD_Y, step: 10, spacing: 1000 * 864000 * 365.2524 }; // 1e3 * 60 * 60 * 24 * 365.2524 * 10 this.TICK_PLACEMENT[GraphConstant.CENTENNIAL] = { datefield: DATEFIELD_Y, step: 100, spacing: 1000 * 8640000 * 365.2524 }; // 1e3 * 60 * 60 * 24 * 365.2524 * 100 this.SHORT_SPACINGS = []; this.SHORT_SPACINGS[GraphConstant.SECONDLY] = 1000 * 1; this.SHORT_SPACINGS[GraphConstant.TWO_SECONDLY] = 1000 * 2; this.SHORT_SPACINGS[GraphConstant.FIVE_SECONDLY] = 1000 * 5; this.SHORT_SPACINGS[GraphConstant.TEN_SECONDLY] = 1000 * 10; this.SHORT_SPACINGS[GraphConstant.THIRTY_SECONDLY] = 1000 * 30; this.SHORT_SPACINGS[GraphConstant.MINUTELY] = 1000 * 60; this.SHORT_SPACINGS[GraphConstant.TWO_MINUTELY] = 1000 * 60 * 2; this.SHORT_SPACINGS[GraphConstant.FIVE_MINUTELY] = 1000 * 60 * 5; this.SHORT_SPACINGS[GraphConstant.TEN_MINUTELY] = 1000 * 60 * 10; this.SHORT_SPACINGS[GraphConstant.THIRTY_MINUTELY] = 1000 * 60 * 30; this.SHORT_SPACINGS[GraphConstant.HOURLY] = 1000 * 3600; this.SHORT_SPACINGS[GraphConstant.TWO_HOURLY] = 1000 * 3600 * 2; this.SHORT_SPACINGS[GraphConstant.SIX_HOURLY] = 1000 * 3600 * 6; this.SHORT_SPACINGS[GraphConstant.DAILY] = 1000 * 86400; this.SHORT_SPACINGS[GraphConstant.WEEKLY] = 1000 * 604800; this.SHORT_SPACINGS[GraphConstant.TWO_DAILY] = 1000 * 86400 * 2; } /** * update date format for legend and range-bar * @param format */ public setFormat = (format: string) => { this.dateformat = format; }; private numDateTicks = (start_time: number, end_time: number, granularity: number) => { const spacing = this.TICK_PLACEMENT[granularity].spacing; return Math.round(1.0 * (end_time - start_time) / spacing); }; private pickDateTickGranularity = (a: any, b: any, pixels: any, opts: any) => { let pixels_per_tick = opts('pixelsPerLabel'); for (let i = 0; i < 21; i++) { let num_ticks = this.numDateTicks(a, b, i); if (pixels / num_ticks >= pixels_per_tick) { return i; } } return -1; }; private zeropad = (x: number) => { if (x < 10) return "0" + x; else return "" + x; }; private getDateAxis = (start: any, end: any, granularity: any, opts: any, dygraph: Dygraph) => { // let formatter = /** @type{AxisLabelFormatter} */( opts("axisLabelFormatter")); let ticks = []; let t; if (granularity < GraphConstant.MONTHLY) { // Generate one tick mark for every fixed interval of time. let spacing = this.SHORT_SPACINGS[granularity]; // Find a time less than start_time which occurs on a "nice" time boundary // for this granularity. let g = spacing / 1000; let d = moment(start).tz(this.timezone ? this.timezone : moment.tz.guess()); d.millisecond(0); let x; if (g <= 60) { // seconds x = d.second(); d.second(x - x % g); } else { d.second(0); g /= 60; if (g <= 60) { // minutes x = d.minute(); d.minute(x - x % g); } else { d.minute(0); g /= 60; if (g <= 24) { // days x = d.hour(); d.hour(x - x % g); } else { d.hour(0); g /= 24; if (g == 7) { // one week d.startOf('week'); } } } } start = d.valueOf(); let start_offset_min = moment(start).tz(this.timezone ? this.timezone : moment.tz.guess()).utcOffset(); let check_dst = (spacing >= this.SHORT_SPACINGS[GraphConstant.TWO_HOURLY]); for (t = start; t <= end; t += spacing) { let d = moment(t).tz(this.timezone ? this.timezone : moment.tz.guess()); // console.info(check_dst , d.utcOffset() , start_offset_min); if (check_dst && d.utcOffset() != start_offset_min) { let delta_min = -(d.utcOffset() - start_offset_min); t += delta_min * 60 * 1000; d = moment(t).tz(this.timezone ? this.timezone : moment.tz.guess()); start_offset_min = d.utcOffset(); // Check whether we've backed into the previous timezone again. // This can happen during a "day light" transition. In this case, // it's best to skip this tick altogether (we may be shooting for a // non-existent time like the 2AM that's skipped) and go to the next // one. if (moment(t + spacing).tz(this.timezone ? this.timezone : moment.tz.guess()).utcOffset() != start_offset_min) { t += spacing; d = moment(t).tz(this.timezone ? this.timezone : moment.tz.guess()); start_offset_min = d.utcOffset(); } } ticks.push({ v: t, label: formatter(d, granularity, opts, dygraph) }); } } else { // Display a tick mark on the first of a set of months of each year. // Years get a tick mark iff y % year_mod == 0. This is useful for // displaying a tick mark once every 10 years, say, on long time scales. let months: number[] = []; let year_mod = 1; // e.g. to only print one point every 10 years. if (granularity == GraphConstant.MONTHLY) { months = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; } else if (granularity == GraphConstant.QUARTERLY) { months = [0, 3, 6, 9]; } else if (granularity == GraphConstant.BIANNUAL) { months = [0, 6]; } else if (granularity == GraphConstant.ANNUAL) { months = [0]; } else if (granularity == GraphConstant.DECADAL) { months = [0]; year_mod = 10; } else if (granularity == GraphConstant.CENTENNIAL) { months = [0]; year_mod = 100; } else { console.warn("Span of dates is too long"); } let start_year = moment(start).tz(this.timezone ? this.timezone : moment.tz.guess()).year(); let end_year = moment(end).tz(this.timezone ? this.timezone : moment.tz.guess()).year(); for (let i = start_year; i <= end_year; i++) { if (i % year_mod !== 0) continue; for (let j = 0; j < months.length; j++) { let dt = moment.tz(new Date(i, months[j], 1), this.timezone ? this.timezone : moment.tz.guess()); dt.year(i); t = dt.valueOf(); if (t < start || t > end) continue; ticks.push({ v: t, label: formatter(moment(t).tz(this.timezone ? this.timezone : moment.tz.guess()), granularity, opts, dygraph) }); } } } return ticks; }; DateTickerTZ = (a: any, b: any, pixels: any, opts: any, dygraph: Dygraph, vals: any) => { let granularity = this.pickDateTickGranularity(a, b, pixels, opts); if (granularity >= 0) { return this.getDateAxis(a, b, granularity, opts, dygraph); // use own function here } else { // this can happen if self.width_ is zero. return []; } }; /** * * legend formatter for multiple series * @param {any} data this data comes from graph * * @memberof Formatters */ legendForAllSeries = (data: any) => { const g = data.dygraph; if (g.getOption('showLabelsOnHighlight') !== true) return ''; if (data.x == null) { // This happens when there's no selection and {legend: 'always'} is set. return '<br>' + data.series.map(function (series: any) { return series.dashHTML + ' ' + series.labelHTML }).join('<br>'); } let html = moment.tz(data.x, this.timezone ? this.timezone : moment.tz.guess()).format(this.dateformat ? this.dateformat : 'lll z'); data.series.forEach(function (series: any) { if (!series.isVisible || series.label.indexOf('_markline') != -1) return; let labeledData = series.labelHTML + ': ' + (series.yHTML ? series.yHTML : ""); if (series.isHighlighted) { labeledData = '<b style="color:' + series.color + ';">' + labeledData + '</b>'; } html += '<br>' + series.dashHTML + ' ' + labeledData; }); return html; }; /** * * legend formatter for single series * @param {any} data this data comes from graph * * @memberof Formatters */ legendForSingleSeries = (data: any) => { const g = data.dygraph; if (g.getOption('showLabelsOnHighlight') !== true) return ''; if (data.x == null) { // This happens when there's no selection and {legend: 'always'} is set. return '<br>' + data.series.map(function (series: any) { return series.dashHTML + ' ' + series.labelHTML }).join('<br>'); } let html = moment.tz(data.x, this.timezone ? this.timezone : moment.tz.guess()).format(this.dateformat ? this.dateformat : 'lll z'); data.series.forEach(function (series: any) { if (!series.isVisible || series.label.indexOf('_markline') != -1) return; let labeledData = series.labelHTML + ': ' + (series.yHTML ? series.yHTML : ""); if (series.isHighlighted) { labeledData = '<b style="color:' + series.color + ';">' + labeledData + '</b>'; html += '<br>' + series.dashHTML + ' ' + labeledData; } }); return html; } /** *formatter for axis label * @param {number|date} d * @param {number} granularity * @param {function} opts * @param {Dygraph} dygraph * @returns {string} * @memberof Formatters */ axisLabel = (d: number | Date, granularity: number, opts?: (name: string) => any, dygraph?: Dygraph): any => { // don't put it into formatters.ts becault we need to timezone later let momentDatetime; if (d instanceof Date) { momentDatetime = moment.tz(d.getTime(), this.timezone ? this.timezone : moment.tz.guess()); } else { momentDatetime = moment.tz(d, this.timezone ? this.timezone : moment.tz.guess()); } let SHORT_MONTH_NAMES = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; let zeropad = (x: number) => { if (x < 10) return "0" + x; else return "" + x; }; let hmsString_ = (hh: number, mm: number, ss: number) => { let ret = zeropad(hh) + ":" + zeropad(mm); if (ss) { ret += ":" + zeropad(ss); } return ret; }; if (granularity >= Dygraph.DECADAL) { return '' + momentDatetime.year(); } else if (granularity >= Dygraph.MONTHLY) { return SHORT_MONTH_NAMES[momentDatetime.month()] + '&#160;' + momentDatetime.year(); } else { let frac = momentDatetime.hours() * 3600 + momentDatetime.minutes() * 60 + momentDatetime.seconds() + 1e-3 * momentDatetime.milliseconds(); if (frac === 0 || granularity >= Dygraph.DAILY) { // e.g. '21 Jan' (%d%b) return zeropad(momentDatetime.date()) + '&#160;' + SHORT_MONTH_NAMES[momentDatetime.month()]; } else { return hmsString_(momentDatetime.hours(), momentDatetime.minutes(), momentDatetime.seconds()); } } } }