diff --git a/packages/nivo-scales/package.json b/packages/nivo-scales/package.json index 4f1c6bf4c..d25bd3a45 100644 --- a/packages/nivo-scales/package.json +++ b/packages/nivo-scales/package.json @@ -32,8 +32,6 @@ }, "scripts": { "lint": "eslint src stories tests", - "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", diff --git a/packages/nivo-scales/src/Scales.js b/packages/nivo-scales/src/Scales.js index 8651eca5a..db6cbbc9e 100644 --- a/packages/nivo-scales/src/Scales.js +++ b/packages/nivo-scales/src/Scales.js @@ -12,16 +12,17 @@ import withPropsOnChange from 'recompose/withPropsOnChange' import setDisplayName from 'recompose/setDisplayName' import pure from 'recompose/pure' import PropTypes from 'prop-types' -import { LinearScalePropType, PointScalePropType } from './propsTypes' -import { scalesFromConfig } from './compute' +import { LinearScalePropType, PointScalePropType, TimeScalePropType } from './propsTypes' +import { scalesFromConfig } from './scaleByType' const Scales = ({ computedScales, children }) => {children(computedScales)} Scales.propTypes = { children: PropTypes.func.isRequired, computedScales: PropTypes.object.isRequired, - scales: PropTypes.arrayOf(PropTypes.oneOfType([LinearScalePropType, PointScalePropType])) - .isRequired, + scales: PropTypes.arrayOf( + PropTypes.oneOfType([LinearScalePropType, PointScalePropType, TimeScalePropType]) + ).isRequired, } const enhance = compose( diff --git a/packages/nivo-scales/src/index.js b/packages/nivo-scales/src/index.js index 3dfe463fe..02c52811f 100644 --- a/packages/nivo-scales/src/index.js +++ b/packages/nivo-scales/src/index.js @@ -7,4 +7,6 @@ * file that was distributed with this source code. */ export { default as Scales } from './Scales' -export * from './compute' +export * from './linearScale' +export * from './pointScale' +export * from './scaleByType' diff --git a/packages/nivo-scales/src/compute.js b/packages/nivo-scales/src/linearScale.js similarity index 54% rename from packages/nivo-scales/src/compute.js rename to packages/nivo-scales/src/linearScale.js index 5fca4017f..a5908b73b 100644 --- a/packages/nivo-scales/src/compute.js +++ b/packages/nivo-scales/src/linearScale.js @@ -8,9 +8,20 @@ */ import getMin from 'lodash/min' import getMax from 'lodash/max' -import uniq from 'lodash/uniq' -import { scaleLinear, scalePoint } from 'd3-scale' +import { scaleLinear } from 'd3-scale' +/** + * Compute a linear scale from config and data set. + * + * @param {Array.} data + * @param {'auto'|number} min + * @param {'auto'|number} max + * @param {string|number} property + * @param {Array.} range + * @param {boolean} stacked + * + * @return {Object} + */ export const computeLinearScale = ({ data, min = 'auto', @@ -47,44 +58,3 @@ export const computeLinearScale = ({ .domain([domainMin, domainMax]) .range(range) } - -export const computePointScale = ({ - data, - domain: _domain, - range, - property, - checkConsistency = false, -}) => { - if (checkConsistency === true) { - const uniqLengths = uniq(data.map(({ data }) => data.length)) - if (uniqLengths.length > 1) { - throw new Error( - [ - `Found inconsistent data for '${property}',`, - `expecting all series to have same length`, - `but found: ${uniqLengths.join(', ')}`, - ].join(' ') - ) - } - } - - const domain = _domain !== undefined ? _domain : data[0].map(d => d[property]) - - return scalePoint() - .range(range) - .domain(domain) -} - -const computeFunctionByType = { - linear: computeLinearScale, - point: computePointScale, -} - -export const scalesFromConfig = scaleConfigs => { - const computedScales = {} - scaleConfigs.forEach(scaleConfig => { - computedScales[scaleConfig.id] = computeFunctionByType[scaleConfig.type](scaleConfig) - }) - - return computedScales -} diff --git a/packages/nivo-scales/src/pointScale.js b/packages/nivo-scales/src/pointScale.js new file mode 100644 index 000000000..3bde3e3de --- /dev/null +++ b/packages/nivo-scales/src/pointScale.js @@ -0,0 +1,37 @@ +/* + * 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 uniq from 'lodash/uniq' +import { scalePoint } from 'd3-scale' + +export const computePointScale = ({ + data, + domain: _domain, + range, + property, + checkConsistency = false, +}) => { + if (checkConsistency === true) { + const uniqLengths = uniq(data.map(({ data }) => data.length)) + if (uniqLengths.length > 1) { + throw new Error( + [ + `Found inconsistent data for '${property}',`, + `expecting all series to have same length`, + `but found: ${uniqLengths.join(', ')}`, + ].join(' ') + ) + } + } + + const domain = _domain !== undefined ? _domain : data[0].map(d => d[property]) + + return scalePoint() + .range(range) + .domain(domain) +} diff --git a/packages/nivo-scales/src/propsTypes.js b/packages/nivo-scales/src/propsTypes.js index ffcbcc5a3..7a8c1fafb 100644 --- a/packages/nivo-scales/src/propsTypes.js +++ b/packages/nivo-scales/src/propsTypes.js @@ -25,3 +25,12 @@ export const PointScalePropType = PropTypes.shape({ domain: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), range: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])).isRequired, }) + +export const TimeScalePropType = PropTypes.shape({ + type: PropTypes.oneOf(['time']), + data: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.object)).isRequired, + property: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + min: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number]), + max: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number]), + range: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])).isRequired, +}) diff --git a/packages/nivo-scales/src/scaleByType.js b/packages/nivo-scales/src/scaleByType.js new file mode 100644 index 000000000..efe680464 --- /dev/null +++ b/packages/nivo-scales/src/scaleByType.js @@ -0,0 +1,26 @@ +/* + * 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 { computeLinearScale } from './linearScale' +import { computePointScale } from './pointScale' +import { computeTimeScale } from './timeScale' + +export const scaleByType = { + linear: computeLinearScale, + point: computePointScale, + time: computeTimeScale, +} + +export const scalesFromConfig = scaleConfigs => { + const computedScales = {} + scaleConfigs.forEach(scaleConfig => { + computedScales[scaleConfig.id] = scaleByType[scaleConfig.type](scaleConfig) + }) + + return computedScales +} diff --git a/packages/nivo-scales/src/timeScale.js b/packages/nivo-scales/src/timeScale.js new file mode 100644 index 000000000..78ed5ee96 --- /dev/null +++ b/packages/nivo-scales/src/timeScale.js @@ -0,0 +1,66 @@ +/* + * 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 uniq from 'lodash/uniq' +import { scaleTime } from 'd3-scale' + +/** + * Add auto date conversion to d3 `timeScale`, + * it helps to be able to pas raw data without having + * to deal with Date objects and pre-process the data. + * + * @param {Object} scale + * + * @return {Object} + */ +const enhanceScale = scale => { + const enhancedScale = function(dateStr) { + return scale(new Date(dateStr)) + } + + // @todo: fix copy() + Object.keys(scale).forEach(key => { + enhancedScale[key] = scale[key].bind(scale) + }) + + return enhancedScale +} + +/** + * Compute a time scale from config and data set. + * + * @param {Array.} data + * @param {'auto'|string} min + * @param {'auto'|string} max + * @param {string|number} property + * @param {Array.} range + * + * @return {Object} + */ +export const computeTimeScale = ({ data, min = 'auto', max = 'auto', property, range }) => { + let domainMin = min !== 'auto' ? new Date(min) : min + let domainMax = max !== 'auto' ? new Date(max) : max + + if (min === 'auto' || max === 'auto') { + const allDates = uniq( + data.reduce((agg, serie) => [...agg, ...serie.map(d => d[property])], []) + ) + .map(dateStr => new Date(dateStr)) + .sort((a, b) => b - a) + .reverse() + + if (min === 'auto') domainMin = allDates[0] + if (max === 'auto') domainMax = allDates[allDates.length - 1] + } + + const scale = scaleTime() + .domain([domainMin, domainMax]) + .range(range) + + return enhanceScale(scale) +} diff --git a/website/src/components/charts/line/LineCanvas/generateData.js b/website/src/components/charts/line/LineCanvas/generateData.js index c5c27c8ce..b7fbc8ae1 100644 --- a/website/src/components/charts/line/LineCanvas/generateData.js +++ b/website/src/components/charts/line/LineCanvas/generateData.js @@ -26,6 +26,9 @@ export default () => { yRand: Math.random() * 2, easing: 'random', xGaps: gaps[key] || [], - }), + }).map(d => ({ + ...d, + y: d.y < 0 ? 0 : d.y, + })), })) }