now as kick ass histogram to represent the numeric fields!

This commit is contained in:
didier.amyot committed 2014-05-24 12:51:25 -04:00
1 parent 082157bc5f
commit b81606fd27
6 files changed
+136 -80

No files matched your search

+65
View File
@@ -158,6 +158,33 @@ func (this *VultureBackend) GetStat(key string) (interface{}, error) {
return <-out, nil return <-out, nil
} }
func (this *VultureBackend) GetHistogram(key string, min, max float64, numberOfBins int) (interface{}, error) {
hm := &HistogramMaker{min, max, numberOfBins}
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 *Histogram)
go hm.MakeHistogram(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) { func (this *VultureBackend) GetAllDocuments(query interface{}) (interface{}, error) {
var result []map[string]interface{} var result []map[string]interface{}
@@ -282,3 +309,41 @@ func getStats(w http.ResponseWriter, request *http.Request) (interface{}, error)
} }
return vb.GetStat(key) return vb.GetStat(key)
} }
func getHistogram(w http.ResponseWriter, request *http.Request) (interface{}, error) {
vb, err := GetVultureBackend(request)
vars := mux.Vars(request)
smin, ok := vars["min"]
if !ok {
return nil, errors.New("min field not specified")
}
min, err := strconv.ParseFloat(smin, 64)
if err != nil {
return nil, errors.New("min value is not an float64")
}
smax, ok := vars["max"]
if !ok {
return nil, errors.New("max field not specified")
}
max, err := strconv.ParseFloat(smax, 64)
if err != nil {
return nil, errors.New("max value is not an float64")
}
sNumberOfBins, ok := vars["number_of_bins"]
if !ok {
return nil, errors.New("number_of_bins field not specified")
}
numberOfBins, err := strconv.Atoi(sNumberOfBins)
if err != nil {
return nil, errors.New("numberOfBins value is not an int")
}
key, ok := vars["key"]
if !ok {
return nil, errors.New("key field not specified")
}
return vb.GetHistogram(key, min, max, numberOfBins)
}
+24 -8
View File
@@ -1,5 +1,6 @@
root = this; root = this;
humanValueOfStatisticalAbreviation = humanValueOfStatisticalAbreviation =
mean: "mean" mean: "mean"
std: "standard deviation" std: "standard deviation"
@@ -8,6 +9,11 @@ humanValueOfStatisticalAbreviation =
max: "maximum" max: "maximum"
n: "number of documents" n: "number of documents"
findHistogramMin = (stats) ->
return Math.max(stats.min, stats.mean - 3 * stats.std)
findHistogramMax = (stats) ->
return Math.min(stats.max, stats.mean + 3 * stats.std)
getAxisLabel = (min, stepSize, numberOfBins) -> getAxisLabel = (min, stepSize, numberOfBins) ->
@@ -17,13 +23,9 @@ root.controllers.controller('keyStatCtrl', ['$scope', '$routeParams', 'util', ($
$scope.name = $routeParams.key $scope.name = $routeParams.key
$scope.stats = [] $scope.stats = []
url = "/api/#{ $routeParams.server }/#{ $routeParams.database }/#{ $routeParams.collection }" base_url = "/api/#{ $routeParams.server }/#{ $routeParams.database }/#{ $routeParams.collection }"
url = "#{ url }/stats/#{$routeParams.key}" stat_url = "#{ base_url }/stats/#{$routeParams.key}"
util.get(url).then((res) ->
for key, val of res.data
$scope.stats.push({name: humanValueOfStatisticalAbreviation[key], value: val })
)
$scope.chartConfig = $scope.chartConfig =
options: options:
@@ -35,15 +37,29 @@ root.controllers.controller('keyStatCtrl', ['$scope', '$routeParams', 'util', ($
pointPadding: 0 pointPadding: 0
borderWidth: 0 borderWidth: 0
xAxis: xAxis:
categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] categories: []
title: title:
text: 'Distribution' text: 'Distribution'
loading: false loading: false
series: [{ series: [{
data: [10, 15, 12, 8, 7, 4, 12, 12, 9, 11], data: [],
name: $routeParams.key name: $routeParams.key
color: '#008CBA' color: '#008CBA'
}] }]
util.get(stat_url).then((res) ->
for key, val of res.data
$scope.stats.push({name: humanValueOfStatisticalAbreviation[key], value: val })
numberOfBins = 50
min = findHistogramMin(res.data)
max = findHistogramMax(res.data)
histogram_url = "#{ base_url }/histogram/#{$routeParams.key}/#{min}/#{max}/#{numberOfBins}"
return util.get(histogram_url)
).then((res) ->
$scope.chartConfig.series[0].data = res.data.values
$scope.chartConfig.xAxis.categories = getAxisLabel(res.data.min, res.data.step_size, res.values.length)
)
]) ])
+1
View File
@@ -92,6 +92,7 @@ func serveApi(router *mux.Router) {
router.Handle("/{server}/{database}/{collection}/idx/{index:[0-9]+}", ApiHandler(getDocumentByIndex)) 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}/_id/{id}", ApiHandler(getDocumentById))
router.Handle("/{server}/{database}/{collection}/stats/{key}", ApiHandler(getStats)) router.Handle("/{server}/{database}/{collection}/stats/{key}", ApiHandler(getStats))
router.Handle("/{server}/{database}/{collection}/histogram/{key}/{min}/{max}/{number_of_bins}", ApiHandler(getHistogram))
router.Handle("/{server}/{database}/{collection}/all", ApiHandler(getAllDocuments)) 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}/idx/{index:[0-9]+}", ApiHandler(getDocumentByQueryAndIndex))
router.Handle("/{server}/{database}/{collection}/query/{query}/all", ApiHandler(getAllDocuments)) router.Handle("/{server}/{database}/{collection}/query/{query}/all", ApiHandler(getAllDocuments))
+4
View File
@@ -21,3 +21,7 @@ MongoServers:
- -
name: mongolab name: mongolab
url: mongodb://ustraca:9OApiiV9En85bBFGfVyYKH4C@ds049288.mongolab.com:49288/ustraca url: mongodb://ustraca:9OApiiV9En85bBFGfVyYKH4C@ds049288.mongolab.com:49288/ustraca
-
name: miaou
url: 192.168.1.10:27017
+38 -36
View File
@@ -2,6 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"log"
"math" "math"
) )
@@ -17,6 +18,43 @@ type Histogram struct {
Values []int `json:"values"` Values []int `json:"values"`
} }
type HistogramMaker struct {
Min float64
Max float64
NumberOfBins int
}
func (this *HistogramMaker) findSuitableBinForDatum(min, step, val float64) int {
var i int
var act float64 = min
for i = 0; act <= val; act += step {
i++
}
return i - 1
}
func (this *HistogramMaker) MakeHistogram(dataChannel chan float64, outputChannel chan *Histogram) {
step := (this.Max - this.Min) / float64(this.NumberOfBins)
hist := make([]int, this.NumberOfBins)
for datum := range dataChannel {
if datum < this.Min || datum > this.Max {
continue
}
i := this.findSuitableBinForDatum(this.Min, step, datum)
if i >= len(hist) || i < 0 {
log.Println("what the fuck?!", this.Min, step, datum, i)
} else {
hist[i]++
}
}
outputChannel <- &Histogram{this.Min, step, hist}
}
type Stats struct { type Stats struct {
Mean float64 `json:"mean"` Mean float64 `json:"mean"`
Var float64 `json:"var"` Var float64 `json:"var"`
@@ -26,42 +64,6 @@ type Stats struct {
N int `json:"n"` N int `json:"n"`
} }
func (this *Stats) findMinHistogramValue() float64 {
return math.Max(this.Min, this.Mean-3*this.Std)
}
func (this *Stats) findMaxHistogramValue() float64 {
return math.Min(this.Max, this.Mean+3*this.Std)
}
func (this *Stats) findSuitableBinForDatum(min, step, val float64) int {
var i int
var act float64 = min
for i = 0; act < val; act += step {
i++
}
return i - 1
}
func (this *Stats) MakeHistogram(numberOfBins int, dataChannel chan float64, outputChannel chan *Histogram) {
min := this.findMinHistogramValue()
max := this.findMaxHistogramValue()
step := (max - min) / float64(numberOfBins)
hist := make([]int, numberOfBins)
for datum := range dataChannel {
if datum < min || datum > max {
continue
}
i := this.findSuitableBinForDatum(min, step, datum)
hist[i]++
}
outputChannel <- &Histogram{min, step, hist}
}
type StatAggregator struct { type StatAggregator struct {
sum float64 sum float64
sumOfTheSquares float64 sumOfTheSquares float64
+4 -36
View File
@@ -62,43 +62,8 @@ func TestGetFinalStats(t *testing.T) {
} }
} }
func TestFindMinHistogramValue(t *testing.T) {
stats := &Stats{
Mean: 10,
Std: 2,
Min: 1,
}
if stats.findMinHistogramValue() != 4 {
t.Fail()
}
stats.Min = 7
if stats.findMinHistogramValue() != 7 {
t.Fail()
}
}
func TestFindMaxHistogramValue(t *testing.T) {
stats := &Stats{
Mean: 10,
Std: 2,
Max: 22,
}
if stats.findMaxHistogramValue() != 16 {
t.Fail()
}
stats.Max = 14
if stats.findMaxHistogramValue() != 14 {
t.Fail()
}
}
func TestFindSuitableBinForDatum(t *testing.T) { func TestFindSuitableBinForDatum(t *testing.T) {
stats := new(Stats) stats := new(HistogramMaker)
if bin := stats.findSuitableBinForDatum(-4, 1, -3.2); bin != 0 { if bin := stats.findSuitableBinForDatum(-4, 1, -3.2); bin != 0 {
t.Error("wrong bin: ", bin) t.Error("wrong bin: ", bin)
} }
@@ -110,5 +75,8 @@ func TestFindSuitableBinForDatum(t *testing.T) {
if bin := stats.findSuitableBinForDatum(-4, 1, 10.1); bin != 14 { if bin := stats.findSuitableBinForDatum(-4, 1, 10.1); bin != 14 {
t.Error("wrong bin: ", bin) t.Error("wrong bin: ", bin)
} }
if bin := stats.findSuitableBinForDatum(1, 50, 1); bin != 0 {
t.Error("wrong bin: ", bin)
}
} }