controls/Gantt/helpers/datapointHelpers.js

import * as d3 from "d3";
import { Shape } from "../../../core";
import { getDefaultSVGProps } from "../../../core/Shape";
import { getXAxisXPosition } from "../../../helpers/axis";
import constants, { SHAPES } from "../../../helpers/constants";
import { getSVGObjectForEvent, getSVGObject } from "../../../helpers/shapeSVG";
import styles from "../../../helpers/styles";
import utils from "../../../helpers/utils";
import { getXAxisYPosition } from "./creationHelpers";
import { transformPoint } from "./translateHelpers";

/**
 * Renders the action items for a track. The data points provided are of ISO8601 datetime format
 * They are dependent on a unique key which needs to match the legend id's provided when initializing
 * the gantt chart.
 *
 * @private
 * @param {object} scale - d3 scale for Graph
 * @param {object} config - Graph config object derived from input JSON
 * @param {object} ganttContentContainerPath - Container for gantt chart's contents (Tracks)
 * @param {object} dataTarget - Data point values and their properties for each action item
 * @param {Function} drawDataPointsHandler - call back function to draw points with options.
 * @param {boolean} event - Data point is an event or not.
 * @returns {undefined} - returns nothing
 */
export const drawDataPoints = (
    scale,
    config,
    ganttContentContainerPath,
    dataTarget,
    drawDataPointsHandler,
    event
) => {
    const allPointsPath = ganttContentContainerPath
        .append("g")
        .classed(styles.currentPointsGroup, true)
        .attr(
            "transform",
            `translate(${getXAxisXPosition(config)}, ${getXAxisYPosition(
                config
            )})`
        )
        .attr("event", event);
    const pointPath = allPointsPath
        .selectAll(`.${styles.point}`)
        .data(dataTarget);
    drawDataPointsHandler(scale, config, pointPath.enter());
    pointPath
        .exit()
        .transition()
        .call(constants.d3Transition(config.settingsDictionary.transition))
        .remove();
};

/**
 * Renders the circle svg element which shows up when clicked on the data point.
 * It is hidden by default and toggled visible onClick.
 *
 * @private
 * @param {object} scale - d3 scale for Graph
 * @param {object} config - Graph config object derived from input JSON
 * @param {SVGElement} path - svg circle element
 * @param {object} dataPoint - data point properties such as shape, color and onClick callback function
 * @param {number} index - data point index
 * @returns {object} - d3 selection object
 */
export const renderSelectionPath = (scale, config, path, dataPoint, index) =>
    path.append(() =>
        new Shape(
            getSVGObject(SHAPES.CIRCLE, constants.DEFAULT_PLOT_SELECTION_SCALE)
        ).getShapeElement(
            getDefaultSVGProps({
                svgClassNames: styles.dataPointSelection,
                svgStyles: ``,
                transformFn: transformPoint(scale, config)(dataPoint),
                onClickFn() {
                    dataPointActionHandler(dataPoint, index, this);
                },
                a11yAttributes: {
                    "aria-hidden": true,
                    "aria-describedby": dataPoint.key,
                    "aria-disabled": !utils.isFunction(dataPoint.onClick)
                }
            })
        )
    );

/**
 * Renders the circle svg element for event which shows up when clicked on the data point.
 * It is hidden by default and toggled visible onClick.
 *
 * @param {object} scale - d3 scale for Graph
 * @param {object} config - Graph config object derived from input JSON
 * @param {SVGElement} path - svg circle element
 * @param {object} dataPoint - data point properties such as shape, color and onClick callback function
 * @param {number} index - data point index
 * @returns {object} - d3 selection object
 */
export const renderSelectionPathForEvents = (
    scale,
    config,
    path,
    dataPoint,
    index
) =>
    path.append(() =>
        new Shape(
            getSVGObjectForEvent(
                SHAPES.CIRCLE,
                dataPoint.shape.options,
                constants.DEFAULT_PLOT_SELECTION_SCALE_FOR_EVENTS
            )
        ).getShapeElement(
            getDefaultSVGProps({
                svgClassNames: styles.dataPointSelection,
                svgStyles: ``,
                transformFn: transformPoint(scale, config)(dataPoint),
                onClickFn() {
                    dataPointActionHandler(dataPoint, index, this);
                },
                a11yAttributes: {
                    "aria-hidden": true,
                    "aria-describedby": dataPoint.key,
                    "aria-disabled": !utils.isFunction(dataPoint.onClick)
                }
            })
        )
    );

/**
 * Handler for the data point on click. If the content property is present for the data point
 * then the callback is executed other wise it is NOP.
 * If the callback is present, the selected data point is toggled and the element is passed as an argument to the
 * consumer in the callback, to execute once the popup is closed.
 *  Callback arguments:
 *      Post close callback function
 *      value [x and y data point values]
 *      Selected data point target [d3 target]
 *  On close of popup, call -> the provided callback
 *
 * @private
 * @param {object} value - data point object
 * @param {number} index - data point index for the set
 * @param {object} target - DOM object of the clicked point
 * @returns {undefined} - returns nothing
 */
export const dataPointActionHandler = (value, index, target) => {
    if (utils.isEmpty(value.onClick)) {
        return;
    }
    toggleDataPointSelection(target).call((selectedTarget) =>
        value.onClick(
            () => selectedTarget.attr("aria-hidden", true),
            value.key,
            index,
            value,
            selectedTarget
        )
    );
};

/**
 * Toggles the selection of a data point, executes on click of a data point.
 *
 * @private
 * @param {object} target - DOM element of the data point clicked
 * @returns {Array} d3 html element of the selected point
 */
const toggleDataPointSelection = (target) => {
    const selectedPointNode = d3
        .select(target.parentNode)
        .select(`.${styles.dataPointSelection}`);
    selectedPointNode.attr(
        "aria-hidden",
        !(selectedPointNode.attr("aria-hidden") === "true")
    );
    return selectedPointNode;
};