Newer
Older
fractals / src / SvgRenderer.ts
import { Polygon } from "./Polygon";
import { FractalSequenceElement, ISpiralParameters } from "./SpiralFractal";
import { zip } from "./utils";

function createSvgPolygon(
    { fill }:{fill:string | null},
): SVGPolygonElement
{
    // Note: it is not possible to create any svg element without the namespace.
    const polygonSvg = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
    polygonSvg.style.fill = fill;
    return polygonSvg;
}

function polygonStyle(strength:number)
{
    return {
        fill: `rgb(0,0,${Math.floor(strength)})`,
    };
}

export function createSvgRootElement(sideLength:number): SVGSVGElement
{
        // Note: it is not possible to create any svg element without the namespace.
        const el = document.createElementNS("http://www.w3.org/2000/svg", "svg") as SVGSVGElement;
        el.width.baseVal.newValueSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_PX, sideLength);
        el.height.baseVal.newValueSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_PX, sideLength);
        el.style.fill = "none";
        el.style.stroke = "purple";
        el.style.strokeWidth = "1";
        return el;
}

export function createBasePolygons(length:number): SVGPolygonElement[]
{
    const initialColorStrength = 20;
    let colorStrength = initialColorStrength;
    const polygons:SVGPolygonElement[] = [];
    for(let i=0;i<length;i++)
    {
        polygons.push(createSvgPolygon(polygonStyle(colorStrength)));
        colorStrength += (255-initialColorStrength)/length;
    }
    return polygons;
}

export class SvgSpiralPolygon
{
    constructor(
        public svgRootElement:SVGSVGElement,
        public polygon:SVGPolygonElement,
        public fn:FractalSequenceElement<ISpiralParameters, Polygon>,
    ) { }

    public update(params:ISpiralParameters): void
    {
        const newState = this.fn(params);
        for(let i=0;i<newState.length && i<this.polygon.points.length;i++)
        {
            const point = this.polygon.points.getItem(i);
            point.x = newState[i].x;
            point.y = newState[i].y;
        }
        if(newState.length > this.polygon.points.length)
        {
            // add additional points
            for(let i=this.polygon.points.length; i<newState.length;i++)
            {
                const point = this.svgRootElement.createSVGPoint();
                point.x = newState[i].x;
                point.y = newState[i].y;
                this.polygon.points.appendItem(point);
            }
        }
        else if(newState.length < this.polygon.points.length)
        {
            // remove points
            const oldPointCount = this.polygon.points.length;
            for(let i=newState.length;i<oldPointCount;i++)
            {
                this.polygon.points.removeItem(newState.length);
            }
        }
    }
}

export class SvgSpiralBody
{
    private elements:SvgSpiralPolygon[];
    constructor(
        public svgRootElement:SVGSVGElement,
        public polygons:SVGPolygonElement[],
        public sequence:Array<FractalSequenceElement<ISpiralParameters, Polygon>>,
    ) {
        if(this.polygons.length !== sequence.length)
        {
            throw new Error("SVGPolygons and sequence functions have a different count.");
        }

        this.elements =
            zip(polygons, sequence, 0)
            .map(([p,s]) => new SvgSpiralPolygon(svgRootElement, p, s));
    }

    public update(params:ISpiralParameters): void
    {
        this.elements.forEach(e => e.update(params));
    }
}