2020-10-02 15:10:00 +00:00
|
|
|
import {typedCompare} from 'app/common/SortFunc';
|
2021-09-30 07:41:36 +00:00
|
|
|
import {decodeObject} from 'app/plugin/objtypes';
|
2020-10-02 15:10:00 +00:00
|
|
|
import {Datum} from 'plotly.js';
|
2021-09-01 09:41:30 +00:00
|
|
|
import range = require('lodash/range');
|
|
|
|
import uniqBy = require('lodash/uniqBy');
|
2021-09-30 07:41:36 +00:00
|
|
|
import flatten = require('lodash/flatten');
|
2020-10-02 15:10:00 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Sort all values in a list of series according to the values in the first one.
|
|
|
|
*/
|
|
|
|
export function sortByXValues(series: Array<{values: Datum[]}>): void {
|
|
|
|
// The order of points matters for graph types that connect points with lines: the lines are
|
|
|
|
// drawn in order in which the points appear in the data. For the chart types we support, it
|
|
|
|
// only makes sense to keep the points sorted. (The only downside is that Grist line charts can
|
|
|
|
// no longer produce arbitrary line drawings.)
|
|
|
|
if (!series[0]) { return; }
|
|
|
|
const xValues = series[0].values;
|
|
|
|
const indices = xValues.map((val, i) => i);
|
|
|
|
indices.sort((a, b) => typedCompare(xValues[a], xValues[b]));
|
|
|
|
for (const s of series) {
|
|
|
|
const values = s.values;
|
|
|
|
s.values = indices.map((i) => values[i]);
|
|
|
|
}
|
|
|
|
}
|
2021-09-01 09:41:30 +00:00
|
|
|
|
|
|
|
// creates new version of series that has a duplicate free version of the values in the first one.
|
|
|
|
export function uniqXValues<T extends {values: Datum[]}>(series: Array<T>): Array<T> {
|
|
|
|
if (!series[0]) { return []; }
|
|
|
|
const n = series[0].values.length;
|
|
|
|
const indexToKeep = new Set(uniqBy(range(n), (i) => series[0].values[i]));
|
|
|
|
return series.map((line: T) => ({
|
|
|
|
...line,
|
|
|
|
values: line.values.filter((_val, i) => indexToKeep.has(i))
|
|
|
|
}));
|
|
|
|
}
|
2021-09-30 07:41:36 +00:00
|
|
|
|
|
|
|
// Creates new version of series that split any entry whose value in the first series is a list into
|
|
|
|
// multiple entries, one entry for each list's item. For all other series, newly created entries have
|
|
|
|
// the same value as the original.
|
|
|
|
export function splitValues<T extends {values: Datum[]}>(series: Array<T>): Array<T> {
|
|
|
|
return splitValuesByIndex(series, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
// This method is like splitValues except it splits according to the values of the series at position index.
|
|
|
|
export function splitValuesByIndex<T extends {values: Datum[]}>(series: Array<T>, index: number): Array<T> {
|
|
|
|
const decoded = (series[index].values as any[]).map(decodeObject);
|
|
|
|
|
|
|
|
return series.map((s, si) => {
|
|
|
|
if (si === index) {
|
|
|
|
return {...series[index], values: flatten(decoded)};
|
|
|
|
}
|
|
|
|
let values: Datum[] = [];
|
|
|
|
for (const [i, splitByValue] of decoded.entries()) {
|
|
|
|
if (Array.isArray(splitByValue)) {
|
|
|
|
values = values.concat(Array(splitByValue.length).fill(s.values[i]));
|
|
|
|
} else {
|
|
|
|
values.push(s.values[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return {...s, values};
|
|
|
|
});
|
|
|
|
}
|