npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

shinetek-legend

v1.2.0

Published

图例组件。用于绘制各种专题图图例或色标卡等

Readme

ShinetekLegend

图例组件,用于绘制各类专题图图例与色标卡。

支持 block(色块图例)、linear(线性渐变图例)、like-linear(类线性图例)、scale(比例尺)等多种图例类型,并提供 Canvas 与 HTML 两种渲染模式。

安装

npm install shinetek-legend

快速开始

<div id="legend-container"></div>
import ShinetekLegend from 'shinetek-legend'

const element = document.getElementById('legend-container')
const legendDoc = [ /* 图例数据,见下文 legendDoc 说明 */ ]
const options = { model: 'canvas' }

const legend = new ShinetekLegend(element, legendDoc, options)
legend.render()

API

new ShinetekLegend(element, legendDoc, options)

创建图例实例。

| 参数 | 类型 | 必填 | 说明 | |---|---|---|---| | element | HTMLElement | 是 | 图例渲染的目标容器,渲染结果会作为子节点插入该容器。建议使用 div 元素 | | legendDoc | Array<LegendSchema> | 是 | 图例配置数组,每个元素代表一组图例,结构见下文「legendDoc」 | | options | Object | 否 | 全局渲染选项,未指定字段会使用默认值,结构见下文「Options」 |

返回 ShinetekLegend 实例,实例上暴露 render() 方法。

构造行为说明:

  • 构造函数仅保存引用并合并 options 与默认配置,不会立即渲染。
  • legendDoc 与 options 在构造后仍被实例持有,外部修改会影响后续 render() 结果。
  • 支持在同一页面创建多个 ShinetekLegend 实例,各自使用独立的容器与配置。

legend.render()

根据当前实例上的 legendDoc 与 options 重新渲染图例。

使用注意:渲染管线为纯函数式设计,不会修改传入的 legendDoc 与 options(派生尺寸通过内部 LayoutContext 传递),同一组数据可安全地多次 render() 或跨实例复用。

渲染行为:

  • 重复 render() 幂等:每次渲染前会自动清空容器内旧图例(mount 执行 innerHTML = ''),不会重复叠加。
  • 根据 options.model 选择 Canvas 或 HTML 渲染模式。
  • 根据 options.itemAlign 选择水平或垂直布局;垂直布局支持 block / linear 组(HTML 模式已验证,scale / like-linear 组会告警跳过,详见下文「Options 字段说明」)。
  • 渲染完成后,图例元素会作为子节点追加到构造时传入的 element 中。
const legend = new ShinetekLegend(container, legendDoc, options)
legend.render()
// 数据更新后再次调用即可,旧图例会被自动替换
legend.render()

异常与边界:

  • legendDoc 中不支持的 type 会抛出 Error:`${group.type}: is undefined`。
  • linear / like-linear 类型要求 value、color、label 均为数组,且长度满足各自约束;不满足时会抛出 Error。
  • scale 类型要求 value 为有效分辨率数值,否则比例尺长度可能为 0。
  • 若 options.width 设为 '100%',实际画布尺寸可能受父容器宽度影响,建议父容器有明确宽度。

legendDoc

legendDoc 为数组,每个元素描述一组图例,通过 type 字段区分图例类型。

组图例结构(LegendSchema)

| 字段 | 类型 | 必填 | 说明 | |---|---|---|---| | title | string | 否 | 该组图例的标题 | | type | string | 是 | 图例类型:block、linear、like-linear、scale | | list | Array<ElementSchema> | 是 | 该组图例包含的图例项 |

通用规则:

  • title 为空字符串或缺失时,不绘制标题。
  • list 至少包含一个元素,否则该组不渲染。
  • 不同类型的 list 元素结构不同,详见下方各类型说明。
  • type 大小写敏感,仅支持全小写形式。

block — 色块图例

用于展示离散类别或符号,每个图例项为一块颜色或一条线。

const blockDoc = [
  {
    title: '行政区划',
    type: 'block',
    list: [
      { label: '省界', color: '#ffc107', style: 'line' },
      { label: '市界', color: '#ee1122', style: 'line' },
      { label: '陆地', color: '#11ff22', style: 'plane' },
      { label: '水体', color: '#1122ff', style: 'plane' }
    ]
  }
]

ElementSchema 字段:

| 字段 | 类型 | 必填 | 说明 | |---|---|---|---| | label | string | 是 | 图例文字 | | color | string | 是 | 色块或线条颜色,支持 #rrggbb、rgb() 等 CSS 颜色 | | style | string | 否 | 图例样式:line(线)、plane(面)、point(点)。默认行为取决于渲染器实现 |

说明:

  • style 为 'line' 时,在色块中部绘制一条横线。
  • style 为 'plane' 时,在色块内部填充颜色。
  • style 为 'point' 时,在色块中心绘制圆点。
  • 当 options.showItemLabel 为 false 时,label 不绘制,但仍建议提供以保证 tooltip 行为。

linear — 线性渐变图例

用于展示连续渐变的数据范围,如温度、高程等。

const linearDoc = [
  {
    title: '温度',
    type: 'linear',
    list: [
      {
        value: [0, 5, 10, 15, 20, 25, 30],
        label: ['0', '5', '10', '15', '20', '25', '30'],
        color: ['#FFFCE8', '#004AFF', '#00E8FF', '#00FF38', '#B2FF00', '#FF9C00', '#FF1300']
      }
    ]
  }
]

ElementSchema 字段:

| 字段 | 类型 | 必填 | 说明 | |---|---|---|---| | value | Array<number> | 是 | 渐变节点数值,数组长度与 label、color 保持一致 | | label | Array<string> | 是 | 每个节点对应的文字标签 | | color | Array<string> | 是 | 每个节点对应的颜色,按顺序形成渐变 |

约束:

  • value、color、label 必须为数组,否则抛出 Error。
  • value.length 必须等于 color.length,否则抛出 Error。
  • value 与 label 长度建议一致;不一致时仅按 value 长度绘制颜色,标签可能缺失。
  • 渐变方向固定为从左到右(value[0] 到 value[last])。

like-linear — 类线性图例

数据结构同 linear,用于实现分段色标等效果。渲染时按色段分隔显示,而非平滑渐变。

const likeLinearDoc = [
  {
    title: '降雨量',
    type: 'like-linear',
    list: [
      {
        value: [0, 10, 25, 50, 100],
        label: ['0', '10', '25', '50', '100'],
        color: ['#E0F7FA', '#81D4FA', '#29B6F6', '#0288D1', '#01579B']
      }
    ]
  }
]

约束:

  • value、color、label 必须为数组,否则抛出 Error。
  • value.length 必须大于 color.length,否则抛出 Error。value[i] 与 value[i+1] 定义第 i 个色段的范围,color[i] 为该色段颜色。
  • 每个色段使用独立边框绘制,视觉上呈现分段效果。
  • 中间标签在空间不足时会被自动跳过,仅保证头尾标签显示。

scale — 比例尺

用于展示地图比例尺,根据分辨率计算并绘制对应长度的线段与标签。

const scaleDoc = [
  {
    title: '比例尺',
    type: 'scale',
    list: [{ value: 0.0025, unit: 'km' }]
  }
]

ElementSchema 字段:

| 字段 | 类型 | 必填 | 说明 | |---|---|---|---| | value | number | 是 | 地图分辨率(单位:度/像素),用于计算比例尺实际长度 | | unit | string | 否 | 单位:km 或 m,默认为 km |

说明:

  • 比例尺按 4 段黑白交替绘制,末段通常用于容纳单位文字。
  • km 模式下步长会取整到 0.05km 的倍数,使刻度值更易读。
  • m 模式下步长直接按分辨率换算并取整。
  • 若容器宽度过小,比例尺会自动缩短以保证刻度值可读。

Options

全局渲染选项及其默认值:

const options = {
  width: '100%',           // 容器宽度
  model: 'canvas',         // 渲染模式
  groupSpacing: 8,         // 组图例之间的间距
  itemAlign: 'horizontal', // 单项布局方向
  itemWidth: 55,           // 色块宽度(px)
  itemHeight: 15,          // 色块高度(px;垂直模式为色标条高度,未显式设置时默认 160)
  itemSpacing: 5,          // 色块间距(px)
  itemFontSize: '12px',    // 图例标签字号
  itemFontColor: '#000000',// 图例标签颜色
  itemFontFamily: 'Arial', // 图例标签字体
  itemFontWeight: 'normal',// 图例标签字重
  itemFontStyle: 'normal', // 图例标签字体样式
  itemBorderWidth: 1,      // 色块边框宽度(px)
  itemBorderColor: '#000000', // 色块边框颜色
  itemTextAlign: 'center', // 图例标签文字对齐
  itemWordWrap: true,      // 图例标签是否自动换行(仅 block 有效)
  itemMaxRows: 0,          // 图例标签最大行数,0 表示不限制;超出显示省略号
  itemLineHeight: 1.2,     // 图例标签行高(相对字号倍数)
  titleFontSize: '14px',   // 标题字号
  titleFontColor: '#000000',// 标题颜色
  titleFontFamily: 'Arial',// 标题字体
  titleFontWeight: 'normal',// 标题字重
  titleFontStyle: 'normal',// 标题字体样式
  titleTextAlign: 'start', // 标题文字对齐
  rowSpacing: 6,           // 图例行间距(px)
  textSpacing: 3,          // 文字与色块间距(px)
  titleSpacing: 5,         // 标题与内容间距(px)
  textRowSpacing: 1,       // 文字行间距(已弃用,由 itemLineHeight 替代)
  showItemLabel: true      // 是否显示图例标签
}

Options 字段说明

| 字段 | 类型 | 默认值 | 说明 | |---|---|---|---| | width | string \| number | '100%' | 容器宽度。水平模式可传 'auto' 或具体数值;垂直模式仅接受数值(px),'100%'/'auto' 回退为内容自适应宽度并告警;数值宽度小于内容宽时溢出(HTML)/裁剪(Canvas)并告警 | | model | string | 'canvas' | 渲染模式:'canvas' 或 'html' | | groupSpacing | number | 8 | 多组图例之间的间距(px;水平模式为垂直间距,垂直模式为水平间距) | | itemAlign | string | 'horizontal' | 单项布局方向:'horizontal'(水平)或 'vertical'(垂直)。垂直模式:色标竖排、多分组从左到右、label 在色标右侧、数值自下而上递增;支持 block / linear 组,scale / like-linear 组告警跳过(scale 请创建独立横向实例);非法值抛错 | | itemWidth | number | 55 | 色块宽度(px;垂直模式下为色标条宽度) | | itemHeight | number | 15 | 色块高度(px)。垂直模式下为色标条高度,未显式设置时默认 160 | | itemSpacing | number | 5 | 相邻图例项之间的间距(px) | | itemFontSize | string | '12px' | 图例标签字号 | | itemFontColor | string | '#000000' | 图例标签颜色 | | itemFontFamily | string | 'Arial' | 图例标签字体 | | itemFontWeight | string | 'normal' | 图例标签字重,如 'normal' / 'bold' | | itemFontStyle | string | 'normal' | 图例标签字体样式,如 'normal' / 'italic' | | itemBorderWidth | number | 1 | 色块边框宽度(px) | | itemBorderColor | string | '#000000' | 色块边框颜色 | | itemTextAlign | string | 'center' | 图例标签文字对齐:'start'、'center'、'end' | | itemWordWrap | boolean | true | 图例标签是否自动换行(仅 block 组有效) | | itemMaxRows | number | 0 | 图例标签最大行数,0 表示不限制;超出时末行显示省略号 | | itemLineHeight | number | 1.2 | 图例标签行高,相对字号倍数 | | labelWidth | number | undefined | 垂直布局 block 专用:标签换行宽度(px);未设置时跟随 itemWidth,可显式设置以解耦色标条宽度与标签宽度 | | titleFontSize | string | '14px' | 图例标题字号 | | titleFontColor | string | '#000000' | 图例标题颜色 | | titleFontFamily | string | 'Arial' | 图例标题字体 | | titleFontWeight | string | 'normal' | 标题字重,如 'normal' / 'bold' | | titleFontStyle | string | 'normal' | 标题字体样式,如 'normal' / 'italic' | | titleTextAlign | string | 'start' | 标题文字对齐:'start'、'center'、'end' | | rowSpacing | number | 6 | 图例内容行间距(px) | | textSpacing | number | 3 | 文字与色块之间的间距(px) | | titleSpacing | number | 5 | 标题与内容之间的间距(px) | | textRowSpacing | number | 1 | 多行文字时的行间距(已弃用,由 itemLineHeight 替代,保留仅用于向后兼容) | | showItemLabel | boolean | true | 是否显示图例标签。支持 block、linear、like-linear、scale |

垂直布局(itemAlign: 'vertical')

专题图国际通行版式(图幅右侧竖色标):色标竖排、多分组从左到右、label 在色标右侧、数值自下而上递增。

// 色标列(竖):一个实例
new ShinetekLegend(container, legendDoc, {
  model: 'html',
  itemAlign: 'vertical',
  itemHeight: 160, // 色标条高度(默认值,可省略)
  width: 120 // 垂直模式建议显式数值 px
}).render()

// 比例尺:独立横向实例(垂直模式不支持 scale 组),放在图幅左下角
new ShinetekLegend(scaleContainer, scaleDoc, {
  model: 'html',
  width: 200
}).render()

行为要点:

  • 支持 block(分段/单值色标卡,含 line/point/plane 样式)与 linear(渐变色标卡)组;scale、like-linear 组渲染时告警并跳过。
  • 色标条尺寸:宽度由 itemWidth 控制,高度由 itemHeight 控制(未显式设置时默认 160);block 组每块高 = max(字高 + 2, itemHeight ÷ 类别数),类别多时总高可超过 itemHeight。
  • block 标签宽度:默认与 itemWidth 一致;可通过 labelWidth 独立设置标签换行宽度,实现色标条宽度与标签宽度解耦。
  • width 仅接受数值 px;'100%'/'auto' 回退为内容自适应宽度并告警。
  • 导出链路已验证:HTML 模式 + html2canvas(1.4.1)栅格化无失真,验收页 demo/vertical.html。

渲染模式对比

model 选项决定图例最终如何呈现。两种模式在视觉上一致,但在 DOM 结构、样式能力、交互能力与性能特征上有明显差异。

| 特性 | Canvas 模式 | HTML 模式 | |---|---|---| | 输出形式 | 生成单个 <canvas> 元素 | 生成多个绝对定位的 div / span 元素 | | DOM 节点数 | 1 个 | 与图例项、标签数量成正比 | | 适用场景 | 截图、导出为图片、静态展示、大数据量 | 需要交互、样式覆盖、响应式调整、无障碍 | | 样式覆盖 | 不易通过 CSS 修改 | 可通过 CSS 直接修改子元素样式 | | 交互能力 | 无原生 hover / click,需自行实现 hit-test | 可直接为子元素绑定 hover / click / title | | 无障碍 | 文本不可被屏幕阅读器读取 | 文本为真实 DOM 节点,可访问性较好 | | 性能 | 渲染后无需维护 DOM 节点,重绘代价低 | 节点较多时可能触发重排,内存占用略高 | | 文字测量 | 使用 Canvas measureText | 使用内部临时 Canvas 测量,结果与 Canvas 模式一致 | | 尺寸自适应 | 画布位图可能因 CSS 缩放而模糊 | 文本与边框为矢量 DOM,缩放清晰 |

选择建议

  • 优先选择 Canvas 模式:
    • 需要将图例导出为图片或截图。
    • 图例项数量较多,对渲染性能敏感。
    • 不需要与图例进行交互,仅作为静态图例展示。
  • 优先选择 HTML 模式:
    • 需要对图例项添加 hover 提示、click 事件或右键菜单。
    • 需要通过 CSS 主题或暗色模式动态调整图例样式。
    • 需要屏幕阅读器可访问,或需复制图例中的文本。
    • 容器尺寸会频繁变化,要求图例矢量清晰缩放。

已知限制

  • 垂直模式(itemAlign: 'vertical')支持 block / linear 组,HTML 模式已验证(含 html2canvas 导出,见 demo/vertical.html);Canvas 垂直路径未专项验证(渲染时会告警提示)。scale / like-linear 组在垂直模式下告警跳过:比例尺请创建独立横向实例,like-linear 垂直化计划在下个迭代支持。
  • Canvas 模式下若父容器宽度变化后需要重新渲染,需手动调用 render() 并确保画布尺寸已更新。
  • HTML 模式下生成的子元素使用绝对定位,父容器需设置 position: relative(组件已自动设置)。

TypeScript

类型声明文件位于 src/ts/,包含:

  • src/ts/legend.d.ts:ShinetekLegend.LegendSchema 与 ShinetekLegend.ElementSchema
  • src/ts/options.d.ts:ShinetekLegend.options

说明:当前声明文件使用全局命名空间 ShinetekLegend,未随 npm 包自动注册为模块类型。建议在 TypeScript 项目中自行扩展或补全模块声明,示例见下文。

使用项目内置声明

将 src/ts/ 下的声明文件复制到你的项目类型目录,或在 tsconfig.json 中包含它们:

{
  "include": [
    "src/**/*",
    "node_modules/shinetek-legend/src/ts/**/*.d.ts"
  ]
}

然后使用全局命名空间类型:

const doc: ShinetekLegend.LegendSchema[] = [
  {
    title: '温度',
    type: 'linear',
    list: [
      {
        value: [0, 10, 20],
        label: ['0', '10', '20'],
        color: ['#0000ff', '#00ff00', '#ff0000']
      }
    ]
  }
]

const options: ShinetekLegend.options = {
  model: 'canvas',
  itemWidth: 60,
  showItemLabel: true
}

推荐:补全模块声明(更完整的类型支持)

在项目类型目录中新建 shinetek-legend.d.ts,扩展模块声明以获得完整的类型检查:

declare module 'shinetek-legend' {
  namespace ShinetekLegend {
    type LegendType = 'block' | 'linear' | 'like-linear' | 'scale'
    type BlockStyle = 'line' | 'plane' | 'point'
    type RenderModel = 'canvas' | 'html'
    type ItemAlign = 'horizontal' | 'vertical'
    type TextAlign = 'start' | 'center' | 'end'
    type ScaleUnit = 'km' | 'm'

    interface BaseElementSchema {
      label: string | string[]
      color: string | string[]
    }

    interface BlockElementSchema extends BaseElementSchema {
      label: string
      color: string
      style: BlockStyle
    }

    interface LinearElementSchema extends BaseElementSchema {
      value: number[]
      label: string[]
      color: string[]
    }

    interface ScaleElementSchema {
      value: number
      unit?: ScaleUnit
    }

    type ElementSchema = BlockElementSchema | LinearElementSchema | ScaleElementSchema

    interface LegendSchema {
      title?: string
      type: LegendType
      list: ElementSchema[]
    }

    interface options {
      width?: string | number
      model?: RenderModel
      groupSpacing?: number
      itemAlign?: ItemAlign
      itemWidth?: number
      itemHeight?: number
      itemSpacing?: number
      itemFontSize?: string
      itemFontColor?: string
      itemFontFamily?: string
      itemBorderWidth?: number
      itemBorderColor?: string
      itemTextAlign?: TextAlign
      titleFontSize?: string
      titleFontColor?: string
      titleFontFamily?: string
      titleTextAlign?: TextAlign
      rowSpacing?: number
      textSpacing?: number
      titleSpacing?: number
      textRowSpacing?: number
      showItemLabel?: boolean
    }

    class ShinetekLegend {
      constructor(
        containerElement: HTMLElement,
        doc: LegendSchema[],
        options?: options
      )
      render(): void
    }
  }

  export = ShinetekLegend
}

使用示例:

import ShinetekLegend from 'shinetek-legend'

const container = document.getElementById('legend-container')
if (!container) {
  throw new Error('legend-container not found')
}

const blockDoc: ShinetekLegend.LegendSchema = {
  title: '行政区划',
  type: 'block',
  list: [
    { label: '省界', color: '#ffc107', style: 'line' },
    { label: '市界', color: '#ee1122', style: 'line' },
    { label: '陆地', color: '#11ff22', style: 'plane' }
  ]
}

const linearDoc: ShinetekLegend.LegendSchema = {
  title: '温度',
  type: 'linear',
  list: [
    {
      value: [0, 10, 20, 30],
      label: ['0', '10', '20', '30'],
      color: ['#FFFCE8', '#00E8FF', '#FF9C00', '#FF1300']
    }
  ]
}

const scaleDoc: ShinetekLegend.LegendSchema = {
  title: '比例尺',
  type: 'scale',
  list: [{ value: 0.0025, unit: 'km' }]
}

const options: ShinetekLegend.options = {
  model: 'html',
  itemWidth: 60,
  itemHeight: 16,
  itemSpacing: 6,
  itemFontSize: '13px',
  showItemLabel: true
}

const legend = new ShinetekLegend(container, [blockDoc, linearDoc, scaleDoc], options)
legend.render()

注意事项

  • 官方类型声明尚未完善,部分字段类型较宽松(如 string 而非字面量联合类型),建议参考上方推荐示例自行扩展。
  • 若不希望引入全局命名空间,可直接使用 as const 推断字面量类型,或将上述模块声明改为纯接口定义。
  • LegendSchema.list 当前声明为单元素元组 [ElementSchema],实际支持多个元素,建议在使用时按 ElementSchema[] 处理。

本地构建

npm install
npm run build

构建产物由 webpack.config.js 生成。

项目文档

许可证

ISC © FanTaSyLin