TwoDraw.js

import * as util from './utils.js'
import TwoView from '../src/TwoView.js'
import ColorMap from '../src/ColorMap.js'

/**
 * Basic 2D view.
 *
 */
class TwoDraw extends TwoView {
    static defaultOptions(model) {
        return {
            patchesColor: 'random',
            initPatches: null,

            turtles: model.turtles,
            turtlesColor: 'random',
            turtlesStrokeColor: 'random',
            turtlesShape: 'dart',
            turtlesSize: 1,
            turtlesRotate: true,

            links: model.links,
            linksColor: 'random',
            linksWidth: 1,

            textProperty: null,
            textSize: 0.5,
            textColor: 'black',

            patchesMap: 'DarkGray',
            turtlesMap: 'Basic16',
        }
    }
    static drawKeys() {
        const defaults = this.defaultOptions({})
        return Object.keys(defaults)
    }
    static separateDrawOptions(viewOptions, drawOptions) {
        if (viewOptions.drawOptions) {
            // drawOptions = viewOptions.drawOptions
            Object.assign(drawOptions, viewOptions.drawOptions)
            delete viewOptions.drawOptions
        }

        const keys = TwoDraw.drawKeys()
        keys.forEach(k => {
            if (viewOptions[k]) {
                drawOptions[k] = viewOptions[k]
                delete viewOptions[k]
            }
        })

        return drawOptions
        // return [viewOptions, drawOptions]
    }

    static fullScreenOptions(patchSize, background = 'black', margin = 10) {
        document.body.style.backgroundColor = background
        document.body.style.margin = `${margin}px`

        const width = window.innerWidth - 2 * margin // - 2
        const height = window.innerHeight - 2 * margin // - 2

        const maxX = Math.floor(width / (2 * patchSize))
        const maxY = Math.floor(height / (2 * patchSize))
        const maxZ = 1

        return {
            maxX: maxX,
            maxY: maxY,
            maxZ: maxZ,
            minX: -maxX,
            minY: -maxY,
            minZ: -maxZ,
        }
    }
    // ======================

    constructor(model, viewOptions = {}, drawOptions = {}) {
        // if (viewOptions.drawOptions) {
        //     drawOptions = viewOptions.drawOptions
        //     delete viewOptions.drawOptions
        // }

        // ;[viewOptions, drawOptions] =
        drawOptions = TwoDraw.separateDrawOptions(viewOptions, drawOptions)

        drawOptions = Object.assign(TwoDraw.defaultOptions(model), drawOptions)

        super(model, viewOptions) // TwoView
        this.model = model

        this.checkOptions(drawOptions)
        this.drawOptions = drawOptions
    }

    // The parameters are easily mistaken: check they are all in the defaults.
    checkOptions(drawOptions) {
        const keys = Object.keys(drawOptions)
        const defaults = TwoDraw.defaultOptions(this.model)

        keys.forEach(k => {
            if (defaults[k] === undefined) {
                console.log(
                    'Legal TwoDraw parameters',
                    Object.keys(TwoDraw.defaultOptions(this.model))
                )
                throw Error('Unknown TwoDraw parameter: ' + k)
            }
        })

        if (typeof drawOptions.patchesMap === 'string') {
            drawOptions.patchesMap = ColorMap[drawOptions.patchesMap]
            if (!drawOptions.patchesMap)
                Error('Unknown patchMap: ' + drawOptions.patchesMap)
        }
        if (typeof drawOptions.turtlesMap === 'string') {
            drawOptions.turtlesMap = ColorMap[drawOptions.turtlesMap]
            if (!drawOptions.turtlesMap)
                Error('Unknown turtlesMap: ' + drawOptions.turtlesMap)
        }
    }

    resetOptions(drawOptions = this.drawOptions) {
        // if (drawOptions !== this.drawOptions)
        //     drawOptions = Object.assign(
        //         {},
        //         TwoDraw.defaultOptions(),
        //         drawOptions
        //     )
        // drawOptions = Object.assign({}, TwoDraw.defaultOptions(), drawOptions)
        this.checkOptions(drawOptions)
        this.drawOptions = drawOptions
        this.ticks = 0
        this.draw()
        // this.view.ticks = 0
        // return drawOptions
    }

    // reset(drawOptions = this.drawOptions) {
    //     // this.resetOptions(drawOptions)
    //     // this.patchesView = new PatchesView(this.world.numX, this.world.numY)
    //     // this.turtlesView = new TurtlesView(this.ctx, this.world, options)
    //     // super.reset()
    //     super.initView()
    //     this.ticks = 0
    //     // this.clear()
    //     // super.reset(this.viewOptions.patchSize)
    //     // this.draw()
    // }
    // reset(redraw = true) {
    //     this.ticks = 0
    //     if (redraw) this.draw()
    //     super.reset()
    // }
    reset(patchesSize) {
        this.ticks = 0
        super.reset(patchesSize)
        this.draw()
    }

    setValue(key, val) {
        if (key === 'patchesSize') {
            this.reset(val)
            return
        }

        // const keys = TwoDraw.drawKeys()
        // keys.unshift('patchesSize')
        const keys = ['patchesSize'].concat(TwoDraw.drawKeys())
        if (!keys.includes(key)) {
            throw new Error(`setValue: ${key} not a valid TwoDraw key. Valid keys:
            ${keys.join()}`)
        }
        this.drawOptions[key] = val
        this.draw()
    }

    draw() {
        // params = Object.assign({}, TwoDraw.defaultOptions(), params)
        const model = this.model
        const view = this
        let {
            // data,
            patchesColor,
            initPatches,

            turtles,
            turtlesColor,
            turtlesStrokeColor,
            turtlesShape,
            turtlesSize,
            turtlesRotate,

            links,
            linksColor,
            linksWidth,

            textProperty,
            textSize,
            textColor,

            patchesMap,
            turtlesMap,
        } = this.drawOptions
        // const { model, view } = this

        if (view.ticks === 0) {
            if (textProperty) view.setTextProperties(textSize)

            if (initPatches) {
                // colors is an array of typedColors or pixels:
                const colors = initPatches(model, view)
                view.createPatchPixels(i => colors[i])
                // console.log(colors)
            } else if (patchesColor === 'random') {
                // NOTE: random colors only done once for patches.
                view.createPatchPixels(i => patchesMap.randomColor())
            }
        }

        // if (patchesColor === 'random' || initPatches) {
        //     view.clear() // patch transparent pixels do not clear the canvas!
        //     view.drawPatches() // redraw cached patches colors onto our view canvas
        // } else if (typeof patchesColor === 'function') {
        //     view.drawPatches(model.patches, p => patchesColor(p))
        // } else if (util.isImageable(patchesColor)) {
        //     view.drawPatchesImage(patchesColor)
        // } else {
        //     view.clear(patchesColor)
        // }

        if (util.isImageable(patchesColor)) {
            view.drawPatchesImage(patchesColor)
        } else {
            if (patchesColor === 'random' || initPatches) {
                view.clear() // patch transparent pixels do not clear the canvas!
                view.drawPatches() // redraw cached patches colors onto our view canvas
            } else if (util.isFunction(patchesColor)) {
                view.clear()
                view.drawPatches(model.patches, p => patchesColor(p))
            } else {
                view.clear(patchesColor)
            }
        }

        const checkColor = (agent, color) =>
            color === 'random' ? turtlesMap.atIndex(agent.id).css : color

        view.drawLinks(links, l => ({
            color:
                linksColor === 'random'
                    ? turtlesMap.atIndex(l.id)
                    : typeof linksColor === 'function'
                    ? checkColor(l, linksColor(l))
                    : linksColor,
            // width: linksWidth,
            width:
                typeof linksWidth === 'function' ? linksWidth(l) : linksWidth,
        }))

        view.drawTurtles(turtles, t => ({
            shape:
                typeof turtlesShape === 'function'
                    ? turtlesShape(t)
                    : turtlesShape,
            color:
                turtlesColor === 'random'
                    ? turtlesMap.atIndex(t.id).css
                    : typeof turtlesColor === 'function'
                    ? checkColor(t, turtlesColor(t))
                    : turtlesColor,
            strokeColor:
                turtlesStrokeColor === 'random'
                    ? turtlesMap.atIndex(t.id + 4).css
                    : typeof turtlesColor === 'function'
                    ? checkColor(t, turtlesColor(t))
                    : turtlesColor,
            size:
                typeof turtlesSize === 'function'
                    ? turtlesSize(t)
                    : turtlesSize,
            noRotate:
                typeof turtlesRotate === 'function'
                    ? !turtlesRotate(t)
                    : !turtlesRotate,
        }))

        if (textProperty) {
            turtles.ask(t => {
                if (t[textProperty] != null)
                    view.drawText(t[textProperty], t.x, t.y, textColor)
            })
        }

        view.tick()
    }
}

export default TwoDraw