public build()

in packages/sanddance-specs/src/layouts/scatter.ts [62:253]


    public build(): InnerScope {
        const { names, prefix, props } = this;
        const { globalScope, parentScope, scatterPointScaleDisplay, size, x, y, z, zGrounded } = props;
        const qsize = size && size.quantitative && size;

        addSignals(globalScope.scope,
            {
                name: SignalNames.PointScale,
                value: 5,
                bind: {
                    name: scatterPointScaleDisplay,
                    debounce: 50,
                    input: 'range',
                    min: 1,
                    max: 10,
                    step: 1
                }
            },
            {
                name: SignalNames.ZGrounded,
                value: false,
                bind: {
                    name: zGrounded,
                    input: 'checkbox'
                }
            }
        );

        if (qsize) {
            addTransforms(globalScope.data,
                {
                    type: 'extent',
                    field: safeFieldName(qsize.name),
                    signal: names.sizeExtent
                }
            );
            addScales(globalScope.scope,
                {
                    name: names.sizeScale,
                    type: 'linear',
                    domain: [0, { signal: `${names.sizeExtent}[1]` }],
                    range: [0, { signal: names.sizeRange }]
                }
            );
            addSignals(globalScope.scope,
                {
                    name: names.sizeRange,
                    update: `min(${parentScope.sizeSignals.layoutHeight}, ${parentScope.sizeSignals.layoutWidth}) / ${scatterSizedDiv}`
                }
            );
        }

        addData(globalScope.scope, {
            name: names.markData,
            source: globalScope.markDataName,
            transform: [x, y, z].map(c => {
                if (!c || !c.quantitative) return;
                const t: Transforms = {
                    type: 'filter',
                    expr: `isValid(datum[${JSON.stringify(c.name)}])`
                };
                return t;
            }).filter(Boolean)
        });
        globalScope.setMarkDataName(names.markData);

        const globalScales: GlobalScales = { showAxes: true, scales: {} };
        const zValue = z ? `scale(${JSON.stringify(names.zScale)}, datum[${JSON.stringify(z.name)}])` : null;
        const sizeValueSignal = qsize ?
            `scale(${JSON.stringify(names.sizeScale)}, datum[${JSON.stringify(qsize.name)}]) * ${SignalNames.PointScale}`
            : SignalNames.PointScale;

        const update: RectEncodeEntry = {
            height: [
                {
                    test: testForCollapseSelection(),
                    value: 0
                },
                {
                    signal: sizeValueSignal
                }
            ],
            width: {
                signal: sizeValueSignal
            },
            ...z && {
                z: [
                    {
                        test: testForCollapseSelection(),
                        value: 0
                    },
                    {
                        signal: `${SignalNames.ZGrounded} ? 0 : ${zValue}`
                    }
                ],
                depth: [
                    {
                        test: testForCollapseSelection(),
                        value: 0
                    },
                    {
                        signal: `${SignalNames.ZGrounded} ? ${zValue} : ${sizeValueSignal}`
                    }
                ]
            }
        };

        const columnSignals: {
            column: Column,
            xyz: 'x' | 'y' | 'z',
            scaleName: string,
            reverse: boolean,
            signal: string
        }[] = [
            {
                column: x,
                xyz: 'x',
                scaleName: names.xScale,
                reverse: false,
                signal: parentScope.sizeSignals.layoutWidth
            },
            {
                column: y,
                xyz: 'y',
                scaleName: names.yScale,
                reverse: true,
                signal: parentScope.sizeSignals.layoutHeight
            },
            {
                column: z,
                xyz: 'z',
                scaleName: names.zScale,
                reverse: false,
                signal: `(${globalScope.zSize}) * ${SignalNames.ZProportion}`
            }
        ];
        columnSignals.forEach(cs => {
            const { column, reverse, scaleName, signal, xyz } = cs;
            if (!column) return;
            let scale: Scale;
            if (column.quantitative) {
                scale = linearScale(
                    scaleName,
                    globalScope.data.name,
                    column.name,
                    [0, { signal }],
                    reverse,
                    false
                );
            } else {
                scale = pointScale(
                    scaleName,
                    globalScope.data.name,
                    [0, { signal }],
                    column.name,
                    reverse
                );
            }
            globalScales.scales[xyz] = [scale];
        });

        const mark: RectMark = {
            name: prefix,
            type: 'rect',
            from: { data: globalScope.markDataName },
            encode: { update }
        };
        addMarks(globalScope.markGroup, mark);

        return {
            offsets: {
                x: addOffsets(parentScope.offsets.x, `scale(${JSON.stringify(names.xScale)}, datum[${JSON.stringify(x.name)}])`),
                y: addOffsets(parentScope.offsets.y, `scale(${JSON.stringify(names.yScale)}, datum[${JSON.stringify(y.name)}]) - ${sizeValueSignal}`),
                h: sizeValueSignal,
                w: sizeValueSignal
            },
            sizeSignals: {
                layoutHeight: null,
                layoutWidth: null
            },
            globalScales,
            mark,
            encodingRuleMap: {
                y: [
                    {
                        test: testForCollapseSelection(),
                        signal: addOffsets(parentScope.offsets.y, parentScope.sizeSignals.layoutHeight)
                    }
                ]
            }
        };
    }