Source: muze/src/canvas/legend-maker.js

import { TextCell, AxisCell } from '@chartshq/visual-cell';
import {
    VERTICAL, HORIZONTAL, LEFT, RIGHT, LEGEND_TYPE_MAP, PADDING, BORDER, MARGIN
} from '../constants';

/**
 *
 *
 * @param {*} legendConfig
 * @param {*} canvases
 *
 */
export const legendCreator = (canvas) => {
    let LegendCls;
    const dataset = [];
    const axes = canvas.getRetinalAxes();

    Object.entries(axes).forEach((axisInfo) => {
        const scale = axisInfo[1][0];
        const scaleType = axisInfo[0];
        const scaleProps = canvas[scaleType]();

        if (scaleProps.field) {
            const {
                type,
                step
            } = scale.config();

            const stepMapper = typeof step === 'boolean' ? step : false;
            LegendCls = LEGEND_TYPE_MAP[`${type}-${stepMapper}-${scaleType}`];
            dataset.push({ scale, canvas, fieldName: scaleProps.field, LegendCls, scaleType });
        }
    });

    return dataset;
};

/**
 *
 *
 * @param {*} legendConfig
 * @param {*} canvases
 * @param {*} measurement
 * @param {*} prevLegends
 *
 */
export const legendInitializer = (legendConfig, canvas, measurement, prevLegends) => {
    const legends = [];
    const {
        height,
        width,
        headerHeight
    } = measurement;
    const {
        position,
        align
    } = legendConfig;

    const legendInfo = legendCreator(canvas);

    legendInfo.forEach((dataInfo, index) => {
        let legend = {};

        const legendMeasures = {};
        const {
                LegendCls,
                scale,
                fieldName,
                scaleType
            } = dataInfo;
        const config = legendConfig[scaleType] || {};
        const title = config.title || {};
        title.text = title.text || fieldName;
        if (config.show) {
            config.position = position;
            config.align = align;

            if (prevLegends[index]) {
                legend = prevLegends[index].legend;
            } else {
                legend = LegendCls.create({
                    labelManager: canvas._dependencies.smartlabel,
                    cells: {
                        AxisCell, TextCell
                    }
                });
            }
            legendMeasures.maxHeight = align === VERTICAL ? (height - headerHeight) : height * 0.2;
            legendMeasures.maxWidth = align === HORIZONTAL ? width : width * 0.2;
            legendMeasures.width = Math.min(legendMeasures.maxWidth, config.width);
            legendMeasures.height = Math.min(legendMeasures.maxHeight, config.height);

            [PADDING, BORDER, MARGIN].forEach((e) => {
                legendMeasures[e] = config[e];
            });
            legend.scale(scale)
                            .title(title)
                            .fieldName(fieldName)
                            .config(config)
                            .metaData(canvas.composition().visualGroup.getGroupByData().project([fieldName]))
                            .measurement(legendMeasures)
                            .canvasAlias(canvas.alias())
                            .setLegendMeasures();

            legends.push({ canvas, legend, scaleType });
        }
    });
    return legends;
};

/**
 *
 *
 * @param {*} legends
 * @param {*} legendConfig
 * @param {*} availableHeight
 * @param {*} availableWidth
 *
 */
export const getLegendSpace = (legends, legendConfig, availableHeight, availableWidth) => {
    const legendMeasures = legends.map(legendInfo => legendInfo.legend.measurement());
    const legendSpace = { width: 0, height: 0 };

    legendMeasures.forEach((space) => {
        let height = 0;
        let width = 0;
        width = Math.min(space.width, space.maxWidth);
        height = Math.min(space.height, space.maxHeight);

        if (legendConfig.align === HORIZONTAL) {
            if (legendSpace.width + width > availableWidth) {
                legendSpace.width = availableWidth;
                legendSpace.height += height;
            } else {
                legendSpace.width += width;
                legendSpace.height = Math.max(legendSpace.height, height);
            }
        } else if (legendSpace.height + height > availableHeight) {
            legendSpace.height = height;
            legendSpace.width += width;
        } else {
            legendSpace.height += height;
            legendSpace.width = Math.max(legendSpace.width, width);
        }
    });
    if (legendConfig.align === HORIZONTAL) {
        legendSpace.width = availableWidth;
    }
    return legendSpace;
};

/**
 *
 *
 * @param {*} context
 * @param {*} headerHeight
 *
 */
export const createLegend = (context, headerHeight, height, width) => {
    const measurement = {
        height,
        width,
        headerHeight
    };
    const { legend } = context.config();
    const { show, position } = legend;

    legend.classPrefix = context.config().classPrefix;
    const align = (position === LEFT || position === RIGHT) ? VERTICAL : HORIZONTAL;

    legend.show = show ? ((align === VERTICAL && width > 200) || (align === HORIZONTAL && height > 200)) : show;
    legend.align = align;
    return legendInitializer(legend, context, measurement, context.legends || []);
};