@@ -3,7 +3,7 @@ import { PanelProps } from '@grafana/data';
33import { useTheme2 , Tooltip } from '@grafana/ui' ;
44import HeatMap from '@uiw/react-heat-map' ;
55import { CalendarHeatmapOptions , HeatmapValue } from '../types' ;
6- import { processTimeSeriesData , getColorPalette } from '../utils/dataProcessor' ;
6+ import { processTimeSeriesData , getColorPalette , getLegendColors } from '../utils/dataProcessor' ;
77import { css } from '@emotion/css' ;
88import { 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