Commit 7e24cf65 authored by Rodrigo Tapia-McClung's avatar Rodrigo Tapia-McClung

Create and update charts with less DB requests

parent 850046ff
...@@ -2,11 +2,11 @@ ...@@ -2,11 +2,11 @@
* Copyright 2019 - All rights reserved. * Copyright 2019 - All rights reserved.
* Rodrigo Tapia-McClung * Rodrigo Tapia-McClung
* *
* August 2019 * August-September 2019
*/ */
/* globals map, userFiles, userDates, indicators, indicatorVars, indicatorsNames */ /* globals map, userFiles, userDates, indicators, indicatorVars, indicatorsNames */
/* exported makeIndicatorGraph, getData, getDataInSelection */ /* exported makeIndicatorGraph, getData, updateData, getDataInSelection */
let minDate, maxDate, xLine, yLine; let minDate, maxDate, xLine, yLine;
...@@ -16,145 +16,80 @@ const sortByDateAscending = (a, b) => { ...@@ -16,145 +16,80 @@ const sortByDateAscending = (a, b) => {
} }
//TODO: scale data to appropriate units? m2-> ha? m -> km? //TODO: scale data to appropriate units? m2-> ha? m -> km?
const getData = async (indicator) => { const getData = async () => {
//let data = [{name: `${indicator}0`, values: []}]; let queryColumns = ["sum(area) as area", "sum(perimeter) as perimeter", "avg(costa) as costa", "avg(df) as df"],
let timeParse = d3.timeParse("%B_%Y"); timeParse = d3.timeParse("%B_%Y"),
//timeFormat = d3.timeFormat("%B %Y"); data = {};
const promises = userFiles.map( async monthYear => { indicators.map( indicator => {
// get sum of area/perimeter or average of coast/fractal dimension data[indicator] = [];
let queryDB = indicator == "costa" || indicator == "df" ? data[indicator].push({name: `${indicator}0`, values: []});
`http://localhost:8090/data/query/${monthYear}?columns=avg(${indicator})%20as%20${indicator}` : });
`http://localhost:8090/data/query/${monthYear}?columns=sum(${indicator})%20as%20${indicator}`;
const userFilePromises = userFiles.map( async monthYear => {
let queryDB = `http://localhost:8090/data/query/${monthYear}?columns=${queryColumns.join(",")}`;
const dbData = await d3.json(queryDB); const dbData = await d3.json(queryDB);
return {date: timeParse(monthYear), value: dbData[0]}; return {date: timeParse(monthYear), value: dbData[0]};
}); });
const chartData = await Promise.all(promises); const chartData = await Promise.all(userFilePromises);
let data = [{name: `${indicator}0`, values: []}]; chartData.map( monthData => {
chartData.map( monthYear => { indicators.map( indicator => {
data[0].values.push({ data[indicator][0].values.push({
"date": monthYear.date, "date": monthData.date,
"value": +monthYear.value[indicator] "value": +monthData.value[indicator]
});
}); });
data.forEach(d => d.values.sort(sortByDateAscending)); // order data[i].values.date
return data; data[indicator].forEach(d => d.values.sort(sortByDateAscending));
}
// FIXME: Improve this function. Too many requests: indicators x months x drawn items...
// Find way to make one request and get all indicators per month and drawn item.
// Something like the commented function below.
const getDataInSelection = async (indicator) => {
let timeParse = d3.timeParse("%B_%Y");
let geojson = drawnItems.toGeoJSON(),
data = [];
drawnItemsPromises = geojson.features.map( async (item, i) => {
// set SRID for drawn features
item.geometry.crs = {"type":"name","properties":{"name":"EPSG:4326"}};
data.push({name: indicator + (i + 1), values: []});
let columns = indicator == "costa" || indicator == "df" ?
`avg(${indicator})%20as%20${indicator}` :`sum(${indicator})%20as%20${indicator}`;
let filter = `ST_Intersects(geom, (SELECT ST_Multi(ST_GeomFromGeoJSON('${JSON.stringify(item.geometry)}') ) ) )`
const filePpromises = userFiles.map(async monthYear => {
let queryData = `http://localhost:8090/data/query/${monthYear}?columns=${columns}&filter=${filter}&group=1%3D1`;
const selectionQuery = await d3.json(queryData);
return {date: timeParse(monthYear), value: selectionQuery[0]};
}); });
const selectionData = await Promise.all(filePpromises);
selectionData.map( itemData => {
let itemDataValue = itemData.value != undefined ? itemData.value[indicator] : 0;
data[i].values.push({date: itemData.date, value: itemDataValue });
}); });
return data; return data;
});
const drawnItemsData = await Promise.all(drawnItemsPromises);
let data2 = [];
data.map( a => data2.push(a.values)); // flatten array of data[i].values
data2 = [].concat.apply([], data2);
// insert sum of each date to first element of data array with name "column0"
data.unshift({
"name": `${indicator}0`,
"values": d3.nest()
.key( d => d.date)
// return d3.sum or d3.average depending on indicator
.rollup( v => { return indicator == "costa" || indicator == "df" ? d3.mean(v, v => v.value) : d3.sum(v, v => v.value) })
.entries(data2)
// map "key" and "value" from d3.nest().rollup() to date and value
.map( group => {
return {
date: new Date(group.key),
value: group.value
}
})
});
// order data[i].values.date
data.forEach(d => d.values.sort(sortByDateAscending));
return data;
} }
/*const getDataInSelection = async () => { const getDataInSelection = async () => {
let geojson = drawnItems.toGeoJSON(); let geojson = drawnItems.toGeoJSON(),
let queryColumns = ["sum(area) as area", "sum(perimeter) as perimeter", "avg(costa) as costa", "avg(df) as df"]; queryColumns = ["sum(area) as area", "sum(perimeter) as perimeter", "avg(costa) as costa", "avg(df) as df"],
let timeParse = d3.timeParse("%B_%Y"), timeParse = d3.timeParse("%B_%Y"),
data = {}; data = {};
indicators.map( indicator => { indicators.map( indicator => {
data[indicator] = []; data[indicator] = [];
}); });
//let promises;
// End up with data = {area: Array(0), perimeter: Array(0), length: Array(0), DI: Array(0)}
// Convert drawn items to GeoJSON and check what's inside each one of them // Convert drawn items to GeoJSON and check what's inside each one of them
drawnItems.toGeoJSON().features.map( async (item, i) => { const drawnItemPromises = drawnItems.toGeoJSON().features.map( async (item, i) => {
// set SRID fro drawn features // set SRID fro drawn features
geojson.features[i].geometry.crs = {"type":"name","properties":{"name":"EPSG:4326"}}; geojson.features[i].geometry.crs = {"type":"name","properties":{"name":"EPSG:4326"}};
indicators.map( async indicator => { indicators.map( async indicator => {
data[indicator].push({name: indicator + (i + 1), values: []}); data[indicator].push({name: indicator + (i + 1), values: []});
}); });
// End up with data = {area: [{name: "area1", values: []}], perimeter: [{name: "perimeter1", values: []}]...} // End up with data = {area: [{name: "area1", values: []}], perimeter: [{name: "perimeter1", values: []}]...}
//console.log(data)
let filter = `ST_Intersects(geom, (SELECT ST_Multi(ST_GeomFromGeoJSON('${JSON.stringify(geojson.features[i].geometry)}') ) ) )` let filter = `ST_Intersects(geom, (SELECT ST_Multi(ST_GeomFromGeoJSON('${JSON.stringify(geojson.features[i].geometry)}') ) ) )`
const promises = userFiles.map(async monthYear => { const userFilePromises = userFiles.map(async monthYear => {
let queryData = `http://localhost:8090/data/query/${monthYear}?columns=${queryColumns.join(",")}&filter=${filter}&group=1%3D1`; let queryData = `http://localhost:8090/data/query/${monthYear}?columns=${queryColumns.join(",")}&filter=${filter}&group=1%3D1`;
const selectionQuery = await d3.json(queryData); const selectionQuery = await d3.json(queryData);
return {date: timeParse(monthYear), value: selectionQuery[0]}; return {date: timeParse(monthYear), value: selectionQuery[0]};
}); });
const selectionData = await Promise.all(promises); const selectionData = await Promise.all(userFilePromises);
// let data = [{name: `${indicator}0`, values: []}];
selectionData.map( itemData => { selectionData.map( itemData => {
//console.log(itemData, i)
indicators.map( indicator => { indicators.map( indicator => {
data[indicator][i].values.push({date: itemData.date, value: itemData.value[indicator] }); let itemDataValue = itemData.value != undefined ? itemData.value[indicator] : 0;
data[indicator][i].values.push({date: itemData.date, value: itemDataValue });
}); });
}); });
}); });
const drawnItemsData = await Promise.all(drawnItemPromises);
return data; return data;
}*/ }
const summarizeData = async (allData, indicator) => { const summarizeData = (allData, indicator) => {
/*let dataFull = await allData; let dataFull = allData;
let data = dataFull[indicator]; let data = dataFull[indicator];
let data2 = []; let data2 = [];
console.log(allData, data[0]); data.map( a => data2.push(a.values)); // flatten array of data[i].values
data.map( a => { console.log(a); data2.push(a.values)}); // flatten array of data[i].values
data2 = [].concat.apply([], data2);
console.log(data2);*/
allData.then( val => {
let data = val['area'];
let data2 = [];
console.log(val, data);
data.forEach( a => { data2.push(a.values)}); // flatten array of data[i].values
data2 = [].concat.apply([], data2); data2 = [].concat.apply([], data2);
console.log(data2); // insert sum of each date to first element of data array with name "column0"
})
/*// insert sum of each date to first element of data array with name "column0"
data.unshift({ data.unshift({
"name": `${indicator}0`, "name": `${indicator}0`,
"values": d3.nest() "values": d3.nest()
...@@ -171,7 +106,7 @@ const summarizeData = async (allData, indicator) => { ...@@ -171,7 +106,7 @@ const summarizeData = async (allData, indicator) => {
}); });
// order data[i].values.date // order data[i].values.date
data.forEach(d => d.values.sort(sortByDateAscending)); data.forEach(d => d.values.sort(sortByDateAscending));
//console.log(data)*/ console.log(data)
return data; return data;
} }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* Copyright 2019 - All rights reserved. * Copyright 2019 - All rights reserved.
* Rodrigo Tapia-McClung * Rodrigo Tapia-McClung
* *
* August 2019 * August-September 2019
*/ */
/* globals omnivore, Promise, chroma, makeBaseMap, makeIndicatorGraph, getData, getDataInSelection */ /* globals omnivore, Promise, chroma, makeBaseMap, makeIndicatorGraph, getData, getDataInSelection */
...@@ -338,7 +338,44 @@ const getMinMax = table => { ...@@ -338,7 +338,44 @@ const getMinMax = table => {
}); });
} }
const populateMap = (mapData) => { const populateMap = async (mapData) => {
const chartData = await getData();
// Define charts with reusable components
indicators.map( async (indicator, index) => {
// indicatorVars[indicator].chart gives areaChart, perimeterChart, etc.
// First, make all charts with same general options
indicatorVars[indicator].chart = makeIndicatorGraph()
.width(Math.floor($(indicatorVars[indicator].container)[0].offsetParent.offsetWidth * 0.95))
.height(Math.floor($(indicatorVars[indicator].container)[0].offsetParent.offsetHeight * 0.95))
.lineVariables([`${indicator}0`]) // d.area is used to draw stuff on y axis
.displayName("date") // d.date is used to draw stuff on x axis
.lineAxisLabel("(m)")
.id(indicator)
.xAxisFormat(d3.timeFormat("%b '%y")) // label x axis as mm 'yy
.yAxisFormat(d3.format(indicatorsxAxisFormat[index])) // yAxis label in SI or other
// Then, specify some parameters for each chart
.tooltipUnits(indicatorsUnits[index])
.tooltipFormat(d3.format(indicatorsxAxisFormat[index])) // tooltip format in SI or other
.title(indicatorsUnits[index] == "" ? indicatorsNames[index] :
`${indicatorsNames[index]} (${indicatorsUnits[index]})`);
// Finally, set chart data with async function calling stuff from DB
indicatorVars[indicator].chart.data( chartData[indicator]);
// create chart with passed options
d3.select(indicatorVars[indicator].container)
.call(indicatorVars[indicator].chart);
// Reload chart data and force chart update
indicatorVars[indicator].chartData = indicatorVars[indicator].chart.data(); // get chart data
indicatorVars[indicator].chart.data(indicatorVars[indicator].chartData); // set chart data
// Highlight plot title according to selected option
let option = $("#indicatorSelect").val(); // option selected from dropdrown
d3.select(indicatorVars[option].container).select("svg text.title").classed("active", true);
});
let map = mapData.map, let map = mapData.map,
minIndicators = mapData.minIndicators, minIndicators = mapData.minIndicators,
maxIndicators = mapData.maxIndicators; maxIndicators = mapData.maxIndicators;
...@@ -411,39 +448,6 @@ const populateMap = (mapData) => { ...@@ -411,39 +448,6 @@ const populateMap = (mapData) => {
layerControl = L.control.layers(baseLayers, overlays).addTo(map); layerControl = L.control.layers(baseLayers, overlays).addTo(map);
makeBaseMap(); // basemap.js makeBaseMap(); // basemap.js
}); });
// Define charts with reusable components
indicators.map( async (indicator, index) => {
// indicatorVars[indicator].chart gives areaChart, perimeterChart, etc.
// First, make all charts with same general options
indicatorVars[indicator].chart = makeIndicatorGraph()
.width(Math.floor($(indicatorVars[indicator].container)[0].offsetParent.offsetWidth * 0.95))
.height(Math.floor($(indicatorVars[indicator].container)[0].offsetParent.offsetHeight * 0.95))
.lineVariables([`${indicator}0`]) // d.area is used to draw stuff on y axis
.displayName("date") // d.date is used to draw stuff on x axis
.lineAxisLabel("(m)")
.id(indicator)
.xAxisFormat(d3.timeFormat("%b '%y")) // label x axis as mm 'yy
.yAxisFormat(d3.format(indicatorsxAxisFormat[index])) // yAxis label in SI or other
// Then, specify some parameters for each chart
.tooltipUnits(indicatorsUnits[index])
.tooltipFormat(d3.format(indicatorsxAxisFormat[index])) // tooltip format in SI or other
.title(indicatorsUnits[index] == "" ? indicatorsNames[index] :
`${indicatorsNames[index]} (${indicatorsUnits[index]})`);
// Finally, set chart data with async function calling stuff from DB
indicatorVars[indicator].chart.data( await getData(indicator));
// create chart with passed options
d3.select(indicatorVars[indicator].container)
.call(indicatorVars[indicator].chart);
// Reload chart data and force chart update
indicatorVars[indicator].chartData = indicatorVars[indicator].chart.data(); // get chart data
indicatorVars[indicator].chart.data(indicatorVars[indicator].chartData); // set chart data
// Highlight plot title according to selected option
let option = $("#indicatorSelect").val(); // option selected from dropdrown
d3.select(indicatorVars[option].container).select("svg text.title").classed("active", true);
});
} }
const updateMap = (mapData) => { const updateMap = (mapData) => {
...@@ -500,19 +504,20 @@ const updateMap = (mapData) => { ...@@ -500,19 +504,20 @@ const updateMap = (mapData) => {
updateCharts(); updateCharts();
} }
const updateCharts = () => { const updateCharts = async () => {
// TODO: add mask while fetching async data // TODO: add mask while fetching async data
// if user has drawn polygons, update chart with data inside selection // if user has drawn polygons, update chart with data inside selection
if (drawnItems.toGeoJSON().features.length != 0) { if (drawnItems.toGeoJSON().features.length != 0) {
let allData = await getDataInSelection();
indicators.map( async indicator => { indicators.map( async indicator => {
//indicatorVars[indicator].chart.data( await summarizeData(await getDataInSelection(), indicator) ); indicatorVars[indicator].chart.data( summarizeData(allData, indicator) );
indicatorVars[indicator].chart.data( await getDataInSelection(indicator) );
}); });
} else { } else {
// otherwise use all data // otherwise use all data
let allData = await getData();
indicators.map( async indicator => { indicators.map( async indicator => {
indicatorVars[indicator].chart.data( await getData(indicator)); indicatorVars[indicator].chart.data( allData[indicator]);
}); });
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment