diff --git a/app/directives/distribution-graph/distribution-graph.directive.js b/app/directives/distribution-graph/distribution-graph.directive.js index fb5565efb..1d6197044 100644 --- a/app/directives/distribution-graph/distribution-graph.directive.js +++ b/app/directives/distribution-graph/distribution-graph.directive.js @@ -1,5 +1,8 @@ import angular from 'angular' import d3 from 'd3' +import React from 'react' // eslint-disable-line no-unused-vars +import ReactDOM from 'react-dom' +import Tooltip from 'appirio-tech-react-components/components/Tooltip/Tooltip.jsx' (function() { 'use strict' @@ -59,7 +62,7 @@ import d3 from 'd3' ] var measurements = { - w: 600, + w: 900, h: 400, padding: { top: 20, @@ -176,7 +179,24 @@ import d3 from 'd3' .attr('fill', function(d) { return ratingToColor($scope.colors, d.start) }) - + + var mousemoveInterval = null + + /* render react tooltip component */ + ReactDOM.unmountComponentAtNode(document.getElementById('chart-tooltip')) + ReactDOM.render( +
+
+
+
+
+
+
+
+
+ , document.getElementById('chart-tooltip')) + + $scope.isFocused = false svg.selectAll('rect.hover') .data(ranges) .enter() @@ -187,24 +207,81 @@ import d3 from 'd3' return xScale(i) }) .attr('y', function(d) { - return padding.top + return yScale(d.number) }) .attr('width', xScale.rangeBand()) .attr('height', function(d) { - return totalH - padding.bottom - padding.top + return totalH - padding.bottom - yScale(d.number) }) .on('mouseover', function(d) { + $scope.isFocused = true $scope.highlightedRating = d.start $scope.displayCoders = true $scope.numCoders = d.number + + /* update tooltip location on mouseover, feature currently not inbuilt in react tooltip component */ + d3.select('#chart-tooltip') + .style('left', (d3.event.pageX-4) + 'px') + .style('top', (d3.event.pageY-4) + 'px') + + $('#chart-tooltip').addClass('distribution') + d3.select('#chart-tooltip .tooltip-container') + .style('left', '20px !important') + .style('top', '-20px !important') + + d3.select('#chart-tooltip .tooltip-container .tooltip-pointer') + .style('left', '-5.5px !important') + .style('bottom', '25px !important') + + d3.select('#chart-tooltip .challenge-name').text($scope.numCoders + ' Coders') + d3.select('#chart-tooltip .challenge-date').text('Rating Range: '+ $scope.highlightedRating + '-'+($scope.highlightedRating+99)) + d3.select('#chart-tooltip .tooltip-rating').text($scope.numCoders) + d3.select('#chart-tooltip .tooltip-rating').style('background', ratingToColor($scope.colors, $scope.highlightedRating)) $scope.$digest() }) + .on('mousemove', function(d) { + + /* update tooltip on mousemove, using interval of 50ms to improve performance */ + window.clearTimeout(mousemoveInterval) + var left = (d3.event.pageX-4) + var top = (d3.event.pageY-4) + + mousemoveInterval = window.setTimeout(function(){ + d3.select('#chart-tooltip') + .style('left', left + 'px') + .style('top', top + 'px') + + d3.select('#chart-tooltip .tooltip-container') + .style('left', '20px !important') + .style('top', '-20px !important') + + d3.select('#chart-tooltip .tooltip-container .tooltip-pointer') + .style('left', '-5.5px !important') + .style('bottom', '25px !important') + }, 50) + + }) .on('mouseout', function(d) { $scope.displayCoders = false $scope.highlightedRating = false + $scope.isFocused = false $scope.$digest() }) - + + /* hide tooltip when clicked anywhere outside */ + d3.select('body').on('click', function(){ + if((d3.event.target.classList[0] != 'tooltip-target') && !$('#chart-tooltip .tooltip-container').hasClass('tooltip-hide') && + !isInArray(d3.event.target.classList[0], ['tooltip-content-container', 'tooltip-container', 'tooltip-body', 'Tooltip']) && + (d3.event.target.tagName.toLowerCase()!='circle') && !(d3.event.target.tagName.toLowerCase()=='rect' && d3.event.target.classList[0] == 'hover')) { + $('#chart-tooltip .tooltip-target').trigger('click') + $('#chart-tooltip').removeClass('distribution') + } + }) + + function isInArray(value, array) { + return array.indexOf(value) > -1 + } + svg.selectAll('line.xaxis') .data(ranges) .enter() diff --git a/app/directives/distribution-graph/distribution-graph.jade b/app/directives/distribution-graph/distribution-graph.jade index 6faf6b4fd..f146363af 100644 --- a/app/directives/distribution-graph/distribution-graph.jade +++ b/app/directives/distribution-graph/distribution-graph.jade @@ -1,15 +1,11 @@ .distribution-graph-directive(ng-show="graphState.show == 'distribution'") - .graph-viewer - .distribution-graph + .graph-title + .text Rating Distribution Graph + .button-group + button.tc-btn.tc-btn-s(ng-click="graphState.show = 'history'") View Rating History + button.tc-btn.tc-btn-s.active(ng-click="graphState.show = 'distribution'") View Rating Distribution - .info-port - .coders(ng-if="displayCoders", style="background: {{highlightedRating || rating | ratingColor}}") - .num {{numCoders}} - .label CODERS - .coders(ng-if="!displayCoders", style="background: {{rating | ratingColor}}") - .num {{rating}} - .label RATING - button.tc-btn.tc-btn-s.compare(ng-click="graphState.show = 'history'") - | View Rating History + .graph-viewer + .distribution-graph \ No newline at end of file diff --git a/app/directives/history-graph/history-graph.directive.js b/app/directives/history-graph/history-graph.directive.js index f42120200..d634cc885 100644 --- a/app/directives/history-graph/history-graph.directive.js +++ b/app/directives/history-graph/history-graph.directive.js @@ -1,6 +1,9 @@ import angular from 'angular' import moment from 'moment' import d3 from 'd3' +import React from 'react' // eslint-disable-line no-unused-vars +import ReactDOM from 'react-dom' +import Tooltip from 'appirio-tech-react-components/components/Tooltip/Tooltip.jsx' (function() { 'use strict' @@ -16,11 +19,11 @@ import d3 from 'd3' rating: '=', graphState: '=' }, - controller: ['$scope', HistoryGraphController] + controller: ['$scope', '$state', '$filter', 'CONSTANTS', HistoryGraphController] } } - function HistoryGraphController($scope) { + function HistoryGraphController($scope, $state, $filter, CONSTANTS) { $scope.colors = [ // grey { @@ -59,7 +62,7 @@ import d3 from 'd3' } ] var measurements = { - w: 600, + w: 900, h: 400, padding: { top: 20, @@ -156,7 +159,6 @@ import d3 from 'd3' .attr('width', w + padding.left + padding.right) .attr('height', h + padding.top + padding.bottom) - svg.append('rect') .attr('x', padding.left) .attr('y', padding.top) @@ -243,7 +245,21 @@ import d3 from 'd3' return y } } - + + /* render react tooltip component */ + ReactDOM.unmountComponentAtNode(document.getElementById('chart-tooltip')) + ReactDOM.render( +
+
+
+
+
+
+
+
+
+ , document.getElementById('chart-tooltip')) + svg.selectAll('circle') .data(history) .enter() @@ -261,20 +277,44 @@ import d3 from 'd3' $scope.historyRating = d.newRating $scope.historyDate = moment(d.ratingDate).format('YYYY-MM-DD') $scope.historyChallenge = d.challengeName + $('#chart-tooltip .tooltip-container').on('click', function(){ + if($state.params && ($state.params.subTrack === 'SRM' || $state.params.subTrack === 'MARATHON_MATCH')) + location.href = $filter('challengeLinks')({'rounds': [{id: d.challengeId, forumId: null}], 'track': $state.params.track, 'subTrack': $state.params.subTrack}, 'detail') + else + location.href = $filter('challengeLinks')({id: d.challengeId, 'track': $state.params.track, 'subTrack': $state.params.subTrack}, 'detail') + }) + + /* update tooltip location on mouseover, feature currently not inbuilt in react tooltip component */ + d3.select('#chart-tooltip') + .style('left', (d3.event.pageX-5) + 'px') + .style('top', (d3.event.pageY-5) + 'px') + d3.select('#chart-tooltip .tooltip-container') + .style('left', '20px !important') + .style('top', '-20px !important') + d3.select('#chart-tooltip .tooltip-container .tooltip-pointer') + .style('left', '-5.5px !important') + .style('bottom', '25px !important') + + d3.select('#chart-tooltip .challenge-name').text($scope.historyChallenge) + d3.select('#chart-tooltip .challenge-date').text(moment(d.ratingDate).format('MMM DD, YYYY')) + d3.select('#chart-tooltip .tooltip-rating').text($scope.historyRating) + d3.select('#chart-tooltip .tooltip-rating').style('background', ratingToColor($scope.colors, $scope.historyRating)) + $('#chart-tooltip').removeClass('distribution') $scope.$digest() - d3.select(this) - .attr('r', 6.0) }) .on('mouseout', function(d) { $scope.historyRating = undefined $scope.$digest() - d3.select(this) - .attr('r', 4.5) - .attr('stroke', 'none') - .attr('stroke-width', '0px') }) - .attr('r', 4.5) + /* hide tooltip when clicked anywhere outside */ + d3.select('body').on('click', function(){ + if((d3.event.target.classList[0] != 'tooltip-target') && !$('#chart-tooltip .tooltip-container').hasClass('tooltip-hide') && + (d3.event.target.tagName.toLowerCase()!='circle') && !(d3.event.target.tagName.toLowerCase()=='rect' && d3.event.target.classList[0] == 'hover')) { + $('#chart-tooltip .tooltip-target').trigger('click') + $('#chart-tooltip .tooltip-container').off('click') + } + }) } diff --git a/app/directives/history-graph/history-graph.jade b/app/directives/history-graph/history-graph.jade index 4eef54967..6d9dd7d16 100644 --- a/app/directives/history-graph/history-graph.jade +++ b/app/directives/history-graph/history-graph.jade @@ -1,13 +1,11 @@ .history-graph-directive(ng-show="graphState.show == 'history'") - .history-graph-container - .history-graph + .graph-title + .text Rating History Graph + .button-group + button.tc-btn.tc-btn-s.active(ng-click="graphState.show = 'history'") View Rating History + button.tc-btn.tc-btn-s(ng-click="graphState.show = 'distribution'") View Rating Distribution + + .history-graph-container - .info-port - .rating(style="background: {{historyRating || rating | ratingColor}}") - .num {{historyRating || rating}} - .label RATING - .history-info - .challenge(ng-if="historyRating") {{historyChallenge}} - .date(ng-if="historyRating") {{historyDate | date}} - button.tc-btn.tc-btn-s.compare(ng-click="graphState.show = 'distribution'") View Rating Distribution + .history-graph \ No newline at end of file diff --git a/app/filters/challengeLinks.filter.js b/app/filters/challengeLinks.filter.js index 5f3922110..e6360ce0d 100644 --- a/app/filters/challengeLinks.filter.js +++ b/app/filters/challengeLinks.filter.js @@ -23,6 +23,15 @@ import angular from 'angular' case 'detail': return String.supplant('https://community.{domain}/longcontest/stats/?module=ViewOverview&rd={roundId}', data) } + } else if (challenge.subTrack === 'SRM') { + data = { + domain: CONSTANTS.domain, + roundId: challenge.rounds[0].id + } + switch (type) { + case 'detail': + return String.supplant('https://community.{domain}/stat?c=round_overview&rd={roundId}', data) + } } else { data = { domain: CONSTANTS.domain, diff --git a/app/index.jade b/app/index.jade index 5a5a0beeb..2424fcc00 100644 --- a/app/index.jade +++ b/app/index.jade @@ -36,3 +36,5 @@ html .fold-pusher div(ui-view="footer") + + #chart-tooltip \ No newline at end of file diff --git a/app/index.js b/app/index.js index e698b267b..89760c27c 100644 --- a/app/index.js +++ b/app/index.js @@ -25,6 +25,7 @@ require('xml2js') require('appirio-tech-ng-ui-components') require('appirio-tech-ng-iso-constants') +require('appirio-tech-react-components') // Vendor styles require('../node_modules/angucomplete-alt/angucomplete-alt.css') diff --git a/assets/css/directives/distribution-graph.scss b/assets/css/directives/distribution-graph.scss index 9b0b83e31..cce226949 100644 --- a/assets/css/directives/distribution-graph.scss +++ b/assets/css/directives/distribution-graph.scss @@ -12,6 +12,51 @@ } +.graph-title { + width: 960px; + padding-left: 45px; + text-align: left; + margin: auto; + font-size: 28px; + line-height: 35px; + color: #47474F; + + .text { + float: left; + } + + .button-group { + float: right; + display: block; + button.tc-btn.tc-btn-s { + background: #fff; + color: #47474f; + border: 1px solid #c3c3c8; + height: 30px; + box-sizing: border-box; + &.active { + background: #dcdce0; + -webkit-box-shadow: inset 0px 1px 3px 0px rgba(0,0,0,1); + -moz-box-shadow: inset 0px 1px 3px 0px rgba(0,0,0,1); + box-shadow: inset 0px 1px 3px 0px rgba(0,0,0,1); + border: none; + } + &:first-child { + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + &:last-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + } + } + } +} + .graph-viewer { margin-top: 18px; display: flex; diff --git a/assets/css/directives/history-graph.scss b/assets/css/directives/history-graph.scss index 38c4692ae..3726d3614 100644 --- a/assets/css/directives/history-graph.scss +++ b/assets/css/directives/history-graph.scss @@ -4,6 +4,7 @@ display: flex; flex-direction: column; align-items: center; + margin-top: 20px; .compare { margin-top: 5px; margin-bottom: 15px; @@ -13,6 +14,35 @@ display: flex; flex-direction: column; align-items: center; + .graph-title { + width: 960px; + padding-left: 45px; + text-align: left; + margin: auto; + font-size: 28px; + line-height: 35px; + color: #47474F; + + .text { + float: left; + } + + .button-group { + float: right; + display: block; + button.tc-btn.tc-btn-s { + background: #fff; + color: #47474f; + &.active { + background: #dcdce0; + -webkit-box-shadow: 0px 1px 3px 0px rgba(0,0,0,1); + -moz-box-shadow: 0px 1px 3px 0px rgba(0,0,0,1); + box-shadow: 0px 1px 3px 0px rgba(0,0,0,1); + } + } + } + } + .history-graph { display: flex; diff --git a/assets/css/my-dashboard/subtrack-stats.scss b/assets/css/my-dashboard/subtrack-stats.scss index c0153a0cf..38ba99048 100644 --- a/assets/css/my-dashboard/subtrack-stats.scss +++ b/assets/css/my-dashboard/subtrack-stats.scss @@ -106,3 +106,81 @@ } } } + +#chart-tooltip { + width: 18px; + height: 18px; + border-radius: 50%; + position: absolute; + z-index: 1000; + cursor: pointer; +} + +#chart-tooltip.distribution { + width: 4px; + height: 4px; + cursor: auto; +} + +.tooltip-target { + width: 18px; + height: 18px; + border-radius: 50%; + display: block; + top: -4px; + cursor: pointer; + left: -4px; +} + +#chart-tooltip.distribution .tooltip-target { + top: -4px; + left: -4px; + width: 8px; + height: 8px; + border-radius: 0; +} + +.Tooltip .tooltip-container { + position: relative; + opacity: 1; + top: 10px !important; + left: -150px !important; + width: 320px; + height: 115px; +} + +.Tooltip .tooltip-container .tooltip-pointer { + left: 150px !important; + top: -5.5px !important; + bottom: auto !important; +} + + + +#chart-tooltip .tooltip-rating { + width: 60px; + height: 60px; + margin-right: 15px; + border-radius: 50%; + float: left; + text-align: center; + padding-top: 23px; +} + +#chart-tooltip .tooltip-challenge { + height: 100%; + width: calc(100% - 90px); + float: left; +} + +#chart-tooltip .tooltip-challenge .challenge-name { + white-space: normal; + line-height: 20px; + word-wrap: break-word; +} + +#chart-tooltip .tooltip-challenge .challenge-date { + font-weight: normal; + margin-top: 13px; + font-size: 12px; +} \ No newline at end of file diff --git a/package.json b/package.json index 469c5a3bb..70fe41f59 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "appirio-styles": "0.x.x", "appirio-tech-ng-iso-constants": "^1.0.6", "appirio-tech-ng-ui-components": "^2.1.2", + "appirio-tech-react-components": "^0.0.12", "auth0-angular": "^4.1.0", "auth0-js": "^6.8.0", "d3": "^3.5.14",