Source: visual-layer/src/layers/text/text.js

import {
    getQualifiedClassName,
    selectElement,
    setStyles,
    createElements
} from 'muze-utils';
import { BaseLayer } from '../../base-layer';
import drawText from './renderer';
import { defaultConfig } from './default-config';
import { getLayerColor, positionPoints, getIndividualClassName } from '../../helpers';
import { TEXT_ANCHOR_MIDDLE, ENCODING } from '../../enums/constants';
import * as PROPS from '../../enums/props';

import './styles.scss';

/**
 * This layer is used to create labels for each data point. It has an encoding property ```text```
 * which determines from which field's data the value of the label will be taken. The text encoding
 * property is necessary for the layer to render the text.The mark type of this layer is ```text```.
 *
 * @public
 *
 * @class
 * @module TextLayer
 * @extends BaseLayer
 */
export default class TextLayer extends BaseLayer {
    /**
     * Returns the default configuration of the text layer
     * @return {Object} Default configuration of the text layer
     */
    static defaultConfig () {
        return defaultConfig;
    }

    static formalName () {
        return 'text';
    }

    elemType () {
        return 'text';
    }

    /**
     * Generates an array of objects containing x, y, width and height of the points from the data
     * @param  {Array.<Array>} data Data Array
     * @param  {Object} encoding  Config
     * @param  {Object} axes     Axes object
     * @return {Array.<Object>}  Array of points
     */
    translatePoints (data, encoding, axes) {
        let points = [];
        const colorAxis = axes.color;
        const textEncoding = encoding.text;
        const { field: textField, value, formatter: textFormatter } = textEncoding;
        const colorEncoding = encoding.color;
        const colorField = colorEncoding && colorEncoding.field;
        const fieldsConfig = this.data().getFieldsConfig();

        const backgroundEncoding = encoding.text.background;
        const backgroundPadding = backgroundEncoding.padding;
        const backgroundValue = backgroundEncoding.value;
        const colorFieldIndex = fieldsConfig[colorField] ? fieldsConfig[colorField].index : -1;
        const textFieldIndex = textField ? fieldsConfig[textField] && fieldsConfig[textField].index : -1;
        const xEnc = ENCODING.X;
        const yEnc = ENCODING.Y;
        for (let i = 0, len = data.length; i < len; i++) {
            const d = data[i];
            const row = d._data;
            const textValue = textField ? row[textFieldIndex] : value;

            const [xPx, yPx] = [xEnc, yEnc].map(type => (axes[type] ? axes[type].getScaleValue(d[type]) +
                    axes[type].getUnitWidth() / 2 : 0));

            const { color, rawColor } = getLayerColor({ datum: d, index: i },
                { colorEncoding, colorAxis, colorFieldIndex });

            const point = {
                enter: {},
                update: {
                    x: xPx,
                    y: yPx
                },
                text: textFormatter ? textFormatter(textValue) : textValue,
                color,
                background: {
                    value: backgroundValue instanceof Function ? backgroundValue(d, i, data, this) : null,
                    padding: backgroundPadding
                },
                meta: {
                    stateColor: {},
                    originalColor: rawColor,
                    colorTransform: {}
                },
                style: {},
                _data: row,
                _id: d._id,
                source: d._data,
                rowId: d._id
            };

            if (d.x !== null && d.y !== null) {
                points.push(point);
            }

            point.className = getIndividualClassName(d, i, data, this);
        }

        points = positionPoints(this, points);

        return points;
    }

    /**
     * Renders the plot in the given container
     * @param  {SVGElement} container SVGElement which will hold the plot
     * @return {textLayer} Instance of text layer
     */
    render (container) {
        let points;
        const config = this.config();
        const encoding = config.encoding;
        const normalizedData = this._store.get(PROPS.NORMALIZED_DATA);
        const className = config.className;
        const qualifiedClassName = getQualifiedClassName(config.defClassName, this.id(), config.classPrefix);
        const axes = this.axes();
        const containerSelection = selectElement(container);

        containerSelection.classed(`${qualifiedClassName.join(' ')} ${className}`, true);
        createElements({
            data: normalizedData,
            append: 'g',
            selector: 'g',
            container,
            each: (dataArr, group, i) => {
                const node = group.node();
                points = this.translatePoints(dataArr, encoding, axes, i);
                setStyles(node, {
                    'text-anchor': TEXT_ANCHOR_MIDDLE
                });
                drawText(node, points, {
                    className: qualifiedClassName[0]
                }, this._dependencies.smartLabel);
            }
        });
        return this;
    }
}