diff --git a/packages/calendar/package.json b/packages/calendar/package.json
index 186f9b6fe..885383f7f 100644
--- a/packages/calendar/package.json
+++ b/packages/calendar/package.json
@@ -29,7 +29,6 @@
"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": {
diff --git a/packages/calendar/src/Calendar.js b/packages/calendar/src/Calendar.js
index f0e5cf0a9..de7068502 100644
--- a/packages/calendar/src/Calendar.js
+++ b/packages/calendar/src/Calendar.js
@@ -111,6 +111,7 @@ const Calendar = ({
)
}
+Calendar.displayName = 'Calendar'
Calendar.propTypes = CalendarPropTypes
export default setDisplayName('Calendar')(enhance(Calendar))
diff --git a/packages/calendar/src/CalendarCanvas.js b/packages/calendar/src/CalendarCanvas.js
new file mode 100644
index 000000000..5316d5811
--- /dev/null
+++ b/packages/calendar/src/CalendarCanvas.js
@@ -0,0 +1,235 @@
+/*
+ * 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 setDisplayName from 'recompose/setDisplayName'
+import {
+ isCursorInRect,
+ getRelativeCursor,
+ Container,
+ degreesToRadians,
+ BasicTooltip,
+} from '@nivo/core'
+import { renderLegendToCanvas } from '@nivo/legends'
+import enhance from './enhance'
+import { CalendarCanvasPropTypes } from './props'
+
+const findDayUnderCursor = (days, size, spacing, margin, x, y) => {
+ return days.find(day => {
+ return (
+ day.value !== undefined &&
+ isCursorInRect(
+ day.x + margin.left - spacing / 2,
+ day.y + margin.top - spacing / 2,
+ size + spacing,
+ size + spacing,
+ x,
+ y
+ )
+ )
+ })
+}
+
+class CalendarCanvas extends Component {
+ static propTypes = CalendarCanvasPropTypes
+
+ componentDidMount() {
+ this.ctx = this.surface.getContext('2d')
+ this.draw(this.props)
+ }
+
+ shouldComponentUpdate(props) {
+ if (
+ this.props.outerWidth !== props.outerWidth ||
+ this.props.outerHeight !== props.outerHeight ||
+ this.props.isInteractive !== props.isInteractive ||
+ this.props.theme !== props.theme
+ ) {
+ return true
+ } else {
+ this.draw(props)
+ return false
+ }
+ }
+
+ componentDidUpdate() {
+ this.ctx = this.surface.getContext('2d')
+ this.draw(this.props)
+ }
+
+ draw(props) {
+ const {
+ pixelRatio,
+
+ margin,
+ width,
+ height,
+ outerWidth,
+ outerHeight,
+
+ colorScale,
+
+ yearLegends,
+ yearLegend,
+
+ monthLegends,
+ monthLegend,
+
+ days,
+ dayBorderWidth,
+ dayBorderColor,
+
+ legends,
+
+ theme,
+ } = props
+
+ this.surface.width = outerWidth * pixelRatio
+ this.surface.height = outerHeight * pixelRatio
+
+ this.ctx.scale(pixelRatio, pixelRatio)
+ this.ctx.fillStyle = theme.background
+ this.ctx.fillRect(0, 0, outerWidth, outerHeight)
+ this.ctx.translate(margin.left, margin.top)
+
+ days.forEach(day => {
+ this.ctx.fillStyle = day.color
+ if (dayBorderWidth > 0) {
+ this.ctx.strokeStyle = dayBorderColor
+ this.ctx.lineWidth = dayBorderWidth
+ }
+
+ this.ctx.beginPath()
+ this.ctx.rect(day.x, day.y, day.size, day.size)
+ this.ctx.fill()
+
+ if (dayBorderWidth > 0) {
+ this.ctx.stroke()
+ }
+ })
+
+ this.ctx.textAlign = 'center'
+ this.ctx.textBaseline = 'middle'
+ this.ctx.fillStyle = theme.labels.text.fill
+ this.ctx.font = `${theme.labels.text.fontSize}px ${theme.labels.text.fontFamily}`
+
+ monthLegends.forEach(month => {
+ this.ctx.save()
+ this.ctx.translate(month.x, month.y)
+ this.ctx.rotate(degreesToRadians(month.rotation))
+ this.ctx.fillText(monthLegend(month.year, month.month, month.date), 0, 0)
+ this.ctx.restore()
+ })
+
+ yearLegends.forEach(year => {
+ this.ctx.save()
+ this.ctx.translate(year.x, year.y)
+ this.ctx.rotate(degreesToRadians(year.rotation))
+ this.ctx.fillText(yearLegend(year.year), 0, 0)
+ this.ctx.restore()
+ })
+
+ legends.forEach(legend => {
+ const legendData = colorScale.ticks(legend.itemCount).map(value => ({
+ id: value,
+ label: value,
+ color: colorScale(value),
+ }))
+
+ renderLegendToCanvas(this.ctx, {
+ ...legend,
+ data: legendData,
+ containerWidth: width,
+ containerHeight: height,
+ theme,
+ })
+ })
+ }
+
+ handleMouseHover = (showTooltip, hideTooltip) => event => {
+ const {
+ isInteractive,
+ margin,
+ theme,
+ days,
+ daySpacing,
+ tooltipFormat,
+ tooltip,
+ } = this.props
+
+ if (!isInteractive || !days || days.length === 0) return
+
+ const [x, y] = getRelativeCursor(this.surface, event)
+ const currentDay = findDayUnderCursor(days, days[0].size, daySpacing, margin, x, y)
+
+ if (currentDay !== undefined) {
+ showTooltip(
+
This component is heavily inspired by{' '} - this block + this demo .
-
- This component is suitable for isomorphic rendering but require to use the{' '}
- Calendar
component not the ResponsiveCalendar
one.
-
This component is available in the{' '} .
+ */} +
+ The responsive alternative of this component is ResponsiveCalendar
,
+ it also offers a canvas implementations, see{' '}
+ CalendarCanvas.
+
See the dedicated guide on how to setup
legends for this component.
diff --git a/website/src/components/charts/calendar/CalendarCanvas.js b/website/src/components/charts/calendar/CalendarCanvas.js
new file mode 100644
index 000000000..413763e73
--- /dev/null
+++ b/website/src/components/charts/calendar/CalendarCanvas.js
@@ -0,0 +1,166 @@
+/*
+ * 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 { Link } from 'react-router-dom'
+import MediaQuery from 'react-responsive'
+import ChartHeader from '../../ChartHeader'
+import ChartTabs from '../../ChartTabs'
+import generateCode from '../../../lib/generateChartCode'
+import CalendarControls from './CalendarControls'
+import { ResponsiveCalendarCanvas, CalendarCanvasDefaultProps } from '@nivo/calendar'
+import ComponentPropsDocumentation from '../../properties/ComponentPropsDocumentation'
+import nivoTheme from '../../../nivoTheme'
+import properties from './props'
+import propsMapper from './propsMapper'
+
+const Tooltip = data => {
+ /* return custom tooltip */
+}
+
+export default class CalendarCanvas extends Component {
+ state = {
+ settings: {
+ pixelRatio: window && window.devicePixelRatio ? window.devicePixelRatio : 1,
+
+ from: '2013-03-01',
+ to: '2019-07-12',
+
+ emptyColor: '#eeeeee',
+ colors: ['#61cdbb', '#97e3d5', '#e8c1a0', '#f47560'],
+ minValue: 0,
+ maxValue: 'auto',
+
+ margin: {
+ top: 40,
+ right: 40,
+ bottom: 50,
+ left: 40,
+ },
+ direction: 'vertical',
+
+ yearSpacing: 30,
+ yearLegendPosition: 'before',
+ yearLegendOffset: 10,
+
+ monthBorderWidth: 2,
+ monthBorderColor: '#ffffff',
+ monthLegendPosition: 'before',
+ monthLegendOffset: 10,
+
+ daySpacing: 0,
+ dayBorderWidth: 2,
+ dayBorderColor: '#ffffff',
+
+ isInteractive: true,
+ 'custom tooltip example': false,
+ tooltip: null,
+
+ legends: [
+ {
+ anchor: 'bottom',
+ direction: 'row',
+ translateY: 40,
+ itemCount: 4,
+ itemWidth: 34,
+ itemHeight: 36,
+ itemDirection: 'top-to-bottom',
+ },
+ ],
+
+ theme: {
+ ...nivoTheme,
+ labels: {
+ ...nivoTheme.labels,
+ text: {
+ ...nivoTheme.labels.text,
+ fontSize: 10,
+ },
+ },
+ },
+ },
+ }
+
+ handleSettingsUpdate = settings => {
+ this.setState({ settings })
+ }
+
+ handleNodeClick = (node, event) => {
+ alert(`${node.day}: ${node.value}\nclicked at x: ${event.clientX}, y: ${event.clientY}`)
+ }
+
+ render() {
+ const { data } = this.props
+ const { settings } = this.state
+
+ const mappedSettings = propsMapper(settings)
+
+ const code = generateCode(
+ 'ResponsiveCalendarCanvas',
+ {
+ ...mappedSettings,
+ tooltip: mappedSettings.tooltip ? Tooltip : undefined,
+ },
+ {
+ pkg: '@nivo/calendar',
+ defaults: CalendarCanvasDefaultProps,
+ }
+ )
+
+ const header =
+ A variation around the Calendar component. Well + suited for large data sets as it does not impact DOM tree depth, however you'll + lose the isomorphic rendering ability. +
+
+ The responsive alternative of this component is{' '}
+ ResponsiveCalendarCanvas
.
+
<ResponsiveCalendar />
.
+ not required if using responsive alternative of the component{' '}
+ <Responsive*/>
.
),
help: 'Chart width.',
@@ -71,8 +71,8 @@ export default [
docScopes: '*',
description: (
- not required if using
- <ResponsiveCalendar />
.
+ not required if using responsive alternative of the component{' '}
+ <Responsive*/>
.
),
help: 'Chart height.',
@@ -176,6 +176,20 @@ export default [
controlType: 'colorPicker',
controlGroup: 'Base',
},
+ {
+ key: 'pixelRatio',
+ scopes: ['CalendarCanvas'],
+ description: `Adjust pixel ratio, useful for HiDPI screens.`,
+ required: false,
+ default: 'Depends on device',
+ type: `{number}`,
+ controlType: 'range',
+ controlGroup: 'Base',
+ controlOptions: {
+ min: 1,
+ max: 2,
+ },
+ },
// Years
{
key: 'yearSpacing',
@@ -227,6 +241,7 @@ export default [
// Months
{
key: 'monthBorderWidth',
+ scopes: ['Calendar', 'api'],
description: 'width of month borders.',
type: '{number}',
required: false,
@@ -241,6 +256,7 @@ export default [
},
{
key: 'monthBorderColor',
+ scopes: ['Calendar', 'api'],
description: 'color to use for months border.',
type: '{string}',
required: false,
@@ -321,7 +337,7 @@ export default [
...marginProperties,
{
key: 'isInteractive',
- scopes: ['Calendar'],
+ scopes: ['Calendar', 'CalendarCanvas'],
description: 'Enable/disable interactivity.',
type: '{boolean}',
required: false,
@@ -331,14 +347,14 @@ export default [
},
{
key: 'onClick',
- scopes: ['Calendar'],
+ scopes: ['Calendar', 'CalendarCanvas'],
description: 'onClick handler, it receives clicked day data and mouse event.',
type: '{Function}',
required: false,
},
{
key: 'custom tooltip example',
- scopes: ['Calendar'],
+ scopes: ['Calendar', 'CalendarCanvas'],
excludeFromDoc: true,
description: (
@@ -352,7 +368,7 @@ export default [
},
{
key: 'tooltip',
- scopes: ['Calendar'],
+ scopes: ['Calendar', 'CalendarCanvas'],
type: '{Function}',
required: false,
description: (
diff --git a/website/src/components/charts/waffle/WaffleCanvas.js b/website/src/components/charts/waffle/WaffleCanvas.js
index 5fa1d22fa..b28da7240 100644
--- a/website/src/components/charts/waffle/WaffleCanvas.js
+++ b/website/src/components/charts/waffle/WaffleCanvas.js
@@ -198,7 +198,7 @@ export default class WaffleCanvas extends Component {
The responsive alternative of this component is{' '}
- ResponsiveWaffleHtml
, it also offers other implementations, see{' '}
+ ResponsiveWaffleCanvas
, it also offers other implementations, see{' '}
Waffle and WaffleHtml.