diff --git a/packages/components/src/components.d.ts b/packages/components/src/components.d.ts index a0ca19cc7..6163fd610 100644 --- a/packages/components/src/components.d.ts +++ b/packages/components/src/components.d.ts @@ -95,6 +95,7 @@ export namespace Components { interface IotTestRoutes { } interface IotTimeSeriesConnector { + "assignDefaultColors": boolean | undefined; "initialViewport": Viewport; "provider": Provider; "renderFunc": (data: TimeSeriesData) => void; @@ -317,6 +318,7 @@ declare namespace LocalJSX { interface IotTestRoutes { } interface IotTimeSeriesConnector { + "assignDefaultColors"?: boolean | undefined; "initialViewport"?: Viewport; "provider"?: Provider; "renderFunc"?: (data: TimeSeriesData) => void; diff --git a/packages/components/src/components/common/bindStylesToDataStreams.spec.ts b/packages/components/src/components/common/bindStylesToDataStreams.spec.ts index c96455680..b3955b225 100644 --- a/packages/components/src/components/common/bindStylesToDataStreams.spec.ts +++ b/packages/components/src/components/common/bindStylesToDataStreams.spec.ts @@ -1,9 +1,11 @@ import { bindStylesToDataStreams } from './bindStylesToDataStreams'; import { DATA_STREAM, DATA_STREAM_2 } from '../../testing/mockWidgetProperties'; +import { colorPalette } from './colorPalette'; it('returns empty array when provided no data streams', () => { expect( bindStylesToDataStreams({ + assignDefaultColors: false, dataStreams: [], styleSettings: { someStyle: { color: 'red' } }, }) @@ -13,6 +15,7 @@ it('returns empty array when provided no data streams', () => { it('returns data streams when no matching styles', () => { expect( bindStylesToDataStreams({ + assignDefaultColors: false, dataStreams: [DATA_STREAM], styleSettings: { someStyle: { color: 'red' } }, }) @@ -22,6 +25,7 @@ it('returns data streams when no matching styles', () => { it('associates styles to corresponding data stream', () => { expect( bindStylesToDataStreams({ + assignDefaultColors: false, dataStreams: [{ ...DATA_STREAM, refId: 'someStyle' }], styleSettings: { someStyle: { color: 'red' } }, }) @@ -31,6 +35,7 @@ it('associates styles to corresponding data stream', () => { it('associates styles to corresponding data stream for multiple data streams', () => { expect( bindStylesToDataStreams({ + assignDefaultColors: false, dataStreams: [ { ...DATA_STREAM, refId: 'someStyle' }, { ...DATA_STREAM_2, refId: 'someStyle2' }, @@ -46,8 +51,51 @@ it('associates styles to corresponding data stream for multiple data streams', ( it('returns data stream when no matching refId', () => { expect( bindStylesToDataStreams({ + assignDefaultColors: false, dataStreams: [{ ...DATA_STREAM, refId: 'someStyle100' }], styleSettings: { someStyle: { color: 'red' } }, }) ).toEqual([{ ...DATA_STREAM, refId: 'someStyle100' }]); }); + +describe('assignDefaultColors', () => { + it('does not assign color when set to false', () => { + expect( + bindStylesToDataStreams({ + assignDefaultColors: false, + dataStreams: [{ ...DATA_STREAM, color: undefined }], + styleSettings: {}, + }) + ).toEqual([expect.objectContaining({ color: undefined })]); + }); + + it('does not assign color when color on styleSettings is specified', () => { + expect( + bindStylesToDataStreams({ + assignDefaultColors: true, + dataStreams: [{ ...DATA_STREAM, refId: 'someStyle', color: undefined }], + styleSettings: { someStyle: { color: 'some-color' } }, + }) + ).toEqual([expect.objectContaining({ color: 'some-color' })]); + }); + + it('does not assign color when color on datastream', () => { + expect( + bindStylesToDataStreams({ + assignDefaultColors: true, + dataStreams: [{ ...DATA_STREAM, color: 'some-color' }], + styleSettings: {}, + }) + ).toEqual([expect.objectContaining({ color: 'some-color' })]); + }); + + it('does assign color when no color specified by data stream or style setting', () => { + expect( + bindStylesToDataStreams({ + assignDefaultColors: true, + dataStreams: [{ ...DATA_STREAM, color: undefined }], + styleSettings: {}, + }) + ).toEqual([expect.objectContaining({ color: colorPalette[0] })]); + }); +}); diff --git a/packages/components/src/components/common/bindStylesToDataStreams.ts b/packages/components/src/components/common/bindStylesToDataStreams.ts index 5315e649a..bda284391 100644 --- a/packages/components/src/components/common/bindStylesToDataStreams.ts +++ b/packages/components/src/components/common/bindStylesToDataStreams.ts @@ -1,14 +1,39 @@ import { StyleSettingsMap, DataStream } from '@iot-app-kit/core'; +import { colorPalette } from './colorPalette'; + +const assignDefaultColor = ({ + dataStream, + index, + styleSettings, +}: { + dataStream: DataStream; + index: number; + styleSettings?: StyleSettingsMap; +}): DataStream => { + const associatedStyles = dataStream.refId != null && styleSettings != null ? styleSettings[dataStream.refId] : {}; + const hasAssociatedColor = 'color' in associatedStyles; + + // Only provide default if one is not already present in the data stream, and none is specified in the associated style settings. + if (dataStream.color == null && !hasAssociatedColor) { + return { + ...dataStream, + color: colorPalette[index % colorPalette.length], + }; + } + return dataStream; +}; // If the data stream has a reference id with associated styles, append those styles to the data stream. export const bindStylesToDataStreams = ({ dataStreams, styleSettings, + assignDefaultColors, }: { dataStreams: DataStream[]; + assignDefaultColors: boolean; styleSettings?: StyleSettingsMap; -}): DataStream[] => - dataStreams.map((dataStream) => +}): DataStream[] => { + const streams = dataStreams.map((dataStream, i) => styleSettings == null || dataStream.refId == null ? dataStream : { @@ -16,3 +41,8 @@ export const bindStylesToDataStreams = ({ ...styleSettings[dataStream.refId], } ); + + return assignDefaultColors + ? streams.map((dataStream, index) => assignDefaultColor({ dataStream, index, styleSettings })) + : streams; +}; diff --git a/packages/components/src/components/common/colorPalette.ts b/packages/components/src/components/common/colorPalette.ts new file mode 100644 index 000000000..d33b23d90 --- /dev/null +++ b/packages/components/src/components/common/colorPalette.ts @@ -0,0 +1,16 @@ +// CSS Color strings, in default order they'll be used. +export const colorPalette = [ + '#5e87b5', + '#e6ac8c', + '#7fc6b1', + '#d99090', + '#ae779c', + '#f9da95', + '#b088f5', + '#c55305', + '#018574', + '#486de8', + '#962249', + '#096f64', + '#8456ce', +]; diff --git a/packages/components/src/components/iot-bar-chart/iot-bar-chart.tsx b/packages/components/src/components/iot-bar-chart/iot-bar-chart.tsx index 602cd9975..793da2e8d 100644 --- a/packages/components/src/components/iot-bar-chart/iot-bar-chart.tsx +++ b/packages/components/src/components/iot-bar-chart/iot-bar-chart.tsx @@ -81,6 +81,7 @@ export class IotBarChart { ( { return ( { return ( ( { viewport, }); }); + +it('when assignDefaultColors is true, provides a default color', async () => { + const renderFunc = jest.fn(); + const { assetId, propertyId } = toSiteWiseAssetProperty(DATA_STREAM.id); + const REF_ID = 'some-ref-id'; + + const { query } = initialize({ + iotSiteWiseClient: mockSiteWiseSDK, + }); + + await connectorSpecPage({ + renderFunc, + provider: query + .timeSeriesData({ assets: [{ assetId, properties: [{ propertyId, refId: REF_ID }] }] }) + .build('widget-id', { viewport, settings: { fetchMostRecentBeforeEnd: true } }), + assignDefaultColors: true, + }); + + expect(renderFunc).lastCalledWith({ + dataStreams: [ + expect.objectContaining({ + color: colorPalette[0], + }), + ], + viewport, + }); +}); diff --git a/packages/components/src/components/iot-time-series-connector/iot-time-series-connector.tsx b/packages/components/src/components/iot-time-series-connector/iot-time-series-connector.tsx index d9658064f..d37ba2c1f 100644 --- a/packages/components/src/components/iot-time-series-connector/iot-time-series-connector.tsx +++ b/packages/components/src/components/iot-time-series-connector/iot-time-series-connector.tsx @@ -26,6 +26,8 @@ export class IotTimeSeriesConnector { @Prop() styleSettings: StyleSettingsMap | undefined; + @Prop() assignDefaultColors: boolean | undefined; + @State() data: TimeSeriesData = { dataStreams: [], viewport: DEFAULT_VIEWPORT, @@ -60,7 +62,11 @@ export class IotTimeSeriesConnector { } = this; return this.renderFunc({ - dataStreams: bindStylesToDataStreams({ dataStreams, styleSettings: this.styleSettings }), + dataStreams: bindStylesToDataStreams({ + dataStreams, + styleSettings: this.styleSettings, + assignDefaultColors: this.assignDefaultColors || false, + }), viewport, }); }