vargburz
3 years ago
7 changed files with 372 additions and 77 deletions
@ -1,55 +0,0 @@ |
|||||||
import React from 'react'; |
|
||||||
import { PanelProps } from '@grafana/data'; |
|
||||||
import { PanelOptions } from 'types'; |
|
||||||
import { css, cx } from 'emotion'; |
|
||||||
import { stylesFactory, useTheme } from '@grafana/ui'; |
|
||||||
|
|
||||||
interface Props extends PanelProps<PanelOptions> {} |
|
||||||
|
|
||||||
export const SimplePanel: React.FC<Props> = ({ options, data, width, height }) => { |
|
||||||
const theme = useTheme(); |
|
||||||
const styles = getStyles(); |
|
||||||
return ( |
|
||||||
<div |
|
||||||
className={cx( |
|
||||||
styles.wrapper, |
|
||||||
css` |
|
||||||
width: ${width}px; |
|
||||||
height: ${height}px; |
|
||||||
` |
|
||||||
)} |
|
||||||
> |
|
||||||
<svg |
|
||||||
className={styles.svg} |
|
||||||
width={width} |
|
||||||
height={height} |
|
||||||
xmlns="http://www.w3.org/2000/svg" |
|
||||||
xmlnsXlink="http://www.w3.org/1999/xlink" |
|
||||||
viewBox={`-${width / 2} -${height / 2} ${width} ${height}`} |
|
||||||
> |
|
||||||
<g> |
|
||||||
<circle style={{ fill: `${theme.isLight ? theme.palette.greenBase : theme.palette.blue95}` }} r={100} /> |
|
||||||
</g> |
|
||||||
</svg> |
|
||||||
</div> |
|
||||||
); |
|
||||||
}; |
|
||||||
|
|
||||||
const getStyles = stylesFactory(() => { |
|
||||||
return { |
|
||||||
wrapper: css` |
|
||||||
position: relative; |
|
||||||
`,
|
|
||||||
svg: css` |
|
||||||
position: absolute; |
|
||||||
top: 0; |
|
||||||
left: 0; |
|
||||||
`,
|
|
||||||
textBox: css` |
|
||||||
position: absolute; |
|
||||||
bottom: 0; |
|
||||||
left: 0; |
|
||||||
padding: 10px; |
|
||||||
`,
|
|
||||||
}; |
|
||||||
}); |
|
@ -0,0 +1,194 @@ |
|||||||
|
import { applyNullInsertThreshold } from './null_insert'; |
||||||
|
|
||||||
|
import { find } from 'lodash'; |
||||||
|
|
||||||
|
import { |
||||||
|
DataFrame, |
||||||
|
dateTime, |
||||||
|
Field, |
||||||
|
FieldType, |
||||||
|
getColorForTheme, |
||||||
|
getFieldDisplayName, |
||||||
|
getTimeField, |
||||||
|
TimeRange, |
||||||
|
} from '@grafana/data'; |
||||||
|
import { colors } from '@grafana/ui'; |
||||||
|
import config from 'grafana/app/core/config'; |
||||||
|
import TimeSeries from 'grafana/app/core/time_series2'; |
||||||
|
|
||||||
|
type Options = { |
||||||
|
dataList: DataFrame[]; |
||||||
|
range?: TimeRange; |
||||||
|
}; |
||||||
|
|
||||||
|
export class DataProcessor { |
||||||
|
constructor(private panel: any) {} |
||||||
|
|
||||||
|
getSeriesList(options: Options): TimeSeries[] { |
||||||
|
const list: TimeSeries[] = []; |
||||||
|
const { dataList, range } = options; |
||||||
|
|
||||||
|
if (!dataList || !dataList.length) { |
||||||
|
return list; |
||||||
|
} |
||||||
|
|
||||||
|
for (let i = 0; i < dataList.length; i++) { |
||||||
|
let series = dataList[i]; |
||||||
|
let { timeField } = getTimeField(series); |
||||||
|
|
||||||
|
if (!timeField) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
series = applyNullInsertThreshold(series, timeField.name); |
||||||
|
timeField = getTimeField(series).timeField!; // use updated length
|
||||||
|
|
||||||
|
for (let j = 0; j < series.fields.length; j++) { |
||||||
|
const field = series.fields[j]; |
||||||
|
|
||||||
|
if (field.type !== FieldType.number) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
const name = getFieldDisplayName(field, series, dataList); |
||||||
|
const datapoints = []; |
||||||
|
|
||||||
|
for (let r = 0; r < series.length; r++) { |
||||||
|
datapoints.push([field.values.get(r), dateTime(timeField.values.get(r)).valueOf()]); |
||||||
|
} |
||||||
|
|
||||||
|
list.push(this.toTimeSeries(field, name, i, j, datapoints, list.length, range)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Merge all the rows if we want to show a histogram
|
||||||
|
if (this.panel?.xaxis?.mode === 'histogram' && !this.panel?.stack && list.length > 1) { |
||||||
|
const first = list[0]; |
||||||
|
// @ts-ignore
|
||||||
|
first.alias = first.aliasEscaped = 'Count'; |
||||||
|
|
||||||
|
for (let i = 1; i < list.length; i++) { |
||||||
|
first.datapoints = first.datapoints.concat(list[i].datapoints); |
||||||
|
} |
||||||
|
|
||||||
|
return [first]; |
||||||
|
} |
||||||
|
|
||||||
|
return list; |
||||||
|
} |
||||||
|
|
||||||
|
private toTimeSeries( |
||||||
|
field: Field, |
||||||
|
alias: string, |
||||||
|
dataFrameIndex: number, |
||||||
|
fieldIndex: number, |
||||||
|
datapoints: any[][], |
||||||
|
index: number, |
||||||
|
range?: TimeRange |
||||||
|
) { |
||||||
|
const colorIndex = index % colors.length; |
||||||
|
const color = colors[colorIndex]; |
||||||
|
|
||||||
|
const series = new TimeSeries({ |
||||||
|
datapoints: datapoints || [], |
||||||
|
alias: alias, |
||||||
|
color: getColorForTheme(color, config.theme), |
||||||
|
unit: field.config ? field.config.unit : undefined, |
||||||
|
dataFrameIndex, |
||||||
|
fieldIndex, |
||||||
|
}); |
||||||
|
|
||||||
|
if (datapoints && datapoints.length > 0 && range) { |
||||||
|
const last = datapoints[datapoints.length - 1][1]; |
||||||
|
const from = range.from; |
||||||
|
|
||||||
|
if (last - from.valueOf() < -10000) { |
||||||
|
// If the data is in reverse order
|
||||||
|
const first = datapoints[0][1]; |
||||||
|
if (first - from.valueOf() < -10000) { |
||||||
|
series.isOutsideRange = true; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return series; |
||||||
|
} |
||||||
|
|
||||||
|
setPanelDefaultsForNewXAxisMode() { |
||||||
|
switch (this.panel.xaxis.mode) { |
||||||
|
case 'time': { |
||||||
|
this.panel.bars = false; |
||||||
|
this.panel.lines = true; |
||||||
|
this.panel.points = false; |
||||||
|
this.panel.legend.show = true; |
||||||
|
this.panel.tooltip.shared = true; |
||||||
|
this.panel.xaxis.values = []; |
||||||
|
break; |
||||||
|
} |
||||||
|
case 'series': { |
||||||
|
this.panel.bars = true; |
||||||
|
this.panel.lines = false; |
||||||
|
this.panel.points = false; |
||||||
|
this.panel.stack = false; |
||||||
|
this.panel.legend.show = false; |
||||||
|
this.panel.tooltip.shared = false; |
||||||
|
this.panel.xaxis.values = ['total']; |
||||||
|
break; |
||||||
|
} |
||||||
|
case 'histogram': { |
||||||
|
this.panel.bars = true; |
||||||
|
this.panel.lines = false; |
||||||
|
this.panel.points = false; |
||||||
|
this.panel.stack = false; |
||||||
|
this.panel.legend.show = false; |
||||||
|
this.panel.tooltip.shared = false; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
validateXAxisSeriesValue() { |
||||||
|
switch (this.panel.xaxis.mode) { |
||||||
|
case 'series': { |
||||||
|
if (this.panel.xaxis.values.length === 0) { |
||||||
|
this.panel.xaxis.values = ['total']; |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
const validOptions = this.getXAxisValueOptions({}); |
||||||
|
const found: any = find(validOptions, { value: this.panel.xaxis.values[0] }); |
||||||
|
if (!found) { |
||||||
|
this.panel.xaxis.values = ['total']; |
||||||
|
} |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
getXAxisValueOptions(options: any) { |
||||||
|
switch (this.panel.xaxis.mode) { |
||||||
|
case 'series': { |
||||||
|
return [ |
||||||
|
{ text: 'Avg', value: 'avg' }, |
||||||
|
{ text: 'Min', value: 'min' }, |
||||||
|
{ text: 'Max', value: 'max' }, |
||||||
|
{ text: 'Total', value: 'total' }, |
||||||
|
{ text: 'Count', value: 'count' }, |
||||||
|
]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return []; |
||||||
|
} |
||||||
|
|
||||||
|
pluckDeep(obj: any, property: string) { |
||||||
|
const propertyParts = property.split('.'); |
||||||
|
let value = obj; |
||||||
|
for (let i = 0; i < propertyParts.length; ++i) { |
||||||
|
if (value[propertyParts[i]]) { |
||||||
|
value = value[propertyParts[i]]; |
||||||
|
} else { |
||||||
|
return undefined; |
||||||
|
} |
||||||
|
} |
||||||
|
return value; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,129 @@ |
|||||||
|
// file is coppied from: grafana-ui\src\components\GraphNG\nullInsertThreshold.ts
|
||||||
|
|
||||||
|
import { ArrayVector, DataFrame, FieldType } from '@grafana/data'; |
||||||
|
|
||||||
|
type InsertMode = (prev: number, next: number, threshold: number) => number; |
||||||
|
|
||||||
|
const INSERT_MODES = { |
||||||
|
threshold: (prev: number, next: number, threshold: number) => prev + threshold, |
||||||
|
midpoint: (prev: number, next: number, threshold: number) => (prev + next) / 2, |
||||||
|
// previous time + 1ms to prevent StateTimeline from forward-interpolating prior state
|
||||||
|
plusone: (prev: number, next: number, threshold: number) => prev + 1, |
||||||
|
}; |
||||||
|
|
||||||
|
export function applyNullInsertThreshold( |
||||||
|
frame: DataFrame, |
||||||
|
refFieldName?: string | null, |
||||||
|
refFieldPseudoMax: number | null = null, |
||||||
|
insertMode: InsertMode = INSERT_MODES.threshold |
||||||
|
): DataFrame { |
||||||
|
if (frame.length === 0) { |
||||||
|
return frame; |
||||||
|
} |
||||||
|
|
||||||
|
const refField = frame.fields.find((field) => { |
||||||
|
// note: getFieldDisplayName() would require full DF[]
|
||||||
|
return refFieldName != null ? field.name === refFieldName : field.type === FieldType.time; |
||||||
|
}); |
||||||
|
|
||||||
|
if (refField == null) { |
||||||
|
return frame; |
||||||
|
} |
||||||
|
|
||||||
|
const thresholds = frame.fields.map((field) => field.config.custom?.insertNulls ?? refField.config.interval ?? null); |
||||||
|
|
||||||
|
const uniqueThresholds = new Set<number>(thresholds); |
||||||
|
|
||||||
|
uniqueThresholds.delete(null as any); |
||||||
|
|
||||||
|
if (uniqueThresholds.size === 0) { |
||||||
|
return frame; |
||||||
|
} |
||||||
|
|
||||||
|
if (uniqueThresholds.size === 1) { |
||||||
|
const threshold = uniqueThresholds.values().next().value; |
||||||
|
|
||||||
|
if (threshold <= 0) { |
||||||
|
return frame; |
||||||
|
} |
||||||
|
|
||||||
|
const refValues = refField.values.toArray(); |
||||||
|
|
||||||
|
const frameValues = frame.fields.map((field) => field.values.toArray()); |
||||||
|
|
||||||
|
const filledFieldValues = nullInsertThreshold(refValues, frameValues, threshold, refFieldPseudoMax, insertMode); |
||||||
|
|
||||||
|
if (filledFieldValues === frameValues) { |
||||||
|
return frame; |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
...frame, |
||||||
|
length: filledFieldValues[0].length, |
||||||
|
fields: frame.fields.map((field, i) => ({ |
||||||
|
...field, |
||||||
|
values: new ArrayVector(filledFieldValues[i]), |
||||||
|
})), |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: unique threshold-per-field (via overrides) is unimplemented
|
||||||
|
// should be done by processing each (refField + thresholdA-field1 + thresholdA-field2...)
|
||||||
|
// as a separate nullInsertThreshold() dataset, then re-join into single dataset via join()
|
||||||
|
return frame; |
||||||
|
} |
||||||
|
|
||||||
|
function nullInsertThreshold( |
||||||
|
refValues: number[], |
||||||
|
frameValues: any[][], |
||||||
|
threshold: number, |
||||||
|
// will insert a trailing null when refFieldPseudoMax > last datapoint + threshold
|
||||||
|
refFieldPseudoMax: number | null = null, |
||||||
|
getInsertValue: InsertMode |
||||||
|
) { |
||||||
|
const len = refValues.length; |
||||||
|
let prevValue: number = refValues[0]; |
||||||
|
const refValuesNew: number[] = [prevValue]; |
||||||
|
|
||||||
|
for (let i = 1; i < len; i++) { |
||||||
|
const curValue = refValues[i]; |
||||||
|
|
||||||
|
if (curValue - prevValue > threshold) { |
||||||
|
refValuesNew.push(getInsertValue(prevValue, curValue, threshold)); |
||||||
|
} |
||||||
|
|
||||||
|
refValuesNew.push(curValue); |
||||||
|
|
||||||
|
prevValue = curValue; |
||||||
|
} |
||||||
|
|
||||||
|
if (refFieldPseudoMax != null && prevValue + threshold <= refFieldPseudoMax) { |
||||||
|
refValuesNew.push(getInsertValue(prevValue, refFieldPseudoMax, threshold)); |
||||||
|
} |
||||||
|
|
||||||
|
const filledLen = refValuesNew.length; |
||||||
|
|
||||||
|
if (filledLen === len) { |
||||||
|
return frameValues; |
||||||
|
} |
||||||
|
|
||||||
|
const filledFieldValues: any[][] = []; |
||||||
|
|
||||||
|
for (let fieldValues of frameValues) { |
||||||
|
let filledValues; |
||||||
|
|
||||||
|
if (fieldValues !== refValues) { |
||||||
|
filledValues = Array(filledLen); |
||||||
|
|
||||||
|
for (let i = 0, j = 0; i < filledLen; i++) { |
||||||
|
filledValues[i] = refValues[j] === refValuesNew[i] ? fieldValues[j++] : null; |
||||||
|
} |
||||||
|
} else { |
||||||
|
filledValues = refValuesNew; |
||||||
|
} |
||||||
|
|
||||||
|
filledFieldValues.push(filledValues); |
||||||
|
} |
||||||
|
|
||||||
|
return filledFieldValues; |
||||||
|
} |
Loading…
Reference in new issue