diff --git a/.gitignore b/.gitignore index e7906cf7..0a2a8a5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ ajax-solr.min.js +*~ diff --git a/core/AbstractFacetWidget.js b/core/AbstractFacetWidget.js index 9e5ca337..f43931c5 100644 --- a/core/AbstractFacetWidget.js +++ b/core/AbstractFacetWidget.js @@ -7,6 +7,7 @@ } }(function () { + /** * Baseclass for all facet widgets. * @@ -82,6 +83,16 @@ AjaxSolr.AbstractFacetWidget = AjaxSolr.AbstractWidget.extend( 'facet.range.include' ]); } + /*************************** + * Added new parameter for facet.pivot: according to the documentation + * there is only facet.pivot.mincount + * ************************/ + else if (this['facet.pivot'] !== undefined) { + this.manager.store.addByValue('facet.pivot', this.field); + parameters = parameters.concat([ + 'facet.pivot.mincount', + ]); + } for (var i = 0, l = parameters.length; i < l; i++) { if (this[parameters[i]] !== undefined) { @@ -121,6 +132,17 @@ AjaxSolr.AbstractFacetWidget = AjaxSolr.AbstractWidget.extend( return this.manager.store.addByValue('fq', this.fq(value)); }); }, + + /** + * Adds a filter query independent of the widget facet field. + * + * @returns {Boolean} Whether a filter query was added. + */ + addByField: function (field, value) { + return this.changeSelection(function () { + return this.manager.store.addByValue('fq', (field+':'+value)); + }); + }, /** * Removes a filter query. @@ -183,19 +205,42 @@ AjaxSolr.AbstractFacetWidget = AjaxSolr.AbstractWidget.extend( else if (this['facet.range'] !== undefined) { property = 'facet_ranges'; } + else if (this['facet.pivot'] !== undefined) { + property = 'facet_pivot'; + } if (property !== undefined) { - switch (this.manager.store.get('json.nl').val()) { - case 'map': - return this.getFacetCountsMap(property); - case 'arrarr': - return this.getFacetCountsArrarr(property); - default: - return this.getFacetCountsFlat(property); - } + /* ------------------------------------------------------------------------------------------------- + New If added to control the case of the facet_pivots: we do not know how the + * json.nl value would affect pivots, therefore this special case. + -----------------------------------------*/ + if (property==='facet_pivot'){ + return this.getPivotCountsMap(property); + }else{ + //--------------------------------------------------------------------------------------------------- + switch (this.manager.store.get('json.nl').val()) { + case 'map': + return this.getFacetCountsMap(property); + case 'arrarr': + return this.getFacetCountsArrarr(property); + default: + return this.getFacetCountsFlat(property); + } + } } throw 'Cannot get facet counts unless one of the following properties is set to "true" on widget "' + this.id + '": "facet.field", "facet.date", or "facet.range".'; }, +/** ---------------------------------------------------------------------------- + * Used "if" it is facet.pivot data. + * + * * @param {JSON} as the solr output for facet.pivots. + * @returns {Map} A map associating to each key an array of two elements (total count, and another map), + * or the count. + */ + getPivotCountsMap: function (property) { + return parse_pivots(this.manager.response.facet_counts[property][this.field]); + }, + /** * Used if the facet counts are represented as a JSON object. * @@ -290,4 +335,26 @@ AjaxSolr.AbstractFacetWidget = AjaxSolr.AbstractWidget.extend( } }); +/********************************* + * Method for parsing pivots output: it works recursively such that at each level + * which has a new pivot the ouput will indicate the counts and then the map of other results. + * It follows the same structure than the solr ourput. + * + * @param {JSON} as the solr output for facet.pivots. + * @returns {Map} A map associating to each key an array of two elements (total count, and another map), + * or the count. + * + * *********************************/ +function parse_pivots(data_input){ + var output = {} + for (var i = 0, l = data_input.length; i < l; i++) { + if ("pivot" in data_input[i]){ + output[data_input[i]["value"]] = [data_input[i]["count"],parse_pivots(data_input[i]["pivot"])]; + } else { + output[data_input[i]["value"]] = data_input[i]["count"]; + } + } + return output +} +/********************************************************/ })); diff --git a/examples/reuters/css/reuters.css b/examples/reuters/css/reuters.css index 3b7dd0ab..cad73043 100644 --- a/examples/reuters/css/reuters.css +++ b/examples/reuters/css/reuters.css @@ -106,6 +106,7 @@ a:hover { } #calendar { width: 160px; + margin-top: 10px; } #search_help { font-size: 80%; @@ -158,3 +159,46 @@ font-size: 170%; a.tagcloud_size_10 { font-size: 180%; } + +/*histogram*/ + +div.bar { + display: inline-block; + width: 20px; + height: 75px; + background-color: teal; + margin-right: 2px; + } + +bar2 { + background-color: blue; +} + +.shared, .bar, .label { + font-size: 8pt; + font-weight: bold; + font-family: Arial, sans-serif; +} +.femalebar, .malebar { + fill: #a7b6c1; +} +.highlight rect.malebar, .highlight rect.femalebar { + fill: #a14538; +} +text.malebar, text.femalebar { + display: none; +} +.highlight text { + display: block; + fill: #000; +} + +rect.highlight2{ + fill: #a14538; +} + +#histogram +{ +margin-top: 10px; +} + diff --git a/examples/reuters/index.html b/examples/reuters/index.html index 1088e3fa..4890dd14 100644 --- a/examples/reuters/index.html +++ b/examples/reuters/index.html @@ -23,12 +23,37 @@ + + + +
@@ -51,21 +76,22 @@

Search

-

Top Topics

-
- -

Top Organisations

-
- -

Top Exchanges

-
- -

By Country

-
-
+

Most commun Words

+
+ +

Most commun Users

+

By Date

+ +

Histogram

+
+ + +

Pyramid age-gender

+
+
diff --git a/examples/reuters/js/reuters.js b/examples/reuters/js/reuters.js index 99a0cf3d..b23b4cd2 100644 --- a/examples/reuters/js/reuters.js +++ b/examples/reuters/js/reuters.js @@ -1,15 +1,19 @@ +//var ip = location.hostname; +var ip = "localhost"; var Manager; (function ($) { $(function () { Manager = new AjaxSolr.Manager({ - solrUrl: 'http://evolvingweb.ca/solr/reuters/' + solrUrl: 'http://' + ip + ':8983/solr/collection1/' }); Manager.addWidget(new AjaxSolr.ResultWidget({ id: 'result', target: '#docs' })); + + Manager.addWidget(new AjaxSolr.PagerWidget({ id: 'pager', target: '#pager', @@ -20,7 +24,8 @@ var Manager; $('#pager-header').html($('').text('displaying ' + Math.min(total, offset + 1) + ' to ' + Math.min(total, offset + perPage) + ' of ' + total)); } })); - var fields = [ 'topics', 'organisations', 'exchanges' ]; + /*var fields = ['comment_id', 'comment_order', 'comment_karma', 'comment_randkey', 'comment_date', 'comment_user_id', 'comment_link_id', 'comment_content', 'comment_votes', 'comment_parent' ];*/ + var fields = ['comment_content', 'comment_user_id' ]; for (var i = 0, l = fields.length; i < l; i++) { Manager.addWidget(new AjaxSolr.TagcloudWidget({ id: fields[i], @@ -32,34 +37,49 @@ var Manager; id: 'currentsearch', target: '#selection' })); + Manager.addWidget(new AjaxSolr.AutocompleteWidget({ id: 'text', target: '#search', - fields: [ 'topics', 'organisations', 'exchanges' ] + fields: [ 'comment_id', 'comment_content'] })); - Manager.addWidget(new AjaxSolr.CountryCodeWidget({ - id: 'countries', - target: '#countries', - field: 'countryCodes' + + Manager.addWidget(new AjaxSolr.HistogramWidget({ + id: 'histogram', + target: '#histogram', + fields: ['comment_date', 'comment_hour'] })); + + //************** new widget pyramid pivots ******************** + + Manager.addWidget(new AjaxSolr.PyramidAges({ + id: 'pyramidAges', + target: '#pyramidAges', + field: 'user_sex,user_age', + 'facet.pivot': true + })); + // ********************************************************** + Manager.addWidget(new AjaxSolr.CalendarWidget({ id: 'calendar', target: '#calendar', - field: 'date' + field: 'comment_date' })); + Manager.init(); Manager.store.addByValue('q', '*:*'); var params = { facet: true, - 'facet.field': [ 'topics', 'organisations', 'exchanges', 'countryCodes' ], + 'facet.field': ['comment_id', 'comment_order', 'comment_karma', 'comment_randkey', 'comment_date', 'comment_user_id', 'comment_link_id', 'comment_content', 'comment_votes', 'comment_parent', 'comment_hour' ], 'facet.limit': 20, 'facet.mincount': 1, 'f.topics.facet.limit': 50, - 'f.countryCodes.facet.limit': -1, - 'facet.date': 'date', - 'facet.date.start': '1987-02-26T00:00:00.000Z/DAY', - 'facet.date.end': '1987-10-20T00:00:00.000Z/DAY+1DAY', + 'facet.date': 'comment_date', + 'facet.date.start': '2005-12-01T00:00:00.000Z/DAY', + 'facet.date.end': '2006-12-31T00:00:00.000Z/DAY+1DAY', 'facet.date.gap': '+1DAY', + //************** new line for pyramid pivot ******************************* + 'facet.pivot':'user_sex,user_age', 'json.nl': 'map' }; for (var name in params) { diff --git a/examples/reuters/widgets/HistogramWidget.js b/examples/reuters/widgets/HistogramWidget.js new file mode 100644 index 00000000..c680ee88 --- /dev/null +++ b/examples/reuters/widgets/HistogramWidget.js @@ -0,0 +1,264 @@ +(function ($) { + +AjaxSolr.HistogramWidget = AjaxSolr.AbstractFacetWidget.extend({ + afterRequest: function () { + var self = this; + + var maxCount = 0; + var objectedItems = []; + + + for (var facet in this.manager.response.facet_counts.facet_dates[this.fields[0]]) { + var count = parseInt(this.manager.response.facet_counts.facet_dates[this.fields[0]][facet]); + if (count > maxCount) { + maxCount = count; + } + if (facet.substr(0,1) == '2') + { + objectedItems.push({ facet: facet, count: count }); + } + } + + var w = 360; + var h = 300; + var n; + var s; + var months = []; + var facets = []; + var maxMonths = 12; + var monthAct; + var monthAnt; + var monthAcum = 0; + var monthAcumMax = 0; + var facets2 = [0,0,0,0,0,0,0,0,0,0,0,0], months2 = [0,0,0,0,0,0,0,0,0,0,0,0]; + + if (objectedItems.length > 1) + { + + monthAct = objectedItems[0].facet.substr(0,7); + monthAnt = monthAct; + + for (var i=0; i monthAct) + { + if (monthAcum > monthAcumMax) + { + monthAcumMax = monthAcum; + } + facets.push(monthAcum); + monthAcum = 0; + months.push(monthAct); + monthAnt = monthAct; + monthAct = objectedItems[i].facet.substr(0,7); + + if (months.length >= 11) + { + break; + } + } + } + + facets.push(monthAcum); + months.push(monthAct); + + if (monthAcumMax == 0) + { + monthAcumMax = monthAcum; + } + + //fill the gap betweens months + + facets2[0] = facets[0]; + months2[0] = months[0]; + + var lastMonth; + var lastYear; + + for (var i=1; i<12; i++) + { + lastMonth = parseInt(months2[i-1].substr(5,2)); + lastYear = parseInt(months2[i-1].substr(0,4)); + + if (lastMonth < 12) + { + if (lastMonth < 9) + { + months2[i] = lastYear.toString() + "-0" + (lastMonth + 1).toString(); + } + else + { + months2[i] = lastYear.toString() + "-" + (lastMonth + 1).toString(); + } + } + else if (lastMonth > 11) + { + months2[i] = (lastYear + 1).toString() + "-01"; + } + } + + for (var i=0; i<12; i++) + { + if (i < months.length) + { + if (months2[i] == months[i]) + { + facets2[i] = facets[i]; + } + } + else + { + break; + } + } + + n = maxMonths; + s = monthAcumMax/h; + + } + else if (objectedItems.length == 1){ + + var hours = []; + var posts = []; + monthAcumMax = 0; + + var testing = ""; + + for (var facet in this.manager.response.facet_counts.facet_fields[this.fields[1]]){ + var countHour = parseInt(this.manager.response.facet_counts.facet_fields[this.fields[1]][facet]); + hours.push(facet); + posts.push(parseInt(countHour)); + + if (countHour > monthAcumMax) + { + monthAcumMax = countHour; + } + } + + var hours2 = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23]; + var posts2 = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; + + var hourAct = 0; + var hourAnt = -1; + + for (var i=0; i<24; i++) + { + for (var j=0; j 0) + { + + //definimos el elemento svg + var svg = d3.select("#histogram") + .html("") + .append("svg") + .attr("width", w+30) + .attr("height", h+30); + + //segun los datos de "facets", creamos las barras verticales + svg.selectAll("rect") + .data(facets2) + .enter() + .append("rect") + .attr("x", function(d, i) {return (i*((w/n)+1))+5;}) + .attr("y", function(d) {return h - (d/s);}) + .attr("width", w/n - 1) + .attr("height", function(d) {return (d/s);}) + .attr("class", "femalebar") + .on("mouseover", function () {d3.select(this).classed("highlight2", true);}) + .on("mouseout", function () {d3.select(this).classed("highlight2", false);}) + .on("click", function (rect, bar) { + + var query; + if (objectedItems.length > 1) + { + if (parseInt(months2[bar].substr(5,2)) < 12) + { + query = '[' + months2[bar] + '-01T00:00:00Z TO ' + months2[bar+1] + '-01T00:00:00Z]'; + } + else + { + query = '[' + months2[bar] + '-01T00:00:00Z TO ' + (parseInt(months2[bar].substr(0,4)) + 1) + '-01-01T00:00:00Z]' + } + if (self.addByField('comment_date', query)) { + self.doRequest(); + } + } + else if (objectedItems.length == 1) + { + if (self.addByField('comment_hour', bar)) { + self.doRequest(); + } + } + + }); + ; + + //dado que en months tenemos las fechas en formato yyyy-mm, mostramos + //un texto encima de cada barra, que dice a que fecha corresponse + svg.selectAll("text") + .data(months2) + .enter() + .append("text") + .text(function(d) { + return d; + }) + .attr("x", function(d, i) { + return (i * ((w / n) + 1))+5; + }) + .attr("y", function(d) { + return h+15; + }) + .attr("width", w/n - 1) + .attr("fill", "green") + .attr("font-size", "8"); + + //definimos un elemento de escala de 0 al mes con el mayor número + //de posts + var yScale = d3.scale.linear() + .domain([0, monthAcumMax]) + .range([h - 0, 0]); + + //definimos un elemento de eje, le decimos la escala que hemos + //definido antes + var yAxis = d3.svg.axis() + .scale(yScale) + .orient("right") + .ticks(9); + + //añadimos a svg el eje que hemos definido + svg.append("g") + .attr("class", "axis") + .attr("transform", "translate(" + w - 20 + ",0)") + .call(yAxis); + + } + else + { + var svg = d3.select("#histogram") + .html(""); + } + + } +}); + +})(jQuery); diff --git a/examples/reuters/widgets/PyramidAges.js b/examples/reuters/widgets/PyramidAges.js new file mode 100644 index 00000000..e3f61f62 --- /dev/null +++ b/examples/reuters/widgets/PyramidAges.js @@ -0,0 +1,272 @@ +(function ($) { + +AjaxSolr.PyramidAges = AjaxSolr.AbstractFacetWidget.extend({ + + afterRequest: function () { + +var self = this; + d3.select("#pyramidAges").html(""); + var resultPivot=this.getFacetCounts(); + + var female = resultPivot.F; + var male = resultPivot.M; + + var femaleAges10 = [0,0,0,0,0,0,0,0,0,0]; + var maleAges10 = [0,0,0,0,0,0,0,0,0,0]; + + var femaleAges = []; + var femaleAges1 = []; + var femaleAges11 = []; + + var maleAges = []; + var maleAges1 = []; + var maleAges11 = []; + + if (resultPivot.F !== undefined) + { + + femaleAges = female[1]; + + for (var key in femaleAges) + { + femaleAges1 = femaleAges[key]; + + switch (true){ + case ((parseInt(key) >= 0) && (parseInt(key) < 10)): + femaleAges10[0] = femaleAges10[0] + femaleAges1; + break; + case ((parseInt(key) >= 11) && (parseInt(key) < 20)): + femaleAges10[1] = femaleAges10[1] + femaleAges1; + break; + case ((parseInt(key) >= 21) && (parseInt(key) < 30)): + femaleAges10[2] = femaleAges10[2] + femaleAges1; + break; + case ((parseInt(key) >= 31) && (parseInt(key) < 40)): + femaleAges10[3] = femaleAges10[3] + femaleAges1; + break; + case ((parseInt(key) >= 41) && (parseInt(key) < 50)): + femaleAges10[4] = femaleAges10[4] + femaleAges1; + break; + case ((parseInt(key) >= 51) && (parseInt(key) < 60)): + femaleAges10[5] = femaleAges10[5] + femaleAges1; + break; + case ((parseInt(key) >= 61) && (parseInt(key) <= 70)): + femaleAges10[6] = femaleAges10[6] + femaleAges1; + break; + case ((parseInt(key) >= 71) && (parseInt(key) <= 80)): + femaleAges10[7] = femaleAges10[7] + femaleAges1; + break; + case ((parseInt(key) >= 81) && (parseInt(key) <= 90)): + femaleAges10[8] = femaleAges10[8] + femaleAges1; + break; + case (parseInt(key) >= 91): + femaleAges10[9] = femaleAges10[9] + femaleAges1; + break; + } + } + +} + +if (resultPivot.M !== undefined) + { + maleAges = male[1]; + + for (var key in maleAges) + { + maleAges1 = maleAges[key]; + + + switch (true){ + case ((parseInt(key) >= 0) && (parseInt(key) <= 9)): + maleAges10[0] = maleAges10[0] + maleAges1; + break; + case ((parseInt(key) >= 10) && (parseInt(key) <= 19)): + maleAges10[1] = maleAges10[1] + maleAges1; + break; + case ((parseInt(key) >= 20) && (parseInt(key) <= 29)): + maleAges10[2] = maleAges10[2] + maleAges1; + break; + case ((parseInt(key) >= 30) && (parseInt(key) <= 39)): + maleAges10[3] = maleAges10[3] + maleAges1; + break; + case ((parseInt(key) >= 40) && (parseInt(key) <= 49)): + maleAges10[4] = maleAges10[4] + maleAges1; + break; + case ((parseInt(key) >= 50) && (parseInt(key) <= 59)): + maleAges10[5] = maleAges10[5] + maleAges1; + break; + case ((parseInt(key) >= 60) && (parseInt(key) <= 69)): + maleAges10[6] = maleAges10[6] + maleAges1; + break; + case ((parseInt(key) >= 70) && (parseInt(key) <= 79)): + maleAges10[7] = maleAges10[7] + maleAges1; + break; + case ((parseInt(key) >= 80) && (parseInt(key) <= 89)): + maleAges10[8] = maleAges10[8] + maleAges1; + break; + case (parseInt(key) >= 90): + maleAges10[9] = maleAges10[9] + maleAges1; + break; + } + } + +} + + /* edit/input your data */ +var data = [ + {"sharedLabel": "90-99", "barData1": maleAges10[9], "barData2": femaleAges10[9]}, + {"sharedLabel": "80-89", "barData1": maleAges10[8], "barData2": femaleAges10[8]}, + {"sharedLabel": "70-79", "barData1": maleAges10[7], "barData2": femaleAges10[7]}, + {"sharedLabel": "60-69", "barData1": maleAges10[6], "barData2": femaleAges10[6]}, + {"sharedLabel": "50-59", "barData1": maleAges10[5], "barData2": femaleAges10[5]}, + {"sharedLabel": "40-49", "barData1": maleAges10[4], "barData2": femaleAges10[4]}, + {"sharedLabel": "30-39", "barData1": maleAges10[3], "barData2": femaleAges10[3]}, + {"sharedLabel": "20-29", "barData1": maleAges10[2], "barData2": femaleAges10[2]}, + {"sharedLabel": "10-19", "barData1": maleAges10[1], "barData2": femaleAges10[1]}, + {"sharedLabel": "0-9", "barData1": maleAges10[0], "barData2": femaleAges10[0]} +]; + +/* edit these settings freely */ +var w = 400, + h = 200, + topMargin = 15, + labelSpace = 40, + innerMargin = w/2+labelSpace, + outerMargin = 15, + gap = 2, + dataRange = d3.max(data.map(function(d) { return Math.max(d.barData1, d.barData2) })); + leftLabel = "Female", + rightLabel = "Male"; + +/* edit with care */ +var chartWidth = w - innerMargin - outerMargin, + barWidth = h / data.length, + yScale = d3.scale.linear().domain([0, data.length]).range([0, h-topMargin]), + total = d3.scale.linear().domain([0, dataRange]).range([0, chartWidth - labelSpace]), + commas = d3.format(",.0f"); + +/* main panel */ +var vis = d3.select("#pyramidAges").html("").append("svg") + .attr("width", w) + .attr("height", h); + +/* barData1 label */ +vis.append("text") + .attr("class", "label") + .text(leftLabel) + .attr("x", w-innerMargin) + .attr("y", topMargin-3) + .attr("text-anchor", "end"); + +/* barData2 label */ +vis.append("text") + .attr("class", "label") + .text(rightLabel) + .attr("x", innerMargin) + .attr("y", topMargin-3); + +/* female bars and data labels */ +var bar = vis.selectAll("g.bar") + .data(data) + .enter().append("g") + .attr("class", "bar") + .attr("transform", function(d, i) { + return "translate(0," + (yScale(i) + topMargin) + ")"; + }); + +var wholebar = bar.append("rect") + .attr("width", w) + .attr("height", barWidth-gap) + .attr("fill", "none") + .attr("pointer-events", "all"); + +var highlight = function(c) { + return function(d, i) { + bar.filter(function(d, j) { + return i === j; + }).attr("class", c); + }; +}; + +bar + .on("mouseover", highlight("highlight bar")) + .on("mouseout", highlight("bar")) + .on("click", function (rect, bar) { + if (self.addByField('user_age','['+ (90-(bar*10)).toString() + ' TO ' + (99-(bar*10)).toString() + ']')) { + self.doRequest(); + } + }); + +bar.append("rect") + .attr("class", "femalebar") + .attr("height", barWidth-gap); + +bar.append("text") + .attr("class", "femalebar") + .attr("dx", -3) + .attr("dy", "1em") + .attr("text-anchor", "end"); + +bar.append("rect") + .attr("class", "malebar") + .attr("height", barWidth-gap) + .attr("x", innerMargin); + +bar.append("text") + .attr("class", "malebar") + .attr("dx", 3) + .attr("dy", "1em"); + +/* sharedLabels */ +bar.append("text") + .attr("class", "shared") + .attr("x", w/2) + .attr("dy", "1em") + .attr("text-anchor", "middle") + .text(function(d) { return d.sharedLabel; }); + +d3.select("#generate").on("click", function() { + for (var i=0; i 300) { - snippet += doc.dateline + ' ' + doc.text.substring(0, 300); - snippet += '' + doc.text.substring(300); + snippet += doc.comment_date + ' -- User id: ' + doc.comment_user_id +'' + if (doc.comment_content.length > 300) { + snippet += doc.comment_content.substring(0, 300); + snippet += '' + doc.comment_content.substring(300); snippet += ' more'; } else { - snippet += doc.dateline + ' ' + doc.text; + snippet += doc.comment_content; } - var output = '

' + doc.title + '

'; - output += ''; - output += '

' + snippet + '

'; + var output = '

' + doc.comment_id + '

'; + output += ''; + output += '

' + snippet + '

'; + output += 'Votes: ' + doc.comment_votes + ' -- Order: ' + doc.comment_order + ' -- Karma: ' + doc.comment_karma + '
'; return output; }, @@ -91,4 +101,4 @@ AjaxSolr.ResultWidget = AjaxSolr.AbstractWidget.extend({ } }); -})(jQuery); \ No newline at end of file +})(jQuery);