|
import { DEFAULT_PRECISION } from "./_constants"
|
|
import { getData, strokeToFill } from "./_data"
|
|
import { convertPathToNode, segmentToD, styleAttrs, svgElem } from "./_utils"
|
|
|
|
export const GradientPath = class {
|
|
constructor({ path, segments, samples, precision = DEFAULT_PRECISION }) {
|
|
// If the path being passed isn't a DOM node already, make it one
|
|
this.path = convertPathToNode(path)
|
|
|
|
this.segments = segments
|
|
this.samples = samples
|
|
this.precision = precision
|
|
|
|
// Check if nodeName is path and that the path is closed, otherwise it's closed by default
|
|
this.pathClosed =
|
|
this.path.nodeName == "path"
|
|
? this.path.getAttribute("d").match(/z/gi)
|
|
: true
|
|
|
|
// Store the render cycles that the user creates
|
|
this.renders = []
|
|
|
|
// Append a group to the SVG to capture everything we render and ensure our paths and circles are properly encapsulated
|
|
this.svg = path.closest("svg")
|
|
this.group = svgElem("g", {
|
|
class: "gradient-path",
|
|
})
|
|
|
|
// Get the data
|
|
this.data = getData({ path, segments, samples, precision })
|
|
|
|
// Append the main group to the SVG
|
|
this.svg.appendChild(this.group)
|
|
|
|
// Remove the main path once we have the data values
|
|
this.path.parentNode.removeChild(this.path)
|
|
}
|
|
|
|
remove() {
|
|
this.group.parentNode.removeChild(this.group)
|
|
}
|
|
|
|
render({
|
|
type,
|
|
stroke = ["white", "black", "white"],
|
|
strokeWidth = 1,
|
|
fill = ["white", "black", "white"],
|
|
width,
|
|
animation = {},
|
|
}) {
|
|
// Store information from this render cycle
|
|
const renderCycle = {}
|
|
|
|
// Create a group for each element
|
|
const elemGroup = svgElem("g", { class: `element-${type}` })
|
|
|
|
this.group.appendChild(elemGroup)
|
|
renderCycle.group = elemGroup
|
|
|
|
if (type === "path") {
|
|
// If we specify a width and fill, then we need to outline the path and then average the join points of the segments
|
|
// If we do not specify a width and fill, then we will be stroking and can leave the data "as is"
|
|
renderCycle.data =
|
|
width && fill
|
|
? strokeToFill(this.data, width, this.precision, this.pathClosed)
|
|
: this.data
|
|
|
|
for (let j = 0; j < renderCycle.data.length; j++) {
|
|
const { samples, progress } = renderCycle.data[j]
|
|
|
|
// Create a path for each segment and append it to its elemGroup
|
|
elemGroup.appendChild(
|
|
svgElem("path", {
|
|
class: "path-segment",
|
|
d: segmentToD(samples),
|
|
...styleAttrs(fill, stroke, strokeWidth, progress, animation),
|
|
})
|
|
)
|
|
}
|
|
} else if (type === "circle") {
|
|
renderCycle.data = this.data.flatMap(({ samples }) => samples)
|
|
|
|
for (let j = 0; j < renderCycle.data.length; j++) {
|
|
const { x, y, progress } = renderCycle.data[j]
|
|
|
|
// Create a circle for each sample and append it to its elemGroup
|
|
elemGroup.appendChild(
|
|
svgElem("circle", {
|
|
class: "circle-sample",
|
|
cx: x,
|
|
cy: y,
|
|
r: width / 2,
|
|
...styleAttrs(fill, stroke, strokeWidth, progress, animation),
|
|
})
|
|
)
|
|
}
|
|
}
|
|
|
|
// Save the information in the current renderCycle and pop it onto the renders array
|
|
this.renders.push(renderCycle)
|
|
|
|
// Return this for method chaining
|
|
return this
|
|
}
|
|
}
|