<commit_before>'use strict';

angular.module('oncokbApp')
    .controller('VariantCtrl', [
        '$scope',
        '$filter',
        '$location',
        '$timeout',
        '$rootScope',
        'dialogs',
        'DatabaseConnector',
        'reportGeneratorParseAnnotation',
        'GenerateReportDataService',
        'reportViewFactory',
        'DeepMerge',
        'x2js',
        'FindRegex',
        'OncoKB',
        'stringUtils',
        '_',
        function($scope, $filter, $location, $timeout, $rootScope, dialogs, DatabaseConnector, reportGeneratorParseAnnotation, ReportDataService, reportViewFactory, DeepMerge, x2js, FindRegex, OncoKB, stringUtils, _) {
            'use strict';

            function getUnique(data, attr) {
                var unique = [];

                if (angular.isArray(data)) {
                    data.forEach(function(e) {
                        if (unique.indexOf(e[attr]) === -1) {
                            unique.push(e[attr]);
                        }
                    });
                    return unique.sort();
                }
                return null;
            }

            function checkUrl() {
                $timeout(function() {
                    if ($location.url() !== $location.path()) {
                        var urlVars = $location.search();
                        if (urlVars.hasOwnProperty('hugoSymbol')) {
                            $scope.gene = $scope.genes[$filter('getIndexByObjectNameInArray')($scope.genes, urlVars.hugoSymbol || '')];
                        }
                        if (urlVars.hasOwnProperty('alteration')) {
                            $scope.alteration = stringUtils.trimMutationName(urlVars.alteration);
                        }
                        if (urlVars.hasOwnProperty('cancerType')) {
                            $scope.view.selectedCancerType = $scope.view.filteredCancerTypes[$filter('getCancerTypeByMainType')($scope.view.filteredCancerTypes, urlVars.cancerType, 'cancerType')];
                        }
                        if (urlVars.hasOwnProperty('subtype')) {
                            $scope.view.selectedSubtype = $scope.view.filteredCancerTypes[$filter('getIndexByObjectNameInArray')($scope.view.filteredSubtypes, urlVars.subtype, 'name')];
                        }
                        $scope.search();
                    }
                }, 200);
            }

            // Retru whether URL changed
            function changeUrl(params) {
                var location = $location.search();
                var same = true;

                if (Object.keys(location).length === Object.keys(params).length) {
                    for (var key in params) {
                        if (!location.hasOwnProperty(key)) {
                            same = false;
                            break;
                        } else if (location[key] !== params[key]) {
                            same = false;
                            break;
                        }
                    }
                } else {
                    same = false;
                }
                if (same) {
                    return false;
                }
                $location.search(params).replace();
                return true;
            }

            function searchAnnotationCallback(status, data) {
                var annotation = {};
                var params = {
                    geneName: '',
                    alteration: '',
                    cancerType: '',
                    subtype: '',
                    annotation: '',
                    relevantCancerType: ''
                };
                if (status === 'success') {
                    annotation = reportGeneratorParseAnnotation.parse(data);
                    $scope.annotation = annotation.annotation;

                    params.geneName = $scope.gene;
                    params.alteration = $scope.alteration;
                    params.tumorType = $scope.view.selectedCancerType ? $scope.view.selectedCancerType.mainType.name : '';// ReportDataService accepts tumorType for now
                    params.subtype = $scope.view.selectedSubtype ? $scope.view.selectedSubtype.name : '';
                    params.annotation = annotation.annotation;
                    params.relevantCancerType = annotation.relevantCancerType;

                    $scope.reportParams.reportContent = ReportDataService.init([params]);
                    $scope.reportParams.requestInfo = {
                        email: $rootScope.user.email,
                        folderName: '',
                        fileName: params.geneName + '_' + params.alteration + '_' + params.cancerType + '_' + params.subtype,
                        userName: $rootScope.user.name
                    };

                    var reportViewParams = {};
                    for (var key in $scope.reportParams.reportContent) {
                        if (key === 'items') {
                            _.each($scope.reportParams.reportContent.items[0], function(item, key1) {
                                reportViewParams[key1] = item;
                            });
                        } else {
                            reportViewParams[key] = $scope.reportParams.reportContent[key];
                        }
                    }
                    $scope.reportViewData = reportViewFactory.getData(reportViewParams);
                }
                $scope.rendering = false;
            }

            function generating() {
                $timeout(function() {
                    if ($scope.generaingReport) {
                        generating();
                    } else {
                        $rootScope.$broadcast('dialogs.wait.complete');
                        dialogs.notify('Request finished', 'The request has been added. Your will received an email when your report(s) is ready. Thank you.');
                    }
                }, 500);
            }

            function separateTumorTypes(tumorTypes) {
                var subtypes = {};
                var cancerTypes = {};
                _.each(tumorTypes, function(tumorType) {
                    if (tumorType) {
                        if (tumorType.name) {
                            subtypes[tumorType.name] = tumorType;
                        }
                        if (!tumorType.name && tumorType.mainType && tumorType.mainType.name) {
                            cancerTypes[tumorType.mainType.name] = tumorType;
                        }
                    }
                });
                return {
                    subtypes: _.values(subtypes),
                    cancerTypes: _.values(cancerTypes)
                };
            }

            function findCancerType(cancerType) {
                for (var i = 0; i < $scope.view.filteredCancerTypes.length; i++) {
                    if ($scope.view.filteredCancerTypes[i].mainType.name === cancerType) {
                        return $scope.view.filteredCancerTypes[i];
                    }
                }
            }

            function findSubtypesByCancerType(cancerType) {
                var subtypes = [];
                for (var i = 0; i < $scope.subtypes.length; i++) {
                    if ($scope.subtypes[i].mainType.name === cancerType) {
                        subtypes.push($scope.subtypes[i]);
                    }
                }
                return subtypes;
            }

            function containSubtype(subtype, subtypes) {
                for (var i = 0; i < subtypes.length; i++) {
                    if (subtypes[i].name === subtype.name) {
                        return true;
                    }
                }
                return false;
            }

            $scope.init = function() {
                $scope.view = {};
                $scope.reportParams = {
                    reportContent: {},
                    requestInfo: {}
                };

                $scope.rendering = false;

                $scope.loadingPage = true;

                $scope.generaingReport = false;

                // Control UI-Bootstrap angularjs, open one at a time
                $scope.oneAtATime = true;

                $scope.isCollapsed = {};

                $scope.displayParts = {
                    annotationSummary: {
                        displayName: 'Summary',
                        objectName: 'annotation_summary'
                    },
                    geneAnnotation: {
                        displayName: 'Gene Annotation',
                        objectName: 'gene_annotation'
                    },
                    variantEffect: {
                        displayName: 'Variant Effect',
                        objectName: 'variant_effect'
                    }
                };

                $scope.summaryTableTitles = [
                    'Treatment Implications',
                    'FDA Approved Drugs in Tumor Type',
                    'FDA Approved Drugs in Other Tumor Type',
                    'Clinical Trials',
                    'Additional Information'
                ];

                $scope.reportMatchedParams = [
                    'treatment',
                    'fdaApprovedInTumor',
                    'fdaApprovedInOtherTumor',
                    'clinicalTrials',
                    'additionalInfo'
                ];

                $scope.summaryTableTitlesContent = {
                    'Treatment Implications': [
                        'nccn_guidelines',
                        'standard_therapeutic_implications'],
                    'Clinical Trials': ['clinical_trial', 'investigational_therapeutic_implications'],
                    'Additional Information': ['prevalence', 'prognostic_implications'],
                    'FDA Approved Drugs in Tumor Type': [],
                    'FDA Approved Drugs in Other Tumor Type': []
                };

                /* eslint camelcase: ["error", {properties: "never"}]*/
                $scope.consequences = {
                    feature_truncation: 'Truncation',
                    frameshift_variant: 'Frame shift',
                    inframe_deletion: 'In frame deletion',
                    inframe_insertion: 'In frame insertion',
                    initiator_codon_variant: 'Initiator codon',
                    missense_variant: 'Missense',
                    stop_gained: 'Stop gained',
                    synonymous_variant: 'Synonymous'
                };

                $scope.specialAttr = ['investigational_therapeutic_implications', 'standard_therapeutic_implications'];

                if (OncoKB.global.genes && OncoKB.global.genes && OncoKB.global.tumorTypes) {
                    var separatedTumorTypes = separateTumorTypes(OncoKB.global.tumorTypes);

                    $scope.genes = getUnique(angular.copy(OncoKB.global.genes), 'hugoSymbol');
                    $scope.cancerTypes = separatedTumorTypes.cancerTypes;
                    $scope.subtypes = separatedTumorTypes.subtypes;
                    $scope.view.filteredCancerTypes = angular.copy($scope.cancerTypes);
                    $scope.view.filteredSubtypes = angular.copy($scope.subtypes);
                    $scope.loadingPage = false;
                } else {
                    DatabaseConnector.getGeneTumorType(function(data) {
                        OncoKB.global.genes = angular.copy(data.genes);
                        OncoKB.global.tumorTypes = angular.copy(data.tumorTypes);

                        var separatedTumorTypes = separateTumorTypes(OncoKB.global.tumorTypes);

                        $scope.genes = getUnique(data.genes, 'hugoSymbol');
                        $scope.cancerTypes = separatedTumorTypes.cancerTypes;
                        $scope.subtypes = separatedTumorTypes.subtypes;
                        $scope.view.filteredCancerTypes = angular.copy($scope.cancerTypes);
                        $scope.view.filteredSubtypes = angular.copy($scope.subtypes);
                        $scope.loadingPage = false;
                        checkUrl();
                    });
                }
            };

            $scope.isSearchable = function() {
                if ($scope.gene && $scope.alteration) {
                    return true;
                }
                return false;
            };

            $scope.fdaApproved = function(drug) {
                if (typeof drug.fda_approved === 'string' && drug.fda_approved.toLowerCase() === 'yes') {
                    return true;
                }
                return false;
            };

            $scope.hasSelectedCancerType = function() {
                if ($scope.view.hasOwnProperty('selectedCancerType') && $scope.view.selectedCancerType) {
                    return true;
                }
                return false;
            };

            $scope.setCollapsed = function(trial, attr) {
                $scope.isCollapsed[trial.trial_id][attr] = !$scope.isCollapsed[trial.trial_id][attr];
            };

            $scope.isArray = function(_var) {
                if (_var instanceof Array) {
                    return true;
                }
                return false;
            };

            $scope.isObject = function(_var) {
                if (typeof _var === 'object') {
                    return true;
                }
                return false;
            };

            $scope.displayProcess = function(str) {
                var specialUpperCasesWords = ['NCCN'];
                var specialLowerCasesWords = ['of', 'for'];

                str = str.replace(/_/g, ' ');
                str = str.replace(
                    /\w\S*/g,
                    function(txt) {
                        var _upperCase = txt.toUpperCase();
                        var _lowerCase = txt.toLowerCase();

                        if (specialUpperCasesWords.indexOf(_upperCase) !== -1) {
                            return _upperCase;
                        }

                        if (specialLowerCasesWords.indexOf(_lowerCase) !== -1) {
                            return _lowerCase;
                        }

                        return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
                    }
                );
                return str;
            };

            $scope.search = function() {
                var hasSelectedCancerType = $scope.hasSelectedCancerType();
                $scope.rendering = true;
                $scope.reportViewActive = hasSelectedCancerType;
                $scope.regularViewActive = !hasSelectedCancerType;
                $scope.alteration = stringUtils.trimMutationName($scope.alteration);
                var params = {alterationType: 'MUTATION'};
                var paramsContent = {
                    hugoSymbol: $scope.gene || '',
                    alteration: $scope.alteration || ''
                };

                for (var key in paramsContent) {
                    if (paramsContent[key] !== '') {
                        params[key] = paramsContent[key];
                    }
                }
                if (hasSelectedCancerType) {
                    params.cancerType = $scope.view.selectedCancerType.mainType.name;
                    if ($scope.view.selectedSubtype && $scope.view.selectedSubtype.name) {
                        params.tumorType = $scope.view.selectedSubtype.name;
                    } else {
                        params.tumorType = params.cancerType;
                    }
                } else {
                    params.cancerType = '';
                    params.subtype = '';
                    params.tumorType = '';
                }
                if ($scope.hasOwnProperty('selectedConsequence') && $scope.selectedConsequence) {
                    params.consequence = $scope.selectedConsequence;
                }

                changeUrl(params);

                DatabaseConnector.searchAnnotation(params, function(data) {
                    searchAnnotationCallback('success', data);
                }, function() {
                    searchAnnotationCallback('fail');
                });
            };

            $scope.generateReport = function() {
                var dlg = dialogs.create('views/emailDialog.html', 'emailDialogCtrl', 'Test string');
                dlg.result.then(function(data) {
                    if (typeof data !== 'undefined' && data && data !== '') {
                        $scope.generaingReport = true;
                        dlg = dialogs.wait('Sending request', 'Please wait...');
                        generating();
                        var params = $scope.reportParams;
                        params.requestInfo.email = data;
                        DatabaseConnector.googleDoc(params, function() {
                            $scope.generaingReport = false;
                        }, function() {
                            $scope.generaingReport = false;
                        });
                    }
                }, function() {
                    console.log('Did not do anything.');
                });
            };

            $scope.useExample = function() {
                $scope.gene = $scope.genes[$filter('getIndexByObjectNameInArray')($scope.genes, 'BRAF')];
                $scope.alteration = 'V600E';
                $scope.view.selectedCancerType = $scope.view.filteredCancerTypes[$filter('getCancerTypeByMainType')($scope.view.filteredCancerTypes, 'Melanoma')];
                $scope.view.selectedSubtype = '';
                $scope.selectedConsequence = '';
                $scope.search();
            };

            $scope.$watch('view.selectedSubtype', function(n) {
                if (n) {
                    $scope.view.selectedCancerType = findCancerType(n.mainType.name);
                } else {
                    // console.log($scope.view.selectedSubtype);
                }
            });

            $scope.$watch('view.selectedCancerType', function(n) {
                if (n) {
                    if ($scope.view.selectedSubtype) {
                        var mappedSubtypes = findSubtypesByCancerType(n.mainType.name);
                        if (!containSubtype($scope.view.selectedSubtype, mappedSubtypes)) {
                            $scope.view.filteredSubtypes = mappedSubtypes;
                            $scope.view.selectedSubtype = '';
                        }
                    } else {
                        $scope.view.filteredSubtypes = findSubtypesByCancerType(n.mainTypeName);
                    }
                } else {
                    $scope.view.filteredSubtypes = angular.copy($scope.subtypes);
                    $scope.view.selectedSubtype = '';
                }
            });
        }]);
<commit_msg>Bug fixed based on comment<commit_after>'use strict';

angular.module('oncokbApp')
    .controller('VariantCtrl', [
        '$scope',
        '$filter',
        '$location',
        '$timeout',
        '$rootScope',
        'dialogs',
        'DatabaseConnector',
        'reportGeneratorParseAnnotation',
        'GenerateReportDataService',
        'reportViewFactory',
        'DeepMerge',
        'x2js',
        'FindRegex',
        'OncoKB',
        'stringUtils',
        '_',
        function($scope, $filter, $location, $timeout, $rootScope, dialogs, DatabaseConnector, reportGeneratorParseAnnotation, ReportDataService, reportViewFactory, DeepMerge, x2js, FindRegex, OncoKB, stringUtils, _) {
            'use strict';

            function getUnique(data, attr) {
                var unique = [];

                if (angular.isArray(data)) {
                    data.forEach(function(e) {
                        if (unique.indexOf(e[attr]) === -1) {
                            unique.push(e[attr]);
                        }
                    });
                    return unique.sort();
                }
                return null;
            }

            function checkUrl() {
                $timeout(function() {
                    if ($location.url() !== $location.path()) {
                        var urlVars = $location.search();
                        if (urlVars.hasOwnProperty('hugoSymbol')) {
                            $scope.gene = $scope.genes[$filter('getIndexByObjectNameInArray')($scope.genes, urlVars.hugoSymbol || '')];
                        }
                        if (urlVars.hasOwnProperty('alteration')) {
                            $scope.alteration = stringUtils.trimMutationName(urlVars.alteration);
                        }
                        if (urlVars.hasOwnProperty('cancerType')) {
                            $scope.view.selectedCancerType = $scope.view.filteredCancerTypes[$filter('getCancerTypeByMainType')($scope.view.filteredCancerTypes, urlVars.cancerType, 'cancerType')];
                        }
                        if (urlVars.hasOwnProperty('subtype')) {
                            $scope.view.selectedSubtype = $scope.view.filteredCancerTypes[$filter('getIndexByObjectNameInArray')($scope.view.filteredSubtypes, urlVars.subtype, 'name')];
                        }
                        $scope.search();
                    }
                }, 200);
            }

            // Retru whether URL changed
            function changeUrl(params) {
                var location = $location.search();
                var same = true;

                if (Object.keys(location).length === Object.keys(params).length) {
                    for (var key in params) {
                        if (!location.hasOwnProperty(key)) {
                            same = false;
                            break;
                        } else if (location[key] !== params[key]) {
                            same = false;
                            break;
                        }
                    }
                } else {
                    same = false;
                }
                if (same) {
                    return false;
                }
                $location.search(params).replace();
                return true;
            }

            function searchAnnotationCallback(status, data) {
                var annotation = {};
                var params = {
                    geneName: '',
                    alteration: '',
                    cancerType: '',
                    subtype: '',
                    annotation: '',
                    relevantCancerType: ''
                };
                if (status === 'success') {
                    annotation = reportGeneratorParseAnnotation.parse(data);
                    $scope.annotation = annotation.annotation;

                    params.geneName = $scope.gene;
                    params.alteration = $scope.alteration;
                    params.tumorType = $scope.view.selectedCancerType ? $scope.view.selectedCancerType.mainType.name : '';// ReportDataService accepts tumorType for now
                    params.subtype = $scope.view.selectedSubtype ? $scope.view.selectedSubtype.name : '';
                    params.annotation = annotation.annotation;
                    params.relevantCancerType = annotation.relevantCancerType;

                    $scope.reportParams.reportContent = ReportDataService.init([params]);
                    $scope.reportParams.requestInfo = {
                        email: $rootScope.user.email,
                        folderName: '',
                        fileName: params.geneName + '_' + params.alteration + '_' + params.cancerType + '_' + params.subtype,
                        userName: $rootScope.user.name
                    };

                    var reportViewParams = {};
                    for (var key in $scope.reportParams.reportContent) {
                        if (key === 'items') {
                            _.each($scope.reportParams.reportContent.items[0], function(item, key1) {
                                reportViewParams[key1] = item;
                            });
                        } else {
                            reportViewParams[key] = $scope.reportParams.reportContent[key];
                        }
                    }
                    $scope.reportViewData = reportViewFactory.getData(reportViewParams);
                }
                $scope.rendering = false;
            }

            function generating() {
                $timeout(function() {
                    if ($scope.generaingReport) {
                        generating();
                    } else {
                        $rootScope.$broadcast('dialogs.wait.complete');
                        dialogs.notify('Request finished', 'The request has been added. Your will received an email when your report(s) is ready. Thank you.');
                    }
                }, 500);
            }

            function separateTumorTypes(tumorTypes) {
                var subtypes = {};
                var cancerTypes = {};
                _.each(tumorTypes, function(tumorType) {
                    if (tumorType) {
                        if (tumorType.name) {
                            subtypes[tumorType.name] = tumorType;
                        }
                        if (!tumorType.name && tumorType.mainType && tumorType.mainType.name) {
                            cancerTypes[tumorType.mainType.name] = tumorType;
                        }
                    }
                });
                return {
                    subtypes: _.values(subtypes),
                    cancerTypes: _.values(cancerTypes)
                };
            }

            function findCancerType(cancerType) {
                for (var i = 0; i < $scope.view.filteredCancerTypes.length; i++) {
                    if ($scope.view.filteredCancerTypes[i].mainType.name === cancerType) {
                        return $scope.view.filteredCancerTypes[i];
                    }
                }
            }

            function findSubtypesByCancerType(cancerType) {
                var subtypes = [];
                for (var i = 0; i < $scope.subtypes.length; i++) {
                    if ($scope.subtypes[i].mainType.name === cancerType) {
                        subtypes.push($scope.subtypes[i]);
                    }
                }
                return subtypes;
            }

            function containSubtype(subtype, subtypes) {
                for (var i = 0; i < subtypes.length; i++) {
                    if (subtypes[i].name === subtype.name) {
                        return true;
                    }
                }
                return false;
            }

            $scope.init = function() {
                $scope.view = {};
                $scope.reportParams = {
                    reportContent: {},
                    requestInfo: {}
                };

                $scope.rendering = false;

                $scope.loadingPage = true;

                $scope.generaingReport = false;

                // Control UI-Bootstrap angularjs, open one at a time
                $scope.oneAtATime = true;

                $scope.isCollapsed = {};

                $scope.displayParts = {
                    annotationSummary: {
                        displayName: 'Summary',
                        objectName: 'annotation_summary'
                    },
                    geneAnnotation: {
                        displayName: 'Gene Annotation',
                        objectName: 'gene_annotation'
                    },
                    variantEffect: {
                        displayName: 'Variant Effect',
                        objectName: 'variant_effect'
                    }
                };

                $scope.summaryTableTitles = [
                    'Treatment Implications',
                    'FDA Approved Drugs in Tumor Type',
                    'FDA Approved Drugs in Other Tumor Type',
                    'Clinical Trials',
                    'Additional Information'
                ];

                $scope.reportMatchedParams = [
                    'treatment',
                    'fdaApprovedInTumor',
                    'fdaApprovedInOtherTumor',
                    'clinicalTrials',
                    'additionalInfo'
                ];

                $scope.summaryTableTitlesContent = {
                    'Treatment Implications': [
                        'nccn_guidelines',
                        'standard_therapeutic_implications'],
                    'Clinical Trials': ['clinical_trial', 'investigational_therapeutic_implications'],
                    'Additional Information': ['prevalence', 'prognostic_implications'],
                    'FDA Approved Drugs in Tumor Type': [],
                    'FDA Approved Drugs in Other Tumor Type': []
                };

                /* eslint camelcase: ["error", {properties: "never"}]*/
                $scope.consequences = {
                    feature_truncation: 'Truncation',
                    frameshift_variant: 'Frame shift',
                    inframe_deletion: 'In frame deletion',
                    inframe_insertion: 'In frame insertion',
                    initiator_codon_variant: 'Initiator codon',
                    missense_variant: 'Missense',
                    stop_gained: 'Stop gained',
                    synonymous_variant: 'Synonymous'
                };

                $scope.specialAttr = ['investigational_therapeutic_implications', 'standard_therapeutic_implications'];

                if (OncoKB.global.genes && OncoKB.global.genes && OncoKB.global.tumorTypes) {
                    var separatedTumorTypes = separateTumorTypes(OncoKB.global.tumorTypes);

                    $scope.genes = getUnique(angular.copy(OncoKB.global.genes), 'hugoSymbol');
                    $scope.cancerTypes = separatedTumorTypes.cancerTypes;
                    $scope.subtypes = separatedTumorTypes.subtypes;
                    $scope.view.filteredCancerTypes = angular.copy($scope.cancerTypes);
                    $scope.view.filteredSubtypes = angular.copy($scope.subtypes);
                    $scope.loadingPage = false;
                } else {
                    DatabaseConnector.getGeneTumorType(function(data) {
                        OncoKB.global.genes = angular.copy(data.genes);
                        OncoKB.global.tumorTypes = angular.copy(data.tumorTypes);

                        var separatedTumorTypes = separateTumorTypes(OncoKB.global.tumorTypes);

                        $scope.genes = getUnique(data.genes, 'hugoSymbol');
                        $scope.cancerTypes = separatedTumorTypes.cancerTypes;
                        $scope.subtypes = separatedTumorTypes.subtypes;
                        $scope.view.filteredCancerTypes = angular.copy($scope.cancerTypes);
                        $scope.view.filteredSubtypes = angular.copy($scope.subtypes);
                        $scope.loadingPage = false;
                        checkUrl();
                    });
                }
            };

            $scope.isSearchable = function() {
                if ($scope.gene && $scope.alteration) {
                    return true;
                }
                return false;
            };

            $scope.fdaApproved = function(drug) {
                if (typeof drug.fda_approved === 'string' && drug.fda_approved.toLowerCase() === 'yes') {
                    return true;
                }
                return false;
            };

            $scope.hasSelectedCancerType = function() {
                if ($scope.view.hasOwnProperty('selectedCancerType') && $scope.view.selectedCancerType) {
                    return true;
                }
                return false;
            };

            $scope.setCollapsed = function(trial, attr) {
                $scope.isCollapsed[trial.trial_id][attr] = !$scope.isCollapsed[trial.trial_id][attr];
            };

            $scope.isArray = function(_var) {
                if (_var instanceof Array) {
                    return true;
                }
                return false;
            };

            $scope.isObject = function(_var) {
                if (typeof _var === 'object') {
                    return true;
                }
                return false;
            };

            $scope.displayProcess = function(str) {
                var specialUpperCasesWords = ['NCCN'];
                var specialLowerCasesWords = ['of', 'for'];

                str = str.replace(/_/g, ' ');
                str = str.replace(
                    /\w\S*/g,
                    function(txt) {
                        var _upperCase = txt.toUpperCase();
                        var _lowerCase = txt.toLowerCase();

                        if (specialUpperCasesWords.indexOf(_upperCase) !== -1) {
                            return _upperCase;
                        }

                        if (specialLowerCasesWords.indexOf(_lowerCase) !== -1) {
                            return _lowerCase;
                        }

                        return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
                    }
                );
                return str;
            };

            $scope.search = function() {
                var hasSelectedCancerType = $scope.hasSelectedCancerType();
                $scope.rendering = true;
                $scope.reportViewActive = hasSelectedCancerType;
                $scope.regularViewActive = !hasSelectedCancerType;
                $scope.alteration = stringUtils.trimMutationName($scope.alteration);
                var params = {alterationType: 'MUTATION'};
                var paramsContent = {
                    hugoSymbol: $scope.gene || '',
                    alteration: $scope.alteration || ''
                };

                for (var key in paramsContent) {
                    if (paramsContent[key] !== '') {
                        params[key] = paramsContent[key];
                    }
                }
                if (hasSelectedCancerType) {
                    params.cancerType = $scope.view.selectedCancerType.mainType.name;
                    if ($scope.view.selectedSubtype && $scope.view.selectedSubtype.name) {
                        params.tumorType = $scope.view.selectedSubtype.name;
                    } else {
                        params.tumorType = params.cancerType;
                    }
                } else {
                    params.cancerType = '';
                    params.subtype = '';
                    params.tumorType = '';
                }
                if ($scope.hasOwnProperty('selectedConsequence') && $scope.selectedConsequence) {
                    params.consequence = $scope.selectedConsequence;
                }

                changeUrl(params);

                DatabaseConnector.searchAnnotation(params, function(data) {
                    searchAnnotationCallback('success', data);
                }, function() {
                    searchAnnotationCallback('fail');
                });
            };

            $scope.generateReport = function() {
                var dlg = dialogs.create('views/emailDialog.html', 'emailDialogCtrl', 'Test string');
                dlg.result.then(function(data) {
                    if (typeof data !== 'undefined' && data && data !== '') {
                        $scope.generaingReport = true;
                        dlg = dialogs.wait('Sending request', 'Please wait...');
                        generating();
                        var params = $scope.reportParams;
                        params.requestInfo.email = data;
                        DatabaseConnector.googleDoc(params, function() {
                            $scope.generaingReport = false;
                        }, function() {
                            $scope.generaingReport = false;
                        });
                    }
                }, function() {
                    console.log('Did not do anything.');
                });
            };

            $scope.useExample = function() {
                $scope.gene = $scope.genes[$filter('getIndexByObjectNameInArray')($scope.genes, 'BRAF')];
                $scope.alteration = 'V600E';
                $scope.view.selectedCancerType = $scope.view.filteredCancerTypes[$filter('getCancerTypeByMainType')($scope.view.filteredCancerTypes, 'Melanoma')];
                $scope.view.selectedSubtype = '';
                $scope.selectedConsequence = '';
                $scope.search();
            };

            $scope.$watch('view.selectedSubtype', function(n) {
                if (n) {
                    $scope.view.selectedCancerType = findCancerType(n.mainType.name);
                } else {
                    // console.log($scope.view.selectedSubtype);
                }
            });

            $scope.$watch('view.selectedCancerType', function(n) {
                if (n) {
                    if ($scope.view.selectedSubtype) {
                        var mappedSubtypes = findSubtypesByCancerType(n.mainType.name);
                        if (!containSubtype($scope.view.selectedSubtype, mappedSubtypes)) {
                            $scope.view.filteredSubtypes = mappedSubtypes;
                            $scope.view.selectedSubtype = '';
                        }
                    } else {
                        $scope.view.filteredSubtypes = findSubtypesByCancerType(n.mainType.name);
                    }
                } else {
                    $scope.view.filteredSubtypes = angular.copy($scope.subtypes);
                    $scope.view.selectedSubtype = '';
                }
            });
        }]);
<|endoftext|>


<commit_before>import React        from 'react';
import TopListChart from './TopListChart';
import Parties      from '../../shared/Parties';
import ImageUtils   from '../utils/ImageUtils';
import Colors       from '../utils/Colors';

const MIN_SPEECH_COUNT = require('../../shared/minSpeechCount');

class ResultStats extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            parties: [],
            people: {},
            query: ''
        };

        this.bigNumberStyle = {
            fontSize: '4rem',
            padding: '10px',
            verticalAlign: '-0.55rem'
        };
    }

    componentWillReceiveProps(props) {
        let parties = [];
        let people  = {};
        let query   = '';

        let focusedIndex = this.props.focusedIndex || 0;
        let result = props.results[focusedIndex];

        if (result) {
            query   = result.query;
            people  = result.result.people;
            parties = result.result.parties;
        }

        this.setState({
            query: query,
            parties: parties,
            people: people,
            topParties: this.topParties(parties),
            labelClassName: Colors.colorAt(focusedIndex)
        });
    }

    render() {
        if (this.state.query.length) {
            return (

                    {this.renderPartyStats()}

                    {this.renderPeopleStats()}

            );
        } else {
            return
;
        }
    }

    renderPartyStats() {
        this.state.parties.sort((a, b) => a[this.props.unit] - b[this.props.unit]);
        let topParty = this.state.topParties[this.props.unit];

        if (topParty) {
            let topPartyName = Parties.names[topParty.key];
            let num          = this.props.unit === 'pct' ? topParty.pct.toFixed(2) + '%' : topParty.count;
            let unitText     = this.props.unit === 'pct' ? 'av sine innlegg' : 'innlegg';

            return (

                                <img
                                    src={ImageUtils.partyLogoFor(topParty.key)}
                                    alt={`${topPartyName}s logo`}
                                    height="200"
                                />

                                    {topPartyName}

                                    har nevnt {this.state.query} i

                                    {num}

                                {unitText}

                        <TopListChart
                            className={this.state.labelClassName}
                            subtitle="Partier"
                            orientation={this.props.orientation}
                            unit={this.props.unit}
                            counts={this.state.parties}
                        />

            );
        }
    }

    renderPeopleStats() {
        let people = (this.state.people[this.props.unit] || []);
        let isPct = this.props.unit === 'pct';

        people = people.slice(0, 8);

        if (people.length) {
            let topPerson      = people[0];
            let num            = isPct ? `${topPerson.pct.toFixed(2)}%` : topPerson.count;
            let unitText       = isPct ? 'av sine innlegg' : 'innlegg';
            // let partyClassName = topPerson.meta.party ? `hdo-party-${topPerson.meta.party.toLowerCase()}` : '';
            let partyText      = topPerson.meta.party ? `(${topPerson.meta.party})` : '';

            return (

                            <img
                                src={ImageUtils.personImageFor(topPerson.meta.external_id)}
                                alt={`Bilde av ${topPerson.key}`}
                                height="200"
                            />

                                {topPerson.key} {partyText}

                                    har nevnt {this.state.query} i

                                    {num}

                                {unitText}

                        {isPct ? this.renderDiclaimer() : null}

                        <TopListChart
                            className={this.state.labelClassName}
                            subtitle={`Personer`}
                            star={isPct}
                            unit={this.props.unit}
                            orientation={this.props.orientation}
                            counts={people}
                            sort={true}
                        />

            );
        } else {
            return
;
        }
    }

    renderDiclaimer() {
        return (
            * - ekskludert representanter med mindre enn {MIN_SPEECH_COUNT} innlegg totalt.
            Dette for å unngå at vararepresentanter dominerer resultatene.
        );

    }

    topParties(parties) {
        return {
            pct: parties.slice(0).sort((a, b) => b.pct - a.pct)[0],
            count: parties.slice(0).sort((a, b) => b.count - a.count)[0]
        };
    }
}

ResultStats.propTypes = {
};

module.exports = ResultStats;<commit_msg>Improve disclaimer<commit_after>import React        from 'react';
import TopListChart from './TopListChart';
import Parties      from '../../shared/Parties';
import ImageUtils   from '../utils/ImageUtils';
import Colors       from '../utils/Colors';

const MIN_SPEECH_COUNT = require('../../shared/minSpeechCount');

class ResultStats extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            parties: [],
            people: {},
            query: ''
        };

        this.bigNumberStyle = {
            fontSize: '4rem',
            padding: '10px',
            verticalAlign: '-0.55rem'
        };
    }

    componentWillReceiveProps(props) {
        let parties = [];
        let people  = {};
        let query   = '';

        let focusedIndex = this.props.focusedIndex || 0;
        let result = props.results[focusedIndex];

        if (result) {
            query   = result.query;
            people  = result.result.people;
            parties = result.result.parties;
        }

        this.setState({
            query: query,
            parties: parties,
            people: people,
            topParties: this.topParties(parties),
            labelClassName: Colors.colorAt(focusedIndex)
        });
    }

    render() {
        if (this.state.query.length) {
            return (

                    {this.renderPartyStats()}

                    {this.renderPeopleStats()}

            );
        } else {
            return
;
        }
    }

    renderPartyStats() {
        this.state.parties.sort((a, b) => a[this.props.unit] - b[this.props.unit]);
        let topParty = this.state.topParties[this.props.unit];

        if (topParty) {
            let topPartyName = Parties.names[topParty.key];
            let num          = this.props.unit === 'pct' ? topParty.pct.toFixed(2) + '%' : topParty.count;
            let unitText     = this.props.unit === 'pct' ? 'av sine innlegg' : 'innlegg';

            return (

                                <img
                                    src={ImageUtils.partyLogoFor(topParty.key)}
                                    alt={`${topPartyName}s logo`}
                                    height="200"
                                />

                                    {topPartyName}

                                    har nevnt {this.state.query} i

                                    {num}

                                {unitText}

                        <TopListChart
                            className={this.state.labelClassName}
                            subtitle="Partier"
                            orientation={this.props.orientation}
                            unit={this.props.unit}
                            counts={this.state.parties}
                        />

            );
        }
    }

    renderPeopleStats() {
        let people = (this.state.people[this.props.unit] || []);
        let isPct = this.props.unit === 'pct';

        people = people.slice(0, 8);

        if (people.length) {
            let topPerson      = people[0];
            let num            = isPct ? `${topPerson.pct.toFixed(2)}%` : topPerson.count;
            let unitText       = isPct ? 'av sine innlegg' : 'innlegg';
            // let partyClassName = topPerson.meta.party ? `hdo-party-${topPerson.meta.party.toLowerCase()}` : '';
            let partyText      = topPerson.meta.party ? `(${topPerson.meta.party})` : '';

            return (

                            <img
                                src={ImageUtils.personImageFor(topPerson.meta.external_id)}
                                alt={`Bilde av ${topPerson.key}`}
                                height="200"
                            />

                                {topPerson.key} {partyText}

                                    har nevnt {this.state.query} i

                                    {num}

                                {unitText}

                        {isPct ? this.renderDiclaimer() : null}

                        <TopListChart
                            className={this.state.labelClassName}
                            subtitle={`Personer`}
                            star={isPct}
                            unit={this.props.unit}
                            orientation={this.props.orientation}
                            counts={people}
                            sort={true}
                        />

            );
        } else {
            return
;
        }
    }

    renderDiclaimer() {
        return (
            * - ekskludert representanter med mindre enn {MIN_SPEECH_COUNT} innlegg totalt.
            Dette for å unngå at vararepresentanter dominerer resultatene.
        );

    }

    topParties(parties) {
        return {
            pct: parties.slice(0).sort((a, b) => b.pct - a.pct)[0],
            count: parties.slice(0).sort((a, b) => b.count - a.count)[0]
        };
    }
}

ResultStats.propTypes = {
};

module.exports = ResultStats;<|endoftext|>


<commit_before>export { NeuralWorker_Proxies as Proxies };

import * as Pool from "../util/Pool.js";
import * as Recyclable from "../util/Recyclable.js";
import * as GSheets from "../util/GSheets.js";
//import * as ValueMax from "../util/ValueMax.js";
import * as NeuralNet from "../Conv/NeuralNet.js";
import * as DEvolution from "../DEvolution.js";
import { Proxy as WorkerProxy } from "./NeuralWorker_Proxy.js";

/**
 * The container of WorkerProxy. It orchestrates these WorkerProxy. Especially, it
 * transfers (scaled) source image data to and from web worker. This could maximize
 * parallel computing under the restriction transferring source image data to every
 * web worker serially.
 *
 * Every worker handles one neural network. When processTensor() is called, the input
 * (usually a large memory block) will be transffered to the 1st worker to start
 * computing, and then transffered to the 2nd worker to start computing, ... etc.
 *
 * When passing large data by Worker.postMessage(), it is preferred by transferring
 * (not by copying). If the large data wants to be transferred (not copied) to many
 * workers, the only possible way is to transferring them serially.
 *
 * However, serially transferring hurts the performance. Workers are better to compute
 * parallelly. So every worker should transfer the (possible scaled) source image data
 * back to this WorkerProxies, and keep computing neural network at the same. And
 * then, this WorkerProxies will transfer the source image data to the next worker
 * as soon as possible.
 *
 * Finally, this WorkerProxies collects all web workers' processTensor() results in
 * a promise. The promise will resolve with an array of typed-array. Every typed-array
 * is the output of one neural network.
 *
 *
 * @member {number} totalWorkerCount
 *   There are how many web worker(s) created.
 *
 * @member {DEvolution.VersusRangeArray} evolutionVersusRangeArray
 *   The range list of all evolution versus (i.e. parent versus offspring).
 */
class NeuralWorker_Proxies extends Recyclable.Root {

  /**
   * Used as default NeuralWorker.Proxies provider for conforming to Recyclable interface.
   */
  static Pool = new Pool.Root( "NeuralWorker.Proxies.Pool",
    NeuralWorker_Proxies, NeuralWorker_Proxies.setAsConstructor );

  /** */
  constructor() {
    super();
    NeuralWorker_Proxies.setAsConstructor_self.call( this );
  }

  /** @override */
  static setAsConstructor() {
    super.setAsConstructor();
    NeuralWorker_Proxies.setAsConstructor_self.call( this );
    return this;
  }

  /** @override */
  static setAsConstructor_self() {
  }

  /** @override */
  disposeResources() {

    this.evolutionVersusRangeArray = null; // Normal array. Just nullify it.

//!!! ...unfinished... (2022/08/26)
    this.disposeWorkers();

    this.hardwareConcurrency = undefined;

    this.bTwoWorkers = undefined;
    this.weightsAPIKey = undefined;
    this.weightsSpreadsheetId = undefined;

    super.disposeResources();
  }

  /**
   *
   */
   disposeWorkers() {
    if ( this.workerProxyArray ) {
      this.workerProxyArray.disposeResources_and_recycleToPool();
      this.workerProxyArray = null;
    }
  }

  /**
   * Initialize this worker proxy controller. It will create two neural networks in
   * one or two web worker(s).
   *
   * @param {string} weightsSpreadsheetId
   *   The Google Sheets spreadsheetId of neural network weights. Every worker will
   * load weights from the spreadsheet to initialize one neural network.
   *
   * @param {string} weightsAPIKey
   *   The API key for accessing the Google Sheets spreadsheet of neural network weights.
   *   - If null, Google Visualization Table Query API will be used.
   *   - If not null, Google Sheets API v4 will be used.
   *
   * @param {boolean} bTwoWorkers
   *   If true, two web workers will be created. If false, one worker will be created.
   */
  async init_async( weightsSpreadsheetId, weightsAPIKey, bTwoWorkers ) {

    this.weightsSpreadsheetId = weightsSpreadsheetId;
    this.weightsAPIKey = weightsAPIKey;
    this.bTwoWorkers = bTwoWorkers;

    this.hardwareConcurrency = navigator.hardwareConcurrency; // logical CPU count.

    // Two web workers are sufficient.
    //
    // Although we might want create as many web worker as logical CPU count, it might
    // not need because our neural networks are learning by differential evolution.
    // Differential evolution evaluates just two entities every time.
    //

//!!! ...unfinished... (2022/08/26) really?

    // Note: How could the two neural networks determine all the actions of so many
    //       game objects? The method is to let the output of one neural network
    //       contains all actions of all game objects. But only half of the output
    //       actions will be used because one neural network only control one alignment
    //       of the game world.
    //
    let totalWorkerCount;
    if ( bTwoWorkers )
      totalWorkerCount = 2;
    else
      totalWorkerCount = 1;

    // 1. Create web workers.
    let initOkArray;
    {
      this.disposeWorkers();
      this.workerProxyArray = Pool.OwnerArray.Pool.get_or_create_by();
      this.workerProxyArray.length = totalWorkerCount;

      let initPromiseArray = new Array( totalWorkerCount );
      for ( let i = 0; i < totalWorkerCount; ++i ) {
        this.workerProxyArray[ i ] = WorkerProxy.Pool.get_or_create_by();
        initPromiseArray[ i ] = this.workerProxyArray[ i ].initWorker_async( i );
      }

      initOkArray = await Promise.all( initPromiseArray );
    }

    // 2. Summary.
    let initOk = initOkArray.reduce(
      ( previousValue, currentValue ) => ( previousValue && currentValue ),
      true
    );

    return initOk;
  }

  /**
   * Create neural networks in the web worker body.
   *
   * @param {NeuralNet.ParamsBase[]} neuralNetParamsBaseArray
   *   An array of configurations for the neural network to be created. These
   * configurations (exclude the array) will be owned (i.e. kept and destroyed)
   * by this NeuralWorker.Proxy.
   *
   * @param {ArrayBuffer[]} weightArrayBufferArray
   *   An array of every neural network's weights. Every element  will be interpreted
   * as Float32Array.
   *
   * @return {Promise}
   *   Return a promise:
   *   - Resolved to true, if success.
   *   - Resolved to false, if failed.
   */
  async NeuralNetArray_create_async( neuralNetParamsBaseArray, weightArrayBufferArray ) {

    if ( neuralNetParamsBaseArray.length != 2 )
      throw Error( `NeuralWorker.Proxies.NeuralNetArray_create_async(): `
        + `neuralNetParamsBaseArray.length ( ${neuralNetParamsBaseArray.length} ) `
        + `should be 2.`
      );

    if ( weightArrayBufferArray.length != 2 )
      throw Error( `NeuralWorker.Proxies.NeuralNetArray_create_async(): `
        + `weightArrayBufferArray.length ( ${weightArrayBufferArray.length} ) `
        + `should be 2.`
      );

    let createOk;

    // 1. Every worker create one neural network.
    if ( bTwoWorkers ) {

      let createPromiseArray = new Array( this.workerProxyArray.length );
      for ( let i = 0; i < this.workerProxyArray.length; ++i ) {
        createPromiseArray[ i ] = this.workerProxyArray[ i ].NeuralNetArray_create_async(
          [ neuralNetParamsBaseArray[ i ] ], [ weightArrayBufferArray[ i ] ] );
      }

      let createOkArray = await Promise.all( createPromiseArray );

      createOk = createOkArray.reduce(
        ( previousValue, currentValue ) => ( previousValue && currentValue ),
        true
      );

    // 2. The only worker creates all neural networks.
    } else {
      createOk = await this.workerProxyArray[ 0 ].NeuralNetArray_create_async(
        neuralNetParamsBaseArray, weightArrayBufferArray );
    }

    return createOk;
  }

  /**
   * @param {integer[]} markValueArray
   *   An array of values representing every neural network playing which alignment.
   *
   * @return {Promise}
   *   Return a promise:
   *   - Resolved to true, if success.
   *   - Resolved to false, if failed.
   */
  alignmentMarkArray_setValue_async( markValueArray ) {

    if ( markValueArray.length != 2 )
      throw Error( `NeuralWorker.Proxies.alignmentMarkArray_setValue_async(): `
        + `markValueArray.length ( ${markValueArray.length} ) `
        + `should be 2.`
      );

    let resultOk;

    // 1. Every worker set one alignment mark value.
    if ( bTwoWorkers ) {

      let resultPromiseArray = new Array( this.workerProxyArray.length );
      for ( let i = 0; i < this.workerProxyArray.length; ++i ) {
        resultPromiseArray[ i ]
          = this.workerProxyArray[ i ].alignmentMarkArray_setValue_async(
            [ markValueArray[ i ] ] );
      }

      let resultOkArray = await Promise.all( resultPromiseArray );

      resultOk = resultOkArray.reduce(
        ( previousValue, currentValue ) => ( previousValue && currentValue ),
        true
      );

    // 2. The only worker sets all alignment mark values.
    } else {
      resultOk = await this.workerProxyArray[ 0 ].alignmentMarkArray_setValue_async(
        markValueArray );
    }

    return resultOk;
  }

//!!! ...unfinished... (2022/09/20)

  /** Load all evolution versus weights ranges. */
  async evolutionVersusRangeArray_load_async() {

    if ( !this.evolutionVersusRangeArray ) {
      this.evolutionVersusRangeArray = DEvolution.VersusRangeArray.Pool.get_or_create_by(
        this.weightsSpreadsheetId, this.weightsAPIKey );
    }

    this.evolutionVersusRangeArray.loadAsync();

//!!! ...unfinished... (2022/08/27)

  }

//!!! ...unfinished... (2022/09/20) Old Codes.

  /**
   * Download image data from source canvas. Pass source image data to every web
   * worker serially by transferring.
   *
   * @param {HTMLCanvasElement} sourceCanvas
   *   The source canvas to be processed. Its shape [ height, width, channelCount ]
   * should be the same as the size which is used when training the neural network.
   *
   * @return {Promise}
   *   Return a promise which will be resolved when all worker processing promises of
   * the same processingId are resolved. The promise resolves with an array of
   * typed-array. Every typed-array comes from the output tensor of one worker's
   * neural network.
   */
  async processCanvas_Async_ByTransfer( sourceCanvas ) {
    let ctx = sourceCanvas.getContext( '2d' );
    let sourceImageData = ctx.getImageData(
      0, 0, sourceCanvas.width, sourceCanvas.height );
    return this.processImageData_Async_ByTransfer( sourceImageData );
  }

  /**
   * Pass source image data to every web worker serially by transferring.
   *
   * @param {ImageData} sourceImageData
   *   The source image data to be processed. Its shape [ height, width, channelCount ]
   * should be the same as the size which is used when training the neural network.
   *
   * @return {Promise}
   *   Return a promise which will be resolved when all worker processing promises of
   * the same processingId are resolved. The promise resolves with an array of
   * typed-array. Every typed-array comes from the output tensor of one worker's
   * neural network.
   */
  async processImageData_Async_ByTransfer( sourceImageData ) {

    // Generate a new processing id so that the result returned from worker could
    // be distinguished.
    ++this.processingId;
    let processingId = this.processingId;

    let workerProxy, processRelayPromises, sourceTypedArray;

    // For first (i == 0) web worker, passing source ImageData.
    workerProxy = this.workerProxyArray[ 0 ];
    this.processTensorPromiseArray[ 0 ]
      = workerProxy.imageData_transferBack_processTensor_async(
          processingId, sourceImageData );
    // Now, sourceImageData.data.buffer has become invalid because it is transferred
    // (not copied) to the above web worker.

    // For all other (i >= 1) web workers, passing scaled source typed-array.
    for ( let i = 1; i < this.workerProxyArray.length; ++i ) {

      // Wait the previous web worker transferring back the scaled source typed-array.
      // (after it has been copied into a new tensor inside the web worker).
      processRelayPromises = workerProxy.processRelayPromisesMap.get( processingId );
      sourceTypedArray = await processRelayPromises.relay.promise;

      workerProxy = this.workerProxyArray[ i ];
      this.processTensorPromiseArray[ i ]
        = workerProxy.typedArray_transferBack_processTensor_async(
            processingId, sourceTypedArray );
      // Now, sourceTypedArray.buffer has become invalid because it is transferred
      // (not copied) to the above web worker.
    }

    // Note: Array push() is faster than unshift(), and unshift() is faster than concat().

    // Since all web worker has received the source image data (although serially),
    // wait for all them done.
    let promiseAllSettled = Promise.allSettled( this.processTensorPromiseArray );
    return promiseAllSettled;
  }

  /**
   * Download image data from source canvas, convert to tensor, scale, pass source
   * image data to every web worker parallelly by copying.
   *
   * The tensorflow.js library should have been loaded so that the source image
   * scaling could be done here (not in web worker).
   *
   * @param {HTMLCanvasElement} sourceCanvas
   *   The source canvas to be processed. Its shape [ height, width, channelCount ]
   * should be the same as the size which is used when training the neural network.
   *
   * @return {Promise}
   *   Return a promise which will be resolved when all worker processing promises of
   * the same processingId are resolved. The promise resolves with an array of
   * typed-array. Every typed-array comes from the output tensor of one worker's
   * neural network.
   */
  async processCanvas_Async_ByCopy( sourceCanvas ) {

    // Create (scaled) source tensor so that every web worker needs not scale again
    // and easier to re-create source tensor.
    //
    // The drawback is that the tensorflow.js library should have been loaded here.
    // And the image scaling is done here (not in web worker).
    //
    let scaledSourceTensor
      = this.neuralNetConfig.create_ScaledSourceTensor_from_PixelData( sourceCanvas );

    let sourceTypedArray = await scaledSourceTensor.data();

    // Discard the source tensor because type-array (not tensor) will be passed to
    // web worker
    scaledSourceTensor.dispose();

    // Generate a new processing id so that the result returned from worker could be
    // distinguished.
    ++this.processingId;
    let processingId = this.processingId;

    let workerProxy, processRelayPromises;

    for ( let i = 0; i < this.workerProxyArray.length; ++i ) {
      workerProxy = this.workerProxyArray[ i ];
      this.processTensorPromiseArray[ i ]
        = workerProxy.typedArray_processTensor_async( processingId, sourceTypedArray );
      // The sourceTypedArray is still valid here because it is copied (not
      // transferred) to the above web worker.
    }

    // Since all web worker has received the source typed-array (parallelly), wait
    // for all them done.
    let promiseAllSettled = Promise.allSettled( this.processTensorPromiseArray );
    return promiseAllSettled;
  }

  get totalWorkerCount() {
    return this.workerProxyArray.length;
  }

}
<commit_msg>alignmentMarkArray_setValue_async<commit_after>export { NeuralWorker_Proxies as Proxies };

import * as Pool from "../util/Pool.js";
import * as Recyclable from "../util/Recyclable.js";
import * as GSheets from "../util/GSheets.js";
//import * as ValueMax from "../util/ValueMax.js";
import * as NeuralNet from "../Conv/NeuralNet.js";
import * as DEvolution from "../DEvolution.js";
import { Proxy as WorkerProxy } from "./NeuralWorker_Proxy.js";

/**
 * The container of WorkerProxy. It orchestrates these WorkerProxy. Especially, it
 * transfers (scaled) source image data to and from web worker. This could maximize
 * parallel computing under the restriction transferring source image data to every
 * web worker serially.
 *
 * Every worker handles one neural network. When processTensor() is called, the input
 * (usually a large memory block) will be transffered to the 1st worker to start
 * computing, and then transffered to the 2nd worker to start computing, ... etc.
 *
 * When passing large data by Worker.postMessage(), it is preferred by transferring
 * (not by copying). If the large data wants to be transferred (not copied) to many
 * workers, the only possible way is to transferring them serially.
 *
 * However, serially transferring hurts the performance. Workers are better to compute
 * parallelly. So every worker should transfer the (possible scaled) source image data
 * back to this WorkerProxies, and keep computing neural network at the same. And
 * then, this WorkerProxies will transfer the source image data to the next worker
 * as soon as possible.
 *
 * Finally, this WorkerProxies collects all web workers' processTensor() results in
 * a promise. The promise will resolve with an array of typed-array. Every typed-array
 * is the output of one neural network.
 *
 *
 * @member {number} totalWorkerCount
 *   There are how many web worker(s) created.
 *
 * @member {DEvolution.VersusRangeArray} evolutionVersusRangeArray
 *   The range list of all evolution versus (i.e. parent versus offspring).
 */
class NeuralWorker_Proxies extends Recyclable.Root {

  /**
   * Used as default NeuralWorker.Proxies provider for conforming to Recyclable interface.
   */
  static Pool = new Pool.Root( "NeuralWorker.Proxies.Pool",
    NeuralWorker_Proxies, NeuralWorker_Proxies.setAsConstructor );

  /** */
  constructor() {
    super();
    NeuralWorker_Proxies.setAsConstructor_self.call( this );
  }

  /** @override */
  static setAsConstructor() {
    super.setAsConstructor();
    NeuralWorker_Proxies.setAsConstructor_self.call( this );
    return this;
  }

  /** @override */
  static setAsConstructor_self() {
  }

  /** @override */
  disposeResources() {

    this.evolutionVersusRangeArray = null; // Normal array. Just nullify it.

//!!! ...unfinished... (2022/08/26)
    this.disposeWorkers();

    this.hardwareConcurrency = undefined;

    this.bTwoWorkers = undefined;
    this.weightsAPIKey = undefined;
    this.weightsSpreadsheetId = undefined;

    super.disposeResources();
  }

  /**
   *
   */
   disposeWorkers() {
    if ( this.workerProxyArray ) {
      this.workerProxyArray.disposeResources_and_recycleToPool();
      this.workerProxyArray = null;
    }
  }

  /**
   * Initialize this worker proxy controller. It will create two neural networks in
   * one or two web worker(s).
   *
   * @param {string} weightsSpreadsheetId
   *   The Google Sheets spreadsheetId of neural network weights. Every worker will
   * load weights from the spreadsheet to initialize one neural network.
   *
   * @param {string} weightsAPIKey
   *   The API key for accessing the Google Sheets spreadsheet of neural network weights.
   *   - If null, Google Visualization Table Query API will be used.
   *   - If not null, Google Sheets API v4 will be used.
   *
   * @param {boolean} bTwoWorkers
   *   If true, two web workers will be created. If false, one worker will be created.
   */
  async init_async( weightsSpreadsheetId, weightsAPIKey, bTwoWorkers ) {

    this.weightsSpreadsheetId = weightsSpreadsheetId;
    this.weightsAPIKey = weightsAPIKey;
    this.bTwoWorkers = bTwoWorkers;

    this.hardwareConcurrency = navigator.hardwareConcurrency; // logical CPU count.

    // Two web workers are sufficient.
    //
    // Although we might want create as many web worker as logical CPU count, it might
    // not need because our neural networks are learning by differential evolution.
    // Differential evolution evaluates just two entities every time.
    //

//!!! ...unfinished... (2022/08/26) really?

    // Note: How could the two neural networks determine all the actions of so many
    //       game objects? The method is to let the output of one neural network
    //       contains all actions of all game objects. But only half of the output
    //       actions will be used because one neural network only control one alignment
    //       of the game world.
    //
    let totalWorkerCount;
    if ( bTwoWorkers )
      totalWorkerCount = 2;
    else
      totalWorkerCount = 1;

    // 1. Create web workers.
    let initOkArray;
    {
      this.disposeWorkers();
      this.workerProxyArray = Pool.OwnerArray.Pool.get_or_create_by();
      this.workerProxyArray.length = totalWorkerCount;

      let initPromiseArray = new Array( totalWorkerCount );
      for ( let i = 0; i < totalWorkerCount; ++i ) {
        this.workerProxyArray[ i ] = WorkerProxy.Pool.get_or_create_by();
        initPromiseArray[ i ] = this.workerProxyArray[ i ].initWorker_async( i );
      }

      initOkArray = await Promise.all( initPromiseArray );
    }

    // 2. Summary.
    let initOk = initOkArray.reduce(
      ( previousValue, currentValue ) => ( previousValue && currentValue ),
      true
    );

    return initOk;
  }

  /**
   * Create neural networks in the web worker body.
   *
   * @param {NeuralNet.ParamsBase[]} neuralNetParamsBaseArray
   *   An array of configurations for the neural network to be created. These
   * configurations (exclude the array) will be owned (i.e. kept and destroyed)
   * by this NeuralWorker.Proxy.
   *
   * @param {ArrayBuffer[]} weightArrayBufferArray
   *   An array of every neural network's weights. Every element  will be interpreted
   * as Float32Array.
   *
   * @return {Promise}
   *   Return a promise:
   *   - Resolved to true, if success.
   *   - Resolved to false, if failed.
   */
  async NeuralNetArray_create_async( neuralNetParamsBaseArray, weightArrayBufferArray ) {

    if ( neuralNetParamsBaseArray.length != 2 )
      throw Error( `NeuralWorker.Proxies.NeuralNetArray_create_async(): `
        + `neuralNetParamsBaseArray.length ( ${neuralNetParamsBaseArray.length} ) `
        + `should be 2.`
      );

    if ( weightArrayBufferArray.length != 2 )
      throw Error( `NeuralWorker.Proxies.NeuralNetArray_create_async(): `
        + `weightArrayBufferArray.length ( ${weightArrayBufferArray.length} ) `
        + `should be 2.`
      );

    let createOk;

    // 1. Every worker create one neural network.
    if ( bTwoWorkers ) {

      let createPromiseArray = new Array( this.workerProxyArray.length );
      for ( let i = 0; i < this.workerProxyArray.length; ++i ) {
        createPromiseArray[ i ] = this.workerProxyArray[ i ].NeuralNetArray_create_async(
          [ neuralNetParamsBaseArray[ i ] ], [ weightArrayBufferArray[ i ] ] );
      }

      let createOkArray = await Promise.all( createPromiseArray );

      createOk = createOkArray.reduce(
        ( previousValue, currentValue ) => ( previousValue && currentValue ),
        true
      );

    // 2. The only worker creates all neural networks.
    } else {
      createOk = await this.workerProxyArray[ 0 ].NeuralNetArray_create_async(
        neuralNetParamsBaseArray, weightArrayBufferArray );
    }

    return createOk;
  }

  /**
   * @param {integer[]} markValueArray
   *   An array of values representing every neural network playing which alignment.
   *
   * @return {Promise}
   *   Return a promise:
   *   - Resolved to true, if success.
   *   - Resolved to false, if failed.
   */
  async alignmentMarkArray_setValue_async( markValueArray ) {

    if ( markValueArray.length != 2 )
      throw Error( `NeuralWorker.Proxies.alignmentMarkArray_setValue_async(): `
        + `markValueArray.length ( ${markValueArray.length} ) `
        + `should be 2.`
      );

    let resultOk;

    // 1. Every worker set one alignment mark value.
    if ( bTwoWorkers ) {

      let resultPromiseArray = new Array( this.workerProxyArray.length );
      for ( let i = 0; i < this.workerProxyArray.length; ++i ) {
        resultPromiseArray[ i ]
          = this.workerProxyArray[ i ].alignmentMarkArray_setValue_async(
            [ markValueArray[ i ] ] );
      }

      let resultOkArray = await Promise.all( resultPromiseArray );

      resultOk = resultOkArray.reduce(
        ( previousValue, currentValue ) => ( previousValue && currentValue ),
        true
      );

    // 2. The only worker sets all alignment mark values.
    } else {
      resultOk = await this.workerProxyArray[ 0 ].alignmentMarkArray_setValue_async(
        markValueArray );
    }

    return resultOk;
  }

//!!! ...unfinished... (2022/09/20)

  /** Load all evolution versus weights ranges. */
  async evolutionVersusRangeArray_load_async() {

    if ( !this.evolutionVersusRangeArray ) {
      this.evolutionVersusRangeArray = DEvolution.VersusRangeArray.Pool.get_or_create_by(
        this.weightsSpreadsheetId, this.weightsAPIKey );
    }

    this.evolutionVersusRangeArray.loadAsync();

//!!! ...unfinished... (2022/08/27)

  }

//!!! ...unfinished... (2022/09/20) Old Codes.

  /**
   * Download image data from source canvas. Pass source image data to every web
   * worker serially by transferring.
   *
   * @param {HTMLCanvasElement} sourceCanvas
   *   The source canvas to be processed. Its shape [ height, width, channelCount ]
   * should be the same as the size which is used when training the neural network.
   *
   * @return {Promise}
   *   Return a promise which will be resolved when all worker processing promises of
   * the same processingId are resolved. The promise resolves with an array of
   * typed-array. Every typed-array comes from the output tensor of one worker's
   * neural network.
   */
  async processCanvas_Async_ByTransfer( sourceCanvas ) {
    let ctx = sourceCanvas.getContext( '2d' );
    let sourceImageData = ctx.getImageData(
      0, 0, sourceCanvas.width, sourceCanvas.height );
    return this.processImageData_Async_ByTransfer( sourceImageData );
  }

  /**
   * Pass source image data to every web worker serially by transferring.
   *
   * @param {ImageData} sourceImageData
   *   The source image data to be processed. Its shape [ height, width, channelCount ]
   * should be the same as the size which is used when training the neural network.
   *
   * @return {Promise}
   *   Return a promise which will be resolved when all worker processing promises of
   * the same processingId are resolved. The promise resolves with an array of
   * typed-array. Every typed-array comes from the output tensor of one worker's
   * neural network.
   */
  async processImageData_Async_ByTransfer( sourceImageData ) {

    // Generate a new processing id so that the result returned from worker could
    // be distinguished.
    ++this.processingId;
    let processingId = this.processingId;

    let workerProxy, processRelayPromises, sourceTypedArray;

    // For first (i == 0) web worker, passing source ImageData.
    workerProxy = this.workerProxyArray[ 0 ];
    this.processTensorPromiseArray[ 0 ]
      = workerProxy.imageData_transferBack_processTensor_async(
          processingId, sourceImageData );
    // Now, sourceImageData.data.buffer has become invalid because it is transferred
    // (not copied) to the above web worker.

    // For all other (i >= 1) web workers, passing scaled source typed-array.
    for ( let i = 1; i < this.workerProxyArray.length; ++i ) {

      // Wait the previous web worker transferring back the scaled source typed-array.
      // (after it has been copied into a new tensor inside the web worker).
      processRelayPromises = workerProxy.processRelayPromisesMap.get( processingId );
      sourceTypedArray = await processRelayPromises.relay.promise;

      workerProxy = this.workerProxyArray[ i ];
      this.processTensorPromiseArray[ i ]
        = workerProxy.typedArray_transferBack_processTensor_async(
            processingId, sourceTypedArray );
      // Now, sourceTypedArray.buffer has become invalid because it is transferred
      // (not copied) to the above web worker.
    }

    // Note: Array push() is faster than unshift(), and unshift() is faster than concat().

    // Since all web worker has received the source image data (although serially),
    // wait for all them done.
    let promiseAllSettled = Promise.allSettled( this.processTensorPromiseArray );
    return promiseAllSettled;
  }

  /**
   * Download image data from source canvas, convert to tensor, scale, pass source
   * image data to every web worker parallelly by copying.
   *
   * The tensorflow.js library should have been loaded so that the source image
   * scaling could be done here (not in web worker).
   *
   * @param {HTMLCanvasElement} sourceCanvas
   *   The source canvas to be processed. Its shape [ height, width, channelCount ]
   * should be the same as the size which is used when training the neural network.
   *
   * @return {Promise}
   *   Return a promise which will be resolved when all worker processing promises of
   * the same processingId are resolved. The promise resolves with an array of
   * typed-array. Every typed-array comes from the output tensor of one worker's
   * neural network.
   */
  async processCanvas_Async_ByCopy( sourceCanvas ) {

    // Create (scaled) source tensor so that every web worker needs not scale again
    // and easier to re-create source tensor.
    //
    // The drawback is that the tensorflow.js library should have been loaded here.
    // And the image scaling is done here (not in web worker).
    //
    let scaledSourceTensor
      = this.neuralNetConfig.create_ScaledSourceTensor_from_PixelData( sourceCanvas );

    let sourceTypedArray = await scaledSourceTensor.data();

    // Discard the source tensor because type-array (not tensor) will be passed to
    // web worker
    scaledSourceTensor.dispose();

    // Generate a new processing id so that the result returned from worker could be
    // distinguished.
    ++this.processingId;
    let processingId = this.processingId;

    let workerProxy, processRelayPromises;

    for ( let i = 0; i < this.workerProxyArray.length; ++i ) {
      workerProxy = this.workerProxyArray[ i ];
      this.processTensorPromiseArray[ i ]
        = workerProxy.typedArray_processTensor_async( processingId, sourceTypedArray );
      // The sourceTypedArray is still valid here because it is copied (not
      // transferred) to the above web worker.
    }

    // Since all web worker has received the source typed-array (parallelly), wait
    // for all them done.
    let promiseAllSettled = Promise.allSettled( this.processTensorPromiseArray );
    return promiseAllSettled;
  }

  get totalWorkerCount() {
    return this.workerProxyArray.length;
  }

}
<|endoftext|>


<commit_before>export { AsyncWorker_Body as Body };

/**
 * The base class for web worker.
 *
 * It should be used in web worker context.
 *
 */
class AsyncWorker_Body {

  /** It will register callback from AsyncWorker_Proxy. */
  constructor() {
    globalThis.onmessage
      = AsyncWorker_Body.onmessage_from_AsyncWorker_Proxy.bind( this );
  }

  /** @override */
  disposeResources() {

//!!! ...unfinished... (2022/09/08) also MessagePort.close().

    close(); // Terminate this worker.

    //super.disposeResources();
  }

  /** Handle message from AsyncWorker_Proxy. */
  static onmessage_from_AsyncWorker_Proxy( e ) {

    // e.data == { processingId, command, args }
    let processingId = e.data.processingId;
    let command = e.data.command;
    let method = this[ command ]; // command name as method name.
    let func = method.bind( this );

//!!! ...unfinished... (2022/09/10)
// Every function should be an async generator.
// Here will postMessage() the { done, value } object back to WorkerProxy.

    try {
      let p = func( e.data.args );

      // For asynchronous function, wait result and then return it.
      if ( p instanceof Promise ) {
        p.then( r => {
          if ( processingId != undefined ) {
            let resultData = { processingId: processingId, workerId: this.workerId, r };
            postMessage( resultData );
          } // Otherwise, no processingId means no need report return value.

        } ).catch( errorReason => {
          let msg = `AsyncWorker_Body.onmessage_from_AsyncWorker_Proxy(): `
            + `workerId=${this.workerId}, processingId=${processingId}, `
            + `command="${command}", asynchronous, failed. `
            + `${errorReason}`;
          console.err( msg );
          //debugger;
        } );

      // For synchronous function, return result immediately.
      } else {
        if ( processingId != undefined ) {
          let resultData = { processingId: processingId, workerId: this.workerId, r };
          postMessage( resultData );
        } // Otherwise, no processingId means no need report return value.
      }

    } catch ( errorReason ) {
      let msg = `AsyncWorker_Body.onmessage_from_AsyncWorker_Proxy(): `
        + `workerId=${this.workerId}, processingId=${processingId}, `
        + `command="${command}", synchronous, failed. `
        + `${errorReason}`;
      console.err( msg );
      //debugger;
    }
  }

}

<commit_msg>onmessage_from_AsyncWorker_Proxy for async generator.<commit_after>export { AsyncWorker_Body as Body };

/**
 * The base class for web worker.
 *
 * It should be used in web worker context.
 *
 */
class AsyncWorker_Body {

  /** It will register callback from AsyncWorker_Proxy. */
  constructor() {
    globalThis.onmessage
      = AsyncWorker_Body.onmessage_from_AsyncWorker_Proxy.bind( this );
  }

  /** @override */
  disposeResources() {

//!!! ...unfinished... (2022/09/08) also MessagePort.close().

    close(); // Terminate this worker.

    //super.disposeResources();
  }

  /** Handle message from AsyncWorker_Proxy.
   * It will
   *
   * @param {AsyncWorker_Body} this
   *   The object which has the method function specified by e.data.command.
   *
   * @param {MessageEvent} e
   *   - e.data.processingId is the request sequence id.
   *
   *   - e.data.command will be used as this object's method name. The method must
   *       be an async generator.
   *
   *   - e.data.args will be passed to the function this[ e.data.command ]().
   */
  static async onmessage_from_AsyncWorker_Proxy( e ) {

    // e.data == { processingId, command, args }
    let processingId = e.data.processingId;
    let command = e.data.command;
    let method = this[ command ]; // command name as method name.
    let func = method.bind( this );

//!!! ...unfinished... (2022/09/10)
// Every function should be an async generator.
// Here will postMessage() the { done, value } object back to WorkerProxy.

    try {
      let asyncGenerator = func( e.data.args );

      if ( processingId != undefined ) {
        let done_value;
        do {
          done_value = await asyncGenerator.next();
            let resultData = { processingId: processingId, done_value };

  //!!! ...unfinished... (2022/09/10)
  // How to specify Transferable Objects array?

            postMessage( resultData );

        } while ( !done_value.done );

      } else { // Otherwise, no processingId means no need report return value.
        for await ( let value of asyncGenerator ) {
          // Do nothing. Just complete the async generator.
        }
      }

    } catch ( errorReason ) {
      let msg = `AsyncWorker_Body.onmessage_from_AsyncWorker_Proxy(): `
        + `processingId=${processingId}, `
        + `command="${command}", failed. `
        + `${errorReason}`;
      console.err( msg );
      //debugger;
    }

// //!!! (2022/09/10 Remarked) Every function should be an async generator..
//     try {
//       let p = func( e.data.args );
//
//       // For asynchronous function, wait result and then return it.
//       if ( p instanceof Promise ) {
//         p.then( r => {
//           if ( processingId != undefined ) {
//             let resultData = { processingId: processingId, workerId: this.workerId, r };
//             postMessage( resultData );
//           } // Otherwise, no processingId means no need report return value.
//
//         } ).catch( errorReason => {
//           let msg = `AsyncWorker_Body.onmessage_from_AsyncWorker_Proxy(): `
//             + `workerId=${this.workerId}, processingId=${processingId}, `
//             + `command="${command}", asynchronous, failed. `
//             + `${errorReason}`;
//           console.err( msg );
//           //debugger;
//         } );
//
//       // For synchronous function, return result immediately.
//       } else {
//         if ( processingId != undefined ) {
//           let resultData = { processingId: processingId, workerId: this.workerId, r };
//           postMessage( resultData );
//         } // Otherwise, no processingId means no need report return value.
//       }
//
//     } catch ( errorReason ) {
//       let msg = `AsyncWorker_Body.onmessage_from_AsyncWorker_Proxy(): `
//         + `workerId=${this.workerId}, processingId=${processingId}, `
//         + `command="${command}", synchronous, failed. `
//         + `${errorReason}`;
//       console.err( msg );
//       //debugger;
//     }
  }

}

<|endoftext|>


<commit_before>import React from 'react';

import Analyzer from 'parser/core/Analyzer';
import Enemies from 'parser/shared/modules/Enemies';

import SPELLS from 'common/SPELLS';
import SpellIcon from 'common/SpellIcon';
import SpellLink from 'common/SpellLink';
import { formatPercentage } from 'common/format';
import StatisticBox, { STATISTIC_ORDER } from 'interface/others/StatisticBox';

class Judgment extends Analyzer {
  static dependencies = {
    enemies: Enemies,
  };
  templarsVerdictConsumptions = 0;
  divineStormConsumptions = 0;
  justicarsVengeanceConsumptions = 0;
  judgmentsApplied = 0;

  on_byPlayer_applydebuff(event) {
    const spellId = event.ability.guid;
    if (spellId !== SPELLS.JUDGMENT_DEBUFF.id) {
      return;
    }
    this.judgmentsApplied += 1;
  }

  on_byPlayer_damage(event) {
    const enemy = this.enemies.getEntity(event);
    const spellId = event.ability.guid;

    if (!enemy) {
      return;
    }
    if (!enemy.hasBuff(SPELLS.JUDGMENT_DEBUFF.id, null, 250)) {
      return;
    }
    switch (spellId) {
      case SPELLS.TEMPLARS_VERDICT_DAMAGE.id:
        this.templarsVerdictConsumptions += 1;
        break;
      case SPELLS.DIVINE_STORM_DAMAGE.id:
        this.divineStormConsumptions += 1;
        break;
      case SPELLS.JUSTICARS_VENGEANCE_TALENT.id:
        this.justicarsVengeanceConsumptions += 1;
        break;
      default:
        break;
    }
  }

  get judgmentsConsumed() {
    return this.templarsVerdictConsumptions + this.divineStormConsumptions + this.justicarsVengeanceConsumptions;
  }

  get percentageJudgmentsConsumed() {
    return this.judgmentsConsumed / this.judgmentsApplied;
  }

  get suggestionThresholds() {
    return {
      actual: this.percentageJudgmentsConsumed,
      isLessThan: {
        minor: 0.95,
        average: 0.9,
        major: 0.85,
      },
      style: 'percentage',
    };
  }

  suggestions(when) {
    when(this.suggestionThresholds).addSuggestion((suggest, actual, recommended) => {
      return suggest(<>You're not consuming all your <SpellLink id={SPELLS.JUDGMENT_CAST.id} icon /> debuffs.</>)
        .icon(SPELLS.JUDGMENT_DEBUFF.icon)
        .actual(`${formatPercentage(this.percentageJudgmentsConsumed)}% Judgments consumed`)
        .recommended(`>${formatPercentage(recommended)}% is recommended`);
    });
  }

  statistic() {
    const justicarsVengeanceText = this.selectedCombatant.hasTalent(SPELLS.JUSTICARS_VENGEANCE_TALENT.id) ? `<br>Justicars Vengeance consumptions: ${this.justicarsVengeanceConsumptions}` : ``;
    return (
      <StatisticBox
        position={STATISTIC_ORDER.CORE(6)}
        icon={<SpellIcon id={SPELLS.JUDGMENT_DEBUFF.id} />}
        value={`${formatPercentage(this.percentageJudgmentsConsumed)}%`}
        label="Judgments Consumed"
        tooltip={`
          Judgments Applied: ${this.judgmentsApplied}

          Templars Verdicts consumptions: ${this.templarsVerdictConsumptions}

          Divine Storm consumptions: ${this.divineStormConsumptions}
          ${justicarsVengeanceText}`}
      />
    );
  }
}

export default Judgment;
<commit_msg>Fix judgment using EnemyInstances instead<commit_after>import React from 'react';

import Analyzer from 'parser/core/Analyzer';
import EnemyInstances from 'parser/shared/modules/EnemyInstances';

import SPELLS from 'common/SPELLS';
import SpellIcon from 'common/SpellIcon';
import SpellLink from 'common/SpellLink';
import { formatPercentage } from 'common/format';
import StatisticBox, { STATISTIC_ORDER } from 'interface/others/StatisticBox';

class Judgment extends Analyzer {
  static dependencies = {
    enemies: EnemyInstances,
  };
  templarsVerdictConsumptions = 0;
  divineStormConsumptions = 0;
  justicarsVengeanceConsumptions = 0;
  judgmentsApplied = 0;

  on_byPlayer_applydebuff(event) {
    const spellId = event.ability.guid;
    if (spellId !== SPELLS.JUDGMENT_DEBUFF.id) {
      return;
    }
    this.judgmentsApplied += 1;
  }

  on_byPlayer_damage(event) {
    const enemy = this.enemies.getEntity(event);
    const spellId = event.ability.guid;

    if (!enemy) {
      return;
    }
    if (!enemy.hasBuff(SPELLS.JUDGMENT_DEBUFF.id, null, 250)) {
      return;
    }
    switch (spellId) {
      case SPELLS.TEMPLARS_VERDICT_DAMAGE.id:
        this.templarsVerdictConsumptions += 1;
        break;
      case SPELLS.DIVINE_STORM_DAMAGE.id:
        this.divineStormConsumptions += 1;
        break;
      case SPELLS.JUSTICARS_VENGEANCE_TALENT.id:
        this.justicarsVengeanceConsumptions += 1;
        break;
      default:
        break;
    }
  }

  get judgmentsConsumed() {
    return this.templarsVerdictConsumptions + this.divineStormConsumptions + this.justicarsVengeanceConsumptions;
  }

  get percentageJudgmentsConsumed() {
    return this.judgmentsConsumed / this.judgmentsApplied;
  }

  get suggestionThresholds() {
    return {
      actual: this.percentageJudgmentsConsumed,
      isLessThan: {
        minor: 0.95,
        average: 0.9,
        major: 0.85,
      },
      style: 'percentage',
    };
  }

  suggestions(when) {
    when(this.suggestionThresholds).addSuggestion((suggest, actual, recommended) => {
      return suggest(<>You're not consuming all your <SpellLink id={SPELLS.JUDGMENT_CAST.id} icon /> debuffs.</>)
        .icon(SPELLS.JUDGMENT_DEBUFF.icon)
        .actual(`${formatPercentage(this.percentageJudgmentsConsumed)}% Judgments consumed`)
        .recommended(`>${formatPercentage(recommended)}% is recommended`);
    });
  }

  statistic() {
    const justicarsVengeanceText = this.selectedCombatant.hasTalent(SPELLS.JUSTICARS_VENGEANCE_TALENT.id) ? `<br>Justicars Vengeance consumptions: ${this.justicarsVengeanceConsumptions}` : ``;
    return (
      <StatisticBox
        position={STATISTIC_ORDER.CORE(6)}
        icon={<SpellIcon id={SPELLS.JUDGMENT_DEBUFF.id} />}
        value={`${formatPercentage(this.percentageJudgmentsConsumed)}%`}
        label="Judgments Consumed"
        tooltip={`
          Judgments Applied: ${this.judgmentsApplied}

          Templars Verdicts consumptions: ${this.templarsVerdictConsumptions}

          Divine Storm consumptions: ${this.divineStormConsumptions}
          ${justicarsVengeanceText}`}
      />
    );
  }
}

export default Judgment;
<|endoftext|>