From 2a450476b18d707022f0380bce6f4451c9ff9e1a Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Tue, 17 Nov 2020 11:51:58 +0100 Subject: [PATCH 01/34] feat: gauge mini --- giraffe/src/components/GaugeMini.tsx | 545 ++++++++++++++++++++++ giraffe/src/components/GaugeMiniLayer.tsx | 42 ++ giraffe/src/components/Plot.tsx | 3 +- giraffe/src/components/SizedTable.tsx | 17 + giraffe/src/constants/gaugeMiniStyles.ts | 78 ++++ giraffe/src/index.ts | 1 + giraffe/src/types/index.ts | 36 ++ giraffe/src/utils/PlotEnv.ts | 1 + stories/src/gaugeMini.stories.tsx | 116 +++++ 9 files changed, 838 insertions(+), 1 deletion(-) create mode 100644 giraffe/src/components/GaugeMini.tsx create mode 100644 giraffe/src/components/GaugeMiniLayer.tsx create mode 100644 giraffe/src/constants/gaugeMiniStyles.ts create mode 100644 stories/src/gaugeMini.stories.tsx diff --git a/giraffe/src/components/GaugeMini.tsx b/giraffe/src/components/GaugeMini.tsx new file mode 100644 index 00000000..df90d6cb --- /dev/null +++ b/giraffe/src/components/GaugeMini.tsx @@ -0,0 +1,545 @@ +// Libraries +import React, {FunctionComponent, useRef, useEffect, useState} from 'react' +import {color as d3Color} from 'd3-color' +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 { + width: number + height: number + value: number | {_field: string; value: number}[] + theme: Required +} + +//#region colors + +export type Colors = { + min: Color + max: Color + secondary: string + thresholds: Color[] + // targets: Color[], +} + +export const getColors = (theme: Required): Colors => { + const {colorSecondary: secondary, gaugeColors: colorsAndTargets} = theme + + colorsAndTargets.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} +} + +//#endregion colors + +//#region svg helpers + +type TSvgTextRectProps = { + onRectChanged?: (rect: DOMRect) => void +} & React.SVGProps + +/** + * Helper component that returns rect when children changes. Usefull for calculating text box size. + */ +export const SvgTextRect: React.FC = props => { + const {onRectChanged = () => {}} = props + + const textRef = useRef(null) + + useEffect(() => { + const rect = textRef.current?.getBBox() + if (!rect) return + + onRectChanged(rect) + }, [props.children]) + + return ( + <> + + + ) +} + +const AutoCenterGroup: FunctionComponent<{ + enabled?: boolean + refreshToken?: number | string +} & React.SVGProps> = props => { + const {children, enabled = true, refreshToken = 0} = props + const ref = useRef(null) + + const [x, setX] = useState(0) + const [y, setY] = useState(0) + + useEffect(() => { + if (!enabled) { + setX(0) + setY(0) + return + } + + const g = ref.current + // we use this function because we want to know how much we are in negative direction + const box = g?.getBBox() + // we use this function because we want to have fixed parent width/height + const boxParent = ((g?.parentElement as any) as + | SVGGraphicsElement + | undefined)?.getBoundingClientRect() + + if (!box || !boxParent) return + + setX((boxParent.width - box.width) / 2 - box.x) + setY((boxParent.height - box.height) / 2 - box.y) + }, [refreshToken]) + + return ( + + {children} + + ) +} + +//#endregion svg helpers + +const barCssClass = 'gauge-mini-bar' + +const BarBackground: FunctionComponent<{ + theme: Required + colors: Colors + barWidth: number + getFrac: (x: number) => number + barCenter: number +}> = ({ + theme, + colors: {max, min, secondary, thresholds}, + barWidth, + getFrac, + barCenter, +}) => { + 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}` + + return ( + <> + + + + + + + + + + {thresholds.length === 0 && mode === 'bullet' ? ( + + ) : ( + colors.map(({col, end, start}) => ( + + )) + )} + + ) +} + +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 colorModeGradient = colors.thresholds.length === 0 + + const colorValue = + mode === 'bullet' + ? colors.secondary + : d3Color( + (() => { + if (colorModeGradient) { + return scaleLinear() + .range([colors.min.hex, colors.max.hex] as any) + .domain([colors.min.value, colors.max.value])(value) as any + } else { + const sortedColors = [ + colors.min, + ...colors.thresholds, + colors.max, + ] + let i = 0 + while ( + i < sortedColors.length && + value >= sortedColors[i].value + ) { + i++ + } + return sortedColors[ + Math.max(Math.min(i - 1, sortedColors.length - 1), 0) + ].hex + } + })() + ) + ?.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 ( + <> + + + + ) +} + +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 { + valueFontColorInside, + valueFontColorOutside, + textMode, + valueFormater, + valueFontSize, + } = theme + const textValue = valueFormater(value) + + 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 textColor = textInside ? valueFontColorInside : valueFontColorOutside + + const x = + textMode === 'follow' + ? Math.max(barValueWidth + (textInside ? -padding : padding), padding) + : padding + + return ( + <> + + {textValue} + + + ) +} + +const Axes: FunctionComponent<{ + theme: Required + barWidth: number + y: number + getFrac: (x: number) => number +}> = ({theme, barWidth, y, getFrac}) => { + const {axesSteps, axesFormater, axesFontColor, axesFontSize} = theme + + 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 axesValuesArray = Array.isArray(axesSteps) + ? axesSteps + : axesSteps === 'thresholds' + ? colors.thresholds.map(x => x.value) + : Number.isInteger(axesSteps) + ? range(axesSteps).map( + x => ((x + 1) * colorLen) / (axesSteps + 1) + colors.min.value + ) + : throwReturn( + `${JSON.stringify( + axesSteps + )} axesSteps must be number | "thresholds" | number[] | undefined.` + ) + + const points: { + anchor: string + value: number + lineLength: number + }[] = axesValuesArray + .map(value => ({ + anchor: 'middle', + value, + lineLength: 5, + })) + .concat([ + { + anchor: 'start', + lineLength: 3, + value: colors.min.value, + }, + { + anchor: 'end', + lineLength: 3, + value: colors.max.value, + }, + ]) + + return ( + <> + + + {points.map(({anchor, lineLength, value}, i) => { + const posX = getFrac(value) * barWidth + const text = axesFormater(value) + return ( + <> + + + + {text} + + + + ) + })} + + + ) +} + +export const GaugeMini: FunctionComponent = ({ + value, + theme, + width, + height, +}) => { + const { + gaugeHeight, + sidePaddings: gaugePaddingSides, + valueHeight, + barPaddings, + labelMain, + labelBars, + labelMainFontSize, + labelMainFontColor, + labelBarsFontColor, + labelBarsFontSize, + } = theme + const [barLabelsWidth] = useState([]) + + const valueArray = Array.isArray(value) + ? value + : [{_field: '_default', 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 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]) + + /** return value as fraction 0->min 1->max */ + const getFrac = (val: number): number => (val - colors.min.value) / colorLen + + return ( + + + {labelMain && ( + + {labelMain} + + )} + {valueArray.map(({_field, value}, i) => { + const y = 0 + i * (maxBarHeight + barPaddings) + const label = labelBars?.find(({_field: f}) => f === _field)?.label + + const textCenter = y + maxBarHeight / 2 + + // todo: no rerender ? + const onRectChanged = (r: DOMRect) => { + barLabelsWidth[i] = r.width + } + + return ( + <> + + + {label} + + + ) + })} + + + + ) +} diff --git a/giraffe/src/components/GaugeMiniLayer.tsx b/giraffe/src/components/GaugeMiniLayer.tsx new file mode 100644 index 00000000..a432fbbe --- /dev/null +++ b/giraffe/src/components/GaugeMiniLayer.tsx @@ -0,0 +1,42 @@ +// Libraries +import React, {FunctionComponent} from 'react' +import AutoSizer from 'react-virtualized-auto-sizer' + +// Types +import {GaugeMiniLayerConfig} from '../types' +import {GaugeMini} from './GaugeMini' + +import {GAUGE_MINI_THEME_BULLET_DARK} from '../constants/gaugeMiniStyles' + +interface Props { + value: number | string + theme: GaugeMiniLayerConfig +} + +export const GaugeMiniLayer: FunctionComponent = (props: Props) => { + const {theme, value} = props + const themeOrDefault: Required = { + ...GAUGE_MINI_THEME_BULLET_DARK, + ...theme, + } + + const valueNumber = typeof value === 'string' ? parseFloat(value) : value + + return ( + + {({width, height}) => ( +
+ +
+ )} +
+ ) +} diff --git a/giraffe/src/components/Plot.tsx b/giraffe/src/components/Plot.tsx index 51b5e5c2..747a65f9 100644 --- a/giraffe/src/components/Plot.tsx +++ b/giraffe/src/components/Plot.tsx @@ -43,7 +43,8 @@ export const Plot: FunctionComponent = ({ if ( graphType === LayerTypes.Table || graphType === LayerTypes.RawFluxDataTable || - graphType === LayerTypes.Gauge + graphType === LayerTypes.Gauge || + graphType === LayerTypes.GaugeMini ) { return ( diff --git a/giraffe/src/components/SizedTable.tsx b/giraffe/src/components/SizedTable.tsx index 6b1f9dd6..de17b83a 100644 --- a/giraffe/src/components/SizedTable.tsx +++ b/giraffe/src/components/SizedTable.tsx @@ -2,12 +2,14 @@ import React, {FunctionComponent, CSSProperties} from 'react' import { GaugeLayerConfig, + GaugeMiniLayerConfig, SizedConfig, TableGraphLayerConfig, LayerTypes, } from '../types' import {GaugeLayer} from './GaugeLayer' +import {GaugeMiniLayer} from './GaugeMiniLayer' import {LatestValueTransform} from './LatestValueTransform' import {newTableFromConfig} from '../utils/newTable' import {RawFluxDataTable} from './RawFluxDataTable' @@ -79,6 +81,21 @@ export const SizedTable: FunctionComponent = ({ )} ) + case LayerTypes.GaugeMini: + return ( + + {latestValue => ( + } + /> + )} + + ) case LayerTypes.RawFluxDataTable: return ( = { + type: 'gauge mini', + mode: 'bullet', + textMode: 'follow', + + valueHeight: 18, + gaugeHeight: 25, + valueRounding: 2, + gaugeRounding: 3, + barPaddings: 5, + sidePaddings: 20, + + 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, + + 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), +} + +export const GAUGE_MINI_THEME_PROGRESS_DARK: Required = { + type: 'gauge mini', + mode: 'progress', + textMode: 'follow', + + valueHeight: 20, + gaugeHeight: 20, + valueRounding: 3, + gaugeRounding: 3, + barPaddings: 5, + sidePaddings: 20, + + 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, + + 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), +} diff --git a/giraffe/src/index.ts b/giraffe/src/index.ts index fd2f378f..3d6d9a99 100644 --- a/giraffe/src/index.ts +++ b/giraffe/src/index.ts @@ -36,6 +36,7 @@ export { Formatter, GaugeLayerConfig, GaugeTheme, + GaugeMiniLayerConfig, GetColumn, HistogramLayerConfig, HistogramPosition, diff --git a/giraffe/src/types/index.ts b/giraffe/src/types/index.ts index 7403cbe6..1d80a8a4 100644 --- a/giraffe/src/types/index.ts +++ b/giraffe/src/types/index.ts @@ -175,6 +175,7 @@ export type FluxDataType = export enum LayerTypes { RawFluxDataTable = 'flux data table', Gauge = 'gauge', + GaugeMini = 'gauge mini', Custom = 'custom', Annotation = 'annotation', SingleStat = 'single stat', @@ -193,6 +194,7 @@ export type LayerConfig = | AnnotationLayerConfig | RawFluxDataTableLayerConfig | GaugeLayerConfig + | GaugeMiniLayerConfig | SingleStatLayerConfig | HeatmapLayerConfig | HistogramLayerConfig @@ -276,6 +278,40 @@ export interface GaugeTheme { overflowDelta: number } +export interface GaugeMiniLayerConfig { + type: 'gauge mini' + mode?: 'progress' | 'bullet' + textMode?: 'follow' | 'left' + + valueHeight?: number + gaugeHeight?: number + valueRounding?: number + gaugeRounding?: number + barPaddings?: number + sidePaddings?: number + + gaugeColors?: Color[] + colorSecondary?: string + + labelMain?: string + labelMainFontSize?: number + labelMainFontColor?: string + + labelBars?: {_field: string; label: string}[] + labelBarsFontSize?: number + labelBarsFontColor?: string + + valueFontSize?: number + valueFontColorInside?: string + valueFontColorOutside?: string + valueFormater?: (value: number) => string + + axesSteps?: number | 'thresholds' | undefined | number[] + axesFontSize?: number + axesFontColor?: string + axesFormater: (value: number) => string +} + export interface SingleStatLayerConfig { type: 'single stat' // do not refactor or restrict to LayerTypes.SingleStat prefix: string diff --git a/giraffe/src/utils/PlotEnv.ts b/giraffe/src/utils/PlotEnv.ts index a04bf549..30e9aaff 100644 --- a/giraffe/src/utils/PlotEnv.ts +++ b/giraffe/src/utils/PlotEnv.ts @@ -375,6 +375,7 @@ export class PlotEnv { case LayerTypes.RawFluxDataTable: case LayerTypes.Gauge: + case LayerTypes.GaugeMini: case LayerTypes.Geo: case LayerTypes.Custom: case LayerTypes.SingleStat: diff --git a/stories/src/gaugeMini.stories.tsx b/stories/src/gaugeMini.stories.tsx new file mode 100644 index 00000000..20b65d61 --- /dev/null +++ b/stories/src/gaugeMini.stories.tsx @@ -0,0 +1,116 @@ +import * as React from 'react' +import {storiesOf} from '@storybook/react' +import {withKnobs, number, select, text} from '@storybook/addon-knobs' +import { + Config, + GaugeMiniLayerConfig, + InfluxColors, + Plot, +} 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' + +type Theme = Required + +const color = (() => { + const colors = (() => { + const obj = {} + Object.keys(InfluxColors).forEach(x => (obj[x] = InfluxColors[x])) + return obj + })() + + // todo: type definitions + return (label: string, ...rest: any[]) => select(label, colors, ...rest) +})() + +const editableLayer = (theme: Theme): Theme => ({ + type: theme.type, + mode: select( + 'Mode', + { + bullet: 'bullet', + progress: 'progress', + }, + theme.mode + ), + textMode: select( + 'textMode', + { + follow: 'follow', + left: 'left', + }, + theme.textMode + ), + + valueHeight: number('valueHeight', theme.valueHeight), + gaugeHeight: number('gaugeHeight', theme.gaugeHeight), + valueRounding: number('valueRounding', theme.valueRounding), + gaugeRounding: number('gaugeRounding', theme.gaugeRounding), + barPaddings: number('barPaddings', theme.barPaddings), + sidePaddings: number('gaugePaddingSides', theme.sidePaddings), + + // todo: add knobs + gaugeColors: theme.gaugeColors, + colorSecondary: color('colorSecondary', theme.colorSecondary), + + labelMain: text('labelMain', 'Gauge-mini example'), + 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), + + valueFontSize: number('valueFontSize', theme.valueFontSize), + valueFontColorInside: color( + 'valueFontColorInside', + theme.valueFontColorInside + ), + valueFontColorOutside: color( + 'valueFontColorOutside', + theme.valueFontColorOutside + ), + // todo: add knobs + valueFormater: theme.valueFormater, + + axesSteps: select( + 'axesSteps', + { + thresholds: 'thresholds', + 0: 0, + 1: 1, + 2: 2, + undefined: null, + }, + theme.axesSteps + ), + axesFontSize: number('axesFontSize', theme.axesFontSize), + axesFontColor: color('axesFontColor', theme.axesFontColor), + // todo: add knobs + axesFormater: theme.axesFormater, +}) + +const createStory = (theme: Theme) => () => { + const config: Config = { + table: gaugeTable(0, 100), + layers: [editableLayer(theme)], + } + return ( + <> + + + + + ) +} + +storiesOf('Gauge mini', module) + .addDecorator(withKnobs) + .add('Bullet', createStory(GAUGE_MINI_THEME_BULLET_DARK)) + .add('Progress', createStory(GAUGE_MINI_THEME_PROGRESS_DARK)) From a79e231ba583042ad5268e6b824c9cb88fe45961 Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Tue, 24 Nov 2020 11:40:07 +0100 Subject: [PATCH 02/34] docs: gauge mini docs created --- giraffe/README.md | 172 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) diff --git a/giraffe/README.md b/giraffe/README.md index 81c1df1b..9b530bed 100644 --- a/giraffe/README.md +++ b/giraffe/README.md @@ -698,6 +698,178 @@ TableGraphLayerConfig uses the `fluxResponse` property from `config` as the data - **overflowDelta**: _number. Optional. Defaults to 0.03 when excluded._ This constant expresses how far past the gauge min or gauge max the needle should be drawn if the value for the needle is less than gauge min or greater than the gauge max. It is expressed as a fraction of the circumference of a circle, e.g. 0.5 means draw halfway around the gauge from the min or max value. + +- **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. + + - **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 + + - **textMode** _'follow' | 'left'_ + - `'left'` text value will stay on _left_ side of bar + - `'follow'` text value will _follow_ width of value bar + + - **valueHeight** _number_ height of value bar + + - **gaugeHeight** _number_ hegiht of backgroud bar + + - **valueRounding** _number_ rounding of value bar corners + + - **gaugeRounding** _number_ rounding of backgroud bar corners + + - **barPaddings** _number_ vertical distance between bars, axes and label + + - **sidePaddings** _number_ empty space on left/right of the bar + + - **oveflowFraction** _number_ fraction number defining how much can value bar go outside of background bar. e.g. with `oveflowFraction: .1` will value bar have max length 110% of background bar for max value and will have 10% length to the left from background bar for minimal value. + + - **gaugeColors** _Color[]_ An array of objects that defines the colors of the Gauge. Each object has the following properties. + + - **id**: _string. **Required**._ The id for this color. Should be unique within the **gaugeColors** array. + + - **type**: _'min' | 'max' | 'threshold'. **Required**._ The type of value associated with this color. _'min'_ type comes first, _'max'_ type comes last, and _'threshold'_ types are in between _'min'_ and _'max'_. **gaugeColors** must contain at least one _'min'_ type and one _'max'_ type for the Gauge-mini to have color. Only the first _'min'_ and first _'max'_ in the array are recognized for each of their respective types. The color will change as a gradient if the **gaugeColors** array contains no 'threshold' that means gradient background bar for 'bullet' mode and continuous value bar color change for 'progress' mode. The color will be segmented if any _'threshold'_ types are included in the **gaugeColors** array that means step color change background bar for 'bullet' mode and fixed color of value bar based on value change for 'progress' mode. + + - **hex**: _string. **Required**._ The [_color hex_](https://www.color-hex.com/) string for this color. + + - **name**: _string. **Required**._ For descriptive purposes only. The name given to this color. + + - **value**: _number. **Required**._ The starting gauge value associated with this color. + + + + + - **colorSecondary** _string_ Secondary color used for value bar in 'bullet' mode or for background bar in 'progress' mode + + Main label + - **labelMain** _string_ Main label text. + + - **labelMainFontSize** _number_ Main label font size. + + - **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 + + Text value + - **valuePadding** _number_ Padding on sides of text (distance from value bar start/end) + + - **valueFontSize** _number_ Text value font size + + - **valueFontColorInside** _string_ Text value color when value bar is behind the text + + - **valueFontColorOutside** _string_ Text value color when value bar is not behind the text + + - **valueFormater** _(value: number) => string_ Function that defines how will be text value shown based on current value. e.g. ```valueFormater: (num: number) => `${num.toFixed(0)}%` ``` for _value=23.213_ will show text value _23%_. + + Axes + - **axesSteps** _number | 'thresholds' | undefined | number[]_ Defines where to show axes: + - _number_ number of how many evenly distributed axes values will be shown between min and max value. Only min and max value will be shown when `axesSteps: 0` + - _'thresholds'_ axes values will be shown at threshold values. + - _undefined_ axes will not show. + - _number[]_ shows axes at given fixed values + + - **axesFontSize** _number_ Axes values font size + + - **axesFontColor** _string_ Color of axes values and axes lines + + - **axesFormater** _(value: number) => string_ Same as **valueFormater** for axes values + + **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), +} +``` + + - **SingleStatLayerConfig**: _Object._ No limit but generally one per ``. Using more than one requires additional styling through configuration and is not recommended.
A Single Stat layer is a pre-defined custom layer that displays a single value on top of any other plot type, or by itself, but usually displayed on top of (single) line graphs. The displayed value is the latest value by timestamp. If more than one value has the latest timestamp, then the first value in the [table](#data-properties) with the latest timestamp will be displayed. Currently, there is no guarantee which value will be considered the first value when there are multiple values with the same timestamp. From c0fb5f41e8b491ee0dd510a034aaf059b007c979 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 03/34] feat: mini gauge integration --- chronograf-ui | 1 + giraffe/README.md | 184 ++++---- 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, 437 insertions(+), 296 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 9b530bed..acff5413 100644 --- a/giraffe/README.md +++ b/giraffe/README.md @@ -701,10 +701,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` + 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() + ``` - - **type**: _'gauge mini'. **Required**._ Specifies that this LayerConfig is a gauge mini layer. + 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 @@ -752,8 +763,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 @@ -784,90 +793,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 1d80a8a4..06c442df 100644 --- a/giraffe/src/types/index.ts +++ b/giraffe/src/types/index.ts @@ -280,6 +280,7 @@ export interface GaugeTheme { export interface GaugeMiniLayerConfig { type: 'gauge mini' + bars?: {_field: string; label?: string}[] mode?: 'progress' | 'bullet' textMode?: 'follow' | 'left' @@ -289,6 +290,7 @@ export interface GaugeMiniLayerConfig { gaugeRounding?: number barPaddings?: number sidePaddings?: number + oveflowFraction?: number gaugeColors?: Color[] colorSecondary?: string @@ -297,10 +299,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 @@ -309,7 +311,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 ( <> From 95cffff988f35b5b39eeb078b5f910da85c1f172 Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Sun, 29 Nov 2020 16:24:33 +0100 Subject: [PATCH 04/34] feat: group by selected columns --- giraffe/src/components/GaugeMini.tsx | 47 +++++++-- giraffe/src/components/GaugeMiniLayer.tsx | 13 +-- .../LatestMultipleValueTransform.tsx | 95 ++++++++++++++++--- giraffe/src/components/SizedTable.tsx | 5 +- giraffe/src/constants/gaugeMiniStyles.ts | 8 +- giraffe/src/types/index.ts | 11 ++- stories/src/gaugeMini.stories.tsx | 11 ++- 7 files changed, 146 insertions(+), 44 deletions(-) diff --git a/giraffe/src/components/GaugeMini.tsx b/giraffe/src/components/GaugeMini.tsx index 58198eab..c8181358 100644 --- a/giraffe/src/components/GaugeMini.tsx +++ b/giraffe/src/components/GaugeMini.tsx @@ -12,10 +12,25 @@ const throwReturn = (msg: string): T => { interface Props { width: number height: number - value: number | {_field: string; value: number}[] + values: {colsMString: string; value: number}[] theme: Required } +/** create merged string for given column string values. String is same for all columns with same values and unique for different ones */ +export const createColsMString = ( + groupedBy: T, + col: {[key in keyof T]: string} +): string => { + const columns = Object.keys(groupedBy).sort() + const columnValues = columns.map(x => col[x]) + /** + * replacing - with -- will ensures that rows + * { a: '0-1', b: '2' } and { a: '0', b: '1-2' } + * will not have same string (0-1-2 instead they will be 0--1-2 and 0-1--2) + */ + return columnValues.map(x => x.split('-').join('--')).join('-') +} + const barCssClass = 'gauge-mini-bar' //#region colors @@ -185,7 +200,7 @@ const BarBackground: FunctionComponent = ({ const y = barCenter - gaugeHeight / 2 // todo: invalid HTML -> multiple same ID attribute possible - const roundingDefId = `rounded-bar-${barWidth}-${gaugeHeight}` + const roundingDefId = `rounded-bar-w-${barWidth}-h-${gaugeHeight}-r-${gaugeRounding}` const gradientDefId = `gradient-${min.hex}-${max.hex}` type Segment = {start: number; end: number; hex: string} @@ -209,6 +224,7 @@ const BarBackground: FunctionComponent = ({ segments.push({start: 0, end: 1, hex: secondary}) } + // todo: dont't render def linear gradient when is not used return ( <> @@ -487,7 +503,7 @@ const Axes: FunctionComponent = ({theme, barWidth, y, getFrac}) => { //#endregion subcomponents export const GaugeMini: FunctionComponent = ({ - value, + values, theme, width, height, @@ -496,7 +512,7 @@ export const GaugeMini: FunctionComponent = ({ gaugeHeight, sidePaddings, valueHeight, - bars, + barsDefinitions, barPaddings, labelMain, labelMainFontSize, @@ -506,13 +522,20 @@ export const GaugeMini: FunctionComponent = ({ } = theme const [barLabelsWidth] = useState([]) - const valueArray = Array.isArray(value) ? value : [{_field: '', value}] const colors = getColors(theme) const colorLen = colors.max.value - colors.min.value const barLabelWidth = Math.max(...barLabelsWidth) || 0 const barWidth = width - sidePaddings * 2 - barLabelWidth const maxBarHeight = Math.max(gaugeHeight, valueHeight) - const allBarsHeight = valueArray.length * (maxBarHeight + barPaddings) + const allBarsHeight = values.length * (maxBarHeight + barPaddings) + + const {groupByColumns} = barsDefinitions + const labelMapping: any = {} + barsDefinitions?.bars?.forEach(x => { + if (!x.label) return + const mstring = createColsMString(groupByColumns, x.barDef) + labelMapping[mstring] = x.label + }) const [autocenterToken, setAutocenterToken] = useState(0) useEffect(() => { @@ -538,9 +561,9 @@ export const GaugeMini: FunctionComponent = ({ {labelMain}
)} - {valueArray.map(({_field, value}, i) => { + {values.map(({colsMString, value}, i) => { const y = 0 + i * (maxBarHeight + barPaddings) - const label = bars?.find(({_field: f}) => f === _field)?.label + const label = labelMapping?.[colsMString] const textCenter = y + maxBarHeight / 2 @@ -567,7 +590,13 @@ export const GaugeMini: FunctionComponent = ({ ) })} diff --git a/giraffe/src/components/GaugeMiniLayer.tsx b/giraffe/src/components/GaugeMiniLayer.tsx index f1f2e92b..a6eb7bc7 100644 --- a/giraffe/src/components/GaugeMiniLayer.tsx +++ b/giraffe/src/components/GaugeMiniLayer.tsx @@ -9,24 +9,17 @@ import {GaugeMini} from './GaugeMini' import {GAUGE_MINI_THEME_BULLET_DARK} from '../constants/gaugeMiniStyles' interface Props { - values: {[field: string]: number} + values: {colsMString: string; value: number}[] theme: GaugeMiniLayerConfig } export const GaugeMiniLayer: FunctionComponent = (props: Props) => { - const {theme, values: valueObj} = props + const {theme, values} = props const themeOrDefault: Required = { ...GAUGE_MINI_THEME_BULLET_DARK, ...theme, } - const values = Object.keys(valueObj) - .map(key => ({_field: key, value: valueObj[key]})) - .map(({_field, value}) => ({ - _field, - value: typeof value === 'string' ? parseFloat(value) : value, - })) - return ( {({width, height}) => ( @@ -37,7 +30,7 @@ export const GaugeMiniLayer: FunctionComponent = (props: Props) => { diff --git a/giraffe/src/components/LatestMultipleValueTransform.tsx b/giraffe/src/components/LatestMultipleValueTransform.tsx index b85d5532..fc36890d 100644 --- a/giraffe/src/components/LatestMultipleValueTransform.tsx +++ b/giraffe/src/components/LatestMultipleValueTransform.tsx @@ -1,26 +1,96 @@ // Libraries import React, {useMemo, FunctionComponent} from 'react' import {Table} from '../types' +import {createColsMString} from './GaugeMini' -// Utils -import {getLatestValues} from '../utils/getLatestValues' +interface SelectedColumns { + [key: string]: true +} + +export const getLatestValuesGrouped = ( + table: Table, + columnsObj: SelectedColumns +) => { + const columns = Object.keys(columnsObj).sort() + + columns.forEach(x => { + if (table.getColumnType(x) !== 'string') { + throw new Error( + `Data can be grouped only by string columns. But column ${x} is typeof ${table.getColumnType( + x + )}` + ) + } + }) + + const valueColumn = table.getColumn('_value', 'number') as number[] + + if (!valueColumn.length) { + return [] + } + + // Fallback to `_stop` column if `_time` column missing otherwise return empty array. + let timeColData: number[] = [] + + if (table.columnKeys.includes('_time')) { + timeColData = table.getColumn('_time', 'number') as number[] + } else if (table.columnKeys.includes('_stop')) { + timeColData = table.getColumn('_stop', 'number') as number[] + } + if (!timeColData && table.length !== 1) { + return [] + } + + const groupColsData = columns.map(k => + table.getColumn(k, 'string') + ) as string[][] + + const result: {[key: string]: number} = {} + + timeColData + // merge time with it's index + .map((time, index) => ({time, index})) + // remove entries without time + .filter(({time}) => time) + // todo: sort time complexity too high ... replace with another solution + // from low time to high time (last is last) + .sort(({time: t1}, {time: t2}) => t1 - t2) + // get relevant data from index (we don't need time anymore) + .map(({index}) => ({ + value: valueColumn[index], + groupRow: groupColsData.map(x => x[index]), + })) + // remove invalid data + .filter(({value}) => Number.isFinite(value) && typeof value === 'number') + // create result + .forEach(({value, groupRow}) => { + const grupObj = {} + groupRow.forEach((x, i) => (grupObj[columns[i]] = x)) + const strKey = createColsMString(columnsObj, grupObj) + // data is inserted from last to first so latest data is first + result[strKey] = value + }) + + return result +} 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 + columns: SelectedColumns + children: (latestValue: {colsMString: string; value: number}[]) => JSX.Element quiet?: boolean } // todo: can return string ? export const LatestMultipleValueTransform: FunctionComponent = ({ table, + columns, quiet = false, children, }) => { - const latestValues = useMemo(() => getLatestValues(table), [table]) + const latestValues = useMemo(() => getLatestValuesGrouped(table, columns), [ + table, + ]) if (latestValues.length === 0 && quiet) { return null @@ -34,13 +104,10 @@ export const LatestMultipleValueTransform: FunctionComponent = ({ ) } - const entries = {} - - for (let i = latestValues.length / 2; i--; ) { - const value = latestValues[i * 2] - const field = latestValues[i * 2 + 1] - entries[field] = value - } + const entries = Object.keys(latestValues).map(x => ({ + colsMString: x, + value: latestValues[x], + })) return children(entries) } diff --git a/giraffe/src/components/SizedTable.tsx b/giraffe/src/components/SizedTable.tsx index 9b49f14c..fbe45829 100644 --- a/giraffe/src/components/SizedTable.tsx +++ b/giraffe/src/components/SizedTable.tsx @@ -87,7 +87,10 @@ export const SizedTable: FunctionComponent = ({ ) + .barsDefinitions.groupByColumns + } > {latestValues => ( = { type: 'gauge mini', mode: 'bullet', textMode: 'follow', - bars: [], + barsDefinitions: {groupByColumns: {_label: true}}, valueHeight: 18, gaugeHeight: 25, @@ -34,19 +34,19 @@ export const GAUGE_MINI_THEME_BULLET_DARK: Required = { valueFontSize: 12, valueFontColorOutside: InfluxColors.Raven, valueFontColorInside: InfluxColors.Cloud, - valueFormater: (val: number) => val.toFixed(0), + valueFormater: (num: number) => num.toFixed(0), axesSteps: 'thresholds', axesFontSize: 11, axesFontColor: InfluxColors.Forge, - axesFormater: (val: number) => val.toFixed(0), + axesFormater: (num: number) => num.toFixed(0), } export const GAUGE_MINI_THEME_PROGRESS_DARK: Required = { type: 'gauge mini', mode: 'progress', textMode: 'follow', - bars: [], + barsDefinitions: {groupByColumns: {_label: true}}, valueHeight: 20, gaugeHeight: 20, diff --git a/giraffe/src/types/index.ts b/giraffe/src/types/index.ts index 06c442df..28eaed3c 100644 --- a/giraffe/src/types/index.ts +++ b/giraffe/src/types/index.ts @@ -278,9 +278,18 @@ export interface GaugeTheme { overflowDelta: number } +export interface GaugeMiniBarsDefinitions { + /** Defines which columns choose as unique bar indentificator. */ + groupByColumns: T + // todo: allow regex ? + /** Give label for given unique column values */ + bars?: {barDef: {[key in keyof T]: string}; label?: string}[] +} + export interface GaugeMiniLayerConfig { type: 'gauge mini' - bars?: {_field: string; label?: string}[] + /** Defines which columns choose as unique bar indentificator. Also bar labels can be defined here. */ + barsDefinitions: GaugeMiniBarsDefinitions mode?: 'progress' | 'bullet' textMode?: 'follow' | 'left' diff --git a/stories/src/gaugeMini.stories.tsx b/stories/src/gaugeMini.stories.tsx index 05d53d3c..20fd78b7 100644 --- a/stories/src/gaugeMini.stories.tsx +++ b/stories/src/gaugeMini.stories.tsx @@ -30,7 +30,7 @@ const color = (() => { return (label: string, ...rest: any[]) => select(label, colors, ...rest) })() -const editableLayer = (theme: Theme): Theme => ({ +const editableLayer = (theme: Theme): Theme & {numberOfBars: number} => ({ type: theme.type, mode: select( 'Mode', @@ -48,9 +48,10 @@ const editableLayer = (theme: Theme): Theme => ({ }, theme.textMode ), - bars: range(number('number of bars', 1)).map(x => ({ - _field: gaugeMiniTableGetField(x), - })), + numberOfBars: number('number of bars', 1), + barsDefinitions: { + groupByColumns: {_field: true}, + }, valueHeight: number('valueHeight', theme.valueHeight), gaugeHeight: number('gaugeHeight', theme.gaugeHeight), @@ -105,7 +106,7 @@ const createStory = (theme: Theme) => () => { const layer = editableLayer(theme) const config: Config = { - table: gaugeMiniTable(0, 100, layer.bars.length), + table: gaugeMiniTable(0, 100, layer.numberOfBars), layers: [layer], } return ( From 715fc23ed091cdd25905565e60f2358a8d523a0d Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Sun, 29 Nov 2020 17:28:24 +0100 Subject: [PATCH 05/34] fix: _label renamed to _field --- giraffe/src/components/GaugeMini.tsx | 4 +++- giraffe/src/constants/gaugeMiniStyles.ts | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/giraffe/src/components/GaugeMini.tsx b/giraffe/src/components/GaugeMini.tsx index c8181358..1a36fa25 100644 --- a/giraffe/src/components/GaugeMini.tsx +++ b/giraffe/src/components/GaugeMini.tsx @@ -532,7 +532,9 @@ export const GaugeMini: FunctionComponent = ({ const {groupByColumns} = barsDefinitions const labelMapping: any = {} barsDefinitions?.bars?.forEach(x => { - if (!x.label) return + if (!x.label) { + return + } const mstring = createColsMString(groupByColumns, x.barDef) labelMapping[mstring] = x.label }) diff --git a/giraffe/src/constants/gaugeMiniStyles.ts b/giraffe/src/constants/gaugeMiniStyles.ts index f434b94e..33de7391 100644 --- a/giraffe/src/constants/gaugeMiniStyles.ts +++ b/giraffe/src/constants/gaugeMiniStyles.ts @@ -5,7 +5,7 @@ export const GAUGE_MINI_THEME_BULLET_DARK: Required = { type: 'gauge mini', mode: 'bullet', textMode: 'follow', - barsDefinitions: {groupByColumns: {_label: true}}, + barsDefinitions: {groupByColumns: {_field: true}}, valueHeight: 18, gaugeHeight: 25, @@ -46,7 +46,7 @@ export const GAUGE_MINI_THEME_PROGRESS_DARK: Required = { type: 'gauge mini', mode: 'progress', textMode: 'follow', - barsDefinitions: {groupByColumns: {_label: true}}, + barsDefinitions: {groupByColumns: {_field: true}}, valueHeight: 20, gaugeHeight: 20, From 565a0707cdfe6c83e10295b8822e543e8de8d136 Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Wed, 23 Dec 2020 03:47:17 +0100 Subject: [PATCH 06/34] fix: false columns --- giraffe/src/components/GaugeMini.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/giraffe/src/components/GaugeMini.tsx b/giraffe/src/components/GaugeMini.tsx index 1a36fa25..6c49fa95 100644 --- a/giraffe/src/components/GaugeMini.tsx +++ b/giraffe/src/components/GaugeMini.tsx @@ -3,6 +3,8 @@ import React, {FunctionComponent, useRef, useEffect, useState} from 'react' import {color as d3Color} from 'd3-color' import {scaleLinear} from 'd3-scale' import {range} from 'd3-array' + +// Types import {Color, GaugeMiniLayerConfig} from '../types' const throwReturn = (msg: string): T => { @@ -21,7 +23,9 @@ export const createColsMString = ( groupedBy: T, col: {[key in keyof T]: string} ): string => { - const columns = Object.keys(groupedBy).sort() + const columns = Object.keys(groupedBy) + .filter(x => groupedBy[x]) + .sort() const columnValues = columns.map(x => col[x]) /** * replacing - with -- will ensures that rows From 4f985f5fc8dd7f2240a3d19d77f824d7fa3fda9c Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Mon, 28 Dec 2020 19:22:58 +0100 Subject: [PATCH 07/34] feat: gauge mini simpler types --- giraffe/src/components/GaugeMini.tsx | 139 ++++++----------- giraffe/src/components/SizedTable.tsx | 7 +- giraffe/src/constants/gaugeMiniStyles.ts | 16 +- giraffe/src/types/index.ts | 27 +++- giraffe/src/utils/formatStatValue.ts | 2 +- giraffe/src/utils/gaugeMiniThemeNormalize.ts | 153 +++++++++++++++++++ stories/src/gaugeMini.stories.tsx | 2 +- 7 files changed, 238 insertions(+), 108 deletions(-) create mode 100644 giraffe/src/utils/gaugeMiniThemeNormalize.ts diff --git a/giraffe/src/components/GaugeMini.tsx b/giraffe/src/components/GaugeMini.tsx index 6c49fa95..c3f2dc2f 100644 --- a/giraffe/src/components/GaugeMini.tsx +++ b/giraffe/src/components/GaugeMini.tsx @@ -2,14 +2,13 @@ import React, {FunctionComponent, useRef, useEffect, useState} from 'react' import {color as d3Color} from 'd3-color' import {scaleLinear} from 'd3-scale' -import {range} from 'd3-array' // Types -import {Color, GaugeMiniLayerConfig} from '../types' - -const throwReturn = (msg: string): T => { - throw new Error(msg) -} +import {GaugeMiniColors, GaugeMiniLayerConfig} from '../types' +import { + gaugeMiniNormalizeTheme, + GaugeMiniThemeNormalized, +} from '../utils/gaugeMiniThemeNormalize' interface Props { width: number @@ -18,6 +17,7 @@ interface Props { theme: Required } +// todo: move into gauge utils /** create merged string for given column string values. String is same for all columns with same values and unique for different ones */ export const createColsMString = ( groupedBy: T, @@ -37,40 +37,6 @@ export const createColsMString = ( const barCssClass = 'gauge-mini-bar' -//#region colors - -export type Colors = { - min: Color - max: Color - secondary: string - thresholds: Color[] -} - -export const getColors = (theme: Required): Colors => { - const {colorSecondary: secondary, gaugeColors} = theme - - gaugeColors.forEach( - ({hex, name}) => - d3Color(hex) ?? - throwReturn(`Object "${hex}" isn"t valid color for name:${name}`) - ) - - 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 - //#region svg helpers type TSvgTextRectProps = { @@ -153,62 +119,67 @@ const AutoCenterGroup: FunctionComponent<{ //#region types type BarBackgroundProps = { - theme: Required - colors: Colors + theme: Required + colors: GaugeMiniColors barWidth: number getFrac: (x: number) => number barCenter: number } type BarValueProps = { - theme: Required + theme: Required barValueWidth: number - colors: Colors + colors: GaugeMiniColors value: number valueFracFixed: number barCenter: number } type TextProps = { - theme: Required + theme: Required barValueWidth: number - colors: Colors + colors: GaugeMiniColors value: number } type BarProps = { value: number - theme: Required + theme: Required barWidth: number y: number getFrac: (x: number) => number } type AxesProps = { - theme: Required + theme: Required barWidth: number y: number getFrac: (x: number) => number } +type BarSegment = { + start: number + end: number + hex: string +} + //#endregion types const BarBackground: FunctionComponent = ({ theme, - colors: {max, min, secondary, thresholds}, barWidth, getFrac, barCenter, }) => { - const {gaugeHeight, mode, gaugeRounding} = theme + const {gaugeHeight, mode, gaugeRounding, colors, colorSecondary} = theme + const {max, min, thresholds = []} = colors const y = barCenter - gaugeHeight / 2 // todo: invalid HTML -> multiple same ID attribute possible const roundingDefId = `rounded-bar-w-${barWidth}-h-${gaugeHeight}-r-${gaugeRounding}` const gradientDefId = `gradient-${min.hex}-${max.hex}` - type Segment = {start: number; end: number; hex: string} - const segments: Segment[] = [] + const segments: BarSegment[] = [] if (mode === 'bullet') { // thresholds are already sorted by getColors const allColors = [min, ...thresholds, max] @@ -225,7 +196,7 @@ const BarBackground: FunctionComponent = ({ segments.push({start, end, hex}) } } else { - segments.push({start: 0, end: 1, hex: secondary}) + segments.push({start: 0, end: 1, hex: colorSecondary}) } // todo: dont't render def linear gradient when is not used @@ -280,8 +251,9 @@ const BarValue: FunctionComponent = ({ valueFracFixed, barCenter, }) => { - const {valueHeight, mode, valueRounding} = theme - const colorModeGradient = colors.thresholds.length === 0 + const {valueHeight, mode, valueRounding, colorSecondary} = theme + const {min, max, thresholds = []} = colors + const colorModeGradient = thresholds.length === 0 const x = Math.sign(valueFracFixed) === -1 ? barValueWidth : 0 const y = barCenter - valueHeight / 2 @@ -290,19 +262,15 @@ const BarValue: FunctionComponent = ({ const colorValue = mode === 'bullet' - ? colors.secondary + ? colorSecondary : d3Color( (() => { if (colorModeGradient) { return scaleLinear() - .range([colors.min.hex, colors.max.hex] as any) - .domain([colors.min.value, colors.max.value])(value) as any + .range([min.hex, max.hex] as any) + .domain([min.value, max.value])(value) as any } else { - const sortedColors = [ - colors.min, - ...colors.thresholds, - colors.max, - ] + const sortedColors = [min, ...thresholds, max] let i = 0 while ( i < sortedColors.length && @@ -393,9 +361,7 @@ const Bar: FunctionComponent = ({ barWidth, getFrac, }) => { - const {gaugeHeight, valueHeight, oveflowFraction} = theme - - const colors = getColors(theme) + const {gaugeHeight, valueHeight, oveflowFraction, colors} = theme const valueFracFixed = Math.max( -oveflowFraction, @@ -423,41 +389,26 @@ const Bar: FunctionComponent = ({ } const Axes: FunctionComponent = ({theme, barWidth, y, getFrac}) => { - const {axesSteps, axesFormater, axesFontColor, axesFontSize} = theme + const {axesSteps, axesFormater, axesFontColor, axesFontSize, colors} = theme - if (axesSteps === undefined || axesSteps === null) { + if (axesSteps === undefined) { return <> } - const colors = getColors(theme) - const colorLen = colors.max.value - colors.min.value + const {min, max} = colors const axesLineStyle: React.CSSProperties = { stroke: axesFontColor, strokeWidth: 2, strokeLinecap: 'round', } - const axesValuesArray = Array.isArray(axesSteps) - ? axesSteps - : axesSteps === 'thresholds' - ? colors.thresholds.map(x => x.value) - : Number.isInteger(axesSteps) - ? range(axesSteps).map( - x => ((x + 1) * colorLen) / (axesSteps + 1) + colors.min.value - ) - : throwReturn( - `${JSON.stringify( - axesSteps - )} axesSteps must be number | "thresholds" | number[] | undefined.` - ) - const points: { anchor: string value: number lineLength: number text: string posX: number - }[] = axesValuesArray + }[] = axesSteps .map(value => ({ value, anchor: 'middle', @@ -465,12 +416,12 @@ const Axes: FunctionComponent = ({theme, barWidth, y, getFrac}) => { })) .concat([ { - value: colors.min.value, + value: min.value, anchor: 'start', lineLength: 3, }, { - value: colors.max.value, + value: max.value, anchor: 'end', lineLength: 3, }, @@ -508,38 +459,42 @@ const Axes: FunctionComponent = ({theme, barWidth, y, getFrac}) => { export const GaugeMini: FunctionComponent = ({ values, - theme, + theme: _theme, width, height, }) => { + const theme = gaugeMiniNormalizeTheme(_theme) + const { gaugeHeight, sidePaddings, valueHeight, - barsDefinitions, barPaddings, labelMain, labelMainFontSize, labelMainFontColor, labelBarsFontColor, labelBarsFontSize, + colors, } = theme const [barLabelsWidth] = useState([]) - const colors = getColors(theme) const colorLen = colors.max.value - colors.min.value const barLabelWidth = Math.max(...barLabelsWidth) || 0 const barWidth = width - sidePaddings * 2 - barLabelWidth const maxBarHeight = Math.max(gaugeHeight, valueHeight) const allBarsHeight = values.length * (maxBarHeight + barPaddings) - const {groupByColumns} = barsDefinitions + const barsDefinitions = theme.barsDefinitions + + // create unified barsDefinition + const labelMapping: any = {} barsDefinitions?.bars?.forEach(x => { if (!x.label) { return } - const mstring = createColsMString(groupByColumns, x.barDef) + const mstring = createColsMString(barsDefinitions.groupByColumns, x.barDef) labelMapping[mstring] = x.label }) diff --git a/giraffe/src/components/SizedTable.tsx b/giraffe/src/components/SizedTable.tsx index fbe45829..10fb09bd 100644 --- a/giraffe/src/components/SizedTable.tsx +++ b/giraffe/src/components/SizedTable.tsx @@ -18,6 +18,7 @@ import {TableGraphLayer} from './TableGraphLayer' import {usePlotEnv} from '../utils/usePlotEnv' import {LatestMultipleValueTransform} from './LatestMultipleValueTransform' +import {getGaugeMiniBarsDefinitions} from '../utils/gaugeMiniThemeNormalize' interface Props { config: SizedConfig @@ -88,8 +89,10 @@ export const SizedTable: FunctionComponent = ({ key={layerIndex} table={newTableFromConfig(config)} columns={ - (layerConfig as Required) - .barsDefinitions.groupByColumns + getGaugeMiniBarsDefinitions( + (layerConfig as Required) + .barsDefinitions + ).groupByColumns } > {latestValues => ( diff --git a/giraffe/src/constants/gaugeMiniStyles.ts b/giraffe/src/constants/gaugeMiniStyles.ts index 33de7391..5013ab05 100644 --- a/giraffe/src/constants/gaugeMiniStyles.ts +++ b/giraffe/src/constants/gaugeMiniStyles.ts @@ -5,7 +5,7 @@ export const GAUGE_MINI_THEME_BULLET_DARK: Required = { type: 'gauge mini', mode: 'bullet', textMode: 'follow', - barsDefinitions: {groupByColumns: {_field: true}}, + barsDefinitions: {groupByColumns: ['_field']}, valueHeight: 18, gaugeHeight: 25, @@ -15,7 +15,7 @@ export const GAUGE_MINI_THEME_BULLET_DARK: Required = { sidePaddings: 20, oveflowFraction: 0.03, - gaugeColors: [ + gaugeMiniColors: [ {value: 0, type: 'min', hex: InfluxColors.Krypton}, {value: 50, type: 'threshold', hex: InfluxColors.Sulfur}, {value: 75, type: 'threshold', hex: InfluxColors.Topaz}, @@ -34,19 +34,19 @@ export const GAUGE_MINI_THEME_BULLET_DARK: Required = { valueFontSize: 12, valueFontColorOutside: InfluxColors.Raven, valueFontColorInside: InfluxColors.Cloud, - valueFormater: (num: number) => num.toFixed(0), + valueFormater: {}, axesSteps: 'thresholds', axesFontSize: 11, axesFontColor: InfluxColors.Forge, - axesFormater: (num: number) => num.toFixed(0), + axesFormater: {}, } export const GAUGE_MINI_THEME_PROGRESS_DARK: Required = { type: 'gauge mini', mode: 'progress', textMode: 'follow', - barsDefinitions: {groupByColumns: {_field: true}}, + barsDefinitions: {groupByColumns: ['_field']}, valueHeight: 20, gaugeHeight: 20, @@ -56,7 +56,7 @@ export const GAUGE_MINI_THEME_PROGRESS_DARK: Required = { sidePaddings: 20, oveflowFraction: 0.03, - gaugeColors: [ + gaugeMiniColors: [ {value: 0, type: 'min', hex: InfluxColors.Krypton}, {value: 100, type: 'max', hex: InfluxColors.Topaz}, ] as Color[], @@ -73,10 +73,10 @@ export const GAUGE_MINI_THEME_PROGRESS_DARK: Required = { valueFontSize: 18, valueFontColorInside: InfluxColors.Raven, valueFontColorOutside: InfluxColors.Cloud, - valueFormater: (val: number) => val.toFixed(0), + valueFormater: {}, axesSteps: undefined as any, axesFontSize: 11, axesFontColor: InfluxColors.Forge, - axesFormater: (val: number) => val.toFixed(0), + axesFormater: {}, } diff --git a/giraffe/src/types/index.ts b/giraffe/src/types/index.ts index 28eaed3c..7faf123f 100644 --- a/giraffe/src/types/index.ts +++ b/giraffe/src/types/index.ts @@ -2,6 +2,7 @@ import CSS from 'csstype' import {CSSProperties, ReactNode} from 'react' import {TimeZone} from './timeZones' import {GeoLayerConfig} from './geo' +import {FormatStatValueOptions} from '../utils/formatStatValue' export type SizedConfig = Config & {width: number; height: number} export interface Config { @@ -278,6 +279,17 @@ export interface GaugeTheme { overflowDelta: number } +export type ColorHexValue = { + value: number + hex: string +} + +export type GaugeMiniColors = { + min: ColorHexValue + max: ColorHexValue + thresholds?: ColorHexValue[] +} + export interface GaugeMiniBarsDefinitions { /** Defines which columns choose as unique bar indentificator. */ groupByColumns: T @@ -286,10 +298,17 @@ export interface GaugeMiniBarsDefinitions { bars?: {barDef: {[key in keyof T]: string}; label?: string}[] } +export interface GaugeMiniBarsDefinitionsArr { + groupByColumns: string[] + bars?: {barDef: string[]; label?: string}[] +} + export interface GaugeMiniLayerConfig { type: 'gauge mini' /** Defines which columns choose as unique bar indentificator. Also bar labels can be defined here. */ - barsDefinitions: GaugeMiniBarsDefinitions + barsDefinitions: + | GaugeMiniBarsDefinitionsArr + | GaugeMiniBarsDefinitions<{[key: string]: true}> mode?: 'progress' | 'bullet' textMode?: 'follow' | 'left' @@ -301,7 +320,7 @@ export interface GaugeMiniLayerConfig { sidePaddings?: number oveflowFraction?: number - gaugeColors?: Color[] + gaugeMiniColors?: Color[] | GaugeMiniColors colorSecondary?: string labelMain?: string @@ -315,12 +334,12 @@ export interface GaugeMiniLayerConfig { valueFontSize?: number valueFontColorInside?: string valueFontColorOutside?: string - valueFormater?: (value: number) => string + valueFormater?: ((value: number) => string) | FormatStatValueOptions axesSteps?: number | 'thresholds' | undefined | number[] axesFontSize?: number axesFontColor?: string - axesFormater?: (value: number) => string + axesFormater?: ((value: number) => string) | FormatStatValueOptions } export interface SingleStatLayerConfig { diff --git a/giraffe/src/utils/formatStatValue.ts b/giraffe/src/utils/formatStatValue.ts index bc56768e..32fda6d8 100644 --- a/giraffe/src/utils/formatStatValue.ts +++ b/giraffe/src/utils/formatStatValue.ts @@ -6,7 +6,7 @@ import {DecimalPlaces} from '../types' export const MAX_DECIMAL_PLACES = 10 -interface FormatStatValueOptions { +export interface FormatStatValueOptions { decimalPlaces?: DecimalPlaces prefix?: string suffix?: string diff --git a/giraffe/src/utils/gaugeMiniThemeNormalize.ts b/giraffe/src/utils/gaugeMiniThemeNormalize.ts new file mode 100644 index 00000000..1bc5a806 --- /dev/null +++ b/giraffe/src/utils/gaugeMiniThemeNormalize.ts @@ -0,0 +1,153 @@ +import {FormatStatValueOptions, formatStatValue} from './formatStatValue' +import { + GaugeMiniBarsDefinitions, + GaugeMiniColors, + GaugeMiniBarsDefinitionsArr, +} from '../types' +import {GaugeMiniLayerConfig} from '..' +import {color as d3Color} from 'd3-color' +import {range} from 'd3-array' + +export const throwReturn = (msg: string): T => { + throw new Error(msg) +} + +type TBarsDefinitions = GaugeMiniBarsDefinitions<{[key: string]: true}> + +type RestrictedTypesProperties = { + barsDefinitions: TBarsDefinitions + colors?: GaugeMiniColors + valueFormater?: (value: number) => string + + axesSteps: undefined | number[] + axesFormater?: (value: number) => string +} + +export type GaugeMiniThemeNormalized = Omit< + GaugeMiniLayerConfig, + keyof RestrictedTypesProperties +> & + RestrictedTypesProperties + +const getFormater = ( + formater: ((value: number) => string) | FormatStatValueOptions +): ((value: number) => string) => + typeof formater === 'function' + ? formater + : (value: number) => formatStatValue(value, formater) + +const isBarsDefinitionsArrayStyle = ( + barsDefinitions: GaugeMiniLayerConfig['barsDefinitions'] +): barsDefinitions is GaugeMiniBarsDefinitionsArr => { + return Array.isArray(barsDefinitions.groupByColumns) +} + +const getBarsDefinitions = ( + barsDefinitions: GaugeMiniLayerConfig['barsDefinitions'] +): TBarsDefinitions => { + if (!isBarsDefinitionsArrayStyle(barsDefinitions)) { + return barsDefinitions + } + + const {groupByColumns, bars} = barsDefinitions + + return { + groupByColumns: groupByColumns.reduce( + (obj, prop) => ((obj[prop] = true), obj), + {} as {[key: string]: true} + ), + bars: bars?.map(x => ({ + barDef: x.barDef.reduce( + (obj, prop, i) => ((obj[prop] = groupByColumns[i]), obj), + {} as Required['bars'][number]['barDef'] + ), + label: x.label, + })), + } +} + +const getAxesSteps = ( + axesSteps: number | 'thresholds' | undefined | number[], + colors: GaugeMiniColors +): number[] | undefined => { + if (axesSteps === undefined || axesSteps === null) { + return undefined + } + const { + max: {value: max}, + min: {value: min}, + } = colors + + if (Array.isArray(axesSteps)) { + if (axesSteps.some(x => x < min || x > max)) { + throw new Error(`All axes values must be inside range of colors!`) + } + return axesSteps + } + + if (axesSteps === 'thresholds') { + return (colors.thresholds ?? []).map(x => x.value) + } + + if (Number.isInteger(axesSteps)) { + const colorLen = max - min + + return range(axesSteps).map( + x => ((x + 1) * colorLen) / (axesSteps + 1) + min + ) + } + + throw new Error( + `AxesSteps must be number | "thresholds" | number[]` + + ` | undefined. But it's value is ${JSON.stringify(axesSteps)}` + ) +} + +const getColors = (theme: Required): GaugeMiniColors => { + const {gaugeMiniColors: colors} = theme + + if (!Array.isArray(colors)) { + return { + ...colors, + thresholds: (colors.thresholds ?? []).sort( + ({value: a}, {value: b}) => a - b + ), + } + } + + colors.forEach( + ({hex, name}) => + d3Color(hex) ?? + throwReturn(`Object "${hex}" isn"t valid color for name:${name}`) + ) + + return { + min: + colors.find(x => x.type === 'min') ?? + throwReturn('color of type min must be defined'), + max: + colors.find(x => x.type === 'max') ?? + throwReturn('color of type max must be defined'), + thresholds: colors + .filter(({type}) => type === 'threshold') + .sort(({value: a}, {value: b}) => a - b), + } +} + +// todo: more validations +export const gaugeMiniNormalizeTheme = ( + theme: Required +): Required => { + const colors = getColors(theme) + return { + ...theme, + barsDefinitions: getBarsDefinitions(theme.barsDefinitions), + colors, + valueFormater: getFormater(theme.valueFormater), + + axesSteps: getAxesSteps(theme.axesSteps, colors), + axesFormater: getFormater(theme.axesFormater), + } +} + +export const getGaugeMiniBarsDefinitions = getBarsDefinitions diff --git a/stories/src/gaugeMini.stories.tsx b/stories/src/gaugeMini.stories.tsx index 20fd78b7..b1bf0937 100644 --- a/stories/src/gaugeMini.stories.tsx +++ b/stories/src/gaugeMini.stories.tsx @@ -62,7 +62,7 @@ const editableLayer = (theme: Theme): Theme & {numberOfBars: number} => ({ oveflowFraction: number('oveflowFraction', theme.oveflowFraction), // todo: add knobs - gaugeColors: theme.gaugeColors, + gaugeMiniColors: theme.gaugeMiniColors, colorSecondary: color('colorSecondary', theme.colorSecondary), labelMain: text('labelMain', 'Gauge-mini example'), From 6632abaf6355d19bac38a9313267034c4c12aed0 Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Tue, 29 Dec 2020 04:23:00 +0100 Subject: [PATCH 08/34] fix: don't throw error on axesSteps out of bounds --- giraffe/src/utils/gaugeMiniThemeNormalize.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/giraffe/src/utils/gaugeMiniThemeNormalize.ts b/giraffe/src/utils/gaugeMiniThemeNormalize.ts index 1bc5a806..6bca02f1 100644 --- a/giraffe/src/utils/gaugeMiniThemeNormalize.ts +++ b/giraffe/src/utils/gaugeMiniThemeNormalize.ts @@ -79,10 +79,11 @@ const getAxesSteps = ( } = colors if (Array.isArray(axesSteps)) { - if (axesSteps.some(x => x < min || x > max)) { - throw new Error(`All axes values must be inside range of colors!`) + const steps = axesSteps.filter(x => x > min || x < max) + if (axesSteps.length !== steps.length) { + console.error(`All axes values must be inside range of colors!`) } - return axesSteps + return steps } if (axesSteps === 'thresholds') { From afbd78dbf9a1b4d215b726f6338b7f1d48d5b439 Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Tue, 29 Dec 2020 23:23:43 +0100 Subject: [PATCH 09/34] docs: gauge mini --- giraffe/README.md | 89 ++++++++++++++++++++++++++--------------------- 1 file changed, 49 insertions(+), 40 deletions(-) diff --git a/giraffe/README.md b/giraffe/README.md index acff5413..ffc0b9cb 100644 --- a/giraffe/README.md +++ b/giraffe/README.md @@ -699,22 +699,19 @@ TableGraphLayerConfig uses the `fluxResponse` property from `config` as the data - **overflowDelta**: _number. Optional. Defaults to 0.03 when excluded._ This constant expresses how far past the gauge min or gauge max the needle should be drawn if the value for the needle is less than gauge min or greater than the gauge max. It is expressed as a fraction of the circumference of a circle, e.g. 0.5 means draw halfway around the gauge from the min or max value. -- **GaugeLayerConfig**: _Object._ Maximum one per ``. Properties are: +- **GaugeMiniLayerConfig**: _Object._ Maximum one per ``. Properties are: - 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 theme `GAUGE_MINI_THEME_BULLET_DARK` - 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 + - **barsDefinitions** _{groupByColumns; bars;}_ _(types of properties based on selected style)_ + - Object style: + - **groupByColumns** _{ [key: string]: true }_ bar for each unique combination of given columns values. _(example: `{cpu: true, _field: true}`)_ + - **bars** _{ barDef: { [key in keyof T]: string }, label?: string }[]_ where _barDef_ contains values for specific bar columns and label for this bar. + - _or_ Array style: + - **groupByColumns** _string[]_ bar for each unique combination of given columns values. _(example: `['cpu', '_field', ]`)_ + - **bars** _{ barDef: string[], label?: string }[]_ where _barDef_ contains values for specific bar columns and label for this bar. Each barDef value belongs to key grom groupByColumns with same index. - **mode**: _'progress' | 'bullet'._ - `'bullet'` backgroud bar is colored and value bar has always secondary color @@ -738,20 +735,23 @@ TableGraphLayerConfig uses the `fluxResponse` property from `config` as the data - **oveflowFraction** _number_ fraction number defining how much can value bar go outside of background bar. e.g. with `oveflowFraction: .1` will value bar have max length 110% of background bar for max value and will have 10% length to the left from background bar for minimal value. - - **gaugeColors** _Color[]_ An array of objects that defines the colors of the Gauge. Each object has the following properties. - - - **id**: _string. **Required**._ The id for this color. Should be unique within the **gaugeColors** array. - - - **type**: _'min' | 'max' | 'threshold'. **Required**._ The type of value associated with this color. _'min'_ type comes first, _'max'_ type comes last, and _'threshold'_ types are in between _'min'_ and _'max'_. **gaugeColors** must contain at least one _'min'_ type and one _'max'_ type for the Gauge-mini to have color. Only the first _'min'_ and first _'max'_ in the array are recognized for each of their respective types. The color will change as a gradient if the **gaugeColors** array contains no 'threshold' that means gradient background bar for 'bullet' mode and continuous value bar color change for 'progress' mode. The color will be segmented if any _'threshold'_ types are included in the **gaugeColors** array that means step color change background bar for 'bullet' mode and fixed color of value bar based on value change for 'progress' mode. + - **gaugeColors** _types based on used style_ + - giraffe style _Color[]_ An array of objects that defines the colors of the Gauge. Each object has the following properties. - - **hex**: _string. **Required**._ The [_color hex_](https://www.color-hex.com/) string for this color. + - **id**: _string. **Required**._ The id for this color. Should be unique within the **gaugeColors** array. - - **name**: _string. **Required**._ For descriptive purposes only. The name given to this color. - - - **value**: _number. **Required**._ The starting gauge value associated with this color. + - **type**: _'min' | 'max' | 'threshold'. **Required**._ The type of value associated with this color. _'min'_ type comes first, _'max'_ type comes last, and _'threshold'_ types are in between _'min'_ and _'max'_. **gaugeColors** must contain at least one _'min'_ type and one _'max'_ type for the Gauge-mini to have color. Only the first _'min'_ and first _'max'_ in the array are recognized for each of their respective types. The color will change as a gradient if the **gaugeColors** array contains no 'threshold' that means gradient background bar for 'bullet' mode and continuous value bar color change for 'progress' mode. The color will be segmented if any _'threshold'_ types are included in the **gaugeColors** array that means step color change background bar for 'bullet' mode and fixed color of value bar based on value change for 'progress' mode. + - **hex**: _string. **Required**._ The [_color hex_](https://www.color-hex.com/) string for this color. + - **name**: _string. **Required**._ For descriptive purposes only. The name given to this color. + - **value**: _number. **Required**._ The starting gauge value associated with this color. + - mini gauge style: _{min; max; thresholds; }_ + - _ColorHexValue_ is _{ value: number; hex: string; }_ where hex is [_color hex_](https://www.color-hex.com/) adn value is where color is applied. + - **min** _ColorHexValue_ **Required** is minimaln value of gauge + - **max** _ColorHexValue_ **Required** is maximaln value of gauge + - **thresholds** _ColorHexValue[]_ is thresholds of gauge. The color will change as a gradient if no thresholds present, that means gradient background bar for 'bullet' mode and continuous value bar color change for 'progress' mode. The color will be segmented one or more thresholds present, that means step color change background bar for 'bullet' mode and fixed color of value bar based on value change for 'progress' mode. - **colorSecondary** _string_ Secondary color used for value bar in 'bullet' mode or for background bar in 'progress' mode @@ -776,8 +776,6 @@ TableGraphLayerConfig uses the `fluxResponse` property from `config` as the data - **valueFontColorOutside** _string_ Text value color when value bar is not behind the text - - **valueFormater** _(value: number) => string_ Function that defines how will be text value shown based on current value. e.g. ```valueFormater: (num: number) => `${num.toFixed(0)}%` ``` for _value=23.213_ will show text value _23%_. - Axes - **axesSteps** _number | 'thresholds' | undefined | number[]_ Defines where to show axes: - _number_ number of how many evenly distributed axes values will be shown between min and max value. Only min and max value will be shown when `axesSteps: 0` @@ -789,7 +787,18 @@ TableGraphLayerConfig uses the `fluxResponse` property from `config` as the data - **axesFontColor** _string_ Color of axes values and axes lines - - **axesFormater** _(value: number) => string_ Same as **valueFormater** for axes values + - Formaters **valueFormater** and **axesFormater** _(value: number) => string_ or _FormatStatValueOptions_ + - How will be text value on bar and axes values formated. + - _FormatStatValueOptions_ + - **prefix**: _string. Optional._ The text that appears before the gauge value. Use an empty string if no text is preferred. + + - **suffix**: _string. Optional._ The text that appears after the gauge value. Use an empty string if no text is preferred. + + - **decimalPlaces**: _Object. Optional._ + + - **isEnforced**: _boolean. Optional. Defaults to false when not included._ Indicates whether the number of decimal places ("**digits**") will be enforced. When **isEnforced** is falsy or omitted, **digits** will be locked to 2 for stat values with a decimal and 0 for stat values that are integers, and the **digits** option will be ignored. + - **digits**: _number. Optional. Defaults to 0 when not included. Maximum 10._ When **digits** is a non-integer number, the decimal portion is ignored. Represents the number of decimal places to display in the stat value. Displayed stat value is subject to rounding. + - example ```valueFormater: (num: number) => `${num.toFixed(0)}%` ``` for _value=23.213_ will show text value _23%_. **Precreated themes** - `GAUGE_MINI_THEME_BULLET_DARK` @@ -798,7 +807,7 @@ TableGraphLayerConfig uses the `fluxResponse` property from `config` as the data type: 'gauge mini', mode: 'bullet', textMode: 'follow', - bars: [], + barsDefinitions: {groupByColumns: ["_field"]}, valueHeight: 18, gaugeHeight: 25, @@ -806,9 +815,9 @@ TableGraphLayerConfig uses the `fluxResponse` property from `config` as the data gaugeRounding: 3, barPaddings: 5, sidePaddings: 20, - oveflowFraction: 0.03, + oveflowFraction: .03, - gaugeColors: [ + gaugeMiniColors: [ {value: 0, type: 'min', hex: InfluxColors.Krypton}, {value: 50, type: 'threshold', hex: InfluxColors.Sulfur}, {value: 75, type: 'threshold', hex: InfluxColors.Topaz}, @@ -827,12 +836,12 @@ TableGraphLayerConfig uses the `fluxResponse` property from `config` as the data valueFontSize: 12, valueFontColorOutside: InfluxColors.Raven, valueFontColorInside: InfluxColors.Cloud, - valueFormater: (val: number) => val.toFixed(0), + valueFormater: {}, axesSteps: 'thresholds', axesFontSize: 11, axesFontColor: InfluxColors.Forge, - axesFormater: (val: number) => val.toFixed(0), + axesFormater: {}, } ``` - `GAUGE_MINI_THEME_PROGRESS_DARK` @@ -841,39 +850,39 @@ TableGraphLayerConfig uses the `fluxResponse` property from `config` as the data type: 'gauge mini', mode: 'progress', textMode: 'follow', - bars: [], - + barsDefinitions: {groupByColumns: ['_field']}, + valueHeight: 20, gaugeHeight: 20, valueRounding: 3, gaugeRounding: 3, barPaddings: 5, sidePaddings: 20, - oveflowFraction: 0.03, - - gaugeColors: [ + oveflowFraction: .03, + + gaugeMiniColors: [ {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), - + valueFormater: {}, + axesSteps: undefined as any, axesFontSize: 11, axesFontColor: InfluxColors.Forge, - axesFormater: (val: number) => val.toFixed(0), + axesFormater: {}, } ``` From 09675d137118736e19e1c75b0ca05e323b9086b8 Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Tue, 29 Dec 2020 23:48:34 +0100 Subject: [PATCH 10/34] fix: autocenter fixed --- giraffe/src/components/GaugeMini.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/giraffe/src/components/GaugeMini.tsx b/giraffe/src/components/GaugeMini.tsx index c3f2dc2f..2c9d7559 100644 --- a/giraffe/src/components/GaugeMini.tsx +++ b/giraffe/src/components/GaugeMini.tsx @@ -476,6 +476,8 @@ export const GaugeMini: FunctionComponent = ({ labelBarsFontColor, labelBarsFontSize, colors, + axesSteps, + axesFontSize, } = theme const [barLabelsWidth] = useState([]) @@ -501,7 +503,19 @@ export const GaugeMini: FunctionComponent = ({ const [autocenterToken, setAutocenterToken] = useState(0) useEffect(() => { setAutocenterToken(x => x + 1) - }, [barLabelWidth, sidePaddings, valueHeight, width, height]) + }, [ + width, + height, + barLabelWidth, + barsDefinitions, + valueHeight, + gaugeHeight, + barPaddings, + sidePaddings, + labelMainFontSize, + axesSteps, + axesFontSize, + ]) /** return value as fraction 0->min 1->max */ const getFrac = (val: number): number => (val - colors.min.value) / colorLen From be8ed845a73adc26cc58cacae7fab0978d678772 Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Wed, 30 Dec 2020 00:18:53 +0100 Subject: [PATCH 11/34] style: types fixed --- giraffe/src/components/SizedTable.tsx | 9 +++------ giraffe/src/constants/gaugeMiniStyles.ts | 2 +- stories/src/gaugeMini.stories.tsx | 4 +--- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/giraffe/src/components/SizedTable.tsx b/giraffe/src/components/SizedTable.tsx index 10fb09bd..e422192a 100644 --- a/giraffe/src/components/SizedTable.tsx +++ b/giraffe/src/components/SizedTable.tsx @@ -2,7 +2,6 @@ import React, {FunctionComponent, CSSProperties} from 'react' import { GaugeLayerConfig, - GaugeMiniLayerConfig, SizedConfig, TableGraphLayerConfig, LayerTypes, @@ -89,16 +88,14 @@ export const SizedTable: FunctionComponent = ({ key={layerIndex} table={newTableFromConfig(config)} columns={ - getGaugeMiniBarsDefinitions( - (layerConfig as Required) - .barsDefinitions - ).groupByColumns + getGaugeMiniBarsDefinitions(layerConfig.barsDefinitions) + .groupByColumns } > {latestValues => ( } + theme={layerConfig} /> )} diff --git a/giraffe/src/constants/gaugeMiniStyles.ts b/giraffe/src/constants/gaugeMiniStyles.ts index 5013ab05..90d22373 100644 --- a/giraffe/src/constants/gaugeMiniStyles.ts +++ b/giraffe/src/constants/gaugeMiniStyles.ts @@ -75,7 +75,7 @@ export const GAUGE_MINI_THEME_PROGRESS_DARK: Required = { valueFontColorOutside: InfluxColors.Cloud, valueFormater: {}, - axesSteps: undefined as any, + axesSteps: undefined, axesFontSize: 11, axesFontColor: InfluxColors.Forge, axesFormater: {}, diff --git a/stories/src/gaugeMini.stories.tsx b/stories/src/gaugeMini.stories.tsx index b1bf0937..45820dd8 100644 --- a/stories/src/gaugeMini.stories.tsx +++ b/stories/src/gaugeMini.stories.tsx @@ -13,9 +13,7 @@ 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' +import {gaugeMiniTable} from './data/gaugeMiniLayer' type Theme = Required From 9deb5b45aa3a2e6760f67f94f8d978d3e8fddeb3 Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Tue, 19 Jan 2021 12:57:21 +0100 Subject: [PATCH 12/34] fix: autocenter cyclic refresh fixed --- giraffe/src/components/GaugeMini.tsx | 8 +++--- giraffe/src/utils/gaugeMiniThemeNormalize.ts | 28 +++++++++++++++----- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/giraffe/src/components/GaugeMini.tsx b/giraffe/src/components/GaugeMini.tsx index 2c9d7559..f9d43db6 100644 --- a/giraffe/src/components/GaugeMini.tsx +++ b/giraffe/src/components/GaugeMini.tsx @@ -6,7 +6,7 @@ import {scaleLinear} from 'd3-scale' // Types import {GaugeMiniColors, GaugeMiniLayerConfig} from '../types' import { - gaugeMiniNormalizeTheme, + gaugeMiniNormalizeThemeMemoized, GaugeMiniThemeNormalized, } from '../utils/gaugeMiniThemeNormalize' @@ -463,7 +463,7 @@ export const GaugeMini: FunctionComponent = ({ width, height, }) => { - const theme = gaugeMiniNormalizeTheme(_theme) + const theme = gaugeMiniNormalizeThemeMemoized(_theme) const { gaugeHeight, @@ -500,9 +500,9 @@ export const GaugeMini: FunctionComponent = ({ labelMapping[mstring] = x.label }) - const [autocenterToken, setAutocenterToken] = useState(0) + const [autocenterToken, setAutocenterToken] = useState(Date.now()) useEffect(() => { - setAutocenterToken(x => x + 1) + setAutocenterToken(Date.now()) }, [ width, height, diff --git a/giraffe/src/utils/gaugeMiniThemeNormalize.ts b/giraffe/src/utils/gaugeMiniThemeNormalize.ts index 6bca02f1..3fcca275 100644 --- a/giraffe/src/utils/gaugeMiniThemeNormalize.ts +++ b/giraffe/src/utils/gaugeMiniThemeNormalize.ts @@ -7,6 +7,7 @@ import { import {GaugeMiniLayerConfig} from '..' import {color as d3Color} from 'd3-color' import {range} from 'd3-array' +import {useMemo} from 'react' export const throwReturn = (msg: string): T => { throw new Error(msg) @@ -104,9 +105,9 @@ const getAxesSteps = ( ) } -const getColors = (theme: Required): GaugeMiniColors => { - const {gaugeMiniColors: colors} = theme - +const getColors = ( + colors: Required['gaugeMiniColors'] +): GaugeMiniColors => { if (!Array.isArray(colors)) { return { ...colors, @@ -136,17 +137,30 @@ const getColors = (theme: Required): GaugeMiniColors => { } // todo: more validations -export const gaugeMiniNormalizeTheme = ( +export const gaugeMiniNormalizeThemeMemoized = ( theme: Required ): Required => { - const colors = getColors(theme) + const barsDefinitions = useMemo( + () => getBarsDefinitions(theme.barsDefinitions), + [theme.barsDefinitions] + ) + + const colors = useMemo(() => getColors(theme.gaugeMiniColors), [ + theme.gaugeMiniColors, + ]) + + const axesSteps = useMemo(() => getAxesSteps(theme.axesSteps, colors), [ + theme.axesSteps, + colors, + ]) + return { ...theme, - barsDefinitions: getBarsDefinitions(theme.barsDefinitions), + barsDefinitions, colors, valueFormater: getFormater(theme.valueFormater), - axesSteps: getAxesSteps(theme.axesSteps, colors), + axesSteps, axesFormater: getFormater(theme.axesFormater), } } From 70c89080bdcbe4559fd3001d269deb18f168868b Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Wed, 20 Jan 2021 08:09:54 +0100 Subject: [PATCH 13/34] feat: stories missing knobs added --- giraffe/README.md | 2 +- giraffe/src/utils/gaugeMiniThemeNormalize.ts | 3 +- stories/src/gaugeMini.stories.tsx | 254 ++++++++++++++++--- 3 files changed, 221 insertions(+), 38 deletions(-) diff --git a/giraffe/README.md b/giraffe/README.md index ffc0b9cb..55d068a5 100644 --- a/giraffe/README.md +++ b/giraffe/README.md @@ -713,7 +713,7 @@ TableGraphLayerConfig uses the `fluxResponse` property from `config` as the data - **groupByColumns** _string[]_ bar for each unique combination of given columns values. _(example: `['cpu', '_field', ]`)_ - **bars** _{ barDef: string[], label?: string }[]_ where _barDef_ contains values for specific bar columns and label for this bar. Each barDef value belongs to key grom groupByColumns with same index. - - **mode**: _'progress' | 'bullet'._ + - **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 diff --git a/giraffe/src/utils/gaugeMiniThemeNormalize.ts b/giraffe/src/utils/gaugeMiniThemeNormalize.ts index 3fcca275..5a1dd663 100644 --- a/giraffe/src/utils/gaugeMiniThemeNormalize.ts +++ b/giraffe/src/utils/gaugeMiniThemeNormalize.ts @@ -18,6 +18,7 @@ type TBarsDefinitions = GaugeMiniBarsDefinitions<{[key: string]: true}> type RestrictedTypesProperties = { barsDefinitions: TBarsDefinitions colors?: GaugeMiniColors + gaugeMiniColors?: GaugeMiniColors valueFormater?: (value: number) => string axesSteps: undefined | number[] @@ -136,7 +137,6 @@ const getColors = ( } } -// todo: more validations export const gaugeMiniNormalizeThemeMemoized = ( theme: Required ): Required => { @@ -158,6 +158,7 @@ export const gaugeMiniNormalizeThemeMemoized = ( ...theme, barsDefinitions, colors, + gaugeMiniColors: colors, valueFormater: getFormater(theme.valueFormater), axesSteps, diff --git a/stories/src/gaugeMini.stories.tsx b/stories/src/gaugeMini.stories.tsx index 45820dd8..f44d6b46 100644 --- a/stories/src/gaugeMini.stories.tsx +++ b/stories/src/gaugeMini.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import {storiesOf} from '@storybook/react' -import {withKnobs, number, select, text} from '@storybook/addon-knobs' +import {withKnobs, number, select, boolean, text} from '@storybook/addon-knobs' import { Config, GaugeMiniLayerConfig, @@ -14,8 +14,16 @@ import { GAUGE_MINI_THEME_PROGRESS_DARK, } from '../../giraffe/src/constants/gaugeMiniStyles' import {gaugeMiniTable} from './data/gaugeMiniLayer' +import { + gaugeMiniNormalizeThemeMemoized, + GaugeMiniThemeNormalized, +} from '../../giraffe/src/utils/gaugeMiniThemeNormalize' +import {range} from 'd3-array' +import {FormatStatValueOptions} from '../../giraffe/src/utils/formatStatValue' type Theme = Required +type ThemeNormalized = Required +type Color = ThemeNormalized['gaugeMiniColors']['min'] const color = (() => { const colors = (() => { @@ -24,11 +32,74 @@ const color = (() => { return obj })() - // todo: type definitions return (label: string, ...rest: any[]) => select(label, colors, ...rest) })() -const editableLayer = (theme: Theme): Theme & {numberOfBars: number} => ({ +const randInt: { + (max: number): number + (min: number, max: number): number +} = (min: number, max?: number) => { + if (max === undefined) return Math.floor(Math.random() * min) + return min + Math.floor(Math.random() * (max - min)) +} + +const colorRandom = () => { + const colors = Object.values(InfluxColors) + return colors[randInt(colors.length)] +} + +const colorHexValue = ( + label: string, + colorDefault: Color, + groupID: string, + numberOptions: any = {} +): Color => { + return { + hex: color(label + ' - hex', colorDefault?.hex ?? '#888888', groupID), + value: number( + label + ' - value', + colorDefault?.value ?? 50, + numberOptions, + groupID + ), + } +} + +const formatStatValueOptions = ( + label: string, + formDefault: FormatStatValueOptions, + groupID: string +): FormatStatValueOptions => { + const l = (sublabel: string) => label + ' - ' + sublabel + return { + decimalPlaces: { + isEnforced: boolean( + l('decimalPlaces - isEnforced'), + formDefault?.decimalPlaces?.isEnforced ?? false, + groupID + ), + digits: number( + l('decimalPlaces - digits'), + formDefault?.decimalPlaces?.digits ?? 2, + {}, + groupID + ), + }, + prefix: text(l('prefix'), formDefault?.prefix, groupID), + suffix: text(l('suffix'), formDefault?.suffix, groupID), + } +} + +const knobGroups = { + basics: 'basics', + sizing: 'sizing', + colors: 'colors', + labels: 'labels', +} + +const editableLayer = ( + theme: ThemeNormalized +): Theme & {numberOfBars: number} => ({ type: theme.type, mode: select( 'Mode', @@ -36,7 +107,8 @@ const editableLayer = (theme: Theme): Theme & {numberOfBars: number} => ({ bullet: 'bullet', progress: 'progress', }, - theme.mode + theme.mode, + knobGroups.basics ), textMode: select( 'textMode', @@ -44,44 +116,144 @@ const editableLayer = (theme: Theme): Theme & {numberOfBars: number} => ({ follow: 'follow', left: 'left', }, - theme.textMode + theme.textMode, + knobGroups.basics ), - numberOfBars: number('number of bars', 1), + numberOfBars: number('number of bars', 1, {}, knobGroups.basics), barsDefinitions: { groupByColumns: {_field: true}, }, - valueHeight: number('valueHeight', theme.valueHeight), - gaugeHeight: number('gaugeHeight', theme.gaugeHeight), - valueRounding: number('valueRounding', theme.valueRounding), - gaugeRounding: number('gaugeRounding', theme.gaugeRounding), - barPaddings: number('barPaddings', theme.barPaddings), - sidePaddings: number('gaugePaddingSides', theme.sidePaddings), - oveflowFraction: number('oveflowFraction', theme.oveflowFraction), + valueHeight: number('valueHeight', theme.valueHeight, {}, knobGroups.sizing), + gaugeHeight: number('gaugeHeight', theme.gaugeHeight, {}, knobGroups.sizing), + valueRounding: number( + 'valueRounding', + theme.valueRounding, + {}, + knobGroups.sizing + ), + gaugeRounding: number( + 'gaugeRounding', + theme.gaugeRounding, + {}, + knobGroups.sizing + ), + barPaddings: number('barPaddings', theme.barPaddings, {}, knobGroups.sizing), + sidePaddings: number( + 'gaugePaddingSides', + theme.sidePaddings, + {}, + knobGroups.sizing + ), + oveflowFraction: number( + 'oveflowFraction', + theme.oveflowFraction, + {}, + knobGroups.sizing + ), + + colorSecondary: color( + 'colorSecondary', + theme.colorSecondary, + knobGroups.colors + ), + gaugeMiniColors: (() => { + const {thresholds} = theme.gaugeMiniColors + const min = colorHexValue( + 'colorMin', + theme.gaugeMiniColors.min, + knobGroups.colors + ) + const max = colorHexValue( + 'colorMax', + theme.gaugeMiniColors.max, + knobGroups.colors + ) - // todo: add knobs - gaugeMiniColors: theme.gaugeMiniColors, - colorSecondary: color('colorSecondary', theme.colorSecondary), + const colors: Theme['gaugeMiniColors'] = { + min, + max, + thresholds: (() => { + const length = number( + 'thresholds number', + thresholds.length, + {}, + knobGroups.colors + ) - labelMain: text('labelMain', 'Gauge-mini example'), - labelMainFontSize: number('labelMainFontSize', theme.labelMainFontSize), - labelMainFontColor: color('labelMainFontColor', theme.labelMainFontColor), + return range(length).map(x => + colorHexValue( + `threshold ${x}`, + thresholds[x] || { + hex: colorRandom(), + value: randInt(min.value + 1, max.value), + }, + knobGroups.colors, + { + range: true, + min: min.value, + max: max.value, + step: 1, + } + ) + ) + })(), + } + return colors + })(), - labelBarsFontSize: number('labelBarsFontSize', theme.labelBarsFontSize), - labelBarsFontColor: color('labelBarsFontColor', theme.labelBarsFontColor), + labelMain: text('labelMain', 'Gauge-mini example', knobGroups.labels), + labelMainFontSize: number( + 'labelMainFontSize', + theme.labelMainFontSize, + {}, + knobGroups.labels + ), + labelMainFontColor: color( + 'labelMainFontColor', + theme.labelMainFontColor, + knobGroups.labels + ), - valuePadding: number('valuePadding', theme.valuePadding), - valueFontSize: number('valueFontSize', theme.valueFontSize), + labelBarsFontSize: number( + 'labelBarsFontSize', + theme.labelBarsFontSize, + {}, + knobGroups.labels + ), + labelBarsFontColor: color( + 'labelBarsFontColor', + theme.labelBarsFontColor, + knobGroups.labels + ), + + valuePadding: number( + 'valuePadding', + theme.valuePadding, + {}, + knobGroups.labels + ), + valueFontSize: number( + 'valueFontSize', + theme.valueFontSize, + {}, + knobGroups.labels + ), valueFontColorInside: color( 'valueFontColorInside', - theme.valueFontColorInside + theme.valueFontColorInside, + knobGroups.labels ), valueFontColorOutside: color( 'valueFontColorOutside', - theme.valueFontColorOutside + theme.valueFontColorOutside, + knobGroups.labels + ), + valueFormater: formatStatValueOptions( + 'valueFormater', + theme.valueFormater as any, + knobGroups.labels ), - // todo: add knobs - valueFormater: theme.valueFormater, axesSteps: select( 'axesSteps', @@ -92,16 +264,26 @@ const editableLayer = (theme: Theme): Theme & {numberOfBars: number} => ({ 2: 2, undefined: null, }, - theme.axesSteps + 'thresholds', + knobGroups.basics + ), + axesFontSize: number( + 'axesFontSize', + theme.axesFontSize, + {}, + knobGroups.labels + ), + axesFontColor: color('axesFontColor', theme.axesFontColor, knobGroups.labels), + axesFormater: formatStatValueOptions( + 'axesFormater', + theme.axesFormater as any, + knobGroups.labels ), - axesFontSize: number('axesFontSize', theme.axesFontSize), - axesFontColor: color('axesFontColor', theme.axesFontColor), - // todo: add knobs - axesFormater: theme.axesFormater, }) -const createStory = (theme: Theme) => () => { - const layer = editableLayer(theme) +const Story: React.FC<{theme: Theme}> = ({theme}) => { + const normalized = gaugeMiniNormalizeThemeMemoized(theme) + const layer = editableLayer(normalized) const config: Config = { table: gaugeMiniTable(0, 100, layer.numberOfBars), @@ -118,5 +300,5 @@ const createStory = (theme: Theme) => () => { storiesOf('Gauge mini', module) .addDecorator(withKnobs) - .add('Bullet', createStory(GAUGE_MINI_THEME_BULLET_DARK)) - .add('Progress', createStory(GAUGE_MINI_THEME_PROGRESS_DARK)) + .add('Bullet', () => ) + .add('Progress', () => ) From 4f1a9270bb875912e2b7d4370edcd2a151840042 Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Mon, 25 Jan 2021 12:02:10 +0100 Subject: [PATCH 14/34] fix: removed bar grouping feature --- giraffe/README.md | 18 ++- giraffe/src/components/GaugeMini.tsx | 43 ++------ giraffe/src/components/GaugeMiniLayer.tsx | 4 +- .../LatestMultipleValueTransform.tsx | 104 ++++++------------ giraffe/src/components/SizedTable.tsx | 5 - giraffe/src/constants/gaugeMiniStyles.ts | 4 +- giraffe/src/types/index.ts | 18 +-- giraffe/src/utils/gaugeMiniThemeNormalize.ts | 49 +-------- stories/src/gaugeMini.stories.tsx | 8 +- 9 files changed, 62 insertions(+), 191 deletions(-) diff --git a/giraffe/README.md b/giraffe/README.md index 55d068a5..e29bd8ff 100644 --- a/giraffe/README.md +++ b/giraffe/README.md @@ -703,15 +703,9 @@ TableGraphLayerConfig uses the `fluxResponse` property from `config` as the data All values (excluding **type**) are Optional and their defaults is defined by theme `GAUGE_MINI_THEME_BULLET_DARK` - - **type**: _'gauge mini'. **Required**._ Specifies that this LayerConfig is a gauge mini layer. + gauge mini creates one bar per unique __field_ value - - **barsDefinitions** _{groupByColumns; bars;}_ _(types of properties based on selected style)_ - - Object style: - - **groupByColumns** _{ [key: string]: true }_ bar for each unique combination of given columns values. _(example: `{cpu: true, _field: true}`)_ - - **bars** _{ barDef: { [key in keyof T]: string }, label?: string }[]_ where _barDef_ contains values for specific bar columns and label for this bar. - - _or_ Array style: - - **groupByColumns** _string[]_ bar for each unique combination of given columns values. _(example: `['cpu', '_field', ]`)_ - - **bars** _{ barDef: string[], label?: string }[]_ where _barDef_ contains values for specific bar columns and label for this bar. Each barDef value belongs to key grom groupByColumns with same index. + - **type**: _'gauge mini'. **Required**._ Specifies that this LayerConfig is a gauge mini layer. - **mode** _'progress' | 'bullet'._ - `'bullet'` backgroud bar is colored and value bar has always secondary color @@ -763,6 +757,8 @@ TableGraphLayerConfig uses the `fluxResponse` property from `config` as the data - **labelMainFontColor** _string_ Main label color. Bar labels + - **labelBarsEnabled** _boolean_ Bar labels shown if true + - **labelBarsFontSize** _number_ Bar labels font size - **labelBarsFontColor** _string_ Bar labels font color @@ -798,7 +794,7 @@ TableGraphLayerConfig uses the `fluxResponse` property from `config` as the data - **isEnforced**: _boolean. Optional. Defaults to false when not included._ Indicates whether the number of decimal places ("**digits**") will be enforced. When **isEnforced** is falsy or omitted, **digits** will be locked to 2 for stat values with a decimal and 0 for stat values that are integers, and the **digits** option will be ignored. - **digits**: _number. Optional. Defaults to 0 when not included. Maximum 10._ When **digits** is a non-integer number, the decimal portion is ignored. Represents the number of decimal places to display in the stat value. Displayed stat value is subject to rounding. - - example ```valueFormater: (num: number) => `${num.toFixed(0)}%` ``` for _value=23.213_ will show text value _23%_. + - example ```valueFormater: (num: number) => `${((num || 0) * 100).toFixed(0)}%` ``` for _value=0.23213_ will show text value _23%_. **Precreated themes** - `GAUGE_MINI_THEME_BULLET_DARK` @@ -807,7 +803,6 @@ TableGraphLayerConfig uses the `fluxResponse` property from `config` as the data type: 'gauge mini', mode: 'bullet', textMode: 'follow', - barsDefinitions: {groupByColumns: ["_field"]}, valueHeight: 18, gaugeHeight: 25, @@ -829,6 +824,7 @@ TableGraphLayerConfig uses the `fluxResponse` property from `config` as the data labelMainFontSize: 13, labelMainFontColor: InfluxColors.Ghost, + labelBarsEnabled: false, labelBarsFontSize: 11, labelBarsFontColor: InfluxColors.Forge, @@ -850,7 +846,6 @@ TableGraphLayerConfig uses the `fluxResponse` property from `config` as the data type: 'gauge mini', mode: 'progress', textMode: 'follow', - barsDefinitions: {groupByColumns: ['_field']}, valueHeight: 20, gaugeHeight: 20, @@ -870,6 +865,7 @@ TableGraphLayerConfig uses the `fluxResponse` property from `config` as the data labelMainFontSize: 13, labelMainFontColor: InfluxColors.Ghost, + labelBarsEnabled: false, labelBarsFontSize: 11, labelBarsFontColor: InfluxColors.Forge, diff --git a/giraffe/src/components/GaugeMini.tsx b/giraffe/src/components/GaugeMini.tsx index f9d43db6..843d9cc5 100644 --- a/giraffe/src/components/GaugeMini.tsx +++ b/giraffe/src/components/GaugeMini.tsx @@ -5,6 +5,8 @@ import {scaleLinear} from 'd3-scale' // Types import {GaugeMiniColors, GaugeMiniLayerConfig} from '../types' +import {GroupedData} from './LatestMultipleValueTransform' + import { gaugeMiniNormalizeThemeMemoized, GaugeMiniThemeNormalized, @@ -13,28 +15,10 @@ import { interface Props { width: number height: number - values: {colsMString: string; value: number}[] + values: GroupedData theme: Required } -// todo: move into gauge utils -/** create merged string for given column string values. String is same for all columns with same values and unique for different ones */ -export const createColsMString = ( - groupedBy: T, - col: {[key in keyof T]: string} -): string => { - const columns = Object.keys(groupedBy) - .filter(x => groupedBy[x]) - .sort() - const columnValues = columns.map(x => col[x]) - /** - * replacing - with -- will ensures that rows - * { a: '0-1', b: '2' } and { a: '0', b: '1-2' } - * will not have same string (0-1-2 instead they will be 0--1-2 and 0-1--2) - */ - return columnValues.map(x => x.split('-').join('--')).join('-') -} - const barCssClass = 'gauge-mini-bar' //#region svg helpers @@ -473,6 +457,7 @@ export const GaugeMini: FunctionComponent = ({ labelMain, labelMainFontSize, labelMainFontColor, + labelBarsEnabled, labelBarsFontColor, labelBarsFontSize, colors, @@ -485,21 +470,11 @@ export const GaugeMini: FunctionComponent = ({ const barLabelWidth = Math.max(...barLabelsWidth) || 0 const barWidth = width - sidePaddings * 2 - barLabelWidth const maxBarHeight = Math.max(gaugeHeight, valueHeight) - const allBarsHeight = values.length * (maxBarHeight + barPaddings) - - const barsDefinitions = theme.barsDefinitions + const allBarsHeight = + Object.keys(values).length * (maxBarHeight + barPaddings) // create unified barsDefinition - const labelMapping: any = {} - barsDefinitions?.bars?.forEach(x => { - if (!x.label) { - return - } - const mstring = createColsMString(barsDefinitions.groupByColumns, x.barDef) - labelMapping[mstring] = x.label - }) - const [autocenterToken, setAutocenterToken] = useState(Date.now()) useEffect(() => { setAutocenterToken(Date.now()) @@ -507,7 +482,6 @@ export const GaugeMini: FunctionComponent = ({ width, height, barLabelWidth, - barsDefinitions, valueHeight, gaugeHeight, barPaddings, @@ -536,11 +510,10 @@ export const GaugeMini: FunctionComponent = ({ {labelMain} )} - {values.map(({colsMString, value}, i) => { + {Object.entries(values).map(([group, value], i) => { const y = 0 + i * (maxBarHeight + barPaddings) - const label = labelMapping?.[colsMString] - const textCenter = y + maxBarHeight / 2 + const label = labelBarsEnabled ? group : '' // todo: no rerender ? const onRectChanged = (r: DOMRect) => { diff --git a/giraffe/src/components/GaugeMiniLayer.tsx b/giraffe/src/components/GaugeMiniLayer.tsx index a6eb7bc7..d88f9d47 100644 --- a/giraffe/src/components/GaugeMiniLayer.tsx +++ b/giraffe/src/components/GaugeMiniLayer.tsx @@ -7,12 +7,14 @@ import {GaugeMiniLayerConfig} from '../types' import {GaugeMini} from './GaugeMini' import {GAUGE_MINI_THEME_BULLET_DARK} from '../constants/gaugeMiniStyles' +import {GroupedData} from './LatestMultipleValueTransform' interface Props { - values: {colsMString: string; value: number}[] + values: GroupedData theme: GaugeMiniLayerConfig } +// todo: move gauge mini here export const GaugeMiniLayer: FunctionComponent = (props: Props) => { const {theme, values} = props const themeOrDefault: Required = { diff --git a/giraffe/src/components/LatestMultipleValueTransform.tsx b/giraffe/src/components/LatestMultipleValueTransform.tsx index fc36890d..a5ce197a 100644 --- a/giraffe/src/components/LatestMultipleValueTransform.tsx +++ b/giraffe/src/components/LatestMultipleValueTransform.tsx @@ -1,34 +1,10 @@ // Libraries import React, {useMemo, FunctionComponent} from 'react' import {Table} from '../types' -import {createColsMString} from './GaugeMini' -interface SelectedColumns { - [key: string]: true -} - -export const getLatestValuesGrouped = ( - table: Table, - columnsObj: SelectedColumns -) => { - const columns = Object.keys(columnsObj).sort() - - columns.forEach(x => { - if (table.getColumnType(x) !== 'string') { - throw new Error( - `Data can be grouped only by string columns. But column ${x} is typeof ${table.getColumnType( - x - )}` - ) - } - }) - - const valueColumn = table.getColumn('_value', 'number') as number[] - - if (!valueColumn.length) { - return [] - } +export type GroupedData = {[key: string]: number} +const getTimeColumn = (table: Table) => { // Fallback to `_stop` column if `_time` column missing otherwise return empty array. let timeColData: number[] = [] @@ -41,73 +17,61 @@ export const getLatestValuesGrouped = ( return [] } - const groupColsData = columns.map(k => - table.getColumn(k, 'string') - ) as string[][] + return timeColData +} - const result: {[key: string]: number} = {} +export const getLatestValuesGrouped = ( + table: Table, + groupColumnKey: string +): GroupedData => { + const valueCol = table.getColumn('_value', 'number') + const groupCol = table.getColumn(groupColumnKey) - timeColData - // merge time with it's index - .map((time, index) => ({time, index})) - // remove entries without time - .filter(({time}) => time) - // todo: sort time complexity too high ... replace with another solution - // from low time to high time (last is last) - .sort(({time: t1}, {time: t2}) => t1 - t2) - // get relevant data from index (we don't need time anymore) - .map(({index}) => ({ - value: valueColumn[index], - groupRow: groupColsData.map(x => x[index]), - })) - // remove invalid data - .filter(({value}) => Number.isFinite(value) && typeof value === 'number') - // create result - .forEach(({value, groupRow}) => { - const grupObj = {} - groupRow.forEach((x, i) => (grupObj[columns[i]] = x)) - const strKey = createColsMString(columnsObj, grupObj) - // data is inserted from last to first so latest data is first - result[strKey] = value - }) + if (!valueCol.length) { + return {} + } - return result + return Object.fromEntries( + getTimeColumn(table) + // merge time with it's index + .map((time, index) => ({time, index})) + // remove entries without time + .filter(({time}) => time) + // todo: sort time complexity too high ... replace with linear solution + // from low time to high time (last is last) + .sort(({time: t1}, {time: t2}) => t1 - t2) + // get relevant data from index (we don't need time anymore) + .map(({index}) => [groupCol?.[index] ?? '', valueCol[index]] as const) + // remove invalid data + .filter( + ([_, value]) => typeof value === 'number' && Number.isFinite(value) + ) + ) } interface Props { table: Table - columns: SelectedColumns - children: (latestValue: {colsMString: string; value: number}[]) => JSX.Element + children: (latestValue: GroupedData) => JSX.Element quiet?: boolean } // todo: can return string ? export const LatestMultipleValueTransform: FunctionComponent = ({ table, - columns, quiet = false, children, }) => { - const latestValues = useMemo(() => getLatestValuesGrouped(table, columns), [ + const latestValues = useMemo(() => getLatestValuesGrouped(table, '_field'), [ table, ]) - if (latestValues.length === 0 && quiet) { - return null - } - - if (latestValues.length === 0) { - return ( + if (Object.keys(latestValues).length === 0) { + return quiet ? null : (

No latest value found

) } - const entries = Object.keys(latestValues).map(x => ({ - colsMString: x, - value: latestValues[x], - })) - - return children(entries) + return children(latestValues) } diff --git a/giraffe/src/components/SizedTable.tsx b/giraffe/src/components/SizedTable.tsx index e422192a..7beddfd8 100644 --- a/giraffe/src/components/SizedTable.tsx +++ b/giraffe/src/components/SizedTable.tsx @@ -17,7 +17,6 @@ import {TableGraphLayer} from './TableGraphLayer' import {usePlotEnv} from '../utils/usePlotEnv' import {LatestMultipleValueTransform} from './LatestMultipleValueTransform' -import {getGaugeMiniBarsDefinitions} from '../utils/gaugeMiniThemeNormalize' interface Props { config: SizedConfig @@ -87,10 +86,6 @@ export const SizedTable: FunctionComponent = ({ {latestValues => ( = { type: 'gauge mini', mode: 'bullet', textMode: 'follow', - barsDefinitions: {groupByColumns: ['_field']}, valueHeight: 18, gaugeHeight: 25, @@ -27,6 +26,7 @@ export const GAUGE_MINI_THEME_BULLET_DARK: Required = { labelMainFontSize: 13, labelMainFontColor: InfluxColors.Ghost, + labelBarsEnabled: false, labelBarsFontSize: 11, labelBarsFontColor: InfluxColors.Forge, @@ -46,7 +46,6 @@ export const GAUGE_MINI_THEME_PROGRESS_DARK: Required = { type: 'gauge mini', mode: 'progress', textMode: 'follow', - barsDefinitions: {groupByColumns: ['_field']}, valueHeight: 20, gaugeHeight: 20, @@ -66,6 +65,7 @@ export const GAUGE_MINI_THEME_PROGRESS_DARK: Required = { labelMainFontSize: 13, labelMainFontColor: InfluxColors.Ghost, + labelBarsEnabled: false, labelBarsFontSize: 11, labelBarsFontColor: InfluxColors.Forge, diff --git a/giraffe/src/types/index.ts b/giraffe/src/types/index.ts index 7faf123f..4142aa51 100644 --- a/giraffe/src/types/index.ts +++ b/giraffe/src/types/index.ts @@ -290,25 +290,8 @@ export type GaugeMiniColors = { thresholds?: ColorHexValue[] } -export interface GaugeMiniBarsDefinitions { - /** Defines which columns choose as unique bar indentificator. */ - groupByColumns: T - // todo: allow regex ? - /** Give label for given unique column values */ - bars?: {barDef: {[key in keyof T]: string}; label?: string}[] -} - -export interface GaugeMiniBarsDefinitionsArr { - groupByColumns: string[] - bars?: {barDef: string[]; label?: string}[] -} - export interface GaugeMiniLayerConfig { type: 'gauge mini' - /** Defines which columns choose as unique bar indentificator. Also bar labels can be defined here. */ - barsDefinitions: - | GaugeMiniBarsDefinitionsArr - | GaugeMiniBarsDefinitions<{[key: string]: true}> mode?: 'progress' | 'bullet' textMode?: 'follow' | 'left' @@ -327,6 +310,7 @@ export interface GaugeMiniLayerConfig { labelMainFontSize?: number labelMainFontColor?: string + labelBarsEnabled?: boolean labelBarsFontSize?: number labelBarsFontColor?: string diff --git a/giraffe/src/utils/gaugeMiniThemeNormalize.ts b/giraffe/src/utils/gaugeMiniThemeNormalize.ts index 5a1dd663..3b6572cb 100644 --- a/giraffe/src/utils/gaugeMiniThemeNormalize.ts +++ b/giraffe/src/utils/gaugeMiniThemeNormalize.ts @@ -1,22 +1,15 @@ +import {useMemo} from 'react' import {FormatStatValueOptions, formatStatValue} from './formatStatValue' -import { - GaugeMiniBarsDefinitions, - GaugeMiniColors, - GaugeMiniBarsDefinitionsArr, -} from '../types' +import {GaugeMiniColors} from '../types' import {GaugeMiniLayerConfig} from '..' import {color as d3Color} from 'd3-color' import {range} from 'd3-array' -import {useMemo} from 'react' export const throwReturn = (msg: string): T => { throw new Error(msg) } -type TBarsDefinitions = GaugeMiniBarsDefinitions<{[key: string]: true}> - type RestrictedTypesProperties = { - barsDefinitions: TBarsDefinitions colors?: GaugeMiniColors gaugeMiniColors?: GaugeMiniColors valueFormater?: (value: number) => string @@ -38,36 +31,6 @@ const getFormater = ( ? formater : (value: number) => formatStatValue(value, formater) -const isBarsDefinitionsArrayStyle = ( - barsDefinitions: GaugeMiniLayerConfig['barsDefinitions'] -): barsDefinitions is GaugeMiniBarsDefinitionsArr => { - return Array.isArray(barsDefinitions.groupByColumns) -} - -const getBarsDefinitions = ( - barsDefinitions: GaugeMiniLayerConfig['barsDefinitions'] -): TBarsDefinitions => { - if (!isBarsDefinitionsArrayStyle(barsDefinitions)) { - return barsDefinitions - } - - const {groupByColumns, bars} = barsDefinitions - - return { - groupByColumns: groupByColumns.reduce( - (obj, prop) => ((obj[prop] = true), obj), - {} as {[key: string]: true} - ), - bars: bars?.map(x => ({ - barDef: x.barDef.reduce( - (obj, prop, i) => ((obj[prop] = groupByColumns[i]), obj), - {} as Required['bars'][number]['barDef'] - ), - label: x.label, - })), - } -} - const getAxesSteps = ( axesSteps: number | 'thresholds' | undefined | number[], colors: GaugeMiniColors @@ -140,11 +103,6 @@ const getColors = ( export const gaugeMiniNormalizeThemeMemoized = ( theme: Required ): Required => { - const barsDefinitions = useMemo( - () => getBarsDefinitions(theme.barsDefinitions), - [theme.barsDefinitions] - ) - const colors = useMemo(() => getColors(theme.gaugeMiniColors), [ theme.gaugeMiniColors, ]) @@ -156,7 +114,6 @@ export const gaugeMiniNormalizeThemeMemoized = ( return { ...theme, - barsDefinitions, colors, gaugeMiniColors: colors, valueFormater: getFormater(theme.valueFormater), @@ -165,5 +122,3 @@ export const gaugeMiniNormalizeThemeMemoized = ( axesFormater: getFormater(theme.axesFormater), } } - -export const getGaugeMiniBarsDefinitions = getBarsDefinitions diff --git a/stories/src/gaugeMini.stories.tsx b/stories/src/gaugeMini.stories.tsx index f44d6b46..fd34be73 100644 --- a/stories/src/gaugeMini.stories.tsx +++ b/stories/src/gaugeMini.stories.tsx @@ -120,9 +120,6 @@ const editableLayer = ( knobGroups.basics ), numberOfBars: number('number of bars', 1, {}, knobGroups.basics), - barsDefinitions: { - groupByColumns: {_field: true}, - }, valueHeight: number('valueHeight', theme.valueHeight, {}, knobGroups.sizing), gaugeHeight: number('gaugeHeight', theme.gaugeHeight, {}, knobGroups.sizing), @@ -215,6 +212,11 @@ const editableLayer = ( knobGroups.labels ), + labelBarsEnabled: boolean( + 'labelBarsEnabled', + theme.labelBarsEnabled, + knobGroups.labels + ), labelBarsFontSize: number( 'labelBarsFontSize', theme.labelBarsFontSize, From 0da624cb1a24a4461bc5340e9ccd5ccd693ee280 Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Tue, 17 Nov 2020 11:51:58 +0100 Subject: [PATCH 15/34] feat: gauge mini --- giraffe/src/components/GaugeMini.tsx | 395 +++++++++++++++++++++++++++ giraffe/src/index.ts | 3 + 2 files changed, 398 insertions(+) diff --git a/giraffe/src/components/GaugeMini.tsx b/giraffe/src/components/GaugeMini.tsx index 843d9cc5..3e87d026 100644 --- a/giraffe/src/components/GaugeMini.tsx +++ b/giraffe/src/components/GaugeMini.tsx @@ -2,6 +2,7 @@ import React, {FunctionComponent, useRef, useEffect, useState} from 'react' import {color as d3Color} from 'd3-color' import {scaleLinear} from 'd3-scale' +<<<<<<< HEAD // Types import {GaugeMiniColors, GaugeMiniLayerConfig} from '../types' @@ -20,6 +21,62 @@ interface Props { } const barCssClass = 'gauge-mini-bar' +======= +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 { + width: number + height: number + value: number | {_field: string; value: number}[] + theme: Required +} + +//#region colors + +export type Colors = { + min: Color + max: Color + secondary: string + thresholds: Color[] + // targets: Color[], +} + +export const getColors = (theme: Required): Colors => { + const {colorSecondary: secondary, gaugeColors: colorsAndTargets} = theme + + colorsAndTargets.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} +} + +//#endregion colors +>>>>>>> f03fd3e (feat: gauge mini) //#region svg helpers @@ -29,7 +86,10 @@ type TSvgTextRectProps = { /** * Helper component that returns rect when children changes. Usefull for calculating text box size. +<<<<<<< HEAD * !onRectChanged called only when children changes! +======= +>>>>>>> f03fd3e (feat: gauge mini) */ export const SvgTextRect: React.FC = props => { const {onRectChanged = () => {}} = props @@ -38,12 +98,19 @@ export const SvgTextRect: React.FC = props => { useEffect(() => { const rect = textRef.current?.getBBox() +<<<<<<< HEAD if (!rect) { return } onRectChanged(rect) }, [props.children, onRectChanged]) +======= + if (!rect) return + + onRectChanged(rect) + }, [props.children]) +>>>>>>> f03fd3e (feat: gauge mini) return ( <> @@ -52,10 +119,13 @@ export const SvgTextRect: React.FC = props => { ) } +<<<<<<< HEAD /** * Helper component for centering content. * !Doesn't react on content size changed. Recententering is done manualy by changing refreshToken! */ +======= +>>>>>>> f03fd3e (feat: gauge mini) const AutoCenterGroup: FunctionComponent<{ enabled?: boolean refreshToken?: number | string @@ -81,6 +151,7 @@ const AutoCenterGroup: FunctionComponent<{ | SVGGraphicsElement | undefined)?.getBoundingClientRect() +<<<<<<< HEAD if (!box || !boxParent) { return } @@ -91,6 +162,16 @@ const AutoCenterGroup: FunctionComponent<{ return ( +======= + if (!box || !boxParent) return + + setX((boxParent.width - box.width) / 2 - box.x) + setY((boxParent.height - box.height) / 2 - box.y) + }, [refreshToken]) + + return ( + +>>>>>>> f03fd3e (feat: gauge mini) {children} ) @@ -98,6 +179,7 @@ const AutoCenterGroup: FunctionComponent<{ //#endregion svg helpers +<<<<<<< HEAD //#region subcomponents //#region types @@ -151,10 +233,24 @@ type BarSegment = { const BarBackground: FunctionComponent = ({ theme, +======= +const barCssClass = 'gauge-mini-bar' + +const BarBackground: FunctionComponent<{ + theme: Required + colors: Colors + barWidth: number + getFrac: (x: number) => number + barCenter: number +}> = ({ + theme, + colors: {max, min, secondary, thresholds}, +>>>>>>> f03fd3e (feat: gauge mini) barWidth, getFrac, barCenter, }) => { +<<<<<<< HEAD const {gaugeHeight, mode, gaugeRounding, colors, colorSecondary} = theme const {max, min, thresholds = []} = colors @@ -184,6 +280,34 @@ const BarBackground: FunctionComponent = ({ } // todo: dont't render def linear gradient when is not used +======= + 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}` + +>>>>>>> f03fd3e (feat: gauge mini) return ( <> @@ -209,6 +333,7 @@ const BarBackground: FunctionComponent = ({ y={y} /> ) : ( +<<<<<<< HEAD segments .reverse() .map(({hex: col, end, start}, i) => ( @@ -222,11 +347,24 @@ const BarBackground: FunctionComponent = ({ y={y} /> )) +======= + colors.map(({col, end, start}) => ( + + )) +>>>>>>> f03fd3e (feat: gauge mini) )} ) } +<<<<<<< HEAD const BarValue: FunctionComponent = ({ colors, barValueWidth, @@ -247,14 +385,41 @@ const BarValue: FunctionComponent = ({ const colorValue = mode === 'bullet' ? colorSecondary +======= +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 colorModeGradient = colors.thresholds.length === 0 + + const colorValue = + mode === 'bullet' + ? colors.secondary +>>>>>>> f03fd3e (feat: gauge mini) : d3Color( (() => { if (colorModeGradient) { return scaleLinear() +<<<<<<< HEAD .range([min.hex, max.hex] as any) .domain([min.value, max.value])(value) as any } else { const sortedColors = [min, ...thresholds, max] +======= + .range([colors.min.hex, colors.max.hex] as any) + .domain([colors.min.value, colors.max.value])(value) as any + } else { + const sortedColors = [ + colors.min, + ...colors.thresholds, + colors.max, + ] +>>>>>>> f03fd3e (feat: gauge mini) let i = 0 while ( i < sortedColors.length && @@ -271,6 +436,14 @@ const BarValue: FunctionComponent = ({ ?.brighter(1) .hex() +<<<<<<< HEAD +======= + const y = barCenter - valueHeight / 2 + const x = Math.sign(valueFracFixed) === -1 ? barValueWidth : 0 + + const className = 'value-rect' + +>>>>>>> f03fd3e (feat: gauge mini) // todo: move styling out -> styling is now multiple times inserted return ( <> @@ -296,12 +469,60 @@ const BarValue: FunctionComponent = ({ ) } +<<<<<<< HEAD const Text: FunctionComponent = ({value, barValueWidth, theme}) => { +======= +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}) => { +>>>>>>> f03fd3e (feat: gauge mini) const { valueFontColorInside, valueFontColorOutside, textMode, valueFormater, +<<<<<<< HEAD valueFontSize: fontSize, valuePadding, } = theme @@ -323,14 +544,43 @@ const Text: FunctionComponent = ({value, barValueWidth, theme}) => { valuePadding ) : valuePadding +======= + valueFontSize, + } = theme + const textValue = valueFormater(value) + + 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 textColor = textInside ? valueFontColorInside : valueFontColorOutside + + const x = + textMode === 'follow' + ? Math.max(barValueWidth + (textInside ? -padding : padding), padding) + : padding +>>>>>>> f03fd3e (feat: gauge mini) return ( <> >>>>>> f03fd3e (feat: gauge mini) > {textValue} @@ -338,6 +588,7 @@ const Text: FunctionComponent = ({value, barValueWidth, theme}) => { ) } +<<<<<<< HEAD const Bar: FunctionComponent = ({ value, theme, @@ -385,21 +636,60 @@ const Axes: FunctionComponent = ({theme, barWidth, y, getFrac}) => { strokeWidth: 2, strokeLinecap: 'round', } +======= +const Axes: FunctionComponent<{ + theme: Required + barWidth: number + y: number + getFrac: (x: number) => number +}> = ({theme, barWidth, y, getFrac}) => { + const {axesSteps, axesFormater, axesFontColor, axesFontSize} = theme + + 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 axesValuesArray = Array.isArray(axesSteps) + ? axesSteps + : axesSteps === 'thresholds' + ? colors.thresholds.map(x => x.value) + : Number.isInteger(axesSteps) + ? range(axesSteps).map( + x => ((x + 1) * colorLen) / (axesSteps + 1) + colors.min.value + ) + : throwReturn( + `${JSON.stringify( + axesSteps + )} axesSteps must be number | "thresholds" | number[] | undefined.` + ) +>>>>>>> f03fd3e (feat: gauge mini) const points: { anchor: string value: number lineLength: number +<<<<<<< HEAD text: string posX: number }[] = axesSteps .map(value => ({ value, anchor: 'middle', +======= + }[] = axesValuesArray + .map(value => ({ + anchor: 'middle', + value, +>>>>>>> f03fd3e (feat: gauge mini) lineLength: 5, })) .concat([ { +<<<<<<< HEAD value: min.value, anchor: 'start', lineLength: 3, @@ -434,11 +724,53 @@ const Axes: FunctionComponent = ({theme, barWidth, y, getFrac}) => { ))} +======= + anchor: 'start', + lineLength: 3, + value: colors.min.value, + }, + { + anchor: 'end', + lineLength: 3, + value: colors.max.value, + }, + ]) + + return ( + <> + + + {points.map(({anchor, lineLength, value}, i) => { + const posX = getFrac(value) * barWidth + const text = axesFormater(value) + return ( + <> + + + + {text} + + + + ) + })} +>>>>>>> f03fd3e (feat: gauge mini) ) } +<<<<<<< HEAD //#endregion subcomponents export const GaugeMini: FunctionComponent = ({ @@ -490,17 +822,68 @@ export const GaugeMini: FunctionComponent = ({ axesSteps, axesFontSize, ]) +======= +export const GaugeMini: FunctionComponent = ({ + value, + theme, + width, + height, +}) => { + const { + gaugeHeight, + sidePaddings: gaugePaddingSides, + valueHeight, + barPaddings, + labelMain, + labelBars, + labelMainFontSize, + labelMainFontColor, + labelBarsFontColor, + labelBarsFontSize, + } = theme + const [barLabelsWidth] = useState([]) + + const valueArray = Array.isArray(value) + ? value + : [{_field: '_default', 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 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]) +>>>>>>> f03fd3e (feat: gauge mini) /** return value as fraction 0->min 1->max */ const getFrac = (val: number): number => (val - colors.min.value) / colorLen return ( +<<<<<<< HEAD +======= + + +>>>>>>> f03fd3e (feat: gauge mini) {labelMain && ( = ({ {labelMain} )} +<<<<<<< HEAD {Object.entries(values).map(([group, value], i) => { const y = 0 + i * (maxBarHeight + barPaddings) const textCenter = y + maxBarHeight / 2 const label = labelBarsEnabled ? group : '' +======= + {valueArray.map(({_field, value}, i) => { + const y = 0 + i * (maxBarHeight + barPaddings) + const label = labelBars?.find(({_field: f}) => f === _field)?.label + + const textCenter = y + maxBarHeight / 2 +>>>>>>> f03fd3e (feat: gauge mini) // todo: no rerender ? const onRectChanged = (r: DOMRect) => { @@ -538,6 +929,7 @@ export const GaugeMini: FunctionComponent = ({ ) })} = ({ y: allBarsHeight + barPaddings, getFrac, }} +======= + {...{barWidth, theme, value, y: allBarsHeight + barPaddings, getFrac}} +>>>>>>> f03fd3e (feat: gauge mini) /> diff --git a/giraffe/src/index.ts b/giraffe/src/index.ts index 3d6d9a99..ab072637 100644 --- a/giraffe/src/index.ts +++ b/giraffe/src/index.ts @@ -35,7 +35,10 @@ export { FluxDataType, Formatter, GaugeLayerConfig, +<<<<<<< HEAD GaugeTheme, +======= +>>>>>>> f03fd3e (feat: gauge mini) GaugeMiniLayerConfig, GetColumn, HistogramLayerConfig, From 7378c4da57f29025a40ee5d54c673044ab7d2af0 Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Tue, 12 Jan 2021 13:32:09 +0100 Subject: [PATCH 16/34] feat: candlestick initial commit --- .gitignore | 1 + giraffe/src/components/Candlestick.tsx | 152 ++++++++++++++++++++ giraffe/src/components/CandlestickLayer.tsx | 22 +++ giraffe/src/components/SizedPlot.tsx | 13 ++ giraffe/src/constants/candlestickStyles.ts | 32 +++++ giraffe/src/constants/index.ts | 2 + giraffe/src/index.ts | 1 + giraffe/src/transforms/candlestick.ts | 114 +++++++++++++++ giraffe/src/types/index.ts | 57 ++++++++ giraffe/src/utils/PlotEnv.ts | 14 +- giraffe/src/utils/array.ts | 15 ++ stories/src/candlestick.stories.tsx | 131 +++++++++++++++++ stories/src/data/ohlc.ts | 109 ++++++++++++++ 13 files changed, 662 insertions(+), 1 deletion(-) create mode 100644 giraffe/src/components/Candlestick.tsx create mode 100644 giraffe/src/components/CandlestickLayer.tsx create mode 100644 giraffe/src/constants/candlestickStyles.ts create mode 100644 giraffe/src/transforms/candlestick.ts create mode 100644 giraffe/src/utils/array.ts create mode 100644 stories/src/candlestick.stories.tsx create mode 100644 stories/src/data/ohlc.ts diff --git a/.gitignore b/.gitignore index 1889071e..022c630e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ dist .env .vscode coverage +debug.log \ No newline at end of file diff --git a/giraffe/src/components/Candlestick.tsx b/giraffe/src/components/Candlestick.tsx new file mode 100644 index 00000000..1d33008b --- /dev/null +++ b/giraffe/src/components/Candlestick.tsx @@ -0,0 +1,152 @@ +import React from 'react' +import {CANDLESTICK_THEME_DARK} from '../constants/candlestickStyles' +import {CandlestickLayerConfig, CandleStyle} from '../types' + +interface CandleValue { + open: number + close: number + high: number + low: number +} + +interface CandlestickProps { + theme: Partial + values: CandleValue[] + width: number + height: number +} + +interface CandleProps { + theme: CandlestickLayerConfig + value: CandleValue + /** width of candle */ + width: number + /** returns height position from value */ + heightPositionFormatter: (value: number) => number +} + +const Candle: React.FC = ({ + heightPositionFormatter, + theme, + value, + width, +}) => { + const {close, high, low, open}: CandleValue = { + close: heightPositionFormatter(value.close), + high: heightPositionFormatter(value.high), + low: heightPositionFormatter(value.low), + open: heightPositionFormatter(value.open), + } + + const isRaise = open >= close + + const { + bodyColor, + bodyFillOpacity, + bodyRounding, + bodyStrokeWidth, + shadowColor, + shadowStrokeWidth, + }: CandleStyle = theme[isRaise ? 'candleRaising' : 'candleDecreasing'] + + const height = Math.abs(open - close) + const y = Math.min(open, close) + + const centerY = width / 2 + + return ( + <> + + {value.high > Math.max(value.open, value.close) && ( + + )} + {value.low < Math.min(value.open, value.close) && ( + + )} + + ) +} + +export const Candlestick: React.FC = ({ + theme: _theme, + values, + width, + height, +}) => { + const theme: CandlestickLayerConfig = { + ...CANDLESTICK_THEME_DARK, + ..._theme, + } + + const { + candlePadding, + // candleDecreasing, candleRaising, mode + } = theme + + const candles = values.length + const candleWidthStep = width / candles + const candleWidth = candleWidthStep - candlePadding + // todo: style + const hegihtRange = height - 2 + // const maxHeight = minHeight + hegihtRange + + const allValues = values.map(x => [x.open, x.close, x.high, x.low]).flat() + const minValue = Math.min(...allValues) + const maxValue = Math.max(...allValues) + const valueRange = maxValue - minValue + + const heightPositionFormatter: CandleProps['heightPositionFormatter'] = ( + value: number + ) => { + const valueFrac = (value - minValue) / valueRange + return (1 - valueFrac) * hegihtRange + } + + return ( + <> + + {values.map((x, i) => ( + <> + + + + + ))} + + + ) +} diff --git a/giraffe/src/components/CandlestickLayer.tsx b/giraffe/src/components/CandlestickLayer.tsx new file mode 100644 index 00000000..a62e4f3d --- /dev/null +++ b/giraffe/src/components/CandlestickLayer.tsx @@ -0,0 +1,22 @@ +import * as React from 'react' +import {FunctionComponent} from 'react' +import { + CandlestickLayerConfig, + CandlestickLayerSpec, + LayerProps, +} from '../types' +import {Candlestick} from './Candlestick' + +export interface Props extends LayerProps { + config: CandlestickLayerConfig + spec: CandlestickLayerSpec +} + +//todo: only proxies props into Candlestick ? if true -> candlestick should be implemented here +export const CandlestickLayer: FunctionComponent = props => { + const {config, width, height, spec} = props + + return ( + + ) +} diff --git a/giraffe/src/components/SizedPlot.tsx b/giraffe/src/components/SizedPlot.tsx index 436bc83e..e8adafd1 100644 --- a/giraffe/src/components/SizedPlot.tsx +++ b/giraffe/src/components/SizedPlot.tsx @@ -18,6 +18,7 @@ import { SingleStatLayerConfig, SizedConfig, SpecTypes, + CandlestickLayerConfig, } from '../types' import {SingleStatLayer} from './SingleStatLayer' import {LineLayer} from './LineLayer' @@ -36,6 +37,7 @@ import {MosaicLayer} from './MosaicLayer' import {GeoLayerConfig} from '../types/geo' import GeoLayer from './GeoLayer' import {AnnotationLayer} from './AnnotationLayer' +import {CandlestickLayer} from './CandlestickLayer' interface Props { config: SizedConfig @@ -273,6 +275,17 @@ export const SizedPlot: FunctionComponent = ({ ) } + case SpecTypes.Candlestick: { + return ( + + ) + } + default: return null } diff --git a/giraffe/src/constants/candlestickStyles.ts b/giraffe/src/constants/candlestickStyles.ts new file mode 100644 index 00000000..4169ccf2 --- /dev/null +++ b/giraffe/src/constants/candlestickStyles.ts @@ -0,0 +1,32 @@ +import {CandlestickLayerConfig} from '../types' +import {InfluxColors} from './colorSchemes' + +export const CANDLESTICK_THEME_DARK: Required = { + type: 'candlestick', + mode: 'candles', + xColumnKey: '_time', + openColumnKey: 'open', + highColumnKey: 'high', + lowColumnKey: 'low', + closeColumnKey: 'close', + window: 'detect', + + candlePadding: 5, + + candleRaising: { + bodyColor: InfluxColors.Krypton, + bodyFillOpacity: 1, + bodyRounding: 4, + bodyStrokeWidth: 2, + shadowColor: InfluxColors.Krypton, + shadowStrokeWidth: 2, + }, + candleDecreasing: { + bodyColor: InfluxColors.Curacao, + bodyFillOpacity: 0, + bodyRounding: 0, + bodyStrokeWidth: 2, + shadowColor: InfluxColors.Curacao, + shadowStrokeWidth: 2, + }, +} diff --git a/giraffe/src/constants/index.ts b/giraffe/src/constants/index.ts index 0b9856f0..3bfd05a2 100644 --- a/giraffe/src/constants/index.ts +++ b/giraffe/src/constants/index.ts @@ -10,6 +10,7 @@ import { } from 'd3-shape' import {Config, LayerConfig, SymbolType} from '../types' +import {CANDLESTICK_THEME_DARK} from './candlestickStyles' import {NINETEEN_EIGHTY_FOUR as DEFAULT_COLOR_SCHEME} from './colorSchemes' // TODO: Make configurable @@ -100,6 +101,7 @@ export const LAYER_DEFAULTS: {[layerType: string]: Partial} = { strokeOpacity: 1, fillOpacity: 0.75, }, + candlestick: CANDLESTICK_THEME_DARK, } export const ALL_SYMBOL_TYPES: SymbolType[] = [ diff --git a/giraffe/src/index.ts b/giraffe/src/index.ts index ab072637..ad772d2b 100644 --- a/giraffe/src/index.ts +++ b/giraffe/src/index.ts @@ -28,6 +28,7 @@ export {DEFAULT_TABLE_COLORS} from './constants/tableGraph' export { AnnotationLayerConfig, BandLayerConfig, + CandlestickLayerConfig, ColumnData, ColumnType, Config, diff --git a/giraffe/src/transforms/candlestick.ts b/giraffe/src/transforms/candlestick.ts new file mode 100644 index 00000000..6939de6e --- /dev/null +++ b/giraffe/src/transforms/candlestick.ts @@ -0,0 +1,114 @@ +import {pairs} from 'd3-array' +import {CandlestickLayerConfig, CandlestickLayerSpec, Table} from '../types' +import {Sorting} from '../utils/array' + +// Window auto-detection will select n-th smallest distance based on collection size, +// to ensure skipping possible anomalies with smaller window size +const WINDOW_DETECT_NTH_SMALLEST_DIST_PERCENT = 5 + +const {ascending} = Sorting + +export const candlestickTransform = ( + inputTable: Table, + layerConfig: Required +): CandlestickLayerSpec => { + const { + xColumnKey, + openColumnKey, + highColumnKey, + lowColumnKey, + closeColumnKey, + } = layerConfig + + const xCol = inputTable.getColumn(xColumnKey, 'number') + + const openCol = inputTable.getColumn(openColumnKey, 'number') + const highCol = inputTable.getColumn(highColumnKey, 'number') + const lowCol = inputTable.getColumn(lowColumnKey, 'number') + const closeCol = inputTable.getColumn(closeColumnKey, 'number') + + let xMin = Infinity + let xMax = -Infinity + let yMin = Infinity + let yMax = -Infinity + + // distence between two values + const window = + typeof layerConfig.window === 'number' + ? layerConfig.window + : pairs([...xCol].sort(ascending)) + .map(([x, y]) => y - x) + .filter(x => x !== 0 && !Number.isNaN(x)) + .sort(ascending)[ + Math.floor( + (xCol.length * WINDOW_DETECT_NTH_SMALLEST_DIST_PERCENT) / 100 + ) + ] + + // key is index rastered by window (value/window) + // [x0, x1] range of candle for selecting open/close + const valuesObj: { + [key: number]: { + candle: CandlestickLayerSpec['values'][0] + xRange: [number, number] + } + } = {} + for (let i = 0; i < inputTable.length; i++) { + const x = xCol[i] + const xKey = Math.round(x / window) + + const open = openCol[i] + const high = highCol[i] + const low = lowCol[i] + const close = closeCol[i] + + xMin = Math.min(xMin, x) + xMax = Math.max(xMax, x) + yMin = Math.min(yMin, open, high, low, close) + yMax = Math.max(yMax, open, high, low, close) + + // todo: filtering NaN ? + const candle = {open, high, low, close} + if (valuesObj[xKey]) { + const {candle: candle2, xRange: x2Range} = valuesObj[xKey] + // merge candles + valuesObj[xKey] = { + candle: { + close: x2Range[1] >= x ? candle2.close : candle.close, + open: x2Range[0] < x ? candle2.open : candle.open, + high: Math.max(candle2.high, candle.high), + low: Math.min(candle2.low, candle.low), + }, + xRange: [Math.min(x, ...x2Range), Math.max(x, ...x2Range)], + } + } else { + valuesObj[xKey] = {candle, xRange: [x, x]} + } + } + + const values: CandlestickLayerSpec['values'] = [] + + const xKeyMin = Math.floor(xMin / window) + const xKeyMax = Math.ceil(xMax / window) + + for (let i = xKeyMin; i <= xKeyMax; i++) { + const val = valuesObj?.[i] + if (!val) { + values.push(undefined) + } else { + values.push(val.candle) + } + } + + // todo + const res = { + type: 'candlestick', + inputTable, + table: inputTable, + values, + calculatedWindow: window, + xDomain: [xMin, xMax], + yDomain: [yMin, yMax], + } as CandlestickLayerSpec + return res as any +} diff --git a/giraffe/src/types/index.ts b/giraffe/src/types/index.ts index 4142aa51..0525bdb4 100644 --- a/giraffe/src/types/index.ts +++ b/giraffe/src/types/index.ts @@ -175,6 +175,7 @@ export type FluxDataType = export enum LayerTypes { RawFluxDataTable = 'flux data table', + Candlestick = 'candlestick', Gauge = 'gauge', GaugeMini = 'gauge mini', Custom = 'custom', @@ -194,6 +195,7 @@ export type LayerConfig = | CustomLayerConfig | AnnotationLayerConfig | RawFluxDataTableLayerConfig + | CandlestickLayerConfig | GaugeLayerConfig | GaugeMiniLayerConfig | SingleStatLayerConfig @@ -247,6 +249,34 @@ export interface RawFluxDataTableLayerConfig { parseObjects?: boolean } +export interface CandleStyle { + shadowColor: string + shadowStrokeWidth: number + + bodyColor: string + bodyFillOpacity: number + bodyStrokeWidth: number + bodyRounding: number +} + +export interface CandlestickLayerConfig { + type: 'candlestick' + /** Defines which columns choose as unique bar indentificator. Also bar labels can be defined here. */ + mode?: 'candles' | 'fence' + + candlePadding: number + candleRaising: CandleStyle + candleDecreasing: CandleStyle + + xColumnKey?: string + openColumnKey?: string + closeColumnKey?: string + lowColumnKey?: string + highColumnKey?: string + + window?: number | 'detect' +} + export interface GaugeLayerConfig { type: 'gauge' // do not refactor or restrict to LayerTypes.Gauge prefix?: string @@ -566,6 +596,7 @@ export type LayerSpec = | AnnotationLayerSpec | LineLayerSpec | BandLayerSpec + | CandlestickLayerSpec | ScatterLayerSpec | RectLayerSpec | MosaicLayerSpec @@ -574,6 +605,7 @@ export enum SpecTypes { Annotation = 'annotation', Line = 'line', Band = 'band', + Candlestick = 'candlestick', Scatter = 'scatter', Rect = 'rect', Mosaic = 'mosaic', @@ -651,6 +683,31 @@ export interface BandLayerSpec { } } +export type OHLCValue = { + open: number + high: number + low: number + close: number +} + +export type CandlestickLayerSpecValues = OHLCValue[] + +export interface CandlestickLayerSpec { + type: 'candlestick' // do not refactor or restrict to SpecTypes + inputTable: Table + calculatedWindow: number // distance between two values + values: CandlestickLayerSpecValues + xDomain: number[] + yDomain: number[] + + // todo: analyze why are these properties required (use them if convinient). + table: Table // same as inputTable (values used instead) + xColumnKey: string + yColumnKey: string + xColumnType: ColumnType + yColumnType: ColumnType +} + export interface ScatterLayerSpec { type: 'scatter' // do not refactor or restrict to SpecTypes.Scatter inputTable: Table diff --git a/giraffe/src/utils/PlotEnv.ts b/giraffe/src/utils/PlotEnv.ts index 30e9aaff..c8d32d83 100644 --- a/giraffe/src/utils/PlotEnv.ts +++ b/giraffe/src/utils/PlotEnv.ts @@ -21,6 +21,7 @@ import { } from '../constants' import { + CandlestickLayerConfig, ColumnType, Formatter, LayerSpec, @@ -30,6 +31,7 @@ import { Scale, SizedConfig, } from '../types' +import {candlestickTransform} from '../transforms/candlestick' const X_DOMAIN_AESTHETICS = ['x', 'xMin', 'xMax'] const Y_DOMAIN_AESTHETICS = ['y', 'yMin', 'yMax'] @@ -301,6 +303,15 @@ export class PlotEnv { ) } + case LayerTypes.Candlestick: { + const transform = this.fns.get( + memoizedTransformKey, + candlestickTransform + ) + + return transform(table, layerConfig as Required) + } + case LayerTypes.Scatter: { const transform = this.fns.get(memoizedTransformKey, scatterTransform) @@ -492,7 +503,8 @@ const applyLayerDefaults = ( layers.map(layer => LAYER_DEFAULTS[layer.type] ? {...LAYER_DEFAULTS[layer.type], ...layer} - : layer + : // todo: what is happening here ? (candlestick broke types) + (layer as any) ) const mergeConfigs = ( diff --git a/giraffe/src/utils/array.ts b/giraffe/src/utils/array.ts new file mode 100644 index 00000000..8cd31a1e --- /dev/null +++ b/giraffe/src/utils/array.ts @@ -0,0 +1,15 @@ +export const Sorting = { + ascending: (a: number, b: number) => a - b, + descending: (a: number, b: number) => b - a, +} + +export const pairs = (arr: T[]): [T, T][] => { + const pairs: [T, T][] = [] + + const maxI = arr.length - 1 + for (let i = 0; i < maxI; i++) { + pairs.push([arr[i], arr[i + 1]]) + } + + return pairs +} diff --git a/stories/src/candlestick.stories.tsx b/stories/src/candlestick.stories.tsx new file mode 100644 index 00000000..23a859e2 --- /dev/null +++ b/stories/src/candlestick.stories.tsx @@ -0,0 +1,131 @@ +import * as React from 'react' +import {storiesOf} from '@storybook/react' +import {withKnobs, number, select, text, boolean} from '@storybook/addon-knobs' +import {Config, fromFlux, InfluxColors, Plot} from '../../giraffe/src' + +import {PlotContainer} from './helpers' +import {CANDLESTICK_THEME_DARK} from '../../giraffe/src/constants/candlestickStyles' +import {CandlestickLayerConfig} from '../../giraffe/src/types' + +// todo: random csv +import {ohlcCsvSample1, ohlcCsvSample1MissingCandles} from './data/ohlc' + +type Theme = Required + +const color = (() => { + const colors = (() => { + const obj = {} + Object.keys(InfluxColors).forEach(x => (obj[x] = InfluxColors[x])) + return obj + })() + + // todo: type definitions + return (label: string, ...rest: any[]) => select(label, colors, ...rest) +})() + +const editableLayer = (theme: Theme): Theme => ({ + type: theme.type, + mode: select( + 'Mode', + { + candles: 'candles', + fence: 'fence', + }, + theme.mode + ), + candlePadding: number('padding candle', theme.candlePadding), + + candleRaising: { + bodyColor: color( + 'candleRaising - bodyColor', + theme.candleRaising.bodyColor + ), + bodyFillOpacity: number( + 'candleRaising - bodyFillOpacity', + theme.candleRaising.bodyFillOpacity + ), + bodyRounding: number( + 'candleRaising - bodyRounding', + theme.candleRaising.bodyRounding + ), + bodyStrokeWidth: number( + 'candleRaising - bodyStrokeWidth', + theme.candleRaising.bodyStrokeWidth + ), + shadowColor: color( + 'candleRaising - shadowColor', + theme.candleRaising.shadowColor + ), + shadowStrokeWidth: number( + 'candleRaising - shadowStrokeWidth', + theme.candleRaising.shadowStrokeWidth + ), + }, + + candleDecreasing: { + bodyColor: color( + 'candleDecreasing - bodyColor', + theme.candleDecreasing.bodyColor + ), + bodyFillOpacity: number( + 'candleDecreasing - bodyFillOpacity', + theme.candleDecreasing.bodyFillOpacity + ), + bodyRounding: number( + 'candleDecreasing - bodyRounding', + theme.candleDecreasing.bodyRounding + ), + bodyStrokeWidth: number( + 'candleDecreasing - bodyStrokeWidth', + theme.candleDecreasing.bodyStrokeWidth + ), + shadowColor: color( + 'candleDecreasing - shadowColor', + theme.candleDecreasing.shadowColor + ), + shadowStrokeWidth: number( + 'candleDecreasing - shadowStrokeWidth', + theme.candleDecreasing.shadowStrokeWidth + ), + }, + xColumnKey: text('xColumn', theme.xColumnKey), + openColumnKey: text('openColumnKey', theme.openColumnKey), + highColumnKey: text('highColumnKey', theme.highColumnKey), + lowColumnKey: text('lowColumnKey', theme.lowColumnKey), + closeColumnKey: text('closeColumnKey', theme.closeColumnKey), + window: (() => { + const detect = boolean('window detect', theme.window === 'detect') + if (detect) return 'detect' + return number('window fixed', typeof theme.window === 'number' || 1_000) + })(), +}) + +const createStory = (theme: Theme, csv: string) => () => { + const layer = editableLayer(theme) + const fromFluxTable = fromFlux(csv).table + + const config: Config = { + table: fromFluxTable, + layers: [layer], + } + + return ( + <> + + + + + ) +} + +storiesOf('Candlestick', module) + .addDecorator(withKnobs) + // todo: candlestick fences + .add( + 'Candlestick simple', + createStory(CANDLESTICK_THEME_DARK, ohlcCsvSample1) + ) + .add( + 'Candlestick missing candles', + createStory(CANDLESTICK_THEME_DARK, ohlcCsvSample1MissingCandles) + ) diff --git a/stories/src/data/ohlc.ts b/stories/src/data/ohlc.ts new file mode 100644 index 00000000..89526fbb --- /dev/null +++ b/stories/src/data/ohlc.ts @@ -0,0 +1,109 @@ +// todo: modify OHLC function to have selectable source(_time, _value) and target columns(open, ...) +// todo: find way to integrate OHLC function into flux +/* +OHLC data flux query example: + +OHLC = (tables=<-, time) => { + O = tables + |> aggregateWindow(every: time, fn: first, createEmpty: false) + |> yield(name: "first") + |> keep(columns: ["_time", "_value"]) + |> rename(columns: {_value: "open"}) + + H = tables + |> aggregateWindow(every: time, fn: max, createEmpty: false) + |> yield(name: "max") + |> keep(columns: ["_time", "_value"]) + |> rename(columns: {_value: "high"}) + + L = tables + |> aggregateWindow(every: time, fn: min, createEmpty: false) + |> yield(name: "min") + |> keep(columns: ["_time", "_value"]) + |> rename(columns: {_value: "low"}) + + C = tables + |> aggregateWindow(every: time, fn: last, createEmpty: false) + |> yield(name: "last") + |> keep(columns: ["_time", "_value"]) + |> rename(columns: {_value: "close"}) + + OH = join(tables: {O,H}, on:["_time"]) + LC = join(tables: {L,C}, on:["_time"]) + OHLC = join(tables: {OH, LC}, on:["_time"]) + + return OHLC +} + +from(bucket: "my-bucket") + |> range(start: v.timeRangeStart, stop: v.timeRangeStop) + |> filter(fn: (r) => r["_measurement"] == "cpu") + |> filter(fn: (r) => r["_field"] == "usage_system") + |> filter(fn: (r) => r["cpu"] == "cpu-total") + |> OHLC(time: 2m) +*/ + +import {OHLCValue} from '../../../giraffe/src/types' + +const dataSample1: (OHLCValue | undefined)[] = [ + {open: 50, high: 60, low: 45, close: 47}, + {open: 47, high: 55, low: 33, close: 35}, + {open: 32, high: 49, low: 28, close: 48}, + {open: 50, high: 63, low: 48, close: 56}, + {open: 56, high: 60, low: 53, close: 58}, + {open: 60, high: 67, low: 55, close: 63}, + {open: 70, high: 100, low: 62, close: 100}, + {open: 98, high: 99, low: 18, close: 30}, + {open: 34, high: 40, low: 32, close: 40}, + {open: 40, high: 50, low: 36, close: 48}, + {open: 55, high: 60, low: 43, close: 45}, + {open: 48, high: 52, low: 30, close: 35}, + {open: 37, high: 45, low: 35, close: 40}, + {open: 39, high: 47, low: 37, close: 45}, + {open: 43, high: 73, low: 28, close: 43}, + {open: 45, high: 78, low: 37, close: 63}, + {open: 74, high: 80, low: 40, close: 63}, + {open: 65, high: 68, low: 43, close: 50}, + {open: 53, high: 55, low: 43, close: 48}, + {open: 50, high: 53, low: 18, close: 38}, + {open: 40, high: 42, low: 37, close: 39}, + {open: 40, high: 41, low: 39, close: 40}, + {open: 38, high: 44, low: 36, close: 41}, + {open: 42, high: 47, low: 35, close: 40}, + {open: 38, high: 25, low: 75, close: 68}, + {open: 70, high: 83, low: 27, close: 30}, +] + +const timeWindow = 10_000 + +const dataToCSV = (data: (OHLCValue | undefined)[]) => { + const now = Date.now() + + const header = `\ +#group,false,false,false,false,false,false,false +#datatype,string,long,double,double,double,double,dateTime:RFC3339 +#default,_result,,,,,, +,result,table,open,high,low,close,_time +` + + return ( + header + + data + .map( + (x, i) => + x && + ',,0,' + + `${x.open},${x.high},${x.low},${x.close},${new Date( + now + timeWindow * i + ).toISOString()}` + ) + .filter(x => x) + .join('\n') + ) +} + +export const ohlcCsvSample1 = dataToCSV(dataSample1) + +export const ohlcCsvSample1MissingCandles = dataToCSV( + dataSample1.map(x => Math.random() > 0.2 && x) +) From 26d2af4596ec6215dfec333811f9f4b044c2d23d Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Fri, 15 Jan 2021 12:01:02 +0100 Subject: [PATCH 17/34] feat: ohlc more data --- stories/src/candlestick.stories.tsx | 10 ++++++- stories/src/data/ohlc.ts | 30 +++++++++++-------- .../src/data/ohlc_Binance_BTCUSDT_1h.csv.ts | 18 +++++++++++ 3 files changed, 45 insertions(+), 13 deletions(-) create mode 100644 stories/src/data/ohlc_Binance_BTCUSDT_1h.csv.ts diff --git a/stories/src/candlestick.stories.tsx b/stories/src/candlestick.stories.tsx index 23a859e2..c053a946 100644 --- a/stories/src/candlestick.stories.tsx +++ b/stories/src/candlestick.stories.tsx @@ -8,7 +8,11 @@ import {CANDLESTICK_THEME_DARK} from '../../giraffe/src/constants/candlestickSty import {CandlestickLayerConfig} from '../../giraffe/src/types' // todo: random csv -import {ohlcCsvSample1, ohlcCsvSample1MissingCandles} from './data/ohlc' +import { + ohlcCsvSample1, + ohlcCsvSample1MissingCandles, + ohlcCsvSampleBinance, +} from './data/ohlc' type Theme = Required @@ -129,3 +133,7 @@ storiesOf('Candlestick', module) 'Candlestick missing candles', createStory(CANDLESTICK_THEME_DARK, ohlcCsvSample1MissingCandles) ) + .add( + 'Candlestick binance BTC/USDT', + createStory(CANDLESTICK_THEME_DARK, ohlcCsvSampleBinance) + ) diff --git a/stories/src/data/ohlc.ts b/stories/src/data/ohlc.ts index 89526fbb..a2e057d9 100644 --- a/stories/src/data/ohlc.ts +++ b/stories/src/data/ohlc.ts @@ -44,6 +44,7 @@ from(bucket: "my-bucket") */ import {OHLCValue} from '../../../giraffe/src/types' +import {getBinanceOHLC} from './ohlc_Binance_BTCUSDT_1h.csv' const dataSample1: (OHLCValue | undefined)[] = [ {open: 50, high: 60, low: 45, close: 47}, @@ -76,27 +77,26 @@ const dataSample1: (OHLCValue | undefined)[] = [ const timeWindow = 10_000 -const dataToCSV = (data: (OHLCValue | undefined)[]) => { - const now = Date.now() - - const header = `\ +const header = `\ #group,false,false,false,false,false,false,false #datatype,string,long,double,double,double,double,dateTime:RFC3339 #default,_result,,,,,, ,result,table,open,high,low,close,_time ` +const csvRow = (candle: OHLCValue & {date: Date}) => { + return `,,0,${candle.open},${candle.high},${candle.low},${ + candle.close + },${candle.date.toISOString()}` +} + +const dataToCSV = (data: (OHLCValue | undefined)[]) => { + const now = Date.now() + return ( header + data - .map( - (x, i) => - x && - ',,0,' + - `${x.open},${x.high},${x.low},${x.close},${new Date( - now + timeWindow * i - ).toISOString()}` - ) + .map((x, i) => x && csvRow({...x, date: new Date(now + timeWindow * i)})) .filter(x => x) .join('\n') ) @@ -107,3 +107,9 @@ export const ohlcCsvSample1 = dataToCSV(dataSample1) export const ohlcCsvSample1MissingCandles = dataToCSV( dataSample1.map(x => Math.random() > 0.2 && x) ) + +export const ohlcCsvSampleBinance = + header + + getBinanceOHLC() + .map(csvRow) + .join('\n') diff --git a/stories/src/data/ohlc_Binance_BTCUSDT_1h.csv.ts b/stories/src/data/ohlc_Binance_BTCUSDT_1h.csv.ts new file mode 100644 index 00000000..fee5caf6 --- /dev/null +++ b/stories/src/data/ohlc_Binance_BTCUSDT_1h.csv.ts @@ -0,0 +1,18 @@ +// todo: check license for this data +// source: https://www.CryptoDataDownload.com +// unix,symbol,open,high,low,close,Volume BTC,Volume USDT,tradecount + +export const getBinanceOHLC = () => + csvData + .split('\n') + .map(x => x.split(',')) + .map(([unix, _date, _symbol, open, high, low, close]) => ({ + date: new Date(+unix), + open: +open, + high: +high, + low: +low, + close: +close, + })) + +const csvData = + '1610582400000,2021-01-14 00:00:00,BTC/USDT,37371.38000000,38172.22000000,37362.49000000,37965.47000000,1933.96427200,73200951.55824713,36661\n1610578800000,2021-01-13 23:00:00,BTC/USDT,37204.09000000,37584.73000000,37000.00000000,37371.38000000,4003.12820400,149324552.65971510,94280\n1610575200000,2021-01-13 22:00:00,BTC/USDT,37291.25000000,37850.00000000,36827.02000000,37203.87000000,6042.03307500,225941892.14352165,145104\n1610571600000,2021-01-13 21:00:00,BTC/USDT,36222.07000000,37427.00000000,36110.76000000,37299.49000000,7450.10466700,273979677.45851544,143713\n1610568000000,2021-01-13 20:00:00,BTC/USDT,36002.24000000,36450.01000000,35704.21000000,36222.07000000,5750.01446700,207658780.19389705,118490\n1610564400000,2021-01-13 19:00:00,BTC/USDT,35719.36000000,36188.00000000,35577.55000000,36002.25000000,5312.21893200,190903832.62323042,102457\n1610560800000,2021-01-13 18:00:00,BTC/USDT,34821.47000000,35901.00000000,34720.00000000,35719.36000000,6649.55774600,235681753.55135671,129664\n1610557200000,2021-01-13 17:00:00,BTC/USDT,34784.98000000,35000.00000000,34652.06000000,34821.48000000,2913.41367000,101556217.35535974,74641\n1610553600000,2021-01-13 16:00:00,BTC/USDT,34673.90000000,34930.00000000,34388.53000000,34783.14000000,2872.01120200,99506730.47508033,74086\n1610550000000,2021-01-13 15:00:00,BTC/USDT,34260.70000000,34850.00000000,34200.00000000,34673.90000000,3658.84742300,126337617.72096771,82393\n1610546400000,2021-01-13 14:00:00,BTC/USDT,34575.51000000,35100.00000000,34216.00000000,34260.70000000,5193.52343100,180511640.73806480,101193\n1610542800000,2021-01-13 13:00:00,BTC/USDT,34266.52000000,34787.24000000,34070.00000000,34575.50000000,3664.90875200,126544042.17738252,77740\n1610539200000,2021-01-13 12:00:00,BTC/USDT,34385.48000000,34512.00000000,34017.00000000,34266.52000000,3585.06836000,122779592.03370346,83868\n1610535600000,2021-01-13 11:00:00,BTC/USDT,34922.99000000,35250.00000000,34242.15000000,34385.48000000,5982.91999400,207938425.33048149,112096\n1610532000000,2021-01-13 10:00:00,BTC/USDT,34343.62000000,35066.64000000,34343.61000000,34923.00000000,4760.85293600,165510800.23160082,108046\n1610528400000,2021-01-13 09:00:00,BTC/USDT,34517.44000000,35107.47000000,34200.00000000,34343.62000000,4738.54255100,164370205.87977076,95053\n1610524800000,2021-01-13 08:00:00,BTC/USDT,34986.90000000,35063.59000000,34438.95000000,34519.19000000,5729.47622900,198712825.27925155,99962\n1610521200000,2021-01-13 07:00:00,BTC/USDT,33780.68000000,35180.67000000,33550.00000000,34986.90000000,6468.36582000,223311028.86666966,129033\n1610517600000,2021-01-13 06:00:00,BTC/USDT,33321.76000000,34212.87000000,33212.12000000,33780.68000000,4919.32255800,166187643.75213094,117208\n1610514000000,2021-01-13 05:00:00,BTC/USDT,33183.89000000,33521.96000000,32800.00000000,33321.75000000,3402.31753600,112789336.11601216,74376\n1610510400000,2021-01-13 04:00:00,BTC/USDT,33818.50000000,33910.00000000,33090.90000000,33183.88000000,3956.30306000,132358668.93296781,81338\n1610506800000,2021-01-13 03:00:00,BTC/USDT,33035.21000000,34375.51000000,32981.94000000,33818.50000000,6460.71206000,218830650.55841690,113365\n1610503200000,2021-01-13 02:00:00,BTC/USDT,33125.30000000,33551.55000000,32566.55000000,33035.21000000,5457.27537100,180313046.83527591,97536\n1610499600000,2021-01-13 01:00:00,BTC/USDT,32526.93000000,33450.00000000,32487.11000000,33124.06000000,6231.71754500,205116100.18111848,110099\n1610496000000,2021-01-13 00:00:00,BTC/USDT,34049.15000000,34049.15000000,32380.00000000,32526.50000000,9275.27934900,306711545.32513445,148548\n1610492400000,2021-01-12 23:00:00,BTC/USDT,33751.70000000,34138.84000000,33260.74000000,34051.24000000,6381.93199200,214601532.84028451,126383\n1610488800000,2021-01-12 22:00:00,BTC/USDT,34721.74000000,35051.02000000,33297.82000000,33743.18000000,3949.37391100,134851382.60556787,100050\n1610485200000,2021-01-12 21:00:00,BTC/USDT,34287.95000000,34877.28000000,33801.04000000,34719.03000000,4053.70405600,139157664.78739752,89121\n1610481600000,2021-01-12 20:00:00,BTC/USDT,34686.10000000,34702.22000000,33730.00000000,34287.95000000,5034.97631600,171715989.97505660,113254\n1610478000000,2021-01-12 19:00:00,BTC/USDT,35040.78000000,35040.79000000,34178.89000000,34685.11000000,3815.68001400,132021423.41342590,83351\n1610474400000,2021-01-12 18:00:00,BTC/USDT,35012.54000000,35550.00000000,34810.01000000,35040.78000000,4048.61314400,142422688.30968393,88596\n1610470800000,2021-01-12 17:00:00,BTC/USDT,34998.91000000,35289.67000000,34560.00000000,35012.53000000,5686.97075900,198481126.85605437,109149\n1610467200000,2021-01-12 16:00:00,BTC/USDT,33829.40000000,35000.00000000,33819.11000000,34998.90000000,5939.78549300,205218579.80101925,116044\n1610463600000,2021-01-12 15:00:00,BTC/USDT,33416.47000000,34285.71000000,33406.88000000,33829.40000000,6681.45394200,226445698.58107617,125863\n1610460000000,2021-01-12 14:00:00,BTC/USDT,33286.00000000,34064.25000000,32531.00000000,33426.47000000,11811.35594900,393694368.81381614,189971\n1610456400000,2021-01-12 13:00:00,BTC/USDT,35012.96000000,35399.70000000,33250.00000000,33291.06000000,10038.22509500,343775108.96552814,161263\n1610452800000,2021-01-12 12:00:00,BTC/USDT,35410.99000000,35688.82000000,34850.00000000,35014.75000000,4399.50968300,155060827.99845856,80069\n1610449200000,2021-01-12 11:00:00,BTC/USDT,35085.76000000,35841.01000000,35084.92000000,35410.99000000,4770.00225200,169178056.75008352,86550\n1610445600000,2021-01-12 10:00:00,BTC/USDT,35925.76000000,36150.71000000,34757.00000000,35085.77000000,6437.92750600,226777326.93409420,143080\n1610442000000,2021-01-12 09:00:00,BTC/USDT,35773.23000000,36150.67000000,35370.21000000,35925.76000000,3783.31674300,135595914.73912248,94262\n1610438400000,2021-01-12 08:00:00,BTC/USDT,36413.60000000,36499.98000000,35587.86000000,35773.24000000,5138.33768500,184973179.98963707,101155\n1610434800000,2021-01-12 07:00:00,BTC/USDT,35998.56000000,36628.00000000,35617.67000000,36413.60000000,6162.13428600,222671027.01391058,112864\n1610431200000,2021-01-12 06:00:00,BTC/USDT,35882.68000000,36348.00000000,35720.43000000,35993.93000000,6584.65752400,237381577.99770093,119516\n1610427600000,2021-01-12 05:00:00,BTC/USDT,35284.05000000,35889.00000000,34971.24000000,35882.68000000,5053.27595400,179047386.94075864,98637\n1610424000000,2021-01-12 04:00:00,BTC/USDT,34984.95000000,35326.42000000,34520.69000000,35287.67000000,4020.85751400,140466119.04782501,102515\n1610420400000,2021-01-12 03:00:00,BTC/USDT,34400.00000000,34998.94000000,34182.24000000,34984.96000000,3550.25392100,123057319.46903642,97055\n1610416800000,2021-01-12 02:00:00,BTC/USDT,34289.30000000,34623.87000000,33677.03000000,34399.99000000,4906.32950600,167606696.70177525,95087\n1610413200000,2021-01-12 01:00:00,BTC/USDT,34929.99000000,35457.00000000,33880.00000000,34289.30000000,5087.44237000,176294252.52681766,109515\n1610409600000,2021-01-12 00:00:00,BTC/USDT,35410.37000000,35547.19000000,34287.34000000,34929.98000000,6612.03638100,230807172.42598563,130795\n1610406000000,2021-01-11 23:00:00,BTC/USDT,34361.16000000,35638.55000000,34361.16000000,35404.47000000,9188.29941400,322597532.80513758,150098\n1610402400000,2021-01-11 22:00:00,BTC/USDT,33962.22000000,34892.74000000,33650.00000000,34359.07000000,5403.70363100,185939943.89889369,134198\n1610398800000,2021-01-11 21:00:00,BTC/USDT,33410.75000000,34466.00000000,33076.05000000,33968.09000000,9804.01167300,332651007.11123541,182255\n1610395200000,2021-01-11 20:00:00,BTC/USDT,32254.63000000,33465.48000000,31408.07000000,33409.80000000,8540.91364700,275831078.34556703,164613\n1610391600000,2021-01-11 19:00:00,BTC/USDT,32562.10000000,32737.40000000,31215.00000000,32254.62000000,7289.18436100,233109852.78429258,153369\n1610388000000,2021-01-11 18:00:00,BTC/USDT,32848.96000000,33300.00000000,32432.32000000,32562.10000000,7184.91161600,236361243.57798098,136615\n1610384400000,2021-01-11 17:00:00,BTC/USDT,31771.77000000,33585.17000000,30701.80000000,32839.87000000,16685.08091700,540672713.18962742,267122\n1610380800000,2021-01-11 16:00:00,BTC/USDT,31582.82000000,32250.00000000,30420.00000000,31777.19000000,20017.08514800,622713357.15522214,315778\n1610377200000,2021-01-11 15:00:00,BTC/USDT,33291.56000000,33679.74000000,31415.90000000,31580.00000000,11202.31877200,363657258.00175475,193457\n1610373600000,2021-01-11 14:00:00,BTC/USDT,32808.92000000,33770.11000000,31115.00000000,33299.12000000,23220.78262900,754881732.25507185,328807\n1610370000000,2021-01-11 13:00:00,BTC/USDT,34273.49000000,34450.00000000,32700.00000000,32813.71000000,12794.35086900,428304476.95230326,203687\n1610366400000,2021-01-11 12:00:00,BTC/USDT,34198.55000000,35110.00000000,33308.01000000,34275.64000000,9904.29308300,339316781.85416521,182659\n1610362800000,2021-01-11 11:00:00,BTC/USDT,35368.81000000,35471.00000000,34020.00000000,34198.56000000,7167.26663200,249203003.73990949,139115\n1610359200000,2021-01-11 10:00:00,BTC/USDT,35765.98000000,36360.00000000,35238.01000000,35367.60000000,6245.55977000,223540104.68064499,130986\n1610355600000,2021-01-11 09:00:00,BTC/USDT,35430.00000000,35788.06000000,34800.00000000,35765.98000000,5625.53336200,198581030.52970092,118919\n1610352000000,2021-01-11 08:00:00,BTC/USDT,34372.25000000,35919.86000000,34249.00000000,35430.00000000,6432.36153600,228082153.68068114,128300\n1610348400000,2021-01-11 07:00:00,BTC/USDT,32843.26000000,34739.98000000,32538.00000000,34375.34000000,12674.17128100,427189376.16139185,229890\n1610344800000,2021-01-11 06:00:00,BTC/USDT,34904.73000000,34904.73000000,32550.00000000,32872.63000000,15763.14870700,531586560.33268066,250357\n1610341200000,2021-01-11 05:00:00,BTC/USDT,35542.92000000,35924.19000000,34500.18000000,34904.73000000,7098.49713400,250190079.36343610,144659\n1610337600000,2021-01-11 04:00:00,BTC/USDT,35438.23000000,35999.95000000,34300.00000000,35544.00000000,9555.24165800,336898762.01782967,189085\n1610334000000,2021-01-11 03:00:00,BTC/USDT,36363.93000000,36697.97000000,33600.00000000,35438.23000000,18753.36008200,656910210.24701858,309583\n1610330400000,2021-01-11 02:00:00,BTC/USDT,37237.67000000,37411.00000000,36123.45000000,36363.94000000,6796.57306400,249081305.98087372,139390\n1610326800000,2021-01-11 01:00:00,BTC/USDT,37192.10000000,37824.00000000,36700.09000000,37237.66000000,4215.99647000,157446432.61687462,94031\n1610323200000,2021-01-11 00:00:00,BTC/USDT,38150.02000000,38264.74000000,36501.00000000,37192.10000000,7568.89448700,282133773.45771687,144478\n1610319600000,2021-01-10 23:00:00,BTC/USDT,38429.85000000,38692.79000000,37935.92000000,38150.02000000,3794.32362000,145426560.60992947,81328\n1610316000000,2021-01-10 22:00:00,BTC/USDT,38052.58000000,39000.00000000,37852.09000000,38428.23000000,4739.66852100,183000874.59129449,117221\n1610312400000,2021-01-10 21:00:00,BTC/USDT,37727.29000000,38288.17000000,37525.22000000,38055.30000000,7661.05957300,290757651.27777476,151403\n1610308800000,2021-01-10 20:00:00,BTC/USDT,37472.40000000,37793.50000000,35111.11000000,37718.88000000,18938.32980400,696190674.07068193,326195\n1610305200000,2021-01-10 19:00:00,BTC/USDT,38106.11000000,38536.65000000,37111.00000000,37456.77000000,4232.50255500,160518393.07414801,108389\n1610301600000,2021-01-10 18:00:00,BTC/USDT,38571.95000000,38778.00000000,37675.30000000,38109.68000000,6090.67144700,232738420.47658579,130643\n1610298000000,2021-01-10 17:00:00,BTC/USDT,39232.76000000,39388.20000000,37200.00000000,38571.86000000,12879.62069600,492818910.76509772,227163\n1610294400000,2021-01-10 16:00:00,BTC/USDT,39671.82000000,39757.55000000,39186.16000000,39234.29000000,2904.11345600,114524017.31947872,74323\n1610290800000,2021-01-10 15:00:00,BTC/USDT,39431.58000000,39934.76000000,39062.91000000,39671.81000000,3219.50221900,127407920.98714545,80993\n1610287200000,2021-01-10 14:00:00,BTC/USDT,39800.89000000,39847.26000000,39247.54000000,39431.59000000,3199.91402300,126603785.26782930,77289\n1610283600000,2021-01-10 13:00:00,BTC/USDT,39538.56000000,40126.00000000,39426.59000000,39800.90000000,3799.31660300,151386269.92626396,83841\n1610280000000,2021-01-10 12:00:00,BTC/USDT,39175.09000000,39777.77000000,38450.00000000,39538.57000000,6929.93683900,272139723.43016981,139744\n1610276400000,2021-01-10 11:00:00,BTC/USDT,40429.19000000,40429.56000000,38888.00000000,39181.76000000,8619.31148000,340819805.54859064,168941\n1610272800000,2021-01-10 10:00:00,BTC/USDT,40768.30000000,40850.00000000,40000.00000000,40429.20000000,5145.08674100,208070825.51832275,124172\n1610269200000,2021-01-10 09:00:00,BTC/USDT,40798.30000000,41060.00000000,40732.00000000,40768.48000000,2642.95874700,108097086.74610234,71415\n1610265600000,2021-01-10 08:00:00,BTC/USDT,40978.57000000,41033.00000000,40555.00000000,40798.29000000,2415.93133500,98459402.84023767,82913\n1610262000000,2021-01-10 07:00:00,BTC/USDT,40904.50000000,41066.62000000,40660.88000000,40978.57000000,2143.46227300,87593443.12207167,63012\n1610258400000,2021-01-10 06:00:00,BTC/USDT,40451.74000000,41073.26000000,40350.00000000,40904.50000000,2194.91295100,89548994.99182704,68652\n1610254800000,2021-01-10 05:00:00,BTC/USDT,40342.46000000,40665.23000000,40260.00000000,40451.84000000,2044.47910700,82813349.32221709,65702\n1610251200000,2021-01-10 04:00:00,BTC/USDT,40320.67000000,40602.29000000,40050.00000000,40342.46000000,2786.99680900,112516572.97811659,70179\n1610247600000,2021-01-10 03:00:00,BTC/USDT,40942.75000000,40991.20000000,40250.00000000,40316.64000000,2477.28851200,100527756.90546729,68596\n1610244000000,2021-01-10 02:00:00,BTC/USDT,41141.55000000,41145.21000000,40814.04000000,40945.28000000,2353.85274600,96509933.21078183,56820\n1610240400000,2021-01-10 01:00:00,BTC/USDT,40581.48000000,41350.00000000,40450.05000000,41141.56000000,4299.15250200,176409335.99983746,113141\n1610236800000,2021-01-10 00:00:00,BTC/USDT,40088.22000000,40736.76000000,40087.15000000,40581.48000000,2697.15194400,109155020.52709842,75975\n1610233200000,2021-01-09 23:00:00,BTC/USDT,40283.73000000,40415.01000000,39928.91000000,40088.22000000,3255.53576100,130848312.14581371,77661\n1610229600000,2021-01-09 22:00:00,BTC/USDT,40718.81000000,40743.47000000,40120.00000000,40281.09000000,1973.25228700,79649919.17291718,63216\n1610226000000,2021-01-09 21:00:00,BTC/USDT,40610.35000000,40755.00000000,40483.17000000,40718.80000000,2433.80943000,98937085.11211261,74533\n1610222400000,2021-01-09 20:00:00,BTC/USDT,40665.91000000,40781.69000000,40050.00000000,40606.22000000,3330.65176500,134668657.46555306,86204\n1610218800000,2021-01-09 19:00:00,BTC/USDT,40650.00000000,40880.00000000,40469.40000000,40665.91000000,1864.14249300,75910582.21499661,55768\n1610215200000,2021-01-09 18:00:00,BTC/USDT,40421.40000000,40800.00000000,40417.36000000,40650.01000000,2109.16468000,85758517.67667532,52834\n1610211600000,2021-01-09 17:00:00,BTC/USDT,39995.09000000,40586.87000000,39985.45000000,40421.40000000,2205.65303100,89095373.96045112,61685\n1610208000000,2021-01-09 16:00:00,BTC/USDT,40525.16000000,40649.96000000,39869.00000000,39995.10000000,3942.09517300,158583517.38012921,101902\n1610204400000,2021-01-09 15:00:00,BTC/USDT,40819.01000000,40953.00000000,40200.00000000,40525.15000000,3364.43316400,136453850.42727688,84201\n1610200800000,2021-01-09 14:00:00,BTC/USDT,41051.66000000,41380.00000000,40700.00000000,40819.01000000,3867.78119200,158901525.79577861,88998\n1610197200000,2021-01-09 13:00:00,BTC/USDT,40874.99000000,41222.00000000,40679.10000000,41051.65000000,3818.65745000,156480184.02975569,101156\n1610193600000,2021-01-09 12:00:00,BTC/USDT,40565.77000000,40912.48000000,40410.01000000,40874.99000000,2732.20120400,111129222.70939649,70238\n1610190000000,2021-01-09 11:00:00,BTC/USDT,40376.03000000,41237.44000000,40256.73000000,40565.77000000,4682.99124700,191114775.55476880,116488\n1610186400000,2021-01-09 10:00:00,BTC/USDT,40348.36000000,40800.00000000,40105.51000000,40376.03000000,3130.35655800,126596569.76688394,79151\n1610182800000,2021-01-09 09:00:00,BTC/USDT,40167.54000000,40790.00000000,40164.03000000,40348.35000000,3653.41544700,147912608.83417118,104095\n1610179200000,2021-01-09 08:00:00,BTC/USDT,39639.21000000,40254.42000000,39290.86000000,40172.14000000,3081.99328900,122880599.47300186,90034\n1610175600000,2021-01-09 07:00:00,BTC/USDT,38978.46000000,39692.00000000,38720.00000000,39638.61000000,3109.88920900,122140688.02104703,82441\n1610172000000,2021-01-09 06:00:00,BTC/USDT,39293.41000000,39627.11000000,38888.00000000,38984.86000000,4352.60593700,170621480.12389127,110337\n1610168400000,2021-01-09 05:00:00,BTC/USDT,40206.43000000,40206.43000000,39154.48000000,39295.37000000,3025.04162500,120011440.79497736,89565\n1610164800000,2021-01-09 04:00:00,BTC/USDT,40209.91000000,40490.00000000,39805.03000000,40206.44000000,1976.48437300,79389762.67386835,61321\n1610161200000,2021-01-09 03:00:00,BTC/USDT,39973.00000000,40376.69000000,39709.61000000,40211.59000000,1917.78678000,76876333.83329589,63739\n1610157600000,2021-01-09 02:00:00,BTC/USDT,40196.51000000,40561.97000000,39500.00000000,39973.00000000,3032.04654100,121509932.94021643,82121\n1610154000000,2021-01-09 01:00:00,BTC/USDT,40286.09000000,40321.92000000,39710.00000000,40198.56000000,4543.73605600,181652860.51564185,96651\n1610150400000,2021-01-09 00:00:00,BTC/USDT,40586.96000000,40976.13000000,39980.27000000,40283.56000000,4382.25498300,177655228.53915624,103817\n1610146800000,2021-01-08 23:00:00,BTC/USDT,40672.02000000,40937.77000000,40465.76000000,40582.81000000,2685.59980500,109371595.53094099,84417\n1610143200000,2021-01-08 22:00:00,BTC/USDT,40075.82000000,40785.00000000,39740.95000000,40674.45000000,3008.04856300,121290642.62703400,94417\n1610139600000,2021-01-08 21:00:00,BTC/USDT,39062.88000000,40116.37000000,38652.00000000,40075.82000000,6171.36420800,243712568.15469437,130312\n1610136000000,2021-01-08 20:00:00,BTC/USDT,40365.09000000,40500.00000000,38888.00000000,39059.15000000,9598.07354700,379934389.42082304,202984\n1610132400000,2021-01-08 19:00:00,BTC/USDT,39920.67000000,40700.00000000,39900.35000000,40365.09000000,3881.07246400,156704308.94925451,89817\n1610128800000,2021-01-08 18:00:00,BTC/USDT,41009.08000000,41153.00000000,39800.00000000,39920.39000000,6673.74778200,269890839.41926600,137300\n1610125200000,2021-01-08 17:00:00,BTC/USDT,41303.41000000,41556.19000000,40888.88000000,41009.07000000,3675.20465500,151525080.93204277,89742\n1610121600000,2021-01-08 16:00:00,BTC/USDT,40829.07000000,41600.00000000,40359.67000000,41303.42000000,6293.05875900,258570569.68834122,125823\n1610118000000,2021-01-08 15:00:00,BTC/USDT,41454.13000000,41950.00000000,39777.00000000,40829.08000000,12863.95161700,525866974.42447445,235224\n1610114400000,2021-01-08 14:00:00,BTC/USDT,41313.24000000,41564.72000000,40970.22000000,41454.13000000,3918.18065100,161743369.58990013,85972\n1610110800000,2021-01-08 13:00:00,BTC/USDT,41318.03000000,41641.83000000,40700.00000000,41311.18000000,5331.45266600,219726996.61805144,102736\n1610107200000,2021-01-08 12:00:00,BTC/USDT,41286.67000000,41785.90000000,40857.37000000,41316.91000000,7590.27892700,314289402.99398151,161233\n1610103600000,2021-01-08 11:00:00,BTC/USDT,40722.71000000,41500.45000000,40345.39000000,41286.67000000,9286.54999900,381339340.10874931,173240\n1610100000000,2021-01-08 10:00:00,BTC/USDT,39358.99000000,41057.79000000,39220.00000000,40721.45000000,10298.22132600,413988645.03214855,192411\n1610096400000,2021-01-08 09:00:00,BTC/USDT,38648.96000000,39374.22000000,38222.00000000,39358.34000000,3995.80383300,155055524.72934390,92046\n1610092800000,2021-01-08 08:00:00,BTC/USDT,38929.56000000,38985.06000000,38414.70000000,38648.95000000,3277.73807000,126726366.48661977,77560\n1610089200000,2021-01-08 07:00:00,BTC/USDT,38538.99000000,39143.80000000,38280.16000000,38930.55000000,4177.46546000,161995711.17798650,91584\n1610085600000,2021-01-08 06:00:00,BTC/USDT,37980.53000000,38685.13000000,37563.92000000,38537.94000000,3989.76181500,152681589.95116749,85138\n1610082000000,2021-01-08 05:00:00,BTC/USDT,38668.92000000,38700.00000000,37895.00000000,37975.25000000,4384.15874300,168179267.74228120,78020\n1610078400000,2021-01-08 04:00:00,BTC/USDT,38347.11000000,38949.97000000,38155.61000000,38668.92000000,5126.66640200,198141885.51559744,92378\n1610074800000,2021-01-08 03:00:00,BTC/USDT,37181.74000000,38460.13000000,37068.40000000,38347.10000000,5036.74691800,191531646.49060035,107958\n1610071200000,2021-01-08 02:00:00,BTC/USDT,38681.38000000,38692.83000000,36500.00000000,37181.74000000,9364.24713500,350671968.12783984,184588\n1610067600000,2021-01-08 01:00:00,BTC/USDT,38793.24000000,39000.00000000,37800.00000000,38681.37000000,5775.38926500,222320078.64383120,118187\n1610064000000,2021-01-08 00:00:00,BTC/USDT,39432.48000000,39699.87000000,38793.23000000,38793.23000000,3387.17488900,133184446.37639539,92545\n1610060400000,2021-01-07 23:00:00,BTC/USDT,39666.00000000,39680.54000000,38541.38000000,39432.28000000,5866.66546500,229001742.14610960,129513\n1610056800000,2021-01-07 22:00:00,BTC/USDT,39687.11000000,39969.00000000,39150.00000000,39665.92000000,2855.62721000,113198084.48035602,94586\n1610053200000,2021-01-07 21:00:00,BTC/USDT,39111.34000000,39882.07000000,38908.11000000,39687.11000000,4533.61478900,178905952.02809147,98626\n1610049600000,2021-01-07 20:00:00,BTC/USDT,39061.99000000,39199.33000000,38352.88000000,39109.38000000,4364.13361200,169359110.49023060,97943\n1610046000000,2021-01-07 19:00:00,BTC/USDT,38907.58000000,39236.12000000,37493.52000000,39062.00000000,9539.54129200,367676058.94426181,178227\n1610042400000,2021-01-07 18:00:00,BTC/USDT,39732.92000000,40365.00000000,36500.00000000,38948.48000000,16920.67287100,656141657.34246253,352801\n1610038800000,2021-01-07 17:00:00,BTC/USDT,39120.01000000,39775.00000000,39101.36000000,39732.92000000,4853.72990500,191791634.74751861,118869\n1610035200000,2021-01-07 16:00:00,BTC/USDT,38970.41000000,39500.00000000,38921.49000000,39120.01000000,8054.61611400,315624646.46786094,152513\n1610031600000,2021-01-07 15:00:00,BTC/USDT,38450.51000000,38970.42000000,38062.75000000,38970.42000000,5401.36264100,208049947.52961345,108411\n1610028000000,2021-01-07 14:00:00,BTC/USDT,38161.60000000,38644.76000000,38079.02000000,38453.97000000,5516.63370100,211915217.15929497,127201\n1610024400000,2021-01-07 13:00:00,BTC/USDT,37941.37000000,38429.00000000,37900.00000000,38161.60000000,5364.34606600,205106242.97582551,109894\n1610020800000,2021-01-07 12:00:00,BTC/USDT,37825.64000000,38200.00000000,37518.00000000,37941.36000000,6363.35906900,241319458.86435367,126651\n1610017200000,2021-01-07 11:00:00,BTC/USDT,37045.45000000,37913.72000000,37038.97000000,37822.09000000,4315.49039700,161597115.72571475,93785\n1610013600000,2021-01-07 10:00:00,BTC/USDT,37492.70000000,37500.00000000,36920.00000000,37045.45000000,3236.82271800,120314344.26502907,73246\n1610010000000,2021-01-07 09:00:00,BTC/USDT,37217.55000000,37500.99000000,36905.00000000,37492.69000000,4407.04559400,163843785.85139342,73931\n1610006400000,2021-01-07 08:00:00,BTC/USDT,36863.04000000,37350.00000000,36300.00000000,37217.55000000,5254.61020900,193767393.70327787,105341\n1610002800000,2021-01-07 07:00:00,BTC/USDT,37130.85000000,37338.72000000,36720.00000000,36859.85000000,3782.27688600,140083390.80394544,91008\n1609999200000,2021-01-07 06:00:00,BTC/USDT,37347.20000000,37439.75000000,36710.00000000,37130.84000000,4095.35114000,151906540.45678327,102410\n1609995600000,2021-01-07 05:00:00,BTC/USDT,37450.95000000,37700.80000000,37100.77000000,37347.20000000,4337.60029100,162305184.42616053,88626\n1609992000000,2021-01-07 04:00:00,BTC/USDT,37452.62000000,37550.00000000,37056.12000000,37450.95000000,4099.95384100,153032050.67556983,93723\n1609988400000,2021-01-07 03:00:00,BTC/USDT,36962.37000000,37699.00000000,36926.53000000,37454.48000000,4747.64690900,177380063.69681458,102236\n1609984800000,2021-01-07 02:00:00,BTC/USDT,36871.47000000,37087.01000000,36456.00000000,36962.37000000,4112.98100900,151378625.95657551,75846\n1609981200000,2021-01-07 01:00:00,BTC/USDT,37150.66000000,37227.61000000,36615.83000000,36871.47000000,4542.03973000,167744153.75912638,92343\n1609977600000,2021-01-07 00:00:00,BTC/USDT,36769.36000000,37287.63000000,36422.71000000,37150.66000000,6259.57897800,231127540.88700173,126956\n1609974000000,2021-01-06 23:00:00,BTC/USDT,35989.64000000,36939.21000000,35949.12000000,36769.36000000,5501.43949000,200767262.24120768,99093\n1609970400000,2021-01-06 22:00:00,BTC/USDT,35869.97000000,36379.99000000,35700.92000000,35989.63000000,2717.84739300,98138204.77164020,84495\n1609966800000,2021-01-06 21:00:00,BTC/USDT,36015.16000000,36470.00000000,35650.00000000,35874.16000000,7481.32434800,269663315.99895945,143699\n1609963200000,2021-01-06 20:00:00,BTC/USDT,34818.95000000,36250.00000000,34280.00000000,36015.88000000,10922.41272400,384838155.99124447,211465\n1609959600000,2021-01-06 19:00:00,BTC/USDT,35215.75000000,35695.00000000,34652.60000000,34816.72000000,5619.20776800,198986931.55874261,127198\n1609956000000,2021-01-06 18:00:00,BTC/USDT,34898.52000000,35245.00000000,34894.22000000,35215.75000000,3704.00848100,130045690.18758426,79341\n1609952400000,2021-01-06 17:00:00,BTC/USDT,34615.98000000,35057.81000000,34562.00000000,34898.52000000,3410.29344000,118876311.09590797,78917\n1609948800000,2021-01-06 16:00:00,BTC/USDT,34566.18000000,35076.42000000,34372.92000000,34612.41000000,6142.98629100,213765663.67000848,119999\n1609945200000,2021-01-06 15:00:00,BTC/USDT,34213.58000000,34790.00000000,34150.00000000,34566.19000000,4050.15684800,139949954.84675728,84656\n1609941600000,2021-01-06 14:00:00,BTC/USDT,34549.99000000,34593.50000000,34052.42000000,34213.46000000,5728.36809500,196539271.47079065,118986\n1609938000000,2021-01-06 13:00:00,BTC/USDT,34929.87000000,35050.00000000,34300.00000000,34550.00000000,4296.27233100,148666291.75269622,101607\n1609934400000,2021-01-06 12:00:00,BTC/USDT,34918.54000000,35088.00000000,34415.61000000,34929.87000000,4569.51466500,158781299.83276104,111089\n1609930800000,2021-01-06 11:00:00,BTC/USDT,34417.37000000,35200.00000000,34342.87000000,34918.53000000,6362.80414200,221652511.71656262,135283\n1609927200000,2021-01-06 10:00:00,BTC/USDT,33919.50000000,34550.00000000,33834.61000000,34417.36000000,3851.57425900,132191268.15797724,81692\n1609923600000,2021-01-06 09:00:00,BTC/USDT,34596.08000000,34607.69000000,33357.90000000,33919.50000000,5753.91297000,195480943.58159614,116923\n1609920000000,2021-01-06 08:00:00,BTC/USDT,34666.80000000,34950.77000000,34015.00000000,34594.28000000,5094.75919600,176018487.58908296,104746\n1609916400000,2021-01-06 07:00:00,BTC/USDT,35125.49000000,35248.63000000,34350.00000000,34676.40000000,3898.73728400,135685914.15764905,90102\n1609912800000,2021-01-06 06:00:00,BTC/USDT,34977.27000000,35229.10000000,34782.44000000,35125.51000000,3923.02640300,137467483.49545430,82118\n1609909200000,2021-01-06 05:00:00,BTC/USDT,35326.82000000,35590.00000000,34510.00000000,34978.91000000,5595.78367600,196115940.87795285,106461\n1609905600000,2021-01-06 04:00:00,BTC/USDT,34116.76000000,35766.58000000,34109.83000000,35325.91000000,12735.41762700,444922768.19202013,211886\n1609902000000,2021-01-06 03:00:00,BTC/USDT,33873.30000000,34170.00000000,33652.00000000,34117.59000000,3183.08804500,107858643.06601506,79479\n1609898400000,2021-01-06 02:00:00,BTC/USDT,33811.28000000,34135.84000000,33768.32000000,33873.30000000,3252.25455600,110509015.75623268,60510\n1609894800000,2021-01-06 01:00:00,BTC/USDT,33918.01000000,34018.93000000,33513.05000000,33811.28000000,3237.65935400,109289337.46651398,62378\n1609891200000,2021-01-06 00:00:00,BTC/USDT,33949.53000000,34107.59000000,33288.00000000,33918.01000000,6106.35192400,205743581.04425707,99660\n1609887600000,2021-01-05 23:00:00,BTC/USDT,34183.43000000,34284.91000000,33650.00000000,33949.53000000,4787.68664100,162432229.83348370,90092\n1609884000000,2021-01-05 22:00:00,BTC/USDT,33699.63000000,34360.00000000,33695.60000000,34183.42000000,4616.40333400,157718468.59895870,113239\n1609880400000,2021-01-05 21:00:00,BTC/USDT,33968.66000000,33996.03000000,33471.94000000,33699.63000000,3682.07415100,124381906.41438341,74894\n1609876800000,2021-01-05 20:00:00,BTC/USDT,33693.80000000,34044.13000000,33318.00000000,33969.17000000,5800.65390700,195673767.39849565,106875\n1609873200000,2021-01-05 19:00:00,BTC/USDT,33372.83000000,34187.09000000,33098.82000000,33697.75000000,8659.33850300,292047625.83670093,179774\n1609869600000,2021-01-05 18:00:00,BTC/USDT,32742.73000000,33433.55000000,32650.03000000,33372.84000000,6647.00992100,219940103.11654606,129769\n1609866000000,2021-01-05 17:00:00,BTC/USDT,32469.90000000,32790.00000000,32254.18000000,32742.73000000,3599.64571800,116970729.32707121,82231\n1609862400000,2021-01-05 16:00:00,BTC/USDT,32099.97000000,32670.00000000,31763.23000000,32469.90000000,4926.17161600,159348378.63575512,114817\n1609858800000,2021-01-05 15:00:00,BTC/USDT,32343.61000000,32374.31000000,31929.41000000,32099.98000000,3616.59901600,116161069.22208343,98120\n1609855200000,2021-01-05 14:00:00,BTC/USDT,31460.11000000,32375.07000000,31111.78000000,32342.33000000,5942.10285400,188316129.32606876,137989\n1609851600000,2021-01-05 13:00:00,BTC/USDT,31585.00000000,31900.00000000,31416.27000000,31460.01000000,2523.02316500,79971740.01184837,64040\n1609848000000,2021-01-05 12:00:00,BTC/USDT,31776.66000000,32057.13000000,31563.84000000,31585.01000000,4161.55133400,132493096.80311255,94633\n1609844400000,2021-01-05 11:00:00,BTC/USDT,31347.73000000,31777.00000000,31284.15000000,31776.66000000,2908.12176800,91671919.09848990,72492\n1609840800000,2021-01-05 10:00:00,BTC/USDT,31569.63000000,31687.11000000,31088.33000000,31345.77000000,3174.76039600,99666137.72028313,81295\n1609837200000,2021-01-05 09:00:00,BTC/USDT,31192.16000000,31821.94000000,31157.90000000,31569.63000000,3935.59797900,124016625.84180410,91763\n1609833600000,2021-01-05 08:00:00,BTC/USDT,30817.78000000,31522.19000000,30817.78000000,31192.09000000,3237.12095700,101016094.16648097,88984\n1609830000000,2021-01-05 07:00:00,BTC/USDT,31123.25000000,31312.03000000,30759.47000000,30817.77000000,2811.38395300,87206069.28127892,71734\n1609826400000,2021-01-05 06:00:00,BTC/USDT,30436.26000000,31270.43000000,29900.00000000,31123.25000000,6611.21073500,202286248.33256154,127994\n1609822800000,2021-01-05 05:00:00,BTC/USDT,30858.98000000,31366.16000000,29900.00000000,30436.25000000,6366.97612400,195158786.10553854,159593\n1609819200000,2021-01-05 04:00:00,BTC/USDT,31190.04000000,31538.45000000,30418.00000000,30858.98000000,7229.48636600,224769397.84667815,157600\n1609815600000,2021-01-05 03:00:00,BTC/USDT,32306.64000000,32536.09000000,31130.86000000,31200.00000000,5695.04449600,181350412.23885364,112880\n1609812000000,2021-01-05 02:00:00,BTC/USDT,32786.05000000,32828.26000000,32221.10000000,32306.64000000,3163.43004400,102893990.21816806,65391\n1609808400000,2021-01-05 01:00:00,BTC/USDT,32430.69000000,32796.00000000,32224.25000000,32786.06000000,4246.96065000,138118302.36451834,83071\n1609804800000,2021-01-05 00:00:00,BTC/USDT,31989.75000000,32853.38000000,31989.74000000,32430.49000000,7707.64341000,250007916.90775211,127581\n1609801200000,2021-01-04 23:00:00,BTC/USDT,31332.05000000,32031.01000000,31150.00000000,31988.71000000,4193.64872800,132893993.10060339,86041\n1609797600000,2021-01-04 22:00:00,BTC/USDT,31014.27000000,31474.78000000,30950.00000000,31332.05000000,1646.48413800,51480692.96027581,54923\n1609794000000,2021-01-04 21:00:00,BTC/USDT,31244.97000000,31579.00000000,30856.08000000,31014.27000000,3202.45612700,99727509.60811839,74424\n1609790400000,2021-01-04 20:00:00,BTC/USDT,31665.52000000,31830.95000000,31210.00000000,31244.97000000,2850.96308700,89770988.08670449,64911\n1609786800000,2021-01-04 19:00:00,BTC/USDT,31165.16000000,31855.75000000,31104.99000000,31665.51000000,3388.37222100,106758748.38653801,71762\n1609783200000,2021-01-04 18:00:00,BTC/USDT,31022.25000000,31465.44000000,31001.00000000,31165.16000000,3361.43987000,104948080.87133230,73391\n1609779600000,2021-01-04 17:00:00,BTC/USDT,31320.30000000,31523.01000000,30626.36000000,31024.17000000,4962.56272900,153975400.83942578,96732\n1609776000000,2021-01-04 16:00:00,BTC/USDT,31745.71000000,31920.00000000,30723.00000000,31320.31000000,8174.28578300,255043539.07925110,133682\n1609772400000,2021-01-04 15:00:00,BTC/USDT,32113.68000000,32185.67000000,31550.00000000,31745.72000000,5321.62025100,169560823.38397644,99381\n1609768800000,2021-01-04 14:00:00,BTC/USDT,31400.00000000,32167.93000000,31400.00000000,32113.68000000,7479.38358700,237746821.62135450,116397\n1609765200000,2021-01-04 13:00:00,BTC/USDT,31219.03000000,31400.00000000,30600.00000000,31400.00000000,5474.65478700,169692056.80846927,101540\n1609761600000,2021-01-04 12:00:00,BTC/USDT,30742.99000000,31434.07000000,30454.25000000,31219.02000000,8316.88082800,257727418.56933529,153242\n1609758000000,2021-01-04 11:00:00,BTC/USDT,30270.76000000,30900.20000000,29400.00000000,30743.06000000,9413.49006700,283733132.88333011,170181\n1609754400000,2021-01-04 10:00:00,BTC/USDT,31038.35000000,31227.33000000,28130.00000000,30270.76000000,19376.83117900,573629351.31642204,305630\n1609750800000,2021-01-04 09:00:00,BTC/USDT,32045.15000000,32152.15000000,30248.85000000,31038.63000000,11271.89653100,351321935.49618102,173055\n1609747200000,2021-01-04 08:00:00,BTC/USDT,32068.84000000,32567.88000000,31450.00000000,32045.14000000,6529.25324800,209132998.40311764,125127\n1609743600000,2021-01-04 07:00:00,BTC/USDT,33100.02000000,33148.69000000,31505.00000000,32068.84000000,9472.37682800,305928119.96734844,163230\n1609740000000,2021-01-04 06:00:00,BTC/USDT,32813.01000000,33408.00000000,32813.00000000,33100.02000000,4768.76492800,157679676.36409471,93791\n1609736400000,2021-01-04 05:00:00,BTC/USDT,33257.56000000,33315.90000000,32802.25000000,32813.01000000,3990.70088000,132098247.27391540,86995\n1609732800000,2021-01-04 04:00:00,BTC/USDT,33505.15000000,33600.00000000,33186.14000000,33257.55000000,3167.02927300,105695860.81390999,70718\n1609729200000,2021-01-04 03:00:00,BTC/USDT,33454.21000000,33510.71000000,33160.00000000,33505.16000000,3174.20913400,105885637.67106639,74546\n1609725600000,2021-01-04 02:00:00,BTC/USDT,33191.05000000,33588.00000000,32958.38000000,33454.22000000,3180.35078300,105765257.03035539,73174\n1609722000000,2021-01-04 01:00:00,BTC/USDT,32843.89000000,33327.00000000,32602.47000000,33191.04000000,3390.02387200,111723279.13155555,77232\n1609718400000,2021-01-04 00:00:00,BTC/USDT,33000.05000000,33111.44000000,32400.25000000,32843.88000000,4792.20683100,157090780.02866342,102303\n1609714800000,2021-01-03 23:00:00,BTC/USDT,33119.14000000,33600.00000000,32600.00000000,33000.05000000,5077.56059200,168463124.87883950,108752\n1609711200000,2021-01-03 22:00:00,BTC/USDT,33559.06000000,33661.10000000,33000.00000000,33119.14000000,2841.03309300,94606664.88271222,74965\n1609707600000,2021-01-03 21:00:00,BTC/USDT,33725.72000000,33861.92000000,33506.00000000,33558.34000000,2123.06799200,71508817.93767056,76216\n1609704000000,2021-01-03 20:00:00,BTC/USDT,32775.10000000,33816.00000000,32727.10000000,33726.21000000,5420.67818300,181382884.16276131,111426\n1609700400000,2021-01-03 19:00:00,BTC/USDT,32769.79000000,32930.00000000,32455.71000000,32772.21000000,2288.43941200,74825548.95742354,73266\n1609696800000,2021-01-03 18:00:00,BTC/USDT,32907.51000000,33031.57000000,32267.00000000,32769.76000000,4707.19994300,153750662.05561621,94512\n1609693200000,2021-01-03 17:00:00,BTC/USDT,32676.75000000,33333.33000000,32454.73000000,32909.27000000,7447.80916300,244812012.99385249,135756\n1609689600000,2021-01-03 16:00:00,BTC/USDT,33505.15000000,33822.69000000,32650.00000000,32676.74000000,7983.73912500,266031558.97452663,129178\n1609686000000,2021-01-03 15:00:00,BTC/USDT,33811.54000000,33873.45000000,32727.00000000,33506.62000000,8391.24975700,279582888.95484869,140091\n1609682400000,2021-01-03 14:00:00,BTC/USDT,33877.98000000,34150.00000000,33450.00000000,33811.54000000,5928.80556300,200638151.95697655,112757\n1609678800000,2021-01-03 13:00:00,BTC/USDT,34103.73000000,34385.02000000,33800.00000000,33880.00000000,4373.73837600,149031480.84569385,87957\n1609675200000,2021-01-03 12:00:00,BTC/USDT,34413.53000000,34600.00000000,33928.75000000,34103.72000000,4546.28348100,155830593.73039518,89900\n1609671600000,2021-01-03 11:00:00,BTC/USDT,33877.96000000,34450.00000000,33787.55000000,34413.53000000,4116.85314100,140535081.14537481,92262\n1609668000000,2021-01-03 10:00:00,BTC/USDT,34189.98000000,34350.00000000,33403.00000000,33877.96000000,6335.22664200,214615660.03475803,120518\n1609664400000,2021-01-03 09:00:00,BTC/USDT,34367.35000000,34588.88000000,33800.00000000,34190.55000000,6052.67897200,206713459.38775728,114590\n1609660800000,2021-01-03 08:00:00,BTC/USDT,34451.78000000,34670.41000000,33757.29000000,34367.68000000,6146.77411200,210713141.05225317,110795\n1609657200000,2021-01-03 07:00:00,BTC/USDT,33967.13000000,34778.11000000,33737.19000000,34452.34000000,7968.07402300,273877823.02471700,135337\n1609653600000,2021-01-03 06:00:00,BTC/USDT,33758.67000000,34110.05000000,33651.12000000,33967.13000000,4526.91097400,153551636.94704574,95086\n1609650000000,2021-01-03 05:00:00,BTC/USDT,33192.53000000,34180.00000000,33109.44000000,33758.67000000,10355.07031500,350167811.09683042,176812\n1609646400000,2021-01-03 04:00:00,BTC/USDT,32576.11000000,33250.00000000,32450.00000000,33192.53000000,3436.07098400,112792746.66880588,60917\n1609642800000,2021-01-03 03:00:00,BTC/USDT,32741.68000000,32756.00000000,32404.00000000,32573.87000000,2125.87880000,69347477.45908845,46215\n1609639200000,2021-01-03 02:00:00,BTC/USDT,32583.04000000,32884.09000000,32550.00000000,32741.67000000,2017.20975900,66039545.22800387,48226\n1609635600000,2021-01-03 01:00:00,BTC/USDT,32447.95000000,32790.07000000,32201.00000000,32585.09000000,3401.15220700,110773399.69333609,70692\n1609632000000,2021-01-03 00:00:00,BTC/USDT,32176.45000000,32497.80000000,31962.99000000,32447.94000000,3346.06214100,108006253.42272902,63472\n1609628400000,2021-01-02 23:00:00,BTC/USDT,32143.53000000,32246.67000000,31500.00000000,32178.33000000,4622.34033800,147557520.62016068,85383\n1609624800000,2021-01-02 22:00:00,BTC/USDT,31693.53000000,32311.92000000,31424.96000000,32145.66000000,3879.06209400,124189288.57427153,91572\n1609621200000,2021-01-02 21:00:00,BTC/USDT,30669.43000000,32005.94000000,30300.00000000,31694.59000000,10395.12064200,326513926.94158377,161944\n1609617600000,2021-01-02 20:00:00,BTC/USDT,33027.20000000,33061.37000000,30550.00000000,30667.22000000,15733.28311200,504482345.09340442,213120\n1609614000000,2021-01-02 19:00:00,BTC/USDT,32999.98000000,33220.00000000,32690.00000000,33027.20000000,5844.85649900,192991798.29034694,100521\n1609610400000,2021-01-02 18:00:00,BTC/USDT,32856.95000000,33300.00000000,32421.00000000,32999.98000000,7170.29541200,235363454.12117517,107993\n1609606800000,2021-01-02 17:00:00,BTC/USDT,32484.28000000,32935.00000000,32394.77000000,32856.96000000,6402.18070100,209101591.46210164,106138\n1609603200000,2021-01-02 16:00:00,BTC/USDT,31691.09000000,33000.00000000,31616.42000000,32482.28000000,15432.28518600,500065150.69207718,243515\n1609599600000,2021-01-02 15:00:00,BTC/USDT,31290.53000000,31800.00000000,31236.37000000,31691.29000000,5626.19375500,177435149.22325535,97954\n1609596000000,2021-01-02 14:00:00,BTC/USDT,31546.06000000,31567.89000000,31065.00000000,31290.53000000,6447.78389700,201609364.53663465,115280\n1609592400000,2021-01-02 13:00:00,BTC/USDT,30604.03000000,31561.87000000,30521.00000000,31541.17000000,9182.42970300,284862696.67061101,155703\n1609588800000,2021-01-02 12:00:00,BTC/USDT,29754.99000000,30888.00000000,29741.39000000,30604.03000000,12772.56604400,387025691.68456484,200036\n1609585200000,2021-01-02 11:00:00,BTC/USDT,29679.51000000,29780.53000000,29622.79000000,29755.00000000,1365.03665500,40570945.42355006,29571\n1609581600000,2021-01-02 10:00:00,BTC/USDT,29612.88000000,29829.00000000,29473.91000000,29680.99000000,2315.18233700,68694488.75869353,49466\n1609578000000,2021-01-02 09:00:00,BTC/USDT,29844.52000000,29899.00000000,29578.05000000,29612.87000000,3188.49737500,94891130.35379542,63645\n1609574400000,2021-01-02 08:00:00,BTC/USDT,29751.47000000,29849.11000000,29640.00000000,29844.51000000,2358.08824100,70213292.48882387,55194\n1609570800000,2021-01-02 07:00:00,BTC/USDT,29709.06000000,29820.50000000,29623.31000000,29750.00000000,3067.81272400,91222945.57779763,56853\n1609567200000,2021-01-02 06:00:00,BTC/USDT,29589.99000000,29745.00000000,29450.00000000,29709.07000000,3030.24082000,89791157.01909066,61113\n1609563600000,2021-01-02 05:00:00,BTC/USDT,29349.63000000,29590.00000000,29349.63000000,29589.99000000,2778.87823100,81957934.92152147,48203\n1609560000000,2021-01-02 04:00:00,BTC/USDT,29351.95000000,29396.00000000,29220.00000000,29349.63000000,988.71107000,28985783.95102539,36593\n1609556400000,2021-01-02 03:00:00,BTC/USDT,29323.82000000,29382.49000000,29256.85000000,29351.95000000,1158.04176100,33963115.16512242,33338\n1609552800000,2021-01-02 02:00:00,BTC/USDT,29359.46000000,29469.00000000,29320.01000000,29323.82000000,1704.83013700,50111457.75322650,34864\n1609549200000,2021-01-02 01:00:00,BTC/USDT,29197.93000000,29400.00000000,29100.00000000,29359.47000000,1891.99724900,55385402.78970417,41136\n1609545600000,2021-01-02 00:00:00,BTC/USDT,29331.70000000,29338.59000000,28946.53000000,29197.48000000,2638.15937900,76856531.55900287,56787\n1609542000000,2021-01-01 23:00:00,BTC/USDT,29262.32000000,29338.89000000,29228.14000000,29331.69000000,971.12338500,28443882.37070015,28620\n1609538400000,2021-01-01 22:00:00,BTC/USDT,29163.17000000,29326.74000000,29104.57000000,29262.32000000,1127.03589700,32972283.53778708,37533\n1609534800000,2021-01-01 21:00:00,BTC/USDT,29200.97000000,29289.82000000,29130.00000000,29160.39000000,1447.22964100,42285361.51600605,34181\n1609531200000,2021-01-01 20:00:00,BTC/USDT,29029.04000000,29279.72000000,28880.37000000,29200.96000000,1838.54908200,53590098.13888548,52949\n1609527600000,2021-01-01 19:00:00,BTC/USDT,29072.70000000,29125.32000000,28950.00000000,29029.04000000,1629.22872100,47324728.28361069,49038\n1609524000000,2021-01-01 18:00:00,BTC/USDT,29077.59000000,29259.98000000,28624.57000000,29072.70000000,4226.43004100,122480565.74309262,88856\n1609520400000,2021-01-01 17:00:00,BTC/USDT,29300.79000000,29339.76000000,28937.00000000,29079.64000000,2504.97709900,73023440.33533108,58010\n1609516800000,2021-01-01 16:00:00,BTC/USDT,29188.67000000,29360.00000000,29125.00000000,29300.57000000,1490.32948400,43610391.87454118,41586\n1609513200000,2021-01-01 15:00:00,BTC/USDT,29327.83000000,29391.00000000,29030.14000000,29188.67000000,2779.06389400,81232002.48765679,71966\n1609509600000,2021-01-01 14:00:00,BTC/USDT,29464.79000000,29530.00000000,29266.15000000,29327.84000000,2612.67217000,76824362.53364532,63341\n1609506000000,2021-01-01 13:00:00,BTC/USDT,29233.49000000,29470.00000000,29200.00000000,29464.79000000,2211.29829000,64952612.17843147,51420\n1609502400000,2021-01-01 12:00:00,BTC/USDT,29313.49000000,29600.00000000,29150.00000000,29233.49000000,4191.91516100,123253838.23617110,82845\n1609498800000,2021-01-01 11:00:00,BTC/USDT,29223.82000000,29402.57000000,29212.44000000,29313.49000000,2309.22771700,67680909.41227815,49883\n1609495200000,2021-01-01 10:00:00,BTC/USDT,29202.21000000,29344.97000000,29152.88000000,29223.82000000,1944.25584100,56881923.87605724,46783\n1609491600000,2021-01-01 09:00:00,BTC/USDT,29000.01000000,29307.73000000,28970.00000000,29202.21000000,2022.05602200,59006512.26098600,43674\n1609488000000,2021-01-01 08:00:00,BTC/USDT,29092.84000000,29178.03000000,28872.24000000,29000.01000000,2008.16573900,58274190.69862189,55012\n1609484400000,2021-01-01 07:00:00,BTC/USDT,29174.35000000,29191.98000000,28806.54000000,29092.83000000,2380.18091800,69034619.09489940,53158\n1609480800000,2021-01-01 06:00:00,BTC/USDT,29187.01000000,29270.00000000,29077.32000000,29174.35000000,1420.72629100,41446013.01819005,46400\n1609477200000,2021-01-01 05:00:00,BTC/USDT,29220.31000000,29235.28000000,29084.11000000,29187.01000000,1469.95626200,42864538.70435811,41800\n1609473600000,2021-01-01 04:00:00,BTC/USDT,29278.41000000,29395.00000000,29029.40000000,29220.31000000,2038.04680300,59614637.30352874,55414\n1609470000000,2021-01-01 03:00:00,BTC/USDT,29195.25000000,29367.00000000,29150.02000000,29278.40000000,1461.34507700,42760776.72551646,42510\n1609466400000,2021-01-01 02:00:00,BTC/USDT,29410.00000000,29465.26000000,29120.03000000,29194.65000000,2384.23156000,69842653.67342030,57646\n1609462800000,2021-01-01 01:00:00,BTC/USDT,28995.13000000,29470.00000000,28960.35000000,29409.99000000,5403.06847100,158357816.81805722,103896\n1609459200000,2021-01-01 00:00:00,BTC/USDT,28923.63000000,29031.34000000,28690.17000000,28995.13000000,2311.81144500,66768830.34010008,58389\n1609455600000,2020-12-31 23:00:00,BTC/USDT,29100.83000000,29110.35000000,28780.00000000,28923.63000000,1976.41929900,57243040.06672289,46165\n1609452000000,2020-12-31 22:00:00,BTC/USDT,28966.36000000,29143.73000000,28910.19000000,29100.84000000,1438.50632600,41807122.88999045,36413\n1609448400000,2020-12-31 21:00:00,BTC/USDT,29126.71000000,29169.55000000,28900.79000000,28966.36000000,2524.47311100,73351462.94451089,51297\n1609444800000,2020-12-31 20:00:00,BTC/USDT,28897.84000000,29139.65000000,28862.00000000,29126.70000000,1936.48029900,56103301.54204034,42101\n1609441200000,2020-12-31 19:00:00,BTC/USDT,28872.24000000,29000.00000000,28742.41000000,28897.83000000,2293.82133900,66289233.16417270,56789\n1609437600000,2020-12-31 18:00:00,BTC/USDT,28571.97000000,28898.00000000,28424.56000000,28872.25000000,2579.41940800,74095956.48706694,48133\n1609434000000,2020-12-31 17:00:00,BTC/USDT,28382.47000000,28723.68000000,28362.00000000,28571.97000000,2435.55698800,69604052.89271796,49032\n1609430400000,2020-12-31 16:00:00,BTC/USDT,28782.01000000,28822.59000000,28311.00000000,28380.60000000,2970.38140600,84889969.84550027,78389\n1609426800000,2020-12-31 15:00:00,BTC/USDT,28371.52000000,28840.26000000,28369.53000000,28770.00000000,3793.59720500,108642922.25717718,74076\n1609423200000,2020-12-31 14:00:00,BTC/USDT,28519.25000000,28523.47000000,28111.25000000,28371.51000000,3624.61853100,102605271.68508579,69397\n1609419600000,2020-12-31 13:00:00,BTC/USDT,28617.65000000,28635.90000000,27850.00000000,28519.00000000,7710.02965200,218204970.10157385,128115\n1609416000000,2020-12-31 12:00:00,BTC/USDT,28910.29000000,28989.03000000,28400.00000000,28617.60000000,4468.90200100,128561483.62739386,85698\n1609412400000,2020-12-31 11:00:00,BTC/USDT,29136.49000000,29140.00000000,28888.43000000,28910.30000000,2148.68443700,62354769.67185555,52675\n1609408800000,2020-12-31 10:00:00,BTC/USDT,28989.99000000,29168.37000000,28960.17000000,29136.49000000,2032.73984400,59164675.18772087,48970\n1609405200000,2020-12-31 09:00:00,BTC/USDT,28926.32000000,29067.82000000,28580.00000000,28989.99000000,2954.20945400,85246205.98696452,63400\n1609401600000,2020-12-31 08:00:00,BTC/USDT,29155.24000000,29210.83000000,28780.00000000,28926.31000000,3372.84919500,97673627.75426602,71919\n1609398000000,2020-12-31 07:00:00,BTC/USDT,28859.02000000,29285.00000000,28859.02000000,29155.25000000,3192.08297300,93052548.87306031,66499\n1609394400000,2020-12-31 06:00:00,BTC/USDT,28972.20000000,29074.22000000,28849.50000000,28859.01000000,2126.65340600,61619103.59331996,49700\n1609390800000,2020-12-31 05:00:00,BTC/USDT,29028.34000000,29031.66000000,28738.10000000,28972.20000000,2155.46662300,62353309.31494652,47822\n1609387200000,2020-12-31 04:00:00,BTC/USDT,29024.01000000,29185.00000000,28824.61000000,29028.35000000,2767.84337300,80326404.95159185,59446\n1609383600000,2020-12-31 03:00:00,BTC/USDT,28720.48000000,29147.75000000,28711.06000000,29024.00000000,2583.08649800,74760530.72493473,50394\n1609380000000,2020-12-31 02:00:00,BTC/USDT,28743.58000000,28792.76000000,28600.38000000,28720.49000000,1906.35578900,54720917.34465397,43654\n1609376400000,2020-12-31 01:00:00,BTC/USDT,29120.50000000,29177.41000000,28120.67000000,28743.57000000,6991.53980600,199886526.64636524,132055\n1609372800000,2020-12-31 00:00:00,BTC/USDT,28875.55000000,29300.00000000,28869.78000000,29120.51000000,5524.78818900,161042717.86644127,100654\n1609369200000,2020-12-30 23:00:00,BTC/USDT,28704.00000000,28911.52000000,28630.00000000,28875.54000000,2183.21430800,62873049.72125565,46795\n1609365600000,2020-12-30 22:00:00,BTC/USDT,28875.21000000,28903.93000000,28571.84000000,28703.88000000,1915.68322400,55106704.61475086,57210\n1609362000000,2020-12-30 21:00:00,BTC/USDT,28785.67000000,28980.00000000,28534.70000000,28875.21000000,3551.04038400,102246784.04567833,77627\n1609358400000,2020-12-30 20:00:00,BTC/USDT,28776.45000000,28996.00000000,28603.24000000,28785.67000000,4271.32751700,123035553.09734903,81707\n1609354800000,2020-12-30 19:00:00,BTC/USDT,28510.86000000,28900.05000000,28444.00000000,28776.46000000,7242.87951400,208100984.02679400,119364\n1609351200000,2020-12-30 18:00:00,BTC/USDT,28239.68000000,28530.00000000,28239.68000000,28510.86000000,3992.53675400,113395838.93303015,73104\n1609347600000,2020-12-30 17:00:00,BTC/USDT,28153.77000000,28309.88000000,28050.00000000,28239.69000000,2913.70261700,82177572.46902899,61132\n1609344000000,2020-12-30 16:00:00,BTC/USDT,28153.75000000,28240.37000000,27887.00000000,28153.78000000,3010.36422000,84526703.84275746,76484\n1609340400000,2020-12-30 15:00:00,BTC/USDT,28182.98000000,28291.82000000,27984.19000000,28153.76000000,3130.42806800,88139585.43869922,76071\n1609336800000,2020-12-30 14:00:00,BTC/USDT,27871.05000000,28184.44000000,27871.05000000,28182.98000000,2539.68508100,71183311.06808460,56468\n1609333200000,2020-12-30 13:00:00,BTC/USDT,27858.72000000,28167.21000000,27768.70000000,27869.61000000,3488.99624000,97640842.50745054,68046\n1609329600000,2020-12-30 12:00:00,BTC/USDT,27836.95000000,27934.43000000,27577.00000000,27858.73000000,2466.71131700,68456108.47536903,57020\n1609326000000,2020-12-30 11:00:00,BTC/USDT,27850.02000000,27947.92000000,27595.43000000,27838.82000000,2876.27538200,79894409.34640959,70390\n1609322400000,2020-12-30 10:00:00,BTC/USDT,27855.91000000,28063.97000000,27742.43000000,27850.02000000,3071.00440800,85724106.20922187,62269\n1609318800000,2020-12-30 09:00:00,BTC/USDT,27732.86000000,27898.63000000,27320.00000000,27855.91000000,5325.64588200,147355395.77455766,106614\n1609315200000,2020-12-30 08:00:00,BTC/USDT,28389.02000000,28579.92000000,27559.23000000,27732.30000000,7768.61402400,217617014.31082520,135951\n1609311600000,2020-12-30 07:00:00,BTC/USDT,28379.99000000,28598.74000000,28270.00000000,28386.62000000,3640.04403500,103430132.70671313,80948\n1609308000000,2020-12-30 06:00:00,BTC/USDT,28016.74000000,28577.56000000,27933.00000000,28380.00000000,6656.99572200,188485478.00267577,121521\n1609304400000,2020-12-30 05:00:00,BTC/USDT,27897.49000000,28159.00000000,27828.28000000,28016.74000000,2801.27654700,78434458.10857875,58507\n1609300800000,2020-12-30 04:00:00,BTC/USDT,28066.57000000,28205.37000000,27722.22000000,27897.49000000,4097.64569400,114488719.91646471,88571\n1609297200000,2020-12-30 03:00:00,BTC/USDT,28000.00000000,28239.00000000,27814.67000000,28065.54000000,5193.97003500,145474743.92683677,93694\n1609293600000,2020-12-30 02:00:00,BTC/USDT,27615.84000000,28000.00000000,27498.92000000,27999.14000000,4435.19551200,123358844.32213855,74615\n1609290000000,2020-12-30 01:00:00,BTC/USDT,27630.75000000,27720.00000000,27530.89000000,27615.85000000,2038.14679900,56326464.41780145,53928\n1609286400000,2020-12-30 00:00:00,BTC/USDT,27385.00000000,27837.71000000,27374.87000000,27630.74000000,6744.67454200,186480432.17779958,118059\n1609282800000,2020-12-29 23:00:00,BTC/USDT,27048.34000000,27410.00000000,26945.57000000,27385.00000000,3514.18197000,95653602.32733610,64719\n1609279200000,2020-12-29 22:00:00,BTC/USDT,26911.36000000,27117.00000000,26893.78000000,27048.32000000,1592.31929400,42998370.24386760,53571\n1609275600000,2020-12-29 21:00:00,BTC/USDT,26990.00000000,27200.87000000,26820.38000000,26913.12000000,3772.22030700,102063908.60542742,67776\n1609272000000,2020-12-29 20:00:00,BTC/USDT,26931.51000000,27000.00000000,26743.42000000,26989.63000000,2729.83530700,73447938.71221806,60182\n1609268400000,2020-12-29 19:00:00,BTC/USDT,26591.38000000,26943.89000000,26505.94000000,26931.62000000,2869.95531100,76799794.94776068,61001\n1609264800000,2020-12-29 18:00:00,BTC/USDT,26621.08000000,26749.82000000,26565.01000000,26591.29000000,2628.08695800,70031517.51499948,58753\n1609261200000,2020-12-29 17:00:00,BTC/USDT,26428.10000000,26628.98000000,26243.50000000,26621.46000000,3245.09447900,85858934.13215567,73729\n1609257600000,2020-12-29 16:00:00,BTC/USDT,26615.28000000,26625.99000000,26204.99000000,26430.84000000,4570.55134000,120598568.49984490,98414\n1609254000000,2020-12-29 15:00:00,BTC/USDT,26664.49000000,26734.40000000,26433.00000000,26614.97000000,2674.21828800,71097824.01410569,58180\n1609250400000,2020-12-29 14:00:00,BTC/USDT,26880.01000000,26902.22000000,26600.65000000,26664.49000000,2589.53772900,69240437.74178789,50584\n1609246800000,2020-12-29 13:00:00,BTC/USDT,26893.22000000,26977.16000000,26730.00000000,26880.00000000,2050.08302600,55126091.53817969,46856\n1609243200000,2020-12-29 12:00:00,BTC/USDT,26874.55000000,27100.00000000,26858.51000000,26893.22000000,3680.93022700,99357694.98701662,69244\n1609239600000,2020-12-29 11:00:00,BTC/USDT,26741.04000000,26900.00000000,26565.13000000,26876.40000000,2525.69740800,67471175.21530080,83088\n1609236000000,2020-12-29 10:00:00,BTC/USDT,26586.94000000,26860.00000000,26586.43000000,26741.03000000,2462.67666600,65925466.13131929,77255\n1609232400000,2020-12-29 09:00:00,BTC/USDT,26660.60000000,26745.00000000,26521.14000000,26590.60000000,2098.54072900,55896573.11198540,66896\n1609228800000,2020-12-29 08:00:00,BTC/USDT,26436.53000000,26765.35000000,26434.49000000,26660.36000000,3037.22678700,80759528.90458442,73312\n1609225200000,2020-12-29 07:00:00,BTC/USDT,26356.23000000,26462.94000000,26145.46000000,26435.63000000,1893.97243300,49789849.62457248,59667\n1609221600000,2020-12-29 06:00:00,BTC/USDT,26452.40000000,26533.30000000,26264.52000000,26356.22000000,2499.72047600,65994608.63618230,85925\n1609218000000,2020-12-29 05:00:00,BTC/USDT,26489.07000000,26492.00000000,25880.00000000,26452.18000000,5229.12380600,136920208.92639515,92466\n1609214400000,2020-12-29 04:00:00,BTC/USDT,26445.50000000,26583.65000000,26203.00000000,26487.62000000,4009.67056700,105947918.35762364,72698\n1609210800000,2020-12-29 03:00:00,BTC/USDT,26858.37000000,26863.07000000,26433.78000000,26444.60000000,3296.20968600,87716374.15018029,64590\n1609207200000,2020-12-29 02:00:00,BTC/USDT,26829.64000000,26949.07000000,26727.25000000,26858.37000000,1421.31556800,38167370.26937193,37081\n1609203600000,2020-12-29 01:00:00,BTC/USDT,26811.27000000,26857.01000000,26636.92000000,26830.53000000,2422.60405400,64849249.47103455,64476\n1609200000000,2020-12-29 00:00:00,BTC/USDT,27079.42000000,27116.78000000,26758.88000000,26811.29000000,2597.82019000,69904213.70498456,64703\n1609196400000,2020-12-28 23:00:00,BTC/USDT,26891.69000000,27163.77000000,26836.01000000,27079.41000000,2768.61289400,74875983.99040128,70528\n1609192800000,2020-12-28 22:00:00,BTC/USDT,26627.31000000,26940.95000000,26415.09000000,26891.69000000,3469.11861700,92603735.81696283,95389\n1609189200000,2020-12-28 21:00:00,BTC/USDT,26866.97000000,26958.64000000,26620.40000000,26627.30000000,2273.35034300,60999372.30902186,52799\n1609185600000,2020-12-28 20:00:00,BTC/USDT,26996.73000000,27098.38000000,26678.00000000,26866.97000000,3655.91074800,98188279.79644093,61827\n1609182000000,2020-12-28 19:00:00,BTC/USDT,27022.83000000,27196.09000000,26945.39000000,26996.74000000,2406.53204600,65211485.72603311,41062\n1609178400000,2020-12-28 18:00:00,BTC/USDT,27140.89000000,27228.53000000,26898.00000000,27023.55000000,2852.85684800,77306204.27001483,50328\n1609174800000,2020-12-28 17:00:00,BTC/USDT,27066.43000000,27236.71000000,27021.21000000,27140.96000000,2273.55272600,61700220.27625713,48884\n1609171200000,2020-12-28 16:00:00,BTC/USDT,26916.32000000,27157.31000000,26743.00000000,27066.44000000,3763.68259800,101672013.26842662,70585\n1609167600000,2020-12-28 15:00:00,BTC/USDT,27276.13000000,27320.00000000,26830.16000000,26916.32000000,4219.10137600,114036883.38129216,72762\n1609164000000,2020-12-28 14:00:00,BTC/USDT,27249.22000000,27355.00000000,27130.02000000,27276.11000000,3246.96023200,88508047.40385192,55929\n1609160400000,2020-12-28 13:00:00,BTC/USDT,27097.27000000,27500.00000000,27097.27000000,27249.22000000,5710.93308000,155991278.72376911,108327\n1609156800000,2020-12-28 12:00:00,BTC/USDT,26859.07000000,27145.27000000,26711.57000000,27097.28000000,4004.09994300,107985719.04627065,70646\n1609153200000,2020-12-28 11:00:00,BTC/USDT,26720.02000000,26889.71000000,26609.51000000,26859.07000000,2728.04662400,73042646.30483201,57834\n1609149600000,2020-12-28 10:00:00,BTC/USDT,26835.84000000,26922.28000000,26557.73000000,26720.58000000,3512.77934500,93918763.69167718,70034\n1609146000000,2020-12-28 09:00:00,BTC/USDT,26936.25000000,27000.00000000,26750.66000000,26835.83000000,2573.74675800,69234894.19132475,57221\n1609142400000,2020-12-28 08:00:00,BTC/USDT,27001.32000000,27080.00000000,26705.41000000,26937.88000000,3589.34838900,96519185.32892204,83455\n1609138800000,2020-12-28 07:00:00,BTC/USDT,27056.15000000,27200.00000000,27001.31000000,27001.31000000,3529.36269200,95675953.32220389,57548\n1609135200000,2020-12-28 06:00:00,BTC/USDT,26942.62000000,27100.00000000,26830.00000000,27057.63000000,2358.01126200,63587000.67336462,38595\n1609131600000,2020-12-28 05:00:00,BTC/USDT,26864.10000000,27153.90000000,26798.94000000,26942.62000000,2718.52980200,73431496.89020482,43314\n1609128000000,2020-12-28 04:00:00,BTC/USDT,27275.66000000,27332.00000000,26848.57000000,26864.11000000,2359.43802300,63829911.96619164,44380\n1609124400000,2020-12-28 03:00:00,BTC/USDT,27082.52000000,27290.00000000,26918.51000000,27275.65000000,2446.26506200,66317972.18822799,46595\n1609120800000,2020-12-28 02:00:00,BTC/USDT,27060.42000000,27169.64000000,26910.27000000,27085.00000000,2603.46947300,70402704.16030073,42982\n1609117200000,2020-12-28 01:00:00,BTC/USDT,26877.69000000,27356.30000000,26800.01000000,27062.00000000,5307.91292600,144092510.36354907,80323\n1609113600000,2020-12-28 00:00:00,BTC/USDT,26281.54000000,26976.00000000,26101.00000000,26877.70000000,5350.12068900,142674545.48481140,86857\n1609110000000,2020-12-27 23:00:00,BTC/USDT,26521.14000000,26713.96000000,26219.00000000,26281.66000000,3731.59514500,98871908.96837776,69596\n1609106400000,2020-12-27 22:00:00,BTC/USDT,26404.45000000,26699.99000000,26263.20000000,26521.14000000,3178.03750800,84137792.71018362,77226\n1609102800000,2020-12-27 21:00:00,BTC/USDT,26219.88000000,26512.00000000,25700.00000000,26400.97000000,8265.39684700,216225729.86703631,134735\n1609099200000,2020-12-27 20:00:00,BTC/USDT,27081.98000000,27095.18000000,26174.58000000,26223.51000000,6281.80491500,167120732.59960956,100435\n1609095600000,2020-12-27 19:00:00,BTC/USDT,27062.67000000,27296.28000000,26987.84000000,27081.67000000,3438.48581500,93334655.90308752,55530\n1609092000000,2020-12-27 18:00:00,BTC/USDT,26803.49000000,27070.01000000,26600.00000000,27061.41000000,4254.59658700,114153292.21421868,64559\n1609088400000,2020-12-27 17:00:00,BTC/USDT,27089.45000000,27089.55000000,26677.00000000,26801.35000000,5321.87878100,143067570.01788433,75469\n1609084800000,2020-12-27 16:00:00,BTC/USDT,27360.09000000,27490.00000000,26600.00000000,27089.44000000,9368.27861800,252535713.31425760,149930\n1609081200000,2020-12-27 15:00:00,BTC/USDT,27334.47000000,27480.00000000,27000.00000000,27359.62000000,5962.26386400,162690522.92772146,96673\n1609077600000,2020-12-27 14:00:00,BTC/USDT,27519.48000000,27777.00000000,27232.45000000,27335.65000000,7144.71835900,196868080.19582196,105504\n1609074000000,2020-12-27 13:00:00,BTC/USDT,27205.47000000,27597.19000000,27060.00000000,27519.48000000,5802.71703300,158588685.73100863,92866\n1609070400000,2020-12-27 12:00:00,BTC/USDT,27647.55000000,27648.75000000,26600.00000000,27205.48000000,12613.48045100,341673812.20124348,192534\n1609066800000,2020-12-27 11:00:00,BTC/USDT,27822.17000000,28422.00000000,26566.00000000,27646.48000000,15858.07547700,441315577.52182234,237960\n1609063200000,2020-12-27 10:00:00,BTC/USDT,27818.18000000,27893.50000000,27687.13000000,27822.16000000,3299.39056900,91697271.11317655,61047\n1609059600000,2020-12-27 09:00:00,BTC/USDT,27460.52000000,27855.00000000,27200.02000000,27818.18000000,6637.46247000,183279646.62111129,112702\n1609056000000,2020-12-27 08:00:00,BTC/USDT,27639.39000000,27935.40000000,27310.00000000,27457.00000000,7892.77938300,219162622.55279621,129016\n1609052400000,2020-12-27 07:00:00,BTC/USDT,27635.28000000,27777.82000000,27453.53000000,27639.38000000,5115.59067700,141360254.56865696,93784\n1609048800000,2020-12-27 06:00:00,BTC/USDT,26889.94000000,27750.00000000,26886.47000000,27635.28000000,9599.11262100,263131263.97930927,141623\n1609045200000,2020-12-27 05:00:00,BTC/USDT,26744.76000000,26939.98000000,26705.13000000,26889.94000000,2630.68005700,70595636.76903177,48811\n1609041600000,2020-12-27 04:00:00,BTC/USDT,26568.24000000,26839.84000000,26567.81000000,26744.76000000,2728.12156700,72926527.16668561,48413\n1609038000000,2020-12-27 03:00:00,BTC/USDT,26573.90000000,26649.50000000,26427.16000000,26568.08000000,2920.83183900,77585482.07073501,60707\n1609034400000,2020-12-27 02:00:00,BTC/USDT,26619.14000000,26859.50000000,26500.00000000,26573.90000000,4905.69398000,131022124.04904550,95570\n1609030800000,2020-12-27 01:00:00,BTC/USDT,26759.35000000,26963.46000000,26503.87000000,26619.73000000,6172.62723500,165304656.10804011,105681\n1609027200000,2020-12-27 00:00:00,BTC/USDT,26493.40000000,26850.00000000,26488.41000000,26759.35000000,5331.96641600,142279124.71116317,88122\n1609023600000,2020-12-26 23:00:00,BTC/USDT,26413.42000000,26660.00000000,26362.26000000,26493.39000000,3419.82500300,90663068.30657589,71548\n1609020000000,2020-12-26 22:00:00,BTC/USDT,26702.18000000,26799.80000000,25842.00000000,26413.43000000,6382.87121600,168416418.60798949,155761\n1609016400000,2020-12-26 21:00:00,BTC/USDT,26417.30000000,26867.03000000,26332.57000000,26702.19000000,10091.24936200,268363789.55314947,146354\n1609012800000,2020-12-26 20:00:00,BTC/USDT,25931.02000000,26485.00000000,25810.56000000,26417.30000000,6943.28946300,181507467.06916705,108711\n1609009200000,2020-12-26 19:00:00,BTC/USDT,25619.24000000,25932.25000000,25590.00000000,25931.02000000,2902.63840200,74809632.17509038,63543\n1609005600000,2020-12-26 18:00:00,BTC/USDT,25789.84000000,25859.00000000,25580.58000000,25619.24000000,2877.80149800,74026503.23165960,60358\n1609002000000,2020-12-26 17:00:00,BTC/USDT,25864.62000000,26000.00000000,25674.87000000,25789.84000000,5621.92202900,145249333.45959934,99767\n1608998400000,2020-12-26 16:00:00,BTC/USDT,25629.99000000,25945.01000000,25575.00000000,25864.61000000,8270.35764100,213062170.81353487,122595\n1608994800000,2020-12-26 15:00:00,BTC/USDT,25381.20000000,25673.52000000,25339.18000000,25629.99000000,6698.53020000,170725174.44705016,100768\n1608991200000,2020-12-26 14:00:00,BTC/USDT,24942.42000000,25434.03000000,24882.55000000,25381.12000000,7468.43089000,188242572.45857522,110067\n1608987600000,2020-12-26 13:00:00,BTC/USDT,24892.06000000,24987.34000000,24809.49000000,24942.42000000,2723.01911200,67824062.49525896,47451\n1608984000000,2020-12-26 12:00:00,BTC/USDT,24834.36000000,24948.70000000,24734.94000000,24892.05000000,2312.46268500,57434088.35180933,45072\n1608980400000,2020-12-26 11:00:00,BTC/USDT,24730.40000000,24887.17000000,24706.16000000,24834.36000000,2180.18523700,54073534.22029464,43186\n1608976800000,2020-12-26 10:00:00,BTC/USDT,24859.36000000,24899.10000000,24562.50000000,24732.06000000,2725.81982700,67440667.03813576,52503\n1608973200000,2020-12-26 09:00:00,BTC/USDT,24901.36000000,24914.81000000,24725.00000000,24859.36000000,2486.71932700,61722654.75345591,46905\n1608969600000,2020-12-26 08:00:00,BTC/USDT,24784.48000000,25000.00000000,24754.16000000,24901.36000000,2493.01992100,62074666.00842795,48116\n1608966000000,2020-12-26 07:00:00,BTC/USDT,24830.66000000,24933.00000000,24715.91000000,24784.48000000,1636.18657800,40617726.09969382,38149\n1608962400000,2020-12-26 06:00:00,BTC/USDT,24708.87000000,24868.98000000,24652.54000000,24830.66000000,1689.13558900,41833717.60525890,33440\n1608958800000,2020-12-26 05:00:00,BTC/USDT,24810.06000000,24843.44000000,24661.30000000,24708.87000000,2783.60872700,68879362.66303206,48636\n1608955200000,2020-12-26 04:00:00,BTC/USDT,24909.80000000,24920.00000000,24745.41000000,24810.06000000,2386.01042100,59260163.47107688,43863\n1608951600000,2020-12-26 03:00:00,BTC/USDT,24838.27000000,25039.96000000,24815.52000000,24909.79000000,3949.92637200,98501567.58551547,67250\n1608948000000,2020-12-26 02:00:00,BTC/USDT,24907.78000000,24976.00000000,24750.00000000,24838.27000000,3438.87458800,85548294.69879413,56328\n1608944400000,2020-12-26 01:00:00,BTC/USDT,24601.07000000,24932.16000000,24500.00000000,24907.78000000,3747.82764300,92775353.15682627,70461\n1608940800000,2020-12-26 00:00:00,BTC/USDT,24712.47000000,24785.81000000,24519.86000000,24602.25000000,2576.80165500,63481076.90560905,54742\n1608937200000,2020-12-25 23:00:00,BTC/USDT,24584.00000000,24789.86000000,24511.58000000,24712.47000000,4213.62783200,103967703.46092959,79727\n1608933600000,2020-12-25 22:00:00,BTC/USDT,24419.60000000,24650.00000000,24368.47000000,24584.00000000,2707.40774300,66431782.43006149,70036\n1608930000000,2020-12-25 21:00:00,BTC/USDT,24455.05000000,24456.90000000,24198.60000000,24419.39000000,1900.13230900,46221364.97153841,38337\n1608926400000,2020-12-25 20:00:00,BTC/USDT,24449.66000000,24490.28000000,24351.47000000,24455.05000000,1937.81447400,47334620.74242955,44527\n1608922800000,2020-12-25 19:00:00,BTC/USDT,24356.31000000,24520.00000000,24294.90000000,24449.66000000,3217.19221600,78557100.57247078,56053\n1608919200000,2020-12-25 18:00:00,BTC/USDT,24185.62000000,24427.64000000,24136.84000000,24356.31000000,2665.28264000,64682616.07339900,48815\n1608915600000,2020-12-25 17:00:00,BTC/USDT,24082.01000000,24265.77000000,23987.16000000,24185.62000000,3000.26387100,72396608.28709903,63561\n1608912000000,2020-12-25 16:00:00,BTC/USDT,24025.13000000,24298.82000000,24003.00000000,24082.01000000,4287.11431300,103477705.37637830,86876\n1608908400000,2020-12-25 15:00:00,BTC/USDT,24026.71000000,24199.93000000,23949.17000000,24025.13000000,4244.36628700,102242226.73786752,78954\n1608904800000,2020-12-25 14:00:00,BTC/USDT,24499.99000000,24520.00000000,23785.50000000,24026.72000000,5976.21056900,144467865.50956871,100197\n1608901200000,2020-12-25 13:00:00,BTC/USDT,24346.97000000,24550.00000000,24330.00000000,24500.00000000,3177.62976600,77630717.21985330,55696\n1608897600000,2020-12-25 12:00:00,BTC/USDT,24574.40000000,24642.34000000,24337.26000000,24346.97000000,4824.53833600,118190726.28544810,77534\n1608894000000,2020-12-25 11:00:00,BTC/USDT,23977.42000000,24681.00000000,23902.43000000,24574.40000000,11279.80526600,274873232.73748018,171284\n1608890400000,2020-12-25 10:00:00,BTC/USDT,23934.52000000,24032.00000000,23766.66000000,23975.26000000,5330.76605400,127659713.00911661,94821\n1608886800000,2020-12-25 09:00:00,BTC/USDT,23677.44000000,23982.82000000,23643.96000000,23934.52000000,4235.27263600,101061989.39525969,74328\n1608883200000,2020-12-25 08:00:00,BTC/USDT,23650.99000000,23870.00000000,23600.00000000,23677.44000000,4010.21034100,95271705.41748297,67991\n1608879600000,2020-12-25 07:00:00,BTC/USDT,23625.47000000,23679.99000000,23510.72000000,23649.82000000,2172.21983000,51244845.06172451,46269\n1608876000000,2020-12-25 06:00:00,BTC/USDT,23599.22000000,23681.19000000,23546.33000000,23625.46000000,1644.88400000,38857225.54553616,42700\n1608872400000,2020-12-25 05:00:00,BTC/USDT,23612.77000000,23649.00000000,23526.67000000,23599.22000000,1384.65885300,32653125.98340991,38201\n1608868800000,2020-12-25 04:00:00,BTC/USDT,23481.20000000,23617.64000000,23451.79000000,23612.78000000,1083.63712500,25516072.99558341,30280\n1608865200000,2020-12-25 03:00:00,BTC/USDT,23597.20000000,23597.20000000,23433.60000000,23481.21000000,1495.99091000,35166545.92882347,40605\n1608858000000,2020-12-25 01:00:00,BTC/USDT,23622.71000000,23654.83000000,23532.54000000,23597.21000000,1487.50060200,35112169.05267915,34061\n1608854400000,2020-12-25 00:00:00,BTC/USDT,23728.99000000,23827.54000000,23560.00000000,23622.66000000,3243.41759600,76748653.87359692,63573\n1608850800000,2020-12-24 23:00:00,BTC/USDT,23707.85000000,23794.43000000,23615.00000000,23729.20000000,5023.15762700,119091536.00734061,107063\n1608847200000,2020-12-24 22:00:00,BTC/USDT,23413.51000000,23750.00000000,23385.98000000,23711.00000000,3315.70377400,78319963.32819762,97184\n1608843600000,2020-12-24 21:00:00,BTC/USDT,23526.22000000,23563.54000000,23366.84000000,23413.51000000,2509.49612400,58931274.15208843,49051\n1608840000000,2020-12-24 20:00:00,BTC/USDT,23287.16000000,23560.00000000,23258.35000000,23525.42000000,2155.70929100,50481664.97753635,40928\n1608836400000,2020-12-24 19:00:00,BTC/USDT,23314.23000000,23404.39000000,23250.18000000,23287.15000000,1169.60234000,27293975.72897554,28597\n1608832800000,2020-12-24 18:00:00,BTC/USDT,23377.27000000,23419.57000000,23280.00000000,23313.35000000,1288.93706800,30087895.26303434,39290\n1608829200000,2020-12-24 17:00:00,BTC/USDT,23219.76000000,23460.97000000,23160.73000000,23377.27000000,2340.01541700,54576251.38070625,52591\n1608825600000,2020-12-24 16:00:00,BTC/USDT,23232.12000000,23345.00000000,23108.01000000,23219.76000000,2635.49636200,61272510.36651802,54459\n1608822000000,2020-12-24 15:00:00,BTC/USDT,23215.21000000,23295.77000000,23100.00000000,23232.12000000,2439.51974800,56603704.26620897,48851\n1608818400000,2020-12-24 14:00:00,BTC/USDT,23379.02000000,23425.95000000,23154.39000000,23215.21000000,2550.44044400,59471961.76088227,53080\n1608814800000,2020-12-24 13:00:00,BTC/USDT,23203.69000000,23391.02000000,23130.00000000,23379.65000000,2654.02338600,61750485.86901670,66749\n1608811200000,2020-12-24 12:00:00,BTC/USDT,23218.67000000,23296.05000000,23012.29000000,23200.00000000,2611.85592200,60454248.99361880,50537\n1608807600000,2020-12-24 11:00:00,BTC/USDT,23122.51000000,23294.16000000,23049.95000000,23218.68000000,2376.62614200,55093229.37496877,51003\n1608804000000,2020-12-24 10:00:00,BTC/USDT,23348.29000000,23357.00000000,23115.00000000,23123.30000000,2600.89180400,60403052.45436357,53265\n1608800400000,2020-12-24 09:00:00,BTC/USDT,23447.42000000,23469.86000000,23261.08000000,23348.30000000,2607.47171700,60840711.60440505,58459\n1608796800000,2020-12-24 08:00:00,BTC/USDT,23173.38000000,23479.26000000,23070.00000000,23447.42000000,3878.41449100,90414279.11370998,60340\n1608793200000,2020-12-24 07:00:00,BTC/USDT,22931.71000000,23219.38000000,22823.99000000,23173.38000000,2670.38338500,61554195.80934520,54654\n1608789600000,2020-12-24 06:00:00,BTC/USDT,23049.97000000,23141.15000000,22868.00000000,22931.71000000,2500.61942200,57435076.56828491,54511\n1608786000000,2020-12-24 05:00:00,BTC/USDT,23023.32000000,23136.48000000,22914.20000000,23049.98000000,2197.87578700,50650813.05317880,46508\n1608782400000,2020-12-24 04:00:00,BTC/USDT,22894.28000000,23043.98000000,22786.67000000,23023.32000000,2797.23445700,64099762.88555705,51105\n1608778800000,2020-12-24 03:00:00,BTC/USDT,22805.26000000,22968.40000000,22703.42000000,22894.28000000,3362.08528000,76770993.93759980,61459\n1608775200000,2020-12-24 02:00:00,BTC/USDT,23202.71000000,23229.98000000,22738.95000000,22805.90000000,3829.75557500,88053956.03720641,73753\n1608771600000,2020-12-24 01:00:00,BTC/USDT,23067.40000000,23229.95000000,22880.00000000,23202.71000000,4179.54421200,96418833.76050658,74809\n1608768000000,2020-12-24 00:00:00,BTC/USDT,23232.39000000,23242.04000000,22800.00000000,23067.41000000,5318.97447700,122323867.38352157,93011\n1608764400000,2020-12-23 23:00:00,BTC/USDT,23290.96000000,23470.05000000,23164.09000000,23232.76000000,5132.94079700,119756872.25012832,94679\n1608760800000,2020-12-23 22:00:00,BTC/USDT,23315.38000000,23379.88000000,22600.00000000,23291.91000000,8071.66895300,186067786.67627288,193120\n1608757200000,2020-12-23 21:00:00,BTC/USDT,23501.99000000,23527.44000000,23153.79000000,23315.39000000,4759.54548100,111100154.30835347,90759\n1608753600000,2020-12-23 20:00:00,BTC/USDT,23556.91000000,23625.00000000,23423.01000000,23501.99000000,3131.77061600,73666169.27507298,67217\n1608750000000,2020-12-23 19:00:00,BTC/USDT,23605.08000000,23622.67000000,23500.00000000,23556.91000000,2534.24089600,59705264.35366028,44086\n1608746400000,2020-12-23 18:00:00,BTC/USDT,23589.38000000,23678.49000000,23550.00000000,23605.07000000,2974.01452400,70210945.76273241,58491\n1608742800000,2020-12-23 17:00:00,BTC/USDT,23406.91000000,23640.63000000,23350.00000000,23589.39000000,3986.41393200,93872305.19557635,72864\n1608739200000,2020-12-23 16:00:00,BTC/USDT,23449.66000000,23651.82000000,23379.73000000,23406.91000000,6003.30843500,141247167.86603048,115128\n1608735600000,2020-12-23 15:00:00,BTC/USDT,23759.68000000,23895.85000000,23336.00000000,23449.66000000,5341.81718600,126292167.80868594,104296\n1608732000000,2020-12-23 14:00:00,BTC/USDT,23617.34000000,23829.09000000,23536.25000000,23759.67000000,5940.74349900,140727169.05970245,109410\n1608728400000,2020-12-23 13:00:00,BTC/USDT,23843.91000000,24100.00000000,23457.98000000,23617.34000000,10187.77729200,242460450.83114575,144758\n1608724800000,2020-12-23 12:00:00,BTC/USDT,23064.01000000,23850.00000000,22931.00000000,23843.91000000,9711.03807200,227281958.78449733,146187\n1608721200000,2020-12-23 11:00:00,BTC/USDT,23435.95000000,23478.81000000,22810.00000000,23064.01000000,10580.08710500,244222868.06815201,166191\n1608717600000,2020-12-23 10:00:00,BTC/USDT,23635.88000000,23735.51000000,23400.00000000,23435.95000000,5035.15842900,118691938.36523702,91470\n1608714000000,2020-12-23 09:00:00,BTC/USDT,23502.80000000,23661.53000000,23422.40000000,23635.87000000,3191.34552200,75137232.53579277,65465\n1608710400000,2020-12-23 08:00:00,BTC/USDT,23555.54000000,23680.00000000,23438.80000000,23502.80000000,2863.18039200,67481340.98563665,64540\n1608706800000,2020-12-23 07:00:00,BTC/USDT,23501.97000000,23636.33000000,23319.11000000,23555.73000000,3238.34513900,76061844.88388973,81126\n1608703200000,2020-12-23 06:00:00,BTC/USDT,23533.01000000,23613.03000000,23439.13000000,23501.97000000,3316.47708800,78016964.28521532,78172\n1608699600000,2020-12-23 05:00:00,BTC/USDT,23450.01000000,23536.20000000,23350.00000000,23533.01000000,4934.99452900,115747295.91425825,98606\n1608696000000,2020-12-23 04:00:00,BTC/USDT,23706.72000000,23711.17000000,23430.00000000,23450.00000000,4014.52581900,94572419.49244190,73235\n1608692400000,2020-12-23 03:00:00,BTC/USDT,23631.76000000,23778.63000000,23605.00000000,23706.72000000,2051.52228600,48629005.22540457,40550\n1608688800000,2020-12-23 02:00:00,BTC/USDT,23709.59000000,23746.33000000,23551.86000000,23631.99000000,2813.52130400,66548107.70668010,52608\n1608685200000,2020-12-23 01:00:00,BTC/USDT,23987.80000000,24050.00000000,23654.72000000,23709.59000000,4774.71169700,113879313.55290823,75975\n1608681600000,2020-12-23 00:00:00,BTC/USDT,23810.79000000,24000.00000000,23695.70000000,23987.80000000,4458.11074000,106311263.88440410,65678\n1608678000000,2020-12-22 23:00:00,BTC/USDT,23721.95000000,23837.10000000,23640.56000000,23810.79000000,4087.04394400,97000097.59047707,71437\n1608674400000,2020-12-22 22:00:00,BTC/USDT,23435.14000000,23760.00000000,23344.92000000,23721.94000000,2793.98430400,65784584.27899137,70150\n1608670800000,2020-12-22 21:00:00,BTC/USDT,23423.76000000,23530.00000000,23323.54000000,23435.13000000,3634.86268000,85232988.42504260,60466\n1608667200000,2020-12-22 20:00:00,BTC/USDT,23404.27000000,23449.00000000,23277.72000000,23423.77000000,2146.82219600,50179738.35311173,44892\n1608663600000,2020-12-22 19:00:00,BTC/USDT,23435.27000000,23509.87000000,23325.95000000,23404.27000000,2140.63387200,50170220.05776768,41941\n1608660000000,2020-12-22 18:00:00,BTC/USDT,23342.54000000,23520.00000000,23342.51000000,23435.27000000,2784.59204900,65276295.81515566,42754\n1608656400000,2020-12-22 17:00:00,BTC/USDT,23348.43000000,23442.00000000,23224.40000000,23342.54000000,2756.85930200,64354695.94987165,43936\n1608652800000,2020-12-22 16:00:00,BTC/USDT,23342.68000000,23456.00000000,23237.00000000,23348.95000000,4298.90255500,100376537.63554489,71396\n1608649200000,2020-12-22 15:00:00,BTC/USDT,23439.99000000,23600.00000000,23300.42000000,23342.58000000,4452.75789000,104613265.12053512,60819\n1608645600000,2020-12-22 14:00:00,BTC/USDT,23487.20000000,23628.89000000,23335.83000000,23439.99000000,6009.58313200,141269971.90051568,73518\n1608642000000,2020-12-22 13:00:00,BTC/USDT,23175.57000000,23536.96000000,23103.98000000,23487.20000000,4972.84068000,115944084.98536786,74209\n1608638400000,2020-12-22 12:00:00,BTC/USDT,23119.47000000,23300.00000000,23062.01000000,23175.57000000,4777.70466000,110674551.72227436,75474\n1608634800000,2020-12-22 11:00:00,BTC/USDT,22701.20000000,23155.00000000,22701.20000000,23119.48000000,4983.54095200,114380957.88618727,78403\n1608631200000,2020-12-22 10:00:00,BTC/USDT,22782.92000000,22819.76000000,22602.12000000,22701.20000000,2835.32679800,64432306.62944577,56764\n1608627600000,2020-12-22 09:00:00,BTC/USDT,22689.87000000,22822.83000000,22600.00000000,22783.85000000,3441.12202100,78193041.49831134,59923\n1608624000000,2020-12-22 08:00:00,BTC/USDT,22655.82000000,22750.00000000,22353.40000000,22689.86000000,4786.23803600,107958892.96084408,84822\n1608620400000,2020-12-22 07:00:00,BTC/USDT,22750.85000000,22826.78000000,22500.00000000,22655.83000000,3555.11835700,80622823.01736425,61246\n1608616800000,2020-12-22 06:00:00,BTC/USDT,22681.50000000,22870.02000000,22551.02000000,22750.96000000,3044.63190700,69233802.69277815,57987\n1608613200000,2020-12-22 05:00:00,BTC/USDT,22951.30000000,22970.00000000,22610.19000000,22681.51000000,3600.42597900,81960727.60418122,67428\n1608609600000,2020-12-22 04:00:00,BTC/USDT,22851.19000000,23076.77000000,22851.19000000,22951.30000000,2681.87136200,61642796.35306549,49339\n1608606000000,2020-12-22 03:00:00,BTC/USDT,22957.58000000,22969.00000000,22737.90000000,22851.20000000,2438.40007900,55707127.59884953,41475\n1608602400000,2020-12-22 02:00:00,BTC/USDT,22753.99000000,22970.00000000,22730.00000000,22957.57000000,2524.45364100,57732092.56455595,48080\n1608598800000,2020-12-22 01:00:00,BTC/USDT,22558.41000000,22875.61000000,22428.86000000,22753.99000000,3850.00338400,87233778.26758052,78537\n1608595200000,2020-12-22 00:00:00,BTC/USDT,22719.88000000,22926.14000000,22500.00000000,22558.42000000,4435.40638000,100783241.33570758,81281\n1608591600000,2020-12-21 23:00:00,BTC/USDT,23169.88000000,23228.35000000,22699.99000000,22719.71000000,3712.99715100,85240121.39147504,65315\n1608588000000,2020-12-21 22:00:00,BTC/USDT,23127.38000000,23254.33000000,23021.17000000,23170.89000000,2084.95169600,48281453.64763994,48307\n1608584400000,2020-12-21 21:00:00,BTC/USDT,22829.79000000,23162.26000000,22765.00000000,23127.37000000,3028.12497300,69626419.82288961,50843\n1608580800000,2020-12-21 20:00:00,BTC/USDT,22816.63000000,22930.00000000,22732.78000000,22829.79000000,1900.46394400,43415707.60438296,36394\n1608577200000,2020-12-21 19:00:00,BTC/USDT,22813.65000000,22940.30000000,22681.32000000,22816.62000000,2874.57823600,65586007.26860901,48995\n1608573600000,2020-12-21 18:00:00,BTC/USDT,22693.65000000,22988.60000000,22621.44000000,22813.66000000,4796.30654600,109361847.50863127,81912\n1608559200000,2020-12-21 14:00:00,BTC/USDT,22646.53000000,22646.53000000,22646.53000000,22646.53000000,0.00000000,0.00000000,0\n1608555600000,2020-12-21 13:00:00,BTC/USDT,22307.50000000,22665.35000000,22251.23000000,22646.53000000,2830.34558700,63790349.91077578,44795\n1608552000000,2020-12-21 12:00:00,BTC/USDT,22646.70000000,22663.00000000,21815.00000000,22307.50000000,11239.20192200,250454720.41287574,162588\n1608548400000,2020-12-21 11:00:00,BTC/USDT,22446.39000000,22833.01000000,22350.00000000,22645.85000000,7582.69498000,172025555.18477164,125474\n1608544800000,2020-12-21 10:00:00,BTC/USDT,23460.93000000,23495.91000000,22441.01000000,22445.99000000,13511.91035700,309208137.98627443,195332\n1608541200000,2020-12-21 09:00:00,BTC/USDT,23659.59000000,23739.18000000,23328.00000000,23461.35000000,6435.52119700,151548561.33304380,106360\n1608537600000,2020-12-21 08:00:00,BTC/USDT,23979.99000000,24075.94000000,23635.08000000,23659.59000000,4670.53785200,111308617.50457337,101962\n1608534000000,2020-12-21 07:00:00,BTC/USDT,23921.74000000,24028.15000000,23888.15000000,23980.00000000,3110.57338900,74540028.66952485,61586\n1608530400000,2020-12-21 06:00:00,BTC/USDT,23909.83000000,23926.80000000,23700.00000000,23921.73000000,2593.61124400,61768427.12990675,63867\n1608526800000,2020-12-21 05:00:00,BTC/USDT,23895.02000000,23975.00000000,23841.99000000,23909.83000000,2315.21212700,55356498.17189838,60219\n1608523200000,2020-12-21 04:00:00,BTC/USDT,23945.30000000,24102.77000000,23790.00000000,23895.73000000,3537.79405600,84773164.17079207,75894\n1608519600000,2020-12-21 03:00:00,BTC/USDT,23856.39000000,24016.93000000,23657.50000000,23945.29000000,3842.13422800,91783164.31014855,72832\n1608516000000,2020-12-21 02:00:00,BTC/USDT,23663.49000000,23887.00000000,23652.48000000,23856.38000000,2136.22237400,50715771.44095200,50840\n1608512400000,2020-12-21 01:00:00,BTC/USDT,23679.55000000,23744.86000000,23587.85000000,23663.48000000,1823.64618400,43167000.14606068,49838\n1608508800000,2020-12-21 00:00:00,BTC/USDT,23455.54000000,23709.31000000,23287.94000000,23679.55000000,3148.36038200,74104376.40894017,72737\n1608505200000,2020-12-20 23:00:00,BTC/USDT,23507.03000000,23594.73000000,23450.00000000,23455.52000000,2197.79247000,51686700.16396077,51505\n1608501600000,2020-12-20 22:00:00,BTC/USDT,23376.94000000,23614.36000000,23090.00000000,23507.79000000,5563.84728100,130200677.90405338,141921\n1608498000000,2020-12-20 21:00:00,BTC/USDT,24172.99000000,24208.62000000,23350.00000000,23373.05000000,7510.99845200,178225487.51600966,130742\n1608494400000,2020-12-20 20:00:00,BTC/USDT,23928.80000000,24295.00000000,23850.18000000,24172.25000000,6396.94464800,154278022.43876312,108441\n1608490800000,2020-12-20 19:00:00,BTC/USDT,23866.68000000,23995.97000000,23780.83000000,23930.00000000,2727.20596000,65128094.41349540,58479\n1608487200000,2020-12-20 18:00:00,BTC/USDT,23722.62000000,23877.00000000,23655.26000000,23866.69000000,2433.33458100,57858507.07633707,57666\n1608483600000,2020-12-20 17:00:00,BTC/USDT,23630.10000000,23800.00000000,23625.41000000,23722.62000000,2050.91755400,48695467.84444800,53390\n1608480000000,2020-12-20 16:00:00,BTC/USDT,23868.08000000,23901.01000000,23628.31000000,23630.10000000,3492.54283800,83064228.90249011,88409\n1608476400000,2020-12-20 15:00:00,BTC/USDT,23537.70000000,23910.00000000,23527.48000000,23868.09000000,4012.70827000,95267938.76710545,85561\n1608472800000,2020-12-20 14:00:00,BTC/USDT,23561.36000000,23682.00000000,23500.19000000,23537.70000000,2538.18035500,59832598.96752928,63644\n1608469200000,2020-12-20 13:00:00,BTC/USDT,23472.45000000,23590.90000000,23296.00000000,23561.36000000,3044.96100200,71369805.98272658,88523\n1608465600000,2020-12-20 12:00:00,BTC/USDT,23553.02000000,23588.71000000,23333.57000000,23472.44000000,2466.12724100,57813278.90876295,75457\n1608462000000,2020-12-20 11:00:00,BTC/USDT,23394.77000000,23625.00000000,23393.00000000,23553.02000000,1945.51786500,45714177.75601246,44295\n1608458400000,2020-12-20 10:00:00,BTC/USDT,23592.93000000,23648.92000000,23358.78000000,23394.76000000,3238.11275400,76062584.71504504,68381\n1608454800000,2020-12-20 09:00:00,BTC/USDT,23698.49000000,23748.40000000,23503.00000000,23592.92000000,2928.14121700,69174540.89233161,68082\n1608451200000,2020-12-20 08:00:00,BTC/USDT,23628.89000000,23791.00000000,23532.00000000,23698.49000000,2953.11334600,69942074.29276992,73961\n1608447600000,2020-12-20 07:00:00,BTC/USDT,23506.67000000,23646.31000000,23410.62000000,23628.88000000,2081.73403800,48977319.07970316,62654\n1608444000000,2020-12-20 06:00:00,BTC/USDT,23481.38000000,23614.84000000,23459.98000000,23506.67000000,1518.98280900,35762985.19490750,37908\n1608440400000,2020-12-20 05:00:00,BTC/USDT,23426.15000000,23588.88000000,23397.58000000,23481.38000000,2113.93787400,49648191.58306973,53921\n1608436800000,2020-12-20 04:00:00,BTC/USDT,23346.25000000,23452.63000000,23060.00000000,23426.54000000,2789.30374700,64876630.72539634,65217\n1608433200000,2020-12-20 03:00:00,BTC/USDT,23429.92000000,23429.92000000,23180.88000000,23346.48000000,2578.31543400,60089074.64762119,53082\n1608429600000,2020-12-20 02:00:00,BTC/USDT,23486.42000000,23542.99000000,23300.00000000,23429.92000000,2007.60910400,47031741.40981669,56355\n1608426000000,2020-12-20 01:00:00,BTC/USDT,23483.11000000,23548.72000000,23390.00000000,23485.56000000,2118.50392700,49722149.09068766,47539\n1608422400000,2020-12-20 00:00:00,BTC/USDT,23821.60000000,23836.48000000,23230.00000000,23481.41000000,5981.31291800,140439796.45726494,108092\n1608418800000,2020-12-19 23:00:00,BTC/USDT,23905.73000000,23915.26000000,23719.25000000,23821.61000000,1987.77768300,47348481.93469174,54871\n1608415200000,2020-12-19 22:00:00,BTC/USDT,23974.70000000,23999.00000000,23825.95000000,23905.73000000,1543.40332600,36899496.88856428,58376\n1608411600000,2020-12-19 21:00:00,BTC/USDT,23902.19000000,24065.41000000,23780.21000000,23974.71000000,3253.89786500,77885971.41909300,58395\n1608408000000,2020-12-19 20:00:00,BTC/USDT,23791.82000000,23937.00000000,23782.91000000,23902.17000000,2634.92120700,62869475.28398441,52915\n1608404400000,2020-12-19 19:00:00,BTC/USDT,23822.66000000,23883.79000000,23651.00000000,23791.91000000,3011.08813300,71594947.93610508,59305\n1608400800000,2020-12-19 18:00:00,BTC/USDT,23886.71000000,23906.66000000,23556.76000000,23822.66000000,4060.08167600,96467369.32701857,85956\n1608397200000,2020-12-19 17:00:00,BTC/USDT,23966.48000000,24100.00000000,23680.00000000,23886.44000000,6294.41307900,150649476.47524402,115671\n1608393600000,2020-12-19 16:00:00,BTC/USDT,23552.00000000,24171.47000000,23456.55000000,23966.48000000,13881.90769400,331707316.13834668,190491\n1608390000000,2020-12-19 15:00:00,BTC/USDT,23296.96000000,23650.00000000,23296.95000000,23552.01000000,6178.38426500,145197062.27630502,115283\n1608386400000,2020-12-19 14:00:00,BTC/USDT,23172.75000000,23627.99000000,23052.00000000,23296.96000000,10267.11671400,240147219.65624463,162737\n1608382800000,2020-12-19 13:00:00,BTC/USDT,23041.53000000,23189.10000000,22992.23000000,23172.74000000,3234.58667200,74695261.30654980,71960\n1608379200000,2020-12-19 12:00:00,BTC/USDT,22888.53000000,23127.72000000,22886.79000000,23041.53000000,3218.56833600,74164924.52129787,68468\n1608375600000,2020-12-19 11:00:00,BTC/USDT,23020.00000000,23063.49000000,22875.01000000,22888.54000000,1942.20473600,44613867.51974666,50293\n1608372000000,2020-12-19 10:00:00,BTC/USDT,22973.06000000,23080.45000000,22950.00000000,23019.99000000,2331.99705600,53679672.52752191,51465\n1608368400000,2020-12-19 09:00:00,BTC/USDT,22983.77000000,23045.49000000,22928.05000000,22973.06000000,2229.92546900,51261037.96077831,46816\n1608364800000,2020-12-19 08:00:00,BTC/USDT,22853.75000000,22990.00000000,22750.00000000,22983.77000000,3048.66552400,69730044.00219995,60398\n1608361200000,2020-12-19 07:00:00,BTC/USDT,22853.51000000,23038.00000000,22832.00000000,22853.75000000,1791.21295700,41091629.41332006,41047\n1608357600000,2020-12-19 06:00:00,BTC/USDT,22958.48000000,23010.60000000,22821.00000000,22853.50000000,1813.01520200,41568273.33781265,40742\n1608354000000,2020-12-19 05:00:00,BTC/USDT,23043.41000000,23075.36000000,22924.79000000,22958.00000000,2023.00009100,46527188.61342074,46498\n1608350400000,2020-12-19 04:00:00,BTC/USDT,23098.05000000,23138.00000000,23032.00000000,23043.40000000,1859.42739300,42908185.20709451,45535\n1608346800000,2020-12-19 03:00:00,BTC/USDT,23219.51000000,23225.00000000,23093.93000000,23098.04000000,2329.27850400,53949354.93298558,49847\n1608343200000,2020-12-19 02:00:00,BTC/USDT,23046.75000000,23220.00000000,23034.75000000,23220.00000000,2910.22358600,67324850.33499496,57625\n1608339600000,2020-12-19 01:00:00,BTC/USDT,22954.02000000,23099.00000000,22902.10000000,23046.76000000,2149.00163800,49439278.50496229,44953\n1608336000000,2020-12-19 00:00:00,BTC/USDT,23107.39000000,23168.28000000,22940.00000000,22954.02000000,2050.96587100,47249493.36858485,48964\n1608332400000,2020-12-18 23:00:00,BTC/USDT,23011.38000000,23143.56000000,22908.45000000,23107.39000000,2542.01135600,58500235.20500242,49084\n1608328800000,2020-12-18 22:00:00,BTC/USDT,22880.07000000,23034.33000000,22800.00000000,23011.38000000,1992.22370300,45714601.55946399,57090\n1608325200000,2020-12-18 21:00:00,BTC/USDT,22755.25000000,23078.47000000,22751.10000000,22880.07000000,3998.59848000,91768270.36360478,78499\n1608321600000,2020-12-18 20:00:00,BTC/USDT,22762.28000000,22795.11000000,22650.29000000,22755.66000000,1760.42501100,40037220.86694732,45696\n1608318000000,2020-12-18 19:00:00,BTC/USDT,22781.44000000,22783.42000000,22626.81000000,22762.28000000,1880.94478000,42718034.97893466,47360\n1608314400000,2020-12-18 18:00:00,BTC/USDT,22749.32000000,22829.04000000,22694.22000000,22781.44000000,2092.86434000,47641275.63383724,52438\n1608310800000,2020-12-18 17:00:00,BTC/USDT,22719.29000000,22818.00000000,22670.22000000,22749.32000000,2476.34500200,56322774.53820737,49498\n1608307200000,2020-12-18 16:00:00,BTC/USDT,22549.00000000,22752.77000000,22463.59000000,22719.28000000,3562.52109600,80614893.09123505,80408\n1608303600000,2020-12-18 15:00:00,BTC/USDT,22610.65000000,22636.27000000,22350.00000000,22549.00000000,4804.64329100,107999695.31970757,87934\n1608300000000,2020-12-18 14:00:00,BTC/USDT,22571.79000000,22758.44000000,22400.00000000,22610.65000000,5664.07888300,127862011.28262873,94656\n1608296400000,2020-12-18 13:00:00,BTC/USDT,22939.41000000,22948.64000000,22548.99000000,22571.78000000,4037.63749500,91913028.46977337,63274\n1608292800000,2020-12-18 12:00:00,BTC/USDT,22886.18000000,23073.25000000,22727.00000000,22939.42000000,3427.45401700,78571974.61252028,64006\n1608289200000,2020-12-18 11:00:00,BTC/USDT,22964.12000000,22967.29000000,22691.61000000,22886.17000000,4048.00235000,92488540.83299563,78147\n1608285600000,2020-12-18 10:00:00,BTC/USDT,23212.82000000,23220.00000000,22933.07000000,22964.67000000,3730.39076900,85938480.84834245,72396\n1608282000000,2020-12-18 09:00:00,BTC/USDT,23114.84000000,23215.00000000,23017.97000000,23212.82000000,2878.47211900,66553659.49187327,62276\n1608278400000,2020-12-18 08:00:00,BTC/USDT,23055.98000000,23285.18000000,22938.87000000,23114.84000000,4699.90906000,108671219.46746846,88611\n1608274800000,2020-12-18 07:00:00,BTC/USDT,22992.06000000,23069.44000000,22838.19000000,23055.98000000,3071.63824500,70520563.00191792,69207\n1608271200000,2020-12-18 06:00:00,BTC/USDT,22955.51000000,23168.59000000,22874.69000000,22994.49000000,3086.57133300,71117617.78184181,56450\n1608267600000,2020-12-18 05:00:00,BTC/USDT,22870.92000000,23028.13000000,22835.02000000,22955.51000000,2184.78142100,50180339.51345186,52363\n1608264000000,2020-12-18 04:00:00,BTC/USDT,22811.58000000,22973.92000000,22772.05000000,22870.93000000,2602.41934800,59562943.31460123,53747\n1608260400000,2020-12-18 03:00:00,BTC/USDT,23003.31000000,23047.88000000,22762.05000000,22811.59000000,3180.18840900,72827291.51132356,61865\n1608256800000,2020-12-18 02:00:00,BTC/USDT,22988.21000000,23248.99000000,22937.90000000,23003.31000000,3844.94547200,88898649.71880710,76920\n1608253200000,2020-12-18 01:00:00,BTC/USDT,22764.77000000,23146.95000000,22634.11000000,22988.21000000,4155.08239500,95448152.68509470,87359\n1608249600000,2020-12-18 00:00:00,BTC/USDT,22797.15000000,22842.76000000,22470.35000000,22764.77000000,3923.98594000,89003887.20501684,84491\n1608246000000,2020-12-17 23:00:00,BTC/USDT,22963.05000000,23000.00000000,22570.59000000,22797.16000000,4979.48947200,113338130.72811062,80408\n1608242400000,2020-12-17 22:00:00,BTC/USDT,22785.93000000,23080.00000000,22757.52000000,22963.04000000,3385.20890000,77573572.48006282,88848\n1608238800000,2020-12-17 21:00:00,BTC/USDT,22791.78000000,22848.39000000,22382.70000000,22791.96000000,5412.10416300,122521140.69933043,116003\n1608235200000,2020-12-17 20:00:00,BTC/USDT,22898.47000000,23106.34000000,22311.10000000,22791.55000000,8469.16815300,192325373.08010062,151845\n1608231600000,2020-12-17 19:00:00,BTC/USDT,23251.16000000,23497.00000000,22804.22000000,22899.08000000,7447.20347200,172681265.34815811,129376\n1608228000000,2020-12-17 18:00:00,BTC/USDT,23023.98000000,23280.10000000,22500.00000000,23250.27000000,11618.26363500,266806234.08882052,170388\n1608224400000,2020-12-17 17:00:00,BTC/USDT,23592.20000000,23699.70000000,23000.00000000,23023.98000000,6924.83407800,162607854.97725225,108481\n1608220800000,2020-12-17 16:00:00,BTC/USDT,23333.81000000,23650.00000000,23200.00000000,23591.23000000,10032.33646400,235661815.69205281,143842\n1608217200000,2020-12-17 15:00:00,BTC/USDT,23086.00000000,23369.00000000,22900.00000000,23333.80000000,6953.46350400,160774302.33556896,100106\n1608213600000,2020-12-17 14:00:00,BTC/USDT,22832.73000000,23257.90000000,22715.38000000,23086.01000000,7463.21496100,172093854.83140895,105833\n1608210000000,2020-12-17 13:00:00,BTC/USDT,23150.00000000,23348.00000000,22647.51000000,22831.84000000,7791.38971600,180104577.16137650,107614\n1608206400000,2020-12-17 12:00:00,BTC/USDT,22752.15000000,23199.00000000,22600.00000000,23149.99000000,6628.22588200,151430103.59404619,86553\n1608202800000,2020-12-17 11:00:00,BTC/USDT,22617.73000000,22808.56000000,22528.73000000,22752.16000000,5168.28526100,117196914.49425187,97875\n1608199200000,2020-12-17 10:00:00,BTC/USDT,22648.86000000,22934.00000000,22380.79000000,22618.11000000,11643.52976700,264117217.79831429,163041\n1608195600000,2020-12-17 09:00:00,BTC/USDT,22904.70000000,23800.00000000,21801.00000000,22650.00000000,23832.91859000,546033142.97530242,300274\n1608192000000,2020-12-17 08:00:00,BTC/USDT,22478.75000000,22990.00000000,22400.00000000,22904.70000000,12107.70688600,275148837.39432177,167507\n1608188400000,2020-12-17 07:00:00,BTC/USDT,22172.72000000,22488.00000000,22102.00000000,22478.76000000,5356.55809400,119575162.55919007,75564\n1608184800000,2020-12-17 06:00:00,BTC/USDT,22280.00000000,22400.00000000,22053.00000000,22172.72000000,6338.23132700,140578100.59207810,91338\n1608181200000,2020-12-17 05:00:00,BTC/USDT,21785.87000000,22311.38000000,21781.99000000,22280.00000000,5326.44149600,117413508.66455225,68690\n1608177600000,2020-12-17 04:00:00,BTC/USDT,21752.65000000,21900.00000000,21735.09000000,21785.88000000,3477.94185800,75898436.69306285,54502\n1608174000000,2020-12-17 03:00:00,BTC/USDT,21913.91000000,22166.00000000,21703.67000000,21753.26000000,7372.38806300,161557498.95194300,117239\n1608170400000,2020-12-17 02:00:00,BTC/USDT,21719.77000000,21994.00000000,21642.13000000,21913.90000000,5864.76053300,128094207.24620305,92091\n1608166800000,2020-12-17 01:00:00,BTC/USDT,21389.26000000,21860.05000000,21389.26000000,21719.22000000,6860.92777900,148684321.41410739,100935\n1608163200000,2020-12-17 00:00:00,BTC/USDT,21335.52000000,21400.00000000,21230.00000000,21389.25000000,4427.88469400,94429993.62626147,76731\n1608159600000,2020-12-16 23:00:00,BTC/USDT,21366.02000000,21560.00000000,21200.00000000,21335.52000000,6953.05725100,148511352.68982300,85781\n1608156000000,2020-12-16 22:00:00,BTC/USDT,21191.53000000,21444.44000000,21172.79000000,21366.42000000,6275.22039300,133749577.33862382,104867\n1608152400000,2020-12-16 21:00:00,BTC/USDT,20802.82000000,21288.00000000,20711.00000000,21192.78000000,7677.80835400,161288020.26549661,90718\n1608148800000,2020-12-16 20:00:00,BTC/USDT,20736.87000000,20839.00000000,20727.30000000,20802.82000000,3210.85421800,66736369.66633460,44368\n1608145200000,2020-12-16 19:00:00,BTC/USDT,20585.79000000,20766.39000000,20550.00000000,20736.87000000,2693.66058200,55675610.69269079,35670\n1608141600000,2020-12-16 18:00:00,BTC/USDT,20639.82000000,20737.44000000,20550.00000000,20585.79000000,3487.83957700,72038763.03364535,42341\n1608138000000,2020-12-16 17:00:00,BTC/USDT,20854.56000000,20855.00000000,20573.82000000,20639.82000000,5243.12347400,108518480.88164673,75219\n1608134400000,2020-12-16 16:00:00,BTC/USDT,20661.37000000,20865.43000000,20620.00000000,20854.56000000,7391.87964200,153319227.94977000,109819\n1608130800000,2020-12-16 15:00:00,BTC/USDT,20650.01000000,20733.00000000,20539.00000000,20661.37000000,7170.61437900,147982438.42032998,104458\n1608127200000,2020-12-16 14:00:00,BTC/USDT,20320.85000000,20799.00000000,20206.16000000,20649.00000000,14801.12204300,303872798.77655953,204195\n1608123600000,2020-12-16 13:00:00,BTC/USDT,19762.80000000,20450.00000000,19762.80000000,20319.51000000,11510.05977200,231551452.51607468,142189\n1608120000000,2020-12-16 12:00:00,BTC/USDT,19739.78000000,19889.99000000,19680.00000000,19762.81000000,6075.10161800,120311034.07526276,84281\n1608116400000,2020-12-16 11:00:00,BTC/USDT,19798.18000000,19860.00000000,19645.90000000,19739.78000000,5884.32677100,116221600.12450060,83101\n1608112800000,2020-12-16 10:00:00,BTC/USDT,19516.22000000,19800.00000000,19498.01000000,19798.17000000,8207.25114000,161452813.23917643,104268\n1608109200000,2020-12-16 09:00:00,BTC/USDT,19482.57000000,19525.00000000,19420.84000000,19516.69000000,2118.95267200,41260965.59132595,36327\n1608105600000,2020-12-16 08:00:00,BTC/USDT,19423.95000000,19487.17000000,19370.62000000,19482.57000000,1854.42690000,36034244.62263834,33760\n1608102000000,2020-12-16 07:00:00,BTC/USDT,19429.90000000,19451.03000000,19399.00000000,19423.96000000,1290.05594600,25058301.98624793,35297\n1608098400000,2020-12-16 06:00:00,BTC/USDT,19373.82000000,19454.93000000,19341.40000000,19429.89000000,1512.57073700,29362597.45650969,36522\n1608094800000,2020-12-16 05:00:00,BTC/USDT,19358.31000000,19421.80000000,19339.35000000,19373.81000000,1444.89275300,28012940.03039856,26718\n1608091200000,2020-12-16 04:00:00,BTC/USDT,19346.51000000,19403.07000000,19300.30000000,19358.68000000,1417.79605700,27440437.88008619,27978\n1608087600000,2020-12-16 03:00:00,BTC/USDT,19442.08000000,19454.00000000,19325.00000000,19346.52000000,2010.88043200,38948590.85645910,32333\n1608084000000,2020-12-16 02:00:00,BTC/USDT,19389.37000000,19488.02000000,19389.37000000,19442.08000000,1919.59724100,37321668.92459128,32475\n1608080400000,2020-12-16 01:00:00,BTC/USDT,19365.28000000,19420.00000000,19317.01000000,19389.37000000,1791.40717700,34702572.30875320,42504\n1608076800000,2020-12-16 00:00:00,BTC/USDT,19426.43000000,19454.97000000,19278.60000000,19365.29000000,2363.83644100,45764099.31489616,43946\n1608073200000,2020-12-15 23:00:00,BTC/USDT,19461.37000000,19471.18000000,19345.51000000,19426.43000000,1412.95426400,27429152.69957443,25218\n1608069600000,2020-12-15 22:00:00,BTC/USDT,19419.93000000,19489.09000000,19379.19000000,19461.38000000,1176.97664300,22884617.05532855,27870\n1608066000000,2020-12-15 21:00:00,BTC/USDT,19491.87000000,19511.99000000,19276.00000000,19419.92000000,3770.65141500,73200283.82168917,47874\n1608062400000,2020-12-15 20:00:00,BTC/USDT,19530.38000000,19547.00000000,19461.55000000,19491.87000000,1595.05370600,31114529.26992910,30211\n1608058800000,2020-12-15 19:00:00,BTC/USDT,19529.99000000,19545.00000000,19465.00000000,19530.38000000,2470.61552000,48206007.97410457,38460\n1608055200000,2020-12-15 18:00:00,BTC/USDT,19399.53000000,19543.00000000,19390.00000000,19530.00000000,3565.30930800,69405393.32942563,48817\n1608051600000,2020-12-15 17:00:00,BTC/USDT,19364.09000000,19422.92000000,19352.48000000,19399.53000000,1851.18927300,35907617.38161144,39315\n1608048000000,2020-12-15 16:00:00,BTC/USDT,19403.31000000,19425.87000000,19328.06000000,19364.10000000,2346.46305800,45467441.86518932,39825\n1608044400000,2020-12-15 15:00:00,BTC/USDT,19337.46000000,19429.27000000,19337.46000000,19403.31000000,2771.86623000,53762615.76934960,43068\n1608040800000,2020-12-15 14:00:00,BTC/USDT,19349.16000000,19433.00000000,19256.27000000,19337.46000000,3320.86511800,64257051.47084430,53109\n1608037200000,2020-12-15 13:00:00,BTC/USDT,19291.40000000,19349.00000000,19263.50000000,19349.00000000,1506.49239100,29090445.92319783,30963\n1608033600000,2020-12-15 12:00:00,BTC/USDT,19293.54000000,19383.98000000,19256.33000000,19291.40000000,2177.67842200,42063871.16111150,38898\n1608030000000,2020-12-15 11:00:00,BTC/USDT,19309.99000000,19334.90000000,19241.07000000,19293.54000000,1837.39166500,35435762.75744592,40846\n1608026400000,2020-12-15 10:00:00,BTC/USDT,19187.51000000,19350.00000000,19117.90000000,19310.00000000,3596.15720800,69249283.58379682,52693\n1608022800000,2020-12-15 09:00:00,BTC/USDT,19136.96000000,19209.99000000,19074.00000000,19187.51000000,2498.76800000,47850670.01291859,37756\n1608019200000,2020-12-15 08:00:00,BTC/USDT,19197.59000000,19219.96000000,19106.95000000,19137.24000000,1854.09426000,35542869.26492726,33932\n1608015600000,2020-12-15 07:00:00,BTC/USDT,19186.12000000,19226.95000000,19146.95000000,19197.60000000,1730.80311700,33199007.21357700,31608\n1608012000000,2020-12-15 06:00:00,BTC/USDT,19133.29000000,19211.76000000,19101.00000000,19186.12000000,2257.16819300,43244582.03565109,37596\n1608008400000,2020-12-15 05:00:00,BTC/USDT,19180.70000000,19259.78000000,19050.00000000,19132.40000000,3984.04366400,76251092.67345880,64164\n1608004800000,2020-12-15 04:00:00,BTC/USDT,19479.69000000,19497.36000000,19161.03000000,19180.71000000,3179.72624100,61473987.92577317,63645\n1608001200000,2020-12-15 03:00:00,BTC/USDT,19458.98000000,19509.57000000,19416.35000000,19479.70000000,1934.60099700,37665313.62082362,41603\n1607997600000,2020-12-15 02:00:00,BTC/USDT,19455.06000000,19570.00000000,19444.25000000,19458.97000000,5055.68350000,98656537.38402874,82608\n1607994000000,2020-12-15 01:00:00,BTC/USDT,19394.94000000,19470.00000000,19320.85000000,19455.06000000,2941.28243100,57047581.23101451,51322\n1607990400000,2020-12-15 00:00:00,BTC/USDT,19273.69000000,19395.00000000,19243.64000000,19394.94000000,2998.53138700,57955812.50103087,45022\n1607986800000,2020-12-14 23:00:00,BTC/USDT,19291.99000000,19349.00000000,19200.85000000,19273.14000000,2882.37201900,55614202.92607254,46021\n1607983200000,2020-12-14 22:00:00,BTC/USDT,19197.82000000,19296.00000000,19188.76000000,19291.90000000,1317.36934800,25349746.39867067,29228\n1607979600000,2020-12-14 21:00:00,BTC/USDT,19208.08000000,19220.85000000,19150.85000000,19197.83000000,1359.13890100,26079866.29044296,29812\n1607976000000,2020-12-14 20:00:00,BTC/USDT,19175.88000000,19216.57000000,19130.85000000,19208.07000000,1386.34673800,26580893.94398627,29621\n1607972400000,2020-12-14 19:00:00,BTC/USDT,19236.75000000,19236.76000000,19165.00000000,19175.88000000,1343.18035800,25786096.44736833,33678\n1607968800000,2020-12-14 18:00:00,BTC/USDT,19143.38000000,19236.76000000,19143.38000000,19236.75000000,1817.18601900,34874284.34561431,32514\n1607965200000,2020-12-14 17:00:00,BTC/USDT,19152.59000000,19170.51000000,19113.08000000,19143.09000000,1255.88460800,24045294.09532957,28451\n1607961600000,2020-12-14 16:00:00,BTC/USDT,19200.00000000,19237.79000000,19114.58000000,19152.37000000,1896.16720200,36339368.45919770,47065\n1607958000000,2020-12-14 15:00:00,BTC/USDT,19221.75000000,19300.00000000,19158.57000000,19200.07000000,3589.35016400,69076198.18817253,54543\n1607954400000,2020-12-14 14:00:00,BTC/USDT,19184.20000000,19227.66000000,19088.88000000,19221.74000000,2439.25088800,46733762.72822593,48156\n1607950800000,2020-12-14 13:00:00,BTC/USDT,19114.66000000,19199.86000000,19063.31000000,19184.19000000,2220.23694700,42499310.01450780,34185\n1607947200000,2020-12-14 12:00:00,BTC/USDT,19106.43000000,19147.00000000,19028.97000000,19114.66000000,2118.83521600,40440153.52199987,34123\n1607943600000,2020-12-14 11:00:00,BTC/USDT,19088.94000000,19144.14000000,19051.11000000,19106.44000000,1864.31923500,35606173.21073918,31482\n1607940000000,2020-12-14 10:00:00,BTC/USDT,19184.60000000,19215.19000000,19052.72000000,19088.94000000,2101.35073700,40218964.13739735,37411\n1607936400000,2020-12-14 09:00:00,BTC/USDT,19222.00000000,19258.96000000,19120.00000000,19184.61000000,1986.24684200,38099270.66165153,30757\n1607932800000,2020-12-14 08:00:00,BTC/USDT,19155.80000000,19244.69000000,19140.00000000,19222.00000000,2065.45688600,39651855.99347659,29422\n1607929200000,2020-12-14 07:00:00,BTC/USDT,19206.24000000,19210.00000000,19100.43000000,19155.80000000,1326.68425700,25419485.44141620,26737\n1607925600000,2020-12-14 06:00:00,BTC/USDT,19085.53000000,19207.93000000,19080.40000000,19206.23000000,1741.87814700,33341909.69147697,28815\n1607922000000,2020-12-14 05:00:00,BTC/USDT,19126.93000000,19130.00000000,19033.84000000,19085.53000000,1556.62698700,29708802.72155159,31826\n1607918400000,2020-12-14 04:00:00,BTC/USDT,19260.68000000,19282.11000000,19055.51000000,19126.94000000,2675.25742900,51189039.77079196,40011\n1607914800000,2020-12-14 03:00:00,BTC/USDT,19297.90000000,19347.00000000,19227.01000000,19260.68000000,2413.53757200,46541743.78891363,35578\n1607911200000,2020-12-14 02:00:00,BTC/USDT,19085.64000000,19307.09000000,19052.62000000,19297.70000000,2765.11832600,53087642.95243247,39013\n1607907600000,2020-12-14 01:00:00,BTC/USDT,19056.23000000,19112.74000000,19019.65000000,19085.64000000,1246.65538400,23782999.42681138,28089\n1607904000000,2020-12-14 00:00:00,BTC/USDT,19174.99000000,19174.99000000,19000.00000000,19057.19000000,1888.75108400,36034622.58334769,35844\n1607900400000,2020-12-13 23:00:00,BTC/USDT,19112.41000000,19225.00000000,19090.00000000,19174.99000000,1627.72683800,31198043.17557941,30929\n1607896800000,2020-12-13 22:00:00,BTC/USDT,19190.00000000,19194.33000000,18971.00000000,19112.41000000,2215.95021000,42225204.52136216,42378\n1607893200000,2020-12-13 21:00:00,BTC/USDT,19154.56000000,19225.63000000,19150.00000000,19190.01000000,1153.64282700,22141548.64356091,29267\n1607889600000,2020-12-13 20:00:00,BTC/USDT,19193.28000000,19193.29000000,19089.63000000,19154.57000000,1908.19497000,36534026.68091457,39046\n1607886000000,2020-12-13 19:00:00,BTC/USDT,19185.33000000,19233.00000000,19155.00000000,19193.78000000,1307.77872600,25098223.16454260,26951\n1607882400000,2020-12-13 18:00:00,BTC/USDT,19273.59000000,19273.60000000,19172.33000000,19185.33000000,1636.42414800,31438157.19188333,35239\n1607878800000,2020-12-13 17:00:00,BTC/USDT,19328.11000000,19334.85000000,19197.36000000,19273.60000000,1835.01721000,35345202.03813577,35618\n1607875200000,2020-12-13 16:00:00,BTC/USDT,19211.34000000,19385.00000000,19208.70000000,19328.12000000,3401.55793700,65743060.89655995,65762\n1607871600000,2020-12-13 15:00:00,BTC/USDT,19292.13000000,19334.08000000,19080.00000000,19211.34000000,3876.89083700,74379774.46576625,60250\n1607868000000,2020-12-13 14:00:00,BTC/USDT,19321.13000000,19364.00000000,19275.41000000,19292.13000000,1662.41871000,32098424.62286307,31680\n1607864400000,2020-12-13 13:00:00,BTC/USDT,19348.97000000,19411.00000000,19290.01000000,19321.13000000,2820.18603500,54575254.92485517,43883\n1607860800000,2020-12-13 12:00:00,BTC/USDT,19316.80000000,19400.00000000,19265.91000000,19348.97000000,3516.70014700,68052068.15436480,50829\n1607857200000,2020-12-13 11:00:00,BTC/USDT,19278.99000000,19349.00000000,19200.00000000,19316.81000000,2247.85911200,43339962.29006435,34439\n1607853600000,2020-12-13 10:00:00,BTC/USDT,19263.26000000,19370.00000000,19255.00000000,19278.99000000,3202.08969000,61854943.98621792,48397\n1607850000000,2020-12-13 09:00:00,BTC/USDT,19245.25000000,19301.14000000,19185.74000000,19263.25000000,2399.86607700,46164906.46770061,34901\n1607846400000,2020-12-13 08:00:00,BTC/USDT,19249.21000000,19322.24000000,19179.50000000,19245.91000000,3622.14140300,69681154.84736808,55762\n1607842800000,2020-12-13 07:00:00,BTC/USDT,18973.93000000,19306.27000000,18973.93000000,19247.68000000,7062.52731100,135268469.39930940,81896\n1607839200000,2020-12-13 06:00:00,BTC/USDT,18849.99000000,18984.55000000,18844.95000000,18973.93000000,1930.01902800,36506250.34472179,33039\n1607835600000,2020-12-13 05:00:00,BTC/USDT,18852.51000000,18938.55000000,18815.00000000,18850.00000000,2197.61688300,41496010.02148245,27690\n1607832000000,2020-12-13 04:00:00,BTC/USDT,18784.56000000,18884.50000000,18768.19000000,18852.51000000,1252.67330600,23588810.06271224,23968\n1607828400000,2020-12-13 03:00:00,BTC/USDT,18812.61000000,18831.36000000,18760.77000000,18784.42000000,932.07127000,17515656.31349500,18965\n1607824800000,2020-12-13 02:00:00,BTC/USDT,18795.12000000,18830.00000000,18750.02000000,18812.61000000,1530.61413800,28756253.11768682,29448\n1607821200000,2020-12-13 01:00:00,BTC/USDT,18750.00000000,18808.98000000,18711.57000000,18795.31000000,1142.36527000,21444519.93926304,23944\n1607817600000,2020-12-13 00:00:00,BTC/USDT,18808.69000000,18875.00000000,18711.12000000,18750.00000000,2078.48966100,39074643.82444415,33741\n1607814000000,2020-12-12 23:00:00,BTC/USDT,18872.07000000,18880.37000000,18768.77000000,18808.69000000,1730.46905800,32547242.00362411,25738\n1607810400000,2020-12-12 22:00:00,BTC/USDT,18787.72000000,18948.66000000,18754.37000000,18870.51000000,2213.09286000,41739813.22391947,42429\n1607806800000,2020-12-12 21:00:00,BTC/USDT,18770.67000000,18840.40000000,18729.09000000,18787.72000000,1855.29318400,34855681.54716077,37817\n1607803200000,2020-12-12 20:00:00,BTC/USDT,18805.29000000,18840.40000000,18745.01000000,18770.68000000,2031.98501600,38177013.40082536,32906\n1607799600000,2020-12-12 19:00:00,BTC/USDT,18687.63000000,18850.00000000,18687.63000000,18805.29000000,3255.89979300,61099760.12686699,50759\n1607796000000,2020-12-12 18:00:00,BTC/USDT,18482.83000000,18746.33000000,18482.47000000,18687.62000000,4917.02800700,91723633.99943071,63277\n1607792400000,2020-12-12 17:00:00,BTC/USDT,18397.87000000,18527.93000000,18374.07000000,18483.17000000,1642.53083700,30293379.92959640,27891\n1607788800000,2020-12-12 16:00:00,BTC/USDT,18399.01000000,18450.46000000,18362.42000000,18397.88000000,1754.68993800,32300285.12535180,37624\n1607785200000,2020-12-12 15:00:00,BTC/USDT,18372.98000000,18434.62000000,18317.67000000,18399.01000000,1786.91668200,32835455.25329444,35603\n1607781600000,2020-12-12 14:00:00,BTC/USDT,18400.21000000,18475.63000000,18308.82000000,18372.97000000,2537.66383300,46678373.32869114,41714\n1607778000000,2020-12-12 13:00:00,BTC/USDT,18445.14000000,18451.35000000,18388.88000000,18400.21000000,1443.03583400,26575944.51858834,26155\n1607774400000,2020-12-12 12:00:00,BTC/USDT,18506.10000000,18525.33000000,18427.01000000,18445.15000000,1831.73177300,33837614.63220157,29863\n1607770800000,2020-12-12 11:00:00,BTC/USDT,18382.11000000,18513.66000000,18366.68000000,18506.10000000,1892.70304600,34939468.42666454,33239\n1607767200000,2020-12-12 10:00:00,BTC/USDT,18433.47000000,18459.42000000,18370.00000000,18382.10000000,1482.55862600,27288453.66323805,30809\n1607763600000,2020-12-12 09:00:00,BTC/USDT,18377.35000000,18478.00000000,18345.14000000,18433.47000000,1957.43315400,36067439.00089533,35727\n1607760000000,2020-12-12 08:00:00,BTC/USDT,18380.85000000,18450.00000000,18318.98000000,18377.64000000,2153.28465200,39587861.42014289,39165\n1607756400000,2020-12-12 07:00:00,BTC/USDT,18315.76000000,18400.00000000,18300.67000000,18380.85000000,1514.44081800,27785295.91735842,34863\n1607752800000,2020-12-12 06:00:00,BTC/USDT,18313.35000000,18366.24000000,18278.91000000,18315.76000000,1342.47028200,24588909.24046947,27515\n1607749200000,2020-12-12 05:00:00,BTC/USDT,18370.28000000,18398.19000000,18310.01000000,18313.36000000,1600.54204300,29369634.50594411,30636\n1607745600000,2020-12-12 04:00:00,BTC/USDT,18282.01000000,18390.00000000,18261.32000000,18370.28000000,1520.77657000,27869553.42412433,33999\n1607742000000,2020-12-12 03:00:00,BTC/USDT,18319.99000000,18336.82000000,18268.34000000,18282.02000000,1178.51983600,21559345.20462112,23250\n1607738400000,2020-12-12 02:00:00,BTC/USDT,18283.84000000,18350.00000000,18278.14000000,18319.99000000,1370.49853900,25103513.95091640,28847\n1607734800000,2020-12-12 01:00:00,BTC/USDT,18342.05000000,18375.00000000,18271.10000000,18283.84000000,1922.63074500,35210286.78755807,42881\n1607731200000,2020-12-12 00:00:00,BTC/USDT,18036.53000000,18370.00000000,18020.70000000,18342.06000000,4583.78330600,83518877.27469954,79103\n1607727600000,2020-12-11 23:00:00,BTC/USDT,18127.81000000,18149.75000000,18012.69000000,18036.53000000,1824.61973600,32973409.54446278,42841\n1607724000000,2020-12-11 22:00:00,BTC/USDT,18100.01000000,18184.00000000,18067.72000000,18127.81000000,1977.13009300,35865598.64736300,46808\n1607720400000,2020-12-11 21:00:00,BTC/USDT,17983.47000000,18125.18000000,17952.15000000,18100.01000000,2363.58389500,42645696.86992499,41747\n1607716800000,2020-12-11 20:00:00,BTC/USDT,17975.08000000,18060.00000000,17925.37000000,17982.89000000,2012.49939600,36169523.12316280,37353\n1607713200000,2020-12-11 19:00:00,BTC/USDT,18044.26000000,18093.95000000,17959.22000000,17977.00000000,2055.80295900,37101541.78500970,32395\n1607709600000,2020-12-11 18:00:00,BTC/USDT,17995.50000000,18046.83000000,17943.29000000,18044.26000000,1971.88283100,35487541.90077533,29206\n1607706000000,2020-12-11 17:00:00,BTC/USDT,17978.15000000,18007.00000000,17852.00000000,17995.53000000,2771.29657700,49724633.94605851,41311\n1607702400000,2020-12-11 16:00:00,BTC/USDT,18104.74000000,18111.55000000,17942.85000000,17978.14000000,2468.64785100,44465838.95788876,37065\n1607698800000,2020-12-11 15:00:00,BTC/USDT,18058.75000000,18132.00000000,18028.00000000,18104.74000000,3191.42360300,57668403.51108962,39004\n1607695200000,2020-12-11 14:00:00,BTC/USDT,17972.00000000,18068.00000000,17901.07000000,18058.76000000,3657.43856700,65772843.02515030,49362\n1607691600000,2020-12-11 13:00:00,BTC/USDT,17864.73000000,17987.98000000,17827.68000000,17972.01000000,2900.77125000,51946637.49573068,46985\n1607688000000,2020-12-11 12:00:00,BTC/USDT,17647.71000000,17916.50000000,17617.00000000,17864.73000000,4028.59243600,71630940.19876540,56403\n1607684400000,2020-12-11 11:00:00,BTC/USDT,17758.46000000,17761.10000000,17600.00000000,17647.71000000,3575.26562500,63181227.87511524,57844\n1607680800000,2020-12-11 10:00:00,BTC/USDT,17802.60000000,17874.01000000,17572.33000000,17758.45000000,5006.23438400,88775697.64247451,66490\n1607677200000,2020-12-11 09:00:00,BTC/USDT,17899.46000000,17908.00000000,17728.25000000,17802.60000000,2599.67167700,46295339.89425719,39175\n1607673600000,2020-12-11 08:00:00,BTC/USDT,17811.96000000,18001.15000000,17810.50000000,17898.79000000,3799.96689700,67987197.49296977,52485\n1607670000000,2020-12-11 07:00:00,BTC/USDT,17923.60000000,17944.87000000,17700.51000000,17811.95000000,3651.86148900,64968977.41091075,58596\n1607666400000,2020-12-11 06:00:00,BTC/USDT,17899.43000000,17968.00000000,17814.73000000,17923.60000000,1844.10117200,33002682.35787750,32584\n1607662800000,2020-12-11 05:00:00,BTC/USDT,17959.73000000,18021.42000000,17833.88000000,17899.44000000,2208.24425200,39566413.09459911,33090\n1607659200000,2020-12-11 04:00:00,BTC/USDT,17990.69000000,18048.60000000,17929.81000000,17959.72000000,2129.21127100,38281486.71726284,30298\n1607655600000,2020-12-11 03:00:00,BTC/USDT,17804.97000000,17996.14000000,17715.60000000,17990.88000000,3471.13457100,62034423.24105488,52850\n1607652000000,2020-12-11 02:00:00,BTC/USDT,17901.44000000,18040.81000000,17801.37000000,17804.97000000,3702.97795800,66344001.68441753,57712\n1607648400000,2020-12-11 01:00:00,BTC/USDT,18018.31000000,18074.80000000,17804.00000000,17901.45000000,5573.67197000,99914282.93931862,75607\n1607644800000,2020-12-11 00:00:00,BTC/USDT,18254.81000000,18292.73000000,17950.00000000,18018.32000000,3824.69379900,69277307.95226034,60599\n1607641200000,2020-12-10 23:00:00,BTC/USDT,18326.36000000,18380.41000000,18220.00000000,18254.63000000,1461.86718900,26759875.40736279,27870\n1607637600000,2020-12-10 22:00:00,BTC/USDT,18348.99000000,18397.77000000,18297.76000000,18326.36000000,1007.94178300,18487455.96298143,26372\n1607634000000,2020-12-10 21:00:00,BTC/USDT,18365.79000000,18405.62000000,18305.74000000,18349.00000000,1405.94011000,25819451.18102512,27157\n1607630400000,2020-12-10 20:00:00,BTC/USDT,18403.11000000,18435.74000000,18342.56000000,18365.78000000,2400.94160500,44171460.14450001,34522\n1607626800000,2020-12-10 19:00:00,BTC/USDT,18274.74000000,18403.28000000,18225.31000000,18403.10000000,2437.61964200,44650435.56302606,34483\n1607623200000,2020-12-10 18:00:00,BTC/USDT,18213.87000000,18316.76000000,18195.01000000,18274.75000000,2327.71276100,42506248.75505001,32224\n1607619600000,2020-12-10 17:00:00,BTC/USDT,18176.99000000,18217.95000000,18076.07000000,18213.86000000,1822.38119900,33088171.07497929,28185\n1607616000000,2020-12-10 16:00:00,BTC/USDT,18132.11000000,18225.31000000,18096.42000000,18176.99000000,2836.09702900,51529050.23518128,43815\n1607612400000,2020-12-10 15:00:00,BTC/USDT,18080.00000000,18151.00000000,17911.12000000,18134.08000000,4777.60176000,86111223.96279673,61235\n1607608800000,2020-12-10 14:00:00,BTC/USDT,18233.65000000,18252.55000000,18040.01000000,18079.99000000,3043.01352700,55105270.78918178,46672\n1607605200000,2020-12-10 13:00:00,BTC/USDT,18209.72000000,18246.80000000,18045.31000000,18233.65000000,3702.02883200,67166588.86451570,48984\n1607601600000,2020-12-10 12:00:00,BTC/USDT,18192.50000000,18290.63000000,18165.89000000,18209.96000000,1783.46088300,32515320.72413903,30897\n1607598000000,2020-12-10 11:00:00,BTC/USDT,18145.00000000,18254.48000000,18117.32000000,18192.49000000,2447.63731200,44515334.61408646,34662\n1607594400000,2020-12-10 10:00:00,BTC/USDT,18237.51000000,18281.01000000,18070.00000000,18145.00000000,3256.28896100,59243010.76751726,50637\n1607590800000,2020-12-10 09:00:00,BTC/USDT,18433.31000000,18480.00000000,18224.01000000,18237.52000000,2921.51327900,53595891.64635654,45654\n1607587200000,2020-12-10 08:00:00,BTC/USDT,18335.91000000,18471.18000000,18319.74000000,18433.30000000,1918.03465700,35334957.48407030,32899\n1607583600000,2020-12-10 07:00:00,BTC/USDT,18298.23000000,18386.16000000,18287.78000000,18335.91000000,1225.14512500,22481483.82324640,26023\n1607580000000,2020-12-10 06:00:00,BTC/USDT,18372.36000000,18435.74000000,18278.44000000,18298.22000000,2065.77300700,37885683.71312600,40012\n1607576400000,2020-12-10 05:00:00,BTC/USDT,18418.15000000,18420.80000000,18305.40000000,18372.37000000,1571.72015300,28869311.52693795,41572\n1607572800000,2020-12-10 04:00:00,BTC/USDT,18409.78000000,18493.28000000,18389.00000000,18418.15000000,1491.72176600,27528502.21418474,31892\n1607569200000,2020-12-10 03:00:00,BTC/USDT,18386.02000000,18500.00000000,18350.00000000,18409.77000000,1492.90043500,27512619.71558739,32012\n1607565600000,2020-12-10 02:00:00,BTC/USDT,18431.25000000,18485.71000000,18353.29000000,18386.02000000,1684.78457800,31027437.46376328,28957\n1607562000000,2020-12-10 01:00:00,BTC/USDT,18432.01000000,18501.96000000,18303.28000000,18431.25000000,1984.13813000,36542232.63636051,40657\n1607558400000,2020-12-10 00:00:00,BTC/USDT,18541.29000000,18557.32000000,18416.17000000,18432.01000000,1824.41137100,33702510.73063102,43758\n1607554800000,2020-12-09 23:00:00,BTC/USDT,18556.12000000,18639.57000000,18531.29000000,18541.28000000,1699.57021100,31587041.25165852,32753\n1607551200000,2020-12-09 22:00:00,BTC/USDT,18519.32000000,18613.99000000,18485.71000000,18556.11000000,2659.08754800,49370818.68720520,53618\n1607547600000,2020-12-09 21:00:00,BTC/USDT,18341.01000000,18550.00000000,18326.04000000,18519.37000000,3203.22194600,59095738.16762049,48544\n1607544000000,2020-12-09 20:00:00,BTC/USDT,18260.02000000,18372.00000000,18211.78000000,18341.02000000,2640.59352400,48343480.94353978,42653\n1607540400000,2020-12-09 19:00:00,BTC/USDT,18196.41000000,18289.06000000,18196.11000000,18260.03000000,1802.91715200,32902865.64033200,29408\n1607536800000,2020-12-09 18:00:00,BTC/USDT,18304.06000000,18352.22000000,18165.00000000,18196.40000000,2456.28116600,44836590.68228841,37361\n1607533200000,2020-12-09 17:00:00,BTC/USDT,18389.73000000,18435.00000000,18297.32000000,18304.06000000,2386.46782200,43812813.00836083,42584\n1607529600000,2020-12-09 16:00:00,BTC/USDT,18349.50000000,18392.00000000,18252.15000000,18389.74000000,2491.70293900,45638377.67814911,42426\n1607526000000,2020-12-09 15:00:00,BTC/USDT,18451.01000000,18470.22000000,18290.49000000,18349.50000000,3458.08501900,63463034.76509870,51301\n1607522400000,2020-12-09 14:00:00,BTC/USDT,18483.24000000,18523.99000000,18425.17000000,18451.01000000,3858.94166200,71275845.74781602,55223\n1607518800000,2020-12-09 13:00:00,BTC/USDT,18234.74000000,18498.00000000,18169.10000000,18483.50000000,3956.73374300,72457875.00538701,56931\n1607515200000,2020-12-09 12:00:00,BTC/USDT,18244.94000000,18365.00000000,18210.05000000,18234.74000000,2551.59298700,46657216.63939070,54134\n1607511600000,2020-12-09 11:00:00,BTC/USDT,18234.66000000,18379.87000000,18180.72000000,18244.95000000,4005.12706700,73188553.24513787,61233\n1607508000000,2020-12-09 10:00:00,BTC/USDT,18040.98000000,18249.00000000,17978.16000000,18234.65000000,4049.21499800,73420817.54414360,65171\n1607504400000,2020-12-09 09:00:00,BTC/USDT,17955.78000000,18108.64000000,17919.93000000,18036.34000000,4587.52736200,82627249.87608896,72230\n1607500800000,2020-12-09 08:00:00,BTC/USDT,17924.06000000,18048.12000000,17650.00000000,17955.78000000,8243.27624300,147105177.31092626,116641\n1607497200000,2020-12-09 07:00:00,BTC/USDT,18214.08000000,18244.26000000,17830.00000000,17924.07000000,5288.68322200,95354508.50097996,75585\n1607493600000,2020-12-09 06:00:00,BTC/USDT,18206.57000000,18280.00000000,18153.36000000,18214.09000000,2142.32315800,39037045.01694565,40631\n1607490000000,2020-12-09 05:00:00,BTC/USDT,18159.57000000,18242.32000000,18058.00000000,18205.50000000,2983.49184900,54160476.89001040,55340\n1607486400000,2020-12-09 04:00:00,BTC/USDT,18300.00000000,18308.39000000,18125.00000000,18159.58000000,2057.22879800,37481803.52436757,34619\n1607482800000,2020-12-09 03:00:00,BTC/USDT,18281.26000000,18310.00000000,18220.88000000,18300.01000000,1888.92734500,34508258.92075024,37911\n1607479200000,2020-12-09 02:00:00,BTC/USDT,18215.50000000,18350.36000000,18151.60000000,18281.00000000,2790.50147700,50924060.56184731,49735\n1607475600000,2020-12-09 01:00:00,BTC/USDT,18180.00000000,18329.65000000,18032.00000000,18215.49000000,4099.08178400,74590786.79162266,74192\n1607472000000,2020-12-09 00:00:00,BTC/USDT,18324.11000000,18380.00000000,18120.00000000,18180.01000000,4284.97477900,78290227.66491701,72252\n1607468400000,2020-12-08 23:00:00,BTC/USDT,18336.50000000,18500.00000000,18200.00000000,18324.11000000,7082.33235600,129826677.09956268,100421\n1607464800000,2020-12-08 22:00:00,BTC/USDT,18778.02000000,18837.45000000,18320.00000000,18340.01000000,4197.20262600,78134606.57203197,76572\n1607461200000,2020-12-08 21:00:00,BTC/USDT,18742.64000000,18827.34000000,18687.80000000,18777.86000000,1763.50432500,33089334.31070798,55785\n1607457600000,2020-12-08 20:00:00,BTC/USDT,18795.74000000,18848.00000000,18664.51000000,18742.63000000,3177.56653800,59586016.16513511,57270\n1607454000000,2020-12-08 19:00:00,BTC/USDT,18831.00000000,18895.00000000,18787.87000000,18795.74000000,1381.00655800,26026454.65701556,33136\n1607450400000,2020-12-08 18:00:00,BTC/USDT,18927.41000000,18930.84000000,18826.08000000,18831.00000000,1791.74626100,33814127.04478135,35900\n1607446800000,2020-12-08 17:00:00,BTC/USDT,18832.02000000,18937.81000000,18818.00000000,18927.41000000,1365.99854100,25788551.58988939,30944\n1607443200000,2020-12-08 16:00:00,BTC/USDT,18809.91000000,18910.00000000,18745.31000000,18832.03000000,2390.26467000,45037469.41603399,47579\n1607439600000,2020-12-08 15:00:00,BTC/USDT,18865.00000000,18913.14000000,18780.00000000,18809.91000000,2017.42127500,37998914.55136814,42920\n1607436000000,2020-12-08 14:00:00,BTC/USDT,18923.42000000,18974.83000000,18861.47000000,18865.01000000,2255.91188600,42670462.00609898,43330\n1607432400000,2020-12-08 13:00:00,BTC/USDT,18726.92000000,18942.30000000,18726.92000000,18922.84000000,3398.70974100,64096011.01973729,55347\n1607428800000,2020-12-08 12:00:00,BTC/USDT,18762.95000000,18864.06000000,18610.00000000,18726.93000000,4536.59873600,84920211.09161877,71532\n1607425200000,2020-12-08 11:00:00,BTC/USDT,18816.00000000,18869.23000000,18730.00000000,18762.96000000,2445.01004900,45973344.95213389,47586\n1607421600000,2020-12-08 10:00:00,BTC/USDT,18791.78000000,18879.00000000,18700.04000000,18816.00000000,3764.95059300,70843765.30293461,66454\n1607418000000,2020-12-08 09:00:00,BTC/USDT,19069.95000000,19101.59000000,18700.00000000,18791.77000000,5751.66347400,108574720.57953909,90356\n1607414400000,2020-12-08 08:00:00,BTC/USDT,19143.10000000,19168.88000000,19010.00000000,19069.96000000,2558.83605300,48883728.92347666,53722\n1607410800000,2020-12-08 07:00:00,BTC/USDT,19160.49000000,19199.62000000,19135.00000000,19143.10000000,1877.75323200,35986698.06044441,33443\n1607407200000,2020-12-08 06:00:00,BTC/USDT,19293.76000000,19294.84000000,19091.00000000,19160.49000000,1931.91375800,37072667.18490267,38499\n1607403600000,2020-12-08 05:00:00,BTC/USDT,19181.21000000,19293.76000000,19181.21000000,19293.76000000,1362.40264800,26182963.16782896,32090\n1607400000000,2020-12-08 04:00:00,BTC/USDT,19154.73000000,19210.00000000,19151.96000000,19181.21000000,1423.21866600,27310523.22784012,23920\n1607396400000,2020-12-08 03:00:00,BTC/USDT,19177.61000000,19196.00000000,19132.78000000,19154.73000000,1086.76353500,20824599.25034294,22675\n1607392800000,2020-12-08 02:00:00,BTC/USDT,19210.63000000,19215.34000000,19160.00000000,19177.61000000,1061.47089100,20363440.60595637,23161\n1607389200000,2020-12-08 01:00:00,BTC/USDT,19228.41000000,19235.60000000,19150.25000000,19210.62000000,1465.00046400,28117324.87693521,27713\n1607385600000,2020-12-08 00:00:00,BTC/USDT,19166.90000000,19229.99000000,19132.67000000,19228.40000000,1539.70073800,29535465.74834286,32826\n1607382000000,2020-12-07 23:00:00,BTC/USDT,19114.49000000,19217.64000000,19072.80000000,19166.90000000,1603.01696300,30715178.90419292,32774\n1607378400000,2020-12-07 22:00:00,BTC/USDT,19075.45000000,19115.78000000,19057.86000000,19114.48000000,788.10171800,15046280.91472147,36202\n1607374800000,2020-12-07 21:00:00,BTC/USDT,19050.63000000,19115.28000000,19015.36000000,19075.41000000,1561.72968300,29788306.53066951,33069\n1607371200000,2020-12-07 20:00:00,BTC/USDT,18967.01000000,19059.80000000,18935.06000000,19050.63000000,1533.44469200,29133124.99346403,34547\n1607367600000,2020-12-07 19:00:00,BTC/USDT,18938.51000000,19039.02000000,18912.31000000,18967.01000000,1898.11913500,36035171.70797653,37810\n1607364000000,2020-12-07 18:00:00,BTC/USDT,19160.01000000,19188.16000000,18902.88000000,18939.70000000,4582.18670200,87134615.14045152,76108\n1607360400000,2020-12-07 17:00:00,BTC/USDT,19188.29000000,19210.09000000,19139.99000000,19160.01000000,1440.24862200,27610072.48267495,31077\n1607356800000,2020-12-07 16:00:00,BTC/USDT,19213.90000000,19241.39000000,19166.79000000,19188.36000000,1587.89743800,30501948.61893084,35523\n1607353200000,2020-12-07 15:00:00,BTC/USDT,19195.84000000,19257.03000000,19168.88000000,19213.90000000,1660.03544400,31894063.41113433,33715\n1607349600000,2020-12-07 14:00:00,BTC/USDT,19187.90000000,19234.61000000,19095.72000000,19195.84000000,2077.87589400,39826155.95031214,41715\n1607346000000,2020-12-07 13:00:00,BTC/USDT,19239.00000000,19253.09000000,19180.70000000,19187.67000000,1522.34235800,29245121.96616379,33363\n1607342400000,2020-12-07 12:00:00,BTC/USDT,19103.79000000,19239.00000000,19097.50000000,19238.80000000,2301.61307500,44142883.91886110,46590\n1607338800000,2020-12-07 11:00:00,BTC/USDT,19183.41000000,19231.22000000,19090.00000000,19103.58000000,1914.16087800,36697437.77963823,37323\n1607335200000,2020-12-07 10:00:00,BTC/USDT,19213.34000000,19254.06000000,19147.05000000,19183.41000000,1839.59864900,35331014.80542378,34324\n1607331600000,2020-12-07 09:00:00,BTC/USDT,19250.85000000,19256.62000000,19177.99000000,19213.34000000,1678.48208100,32253302.16985129,30131\n1607328000000,2020-12-07 08:00:00,BTC/USDT,19371.24000000,19386.15000000,19200.00000000,19250.84000000,1808.05797200,34838283.46477790,32972\n1607324400000,2020-12-07 07:00:00,BTC/USDT,19306.36000000,19399.00000000,19293.93000000,19371.30000000,1503.21260800,29075412.95372914,27661\n1607320800000,2020-12-07 06:00:00,BTC/USDT,19246.25000000,19356.30000000,19237.37000000,19306.36000000,1454.81203300,28086827.92152083,31801\n1607317200000,2020-12-07 05:00:00,BTC/USDT,19285.97000000,19299.00000000,19240.00000000,19246.26000000,1023.88277600,19728551.96851123,24790\n1607313600000,2020-12-07 04:00:00,BTC/USDT,19282.68000000,19306.11000000,19233.36000000,19285.97000000,1193.98592600,23013756.45878229,23201\n1607310000000,2020-12-07 03:00:00,BTC/USDT,19200.01000000,19288.23000000,19157.55000000,19282.68000000,1339.84959700,25769706.56043463,28915\n1607306400000,2020-12-07 02:00:00,BTC/USDT,19293.09000000,19307.49000000,19169.35000000,19200.00000000,1548.26916600,29775458.62265290,27188\n1607302800000,2020-12-07 01:00:00,BTC/USDT,19318.56000000,19349.55000000,19219.99000000,19293.08000000,1527.85634800,29466467.43325096,31495\n1607299200000,2020-12-07 00:00:00,BTC/USDT,19358.67000000,19420.91000000,19288.23000000,19318.56000000,1983.51653500,38408524.00581976,38660\n1607295600000,2020-12-06 23:00:00,BTC/USDT,19125.55000000,19420.00000000,19125.55000000,19359.40000000,4312.40788100,83417946.61315767,66139\n1607292000000,2020-12-06 22:00:00,BTC/USDT,19238.08000000,19249.00000000,19045.00000000,19125.44000000,1481.42844200,28388390.28543065,38970\n1607288400000,2020-12-06 21:00:00,BTC/USDT,19168.73000000,19244.00000000,19114.01000000,19238.08000000,1606.57008500,30821953.39992698,26833\n1607284800000,2020-12-06 20:00:00,BTC/USDT,19132.62000000,19184.62000000,19123.41000000,19168.73000000,868.81471400,16639515.78026271,21093\n1607281200000,2020-12-06 19:00:00,BTC/USDT,19105.20000000,19156.96000000,19076.47000000,19132.62000000,870.44194400,16640069.76086090,21758\n1607277600000,2020-12-06 18:00:00,BTC/USDT,19127.86000000,19174.36000000,19093.78000000,19105.21000000,870.08646100,16644336.52278490,22605\n1607274000000,2020-12-06 17:00:00,BTC/USDT,19106.46000000,19177.16000000,19065.91000000,19128.02000000,920.59684800,17615931.57344884,25191\n1607270400000,2020-12-06 16:00:00,BTC/USDT,19125.84000000,19213.09000000,19018.00000000,19106.80000000,1927.44402700,36849081.93696932,41564\n1607266800000,2020-12-06 15:00:00,BTC/USDT,19120.27000000,19152.38000000,19094.00000000,19125.83000000,1350.28603400,25822584.15986674,29328\n1607263200000,2020-12-06 14:00:00,BTC/USDT,18953.07000000,19131.53000000,18930.98000000,19120.00000000,1809.57926000,34464912.68598285,38591\n1607259600000,2020-12-06 13:00:00,BTC/USDT,18982.87000000,19021.00000000,18857.00000000,18953.07000000,2100.60158800,39781893.10495557,43707\n1607256000000,2020-12-06 12:00:00,BTC/USDT,19089.15000000,19089.16000000,18960.55000000,18982.47000000,1111.22632800,21136041.01432083,26401\n1607252400000,2020-12-06 11:00:00,BTC/USDT,19041.32000000,19100.00000000,18959.73000000,19089.16000000,1115.62880600,21217995.16180690,26255\n1607248800000,2020-12-06 10:00:00,BTC/USDT,19042.23000000,19061.53000000,18957.04000000,19041.31000000,1124.01615600,21377501.83912832,32895\n1607245200000,2020-12-06 09:00:00,BTC/USDT,19017.54000000,19083.98000000,18950.87000000,19042.22000000,2087.15759200,39712662.14555090,45224\n1607241600000,2020-12-06 08:00:00,BTC/USDT,19230.99000000,19251.00000000,19003.64000000,19017.56000000,2003.08053900,38281917.08173657,40401\n1607238000000,2020-12-06 07:00:00,BTC/USDT,19184.23000000,19260.00000000,19172.30000000,19230.99000000,921.80597300,17715712.00128943,27326\n1607234400000,2020-12-06 06:00:00,BTC/USDT,19152.00000000,19224.66000000,19133.50000000,19183.94000000,845.30298100,16221070.70848535,28887\n1607230800000,2020-12-06 05:00:00,BTC/USDT,19185.92000000,19222.54000000,19105.00000000,19151.97000000,1054.98823100,20211211.36741765,25714\n1607227200000,2020-12-06 04:00:00,BTC/USDT,19183.39000000,19231.08000000,19155.04000000,19185.92000000,881.83428600,16922961.34948862,19751\n1607223600000,2020-12-06 03:00:00,BTC/USDT,19233.44000000,19245.91000000,19137.18000000,19183.39000000,1223.12757700,23468724.50181641,27588\n1607220000000,2020-12-06 02:00:00,BTC/USDT,19261.51000000,19288.06000000,19206.03000000,19233.43000000,1450.02811000,27910630.54357203,33136\n1607216400000,2020-12-06 01:00:00,BTC/USDT,19268.81000000,19342.00000000,19228.00000000,19261.52000000,2377.43934400,45855946.81144811,50993\n1607212800000,2020-12-06 00:00:00,BTC/USDT,19147.66000000,19277.00000000,19131.02000000,19268.81000000,2729.19865400,52439943.23319755,60032\n1607209200000,2020-12-05 23:00:00,BTC/USDT,19020.59000000,19157.52000000,19020.08000000,19147.66000000,1337.36733200,25538250.72515192,29495\n1607205600000,2020-12-05 22:00:00,BTC/USDT,19007.04000000,19052.17000000,18977.16000000,19020.50000000,772.95269300,14698713.29506243,29458\n1607202000000,2020-12-05 21:00:00,BTC/USDT,19055.24000000,19110.00000000,19000.00000000,19007.04000000,1050.88054700,20024806.68119142,25974\n1607198400000,2020-12-05 20:00:00,BTC/USDT,19119.19000000,19140.00000000,18993.92000000,19054.90000000,1291.84607400,24637011.65796819,29085\n1607194800000,2020-12-05 19:00:00,BTC/USDT,19116.65000000,19172.37000000,19075.30000000,19119.19000000,1273.08141400,24344292.32519472,25658\n1607191200000,2020-12-05 18:00:00,BTC/USDT,19125.77000000,19140.00000000,19085.00000000,19116.30000000,1113.48195500,21289530.88161600,32975\n1607187600000,2020-12-05 17:00:00,BTC/USDT,19098.99000000,19125.76000000,19026.00000000,19125.76000000,1130.56601500,21572714.95161235,26206\n1607184000000,2020-12-05 16:00:00,BTC/USDT,19111.13000000,19149.92000000,19044.28000000,19099.00000000,1712.33209900,32693160.55403845,38147\n1607180400000,2020-12-05 15:00:00,BTC/USDT,19080.01000000,19150.00000000,19051.00000000,19110.78000000,1878.88140300,35886470.91346511,45607\n1607176800000,2020-12-05 14:00:00,BTC/USDT,18982.57000000,19092.03000000,18963.83000000,19080.01000000,2031.09956000,38659387.40414819,50890\n1607173200000,2020-12-05 13:00:00,BTC/USDT,19060.27000000,19069.90000000,18941.74000000,18983.13000000,1663.18758000,31592536.94970767,38876\n1607169600000,2020-12-05 12:00:00,BTC/USDT,19013.43000000,19080.01000000,18965.45000000,19060.01000000,1505.47903300,28648203.04673590,32530\n1607166000000,2020-12-05 11:00:00,BTC/USDT,19114.48000000,19127.46000000,18959.03000000,19013.43000000,2031.72423400,38633648.63906200,42099\n1607162400000,2020-12-05 10:00:00,BTC/USDT,19110.42000000,19137.28000000,19037.50000000,19114.48000000,1404.53563700,26817996.67002897,29312\n1607158800000,2020-12-05 09:00:00,BTC/USDT,19149.90000000,19165.00000000,19053.05000000,19110.42000000,1671.42061900,31921737.41184100,37851\n1607155200000,2020-12-05 08:00:00,BTC/USDT,18911.30000000,19177.00000000,18911.30000000,19149.90000000,3390.54923600,64687978.98939646,54083\n1607151600000,2020-12-05 07:00:00,BTC/USDT,18963.03000000,18986.61000000,18900.00000000,18911.31000000,1514.20482400,28673006.45581073,39355\n1607148000000,2020-12-05 06:00:00,BTC/USDT,18955.83000000,18990.08000000,18906.16000000,18963.03000000,1590.75053100,30146928.12825393,32633\n1607144400000,2020-12-05 05:00:00,BTC/USDT,18863.90000000,18970.00000000,18840.69000000,18954.42000000,1647.77831300,31157664.84072058,33808\n1607140800000,2020-12-05 04:00:00,BTC/USDT,18818.05000000,18932.00000000,18800.00000000,18863.90000000,1551.89466600,29273684.12444952,34481\n1607137200000,2020-12-05 03:00:00,BTC/USDT,18789.65000000,18880.00000000,18738.34000000,18818.85000000,2317.88868400,43604121.01517010,39750\n1607133600000,2020-12-05 02:00:00,BTC/USDT,18644.88000000,18848.62000000,18641.10000000,18789.66000000,2388.32171300,44794505.28143370,50229\n1607130000000,2020-12-05 01:00:00,BTC/USDT,18764.23000000,18819.83000000,18634.50000000,18644.89000000,2253.93186900,42235243.73747503,52098\n1607126400000,2020-12-05 00:00:00,BTC/USDT,18650.51000000,18791.53000000,18500.00000000,18764.23000000,4398.59254200,81913839.45127983,81524\n1607122800000,2020-12-04 23:00:00,BTC/USDT,18665.31000000,18841.00000000,18601.50000000,18650.52000000,2948.57260400,55226122.97793165,64625\n1607119200000,2020-12-04 22:00:00,BTC/USDT,18807.09000000,18875.27000000,18565.31000000,18665.30000000,2805.20343100,52579885.42071959,84979\n1607115600000,2020-12-04 21:00:00,BTC/USDT,18962.52000000,18988.75000000,18725.60000000,18806.41000000,3265.94108900,61561240.65911373,66152\n1607112000000,2020-12-04 20:00:00,BTC/USDT,19038.73000000,19045.34000000,18880.00000000,18962.53000000,2390.28912900,45336675.44738781,50160\n1607108400000,2020-12-04 19:00:00,BTC/USDT,19056.45000000,19065.00000000,19000.00000000,19038.73000000,1689.61723600,32159056.90054567,31547\n1607104800000,2020-12-04 18:00:00,BTC/USDT,18968.82000000,19078.68000000,18929.16000000,19056.45000000,1718.02023200,32649923.38013036,35115\n1607101200000,2020-12-04 17:00:00,BTC/USDT,18981.28000000,19029.20000000,18932.23000000,18968.93000000,1898.74850000,36049805.31495437,39095\n1607097600000,2020-12-04 16:00:00,BTC/USDT,18944.06000000,18991.70000000,18817.00000000,18981.28000000,3493.46569700,66066942.83135513,64762\n1607094000000,2020-12-04 15:00:00,BTC/USDT,19046.11000000,19073.46000000,18938.25000000,18943.35000000,2288.00545200,43443319.94608559,43666\n1607090400000,2020-12-04 14:00:00,BTC/USDT,19005.34000000,19146.22000000,18917.84000000,19046.11000000,3301.09525500,62889972.10745246,51967\n1607086800000,2020-12-04 13:00:00,BTC/USDT,19026.49000000,19027.00000000,18914.42000000,19005.34000000,2476.86035000,47023525.47837189,42242\n1607083200000,2020-12-04 12:00:00,BTC/USDT,18834.48000000,19029.56000000,18686.38000000,19026.49000000,6651.62561800,125375497.63885147,92836\n1607079600000,2020-12-04 11:00:00,BTC/USDT,18978.35000000,19077.16000000,18700.00000000,18833.25000000,5226.95006100,99082630.02125020,84724\n1607076000000,2020-12-04 10:00:00,BTC/USDT,19354.23000000,19360.73000000,18900.00000000,18978.35000000,9630.66309300,183213562.52145367,126127\n1607072400000,2020-12-04 09:00:00,BTC/USDT,19388.90000000,19410.49000000,19316.11000000,19354.23000000,1738.76710300,33657499.38507212,35837\n1607068800000,2020-12-04 08:00:00,BTC/USDT,19283.94000000,19447.14000000,19281.23000000,19388.89000000,3152.63811700,61100292.85079060,60776\n1607065200000,2020-12-04 07:00:00,BTC/USDT,19317.13000000,19335.83000000,19238.31000000,19283.94000000,2191.96097400,42274324.52328992,45144\n1607061600000,2020-12-04 06:00:00,BTC/USDT,19200.01000000,19367.05000000,19192.89000000,19317.13000000,1791.57506300,34584785.74624994,32524\n1607058000000,2020-12-04 05:00:00,BTC/USDT,19286.78000000,19312.79000000,19190.52000000,19200.07000000,1607.20170600,30978648.73763958,31543\n1607054400000,2020-12-04 04:00:00,BTC/USDT,19162.62000000,19318.83000000,19122.48000000,19286.78000000,2332.87907300,44908846.32217946,41673\n1607050800000,2020-12-04 03:00:00,BTC/USDT,19251.92000000,19323.31000000,19122.74000000,19162.62000000,2645.78391000,50893448.50869055,42986\n1607047200000,2020-12-04 02:00:00,BTC/USDT,19323.31000000,19375.00000000,19250.00000000,19251.92000000,2016.84764800,38944350.97337438,40964\n1607043600000,2020-12-04 01:00:00,BTC/USDT,19462.49000000,19489.84000000,19319.39000000,19321.42000000,1937.05663400,37547835.84101095,39608\n1607040000000,2020-12-04 00:00:00,BTC/USDT,19422.34000000,19527.00000000,19378.92000000,19460.65000000,2083.90022500,40531781.49159696,37605\n1607036400000,2020-12-03 23:00:00,BTC/USDT,19464.12000000,19540.00000000,19402.11000000,19421.90000000,2261.04089400,44031158.54872415,37248\n1607032800000,2020-12-03 22:00:00,BTC/USDT,19438.86000000,19479.71000000,19402.00000000,19464.11000000,1224.31092300,23805136.83458776,38490\n1607029200000,2020-12-03 21:00:00,BTC/USDT,19369.44000000,19462.24000000,19328.92000000,19438.86000000,1585.05878000,30749329.82565039,37401\n1607025600000,2020-12-03 20:00:00,BTC/USDT,19414.29000000,19435.29000000,19306.27000000,19369.44000000,1970.71359000,38191320.78205682,46265\n1607022000000,2020-12-03 19:00:00,BTC/USDT,19371.46000000,19422.90000000,19328.51000000,19414.29000000,1747.88877700,33864135.81323945,36086\n1607018400000,2020-12-03 18:00:00,BTC/USDT,19299.24000000,19398.90000000,19299.24000000,19371.46000000,2030.27025300,39291195.88601159,36823\n1607014800000,2020-12-03 17:00:00,BTC/USDT,19354.78000000,19384.23000000,19194.06000000,19299.23000000,2612.55142100,50355373.46640128,50278\n1607011200000,2020-12-03 16:00:00,BTC/USDT,19535.00000000,19598.00000000,19251.76000000,19354.78000000,5994.11349500,116450803.87043408,92162\n1607007600000,2020-12-03 15:00:00,BTC/USDT,19351.32000000,19536.20000000,19300.00000000,19535.00000000,4242.67237900,82419984.97534082,60347\n1607004000000,2020-12-03 14:00:00,BTC/USDT,19281.51000000,19397.00000000,19274.00000000,19351.32000000,2196.78560300,42510325.27477713,42753\n1607000400000,2020-12-03 13:00:00,BTC/USDT,19321.26000000,19375.16000000,19252.20000000,19281.00000000,2397.63164500,46326758.09642191,45055\n1606996800000,2020-12-03 12:00:00,BTC/USDT,19396.49000000,19425.00000000,19219.78000000,19321.26000000,3073.25548400,59334124.44464987,60165\n1606993200000,2020-12-03 11:00:00,BTC/USDT,19353.69000000,19444.40000000,19290.00000000,19396.50000000,3752.00635800,72714243.48643473,71749\n1606989600000,2020-12-03 10:00:00,BTC/USDT,19363.98000000,19422.90000000,19244.75000000,19353.69000000,3404.98273500,65905387.22088700,55383\n1606986000000,2020-12-03 09:00:00,BTC/USDT,19378.79000000,19420.96000000,19097.60000000,19363.99000000,4473.86282600,86144417.12953802,71994\n1606982400000,2020-12-03 08:00:00,BTC/USDT,19208.11000000,19450.00000000,19161.85000000,19378.79000000,6548.80470700,126857872.14393028,90174\n1606978800000,2020-12-03 07:00:00,BTC/USDT,18970.24000000,19250.00000000,18911.00000000,19208.11000000,3187.18640700,60941439.47702374,58522\n1606975200000,2020-12-03 06:00:00,BTC/USDT,18922.83000000,19015.42000000,18880.51000000,18970.24000000,1915.07394100,36333616.29513362,45140\n1606971600000,2020-12-03 05:00:00,BTC/USDT,19041.21000000,19110.00000000,18867.20000000,18922.83000000,2207.43948900,41917491.97674468,57785\n1606968000000,2020-12-03 04:00:00,BTC/USDT,19087.01000000,19124.03000000,19022.22000000,19041.21000000,1398.14332800,26658568.29874753,36604\n1606964400000,2020-12-03 03:00:00,BTC/USDT,19041.73000000,19178.87000000,19013.17000000,19087.00000000,1658.09331800,31695256.86000887,36748\n1606960800000,2020-12-03 02:00:00,BTC/USDT,19016.92000000,19099.05000000,18945.00000000,19041.73000000,2011.15410300,38288417.42044118,39713\n1606957200000,2020-12-03 01:00:00,BTC/USDT,19180.00000000,19184.54000000,18940.00000000,19016.91000000,2664.78993700,50774887.11280391,49367\n1606953600000,2020-12-03 00:00:00,BTC/USDT,19204.08000000,19299.00000000,19150.76000000,19180.00000000,2131.56088600,40987065.58671698,45977\n1606950000000,2020-12-02 23:00:00,BTC/USDT,19111.13000000,19260.00000000,19089.50000000,19204.09000000,2012.40777000,38612371.42477807,43735\n1606946400000,2020-12-02 22:00:00,BTC/USDT,19145.00000000,19235.00000000,19099.00000000,19111.13000000,1845.35460300,35402766.13893307,50137\n1606942800000,2020-12-02 21:00:00,BTC/USDT,19083.77000000,19168.91000000,19046.30000000,19145.01000000,1486.37157300,28394539.15745830,38002\n1606939200000,2020-12-02 20:00:00,BTC/USDT,19101.10000000,19150.00000000,19044.85000000,19083.40000000,2075.77846400,39653698.09533935,39734\n1606935600000,2020-12-02 19:00:00,BTC/USDT,19011.97000000,19139.06000000,18964.08000000,19101.10000000,2128.49847300,40566080.01771435,47884\n1606932000000,2020-12-02 18:00:00,BTC/USDT,18976.33000000,19068.00000000,18894.80000000,19011.99000000,2505.87259700,47678898.08209294,56302\n1606928400000,2020-12-02 17:00:00,BTC/USDT,18856.25000000,18999.00000000,18810.27000000,18976.33000000,2378.36303600,44974224.82939649,55120\n1606924800000,2020-12-02 16:00:00,BTC/USDT,18891.57000000,19015.70000000,18770.00000000,18856.25000000,3304.38303900,62422634.34854100,68373\n1606921200000,2020-12-02 15:00:00,BTC/USDT,18910.21000000,18989.00000000,18728.38000000,18891.57000000,3966.23942200,74812648.36020829,80044\n1606917600000,2020-12-02 14:00:00,BTC/USDT,19126.75000000,19175.00000000,18850.00000000,18910.21000000,4219.85660400,80206787.77920484,83878\n1606914000000,2020-12-02 13:00:00,BTC/USDT,18940.97000000,19250.70000000,18919.31000000,19124.51000000,3955.23988400,75686931.89059800,77269\n1606910400000,2020-12-02 12:00:00,BTC/USDT,19127.31000000,19129.13000000,18792.31000000,18940.98000000,4128.99782400,78086569.51139689,83936\n1606906800000,2020-12-02 11:00:00,BTC/USDT,19103.38000000,19199.00000000,19022.47000000,19127.31000000,2688.86401900,51411798.26088833,70264\n1606903200000,2020-12-02 10:00:00,BTC/USDT,19036.29000000,19196.63000000,18991.43000000,19103.39000000,3344.07393600,63905713.03185523,73415\n1606899600000,2020-12-02 09:00:00,BTC/USDT,19195.52000000,19319.67000000,18977.03000000,19035.99000000,3695.07897700,70830703.94675170,74346\n1606896000000,2020-12-02 08:00:00,BTC/USDT,19124.49000000,19342.00000000,19059.09000000,19195.19000000,4980.51787100,95644083.03067570,79747\n1606892400000,2020-12-02 07:00:00,BTC/USDT,18924.13000000,19135.00000000,18833.00000000,19124.48000000,4325.75023600,81901891.72064776,83904\n1606888800000,2020-12-02 06:00:00,BTC/USDT,18654.27000000,18939.97000000,18630.63000000,18926.66000000,4068.00177700,76442400.92907904,80793\n1606885200000,2020-12-02 05:00:00,BTC/USDT,18550.95000000,18660.00000000,18465.42000000,18654.41000000,2661.91571200,49412594.81090894,50730\n1606881600000,2020-12-02 04:00:00,BTC/USDT,18618.00000000,18700.00000000,18330.00000000,18550.96000000,4330.09472400,79995995.48203154,78312\n1606878000000,2020-12-02 03:00:00,BTC/USDT,18676.39000000,18788.32000000,18506.16000000,18618.25000000,3284.66614900,61179206.34463118,60636\n1606874400000,2020-12-02 02:00:00,BTC/USDT,18854.01000000,18863.73000000,18639.86000000,18676.40000000,1647.54545200,30890586.07332112,48710\n1606870800000,2020-12-02 01:00:00,BTC/USDT,18836.50000000,18972.12000000,18703.00000000,18854.01000000,2504.97901900,47169500.27016453,60342\n1606867200000,2020-12-02 00:00:00,BTC/USDT,18764.96000000,18877.92000000,18433.00000000,18836.51000000,4372.16231700,81612721.28933284,81022\n1606863600000,2020-12-01 23:00:00,BTC/USDT,18895.01000000,18943.26000000,18725.00000000,18764.96000000,2883.10159000,54213163.06816306,55514\n1606860000000,2020-12-01 22:00:00,BTC/USDT,19038.39000000,19156.72000000,18830.18000000,18895.00000000,1719.52279600,32680701.11332183,58467\n1606856400000,2020-12-01 21:00:00,BTC/USDT,19024.33000000,19211.00000000,18936.61000000,19038.39000000,2275.27857000,43443576.82689272,52497\n1606852800000,2020-12-01 20:00:00,BTC/USDT,19069.79000000,19150.00000000,18862.00000000,19024.32000000,2451.41962400,46548870.20844088,56019\n1606849200000,2020-12-01 19:00:00,BTC/USDT,18738.83000000,19135.19000000,18720.48000000,19069.79000000,2954.65137500,56010750.04291621,68368\n1606845600000,2020-12-01 18:00:00,BTC/USDT,19058.40000000,19074.64000000,18693.37000000,18738.82000000,3316.50591100,62575960.55636555,66089\n1606842000000,2020-12-01 17:00:00,BTC/USDT,19058.80000000,19086.95000000,18611.88000000,19058.40000000,6526.02898300,123114241.68393634,98525\n1606838400000,2020-12-01 16:00:00,BTC/USDT,19263.36000000,19325.83000000,18938.22000000,19058.80000000,5690.01703900,108612965.93002929,93277\n1606834800000,2020-12-01 15:00:00,BTC/USDT,19285.30000000,19489.30000000,19147.60000000,19263.37000000,6838.16271300,132143477.46159945,105756\n1606831200000,2020-12-01 14:00:00,BTC/USDT,18759.74000000,19364.82000000,18651.00000000,19285.31000000,7821.04718900,148998996.05020139,122582\n1606827600000,2020-12-01 13:00:00,BTC/USDT,18550.25000000,18844.15000000,18001.12000000,18759.73000000,14772.77763900,273340123.07018385,186620\n1606824000000,2020-12-01 12:00:00,BTC/USDT,19425.40000000,19482.01000000,18399.99000000,18551.35000000,17554.24707000,331338492.04643760,214216\n1606820400000,2020-12-01 11:00:00,BTC/USDT,19739.51000000,19888.00000000,18886.00000000,19425.00000000,14556.65715100,283662723.37185542,189171\n1606816800000,2020-12-01 10:00:00,BTC/USDT,19564.99000000,19800.00000000,19558.77000000,19739.51000000,7640.26076700,150582903.62734859,109266\n1606813200000,2020-12-01 09:00:00,BTC/USDT,19467.00000000,19570.00000000,19426.96000000,19565.00000000,2824.26869500,55063014.73673623,49666\n1606809600000,2020-12-01 08:00:00,BTC/USDT,19515.62000000,19567.00000000,19441.19000000,19466.99000000,3143.17296100,61341043.46623811,55597\n1606806000000,2020-12-01 07:00:00,BTC/USDT,19338.33000000,19546.81000000,19300.00000000,19515.63000000,3009.22518200,58493432.64001075,60058\n1606802400000,2020-12-01 06:00:00,BTC/USDT,19483.73000000,19517.94000000,19309.87000000,19338.34000000,3129.77632900,60740855.67468755,48503\n1606798800000,2020-12-01 05:00:00,BTC/USDT,19352.64000000,19502.54000000,19281.38000000,19483.73000000,2620.88379200,50845084.16324997,46027\n1606795200000,2020-12-01 04:00:00,BTC/USDT,19419.73000000,19527.02000000,19344.92000000,19354.31000000,3400.85794100,66114168.28625028,56705\n1606791600000,2020-12-01 03:00:00,BTC/USDT,19680.96000000,19682.77000000,19340.00000000,19419.74000000,2889.84860400,56474738.28684640,46370\n1606788000000,2020-12-01 02:00:00,BTC/USDT,19605.75000000,19704.93000000,19548.57000000,19680.95000000,2408.22997800,47287571.79669132,46470\n1606784400000,2020-12-01 01:00:00,BTC/USDT,19565.47000000,19639.99000000,19433.15000000,19605.75000000,2702.45923500,52841596.71273252,57117\n1606780800000,2020-12-01 00:00:00,BTC/USDT,19695.87000000,19720.00000000,19479.80000000,19565.47000000,4570.36151800,89601884.00766336,80922\n1606777200000,2020-11-30 23:00:00,BTC/USDT,19500.00000000,19747.05000000,19450.43000000,19695.87000000,5413.39864800,106198119.26033051,82098\n1606773600000,2020-11-30 22:00:00,BTC/USDT,19379.86000000,19500.00000000,19233.09000000,19499.99000000,2253.14788500,43639914.01998113,53837\n1606770000000,2020-11-30 21:00:00,BTC/USDT,19439.27000000,19520.00000000,19323.18000000,19381.25000000,2732.12400800,53126518.81748252,50985\n1606766400000,2020-11-30 20:00:00,BTC/USDT,19259.98000000,19494.35000000,19170.78000000,19439.48000000,3745.42207700,72606810.34306796,68293\n1606762800000,2020-11-30 19:00:00,BTC/USDT,19175.87000000,19315.00000000,19050.00000000,19259.98000000,3291.46620000,63281645.48512806,63576\n1606759200000,2020-11-30 18:00:00,BTC/USDT,19393.60000000,19467.23000000,19141.89000000,19175.78000000,4761.07583700,91704151.95421973,73158\n1606755600000,2020-11-30 17:00:00,BTC/USDT,19196.41000000,19470.00000000,19011.10000000,19393.60000000,6736.14526100,129727112.26482895,96156\n1606752000000,2020-11-30 16:00:00,BTC/USDT,19291.88000000,19579.00000000,19127.48000000,19196.40000000,7387.10582400,143348755.91693022,124396\n1606748400000,2020-11-30 15:00:00,BTC/USDT,19480.73000000,19863.16000000,19152.25000000,19291.88000000,16227.99653500,317695224.52500200,203156\n1606744800000,2020-11-30 14:00:00,BTC/USDT,19151.49000000,19782.10000000,19135.00000000,19480.80000000,15792.38031000,307214715.04574494,187250\n1606741200000,2020-11-30 13:00:00,BTC/USDT,18837.16000000,19210.00000000,18788.11000000,19151.49000000,9286.98736600,176378855.88769829,121369\n1606737600000,2020-11-30 12:00:00,BTC/USDT,18600.94000000,18840.00000000,18600.94000000,18837.16000000,4675.20875400,87625034.60270693,64500\n1606734000000,2020-11-30 11:00:00,BTC/USDT,18503.11000000,18655.00000000,18503.11000000,18600.95000000,3413.48268800,63562162.75846537,50305\n1606730400000,2020-11-30 10:00:00,BTC/USDT,18478.54000000,18589.00000000,18446.50000000,18503.06000000,2240.91788300,41516090.56722040,40452\n1606726800000,2020-11-30 09:00:00,BTC/USDT,18462.70000000,18510.00000000,18326.03000000,18478.54000000,3054.08560600,56248649.23190862,62166\n1606723200000,2020-11-30 08:00:00,BTC/USDT,18623.05000000,18625.00000000,18375.55000000,18462.31000000,4220.60940300,77972731.24946443,67233\n1606719600000,2020-11-30 07:00:00,BTC/USDT,18520.27000000,18677.00000000,18512.90000000,18623.00000000,3499.55549900,65178082.92646582,52020\n1606712400000,2020-11-30 05:00:00,BTC/USDT,18523.79000000,18627.29000000,18477.62000000,18520.47000000,1835.53540700,34068066.56167236,35314\n1606708800000,2020-11-30 04:00:00,BTC/USDT,18579.67000000,18619.79000000,18450.00000000,18523.79000000,2119.71817300,39247661.09306119,37505\n1606705200000,2020-11-30 03:00:00,BTC/USDT,18506.87000000,18593.81000000,18431.26000000,18579.50000000,2283.37140400,42288794.16279001,42078\n1606701600000,2020-11-30 02:00:00,BTC/USDT,18482.10000000,18550.00000000,18435.81000000,18506.87000000,2470.42779900,45664903.49372367,32913\n1606698000000,2020-11-30 01:00:00,BTC/USDT,18430.33000000,18548.32000000,18347.81000000,18481.97000000,3561.59241700,65690857.94589032,62323\n1606694400000,2020-11-30 00:00:00,BTC/USDT,18185.00000000,18477.00000000,18184.99000000,18430.33000000,4461.71190400,81944261.39155276,73066\n1606690800000,2020-11-29 23:00:00,BTC/USDT,18133.22000000,18230.00000000,18088.00000000,18184.99000000,1687.70425000,30672391.23924202,41518\n1606687200000,2020-11-29 22:00:00,BTC/USDT,18254.24000000,18264.99000000,18060.83000000,18133.21000000,2136.94469800,38775028.11111238,67857\n1606683600000,2020-11-29 21:00:00,BTC/USDT,18207.39000000,18360.05000000,18202.36000000,18255.00000000,3253.80510500,59500779.39283669,63971\n1606680000000,2020-11-29 20:00:00,BTC/USDT,18042.85000000,18228.00000000,18027.82000000,18207.40000000,2554.56347000,46379544.50987765,42215\n1606676400000,2020-11-29 19:00:00,BTC/USDT,18118.27000000,18125.17000000,18008.12000000,18042.86000000,1293.54575400,23367580.87499710,34202\n1606672800000,2020-11-29 18:00:00,BTC/USDT,18094.31000000,18128.94000000,18040.00000000,18118.27000000,1310.44397900,23685970.40941645,33822\n1606669200000,2020-11-29 17:00:00,BTC/USDT,18089.79000000,18145.14000000,18045.31000000,18094.31000000,1930.81142800,34952920.49608570,42287\n1606665600000,2020-11-29 16:00:00,BTC/USDT,18119.97000000,18147.00000000,17926.00000000,18089.78000000,2833.66810200,51070079.87906341,64783\n1606662000000,2020-11-29 15:00:00,BTC/USDT,18039.82000000,18140.00000000,17966.16000000,18119.97000000,2052.87758500,37105783.22017291,46192\n1606658400000,2020-11-29 14:00:00,BTC/USDT,18097.46000000,18194.13000000,18030.00000000,18039.82000000,2431.45632500,44037886.92552382,51913\n1606654800000,2020-11-29 13:00:00,BTC/USDT,18030.08000000,18148.97000000,17919.00000000,18097.46000000,2430.79196100,43898978.21394214,44041\n1606651200000,2020-11-29 12:00:00,BTC/USDT,18089.75000000,18132.32000000,17882.82000000,18030.08000000,2938.19158000,52902223.12357814,59571\n1606647600000,2020-11-29 11:00:00,BTC/USDT,18192.83000000,18250.00000000,18000.00000000,18090.44000000,3536.06544800,64083773.00173915,61089\n1606644000000,2020-11-29 10:00:00,BTC/USDT,18104.35000000,18227.30000000,18036.04000000,18192.82000000,3715.25868000,67428351.46783665,55534\n1606640400000,2020-11-29 09:00:00,BTC/USDT,18144.98000000,18209.65000000,18049.92000000,18104.35000000,3332.48121600,60423112.01901895,58620\n1606636800000,2020-11-29 08:00:00,BTC/USDT,17817.34000000,18154.44000000,17803.78000000,18144.99000000,4846.14602600,87410936.62606818,80234\n1606633200000,2020-11-29 07:00:00,BTC/USDT,17729.27000000,17849.21000000,17666.00000000,17817.34000000,1937.99308900,34432193.70422020,41876\n1606629600000,2020-11-29 06:00:00,BTC/USDT,17790.31000000,17792.62000000,17707.31000000,17729.27000000,1179.75644200,20931391.88221153,28500\n1606626000000,2020-11-29 05:00:00,BTC/USDT,17763.00000000,17837.53000000,17751.00000000,17790.31000000,1006.51621800,17914762.28942701,27884\n1606622400000,2020-11-29 04:00:00,BTC/USDT,17867.48000000,17867.49000000,17758.66000000,17763.00000000,1324.77180700,23588393.68282973,30739\n1606618800000,2020-11-29 03:00:00,BTC/USDT,17742.25000000,17886.21000000,17724.19000000,17867.48000000,1891.22161700,33649628.76788277,37781\n1606615200000,2020-11-29 02:00:00,BTC/USDT,17587.53000000,17793.00000000,17545.28000000,17742.25000000,1601.80020100,28303720.39342215,35377\n1606611600000,2020-11-29 01:00:00,BTC/USDT,17649.24000000,17663.11000000,17520.00000000,17587.53000000,1442.26754900,25377382.49322006,38487\n1606608000000,2020-11-29 00:00:00,BTC/USDT,17719.84000000,17760.00000000,17517.00000000,17649.23000000,2659.93377300,46850986.14119687,52796\n1606604400000,2020-11-28 23:00:00,BTC/USDT,17760.03000000,17807.10000000,17661.21000000,17719.85000000,1869.46273700,33160850.55902866,41228\n1606600800000,2020-11-28 22:00:00,BTC/USDT,17741.05000000,17843.00000000,17612.33000000,17760.03000000,2697.87402900,47924683.97305394,62787\n1606597200000,2020-11-28 21:00:00,BTC/USDT,17673.21000000,17751.99000000,17660.30000000,17741.05000000,1493.25252200,26452771.27052839,35663\n1606593600000,2020-11-28 20:00:00,BTC/USDT,17814.50000000,17880.49000000,17671.14000000,17673.21000000,3336.93089200,59396545.20320496,55444\n1606590000000,2020-11-28 19:00:00,BTC/USDT,17730.00000000,17840.00000000,17670.75000000,17814.50000000,2843.66955500,50494568.67691225,49406\n1606586400000,2020-11-28 18:00:00,BTC/USDT,17745.73000000,17765.23000000,17640.38000000,17729.99000000,3355.36336500,59436068.97657424,49832\n1606582800000,2020-11-28 17:00:00,BTC/USDT,17707.26000000,17748.99000000,17580.64000000,17745.74000000,3612.39675300,63872288.84620567,64583\n1606579200000,2020-11-28 16:00:00,BTC/USDT,17517.04000000,17772.09000000,17516.52000000,17707.26000000,7586.11041800,134027902.98582211,113500\n1606575600000,2020-11-28 15:00:00,BTC/USDT,17336.07000000,17530.46000000,17336.06000000,17517.04000000,4554.76150500,79264111.21451898,80655\n1606572000000,2020-11-28 14:00:00,BTC/USDT,17356.03000000,17390.00000000,17264.59000000,17336.07000000,3782.34943800,65582604.51307446,65387\n1606568400000,2020-11-28 13:00:00,BTC/USDT,17134.42000000,17358.00000000,17074.39000000,17356.02000000,3547.88444000,61206314.12637615,64527\n1606564800000,2020-11-28 12:00:00,BTC/USDT,17160.29000000,17227.08000000,17070.00000000,17134.42000000,2652.53890800,45480481.43286144,46636\n1606561200000,2020-11-28 11:00:00,BTC/USDT,17195.73000000,17200.00000000,17106.02000000,17160.29000000,2054.32390100,35253479.58715714,38689\n1606557600000,2020-11-28 10:00:00,BTC/USDT,16916.42000000,17257.97000000,16880.50000000,17195.73000000,3981.80107700,68170152.70733766,61751\n1606554000000,2020-11-28 09:00:00,BTC/USDT,16875.01000000,16962.74000000,16874.99000000,16916.42000000,1327.50858000,22467331.79295585,30676\n1606550400000,2020-11-28 08:00:00,BTC/USDT,17014.72000000,17046.41000000,16865.56000000,16875.01000000,1994.86419800,33787561.76040276,40906\n1606546800000,2020-11-28 07:00:00,BTC/USDT,17019.31000000,17067.14000000,16992.00000000,17014.72000000,1563.34588100,26631846.45139548,30799\n1606543200000,2020-11-28 06:00:00,BTC/USDT,16978.00000000,17036.89000000,16962.99000000,17019.32000000,1424.45316100,24206677.52991829,29878\n1606539600000,2020-11-28 05:00:00,BTC/USDT,16961.37000000,16978.00000000,16872.00000000,16977.99000000,1373.95417800,23252375.37925604,28982\n1606536000000,2020-11-28 04:00:00,BTC/USDT,16990.27000000,17026.60000000,16891.90000000,16961.38000000,1516.97955900,25721134.62338771,35817\n1606532400000,2020-11-28 03:00:00,BTC/USDT,16984.52000000,17012.09000000,16923.50000000,16990.27000000,2031.83120200,34467315.81819859,39718\n1606528800000,2020-11-28 02:00:00,BTC/USDT,17129.65000000,17131.23000000,16979.61000000,16983.94000000,1648.32037900,28092447.92452285,42061\n1606525200000,2020-11-28 01:00:00,BTC/USDT,17150.00000000,17190.00000000,17046.46000000,17129.66000000,2239.80818100,38354747.08047155,49682\n1606521600000,2020-11-28 00:00:00,BTC/USDT,17139.53000000,17178.45000000,16974.30000000,17150.00000000,2420.91511100,41357342.67405990,50778\n1606518000000,2020-11-27 23:00:00,BTC/USDT,16956.59000000,17235.70000000,16956.59000000,17139.52000000,2904.91244000,49648855.89230045,52946\n1606514400000,2020-11-27 22:00:00,BTC/USDT,16994.91000000,17047.84000000,16910.36000000,16956.45000000,1454.79033600,24709107.59035379,50300\n1606510800000,2020-11-27 21:00:00,BTC/USDT,17068.01000000,17087.46000000,16634.34000000,16994.92000000,3481.63699300,59070079.16578239,58994\n1606507200000,2020-11-27 20:00:00,BTC/USDT,16803.14000000,17200.00000000,16750.00000000,17069.73000000,4793.95756600,81582200.52203768,81377\n1606503600000,2020-11-27 19:00:00,BTC/USDT,16781.22000000,16846.91000000,16730.00000000,16803.14000000,1754.45851600,29447505.10034436,39221\n1606500000000,2020-11-27 18:00:00,BTC/USDT,16727.89000000,16866.66000000,16686.71000000,16781.22000000,2375.22439200,39895360.84218938,46569\n1606496400000,2020-11-27 17:00:00,BTC/USDT,16810.08000000,16810.08000000,16660.33000000,16727.51000000,2533.67119100,42427654.44766893,52407\n1606492800000,2020-11-27 16:00:00,BTC/USDT,16501.10000000,16834.28000000,16465.48000000,16810.09000000,4546.94011200,75811580.96143081,82504\n1606489200000,2020-11-27 15:00:00,BTC/USDT,16768.51000000,16788.13000000,16438.08000000,16498.85000000,6801.87428400,113150233.09289623,94375\n1606485600000,2020-11-27 14:00:00,BTC/USDT,16944.79000000,16960.63000000,16724.82000000,16768.39000000,3504.62104500,59048293.40480856,64793\n1606482000000,2020-11-27 13:00:00,BTC/USDT,17066.94000000,17083.35000000,16910.00000000,16944.79000000,2848.30462900,48430291.44284992,56404\n1606478400000,2020-11-27 12:00:00,BTC/USDT,16789.46000000,17074.08000000,16772.52000000,17066.95000000,3806.79004300,64524782.91597313,62485\n1606474800000,2020-11-27 11:00:00,BTC/USDT,16920.55000000,16940.87000000,16758.00000000,16789.37000000,2862.00014800,48180163.44161932,57405\n1606471200000,2020-11-27 10:00:00,BTC/USDT,16656.54000000,16942.17000000,16654.26000000,16920.69000000,4084.19701900,68610725.34903623,72381\n1606467600000,2020-11-27 09:00:00,BTC/USDT,16850.86000000,17200.00000000,16620.00000000,16656.53000000,6652.16710200,112162219.01905506,107768\n1606464000000,2020-11-27 08:00:00,BTC/USDT,17095.47000000,17118.15000000,16713.46000000,16850.85000000,5678.35250600,95796924.19657012,95963\n1606460400000,2020-11-27 07:00:00,BTC/USDT,17282.03000000,17296.75000000,17037.57000000,17094.18000000,2967.29896900,50856864.62067209,55017\n1606456800000,2020-11-27 06:00:00,BTC/USDT,17258.50000000,17311.00000000,17172.53000000,17282.03000000,2278.94494300,39295975.74554359,51478\n1606453200000,2020-11-27 05:00:00,BTC/USDT,17071.42000000,17340.49000000,17069.27000000,17258.50000000,2564.73527400,44218007.03664124,61654\n1606449600000,2020-11-27 04:00:00,BTC/USDT,17078.11000000,17186.87000000,17050.00000000,17073.39000000,2532.92050200,43351613.29798984,58445\n1606446000000,2020-11-27 03:00:00,BTC/USDT,17092.50000000,17269.46000000,17022.18000000,17078.11000000,3126.20587300,53685659.83534390,59684\n1606442400000,2020-11-27 02:00:00,BTC/USDT,17306.48000000,17320.00000000,17068.00000000,17092.50000000,3644.42739200,62652119.30679699,64532\n1606438800000,2020-11-27 01:00:00,BTC/USDT,17395.34000000,17457.62000000,17266.08000000,17306.48000000,4026.38596500,69968357.68174060,73303\n1606435200000,2020-11-27 00:00:00,BTC/USDT,17149.47000000,17400.00000000,17012.56000000,17393.73000000,4072.20754700,70145776.52544339,72294\n1606431600000,2020-11-26 23:00:00,BTC/USDT,17167.49000000,17177.91000000,16807.69000000,17149.47000000,2491.15748600,42483588.34612535,54847\n1606428000000,2020-11-26 22:00:00,BTC/USDT,17031.17000000,17229.94000000,16968.11000000,17167.99000000,2396.21421000,40979614.36238680,65503\n1606424400000,2020-11-26 21:00:00,BTC/USDT,17136.33000000,17294.88000000,17005.00000000,17031.18000000,5804.86758600,99623996.49200791,93065\n1606420800000,2020-11-26 20:00:00,BTC/USDT,16516.86000000,17152.01000000,16513.35000000,17136.33000000,7110.67075900,120074012.18664390,113409\n1606417200000,2020-11-26 19:00:00,BTC/USDT,16532.35000000,16627.65000000,16300.00000000,16516.87000000,6459.83489100,106312760.79786177,96902\n1606413600000,2020-11-26 18:00:00,BTC/USDT,16690.11000000,16823.00000000,16500.00000000,16531.73000000,4809.33957800,80228037.16230389,76410\n1606410000000,2020-11-26 17:00:00,BTC/USDT,16669.90000000,16794.92000000,16188.00000000,16693.76000000,12743.41651100,209969836.09814550,156476\n1606406400000,2020-11-26 16:00:00,BTC/USDT,16906.23000000,17036.65000000,16585.41000000,16674.52000000,7071.37102700,118994844.58628949,111238\n1606402800000,2020-11-26 15:00:00,BTC/USDT,16732.23000000,17250.00000000,16720.00000000,16905.18000000,6812.67708900,116146690.76471662,112530\n1606399200000,2020-11-26 14:00:00,BTC/USDT,17054.02000000,17120.00000000,16732.24000000,16732.33000000,9420.65805700,159100468.70414321,132603\n1606395600000,2020-11-26 13:00:00,BTC/USDT,17369.17000000,17395.47000000,17045.08000000,17054.57000000,4976.67351200,85845789.16361973,84901\n1606392000000,2020-11-26 12:00:00,BTC/USDT,17100.00000000,17384.95000000,16963.79000000,17369.17000000,6235.36619100,107529727.68163147,105438\n1606388400000,2020-11-26 11:00:00,BTC/USDT,17285.00000000,17420.93000000,16930.30000000,17100.00000000,8207.28851100,141392799.24533121,121689\n1606384800000,2020-11-26 10:00:00,BTC/USDT,17060.81000000,17321.84000000,17058.33000000,17285.00000000,8065.90697100,138698645.52837011,118965\n1606381200000,2020-11-26 09:00:00,BTC/USDT,16878.80000000,17129.61000000,16652.18000000,17060.77000000,10276.57520600,173775659.89293452,139556\n1606377600000,2020-11-26 08:00:00,BTC/USDT,17610.92000000,17620.19000000,16334.00000000,16878.80000000,20495.74479000,347621838.41499607,240683\n1606374000000,2020-11-26 07:00:00,BTC/USDT,17954.10000000,17954.10000000,17435.00000000,17619.90000000,7124.12890400,126033343.55870552,129184\n1606370400000,2020-11-26 06:00:00,BTC/USDT,17930.86000000,18015.79000000,17825.63000000,17954.09000000,3869.49783300,69387724.98449473,80980\n1606366800000,2020-11-26 05:00:00,BTC/USDT,17772.03000000,17954.56000000,17697.76000000,17930.86000000,5076.27605200,90511186.12080438,91680\n1606363200000,2020-11-26 04:00:00,BTC/USDT,17927.98000000,18100.00000000,17566.54000000,17772.02000000,9629.02765800,171841444.47761463,159055\n1606359600000,2020-11-26 03:00:00,BTC/USDT,18400.97000000,18400.97000000,17200.00000000,17927.98000000,18992.02049200,339332601.93662398,237194\n1606356000000,2020-11-26 02:00:00,BTC/USDT,18799.32000000,18804.28000000,18355.85000000,18400.98000000,6742.35842800,124990699.38875561,111236\n1606352400000,2020-11-26 01:00:00,BTC/USDT,18862.55000000,18886.59000000,18749.33000000,18799.32000000,2246.24453500,42257887.05424120,54422\n1606348800000,2020-11-26 00:00:00,BTC/USDT,18718.83000000,18915.03000000,18613.92000000,18862.54000000,3947.93041600,74180946.15582731,82517\n1606345200000,2020-11-25 23:00:00,BTC/USDT,18758.71000000,18854.59000000,18500.27000000,18719.11000000,6068.02275000,113352118.27464989,116630\n1606341600000,2020-11-25 22:00:00,BTC/USDT,18880.27000000,18927.85000000,18695.00000000,18758.71000000,3637.37498600,68313551.77471783,101631\n1606338000000,2020-11-25 21:00:00,BTC/USDT,18902.38000000,19042.42000000,18800.47000000,18880.27000000,3371.60464900,63859255.63852216,70730\n1606334400000,2020-11-25 20:00:00,BTC/USDT,18981.29000000,19096.63000000,18731.00000000,18902.39000000,5306.78826700,100166685.11258753,97964\n1606330800000,2020-11-25 19:00:00,BTC/USDT,19056.68000000,19125.00000000,18896.28000000,18981.29000000,2787.53146900,52983936.16313788,63363\n1606327200000,2020-11-25 18:00:00,BTC/USDT,19061.31000000,19125.53000000,19032.36000000,19056.67000000,2685.78376400,51249520.70377561,51679\n1606323600000,2020-11-25 17:00:00,BTC/USDT,18988.65000000,19070.44000000,18811.00000000,19061.31000000,4276.60274100,81054930.27773966,73395\n1606320000000,2020-11-25 16:00:00,BTC/USDT,19126.84000000,19196.33000000,18856.00000000,18988.62000000,6463.48902600,123005659.85363399,106949\n1606316400000,2020-11-25 15:00:00,BTC/USDT,19269.25000000,19327.80000000,19115.79000000,19126.84000000,4413.79032700,84871683.86346367,79532\n1606312800000,2020-11-25 14:00:00,BTC/USDT,19197.25000000,19314.17000000,19114.46000000,19268.99000000,4166.86435500,80014324.38539326,76043\n1606309200000,2020-11-25 13:00:00,BTC/USDT,19291.60000000,19484.21000000,19100.00000000,19198.55000000,6709.29080100,129500619.27295897,106327\n1606305600000,2020-11-25 12:00:00,BTC/USDT,19281.55000000,19315.00000000,19152.33000000,19291.59000000,3616.42369800,69573250.71162238,73627\n1606302000000,2020-11-25 11:00:00,BTC/USDT,19287.88000000,19345.99000000,19254.00000000,19281.55000000,3365.50591300,64951768.21764395,68970\n1606298400000,2020-11-25 10:00:00,BTC/USDT,19169.79000000,19338.97000000,19157.09000000,19287.88000000,4231.16594700,81509289.18360995,73886\n1606294800000,2020-11-25 09:00:00,BTC/USDT,19115.18000000,19173.38000000,19055.69000000,19169.79000000,2790.62279300,53337272.75711253,56273\n1606291200000,2020-11-25 08:00:00,BTC/USDT,18975.12000000,19218.00000000,18970.01000000,19115.18000000,3930.52421800,75192045.81300255,65867\n1606287600000,2020-11-25 07:00:00,BTC/USDT,18895.00000000,19050.73000000,18833.49000000,18975.13000000,2892.80204200,54882001.31140709,54525\n1606284000000,2020-11-25 06:00:00,BTC/USDT,18879.50000000,18936.42000000,18847.09000000,18895.00000000,2561.57281800,48389556.43928188,52490\n1606280400000,2020-11-25 05:00:00,BTC/USDT,18685.82000000,18887.61000000,18625.00000000,18879.49000000,3511.59473200,65941907.15845909,61483\n1606276800000,2020-11-25 04:00:00,BTC/USDT,18808.04000000,18921.45000000,18670.00000000,18685.82000000,4245.38899400,79867174.67549952,77395\n1606273200000,2020-11-25 03:00:00,BTC/USDT,18975.81000000,19049.99000000,18734.22000000,18808.03000000,3923.89541400,74177944.13612140,77332\n1606269600000,2020-11-25 02:00:00,BTC/USDT,19099.63000000,19099.63000000,18900.00000000,18975.81000000,3381.45349300,64254627.02475431,67957\n1606266000000,2020-11-25 01:00:00,BTC/USDT,19161.74000000,19218.12000000,19065.29000000,19099.63000000,2381.86013000,45593463.06519785,52665\n1606262400000,2020-11-25 00:00:00,BTC/USDT,19160.00000000,19218.00000000,19090.19000000,19161.74000000,2546.62356000,48828210.47393982,58695\n1606258800000,2020-11-24 23:00:00,BTC/USDT,19123.23000000,19190.00000000,19082.53000000,19160.01000000,2583.58056300,49440686.86752832,53849\n1606255200000,2020-11-24 22:00:00,BTC/USDT,18939.30000000,19130.00000000,18857.00000000,19123.23000000,1921.09903600,36551150.97944145,71511\n1606251600000,2020-11-24 21:00:00,BTC/USDT,19057.69000000,19122.27000000,18842.31000000,18939.30000000,4034.76875400,76594800.01146727,79274\n1606248000000,2020-11-24 20:00:00,BTC/USDT,19160.01000000,19248.28000000,18925.51000000,19057.68000000,4865.16478300,92843160.06471350,92229\n1606244400000,2020-11-24 19:00:00,BTC/USDT,19254.42000000,19277.63000000,19160.00000000,19160.01000000,2255.86804600,43404115.90239763,52259\n1606240800000,2020-11-24 18:00:00,BTC/USDT,19200.91000000,19262.97000000,19150.00000000,19254.42000000,2986.20041500,57365488.31257768,60663\n1606237200000,2020-11-24 17:00:00,BTC/USDT,19296.14000000,19339.70000000,19138.49000000,19204.64000000,3502.42500200,67431236.26948610,73373\n1606233600000,2020-11-24 16:00:00,BTC/USDT,19358.56000000,19410.00000000,19074.40000000,19296.13000000,6567.11285500,126651587.79822455,119990\n1606230000000,2020-11-24 15:00:00,BTC/USDT,19384.43000000,19418.97000000,19220.00000000,19358.57000000,5551.38849200,107352060.93941532,87156\n1606226400000,2020-11-24 14:00:00,BTC/USDT,19167.81000000,19389.00000000,19095.00000000,19384.43000000,5022.50903600,96718507.74183623,91875\n1606222800000,2020-11-24 13:00:00,BTC/USDT,19176.05000000,19300.00000000,19092.00000000,19167.81000000,7269.05069600,139773137.72358722,119072\n1606219200000,2020-11-24 12:00:00,BTC/USDT,19064.99000000,19247.86000000,18979.99000000,19176.06000000,8267.52594000,158215914.59078368,134458\n1606215600000,2020-11-24 11:00:00,BTC/USDT,18886.99000000,19080.19000000,18810.00000000,19064.99000000,4335.56778300,82152081.89749465,92309\n1606212000000,2020-11-24 10:00:00,BTC/USDT,18983.16000000,19100.00000000,18683.64000000,18887.00000000,8744.47793400,165336683.57234790,146682\n1606208400000,2020-11-24 09:00:00,BTC/USDT,18613.80000000,19065.00000000,18552.20000000,18983.15000000,12920.75323700,243481393.76678015,185866\n1606204800000,2020-11-24 08:00:00,BTC/USDT,18369.35000000,18615.84000000,18369.35000000,18613.80000000,4419.93030800,81812384.77934830,92439\n1606201200000,2020-11-24 07:00:00,BTC/USDT,18394.31000000,18450.00000000,18360.00000000,18369.36000000,3381.99992800,62236733.68761414,78751\n1606197600000,2020-11-24 06:00:00,BTC/USDT,18322.38000000,18394.28000000,18018.00000000,18394.28000000,5807.60306900,106017684.71479534,128433\n1606194000000,2020-11-24 05:00:00,BTC/USDT,18441.19000000,18441.96000000,18290.00000000,18322.38000000,3372.83377500,61950258.16873918,82609\n1606190400000,2020-11-24 04:00:00,BTC/USDT,18338.23000000,18448.00000000,18276.73000000,18441.19000000,3461.35300100,63577339.55664232,72391\n1606186800000,2020-11-24 03:00:00,BTC/USDT,18337.04000000,18404.45000000,18303.00000000,18338.52000000,2332.13385500,42818485.69563627,52393\n1606183200000,2020-11-24 02:00:00,BTC/USDT,18465.97000000,18483.65000000,18337.04000000,18337.04000000,2922.15191500,53785234.42399040,64148\n1606179600000,2020-11-24 01:00:00,BTC/USDT,18367.54000000,18530.00000000,18334.71000000,18465.98000000,4006.28761300,73987161.98772550,78992\n1606176000000,2020-11-24 00:00:00,BTC/USDT,18368.01000000,18382.06000000,18207.98000000,18367.54000000,3049.72320500,55804161.00536818,69140\n1606172400000,2020-11-23 23:00:00,BTC/USDT,18415.71000000,18436.22000000,18262.00000000,18368.00000000,2846.90880300,52227715.70501353,75942\n1606168800000,2020-11-23 22:00:00,BTC/USDT,18410.29000000,18472.27000000,18350.00000000,18415.70000000,1883.70994700,34681179.63835348,68761\n1606165200000,2020-11-23 21:00:00,BTC/USDT,18373.00000000,18457.04000000,18326.08000000,18410.29000000,2872.44722900,52845292.25815554,64506\n1606161600000,2020-11-23 20:00:00,BTC/USDT,18444.45000000,18485.01000000,18345.52000000,18373.00000000,2094.07200400,38544581.99366566,50873\n1606158000000,2020-11-23 19:00:00,BTC/USDT,18346.23000000,18459.99000000,18300.10000000,18444.44000000,2296.23215900,42203837.01281946,53665\n1606154400000,2020-11-23 18:00:00,BTC/USDT,18352.93000000,18418.62000000,18283.43000000,18346.23000000,2307.17565900,42331573.25702155,50905\n1606150800000,2020-11-23 17:00:00,BTC/USDT,18376.61000000,18461.91000000,18323.06000000,18352.93000000,2961.66192800,54443006.70162753,62216\n1606147200000,2020-11-23 16:00:00,BTC/USDT,18261.86000000,18410.55000000,18134.11000000,18376.42000000,4920.69113400,89972362.43023005,97616\n1606143600000,2020-11-23 15:00:00,BTC/USDT,18530.32000000,18557.04000000,18222.22000000,18261.86000000,6433.30982700,118186715.39524217,110672\n1606140000000,2020-11-23 14:00:00,BTC/USDT,18594.27000000,18700.00000000,18502.01000000,18530.07000000,4756.82355400,88481268.66264780,93831\n1606136400000,2020-11-23 13:00:00,BTC/USDT,18619.88000000,18662.15000000,18331.50000000,18594.27000000,7020.73309300,129724042.61576464,95359\n1606132800000,2020-11-23 12:00:00,BTC/USDT,18567.70000000,18645.00000000,18480.07000000,18619.88000000,3346.11722400,62126748.05015691,63940\n1606129200000,2020-11-23 11:00:00,BTC/USDT,18747.03000000,18747.89000000,18560.00000000,18567.70000000,3580.10218700,66733387.25171788,70197\n1606125600000,2020-11-23 10:00:00,BTC/USDT,18637.80000000,18766.00000000,18636.41000000,18747.04000000,3953.70076200,73949829.32750978,76569\n1606122000000,2020-11-23 09:00:00,BTC/USDT,18575.01000000,18657.88000000,18534.12000000,18637.79000000,3068.09191700,57072099.39039431,58401\n1606118400000,2020-11-23 08:00:00,BTC/USDT,18458.12000000,18660.00000000,18453.55000000,18575.01000000,3674.18164100,68313958.65326358,68984\n1606114800000,2020-11-23 07:00:00,BTC/USDT,18519.99000000,18556.83000000,18428.39000000,18458.13000000,2479.15684700,45828894.58419206,50975\n1606111200000,2020-11-23 06:00:00,BTC/USDT,18438.26000000,18520.00000000,18308.00000000,18520.00000000,3063.31651900,56354619.47259563,52943\n1606107600000,2020-11-23 05:00:00,BTC/USDT,18461.19000000,18509.98000000,18381.14000000,18438.26000000,2315.74416800,42749697.13937960,46802\n1606104000000,2020-11-23 04:00:00,BTC/USDT,18367.49000000,18515.45000000,18337.04000000,18461.19000000,2543.77378500,46916950.77168609,50727\n1606100400000,2020-11-23 03:00:00,BTC/USDT,18210.49000000,18436.70000000,18180.14000000,18369.00000000,2749.24658100,50410233.27844853,54145\n1606096800000,2020-11-23 02:00:00,BTC/USDT,18211.56000000,18349.05000000,18188.26000000,18210.50000000,2722.72240900,49732722.77923737,49826\n1606093200000,2020-11-23 01:00:00,BTC/USDT,18136.31000000,18273.08000000,18000.00000000,18209.51000000,4681.48116500,84908122.41769941,77543\n1606089600000,2020-11-23 00:00:00,BTC/USDT,18413.88000000,18523.09000000,18100.05000000,18136.30000000,4390.10555100,80325480.88262096,83981\n1606086000000,2020-11-22 23:00:00,BTC/USDT,18613.41000000,18623.37000000,18367.82000000,18414.43000000,4034.86920200,74528078.39855852,66753\n1606082400000,2020-11-22 22:00:00,BTC/USDT,18567.05000000,18649.00000000,18517.50000000,18613.41000000,1505.65348700,28008957.21371000,50631\n1606078800000,2020-11-22 21:00:00,BTC/USDT,18548.95000000,18614.34000000,18490.00000000,18567.04000000,1563.08026500,29035450.50438254,40044\n1606075200000,2020-11-22 20:00:00,BTC/USDT,18573.59000000,18683.77000000,18506.00000000,18550.21000000,3206.15154100,59625725.32386334,72224\n1606071600000,2020-11-22 19:00:00,BTC/USDT,18504.89000000,18593.06000000,18487.52000000,18573.58000000,2107.34606900,39078721.21159252,54205\n1606068000000,2020-11-22 18:00:00,BTC/USDT,18480.00000000,18630.03000000,18468.17000000,18504.89000000,3087.05176400,57280013.37845470,55608\n1606064400000,2020-11-22 17:00:00,BTC/USDT,18554.93000000,18580.00000000,18444.40000000,18480.00000000,2847.52812500,52699019.75810867,46687\n1606060800000,2020-11-22 16:00:00,BTC/USDT,18295.41000000,18589.96000000,18258.11000000,18554.93000000,4155.17041600,76627794.39203574,68561\n1606057200000,2020-11-22 15:00:00,BTC/USDT,18179.07000000,18333.52000000,18178.53000000,18295.40000000,2220.81287500,40577333.31836239,47267\n1606053600000,2020-11-22 14:00:00,BTC/USDT,18196.36000000,18296.04000000,18142.21000000,18179.07000000,2890.91744400,52671694.20845110,53937\n1606050000000,2020-11-22 13:00:00,BTC/USDT,18107.25000000,18240.00000000,18049.26000000,18196.37000000,3634.63097300,66023218.34522694,67842\n1606046400000,2020-11-22 12:00:00,BTC/USDT,17891.76000000,18158.55000000,17820.00000000,18107.25000000,5916.45639100,106695343.18781003,95313\n1606042800000,2020-11-22 11:00:00,BTC/USDT,18172.44000000,18218.81000000,17610.86000000,17887.82000000,9274.53096000,165911150.18051982,130750\n1606039200000,2020-11-22 10:00:00,BTC/USDT,18379.06000000,18379.06000000,18051.00000000,18172.45000000,3764.87366900,68502012.35455606,66633\n1606035600000,2020-11-22 09:00:00,BTC/USDT,18211.66000000,18419.71000000,18121.00000000,18379.07000000,4004.58523500,73291187.11284343,75543\n1606032000000,2020-11-22 08:00:00,BTC/USDT,18498.31000000,18503.14000000,18185.86000000,18211.65000000,4029.65147700,73900600.50218578,77480\n1606028400000,2020-11-22 07:00:00,BTC/USDT,18481.99000000,18511.55000000,18255.93000000,18498.31000000,3648.26248800,67127663.23033975,67583\n1606024800000,2020-11-22 06:00:00,BTC/USDT,18517.31000000,18588.86000000,18400.30000000,18481.99000000,2206.75293300,40777060.23651072,48637\n1606021200000,2020-11-22 05:00:00,BTC/USDT,18602.45000000,18605.96000000,18478.01000000,18517.31000000,1559.84019500,28900830.78638501,33900\n1606017600000,2020-11-22 04:00:00,BTC/USDT,18559.91000000,18611.00000000,18430.68000000,18602.46000000,1570.51797500,29096709.50523353,38649\n1606014000000,2020-11-22 03:00:00,BTC/USDT,18583.91000000,18593.48000000,18492.39000000,18559.91000000,1911.45564500,35444170.82444279,39207\n1606010400000,2020-11-22 02:00:00,BTC/USDT,18470.39000000,18602.99000000,18430.83000000,18583.91000000,2389.61582900,44264815.01808885,54725\n1606006800000,2020-11-22 01:00:00,BTC/USDT,18547.84000000,18612.04000000,18350.00000000,18470.40000000,6262.82206700,115728023.57554851,96234\n1606003200000,2020-11-22 00:00:00,BTC/USDT,18703.80000000,18750.00000000,18500.00000000,18547.96000000,3853.16075300,71866977.48683207,80111\n1605999600000,2020-11-21 23:00:00,BTC/USDT,18636.95000000,18745.47000000,18600.00000000,18703.80000000,2920.57705900,54564417.43695964,59799' From 831b0affa287b1e682aa9cd1c3f04c5bdf51d6f0 Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Fri, 15 Jan 2021 12:05:36 +0100 Subject: [PATCH 18/34] feat: use scalings --- giraffe/src/components/Candlestick.tsx | 152 -------------------- giraffe/src/components/CandlestickLayer.tsx | 132 ++++++++++++++++- giraffe/src/constants/candlestickStyles.ts | 2 +- giraffe/src/transforms/candlestick.ts | 38 +++-- giraffe/src/types/index.ts | 2 +- 5 files changed, 147 insertions(+), 179 deletions(-) delete mode 100644 giraffe/src/components/Candlestick.tsx diff --git a/giraffe/src/components/Candlestick.tsx b/giraffe/src/components/Candlestick.tsx deleted file mode 100644 index 1d33008b..00000000 --- a/giraffe/src/components/Candlestick.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import React from 'react' -import {CANDLESTICK_THEME_DARK} from '../constants/candlestickStyles' -import {CandlestickLayerConfig, CandleStyle} from '../types' - -interface CandleValue { - open: number - close: number - high: number - low: number -} - -interface CandlestickProps { - theme: Partial - values: CandleValue[] - width: number - height: number -} - -interface CandleProps { - theme: CandlestickLayerConfig - value: CandleValue - /** width of candle */ - width: number - /** returns height position from value */ - heightPositionFormatter: (value: number) => number -} - -const Candle: React.FC = ({ - heightPositionFormatter, - theme, - value, - width, -}) => { - const {close, high, low, open}: CandleValue = { - close: heightPositionFormatter(value.close), - high: heightPositionFormatter(value.high), - low: heightPositionFormatter(value.low), - open: heightPositionFormatter(value.open), - } - - const isRaise = open >= close - - const { - bodyColor, - bodyFillOpacity, - bodyRounding, - bodyStrokeWidth, - shadowColor, - shadowStrokeWidth, - }: CandleStyle = theme[isRaise ? 'candleRaising' : 'candleDecreasing'] - - const height = Math.abs(open - close) - const y = Math.min(open, close) - - const centerY = width / 2 - - return ( - <> - - {value.high > Math.max(value.open, value.close) && ( - - )} - {value.low < Math.min(value.open, value.close) && ( - - )} - - ) -} - -export const Candlestick: React.FC = ({ - theme: _theme, - values, - width, - height, -}) => { - const theme: CandlestickLayerConfig = { - ...CANDLESTICK_THEME_DARK, - ..._theme, - } - - const { - candlePadding, - // candleDecreasing, candleRaising, mode - } = theme - - const candles = values.length - const candleWidthStep = width / candles - const candleWidth = candleWidthStep - candlePadding - // todo: style - const hegihtRange = height - 2 - // const maxHeight = minHeight + hegihtRange - - const allValues = values.map(x => [x.open, x.close, x.high, x.low]).flat() - const minValue = Math.min(...allValues) - const maxValue = Math.max(...allValues) - const valueRange = maxValue - minValue - - const heightPositionFormatter: CandleProps['heightPositionFormatter'] = ( - value: number - ) => { - const valueFrac = (value - minValue) / valueRange - return (1 - valueFrac) * hegihtRange - } - - return ( - <> - - {values.map((x, i) => ( - <> - - - - - ))} - - - ) -} diff --git a/giraffe/src/components/CandlestickLayer.tsx b/giraffe/src/components/CandlestickLayer.tsx index a62e4f3d..1be57314 100644 --- a/giraffe/src/components/CandlestickLayer.tsx +++ b/giraffe/src/components/CandlestickLayer.tsx @@ -3,9 +3,93 @@ import {FunctionComponent} from 'react' import { CandlestickLayerConfig, CandlestickLayerSpec, + CandleStyle, LayerProps, } from '../types' -import {Candlestick} from './Candlestick' +import {CANDLESTICK_THEME_DARK} from '../constants/candlestickStyles' + +interface CandleValue { + open: number + close: number + high: number + low: number +} + +interface CandleProps { + theme: CandlestickLayerConfig + candle: CandleValue + /** width of candle */ + width: number + /** returns height position from value */ + heightPositionFormatter: (value: number) => number +} + +const Candle: React.FC = ({ + heightPositionFormatter, + theme, + candle, + width, +}) => { + const {close, high, low, open}: CandleValue = { + close: heightPositionFormatter(candle.close), + high: heightPositionFormatter(candle.high), + low: heightPositionFormatter(candle.low), + open: heightPositionFormatter(candle.open), + } + + const isRaise = open >= close + + const { + bodyColor, + bodyFillOpacity, + bodyRounding, + bodyStrokeWidth, + shadowColor, + shadowStrokeWidth, + }: CandleStyle = theme[isRaise ? 'candleRaising' : 'candleDecreasing'] + + const height = Math.abs(open - close) + const y = Math.min(open, close) + + const centerY = width / 2 + + return ( + <> + + {candle.high > Math.max(candle.open, candle.close) && ( + + )} + {candle.low < Math.min(candle.open, candle.close) && ( + + )} + + ) +} export interface Props extends LayerProps { config: CandlestickLayerConfig @@ -14,9 +98,51 @@ export interface Props extends LayerProps { //todo: only proxies props into Candlestick ? if true -> candlestick should be implemented here export const CandlestickLayer: FunctionComponent = props => { - const {config, width, height, spec} = props + const {config: _theme, width, height, spec, xScale, yScale} = props + // todo default values already present in _theme ? + const theme: CandlestickLayerConfig = { + ...CANDLESTICK_THEME_DARK, + ..._theme, + } + + const {candlePadding} = theme + + const {values, calculatedWindow} = spec + + const getXSVGCoords = (xKey: number) => xScale(xKey * calculatedWindow) + // (xKey: number) => ((xKey * calculatedWindow - xMin) / xLen) * width + + const candleWidth = + Math.round(Math.abs((xScale(0) - xScale(calculatedWindow)) * 100)) / 100 - + candlePadding + // todo: style + // const maxHeight = minHeight + hegihtRange + + const heightPositionFormatter: CandleProps['heightPositionFormatter'] = yScale return ( - + <> + + {Object.entries(values) + .map(([xKey, candle]) => [+xKey, candle] as const) + .map(([xKey, candle]) => ( + <> + + + + + ))} + + ) } diff --git a/giraffe/src/constants/candlestickStyles.ts b/giraffe/src/constants/candlestickStyles.ts index 4169ccf2..0ac0c0af 100644 --- a/giraffe/src/constants/candlestickStyles.ts +++ b/giraffe/src/constants/candlestickStyles.ts @@ -11,7 +11,7 @@ export const CANDLESTICK_THEME_DARK: Required = { closeColumnKey: 'close', window: 'detect', - candlePadding: 5, + candlePadding: 10, candleRaising: { bodyColor: InfluxColors.Krypton, diff --git a/giraffe/src/transforms/candlestick.ts b/giraffe/src/transforms/candlestick.ts index 6939de6e..5410cc07 100644 --- a/giraffe/src/transforms/candlestick.ts +++ b/giraffe/src/transforms/candlestick.ts @@ -27,8 +27,8 @@ export const candlestickTransform = ( const lowCol = inputTable.getColumn(lowColumnKey, 'number') const closeCol = inputTable.getColumn(closeColumnKey, 'number') - let xMin = Infinity - let xMax = -Infinity + let xKeyMin = Infinity + let xKeyMax = -Infinity let yMin = Infinity let yMax = -Infinity @@ -47,7 +47,7 @@ export const candlestickTransform = ( // key is index rastered by window (value/window) // [x0, x1] range of candle for selecting open/close - const valuesObj: { + const valuesWithOrigX: { [key: number]: { candle: CandlestickLayerSpec['values'][0] xRange: [number, number] @@ -62,17 +62,17 @@ export const candlestickTransform = ( const low = lowCol[i] const close = closeCol[i] - xMin = Math.min(xMin, x) - xMax = Math.max(xMax, x) + xKeyMin = Math.min(xKeyMin, xKey) + xKeyMax = Math.max(xKeyMax, xKey) yMin = Math.min(yMin, open, high, low, close) yMax = Math.max(yMax, open, high, low, close) // todo: filtering NaN ? const candle = {open, high, low, close} - if (valuesObj[xKey]) { - const {candle: candle2, xRange: x2Range} = valuesObj[xKey] + if (valuesWithOrigX[xKey]) { + const {candle: candle2, xRange: x2Range} = valuesWithOrigX[xKey] // merge candles - valuesObj[xKey] = { + valuesWithOrigX[xKey] = { candle: { close: x2Range[1] >= x ? candle2.close : candle.close, open: x2Range[0] < x ? candle2.open : candle.open, @@ -82,33 +82,27 @@ export const candlestickTransform = ( xRange: [Math.min(x, ...x2Range), Math.max(x, ...x2Range)], } } else { - valuesObj[xKey] = {candle, xRange: [x, x]} + valuesWithOrigX[xKey] = {candle, xRange: [x, x]} } } - const values: CandlestickLayerSpec['values'] = [] + const values = Object.fromEntries( + Object.entries(valuesWithOrigX).map(([x, y]) => [x, y.candle]) + ) - const xKeyMin = Math.floor(xMin / window) - const xKeyMax = Math.ceil(xMax / window) - - for (let i = xKeyMin; i <= xKeyMax; i++) { - const val = valuesObj?.[i] - if (!val) { - values.push(undefined) - } else { - values.push(val.candle) - } - } + const xMin = (xKeyMin - 0.5) * window + const xMax = (xKeyMax + 0.5) * window // todo const res = { type: 'candlestick', inputTable, - table: inputTable, values, calculatedWindow: window, xDomain: [xMin, xMax], yDomain: [yMin, yMax], + xColumnKey, + xColumnType: inputTable.getColumnType(xColumnKey), } as CandlestickLayerSpec return res as any } diff --git a/giraffe/src/types/index.ts b/giraffe/src/types/index.ts index 0525bdb4..81393c7a 100644 --- a/giraffe/src/types/index.ts +++ b/giraffe/src/types/index.ts @@ -690,7 +690,7 @@ export type OHLCValue = { close: number } -export type CandlestickLayerSpecValues = OHLCValue[] +export type CandlestickLayerSpecValues = {[key: number]: OHLCValue} export interface CandlestickLayerSpec { type: 'candlestick' // do not refactor or restrict to SpecTypes From 093cd2c88b78ca7d565ae72365434de16b55be4a Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Fri, 5 Feb 2021 11:14:51 +0100 Subject: [PATCH 19/34] feat: fences, optimisation --- giraffe/src/components/CandlestickLayer.tsx | 93 +++++++++++--- giraffe/src/constants/candlestickStyles.ts | 3 + giraffe/src/transforms/candlestick.ts | 97 ++++---------- giraffe/src/types/index.ts | 7 +- giraffe/src/utils/ohlc.ts | 132 ++++++++++++++++++++ stories/src/candlestick.stories.tsx | 3 + 6 files changed, 240 insertions(+), 95 deletions(-) create mode 100644 giraffe/src/utils/ohlc.ts diff --git a/giraffe/src/components/CandlestickLayer.tsx b/giraffe/src/components/CandlestickLayer.tsx index 1be57314..6a67d510 100644 --- a/giraffe/src/components/CandlestickLayer.tsx +++ b/giraffe/src/components/CandlestickLayer.tsx @@ -7,6 +7,7 @@ import { LayerProps, } from '../types' import {CANDLESTICK_THEME_DARK} from '../constants/candlestickStyles' +import {OHLCResultEntry} from '../utils/ohlc' interface CandleValue { open: number @@ -53,8 +54,8 @@ const Candle: React.FC = ({ const centerY = width / 2 - return ( - <> + const body = + theme.mode === 'candles' ? ( = ({ rx={bodyRounding} ry={bodyRounding} > + ) : ( + <> + + + + + ) + + const shadow = ( + <> {candle.high > Math.max(candle.open, candle.close) && ( = ({ )} ) + + return ( + <> + {body} + {shadow} + + ) } export interface Props extends LayerProps { @@ -109,8 +151,9 @@ export const CandlestickLayer: FunctionComponent = props => { const {values, calculatedWindow} = spec - const getXSVGCoords = (xKey: number) => xScale(xKey * calculatedWindow) - // (xKey: number) => ((xKey * calculatedWindow - xMin) / xLen) * width + // todo: naming + const getXSVGCoords = xScale + const heightPositionFormatter: CandleProps['heightPositionFormatter'] = yScale const candleWidth = Math.round(Math.abs((xScale(0) - xScale(calculatedWindow)) * 100)) / 100 - @@ -118,7 +161,19 @@ export const CandlestickLayer: FunctionComponent = props => { // todo: style // const maxHeight = minHeight + hegihtRange - const heightPositionFormatter: CandleProps['heightPositionFormatter'] = yScale + const isCandleVisible = (candle: OHLCResultEntry) => { + const x = getXSVGCoords(candle.windowStart) + const yMin = heightPositionFormatter(candle.yRange[0]) + const yMax = heightPositionFormatter(candle.yRange[1]) + + return ( + x >= -candleWidth && + x <= width + candleWidth && + // svg has inversed y drawing so lower value has higher svg coords + yMax <= height && + yMin >= 0 + ) + } return ( <> @@ -127,21 +182,19 @@ export const CandlestickLayer: FunctionComponent = props => { height={height} style={{fontFamily: 'Rubik, monospace', userSelect: 'none'}} > - {Object.entries(values) - .map(([xKey, candle]) => [+xKey, candle] as const) - .map(([xKey, candle]) => ( - <> - - - - - ))} + {values.filter(isCandleVisible).map(({windowStart, value: candle}) => ( + <> + + + + + ))} ) diff --git a/giraffe/src/constants/candlestickStyles.ts b/giraffe/src/constants/candlestickStyles.ts index 0ac0c0af..9bdad2c8 100644 --- a/giraffe/src/constants/candlestickStyles.ts +++ b/giraffe/src/constants/candlestickStyles.ts @@ -9,7 +9,10 @@ export const CANDLESTICK_THEME_DARK: Required = { highColumnKey: 'high', lowColumnKey: 'low', closeColumnKey: 'close', + window: 'detect', + windowMax: 100, + windowMergeStrategy: 'caping', candlePadding: 10, diff --git a/giraffe/src/transforms/candlestick.ts b/giraffe/src/transforms/candlestick.ts index 5410cc07..fe6fb912 100644 --- a/giraffe/src/transforms/candlestick.ts +++ b/giraffe/src/transforms/candlestick.ts @@ -1,12 +1,5 @@ -import {pairs} from 'd3-array' import {CandlestickLayerConfig, CandlestickLayerSpec, Table} from '../types' -import {Sorting} from '../utils/array' - -// Window auto-detection will select n-th smallest distance based on collection size, -// to ensure skipping possible anomalies with smaller window size -const WINDOW_DETECT_NTH_SMALLEST_DIST_PERCENT = 5 - -const {ascending} = Sorting +import {calculateWindow, getOhlcValues} from '../utils/ohlc' export const candlestickTransform = ( inputTable: Table, @@ -20,80 +13,40 @@ export const candlestickTransform = ( closeColumnKey, } = layerConfig - const xCol = inputTable.getColumn(xColumnKey, 'number') - - const openCol = inputTable.getColumn(openColumnKey, 'number') - const highCol = inputTable.getColumn(highColumnKey, 'number') - const lowCol = inputTable.getColumn(lowColumnKey, 'number') - const closeCol = inputTable.getColumn(closeColumnKey, 'number') + const columns = { + xColumnKey, + openColumnKey, + highColumnKey, + lowColumnKey, + closeColumnKey, + } - let xKeyMin = Infinity - let xKeyMax = -Infinity - let yMin = Infinity - let yMax = -Infinity + const xCol = inputTable.getColumn(xColumnKey, 'number') // distence between two values - const window = - typeof layerConfig.window === 'number' - ? layerConfig.window - : pairs([...xCol].sort(ascending)) - .map(([x, y]) => y - x) - .filter(x => x !== 0 && !Number.isNaN(x)) - .sort(ascending)[ - Math.floor( - (xCol.length * WINDOW_DETECT_NTH_SMALLEST_DIST_PERCENT) / 100 - ) - ] + const window = calculateWindow(layerConfig.window, xCol) - // key is index rastered by window (value/window) - // [x0, x1] range of candle for selecting open/close - const valuesWithOrigX: { - [key: number]: { - candle: CandlestickLayerSpec['values'][0] - xRange: [number, number] - } - } = {} - for (let i = 0; i < inputTable.length; i++) { - const x = xCol[i] - const xKey = Math.round(x / window) + const values = getOhlcValues(inputTable, columns, window) - const open = openCol[i] - const high = highCol[i] - const low = lowCol[i] - const close = closeCol[i] - - xKeyMin = Math.min(xKeyMin, xKey) - xKeyMax = Math.max(xKeyMax, xKey) - yMin = Math.min(yMin, open, high, low, close) - yMax = Math.max(yMax, open, high, low, close) + let xMin = values[0].windowStart + let xMax = values[0].windowStart + let yMin = Infinity + let yMax = -Infinity - // todo: filtering NaN ? - const candle = {open, high, low, close} - if (valuesWithOrigX[xKey]) { - const {candle: candle2, xRange: x2Range} = valuesWithOrigX[xKey] - // merge candles - valuesWithOrigX[xKey] = { - candle: { - close: x2Range[1] >= x ? candle2.close : candle.close, - open: x2Range[0] < x ? candle2.open : candle.open, - high: Math.max(candle2.high, candle.high), - low: Math.min(candle2.low, candle.low), - }, - xRange: [Math.min(x, ...x2Range), Math.max(x, ...x2Range)], - } - } else { - valuesWithOrigX[xKey] = {candle, xRange: [x, x]} + values.forEach(({windowStart, yRange}) => { + if (windowStart < xMin) { + xMin = windowStart + } else if (windowStart > xMax) { + xMax = windowStart } - } - const values = Object.fromEntries( - Object.entries(valuesWithOrigX).map(([x, y]) => [x, y.candle]) - ) + yMin = Math.min(yMin, yRange[0]) + yMax = Math.max(yMax, yRange[1]) + }) - const xMin = (xKeyMin - 0.5) * window - const xMax = (xKeyMax + 0.5) * window + // xMax is window start but we want to show whole window + xMax += window - // todo const res = { type: 'candlestick', inputTable, diff --git a/giraffe/src/types/index.ts b/giraffe/src/types/index.ts index 81393c7a..06b2ff73 100644 --- a/giraffe/src/types/index.ts +++ b/giraffe/src/types/index.ts @@ -3,6 +3,7 @@ import {CSSProperties, ReactNode} from 'react' import {TimeZone} from './timeZones' import {GeoLayerConfig} from './geo' import {FormatStatValueOptions} from '../utils/formatStatValue' +import {OHLCResultEntry} from '../utils/ohlc' export type SizedConfig = Config & {width: number; height: number} export interface Config { @@ -275,6 +276,8 @@ export interface CandlestickLayerConfig { highColumnKey?: string window?: number | 'detect' + windowMax?: number + windowMergeStrategy?: 'caping' | 'halving' } export interface GaugeLayerConfig { @@ -690,13 +693,11 @@ export type OHLCValue = { close: number } -export type CandlestickLayerSpecValues = {[key: number]: OHLCValue} - export interface CandlestickLayerSpec { type: 'candlestick' // do not refactor or restrict to SpecTypes inputTable: Table calculatedWindow: number // distance between two values - values: CandlestickLayerSpecValues + values: OHLCResultEntry[] xDomain: number[] yDomain: number[] diff --git a/giraffe/src/utils/ohlc.ts b/giraffe/src/utils/ohlc.ts new file mode 100644 index 00000000..718e163d --- /dev/null +++ b/giraffe/src/utils/ohlc.ts @@ -0,0 +1,132 @@ +import {pairs} from 'd3-array' +import { + CandlestickLayerConfig, + NumericColumnData, + OHLCValue, + Table, +} from '../types' +import {Sorting} from './array' + +const {ascending} = Sorting + +// Window auto-detection will select n-th smallest distance based on collection size, +// to ensure skipping possible anomalies with smaller window size +const WINDOW_DETECT_NTH_SMALLEST_DIST_PERCENT = 5 + +type OHLCColumns = { + xColumnKey: string + openColumnKey: string + highColumnKey: string + lowColumnKey: string + closeColumnKey: string +} + +export type Range = [number, number] + +export type OHLCResultEntry = { + windowStart: number + xRange: Range + yRange: Range + entriesTableIndexes: number[] + value: OHLCValue +} + +export const getOhlcValues = ( + table: Table, + columns: OHLCColumns, + window: number +): OHLCResultEntry[] => { + const { + xColumnKey, + openColumnKey, + highColumnKey, + lowColumnKey, + closeColumnKey, + } = columns + const xCol = table.getColumn(xColumnKey, 'number') + + const obj: { + [key: number]: number[] + } = {} + for (let i = 0; i < table.length; i++) { + const x = xCol[i] + const xKey = Math.round(x / window) * window + + const targetArray = obj[xKey] + if (targetArray) { + targetArray.push(i) + } else { + obj[xKey] = [i] + } + } + + const openCol = table.getColumn(openColumnKey, 'number') + const highCol = table.getColumn(highColumnKey, 'number') + const lowCol = table.getColumn(lowColumnKey, 'number') + const closeCol = table.getColumn(closeColumnKey, 'number') + + return Object.entries(obj) + .map(([a, b]) => [+a, b] as const) + .map(([windowStart, entriesTableIndexes]) => { + let close = 0 + let high = -Infinity + let low = Infinity + let open = 0 + + let xMin = Infinity + let xMax = -Infinity + let yMin = Infinity + let yMax = -Infinity + + entriesTableIndexes.forEach(i => { + const x = xCol[i] + const o = openCol[i] + const h = highCol[i] + const l = lowCol[i] + const c = closeCol[i] + + xMin = Math.min(xMin, x) + xMax = Math.max(xMax, x) + yMin = Math.min(yMin, o, h, l, c) + yMax = Math.max(yMax, o, h, l, c) + + if (xMin === x) { + open = o + } + if (xMax === x) { + close = c + } + high = Math.max(high, h) + low = Math.min(low, l) + }) + + return { + windowStart, + entriesTableIndexes, + xRange: [xMin, xMax], + yRange: [yMin, yMax], + value: { + open, + high, + low, + close, + }, + } + }) +} + +export const calculateWindow = ( + window: number | 'detect', + xCol: NumericColumnData +): number => { + return typeof window === 'number' + ? window + : pairs([...xCol].sort(ascending)) + .map(([x, y]) => y - x) + .filter(x => x !== 0 && !Number.isNaN(x)) + .sort(ascending)[ + Math.floor( + (xCol.length * WINDOW_DETECT_NTH_SMALLEST_DIST_PERCENT) / 100 + ) + ] +} diff --git a/stories/src/candlestick.stories.tsx b/stories/src/candlestick.stories.tsx index c053a946..5b4974a5 100644 --- a/stories/src/candlestick.stories.tsx +++ b/stories/src/candlestick.stories.tsx @@ -102,6 +102,9 @@ const editableLayer = (theme: Theme): Theme => ({ if (detect) return 'detect' return number('window fixed', typeof theme.window === 'number' || 1_000) })(), + windowMax: number('windowMax', theme.windowMax), + // todo: + windowMergeStrategy: 'caping', }) const createStory = (theme: Theme, csv: string) => () => { From c9955998566fc0daa4c5c8de3716f88324bb7dac Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Tue, 16 Feb 2021 13:41:16 +0100 Subject: [PATCH 20/34] feat: candlestick tooltip --- .../src/components/CandlestickHoverLayer.tsx | 82 +++++++++++++++++++ giraffe/src/components/CandlestickLayer.tsx | 18 +++- giraffe/src/transforms/candlestick.ts | 11 ++- giraffe/src/utils/ohlc.ts | 7 +- 4 files changed, 107 insertions(+), 11 deletions(-) create mode 100644 giraffe/src/components/CandlestickHoverLayer.tsx diff --git a/giraffe/src/components/CandlestickHoverLayer.tsx b/giraffe/src/components/CandlestickHoverLayer.tsx new file mode 100644 index 00000000..6efde44d --- /dev/null +++ b/giraffe/src/components/CandlestickHoverLayer.tsx @@ -0,0 +1,82 @@ +import React, {FunctionComponent} from 'react' + +import {Tooltip} from './Tooltip' +import {CandlestickLayerProps} from './CandlestickLayer' +import {TooltipData} from '../types' + +const createCheckMouseBounding = ({ + height, + width, +}: { + width: number + height: number +}) => ({mouseX, mouseY}: {mouseX: number; mouseY: number}) => { + return ( + mouseX !== undefined && + mouseX !== null && + mouseX >= 0 && + mouseX < width && + mouseY !== undefined && + mouseY !== null && + mouseY >= 0 && + mouseY < height + ) +} + +export const CandlestickHoverLayer: FunctionComponent = props => { + const { + plotConfig, + spec: {values, calculatedWindow, table}, + config: { + xColumnKey, + openColumnKey, + highColumnKey, + lowColumnKey, + closeColumnKey, + }, + xScale, + hoverX, + hoverY, + columnFormatter, + } = props + + const checkMouseBounding = createCheckMouseBounding(props) + + if (!checkMouseBounding({mouseX: hoverX, mouseY: hoverY})) { + return null + } + + const xIndex = + Math.round(xScale.invert(hoverX) / calculatedWindow) * calculatedWindow + + const value = values.find(x => x.windowStart === xIndex) + + if (!value) { + return null + } + + const tableIndexes = value.entriesTableIndexes + + const tooltipData: TooltipData = [ + xColumnKey, + openColumnKey, + highColumnKey, + lowColumnKey, + closeColumnKey, + ].map(key => { + const col = table.getColumn(key) + const type = table.getColumnType(key) + const formater = columnFormatter(key) + return { + colors: [], + key, + name: key, + type, + values: tableIndexes.map(i => formater(col[i])), + } + }) + + return +} + +CandlestickHoverLayer.displayName = 'ScatterHoverLayer' diff --git a/giraffe/src/components/CandlestickLayer.tsx b/giraffe/src/components/CandlestickLayer.tsx index 6a67d510..374b17a5 100644 --- a/giraffe/src/components/CandlestickLayer.tsx +++ b/giraffe/src/components/CandlestickLayer.tsx @@ -8,6 +8,8 @@ import { } from '../types' import {CANDLESTICK_THEME_DARK} from '../constants/candlestickStyles' import {OHLCResultEntry} from '../utils/ohlc' +import {useHoverPointIndices} from '../utils/useHoverPointIndices' +import {CandlestickHoverLayer} from './CandlestickHoverLayer' interface CandleValue { open: number @@ -16,7 +18,7 @@ interface CandleValue { low: number } -interface CandleProps { +export interface CandleProps { theme: CandlestickLayerConfig candle: CandleValue /** width of candle */ @@ -138,9 +140,20 @@ export interface Props extends LayerProps { spec: CandlestickLayerSpec } +export type CandlestickLayerProps = Props + //todo: only proxies props into Candlestick ? if true -> candlestick should be implemented here export const CandlestickLayer: FunctionComponent = props => { - const {config: _theme, width, height, spec, xScale, yScale} = props + const { + config: _theme, + width, + height, + spec, + xScale, + yScale, + hoverX, + hoverY, + } = props // todo default values already present in _theme ? const theme: CandlestickLayerConfig = { ...CANDLESTICK_THEME_DARK, @@ -196,6 +209,7 @@ export const CandlestickLayer: FunctionComponent = props => { ))} + ) } diff --git a/giraffe/src/transforms/candlestick.ts b/giraffe/src/transforms/candlestick.ts index fe6fb912..71e0bc57 100644 --- a/giraffe/src/transforms/candlestick.ts +++ b/giraffe/src/transforms/candlestick.ts @@ -47,15 +47,20 @@ export const candlestickTransform = ( // xMax is window start but we want to show whole window xMax += window - const res = { + const yColumnKey = openColumnKey + + const res: CandlestickLayerSpec = { type: 'candlestick', inputTable, + table: inputTable, values, calculatedWindow: window, xDomain: [xMin, xMax], yDomain: [yMin, yMax], xColumnKey, xColumnType: inputTable.getColumnType(xColumnKey), - } as CandlestickLayerSpec - return res as any + yColumnKey, + yColumnType: inputTable.getColumnType(yColumnKey), + } + return res } diff --git a/giraffe/src/utils/ohlc.ts b/giraffe/src/utils/ohlc.ts index 718e163d..0959fc73 100644 --- a/giraffe/src/utils/ohlc.ts +++ b/giraffe/src/utils/ohlc.ts @@ -1,10 +1,5 @@ import {pairs} from 'd3-array' -import { - CandlestickLayerConfig, - NumericColumnData, - OHLCValue, - Table, -} from '../types' +import {NumericColumnData, OHLCValue, Table} from '../types' import {Sorting} from './array' const {ascending} = Sorting From a2fc96bb243bb6a6f44b142f8e942c09b5ef87af Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Wed, 17 Feb 2021 09:16:44 +0100 Subject: [PATCH 21/34] feat: highlight hovered item --- .../src/components/CandlestickHoverLayer.tsx | 82 ---------------- .../CandlestickHoverTooltipLayer.tsx | 93 +++++++++++++++++++ giraffe/src/components/CandlestickLayer.tsx | 40 +++++--- giraffe/src/constants/candlestickStyles.ts | 10 ++ giraffe/src/types/index.ts | 2 + stories/src/candlestick.stories.tsx | 5 +- 6 files changed, 134 insertions(+), 98 deletions(-) delete mode 100644 giraffe/src/components/CandlestickHoverLayer.tsx create mode 100644 giraffe/src/components/CandlestickHoverTooltipLayer.tsx diff --git a/giraffe/src/components/CandlestickHoverLayer.tsx b/giraffe/src/components/CandlestickHoverLayer.tsx deleted file mode 100644 index 6efde44d..00000000 --- a/giraffe/src/components/CandlestickHoverLayer.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import React, {FunctionComponent} from 'react' - -import {Tooltip} from './Tooltip' -import {CandlestickLayerProps} from './CandlestickLayer' -import {TooltipData} from '../types' - -const createCheckMouseBounding = ({ - height, - width, -}: { - width: number - height: number -}) => ({mouseX, mouseY}: {mouseX: number; mouseY: number}) => { - return ( - mouseX !== undefined && - mouseX !== null && - mouseX >= 0 && - mouseX < width && - mouseY !== undefined && - mouseY !== null && - mouseY >= 0 && - mouseY < height - ) -} - -export const CandlestickHoverLayer: FunctionComponent = props => { - const { - plotConfig, - spec: {values, calculatedWindow, table}, - config: { - xColumnKey, - openColumnKey, - highColumnKey, - lowColumnKey, - closeColumnKey, - }, - xScale, - hoverX, - hoverY, - columnFormatter, - } = props - - const checkMouseBounding = createCheckMouseBounding(props) - - if (!checkMouseBounding({mouseX: hoverX, mouseY: hoverY})) { - return null - } - - const xIndex = - Math.round(xScale.invert(hoverX) / calculatedWindow) * calculatedWindow - - const value = values.find(x => x.windowStart === xIndex) - - if (!value) { - return null - } - - const tableIndexes = value.entriesTableIndexes - - const tooltipData: TooltipData = [ - xColumnKey, - openColumnKey, - highColumnKey, - lowColumnKey, - closeColumnKey, - ].map(key => { - const col = table.getColumn(key) - const type = table.getColumnType(key) - const formater = columnFormatter(key) - return { - colors: [], - key, - name: key, - type, - values: tableIndexes.map(i => formater(col[i])), - } - }) - - return -} - -CandlestickHoverLayer.displayName = 'ScatterHoverLayer' diff --git a/giraffe/src/components/CandlestickHoverTooltipLayer.tsx b/giraffe/src/components/CandlestickHoverTooltipLayer.tsx new file mode 100644 index 00000000..660ef247 --- /dev/null +++ b/giraffe/src/components/CandlestickHoverTooltipLayer.tsx @@ -0,0 +1,93 @@ +import React, {FunctionComponent} from 'react' + +import {Tooltip} from './Tooltip' +import {CandlestickLayerProps} from './CandlestickLayer' +import {TooltipData} from '../types' +import {OHLCResultEntry} from '../utils/ohlc' + +const createCheckMouseBounding = ({ + height, + width, +}: { + width: number + height: number +}) => ({hoverX, hoverY}: {hoverX: number; hoverY: number}) => + hoverX !== undefined && + hoverX !== null && + hoverX >= 0 && + hoverX < width && + hoverY !== undefined && + hoverY !== null && + hoverY >= 0 && + hoverY < height + +const getCandlestickHoveredValue = ( + props: CandlestickLayerProps +): OHLCResultEntry | undefined => { + const checkMouseBounding = createCheckMouseBounding(props) + + if (!checkMouseBounding(props)) { + return null + } + + const { + xScale, + hoverX, + spec: {calculatedWindow, values}, + } = props + + const xIndex = + Math.round(xScale.invert(hoverX) / calculatedWindow) * calculatedWindow + + return values.find(x => x.windowStart === xIndex) +} + +export const candlestickGetHoveredValueEntry = getCandlestickHoveredValue + +export const CandlestickHoverTooltipLayer: FunctionComponent = props => { + const { + plotConfig, + spec: {table}, + config: { + xColumnKey, + openColumnKey, + highColumnKey, + lowColumnKey, + closeColumnKey, + }, + columnFormatter, + } = props + + const value = getCandlestickHoveredValue(props) + + if (!value) { + return null + } + + const allKeys = [ + xColumnKey, + openColumnKey, + highColumnKey, + lowColumnKey, + closeColumnKey, + ] + + const tooltipData: TooltipData = allKeys + .map(key => ({ + key, + col: table.getColumn(key), + type: table.getColumnType(key), + formater: columnFormatter(key), + })) + .map(({key, col, formater, type}) => ({ + colors: [], + key, + name: key, + type, + values: value.entriesTableIndexes.map(i => formater(col[i])), + })) + + return +} + +CandlestickHoverTooltipLayer.displayName = 'ScatterHoverLayer' diff --git a/giraffe/src/components/CandlestickLayer.tsx b/giraffe/src/components/CandlestickLayer.tsx index 374b17a5..e708f930 100644 --- a/giraffe/src/components/CandlestickLayer.tsx +++ b/giraffe/src/components/CandlestickLayer.tsx @@ -8,8 +8,10 @@ import { } from '../types' import {CANDLESTICK_THEME_DARK} from '../constants/candlestickStyles' import {OHLCResultEntry} from '../utils/ohlc' -import {useHoverPointIndices} from '../utils/useHoverPointIndices' -import {CandlestickHoverLayer} from './CandlestickHoverLayer' +import { + candlestickGetHoveredValueEntry, + CandlestickHoverTooltipLayer, +} from './CandlestickHoverTooltipLayer' interface CandleValue { open: number @@ -24,7 +26,9 @@ export interface CandleProps { /** width of candle */ width: number /** returns height position from value */ + // todo: use yScale heightPositionFormatter: (value: number) => number + hovered: boolean } const Candle: React.FC = ({ @@ -32,6 +36,7 @@ const Candle: React.FC = ({ theme, candle, width, + hovered, }) => { const {close, high, low, open}: CandleValue = { close: heightPositionFormatter(candle.close), @@ -42,6 +47,13 @@ const Candle: React.FC = ({ const isRaise = open >= close + const style = { + ...theme[isRaise ? 'candleRaising' : 'candleDecreasing'], + ...(hovered + ? theme[isRaise ? 'candleRaisingHover' : 'candleDecreasingHover'] + : {}), + } + const { bodyColor, bodyFillOpacity, @@ -49,7 +61,7 @@ const Candle: React.FC = ({ bodyStrokeWidth, shadowColor, shadowStrokeWidth, - }: CandleStyle = theme[isRaise ? 'candleRaising' : 'candleDecreasing'] + }: CandleStyle = style const height = Math.abs(open - close) const y = Math.min(open, close) @@ -144,16 +156,7 @@ export type CandlestickLayerProps = Props //todo: only proxies props into Candlestick ? if true -> candlestick should be implemented here export const CandlestickLayer: FunctionComponent = props => { - const { - config: _theme, - width, - height, - spec, - xScale, - yScale, - hoverX, - hoverY, - } = props + const {config: _theme, width, height, spec, xScale, yScale} = props // todo default values already present in _theme ? const theme: CandlestickLayerConfig = { ...CANDLESTICK_THEME_DARK, @@ -188,6 +191,8 @@ export const CandlestickLayer: FunctionComponent = props => { ) } + const hoveredValue = candlestickGetHoveredValueEntry(props) + return ( <> = props => { > ))} - + ) } diff --git a/giraffe/src/constants/candlestickStyles.ts b/giraffe/src/constants/candlestickStyles.ts index 9bdad2c8..9875a9d6 100644 --- a/giraffe/src/constants/candlestickStyles.ts +++ b/giraffe/src/constants/candlestickStyles.ts @@ -24,6 +24,11 @@ export const CANDLESTICK_THEME_DARK: Required = { shadowColor: InfluxColors.Krypton, shadowStrokeWidth: 2, }, + candleRaisingHover: { + bodyFillOpacity: 0.9, + bodyStrokeWidth: 6, + shadowStrokeWidth: 6, + }, candleDecreasing: { bodyColor: InfluxColors.Curacao, bodyFillOpacity: 0, @@ -32,4 +37,9 @@ export const CANDLESTICK_THEME_DARK: Required = { shadowColor: InfluxColors.Curacao, shadowStrokeWidth: 2, }, + candleDecreasingHover: { + bodyFillOpacity: 0.3, + bodyStrokeWidth: 6, + shadowStrokeWidth: 6, + }, } diff --git a/giraffe/src/types/index.ts b/giraffe/src/types/index.ts index 06b2ff73..cebaf77c 100644 --- a/giraffe/src/types/index.ts +++ b/giraffe/src/types/index.ts @@ -267,7 +267,9 @@ export interface CandlestickLayerConfig { candlePadding: number candleRaising: CandleStyle + candleRaisingHover: Partial candleDecreasing: CandleStyle + candleDecreasingHover: Partial xColumnKey?: string openColumnKey?: string diff --git a/stories/src/candlestick.stories.tsx b/stories/src/candlestick.stories.tsx index 5b4974a5..32a4c079 100644 --- a/stories/src/candlestick.stories.tsx +++ b/stories/src/candlestick.stories.tsx @@ -65,7 +65,8 @@ const editableLayer = (theme: Theme): Theme => ({ theme.candleRaising.shadowStrokeWidth ), }, - + // todo: + candleRaisingHover: theme.candleRaisingHover, candleDecreasing: { bodyColor: color( 'candleDecreasing - bodyColor', @@ -92,6 +93,8 @@ const editableLayer = (theme: Theme): Theme => ({ theme.candleDecreasing.shadowStrokeWidth ), }, + // todo: + candleDecreasingHover: theme.candleDecreasingHover, xColumnKey: text('xColumn', theme.xColumnKey), openColumnKey: text('openColumnKey', theme.openColumnKey), highColumnKey: text('highColumnKey', theme.highColumnKey), From 9fc51e828bea4232b1118664e6c9d194a2244c70 Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Wed, 17 Feb 2021 13:49:32 +0100 Subject: [PATCH 22/34] fix: candle positioning --- giraffe/src/components/CandlestickHoverTooltipLayer.tsx | 3 ++- giraffe/src/components/CandlestickLayer.tsx | 4 ++-- giraffe/src/utils/ohlc.ts | 7 ++++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/giraffe/src/components/CandlestickHoverTooltipLayer.tsx b/giraffe/src/components/CandlestickHoverTooltipLayer.tsx index 660ef247..bfcacda6 100644 --- a/giraffe/src/components/CandlestickHoverTooltipLayer.tsx +++ b/giraffe/src/components/CandlestickHoverTooltipLayer.tsx @@ -37,7 +37,8 @@ const getCandlestickHoveredValue = ( } = props const xIndex = - Math.round(xScale.invert(hoverX) / calculatedWindow) * calculatedWindow + Math.round(xScale.invert(hoverX) / calculatedWindow - 0.5) * + calculatedWindow return values.find(x => x.windowStart === xIndex) } diff --git a/giraffe/src/components/CandlestickLayer.tsx b/giraffe/src/components/CandlestickLayer.tsx index e708f930..23b32cd7 100644 --- a/giraffe/src/components/CandlestickLayer.tsx +++ b/giraffe/src/components/CandlestickLayer.tsx @@ -203,8 +203,8 @@ export const CandlestickLayer: FunctionComponent = props => { {values.filter(isCandleVisible).map(({windowStart, value: candle}) => ( <> [+a, b] as const) .map(([windowStart, entriesTableIndexes]) => { let close = 0 @@ -106,8 +106,9 @@ export const getOhlcValues = ( low, close, }, - } + } as OHLCResultEntry }) + return values } export const calculateWindow = ( From d1209fb014c0cc42b713052a824f110508d7fc18 Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Wed, 17 Feb 2021 14:16:27 +0100 Subject: [PATCH 23/34] fix: candlestick minor fixes --- giraffe/src/components/CandlestickLayer.tsx | 60 ++++++++++----------- giraffe/src/types/index.ts | 10 ++-- 2 files changed, 32 insertions(+), 38 deletions(-) diff --git a/giraffe/src/components/CandlestickLayer.tsx b/giraffe/src/components/CandlestickLayer.tsx index 23b32cd7..5275bf93 100644 --- a/giraffe/src/components/CandlestickLayer.tsx +++ b/giraffe/src/components/CandlestickLayer.tsx @@ -5,8 +5,8 @@ import { CandlestickLayerSpec, CandleStyle, LayerProps, + Scale, } from '../types' -import {CANDLESTICK_THEME_DARK} from '../constants/candlestickStyles' import {OHLCResultEntry} from '../utils/ohlc' import { candlestickGetHoveredValueEntry, @@ -21,36 +21,35 @@ interface CandleValue { } export interface CandleProps { - theme: CandlestickLayerConfig + config: CandlestickLayerConfig candle: CandleValue /** width of candle */ width: number /** returns height position from value */ - // todo: use yScale - heightPositionFormatter: (value: number) => number + yScale: Scale hovered: boolean } const Candle: React.FC = ({ - heightPositionFormatter, - theme, + config, candle, + yScale, width, hovered, }) => { const {close, high, low, open}: CandleValue = { - close: heightPositionFormatter(candle.close), - high: heightPositionFormatter(candle.high), - low: heightPositionFormatter(candle.low), - open: heightPositionFormatter(candle.open), + close: yScale(candle.close), + high: yScale(candle.high), + low: yScale(candle.low), + open: yScale(candle.open), } const isRaise = open >= close const style = { - ...theme[isRaise ? 'candleRaising' : 'candleDecreasing'], + ...config[isRaise ? 'candleRaising' : 'candleDecreasing'], ...(hovered - ? theme[isRaise ? 'candleRaisingHover' : 'candleDecreasingHover'] + ? config[isRaise ? 'candleRaisingHover' : 'candleDecreasingHover'] : {}), } @@ -69,7 +68,7 @@ const Candle: React.FC = ({ const centerY = width / 2 const body = - theme.mode === 'candles' ? ( + config.mode === 'candles' ? ( candlestick should be implemented here export const CandlestickLayer: FunctionComponent = props => { - const {config: _theme, width, height, spec, xScale, yScale} = props - // todo default values already present in _theme ? - const theme: CandlestickLayerConfig = { - ...CANDLESTICK_THEME_DARK, - ..._theme, - } - - const {candlePadding} = theme + const { + config: {candlePadding}, + config, + width, + height, + spec, + xScale, + yScale, + } = props const {values, calculatedWindow} = spec - // todo: naming - const getXSVGCoords = xScale - const heightPositionFormatter: CandleProps['heightPositionFormatter'] = yScale - const candleWidth = Math.round(Math.abs((xScale(0) - xScale(calculatedWindow)) * 100)) / 100 - candlePadding - // todo: style - // const maxHeight = minHeight + hegihtRange const isCandleVisible = (candle: OHLCResultEntry) => { - const x = getXSVGCoords(candle.windowStart) - const yMin = heightPositionFormatter(candle.yRange[0]) - const yMax = heightPositionFormatter(candle.yRange[1]) + const x = xScale(candle.windowStart) + const yMin = yScale(candle.yRange[0]) + const yMax = yScale(candle.yRange[1]) return ( x >= -candleWidth && @@ -203,15 +197,15 @@ export const CandlestickLayer: FunctionComponent = props => { {values.filter(isCandleVisible).map(({windowStart, value: candle}) => ( <> diff --git a/giraffe/src/types/index.ts b/giraffe/src/types/index.ts index cebaf77c..d0705a88 100644 --- a/giraffe/src/types/index.ts +++ b/giraffe/src/types/index.ts @@ -265,11 +265,11 @@ export interface CandlestickLayerConfig { /** Defines which columns choose as unique bar indentificator. Also bar labels can be defined here. */ mode?: 'candles' | 'fence' - candlePadding: number - candleRaising: CandleStyle - candleRaisingHover: Partial - candleDecreasing: CandleStyle - candleDecreasingHover: Partial + candlePadding?: number + candleRaising?: CandleStyle + candleRaisingHover?: Partial + candleDecreasing?: CandleStyle + candleDecreasingHover?: Partial xColumnKey?: string openColumnKey?: string From d903b0d7f16c7859c85a3489f5995fbd62c46282 Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Thu, 18 Feb 2021 07:53:13 +0100 Subject: [PATCH 24/34] fix: candlestick redundat call of get hover value --- .gitignore | 2 +- .../src/components/CandlestickHoverTooltipLayer.tsx | 11 ++++------- giraffe/src/components/CandlestickLayer.tsx | 4 +++- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 022c630e..ac9351a3 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,4 @@ dist .env .vscode coverage -debug.log \ No newline at end of file +debug.log diff --git a/giraffe/src/components/CandlestickHoverTooltipLayer.tsx b/giraffe/src/components/CandlestickHoverTooltipLayer.tsx index bfcacda6..d47a2171 100644 --- a/giraffe/src/components/CandlestickHoverTooltipLayer.tsx +++ b/giraffe/src/components/CandlestickHoverTooltipLayer.tsx @@ -45,7 +45,9 @@ const getCandlestickHoveredValue = ( export const candlestickGetHoveredValueEntry = getCandlestickHoveredValue -export const CandlestickHoverTooltipLayer: FunctionComponent = props => { +export const CandlestickHoverTooltipLayer: FunctionComponent = props => { const { plotConfig, spec: {table}, @@ -57,14 +59,9 @@ export const CandlestickHoverTooltipLayer: FunctionComponent = props => { ))} - + {hoveredValue && ( + + )} ) } From fcf1dff00845ba09ee90e7f67f0a88a975578ad2 Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Thu, 18 Feb 2021 08:00:56 +0100 Subject: [PATCH 25/34] style: candlestick comments --- giraffe/src/components/CandlestickLayer.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/giraffe/src/components/CandlestickLayer.tsx b/giraffe/src/components/CandlestickLayer.tsx index 8bdaa718..103b18a0 100644 --- a/giraffe/src/components/CandlestickLayer.tsx +++ b/giraffe/src/components/CandlestickLayer.tsx @@ -25,7 +25,6 @@ export interface CandleProps { candle: CandleValue /** width of candle */ width: number - /** returns height position from value */ yScale: Scale hovered: boolean } @@ -153,7 +152,6 @@ export interface Props extends LayerProps { export type CandlestickLayerProps = Props -//todo: only proxies props into Candlestick ? if true -> candlestick should be implemented here export const CandlestickLayer: FunctionComponent = props => { const { config: {candlePadding}, @@ -173,15 +171,15 @@ export const CandlestickLayer: FunctionComponent = props => { const isCandleVisible = (candle: OHLCResultEntry) => { const x = xScale(candle.windowStart) - const yMin = yScale(candle.yRange[0]) - const yMax = yScale(candle.yRange[1]) + // svg has inversed y drawing so lower value has higher svg coords + const yMax = yScale(candle.yRange[0]) + const yMin = yScale(candle.yRange[1]) return ( x >= -candleWidth && x <= width + candleWidth && - // svg has inversed y drawing so lower value has higher svg coords - yMax <= height && - yMin >= 0 + yMin <= height && + yMax >= 0 ) } From a01b270fafb4fe392a54ce6d0e2d96bae0af034c Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Thu, 18 Feb 2021 09:07:42 +0100 Subject: [PATCH 26/34] style: fixed types --- giraffe/src/constants/index.ts | 4 +++- giraffe/src/utils/PlotEnv.ts | 8 +++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/giraffe/src/constants/index.ts b/giraffe/src/constants/index.ts index 3bfd05a2..91488ad1 100644 --- a/giraffe/src/constants/index.ts +++ b/giraffe/src/constants/index.ts @@ -56,7 +56,9 @@ export const CONFIG_DEFAULTS: Partial = { legendColorizeRows: true, } -export const LAYER_DEFAULTS: {[layerType: string]: Partial} = { +export const LAYER_DEFAULTS: { + [layerType in LayerConfig['type']]?: Partial +} = { line: { lineWidth: 1, hoverDimension: 'auto', diff --git a/giraffe/src/utils/PlotEnv.ts b/giraffe/src/utils/PlotEnv.ts index c8d32d83..a81a8ff6 100644 --- a/giraffe/src/utils/PlotEnv.ts +++ b/giraffe/src/utils/PlotEnv.ts @@ -500,11 +500,9 @@ export class PlotEnv { const applyLayerDefaults = ( layers: SizedConfig['layers'] ): SizedConfig['layers'] => - layers.map(layer => - LAYER_DEFAULTS[layer.type] - ? {...LAYER_DEFAULTS[layer.type], ...layer} - : // todo: what is happening here ? (candlestick broke types) - (layer as any) + layers.map( + layer => + ({...LAYER_DEFAULTS[layer.type], ...layer} as SizedConfig['layers'][0]) ) const mergeConfigs = ( From 0ed80f37430d42497a152d94c22fcb35000640c2 Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Mon, 22 Feb 2021 09:34:13 +0100 Subject: [PATCH 27/34] fix: candlestick negative width --- giraffe/src/components/CandlestickLayer.tsx | 2 +- giraffe/src/transforms/candlestick.ts | 18 ++---------------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/giraffe/src/components/CandlestickLayer.tsx b/giraffe/src/components/CandlestickLayer.tsx index 103b18a0..81005b0e 100644 --- a/giraffe/src/components/CandlestickLayer.tsx +++ b/giraffe/src/components/CandlestickLayer.tsx @@ -69,7 +69,7 @@ const Candle: React.FC = ({ const body = config.mode === 'candles' ? ( ): CandlestickLayerSpec => { - const { - xColumnKey, - openColumnKey, - highColumnKey, - lowColumnKey, - closeColumnKey, - } = layerConfig - - const columns = { - xColumnKey, - openColumnKey, - highColumnKey, - lowColumnKey, - closeColumnKey, - } + const {xColumnKey, openColumnKey} = layerConfig const xCol = inputTable.getColumn(xColumnKey, 'number') // distence between two values const window = calculateWindow(layerConfig.window, xCol) - const values = getOhlcValues(inputTable, columns, window) + const values = getOhlcValues(inputTable, layerConfig, window) let xMin = values[0].windowStart let xMax = values[0].windowStart From ba484004ef1799bbdf7c70e88e5038ed17b8772f Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Mon, 22 Feb 2021 09:35:52 +0100 Subject: [PATCH 28/34] style: candlestick removed unused props --- giraffe/src/constants/candlestickStyles.ts | 2 -- giraffe/src/types/index.ts | 2 -- stories/src/candlestick.stories.tsx | 3 --- 3 files changed, 7 deletions(-) diff --git a/giraffe/src/constants/candlestickStyles.ts b/giraffe/src/constants/candlestickStyles.ts index 9875a9d6..68e63dba 100644 --- a/giraffe/src/constants/candlestickStyles.ts +++ b/giraffe/src/constants/candlestickStyles.ts @@ -11,8 +11,6 @@ export const CANDLESTICK_THEME_DARK: Required = { closeColumnKey: 'close', window: 'detect', - windowMax: 100, - windowMergeStrategy: 'caping', candlePadding: 10, diff --git a/giraffe/src/types/index.ts b/giraffe/src/types/index.ts index d0705a88..a278aecb 100644 --- a/giraffe/src/types/index.ts +++ b/giraffe/src/types/index.ts @@ -278,8 +278,6 @@ export interface CandlestickLayerConfig { highColumnKey?: string window?: number | 'detect' - windowMax?: number - windowMergeStrategy?: 'caping' | 'halving' } export interface GaugeLayerConfig { diff --git a/stories/src/candlestick.stories.tsx b/stories/src/candlestick.stories.tsx index 32a4c079..27045fc0 100644 --- a/stories/src/candlestick.stories.tsx +++ b/stories/src/candlestick.stories.tsx @@ -105,9 +105,6 @@ const editableLayer = (theme: Theme): Theme => ({ if (detect) return 'detect' return number('window fixed', typeof theme.window === 'number' || 1_000) })(), - windowMax: number('windowMax', theme.windowMax), - // todo: - windowMergeStrategy: 'caping', }) const createStory = (theme: Theme, csv: string) => () => { From 24ca65b7000f1e0468b51a4a0e3cb932d11b23bc Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Mon, 22 Feb 2021 10:41:59 +0100 Subject: [PATCH 29/34] feat: candlestick stories --- stories/src/candlestick.stories.tsx | 158 ++++++++++++++++------------ 1 file changed, 90 insertions(+), 68 deletions(-) diff --git a/stories/src/candlestick.stories.tsx b/stories/src/candlestick.stories.tsx index 27045fc0..c2bb6e86 100644 --- a/stories/src/candlestick.stories.tsx +++ b/stories/src/candlestick.stories.tsx @@ -5,9 +5,8 @@ import {Config, fromFlux, InfluxColors, Plot} from '../../giraffe/src' import {PlotContainer} from './helpers' import {CANDLESTICK_THEME_DARK} from '../../giraffe/src/constants/candlestickStyles' -import {CandlestickLayerConfig} from '../../giraffe/src/types' +import {CandlestickLayerConfig, CandleStyle} from '../../giraffe/src/types' -// todo: random csv import { ohlcCsvSample1, ohlcCsvSample1MissingCandles, @@ -16,6 +15,13 @@ import { type Theme = Required +const knobGroups = { + basics: 'basics', + sizing: 'sizing', + colors: 'colors', + table: 'table', +} + const color = (() => { const colors = (() => { const obj = {} @@ -23,10 +29,51 @@ const color = (() => { return obj })() - // todo: type definitions return (label: string, ...rest: any[]) => select(label, colors, ...rest) })() +const candleStyle = ( + candleName: string, + candleDefaults: Partial, + candleDefaultsFallback?: CandleStyle +): CandleStyle => ({ + bodyColor: color( + `${candleName} bodyColor`, + candleDefaults.bodyColor ?? candleDefaultsFallback.bodyColor, + knobGroups.colors + ), + bodyFillOpacity: number( + `${candleName} bodyFillOpacity`, + candleDefaults.bodyFillOpacity ?? candleDefaultsFallback.bodyFillOpacity, + {}, + knobGroups.colors + ), + bodyRounding: number( + `${candleName} bodyRounding`, + candleDefaults.bodyRounding ?? candleDefaultsFallback.bodyRounding, + {}, + knobGroups.sizing + ), + bodyStrokeWidth: number( + `${candleName} bodyStrokeWidth`, + candleDefaults.bodyStrokeWidth ?? candleDefaultsFallback.bodyStrokeWidth, + {}, + knobGroups.sizing + ), + shadowColor: color( + `${candleName} shadowColor`, + candleDefaults.shadowColor ?? candleDefaultsFallback.shadowColor, + knobGroups.colors + ), + shadowStrokeWidth: number( + `${candleName} shadowStrokeWidth`, + candleDefaults.shadowStrokeWidth ?? + candleDefaultsFallback.shadowStrokeWidth, + {}, + knobGroups.sizing + ), +}) + const editableLayer = (theme: Theme): Theme => ({ type: theme.type, mode: select( @@ -35,75 +82,50 @@ const editableLayer = (theme: Theme): Theme => ({ candles: 'candles', fence: 'fence', }, - theme.mode + theme.mode, + knobGroups.basics + ), + candlePadding: number( + 'padding candle', + theme.candlePadding, + {}, + knobGroups.sizing ), - candlePadding: number('padding candle', theme.candlePadding), - candleRaising: { - bodyColor: color( - 'candleRaising - bodyColor', - theme.candleRaising.bodyColor - ), - bodyFillOpacity: number( - 'candleRaising - bodyFillOpacity', - theme.candleRaising.bodyFillOpacity - ), - bodyRounding: number( - 'candleRaising - bodyRounding', - theme.candleRaising.bodyRounding - ), - bodyStrokeWidth: number( - 'candleRaising - bodyStrokeWidth', - theme.candleRaising.bodyStrokeWidth - ), - shadowColor: color( - 'candleRaising - shadowColor', - theme.candleRaising.shadowColor - ), - shadowStrokeWidth: number( - 'candleRaising - shadowStrokeWidth', - theme.candleRaising.shadowStrokeWidth - ), - }, - // todo: - candleRaisingHover: theme.candleRaisingHover, - candleDecreasing: { - bodyColor: color( - 'candleDecreasing - bodyColor', - theme.candleDecreasing.bodyColor - ), - bodyFillOpacity: number( - 'candleDecreasing - bodyFillOpacity', - theme.candleDecreasing.bodyFillOpacity - ), - bodyRounding: number( - 'candleDecreasing - bodyRounding', - theme.candleDecreasing.bodyRounding - ), - bodyStrokeWidth: number( - 'candleDecreasing - bodyStrokeWidth', - theme.candleDecreasing.bodyStrokeWidth - ), - shadowColor: color( - 'candleDecreasing - shadowColor', - theme.candleDecreasing.shadowColor - ), - shadowStrokeWidth: number( - 'candleDecreasing - shadowStrokeWidth', - theme.candleDecreasing.shadowStrokeWidth - ), - }, - // todo: - candleDecreasingHover: theme.candleDecreasingHover, - xColumnKey: text('xColumn', theme.xColumnKey), - openColumnKey: text('openColumnKey', theme.openColumnKey), - highColumnKey: text('highColumnKey', theme.highColumnKey), - lowColumnKey: text('lowColumnKey', theme.lowColumnKey), - closeColumnKey: text('closeColumnKey', theme.closeColumnKey), + candleRaising: candleStyle('candleRaising', theme.candleRaising), + candleRaisingHover: candleStyle( + 'candleRaisingHover', + theme.candleRaisingHover, + theme.candleRaising + ), + candleDecreasing: candleStyle('candleDecreasing', theme.candleDecreasing), + candleDecreasingHover: candleStyle( + 'candleDecreasingHover', + theme.candleDecreasingHover, + theme.candleDecreasing + ), + xColumnKey: text('xColumn', theme.xColumnKey, knobGroups.table), + openColumnKey: text('openColumnKey', theme.openColumnKey, knobGroups.table), + highColumnKey: text('highColumnKey', theme.highColumnKey, knobGroups.table), + lowColumnKey: text('lowColumnKey', theme.lowColumnKey, knobGroups.table), + closeColumnKey: text( + 'closeColumnKey', + theme.closeColumnKey, + knobGroups.table + ), window: (() => { - const detect = boolean('window detect', theme.window === 'detect') + const detect = boolean( + 'window detect', + theme.window === 'detect', + knobGroups.basics + ) if (detect) return 'detect' - return number('window fixed', typeof theme.window === 'number' || 1_000) + return number( + 'window fixed', + typeof theme.window === 'number' || 1_000, + {}, + knobGroups.basics + ) })(), }) From cf23c0fd8b7144a003608382b48a3eafd8c96589 Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Mon, 22 Feb 2021 10:44:05 +0100 Subject: [PATCH 30/34] feat: storybook candlestick fence simple --- stories/src/candlestick.stories.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/stories/src/candlestick.stories.tsx b/stories/src/candlestick.stories.tsx index c2bb6e86..c7b5fc16 100644 --- a/stories/src/candlestick.stories.tsx +++ b/stories/src/candlestick.stories.tsx @@ -149,11 +149,14 @@ const createStory = (theme: Theme, csv: string) => () => { storiesOf('Candlestick', module) .addDecorator(withKnobs) - // todo: candlestick fences .add( 'Candlestick simple', createStory(CANDLESTICK_THEME_DARK, ohlcCsvSample1) ) + .add( + 'Candlestick simple fence', + createStory({...CANDLESTICK_THEME_DARK, mode: 'fence'}, ohlcCsvSample1) + ) .add( 'Candlestick missing candles', createStory(CANDLESTICK_THEME_DARK, ohlcCsvSample1MissingCandles) From f76609f3e9a6287926810347160ff191d67696b2 Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Mon, 22 Feb 2021 12:35:00 +0100 Subject: [PATCH 31/34] feat: candlestick optional candlestyles --- giraffe/src/components/CandlestickLayer.tsx | 11 +++++++---- giraffe/src/constants/candlestickStyles.ts | 10 +++++++++- giraffe/src/types/index.ts | 5 +++-- stories/src/candlestick.stories.tsx | 2 +- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/giraffe/src/components/CandlestickLayer.tsx b/giraffe/src/components/CandlestickLayer.tsx index 81005b0e..63e5dce4 100644 --- a/giraffe/src/components/CandlestickLayer.tsx +++ b/giraffe/src/components/CandlestickLayer.tsx @@ -1,5 +1,6 @@ import * as React from 'react' import {FunctionComponent} from 'react' +import {CANDLESTICK_THEME_DARK} from '../constants/candlestickStyles' import { CandlestickLayerConfig, CandlestickLayerSpec, @@ -45,11 +46,13 @@ const Candle: React.FC = ({ const isRaise = open >= close + const styleKey = isRaise ? 'candleRaising' : 'candleDecreasing' + const styleKeyHover = isRaise ? 'candleRaisingHover' : 'candleDecreasingHover' + const style = { - ...config[isRaise ? 'candleRaising' : 'candleDecreasing'], - ...(hovered - ? config[isRaise ? 'candleRaisingHover' : 'candleDecreasingHover'] - : {}), + ...CANDLESTICK_THEME_DARK[styleKey], + ...config[styleKey], + ...(hovered ? config[styleKeyHover] : {}), } const { diff --git a/giraffe/src/constants/candlestickStyles.ts b/giraffe/src/constants/candlestickStyles.ts index 68e63dba..fd3a5d9d 100644 --- a/giraffe/src/constants/candlestickStyles.ts +++ b/giraffe/src/constants/candlestickStyles.ts @@ -1,7 +1,15 @@ import {CandlestickLayerConfig} from '../types' import {InfluxColors} from './colorSchemes' -export const CANDLESTICK_THEME_DARK: Required = { +type CandlestickLayerConfigDefault = Omit< + Required, + 'candleRaising' | 'candleDecreasing' +> & { + candleRaising: Required + candleDecreasing: Required +} + +export const CANDLESTICK_THEME_DARK: CandlestickLayerConfigDefault = { type: 'candlestick', mode: 'candles', xColumnKey: '_time', diff --git a/giraffe/src/types/index.ts b/giraffe/src/types/index.ts index a278aecb..56c2d3e0 100644 --- a/giraffe/src/types/index.ts +++ b/giraffe/src/types/index.ts @@ -266,9 +266,10 @@ export interface CandlestickLayerConfig { mode?: 'candles' | 'fence' candlePadding?: number - candleRaising?: CandleStyle + + candleRaising?: Partial candleRaisingHover?: Partial - candleDecreasing?: CandleStyle + candleDecreasing?: Partial candleDecreasingHover?: Partial xColumnKey?: string diff --git a/stories/src/candlestick.stories.tsx b/stories/src/candlestick.stories.tsx index c7b5fc16..342a7d42 100644 --- a/stories/src/candlestick.stories.tsx +++ b/stories/src/candlestick.stories.tsx @@ -35,7 +35,7 @@ const color = (() => { const candleStyle = ( candleName: string, candleDefaults: Partial, - candleDefaultsFallback?: CandleStyle + candleDefaultsFallback?: Partial ): CandleStyle => ({ bodyColor: color( `${candleName} bodyColor`, From 53053d4f2c414e0254d56dbb5d0bd2f383f2251f Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Mon, 22 Feb 2021 12:35:09 +0100 Subject: [PATCH 32/34] docs: candlestick docs --- giraffe/README.md | 74 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/giraffe/README.md b/giraffe/README.md index e29bd8ff..5bdc42f8 100644 --- a/giraffe/README.md +++ b/giraffe/README.md @@ -474,6 +474,80 @@ Giraffe comes with utility functions. - **lowerColumnName**: _string. Optional._ A string indicating the shaded portion of each band that extends below the **mainColumnName** line. + + +- **CandlestickLayerConfig**: _Object_ Maximum one per ``. + All values (excluding **type**) are Optional and their defaults is defined by theme `GAUGE_MINI_THEME_BULLET_DARK` + + - **type**: _'candlestick'_. **Required**. + + Data: + - **xColumnKey**: _string_. defines which column of table is used for x coord + - **openColumnKey** , **highColumnKey** , **lowColumnKey** , **closeColumnKey**: _string_. columns of OHLC values + - **window**: _'detect' | number_. Defines how is data wise candle width on x. When value is `'detect'` window is selected as lowest distance between ohlc entries. If window is bigger than real distance between entries, more table rows can be merged into single candle _(all entries still will be shown in tooltip)_ + + Style + - **mode**: _'candles' | 'fence'_. Select if candle body is rectangle or lines + - **candlePadding**: _number_. Distance between candles. + + - **candleRaising** , **candleDecreasing** , **candleRaisingHover** , **candleDecreasingHover**: _CandleStyle_. style of single candles. Depending on candle raising/decreasing _(depends on close>open)_ and if candle is hovered. If **hover** property not present, then not hovered property is used. + + ***CandleStyle:*** + + | name | type | function | + | ---- | ---- | -------- | + | **bodyColor** | _hex color_ | Color applied on fill and stroke of body | + | **bodyFillOpacity** | _number_ | Fill opacity of body _(candle mode only)_. **0 >= value >= 1** | + | **bodyRounding** | _number_ | Rounding of body rect _(candle mode only)_ | + | **bodyStrokeWidth** | _number_ | Width of body rect/fence-lines | + | **shadowColor** | _hex color_ | Color of low/high lines | + | **shadowStrokeWidth** | _number_ | Width of low/high lines | + + **Precreated themes** + - `GAUGE_MINI_THEME_BULLET_DARK` + ``` + { + type: 'candlestick', + mode: 'candles', + + xColumnKey: '_time', + openColumnKey: 'open', + highColumnKey: 'high', + lowColumnKey: 'low', + closeColumnKey: 'close', + + window: 'detect', + + candlePadding: 10, + + candleRaising: { + bodyColor: InfluxColors.Krypton, + bodyFillOpacity: 1, + bodyRounding: 4, + bodyStrokeWidth: 2, + shadowColor: InfluxColors.Krypton, + shadowStrokeWidth: 2, + }, + candleRaisingHover: { + bodyFillOpacity: 0.9, + bodyStrokeWidth: 6, + shadowStrokeWidth: 6, + }, + candleDecreasing: { + bodyColor: InfluxColors.Curacao, + bodyFillOpacity: 0, + bodyRounding: 0, + bodyStrokeWidth: 2, + shadowColor: InfluxColors.Curacao, + shadowStrokeWidth: 2, + }, + candleDecreasingHover: { + bodyFillOpacity: 0.3, + bodyStrokeWidth: 6, + shadowStrokeWidth: 6, + }, + } + ``` - **ScatterLayerConfig**: _Object._ Maximum one per ``. Properties are: - **type**: _"scatter". **Required**._ Specifies that this LayerConfig and `` is a scatter plot. From dda825a8ab60b4ddf7c470bfe3ebb3a96d70f249 Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Mon, 1 Mar 2021 11:44:44 +0100 Subject: [PATCH 33/34] fix: removed bugged merge --- giraffe/src/components/GaugeMini.tsx | 395 --------------------------- giraffe/src/index.ts | 3 - 2 files changed, 398 deletions(-) diff --git a/giraffe/src/components/GaugeMini.tsx b/giraffe/src/components/GaugeMini.tsx index 3e87d026..843d9cc5 100644 --- a/giraffe/src/components/GaugeMini.tsx +++ b/giraffe/src/components/GaugeMini.tsx @@ -2,7 +2,6 @@ import React, {FunctionComponent, useRef, useEffect, useState} from 'react' import {color as d3Color} from 'd3-color' import {scaleLinear} from 'd3-scale' -<<<<<<< HEAD // Types import {GaugeMiniColors, GaugeMiniLayerConfig} from '../types' @@ -21,62 +20,6 @@ interface Props { } const barCssClass = 'gauge-mini-bar' -======= -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 { - width: number - height: number - value: number | {_field: string; value: number}[] - theme: Required -} - -//#region colors - -export type Colors = { - min: Color - max: Color - secondary: string - thresholds: Color[] - // targets: Color[], -} - -export const getColors = (theme: Required): Colors => { - const {colorSecondary: secondary, gaugeColors: colorsAndTargets} = theme - - colorsAndTargets.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} -} - -//#endregion colors ->>>>>>> f03fd3e (feat: gauge mini) //#region svg helpers @@ -86,10 +29,7 @@ type TSvgTextRectProps = { /** * Helper component that returns rect when children changes. Usefull for calculating text box size. -<<<<<<< HEAD * !onRectChanged called only when children changes! -======= ->>>>>>> f03fd3e (feat: gauge mini) */ export const SvgTextRect: React.FC = props => { const {onRectChanged = () => {}} = props @@ -98,19 +38,12 @@ export const SvgTextRect: React.FC = props => { useEffect(() => { const rect = textRef.current?.getBBox() -<<<<<<< HEAD if (!rect) { return } onRectChanged(rect) }, [props.children, onRectChanged]) -======= - if (!rect) return - - onRectChanged(rect) - }, [props.children]) ->>>>>>> f03fd3e (feat: gauge mini) return ( <> @@ -119,13 +52,10 @@ export const SvgTextRect: React.FC = props => { ) } -<<<<<<< HEAD /** * Helper component for centering content. * !Doesn't react on content size changed. Recententering is done manualy by changing refreshToken! */ -======= ->>>>>>> f03fd3e (feat: gauge mini) const AutoCenterGroup: FunctionComponent<{ enabled?: boolean refreshToken?: number | string @@ -151,7 +81,6 @@ const AutoCenterGroup: FunctionComponent<{ | SVGGraphicsElement | undefined)?.getBoundingClientRect() -<<<<<<< HEAD if (!box || !boxParent) { return } @@ -162,16 +91,6 @@ const AutoCenterGroup: FunctionComponent<{ return ( -======= - if (!box || !boxParent) return - - setX((boxParent.width - box.width) / 2 - box.x) - setY((boxParent.height - box.height) / 2 - box.y) - }, [refreshToken]) - - return ( - ->>>>>>> f03fd3e (feat: gauge mini) {children} ) @@ -179,7 +98,6 @@ const AutoCenterGroup: FunctionComponent<{ //#endregion svg helpers -<<<<<<< HEAD //#region subcomponents //#region types @@ -233,24 +151,10 @@ type BarSegment = { const BarBackground: FunctionComponent = ({ theme, -======= -const barCssClass = 'gauge-mini-bar' - -const BarBackground: FunctionComponent<{ - theme: Required - colors: Colors - barWidth: number - getFrac: (x: number) => number - barCenter: number -}> = ({ - theme, - colors: {max, min, secondary, thresholds}, ->>>>>>> f03fd3e (feat: gauge mini) barWidth, getFrac, barCenter, }) => { -<<<<<<< HEAD const {gaugeHeight, mode, gaugeRounding, colors, colorSecondary} = theme const {max, min, thresholds = []} = colors @@ -280,34 +184,6 @@ const BarBackground: FunctionComponent<{ } // todo: dont't render def linear gradient when is not used -======= - 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}` - ->>>>>>> f03fd3e (feat: gauge mini) return ( <> @@ -333,7 +209,6 @@ const BarBackground: FunctionComponent<{ y={y} /> ) : ( -<<<<<<< HEAD segments .reverse() .map(({hex: col, end, start}, i) => ( @@ -347,24 +222,11 @@ const BarBackground: FunctionComponent<{ y={y} /> )) -======= - colors.map(({col, end, start}) => ( - - )) ->>>>>>> f03fd3e (feat: gauge mini) )} ) } -<<<<<<< HEAD const BarValue: FunctionComponent = ({ colors, barValueWidth, @@ -385,41 +247,14 @@ const BarValue: FunctionComponent = ({ const colorValue = mode === 'bullet' ? colorSecondary -======= -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 colorModeGradient = colors.thresholds.length === 0 - - const colorValue = - mode === 'bullet' - ? colors.secondary ->>>>>>> f03fd3e (feat: gauge mini) : d3Color( (() => { if (colorModeGradient) { return scaleLinear() -<<<<<<< HEAD .range([min.hex, max.hex] as any) .domain([min.value, max.value])(value) as any } else { const sortedColors = [min, ...thresholds, max] -======= - .range([colors.min.hex, colors.max.hex] as any) - .domain([colors.min.value, colors.max.value])(value) as any - } else { - const sortedColors = [ - colors.min, - ...colors.thresholds, - colors.max, - ] ->>>>>>> f03fd3e (feat: gauge mini) let i = 0 while ( i < sortedColors.length && @@ -436,14 +271,6 @@ const BarValue: FunctionComponent<{ ?.brighter(1) .hex() -<<<<<<< HEAD -======= - const y = barCenter - valueHeight / 2 - const x = Math.sign(valueFracFixed) === -1 ? barValueWidth : 0 - - const className = 'value-rect' - ->>>>>>> f03fd3e (feat: gauge mini) // todo: move styling out -> styling is now multiple times inserted return ( <> @@ -469,60 +296,12 @@ const BarValue: FunctionComponent<{ ) } -<<<<<<< HEAD const Text: FunctionComponent = ({value, barValueWidth, theme}) => { -======= -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}) => { ->>>>>>> f03fd3e (feat: gauge mini) const { valueFontColorInside, valueFontColorOutside, textMode, valueFormater, -<<<<<<< HEAD valueFontSize: fontSize, valuePadding, } = theme @@ -544,43 +323,14 @@ const Text: FunctionComponent<{ valuePadding ) : valuePadding -======= - valueFontSize, - } = theme - const textValue = valueFormater(value) - - 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 textColor = textInside ? valueFontColorInside : valueFontColorOutside - - const x = - textMode === 'follow' - ? Math.max(barValueWidth + (textInside ? -padding : padding), padding) - : padding ->>>>>>> f03fd3e (feat: gauge mini) return ( <> >>>>>> f03fd3e (feat: gauge mini) > {textValue} @@ -588,7 +338,6 @@ const Text: FunctionComponent<{ ) } -<<<<<<< HEAD const Bar: FunctionComponent = ({ value, theme, @@ -636,60 +385,21 @@ const Axes: FunctionComponent = ({theme, barWidth, y, getFrac}) => { strokeWidth: 2, strokeLinecap: 'round', } -======= -const Axes: FunctionComponent<{ - theme: Required - barWidth: number - y: number - getFrac: (x: number) => number -}> = ({theme, barWidth, y, getFrac}) => { - const {axesSteps, axesFormater, axesFontColor, axesFontSize} = theme - - 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 axesValuesArray = Array.isArray(axesSteps) - ? axesSteps - : axesSteps === 'thresholds' - ? colors.thresholds.map(x => x.value) - : Number.isInteger(axesSteps) - ? range(axesSteps).map( - x => ((x + 1) * colorLen) / (axesSteps + 1) + colors.min.value - ) - : throwReturn( - `${JSON.stringify( - axesSteps - )} axesSteps must be number | "thresholds" | number[] | undefined.` - ) ->>>>>>> f03fd3e (feat: gauge mini) const points: { anchor: string value: number lineLength: number -<<<<<<< HEAD text: string posX: number }[] = axesSteps .map(value => ({ value, anchor: 'middle', -======= - }[] = axesValuesArray - .map(value => ({ - anchor: 'middle', - value, ->>>>>>> f03fd3e (feat: gauge mini) lineLength: 5, })) .concat([ { -<<<<<<< HEAD value: min.value, anchor: 'start', lineLength: 3, @@ -724,53 +434,11 @@ const Axes: FunctionComponent<{ ))} -======= - anchor: 'start', - lineLength: 3, - value: colors.min.value, - }, - { - anchor: 'end', - lineLength: 3, - value: colors.max.value, - }, - ]) - - return ( - <> - - - {points.map(({anchor, lineLength, value}, i) => { - const posX = getFrac(value) * barWidth - const text = axesFormater(value) - return ( - <> - - - - {text} - - - - ) - })} ->>>>>>> f03fd3e (feat: gauge mini) ) } -<<<<<<< HEAD //#endregion subcomponents export const GaugeMini: FunctionComponent = ({ @@ -822,68 +490,17 @@ export const GaugeMini: FunctionComponent = ({ axesSteps, axesFontSize, ]) -======= -export const GaugeMini: FunctionComponent = ({ - value, - theme, - width, - height, -}) => { - const { - gaugeHeight, - sidePaddings: gaugePaddingSides, - valueHeight, - barPaddings, - labelMain, - labelBars, - labelMainFontSize, - labelMainFontColor, - labelBarsFontColor, - labelBarsFontSize, - } = theme - const [barLabelsWidth] = useState([]) - - const valueArray = Array.isArray(value) - ? value - : [{_field: '_default', 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 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]) ->>>>>>> f03fd3e (feat: gauge mini) /** return value as fraction 0->min 1->max */ const getFrac = (val: number): number => (val - colors.min.value) / colorLen return ( -<<<<<<< HEAD -======= - - ->>>>>>> f03fd3e (feat: gauge mini) {labelMain && ( = ({ {labelMain} )} -<<<<<<< HEAD {Object.entries(values).map(([group, value], i) => { const y = 0 + i * (maxBarHeight + barPaddings) const textCenter = y + maxBarHeight / 2 const label = labelBarsEnabled ? group : '' -======= - {valueArray.map(({_field, value}, i) => { - const y = 0 + i * (maxBarHeight + barPaddings) - const label = labelBars?.find(({_field: f}) => f === _field)?.label - - const textCenter = y + maxBarHeight / 2 ->>>>>>> f03fd3e (feat: gauge mini) // todo: no rerender ? const onRectChanged = (r: DOMRect) => { @@ -929,7 +538,6 @@ export const GaugeMini: FunctionComponent = ({ ) })} = ({ y: allBarsHeight + barPaddings, getFrac, }} -======= - {...{barWidth, theme, value, y: allBarsHeight + barPaddings, getFrac}} ->>>>>>> f03fd3e (feat: gauge mini) /> diff --git a/giraffe/src/index.ts b/giraffe/src/index.ts index ad772d2b..dcfea112 100644 --- a/giraffe/src/index.ts +++ b/giraffe/src/index.ts @@ -36,10 +36,7 @@ export { FluxDataType, Formatter, GaugeLayerConfig, -<<<<<<< HEAD GaugeTheme, -======= ->>>>>>> f03fd3e (feat: gauge mini) GaugeMiniLayerConfig, GetColumn, HistogramLayerConfig, From 65e98e55f1f7799526ac2a389e876a7668728321 Mon Sep 17 00:00:00 2001 From: Sciator <39964450+Sciator@users.noreply.github.com> Date: Mon, 1 Mar 2021 11:54:34 +0100 Subject: [PATCH 34/34] style: prettier fix --- stories/src/candlestick.stories.tsx | 4 +++- stories/src/gaugeMini.stories.tsx | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/stories/src/candlestick.stories.tsx b/stories/src/candlestick.stories.tsx index 342a7d42..3c6eced5 100644 --- a/stories/src/candlestick.stories.tsx +++ b/stories/src/candlestick.stories.tsx @@ -119,7 +119,9 @@ const editableLayer = (theme: Theme): Theme => ({ theme.window === 'detect', knobGroups.basics ) - if (detect) return 'detect' + if (detect) { + return 'detect' + } return number( 'window fixed', typeof theme.window === 'number' || 1_000, diff --git a/stories/src/gaugeMini.stories.tsx b/stories/src/gaugeMini.stories.tsx index fd34be73..d81e284d 100644 --- a/stories/src/gaugeMini.stories.tsx +++ b/stories/src/gaugeMini.stories.tsx @@ -39,7 +39,9 @@ const randInt: { (max: number): number (min: number, max: number): number } = (min: number, max?: number) => { - if (max === undefined) return Math.floor(Math.random() * min) + if (max === undefined) { + return Math.floor(Math.random() * min) + } return min + Math.floor(Math.random() * (max - min)) }