Commit 94fcb056 authored by Tania Gómez's avatar Tania Gómez

Chords diagram plot

parent 8884b2e5
......@@ -19,7 +19,8 @@ let baseLayerPromises = [];
currentBaseLayer = 1;*/
//let drawnItems;
let od, flowMapsArray = [], odData = {};
let od, flowMapsArray = [],
odData = {};
/*Object.keys(baseFileSize).forEach((name) => {
if (name.split(".")[1] == "zip") {
......@@ -900,9 +901,9 @@ const createFlowLayer = (geojson, type, addOnCreate) => {
animatedCanvasBezierStyle = [];
// define styles based on properties and values
intervals[type].values.forEach( (val, idx) => {
intervals[type].values.forEach((val, idx) => {
canvasBezierStyle.push({
classMinValue: idx == 0 ? 0 : intervals[type].values[idx-1] + 1,
classMinValue: idx == 0 ? 0 : intervals[type].values[idx - 1] + 1,
classMaxValue: val,
symbol: {
strokeStyle: intervals[type].colors[idx],
......@@ -914,7 +915,7 @@ const createFlowLayer = (geojson, type, addOnCreate) => {
});
animatedCanvasBezierStyle.push({
classMinValue: idx == 0 ? 0 : intervals[type].values[idx-1] + 1,
classMinValue: idx == 0 ? 0 : intervals[type].values[idx - 1] + 1,
classMaxValue: val,
symbol: {
strokeStyle: intervals[type].colors[idx],
......@@ -931,12 +932,12 @@ const createFlowLayer = (geojson, type, addOnCreate) => {
// build data array from geojson to be used in amchart
let source = data.features;
let newData = [];
source.forEach(o => newData.push({'xVar': o.properties.muni_origen, 'yVar': o.properties.muni_destino, 'flowCount': o.properties.viajes}) );
source.forEach(o => newData.push({ 'xVar': o.properties.muni_origen, 'yVar': o.properties.muni_destino, 'flowCount': o.properties.viajes }));
let origins= [],
let origins = [],
destinations = [];
newData.forEach( data => {
newData.forEach(data => {
origins.push(data.xVar); // all origins
destinations.push(data.yVar) // all destinations
});
......@@ -944,16 +945,16 @@ const createFlowLayer = (geojson, type, addOnCreate) => {
destinations = destinations.filter((v, i, a) => a.indexOf(v) === i); // get unique ones
// add missing combinations with 0 trips
origins.forEach( (o) => {
destinations.forEach( (d) => {
if (!newData.some( data => data.xVar === o && data.yVar === d ) ) {
newData.push({'xVar': o, 'yVar': d, 'flowCount': 0});
origins.forEach((o) => {
destinations.forEach((d) => {
if (!newData.some(data => data.xVar === o && data.yVar === d)) {
newData.push({ 'xVar': o, 'yVar': d, 'flowCount': 0 });
}
})
});
// sort array by origins and then by destinations
newData.sort( (el1,el2) => {
newData.sort((el1, el2) => {
let compared = compare(el1, el2, "xVar")
return compared == 0 ? compare(el1, el2, "yVar") : compared;
});
......@@ -989,7 +990,7 @@ const createFlowLayer = (geojson, type, addOnCreate) => {
},
// dot styles
style: function (geoJsonFeature) {
style: function(geoJsonFeature) {
if (geoJsonFeature.properties.isOrigin) {
return {
renderer: canvasPointRenderer,
......@@ -1020,9 +1021,9 @@ const createFlowLayer = (geojson, type, addOnCreate) => {
animationDuration: 2000,
customLayerId: type,
pane: "pane_flujos"
}).bindTooltip( layer => { // what to display on hover
}).bindTooltip(layer => { // what to display on hover
let coords = layer.getLatLng();
let label = od.features.filter( f => f.properties.lng == coords.lng && f.properties.lat == coords.lat);
let label = od.features.filter(f => f.properties.lng == coords.lng && f.properties.lat == coords.lat);
return label[0].properties.nombre;
})
......@@ -1031,6 +1032,10 @@ const createFlowLayer = (geojson, type, addOnCreate) => {
flowMapLayer.addTo(map).on('click', odClick);
let amchart = am4core.registry.baseSprites.find(c => c.htmlContainer.id === "amchartdiv");
amchart.data = newData;
let amchart2 = am4core.registry.baseSprites.find(c => c.htmlContainer.id === "prueba2");
amchart2.data = newData;
amchart2.data = amchart2.data.filter(function(e) { return e.flowCount > 0 });
}
flowMapsArray.push(flowMapLayer);
resolve(flowMapsArray);
......@@ -1135,15 +1140,15 @@ const makeBaseMap = () => {
// create one flow, then another, then another...
// and finally set reset control funcionality
createFlowLayer("data/viajes_ocupados_desde.geojson", "ocupadosDesde", true)
.then( () => createFlowLayer("data/viajes_ocupados_hacia.geojson", "ocupadosHacia") )
.then( () => createFlowLayer("data/viajes_ocupados_entre.geojson", "ocupadosEntre") )
.then( () => createFlowLayer("data/viajes_ocupados_POIC_desde.geojson", "poicDesde") )
.then( () => createFlowLayer("data/viajes_ocupados_POIC_hacia.geojson", "poicHacia") )
.then( () => createFlowLayer("data/viajes_ocupados_POIC_entre.geojson", "poicEntre") )
.then( layers => {
.then(() => createFlowLayer("data/viajes_ocupados_hacia.geojson", "ocupadosHacia"))
.then(() => createFlowLayer("data/viajes_ocupados_entre.geojson", "ocupadosEntre"))
.then(() => createFlowLayer("data/viajes_ocupados_POIC_desde.geojson", "poicDesde"))
.then(() => createFlowLayer("data/viajes_ocupados_POIC_hacia.geojson", "poicHacia"))
.then(() => createFlowLayer("data/viajes_ocupados_POIC_entre.geojson", "poicEntre"))
.then(layers => {
// reset to all flows displayed
$("#resetFlows").on("click", () => {
layers.forEach( layer => {
layers.forEach(layer => {
if (map.hasLayer(layer)) {
layer.originAndDestinationGeoJsonPoints.features.forEach(function(feature) {
if (feature.properties.isOrigin === true) {
......
......@@ -40,23 +40,23 @@ am4core.ready(function() {
//heatLegend.minValue = minValue;
//heatLegend.maxValue = maxValue;
heatLegend.minValue = 0;
heatLegend.maxValue = intervals[option].values[intervals[option].values.length-1];
heatLegend.maxValue = intervals[option].values[intervals[option].values.length - 1];
// update heatLegend colors
let heatColors = [];
["#333", ...intervals[option].colors].forEach( c => heatColors.push(am4core.color(c)) );
["#333", ...intervals[option].colors].forEach(c => heatColors.push(am4core.color(c)));
heatLegend.minColor = heatColors[0];
heatLegend.maxColor = heatColors[intervals[option].colors.length - 1];
//let checkConditions = [minValue, ...intervals[option].values.slice(1)];
let checkConditions = [minValue, ...intervals[option].values];
let lastValue = intervals[option].values[intervals[option].values.length-1]
let lastValue = intervals[option].values[intervals[option].values.length - 1]
// Override heatLegend gradient
let gradient = new am4core.LinearGradient();
heatColors.forEach(function(color, index) {
// addColor(color, opacity, offset) use offset to put colors in proper alignment
gradient.addColor(color, undefined, (checkConditions[index] - checkConditions[0])/lastValue);
gradient.addColor(color, undefined, (checkConditions[index] - checkConditions[0]) / lastValue);
});
//heatLegend.markers.template.applyOnClones = true;
......@@ -70,14 +70,14 @@ am4core.ready(function() {
let workingValue = column.dataItem.values["value"].workingValue;
// use min max values calculated from data on beforedatavalidated
if (am4core.type.isNumber(workingValue)) {
checkConditions.forEach( (condition, index) => {
if ( index < checkConditions.length-1 ) {
if (workingValue >= condition && workingValue <= checkConditions[index+1]) {
checkConditions.forEach((condition, index) => {
if (index < checkConditions.length - 1) {
if (workingValue >= condition && workingValue <= checkConditions[index + 1]) {
//console.log(`${workingValue} entre ${condition} y ${checkConditions[index+1]}`)
fill = new am4core.Color(
am4core.colors.interpolate(
heatColors[index].rgb,
heatColors[index+1].rgb,
heatColors[index + 1].rgb,
workingValue
)
);
......@@ -119,7 +119,7 @@ am4core.ready(function() {
xAxis.events.on("sizechanged", function(ev) {
let axis = ev.target;
let cellWidth = axis.pixelWidth / (axis.endIndex - axis.startIndex);
axis.renderer.labels.template.maxWidth = 2*Math.ceil(cellWidth)*0.8;
axis.renderer.labels.template.maxWidth = 2 * Math.ceil(cellWidth) * 0.8;
});
// on data change change, resize labels
......@@ -127,7 +127,7 @@ am4core.ready(function() {
xAxis.events.on("datarangechanged", function(ev) {
let axis = ev.target;
let cellWidth = axis.pixelWidth / (axis.endIndex - axis.startIndex);
axis.renderer.labels.template.maxWidth = 2*Math.ceil(cellWidth)*0.8;
axis.renderer.labels.template.maxWidth = 2 * Math.ceil(cellWidth) * 0.8;
});
xAxis.renderer.labels.template.fontSize = 12;
......@@ -184,7 +184,7 @@ am4core.ready(function() {
columnTemplate.adapter.add("strokeWidth", function(width, column) {
var workingValue = column.dataItem.values["value"].workingValue;
if (am4core.type.isNumber(workingValue)) {
width = workingValue != 0 ? 1: 0;
width = workingValue != 0 ? 1 : 0;
}
return width;
});
......@@ -227,8 +227,7 @@ am4core.ready(function() {
column.strokeWidth = 2;
column.strokeOpacity = 0.2;
heatLegend.valueAxis.showTooltipAt(column.dataItem.value);
}
else {
} else {
column.strokeWidth = 0;
column.strokeOpacity = 0;
heatLegend.valueAxis.hideTooltip();
......@@ -246,4 +245,85 @@ am4core.ready(function() {
chart.responsive.enabled = true;
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++CC
// Themes begin
am4core.useTheme(am4themes_animated);
// Themes end
var cd_chart = am4core.create("prueba2", am4charts.ChordDiagram);
var cd_title = cd_chart.titles.create();
//cd_title.text = "Viajes de ocupados desde centros de mercado";
cd_title.fill = am4core.color(mainTextColor);
cd_title.fontSize = 13;
cd_title.align = "left";
cd_title.marginBottom = 20;
cd_title.paddingLeft = 10;
cd_chart.events.on("beforedatavalidated", function(ev) {
let option = $("#indicatorSelect").val();
let optionTitle = $("#indicatorSelect option:selected").text();
cd_title.text = "[bold]Conectividad origen -destino entre \n" + optionTitle.toLowerCase();
//var data = ev.target.data;
//let data = data.filter(function(e) { return e.flowCount > 0 });
//console.log(data);
});
cd_chart.dataFields.fromName = "xVar";
cd_chart.dataFields.toName = "yVar";
cd_chart.dataFields.value = "flowCount";
//cd_chart.labels.fontSize = 15;
// make nodes draggable
var nodeTemplate = cd_chart.nodes.template;
nodeTemplate.readerTitle = "Oculta/muestra para reorganizar la red"; //"Click to show/hide or drag to rearrange";
nodeTemplate.showSystemTooltip = true;
var nodeLink = cd_chart.links.template;
var bullet = nodeLink.bullets.push(new am4charts.CircleBullet());
bullet.fillOpacity = 0.8;
bullet.circle.radius = 3;
bullet.locationX = 0.5;
// create animations
cd_chart.events.on("over", function() {
// nodeTemplate.events.on("ready", function() {
for (var i = 0; i < cd_chart.links.length; i++) {
var link = cd_chart.links.getIndex(i);
var bullet = link.bullets.getIndex(0);
animateBullet(bullet);
}
})
function animateBullet(bullet) {
var duration = 3000 * Math.random() + 2000;
var animation = bullet.animate([{ property: "locationX", from: 0, to: 1 }], duration)
animation.events.on("animationended", function(event) {
animateBullet(event.target.object)
})
}
var label = nodeTemplate.label;
label.relativeRotation = 90;
label.fontSize = 10;
label.fill = am4core.color(mainTextColor);
//label.wrap = true;
//label.bent = true;
//cd_chart.responsive.enabled = true;
}); // end am4core.ready()
\ No newline at end of file
......@@ -34,10 +34,12 @@ let maxIndicators = {},
indicators = ["ocupadosDesde", "ocupadosHacia", "ocupadosEntre", "poicDesde", "poicHacia", "poicEntre"],
indicatorsNames = ["Viajes de ocupados desde centros de mercado", "Viajes de ocupados hacia centros de mercado",
"Viajes de ocupados entre zonas", "Viajes de personas en OIC desde centros de mercado",
"Viajes de personas en OIC hacia centros de mercado", "Viajes de personas en OIC entre zonas"],
"Viajes de personas en OIC hacia centros de mercado", "Viajes de personas en OIC entre zonas"
],
indicatorsShortNames = ["Ocupados desde <br/> centros de mercado", "Ocupados hacia <br/> centros de mercado",
"Ocupados entre zonas", "OIC desde centros <br/> de mercado",
"OIC hacia centros <br/> de mercado", "OIC entre zonas"],
"OIC hacia centros <br/> de mercado", "OIC entre zonas"
],
//indicatorsUnits = ["m\u00B2", "m", "", ""],
indicatorsxAxisFormat = [".2s", ".2s", ".2f", ".2f"],
indicatorVars = {},
......@@ -64,42 +66,42 @@ indicators.forEach((indicator, index) => {
let intervals = {
"ocupadosDesde": {
"classes": ["Menos de 1,000", "1,001 - 2,000", "2,001 - 6,000", "6,001 - 7,200"],
"values" : [1000, 2000, 6000, 7200],
"values": [1000, 2000, 6000, 7200],
"colors": ["#00c5ff", "#008fdc", "#005ce6", "#4c0073"],
"thickness": [0.5, 1.5, 2, 10],
"animThickness": [0.5, 1.5, 4, 15]
},
"ocupadosHacia": {
"classes": ["Menos de 1,000", "1,001 - 2,000", "2,001 - 6,000", "6,001 - 32,000", "32,001 - 53,300"],
"values" : [1000, 2000, 6000, 32000, 53300],
"values": [1000, 2000, 6000, 32000, 53300],
"colors": ["#ffbee8", "#ff73df", "#ff00c5", "#ad027d", "#80006b"],
"thickness": [0.5, 1.5, 2, 4, 10],
"animThickness": [0.5, 1.5, 4, 10, 15]
},
"ocupadosEntre": {
"classes": ["Menos de 1,000", "1,001 - 2,000", "2,001 - 3,500", "3,501 - 8,000"],
"values" : [1000, 2000, 3500, 8000],
"values": [1000, 2000, 3500, 8000],
"colors": ["#70a800", "#ffaa00", "#e64c00", "#a80000"],
"thickness": [0.5, 1.5, 2, 4],
"animThickness": [0.5, 1.5, 4, 10]
},
"poicDesde": {
"classes": ["Menos de 150", "151 - 500", "501 - 1,000", "1,001 - 1,500"],
"values" : [150, 500, 1000, 1500],
"values": [150, 500, 1000, 1500],
"colors": ["#00c5ff", "#008fe6", "#005ce6", "#002673"],
"thickness": [0.5, 1.5, 2, 4],
"animThickness": [0.5, 1.5, 4, 10]
},
"poicHacia": {
"classes": ["Menos de 200", "201 - 400", "401 - 600", "601 - 3,000", "3,001 - 10,700"],
"values" : [200, 400, 600, 3000, 10700],
"values": [200, 400, 600, 3000, 10700],
"colors": ["#ffbee8", "#ff73df", "#ff00c5", "#ad027d", "#80006b"],
"thickness": [0.5, 1.5, 2, 4, 10],
"animThickness": [0.5, 1.5, 4, 10, 15]
},
"poicEntre": {
"classes": ["150 - 500", "501 - 700", "701 - 950", "951 - 1,200"],
"values" : [500, 700, 950, 1200],
"values": [500, 700, 950, 1200],
"colors": ["#70a800", "#ffaa00", "#e64c00", "#a80000"],
"thickness": [0.5, 1.5, 2, 4],
"animThickness": [0.5, 1.5, 4, 10]
......@@ -138,7 +140,7 @@ d3.json("https://unpkg.com/d3-time-format@2.1.1/locale/es-MX.json").then(locale
timeFormat = d3.timeFormat("%B_%Y");
//setupTimeDimensionControl();
// FIX: Need to remove setup and populate dates and only setup and populate map
// FIX: Need to remove setup and populate dates and only setup and populate map
/*setupDates()
.then(dates => populateDates(dates))
.then(userData => setupMap(userData))
......@@ -284,7 +286,7 @@ const populateDates = (dates) => { // fill out date pickers with available dates
})
}
const setupMap = () => {//(dates) => {
const setupMap = () => { //(dates) => {
return new Promise(resolve => {
// make body tag to have style height: 100%
......@@ -376,7 +378,7 @@ const setupMap = () => {//(dates) => {
//console.log(userFiles);
// query db to get min/max values per month and indicator and store them in an object
// FIX: comment out to avoid DB calls
// FIX: comment out to avoid DB calls
/*queryFiles().then(minmax => {
minmax.map(minmaxMonth => {
indicators.forEach((indicator) => {
......@@ -386,7 +388,7 @@ const setupMap = () => {//(dates) => {
});
resolve({ "map": map, "minIndicators": minIndicators, "maxIndicators": maxIndicators });
});*/
// FIX: resolve with fake values
// FIX: resolve with fake values
resolve({ "map": map, "minIndicators": minIndicators, "maxIndicators": maxIndicators });
});
}
......@@ -422,9 +424,9 @@ const odClick = (e) => {
});*/
}
// select and add flows toward those origins, i.e., for dots that are both origins and destinations
let dests = e.target.originAndDestinationGeoJsonPoints.features.filter( f => f.geometry.coordinates[0] === e.latlng.lng && f.geometry.coordinates[1] === e.latlng.lat);
let dests = e.target.originAndDestinationGeoJsonPoints.features.filter(f => f.geometry.coordinates[0] === e.latlng.lng && f.geometry.coordinates[1] === e.latlng.lat);
e.target.selectFeaturesForPathDisplay(dests, "SELECTION_ADD");
dests.forEach( dest => {
dests.forEach(dest => {
//origins += `${dest.properties.muni_origen} &rarr; ${dest.properties.muni_destino}: ${dest.properties.viajes} viajes <br>`;
// style viajes thousands with #,###
origins += `<tr><td>${dest.properties.muni_origen}</td><td>${dest.properties.muni_destino}</td><td>${dest.properties.viajes.toLocaleString()}</td></tr>`;
......@@ -445,13 +447,13 @@ const odClick = (e) => {
const populateMap = async(mapData) => {
// FIX: comment out to avoid DB calls
// FIX: comment out to avoid DB calls
//const chartData = await getData();
// fake null data to avoid DB requests
chartData = [];
indicators.map( indicator => {
indicators.map(indicator => {
chartData[indicator] = [];
chartData[indicator].push({name: `${indicator}0`, values: [{"date": new Date("2016-01-01T06:00:00.000Z"),"value":0}]});
chartData[indicator].push({ name: `${indicator}0`, values: [{ "date": new Date("2016-01-01T06:00:00.000Z"), "value": 0 }] });
})
// Define charts with reusable components
......@@ -495,7 +497,7 @@ const populateMap = async(mapData) => {
//map.createPane("wb-Tiles");
//map.getPane("wb-Tiles").style.zIndex = 450;
// FIX: comment out to avoid DB calls
// FIX: comment out to avoid DB calls
// create mvt layers
/*userFiles.forEach(f => {
f = mapboxLayer(f);
......@@ -512,7 +514,7 @@ const populateMap = async(mapData) => {
// after mapboxGL map is ready with styles do this:
glmap.getMapboxMap().on("style.load", () => {
// FIX: comment out to avoid DB calls
// FIX: comment out to avoid DB calls
/*userFiles.forEach(monthYear => {
glmap.getMapboxMap().addLayer(currentTiles[monthYear]);
});
......@@ -533,7 +535,7 @@ const populateMap = async(mapData) => {
});
timeLayer.addTo(map);*/
// FIX: comment out to avoid DB calls
// FIX: comment out to avoid DB calls
// style currentTiles
/*let option = $("#indicatorSelect").val(); // option selected from dropdrown
styleTiles(option, minIndicators, maxIndicators)
......@@ -559,16 +561,16 @@ const populateMap = async(mapData) => {
// setting their z-index
let glmapChildren = map.getPanes().tilePane.children,
children = Array.from(glmapChildren);
children.forEach( c => c.style.zIndex = "inherit" );
children.forEach(c => c.style.zIndex = "inherit");
});
}
const updateMap = (mapData) => {
//console.log(userFiles);
// FIX: comment out to avoid DB calls
// FIX: comment out to avoid DB calls
// clear tiles
/* currentTiles = {};
/* currentTiles = {};
//retrieve or create tiles for current dates
userFiles.forEach(monthYear => {
if (Object.keys(allTiles).includes(monthYear)) {
......@@ -616,7 +618,7 @@ const updateMap = (mapData) => {
});
// Update charts
updateCharts();
*/
*/
}
const updateCharts = async() => {
......@@ -628,12 +630,14 @@ const updateCharts = async() => {
let allData = await getDataInSelection();
indicators.map(async indicator => {
indicatorVars[indicator].chart.data(summarizeData(allData, indicator));
// indicatorVars[indicator].cd_chart.data(summarizeData(allData, indicator));
});
} else {
// otherwise use all data
let allData = await getData();
indicators.map(async indicator => {
indicatorVars[indicator].chart.data(allData[indicator]);
// indicatorVars[indicator].cd_chart.data(allData[indicator]);
});
}
$(".loader").hide("fade", 750);
......@@ -770,6 +774,7 @@ L.TimeDimension.Layer.Tile = L.TimeDimension.Layer.extend({
indicators.forEach(indicator => {
indicatorVars[indicator].chartData = indicatorVars[indicator].chart.data(); // get chart data
indicatorVars[indicator].chart.data(indicatorVars[indicator].chartData); // set chart data
// indicatorVars[indicator].cd_chart.data(indicatorVars[indicator].chartData); // set chart data
});
//console.time("process");
......@@ -795,7 +800,7 @@ $("#indicatorSelect").on("change", function() {
// style currentTiles
let option = this.value; // option selected from dropdrown
flowMapsArray.forEach( layer => {
flowMapsArray.forEach(layer => {
if (layer.options.customLayerId === option) {
map.addLayer(layer);
layer.on('click', odClick);
......@@ -815,6 +820,18 @@ $("#indicatorSelect").on("change", function() {
let amchart = am4core.registry.baseSprites.find(c => c.htmlContainer.id === "amchartdiv")
amchart.data = odData[option];
let amchart2 = am4core.registry.baseSprites.find(c => c.htmlContainer.id === "prueba2")
amchart2.data = odData[option];
amchart2.data = amchart2.data.filter(function(e) { return e.flowCount > 0 });
//console.log(amchart2.data.filter(function(e) { return e.flowCount > 0 }))
/* let amchart1 = am4core.registry.baseSprites.find(c => c.htmlContainer.id === "prueba")
amchart1.data = odData[option];
amchart1.data = amchart1.data.filter(function(e) { return e.flowCount > 0 });*/
styleTiles(option, minIndicators, maxIndicators)
.then(legend.addTo(map)); // add legend control -> it updates
// FIXME: re-adding control updates its contents... why?
......@@ -876,7 +893,7 @@ legend.onAdd = function() {
});*/
// create stuff for each type of flow
classes.forEach( (c, idx) => {
classes.forEach((c, idx) => {
// create list element
let item = L.DomUtil.create('li');
// create canvas element with arc of appropriate thickness ad color
......@@ -911,4 +928,3 @@ legend.onAdd = function() {
return div;
};
\ No newline at end of file
......@@ -494,4 +494,4 @@ am4core.ready(function() {
}); // end am4core.ready()*/
\ No newline at end of file
}); // end am4core.ready */
\ No newline at end of file
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