diff --git a/Makefile b/Makefile index 80f3a18b42..cadcd815fe 100644 --- a/Makefile +++ b/Makefile @@ -118,6 +118,15 @@ package-build-watch-%: ##@packages build package (es flavor) on change, eg. `pac @echo "${YELLOW}Running build watcher for package ${WHITE}${*}${RESET}" @cd packages/nivo-${*} && yarn build:es:watch +package-build-%: ##@packages build package (all flavors), eg. `package-build-bar` + @echo "${YELLOW}Build package ${WHITE}${*}${RESET}" + @cd packages/nivo-${*} && yarn build + +package-dev-%: ##@packages setup package for development, link to website, run watcher + @echo "${YELLOW}Preparing package ${WHITE}${*}${YELLOW} for development${RESET}" + @cd packages/nivo-${*} && yarn link + @cd website && yarn link @nivo/${*} + @make package-build-watch-${*} ######################################################################################################################## # diff --git a/README.md b/README.md index 5bd3924020..638e512f5e 100644 --- a/README.md +++ b/README.md @@ -117,10 +117,9 @@ Join the [nivo discord community](https://discord.gg/n7Ft74f). ## Repositories -* [nivo](https://github.com/plouc/nivo) - the nivo library +* [nivo](https://github.com/plouc/nivo) - nivo packages, website, storybook and examples * [nivo-api](https://github.com/plouc/nivo-api) - the nivo http api * [nivo-api-docker](https://github.com/plouc/nivo-api-docker) - a Docker image for the nivo http api -* [nivo-generators](https://github.com/plouc/nivo-generators) - the data generators used for nivo-website and http API samples ## Credits diff --git a/packages/nivo-bar/package.json b/packages/nivo-bar/package.json index c15a0d099e..3329bcddf7 100644 --- a/packages/nivo-bar/package.json +++ b/packages/nivo-bar/package.json @@ -7,7 +7,6 @@ "jsnext:main": "es/index.js", "dependencies": { "@nivo/core": "0.32.0", - "@nivo/legend": "0.32.0", "d3-scale": "^1.0.6", "d3-shape": "^1.2.0", "react-motion": "^0.5.2", diff --git a/packages/nivo-bar/src/Bar.js b/packages/nivo-bar/src/Bar.js index 741faec978..0d60305826 100644 --- a/packages/nivo-bar/src/Bar.js +++ b/packages/nivo-bar/src/Bar.js @@ -14,7 +14,6 @@ import setDisplayName from 'recompose/setDisplayName' import enhance from './enhance' import { BarPropTypes } from './props' import { Container, SvgWrapper } from '@nivo/core' -import { BoxLegendSvg } from '@nivo/legends' import { Grid, Axes } from '@nivo/core' import { CartesianMarkers } from '@nivo/core' @@ -255,120 +254,6 @@ const Bar = ({ yScale={result.yScale} theme={theme} /> - - - - - - - - - - - - ) }} diff --git a/packages/nivo-calendar/package.json b/packages/nivo-calendar/package.json index aa0f15c3be..7449d84635 100644 --- a/packages/nivo-calendar/package.json +++ b/packages/nivo-calendar/package.json @@ -7,6 +7,7 @@ "jsnext:main": "es/index.js", "dependencies": { "@nivo/core": "0.32.0", + "@nivo/legends": "0.32.0", "d3-scale": "^1.0.6", "d3-time": "^1.0.7", "d3-time-format": "^2.0.5", diff --git a/packages/nivo-calendar/src/Calendar.js b/packages/nivo-calendar/src/Calendar.js index 59538a028d..58e211e5c6 100644 --- a/packages/nivo-calendar/src/Calendar.js +++ b/packages/nivo-calendar/src/Calendar.js @@ -7,9 +7,10 @@ * file that was distributed with this source code. */ import React from 'react' +import { timeFormat } from 'd3-time-format' +import { BoxLegendSvg } from '@nivo/legends' import computeCalendar from './computeCalendar' import { CalendarPropTypes } from './props' -import { timeFormat } from 'd3-time-format' import { DIRECTION_HORIZONTAL } from './constants' import CalendarDay from './CalendarDay' import CalendarMonthPath from './CalendarMonthPath' @@ -49,6 +50,8 @@ const Calendar = ({ isInteractive, tooltipFormat, onClick, + + legends, }) => { const { years, months, days } = computeCalendar({ width, @@ -135,6 +138,22 @@ const Calendar = ({ ) })} + {legends.map((legend, i) => { + const legendData = colorScale.ticks(legend.itemCount).map(value => ({ + label: value, + fill: colorScale(value), + })) + + return ( + + ) + })} )} diff --git a/packages/nivo-calendar/src/props.js b/packages/nivo-calendar/src/props.js index a7b7d5851a..9de6d64d9f 100644 --- a/packages/nivo-calendar/src/props.js +++ b/packages/nivo-calendar/src/props.js @@ -8,6 +8,7 @@ */ import PropTypes from 'prop-types' import { noop } from '@nivo/core' +import { LegendPropShape } from '@nivo/legends' import { DIRECTION_HORIZONTAL, DIRECTION_VERTICAL } from './constants' /** @@ -52,6 +53,13 @@ export const CalendarPropTypes = { isInteractive: PropTypes.bool, onClick: PropTypes.func.isRequired, tooltipFormat: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + + legends: PropTypes.arrayOf( + PropTypes.shape({ + ...LegendPropShape, + itemCount: PropTypes.number, + }) + ).isRequired, } /** @@ -84,4 +92,6 @@ export const CalendarDefaultProps = { // interactivity isInteractive: true, onClick: noop, + + legends: [], } diff --git a/packages/nivo-heatmap/src/HeatMap.js b/packages/nivo-heatmap/src/HeatMap.js index cb269e9e63..08183973ec 100644 --- a/packages/nivo-heatmap/src/HeatMap.js +++ b/packages/nivo-heatmap/src/HeatMap.js @@ -10,15 +10,18 @@ import React, { Component } from 'react' import { partial } from 'lodash' import { TransitionMotion } from 'react-motion' import { colorMotionSpring, getInterpolatedColor } from '@nivo/core' +import { Container, SvgWrapper } from '@nivo/core' +import { Grid, Axes } from '@nivo/core' +import setDisplayName from 'recompose/setDisplayName' import { HeatMapPropTypes } from './props' import computeNodes from './computeNodes' import enhance from './enhance' -import { Container, SvgWrapper } from '@nivo/core' -import { Grid, Axes } from '@nivo/core' import HeatMapCellRect from './HeatMapCellRect' import HeatMapCellCircle from './HeatMapCellCircle' import HeatMapCellTooltip from './HeatMapCellTooltip' +import { scaleLinear } from 'd3-scale' + class HeatMap extends Component { static propTypes = HeatMapPropTypes @@ -39,6 +42,8 @@ class HeatMap extends Component { yScale, offsetX, offsetY, + minValue, + maxValue, margin, width, @@ -65,6 +70,7 @@ class HeatMap extends Component { // theming theme, + colorScale, // motion animate, @@ -93,6 +99,14 @@ class HeatMap extends Component { motionStiffness, } + const legendItems = scaleLinear() + .domain([minValue, maxValue]) + .ticks(4) + .map(i => ({ + label: i, + fill: colorScale(i), + })) + return ( {({ showTooltip, hideTooltip }) => { @@ -210,4 +224,4 @@ class HeatMap extends Component { } } -export default enhance(HeatMap) +export default setDisplayName('HeatMap')(enhance(HeatMap)) diff --git a/packages/nivo-legends/package.json b/packages/nivo-legends/package.json index 25e9b81097..4f42278d11 100644 --- a/packages/nivo-legends/package.json +++ b/packages/nivo-legends/package.json @@ -1,5 +1,6 @@ { "name": "@nivo/legends", + "description": "legend components for nivo dataviz library", "version": "0.32.0", "license": "MIT", "main": "./lib/index.js", diff --git a/packages/nivo-legends/src/compute.js b/packages/nivo-legends/src/compute.js new file mode 100644 index 0000000000..57875134af --- /dev/null +++ b/packages/nivo-legends/src/compute.js @@ -0,0 +1,56 @@ +/* + * 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 { isNumber, isPlainObject } from 'lodash' +import { DIRECTION_COLUMN, DIRECTION_ROW } from './constants' + +const zeroPadding = { + top: 0, + right: 0, + bottom: 0, + left: 0, +} + +export const computeDimensions = ({ + itemCount, + itemWidth, + itemHeight, + direction, + itemsSpacing, + padding: _padding, +}) => { + let padding + if (isNumber(_padding)) { + padding = { + top: _padding, + right: _padding, + bottom: _padding, + left: _padding, + } + } else if (isPlainObject(_padding)) { + padding = { + ...zeroPadding, + ..._padding, + } + } else { + throw new TypeError(`Invalid property padding, must be one of: number, object`) + } + + const horizontalPadding = padding.left + padding.right + const verticalPadding = padding.top + padding.bottom + let width = itemWidth + horizontalPadding + let height = itemHeight + verticalPadding + let spacing = (itemCount - 1) * itemsSpacing + if (direction === DIRECTION_ROW) { + width = itemWidth * itemCount + spacing + horizontalPadding + } else if (direction === DIRECTION_COLUMN) { + height = itemHeight * itemCount + spacing + verticalPadding + } + + return { width, height, padding } +} diff --git a/packages/nivo-legends/src/constants.js b/packages/nivo-legends/src/constants.js index 37a87c03be..1336fec985 100644 --- a/packages/nivo-legends/src/constants.js +++ b/packages/nivo-legends/src/constants.js @@ -17,6 +17,7 @@ export const ANCHOR_BOTTOM = 'bottom' export const ANCHOR_BOTTOM_LEFT = 'bottom-left' export const ANCHOR_LEFT = 'left' export const ANCHOR_TOP_LEFT = 'top-left' +export const ANCHOR_CENTER = 'center' export const DIRECTION_LEFT_TO_RIGHT = 'left-to-right' export const DIRECTION_RIGHT_TO_LEFT = 'right-to-left' diff --git a/packages/nivo-legends/src/index.js b/packages/nivo-legends/src/index.js index b59a4003e3..a8858f2702 100644 --- a/packages/nivo-legends/src/index.js +++ b/packages/nivo-legends/src/index.js @@ -7,3 +7,5 @@ * file that was distributed with this source code. */ export * from './svg' +export * from './constants' +export * from './props' diff --git a/packages/nivo-legends/src/props.js b/packages/nivo-legends/src/props.js new file mode 100644 index 0000000000..bbb00140e8 --- /dev/null +++ b/packages/nivo-legends/src/props.js @@ -0,0 +1,79 @@ +/* + * 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 PropTypes from 'prop-types' +import { + ANCHOR_BOTTOM, + ANCHOR_BOTTOM_LEFT, + ANCHOR_BOTTOM_RIGHT, + ANCHOR_CENTER, + ANCHOR_LEFT, + ANCHOR_RIGHT, + ANCHOR_TOP, + ANCHOR_TOP_LEFT, + ANCHOR_TOP_RIGHT, + DIRECTION_BOTTOM_TO_TOP, + DIRECTION_COLUMN, + DIRECTION_LEFT_TO_RIGHT, + DIRECTION_RIGHT_TO_LEFT, + DIRECTION_ROW, + DIRECTION_TOP_TO_BOTTOM, +} from './constants' + +/** + * The prop type is exported as a simple object instead of `PropTypes.shape` + * to be able to add extra properties. + * + * @example + * ```javascript + * import { LegendPropShape } from '@nivo/legends' + * + * const customLegendPropType = PropTypes.shape({ + * ...LegendPropShape, + * extra: PropTypes.any.isRequired, + * }) + * ``` + */ +export const LegendPropShape = { + data: PropTypes.arrayOf( + PropTypes.shape({ + label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + fill: PropTypes.string.isRequired, + }) + ), + + // position & layout + anchor: PropTypes.oneOf([ + ANCHOR_TOP, + ANCHOR_TOP_RIGHT, + ANCHOR_RIGHT, + ANCHOR_BOTTOM_RIGHT, + ANCHOR_BOTTOM, + ANCHOR_BOTTOM_LEFT, + ANCHOR_LEFT, + ANCHOR_TOP_LEFT, + ANCHOR_CENTER, + ]).isRequired, + translateX: PropTypes.number, + translateY: PropTypes.number, + direction: PropTypes.oneOf([DIRECTION_ROW, DIRECTION_COLUMN]).isRequired, + + // items + itemWidth: PropTypes.number.isRequired, + itemHeight: PropTypes.number.isRequired, + itemDirection: PropTypes.oneOf([ + DIRECTION_LEFT_TO_RIGHT, + DIRECTION_RIGHT_TO_LEFT, + DIRECTION_TOP_TO_BOTTOM, + DIRECTION_BOTTOM_TO_TOP, + ]), + itemsSpacing: PropTypes.number, + symbolSize: PropTypes.number, + symbolSpacing: PropTypes.number, + symbolShape: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), +} diff --git a/packages/nivo-legends/src/svg/BoxLegendSvg.js b/packages/nivo-legends/src/svg/BoxLegendSvg.js index ae71a012c0..e99650e011 100644 --- a/packages/nivo-legends/src/svg/BoxLegendSvg.js +++ b/packages/nivo-legends/src/svg/BoxLegendSvg.js @@ -9,6 +9,7 @@ import React from 'react' import PropTypes from 'prop-types' import LegendSvg from './LegendSvg' +import { computeDimensions } from '../compute' import { DIRECTION_ROW, DIRECTION_COLUMN, @@ -24,6 +25,7 @@ import { ANCHOR_BOTTOM_LEFT, ANCHOR_LEFT, ANCHOR_TOP_LEFT, + ANCHOR_CENTER, } from '../constants' const BoxLegendSvg = ({ @@ -31,8 +33,11 @@ const BoxLegendSvg = ({ containerWidth, containerHeight, + translateX, + translateY, anchor, direction, + padding, justify, itemWidth, @@ -41,48 +46,54 @@ const BoxLegendSvg = ({ itemsSpacing, symbolSize, symbolSpacing, + symbolShape, }) => { - let x = 0 - let y = 0 - - let width = itemWidth - let height = itemHeight - if (direction === DIRECTION_ROW) { - width = itemWidth * data.length - } else if (direction === DIRECTION_COLUMN) { - height = itemHeight * data.length - } - + const { width, height } = computeDimensions({ + itemCount: data.length, + itemWidth, + itemHeight, + itemsSpacing, + direction, + padding, + }) + + let x = translateX + let y = translateY switch (anchor) { case ANCHOR_TOP: - x = (containerWidth - width) / 2 + x += (containerWidth - width) / 2 break case ANCHOR_TOP_RIGHT: - x = containerWidth - width + x += containerWidth - width break case ANCHOR_RIGHT: - x = containerWidth - width - y = (containerHeight - height) / 2 + x += containerWidth - width + y += (containerHeight - height) / 2 break case ANCHOR_BOTTOM_RIGHT: - x = containerWidth - width - y = containerHeight - height + x += containerWidth - width + y += containerHeight - height break case ANCHOR_BOTTOM: - x = (containerWidth - width) / 2 - y = containerHeight - height + x += (containerWidth - width) / 2 + y += containerHeight - height break case ANCHOR_BOTTOM_LEFT: - y = containerHeight - height + y += containerHeight - height break case ANCHOR_LEFT: - y = (containerHeight - height) / 2 + y += (containerHeight - height) / 2 + break + + case ANCHOR_CENTER: + x += (containerWidth - width) / 2 + y += (containerHeight - height) / 2 break } @@ -91,9 +102,8 @@ const BoxLegendSvg = ({ data={data} x={x} y={y} - width={width} - height={height} direction={direction} + padding={padding} justify={justify} itemWidth={itemWidth} itemHeight={itemHeight} @@ -101,6 +111,7 @@ const BoxLegendSvg = ({ itemsSpacing={itemsSpacing} symbolSize={symbolSize} symbolSpacing={symbolSpacing} + symbolShape={symbolShape} /> ) } @@ -108,6 +119,8 @@ const BoxLegendSvg = ({ BoxLegendSvg.propTypes = { containerWidth: PropTypes.number.isRequired, containerHeight: PropTypes.number.isRequired, + translateX: PropTypes.number.isRequired, + translateY: PropTypes.number.isRequired, anchor: PropTypes.oneOf([ ANCHOR_TOP, ANCHOR_TOP_RIGHT, @@ -117,8 +130,18 @@ BoxLegendSvg.propTypes = { ANCHOR_BOTTOM_LEFT, ANCHOR_LEFT, ANCHOR_TOP_LEFT, + ANCHOR_CENTER, ]).isRequired, direction: PropTypes.oneOf([DIRECTION_ROW, DIRECTION_COLUMN]).isRequired, + padding: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.shape({ + top: PropTypes.number, + right: PropTypes.number, + bottom: PropTypes.number, + left: PropTypes.number, + }), + ]).isRequired, justify: PropTypes.bool, itemWidth: PropTypes.number.isRequired, @@ -132,10 +155,14 @@ BoxLegendSvg.propTypes = { itemsSpacing: PropTypes.number.isRequired, symbolSize: PropTypes.number, symbolSpacing: PropTypes.number, + symbolShape: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), } BoxLegendSvg.defaultProps = { - itemsSpacing: 0, + translateX: 0, + translateY: 0, + itemsSpacing: LegendSvg.defaultProps.itemsSpacing, + padding: LegendSvg.defaultProps.padding, } export default BoxLegendSvg diff --git a/packages/nivo-legends/src/svg/LegendSvg.js b/packages/nivo-legends/src/svg/LegendSvg.js index 5b725d40ae..404275d154 100644 --- a/packages/nivo-legends/src/svg/LegendSvg.js +++ b/packages/nivo-legends/src/svg/LegendSvg.js @@ -9,6 +9,7 @@ import React from 'react' import PropTypes from 'prop-types' import LegendSvgItem from './LegendSvgItem' +import { computeDimensions } from '../compute' import { DIRECTION_COLUMN, DIRECTION_ROW, @@ -24,9 +25,8 @@ const LegendSvg = ({ // position/layout x, y, - width, - height, direction, + padding: _padding, justify, // items @@ -36,30 +36,44 @@ const LegendSvg = ({ itemsSpacing, symbolSize, symbolSpacing, + symbolShape, }) => { + const { width, height, padding } = computeDimensions({ + itemCount: data.length, + itemWidth, + itemHeight, + itemsSpacing, + direction, + padding: _padding, + }) + let xStep = 0 let yStep = 0 if (direction === DIRECTION_ROW) { - xStep = itemWidth + xStep = itemWidth + itemsSpacing } else if (direction === DIRECTION_COLUMN) { - yStep = itemHeight + yStep = itemHeight + itemsSpacing } return ( + {/* - {data.map((d, i) => ( + */} + {data.map(({ label, fill }, i) => ( ))} @@ -67,14 +81,26 @@ const LegendSvg = ({ } LegendSvg.propTypes = { - data: PropTypes.array.isRequired, + data: PropTypes.arrayOf( + PropTypes.shape({ + label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + fill: PropTypes.string.isRequired, + }) + ).isRequired, // position/layout x: PropTypes.number.isRequired, y: PropTypes.number.isRequired, - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, direction: PropTypes.oneOf([DIRECTION_COLUMN, DIRECTION_ROW]).isRequired, + padding: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.shape({ + top: PropTypes.number, + right: PropTypes.number, + bottom: PropTypes.number, + left: PropTypes.number, + }), + ]).isRequired, justify: PropTypes.bool.isRequired, // items @@ -89,11 +115,12 @@ LegendSvg.propTypes = { itemsSpacing: PropTypes.number.isRequired, symbolSize: PropTypes.number, symbolSpacing: PropTypes.number, + symbolShape: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), } LegendSvg.defaultProps = { // position/layout - direction: DIRECTION_COLUMN, + padding: 0, justify: false, // items diff --git a/packages/nivo-legends/src/svg/LegendSvgItem.js b/packages/nivo-legends/src/svg/LegendSvgItem.js index da46c5812c..63bad8a7b6 100644 --- a/packages/nivo-legends/src/svg/LegendSvgItem.js +++ b/packages/nivo-legends/src/svg/LegendSvgItem.js @@ -14,36 +14,16 @@ import { DIRECTION_TOP_TO_BOTTOM, DIRECTION_BOTTOM_TO_TOP, } from '../constants' +import { SymbolCircle, SymbolDiamond, SymbolSquare, SymbolTriangle } from './symbols' -class LegendSvgItem extends Component { - static propTypes = { - x: PropTypes.number.isRequired, - y: PropTypes.number.isRequired, - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, - - symbolSize: PropTypes.number.isRequired, - symbolSpacing: PropTypes.number.isRequired, - - label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, - fill: PropTypes.string.isRequired, - - direction: PropTypes.oneOf([ - DIRECTION_LEFT_TO_RIGHT, - DIRECTION_RIGHT_TO_LEFT, - DIRECTION_TOP_TO_BOTTOM, - DIRECTION_BOTTOM_TO_TOP, - ]).isRequired, - justify: PropTypes.bool.isRequired, - } - - static defaultProps = { - direction: DIRECTION_LEFT_TO_RIGHT, - justify: false, - symbolSize: 16, - symbolSpacing: 6, - } +const symbolByShape = { + circle: SymbolCircle, + diamond: SymbolDiamond, + square: SymbolSquare, + triangle: SymbolTriangle, +} +class LegendSvgItem extends Component { render() { const { x, @@ -52,6 +32,7 @@ class LegendSvgItem extends Component { height, symbolSize, symbolSpacing, + symbolShape, label, fill, direction, @@ -129,15 +110,11 @@ class LegendSvgItem extends Component { break } + const Symbol = symbolByShape[symbolShape] + return ( - + ) } + + static propTypes = { + x: PropTypes.number.isRequired, + y: PropTypes.number.isRequired, + width: PropTypes.number.isRequired, + height: PropTypes.number.isRequired, + + symbolSize: PropTypes.number.isRequired, + symbolSpacing: PropTypes.number.isRequired, + symbolShape: PropTypes.oneOfType([ + PropTypes.oneOf(Object.keys(symbolByShape)), + PropTypes.func, + ]).isRequired, + + label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + fill: PropTypes.string.isRequired, + + direction: PropTypes.oneOf([ + DIRECTION_LEFT_TO_RIGHT, + DIRECTION_RIGHT_TO_LEFT, + DIRECTION_TOP_TO_BOTTOM, + DIRECTION_BOTTOM_TO_TOP, + ]).isRequired, + justify: PropTypes.bool.isRequired, + } + + static defaultProps = { + direction: DIRECTION_LEFT_TO_RIGHT, + justify: false, + symbolSize: 16, + symbolSpacing: 8, + symbolShape: 'square', + } } export default LegendSvgItem diff --git a/packages/nivo-legends/src/svg/index.js b/packages/nivo-legends/src/svg/index.js index 743c9432b7..b1f7890949 100644 --- a/packages/nivo-legends/src/svg/index.js +++ b/packages/nivo-legends/src/svg/index.js @@ -8,3 +8,4 @@ */ export { default as BoxLegendSvg } from './BoxLegendSvg' export { default as LegendSvg } from './LegendSvg' +export { default as LegendSvgItem } from './LegendSvgItem' diff --git a/packages/nivo-legends/src/svg/symbols/SymbolCircle.js b/packages/nivo-legends/src/svg/symbols/SymbolCircle.js new file mode 100644 index 0000000000..c639489718 --- /dev/null +++ b/packages/nivo-legends/src/svg/symbols/SymbolCircle.js @@ -0,0 +1,23 @@ +/* + * 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 PropTypes from 'prop-types' + +const SymbolCircle = ({ x, y, size, fill }) => ( + +) + +SymbolCircle.propTypes = { + x: PropTypes.number.isRequired, + y: PropTypes.number.isRequired, + size: PropTypes.number.isRequired, + fill: PropTypes.string.isRequired, +} + +export default SymbolCircle diff --git a/packages/nivo-legends/src/svg/symbols/SymbolDiamond.js b/packages/nivo-legends/src/svg/symbols/SymbolDiamond.js new file mode 100644 index 0000000000..6df4fa5190 --- /dev/null +++ b/packages/nivo-legends/src/svg/symbols/SymbolDiamond.js @@ -0,0 +1,36 @@ +/* + * 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 PropTypes from 'prop-types' + +const SymbolDiamond = ({ x, y, size, fill }) => { + return ( + + + + ) +} + +SymbolDiamond.propTypes = { + x: PropTypes.number.isRequired, + y: PropTypes.number.isRequired, + size: PropTypes.number.isRequired, + fill: PropTypes.string.isRequired, +} + +export default SymbolDiamond diff --git a/packages/nivo-legends/src/svg/symbols/SymbolSquare.js b/packages/nivo-legends/src/svg/symbols/SymbolSquare.js new file mode 100644 index 0000000000..3770cfc389 --- /dev/null +++ b/packages/nivo-legends/src/svg/symbols/SymbolSquare.js @@ -0,0 +1,23 @@ +/* + * 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 PropTypes from 'prop-types' + +const SymbolSquare = ({ x, y, size, fill }) => ( + +) + +SymbolSquare.propTypes = { + x: PropTypes.number.isRequired, + y: PropTypes.number.isRequired, + size: PropTypes.number.isRequired, + fill: PropTypes.string.isRequired, +} + +export default SymbolSquare diff --git a/packages/nivo-legends/src/svg/symbols/SymbolTriangle.js b/packages/nivo-legends/src/svg/symbols/SymbolTriangle.js new file mode 100644 index 0000000000..5afea798f0 --- /dev/null +++ b/packages/nivo-legends/src/svg/symbols/SymbolTriangle.js @@ -0,0 +1,33 @@ +/* + * 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 PropTypes from 'prop-types' + +const SymbolTriangle = ({ x, y, size, fill }) => ( + + + +) + +SymbolTriangle.propTypes = { + x: PropTypes.number.isRequired, + y: PropTypes.number.isRequired, + size: PropTypes.number.isRequired, + fill: PropTypes.string.isRequired, +} + +export default SymbolTriangle diff --git a/packages/nivo-legends/src/svg/symbols/index.js b/packages/nivo-legends/src/svg/symbols/index.js new file mode 100644 index 0000000000..4d252744a2 --- /dev/null +++ b/packages/nivo-legends/src/svg/symbols/index.js @@ -0,0 +1,12 @@ +/* + * 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. + */ +export { default as SymbolCircle } from './SymbolCircle' +export { default as SymbolDiamond } from './SymbolDiamond' +export { default as SymbolSquare } from './SymbolSquare' +export { default as SymbolTriangle } from './SymbolTriangle' diff --git a/packages/nivo-legends/tests/svg/__snapshots__/LegendSvgItem.test.js.snap b/packages/nivo-legends/tests/svg/__snapshots__/LegendSvgItem.test.js.snap index e8b0648191..3735de1102 100644 --- a/packages/nivo-legends/tests/svg/__snapshots__/LegendSvgItem.test.js.snap +++ b/packages/nivo-legends/tests/svg/__snapshots__/LegendSvgItem.test.js.snap @@ -5,7 +5,7 @@ exports[`should support bottom-to-top direction 1`] = ` transform="translate(0,0)" > testing @@ -32,7 +32,7 @@ exports[`should support bottom-to-top direction justified 1`] = ` transform="translate(0,0)" > testing @@ -86,7 +86,7 @@ exports[`should support left-to-right direction justified 1`] = ` transform="translate(0,0)" > testing @@ -140,7 +140,7 @@ exports[`should support right-to-left direction justified 1`] = ` transform="translate(0,0)" > testing @@ -194,7 +194,7 @@ exports[`should support top-to-bottom direction justified 1`] = ` transform="translate(0,0)" > .

+

+ See the dedicated guide on how to setup + legends for this component. +

) diff --git a/website/src/components/charts/calendar/Calendar.js b/website/src/components/charts/calendar/Calendar.js index 58c6562a9a..13088ce6bf 100644 --- a/website/src/components/charts/calendar/Calendar.js +++ b/website/src/components/charts/calendar/Calendar.js @@ -7,6 +7,7 @@ * file that was distributed with this source code. */ import React, { Component } from 'react' +import { Link } from 'react-router-dom' import MediaQuery from 'react-responsive' import ChartHeader from '../../ChartHeader' import ChartTabs from '../../ChartTabs' @@ -30,7 +31,7 @@ export default class Calendar extends Component { margin: { top: 100, right: 30, - bottom: 30, + bottom: 60, left: 30, }, direction: 'horizontal', @@ -51,6 +52,18 @@ export default class Calendar extends Component { // interactivity isInteractive: true, + + legends: [ + { + anchor: 'bottom-right', + direction: 'row', + translateY: 36, + itemCount: 4, + itemWidth: 34, + itemHeight: 36, + itemDirection: 'top-to-bottom', + }, + ], }, } @@ -115,6 +128,10 @@ export default class Calendar extends Component { This component is suitable for isomorphic rendering but require to use the{' '} Calendar component not the ResponsiveCalendar one.

+

+ See the dedicated guide on how to setup + legends for this component. +

) diff --git a/website/src/components/charts/heatmap/HeatMap.js b/website/src/components/charts/heatmap/HeatMap.js index c43a822b58..87517ce827 100644 --- a/website/src/components/charts/heatmap/HeatMap.js +++ b/website/src/components/charts/heatmap/HeatMap.js @@ -32,7 +32,7 @@ export default class HeatMap extends Component { margin: { top: 100, right: 60, - bottom: 30, + bottom: 60, left: 60, }, @@ -190,6 +190,10 @@ export default class HeatMap extends Component { the storybook .

+

+ See the dedicated guide on how to setup + legends for this component. +

) diff --git a/website/src/components/guides/legends/LegendDirection.js b/website/src/components/guides/legends/LegendDirection.js new file mode 100644 index 0000000000..f017bb53d5 --- /dev/null +++ b/website/src/components/guides/legends/LegendDirection.js @@ -0,0 +1,42 @@ +import React from 'react' +import { BoxLegendSvg } from '@nivo/legends' + +const legendProps = { + containerWidth: 800, + containerHeight: 90, + itemWidth: 70, + itemHeight: 24, +} + +export default () => ( +
+

Legend direction

+

+ Legends support two directions, using the direction property,{' '} + column or row. +

+ + + + +
+) diff --git a/website/src/components/guides/legends/LegendItemDirection.js b/website/src/components/guides/legends/LegendItemDirection.js new file mode 100644 index 0000000000..9e7d494a95 --- /dev/null +++ b/website/src/components/guides/legends/LegendItemDirection.js @@ -0,0 +1,83 @@ +import React from 'react' +import { + LegendSvgItem, + DIRECTION_LEFT_TO_RIGHT, + DIRECTION_RIGHT_TO_LEFT, + DIRECTION_TOP_TO_BOTTOM, + DIRECTION_BOTTOM_TO_TOP, +} from '@nivo/legends' + +const itemDirections = [ + DIRECTION_LEFT_TO_RIGHT, + DIRECTION_RIGHT_TO_LEFT, + DIRECTION_TOP_TO_BOTTOM, + DIRECTION_BOTTOM_TO_TOP, +] + +const itemsProps = { + x: 0, + y: 0, + width: 160, + height: 40, + fill: '#dc5a32', +} + +export default () => ( +
+

Legend item direction

+

+ The itemDirection property defines how symbol and label are positioned.
+ You have 4 available directives: +

+
+ {itemDirections.map(dir => ( +
+ + + +
+ ))} +
+

+ The behavior is slightly different if you set justify to true{' '} + as the label will be positioned at the opposite of the symbol, filling up the whole + width/height of the legend's item. +

+
+ {itemDirections.map(dir => ( +
+ + + +
+ ))} +
+
+) diff --git a/website/src/components/guides/legends/LegendPosition.js b/website/src/components/guides/legends/LegendPosition.js new file mode 100644 index 0000000000..bfd553356a --- /dev/null +++ b/website/src/components/guides/legends/LegendPosition.js @@ -0,0 +1,161 @@ +import React from 'react' +import { omit } from 'lodash' +import { + BoxLegendSvg, + ANCHOR_TOP_LEFT, + ANCHOR_TOP, + ANCHOR_TOP_RIGHT, + ANCHOR_RIGHT, + ANCHOR_BOTTOM_RIGHT, + ANCHOR_BOTTOM, + ANCHOR_BOTTOM_LEFT, + ANCHOR_LEFT, + ANCHOR_CENTER, +} from '@nivo/legends' + +const anchors = [ + ANCHOR_TOP_LEFT, + ANCHOR_TOP, + ANCHOR_TOP_RIGHT, + ANCHOR_RIGHT, + ANCHOR_BOTTOM_RIGHT, + ANCHOR_BOTTOM, + ANCHOR_BOTTOM_LEFT, + ANCHOR_LEFT, + ANCHOR_CENTER, +] + +const translateExamples = [ + { + anchor: ANCHOR_TOP, + translateX: -160, + translateY: -30, + }, + { + anchor: ANCHOR_LEFT, + translateX: -30, + translateY: 50, + }, + { + anchor: ANCHOR_RIGHT, + translateX: -30, + translateY: -60, + }, + { + anchor: ANCHOR_BOTTOM, + translateX: 160, + translateY: 30, + }, +] + +const margin = 30 +const legendProps = { + containerWidth: 800 - margin * 2, + containerHeight: 300 - margin * 2, + itemWidth: 120, + itemHeight: 16, + itemsSpacing: 4, + direction: 'column', +} + +export default () => ( +
+

Legend position

+

+ The legend can be positioned in your chart area using the anchor property.
+ You have 9 available directives: +

+ + + margin + + + + {anchors.map(anchor => ( + + ))} + + +

+ The legend's anchor is relative to the inner chart area (with margin applied), but + depending on the chart type, you'll probably want to move it outside of this area.
+ That's where translateX & translateY come into play, allowing + to adjust the legend position from its original anchor. +

+ + + margin + + + + {translateExamples.map((example, i) => ( + + ))} + {translateExamples.map((example, i) => ( + + ))} + + +
+) diff --git a/website/src/components/guides/legends/Legends.js b/website/src/components/guides/legends/Legends.js new file mode 100644 index 0000000000..a64a358955 --- /dev/null +++ b/website/src/components/guides/legends/Legends.js @@ -0,0 +1,33 @@ +import React, { Component } from 'react' +import Helmet from 'react-helmet' +import LegendPosition from './LegendPosition' +import LegendDirection from './LegendDirection' +import LegendItemDirection from './LegendItemDirection' +import SymbolShape from './SymbolShape' + +export default class Legends extends Component { + render() { + return ( +
+ +
+
+

Legends

+
+
+
+

Let's see how to add legends to your charts.

+

+ Legend components are available via the @nivo/legends package, + however it's added as a dependency for most chart packages supporting them, + in most cases you won't have to add it as a direct dependency. +

+ + + + +
+
+ ) + } +} diff --git a/website/src/components/guides/legends/SymbolShape.js b/website/src/components/guides/legends/SymbolShape.js new file mode 100644 index 0000000000..39e496c196 --- /dev/null +++ b/website/src/components/guides/legends/SymbolShape.js @@ -0,0 +1,49 @@ +import React from 'react' +import { LegendSvgItem, DIRECTION_LEFT_TO_RIGHT } from '@nivo/legends' + +const shapes = ['square', 'circle', 'triangle', 'diamond'] + +const itemsProps = { + x: 0, + y: 0, + width: 160, + height: 40, + fill: '#dc5a32', +} + +export default () => ( +
+

Symbol shape

+

+ You can customize symbols using symbolShape property. +

+
+ {shapes.map(shape => ( +
+ + + +
+ ))} +
+
+) diff --git a/website/src/components/pages/About.js b/website/src/components/pages/About.js index 8b0df088f0..ef4c57a83d 100644 --- a/website/src/components/pages/About.js +++ b/website/src/components/pages/About.js @@ -126,7 +126,25 @@ const About = () => ( > nivo {' '} - - nivo packages & website + - nivo{' '} + + packages + ,{' '} + + website + ,{' '} + + storybook + {' '} + and examples
  • ( {' '} - a Docker image for the nivo http api
  • -
  • - - nivo-generators - {' '} - - the data generators used for nivo-website and http API samples -