Source: muze-axis/src/cartesian-axis/helper.js

import { TOP, LEFT, BOTTOM } from '../enums/axis-orientation';
import { LOG } from '../enums/scale-type';

export const getNumberOfTicks = (availableSpace, labelDim, axis, axisInstance) => {
    const ticks = axis.scale().ticks();
    const { numberOfTicks } = axisInstance.config();
    const tickLength = ticks.length;
    let numberOfValues = tickLength;

    if (tickLength * (labelDim * 1.5) > availableSpace) {
        numberOfValues = Math.floor(availableSpace / (labelDim * 1.5));
    }

    numberOfValues = Math.min(numberOfTicks, Math.max(1, numberOfValues));
    return axis.scale().ticks(numberOfValues);
};

export const sanitizeDomain = (domain, context) => {
    const interpolator = context.config().interpolator;
    // @todo: Get from scale decorator
    if (interpolator === LOG && domain[0] >= 0) {
        return [Math.max(1, domain[0]), Math.max(1, domain[1])];
    }
    return domain;
};

export const getTickFormatter = (tickFormat, numberFormat) => {
    if (tickFormat) {
        return ticks => (val, i) => tickFormat(numberFormat(val), i, ticks);
    }
    return () => val => numberFormat(val);
};

/**
 *
 *
 *
 * @memberof SimpleAxis
 */
export const getTickLabelInfo = (context) => {
    let largestLabel = '';
    let labelProps;
    let smartTick = {};
    let axisTickLabels;
    const scale = context.scale();
    const allLabelLengths = [];
    const { tickFormat, tickValues, numberFormat } = context.config();
    const labelFunc = scale.ticks || scale.quantile || scale.domain;
    // set the style on the shared label manager instance
    const { labelManager } = context.dependencies();

    labelManager.setStyle(context._tickLabelStyle);
    // get the values along the domain

    axisTickLabels = tickValues || labelFunc();
    // Get the tick labels
    axisTickLabels = axisTickLabels.map((originalLabel, i) => {
        const formattedLabel = numberFormat(originalLabel);

        //  get formats of tick if any
        const label = tickFormat ? tickFormat(formattedLabel, i, axisTickLabels) : (scale.tickFormat ?
            numberFormat(scale.tickFormat()(originalLabel)) : formattedLabel);

        // convert to string for quant values
        const temp = label.toString();
        // Get spaces for all labels
        allLabelLengths.push(labelManager.getOriSize(temp));
        // Getting largest label
        if (temp.length > largestLabel.length) {
            largestLabel = temp;
            smartTick = context.smartTicks() ? context.smartTicks()[i] : {};
            labelProps = allLabelLengths[i];
        }
        return label;
    });

    labelProps = labelManager.getOriSize(largestLabel);

    return { largestLabel, largestLabelDim: labelProps, axisTickLabels, allLabelLengths, smartTick };
};

/**
 *
 *
 *
 * @memberof SimpleAxis
 */
export const computeAxisDimensions = (context) => {
    let tickLabelDim = {};
    const {
        name,
        labels,
        tickValues
    } = context.config();
    const angle = ((labels.smartTicks) ? 0 : labels.rotation) * Math.PI / 180;
    const { labelManager } = context.dependencies();
    const {
        largestLabelDim,
        axisTickLabels,
        smartTick
    } = getTickLabelInfo(context);
    const { height: labelHeight, width: labelWidth } = largestLabelDim;
    // get the domain of axis
    const domain = context.domain();

    if (domain.length === 0) {
        return null;
    }
    if (context._rotationLock === false) {
        context.setRotationConfig(tickValues || axisTickLabels, largestLabelDim.width);
        context._rotationLock = false;
    }
    if (labels.smartTicks) {
        tickLabelDim = smartTick;
    } else {
        tickLabelDim = {
            width: Math.abs(labelHeight * Math.sin(angle)) + Math.abs(labelWidth * Math.cos(angle)),
            height: Math.abs(labelWidth * Math.sin(angle)) + Math.abs(labelHeight * Math.cos(angle))
        };
    }

    labelManager.setStyle(context._axisNameStyle);
    return {
        tickSize: context.getTickSize(),
        tickLabelDim,
        axisLabelDim: labelManager.getOriSize(name),
        largestLabelDim,
        axisTickLabels
    };
};

/**
*
*
* @memberof SimpleAxis
*/
export const setOffset = (context) => {
    let x = 0;
    let y = 0;
    const logicalSpace = context.logicalSpace();
    const config = context.config();
    const {
        orientation,
        xOffset,
        yOffset
    } = config;
    if (orientation === LEFT) {
        x = xOffset === undefined ? logicalSpace.width : xOffset;
    }
    if (orientation === TOP) {
        y = yOffset === undefined ? logicalSpace.height : yOffset;
    }
    context.config({ xOffset: x, yOffset: y });
};

/**
 *
 *
 * @param {*} timeDiff
 * @param {*} range
 * @param {*} domain
 *
 */
const getAxisOffset = (timeDiff, range, domain) => {
    const pvr = Math.abs(range[1] - range[0]) / (domain[1] - domain[0]);
    const width = (pvr * timeDiff);
    const avWidth = (range[1] - range[0]);
    const bars = avWidth / width;
    const barWidth = avWidth / (bars + 1);
    const diff = avWidth - barWidth * bars;

    return diff / 2;
};

export const adjustRange = (minDiff, range, domain, orientation) => {
    const diff = getAxisOffset(minDiff, range, domain);

    if (orientation === TOP || orientation === BOTTOM) {
        range[0] += diff;
        range[1] -= diff;
    } else {
        range[0] -= diff;
        range[1] += diff;
    }
    return range;
};

 /**
 * Listener attached to the axis on change of parameters.
 *
 * @param {Function} callback to be excuted on change of domain range etc
 * @memberof SimpleAxis
 */
export const registerChangeListeners = (context) => {
    const store = context.store();

    store.model.next(['domain', 'range', 'mount', 'config'], (...params) => {
        context.render();
        context._domainLock = false;
        context._eventList.forEach((e) => {
            e.action instanceof Function && e.action(...params);
        });
    }, true);
    return context;
};

/**
 *
 *
 * @param {*} axisDimensions
 * @param {*} config
 * @param {*} range
 *
 */
export const getHorizontalAxisSpace = (context, axisDimensions, config, range) => {
    let width;
    let height;
    const {
        tickSize,
        tickLabelDim,
        axisLabelDim
    } = axisDimensions;
    const {
        axisNamePadding,
        showAxisName,
        tickValues
   } = config;
    const domain = context.domain();
    const { height: axisDimHeight } = axisLabelDim;
    const { height: tickDimHeight, width: tickDimWidth } = tickLabelDim;

    width = range && range.length ? range[1] - range[0] : 0;

    height = 0;
    if (tickValues) {
        const minTickDiff = context.getMinTickDifference();
        const [min, max] = [Math.min(...tickValues, ...domain), Math.max(...tickValues, ...domain)];

        width = ((max - min) / Math.abs(minTickDiff)) * (tickDimWidth + context._minTickDistance.width);
    }
    if (!width || width === 0) {
        height = Math.max(tickDimWidth, tickDimHeight);
    } else {
        height = tickDimHeight;
    }
    height += (showAxisName ? (axisDimHeight + axisNamePadding) : 0) + tickSize;
    return {
        width,
        height
    };
};

/**
 *
 *
 * @param {*} axisDimensions
 * @param {*} config
 * @param {*} range
 *
 */
export const getVerticalAxisSpace = (context, axisDimensions, config) => {
    let height;
    let width;
    const {
        tickSize,
        tickLabelDim,
        axisLabelDim
    } = axisDimensions;
    const {
        axisNamePadding,
        showAxisName,
        tickValues
   } = config;
    const domain = context.domain();
    const { height: axisDimHeight } = axisLabelDim;
    const { height: tickDimHeight, width: tickDimWidth } = tickLabelDim;

    height = 0;
    width = tickDimWidth;
    if (tickValues) {
        const minTickDiff = context.getMinTickDifference();
        const [min, max] = [Math.min(...tickValues, ...domain), Math.max(...tickValues, ...domain)];

        height = ((max - min) / Math.abs(minTickDiff)) * (tickDimHeight);
    }
    width += (showAxisName ? axisDimHeight : 0) + tickSize + axisNamePadding;

    return {
        height,
        width
    };
};

 /**
     * Calculates the logical space of the axis
     * @return {Object} Width and height occupied by the axis.
     */
export const calculateBandSpace = (context) => {
    const range = context.range();
    const config = context.config();
    const {
        orientation,
        show
    } = config;
    const axisDimensions = context.getAxisDimensions();
    const {
        largestLabelDim,
        axisTickLabels
    } = axisDimensions;
    const { height: largestDimHeight, width: largestDimWidth } = largestLabelDim;

    if (orientation === TOP || orientation === BOTTOM) {
        let { width, height } = getHorizontalAxisSpace(context, axisDimensions, config, range);
        if (!width || width === 0) {
            width = axisTickLabels.length * (Math.min(largestDimWidth + context._minTickDistance.width,
                         largestDimHeight + context._minTickDistance.width));
        }
        if (show === false) {
            height = 0;
        }
        return {
            width,
            height
        };
    }

    let { width, height } = getVerticalAxisSpace(context, axisDimensions, config, range);

    if (!height || height === 0) {
        height = axisTickLabels.length * (largestDimHeight + largestDimHeight / 2) + largestDimHeight;
    }
    if (show === false) {
        width = 0;
    }
    return {
        width,
        height
    };
};

/**
     * Calculates the logical space of the axis
     * @return {Object} Width and height occupied by the axis.
     */
export const calculateContinousSpace = (context) => {
    const range = context.range();
    const config = context.config();
    const axisDimensions = context.getAxisDimensions();

    const {
        orientation,
        show,
        showAxisName
    } = config;
    const {
        axisLabelDim
    } = axisDimensions;

    if (show === false) {
        return {
            width: 0,
            height: 0
        };
    }

    const { width: axisDimWidth } = axisLabelDim;

    if (orientation === TOP || orientation === BOTTOM) {
        const { width, height } = getHorizontalAxisSpace(context, axisDimensions, config);
        const axisWidth = Math.max(width, axisDimWidth);

        return {
            width: axisWidth,
            height
        };
    }

    const { width, height } = getVerticalAxisSpace(context, axisDimensions, config, range);

    const effHeight = Math.max(height, showAxisName ? axisDimWidth : 0);

    return {
        width,
        height: effHeight
    };
};