diff --git a/.eslintrc.js b/.eslintrc.js
deleted file mode 100644
index 631b6dc..0000000
--- a/.eslintrc.js
+++ /dev/null
@@ -1,25 +0,0 @@
-module.exports = {
- env: {
- browser: true,
- commonjs: true,
- es6: true,
- node: true,
- mocha: true
- },
- plugins: [
- 'mocha'
- ],
- extends: [
- 'standard',
- 'plugin:mocha/recommended'
- ],
- globals: {
- Atomics: 'readonly',
- SharedArrayBuffer: 'readonly'
- },
- parserOptions: {
- ecmaVersion: 2018
- },
- rules: {
- }
-}
diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml
deleted file mode 100644
index 55f434e..0000000
--- a/.github/workflows/nodejs.yml
+++ /dev/null
@@ -1,28 +0,0 @@
-# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
-# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
-
-name: Tests
-
-on:
- push:
- branches: [ master ]
- pull_request:
- branches: [ master ]
-
-jobs:
- build:
-
- runs-on: ubuntu-latest
-
- strategy:
- matrix:
- node-version: [16.x, 18.x]
-
- steps:
- - uses: actions/checkout@v3
- - name: Using Node.js v${{ matrix.node-version }}
- uses: actions/setup-node@v3
- with:
- node-version: ${{ matrix.node-version }}
- - run: npm install
- - run: npm test
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..4640904
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1 @@
+# TODO
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..3a4cacc
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,22 @@
+name: Tests
+on:
+ push:
+ branches: [master]
+ pull_request:
+ branches: [master]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '22'
+ cache: 'npm'
+ - name: Install dependencies
+ run: npm ci --no-audit
+ - name: Run Jest tests
+ run: npm test
diff --git a/.gitignore b/.gitignore
index 25c8fdb..b07ff2a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
node_modules
-package-lock.json
\ No newline at end of file
+package-lock.json
+coverage
diff --git a/README.md b/README.md
index 942f227..b04dec8 100644
--- a/README.md
+++ b/README.md
@@ -1,603 +1,729 @@
-[![Version](http://img.shields.io/npm/v/@pangenerator/utils.svg)](https://www.npmjs.org/package/@pangenerator/utils) [![Tests](https://img.shields.io/github/workflow/status/panGenerator/utils/Tests)](https://github.com/panGenerator/utils)
-![Dependencies](https://img.shields.io/david/panGenerator/utils) ![Dev Dependencies](https://img.shields.io/david/dev/panGenerator/utils)
-
-
-
-## utils
-Various functions used in javascript tools
-
-
-* [utils](#module_utils)
- * _static_
- * [.map(value, low1, high1, low2, high2)](#module_utils.map) ⇒ Number
- * [.clamp(value, min, max)](#module_utils.clamp) ⇒ Number
- * [.norm(value, start, stop)](#module_utils.norm) ⇒ Number
- * [.random([low], high)](#module_utils.random) ⇒ Number
- * [.randomDir()](#module_utils.randomDir) ⇒ Number
- * [.lerp(start, stop, amt)](#module_utils.lerp) ⇒ Number
- * [.lerp3(A, B, amt)](#module_utils.lerp3) ⇒ Point
- * [.lerpedPoints(A, B, count)](#module_utils.lerpedPoints) ⇒ Array.<Point>
- * [.square(a)](#module_utils.square) ⇒ Number
- * [.dist(A, B)](#module_utils.dist) ⇒ Number
- * [.degrees(radians)](#module_utils.degrees) ⇒ Number
- * [.radians(degrees)](#module_utils.radians) ⇒ Number
- * [.intersection(c1, c2)](#module_utils.intersection) ⇒ Array
\| Boolean
- * [.randomName(N)](#module_utils.randomName) ⇒ String
- * [.timestampName()](#module_utils.timestampName) ⇒ String
- * [.randomIndex(N)](#module_utils.randomIndex) ⇒ Number
- * [.copyArray(source)](#module_utils.copyArray) ⇒ Array
- * [.shuffleArray(source)](#module_utils.shuffleArray) ⇒ Array
- * [.filterUnique(source)](#module_utils.filterUnique) ⇒ Array
- * [.lerpColor(a, b, amt)](#module_utils.lerpColor) ⇒ String
- * [.precision(value, precision)](#module_utils.precision) ⇒ Number
- * [.loadJSON(address, callback)](#module_utils.loadJSON)
- * [.removeDiacritics(str)](#module_utils.removeDiacritics) ⇒ String
- * [.removeNonAlphaNumeric(str)](#module_utils.removeNonAlphaNumeric) ⇒ String
- * [.splitChunks(str, n, discard)](#module_utils.splitChunks) ⇒ Array
- * [.getQuarter(d)](#module_utils.getQuarter) ⇒ Array
- * [.quarterExtent(quarter, year)](#module_utils.quarterExtent) ⇒ Array
- * [.datesBetween(start, end)](#module_utils.datesBetween) ⇒ Array
- * [.downloadDataUri(options)](#module_utils.downloadDataUri)
- * [.polarToCartesian(r, angle)](#module_utils.polarToCartesian) ⇒ Point
- * [.cartesianToPolar(x, y)](#module_utils.cartesianToPolar) ⇒ Object
- * [.pageOffset(elem)](#module_utils.pageOffset) ⇒ Object
- * [.fuzzySearch(list, searchValue)](#module_utils.fuzzySearch) ⇒ Array
- * [.dist2(A, B)](#module_utils.dist2) ⇒ Number
- * [.distToSegment2(A, S, E)](#module_utils.distToSegment2) ⇒ Number
- * [.distToSegment(A, S, E)](#module_utils.distToSegment) ⇒ Number
- * [.sepCase(str)](#module_utils.sepCase) ⇒ string
- * [.snakeCase(str)](#module_utils.snakeCase) ⇒ string
- * [.kebabCase(str)](#module_utils.kebabCase) ⇒ string
- * [.camelCase(str)](#module_utils.camelCase) ⇒ string
- * [.contains(elem, arr)](#module_utils.contains) ⇒ boolean
- * [.getCSS(parentElement)](#module_utils.getCSS) ⇒ string
- * [.appendCSS(cssText, element)](#module_utils.appendCSS)
- * [.getSVGString(svgNode)](#module_utils.getSVGString) ⇒ string
- * [.svgStringToImage(svgString, width, height, format, transparent, callback)](#module_utils.svgStringToImage)
- * [.svgToUri(svgNode)](#module_utils.svgToUri) ⇒ string
- * [.shallowCopyExcluding(obj, prop)](#module_utils.shallowCopyExcluding) ⇒ Object
- * _inner_
- * [~Point](#module_utils..Point) : Object
- * [~Circle](#module_utils..Circle) : Object
-
-
-* * *
-
-
-
-### utils.map(value, low1, high1, low2, high2) ⇒ Number
-Map a number from one range to another
-
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Number
- Mapped number
+[![Version](http://img.shields.io/npm/v/@pangenerator/utils.svg)](https://www.npmjs.org/package/@pangenerator/utils)
+[![Tests](https://img.shields.io/github/actions/workflow/status/panGenerator/utils/tests.yml)](https://github.com/panGenerator/utils)
+
+## Modules
+
+
+- PID
+PID controller
+
+
+
+## Constants
+
+
+- copyArray ⇒
Array
+Copy array
+
+- shuffleArray ⇒
Array
+Shuffle array
+
+- filterUnique ⇒
Array
+Filter array unique
+
+- fuzzySearch ⇒
Array
+Fuzzy search element in list
+
+- contains ⇒
boolean
+Check if array contains
+
+- lerpColor ⇒
String
+Linear color interpolation
+
+- getQuarter ⇒
Array
+Get quarter from date
+
+- quarterExtent ⇒
Array
+Get quarter extent
+
+- datesBetween ⇒
Array
+Get all dates between two dates
+
+- lerp3 ⇒
Point
+Linear interpolation in 3D
+
+- lerpStops ⇒
Array.<Point>
+Linear interpolation in 3D array
+
+- dist ⇒
Number
+Distance between two points (2D and 3D)
+
+- intersectLines ⇒
Point
| Boolean
+Find intersection point between two lines
+
+- intersectCircles ⇒
Array
| Boolean
+Find intersection points between two circles
+
+- polarToCartesian ⇒
Point
+Convert coordinates from polar to cartesian
+
+- cartesianToPolar ⇒
Object
+Convert coordinates from cartesian to polar
+
+- dist2 ⇒
Number
+Distance between two points (2D and 3D) squared
+
+- distToSegment2 ⇒
Number
+Distance between point and segment squared
+
+- distToSegment ⇒
Number
+Distance between point and segment
+
+- map ⇒
Number
+Map a number from one range to another
+
+- clamp ⇒
Number
+Clamp a number to range
+
+- norm ⇒
Number
+Normalize a number
+
+- lerp ⇒
Number
+Linear interpolation
+
+- square ⇒
Number
+Square
+
+- degrees ⇒
Number
+Convert angle in radians to degrees
+
+- radians ⇒
Number
+Convert angle in degrees to radians
+
+- precision ⇒
Number
+Round number to precision
+
+- shallowCopyExcluding ⇒
Object
+Copy object excluding property
+
+- random ⇒
Number
+Generate random number from range
+
+- randomDir ⇒
Number
+Generate random direction (-1 or 1)
+
+- randomIndex ⇒
Number
+Generate random index
+
+- randomName ⇒
String
+Generate random name
+
+- timestampName ⇒
String
+Generate timestamp name
+
+- removeDiacritics ⇒
String
+Remove polish diacritics
+
+- removeNonAlphaNumeric ⇒
String
+Remove all non alphanumeric characters
+
+- splitChunks ⇒
Array
+Split string to N sized chunks
+
+- sepCase ⇒
string
+Convert string to custom separator case
+
+- snakeCase ⇒
string
+Convert string to snake case
+
+- kebabCase ⇒
string
+Convert string to kebab case
+
+- camelCase ⇒
string
+Convert string to camel case
+
+
+
+## Typedefs
+
+
+- Point :
Object
+
+- Circle :
Object
+
+
+
+
+
+## PID
+PID controller
+
+
+* [PID](#module_PID)
+ * [.set(P, I, D)](#module_PID+set)
+ * [.update(current, target)](#module_PID+update) ⇒ number
+
+
+* * *
+
+
+
+### piD.set(P, I, D)
+Set PID gains
+
+**Kind**: instance method of [PID
](#module_PID)
+**Params**
+
+- P number
= 0
- Proportional Gain
+- I number
= 0
- Integral Gain
+- D number
= 0
- Derivative Gain
+
+
+* * *
+
+
+
+### piD.update(current, target) ⇒ number
+Update PID controller
+
+**Kind**: instance method of [PID
](#module_PID)
+**Returns**: number
- Output value
**Params**
-- value Number
- Number to map
-- low1 Number
- Source range lower bound
-- high1 Number
- Source range upper bound
-- low2 Number
- Target range lower bound
-- high2 Number
- Target range upper bound
+- current number
- Current value
+- target number
- Target value
* * *
-
+
-### utils.clamp(value, min, max) ⇒ Number
-Clamp a number to range
+## copyArray ⇒ Array
+Copy array
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Number
- Clamped number
+**Kind**: global constant
+**Returns**: Array
- copy of the array
**Params**
-- value Number
- Number to clamp
-- min Number
- Range lower bound
-- max Number
- Range upper bound
+- source Array
- source array
* * *
-
+
-### utils.norm(value, start, stop) ⇒ Number
-Normalize a number
+## shuffleArray ⇒ Array
+Shuffle array
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Number
- normalized number (0.0 - 1.0)
+**Kind**: global constant
+**Returns**: Array
- shuffled array copy
**Params**
-- value Number
- value to normalize
-- start Number
- Source range lower bound
-- stop Number
- Source range upper bound
+- source Array
- source array
* * *
-
+
-### utils.random([low], high) ⇒ Number
-Generate random number from range
+## filterUnique ⇒ Array
+Filter array unique
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Number
- Random number
+**Kind**: global constant
+**Returns**: Array
- array with unique elements only
**Params**
-- [low] Number
- Range lower bound
-- high Number
- Range upper bound
+- source Array
- source array
* * *
-
+
-### utils.randomDir() ⇒ Number
-Generate random direction (-1 or 1)
+## fuzzySearch ⇒ Array
+Fuzzy search element in list
+
+**Kind**: global constant
+**Returns**: Array
- elements matching search value
+**Params**
+
+- list Array
- Array of terms
+- searchValue String
- search value to find
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Number
- Random direction
* * *
-
+
-### utils.lerp(start, stop, amt) ⇒ Number
-Linear interpolation
+## contains ⇒ boolean
+Check if array contains
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Number
- Interpolated value
+**Kind**: global constant
+**Returns**: boolean
- - true when element is in array
**Params**
-- start Number
- First value
-- stop Number
- Second value
-- amt Number
- amount to interpolate
+- elem any
- element to find in array
+- arr Array
- array to look in
* * *
-
+
-### utils.lerp3(A, B, amt) ⇒ Point
-Linear interpolation in 3D
+## lerpColor ⇒ String
+Linear color interpolation
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Point
- Interpolated point
+**Kind**: global constant
+**Returns**: String
- Interpolated color
**Params**
-- A Point
- First point
-- B Point
- Second point
+- a String
- First color
+- b String
- Second color
- amt Number
- amount to interpolate
* * *
-
+
-### utils.lerpedPoints(A, B, count) ⇒ Array.<Point>
-Linear interpolation in 3D array
+## getQuarter ⇒ Array
+Get quarter from date
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Array.<Point>
- Interpolated points
+**Kind**: global constant
+**Returns**: Array
- year and quarter (1-4)
**Params**
-- A Point
- First point
-- B Point
- Second point
-- count Number
- Point count
+- d Date
- Date to get quarter from
* * *
-
+
-### utils.square(a) ⇒ Number
-Square
+## quarterExtent ⇒ Array
+Get quarter extent
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Number
- squared number
+**Kind**: global constant
+**Returns**: Array
- start and end date of quarter
**Params**
-- a Number
- Number to square
+- quarter Number
- quarter (1-4)
+- year Number
- full year
* * *
-
+
-### utils.dist(A, B) ⇒ Number
-Distance between two points (2D and 3D)
+## datesBetween ⇒ Array
+Get all dates between two dates
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Number
- distance between the points
+**Kind**: global constant
+**Returns**: Array
- all dates between start and end
**Params**
-- A Point
- First point
-- B Point
- Second point
+- start Date
- start date
+- end Date
- end date
* * *
-
+
-### utils.degrees(radians) ⇒ Number
-Convert angle in radians to degrees
+## lerp3 ⇒ [Point
](#Point)
+Linear interpolation in 3D
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Number
- angle in degrees
+**Kind**: global constant
+**Returns**: [Point
](#Point) - Interpolated point
**Params**
-- radians Number
- angle in radians
+- A [Point
](#Point) - First point
+- B [Point
](#Point) - Second point
+- amt Number
- amount to interpolate
* * *
-
+
-### utils.radians(degrees) ⇒ Number
-Convert angle in degrees to radians
+## lerpStops ⇒ [Array.<Point>
](#Point)
+Linear interpolation in 3D array
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Number
- angle in radians
+**Kind**: global constant
+**Returns**: [Array.<Point>
](#Point) - Interpolated points
**Params**
-- degrees Number
- angle in degrees
+- A [Point
](#Point) - First point
+- B [Point
](#Point) - Second point
+- count Number
- Point count
* * *
-
+
-### utils.intersection(c1, c2) ⇒ Array
\| Boolean
-Find intersection points between two circles
+## dist ⇒ Number
+Distance between two points (2D and 3D)
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Array
\| Boolean
- intersection or false (if no intersection)
+**Kind**: global constant
+**Returns**: Number
- distance between the points
**Params**
-- c1 Circle
- first circle
-- c2 Circle
- second circle
+- A [Point
](#Point) - First point
+- B [Point
](#Point) - Second point
* * *
-
+
-### utils.randomName(N) ⇒ String
-Generate random name
+## intersectLines ⇒ [Point
](#Point) \| Boolean
+Find intersection point between two lines
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: String
- random name
+**Kind**: global constant
+**Returns**: [Point
](#Point) \| Boolean
- intersection or false (if no intersection)
**Params**
-- N Number
- length of the name
+- p1 [Point
](#Point) - first point of first line
+- p2 [Point
](#Point) - second point of first line
+- p3 [Point
](#Point) - first point of second line
+- p4 [Point
](#Point) - second point of second line
* * *
-
+
-### utils.timestampName() ⇒ String
-Generate timestamp name
+## intersectCircles ⇒ Array
\| Boolean
+Find intersection points between two circles
+
+**Kind**: global constant
+**Returns**: Array
\| Boolean
- intersection or false (if no intersection)
+**Params**
+
+- c1 [Circle
](#Circle) - first circle
+- c2 [Circle
](#Circle) - second circle
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: String
- timestamp name
* * *
-
+
-### utils.randomIndex(N) ⇒ Number
-Generate random name
+## polarToCartesian ⇒ [Point
](#Point)
+Convert coordinates from polar to cartesian
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Number
- random index
+**Kind**: global constant
+**Returns**: [Point
](#Point) - cartesian coordinates
**Params**
-- N Number
- max index
+- r Number
- radius
+- angle Number
- angle
* * *
-
+
-### utils.copyArray(source) ⇒ Array
-Copy array
+## cartesianToPolar ⇒ Object
+Convert coordinates from cartesian to polar
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Array
- array copy
+**Kind**: global constant
+**Returns**: Object
- polar coordinates
**Params**
-- source Array
- source array
+- P [Point
](#Point) - cartesian coordinates
* * *
-
+
-### utils.shuffleArray(source) ⇒ Array
-Shuffle array
+## dist2 ⇒ Number
+Distance between two points (2D and 3D) squared
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Array
- shuffled array copy
+**Kind**: global constant
+**Returns**: Number
- squared distance between the points
**Params**
-- source Array
- source array
+- A [Point
](#Point) - First point
+- B [Point
](#Point) - Second point
* * *
-
+
-### utils.filterUnique(source) ⇒ Array
-Filter array unique
+## distToSegment2 ⇒ Number
+Distance between point and segment squared
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Array
- array with unique elements only
+**Kind**: global constant
+**Returns**: Number
- squared distance between the point and the segment
**Params**
-- source Array
- source array
+- A [Point
](#Point) - First point
+- S [Point
](#Point) - Segment start
+- E [Point
](#Point) - Segment end
* * *
-
+
-### utils.lerpColor(a, b, amt) ⇒ String
-Linear color interpolation
+## distToSegment ⇒ Number
+Distance between point and segment
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: String
- Interpolated color
+**Kind**: global constant
+**Returns**: Number
- distance between the point and the segment
**Params**
-- a String
- First color
-- b String
- Second color
-- amt Number
- amount to interpolate
+- A [Point
](#Point) - First point
+- S [Point
](#Point) - Segment start
+- E [Point
](#Point) - Segment end
* * *
-
+
-### utils.precision(value, precision) ⇒ Number
-Round number to precision
+## map ⇒ Number
+Map a number from one range to another
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Number
- rounded number
+**Kind**: global constant
+**Returns**: Number
- Mapped number
**Params**
-- value Number
- value to round
-- precision Number
- decimal places
+- value Number
- Number to map
+- low1 Number
- Source range lower bound
+- high1 Number
- Source range upper bound
+- low2 Number
- Target range lower bound
+- high2 Number
- Target range upper bound
* * *
-
+
-### utils.loadJSON(address, callback)
-Load JSON
+## clamp ⇒ Number
+Clamp a number to range
-**Kind**: static method of [utils
](#module_utils)
+**Kind**: global constant
+**Returns**: Number
- Clamped number
**Params**
-- address String
- address of JSON to load
-- callback function
- function to call on result
+- value Number
- Number to clamp
+- min Number
- Range lower bound
+- max Number
- Range upper bound
* * *
-
+
-### utils.removeDiacritics(str) ⇒ String
-Remove polish diacritics
+## norm ⇒ Number
+Normalize a number
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: String
- string without diacritics
+**Kind**: global constant
+**Returns**: Number
- normalized number (0.0 - 1.0)
**Params**
-- str String
- string with diacritics
+- value Number
- value to normalize
+- start Number
- Source range lower bound
+- stop Number
- Source range upper bound
* * *
-
+
-### utils.removeNonAlphaNumeric(str) ⇒ String
-Remove all non alphanumeric characters
+## lerp ⇒ Number
+Linear interpolation
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: String
- string without non alphanumeric characters
+**Kind**: global constant
+**Returns**: Number
- Interpolated value
**Params**
-- str String
- string with non alphanumeric characters
+- start Number
- First value
+- stop Number
- Second value
+- amt Number
- amount to interpolate
* * *
-
+
-### utils.splitChunks(str, n, discard) ⇒ Array
-Split string to N sized chunks
+## square ⇒ Number
+Square
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Array
- array of string chunks
+**Kind**: global constant
+**Returns**: Number
- squared number
**Params**
-- str String
- string to split
-- n Number
- chunk length
-- discard Boolean
- discard chunks shorter than N
+- a Number
- Number to square
* * *
-
+
-### utils.getQuarter(d) ⇒ Array
-Get quarter from date
+## degrees ⇒ Number
+Convert angle in radians to degrees
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Array
- year and quarter (1-4)
+**Kind**: global constant
+**Returns**: Number
- angle in degrees
**Params**
-- d Date
- Date to get quarter from
+- radians Number
- angle in radians
* * *
-
+
-### utils.quarterExtent(quarter, year) ⇒ Array
-Get quarter extent
+## radians ⇒ Number
+Convert angle in degrees to radians
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Array
- start and end date of quarter
+**Kind**: global constant
+**Returns**: Number
- angle in radians
**Params**
-- quarter Number
- quarter (1-4)
-- year Number
- full year
+- degrees Number
- angle in degrees
* * *
-
+
-### utils.datesBetween(start, end) ⇒ Array
-Get all dates between two dates
+## precision ⇒ Number
+Round number to precision
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Array
- all dates between start and end
+**Kind**: global constant
+**Returns**: Number
- rounded number
**Params**
-- start Date
- start date
-- end Date
- end date
+- value Number
- value to round
+- precision Number
- decimal places
* * *
-
+
-### utils.downloadDataUri(options)
-Download file from base64 data uri
+## shallowCopyExcluding ⇒ Object
+Copy object excluding property
-**Kind**: static method of [utils
](#module_utils)
+**Kind**: global constant
+**Returns**: Object
- - copied object
**Params**
-- options Object
- options for the downloaded file
- - .data String
- contents of the file
- - .filename String
- name of the file
+- obj Object
- Object to copy
+- prop string
- property name
* * *
-
+
-### utils.polarToCartesian(r, angle) ⇒ Point
-Convert coordinates from polar to cartesian
+## random ⇒ Number
+Generate random number from range
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Point
- cartesian coordinates
+**Kind**: global constant
+**Returns**: Number
- Random number
**Params**
-- r Number
- radius
-- angle Number
- angle
+- [low] Number
- Range lower bound
+- high Number
- Range upper bound
* * *
-
+
-### utils.cartesianToPolar(x, y) ⇒ Object
-Convert coordinates from cartesian to polar
-
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Object
- polar coordinates
-**Params**
-
-- x Number
- x coordinate
-- y Number
- y coordinate
+## randomDir ⇒ Number
+Generate random direction (-1 or 1)
+**Kind**: global constant
+**Returns**: Number
- Random direction
* * *
-
+
-### utils.pageOffset(elem) ⇒ Object
-Get element page offset
+## randomIndex ⇒ Number
+Generate random index
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Object
- top and left page offset
+**Kind**: global constant
+**Returns**: Number
- random index
**Params**
-- elem Object
- HTML element
+- N Number
- max index
* * *
-
+
-### utils.fuzzySearch(list, searchValue) ⇒ Array
-Fuzzy search element in list
+## randomName ⇒ String
+Generate random name
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Array
- elements matching search value
+**Kind**: global constant
+**Returns**: String
- random name
**Params**
-- list Array
- Array of terms
-- searchValue String
- search value to find
+- N Number
- length of the name
* * *
-
+
-### utils.dist2(A, B) ⇒ Number
-Distance between two points (2D and 3D) squared
+## timestampName ⇒ String
+Generate timestamp name
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Number
- squared distance between the points
+**Kind**: global constant
+**Returns**: String
- timestamp name
+
+* * *
+
+
+
+## removeDiacritics ⇒ String
+Remove polish diacritics
+
+**Kind**: global constant
+**Returns**: String
- string without diacritics
**Params**
-- A Point
- First point
-- B Point
- Second point
+- str String
- string with diacritics
* * *
-
+
-### utils.distToSegment2(A, S, E) ⇒ Number
-Distance between point and segment squared
+## removeNonAlphaNumeric ⇒ String
+Remove all non alphanumeric characters
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Number
- squared distance between the point and the segment
+**Kind**: global constant
+**Returns**: String
- string without non alphanumeric characters
**Params**
-- A Point
- First point
-- S Point
- Segment start
-- E Point
- Segment end
+- str String
- string with non alphanumeric characters
* * *
-
+
-### utils.distToSegment(A, S, E) ⇒ Number
-Distance between point and segment
+## splitChunks ⇒ Array
+Split string to N sized chunks
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Number
- distance between the point and the segment
+**Kind**: global constant
+**Returns**: Array
- array of string chunks
**Params**
-- A Point
- First point
-- S Point
- Segment start
-- E Point
- Segment end
+- str String
- string to split
+- n Number
- chunk length
+- discard Boolean
- discard chunks shorter than N
* * *
-
+
-### utils.sepCase(str) ⇒ string
+## sepCase ⇒ string
Convert string to custom separator case
-**Kind**: static method of [utils
](#module_utils)
+**Kind**: global constant
**Returns**: string
- custom cased string
**Params**
@@ -606,12 +732,12 @@ Convert string to custom separator case
* * *
-
+
-### utils.snakeCase(str) ⇒ string
+## snakeCase ⇒ string
Convert string to snake case
-**Kind**: static method of [utils
](#module_utils)
+**Kind**: global constant
**Returns**: string
- snake cased string
**Params**
@@ -620,12 +746,12 @@ Convert string to snake case
* * *
-
+
-### utils.kebabCase(str) ⇒ string
+## kebabCase ⇒ string
Convert string to kebab case
-**Kind**: static method of [utils
](#module_utils)
+**Kind**: global constant
**Returns**: string
- kebab cased string
**Params**
@@ -634,12 +760,12 @@ Convert string to kebab case
* * *
-
+
-### utils.camelCase(str) ⇒ string
+## camelCase ⇒ string
Convert string to camel case
-**Kind**: static method of [utils
](#module_utils)
+**Kind**: global constant
**Returns**: string
- camel cased string
**Params**
@@ -648,114 +774,10 @@ Convert string to camel case
* * *
-
-
-### utils.contains(elem, arr) ⇒ boolean
-Check if array contains
-
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: boolean
- - true when element is in array
-**Params**
-
-- elem any
- element to find in array
-- arr Array
- array to look in
-
-
-* * *
-
-
-
-### utils.getCSS(parentElement) ⇒ string
-Get CSS Styles from element
-
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: string
- - extracted CSS
-**Params**
-
-- parentElement HTMLElement
- Element to get styles from
-
-
-* * *
-
-
-
-### utils.appendCSS(cssText, element)
-Append CSS to element
-
-**Kind**: static method of [utils
](#module_utils)
-**Params**
-
-- cssText string
- CSS text to append
-- element HTMLElement
- element to append CSS to
-
-
-* * *
-
-
-
-### utils.getSVGString(svgNode) ⇒ string
-Get SVG string from node
-
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: string
- - svg as string
-**Params**
-
-- svgNode HTMLElement
- svg node to get text from
-
-
-* * *
-
-
-
-### utils.svgStringToImage(svgString, width, height, format, transparent, callback)
-Convert SVG string to image and call the callback
-
-**Kind**: static method of [utils
](#module_utils)
-**Params**
-
-- svgString string
- SVG string to convert
-- width Number
- width of output image
-- height Number
- height of output image
-- format string
- format of output image
-- transparent boolean
- transparency flag
-- callback function
- function to call when ready
-
-
-* * *
-
-
-
-### utils.svgToUri(svgNode) ⇒ string
-Convert SVG to data uri
-
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: string
- - uri data scheme string
-**Params**
-
-- svgNode HTMLElement
- SVG element to get uri from
-
-
-* * *
-
-
-
-### utils.shallowCopyExcluding(obj, prop) ⇒ Object
-Copy object excluding property
-
-**Kind**: static method of [utils
](#module_utils)
-**Returns**: Object
- - copied object
-**Params**
-
-- obj Object
- Object to copy
-- prop string
- property name
-
-
-* * *
-
-
+
-### utils~Point : Object
-**Kind**: inner typedef of [utils
](#module_utils)
+## Point : Object
+**Kind**: global typedef
**Properties**
- x Number
- x coordinate
@@ -765,10 +787,10 @@ Copy object excluding property
* * *
-
+
-### utils~Circle : Object
-**Kind**: inner typedef of [utils
](#module_utils)
+## Circle : Object
+**Kind**: global typedef
**Properties**
- x Number
- x coordinate of the center point
@@ -779,4 +801,4 @@ Copy object excluding property
* * *
-Copyright © 2023 panGenerator
+[panGenerator](https://pangenerator.com) 2024
diff --git a/jsdoc.conf b/jsdoc.conf
new file mode 100644
index 0000000..15d1b97
--- /dev/null
+++ b/jsdoc.conf
@@ -0,0 +1,5 @@
+{
+ "source": {
+ "includePattern": ".+\\.(js(doc|x)?|mjs)$"
+ }
+}
diff --git a/package.json b/package.json
index 84f7d31..1c3b09a 100644
--- a/package.json
+++ b/package.json
@@ -1,14 +1,13 @@
{
"name": "@pangenerator/utils",
"version": "2.8.7",
- "description": "A collection of snippets for creative coding",
+ "description": "A collection of functions and classes for creative coding and interactive projects",
"main": "utils.js",
"scripts": {
"rollup": "rollup --config src/rollup.config.mjs",
- "docs": "jsdoc2md --template src/README.hbs --files src/utils.js --separators --param-list-format list --property-list-format list --helper src/year.js> README.md",
+ "docs": "jsdoc2md -c jsdoc.conf --template src/README.hbs --files src/**/*.mjs --separators --param-list-format list --property-list-format list --helper src/year.js> README.md",
"build": "npm run rollup && npm run docs",
- "pretest": "npm run build",
- "test": "mocha"
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --collectCoverage"
},
"engines": {
"npm": ">=7.0.0 <=20.0.0",
@@ -32,16 +31,22 @@
},
"license": "MIT",
"devDependencies": {
- "@rollup/plugin-terser": "^0.4.3",
- "eslint": "^7.0.0",
- "eslint-config-standard": "^14.1.1",
- "eslint-plugin-import": "^2.20.2",
- "eslint-plugin-mocha": "^7.0.0",
- "eslint-plugin-node": "^11.1.0",
- "eslint-plugin-promise": "^4.2.1",
- "eslint-plugin-standard": "^4.0.1",
- "jsdoc-to-markdown": "^6.0.1",
- "mocha": "^7.1.2",
- "rollup": "^3.25.1"
+ "@rollup/plugin-terser": "^0.4.4",
+ "jest": "^29.7.0",
+ "jsdoc-to-markdown": "^8.0.3",
+ "rollup": "^4.20.0"
+ },
+ "jest": {
+ "testMatch": [
+ "**/?(*.)test.?js"
+ ],
+ "transform": {}
+ },
+ "prettier": {
+ "bracketSameLine": true,
+ "printWidth": 80,
+ "semi": false,
+ "singleQuote": true,
+ "tabWidth": 2
}
}
diff --git a/src/README.hbs b/src/README.hbs
index 407ed92..b4c7889 100644
--- a/src/README.hbs
+++ b/src/README.hbs
@@ -1,6 +1,6 @@
-[![Version](http://img.shields.io/npm/v/@pangenerator/utils.svg)](https://www.npmjs.org/package/@pangenerator/utils) [![Tests](https://img.shields.io/github/workflow/status/panGenerator/utils/Tests)](https://github.com/panGenerator/utils)
-![Dependencies](https://img.shields.io/david/panGenerator/utils) ![Dev Dependencies](https://img.shields.io/david/dev/panGenerator/utils)
+[![Version](http://img.shields.io/npm/v/@pangenerator/utils.svg)](https://www.npmjs.org/package/@pangenerator/utils)
+[![Tests](https://img.shields.io/github/actions/workflow/status/panGenerator/utils/tests.yml)](https://github.com/panGenerator/utils)
{{>main}}
-Copyright © {{year}} panGenerator
+[panGenerator](https://pangenerator.com) {{year}}
diff --git a/src/TweakpaneSettings.js b/src/TweakpaneSettings.js
deleted file mode 100644
index 8c71007..0000000
--- a/src/TweakpaneSettings.js
+++ /dev/null
@@ -1,202 +0,0 @@
-import { snakeCase, shallowCopyExcluding } from "./utils.js";
-
-export default class TweakpaneSettings {
- constructor(ctrl, controllables, settingsName = null) {
- this.settingsName = settingsName ?? ctrl.title + "-settings";
- this.ctrl = ctrl;
- this.presets = controllables[0].settings.presets;
- if (this.presets) {
- Object.keys(this.presets).forEach((p) => {
- this.presets[p] = JSON.parse(this.presets[p]);
- });
- } else {
- this.presets = {};
- }
- controllables.forEach((g, i) => {
- const folder = g.settings.name
- ? ctrl.addFolder({ title: g.settings.name })
- : ctrl;
-
- if ("controls" in g.settings) {
- Object.keys(g.settings.controls).forEach((s) => {
- const options = shallowCopyExcluding(g.settings.controls[s], "val");
- options.presetKey = g.settings.name
- ? snakeCase(g.settings.name) + "_$" + s
- : null;
- options.label = options.label ? options.label : s;
-
- // shortcut props
- Object.defineProperty(g, "$" + s, {
- get: () => {
- return g.settings.controls[s].val;
- },
- set: (v) => {
- g.settings.controls[s].val = v;
- },
- });
-
- const input = folder.addInput(g, "$" + s, options); //g.settings.controls[s].val
-
- if (options.callback) {
- input.on("change", (ev) => {
- options.callback(ev);
- });
- }
- });
- }
-
- if ("buttons" in g.settings) {
- Object.keys(g.settings.buttons).forEach((b) => {
- folder
- .addButton({ title: g.settings.buttons[b].label })
- .on("click", () => {
- g.settings.buttons[b].callback();
- });
- });
- }
-
- if ("buttons_grid" in g.settings) {
- g.settings.buttons_grid.grids.forEach((bg) => {
- folder
- .addBlade({
- view: "buttongrid",
- size: bg.size,
- label: bg.label,
- cells: bg.cells,
- })
- .on("click", (ev) => {
- bg.callbacks[ev.index[1]][ev.index[0]]();
- });
- });
- }
-
- if ("monitors" in g.settings) {
- Object.keys(g.settings.monitors).forEach((m) => {
- const options = g.settings.monitors_options[m];
- folder.addMonitor(g.settings.monitors, m, options);
-
- // shortcut props
- Object.defineProperty(g, "$" + m, {
- get: () => {
- return g.settings.monitors[m];
- },
- set: (v) => {
- g.settings.monitors[m] = v;
- },
- });
- });
- }
- });
-
- const presets = ctrl.addFolder({ title: "Presets", expanded: false });
-
- if (Object.keys(this.presets).length > 0) {
- presets
- .addBlade({
- view: "list",
- label: "preset",
- options: Object.keys(this.presets).map((p) => {
- return { text: p, value: p };
- }),
- value: Object.keys(this.presets)[0],
- })
- .on("change", (ev) => {
- this.loadSettings(this.presets[ev.value]);
- });
-
- presets.addSeparator();
- }
-
- presets.addButton({ title: "Store settings" }).on("click", () => {
- console.log("save settings");
- const preset = ctrl.exportPreset();
- localStorage.setItem(settingsName, JSON.stringify(preset));
- console.log(preset);
- console.log("json:\n", JSON.stringify(preset));
- });
-
- presets.addButton({ title: "Restore settings" }).on("click", () => {
- this.loadSettings();
- });
-
- presets.addButton({ title: "Download settings" }).on("click", () => {
- console.log("download settings");
-
- const fileName =
- settingsName +
- "_" +
- new Date().toLocaleString().replace(/[^0-9]+/g, "-") +
- ".json";
- const url =
- "data:text/json;charset=utf-8," +
- encodeURIComponent(JSON.stringify(ctrl.exportPreset(), null, 2));
- console.log(JSON.stringify(ctrl.exportPreset()));
- const link = document.createElement("a");
- link.download = fileName;
- link.href = url;
- link.click();
- link.remove();
- });
-
- presets.addButton({ title: "Upload settings" }).on("click", () => {
- console.log("upload settings");
- const input = document.createElement("input");
- input.setAttribute("type", "file");
- input.setAttribute("accept", "application/json");
- input.style.opacity = "0";
- input.style.position = "fixed";
- document.body.appendChild(input);
- input.addEventListener(
- "input",
- (ev) => {
- if (input.files && input.files[0]) {
- const file = input.files[0];
- let reader = new FileReader();
- reader.readAsText(file);
-
- reader.onload = () => {
- console.log("settings loaded...");
- console.log(reader.result);
- ctrl.importPreset(JSON.parse(reader.result));
- };
-
- reader.onerror = () => {
- console.log("error loading file!");
- console.log(reader.error);
- };
- }
-
- document.body.removeChild(input);
- },
- { once: true }
- );
- input.click();
- });
-
- presets.addButton({ title: "Default settings" }).on("click", () => {
- if (this.presets.default) {
- this.loadSettings(this.presets.default);
- }
- });
- }
-
- loadSettings(settings = null) {
- console.log("restore settings");
- console.log("presets", this.presets);
- if (settings) {
- this.ctrl.importPreset(settings);
- console.log("loaded settings:", settings);
- } else {
- if (this.presets.default) {
- this.ctrl.importPreset(this.presets.default);
- console.log("loaded default settings:", this.presets.default);
- } else {
- const localPreset = localStorage.getItem(this.settingsName);
- if (localPreset) {
- this.ctrl.importPreset(JSON.parse(localPreset));
- console.log("loaded settings from local storage:", localPreset);
- }
- }
- }
- }
-}
diff --git a/src/banner.mjs b/src/banner.mjs
index dee54b0..68c8416 100644
--- a/src/banner.mjs
+++ b/src/banner.mjs
@@ -1,11 +1,11 @@
//const pkg = require('../package.json')
-import pkg from '../package.json' assert { type: 'json' };
-const year = new Date().getFullYear()
+import pkg from "../package.json" with { "type": "json" }
+const year = new Date().getFullYear();
export default (pluginFilename) => {
return `/*!
- * @license ${pkg.name} v${pkg.version}, Copyright © ${year} ${pkg.author}
+ * @license ${pkg.name} v${pkg.version}, ${pkg.author} ${year}
* Released under ${pkg.license} license
* ${pkg.homepage}
- */`
-}
+ */`;
+};
diff --git a/src/main.js b/src/main.js
deleted file mode 100644
index 8ce1e01..0000000
--- a/src/main.js
+++ /dev/null
@@ -1,4 +0,0 @@
-import * as utils from './utils.js'
-import TweakpaneSettings from './TweakpaneSettings.js'
-
-export default { TweakpaneSettings, ...utils }
diff --git a/src/modules/arrays.mjs b/src/modules/arrays.mjs
new file mode 100644
index 0000000..5f03f2a
--- /dev/null
+++ b/src/modules/arrays.mjs
@@ -0,0 +1,63 @@
+/**
+ * Array functions
+ */
+
+/**
+ * Copy array
+ * @param {Array} source - source array
+ * @returns {Array} copy of the array
+ */
+export const copyArray = (source) => {
+ const array = Array(source.length)
+ for (let i = 0; i < source.length; i++) {
+ array[i] = source[i]
+ }
+ return array
+}
+
+/**
+ * Shuffle array
+ * @param {Array} source - source array
+ * @returns {Array} shuffled array copy
+ */
+export const shuffleArray = (source) => {
+ const array = copyArray(source)
+ for (let i = array.length - 1; i > 0; i--) {
+ const j = Math.floor(Math.random() * (i + 1))
+ ;[array[i], array[j]] = [array[j], array[i]]
+ }
+ return array
+}
+
+/**
+ * Filter array unique
+ * @param {Array} source - source array
+ * @returns {Array} array with unique elements only
+ */
+export const filterUnique = (source) => {
+ return [...new Set(source)]
+}
+
+/**
+ * Fuzzy search element in list
+ * @param {Array} list - Array of terms
+ * @param {String} searchValue - search value to find
+ * @returns {Array} elements matching search value
+ */
+export const fuzzySearch = (list, searchValue) => {
+ const buf = '.*' + searchValue.replace(/(.)/g, '$1.*').toLowerCase()
+ var reg = new RegExp(buf)
+ return list.filter((e) => {
+ return reg.test(e.toLowerCase())
+ })
+}
+
+/**
+ * Check if array contains
+ * @param {any} elem - element to find in array
+ * @param {Array} arr - array to look in
+ * @returns {boolean} - true when element is in array
+ */
+export const contains = (elem, arr) => {
+ return arr.indexOf(elem) !== -1
+}
diff --git a/src/modules/browser.js b/src/modules/browser.js
new file mode 100644
index 0000000..1b66a52
--- /dev/null
+++ b/src/modules/browser.js
@@ -0,0 +1,198 @@
+/**
+ * Browser functions
+ */
+
+/**
+ * Load JSON
+ * @param {String} address - address of JSON to load
+ * @param {Function} callback - function to call on result
+ */
+export const loadJSON = (address, callback) => {
+ const xObj = new XMLHttpRequest()
+ xObj.overrideMimeType('application/json')
+ xObj.open('GET', address, true)
+ xObj.onreadystatechange = () => {
+ if (xObj.readyState === 4 && xObj.status === 200) {
+ callback(JSON.parse(xObj.responseText))
+ }
+ }
+ xObj.send(null)
+}
+
+/**
+ * Download file from base64 data uri
+ * @param {Object} options - options for the downloaded file
+ * @param {String} options.data - contents of the file
+ * @param {String} options.filename - name of the file
+ */
+export const downloadDataUri = (options) => {
+ var element = document.createElement('a')
+ element.setAttribute('href', options.data)
+ element.setAttribute('download', options.filename)
+ element.style.display = 'none'
+ document.body.appendChild(element)
+ element.click()
+ document.body.removeChild(element)
+}
+
+/**
+ * Get element page offset
+ * @param {Object} elem - HTML element
+ * @returns {Object} top and left page offset
+ */
+export const pageOffset = (elem) => {
+ const rect = elem.getBoundingClientRect()
+ const win = elem.ownerDocument.defaultView
+ return {
+ top: rect.top + win.pageYOffset,
+ left: rect.left + win.pageXOffset,
+ }
+}
+
+/**
+ * Get CSS Styles from element
+ * @param {HTMLElement} parentElement - Element to get styles from
+ * @returns {string} - extracted CSS
+ */
+export const getCSS = (parentElement) => {
+ const selectorTextArr = []
+
+ // Add Parent element Id and Classes to the list
+ selectorTextArr.push('#' + parentElement.id)
+ for (let c = 0; c < parentElement.classList.length; c++) {
+ if (!contains('.' + parentElement.classList[c], selectorTextArr)) {
+ selectorTextArr.push('.' + parentElement.classList[c])
+ }
+ }
+
+ // Add Children element Ids and Classes to the list
+ const nodes = parentElement.getElementsByTagName('*')
+ for (let i = 0; i < nodes.length; i++) {
+ const id = nodes[i].id
+ if (!contains('#' + id, selectorTextArr)) {
+ selectorTextArr.push('#' + id)
+ }
+
+ const classes = nodes[i].classList
+ for (let c = 0; c < classes.length; c++) {
+ if (!contains('.' + classes[c], selectorTextArr)) {
+ selectorTextArr.push('.' + classes[c])
+ }
+ }
+ }
+ // Extract CSS Rules
+ let extractedCSSText = ''
+ for (let i = 0; i < document.styleSheets.length; i++) {
+ var s = document.styleSheets[i]
+ try {
+ if (!s.cssRules) continue
+ } catch (e) {
+ if (e.name !== 'SecurityError') throw e // for Firefox
+ continue
+ }
+
+ var cssRules = s.cssRules
+ for (var r = 0; r < cssRules.length; r++) {
+ if (contains(cssRules[r].selectorText, selectorTextArr)) {
+ extractedCSSText += cssRules[r].cssText
+ }
+ }
+ }
+ return extractedCSSText
+}
+
+/**
+ * Append CSS to element
+ * @param {string} cssText - CSS text to append
+ * @param {HTMLElement} element - element to append CSS to
+ */
+export const appendCSS = (cssText, element) => {
+ var styleElement = document.createElement('style')
+ styleElement.setAttribute('type', 'text/css')
+ styleElement.innerHTML = cssText
+ var refNode = element.hasChildNodes() ? element.children[0] : null
+ element.insertBefore(styleElement, refNode)
+}
+
+/**
+ * Get SVG string from node
+ * @param {HTMLElement} svgNode - svg node to get text from
+ * @returns {string} - svg as string
+ */
+export const getSVGString = (svgNode) => {
+ svgNode.setAttribute('xlink', 'http://www.w3.org/1999/xlink')
+ var cssStyleText = getCSS(svgNode)
+ appendCSS(cssStyleText, svgNode)
+
+ var serializer = new XMLSerializer()
+ var svgString = serializer.serializeToString(svgNode)
+ svgString = svgString.replace(/(\w+)?:?xlink=/g, 'xmlns:xlink=') // Fix root xlink without namespace
+ svgString = svgString.replace(/NS\d+:href/g, 'xlink:href') // Safari NS namespace fix
+
+ return svgString
+}
+
+/**
+ * Convert SVG string to image and call the callback
+ * @param {string} svgString - SVG string to convert
+ * @param {Number} width - width of output image
+ * @param {Number} height - height of output image
+ * @param {string} format - format of output image
+ * @param {boolean} transparent - transparency flag
+ * @param {Function} callback - function to call when ready
+ */
+export const svgStringToImage = (
+ svgString,
+ width,
+ height,
+ format,
+ transparent,
+ callback
+) => {
+ format = format || 'png'
+
+ var imgsrc =
+ 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgString))) // Convert SVG string to data URL
+
+ var canvas = document.createElement('canvas')
+ var context = canvas.getContext('2d')
+
+ canvas.width = width
+ canvas.height = height
+
+ var image = new Image()
+ image.onload = () => {
+ context.clearRect(0, 0, width, height)
+ if (!transparent) {
+ context.beginPath()
+ context.fillStyle = '#fff'
+ context.fillRect(0, 0, canvas.width, canvas.height)
+ }
+ context.drawImage(image, 0, 0, width, height)
+ if (callback) callback(canvas.toDataURL())
+ }
+ image.src = imgsrc
+}
+
+/**
+ * Convert SVG to data uri
+ * @param {HTMLElement} svgNode - SVG element to get uri from
+ * @returns {string} - uri data scheme string
+ */
+export const svgToUri = (svgNode) => {
+ const serializer = new XMLSerializer()
+ let source = serializer.serializeToString(svgNode)
+ if (!source.match(/^