Make legend much more clever regarding filters, use positive filters if they are shorter

pull/54/merge
Candid Dauth 2016-10-26 16:26:57 +03:00
rodzic 05fe205e40
commit 68857c0d0e
3 zmienionych plików z 106 dodań i 26 usunięć

Wyświetl plik

@ -1,6 +1,6 @@
(function(fm, $, ng, undefined) {
fm.app.factory("fmFilter", function(compileExpression, $rootScope, $uibModal) {
fm.app.factory("fmFilter", function(compileExpression, $rootScope, $uibModal, fmUtils) {
var currentVal;
var fmFilter = {
@ -53,26 +53,66 @@
return '"' + (""+str).replace(/["\\]/g, '\\\1').replace(/\n/g, "\\n") + '"';
},
_getMatchesWithBrackets: function(str, regexp) {
var ret = [ ];
(str || "").replace(new RegExp(regexp, "gi"), function() {
var offset = arguments[arguments.length-2];
var match = arguments[0];
var open = match.match(/\(/g);
var close = match.match(/\)/g);
var needBraces = (open ? open.length : 0) - (close ? close.length : 0);
for(var i=offset+match.length; i<str.length && needBraces > 0; i++) {
if(str[i] == "(")
needBraces++;
else if(str[i] == ")")
needBraces--;
}
ret.push(str.substring(offset, i));
});
return ret;
},
makeTypeFilter: function(previousFilter, typeId, filteredData) {
function removePart(str, regexp) {
return (str || "")
.replace(new RegExp("^" + regexp + "($|\\s+and\\s+|\\s+or\\s+)", "ig"), "")
.replace(new RegExp("\\s+(and|or)\\s+" + regexp + "($|[^0-9a-z])", "ig"), function() { return arguments[arguments.length-3]; })
str = str || "";
regexp.forEach(function(r) {
str = str
.replace(new RegExp("^" + r + "($|\\s+and\\s+|\\s+or\\s+)", "ig"), "")
.replace(new RegExp("\\s+(and|or)\\s+" + r + "($|[^0-9a-z])", "ig"), function() { return arguments[arguments.length-3]; })
});
return str;
}
var ret = removePart(previousFilter, "typeId\\s*!=\\s*" + typeId);
ret = removePart(ret, "not\\s+\\(typeId\\s*==\\s*" + typeId + "\\s([^\\(\\)]*(\\([^\\)]*\\))?)*?\\)");
var ret = removePart(previousFilter,
fmFilter._getMatchesWithBrackets(previousFilter, "(not\\s+)?\\(typeId\\s*==\\s*" + typeId).map(fmUtils.quoteRegExp)
.concat([ "typeId\\s*[!=]=\\s*" + typeId ]));
if(typeof filteredData == "boolean") {
if(filteredData)
ret = (ret ? ret + " and " : "") + "typeId!=" + typeId;
} else {
var append = [ ];
for(var i in filteredData)
append.push("prop(data," + fmFilter.quote(i) + ")" + (filteredData[i].length > 1 ? " in (" + filteredData[i].map(fmFilter.quote).join(",") + ")" : "==" + fmFilter.quote(filteredData[i][0])));
for(var i in filteredData) {
var no = Object.keys(filteredData[i]).filter(function(it) { return !filteredData[i][it]; });
var yes = Object.keys(filteredData[i]).filter(function(it) { return filteredData[i][it]; });
if(no.length == 0) // No item is not filtered, so we can filter the whole type
return (ret ? ret + " and " : "") + "typeId!=" + typeId;
if(yes.length > 0) {
var negative = "prop(data," + fmFilter.quote(i) + ")" + (no.length > 1 ? " not in (" + no.map(fmFilter.quote).join(",") + ")" : "!=" + fmFilter.quote(no[0]));
var positive = "prop(data," + fmFilter.quote(i) + ")" + (yes.length > 1 ? " in (" + yes.map(fmFilter.quote).join(",") + ")" : "==" + fmFilter.quote(yes[0]));
append.push(negative.length < positive.length ? negative : positive);
}
}
if(append.length > 0)
ret = (ret ? ret + " and " : "") + "not (typeId=="+typeId+" and " + append.join(" or ") + ")";
ret = (ret ? ret + " and " : "") + "not (typeId=="+typeId+" and " + (append.length > 1 ? "(" + append.join(" or ") + ")" : append[0]) + ")";
}
return ret;

Wyświetl plik

@ -2,9 +2,9 @@
<div class="panel panel-default" ng-show="legendItems.length > 0" ng-class="{'hidden-xs': !showXs}">
<div class="panel-body">
<hr ng-repeat-start="type in legendItems" ng-if="$index > 0">
<h3 ng-click="toggleFilter(type)" ng-class="{ filtered: isFiltered(type) }">{{type.name}}</h3>
<h3 ng-click="toggleFilter(type)" ng-class="{ filtered: type.filtered }">{{type.name}}</h3>
<dl ng-repeat-end>
<dt ng-repeat-start="item in type.items" ng-class="thisClass = [ 'fm-' + type.type, { filtered: isFiltered(type, item) } ]" ng-click="toggleFilter(type, item)" ng-bind-html="item | fmMapLegendMakeSymbol:type.type"></dt>
<dt ng-repeat-start="item in type.items" ng-class="thisClass = [ 'fm-' + type.type, { filtered: item.filtered } ]" ng-click="toggleFilter(type, item)" ng-bind-html="item | fmMapLegendMakeSymbol:type.type"></dt>
<dd ng-repeat-end ng-class="thisClass" ng-click="toggleFilter(type, item)">{{item.value}}</dd>
</dl>
</div>

Wyświetl plik

@ -9,6 +9,7 @@
for(var i in scope.types) {
var type = scope.types[i];
var items = [ ];
var fields = { };
if(type.colourFixed || (type.type == "marker" && type.symbolFixed && type.defaultSymbol && fmIcons[type.defaultSymbol]) || (type.type == "line" && type.widthFixed)) {
var item = { value: type.name };
@ -27,8 +28,10 @@
if(!field.type == "dropdown" || (!field.controlColour && (type.type != "marker" || !field.controlSymbol) && (type.type != "line" || !field.controlWidth)))
return;
fields[field.name] = [ ];
(field.options || [ ]).forEach(function(option) {
var item = { value: option.value, field: field.name };
var item = { value: option.value, field: field.name, filtered: true };
if(field.controlColour)
item.colour = option.colour;
if(type.type == "marker" && field.controlSymbol)
@ -36,17 +39,41 @@
if(type.type == "line" && field.controlWidth)
item.width = option.width;
items.push(item);
fields[field.name].push(item.value);
});
});
if(items.length > 0)
scope.legendItems.push({ type: type.type, typeId: type.id, name: type.name, items: items });
if(items.length > 0) {
var type = { type: type.type, typeId: type.id, name: type.name, items: items, filtered: true };
// Check which fields are filtered
_allCombinations(fields, function(data) {
if(map.socket.filterFunc({ typeId: type.typeId, data: data }, true)) {
type.filtered = false;
items.forEach(function(it) {
if(!it.field)
it.filtered = false;
});
for(var i in data) {
items.forEach(function(it) {
if(it.field == i && it.value == data[i])
it.filtered = false;
});
}
}
});
scope.legendItems.push(type);
}
}
}
update();
scope.$watch("types", update, true);
map.socket.on("filter", update);
var el = $($templateCache.get("map/legend/legend.html")).insertAfter(map.map.getContainer());
$compile(el)(scope);
@ -61,28 +88,41 @@
el.css("max-height", getMaxHeight()+"px");
}
function _allCombinations(fields, cb) {
var fieldKeys = Object.keys(fields);
function rec(i, vals) {
if(i == fieldKeys.length)
return cb(vals);
var field = fields[fieldKeys[i]];
for(var j=0; j<field.length; j++) {
vals[fieldKeys[i]] = field[j];
rec(i+1, vals);
}
}
rec(0, { });
}
resize();
scope.$watch(getMaxHeight, resize);
$(window).resize(resize);
scope.isFiltered = function(typeInfo, item) {
var obj = { typeId: typeInfo.typeId, data: { } };
if(item && item.field)
obj.data[item.field] = item.value;
return !map.socket.filterFunc(obj, true);
};
scope.toggleFilter = function(typeInfo, item) {
var filters = { };
if(!item || !item.field) // We are toggling the visibility of one whole type
filters = !scope.isFiltered(typeInfo, item);
filters = !typeInfo.filtered;
else {
typeInfo.items.forEach(function(it) {
if(it.field && scope.isFiltered(typeInfo, it) == (it != item)) {
if(it.field) {
if(!filters[it.field])
filters[it.field] = [ ];
filters[it.field].push(it.value);
filters[it.field] = { };
if(!typeInfo.filtered || it.field == item.field)
filters[it.field][it.value] = (it.filtered == (it != item));
else // If the whole type is filtered, we have to enable the filters of the other fields, otherwise the type will still be completely filtered
filters[it.field][it.value] = false;
}
});
}