svelte-heatmap
Advanced tools
Comparing version 0.2.1 to 0.3.0
@@ -274,18 +274,30 @@ 'use strict'; | ||
// for now these are just hard coded. in the future, this | ||
// component should accept props to calculate these. | ||
var colors = ['#c6e48b', '#7bc96f', '#239a3b', '#196127']; | ||
// determine what color a day is | ||
function attachDayColor(_ref) { | ||
var colors = _ref.colors, | ||
emptyColor = _ref.emptyColor, | ||
highColor = _ref.highColor, | ||
lowColor = _ref.lowColor, | ||
normalizedHistory = _ref.normalizedHistory; | ||
var emptyColor = '#ebedf0'; | ||
// determine what color a day is | ||
function attachDayColor(normalizedHistory) { | ||
var max = Math.max.apply(Math, toConsumableArray(normalizedHistory.map(function (day) { | ||
var values = normalizedHistory.map(function (day) { | ||
return day.value; | ||
}))); | ||
var colorValues = colors.map(function (color, i) { | ||
return { color: color, value: i / colors.length }; | ||
}); | ||
var max = Math.max.apply(Math, toConsumableArray(values)); | ||
var min = Math.min.apply(Math, toConsumableArray(values)) || 1; | ||
var colorValues = []; | ||
if (Array.isArray(colors)) { | ||
colorValues = colors.map(function (color, i) { | ||
return { color: color, value: i / colors.length }; | ||
}); | ||
} else if (isValidColorsNumber(colors)) { | ||
try { | ||
colorValues = gradient(lowColor, highColor, colors).map(function (color, i) { | ||
return { color: color, value: i / colors }; | ||
}); | ||
} catch (e) {} | ||
} | ||
return normalizedHistory.map(function (day) { | ||
@@ -297,3 +309,3 @@ var color = emptyColor; | ||
for (var i = 0, end = colorValues.length; i < end; i++) { | ||
if (dayValue < colorValues[i].value) { | ||
if (dayValue <= colorValues[i].value) { | ||
break; | ||
@@ -315,2 +327,71 @@ } | ||
// validate the colors props | ||
function validateColors(_ref2) { | ||
var colors = _ref2.colors, | ||
emptyColor = _ref2.emptyColor, | ||
lowColor = _ref2.lowColor, | ||
highColor = _ref2.highColor; | ||
if (typeof colors === 'number') { | ||
// make sure the colors is a positive whole number | ||
if (!isValidColorsNumber(colors)) { | ||
throw 'Invalid color value. Expected a whole number greater than 2, got ' + colors + '.'; | ||
} | ||
// if colors are a number, lowColor and highColor must be valid | ||
var hexRegex = /#[0-9A-Fa-f]{6}/; | ||
if (typeof lowColor !== 'string' || !hexRegex.test(lowColor)) { | ||
throw 'Invalid lowColor. Expected 6 digit hex color, got ' + lowColor + '.'; | ||
} | ||
if (typeof highColor !== 'string' || !hexRegex.test(highColor)) { | ||
throw 'Invalid highColor. Expected 6 digit hex color, got ' + highColor + '.'; | ||
} | ||
} | ||
} | ||
// make sure a colors number is valid | ||
function isValidColorsNumber(colors) { | ||
return colors > 2 && colors < Infinity && !isNaN(colors) && colors % 1 === 0; | ||
} | ||
// calculate a gradient between two colors | ||
function gradient(lowColor, highColor, colors) { | ||
// parse the low and high | ||
var fromColors = toRgb(lowColor); | ||
var toColors = toRgb(highColor); | ||
// calculate the step variance for each color | ||
var stepR = (toColors.r - fromColors.r) / (colors - 1); | ||
var stepG = (toColors.g - fromColors.g) / (colors - 1); | ||
var stepB = (toColors.b - fromColors.b) / (colors - 1); | ||
// calculate each color in the gradient and return the result | ||
return [].concat(toConsumableArray(new Array(colors))).map(function (step, i) { | ||
return toHex(Math.round(fromColors.r + stepR * i), Math.round(fromColors.g + stepG * i), Math.round(fromColors.b + stepB * i)); | ||
}); | ||
} | ||
// convert hex colors to rgb values | ||
function toRgb(color) { | ||
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(color); | ||
return { | ||
r: parseInt(result[1], 16), | ||
g: parseInt(result[2], 16), | ||
b: parseInt(result[3], 16) | ||
}; | ||
} | ||
// convert rgb values to a hex color | ||
function toHex(r, g, b) { | ||
var red = r < 16 ? '0' + r.toString(16) : r.toString(16); | ||
var green = g < 16 ? '0' + g.toString(16) : g.toString(16); | ||
var blue = b < 16 ? '0' + b.toString(16) : b.toString(16); | ||
return '#' + red + green + blue; | ||
} | ||
// group our normalized history by week | ||
@@ -340,4 +421,4 @@ function groupWeeks(normalizedHist) { | ||
// from oldest to newest, and filling in gaps between days. | ||
function normalize(hist) { | ||
var normalizedHistory = hist.slice(0).sort(function (a, b) { | ||
function normalize(heatmap) { | ||
var normalizedHistory = heatmap.history.slice(0).sort(function (a, b) { | ||
return new Date(a.date) - new Date(b.date); | ||
@@ -347,7 +428,13 @@ }).reduce(fillMissingDates, []).map(attachDayOfWeek); | ||
// finally, attach a color to each piece of history | ||
return attachDayColor(normalizedHistory); | ||
return attachDayColor({ | ||
colors: heatmap.colors, | ||
emptyColor: heatmap.emptyColor, | ||
highColor: heatmap.highColor, | ||
lowColor: heatmap.lowColor, | ||
normalizedHistory: normalizedHistory | ||
}); | ||
} | ||
// validate that the history prop is in the correct format | ||
function validate(hist) { | ||
function validateHistory(hist) { | ||
// make sure history is present | ||
@@ -440,4 +527,10 @@ if (typeof hist === 'undefined') { | ||
/* src\heatmap.html generated by Svelte v1.41.3 */ | ||
function normalizedHistory(history) { | ||
return normalize(history || []); | ||
function normalizedHistory(colors, emptyColor, highColor, history, lowColor) { | ||
return normalize({ | ||
colors: colors, | ||
emptyColor: emptyColor, | ||
highColor: highColor, | ||
history: history || [], | ||
lowColor: lowColor, | ||
}); | ||
} | ||
@@ -447,2 +540,6 @@ | ||
return { | ||
colors: ['#c6e48b', '#7bc96f', '#239a3b', '#196127'], | ||
emptyColor: '#ebedf0', | ||
highColor: null, | ||
lowColor: null, | ||
tooltip: (date, value) => `${value} on ${date}`, | ||
@@ -454,4 +551,12 @@ }; | ||
try { | ||
// validate the colors that were provided | ||
validateColors({ | ||
colors: this.get('colors'), | ||
emptyColor: this.get('emptyColor'), | ||
highColor: this.get('highColor'), | ||
lowColor: this.get('lowColor'), | ||
}); | ||
// validate the history prop | ||
validate(this.get('history')); | ||
validateHistory(this.get('history')); | ||
@@ -470,3 +575,3 @@ // validate the tooltip prop | ||
function encapsulateStyles(node) { | ||
setAttribute(node, "svelte-481602688", ""); | ||
setAttribute(node, "svelte-3568561252", ""); | ||
} | ||
@@ -712,3 +817,3 @@ | ||
this._state = assign(data(), options.data); | ||
this._recompute({ history: 1 }, this._state); | ||
this._recompute({ colors: 1, emptyColor: 1, highColor: 1, history: 1, lowColor: 1 }, this._state); | ||
@@ -736,4 +841,4 @@ var _oncreate = oncreate.bind(this); | ||
Heatmap.prototype._recompute = function _recompute(changed, state) { | ||
if (changed.history) { | ||
if (differs(state.normalizedHistory, (state.normalizedHistory = normalizedHistory(state.history)))) changed.normalizedHistory = true; | ||
if (changed.colors || changed.emptyColor || changed.highColor || changed.history || changed.lowColor) { | ||
if (differs(state.normalizedHistory, (state.normalizedHistory = normalizedHistory(state.colors, state.emptyColor, state.highColor, state.history, state.lowColor)))) changed.normalizedHistory = true; | ||
} | ||
@@ -740,0 +845,0 @@ }; |
@@ -272,18 +272,30 @@ function noop() {} | ||
// for now these are just hard coded. in the future, this | ||
// component should accept props to calculate these. | ||
var colors = ['#c6e48b', '#7bc96f', '#239a3b', '#196127']; | ||
// determine what color a day is | ||
function attachDayColor(_ref) { | ||
var colors = _ref.colors, | ||
emptyColor = _ref.emptyColor, | ||
highColor = _ref.highColor, | ||
lowColor = _ref.lowColor, | ||
normalizedHistory = _ref.normalizedHistory; | ||
var emptyColor = '#ebedf0'; | ||
// determine what color a day is | ||
function attachDayColor(normalizedHistory) { | ||
var max = Math.max.apply(Math, toConsumableArray(normalizedHistory.map(function (day) { | ||
var values = normalizedHistory.map(function (day) { | ||
return day.value; | ||
}))); | ||
var colorValues = colors.map(function (color, i) { | ||
return { color: color, value: i / colors.length }; | ||
}); | ||
var max = Math.max.apply(Math, toConsumableArray(values)); | ||
var min = Math.min.apply(Math, toConsumableArray(values)) || 1; | ||
var colorValues = []; | ||
if (Array.isArray(colors)) { | ||
colorValues = colors.map(function (color, i) { | ||
return { color: color, value: i / colors.length }; | ||
}); | ||
} else if (isValidColorsNumber(colors)) { | ||
try { | ||
colorValues = gradient(lowColor, highColor, colors).map(function (color, i) { | ||
return { color: color, value: i / colors }; | ||
}); | ||
} catch (e) {} | ||
} | ||
return normalizedHistory.map(function (day) { | ||
@@ -295,3 +307,3 @@ var color = emptyColor; | ||
for (var i = 0, end = colorValues.length; i < end; i++) { | ||
if (dayValue < colorValues[i].value) { | ||
if (dayValue <= colorValues[i].value) { | ||
break; | ||
@@ -313,2 +325,71 @@ } | ||
// validate the colors props | ||
function validateColors(_ref2) { | ||
var colors = _ref2.colors, | ||
emptyColor = _ref2.emptyColor, | ||
lowColor = _ref2.lowColor, | ||
highColor = _ref2.highColor; | ||
if (typeof colors === 'number') { | ||
// make sure the colors is a positive whole number | ||
if (!isValidColorsNumber(colors)) { | ||
throw 'Invalid color value. Expected a whole number greater than 2, got ' + colors + '.'; | ||
} | ||
// if colors are a number, lowColor and highColor must be valid | ||
var hexRegex = /#[0-9A-Fa-f]{6}/; | ||
if (typeof lowColor !== 'string' || !hexRegex.test(lowColor)) { | ||
throw 'Invalid lowColor. Expected 6 digit hex color, got ' + lowColor + '.'; | ||
} | ||
if (typeof highColor !== 'string' || !hexRegex.test(highColor)) { | ||
throw 'Invalid highColor. Expected 6 digit hex color, got ' + highColor + '.'; | ||
} | ||
} | ||
} | ||
// make sure a colors number is valid | ||
function isValidColorsNumber(colors) { | ||
return colors > 2 && colors < Infinity && !isNaN(colors) && colors % 1 === 0; | ||
} | ||
// calculate a gradient between two colors | ||
function gradient(lowColor, highColor, colors) { | ||
// parse the low and high | ||
var fromColors = toRgb(lowColor); | ||
var toColors = toRgb(highColor); | ||
// calculate the step variance for each color | ||
var stepR = (toColors.r - fromColors.r) / (colors - 1); | ||
var stepG = (toColors.g - fromColors.g) / (colors - 1); | ||
var stepB = (toColors.b - fromColors.b) / (colors - 1); | ||
// calculate each color in the gradient and return the result | ||
return [].concat(toConsumableArray(new Array(colors))).map(function (step, i) { | ||
return toHex(Math.round(fromColors.r + stepR * i), Math.round(fromColors.g + stepG * i), Math.round(fromColors.b + stepB * i)); | ||
}); | ||
} | ||
// convert hex colors to rgb values | ||
function toRgb(color) { | ||
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(color); | ||
return { | ||
r: parseInt(result[1], 16), | ||
g: parseInt(result[2], 16), | ||
b: parseInt(result[3], 16) | ||
}; | ||
} | ||
// convert rgb values to a hex color | ||
function toHex(r, g, b) { | ||
var red = r < 16 ? '0' + r.toString(16) : r.toString(16); | ||
var green = g < 16 ? '0' + g.toString(16) : g.toString(16); | ||
var blue = b < 16 ? '0' + b.toString(16) : b.toString(16); | ||
return '#' + red + green + blue; | ||
} | ||
// group our normalized history by week | ||
@@ -338,4 +419,4 @@ function groupWeeks(normalizedHist) { | ||
// from oldest to newest, and filling in gaps between days. | ||
function normalize(hist) { | ||
var normalizedHistory = hist.slice(0).sort(function (a, b) { | ||
function normalize(heatmap) { | ||
var normalizedHistory = heatmap.history.slice(0).sort(function (a, b) { | ||
return new Date(a.date) - new Date(b.date); | ||
@@ -345,7 +426,13 @@ }).reduce(fillMissingDates, []).map(attachDayOfWeek); | ||
// finally, attach a color to each piece of history | ||
return attachDayColor(normalizedHistory); | ||
return attachDayColor({ | ||
colors: heatmap.colors, | ||
emptyColor: heatmap.emptyColor, | ||
highColor: heatmap.highColor, | ||
lowColor: heatmap.lowColor, | ||
normalizedHistory: normalizedHistory | ||
}); | ||
} | ||
// validate that the history prop is in the correct format | ||
function validate(hist) { | ||
function validateHistory(hist) { | ||
// make sure history is present | ||
@@ -438,4 +525,10 @@ if (typeof hist === 'undefined') { | ||
/* src\heatmap.html generated by Svelte v1.41.3 */ | ||
function normalizedHistory(history) { | ||
return normalize(history || []); | ||
function normalizedHistory(colors, emptyColor, highColor, history, lowColor) { | ||
return normalize({ | ||
colors: colors, | ||
emptyColor: emptyColor, | ||
highColor: highColor, | ||
history: history || [], | ||
lowColor: lowColor, | ||
}); | ||
} | ||
@@ -445,2 +538,6 @@ | ||
return { | ||
colors: ['#c6e48b', '#7bc96f', '#239a3b', '#196127'], | ||
emptyColor: '#ebedf0', | ||
highColor: null, | ||
lowColor: null, | ||
tooltip: (date, value) => `${value} on ${date}`, | ||
@@ -452,4 +549,12 @@ }; | ||
try { | ||
// validate the colors that were provided | ||
validateColors({ | ||
colors: this.get('colors'), | ||
emptyColor: this.get('emptyColor'), | ||
highColor: this.get('highColor'), | ||
lowColor: this.get('lowColor'), | ||
}); | ||
// validate the history prop | ||
validate(this.get('history')); | ||
validateHistory(this.get('history')); | ||
@@ -468,3 +573,3 @@ // validate the tooltip prop | ||
function encapsulateStyles(node) { | ||
setAttribute(node, "svelte-481602688", ""); | ||
setAttribute(node, "svelte-3568561252", ""); | ||
} | ||
@@ -710,3 +815,3 @@ | ||
this._state = assign(data(), options.data); | ||
this._recompute({ history: 1 }, this._state); | ||
this._recompute({ colors: 1, emptyColor: 1, highColor: 1, history: 1, lowColor: 1 }, this._state); | ||
@@ -734,4 +839,4 @@ var _oncreate = oncreate.bind(this); | ||
Heatmap.prototype._recompute = function _recompute(changed, state) { | ||
if (changed.history) { | ||
if (differs(state.normalizedHistory, (state.normalizedHistory = normalizedHistory(state.history)))) changed.normalizedHistory = true; | ||
if (changed.colors || changed.emptyColor || changed.highColor || changed.history || changed.lowColor) { | ||
if (differs(state.normalizedHistory, (state.normalizedHistory = normalizedHistory(state.colors, state.emptyColor, state.highColor, state.history, state.lowColor)))) changed.normalizedHistory = true; | ||
} | ||
@@ -738,0 +843,0 @@ }; |
@@ -278,18 +278,30 @@ (function (global, factory) { | ||
// for now these are just hard coded. in the future, this | ||
// component should accept props to calculate these. | ||
var colors = ['#c6e48b', '#7bc96f', '#239a3b', '#196127']; | ||
// determine what color a day is | ||
function attachDayColor(_ref) { | ||
var colors = _ref.colors, | ||
emptyColor = _ref.emptyColor, | ||
highColor = _ref.highColor, | ||
lowColor = _ref.lowColor, | ||
normalizedHistory = _ref.normalizedHistory; | ||
var emptyColor = '#ebedf0'; | ||
// determine what color a day is | ||
function attachDayColor(normalizedHistory) { | ||
var max = Math.max.apply(Math, toConsumableArray(normalizedHistory.map(function (day) { | ||
var values = normalizedHistory.map(function (day) { | ||
return day.value; | ||
}))); | ||
var colorValues = colors.map(function (color, i) { | ||
return { color: color, value: i / colors.length }; | ||
}); | ||
var max = Math.max.apply(Math, toConsumableArray(values)); | ||
var min = Math.min.apply(Math, toConsumableArray(values)) || 1; | ||
var colorValues = []; | ||
if (Array.isArray(colors)) { | ||
colorValues = colors.map(function (color, i) { | ||
return { color: color, value: i / colors.length }; | ||
}); | ||
} else if (isValidColorsNumber(colors)) { | ||
try { | ||
colorValues = gradient(lowColor, highColor, colors).map(function (color, i) { | ||
return { color: color, value: i / colors }; | ||
}); | ||
} catch (e) {} | ||
} | ||
return normalizedHistory.map(function (day) { | ||
@@ -301,3 +313,3 @@ var color = emptyColor; | ||
for (var i = 0, end = colorValues.length; i < end; i++) { | ||
if (dayValue < colorValues[i].value) { | ||
if (dayValue <= colorValues[i].value) { | ||
break; | ||
@@ -319,2 +331,71 @@ } | ||
// validate the colors props | ||
function validateColors(_ref2) { | ||
var colors = _ref2.colors, | ||
emptyColor = _ref2.emptyColor, | ||
lowColor = _ref2.lowColor, | ||
highColor = _ref2.highColor; | ||
if (typeof colors === 'number') { | ||
// make sure the colors is a positive whole number | ||
if (!isValidColorsNumber(colors)) { | ||
throw 'Invalid color value. Expected a whole number greater than 2, got ' + colors + '.'; | ||
} | ||
// if colors are a number, lowColor and highColor must be valid | ||
var hexRegex = /#[0-9A-Fa-f]{6}/; | ||
if (typeof lowColor !== 'string' || !hexRegex.test(lowColor)) { | ||
throw 'Invalid lowColor. Expected 6 digit hex color, got ' + lowColor + '.'; | ||
} | ||
if (typeof highColor !== 'string' || !hexRegex.test(highColor)) { | ||
throw 'Invalid highColor. Expected 6 digit hex color, got ' + highColor + '.'; | ||
} | ||
} | ||
} | ||
// make sure a colors number is valid | ||
function isValidColorsNumber(colors) { | ||
return colors > 2 && colors < Infinity && !isNaN(colors) && colors % 1 === 0; | ||
} | ||
// calculate a gradient between two colors | ||
function gradient(lowColor, highColor, colors) { | ||
// parse the low and high | ||
var fromColors = toRgb(lowColor); | ||
var toColors = toRgb(highColor); | ||
// calculate the step variance for each color | ||
var stepR = (toColors.r - fromColors.r) / (colors - 1); | ||
var stepG = (toColors.g - fromColors.g) / (colors - 1); | ||
var stepB = (toColors.b - fromColors.b) / (colors - 1); | ||
// calculate each color in the gradient and return the result | ||
return [].concat(toConsumableArray(new Array(colors))).map(function (step, i) { | ||
return toHex(Math.round(fromColors.r + stepR * i), Math.round(fromColors.g + stepG * i), Math.round(fromColors.b + stepB * i)); | ||
}); | ||
} | ||
// convert hex colors to rgb values | ||
function toRgb(color) { | ||
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(color); | ||
return { | ||
r: parseInt(result[1], 16), | ||
g: parseInt(result[2], 16), | ||
b: parseInt(result[3], 16) | ||
}; | ||
} | ||
// convert rgb values to a hex color | ||
function toHex(r, g, b) { | ||
var red = r < 16 ? '0' + r.toString(16) : r.toString(16); | ||
var green = g < 16 ? '0' + g.toString(16) : g.toString(16); | ||
var blue = b < 16 ? '0' + b.toString(16) : b.toString(16); | ||
return '#' + red + green + blue; | ||
} | ||
// group our normalized history by week | ||
@@ -344,4 +425,4 @@ function groupWeeks(normalizedHist) { | ||
// from oldest to newest, and filling in gaps between days. | ||
function normalize(hist) { | ||
var normalizedHistory = hist.slice(0).sort(function (a, b) { | ||
function normalize(heatmap) { | ||
var normalizedHistory = heatmap.history.slice(0).sort(function (a, b) { | ||
return new Date(a.date) - new Date(b.date); | ||
@@ -351,7 +432,13 @@ }).reduce(fillMissingDates, []).map(attachDayOfWeek); | ||
// finally, attach a color to each piece of history | ||
return attachDayColor(normalizedHistory); | ||
return attachDayColor({ | ||
colors: heatmap.colors, | ||
emptyColor: heatmap.emptyColor, | ||
highColor: heatmap.highColor, | ||
lowColor: heatmap.lowColor, | ||
normalizedHistory: normalizedHistory | ||
}); | ||
} | ||
// validate that the history prop is in the correct format | ||
function validate(hist) { | ||
function validateHistory(hist) { | ||
// make sure history is present | ||
@@ -444,4 +531,10 @@ if (typeof hist === 'undefined') { | ||
/* src\heatmap.html generated by Svelte v1.41.3 */ | ||
function normalizedHistory(history) { | ||
return normalize(history || []); | ||
function normalizedHistory(colors, emptyColor, highColor, history, lowColor) { | ||
return normalize({ | ||
colors: colors, | ||
emptyColor: emptyColor, | ||
highColor: highColor, | ||
history: history || [], | ||
lowColor: lowColor, | ||
}); | ||
} | ||
@@ -451,2 +544,6 @@ | ||
return { | ||
colors: ['#c6e48b', '#7bc96f', '#239a3b', '#196127'], | ||
emptyColor: '#ebedf0', | ||
highColor: null, | ||
lowColor: null, | ||
tooltip: (date, value) => `${value} on ${date}`, | ||
@@ -458,4 +555,12 @@ }; | ||
try { | ||
// validate the colors that were provided | ||
validateColors({ | ||
colors: this.get('colors'), | ||
emptyColor: this.get('emptyColor'), | ||
highColor: this.get('highColor'), | ||
lowColor: this.get('lowColor'), | ||
}); | ||
// validate the history prop | ||
validate(this.get('history')); | ||
validateHistory(this.get('history')); | ||
@@ -474,3 +579,3 @@ // validate the tooltip prop | ||
function encapsulateStyles(node) { | ||
setAttribute(node, "svelte-481602688", ""); | ||
setAttribute(node, "svelte-3568561252", ""); | ||
} | ||
@@ -716,3 +821,3 @@ | ||
this._state = assign(data(), options.data); | ||
this._recompute({ history: 1 }, this._state); | ||
this._recompute({ colors: 1, emptyColor: 1, highColor: 1, history: 1, lowColor: 1 }, this._state); | ||
@@ -740,4 +845,4 @@ var _oncreate = oncreate.bind(this); | ||
Heatmap.prototype._recompute = function _recompute(changed, state) { | ||
if (changed.history) { | ||
if (differs(state.normalizedHistory, (state.normalizedHistory = normalizedHistory(state.history)))) changed.normalizedHistory = true; | ||
if (changed.colors || changed.emptyColor || changed.highColor || changed.history || changed.lowColor) { | ||
if (differs(state.normalizedHistory, (state.normalizedHistory = normalizedHistory(state.colors, state.emptyColor, state.highColor, state.history, state.lowColor)))) changed.normalizedHistory = true; | ||
} | ||
@@ -744,0 +849,0 @@ }; |
{ | ||
"name": "svelte-heatmap", | ||
"version": "0.2.1", | ||
"version": "0.3.0", | ||
"description": "A GitHub style heatmap made with Svelte.js", | ||
@@ -5,0 +5,0 @@ "main": "dist/heatmap.cjs.js", |
@@ -53,1 +53,31 @@ # svelte-heatmap | ||
``` | ||
### Custom colors | ||
To define a set of custom colors, simply provide an array of css colors. The `emptyColor` property will be used for days with no value. | ||
```js | ||
new SvelteHeatmap({ | ||
target: el, | ||
data: { | ||
colors: ['#c6e48b', '#7bc96f', '#239a3b', '#196127'], | ||
emptyColor: '#dddddd', | ||
history: [], | ||
}, | ||
}); | ||
``` | ||
Alternatively, you can calculate colors on the fly using the following options. Be aware when doing this, `lowColor` and `highColor` must be 6 digit hex values (ex: `#123456`). | ||
```js | ||
new SvelteHeatmap({ | ||
target: el, | ||
data: { | ||
colors: 10, // <- number of colors to use | ||
lowColor: '#aaaaaa', // <- color for low values | ||
highColor: '#000000', // <- color for high values | ||
emptyColor: '#dddddd', | ||
history: [], | ||
}, | ||
}); | ||
``` |
@@ -1,20 +0,21 @@ | ||
// for now these are just hard coded. in the future, this | ||
// component should accept props to calculate these. | ||
export const colors = [ | ||
'#c6e48b', | ||
'#7bc96f', | ||
'#239a3b', | ||
'#196127', | ||
]; | ||
// determine what color a day is | ||
export function attachDayColor({ colors, emptyColor, highColor, lowColor, normalizedHistory }) { | ||
const values = normalizedHistory.map(day => day.value); | ||
const max = Math.max(...values); | ||
const min = Math.min(...values) || 1; | ||
export const emptyColor = '#ebedf0'; | ||
let colorValues = []; | ||
// determine what color a day is | ||
export function attachDayColor(normalizedHistory) { | ||
const max = Math.max(...normalizedHistory.map(day => day.value)); | ||
if (Array.isArray(colors)) { | ||
colorValues = colors.map((color, i) => { | ||
return { color, value: i / colors.length }; | ||
}); | ||
} else if (isValidColorsNumber(colors)) { | ||
try { | ||
colorValues = gradient(lowColor, highColor, colors).map((color, i) => { | ||
return { color, value: i / colors }; | ||
}); | ||
} catch (e) {} | ||
} | ||
const colorValues = colors.map((color, i) => { | ||
return { color, value: i / colors.length }; | ||
}); | ||
return normalizedHistory.map(day => { | ||
@@ -26,3 +27,3 @@ let color = emptyColor; | ||
for (let i = 0, end = colorValues.length; i < end; i++) { | ||
if (dayValue < colorValues[i].value) { | ||
if (dayValue <= colorValues[i].value) { | ||
break; | ||
@@ -43,1 +44,70 @@ } | ||
} | ||
// validate the colors props | ||
export function validateColors({ colors, emptyColor, lowColor, highColor }) { | ||
if (typeof colors === 'number') { | ||
// make sure the colors is a positive whole number | ||
if (!isValidColorsNumber(colors)) { | ||
throw `Invalid color value. Expected a whole number greater than 2, got ${colors}.`; | ||
} | ||
// if colors are a number, lowColor and highColor must be valid | ||
const hexRegex = /#[0-9A-Fa-f]{6}/; | ||
if (typeof lowColor !== 'string' || !hexRegex.test(lowColor)) { | ||
throw `Invalid lowColor. Expected 6 digit hex color, got ${lowColor}.`; | ||
} | ||
if (typeof highColor !== 'string' || !hexRegex.test(highColor)) { | ||
throw `Invalid highColor. Expected 6 digit hex color, got ${highColor}.`; | ||
} | ||
} | ||
} | ||
// make sure a colors number is valid | ||
function isValidColorsNumber(colors) { | ||
return colors > 2 | ||
&& colors < Infinity | ||
&& !isNaN(colors) | ||
&& colors % 1 === 0; | ||
} | ||
// calculate a gradient between two colors | ||
function gradient(lowColor, highColor, colors) { | ||
// parse the low and high | ||
const fromColors = toRgb(lowColor); | ||
const toColors = toRgb(highColor); | ||
// calculate the step variance for each color | ||
const stepR = (toColors.r - fromColors.r) / (colors - 1); | ||
const stepG = (toColors.g - fromColors.g) / (colors - 1); | ||
const stepB = (toColors.b - fromColors.b) / (colors - 1); | ||
// calculate each color in the gradient and return the result | ||
return [...new Array(colors)].map((step, i) => toHex( | ||
Math.round(fromColors.r + (stepR * i)), | ||
Math.round(fromColors.g + (stepG * i)), | ||
Math.round(fromColors.b + (stepB * i)), | ||
)); | ||
} | ||
// convert hex colors to rgb values | ||
function toRgb(color) { | ||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(color); | ||
return { | ||
r: parseInt(result[1], 16), | ||
g: parseInt(result[2], 16), | ||
b: parseInt(result[3], 16), | ||
}; | ||
} | ||
// convert rgb values to a hex color | ||
export function toHex(r, g, b) { | ||
const red = r < 16 ? `0${r.toString(16)}` : r.toString(16); | ||
const green = g < 16 ? `0${g.toString(16)}` : g.toString(16); | ||
const blue = b < 16 ? `0${b.toString(16)}` : b.toString(16); | ||
return `#${red}${green}${blue}`; | ||
} |
@@ -27,4 +27,4 @@ import { attachDayColor } from './color'; | ||
// from oldest to newest, and filling in gaps between days. | ||
export function normalize(hist) { | ||
const normalizedHistory = hist.slice(0) | ||
export function normalize(heatmap) { | ||
const normalizedHistory = heatmap.history.slice(0) | ||
.sort((a, b) => new Date(a.date) - new Date(b.date)) | ||
@@ -35,7 +35,13 @@ .reduce(fillMissingDates, []) | ||
// finally, attach a color to each piece of history | ||
return attachDayColor(normalizedHistory); | ||
return attachDayColor({ | ||
colors: heatmap.colors, | ||
emptyColor: heatmap.emptyColor, | ||
highColor: heatmap.highColor, | ||
lowColor: heatmap.lowColor, | ||
normalizedHistory, | ||
}); | ||
} | ||
// validate that the history prop is in the correct format | ||
export function validate(hist) { | ||
export function validateHistory(hist) { | ||
// make sure history is present | ||
@@ -42,0 +48,0 @@ if (typeof hist === 'undefined') { |
@@ -122,2 +122,59 @@ const { expect } = require('chai'); | ||
}); | ||
it('throws a warning if an invalid color number is provided', () => { | ||
const warn = sinon.stub(console, 'warn'); | ||
new Heatmap({ | ||
target: div(), | ||
data: { | ||
colors: -6, | ||
highColor: '#000000', | ||
history: [], | ||
lowColor: '#ffffff', | ||
}, | ||
}); | ||
expect(warn.called).to.be.true; | ||
expect(warn.lastCall.args[0]).to.include('Invalid color value. Expected a whole number greater than 2, got -6'); | ||
warn.restore(); | ||
}); | ||
it('throws a warning if colors is a number, but lowColor is not a valid string', () => { | ||
const warn = sinon.stub(console, 'warn'); | ||
new Heatmap({ | ||
target: div(), | ||
data: { | ||
colors: 5, | ||
highColor: '#f00000', | ||
history: [], | ||
lowColor: 'blahhhhhhh', | ||
}, | ||
}); | ||
expect(warn.called).to.be.true; | ||
expect(warn.lastCall.args[0]).to.include('Invalid lowColor. Expected 6 digit hex color, got blahhhhhhh'); | ||
warn.restore(); | ||
}); | ||
it('throws a warning if colors is a number, but highColor is not a valid string', () => { | ||
const warn = sinon.stub(console, 'warn'); | ||
new Heatmap({ | ||
target: div(), | ||
data: { | ||
colors: 5, | ||
highColor: 4, | ||
history: [], | ||
lowColor: '#aabbcc', | ||
}, | ||
}); | ||
expect(warn.called).to.be.true; | ||
expect(warn.lastCall.args[0]).to.include('Invalid highColor. Expected 6 digit hex color, got 4'); | ||
warn.restore(); | ||
}); | ||
}); | ||
@@ -208,7 +265,7 @@ | ||
{ date: '2017/11/05', value: 0 }, | ||
// omitting 2017/11/04 | ||
// omitting 2017/11/06 | ||
{ date: '2017/11/07', value: 1 }, | ||
{ date: '2017/11/08', value: 3 }, | ||
{ date: '2017/11/09', value: 7 }, | ||
{ date: '2017/11/10', value: 10 }, | ||
{ date: '2017/11/08', value: 2 }, | ||
{ date: '2017/11/09', value: 3 }, | ||
{ date: '2017/11/10', value: 4 }, | ||
], | ||
@@ -226,3 +283,71 @@ }, | ||
}); | ||
it('accepts a custom empty color', () => { | ||
const el = div(); | ||
new Heatmap({ | ||
target: el, | ||
data: { | ||
emptyColor: 'red', | ||
history: [ | ||
{ date: '2017/11/05', value: 0 }, | ||
], | ||
}, | ||
}); | ||
expect(el.querySelector('.svelte-heatmap-day-inner').style.backgroundColor).to.equal('red'); | ||
}); | ||
it('accepts an array of colors', () => { | ||
const el = div(); | ||
new Heatmap({ | ||
target: el, | ||
data: { | ||
colors: ['red', 'green', 'blue'], | ||
history: [ | ||
{ date: '2017/11/05', value: 1 }, | ||
{ date: '2017/11/06', value: 2 }, | ||
{ date: '2017/11/07', value: 3 }, | ||
], | ||
}, | ||
}); | ||
expect(el.querySelector('.svelte-heatmap-day:nth-child(1) .svelte-heatmap-day-inner').style.backgroundColor) | ||
.to.equal('red'); | ||
expect(el.querySelector('.svelte-heatmap-day:nth-child(2) .svelte-heatmap-day-inner').style.backgroundColor) | ||
.to.equal('green'); | ||
expect(el.querySelector('.svelte-heatmap-day:nth-child(3) .svelte-heatmap-day-inner').style.backgroundColor) | ||
.to.equal('blue'); | ||
}); | ||
it('accepts a low and high color', () => { | ||
const el = div(); | ||
new Heatmap({ | ||
target: el, | ||
data: { | ||
colors: 3, | ||
lowColor: '#000000', | ||
highColor: '#ffffff', | ||
history: [ | ||
{ date: '2017/11/05', value: 1 }, | ||
{ date: '2017/11/06', value: 2 }, | ||
{ date: '2017/11/07', value: 3 }, | ||
], | ||
}, | ||
}); | ||
expect(el.querySelector('.svelte-heatmap-day:nth-child(1) .svelte-heatmap-day-inner').style.backgroundColor) | ||
.to.equal('rgb(0, 0, 0)'); | ||
expect(el.querySelector('.svelte-heatmap-day:nth-child(2) .svelte-heatmap-day-inner').style.backgroundColor) | ||
.to.equal('rgb(128, 128, 128)'); | ||
expect(el.querySelector('.svelte-heatmap-day:nth-child(3) .svelte-heatmap-day-inner').style.backgroundColor) | ||
.to.equal('rgb(255, 255, 255)'); | ||
}) | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
215500
2420
82