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