From fec51a18d7a78e4e64eec9b8a222b2441904dcb3 Mon Sep 17 00:00:00 2001 From: Neil Kistner Date: Mon, 7 Jun 2021 09:49:05 -0500 Subject: [PATCH] feat(website): add TimeRange component --- .../src/data/components/time-range/mapper.js | 55 +++ .../src/data/components/time-range/meta.yml | 13 + .../src/data/components/time-range/props.js | 371 ++++++++++++++++++ website/src/data/nav.js | 7 + website/src/pages/time-range/index.js | 115 ++++++ 5 files changed, 561 insertions(+) create mode 100644 website/src/data/components/time-range/mapper.js create mode 100644 website/src/data/components/time-range/meta.yml create mode 100644 website/src/data/components/time-range/props.js create mode 100644 website/src/pages/time-range/index.js diff --git a/website/src/data/components/time-range/mapper.js b/website/src/data/components/time-range/mapper.js new file mode 100644 index 000000000..1804d37bc --- /dev/null +++ b/website/src/data/components/time-range/mapper.js @@ -0,0 +1,55 @@ +/* + * This file is part of the nivo project. + * + * Copyright 2016-present, Raphaël Benitte. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +import React from 'react' +import styled from 'styled-components' +import { settingsMapper } from '../../../lib/settings' + +const TooltipWrapper = styled.div` + display: grid; + background: #333; + padding: 10px; + border-radius: 4px; + grid-template-columns: 100px 1fr; + grid-column-gap: 12px; +` +const TooltipKey = styled.span` + font-weight: 600; +` + +const CustomTooltip = day => { + return ( + + day + {day.day} + value + {day.value} + coordinates.x + {day.coordinates.x} + coordinates.y + {day.coordinates.y} + height + {day.height} + width + {day.width} + + ) +} + +export default settingsMapper( + { + tooltip: (value, values) => { + if (!values['custom tooltip example']) return undefined + + return CustomTooltip + }, + }, + { + exclude: ['custom tooltip example'], + } +) diff --git a/website/src/data/components/time-range/meta.yml b/website/src/data/components/time-range/meta.yml new file mode 100644 index 000000000..ce61bfd46 --- /dev/null +++ b/website/src/data/components/time-range/meta.yml @@ -0,0 +1,13 @@ +flavors: + - flavor: svg + path: /time-range/ + +TimeRange: + package: '@nivo/calendar' + tags: + - svg + - isomorphic + stories: [] + description: | + The TimeRange chart is similar to the [Calendar](self:/calendar) chart, but + it allows you to specify dates less than a year. diff --git a/website/src/data/components/time-range/props.js b/website/src/data/components/time-range/props.js new file mode 100644 index 000000000..007c13f5d --- /dev/null +++ b/website/src/data/components/time-range/props.js @@ -0,0 +1,371 @@ +/* + * This file is part of the nivo project. + * + * Copyright 2016-present, Raphaël Benitte. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +import { boxAlignments } from '@nivo/core' +import { timeRangeDefaultProps as defaults } from '@nivo/calendar' +import { themeProperty, getLegendsProps, groupProperties } from '../../../lib/componentProperties' + +const props = [ + { + key: 'data', + group: 'Base', + help: 'Chart data.', + description: ` + Chart data, which must conform to this structure: + \`\`\` + Array<{ + day: string, // format must be YYYY-MM-DD + value: number + }> + \`\`\` + You can also add arbitrary data to this structure + to be used in tooltips or event handlers. + `, + type: 'object[]', + required: true, + }, + { + key: 'from', + group: 'Base', + help: 'start date', + type: 'string | Date', + required: true, + }, + { + key: 'to', + group: 'Base', + help: 'end date', + type: 'string | Date', + required: true, + }, + { + key: 'width', + enableControlForFlavors: ['api'], + help: 'Chart width.', + description: ` + not required if using responsive alternative of + the component \`\`. + `, + type: 'number', + required: true, + controlType: 'range', + group: 'Base', + controlOptions: { + unit: 'px', + min: 100, + max: 1000, + step: 5, + }, + }, + { + key: 'height', + enableControlForFlavors: ['api'], + help: 'Chart height.', + description: ` + not required if using responsive alternative of + the component \`\`. + `, + type: 'number', + required: true, + controlType: 'range', + group: 'Base', + controlOptions: { + unit: 'px', + min: 100, + max: 1000, + step: 5, + }, + }, + { + key: 'direction', + help: 'defines calendar layout direction.', + type: 'string', + required: false, + defaultValue: defaults.direction, + controlType: 'radio', + group: 'Base', + controlOptions: { + choices: [ + { label: 'horizontal', value: 'horizontal' }, + { label: 'vertical', value: 'vertical' }, + ], + }, + }, + { + key: 'margin', + help: 'Chart margin.', + type: 'object', + required: false, + controlType: 'margin', + group: 'Base', + }, + { + key: 'align', + help: 'defines how calendar should be aligned inside chart container.', + type: 'string', + required: false, + defaultValue: defaults.align, + controlType: 'boxAnchor', + group: 'Base', + controlOptions: { + choices: boxAlignments.map(align => ({ + label: align, + value: align, + })), + }, + }, + { + key: 'minValue', + help: 'Minimum value.', + description: ` + Minimum value. If 'auto', will pick the lowest value + in the provided data set. + Should be overriden if your data set does not contain + desired lower bound value. + `, + required: false, + defaultValue: defaults.minValue, + type: `number | 'auto'`, + controlType: 'switchableRange', + group: 'Base', + controlOptions: { + disabledValue: 'auto', + defaultValue: 0, + min: -300, + max: 300, + }, + }, + { + key: 'maxValue', + help: 'Maximum value.', + description: ` + Maximum value. If 'auto', will pick the highest value + in the provided data set. + Should be overriden if your data set does not contain + desired higher bound value. + `, + required: false, + defaultValue: defaults.maxValue, + type: `number | 'auto'`, + controlType: 'switchableRange', + group: 'Base', + controlOptions: { + disabledValue: 'auto', + defaultValue: 100, + min: 0, + max: 600, + }, + }, + themeProperty, + { + key: 'colors', + group: 'Style', + help: 'Cell colors.', + description: ` + An array of colors to be used in conjunction with + \`domain\` to compute days' color. + It applies to days having a value defined, otherwise, + \`emptyColor\` will be used. + `, + type: 'string[]', + required: false, + defaultValue: defaults.colors, + }, + // Months + { + key: 'monthLegend', + help: `can be used to customize months label, returns abbreviated month name (english) by default. This can be used to use a different language`, + type: '(year: number, month: number, date: Date) => string | number', + required: false, + group: 'Months', + }, + { + key: 'monthLegendPosition', + help: 'defines month legends position.', + type: `'before' | 'after'`, + required: false, + defaultValue: defaults.monthLegendPosition, + controlType: 'radio', + group: 'Months', + controlOptions: { + choices: [ + { label: 'before', value: 'before' }, + { label: 'after', value: 'after' }, + ], + }, + }, + { + key: 'monthLegendOffset', + help: 'define offset from month edge to its label.', + type: 'number', + required: false, + defaultValue: defaults.monthLegendOffset, + controlType: 'range', + group: 'Months', + controlOptions: { + unit: 'px', + min: 0, + max: 36, + }, + }, + // Weekday + { + key: 'weekdayLegendOffset', + help: 'define offset from weekday edge to its label.', + type: 'number', + required: false, + defaultValue: defaults.weekdayLegendOffset, + controlType: 'range', + group: 'Weekday', + controlOptions: { + unit: 'px', + min: 0, + max: 100, + }, + }, + // Days + { + key: 'square', + help: 'force day cell into square shape', + type: 'boolean', + required: false, + defaultValue: defaults.square, + controlType: 'switch', + group: 'Days', + }, + { + key: 'dayRadius', + help: 'define border radius of each day cell.', + type: 'number', + required: false, + defaultValue: defaults.dayRadius, + controlType: 'range', + group: 'Days', + controlOptions: { + unit: 'px', + min: 0, + max: 20, + }, + }, + { + key: 'daySpacing', + help: 'define spacing between each day cell.', + type: 'number', + required: false, + defaultValue: defaults.daySpacing, + controlType: 'range', + group: 'Days', + controlOptions: { + unit: 'px', + min: 0, + max: 20, + }, + }, + { + key: 'dayBorderWidth', + help: 'width of days border.', + type: 'number', + required: false, + defaultValue: defaults.dayBorderWidth, + controlType: 'lineWidth', + group: 'Days', + }, + { + key: 'dayBorderColor', + help: 'color to use for days border.', + type: 'string', + required: false, + defaultValue: defaults.dayBorderColor, + controlType: 'colorPicker', + group: 'Days', + }, + { + key: 'isInteractive', + help: 'Enable/disable interactivity.', + type: 'boolean', + required: false, + defaultValue: defaults.isInteractive, + controlType: 'switch', + group: 'Interactivity', + }, + { + key: 'onClick', + group: 'Interactivity', + help: 'onClick handler, it receives clicked day data and mouse event.', + type: '(day, event) => void', + required: false, + }, + { + key: 'tooltip', + group: 'Interactivity', + type: 'Function', + required: false, + help: 'Custom tooltip component.', + description: ` + A function allowing complete tooltip customisation, it must return a valid HTML + element and will receive the following data: + \`\`\` + { + day: string, + date: {Date}, + value: number, + color: string, + coordinates: { + x: number, + y: number, + }, + height: number + width: number + } + \`\`\` + You can also customize the tooltip style + using the \`theme.tooltip\` object. + `, + }, + { + key: 'custom tooltip example', + help: 'Showcase custom tooltip.', + type: 'boolean', + controlType: 'switch', + group: 'Interactivity', + }, + { + key: 'legends', + flavors: ['svg'], + type: 'LegendProps[]', + help: `Optional chart's legends.`, + group: 'Legends', + controlType: 'array', + controlOptions: { + props: getLegendsProps(['svg']), + shouldCreate: true, + addLabel: 'add legend', + shouldRemove: true, + getItemTitle: (index, legend) => + `legend[${index}]: ${legend.anchor}, ${legend.direction}`, + defaults: { + anchor: 'bottom-right', + direction: 'row', + justify: false, + itemCount: 4, + itemWidth: 42, + itemHeight: 36, + itemsSpacing: 14, + itemDirection: 'right-to-left', + translateX: -85, + translateY: -240, + symbolSize: 20, + onClick: data => { + alert(JSON.stringify(data, null, ' ')) + }, + }, + }, + }, +] + +export const groups = groupProperties(props) diff --git a/website/src/data/nav.js b/website/src/data/nav.js index 8ed22cecc..c6e7147fe 100644 --- a/website/src/data/nav.js +++ b/website/src/data/nav.js @@ -21,6 +21,7 @@ import scatterplot from './components/scatterplot/meta.yml' import stream from './components/stream/meta.yml' import sunburst from './components/sunburst/meta.yml' import swarmplot from './components/swarmplot/meta.yml' +import timeRange from './components/time-range/meta.yml' import treemap from './components/treemap/meta.yml' import voronoi from './components/voronoi/meta.yml' import waffle from './components/waffle/meta.yml' @@ -158,6 +159,12 @@ export const components = [ icon: 'swarmplot', tags: swarmplot.SwarmPlot.tags, }, + { + label: 'TimeRange', + path: '/time-range/', + icon: 'calendar', + tags: timeRange.TimeRange.tags, + }, { label: 'TreeMap', path: '/treemap/', diff --git a/website/src/pages/time-range/index.js b/website/src/pages/time-range/index.js new file mode 100644 index 000000000..3c9d8431e --- /dev/null +++ b/website/src/pages/time-range/index.js @@ -0,0 +1,115 @@ +/* + * This file is part of the nivo project. + * + * Copyright 2016-present, Raphaël Benitte. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +import React from 'react' +import { ResponsiveTimeRange, timeRangeDefaultProps } from '@nivo/calendar' +import { generateOrderedDayCounts } from '@nivo/generators' +import ComponentTemplate from '../../components/components/ComponentTemplate' +import meta from '../../data/components/time-range/meta.yml' +import mapper from '../../data/components/time-range/mapper' +import { groups } from '../../data/components/time-range/props' + +const Tooltip = data => { + /* return custom tooltip */ +} + +const from = new Date(2018, 3, 1) +const to = new Date(2018, 7, 12) +const generateData = () => generateOrderedDayCounts(from, to) + +const initialProperties = { + from: '2018-04-01', + to: '2018-08-12', + + align: 'center', + emptyColor: '#eeeeee', + colors: ['#61cdbb', '#97e3d5', '#e8c1a0', '#f47560'], + minValue: 0, + maxValue: 'auto', + + margin: { + top: 40, + right: 40, + bottom: 100, + left: 40, + }, + direction: 'horizontal', + + monthLegendPosition: 'before', + monthLegendOffset: 10, + + weekdayLegendOffset: 75, + + square: true, + dayRadius: 0, + daySpacing: 0, + dayBorderWidth: 2, + dayBorderColor: '#ffffff', + + isInteractive: true, + 'custom tooltip example': false, + tooltip: null, + + legends: [ + { + anchor: 'bottom-right', + direction: 'row', + justify: false, + itemCount: 4, + itemWidth: 42, + itemHeight: 36, + itemsSpacing: 14, + itemDirection: 'right-to-left', + translateX: -85, + translateY: -240, + symbolSize: 20, + }, + ], +} + +const TimeRange = () => { + console.log('m', mapper) + return ( + ({ + ...properties, + tooltip: properties.tooltip ? Tooltip : undefined, + })} + generateData={generateData} + > + {(properties, data, theme, logAction) => { + return ( + { + logAction({ + type: 'click', + label: `[day] ${day.day}: ${day.value}`, + color: day.color, + data: day, + }) + }} + /> + ) + }} + + ) +} + +export default TimeRange