Work on modes
This commit is contained in:
parent
7d3ea8e5dd
commit
cf586e853a
21
server.go
21
server.go
@ -52,38 +52,33 @@ func executeQuery(rw http.ResponseWriter, req *http.Request) {
|
||||
|
||||
entries := getRecords(queryContext{geo, request.Profile, request.WalkingDist})
|
||||
features := fixFeatures(request.Features)
|
||||
modes := fixModes(request.Modes)
|
||||
|
||||
minScore := request.MinScore
|
||||
if request.Bracket != nil {
|
||||
bracket := namedBracket{
|
||||
request.Bracket.Name,
|
||||
request.Bracket.Min,
|
||||
request.Bracket.Max}
|
||||
|
||||
minScore = calibrateMinScore(entries, features, bracket)
|
||||
}
|
||||
|
||||
foundEntries := findRecords(entries, features, minScore)
|
||||
foundEntries := findRecords(entries, features, modes, request.MinScore)
|
||||
sorter := recordSorter{entries: foundEntries, key: request.SortKey, ascending: request.SortAsc}
|
||||
sorter.sort()
|
||||
|
||||
response := jsonQueryResponse{
|
||||
Count: len(foundEntries),
|
||||
Columns: make(map[string]jsonColumn),
|
||||
MinScore: minScore,
|
||||
MinScore: request.MinScore,
|
||||
Records: make([]jsonRecord, 0)}
|
||||
|
||||
for name, value := range features {
|
||||
mode, _ := modes[name]
|
||||
|
||||
column := jsonColumn{
|
||||
Bracket: jsonBracket{Max: -1, Min: 1},
|
||||
Mode: mode.String(),
|
||||
Value: value,
|
||||
Steps: request.Resolution}
|
||||
|
||||
hints := project(
|
||||
entries,
|
||||
features,
|
||||
modes,
|
||||
name,
|
||||
minScore,
|
||||
request.MinScore,
|
||||
request.Resolution)
|
||||
|
||||
for _, hint := range hints {
|
||||
|
@ -88,7 +88,7 @@
|
||||
var _height = 500;
|
||||
var _padding = 10;
|
||||
var _panelSize = 20;
|
||||
var _buttonSize = 20;
|
||||
var _modeSize = 20;
|
||||
var _tickSize = 5;
|
||||
var _width = 120;
|
||||
|
||||
@ -111,7 +111,7 @@
|
||||
_densitySize,
|
||||
0,
|
||||
_width - (_bracketSize + _densitySize),
|
||||
_height - (_panelSize + _buttonSize)
|
||||
_height - (_panelSize + _modeSize)
|
||||
).attr({
|
||||
cursor: 'crosshair',
|
||||
stroke: _borderColor,
|
||||
@ -123,7 +123,7 @@
|
||||
0,
|
||||
0,
|
||||
_densitySize,
|
||||
_height - (_panelSize + _buttonSize)
|
||||
_height - (_panelSize + _modeSize)
|
||||
).attr({
|
||||
stroke: _borderColor
|
||||
});
|
||||
@ -131,7 +131,7 @@
|
||||
// panel
|
||||
_elements.panel = _canvas.rect(
|
||||
0,
|
||||
_height - (_panelSize + _buttonSize),
|
||||
_height - (_panelSize + _modeSize),
|
||||
_width - _bracketSize,
|
||||
_panelSize
|
||||
).attr({
|
||||
@ -141,7 +141,7 @@
|
||||
// label
|
||||
_elements.label = _canvas.text(
|
||||
(_width - _bracketSize) / 2,
|
||||
_height - (_panelSize / 2 + _buttonSize),
|
||||
_height - (_panelSize / 2 + _modeSize),
|
||||
_name
|
||||
).attr({
|
||||
'dominant-baseline': 'middle',
|
||||
@ -153,10 +153,10 @@
|
||||
_width - _bracketSize,
|
||||
0,
|
||||
_bracketSize,
|
||||
_height - (_panelSize + _buttonSize)
|
||||
_height - (_panelSize + _modeSize)
|
||||
).attr({
|
||||
fill: _bracketColorBg
|
||||
}).click(bracketClick);
|
||||
});
|
||||
|
||||
// indiciator
|
||||
updateIndicator(_data.value);
|
||||
@ -191,7 +191,7 @@
|
||||
_elements.panel,
|
||||
_elements.tick,
|
||||
_elements.label,
|
||||
_elements.button
|
||||
_elements.mode
|
||||
);
|
||||
|
||||
_elements.group.transform(
|
||||
@ -258,7 +258,7 @@
|
||||
strokeWidth: 1,
|
||||
stroke: _bracketColorFg,
|
||||
'stroke-dasharray': '5, 1'
|
||||
}).click(bracketClick);
|
||||
});
|
||||
}
|
||||
|
||||
if (_.has(_elements, 'bracketMin')) {
|
||||
@ -297,19 +297,19 @@
|
||||
}
|
||||
|
||||
function updateMode() {
|
||||
if (_.has(_elements, 'button')) {
|
||||
if (_.has(_elements, 'mode')) {
|
||||
|
||||
|
||||
}
|
||||
else {
|
||||
_elements.button = _canvas.text(
|
||||
_elements.mode = _canvas.text(
|
||||
(_width - _bracketSize) / 2,
|
||||
_height - _buttonSize / 2,
|
||||
''
|
||||
_height - _modeSize / 2,
|
||||
'x'
|
||||
).attr({
|
||||
'dominant-baseline': 'middle',
|
||||
'text-anchor': 'middle'
|
||||
});
|
||||
}).click(modeClick);
|
||||
}
|
||||
}
|
||||
|
||||
@ -344,25 +344,12 @@
|
||||
return colorStops;
|
||||
}
|
||||
|
||||
function updateState(value, bracket) {
|
||||
var updateBracket = bracket !== null;
|
||||
var updateValue = value !== null;
|
||||
|
||||
if (updateBracket) {
|
||||
_data.bracket.max = _range.clamp(bracket.max);
|
||||
_data.bracket.min = _range.clamp(bracket.min);
|
||||
}
|
||||
|
||||
if (updateValue) {
|
||||
function updateState(value, mode) {
|
||||
_data.value = _range.clamp(value);
|
||||
}
|
||||
_data.mode = mode;
|
||||
|
||||
if (_onStateChanged) {
|
||||
_onStateChanged(
|
||||
_name,
|
||||
updateValue ? _data.value : null,
|
||||
updateBracket ? _data.bracket : null
|
||||
);
|
||||
_onStateChanged(_name, _data.value, _data.mode);
|
||||
}
|
||||
|
||||
animateIndicator(_valueTrans, _data.value);
|
||||
@ -462,19 +449,11 @@
|
||||
|
||||
function indicatorClick(event, x, y) {
|
||||
var rect = _canvas.node.getBoundingClientRect();
|
||||
|
||||
updateState(indicatorToValue(y - rect.top), null);
|
||||
updateState(indicatorToValue(y - rect.top), _data.mode);
|
||||
}
|
||||
|
||||
function bracketClick(event, x, y) {
|
||||
var mid = (_data.bracket.min + _data.bracket.max) / 2;
|
||||
var rect = _canvas.node.getBoundingClientRect();
|
||||
|
||||
var dist = Math.abs(mid - bracketToValue(y - rect.top));
|
||||
dist = Math.min(dist, Math.abs(_range.max - mid));
|
||||
dist = Math.min(dist, Math.abs(_range.min - mid));
|
||||
|
||||
updateState(_data.value, {max: mid + dist, min: mid - dist});
|
||||
function modeClick(event, x, y) {
|
||||
alert('mode clicked');
|
||||
}
|
||||
|
||||
this.update = function(data, scale) {
|
||||
@ -492,6 +471,10 @@
|
||||
_data.bracket = data.bracket;
|
||||
animateBracket(_bracketTrans, _data.bracket);
|
||||
}
|
||||
if (_.has(data, 'mode')) {
|
||||
_data.mode = data.mode;
|
||||
updateMode();
|
||||
}
|
||||
};
|
||||
|
||||
createShapes();
|
||||
|
@ -25,18 +25,9 @@
|
||||
|
||||
var _ctx = {};
|
||||
|
||||
function onStateChanged(name, value, bracket) {
|
||||
function onStateChanged(name, value, mode) {
|
||||
_ctx.query.features[name] = value;
|
||||
if (bracket === null) {
|
||||
_ctx.query.bracket = null;
|
||||
}
|
||||
else {
|
||||
_ctx.query.bracket = {
|
||||
name: name,
|
||||
min: bracket.min,
|
||||
max: bracket.max
|
||||
};
|
||||
}
|
||||
_ctx.query.modes[name] = mode;
|
||||
|
||||
$.post('/query', JSON.stringify(_ctx.query), function(results) {
|
||||
saveSnapshot(results);
|
||||
@ -87,7 +78,7 @@
|
||||
function onSearch() {
|
||||
_ctx.query = {
|
||||
features: _ctx.query.features || {},
|
||||
bracket: null,
|
||||
modes: _ctx.query.modes || {},
|
||||
sortKey: _ctx.sortKey,
|
||||
sortAsc: _ctx.sortAsc,
|
||||
profile: getProfile(),
|
||||
@ -132,7 +123,8 @@
|
||||
columns[feature] = {
|
||||
value: column.value,
|
||||
hints: column.hints,
|
||||
bracket: column.bracket
|
||||
bracket: column.bracket,
|
||||
mode: column.mode
|
||||
};
|
||||
}
|
||||
|
||||
|
45
types.go
45
types.go
@ -24,7 +24,16 @@ package main
|
||||
|
||||
import "sort"
|
||||
|
||||
type modeType int
|
||||
|
||||
const (
|
||||
ModeTypeNone modeType = iota
|
||||
ModeTypeProduct
|
||||
ModeTypeDist
|
||||
)
|
||||
|
||||
type featureMap map[string]float64
|
||||
type modeMap map[string]modeType
|
||||
|
||||
type jsonAccessRequest struct {
|
||||
Id int `json:"id"`
|
||||
@ -37,11 +46,11 @@ type jsonGeoData struct {
|
||||
}
|
||||
|
||||
type jsonQueryRequest struct {
|
||||
Bracket *jsonNamedBracket `json:"bracket"`
|
||||
Features featureMap `json:"features"`
|
||||
Geo *jsonGeoData `json:"geo"`
|
||||
MaxResults int `json:"maxResults"`
|
||||
MinScore float64 `json:"minScore"`
|
||||
Modes map[string]string `json:"modeMap"`
|
||||
Profile featureMap `json:"profile"`
|
||||
Resolution int `json:"resolution"`
|
||||
SortAsc bool `json:"sortAsc"`
|
||||
@ -52,6 +61,7 @@ type jsonQueryRequest struct {
|
||||
type jsonColumn struct {
|
||||
Bracket jsonBracket `json:"bracket"`
|
||||
Hints []jsonProjection `json:"hints"`
|
||||
Mode string `json:"mode"`
|
||||
Steps int `json:"steps"`
|
||||
Value float64 `json:"value"`
|
||||
}
|
||||
@ -79,11 +89,6 @@ type jsonBracket struct {
|
||||
Max float64 `json:"max"`
|
||||
}
|
||||
|
||||
type jsonNamedBracket struct {
|
||||
jsonBracket
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type jsonQueryResponse struct {
|
||||
Columns map[string]jsonColumn `json:"columns"`
|
||||
Count int `json:"count"`
|
||||
@ -131,12 +136,6 @@ type geoData struct {
|
||||
longitude float64
|
||||
}
|
||||
|
||||
type namedBracket struct {
|
||||
name string
|
||||
min float64
|
||||
max float64
|
||||
}
|
||||
|
||||
type record struct {
|
||||
accessCount int
|
||||
closestStn string
|
||||
@ -196,3 +195,25 @@ func (s recordSorter) Less(i, j int) bool {
|
||||
func (s recordSorter) Swap(i, j int) {
|
||||
s.entries[i], s.entries[j] = s.entries[j], s.entries[i]
|
||||
}
|
||||
|
||||
func (m modeType) String() string {
|
||||
switch m {
|
||||
case ModeTypeProduct:
|
||||
return "prod"
|
||||
case ModeTypeDist:
|
||||
return "dist"
|
||||
default:
|
||||
return "invalid"
|
||||
}
|
||||
}
|
||||
|
||||
func strToModeType(mode string) modeType {
|
||||
switch mode {
|
||||
case "prod":
|
||||
return ModeTypeProduct
|
||||
case "dist":
|
||||
return ModeTypeDist
|
||||
default:
|
||||
return ModeTypeNone
|
||||
}
|
||||
}
|
||||
|
80
util.go
80
util.go
@ -47,6 +47,16 @@ func fixFeatures(features featureMap) featureMap {
|
||||
return fixedFeatures
|
||||
}
|
||||
|
||||
func fixModes(modes map[string]string) modeMap {
|
||||
result := make(modeMap)
|
||||
|
||||
for name, value := range modes {
|
||||
result[name] = strToModeType(value)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func innerProduct(features1 featureMap, features2 featureMap) float64 {
|
||||
var result float64
|
||||
for key, value1 := range features1 {
|
||||
@ -57,19 +67,35 @@ func innerProduct(features1 featureMap, features2 featureMap) float64 {
|
||||
return result
|
||||
}
|
||||
|
||||
func walkMatches(entries records, features featureMap, minScore float64, callback func(record, float64)) {
|
||||
func compare(features1 featureMap, features2 featureMap, modes modeMap) float64 {
|
||||
var result float64
|
||||
for key, value1 := range features1 {
|
||||
value2, _ := features2[key]
|
||||
|
||||
switch mode, _ := modes[key]; mode {
|
||||
case ModeTypeDist:
|
||||
result += 1 - math.Abs(value1-value2)/2
|
||||
default:
|
||||
result += value1 * value2
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func walkMatches(entries records, features featureMap, modes modeMap, minScore float64, callback func(record, float64)) {
|
||||
for _, entry := range entries {
|
||||
if score := innerProduct(features, entry.features); score >= minScore {
|
||||
if score := compare(features, entry.features, modes); score >= minScore {
|
||||
callback(entry, score)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func statRecords(entries records, features featureMap, minScore float64) (float64, int) {
|
||||
func statRecords(entries records, features featureMap, modes modeMap, minScore float64) (float64, int) {
|
||||
var compatibility float64
|
||||
var count int
|
||||
|
||||
walkMatches(entries, features, minScore, func(entry record, score float64) {
|
||||
walkMatches(entries, features, modes, minScore, func(entry record, score float64) {
|
||||
compatibility += entry.compatibility
|
||||
count++
|
||||
})
|
||||
@ -89,10 +115,10 @@ func stepRange(min, max float64, steps int, callback func(float64)) {
|
||||
}
|
||||
}
|
||||
|
||||
func findRecords(entries records, features featureMap, minScore float64) records {
|
||||
func findRecords(entries records, features featureMap, modes modeMap, minScore float64) records {
|
||||
var foundEntries records
|
||||
|
||||
walkMatches(entries, features, minScore, func(entry record, score float64) {
|
||||
walkMatches(entries, features, modes, minScore, func(entry record, score float64) {
|
||||
entry.score = score
|
||||
foundEntries = append(foundEntries, entry)
|
||||
})
|
||||
@ -100,45 +126,7 @@ func findRecords(entries records, features featureMap, minScore float64) records
|
||||
return foundEntries
|
||||
}
|
||||
|
||||
func calibrateMinScore(entries records, features featureMap, bracket namedBracket) float64 {
|
||||
bestScoreRank := -math.MaxFloat64
|
||||
var bestMinScore float64
|
||||
|
||||
for minScore := float64(-len(features)); minScore <= float64(len(features)); minScore += 0.1 {
|
||||
var scoreRank float64
|
||||
|
||||
for _, entry := range entries {
|
||||
value, ok := entry.features[bracket.name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
score := innerProduct(features, entry.features)
|
||||
if score < minScore {
|
||||
continue
|
||||
}
|
||||
|
||||
if score > minScore {
|
||||
if value >= bracket.min && value <= bracket.max {
|
||||
dist := math.Abs(value - features[bracket.name])
|
||||
scoreRank += 1 / (dist * dist)
|
||||
} else {
|
||||
dist := math.Min(math.Abs(value-bracket.min), math.Abs(value-bracket.max))
|
||||
scoreRank -= 1 / (dist * dist)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if scoreRank > bestScoreRank {
|
||||
bestScoreRank = scoreRank
|
||||
bestMinScore = minScore
|
||||
}
|
||||
}
|
||||
|
||||
return bestMinScore
|
||||
}
|
||||
|
||||
func project(entries records, features featureMap, featureName string, minScore float64, steps int) []queryProjection {
|
||||
func project(entries records, features featureMap, modes modeMap, featureName string, minScore float64, steps int) []queryProjection {
|
||||
sampleFeatures := make(featureMap)
|
||||
for key, value := range features {
|
||||
sampleFeatures[key] = value
|
||||
@ -147,7 +135,7 @@ func project(entries records, features featureMap, featureName string, minScore
|
||||
var projection []queryProjection
|
||||
stepRange(-1.0, 1.0, steps, func(sample float64) {
|
||||
sample, sampleFeatures[featureName] = sampleFeatures[featureName], sample
|
||||
compatibility, count := statRecords(entries, sampleFeatures, minScore)
|
||||
compatibility, count := statRecords(entries, sampleFeatures, modes, minScore)
|
||||
sample, sampleFeatures[featureName] = sampleFeatures[featureName], sample
|
||||
|
||||
projection = append(projection, queryProjection{compatibility, count, sample})
|
||||
|
Loading…
Reference in New Issue
Block a user