1

Work on modes

This commit is contained in:
Alex Yatskov 2015-06-29 13:06:26 +09:00
parent 7d3ea8e5dd
commit cf586e853a
5 changed files with 105 additions and 126 deletions

View File

@ -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 {

View File

@ -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) {
_data.value = _range.clamp(value);
}
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();

View File

@ -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
};
}

View File

@ -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
View File

@ -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})