fix: Partial fix for #404 needs more work.
Better null value handling. Add option for filling zero/null values to maintain backwards compat.
This commit is contained in:
parent
9dce5cf5a8
commit
256649fbcc
@ -1,26 +1,26 @@
|
|||||||
import SvgTip from "../objects/SvgTip";
|
import SvgTip from "../objects/SvgTip";
|
||||||
import {
|
import {
|
||||||
$,
|
$,
|
||||||
isElementInViewport,
|
isElementInViewport,
|
||||||
getElementContentWidth,
|
getElementContentWidth,
|
||||||
isHidden,
|
isHidden,
|
||||||
} from "../utils/dom";
|
} from "../utils/dom";
|
||||||
import {
|
import {
|
||||||
makeSVGContainer,
|
makeSVGContainer,
|
||||||
makeSVGDefs,
|
makeSVGDefs,
|
||||||
makeSVGGroup,
|
makeSVGGroup,
|
||||||
makeText,
|
makeText,
|
||||||
} from "../utils/draw";
|
} from "../utils/draw";
|
||||||
import { LEGEND_ITEM_WIDTH } from "../utils/constants";
|
import { LEGEND_ITEM_WIDTH } from "../utils/constants";
|
||||||
import {
|
import {
|
||||||
BASE_MEASURES,
|
BASE_MEASURES,
|
||||||
getExtraHeight,
|
getExtraHeight,
|
||||||
getExtraWidth,
|
getExtraWidth,
|
||||||
getTopOffset,
|
getTopOffset,
|
||||||
getLeftOffset,
|
getLeftOffset,
|
||||||
INIT_CHART_UPDATE_TIMEOUT,
|
INIT_CHART_UPDATE_TIMEOUT,
|
||||||
CHART_POST_ANIMATE_TIMEOUT,
|
CHART_POST_ANIMATE_TIMEOUT,
|
||||||
DEFAULT_COLORS,
|
DEFAULT_COLORS,
|
||||||
} from "../utils/constants";
|
} from "../utils/constants";
|
||||||
import { getColor, isValidColor } from "../utils/colors";
|
import { getColor, isValidColor } from "../utils/colors";
|
||||||
import { runSMILAnimation } from "../utils/animation";
|
import { runSMILAnimation } from "../utils/animation";
|
||||||
@ -28,348 +28,353 @@ import { downloadFile, prepareForExport } from "../utils/export";
|
|||||||
import { deepClone } from "../utils/helpers";
|
import { deepClone } from "../utils/helpers";
|
||||||
|
|
||||||
export default class BaseChart {
|
export default class BaseChart {
|
||||||
constructor(parent, options) {
|
constructor(parent, options) {
|
||||||
// deepclone options to avoid making changes to orignal object
|
// deepclone options to avoid making changes to orignal object
|
||||||
options = deepClone(options);
|
options = deepClone(options);
|
||||||
|
|
||||||
this.parent =
|
this.parent =
|
||||||
typeof parent === "string" ? document.querySelector(parent) : parent;
|
typeof parent === "string"
|
||||||
|
? document.querySelector(parent)
|
||||||
|
: parent;
|
||||||
|
|
||||||
if (!(this.parent instanceof HTMLElement)) {
|
if (!(this.parent instanceof HTMLElement)) {
|
||||||
throw new Error("No `parent` element to render on was provided.");
|
throw new Error("No `parent` element to render on was provided.");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.rawChartArgs = options;
|
this.rawChartArgs = options;
|
||||||
|
|
||||||
this.title = options.title || "";
|
this.title = options.title || "";
|
||||||
this.type = options.type || "";
|
this.type = options.type || "";
|
||||||
|
|
||||||
this.realData = this.prepareData(options.data);
|
this.realData = this.prepareData(options.data, options.config);
|
||||||
this.data = this.prepareFirstData(this.realData);
|
this.data = this.prepareFirstData(this.realData);
|
||||||
|
|
||||||
this.colors = this.validateColors(options.colors, this.type);
|
this.colors = this.validateColors(options.colors, this.type);
|
||||||
|
|
||||||
this.config = {
|
this.config = {
|
||||||
showTooltip: 1, // calculate
|
showTooltip: 1, // calculate
|
||||||
showLegend:
|
showLegend:
|
||||||
typeof options.showLegend !== "undefined" ? options.showLegend : 1,
|
typeof options.showLegend !== "undefined"
|
||||||
isNavigable: options.isNavigable || 0,
|
? options.showLegend
|
||||||
animate: typeof options.animate !== "undefined" ? options.animate : 1,
|
: 1,
|
||||||
truncateLegends:
|
isNavigable: options.isNavigable || 0,
|
||||||
typeof options.truncateLegends !== "undefined"
|
animate:
|
||||||
? options.truncateLegends
|
typeof options.animate !== "undefined" ? options.animate : 0,
|
||||||
: 1,
|
truncateLegends:
|
||||||
};
|
typeof options.truncateLegends !== "undefined"
|
||||||
|
? options.truncateLegends
|
||||||
|
: 1,
|
||||||
|
};
|
||||||
|
|
||||||
this.measures = JSON.parse(JSON.stringify(BASE_MEASURES));
|
this.measures = JSON.parse(JSON.stringify(BASE_MEASURES));
|
||||||
let m = this.measures;
|
let m = this.measures;
|
||||||
this.setMeasures(options);
|
this.setMeasures(options);
|
||||||
if (!this.title.length) {
|
if (!this.title.length) {
|
||||||
m.titleHeight = 0;
|
m.titleHeight = 0;
|
||||||
}
|
}
|
||||||
if (!this.config.showLegend) m.legendHeight = 0;
|
if (!this.config.showLegend) m.legendHeight = 0;
|
||||||
this.argHeight = options.height || m.baseHeight;
|
this.argHeight = options.height || m.baseHeight;
|
||||||
|
|
||||||
this.state = {};
|
this.state = {};
|
||||||
this.options = {};
|
this.options = {};
|
||||||
|
|
||||||
this.initTimeout = INIT_CHART_UPDATE_TIMEOUT;
|
this.initTimeout = INIT_CHART_UPDATE_TIMEOUT;
|
||||||
|
|
||||||
if (this.config.isNavigable) {
|
if (this.config.isNavigable) {
|
||||||
this.overlays = [];
|
this.overlays = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
this.configure(options);
|
this.configure(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareData(data) {
|
prepareData(data) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareFirstData(data) {
|
prepareFirstData(data) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
validateColors(colors, type) {
|
validateColors(colors, type) {
|
||||||
const validColors = [];
|
const validColors = [];
|
||||||
colors = (colors || []).concat(DEFAULT_COLORS[type]);
|
colors = (colors || []).concat(DEFAULT_COLORS[type]);
|
||||||
colors.forEach((string) => {
|
colors.forEach((string) => {
|
||||||
const color = getColor(string);
|
const color = getColor(string);
|
||||||
if (!isValidColor(color)) {
|
if (!isValidColor(color)) {
|
||||||
console.warn('"' + string + '" is not a valid color.');
|
console.warn('"' + string + '" is not a valid color.');
|
||||||
} else {
|
} else {
|
||||||
validColors.push(color);
|
validColors.push(color);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return validColors;
|
return validColors;
|
||||||
}
|
}
|
||||||
|
|
||||||
setMeasures() {
|
setMeasures() {
|
||||||
// Override measures, including those for title and legend
|
// Override measures, including those for title and legend
|
||||||
// set config for legend and title
|
// set config for legend and title
|
||||||
}
|
}
|
||||||
|
|
||||||
configure() {
|
configure() {
|
||||||
let height = this.argHeight;
|
let height = this.argHeight;
|
||||||
this.baseHeight = height;
|
this.baseHeight = height;
|
||||||
this.height = height - getExtraHeight(this.measures);
|
this.height = height - getExtraHeight(this.measures);
|
||||||
|
|
||||||
// Bind window events
|
// Bind window events
|
||||||
this.boundDrawFn = () => this.draw(true);
|
this.boundDrawFn = () => this.draw(true);
|
||||||
// Look into improving responsiveness
|
// Look into improving responsiveness
|
||||||
//if (ResizeObserver) {
|
//if (ResizeObserver) {
|
||||||
// this.resizeObserver = new ResizeObserver(this.boundDrawFn);
|
// this.resizeObserver = new ResizeObserver(this.boundDrawFn);
|
||||||
// this.resizeObserver.observe(this.parent);
|
// this.resizeObserver.observe(this.parent);
|
||||||
//}
|
//}
|
||||||
window.addEventListener("resize", this.boundDrawFn);
|
window.addEventListener("resize", this.boundDrawFn);
|
||||||
window.addEventListener("orientationchange", this.boundDrawFn);
|
window.addEventListener("orientationchange", this.boundDrawFn);
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
//if (this.resizeObserver) this.resizeObserver.disconnect();
|
//if (this.resizeObserver) this.resizeObserver.disconnect();
|
||||||
window.removeEventListener("resize", this.boundDrawFn);
|
window.removeEventListener("resize", this.boundDrawFn);
|
||||||
window.removeEventListener("orientationchange", this.boundDrawFn);
|
window.removeEventListener("orientationchange", this.boundDrawFn);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Has to be called manually
|
// Has to be called manually
|
||||||
setup() {
|
setup() {
|
||||||
this.makeContainer();
|
this.makeContainer();
|
||||||
this.updateWidth();
|
this.updateWidth();
|
||||||
this.makeTooltip();
|
this.makeTooltip();
|
||||||
|
|
||||||
this.draw(false, true);
|
this.draw(false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
makeContainer() {
|
makeContainer() {
|
||||||
// Chart needs a dedicated parent element
|
// Chart needs a dedicated parent element
|
||||||
this.parent.innerHTML = "";
|
this.parent.innerHTML = "";
|
||||||
|
|
||||||
let args = {
|
let args = {
|
||||||
inside: this.parent,
|
inside: this.parent,
|
||||||
className: "chart-container",
|
className: "chart-container",
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.independentWidth) {
|
if (this.independentWidth) {
|
||||||
args.styles = { width: this.independentWidth + "px" };
|
args.styles = { width: this.independentWidth + "px" };
|
||||||
}
|
}
|
||||||
|
|
||||||
this.container = $.create("div", args);
|
this.container = $.create("div", args);
|
||||||
}
|
}
|
||||||
|
|
||||||
makeTooltip() {
|
makeTooltip() {
|
||||||
this.tip = new SvgTip({
|
this.tip = new SvgTip({
|
||||||
parent: this.container,
|
parent: this.container,
|
||||||
colors: this.colors,
|
colors: this.colors,
|
||||||
});
|
});
|
||||||
this.bindTooltip();
|
this.bindTooltip();
|
||||||
}
|
}
|
||||||
|
|
||||||
bindTooltip() {}
|
bindTooltip() {}
|
||||||
|
|
||||||
draw(onlyWidthChange = false, init = false) {
|
draw(onlyWidthChange = false, init = false) {
|
||||||
if (onlyWidthChange && isHidden(this.parent)) {
|
if (onlyWidthChange && isHidden(this.parent)) {
|
||||||
// Don't update anything if the chart is hidden
|
// Don't update anything if the chart is hidden
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.updateWidth();
|
this.updateWidth();
|
||||||
|
|
||||||
this.calc(onlyWidthChange);
|
this.calc(onlyWidthChange);
|
||||||
this.makeChartArea();
|
this.makeChartArea();
|
||||||
this.setupComponents();
|
this.setupComponents();
|
||||||
|
|
||||||
this.components.forEach((c) => c.setup(this.drawArea));
|
this.components.forEach((c) => c.setup(this.drawArea));
|
||||||
// this.components.forEach(c => c.make());
|
// this.components.forEach(c => c.make());
|
||||||
this.render(this.components, false);
|
this.render(this.components, false);
|
||||||
|
|
||||||
if (init) {
|
if (init) {
|
||||||
this.data = this.realData;
|
this.data = this.realData;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.update(this.data, true);
|
this.update(this.data, true);
|
||||||
}, this.initTimeout);
|
}, this.initTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.config.showLegend) {
|
if (this.config.showLegend) {
|
||||||
this.renderLegend();
|
this.renderLegend();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setupNavigation(init);
|
this.setupNavigation(init);
|
||||||
}
|
}
|
||||||
|
|
||||||
calc() {} // builds state
|
calc() {} // builds state
|
||||||
|
|
||||||
updateWidth() {
|
updateWidth() {
|
||||||
this.baseWidth = getElementContentWidth(this.parent);
|
this.baseWidth = getElementContentWidth(this.parent);
|
||||||
this.width = this.baseWidth - getExtraWidth(this.measures);
|
this.width = this.baseWidth - getExtraWidth(this.measures);
|
||||||
}
|
}
|
||||||
|
|
||||||
makeChartArea() {
|
makeChartArea() {
|
||||||
if (this.svg) {
|
if (this.svg) {
|
||||||
this.container.removeChild(this.svg);
|
this.container.removeChild(this.svg);
|
||||||
}
|
}
|
||||||
let m = this.measures;
|
let m = this.measures;
|
||||||
|
|
||||||
this.svg = makeSVGContainer(
|
this.svg = makeSVGContainer(
|
||||||
this.container,
|
this.container,
|
||||||
"frappe-chart chart",
|
"frappe-chart chart",
|
||||||
this.baseWidth,
|
this.baseWidth,
|
||||||
this.baseHeight
|
this.baseHeight
|
||||||
);
|
);
|
||||||
this.svgDefs = makeSVGDefs(this.svg);
|
this.svgDefs = makeSVGDefs(this.svg);
|
||||||
|
|
||||||
if (this.title.length) {
|
if (this.title.length) {
|
||||||
this.titleEL = makeText(
|
this.titleEL = makeText(
|
||||||
"title",
|
"title",
|
||||||
m.margins.left,
|
m.margins.left,
|
||||||
m.margins.top,
|
m.margins.top,
|
||||||
this.title,
|
this.title,
|
||||||
{
|
{
|
||||||
fontSize: m.titleFontSize,
|
fontSize: m.titleFontSize,
|
||||||
fill: "#666666",
|
fill: "#666666",
|
||||||
dy: m.titleFontSize,
|
dy: m.titleFontSize,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let top = getTopOffset(m);
|
let top = getTopOffset(m);
|
||||||
this.drawArea = makeSVGGroup(
|
this.drawArea = makeSVGGroup(
|
||||||
this.type + "-chart chart-draw-area",
|
this.type + "-chart chart-draw-area",
|
||||||
`translate(${getLeftOffset(m)}, ${top})`
|
`translate(${getLeftOffset(m)}, ${top})`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.config.showLegend) {
|
if (this.config.showLegend) {
|
||||||
top += this.height + m.paddings.bottom;
|
top += this.height + m.paddings.bottom;
|
||||||
this.legendArea = makeSVGGroup(
|
this.legendArea = makeSVGGroup(
|
||||||
"chart-legend",
|
"chart-legend",
|
||||||
`translate(${getLeftOffset(m)}, ${top})`
|
`translate(${getLeftOffset(m)}, ${top})`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.title.length) {
|
if (this.title.length) {
|
||||||
this.svg.appendChild(this.titleEL);
|
this.svg.appendChild(this.titleEL);
|
||||||
}
|
}
|
||||||
this.svg.appendChild(this.drawArea);
|
this.svg.appendChild(this.drawArea);
|
||||||
if (this.config.showLegend) {
|
if (this.config.showLegend) {
|
||||||
this.svg.appendChild(this.legendArea);
|
this.svg.appendChild(this.legendArea);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateTipOffset(getLeftOffset(m), getTopOffset(m));
|
this.updateTipOffset(getLeftOffset(m), getTopOffset(m));
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTipOffset(x, y) {
|
updateTipOffset(x, y) {
|
||||||
this.tip.offset = {
|
this.tip.offset = {
|
||||||
x: x,
|
x: x,
|
||||||
y: y,
|
y: y,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setupComponents() {
|
setupComponents() {
|
||||||
this.components = new Map();
|
this.components = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
update(data, drawing = false) {
|
update(data, drawing = false) {
|
||||||
if (!data) console.error("No data to update.");
|
if (!data) console.error("No data to update.");
|
||||||
if (!drawing) data = deepClone(data);
|
if (!drawing) data = deepClone(data);
|
||||||
this.data = this.prepareData(data);
|
this.data = this.prepareData(data);
|
||||||
this.calc(); // builds state
|
this.calc(); // builds state
|
||||||
this.render(this.components, this.config.animate);
|
this.render(this.components, this.config.animate);
|
||||||
}
|
}
|
||||||
|
|
||||||
render(components = this.components, animate = true) {
|
render(components = this.components, animate = true) {
|
||||||
if (this.config.isNavigable) {
|
if (this.config.isNavigable) {
|
||||||
// Remove all existing overlays
|
// Remove all existing overlays
|
||||||
this.overlays.map((o) => o.parentNode.removeChild(o));
|
this.overlays.map((o) => o.parentNode.removeChild(o));
|
||||||
// ref.parentNode.insertBefore(element, ref);
|
// ref.parentNode.insertBefore(element, ref);
|
||||||
}
|
}
|
||||||
let elementsToAnimate = [];
|
let elementsToAnimate = [];
|
||||||
// Can decouple to this.refreshComponents() first to save animation timeout
|
// Can decouple to this.refreshComponents() first to save animation timeout
|
||||||
components.forEach((c) => {
|
components.forEach((c) => {
|
||||||
elementsToAnimate = elementsToAnimate.concat(c.update(animate));
|
elementsToAnimate = elementsToAnimate.concat(c.update(animate));
|
||||||
});
|
});
|
||||||
if (elementsToAnimate.length > 0) {
|
if (elementsToAnimate.length > 0) {
|
||||||
runSMILAnimation(this.container, this.svg, elementsToAnimate);
|
runSMILAnimation(this.container, this.svg, elementsToAnimate);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
components.forEach((c) => c.make());
|
components.forEach((c) => c.make());
|
||||||
this.updateNav();
|
this.updateNav();
|
||||||
}, CHART_POST_ANIMATE_TIMEOUT);
|
}, CHART_POST_ANIMATE_TIMEOUT);
|
||||||
} else {
|
} else {
|
||||||
components.forEach((c) => c.make());
|
components.forEach((c) => c.make());
|
||||||
this.updateNav();
|
this.updateNav();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateNav() {
|
updateNav() {
|
||||||
if (this.config.isNavigable) {
|
if (this.config.isNavigable) {
|
||||||
this.makeOverlay();
|
this.makeOverlay();
|
||||||
this.bindUnits();
|
this.bindUnits();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLegend(dataset) {
|
renderLegend(dataset) {
|
||||||
this.legendArea.textContent = "";
|
this.legendArea.textContent = "";
|
||||||
let count = 0;
|
let count = 0;
|
||||||
let y = 0;
|
let y = 0;
|
||||||
|
|
||||||
dataset.map((data, index) => {
|
dataset.map((data, index) => {
|
||||||
let divisor = Math.floor(this.width / LEGEND_ITEM_WIDTH);
|
let divisor = Math.floor(this.width / LEGEND_ITEM_WIDTH);
|
||||||
if (count > divisor) {
|
if (count > divisor) {
|
||||||
count = 0;
|
count = 0;
|
||||||
y += this.config.legendRowHeight;
|
y += this.config.legendRowHeight;
|
||||||
}
|
}
|
||||||
let x = LEGEND_ITEM_WIDTH * count;
|
let x = LEGEND_ITEM_WIDTH * count;
|
||||||
let dot = this.makeLegend(data, index, x, y);
|
let dot = this.makeLegend(data, index, x, y);
|
||||||
this.legendArea.appendChild(dot);
|
this.legendArea.appendChild(dot);
|
||||||
count++;
|
count++;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
makeLegend() {}
|
makeLegend() {}
|
||||||
|
|
||||||
setupNavigation(init = false) {
|
setupNavigation(init = false) {
|
||||||
if (!this.config.isNavigable) return;
|
if (!this.config.isNavigable) return;
|
||||||
|
|
||||||
if (init) {
|
if (init) {
|
||||||
this.bindOverlay();
|
this.bindOverlay();
|
||||||
|
|
||||||
this.keyActions = {
|
this.keyActions = {
|
||||||
13: this.onEnterKey.bind(this),
|
13: this.onEnterKey.bind(this),
|
||||||
37: this.onLeftArrow.bind(this),
|
37: this.onLeftArrow.bind(this),
|
||||||
38: this.onUpArrow.bind(this),
|
38: this.onUpArrow.bind(this),
|
||||||
39: this.onRightArrow.bind(this),
|
39: this.onRightArrow.bind(this),
|
||||||
40: this.onDownArrow.bind(this),
|
40: this.onDownArrow.bind(this),
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener("keydown", (e) => {
|
document.addEventListener("keydown", (e) => {
|
||||||
if (isElementInViewport(this.container)) {
|
if (isElementInViewport(this.container)) {
|
||||||
e = e || window.event;
|
e = e || window.event;
|
||||||
if (this.keyActions[e.keyCode]) {
|
if (this.keyActions[e.keyCode]) {
|
||||||
this.keyActions[e.keyCode]();
|
this.keyActions[e.keyCode]();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
makeOverlay() {}
|
makeOverlay() {}
|
||||||
updateOverlay() {}
|
updateOverlay() {}
|
||||||
bindOverlay() {}
|
bindOverlay() {}
|
||||||
bindUnits() {}
|
bindUnits() {}
|
||||||
|
|
||||||
onLeftArrow() {}
|
onLeftArrow() {}
|
||||||
onRightArrow() {}
|
onRightArrow() {}
|
||||||
onUpArrow() {}
|
onUpArrow() {}
|
||||||
onDownArrow() {}
|
onDownArrow() {}
|
||||||
onEnterKey() {}
|
onEnterKey() {}
|
||||||
|
|
||||||
addDataPoint() {}
|
addDataPoint() {}
|
||||||
removeDataPoint() {}
|
removeDataPoint() {}
|
||||||
|
|
||||||
getDataPoint() {}
|
getDataPoint() {}
|
||||||
setCurrentDataPoint() {}
|
setCurrentDataPoint() {}
|
||||||
|
|
||||||
updateDataset() {}
|
updateDataset() {}
|
||||||
|
|
||||||
export() {
|
export() {
|
||||||
let chartSvg = prepareForExport(this.svg);
|
let chartSvg = prepareForExport(this.svg);
|
||||||
downloadFile(this.title || "Chart", [chartSvg]);
|
downloadFile(this.title || "Chart", [chartSvg]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { ANGLE_RATIO } from "./constants";
|
|||||||
* @param {Number} d Any number
|
* @param {Number} d Any number
|
||||||
*/
|
*/
|
||||||
export function floatTwo(d) {
|
export function floatTwo(d) {
|
||||||
return parseFloat(d.toFixed(2));
|
return parseFloat(d.toFixed(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -14,12 +14,12 @@ export function floatTwo(d) {
|
|||||||
* @param {Array} arr2 Second array
|
* @param {Array} arr2 Second array
|
||||||
*/
|
*/
|
||||||
export function arraysEqual(arr1, arr2) {
|
export function arraysEqual(arr1, arr2) {
|
||||||
if (arr1.length !== arr2.length) return false;
|
if (arr1.length !== arr2.length) return false;
|
||||||
let areEqual = true;
|
let areEqual = true;
|
||||||
arr1.map((d, i) => {
|
arr1.map((d, i) => {
|
||||||
if (arr2[i] !== d) areEqual = false;
|
if (arr2[i] !== d) areEqual = false;
|
||||||
});
|
});
|
||||||
return areEqual;
|
return areEqual;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,16 +27,16 @@ export function arraysEqual(arr1, arr2) {
|
|||||||
* @param {Array} array An array containing the items.
|
* @param {Array} array An array containing the items.
|
||||||
*/
|
*/
|
||||||
export function shuffle(array) {
|
export function shuffle(array) {
|
||||||
// Awesomeness: https://bost.ocks.org/mike/shuffle/
|
// Awesomeness: https://bost.ocks.org/mike/shuffle/
|
||||||
// https://stackoverflow.com/a/2450976/6495043
|
// https://stackoverflow.com/a/2450976/6495043
|
||||||
// https://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array?noredirect=1&lq=1
|
// https://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array?noredirect=1&lq=1
|
||||||
|
|
||||||
for (let i = array.length - 1; i > 0; i--) {
|
for (let i = array.length - 1; i > 0; i--) {
|
||||||
let j = Math.floor(Math.random() * (i + 1));
|
let j = Math.floor(Math.random() * (i + 1));
|
||||||
[array[i], array[j]] = [array[j], array[i]];
|
[array[i], array[j]] = [array[j], array[i]];
|
||||||
}
|
}
|
||||||
|
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -47,12 +47,12 @@ export function shuffle(array) {
|
|||||||
* @param {Boolean} start fill at start?
|
* @param {Boolean} start fill at start?
|
||||||
*/
|
*/
|
||||||
export function fillArray(array, count, element, start = false) {
|
export function fillArray(array, count, element, start = false) {
|
||||||
if (!element) {
|
if (element == undefined) {
|
||||||
element = start ? array[0] : array[array.length - 1];
|
element = start ? array[0] : array[array.length - 1];
|
||||||
}
|
}
|
||||||
let fillerArray = new Array(Math.abs(count)).fill(element);
|
let fillerArray = new Array(Math.abs(count)).fill(element);
|
||||||
array = start ? fillerArray.concat(array) : array.concat(fillerArray);
|
array = start ? fillerArray.concat(array) : array.concat(fillerArray);
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,36 +61,36 @@ export function fillArray(array, count, element, start = false) {
|
|||||||
* @param {Number} charWidth Width of single char in pixels
|
* @param {Number} charWidth Width of single char in pixels
|
||||||
*/
|
*/
|
||||||
export function getStringWidth(string, charWidth) {
|
export function getStringWidth(string, charWidth) {
|
||||||
return (string + "").length * charWidth;
|
return (string + "").length * charWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function bindChange(obj, getFn, setFn) {
|
export function bindChange(obj, getFn, setFn) {
|
||||||
return new Proxy(obj, {
|
return new Proxy(obj, {
|
||||||
set: function (target, prop, value) {
|
set: function (target, prop, value) {
|
||||||
setFn();
|
setFn();
|
||||||
return Reflect.set(target, prop, value);
|
return Reflect.set(target, prop, value);
|
||||||
},
|
},
|
||||||
get: function (target, prop) {
|
get: function (target, prop) {
|
||||||
getFn();
|
getFn();
|
||||||
return Reflect.get(target, prop);
|
return Reflect.get(target, prop);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://stackoverflow.com/a/29325222
|
// https://stackoverflow.com/a/29325222
|
||||||
export function getRandomBias(min, max, bias, influence) {
|
export function getRandomBias(min, max, bias, influence) {
|
||||||
const range = max - min;
|
const range = max - min;
|
||||||
const biasValue = range * bias + min;
|
const biasValue = range * bias + min;
|
||||||
var rnd = Math.random() * range + min, // random in range
|
var rnd = Math.random() * range + min, // random in range
|
||||||
mix = Math.random() * influence; // random mixer
|
mix = Math.random() * influence; // random mixer
|
||||||
return rnd * (1 - mix) + biasValue * mix; // mix full range and bias
|
return rnd * (1 - mix) + biasValue * mix; // mix full range and bias
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPositionByAngle(angle, radius) {
|
export function getPositionByAngle(angle, radius) {
|
||||||
return {
|
return {
|
||||||
x: Math.sin(angle * ANGLE_RATIO) * radius,
|
x: Math.sin(angle * ANGLE_RATIO) * radius,
|
||||||
y: Math.cos(angle * ANGLE_RATIO) * radius,
|
y: Math.cos(angle * ANGLE_RATIO) * radius,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -99,11 +99,11 @@ export function getPositionByAngle(angle, radius) {
|
|||||||
* @param {Boolean} nonNegative flag to treat negative number as invalid
|
* @param {Boolean} nonNegative flag to treat negative number as invalid
|
||||||
*/
|
*/
|
||||||
export function isValidNumber(candidate, nonNegative = false) {
|
export function isValidNumber(candidate, nonNegative = false) {
|
||||||
if (Number.isNaN(candidate)) return false;
|
if (Number.isNaN(candidate)) return false;
|
||||||
else if (candidate === undefined) return false;
|
else if (candidate === undefined) return false;
|
||||||
else if (!Number.isFinite(candidate)) return false;
|
else if (!Number.isFinite(candidate)) return false;
|
||||||
else if (nonNegative && candidate < 0) return false;
|
else if (nonNegative && candidate < 0) return false;
|
||||||
else return true;
|
else return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -111,9 +111,9 @@ export function isValidNumber(candidate, nonNegative = false) {
|
|||||||
* @param {Number} d Any Number
|
* @param {Number} d Any Number
|
||||||
*/
|
*/
|
||||||
export function round(d) {
|
export function round(d) {
|
||||||
// https://floating-point-gui.de/
|
// https://floating-point-gui.de/
|
||||||
// https://www.jacklmoore.com/notes/rounding-in-javascript/
|
// https://www.jacklmoore.com/notes/rounding-in-javascript/
|
||||||
return Number(Math.round(d + "e4") + "e-4");
|
return Number(Math.round(d + "e4") + "e-4");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -121,23 +121,23 @@ export function round(d) {
|
|||||||
* @param {Object} candidate Any Object
|
* @param {Object} candidate Any Object
|
||||||
*/
|
*/
|
||||||
export function deepClone(candidate) {
|
export function deepClone(candidate) {
|
||||||
let cloned, value, key;
|
let cloned, value, key;
|
||||||
|
|
||||||
if (candidate instanceof Date) {
|
if (candidate instanceof Date) {
|
||||||
return new Date(candidate.getTime());
|
return new Date(candidate.getTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof candidate !== "object" || candidate === null) {
|
if (typeof candidate !== "object" || candidate === null) {
|
||||||
return candidate;
|
return candidate;
|
||||||
}
|
}
|
||||||
|
|
||||||
cloned = Array.isArray(candidate) ? [] : {};
|
cloned = Array.isArray(candidate) ? [] : {};
|
||||||
|
|
||||||
for (key in candidate) {
|
for (key in candidate) {
|
||||||
value = candidate[key];
|
value = candidate[key];
|
||||||
|
|
||||||
cloned[key] = deepClone(value);
|
cloned[key] = deepClone(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cloned;
|
return cloned;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user