ui/src/app/data-explorer-shared/components/charts/status-heatmap/status-heatmap-renderer.service.ts (152 lines of code) (raw):
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { SpBaseEchartsRenderer } from '../../../echarts-renderer/base-echarts-renderer';
import { StatusHeatmapWidgetModel } from './model/status-heatmap-widget.model';
import { GeneratedDataset, TagValue } from '../../../models/dataset.model';
import { EChartsOption } from 'echarts';
import {
DimensionDefinitionLoose,
OptionDataValue,
OptionSourceDataArrayRows,
} from 'echarts/types/src/util/types';
import { Injectable, inject } from '@angular/core';
import { FieldUpdateInfo } from '../../../models/field-update.model';
import { ColorMappingService } from '../../../services/color-mapping.service';
@Injectable({ providedIn: 'root' })
export class SpStatusHeatmapRendererService extends SpBaseEchartsRenderer<StatusHeatmapWidgetModel> {
colorMappingService = inject(ColorMappingService);
applyOptions(
generatedDataset: GeneratedDataset,
options: EChartsOption,
widgetConfig: StatusHeatmapWidgetModel,
): void {
this.basicOptions(options);
const field = widgetConfig.visualizationConfig.selectedProperty;
const sourceIndex = field.sourceIndex;
const rawDataset = this.datasetUtilsService.findPreparedDataset(
generatedDataset,
sourceIndex,
);
const rawDatasetSource: OptionSourceDataArrayRows = rawDataset
.rawDataset.source as OptionSourceDataArrayRows;
const tags = rawDataset.tagValues;
const statusIndex = rawDataset.rawDataset.dimensions.indexOf(
field.fullDbName,
);
const colorMapping =
widgetConfig.visualizationConfig.colorMappingsStatusHeatmap;
rawDatasetSource.shift();
rawDatasetSource.sort((a, b) => {
return new Date(a[0]).getTime() - new Date(b[0]).getTime();
});
if (statusIndex >= 0) {
const mappedRawDataset = rawDatasetSource?.map(
row => row[statusIndex],
);
const uniqueValuesSet =
mappedRawDataset !== undefined
? new Set(mappedRawDataset)
: new Set();
const uniqueValues = [...uniqueValuesSet];
const uniqueValueSorted = uniqueValues.sort();
const valueMapping = new Map(
uniqueValueSorted.map((val, index) => [val, index]),
);
const transformedDataset = rawDatasetSource.map((row, index) => {
const statusValue = valueMapping.get(row[statusIndex]) ?? null;
return [
index,
this.makeTag(rawDataset.rawDataset.dimensions, tags, row),
statusValue,
];
});
options.dataset = { source: transformedDataset };
(options.xAxis as any).data = rawDatasetSource.map(s => {
return new Date(s[0]).toLocaleString();
});
options.tooltip = {
formatter: params => {
const timestamp = rawDatasetSource[params.data[0]][0];
const statusValue = params.value[2];
const originalValue = uniqueValueSorted[statusValue];
const statusLabel =
colorMapping.find(
c => c.value === originalValue.toString(),
)?.label || originalValue.toString();
return `${params.marker} ${new Date(timestamp).toLocaleString()}<br/>Status: <b>${statusLabel}</b>`;
},
};
const dynamicPieces = uniqueValueSorted.map((val, index) => ({
value: index,
label:
colorMapping.find(c => c.value === val.toString())?.label ||
val.toString(),
color:
colorMapping.find(c => c.value === val.toString())?.color ||
this.colorMappingService.getDefaultColor(val.toString()),
}));
options.visualMap = {
type: 'piecewise',
pieces: dynamicPieces,
orient: 'horizontal',
right: '5%',
top: '20',
};
options.legend = {
type: 'scroll',
};
options.series = [
{
name: '',
type: 'heatmap',
datasetIndex: 0,
encode: {
itemId: 0,
value: 2,
},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
},
];
}
}
public handleUpdatedFields(
fieldUpdateInfo: FieldUpdateInfo,
widgetConfig: StatusHeatmapWidgetModel,
): void {
this.fieldUpdateService.updateAnyField(
widgetConfig.visualizationConfig.selectedProperty,
fieldUpdateInfo,
);
}
basicOptions(options: EChartsOption): void {
options.grid = {
height: '80%',
top: '80',
};
options.xAxis = {
type: 'category',
splitArea: { show: true },
};
options.yAxis = {
type: 'category',
splitArea: { show: true },
};
}
private makeTag(
dimensions: DimensionDefinitionLoose[],
tags: TagValue[],
row: Array<OptionDataValue>,
) {
if (tags.length > 0) {
return tags[0].tagKeys
.map(key => {
const index = dimensions.indexOf(key);
return row[index];
})
.toString();
}
}
}