import stats from "../analyze/components/stats.js";
import * as divisors from "./divisors.js";
import { googleCdn } from "../../external/googlecharts/googlecharts.js";
window.loaded = false;
var g = googleCdn();
/**
* Module for visualization of charts and tables.
* @class visualize
*/
/**
* Creates new charts depending on what the user requires. It can
* generate scatter, histograms, columns, lines, timelines, etc.
* It creates a new div space for each chart generated.
* @function chart
* @memberof visualize
* @param {Object} params - Contains: charType, id, drawing options (see google charts docs).
* @param {Object} data - Contains: data as JS nd-array.
* @returns {Element} Chart appended to new div in body.
* @example
* hydro.visualize.chart({params: {chartType: 'column', id: 'id', options: {'some options'}}, data: [data1, data2,...]});
*/
function chart({ params, args, data } = {}) {
if (typeof data[0] === "number") {
data = [Array.from(Array(data.length).keys()), data];
}
let { returnEle } = params
if (!returnEle) {
const id = params.id || "visualize";
if (!divisors.isdivAdded({ params: { id } })) {
divisors.createDiv({ params: { id: id, class: "visualize" } });
}
}
//Used to resize whenever window is smaller/bigger
var resizer;
const drawChart = () => {
//Creating a container to append the chart to.
var container,
//Data read from the parameters passed by the user.
chartType = params.chartType,
options = params.options || {},
names = params.names || [],
//To avoid having to load the entire library, the optional JS evaluator is used
//to read the requirements for drawing.
ch = eval(g[1][chartType]),
t1 = eval(g[2]["data"]),
//Temporal variable holder
columns = [],
//rearrange data into nxm from mxn
dataMatrix = stats.arrchange({ data });
if (!returnEle)
{
if (divisors.isdivAdded({ params: { id: params.id } })) {
container = document.getElementById(params.id);
}
}
//Change the way of creating charts depending on the type of chart required.
switch (chartType) {
case "scatter":
case "column":
case "combo":
case "histogram":
for (let k = 0; k < dataMatrix[0].length; k++) {
columns.push({ label: names[k] || `Value${k}`, type: "number" });
}
dataMatrix.unshift(columns);
break;
case "line":
case "timeline":
if (typeof dataMatrix[0][0] === "string") {
columns.push({ label: "Date", type: "date" });
} else {
columns.push({ label: "Value", type: "number" });
}
dataMatrix.shift();
for (let k = 1; k < dataMatrix[0].length; k++) {
columns.push({
label: names[k - 1] || `Value_item${k}`,
type: "number",
});
}
for (let i = 0; i < dataMatrix.length; i++) {
dataMatrix[i][0] = new Date(dataMatrix[i][0]);
if (dataMatrix[i][1] > 99998) {
dataMatrix[i][1] = 0;
}
}
dataMatrix.unshift(columns);
break;
default:
break;
}
const dataTableObject = google.visualization.arrayToDataTable(dataMatrix);
//Draw the chart.
const drawFunction = (chartObject) => {
options
? chartObject.draw(dataTableObject, options)
: chartObject.draw(dataTableObject);
};
if (!returnEle) {
var chartObject = new ch(container);
drawFunction(chartObject);
window.addEventListener("resize", () => {
clearTimeout(resizer);
resizer = setTimeout(() => {
drawFunction(chartObject);
}, 100);
});
return console.log(`Chart ${params.id} is drawn based on given parameters`);
}
else {
const cont = (container) => {
let localContainer = new ch(container);
drawFunction(localContainer)
window.addEventListener("resize", () => {
clearTimeout(resizer);
resizer = setTimeout(() => {
drawFunction(localContainer);
}, 100);
});
return
}
return cont
}
};
return drawChart();
}
/**
* Generates a new table for the data given by the user.
* @function table
* @memberof visualize
* @param {Object} params - contanis: id, dataType and applicable options.
* @param {Object} data - Contains: data
* @returns {Element} Table appended to new div in body.
* @example
* hydro.visualize.table({params: {id: "new", dataType: ["string", "number"]}, data: [data1, data2...]});
*/
function table({ params, args, data } = {}) {
//Verify if the visualize div has already been added into screen.
if (!divisors.isdivAdded({ params: { id: "visualize" } })) {
divisors.createDiv({ params: { id: "visualize" } });
}
const drawTable = () => {
divisors.createDiv({
params: {
id: params.id,
title: `Table of ${params.id}`,
class: "tables",
maindiv: "visualize",
},
});
//Create container for table.
var container,
//Call the data types required for table generation.
t1 = eval(g[2]["data"]),
t2 = eval(g[2]["view"]),
t3 = eval(g[2]["table"]),
//Assign data into new variables for manipulation.
types = params.datatype,
dat = new t1(),
columns = [],
tr = stats.arrchange({ data: data });
if (divisors.isdivAdded({ params: { id: params.id } })) {
container = document.getElementById(params.id);
}
for (var k = 0; k < types.length; k++) {
dat.addColumn(types[k]);
}
var tr = stats.arrchange({ data: data });
for (var l = 1; l < tr.length; l++) {
columns.push(tr[l]);
}
dat.addRows(columns);
var view = new t2(dat),
table = new t3(container);
//Draw table.
if (params.hasOwnProperty("options")) {
var options = params.options;
table.draw(view, options);
} else {
table.draw(view);
}
``;
return console.log(`Table ${params.id} drawn on the given parameters.`);
};
drawTable();
}
/**
* Preset styles for both charts and tables. The user can access by
* passing parameters of data, type(chart or table).
* @function draw
* @memberof visualize
* @param {Object} params - Contains: type (chart, table, or json), name.
* @param {Object} args - Contains: charttype (column, scatter, line, timeline) only use if drawing charts.
* @param {Object} data - Contains: data as JS nd-array.
* @returns {Element} Chart (graph, table, or json render) appended in body of HTML document.
* @example
* hydro.visualize.draw({params: {type: 'chart', name: 'someName'}, args: {charttype: 'column'}}, data: [data1, data2,...]});
*/
function draw({ params = {}, args = {}, data = [] } = {}) {
if (!window.loaded) {
(() => {
google.charts.load("current", {
packages: ["corechart", "table", "line"],
});
})();
}
window.loaded = true;
let {
type,
id = `chart-${Math.floor(Math.random() * 100)}-gen`,
name,
returnEle = false
} = params;
name === undefined ? (name = id) : name;
var dat = data,
pm;
if (type !== "json") {
//change the input in case its just a 1d array
if (typeof dat[0] === "number") {
let m = Array.from({ length: dat.length }, (_, i) => i);
dat = [m, dat];
} else {
dat = dat;
}
dat[1] = dat[1].map(Number);
}
//defaults in case the user wants to generate a chart
let { charttype = "line", names} = args;
//Chart drawing options.
if (type === "chart") {
var pm = {
chartType: charttype,
id: name,
options: {
title: name,
fontName: "monospace",
},
names: names,
returnEle
};
switch (charttype) {
case "column":
Object.assign(pm.options, {
titlePosition: "center",
width: "100%",
height: "100%",
legend: {
position: "top",
},
bar: {
groupWidth: "95%",
},
explorer: {
actions: ["dragToZoom", "rightClickToReset"],
},
});
break;
case "line":
Object.assign(pm.options, {
curveType: "function",
lineWidth: 2,
explorer: {
actions: ["dragToZoom", "rightClickToReset"],
},
legend: {
position: "bottom",
},
style: {
height: "100%",
width: "100%",
},
});
break;
case "scatter":
Object.assign(pm.options, {
legend: {
position: "bottom",
},
crosshair: {
trigger: "both",
orientation: "both",
},
});
break;
case "timeline":
Object.assign(pm, {
fontName: "monospace",
options: {
dateFormat: "HH:mm MMMM dd, yyyy",
thickness: 1,
},
});
break;
default:
break;
}
if (!returnEle) {
setTimeout(() => chart({ params: pm, data: dat }), 200);
return;
} else {
return chart({ params: pm, data: dat })
}
}
//Table options
else if (type === "table") {
var datatype = [];
dat[1][0] = "Value";
datatype.push("string");
datatype.push("number");
//Customizable chart for two columns. Will be expanded to n columns.
pm = {
id: id,
datatype: datatype,
options: {
title: id,
width: "100%",
height: "80%",
},
};
setTimeout(() => table({ params: pm, data: dat }), 200);
return;
}
//JSON options.
else if (type === "json") {
return prettyPrint({ params: params, data: data });
}
}
/**
* Returns a space in screen to visualize JSON formart objects saved in the local storage.
* Will be expanded to visualize other types of data.
* @function prettyPrint
* @memberof visualize
* @param {Object} params - Contains: input (single or all objects), type (currently only JSON)
* @param {Object} data - Contains: data as JS Objects.
* @returns {Element} Renders to screen the json object to render.
* @example
* hydro.visualize.prettyPrint({params: {input: 'all', type: 'JSON'} data: {Objects}})
*/
function prettyPrint({ params, args, data } = {}) {
//Add div for rendering JSON
if (!divisors.isdivAdded({ params: { id: "jsonrender" } })) {
divisors.createDiv({
params: {
id: "jsonrender",
class: "jsonrender",
},
});
}
//Using external library to render json on screen. Could be any type of json file.
//Documentation + library found at: https://github.com/caldwell/renderjson
var src = "https://cdn.rawgit.com/caldwell/renderjson/master/renderjson.js";
var sc = divisors.createScript({ params: { src: src, name: "jsonrender" } });
sc.addEventListener("load", () => {
//Change
renderjson.set_icons("+", "-");
renderjson.set_show_to_level(1);
if (divisors.isdivAdded({ params: { id: "jsonrender" } })) {
var name;
if (data) {
// Render the JSON object passed to the function
name = document.createTextNode(params.title || "");
document.getElementById("jsonrender").appendChild(name);
document.getElementById("jsonrender").appendChild(renderjson(data));
} else {
// Render the objects saved in local storage
if (window.localStorage.length === 0) {
return alert("No items stored!");
}
for (var i = 0; i < Object.keys(window.localStorage).length; i++) {
name = document.createTextNode(Object.keys(window.localStorage)[i]);
document.getElementById("jsonrender").appendChild(name);
document
.getElementById("jsonrender")
.appendChild(
renderjson(
JSON.parse(
window.localStorage[Object.keys(window.localStorage)[i]]
)
)
);
}
}
}
});
}
export { draw };