From c27aae45e81cdf9011c6f8ddb39ee40cdd0a2faa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Benitte?= Date: Thu, 7 Dec 2017 05:31:13 +0900 Subject: [PATCH] feat(legends): init legends package --- packages/nivo-bar/package.json | 1 + packages/nivo-bar/src/Bar.js | 115 ++++++++++ packages/nivo-legends/.npmignore | 20 ++ packages/nivo-legends/package.json | 37 +++ packages/nivo-legends/src/constants.js | 24 ++ packages/nivo-legends/src/index.js | 9 + packages/nivo-legends/src/svg/BoxLegendSvg.js | 141 ++++++++++++ packages/nivo-legends/src/svg/LegendSvg.js | 104 +++++++++ .../nivo-legends/src/svg/LegendSvgItem.js | 154 +++++++++++++ packages/nivo-legends/src/svg/index.js | 10 + .../tests/svg/LegendSvgItem.test.js | 51 ++++ .../__snapshots__/LegendSvgItem.test.js.snap | 217 ++++++++++++++++++ 12 files changed, 883 insertions(+) create mode 100644 packages/nivo-legends/.npmignore create mode 100644 packages/nivo-legends/package.json create mode 100644 packages/nivo-legends/src/constants.js create mode 100644 packages/nivo-legends/src/index.js create mode 100644 packages/nivo-legends/src/svg/BoxLegendSvg.js create mode 100644 packages/nivo-legends/src/svg/LegendSvg.js create mode 100644 packages/nivo-legends/src/svg/LegendSvgItem.js create mode 100644 packages/nivo-legends/src/svg/index.js create mode 100644 packages/nivo-legends/tests/svg/LegendSvgItem.test.js create mode 100644 packages/nivo-legends/tests/svg/__snapshots__/LegendSvgItem.test.js.snap diff --git a/packages/nivo-bar/package.json b/packages/nivo-bar/package.json index 3329bcddf7..c15a0d099e 100644 --- a/packages/nivo-bar/package.json +++ b/packages/nivo-bar/package.json @@ -7,6 +7,7 @@ "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 0d60305826..741faec978 100644 --- a/packages/nivo-bar/src/Bar.js +++ b/packages/nivo-bar/src/Bar.js @@ -14,6 +14,7 @@ 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' @@ -254,6 +255,120 @@ const Bar = ({ yScale={result.yScale} theme={theme} /> + + + + + + + + + + + + ) }} diff --git a/packages/nivo-legends/.npmignore b/packages/nivo-legends/.npmignore new file mode 100644 index 0000000000..38b8a4b844 --- /dev/null +++ b/packages/nivo-legends/.npmignore @@ -0,0 +1,20 @@ +# src (will be transpiled) +/src + +# logs +*.log* + +# OSX +.DS_Store + +# config/build +.babelrc + +# storybook +/.storybook +/stories +/storybook-static + +# test +/coverage +/tests diff --git a/packages/nivo-legends/package.json b/packages/nivo-legends/package.json new file mode 100644 index 0000000000..25e9b81097 --- /dev/null +++ b/packages/nivo-legends/package.json @@ -0,0 +1,37 @@ +{ + "name": "@nivo/legends", + "version": "0.32.0", + "license": "MIT", + "main": "./lib/index.js", + "module": "es/index.js", + "jsnext:main": "es/index.js", + "dependencies": { + "recompose": "^0.26.0" + }, + "devDependencies": { + "@nivo/babel-preset": "0.32.0", + "babel-cli": "^6.26.0", + "babel-jest": "^20.0.3", + "cross-env": "^5.0.5", + "jest": "^21.0.1", + "react": "^15.6.1", + "react-dom": "^15.6.1", + "react-test-renderer": "^15.6.1" + }, + "peerDependencies": { + "prop-types": "^15.5.10", + "react": ">= 15.6.1 < 17.0.0" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "test": "jest --verbose ./tests", + "test:cover": "jest --verbose --coverage ./tests", + "build:commonjs": "rm -rf lib && cross-env NODE_ENV=commonjs babel src --out-dir lib", + "build:commonjs:watch": "npm run build:commonjs -- --watch", + "build:es": "rm -rf es && cross-env NODE_ENV=es babel src --out-dir es", + "build:es:watch": "npm run build:es -- --watch", + "build": "npm run build:commonjs && npm run build:es" + } +} diff --git a/packages/nivo-legends/src/constants.js b/packages/nivo-legends/src/constants.js new file mode 100644 index 0000000000..37a87c03be --- /dev/null +++ b/packages/nivo-legends/src/constants.js @@ -0,0 +1,24 @@ +/* + * 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 const DIRECTION_ROW = 'row' +export const DIRECTION_COLUMN = 'column' + +export const ANCHOR_TOP = 'top' +export const ANCHOR_TOP_RIGHT = 'top-right' +export const ANCHOR_RIGHT = 'right' +export const ANCHOR_BOTTOM_RIGHT = 'bottom-right' +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 DIRECTION_LEFT_TO_RIGHT = 'left-to-right' +export const DIRECTION_RIGHT_TO_LEFT = 'right-to-left' +export const DIRECTION_TOP_TO_BOTTOM = 'top-to-bottom' +export const DIRECTION_BOTTOM_TO_TOP = 'bottom-to-top' diff --git a/packages/nivo-legends/src/index.js b/packages/nivo-legends/src/index.js new file mode 100644 index 0000000000..b59a4003e3 --- /dev/null +++ b/packages/nivo-legends/src/index.js @@ -0,0 +1,9 @@ +/* + * 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 * from './svg' diff --git a/packages/nivo-legends/src/svg/BoxLegendSvg.js b/packages/nivo-legends/src/svg/BoxLegendSvg.js new file mode 100644 index 0000000000..ae71a012c0 --- /dev/null +++ b/packages/nivo-legends/src/svg/BoxLegendSvg.js @@ -0,0 +1,141 @@ +/* + * 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' +import LegendSvg from './LegendSvg' +import { + DIRECTION_ROW, + DIRECTION_COLUMN, + DIRECTION_BOTTOM_TO_TOP, + DIRECTION_LEFT_TO_RIGHT, + DIRECTION_RIGHT_TO_LEFT, + DIRECTION_TOP_TO_BOTTOM, + ANCHOR_TOP, + ANCHOR_TOP_RIGHT, + ANCHOR_RIGHT, + ANCHOR_BOTTOM_RIGHT, + ANCHOR_BOTTOM, + ANCHOR_BOTTOM_LEFT, + ANCHOR_LEFT, + ANCHOR_TOP_LEFT, +} from '../constants' + +const BoxLegendSvg = ({ + data, + + containerWidth, + containerHeight, + anchor, + direction, + justify, + + itemWidth, + itemHeight, + itemDirection, + itemsSpacing, + symbolSize, + symbolSpacing, +}) => { + 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 + } + + switch (anchor) { + case ANCHOR_TOP: + x = (containerWidth - width) / 2 + break + + case ANCHOR_TOP_RIGHT: + x = containerWidth - width + break + + case ANCHOR_RIGHT: + x = containerWidth - width + y = (containerHeight - height) / 2 + break + + case ANCHOR_BOTTOM_RIGHT: + x = containerWidth - width + y = containerHeight - height + break + + case ANCHOR_BOTTOM: + x = (containerWidth - width) / 2 + y = containerHeight - height + break + + case ANCHOR_BOTTOM_LEFT: + y = containerHeight - height + break + + case ANCHOR_LEFT: + y = (containerHeight - height) / 2 + break + } + + return ( + + ) +} + +BoxLegendSvg.propTypes = { + containerWidth: PropTypes.number.isRequired, + containerHeight: PropTypes.number.isRequired, + anchor: PropTypes.oneOf([ + ANCHOR_TOP, + ANCHOR_TOP_RIGHT, + ANCHOR_RIGHT, + ANCHOR_BOTTOM_RIGHT, + ANCHOR_BOTTOM, + ANCHOR_BOTTOM_LEFT, + ANCHOR_LEFT, + ANCHOR_TOP_LEFT, + ]).isRequired, + direction: PropTypes.oneOf([DIRECTION_ROW, DIRECTION_COLUMN]).isRequired, + justify: PropTypes.bool, + + 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.isRequired, + symbolSize: PropTypes.number, + symbolSpacing: PropTypes.number, +} + +BoxLegendSvg.defaultProps = { + itemsSpacing: 0, +} + +export default BoxLegendSvg diff --git a/packages/nivo-legends/src/svg/LegendSvg.js b/packages/nivo-legends/src/svg/LegendSvg.js new file mode 100644 index 0000000000..5b725d40ae --- /dev/null +++ b/packages/nivo-legends/src/svg/LegendSvg.js @@ -0,0 +1,104 @@ +/* + * 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' +import LegendSvgItem from './LegendSvgItem' +import { + DIRECTION_COLUMN, + DIRECTION_ROW, + DIRECTION_LEFT_TO_RIGHT, + DIRECTION_RIGHT_TO_LEFT, + DIRECTION_TOP_TO_BOTTOM, + DIRECTION_BOTTOM_TO_TOP, +} from '../constants' + +const LegendSvg = ({ + data, + + // position/layout + x, + y, + width, + height, + direction, + justify, + + // items + itemWidth, + itemHeight, + itemDirection, + itemsSpacing, + symbolSize, + symbolSpacing, +}) => { + let xStep = 0 + let yStep = 0 + if (direction === DIRECTION_ROW) { + xStep = itemWidth + } else if (direction === DIRECTION_COLUMN) { + yStep = itemHeight + } + + return ( + + + {data.map((d, i) => ( + + ))} + + ) +} + +LegendSvg.propTypes = { + data: PropTypes.array.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, + justify: PropTypes.bool.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, + ]).isRequired, + itemsSpacing: PropTypes.number.isRequired, + symbolSize: PropTypes.number, + symbolSpacing: PropTypes.number, +} + +LegendSvg.defaultProps = { + // position/layout + direction: DIRECTION_COLUMN, + justify: false, + + // items + itemDirection: DIRECTION_LEFT_TO_RIGHT, + itemsSpacing: 0, +} + +export default LegendSvg diff --git a/packages/nivo-legends/src/svg/LegendSvgItem.js b/packages/nivo-legends/src/svg/LegendSvgItem.js new file mode 100644 index 0000000000..da46c5812c --- /dev/null +++ b/packages/nivo-legends/src/svg/LegendSvgItem.js @@ -0,0 +1,154 @@ +/* + * 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, { Component } from 'react' +import PropTypes from 'prop-types' +import { + DIRECTION_LEFT_TO_RIGHT, + DIRECTION_RIGHT_TO_LEFT, + DIRECTION_TOP_TO_BOTTOM, + DIRECTION_BOTTOM_TO_TOP, +} from '../constants' + +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, + } + + render() { + const { + x, + y, + width, + height, + symbolSize, + symbolSpacing, + label, + fill, + direction, + justify, + } = this.props + + let symbolX + let symbolY + + let labelX + let labelY + let labelAnchor + let labelAlignment + + switch (direction) { + case DIRECTION_LEFT_TO_RIGHT: + symbolX = 0 + symbolY = (height - symbolSize) / 2 + + labelY = height / 2 + labelAlignment = 'middle' + if (justify === true) { + labelX = width + labelAnchor = 'end' + } else { + labelX = symbolSize + symbolSpacing + labelAnchor = 'start' + } + break + + case DIRECTION_RIGHT_TO_LEFT: + symbolX = width - symbolSize + symbolY = (height - symbolSize) / 2 + + labelY = height / 2 + labelAlignment = 'middle' + if (justify === true) { + labelX = 0 + labelAnchor = 'start' + } else { + labelX = width - symbolSize - symbolSpacing + labelAnchor = 'end' + } + break + + case DIRECTION_TOP_TO_BOTTOM: + symbolX = (width - symbolSize) / 2 + symbolY = 0 + + labelX = width / 2 + + labelAnchor = 'middle' + if (justify === true) { + labelY = height + labelAlignment = 'baseline' + } else { + labelY = symbolSize + symbolSpacing + labelAlignment = 'hanging' + } + break + + case DIRECTION_BOTTOM_TO_TOP: + symbolX = (width - symbolSize) / 2 + symbolY = height - symbolSize + + labelX = width / 2 + labelAnchor = 'middle' + if (justify === true) { + labelY = 0 + labelAlignment = 'hanging' + } else { + labelY = height - symbolSize - symbolSpacing + labelAlignment = 'baseline' + } + break + } + + return ( + + + + {label} + + + ) + } +} + +export default LegendSvgItem diff --git a/packages/nivo-legends/src/svg/index.js b/packages/nivo-legends/src/svg/index.js new file mode 100644 index 0000000000..743c9432b7 --- /dev/null +++ b/packages/nivo-legends/src/svg/index.js @@ -0,0 +1,10 @@ +/* + * 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 BoxLegendSvg } from './BoxLegendSvg' +export { default as LegendSvg } from './LegendSvg' diff --git a/packages/nivo-legends/tests/svg/LegendSvgItem.test.js b/packages/nivo-legends/tests/svg/LegendSvgItem.test.js new file mode 100644 index 0000000000..fa8a2cc3a4 --- /dev/null +++ b/packages/nivo-legends/tests/svg/LegendSvgItem.test.js @@ -0,0 +1,51 @@ +/* + * 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 renderer from 'react-test-renderer' +import LegendSvgItem from '../../src/svg/LegendSvgItem' +import { + DIRECTION_LEFT_TO_RIGHT, + DIRECTION_RIGHT_TO_LEFT, + DIRECTION_TOP_TO_BOTTOM, + DIRECTION_BOTTOM_TO_TOP, +} from '../../src/constants' + +const commonProps = { + x: 0, + y: 0, + width: 200, + height: 36, + label: 'testing', + fill: 'red', +} + +const directions = [ + DIRECTION_LEFT_TO_RIGHT, + DIRECTION_RIGHT_TO_LEFT, + DIRECTION_TOP_TO_BOTTOM, + DIRECTION_BOTTOM_TO_TOP, +] + +directions.forEach(direction => { + it(`should support ${direction} direction`, () => { + const component = renderer.create() + + const tree = component.toJSON() + expect(tree).toMatchSnapshot() + }) + + it(`should support ${direction} direction justified`, () => { + const component = renderer.create( + + ) + + const tree = component.toJSON() + expect(tree).toMatchSnapshot() + }) +}) diff --git a/packages/nivo-legends/tests/svg/__snapshots__/LegendSvgItem.test.js.snap b/packages/nivo-legends/tests/svg/__snapshots__/LegendSvgItem.test.js.snap new file mode 100644 index 0000000000..e8b0648191 --- /dev/null +++ b/packages/nivo-legends/tests/svg/__snapshots__/LegendSvgItem.test.js.snap @@ -0,0 +1,217 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should support bottom-to-top direction 1`] = ` + + + + testing + + +`; + +exports[`should support bottom-to-top direction justified 1`] = ` + + + + testing + + +`; + +exports[`should support left-to-right direction 1`] = ` + + + + testing + + +`; + +exports[`should support left-to-right direction justified 1`] = ` + + + + testing + + +`; + +exports[`should support right-to-left direction 1`] = ` + + + + testing + + +`; + +exports[`should support right-to-left direction justified 1`] = ` + + + + testing + + +`; + +exports[`should support top-to-bottom direction 1`] = ` + + + + testing + + +`; + +exports[`should support top-to-bottom direction justified 1`] = ` + + + + testing + + +`;