生态产品多个设计器现已发布候选版本

查看生态总览
跳转到内容

Heatmap 热力图 ​

按网格铺开的强度图:一格是一个观测点,颜色深浅表示数值所在的档位。三种形态共用同一套分档、色阶、图例与详情条,只是格子的排列方式不同:连续周列的一年日历、按自然月分块的月历、行列由作者提供的矩阵。

用法 ​

一整年铺为周列 × 星期行的方格阵,颜色深浅表示当天数值所在的档位

LessMore

组件结构 ​

加粗的是必需部件。

data-scope="heatmap":root · grid · month-block · row · week-day · month-label · row-label · column-label · cell · tooltip · legend · legend-label · legend-item · empty

示例 ​

语气换色 ​

tone 决定使用哪族颜色,色阶两端随之更换,格子的分档不变

LessMore

色板换色 ​

palette 直接按颜色指定,取基础色板同名色相的满档一端;分档与空格底色都不变

red
LessMore
orange
LessMore
amber
LessMore
yellow
LessMore
lime
LessMore
green
LessMore
teal
LessMore
cyan
LessMore
blue
LessMore
indigo
LessMore
purple
LessMore
pink
LessMore
gray
LessMore

尺寸 ​

size 改变格子边长与行首星期名的留白,一屏能放下的周数随之变化

LessMore
LessMore

档数 ​

levels 决定分几档,图例与格子共用同一条色阶

LessMore

焦点明细 ​

悬停或焦点落到某一天时报告日期与计数,键盘用户与鼠标用户看到同一份明细

LessMore
(把焦点移到某一格)

月历形态 ​

按自然月分块,每块是一张真实月历,1 号落在它实际的星期几上

LessMore

矩阵形态 ​

行列都由作者提供,数据按行列定位而不按日期:星期 × 时段的活跃度

09:0012:0015:0018:0021:00
周一
周二
周三
周四
周五
LessMore

悬停详情 ​

指针悬停与键盘聚焦经同一条路径:详情条跟随该格,Escape 收起

LessMore

年份切换 ​

一排按钮切换的是区间,网格、月份段、色阶与锚点全部按新区间重新计算

LessMore

数据统计 ​

总天数、空白天数与占比、最大值、平均值都从网格模型直接读取,不必再遍历一遍数据

LessMore
总天数:366 天空白:81 天(22%)最多:10 次平均:3.9 次/天

发散色阶 ​

数据里有负数时以 0 为中点分两侧着色:相关系数矩阵,负相关与正相关各走一种颜色

时长页数跳出转化
时长
页数
跳出
转化
-11

连续色阶 ​

着色按数值的确切比例,不按档位取整:挤在同一档里的几格也分得出高低

000408121620
华北
华东
华南
LessMore

按下下钻 ​

点一格或焦点在格上按 Enter,报告那一格:据此打开明细

上午下午夜里
周一
周二
周三
LessMore

点一格查看明细

设计指引 ​

何时使用 ​

  • 查看一段连续日期上的多少分布,以及是否存在成片的空档。
  • 查看逐月分布,月与月之间需要对齐比较。
  • 查看两条离散坐标交叉产生的强度:“星期 × 小时”的活跃度、“品类 × 月份”的销量、相关系数矩阵。
  • 强调节奏与分布,而不是某一格的准确数值。

何时不用 ​

特性 ​

  • 典型用法是一整年:calendar 形态给出整年的起止日期,铺出 53 个周列 × 7 行。以下各条的取值都按这个尺度确定。
  • 三种形态由 variant 切换,默认 calendar:
    • calendar:连续周列 × 星期行,月份名浮在网格上沿作为坐标轴,适合查看整年的节奏;
    • month:按自然月分块,每块是一张真实月历,1 号落在真实的星期位置,适合查看逐月分布;连续周列看不出月界,这是它存在的理由;
    • matrix:行列都由作者通过 rows / columns 提供,数据按 (row, column) 定位,与日期无关。
  • 矩阵形态的格子两向分别度量:宽度使用 --xh-heatmap-column-w,高度使用 --xh-heatmap-row-h,两者都不使用日历形态的 --xh-heatmap-cell-size。日历尺按一年五十多列确定,矩阵的行首挂着行名,格子按它定高会矮于行名,行高由行名文字决定、格子在行内拉成细条。行高未设置时取格宽尺的两倍(sm 16px / md 20px / lg 24px),每一档都高于行名文字;列宽未设置时取行高,默认格子为正方形。行高不使用控件高度尺:该尺用于可交互控件在一行内对齐,矩阵格子不可交互,接入后密度档切换会导致矩阵变形。
  • 月份名与星期名跟随 locale:未提供时跟随宿主浏览器语言,读取失败时使用 en-US。周首日由 firstDayOfWeek 单独决定,默认星期一。
  • 数据接受两种形状之一,按是否含 date 区分:日期形态写 { date, count },矩阵形态写 { row, column, value }。同一格出现多次即累加。
  • 日期只接受 ISO 的 YYYY-MM-DD 串,加减以 UTC 计算,不引入日期库。
  • 区间起点不在周首日时会错列,两种日期形态的口径不同:calendar 是排在起点星期之前的整行向后错一列,month 是本月首格错到 1 号真实的星期位置。两者都不铺没有日期的占位格。
  • 月份名落在该月首日所在的列上;一列跨两个月时整列归后一个月。1 号极少正好是周首日,按列内首日所在月分段会使十二个月份名中的九个整体晚一列,读者顺着“2 月”向下看反而错过月初几天。第 0 列是唯一例外,它按区间首日所在的月归属:起点落在月末(如 2024-01-29 起的近 365 天)时该列只露出上月末尾几天,跟随末日会使首月没有名字。代价是这种区间中第二个月的名字排在第 1 列,比 1 号所在的第 0 列晚一列。
  • 一整年放不下一屏时网格横向滚动,行首的星期名列固定在起始缘不随之滚动(矩阵形态的行名同理)。固定列自带 --xh-bg-surface 的底色,整块放在其他底色上(卡片、分区)时需要改写 --xh-heatmap-bg,否则该列会形成色块。月历形态例外:月块换行排列,宽度不超过容器,不作为滚动容器,也没有固定列。
  • 三张网格的推导都是纯函数(buildHeatmapGrid / buildHeatmapMonthGrid / buildHeatmapMatrixGrid),可脱离组件单独调用以预生成数据。
  • 格距四周相同:数据行的高度固定为格子边长,不由行首星期名的文字撑开。三档尺寸的横向与纵向格距是同一个值(默认 4px),格子 sm 8px / md 10px / lg 12px。
  • 行首的星期名隔行绘制:只保留第 0/2/4/6 行之外的三行,周首日是星期一时是“二 / 四 / 六”,是星期日时是“一 / 三 / 五”。一行只有 10px 高而字号是 12px,七个连续书写会上下重叠;隔行后每个保留的字有两行高度可用。三档尺寸一致,不随档位变化。需要七行全部绘制时把 --xh-heatmap-week-day-skip 改为可见颜色,例如 var(--xh-heatmap-label-fg, var(--xh-fg-subtle))(此时 sm 档会较为紧凑)。跳过的是文字着色而不是盒子:节点、文字、盒子与底色都在,固定列的实色底不能缺四行,否则格子会从行首透出,那四行也会不再参与命中测试。
  • 色阶对照条两端各有一个词(默认 Less More),一排色块本身无法说明哪端表示多。文案使用 translations.legendLow / legendHigh(只作用于顺序色阶,发散色阶两端恒写数值),部件是 legend-label(value 为 low 或 high);不写默认插槽(WC 侧只写 root)时两端自动铺出,WC 侧手写时从元素的 legendText 属性读取。
  • 配色有两条路径,默认为品牌色。一条是 tone 语气轴(brand / neutral / success / warning / danger / info),与其他组件共用;另一条是 palette 色板轴(基础色板的十二个色相 red / orange / amber / yellow / lime / green / teal / cyan / blue / indigo / purple / pink,加上 gray),直接按颜色指定,满档取同名色相的 600 档。色板只决定色阶满档一端的实心底,0 档的空格底与中间各档的混合方式不变,也不参与语气层的悬停 / 淡底 / 前景派生;它是装饰性的轴,不是语义轴。两者都写时以色板为准:色板指定了具体颜色,语气只能推导出颜色。
  • 色阶有两种,由 scale 切换:sequential 顺序色阶从空格底到满档单向加深;diverging 发散色阶以 midpoint(缺省 0)为界,低于中点兑向负向色、高于中点兑向正向色,中点那一格是中性的中点色。不写 scale 时按数据定:出现负数即按发散色阶,相关系数、同比涨跌、盈亏这类有正有负的数据不必额外声明;显式写 sequential 时负数照旧落第 0 档。发散色阶取数据色的发散三色(--xh-chart-diverging-negative / -center / -positive,组件槽 --xh-heatmap-diverging-* 可单独改写),tone 与 palette 在这一档不生效:两侧的含义由中点决定,不由语气决定。两侧共用同一套档数,按离中点最远的距离均分,离中点同样远的正负两格深浅相同;格子与详情带 polarity(negative / positive,中点那一格为 null)。对照条随之从负向满档经中点排到正向满档,两端文字缺省是两侧最远的那两个数。打印时两侧同档的环一样粗,负向一侧改用点线区分。
  • continuous 打开连续色阶:着色按数值在色阶上的确切比例,不按档位取整,挤在同一档里的几格也分得出高低。档位照常计算,格子的 data-level、打印纹理与可访问名称里的档位都不变;对照条仍是分档的色块,作为刻度参照。
  • 一格被按下时(指针点击,或焦点在格上按 Enter)派发 onDatumPress(事件名 datum-press),悬停或聚焦到某一格派发 onDatumActive(datum-active,收起时为 null),与其余图表同名同义。载荷与详情同源:联动键 key、日期或行列、原始值 count、按 format 写好的 formatted.value、档位、侧别、在色阶中的位置,用来下钻到那一天或那一对行列。它只报告按下,不产生选中状态。
  • 联动键:日期形态是 ISO 日期,矩阵形态是 heatmapMatrixKey(row, column)。指针或键盘换格时派发 onActiveKeyChange(active-key-change);给了 activeKey 即受控,指针与键盘都不在网格上时,键命中的那一格描出与悬停相同的一圈,用来与别的图联动,或标出另一处选中的值。联动不打开详情条:详情条的落点在事件那一刻量 DOM,联动没有这一步。联动键只认字符串:日期一律写 ISO 日期串(YYYY-MM-DD),Date 对象与数字不命中任何一格——热力图的日期是不带时区的墙上日期,组件不替使用者换算。
  • 数值格式由 format 决定(NumberFormatSpec 或函数),详情的 formatted.value、格子的可访问名称与发散色阶对照条两端的数值共用这一份;缺省按 locale 的常规数字写法。
  • pending 表示数据重取中:网格与月份行保留上一帧、淡到与其余图表相同的 --xh-chart-pending-alpha,对照条照常,根上 aria-busy。
  • 网格里一条数据都没有时(日期形态看区间内,矩阵形态看行列上)显示 empty 部件,叠在热力图上居中,文案取 translations.emptyText;取数中还没有数据时改写 translations.loadingText 并带一枚加载环。判据是「没有数据」而不是「全为 0」:写了 0 的条目也算数据,0 本身是能画出来的事实。
  • 档数可调:未提供 thresholds 时把有数据的那几档在 (0, 最大值] 上等宽分开(分界取整、逐档至少加 1),提供时以其为准。分档由图表引擎的分档比例尺完成;thresholds 里有重复或不是有限数的值时剔除并在诊断通道报 chart.invalid-range,档数按剩下的算。
  • 在图外报告“总天数 / 空白天数与占比 / 最大值 / 平均值”不需要再次遍历数据:三张网格都带 max(最大值)、total(总和)与 emptyCount(值为 0 的格子数),格子总数从 cells.size 读取。emptyCount 统计值为 0 的格子:没有数据的日期与写了 0 的日期都计入。它不是色阶第 0 档的格子数:未提供 thresholds 时首个下界始终为 1,两个数相等;提供 thresholds 后第 0 档还会包含低于首个下界的非零值,两个数不再相等。
  • 悬停或键盘聚焦到某一格时显示详情条,内容由作者编写;组件只提供身份、位置与该格的数据(日期或行列、原始值、档位、在色阶中的位置)。详情条与对照条走 Chart 家族配方:详情条是与其余图表提示框同一副 frosted 材质、overlay 圆角与正文排版,不反白,--xh-heatmap-tooltip-bg / -fg / -border 只换颜色那一层,顶部的边界光与背景模糊照旧。
  • 首次出现时网格、星期名、月份名与对照条原样在场,有颜色的格子从空格底色填到自己的档位色,按日期先后(矩阵按列先后)一路扫过去;数据晚于挂载到达(异步取数,或 Web Components 连上之后才赋 value)时同样播这段填色,网格一格颜色都没有时不播。之后的数据变化让各格从旧档的颜色过渡到新档,只在这一段过渡,主题、语气与色板的换色照常一步到位。扫描取 --xh-motion-duration-reveal,每格的填色取 --xh-motion-duration-enter,数据变化取 --xh-motion-duration-morph,在图或它的容器上改写它们只影响这张图。animated={false}(Web Components 写 animated="false")关闭过渡;系统开了减弱动效或容器写了 data-motion="reduce" 时不再逐格扫过去,各格一起淡变填色,数据变化直接换色。
  • 语气与尺寸两轴与其他组件同源;色板轴是热力图独有的。
  • 三端的作者侧写法不同,最终 DOM 一致:Vue 侧不写默认插槽时按形态自动铺开整棵树,另有 cell 插槽向每格放入内容(三种形态都铺,载荷是日历格或矩阵格,用 'date' in cell 区分)、tooltip 插槽编写详情条内容(写了才铺出详情条)、empty 插槽改写空态内容;React 侧对应 renderCell / renderTooltip / renderEmpty;Web Components 侧只写 root 时元素按形态在其中生成同一棵树,生成的节点带 data-xh-gen、按身份复用。三端的这棵缺省树都出自 headless 的 heatmapStructure(api):它按当前形态的网格模型产出与 anatomy 对应的部件节点(StructureNode),Vue / React 照它渲部件组件,Web Components 照它生成节点。
  • Web Components 侧 root 里可以另写 tooltip、empty、legend,元素只补其余部分;详情条的内容经 datum-active 事件自行填写,empty 留空时元素写入空态文案。需要改结构时在 root 里写 grid:此时元素不生成任何节点,各部件由作者写进标记,可读取 grid / monthGrid / matrixGrid 三个只读属性(三种形态推导出的行、列、月份段、星期名、档位标尺)循环铺设。每次读取都重算整张网格,读取一次后缓存使用。
  • 自行编写默认插槽铺网格时,锚点从载荷的 focusedCell / anchorCell 读取、用 setFocusedCell 移动:这一组三种形态通用,带 Date 的一组在矩阵形态下始终为 null。

从其他库迁移时的对照表。左列是那些库写在 color-theme 上的取值,右侧是本库的色板轴。色板取基础色板的色相,语气轴取的是语气色(专表好坏),两者的颜色不再相同:只想换颜色时写色板,颜色本身带好坏含义时才写语气:

其他库的 color-theme色板轴满档实心底取的原语
greenpalette="green"--xh-color-green-600
bluepalette="blue"--xh-color-blue-600
orangepalette="orange"--xh-color-orange-600
purplepalette="purple"--xh-color-purple-600
redpalette="red"--xh-color-red-600
其余色相palette="amber" 等十二个色相名--xh-color-<色相>-600
(多数库没有)palette="gray"--xh-color-neutral-600;深色态换 --xh-color-neutral-450
(多数库默认为绿)不写--xh-bg-brand,跟随使用者的品牌色
  • 灰色是唯一按主题换档的一族:十二个色相的 600 档明度都是 0.546,深色态的空格底(neutral-800,明度 0.269)距离足够;中性 600 档只有 0.439,五档均分后每档只差 0.0425、相邻两档对比度 1.16–1.20,几乎无法分辨。因此深色态改取 neutral-450(明度 0.65),步长回到 0.095、相邻两档 1.42–1.50,不输彩色族;不取更亮的 neutral-400 是因为高对比档的 border-default 正是该档,满档格子的描边会与底色同色。tone="neutral" 没有这层处理,需要灰色热力图时写 palette="gray"。
  • 色板之外的颜色可以直接指定:改写 --xh-heatmap-ink(满档的实心底)与 --xh-heatmap-empty(0 档的空格底),中间各档由两端在 oklab 中混合。这个入口优先级最高,色板与语气都不能覆盖它。

组合 ​

  • 详情条不引入浮层引擎,也不参与浮层的层级与关闭协议:它按 root 的内边距盒绝对定位,随网格一起横向滚动,摆在上方还是下方按格子的行序决定,因此始终压在网格自身上。条比一格宽得多,格子末缘之前的空间比起始缘之后多时它改从末缘反向延伸,不越过滚动容器的末缘;否则整年铺开时后三分之一的格子悬停出的条会被裁掉,横向滚动条也会随之伸缩。选择的是两侧空间较大的一边,与条自身的宽度无关(条的宽度在计算落点时尚不可得:内容即将替换,收起时又是 display:none);容器窄到条比较大的一侧还宽时,条仍会被裁掉一截,两侧都放不下时没有解。需要会翻转、会避让视口的真正浮层时,在格子上挂文字提示。
  • 与日历选择器并排:一个查看分布,一个选择日期。

最佳实践 ​

  • 图例两端的词按默认铺出即可;需要换成“少 / 多”或其他说法时改 translations.legendLow / legendHigh,不要删除,只有一排色块时读者无法判断深色的含义。
  • 深浅不是唯一线索:数值必须在文案中说明,否则色觉障碍的用户无法读取。
  • 一张图只选一条轴:表达“这一族数据是告警”时写 tone,表达“这张图使用绿色”时写 palette,两者都写会让读者猜测优先级。同一页并排的几张图更要统一,混用会让读者以为颜色有语义。
  • 色板只改变颜色不改变语义:palette="red" 的格子不代表这些天出了问题,该含义只能由标题与文案表达。
  • 数据中出现极端值时自行提供 thresholds,按最大值均分会把其余格子全部压到第 0、1 档,整张图变成一片空白加一个亮点。
  • 数据有天然的中点时(相关系数的 0、目标完成率的 100%)写 scale="diverging" 与 midpoint,读者一眼分得出“高于”与“低于”;只有大小没有方向的数据(次数、时长)用顺序色阶,强行分两侧会让读者以为低于中点是坏事。
  • 一屏放不下时让网格横向滚动,不要把格子压到无法辨认。整年在 md 档约需 774px,sm 档约 660px,容器窄于该值会出现横向滚动条。
  • 键盘移动到视口外的格子时网格会自动滚动,落点已避开固定列;为 root 另加内边距或更换 --xh-heatmap-gutter 时不需要再处理,两处读取同一个值。
  • 矩阵的列名与其下一列的格子同宽:标签较长时整体调大 --xh-heatmap-column-w,不只撑开标签,否则标签会与格子错位。
  • 调宽列时一并调高行:只设置 --xh-heatmap-column-w 而不设置 --xh-heatmap-row-h,格子会横向拉成细条。两个值在 1.5 : 1 左右最易读,一屏放不下时减少列数而不压扁行。
  • 矩阵的行列顺序就是渲染顺序,按希望读者阅读的次序提供,组件不排序。
  • 月历形态中上下键移动的是“上一周 / 下一周同一天”,可以跨月块:从一月块最后一行按 ↓ 会落到二月块,而月块并排换行,视觉上像向右上方跳动。按块阅读时使用左右键,它逐日移动、次序与视觉一致。矩阵形态没有这种情况,它的行列是无序类目,上下键到边即停。

反模式 ​

  • 将它用作日期选择器:格子只报告按下(onDatumPress),没有选中语义,也不保留按下过哪一格;受控的 activeKey 只描圈,不是表单值。
  • 一次铺多年:列数上千后既看不出节奏,键盘也无法走到尽头。
  • 只用颜色不提供文案:没有可访问名称的方格阵对读屏用户等于空白。
  • 把详情条作为唯一的信息出口:它对读屏隐藏,只写在那里等于只提供给鼠标用户。
  • 矩阵形态中期望组件从数据推断行列:轴上没有的行列,数据中写了也不进入网格。

API 参考 ​

产物 ​

层值
自定义元素<xh-heatmap>
Vue 组件XhHeatmapCell XhHeatmapColumnLabel XhHeatmapEmpty XhHeatmapGrid XhHeatmapLegend XhHeatmapLegendItem XhHeatmapLegendLabel XhHeatmapMonthBlock XhHeatmapMonthLabel XhHeatmapRoot XhHeatmapRow XhHeatmapRowLabel XhHeatmapTooltip XhHeatmapWeekDay
组合式函数useHeatmap
状态机heatmapMachine
皮肤@xihan-ui/styles/heatmap.css

Props ​

属性类型必填说明
variantHeatmapVariant形态:calendar 连续周列、month 按自然月分块、matrix 行列由作者提供;默认 calendar。
valueHeatmapValue[]数据。日期形态接受 { date, count },矩阵形态接受 { row, column, value };同一格出现多次即累加。
rowsHeatmapAxisInput[]矩阵的行,顺序即渲染顺序;只写身份或身份与文本分开写均可。
columnsHeatmapAxisInput[]矩阵的列,顺序即渲染顺序。
startDatestring区间起点(含),ISO YYYY-MM-DD。未提供或非法时为空网格。
endDatestring区间终点(含)。早于起点即空网格。
levelsnumber档数,默认 5;提供 thresholds 时档数由它决定。
thresholdsnumber[]各档的下界,升序;提供后 levels 不再生效。发散色阶里它是离中点的距离,两侧共用。
scaleHeatmapScaleMode色阶:sequential 从空格底色单向加深;diverging 以 midpoint 为界往两侧各自加深, 低于中点取负向色、高于中点取正向色(相关系数、同比涨跌、盈亏)。缺省按数据定:出现负数即 diverging。 发散色阶取数据色的发散三色,tone 与 palette 不再生效。
midpointnumber发散色阶的中点,缺省 0。
continuousboolean连续色阶:不分档,按数值在色阶上的确切位置着色;档位照常算,打印与读屏仍按档。缺省 false。
firstDayOfWeeknumber周首日,0 = 星期日,默认 1。
localestring月份名与星期名的书写 locale,未提供时按宿主语言,宿主也没有时按 en-US。
dirDirection文字方向。只作显式覆盖:未提供时方向从 DOM 读取, 左右方向键的语义跟随视觉次序,上下键与它无关。
toneTone语气:brand / neutral / success / warning / danger / info,决定使用哪族颜色。
paletteHeatmapPalette色板:基础色板的十二个色相加 gray,直接指定色阶满档一端的颜色;同时提供 tone 时以色板为准。
sizeSize尺寸:sm / md / lg。
animatedboolean播放过渡,默认开:首次出现时有数据的格子按先后从空格底色填到自己的档位色, 之后的数据变化从旧档的颜色过渡到新档。关闭后直接画终态。
formatNumberFormatSpec | ((value: number) => string)数值格式:详情、格子的可及名与发散色阶对照条两端共用;缺省按 locale 的常规数字写法。
pendingboolean数据重取中:保留上一帧,网格整体降低不透明度,根上 aria-busy。
activeKeystring | null激活的键(受控):日期形态是 ISO 日期,矩阵形态是 heatmapMatrixKey(row, column)。 指针与键盘都不在网格上时,键命中的那一格描出与悬停相同的一圈,用来与别的图联动; 字符串之外的键不命中任何一格。
onActiveKeyChange(details: HeatmapActiveKeyChangeDetails) => void指针或键盘把激活的格子换掉时通知;收起时为 null。
translationsPartial<HeatmapTranslations>
onDatumActive(details: HeatmapDatumDetails | null) => void详情应显示哪一格:指针悬停或键盘聚焦都会走到这里,收起时为 null。 详情条的内容由作者决定,组件只报告是哪一格、数值多少。
onDatumPress(details: HeatmapDatumDetails) => void一格被按下:指针点击,或焦点在格上按 Enter。载荷与 onDatumActive 同形, 用来下钻到那一天或那一对行列。Space 不接,照常滚动页面。

事件 ​

自定义元素将载荷放在 detail;Vue 使用同名 emit。

事件载荷说明
datum-activeHeatmapDatumDetails详情应显示哪一格(悬停或聚焦);detail 为 { key, date, row, column, count, level, polarity, percent, formatted },收起时为 null
datum-pressHeatmapDatumDetails点按一格,或焦点在格上按 Enter;detail 与 datum-active 同形
active-key-changeHeatmapActiveKeyChangeDetails指针或键盘换了激活的格子;detail 为 { activeKey },收起时为 null

插槽 ​

仅列出带载荷的插槽。

Vue 组件插槽载荷说明
XhHeatmapRootdefaultHeatmapRootSlotProps
XhHeatmapRootcellHeatmapCellSlotProps铺开网格时每一格的内容插槽,默认是空格子;三种形态都铺设。
XhHeatmapRoottooltipHeatmapDatumDetails | null详情条的内容插槽;写了它才会铺设 tooltip 部件。
XhHeatmapRootempty—缺省结构里的空态内容;未写时是空态文案。

React 适配器 props ​

只列各组件自己声明的那些:继承自 ComponentPropsWithRef 的 DOM 属性不在其中,根组件上与上面 Props 表同名的也不重复列。Vue 的对应物是上面的插槽表。

React 组件属性类型必填说明
XhHeatmapCellvaluestring是日期形态是 ISO 日期,矩阵形态是列身份。
XhHeatmapCellrowstring矩阵形态:行身份;写在行中时不必再写一遍。
XhHeatmapColumnLabelvaluestring是列身份。
XhHeatmapLegendItemvaluenumber | string是档位,兼收字符串。
XhHeatmapLegendItempolarityHeatmapPolarity发散色阶下这一格在中点的哪一侧;顺序色阶与中点那一格不写。
XhHeatmapLegendLabelvalueHeatmapLegendBound是挂在哪一端:low 是色阶起点,high 是终点。
XhHeatmapMonthBlockvaluestring是月份身份 YYYY-MM。
XhHeatmapMonthLabelvaluestring是月份身份 YYYY-MM。
XhHeatmapRootrenderCell(cell: HeatmapCellSlotProps) => ReactNode铺开网格时每一格的内容;未提供时是空格子。
XhHeatmapRootrenderTooltip(details: HeatmapDatumDetails | null) => ReactNode详情条的内容;提供后才铺设 tooltip 部件。
XhHeatmapRootrenderEmpty() => ReactNode缺省结构里的空态内容;未提供时是空态文案。
XhHeatmapRootchildrenSlotChildren<HeatmapRootSlotProps>
XhHeatmapRowvaluenumber | string行的身份。日历形态是行序 0-6,月历形态是月内第几周,矩阵形态是行身份; 未写即坐标轴行。
XhHeatmapRowmonthstring月历形态:所属月份 YYYY-MM;写在月块中时不必再写一遍。
XhHeatmapRowLabelvaluestring行身份;未写即表头行行首的角落占位。
XhHeatmapWeekDayvaluenumber | string行序 0-6;未提供即坐标轴行行首的占位。

状态 ​

公开状态写入 data-state。

部件取值
tooltip'hidden' | 'visible'
empty'loading' | undefined

以下名称仅用于内部状态机。

状态:idle

事件:CELL.FOCUS · CELL.BLUR · CELL.ENTER · CELL.LEAVE · DETAIL.DISMISS · FOCUS.SET · TRANSITION.END

connect API ​

getXxxProps() 返回对应部件的宿主属性。

成员类型说明
variantHeatmapVariant当前形态。
scaleModeHeatmapScaleMode生效的色阶:显式给的,或按数据定出来的。
legendItemsreadonly HeatmapLegendEntry[]对照条上的一排,作者按它逐个铺 legend-item:顺序色阶从第 0 档到满档, 发散色阶从负向满档经中点到正向满档。
gridHeatmapGrid日历网格:行是星期几、列是周次,另带月份段、星期名与档位标尺。其余形态下是一张空网格。
monthGridHeatmapMonthGrid | null月历网格:按自然月分块;不是 month 形态时为 null。
matrixGridHeatmapMatrixGrid | null矩阵网格:行列由作者提供;不是 matrix 形态时为 null。
focusedCellHeatmapCellRef | null最后一次被聚焦的格;从未聚焦过时为 null。
focusedDatestring | null最后一次被聚焦的日期;矩阵形态下恒为 null。
anchorCellHeatmapCellRef | null当前占据 Tab 位的格:锚点仍在网格中即为它,否则回退为文档序首格。
anchorDatestring | null当前占据 Tab 位的日期;矩阵形态下恒为 null。
activeCellHeatmapDatumDetails | null详情应显示的格的数据:身份、原始值、档位与色阶位置;不显示时为 null。
activeKeystring | null激活的键:指针、键盘或受控的 activeKey 给出的那一格;没有时为 null。
emptyboolean网格里一条数据都没有。
emptyTextstring空态的文字:取数中写加载文案,否则写空态文案。
detailOpenboolean详情条当前是否显示。
legendText{ low: string, high: string }对照条两端的文字,作者按它渲染 legend-label 部件。 与 getLegendLabelProps 同源,修改 translations 两处一起变化。
cellAt(date: string) => HeatmapCellMeta | null按日期取一格;不在区间内时为 null。矩阵形态下恒为 null。
setFocusedCell(cell: HeatmapCellRef | null) => void移动锚点。只改锚点不移动 DOM 焦点;需要焦点跟随时自行调用元素的 focus()。
setFocusedDate(date: string | null) => void按日期移动锚点,等同于 setFocusedCell({ date })。
getRootProps() => T['element']
getGridProps() => T['element']
getMonthBlockProps(props: HeatmapMonthBlockProps) => T['element']
getRowProps(props: HeatmapRowProps) => T['element']
getWeekDayProps(props: HeatmapWeekDayProps) => T['element']
getMonthLabelProps(props: HeatmapMonthLabelProps) => T['element']
getRowLabelProps(props: HeatmapRowLabelProps) => T['element']
getColumnLabelProps(props: HeatmapColumnLabelProps) => T['element']
getCellProps(props: HeatmapCellProps) => T['element']
getTooltipProps() => T['element']
getLegendProps() => T['element']
getLegendLabelProps(props: HeatmapLegendLabelProps) => T['element']
getLegendItemProps(props: HeatmapLegendItemProps) => T['element']
getEmptyProps() => T['element']

无障碍 ​

键盘 ​

规格出处:W3C APG

按键生效条件行为
Tab / Shift+Tab总是整张网格只占一个 Tab 位:焦点落到锚点那一格,一格都没有时落网格自己
ArrowLeftfocus in grid焦点横着退一格:日历形态是上一周的同一天,月历形态是前一天(跨得过月块),矩阵形态是左边一列;已在头一格则原地不动。dir=rtl 时改由 ArrowRight 承担
ArrowRightfocus in grid焦点横着进一格:日历形态是下一周的同一天,月历形态是后一天(跨得过月块),矩阵形态是右边一列;已在末一格则原地不动。dir=rtl 时改由 ArrowLeft 承担
ArrowUpfocus in grid焦点竖着退一格:日历形态是前一天,月历形态是上一周的同一天,矩阵形态是上面一行;走出网格则原地不动
ArrowDownfocus in grid焦点竖着进一格:日历形态是后一天,月历形态是下一周的同一天,矩阵形态是下面一行;走出网格则原地不动
Homefocus in grid焦点移到本行头一格:日历形态是这个星期几最早的一天,月历形态是这一周在本月里的头一天,矩阵形态是头一列
Endfocus in grid焦点移到本行末一格:日历形态是这个星期几最晚的一天,月历形态是这一周在本月里的末一天,矩阵形态是末一列
Ctrl+Homefocus in grid焦点移到整张网格文档序的头一格
Ctrl+Endfocus in grid焦点移到整张网格文档序的末一格
Escape详情条显示着收起详情条;焦点留在原处,按键不拦截(外层浮层的关闭仍归它自己管)
Enterfocus in grid按下焦点那一格:报告它(onDatumPress,载荷与详情同源),用来下钻;Space 不接,照常滚动页面

ARIA ​

以下属性由 connect 生成。

部件属性值
rootaria-busy'true' | undefined
gridaria-colcountcounts.columns | undefined
gridaria-labelgridLabel
gridaria-readonly'true'
gridaria-rowcountcounts.rows | undefined
gridrole'grid'
month-blockaria-labelmonthBlockOf.get(block.value)?.long
month-blockrole'rowgroup'
rowaria-hidden'true'
rowaria-labelundefined | grid.weekDays[row.weekDay]?.long
rowaria-rowindex1
rowrole'row'
week-dayaria-hidden'true'
month-labelaria-hidden'true'
row-labelaria-colindex1
row-labelaria-hidden'true'
row-labelrole'rowheader'
column-labelaria-colindexmeta.index + 2 | undefined
column-labelrole'columnheader'
cellaria-colindexmeta.columnIndex + 2 | undefined
cellaria-labelmatrixCellLabel(heatmapDatumDetails({ row, column }, …
cellrole'gridcell'
tooltiparia-hidden'true'
legendaria-labellegendLabel
legendrole'group'
legend-itemaria-hidden'true'
  • 网格是 role="grid" 且 aria-readonly,每行一个 role="row",每格一个 role="gridcell"。
  • 月历形态中一个自然月是一个 role="rowgroup",块的可访问名称是该月的完整名称;块内的星期名坐标轴整条 aria-hidden,读屏看到的每一块只有合法的行。
  • 矩阵形态中行名是 role="rowheader"、列名是 role="columnheader",表头行行首的角落占位 aria-hidden;行列总数把两条表头计入,读屏报告的行列号才正确。
  • 格子内没有文字,可访问名称完全由文案拼出:日期形态使用 translations.cellLabel(日期 + 数值),矩阵形态使用 translations.matrixCellLabel(行 + 列 + 数值),务必按本地语言改写。
  • 星期名、月份名与图例中的色块都是视觉坐标轴,一律 aria-hidden:每格自身能读出完整身份与数值,再读一遍轴只是噪音。
  • 日历形态中星期名隔行绘制,视觉上可以根据上下文推断被跳过的行:因此每一行自带 aria-label,写的是该行星期的全称(如“星期一”)。这个名称跟随 locale、不进 translations,读的就是坐标轴上的词,两处必须逐字一致(月块的名称同理)。它只是补充:role="row" 上的名称并非所有读屏在方向键导航时都播报,而每一格的可访问名称本就带完整日期。
  • 图例整体是 role="group",名称使用 translations.legendLabel。两端的词是真实文字、不隐藏,因此能说明色阶的方向;深色表示多还是少是约定而非自明。
  • 详情条 aria-hidden:它显示的信息与格子的可访问名称是同一份,读两遍反而干扰。因此详情条内不能出现可访问名称中没有的内容,否则读屏用户会缺少信息。
  • 各行格子数不一定相同(首行可能少一格),格子上带 aria-colindex,读屏报出的列号才正确。
  • 整张网格只占一个 Tab 位,进入后靠方向键移动。键盘聚焦与指针悬停触发同一条详情:只支持悬停会把键盘与读屏用户排除在外。
  • Escape 收起详情条但不拦截按键:外层浮层的关闭仍由其自身处理。
  • Enter 按下焦点那一格(onDatumPress),与点击同一条路径;Space 不接,照常滚动页面。
  • 取数中根上 aria-busy;空态部件是普通文字,有数据时带 hidden。
  • 发散色阶不只靠颜色区分两侧:每格的可访问名称写的是带符号的原始值,详情条同理;色觉障碍的读者依然读得出正负。

样式参考 ​

皮肤 ​

@xihan-ui/styles/heatmap.css 按 [data-scope="heatmap"][data-part="root"] 部件选择器书写,发布产物以挂载类 .xh-scope-heatmap 代替其中的 data-scope(特异性相同),位于 xihan.components 与 xihan.motion 层。覆盖样式使用 xihan.overrides,部件选择器照常可用。

forced-colors: active 下另有一套规则:颜色交给系统,边框与状态标记改用系统色关键字。

数据属性 ​

由 connect 生成;条件不成立时不输出无值属性。

部件属性值
rootdata-animating''(条件成立时才出现)
rootdata-loading''(条件成立时才出现)
rootdata-paletteprops.palette
rootdata-scale'diverging' | undefined
rootdata-sizeprops.size
rootdata-toneprops.tone
rootdata-variantprops.variant
rowdata-weekString(row.week)
rowdata-week-dayundefined | String(row.weekDay)
week-daydata-week-dayundefined | String(label.weekDay)
celldata-drawing''(条件成立时才出现)
celldata-highlighted''(条件成立时才出现)
celldata-levelString(place.level)
celldata-polarityplace.polarity
tooltipdata-inline-anchorundefined | tip.inlineAnchor
tooltipdata-placementundefined | ((): 'block-start' | 'block-end' => { if (activeRef =…
tooltipdata-state'hidden' | 'visible'
tooltipdata-xh-chart-part'tooltip'
legenddata-xh-chart-part'legend'
legend-labeldata-boundlabel.bound
legend-itemdata-levelString(item.level)
legend-itemdata-polarityitem.polarity
emptydata-loading''(条件成立时才出现)
emptydata-state'loading' | undefined
emptydata-xh-chart-part'empty'
emptydata-xh-loading-ring''

CSS 变量 ​

本组件公开覆盖槽由独立皮肤的实际消费位生成;默认来源、作用部件和状态均与 CSS 同源。

变量部件CSS 属性状态默认来源说明
--xh-heatmap-bgroot
row-label
week-day
backgrounddefault--xh-bg-surfaceheatmap 的 root、row-label、week-day 部件 background 覆盖槽。
--xh-heatmap-block-gapgrid
root
gapvariant=month--xh-_heatmap-gutterheatmap 的 grid、root 部件 gap 覆盖槽。
--xh-heatmap-block-inner-gapmonth-blockgapdefault--xh-_heatmap-gapheatmap 的 month-block 部件 gap 覆盖槽。
--xh-heatmap-cell-bgcell
legend-item
backgrounddefault--xh-_heatmap-inkheatmap 的 cell、legend-item 部件 background 覆盖槽。
--xh-heatmap-cell-bordercell
legend-item
box-shadowdefault--xh-border-defaultheatmap 的 cell、legend-item 部件 box-shadow 覆盖槽。
--xh-heatmap-cell-border-highlightedcellbox-shadowhighlighted--xh-fg-defaultheatmap 的 cell 部件 box-shadow 覆盖槽。
--xh-heatmap-cell-radiuscell
legend-item
border-radiusdefault--xh-shape-insetheatmap 的 cell、legend-item 部件 border-radius 覆盖槽。
--xh-heatmap-cell-sizecell
legend-item
month-label
root
row
week-day
block-size
border
inline-size
margin-inline-start
@media print
default
first-child
level=1
level=2
level=3
size=lg
size=sm
variant=month
week
week-day
--xh-space-2
--xh-space-2_5
--xh-space-3
heatmap 的 cell、legend-item、month-label、root、row、week-day 部件 block-size、border、inline-size、margin-inline-start 覆盖槽。
--xh-heatmap-column-wcell
column-label
root
inline-sizedefault
variant=matrix
--xh-_heatmap-row-hheatmap 的 cell、column-label、root 部件 inline-size 覆盖槽。
--xh-heatmap-diverging-centercell
legend-item
root
background
background-color
@keyframes xh-heatmap-fill
scale=diverging
--xh-chart-diverging-centerheatmap 的 cell、legend-item、root 部件 background、background-color 覆盖槽。
--xh-heatmap-diverging-negativecell
legend-item
root
backgroundis([data-scope='heatmap'][data-part='cell'], [data-scope='heatmap'][data-part='legend-item'])
polarity=negative
scale=diverging
--xh-chart-diverging-negativeheatmap 的 cell、legend-item、root 部件 background 覆盖槽。
--xh-heatmap-diverging-positivecell
legend-item
root
backgroundis([data-scope='heatmap'][data-part='cell'], [data-scope='heatmap'][data-part='legend-item'])
polarity=positive
scale=diverging
--xh-chart-diverging-positiveheatmap 的 cell、legend-item、root 部件 background 覆盖槽。
--xh-heatmap-emptycell
legend-item
root
background
background-color
@keyframes xh-heatmap-fill
default
--xh-bg-subtle-opaqueheatmap 的 cell、legend-item、root 部件 background、background-color 覆盖槽。
--xh-heatmap-empty-gapemptygapstate=loading--xh-space-2heatmap 的 empty 部件 gap 覆盖槽。
--xh-heatmap-fgrootcolordefault--xh-fg-mutedheatmap 的 root 部件 color 覆盖槽。
--xh-heatmap-font-sizerootfont-sizedefault--xh-_heatmap-font-sizeheatmap 的 root 部件 font-size 覆盖槽。
--xh-heatmap-gaprootgapdefault--xh-space-2heatmap 的 root 部件 gap 覆盖槽。
--xh-heatmap-grid-gapgridgapdefault--xh-_heatmap-gapheatmap 的 grid 部件 gap 覆盖槽。
--xh-heatmap-guttergrid
root
row-label
week-day
gap
inline-size
scroll-padding-inline-start
default
size=sm
variant=month
--xh-space-6
--xh-space-8
heatmap 的 grid、root、row-label、week-day 部件 gap、inline-size、scroll-padding-inline-start 覆盖槽。
--xh-heatmap-inkcell
legend-item
root
backgrounddefault
is([data-theme='dark'] *, [data-theme='dark'])
palette=amber
palette=blue
palette=cyan
palette=gray
palette=green
palette=indigo
palette=lime
palette=orange
palette=pink
palette=purple
palette=red
palette=teal
palette=yellow
theme=dark
tone
--xh-_tone
--xh-bg-brand
--xh-color-amber-600
--xh-color-blue-600
--xh-color-cyan-600
--xh-color-green-600
--xh-color-indigo-600
--xh-color-lime-600
--xh-color-neutral-450
--xh-color-neutral-600
--xh-color-orange-600
--xh-color-pink-600
--xh-color-purple-600
--xh-color-red-600
--xh-color-teal-600
--xh-color-yellow-600
heatmap 的 cell、legend-item、root 部件 background 覆盖槽。
--xh-heatmap-label-fgcolumn-label
legend
month-label
root
row-label
week-day
colordefault
variant=month
week-day=0
week-day=2
week-day=4
week-day=6
--xh-fg-subtleheatmap 的 column-label、legend、month-label、root、row-label、week-day 部件 color 覆盖槽。
--xh-heatmap-legend-gaplegendgapdefault--xh-_heatmap-gapheatmap 的 legend 部件 gap 覆盖槽。
--xh-heatmap-pyrootpadding-blockdefault--xh-_heatmap-gapheatmap 的 root 部件 padding-block 覆盖槽。
--xh-heatmap-row-gaprowgapdefault--xh-_heatmap-gapheatmap 的 row 部件 gap 覆盖槽。
--xh-heatmap-row-hcell
column-label
root
block-size
inline-size
default
size=lg
size=sm
variant=matrix
--xh-space-4
--xh-space-5
--xh-space-6
heatmap 的 cell、column-label、root 部件 block-size、inline-size 覆盖槽。
--xh-heatmap-sticky-layerrow-label
week-day
z-indexdefault1heatmap 的 row-label、week-day 部件 z-index 覆盖槽。
--xh-heatmap-title-fgmonth-label
root
colorvariant=month--xh-fg-defaultheatmap 的 month-label、root 部件 color 覆盖槽。
--xh-heatmap-tooltip-bgtooltipbackground-colordefault--xh-material-frosted-bgheatmap 的 tooltip 部件 background-color 覆盖槽。
--xh-heatmap-tooltip-bordertooltipborder-colordefault--xh-material-frosted-borderheatmap 的 tooltip 部件 border-color 覆盖槽。
--xh-heatmap-tooltip-fgtooltipcolordefault--xh-material-frosted-fgheatmap 的 tooltip 部件 color 覆盖槽。
--xh-heatmap-tooltip-font-sizetooltipfont-sizedefault--xh-text-body-sizeheatmap 的 tooltip 部件 font-size 覆盖槽。
--xh-heatmap-tooltip-layertooltipz-indexdefault2heatmap 的 tooltip 部件 z-index 覆盖槽。
--xh-heatmap-tooltip-max-wtooltipmax-inline-sizedefault--xh-overlay-max-wheatmap 的 tooltip 部件 max-inline-size 覆盖槽。
--xh-heatmap-tooltip-pxtooltippadding-inlinedefault--xh-surface-pad-smheatmap 的 tooltip 部件 padding-inline 覆盖槽。
--xh-heatmap-tooltip-pytooltippadding-blockdefault--xh-surface-pad-smheatmap 的 tooltip 部件 padding-block 覆盖槽。
--xh-heatmap-tooltip-radiustooltipborder-radiusdefault--xh-shape-overlayheatmap 的 tooltip 部件 border-radius 覆盖槽。
--xh-heatmap-tooltip-shadowtooltipbox-shadowdefault--xh-material-frosted-shadowheatmap 的 tooltip 部件 box-shadow 覆盖槽。
--xh-heatmap-week-day-skiproot
week-day
colorweek-day=0
week-day=2
week-day=4
week-day=6
transparentheatmap 的 root、week-day 部件 color 覆盖槽。

动效 ​

动效角色:状态 · 出现(见动效规范)。

关键帧 xh-heatmap-fill 随皮肤自带,不引用别处文件里的名字;background-color · box-shadow · opacity 走 transition 过渡。时长与缓动读动效令牌,改令牌即改全局节奏。

系统开启减弱动效时由令牌层统一收敛,皮肤不另作判断。

RTL ​

皮肤用逻辑属性排布(inline-start 一族),dir="rtl" 下自动镜像。

  • 横轴沿 inline 方向排列,整页切换为 rtl 时视觉次序整体翻转,左右方向键的语义跟随翻转,上下键不受影响。
  • 方向从 DOM 实时读取,祖先链上任意一处 dir 或 CSS direction 都有效;dir 属性只作显式覆盖。
  • 详情条的落点也按逻辑方向计算:rtl 下从末缘反向度量,样式侧只写一条 inset-inline-start 即两向通用。

Released under The MIT License