Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix chart that generates duplicate plotly data #1744

Merged
merged 1 commit into from
Sep 24, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 52 additions & 56 deletions libs/mlchartlib/src/lib/components/ChartBuilder.ts
Original file line number Diff line number Diff line change
@@ -8,13 +8,19 @@ import { Data, Datum } from "plotly.js";
import { accessorMappingFunctions } from "./accessorMappingFunctions";
import { IData } from "./IData";

interface IRow {
x: any;
y: any;
group: any;
size: any;
}
export class ChartBuilder {
public static buildPlotlySeries<T>(
datum: IData,
rows: T[]
): Array<Partial<Data>> {
const groupingDictionary: { [key: string]: Partial<Data> } = {};
const defaultSeries = ChartBuilder.buildDefaultSeries(datum);
let defaultSeries: Partial<Data> | undefined;
const datumLevelPaths: string = datum.datapointLevelAccessors
? `, ${Object.keys(datum.datapointLevelAccessors)
.map((key) => {
@@ -24,12 +30,7 @@ export class ChartBuilder {
})
.join(", ")}`
: "";
const projectedRows: Array<{
x: any;
y: any;
group: any;
size: any;
}> = jmespath.search(
const projectedRows: IRow[] = jmespath.search(
rows,
`${datum.xAccessorPrefix || ""}[*].{x: ${datum.xAccessor}, y: ${
datum.yAccessor
@@ -42,31 +43,36 @@ export class ChartBuilder {
// the preferred solution of size ref
const maxBubbleValue = 10;
projectedRows.forEach((row) => {
const series: Partial<Data> = ChartBuilder.getSeries(
datum,
row,
defaultSeries,
groupingDictionary
);
let series: Partial<Data>;

// Handle mutiple group by in the future
if (datum.groupBy && datum.groupBy.length > 0) {
const key = row.group;
if (key === undefined || key === null) {
if (defaultSeries === undefined) {
defaultSeries = ChartBuilder.buildDefaultSeries(datum);
}
series = defaultSeries;
} else {
if (groupingDictionary[key] === undefined) {
const temp = ChartBuilder.buildDefaultSeries(datum);
temp.name = key;
groupingDictionary[key] = temp;
}
series = groupingDictionary[key];
}
} else {
if (defaultSeries === undefined) {
defaultSeries = ChartBuilder.buildDefaultSeries(datum);
}
series = defaultSeries;
}

// Due to logging supporting heterogeneous metric types, a metric can be a scalar on one run and a vector on another
// Support these cases in the minimally surprising way by upcasting a scalar point to match the highest dimension for that row (like numpy does)
// If two arrays are logged, but of different lengths, pad the shorter ones with undefined to avoid series having different lengths concatted.
// We always have a size of at least one, this avoids corner case of one array being empty
let maxLength = 1;
let hasVectorValues = false;
if (Array.isArray(row.x)) {
hasVectorValues = true;
maxLength = Math.max(maxLength, row.x.length);
}
if (Array.isArray(row.y)) {
hasVectorValues = true;
maxLength = Math.max(maxLength, row.y.length);
}
if (Array.isArray(row.size)) {
hasVectorValues = true;
maxLength = Math.max(maxLength, row.size.length);
}
const { hasVectorValues, maxLength } = ChartBuilder.getHasVectors(row);
if (hasVectorValues) {
// for making scalars into a vector, fill the vector with that scalar value
if (!Array.isArray(row.x)) {
@@ -143,37 +149,27 @@ export class ChartBuilder {
return result;
}

private static getSeries(
datum: IData,
row: {
x: any;
y: any;
group: any;
size: any;
},
defaultSeries: Partial<Data>,
groupingDictionary: { [key: string]: Partial<Data> }
): Partial<Data> {
let series: Partial<Data>;

// Handle mutiple group by in the future
if (datum.groupBy && datum.groupBy.length > 0) {
const key = row.group;
if (!key) {
series = defaultSeries;
} else {
if (groupingDictionary[key] === undefined) {
const temp = ChartBuilder.buildDefaultSeries(datum);
temp.name = key;
groupingDictionary[key] = temp;
}
series = groupingDictionary[key];
}
} else {
series = defaultSeries;
private static getHasVectors(row: IRow): {
maxLength: number;
hasVectorValues: boolean;
} {
let maxLength = 1;
let hasVectorValues = false;
if (Array.isArray(row.x)) {
hasVectorValues = true;
maxLength = Math.max(maxLength, row.x.length);
}
return series;
if (Array.isArray(row.y)) {
hasVectorValues = true;
maxLength = Math.max(maxLength, row.y.length);
}
if (Array.isArray(row.size)) {
hasVectorValues = true;
maxLength = Math.max(maxLength, row.size.length);
}
return { hasVectorValues, maxLength };
}

private static buildDefaultSeries(datum: IData): Partial<Data> {
const series: Partial<Data> = _.cloneDeep(datum);
// defining an x/y accessor will overwrite any hardcoded x or y values.