From 4a6e7da58082c60697101a303e79a7ee15455f5c Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Fri, 27 Nov 2020 11:06:18 +0100 Subject: [PATCH] feat: mini gauge integration --- chronograf-ui | 1 + giraffe/README.md | 186 ++++---- giraffe/src/components/GaugeMini.tsx | 412 ++++++++++-------- giraffe/src/components/GaugeMiniLayer.tsx | 13 +- .../LatestMultipleValueTransform.tsx | 46 ++ giraffe/src/components/SizedTable.tsx | 9 +- giraffe/src/constants/gaugeMiniStyles.ts | 8 +- giraffe/src/types/index.ts | 6 +- stories/src/data/gaugeMiniLayer.ts | 37 ++ stories/src/gaugeMini.stories.tsx | 17 +- 10 files changed, 438 insertions(+), 297 deletions(-) create mode 160000 chronograf-ui create mode 100644 giraffe/src/components/LatestMultipleValueTransform.tsx create mode 100644 stories/src/data/gaugeMiniLayer.ts diff --git a/chronograf-ui b/chronograf-ui new file mode 160000 index 00000000..e1de5936 --- /dev/null +++ b/chronograf-ui @@ -0,0 +1 @@ +Subproject commit e1de5936e5ba9f1c347b5f8d5c59e6d31d41e617 diff --git a/giraffe/README.md b/giraffe/README.md index faa8b69c..3035d93b 100644 --- a/giraffe/README.md +++ b/giraffe/README.md @@ -608,10 +608,21 @@ TableGraphLayerConfig uses the `fluxResponse` property from `config` as the data - **GaugeLayerConfig**: _Object._ Maximum one per ``. Properties are: - All values (excluding **type**) are Optional and their defaults is defined in precreated theme `GAUGE_MINI_THEME_BULLET_DARK` - - - **type**: _'gauge mini'. **Required**._ Specifies that this LayerConfig is a gauge mini layer. + For multiple bars values has to be passed as columns (not fields). That can be done in flux by + ``` + import "influxdata/influxdb/v1" + + |> v1.fieldsAsCols() + ``` + + All values (excluding **type**) are Optional and their defaults is defined by precreated theme `GAUGE_MINI_THEME_BULLET_DARK` + - **type**: _'gauge mini'. **Required**._ Specifies that this LayerConfig is a gauge mini layer. + + - **bars** _{\_field: string; label?: string}[]_ per bar settings: + - __field_ giraffe table column name (that contains value), can be set to `'_value'` when there is only one field present + - _label_ optional label for bar + - **mode**: _'progress' | 'bullet'._ - `'bullet'` backgroud bar is colored and value bar has always secondary color - `'progress'` value bar is colored and backgroud bar has always secondary color @@ -659,8 +670,6 @@ TableGraphLayerConfig uses the `fluxResponse` property from `config` as the data - **labelMainFontColor** _string_ Main label color. Bar labels - - **labelBars** _{\_field: string; label: string}[]_ Labels for individual fields/bars. - - **labelBarsFontSize** _number_ Bar labels font size - **labelBarsFontColor** _string_ Bar labels font color @@ -691,90 +700,89 @@ TableGraphLayerConfig uses the `fluxResponse` property from `config` as the data **Precreated themes** - `GAUGE_MINI_THEME_BULLET_DARK` - -``` -{ - type: 'gauge mini', - mode: 'bullet', - textMode: 'follow', - - valueHeight: 18, - gaugeHeight: 25, - valueRounding: 2, - gaugeRounding: 3, - barPaddings: 5, - sidePaddings: 20, - oveflowFraction: 0.03, - - gaugeColors: [ - {value: 0, type: 'min', hex: InfluxColors.Krypton}, - {value: 50, type: 'threshold', hex: InfluxColors.Sulfur}, - {value: 75, type: 'threshold', hex: InfluxColors.Topaz}, - {value: 100, type: 'max', hex: InfluxColors.Topaz}, - ] as Color[], - colorSecondary: InfluxColors.Kevlar, - - labelMain: '', - labelMainFontSize: 13, - labelMainFontColor: InfluxColors.Ghost, - - labelBars: [], - labelBarsFontSize: 11, - labelBarsFontColor: InfluxColors.Forge, - - valuePadding: 5, - valueFontSize: 12, - valueFontColorOutside: InfluxColors.Raven, - valueFontColorInside: InfluxColors.Cloud, - valueFormater: (num: number) => num.toFixed(0), - - axesSteps: 'thresholds', - axesFontSize: 11, - axesFontColor: InfluxColors.Forge, - axesFormater: (num: number) => num.toFixed(0), -} -``` - - `GAUGE_MINI_THEME_PROGRESS_DARK` -``` -{ - type: 'gauge mini', - mode: 'progress', - textMode: 'follow', - - valueHeight: 20, - gaugeHeight: 20, - valueRounding: 3, - gaugeRounding: 3, - barPaddings: 5, - sidePaddings: 20, - oveflowFraction: 0.03, - - gaugeColors: [ - {value: 0, type: 'min', hex: InfluxColors.Krypton}, - {value: 100, type: 'max', hex: InfluxColors.Topaz}, - ] as Color[], - colorSecondary: InfluxColors.Kevlar, - - labelMain: '', - labelMainFontSize: 13, - labelMainFontColor: InfluxColors.Ghost, - - labelBars: [], - labelBarsFontSize: 11, - labelBarsFontColor: InfluxColors.Forge, - - valuePadding: 5, - valueFontSize: 18, - valueFontColorInside: InfluxColors.Raven, - valueFontColorOutside: InfluxColors.Cloud, - valueFormater: (val: number) => val.toFixed(0), - - axesSteps: undefined as any, - axesFontSize: 11, - axesFontColor: InfluxColors.Forge, - axesFormater: (val: number) => val.toFixed(0), -} -``` + ``` + { + type: 'gauge mini', + mode: 'bullet', + textMode: 'follow', + bars: [], + + valueHeight: 18, + gaugeHeight: 25, + valueRounding: 2, + gaugeRounding: 3, + barPaddings: 5, + sidePaddings: 20, + oveflowFraction: 0.03, + + gaugeColors: [ + {value: 0, type: 'min', hex: InfluxColors.Krypton}, + {value: 50, type: 'threshold', hex: InfluxColors.Sulfur}, + {value: 75, type: 'threshold', hex: InfluxColors.Topaz}, + {value: 100, type: 'max', hex: InfluxColors.Topaz}, + ] as Color[], + colorSecondary: InfluxColors.Kevlar, + + labelMain: '', + labelMainFontSize: 13, + labelMainFontColor: InfluxColors.Ghost, + + labelBarsFontSize: 11, + labelBarsFontColor: InfluxColors.Forge, + + valuePadding: 5, + valueFontSize: 12, + valueFontColorOutside: InfluxColors.Raven, + valueFontColorInside: InfluxColors.Cloud, + valueFormater: (val: number) => val.toFixed(0), + + axesSteps: 'thresholds', + axesFontSize: 11, + axesFontColor: InfluxColors.Forge, + axesFormater: (val: number) => val.toFixed(0), + } + ``` + - `GAUGE_MINI_THEME_PROGRESS_DARK` + ``` + { + type: 'gauge mini', + mode: 'progress', + textMode: 'follow', + bars: [], + + valueHeight: 20, + gaugeHeight: 20, + valueRounding: 3, + gaugeRounding: 3, + barPaddings: 5, + sidePaddings: 20, + oveflowFraction: 0.03, + + gaugeColors: [ + {value: 0, type: 'min', hex: InfluxColors.Krypton}, + {value: 100, type: 'max', hex: InfluxColors.Topaz}, + ] as Color[], + colorSecondary: InfluxColors.Kevlar, + + labelMain: '', + labelMainFontSize: 13, + labelMainFontColor: InfluxColors.Ghost, + + labelBarsFontSize: 11, + labelBarsFontColor: InfluxColors.Forge, + + valuePadding: 5, + valueFontSize: 18, + valueFontColorInside: InfluxColors.Raven, + valueFontColorOutside: InfluxColors.Cloud, + valueFormater: (val: number) => val.toFixed(0), + + axesSteps: undefined as any, + axesFontSize: 11, + axesFontColor: InfluxColors.Forge, + axesFormater: (val: number) => val.toFixed(0), + } + ``` - **SingleStatLayerConfig**: _Object._ No limit but generally one per ``. Using more than one requires additional styling through configuration and is not recommended. diff --git a/giraffe/src/components/GaugeMini.tsx b/giraffe/src/components/GaugeMini.tsx index df90d6cb..58198eab 100644 --- a/giraffe/src/components/GaugeMini.tsx +++ b/giraffe/src/components/GaugeMini.tsx @@ -5,22 +5,19 @@ import {scaleLinear} from 'd3-scale' import {range} from 'd3-array' import {Color, GaugeMiniLayerConfig} from '../types' -// todo: remove before minigauge release -export const t = (x: number, y: number) => ({ - transform: `translate(${x},${y})`, -}) - const throwReturn = (msg: string): T => { throw new Error(msg) } -interface IProps { +interface Props { width: number height: number value: number | {_field: string; value: number}[] theme: Required } +const barCssClass = 'gauge-mini-bar' + //#region colors export type Colors = { @@ -28,31 +25,29 @@ export type Colors = { max: Color secondary: string thresholds: Color[] - // targets: Color[], } export const getColors = (theme: Required): Colors => { - const {colorSecondary: secondary, gaugeColors: colorsAndTargets} = theme + const {colorSecondary: secondary, gaugeColors} = theme - colorsAndTargets.forEach( + gaugeColors.forEach( ({hex, name}) => d3Color(hex) ?? throwReturn(`Object "${hex}" isn"t valid color for name:${name}`) ) - const min: Color = - colorsAndTargets.find(x => x.type === 'min') ?? - throwReturn('color of type min must be defined') - const max: Color = - colorsAndTargets.find(x => x.type === 'max') ?? - throwReturn('color of type max must be defined') - - const thresholds = colorsAndTargets - .filter(({type}) => type === 'threshold') - .sort(({value: a}, {value: b}) => a - b) - // const targets = colorsAndTargets.filter(({ type }) => type === "target").sort(({ value: a }, { value: b }) => a - b); - - return {max, min, secondary, /* targets, */ thresholds} + return { + min: + gaugeColors.find(x => x.type === 'min') ?? + throwReturn('color of type min must be defined'), + max: + gaugeColors.find(x => x.type === 'max') ?? + throwReturn('color of type max must be defined'), + thresholds: gaugeColors + .filter(({type}) => type === 'threshold') + .sort(({value: a}, {value: b}) => a - b), + secondary, + } } //#endregion colors @@ -65,6 +60,7 @@ type TSvgTextRectProps = { /** * Helper component that returns rect when children changes. Usefull for calculating text box size. + * !onRectChanged called only when children changes! */ export const SvgTextRect: React.FC = props => { const {onRectChanged = () => {}} = props @@ -73,10 +69,12 @@ export const SvgTextRect: React.FC = props => { useEffect(() => { const rect = textRef.current?.getBBox() - if (!rect) return + if (!rect) { + return + } onRectChanged(rect) - }, [props.children]) + }, [props.children, onRectChanged]) return ( <> @@ -85,6 +83,10 @@ export const SvgTextRect: React.FC = props => { ) } +/** + * Helper component for centering content. + * !Doesn't react on content size changed. Recententering is done manualy by changing refreshToken! + */ const AutoCenterGroup: FunctionComponent<{ enabled?: boolean refreshToken?: number | string @@ -110,14 +112,16 @@ const AutoCenterGroup: FunctionComponent<{ | SVGGraphicsElement | undefined)?.getBoundingClientRect() - if (!box || !boxParent) return + if (!box || !boxParent) { + return + } setX((boxParent.width - box.width) / 2 - box.x) setY((boxParent.height - box.height) / 2 - box.y) - }, [refreshToken]) + }, [refreshToken, enabled]) return ( - + {children} ) @@ -125,15 +129,52 @@ const AutoCenterGroup: FunctionComponent<{ //#endregion svg helpers -const barCssClass = 'gauge-mini-bar' +//#region subcomponents + +//#region types -const BarBackground: FunctionComponent<{ +type BarBackgroundProps = { theme: Required colors: Colors barWidth: number getFrac: (x: number) => number barCenter: number -}> = ({ +} + +type BarValueProps = { + theme: Required + barValueWidth: number + colors: Colors + value: number + valueFracFixed: number + barCenter: number +} + +type TextProps = { + theme: Required + barValueWidth: number + colors: Colors + value: number +} + +type BarProps = { + value: number + theme: Required + barWidth: number + y: number + getFrac: (x: number) => number +} + +type AxesProps = { + theme: Required + barWidth: number + y: number + getFrac: (x: number) => number +} + +//#endregion types + +const BarBackground: FunctionComponent = ({ theme, colors: {max, min, secondary, thresholds}, barWidth, @@ -142,30 +183,32 @@ const BarBackground: FunctionComponent<{ }) => { const {gaugeHeight, mode, gaugeRounding} = theme - const colors: {start: number; end: number; col: string}[] = [] - if (mode === 'progress') { - colors.push({start: 0, end: 1, col: secondary}) - } else { - const all = [min, ...thresholds, max] - let start = 0 - for (let i = 0; i + 1 < all.length; i++) { - const {hex: col} = all[i] - const {value} = all[i + 1] - - const end = getFrac(value) - - colors.push({start, end, col}) - start = end - } - } - const y = barCenter - gaugeHeight / 2 - // todo: invalid HTML -> multiple same ID attribute possible - // todo: move to svg root const roundingDefId = `rounded-bar-${barWidth}-${gaugeHeight}` const gradientDefId = `gradient-${min.hex}-${max.hex}` + type Segment = {start: number; end: number; hex: string} + const segments: Segment[] = [] + if (mode === 'bullet') { + // thresholds are already sorted by getColors + const allColors = [min, ...thresholds, max] + + for ( + let i = 0, start = 0, end = 0; + i + 1 < allColors.length; + i++, start = end + ) { + const {hex} = allColors[i] + const next = allColors[i + 1].value + + end = getFrac(next) + segments.push({start, end, hex}) + } + } else { + segments.push({start: 0, end: 1, hex: secondary}) + } + return ( <> @@ -191,32 +234,40 @@ const BarBackground: FunctionComponent<{ y={y} /> ) : ( - colors.map(({col, end, start}) => ( - - )) + segments + .reverse() + .map(({hex: col, end, start}, i) => ( + + )) )} ) } -const BarValue: FunctionComponent<{ - theme: Required - barValueWidth: number - colors: Colors - value: number - valueFracFixed: number - barCenter: number -}> = ({colors, barValueWidth, value, theme, valueFracFixed, barCenter}) => { - const {valueHeight, gaugeHeight, mode, valueRounding} = theme +const BarValue: FunctionComponent = ({ + colors, + barValueWidth, + value, + theme, + valueFracFixed, + barCenter, +}) => { + const {valueHeight, mode, valueRounding} = theme const colorModeGradient = colors.thresholds.length === 0 + const x = Math.sign(valueFracFixed) === -1 ? barValueWidth : 0 + const y = barCenter - valueHeight / 2 + + const className = 'value-rect' + const colorValue = mode === 'bullet' ? colors.secondary @@ -248,11 +299,6 @@ const BarValue: FunctionComponent<{ ?.brighter(1) .hex() - const y = barCenter - valueHeight / 2 - const x = Math.sign(valueFracFixed) === -1 ? barValueWidth : 0 - - const className = 'value-rect' - // todo: move styling out -> styling is now multiple times inserted return ( <> @@ -278,83 +324,41 @@ const BarValue: FunctionComponent<{ ) } -const Bar: FunctionComponent<{ - value: number - theme: Required - barWidth: number - y: number - getFrac: (x: number) => number -}> = ({value, theme, y, barWidth, getFrac}) => { - const {gaugeHeight, valueHeight} = theme - - const colors = getColors(theme) - - const oveflowFrac = 0.03 - // fixes fraction into -oveflowFrac <-> 1+oveflowFrac - const getFixedFrac = (val: number) => - Math.max(-oveflowFrac, Math.min(oveflowFrac + 1, getFrac(val))) - const valueFracFixed = getFixedFrac(value) - - const barY = y - const barValueWidth = barWidth * valueFracFixed - const maxBarHeight = Math.max(gaugeHeight, valueHeight) - const barCenter = maxBarHeight / 2 - - return ( - - - - - - - - - - - ) -} - -const Text: FunctionComponent<{ - theme: Required - barValueWidth: number - colors: Colors - value: number -}> = ({value, barValueWidth, theme}) => { +const Text: FunctionComponent = ({value, barValueWidth, theme}) => { const { valueFontColorInside, valueFontColorOutside, textMode, valueFormater, - valueFontSize, + valueFontSize: fontSize, + valuePadding, } = theme const textValue = valueFormater(value) + const follow = textMode === 'follow' const [textBBox, setTextBBox] = useState(null) - const padding = 5 - const textInside = - (textBBox?.width ? textBBox?.width + padding * 2 : 0) < barValueWidth - - const textAnchor = textInside && textMode === 'follow' ? 'end' : 'start' + const textWidth = textBBox?.width ? textBBox?.width + valuePadding * 2 : 0 + const textInside = textWidth < barValueWidth const textColor = textInside ? valueFontColorInside : valueFontColorOutside - const x = - textMode === 'follow' - ? Math.max(barValueWidth + (textInside ? -padding : padding), padding) - : padding + const textAnchor = textInside && follow ? 'end' : 'start' + + const x = follow + ? Math.max( + barValueWidth + (textInside ? -1 : +1) * valuePadding, + valuePadding + ) + : valuePadding return ( <> {textValue} @@ -362,21 +366,56 @@ const Text: FunctionComponent<{ ) } -const Axes: FunctionComponent<{ - theme: Required - barWidth: number - y: number - getFrac: (x: number) => number -}> = ({theme, barWidth, y, getFrac}) => { +const Bar: FunctionComponent = ({ + value, + theme, + y, + barWidth, + getFrac, +}) => { + const {gaugeHeight, valueHeight, oveflowFraction} = theme + + const colors = getColors(theme) + + const valueFracFixed = Math.max( + -oveflowFraction, + Math.min(oveflowFraction + 1, getFrac(value)) + ) + + const barValueWidth = barWidth * valueFracFixed + const maxBarHeight = Math.max(gaugeHeight, valueHeight) + const barCenter = maxBarHeight / 2 + + return ( + + + + + + + + + + + ) +} + +const Axes: FunctionComponent = ({theme, barWidth, y, getFrac}) => { const {axesSteps, axesFormater, axesFontColor, axesFontSize} = theme - if (axesSteps === undefined || axesSteps === null) return <> + if (axesSteps === undefined || axesSteps === null) { + return <> + } const colors = getColors(theme) - const colorLen = colors.max.value - colors.min.value - - const axesLineStyle = {stroke: axesFontColor, strokeWidth: 2} + const axesLineStyle: React.CSSProperties = { + stroke: axesFontColor, + strokeWidth: 2, + strokeLinecap: 'round', + } const axesValuesArray = Array.isArray(axesSteps) ? axesSteps @@ -396,59 +435,58 @@ const Axes: FunctionComponent<{ anchor: string value: number lineLength: number + text: string + posX: number }[] = axesValuesArray .map(value => ({ - anchor: 'middle', value, + anchor: 'middle', lineLength: 5, })) .concat([ { + value: colors.min.value, anchor: 'start', lineLength: 3, - value: colors.min.value, }, { + value: colors.max.value, anchor: 'end', lineLength: 3, - value: colors.max.value, }, ]) + .map(x => ({ + ...x, + posX: getFrac(x.value) * barWidth, + text: axesFormater(x.value), + })) return ( <> - - - {points.map(({anchor, lineLength, value}, i) => { - const posX = getFrac(value) * barWidth - const text = axesFormater(value) - return ( - <> - - - - {text} - - - - ) - })} + + + {points.map(({posX, lineLength, anchor, text}, i) => ( + + + + {text} + + + ))} ) } -export const GaugeMini: FunctionComponent = ({ +//#endregion subcomponents + +export const GaugeMini: FunctionComponent = ({ value, theme, width, @@ -456,11 +494,11 @@ export const GaugeMini: FunctionComponent = ({ }) => { const { gaugeHeight, - sidePaddings: gaugePaddingSides, + sidePaddings, valueHeight, + bars, barPaddings, labelMain, - labelBars, labelMainFontSize, labelMainFontColor, labelBarsFontColor, @@ -468,37 +506,29 @@ export const GaugeMini: FunctionComponent = ({ } = theme const [barLabelsWidth] = useState([]) - const valueArray = Array.isArray(value) - ? value - : [{_field: '_default', value}] - + const valueArray = Array.isArray(value) ? value : [{_field: '', value}] const colors = getColors(theme) const colorLen = colors.max.value - colors.min.value - const centerY = height / 2 - const barLabelWidth = Math.max(...barLabelsWidth) || 0 - - const barWidth = width - gaugePaddingSides * 2 - barLabelWidth - + const barWidth = width - sidePaddings * 2 - barLabelWidth const maxBarHeight = Math.max(gaugeHeight, valueHeight) - const allBarsHeight = valueArray.length * (maxBarHeight + barPaddings) const [autocenterToken, setAutocenterToken] = useState(0) useEffect(() => { - setAutocenterToken(autocenterToken + 1) - }, [barLabelsWidth, gaugePaddingSides, valueHeight, width, height]) + setAutocenterToken(x => x + 1) + }, [barLabelWidth, sidePaddings, valueHeight, width, height]) /** return value as fraction 0->min 1->max */ const getFrac = (val: number): number => (val - colors.min.value) / colorLen return ( - - + + {labelMain && ( = ({ )} {valueArray.map(({_field, value}, i) => { const y = 0 + i * (maxBarHeight + barPaddings) - const label = labelBars?.find(({_field: f}) => f === _field)?.label + const label = bars?.find(({_field: f}) => f === _field)?.label const textCenter = y + maxBarHeight / 2 diff --git a/giraffe/src/components/GaugeMiniLayer.tsx b/giraffe/src/components/GaugeMiniLayer.tsx index a432fbbe..f1f2e92b 100644 --- a/giraffe/src/components/GaugeMiniLayer.tsx +++ b/giraffe/src/components/GaugeMiniLayer.tsx @@ -9,18 +9,23 @@ import {GaugeMini} from './GaugeMini' import {GAUGE_MINI_THEME_BULLET_DARK} from '../constants/gaugeMiniStyles' interface Props { - value: number | string + values: {[field: string]: number} theme: GaugeMiniLayerConfig } export const GaugeMiniLayer: FunctionComponent = (props: Props) => { - const {theme, value} = props + const {theme, values: valueObj} = props const themeOrDefault: Required = { ...GAUGE_MINI_THEME_BULLET_DARK, ...theme, } - const valueNumber = typeof value === 'string' ? parseFloat(value) : value + const values = Object.keys(valueObj) + .map(key => ({_field: key, value: valueObj[key]})) + .map(({_field, value}) => ({ + _field, + value: typeof value === 'string' ? parseFloat(value) : value, + })) return ( @@ -32,7 +37,7 @@ export const GaugeMiniLayer: FunctionComponent = (props: Props) => { diff --git a/giraffe/src/components/LatestMultipleValueTransform.tsx b/giraffe/src/components/LatestMultipleValueTransform.tsx new file mode 100644 index 00000000..b85d5532 --- /dev/null +++ b/giraffe/src/components/LatestMultipleValueTransform.tsx @@ -0,0 +1,46 @@ +// Libraries +import React, {useMemo, FunctionComponent} from 'react' +import {Table} from '../types' + +// Utils +import {getLatestValues} from '../utils/getLatestValues' + +interface Props { + table: Table + children: (latestValue: {[field: string]: number}) => JSX.Element + allowString: boolean + // If `quiet` is set and a latest value can't be found, this component will + // display nothing instead of an empty graph error message + quiet?: boolean +} + +// todo: can return string ? +export const LatestMultipleValueTransform: FunctionComponent = ({ + table, + quiet = false, + children, +}) => { + const latestValues = useMemo(() => getLatestValues(table), [table]) + + if (latestValues.length === 0 && quiet) { + return null + } + + if (latestValues.length === 0) { + return ( +
+

No latest value found

+
+ ) + } + + const entries = {} + + for (let i = latestValues.length / 2; i--; ) { + const value = latestValues[i * 2] + const field = latestValues[i * 2 + 1] + entries[field] = value + } + + return children(entries) +} diff --git a/giraffe/src/components/SizedTable.tsx b/giraffe/src/components/SizedTable.tsx index de17b83a..9b49f14c 100644 --- a/giraffe/src/components/SizedTable.tsx +++ b/giraffe/src/components/SizedTable.tsx @@ -17,6 +17,7 @@ import {FluxTablesTransform} from './FluxTablesTransform' import {TableGraphLayer} from './TableGraphLayer' import {usePlotEnv} from '../utils/usePlotEnv' +import {LatestMultipleValueTransform} from './LatestMultipleValueTransform' interface Props { config: SizedConfig @@ -83,18 +84,18 @@ export const SizedTable: FunctionComponent = ({ ) case LayerTypes.GaugeMini: return ( - - {latestValue => ( + {latestValues => ( } /> )} - + ) case LayerTypes.RawFluxDataTable: return ( diff --git a/giraffe/src/constants/gaugeMiniStyles.ts b/giraffe/src/constants/gaugeMiniStyles.ts index 68cc06a6..d4aad5ab 100644 --- a/giraffe/src/constants/gaugeMiniStyles.ts +++ b/giraffe/src/constants/gaugeMiniStyles.ts @@ -5,6 +5,7 @@ export const GAUGE_MINI_THEME_BULLET_DARK: Required = { type: 'gauge mini', mode: 'bullet', textMode: 'follow', + bars: [], valueHeight: 18, gaugeHeight: 25, @@ -12,6 +13,7 @@ export const GAUGE_MINI_THEME_BULLET_DARK: Required = { gaugeRounding: 3, barPaddings: 5, sidePaddings: 20, + oveflowFraction: 0.03, gaugeColors: [ {value: 0, type: 'min', hex: InfluxColors.Krypton}, @@ -25,10 +27,10 @@ export const GAUGE_MINI_THEME_BULLET_DARK: Required = { labelMainFontSize: 13, labelMainFontColor: InfluxColors.Ghost, - labelBars: [], labelBarsFontSize: 11, labelBarsFontColor: InfluxColors.Forge, + valuePadding: 5, valueFontSize: 12, valueFontColorOutside: InfluxColors.Raven, valueFontColorInside: InfluxColors.Cloud, @@ -44,6 +46,7 @@ export const GAUGE_MINI_THEME_PROGRESS_DARK: Required = { type: 'gauge mini', mode: 'progress', textMode: 'follow', + bars: [], valueHeight: 20, gaugeHeight: 20, @@ -51,6 +54,7 @@ export const GAUGE_MINI_THEME_PROGRESS_DARK: Required = { gaugeRounding: 3, barPaddings: 5, sidePaddings: 20, + oveflowFraction: 0.03, gaugeColors: [ {value: 0, type: 'min', hex: InfluxColors.Krypton}, @@ -62,10 +66,10 @@ export const GAUGE_MINI_THEME_PROGRESS_DARK: Required = { labelMainFontSize: 13, labelMainFontColor: InfluxColors.Ghost, - labelBars: [], labelBarsFontSize: 11, labelBarsFontColor: InfluxColors.Forge, + valuePadding: 5, valueFontSize: 18, valueFontColorInside: InfluxColors.Raven, valueFontColorOutside: InfluxColors.Cloud, diff --git a/giraffe/src/types/index.ts b/giraffe/src/types/index.ts index 9b7667c0..3766befc 100644 --- a/giraffe/src/types/index.ts +++ b/giraffe/src/types/index.ts @@ -236,6 +236,7 @@ export interface GaugeTheme { export interface GaugeMiniLayerConfig { type: 'gauge mini' + bars?: {_field: string; label?: string}[] mode?: 'progress' | 'bullet' textMode?: 'follow' | 'left' @@ -245,6 +246,7 @@ export interface GaugeMiniLayerConfig { gaugeRounding?: number barPaddings?: number sidePaddings?: number + oveflowFraction?: number gaugeColors?: Color[] colorSecondary?: string @@ -253,10 +255,10 @@ export interface GaugeMiniLayerConfig { labelMainFontSize?: number labelMainFontColor?: string - labelBars?: {_field: string; label: string}[] labelBarsFontSize?: number labelBarsFontColor?: string + valuePadding?: number valueFontSize?: number valueFontColorInside?: string valueFontColorOutside?: string @@ -265,7 +267,7 @@ export interface GaugeMiniLayerConfig { axesSteps?: number | 'thresholds' | undefined | number[] axesFontSize?: number axesFontColor?: string - axesFormater: (value: number) => string + axesFormater?: (value: number) => string } export interface SingleStatLayerConfig { diff --git a/stories/src/data/gaugeMiniLayer.ts b/stories/src/data/gaugeMiniLayer.ts new file mode 100644 index 00000000..6ed66132 --- /dev/null +++ b/stories/src/data/gaugeMiniLayer.ts @@ -0,0 +1,37 @@ +import {newTable, Table} from '../../../giraffe/src' +import memoizeOne from 'memoize-one' + +const now = Date.now() +const numberOfRecords = 20 +const recordsPerLine = 20 + +let TIME_COL: Array +let VALUE_COL: Array +let FIELD_COL: Array + +function getRandomNumber(min: number, max: number) { + return Math.random() * (max - min) + min +} +const createColumns = (minValue: number, maxValue: number, fields: number) => { + TIME_COL = [] + VALUE_COL = [] + FIELD_COL = [] + for (let i = 0; i < numberOfRecords * fields; i += 1) { + VALUE_COL.push(getRandomNumber(minValue, maxValue)) + TIME_COL.push(now + ((i % recordsPerLine) % fields) * 1000 * 60) + FIELD_COL.push(gaugeMiniTableGetField(Math.floor(i / numberOfRecords))) + } +} + +/** return field name for given index */ +export const gaugeMiniTableGetField = (i: number) => `_field_${i}` + +export const gaugeMiniTable = memoizeOne( + (minValue: number, maxValue: number, fields: number): Table => { + createColumns(minValue, maxValue, fields) + return newTable(numberOfRecords * fields) + .addColumn('_time', 'dateTime:RFC3339', 'time', TIME_COL) + .addColumn('_value', 'system', 'number', VALUE_COL) + .addColumn('_field', 'string', 'string', FIELD_COL) + } +) diff --git a/stories/src/gaugeMini.stories.tsx b/stories/src/gaugeMini.stories.tsx index 20b65d61..05d53d3c 100644 --- a/stories/src/gaugeMini.stories.tsx +++ b/stories/src/gaugeMini.stories.tsx @@ -9,11 +9,13 @@ import { } from '../../giraffe/src' import {PlotContainer} from './helpers' -import {gaugeTable} from './data/gaugeLayer' import { GAUGE_MINI_THEME_BULLET_DARK, GAUGE_MINI_THEME_PROGRESS_DARK, } from '../../giraffe/src/constants/gaugeMiniStyles' +// import {gaugeTable as gaugeMiniTable} from './data/gaugeLayer' +import {gaugeMiniTable, gaugeMiniTableGetField} from './data/gaugeMiniLayer' +import {range} from 'd3-array' type Theme = Required @@ -46,6 +48,9 @@ const editableLayer = (theme: Theme): Theme => ({ }, theme.textMode ), + bars: range(number('number of bars', 1)).map(x => ({ + _field: gaugeMiniTableGetField(x), + })), valueHeight: number('valueHeight', theme.valueHeight), gaugeHeight: number('gaugeHeight', theme.gaugeHeight), @@ -53,6 +58,7 @@ const editableLayer = (theme: Theme): Theme => ({ gaugeRounding: number('gaugeRounding', theme.gaugeRounding), barPaddings: number('barPaddings', theme.barPaddings), sidePaddings: number('gaugePaddingSides', theme.sidePaddings), + oveflowFraction: number('oveflowFraction', theme.oveflowFraction), // todo: add knobs gaugeColors: theme.gaugeColors, @@ -62,11 +68,10 @@ const editableLayer = (theme: Theme): Theme => ({ labelMainFontSize: number('labelMainFontSize', theme.labelMainFontSize), labelMainFontColor: color('labelMainFontColor', theme.labelMainFontColor), - // todo: add knobs - labelBars: theme.labelBars, labelBarsFontSize: number('labelBarsFontSize', theme.labelBarsFontSize), labelBarsFontColor: color('labelBarsFontColor', theme.labelBarsFontColor), + valuePadding: number('valuePadding', theme.valuePadding), valueFontSize: number('valueFontSize', theme.valueFontSize), valueFontColorInside: color( 'valueFontColorInside', @@ -97,9 +102,11 @@ const editableLayer = (theme: Theme): Theme => ({ }) const createStory = (theme: Theme) => () => { + const layer = editableLayer(theme) + const config: Config = { - table: gaugeTable(0, 100), - layers: [editableLayer(theme)], + table: gaugeMiniTable(0, 100, layer.bars.length), + layers: [layer], } return ( <>