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

Filters #42

Merged
merged 53 commits into from
Dec 14, 2015
Merged

Filters #42

merged 53 commits into from
Dec 14, 2015

Conversation

fdansv
Copy link
Contributor

@fdansv fdansv commented Dec 2, 2015

No description provided.

@@ -3,6 +3,7 @@ var cartodb = global.cartodb || {};
var carto = global.carto || require('carto');
var _ = global._ || require('underscore');
var geo = require("./geo");
var Filter = require("./filter");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why the Filter is part of the renderer, and why is renderer responsible of adding tiles to the Filter. I'd expect someone listening to the "tileAdded" event to load the data into an object where you can add filters.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me it's a bit strange as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The layer (CartoDBd3Layer) receives a geojson tile that contains/can contain several layers, each one of which is represented by a renderer. Keeping that in mind, would it be better to create a new class, called something along the lines of sublayer, and restrict the functions of the renderer to generating the svgs?

@alonsogarciapablo
Copy link
Contributor

I've been looking into this a bit and I still don't quite get the concept of Filter and Expressions. Perhaps it's because of the naming...

I would expect to have an object where you could add filters and query for the stuff we need for widgets. Here's some pseudocode (I called this object datasource but it could be whatever makes sense)


// { city: 'Madrid', population: '10' }
// { city: 'Barcelona', population: '20' }
// { city: 'A Coruña', population: '30' }

var categoryFilter = new CategoryFilter({
  widgetId: 'myWidget1', // not sure if this is needed
  column: 'city'
});
var rangeFilter = new RangeFilter({
  widgetId: 'myWidget2', // not sure if this is needed
  column: 'population',
  bins: 10
});

datasource.addFilter(rangeFilter);
datasource.addFilter(categoryFilter);

datasource.getCategories('myWidget1');
// It would return all cities

// Change the filter
rangeFilter.setRange(20, 30);

datasource.getCategories(); // not sure if it would need more paremeters like the column or widgetId
// It would return "Barcelona" and "A Coruña"

categoryFilter.reject("Barcelona")

datasource.getCategories(); // not sure if it would need more paremeters like the column or widgetId
// It would return "A Coruña"

datasource.getHistogram();

@fdansv
Copy link
Contributor Author

fdansv commented Dec 4, 2015

@alonsogarciapablo right now I'm focusing more on the logic of the widgets themselves, but here's the way I'm seeing it:

  • First of all, I called expressions to the declarations of widgets within the filtering system. It's probably a poor choice of naming and I'm open to changing it, or just call them widgets as well. For the sake of clarity I'll refer to them here as expressions.
  • When we add a new expression, we're asking the Filter to "keep track" of the column, with the parameters indicated. The main reason why we do this is that we have to instantiate a dimension with it. Dimensions are super expensive to create and store, so we can't just create new ones as the widgets ask for data.
filter.addExpression("category_de_pepito", {
  type: "aggregation",
  column: "sov_a3",
  aggregation: "count",
  sync: true
});
  • When we keep track of that column with those parameters, this means that their returning data will be recalculated each time filter.update() is run. What that method does is iterating over the list of expressions that have been declared and run the function that was generated for each one at the moment of the declaration (see this, for example).
filter.update();
// will update the this.report object, and return:

{
  "category_de_pepito": {
    "count": 2722,
    "nulls": 0,
    "min": 1,
    "max": 145,
    "categoriesCount": 136,
    "categories": [
      {
        "category": "RUS",
        "value": 145,
        "agg": false
      }, // [...]
    ],
    "type": "aggregation"
  }
}
  • As soon as I'm done coding histograms I'll add both a method to filter according the widget id and a getter function for the widgets to use.

@javisantana
Copy link
Contributor

I think the abstraction level for that api is higher than expected. I'd expect something like:

for filtering:

layer.filterRange('attr', [0, 1000]);
layer.filterExact('attr2', 'pepe');
layer.clearFilters();

and to fetch data:

var values = layer.getValues('attr') // values will contain the attr values with the current filters applied
// histogram other stuff is calculated outside the layer

@fdansv
Copy link
Contributor Author

fdansv commented Dec 7, 2015

OK so now the Filter is connected to the rendering part so setting a filter with the API above will reflect those changes on the map, which will update when renderer.redraw() is called. Also I added a few tests to provide good coverage for filters.

@rochoa @alonsogarciapablo this is ready to merge my side.

function Filter(){
this.crossfilter = new Crossfilter();
this.dimensions = {};
this.tiles = new Set();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Set is a ES6 feature. We need another approach or a polyfill.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌 just changed this, it was supposed to be a temporary use. Replaced it with a normal JS object.

@rochoa
Copy link
Contributor

rochoa commented Dec 7, 2015

As per previous comments I was expecting to have the filtering at layer level, but probably I'm missing something here.

Having some kind of integration test would help a lot to understand the layer high level API.

}

WindshaftProvider.prototype = {
initialize: function(){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we have this in the constructor? Is there any other way to use initialize apart from being called from the constructor?


},

filterReject: function(column, terms){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All filter* functions are quite similar. Wouldn't be better to have a generic filter function that receives the actual filter function and applies it by column?

Or even better, having different filter entities with a common interface that can be applied. Having different functions for different types of filters means that we need as many public functions as filters we want to have.

@rochoa
Copy link
Contributor

rochoa commented Dec 9, 2015

I'm still missing some tests or examples displaying how the layer/high level API is.

fdansv added a commit that referenced this pull request Dec 14, 2015
@fdansv fdansv merged commit 85f80e0 into gh-pages Dec 14, 2015
@fdansv fdansv deleted the crossfilter branch March 11, 2016 11:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants