Updating to use features instead of keywords
This commit is contained in:
parent
bfadd2038b
commit
8b7759a82a
@ -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"
|
||||
|
@ -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">
|
||||
|
@ -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 || {}));
|
||||
|
@ -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;
|
||||
|
@ -1,7 +0,0 @@
|
||||
.unselectable {
|
||||
-moz-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
@ -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
|
||||
};
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user