Skip to content

Commit

Permalink
feat(calendar): add support for day spacing
Browse files Browse the repository at this point in the history
  • Loading branch information
Raphael Benitte committed May 10, 2016
1 parent 2b23da5 commit 2acc80b
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 74 deletions.
105 changes: 31 additions & 74 deletions src/components/charts/calendar/Calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,111 +15,61 @@ import _ from 'lodash';
import Nivo from '../../../Nivo';
import { margin as marginPropType } from '../../../PropTypes';
import decoratorsFromReactChildren from '../../../lib/decoratorsFromReactChildren';
import CalendarLayout from '../../../lib/charts/calendar/CalendarLayout';
import {
DIRECTION_HORIZONTAL,
DIRECTION_VERTICAL
} from '../../../constants/directions'


const monthPath = (t0, cellSize, direction) => {
const t1 = new Date(t0.getFullYear(), t0.getMonth() + 1, 0); // first day of next month
const d0 = t0.getDay(); // first day of month
const w0 = d3.time.weekOfYear(t0); // first week of month
const d1 = t1.getDay(); // last day of month
const w1 = d3.time.weekOfYear(t1); // last week of month

if (direction === DIRECTION_HORIZONTAL) {
return [
`M${(w0 + 1) * cellSize},${d0 * cellSize}`,
`H${w0 * cellSize}V${7 * cellSize}`,
`H${w1 * cellSize}V${(d1 + 1) * cellSize}`,
`H${(w1 + 1) * cellSize}V0`,
`H${(w0 + 1) * cellSize}Z`
].join('');
}

return [
`M${d0 * cellSize},${(w0 + 1) * cellSize}`,
`H0V${(w1 + 1) * cellSize}`,
`H${(d1 + 1) * cellSize}V${w1 * cellSize}`,
`H${7 * cellSize}V${w0 * cellSize}`,
`H${d0 * cellSize}Z`
].join('');
};


const color = d3.scale.category20();

class Calendar extends Component {
constructor(props) {
super(props);

this.previousData = null;
}

renderD3(props, state) {
renderD3(props) {
const {
data,
direction,
dayBorderWidth, dayBorderColor,
daySpacing, dayBorderWidth, dayBorderColor,
monthBorderWidth, monthBorderColor,
transitionDuration, transitionEasing, transitionStaggering
} = props;


let rectPosition;
if (direction === DIRECTION_HORIZONTAL) {
rectPosition = {
x: d => d3.time.weekOfYear(d) * cellSize,
y: d => d.getDay() * cellSize,
};
} else {
rectPosition = {
x: d => d.getDay() * cellSize,
y: d => d3.time.weekOfYear(d) * cellSize,
};
}

const element = d3.select(findDOMNode(this));
const wrapper = element.select('.nivo_calendar_wrapper');

const margin = _.assign({}, Nivo.defaults.margin, props.margin);
const width = props.width - margin.left - margin.right;
const height = props.height - margin.top - margin.bottom;


element.attr({
width: props.width,
height: props.height
});
wrapper.attr('transform', `translate(${margin.left},${margin.top})`);

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]);

let cellSize;
if (direction === DIRECTION_HORIZONTAL) {
cellSize = width / (weekCount + 1);
} else {
cellSize = height / (weekCount + 1);
}
const { days, months } = this.calendarLayout.compute({
width, height,
direction,
daySpacing
});


const rects = wrapper.selectAll('.nivo_calendar_day').data(days);
const rects = wrapper.selectAll('.nivo_calendar_day').data(days, d => d.date);

rects
.enter().append('rect')
.attr('class', 'nivo_calendar_day')
.attr('width', cellSize)
.attr('height', cellSize)
.attr({ x: 0, y: 0 })
.attr('width', d => d.cellSize)
.attr('height', d => d.cellSize)
.attr('x', 0)
.attr('y', 0)
.style({
opacity: 0,
fill: '#fff',//d => color(d.getMonth()),
fill: d => color(d.date.getMonth()),
stroke: dayBorderColor,
'stroke-width': dayBorderWidth,
})
Expand All @@ -129,20 +79,21 @@ class Calendar extends Component {
.transition()
.duration(transitionDuration)
.ease(transitionEasing)
.delay(d => d3.time.dayOfYear(d) * transitionStaggering)
.attr('width', cellSize)
.attr('height', cellSize)
.attr(rectPosition)
.delay(d => d3.time.dayOfYear(d.date) * transitionStaggering)
.attr('width', d => d.cellSize)
.attr('height', d => d.cellSize)
.attr('x', d => d.x)
.attr('y', d => d.y)
.style({
opacity: 1,
fill: '#fff',//d => color(d.getMonth()),
fill: d => color(d.date.getMonth()),
stroke: dayBorderColor,
'stroke-width': dayBorderWidth,
})
;


const paths = wrapper.selectAll('.nivo_calendar_month').data(months);
const paths = wrapper.selectAll('.nivo_calendar_month').data(months, d => d.date);

paths.enter().append('path')
.attr('class', 'nivo_calendar_month')
Expand All @@ -151,7 +102,7 @@ class Calendar extends Component {
stroke: monthBorderColor,
'stroke-width': monthBorderWidth,
})
.attr('d', d => monthPath(d, cellSize, direction))
.attr('d', d => d.path)
;

paths
Expand All @@ -163,13 +114,17 @@ class Calendar extends Component {
stroke: monthBorderColor,
'stroke-width': monthBorderWidth,
})
.attr('d', d => monthPath(d, cellSize, direction))
.attr('d', d => d.path)
;

this.decorators.forEach(decorator => {
});
}

componentWillMount() {
this.calendarLayout = CalendarLayout();
}

shouldComponentUpdate(nextProps, nextState) {
this.decorators = decoratorsFromReactChildren(nextProps.children, 'decorateCalendar');

Expand All @@ -193,13 +148,14 @@ class Calendar extends Component {
}
}

const { array, number, string, func, any, oneOf } = PropTypes;
const { number, string, oneOf } = PropTypes;

Calendar.propTypes = {
width: number.isRequired,
height: number.isRequired,
margin: marginPropType,
direction: oneOf([DIRECTION_HORIZONTAL, DIRECTION_VERTICAL]),
daySpacing: number.isRequired,
dayBorderWidth: number.isRequired,
dayBorderColor: string.isRequired,
monthBorderWidth: number.isRequired,
Expand All @@ -212,6 +168,7 @@ Calendar.propTypes = {
Calendar.defaultProps = {
margin: Nivo.defaults.margin,
direction: DIRECTION_HORIZONTAL,
daySpacing: 0,
dayBorderWidth: 1,
dayBorderColor: '#000',
monthBorderWidth: 2,
Expand Down
111 changes: 111 additions & 0 deletions src/lib/charts/calendar/CalendarLayout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* This file is part of the nivo library.
*
* (c) Raphaël Benitte
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';

import d3 from 'd3';
import _ from 'lodash';
import { DIRECTION_HORIZONTAL } from '../../../constants/directions';


const monthPathGenerator = (date, cellSize, daySpacing, direction) => {
const t1 = new Date(date.getFullYear(), date.getMonth() + 1, 0); // first day of next month
const d0 = date.getDay(); // first day of month
const w0 = d3.time.weekOfYear(date); // first week of month
const d1 = t1.getDay(); // last day of month
const w1 = d3.time.weekOfYear(t1); // last week of month

if (direction === DIRECTION_HORIZONTAL) {
return [
`M${(w0 + 1) * (cellSize + daySpacing)},${d0 * (cellSize + daySpacing)}`,
`H${w0 * (cellSize + daySpacing)}V${7 * (cellSize + daySpacing)}`,
`H${w1 * (cellSize + daySpacing)}V${(d1 + 1) * (cellSize + daySpacing)}`,
`H${(w1 + 1) * (cellSize + daySpacing)}V0`,
`H${(w0 + 1) * (cellSize + daySpacing)}Z`
].join('');
}

return [
`M${d0 * (cellSize + daySpacing)},${(w0 + 1) * (cellSize + daySpacing)}`,
`H0V${(w1 + 1) * (cellSize + daySpacing)}`,
`H${(d1 + 1) * (cellSize + daySpacing)}V${w1 * (cellSize + daySpacing)}`,
`H${7 * (cellSize + daySpacing)}V${w0 * (cellSize + daySpacing)}`,
`H${d0 * (cellSize + daySpacing)}Z`
].join('');
};


/**
* This layout is responsible for computing Calendar chart data/positions….
* It's used for all Calendar related chart components.
*
* @returns {{ compute: (function) }}
* @constructor
*/
const CalendarLayout = () => {
return {
/**
* @param {number} width
* @param {number} height
* @param {string} direction
* @param {number} daySpacing
* @returns {object}
*/
compute({
width, height,
direction,
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]);

let cellSize;
if (direction === DIRECTION_HORIZONTAL) {
cellSize = (width - daySpacing * (weekCount + 2)) / (weekCount + 1);
} else {
cellSize = (height - daySpacing * (weekCount + 2)) / (weekCount + 1);
}

let cellPosition;
if (direction === DIRECTION_HORIZONTAL) {
cellPosition = d => ({
x: d3.time.weekOfYear(d) * (cellSize + daySpacing) + daySpacing / 2,
y: d.getDay() * (cellSize + daySpacing) + daySpacing / 2,
});
} else {
cellPosition = d => ({
x: d.getDay() * (cellSize + daySpacing) + daySpacing / 2,
y: d3.time.weekOfYear(d) * (cellSize + daySpacing) + daySpacing / 2,
});
}

return {
days: days.map(dayDate => {
return _.assign({
date: dayDate,
cellSize,
}, cellPosition(dayDate));
}),
months: months.map(monthDate => {
return {
date: monthDate,
path: monthPathGenerator(monthDate, cellSize, daySpacing, direction),
}
})
};
}
}
};


export default CalendarLayout;

0 comments on commit 2acc80b

Please sign in to comment.