mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 06:26:03 +08:00
fix(UI): graph legends is fixed (#461)
* fix(UI): graph legends is fixed * chore(UI): some changes regarding the color of the chart is updated * full view css is fixed * usage explorer graph is fixed * default query is removed * fix: scroll is removed
This commit is contained in:
parent
d9a99827c0
commit
c79223742f
@ -1,24 +0,0 @@
|
||||
import { Typography } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
import { ColorContainer, Container } from './styles';
|
||||
|
||||
const Legend = ({ text, color }: LegendProps): JSX.Element => {
|
||||
if (text.length === 0) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<ColorContainer color={color}></ColorContainer>
|
||||
<Typography>{text}</Typography>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
interface LegendProps {
|
||||
text: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
export default Legend;
|
@ -1,20 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Container = styled.div`
|
||||
margin-left: 2rem;
|
||||
margin-right: 2rem;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
color: string;
|
||||
}
|
||||
|
||||
export const ColorContainer = styled.div<Props>`
|
||||
background-color: ${({ color }): string => color};
|
||||
border-radius: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 0.5rem;
|
||||
`;
|
91
frontend/src/components/Graph/Plugin/Legend.ts
Normal file
91
frontend/src/components/Graph/Plugin/Legend.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import { Plugin, ChartType, Chart, ChartOptions } from 'chart.js';
|
||||
import { colors } from 'lib/getRandomColor';
|
||||
|
||||
const getOrCreateLegendList = (chart: Chart, id: string, isLonger: boolean) => {
|
||||
const legendContainer = document.getElementById(id);
|
||||
let listContainer = legendContainer?.querySelector('ul');
|
||||
|
||||
if (!listContainer) {
|
||||
listContainer = document.createElement('ul');
|
||||
listContainer.style.display = 'flex';
|
||||
// listContainer.style.flexDirection = isLonger ? 'column' : 'row';
|
||||
listContainer.style.margin = '0';
|
||||
listContainer.style.padding = '0';
|
||||
listContainer.style.overflowY = 'scroll';
|
||||
listContainer.style.justifyContent = isLonger ? 'start' : 'center';
|
||||
listContainer.style.alignItems = isLonger ? 'start' : 'center';
|
||||
listContainer.style.height = '100%';
|
||||
listContainer.style.flexWrap = 'wrap';
|
||||
listContainer.style.justifyContent = 'center';
|
||||
|
||||
legendContainer?.appendChild(listContainer);
|
||||
}
|
||||
|
||||
return listContainer;
|
||||
};
|
||||
|
||||
export const legend = (id: string, isLonger: boolean): Plugin<ChartType> => {
|
||||
return {
|
||||
id: 'htmlLegend',
|
||||
afterUpdate(chart, args, options: ChartOptions) {
|
||||
const ul = getOrCreateLegendList(chart, id || 'legend', isLonger);
|
||||
|
||||
// Remove old legend items
|
||||
while (ul.firstChild) {
|
||||
ul.firstChild.remove();
|
||||
}
|
||||
|
||||
// Reuse the built-in legendItems generator
|
||||
const items = chart?.options?.plugins?.legend?.labels?.generateLabels(chart);
|
||||
|
||||
items?.forEach((item, index) => {
|
||||
const li = document.createElement('li');
|
||||
li.style.alignItems = 'center';
|
||||
li.style.cursor = 'pointer';
|
||||
li.style.display = 'flex';
|
||||
li.style.marginLeft = '10px';
|
||||
li.style.marginTop = '5px';
|
||||
|
||||
li.onclick = () => {
|
||||
const { type } = chart.config;
|
||||
if (type === 'pie' || type === 'doughnut') {
|
||||
// Pie and doughnut charts only have a single dataset and visibility is per item
|
||||
chart.toggleDataVisibility(index);
|
||||
} else {
|
||||
chart.setDatasetVisibility(
|
||||
item.datasetIndex,
|
||||
!chart.isDatasetVisible(item.datasetIndex),
|
||||
);
|
||||
}
|
||||
chart.update();
|
||||
};
|
||||
|
||||
// Color box
|
||||
const boxSpan = document.createElement('span');
|
||||
boxSpan.style.background = item.strokeStyle || colors[0];
|
||||
boxSpan.style.borderColor = item?.strokeStyle;
|
||||
boxSpan.style.borderWidth = item.lineWidth + 'px';
|
||||
boxSpan.style.display = 'inline-block';
|
||||
boxSpan.style.minHeight = '20px';
|
||||
boxSpan.style.marginRight = '10px';
|
||||
boxSpan.style.minWidth = '20px';
|
||||
boxSpan.style.borderRadius = '50%';
|
||||
|
||||
if (item.text) {
|
||||
// Text
|
||||
const textContainer = document.createElement('span');
|
||||
textContainer.style.margin = '0';
|
||||
textContainer.style.padding = '0';
|
||||
textContainer.style.textDecoration = item.hidden ? 'line-through' : '';
|
||||
|
||||
const text = document.createTextNode(item.text);
|
||||
textContainer.appendChild(text);
|
||||
|
||||
li.appendChild(boxSpan);
|
||||
li.appendChild(textContainer);
|
||||
ul.appendChild(li);
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
1
frontend/src/components/Graph/Plugin/index.ts
Normal file
1
frontend/src/components/Graph/Plugin/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './Legend';
|
@ -11,7 +11,6 @@ import {
|
||||
Decimation,
|
||||
Filler,
|
||||
Legend,
|
||||
// LegendItem,
|
||||
LinearScale,
|
||||
LineController,
|
||||
LineElement,
|
||||
@ -23,15 +22,11 @@ import {
|
||||
Tooltip,
|
||||
} from 'chart.js';
|
||||
import * as chartjsAdapter from 'chartjs-adapter-date-fns';
|
||||
// import { colors } from 'lib/getRandomColor';
|
||||
// import stringToHTML from 'lib/stringToHTML';
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
// import Legends from './Legend';
|
||||
// import { LegendsContainer } from './styles';
|
||||
Chart.register(
|
||||
LineElement,
|
||||
PointElement,
|
||||
@ -49,6 +44,8 @@ Chart.register(
|
||||
BarController,
|
||||
BarElement,
|
||||
);
|
||||
import { legend } from './Plugin';
|
||||
import { LegendsContainer } from './styles';
|
||||
|
||||
const Graph = ({
|
||||
data,
|
||||
@ -56,6 +53,7 @@ const Graph = ({
|
||||
title,
|
||||
isStacked,
|
||||
onClickHandler,
|
||||
name,
|
||||
}: GraphProps): JSX.Element => {
|
||||
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const chartRef = useRef<HTMLCanvasElement>(null);
|
||||
@ -95,20 +93,7 @@ const Graph = ({
|
||||
text: title,
|
||||
},
|
||||
legend: {
|
||||
// just making sure that label is present
|
||||
display: !(
|
||||
data.datasets.find((e) => {
|
||||
if (e.label?.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return e.label !== undefined;
|
||||
}) === undefined
|
||||
),
|
||||
labels: {
|
||||
usePointStyle: true,
|
||||
pointStyle: 'circle',
|
||||
},
|
||||
position: 'bottom',
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
layout: {
|
||||
@ -156,6 +141,7 @@ const Graph = ({
|
||||
type: type,
|
||||
data: data,
|
||||
options,
|
||||
plugins: [legend(name, data.datasets.length > 3)],
|
||||
});
|
||||
}
|
||||
}, [chartRef, data, type, title, isStacked, getGridColor, onClickHandler]);
|
||||
@ -164,7 +150,12 @@ const Graph = ({
|
||||
buildChart();
|
||||
}, [buildChart]);
|
||||
|
||||
return <canvas ref={chartRef} />;
|
||||
return (
|
||||
<div style={{ height: '85%' }}>
|
||||
<canvas ref={chartRef} />
|
||||
<LegendsContainer id={name} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface GraphProps {
|
||||
@ -174,6 +165,7 @@ interface GraphProps {
|
||||
isStacked?: boolean;
|
||||
label?: string[];
|
||||
onClickHandler?: graphOnClickHandler;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type graphOnClickHandler = (
|
||||
|
@ -1,8 +1,14 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const LegendsContainer = styled.div`
|
||||
display: flex;
|
||||
overflow-y: scroll;
|
||||
margin-right: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
height: 15%;
|
||||
|
||||
* {
|
||||
::-webkit-scrollbar {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
-ms-overflow-style: none !important; /* IE and Edge */
|
||||
scrollbar-width: none !important; /* Firefox */
|
||||
}
|
||||
`;
|
||||
|
@ -15,6 +15,7 @@ const GridGraphComponent = ({
|
||||
opacity,
|
||||
isStacked,
|
||||
onClickHandler,
|
||||
name,
|
||||
}: GridGraphComponentProps): JSX.Element | null => {
|
||||
const location = history.location.pathname;
|
||||
|
||||
@ -31,6 +32,7 @@ const GridGraphComponent = ({
|
||||
opacity,
|
||||
xAxisType: 'time',
|
||||
onClickHandler: onClickHandler,
|
||||
name,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@ -69,6 +71,7 @@ export interface GridGraphComponentProps {
|
||||
opacity?: string;
|
||||
isStacked?: boolean;
|
||||
onClickHandler?: graphOnClickHandler;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default GridGraphComponent;
|
||||
|
@ -27,6 +27,7 @@ const FullView = ({
|
||||
fullViewOptions = true,
|
||||
onClickHandler,
|
||||
noDataGraph = false,
|
||||
name,
|
||||
}: FullViewProps): JSX.Element => {
|
||||
const { minTime, maxTime } = useSelector<AppState, GlobalTime>(
|
||||
(state) => state.globalTime,
|
||||
@ -189,7 +190,7 @@ const FullView = ({
|
||||
</TimeContainer>
|
||||
)}
|
||||
|
||||
<GraphContainer>
|
||||
{/* <GraphContainer> */}
|
||||
<GridGraphComponent
|
||||
{...{
|
||||
GRAPH_TYPES: widget.panelTypes,
|
||||
@ -198,9 +199,10 @@ const FullView = ({
|
||||
opacity: widget.opacity,
|
||||
title: widget.title,
|
||||
onClickHandler: onClickHandler,
|
||||
name,
|
||||
}}
|
||||
/>
|
||||
</GraphContainer>
|
||||
{/* </GraphContainer> */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -217,6 +219,7 @@ interface FullViewProps {
|
||||
fullViewOptions?: boolean;
|
||||
onClickHandler?: graphOnClickHandler;
|
||||
noDataGraph?: boolean;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default FullView;
|
||||
|
@ -23,12 +23,13 @@ import { Widgets } from 'types/api/dashboard/getAll';
|
||||
|
||||
import Bar from './Bar';
|
||||
import FullView from './FullView';
|
||||
import { Modal } from './styles';
|
||||
import { Modal, FullViewContainer } from './styles';
|
||||
|
||||
const GridCardGraph = ({
|
||||
widget,
|
||||
deleteWidget,
|
||||
isDeleted,
|
||||
name,
|
||||
}: GridCardGraphProps): JSX.Element => {
|
||||
const [state, setState] = useState<GridCardGraphState>({
|
||||
loading: true,
|
||||
@ -166,7 +167,9 @@ const GridCardGraph = ({
|
||||
width="85%"
|
||||
destroyOnClose
|
||||
>
|
||||
<FullView widget={widget} />
|
||||
<FullViewContainer>
|
||||
<FullView name={name} widget={widget} />
|
||||
</FullViewContainer>
|
||||
</Modal>
|
||||
|
||||
<GridGraphComponent
|
||||
@ -176,6 +179,7 @@ const GridCardGraph = ({
|
||||
isStacked: widget.isStacked,
|
||||
opacity: widget.opacity,
|
||||
title: widget.title,
|
||||
name,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
@ -198,6 +202,7 @@ interface DispatchProps {
|
||||
interface GridCardGraphProps extends DispatchProps {
|
||||
widget: Widgets;
|
||||
isDeleted: React.MutableRefObject<boolean>;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
|
@ -11,3 +11,7 @@ export const Modal = styled(ModalComponent)<Props>`
|
||||
min-height: ${({ height = '80vh' }): string => height};
|
||||
}
|
||||
`;
|
||||
|
||||
export const FullViewContainer = styled.div`
|
||||
height: 70vh;
|
||||
`;
|
||||
|
@ -60,7 +60,11 @@ const GridGraph = (): JSX.Element => {
|
||||
i: (index + 1).toString(),
|
||||
x: (index % 2) * 6,
|
||||
Component: (): JSX.Element => (
|
||||
<Graph isDeleted={isDeleted} widget={widgets[index]} />
|
||||
<Graph
|
||||
name={e.id + index}
|
||||
isDeleted={isDeleted}
|
||||
widget={widgets[index]}
|
||||
/>
|
||||
),
|
||||
};
|
||||
});
|
||||
@ -69,7 +73,7 @@ const GridGraph = (): JSX.Element => {
|
||||
...e,
|
||||
y: 0,
|
||||
Component: (): JSX.Element => (
|
||||
<Graph isDeleted={isDeleted} widget={widgets[index]} />
|
||||
<Graph name={e.i + index} isDeleted={isDeleted} widget={widgets[index]} />
|
||||
),
|
||||
}));
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ export const Card = styled(CardComponent)<Props>`
|
||||
}
|
||||
|
||||
.ant-card-body {
|
||||
height: 100%;
|
||||
height: 95%;
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
|
@ -114,6 +114,7 @@ const Application = ({ getWidget }: DashboardProps): JSX.Element => {
|
||||
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
|
||||
onClickhandler(ChartEvent, activeElements, chart, data, 'Application');
|
||||
}}
|
||||
name="application_latency"
|
||||
type="line"
|
||||
data={{
|
||||
datasets: [
|
||||
@ -177,6 +178,7 @@ const Application = ({ getWidget }: DashboardProps): JSX.Element => {
|
||||
<GraphTitle>Request per sec</GraphTitle>
|
||||
<GraphContainer>
|
||||
<FullView
|
||||
name="request_per_sec"
|
||||
noDataGraph
|
||||
fullViewOptions={false}
|
||||
onClickHandler={(event, element, chart, data): void => {
|
||||
@ -206,11 +208,11 @@ const Application = ({ getWidget }: DashboardProps): JSX.Element => {
|
||||
View Traces
|
||||
</Button>
|
||||
|
||||
<Card>
|
||||
<Card>
|
||||
<GraphTitle>Error Percentage (%)</GraphTitle>
|
||||
<GraphContainer>
|
||||
<FullView
|
||||
name="error_percentage_%"
|
||||
noDataGraph
|
||||
fullViewOptions={false}
|
||||
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
|
||||
@ -225,7 +227,6 @@ const Application = ({ getWidget }: DashboardProps): JSX.Element => {
|
||||
/>
|
||||
</GraphContainer>
|
||||
</Card>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col span={12}>
|
||||
|
@ -17,6 +17,7 @@ const DBCall = ({ getWidget }: DBCallProps): JSX.Element => {
|
||||
<GraphTitle>Database Calls RPS</GraphTitle>
|
||||
<GraphContainer>
|
||||
<FullView
|
||||
name="database_call_rps"
|
||||
noDataGraph
|
||||
fullViewOptions={false}
|
||||
widget={getWidget([
|
||||
@ -35,6 +36,7 @@ const DBCall = ({ getWidget }: DBCallProps): JSX.Element => {
|
||||
<GraphTitle>Database Calls Avg Duration (in ms)</GraphTitle>
|
||||
<GraphContainer>
|
||||
<FullView
|
||||
name="database_call_avg_duration"
|
||||
noDataGraph
|
||||
fullViewOptions={false}
|
||||
widget={getWidget([
|
||||
|
@ -17,6 +17,7 @@ const External = ({ getWidget }: ExternalProps): JSX.Element => {
|
||||
<GraphTitle>External Call Error Percentage (%)</GraphTitle>
|
||||
<GraphContainer>
|
||||
<FullView
|
||||
name="external_call_error_percentage"
|
||||
fullViewOptions={false}
|
||||
noDataGraph
|
||||
widget={getWidget([
|
||||
@ -35,6 +36,7 @@ const External = ({ getWidget }: ExternalProps): JSX.Element => {
|
||||
<GraphTitle>External Call duration</GraphTitle>
|
||||
<GraphContainer>
|
||||
<FullView
|
||||
name="external_call_duration"
|
||||
noDataGraph
|
||||
fullViewOptions={false}
|
||||
widget={getWidget([
|
||||
@ -55,6 +57,7 @@ const External = ({ getWidget }: ExternalProps): JSX.Element => {
|
||||
<GraphTitle>External Call RPS(by Address)</GraphTitle>
|
||||
<GraphContainer>
|
||||
<FullView
|
||||
name="external_call_rps_by_address"
|
||||
noDataGraph
|
||||
fullViewOptions={false}
|
||||
widget={getWidget([
|
||||
@ -74,6 +77,7 @@ const External = ({ getWidget }: ExternalProps): JSX.Element => {
|
||||
<GraphContainer>
|
||||
<FullView
|
||||
noDataGraph
|
||||
name="external_call_duration_by_address"
|
||||
fullViewOptions={false}
|
||||
widget={getWidget([
|
||||
{
|
||||
|
@ -30,13 +30,7 @@ export const Col = styled(ColComponent)`
|
||||
`;
|
||||
|
||||
export const GraphContainer = styled.div`
|
||||
min-height: 40vh;
|
||||
max-height: 40vh;
|
||||
|
||||
div {
|
||||
min-height: 40vh;
|
||||
max-height: 40vh;
|
||||
}
|
||||
height: 40vh;
|
||||
`;
|
||||
|
||||
export const GraphTitle = styled(Typography)`
|
||||
|
@ -50,6 +50,7 @@ const WidgetGraph = ({ selectedGraph }: WidgetGraphProps): JSX.Element => {
|
||||
opacity={opacity}
|
||||
data={chartDataSet}
|
||||
GRAPH_TYPES={selectedGraph}
|
||||
name={widgetId || 'legend_widget'}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const CustomGraphContainer = styled.div`
|
||||
min-height: 30vh;
|
||||
height: 30vh;
|
||||
`;
|
||||
|
||||
export const Card = styled(CardComponent)`
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { renderToString } from 'react-dom/server';
|
||||
|
||||
const stringToHTML = function (str: JSX.Element): HTMLElement {
|
||||
const JSXtoHTML = function (str: JSX.Element): HTMLElement {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(renderToString(str), 'text/html');
|
||||
return doc.body.firstChild as HTMLElement;
|
||||
};
|
||||
|
||||
export default stringToHTML;
|
||||
export default JSXtoHTML;
|
@ -47,7 +47,7 @@ const getChartData = ({ queryData }: GetChartDataProps): ChartData => {
|
||||
borderWidth: 1.5,
|
||||
spanGaps: true,
|
||||
animations: false,
|
||||
borderColor: colors[index] || 'red',
|
||||
borderColor: colors[index % colors.length] || 'red',
|
||||
showLine: true,
|
||||
pointRadius: 0,
|
||||
};
|
||||
|
@ -186,7 +186,7 @@ const _UsageExplorer = (props: UsageExplorerProps): JSX.Element => {
|
||||
</Space>
|
||||
|
||||
<Card>
|
||||
<Graph data={data} type="bar" />
|
||||
<Graph name="usage" data={data} type="bar" />
|
||||
</Card>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
@ -8,7 +8,6 @@ export const Card = styled(CardComponent)`
|
||||
}
|
||||
|
||||
.ant-card-body {
|
||||
height: 100%;
|
||||
min-height: 70vh;
|
||||
height: 70vh;
|
||||
}
|
||||
`;
|
||||
|
Loading…
x
Reference in New Issue
Block a user