widget-common-container 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/.angular/cache/19.2.26/ng-packagr/0997d7866dbb7999756bcd6dce071e827244865f4ff149c26d049d1515686b01 +1 -0
  2. package/.angular/cache/19.2.26/ng-packagr/882519e10fb587011272af89a34932495c0e2cc97d0c030536d1033ee55ce561 +1 -0
  3. package/.angular/cache/19.2.26/ng-packagr/8b15b546ec32b4e82b20945f62d23ba911050bdc6a4a60d34875eea6fe236635 +1 -0
  4. package/.angular/cache/19.2.26/ng-packagr/9711df1afdc00e2ac901d2b22d9559a220e74d16c0bf6d3917e410c565aed496 +1 -0
  5. package/.angular/cache/19.2.26/ng-packagr/tsbuildinfo/inspark-widget-common.tsbuildinfo +1 -0
  6. package/.editorconfig +13 -0
  7. package/.eslintrc.json +28 -0
  8. package/README.md +27 -0
  9. package/angular.json +46 -0
  10. package/e2e/protractor.conf.js +28 -0
  11. package/e2e/src/app.e2e-spec.ts +14 -0
  12. package/e2e/src/app.po.ts +11 -0
  13. package/e2e/tsconfig.e2e.json +13 -0
  14. package/package.json +75 -0
  15. package/projects/widget-common/copy.json +8 -0
  16. package/projects/widget-common/karma.conf.js +31 -0
  17. package/projects/widget-common/ng-package.json +7 -0
  18. package/projects/widget-common/package-lock.json +78 -0
  19. package/projects/widget-common/package.json +23 -0
  20. package/projects/widget-common/src/assets/error.svg +6 -0
  21. package/projects/widget-common/src/assets/falsevalue.svg +6 -0
  22. package/projects/widget-common/src/assets/icon.svg +6 -0
  23. package/projects/widget-common/src/assets/none.svg +6 -0
  24. package/projects/widget-common/src/assets/success.svg +6 -0
  25. package/projects/widget-common/src/assets/warning.svg +6 -0
  26. package/projects/widget-common/src/lib/common.ts +23 -0
  27. package/projects/widget-common/src/lib/communication.service.ts +59 -0
  28. package/projects/widget-common/src/lib/echart/echart.component.html +2 -0
  29. package/projects/widget-common/src/lib/echart/echart.component.scss +40 -0
  30. package/projects/widget-common/src/lib/echart/echart.component.ts +703 -0
  31. package/projects/widget-common/src/lib/echart/en.ts +156 -0
  32. package/projects/widget-common/src/lib/echart/ru.ts +156 -0
  33. package/projects/widget-common/src/lib/forge/ParamAssign.ts +187 -0
  34. package/projects/widget-common/src/lib/forge/Script.service.ts +86 -0
  35. package/projects/widget-common/src/lib/forge/extenstion.ts +263 -0
  36. package/projects/widget-common/src/lib/forge/forge.component.html +2 -0
  37. package/projects/widget-common/src/lib/forge/forge.component.scss +6 -0
  38. package/projects/widget-common/src/lib/forge/forge.component.ts +184 -0
  39. package/projects/widget-common/src/lib/forge/forge.interface.ts +4 -0
  40. package/projects/widget-common/src/lib/loader.ts +19 -0
  41. package/projects/widget-common/src/lib/pie-chart/pie-chart.component.html +1 -0
  42. package/projects/widget-common/src/lib/pie-chart/pie-chart.component.scss +3 -0
  43. package/projects/widget-common/src/lib/pie-chart/pie-chart.component.ts +252 -0
  44. package/projects/widget-common/src/lib/shared.module.ts +40 -0
  45. package/projects/widget-common/src/lib/sprintf.ts +212 -0
  46. package/projects/widget-common/src/lib/widget-container/widget-container.component.ts +53 -0
  47. package/projects/widget-common/src/lib/widget.api.ts +24 -0
  48. package/projects/widget-common/src/lib/widget.component.ts +87 -0
  49. package/projects/widget-common/src/lib/widget.dialog.ts +14 -0
  50. package/projects/widget-common/src/lib/widget.generator.ts +1532 -0
  51. package/projects/widget-common/src/lib/widget.interface.ts +871 -0
  52. package/projects/widget-common/src/lib/widget.utils.ts +634 -0
  53. package/projects/widget-common/src/public_api.ts +9 -0
  54. package/projects/widget-common/src/scss/main.scss +24 -0
  55. package/projects/widget-common/src/test.ts +22 -0
  56. package/projects/widget-common/tsconfig.lib.json +38 -0
  57. package/projects/widget-common/tsconfig.lib.prod.json +6 -0
  58. package/projects/widget-common/tsconfig.spec.json +17 -0
  59. package/projects/widget-common/tslint.json +17 -0
  60. package/projects/widget-common/versions/full/copy.json +15 -0
  61. package/projects/widget-common/versions/full/package.json +7 -0
  62. package/projects/widget-common/versions/full/public_api.ts +19 -0
  63. package/projects/widget-common/versions/interfaces/copy.json +15 -0
  64. package/projects/widget-common/versions/interfaces/package.json +7 -0
  65. package/projects/widget-common/versions/interfaces/public_api.ts +9 -0
  66. package/src/app/app.component.html +20 -0
  67. package/src/app/app.component.scss +0 -0
  68. package/src/app/app.component.spec.ts +31 -0
  69. package/src/app/app.component.ts +10 -0
  70. package/src/app/app.module.ts +16 -0
  71. package/src/assets/.gitkeep +0 -0
  72. package/src/browserslist +11 -0
  73. package/src/environments/environment.prod.ts +3 -0
  74. package/src/environments/environment.ts +16 -0
  75. package/src/favicon.ico +0 -0
  76. package/src/index.html +14 -0
  77. package/src/karma.conf.js +31 -0
  78. package/src/main.ts +12 -0
  79. package/src/polyfills.ts +80 -0
  80. package/src/styles.scss +1 -0
  81. package/src/test.ts +20 -0
  82. package/src/tsconfig.app.json +11 -0
  83. package/src/tsconfig.spec.json +18 -0
  84. package/src/tslint.json +17 -0
  85. package/tsconfig.json +34 -0
  86. package/tslint.json +131 -0
  87. package/update_full.js +27 -0
@@ -0,0 +1,703 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ ChangeDetectorRef,
4
+ Component,
5
+ Input,
6
+ LOCALE_ID,
7
+ NgModule,
8
+ OnChanges,
9
+ OnDestroy,
10
+ OnInit,
11
+ ViewEncapsulation
12
+ } from '@angular/core';
13
+ import {
14
+ ChartTypes, ChartViews,
15
+ ItemSeries,
16
+ ParamConfigSeries,
17
+ SeriesCandleValue,
18
+ SeriesDuration,
19
+ SeriesLineValue,
20
+ SiteTheme
21
+ } from '../widget.interface';
22
+ import {CommonModule, DatePipe} from '@angular/common';
23
+ import {NgxEchartsModule} from 'ngx-echarts';
24
+ import {EChartsOption, SeriesOption} from 'echarts/types/dist/echarts';
25
+ import * as echarts from 'echarts';
26
+ import {PieChartComponent} from '../pie-chart/pie-chart.component';
27
+ import langRU from './ru';
28
+ import langEN from './en';
29
+
30
+ echarts.registerLocale('RU', langRU);
31
+ echarts.registerLocale('EN', langEN);
32
+
33
+ const FULL_DAY = 86400000;
34
+ const GRADIENT = [
35
+ new echarts.graphic.LinearGradient(0, 0, 0, 1, [
36
+ {offset: 0, color: 'rgb(128, 255, 165)'},
37
+ {offset: 1, color: 'rgb(1, 191, 236)'}
38
+ ]),
39
+ new echarts.graphic.LinearGradient(0, 0, 0, 1, [
40
+ {offset: 0, color: 'rgb(0, 221, 255)'},
41
+ {offset: 1, color: 'rgb(77, 119, 255)'}
42
+ ]),
43
+ new echarts.graphic.LinearGradient(0, 0, 0, 1, [
44
+ {offset: 0, color: 'rgb(55, 162, 255)'},
45
+ {offset: 1, color: 'rgb(116, 21, 219)'}
46
+ ]),
47
+ new echarts.graphic.LinearGradient(0, 0, 0, 1, [
48
+ {offset: 0, color: 'rgb(255, 0, 135)'},
49
+ {offset: 1, color: 'rgb(135, 0, 157)'}
50
+ ]),
51
+ new echarts.graphic.LinearGradient(0, 0, 0, 1, [
52
+ {offset: 0, color: 'rgb(255, 191, 0)'},
53
+ {offset: 1, color: 'rgb(224, 62, 76)'}
54
+ ])
55
+ ];
56
+
57
+ const getGradient = (seed: number) => GRADIENT[seed % GRADIENT.length];
58
+
59
+ const cutName = (name: string, length: number) => {
60
+ if (name.length > length + 3) {
61
+ return name.substr(0, length) + '...';
62
+ }
63
+ return name;
64
+ };
65
+
66
+ const BackGround = {
67
+ [SiteTheme.light]: '#FFFFFF',
68
+ [SiteTheme.dark]: '#494C55'
69
+ };
70
+
71
+ const RedZone = {
72
+ [SiteTheme.light]: 'rgba(255, 173, 177, 0.4)',
73
+ [SiteTheme.dark]: 'rgba(145, 30, 49, 0.2)'
74
+ };
75
+
76
+ export interface SimpleData {
77
+ title?: string;
78
+ data: number[];
79
+ }
80
+
81
+ @Component({
82
+ selector: 'app-echart',
83
+ templateUrl: './echart.component.html',
84
+ styleUrls: ['./echart.component.scss'],
85
+ changeDetection: ChangeDetectionStrategy.OnPush,
86
+ encapsulation: ViewEncapsulation.Emulated,
87
+ })
88
+ export class EchartComponent implements OnInit, OnChanges, OnDestroy {
89
+
90
+ @Input() values: ItemSeries[];
91
+ @Input() simpleData: SimpleData[];
92
+ @Input() config: ParamConfigSeries; // ожидается поле duration и count (count трактуем как shift)
93
+ @Input() locale = 'en';
94
+ @Input() noDataMessage = 'No data';
95
+ @Input() width = 250;
96
+ @Input() height = 250;
97
+ @Input() margin = {top: 70, right: 20, bottom: 60, left: 20};
98
+ @Input() theme: SiteTheme;
99
+
100
+ data: any[] = [];
101
+ options: EChartsOption = {
102
+ backgroundColor: 'transparent',
103
+ tooltip: {
104
+ appendToBody: true,
105
+ renderMode: 'html',
106
+ trigger: 'axis',
107
+ axisPointer: {type: 'cross'},
108
+ className: 'label-formatter',
109
+ formatter: (params: any[]) => {
110
+ let res = 'No data';
111
+ if (params && params[0]) {
112
+ res = `<div class="title">${new DatePipe(this.getLocale(this.locale)).transform(params[0].value[0], 'd MMM y, HH:mm')}</div><table>`;
113
+ res += params.map((val: any) => {
114
+ if (typeof val.value[val.value.length - 1] === 'number') {
115
+ return `<tr><td class="label">${val.marker} ${cutName(val.seriesName, 20)}</td>
116
+ <td><div class="value"><b>${this.roundData(val.value[1])}</b></div></td></tr>`;
117
+ } else {
118
+ const unit = val.value[val.value.length - 1].device.param.measure.unit ? val.value[val.value.length - 1].device.param.measure.unit : '';
119
+ if (val.seriesType === 'candlestick') {
120
+ return `<tr><td class="label">${val.marker} ${cutName(val.seriesName, 20)}</td>
121
+ <td><div class="value"><div><span>ОТКР:</span> ${this.roundData(val.value[1])}</div><div><span>ЗАКР:</span> ${this.roundData(val.value[2])}</div><div><span>МИН:</span> ${this.roundData(val.value[3])}</div><div><span>МАКС:</span> ${this.roundData(val.value[4])}</div></div></td></tr>`;
122
+ } else {
123
+ return `<tr><td class="label">${val.marker} ${cutName(val.seriesName, 20)}</td>
124
+ <td><div class="value"><b>${this.roundData(val.value[1])}</b> ${cutName(unit, 10)}</div></td></tr>`;
125
+ }
126
+ }
127
+ }).join('') + '</table>';
128
+ }
129
+ return res;
130
+ }
131
+ },
132
+ xAxis: {type: 'time', boundaryGap: false},
133
+ yAxis: {type: 'value'},
134
+ toolbox: {
135
+ right: '20px',
136
+ showTitle: false,
137
+ feature: {},
138
+ tooltip: {
139
+ show: true,
140
+ formatter: function (param) {
141
+ return '<div>' + param.title + '</div>';
142
+ },
143
+ backgroundColor: '#222',
144
+ extraCssText: 'color: #fff'
145
+ }
146
+ },
147
+ grid: {
148
+ left: `${this.margin.left}px`,
149
+ right: `${this.margin.right}px`,
150
+ bottom: `${this.margin.bottom}px`,
151
+ top: `${this.margin.top}px`,
152
+ containLabel: true
153
+ },
154
+ series: [],
155
+ };
156
+
157
+ uniqTitles: string[] = [];
158
+ initOpts: any = {locale: this.locale.toLocaleUpperCase()};
159
+
160
+ darkColor = [
161
+ {'color': '#00FF7F'}, {'color': '#00E0E0'}, {'color': '#FF00FF'}, {'color': '#B2CCE5'}, {'color': '#F1F227'},
162
+ {'color': '#FDE3A7'}, {'color': '#F64747'}, {'color': '#8BB82D'}, {'color': '#1E90FF'}, {'color': '#D2527F'},
163
+ {'color': '#91A6BA'}
164
+ ];
165
+
166
+ lightColor = [
167
+ {'color': '#007A4B'}, {'color': '#3477DB'}, {'color': '#D252B2'}, {'color': '#708090'}, {'color': '#AF851A'},
168
+ {'color': '#BB671C'}, {'color': '#E00000'}, {'color': '#005031'}, {'color': '#34415E'}, {'color': '#58007E'},
169
+ {'color': '#2E343B'}
170
+ ];
171
+
172
+ display = true;
173
+ echartsInstance: any;
174
+
175
+ constructor(private cdr: ChangeDetectorRef) {
176
+ }
177
+
178
+ ngOnChanges(changes: any) {
179
+ if (changes.locale) {
180
+ this.initOpts = {locale: this.locale};
181
+ this.display = false;
182
+ this.cdr.detectChanges();
183
+ setTimeout(() => {
184
+ this.display = true;
185
+ this.cdr.detectChanges();
186
+ });
187
+ }
188
+ if (this.echartsInstance && (changes.width || changes.height)) {
189
+ this.resizeChart();
190
+ }
191
+ if (changes.config || changes.values || changes.theme || changes.simpleData) {
192
+ this.data = [];
193
+ if (this.config && (this.values || this.simpleData)) {
194
+ if (this.values && this.values.length && this.values[0].data) {
195
+ this.updateData(this.config, this.values);
196
+ }
197
+ if (this.simpleData && this.simpleData.length) {
198
+ this.updateArrayData(this.config, this.simpleData);
199
+ }
200
+ }
201
+ }
202
+ }
203
+
204
+ ngOnInit() {
205
+ this.initOpts = {locale: this.locale};
206
+ }
207
+
208
+ ngOnDestroy() {}
209
+
210
+ onChartInit(ec: any) {
211
+ this.echartsInstance = ec;
212
+ }
213
+
214
+ getLocale(locale: string) {
215
+ return locale.toLowerCase() === 'ru' ? 'ru-RU' : 'en-US';
216
+ }
217
+
218
+ resizeChart() {
219
+ if (this.echartsInstance) {
220
+ this.echartsInstance.resize();
221
+ }
222
+ }
223
+
224
+ roundData(value: number, numbers = 2) {
225
+ return Math.round(value * Math.pow(10, numbers)) / Math.pow(10, numbers);
226
+ }
227
+
228
+ updateArrayData(config: ParamConfigSeries, values: SimpleData[]) {
229
+ const res: SeriesOption[] = [];
230
+ const xAxis: any = {
231
+ type: 'value',
232
+ boundaryGap: true,
233
+ max: values[0].data.length,
234
+ axisLabel: {
235
+ formatter: function (value: number) {
236
+ return value / 1000 + 'kGhz';
237
+ }
238
+ }
239
+ };
240
+ const legend: string[] = values.map((val) => val.title || '');
241
+ const yAxis: any = {type: 'value'};
242
+
243
+ values.forEach((value, ind) => {
244
+ res.push({
245
+ type: 'line',
246
+ name: value.title,
247
+ lineStyle: {color: this.getChartColor(ind)},
248
+ itemStyle: {color: this.getChartColor(ind)},
249
+ data: value.data.map((val, i) => [i, val]),
250
+ });
251
+ });
252
+
253
+ const magicType = {type: ['bar', 'line']};
254
+
255
+ this.options = {
256
+ ...this.options,
257
+ xAxis,
258
+ yAxis,
259
+ legend: {data: legend, bottom: `8px`, show: true, type: 'scroll'},
260
+ series: res,
261
+ grid: {
262
+ ...this.options.grid,
263
+ left: this.margin.left + 30,
264
+ right: this.margin.right + 30,
265
+ },
266
+ toolbox: {
267
+ ...this.options.toolbox,
268
+ feature: {
269
+ ...(this.options.toolbox as any).feature,
270
+ saveAsImage: {show: true, backgroundColor: BackGround[this.theme]},
271
+ magicType,
272
+ }
273
+ }
274
+ };
275
+ }
276
+
277
+ updateData(config: ParamConfigSeries, values: ItemSeries[]) {
278
+ const res: SeriesOption[] = [];
279
+ const legend: string[] = [];
280
+ const xAxis: any = {
281
+ type: 'time',
282
+ boundaryGap: false,
283
+ axisLabel: {formatter: '{d} {MMM}\n{HH}:{mm}'}
284
+ };
285
+ let yAxis: any[] = [];
286
+ this.uniqTitles = [];
287
+
288
+ values = this.updateValueTimeZone(values);
289
+ const first = values[0];
290
+
291
+ // shift трактуем из config.count
292
+ const shift = Math.max(0, Number(config.count || 0));
293
+ const tz = first?.device?.object?.timezone ?? 0;
294
+
295
+ // Конечная точка всегда "сейчас" с учётом TZ
296
+ const endNow = this.nowWithTZ(tz);
297
+
298
+ // Выбор периода
299
+ let period: { startTime: number; endTime: number; text: string };
300
+ switch (config.duration) {
301
+ case SeriesDuration.day:
302
+ period = this.getDay(shift, tz, endNow);
303
+ break;
304
+ case SeriesDuration.week:
305
+ period = this.getWeek(shift, tz, endNow);
306
+ break;
307
+ case SeriesDuration.month:
308
+ period = this.getMonth(shift, tz, endNow);
309
+ break;
310
+ default:
311
+ period = this.getDay(0, tz, endNow);
312
+ }
313
+
314
+ if (config.generator) {
315
+ const times = this.getMaxMinTimeline(first);
316
+ xAxis.min = times.min - FULL_DAY;
317
+ xAxis.max = times.max + FULL_DAY;
318
+ } else {
319
+ xAxis.min = period.startTime;
320
+ xAxis.max = period.endTime;
321
+ }
322
+ if (config.viewtype !== ChartViews.stackedAreaChart) {
323
+ const diff = Math.round((xAxis.max - xAxis.min) * 0.02);
324
+ xAxis.min = xAxis.min - diff;
325
+ xAxis.max = xAxis.max + diff;
326
+ }
327
+
328
+ const measures = values.map(v => v.device.param.measure.id).filter((v, i, s) => s.indexOf(v) === i);
329
+ if (measures.length > 1 && config.viewtype !== ChartViews.stackedAreaChart) {
330
+ yAxis = values.map((v, ind) => {
331
+ const maxmin = this.getMaxMin(v);
332
+ const diff = Math.round((maxmin.max - maxmin.min) * 0.1);
333
+ return {
334
+ type: 'value',
335
+ min: Math.round(maxmin.min - diff),
336
+ max: Math.round(maxmin.max + diff),
337
+ position: ind % 2 === 0 ? 'left' : 'right',
338
+ offset: 60 * Math.floor(ind / 2),
339
+ axisLine: {show: true, lineStyle: {color: this.getChartColor(ind)}},
340
+ splitLine: {show: false},
341
+ axisLabel: {formatter: '{value} ', color: this.getChartColor(ind)}
342
+ };
343
+ });
344
+ } else {
345
+ yAxis.push({
346
+ type: 'value',
347
+ splitLine: {lineStyle: {color: ['#aaa', '#aaa']}},
348
+ axisLine: {show: true, lineStyle: {color: this.getChartColor(0)}},
349
+ axisLabel: {formatter: '{value} ', color: this.getChartColor(0)}
350
+ });
351
+ }
352
+
353
+ if (config.viewtype === ChartViews.histogramChart) {
354
+ values.forEach((value, ind) => {
355
+ const title = this.findUniqName(value.title) + ' ' + (value.device.param.measure.unit ? value.device.param.measure.unit : '');
356
+ legend.push(title);
357
+ res.push({
358
+ type: 'bar',
359
+ data: value.data.map((val: any) => [val.timestmp, val.value, value]),
360
+ itemStyle: {color: this.getChartColor(ind)},
361
+ name: title,
362
+ yAxisIndex: yAxis.length > 1 ? ind : 0,
363
+ });
364
+ });
365
+ } else if (config.viewtype === ChartViews.candlestickBarChart) {
366
+ values.forEach((value, ind) => {
367
+ const title = this.findUniqName(value.title) + ' ' + (value.device.param.measure.unit ? value.device.param.measure.unit : '');
368
+ legend.push(title);
369
+ res.push({
370
+ type: 'candlestick',
371
+ data: value.data.map((val: any) => [val.timestmp, val.open, val.close, val.low, val.high, value]),
372
+ name: title,
373
+ yAxisIndex: yAxis.length > 1 ? ind : 0,
374
+ });
375
+ });
376
+ } else {
377
+ values.forEach((value, ind) => {
378
+ const title = this.findUniqName(value.title) + ' ' + (value.device.param.measure.unit ? value.device.param.measure.unit : '');
379
+ legend.push(title);
380
+
381
+ if (config.viewtype === ChartViews.stackedAreaChart) {
382
+ res.push({
383
+ type: 'line',
384
+ stack: 'Total',
385
+ smooth: true,
386
+ name: title,
387
+ yAxisIndex: yAxis.length > 1 ? ind : 0,
388
+ data: value.data.map((val: any) => [val.timestmp, val.value, value]),
389
+ lineStyle: {width: 0},
390
+ showSymbol: false,
391
+ emphasis: {focus: 'series'},
392
+ itemStyle: {color: getGradient(ind)},
393
+ areaStyle: {opacity: 0.8, color: getGradient(ind)},
394
+ });
395
+ } else {
396
+ let markArea: any;
397
+ if (config.viewtype === ChartViews.lineWeekend) {
398
+ const data = [];
399
+ const days = Math.ceil((xAxis.max - xAxis.min) / FULL_DAY);
400
+ if (days > 1) {
401
+ for (let i = 0; i < days; i++) {
402
+ const date = (new Date(xAxis.min + i * FULL_DAY));
403
+ this.modifyTime(date, 0);
404
+ const day = date.getUTCDay();
405
+ if (day === 0) {
406
+ const d = (new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0));
407
+ data.push([{xAxis: d.getTime()}, {xAxis: d.getTime() + FULL_DAY}]);
408
+ }
409
+ if (day === 6) {
410
+ const d = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
411
+ data.push([{xAxis: d.getTime()}, {xAxis: d.getTime() + FULL_DAY * 2}]);
412
+ i++;
413
+ }
414
+ }
415
+ }
416
+ markArea = {itemStyle: {color: RedZone[this.theme]}, data};
417
+ }
418
+
419
+ res.push({
420
+ type: 'line',
421
+ smooth: true,
422
+ name: title,
423
+ markArea,
424
+ yAxisIndex: yAxis.length > 1 ? ind : 0,
425
+ lineStyle: {color: this.getChartColor(ind)},
426
+ itemStyle: {color: this.getChartColor(ind)},
427
+ data: value.data.map((val: any) => [val.timestmp, val.value, value]),
428
+ });
429
+ }
430
+ });
431
+ }
432
+
433
+ const magicType = config.viewtype === ChartViews.candlestickBarChart ? {} : {
434
+ type: yAxis.length === 1 ? ['bar', 'stack', 'line'] : ['bar', 'line'],
435
+ };
436
+
437
+ this.options = {
438
+ ...this.options,
439
+ xAxis,
440
+ yAxis,
441
+ legend: {data: legend, bottom: `8px`, show: true, type: 'scroll'},
442
+ series: res,
443
+ grid: {
444
+ ...this.options.grid,
445
+ left: this.margin.left + Math.floor(yAxis.length / 2) * 30,
446
+ right: this.margin.right + Math.floor(yAxis.length / 2) * 30,
447
+ },
448
+ toolbox: {
449
+ ...this.options.toolbox,
450
+ feature: {
451
+ ...(this.options.toolbox as any).feature,
452
+ saveAsImage: {show: true, backgroundColor: BackGround[this.theme]},
453
+ magicType,
454
+ }
455
+ }
456
+ };
457
+ }
458
+
459
+ findUniqName(title: string, ind = 0): string {
460
+ if (ind === 0) {
461
+ if (this.uniqTitles.indexOf(title) !== -1) {
462
+ return this.findUniqName(title, ind + 1);
463
+ }
464
+ this.uniqTitles.push(title);
465
+ return title;
466
+ } else {
467
+ const newTitle = `${title} (${ind})`;
468
+ if (this.uniqTitles.indexOf(newTitle) !== -1) {
469
+ return this.findUniqName(title, ind + 1);
470
+ }
471
+ this.uniqTitles.push(newTitle);
472
+ return newTitle;
473
+ }
474
+ }
475
+
476
+ getMaxMinSimple(values: SimpleData['data']) {
477
+ let max = Number.MIN_VALUE, min = Number.MAX_VALUE;
478
+ values.forEach((value) => {
479
+ if (value > max) {
480
+ max = value;
481
+ }
482
+ if (value < min) {
483
+ min = value;
484
+ }
485
+ });
486
+ return {max, min};
487
+ }
488
+
489
+ getMaxMin(values: ItemSeries) {
490
+ let max = Number.MIN_VALUE, min = Number.MAX_VALUE;
491
+ if (this.config.charttype === ChartTypes.candlestickBarChart) {
492
+ values.data.forEach((val: SeriesCandleValue) => {
493
+ if (val.high > max) {
494
+ max = val.high;
495
+ }
496
+ if (val.low < min) {
497
+ min = val.low;
498
+ }
499
+ });
500
+ } else {
501
+ values.data.forEach((val: SeriesLineValue) => {
502
+ if (val.value > max) {
503
+ max = val.value;
504
+ }
505
+ if (val.value < min) {
506
+ min = val.value;
507
+ }
508
+ });
509
+ }
510
+ return {max, min};
511
+ }
512
+
513
+ updateValueTimeZone(values: ItemSeries[]): ItemSeries[] {
514
+ return values.map(value => {
515
+ if (value.data) {
516
+ return {
517
+ ...value,
518
+ data: value.data.map((val: SeriesLineValue) => {
519
+ const date = new Date(val.timestmp);
520
+ this.modifyTime(date, 0);
521
+ return {value: (val as any).value ?? (val as any).close ?? 0, timestmp: date.getTime()};
522
+ })
523
+ };
524
+ }
525
+ return value;
526
+ });
527
+ }
528
+
529
+ getMaxMinTimeline(values: ItemSeries) {
530
+ let max = Number.MIN_VALUE, min = Number.MAX_VALUE;
531
+ if (values.data) {
532
+ values.data.forEach((val: SeriesLineValue) => {
533
+ if (val.timestmp > max) {
534
+ max = val.timestmp;
535
+ }
536
+ if (val.timestmp < min) {
537
+ min = val.timestmp;
538
+ }
539
+ });
540
+ }
541
+ return {max, min};
542
+ }
543
+
544
+ // ===== ВРЕМЯ И ПЕРИОДЫ С УЧЁТОМ TZ =====
545
+
546
+ // "Сейчас" с учётом таймзоны объекта (timezone в часах)
547
+ private nowWithTZ(timezone: number): Date {
548
+ const now = new Date();
549
+ const shiftMs = (now.getTimezoneOffset() - timezone * 60) * 60000;
550
+ return new Date(now.getTime() + shiftMs);
551
+ }
552
+
553
+ private startOfDayTZ(d: Date): Date {
554
+ const x = new Date(d.getTime());
555
+ x.setHours(0, 0, 0, 0);
556
+ return x;
557
+ }
558
+
559
+ // Неделя начинается в понедельник
560
+ private startOfWeekTZ(d: Date): Date {
561
+ const x = new Date(d.getTime());
562
+ const dow = x.getDay(); // 0..6, где 1 — понедельник при сдвиге
563
+ const mondayOffset = (dow + 6) % 7; // 0 для понедельника
564
+ x.setDate(x.getDate() - mondayOffset);
565
+ x.setHours(0, 0, 0, 0);
566
+ return x;
567
+ }
568
+
569
+ private startOfMonthTZ(d: Date): Date {
570
+ const x = new Date(d.getTime());
571
+ x.setDate(1);
572
+ x.setHours(0, 0, 0, 0);
573
+ return x;
574
+ }
575
+
576
+ // day:
577
+ // shift=0 -> от начала текущего дня до "сейчас"
578
+ // shift>=1 -> от (сейчас - shift*дней) до "сейчас"
579
+ getDay(shift: number, timezone: number, nowTZ: Date) {
580
+ const end = new Date(nowTZ.getTime());
581
+ let start: Date;
582
+ if (shift === 0) {
583
+ start = this.startOfDayTZ(end);
584
+ } else {
585
+ start = new Date(end.getTime() - shift * FULL_DAY);
586
+ }
587
+ return {
588
+ startTime: start.getTime(),
589
+ endTime: end.getTime(),
590
+ text: ''
591
+ };
592
+ }
593
+
594
+ // week:
595
+ // всегда от начала недели; shift=0 текущая неделя, shift>=1 — на shift недель назад
596
+ getWeek(shift: number, timezone: number, nowTZ: Date) {
597
+ const end = new Date(nowTZ.getTime());
598
+ let start = this.startOfWeekTZ(end);
599
+ if (shift > 0) {
600
+ start = new Date(start.getTime() - shift * 7 * FULL_DAY);
601
+ }
602
+ return {
603
+ startTime: start.getTime(),
604
+ endTime: end.getTime(),
605
+ text: ''
606
+ };
607
+ }
608
+
609
+ // month:
610
+ // всегда от начала месяца; shift=0 текущий, shift>=1 — на shift месяцев назад
611
+ getMonth(shift: number, timezone: number, nowTZ: Date) {
612
+ const end = new Date(nowTZ.getTime());
613
+ let start = this.startOfMonthTZ(end);
614
+ if (shift > 0) {
615
+ const s = new Date(start.getTime());
616
+ s.setMonth(s.getMonth() - shift);
617
+ start = s;
618
+ }
619
+ return {
620
+ startTime: start.getTime(),
621
+ endTime: end.getTime(),
622
+ text: ''
623
+ };
624
+ }
625
+
626
+ // сохраняем для совместимости (используется в markArea)
627
+ modifyTime(date: Date, timezone: number) {
628
+ return date.setTime(date.getTime() + (date.getTimezoneOffset() - timezone * 60) * 60000);
629
+ }
630
+
631
+ getChartColor(seed: number) {
632
+ if (this.theme === SiteTheme.dark) {
633
+ return this.darkColor[seed % this.darkColor.length].color;
634
+ }
635
+ return this.lightColor[seed % this.lightColor.length].color;
636
+ }
637
+ }
638
+
639
+ @NgModule({
640
+ declarations: [
641
+ EchartComponent,
642
+ PieChartComponent,
643
+ ],
644
+ imports: [
645
+ CommonModule,
646
+ NgxEchartsModule
647
+ ],
648
+ exports: [
649
+ EchartComponent,
650
+ PieChartComponent,
651
+ ],
652
+ providers: [
653
+ {provide: LOCALE_ID, useValue: 'en-US'},
654
+ {provide: LOCALE_ID, useValue: 'ru-RU'},
655
+ ],
656
+ entryComponents: [EchartComponent, PieChartComponent]
657
+ })
658
+ export class EChartComponentModule {
659
+ }
660
+
661
+ function makeTimeScaleOption(data: any[], opt?: { useUTC?: boolean; tooltipFormatter?: (p: any) => string }) {
662
+ opt = opt || {};
663
+ const useUTC = opt.useUTC;
664
+ const tooltipFormatter = opt.tooltipFormatter;
665
+ return {
666
+ useUTC: useUTC,
667
+ tooltip: {
668
+ trigger: 'axis',
669
+ formatter: tooltipFormatter
670
+ },
671
+ xAxis: [{
672
+ type: 'time',
673
+ splitNumber: 7,
674
+ axisLabel: {
675
+ formatter: function (tick: number) {
676
+ return echarts.time.format(
677
+ tick,
678
+ '{yyyy}-{MM}-{dd} {HH}:{mm}:{ss}',
679
+ useUTC
680
+ );
681
+ },
682
+ rotate: 10
683
+ },
684
+ splitLine: {show: false}
685
+ }],
686
+ yAxis: [{type: 'value', splitLine: {show: false}}],
687
+ series: [{
688
+ type: 'line',
689
+ smooth: true,
690
+ data: data,
691
+ label: {
692
+ show: true,
693
+ formatter: function (params: any) {
694
+ return echarts.time.format(
695
+ params.value[0],
696
+ '{yyyy}-{MM}-{dd} {HH}:{mm}:{ss}',
697
+ useUTC
698
+ );
699
+ }
700
+ }
701
+ }]
702
+ };
703
+ }