diff --git a/web/app.html b/web/app.html
index 9d9685a..e01c577 100644
--- a/web/app.html
+++ b/web/app.html
@@ -32,4 +32,8 @@
+
+
+
+
diff --git a/web/app.js b/web/app.js
index 69a3d8e..f00e7d4 100644
--- a/web/app.js
+++ b/web/app.js
@@ -14,312 +14,11 @@ const width = window.innerWidth - margin.right - margin.left -10;
// url
const currentUrl = window.location.href;
-// global delete key
-var del_key = null;
-
// reload page when window is resized
// (resizes every elements)
window.onresize = function(){ location.reload(); }
-async function fetch_history() {
- try {
- const response = await fetch(`${currentUrl}app/datahistory`);
- const rawData = await response.json();
- var dateFormat = d3.timeParse("%a, %d %b %Y %H:%M:%S GMT");
- // SELECT uuid, quantity, discount_percentage, price, currency, h_timestamp
- let historyData = rawData.map(d => ({
- uuid: d[0],
- value: parseFloat(d[3].replace('$', '')),
- currency: d[4],
- date: dateFormat(d[5]),
- }));
- return historyData;
- } catch (error) {
- console.error('Error fetching data history: ', error);
- throw error;
- }
-}
-
-async function fetch_item() {
- try {
- // SELECT uuid, itemid, skuid, choice, attributes, image
- const response = await fetch(`${currentUrl}app/dataitem`);
- const rawData = await response.json();
- const items = rawData.reduce((item, row) => {
- const uuid = row[0];
-
- const itemid = row[1];
- const skuid = row[2];
- const choice = row[3];
- const attributes = row[4];
- const image = row[5];
-
- const values = {
- itemid: itemid,
- skuid: skuid,
- choice: choice,
- attributes: attributes,
- image: image,
- show: true
- };
-
- item[uuid] = values;
-
- return item;
- }, {});
-
- return items;
- } catch (error) {
- console.error('Error fetching data item: ', error);
- throw error;
- }
-}
-
-async function get_data() {
- items_data = await fetch_item();
- history_data = await fetch_history();
- // console.log(items_data)
- // console.log(history_data)
- return Array(items_data, history_data);
-}
-
-
-function render_graphs(items, history) {
-
- // by default, do not show hidden items
- var show_hidden = false;
-
- // Date domain (width)
- const x = d3.scaleTime()
- .domain(d3.extent(history, d => d.date))
- .range([3, width-3]);
-
- // Group the data by (uuid)
- const nestedData = d3.group(history, d => d.uuid);
- // Create a div for each graph
- const graphDivs = d3.select("#graphs")
- .selectAll(".graph-container")
- .data(nestedData.keys())
- .enter().append("div")
- .attr("class", "graph-container");
-
- // Append SVG elements to each div
- const svgs = graphDivs.append("svg")
- .attr("width", width + margin.left + margin.right)
- .attr("height", height + margin.top + margin.bottom)
- .append("g")
- .attr('viewBox', `0 0 ${width} ${height}`)
- .attr("transform", `translate(${margin.left},${margin.top})`);
-
- var dict_xAxis = {};
- var dict_scatter = {};
-
- // plot for each graph
- svgs.each(function(key) {
- if (show_hidden || items[key].show) {
- const dataSubset = nestedData.get(key);
- const svg = d3.select(this);
-
- // context on right side
- const link = `https://fr.aliexpress.com/item/${items[key].itemid}.html`;
- // image
- svg.append("image")
- .attr("x", width + margin.right*0.1)
- .attr("y", height*0.1)
- .attr("width", height*0.8)
- .attr("height", height*0.8)
- .attr("xlink:href", items[key].image)
- .on("click", function() {
- window.open(link, '_blank', 'noopener,noreferrer');
- });
-
- // BUTTONS FOR EACH GRAPH
- // buttons to hide or show item
- g_div = this.parentNode.parentNode;
- const hide_button = document.createElement("button");
- hide_button.innerHTML = "hide";
- hide_button.style.position = "relative";
- hide_button.style.top = `-${height+margin.bottom}px`;
- hide_button.style.left = `${margin.left+20}px`;
- g_div.append(hide_button);
- // button to delete item
- const del_button = document.createElement("button");
- del_button.innerHTML = "delete";
- del_button.style.position = "relative";
- del_button.style.top = `-${height+margin.bottom}px`;
- del_button.style.left = `${margin.left+30}px`;
- del_button.addEventListener("click", function () {
- confirmationPopup.style.display = 'flex';
- del_key = key;
- });
- g_div.append(del_button);
-
-
- // Price domain (height)
- const y = d3.scaleLinear()
- .domain([d3.min(dataSubset, d => d.value) - 1, d3.max(dataSubset, d => d.value) + 1])
- .range([height, 0]);
-
- // height axis
- dict_xAxis[key] = svg.append("g")
- .attr("transform", `translate(0, ${height})`)
- .call(d3.axisBottom(x));
-
- // width axis
- svg.append("g")
- .call(d3.axisLeft(y));
-
-
- // clipPath, area drawn
- const clip_line = svg.append("defs").append("svg:clipPath")
- .attr("id", "clip_line")
- .append("svg:rect")
- .attr("width", width )
- .attr("height", height )
- .attr("x", 0)
- .attr("y", 0);
- const clip_dot = svg.append("defs").append("svg:clipPath")
- .attr("id", "clip_dot")
- .append("svg:rect")
- .attr("width", width+10 )
- .attr("height", height )
- .attr("x", -5)
- .attr("y", 0);
-
- // lines between dots
- // plotted before dots to stay below them
- svg.append("path")
- .datum(dataSubset)
- .attr("id", "line_path")
- .attr("clip-path", "url(#clip_line)") // bind to clipPath
- .attr("fill", "none")
- .attr("stroke", "black")
- .attr("stroke-width", 1.5)
- .attr("d", d3.line()
- .x(d => x(d.date))
- .y(d => y(d.value))
- );
-
- // brushing
- const brush = d3.brushX()
- .extent( [ [0,0], [width,height] ] ) // select whole graph
- .on("end", updateChart) // update graph on brush release
-
- // line, area brushed
- const line = svg.append('g')
- .attr("clip-path", "url(#clip_line)")
-
- // Add brushing
- line.append("g")
- .attr("class", "brush")
- .call(brush);
-
- let idleTimeout
- function idled() { idleTimeout = null; }
-
- // update graph on zoom brush release
- function updateChart(event,d) {
- extent = event.selection
- if(!extent){
- if (!idleTimeout) return idleTimeout = setTimeout(idled, 350);
- }else{
- x.domain([ x.invert(extent[0]), x.invert(extent[1]) ]) // new domain is brushing area
- line.select(".brush").call(brush.move, null) // remove brushing area
- }
- // Update axis
- dict_xAxis[key].transition().duration(1000).call(d3.axisBottom(x))
- // Update lines
- svg.selectAll("#line_path")
- .transition()
- .duration(1000)
- .attr("d", d3.line()
- .x(d => x(d.date))
- .y(d => y(d.value))
- );
- // Update dots
- svg.selectAll("circle")
- .data(dataSubset)
- .transition()
- .duration(1000)
- .attr("cx", d => x(d.date))
- }
-
- // reset zoom on double click
- svg.on("dblclick",function(){
- x.domain(d3.extent(history, function(d) { return d.date; }))
- // Axis
- dict_xAxis[key].transition().call(d3.axisBottom(x))
- // Lines
- svg.selectAll("#line_path")
- .transition()
- .duration(1000)
- .attr("d", d3.line()
- .x(d => x(d.date))
- .y(d => y(d.value))
- );
- // Dots
- svg.selectAll("circle")
- .data(dataSubset)
- .transition()
- .duration(1000)
- .attr("cx", d => x(d.date))
- });
-
- // TOOLTIP
- // tooltip: box with node value
- const Tooltip = d3.select(this.parentNode.parentNode).append("div")
- .style("opacity", 0)
- .attr("class", "tooltip")
- .style("background-color", "white")
- .style("border", "solid")
- .style("border-width", "2px")
- .style("border-radius", "5px")
- .style("padding", "5px")
- .style("position", "absolute");
-
- // Interaction functions
- const mouseover = function(event, d) {
- var formatted_date = d.date.toISOString().split('T')[0]
- Tooltip
- .style("opacity", 1)
- .html(formatted_date + "
" + d.value + " " + d.currency)
- .style("left", (event.pageX + 10) + "px")
- .style("top", (event.pageY + 10) + "px");
- };
-
- const mousemove = function(event) {
- Tooltip
- .style("left", (event.pageX + 10) + "px")
- .style("top", (event.pageY + 10) + "px");
- };
-
- const mouseleave = function() {
- Tooltip.style("opacity", 0)
- .style("left", 0 + "px")
- .style("top", 0 + "px"); // send tooltip outside of brushing area
- };
-
- // points plot
- dict_scatter[key] = svg.append("g")
- .selectAll("circle")
- .data(dataSubset)
- .enter()
- .append("circle")
- .attr("clip-path", "url(#clip_dot)") // bind to clipPath
- .attr("cx", d => x(d.date))
- .attr("cy", d => y(d.value))
- .attr("r", 5)
- .attr("stroke", "#69b3a2")
- .attr("stroke-width", 3)
- .attr("fill", "white")
- .on("mouseover", mouseover)
- .on("mousemove", mousemove)
- .on("mouseleave", mouseleave);
- }
- });
-
-}
function render_graphs_wrapper() {
get_data().then(function(data){
@@ -409,7 +108,7 @@ document.addEventListener('DOMContentLoaded', function () {
const yesBtn = document.getElementById('yesBtn');
const noBtn = document.getElementById('noBtn');
yesBtn.addEventListener('click', function () {
- delItem(del_key);
+ delItem(confirmationPopup.style.name);
confirmationPopup.style.display = 'none';
});
noBtn.addEventListener('click', function () {
diff --git a/web/fetch.js b/web/fetch.js
new file mode 100644
index 0000000..018a6cf
--- /dev/null
+++ b/web/fetch.js
@@ -0,0 +1,61 @@
+async function fetch_history() {
+ try {
+ const response = await fetch(`${currentUrl}app/datahistory`);
+ const rawData = await response.json();
+ var dateFormat = d3.timeParse("%a, %d %b %Y %H:%M:%S GMT");
+ // SELECT uuid, quantity, discount_percentage, price, currency, h_timestamp
+ let historyData = rawData.map(d => ({
+ uuid: d[0],
+ value: parseFloat(d[3].replace('$', '')),
+ currency: d[4],
+ date: dateFormat(d[5]),
+ }));
+ return historyData;
+ } catch (error) {
+ console.error('Error fetching data history: ', error);
+ throw error;
+ }
+}
+
+async function fetch_item() {
+ try {
+ // SELECT uuid, itemid, skuid, choice, attributes, image
+ const response = await fetch(`${currentUrl}app/dataitem`);
+ const rawData = await response.json();
+ const items = rawData.reduce((item, row) => {
+ const uuid = row[0];
+
+ const itemid = row[1];
+ const skuid = row[2];
+ const choice = row[3];
+ const attributes = row[4];
+ const image = row[5];
+
+ const values = {
+ itemid: itemid,
+ skuid: skuid,
+ choice: choice,
+ attributes: attributes,
+ image: image,
+ show: true
+ };
+
+ item[uuid] = values;
+
+ return item;
+ }, {});
+
+ return items;
+ } catch (error) {
+ console.error('Error fetching data item: ', error);
+ throw error;
+ }
+}
+
+async function get_data() {
+ items_data = await fetch_item();
+ history_data = await fetch_history();
+ // console.log(items_data)
+ // console.log(history_data)
+ return Array(items_data, history_data);
+}
diff --git a/web/rendergraphs.js b/web/rendergraphs.js
new file mode 100644
index 0000000..d862f44
--- /dev/null
+++ b/web/rendergraphs.js
@@ -0,0 +1,235 @@
+function render_graphs(items, history) {
+
+ // by default, do not show hidden items
+ var show_hidden = false;
+
+ // Date domain (width)
+ const x = d3.scaleTime()
+ .domain(d3.extent(history, d => d.date))
+ .range([3, width-3]);
+
+ // Group the data by (uuid)
+ const nestedData = d3.group(history, d => d.uuid);
+ // Create a div for each graph
+ const graphDivs = d3.select("#graphs")
+ .selectAll(".graph-container")
+ .data(nestedData.keys())
+ .enter().append("div")
+ .attr("class", "graph-container");
+
+ // Append SVG elements to each div
+ const svgs = graphDivs.append("svg")
+ .attr("width", width + margin.left + margin.right)
+ .attr("height", height + margin.top + margin.bottom)
+ .append("g")
+ .attr('viewBox', `0 0 ${width} ${height}`)
+ .attr("transform", `translate(${margin.left},${margin.top})`);
+
+ var dict_xAxis = {};
+ var dict_scatter = {};
+
+ // plot for each graph
+ svgs.each(function(key) {
+ if (show_hidden || items[key].show) {
+ const dataSubset = nestedData.get(key);
+ const svg = d3.select(this);
+
+ // context on right side
+ const link = `https://fr.aliexpress.com/item/${items[key].itemid}.html`;
+ // image
+ svg.append("image")
+ .attr("x", width + margin.right*0.1)
+ .attr("y", height*0.1)
+ .attr("width", height*0.8)
+ .attr("height", height*0.8)
+ .attr("xlink:href", items[key].image)
+ .on("click", function() {
+ window.open(link, '_blank', 'noopener,noreferrer');
+ });
+
+ // BUTTONS FOR EACH GRAPH
+ // buttons to hide or show item
+ g_div = this.parentNode.parentNode;
+ const hide_button = document.createElement("button");
+ hide_button.innerHTML = "hide";
+ hide_button.style.position = "relative";
+ hide_button.style.top = `-${height+margin.bottom}px`;
+ hide_button.style.left = `${margin.left+20}px`;
+ g_div.append(hide_button);
+ // button to delete item
+ const del_button = document.createElement("button");
+ del_button.innerHTML = "delete";
+ del_button.style.position = "relative";
+ del_button.style.top = `-${height+margin.bottom}px`;
+ del_button.style.left = `${margin.left+30}px`;
+ del_button.addEventListener("click", function () {
+ confirmationPopup.style.name = key;
+ confirmationPopup.style.display = 'flex';
+ });
+ g_div.append(del_button);
+
+
+ // Price domain (height)
+ const y = d3.scaleLinear()
+ .domain([d3.min(dataSubset, d => d.value) - 1, d3.max(dataSubset, d => d.value) + 1])
+ .range([height, 0]);
+
+ // height axis
+ dict_xAxis[key] = svg.append("g")
+ .attr("transform", `translate(0, ${height})`)
+ .call(d3.axisBottom(x));
+
+ // width axis
+ svg.append("g")
+ .call(d3.axisLeft(y));
+
+
+ // clipPath, area drawn
+ const clip_line = svg.append("defs").append("svg:clipPath")
+ .attr("id", "clip_line")
+ .append("svg:rect")
+ .attr("width", width )
+ .attr("height", height )
+ .attr("x", 0)
+ .attr("y", 0);
+ const clip_dot = svg.append("defs").append("svg:clipPath")
+ .attr("id", "clip_dot")
+ .append("svg:rect")
+ .attr("width", width+10 )
+ .attr("height", height )
+ .attr("x", -5)
+ .attr("y", 0);
+
+ // lines between dots
+ // plotted before dots to stay below them
+ svg.append("path")
+ .datum(dataSubset)
+ .attr("id", "line_path")
+ .attr("clip-path", "url(#clip_line)") // bind to clipPath
+ .attr("fill", "none")
+ .attr("stroke", "black")
+ .attr("stroke-width", 1.5)
+ .attr("d", d3.line()
+ .x(d => x(d.date))
+ .y(d => y(d.value))
+ );
+
+ // brushing
+ const brush = d3.brushX()
+ .extent( [ [0,0], [width,height] ] ) // select whole graph
+ .on("end", updateChart) // update graph on brush release
+
+ // line, area brushed
+ const line = svg.append('g')
+ .attr("clip-path", "url(#clip_line)")
+
+ // Add brushing
+ line.append("g")
+ .attr("class", "brush")
+ .call(brush);
+
+ let idleTimeout
+ function idled() { idleTimeout = null; }
+
+ // update graph on zoom brush release
+ function updateChart(event,d) {
+ extent = event.selection
+ if(!extent){
+ if (!idleTimeout) return idleTimeout = setTimeout(idled, 350);
+ }else{
+ x.domain([ x.invert(extent[0]), x.invert(extent[1]) ]) // new domain is brushing area
+ line.select(".brush").call(brush.move, null) // remove brushing area
+ }
+ // Update axis
+ dict_xAxis[key].transition().duration(1000).call(d3.axisBottom(x))
+ // Update lines
+ svg.selectAll("#line_path")
+ .transition()
+ .duration(1000)
+ .attr("d", d3.line()
+ .x(d => x(d.date))
+ .y(d => y(d.value))
+ );
+ // Update dots
+ svg.selectAll("circle")
+ .data(dataSubset)
+ .transition()
+ .duration(1000)
+ .attr("cx", d => x(d.date))
+ }
+
+ // reset zoom on double click
+ svg.on("dblclick",function(){
+ x.domain(d3.extent(history, function(d) { return d.date; }))
+ // Axis
+ dict_xAxis[key].transition().call(d3.axisBottom(x))
+ // Lines
+ svg.selectAll("#line_path")
+ .transition()
+ .duration(1000)
+ .attr("d", d3.line()
+ .x(d => x(d.date))
+ .y(d => y(d.value))
+ );
+ // Dots
+ svg.selectAll("circle")
+ .data(dataSubset)
+ .transition()
+ .duration(1000)
+ .attr("cx", d => x(d.date))
+ });
+
+ // TOOLTIP
+ // tooltip: box with node value
+ const Tooltip = d3.select(this.parentNode.parentNode).append("div")
+ .style("opacity", 0)
+ .attr("class", "tooltip")
+ .style("background-color", "white")
+ .style("border", "solid")
+ .style("border-width", "2px")
+ .style("border-radius", "5px")
+ .style("padding", "5px")
+ .style("position", "absolute");
+
+ // Interaction functions
+ const mouseover = function(event, d) {
+ var formatted_date = d.date.toISOString().split('T')[0]
+ Tooltip
+ .style("opacity", 1)
+ .html(formatted_date + "
" + d.value + " " + d.currency)
+ .style("left", (event.pageX + 10) + "px")
+ .style("top", (event.pageY + 10) + "px");
+ };
+
+ const mousemove = function(event) {
+ Tooltip
+ .style("left", (event.pageX + 10) + "px")
+ .style("top", (event.pageY + 10) + "px");
+ };
+
+ const mouseleave = function() {
+ Tooltip.style("opacity", 0)
+ .style("left", 0 + "px")
+ .style("top", 0 + "px"); // send tooltip outside of brushing area
+ };
+
+ // points plot
+ dict_scatter[key] = svg.append("g")
+ .selectAll("circle")
+ .data(dataSubset)
+ .enter()
+ .append("circle")
+ .attr("clip-path", "url(#clip_dot)") // bind to clipPath
+ .attr("cx", d => x(d.date))
+ .attr("cy", d => y(d.value))
+ .attr("r", 5)
+ .attr("stroke", "#69b3a2")
+ .attr("stroke-width", 3)
+ .attr("fill", "white")
+ .on("mouseover", mouseover)
+ .on("mousemove", mousemove)
+ .on("mouseleave", mouseleave);
+ }
+ });
+
+}