Skip to content

Commit 855e7af

Browse files
CyanCyan
authored andcommitted
已搞定:
- 自定义颜色步长(比如0,1,4,12) - 最小从0.01开始上色(之前是1以下不上色)
1 parent cdee24d commit 855e7af

4 files changed

Lines changed: 432 additions & 363 deletions

File tree

src/components/CalendarHeatmapPanel.tsx

Lines changed: 70 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { PanelProps } from '@grafana/data';
33
import { useTheme2, Tooltip } from '@grafana/ui';
44
import HeatMap from '@uiw/react-heat-map';
55
import { CalendarHeatmapOptions, HeatmapValue } from '../types';
6-
import { processTimeSeriesData, getColorPalette } from '../utils/dataProcessor';
6+
import { processTimeSeriesData, getColorPalette, getLegendColors } from '../utils/dataProcessor';
77
import { css } from '@emotion/css';
88
import { t } from '@grafana/i18n';
99

@@ -50,14 +50,7 @@ function rotateWeek(labelsSunFirst: string[], weekStart: 'sunday' | 'monday'): s
5050
return weekStart === 'monday' ? [...labelsSunFirst.slice(1), labelsSunFirst[0]] : labelsSunFirst;
5151
}
5252

53-
export const CalendarHeatmapPanel: React.FC<Props> = ({
54-
data,
55-
width,
56-
height,
57-
options,
58-
timeRange,
59-
timeZone,
60-
}) => {
53+
export const CalendarHeatmapPanel: React.FC<Props> = ({ data, width, height, options, timeRange, timeZone }) => {
6154
const theme = useTheme2();
6255

6356
const heatmapData = useMemo(() => {
@@ -106,108 +99,68 @@ export const CalendarHeatmapPanel: React.FC<Props> = ({
10699
return Math.max(1, Math.ceil((diffDays + 1) / 7));
107100
}, [shiftedStartDate, shiftedEndDate]);
108101

102+
// 统一维护 legend 相关尺寸:legendOffset 和 styles.legend 共用同一份常量
103+
const LEGEND_LAYOUT = {
104+
marginTop: 10,
105+
paddingBottom: 10,
106+
inferredLineHeight: 14,
107+
fudgePx: 0,
108+
};
109109

110+
const legendOffset = options.showLegend
111+
? LEGEND_LAYOUT.marginTop + LEGEND_LAYOUT.paddingBottom + LEGEND_LAYOUT.inferredLineHeight + LEGEND_LAYOUT.fudgePx
112+
: 0;
110113

114+
// 计算 rectSize:宽度自适应 + 高度自动约束,并留一个变量可手动微调
115+
const computedRectSize = useMemo(() => {
116+
if (!options.autoRectSize) {
117+
return options.rectSize;
118+
}
111119

120+
const leftPad = options.showWeekLabels ? 28 : 5;
121+
const usableW = Math.max(0, availableWidth - leftPad);
122+
const rawByWidth = Math.floor(usableW / weekCount) - options.space;
112123

124+
const heatmapHeight = Math.max(0, height - legendOffset);
113125

126+
const monthLabelFontSize = 12;
127+
const inferredMonthLabelLineHeight = Math.ceil(monthLabelFontSize * 1.35);
128+
const inferredTopLabelsHeight = options.showMonthLabels ? inferredMonthLabelLineHeight : 0;
114129

130+
const heightFudgePx = 0;
131+
const usableH = Math.max(0, heatmapHeight - inferredTopLabelsHeight - heightFudgePx);
115132

133+
const rawByHeight = Math.floor((usableH - 6 * options.space) / 7);
116134

117-
118-
// 统一维护 legend 相关尺寸:legendOffset 和 styles.legend 共用同一份常量
119-
const LEGEND_LAYOUT = {
120-
marginTop: 10,
121-
paddingBottom: 10,
122-
// 没有显式 line-height 时,用一个推导值(你也可以改成 Math.ceil(fontSize * 1.35))
123-
inferredLineHeight: 14,
124-
// 手动微调:挡住就 +2/+4;留白太多就 -2
125-
fudgePx: 0,
126-
};
127-
128-
129-
const legendOffset = options.showLegend
130-
? LEGEND_LAYOUT.marginTop + LEGEND_LAYOUT.paddingBottom + LEGEND_LAYOUT.inferredLineHeight + LEGEND_LAYOUT.fudgePx
131-
: 0;
132-
133-
134-
// 计算 rectSize:宽度自适应 + 高度自动约束,并留一个变量可手动微调
135-
const computedRectSize = useMemo(() => {
136-
if (!options.autoRectSize) {
137-
return options.rectSize;
138-
}
139-
140-
// 1) width constraint(原逻辑)
141-
const leftPad = options.showWeekLabels ? 28 : 5;
142-
const usableW = Math.max(0, availableWidth - leftPad);
143-
const rawByWidth = Math.floor(usableW / weekCount) - options.space;
144-
145-
// 2) height constraint(自动计算,不写死 monthPad/safety)
146-
// HeatMap 实际可用高度(与你传给 <HeatMap height={...}/> 保持一致)
147-
const heatmapHeight = Math.max(0, height - legendOffset);
148-
149-
// 关键:月标签/字体/基线/主题差异等,���法在不测量 DOM 的情况下 100% 精确推导
150-
// 这里采用“基于 fontSize 的推导”,再提供一个手动微调项
151-
const monthLabelFontSize = 12; // 你在 CSS 里 text[data-size] 用的是 12px
152-
const inferredMonthLabelLineHeight = Math.ceil(monthLabelFontSize * 1.35); // 自动推导一行文本高度
153-
const inferredTopLabelsHeight = options.showMonthLabels ? inferredMonthLabelLineHeight : 0;
154-
155-
// 手动微调(你只需要改这个值来测试:0 / 2 / 4 / 6 / 8 / 10 ...)
156-
const heightFudgePx = 0;
157-
158-
const usableH = Math.max(0, heatmapHeight - inferredTopLabelsHeight - heightFudgePx);
159-
160-
// 7 行日历 + 行间 6 个 gap
161-
const rawByHeight = Math.floor((usableH - 6 * options.space) / 7);
162-
163-
const raw = Math.min(rawByWidth, rawByHeight);
164-
return Math.max(4, Math.min(24, raw));
165-
}, [
166-
options.autoRectSize,
167-
options.rectSize,
168-
options.showWeekLabels,
169-
options.showMonthLabels,
170-
options.space,
171-
availableWidth,
172-
weekCount,
173-
height,
174-
legendOffset,
175-
]);
176-
177-
178-
179-
180-
181-
182-
183-
184-
185-
186-
187-
188-
189-
190-
191-
135+
const raw = Math.min(rawByWidth, rawByHeight);
136+
return Math.max(4, Math.min(24, raw));
137+
}, [
138+
options.autoRectSize,
139+
options.rectSize,
140+
options.showWeekLabels,
141+
options.showMonthLabels,
142+
options.space,
143+
availableWidth,
144+
weekCount,
145+
height,
146+
legendOffset,
147+
]);
192148

193149
const weekLabels = useMemo(() => {
194150
if (!options.showWeekLabels) {
195151
return false as const;
196152
}
197153

198-
// 1) Build base labels in Sun..Sat order
199154
let labelsSunFirst: string[] | null = null;
200155

201156
if (options.weekLabelMode === 'number') {
202-
// Sun..Sat => 1..7 (then rotate by weekStart)
203157
labelsSunFirst = ['1', '2', '3', '4', '5', '6', '7'];
204158
} else if (options.weekLabelMode === 'custom') {
205159
const custom = splitCsv(options.weekLabelCustom);
206160
labelsSunFirst = custom.length === 7 ? custom : null;
207161
}
208162

209163
if (!labelsSunFirst) {
210-
// default (Sun..Sat)
211164
labelsSunFirst = [
212165
t('panel.component.weekLabels.sun', 'Sun'),
213166
t('panel.component.weekLabels.mon', 'Mon'),
@@ -219,14 +172,8 @@ const computedRectSize = useMemo(() => {
219172
];
220173
}
221174

222-
// 2) Rotate for weekStart (Monday-first => Mon..Sun)
223175
return rotateWeek(labelsSunFirst, options.weekStart);
224-
}, [
225-
options.showWeekLabels,
226-
options.weekStart,
227-
options.weekLabelMode,
228-
options.weekLabelCustom,
229-
]);
176+
}, [options.showWeekLabels, options.weekStart, options.weekLabelMode, options.weekLabelCustom]);
230177

231178
const monthLabels = useMemo(() => {
232179
if (!options.showMonthLabels) {
@@ -242,10 +189,8 @@ const computedRectSize = useMemo(() => {
242189
if (custom.length === 12) {
243190
return custom;
244191
}
245-
// fall through to default if invalid
246192
}
247193

248-
// default
249194
return [
250195
t('panel.component.monthLabels.jan', 'Jan'),
251196
t('panel.component.monthLabels.feb', 'Feb'),
@@ -270,8 +215,19 @@ const computedRectSize = useMemo(() => {
270215
}, [heatmapData]);
271216

272217
const colors = useMemo(() => {
273-
return getColorPalette(options.colorScheme, theme, maxValue, options.customColor);
274-
}, [options.colorScheme, theme, maxValue, options.customColor]);
218+
return getColorPalette(
219+
options.colorScheme,
220+
theme,
221+
maxValue,
222+
options.customColor,
223+
options.bucketMode,
224+
options.customBuckets
225+
);
226+
}, [options.colorScheme, theme, maxValue, options.customColor, options.bucketMode, options.customBuckets]);
227+
228+
const legendColors = useMemo(() => {
229+
return getLegendColors(options.colorScheme, theme, options.customColor);
230+
}, [options.colorScheme, theme, options.customColor]);
275231

276232
const styles = {
277233
container: css`
@@ -280,17 +236,12 @@ const computedRectSize = useMemo(() => {
280236
display: flex;
281237
flex-direction: column;
282238
align-items: center;
283-
// justify-content: center;
284239
justify-content: flex-start;
285240
overflow: auto;
286241
padding: 1px;
287242
`,
288243
heatmap: css`
289-
/* 关于热力图上面的空白,轻微上移:建议从 6~12px 试 */
290244
margin-top: -5px;
291-
// width: 100%;
292-
293-
/* @uiw/react-heat-map sets inline color: var(--rhm-text-color, ...) */
294245
--rhm-text-color: ${theme.colors.text.secondary};
295246
296247
.w-heatmap-week {
@@ -326,17 +277,13 @@ const computedRectSize = useMemo(() => {
326277
};
327278

328279
if (data.series.length === 0) {
329-
330280
return (
331281
<div className={styles.container}>
332282
<span className={styles.noData}>{t('panel.component.noData', 'No data available')}</span>
333283
</div>
334284
);
335285
}
336286

337-
/* legend 隐藏时,不要预留固定高度。而且显示legend时,legend下面的留白 */
338-
// const legendOffset = options.showLegend ? 10 : 0;
339-
340287
return (
341288
<div className={styles.container}>
342289
<HeatMap
@@ -350,39 +297,15 @@ const computedRectSize = useMemo(() => {
350297
space={options.space}
351298
radius={options.radius}
352299
legendCellSize={0}
353-
// weekLabels={options.showWeekLabels ? [
354-
// t('panel.component.weekLabels.sun', 'Sun'),
355-
// t('panel.component.weekLabels.mon', 'Mon'),
356-
// t('panel.component.weekLabels.tue', 'Tue'),
357-
// t('panel.component.weekLabels.wed', 'Wed'),
358-
// t('panel.component.weekLabels.thu', 'Thu'),
359-
// t('panel.component.weekLabels.fri', 'Fri'),
360-
// t('panel.component.weekLabels.sat', 'Sat'),
361-
// ] : false}
362-
// monthLabels={options.showMonthLabels ? [
363-
// t('panel.component.monthLabels.jan', 'Jan'),
364-
// t('panel.component.monthLabels.feb', 'Feb'),
365-
// t('panel.component.monthLabels.mar', 'Mar'),
366-
// t('panel.component.monthLabels.apr', 'Apr'),
367-
// t('panel.component.monthLabels.may', 'May'),
368-
// t('panel.component.monthLabels.jun', 'Jun'),
369-
// t('panel.component.monthLabels.jul', 'Jul'),
370-
// t('panel.component.monthLabels.aug', 'Aug'),
371-
// t('panel.component.monthLabels.sep', 'Sep'),
372-
// t('panel.component.monthLabels.oct', 'Oct'),
373-
// t('panel.component.monthLabels.nov', 'Nov'),
374-
// t('panel.component.monthLabels.dec', 'Dec'),
375-
// ] : false}
376300
weekLabels={weekLabels}
377301
monthLabels={monthLabels}
378302
panelColors={colors}
379303
rectRender={(props, cell) => {
380-
// Convert rendered cell date back to original date for correct tooltip/value
381304
const renderedDate = parseAnyYMD(cell.date);
382305
let originalKey = String(cell.date);
383306

384307
if (renderedDate) {
385-
const originalDate = addDays(renderedDate, -renderShiftDays); // reverse shift
308+
const originalDate = addDays(renderedDate, -renderShiftDays);
386309
originalKey = toKey(originalDate);
387310
} else {
388311
originalKey = String(cell.date).replace(/-/g, '/');
@@ -410,19 +333,25 @@ const computedRectSize = useMemo(() => {
410333
{options.showLegend && (
411334
<div className={styles.legend}>
412335
<span>{t('panel.component.legend.less', 'Less')}</span>
413-
{Object.entries(colors)
414-
.map(([key, color]) => [Number(key), color] as const)
415-
.filter(([key]) => !Number.isNaN(key) && key !== 1)
416-
.sort(([a], [b]) => a - b)
417-
.map(([key, color]) => (
336+
337+
{legendColors.map((color, idx) => {
338+
const title =
339+
idx === 0
340+
? t('panel.component.legend.tooltip.empty', 'Empty')
341+
: t('panel.component.legend.tooltip.level', 'Level {{level}}', { level: idx });
342+
343+
return (
418344
<div
419-
key={key}
345+
key={idx}
420346
className={styles.legendRect}
421347
style={{ backgroundColor: color }}
422-
title={t('panel.component.legend.tooltip', 'Level {{level}}', { level: key })}
348+
title={title}
423349
/>
424-
))}
350+
);
351+
})}
352+
425353
<span>{t('panel.component.legend.more', 'More')}</span>
354+
426355
{maxValue > 0 && (
427356
<span style={{ marginLeft: 8 }}>
428357
({t('panel.component.legend.max', 'Max')}: {maxValue.toLocaleString()})
@@ -432,4 +361,4 @@ const computedRectSize = useMemo(() => {
432361
)}
433362
</div>
434363
);
435-
};
364+
};

0 commit comments

Comments
 (0)