import {
FieldType,
getDomainFromData,
setStyles,
easeFns,
selectElement,
DimensionSubtype
} from 'muze-utils';
import { ScaleType } from '@chartshq/muze-axis';
import { transformFactory } from '@chartshq/transform';
import { IDENTITY, STACK, GROUP, COLOR, SHAPE, SIZE, ENCODING } from '../enums/constants';
const BAND = ScaleType.BAND;
export const getLayerColor = ({ datum, index }, { colorEncoding, colorAxis, colorFieldIndex }) => {
let rawColor = '';
let color = '';
if (colorEncoding && colorEncoding.value instanceof Function) {
color = colorEncoding.value(datum, index);
rawColor = colorEncoding.value(datum, index);
} else {
rawColor = colorAxis.getRawColor(datum._data[colorFieldIndex]);
color = colorAxis.getHslString(rawColor);
}
return { color, rawColor };
};
const transfromColor = (colorAxis, datum, styleType, intensity) => {
datum.meta.stateColor[styleType] = datum.meta.stateColor[styleType] || datum.meta.originalColor;
const fillColorInfo = colorAxis.transformColor(datum.meta.stateColor[styleType], intensity);
datum.meta.stateColor[styleType] = fillColorInfo.hsla;
return fillColorInfo;
};
export const applyInteractionStyle = (context, selectionSet, interactionStyles, config) => {
const elements = context.getPlotElementsFromSet(selectionSet);
const axes = context.axes();
const colorAxis = axes.color;
const apply = config.apply;
const interactionType = config.interactionType;
interactionStyles.forEach((style) => {
const styleType = style.type;
elements.style(styleType, ((d) => {
const { colorTransform, stateColor, originalColor } = d.meta;
colorTransform[interactionType] = colorTransform[interactionType] || {};
if (apply && !colorTransform[interactionType][styleType]) {
// fade selections
colorTransform[interactionType][styleType] = style.intensity;
const color = transfromColor(colorAxis, d, styleType, style.intensity).color;
return color;
}
if (!apply && colorTransform[interactionType][styleType]) {
// unfade selections
colorTransform[interactionType][styleType] = null;
return transfromColor(colorAxis, d, styleType, style.intensity.map(e => -e)).color;
}
const [h, s, l, a] = stateColor[styleType] ? stateColor[styleType] : originalColor;
return `hsla(${h * 360},${s * 100}%,${l * 100}%, ${a || 1})`;
}));
});
};
/**
*
*
* @param {*} selectionSet
* @param {*} className
* @param {*} hasFaded
*/
export const fadeUnfadeSelection = (context, selectionSet, hasFaded, interaction) => {
const interactionConfig = { interaction, apply: hasFaded };
applyInteractionStyle(context, selectionSet, 'fade', interactionConfig);
};
/**
*
*
* @param {*} selectionSet
* @param {*} className
* @param {*} hasFaded
*/
export const focusUnfocusSelection = (context, selectionSet, isFocussed, interaction) => {
const interactionConfig = { interaction, apply: isFocussed };
applyInteractionStyle(context, selectionSet, 'focus', interactionConfig);
};
/**
*
*
* @param {*} axes
*
*/
export const getAxesScales = (axes) => {
const [xAxis, yAxis] = [ENCODING.X, ENCODING.Y].map(e => axes[e]);
const [xScale, yScale] = [xAxis, yAxis].map(e => e && e.scale());
return {
xAxis,
yAxis,
xScale,
yScale
};
};
/**
*
*
* @param {*} encoding
* @param {*} fieldsConfig
*
*/
export const getEncodingFieldInf = (encoding, fieldsConfig) => {
const [xField, yField, x0Field, y0Field, colorField, shapeField, sizeField] =
[ENCODING.X, ENCODING.Y, ENCODING.X0, ENCODING.Y0, COLOR, SHAPE, SIZE].map(e => encoding[e] &&
encoding[e].field);
const [xFieldType, yFieldType] = [xField, yField, x0Field, y0Field].map(e => fieldsConfig[e] &&
fieldsConfig[e].def.type);
const [xFieldSubType, yFieldSubType] = [xField, yField].map(e => fieldsConfig[e] && (fieldsConfig[e].def.subtype ||
fieldsConfig[e].def.type));
const [xFieldIndex, yFieldIndex, x0FieldIndex, y0FieldIndex] = [xField, yField, x0Field, y0Field]
.map(e => fieldsConfig[e] && fieldsConfig[e].index);
return {
xField,
yField,
colorField,
shapeField,
sizeField,
x0Field,
y0Field,
xFieldType,
yFieldType,
xFieldSubType,
yFieldSubType,
xFieldIndex,
yFieldIndex,
x0FieldIndex,
y0FieldIndex
};
};
/**
*
*
* @param {*} layerConfig
* @param {*} fieldsConfig
*
*/
export const getValidTransform = (layerConfig, fieldsConfig, encodingFieldInf) => {
let transformType;
const {
transform
} = layerConfig;
const {
xField,
yField,
xFieldType,
yFieldType
} = encodingFieldInf;
const groupByField = transform.groupBy;
const groupByFieldMeasure = fieldsConfig[groupByField] && fieldsConfig[groupByField].def.type === FieldType.MEASURE;
transformType = transform.type;
if (!xField || !yField || groupByFieldMeasure || !groupByField || xFieldType === FieldType.DIMENSION &&
yFieldType === FieldType.DIMENSION) {
transformType = IDENTITY;
}
return transformType;
};
/**
*
*
* @param {*} dataModel
* @param {*} config
* @param {*} transformType
*
*/
export const transformData = (dataModel, config, transformType, encodingFieldInf) => {
const data = dataModel.getData({ withUid: true });
const schema = data.schema;
const transform = config.transform;
const {
xField,
yField,
xFieldType,
yFieldType
} = encodingFieldInf;
const uniqueField = xFieldType === FieldType.MEASURE ? yField : xField;
return transformFactory(transformType)(schema, data.data, {
groupBy: transform.groupBy,
uniqueField,
sort: transform.sort || 'none',
offset: transform.offset,
orderBy: transform.orderBy,
value: yFieldType === FieldType.MEASURE ? yField : xField
}, data.uids);
};
export const getIndividualClassName = (d, i, data, context) => {
const className = context.config().individualClassName;
let classNameStr = '';
if (className instanceof Function) {
classNameStr = className(d, i, data, context);
}
return classNameStr;
};
/*
* This method resolves the x, y, x0 and y0 values from the transformed data.
* It also checks the type of transformed data for example, if it is a stacked data
* then it fetches the y and y0 values from the stacked data.
* @param {Array.<Array>} transformedData transformed data
* @param {Object} fieldsConfig field definitions
* @param {string} transformType type of transformed data - stack, group or identity.
* @return {Array.<Object>} Normalized data
*/
export const getNormalizedData = (transformedData, fieldsConfig, encodingFieldInf, transformType) => {
const transformedDataArr = transformType === IDENTITY ? [transformedData] : transformedData;
const {
xFieldType,
xFieldIndex,
yFieldIndex,
x0FieldIndex,
y0FieldIndex
} = encodingFieldInf;
const fieldsLen = Object.keys(fieldsConfig).length;
/**
* Returns normalized data from transformed data. It recursively traverses through
* the transformed data if there it is nested.
*/
return transformedDataArr.map((data) => {
const values = transformType === GROUP ? data.values : data;
return values.map((d) => {
let pointObj = {};
let tuple;
if (transformType === STACK) {
tuple = d.data || [];
let y;
let y0;
let x;
let x0;
if (d[1] >= d[0]) {
y = x0 = d[1];
x = y0 = d[0];
} else {
y = x0 = d[0];
x = y0 = d[1];
}
pointObj = xFieldType === FieldType.MEASURE ? {
x,
x0,
y: tuple[yFieldIndex],
y0: tuple[yFieldIndex]
} : {
x: tuple[xFieldIndex],
x0: tuple[xFieldIndex],
y,
y0
};
pointObj._data = tuple;
pointObj._id = tuple[fieldsLen];
} else {
pointObj = {
x: d[xFieldIndex],
y: d[yFieldIndex],
x0: d[x0FieldIndex],
y0: d[y0FieldIndex]
};
pointObj._data = d;
pointObj._id = d[fieldsLen];
}
return pointObj;
});
}).filter(d => d.length);
};
export const calculateDomainFromData = (data, encodingFieldInf, transformType) => {
const {
xFieldSubType,
yFieldSubType,
xField,
yField,
x0Field,
y0Field
} = encodingFieldInf;
const domains = {};
const yEnc = ENCODING.Y;
const xEnc = ENCODING.X;
if (xField) {
domains.x = getDomainFromData(data, x0Field || transformType === STACK ? [xEnc, ENCODING.X0] : [xEnc, xEnc],
xFieldSubType);
}
if (yField) {
domains.y = getDomainFromData(data, y0Field || transformType === STACK ? [ENCODING.Y0, ENCODING.Y] :
[yEnc, yEnc], yFieldSubType);
}
return domains;
};
export const attachDataToVoronoi = (voronoi, points) => {
voronoi.data([].concat(...points).filter(d => d._id !== undefined).map((d) => {
const point = d.update;
return {
x: point.x,
y: point.y,
data: d
};
}));
};
/**
*
*
* @param {*} target
* @param {*} styles
* @param {*} remove
*/
export const updateStyle = (target, styles, remove) => {
for (const key in styles) {
if ({}.hasOwnProperty.call(styles, key)) {
target.style(key, remove ? null : styles[key]);
}
}
};
/**
*
*
* @param {*} mount
* @param {*} context
*/
export const animateGroup = (mount, context) => {
let groupTransition;
let update;
const { transition, groupAnimateStyle } = context;
const { duration, effect, disabled } = transition;
if (groupAnimateStyle) {
setStyles(mount.node(), groupAnimateStyle.enter);
update = groupAnimateStyle.update;
if (!disabled) {
groupTransition = mount.transition()
.ease(easeFns[effect])
.duration(duration)
.on('end', function () {
updateStyle(selectElement(this), update, true);
});
} else {
groupTransition = mount;
}
updateStyle(groupTransition, update);
}
};
export const positionPoints = (context, points) => {
const positioner = context.encodingTransform();
if (positioner) {
return positioner(points, context, { smartLabel: context._dependencies.smartLabel });
}
return points;
};
/**
* Gets the width of each group. It gets the width from axis if it is available for
* example when the scale is nominal else it calculates the width from the
* range of the axis and number of data points.
*
* @param {SimpleAxis} axis instance of axis
* @param {number} minDiff Minimum difference between data points
* @return {number} width of each bar
* @private
*/
export const getGroupSpan = (axis, minDiff) => {
let groupSpan;
const width = axis.getUnitWidth();
const scale = axis.scale();
const range = scale.range();
const domain = scale.domain();
!width ? groupSpan = (Math.abs(range[1] - range[0]) / Math.abs(domain[1] - domain[0])) * minDiff :
(groupSpan = width);
return groupSpan;
};
export const getPlotMeasurement = (context, dimensionalValues) => {
const fieldInfo = context.encodingFieldsInf();
const axes = context.axes();
const transformType = context.transformType();
const config = context.config();
const bandScale = context._bandScale;
return ['x', 'y'].map((type) => {
let span = 0;
let groupSpan = 0;
let padding = 0;
let offsetValues = [];
if (fieldInfo[`${type}FieldType`] === FieldType.DIMENSION) {
let actualGroupWidth;
const isTemporal = fieldInfo[`${type}FieldSubType`] === DimensionSubtype.TEMPORAL;
const timeDiff = isTemporal ? context.dataProps().timeDiffs[type] : 0;
const axis = axes[type];
const pad = config[`pad${type.toUpperCase()}`];
const innerPadding = config.innerPadding;
const keys = dimensionalValues;
const scale = axis.scale();
groupSpan = getGroupSpan(axis, timeDiff);
const isAxisBandScale = axis.constructor.type() === BAND;
const axisPadding = axis.config().padding;
// If it is a grouped bar then the width of each bar in a grouping is retrieved from
// a band scale. The band scale will have range equal to width of one group of bars and
// the domain is set to series keys.
if (transformType === 'group') {
const groupPadding = isAxisBandScale ? 0 : axisPadding * groupSpan / 2;
bandScale.range([groupPadding, groupSpan - groupPadding]).domain(keys).paddingInner(innerPadding);
span = bandScale.bandwidth();
actualGroupWidth = groupSpan - (isAxisBandScale ? 0 : axisPadding * groupSpan);
offsetValues = keys.map(key => bandScale(key) - (isAxisBandScale ? 0 : (groupSpan / 2)));
} else if (pad !== undefined) {
let offset;
if (isAxisBandScale) {
const step = scale.step();
offset = scale.padding() * step;
span = scale.bandwidth() + offset;
} else {
span = groupSpan;
}
offsetValues = keys.map(() => (isAxisBandScale ? -(offset / 2) : -(span / 2)));
} else {
padding = isAxisBandScale ? 0 : axisPadding * groupSpan;
span = groupSpan - padding;
actualGroupWidth = span;
offsetValues = keys.map(() => (isAxisBandScale ? 0 : -(span / 2)));
}
groupSpan = actualGroupWidth;
padding = isAxisBandScale ? axisPadding * axis.scale().step() : axisPadding * groupSpan;
}
return {
span,
offsetValues,
groupSpan,
padding
};
});
};