From 1bbb2b07c44c06636a70fc0d6eddc986560a80fc Mon Sep 17 00:00:00 2001 From: Raphael Benitte Date: Wed, 11 May 2016 00:27:24 +0900 Subject: [PATCH] feat(calendar): init support for multiple years --- src/components/charts/calendar/Calendar.js | 2 + src/components/charts/calendar/CalendarD3.js | 37 ++++----- .../charts/calendar/CalendarProps.js | 6 +- src/lib/charts/calendar/CalendarLayout.js | 76 ++++++++++++------- 4 files changed, 72 insertions(+), 49 deletions(-) diff --git a/src/components/charts/calendar/Calendar.js b/src/components/charts/calendar/Calendar.js index abb1c946a..17637c392 100644 --- a/src/components/charts/calendar/Calendar.js +++ b/src/components/charts/calendar/Calendar.js @@ -22,6 +22,7 @@ class Calendar extends Component { render() { const { + from, to, direction, daySpacing, dayBorderWidth, dayBorderColor, monthBorderWidth, monthBorderColor, @@ -34,6 +35,7 @@ class Calendar extends Component { const { days, months } = this.calendarLayout.compute({ width, height, + from, to, direction, daySpacing }); diff --git a/src/components/charts/calendar/CalendarD3.js b/src/components/charts/calendar/CalendarD3.js index 80f28449b..ef7e1da06 100644 --- a/src/components/charts/calendar/CalendarD3.js +++ b/src/components/charts/calendar/CalendarD3.js @@ -13,12 +13,11 @@ import { findDOMNode } from 'react-dom'; import d3 from 'd3'; import _ from 'lodash'; import Nivo from '../../../Nivo'; -import decoratorsFromReactChildren from '../../../lib/decoratorsFromReactChildren'; import CalendarLayout from '../../../lib/charts/calendar/CalendarLayout'; import { calendarPropTypes, calendarDefaultProps } from './CalendarProps'; -const color = d3.scale.category20(); +const color = d3.scale.category20b(); class CalendarD3 extends Component { constructor(props) { @@ -27,7 +26,9 @@ class CalendarD3 extends Component { renderD3(props) { const { + from, to, direction, + yearSpacing, daySpacing, dayBorderWidth, dayBorderColor, monthBorderWidth, monthBorderColor, transitionDuration, transitionEasing, transitionStaggering @@ -49,29 +50,30 @@ class CalendarD3 extends Component { const { days, months } = this.calendarLayout.compute({ width, height, + from, to, direction, + yearSpacing, daySpacing }); + const dayNodes = wrapper.selectAll('.nivo_calendar_day').data(days, d => d.date); - const rects = wrapper.selectAll('.nivo_calendar_day').data(days, d => d.date); - - rects + dayNodes .enter().append('rect') - .attr('class', 'nivo_calendar_day') + .attr('class', d => `nivo_calendar_day nivo_calendar_day-month-${d.date.getMonth()}`) .attr('width', d => d.size) .attr('height', d => d.size) .attr('x', 0) .attr('y', 0) .style({ opacity: 0, - fill: d => color(d.date.getMonth()), + fill: d => color(`${d.date.getFullYear()}.${d.date.getMonth()}`), stroke: dayBorderColor, 'stroke-width': dayBorderWidth, }) ; - rects + dayNodes .transition() .duration(transitionDuration) .ease(transitionEasing) @@ -82,16 +84,16 @@ class CalendarD3 extends Component { .attr('y', d => d.y) .style({ opacity: 1, - fill: d => color(d.date.getMonth()), + fill: d => color(`${d.date.getFullYear()}.${d.date.getMonth()}`), stroke: dayBorderColor, 'stroke-width': dayBorderWidth, }) ; + return; + const monthNodes = wrapper.selectAll('.nivo_calendar_month').data(months, d => d.date); - const paths = wrapper.selectAll('.nivo_calendar_month').data(months, d => d.date); - - paths.enter().append('path') + monthNodes.enter().append('path') .attr('class', 'nivo_calendar_month') .style({ fill: 'none', @@ -101,20 +103,17 @@ class CalendarD3 extends Component { .attr('d', d => d.path) ; - paths + monthNodes .transition() .duration(transitionDuration) .ease(transitionEasing) - .delay((d, i) => i * 30 * transitionStaggering) + .delay(d => (d.date.getMonth() + 1) * 30 * transitionStaggering) .style({ stroke: monthBorderColor, 'stroke-width': monthBorderWidth, }) .attr('d', d => d.path) ; - - this.decorators.forEach(decorator => { - }); } componentWillMount() { @@ -122,16 +121,12 @@ class CalendarD3 extends Component { } shouldComponentUpdate(nextProps, nextState) { - this.decorators = decoratorsFromReactChildren(nextProps.children, 'decorateCalendar'); - this.renderD3(nextProps, nextState); return false; } componentDidMount() { - this.decorators = decoratorsFromReactChildren(this.props.children, 'decorateCalendar'); - this.renderD3(this.props, this.state); } diff --git a/src/components/charts/calendar/CalendarProps.js b/src/components/charts/calendar/CalendarProps.js index f18bf62dc..e32c30b84 100644 --- a/src/components/charts/calendar/CalendarProps.js +++ b/src/components/charts/calendar/CalendarProps.js @@ -17,7 +17,7 @@ import { } from '../../../constants/directions'; -const { object, number, string, any, func, oneOf } = PropTypes; +const { number, string, oneOf, oneOfType, instanceOf } = PropTypes; /** @@ -29,6 +29,9 @@ export const calendarPropTypes = { width: number.isRequired, height: number.isRequired, margin, + from: oneOfType([string, instanceOf(Date)]).isRequired, + to: oneOfType([string, instanceOf(Date)]).isRequired, + yearSpacing: number.isRequired, direction: oneOf([DIRECTION_HORIZONTAL, DIRECTION_VERTICAL]), // days daySpacing: number.isRequired, @@ -54,6 +57,7 @@ export const calendarPropTypes = { export const calendarDefaultProps = { margin: Nivo.defaults.margin, direction: DIRECTION_HORIZONTAL, + yearSpacing: 20, // days daySpacing: 0, dayBorderWidth: 1, diff --git a/src/lib/charts/calendar/CalendarLayout.js b/src/lib/charts/calendar/CalendarLayout.js index f996b8238..db88d4a25 100644 --- a/src/lib/charts/calendar/CalendarLayout.js +++ b/src/lib/charts/calendar/CalendarLayout.js @@ -50,58 +50,80 @@ const monthPathGenerator = (date, cellSize, daySpacing, direction) => { const CalendarLayout = () => { return { /** - * @param {number} width - * @param {number} height - * @param {string} direction - * @param {number} daySpacing + * @param {number} width + * @param {number} height + * @param {string|Date} from + * @param {string|Date} to + * @param {string} direction + * @param {number} yearSpacing + * @param {number} daySpacing * @returns {object} */ compute({ width, height, + from, to, direction, + yearSpacing, daySpacing }) { // time related data - const startDate = new Date(2005, 0, 1); - const endDate = new Date(2006, 0, 1); - const days = d3.time.days(startDate, endDate); - const months = d3.time.months(startDate, endDate); - const weekCount = d3.time.weekOfYear(days[days.length - 1]); + const fromDate = _.isDate(from) ? from : new Date(from); + const toDate = _.isDate(to) ? to : new Date(to); + let years = d3.range(fromDate.getFullYear(), toDate.getFullYear() + 1); + const maxWeeks = d3.max(years, year => d3.time.weeks(new Date(year, 0, 1), new Date(year + 1, 0, 1)).length) + 1; let cellSize; if (direction === DIRECTION_HORIZONTAL) { - cellSize = (width - daySpacing * (weekCount + 2)) / (weekCount + 1); + const hCellSize = (width - daySpacing * (maxWeeks + 1)) / maxWeeks; + const vCellSize = (height - (years.length - 1) * yearSpacing - years.length * (8 * daySpacing)) / (years.length * 7); + cellSize = Math.min(hCellSize, vCellSize); } else { - cellSize = (height - daySpacing * (weekCount + 2)) / (weekCount + 1); + cellSize = (height - daySpacing * (maxWeeks + 1)) / maxWeeks; } let cellPosition; if (direction === DIRECTION_HORIZONTAL) { - cellPosition = d => ({ + cellPosition = (d, yearIndex) => ({ x: d3.time.weekOfYear(d) * (cellSize + daySpacing) + daySpacing / 2, - y: d.getDay() * (cellSize + daySpacing) + daySpacing / 2, + y: d.getDay() * (cellSize + daySpacing) + daySpacing / 2 + yearIndex * (yearSpacing + 7 * cellSize + 8 * daySpacing), }); } else { - cellPosition = d => ({ - x: d.getDay() * (cellSize + daySpacing) + daySpacing / 2, + cellPosition = (d, yearIndex) => ({ + x: d.getDay() * (cellSize + daySpacing) + daySpacing / 2 + yearIndex * (yearSpacing + 7 * cellSize + 8 * daySpacing), y: d3.time.weekOfYear(d) * (cellSize + daySpacing) + daySpacing / 2, }); } + let dayNodes = []; + let monthNodes = []; + + years = years.forEach((year, i) => { + const yearStart = new Date(year, 0, 1); + const yearEnd = new Date(year + 1, 0, 1); + + dayNodes = dayNodes.concat(d3.time.days(yearStart, yearEnd) + .map(dayDate => { + return _.assign({ + date: dayDate, + size: cellSize, + }, cellPosition(dayDate, i)); + }) + ); + + monthNodes = monthNodes.concat(d3.time.months(yearStart, yearEnd) + .map(monthDate => { + return { + date: monthDate, + path: monthPathGenerator(monthDate, cellSize, daySpacing, direction), + }; + }) + ); + }); + return { - days: days.map(dayDate => { - return _.assign({ - date: dayDate, - size: cellSize, - }, cellPosition(dayDate)); - }), - months: months.map(monthDate => { - return { - date: monthDate, - path: monthPathGenerator(monthDate, cellSize, daySpacing, direction), - } - }) + days: dayNodes, + months: monthNodes, }; } }