Source: view/viz/Voronoi1D.js

/** @module */

// d3-voronoi
// d3-select
// d3-range

import { Delaunay } from 'd3-delaunay'
// import { Delaunay } from 'd3-delaunay'
// import { Delaunay } from 'https://cdn.skypack.dev/d3-delaunay@6'
// https://github.com/d3/d3-delaunay

/**
 * Draw Voronoi cells to show votes.
 * Voronoi1D is called by VizOne.
 * @param {VoterGroup} voterGroup
 * @param {candidateList} candidateList
 * @param {Screen} screen
 * @constructor
 */
export default function Voronoi1D(voterGroup, candidateList, screen) {
    const self = this

    let voronoi
    let canList
    self.update = function () {
        canList = candidateList.getEntities()
        if (canList === undefined) return
        const points = canList.map((e) => [e.shape1.x, 0])
        const delaunay = Delaunay.from(points)
        voronoi = delaunay.voronoi([0, 0, screen.width, screen.height])
    }

    self.render = function () {
        if (canList === undefined) return

        const { ctx } = screen
        const { x, w, densityProfile } = voterGroup.shape1
        const h = 100

        ctx.save()

        // clip the voronoi diagram

        // http://jsfiddle.net/jimrhoskins/dDUC3/1/
        // https://dustinpfister.github.io/2019/10/08/canvas-clip/

        ctx.beginPath()
        doPath()
        // ctx.closePath()
        ctx.clip()

        const n = canList.length
        for (let i = 0; i < n; i++) {
            ctx.beginPath()
            voronoi.renderCell(i, ctx)
            ctx.fillStyle = canList[i].color
            ctx.fill()
            ctx.stroke()
        }

        ctx.beginPath()
        doPath()
        ctx.stroke()

        ctx.restore()

        function doPath() {
            if (densityProfile === 'gaussian') {
                gaussianPath()
            } else {
                rectanglePath()
            }
        }
        function gaussianPath() {
            const sigma = w / Math.sqrt(2 * Math.PI) // w = sigma * sqrt(2*pi)
            const amp = h
            const bottom = 150 + h * 0.5
            // start bottom left
            ctx.moveTo(0, bottom)
            const pa = []
            for (let i = 0; i <= screen.width; i += 1) {
                const xp = 0.5 * ((i - x) / sigma) ** 2
                const y = bottom - amp * Math.exp(-xp)
                pa.push(y)
                ctx.lineTo(i, y)
            }
            // end bottom right
            ctx.lineTo(screen.width, bottom)
            ctx.lineTo(0, bottom)
            // ctx.closePath()
        }
        function rectanglePath() {
            ctx.rect(x - w * 0.5, 150 - h * 0.5, w, h)
        }
    }
}