diff --git a/src/main/java/ru/org/linux/user/WhoisController.java b/src/main/java/ru/org/linux/user/WhoisController.java index 908f3aaad7..eb56d5292a 100644 --- a/src/main/java/ru/org/linux/user/WhoisController.java +++ b/src/main/java/ru/org/linux/user/WhoisController.java @@ -221,7 +221,7 @@ public ModelAndView handleUserNotFound() { @RequestMapping(value="/people/{nick}/profile", method = {RequestMethod.GET, RequestMethod.HEAD}, params="year-stats") @ResponseBody - public CompletionStage> yearStats(@PathVariable String nick) { + public CompletionStage>> yearStats(@PathVariable String nick) { User user = userService.getUser(nick); user.checkBlocked(); diff --git a/src/main/scala/ru/org/linux/user/UserStatisticsService.scala b/src/main/scala/ru/org/linux/user/UserStatisticsService.scala index 76c001dcd3..dd32a9920d 100644 --- a/src/main/scala/ru/org/linux/user/UserStatisticsService.scala +++ b/src/main/scala/ru/org/linux/user/UserStatisticsService.scala @@ -21,6 +21,7 @@ import java.util.concurrent.CompletionStage import com.sksamuel.elastic4s.http.ElasticClient import com.sksamuel.elastic4s.http.ElasticDsl._ +import com.sksamuel.elastic4s.http.Response import com.sksamuel.elastic4s.http.search.SearchResponse import com.sksamuel.elastic4s.searches.DateHistogramInterval import com.typesafe.scalalogging.StrictLogging @@ -84,18 +85,29 @@ class UserStatisticsService( ) } - def getYearStats(user: User): CompletionStage[java.util.Map[Long, Long]] = { - Future.successful(elastic).flatMap { - _ execute { - val root = boolQuery().filter(termQuery("author", user.getNick), rangeQuery("postdate").gt("now-1y/M")) - - search(MessageIndex) size 0 timeout 30.seconds query root aggs - dateHistogramAgg("days", "postdate").interval(DateHistogramInterval.days(1)).minDocCount(1) - } map { - _.result.aggregations.dateHistogram("days").buckets.map { bucket => - bucket.timestamp/1000 -> bucket.docCount - }.toMap.asJava - } + def getYearStats(user: User): CompletionStage[java.util.Map[String, java.util.Map[Long, Long]]] = { + val queries = Map( + "talks" -> termsQuery("group", Seq("talks", "science", "club")), + "news" -> termsQuery("section", Seq("news", "polls", "gallery")), + "tech" -> filter( termQuery("section", "forum"), not( termsQuery("group", Seq("talks", "science", "club")) ) ) + ) + + Future.successful(elastic).flatMap { el => + Future.successful(queries.keys.map { key => + key -> Await.result(el.execute { + val root = boolQuery().filter( + termQuery("author", user.getNick), + rangeQuery("postdate").gt("now-1y/M"), + queries(key)) + + search(MessageIndex) size 0 timeout 30.seconds query root aggs + dateHistogramAgg("days", "postdate").interval(DateHistogramInterval.days(1)).minDocCount(1) + } map { sr: Response[SearchResponse] => + sr.result.aggregations.dateHistogram("days").buckets.map { bucket => + bucket.timestamp/1000 -> bucket.docCount + }.toMap.asJava + }, 5.seconds) + }.toMap[String, java.util.Map[Long, Long]].asJava) }.toJava } diff --git a/src/main/webapp/WEB-INF/jsp/whois.jsp b/src/main/webapp/WEB-INF/jsp/whois.jsp index 556accc6d1..0cd15b70cf 100644 --- a/src/main/webapp/WEB-INF/jsp/whois.jsp +++ b/src/main/webapp/WEB-INF/jsp/whois.jsp @@ -63,21 +63,83 @@ range: 12, domainDynamicDimension: false, displayLegend: false, - legend: [8, 32, 64, 128], + legend: [8], + legendColors: ['#000', '#fff'], cellSize: size, start: new Date("<%= DateTime.now().minusMonths(11).toString() %>"), - tooltip: true, + tooltip: false, domainLabelFormat: function (date) { return moment(date).format("MMMM"); }, subDomainDateFormat: function (date) { return moment(date).format("LL"); }, - subDomainTitleFormat: { - empty: "{date}", - filled: "{date}
сообщений: {count}" + afterLoadData: function(data) { + var stats = {}; + var sections = { + talks: 16, + tech: 8, + news: 0, + }; + var curIntTs, ts, addition, curCount; + + for (var section in sections) { + if (sections.hasOwnProperty(section) && data[section]) { + for (ts in data[section]) { + curIntTs = parseInt(ts); + if (!isNaN(curIntTs)) { + curCount = data[section][ts]; + if (curCount > 0xff) curCount = 0xff; // clamp + addition = curCount << sections[section]; + if (stats[curIntTs] !== undefined) { + stats[curIntTs] += addition; + } else { + stats[curIntTs] = addition; + } + } + } + } + } + + return stats; + }, + onComplete: function() { + var rects = document.getElementById('cal-heatmap').getElementsByTagName('rect'); + var rect, value, talks, tech, news, r, g, b; + var PI_0019 = Math.PI * 0.0019; + for (var i = 0; i < rects.length; i ++) { + rect = rects[i]; + if (rect.__data__ && rect.__data__.v) { + value = parseInt(rect.__data__.v); + if (!isNaN(value)) { + talks = value >> 16; + tech = (value >> 8) & 0xff; + news = value & 0xff; + + // excite + r = talks ? parseInt(180 + 60 * Math.sin(talks * PI_0019)) : 0; + g = tech ? parseInt(180 + 60 * Math.sin(tech * PI_0019)) : 0; + b = news ? parseInt(180 + 60 * Math.sin(news * PI_0019)) : 0; + + rect.setAttribute('fill', 'rgb(' + r + ',' + g + ',' + b + ')'); + rect.setAttribute('class', ''); + + if (rect.nextSibling && rect.nextSibling.tagName === 'title') { + rect.nextSibling.innerHTML += '\n' + + (talks ? 'Talks / Клуб / S&E: ' + + (talks === 0xff ? '≥' : '') + talks + '\n' : '') + + (tech ? 'Технические разделы: ' + + (tech === 0xff ? '≥' : '') + tech + '\n' : '') + + (news ? 'Новости / Галерея / Опросы: ' + + (news === 0xff ? '≥' : '') + news : ''); + } + } + } + } } }); + // suppress automatic redraws + CalHeatMap.prototype.fill = function() {}; } }); }); diff --git a/src/main/webapp/sass/_cal-heapmap.scss b/src/main/webapp/sass/_cal-heapmap.scss index 1e45830e5d..1ad7980321 100644 --- a/src/main/webapp/sass/_cal-heapmap.scss +++ b/src/main/webapp/sass/_cal-heapmap.scss @@ -46,45 +46,6 @@ fill: #999 } -/* -.cal-heatmap-container .q0 -{ - background-color: grayscale(darken($heatmap_max, 20%)); - fill: grayscale(darken($heatmap_max, 20%)); - stroke: #ededed -} -*/ - -.cal-heatmap-container .q1 -{ - background-color: adjust-color($heatmap_max, $lightness: -20%*$heatmap_scale); - fill: adjust-color($heatmap_max, $lightness: -20%*$heatmap_scale); -} - -.cal-heatmap-container .q2 -{ - background-color: adjust-color($heatmap_max, $lightness: -15%*$heatmap_scale); - fill: adjust-color($heatmap_max, $lightness: -15%*$heatmap_scale); -} - -.cal-heatmap-container .q3 -{ - background-color: adjust-color($heatmap_max, $lightness: -10%*$heatmap_scale); - fill: adjust-color($heatmap_max, $lightness: -10%*$heatmap_scale); -} - -.cal-heatmap-container .q4 -{ - background-color: adjust-color($heatmap_max, $lightness: -5%*$heatmap_scale); - fill: adjust-color($heatmap_max, $lightness: -5%*$heatmap_scale); -} - -.cal-heatmap-container .q5 -{ - background-color: $heatmap_max; - fill: $heatmap_max; -} - .cal-heatmap-container rect.highlight { stroke:#444; diff --git a/src/main/webapp/sass/waltz/_colors.scss b/src/main/webapp/sass/waltz/_colors.scss index 800c9b75a6..8a70aa21c3 100644 --- a/src/main/webapp/sass/waltz/_colors.scss +++ b/src/main/webapp/sass/waltz/_colors.scss @@ -48,6 +48,6 @@ $tagpage_group_label_background: #E7AF55; $tagpage_group_label_color: $table_link_color; $stars_color: #465A48; -$heatmap_max: darken(#8ae234, 15%); +$heatmap_max: #8ae234; $heatmap_scale: -1.5; -$tracker_tag_color: $table_border_color; \ No newline at end of file +$tracker_tag_color: $table_border_color; diff --git a/src/main/webapp/sass/waltz/style.scss b/src/main/webapp/sass/waltz/style.scss index 63c2e13f4b..dfa17b334f 100644 --- a/src/main/webapp/sass/waltz/style.scss +++ b/src/main/webapp/sass/waltz/style.scss @@ -477,4 +477,9 @@ fieldset legend { color: $stars_color; } +.cal-heatmap-container .graph-label +{ + fill: $text_color; +} + @import "../responsive"; diff --git a/src/main/webapp/sass/white2/style.scss b/src/main/webapp/sass/white2/style.scss index 085634ff42..417243e55f 100644 --- a/src/main/webapp/sass/white2/style.scss +++ b/src/main/webapp/sass/white2/style.scss @@ -280,8 +280,14 @@ textarea { @import "../responsive"; -$heatmap_max: darken(#8ae234, 15%); +$heatmap_max: #8ae234; $heatmap_scale: -1.5; @import "../cal-heapmap"; + +.cal-heatmap-container .graph-label +{ + fill: black; +} + @import "../tracker"; diff --git a/src/main/webapp/sass/zomg_ponies/_colors.scss b/src/main/webapp/sass/zomg_ponies/_colors.scss index bd35ad8452..a434efe551 100644 --- a/src/main/webapp/sass/zomg_ponies/_colors.scss +++ b/src/main/webapp/sass/zomg_ponies/_colors.scss @@ -51,6 +51,6 @@ $stars_color: #400040; $icon_button_active_color: $stars_color; -$heatmap_max: darken(#8ae234, 15%); +$heatmap_max: #8ae234; $heatmap_scale: -1.5; $tracker_tag_color: $tag_even_color; diff --git a/src/main/webapp/sass/zomg_ponies/style.scss b/src/main/webapp/sass/zomg_ponies/style.scss index ed29f32a9a..1d9048b711 100644 --- a/src/main/webapp/sass/zomg_ponies/style.scss +++ b/src/main/webapp/sass/zomg_ponies/style.scss @@ -166,7 +166,7 @@ a:hover { display: none; } -article, .boxlet, #counter_block, .infoblock, #nav-menu, #p-lang, .messages .msg, #warning-text, #tag-page-forum, fieldset, legend, #tag-page-news, #related-topics, .tags-first-letters{ +article, .boxlet, #counter_block, .infoblock, #nav-menu, #p-lang, .messages .msg, #warning-text, #tag-page-forum, fieldset, legend, #tag-page-news, #related-topics, .tags-first-letters, #cal-heatmap{ background-color: $article_background; border-top-color: white; border-left-color: white; @@ -725,4 +725,9 @@ fieldset legend { border: 2px solid $targeted_message_border_color; } +.cal-heatmap-container .graph-label +{ + fill: $text_color; +} + @import "../responsive";