koishi-plugin-drf 0.0.3 → 0.0.5

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/lib/index.d.ts CHANGED
@@ -1,15 +1,51 @@
1
- import { Context, Schema } from 'koishi';
1
+ import { Context, Schema, h } from 'koishi';
2
+ declare module 'koishi' {
3
+ interface Context {
4
+ canvas: CanvasService;
5
+ }
6
+ }
7
+ interface CanvasRenderingContext2D<C extends Canvas = Canvas, I extends Image = Image> extends Omit<globalThis.CanvasRenderingContext2D, 'drawImage' | 'canvas'> {
8
+ canvas: C;
9
+ drawImage(image: C | I, dx: number, dy: number): void;
10
+ drawImage(image: C | I, dx: number, dy: number, dw: number, dh: number): void;
11
+ drawImage(image: C | I, sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number): void;
12
+ }
13
+ interface Canvas {
14
+ width: number;
15
+ height: number;
16
+ getContext(type: '2d'): CanvasRenderingContext2D;
17
+ toBuffer(type: 'image/png'): Promise<Buffer>;
18
+ toDataURL(type: 'image/png'): Promise<string>;
19
+ dispose(): Promise<void>;
20
+ }
21
+ interface Image {
22
+ readonly naturalWidth: number;
23
+ readonly naturalHeight: number;
24
+ dispose(): Promise<void>;
25
+ }
26
+ declare abstract class CanvasService {
27
+ abstract createCanvas(width: number, height: number): Promise<Canvas>;
28
+ abstract loadImage(source: string | URL | Buffer | ArrayBufferLike): Promise<Image>;
29
+ render(width: number, height: number, callback: (ctx: CanvasRenderingContext2D) => Promise<void>): Promise<h>;
30
+ }
2
31
  export declare const name = "function-plotter";
32
+ export declare const using: readonly ["canvas"];
3
33
  export interface Config {
4
34
  width: number;
5
35
  height: number;
6
36
  xMin: number;
7
37
  xMax: number;
8
38
  step: number;
39
+ tMin: number;
40
+ tMax: number;
41
+ tSamples: number;
42
+ implicitResolution: number;
9
43
  backgroundColor: string;
10
44
  lineColor: string;
11
45
  gridColor: string;
12
46
  showGrid: boolean;
13
47
  }
48
+ export declare const usage: string;
14
49
  export declare const Config: Schema<Config>;
15
50
  export declare function apply(ctx: Context, config: Config): void;
51
+ export {};
package/lib/index.js CHANGED
@@ -22,111 +22,365 @@ var src_exports = {};
22
22
  __export(src_exports, {
23
23
  Config: () => Config,
24
24
  apply: () => apply,
25
- name: () => name
25
+ name: () => name,
26
+ usage: () => usage,
27
+ using: () => using
26
28
  });
27
29
  module.exports = __toCommonJS(src_exports);
28
30
  var import_koishi = require("koishi");
29
- var import_canvas = require("canvas");
30
31
  var import_mathjs = require("mathjs");
31
32
  var name = "function-plotter";
33
+ var using = ["canvas"];
34
+ var usage = `
35
+ ### Math Forest 群聊用函数绘图器 使用说明
36
+
37
+ #### 支持格式
38
+ 1. 显式函数(y= 可省略)
39
+ - x^2
40
+ - y = sin(x) + cos(x)
41
+ - x = y^2 - 3
42
+
43
+ 3. 隐函数(以 0= 开头)
44
+ - 0 = x^2 + y^2 - 1
45
+ - 0 = sin(x) - y
46
+
47
+ 4. 参数方程
48
+ - drf (t)[t * cos(3 * t), t * sin(3 * t)]
49
+
50
+ #### 缩放参数
51
+ @数值 -> 坐标范围为 [-n, n]
52
+
53
+ #### 示例
54
+ - drf x^3 @30
55
+ - drf 0 = x^2+y^2-25
56
+ - drf (t)[t * cos(t), t * sin(t)] @15
57
+
58
+ ---
59
+
60
+ #### 关于
61
+ \`\`\`
62
+
63
+ Duo : https://www.mduo.cloud/
64
+ Math Forest : 663251235
65
+ \`\`\`
66
+
67
+ `.trim();
32
68
  var Config = import_koishi.Schema.object({
33
- width: import_koishi.Schema.number().default(600).description("宽度"),
34
- height: import_koishi.Schema.number().default(600).description("高度"),
35
- xMin: import_koishi.Schema.number().default(-10).description("X轴始值"),
36
- xMax: import_koishi.Schema.number().default(10).description("X轴终值"),
37
- step: import_koishi.Schema.number().default(0.1).description("步长"),
38
- backgroundColor: import_koishi.Schema.string().default("white").description("背景色"),
39
- lineColor: import_koishi.Schema.string().default("navy").description("线条颜色,例如red,green,blue,cyan,teal,maroon"),
40
- gridColor: import_koishi.Schema.string().default("#e0e0e0").description("网格色"),
69
+ width: import_koishi.Schema.number().default(600).description("图像宽度"),
70
+ height: import_koishi.Schema.number().default(600).description("图像高度"),
71
+ xMin: import_koishi.Schema.number().default(-10).description("X轴默认最小值"),
72
+ xMax: import_koishi.Schema.number().default(10).description("X轴默认最大值"),
73
+ step: import_koishi.Schema.number().default(0.05).description("显式函数步长"),
74
+ tMin: import_koishi.Schema.number().default(-10).description("参数方程 t 的最小值"),
75
+ tMax: import_koishi.Schema.number().default(10).description("参数方程 t 的最大值"),
76
+ tSamples: import_koishi.Schema.number().default(3e3).description("参数方程采样数"),
77
+ implicitResolution: import_koishi.Schema.number().default(400).description("隐函数网格精度"),
78
+ backgroundColor: import_koishi.Schema.string().default("white").description("背景颜色"),
79
+ lineColor: import_koishi.Schema.string().default("blue").description("线条颜色"),
80
+ gridColor: import_koishi.Schema.string().default("#e0e0e0").description("网格颜色"),
41
81
  showGrid: import_koishi.Schema.boolean().default(true).description("显示网格")
42
82
  });
43
83
  function apply(ctx, config) {
44
- ctx.command("drf <expression>", "绘制函数图像").alias("函数绘图").example("drf f(x)=x^2").action(async ({ session }, expression) => {
84
+ if (!ctx.canvas) {
85
+ ctx.logger.warn("canvas 插件未安装,函数绘图功能将不可用");
86
+ return;
87
+ }
88
+ ctx.command("drf <expression:text>", "绘制函数图像").alias("函数绘图").example("drf y=x^2 @100").action(async ({ session }, expression) => {
45
89
  if (!expression) {
46
- return "[color提醒您]不给我表达式怎麽画(*´・д・)?";
90
+ return "mf群聊绘图器0.1.3\n示例输入:\ndrf x^2\ndrf 0=x^2+y^2-1\ndrf (t)[cos(t), sin(t)]\n(在末尾加上 @数值 来设置缩放,例如:drf x^3 @100)";
91
+ }
92
+ const originalExpression = expression.trim();
93
+ let rawExpr = originalExpression;
94
+ let dynamicXMin = config.xMin;
95
+ let dynamicXMax = config.xMax;
96
+ const scaleMatch = rawExpr.match(/^(.*?)\s*@\s*([\d.]+)\s*$/);
97
+ if (scaleMatch) {
98
+ rawExpr = scaleMatch[1].trim();
99
+ const scale = parseFloat(scaleMatch[2]);
100
+ if (!isNaN(scale) && scale > 0) {
101
+ dynamicXMin = -scale;
102
+ dynamicXMax = scale;
103
+ }
104
+ }
105
+ if (dynamicXMax <= dynamicXMin) {
106
+ return "X轴最大值必须大于最小值。";
107
+ }
108
+ let plotType = "explicit_y";
109
+ let eq1 = "", eq2 = "";
110
+ const paramMatch = rawExpr.match(/^\(t\)\s*\[\s*(.*?)\s*,\s*(.*?)\s*\]$/);
111
+ if (paramMatch) {
112
+ plotType = "parametric";
113
+ eq1 = paramMatch[1];
114
+ eq2 = paramMatch[2];
115
+ } else if (rawExpr.startsWith("0=") || rawExpr.startsWith("0 =")) {
116
+ plotType = "implicit";
117
+ eq1 = rawExpr.replace(/^0\s*=\s*/, "");
118
+ } else if (rawExpr.startsWith("x=") || rawExpr.startsWith("x =")) {
119
+ plotType = "explicit_x";
120
+ eq1 = rawExpr.replace(/^x\s*=\s*/, "");
121
+ } else {
122
+ plotType = "explicit_y";
123
+ eq1 = rawExpr.replace(/^y\s*=\s*/, "");
47
124
  }
48
125
  try {
49
- const compiled = (0, import_mathjs.compile)(expression);
50
- const scope = { x: 0 };
51
- const canvas = (0, import_canvas.createCanvas)(config.width, config.height);
52
- const ctxCanvas = canvas.getContext("2d");
53
- ctxCanvas.fillStyle = config.backgroundColor;
54
- ctxCanvas.fillRect(0, 0, config.width, config.height);
55
- const xAxis = config.height / 2;
56
- const yAxis = config.width / 2;
57
- const xScale = config.width / (config.xMax - config.xMin);
58
- const yScale = config.height / 20;
59
- if (config.showGrid) {
60
- ctxCanvas.strokeStyle = config.gridColor;
61
- ctxCanvas.lineWidth = 1;
62
- for (let x = Math.ceil(config.xMin); x <= config.xMax; x++) {
63
- if (x === 0) continue;
64
- const xPos = yAxis + x * xScale;
126
+ return await ctx.canvas.render(config.width, config.height, async (ctxCanvas) => {
127
+ const xrange = dynamicXMax - dynamicXMin;
128
+ const yrange = xrange * (config.height / config.width);
129
+ const yMid = 0;
130
+ const yMin = yMid - yrange / 2;
131
+ const yMax = yMid + yrange / 2;
132
+ const w2sX = /* @__PURE__ */ __name((x) => (x - dynamicXMin) / xrange * config.width, "w2sX");
133
+ const w2sY = /* @__PURE__ */ __name((y) => config.height - (y - yMin) / yrange * config.height, "w2sY");
134
+ ctxCanvas.fillStyle = config.backgroundColor;
135
+ ctxCanvas.fillRect(0, 0, config.width, config.height);
136
+ const getNiceStep = /* @__PURE__ */ __name((range, targetTicks2) => {
137
+ const rawStep = range / targetTicks2;
138
+ const mag = Math.floor(Math.log10(rawStep));
139
+ const magPow = Math.pow(10, mag);
140
+ const frac = rawStep / magPow;
141
+ let niceFrac = 10;
142
+ if (frac < 1.5) niceFrac = 1;
143
+ else if (frac < 3) niceFrac = 2;
144
+ else if (frac < 7) niceFrac = 5;
145
+ return niceFrac * magPow;
146
+ }, "getNiceStep");
147
+ const formatNumber = /* @__PURE__ */ __name((num) => {
148
+ if (num === 0) return "0";
149
+ const abs = Math.abs(num);
150
+ if (abs >= 1e5 || abs <= 1e-4) {
151
+ let [mantissa, exponent] = num.toExponential(2).split("e");
152
+ mantissa = parseFloat(mantissa).toString();
153
+ exponent = exponent.replace("+", "");
154
+ return `${mantissa}e${exponent}`;
155
+ }
156
+ return parseFloat(num.toPrecision(10)).toString();
157
+ }, "formatNumber");
158
+ const targetTicks = Math.max(4, Math.floor(config.width / 80));
159
+ const tickStep = getNiceStep(xrange, targetTicks);
160
+ const startX = Math.ceil(dynamicXMin / tickStep);
161
+ const endX = Math.floor(dynamicXMax / tickStep);
162
+ const startY = Math.ceil(yMin / tickStep);
163
+ const endY = Math.floor(yMax / tickStep);
164
+ const originX = w2sX(0);
165
+ const originY = w2sY(0);
166
+ if (config.showGrid) {
167
+ ctxCanvas.strokeStyle = config.gridColor;
168
+ ctxCanvas.lineWidth = 1;
65
169
  ctxCanvas.beginPath();
66
- ctxCanvas.moveTo(xPos, 0);
67
- ctxCanvas.lineTo(xPos, config.height);
170
+ for (let i = startX; i <= endX; i++) {
171
+ if (i === 0) continue;
172
+ const px = w2sX(i * tickStep);
173
+ ctxCanvas.moveTo(px, 0);
174
+ ctxCanvas.lineTo(px, config.height);
175
+ }
176
+ for (let i = startY; i <= endY; i++) {
177
+ if (i === 0) continue;
178
+ const py = w2sY(i * tickStep);
179
+ ctxCanvas.moveTo(0, py);
180
+ ctxCanvas.lineTo(config.width, py);
181
+ }
68
182
  ctxCanvas.stroke();
69
183
  }
70
- for (let y = -10; y <= 10; y++) {
71
- if (y === 0) continue;
72
- const yPos = xAxis - y * yScale;
73
- ctxCanvas.beginPath();
74
- ctxCanvas.moveTo(0, yPos);
75
- ctxCanvas.lineTo(config.width, yPos);
76
- ctxCanvas.stroke();
184
+ ctxCanvas.strokeStyle = "black";
185
+ ctxCanvas.lineWidth = 2;
186
+ ctxCanvas.beginPath();
187
+ if (originY >= 0 && originY <= config.height) {
188
+ ctxCanvas.moveTo(0, originY);
189
+ ctxCanvas.lineTo(config.width, originY);
77
190
  }
78
- }
79
- ctxCanvas.strokeStyle = "black";
80
- ctxCanvas.lineWidth = 2;
81
- ctxCanvas.beginPath();
82
- ctxCanvas.moveTo(0, xAxis);
83
- ctxCanvas.lineTo(config.width, xAxis);
84
- ctxCanvas.stroke();
85
- ctxCanvas.beginPath();
86
- ctxCanvas.moveTo(yAxis, 0);
87
- ctxCanvas.lineTo(yAxis, config.height);
88
- ctxCanvas.stroke();
89
- ctxCanvas.fillStyle = "black";
90
- ctxCanvas.font = "12px Arial";
91
- ctxCanvas.fillText("X", config.width - 15, xAxis - 5);
92
- ctxCanvas.fillText("Y", yAxis + 5, 15);
93
- ctxCanvas.strokeStyle = config.lineColor;
94
- ctxCanvas.lineWidth = 3;
95
- ctxCanvas.beginPath();
96
- let isFirstPoint = true;
97
- for (let x = config.xMin; x <= config.xMax; x += config.step) {
98
- scope.x = x;
99
- let y;
100
- try {
101
- y = compiled.evaluate(scope);
102
- if (typeof y !== "number" || !isFinite(y)) {
103
- isFirstPoint = true;
104
- continue;
105
- }
106
- if (y < -100) y = -100;
107
- if (y > 100) y = 100;
108
- const xPixel = yAxis + x * xScale;
109
- const yPixel = xAxis - y * yScale;
110
- if (isFirstPoint) {
111
- ctxCanvas.moveTo(xPixel, yPixel);
112
- isFirstPoint = false;
191
+ if (originX >= 0 && originX <= config.width) {
192
+ ctxCanvas.moveTo(originX, 0);
193
+ ctxCanvas.lineTo(originX, config.height);
194
+ }
195
+ ctxCanvas.stroke();
196
+ const clamp = /* @__PURE__ */ __name((val, min, max) => Math.max(min, Math.min(max, val)), "clamp");
197
+ const labelOriginY = clamp(originY, 20, config.height - 20);
198
+ const labelOriginX = clamp(originX, 20, config.width - 20);
199
+ ctxCanvas.fillStyle = "black";
200
+ ctxCanvas.font = "12px Arial";
201
+ ctxCanvas.textAlign = "center";
202
+ ctxCanvas.textBaseline = "top";
203
+ for (let i = startX; i <= endX; i++) {
204
+ if (i === 0) continue;
205
+ const x = i * tickStep;
206
+ ctxCanvas.fillText(formatNumber(x), w2sX(x), labelOriginY + 5);
207
+ }
208
+ for (let i = startY; i <= endY; i++) {
209
+ if (i === 0) continue;
210
+ const y = i * tickStep;
211
+ if (labelOriginX < 30) {
212
+ ctxCanvas.textAlign = "left";
213
+ ctxCanvas.textBaseline = "middle";
214
+ ctxCanvas.fillText(formatNumber(y), labelOriginX + 5, w2sY(y));
113
215
  } else {
114
- ctxCanvas.lineTo(xPixel, yPixel);
216
+ ctxCanvas.textAlign = "right";
217
+ ctxCanvas.textBaseline = "middle";
218
+ ctxCanvas.fillText(formatNumber(y), labelOriginX - 5, w2sY(y));
115
219
  }
116
- } catch (error) {
117
- isFirstPoint = true;
118
- continue;
119
220
  }
120
- }
121
- ctxCanvas.stroke();
122
- ctxCanvas.fillStyle = "#333";
123
- ctxCanvas.font = "bold 16px Arial";
124
- ctxCanvas.textAlign = "center";
125
- ctxCanvas.fillText("draw by mf[color]: " + expression, config.width / 2, 25);
126
- const buffer = canvas.toBuffer("image/png");
127
- return import_koishi.h.image(buffer, "image/png");
221
+ ctxCanvas.textAlign = labelOriginX < 30 ? "left" : "right";
222
+ ctxCanvas.textBaseline = "top";
223
+ ctxCanvas.fillText("0", labelOriginX < 30 ? labelOriginX + 5 : labelOriginX - 5, labelOriginY + 5);
224
+ ctxCanvas.font = "italic bold 14px Arial";
225
+ ctxCanvas.textAlign = "right";
226
+ ctxCanvas.textBaseline = "bottom";
227
+ if (originY >= 0 && originY <= config.height) {
228
+ ctxCanvas.fillText("x", config.width - 15, originY - 5);
229
+ }
230
+ ctxCanvas.textAlign = "left";
231
+ ctxCanvas.textBaseline = "top";
232
+ if (originX >= 0 && originX <= config.width) {
233
+ ctxCanvas.fillText("y", originX + 5, 15);
234
+ }
235
+ ctxCanvas.strokeStyle = config.lineColor;
236
+ ctxCanvas.lineWidth = 2;
237
+ ctxCanvas.lineJoin = "round";
238
+ ctxCanvas.lineCap = "round";
239
+ ctxCanvas.beginPath();
240
+ if (plotType === "explicit_y" || plotType === "explicit_x") {
241
+ const compiled = (0, import_mathjs.compile)(eq1);
242
+ const scope = {};
243
+ const ASYMPTOTE_THRESHOLD = yrange * 10;
244
+ const defaultRange = config.xMax - config.xMin;
245
+ const plotStep = config.step * (xrange / defaultRange);
246
+ let first = true;
247
+ let prevV = 0;
248
+ const isY = plotType === "explicit_y";
249
+ const loopMin = isY ? dynamicXMin : yMin;
250
+ const loopMax = isY ? dynamicXMax : yMax;
251
+ for (let i = loopMin; i <= loopMax; i += plotStep) {
252
+ scope[isY ? "x" : "y"] = i;
253
+ try {
254
+ let v = compiled.evaluate(scope);
255
+ if (typeof v !== "number" || !isFinite(v)) {
256
+ first = true;
257
+ continue;
258
+ }
259
+ if (!first && Math.abs(v - prevV) > ASYMPTOTE_THRESHOLD) {
260
+ first = true;
261
+ }
262
+ const px = w2sX(isY ? i : v);
263
+ const py = w2sY(isY ? v : i);
264
+ if (first) {
265
+ ctxCanvas.moveTo(px, py);
266
+ first = false;
267
+ } else {
268
+ ctxCanvas.lineTo(px, py);
269
+ }
270
+ prevV = v;
271
+ } catch (e) {
272
+ first = true;
273
+ }
274
+ }
275
+ ctxCanvas.stroke();
276
+ } else if (plotType === "parametric") {
277
+ const compX = (0, import_mathjs.compile)(eq1);
278
+ const compY = (0, import_mathjs.compile)(eq2);
279
+ const scope = { t: 0 };
280
+ const tMin = config.tMin;
281
+ const tMax = config.tMax;
282
+ if (tMax <= tMin) throw new Error("t 的最大值必须大于最小值");
283
+ const tLen = tMax - tMin;
284
+ const totalSamples = config.tSamples;
285
+ const stepT = tLen / totalSamples;
286
+ const JUMP_THRESHOLD_FACTOR = 2;
287
+ const maxJumpDistSq = Math.pow(yrange * JUMP_THRESHOLD_FACTOR, 2);
288
+ let first = true;
289
+ let prevWX = 0, prevWY = 0;
290
+ for (let i = 0; i <= totalSamples; i++) {
291
+ scope.t = tMin + i * stepT;
292
+ try {
293
+ let wx = compX.evaluate(scope);
294
+ let wy = compY.evaluate(scope);
295
+ if (!isFinite(wx) || !isFinite(wy)) {
296
+ first = true;
297
+ continue;
298
+ }
299
+ if (!first) {
300
+ const dx = wx - prevWX;
301
+ const dy = wy - prevWY;
302
+ if (dx * dx + dy * dy > maxJumpDistSq) {
303
+ first = true;
304
+ }
305
+ }
306
+ const px = w2sX(wx);
307
+ const py = w2sY(wy);
308
+ if (first) {
309
+ ctxCanvas.moveTo(px, py);
310
+ first = false;
311
+ } else {
312
+ ctxCanvas.lineTo(px, py);
313
+ }
314
+ prevWX = wx;
315
+ prevWY = wy;
316
+ } catch (e) {
317
+ first = true;
318
+ }
319
+ }
320
+ ctxCanvas.stroke();
321
+ } else if (plotType === "implicit") {
322
+ const compiled = (0, import_mathjs.compile)(eq1);
323
+ const scope = { x: 0, y: 0 };
324
+ const gridW = config.implicitResolution;
325
+ const gridH = Math.floor(gridW * (config.height / config.width));
326
+ const stepX = xrange / gridW;
327
+ const stepY = yrange / gridH;
328
+ const epsX = stepX * 13249e-7;
329
+ const epsY = stepY * 17381e-7;
330
+ const values = new Float64Array((gridW + 1) * (gridH + 1));
331
+ for (let i = 0; i <= gridW; i++) {
332
+ scope.x = dynamicXMin + i * stepX + epsX;
333
+ for (let j = 0; j <= gridH; j++) {
334
+ scope.y = yMin + j * stepY + epsY;
335
+ try {
336
+ let v = compiled.evaluate(scope);
337
+ values[i * (gridH + 1) + j] = typeof v === "number" && isFinite(v) ? v : NaN;
338
+ } catch (e) {
339
+ values[i * (gridH + 1) + j] = NaN;
340
+ }
341
+ }
342
+ }
343
+ const interp = /* @__PURE__ */ __name((v0, v1) => {
344
+ const diff = v1 - v0;
345
+ if (Math.abs(diff) < 1e-15) return 0.5;
346
+ return Math.max(0, Math.min(1, -v0 / diff));
347
+ }, "interp");
348
+ for (let i = 0; i < gridW; i++) {
349
+ const wx = dynamicXMin + i * stepX + epsX;
350
+ for (let j = 0; j < gridH; j++) {
351
+ const wy = yMin + j * stepY + epsY;
352
+ const v00 = values[i * (gridH + 1) + j];
353
+ const v10 = values[(i + 1) * (gridH + 1) + j];
354
+ const v01 = values[i * (gridH + 1) + (j + 1)];
355
+ const v11 = values[(i + 1) * (gridH + 1) + (j + 1)];
356
+ if (isNaN(v00) || isNaN(v10) || isNaN(v01) || isNaN(v11)) continue;
357
+ const pts = [];
358
+ if (v00 * v10 <= 0) pts.push([wx + interp(v00, v10) * stepX, wy]);
359
+ if (v10 * v11 <= 0) pts.push([wx + stepX, wy + interp(v10, v11) * stepY]);
360
+ if (v01 * v11 <= 0) pts.push([wx + interp(v01, v11) * stepX, wy + stepY]);
361
+ if (v00 * v01 <= 0) pts.push([wx, wy + interp(v00, v01) * stepY]);
362
+ if (pts.length === 2) {
363
+ ctxCanvas.moveTo(w2sX(pts[0][0]), w2sY(pts[0][1]));
364
+ ctxCanvas.lineTo(w2sX(pts[1][0]), w2sY(pts[1][1]));
365
+ } else if (pts.length === 4) {
366
+ ctxCanvas.moveTo(w2sX(pts[0][0]), w2sY(pts[0][1]));
367
+ ctxCanvas.lineTo(w2sX(pts[1][0]), w2sY(pts[1][1]));
368
+ ctxCanvas.moveTo(w2sX(pts[2][0]), w2sY(pts[2][1]));
369
+ ctxCanvas.lineTo(w2sX(pts[3][0]), w2sY(pts[3][1]));
370
+ }
371
+ }
372
+ }
373
+ ctxCanvas.stroke();
374
+ }
375
+ ctxCanvas.fillStyle = "#333";
376
+ ctxCanvas.font = "bold 16px Arial";
377
+ ctxCanvas.textAlign = "center";
378
+ ctxCanvas.textBaseline = "bottom";
379
+ ctxCanvas.fillText(`mf-draw: ${originalExpression}`, config.width / 2, 25);
380
+ });
128
381
  } catch (error) {
129
- return `绘制函数图像时出错: ${error.message}`;
382
+ ctx.logger.error("绘制函数图像时出错:", error);
383
+ return `呜!错误:「${error.message}」~哈!!`;
130
384
  }
131
385
  });
132
386
  }
@@ -135,5 +389,7 @@ __name(apply, "apply");
135
389
  0 && (module.exports = {
136
390
  Config,
137
391
  apply,
138
- name
392
+ name,
393
+ usage,
394
+ using
139
395
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-drf",
3
3
  "description": "draw math function in your group",
4
- "version": "0.0.3",
4
+ "version": "0.0.5",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
package/readme.md CHANGED
@@ -3,3 +3,38 @@
3
3
  [![npm](https://img.shields.io/npm/v/koishi-plugin-drf?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-drf)
4
4
 
5
5
  draw math function in your group
6
+
7
+ ---
8
+
9
+ ### Math Forest 群聊用函数绘图器 使用说明
10
+
11
+ #### 支持格式
12
+ 1. 显式函数(y= 可省略)
13
+ - x^2
14
+ - y = sin(x) + cos(x)
15
+ - x = y^2 - 3
16
+
17
+ 3. 隐函数(以 0= 开头)
18
+ - 0 = x^2 + y^2 - 1
19
+ - 0 = sin(x) - y
20
+
21
+ 4. 参数方程
22
+ - drf (t)[t * cos(3 * t), t * sin(3 * t)]
23
+
24
+ #### 缩放参数
25
+ @数值 -> 坐标范围为 [-n, n]
26
+
27
+ #### 示例
28
+ - drf sin(x) @30
29
+ - drf 0 = x^2+y^2-25
30
+ - drf (t)[t * cos(t), t * sin(t)] @15
31
+ - drf (t)[(16*sin(t)^3), (13*cos(t) - 5*cos(2*t) - 2*cos(3*t) - cos(4*t))] @20
32
+
33
+ ---
34
+
35
+ #### 关于
36
+ ```
37
+
38
+ Duo : https://www.mduo.cloud/
39
+ Math Forest : 663251235
40
+ ```