diff --git a/api.go b/api.go index 9f2a51c..7a0ae61 100644 --- a/api.go +++ b/api.go @@ -131,6 +131,33 @@ func (this *VultureBackend) wrapDocumentWithMetadata(doc interface{}, query *mgo return ret, nil } +func (this *VultureBackend) GetStat(key string) (interface{}, error) { + s := NewStatAggregator() + query := bson.M{key: bson.M{"$exists": true}} + iter := this.Collection.Find(query).Select(bson.M{key: 1}).Iter() + result := make(map[string]interface{}) + + in := make(chan float64) + out := make(chan *Stats) + go s.AddStats(in, out) + + for iter.Next(&result) { + + if val, ok := result[key]; ok { + if fval, isFloat := val.(float64); isFloat { + in <- fval + } else if ival, isInt := val.(int); isInt { + in <- float64(ival) + } + } + } + close(in) + if err := iter.Close(); err != nil { + return nil, err + } + return <-out, nil +} + func (this *VultureBackend) GetAllDocuments(query interface{}) (interface{}, error) { var result []map[string]interface{} @@ -241,3 +268,17 @@ func getDocumentByQueryAndIndex(w http.ResponseWriter, request *http.Request) (i } return vb.GetDocumentByIndex(index, query) } + +func getStats(w http.ResponseWriter, request *http.Request) (interface{}, error) { + vb, err := GetVultureBackend(request) + if err != nil { + return nil, err + } + + vars := mux.Vars(request) + key, ok := vars["key"] + if !ok { + return nil, errors.New("key field not specified") + } + return vb.GetStat(key) +} diff --git a/client/coffee/controllers/keyStatCtrl.coffee b/client/coffee/controllers/keyStatCtrl.coffee new file mode 100644 index 0000000..f455c7b --- /dev/null +++ b/client/coffee/controllers/keyStatCtrl.coffee @@ -0,0 +1,22 @@ +root = this; + +humanValueOfStatisticalAbreviation = + mean: "mean" + std: "standard deviation" + var: "variance" + + +root.controllers.controller('keyStatCtrl', ['$scope', '$routeParams', '$http', ($scope, $routeParams, $http) -> + $scope.name = "didier!" + $scope.stats = [] + + url = "/api/#{ $routeParams.server }/#{ $routeParams.database }/#{ $routeParams.collection }" + url = "#{ url }/stats/#{$routeParams.key}" + + $http.get(url).then((res) -> + for key, val of res.data + $scope.stats.push({name: humanValueOfStatisticalAbreviation[key], value: val }) + ) + +]) + diff --git a/client/coffee/directives/key.coffee b/client/coffee/directives/key.coffee index 7d55c84..827f426 100644 --- a/client/coffee/directives/key.coffee +++ b/client/coffee/directives/key.coffee @@ -15,6 +15,10 @@ hasIndexDesc = (keyName, indexes) -> has2dIndex = (keyName, indexes) -> hasIndex("$2dsphere:#{keyName}", indexes) +getStatsUrl = (keyName, routeParams)-> + r = routeParams + "#/#{r.server}/#{r.database}/#{r.collection}/stats/#{ keyName }" + directive = () -> directive = templateUrl: "/partials/directives/key.html" @@ -27,6 +31,7 @@ directive = () -> indexes: "=" controller:["$scope", "$routeParams", ($scope, $routeParams) -> $scope.canMakeHistogram = _.isNumber($scope.value) + $scope.statsUrl = getStatsUrl($scope.keyName, $routeParams) $scope.hasIndexDesc = hasIndexDesc($scope.keyName, $scope.indexes) $scope.hasIndexAsc = hasIndexAsc($scope.keyName, $scope.indexes) $scope.has2DIndex = has2dIndex($scope.keyName, $scope.indexes) diff --git a/client/coffee/vultureApp.coffee b/client/coffee/vultureApp.coffee index feb39c6..e8542f0 100644 --- a/client/coffee/vultureApp.coffee +++ b/client/coffee/vultureApp.coffee @@ -18,6 +18,7 @@ config(['$routeProvider', ($routeProvider) -> $routeProvider.when('/:server/:database/:collection/_id/:id', {templateUrl: 'partials/document.html', controller: 'documentCtrl'}) $routeProvider.when('/:server/:database/:collection/_id/:id/field/:field', {templateUrl: 'partials/field.html', controller: 'fieldCtrl'}) $routeProvider.when('/:server/:database/:collection/idx/:idx', {templateUrl: 'partials/document.html', controller: 'documentCtrl'}) + $routeProvider.when('/:server/:database/:collection/stats/:key', {templateUrl: 'partials/keyStat.html', controller: 'keyStatCtrl'}) $routeProvider.when('/:server/:database/:collection/idx/:idx/query/:query', {templateUrl: 'partials/document.html', controller: 'documentCtrl'}) $routeProvider.when('/:server/:database/:collection/idx/all/geojson/:key', {templateUrl: 'partials/geojsonmap.html', controller: 'geojsonmapCtrl'}) $routeProvider.when('/:server/:database/:collection/idx/:idx/geojson/:key', {templateUrl: 'partials/geojsonmap.html', controller: 'geojsonmapCtrl'}) diff --git a/client/index.html b/client/index.html index c0f86b2..6ca2e58 100644 --- a/client/index.html +++ b/client/index.html @@ -31,7 +31,7 @@
@@ -60,6 +60,7 @@ + diff --git a/client/partials/directives/key.html b/client/partials/directives/key.html index 99872b7..bbe9b02 100644 --- a/client/partials/directives/key.html +++ b/client/partials/directives/key.html @@ -3,14 +3,11 @@ - + - - - \ No newline at end of file diff --git a/client/partials/keyStat.html b/client/partials/keyStat.html new file mode 100644 index 0000000..f1cb921 --- /dev/null +++ b/client/partials/keyStat.html @@ -0,0 +1,11 @@ + +
+
+ +
+
\ No newline at end of file diff --git a/main.go b/main.go index 93502a4..879598f 100644 --- a/main.go +++ b/main.go @@ -91,6 +91,7 @@ func serveApi(router *mux.Router) { router.Handle("/{server}/{database}/collections", ApiHandler(getAvailableCollections)) router.Handle("/{server}/{database}/{collection}/idx/{index:[0-9]+}", ApiHandler(getDocumentByIndex)) router.Handle("/{server}/{database}/{collection}/_id/{id}", ApiHandler(getDocumentById)) + router.Handle("/{server}/{database}/{collection}/stats/{key}", ApiHandler(getStats)) router.Handle("/{server}/{database}/{collection}/all", ApiHandler(getAllDocuments)) router.Handle("/{server}/{database}/{collection}/query/{query}/idx/{index:[0-9]+}", ApiHandler(getDocumentByQueryAndIndex)) router.Handle("/{server}/{database}/{collection}/query/{query}/all", ApiHandler(getAllDocuments)) diff --git a/params.yml b/params.yml index 3fad683..938d8ee 100644 --- a/params.yml +++ b/params.yml @@ -18,6 +18,6 @@ MongoServers: - name: localhost url: localhost:27017 - # - - # name: example - # url: mongodb://username:password@ds90210.mongolab.com:52652/database_name + - + name: mongolab + url: mongodb://ustraca:9OApiiV9En85bBFGfVyYKH4C@ds049288.mongolab.com:49288/ustraca diff --git a/stats.go b/stats.go index b90f015..af0e185 100644 --- a/stats.go +++ b/stats.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "math" ) var _ = fmt.Print @@ -10,6 +11,12 @@ const initialLength int = 1024 const verySmall float64 = -100000 const veryBig float64 = 100000 +type Stats struct { + Mean float64 `json:"mean"` + Var float64 `json:"var"` + Std float64 `json:"std"` +} + type StatAggregator struct { sum float64 sumOfTheSquares float64 @@ -19,8 +26,6 @@ type StatAggregator struct { max float64 } -type Stats struct{} - func NewStatAggregator() *StatAggregator { return &StatAggregator{0, 0, 0, make([]float64, initialLength), veryBig, verySmall} } @@ -37,5 +42,15 @@ func (this *StatAggregator) AddStats(dataChannel chan float64, output chan *Stat this.max = datum } } - output <- nil + output <- this.getFinalStats() +} + +func (this *StatAggregator) getFinalStats() *Stats { + stats := new(Stats) + stats.Mean = this.sum / float64(this.n) + stats.Var = this.sumOfTheSquares/float64(this.n) - stats.Mean*stats.Mean + stats.Std = math.Sqrt(stats.Var) + + return stats + } diff --git a/stats_test.go b/stats_test.go index c10951e..01019b3 100644 --- a/stats_test.go +++ b/stats_test.go @@ -33,3 +33,20 @@ func TestAddStatsBasic(t *testing.T) { } } + +func TestGetFinalStats(t *testing.T) { + s := NewStatAggregator() + s.sumOfTheSquares = 5000 + s.sum = 200 + s.n = 10 + stats := s.getFinalStats() + if stats.Mean != 20. { + t.Fail() + } + if stats.Var != 100. { + t.Fail() + } + if stats.Std != 10. { + t.Fail() + } +}