mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 08:35:54 +08:00
feat: add histogram visualisation support (#4858)
* refactor: added panel type histogram and basic setup done * feat: histogram for one query * refactor: multiple query support histogram * chore: typecorrection * refactor: done with legend part * refactor: legend change fix * refactor: fix the tooltip value for histogram * refactor: disable drag for histogram * fix: tsc * refactor: handled panel type change in edit mode * refactor: full view done (#4881) Co-authored-by: Rajat-Dabade <rajat@signoz.io> * [Feat]: Full view histogram (#4867) * refactor: added panel type histogram and basic setup done * feat: histogram for one query * refactor: multiple query support histogram * chore: typecorrection * refactor: done with legend part * refactor: legend change fix * refactor: fix the tooltip value for histogram * refactor: disable drag for histogram * fix: tsc * refactor: handled panel type change in edit mode * refactor: full view done --------- Co-authored-by: Rajat-Dabade <rajat@signoz.io> Co-authored-by: Yunus M <myounis.ar@live.com> * feat: histogram customisations (#5133) * feat: added bucket size and bucket width enhancements for histogram * fix: added handling for bucket size bucket count and combine into one series * fix: added bidirectional sync * fix: remove extra props from interfaces * fix: hide legends when merging all queries * fix: minor legend fixes * fix: build issues --------- Co-authored-by: Rajat-Dabade <rajat@signoz.io> Co-authored-by: Yunus M <myounis.ar@live.com> Co-authored-by: Vikrant Gupta <vikrant.thomso@gmail.com>
This commit is contained in:
parent
b39f703919
commit
7e9bf2d48d
@ -30,6 +30,7 @@ export const getComponentForPanelType = (
|
|||||||
dataSource === DataSource.LOGS ? LogsPanelComponent : TracesTableComponent,
|
dataSource === DataSource.LOGS ? LogsPanelComponent : TracesTableComponent,
|
||||||
[PANEL_TYPES.BAR]: Uplot,
|
[PANEL_TYPES.BAR]: Uplot,
|
||||||
[PANEL_TYPES.PIE]: null,
|
[PANEL_TYPES.PIE]: null,
|
||||||
|
[PANEL_TYPES.HISTOGRAM]: Uplot,
|
||||||
[PANEL_TYPES.EMPTY_WIDGET]: null,
|
[PANEL_TYPES.EMPTY_WIDGET]: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -286,6 +286,7 @@ export enum PANEL_TYPES {
|
|||||||
TRACE = 'trace',
|
TRACE = 'trace',
|
||||||
BAR = 'bar',
|
BAR = 'bar',
|
||||||
PIE = 'pie',
|
PIE = 'pie',
|
||||||
|
HISTOGRAM = 'histogram',
|
||||||
EMPTY_WIDGET = 'EMPTY_WIDGET',
|
EMPTY_WIDGET = 'EMPTY_WIDGET',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,11 +159,14 @@ export function BillingUsageGraph(props: BillingUsageGraphProps): JSX.Element {
|
|||||||
},
|
},
|
||||||
padding: [32, 32, 16, 16],
|
padding: [32, 32, 16, 16],
|
||||||
plugins: [
|
plugins: [
|
||||||
tooltipPlugin(
|
tooltipPlugin({
|
||||||
fillMissingValuesForQuantities(graphCompatibleData, chartData[0]),
|
apiResponse: fillMissingValuesForQuantities(
|
||||||
'',
|
graphCompatibleData,
|
||||||
true,
|
chartData[0],
|
||||||
),
|
),
|
||||||
|
yAxisUnit: '',
|
||||||
|
isBillingUsageGraphs: true,
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
|
@ -17,6 +17,10 @@
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.height-widget {
|
||||||
|
height: calc(100% - 40px);
|
||||||
|
}
|
||||||
|
|
||||||
.list-graph-container {
|
.list-graph-container {
|
||||||
height: calc(100% - 40px);
|
height: calc(100% - 40px);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
@ -27,5 +27,6 @@ export const PANEL_TYPES_VS_FULL_VIEW_TABLE: PanelTypeAndGraphManagerVisibilityP
|
|||||||
TRACE: false,
|
TRACE: false,
|
||||||
BAR: true,
|
BAR: true,
|
||||||
PIE: false,
|
PIE: false,
|
||||||
|
HISTOGRAM: false,
|
||||||
EMPTY_WIDGET: false,
|
EMPTY_WIDGET: false,
|
||||||
};
|
};
|
||||||
|
@ -204,6 +204,7 @@ function FullView({
|
|||||||
<div
|
<div
|
||||||
className={cx('graph-container', {
|
className={cx('graph-container', {
|
||||||
disabled: isDashboardLocked,
|
disabled: isDashboardLocked,
|
||||||
|
'height-widget': widget?.mergeAllActiveQueries,
|
||||||
'list-graph-container': isListView,
|
'list-graph-container': isListView,
|
||||||
})}
|
})}
|
||||||
ref={fullViewRef}
|
ref={fullViewRef}
|
||||||
|
@ -49,6 +49,7 @@ const GridPanelSwitch = forwardRef<
|
|||||||
options,
|
options,
|
||||||
ref,
|
ref,
|
||||||
},
|
},
|
||||||
|
[PANEL_TYPES.HISTOGRAM]: null,
|
||||||
[PANEL_TYPES.EMPTY_WIDGET]: null,
|
[PANEL_TYPES.EMPTY_WIDGET]: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -43,5 +43,6 @@ export type PropsTypePropsMap = {
|
|||||||
[PANEL_TYPES.BAR]: UplotProps & {
|
[PANEL_TYPES.BAR]: UplotProps & {
|
||||||
ref: ForwardedRef<ToggleGraphProps | undefined>;
|
ref: ForwardedRef<ToggleGraphProps | undefined>;
|
||||||
};
|
};
|
||||||
|
[PANEL_TYPES.HISTOGRAM]: null;
|
||||||
[PANEL_TYPES.EMPTY_WIDGET]: null;
|
[PANEL_TYPES.EMPTY_WIDGET]: null;
|
||||||
};
|
};
|
||||||
|
@ -11,6 +11,7 @@ export const PANEL_TYPES_INITIAL_QUERY = {
|
|||||||
[PANEL_TYPES.TRACE]: initialQueriesMap.traces,
|
[PANEL_TYPES.TRACE]: initialQueriesMap.traces,
|
||||||
[PANEL_TYPES.BAR]: initialQueriesMap.metrics,
|
[PANEL_TYPES.BAR]: initialQueriesMap.metrics,
|
||||||
[PANEL_TYPES.PIE]: initialQueriesMap.metrics,
|
[PANEL_TYPES.PIE]: initialQueriesMap.metrics,
|
||||||
|
[PANEL_TYPES.HISTOGRAM]: initialQueriesMap.metrics,
|
||||||
[PANEL_TYPES.EMPTY_WIDGET]: initialQueriesMap.metrics,
|
[PANEL_TYPES.EMPTY_WIDGET]: initialQueriesMap.metrics,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -40,6 +40,11 @@ const Items: ItemsProps[] = [
|
|||||||
icon: <PieChart size={16} color={Color.BG_ROBIN_400} />,
|
icon: <PieChart size={16} color={Color.BG_ROBIN_400} />,
|
||||||
display: 'Pie',
|
display: 'Pie',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: PANEL_TYPES.HISTOGRAM,
|
||||||
|
icon: <BarChart3 size={16} color={Color.BG_ROBIN_400} />,
|
||||||
|
display: 'Histogram',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface ItemsProps {
|
export interface ItemsProps {
|
||||||
|
@ -428,9 +428,12 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
|||||||
{!isEmpty(description) && (
|
{!isEmpty(description) && (
|
||||||
<section className="dashboard-description-section">{description}</section>
|
<section className="dashboard-description-section">{description}</section>
|
||||||
)}
|
)}
|
||||||
<section className="dashboard-variables">
|
|
||||||
<DashboardVariableSelection />
|
{!isEmpty(selectedData.variables) && (
|
||||||
</section>
|
<section className="dashboard-variables">
|
||||||
|
<DashboardVariableSelection />
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
<DashboardGraphSlider />
|
<DashboardGraphSlider />
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
|
@ -240,6 +240,62 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bucket-config {
|
||||||
|
margin-top: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-family: 'Space Mono';
|
||||||
|
font-size: 13px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 18px; /* 138.462% */
|
||||||
|
letter-spacing: 0.52px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bucket-size-label {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bucket-input {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 32px;
|
||||||
|
padding: 6px 6px 6px 8px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
align-self: stretch;
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid var(--bg-slate-400);
|
||||||
|
background: var(--bg-ink-300);
|
||||||
|
|
||||||
|
.ant-input {
|
||||||
|
background: var(--bg-ink-300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.combine-hist {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 8px;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-family: 'Space Mono';
|
||||||
|
font-size: 13px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 18px; /* 138.462% */
|
||||||
|
letter-spacing: 0.52px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.alerts {
|
.alerts {
|
||||||
|
@ -28,6 +28,7 @@ export const panelTypeVsThreshold: { [key in PANEL_TYPES]: boolean } = {
|
|||||||
[PANEL_TYPES.LIST]: false,
|
[PANEL_TYPES.LIST]: false,
|
||||||
[PANEL_TYPES.PIE]: false,
|
[PANEL_TYPES.PIE]: false,
|
||||||
[PANEL_TYPES.BAR]: true,
|
[PANEL_TYPES.BAR]: true,
|
||||||
|
[PANEL_TYPES.HISTOGRAM]: false,
|
||||||
[PANEL_TYPES.TRACE]: false,
|
[PANEL_TYPES.TRACE]: false,
|
||||||
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||||
} as const;
|
} as const;
|
||||||
@ -39,6 +40,7 @@ export const panelTypeVsSoftMinMax: { [key in PANEL_TYPES]: boolean } = {
|
|||||||
[PANEL_TYPES.LIST]: false,
|
[PANEL_TYPES.LIST]: false,
|
||||||
[PANEL_TYPES.PIE]: false,
|
[PANEL_TYPES.PIE]: false,
|
||||||
[PANEL_TYPES.BAR]: true,
|
[PANEL_TYPES.BAR]: true,
|
||||||
|
[PANEL_TYPES.HISTOGRAM]: false,
|
||||||
[PANEL_TYPES.TRACE]: false,
|
[PANEL_TYPES.TRACE]: false,
|
||||||
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||||
} as const;
|
} as const;
|
||||||
@ -50,6 +52,7 @@ export const panelTypeVsDragAndDrop: { [key in PANEL_TYPES]: boolean } = {
|
|||||||
[PANEL_TYPES.PIE]: false,
|
[PANEL_TYPES.PIE]: false,
|
||||||
[PANEL_TYPES.LIST]: false,
|
[PANEL_TYPES.LIST]: false,
|
||||||
[PANEL_TYPES.BAR]: false,
|
[PANEL_TYPES.BAR]: false,
|
||||||
|
[PANEL_TYPES.HISTOGRAM]: false,
|
||||||
[PANEL_TYPES.TRACE]: false,
|
[PANEL_TYPES.TRACE]: false,
|
||||||
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||||
} as const;
|
} as const;
|
||||||
@ -61,6 +64,7 @@ export const panelTypeVsFillSpan: { [key in PANEL_TYPES]: boolean } = {
|
|||||||
[PANEL_TYPES.LIST]: false,
|
[PANEL_TYPES.LIST]: false,
|
||||||
[PANEL_TYPES.PIE]: false,
|
[PANEL_TYPES.PIE]: false,
|
||||||
[PANEL_TYPES.BAR]: false,
|
[PANEL_TYPES.BAR]: false,
|
||||||
|
[PANEL_TYPES.HISTOGRAM]: false,
|
||||||
[PANEL_TYPES.TRACE]: false,
|
[PANEL_TYPES.TRACE]: false,
|
||||||
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||||
} as const;
|
} as const;
|
||||||
@ -72,6 +76,7 @@ export const panelTypeVsYAxisUnit: { [key in PANEL_TYPES]: boolean } = {
|
|||||||
[PANEL_TYPES.LIST]: false,
|
[PANEL_TYPES.LIST]: false,
|
||||||
[PANEL_TYPES.PIE]: false,
|
[PANEL_TYPES.PIE]: false,
|
||||||
[PANEL_TYPES.BAR]: true,
|
[PANEL_TYPES.BAR]: true,
|
||||||
|
[PANEL_TYPES.HISTOGRAM]: false,
|
||||||
[PANEL_TYPES.TRACE]: false,
|
[PANEL_TYPES.TRACE]: false,
|
||||||
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||||
} as const;
|
} as const;
|
||||||
@ -83,6 +88,19 @@ export const panelTypeVsCreateAlert: { [key in PANEL_TYPES]: boolean } = {
|
|||||||
[PANEL_TYPES.LIST]: false,
|
[PANEL_TYPES.LIST]: false,
|
||||||
[PANEL_TYPES.PIE]: false,
|
[PANEL_TYPES.PIE]: false,
|
||||||
[PANEL_TYPES.BAR]: true,
|
[PANEL_TYPES.BAR]: true,
|
||||||
|
[PANEL_TYPES.HISTOGRAM]: false,
|
||||||
|
[PANEL_TYPES.TRACE]: false,
|
||||||
|
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const panelTypeVsBucketConfig: { [key in PANEL_TYPES]: boolean } = {
|
||||||
|
[PANEL_TYPES.TIME_SERIES]: false,
|
||||||
|
[PANEL_TYPES.VALUE]: false,
|
||||||
|
[PANEL_TYPES.TABLE]: false,
|
||||||
|
[PANEL_TYPES.LIST]: false,
|
||||||
|
[PANEL_TYPES.PIE]: false,
|
||||||
|
[PANEL_TYPES.BAR]: false,
|
||||||
|
[PANEL_TYPES.HISTOGRAM]: true,
|
||||||
[PANEL_TYPES.TRACE]: false,
|
[PANEL_TYPES.TRACE]: false,
|
||||||
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||||
} as const;
|
} as const;
|
||||||
@ -96,6 +114,7 @@ export const panelTypeVsPanelTimePreferences: {
|
|||||||
[PANEL_TYPES.LIST]: false,
|
[PANEL_TYPES.LIST]: false,
|
||||||
[PANEL_TYPES.PIE]: true,
|
[PANEL_TYPES.PIE]: true,
|
||||||
[PANEL_TYPES.BAR]: true,
|
[PANEL_TYPES.BAR]: true,
|
||||||
|
[PANEL_TYPES.HISTOGRAM]: false,
|
||||||
[PANEL_TYPES.TRACE]: false,
|
[PANEL_TYPES.TRACE]: false,
|
||||||
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||||
} as const;
|
} as const;
|
||||||
@ -110,5 +129,6 @@ export const panelTypeVsColumnUnitPreferences: {
|
|||||||
[PANEL_TYPES.PIE]: false,
|
[PANEL_TYPES.PIE]: false,
|
||||||
[PANEL_TYPES.BAR]: false,
|
[PANEL_TYPES.BAR]: false,
|
||||||
[PANEL_TYPES.TRACE]: false,
|
[PANEL_TYPES.TRACE]: false,
|
||||||
|
[PANEL_TYPES.HISTOGRAM]: false,
|
||||||
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -23,6 +23,7 @@ import { DataSource } from 'types/common/queryBuilder';
|
|||||||
|
|
||||||
import { ColumnUnitSelector } from './ColumnUnitSelector/ColumnUnitSelector';
|
import { ColumnUnitSelector } from './ColumnUnitSelector/ColumnUnitSelector';
|
||||||
import {
|
import {
|
||||||
|
panelTypeVsBucketConfig,
|
||||||
panelTypeVsColumnUnitPreferences,
|
panelTypeVsColumnUnitPreferences,
|
||||||
panelTypeVsCreateAlert,
|
panelTypeVsCreateAlert,
|
||||||
panelTypeVsFillSpan,
|
panelTypeVsFillSpan,
|
||||||
@ -45,12 +46,18 @@ function RightContainer({
|
|||||||
setTitle,
|
setTitle,
|
||||||
title,
|
title,
|
||||||
selectedGraph,
|
selectedGraph,
|
||||||
|
bucketCount,
|
||||||
|
bucketWidth,
|
||||||
|
setBucketCount,
|
||||||
|
setBucketWidth,
|
||||||
setSelectedTime,
|
setSelectedTime,
|
||||||
selectedTime,
|
selectedTime,
|
||||||
yAxisUnit,
|
yAxisUnit,
|
||||||
setYAxisUnit,
|
setYAxisUnit,
|
||||||
setGraphHandler,
|
setGraphHandler,
|
||||||
thresholds,
|
thresholds,
|
||||||
|
combineHistogram,
|
||||||
|
setCombineHistogram,
|
||||||
setThresholds,
|
setThresholds,
|
||||||
selectedWidget,
|
selectedWidget,
|
||||||
isFillSpans,
|
isFillSpans,
|
||||||
@ -79,6 +86,7 @@ function RightContainer({
|
|||||||
const allowFillSpans = panelTypeVsFillSpan[selectedGraph];
|
const allowFillSpans = panelTypeVsFillSpan[selectedGraph];
|
||||||
const allowYAxisUnit = panelTypeVsYAxisUnit[selectedGraph];
|
const allowYAxisUnit = panelTypeVsYAxisUnit[selectedGraph];
|
||||||
const allowCreateAlerts = panelTypeVsCreateAlert[selectedGraph];
|
const allowCreateAlerts = panelTypeVsCreateAlert[selectedGraph];
|
||||||
|
const allowBucketConfig = panelTypeVsBucketConfig[selectedGraph];
|
||||||
const allowPanelTimePreference =
|
const allowPanelTimePreference =
|
||||||
panelTypeVsPanelTimePreferences[selectedGraph];
|
panelTypeVsPanelTimePreferences[selectedGraph];
|
||||||
|
|
||||||
@ -222,6 +230,47 @@ function RightContainer({
|
|||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{allowBucketConfig && (
|
||||||
|
<section className="bucket-config">
|
||||||
|
<Typography.Text className="label">Number of buckets</Typography.Text>
|
||||||
|
<InputNumber
|
||||||
|
value={bucketCount || null}
|
||||||
|
type="number"
|
||||||
|
min={0}
|
||||||
|
rootClassName="bucket-input"
|
||||||
|
placeholder="Default: 30"
|
||||||
|
onChange={(val): void => {
|
||||||
|
setBucketCount(val || 0);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Typography.Text className="label bucket-size-label">
|
||||||
|
Bucket width
|
||||||
|
</Typography.Text>
|
||||||
|
<InputNumber
|
||||||
|
value={bucketWidth || null}
|
||||||
|
type="number"
|
||||||
|
precision={2}
|
||||||
|
placeholder="Default: Auto"
|
||||||
|
step={0.1}
|
||||||
|
min={0.0}
|
||||||
|
rootClassName="bucket-input"
|
||||||
|
onChange={(val): void => {
|
||||||
|
setBucketWidth(val || 0);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<section className="combine-hist">
|
||||||
|
<Typography.Text className="label">
|
||||||
|
Merge all series into one
|
||||||
|
</Typography.Text>
|
||||||
|
<Switch
|
||||||
|
checked={combineHistogram}
|
||||||
|
size="small"
|
||||||
|
onChange={(checked): void => setCombineHistogram(checked)}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{allowCreateAlerts && (
|
{allowCreateAlerts && (
|
||||||
@ -263,6 +312,12 @@ interface RightContainerProps {
|
|||||||
setSelectedTime: Dispatch<SetStateAction<timePreferance>>;
|
setSelectedTime: Dispatch<SetStateAction<timePreferance>>;
|
||||||
selectedTime: timePreferance;
|
selectedTime: timePreferance;
|
||||||
yAxisUnit: string;
|
yAxisUnit: string;
|
||||||
|
bucketWidth: number;
|
||||||
|
bucketCount: number;
|
||||||
|
combineHistogram: boolean;
|
||||||
|
setCombineHistogram: Dispatch<SetStateAction<boolean>>;
|
||||||
|
setBucketWidth: Dispatch<SetStateAction<number>>;
|
||||||
|
setBucketCount: Dispatch<SetStateAction<number>>;
|
||||||
setYAxisUnit: Dispatch<SetStateAction<string>>;
|
setYAxisUnit: Dispatch<SetStateAction<string>>;
|
||||||
setGraphHandler: (type: PANEL_TYPES) => void;
|
setGraphHandler: (type: PANEL_TYPES) => void;
|
||||||
thresholds: ThresholdProps[];
|
thresholds: ThresholdProps[];
|
||||||
|
@ -10,6 +10,7 @@ import { QueryParams } from 'constants/query';
|
|||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import { DashboardShortcuts } from 'constants/shortcuts/DashboardShortcuts';
|
import { DashboardShortcuts } from 'constants/shortcuts/DashboardShortcuts';
|
||||||
|
import { DEFAULT_BUCKET_COUNT } from 'container/PanelWrapper/constants';
|
||||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||||
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
|
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
@ -138,6 +139,18 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
const [saveModal, setSaveModal] = useState(false);
|
const [saveModal, setSaveModal] = useState(false);
|
||||||
const [discardModal, setDiscardModal] = useState(false);
|
const [discardModal, setDiscardModal] = useState(false);
|
||||||
|
|
||||||
|
const [bucketWidth, setBucketWidth] = useState<number>(
|
||||||
|
selectedWidget?.bucketWidth || 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [bucketCount, setBucketCount] = useState<number>(
|
||||||
|
selectedWidget?.bucketCount || DEFAULT_BUCKET_COUNT,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [combineHistogram, setCombineHistogram] = useState<boolean>(
|
||||||
|
selectedWidget?.mergeAllActiveQueries || false,
|
||||||
|
);
|
||||||
|
|
||||||
const [softMin, setSoftMin] = useState<number | null>(
|
const [softMin, setSoftMin] = useState<number | null>(
|
||||||
selectedWidget?.softMin === null || selectedWidget?.softMin === undefined
|
selectedWidget?.softMin === null || selectedWidget?.softMin === undefined
|
||||||
? null
|
? null
|
||||||
@ -181,6 +194,9 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
softMax,
|
softMax,
|
||||||
fillSpans: isFillSpans,
|
fillSpans: isFillSpans,
|
||||||
columnUnits,
|
columnUnits,
|
||||||
|
bucketCount,
|
||||||
|
bucketWidth,
|
||||||
|
mergeAllActiveQueries: combineHistogram,
|
||||||
selectedLogFields,
|
selectedLogFields,
|
||||||
selectedTracesFields,
|
selectedTracesFields,
|
||||||
};
|
};
|
||||||
@ -200,6 +216,9 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
thresholds,
|
thresholds,
|
||||||
title,
|
title,
|
||||||
yAxisUnit,
|
yAxisUnit,
|
||||||
|
bucketWidth,
|
||||||
|
bucketCount,
|
||||||
|
combineHistogram,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const closeModal = (): void => {
|
const closeModal = (): void => {
|
||||||
@ -296,6 +315,9 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
softMin: selectedWidget?.softMin || 0,
|
softMin: selectedWidget?.softMin || 0,
|
||||||
softMax: selectedWidget?.softMax || 0,
|
softMax: selectedWidget?.softMax || 0,
|
||||||
fillSpans: selectedWidget?.fillSpans,
|
fillSpans: selectedWidget?.fillSpans,
|
||||||
|
bucketWidth: selectedWidget?.bucketWidth || 0,
|
||||||
|
bucketCount: selectedWidget?.bucketCount || 0,
|
||||||
|
mergeAllActiveQueries: selectedWidget?.mergeAllActiveQueries || false,
|
||||||
selectedLogFields: selectedWidget?.selectedLogFields || [],
|
selectedLogFields: selectedWidget?.selectedLogFields || [],
|
||||||
selectedTracesFields: selectedWidget?.selectedTracesFields || [],
|
selectedTracesFields: selectedWidget?.selectedTracesFields || [],
|
||||||
},
|
},
|
||||||
@ -318,6 +340,9 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
softMin: selectedWidget?.softMin || 0,
|
softMin: selectedWidget?.softMin || 0,
|
||||||
softMax: selectedWidget?.softMax || 0,
|
softMax: selectedWidget?.softMax || 0,
|
||||||
fillSpans: selectedWidget?.fillSpans,
|
fillSpans: selectedWidget?.fillSpans,
|
||||||
|
bucketWidth: selectedWidget?.bucketWidth || 0,
|
||||||
|
bucketCount: selectedWidget?.bucketCount || 0,
|
||||||
|
mergeAllActiveQueries: selectedWidget?.mergeAllActiveQueries || false,
|
||||||
selectedLogFields: selectedWidget?.selectedLogFields || [],
|
selectedLogFields: selectedWidget?.selectedLogFields || [],
|
||||||
selectedTracesFields: selectedWidget?.selectedTracesFields || [],
|
selectedTracesFields: selectedWidget?.selectedTracesFields || [],
|
||||||
},
|
},
|
||||||
@ -511,6 +536,12 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
yAxisUnit={yAxisUnit}
|
yAxisUnit={yAxisUnit}
|
||||||
columnUnits={columnUnits}
|
columnUnits={columnUnits}
|
||||||
setColumnUnits={setColumnUnits}
|
setColumnUnits={setColumnUnits}
|
||||||
|
bucketCount={bucketCount}
|
||||||
|
bucketWidth={bucketWidth}
|
||||||
|
combineHistogram={combineHistogram}
|
||||||
|
setCombineHistogram={setCombineHistogram}
|
||||||
|
setBucketWidth={setBucketWidth}
|
||||||
|
setBucketCount={setBucketCount}
|
||||||
setOpacity={setOpacity}
|
setOpacity={setOpacity}
|
||||||
selectedNullZeroValue={selectedNullZeroValue}
|
selectedNullZeroValue={selectedNullZeroValue}
|
||||||
setSelectedNullZeroValue={setSelectedNullZeroValue}
|
setSelectedNullZeroValue={setSelectedNullZeroValue}
|
||||||
|
@ -32,6 +32,7 @@ export type PartialPanelTypes = {
|
|||||||
[PANEL_TYPES.TIME_SERIES]: 'graph';
|
[PANEL_TYPES.TIME_SERIES]: 'graph';
|
||||||
[PANEL_TYPES.VALUE]: 'value';
|
[PANEL_TYPES.VALUE]: 'value';
|
||||||
[PANEL_TYPES.PIE]: 'pie';
|
[PANEL_TYPES.PIE]: 'pie';
|
||||||
|
[PANEL_TYPES.HISTOGRAM]: 'histogram';
|
||||||
};
|
};
|
||||||
|
|
||||||
export const panelTypeDataSourceFormValuesMap: Record<
|
export const panelTypeDataSourceFormValuesMap: Record<
|
||||||
@ -144,6 +145,94 @@ export const panelTypeDataSourceFormValuesMap: Record<
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
[PANEL_TYPES.HISTOGRAM]: {
|
||||||
|
[DataSource.LOGS]: {
|
||||||
|
builder: {
|
||||||
|
queryData: [
|
||||||
|
'filters',
|
||||||
|
'aggregateOperator',
|
||||||
|
'aggregateAttribute',
|
||||||
|
'groupBy',
|
||||||
|
'limit',
|
||||||
|
'having',
|
||||||
|
'orderBy',
|
||||||
|
'functions',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[DataSource.METRICS]: {
|
||||||
|
builder: {
|
||||||
|
queryData: [
|
||||||
|
'filters',
|
||||||
|
'aggregateOperator',
|
||||||
|
'aggregateAttribute',
|
||||||
|
'groupBy',
|
||||||
|
'limit',
|
||||||
|
'having',
|
||||||
|
'orderBy',
|
||||||
|
'functions',
|
||||||
|
'spaceAggregation',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[DataSource.TRACES]: {
|
||||||
|
builder: {
|
||||||
|
queryData: [
|
||||||
|
'filters',
|
||||||
|
'aggregateOperator',
|
||||||
|
'aggregateAttribute',
|
||||||
|
'groupBy',
|
||||||
|
'limit',
|
||||||
|
'having',
|
||||||
|
'orderBy',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[PANEL_TYPES.HISTOGRAM]: {
|
||||||
|
[DataSource.LOGS]: {
|
||||||
|
builder: {
|
||||||
|
queryData: [
|
||||||
|
'filters',
|
||||||
|
'aggregateOperator',
|
||||||
|
'aggregateAttribute',
|
||||||
|
'groupBy',
|
||||||
|
'limit',
|
||||||
|
'having',
|
||||||
|
'orderBy',
|
||||||
|
'functions',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[DataSource.METRICS]: {
|
||||||
|
builder: {
|
||||||
|
queryData: [
|
||||||
|
'filters',
|
||||||
|
'aggregateOperator',
|
||||||
|
'aggregateAttribute',
|
||||||
|
'groupBy',
|
||||||
|
'limit',
|
||||||
|
'having',
|
||||||
|
'orderBy',
|
||||||
|
'functions',
|
||||||
|
'spaceAggregation',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[DataSource.TRACES]: {
|
||||||
|
builder: {
|
||||||
|
queryData: [
|
||||||
|
'filters',
|
||||||
|
'aggregateOperator',
|
||||||
|
'aggregateAttribute',
|
||||||
|
'groupBy',
|
||||||
|
'limit',
|
||||||
|
'having',
|
||||||
|
'orderBy',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
[PANEL_TYPES.TABLE]: {
|
[PANEL_TYPES.TABLE]: {
|
||||||
[DataSource.LOGS]: {
|
[DataSource.LOGS]: {
|
||||||
builder: {
|
builder: {
|
||||||
|
103
frontend/src/container/PanelWrapper/HistogramPanelWrapper.tsx
Normal file
103
frontend/src/container/PanelWrapper/HistogramPanelWrapper.tsx
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import { ToggleGraphProps } from 'components/Graph/types';
|
||||||
|
import Uplot from 'components/Uplot';
|
||||||
|
import GraphManager from 'container/GridCardLayout/GridCard/FullView/GraphManager';
|
||||||
|
import { getLocalStorageGraphVisibilityState } from 'container/GridCardLayout/GridCard/utils';
|
||||||
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
|
import { useResizeObserver } from 'hooks/useDimensions';
|
||||||
|
import { getUplotHistogramChartOptions } from 'lib/uPlotLib/getUplotHistogramChartOptions';
|
||||||
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||||
|
import { useEffect, useMemo, useRef } from 'react';
|
||||||
|
|
||||||
|
import { buildHistogramData } from './histogram';
|
||||||
|
import { PanelWrapperProps } from './panelWrapper.types';
|
||||||
|
|
||||||
|
function HistogramPanelWrapper({
|
||||||
|
queryResponse,
|
||||||
|
widget,
|
||||||
|
setGraphVisibility,
|
||||||
|
graphVisibility,
|
||||||
|
isFullViewMode,
|
||||||
|
onToggleModelHandler,
|
||||||
|
}: PanelWrapperProps): JSX.Element {
|
||||||
|
const graphRef = useRef<HTMLDivElement>(null);
|
||||||
|
const { toScrollWidgetId, setToScrollWidgetId } = useDashboard();
|
||||||
|
const isDarkMode = useIsDarkMode();
|
||||||
|
const containerDimensions = useResizeObserver(graphRef);
|
||||||
|
|
||||||
|
const histogramData = buildHistogramData(
|
||||||
|
queryResponse.data?.payload.data.result,
|
||||||
|
widget?.bucketWidth,
|
||||||
|
widget?.bucketCount,
|
||||||
|
widget?.mergeAllActiveQueries,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (toScrollWidgetId === widget.id) {
|
||||||
|
graphRef.current?.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'center',
|
||||||
|
});
|
||||||
|
graphRef.current?.focus();
|
||||||
|
setToScrollWidgetId('');
|
||||||
|
}
|
||||||
|
}, [toScrollWidgetId, setToScrollWidgetId, widget.id]);
|
||||||
|
const lineChartRef = useRef<ToggleGraphProps>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const {
|
||||||
|
graphVisibilityStates: localStoredVisibilityState,
|
||||||
|
} = getLocalStorageGraphVisibilityState({
|
||||||
|
apiResponse: queryResponse.data?.payload.data.result || [],
|
||||||
|
name: widget.id,
|
||||||
|
});
|
||||||
|
if (setGraphVisibility) {
|
||||||
|
setGraphVisibility(localStoredVisibilityState);
|
||||||
|
}
|
||||||
|
}, [queryResponse.data?.payload.data.result, setGraphVisibility, widget.id]);
|
||||||
|
|
||||||
|
const histogramOptions = useMemo(
|
||||||
|
() =>
|
||||||
|
getUplotHistogramChartOptions({
|
||||||
|
id: widget.id,
|
||||||
|
dimensions: containerDimensions,
|
||||||
|
isDarkMode,
|
||||||
|
apiResponse: queryResponse.data?.payload,
|
||||||
|
histogramData,
|
||||||
|
panelType: widget.panelTypes,
|
||||||
|
setGraphsVisibilityStates: setGraphVisibility,
|
||||||
|
graphsVisibilityStates: graphVisibility,
|
||||||
|
mergeAllQueries: widget.mergeAllActiveQueries,
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
containerDimensions,
|
||||||
|
graphVisibility,
|
||||||
|
histogramData,
|
||||||
|
isDarkMode,
|
||||||
|
queryResponse.data?.payload,
|
||||||
|
setGraphVisibility,
|
||||||
|
widget.id,
|
||||||
|
widget.mergeAllActiveQueries,
|
||||||
|
widget.panelTypes,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ height: '100%', width: '100%' }} ref={graphRef}>
|
||||||
|
<Uplot options={histogramOptions} data={histogramData} ref={lineChartRef} />
|
||||||
|
{isFullViewMode && setGraphVisibility && !widget.mergeAllActiveQueries && (
|
||||||
|
<GraphManager
|
||||||
|
data={histogramData}
|
||||||
|
name={widget.id}
|
||||||
|
options={histogramOptions}
|
||||||
|
yAxisUnit={widget.yAxisUnit}
|
||||||
|
onToggleModelHandler={onToggleModelHandler}
|
||||||
|
setGraphsVisibilityStates={setGraphVisibility}
|
||||||
|
graphsVisibilityStates={graphVisibility}
|
||||||
|
lineChartRef={lineChartRef}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HistogramPanelWrapper;
|
@ -1,5 +1,6 @@
|
|||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
|
||||||
|
import HistogramPanelWrapper from './HistogramPanelWrapper';
|
||||||
import ListPanelWrapper from './ListPanelWrapper';
|
import ListPanelWrapper from './ListPanelWrapper';
|
||||||
import PiePanelWrapper from './PiePanelWrapper';
|
import PiePanelWrapper from './PiePanelWrapper';
|
||||||
import TablePanelWrapper from './TablePanelWrapper';
|
import TablePanelWrapper from './TablePanelWrapper';
|
||||||
@ -15,4 +16,34 @@ export const PanelTypeVsPanelWrapper = {
|
|||||||
[PANEL_TYPES.EMPTY_WIDGET]: null,
|
[PANEL_TYPES.EMPTY_WIDGET]: null,
|
||||||
[PANEL_TYPES.PIE]: PiePanelWrapper,
|
[PANEL_TYPES.PIE]: PiePanelWrapper,
|
||||||
[PANEL_TYPES.BAR]: UplotPanelWrapper,
|
[PANEL_TYPES.BAR]: UplotPanelWrapper,
|
||||||
|
[PANEL_TYPES.HISTOGRAM]: HistogramPanelWrapper,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const DEFAULT_BUCKET_COUNT = 30;
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
export const histogramBucketSizes = [
|
||||||
|
1e-9, 2e-9, 2.5e-9, 4e-9, 5e-9,
|
||||||
|
1e-8, 2e-8, 2.5e-8, 4e-8, 5e-8,
|
||||||
|
1e-7, 2e-7, 2.5e-7, 4e-7, 5e-7,
|
||||||
|
1e-6, 2e-6, 2.5e-6, 4e-6, 5e-6,
|
||||||
|
1e-5, 2e-5, 2.5e-5, 4e-5, 5e-5,
|
||||||
|
1e-4, 2e-4, 2.5e-4, 4e-4, 5e-4,
|
||||||
|
1e-3, 2e-3, 2.5e-3, 4e-3, 5e-3,
|
||||||
|
1e-2, 2e-2, 2.5e-2, 4e-2, 5e-2,
|
||||||
|
1e-1, 2e-1, 2.5e-1, 4e-1, 5e-1,
|
||||||
|
1, 2, 4, 5,
|
||||||
|
1e+1, 2e+1, 2.5e+1, 4e+1, 5e+1,
|
||||||
|
1e+2, 2e+2, 2.5e+2, 4e+2, 5e+2,
|
||||||
|
1e+3, 2e+3, 2.5e+3, 4e+3, 5e+3,
|
||||||
|
1e+4, 2e+4, 2.5e+4, 4e+4, 5e+4,
|
||||||
|
1e+5, 2e+5, 2.5e+5, 4e+5, 5e+5,
|
||||||
|
1e+6, 2e+6, 2.5e+6, 4e+6, 5e+6,
|
||||||
|
1e+7, 2e+7, 2.5e+7, 4e+7, 5e+7,
|
||||||
|
1e+8, 2e+8, 2.5e+8, 4e+8, 5e+8,
|
||||||
|
1e+9, 2e+9, 2.5e+9, 4e+9, 5e+9,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const NULL_REMOVE = 0;
|
||||||
|
export const NULL_RETAIN = 1;
|
||||||
|
export const NULL_EXPAND = 2;
|
||||||
|
276
frontend/src/container/PanelWrapper/histogram.ts
Normal file
276
frontend/src/container/PanelWrapper/histogram.ts
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
/* eslint-disable sonarjs/cognitive-complexity */
|
||||||
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||||
|
/* eslint-disable no-param-reassign */
|
||||||
|
import { histogramBucketSizes } from '@grafana/data';
|
||||||
|
import { QueryData } from 'types/api/widgets/getQuery';
|
||||||
|
import uPlot, { AlignedData } from 'uplot';
|
||||||
|
|
||||||
|
import {
|
||||||
|
DEFAULT_BUCKET_COUNT,
|
||||||
|
NULL_EXPAND,
|
||||||
|
NULL_REMOVE,
|
||||||
|
NULL_RETAIN,
|
||||||
|
} from './constants';
|
||||||
|
|
||||||
|
export function incrRoundDn(num: number, incr: number): number {
|
||||||
|
return Math.floor(num / incr) * incr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const histSort = (a: number, b: number): number => a - b;
|
||||||
|
|
||||||
|
export function roundDecimals(val: number, dec = 0): number {
|
||||||
|
if (Number.isInteger(val)) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
const p = 10 ** dec;
|
||||||
|
const n = val * p * (1 + Number.EPSILON);
|
||||||
|
return Math.round(n) / p;
|
||||||
|
}
|
||||||
|
|
||||||
|
function nullExpand(
|
||||||
|
yVals: Array<number | null>,
|
||||||
|
nullIdxs: number[],
|
||||||
|
alignedLen: number,
|
||||||
|
): void {
|
||||||
|
for (let i = 0, xi, lastNullIdx = -1; i < nullIdxs.length; i++) {
|
||||||
|
const nullIdx = nullIdxs[i];
|
||||||
|
|
||||||
|
if (nullIdx > lastNullIdx) {
|
||||||
|
xi = nullIdx - 1;
|
||||||
|
while (xi >= 0 && yVals[xi] == null) {
|
||||||
|
yVals[xi--] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
xi = nullIdx + 1;
|
||||||
|
while (xi < alignedLen && yVals[xi] == null) {
|
||||||
|
yVals[(lastNullIdx = xi++)] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function join(
|
||||||
|
tables: AlignedData[],
|
||||||
|
nullModes?: number[][],
|
||||||
|
): AlignedData[] {
|
||||||
|
let xVals: Set<number>;
|
||||||
|
|
||||||
|
// eslint-disable-next-line prefer-const
|
||||||
|
xVals = new Set();
|
||||||
|
|
||||||
|
for (let ti = 0; ti < tables.length; ti++) {
|
||||||
|
const t = tables[ti];
|
||||||
|
const xs = t[0];
|
||||||
|
const len = xs.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
xVals.add(xs[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = [Array.from(xVals).sort((a, b) => a - b)];
|
||||||
|
|
||||||
|
const alignedLen = data[0].length;
|
||||||
|
|
||||||
|
const xIdxs = new Map();
|
||||||
|
|
||||||
|
for (let i = 0; i < alignedLen; i++) {
|
||||||
|
xIdxs.set(data[0][i], i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let ti = 0; ti < tables.length; ti++) {
|
||||||
|
const t = tables[ti];
|
||||||
|
const xs = t[0];
|
||||||
|
|
||||||
|
for (let si = 1; si < t.length; si++) {
|
||||||
|
const ys = t[si];
|
||||||
|
|
||||||
|
const yVals = Array(alignedLen).fill(undefined);
|
||||||
|
|
||||||
|
const nullMode = nullModes ? nullModes[ti][si] : NULL_RETAIN;
|
||||||
|
|
||||||
|
const nullIdxs = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < ys.length; i++) {
|
||||||
|
const yVal = ys[i];
|
||||||
|
const alignedIdx = xIdxs.get(xs[i]);
|
||||||
|
|
||||||
|
if (yVal === null) {
|
||||||
|
if (nullMode !== NULL_REMOVE) {
|
||||||
|
yVals[alignedIdx] = yVal;
|
||||||
|
|
||||||
|
if (nullMode === NULL_EXPAND) {
|
||||||
|
nullIdxs.push(alignedIdx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
yVals[alignedIdx] = yVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nullExpand(yVals, nullIdxs, alignedLen);
|
||||||
|
|
||||||
|
data.push(yVals);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (data as unknown) as AlignedData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function histogram(
|
||||||
|
vals: number[],
|
||||||
|
getBucket: (v: number) => number,
|
||||||
|
sort?: ((a: number, b: number) => number) | null,
|
||||||
|
): AlignedData {
|
||||||
|
const hist = new Map();
|
||||||
|
|
||||||
|
for (let i = 0; i < vals.length; i++) {
|
||||||
|
let v = vals[i];
|
||||||
|
|
||||||
|
if (v != null) {
|
||||||
|
v = getBucket(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = hist.get(v);
|
||||||
|
|
||||||
|
if (entry) {
|
||||||
|
entry.count++;
|
||||||
|
} else {
|
||||||
|
hist.set(v, { value: v, count: 1 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bins = [...hist.values()];
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||||
|
sort && bins.sort((a, b) => sort(a.value, b.value));
|
||||||
|
|
||||||
|
const values = Array(bins.length);
|
||||||
|
const counts = Array(bins.length);
|
||||||
|
|
||||||
|
for (let i = 0; i < bins.length; i++) {
|
||||||
|
values[i] = bins[i].value;
|
||||||
|
counts[i] = bins[i].count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [values, counts];
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceUndefinedWithNull(arrays: (number | null)[][]): AlignedData[] {
|
||||||
|
for (let i = 0; i < arrays.length; i++) {
|
||||||
|
for (let j = 0; j < arrays[i].length; j++) {
|
||||||
|
if (arrays[i][j] === undefined) {
|
||||||
|
arrays[i][j] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (arrays as unknown) as AlignedData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function addNullToFirstHistogram(
|
||||||
|
histograms: (number | null)[][],
|
||||||
|
bucketSize: number,
|
||||||
|
): void {
|
||||||
|
if (
|
||||||
|
histograms.length > 0 &&
|
||||||
|
histograms[0].length > 0 &&
|
||||||
|
histograms[0][0] !== null
|
||||||
|
) {
|
||||||
|
histograms[0].unshift(histograms[0][0] - bucketSize);
|
||||||
|
for (let i = 1; i < histograms.length; i++) {
|
||||||
|
histograms[i].unshift(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const buildHistogramData = (
|
||||||
|
data: QueryData[] | undefined,
|
||||||
|
widgetBucketSize?: number,
|
||||||
|
widgteBucketCount?: number,
|
||||||
|
widgetMergeAllActiveQueries?: boolean,
|
||||||
|
): uPlot.AlignedData => {
|
||||||
|
let bucketSize = 0;
|
||||||
|
const bucketCount = widgteBucketCount || DEFAULT_BUCKET_COUNT;
|
||||||
|
const bucketOffset = 0;
|
||||||
|
|
||||||
|
const seriesValues: number[] = [];
|
||||||
|
data?.forEach((item) => {
|
||||||
|
item.values.forEach((value) => {
|
||||||
|
seriesValues.push(parseFloat(value[1]) || 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
seriesValues.sort((a, b) => a - b);
|
||||||
|
|
||||||
|
let smallestDelta = Infinity;
|
||||||
|
if (seriesValues.length === 1) {
|
||||||
|
smallestDelta = 0;
|
||||||
|
} else {
|
||||||
|
for (let i = 1; i < seriesValues.length; i++) {
|
||||||
|
const delta = seriesValues[i] - seriesValues[i - 1];
|
||||||
|
if (delta !== 0) {
|
||||||
|
smallestDelta = Math.min(smallestDelta, delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const min = seriesValues[0];
|
||||||
|
const max = seriesValues[seriesValues.length - 1];
|
||||||
|
|
||||||
|
const range = max - min;
|
||||||
|
const targetSize = range / bucketCount;
|
||||||
|
|
||||||
|
for (let i = 0; i < histogramBucketSizes.length; i++) {
|
||||||
|
const newBucketSize = histogramBucketSizes[i];
|
||||||
|
|
||||||
|
if (targetSize < newBucketSize && newBucketSize >= smallestDelta) {
|
||||||
|
bucketSize = newBucketSize;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widgetBucketSize) {
|
||||||
|
bucketSize = widgetBucketSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getBucket = (v: number): number =>
|
||||||
|
roundDecimals(incrRoundDn(v - bucketOffset, bucketSize) + bucketOffset, 9);
|
||||||
|
|
||||||
|
const frames: number[][] = [];
|
||||||
|
|
||||||
|
data?.forEach((item) => {
|
||||||
|
const newFrame: number[] = [];
|
||||||
|
item.values.forEach((value) => {
|
||||||
|
newFrame.push(parseFloat(value[1]) || 0);
|
||||||
|
});
|
||||||
|
frames.push(newFrame);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (widgetMergeAllActiveQueries) {
|
||||||
|
for (let i = 1; i < frames.length; i++) {
|
||||||
|
frames[i].forEach((val) => {
|
||||||
|
frames[0].push(val);
|
||||||
|
});
|
||||||
|
frames[i] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const histograms: AlignedData[] = [];
|
||||||
|
|
||||||
|
frames.forEach((frame) => {
|
||||||
|
const fieldHist = histogram(frame, getBucket, histSort);
|
||||||
|
histograms.push(fieldHist);
|
||||||
|
});
|
||||||
|
|
||||||
|
const joinHistogram = replaceUndefinedWithNull(
|
||||||
|
(join(histograms) as unknown) as (number | null)[][],
|
||||||
|
);
|
||||||
|
|
||||||
|
addNullToFirstHistogram(
|
||||||
|
(joinHistogram as unknown) as (number | null)[][],
|
||||||
|
bucketSize,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (joinHistogram as unknown) as AlignedData;
|
||||||
|
};
|
@ -57,7 +57,6 @@ export const getUPlotChartOptions = ({
|
|||||||
graphsVisibilityStates,
|
graphsVisibilityStates,
|
||||||
setGraphsVisibilityStates,
|
setGraphsVisibilityStates,
|
||||||
thresholds,
|
thresholds,
|
||||||
fillSpans,
|
|
||||||
softMax,
|
softMax,
|
||||||
softMin,
|
softMin,
|
||||||
panelType,
|
panelType,
|
||||||
@ -108,7 +107,7 @@ export const getUPlotChartOptions = ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
tooltipPlugin(apiResponse, yAxisUnit, fillSpans),
|
tooltipPlugin({ apiResponse, yAxisUnit }),
|
||||||
onClickPlugin({
|
onClickPlugin({
|
||||||
onClick: onClickHandler,
|
onClick: onClickHandler,
|
||||||
}),
|
}),
|
||||||
|
199
frontend/src/lib/uPlotLib/getUplotHistogramChartOptions.ts
Normal file
199
frontend/src/lib/uPlotLib/getUplotHistogramChartOptions.ts
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
/* eslint-disable sonarjs/cognitive-complexity */
|
||||||
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import { themeColors } from 'constants/theme';
|
||||||
|
import { saveLegendEntriesToLocalStorage } from 'container/GridCardLayout/GridCard/FullView/utils';
|
||||||
|
import { Dimensions } from 'hooks/useDimensions';
|
||||||
|
import getLabelName from 'lib/getLabelName';
|
||||||
|
import { Dispatch, SetStateAction } from 'react';
|
||||||
|
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||||
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
import { QueryData } from 'types/api/widgets/getQuery';
|
||||||
|
import uPlot from 'uplot';
|
||||||
|
|
||||||
|
import tooltipPlugin from './plugins/tooltipPlugin';
|
||||||
|
import { drawStyles } from './utils/constants';
|
||||||
|
import { generateColor } from './utils/generateColor';
|
||||||
|
import getAxes from './utils/getAxes';
|
||||||
|
|
||||||
|
type GetUplotHistogramChartOptionsProps = {
|
||||||
|
id?: string;
|
||||||
|
apiResponse?: MetricRangePayloadProps;
|
||||||
|
histogramData: uPlot.AlignedData;
|
||||||
|
dimensions: Dimensions;
|
||||||
|
isDarkMode: boolean;
|
||||||
|
panelType?: PANEL_TYPES;
|
||||||
|
onDragSelect?: (startTime: number, endTime: number) => void;
|
||||||
|
currentQuery?: Query;
|
||||||
|
graphsVisibilityStates?: boolean[];
|
||||||
|
setGraphsVisibilityStates?: Dispatch<SetStateAction<boolean[]>>;
|
||||||
|
mergeAllQueries?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type GetHistogramSeriesProps = {
|
||||||
|
apiResponse?: MetricRangePayloadProps;
|
||||||
|
currentQuery?: Query;
|
||||||
|
widgetMetaData?: QueryData[];
|
||||||
|
graphsVisibilityStates?: boolean[];
|
||||||
|
isMergedSeries?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const { bars } = uPlot.paths;
|
||||||
|
|
||||||
|
const paths = (
|
||||||
|
u: uPlot,
|
||||||
|
seriesIdx: number,
|
||||||
|
idx0: number,
|
||||||
|
idx1: number,
|
||||||
|
): uPlot.Series.Paths | null | undefined => {
|
||||||
|
const renderer = bars && bars({ size: [1], align: -1 });
|
||||||
|
|
||||||
|
return renderer && renderer(u, seriesIdx, idx0, idx1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getHistogramSeries = ({
|
||||||
|
apiResponse,
|
||||||
|
currentQuery,
|
||||||
|
widgetMetaData,
|
||||||
|
graphsVisibilityStates,
|
||||||
|
isMergedSeries,
|
||||||
|
}: GetHistogramSeriesProps): uPlot.Options['series'] => {
|
||||||
|
const configurations: uPlot.Series[] = [
|
||||||
|
{ label: 'Timestamp', stroke: 'purple' },
|
||||||
|
];
|
||||||
|
const seriesList = apiResponse?.data.result || [];
|
||||||
|
|
||||||
|
const newGraphVisibilityStates = graphsVisibilityStates?.slice(1);
|
||||||
|
|
||||||
|
for (let i = 0; i < seriesList?.length; i += 1) {
|
||||||
|
const { metric = {}, queryName = '', legend: lgd } =
|
||||||
|
(widgetMetaData && widgetMetaData[i]) || {};
|
||||||
|
|
||||||
|
const newLegend =
|
||||||
|
currentQuery?.builder.queryData.find((item) => item.queryName === queryName)
|
||||||
|
?.legend || '';
|
||||||
|
|
||||||
|
const legend = newLegend || lgd || '';
|
||||||
|
|
||||||
|
const label = isMergedSeries
|
||||||
|
? 'merged_series'
|
||||||
|
: getLabelName(metric, queryName || '', legend);
|
||||||
|
|
||||||
|
const color = generateColor(label, themeColors.chartcolors);
|
||||||
|
|
||||||
|
const pointSize = seriesList[i].values.length > 1 ? 5 : 10;
|
||||||
|
const showPoints = !(seriesList[i].values.length > 1);
|
||||||
|
|
||||||
|
const seriesObj: uPlot.Series = {
|
||||||
|
paths,
|
||||||
|
drawStyle: drawStyles.bars,
|
||||||
|
lineInterpolation: null,
|
||||||
|
show: newGraphVisibilityStates ? newGraphVisibilityStates[i] : true,
|
||||||
|
label,
|
||||||
|
fill: `${color}40`,
|
||||||
|
stroke: color,
|
||||||
|
width: 2,
|
||||||
|
points: {
|
||||||
|
size: pointSize,
|
||||||
|
show: showPoints,
|
||||||
|
stroke: color,
|
||||||
|
},
|
||||||
|
} as uPlot.Series;
|
||||||
|
|
||||||
|
configurations.push(seriesObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
return configurations;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUplotHistogramChartOptions = ({
|
||||||
|
id,
|
||||||
|
dimensions,
|
||||||
|
isDarkMode,
|
||||||
|
apiResponse,
|
||||||
|
currentQuery,
|
||||||
|
graphsVisibilityStates,
|
||||||
|
setGraphsVisibilityStates,
|
||||||
|
mergeAllQueries,
|
||||||
|
}: GetUplotHistogramChartOptionsProps): uPlot.Options =>
|
||||||
|
({
|
||||||
|
id,
|
||||||
|
width: dimensions.width,
|
||||||
|
height: dimensions.height - 30,
|
||||||
|
legend: {
|
||||||
|
show: !mergeAllQueries,
|
||||||
|
live: false,
|
||||||
|
isolate: true,
|
||||||
|
},
|
||||||
|
focus: {
|
||||||
|
alpha: 0.3,
|
||||||
|
},
|
||||||
|
padding: [16, 16, 8, 8],
|
||||||
|
plugins: [
|
||||||
|
tooltipPlugin({
|
||||||
|
apiResponse,
|
||||||
|
isHistogramGraphs: true,
|
||||||
|
isMergedSeries: mergeAllQueries,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
time: false,
|
||||||
|
auto: true,
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
auto: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cursor: {
|
||||||
|
drag: {
|
||||||
|
x: false,
|
||||||
|
y: false,
|
||||||
|
setScale: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
series: getHistogramSeries({
|
||||||
|
apiResponse,
|
||||||
|
widgetMetaData: apiResponse?.data.result,
|
||||||
|
currentQuery,
|
||||||
|
graphsVisibilityStates,
|
||||||
|
isMergedSeries: mergeAllQueries,
|
||||||
|
}),
|
||||||
|
hooks: {
|
||||||
|
ready: [
|
||||||
|
(self): void => {
|
||||||
|
const legend = self.root.querySelector('.u-legend');
|
||||||
|
if (legend) {
|
||||||
|
const seriesEls = legend.querySelectorAll('.u-series');
|
||||||
|
const seriesArray = Array.from(seriesEls);
|
||||||
|
seriesArray.forEach((seriesEl, index) => {
|
||||||
|
seriesEl.addEventListener('click', () => {
|
||||||
|
if (graphsVisibilityStates) {
|
||||||
|
setGraphsVisibilityStates?.((prev) => {
|
||||||
|
const newGraphVisibilityStates = [...prev];
|
||||||
|
if (
|
||||||
|
newGraphVisibilityStates[index + 1] &&
|
||||||
|
newGraphVisibilityStates.every((value, i) =>
|
||||||
|
i === index + 1 ? value : !value,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
newGraphVisibilityStates.fill(true);
|
||||||
|
} else {
|
||||||
|
newGraphVisibilityStates.fill(false);
|
||||||
|
newGraphVisibilityStates[index + 1] = true;
|
||||||
|
}
|
||||||
|
saveLegendEntriesToLocalStorage({
|
||||||
|
options: self,
|
||||||
|
graphVisibilityState: newGraphVisibilityStates,
|
||||||
|
name: id || '',
|
||||||
|
});
|
||||||
|
return newGraphVisibilityStates;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
axes: getAxes(isDarkMode),
|
||||||
|
} as uPlot.Options);
|
@ -29,6 +29,8 @@ const generateTooltipContent = (
|
|||||||
yAxisUnit?: string,
|
yAxisUnit?: string,
|
||||||
series?: uPlot.Options['series'],
|
series?: uPlot.Options['series'],
|
||||||
isBillingUsageGraphs?: boolean,
|
isBillingUsageGraphs?: boolean,
|
||||||
|
isHistogramGraphs?: boolean,
|
||||||
|
isMergedSeries?: boolean,
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
): HTMLElement => {
|
): HTMLElement => {
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
@ -49,7 +51,9 @@ const generateTooltipContent = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(series) && series.length > 0) {
|
if (Array.isArray(series) && series.length > 0) {
|
||||||
|
console.log(series);
|
||||||
series.forEach((item, index) => {
|
series.forEach((item, index) => {
|
||||||
|
console.log(item, index);
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
if (isBillingUsageGraphs) {
|
if (isBillingUsageGraphs) {
|
||||||
tooltipTitle = dayjs(data[0][idx] * 1000).format('MMM DD YYYY');
|
tooltipTitle = dayjs(data[0][idx] * 1000).format('MMM DD YYYY');
|
||||||
@ -67,7 +71,9 @@ const generateTooltipContent = (
|
|||||||
|
|
||||||
const value = data[index][idx];
|
const value = data[index][idx];
|
||||||
const dataIngested = quantity[idx];
|
const dataIngested = quantity[idx];
|
||||||
const label = getLabelName(metric, queryName || '', legend || '');
|
const label = isMergedSeries
|
||||||
|
? 'merged_series'
|
||||||
|
: getLabelName(metric, queryName || '', legend || '');
|
||||||
|
|
||||||
let color = generateColor(label, themeColors.chartcolors);
|
let color = generateColor(label, themeColors.chartcolors);
|
||||||
|
|
||||||
@ -146,7 +152,7 @@ const generateTooltipContent = (
|
|||||||
|
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.classList.add('tooltip-content-row');
|
div.classList.add('tooltip-content-row');
|
||||||
div.textContent = tooltipTitle;
|
div.textContent = isHistogramGraphs ? '' : tooltipTitle;
|
||||||
div.classList.add('tooltip-content-header');
|
div.classList.add('tooltip-content-header');
|
||||||
container.appendChild(div);
|
container.appendChild(div);
|
||||||
|
|
||||||
@ -191,11 +197,21 @@ const generateTooltipContent = (
|
|||||||
return container;
|
return container;
|
||||||
};
|
};
|
||||||
|
|
||||||
const tooltipPlugin = (
|
type ToolTipPluginProps = {
|
||||||
apiResponse: MetricRangePayloadProps | undefined,
|
apiResponse: MetricRangePayloadProps | undefined;
|
||||||
yAxisUnit?: string,
|
yAxisUnit?: string;
|
||||||
isBillingUsageGraphs?: boolean,
|
isBillingUsageGraphs?: boolean;
|
||||||
): any => {
|
isHistogramGraphs?: boolean;
|
||||||
|
isMergedSeries?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const tooltipPlugin = ({
|
||||||
|
apiResponse,
|
||||||
|
yAxisUnit,
|
||||||
|
isBillingUsageGraphs,
|
||||||
|
isHistogramGraphs,
|
||||||
|
isMergedSeries,
|
||||||
|
}: ToolTipPluginProps): any => {
|
||||||
let over: HTMLElement;
|
let over: HTMLElement;
|
||||||
let bound: HTMLElement;
|
let bound: HTMLElement;
|
||||||
let bLeft: any;
|
let bLeft: any;
|
||||||
@ -256,6 +272,8 @@ const tooltipPlugin = (
|
|||||||
yAxisUnit,
|
yAxisUnit,
|
||||||
u.series,
|
u.series,
|
||||||
isBillingUsageGraphs,
|
isBillingUsageGraphs,
|
||||||
|
isHistogramGraphs,
|
||||||
|
isMergedSeries,
|
||||||
);
|
);
|
||||||
overlay.appendChild(content);
|
overlay.appendChild(content);
|
||||||
placement(overlay, anchor, 'right', 'start', { bound });
|
placement(overlay, anchor, 'right', 'start', { bound });
|
||||||
|
@ -124,6 +124,10 @@ GetYAxisScale): { auto?: boolean; range?: uPlot.Scale.Range } => {
|
|||||||
|
|
||||||
// Situation: thresholds are absent
|
// Situation: thresholds are absent
|
||||||
if (!thresholds || thresholds.length === 0) {
|
if (!thresholds || thresholds.length === 0) {
|
||||||
|
if (softMax === softMin) {
|
||||||
|
return { auto: true };
|
||||||
|
}
|
||||||
|
|
||||||
// Situation: No thresholds data but series data is present
|
// Situation: No thresholds data but series data is present
|
||||||
if (series && !areAllSeriesEmpty(series)) {
|
if (series && !areAllSeriesEmpty(series)) {
|
||||||
// Situation: softMin and softMax are null
|
// Situation: softMin and softMax are null
|
||||||
|
@ -97,6 +97,9 @@ export interface IBaseWidget {
|
|||||||
timePreferance: timePreferenceType;
|
timePreferance: timePreferenceType;
|
||||||
stepSize?: number;
|
stepSize?: number;
|
||||||
yAxisUnit?: string;
|
yAxisUnit?: string;
|
||||||
|
bucketCount?: number;
|
||||||
|
bucketWidth?: number;
|
||||||
|
mergeAllActiveQueries?: boolean;
|
||||||
thresholds?: ThresholdProps[];
|
thresholds?: ThresholdProps[];
|
||||||
softMin: number | null;
|
softMin: number | null;
|
||||||
softMax: number | null;
|
softMax: number | null;
|
||||||
|
@ -2,7 +2,7 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
|
|||||||
|
|
||||||
export const getGraphType = (panelType: PANEL_TYPES): PANEL_TYPES => {
|
export const getGraphType = (panelType: PANEL_TYPES): PANEL_TYPES => {
|
||||||
// backend don't support graphType as bar, as we consume time series data, sending graphType as time_series whenever we use bar as panel_type
|
// backend don't support graphType as bar, as we consume time series data, sending graphType as time_series whenever we use bar as panel_type
|
||||||
if (panelType === PANEL_TYPES.BAR) {
|
if (panelType === PANEL_TYPES.BAR || panelType === PANEL_TYPES.HISTOGRAM) {
|
||||||
return PANEL_TYPES.TIME_SERIES;
|
return PANEL_TYPES.TIME_SERIES;
|
||||||
}
|
}
|
||||||
if (panelType === PANEL_TYPES.PIE) {
|
if (panelType === PANEL_TYPES.PIE) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user