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