diff --git a/src/ui/Sparkline/Sparkline.spec.jsx b/src/ui/Sparkline/Sparkline.spec.tsx
similarity index 91%
rename from src/ui/Sparkline/Sparkline.spec.jsx
rename to src/ui/Sparkline/Sparkline.spec.tsx
index 614d9ca110..ec9a4d7acd 100644
--- a/src/ui/Sparkline/Sparkline.spec.jsx
+++ b/src/ui/Sparkline/Sparkline.spec.tsx
@@ -3,7 +3,11 @@ import { render, screen } from '@testing-library/react'
import Sparkline from '.'
describe('Sparkline', () => {
- function setup(props) {
+ function setup(props: {
+ datum: any[]
+ description: string
+ dataTemplate: (d: number | null | undefined) => string
+ }) {
render()
}
@@ -31,12 +35,14 @@ describe('Sparkline', () => {
it("renders the correct number of tr's", () => {
expect(screen.queryAllByRole('cell').length).toBe(8)
})
+
it('lines have an normal state', () => {
expect(screen.queryAllByRole('cell')[0]).toHaveAttribute(
'data-mode',
'normal'
)
})
+
it('lines have an empty state', () => {
expect(screen.queryAllByRole('cell')[4]).toHaveAttribute(
'data-mode',
diff --git a/src/ui/Sparkline/Sparkline.stories.jsx b/src/ui/Sparkline/Sparkline.stories.jsx
deleted file mode 100644
index 132892f44b..0000000000
--- a/src/ui/Sparkline/Sparkline.stories.jsx
+++ /dev/null
@@ -1,104 +0,0 @@
-import Sparkline from './Sparkline'
-
-const Template = (args) => (
-
- {/* Sparkline conforms to the width and height of it's parent. */}
-
-
-)
-const ManyTemplate = (args) => {
- const range = 200
- const largeDataSetWithReusedData = Array(50)
- .fill()
- .map(() => Math.random() * range)
- return (
- <>
- {Array(50)
- .fill()
- .map((_, i) => {
- return (
-
-
-
- )
- })}
- >
- )
-}
-
-const range = 200
-const createTestData = Array(20)
- .fill()
- .map(() => Math.random() * range - range / 2)
-
-export const NormalSparkline = Template.bind({})
-NormalSparkline.args = {
- datum: createTestData,
- description: 'storybook sparkline',
- dataTemplate: (d) => `Foo ${d}%`,
-}
-
-const createTestDataWMissing = Array(30)
- .fill()
- .map(() => (Math.random() > 0.4 ? Math.random() * range - range / 2 : null))
-
-export const SparklineWithMissingData = Template.bind({})
-SparklineWithMissingData.args = {
- datum: createTestDataWMissing,
- description: 'storybook sparkline',
- dataTemplate: (d) => `Foo ${d}%`,
-}
-
-const createTestDataWMissingBeginning = Array(7)
- .fill()
- .map((_, i) => (i > 2 ? Math.random() * range - range / 2 : null))
-
-export const SparklineWithMissingDataBeginning = Template.bind({})
-SparklineWithMissingDataBeginning.args = {
- datum: createTestDataWMissingBeginning,
- description: 'storybook sparkline',
- dataTemplate: (d) => `Foo ${d}%`,
-}
-
-const createTestDataWMissingEnding = Array(20)
- .fill()
- .map((_, i) => (i < 18 ? Math.random() * range - range / 2 : null))
-
-export const SparklineWithMissingDataEnding = Template.bind({})
-SparklineWithMissingDataEnding.args = {
- datum: createTestDataWMissingEnding,
- description: 'storybook sparkline',
- dataTemplate: (d) => `Foo ${d}%`,
-}
-
-const createTestDataComplex = Array(10)
- .fill()
- .map((_) => ({ value: Math.random() * range - range / 2, foo: 'bar' }))
-
-export const SparklineWithComplexData = Template.bind({})
-SparklineWithComplexData.args = {
- datum: createTestDataComplex,
- select: (d) => d?.value,
- description: 'storybook sparkline',
- dataTemplate: (d) => `Foo ${d}%`,
-}
-
-export const SparklineCustomLineWidth = Template.bind({})
-SparklineCustomLineWidth.args = {
- datum: createTestData,
- description: 'storybook sparkline',
- dataTemplate: (d) => `Foo ${d}%`,
- lineSize: 2,
-}
-
-export const ManySparklines = ManyTemplate.bind({})
-ManySparklines.args = {
- description: 'storybook sparkline',
- dataTemplate: (d) => `${d}%`,
-}
-
-export default {
- title: 'Components/Sparkline',
- component: Sparkline,
- parameters: {},
-}
diff --git a/src/ui/Sparkline/Sparkline.stories.tsx b/src/ui/Sparkline/Sparkline.stories.tsx
new file mode 100644
index 0000000000..b0148f4e53
--- /dev/null
+++ b/src/ui/Sparkline/Sparkline.stories.tsx
@@ -0,0 +1,135 @@
+import { Meta, StoryObj } from '@storybook/react'
+
+import Sparkline, { SparklineProps } from './Sparkline'
+
+export default {
+ title: 'Components/Sparkline',
+ component: Sparkline,
+} as Meta
+
+const renderTemplate = (args: SparklineProps) => (
+
+ {/* Sparkline conforms to the width and height of it's parent. */}
+
+
+)
+
+const renderManyTemplate = (args: SparklineProps) => {
+ const range = 200
+ const largeDataSetWithReusedData = Array(50)
+ .fill(0)
+ .map(() => Math.random() * range)
+ return (
+ <>
+ {Array(50)
+ .fill(0)
+ .map((_, i) => {
+ return (
+
+
+
+ )
+ })}
+ >
+ )
+}
+
+type Story = StoryObj
+const range = 200
+const createTestData = Array(20)
+ .fill(0)
+ .map(() => Math.random() * range - range / 2)
+
+export const NormalSparkline: Story = {
+ args: {
+ datum: createTestData,
+ description: 'storybook sparkline',
+ dataTemplate: (d) => `Foo ${d}%`,
+ },
+ render: (args) => {
+ return renderTemplate(args)
+ },
+}
+
+const createTestDataWithMissingValues = Array(30)
+ .fill(0)
+ .map(() => (Math.random() > 0.4 ? Math.random() * range - range / 2 : null))
+
+export const SparklineWithMissingData: Story = {
+ args: {
+ datum: createTestDataWithMissingValues,
+ description: 'storybook sparkline',
+ dataTemplate: (d) => `Foo ${d}%`,
+ },
+ render: (args) => {
+ return renderTemplate(args)
+ },
+}
+
+const createTestDataWithMissingValuesBeginning = Array(7)
+ .fill(0)
+ .map((_, i) => (i > 2 ? Math.random() * range - range / 2 : null))
+
+export const SparklineWithMissingDataBeginning: Story = {
+ args: {
+ datum: createTestDataWithMissingValuesBeginning,
+ description: 'storybook sparkline',
+ dataTemplate: (d) => `Foo ${d}%`,
+ },
+ render: (args) => {
+ return renderTemplate(args)
+ },
+}
+
+const createTestDataWithMissingValuesEnding = Array(20)
+ .fill(0)
+ .map((_, i) => (i < 18 ? Math.random() * range - range / 2 : null))
+
+export const SparklineWithMissingDataEnding: Story = {
+ args: {
+ datum: createTestDataWithMissingValuesEnding,
+ description: 'storybook sparkline',
+ dataTemplate: (d) => `Foo ${d}%`,
+ },
+ render: (args) => {
+ return renderTemplate(args)
+ },
+}
+
+const createTestDataComplex = Array(10)
+ .fill(0)
+ .map((_) => ({ value: Math.random() * range - range / 2, foo: 'bar' }))
+
+export const SparklineWithComplexData: Story = {
+ args: {
+ datum: createTestDataComplex,
+ select: (d) => d?.value,
+ description: 'storybook sparkline',
+ dataTemplate: (d) => `Foo ${d}%`,
+ },
+ render: (args) => {
+ return renderTemplate(args)
+ },
+}
+
+export const SparklineCustomLineWidth: Story = {
+ args: {
+ datum: createTestData,
+ description: 'storybook sparkline',
+ dataTemplate: (d) => `Foo ${d}%`,
+ lineSize: 2,
+ },
+ render: (args) => {
+ return renderTemplate(args)
+ },
+}
+
+export const ManySparklines: Story = {
+ args: {
+ description: 'storybook sparkline',
+ dataTemplate: (d) => `${d}%`,
+ },
+ render: (args) => {
+ return renderManyTemplate(args)
+ },
+}
diff --git a/src/ui/Sparkline/Sparkline.jsx b/src/ui/Sparkline/Sparkline.tsx
similarity index 56%
rename from src/ui/Sparkline/Sparkline.jsx
rename to src/ui/Sparkline/Sparkline.tsx
index d3ef2abeff..ca5c6039f3 100644
--- a/src/ui/Sparkline/Sparkline.jsx
+++ b/src/ui/Sparkline/Sparkline.tsx
@@ -2,24 +2,39 @@ import { extent } from 'd3-array'
import { scaleLinear } from 'd3-scale'
import isFinite from 'lodash/isFinite'
import uniqueId from 'lodash/uniqueId'
-import PropTypes from 'prop-types'
import { useMemo } from 'react'
import './sparkline.css'
const HORIZONTAL_PADDING = 10
const FALLBACK_LINE_POS = 0.5 // Value between 0-1
+type NumberOrNullOrUndefined = number | null | undefined
-const Sparkline = ({
+interface SparklineData {
+ value: NumberOrNullOrUndefined
+ start: NumberOrNullOrUndefined
+ end: NumberOrNullOrUndefined
+ mode: 'empty' | 'normal'
+}
+
+export interface SparklineProps {
+ datum: any[]
+ description: string
+ dataTemplate: (value: NumberOrNullOrUndefined) => string
+ select?: (data: any) => NumberOrNullOrUndefined
+ lineSize?: number
+}
+
+const Sparkline: React.FC = ({
datum,
description,
dataTemplate,
select = (data) => data,
lineSize = 1,
}) => {
- const data = useMemo(
+ const data: SparklineData[] = useMemo(
() =>
- datum.reduce((prev, curr, index) => {
+ datum.reduce((prev, curr, index) => {
const nextEntry = datum[index + 1]
const previousPoint = prev[prev.length - 1]
@@ -27,7 +42,7 @@ const Sparkline = ({
...prev,
{
/*
- Save the data points original selected value
+ Save the data point's original selected value
*/
value: select(curr),
/*
@@ -37,10 +52,10 @@ const Sparkline = ({
*/
start: select(curr) ? select(curr) : previousPoint?.end,
/*
- End is the next entire's value.
+ End is the next entry's value.
Used to draw a line from point a to b
*/
- end: select(nextEntry),
+ end: nextEntry ? select(nextEntry) : nextEntry,
/*
Sets the rendering mode of the line.
*/
@@ -50,25 +65,47 @@ const Sparkline = ({
}, []),
[datum, select]
)
- const [lowerDomain, upperDomain] = extent(data.map(({ value }) => value))
- const yPadding = upperDomain / HORIZONTAL_PADDING
- const yScale = scaleLinear()
- .domain([lowerDomain - yPadding, upperDomain + yPadding])
- .range([0, 1])
+
+ let yPadding
+ let yScale: (num: number) => number = (num) => num
+ const numericData = data
+ .map(({ value }) => value)
+ .filter((val) => typeof val === 'number') as number[]
+ const [lowerDomain, upperDomain] = extent(numericData)
+
+ if (upperDomain && lowerDomain) {
+ yPadding = upperDomain / HORIZONTAL_PADDING
+ yScale = scaleLinear()
+ .domain([lowerDomain - yPadding, upperDomain + yPadding])
+ .range([0, 1])
+ }
+
+ interface TableCustomCSSProperties extends React.CSSProperties {
+ '--line-width': string
+ '--start': string
+ '--size': string
+ }
const tableCssProperties = {
'--line-width': `${lineSize}px`,
}
return (
-
+
{description}
{data.map(({ start, end, mode, value }) => {
// Inline styles are not performant but because this is memoized it should be ok.
const properties = {
- '--start': start ? yScale(start).toFixed(2) : FALLBACK_LINE_POS,
- '--size': end ? yScale(end).toFixed(2) : FALLBACK_LINE_POS,
+ '--start': start
+ ? yScale(start).toFixed(2)
+ : FALLBACK_LINE_POS.toString(),
+ '--size': end
+ ? yScale(end).toFixed(2)
+ : FALLBACK_LINE_POS.toString(),
}
return (
{dataTemplate(value)}
@@ -90,12 +127,4 @@ const Sparkline = ({
)
}
-Sparkline.propTypes = {
- datum: PropTypes.array,
- select: PropTypes.func,
- description: PropTypes.string.isRequired,
- dataTemplate: PropTypes.func.isRequired,
- lineSize: PropTypes.number,
-}
-
export default Sparkline
|