TargetProcess/tauCharts

View on GitHub
src/elements/decorators/anchors.ts

Summary

Maintainability
A
2 hrs
Test Coverage
import {CSS_PREFIX} from '../../const';
import {
    d3_animationInterceptor as createUpdateFunc,
    d3_setAttrs as attrs,
} from '../../utils/d3-decorators';
import * as utilsDom from '../../utils/utils-dom';
import {isNonSyntheticRecord} from '../../utils/utils-grammar';
import {
    d3Selection,
    GrammarElement,
    GrammarModel,
    UnitGuide,
} from '../../definitions';
import {getBrushLine} from '../../utils/path/svg/brush-line';

export function drawAnchors(node: GrammarElement, model: any, selection: d3Selection) {
    const shape = model.anchorShape;
    const config = node.config;
    const guide = config.guide;
    const screenModel = node.screenModel;

    const anchorClass = 'i-data-anchor';
    const attr = {
        ...anchorShapes[shape].getInitialAttrs(node, model),
        opacity: (guide.showAnchors === 'hover' ? 0 : 1),
        fill: (d) => screenModel.color(d),
        class: anchorClass
    };

    const dots = selection
        .selectAll(`.${anchorClass}`)
        .data((fiber) => fiber.filter(isNonSyntheticRecord), screenModel.id);
    dots.exit()
        .remove();
    dots.call(createUpdateFunc(guide.animationSpeed, null, attr));
    const allDots = dots.enter()
        .append(anchorShapes[model.anchorShape].element)
        .call(createUpdateFunc(guide.animationSpeed, {r: 0}, attr))
        .merge(dots) as d3Selection;

    return allDots;
}

export function highlightAnchors(node: GrammarElement, model: any, filter: (row) => boolean | null) {
    const shape = model.anchorShape;
    const cssClass = 'i-data-anchor';
    const screenModel = node.screenModel;
    const showOnHover = node.config.guide.showAnchors === 'hover';
    const container = node.config.options.container;
    const dots = container
        .selectAll(`.${cssClass}`)
        .call(attrs(anchorShapes[shape].getHighlightAttrs(node, model, filter)))
        .attr('opacity', (showOnHover ? ((d) => filter(d) ? 1 : 0) : () => 1))
        .attr('fill', (d) => screenModel.color(d))
        .attr('class', (d) => utilsDom.classes(cssClass, screenModel.class(d)))
        .classed(`${CSS_PREFIX}highlighted`, filter) as d3Selection;

    return dots;
}

interface ShapesAttrsDictionary {
    [shape: string]: {
        element: string;
        getInitialAttrs: (node: GrammarElement, model: any) => Object;
        getHighlightAttrs: (node: GrammarElement, model: any, filter: (row) => boolean | null) => Object;
    };
}

const anchorShapes: ShapesAttrsDictionary = {

    'circle': {

        element: 'circle',

        getInitialAttrs: function (node, model) {
            const config = node.config;
            const guide = config.guide;
            const screenModel = node.screenModel;
            return {
                r: (guide.showAnchors === 'hover' ? 0 :
                    ((d) => screenModel.size(d) / 2)
                ),
                cx: (d) => model.x(d),
                cy: (d) => model.y(d),
            };
        },

        getHighlightAttrs: function (node, model, filter) {
            const config = node.config;
            const guide = config.guide;
            const screenModel = node.screenModel;
            const showOnHover = node.config.guide.showAnchors === 'hover';
            const rmin = 4; // Min highlight radius
            const rx = 1.25; // Highlight multiplier
            return {
                r: (showOnHover ?
                    ((d) => filter(d) ? Math.max(rmin, (screenModel.size(d) / 2)) : 0) :
                    ((d) => {
                        // NOTE: Highlight point with larger radius.
                        var r = screenModel.size(d) / 2;
                        if (filter(d)) {
                            r = Math.max(rmin, Math.ceil(r * rx));
                        }
                        return r;
                    })
                )
            };
        }
    },

    'vertical-stick': {

        element: 'path',

        getInitialAttrs: function (node, model) {
            const config = node.config;
            const guide = config.guide;
            const screenModel = node.screenModel;
            return {
                'shape-rendering': 'crispEdges',
                d: (d) => {
                    const x = model.x(d);
                    const y = model.y(d);
                    const x0 = model.x0(d);
                    const y0 = model.y0(d);
                    const r = (guide.showAnchors === 'hover' ? 0 : (screenModel.size(d) / 2));
                    const path = getBrushLine([
                        {x, y, size: r},
                        {x: x0, y: y0, size: r}
                    ]);
                    return path;
                }
            };
        },

        getHighlightAttrs: function (node, model, filter) {
            const config = node.config;
            const guide = config.guide;
            const screenModel = node.screenModel;
            const showOnHover = node.config.guide.showAnchors === 'hover';
            const rmin = 4; // Min highlight radius
            const rx = 1.25; // Highlight multiplier
            return {
                d: (d) => {
                    const x = model.x(d);
                    const y = model.y(d);
                    const x0 = model.x0(d);
                    const y0 = model.y0(d);
                    const r = (showOnHover ?
                        (filter(d) ? Math.max(rmin, (screenModel.size(d) / 2)) : 0) :
                        filter(d) ?
                            // Note: Highlight stick with larger width.
                            Math.max(rmin, Math.ceil(screenModel.size(d) / 2 * rx)) :
                            (screenModel.size(d) / 2)
                    );
                    const path = getBrushLine([
                        {x, y, size: r},
                        {x: x0, y: y0, size: r}
                    ]);
                    return path;
                }
            };
        }
    }
};