Skip to content

Commit

Permalink
feat: add gauge component to dashboard
Browse files Browse the repository at this point in the history
  • Loading branch information
gmuralidharreddy committed May 3, 2024
1 parent 785a1cd commit 128cb18
Show file tree
Hide file tree
Showing 17 changed files with 266 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,23 @@ export const disableAdd = (
}
break;
}
case 'gauge': {
const modeledProperties =
get(selectedWidget, 'properties.queryConfig.query.assets') ?? [];
const unmodeledProperties =
get(selectedWidget, 'properties.queryConfig.query.properties') ?? [];
const assetModelProperties =
get(selectedWidget, 'properties.queryConfig.query.assetModels') ?? [];
if (
modeledProperties.length ||
unmodeledProperties.length ||
assetModelProperties.length ||
collectionPropsLength !== 1
) {
widgetBasedDisable = true;
}
break;
}
default:
}
return (
Expand Down
4 changes: 4 additions & 0 deletions packages/dashboard/src/customization/widgets/appKitPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ import { kpiPlugin } from './kpi/plugin';
import { statusTimelineChartPlugin } from './status-timeline/statusTimelinePlugin';
import { tablePlugin } from './table/plugin';
import { statusPlugin } from './status/plugin';
import { gaugePlugin } from './gauge/plugin';

const hasGauge = !!localStorage.getItem('HAS_GAUGE_WIDGET');

export const appKitPlugin: DashboardPlugin = {
install: (options) => {
lineScatterChartPlugin.install(options);
barChartPlugin.install(options);
statusTimelineChartPlugin.install(options);
kpiPlugin.install(options);
hasGauge && gaugePlugin.install(options);
statusPlugin.install(options);
tablePlugin.install(options);
},
Expand Down
14 changes: 14 additions & 0 deletions packages/dashboard/src/customization/widgets/gauge/component.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.gauge-widget-empty-state {
display: flex;
flex-direction: column;
height: 100%;
}

.gauge-widget-empty-state-message-container {
align-items: center;
display: flex;
flex-direction: column;
flex-grow: 1;
justify-content: center;
text-align: center;
}
105 changes: 105 additions & 0 deletions packages/dashboard/src/customization/widgets/gauge/component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React from 'react';
import { useSelector } from 'react-redux';
import pickBy from 'lodash/pickBy';
import { Gauge, useViewport } from '@iot-app-kit/react-components';
import { createWidgetRenderKey } from '../utils/createWidgetRenderKey';
import type { DashboardState } from '~/store/state';
import type { GaugeWidget } from '../types';
import { Box } from '@cloudscape-design/components';
import { useQueries } from '~/components/dashboard/queryContext';
import { isDefined } from '~/util/isDefined';

import WidgetTile from '~/components/widgets/tile';
import { useChartSize } from '~/hooks/useChartSize';
import './component.css';

const GaugeWidgetComponent: React.FC<GaugeWidget> = (widget) => {
const { viewport } = useViewport();
const dashboardSignificantDigits = useSelector(
(state: DashboardState) => state.significantDigits
);

const {
styleSettings,
queryConfig,
showUnit,
showName,
thresholds,
significantDigits: widgetSignificantDigits,
yMin,
yMax,
} = widget.properties;

const queries = useQueries(queryConfig.query);
const key = createWidgetRenderKey(widget.id);
const chartSize = useChartSize(widget);

const shouldShowEmptyState = queries.length === 0;

if (shouldShowEmptyState) {
return (
<WidgetTile widget={widget}>
<GaugeWidgetEmptyStateComponent />
</WidgetTile>
);
}

const settings = pickBy(
{
yMin,
yMax,
showName,
showUnit,
},
isDefined
);

const significantDigits =
widgetSignificantDigits ?? dashboardSignificantDigits;

const size = { width: chartSize.width - 8, height: chartSize.height - 8 };

return (
<WidgetTile widget={widget} key={key}>
<Gauge
size={size}
query={queries[0]}
viewport={viewport}
styles={styleSettings}
settings={settings}
thresholds={thresholds}
significantDigits={significantDigits}
/>
</WidgetTile>
);
};

const GaugeWidgetEmptyStateComponent: React.FC = () => {
return (
<div className='gauge-widget-empty-state'>
<Box variant='strong' color='text-status-inactive' margin='s'>
Gauge
</Box>

<div className='gauge-widget-empty-state-message-container'>
<Box
variant='strong'
color='text-status-inactive'
margin={{ horizontal: 's' }}
>
No properties or alarms
</Box>

<Box
variant='p'
color='text-status-inactive'
margin={{ bottom: 's', horizontal: 's' }}
>
Add a property or alarm to populate Gauge
</Box>
</div>
</div>
);
};

export default GaugeWidgetComponent;
12 changes: 12 additions & 0 deletions packages/dashboard/src/customization/widgets/gauge/gauge-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions packages/dashboard/src/customization/widgets/gauge/gauge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions packages/dashboard/src/customization/widgets/gauge/icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import { default as GaugeSvg } from './gauge.svg';
import { default as GaugeSvgDark } from './gauge-dark.svg';
import WidgetIcon from '../components/widgetIcon';

const GaugeIcon = () => {
return (
<WidgetIcon widget='gauge' defaultIcon={GaugeSvg} darkIcon={GaugeSvgDark} />
);
};

export default GaugeIcon;
33 changes: 33 additions & 0 deletions packages/dashboard/src/customization/widgets/gauge/plugin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import GaugeWidgetComponent from './component';
import GaugeIcon from './icon';
import type { DashboardPlugin } from '~/customization/api';
import type { GaugeWidget } from '../types';
import { WIDGET_INITIAL_HEIGHT, WIDGET_INITIAL_WIDTH } from '../constants';

export const gaugePlugin: DashboardPlugin = {
install: ({ registerWidget }) => {
registerWidget<GaugeWidget>('gauge', {
render: (widget) => <GaugeWidgetComponent {...widget} />,
componentLibrary: {
name: 'Gauge',
icon: GaugeIcon,
},
properties: () => ({
queryConfig: {
source: 'iotsitewise',
query: undefined,
},
showName: true,
showUnit: true,
yMin: 0,
yMax: 100,
thresholds: [],
}),
initialSize: {
height: WIDGET_INITIAL_HEIGHT,
width: WIDGET_INITIAL_WIDTH,
},
});
},
};
1 change: 1 addition & 0 deletions packages/dashboard/src/customization/widgets/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './barChart/plugin';
export * from './kpi/plugin';
export * from './gauge/plugin';
export * from './status-timeline/statusTimelinePlugin';
export * from './table/plugin';
export * from './text/plugin';
Expand Down
14 changes: 14 additions & 0 deletions packages/dashboard/src/customization/widgets/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,19 @@ export type LineProperties = {
thickness?: number;
};

export type GaugeProperties = QueryProperties & {
gaugeThickness?: number;
showName?: boolean;
showUnit?: boolean;
fontSize?: number;
labelFontSize?: number;
unitFontSize?: number;
yMin?: number;
yMax?: number;
thresholds?: StyledThreshold[];
significantDigits?: number;
};

type ChartPropertiesUnion =
| KPIProperties
| StatusProperties
Expand All @@ -256,6 +269,7 @@ export type CommonChartProperties = Pick<

export type QueryWidget = DashboardWidget<QueryProperties>;

export type GaugeWidget = DashboardWidget<GaugeProperties>;
export type KPIWidget = DashboardWidget<KPIProperties>;
export type StatusWidget = DashboardWidget<StatusProperties>;
export type LineScatterChartWidget =
Expand Down
12 changes: 6 additions & 6 deletions packages/react-components/src/components/gauge/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ export const DEFAULT_GAUGE_SETTINGS = {
series: [
{
type: 'gauge',
center: ['50%', '50%'], //center of the gauge
radius: '84%', //radius of the gauge
center: ['50%', '75%'], //The first value '50%' is used to horizontally center the component, while the second value '75%' is used to vertically position the component starting from the top.
radius: '120%', //radius of the gauge
startAngle: 190, //start angle of the gauge in degrees
endAngle: -10, //end angle of the gauge in degrees
min: 0, //guage default start point
Expand Down Expand Up @@ -36,8 +36,8 @@ export const DEFAULT_GAUGE_PROGRESS_SETTINGS = {
...DEFAULT_GAUGE_SETTINGS.series,
{
type: 'gauge',
center: ['50%', '50%'], //center of the gauge progress bar
radius: '84%', //radius of the gauge progress bar
center: ['50%', '75%'], //The first value '50%' is used to horizontally center the component, while the second value '75%' is used to vertically position the component starting from the top.
radius: '120%', //radius of the gauge progress bar
startAngle: 190, //start angle of the gauge in degrees progress bar
endAngle: -10, //end angle of the gauge in degrees progress bar
silent: true, //control mouse hover actions
Expand Down Expand Up @@ -103,8 +103,8 @@ export const DEFAULT_GAUGE_PROGRESS_SETTINGS_WITH_THRESHOLDS = {
...DEFAULT_GAUGE_PROGRESS_SETTINGS.series,
{
type: 'gauge',
center: ['50%', '50%'], //center of the gauge outside the arc
radius: '86%', //radius of the gauge outside the arc
center: ['50%', '75%'], //The first value '50%' is used to horizontally center the component, while the second value '75%' is used to vertically position the component starting from the top.
radius: '122%', //radius of the gauge outside the arc
startAngle: 190, //start angle of the gauge in degrees
endAngle: -10, //end angle of the gauge in degrees
min: 0, //outside the arc default start point
Expand Down
2 changes: 2 additions & 0 deletions packages/react-components/src/components/gauge/gauge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { DataStream } from '@iot-app-kit/core';
import { DEFAULT_GAUGE_STYLES } from './constants';

export const Gauge = ({
size,
query,
viewport: passedInViewport,
thresholds = [],
Expand Down Expand Up @@ -47,6 +48,7 @@ export const Gauge = ({

return (
<GaugeBase
size={size}
propertyPoint={propertyPoint}
name={name}
unit={unit}
Expand Down
21 changes: 15 additions & 6 deletions packages/react-components/src/components/gauge/gaugeBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from 'react';
import { useECharts, useLoadableEChart } from '../../hooks/useECharts';
import { GaugeBaseProperties } from './types';
import { useGaugeConfiguration } from './hooks/useGaugeConfiguration';
import { useResizableGauge } from './hooks/useResizableGauge';
import { GaugeErrorText } from './gaugeErrorText';
import { GaugeDataQualityText } from './gaugeDataQualityText';
import './gauge.css';
Expand All @@ -21,6 +22,7 @@ import './gauge.css';
* @return {ReactElement} The rendered gauge component.
*/
export const GaugeBase: React.FC<GaugeBaseProperties> = ({
size,
propertyPoint,
thresholds = [],
settings,
Expand Down Expand Up @@ -51,20 +53,27 @@ export const GaugeBase: React.FC<GaugeBaseProperties> = ({
error,
});

// resize on widget resize
useResizableGauge(chartRef, size);

return (
<div
className='gauge-base-container gauge-base'
data-testid={
!error ? 'gauge-base-component' : 'gauge-base-component-error'
}
>
<div ref={ref} className='gauge-base' />
<GaugeErrorText error={error} />
<GaugeDataQualityText
error={error}
quality={quality}
showName={settings?.showName}
<div
ref={ref}
className='gauge-base'
data-testid='kpi-name-and-unit'
style={{
width: size?.width,
height: size?.height,
}}
/>
<GaugeErrorText error={error} />
{!isLoading && <GaugeDataQualityText error={error} quality={quality} />}
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,15 @@ import { DataQualityText } from '../data-quality/data-quality-text';
export const GaugeDataQualityText = ({
error,
quality,
showName,
}: {
error?: string;
quality?: Quality;
showName?: boolean;
}) => {
if (error || !quality) {
return null;
}
return (
<div
className='gauge-data-quality'
style={{ bottom: showName ? '36%' : '40%' }}
>
<div className='gauge-data-quality' style={{ bottom: '2%' }}>
<div className='gauge-info-text'>
<DataQualityText quality={quality} />
</div>
Expand Down
Loading

0 comments on commit 128cb18

Please sign in to comment.