Skip to content

Commit

Permalink
fix: bugfix for overlapping colors in color palette
Browse files Browse the repository at this point in the history
  • Loading branch information
jmbuss authored and diehbria committed Nov 21, 2023
1 parent 0620284 commit 7b4c95b
Show file tree
Hide file tree
Showing 33 changed files with 1,450 additions and 259 deletions.
988 changes: 918 additions & 70 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/core-util/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import 'jest-extended';
20 changes: 20 additions & 0 deletions packages/core-util/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { baseConfig } from '@iot-app-kit/jest-config';

const config = {
...baseConfig,
testEnvironment: 'jsdom',
coverageThreshold: {
/**
* starting low to account for untested code
* since this package wasn't a part of the test command
*/
global: {
statements: 54,
branches: 50,
functions: 51,
lines: 51,
},
},
};

export default config;
17 changes: 14 additions & 3 deletions packages/core-util/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,25 @@
"build:cjs": "tsc -p ./tsconfig.build.json --outDir ./dist/cjs --module CommonJS",
"clean": "rm -rf dist",
"lint": "eslint . --max-warnings=0",
"fix": "eslint --fix ."
"fix": "eslint --fix .",
"test": "npm run test:jest && npm run test:typescript",
"test:jest": "TZ=UTC jest",
"test:typescript": "tsc --noEmit"
},
"dependencies": {
"@aws-sdk/client-iot-events": "3.354.0",
"@aws-sdk/client-iotsitewise": "3.391.0",
"@iot-app-kit/core": "9.6.0"
"@iot-app-kit/core": "9.6.0",
"lodash.difference": "4.5.0"
},
"devDependencies": {
"@aws-sdk/types": "^3.310.0"
"@iot-app-kit/jest-config": "9.6.0",
"@iot-app-kit/ts-config": "9.6.0",
"@aws-sdk/types": "^3.310.0",
"@types/lodash.difference": "4.5.9",
"@types/jest": "^29.4.0",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"jest-extended": "^3.2.4"
}
}
2 changes: 1 addition & 1 deletion packages/core-util/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export { getSiteWiseClient } from './sdks/sitewise';
export { getIotEventsClient } from './sdks/events';
export { colorPalette } from './sdks/colorPalette';
export { assignDefaultColor } from './sdks/assignDefaultColors';
export { round } from './sdks/number';
export { isNumeric } from './sdks/number';
export { Colorizer } from './sdks/colorizer';
18 changes: 0 additions & 18 deletions packages/core-util/src/sdks/assignDefaultColors.ts

This file was deleted.

101 changes: 101 additions & 0 deletions packages/core-util/src/sdks/colorizer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { colorPalette } from './colorPalette';
import { Colorizer } from './colorizer';

it('returns colors in a rotation', () => {
const colorer = Colorizer(['red', 'white', 'blue']);
expect(colorer.next()).toEqual('red');
expect(colorer.next()).toEqual('white');
expect(colorer.next()).toEqual('blue');
});

it('applies colors in a rotation to a colorable object', () => {
const colorer = Colorizer(['red', 'white', 'blue']);
expect(colorer.nextApply({})).toEqual({ color: 'red' });
expect(colorer.nextApply({})).toEqual({ color: 'white' });
expect(colorer.nextApply({})).toEqual({ color: 'blue' });
});

it('does not repeat colors after 50 rotations', () => {
const colorer = Colorizer();
const colors: ReturnType<typeof colorer.next>[] = [];

const testColor = () => {
const color = colorer.next();
expect(color).toBeDefined();
expect(colors).not.toContain(color);
colors.push(color);
};

for (let i = 0; i < 50; i++) {
testColor();
}
});

it('can add a color(s) at the start of the rotation', () => {
const colorer = Colorizer();

const color = colorer.next();
expect(color).toBeDefined();

const nextColor = colorer.next();
expect(nextColor).not.toEqual(color);

// Need to check for typescript purposes
if (!color || !nextColor) throw new Error('Should not happen.');
colorer.add(color);

expect(colorer.next()).toEqual(color);

colorer.add([nextColor]);

expect(colorer.next()).toEqual(nextColor);
});

it('can remove a color(s) from the rotation', () => {
const colors = [...colorPalette];
let colorer = Colorizer(colors);
const thirdColorToRemove = colors.at(2);

// Need to check for typescript purposes
if (!thirdColorToRemove) throw new Error('Should not happen.');

colorer.remove(thirdColorToRemove);

for (let i = 0; i < colorPalette.length - 1; i++) {
expect(colorer.next()).not.toEqual(thirdColorToRemove);
}

colorer = Colorizer(colors);
// colors 3 - 5
const colorsToRemove = colors.slice(2, 5);

colorer.remove(colorsToRemove);

for (let i = 0; i < colorPalette.length - 3; i++) {
expect(colorer.next()).not.toEqual(thirdColorToRemove);
}
});

it('resets the rotation if all colors are removed', () => {
const palette = ['red', 'white', 'blue'];
const colorer = Colorizer(palette);
colorer.remove(palette);

expect(colorer.next()).toEqual('red');
expect(colorer.next()).toEqual('white');
expect(colorer.next()).toEqual('blue');
});

it('starts the rotation over once exhausted', () => {
const colors = [...colorPalette];
const colorer = Colorizer(colors);

const firstColor = colorer.next();

for (let i = 0; i < colorPalette.length - 1; i++) {
// exhaust rotation
colorer.next();
}

expect(colorer.next()).toEqual(firstColor);
});
74 changes: 74 additions & 0 deletions packages/core-util/src/sdks/colorizer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import difference from 'lodash.difference';
import { colorPalette } from './colorPalette';

type Colorable = { color?: string };

export const Colorizer = (palette: string[] = colorPalette) => {
let colors = [...palette];

/**
*
* Reset the color rotation back to the original state
*/
const reset = () => {
colors = [...palette];
};

/**
*
* @returns if the color rotation is empty
*/
const noColors = () => colors.length === 0;

/**
*
* @param color the color(s) you want to add to the front of the rotation to be used next
*/
const add = (color: string | string[]) => {
const toAdd = Array.isArray(color) ? color : [color];
colors = [...toAdd, ...colors];
};

/**
*
* @param color the color(s) you want to remove from the rotation so that they are not used in the rotation
*/
const remove = (color: string | string[]) => {
const toRemove = Array.isArray(color) ? color : [color];
colors = difference(colors, toRemove);
if (noColors()) reset();
};

/**
*
* @returns the next color in the rotation
*/
const next = () => {
if (noColors()) reset();

const nextColor = colors.at(0);
colors = colors.slice(1);

return nextColor;
};

/**
*
* @param item an object with a color property
* @returns the object with the color property set to the next color in the rotation
*/
const nextApply = <T extends Colorable>(item: T): T => {
const color = next();
return {
...item,
color,
};
};

return {
add,
remove,
next,
nextApply,
};
};
4 changes: 2 additions & 2 deletions packages/core-util/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"extends": "@iot-app-kit/ts-config/base.json",
"include": ["src"],
}

"files": ["global.d.ts"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,25 @@ import { styledQueryWidgetOnDrop } from '~/components/queryEditor/useQuery';
import { assignDefaultStyles } from '~/customization/widgets/utils/assignDefaultStyleSettings';
import { IoTSiteWiseDataStreamQuery } from '~/types';

const mergeAssetModelProperties = (
currentQuery: QueryConfigWidget['properties']['queryConfig']['query'],
updatedAssetModels: AssetModelQuery[] | undefined = []
) => {
const currentAssetModels = currentQuery?.assetModels ?? [];
return updatedAssetModels.map((assetModel) => ({
...assetModel,
properties: assetModel.properties.map((property) => {
const currentProperty = currentAssetModels
.find((currentAssetModel) => currentAssetModel.assetModelId === assetModel.assetModelId)
?.properties.find((currentAssetModelProperty) => currentAssetModelProperty.propertyId === property.propertyId);
return {
...currentProperty,
...property,
};
}),
}));
};

export const useModelBasedQuerySelection = () => {
const selection = useSelection({ filter: isQueryWidget });

Expand Down Expand Up @@ -41,7 +60,13 @@ export const useModelBasedQuerySelection = () => {
// handle styled widget
if (selectionType.value === 'xy-plot') {
const styledQuery = styledQueryWidgetOnDrop(
{ assetModels: updatedAssetModels },
{
...compositeWidgetForAggregationInformation.properties.queryConfig.query,
assetModels: mergeAssetModelProperties(
compositeWidgetForAggregationInformation.properties.queryConfig.query,
updatedAssetModels
),
},
compositeWidgetForAggregationInformation
);
updatedProperties = {
Expand All @@ -63,7 +88,10 @@ export const useModelBasedQuerySelection = () => {
...compositeWidgetForAggregationInformation.properties.queryConfig,
query: {
...compositeWidgetForAggregationInformation.properties.queryConfig.query,
assetModels: updatedAssetModels,
assetModels: mergeAssetModelProperties(
compositeWidgetForAggregationInformation.properties.queryConfig.query,
updatedAssetModels
),
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ export const defaultOnDeleteQuery: OnDeleteAssetQuery =

const properties = siteWiseAssetQuery?.properties?.filter((p) => p.propertyAlias !== propertyId) ?? [];

updateSiteWiseAssetQuery({ assets, properties });
updateSiteWiseAssetQuery({ ...siteWiseAssetQuery, assets, properties });
};
2 changes: 1 addition & 1 deletion packages/dashboard/src/customization/widgets/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export type SymbolStyles = {
export type AssetPropertyStyles = LineAndScatterStyles & { yAxis?: YAxisOptions };
export type StyledAssetPropertyQuery = AssetPropertyQuery & AssetPropertyStyles;
export type StyledAssetQuery = {
assets: {
assets?: {
assetId: SiteWiseAssetQuery['assets'][number]['assetId'];
properties: StyledAssetPropertyQuery[];
}[];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
import { assignDefaultColor } from '@iot-app-kit/core-util';
import type { IoTSiteWiseDataStreamQuery } from '~/types';
import { StyledAssetQuery } from '../../types';
import { colorerFromStyledQuery } from './defaultColors';

export const applyDefaultStylesToQuery = ({ assets = [], properties = [], assetModels = [] }: StyledAssetQuery) => {
const assignDefaultColor = colorerFromStyledQuery({ assets, assetModels, properties });

export const applyDefaultStylesToQuery = ({
assets = [],
properties = [],
assetModels = [],
}: IoTSiteWiseDataStreamQuery) => {
let offset = 0;
return {
assets: assets.map(({ properties, ...others }) => ({
...others,
properties: properties.map((propertyQuery) => assignDefaultColor(propertyQuery, offset++)),
properties: properties.map((propertyQuery) => assignDefaultColor(propertyQuery)),
})),
properties: properties.map((property, propertyIndex) => assignDefaultColor(property, propertyIndex + offset++)),
properties: properties.map((property) => assignDefaultColor(property)),
assetModels: assetModels.map(({ properties, ...others }) => ({
...others,
properties: properties.map((propertyQuery) => assignDefaultColor(propertyQuery, offset++)),
properties: properties.map((propertyQuery) => assignDefaultColor(propertyQuery)),
})),
};
};

0 comments on commit 7b4c95b

Please sign in to comment.