mirror of
https://github.com/moroshko/rxviz.git
synced 2026-01-18 16:22:20 +00:00
234 lines
5.7 KiB
JavaScript
234 lines
5.7 KiB
JavaScript
import get from 'lodash.get';
|
|
import set from 'lodash.set';
|
|
import omit from 'lodash.omit';
|
|
import isUndefined from 'lodash.isundefined';
|
|
import isPlainObject from 'lodash.isplainobject';
|
|
import isFunction from 'lodash.isfunction';
|
|
import cloneDeep from 'lodash.clonedeep';
|
|
import TinyQueue from 'tinyqueue';
|
|
import {
|
|
defaultMainColor,
|
|
defaultObservableColor,
|
|
defaultShapeColor
|
|
} from './colors';
|
|
|
|
const isObservable = value =>
|
|
isPlainObject(value) &&
|
|
value.type === 'observable' &&
|
|
Array.isArray(value.values);
|
|
const isError = value => isPlainObject(value) && !isUndefined(value.error);
|
|
const isCompleted = value => isPlainObject(value) && value.completed === true;
|
|
const isMeta = value => isPlainObject(value) && value.meta === true;
|
|
|
|
export const isTimeout = value => isMeta(value) && value.timeout === true;
|
|
|
|
const colorizeItem = ({ isObservable, item }) => {
|
|
if (typeof item.color === 'string') {
|
|
return item;
|
|
}
|
|
|
|
return {
|
|
...item,
|
|
color: isObservable ? defaultObservableColor : defaultShapeColor
|
|
};
|
|
};
|
|
|
|
const prepareItem = ({
|
|
isObservable,
|
|
isError,
|
|
item,
|
|
observableIndex,
|
|
valueIndex,
|
|
renderer
|
|
}) => {
|
|
if (isFunction(renderer)) {
|
|
const { value, ...rest } = item;
|
|
const renderedItem = renderer({
|
|
isObservable,
|
|
isError,
|
|
value: isError ? item.error : value,
|
|
observableIndex,
|
|
valueIndex
|
|
});
|
|
const color = isError
|
|
? {}
|
|
: { color: isObservable ? defaultObservableColor : defaultShapeColor };
|
|
|
|
return {
|
|
...color,
|
|
...rest,
|
|
...renderedItem
|
|
};
|
|
}
|
|
|
|
return isError ? omit(item, 'error') : colorizeItem({ isObservable, item });
|
|
};
|
|
|
|
export const updateData = (observable, path, value) => {
|
|
const result = cloneDeep(observable);
|
|
const dataPath = path.reduce((acc, index) => acc.concat(index, 'values'), [
|
|
'values'
|
|
]);
|
|
|
|
return set(result, dataPath, get(result, dataPath).concat(value));
|
|
};
|
|
|
|
const getTextForMergedTooltip = value => {
|
|
const { tooltip } = value;
|
|
|
|
if (tooltip && tooltip.text) {
|
|
return tooltip.text;
|
|
}
|
|
|
|
return value.text;
|
|
};
|
|
|
|
const calcMergedTooltip = mergedTooltip => ({
|
|
text: mergedTooltip.join(', ')
|
|
});
|
|
|
|
export const getModel = ({
|
|
data,
|
|
renderer,
|
|
inheritMainColor = true,
|
|
mergeThreshold
|
|
}) => {
|
|
let result = {
|
|
observables: [],
|
|
connectors: []
|
|
};
|
|
|
|
if (!isObservable(data)) {
|
|
return result;
|
|
}
|
|
|
|
let queue = new TinyQueue(
|
|
[
|
|
{
|
|
observable: data,
|
|
startTime: 0,
|
|
mainColor: defaultMainColor
|
|
}
|
|
],
|
|
(obs1, obs2) => obs1.startTime - obs2.startTime
|
|
);
|
|
|
|
while (queue.length > 0) {
|
|
const { observable, startTime, mainColor, fromIndex } = queue.pop();
|
|
const observableIndex = result.observables.length;
|
|
|
|
if (!isUndefined(fromIndex)) {
|
|
result.connectors.push({
|
|
time: startTime,
|
|
fromIndex,
|
|
toIndex: observableIndex,
|
|
color: mainColor
|
|
});
|
|
}
|
|
|
|
let resultObservable = {
|
|
values: [],
|
|
startTime: startTime,
|
|
mainColor: mainColor
|
|
};
|
|
|
|
for (let i = 0, len = observable.values.length; i < len; i++) {
|
|
const item = observable.values[i];
|
|
const valueIndex = resultObservable.values.length;
|
|
|
|
if (isObservable(item)) {
|
|
const { time } = item;
|
|
const value = prepareItem({
|
|
isObservable: true,
|
|
isError: false,
|
|
item: {
|
|
...omit(item, 'type', 'values'),
|
|
isObservable: true,
|
|
text: ''
|
|
},
|
|
observableIndex,
|
|
valueIndex,
|
|
renderer
|
|
});
|
|
const { color } = value;
|
|
|
|
resultObservable.values.push(value);
|
|
|
|
queue.push({
|
|
observable: item,
|
|
startTime: time,
|
|
mainColor: inheritMainColor ? color : defaultMainColor,
|
|
fromIndex: observableIndex
|
|
});
|
|
} else if (isError(item)) {
|
|
resultObservable.endTime = item.time;
|
|
resultObservable.error = prepareItem({
|
|
isObservable: false,
|
|
isError: true,
|
|
item,
|
|
observableIndex,
|
|
valueIndex,
|
|
renderer
|
|
});
|
|
} else if (isCompleted(item)) {
|
|
resultObservable.endTime = item.time;
|
|
resultObservable.completed = {
|
|
time: item.time
|
|
};
|
|
|
|
const valuesCount = resultObservable.values.length;
|
|
|
|
if (valuesCount > 0) {
|
|
resultObservable.completed.lastValueBeforeCompletedTime =
|
|
resultObservable.values[valuesCount - 1].time;
|
|
}
|
|
} else {
|
|
const value = prepareItem({
|
|
isObservable: false,
|
|
isError: false,
|
|
item,
|
|
observableIndex,
|
|
valueIndex,
|
|
renderer
|
|
});
|
|
const lastValue =
|
|
valueIndex === 0 ? null : resultObservable.values[valueIndex - 1];
|
|
|
|
if (
|
|
!mergeThreshold ||
|
|
!lastValue ||
|
|
value.time - lastValue.time > mergeThreshold
|
|
) {
|
|
resultObservable.values.push(value);
|
|
} else {
|
|
if (typeof lastValue.count === 'number') {
|
|
lastValue.count += 1;
|
|
lastValue.mergedTooltip.push(getTextForMergedTooltip(value));
|
|
} else {
|
|
/* First merge */
|
|
lastValue.mergedTooltip = [
|
|
getTextForMergedTooltip(lastValue),
|
|
getTextForMergedTooltip(value)
|
|
];
|
|
lastValue.text = '...'; // Note that we change the text AFTER it's passed to lastValue.mergedTooltip
|
|
lastValue.count = 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
resultObservable.values.forEach(value => {
|
|
if (value.mergedTooltip) {
|
|
value.tooltip = calcMergedTooltip(value.mergedTooltip);
|
|
|
|
delete value.mergedTooltip;
|
|
delete value.textStyle;
|
|
}
|
|
});
|
|
|
|
result.observables.push(resultObservable);
|
|
}
|
|
|
|
return result;
|
|
};
|