Skip to content
Navigation Menu
{{ message }}
forked from chartist-js/chartist
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsvg-path.js
More file actions
385 lines (351 loc) · 15.4 KB
/
Copy pathsvg-path.js
File metadata and controls
385 lines (351 loc) · 15.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
/**
* Chartist SVG path module for SVG path description creation and modification.
*
* @module Chartist.Svg.Path
*/
/* global Chartist */
(function(window, document, Chartist) {
'use strict';
/**
* Contains the descriptors of supported element types in a SVG path. Currently only move, line and curve are supported.
*
* @memberof Chartist.Svg.Path
* @type {Object}
*/
var elementDescriptions = {
m: ['x', 'y'],
l: ['x', 'y'],
c: ['x1', 'y1', 'x2', 'y2', 'x', 'y'],
a: ['rx', 'ry', 'xAr', 'lAf', 'sf', 'x', 'y']
};
/**
* Default options for newly created SVG path objects.
*
* @memberof Chartist.Svg.Path
* @type {Object}
*/
var defaultOptions = {
// The accuracy in digit count after the decimal point. This will be used to round numbers in the SVG path. If this option is set to false then no rounding will be performed.
accuracy: 3
};
function element(command, params, pathElements, pos, relative, data) {
var pathElement = Chartist.extend({
command: relative ? command.toLowerCase() : command.toUpperCase()
}, params, data ? { data: data } : {} );
pathElements.splice(pos, 0, pathElement);
}
function forEachParam(pathElements, cb) {
pathElements.forEach(function(pathElement, pathElementIndex) {
elementDescriptions[pathElement.command.toLowerCase()].forEach(function(paramName, paramIndex) {
cb(pathElement, paramName, pathElementIndex, paramIndex, pathElements);
});
});
}
/**
* Used to construct a new path object.
*
* @memberof Chartist.Svg.Path
* @param {Boolean} close If set to true then this path will be closed when stringified (with a Z at the end)
* @param {Object} options Options object that overrides the default objects. See default options for more details.
* @constructor
*/
function SvgPath(close, options) {
this.pathElements = [];
this.pos = 0;
this.close = close;
this.options = Chartist.extend({}, defaultOptions, options);
}
/**
* Gets or sets the current position (cursor) inside of the path. You can move around the cursor freely but limited to 0 or the count of existing elements. All modifications with element functions will insert new elements at the position of this cursor.
*
* @memberof Chartist.Svg.Path
* @param {Number} [pos] If a number is passed then the cursor is set to this position in the path element array.
* @return {Chartist.Svg.Path|Number} If the position parameter was passed then the return value will be the path object for easy call chaining. If no position parameter was passed then the current position is returned.
*/
function position(pos) {
if(pos !== undefined) {
this.pos = Math.max(0, Math.min(this.pathElements.length, pos));
return this;
} else {
return this.pos;
}
}
/**
* Removes elements from the path starting at the current position.
*
* @memberof Chartist.Svg.Path
* @param {Number} count Number of path elements that should be removed from the current position.
* @return {Chartist.Svg.Path} The current path object for easy call chaining.
*/
function remove(count) {
this.pathElements.splice(this.pos, count);
return this;
}
/**
* Use this function to add a new move SVG path element.
*
* @memberof Chartist.Svg.Path
* @param {Number} x The x coordinate for the move element.
* @param {Number} y The y coordinate for the move element.
* @param {Boolean} [relative] If set to true the move element will be created with relative coordinates (lowercase letter)
* @param {*} [data] Any data that should be stored with the element object that will be accessible in pathElement
* @return {Chartist.Svg.Path} The current path object for easy call chaining.
*/
function move(x, y, relative, data) {
element('M', {
x: +x,
y: +y
}, this.pathElements, this.pos++, relative, data);
return this;
}
/**
* Use this function to add a new line SVG path element.
*
* @memberof Chartist.Svg.Path
* @param {Number} x The x coordinate for the line element.
* @param {Number} y The y coordinate for the line element.
* @param {Boolean} [relative] If set to true the line element will be created with relative coordinates (lowercase letter)
* @param {*} [data] Any data that should be stored with the element object that will be accessible in pathElement
* @return {Chartist.Svg.Path} The current path object for easy call chaining.
*/
function line(x, y, relative, data) {
element('L', {
x: +x,
y: +y
}, this.pathElements, this.pos++, relative, data);
return this;
}
/**
* Use this function to add a new curve SVG path element.
*
* @memberof Chartist.Svg.Path
* @param {Number} x1 The x coordinate for the first control point of the bezier curve.
* @param {Number} y1 The y coordinate for the first control point of the bezier curve.
* @param {Number} x2 The x coordinate for the second control point of the bezier curve.
* @param {Number} y2 The y coordinate for the second control point of the bezier curve.
* @param {Number} x The x coordinate for the target point of the curve element.
* @param {Number} y The y coordinate for the target point of the curve element.
* @param {Boolean} [relative] If set to true the curve element will be created with relative coordinates (lowercase letter)
* @param {*} [data] Any data that should be stored with the element object that will be accessible in pathElement
* @return {Chartist.Svg.Path} The current path object for easy call chaining.
*/
function curve(x1, y1, x2, y2, x, y, relative, data) {
element('C', {
x1: +x1,
y1: +y1,
x2: +x2,
y2: +y2,
x: +x,
y: +y
}, this.pathElements, this.pos++, relative, data);
return this;
}
/**
* Use this function to add a new non-bezier curve SVG path element.
*
* @memberof Chartist.Svg.Path
* @param {Number} rx The radius to be used for the x-axis of the arc.
* @param {Number} ry The radius to be used for the y-axis of the arc.
* @param {Number} xAr Defines the orientation of the arc
* @param {Number} lAf Large arc flag
* @param {Number} sf Sweep flag
* @param {Number} x The x coordinate for the target point of the curve element.
* @param {Number} y The y coordinate for the target point of the curve element.
* @param {Boolean} [relative] If set to true the curve element will be created with relative coordinates (lowercase letter)
* @param {*} [data] Any data that should be stored with the element object that will be accessible in pathElement
* @return {Chartist.Svg.Path} The current path object for easy call chaining.
*/
function arc(rx, ry, xAr, lAf, sf, x, y, relative, data) {
element('A', {
rx: +rx,
ry: +ry,
xAr: +xAr,
lAf: +lAf,
sf: +sf,
x: +x,
y: +y
}, this.pathElements, this.pos++, relative, data);
return this;
}
/**
* Parses an SVG path seen in the d attribute of path elements, and inserts the parsed elements into the existing path object at the current cursor position. Any closing path indicators (Z at the end of the path) will be ignored by the parser as this is provided by the close option in the options of the path object.
*
* @memberof Chartist.Svg.Path
* @param {String} path Any SVG path that contains move (m), line (l) or curve (c) components.
* @return {Chartist.Svg.Path} The current path object for easy call chaining.
*/
function parse(path) {
// Parsing the SVG path string into an array of arrays [['M', '10', '10'], ['L', '100', '100']]
var chunks = path.replace(/([A-Za-z])([0-9])/g, '$1 $2')
.replace(/([0-9])([A-Za-z])/g, '$1 $2')
.split(/[\s,]+/)
.reduce(function(result, element) {
if(element.match(/[A-Za-z]/)) {
result.push([]);
}
result[result.length - 1].push(element);
return result;
}, []);
// If this is a closed path we remove the Z at the end because this is determined by the close option
if(chunks[chunks.length - 1][0].toUpperCase() === 'Z') {
chunks.pop();
}
// Using svgPathElementDescriptions to map raw path arrays into objects that contain the command and the parameters
// For example {command: 'M', x: '10', y: '10'}
var elements = chunks.map(function(chunk) {
var command = chunk.shift(),
description = elementDescriptions[command.toLowerCase()];
return Chartist.extend({
command: command
}, description.reduce(function(result, paramName, index) {
result[paramName] = +chunk[index];
return result;
}, {}));
});
// Preparing a splice call with the elements array as var arg params and insert the parsed elements at the current position
var spliceArgs = [this.pos, 0];
Array.prototype.push.apply(spliceArgs, elements);
Array.prototype.splice.apply(this.pathElements, spliceArgs);
// Increase the internal position by the element count
this.pos += elements.length;
return this;
}
/**
* This function renders to current SVG path object into a final SVG string that can be used in the d attribute of SVG path elements. It uses the accuracy option to round big decimals. If the close parameter was set in the constructor of this path object then a path closing Z will be appended to the output string.
*
* @memberof Chartist.Svg.Path
* @return {String}
*/
function stringify() {
var accuracyMultiplier = Math.pow(10, this.options.accuracy);
return this.pathElements.reduce(function(path, pathElement) {
var params = elementDescriptions[pathElement.command.toLowerCase()].map(function(paramName) {
return this.options.accuracy ?
(Math.round(pathElement[paramName] * accuracyMultiplier) / accuracyMultiplier) :
pathElement[paramName];
}.bind(this));
return path + pathElement.command + params.join(',');
}.bind(this), '') + (this.close ? 'Z' : '');
}
/**
* Scales all elements in the current SVG path object. There is an individual parameter for each coordinate. Scaling will also be done for control points of curves, affecting the given coordinate.
*
* @memberof Chartist.Svg.Path
* @param {Number} x The number which will be used to scale the x, x1 and x2 of all path elements.
* @param {Number} y The number which will be used to scale the y, y1 and y2 of all path elements.
* @return {Chartist.Svg.Path} The current path object for easy call chaining.
*/
function scale(x, y) {
forEachParam(this.pathElements, function(pathElement, paramName) {
pathElement[paramName] *= paramName[0] === 'x' ? x : y;
});
return this;
}
/**
* Translates all elements in the current SVG path object. The translation is relative and there is an individual parameter for each coordinate. Translation will also be done for control points of curves, affecting the given coordinate.
*
* @memberof Chartist.Svg.Path
* @param {Number} x The number which will be used to translate the x, x1 and x2 of all path elements.
* @param {Number} y The number which will be used to translate the y, y1 and y2 of all path elements.
* @return {Chartist.Svg.Path} The current path object for easy call chaining.
*/
function translate(x, y) {
forEachParam(this.pathElements, function(pathElement, paramName) {
pathElement[paramName] += paramName[0] === 'x' ? x : y;
});
return this;
}
/**
* This function will run over all existing path elements and then loop over their attributes. The callback function will be called for every path element attribute that exists in the current path.
* The method signature of the callback function looks like this:
* ```javascript
* function(pathElement, paramName, pathElementIndex, paramIndex, pathElements)
* ```
* If something else than undefined is returned by the callback function, this value will be used to replace the old value. This allows you to build custom transformations of path objects that can't be achieved using the basic transformation functions scale and translate.
*
* @memberof Chartist.Svg.Path
* @param {Function} transformFnc The callback function for the transformation. Check the signature in the function description.
* @return {Chartist.Svg.Path} The current path object for easy call chaining.
*/
function transform(transformFnc) {
forEachParam(this.pathElements, function(pathElement, paramName, pathElementIndex, paramIndex, pathElements) {
var transformed = transformFnc(pathElement, paramName, pathElementIndex, paramIndex, pathElements);
if(transformed || transformed === 0) {
pathElement[paramName] = transformed;
}
});
return this;
}
/**
* This function clones a whole path object with all its properties. This is a deep clone and path element objects will also be cloned.
*
* @memberof Chartist.Svg.Path
* @param {Boolean} [close] Optional option to set the new cloned path to closed. If not specified or false, the original path close option will be used.
* @return {Chartist.Svg.Path}
*/
function clone(close) {
var c = new Chartist.Svg.Path(close || this.close);
c.pos = this.pos;
c.pathElements = this.pathElements.slice().map(function cloneElements(pathElement) {
return Chartist.extend({}, pathElement);
});
c.options = Chartist.extend({}, this.options);
return c;
}
/**
* Split a Svg.Path object by a specific command in the path chain. The path chain will be split and an array of newly created paths objects will be returned. This is useful if you'd like to split an SVG path by it's move commands, for example, in order to isolate chunks of drawings.
*
* @memberof Chartist.Svg.Path
* @param {String} command The command you'd like to use to split the path
* @return {Array<Chartist.Svg.Path>}
*/
function splitByCommand(command) {
var split = [
new Chartist.Svg.Path()
];
this.pathElements.forEach(function(pathElement) {
if(pathElement.command === command.toUpperCase() && split[split.length - 1].pathElements.length !== 0) {
split.push(new Chartist.Svg.Path());
}
split[split.length - 1].pathElements.push(pathElement);
});
return split;
}
/**
* This static function on `Chartist.Svg.Path` is joining multiple paths together into one paths.
*
* @memberof Chartist.Svg.Path
* @param {Array<Chartist.Svg.Path>} paths A list of paths to be joined together. The order is important.
* @param {boolean} close If the newly created path should be a closed path
* @param {Object} options Path options for the newly created path.
* @return {Chartist.Svg.Path}
*/
function join(paths, close, options) {
var joinedPath = new Chartist.Svg.Path(close, options);
for(var i = 0; i < paths.length; i++) {
var path = paths[i];
for(var j = 0; j < path.pathElements.length; j++) {
joinedPath.pathElements.push(path.pathElements[j]);
}
}
return joinedPath;
}
Chartist.Svg.Path = Chartist.Class.extend({
constructor: SvgPath,
position: position,
remove: remove,
move: move,
line: line,
curve: curve,
arc: arc,
scale: scale,
translate: translate,
transform: transform,
parse: parse,
stringify: stringify,
clone: clone,
splitByCommand: splitByCommand
});
Chartist.Svg.Path.elementDescriptions = elementDescriptions;
Chartist.Svg.Path.join = join;
}(window, document, Chartist));
You can’t perform that action at this time.
