@wil-works/evoke-charts 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 willove
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # Evoke Charts
2
+
3
+ `@wil-works/evoke-charts` — 零依赖 Canvas 自绘图表库(Vue 3)。Evoke 生态的独立图表包,不依赖任何第三方图表引擎,也不依赖 Evoke 其他组件库;与 `@wil-works/evoke-business-ui` 同仓(monorepo)管理。
4
+
5
+ ## 特性
6
+
7
+ - **20+ 图表类型**:折线 / 面积 / 柱状 / 堆叠柱 / 条形 / 饼图 / 环形 / 玫瑰图 / 散点(含趋势线)/ 雷达 / 漏斗 / 仪表盘 / 热力图 / K 线 / 子弹图 / 直方图 / 矩形树图 / 旭日图 / 瀑布图 / 箱线图 / 混合图 / 迷你图
8
+ - **零运行时依赖**:仅 peer 依赖 Vue 3,全部绘制自研 Canvas 2D
9
+ - **主题跟随**:从 `--ev-*` CSS 令牌实时读取颜色,宿主换主题 / 暗色即跟随;无令牌时使用内置色板兜底
10
+ - **交互完备**:tooltip、图例点选 / 悬停高亮、十字准线、dataZoom(滑块 / 滚轮 / 触摸平移)、框选刷选、工具箱(导出 PNG / 还原)、多图 connect 联动
11
+ - **导出**:`toDataURL`(PNG)、`exportSVG`(真矢量,渲染指令录制重放)
12
+ - **无障碍**:`role="img"` + aria-label、hover 信息 aria-live 播报
13
+
14
+ ## 安装
15
+
16
+ ```bash
17
+ pnpm add @wil-works/evoke-charts
18
+ ```
19
+
20
+ ```js
21
+ import { createApp } from 'vue'
22
+ import EvokeCharts from '@wil-works/evoke-charts'
23
+ import '@wil-works/evoke-charts/styles'
24
+
25
+ const app = createApp(App)
26
+ app.use(EvokeCharts) // 全局注册 <EvChart />
27
+ ```
28
+
29
+ 或按需引入:
30
+
31
+ ```js
32
+ import { EvChart, useChart } from '@wil-works/evoke-charts'
33
+ import '@wil-works/evoke-charts/styles'
34
+ ```
35
+
36
+ ## 快速上手
37
+
38
+ ```vue
39
+ <template>
40
+ <EvChart :options="options" :height="320" />
41
+ </template>
42
+
43
+ <script setup>
44
+ import { EvChart } from '@wil-works/evoke-charts'
45
+
46
+ const options = {
47
+ type: 'line',
48
+ labels: ['一月', '二月', '三月', '四月'],
49
+ series: [
50
+ { name: '营收', data: [120, 200, 150, 80] },
51
+ { name: '成本', data: [60, 90, 70, 40] },
52
+ ],
53
+ }
54
+ </script>
55
+ ```
56
+
57
+ ## 主题接入
58
+
59
+ 图表颜色按以下 `--ev-*` 令牌实时读取(`document.documentElement` 上取值),任一缺失即整组回落到内置色板:
60
+
61
+ - 系列色:`--ev-color-primary / -success / -warning / -danger / -info`、`--ev-color-ext-violet / -cyan / -magenta`
62
+ - 表面:`--ev-bg-color`、`--ev-bg-color-overlay`、`--ev-app-card-border`
63
+ - 文字:`--ev-text-color-primary / -secondary / -danger`
64
+ - 边线:`--ev-border-color / -light / -dark`
65
+ - 填充:`--ev-fill-color-light / -dark`
66
+ - 主色 RGB 三元组(用于高亮/准线):`--ev-color-primary-rgb`
67
+
68
+ 暗色跟随约定:在 `html.dark` 选择器下重映射上述令牌(组件内部监听 `<html>` 的 `class` 变化自动重绘)。
69
+
70
+ ### 与 evoke-business-ui 搭配
71
+
72
+ `evoke-business-ui` 依赖本包并以 `EbChart`(模板 `<eb-chart>`)提供同一组件,其样式包内置了 `--eb-*` 令牌到 `--ev-*`(本包读取面)的映射,两库同用时图表自动跟随其主题、暗色与运行时换肤(其换肤时会派发 `ev-theme-change` 事件),无需额外配置。
73
+
74
+ ## 组件 API(摘要)
75
+
76
+ ### Props
77
+
78
+ | 名称 | 类型 | 默认值 | 说明 |
79
+ | --- | --- | --- | --- |
80
+ | options | Object | 必填 | 图表配置:`type`、`labels`、`series`、`legend`、`tooltip`、`title`、`dataZoom`、`theme` 等 |
81
+ | width | String / Number | `'100%'` | 容器宽 |
82
+ | height | String / Number | `400` | 容器高(数字按 px) |
83
+ | responsive | Boolean | `true` | 容器尺寸变化时自动重绘 |
84
+ | devicePixelRatio | Number | 自动 | 画布 DPR |
85
+
86
+ ### 事件
87
+
88
+ `ready` / `click` / `legend-click` / `hover` / `unhover` / `animation-end` / `data-update` / `brush-select` / `zoom`
89
+
90
+ ### 实例方法(ref)
91
+
92
+ `refresh` / `update` / `resize` / `toDataURL` / `exportSVG` / `toggleSeries` / `highlightSeries` / `clearHighlight` / `getDataExtent` / `getPlotArea` / `setTheme` / `getOption` / `setDataZoomRange` / `getDataZoomRange` / `getCanvas` / `destroy`
93
+
94
+ ### Composable
95
+
96
+ ```js
97
+ import { useChart } from '@wil-works/evoke-charts'
98
+
99
+ const { options, chartProps, chartRef, resize, exportPNG, setTheme } = useChart({ options })
100
+ ```
101
+
102
+ ```vue
103
+ <EvChart v-bind="chartProps" ref="chartRef" />
104
+ ```
105
+
106
+ ## 开发(monorepo 内)
107
+
108
+ ```bash
109
+ pnpm --filter @wil-works/evoke-charts build # 铁律检查 + vite 库模式构建
110
+ pnpm test # 仓库根统一 vitest
111
+ ```
112
+
113
+ ## License
114
+
115
+ MIT
@@ -0,0 +1,227 @@
1
+
2
+ .ev-chart[data-v-4ea20430] {
3
+ position: relative;
4
+ overflow: hidden;
5
+ border-radius: 8px;
6
+ background: var(--ev-bg-color, #ffffff);
7
+ border: 1px solid var(--ev-app-card-border, #e2e8f0);
8
+ }
9
+
10
+ /* 关键:canvas 必须能接收鼠标事件 */
11
+ .ev-chart__canvas[data-v-4ea20430] {
12
+ position: absolute;
13
+ top: 0;
14
+ left: 0;
15
+ width: 100%;
16
+ height: 100%;
17
+ display: block;
18
+ /* 触摸:纵向留给页面滚动,横向拖拽由图表接管(dataZoom 平移) */
19
+ touch-action: pan-y;
20
+ }
21
+ .ev-chart__tooltip[data-v-4ea20430] {
22
+ position: absolute;
23
+ pointer-events: none;
24
+ z-index: 100;
25
+ padding: 10px 14px;
26
+ background: var(--ev-bg-color-overlay, #ffffff);
27
+ border: 1px solid var(--ev-border-color, #e2e8f0);
28
+ border-radius: 8px;
29
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
30
+ font-size: 13px;
31
+ line-height: 1.6;
32
+ white-space: nowrap;
33
+ /* 定位由 left/top 精确计算(含水平 clamp 与垂直翻转) */
34
+ /* 位置变化平滑过渡:鼠标停下后 tooltip 滑过去,而非实时紧跟 */
35
+ transition:
36
+ left 0.25s cubic-bezier(0.22, 1, 0.36, 1),
37
+ top 0.25s cubic-bezier(0.22, 1, 0.36, 1);
38
+ will-change: left, top;
39
+ }
40
+
41
+ /* tooltip 淡入淡出(仅 opacity,位置由 left/top transition 负责) */
42
+ .ev-chart-tooltip-enter-active[data-v-4ea20430],
43
+ .ev-chart-tooltip-leave-active[data-v-4ea20430] {
44
+ transition: opacity 0.18s ease;
45
+ }
46
+
47
+ /* 进入期间禁用位置 transition,避免首次出现时从旧位置滑入 */
48
+ .ev-chart-tooltip-enter-active[data-v-4ea20430] {
49
+ transition:
50
+ opacity 0.18s ease,
51
+ left 0s,
52
+ top 0s;
53
+ }
54
+ .ev-chart-tooltip-enter-from[data-v-4ea20430],
55
+ .ev-chart-tooltip-leave-to[data-v-4ea20430] {
56
+ opacity: 0;
57
+ }
58
+
59
+ /* v-html 注入的内容不带 scoped 属性,需用 :deep() 穿透 */
60
+ [data-v-4ea20430] .ev-chart__tooltip-title {
61
+ color: var(--ev-text-color-secondary, #6b7280);
62
+ font-size: 12px;
63
+ margin-bottom: 6px;
64
+ font-weight: 500;
65
+ }
66
+ [data-v-4ea20430] .ev-chart__tooltip-item {
67
+ display: flex;
68
+ align-items: center;
69
+ gap: 8px;
70
+ padding: 2px 0;
71
+ }
72
+ [data-v-4ea20430] .ev-chart__tooltip-dot {
73
+ width: 8px;
74
+ height: 8px;
75
+ border-radius: 50%;
76
+ flex-shrink: 0;
77
+ display: inline-block;
78
+ }
79
+ [data-v-4ea20430] .ev-chart__tooltip-name {
80
+ color: var(--ev-text-color-primary, #111827);
81
+ }
82
+ [data-v-4ea20430] .ev-chart__tooltip-value {
83
+ color: var(--ev-text-color-primary, #111827);
84
+ font-weight: 600;
85
+ margin-left: auto;
86
+ padding-left: 12px;
87
+ font-variant-numeric: tabular-nums;
88
+ }
89
+
90
+ /* ══════ 空数据占位 ══════ */
91
+ .ev-chart__empty[data-v-4ea20430] {
92
+ position: absolute;
93
+ inset: 0;
94
+ display: flex;
95
+ flex-direction: column;
96
+ align-items: center;
97
+ justify-content: center;
98
+ color: var(--ev-text-color-secondary, #9ca3af);
99
+ font-size: 13px;
100
+ gap: 14px;
101
+ user-select: none;
102
+ }
103
+ .ev-chart__empty-icon-wrap[data-v-4ea20430] {
104
+ display: flex;
105
+ align-items: center;
106
+ justify-content: center;
107
+ width: 64px;
108
+ height: 64px;
109
+ border-radius: 16px;
110
+ background: var(--ev-fill-color-light, #f5f7fa);
111
+ }
112
+ .ev-chart__empty-img[data-v-4ea20430] {
113
+ width: 32px;
114
+ height: 32px;
115
+ opacity: 0.4;
116
+ }
117
+ .ev-chart__empty-text[data-v-4ea20430] {
118
+ letter-spacing: 0.04em;
119
+ font-weight: 500;
120
+ font-size: 13px;
121
+ }
122
+
123
+ /* ══════ 加载中骨架 ══════ */
124
+ .ev-chart__loading[data-v-4ea20430] {
125
+ position: absolute;
126
+ inset: 0;
127
+ display: flex;
128
+ align-items: center;
129
+ justify-content: center;
130
+ background: var(--ev-bg-color, #ffffff);
131
+ }
132
+ .ev-chart__loading-bars[data-v-4ea20430] {
133
+ display: flex;
134
+ align-items: flex-end;
135
+ gap: 6px;
136
+ height: 60px;
137
+ }
138
+ .ev-chart__loading-bar[data-v-4ea20430] {
139
+ width: 10px;
140
+ height: 40px;
141
+ border-radius: 4px;
142
+ background: var(--ev-border-color, #e2e8f0);
143
+ animation: ev-chart-shimmer-4ea20430 1.2s ease-in-out infinite;
144
+ }
145
+ @keyframes ev-chart-shimmer-4ea20430 {
146
+ 0%,
147
+ 100% {
148
+ transform: scaleY(0.4);
149
+ opacity: 0.5;
150
+ }
151
+ 50% {
152
+ transform: scaleY(1);
153
+ opacity: 1;
154
+ }
155
+ }
156
+
157
+ /* ══════ 错误边界 ══════ */
158
+ .ev-chart__error[data-v-4ea20430] {
159
+ position: absolute;
160
+ inset: 0;
161
+ display: flex;
162
+ flex-direction: column;
163
+ align-items: center;
164
+ justify-content: center;
165
+ color: var(--ev-text-color-danger, #dc2626);
166
+ background: var(--ev-bg-color, #ffffff);
167
+ gap: 8px;
168
+ padding: 20px;
169
+ text-align: center;
170
+ }
171
+ .ev-chart__error-icon[data-v-4ea20430] {
172
+ font-size: 28px;
173
+ line-height: 1;
174
+ }
175
+ .ev-chart__error-text[data-v-4ea20430] {
176
+ font-size: 12px;
177
+ line-height: 1.5;
178
+ max-width: 80%;
179
+ word-break: break-word;
180
+ }
181
+
182
+ /* ══════ 进入/离开淡入淡出 ══════ */
183
+ .ev-chart-fade-enter-active[data-v-4ea20430],
184
+ .ev-chart-fade-leave-active[data-v-4ea20430] {
185
+ transition: opacity 0.2s ease;
186
+ }
187
+ .ev-chart-fade-enter-from[data-v-4ea20430],
188
+ .ev-chart-fade-leave-to[data-v-4ea20430] {
189
+ opacity: 0;
190
+ }
191
+
192
+ /* ══════ 无障碍:sr-only ══════ */
193
+ .ev-chart__sr-only[data-v-4ea20430] {
194
+ position: absolute;
195
+ width: 1px;
196
+ height: 1px;
197
+ padding: 0;
198
+ margin: -1px;
199
+ overflow: hidden;
200
+ clip: rect(0, 0, 0, 0);
201
+ white-space: nowrap;
202
+ border: 0;
203
+ }
204
+
205
+ html.dark .ev-chart__tooltip {
206
+ background: var(--ev-bg-color-overlay, #1f2937);
207
+ border-color: var(--ev-border-color-dark, #374151);
208
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
209
+ color: var(--ev-text-color-primary, #f3f4f6);
210
+ }
211
+ html.dark .ev-chart__loading {
212
+ background: var(--ev-bg-color, #111827);
213
+ }
214
+ html.dark .ev-chart__loading-bar {
215
+ background: var(--ev-border-color-dark, #374151);
216
+ }
217
+ html.dark .ev-chart__empty {
218
+ color: var(--ev-text-color-secondary, #6b7280);
219
+ }
220
+ html.dark .ev-chart__empty-icon-wrap {
221
+ background: var(--ev-fill-color-dark, #1f2937);
222
+ }
223
+ html.dark .ev-chart__error {
224
+ background: var(--ev-bg-color, #111827);
225
+ color: var(--ev-color-danger-light-3, #f87171);
226
+ }
227
+ /*$vite$:1*/