Skip to content

Commit

Permalink
fix: Use correct scale when hovering over stacked line graphs (#153)
Browse files Browse the repository at this point in the history
* fix: Use correct scale when hovering over stacked line graphs

* fix: remove unnecessary options from createSampleTable

* fix: use random decimals in Storybook

* fix: update tests and improve code readability
  • Loading branch information
TCL735 authored Feb 7, 2020
1 parent d9fc633 commit f8920cf
Show file tree
Hide file tree
Showing 7 changed files with 374 additions and 20 deletions.
11 changes: 8 additions & 3 deletions giraffe/src/components/LineLayer.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as React from 'react'
import {useMemo, useRef, FunctionComponent} from 'react'

import {LayerProps, LineLayerSpec, LineLayerConfig} from '../types'
import {LayerProps, LineLayerSpec, LineLayerConfig, DomainLabel} from '../types'
import {LineHoverLayer} from './LineHoverLayer'
import {simplifyLineData} from '../utils/lineData'
import {simplifyLineData, getDomainDataFromLines} from '../utils/lineData'
import {useCanvas} from '../utils/useCanvas'
import {drawLines} from '../utils/drawLines'
import {useHoverPointIndices} from '../utils/useHoverPointIndices'
Expand All @@ -16,6 +16,7 @@ export interface Props extends LayerProps {

export const LineLayer: FunctionComponent<Props> = props => {
const {config, spec, width, height, xScale, yScale, hoverX, hoverY} = props
const {position} = config

const simplifiedLineData = useMemo(
() => simplifyLineData(spec.lineData, xScale, yScale),
Expand Down Expand Up @@ -54,12 +55,16 @@ export const LineLayer: FunctionComponent<Props> = props => {
hoverDimension = config.hoverDimension
}

const hoverYColumnData =
position === 'stacked'
? getDomainDataFromLines(spec.lineData, DomainLabel.Y)
: spec.table.getColumn(config.y, 'number')
const hoverRowIndices = useHoverPointIndices(
hoverDimension,
hoverX,
hoverY,
spec.table.getColumn(config.x, 'number'),
spec.table.getColumn(config.y, 'number'),
hoverYColumnData,
spec.table.getColumn(FILL, 'number'),
xScale,
yScale,
Expand Down
2 changes: 2 additions & 0 deletions giraffe/src/constants/columnKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ export const COUNT = 'count'
// Computed column names mapped to group aesthetics
export const FILL = '__fill'
export const SYMBOL = '__symbol'
export const STACKED_LINE_CUMULATIVE = 'cumulative'
export const LINE_COUNT = 'lines'
56 changes: 56 additions & 0 deletions giraffe/src/utils/fixtures/tooltip.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {newTable} from '../newTable'

interface SampleTableOptions {
include_negative?: boolean
all_negative?: boolean
decimalPlaces?: number
maxValue?: number
numberOfRecords?: number
recordsPerLine?: number
}

const getRandomNumber = (
max: number,
decimalPlaces: number,
include_negative?: boolean
) => {
const result = include_negative
? Number((Math.random() * (2 * max + 1) - max).toFixed(decimalPlaces))
: Number((Math.random() * max).toFixed(decimalPlaces))

return result === -0 ? 0 : result // eslint-disable-line no-compare-neg-zero
}

export const COLUMN_KEY = 'cpu'

export const createSampleTable = (options: SampleTableOptions) => {
const {
include_negative = false,
all_negative = false,
decimalPlaces = 2,
maxValue = 100,
numberOfRecords = 20,
recordsPerLine = 5,
} = options

const now = Date.now()
const TIME_COL = []
const VALUE_COL = []
const CPU_COL = []

for (let i = 0; i < numberOfRecords; i += 1) {
let num = getRandomNumber(maxValue, decimalPlaces)
if (include_negative) {
num = all_negative
? Math.abs(num) * -1
: getRandomNumber(maxValue, decimalPlaces, true)
}
VALUE_COL.push(num)
CPU_COL.push(`${COLUMN_KEY}${Math.floor(i / recordsPerLine)}`)
TIME_COL.push(now + (i % recordsPerLine) * 1000 * 60)
}
return newTable(numberOfRecords)
.addColumn('_time', 'time', TIME_COL)
.addColumn('_value', 'number', VALUE_COL)
.addColumn('cpu', 'string', CPU_COL)
}
227 changes: 227 additions & 0 deletions giraffe/src/utils/tooltip.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
import {getPointsTooltipData} from './tooltip'
import {NINETEEN_EIGHTY_FOUR} from '../constants/colorSchemes'
import {
FILL,
STACKED_LINE_CUMULATIVE,
LINE_COUNT,
} from '../constants/columnKeys'
import {getNominalColorScale, createGroupIDColumn} from '../transforms'
import {lineTransform} from '../transforms/line'
import {createSampleTable, COLUMN_KEY} from './fixtures/tooltip'

describe('getPointsTooltipData', () => {
let sampleTable
const xColKey = '_time'
const yColKey = '_value'
const columnFormatter = () => x => String(x)
let lineSpec
let fillScale
let hoveredValues
let result
let cumulativeValueColumn

const numberOfRecords = 1000
const recordsPerLine = 10
let startingIndex

const setUp = options => {
const {hoveredRowIndices, position, ...tableOptions} = options
sampleTable = createSampleTable(tableOptions)
lineSpec = lineTransform(
sampleTable,
xColKey,
yColKey,
[COLUMN_KEY],
NINETEEN_EIGHTY_FOUR,
position
)

const [fillColumn, fillColumnMap] = createGroupIDColumn(sampleTable, [
COLUMN_KEY,
])
fillScale = getNominalColorScale(fillColumnMap, NINETEEN_EIGHTY_FOUR)
sampleTable = sampleTable.addColumn(FILL, 'number', fillColumn)

const fillColumnFromSampleTable = sampleTable.getColumn(FILL, 'number')
fillColumn.forEach((item, index) =>
expect(item).toEqual(fillColumnFromSampleTable[index])
)

const allValues = sampleTable.getColumn('_value')
hoveredValues = hoveredRowIndices.map(i => allValues[i])
}

describe('tooltip for overlaid line graph', () => {
const position = 'overlaid'

it('should have a value column that is not necessarily sorted', () => {
startingIndex = 3
const hoveredRowIndices = []
for (let i = startingIndex; i < numberOfRecords; i += recordsPerLine) {
hoveredRowIndices.push(i)
}
setUp({
include_negative: true,
all_negative: false,
numberOfRecords,
recordsPerLine,
hoveredRowIndices,
position,
})
result = getPointsTooltipData(
hoveredRowIndices,
sampleTable,
xColKey,
yColKey,
FILL,
columnFormatter,
[COLUMN_KEY],
fillScale,
'overlaid',
lineSpec.lineData
)
const singleValueColumn = result.find(column => column.key === yColKey)
expect(singleValueColumn.values.map(value => Number(value))).toEqual(
hoveredValues
)
})
})

describe('tooltip for stacked line graph', () => {
const position = 'stacked'

afterEach(() => {
const totalColumns = result.length
const colorsCounter = {}

result.forEach(column => {
const {colors} = column
colors.forEach(color => {
if (!colorsCounter[color]) {
colorsCounter[color] = 0
}
colorsCounter[color] += 1
})
})
expect(Object.keys(colorsCounter).length).toEqual(
numberOfRecords / recordsPerLine
)
expect(
Object.values(colorsCounter).every(
colorCount => colorCount === totalColumns
)
).toEqual(true)

cumulativeValueColumn = result.find(
column => column.key === STACKED_LINE_CUMULATIVE
)
expect(cumulativeValueColumn).toBeTruthy()
expect(
cumulativeValueColumn.values.every((value, index, arr) => {
if (index === 0) {
return true
}
return Number(arr[index - 1]) >= Number(value)
})
).toEqual(true)

expect(result.find(column => column.key === LINE_COUNT)).toBeTruthy()
})

it('should create proper columns when all values are positive numbers', () => {
startingIndex = 0
const hoveredRowIndices = []
for (let i = startingIndex; i < numberOfRecords; i += recordsPerLine) {
hoveredRowIndices.push(i)
}
setUp({
include_negative: false,
all_negative: false,
numberOfRecords,
recordsPerLine,
hoveredRowIndices,
position,
})
result = getPointsTooltipData(
hoveredRowIndices,
sampleTable,
xColKey,
yColKey,
FILL,
columnFormatter,
[COLUMN_KEY],
fillScale,
'stacked',
lineSpec.lineData
)
const singleValueColumn = result.find(column => column.key === yColKey)
expect(singleValueColumn).toBeTruthy()
expect(
singleValueColumn.values.map(value => Number(value)).reverse()
).toEqual(hoveredValues)
})

it('should create proper columns when all values are negative numbers', () => {
startingIndex = 1
const hoveredRowIndices = []
for (let i = startingIndex; i < numberOfRecords; i += recordsPerLine) {
hoveredRowIndices.push(i)
}
setUp({
include_negative: true,
all_negative: true,
numberOfRecords,
recordsPerLine,
hoveredRowIndices,
position,
})
result = getPointsTooltipData(
hoveredRowIndices,
sampleTable,
xColKey,
yColKey,
FILL,
columnFormatter,
[COLUMN_KEY],
fillScale,
'stacked',
lineSpec.lineData
)
const singleValueColumn = result.find(column => column.key === yColKey)

expect(singleValueColumn).toBeTruthy()
expect(singleValueColumn.values.map(value => Number(value))).toEqual(
hoveredValues
)
})

it('should create proper columns when values can be positive or negative', () => {
startingIndex = 2
const hoveredRowIndices = []
for (let i = startingIndex; i < numberOfRecords; i += recordsPerLine) {
hoveredRowIndices.push(i)
}
setUp({
include_negative: true,
all_negative: false,
numberOfRecords,
recordsPerLine,
hoveredRowIndices,
position,
})
result = getPointsTooltipData(
hoveredRowIndices,
sampleTable,
xColKey,
yColKey,
FILL,
columnFormatter,
[COLUMN_KEY],
fillScale,
'stacked',
lineSpec.lineData
)
expect(result.find(column => column.key === yColKey)).toBeTruthy()
})
})
})
Loading

0 comments on commit f8920cf

Please sign in to comment.