dv/frontend/src/adapters/gateways/EChartsOptionBuilder/heatmapOptionBuilder.ts

151 lines
3.9 KiB
TypeScript

import { type ChartInstance } from '@/domain/entities/ChartInstance';
import { type FieldBinding } from '@/domain/entities/FieldBinding';
function getBinding(bindings: FieldBinding[], axis: string): FieldBinding | undefined {
return bindings.find((b) => b.axis === axis);
}
export function buildHeatmapOption(
chart: ChartInstance,
data: Record<string, any>[],
): Record<string, any> {
const { bindings, style } = chart;
const xBinding = getBinding(bindings, 'x');
const yBinding = getBinding(bindings, 'y');
const valueBinding = getBinding(bindings, 'value');
const xField = xBinding?.columnName ?? '';
const yField = yBinding?.columnName ?? '';
const valueField = valueBinding?.columnName ?? '';
const option: Record<string, any> = {};
if (style.title.visible) {
option.title = {
text: style.title.text,
left: style.title.align,
textStyle: {
fontSize: style.title.fontSize,
fontWeight: style.title.fontWeight,
color: style.title.color,
},
};
}
if (style.legend.visible) {
option.legend = {
show: true,
orient: style.legend.orient,
top: style.legend.position === 'top' ? 'top' : style.legend.position === 'bottom' ? 'bottom' : 'middle',
left: style.legend.position === 'left' ? 'left' : style.legend.position === 'right' ? 'right' : 'center',
textStyle: {
fontSize: style.legend.fontSize,
color: style.legend.color,
},
};
} else {
option.legend = { show: false };
}
option.backgroundColor = style.background.color || 'transparent';
option.tooltip = { position: 'top' };
option.grid = { containLabel: true, top: 60, right: 80, bottom: 30, left: 30 };
if (style.animation.enabled) {
option.animation = true;
option.animationDuration = style.animation.duration;
} else {
option.animation = false;
}
const xCategories = [...new Set(data.map((row) => String(row[xField])))];
const yCategories = [...new Set(data.map((row) => String(row[yField])))];
option.xAxis = {
type: 'category',
data: xCategories,
show: style.xAxis.visible,
axisLabel: {
show: style.xAxis.labelVisible,
fontSize: style.xAxis.labelFontSize,
color: style.xAxis.labelColor,
rotate: style.xAxis.labelRotation,
},
splitArea: { show: true },
};
option.yAxis = {
type: 'category',
data: yCategories,
show: style.yAxis.visible,
axisLabel: {
show: style.yAxis.labelVisible,
fontSize: style.yAxis.labelFontSize,
color: style.yAxis.labelColor,
},
splitArea: { show: true },
};
const allValues = data.map((row) => Number(row[valueField]));
const minVal = Math.min(...allValues);
const maxVal = Math.max(...allValues);
const heatmapData = data.map((row) => [
xCategories.indexOf(String(row[xField])),
yCategories.indexOf(String(row[yField])),
Number(row[valueField]),
]);
option.visualMap = {
min: minVal,
max: maxVal,
calculable: true,
orient: 'horizontal',
left: 'center',
bottom: 0,
inRange: {
color: style.colors.length >= 2
? style.colors.slice(0, 2)
: ['#313695', '#d73027'],
},
};
let label: Record<string, any>;
if (style.dataLabel.visible) {
let formatter: string | undefined;
switch (style.dataLabel.format) {
case 'percent':
formatter = '{b}: {d}%';
break;
case 'custom':
formatter = style.dataLabel.template;
break;
case 'value':
default:
formatter = '{c}';
break;
}
label = {
show: true,
fontSize: style.dataLabel.fontSize,
color: style.dataLabel.color,
formatter,
};
} else {
label = { show: false };
}
option.series = [
{
type: 'heatmap',
data: heatmapData,
label,
emphasis: {
itemStyle: { shadowBlur: 10, shadowColor: 'rgba(0, 0, 0, 0.5)' },
},
},
];
return option;
}