controls/Pie/PieContent.js

"use strict";
/**
 * @typedef {object} PieContent
 * @typedef {object} GraphContent
 * @typedef {object} PieConfig
 * @typedef {object} d3
 */
import { GraphContent } from "../../core";
import constants from "../../helpers/constants";
import errors from "../../helpers/errors";
import { removeLegendItem } from "../../helpers/legend";
import styles from "../../helpers/styles";
import utils from "../../helpers/utils";
import { isUniqueKey } from "../Graph/GraphConfig";
import {
    clear,
    createPieContentGroup,
    createSlice,
    prepareLegendItems
} from "./helpers/creationHelpers";
import { translateSlices } from "./helpers/translateHelpers";

/**
 * Processes the input JSON and adds the shapes, colors, labels etc. to each data points so that we
 * can use them when rendering the data point.
 *
 * @private
 * @param {object} graphConfig - config object of Pie instance Construct
 * @param {object} dataTarget - Data points object
 * @returns {object} dataTarget - Updated data target object
 */
const processDataPoints = (graphConfig, dataTarget) =>
    Object.assign({}, dataTarget, {
        onClick: dataTarget.onClick,
        color: dataTarget.color || constants.DEFAULT_PIE_COLOR,
        shape: constants.DEFAULT_PIE_LEGEND_SHAPE,
        label: dataTarget.label || {},
        key: dataTarget.key
    });
/**
 * Validates the newly added pie content into the graph before rendering
 *
 * @private
 * @param {object} input - Newly added graph tasks
 * @throws {module:errors.THROW_MSG_NO_DATA_LOADED}
 * @throws {module:errors.THROW_MSG_NO_DATA_POINTS}
 * @throws {module:errors.THROW_MSG_UNIQUE_KEY_NOT_PROVIDED}
 * @throws {module:errors.THROW_MSG_UNIQUE_LABEL_NOT_PROVIDED}
 * @returns {undefined} - returns nothing
 */
export const validateContent = (input) => {
    if (utils.isEmpty(input)) {
        throw new Error(errors.THROW_MSG_NO_DATA_LOADED);
    }
    if (utils.isEmpty(input.key)) {
        throw new Error(errors.THROW_MSG_UNIQUE_KEY_NOT_PROVIDED);
    }
    // Label is necessary since it shows the value along with the display
    // This is essential for a11y in a pie chart
    if (utils.isEmpty(input.label) || utils.isEmpty(input.label.display)) {
        throw new Error(errors.THROW_MSG_UNIQUE_LABEL_NOT_PROVIDED);
    }
    if (utils.isEmpty(input.value)) {
        throw new Error(errors.THROW_MSG_NO_DATA_POINTS);
    }
};
/**
 * Draws an arc based on the value provided within the larger Pie
 *
 * @private
 * @param {PieContent} control - PieContent instance
 * @param {object} graph - Graph instance
 * @param {d3.selection} pieChartContentSVG - d3 selection node of canvas svg
 * @param {d3.selection} legendPath - d3 selection node of legend `ul` path
 * @param {object} dataTarget - Pie data object
 * @returns {undefined} - returns nothing
 */
const draw = (control, graph, pieChartContentSVG, legendPath, dataTarget) => {
    const contentGroupPath = createPieContentGroup(
        graph.config,
        pieChartContentSVG,
        legendPath,
        control.config,
        dataTarget
    );
    /* Set the data to the group as well as the slice within, since the slice data will be changed
     * to accommodate d3 arc object data as well during translate phase to combine all the pie slices
     * together */
    contentGroupPath.data([dataTarget]);
    createSlice(graph.config, contentGroupPath);
};
/**
 * Data point sets can be loaded using this function.
 * Load function validates, clones and stores the input onto a config object.
 *
 * @private
 * @param {object} inputJSON - Input JSON provided by the consumer
 * @returns {object} config object containing consumer data
 */
const loadInput = (inputJSON) => {
    validateContent(inputJSON);
    return utils.deepClone(inputJSON);
};

/**
 * PieContent is part of a Pie graph
 *
 * @module PieContent
 * @class PieContent
 */
class PieContent extends GraphContent {
    /**
     * @class
     * @param {object} input - Input JSON
     */
    constructor(input) {
        super();
        this.config = loadInput(input);
        this.dataTarget = null;
    }

    /**
     * @inheritdoc
     */
    load(graph) {
        if (!isUniqueKey(graph.contentConfig, this.config.key)) {
            throw new Error(errors.THROW_MSG_UNIQUE_KEY_NOT_PROVIDED);
        }
        this.dataTarget = processDataPoints(graph.config, this.config);
        draw(
            this,
            graph,
            graph.svg.select(`.${styles.pieChartContent}`),
            graph.legendSVG,
            this.dataTarget
        );
        prepareLegendItems(
            this.dataTarget,
            graph.svg,
            graph.legendSVG,
            graph.config
        );
        return this;
    }

    /**
     * @inheritdoc
     */
    unload(graph) {
        clear(graph.svg, this.dataTarget);
        removeLegendItem(graph.legendSVG, this.dataTarget);
        this.dataTarget = {};
        this.config = {};
        return this;
    }

    /**
     * @inheritdoc
     */
    resize(graph) {
        translateSlices(
            graph.config,
            graph.svg.select(`.${styles.pieChartContent}`),
            graph.d3PieLayoutTransformer,
            graph.d3PieArcTransformer
        );
        return this;
    }

    /**
     * @inheritdoc
     */
    redraw() {
        /*
         * Legends are not actionable within Pie chart, no redraw
         * is necessary due to this
         * */
        return this;
    }
}

export default PieContent;