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 } }