@manufac/reactjs-calendar-heatmap
Advanced tools
Comparing version 0.2.14 to 0.2.15
import { createRef, Component } from 'react'; | ||
import moment from 'moment'; | ||
import { select, timeYears, max, scaleBand, scaleLinear, easeLinear, timeMonths, timeDays, scaleTime, timeSecond, timeHours } from 'd3'; | ||
import { scaleLinear, scaleSequential, interpolateSpectral, hsl, select, timeYears, extent, scaleBand, easeLinear, timeMonths, timeDays, scaleTime, timeSecond, timeHours } from 'd3'; | ||
import { jsx } from 'react/jsx-runtime'; | ||
@@ -115,2 +115,89 @@ | ||
function _slicedToArray(arr, i) { | ||
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); | ||
} | ||
function _arrayWithHoles(arr) { | ||
if (Array.isArray(arr)) return arr; | ||
} | ||
function _iterableToArrayLimit(arr, i) { | ||
var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; | ||
if (_i == null) return; | ||
var _arr = []; | ||
var _n = true; | ||
var _d = false; | ||
var _s, _e; | ||
try { | ||
for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { | ||
_arr.push(_s.value); | ||
if (i && _arr.length === i) break; | ||
} | ||
} catch (err) { | ||
_d = true; | ||
_e = err; | ||
} finally { | ||
try { | ||
if (!_n && _i["return"] != null) _i["return"](); | ||
} finally { | ||
if (_d) throw _e; | ||
} | ||
} | ||
return _arr; | ||
} | ||
function _unsupportedIterableToArray(o, minLen) { | ||
if (!o) return; | ||
if (typeof o === "string") return _arrayLikeToArray(o, minLen); | ||
var n = Object.prototype.toString.call(o).slice(8, -1); | ||
if (n === "Object" && o.constructor) n = o.constructor.name; | ||
if (n === "Map" || n === "Set") return Array.from(o); | ||
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); | ||
} | ||
function _arrayLikeToArray(arr, len) { | ||
if (len == null || len > arr.length) len = arr.length; | ||
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; | ||
return arr2; | ||
} | ||
function _nonIterableRest() { | ||
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); | ||
} | ||
function getHSL(val) { | ||
return hsl(360 * val, 0.85, 0.7); | ||
} | ||
function createColorGenerator(min_value, max_value, color) { | ||
var colorGenerator; | ||
switch (color) { | ||
case 'spectral': | ||
colorGenerator = scaleSequential().domain([min_value, max_value]).interpolator(interpolateSpectral); | ||
break; | ||
case 'hsl': | ||
colorGenerator = scaleSequential().domain([min_value, max_value]).interpolator(getHSL); | ||
break; | ||
case null: | ||
case undefined: | ||
colorGenerator = scaleLinear().range(['#ffffff', '#ff4500']).domain([min_value, max_value]); | ||
break; | ||
default: | ||
colorGenerator = scaleLinear().range(['#ffffff', color]).domain([min_value, max_value]); | ||
} | ||
return colorGenerator; | ||
} | ||
function styleInject(css, ref) { | ||
@@ -207,5 +294,3 @@ if (ref === void 0) ref = {}; | ||
this.labels = this.svg.append('g'); | ||
this.buttons = this.svg.append('g'); // Add tooltip to the same element as main svg | ||
this.tooltip = select('#calendar-heatmap').append('div').attr('class', 'heatmapTooltip').style('opacity', 0).style('pointer-events', 'none').style('position', 'absolute').style('z-index', 9999).style('width', '250px').style('max-width', '250px').style('overflow', 'hidden').style('padding', '15px').style('font-size', '12px').style('line-height', '14px').style('color', 'rgb(51, 51, 51)').style('background', 'rgba(255, 255, 255, 0.75)'); | ||
this.buttons = this.svg.append('g'); | ||
this.calcDimensions(); | ||
@@ -228,38 +313,32 @@ } // Calculate dimensions based on available width | ||
if (!!this.props.data && !!this.props.data[0].summary) { | ||
if (Array.isArray(this.props.data) && this.props.data[0].summary !== null && this.props.data[0].summary !== undefined) { | ||
this.drawChart(); | ||
} | ||
} | ||
} // Calculate daily summary if that was not provided | ||
}, { | ||
key: "parseData", | ||
value: function parseData() { | ||
if (!this.props.data) { | ||
return; | ||
} // Get daily summary if that was not provided | ||
if (Array.isArray(this.props.data)) { | ||
if (this.props.data[0].summary === null || this.props.data[0].summary === undefined) { | ||
this.props.data.forEach(function (d) { | ||
// Create project dictionary: Record<string, {name: string; value: number}> | ||
var summaryDictionary = d.details.reduce(function (uniques, project) { | ||
if (uniques[project.name] === undefined) { | ||
uniques[project.name] = { | ||
name: project.name, | ||
value: project.value | ||
}; | ||
} else { | ||
uniques[project.name].value += project.value; | ||
} | ||
return uniques; | ||
}, {}); // Update "summary" property of the array element | ||
if (!this.props.data[0].summary) { | ||
this.props.data.map(function (d) { | ||
var summary = d.details.reduce(function (uniques, project) { | ||
if (!uniques[project.name]) { | ||
uniques[project.name] = { | ||
value: project.value | ||
}; | ||
} else { | ||
uniques[project.name].value += project.value; | ||
} | ||
return uniques; | ||
}, {}); | ||
var unsorted_summary = Object.keys(summary).map(function (key) { | ||
return { | ||
name: key, | ||
value: summary[key].value | ||
}; | ||
d.summary = Object.values(summaryDictionary).sort(function (a, b) { | ||
return b.value - a.value; | ||
}); | ||
}); | ||
d.summary = unsorted_summary.sort(function (a, b) { | ||
return b.value - a.value; | ||
}); | ||
return d; | ||
}); | ||
} | ||
} | ||
@@ -342,8 +421,13 @@ } | ||
}; | ||
}); // Calculate max value of all the years in the dataset | ||
}); // Calculate min and max value of all the years in the dataset | ||
var max_value = max(year_data, function (d) { | ||
var _extent = extent(year_data, function (d) { | ||
return d.total; | ||
}); // Define year labels and axis | ||
}), | ||
_extent2 = _slicedToArray(_extent, 2), | ||
max_value = _extent2[1]; // Generates color generator function | ||
var colorGenerator = createColorGenerator(-0.15 * max_value, max_value, this.props.color); // Define year labels and axis | ||
var year_labels = timeYears(start, end).map(function (d) { | ||
@@ -364,4 +448,3 @@ return moment(d); | ||
}).attr('fill', function (d) { | ||
var color = scaleLinear().range(['#ffffff', _this2.props.color]).domain([-0.15 * max_value, max_value]); | ||
return color(d.total) || '#ff4500'; | ||
return colorGenerator(d.total); | ||
}).on('click', function (_event, datum) { | ||
@@ -492,9 +575,13 @@ var _this2$props$onHideTo, _this2$props; | ||
return start_of_year <= moment(d.date) && moment(d.date) < end_of_year; | ||
}); // Calculate max value of the year data | ||
}); // Calculate min and max value of the year data | ||
var max_value = max(year_data, function (d) { | ||
var _extent3 = extent(year_data, function (d) { | ||
return d.total; | ||
}); | ||
var color = scaleLinear().range(['#ffffff', this.props.color]).domain([-0.15 * max_value, max_value]); | ||
}), | ||
_extent4 = _slicedToArray(_extent3, 2), | ||
max_value = _extent4[1]; // Generates color generator function | ||
var colorGenerator = createColorGenerator(-0.15 * max_value, max_value, this.props.color); | ||
var calcItemX = function calcItemX(d) { | ||
@@ -533,3 +620,4 @@ var date = moment(d.date); | ||
}).attr('fill', function (d) { | ||
return d.total > 0 ? color(d.total) : 'transparent'; | ||
var finalColor = colorGenerator(d.total); | ||
return d.total > 0 ? finalColor : 'transparent'; | ||
}).on('click', function (event, d) { | ||
@@ -741,8 +829,15 @@ var _this3$props$onHideTo, _this3$props; | ||
}); | ||
var max_value = max(month_data, function (d) { | ||
return max(d.summary, function (d) { | ||
return d.value; | ||
}); | ||
}); // Define day labels and axis | ||
var monthSummaries = month_data.flatMap(function (e) { | ||
return e.summary; | ||
}); // Calculate min and max value of month in the dataset | ||
var _extent5 = extent(monthSummaries, function (d) { | ||
return d.value; | ||
}), | ||
_extent6 = _slicedToArray(_extent5, 2), | ||
max_value = _extent6[1]; // Generates color generator function | ||
var colorGenerator = createColorGenerator(-0.15 * max_value, max_value, this.props.color); // Define day labels and axis | ||
var day_labels = timeDays(moment().startOf('week'), moment().endOf('week')); | ||
@@ -817,4 +912,3 @@ var dayScale = scaleBand().rangeRound([this.settings.label_padding, this.settings.height]).domain(day_labels.map(function (d) { | ||
}).attr('fill', function (d) { | ||
var color = scaleLinear().range(['#ffffff', _this4.props.color]).domain([-0.15 * max_value, max_value]); | ||
return color(d.value) || '#ff4500'; | ||
return colorGenerator(d.value); | ||
}).style('opacity', 0).on('mouseover', function (event, d) { | ||
@@ -961,8 +1055,15 @@ var _this4$props$onToolti, _this4$props2; | ||
}); | ||
var max_value = max(week_data, function (d) { | ||
return max(d.summary, function (d) { | ||
return d.value; | ||
}); | ||
}); // Define day labels and axis | ||
var weekSummaries = week_data.flatMap(function (e) { | ||
return e.summary; | ||
}); // Calculate min and max value of week in the dataset | ||
var _extent7 = extent(weekSummaries, function (d) { | ||
return d.value; | ||
}), | ||
_extent8 = _slicedToArray(_extent7, 2), | ||
max_value = _extent8[1]; // Generates color generator function | ||
var colorGenerator = createColorGenerator(-0.15 * max_value, max_value, this.props.color); // Define day labels and axis | ||
var day_labels = timeDays(moment().startOf('week'), moment().endOf('week')); | ||
@@ -1032,4 +1133,3 @@ var dayScale = scaleBand().rangeRound([this.settings.label_padding, this.settings.height]).domain(day_labels.map(function (d) { | ||
}).attr('fill', function (d) { | ||
var color = scaleLinear().range(['#ffffff', _this5.props.color]).domain([-0.15 * max_value, max_value]); | ||
return color(d.value) || '#ff4500'; | ||
return colorGenerator(d.value); | ||
}).style('opacity', 0).on('mouseover', function (_event, d) { | ||
@@ -1146,3 +1246,22 @@ var _this5$props$onToolti, _this5$props2; | ||
}); | ||
var projectScale = scaleBand().rangeRound([this.settings.label_padding, this.settings.height]).domain(project_labels); | ||
var projectScale = scaleBand().rangeRound([this.settings.label_padding, this.settings.height]).domain(project_labels); // Define beginning and end of the day | ||
var start_of_day = moment(this.selected.date).startOf('day'); | ||
var end_of_day = moment(this.selected.date).endOf('day'); // Filter data down to the selected week | ||
var day_data = this.props.data.filter(function (d) { | ||
return start_of_day <= moment(d.date) && moment(d.date) < end_of_day; | ||
}); | ||
var daySummaries = day_data.flatMap(function (e) { | ||
return e.summary; | ||
}); // Calculate min and max value of day in the dataset | ||
var _extent9 = extent(daySummaries, function (d) { | ||
return d.value; | ||
}), | ||
_extent10 = _slicedToArray(_extent9, 2), | ||
max_value = _extent10[1]; // Generates color generator function | ||
var colorGenerator = createColorGenerator(-0.15 * max_value, max_value, this.props.color); | ||
var itemScale = scaleTime().range([this.settings.label_padding * 2, this.settings.width]).domain([moment(this.selected.date).startOf('day'), moment(this.selected.date).endOf('day')]); | ||
@@ -1159,4 +1278,4 @@ this.items.selectAll('.item-block').remove(); | ||
return Math.min(projectScale.bandwidth(), _this6.settings.max_block_height); | ||
}).attr('fill', function () { | ||
return _this6.props.color; | ||
}).attr('fill', function (d) { | ||
return colorGenerator(d.value); | ||
}).style('opacity', 0).on('mouseover', function (_event, d) { | ||
@@ -1163,0 +1282,0 @@ var _this6$props$onToolti, _this6$props; |
@@ -1,70 +0,81 @@ | ||
import type { Component, RefObject } from "react"; | ||
import type { Component, RefObject } from 'react'; | ||
export interface CalendarHeatmapDetail { | ||
date: string; | ||
name: string; | ||
value: number; | ||
} | ||
export interface CalendarHeatmapDatum { | ||
date: string; | ||
total: number; | ||
details: { name: string; date: string; value: number; }[]; | ||
summary?: { name: string; value: number; }[]; | ||
date: string; | ||
total: number; | ||
details: CalendarHeatmapDetail[]; | ||
summary?: { name: string; value: number }[]; | ||
} | ||
export type CalendarHeatmapOverview = 'global' | 'year' | 'month' | 'week' | 'day'; | ||
export type CalendarHeatmapOverview = | ||
| 'global' | ||
| 'year' | ||
| 'month' | ||
| 'week' | ||
| 'day'; | ||
export interface CalendarHeatmapProps { | ||
data: CalendarHeatmapDatum[]; | ||
color?: string; | ||
overview?: CalendarHeatmapOverview; | ||
handler?: (d: CalendarHeatmapDatum) => void; | ||
onTooltip?: (datum: { value: unknown }) => void; | ||
onHideTooltip?: () => void; | ||
data: CalendarHeatmapDatum[]; | ||
color?: string; | ||
overview?: CalendarHeatmapOverview; | ||
handler?: (d: CalendarHeatmapDetail) => void; | ||
onTooltip?: (datum: { value: unknown }) => void; | ||
onHideTooltip?: () => void; | ||
} | ||
export interface CalendarHeatmapSettings { | ||
gutter: number; | ||
item_gutter: number; | ||
width: number; | ||
height: number; | ||
item_size: number; | ||
label_padding: number; | ||
max_block_height: number; | ||
transition_duration: number; | ||
tooltip_width: number; | ||
tooltip_padding: number; | ||
gutter: number; | ||
item_gutter: number; | ||
width: number; | ||
height: number; | ||
item_size: number; | ||
label_padding: number; | ||
max_block_height: number; | ||
transition_duration: number; | ||
tooltip_width: number; | ||
tooltip_padding: number; | ||
} | ||
export class CalendarHeatmap extends Component<CalendarHeatmapProps, unknown> { | ||
settings: CalendarHeatmapSettings; | ||
in_transition: boolean; | ||
overview: CalendarHeatmapOverview; | ||
history: CalendarHeatmapOverview[]; | ||
selected: CalendarHeatmapDatum; | ||
ref: RefObject<HTMLDivElement>; | ||
settings: CalendarHeatmapSettings; | ||
in_transition: boolean; | ||
overview: CalendarHeatmapOverview; | ||
history: CalendarHeatmapOverview[]; | ||
selected: CalendarHeatmapDatum; | ||
ref: RefObject<HTMLDivElement>; | ||
calcDimensions: () => void; | ||
createElements: () => void; | ||
calcDimensions: () => void; | ||
createElements: () => void; | ||
parseData: () => void; | ||
formatTime: (seconds: number) => string; | ||
parseData: () => void; | ||
formatTime: (seconds: number) => string; | ||
hideTooltip: () => void; | ||
drawButton: () => void; | ||
hideBackButton: () => void; | ||
hideTooltip: () => void; | ||
drawButton: () => void; | ||
hideBackButton: () => void; | ||
drawGlobalOverview: () => void; | ||
removeGlobalOverview: () => void; | ||
drawGlobalOverview: () => void; | ||
removeGlobalOverview: () => void; | ||
drawYearOverview: () => void; | ||
removeYearOverview: () => void; | ||
drawYearOverview: () => void; | ||
removeYearOverview: () => void; | ||
drawMonthOverview: () => void; | ||
removeMonthOverview: () => void; | ||
drawMonthOverview: () => void; | ||
removeMonthOverview: () => void; | ||
drawWeekOverview: () => void; | ||
removeWeekOverview: () => void; | ||
drawWeekOverview: () => void; | ||
removeWeekOverview: () => void; | ||
drawDayOverview: () => void; | ||
removeDayOverview: () => void; | ||
drawDayOverview: () => void; | ||
removeDayOverview: () => void; | ||
drawChart: () => void; | ||
drawChart: () => void; | ||
} | ||
export default CalendarHeatmap; | ||
export default CalendarHeatmap; |
{ | ||
"name": "@manufac/reactjs-calendar-heatmap", | ||
"version": "0.2.14", | ||
"version": "0.2.15", | ||
"description": "React component for d3.js calendar heatmap graph.", | ||
@@ -18,7 +18,10 @@ "homepage": "https://manufac-analytics.github.io/reactjs-calendar-heatmap/", | ||
"scripts": { | ||
"clean": "rm -rf dist/*", | ||
"refresh": "rm -rf dist storybook-static", | ||
"build:umd": "webpack", | ||
"build:esm": "rollup -c", | ||
"build": "yarn build:umd && yarn build:esm", | ||
"start": "webpack --watch" | ||
"build:storybook": "build-storybook -o docs && cp .nojekyll docs", | ||
"build": "yarn build:umd && yarn build:esm && yarn build:storybook", | ||
"watch": "webpack --watch", | ||
"start": "start-storybook -p 6006", | ||
"pretty": "prettier --write ." | ||
}, | ||
@@ -41,2 +44,10 @@ "exports": { | ||
"@rollup/plugin-node-resolve": "^13.2.1", | ||
"@storybook/addon-actions": "^6.5.0-beta.1", | ||
"@storybook/addon-console": "^1.2.3", | ||
"@storybook/addon-essentials": "^6.5.0-beta.1", | ||
"@storybook/addon-interactions": "^6.5.0-beta.1", | ||
"@storybook/addon-links": "^6.5.0-beta.1", | ||
"@storybook/builder-webpack5": "^6.5.0-beta.1", | ||
"@storybook/manager-webpack5": "^6.5.0-beta.1", | ||
"@storybook/react": "^6.5.0-beta.1", | ||
"babel-loader": "^8.2.5", | ||
@@ -46,2 +57,3 @@ "copy-webpack-plugin": "^10.2.4", | ||
"postcss": "^8.4.12", | ||
"prettier": "^2.6.2", | ||
"react": "^18.0.0", | ||
@@ -48,0 +60,0 @@ "react-dom": "^18.0.0", |
@@ -52,18 +52,40 @@ # React component for D3.js Calendar Heatmap | ||
<CalendarHeatmap | ||
data="{data}" | ||
color="{color}" | ||
overview="{overview}" | ||
handler="{print}" | ||
data={data} | ||
color={color} | ||
overview={overview} | ||
handler={print} | ||
onTooltip={show} | ||
onHideTooltip={hide} | ||
/> | ||
``` | ||
### Interfaces | ||
```ts | ||
export interface CalendarHeatmapDetail { | ||
date: string; | ||
name: string; | ||
value: number; | ||
} | ||
``` | ||
```ts | ||
interface CalendarHeatmapDatum { | ||
date: string; | ||
total: number; | ||
details: CalendarHeatmapDetail[]; | ||
summary?: { name: string; value: number }[]; | ||
} | ||
``` | ||
### Properties | ||
| Property | Type | Usage | Default | Required | | ||
|:--------------|:---------------------------------------------------------------------------------|:---------------------------------------------------------------------|:-------:|:--------:| | ||
| data | `CalendarHeatmapDatum[]` | Time series data from max a year back | none | yes | | ||
| color | color hex code, valid css color name or color scheme names (`'spectral'` or `'hsl'`) | Theme color for the visual elements | #ff4500 | no | | ||
| overview | `'global' \| 'year' \| 'month' \| 'week' \| 'day'` | Initial overview for the map | year | no | | ||
| handler | `(d: CalendarHeatmapDetail) => void;` | Handler function is fired on click of a time entry in daily overview | none | no | | ||
| onTooltip | `(datum: { value: unknown }) => void;` | onTooltip function is fired on "mouseover" over a visual element | none | no | | ||
| onHideTooltip | `() => void;` | onHideTooltip function is fired on "mouseout" over a visual element | none | no | | ||
| Property | Usage | Default | Required | | ||
| :------- | :------------------------------------------------------------------- | :-----: | :------: | | ||
| data | Time series data from max a year back | none | yes | | ||
| color | Theme hex color | #45ff00 | no | | ||
| overview | Initial overview type (choices are: year, month, day) | year | no | | ||
| handler | Handler function is fired on click of a time entry in daily overview | none | no | | ||
### Example data | ||
@@ -74,3 +96,3 @@ | ||
``` | ||
```js | ||
var data = [{ | ||
@@ -103,3 +125,3 @@ "date": "2016-01-01", | ||
``` | ||
```js | ||
var data = [{ | ||
@@ -106,0 +128,0 @@ "date": "2016-01-01", |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
1848826
2870
148
25