151 lines
3.9 KiB
TypeScript
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;
|
|
}
|