1

Updating to use features instead of keywords

This commit is contained in:
Alex Yatskov 2014-11-08 11:23:42 +09:00
parent bfadd2038b
commit 8b7759a82a
7 changed files with 111 additions and 142 deletions

View File

@ -16,7 +16,6 @@
"bootstrap": "~3.2.0",
"handlebars": "~1.3.0",
"underscore": "~1.6.0",
"bootstrap-select": "~1.6.2",
"tinycolor": "~1.0.0",
"seiyria-bootstrap-slider": "~4.0.1",
"snap.svg": "~0.3.0"

View File

@ -14,11 +14,11 @@
</div>
<!-- query input -->
<div id="input" class="form-horizontal">
<div class="form-horizontal">
<div class="form-group">
<label for="keywordsToSearch" class="col-md-2 control-label">Keywords</label>
<div class="col-md-10">
<select id="keywordsToSearch" class="form-control" multiple="multiple" data-max-options="4" data-live-search="data-live-search"></select>
<select id="keywordsToSearch" class="form-control"></select>
</div>
</div>
<div class="form-group">
@ -39,13 +39,6 @@
<input class="form-control" type="number" value="100" id="maxResults">
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button class="btn btn-primary" id="searchKeywords" type="button" disabled="disabled">
<span class="glyphicon glyphicon-search"></span> Search
</button>
</div>
</div>
</div>
<!-- options dialog -->
@ -113,9 +106,9 @@
</div>
<!-- query output -->
<div id="output" style="display: none;">
<div>
<!-- semantic tweaker -->
<div class="panel panel-default unselectable">
<div class="panel panel-default">
<div class="panel-heading">
<big>Semantic tweaks to: <span id="query"></span></big>
<div class="btn-group pull-right">

View File

@ -27,18 +27,19 @@
(function(hscd) {
'use strict';
var ctx = {};
var log = [];
var ctx = {
log: []
};
function onAdjust(name, value) {
ctx.searchParams[name] = value;
ctx.features[name] = value;
var query = {
searchParams: ctx.searchParams,
searchRange: ctx.searchRange,
minScore: ctx.minScore,
hintSteps: ctx.hintSteps,
maxResults: ctx.maxResults
features: ctx.features,
range: ctx.range,
minScore: ctx.minScore,
hintSteps: ctx.hintSteps,
maxResults: ctx.maxResults
};
ctx.grapher.enable(false);
@ -49,33 +50,83 @@
});
}
function onSearch() {
var keywords = $('#keywordsToSearch').val() || [];
var searchParams = {};
function onReady() {
$('#history').on('slideStop', onSelectSnapshot);
$('#history').slider({
formatter: function(value) {
var delta = ctx.log.length - (value + 1);
switch (delta) {
case 0:
return 'Most recent query';
case 1:
return 'Previous query';
default:
return String(delta) + ' queries back';
}
}
});
for (var i = 0, count = keywords.length; i < count; ++i) {
searchParams[keywords[i]] = 1.0;
$('#forgetKeyword').click(onForget);
$('#forgetDialog').on('show.bs.modal', function() {
$('#forgetError').hide();
$.getJSON('/get_keywords', function(keywords) {
$('#keywordToForget').empty();
for (var i = 0, count = keywords.length; i < count; ++i) {
$('#keywordToForget').append($('<option></option>', {
value: keywords[i],
text: keywords[i]
}));
}
});
});
$('#learnKeyword').click(onLearn);
$('#learnDialog').on('show.bs.modal', function() {
$('#learnError').hide();
$('#learnKeyword').prop('disabled', true);
$('#keywordToLearn').val('');
});
$('#keywordToLearn').bind('input', function() {
$('#learnKeyword').prop('disabled', !$(this).val());
});
$.getJSON('/get_keywords', function(keywords) {
ctx.keywords = keywords;
for (var keyword in keywords) {
$('#keywordsToSearch').append($('<option></option>', { value: keyword, text: keyword }));
}
onSearch();
});
}
function onSearch() {
var keyword = $('#keywordsToSearch').val();
var features = {};
for (var feature in ctx.keywords[keyword]) {
features[feature] = 1.0;
}
var query = {
searchParams: searchParams,
searchRange: { min: -1.0, max: 1.0 },
minScore: parseFloat($('#minScore').val()),
hintSteps: parseInt($('#hintSteps').val()),
maxResults: parseInt($('#maxResults').val())
features: features,
range: { min: -1.0, max: 1.0 },
minScore: parseFloat($('#minScore').val()),
hintSteps: parseInt($('#hintSteps').val()),
maxResults: parseInt($('#maxResults').val())
};
$.getJSON('/search', query, function(results) {
ctx.searchParams = query.searchParams;
ctx.searchRange = query.searchRange;
ctx.minScore = query.minScore;
ctx.hintSteps = query.hintSteps;
ctx.maxResults = query.maxResults;
ctx.features = query.features;
ctx.range = query.range;
ctx.minScore = query.minScore;
ctx.hintSteps = query.hintSteps;
ctx.maxResults = query.maxResults;
ctx.grapher = new grapher.Grapher({
canvas: new Snap('#svg'),
steps: ctx.hintSteps,
range: ctx.searchRange,
range: ctx.range,
onValueChanged: onAdjust,
useLocalScale: true,
useRelativeScale: true
@ -85,7 +136,7 @@
saveSnapshot(results);
outputMatches(results.items, results.count);
$('#query').text(keywords.join(', '));
$('#query').text(keyword);
$('#useLocalScale').click(function() {
var useLocalScale = $('#useLocalScale').is(':checked');
ctx.grapher.setUseLocalScale(useLocalScale);
@ -94,9 +145,6 @@
var useRelativeScale = $('#useRelativeScale').is(':checked');
ctx.grapher.setUseRelativeScale(useRelativeScale);
});
$('#input').fadeOut(function() {
$('#output').fadeIn();
});
});
}
@ -105,7 +153,7 @@
$('#learnError').slideUp(function() {
var query = {
keyword: $('#keywordToLearn').val(),
params: ctx.searchParams
params: ctx.features
};
$.getJSON('/add_keyword', query, function(results) {
@ -143,13 +191,13 @@
function onSelectSnapshot() {
var index = $('#history').slider('getValue');
outputSnapshot(log[index]);
outputSnapshot(ctx.log[index]);
}
function saveSnapshot(results) {
log.push(results);
ctx.log.push(results);
var count = log.length;
var count = ctx.log.length;
var history = $('#history').slider();
history.slider('setAttribute', 'max', count - 1);
history.slider('setValue', count - 1);
@ -161,7 +209,7 @@
function outputSnapshot(results) {
for (var name in results.columns) {
ctx.searchParams[name] = results.columns[name].value;
ctx.features[name] = results.columns[name].value;
}
ctx.grapher.setColumns(results.columns);
@ -181,70 +229,9 @@
}
$(document).on({
ajaxStart: function() {
$('#spinner').show();
},
ajaxStop: function() {
$('#spinner').hide();
},
ready: function() {
$('#keywordsToSearch').selectpicker();
$('#history').slider({
formatter: function(value) {
var delta = log.length - (value + 1);
switch (delta) {
case 0:
return 'Most recent query';
case 1:
return 'Previous query';
default:
return String(delta) + ' queries back';
}
}
});
$.getJSON('/get_keywords', function(keywords) {
$('#searchKeywords').click(onSearch);
for (var i = 0, count = keywords.length; i < count; ++i) {
$('#keywordsToSearch').append($('<option></option>', {
value: keywords[i],
text: keywords[i]
}));
}
$('#keywordsToSearch').selectpicker('refresh');
$('#keywordsToSearch').change(function() {
$('#searchKeywords').prop('disabled', !$(this).val());
});
$('#forgetKeyword').click(onForget);
$('#forgetDialog').on('show.bs.modal', function() {
$('#forgetError').hide();
$.getJSON('/get_keywords', function(keywords) {
$('#forgetKeyword').prop('disabled', keywords.length === 0);
$('#keywordToForget').empty();
for (var i = 0, count = keywords.length; i < count; ++i) {
$('#keywordToForget').append($('<option></option>', {
value: keywords[i],
text: keywords[i]
}));
}
});
});
$('#history').on('slideStop', onSelectSnapshot);
$('#learnKeyword').click(onLearn);
$('#keywordToLearn').bind('input', function() {
$('#learnKeyword').prop('disabled', !$(this).val());
});
$('#learnDialog').on('show.bs.modal', function() {
$('#learnKeyword').prop('disabled', true);
$('#keywordToLearn').val('');
$('#learnError').hide();
});
});
}
ajaxStart: function() { $('#spinner').show(); },
ajaxStop: function() { $('#spinner').hide(); },
ready: onReady()
});
}(window.hscd = window.hscd || {}));

View File

@ -281,7 +281,7 @@
var _canvas = params.canvas;
var _columns = {};
var _data = {};
var _range = new Range(-1.0, 1.0);
var _range = new Range(params.range.min || -1.0, params.range.max || 1.0);
var _steps = params.steps || 20;
var _useLocalScale = params.useLocalScale || true;
var _useRelativeScale = params.useRelativeScale || true;

View File

@ -1,7 +0,0 @@
.unselectable {
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}

View File

@ -83,9 +83,7 @@ function combine(dict, params) {
return result;
}
function walkRecords(data, searchParams, minScore, callback) {
var features = combine(data.keywords, searchParams);
function walkMatches(data, features, minScore, callback) {
for (var i = 0, count = data.records.length; i < count; ++i) {
var record = data.records[i];
var score = innerProduct(features, record.rating);
@ -94,22 +92,20 @@ function walkRecords(data, searchParams, minScore, callback) {
callback(record, score);
}
}
return features;
}
function countRecords(data, searchParams, minScore) {
function countRecords(data, features, minScore) {
var count = 0;
walkRecords(data, searchParams, minScore, function(record, score) {
walkMatches(data, features, minScore, function(record, score) {
++count;
});
return count;
}
function findRecords(data, searchParams, minScore) {
function findRecords(data, features, minScore) {
var results = [];
walkRecords(data, searchParams, minScore, function(record, score) {
walkMatches(data, features, minScore, function(record, score) {
results.push({
name: record.name,
url: 'http://www.tripadvisor.com' + record.relativeUrl,
@ -137,27 +133,27 @@ function step(range, steps, callback) {
}
}
function project(data, searchParams, minScore, keyword, range, steps) {
var testParams = _.clone(searchParams);
var results = [];
function project(data, features, feature, minScore, range, steps) {
var sample = _.clone(features);
var results = [];
step(range, steps, function(position) {
testParams[keyword] = position;
sample[feature] = position;
results.push({
sample: position,
count: countRecords(data, testParams, minScore)
count: countRecords(data, sample, minScore)
});
});
return results;
}
function buildHints(data, searchParams, minScore, keyword, range, steps) {
function buildHints(data, features, feature, minScore, range, steps) {
var projection = project(
data,
searchParams,
features,
feature,
minScore,
keyword,
range,
steps
);
@ -275,23 +271,24 @@ function execQuery(query, callback) {
getData(function(data) {
var searchResults = findRecords(
data,
query.searchParams,
query.features,
query.minScore
);
var graphColumns = {};
for (var keyword in query.searchParams) {
console.log(query);
for (var feature in query.features) {
var searchHints = buildHints(
data,
query.searchParams,
query.features,
feature,
query.minScore,
keyword,
query.searchRange,
query.range,
query.hintSteps
);
graphColumns[keyword] = {
value: query.searchParams[keyword],
graphColumns[feature] = {
value: query.features[feature],
hints: searchHints,
steps: query.hintSteps
};

View File

@ -45,7 +45,7 @@ function main(staticFiles, port) {
app.use('/get_keywords', function(req, res) {
search.getKeywords(function(keywords) {
res.json(_.keys(keywords).sort());
res.json(keywords);
});
});