Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

392 zone filters + continuous and nulls refactor #451

Merged
merged 14 commits into from
Aug 9, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 49 additions & 15 deletions docs/tool/js/core/housing-insights.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,26 +49,46 @@ function StateModule() {

function setState(key,value) { // making all state properties arrays so that previous value is held on to
// current state to be accessed as state[key][0].
if ( state[key] === undefined ) {
var stateIsNew = (state[key] === undefined);


console.groupCollapsed("setState:", key, value)
if ( stateIsNew ) {
state[key] = [value];
PubSub.publish(key, value);
console.log('STATE CHANGE', key, value);
} else if ( state[key][0] !== value ) { // only for when `value` is a string or number. doesn't seem
// to cause an issue when value is an object, but it does duplicate
// the state. i.e. key[0] and key[1] will be equivalent. avoid that
// with logic before making the setState call.
state[key].unshift(value);
PubSub.publish(key, value);
console.log('STATE CHANGE', key, value);
if ( state[key].length > 2 ) {
state[key].length = 2;
}
console.log('STATE CREATED', key, value);
console.trace("Stack trace for state " + key);

} else {
//TODO do we want for there to be another 'announceState' method that publishes every time it fires, even if the value remains the same??
console.log("State value is the same as previous:", key, value);
//If it's a string or array and values are the same, stateChanged=False+
var stateChanged = true;
if (typeof value === 'string') {
stateChanged = (state[key][0] !== value)
} else if (Array.isArray(value) && Array.isArray(state[key][0])) {
stateChanged = !value.compare(state[key][0])
} else {
stateChanged = true; //assume it's changed if we can't verify
}

//Only publish if we've changed state
if (stateChanged ) {
state[key].unshift(value);
PubSub.publish(key, value);

console.log('STATE CHANGE', key, value);
console.trace("Stack trace for state " + key);
if ( state[key].length > 2 ) {
state[key].length = 2;
}
} else {
//TODO do we want for there to be another 'announceState' method that publishes every time it fires, even if the value remains the same??
console.log("STATE UNCHANGED, NO PUB:", key, value);
console.trace("Stack trace for state " + key);
}
}

console.groupEnd()
}

function clearState(key) {
delete state[key];
PubSub.publish('clearState', key);
Expand Down Expand Up @@ -363,6 +383,20 @@ var getFieldFromMeta = function(table,sql_name){
return this; // for testing purposes
};

//array.compare(otherArray) //HT https://stackoverflow.com/questions/6229197/how-to-know-if-two-arrays-have-the-same-values
Array.prototype.compare = function(testArr) {
if (this.length != testArr.length) return false;
if (this.length === 0 && testArr.length === 0) return true;
console.log("in compare");
console.log(this);
for (var i = 0; i < testArr.length; i++) {
if (this[i] !== testArr[i]) {
return false;
}
}
return true;
}

// HELPER String.hashCode()

String.prototype.hashCode = function() {
Expand Down
55 changes: 17 additions & 38 deletions docs/tool/js/core/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ var router = {
if ( router.isFilterInitialized ) return;
setSubs([
['filterValues', router.pushFilter]
// ['nullsShown', router.nullsHandler] // triggered when the nullsShown check is toggled. not when a filter range is adjusted
]);
router.isFilterInitialized = true;
if ( router.hasInitialFilterState ) router.decodeState();
Expand Down Expand Up @@ -42,15 +41,16 @@ var router = {
var dataChoice = dataChoices.find(function(obj){
return key.split('.')[1] === obj.source;
});
var separator = getState()['nullsShown.' + dataChoice.source] && getState()['nullsShown.' + dataChoice.source][0] ? '-' : '_';

if ( dataChoice.component_type === 'continuous' ) {
var separator = router.stateObj[key] && router.stateObj[key][2] ? '-' : '_';
paramsArray.push(dataChoice.short_name + '=' + Math.round(router.stateObj[key][0]) + separator + Math.round(router.stateObj[key][1]));
}
if ( dataChoice.component_type === 'categorical' ){
paramsArray.push( dataChoice.short_name + '=' + router.stateObj[key].join('+'));
}
if ( dataChoice.component_type === 'date' ){
var separator = router.stateObj[key] && router.stateObj[key][2] ? '-' : '_';
var dateStrings = [];
for ( var i = 0; i < 2; i++ ){ // i < 2 bc index 2 is true/false of nullsShown
var date = router.stateObj[key][i].getDate();
Expand All @@ -68,24 +68,23 @@ var router = {
document.getElementById('loading-state-screen').style.display = 'block';
},
decodeState: function(){
console.log("decoding state from URL");
var stateArray = window.location.hash.replace('#/HI/','').split('&');
stateArray.forEach(function(each){
var eachArray = each.split('=');
var dataChoice = dataChoices.find(function(obj){
return obj.short_name === eachArray[0];
});
var filterControlObj = filterView.filterControlsDict[dataChoice.short_name]

if ( dataChoice.component_type === 'continuous' ) {
var separator = eachArray[1].indexOf('-') !== -1 ? '-' : '_';
var values = eachArray[1].split(separator);
// set the values of the corresponding textInput
filterView.filterInputs[dataChoice.short_name].setValues([['min', +values[0]]],[['max', +values[1]]]);
if ( separator === '_') { // encoded nullShown == false
document.querySelector('[name="showNulls-' + dataChoice.source + '"]').checked = false;
setState('nullsShown.' + dataChoice.source, false); // this will eventually trigger the callback
// so no need to trigger it here
} else { // call the corresponding textInput's callback
filterView.filterInputs[dataChoice.short_name].callback();
}
var min = +values[0]
var max = +values[1]
var nullsShown = separator === '_' ? false : true;

filterControlObj.set(min,max,nullsShown);

}
if ( dataChoice.component_type === 'categorical' ){
Expand All @@ -95,7 +94,10 @@ var router = {
if ( dataChoice.component_type === 'date' ){
// handle decoding for date type filter here
var separator = eachArray[1].indexOf('-') !== -1 ? '-' : '_';
var nullsShown = separator === '_' ? false : true;
var values = eachArray[1].split(separator);

//Set the values on the input boxes
filterView.filterInputs[dataChoice.short_name].setValues(
[
[ 'year', values[0].match(/\d{4}/)[0] ],
Expand All @@ -108,13 +110,10 @@ var router = {
[ 'day', values[1].match(/d(\d+)/)[1]]
]
);
if ( separator === '_') { // encoded nullShown == false
document.querySelector('[name="showNulls-' + dataChoice.source + '"]').checked = false;
setState('nullsShown.' + dataChoice.source, false); // this will eventually trigger the callback
// so no need to trigger it here
} else { // call the corresponding textInput's callback
filterView.filterInputs[dataChoice.short_name].callback();
}
document.querySelector('[name="showNulls-' + dataChoice.source + '"]').checked = nullsShown;

//Commit the values using callback(), which will update slider to match.
filterView.filterInputs[dataChoice.short_name].callback();
}

});
Expand All @@ -137,26 +136,6 @@ var router = {
hashChangeHandler: function(){
controller.goBack();
},
nullsHandler: function (msg,data) { // for handling when a nullsShown checkbox is toggled. needs to grab values of the filter
var component = msg.split('.')[1];
var dataChoice = dataChoices.find(function(each){
return each.source === component;
});
var type = dataChoice.component_type;
var shortName = dataChoice.short_name;
if ( type === 'categorical' ) {
// i think this is a null set; no unknowns in categorical filters
}
if ( type === 'continuous' ) {

}
if ( type === 'date' ) {

}
if ( type === 'location' ) {
// to come
}
}

};
window.onhashchange = function() {
Expand Down
73 changes: 32 additions & 41 deletions docs/tool/js/util/filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,30 +54,18 @@ var filterUtil = {
filterData: function(){

var workingData = model.dataCollection['filterData'].objects;
var nullsShown = filterUtil.getNullsShown(); // creates object with nullShown state ofall filters
var filterValues = filterUtil.getFilterValues();

for (var key in nullsShown){
var component = (filterView.components.filter(function(obj){
return obj.source == key;
}))[0];

if (component.component_type == 'continuous' || component.component_type == 'date'){
workingData = workingData.filter(function(d){
return d[key] !== null || nullsShown[key][0]; // nullsShown[key][0] is true or fals; if false returns when d[key] is not null
});
}
}

for (key in filterValues) { // iterate through registered filters

//need to get the matching component out of a list of objects
var component = filterView.components.filter(function(obj) {
var component = dataChoices.filter(function(obj) {
return obj.source == key;
});
component = component[0] //the most recent filter (current and previous are returned in a list)
component = component[0]


if (component['component_type'] == 'continuous') { //filter data for a 'continuous' filter
if (component['component_type'] == 'continuous') {

if (filterValues[key][0].length == 0){
// don't filter because the filter has been removed.
Expand All @@ -86,41 +74,44 @@ var filterUtil = {
//javascript rounding is weird
var decimalPlaces = component['data_type'] == 'integer' ? 0 : 2 //ternary operator
var min = Number(Math.round(filterValues[key][0][0]+'e'+decimalPlaces) +'e-'+decimalPlaces);
var max =Number(Math.round(filterValues[key][0][1]+'e'+decimalPlaces)+'e-'+decimalPlaces);
var max = Number(Math.round(filterValues[key][0][1]+'e'+decimalPlaces)+'e-'+decimalPlaces);

//If it's a zone-level data set, need to choose the right column
var modifier = component.data_level == 'zone' ? ("_" + getState()['mapLayer'][0]) : ''

//filter it
workingData = workingData.filter(function(d){
// Refer to the third element of the most recent value of
// filterValues, which is a boolean indicating whether
// null values will be shown.
if(d[key] === null){
return nullsShown[key];
if(d[(key + modifier)] === null){
return filterValues[key][0][2]; //third element in array is true/false for nulls shown
}
return (d[key] >= min && d[key] <= max);
return (d[(key + modifier)] >= min && d[(key + modifier)] <= max);
});
};
}

if (component['component_type']== 'date') {

workingData = workingData.filter(function(d){
// Refer to the third element of the most recent value of
// filterValues, which is a boolean indicating whether
// null values will be shown.
if(d[key] === null){
return nullsShown[key];
}

// If the filter is 'empty' and the value isn't null,
// show the project.
if(filterValues[key][0].length === 0){
return true;
}

return d[key].valueOf() >= filterValues[key][0][0].valueOf()
&& d[key].valueOf() <= filterValues[key][0][1].valueOf();
});
if (filterValues[key][0].length == 0){
// don't filter because the filter has been removed.
// The length would be '1' because nullsShown is always included.
} else {
workingData = workingData.filter(function(d){
// Refer to the third element of the most recent value of
// filterValues, which is a boolean indicating whether
// null values will be shown.
if(d[key] === null){
return filterValues[key][0][2];
}

// If the filter is 'empty' and the value isn't null,
// show the project.
if(filterValues[key][0].length === 0){
return true;
}

return d[key].valueOf() >= filterValues[key][0][0].valueOf()
&& d[key].valueOf() <= filterValues[key][0][1].valueOf();
});
}
}

if (component['component_type'] == 'categorical') {
Expand Down
Loading