/* * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ import { EuiEmptyPrompt, EuiText } from '@elastic/eui'; import _ from 'lodash'; import moment from 'moment'; // @ts-ignore import Plotly, { PlotData } from 'plotly.js-dist'; import React, { useEffect } from 'react'; import plotComponentFactory from 'react-plotly.js/factory'; import { UiSettingsClient } from 'src/core/public/ui_settings'; import { ExprVis } from 'src/plugins/visualizations/public'; import { v1 as uuid } from 'uuid'; import { GanttParams, GanttSuccessResponse } from '../gantt_vis_type'; export function GanttChart({ config, vis, visData, visParams, }: { config: UiSettingsClient; vis: ExprVis; visData: GanttSuccessResponse; visParams: GanttParams; }) { const PlotComponent = plotComponentFactory(Plotly); const getGanttData = (): { data: PlotData[]; tickvals: number[]; ticktext: string[]; yLabels: string[]; yTexts: string[]; } => { const source: any[] = visData.source; const data: PlotData[] = []; if (source.length === 0) return { data, tickvals: [], ticktext: [], yLabels: [], yTexts: [] }; const getStartTime = typeof _.get(source[0], visParams.startTimeField) === 'string' ? (document: any): number => Date.parse(_.get(document, visParams.startTimeField)) : (document: any): number => _.get(document, visParams.startTimeField); // source is ordered by startTimeField desc, last trace in source is the earliest trace and should start at 0 const minStartTime: number = getStartTime(source[source.length - 1]); let maxEndTime: number = 0; source.forEach((document) => { const rawStartTime: number = getStartTime(document); // subtract with start time of earliest trace to get relative start time const startTime: number = rawStartTime - minStartTime; const duration: number = Number(_.get(document, visParams.durationField)); maxEndTime = Math.max(maxEndTime, rawStartTime + duration); const label: string = _.get(document, visParams.labelField); const uniqueLabel: string = label + uuid(); data.push( { x: [startTime], y: [uniqueLabel], type: 'bar', orientation: 'h', width: 0.4, marker: { color: 'rgba(0, 0, 0, 0)' }, hoverinfo: 'none', showlegend: false, } as PlotData, { x: [duration], y: [uniqueLabel], type: 'bar', orientation: 'h', width: 0.4, name: label || 'undefined', text: [duration && duration.toString()], hovertemplate: '%{text}', marker: { color: visParams.colors, }, } as PlotData ); }); // get unique labels from traces const yLabels = data.map((d) => d.y[0]).filter((label, i, self) => self.indexOf(label) === i); // remove uuid when displaying y-ticks const yTexts = yLabels.map((label) => label.substring(0, label.length - 36)); return { data, ...getTicks(minStartTime, maxEndTime), yLabels, yTexts }; }; const getTicks = (minStartTime: number, maxEndTime: number) => { const ticks = 5; const interval = Math.round((maxEndTime - minStartTime) / ticks); const tickvals = Array.from({ length: ticks }, (v, i: number) => interval * i); const ticktext = tickvals.map((val: number) => toTimeString(val + minStartTime)); return { tickvals, ticktext }; }; const toTimeString = (val: number) => { let divisor = 1; const valStr = Math.floor(val).toString(); if (valStr.length <= 10) // unit is seconds divisor = 0.001; else if (valStr.length <= 13) // unit is milliseconds divisor = 1; else if (valStr.length <= 16) // unit is microseconds divisor = 1000; else if (valStr.length <= 19) // unit is nanoseconds divisor = 1000 * 1000; return moment(val / divisor).format(visParams.timeFormat); }; const ganttData = getGanttData(); // workaround to disable 'data-loading' filter effects useEffect(() => { let node: Element | null = document.querySelector('#plotly-gantt-chart'); while (node && node?.tagName !== 'HTML') { if (node?.className.includes('visEditor')) { break; } if (node?.getAttribute('data-loading')) { node.setAttribute('data-type', 'plotlyGanttChart'); break; } node = node?.parentElement; } }, []); return ( <> {visParams.labelField && visParams.startTimeField && visParams.durationField && ganttData.data.length > 0 ? ( ) : visParams.labelField && visParams.startTimeField && visParams.durationField ? ( No data} body={No data matching the selected filter.} /> ) : ( No data} body={ Specify data to plot the chart using the Data & Options panel
on the right.
} /> )} ); }