x4js 2.0.34 → 2.1.0-manual
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 +21 -21
- package/package.json +39 -26
- package/src/components/base.scss +25 -89
- package/src/components/boxes/boxes.module.scss +54 -54
- package/src/components/boxes/boxes.ts +513 -513
- package/src/components/breadcrumb/breadcrumb.scss +56 -56
- package/src/components/breadcrumb/breadcrumb.ts +93 -93
- package/src/components/btngroup/btngroup.module.scss +40 -40
- package/src/components/btngroup/btngroup.ts +152 -152
- package/src/components/button/button.module.scss +172 -172
- package/src/components/button/button.ts +232 -232
- package/src/components/calendar/calendar.module.scss +162 -162
- package/src/components/calendar/calendar.ts +326 -326
- package/src/components/canvas/canvas.module.scss +24 -24
- package/src/components/canvas/canvas.ts +195 -195
- package/src/components/canvas/canvas_ex.ts +275 -275
- package/src/components/checkbox/check.svg +3 -3
- package/src/components/checkbox/checkbox.module.scss +141 -141
- package/src/components/checkbox/checkbox.ts +139 -139
- package/src/components/colorinput/colorinput.module.scss +64 -64
- package/src/components/colorinput/colorinput.ts +90 -90
- package/src/components/colorpicker/colorpicker.module.scss +132 -132
- package/src/components/colorpicker/colorpicker.ts +481 -481
- package/src/components/combobox/combobox.module.scss +145 -145
- package/src/components/combobox/combobox.ts +282 -282
- package/src/components/combobox/updown.svg +3 -3
- package/src/components/components.ts +45 -44
- package/src/components/dialog/dialog.module.scss +103 -105
- package/src/components/dialog/dialog.ts +233 -233
- package/src/components/filedrop/filedrop.module.scss +69 -69
- package/src/components/filedrop/filedrop.ts +130 -130
- package/src/components/form/form.module.scss +38 -38
- package/src/components/form/form.ts +172 -172
- package/src/components/gridview/gridview.module.scss +323 -337
- package/src/components/gridview/gridview.ts +1276 -1315
- package/src/components/header/header.module.scss +40 -40
- package/src/components/header/header.ts +141 -141
- package/src/components/icon/icon.module.scss +32 -32
- package/src/components/icon/icon.ts +165 -165
- package/src/components/image/image.module.scss +27 -27
- package/src/components/image/image.ts +168 -168
- package/src/components/input/input.module.scss +74 -74
- package/src/components/input/input.ts +537 -537
- package/src/components/keyboard/keyboard.module.scss +136 -136
- package/src/components/keyboard/keyboard.ts +549 -549
- package/src/components/label/label.module.scss +90 -91
- package/src/components/label/label.ts +101 -101
- package/src/components/link/link.module.scss +44 -44
- package/src/components/link/link.ts +87 -87
- package/src/components/listbox/listbox.module.scss +179 -179
- package/src/components/listbox/listbox.ts +596 -596
- package/src/components/menu/menu.module.scss +128 -128
- package/src/components/menu/menu.ts +174 -174
- package/src/components/messages/messages.module.scss +92 -146
- package/src/components/messages/messages.ts +237 -303
- package/src/components/normalize.scss +391 -391
- package/src/components/notification/notification.module.scss +83 -83
- package/src/components/notification/notification.ts +107 -107
- package/src/components/panel/panel.module.scss +66 -71
- package/src/components/panel/panel.ts +57 -57
- package/src/components/popup/popup.module.scss +51 -51
- package/src/components/popup/popup.ts +457 -457
- package/src/components/progress/progress.module.scss +56 -56
- package/src/components/progress/progress.ts +43 -43
- package/src/components/propgrid/progrid.module.scss +111 -111
- package/src/components/propgrid/propgrid.ts +300 -300
- package/src/components/propgrid/updown.svg +3 -3
- package/src/components/radio/radio.module.scss +163 -163
- package/src/components/radio/radio.svg +3 -3
- package/src/components/radio/radio.ts +141 -141
- package/src/components/rating/rating.module.scss +22 -22
- package/src/components/rating/rating.ts +131 -131
- package/src/components/select/select.module.scss +8 -8
- package/src/components/select/select.ts +134 -134
- package/src/components/shared.scss +141 -71
- package/src/components/sizers/sizer.module.scss +90 -107
- package/src/components/sizers/sizer.ts +131 -134
- package/src/components/slider/slider.module.scss +117 -117
- package/src/components/slider/slider.ts +197 -197
- package/src/components/spreadsheet/spreadsheet.module.scss +307 -307
- package/src/components/spreadsheet/spreadsheet.ts +1223 -1223
- package/src/components/switch/switch.module.scss +126 -126
- package/src/components/switch/switch.ts +61 -61
- package/src/components/tabs/tabs.module.scss +46 -67
- package/src/components/tabs/tabs.ts +229 -234
- package/src/components/textarea/textarea.module.scss +63 -63
- package/src/components/textarea/textarea.ts +131 -131
- package/src/components/textedit/textedit.module.scss +115 -115
- package/src/components/textedit/textedit.ts +122 -122
- package/src/components/themes.scss +90 -90
- package/src/components/tickline/tickline.module.scss +25 -25
- package/src/components/tickline/tickline.ts +81 -81
- package/src/components/tooltips/tooltips.scss +71 -71
- package/src/components/tooltips/tooltips.ts +120 -120
- package/src/components/treeview/treeview.module.scss +192 -192
- package/src/components/treeview/treeview.ts +484 -484
- package/src/components/viewport/viewport.module.scss +31 -31
- package/src/components/viewport/viewport.ts +41 -41
- package/src/core/component.ts +1299 -1299
- package/src/core/core_application.ts +361 -361
- package/src/core/core_colors.ts +512 -512
- package/src/core/core_data.ts +1297 -1297
- package/src/core/core_dom.ts +481 -481
- package/src/core/core_dragdrop.ts +225 -225
- package/src/core/core_element.ts +221 -221
- package/src/core/core_events.ts +214 -214
- package/src/core/core_i18n.ts +395 -395
- package/src/core/core_pdf.ts +454 -454
- package/src/core/core_react.ts +78 -78
- package/src/core/core_router.ts +296 -296
- package/src/core/core_state.ts +62 -62
- package/src/core/core_styles.ts +213 -213
- package/src/core/core_svg.ts +1042 -1042
- package/src/core/core_tools.ts +996 -996
- package/src/types/scss.d.ts +4 -4
- package/src/types/x4react.d.ts +8 -8
- package/src/x4.scss +19 -19
- package/src/x4.ts +36 -36
- package/src/x4tsx.d.ts +26 -26
- package/.vscode/launch.json +0 -14
- package/.vscode/settings.json +0 -2
- package/demo/assets/house-light.svg +0 -1
- package/demo/assets/radio.svg +0 -4
- package/demo/index.html +0 -12
- package/demo/main.scss +0 -23
- package/demo/main.ts +0 -324
- package/demo/package.json +0 -26
- package/demo/scss.d.ts +0 -4
- package/demo/svg.d.ts +0 -1
- package/demo/tsconfig.json +0 -14
- package/src/components/gridview/folder-open.svg +0 -1
- package/src/components/messages/spinner.svg +0 -1
- package/src/x4.d.ts +0 -10
- package/tsconfig.json +0 -11
package/src/core/core_svg.ts
CHANGED
|
@@ -1,1042 +1,1042 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ___ ___ __
|
|
3
|
-
* \ \/ / / _
|
|
4
|
-
* \ / /_| |_
|
|
5
|
-
* / \____ _|
|
|
6
|
-
* /__/\__\ |_|
|
|
7
|
-
*
|
|
8
|
-
* @file core_svg.ts
|
|
9
|
-
* @author Etienne Cochard
|
|
10
|
-
*
|
|
11
|
-
* @copyright (c) 2024 R-libre ingenierie
|
|
12
|
-
*
|
|
13
|
-
* Use of this source code is governed by an MIT-style license
|
|
14
|
-
* that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
|
|
15
|
-
**/
|
|
16
|
-
|
|
17
|
-
import { Component, ComponentProps } from './component';
|
|
18
|
-
import { isUnitLess } from "./core_styles";
|
|
19
|
-
import { DOMEventHandler, GlobalDOMEvents, addEvent } from './core_dom';
|
|
20
|
-
import { isNumber, isString } from './core_tools';
|
|
21
|
-
|
|
22
|
-
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
23
|
-
|
|
24
|
-
// degrees to radian
|
|
25
|
-
function d2r( d: number ): number {
|
|
26
|
-
return d * Math.PI / 180.0;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// polar to cartesian
|
|
30
|
-
function p2c( x: number, y: number, r: number, deg: number ): {x: number,y: number} {
|
|
31
|
-
const rad = d2r( deg );
|
|
32
|
-
return {
|
|
33
|
-
x: x + r * Math.cos( rad ),
|
|
34
|
-
y: y + r * Math.sin( rad )
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// fix prec for numbers
|
|
39
|
-
function num( x: number ): number {
|
|
40
|
-
return Math.round( x * 1000 ) / 1000;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// clean values
|
|
44
|
-
function clean( a: any, ...b: any ) {
|
|
45
|
-
// just round number values to 3 digits
|
|
46
|
-
b = b.map( ( v: any ) => {
|
|
47
|
-
if( typeof v === 'number' && isFinite(v) ) {
|
|
48
|
-
return num(v);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return v;
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
return String.raw( a, ...b );
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Represents a lightweight wrapper around an SVG DOM element that provides
|
|
61
|
-
* convenient, chainable helpers for common SVG manipulations (attributes,
|
|
62
|
-
* styles, transforms, classes and events).
|
|
63
|
-
*
|
|
64
|
-
* The class encapsulates an underlying SVGElement and
|
|
65
|
-
* exposes methods that operate directly on that element while allowing fluent chaining.
|
|
66
|
-
*
|
|
67
|
-
* Key behaviors:
|
|
68
|
-
* - Creation: instances are constructed with the local name of the SVG tag
|
|
69
|
-
* (e.g. "rect", "circle", "g") and internally create the element in the
|
|
70
|
-
* SVG namespace.
|
|
71
|
-
* - Attribute management: `getAttr`, `getNumAttr`, and `setAttr` let you read
|
|
72
|
-
* and write attributes. Passing `null` or `undefined` to `setAttr` removes
|
|
73
|
-
* the attribute.
|
|
74
|
-
* - Styling: `setStyle` assigns CSS properties on the element. When a numeric
|
|
75
|
-
* value is provided, a "px" unit is appended unless the property is known
|
|
76
|
-
* to be unitless (the implementation relies on a small helper to detect
|
|
77
|
-
* unitless properties).
|
|
78
|
-
* - Geometry & transforms: helpers exist for setting or appending transforms
|
|
79
|
-
* (`transform`, `add_transformation`, `clear_transform`), and higher-level
|
|
80
|
-
* helpers for rotate, translate and scale (both replace and append forms).
|
|
81
|
-
* - Stroke & fill helpers: `stroke`, `strokeWidth`, `strokeCap`, `strokeOpacity`,
|
|
82
|
-
* `fill`, and `no_fill` provide convenient setters for common paint
|
|
83
|
-
* properties. These methods return `this` so they can be chained.
|
|
84
|
-
* - Classes & clipping: `addClass` / `removeClass` manipulate the element's
|
|
85
|
-
* class list (supporting space-separated lists) and `clip` sets a clip-path
|
|
86
|
-
* reference using an id.
|
|
87
|
-
* - Reset: `reset` removes all attributes from the element (but does not
|
|
88
|
-
* remove the element itself).
|
|
89
|
-
* - Events: `addDOMEvent` attaches DOM events to the underlying element.
|
|
90
|
-
*
|
|
91
|
-
* Notes and caveats:
|
|
92
|
-
* - This class is not a full DOM abstraction; it intentionally focuses on a
|
|
93
|
-
* small, ergonomic surface for building and manipulating SVG elements.
|
|
94
|
-
* - Some behaviors depend on small utility helpers (e.g. numeric/unit detection
|
|
95
|
-
* and event-attachment helpers) provided elsewhere in the codebase.
|
|
96
|
-
*
|
|
97
|
-
*/
|
|
98
|
-
|
|
99
|
-
class SvgItem {
|
|
100
|
-
protected _dom : SVGElement;
|
|
101
|
-
|
|
102
|
-
constructor( tag: string ) {
|
|
103
|
-
this._dom = document.createElementNS("http://www.w3.org/2000/svg", tag );
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* @returns the svh element dom
|
|
108
|
-
*/
|
|
109
|
-
|
|
110
|
-
getDom( ) {
|
|
111
|
-
return this._dom;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
*
|
|
116
|
-
*/
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Remove all attributes from the underlying DOM element.
|
|
120
|
-
* @returns The current instance (this) to allow method chaining.
|
|
121
|
-
*/
|
|
122
|
-
reset( ) {
|
|
123
|
-
const attrs = this._dom.attributes;
|
|
124
|
-
for (let i = attrs.length - 1; i >= 0; i--) {
|
|
125
|
-
this._dom.removeAttribute(attrs[i].name);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return this;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* change the stroke color
|
|
134
|
-
* @param color
|
|
135
|
-
*/
|
|
136
|
-
|
|
137
|
-
stroke( color: string, width?: number ): this {
|
|
138
|
-
this.setAttr( 'stroke', color );
|
|
139
|
-
if( width!==undefined ) {
|
|
140
|
-
this.setAttr( 'stroke-width', width+'px' );
|
|
141
|
-
}
|
|
142
|
-
return this;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* change the stroke width
|
|
147
|
-
* @param width
|
|
148
|
-
*/
|
|
149
|
-
strokeWidth( width: number ): this {
|
|
150
|
-
this.setAttr( 'stroke-width', width+'px' );
|
|
151
|
-
return this;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* change the stroke cap
|
|
156
|
-
* @param cap
|
|
157
|
-
*/
|
|
158
|
-
|
|
159
|
-
strokeCap( cap: "butt" | "round" | "sqaure" ) {
|
|
160
|
-
return this.setAttr( "stroke-linecap", cap );
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* change the stroke opacity attribute on the element.
|
|
165
|
-
* @param opacity - Opacity value where 0 is fully transparent and 1 is fully opaque.
|
|
166
|
-
* @returns The current instance to allow method chaining.
|
|
167
|
-
*/
|
|
168
|
-
|
|
169
|
-
strokeOpacity( opacity: number ) {
|
|
170
|
-
return this.setAttr( "stroke-opacity", opacity+"" );
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Set the shape rendering attribute to control anti-aliasing for shapes.
|
|
175
|
-
*
|
|
176
|
-
* When enabled, the attribute is set to "auto" to allow smoothing/anti-aliasing.
|
|
177
|
-
* When disabled, the attribute is set to "crispEdges" to favor pixel-aligned,
|
|
178
|
-
* non-anti-aliased rendering.
|
|
179
|
-
*
|
|
180
|
-
* @param set - True to enable anti-aliasing ("auto"), false to disable ("crispEdges").
|
|
181
|
-
* @returns the current instance to allow chaining).
|
|
182
|
-
*/
|
|
183
|
-
|
|
184
|
-
antiAlias( set: boolean ) {
|
|
185
|
-
return this.setAttr( "shape-rendering", set ? "auto" : "crispEdges" );
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* change the fill color
|
|
190
|
-
* @param color
|
|
191
|
-
*/
|
|
192
|
-
|
|
193
|
-
fill( color: string ): this {
|
|
194
|
-
this.setAttr( 'fill', color );
|
|
195
|
-
return this;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* the element is not filled
|
|
200
|
-
*/
|
|
201
|
-
|
|
202
|
-
no_fill( ): this {
|
|
203
|
-
this.setAttr( 'fill', "transparent" );
|
|
204
|
-
return this;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* return the given attribute if any
|
|
209
|
-
*/
|
|
210
|
-
|
|
211
|
-
getAttr( name: string ) : string {
|
|
212
|
-
const a = this._dom.getAttribute( name ) || '';
|
|
213
|
-
return a;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* return the attribute as number
|
|
218
|
-
*/
|
|
219
|
-
|
|
220
|
-
getNumAttr( name: string ) {
|
|
221
|
-
const a = this._dom.getAttribute( name )
|
|
222
|
-
if( a=='' ) {
|
|
223
|
-
return 0;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
return parseInt( a );
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* define a new attribute
|
|
231
|
-
* @param name attibute name
|
|
232
|
-
* @param value attribute value
|
|
233
|
-
* @returns this
|
|
234
|
-
*/
|
|
235
|
-
|
|
236
|
-
setAttr( name: string, value: string ) : this {
|
|
237
|
-
if( value===null || value===undefined ) {
|
|
238
|
-
this._dom.removeAttribute( name );
|
|
239
|
-
}
|
|
240
|
-
else {
|
|
241
|
-
this._dom.setAttribute( name, value );
|
|
242
|
-
}
|
|
243
|
-
return this;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* change one style value
|
|
248
|
-
*/
|
|
249
|
-
|
|
250
|
-
setStyle<K extends keyof CSSStyleDeclaration>( name: K, value: string | number ) : this {
|
|
251
|
-
const _style = this._dom.style;
|
|
252
|
-
|
|
253
|
-
if( isNumber(value) ) {
|
|
254
|
-
let v = value+"";
|
|
255
|
-
if( !isUnitLess(name as string) ) {
|
|
256
|
-
v += "px";
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
(_style as any)[name] = v;
|
|
260
|
-
}
|
|
261
|
-
else {
|
|
262
|
-
(_style as any)[name] = value;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return this;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* add a class
|
|
270
|
-
* @param name class name to add
|
|
271
|
-
*/
|
|
272
|
-
|
|
273
|
-
addClass( cls: string ): this {
|
|
274
|
-
if( !cls ) return;
|
|
275
|
-
|
|
276
|
-
cls = cls.trim();
|
|
277
|
-
if( cls.indexOf(' ')>=0 ) {
|
|
278
|
-
const ccs = cls.split( " " );
|
|
279
|
-
this._dom.classList.add(...ccs);
|
|
280
|
-
}
|
|
281
|
-
else {
|
|
282
|
-
this._dom.classList.add(cls);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
return this;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* remove a class
|
|
290
|
-
* @param name class name to remove
|
|
291
|
-
*/
|
|
292
|
-
|
|
293
|
-
removeClass( cls: string ): this {
|
|
294
|
-
if( !cls ) return;
|
|
295
|
-
|
|
296
|
-
if( cls.indexOf(' ')>=0 ) {
|
|
297
|
-
const ccs = cls.split( " " );
|
|
298
|
-
this._dom.classList.remove(...ccs);
|
|
299
|
-
}
|
|
300
|
-
else {
|
|
301
|
-
this._dom.classList.remove(cls);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
return this;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
*
|
|
309
|
-
*/
|
|
310
|
-
|
|
311
|
-
clip( id: string ): this {
|
|
312
|
-
this.setAttr( "clip-path", `url(#${id})` );
|
|
313
|
-
return this;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* define the whole transformation
|
|
318
|
-
*/
|
|
319
|
-
|
|
320
|
-
transform( tr: string ): this {
|
|
321
|
-
this.setAttr( "transform", tr );
|
|
322
|
-
return this;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* add a transformation to the current transformation
|
|
327
|
-
*/
|
|
328
|
-
|
|
329
|
-
add_transformation( tr: string ): this {
|
|
330
|
-
const t = this.getAttr( "transform" );
|
|
331
|
-
this.setAttr( "transform", t+' '+tr );
|
|
332
|
-
return this;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* remove all transformations
|
|
337
|
-
*/
|
|
338
|
-
|
|
339
|
-
clear_transform( ) {
|
|
340
|
-
this.setAttr( "transform", null );
|
|
341
|
-
return this;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* rotation
|
|
346
|
-
*/
|
|
347
|
-
|
|
348
|
-
rotate( deg: number, cx: number, cy: number ): this {
|
|
349
|
-
this.transform( `rotate( ${deg} ${cx} ${cy} )` );
|
|
350
|
-
return this;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
add_rotation( deg: number, cx: number, cy: number ): this {
|
|
354
|
-
this.add_transformation( `rotate( ${deg} ${cx} ${cy} )` );
|
|
355
|
-
return this;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* translation
|
|
360
|
-
*/
|
|
361
|
-
|
|
362
|
-
translate( dx: number, dy: number ): this {
|
|
363
|
-
this.transform( `translate( ${dx} ${dy} )` );
|
|
364
|
-
return this;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
add_translation( dx: number, dy: number ): this {
|
|
368
|
-
this.add_transformation( `translate( ${dx} ${dy} )` );
|
|
369
|
-
return this;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* scaling
|
|
374
|
-
*/
|
|
375
|
-
|
|
376
|
-
scale( x: number ): this {
|
|
377
|
-
this.transform( `scale( ${x} )` );
|
|
378
|
-
return this;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
add_scale( x: number ): this {
|
|
382
|
-
this.add_transformation( `scale( ${x} )` );
|
|
383
|
-
return this;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* handle SVG DOM event
|
|
388
|
-
*/
|
|
389
|
-
|
|
390
|
-
addDOMEvent<K extends keyof GlobalDOMEvents>( name: K, listener: GlobalDOMEvents[K], prepend = false ) {
|
|
391
|
-
addEvent( this._dom, name, listener as DOMEventHandler, prepend );
|
|
392
|
-
return this;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* Represents an SVG path element, providing methods for constructing and manipulating SVG paths.
|
|
400
|
-
* It extends `SvgItem` to inherit common SVG element functionalities.
|
|
401
|
-
*/
|
|
402
|
-
|
|
403
|
-
export class SvgPath extends SvgItem {
|
|
404
|
-
private _path: string;
|
|
405
|
-
|
|
406
|
-
constructor( ) {
|
|
407
|
-
super( 'path' );
|
|
408
|
-
this._path = '';
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
private _update( ): this {
|
|
412
|
-
this.setAttr( 'd', this._path );
|
|
413
|
-
return this;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* Resets the path data and all attributes of the SVG path element.
|
|
418
|
-
* @returns The current `SvgPath` instance for chaining.
|
|
419
|
-
*/
|
|
420
|
-
reset( ) {
|
|
421
|
-
this._path = "";
|
|
422
|
-
super.reset( );
|
|
423
|
-
return this;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
/**
|
|
427
|
-
* Moves the current drawing position to the specified coordinates without drawing a line.
|
|
428
|
-
* This is typically the first command in a path.
|
|
429
|
-
*
|
|
430
|
-
* @param x - The x-coordinate to move to.
|
|
431
|
-
* @param y - The y-coordinate to move to.
|
|
432
|
-
* @returns this
|
|
433
|
-
*/
|
|
434
|
-
|
|
435
|
-
moveTo( x: number, y: number ) : this {
|
|
436
|
-
this._path += clean`M${x},${y}`;
|
|
437
|
-
return this._update( );
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* Draws a straight line from the current position to the specified coordinates.
|
|
442
|
-
*
|
|
443
|
-
* @param x - The x-coordinate of the end point.
|
|
444
|
-
* @param y - The y-coordinate of the end point.
|
|
445
|
-
* @returns this
|
|
446
|
-
*/
|
|
447
|
-
|
|
448
|
-
lineTo( x: number, y: number ): this {
|
|
449
|
-
this._path += clean`L${x},${y}`;
|
|
450
|
-
return this._update( );
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
/**
|
|
454
|
-
* Draws a cubic Bézier curve from the current point to `(x3, y3)` using `(x1, y1)`
|
|
455
|
-
* as the control point at the beginning of the curve and `(x2, y2)` as the
|
|
456
|
-
* control point at the end of the curve.
|
|
457
|
-
*
|
|
458
|
-
* @param x1 - The x-coordinate of the first control point.
|
|
459
|
-
* @param y1 - The y-coordinate of the first control point.
|
|
460
|
-
* @param x2 - The x-coordinate of the second control point.
|
|
461
|
-
* @param y2 - The y-coordinate of the second control point.
|
|
462
|
-
* @param x3 - The x-coordinate of the end point of the curve.
|
|
463
|
-
* @param y3 - The y-coordinate of the end point of the curve.
|
|
464
|
-
*
|
|
465
|
-
* @returns The current `SvgPath` instance for chaining.
|
|
466
|
-
*/
|
|
467
|
-
|
|
468
|
-
curveTo( x1: number, y1: number, x2: number, y2: number, x3: number, y3: number ) {
|
|
469
|
-
this._path += clean`C${x1},${y1} ${x2},${y2} ${x3},${y3}`;
|
|
470
|
-
return this._update( );
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
/**
|
|
475
|
-
* Closes the current subpath by drawing a straight line from the current position
|
|
476
|
-
* to the initial point of the current subpath.
|
|
477
|
-
* @returns The current `SvgPath` instance for chaining.
|
|
478
|
-
*/
|
|
479
|
-
|
|
480
|
-
closePath( ): this {
|
|
481
|
-
this._path += 'Z';
|
|
482
|
-
return this._update( );
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* draw an arc
|
|
487
|
-
* Draws an elliptical arc from the current point to a new point.
|
|
488
|
-
*
|
|
489
|
-
* @param x - The x-coordinate of the center of the ellipse.
|
|
490
|
-
* @param y - The y-coordinate of the center of the ellipse.
|
|
491
|
-
* @param r - The radius of the arc.
|
|
492
|
-
* @param start - The start angle of the arc in degrees (0 is right, 90 is down).
|
|
493
|
-
* @param end - The end angle of the arc in degrees.
|
|
494
|
-
* @param clockwise - If `true`, the arc is drawn clockwise; otherwise, counter-clockwise. Defaults to `true`.
|
|
495
|
-
*
|
|
496
|
-
* @returns this
|
|
497
|
-
*/
|
|
498
|
-
|
|
499
|
-
arc( x: number, y: number, r: number, start: number, end: number, clockwise= true ): this {
|
|
500
|
-
|
|
501
|
-
const st = p2c( x, y, r, start-90 );
|
|
502
|
-
const en = p2c( x, y, r, end-90 );
|
|
503
|
-
|
|
504
|
-
const flag = ((end-start) <= 180 ? "0" : "1");
|
|
505
|
-
this._path += clean`M${st.x},${st.y}A${r},${r} 0 ${flag} ${(clockwise ? '1' : '0')} ${en.x},${en.y}`;
|
|
506
|
-
|
|
507
|
-
return this._update( );
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
/**
|
|
512
|
-
* Represents an SVG text element, providing methods for positioning and styling text.
|
|
513
|
-
* It extends `SvgItem` to inherit common SVG element functionalities.
|
|
514
|
-
*/
|
|
515
|
-
|
|
516
|
-
export class SvgText extends SvgItem {
|
|
517
|
-
/**
|
|
518
|
-
* Creates an instance of `SvgText`.
|
|
519
|
-
* @param x - The x-coordinate for the text's starting position.
|
|
520
|
-
* @param y - The y-coordinate for the text's starting position.
|
|
521
|
-
* @param txt - The text content.
|
|
522
|
-
*/
|
|
523
|
-
constructor( x: number, y: number, txt: string ) {
|
|
524
|
-
super( 'text' );
|
|
525
|
-
|
|
526
|
-
this.setAttr( 'x', num(x)+'' );
|
|
527
|
-
this.setAttr( 'y', num(y)+'' );
|
|
528
|
-
|
|
529
|
-
this._dom.innerHTML = txt;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
/**
|
|
533
|
-
* Sets the font family for the text.
|
|
534
|
-
* @param font - The font family name (e.g., "Arial", "sans-serif").
|
|
535
|
-
* @returns The current `SvgText` instance for chaining.
|
|
536
|
-
*/
|
|
537
|
-
font( font: string ): this {
|
|
538
|
-
return this.setAttr( 'font-family', font );
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
/**
|
|
542
|
-
* Sets the font size for the text.
|
|
543
|
-
* @param size - The font size, either as a number (e.g., 12) or a string (e.g., "1.2em").
|
|
544
|
-
* @returns The current `SvgText` instance for chaining.
|
|
545
|
-
*/
|
|
546
|
-
fontSize( size: number | string ): this {
|
|
547
|
-
return this.setAttr( 'font-size', size+'' );
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
/**
|
|
551
|
-
* Sets the font weight for the text.
|
|
552
|
-
* @param weight - The font weight ("light", "normal", or "bold").
|
|
553
|
-
* @returns The current `SvgText` instance for chaining.
|
|
554
|
-
*/
|
|
555
|
-
fontWeight( weight: 'light' | 'normal' | 'bold' ): this {
|
|
556
|
-
return this.setAttr( 'font-weight', weight );
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
/**
|
|
560
|
-
* Sets the horizontal text alignment.
|
|
561
|
-
* @param align - The horizontal alignment ("left", "center", or "right").
|
|
562
|
-
* @returns The current `SvgText` instance for chaining.
|
|
563
|
-
*/
|
|
564
|
-
textAlign( align: 'left' | 'center' | 'right' ): this {
|
|
565
|
-
|
|
566
|
-
let al;
|
|
567
|
-
switch( align ) {
|
|
568
|
-
case 'left': al = 'start'; break;
|
|
569
|
-
case 'center': al = 'middle'; break;
|
|
570
|
-
case 'right': al = 'end'; break;
|
|
571
|
-
default:
|
|
572
|
-
// If an invalid alignment is provided, do nothing and return this for chaining.
|
|
573
|
-
// This prevents setting an invalid attribute value.
|
|
574
|
-
console.warn(`Invalid textAlign value: ${align}. Must be 'left', 'center', or 'right'.`);
|
|
575
|
-
return this;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
return this.setAttr( 'text-anchor', al );
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
/**
|
|
582
|
-
* change the vertical alignment
|
|
583
|
-
*/
|
|
584
|
-
verticalAlign( align: 'top' | 'center' | 'bottom' | 'baseline' ): this {
|
|
585
|
-
|
|
586
|
-
let al;
|
|
587
|
-
switch( align ) {
|
|
588
|
-
case 'top': al = 'hanging'; break;
|
|
589
|
-
case 'center': al = 'middle'; break;
|
|
590
|
-
case 'bottom': al = 'baseline'; break;
|
|
591
|
-
case 'baseline': al = 'mathematical'; break;
|
|
592
|
-
default:
|
|
593
|
-
// If an invalid alignment is provided, do nothing and return this for chaining.
|
|
594
|
-
console.warn(`Invalid verticalAlign value: ${align}. Must be 'top', 'center', 'bottom', or 'baseline'.`);
|
|
595
|
-
return this;
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
return this.setAttr( 'alignment-baseline', al );
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
/**
|
|
603
|
-
* Represents an SVG icon, which is essentially an SVG element embedded within another.
|
|
604
|
-
* It extends `SvgItem` to inherit common SVG element functionalities.
|
|
605
|
-
*/
|
|
606
|
-
|
|
607
|
-
export class SvgIcon extends SvgItem {
|
|
608
|
-
/**
|
|
609
|
-
* Creates an instance of `SvgIcon` from an SVG string.
|
|
610
|
-
* @param svg - The SVG string, optionally prefixed with "data:image/svg+xml,".
|
|
611
|
-
*/
|
|
612
|
-
constructor( svg: string ) {
|
|
613
|
-
super( "svg" );
|
|
614
|
-
|
|
615
|
-
if( svg.startsWith("data:image/svg+xml,") ) {
|
|
616
|
-
svg = svg.substring( 19 );
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
const parser = new DOMParser();
|
|
620
|
-
const doc = parser.parseFromString( decodeURIComponent(svg), "image/svg+xml");
|
|
621
|
-
|
|
622
|
-
const parserErrorElement = doc.querySelector("parsererror");
|
|
623
|
-
if( parserErrorElement ) {
|
|
624
|
-
console.error( "error while parsing svg:\n"+ parserErrorElement.textContent );
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
const svgRoot = doc.documentElement; // The <svg> element from the string
|
|
628
|
-
for( let i=0; i<svgRoot.attributes.length; i++) {
|
|
629
|
-
this._dom.setAttribute( svgRoot.attributes[i].name, svgRoot.attributes[i].value );
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
for( let i=0; i<svgRoot.childNodes.length; i++) {
|
|
633
|
-
const child = svgRoot.childNodes[i];
|
|
634
|
-
if (child.nodeType === 1) {
|
|
635
|
-
this._dom.appendChild(child);
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
/**
|
|
644
|
-
* Represents a generic SVG shape element.
|
|
645
|
-
* It extends `SvgItem` to inherit common SVG element functionalities.
|
|
646
|
-
*/
|
|
647
|
-
|
|
648
|
-
export class SvgShape extends SvgItem {
|
|
649
|
-
constructor( tag: string ) {
|
|
650
|
-
super( tag );
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
/**
|
|
655
|
-
*
|
|
656
|
-
* Type alias for a number or a percentage string.
|
|
657
|
-
*/
|
|
658
|
-
|
|
659
|
-
type number_or_perc = number | `${string}%`
|
|
660
|
-
|
|
661
|
-
/**
|
|
662
|
-
* Represents an SVG linear gradient element.
|
|
663
|
-
* It extends `SvgItem` to inherit common SVG element functionalities.
|
|
664
|
-
*/
|
|
665
|
-
export class SvgGradient extends SvgItem {
|
|
666
|
-
|
|
667
|
-
private static g_id = 1;
|
|
668
|
-
|
|
669
|
-
private _id: string;
|
|
670
|
-
private _stops: { offset: number_or_perc, color: string } [];
|
|
671
|
-
|
|
672
|
-
/**
|
|
673
|
-
* Creates an instance of `SvgGradient`.
|
|
674
|
-
* @param x1 - The x-coordinate of the starting point of the gradient vector.
|
|
675
|
-
* @param y1 - The y-coordinate of the starting point of the gradient vector.
|
|
676
|
-
* @param x2 - The x-coordinate of the ending point of the gradient vector.
|
|
677
|
-
* @param y2 - The y-coordinate of the ending point of the gradient vector.
|
|
678
|
-
* @returns The current `SvgGradient` instance for chaining.
|
|
679
|
-
*/
|
|
680
|
-
constructor( x1: number_or_perc, y1: number_or_perc, x2: number_or_perc, y2: number_or_perc ) {
|
|
681
|
-
super( 'linearGradient')
|
|
682
|
-
|
|
683
|
-
this._id = 'gx-'+SvgGradient.g_id;
|
|
684
|
-
SvgGradient.g_id++;
|
|
685
|
-
|
|
686
|
-
this.setAttr( 'id', this._id );
|
|
687
|
-
this.setAttr( 'x1', isString(x1) ? x1 : num(x1)+'' );
|
|
688
|
-
this.setAttr( 'x2', isString(x2) ? x2 : num(x2)+'' );
|
|
689
|
-
this.setAttr( 'y1', isString(y1) ? y1 : num(y1)+'' );
|
|
690
|
-
this.setAttr( 'y2', isString(y2) ? y2 : num(y2)+'' );
|
|
691
|
-
|
|
692
|
-
this._stops = [];
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
/**
|
|
696
|
-
* Gets the URL reference to this gradient, suitable for use in `fill` or `stroke` attributes.
|
|
697
|
-
* @returns A string in the format `url(#<gradient_id>)`.
|
|
698
|
-
*/
|
|
699
|
-
get id( ) {
|
|
700
|
-
return 'url(#'+this._id+')';
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
/**
|
|
704
|
-
* Adds a color stop to the gradient.
|
|
705
|
-
* @param offset - The offset of the color stop, either as a number (0-100) or a percentage string.
|
|
706
|
-
* @param color - The color at this stop.
|
|
707
|
-
* @returns The current `SvgGradient` instance for chaining.
|
|
708
|
-
*/
|
|
709
|
-
addStop( offset: number_or_perc, color: string ): this {
|
|
710
|
-
this._dom.insertAdjacentHTML( "beforeend", `<stop offset="${offset}%" stop-color="${color}"></stop>`);
|
|
711
|
-
return this;
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
/**
|
|
716
|
-
* Represents an SVG group element (`<g>`), which can contain other SVG elements.
|
|
717
|
-
* It extends `SvgItem` and provides methods for appending various SVG shapes and gradients.
|
|
718
|
-
*/
|
|
719
|
-
|
|
720
|
-
export class SvgGroup extends SvgItem {
|
|
721
|
-
|
|
722
|
-
/**
|
|
723
|
-
* Creates an instance of `SvgGroup`.
|
|
724
|
-
* @param tag - The SVG tag name for the group element (defaults to "g").
|
|
725
|
-
*/
|
|
726
|
-
constructor( tag = "g" ) {
|
|
727
|
-
super( tag )
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
/**
|
|
731
|
-
* Appends an `SvgItem` to this group.
|
|
732
|
-
*
|
|
733
|
-
* @template K - The type of the `SvgItem` being appended.
|
|
734
|
-
* @param item - The `SvgItem` instance to append.
|
|
735
|
-
*
|
|
736
|
-
* @returns The appended `SvgItem` instance.
|
|
737
|
-
*/
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
append<K extends SvgItem>( item: K ): K {
|
|
741
|
-
this._dom.appendChild( item.getDom() );
|
|
742
|
-
return item;
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
appendItems<K extends SvgItem>( items: K[] ) {
|
|
746
|
-
items.forEach( item => {
|
|
747
|
-
this._dom.appendChild( item.getDom() );
|
|
748
|
-
} );
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
/**
|
|
752
|
-
* Creates and appends an `SvgPath` element to this group.
|
|
753
|
-
* @returns The newly created `SvgPath` instance.
|
|
754
|
-
*/
|
|
755
|
-
|
|
756
|
-
path( ): SvgPath {
|
|
757
|
-
const path = new SvgPath( );
|
|
758
|
-
return this.append( path );
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
/**
|
|
762
|
-
* Creates and appends an `SvgText` element to this group.
|
|
763
|
-
* @param x - The x-coordinate for the text.
|
|
764
|
-
* @param y - The y-coordinate for the text.
|
|
765
|
-
* @param txt - The text content.
|
|
766
|
-
* @returns The newly created `SvgText` instance.
|
|
767
|
-
*/
|
|
768
|
-
text( x: number, y: number, txt: string ) {
|
|
769
|
-
const text = new SvgText( x, y, txt );
|
|
770
|
-
return this.append( text );
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
/**
|
|
774
|
-
* Creates and appends an SVG ellipse element to this group.
|
|
775
|
-
*
|
|
776
|
-
* @param x - The x-coordinate of the center of the ellipse.
|
|
777
|
-
* @param y - The y-coordinate of the center of the ellipse.
|
|
778
|
-
* @param r1 - The x-radius of the ellipse.
|
|
779
|
-
* @param r2 - The y-radius of the ellipse.
|
|
780
|
-
*
|
|
781
|
-
* @returns The newly created `SvgShape` instance representing the ellipse.
|
|
782
|
-
*/
|
|
783
|
-
ellipse( x: number, y: number, r1: number, r2?: number ): SvgShape {
|
|
784
|
-
const shape = new SvgShape( 'ellipse' );
|
|
785
|
-
shape.setAttr( 'cx', num(x)+'' );
|
|
786
|
-
shape.setAttr( 'cy', num(y)+'' );
|
|
787
|
-
shape.setAttr( 'rx', num(r1)+'' );
|
|
788
|
-
shape.setAttr( 'ry', num(r2 ?? r1)+'' );
|
|
789
|
-
return this.append( shape );
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
/**
|
|
793
|
-
* Creates and appends an SVG circle element to this group.
|
|
794
|
-
* (Internally uses an ellipse with equal x and y radii).
|
|
795
|
-
*
|
|
796
|
-
* @param x - The x-coordinate of the center of the circle.
|
|
797
|
-
* @param y - The y-coordinate of the center of the circle.
|
|
798
|
-
* @param r1 - The radius of the circle.
|
|
799
|
-
* @returns The newly created `SvgShape` instance representing the circle.
|
|
800
|
-
*/
|
|
801
|
-
circle( x: number, y: number, r1: number ): SvgShape {
|
|
802
|
-
const shape = new SvgShape( 'ellipse' );
|
|
803
|
-
shape.setAttr( 'cx', num(x)+'' );
|
|
804
|
-
shape.setAttr( 'cy', num(y)+'' );
|
|
805
|
-
shape.setAttr( 'rx', num(r1)+'' );
|
|
806
|
-
shape.setAttr( 'ry', num(r1)+'' );
|
|
807
|
-
return this.append( shape );
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
/**
|
|
811
|
-
* Creates and appends an `SvgIcon` element to this group.
|
|
812
|
-
*
|
|
813
|
-
* @param svg - The SVG string content for the icon.
|
|
814
|
-
* @param x - The x-coordinate for the icon's position.
|
|
815
|
-
* @param y - The y-coordinate for the icon's position.
|
|
816
|
-
* @param w - The width of the icon.
|
|
817
|
-
* @param h - The height of the icon.
|
|
818
|
-
* @returns The newly created `SvgIcon` instance.
|
|
819
|
-
*/
|
|
820
|
-
icon( svg: string, x: number, y: number, w: number, h: number ): SvgIcon {
|
|
821
|
-
const icon = new SvgIcon( svg );
|
|
822
|
-
icon.setAttr( 'x', num(x)+'' );
|
|
823
|
-
icon.setAttr( 'y', num(y)+'' );
|
|
824
|
-
icon.setAttr( 'width', num(w)+'' );
|
|
825
|
-
icon.setAttr( 'height', num(h)+'' );
|
|
826
|
-
icon.setStyle( 'width', num(w)+'px' );
|
|
827
|
-
icon.setStyle( 'height', num(h)+'px' );
|
|
828
|
-
return this.append( icon );
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
/**
|
|
832
|
-
* Creates and appends an SVG rectangle element to this group.
|
|
833
|
-
* Handles negative height by adjusting `y` and `h` accordingly.
|
|
834
|
-
*
|
|
835
|
-
* @param x - The x-coordinate of the top-left corner of the rectangle.
|
|
836
|
-
* @param y - The y-coordinate of the top-left corner of the rectangle.
|
|
837
|
-
* @param w - The width of the rectangle.
|
|
838
|
-
* @param h - The height of the rectangle.
|
|
839
|
-
* @returns The newly created `SvgShape` instance representing the rectangle.
|
|
840
|
-
*/
|
|
841
|
-
rect( x: number, y: number, w: number, h: number ): SvgShape {
|
|
842
|
-
|
|
843
|
-
if( h<0 ) {
|
|
844
|
-
y = y+h;
|
|
845
|
-
h = -h;
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
const shape = new SvgShape( 'rect' );
|
|
849
|
-
shape.setAttr( 'x', num(x)+'' );
|
|
850
|
-
shape.setAttr( 'y', num(y)+'' );
|
|
851
|
-
shape.setAttr( 'width', num(w)+'' );
|
|
852
|
-
shape.setAttr( 'height', num(h)+'' );
|
|
853
|
-
return this.append( shape );
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
/**
|
|
857
|
-
* Creates and appends an SVG group element (`<g>`) to this group.
|
|
858
|
-
* @param id - Optional. An ID for the new group.
|
|
859
|
-
* @returns The newly created `SvgGroup` instance.
|
|
860
|
-
*/
|
|
861
|
-
group( id?: string ) {
|
|
862
|
-
const group = new SvgGroup( );
|
|
863
|
-
if( id ) {
|
|
864
|
-
group.setAttr( 'id', id );
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
return this.append( group );
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
/**
|
|
871
|
-
*
|
|
872
|
-
* Creates and appends an SVG linear gradient definition to this group.
|
|
873
|
-
*
|
|
874
|
-
* @example
|
|
875
|
-
* ```typescript
|
|
876
|
-
* const gradient = svgGroup.linear_gradient('0%', '0%', '0%', '100%')
|
|
877
|
-
* .addStop(0, 'red')
|
|
878
|
-
* .addStop(100, 'green');
|
|
879
|
-
* svgGroup.rect(0, 0, 100, 100).fill(gradient.id);
|
|
880
|
-
* ```
|
|
881
|
-
* @param x1 - The x-coordinate of the starting point of the gradient vector.
|
|
882
|
-
* @param y1 - The y-coordinate of the starting point of the gradient vector.
|
|
883
|
-
* @param x2 - The x-coordinate of the ending point of the gradient vector.
|
|
884
|
-
* @param y2 - The y-coordinate of the ending point of the gradient vector.
|
|
885
|
-
* @returns The newly created `SvgGradient` instance.
|
|
886
|
-
*/
|
|
887
|
-
|
|
888
|
-
linear_gradient( x1: number_or_perc, y1: number_or_perc, x2: number_or_perc, y2: number_or_perc ) {
|
|
889
|
-
const grad = new SvgGradient( x1, y1, x2, y2 );
|
|
890
|
-
return this.append( grad );
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
/**
|
|
894
|
-
* Clears all child elements from this SVG group.
|
|
895
|
-
*
|
|
896
|
-
* @returns void
|
|
897
|
-
*/
|
|
898
|
-
|
|
899
|
-
clear( ) {
|
|
900
|
-
const dom = this._dom;
|
|
901
|
-
while( dom.firstChild ) {
|
|
902
|
-
dom.removeChild( dom.firstChild );
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
/**
|
|
912
|
-
* A specialized `SvgGroup` that provides methods for adding SVG definitions like clip paths and patterns.
|
|
913
|
-
* It extends `SvgGroup` to inherit common SVG element functionalities and acts as a container for definitions.
|
|
914
|
-
*/
|
|
915
|
-
export class SvgBuilder extends SvgGroup {
|
|
916
|
-
private static g_clip_id = 1;
|
|
917
|
-
private static g_pat_id = 1;
|
|
918
|
-
|
|
919
|
-
/**
|
|
920
|
-
* Creates an instance of `SvgBuilder`.
|
|
921
|
-
*/
|
|
922
|
-
constructor( ) {
|
|
923
|
-
super( );
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
/**
|
|
927
|
-
* Adds an SVG clip path definition to the builder.
|
|
928
|
-
*
|
|
929
|
-
* @param x - The x-coordinate of the top-left corner of the clipping rectangle.
|
|
930
|
-
* @param y - The y-coordinate of the top-left corner of the clipping rectangle.
|
|
931
|
-
* @param w - The width of the clipping rectangle.
|
|
932
|
-
* @param h - The height of the clipping rectangle.
|
|
933
|
-
* @returns An object containing the generated `id` for the clip path and the `SvgGroup` instance representing the clip path.
|
|
934
|
-
*/
|
|
935
|
-
addClip( x: number, y: number, w: number, h: number ) {
|
|
936
|
-
/**
|
|
937
|
-
* Adds an SVG clip path definition to the builder.
|
|
938
|
-
*
|
|
939
|
-
* @param x - The x-coordinate of the top-left corner of the clipping rectangle.
|
|
940
|
-
* @param y - The y-coordinate of the top-left corner of the clipping rectangle.
|
|
941
|
-
* @param w - The width of the clipping rectangle.
|
|
942
|
-
* @param h - The height of the clipping rectangle.
|
|
943
|
-
* @returns An object containing the generated `id` for the clip path and the `SvgGroup` instance representing the clip path.
|
|
944
|
-
*/
|
|
945
|
-
const id = 'clip-'+SvgBuilder.g_clip_id++;
|
|
946
|
-
const clip = new SvgGroup( 'clipPath' );
|
|
947
|
-
clip.setAttr('id', id );
|
|
948
|
-
clip.rect( x, y, w, h );
|
|
949
|
-
|
|
950
|
-
this.append(clip);
|
|
951
|
-
return {id,clip};
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
/**
|
|
955
|
-
* Adds an SVG pattern definition to the builder.
|
|
956
|
-
*
|
|
957
|
-
* @param x - The x-coordinate of the pattern tile's top-left corner.
|
|
958
|
-
* @param y - The y-coordinate of the pattern tile's top-left corner.
|
|
959
|
-
* @param w - The width of the pattern tile.
|
|
960
|
-
* @param h - The height of the pattern tile.
|
|
961
|
-
* @returns An object containing the generated `id` for the pattern and the `SvgGroup` instance representing the pattern.
|
|
962
|
-
*/
|
|
963
|
-
addPattern( x: number, y: number, w: number, h: number ) {
|
|
964
|
-
/**
|
|
965
|
-
* Adds an SVG pattern definition to the builder.
|
|
966
|
-
*
|
|
967
|
-
* @param x - The x-coordinate of the pattern tile's top-left corner.
|
|
968
|
-
* @param y - The y-coordinate of the pattern tile's top-left corner.
|
|
969
|
-
* @param w - The width of the pattern tile.
|
|
970
|
-
* @param h - The height of the pattern tile.
|
|
971
|
-
* @returns An object containing the generated `id` for the pattern and the `SvgGroup` instance representing the pattern.
|
|
972
|
-
*/
|
|
973
|
-
const id = 'pat-'+SvgBuilder.g_pat_id++;
|
|
974
|
-
|
|
975
|
-
const pat = new SvgGroup( 'pattern' );
|
|
976
|
-
pat.setAttr( 'id', id );
|
|
977
|
-
pat.setAttr( 'x', num(x)+'' );
|
|
978
|
-
pat.setAttr( 'y', num(y)+'' );
|
|
979
|
-
pat.setAttr( 'width', num(w)+'' );
|
|
980
|
-
pat.setAttr( 'height', num(h)+'' );
|
|
981
|
-
pat.setAttr( 'patternUnits', "userSpaceOnUse" );
|
|
982
|
-
|
|
983
|
-
this.append(pat);
|
|
984
|
-
return {id,pat};
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
/**
|
|
991
|
-
* Properties for the `SvgComponent`.
|
|
992
|
-
*/
|
|
993
|
-
|
|
994
|
-
export interface SvgProps extends ComponentProps {
|
|
995
|
-
viewbox?: string;
|
|
996
|
-
svg?: SvgBuilder;
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
/**
|
|
1000
|
-
* A component that renders an SVG element, acting as a container for SVG graphics.
|
|
1001
|
-
* It extends `Component` and provides methods for setting SVG content.
|
|
1002
|
-
*/
|
|
1003
|
-
|
|
1004
|
-
export class SvgComponent<P extends SvgProps = SvgProps> extends Component<P> {
|
|
1005
|
-
|
|
1006
|
-
/**
|
|
1007
|
-
* Creates an instance of `SvgComponent`.
|
|
1008
|
-
* @param props - The properties for the SVG component.
|
|
1009
|
-
*/
|
|
1010
|
-
constructor( props: P ) {
|
|
1011
|
-
super( { ...props, tag: "svg", ns: SVG_NS } );
|
|
1012
|
-
|
|
1013
|
-
this.setAttribute( 'xmlns', SVG_NS );
|
|
1014
|
-
|
|
1015
|
-
if( props.viewbox ) {
|
|
1016
|
-
this.setAttribute( "viewBox", props.viewbox );
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
if( props.svg ) {
|
|
1020
|
-
this.dom.appendChild( props.svg.getDom() );
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
/**
|
|
1025
|
-
* Sets the entire SVG content of the component using an `SvgBuilder`.
|
|
1026
|
-
* Any existing content will be cleared.
|
|
1027
|
-
* @param bld - The `SvgBuilder` instance containing the SVG elements to render.
|
|
1028
|
-
*/
|
|
1029
|
-
setSvg( bld: SvgBuilder ) {
|
|
1030
|
-
this.clearContent( );
|
|
1031
|
-
this.dom.appendChild( bld.getDom() );
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
/**
|
|
1035
|
-
* Appends one or more `SvgItem` instances directly to the SVG component's DOM.
|
|
1036
|
-
*
|
|
1037
|
-
* @param items - A spread array of `SvgItem` instances to append.
|
|
1038
|
-
*/
|
|
1039
|
-
addItems( ...items: SvgItem[] ) {
|
|
1040
|
-
items.forEach( item => this.dom.appendChild( item.getDom() ) );
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* ___ ___ __
|
|
3
|
+
* \ \/ / / _
|
|
4
|
+
* \ / /_| |_
|
|
5
|
+
* / \____ _|
|
|
6
|
+
* /__/\__\ |_|
|
|
7
|
+
*
|
|
8
|
+
* @file core_svg.ts
|
|
9
|
+
* @author Etienne Cochard
|
|
10
|
+
*
|
|
11
|
+
* @copyright (c) 2024 R-libre ingenierie
|
|
12
|
+
*
|
|
13
|
+
* Use of this source code is governed by an MIT-style license
|
|
14
|
+
* that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
|
|
15
|
+
**/
|
|
16
|
+
|
|
17
|
+
import { Component, ComponentProps } from './component';
|
|
18
|
+
import { isUnitLess } from "./core_styles";
|
|
19
|
+
import { DOMEventHandler, GlobalDOMEvents, addEvent } from './core_dom';
|
|
20
|
+
import { isNumber, isString } from './core_tools';
|
|
21
|
+
|
|
22
|
+
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
23
|
+
|
|
24
|
+
// degrees to radian
|
|
25
|
+
function d2r( d: number ): number {
|
|
26
|
+
return d * Math.PI / 180.0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// polar to cartesian
|
|
30
|
+
function p2c( x: number, y: number, r: number, deg: number ): {x: number,y: number} {
|
|
31
|
+
const rad = d2r( deg );
|
|
32
|
+
return {
|
|
33
|
+
x: x + r * Math.cos( rad ),
|
|
34
|
+
y: y + r * Math.sin( rad )
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// fix prec for numbers
|
|
39
|
+
function num( x: number ): number {
|
|
40
|
+
return Math.round( x * 1000 ) / 1000;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// clean values
|
|
44
|
+
function clean( a: any, ...b: any ) {
|
|
45
|
+
// just round number values to 3 digits
|
|
46
|
+
b = b.map( ( v: any ) => {
|
|
47
|
+
if( typeof v === 'number' && isFinite(v) ) {
|
|
48
|
+
return num(v);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return v;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return String.raw( a, ...b );
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Represents a lightweight wrapper around an SVG DOM element that provides
|
|
61
|
+
* convenient, chainable helpers for common SVG manipulations (attributes,
|
|
62
|
+
* styles, transforms, classes and events).
|
|
63
|
+
*
|
|
64
|
+
* The class encapsulates an underlying SVGElement and
|
|
65
|
+
* exposes methods that operate directly on that element while allowing fluent chaining.
|
|
66
|
+
*
|
|
67
|
+
* Key behaviors:
|
|
68
|
+
* - Creation: instances are constructed with the local name of the SVG tag
|
|
69
|
+
* (e.g. "rect", "circle", "g") and internally create the element in the
|
|
70
|
+
* SVG namespace.
|
|
71
|
+
* - Attribute management: `getAttr`, `getNumAttr`, and `setAttr` let you read
|
|
72
|
+
* and write attributes. Passing `null` or `undefined` to `setAttr` removes
|
|
73
|
+
* the attribute.
|
|
74
|
+
* - Styling: `setStyle` assigns CSS properties on the element. When a numeric
|
|
75
|
+
* value is provided, a "px" unit is appended unless the property is known
|
|
76
|
+
* to be unitless (the implementation relies on a small helper to detect
|
|
77
|
+
* unitless properties).
|
|
78
|
+
* - Geometry & transforms: helpers exist for setting or appending transforms
|
|
79
|
+
* (`transform`, `add_transformation`, `clear_transform`), and higher-level
|
|
80
|
+
* helpers for rotate, translate and scale (both replace and append forms).
|
|
81
|
+
* - Stroke & fill helpers: `stroke`, `strokeWidth`, `strokeCap`, `strokeOpacity`,
|
|
82
|
+
* `fill`, and `no_fill` provide convenient setters for common paint
|
|
83
|
+
* properties. These methods return `this` so they can be chained.
|
|
84
|
+
* - Classes & clipping: `addClass` / `removeClass` manipulate the element's
|
|
85
|
+
* class list (supporting space-separated lists) and `clip` sets a clip-path
|
|
86
|
+
* reference using an id.
|
|
87
|
+
* - Reset: `reset` removes all attributes from the element (but does not
|
|
88
|
+
* remove the element itself).
|
|
89
|
+
* - Events: `addDOMEvent` attaches DOM events to the underlying element.
|
|
90
|
+
*
|
|
91
|
+
* Notes and caveats:
|
|
92
|
+
* - This class is not a full DOM abstraction; it intentionally focuses on a
|
|
93
|
+
* small, ergonomic surface for building and manipulating SVG elements.
|
|
94
|
+
* - Some behaviors depend on small utility helpers (e.g. numeric/unit detection
|
|
95
|
+
* and event-attachment helpers) provided elsewhere in the codebase.
|
|
96
|
+
*
|
|
97
|
+
*/
|
|
98
|
+
|
|
99
|
+
class SvgItem {
|
|
100
|
+
protected _dom : SVGElement;
|
|
101
|
+
|
|
102
|
+
constructor( tag: string ) {
|
|
103
|
+
this._dom = document.createElementNS("http://www.w3.org/2000/svg", tag );
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @returns the svh element dom
|
|
108
|
+
*/
|
|
109
|
+
|
|
110
|
+
getDom( ) {
|
|
111
|
+
return this._dom;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
*
|
|
116
|
+
*/
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Remove all attributes from the underlying DOM element.
|
|
120
|
+
* @returns The current instance (this) to allow method chaining.
|
|
121
|
+
*/
|
|
122
|
+
reset( ) {
|
|
123
|
+
const attrs = this._dom.attributes;
|
|
124
|
+
for (let i = attrs.length - 1; i >= 0; i--) {
|
|
125
|
+
this._dom.removeAttribute(attrs[i].name);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return this;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* change the stroke color
|
|
134
|
+
* @param color
|
|
135
|
+
*/
|
|
136
|
+
|
|
137
|
+
stroke( color: string, width?: number ): this {
|
|
138
|
+
this.setAttr( 'stroke', color );
|
|
139
|
+
if( width!==undefined ) {
|
|
140
|
+
this.setAttr( 'stroke-width', width+'px' );
|
|
141
|
+
}
|
|
142
|
+
return this;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* change the stroke width
|
|
147
|
+
* @param width
|
|
148
|
+
*/
|
|
149
|
+
strokeWidth( width: number ): this {
|
|
150
|
+
this.setAttr( 'stroke-width', width+'px' );
|
|
151
|
+
return this;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* change the stroke cap
|
|
156
|
+
* @param cap
|
|
157
|
+
*/
|
|
158
|
+
|
|
159
|
+
strokeCap( cap: "butt" | "round" | "sqaure" ) {
|
|
160
|
+
return this.setAttr( "stroke-linecap", cap );
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* change the stroke opacity attribute on the element.
|
|
165
|
+
* @param opacity - Opacity value where 0 is fully transparent and 1 is fully opaque.
|
|
166
|
+
* @returns The current instance to allow method chaining.
|
|
167
|
+
*/
|
|
168
|
+
|
|
169
|
+
strokeOpacity( opacity: number ) {
|
|
170
|
+
return this.setAttr( "stroke-opacity", opacity+"" );
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Set the shape rendering attribute to control anti-aliasing for shapes.
|
|
175
|
+
*
|
|
176
|
+
* When enabled, the attribute is set to "auto" to allow smoothing/anti-aliasing.
|
|
177
|
+
* When disabled, the attribute is set to "crispEdges" to favor pixel-aligned,
|
|
178
|
+
* non-anti-aliased rendering.
|
|
179
|
+
*
|
|
180
|
+
* @param set - True to enable anti-aliasing ("auto"), false to disable ("crispEdges").
|
|
181
|
+
* @returns the current instance to allow chaining).
|
|
182
|
+
*/
|
|
183
|
+
|
|
184
|
+
antiAlias( set: boolean ) {
|
|
185
|
+
return this.setAttr( "shape-rendering", set ? "auto" : "crispEdges" );
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* change the fill color
|
|
190
|
+
* @param color
|
|
191
|
+
*/
|
|
192
|
+
|
|
193
|
+
fill( color: string ): this {
|
|
194
|
+
this.setAttr( 'fill', color );
|
|
195
|
+
return this;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* the element is not filled
|
|
200
|
+
*/
|
|
201
|
+
|
|
202
|
+
no_fill( ): this {
|
|
203
|
+
this.setAttr( 'fill', "transparent" );
|
|
204
|
+
return this;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* return the given attribute if any
|
|
209
|
+
*/
|
|
210
|
+
|
|
211
|
+
getAttr( name: string ) : string {
|
|
212
|
+
const a = this._dom.getAttribute( name ) || '';
|
|
213
|
+
return a;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* return the attribute as number
|
|
218
|
+
*/
|
|
219
|
+
|
|
220
|
+
getNumAttr( name: string ) {
|
|
221
|
+
const a = this._dom.getAttribute( name )
|
|
222
|
+
if( a=='' ) {
|
|
223
|
+
return 0;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return parseInt( a );
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* define a new attribute
|
|
231
|
+
* @param name attibute name
|
|
232
|
+
* @param value attribute value
|
|
233
|
+
* @returns this
|
|
234
|
+
*/
|
|
235
|
+
|
|
236
|
+
setAttr( name: string, value: string ) : this {
|
|
237
|
+
if( value===null || value===undefined ) {
|
|
238
|
+
this._dom.removeAttribute( name );
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
this._dom.setAttribute( name, value );
|
|
242
|
+
}
|
|
243
|
+
return this;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* change one style value
|
|
248
|
+
*/
|
|
249
|
+
|
|
250
|
+
setStyle<K extends keyof CSSStyleDeclaration>( name: K, value: string | number ) : this {
|
|
251
|
+
const _style = this._dom.style;
|
|
252
|
+
|
|
253
|
+
if( isNumber(value) ) {
|
|
254
|
+
let v = value+"";
|
|
255
|
+
if( !isUnitLess(name as string) ) {
|
|
256
|
+
v += "px";
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
(_style as any)[name] = v;
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
(_style as any)[name] = value;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return this;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* add a class
|
|
270
|
+
* @param name class name to add
|
|
271
|
+
*/
|
|
272
|
+
|
|
273
|
+
addClass( cls: string ): this {
|
|
274
|
+
if( !cls ) return;
|
|
275
|
+
|
|
276
|
+
cls = cls.trim();
|
|
277
|
+
if( cls.indexOf(' ')>=0 ) {
|
|
278
|
+
const ccs = cls.split( " " );
|
|
279
|
+
this._dom.classList.add(...ccs);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
this._dom.classList.add(cls);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return this;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* remove a class
|
|
290
|
+
* @param name class name to remove
|
|
291
|
+
*/
|
|
292
|
+
|
|
293
|
+
removeClass( cls: string ): this {
|
|
294
|
+
if( !cls ) return;
|
|
295
|
+
|
|
296
|
+
if( cls.indexOf(' ')>=0 ) {
|
|
297
|
+
const ccs = cls.split( " " );
|
|
298
|
+
this._dom.classList.remove(...ccs);
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
this._dom.classList.remove(cls);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return this;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
*
|
|
309
|
+
*/
|
|
310
|
+
|
|
311
|
+
clip( id: string ): this {
|
|
312
|
+
this.setAttr( "clip-path", `url(#${id})` );
|
|
313
|
+
return this;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* define the whole transformation
|
|
318
|
+
*/
|
|
319
|
+
|
|
320
|
+
transform( tr: string ): this {
|
|
321
|
+
this.setAttr( "transform", tr );
|
|
322
|
+
return this;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* add a transformation to the current transformation
|
|
327
|
+
*/
|
|
328
|
+
|
|
329
|
+
add_transformation( tr: string ): this {
|
|
330
|
+
const t = this.getAttr( "transform" );
|
|
331
|
+
this.setAttr( "transform", t+' '+tr );
|
|
332
|
+
return this;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* remove all transformations
|
|
337
|
+
*/
|
|
338
|
+
|
|
339
|
+
clear_transform( ) {
|
|
340
|
+
this.setAttr( "transform", null );
|
|
341
|
+
return this;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* rotation
|
|
346
|
+
*/
|
|
347
|
+
|
|
348
|
+
rotate( deg: number, cx: number, cy: number ): this {
|
|
349
|
+
this.transform( `rotate( ${deg} ${cx} ${cy} )` );
|
|
350
|
+
return this;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
add_rotation( deg: number, cx: number, cy: number ): this {
|
|
354
|
+
this.add_transformation( `rotate( ${deg} ${cx} ${cy} )` );
|
|
355
|
+
return this;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* translation
|
|
360
|
+
*/
|
|
361
|
+
|
|
362
|
+
translate( dx: number, dy: number ): this {
|
|
363
|
+
this.transform( `translate( ${dx} ${dy} )` );
|
|
364
|
+
return this;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
add_translation( dx: number, dy: number ): this {
|
|
368
|
+
this.add_transformation( `translate( ${dx} ${dy} )` );
|
|
369
|
+
return this;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* scaling
|
|
374
|
+
*/
|
|
375
|
+
|
|
376
|
+
scale( x: number ): this {
|
|
377
|
+
this.transform( `scale( ${x} )` );
|
|
378
|
+
return this;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
add_scale( x: number ): this {
|
|
382
|
+
this.add_transformation( `scale( ${x} )` );
|
|
383
|
+
return this;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* handle SVG DOM event
|
|
388
|
+
*/
|
|
389
|
+
|
|
390
|
+
addDOMEvent<K extends keyof GlobalDOMEvents>( name: K, listener: GlobalDOMEvents[K], prepend = false ) {
|
|
391
|
+
addEvent( this._dom, name, listener as DOMEventHandler, prepend );
|
|
392
|
+
return this;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Represents an SVG path element, providing methods for constructing and manipulating SVG paths.
|
|
400
|
+
* It extends `SvgItem` to inherit common SVG element functionalities.
|
|
401
|
+
*/
|
|
402
|
+
|
|
403
|
+
export class SvgPath extends SvgItem {
|
|
404
|
+
private _path: string;
|
|
405
|
+
|
|
406
|
+
constructor( ) {
|
|
407
|
+
super( 'path' );
|
|
408
|
+
this._path = '';
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
private _update( ): this {
|
|
412
|
+
this.setAttr( 'd', this._path );
|
|
413
|
+
return this;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Resets the path data and all attributes of the SVG path element.
|
|
418
|
+
* @returns The current `SvgPath` instance for chaining.
|
|
419
|
+
*/
|
|
420
|
+
reset( ) {
|
|
421
|
+
this._path = "";
|
|
422
|
+
super.reset( );
|
|
423
|
+
return this;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Moves the current drawing position to the specified coordinates without drawing a line.
|
|
428
|
+
* This is typically the first command in a path.
|
|
429
|
+
*
|
|
430
|
+
* @param x - The x-coordinate to move to.
|
|
431
|
+
* @param y - The y-coordinate to move to.
|
|
432
|
+
* @returns this
|
|
433
|
+
*/
|
|
434
|
+
|
|
435
|
+
moveTo( x: number, y: number ) : this {
|
|
436
|
+
this._path += clean`M${x},${y}`;
|
|
437
|
+
return this._update( );
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Draws a straight line from the current position to the specified coordinates.
|
|
442
|
+
*
|
|
443
|
+
* @param x - The x-coordinate of the end point.
|
|
444
|
+
* @param y - The y-coordinate of the end point.
|
|
445
|
+
* @returns this
|
|
446
|
+
*/
|
|
447
|
+
|
|
448
|
+
lineTo( x: number, y: number ): this {
|
|
449
|
+
this._path += clean`L${x},${y}`;
|
|
450
|
+
return this._update( );
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Draws a cubic Bézier curve from the current point to `(x3, y3)` using `(x1, y1)`
|
|
455
|
+
* as the control point at the beginning of the curve and `(x2, y2)` as the
|
|
456
|
+
* control point at the end of the curve.
|
|
457
|
+
*
|
|
458
|
+
* @param x1 - The x-coordinate of the first control point.
|
|
459
|
+
* @param y1 - The y-coordinate of the first control point.
|
|
460
|
+
* @param x2 - The x-coordinate of the second control point.
|
|
461
|
+
* @param y2 - The y-coordinate of the second control point.
|
|
462
|
+
* @param x3 - The x-coordinate of the end point of the curve.
|
|
463
|
+
* @param y3 - The y-coordinate of the end point of the curve.
|
|
464
|
+
*
|
|
465
|
+
* @returns The current `SvgPath` instance for chaining.
|
|
466
|
+
*/
|
|
467
|
+
|
|
468
|
+
curveTo( x1: number, y1: number, x2: number, y2: number, x3: number, y3: number ) {
|
|
469
|
+
this._path += clean`C${x1},${y1} ${x2},${y2} ${x3},${y3}`;
|
|
470
|
+
return this._update( );
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Closes the current subpath by drawing a straight line from the current position
|
|
476
|
+
* to the initial point of the current subpath.
|
|
477
|
+
* @returns The current `SvgPath` instance for chaining.
|
|
478
|
+
*/
|
|
479
|
+
|
|
480
|
+
closePath( ): this {
|
|
481
|
+
this._path += 'Z';
|
|
482
|
+
return this._update( );
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* draw an arc
|
|
487
|
+
* Draws an elliptical arc from the current point to a new point.
|
|
488
|
+
*
|
|
489
|
+
* @param x - The x-coordinate of the center of the ellipse.
|
|
490
|
+
* @param y - The y-coordinate of the center of the ellipse.
|
|
491
|
+
* @param r - The radius of the arc.
|
|
492
|
+
* @param start - The start angle of the arc in degrees (0 is right, 90 is down).
|
|
493
|
+
* @param end - The end angle of the arc in degrees.
|
|
494
|
+
* @param clockwise - If `true`, the arc is drawn clockwise; otherwise, counter-clockwise. Defaults to `true`.
|
|
495
|
+
*
|
|
496
|
+
* @returns this
|
|
497
|
+
*/
|
|
498
|
+
|
|
499
|
+
arc( x: number, y: number, r: number, start: number, end: number, clockwise= true ): this {
|
|
500
|
+
|
|
501
|
+
const st = p2c( x, y, r, start-90 );
|
|
502
|
+
const en = p2c( x, y, r, end-90 );
|
|
503
|
+
|
|
504
|
+
const flag = ((end-start) <= 180 ? "0" : "1");
|
|
505
|
+
this._path += clean`M${st.x},${st.y}A${r},${r} 0 ${flag} ${(clockwise ? '1' : '0')} ${en.x},${en.y}`;
|
|
506
|
+
|
|
507
|
+
return this._update( );
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Represents an SVG text element, providing methods for positioning and styling text.
|
|
513
|
+
* It extends `SvgItem` to inherit common SVG element functionalities.
|
|
514
|
+
*/
|
|
515
|
+
|
|
516
|
+
export class SvgText extends SvgItem {
|
|
517
|
+
/**
|
|
518
|
+
* Creates an instance of `SvgText`.
|
|
519
|
+
* @param x - The x-coordinate for the text's starting position.
|
|
520
|
+
* @param y - The y-coordinate for the text's starting position.
|
|
521
|
+
* @param txt - The text content.
|
|
522
|
+
*/
|
|
523
|
+
constructor( x: number, y: number, txt: string ) {
|
|
524
|
+
super( 'text' );
|
|
525
|
+
|
|
526
|
+
this.setAttr( 'x', num(x)+'' );
|
|
527
|
+
this.setAttr( 'y', num(y)+'' );
|
|
528
|
+
|
|
529
|
+
this._dom.innerHTML = txt;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Sets the font family for the text.
|
|
534
|
+
* @param font - The font family name (e.g., "Arial", "sans-serif").
|
|
535
|
+
* @returns The current `SvgText` instance for chaining.
|
|
536
|
+
*/
|
|
537
|
+
font( font: string ): this {
|
|
538
|
+
return this.setAttr( 'font-family', font );
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Sets the font size for the text.
|
|
543
|
+
* @param size - The font size, either as a number (e.g., 12) or a string (e.g., "1.2em").
|
|
544
|
+
* @returns The current `SvgText` instance for chaining.
|
|
545
|
+
*/
|
|
546
|
+
fontSize( size: number | string ): this {
|
|
547
|
+
return this.setAttr( 'font-size', size+'' );
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Sets the font weight for the text.
|
|
552
|
+
* @param weight - The font weight ("light", "normal", or "bold").
|
|
553
|
+
* @returns The current `SvgText` instance for chaining.
|
|
554
|
+
*/
|
|
555
|
+
fontWeight( weight: 'light' | 'normal' | 'bold' ): this {
|
|
556
|
+
return this.setAttr( 'font-weight', weight );
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Sets the horizontal text alignment.
|
|
561
|
+
* @param align - The horizontal alignment ("left", "center", or "right").
|
|
562
|
+
* @returns The current `SvgText` instance for chaining.
|
|
563
|
+
*/
|
|
564
|
+
textAlign( align: 'left' | 'center' | 'right' ): this {
|
|
565
|
+
|
|
566
|
+
let al;
|
|
567
|
+
switch( align ) {
|
|
568
|
+
case 'left': al = 'start'; break;
|
|
569
|
+
case 'center': al = 'middle'; break;
|
|
570
|
+
case 'right': al = 'end'; break;
|
|
571
|
+
default:
|
|
572
|
+
// If an invalid alignment is provided, do nothing and return this for chaining.
|
|
573
|
+
// This prevents setting an invalid attribute value.
|
|
574
|
+
console.warn(`Invalid textAlign value: ${align}. Must be 'left', 'center', or 'right'.`);
|
|
575
|
+
return this;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return this.setAttr( 'text-anchor', al );
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* change the vertical alignment
|
|
583
|
+
*/
|
|
584
|
+
verticalAlign( align: 'top' | 'center' | 'bottom' | 'baseline' ): this {
|
|
585
|
+
|
|
586
|
+
let al;
|
|
587
|
+
switch( align ) {
|
|
588
|
+
case 'top': al = 'hanging'; break;
|
|
589
|
+
case 'center': al = 'middle'; break;
|
|
590
|
+
case 'bottom': al = 'baseline'; break;
|
|
591
|
+
case 'baseline': al = 'mathematical'; break;
|
|
592
|
+
default:
|
|
593
|
+
// If an invalid alignment is provided, do nothing and return this for chaining.
|
|
594
|
+
console.warn(`Invalid verticalAlign value: ${align}. Must be 'top', 'center', 'bottom', or 'baseline'.`);
|
|
595
|
+
return this;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
return this.setAttr( 'alignment-baseline', al );
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Represents an SVG icon, which is essentially an SVG element embedded within another.
|
|
604
|
+
* It extends `SvgItem` to inherit common SVG element functionalities.
|
|
605
|
+
*/
|
|
606
|
+
|
|
607
|
+
export class SvgIcon extends SvgItem {
|
|
608
|
+
/**
|
|
609
|
+
* Creates an instance of `SvgIcon` from an SVG string.
|
|
610
|
+
* @param svg - The SVG string, optionally prefixed with "data:image/svg+xml,".
|
|
611
|
+
*/
|
|
612
|
+
constructor( svg: string ) {
|
|
613
|
+
super( "svg" );
|
|
614
|
+
|
|
615
|
+
if( svg.startsWith("data:image/svg+xml,") ) {
|
|
616
|
+
svg = svg.substring( 19 );
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const parser = new DOMParser();
|
|
620
|
+
const doc = parser.parseFromString( decodeURIComponent(svg), "image/svg+xml");
|
|
621
|
+
|
|
622
|
+
const parserErrorElement = doc.querySelector("parsererror");
|
|
623
|
+
if( parserErrorElement ) {
|
|
624
|
+
console.error( "error while parsing svg:\n"+ parserErrorElement.textContent );
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const svgRoot = doc.documentElement; // The <svg> element from the string
|
|
628
|
+
for( let i=0; i<svgRoot.attributes.length; i++) {
|
|
629
|
+
this._dom.setAttribute( svgRoot.attributes[i].name, svgRoot.attributes[i].value );
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
for( let i=0; i<svgRoot.childNodes.length; i++) {
|
|
633
|
+
const child = svgRoot.childNodes[i];
|
|
634
|
+
if (child.nodeType === 1) {
|
|
635
|
+
this._dom.appendChild(child);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Represents a generic SVG shape element.
|
|
645
|
+
* It extends `SvgItem` to inherit common SVG element functionalities.
|
|
646
|
+
*/
|
|
647
|
+
|
|
648
|
+
export class SvgShape extends SvgItem {
|
|
649
|
+
constructor( tag: string ) {
|
|
650
|
+
super( tag );
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
*
|
|
656
|
+
* Type alias for a number or a percentage string.
|
|
657
|
+
*/
|
|
658
|
+
|
|
659
|
+
type number_or_perc = number | `${string}%`
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Represents an SVG linear gradient element.
|
|
663
|
+
* It extends `SvgItem` to inherit common SVG element functionalities.
|
|
664
|
+
*/
|
|
665
|
+
export class SvgGradient extends SvgItem {
|
|
666
|
+
|
|
667
|
+
private static g_id = 1;
|
|
668
|
+
|
|
669
|
+
private _id: string;
|
|
670
|
+
private _stops: { offset: number_or_perc, color: string } [];
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Creates an instance of `SvgGradient`.
|
|
674
|
+
* @param x1 - The x-coordinate of the starting point of the gradient vector.
|
|
675
|
+
* @param y1 - The y-coordinate of the starting point of the gradient vector.
|
|
676
|
+
* @param x2 - The x-coordinate of the ending point of the gradient vector.
|
|
677
|
+
* @param y2 - The y-coordinate of the ending point of the gradient vector.
|
|
678
|
+
* @returns The current `SvgGradient` instance for chaining.
|
|
679
|
+
*/
|
|
680
|
+
constructor( x1: number_or_perc, y1: number_or_perc, x2: number_or_perc, y2: number_or_perc ) {
|
|
681
|
+
super( 'linearGradient')
|
|
682
|
+
|
|
683
|
+
this._id = 'gx-'+SvgGradient.g_id;
|
|
684
|
+
SvgGradient.g_id++;
|
|
685
|
+
|
|
686
|
+
this.setAttr( 'id', this._id );
|
|
687
|
+
this.setAttr( 'x1', isString(x1) ? x1 : num(x1)+'' );
|
|
688
|
+
this.setAttr( 'x2', isString(x2) ? x2 : num(x2)+'' );
|
|
689
|
+
this.setAttr( 'y1', isString(y1) ? y1 : num(y1)+'' );
|
|
690
|
+
this.setAttr( 'y2', isString(y2) ? y2 : num(y2)+'' );
|
|
691
|
+
|
|
692
|
+
this._stops = [];
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Gets the URL reference to this gradient, suitable for use in `fill` or `stroke` attributes.
|
|
697
|
+
* @returns A string in the format `url(#<gradient_id>)`.
|
|
698
|
+
*/
|
|
699
|
+
get id( ) {
|
|
700
|
+
return 'url(#'+this._id+')';
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Adds a color stop to the gradient.
|
|
705
|
+
* @param offset - The offset of the color stop, either as a number (0-100) or a percentage string.
|
|
706
|
+
* @param color - The color at this stop.
|
|
707
|
+
* @returns The current `SvgGradient` instance for chaining.
|
|
708
|
+
*/
|
|
709
|
+
addStop( offset: number_or_perc, color: string ): this {
|
|
710
|
+
this._dom.insertAdjacentHTML( "beforeend", `<stop offset="${offset}%" stop-color="${color}"></stop>`);
|
|
711
|
+
return this;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Represents an SVG group element (`<g>`), which can contain other SVG elements.
|
|
717
|
+
* It extends `SvgItem` and provides methods for appending various SVG shapes and gradients.
|
|
718
|
+
*/
|
|
719
|
+
|
|
720
|
+
export class SvgGroup extends SvgItem {
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Creates an instance of `SvgGroup`.
|
|
724
|
+
* @param tag - The SVG tag name for the group element (defaults to "g").
|
|
725
|
+
*/
|
|
726
|
+
constructor( tag = "g" ) {
|
|
727
|
+
super( tag )
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Appends an `SvgItem` to this group.
|
|
732
|
+
*
|
|
733
|
+
* @template K - The type of the `SvgItem` being appended.
|
|
734
|
+
* @param item - The `SvgItem` instance to append.
|
|
735
|
+
*
|
|
736
|
+
* @returns The appended `SvgItem` instance.
|
|
737
|
+
*/
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
append<K extends SvgItem>( item: K ): K {
|
|
741
|
+
this._dom.appendChild( item.getDom() );
|
|
742
|
+
return item;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
appendItems<K extends SvgItem>( items: K[] ) {
|
|
746
|
+
items.forEach( item => {
|
|
747
|
+
this._dom.appendChild( item.getDom() );
|
|
748
|
+
} );
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Creates and appends an `SvgPath` element to this group.
|
|
753
|
+
* @returns The newly created `SvgPath` instance.
|
|
754
|
+
*/
|
|
755
|
+
|
|
756
|
+
path( ): SvgPath {
|
|
757
|
+
const path = new SvgPath( );
|
|
758
|
+
return this.append( path );
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Creates and appends an `SvgText` element to this group.
|
|
763
|
+
* @param x - The x-coordinate for the text.
|
|
764
|
+
* @param y - The y-coordinate for the text.
|
|
765
|
+
* @param txt - The text content.
|
|
766
|
+
* @returns The newly created `SvgText` instance.
|
|
767
|
+
*/
|
|
768
|
+
text( x: number, y: number, txt: string ) {
|
|
769
|
+
const text = new SvgText( x, y, txt );
|
|
770
|
+
return this.append( text );
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Creates and appends an SVG ellipse element to this group.
|
|
775
|
+
*
|
|
776
|
+
* @param x - The x-coordinate of the center of the ellipse.
|
|
777
|
+
* @param y - The y-coordinate of the center of the ellipse.
|
|
778
|
+
* @param r1 - The x-radius of the ellipse.
|
|
779
|
+
* @param r2 - The y-radius of the ellipse.
|
|
780
|
+
*
|
|
781
|
+
* @returns The newly created `SvgShape` instance representing the ellipse.
|
|
782
|
+
*/
|
|
783
|
+
ellipse( x: number, y: number, r1: number, r2?: number ): SvgShape {
|
|
784
|
+
const shape = new SvgShape( 'ellipse' );
|
|
785
|
+
shape.setAttr( 'cx', num(x)+'' );
|
|
786
|
+
shape.setAttr( 'cy', num(y)+'' );
|
|
787
|
+
shape.setAttr( 'rx', num(r1)+'' );
|
|
788
|
+
shape.setAttr( 'ry', num(r2 ?? r1)+'' );
|
|
789
|
+
return this.append( shape );
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Creates and appends an SVG circle element to this group.
|
|
794
|
+
* (Internally uses an ellipse with equal x and y radii).
|
|
795
|
+
*
|
|
796
|
+
* @param x - The x-coordinate of the center of the circle.
|
|
797
|
+
* @param y - The y-coordinate of the center of the circle.
|
|
798
|
+
* @param r1 - The radius of the circle.
|
|
799
|
+
* @returns The newly created `SvgShape` instance representing the circle.
|
|
800
|
+
*/
|
|
801
|
+
circle( x: number, y: number, r1: number ): SvgShape {
|
|
802
|
+
const shape = new SvgShape( 'ellipse' );
|
|
803
|
+
shape.setAttr( 'cx', num(x)+'' );
|
|
804
|
+
shape.setAttr( 'cy', num(y)+'' );
|
|
805
|
+
shape.setAttr( 'rx', num(r1)+'' );
|
|
806
|
+
shape.setAttr( 'ry', num(r1)+'' );
|
|
807
|
+
return this.append( shape );
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* Creates and appends an `SvgIcon` element to this group.
|
|
812
|
+
*
|
|
813
|
+
* @param svg - The SVG string content for the icon.
|
|
814
|
+
* @param x - The x-coordinate for the icon's position.
|
|
815
|
+
* @param y - The y-coordinate for the icon's position.
|
|
816
|
+
* @param w - The width of the icon.
|
|
817
|
+
* @param h - The height of the icon.
|
|
818
|
+
* @returns The newly created `SvgIcon` instance.
|
|
819
|
+
*/
|
|
820
|
+
icon( svg: string, x: number, y: number, w: number, h: number ): SvgIcon {
|
|
821
|
+
const icon = new SvgIcon( svg );
|
|
822
|
+
icon.setAttr( 'x', num(x)+'' );
|
|
823
|
+
icon.setAttr( 'y', num(y)+'' );
|
|
824
|
+
icon.setAttr( 'width', num(w)+'' );
|
|
825
|
+
icon.setAttr( 'height', num(h)+'' );
|
|
826
|
+
icon.setStyle( 'width', num(w)+'px' );
|
|
827
|
+
icon.setStyle( 'height', num(h)+'px' );
|
|
828
|
+
return this.append( icon );
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* Creates and appends an SVG rectangle element to this group.
|
|
833
|
+
* Handles negative height by adjusting `y` and `h` accordingly.
|
|
834
|
+
*
|
|
835
|
+
* @param x - The x-coordinate of the top-left corner of the rectangle.
|
|
836
|
+
* @param y - The y-coordinate of the top-left corner of the rectangle.
|
|
837
|
+
* @param w - The width of the rectangle.
|
|
838
|
+
* @param h - The height of the rectangle.
|
|
839
|
+
* @returns The newly created `SvgShape` instance representing the rectangle.
|
|
840
|
+
*/
|
|
841
|
+
rect( x: number, y: number, w: number, h: number ): SvgShape {
|
|
842
|
+
|
|
843
|
+
if( h<0 ) {
|
|
844
|
+
y = y+h;
|
|
845
|
+
h = -h;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
const shape = new SvgShape( 'rect' );
|
|
849
|
+
shape.setAttr( 'x', num(x)+'' );
|
|
850
|
+
shape.setAttr( 'y', num(y)+'' );
|
|
851
|
+
shape.setAttr( 'width', num(w)+'' );
|
|
852
|
+
shape.setAttr( 'height', num(h)+'' );
|
|
853
|
+
return this.append( shape );
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* Creates and appends an SVG group element (`<g>`) to this group.
|
|
858
|
+
* @param id - Optional. An ID for the new group.
|
|
859
|
+
* @returns The newly created `SvgGroup` instance.
|
|
860
|
+
*/
|
|
861
|
+
group( id?: string ) {
|
|
862
|
+
const group = new SvgGroup( );
|
|
863
|
+
if( id ) {
|
|
864
|
+
group.setAttr( 'id', id );
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
return this.append( group );
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
*
|
|
872
|
+
* Creates and appends an SVG linear gradient definition to this group.
|
|
873
|
+
*
|
|
874
|
+
* @example
|
|
875
|
+
* ```typescript
|
|
876
|
+
* const gradient = svgGroup.linear_gradient('0%', '0%', '0%', '100%')
|
|
877
|
+
* .addStop(0, 'red')
|
|
878
|
+
* .addStop(100, 'green');
|
|
879
|
+
* svgGroup.rect(0, 0, 100, 100).fill(gradient.id);
|
|
880
|
+
* ```
|
|
881
|
+
* @param x1 - The x-coordinate of the starting point of the gradient vector.
|
|
882
|
+
* @param y1 - The y-coordinate of the starting point of the gradient vector.
|
|
883
|
+
* @param x2 - The x-coordinate of the ending point of the gradient vector.
|
|
884
|
+
* @param y2 - The y-coordinate of the ending point of the gradient vector.
|
|
885
|
+
* @returns The newly created `SvgGradient` instance.
|
|
886
|
+
*/
|
|
887
|
+
|
|
888
|
+
linear_gradient( x1: number_or_perc, y1: number_or_perc, x2: number_or_perc, y2: number_or_perc ) {
|
|
889
|
+
const grad = new SvgGradient( x1, y1, x2, y2 );
|
|
890
|
+
return this.append( grad );
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* Clears all child elements from this SVG group.
|
|
895
|
+
*
|
|
896
|
+
* @returns void
|
|
897
|
+
*/
|
|
898
|
+
|
|
899
|
+
clear( ) {
|
|
900
|
+
const dom = this._dom;
|
|
901
|
+
while( dom.firstChild ) {
|
|
902
|
+
dom.removeChild( dom.firstChild );
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
|
|
908
|
+
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* A specialized `SvgGroup` that provides methods for adding SVG definitions like clip paths and patterns.
|
|
913
|
+
* It extends `SvgGroup` to inherit common SVG element functionalities and acts as a container for definitions.
|
|
914
|
+
*/
|
|
915
|
+
export class SvgBuilder extends SvgGroup {
|
|
916
|
+
private static g_clip_id = 1;
|
|
917
|
+
private static g_pat_id = 1;
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* Creates an instance of `SvgBuilder`.
|
|
921
|
+
*/
|
|
922
|
+
constructor( ) {
|
|
923
|
+
super( );
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* Adds an SVG clip path definition to the builder.
|
|
928
|
+
*
|
|
929
|
+
* @param x - The x-coordinate of the top-left corner of the clipping rectangle.
|
|
930
|
+
* @param y - The y-coordinate of the top-left corner of the clipping rectangle.
|
|
931
|
+
* @param w - The width of the clipping rectangle.
|
|
932
|
+
* @param h - The height of the clipping rectangle.
|
|
933
|
+
* @returns An object containing the generated `id` for the clip path and the `SvgGroup` instance representing the clip path.
|
|
934
|
+
*/
|
|
935
|
+
addClip( x: number, y: number, w: number, h: number ) {
|
|
936
|
+
/**
|
|
937
|
+
* Adds an SVG clip path definition to the builder.
|
|
938
|
+
*
|
|
939
|
+
* @param x - The x-coordinate of the top-left corner of the clipping rectangle.
|
|
940
|
+
* @param y - The y-coordinate of the top-left corner of the clipping rectangle.
|
|
941
|
+
* @param w - The width of the clipping rectangle.
|
|
942
|
+
* @param h - The height of the clipping rectangle.
|
|
943
|
+
* @returns An object containing the generated `id` for the clip path and the `SvgGroup` instance representing the clip path.
|
|
944
|
+
*/
|
|
945
|
+
const id = 'clip-'+SvgBuilder.g_clip_id++;
|
|
946
|
+
const clip = new SvgGroup( 'clipPath' );
|
|
947
|
+
clip.setAttr('id', id );
|
|
948
|
+
clip.rect( x, y, w, h );
|
|
949
|
+
|
|
950
|
+
this.append(clip);
|
|
951
|
+
return {id,clip};
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/**
|
|
955
|
+
* Adds an SVG pattern definition to the builder.
|
|
956
|
+
*
|
|
957
|
+
* @param x - The x-coordinate of the pattern tile's top-left corner.
|
|
958
|
+
* @param y - The y-coordinate of the pattern tile's top-left corner.
|
|
959
|
+
* @param w - The width of the pattern tile.
|
|
960
|
+
* @param h - The height of the pattern tile.
|
|
961
|
+
* @returns An object containing the generated `id` for the pattern and the `SvgGroup` instance representing the pattern.
|
|
962
|
+
*/
|
|
963
|
+
addPattern( x: number, y: number, w: number, h: number ) {
|
|
964
|
+
/**
|
|
965
|
+
* Adds an SVG pattern definition to the builder.
|
|
966
|
+
*
|
|
967
|
+
* @param x - The x-coordinate of the pattern tile's top-left corner.
|
|
968
|
+
* @param y - The y-coordinate of the pattern tile's top-left corner.
|
|
969
|
+
* @param w - The width of the pattern tile.
|
|
970
|
+
* @param h - The height of the pattern tile.
|
|
971
|
+
* @returns An object containing the generated `id` for the pattern and the `SvgGroup` instance representing the pattern.
|
|
972
|
+
*/
|
|
973
|
+
const id = 'pat-'+SvgBuilder.g_pat_id++;
|
|
974
|
+
|
|
975
|
+
const pat = new SvgGroup( 'pattern' );
|
|
976
|
+
pat.setAttr( 'id', id );
|
|
977
|
+
pat.setAttr( 'x', num(x)+'' );
|
|
978
|
+
pat.setAttr( 'y', num(y)+'' );
|
|
979
|
+
pat.setAttr( 'width', num(w)+'' );
|
|
980
|
+
pat.setAttr( 'height', num(h)+'' );
|
|
981
|
+
pat.setAttr( 'patternUnits', "userSpaceOnUse" );
|
|
982
|
+
|
|
983
|
+
this.append(pat);
|
|
984
|
+
return {id,pat};
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
|
|
989
|
+
|
|
990
|
+
/**
|
|
991
|
+
* Properties for the `SvgComponent`.
|
|
992
|
+
*/
|
|
993
|
+
|
|
994
|
+
export interface SvgProps extends ComponentProps {
|
|
995
|
+
viewbox?: string;
|
|
996
|
+
svg?: SvgBuilder;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
/**
|
|
1000
|
+
* A component that renders an SVG element, acting as a container for SVG graphics.
|
|
1001
|
+
* It extends `Component` and provides methods for setting SVG content.
|
|
1002
|
+
*/
|
|
1003
|
+
|
|
1004
|
+
export class SvgComponent<P extends SvgProps = SvgProps> extends Component<P> {
|
|
1005
|
+
|
|
1006
|
+
/**
|
|
1007
|
+
* Creates an instance of `SvgComponent`.
|
|
1008
|
+
* @param props - The properties for the SVG component.
|
|
1009
|
+
*/
|
|
1010
|
+
constructor( props: P ) {
|
|
1011
|
+
super( { ...props, tag: "svg", ns: SVG_NS } );
|
|
1012
|
+
|
|
1013
|
+
this.setAttribute( 'xmlns', SVG_NS );
|
|
1014
|
+
|
|
1015
|
+
if( props.viewbox ) {
|
|
1016
|
+
this.setAttribute( "viewBox", props.viewbox );
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
if( props.svg ) {
|
|
1020
|
+
this.dom.appendChild( props.svg.getDom() );
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
/**
|
|
1025
|
+
* Sets the entire SVG content of the component using an `SvgBuilder`.
|
|
1026
|
+
* Any existing content will be cleared.
|
|
1027
|
+
* @param bld - The `SvgBuilder` instance containing the SVG elements to render.
|
|
1028
|
+
*/
|
|
1029
|
+
setSvg( bld: SvgBuilder ) {
|
|
1030
|
+
this.clearContent( );
|
|
1031
|
+
this.dom.appendChild( bld.getDom() );
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
/**
|
|
1035
|
+
* Appends one or more `SvgItem` instances directly to the SVG component's DOM.
|
|
1036
|
+
*
|
|
1037
|
+
* @param items - A spread array of `SvgItem` instances to append.
|
|
1038
|
+
*/
|
|
1039
|
+
addItems( ...items: SvgItem[] ) {
|
|
1040
|
+
items.forEach( item => this.dom.appendChild( item.getDom() ) );
|
|
1041
|
+
}
|
|
1042
|
+
}
|