diff --git a/packages/heatmap/src/types.ts b/packages/heatmap/src/types.ts index d7c2488c36..06dddd5a32 100644 --- a/packages/heatmap/src/types.ts +++ b/packages/heatmap/src/types.ts @@ -145,7 +145,10 @@ export type HeatMapCommonProps = { > labelTextColor: InheritedColorConfig, 'labelTextColor'>> - legends: Omit[] + legends: Omit< + AnchoredContinuousColorsLegendProps, + 'scale' | 'containerWidth' | 'containerHeight' + >[] annotations: AnnotationMatcher>[] diff --git a/website/src/components/components/ActionsLogger.tsx b/website/src/components/components/ActionsLogger.tsx index 3dc328d6bf..69112311c0 100644 --- a/website/src/components/components/ActionsLogger.tsx +++ b/website/src/components/components/ActionsLogger.tsx @@ -1,13 +1,15 @@ import React, { useState, useCallback } from 'react' import styled from 'styled-components' import { FaRegHandPointer } from 'react-icons/fa' -import { ActionsLoggerLog } from './ActionsLoggerLog' +import { ActionsLoggerLog, ActionsLoggerLogData } from './ActionsLoggerLog' import media from '../../theming/mediaQueries' -export const useActionsLogger = (): [any[], (action: any) => void] => { - const [actions, setActions] = useState([]) +export type ActionLoggerLogFn = (action: ActionsLoggerLogData) => void + +export const useActionsLogger = (): [ActionsLoggerLogData[], ActionLoggerLogFn] => { + const [actions, setActions] = useState([]) const logAction = useCallback( - action => { + (action: ActionsLoggerLogData) => { setActions(actions => [action, ...actions]) }, [setActions] @@ -17,7 +19,7 @@ export const useActionsLogger = (): [any[], (action: any) => void] => { } interface ActionsLoggerProps { - actions: any[] + actions: ActionsLoggerLogData[] isFullWidth?: boolean } @@ -31,11 +33,9 @@ export const ActionsLogger = ({ actions, isFullWidth = false }: ActionsLoggerPro Start interacting with the chart to log actions )} - {actions.map((action, i) => { - return ( - - ) - })} + {actions.map((action, i) => ( + + ))} ) } diff --git a/website/src/components/components/ActionsLoggerLog.tsx b/website/src/components/components/ActionsLoggerLog.tsx index fc6d25ca44..536ea25023 100644 --- a/website/src/components/components/ActionsLoggerLog.tsx +++ b/website/src/components/components/ActionsLoggerLog.tsx @@ -1,7 +1,14 @@ import React, { useState, useCallback } from 'react' import styled from 'styled-components' -export const ActionsLoggerLog = ({ action }: { action: any }) => { +export interface ActionsLoggerLogData { + type: string + label: string + color?: string + data?: object +} + +export const ActionsLoggerLog = ({ action }: { action: ActionsLoggerLogData }) => { const [isOpen, setIsOpen] = useState(false) const toggle = useCallback(() => setIsOpen(flag => !flag), [setIsOpen]) diff --git a/website/src/components/components/ComponentTemplate.tsx b/website/src/components/components/ComponentTemplate.tsx index 247f8d49ad..4bf0e9baf4 100644 --- a/website/src/components/components/ComponentTemplate.tsx +++ b/website/src/components/components/ComponentTemplate.tsx @@ -11,12 +11,17 @@ import { ComponentHeader } from './ComponentHeader' import { ComponentFlavorSelector } from './ComponentFlavorSelector' import { ComponentDescription } from './ComponentDescription' import { ComponentTabs } from './ComponentTabs' -import { ActionsLogger, useActionsLogger } from './ActionsLogger' +import { ActionsLogger, useActionsLogger, ActionLoggerLogFn } from './ActionsLogger' import { ComponentSettings } from './ComponentSettings' import { Stories } from './Stories' import { ChartMeta, ChartProperty, Flavor } from '../../types' -interface ComponentTemplateProps { +interface ComponentTemplateProps< + UnmappedProps extends object, + MappedProps extends object, + Data, + ComponentProps extends object +> { name: string meta: ChartMeta icon: string @@ -32,22 +37,27 @@ interface ComponentTemplateProps - propertiesMapper?: (unmappedProps: UnmappedProps) => Props - codePropertiesMapper?: Function - hasData?: boolean - generateData?: (previousData?: Data | null) => Data | undefined + defaultProperties?: Partial + propertiesMapper?: (props: UnmappedProps, data: Data) => MappedProps + codePropertiesMapper?: (props: MappedProps, data: Data) => MappedProps + generateData: (previousData?: Data | null) => Data dataKey?: string getDataSize?: (data: Data) => number getTabData?: (data: Data) => Data - children: (properties: Props, data: Data, theme: NivoTheme, logAction: any) => JSX.Element + children: ( + properties: MappedProps, + data: Data, + theme: NivoTheme, + logAction: ActionLoggerLogFn + ) => JSX.Element image?: IGatsbyImageData } export const ComponentTemplate = < UnmappedProps extends object = any, - Props extends object = any, - Data = any + MappedProps extends object = any, + Data = any, + ComponentProps extends object = any >({ name, meta, @@ -59,29 +69,30 @@ export const ComponentTemplate = < defaultProperties = {}, propertiesMapper, codePropertiesMapper, - hasData = true, - generateData = () => undefined, - dataKey, + generateData, + dataKey = 'data', getDataSize, getTabData = data => data, image, children, -}: ComponentTemplateProps) => { +}: ComponentTemplateProps) => { const theme = useTheme() - const [settings, setSettings] = useState(initialProperties) + const [settings, setSettings] = useState(initialProperties) + + const [data, setData] = useState(() => generateData()) - const initialData = useMemo(() => (hasData ? generateData() : null), []) - const [data, setData] = useState(initialData) const diceRoll = useCallback(() => { setData(currentData => generateData(currentData)) - }, [setData]) + }, [setData, generateData]) const [actions, logAction] = useActionsLogger() - let mappedProperties = settings as unknown as Props + let mappedProperties: MappedProps if (propertiesMapper !== undefined) { mappedProperties = propertiesMapper(settings, data) + } else { + mappedProperties = settings as unknown as MappedProps } let codeProperties = mappedProperties @@ -92,7 +103,7 @@ export const ComponentTemplate = < const code = generateChartCode(`Responsive${name}`, codeProperties, { pkg: meta.package, defaults: defaultProperties, - dataKey: hasData ? dataKey : undefined, + dataKey: data !== undefined ? dataKey : undefined, }) const hasStories = meta.stories !== undefined && meta.stories.length > 0 @@ -103,6 +114,8 @@ export const ComponentTemplate = < const flavorKeys = useMemo(() => flavors.map(flavor => flavor.flavor), [flavors]) + const tabData = useMemo(() => getTabData(data), [data]) + return ( @@ -113,12 +126,10 @@ export const ComponentTemplate = < chartClass={icon} code={code} - data={hasData ? getTabData(data!) : undefined} + data={tabData} dataKey={dataKey} - nodeCount={ - hasData && getDataSize !== undefined ? getDataSize(data!) : undefined - } - diceRoll={hasData ? diceRoll : undefined} + nodeCount={getDataSize !== undefined ? getDataSize(data) : undefined} + diceRoll={data !== undefined ? diceRoll : undefined} > {children(mappedProperties, data, theme.nivo, logAction)} diff --git a/website/src/data/components/heatmap/generator.ts b/website/src/data/components/heatmap/generator.ts index 9a6cef4e5c..041294e607 100644 --- a/website/src/data/components/heatmap/generator.ts +++ b/website/src/data/components/heatmap/generator.ts @@ -1,70 +1,30 @@ -import { generateCountriesData } from '@nivo/generators' +import { generateXYSeries, sets } from '@nivo/generators' +import { Data } from './types' -const dishes = [ - 'hot dog', - 'burger', - 'sandwich', - 'kebab', - 'fries', - 'donut', - 'junk', - 'sushi', - 'ramen', - 'curry', - 'udon', - 'bagel', - 'yakitori', - 'takoyaki', - 'tacos', - 'miso soup', - 'tortilla', - 'tapas', - 'chipirones', - 'gazpacho', - 'soba', - 'bavette', - 'steak', - 'pizza', - 'spaghetti', - 'ravioli', - 'salad', - 'pad thai', - 'bun', - 'waffle', - 'crepe', - 'churros', - 'paella', - 'empanadas', - 'bruschetta', - 'onion soup', - 'cassoulet', - 'bouillabaisse', - 'unagi', - 'tempura', - 'tonkatsu', - 'shabu-shabu', - 'twinkies', - 'jerky', - 'fajitas', - 'jambalaya', - 'meatloaf', - `mac n' cheese`, - 'baked beans', - 'popcorn', - 'buffalo wings', - 'BBQ ribs', - 'apple pie', - 'nachos', - 'risotto', - 'tiramisu', -] +export const getLightData = () => + generateXYSeries({ + serieIds: ['Japan', 'France', 'US', 'Germany', 'Norway', 'Iceland', 'UK', 'Vietnam'], + x: { + values: ['Train', 'Subway', 'Bus', 'Car', 'Boat', 'Moto', 'Moped', 'Bicycle', 'Others'], + }, + y: { + length: NaN, + min: -100_000, + max: 100_000, + round: true, + }, + }) as Data -export const generateLightDataSet = () => ({ - data: generateCountriesData(dishes.slice(0, 11), { size: 9, min: 0, max: 100 }), - keys: dishes.slice(0, 11), -}) - -export const generateHeavyDataSet = () => ({ - data: generateCountriesData(dishes, { size: 22, min: 0, max: 100 }), - keys: dishes, -}) +export const getHeavyData = () => + generateXYSeries({ + serieIds: sets.countryCodes.slice(0, 26), + x: { + values: sets.names, + }, + y: { + length: NaN, + min: -100_000, + max: 100_000, + round: true, + }, + }) as Data diff --git a/website/src/data/components/heatmap/mapper.ts b/website/src/data/components/heatmap/mapper.ts new file mode 100644 index 0000000000..d26d26b5b9 --- /dev/null +++ b/website/src/data/components/heatmap/mapper.ts @@ -0,0 +1,21 @@ +import { settingsMapper, mapAxis, mapFormat } from '../../../lib/settings' +import { SvgUnmappedProps, SvgMappedProps } from './types' + +export default settingsMapper( + { + valueFormat: mapFormat, + axisTop: mapAxis('top'), + axisRight: mapAxis('right'), + axisBottom: mapAxis('bottom'), + axisLeft: mapAxis('left'), + legends: (legends: SvgUnmappedProps['legends'][number][]): SvgMappedProps['legends'] => { + return legends.map(legend => ({ + ...legend, + tickFormat: mapFormat(legend.tickFormat), + })) + }, + }, + { + exclude: ['enable axisTop', 'enable axisRight', 'enable axisBottom', 'enable axisLeft'], + } +) diff --git a/website/src/data/components/heatmap/mapper.tsx b/website/src/data/components/heatmap/mapper.tsx deleted file mode 100644 index 571aee19e5..0000000000 --- a/website/src/data/components/heatmap/mapper.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react' -import { settingsMapper, mapAxis, mapFormat } from '../../../lib/settings' - -const CustomCell = ({ - value, - x, - y, - width, - height, - color, - opacity, - borderWidth, - borderColor, - textColor, -}) => ( - - - - {value} - - -) - -export default settingsMapper( - { - valueFormat: mapFormat, - cellShape: (value: string) => { - if (value === `Custom(props) => (…)`) return CustomCell - return value - }, - axisTop: mapAxis('top'), - axisRight: mapAxis('right'), - axisBottom: mapAxis('bottom'), - axisLeft: mapAxis('left'), - legends: (legends: any[]) => { - return legends.map(legend => ({ - ...legend, - tickFormat: mapFormat(legend.tickFormat), - })) - }, - }, - { - exclude: ['enable axisTop', 'enable axisRight', 'enable axisBottom', 'enable axisLeft'], - } -) diff --git a/website/src/data/components/heatmap/props.ts b/website/src/data/components/heatmap/props.ts index 9c352c81ee..ebe800b6c1 100644 --- a/website/src/data/components/heatmap/props.ts +++ b/website/src/data/components/heatmap/props.ts @@ -426,7 +426,7 @@ const props: ChartProperty[] = [ defaultValue: 'rect', control: { type: 'choices', - choices: ['rect', 'circle', 'CustomCell'].map(key => ({ + choices: ['rect', 'circle'].map(key => ({ label: key, value: key, })), diff --git a/website/src/data/components/heatmap/types.ts b/website/src/data/components/heatmap/types.ts new file mode 100644 index 0000000000..9d411345a5 --- /dev/null +++ b/website/src/data/components/heatmap/types.ts @@ -0,0 +1,69 @@ +import { HeatMapDataProps, HeatMapSvgProps, HeatMapCanvasProps } from '@nivo/heatmap' + +export type Datum = { + x: string + y: number +} + +export type ExtraProps = Record + +export type Data = HeatMapDataProps['data'] + +export type SvgMappedProps = Omit, 'data' | 'width' | 'height'> + +type LegendProps = NonNullable[number] + +export type SvgUnmappedProps = Omit< + SvgMappedProps, + 'valueFormat' | 'axisTop' | 'axisRight' | 'axisBottom' | 'axisLeft' | 'legends' +> & { + valueFormat: { + format: string + enabled: boolean + } + axisTop: SvgMappedProps['axisTop'] & { enable: boolean } + axisRight: SvgMappedProps['axisRight'] & { enable: boolean } + axisBottom: SvgMappedProps['axisBottom'] & { enable: boolean } + axisLeft: SvgMappedProps['axisLeft'] & { enable: boolean } + legends: (Omit & { + tickFormat: { + format: string + enabled: boolean + } + })[] +} + +export type SvgComponentProps = Omit< + HeatMapSvgProps, + 'data' | 'width' | 'height' +> + +export type CanvasMappedProps = Omit< + HeatMapCanvasProps, + 'data' | 'width' | 'height' +> + +export type CanvasUnmappedProps = Omit< + CanvasMappedProps, + 'valueFormat' | 'axisTop' | 'axisRight' | 'axisBottom' | 'axisLeft' | 'legends' +> & { + valueFormat: { + format: string + enabled: boolean + } + axisTop: SvgMappedProps['axisTop'] & { enable: boolean } + axisRight: SvgMappedProps['axisRight'] & { enable: boolean } + axisBottom: SvgMappedProps['axisBottom'] & { enable: boolean } + axisLeft: SvgMappedProps['axisLeft'] & { enable: boolean } + legends: (Omit & { + tickFormat: { + format: string + enabled: boolean + } + })[] +} + +export type CanvasComponentProps = Omit< + HeatMapCanvasProps, + 'data' | 'width' | 'height' +> diff --git a/website/src/lib/generateChartCode.ts b/website/src/lib/generateChartCode.ts index 5aa61f411b..766e32fbd7 100644 --- a/website/src/lib/generateChartCode.ts +++ b/website/src/lib/generateChartCode.ts @@ -34,7 +34,7 @@ export const generateChartCode = ( name: string, props: any, { - dataKey = 'data', + dataKey, children = [], defaults = {}, pkg = 'nivo', @@ -48,7 +48,7 @@ export const generateChartCode = ( const properties = [] let args = '' - if (dataKey !== null) { + if (dataKey !== undefined) { properties.push(`${dataKey}={${dataKey}}`) args = `{ ${dataKey} /* see ${dataKey} tab */ }` } diff --git a/website/src/lib/settings.ts b/website/src/lib/settings.ts index fc5d81db73..5d6d9c0e61 100644 --- a/website/src/lib/settings.ts +++ b/website/src/lib/settings.ts @@ -21,5 +21,5 @@ export const settingsMapper = export const mapAxis = (type: string) => (value: any, settings: any) => settings[`axis${upperFirst(type)}`].enable ? omit(value, ['enable']) : null -export const mapFormat = ({ format, enabled }: { format: any; enabled: boolean }) => +export const mapFormat = ({ format, enabled }: { format: string; enabled: boolean }) => enabled ? format : undefined diff --git a/website/src/pages/geomap/canvas.js b/website/src/pages/geomap/canvas.js index 33e1e41b2b..4070eb0438 100644 --- a/website/src/pages/geomap/canvas.js +++ b/website/src/pages/geomap/canvas.js @@ -65,7 +65,7 @@ const GeoMapCanvas = () => { features: '/* please have a look at the description for usage */', ...properties, })} - hasData={false} + generateData={() => undefined} image={image} > {(properties, data, theme, logAction) => { diff --git a/website/src/pages/geomap/index.js b/website/src/pages/geomap/index.js index 1f3ff1fe3c..3eb60e8a74 100644 --- a/website/src/pages/geomap/index.js +++ b/website/src/pages/geomap/index.js @@ -62,7 +62,7 @@ const GeoMap = () => { features: '/* please have a look at the description for usage */', ...properties, })} - hasData={false} + generateData={() => undefined} image={image} > {(properties, data, theme, logAction) => { diff --git a/website/src/pages/heatmap/api.tsx b/website/src/pages/heatmap/api.tsx index 164e4709d3..b979a9bf22 100644 --- a/website/src/pages/heatmap/api.tsx +++ b/website/src/pages/heatmap/api.tsx @@ -3,11 +3,11 @@ import { Seo } from '../../components/Seo' import { ApiClient } from '../../components/components/api-client/ApiClient' import { groups } from '../../data/components/heatmap/props' import mapper from '../../data/components/heatmap/mapper' -import { generateLightDataSet } from '../../data/components/heatmap/generator' +import { getLightData } from '../../data/components/heatmap/generator' import meta from '../../data/components/heatmap/meta.yml' import { graphql, useStaticQuery } from 'gatsby' -const data = generateLightDataSet() +const data = getLightData() const HeatMapApi = () => { const { diff --git a/website/src/pages/heatmap/canvas.tsx b/website/src/pages/heatmap/canvas.tsx index 9ce82526b8..013f9ad393 100644 --- a/website/src/pages/heatmap/canvas.tsx +++ b/website/src/pages/heatmap/canvas.tsx @@ -1,28 +1,21 @@ import React from 'react' import { graphql, useStaticQuery } from 'gatsby' -import isFunction from 'lodash/isFunction' import { ResponsiveHeatMapCanvas, canvasDefaultProps as defaults } from '@nivo/heatmap' -import { generateXYSeries, sets } from '@nivo/generators' import { ComponentTemplate } from '../../components/components/ComponentTemplate' import meta from '../../data/components/heatmap/meta.yml' import mapper from '../../data/components/heatmap/mapper' import { groups } from '../../data/components/heatmap/props' +import { getHeavyData } from '../../data/components/heatmap/generator' +import { + Datum, + ExtraProps, + Data, + CanvasUnmappedProps, + CanvasMappedProps, + CanvasComponentProps, +} from '../../data/components/heatmap/types' -const getData = () => - generateXYSeries({ - serieIds: sets.countryCodes.slice(0, 26), - x: { - values: sets.names, - }, - y: { - length: NaN, - min: -100_000, - max: 100_000, - round: true, - }, - }) - -const initialProperties = { +const initialProperties: CanvasUnmappedProps = { margin: { top: 70, right: 90, @@ -48,7 +41,6 @@ const initialProperties = { enableGridY: false, axisTop: { enable: true, - orient: 'top', tickSize: 5, tickPadding: 5, tickRotation: -90, @@ -57,7 +49,6 @@ const initialProperties = { }, axisRight: { enable: true, - orient: 'right', tickSize: 5, tickPadding: 5, tickRotation: 0, @@ -67,7 +58,6 @@ const initialProperties = { }, axisBottom: { enable: false, - orient: 'bottom', tickSize: 5, tickPadding: 5, tickRotation: -90, @@ -77,7 +67,6 @@ const initialProperties = { }, axisLeft: { enable: true, - orient: 'left', tickSize: 5, tickPadding: 5, tickRotation: 0, @@ -106,6 +95,7 @@ const initialProperties = { legends: [ { + id: 'default', anchor: 'bottom', translateX: 0, translateY: 30, @@ -148,7 +138,7 @@ const HeatMapCanvas = () => { `) return ( - name="HeatMapCanvas" meta={meta.HeatMapCanvas} icon="heatmap" @@ -156,21 +146,15 @@ const HeatMapCanvas = () => { currentFlavor="canvas" properties={groups} initialProperties={initialProperties} + defaultProperties={defaults as CanvasComponentProps} propertiesMapper={mapper} - codePropertiesMapper={(properties, data) => ({ - keys: data.keys, - ...properties, - cellShape: isFunction(properties.cellShape) - ? 'Custom(props) => (…)' - : properties.cellShape, - })} - generateData={getData} + generateData={getHeavyData} getDataSize={data => data.length * data[0].data.length} image={image} > {(properties, data, theme, logAction) => { return ( - data={data} {...properties} theme={theme} diff --git a/website/src/pages/heatmap/index.tsx b/website/src/pages/heatmap/index.tsx index af373c3c4a..9fd968f81f 100644 --- a/website/src/pages/heatmap/index.tsx +++ b/website/src/pages/heatmap/index.tsx @@ -1,28 +1,21 @@ import React from 'react' import { graphql, useStaticQuery } from 'gatsby' -import isFunction from 'lodash/isFunction' import { ResponsiveHeatMap, svgDefaultProps as defaults } from '@nivo/heatmap' -import { generateXYSeries } from '@nivo/generators' import { ComponentTemplate } from '../../components/components/ComponentTemplate' import meta from '../../data/components/heatmap/meta.yml' import mapper from '../../data/components/heatmap/mapper' +import { getLightData } from '../../data/components/heatmap/generator' import { groups } from '../../data/components/heatmap/props' +import { + Datum, + ExtraProps, + Data, + SvgUnmappedProps, + SvgMappedProps, + SvgComponentProps, +} from '../../data/components/heatmap/types' -const getData = () => - generateXYSeries({ - serieIds: ['Japan', 'France', 'US', 'Germany', 'Norway', 'Iceland', 'UK', 'Vietnam'], - x: { - values: ['Train', 'Subway', 'Bus', 'Car', 'Boat', 'Moto', 'Moped', 'Bicycle', 'Others'], - }, - y: { - length: NaN, - min: -100_000, - max: 100_000, - round: true, - }, - }) - -const initialProperties = { +const initialProperties: SvgUnmappedProps = { margin: { top: 60, right: 90, @@ -45,7 +38,6 @@ const initialProperties = { enableGridY: defaults.enableGridY, axisTop: { enable: true, - orient: 'top', tickSize: 5, tickPadding: 5, tickRotation: -90, @@ -54,7 +46,6 @@ const initialProperties = { }, axisRight: { enable: true, - orient: 'right', tickSize: 5, tickPadding: 5, tickRotation: 0, @@ -64,7 +55,6 @@ const initialProperties = { }, axisBottom: { enable: false, - orient: 'bottom', tickSize: 5, tickPadding: 5, tickRotation: -90, @@ -74,7 +64,6 @@ const initialProperties = { }, axisLeft: { enable: true, - orient: 'left', tickSize: 5, tickPadding: 5, tickRotation: 0, @@ -104,6 +93,7 @@ const initialProperties = { legends: [ { + id: 'default', anchor: 'bottom', translateX: 0, translateY: 30, @@ -146,7 +136,7 @@ const HeatMap = () => { `) return ( - name="HeatMap" meta={meta.HeatMap} icon="heatmap" @@ -154,21 +144,14 @@ const HeatMap = () => { currentFlavor="svg" properties={groups} initialProperties={initialProperties} - defaultProperties={defaults} + defaultProperties={defaults as SvgComponentProps} propertiesMapper={mapper} - codePropertiesMapper={properties => ({ - ...properties, - cellShape: isFunction(properties.cellShape) - ? 'Custom(props) => (…)' - : properties.cellShape, - })} - generateData={getData} - // getTabData={data => data.data} + generateData={getLightData} image={image} > {(properties, data, theme, logAction) => { return ( - data={data} {...properties} theme={theme}