diff --git a/frontend/src/components/Graph/Legend/index.tsx b/frontend/src/components/Graph/Legend/index.tsx deleted file mode 100644 index 41255241db..0000000000 --- a/frontend/src/components/Graph/Legend/index.tsx +++ /dev/null @@ -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 ( - - - {text} - - ); -}; - -interface LegendProps { - text: string; - color: string; -} - -export default Legend; diff --git a/frontend/src/components/Graph/Legend/styles.ts b/frontend/src/components/Graph/Legend/styles.ts deleted file mode 100644 index ac627bf0ea..0000000000 --- a/frontend/src/components/Graph/Legend/styles.ts +++ /dev/null @@ -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` - background-color: ${({ color }): string => color}; - border-radius: 50%; - width: 20px; - height: 20px; - margin-right: 0.5rem; -`; diff --git a/frontend/src/components/Graph/Plugin/Legend.ts b/frontend/src/components/Graph/Plugin/Legend.ts new file mode 100644 index 0000000000..6c4826e5f7 --- /dev/null +++ b/frontend/src/components/Graph/Plugin/Legend.ts @@ -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 => { + 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); + } + }); + }, + }; +}; diff --git a/frontend/src/components/Graph/Plugin/index.ts b/frontend/src/components/Graph/Plugin/index.ts new file mode 100644 index 0000000000..6adb3cc7d3 --- /dev/null +++ b/frontend/src/components/Graph/Plugin/index.ts @@ -0,0 +1 @@ +export * from './Legend'; diff --git a/frontend/src/components/Graph/index.tsx b/frontend/src/components/Graph/index.tsx index 45041ad505..b6905495d4 100644 --- a/frontend/src/components/Graph/index.tsx +++ b/frontend/src/components/Graph/index.tsx @@ -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((state) => state.app); const chartRef = useRef(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 ; + return ( +
+ + +
+ ); }; interface GraphProps { @@ -174,6 +165,7 @@ interface GraphProps { isStacked?: boolean; label?: string[]; onClickHandler?: graphOnClickHandler; + name: string; } export type graphOnClickHandler = ( diff --git a/frontend/src/components/Graph/styles.ts b/frontend/src/components/Graph/styles.ts index 204a78a57f..e0fa56dee1 100644 --- a/frontend/src/components/Graph/styles.ts +++ b/frontend/src/components/Graph/styles.ts @@ -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 */ + } `; diff --git a/frontend/src/container/GridGraphComponent/index.tsx b/frontend/src/container/GridGraphComponent/index.tsx index 6849903c2f..ba3107f96b 100644 --- a/frontend/src/container/GridGraphComponent/index.tsx +++ b/frontend/src/container/GridGraphComponent/index.tsx @@ -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; diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx b/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx index 1d66a60488..b2cbc0366d 100644 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx +++ b/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx @@ -27,6 +27,7 @@ const FullView = ({ fullViewOptions = true, onClickHandler, noDataGraph = false, + name, }: FullViewProps): JSX.Element => { const { minTime, maxTime } = useSelector( (state) => state.globalTime, @@ -189,18 +190,19 @@ const FullView = ({ )} - - - + {/* */} + + {/* */} ); }; @@ -217,6 +219,7 @@ interface FullViewProps { fullViewOptions?: boolean; onClickHandler?: graphOnClickHandler; noDataGraph?: boolean; + name: string; } export default FullView; diff --git a/frontend/src/container/GridGraphLayout/Graph/index.tsx b/frontend/src/container/GridGraphLayout/Graph/index.tsx index e1a21c383c..60a086befa 100644 --- a/frontend/src/container/GridGraphLayout/Graph/index.tsx +++ b/frontend/src/container/GridGraphLayout/Graph/index.tsx @@ -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({ loading: true, @@ -166,7 +167,9 @@ const GridCardGraph = ({ width="85%" destroyOnClose > - + + + @@ -198,6 +202,7 @@ interface DispatchProps { interface GridCardGraphProps extends DispatchProps { widget: Widgets; isDeleted: React.MutableRefObject; + name: string; } const mapDispatchToProps = ( diff --git a/frontend/src/container/GridGraphLayout/Graph/styles.ts b/frontend/src/container/GridGraphLayout/Graph/styles.ts index 156b7553d7..61f4c7c9c9 100644 --- a/frontend/src/container/GridGraphLayout/Graph/styles.ts +++ b/frontend/src/container/GridGraphLayout/Graph/styles.ts @@ -11,3 +11,7 @@ export const Modal = styled(ModalComponent)` min-height: ${({ height = '80vh' }): string => height}; } `; + +export const FullViewContainer = styled.div` + height: 70vh; +`; diff --git a/frontend/src/container/GridGraphLayout/index.tsx b/frontend/src/container/GridGraphLayout/index.tsx index 49fbd0df25..c72885f9ef 100644 --- a/frontend/src/container/GridGraphLayout/index.tsx +++ b/frontend/src/container/GridGraphLayout/index.tsx @@ -60,7 +60,11 @@ const GridGraph = (): JSX.Element => { i: (index + 1).toString(), x: (index % 2) * 6, Component: (): JSX.Element => ( - + ), }; }); @@ -69,7 +73,7 @@ const GridGraph = (): JSX.Element => { ...e, y: 0, Component: (): JSX.Element => ( - + ), })); } diff --git a/frontend/src/container/GridGraphLayout/styles.ts b/frontend/src/container/GridGraphLayout/styles.ts index d8172e4ebf..0b8f4cfc89 100644 --- a/frontend/src/container/GridGraphLayout/styles.ts +++ b/frontend/src/container/GridGraphLayout/styles.ts @@ -13,7 +13,7 @@ export const Card = styled(CardComponent)` } .ant-card-body { - height: 100%; + height: 95%; padding: 0; } `; diff --git a/frontend/src/container/MetricsApplication/Tabs/Application.tsx b/frontend/src/container/MetricsApplication/Tabs/Application.tsx index d31a12a8a7..90e85230a1 100644 --- a/frontend/src/container/MetricsApplication/Tabs/Application.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/Application.tsx @@ -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 => { Request per sec { @@ -207,24 +209,23 @@ const Application = ({ getWidget }: DashboardProps): JSX.Element => { - - Error Percentage (%) - - { - onClickhandler(ChartEvent, activeElements, chart, data, 'Error'); - }} - widget={getWidget([ - { - query: `sum(rate(signoz_calls_total{service_name="${servicename}", span_kind="SPAN_KIND_SERVER", status_code="STATUS_CODE_ERROR"}[1m]) OR rate(signoz_calls_total{service_name="${servicename}", span_kind="SPAN_KIND_SERVER", http_status_code=~"5.."}[1m]) OR vector(0))*100/sum(rate(signoz_calls_total{service_name="${servicename}", span_kind="SPAN_KIND_SERVER"}[1m]))`, - legend: 'Error Percentage (%)', - }, - ])} - /> - - + Error Percentage (%) + + { + onClickhandler(ChartEvent, activeElements, chart, data, 'Error'); + }} + widget={getWidget([ + { + query: `sum(rate(signoz_calls_total{service_name="${servicename}", span_kind="SPAN_KIND_SERVER", status_code="STATUS_CODE_ERROR"}[1m]) OR rate(signoz_calls_total{service_name="${servicename}", span_kind="SPAN_KIND_SERVER", http_status_code=~"5.."}[1m]) OR vector(0))*100/sum(rate(signoz_calls_total{service_name="${servicename}", span_kind="SPAN_KIND_SERVER"}[1m]))`, + legend: 'Error Percentage (%)', + }, + ])} + /> + diff --git a/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx b/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx index 45c081d1da..44fda1e230 100644 --- a/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx @@ -17,6 +17,7 @@ const DBCall = ({ getWidget }: DBCallProps): JSX.Element => { Database Calls RPS { Database Calls Avg Duration (in ms) { External Call Error Percentage (%) { External Call duration { External Call RPS(by Address) { { opacity={opacity} data={chartDataSet} GRAPH_TYPES={selectedGraph} + name={widgetId || 'legend_widget'} /> ); }; diff --git a/frontend/src/container/TraceCustomVisualization/styles.ts b/frontend/src/container/TraceCustomVisualization/styles.ts index 6a06cbd717..069feaad73 100644 --- a/frontend/src/container/TraceCustomVisualization/styles.ts +++ b/frontend/src/container/TraceCustomVisualization/styles.ts @@ -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)` diff --git a/frontend/src/lib/stringToHTML.ts b/frontend/src/lib/JSXtoHTML.ts similarity index 68% rename from frontend/src/lib/stringToHTML.ts rename to frontend/src/lib/JSXtoHTML.ts index b5fb77afbf..48a758d085 100644 --- a/frontend/src/lib/stringToHTML.ts +++ b/frontend/src/lib/JSXtoHTML.ts @@ -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; diff --git a/frontend/src/lib/getChartData.ts b/frontend/src/lib/getChartData.ts index e20df3db49..d745706a94 100644 --- a/frontend/src/lib/getChartData.ts +++ b/frontend/src/lib/getChartData.ts @@ -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, }; diff --git a/frontend/src/modules/Usage/UsageExplorer.tsx b/frontend/src/modules/Usage/UsageExplorer.tsx index f6bbe06c7b..a7566b1bd0 100644 --- a/frontend/src/modules/Usage/UsageExplorer.tsx +++ b/frontend/src/modules/Usage/UsageExplorer.tsx @@ -186,7 +186,7 @@ const _UsageExplorer = (props: UsageExplorerProps): JSX.Element => { - + ); diff --git a/frontend/src/modules/Usage/styles.ts b/frontend/src/modules/Usage/styles.ts index 26efebc856..4bb27fc585 100644 --- a/frontend/src/modules/Usage/styles.ts +++ b/frontend/src/modules/Usage/styles.ts @@ -8,7 +8,6 @@ export const Card = styled(CardComponent)` } .ant-card-body { - height: 100%; - min-height: 70vh; + height: 70vh; } `;