Skip to content

Commit

Permalink
feat(calendar): add support for min/max value
Browse files Browse the repository at this point in the history
  • Loading branch information
Raphaël Benitte authored and Raphaël Benitte committed Mar 22, 2019
1 parent fed5fc4 commit e0a46f5
Show file tree
Hide file tree
Showing 10 changed files with 87 additions and 58 deletions.
1 change: 1 addition & 0 deletions packages/calendar/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"d3-time": "^1.0.10",
"d3-time-format": "^2.1.3",
"lodash": "^4.17.4",
"react-spring": "^8.0.18",
"recompose": "^0.30.0"
},
"peerDependencies": {
Expand Down
16 changes: 16 additions & 0 deletions packages/calendar/src/computeCalendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,22 @@ import { DIRECTION_HORIZONTAL } from './constants'
import { timeFormat } from 'd3-time-format'
import { timeDays, timeWeek, timeWeeks, timeMonths, timeYear } from 'd3-time'

/**
* Compute min/max values.
*
* @param {Array<>} data
* @param {number|'auto'} minSpec - Define the strategy to use to compute min value, if number, it will be used, if 'auto', will use the lower value from the dataset
* @param {number|'auto'} maxSpec - Define the strategy to use to compute max value, if number, it will be used, if 'auto', will use the higher value from the dataset
* @return {[number, string]}
*/
export const computeDomain = (data, minSpec, maxSpec) => {
const allValues = data.map(d => d.value)
const minValue = minSpec === 'auto' ? Math.min(...allValues) : minSpec
const maxValue = maxSpec === 'auto' ? Math.max(...allValues) : maxSpec

return [minValue, maxValue]
}

/**
* Compute day cell size according to current context.
*
Expand Down
29 changes: 11 additions & 18 deletions packages/calendar/src/enhance.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,27 @@ import compose from 'recompose/compose'
import defaultProps from 'recompose/defaultProps'
import withPropsOnChange from 'recompose/withPropsOnChange'
import pure from 'recompose/pure'
import minBy from 'lodash/minBy'
import maxBy from 'lodash/maxBy'
import { scaleQuantize } from 'd3-scale'
import { withTheme, withDimensions } from '@nivo/core'
import { CalendarDefaultProps } from './props'
import { computeDomain } from './computeCalendar'

export default Component =>
compose(
defaultProps(CalendarDefaultProps),
withTheme(),
withDimensions(),
withPropsOnChange(['data', 'domain', 'colors'], ({ data, domain, colors }) => {
let colorDomain
if (domain === 'auto') {
if (data.length === 0) {
colorDomain = [0, 0]
} else {
colorDomain = [minBy(data, 'value').value, maxBy(data, 'value').value]
}
} else {
colorDomain = [...domain]
}
withPropsOnChange(
['data', 'minValue', 'maxValue', 'colors'],
({ data, minValue, maxValue, colors }) => {
const domain = computeDomain(data, minValue, maxValue)

const colorScale = scaleQuantize()
.domain(colorDomain)
.range(colors)
const colorScale = scaleQuantize()
.domain(domain)
.range(colors)

return { colorScale }
}),
return { colorScale }
}
),
pure
)(Component)
27 changes: 6 additions & 21 deletions packages/calendar/src/props.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,6 @@ import { DIRECTION_HORIZONTAL, DIRECTION_VERTICAL } from './constants'

const monthLabelFormat = timeFormat('%b')

/**
* Calendar components propTypes.
*
* @type {object}
*/
export const CalendarPropTypes = {
from: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]).isRequired,
to: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]).isRequired,
Expand All @@ -29,31 +24,28 @@ export const CalendarPropTypes = {
})
).isRequired,

domain: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.arrayOf(PropTypes.number)])
.isRequired,
minValue: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number]).isRequired,
maxValue: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number]).isRequired,

colors: PropTypes.arrayOf(PropTypes.string).isRequired,
colorScale: PropTypes.func.isRequired,

direction: PropTypes.oneOf([DIRECTION_HORIZONTAL, DIRECTION_VERTICAL]),
emptyColor: PropTypes.string.isRequired,

// years
yearLegend: PropTypes.func.isRequired,
yearSpacing: PropTypes.number.isRequired,
yearLegendOffset: PropTypes.number.isRequired,

// months
monthLegend: PropTypes.func.isRequired,
monthBorderWidth: PropTypes.number.isRequired,
monthBorderColor: PropTypes.string.isRequired,
monthLegendOffset: PropTypes.number.isRequired,

// days
daySpacing: PropTypes.number.isRequired,
dayBorderWidth: PropTypes.number.isRequired,
dayBorderColor: PropTypes.string.isRequired,

// interactivity
isInteractive: PropTypes.bool,
onClick: PropTypes.func.isRequired,
tooltipFormat: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
Expand All @@ -67,35 +59,28 @@ export const CalendarPropTypes = {
).isRequired,
}

/**
* Calendar components defaultProps.
*
* @type {object}
*/
export const CalendarDefaultProps = {
domain: 'auto',
colors: ['#61cdbb', '#97e3d5', '#e8c1a0', '#f47560'],

direction: DIRECTION_HORIZONTAL,
emptyColor: '#fff',

// years
minValue: 0,
maxValue: 'auto',

yearLegend: year => year,
yearSpacing: 30,
yearLegendOffset: 10,

// months
monthLegend: (year, month, date) => monthLabelFormat(date),
monthBorderWidth: 2,
monthBorderColor: '#000',
monthLegendOffset: 6,

// days
daySpacing: 0,
dayBorderWidth: 1,
dayBorderColor: '#000',

// interactivity
isInteractive: true,
onClick: noop,

Expand Down
7 changes: 2 additions & 5 deletions website/src/components/charts/calendar/Calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ export default class Calendar extends Component {
from: '2015-03-01',
to: '2016-07-12',

domain: 'auto',
emptyColor: '#eeeeee',
colors: ['#61cdbb', '#97e3d5', '#e8c1a0', '#f47560'],
minValue: 0,
maxValue: 'auto',

margin: {
top: 100,
Expand All @@ -43,21 +44,17 @@ export default class Calendar extends Component {
},
direction: 'horizontal',

// years
yearSpacing: 40,
yearLegendOffset: 10,

// months
monthBorderWidth: 2,
monthBorderColor: '#ffffff',
monthLegendOffset: 10,

// days
daySpacing: 0,
dayBorderWidth: 2,
dayBorderColor: '#ffffff',

// interactivity
isInteractive: true,
'custom tooltip example': false,
tooltip: null,
Expand Down
44 changes: 37 additions & 7 deletions website/src/components/charts/calendar/props.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,46 @@ export default [
},
},
{
key: 'domain',
key: 'minValue',
scopes: '*',
description: (
<span>
define min/max value (eg. <code className="code-number">[0, 300]</code>) to compute
colors, if set to <code className="code-string">auto</code>, it extract min/max
value from <code>data</code> property.
</span>
<>
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,
default: defaults.minValue,
type: `{number|'auto'}`,
controlType: 'switchableRange',
controlGroup: 'Base',
controlOptions: {
disabledValue: 'auto',
defaultValue: 0,
min: -300,
max: 300,
},
},
{
key: 'maxValue',
scopes: '*',
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.
</>
),
type: '{string|Array.<number>}',
required: false,
default: defaults.maxValue,
type: `{number|'auto'}`,
controlType: 'switchableRange',
controlGroup: 'Base',
controlOptions: {
disabledValue: 'auto',
defaultValue: 100,
min: 0,
max: 600,
},
},
{
key: 'direction',
Expand Down
2 changes: 1 addition & 1 deletion website/src/components/controls/SliderControl.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default class SliderControl extends Component {
value: PropTypes.number.isRequired,
unit: PropTypes.string,
onChange: PropTypes.func.isRequired,
help: PropTypes.string.isRequired,
help: PropTypes.node.isRequired,
min: PropTypes.number.isRequired,
max: PropTypes.number.isRequired,
step: PropTypes.number,
Expand Down
2 changes: 1 addition & 1 deletion website/src/components/controls/SwitchableSliderControl.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default class SwitchableSliderControl extends Component {
disabledValue: PropTypes.any.isRequired,
unit: PropTypes.string,
onChange: PropTypes.func.isRequired,
help: PropTypes.string.isRequired,
help: PropTypes.node.isRequired,
defaultValue: PropTypes.number.isRequired,
min: PropTypes.number.isRequired,
max: PropTypes.number.isRequired,
Expand Down
2 changes: 1 addition & 1 deletion website/src/components/controls/TextControl.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default class TextControl extends Component {
label: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
onChange: PropTypes.func.isRequired,
help: PropTypes.string.isRequired,
help: PropTypes.node.isRequired,
disabled: PropTypes.bool,
}

Expand Down
15 changes: 11 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -897,7 +897,7 @@
dependencies:
regenerator-runtime "^0.12.0"

"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.4":
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4":
version "7.4.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.2.tgz#f5ab6897320f16decd855eed70b705908a313fe8"
integrity sha512-7Bl2rALb7HpvXFL7TETNzKSAeBVCPHELzc0C//9FCxN8nsiueWSJBqaF+2oIJScyILStASR/Cx5WMkXGYTiJFA==
Expand Down Expand Up @@ -10809,7 +10809,7 @@ react-docgen@^3.0.0:
node-dir "^0.1.10"
recast "^0.16.0"

react-dom@16.8.4, react-dom@^16.8.1, react-dom@^16.8.4:
react-dom@^16.8.1, react-dom@^16.8.4:
version "16.8.4"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.4.tgz#1061a8e01a2b3b0c8160037441c3bf00a0e3bc48"
integrity sha512-Ob2wK7XG2tUDt7ps7LtLzGYYB6DXMCLj0G5fO6WeEICtT4/HdpOi7W/xLzZnR6RCG1tYza60nMdqtxzA8FaPJQ==
Expand Down Expand Up @@ -10970,6 +10970,13 @@ react-select@^2.3.0:
react-input-autosize "^2.2.1"
react-transition-group "^2.2.1"

react-spring@^8.0.18:
version "8.0.18"
resolved "https://registry.yarnpkg.com/react-spring/-/react-spring-8.0.18.tgz#f6fa09ab5acdeb37618c0e340d59830171b95c33"
integrity sha512-gb1Rtsnez2gxIHtbrbwQsAjE4ir/62m+gI+eT+1E0Uky5L1PbLccbywRIdIpK3W5jiNmU6OlEazVk0tfI8pZrA==
dependencies:
"@babel/runtime" "^7.3.1"

react-syntax-highlighter@^8.0.1:
version "8.1.0"
resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-8.1.0.tgz#59103ff17a828a27ed7c8f035ae2558f09b6b78c"
Expand Down Expand Up @@ -11009,7 +11016,7 @@ react-transition-group@^2.2.1:
prop-types "^15.6.2"
react-lifecycles-compat "^3.0.4"

react@16.8.4, react@^16.8.1, react@^16.8.4:
react@^16.8.1, react@^16.8.4:
version "16.8.4"
resolved "https://registry.yarnpkg.com/react/-/react-16.8.4.tgz#fdf7bd9ae53f03a9c4cd1a371432c206be1c4768"
integrity sha512-0GQ6gFXfUH7aZcjGVymlPOASTuSjlQL4ZtVC5YKH+3JL6bBLCVO21DknzmaPlI90LN253ojj02nsapy+j7wIjg==
Expand Down Expand Up @@ -13066,7 +13073,7 @@ unzip-response@^2.0.1:
resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97"
integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=

upath@1.1.0, upath@^1.1.0:
upath@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd"
integrity sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==
Expand Down

0 comments on commit e0a46f5

Please sign in to comment.