Commit fe147516 authored by Rodrigo Tapia-McClung's avatar Rodrigo Tapia-McClung

Added charts; working when changing current date, not on load...

parent ca26ff5f
......@@ -22,8 +22,10 @@
<!--<link rel="stylesheet" href="./css/map.css" type="text/css">-->
</head>
<body>
<img class="img_bg" src="../img/sumidero2.jpg" alt="Ca&ntilde;&oacute;n del Sumidero">
<span class="photo-credit">Foto: Gabriela L&oacute;pez</span>
<div id="initial-backdrop">
<img class="img_bg" src="../img/sumidero2.jpg" alt="Ca&ntilde;&oacute;n del Sumidero">
<span class="photo-credit">Foto: Gabriela L&oacute;pez</span>
</div>
<div class="container" id="startHeader">
<div class="jumbotron mt-5" style="background-color: rgba(233,236,239,0.75); opacity: 0.99;">
<h1 class="display-4">Contenido de agua en la cuenca del r&iacute;o Grijalva</h1>
......@@ -135,17 +137,6 @@
</div>
</div>
<!--
<div id='mapmex'></div>
<div class="picker">
<select id="indicatorSelect"></select>
</div>
<div id="datePickers">
<input type="text" name="date-initial" id="date-initial" readonly="readonly" size="12"
placeholder="Fecha inicial" data-calendar="false" />
<input type="text" name="date-final" id="date-final" readonly="readonly" size="12"
placeholder="Fecha final" data-calendar="true" />
</div>-->
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
......@@ -167,6 +158,7 @@
<script src="https://npmcdn.com/@turf/turf/turf.min.js"></script>
<script src="../js/grijalva_functions.js"></script>
<script src="../js/grijalva_charts.js"></script>
<!--<script>
......
/*
* Copyright 2019 - All rights reserved.
* Rodrigo Tapia-McClung
*
* August 2019
*/
/* globals flatten, map, userFiles, userDates, indicators, indicatorVars, indicatorsNames */
/* exported makeIndicatorGraph, getData, queryIndicators */
let minDate, maxDate, xLine, yLine;
const sortByDateAscending = (a, b) => {
// Dates will be cast to numbers automagically:
return a.date - b.date;
}
const queryIndicators = () => {
return Promise.all(indicators.map(queryMonths))
// the result is an array of arrays, so we'll flatten them here
.then(flatten);
}
const queryMonths = (indicator) => {
return Promise.all(userFiles.map( monthYear => {
return getData(monthYear, indicator);
}));
}
//queryIndicators(queryMonths).then( results => {...} )
const getData = (monthYear, indicator) => {
return new Promise( resolve => {
//let data = [{name: `${indicator}0`, values: []}];
let timeParse = d3.timeParse("%B_%Y");
//timeFormat = d3.timeFormat("%B %Y");
// get sum of area/perimeter or average of coast/fractal dimension
let queryDB = indicator == "costa" || indicator == "df" ?
`http://localhost:8090/data/query/${monthYear}?columns=avg(${indicator})%20as%20${indicator}` :
`http://localhost:8090/data/query/${monthYear}?columns=sum(${indicator})%20as%20${indicator}`;
d3.json(queryDB).then( result => {
/*data[0].values.push({
"date": timeParse(monthYear),
"value": +result[0][indicator]
});
data.forEach(d => d.values.sort(sortByDateAscending));
resolve(data[0])*/
resolve({date: timeParse(monthYear), value: result[0][indicator]})
});
});
}
//TODO: scale data to appropriate units? m2-> ha? m -> km?
const getData2 = async (indicator) => {
let data = [{name: `${indicator}0`, values: []}];
let timeParse = d3.timeParse("%B_%Y");
//timeFormat = d3.timeFormat("%B %Y");
userFiles.map( async monthYear => {
// get sum of area/perimeter or average of coast/fractal dimension
let queryDB = indicator == "costa" || indicator == "df" ?
`http://localhost:8090/data/query/${monthYear}?columns=avg(${indicator})%20as%20${indicator}` :
`http://localhost:8090/data/query/${monthYear}?columns=sum(${indicator})%20as%20${indicator}`;
/*d3.json(queryDB).then( result => {
data[0].values.push({
"date": timeParse(monthYear),
"value": +result[0][indicator]
});
data.forEach(d => d.values.sort(sortByDateAscending));
});*/
let dbData = await d3.json(queryDB);
data[0].values.push({
"date": timeParse(monthYear),
"value": await +dbData[0][indicator]
});
data.forEach(d => d.values.sort(sortByDateAscending));
})
return data;
}
function makeIndicatorGraph() {
let data = [],
width = 450,
height = 400,
padding = 30,
margin = {top: 5, right: 10, bottom: 100, left: 35},
id, // variable in data to use as identifier
lineVariables = null, // list of variables to display as lines
displayName, // variable in data to use as x axis labels
transitionTime = 500,
//lineColor = "#17678E",
//pointColor = "#17678E",
defaultLineColor = "CadetBlue",
defaultPointColor = "CadetBlue",
lineColors = d3.scaleOrdinal(d3.schemeCategory10),
pointColors = d3.scaleOrdinal(d3.schemeCategory10),
lineAxisLabel = "",
xAxisFormat = d3.timeFormat("%b '%y"), // mm 'yy
yAxisFormat = d3.format(".2s"), // SI format with 2 significant numbers
//rightAxisFormat = ".0%", // rounded percentage
legend = false,
title = "",
tooltipUnits = "",
tooltipFormat = "",
//legend = {title: "title", translateX: 100, translateY: 0, items:["item1","item2"]}
legendContainer = "legendZone",
updateData;
function chart(selection) {
console.log("data values: ", data[0].values) // FIXME: get async data here...
// get data ranges using values from displayName
minDate = d3.min(data[0].values, d => {
return d[displayName];
});
minDate = d3.timeDay.offset(minDate, -15) // get previous month to get correct x-axis alignment
maxDate = d3.max(data[0], d => {
return d[displayName];
});
maxDate = d3.timeDay.offset(maxDate, 15) // get next month to get correct x-axis alignment
console.log("minDate: ", minDate)
selection.each(function () {
// add graph svg to selected container on webpage
let areaSVG = selection
.append("svg")
// don't set height/width but use viewBox so charts get resized
// if they are within a container that can be resized (also scales text)
.attr("viewBox", `0 0 ${width} ${height}`)
//.attr("preserveAspectRatio", "xMinYMin meet"),
.attr("preserveAspectRatio", "xMidYMid meet"),
g = areaSVG.append("g")
.attr("class", "lineChart")
.attr("transform", `translate(${margin.left}, ${margin.top})`);
// add x axis
g.append("g")
.attr("class", "x axis")
.attr("transform", `translate(30, ${height - margin.left})`);
// add x axis for ticks
g.append("g")
.attr("class", "x axis-ticks")
.attr("transform", `translate(30, ${height - margin.left})`);
// add x axis for month labels
g.append("g")
.attr("class", "x axis-month-labels")
.attr("transform", `translate(30, ${height - margin.left})`);
// add x axis for year labels
g.append("g")
.attr("class", "x axis-year-labels")
.attr("transform", `translate(30, ${height - margin.left + 12})`);
//add left y axis
g.append("g")
.attr("class", "y axis")
.attr("transform", "translate(30,0)");
//label y axes
areaSVG.append("text")
.attr("class", "label")
.attr("text-anchor", "middle") // this makes it easy to centre the text as the transform is applied to the anchor
.attr("transform", `translate(${padding / 3}, ${height / 2}) rotate(-90)`) // text is drawn off the screen top left, move down and out and rotate
.attr("fill", "#b2b2b2");
//.text("Superficie inundada");
// append line container
g.append("g")
.attr("class", "lines")
.attr("transform", "translate(30, 0)");
// append circle container
g.append("g")
.attr("class", "circles")
.attr("transform", "translate(30, 0)");
// add chart title container
areaSVG.append("text")
.attr("class", "title label")
.attr("text-anchor", "middle") // this makes it easy to centre the text as the transform is applied to the anchor
.attr("transform", `translate(${width / 2}, ${margin.top * 4})`); // text is drawn off the screen top left, move down and out and rotate
// add the tooltip area to the webpage
let tooltip;
if (d3.select("div.tooltip").empty()) { // check if no tooltip div exists and add if necessary
tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
}
// define axes with ranges
xLine = d3.scaleTime().range([0, width - margin.left - margin.right - 30]).clamp(true);
yLine = d3.scaleLinear().range([height - margin.left - margin.right, 0 + 35])
// scale data range
xLine.domain([minDate, maxDate]).nice();
//yLine.domain(d3.extent(data, function(d) { return d[lineVariables[0]]; })).nice();
//yLine.domain([0, d3.max(data, function(d) { return d[lineVariables[0]]; })]).nice();
// Calculate min and max values of data to define chart y-axis domain
//let minDomain = d3.min(data.map(d => d.values).flat().map(d => d.value));
let maxDomain = d3.max(data.map(d => d.values).flat().map(d => d.value));
yLine.domain([0, maxDomain]).nice();
//yLine.domain([0, d3.max(data[0].values, d => d.value)]).nice();
// add axes
let xaxis = g.select(".x.axis");
xaxis.call(
d3.axisBottom(xLine)
// .ticks(d3.timeMonth.every(1))
// .tickFormat(d3.timeFormat("%b '%y"))
)
.selectAll("text")
//.attr("y", 0)
//.attr("x", 9)
.attr("y", 15)
.attr("x", 0)
.attr("dy", ".35em")
.attr("font-size", "12px")
//.attr("transform", "rotate(90)")
//.style("text-anchor", "start");
.style("text-anchor", "middle")
.style("fill", "#b2b2b2");
// format x-axis lines for minor and major ticks
//TODO: check this line to separate years starting in Jan. Maybe it is not needed
xaxis.selectAll("g.tick line")
.attr("y2", d => { // d is the tick value
if (d3.timeFormat("%m")(d) == "01") //if it is Jan
return -height - 6;
else
return 6;
})
.attr("transform", d => { // d is the tick value
if (d3.timeFormat("%m")(d) == "01") //if it is Jan
return "translate(0,6)";
})
.attr("class", d => { // d is the tick value
if (d3.timeFormat("%m")(d) == "01") //if it is Jan
return "yearTick";
});
let yaxis = g.select(".y.axis");
yaxis.transition()
.duration(500)
.call(d3.axisLeft(yLine)
/*.tickFormat(d3.format(".2s"))*/
);
// color axis labels
g.selectAll(".y.axis g.tick text")
.style("fill", "#b2b2b2"); // function (d,i) {return (i%2)?"red":"blue";})
let title = selection.select(".title.label");
title.text(chart.title())
.style("fill", "#b2b2b2");
selection.select(".title.label")
.append("tspan")
.attr("class", "far")
.html(" \uf059")
//.style("color", "#b2b2b2")
.style("cursor", "pointer")
.on("click", () => { // when clicking ? display modal with information about indicator
$("#explainIndicatorModal").on("show.bs.modal", () => {
let indicatorName = indicatorsNames[indicators.indexOf(id)]
$("#explainIndicatorModal").find("#explainIndicatorModalTitle").text(indicatorName);
$("#explainIndicatorModal").find(".modal-body").html(indicatorVars[id].explanation);
});
$("#explainIndicatorModal").modal();
});
//});
// update chart
updateData = function () {
let time = map.timeDimension.getCurrentTime();
// update data ranges using values from displayName and lineVariables
minDate = d3.min(data[0].values, d => {
return d[displayName];
});
console.log("update data: ", data[0].values.length)
minDate = d3.timeDay.offset(minDate, -15) // get previous month to get correct x-axis alignment
maxDate = d3.max(data[0].values, d => {
return d[displayName];
});
maxDate = d3.timeDay.offset(maxDate, 15) // get next month to get correct x-axis alignment
// update axes' domain
xLine.domain([minDate, maxDate])
//.nice()
//.nice(d3.timeMonth)
.range([0, width - margin.left - margin.right - 30])
.clamp(true);
//yLine.domain(d3.extent(data, function(d) { return d[lineVariables[0]]; }))
//yLine.domain([0, d3.max(data, function(d) { return d[lineVariables[0]]; })]).nice()
// Calculate min and max values of data to define chart y-axis domain
//let minDomain = d3.min(data.map(d => d.values).flat().map(d => d.value));
let maxDomain = d3.max(data.map(d => d.values).flat().map(d => d.value));
yLine.domain([0, maxDomain])
//yLine.domain([0, d3.max(data[0].values, d => d.value)]).nice()
.range([height - margin.left - margin.right, 0 + 35])
.nice();
// filter data to only use the ones needed for current time
let filteredData = [];
data.forEach((d, i) => {
filteredData.push({
name: d.name,
values: []
});
d.values.filter(v => {
if (v.date <= new Date(time)) {
filteredData[i].values.push(v);
}
});
})
// update axes
let g = selection.select(".lineChart");
// update axis line with no ticks
let xaxis = g.select(".x.axis");
xaxis.transition()
.duration(500)
.call(
d3.axisBottom(xLine)
.ticks(0)
.tickSize(0)
);
// update axis ticks - one for each month
let xaxisTicks = g.select(".x.axis-ticks");
let monthsInGraph = [];
userDates.forEach(d => {
monthsInGraph.push(new Date(d));
});
xaxisTicks.transition()
.duration(500)
.call( // FIXME: this call causes a "translate(NaN,0)" ...
d3.axisBottom(xLine)
.tickValues(monthsInGraph)
.tickFormat("")
);
// update axis month labels depending on how many months there are
let xaxisLabels = g.select(".x.axis-month-labels");
// FIXME: check how this displays ticks when there are many months/years
// this fix gives up to 10 ticks
// maybe need to set up more conditions
let numberOfTicks = monthsInGraph.length < 6 ? d3.timeMonth.every(1) : 6;
xaxisLabels.transition().duration(500)
.call(
d3.axisBottom(xLine)
.ticks(numberOfTicks)
.tickFormat(d3.timeFormat("%b"))
)
.selectAll("text")
.style("fill", "#b2b2b2")
.attr("font-size", "12px");
// update axis year labels depending on how many months there are
let xaxisYearLabels = g.select(".x.axis-year-labels");
xaxisYearLabels.transition().duration(500)
.call(
d3.axisBottom(xLine)
.ticks(numberOfTicks)
.tickFormat(d3.timeFormat("'%y"))
)
.selectAll("text")
.style("fill", "#b2b2b2")
.attr("font-size", "12px");
// format x-axis lines for minor and major ticks
//TODO: check this line to separate years starting in Jan. Maybe it is not needed
xaxis.selectAll("g.tick line")
.attr("y2", d => { // d is the tick value
if (d3.timeFormat("%m")(d) == "01") //if it is Jan
return -height - 6;
else
return 6;
})
.attr("transform", d => { // d is the tick value
if (d3.timeFormat("%m")(d) == "01") //if it is Jan
return "translate(0,6)";
})
.attr("class", d => { // d is the tick value
if (d3.timeFormat("%m")(d) == "01") //if it is Jan
return "yearTick";
});
let yaxis = g.select(".y.axis");
yaxis.transition()
.duration(500)
.call(d3.axisLeft(yLine)
.tickFormat(yAxisFormat));
// color axis labels
g.selectAll(".y.axis g.tick text")
.style("fill", "#b2b2b2");
// define line
let line = d3.line()
.defined(d => d)
.curve(d3.curveMonotoneX)
.x(d => xLine(d.date))
.y(d => yLine(d.value));
// get lines
let lines = g.select(".lines");
lines.selectAll("path.line")
.data(filteredData)
.join("path") // handle enter, update, and exit automagically
.attr("stroke", (d, i) => {
return i == 0 ? defaultLineColor : lineColors(i - 1);
})
.attr("class", "line")
.attr("fill", "none")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", d => line(d.values))
// get circles
let circles = g.select(".circles"),
tooltip = d3.select(".tooltip");
//tooltipFormat = d3.timeFormat("%b "%y"),
//tooltipFormat = d3.format(",.2f")
//tooltipFormat = d3.format(",.3s");
circles.selectAll("g") // using nested data, so need to make a g to contain stuff
.data(filteredData)
.join( // handle enter, update, and exit separately for overall g circle containers
enter => enter
.append("g")
.attr("class", d => d.name)
.attr("stroke", (d, i) => {
return i == 0 ? defaultPointColor : pointColors(i - 1);
})
.attr("fill", (d, i) => {
return i == 0 ? defaultPointColor : pointColors(i - 1);
}),
update => update
.attr("class", d => d.name)
.attr("stroke", (d, i) => {
return i == 0 ? defaultPointColor : pointColors(i - 1);
})
.attr("fill", (d, i) => {
return i == 0 ? defaultPointColor : pointColors(i - 1);
}),
exit => exit.remove()
)
// nested join
.selectAll("circle").data(d => d.values) // access internal values array for circles here
.join( // handle enter, update, and exit separately for circles
enter => enter
.append("circle")
.attr("class", "circle")
.attr("r", 2.5)
.attr("cx", d => xLine(d.date))
.attr("cy", d => yLine(d.value))
.on("mouseover", d => {
tooltip.html(`${tooltipFormat(d.value)} ${tooltipUnits}`);
let tpWidth = tooltip.node().offsetWidth; // to center tooltip
tooltip.style("left", `${d3.event.pageX - tpWidth / 2}px`)
.style("top", `${d3.event.pageY - 23}px`)
.style("font-family", "Consolas, courier")
.style("font-size", "10pt")
.transition()
.duration(200)
.style("opacity", .9);
})
.on("mouseout", () => {
tooltip.transition()
.duration(500)
.style("opacity", 0);
}),
update => update
.attr("cx", d => xLine(d.date))
.attr("cy", d => yLine(d.value)),
exit => exit.remove()
);
}
});
//});
}
chart.width = function(value) {
if (!arguments.length) return width;
width = value;
return chart;
};
chart.height = function(value) {
if (!arguments.length) return height;
height = value;
return chart;
};
chart.margin = function(value) {
if (!arguments.length) return margin;
margin = value;
return chart;
};
chart.id = function(value) {
if (!arguments.length) return id;
id = value;
return chart;
};
chart.lineVariables = function(value) {
if (!arguments.length) return lineVariables;
lineVariables = value;
return chart;
};
chart.displayName = function(value) {
if (!arguments.length) return displayName;
displayName = value;
return chart;
};
chart.transitionTime = function(value) {
if (!arguments.length) return transitionTime;
transitionTime = value;
return chart;
};
chart.legend = function(value) {
if (!arguments.length) return legend;
legend = value;
return chart;
};
chart.legendContainer = function(value) {
if (!arguments.length) return legendContainer;
legendContainer = value;
return chart;
};
chart.data = function(value) {
if (!arguments.length) return data;
data = value;
if (typeof updateData === "function") updateData();
return chart;
};
chart.lineColors = function(value) {
if (!arguments.length) return lineColors;
lineColors = value;
return chart;
};
chart.pointColors = function(value) {
if (!arguments.length) return pointColors;
pointColors = value;
return chart;
};
chart.xAxisFormat = function(value) {
if (!arguments.length) return xAxisFormat;
xAxisFormat = value;
return chart;
};
chart.yAxisFormat = function(value) {
if (!arguments.length) return yAxisFormat;
yAxisFormat = value;
return chart;
};
chart.transitionTime = function(value) {
if (!arguments.length) return transitionTime;
transitionTime = value;
return chart;
};
chart.lineAxisLabel = function(value) {
if (!arguments.length) return lineAxisLabel;
lineAxisLabel = value;
return chart;
};
chart.title = function(value) {
if (!arguments.length) return title;
title = value;
return chart;
};
chart.tooltipUnits = function(value) {
if (!arguments.length) return tooltipUnits;
tooltipUnits = value;
return chart;
};
chart.tooltipFormat = function(value) {
if (!arguments.length) return tooltipFormat;
tooltipFormat = value;
return chart;
};
return chart;
}
\ No newline at end of file
......@@ -5,8 +5,8 @@
* August 2019
*/
/* globals omnivore, Promise, chroma */
/* exported layerControl */
/* globals omnivore, Promise, chroma, makeBaseMap, makeIndicatorGraph, getData */
/* exported userFiles, userDates, layerControl, queryFiles, flatten */
let timeParse,
timeFormat,
......@@ -32,14 +32,48 @@ let maxIndicators = {},
minIndicators = {},
indicators = ["area", "perimeter", "costa", "df"],
indicatorsNames = ["Área", "Perímetro", "Desarrollo de la línea de costa", "Dimensión fractal"],
indicatorsUnits = ["m\u00B2", "m", "", ""];
let cols = [];
indicators.forEach( (indicator) => {
cols.push("min(" + indicator + ") as min" + indicator);
cols.push("max(" + indicator + ") as max" + indicator);
indicatorsUnits = ["m\u00B2", "m", "", ""],
indicatorsxAxisFormat = [".2s", ".2s", ".2f", ".2f"],
indicatorVars = {},
cols = [];
// Add options to combo box, and set their chart variables
// chart containers must already exist in index.php
indicators.forEach( (indicator, index) => {
// colnames for queries
cols.push(`min(${indicator}) as min${indicator}`);
cols.push(`max(${indicator}) as max${indicator}`);
// initialize min and max objects to hold values for each indicator and add select options
maxIndicators[indicator] = 0;
minIndicators[indicator] = 1e30;
$("#indicatorSelect").append(`<option value="${indicator}"> ${indicatorsNames[index]}</option>`);
// chart variables
indicatorVars[indicator] = {
"chart": `${indicator}Chart`,
"chartaData": `${indicator}Data`,
"container": `#${indicator}-graph`
};
});
indicatorVars.area.explanation = "Muestra la suma del &aacute;rea contenida en los pol&iacute;gonos dentro de la selecci&oacute;n o en toda la regi&oacute;n de estudio.";
indicatorVars.perimeter.explanation = "Muestra la suma del per&iacute;metro de los pol&iacute;gonos dentro de la selecci&oacute;n o en toda la regi&oacute;n de estudio.";
indicatorVars.costa.explanation = "Relaciona el per&iacute;metro de un cuerpo de agua o longitud de l&iacute;nea de costa con el per&iacute;metro de un c&iacute;rculo de igual \
&aacute;rea (A) que el cuerpo. El valor m&iacute;nimo es 1, el cual corresponde a un c&iacute;rculo perfecto, mientras que el valor m&aacute;ximo no tiene l&iacute;mite. \
En varios estudios se han obtenido valores mayores de 20, como en algunos lagos en Finlandia. Los valores cercanos a 1 son indicativos de formas circulares, mientras que \
los valores mayores de 3 son referencia de cuerpos de agua con contornos de formas alargadas. \
<br/><br/> \
Se calcula como \
<p class=\"equation\">DI = <span class=\"frac\"><sup>P</sup><span>&frasl;</span><sub>2&radic;<span style=\"text-decoration: overline;\">&pi; A</span></sub></span>,</p> \
donde P es el per&iacute;metro y A es el &aacute;rea.";
indicatorVars.df.explanation = "La geometr&iacute;a fractal se usa para hacer referencia a objetos demasiado irregulares y que tienen la propiedad de ser invariantes ante \
cambios de escala. En el caso de la hidrolog&iacute;a, este &iacute;ndice nos refiere la sinuosidad del contorno de un cuerpo de agua. La dimensi&oacute;n presenta valores \
reales no negativos entre 1 y 2. Los valores de este &iacute;ndice aumentan conforme el contorno del cuerpo de agua es más sinuoso. El an&aacute;lisis fractal se ha utilizado \
con &eacute;xito para medir y caracterizar rasgos lineales irregulares como las costas. \
<br/></br> \
Se calcula como \
<p class=\"equation\">DF = <span class=\"frac\"><sup>2 ln(P/4)</sup><span>&frasl;</span><sub>ln(A)</sub></span>,</p> \
donde P es el per&iacute;metro y A es el &aacute;rea.";
let currentTiles = {},
allTiles = {};
......@@ -161,11 +195,11 @@ const populateDates = (dates) => { // fill out date pickers with available dates
// hide initial screen and show map
$("#startHeader").remove();
$(".img_bg").remove();
$("#initial-backdrop").remove();
$("#mainContainer")[0].style.setProperty("display", "flex", "important")
$("#mexmap").show();
// TODO: on close trigger update map
// When closing final-date, either setup or update map
if (!map) {
resolve({min: startUserDate, max: endUserDate});
} else {
......@@ -271,24 +305,15 @@ const setupMap = (dates) => {
return refMap.unproject(pt, zoom);
}
// initialize min and max objects to hold values for each indicator
// and add select options
indicators.forEach( (indicator, index) => {
maxIndicators[indicator] = 0;
minIndicators[indicator] = 1e30;
$("#indicatorSelect").append("<option value=\""+ indicator + "\">" + indicatorsNames[index] + "</option>");
});
console.log(userFiles);
// query db to get min/max values per month and indicator and store them in an object
queryFiles().then( minmax => {
minmax.map( minmaxMonth => {
indicators.forEach( (indicator) => {
minIndicators[indicator] = Math.min(minIndicators[indicator], minmaxMonth["min" + indicator]);
maxIndicators[indicator] = Math.max(maxIndicators[indicator], minmaxMonth["max" + indicator]);
minIndicators[indicator] = Math.min(minIndicators[indicator], minmaxMonth[`min${indicator}`]);
maxIndicators[indicator] = Math.max(maxIndicators[indicator], minmaxMonth[`max${indicator}`]);
});
});
timeDimensionControl.addTo(map);
resolve({"map": map, "minIndicators": minIndicators, "maxIndicators": maxIndicators});
});
});
......@@ -306,7 +331,7 @@ const queryFiles = () => {
const getMinMax = table => {
return new Promise( resolve => {
let minmaxQuery = "http://localhost:8090/data/query/" + table + "?columns=" + cols.join(", ");
let minmaxQuery = `http://localhost:8090/data/query/${table}?columns=${cols.join(", ")}`;
d3.json(minmaxQuery).then( minmax => {
resolve(minmax[0]);
});
......@@ -329,7 +354,7 @@ const updateMap = (mapData) => {
let newTile = mapboxLayer(monthYear);
glmap._glMap.addLayer(newTile);
if (monthYear == userFiles[0]) {
glmap._glMap.setPaintProperty(monthYear, 'fill-opacity', 0.7)
glmap._glMap.setPaintProperty(monthYear, "fill-opacity", 0.7)
}
}
});
......@@ -351,8 +376,8 @@ const updateMap = (mapData) => {
queryFiles().then( minmax => {
minmax.map( minmaxMonth => {
indicators.forEach( (indicator) => {
minIndicators[indicator] = Math.min(minIndicators[indicator], minmaxMonth["min" + indicator]);
maxIndicators[indicator] = Math.max(maxIndicators[indicator], minmaxMonth["max" + indicator]);
minIndicators[indicator] = Math.min(minIndicators[indicator], minmaxMonth[`min${indicator}`]);
maxIndicators[indicator] = Math.max(maxIndicators[indicator], minmaxMonth[`max${indicator}`]);
});
});
resolve({"map": map, "minIndicators": minIndicators, "maxIndicators": maxIndicators});
......@@ -385,8 +410,8 @@ const populateMap = (mapData) => {
});
let cuencaBufferMask = omnivore.topojson("data/buffer_cuenca.topojson", null, cuencaLayer);
cuencaBufferMask.addTo(map);
//TODO: makeBaseMap()
//map.createPane("wb-Tiles");
//map.getPane("wb-Tiles").style.zIndex = 450;
// create mvt layers
userFiles.forEach( f => {
......@@ -394,11 +419,11 @@ const populateMap = (mapData) => {
});
glmap = L.mapboxGL({
accessToken: 'no-token',
accessToken: "no-token",
style: {
'version': 8,
'sources': {},
'layers': []
"version": 8,
"sources": {},
"layers": []
}
}).addTo(map);
......@@ -410,10 +435,11 @@ const populateMap = (mapData) => {
Object.keys(allTiles).forEach(layer => {
if (layer == userFiles[0]) {
glmap._glMap.setPaintProperty(layer, 'fill-opacity', 0.7)
glmap._glMap.setPaintProperty(layer, "fill-opacity", 0.7)
}
});
timeDimensionControl.addTo(map);
// Pass dummy geojson layer to timeDimension in order to register and sync
timeLayer = L.timeDimension.layer.Tile(L.geoJSON(), {
updateTimeDimension: true,
......@@ -440,37 +466,70 @@ const populateMap = (mapData) => {
layerControl = L.control.layers(baseLayers, overlays).addTo(map);
});
//TODO: instantiate charts
// Define charts with reusable components
indicators.forEach( 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]})`);
let data = await getData2(indicator);
await indicatorVars[indicator].chart.data(data);
console.log(data, data[0]) // data has stuff, but data[0] has empty values
d3.select(indicatorVars[indicator].container)
.call(await indicatorVars[indicator].chart);
});
// 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);
// TODO: basemap
//makeBaseMap(); // basemap.js
}
// define MVT layer for given month table and all indicators
const mapboxLayer = (monthYear) => {
let pbfLayer = {
id: monthYear,
source: {
type: 'vector',
tiles: ["http://localhost:8090/data/" + monthYear + "/mvt/{z}/{x}/{y}?geom_column=geom&columns=" + indicators.join()],
type: "vector",
tiles: [`http://localhost:8090/data/${monthYear}/mvt/{z}/{x}/{y}?geom_column=geom&columns=${indicators.join()}`],
maxzoom: 14,
minzoom: 5
},
'source-layer': monthYear,
type: 'fill',
"source-layer": monthYear,
type: "fill",
minzoom: 5,
'paint': {
'fill-opacity': 0,
/*'fill-color': [
'interpolate',
['linear'],
['get', 'df'],
1, 'rgba(255, 0, 0, 0.5)', // red
1.3, 'rgba(0, 255, 0, 0.5)', // green
"paint": {
"fill-opacity": 0,
/*"fill-color": [
"interpolate",
["linear"],
["get", "df"],
1, "rgba(255, 0, 0, 0.5)", // red
1.3, "rgba(0, 255, 0, 0.5)", // green
]/*,
'fill-outline-color': [
'interpolate',
['linear'],
['get', 'df'],
1, 'rgba(255, 0, 0, 0.6)', // red
1.3, 'rgba(0, 255, 0, 0.6)', // green
"fill-outline-color": [
"interpolate",
["linear"],
["get", "df"],
1, "rgba(255, 0, 0, 0.6)", // red
1.3, "rgba(0, 255, 0, 0.6)", // green
]*/
}
}
......@@ -486,7 +545,7 @@ const setupTimeDimensionControl = () => {
let d = new Date(date);
let year = d.getFullYear().toString();
let month = d.getUTCMonth();
return monthArray[month] + " " + year;
return `${monthArray[month]} ${year}`;
}
});
......@@ -542,7 +601,7 @@ L.TimeDimension.Layer.Tile = L.TimeDimension.Layer.extend({
this._timeDimension.off("timeload", this._update, this);
//this._baseLayer.getContainer().style.display = "none";
Object.keys(allTiles).forEach(layer => { // hide all tiles
glmap._glMap.setPaintProperty(layer, 'fill-opacity', 0);
glmap._glMap.setPaintProperty(layer, "fill-opacity", 0);
});
//this.eachLayer(map.removeLayer, map);
//this._map = null;
......@@ -565,26 +624,26 @@ L.TimeDimension.Layer.Tile = L.TimeDimension.Layer.extend({
year = d.getFullYear().toString(),
m = d.getUTCMonth(),
month = monthArray[m].toLowerCase(),
monthYear = month + "_" + year;
monthYear = `${month}_${year}`;
// Update title
let title = $("#title");
title.html("<h2>Cobertura de agua en la cuenca del r&iacute;o Grijalva en " + month + " de " + year + "</h2>");
title.html(`<h2>Cobertura de agua en la cuenca del r&iacute;o Grijalva en ${month} de ${year}</h2>`);
// Update graphs only on timeload event
//indicators.forEach( indicator => {
// indicatorVars[indicator].chartData = indicatorVars[indicator].chart.data(); // get chart data
// indicatorVars[indicator].chart.data(indicatorVars[indicator].chartData); // set chart data
//});
indicators.forEach( indicator => {
indicatorVars[indicator].chartData = indicatorVars[indicator].chart.data(); // get chart data
indicatorVars[indicator].chart.data(indicatorVars[indicator].chartData); // set chart data
});
//console.time("process");
console.log("data for", monthYear);
//console.log(currentTiles)
Object.keys(allTiles).forEach(layer => {
if (layer !== monthYear) { // hide all other months
glmap._glMap.setPaintProperty(layer, 'fill-opacity', 0);
glmap._glMap.setPaintProperty(layer, "fill-opacity", 0);
} else { // except current one
glmap._glMap.setPaintProperty(layer, 'fill-opacity', 0.7);
glmap._glMap.setPaintProperty(layer, "fill-opacity", 0.7);
}
});
//console.timeEnd("process");
......@@ -604,9 +663,9 @@ $("#indicatorSelect").on("change", function() {
.then(legend.addTo(map)); // add legend control -> it updates
// FIXME: re-adding control updates its contents... why?
// Highlight plot title according to selected option
//indicators.forEach( indicator => {
// d3.select(indicatorVars[indicator].container).select("svg text.title").classed("active", indicator === option ? true : false);
//});
indicators.forEach( indicator => {
d3.select(indicatorVars[indicator].container).select("svg text.title").classed("active", indicator === option ? true : false);
});
});
const styleTiles = (option, minIndicators, maxIndicators) => {
......@@ -616,13 +675,13 @@ const styleTiles = (option, minIndicators, maxIndicators) => {
scale = chroma.scale("PuBu").padding([0.5, 0]).domain(domain).classes(5);
Object.keys(currentTiles).forEach(layer => {
let color = [
'interpolate',
['linear'],
['get', option],
"interpolate",
["linear"],
["get", option],
minIndicators[option], scale(minIndicators[option]).hex(),
maxIndicators[option], scale(maxIndicators[option]).hex()
];
glmap._glMap.setPaintProperty(layer, 'fill-color', color);
glmap._glMap.setPaintProperty(layer, "fill-color", color);
});
return Promise.resolve(scale);
}
......@@ -636,13 +695,12 @@ legend.onAdd = () => {
let option = $("#indicatorSelect").val();
let optionIndex = indicators.indexOf(option);
let legendText = indicatorsUnits[optionIndex] == "" ? indicatorsNames[optionIndex] :
indicatorsNames[optionIndex] + " (" + indicatorsUnits[optionIndex] + ")";
var html = "<h6>" + legendText + "</h6><ul>";
`${indicatorsNames[optionIndex]} (${indicatorsUnits[optionIndex]})`;
var html = `<h6>${legendText}</h6><ul>`;
let classes = scale.classes();
classes.forEach( (c, idx, array) => {
if (idx != array.length - 1) {
html += "<li><i style=\"background:" + scale(c).hex() + "\"></i> " +
" " + d3.format(",.4~s")(classes[idx]) + " - " + d3.format(",.4~s")(classes[idx+1]) + "</li>";
html += `<li><i style="background: ${scale(c).hex()}"></i>${d3.format(",.4~s")(classes[idx])} - ${d3.format(",.4~s")(classes[idx+1])}</li>`;
}
});
html += "</ul>";
......
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