v-auto-color 1.0.5 → 1.0.6
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/README.md +44 -0
- package/dist/chunk-754HBQKQ.mjs +222 -0
- package/dist/chunk-RECQIKGP.mjs +144 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +95 -1
- package/dist/index.mjs +93 -1
- package/dist/vite-plugin.js +3 -1
- package/dist/vite-plugin.mjs +1 -1
- package/package.json +1 -1
- package/src/core/color.ts +7 -1
- package/src/core/similarity.ts +95 -0
- package/src/index.ts +26 -1
package/README.md
CHANGED
|
@@ -81,6 +81,8 @@ interface ColorConfig {
|
|
|
81
81
|
hue?: [number, number]; // 色相范围,默认 [0, 360]
|
|
82
82
|
saturation?: [number, number]; // 饱和度范围,默认 [70, 100]
|
|
83
83
|
lightness?: [number, number]; // 亮度范围,默认 [40, 60]
|
|
84
|
+
algorithm?: 'hash' | 'levenshtein' | 'cosine' | 'jaccard'; // 算法类型,默认 'hash'
|
|
85
|
+
similarityThreshold?: number; // 相似度阈值,默认 0.7
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
// 两种使用方式
|
|
@@ -88,6 +90,48 @@ const colorSet1 = useAutoColor('category'); // 使用字符串作为类别
|
|
|
88
90
|
const colorSet2 = useAutoColor({ /* 完整配置 */ }); // 使用对象配置
|
|
89
91
|
```
|
|
90
92
|
|
|
93
|
+
### 算法说明
|
|
94
|
+
|
|
95
|
+
| 算法 | 描述 | 适用场景 |
|
|
96
|
+
|------|------|----------|
|
|
97
|
+
| `hash` | 基于文本哈希值生成颜色,相同文本返回相同颜色 | 快速生成,适用于文本完全匹配的场景 |
|
|
98
|
+
| `levenshtein` | 基于编辑距离计算相似度,相似文本返回相似颜色 | 适用于拼写相似的文本,如 "hello" 和 "hello world" |
|
|
99
|
+
| `cosine` | 基于字符频率的余弦相似度,相似文本返回相似颜色 | 适用于长度不同但字符分布相似的文本 |
|
|
100
|
+
| `jaccard` | 基于字符集合的Jaccard相似度,相似文本返回相似颜色 | 适用于字符组成相似的文本 |
|
|
101
|
+
|
|
102
|
+
### 算法配置示例
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import { useAutoColor } from 'v-auto-color';
|
|
106
|
+
|
|
107
|
+
// 使用Levenshtein算法(编辑距离)
|
|
108
|
+
const colorSet1 = useAutoColor({
|
|
109
|
+
category: 'set1',
|
|
110
|
+
algorithm: 'levenshtein',
|
|
111
|
+
similarityThreshold: 0.6 // 降低相似度阈值,使更多文本被认为相似
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// 使用余弦相似度算法
|
|
115
|
+
const colorSet2 = useAutoColor({
|
|
116
|
+
category: 'set2',
|
|
117
|
+
algorithm: 'cosine',
|
|
118
|
+
hue: [0, 180] // 限制色相范围为红色到青色
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// 使用Jaccard相似度算法
|
|
122
|
+
const colorSet3 = useAutoColor({
|
|
123
|
+
category: 'set3',
|
|
124
|
+
algorithm: 'jaccard',
|
|
125
|
+
saturation: [80, 100], // 提高饱和度,使颜色更鲜艳
|
|
126
|
+
lightness: [50, 70] // 提高亮度,使颜色更明亮
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// 获取颜色
|
|
130
|
+
const color1 = colorSet1.getColor('hello');
|
|
131
|
+
const color2 = colorSet1.getColor('hello world'); // 与 'hello' 相似,返回相似颜色
|
|
132
|
+
const color3 = colorSet1.getColor('test'); // 与 'hello' 不相似,返回不同颜色
|
|
133
|
+
```
|
|
134
|
+
|
|
91
135
|
## 性能优化
|
|
92
136
|
|
|
93
137
|
### 编译时预计算
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
|
|
5
|
+
// src/core/algorithms/algorithm.ts
|
|
6
|
+
var DEFAULT_ALGORITHM = {
|
|
7
|
+
type: "hash"
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// src/core/hash.ts
|
|
11
|
+
function murmurHash3(text) {
|
|
12
|
+
let h1 = 3735928559;
|
|
13
|
+
const c1 = 3432918353;
|
|
14
|
+
const c2 = 461845907;
|
|
15
|
+
const r1 = 15;
|
|
16
|
+
const r2 = 13;
|
|
17
|
+
const m = 5;
|
|
18
|
+
const n = 3864292196;
|
|
19
|
+
let i = 0;
|
|
20
|
+
const length = text.length;
|
|
21
|
+
let k1 = 0;
|
|
22
|
+
while (i < length) {
|
|
23
|
+
const char = text.charCodeAt(i++);
|
|
24
|
+
k1 = k1 << 8 | char;
|
|
25
|
+
}
|
|
26
|
+
k1 = k1 * c1 >>> 0;
|
|
27
|
+
k1 = (k1 << r1 | k1 >>> 32 - r1) >>> 0;
|
|
28
|
+
k1 = k1 * c2 >>> 0;
|
|
29
|
+
h1 ^= k1;
|
|
30
|
+
h1 = (h1 << r2 | h1 >>> 32 - r2) >>> 0;
|
|
31
|
+
h1 = h1 * m + n >>> 0;
|
|
32
|
+
h1 ^= length;
|
|
33
|
+
h1 ^= h1 >>> 16;
|
|
34
|
+
h1 = h1 * 2246822507 >>> 0;
|
|
35
|
+
h1 ^= h1 >>> 13;
|
|
36
|
+
h1 = h1 * 3266489909 >>> 0;
|
|
37
|
+
h1 ^= h1 >>> 16;
|
|
38
|
+
return h1;
|
|
39
|
+
}
|
|
40
|
+
function getTextHash(text) {
|
|
41
|
+
return murmurHash3(text);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/core/algorithms/hashAlgorithm.ts
|
|
45
|
+
var HashAlgorithm = class {
|
|
46
|
+
// Generate color from text using hash
|
|
47
|
+
generateColor(text, config) {
|
|
48
|
+
const hash = getTextHash(text);
|
|
49
|
+
const hue = config?.hue || [0, 360];
|
|
50
|
+
const saturation = config?.saturation || [70, 100];
|
|
51
|
+
const lightness = config?.lightness || [40, 60];
|
|
52
|
+
const hueRange = hue[1] - hue[0];
|
|
53
|
+
const calculatedHue = hue[0] + hash % hueRange;
|
|
54
|
+
const satRange = saturation[1] - saturation[0];
|
|
55
|
+
const calculatedSat = saturation[0] + (hash >> 8) % satRange;
|
|
56
|
+
const lightRange = lightness[1] - lightness[0];
|
|
57
|
+
const calculatedLight = lightness[0] + (hash >> 16) % lightRange;
|
|
58
|
+
return `hsl(${calculatedHue}, ${calculatedSat}%, ${calculatedLight}%)`;
|
|
59
|
+
}
|
|
60
|
+
// Get algorithm name
|
|
61
|
+
getName() {
|
|
62
|
+
return "hash";
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// src/core/algorithms/featureAlgorithm.ts
|
|
67
|
+
var FeatureAlgorithm = class {
|
|
68
|
+
// Calculate text features
|
|
69
|
+
calculateFeatures(text) {
|
|
70
|
+
const length = text.length;
|
|
71
|
+
const vowels = "aeiouAEIOU";
|
|
72
|
+
const consonants = "bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ";
|
|
73
|
+
let vowelCount = 0;
|
|
74
|
+
let consonantCount = 0;
|
|
75
|
+
const uniqueChars = /* @__PURE__ */ new Set();
|
|
76
|
+
let totalCharCode = 0;
|
|
77
|
+
for (const char of text) {
|
|
78
|
+
if (vowels.includes(char)) {
|
|
79
|
+
vowelCount++;
|
|
80
|
+
} else if (consonants.includes(char)) {
|
|
81
|
+
consonantCount++;
|
|
82
|
+
}
|
|
83
|
+
uniqueChars.add(char);
|
|
84
|
+
totalCharCode += char.charCodeAt(0);
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
length,
|
|
88
|
+
vowelRatio: length > 0 ? vowelCount / length : 0,
|
|
89
|
+
consonantRatio: length > 0 ? consonantCount / length : 0,
|
|
90
|
+
uniqueCharRatio: length > 0 ? uniqueChars.size / length : 0,
|
|
91
|
+
avgCharCode: length > 0 ? totalCharCode / length : 0
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
// Generate color from text using features
|
|
95
|
+
generateColor(text, config) {
|
|
96
|
+
const features = this.calculateFeatures(text);
|
|
97
|
+
const hue = config?.hue || [0, 360];
|
|
98
|
+
const saturation = config?.saturation || [70, 100];
|
|
99
|
+
const lightness = config?.lightness || [40, 60];
|
|
100
|
+
const hueRange = hue[1] - hue[0];
|
|
101
|
+
const hueFactor = (features.length % 100 + features.uniqueCharRatio * 100 + features.avgCharCode % 100) / 3;
|
|
102
|
+
const calculatedHue = hue[0] + hueFactor / 100 * hueRange;
|
|
103
|
+
const satRange = saturation[1] - saturation[0];
|
|
104
|
+
const calculatedSat = saturation[0] + features.vowelRatio * satRange;
|
|
105
|
+
const lightRange = lightness[1] - lightness[0];
|
|
106
|
+
const calculatedLight = lightness[0] + features.consonantRatio * lightRange;
|
|
107
|
+
return `hsl(${calculatedHue}, ${calculatedSat}%, ${calculatedLight}%)`;
|
|
108
|
+
}
|
|
109
|
+
// Get algorithm name
|
|
110
|
+
getName() {
|
|
111
|
+
return "feature";
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// src/core/algorithms/algorithmFactory.ts
|
|
116
|
+
var AlgorithmFactory = class {
|
|
117
|
+
// Create algorithm instance based on config
|
|
118
|
+
static createAlgorithm(config = DEFAULT_ALGORITHM) {
|
|
119
|
+
switch (config.type) {
|
|
120
|
+
case "feature":
|
|
121
|
+
return new FeatureAlgorithm();
|
|
122
|
+
case "hash":
|
|
123
|
+
default:
|
|
124
|
+
return new HashAlgorithm();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// src/core/color.ts
|
|
130
|
+
var ColorGenerator = class {
|
|
131
|
+
constructor(config = {}) {
|
|
132
|
+
__publicField(this, "config");
|
|
133
|
+
__publicField(this, "algorithm");
|
|
134
|
+
this.config = {
|
|
135
|
+
category: config.category || "default",
|
|
136
|
+
hue: config.hue || [0, 360],
|
|
137
|
+
saturation: config.saturation || [70, 100],
|
|
138
|
+
lightness: config.lightness || [40, 60],
|
|
139
|
+
algorithm: config.algorithm || DEFAULT_ALGORITHM
|
|
140
|
+
};
|
|
141
|
+
this.algorithm = AlgorithmFactory.createAlgorithm(this.config.algorithm);
|
|
142
|
+
}
|
|
143
|
+
// Get color for text
|
|
144
|
+
getColor(text) {
|
|
145
|
+
return this.algorithm.generateColor(text, this.config);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// src/vite-plugin.ts
|
|
150
|
+
function createFilter(include, exclude) {
|
|
151
|
+
return function(id) {
|
|
152
|
+
const included = include.some((pattern) => {
|
|
153
|
+
if (pattern === "**/*") return true;
|
|
154
|
+
const regex = new RegExp(pattern.replace(/\*/g, ".*"));
|
|
155
|
+
return regex.test(id);
|
|
156
|
+
});
|
|
157
|
+
const excluded = exclude && exclude.some((pattern) => {
|
|
158
|
+
const regex = new RegExp(pattern.replace(/\*/g, ".*"));
|
|
159
|
+
return regex.test(id);
|
|
160
|
+
});
|
|
161
|
+
return included && !excluded;
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function viteAutoColorPlugin() {
|
|
165
|
+
const filter = createFilter(["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "**/*.vue"]);
|
|
166
|
+
return {
|
|
167
|
+
name: "v-auto-color",
|
|
168
|
+
// Analyze code during build and inject precomputed colors
|
|
169
|
+
transform(code, id) {
|
|
170
|
+
if (!filter(id)) return null;
|
|
171
|
+
if (!code.includes("useAutoColor")) return null;
|
|
172
|
+
const useAutoColorRegex = /useAutoColor\s*\(\s*(?:(['"])([^'"]+)\1|\{[^}]*\})\s*\)/g;
|
|
173
|
+
const getColorRegex = /\.getColor\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
174
|
+
let match;
|
|
175
|
+
const colorSets = /* @__PURE__ */ new Set();
|
|
176
|
+
while ((match = useAutoColorRegex.exec(code)) !== null) {
|
|
177
|
+
let category = "default";
|
|
178
|
+
if (match[2]) {
|
|
179
|
+
category = match[2];
|
|
180
|
+
} else {
|
|
181
|
+
const configMatch = match[0].match(/category\s*:\s*['"]([^'"]+)['"]/);
|
|
182
|
+
if (configMatch) {
|
|
183
|
+
category = configMatch[1];
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
colorSets.add(category);
|
|
187
|
+
}
|
|
188
|
+
const texts = /* @__PURE__ */ new Set();
|
|
189
|
+
while ((match = getColorRegex.exec(code)) !== null) {
|
|
190
|
+
texts.add(match[1]);
|
|
191
|
+
}
|
|
192
|
+
const colorUsage = {};
|
|
193
|
+
colorSets.forEach((category) => {
|
|
194
|
+
if (!colorUsage[category]) {
|
|
195
|
+
colorUsage[category] = {};
|
|
196
|
+
}
|
|
197
|
+
texts.forEach((text) => {
|
|
198
|
+
const generator = new ColorGenerator({ category });
|
|
199
|
+
const color = generator.getColor(text);
|
|
200
|
+
colorUsage[category][text] = color;
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
if (Object.keys(colorUsage).length > 0) {
|
|
204
|
+
const precomputedCode = `
|
|
205
|
+
// Precomputed colors for v-auto-color
|
|
206
|
+
import { __internal__setPrecomputedColors } from 'v-auto-color';
|
|
207
|
+
__internal__setPrecomputedColors(${JSON.stringify(colorUsage, null, 2)});
|
|
208
|
+
`;
|
|
209
|
+
code = precomputedCode + "\n" + code;
|
|
210
|
+
}
|
|
211
|
+
return code;
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
var vite_plugin_default = viteAutoColorPlugin;
|
|
216
|
+
|
|
217
|
+
export {
|
|
218
|
+
__publicField,
|
|
219
|
+
ColorGenerator,
|
|
220
|
+
viteAutoColorPlugin,
|
|
221
|
+
vite_plugin_default
|
|
222
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
|
|
5
|
+
// src/core/hash.ts
|
|
6
|
+
function murmurHash3(text) {
|
|
7
|
+
let h1 = 3735928559;
|
|
8
|
+
const c1 = 3432918353;
|
|
9
|
+
const c2 = 461845907;
|
|
10
|
+
const r1 = 15;
|
|
11
|
+
const r2 = 13;
|
|
12
|
+
const m = 5;
|
|
13
|
+
const n = 3864292196;
|
|
14
|
+
let i = 0;
|
|
15
|
+
const length = text.length;
|
|
16
|
+
let k1 = 0;
|
|
17
|
+
while (i < length) {
|
|
18
|
+
const char = text.charCodeAt(i++);
|
|
19
|
+
k1 = k1 << 8 | char;
|
|
20
|
+
}
|
|
21
|
+
k1 = k1 * c1 >>> 0;
|
|
22
|
+
k1 = (k1 << r1 | k1 >>> 32 - r1) >>> 0;
|
|
23
|
+
k1 = k1 * c2 >>> 0;
|
|
24
|
+
h1 ^= k1;
|
|
25
|
+
h1 = (h1 << r2 | h1 >>> 32 - r2) >>> 0;
|
|
26
|
+
h1 = h1 * m + n >>> 0;
|
|
27
|
+
h1 ^= length;
|
|
28
|
+
h1 ^= h1 >>> 16;
|
|
29
|
+
h1 = h1 * 2246822507 >>> 0;
|
|
30
|
+
h1 ^= h1 >>> 13;
|
|
31
|
+
h1 = h1 * 3266489909 >>> 0;
|
|
32
|
+
h1 ^= h1 >>> 16;
|
|
33
|
+
return h1;
|
|
34
|
+
}
|
|
35
|
+
function getTextHash(text) {
|
|
36
|
+
return murmurHash3(text);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/core/color.ts
|
|
40
|
+
var ColorGenerator = class {
|
|
41
|
+
constructor(config = {}) {
|
|
42
|
+
__publicField(this, "config");
|
|
43
|
+
this.config = {
|
|
44
|
+
category: config.category || "default",
|
|
45
|
+
hue: config.hue || [0, 360],
|
|
46
|
+
saturation: config.saturation || [70, 100],
|
|
47
|
+
lightness: config.lightness || [40, 60],
|
|
48
|
+
algorithm: config.algorithm || "hash",
|
|
49
|
+
similarityThreshold: config.similarityThreshold || 0.7
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
// Generate color from hash value
|
|
53
|
+
generateColor(hash) {
|
|
54
|
+
const { hue, saturation, lightness } = this.config;
|
|
55
|
+
const hueRange = hue[1] - hue[0];
|
|
56
|
+
const calculatedHue = hue[0] + hash % hueRange;
|
|
57
|
+
const satRange = saturation[1] - saturation[0];
|
|
58
|
+
const calculatedSat = saturation[0] + (hash >> 8) % satRange;
|
|
59
|
+
const lightRange = lightness[1] - lightness[0];
|
|
60
|
+
const calculatedLight = lightness[0] + (hash >> 16) % lightRange;
|
|
61
|
+
return `hsl(${calculatedHue}, ${calculatedSat}%, ${calculatedLight}%)`;
|
|
62
|
+
}
|
|
63
|
+
// Get color for text (wrapper method)
|
|
64
|
+
getColor(text, hashFn) {
|
|
65
|
+
const hash = hashFn(text);
|
|
66
|
+
return this.generateColor(hash);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// src/vite-plugin.ts
|
|
71
|
+
function createFilter(include, exclude) {
|
|
72
|
+
return function(id) {
|
|
73
|
+
const included = include.some((pattern) => {
|
|
74
|
+
if (pattern === "**/*") return true;
|
|
75
|
+
const regex = new RegExp(pattern.replace(/\*/g, ".*"));
|
|
76
|
+
return regex.test(id);
|
|
77
|
+
});
|
|
78
|
+
const excluded = exclude && exclude.some((pattern) => {
|
|
79
|
+
const regex = new RegExp(pattern.replace(/\*/g, ".*"));
|
|
80
|
+
return regex.test(id);
|
|
81
|
+
});
|
|
82
|
+
return included && !excluded;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function viteAutoColorPlugin() {
|
|
86
|
+
const filter = createFilter(["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "**/*.vue"]);
|
|
87
|
+
return {
|
|
88
|
+
name: "v-auto-color",
|
|
89
|
+
// Analyze code during build and inject precomputed colors
|
|
90
|
+
transform(code, id) {
|
|
91
|
+
if (!filter(id)) return null;
|
|
92
|
+
if (!code.includes("useAutoColor")) return null;
|
|
93
|
+
const useAutoColorRegex = /useAutoColor\s*\(\s*(?:(['"])([^'"]+)\1|\{[^}]*\})\s*\)/g;
|
|
94
|
+
const getColorRegex = /\.getColor\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
95
|
+
let match;
|
|
96
|
+
const colorSets = /* @__PURE__ */ new Set();
|
|
97
|
+
while ((match = useAutoColorRegex.exec(code)) !== null) {
|
|
98
|
+
let category = "default";
|
|
99
|
+
if (match[2]) {
|
|
100
|
+
category = match[2];
|
|
101
|
+
} else {
|
|
102
|
+
const configMatch = match[0].match(/category\s*:\s*['"]([^'"]+)['"]/);
|
|
103
|
+
if (configMatch) {
|
|
104
|
+
category = configMatch[1];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
colorSets.add(category);
|
|
108
|
+
}
|
|
109
|
+
const texts = /* @__PURE__ */ new Set();
|
|
110
|
+
while ((match = getColorRegex.exec(code)) !== null) {
|
|
111
|
+
texts.add(match[1]);
|
|
112
|
+
}
|
|
113
|
+
const colorUsage = {};
|
|
114
|
+
colorSets.forEach((category) => {
|
|
115
|
+
if (!colorUsage[category]) {
|
|
116
|
+
colorUsage[category] = {};
|
|
117
|
+
}
|
|
118
|
+
texts.forEach((text) => {
|
|
119
|
+
const generator = new ColorGenerator({ category });
|
|
120
|
+
const color = generator.getColor(text, getTextHash);
|
|
121
|
+
colorUsage[category][text] = color;
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
if (Object.keys(colorUsage).length > 0) {
|
|
125
|
+
const precomputedCode = `
|
|
126
|
+
// Precomputed colors for v-auto-color
|
|
127
|
+
import { __internal__setPrecomputedColors } from 'v-auto-color';
|
|
128
|
+
__internal__setPrecomputedColors(${JSON.stringify(colorUsage, null, 2)});
|
|
129
|
+
`;
|
|
130
|
+
code = precomputedCode + "\n" + code;
|
|
131
|
+
}
|
|
132
|
+
return code;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
var vite_plugin_default = viteAutoColorPlugin;
|
|
137
|
+
|
|
138
|
+
export {
|
|
139
|
+
__publicField,
|
|
140
|
+
getTextHash,
|
|
141
|
+
ColorGenerator,
|
|
142
|
+
viteAutoColorPlugin,
|
|
143
|
+
vite_plugin_default
|
|
144
|
+
};
|
package/dist/index.d.mts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
export { default as viteAutoColorPlugin } from './vite-plugin.mjs';
|
|
2
2
|
import 'vite';
|
|
3
3
|
|
|
4
|
+
type AlgorithmType = 'hash' | 'levenshtein' | 'cosine' | 'jaccard';
|
|
4
5
|
interface ColorConfig {
|
|
5
6
|
category?: string;
|
|
6
7
|
hue?: [number, number];
|
|
7
8
|
saturation?: [number, number];
|
|
8
9
|
lightness?: [number, number];
|
|
10
|
+
algorithm?: AlgorithmType;
|
|
11
|
+
similarityThreshold?: number;
|
|
9
12
|
}
|
|
10
13
|
|
|
11
14
|
declare class ColorSet {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
export { default as viteAutoColorPlugin } from './vite-plugin.js';
|
|
2
2
|
import 'vite';
|
|
3
3
|
|
|
4
|
+
type AlgorithmType = 'hash' | 'levenshtein' | 'cosine' | 'jaccard';
|
|
4
5
|
interface ColorConfig {
|
|
5
6
|
category?: string;
|
|
6
7
|
hue?: [number, number];
|
|
7
8
|
saturation?: [number, number];
|
|
8
9
|
lightness?: [number, number];
|
|
10
|
+
algorithm?: AlgorithmType;
|
|
11
|
+
similarityThreshold?: number;
|
|
9
12
|
}
|
|
10
13
|
|
|
11
14
|
declare class ColorSet {
|
package/dist/index.js
CHANGED
|
@@ -71,7 +71,9 @@ var ColorGenerator = class {
|
|
|
71
71
|
category: config.category || "default",
|
|
72
72
|
hue: config.hue || [0, 360],
|
|
73
73
|
saturation: config.saturation || [70, 100],
|
|
74
|
-
lightness: config.lightness || [40, 60]
|
|
74
|
+
lightness: config.lightness || [40, 60],
|
|
75
|
+
algorithm: config.algorithm || "hash",
|
|
76
|
+
similarityThreshold: config.similarityThreshold || 0.7
|
|
75
77
|
};
|
|
76
78
|
}
|
|
77
79
|
// Generate color from hash value
|
|
@@ -92,6 +94,81 @@ var ColorGenerator = class {
|
|
|
92
94
|
}
|
|
93
95
|
};
|
|
94
96
|
|
|
97
|
+
// src/core/similarity.ts
|
|
98
|
+
function levenshteinDistance(str1, str2) {
|
|
99
|
+
const matrix = [];
|
|
100
|
+
for (let i = 0; i <= str2.length; i++) {
|
|
101
|
+
matrix[i] = [i];
|
|
102
|
+
}
|
|
103
|
+
for (let j = 0; j <= str1.length; j++) {
|
|
104
|
+
matrix[0][j] = j;
|
|
105
|
+
}
|
|
106
|
+
for (let i = 1; i <= str2.length; i++) {
|
|
107
|
+
for (let j = 1; j <= str1.length; j++) {
|
|
108
|
+
if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
|
|
109
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
110
|
+
} else {
|
|
111
|
+
matrix[i][j] = Math.min(
|
|
112
|
+
matrix[i - 1][j - 1] + 1,
|
|
113
|
+
// substitution
|
|
114
|
+
matrix[i][j - 1] + 1,
|
|
115
|
+
// insertion
|
|
116
|
+
matrix[i - 1][j] + 1
|
|
117
|
+
// deletion
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const maxLength = Math.max(str1.length, str2.length);
|
|
123
|
+
return maxLength === 0 ? 1 : 1 - matrix[str2.length][str1.length] / maxLength;
|
|
124
|
+
}
|
|
125
|
+
function cosineSimilarity(str1, str2) {
|
|
126
|
+
const freq1 = /* @__PURE__ */ new Map();
|
|
127
|
+
const freq2 = /* @__PURE__ */ new Map();
|
|
128
|
+
const allChars = /* @__PURE__ */ new Set();
|
|
129
|
+
for (const char of str1) {
|
|
130
|
+
freq1.set(char, (freq1.get(char) || 0) + 1);
|
|
131
|
+
allChars.add(char);
|
|
132
|
+
}
|
|
133
|
+
for (const char of str2) {
|
|
134
|
+
freq2.set(char, (freq2.get(char) || 0) + 1);
|
|
135
|
+
allChars.add(char);
|
|
136
|
+
}
|
|
137
|
+
let dotProduct = 0;
|
|
138
|
+
let magnitude1 = 0;
|
|
139
|
+
let magnitude2 = 0;
|
|
140
|
+
for (const char of allChars) {
|
|
141
|
+
const f1 = freq1.get(char) || 0;
|
|
142
|
+
const f2 = freq2.get(char) || 0;
|
|
143
|
+
dotProduct += f1 * f2;
|
|
144
|
+
magnitude1 += f1 * f1;
|
|
145
|
+
magnitude2 += f2 * f2;
|
|
146
|
+
}
|
|
147
|
+
if (magnitude1 === 0 || magnitude2 === 0) {
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
return dotProduct / (Math.sqrt(magnitude1) * Math.sqrt(magnitude2));
|
|
151
|
+
}
|
|
152
|
+
function jaccardSimilarity(str1, str2) {
|
|
153
|
+
const set1 = new Set(str1);
|
|
154
|
+
const set2 = new Set(str2);
|
|
155
|
+
const intersection = new Set([...set1].filter((x) => set2.has(x)));
|
|
156
|
+
const union = /* @__PURE__ */ new Set([...set1, ...set2]);
|
|
157
|
+
return union.size === 0 ? 0 : intersection.size / union.size;
|
|
158
|
+
}
|
|
159
|
+
function getSimilarityScore(str1, str2, algorithm) {
|
|
160
|
+
switch (algorithm) {
|
|
161
|
+
case "levenshtein":
|
|
162
|
+
return levenshteinDistance(str1, str2);
|
|
163
|
+
case "cosine":
|
|
164
|
+
return cosineSimilarity(str1, str2);
|
|
165
|
+
case "jaccard":
|
|
166
|
+
return jaccardSimilarity(str1, str2);
|
|
167
|
+
default:
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
95
172
|
// src/vite-plugin.ts
|
|
96
173
|
function createFilter(include, exclude) {
|
|
97
174
|
return function(id) {
|
|
@@ -179,6 +256,23 @@ var ColorSet = class {
|
|
|
179
256
|
if (precomputedColors[this.category] && precomputedColors[this.category][text]) {
|
|
180
257
|
return precomputedColors[this.category][text];
|
|
181
258
|
}
|
|
259
|
+
if (this.config.algorithm && this.config.algorithm !== "hash" && precomputedColors[this.category]) {
|
|
260
|
+
const similarityThreshold = this.config.similarityThreshold || 0.7;
|
|
261
|
+
let mostSimilarText = text;
|
|
262
|
+
let highestSimilarity = 0;
|
|
263
|
+
for (const existingText in precomputedColors[this.category]) {
|
|
264
|
+
const similarity = getSimilarityScore(text, existingText, this.config.algorithm);
|
|
265
|
+
if (similarity > highestSimilarity && similarity >= similarityThreshold) {
|
|
266
|
+
highestSimilarity = similarity;
|
|
267
|
+
mostSimilarText = existingText;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (mostSimilarText !== text && precomputedColors[this.category][mostSimilarText]) {
|
|
271
|
+
const color2 = precomputedColors[this.category][mostSimilarText];
|
|
272
|
+
precomputedColors[this.category][text] = color2;
|
|
273
|
+
return color2;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
182
276
|
const color = this.generator.getColor(text, getTextHash);
|
|
183
277
|
if (!precomputedColors[this.category]) {
|
|
184
278
|
precomputedColors[this.category] = {};
|
package/dist/index.mjs
CHANGED
|
@@ -3,7 +3,82 @@ import {
|
|
|
3
3
|
__publicField,
|
|
4
4
|
getTextHash,
|
|
5
5
|
viteAutoColorPlugin
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-RECQIKGP.mjs";
|
|
7
|
+
|
|
8
|
+
// src/core/similarity.ts
|
|
9
|
+
function levenshteinDistance(str1, str2) {
|
|
10
|
+
const matrix = [];
|
|
11
|
+
for (let i = 0; i <= str2.length; i++) {
|
|
12
|
+
matrix[i] = [i];
|
|
13
|
+
}
|
|
14
|
+
for (let j = 0; j <= str1.length; j++) {
|
|
15
|
+
matrix[0][j] = j;
|
|
16
|
+
}
|
|
17
|
+
for (let i = 1; i <= str2.length; i++) {
|
|
18
|
+
for (let j = 1; j <= str1.length; j++) {
|
|
19
|
+
if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
|
|
20
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
21
|
+
} else {
|
|
22
|
+
matrix[i][j] = Math.min(
|
|
23
|
+
matrix[i - 1][j - 1] + 1,
|
|
24
|
+
// substitution
|
|
25
|
+
matrix[i][j - 1] + 1,
|
|
26
|
+
// insertion
|
|
27
|
+
matrix[i - 1][j] + 1
|
|
28
|
+
// deletion
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const maxLength = Math.max(str1.length, str2.length);
|
|
34
|
+
return maxLength === 0 ? 1 : 1 - matrix[str2.length][str1.length] / maxLength;
|
|
35
|
+
}
|
|
36
|
+
function cosineSimilarity(str1, str2) {
|
|
37
|
+
const freq1 = /* @__PURE__ */ new Map();
|
|
38
|
+
const freq2 = /* @__PURE__ */ new Map();
|
|
39
|
+
const allChars = /* @__PURE__ */ new Set();
|
|
40
|
+
for (const char of str1) {
|
|
41
|
+
freq1.set(char, (freq1.get(char) || 0) + 1);
|
|
42
|
+
allChars.add(char);
|
|
43
|
+
}
|
|
44
|
+
for (const char of str2) {
|
|
45
|
+
freq2.set(char, (freq2.get(char) || 0) + 1);
|
|
46
|
+
allChars.add(char);
|
|
47
|
+
}
|
|
48
|
+
let dotProduct = 0;
|
|
49
|
+
let magnitude1 = 0;
|
|
50
|
+
let magnitude2 = 0;
|
|
51
|
+
for (const char of allChars) {
|
|
52
|
+
const f1 = freq1.get(char) || 0;
|
|
53
|
+
const f2 = freq2.get(char) || 0;
|
|
54
|
+
dotProduct += f1 * f2;
|
|
55
|
+
magnitude1 += f1 * f1;
|
|
56
|
+
magnitude2 += f2 * f2;
|
|
57
|
+
}
|
|
58
|
+
if (magnitude1 === 0 || magnitude2 === 0) {
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
return dotProduct / (Math.sqrt(magnitude1) * Math.sqrt(magnitude2));
|
|
62
|
+
}
|
|
63
|
+
function jaccardSimilarity(str1, str2) {
|
|
64
|
+
const set1 = new Set(str1);
|
|
65
|
+
const set2 = new Set(str2);
|
|
66
|
+
const intersection = new Set([...set1].filter((x) => set2.has(x)));
|
|
67
|
+
const union = /* @__PURE__ */ new Set([...set1, ...set2]);
|
|
68
|
+
return union.size === 0 ? 0 : intersection.size / union.size;
|
|
69
|
+
}
|
|
70
|
+
function getSimilarityScore(str1, str2, algorithm) {
|
|
71
|
+
switch (algorithm) {
|
|
72
|
+
case "levenshtein":
|
|
73
|
+
return levenshteinDistance(str1, str2);
|
|
74
|
+
case "cosine":
|
|
75
|
+
return cosineSimilarity(str1, str2);
|
|
76
|
+
case "jaccard":
|
|
77
|
+
return jaccardSimilarity(str1, str2);
|
|
78
|
+
default:
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
7
82
|
|
|
8
83
|
// src/index.ts
|
|
9
84
|
var precomputedColors = {};
|
|
@@ -25,6 +100,23 @@ var ColorSet = class {
|
|
|
25
100
|
if (precomputedColors[this.category] && precomputedColors[this.category][text]) {
|
|
26
101
|
return precomputedColors[this.category][text];
|
|
27
102
|
}
|
|
103
|
+
if (this.config.algorithm && this.config.algorithm !== "hash" && precomputedColors[this.category]) {
|
|
104
|
+
const similarityThreshold = this.config.similarityThreshold || 0.7;
|
|
105
|
+
let mostSimilarText = text;
|
|
106
|
+
let highestSimilarity = 0;
|
|
107
|
+
for (const existingText in precomputedColors[this.category]) {
|
|
108
|
+
const similarity = getSimilarityScore(text, existingText, this.config.algorithm);
|
|
109
|
+
if (similarity > highestSimilarity && similarity >= similarityThreshold) {
|
|
110
|
+
highestSimilarity = similarity;
|
|
111
|
+
mostSimilarText = existingText;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (mostSimilarText !== text && precomputedColors[this.category][mostSimilarText]) {
|
|
115
|
+
const color2 = precomputedColors[this.category][mostSimilarText];
|
|
116
|
+
precomputedColors[this.category][text] = color2;
|
|
117
|
+
return color2;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
28
120
|
const color = this.generator.getColor(text, getTextHash);
|
|
29
121
|
if (!precomputedColors[this.category]) {
|
|
30
122
|
precomputedColors[this.category] = {};
|
package/dist/vite-plugin.js
CHANGED
|
@@ -69,7 +69,9 @@ var ColorGenerator = class {
|
|
|
69
69
|
category: config.category || "default",
|
|
70
70
|
hue: config.hue || [0, 360],
|
|
71
71
|
saturation: config.saturation || [70, 100],
|
|
72
|
-
lightness: config.lightness || [40, 60]
|
|
72
|
+
lightness: config.lightness || [40, 60],
|
|
73
|
+
algorithm: config.algorithm || "hash",
|
|
74
|
+
similarityThreshold: config.similarityThreshold || 0.7
|
|
73
75
|
};
|
|
74
76
|
}
|
|
75
77
|
// Generate color from hash value
|
package/dist/vite-plugin.mjs
CHANGED
package/package.json
CHANGED
package/src/core/color.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
// Color generation based on hash values
|
|
2
|
+
export type AlgorithmType = 'hash' | 'levenshtein' | 'cosine' | 'jaccard';
|
|
3
|
+
|
|
2
4
|
export interface ColorConfig {
|
|
3
5
|
category?: string;
|
|
4
6
|
hue?: [number, number];
|
|
5
7
|
saturation?: [number, number];
|
|
6
8
|
lightness?: [number, number];
|
|
9
|
+
algorithm?: AlgorithmType;
|
|
10
|
+
similarityThreshold?: number; // 相似度阈值,用于相似度算法
|
|
7
11
|
}
|
|
8
12
|
|
|
9
13
|
export class ColorGenerator {
|
|
@@ -14,7 +18,9 @@ export class ColorGenerator {
|
|
|
14
18
|
category: config.category || 'default',
|
|
15
19
|
hue: config.hue || [0, 360],
|
|
16
20
|
saturation: config.saturation || [70, 100],
|
|
17
|
-
lightness: config.lightness || [40, 60]
|
|
21
|
+
lightness: config.lightness || [40, 60],
|
|
22
|
+
algorithm: config.algorithm || 'hash',
|
|
23
|
+
similarityThreshold: config.similarityThreshold || 0.7
|
|
18
24
|
};
|
|
19
25
|
}
|
|
20
26
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// String similarity algorithms
|
|
2
|
+
|
|
3
|
+
// Levenshtein distance algorithm (edit distance)
|
|
4
|
+
export function levenshteinDistance(str1: string, str2: string): number {
|
|
5
|
+
const matrix: number[][] = [];
|
|
6
|
+
|
|
7
|
+
// Initialize matrix
|
|
8
|
+
for (let i = 0; i <= str2.length; i++) {
|
|
9
|
+
matrix[i] = [i];
|
|
10
|
+
}
|
|
11
|
+
for (let j = 0; j <= str1.length; j++) {
|
|
12
|
+
matrix[0][j] = j;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Calculate Levenshtein distance
|
|
16
|
+
for (let i = 1; i <= str2.length; i++) {
|
|
17
|
+
for (let j = 1; j <= str1.length; j++) {
|
|
18
|
+
if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
|
|
19
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
20
|
+
} else {
|
|
21
|
+
matrix[i][j] = Math.min(
|
|
22
|
+
matrix[i - 1][j - 1] + 1, // substitution
|
|
23
|
+
matrix[i][j - 1] + 1, // insertion
|
|
24
|
+
matrix[i - 1][j] + 1 // deletion
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Calculate similarity score (0-1)
|
|
31
|
+
const maxLength = Math.max(str1.length, str2.length);
|
|
32
|
+
return maxLength === 0 ? 1 : 1 - matrix[str2.length][str1.length] / maxLength;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Cosine similarity algorithm
|
|
36
|
+
export function cosineSimilarity(str1: string, str2: string): number {
|
|
37
|
+
// Create character frequency maps
|
|
38
|
+
const freq1 = new Map<string, number>();
|
|
39
|
+
const freq2 = new Map<string, number>();
|
|
40
|
+
const allChars = new Set<string>();
|
|
41
|
+
|
|
42
|
+
// Count frequencies for str1
|
|
43
|
+
for (const char of str1) {
|
|
44
|
+
freq1.set(char, (freq1.get(char) || 0) + 1);
|
|
45
|
+
allChars.add(char);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Count frequencies for str2
|
|
49
|
+
for (const char of str2) {
|
|
50
|
+
freq2.set(char, (freq2.get(char) || 0) + 1);
|
|
51
|
+
allChars.add(char);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Calculate dot product
|
|
55
|
+
let dotProduct = 0;
|
|
56
|
+
let magnitude1 = 0;
|
|
57
|
+
let magnitude2 = 0;
|
|
58
|
+
|
|
59
|
+
for (const char of allChars) {
|
|
60
|
+
const f1 = freq1.get(char) || 0;
|
|
61
|
+
const f2 = freq2.get(char) || 0;
|
|
62
|
+
dotProduct += f1 * f2;
|
|
63
|
+
magnitude1 += f1 * f1;
|
|
64
|
+
magnitude2 += f2 * f2;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Calculate cosine similarity
|
|
68
|
+
if (magnitude1 === 0 || magnitude2 === 0) {
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
return dotProduct / (Math.sqrt(magnitude1) * Math.sqrt(magnitude2));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Jaccard similarity algorithm
|
|
75
|
+
export function jaccardSimilarity(str1: string, str2: string): number {
|
|
76
|
+
const set1 = new Set(str1);
|
|
77
|
+
const set2 = new Set(str2);
|
|
78
|
+
const intersection = new Set([...set1].filter(x => set2.has(x)));
|
|
79
|
+
const union = new Set([...set1, ...set2]);
|
|
80
|
+
return union.size === 0 ? 0 : intersection.size / union.size;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Get similarity score based on algorithm
|
|
84
|
+
export function getSimilarityScore(str1: string, str2: string, algorithm: 'levenshtein' | 'cosine' | 'jaccard'): number {
|
|
85
|
+
switch (algorithm) {
|
|
86
|
+
case 'levenshtein':
|
|
87
|
+
return levenshteinDistance(str1, str2);
|
|
88
|
+
case 'cosine':
|
|
89
|
+
return cosineSimilarity(str1, str2);
|
|
90
|
+
case 'jaccard':
|
|
91
|
+
return jaccardSimilarity(str1, str2);
|
|
92
|
+
default:
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getTextHash } from './core/hash';
|
|
2
2
|
import { ColorConfig, ColorGenerator } from './core/color';
|
|
3
|
+
import { getSimilarityScore } from './core/similarity';
|
|
3
4
|
import { viteAutoColorPlugin } from './vite-plugin';
|
|
4
5
|
|
|
5
6
|
// Precomputed colors cache (filled by Vite plugin at build time)
|
|
@@ -29,7 +30,31 @@ export class ColorSet {
|
|
|
29
30
|
return precomputedColors[this.category][text];
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
//
|
|
33
|
+
// Check for similar strings in the same category (if using similarity algorithm)
|
|
34
|
+
if (this.config.algorithm && this.config.algorithm !== 'hash' && precomputedColors[this.category]) {
|
|
35
|
+
const similarityThreshold = this.config.similarityThreshold || 0.7;
|
|
36
|
+
let mostSimilarText = text;
|
|
37
|
+
let highestSimilarity = 0;
|
|
38
|
+
|
|
39
|
+
// Find most similar text in the category
|
|
40
|
+
for (const existingText in precomputedColors[this.category]) {
|
|
41
|
+
const similarity = getSimilarityScore(text, existingText, this.config.algorithm as 'levenshtein' | 'cosine' | 'jaccard');
|
|
42
|
+
if (similarity > highestSimilarity && similarity >= similarityThreshold) {
|
|
43
|
+
highestSimilarity = similarity;
|
|
44
|
+
mostSimilarText = existingText;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Use color from most similar text if found
|
|
49
|
+
if (mostSimilarText !== text && precomputedColors[this.category][mostSimilarText]) {
|
|
50
|
+
const color = precomputedColors[this.category][mostSimilarText];
|
|
51
|
+
// Cache the color for current text
|
|
52
|
+
precomputedColors[this.category][text] = color;
|
|
53
|
+
return color;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Generate color at runtime if not precomputed and no similar text found
|
|
33
58
|
const color = this.generator.getColor(text, getTextHash);
|
|
34
59
|
|
|
35
60
|
// Cache the generated color for future use
|