|
@@ -1,3 +1,54 @@
|
|
|
|
|
+const ai = new Vue({
|
|
|
|
|
+ el: "#ai",
|
|
|
|
|
+ data: {
|
|
|
|
|
+ error: "",
|
|
|
|
|
+ forecast: [],
|
|
|
|
|
+ max: undefined,
|
|
|
|
|
+ min: undefined,
|
|
|
|
|
+ net: new brain.recurrent.LSTMTimeStep(),
|
|
|
|
|
+ trained: false,
|
|
|
|
|
+ training: false
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ predict: (values) => {
|
|
|
|
|
+ final = [];
|
|
|
|
|
+ for (value of ai.net.forecast(values, 30)) {
|
|
|
|
|
+ final.push(value * (ai.max - ai.min) + ai.min);
|
|
|
|
|
+ };
|
|
|
|
|
+ ai.forecast = final;
|
|
|
|
|
+ },
|
|
|
|
|
+ train: (data) => {
|
|
|
|
|
+ values = data.map((d) => { return d.value });
|
|
|
|
|
+ trainData = [];
|
|
|
|
|
+ ai.max = Math.max(...values);
|
|
|
|
|
+ ai.min = Math.min(...values);
|
|
|
|
|
+
|
|
|
|
|
+ for (value of values) {
|
|
|
|
|
+ trainData.push( (value - ai.min) / (ai.max - ai.min) );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!ai.training && !ai.trained) {
|
|
|
|
|
+ ai.error = "";
|
|
|
|
|
+ ai.training = true;
|
|
|
|
|
+
|
|
|
|
|
+ setTimeout(
|
|
|
|
|
+ () => {
|
|
|
|
|
+ ai.net.train([ trainData ], { iterations: 5 });
|
|
|
|
|
+ ai.training = false;
|
|
|
|
|
+ ai.trained = true;
|
|
|
|
|
+ document.getElementById("network").innerHTML = brain.utilities.toSVG(
|
|
|
|
|
+ ai.net, { height: 500 }
|
|
|
|
|
+ );
|
|
|
|
|
+ ai.predict(values);
|
|
|
|
|
+ }, 250
|
|
|
|
|
+ );
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ai.predict(values);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
const graph = new Vue({
|
|
const graph = new Vue({
|
|
|
el: "#graph",
|
|
el: "#graph",
|
|
|
data: {
|
|
data: {
|
|
@@ -27,6 +78,8 @@ const graph = new Vue({
|
|
|
dates.push(last + 60000);
|
|
dates.push(last + 60000);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ ai.train(sorted);
|
|
|
|
|
+
|
|
|
d3.select("#data").select("g").remove();
|
|
d3.select("#data").select("g").remove();
|
|
|
|
|
|
|
|
element = document.querySelector("#chart-space");
|
|
element = document.querySelector("#chart-space");
|
|
@@ -85,6 +138,28 @@ const graph = new Vue({
|
|
|
return y(d.value);
|
|
return y(d.value);
|
|
|
}));
|
|
}));
|
|
|
|
|
|
|
|
|
|
+ if (ai.forecast.length > 0) {
|
|
|
|
|
+ forecast = [];
|
|
|
|
|
+ position = 30;
|
|
|
|
|
+ for (value of ai.forecast) {
|
|
|
|
|
+ forecast.push({
|
|
|
|
|
+ date: dates[dates.length - position],
|
|
|
|
|
+ value: value
|
|
|
|
|
+ });
|
|
|
|
|
+ position--;
|
|
|
|
|
+ }
|
|
|
|
|
+ svg.append("path")
|
|
|
|
|
+ .datum(forecast)
|
|
|
|
|
+ .attr("fill", "none")
|
|
|
|
|
+ .attr("stroke", "green")
|
|
|
|
|
+ .attr("stroke-width", 1.5)
|
|
|
|
|
+ .attr("d", d3.line().x((d) => {
|
|
|
|
|
+ return x(d.date);
|
|
|
|
|
+ }).y((d) => {
|
|
|
|
|
+ return y(d.value);
|
|
|
|
|
+ }));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|