Skip to content

Commit

Permalink
SNRGY-3064 chart widget_ improve drill down color generation
Browse files Browse the repository at this point in the history
  • Loading branch information
franzmueller committed Dec 12, 2024
1 parent 94b2409 commit 980904b
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 108 deletions.
10 changes: 10 additions & 0 deletions src/app/core/services/util.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,13 @@ export function hashCode(s: string): number {
}
return hash;
}

export async function digestMessage(message: string, algorithm = 'SHA-256'): Promise<string> {
const msgUint8 = new TextEncoder().encode(message); // encode as (utf-8) Uint8Array
const hashBuffer = await window.crypto.subtle.digest(algorithm, msgUint8); // hash the message
const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array
const hashHex = hashArray
.map((b) => b.toString(16).padStart(2, '0'))
.join(''); // convert bytes to hex string
return hashHex;
}
218 changes: 110 additions & 108 deletions src/app/widgets/charts/export/shared/charts-export.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import { Injectable } from '@angular/core';
import { forkJoin, Observable, of } from 'rxjs';
import { forkJoin, Observable, of, from } from 'rxjs';
import { ChartsModel } from '../../shared/charts.model';
import { ElementSizeService } from '../../../../core/services/element-size.service';
import { ErrorHandlerService } from '../../../../core/services/error-handler.service';
Expand All @@ -42,11 +42,11 @@ import {
QueriesRequestTimeModel,
QueriesRequestV2ElementTimescaleModel,
} from '../../../shared/export-data.model';
import { catchError, concatMap, map } from 'rxjs/operators';
import { catchError, concatMap, map, mergeMap } from 'rxjs/operators';
import { environment } from '../../../../../environments/environment';
import { DeviceInstancesTotalModel, DeviceInstancesWithDeviceTypeTotalModel, DeviceInstanceWithDeviceTypeModel } from 'src/app/modules/devices/device-instances/shared/device-instances.model';
import { DeviceInstancesWithDeviceTypeTotalModel, DeviceInstanceWithDeviceTypeModel } from 'src/app/modules/devices/device-instances/shared/device-instances.model';
import { DeviceInstancesService } from 'src/app/modules/devices/device-instances/shared/device-instances.service';
import { hashCode } from 'src/app/core/services/util.service';
import { digestMessage } from 'src/app/core/services/util.service';

const customColor = '#4484ce'; // /* cc */

Expand Down Expand Up @@ -214,73 +214,73 @@ export class ChartsExportService {
const metadataElem = { exportId: values[elementIndex].exportId, deviceId: values[elementIndex].deviceId, serviceId: values[elementIndex].serviceId, columnName: columnNames !== undefined && columnNames.length === 1 ? columnNames[0] : undefined };
if (threeD !== null) {
switch ((properties.vAxes || [])[timescaleResultMapper[values[elementIndex].requestIndex]].deviceGroupMergingStrategy) {
case ChartsExportDeviceGroupMergingStrategy.Merge:
// merge into one table row
if (res[values[elementIndex].requestIndex].length === 0) {
res[values[elementIndex].requestIndex].push([]);
case ChartsExportDeviceGroupMergingStrategy.Merge:
// merge into one table row
if (res[values[elementIndex].requestIndex].length === 0) {
res[values[elementIndex].requestIndex].push([]);
}
threeD.forEach(rows => {
if (rows.length === 0) {
return;
}
threeD.forEach(rows => {
if (rows.length === 0) {
return;
}
if (rows[0].length > 2) {
rows.forEach(row => {
row.slice(1).forEach(r => {
res[values[elementIndex].requestIndex][0].push([row[0], r]);
});
if (rows[0].length > 2) {
rows.forEach(row => {
row.slice(1).forEach(r => {
res[values[elementIndex].requestIndex][0].push([row[0], r]);
});
});
} else {
res[values[elementIndex].requestIndex][0].push(...rows);
}
});
break;
case ChartsExportDeviceGroupMergingStrategy.Sum:
// sum by timestamp
// can only be selected together with a group to ensure identical timestamps
if (res[values[elementIndex].requestIndex].length === 0) {
res[values[elementIndex].requestIndex].push([]);
}
threeD.forEach(rows => {
rows.forEach(row => {
const elem = res[values[elementIndex].requestIndex][0].find((r: any) => r.length > 0 && r[0] === row[0]);
if (elem === undefined) {
res[values[elementIndex].requestIndex][0].push(row);
} else {
res[values[elementIndex].requestIndex][0].push(...rows);
let v = (elem[1] as number);
row.slice(1).forEach(n => v += n);
elem[1] = v;
}
});
break;
case ChartsExportDeviceGroupMergingStrategy.Sum:
// sum by timestamp
// can only be selected together with a group to ensure identical timestamps
if (res[values[elementIndex].requestIndex].length === 0) {
res[values[elementIndex].requestIndex].push([]);
});
break;
case ChartsExportDeviceGroupMergingStrategy.Separate:
default:
//preserve individual rows
threeD.forEach(rows => {
if (rows.length === 0) {
return;
}
threeD.forEach(rows => {
if (rows[0].length > 2) {
const off = res[values[elementIndex].requestIndex].length;
rows.forEach(row => {
const elem = res[values[elementIndex].requestIndex][0].find((r: any) => r.length > 0 && r[0] === row[0]);
if (elem === undefined) {
res[values[elementIndex].requestIndex][0].push(row);
} else {
let v = (elem[1] as number);
row.slice(1).forEach(n => v += n);
elem[1] = v;
}
});
});
break;
case ChartsExportDeviceGroupMergingStrategy.Separate:
default:
//preserve individual rows
threeD.forEach(rows => {
if (rows.length === 0) {
return;
}
if (rows[0].length > 2) {
const off = res[values[elementIndex].requestIndex].length;
rows.forEach(row => {
row.slice(1).forEach((r, i) => {
while (res[values[elementIndex].requestIndex].length <= off + i) {
res[values[elementIndex].requestIndex].push([]);
metadata[values[elementIndex].requestIndex].push({});
}
res[values[elementIndex].requestIndex][off + i].push([row[0], r]);
metadata[values[elementIndex].requestIndex][off + i] = JSON.parse(JSON.stringify(metadataElem));
if (columnNames !== undefined && columnNames.length > i) {
metadata[values[elementIndex].requestIndex][off + i].columnName = columnNames[i];
}
});
row.slice(1).forEach((r, i) => {
while (res[values[elementIndex].requestIndex].length <= off + i) {
res[values[elementIndex].requestIndex].push([]);
metadata[values[elementIndex].requestIndex].push({});
}
res[values[elementIndex].requestIndex][off + i].push([row[0], r]);
metadata[values[elementIndex].requestIndex][off + i] = JSON.parse(JSON.stringify(metadataElem));
if (columnNames !== undefined && columnNames.length > i) {
metadata[values[elementIndex].requestIndex][off + i].columnName = columnNames[i];
}
});
} else {
res[values[elementIndex].requestIndex].push(rows);
metadata[values[elementIndex].requestIndex].push(metadataElem);
}
});
break;
});
} else {
res[values[elementIndex].requestIndex].push(rows);
metadata[values[elementIndex].requestIndex].push(metadataElem);
}
});
break;
}
}
});
Expand All @@ -298,12 +298,12 @@ export class ChartsExportService {
let mapper: number[] = [];
res.forEach(r => {
switch (r.source) {
case 'influx':
mapper = influxResultMapper;
break;
case 'timescale':
mapper = timescaleResultMapper;
break;
case 'influx':
mapper = influxResultMapper;
break;
case 'timescale':
mapper = timescaleResultMapper;
break;
}
r.res.forEach((req, index) => {
table[mapper[index]] = req;
Expand All @@ -327,47 +327,49 @@ export class ChartsExportService {
}


getChartData(widget: WidgetModel, from?: string, to?: string, groupInterval?: string, hAxisFormat?: string, lastOverride?: string, chooseColors = false): Observable<ChartsModel | ErrorModel> {
return new Observable<ChartsModel | ErrorModel>((observer) => {
this.getData(widget.properties, from, to, groupInterval, lastOverride).pipe(concatMap(r => {
let obs: Observable<DeviceInstancesWithDeviceTypeTotalModel> = of({ result: [], total: 0 });
const deviceIds: string[] = [];
r.metadata.forEach(m => {
m.forEach(subM => {
if (subM.deviceId !== undefined && !this.devices.has(subM.deviceId) && deviceIds.indexOf(subM.deviceId) === -1) {
deviceIds.push(subM.deviceId);
}
});
});
if (deviceIds.length > 0) {
obs = this.deviceInstancesService.getDeviceInstancesWithDeviceType({
limit: deviceIds.length,
offset: 0,
deviceIds,
});
}
return obs.pipe(map(devices => {
devices.result.forEach(d => this.devices.set(d.id, d));
return r;
}));
})).subscribe(r => {
const resp = r.data;
if (resp === null) {
// no data
observer.next(this.setProcessInstancesStatusValues(widget, new ChartDataTableModel([[]])));
} else if (this.errorHandlerService.checkIfErrorExists(resp)) {
observer.next(resp as ErrorModel);
} else {
const tableData = this.setData(resp, widget.properties, r.metadata);
if (chooseColors) {
tableData.colors = tableData.table.data[0].slice(1).map(title =>
'#' + Math.abs(hashCode(JSON.stringify(title))).toString(16).slice(0, 6)); // semi-random color that is always the same for the same title
getChartData(widget: WidgetModel, _from?: string, to?: string, groupInterval?: string, hAxisFormat?: string, lastOverride?: string, chooseColors = false): Observable<ChartsModel | ErrorModel> {
return this.getData(widget.properties, _from, to, groupInterval, lastOverride).pipe(concatMap(r => {
let obs: Observable<DeviceInstancesWithDeviceTypeTotalModel> = of({ result: [], total: 0 });
const deviceIds: string[] = [];
r.metadata.forEach(m => {
m.forEach(subM => {
if (subM.deviceId !== undefined && !this.devices.has(subM.deviceId) && deviceIds.indexOf(subM.deviceId) === -1) {
deviceIds.push(subM.deviceId);
}
observer.next(this.setProcessInstancesStatusValues(widget, tableData.table, tableData.colors, hAxisFormat));
}
observer.complete();
});
});
});
if (deviceIds.length > 0) {
obs = this.deviceInstancesService.getDeviceInstancesWithDeviceType({
limit: deviceIds.length,
offset: 0,
deviceIds,
});
}
return obs.pipe(map(devices => {
devices.result.forEach(d => this.devices.set(d.id, d));
return r;
}));
})).pipe(mergeMap(r => {
const resp = r.data;
if (resp === null) {
// no data
return of(this.setProcessInstancesStatusValues(widget, new ChartDataTableModel([[]])));
} else if (this.errorHandlerService.checkIfErrorExists(resp)) {
return of(resp as ErrorModel);
} else {
const obs: Observable<any>[] = [of(null)]; // needs at leat one Obserfable for forkJoin
const tableData = this.setData(resp, widget.properties, r.metadata);
if (chooseColors) {
tableData.colors = Array(tableData.table.data[0].length - 1);
obs.push(...tableData.table.data[0].slice(1).map((title, i) =>
// i just created it...
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
from(digestMessage(title as string, 'SHA-1')).pipe(map(digest => tableData.colors![i-1] = '#' + digest.slice(0, 6)))));
//'#' + Math.abs(hashCode(JSON.stringify(title))).toString(16).slice(0, 6)); // semi-random color that is always the same for the same title
}
return forkJoin(obs).pipe(map(_ => this.setProcessInstancesStatusValues(widget, tableData.table, tableData.colors, hAxisFormat)));
}
}));
}

private setData(data: any[][][][], properties: WidgetPropertiesModels, metadata: { exportId?: string; deviceId?: string; serviceId?: string; columnName?: string }[][]): {
Expand Down

0 comments on commit 980904b

Please sign in to comment.