www.gusucode.com > risk 工具箱matlab源码程序 > risk/+risk/+internal/+app/BinningApp.m

    classdef (Sealed) BinningApp < toolpack.desktop.ToolGroup
% BINNINGAPP is a class that provides binning functionalities credit scoring
%
% Syntax:
%
%   h = BinningApp()
%
% Description:
%
%   BINNINGAPP opens a Toolstrip App allowing the user to perform automatic 
%   and manual binning of the data stored in the creditscorecard object, 
%   sc.
%   (...)
%   
%
% Output Arguments:
% 
%   h  - Handle to the Toolstrip object
%

% Copyright 2016 The MathWorks, Inc.

    properties (Access = protected)
        % Toolstrip properties
        CtrlsOverview  % Stores toolstrip's UIControls for the "Binning" Tab
        CtrlsManual    % Stores toolstrip's UIControls for the "Manual" Tab
        OverviewFigure % The main figure in the UI, that hosts all subplots
        TabGroup       % the tab group in the toolstrip
        
        PredictorInfo  % Figure to store the BININFO statistics
        BinInfo        % Figure to store the table for binning information
        PlotSettings   % Plot options used by PLOTBINS
        TableSettings  % Table settings used by BININFO
        isPlotChanged  % Boolean indicating whether algorithm or plot 
                       % settings were modified
        isWOEChanged   = false; % Boolean for when the WOE checkbox is ticked
        isBinTextChanged = false;
        isTableViewChanged = false;
        MembersCheckbox = 'overview';
        isViewExpanded % Boolean indicating whether or not the view is expanded
        isAlgoUICalled = false; % Boolean indicating if the BinningAppAlgorithmSettings class is used
        NumApps = 0;   % Integer. If >0, the existing data must be deleted before loading new data
        isInitPlot = true;  % Boolean. Used by the 'Plot Options' button callback 
        isInitTable = true;   % Boolean. Used by the 'Plot Options' button callback 
        PlotMenu       % Drop down menu on the 'Plot Options' button
        TableMenu      % Drop down menu on the 'Table Options' button
        nCallBinText = false; % Boolean used during initialization of PlotMenu, with selected items
        nCallWOE = false;     % Boolean used during initialization of PlotMenu, with selected WOE
        nCallStats = 0;
        
        % Plot properties
        Legend 
        TooltipOn      % Boolean indicating whether or not the tooltip is displayed on the histograms
        TooltipHandleAxes
        TooltipHandleBins
        SelectedAxes = {};
        AllAxesHandles = struct();
        BinTextHandles = struct();
        PlotPanel      % UIpanel to store all the subplots
        ScrollBar      % Track the scroll bar position to slide the main figure's uipanel
        ZoomedFigureAxesPosition
        
        % Manual binning properties
        ManualBinningPanel   % TSPanel that in the Manual Binning section that updates with the predictor data type. 
        ManualBinningSection % ToolSection to store the Manual binning section
        ZoomedFigure    % Store the expanded view when manual binning is performed
        ZoomedAxes      % Store the axes in the expanded view 
        BarHandles      % Store the handle to the bar plots
        LeftEdgesXData  % Array that stores the X-position of the left edges of the histogram
        RightEdgesXData % Array that stores the X-position of the right edges of the histogram
        LeftEdgeValue   % Value of LeftEdges for the selected bin
        RightEdgeValue  % Value of RightEdges for the selected bin
        BinHeights      % Height, in bin count, of each bin
        NumBins         % Number of bins in the histogram of the zoomed figure
        SelectedBins    % Array storing the bin numbers of selected bins
        BinMembers      % object of type TSList that displays the members of the selected bin (categorical)
        isMergeApplied = false;
        isShiftApplied = false;
        isSplitApplied = false;
        isDuplicateCutPoint = false;
        isPredictorChanged  = false;
        
        % Split / Merge properties
        SplitFigure
        OldNumBins = 2;
        MaxNumBinsSplit = 4; % For a maximum of 5 sub-bins
        SplitBinLine    % Array of line objects that are used as visual cue for splitting bins (Numeric)
        CutPointBox     % Array that holds the cutpoint as a text value
        InvalidCutPoint = false; % If the user clicks ok before validating the edit box cutpoint
        SplitCutPoints  % Store the values of the new cutpoints for the selected bin to split
        OldCutPoints    % Store the cutpoints before confirming the split
        NumMergedBins = 0; % Used when merging happens, to determine which bin to select. Positive: merge to the right. Negative: merge to the left.
        SplitCategories % Nx2 cell array. Stores the values of the new catgroupings for the selected bin to split
        OldCatGroupings % Store the catgroupings before confirming the split
        SelectedCategories  % Cell array to store selected categories used for bin split. First element is the bin number
        DefaultBinListing   % Cell array of original bins and their numbers
        OldBinNumber = [];  % Used by the WindowButtonMotionFcn for the zoomed figure
        PatchColor = [0 153/255 1];
        
        % Status bar Java objects
        JStatusBar 
        JProgressBar 
        JLabel 
        JFrame
        
    end
    
    properties (Access = protected)
        % Credit Scorecard properties
        OriginalData
        Scorecard = [];
        OldScorecard = [];
        PredictorVars 
        PreviousPredictor
        SelectedPredictors 
        ModifiedPredictors
        isScorecardExported = false;
        StatsTable = struct(); 
        ResponseChangedEvent
        MonotoneRadioBtn
        NoBinningRadioBtn
        
        % Algorithm settings
        Algorithm = struct('AlgoName','');
        DefaultAlgoSettings = struct();
        
        % Bin info statistics
        BinInfoStats = {'odds','woe','infovalue','entropy','members'};
        
    end
    
    properties(Access = private)
        ActionListeners
    end
    
    events
        PlotSettingsChanged
        SelectedFigureTab
    end
    
    methods (Access = public)
        %% Constructor
        
        function obj = BinningApp()
            
            % Get the Binning App handle from the root
            obj = obj@toolpack.desktop.ToolGroup('binningexplorer',risk.internal.app.utils.getMessage('risk:binningexplorerapp:OverviewFigureName'));
            
            % Create the app
            obj.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:AppDescription');
            
            % Setup toolstrip sections
            tab1 = obj.addTab('overview',risk.internal.app.utils.getMessage('risk:binningexplorerapp:OverviewTabName'));
            obj.SelectedTab = 'overview';
            obj.addSection(tab1);
              
            % Disable all controls that are not I/O related. This will
            % promt the user to first load a creditscorecard object.
            obj.enableControls(false);

            % Set help for the Binning Explorer
            obj.setContextualHelpCallback(@(src,ev) obj.launchBinningExplorerHelp)
            
            % Hide the View tab 
            g = obj.Peer.getWrappedComponent;
            g.putGroupProperty(com.mathworks.widgets.desk.DTGroupProperty.ACCEPT_DEFAULT_VIEW_TAB, false);
            
            % Customize the App's icon
            Icon = getIcon('Binning_App_Icon_16.png');
            g.putGroupProperty(com.mathworks.widgets.desk.DTGroupProperty.ICON,Icon.Peer);

            % Open the app
            obj.open;
            obj.setClosingApprovalNeeded(true);
            obj.ActionListeners.GroupAction = addlistener(obj,'GroupAction',@obj.CloseReqFcn);
  
            % Hide Data Browser
            md = com.mathworks.mlservices.MatlabDesktopServices.getDesktop;
            md.hideClient('DataBrowserContainer',obj.Name)
            
            % Setup the initial window size
            Width  = 980;
            Height = 1000;
            frame  = md.getFrameContainingGroup(obj.Name);
            frame.setSize(Width,Height);
            
            obj.ActionListeners.PlotSettings = addlistener(obj,'PlotSettingsChanged',@obj.applyPlotSettings);
            obj.ActionListeners.SelectedFigure = addlistener(obj,'SelectedFigureTab',@obj.updateZoomedFigure);
            
        end
       
        %% Doc center page for BinningExplorer
        function launchBinningExplorerHelp(~)
            
            helpview(fullfile(docroot,'risk','risk.map'),'BinningExplorerDoc');
            
        end

        %% Get/Set methods
        
        function Predictors = getSelectedPredictors(obj)
            % Called by the BinningAlgorithmSettings method to retrieve the
            % SelectedPredictors property of the app.
            
            Predictors = obj.SelectedPredictors;
            
        end
        
        function Predictors = getPredictors(obj)
            Predictors = obj.PredictorVars;
        end
        
        function NumPredictors = getNumericPredictors(obj)
            NumPredictors = obj.Scorecard.NumericPredictors(:);
        end
                
        function CatPredictors = getCategoricalPredictors(obj)
            CatPredictors = obj.Scorecard.CategoricalPredictors(:);
        end
        
        function OrdPredictors = getOrdinalPredictors(obj)
            CatPredictors  = obj.getCategoricalPredictors();
            NumPreds       = length(CatPredictors);
            isOrdinalPreds = false(NumPreds,1);
            
            for i = 1 : NumPreds
                data = obj.Scorecard.Data;
                if ~iscell(data.(CatPredictors{i})) && isordinal(data.(CatPredictors{i}))
                    isOrdinalPreds(i) = true;
                else
                    isOrdinalPreds(i) = false;
                end
            end
            
            OrdPredictors = CatPredictors(isOrdinalPreds);
        end
        
        function plotSettings = getPlotSettings(obj)
            Values = struct2cell(obj.PlotSettings);
            Fields = fieldnames(obj.PlotSettings);
            plotSettings = [Fields(1) Values(1) Fields(2) Values(2)];
        end

        %% Toolstrip Sections and callbacks
        
        function obj = addSection(obj,Tab)
            % Add section to the selected tab 'Tab'
            
            TabName = Tab.Name;
            % File section
            if strcmpi(TabName,'overview')
                sec = toolpack.desktop.ToolSection('data',risk.internal.app.utils.getMessage('risk:binningexplorerapp:DataSection'));
                sec.add(obj.LocalFileSection);
                Tab.add(sec);
            end
                        
            % Predictor section
            sec = toolpack.desktop.ToolSection('predictor',risk.internal.app.utils.getMessage('risk:binningexplorerapp:PredictorSection'));
            sec.add(obj.LocalPredictorSection(TabName));
            Tab.add(sec);
            
            % Algorithm / Automatic binning section
            sec = toolpack.desktop.ToolSection('algo',risk.internal.app.utils.getMessage('risk:binningexplorerapp:AutoBinningSection'));
            sec.add(obj.LocalAlgoSection(TabName));
            Tab.add(sec);
            
            % Manual binning section
            if strcmpi(Tab.Name,'overview')
                sec = toolpack.desktop.ToolSection('binning',risk.internal.app.utils.getMessage('risk:binningexplorerapp:ManualBinningSection'));
                sec.add(obj.LocalBinningSection);
                Tab.add(sec);
            end
            
            % Split / Merge section
            if strcmpi(Tab.Name,'manual')
                sec = toolpack.desktop.ToolSection('splitmerge',risk.internal.app.utils.getMessage('risk:binningexplorerapp:ManualBinningSection'));
                sec.add(obj.LocalSplitMergeSection);
                Tab.add(sec);
                obj.ManualBinningSection = sec;
            end
            
            % Plot section
            sec = toolpack.desktop.ToolSection('plot',risk.internal.app.utils.getMessage('risk:binningexplorerapp:PlotSection')); 
            sec.add(obj.LocalPlotSection(TabName));
            Tab.add(sec);
           
            % Table section
            sec = toolpack.desktop.ToolSection('table',risk.internal.app.utils.getMessage('risk:binningexplorerapp:TableSection')); 
            sec.add(obj.LocalTableSection(TabName));
            Tab.add(sec);
            
            % Export section
            if strcmpi(Tab.Name,'overview')
                sec = toolpack.desktop.ToolSection('export',risk.internal.app.utils.getMessage('risk:binningexplorerapp:ExportSection'));
                sec.add(obj.LocalExportSection);
                Tab.add(sec);
            end
                       
        end
        
        %% File and Update Section
        
        function section = LocalFileSection(obj)
            
            importDataBtn = toolpack.component.TSButton(risk.internal.app.utils.getMessage('risk:binningexplorerapp:ImportData'), ...
                toolpack.component.Icon.IMPORT_24);
%             importDataBtn.setAutoMnemonicEnabled(true);
            importDataBtn.Orientation = toolpack.component.ButtonOrientation.VERTICAL;
            importDataBtn.Name = 'importbutton';
            importDataBtn.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:ImportDataTooltip');
            
            section = toolpack.component.TSPanel('f:p:g','f:p,1dlu');
            section.Name = 'FileSection';
            
            section.add(importDataBtn,'xy(1,1,''c,c'')');
            obj.ActionListeners.Load = addlistener(importDataBtn,'ActionPerformed',@obj.loadData);
                      
            obj.CtrlsOverview.FileButtons = importDataBtn;
            
        end
           
        function section = LocalExportSection(obj)
            
            exportScorecardBtn = toolpack.component.TSButton(risk.internal.app.utils.getMessage('risk:binningexplorerapp:Export'), ...
                toolpack.component.Icon.CONFIRM_24);
            
            exportScorecardBtn.Orientation = toolpack.component.ButtonOrientation.VERTICAL;
            exportScorecardBtn.Name = 'exportbutton';
            exportScorecardBtn.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:ExportTooltip');
            
            section = toolpack.component.TSPanel('f:p:g','f:p,1dlu');
            section.Name = 'ExportSection';

            section.add(exportScorecardBtn,'xy(1,1,''c,c'')');
            obj.ActionListeners.Export = addlistener(exportScorecardBtn,'ActionPerformed',@obj.exportScorecard);
            
            obj.CtrlsOverview.UpdateButtons = exportScorecardBtn;

        end
        
        %% Predictor Section
        
        function section = LocalPredictorSection(obj,TabName)
            if strcmpi(TabName,'manual')
                Name1 = obj.CtrlsOverview.PredictorButtons(2).Text;
            else
                Name1 = risk.internal.app.utils.getMessage('risk:binningexplorerapp:BlankSelect');
            end
            
            if isempty(obj.PredictorVars)
                Name2 = risk.internal.app.utils.getMessage('risk:binningexplorerapp:BlankSelect');
            else
                Name2 = obj.SelectedPredictors{1};
            end
            
            PredLabel   = toolpack.component.TSLabel(risk.internal.app.utils.getMessage('risk:binningexplorerapp:PredictorType'));
            PredTypeBtn = toolpack.component.TSDropDownButton(Name1);
            PredTypeBtn.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:PredictorTypeTooltip');
            PredTypeBtn.Popup = obj.setPredTypeList('text_only');
            PredTypeBtn.Name  = 'predictortypebutton';
            
            addlistener(PredTypeBtn.Popup,'ListItemSelected',...
                @(src,ev)obj.setPredTypeString(src,ev,TabName));
            
            PredListLabel = toolpack.component.TSLabel(risk.internal.app.utils.getMessage('risk:binningexplorerapp:SelectedPredictor'));  
            SelectedPredBtn = toolpack.component.TSDropDownButton(Name2);
            SelectedPredBtn.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:SelectedPredictorTooltip');
            SelectedPredBtn.Name  = 'selectedpredictorsbutton'; 
            SelectedPredBtn.Popup = obj.setPredictorList('text_only');
                
            if ~isempty(obj.PredictorVars)
                addlistener(SelectedPredBtn.Popup,'ListItemSelected',...
                    @(src,ev)obj.selectAxesWrapper(src,ev,TabName));
            end
            
            section = toolpack.component.TSPanel('l:p,4dlu,l:p','f:p:g,c:p,1dlu,c:p,f:p:g');
            section.Name = 'Predictor';
            section.add(PredListLabel,'xy(1,2)');
            section.add(SelectedPredBtn,'xy(1,4)');
            section.add(PredLabel,'xy(3,2)');
            section.add(PredTypeBtn,'xy(3,4)');
             
            if strcmpi(TabName,'overview')
                obj.CtrlsOverview.PredictorButtons = [PredLabel ...
                    PredTypeBtn PredListLabel SelectedPredBtn];
            else
                obj.CtrlsManual.PredictorButtons = [PredLabel ...
                    PredTypeBtn PredListLabel SelectedPredBtn];
            end
            
        end
        
        function popup = setPredTypeList(~,TextOption)
            listItems = struct( ...
                'Title', '', ...
                'Style', '', ...
                'Header', cell(3,1));
            
            % Numeric
            listItems(1).Title = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Numeric');
            listItems(1).Style  = TextOption;
            listItems(1).Header = false;
            
            % Categorical
            listItems(2).Title = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Categorical');
            listItems(2).Style  = TextOption;
            listItems(2).Header = false;
            
            % Ordinal
            listItems(3).Title = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Ordinal');
            listItems(3).Style  = TextOption;
            listItems(3).Header = false;
            
            popup = toolpack.component.TSDropDownPopup(listItems,TextOption);
            popup.Name = 'predictortypelistpopup';
            
        end
        
        function obj = setPredTypeString(obj,varargin)
            % Callback on the popup menu from the 'Predictor type' button.
            % It updates the string that appears on the button
            
            src = varargin{1};
            TabName = varargin{3};
            
            % Get the control button
            if strcmpi(TabName,'overview')
                PredTypeBtn = obj.CtrlsOverview.PredictorButtons(2);
                if obj.isViewExpanded
                    AltTabBtn = obj.CtrlsManual.PredictorButtons(2); 
                else
                    AltTabBtn = [];
                end
            else
                PredTypeBtn = obj.CtrlsManual.PredictorButtons(2);
                AltTabBtn = obj.CtrlsOverview.PredictorButtons(2);
            end
            
            idx = src.SelectedIndex;
            PredType = src.Items(idx).Title;
            PredTypeBtn.Text = PredType;
            
            % Update the other tab's button
            if ~isempty(AltTabBtn)
                AltTabBtn.Text = PredType;
            end

            % Make the predictor type conversion and update the plot
            obj.setPredictorType(PredTypeBtn);

        end
        
        function obj = setPredictorType(obj,varargin)
            % Callback on the 'Predictor Type' button. It sets predictor 
            % types and updates the scorecard object
            
            src = varargin{1};
            sc = obj.Scorecard;
            NumPreds = length(obj.SelectedPredictors);
            if strcmpi(src.Text,'ordinal')
                PredictorType = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Categorical');
                isOrdinal = true;
            else
                PredictorType = src.Text;
                isOrdinal = false;
            end

            % Warn if the user converts from Numeric to Categorical, since
            % converting back to Numeric could result in loss of the
            % previous (numeric) binning.
            if any(ismember(obj.SelectedPredictors,obj.getNumericPredictors())) ...
                    && strcmpi(PredictorType,'categorical')

                Choice = questdlg(risk.internal.app.utils.getMessage('risk:binningexplorerapp:DataTypeConversionDlg',lower(src.Text)),...
                    risk.internal.app.utils.getMessage('risk:binningexplorerapp:DataTypeConversionTitle'),...
                    risk.internal.app.utils.getMessage('risk:binningexplorerapp:Yes'),...
                    risk.internal.app.utils.getMessage('risk:binningexplorerapp:No'),...
                    risk.internal.app.utils.getMessage('risk:binningexplorerapp:No'));

                switch lower(Choice)
                    case 'no'
                        src.Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Numeric');
                        return;
                end
            end

            for i = 1 : NumPreds
                Predictor = obj.SelectedPredictors{i};
                sc = modifypredictor(sc,Predictor,'PredictorType',...
                    PredictorType,'Ordinal',isOrdinal);
                obj.Scorecard = sc;
            end

            obj.ModifiedPredictors = obj.SelectedPredictors;
            obj.isScorecardExported = false;
            
            % Update the status of the "Members" checkbox
            if strcmpi(PredictorType,'categorical')
                PreviousCheckedState = obj.CtrlsOverview.TableButtons(end).Selected ;
                obj.CtrlsOverview.TableButtons(end).Enabled = true;
                obj.CtrlsOverview.TableButtons(end).Selected = PreviousCheckedState;
            else
                obj.CtrlsOverview.TableButtons(end).Enabled = false;
            end

            % Update the relevant plots
            obj.isPlotChanged = true;
            obj.isPredictorChanged = true;
            obj.showPlots('predictortype');
            obj.isPredictorChanged = false;
            obj.isPlotChanged = false;
            
        end
                  
        function popup = setPredictorList(obj,TextOption)
            % Callback on the "Selected Predictors" drop-down list. It
            % lists the predictors loaded in the app.
    
            if isempty(obj.PredictorVars)
                listItems = struct('Title','','Style','text_only','Header',true);
            else
                listItems = struct( ...
                    'Title', '', ...
                    'Style', TextOption, ...
                    'Header', cell(length(obj.PredictorVars),1));

                Predictors = sort(obj.PredictorVars);
                for i = 1 : length(Predictors)
                    listItems(i).Title = Predictors{i};
                    listItems(i).Header = false;
                end
            end
            
            popup = toolpack.component.TSDropDownPopup(listItems,TextOption);
            popup.Name = 'predictorlistpopup';
            
        end

        function obj = selectAxesWrapper(obj,varargin)
            % Callback on the "Selected Predictor" drop-down list.
            
            src = varargin{1};
            if nargin == 4
                TabName = varargin{3};
            else
                TabName = '';
            end
            
             % First update the string on the button
            if any(strcmpi(TabName,{'','overview'}))
                SelectedPredBtn = obj.CtrlsOverview.PredictorButtons(4);
                if obj.isViewExpanded
                    AltTabBtn = obj.CtrlsManual.PredictorButtons(4); 
                else
                    AltTabBtn = [];
                end
            else
                SelectedPredBtn = obj.CtrlsManual.PredictorButtons(4);
                AltTabBtn = obj.CtrlsOverview.PredictorButtons(4);
            end
            
            idx = src.SelectedIndex;
            Predictor = src.Items(idx).Title;
            SelectedPredBtn.Text = Predictor;
            
            % Update the other tab's button
            if ~isempty(AltTabBtn)
                AltTabBtn.Text = Predictor;
            end

            % Select the corresponding axes
            hax = obj.AllAxesHandles.(src.Items(src.SelectedIndex).Title);
            obj.isPredictorChanged = true;
            obj.selectAxes(hax(1),[]);
            obj.isPredictorChanged = false;

            % Shift the slider
            % Use obj.SelectedAxes if the manual figure is open
            hax = obj.SelectedAxes(1);
            setValue(obj.ScrollBar,100*(1-hax.Position(2)-hax.Position(4)));
            
        end
        
        %% Algorithm / Automatic Binning Section
        
        function section = LocalAlgoSection(obj,TabName)
            algoBtn = toolpack.component.TSSplitButton(risk.internal.app.utils.getMessage('risk:binningexplorerapp:ApplyAlgo',...     
                obj.Algorithm.AlgoName),toolpack.component.Icon.RUN_24);
            algoBtn.Orientation = toolpack.component.ButtonOrientation.VERTICAL;
            algoBtn.Popup = obj.setAlgoList('icon_text_description');
            algoBtn.Name = 'algobutton';
            algoBtn.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:ApplyAlgoTooltip');
            
            obj.ActionListeners.RunAlgo = addlistener(algoBtn,'ActionPerformed',@obj.runAlgorithm);
            obj.ActionListeners.SetAlgo = addlistener(algoBtn.Popup,'ListItemSelected',...
                @(src,ev)obj.setAlgoName(src,ev,TabName));
            
            algoSettings = toolpack.component.TSButton(risk.internal.app.utils.getMessage('risk:binningexplorerapp:AlgoOptions'),...
                toolpack.component.Icon.SETTINGS_24);
            algoSettings.Orientation = toolpack.component.ButtonOrientation.VERTICAL;
            algoSettings.Name = 'algooptionsbutton';
            algoSettings.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:AlgoOptionsTooltip');
            
            obj.ActionListeners.SetAlgoUI = addlistener(algoSettings,...
                'ActionPerformed',@(src,ev)obj.setAlgoSettingsUI(src,ev,TabName));
            
            section = toolpack.component.TSPanel('110px,10px,f:p:g','f:p,1dlu');
            section.Name = 'AlgoSection';
            section.add(algoBtn,'xy(1,1)'); 
            section.add(algoSettings,'xy(3,1)'); 
             
            if strcmpi(TabName,'overview')
                obj.CtrlsOverview.AlgoButtons = [algoBtn;algoSettings];
            else
                obj.CtrlsManual.AlgoButtons = [algoBtn;algoSettings];
            end
            
        end
        
        function popup = setAlgoList(~,TextOption)
            listItems = struct( ...
                'Title', '', ...
                'Description', '', ...
                'Style', '', ...
                'Icon', [],  ...
                'Header', cell(3,1));
            
            % Monotone
            listItems(1).Title = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Monotone');
            listItems(1).Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:MonotoneDescription');
            listItems(1).Style  = TextOption;
            listItems(1).Icon   = getIcon('Monotone_24.png');
            listItems(1).Header = false;
            
            % Equal Frequency
            listItems(2).Title = risk.internal.app.utils.getMessage('risk:binningexplorerapp:EF');
            listItems(2).Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:EFDescription');
            listItems(2).Style  = TextOption;
            listItems(2).Icon   = getIcon('Equal_Frequency_24.png');
            listItems(2).Header = false;
            
            % Equal Width
            listItems(3).Title = risk.internal.app.utils.getMessage('risk:binningexplorerapp:EW');
            listItems(3).Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:EWDescription');
            listItems(3).Style  = TextOption;
            listItems(3).Icon   = getIcon('Equal_Width_24.png');
            listItems(3).Header = false;
            
            popup = toolpack.component.TSDropDownPopup(listItems,TextOption);
            popup.Name = 'algolistpopup';
            
        end
        
        function obj = setAlgoName(obj,varargin)
            S = dbstack;
            if strcmpi(S(2).file(1:end-2),'BinningAppAlgorithmSettings')
                AlgoName = varargin{1};
                TabName  = varargin{2};
                obj.isAlgoUICalled = varargin{3};
                AlgoNameDisp = risk.internal.app.utils.formatNamesWithoutSpace({AlgoName},'equal');
                AlgoNameDisp = AlgoNameDisp{:};
            else
                src = varargin{1};
                idx = src.SelectedIndex;
                TabName  = varargin{3};
                AlgoNameDisp = src.Items(idx).Title;
                AlgoName = regexprep(AlgoNameDisp,' ','');
            end
            
            obj.Algorithm.AlgoName = AlgoName;
            
            % The algorithm settings are default unless called by the
            % settings UI
            if ~obj.isAlgoUICalled
                obj.Algorithm.Settings = obj.DefaultAlgoSettings.(AlgoName);
            end
            
            % Update the name of the algorithm on the button
            if strcmpi(TabName,'overview')
                AlgoBtn = obj.CtrlsOverview.AlgoButtons(1);
                if obj.isViewExpanded
                    AltTabBtn = obj.CtrlsManual.AlgoButtons(1); 
                else
                    AltTabBtn = [];
                end
            else
                AlgoBtn = obj.CtrlsManual.AlgoButtons(1);
                AltTabBtn = obj.CtrlsOverview.AlgoButtons(1);
            end
            
            AlgoBtn.Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:ApplyAlgo',AlgoNameDisp);
            
            if ~isempty(AltTabBtn)
                AltTabBtn.Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:ApplyAlgo',AlgoNameDisp);
            end

            obj.runAlgorithm;
            
            % Reset the flag for obj.isAlgoUICalled
            obj.isAlgoUICalled = false;
            
        end
        
        function runAlgorithm(obj,varargin)
            % This callback is a wrapper around the method 'obj.showPlots'
            obj.isPlotChanged = true;
            obj.showPlots();
            obj.isPlotChanged = false;
        end
        
        function setAlgoSettingsUI(obj,varargin)
            % This method opens a UI to input the binning options.
            % Create a handle class that opens a UI, where the user inputs
            % the binning options. The options are stores in a property
            % 'Settings' and retried to be stored in obj.
            
            TabName = varargin{3};
            ba = risk.internal.app.BinningAppAlgorithmSettings(obj,...
                obj.Algorithm.AlgoName,TabName);
            hf = ba.getFigure();
            uiwait(hf);
        end
        
        function obj = setAlgoSettings(obj,Settings)
            % Settings is a structure containing the algorithm settings,
            % for the given algorithm name set by 'setAlgoName'.
            
            obj.Algorithm.AlgoName = Settings.AlgoName;
            obj.Algorithm.Settings = Settings.Settings;
        end
      
        function Settings = getAlgoSettings(obj)
            Settings.AlgoName = obj.Algorithm.AlgoName;
            Settings.Settings = obj.Algorithm.Settings;
          
        end
        
        function obj = setDefaultAlgoSettings(obj)
            obj.DefaultAlgoSettings.Monotone = {'InitialNumBins',10,...
                'Trend','Auto','SortCategories','Odds'};
            
            obj.DefaultAlgoSettings.EqualFrequency = {'NumBins',5,...
                'SortCategories','Odds'};

            obj.DefaultAlgoSettings.EqualWidth = {'NumBins',5,...
                'SortCategories','Odds'};
            
        end
        
        function Settings = getDefaultAlgoSettings(obj,algoName)
            % Called by BinningAppAlgorithmSettings UI
            
            Settings.AlgoName = algoName;
            Settings.Settings = obj.DefaultAlgoSettings.(algoName);
            
        end
        
        %% Manual Binning Section
        
        function section = LocalBinningSection(obj)
            % The description is 'expand' when in manual binning mode. It 
            % opens a contextual tab with a new figure and new controls on
            % the toolstrip. The description becomes 'overview' when the 
            % user closes the zoomed-in figure
            
            Icon = getIcon('Manual_Binning_24.png');
            expandBtn = toolpack.component.TSButton(risk.internal.app.utils.getMessage('risk:binningexplorerapp:ManualBinning'),...
                Icon);
            
            expandBtn.Orientation = toolpack.component.ButtonOrientation.VERTICAL;
            expandBtn.Name = 'manualbinningbutton'; 
            expandBtn.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:ManualBinningTooltip');
            
            section = toolpack.component.TSPanel('f:p:g','f:p,1dlu');
            section.Name = 'ViewSection';
            
            section.add(expandBtn,'xy(1,1,''c,c'')');
            obj.ActionListeners.ExpandView = addlistener(expandBtn,...
                'ActionPerformed',@obj.expandView);  
            
            obj.CtrlsOverview.ExpandButton = expandBtn;
            
        end      
        
        function section = LocalSplitMergeSection(obj)
            % 1. Split button  
            SplitIcon = getIcon('Split_Binning_24.png');
            SplitBtn = toolpack.component.TSButton(risk.internal.app.utils.getMessage('risk:binningexplorerapp:Split'),SplitIcon);
            SplitBtn.Orientation = toolpack.component.ButtonOrientation.VERTICAL;
            SplitBtn.Name = 'splitbutton';
            SplitBtn.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:SplitTooltip');
            
            % 2. Merge button
            MergeIcon = getIcon('Merge_Binning_24.png');
            MergeBtn = toolpack.component.TSButton(risk.internal.app.utils.getMessage('risk:binningexplorerapp:Merge'),MergeIcon);
            MergeBtn.Orientation = toolpack.component.ButtonOrientation.VERTICAL;
            MergeBtn.Enabled = false;
            MergeBtn.Name = 'mergebutton';
            MergeBtn.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:MergeTooltip');
            
            % 3. Labels for bin members or edges
            Predictor = obj.SelectedPredictors{1};
            
            if ismember(Predictor,obj.getNumericPredictors())
                String = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Edges');
                Edge1 = toolpack.component.TSTextField(num2str(obj.LeftEdgeValue),4); 
                Edge2 = toolpack.component.TSTextField(num2str(obj.RightEdgeValue),4); 
                Edge1.Name = 'leftedge';
                Edge2.Name = 'rightedge';
                Edge1.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Edge1Tooltip');
                Edge2.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Edge2Tooltip');
                obj.CtrlsManual.LeftEdge  = Edge1;
                obj.CtrlsManual.RightEdge = Edge2;
                obj.ActionListeners.ShiftCutPoint(1) = ...
                    addlistener(obj.CtrlsManual.LeftEdge,'ActionPerformed',@obj.shiftCutpoint);
                obj.ActionListeners.ShiftCutPoint(2) = ...
                    addlistener(obj.CtrlsManual.RightEdge,'ActionPerformed',@obj.shiftCutpoint);
                
            else
                String = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Members');
                Members = toolpack.component.TSLabel(obj.formatBinMembersText()); 
                Members.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:MembersTooltip');
                obj.CtrlsManual.Members = Members;
                if length(obj.BinMembers) < 2
                    SplitBtn.Enabled = false;
                end
                Members.Name = 'binmemberslist';
            end
            Label = toolpack.component.TSLabel(String);
            
            section = toolpack.component.TSPanel(...
                'c:p,3dlu,c:p,3dlu,f:p,1dlu,f:p,3dlu,f:p',...
                'f:p:g,c:p:g,f:p:g,c:p');

            section.Name = 'SplitMergeSection';
            section.add(SplitBtn,'xywh(1,1,1,4)');
            section.add(MergeBtn,'xywh(3,1,1,4)');
            if ismember(Predictor,obj.getNumericPredictors())
                section.add(Label,'xy(5,2)');
                section.add(Edge1,'xy(7,2)');
                section.add(Edge2,'xy(9,2)');
            else
                section.add(Label,'xy(5,1)');
                section.add(Members,'xywh(5,2,1,1)');
            end

            obj.CtrlsManual.ManualButtons = [SplitBtn;MergeBtn;Label];
            obj.ActionListeners.SplitBin = addlistener(SplitBtn,'ActionPerformed',@obj.splitBin);
            obj.ActionListeners.MergeBin = addlistener(MergeBtn,'ActionPerformed',@obj.mergeBins);
            
            obj.ManualBinningPanel = section;
        end
        
        function obj = expandView(obj,varargin)
            % Callback on the button in the 'Manual Binning' section. 
            % Creates new tabbed figure document, associated with a 
            % contextual tab for manual binning.
            
            % Set the main figure as the current figure to ensure that the
            % addition of the zoomed figure is tiled with the overview figure
            
            % Make sure the visibility is on
            set(groot,'DefaultFigureVisible','On')
            
            figure(obj.OverviewFigure); 
            Predictor = obj.SelectedPredictors{1};

            try
                if ~isempty(obj.TabGroup) 
                    if strcmpi(obj.ZoomedFigure.Tag,Predictor)
                        figure(obj.ZoomedFigure);
                        obj.isViewExpanded = true;
                    else
                        obj.updateZoomedFigure();
                        obj.isViewExpanded = true;
                    end
                else
                    createContextualTabAndFigure()
                end
                
            catch MException
                if strcmpi(MException.identifier,'MATLAB:class:InvalidHandle')
                    createContextualTabAndFigure()
                else
                    throw(MException)
                end
            end
             
            function createContextualTabAndFigure()
                hf = plotbins(obj.Scorecard,Predictor,obj.getPlotSettings{:});
                hf.MenuBar = 'None';
                hf.IntegerHandle = 'Off';
                hf.HandleVisibility = 'Off';
                hf.Name = risk.internal.app.utils.getMessage('risk:binningexplorerapp:ManualFigureName',...
                    obj.SelectedPredictors{1});
                hf.Tag = Predictor;
                hf.CloseRequestFcn = @obj.closeZoomedFigure;
                hf.WindowButtonDownFcn = @obj.selectBin;
                hf.KeyPressFcn   = @obj.figureKeyPress;
                hf.KeyReleaseFcn = @obj.figureKeyRelease;
                hf.WindowKeyPressFcn   = @obj.figureShiftPress;
                hf.WindowKeyReleaseFcn = @obj.figureShiftRelease;
                hf.WindowButtonMotionFcn = @obj.showBinInformation;
                hl = risk.internal.app.utils.extractChildren(hf,'legend'); 
                hl.PickableParts = 'None';
                hf.SizeChangedFcn = @obj.figureResizeCallback;
            
                % Highlight first bin
                hax = risk.internal.app.utils.extractChildren(hf,'axes');
                if find(strcmpi(get(hax,'Tag'),'WOE')) == 1
                    hax = flipud(hax);
                end

                obj.SelectedBins = 1;
                obj.resetBinEdgesMembersAfterReplot(hax);
                obj.ZoomedFigure = hf;
                obj.createContextualTab();
                obj.isViewExpanded = true;
                
            end
            
        end
          
        function obj = createContextualTab(obj,varargin)
            % Creates a contextual tab for manual binning. Called by
            % 'obj.expandView'

            obj.addFigure(obj.ZoomedFigure);
            if isempty(obj.TabGroup)
                tg = toolpack.desktop.TabGroup;
                tab = toolpack.desktop.ToolTab('manual',risk.internal.app.utils.getMessage('risk:binningexplorerapp:ManualTabName'));
                tg.Tabs = tab;
                tg.SelectedTab = tab;
                obj.addSection(tab);
                obj.addClientTabGroup(obj.ZoomedFigure,tg);
                obj.TabGroup = tg;
            end
        end
        
        function obj = selectBin(obj,varargin)
            % Highlights the bin that the user clicks on. This is a callback
            % (ButtonDownFcn) on the axes 'obj.ZoomedAxes'. Called by
            % 'obj.expandView' and 'obj.updateZoomedFigure'
                      
            hax = obj.ZoomedAxes;
            if isempty(obj.ZoomedFigure.UserData) || ...
                    any(strcmpi(obj.ZoomedFigure.UserData,{'ctrlclickoff','shiftclickoff'}))
                
                delete(risk.internal.app.utils.extractChildren(hax,'rectangle'));
                obj.resetBinControls();
            end
            
            % Make sure the edit box is enabled
            obj.CtrlsManual.LeftEdge.Enabled  = true;
            obj.CtrlsManual.RightEdge.Enabled = true; 
            
            XPos = hax.CurrentPoint(1,1);
            YPos = hax.CurrentPoint(1,2);
            
            BinNumber = find(XPos >= obj.LeftEdgesXData & XPos < obj.RightEdgesXData);
            
            if ~isempty(BinNumber)
                YBins = sum(cell2mat({obj.BarHandles.YData}'));
                if YPos <= YBins(BinNumber)
                    x = obj.LeftEdgesXData(BinNumber);
                    y = 0;
                    w = obj.BarHandles(1).BarWidth;
                    h = YBins(BinNumber);
                    rectangle('Parent',hax,'Position',[x y w h],...
                        'LineWidth',2,'EdgeColor','blue','Selected','On');
                    if ~isempty(obj.ZoomedFigure.UserData) && ...
                            any(strcmpi(obj.ZoomedFigure.UserData,...
                            {'ctrlclickon','shiftclickon'}))%obj.ZoomedFigure.UserData
                        
                        obj.SelectedBins = unique([obj.SelectedBins BinNumber]);
                        
                        % Disable editing bin edges for shifting
                        obj.CtrlsManual.LeftEdge.Enabled  = false;
                        obj.CtrlsManual.RightEdge.Enabled = false; 
                    else
                        obj.SelectedBins = BinNumber;
                    end
                    
                    % Display the bin edges on the toolstrip
                    Predictor = obj.SelectedPredictors{1};
                    if ismember(Predictor,obj.getNumericPredictors())
                        obj.setBinEdges(obj.SelectedBins); 
                    else
                        obj.setBinMembers(obj.SelectedBins);
                    end

                    % If non-adjacent bins are selected, select the ones in
                    % between for numeric predictors and update split /
                    % merge controls
                    if length(obj.SelectedBins) > 1 
                        obj.CtrlsManual.ManualButtons(1).Enabled = false;
                        obj.CtrlsManual.ManualButtons(2).Enabled = true;
                        Clause1 = ismember(Predictor,obj.getNumericPredictors());
                        Clause2 = ~isempty(obj.ZoomedFigure.UserData) && ...
                            any(strcmpi(obj.ZoomedFigure.UserData,...
                            {'ctrlclickon','shiftclickon'}));
                        Clause3 = ismember(Predictor,obj.getCategoricalPredictors());
                        Clause4 = ~isempty(obj.ZoomedFigure.UserData) && ...
                            strcmpi(obj.ZoomedFigure.UserData,'shiftclickon');
                        
                        if any(diff(obj.SelectedBins)>1) && ...
                                ((Clause1 && Clause2) || (Clause3 && Clause4))
                            
                            obj.selectIntermediaryBins(YBins);
                            
                        end

                    else
                        if ismember(Predictor,obj.getNumericPredictors())...
                                || (ismember(Predictor,obj.getCategoricalPredictors()) ...
                                && length(obj.BinMembers) > 1)
                            obj.CtrlsManual.ManualButtons(1).Enabled = true;
                        else
                            obj.CtrlsManual.ManualButtons(1).Enabled = false;
                        end
                    end

                    obj.CtrlsManual.ManualButtons(3).Enabled = true;
                  
                else
                    obj.resetBinControls();
                end
            else
                obj.resetBinControls();
            end
            
        end
        
        function obj = resetBinControls(obj)
            % If the user clicks outside the bins or a bin is selected for
            % the first time. Called by 'obj.selectBin'.
            
            obj.SelectedBins = [];
            obj.CtrlsManual.ManualButtons(1).Enabled = false;
            obj.CtrlsManual.ManualButtons(2).Enabled = false;
            obj.CtrlsManual.ManualButtons(3).Enabled = false;
            
            if ismember(obj.SelectedPredictors{1},obj.getNumericPredictors())
                obj.CtrlsManual.LeftEdge.Text  = '';
                obj.CtrlsManual.RightEdge.Text = '';
                obj.CtrlsManual.LeftEdge.Enabled  = false;
                obj.CtrlsManual.RightEdge.Enabled = false;
            else
                obj.CtrlsManual.Members.Text = '';
            end
            
        end
      
        function obj = highlightBin(obj,hax,BinNumber)
            % Called by 'obj.updateZoomedFigure'. Highlights the bin number
            % 'BinNumber' for manual binning
            
            hBar = risk.internal.app.utils.extractChildren(hax,'bar');
            YDataBad  = hBar(1).YData; 
            YDataGood = hBar(2).YData; 
            x = BinNumber - 0.5*hBar(1).BarWidth;
            y = 0;
            w = hBar(1).BarWidth;
            h = YDataBad(BinNumber) + YDataGood(BinNumber);
            rectangle('Parent',hax(1),'Position',[x y w h],'Linewidth',2,...
                'EdgeColor','blue','Selected','On');
        end
        
        function obj = selectIntermediaryBins(obj,YBins)
            % This method selects the intermediary bins when the user, say 
            % selects bins 1 and 3. Called by 'obj.selectBin'.
            
            iMin = obj.SelectedBins(1);
            iMax = obj.SelectedBins(end);
            iMissingBins = setdiff(iMin:iMax,obj.SelectedBins);
            
            for i = 1 : length(iMissingBins)
                BinNumber = iMissingBins(i);
                x = obj.LeftEdgesXData(BinNumber);
                y = 0;
                w = obj.BarHandles(1).BarWidth;
                h = YBins(BinNumber);
                rectangle('Parent',obj.ZoomedAxes,'Position',...
                    [x y w h],'LineWidth',2,'EdgeColor','blue',...
                    'Selected','On');
            end
            
            obj.SelectedBins = iMin:iMax;
            
        end
                
        function obj = setBinEdgesAndNumber(obj,hax)
            % Defines the left and right edges of the bins in the bar plot,
            % as well as the total number of bins. Useful for methods that
            % need to locate the selected bin for splittinhg or merging.

            hBar = risk.internal.app.utils.extractChildren(hax,'bar');
            if ~isempty(hBar) && all(isvalid(hBar))
                obj.BarHandles = hBar;
                obj.NumBins    = length(hBar(1).XData);
                obj.LeftEdgesXData  = hBar(1).XData - 0.5*hBar(1).BarWidth;
                obj.RightEdgesXData = hBar(1).XData + 0.5*hBar(1).BarWidth;
                if length(obj.SelectedAxes) < 2
                    obj.BinHeights = sum(cell2mat({hBar.YData}'),1);
                end
            end

        end
        
        function obj = setBinEdges(obj,BinNumbers)
            % Called by 'obj.selectBin'. It displays the bin edges of the
            % selected bin, for numeric predictors, on the toolstrip.
            
            Predictor = obj.SelectedPredictors{1};
            [~,cp,mv] = bininfo(obj.Scorecard,Predictor);
                   
            % To avoid displaying "+/-Inf", we use the Min and/or Max
            % values
            if (BinNumbers(1) == 1) 
                if ~obj.isShiftApplied || (obj.isShiftApplied && (obj.isDuplicateCutPoint || obj.isMergeApplied))
                    obj.LeftEdgeValue = mv(1); 
                end
                if (BinNumbers(end) == length(cp)+1)
                    if ~obj.isShiftApplied
                        obj.RightEdgeValue = mv(2);
                    end
                else
                    obj.RightEdgeValue = cp(BinNumbers(end));
                end
                
            elseif (BinNumbers(end) == length(cp)+1)
                obj.LeftEdgeValue = cp(BinNumbers(1)-1);
                if ~obj.isShiftApplied || (obj.isShiftApplied && obj.isDuplicateCutPoint)
                    obj.RightEdgeValue = mv(2); 
                end
                
            else
                obj.LeftEdgeValue = cp(BinNumbers(1)-1);
                obj.RightEdgeValue = cp(BinNumbers(end));
            end
            
            % Update the bin edge display on the toolstrip
            obj.CtrlsManual.LeftEdge.Text  = num2str(obj.LeftEdgeValue);
            obj.CtrlsManual.RightEdge.Text = num2str(obj.RightEdgeValue);
             
        end
        
        function obj = setBinMembers(obj,BinNumbers)
            % Called by 'obj.selectBin'. It displays the bin members of the
            % selected bin, for categorical predictors, on the toolstrip.
            
            [~,cg] = bininfo(obj.Scorecard,obj.SelectedPredictors{1});

            Ind = zeros(length(cg.BinNumber),1);
            
            % If 'shiftclickon', then use all intermediary bins. Otherwise,
            % only the selected bins ('ctrlclickon')
            if ~isempty(obj.ZoomedFigure) && (isa(obj.ZoomedFigure,'handle') && isvalid(obj.ZoomedFigure))% First click on "Manual Binning"
                if strcmpi(obj.ZoomedFigure.UserData,'shiftclickon') 
                    Ind = BinNumbers(1):BinNumbers(end);
                else
                    for i = 1 : length(BinNumbers)
                        Ind = xor(Ind,(cg.BinNumber == BinNumbers(i)));
                    end
                end
            else
                for i = 1 : length(BinNumbers)
                    Ind = xor(Ind,(cg.BinNumber == BinNumbers(i)));
                end
            end
            obj.BinMembers = cg.Category(Ind);
            
            % Update the bin members display on the toolstrip
            obj.CtrlsManual.Members.Text = obj.formatBinMembersText();
            
            % If the selected bin has only one member, disable the "Split"
            % button
            if length(obj.BinMembers) < 2
                obj.CtrlsManual.ManualButtons(1).Enabled = false;
            end
            
        end
        
        function Text = formatBinMembersText(obj)
            % Called by obj.setBinMembers and LocalSplitMergeSection to
            % format the text label for the selected bin's members 
            
            if length(obj.BinMembers) == 1
                Text = obj.BinMembers{:};
            elseif length(obj.BinMembers) == 2
                Text = sprintf('<html>%s<br>%s</html>',obj.BinMembers{1},...
                    obj.BinMembers{2});
            else
                Text = sprintf('<html>%s<br>%s</html>',obj.BinMembers{1},...
                    [obj.BinMembers{2} '...']);
            end
            
        end
        
        function obj = mergeBins(obj,varargin)
            % Callback on the Toolstrip button 'Merge'. It merges the bins
            % in 'obj.SelectedBins' into one. Bins must be adjacent.
            
            Predictor = obj.SelectedPredictors{1};
            
            if ismember(Predictor,obj.getNumericPredictors())
                [~,CutPoints] = bininfo(obj.Scorecard,Predictor);
                CutPoints(obj.SelectedBins(1:end-1)) = [];
                obj.Scorecard = modifybins(obj.Scorecard,Predictor,...
                    'CutPoints',CutPoints);
                
            else
                [~,CatGrouping] = bininfo(obj.Scorecard,Predictor);
                Ind = zeros(length(CatGrouping.BinNumber),1);
                
                for i = 1 : length(obj.SelectedBins)
                    Ind = xor(Ind,(CatGrouping.BinNumber == obj.SelectedBins(i)));
                end
                
                CatGrouping.BinNumber(Ind) = obj.SelectedBins(1);
             
                if any(diff(CatGrouping.BinNumber) < 0) && ...
                    ismember(Predictor,obj.getOrdinalPredictors())
                    w = warning('off','finance:internal:finance:binning:Categorical:NonMonotonicGrouping');
                    c = onCleanup(@() warning(w));
                    uiwait(msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:OrdinalMergeNonAdjacentMsg'),...
                            risk.internal.app.utils.getMessage('risk:binningexplorerapp:OrdinalMergeNonAdjacentTitle'),'warn','modal'));
                end
                obj.Scorecard = modifybins(obj.Scorecard,Predictor,...
                        'CatGrouping',CatGrouping);
                    
            end
            
            obj.isScorecardExported = false;
            
            % Update the zoomed and overview figures
            obj.isMergeApplied = true;
            obj.isPlotChanged = true;
            notify(obj,'SelectedFigureTab'); 
            
            % Update the Bin and Predictor Information tables
            obj.setBinInfo();
            obj.setPredictorInfo();
            
            % Disable the 'Merge' button and enable the 'Split button'
            obj.CtrlsManual.ManualButtons(2).Enabled = false;
            obj.isMergeApplied = false;
            
            % Bring the zoomed fgure to the foreground
            figure(obj.ZoomedFigure)
            
        end
        
        function obj = resetBinEdgesMembersAfterReplot(obj,hax)
            % Called by 'obj.updateZoomedFigure', 'obj.expandView' and 
            % 'obj.mergeBins'. 
            % Selects the first bin and set the bin edges of bin members 
            % after the plot is refreshed.
            
            try
                if strcmpi(obj.SelectedPredictors{1},obj.PreviousPredictor) ||...
                        obj.isMergeApplied || obj.isShiftApplied || obj.isSplitApplied
                    
                    if obj.NumMergedBins >= 0
                        BinNumber = obj.SelectedBins(1);
                    else
                        i1 = obj.SelectedBins(1);
                        i2 = i1 + obj.NumMergedBins;
                        if i2 <= 0
                            BinNumber = 1;
                        else
                            BinNumber = i2 + 1;
                        end
                    end
                    
                else
                    BinNumber = 1;
                end
            catch
                BinNumber = 1;
            end
            
            
            obj.SelectedBins = BinNumber;
            obj.highlightBin(hax,BinNumber);
            obj.ZoomedAxes = hax(1);
            obj.setBinEdgesAndNumber(obj.ZoomedAxes);
            
            if ismember(obj.SelectedPredictors{1},obj.getNumericPredictors())
                obj.setBinEdges(BinNumber);
            else
                obj.setBinMembers(BinNumber);
            end
        end
        
        function obj = shiftCutpoint(obj,varargin)
            % Callback on the left and right edge text fields in the 
            % contextual tab. It shifts the cutpoint when the user enters a
            % new valid value.
            
            src = varargin{1};
            EdgeValue = str2double(src.Text);
            Predictor = obj.SelectedPredictors{1};
            [~,cp,mv] = bininfo(obj.Scorecard,Predictor);
            [~,Stats] = predictorinfo(obj.Scorecard,Predictor);
            
            if strcmpi(src.Name,'leftedge')
                if (obj.SelectedBins == 1)
                    Ind = 1;
                elseif (obj.SelectedBins == obj.NumBins)
                    Ind = obj.SelectedBins;
                else
                    Ind = obj.SelectedBins-1;
                end
            else
                Ind = obj.SelectedBins;
            end
            
            % Step 0: Troubleshoot edge cases
            if isnan(EdgeValue)
                msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:ShiftCutPointsMsg1'),...
                    risk.internal.app.utils.getMessage('risk:binningexplorerapp:ShiftCutPointsTitle'),'warn','modal');
                if strcmpi(src.Name,'leftedge')
                    src.Text = num2str(obj.LeftEdgeValue);
                else
                    src.Text = num2str(obj.RightEdgeValue);
                end
                
                return;
                
            elseif (strcmpi(src.Name,'leftedge') && EdgeValue>obj.RightEdgeValue)...
                    || (strcmpi(src.Name,'rightedge') && EdgeValue<obj.LeftEdgeValue)
                msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:ShiftCutPointsMsg2'),...
                    risk.internal.app.utils.getMessage('risk:binningexplorerapp:ShiftCutPointsTitle'),'warn','modal');
                if strcmpi(src.Name,'leftedge')
                    src.Text = num2str(obj.LeftEdgeValue);
                else
                    src.Text = num2str(obj.RightEdgeValue);
                end
                
                return;
            end
            
            % Step 1: If more than one bin is selected, disable the text boxes
            if length(obj.SelectedBins) > 1
                obj.CtrlsManual.LeftEdge.Enabled  = false;
                obj.CtrlsManual.RightEdge.Enabled = false; 
                return;
            end

            % Step 2: Shift the bin edge
            w = catchWarningAndBin(Ind,src);
            if ~isempty(w)
                c = onCleanup(@() warning(w));
            end
                   
            obj.isScorecardExported = false;
            obj.isShiftApplied = true;
            
            % Step 3: Update the zoomed and overview figures
            obj.isPlotChanged = true;
            notify(obj,'SelectedFigureTab');
            obj.isPlotChanged = false;
            
            % Step 4: Update statistic information
            obj.setBinInfo();
            obj.setPredictorInfo();
            figure(obj.ZoomedFigure)
            
            obj.isShiftApplied = false;
            
            if obj.isMergeApplied 
                obj.isMergeApplied = false;
            end
            obj.NumMergedBins = 0;
            
            function w = catchWarningAndBin(Ind,src)
                % Helper nested function. Returns the status of warnings
                % returned by the creditscorecard, displays the message
                % boxes and re-bins the data given the new rules.
                
                obj.OldCutPoints = cp;
                [w,okDuplicate] = catchWarningsAndUpdateCutPoints();
                
                if (Ind == 1) || (Ind == obj.NumBins)
                    if okDuplicate
                        obj.isDuplicateCutPoint = true;
                    end           
                    if strcmpi(src.Name,'leftedge')
                        obj.LeftEdgeValue = EdgeValue;
                    elseif (strcmpi(src.Name,'rightedge'))
                        obj.RightEdgeValue = EdgeValue;
                    end
                    
                end
                
                function [w,ok1] = catchWarningsAndUpdateCutPoints()
                    % Helper function that catches all warning due to
                    % duplicate cutpoints, non-monotonicity of cutpoints,
                    % and sets MinValue andMaxValue, when appropriate
                    
                    ok1 = false;
                    if Ind == 1
                        Str = {'DuplicateCutPoints','MinAboveData',...
                            'MinAboveCutPoints','MinValue','CutPointsBelowMin'};
                    elseif Ind == obj.NumBins
                        Str = {'DuplicateCutPoints','MaxBelowData',...
                            'MaxBelowCutPoints','MaxValue','CutPointsAboveMax'};
                    else
                        Str = {'DuplicateCutPoints'};
                    end
                    
                    if (Ind == 1) || (Ind == obj.NumBins)
                        [ok1,w1] = isWarningTriggered(Str{1});
                        [ok2,w2] = isWarningTriggered(Str{2});
                        [ok3,w3] = isWarningTriggered(Str{3});
                        ok = [ok1 ok2 ok3];
                        w  = {w1 w2 w3};
                        
                        if all(cellfun(@isempty,w))
                            w = [];
                        else
                            w  = w{ok};
                        end
                        
                        if any(ok)
                            MessageStr = cellfun(@(x) risk.internal.app.utils.getMessage(x),{w.identifier}','UniformOutput',false);
                            uiwait(msgbox(MessageStr,...
                                risk.internal.app.utils.getMessage(...
                                ['risk:binningexplorerapp:' Str{4} 'TruncatedDataTitle']),...
                                'warn','modal'));
                        end
                        
                        if ((Ind == 1) && strcmpi(src.Name,'leftedge')) || ...
                                ((Ind == obj.NumBins) && strcmpi(src.Name,'rightedge'))
                            
                                obj.Scorecard = modifybins(obj.Scorecard,Predictor,Str{4},EdgeValue);
                                
                        elseif ((Ind == 1) && strcmpi(src.Name,'rightedge')) || ...
                                ((Ind == obj.NumBins) && strcmpi(src.Name,'leftedge'))
                            
                            if Ind ==1
                                cp(Ind) = EdgeValue;
                            else
                                cp(Ind-1) = EdgeValue; 
                            end
                            [ok4,w4] = isWarningTriggered(Str{5});
                            if ok4
                                MessageStr = cellfun(@(x) risk.internal.app.utils.getMessage(x),{w4.identifier}','UniformOutput',false);
                                uiwait(msgbox(MessageStr,...
                                    risk.internal.app.utils.getMessage(...
                                    ['risk:binningexplorerapp:' Str{4} 'TruncatedDataTitle']),...
                                    'warn','modal'));
                            end

                            [cp,isExit] = testForMonotonicity(cp);

                            if isExit
                                return;
                            else
                                obj.Scorecard = modifybins(obj.Scorecard,Predictor,'CutPoints',cp);
                            end
                            w = {w1 w2 w3 w4};
                            if all(cellfun(@isempty,w))
                                w = [];
                            else
                                w  = w{[ok ok4]};
                            end

                        end
                    else
                        [ok,w]  = isWarningTriggered(Str{1});
                        cp(Ind) = EdgeValue;
                        if ok
                            uiwait(msgbox(risk.internal.app.utils.getMessage(w.identifier),...
                                risk.internal.app.utils.getMessage('risk:binningexplorerapp:DuplicateCutPointsTitle'),'warn','modal'));
                            obj.isDuplicateCutPoint = true;
                        end
                        
                        [cp,isExit] = testForMonotonicity(cp);
                        if isExit
                            return;
                        else
                            obj.Scorecard = modifybins(obj.Scorecard,Predictor,'CutPoints',cp);
                        end

                    end
                 
                end
                
                function [cpOut,isExit] = testForMonotonicity(cpIn)
                    isExit = false;
                    if any(diff(cpIn)<0)
                        Choice = questdlg(risk.internal.app.utils.getMessage('risk:binningexplorerapp:EditEdgeValueMsg'),...
                            risk.internal.app.utils.getMessage('risk:binningexplorerapp:EditEdgeValueTitle'),...
                            risk.internal.app.utils.getMessage('risk:binningexplorerapp:Yes'),...
                            risk.internal.app.utils.getMessage('risk:binningexplorerapp:No'),...
                            risk.internal.app.utils.getMessage('risk:binningexplorerapp:Yes'));
                        
                        switch lower(Choice)
                            case 'yes'
                                if strcmpi(src.Name,'rightedge')
                                    Ind1 = (obj.OldCutPoints > EdgeValue);
                                    if Ind == obj.NumBins
                                        Ind2 = [];
                                    else
                                        Ind2 = Ind + find(~Ind1(Ind+1:end));
                                    end
                                    Sign = 1; % Merge to the right
                                else
                                    Ind1 = (cpIn >= EdgeValue);
                                    if Ind == 1
                                        Ind2 = [];
                                    elseif Ind == obj.NumBins
                                        Ind2 = find(Ind1(1:Ind-2));
                                    else
                                        Ind2 = find(Ind1(1:Ind-1));
                                    end
                                    Sign = -1; % Merge to the left
                                end
                                cpIn(Ind2) = [];
                                cpIn = cpIn(isfinite(cpIn));
                                obj.isMergeApplied = true;
                                cpOut = cpIn;
                                obj.NumMergedBins  = Sign*abs(length(obj.OldCutPoints)-length(cpIn)+1); 
                            case 'no'
                                cpOut = cpIn;
                                obj.NumMergedBins  = 0;
                                isExit = true;
                                return;
                        end
                    else
                        cpOut = cpIn;
                    end
                end
    
            end
            
            function [Flag,w] = isWarningTriggered(Str)
                % Helper function to test for the keyword given by "Str"
                % 'DuplicateCutPoints' : test for duplicate cutpoints
                % 'MinAboveData'       : test for Min above data
                % 'MaxBelowData'       : test for Max below data
                % 'MinAboveCutPoints'  : test for cutpoint below the Min
                % 'MaxBelowCutPoints'  : test for cutpoint above the Max
                %
                % If 'Flag' is true, then the warning for the message identifier
                % below is removed from the command line, and instead, 
                % displayed in a message box.
                
                Flag = false;
                Msg  = 'finance:internal:finance:binning:Numeric:';
                switch lower(Str)
                    case 'duplicatecutpoints'
                        if ismember(EdgeValue,cp) || ...
                                ((Ind == obj.NumBins) && ...
                                (EdgeValue == obj.RightEdgeValue) && ...
                                strcmpi(src.Name,'leftedge')) || ...
                                ((Ind == 1) && ...
                                (EdgeValue == obj.LeftEdgeValue) && ...
                                strcmpi(src.Name,'rightedge'))
                            w = warning('off',[Msg 'DuplicateCutPoints']);
                            Flag = true;
                        else
                            w = [];
                        end
                    case 'minabovedata'
                        if (EdgeValue > max(mv(1),Stats.Value(1))) && strcmpi(src.Name,'leftedge')
                            w = warning('off',[Msg 'MinAboveData']);
                            Flag = true;
                        else
                            w = [];
                        end
                    case 'maxbelowdata'
                        if (EdgeValue < min(mv(2),Stats.Value(2))) && strcmpi(src.Name,'rightedge')
                            w = warning('off',[Msg 'MaxBelowData']);
                            Flag = true;
                        else 
                            w = [];
                        end
                    case 'minabovecutpoints'
                        if (EdgeValue >= min(cp)) && strcmpi(src.Name,'leftedge')
                            w = warning('off',[Msg 'MinAboveCutPoints']);
                            Flag = true;
                        else
                            w = [];
                        end
                    case 'maxbelowcutpoints'
                        if (EdgeValue < max(cp)) && strcmpi(src.Name,'rightedge')
                            w = warning('off',[Msg 'MaxBelowCutPoints']);
                            Flag = true;
                        else
                            w = [];
                        end
                    case 'cutpointsbelowmin'
                        if (max(mv(1),Stats.Value(1)) >= min(cp))% && strcmpi(src.Name,'leftedge')
                            w = warning('off',[Msg 'CutPointsBelowMin']);
                            Flag = true;
                        else
                            w = [];
                        end
                    case 'cutpointsabovemax'                        
                        if (min(mv(2),Stats.Value(2)) <= max(cp)) %&& strcmpi(src.Name,'rightedge')
                            w = warning('off',[Msg 'CutPointsAboveMax']);
                            Flag = true;
                        else
                            w = [];
                        end
                end
            end
                        
        end
        
        function obj = splitBin(obj,varargin)
            % Callback on the Toolstrip button 'Split'. It splits the bin
            % in 'obj.SelectedBins' according to rules set interactively by 
            % the user.
            
            Predictor = obj.SelectedPredictors{1};
            
            % Bring up the interface
            if ismember(Predictor,obj.getNumericPredictors())
                obj.splitNumericUI();
            else
                obj.splitCategoricalUI();
            end
            
        end
        
        function obj = splitNumericUI(obj)
            % Called by 'obj.splitBin'. Open a UI interface to allow the
            % user to split the selected bin into smaller bins. For numeric
            % predictors only.

            % Make sure the visibility is on
            set(groot,'DefaultFigureVisible','On');
            
            InitNumBins = 2;
            obj.SplitBinLine = gobjects(obj.MaxNumBinsSplit,1);
            obj.CutPointBox  = gobjects(obj.MaxNumBinsSplit,1);
            Predictor = obj.SelectedPredictors{1};
           
            obj.SplitFigure = figure('Name',risk.internal.app.utils.getMessage('risk:binningexplorerapp:Split'),...
                'IntegerHandle','Off','HandleVisibility','Off','Units',...
                'characters','MenuBar','None','Position',[84 17 80 35],...
                'CloseRequestFcn',@obj.closeSplitWindow,'Resize','Off',...
                'Tag','splitfigure','WindowStyle','modal');
            
            movegui(obj.SplitFigure,'center');
            
            hp = uipanel('Parent',obj.SplitFigure,'Units','Normalized',...
                'Position',[0 0 1 1]);
            
            uicontrol('Style','pushbutton','Parent',hp,'Units',...
                'characters','String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:OK'),...
                'Position',[57 1 10 2],'Tag','OK','Callback',@obj.okSplitCallback);
            
            uicontrol('Style','pushbutton','Parent',hp,'Units',...
                'characters','String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:Cancel'),...
                'Position',[68 1 10 2],'Tag','Cancel','Callback',@obj.cancelSplitCallback);
            
            % Retrieve the data from the selected bin
            if isa(obj.OriginalData,'table')
                Data = obj.OriginalData.(Predictor);
            else
                Data = obj.OriginalData.Data.(Predictor);
            end
            
            % Predictor was converted from Categorical to Numeric
            if ismember(Predictor,obj.ModifiedPredictors)    
                Data = double(categorical(Data));    
            end
            
            Data = Data(Data >= obj.LeftEdgeValue & Data < obj.RightEdgeValue);
            
            % Properly display bins and tick labels
            hax = axes('Parent',hp,'Visible','Off','Units','characters',...
                'Position',[12 7 60 13]);
            hHist = histogram(hax,Data);
            drawnow;

            % Plot slider line to hold cutpoint values
            hax.XLimMode = 'manual';
            hax.YLimMode = 'manual';
            set(hax,'Clipping','off')
            hax.XLabel.String = '';
            hax.YLabel.String = risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinCount');
            
            hax.XTick = setdiff(hax.XTick,hHist.BinEdges,'stable');
            
            text('Parent',hax,'Units','characters','Position',...
                [-9 18],'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:CutPoints'),...
                'FontWeight','Demi');
            text('Parent',hax,'Units','characters','Position',...
                [-4 15],'String',num2str(obj.LeftEdgeValue),'EdgeColor',...
                'k','BackgroundColor',[0.8 0.8 0.8]);
            text('Parent',hax,'Units','characters','Position',...
                [61 15],'String',num2str(obj.RightEdgeValue),...
                'EdgeColor','k','BackgroundColor',[0.8 0.8 0.8]);

            XPos = hHist.BinEdges(floor(hHist.NumBins/2)+1);
            NumWidth = max(2,floor(log10(XPos)));
            obj.SplitCutPoints  = ceil(XPos);
            obj.SplitBinLine(1) = line([XPos XPos],...
                [0 hax.YLim(2)],'LineStyle','--','Color',[0 0 0],...
                'Parent',hax,'Tag','splitline1');    
            
            obj.CutPointBox(1) = uicontrol('Style','Edit','Parent',hp,...
                'Units','characters','Position',[obj.SplitFigure.Position(3)/2-NumWidth 21 2*NumWidth 2],...
                'String',num2str(ceil(XPos)),'Tag','cutpoint1',...
                'Callback',{@(src,ev)obj.slideSplitLine(src,ev,hax)});
            
            % Add patch to visually distinguish between new bins
            patch([hax.XLim(1) XPos XPos hax.XLim(1)],...
                [0 0 hax.YLim(2) hax.YLim(2)],obj.PatchColor,...
                'Parent',hax,'FaceAlpha',0.1,'EdgeColor','none');
            
            patch([XPos hax.XLim(2) hax.XLim(2) XPos],...
                [0 0 hax.YLim(2) hax.YLim(2)],obj.PatchColor,...
                'Parent',hax,'FaceAlpha',0.2,'EdgeColor','none');

            uistack(hHist,'top');
            
            % Add the title and center it
            ht = uicontrol('Style','text','String',...
                risk.internal.app.utils.getMessage('risk:binningexplorerapp:SplitNumericTitle',...
                num2str(obj.SelectedBins),Predictor),'Parent',hp,'Units',...
                'characters','Position',[10 31.5 80 2],'FontWeight','Bold');
            
            ht.Position(3) = ht.Extent(3);
            ht.Position(1) = hax.Position(1) +(hax.Position(3)-ht.Position(3))/2;
            
            % Add/subtract bin controls
            uicontrol('Style','text','String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:NumberOfBins'),...
                'Parent',hp,'Units','characters','Position',[-3 27 30 2],...
                'FontWeight','Bold');
            
            hEdit = uicontrol('Style','edit','Parent',hp,'Units',...
                'characters','Position',[27 28 5 1],'String',...
                num2str(InitNumBins),'Tag','numbinsedit','Callback',...
                {@(src,ev)obj.addOrRemoveBinForSplitNumeric(src,ev,hax,hHist)});
            
            pathstr    = fileparts(mfilename('fullpath'));
            AddIcon    = fullfile(pathstr,'icons','AddItem.png');
            RemoveIcon = fullfile(pathstr,'icons','RemoveItem.png');

            uicontrol('Style','pushbutton','Parent',hp,'Units',...
                'characters','Position',[25 28 2 1],'tag','removebin','CData',...
                double(imread(RemoveIcon,'BackgroundColor',[0.9 0.9 0.9]))/255,...
                'Callback',{@(src,ev) obj.addOrRemoveBinForSplitNumeric(src,ev,hEdit,hax,hHist)});
            
            uicontrol('Style','pushbutton','Parent',hp,'Units',...
                'characters','Position',[32 28 2 1],'tag','addbin','CData',...
                double(imread(AddIcon,'BackgroundColor',[0.9 0.9 0.9]))/255,...
                'Callback',{@(src,ev) obj.addOrRemoveBinForSplitNumeric(src,ev,hEdit,hax,hHist)});
            
        end
        
        function obj = splitCategoricalUI(obj)
            % Called by 'obj.splitBin'. Open a UI interface to allow the
            % user to split the selected bin into smaller bins. For
            % categorical predictors only.
            
            % Make sure the visibility is on
            set(groot,'DefaultFigureVisible','On');
            
            InitNumBins = 2;
            obj.SplitBinLine = gobjects(obj.MaxNumBinsSplit,1);
            obj.CutPointBox  = gobjects(obj.MaxNumBinsSplit,1);
            Predictor = obj.SelectedPredictors{1};
           
            obj.SplitFigure = figure('Name',risk.internal.app.utils.getMessage('risk:binningexplorerapp:Split'),...
                'IntegerHandle','Off','HandleVisibility','Off','Units',...
                'characters','MenuBar','None','Position',[84 17 80 35],...
                'CloseRequestFcn',@obj.closeSplitWindow,'Resize','Off',...
                'Tag','splitfigure','WindowStyle','modal');
            
            movegui(obj.SplitFigure,'center');
            
            hp = uipanel('Parent',obj.SplitFigure,'Units','Normalized','Position',...
                [0 0 1 1]);
            
            uicontrol('Style','pushbutton','Parent',hp,'Units',...
                'characters','String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:OK'),...
                'Position',[57 0.5 10 2],'Tag','OK','Callback',@obj.okSplitCallback);
            
            uicontrol('Style','pushbutton','Parent',hp,'Units',...
                'characters','String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:Cancel'),...
                'Position',[68 0.5 10 2],'Tag','Cancel','Callback',@obj.cancelSplitCallback);
            
            % Retrieve the data from the selected bin
            [~,Info] = predictorinfo(obj.Scorecard,Predictor);
            [~,Ind]  = ismember(obj.BinMembers,Info.Properties.RowNames);
            Counts   = Info.Count(Ind);
            Data     = cell(sum(Counts),1);
            
            for i = 1 : length(Ind)
                if i == 1
                    i1 = 1;
                    i2 = Counts(i);
                else
                    i1 = sum(Counts(1:i-1))+1;
                    i2 = i1 - 1 + Counts(i);
                end
                
                Data(i1:i2,:) = repmat(Info.Properties.RowNames(Ind(i)),Counts(i),1);
            end

            % Properly display bins and tick labels
            hax = axes('Parent',hp,'Visible','Off','Units','characters',...
                'Position',[15 16 60 11]);
            hHist = histogram(hax,categorical(Data),obj.BinMembers);
            drawnow;

            hax.YLabel.String = risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinCount');
            
            XPos = floor(length(obj.BinMembers)/2)+0.5;
            obj.SplitBinLine(1) = line([XPos XPos],...
                [0 hax.YLim(2)],'LineStyle','--','Color',[0 0 0],...
                'Parent',hax,'Tag','splitline1');
            
            % Add patch to visually distinguish between new bins
            patch([hax.XLim(1) XPos XPos hax.XLim(1)],...
                [0 0 hax.YLim(2) hax.YLim(2)],obj.PatchColor,...
                'Parent',hax,'FaceAlpha',0.1,'EdgeColor','none');
            
            patch([XPos hax.XLim(2) hax.XLim(2) XPos],...
                [0 0 hax.YLim(2) hax.YLim(2)],obj.PatchColor,...
                'Parent',hax,'FaceAlpha',0.2,'EdgeColor','none');

            uistack(hHist,'top');
            
            % Add the title and center it
            ht = uicontrol('Style','text','String',...
                risk.internal.app.utils.getMessage('risk:binningexplorerapp:SplitCategoricalTitle',...
                num2str(obj.SelectedBins),Predictor),'Parent',hp,'Units',...
                'characters','Position',[10 31.5 80 2],'FontWeight','Bold');
            
            ht.Position(3) = ht.Extent(3);
            ht.Position(1) = hax.Position(1) +(hax.Position(3)-ht.Position(3))/2;
            
            % Add/subtract bin controls
            uicontrol('Style','text','String',...
                risk.internal.app.utils.getMessage('risk:binningexplorerapp:NumberOfBins'),...
                'Parent',hp,'Units','characters','Position',[0 29.5 30 2],...
                'FontWeight','Bold');
            
            hEdit = uicontrol('Style','edit','Parent',hp,'Units',...
                'characters','Position',[30 30.5 5 1],'String',...
                num2str(InitNumBins),'Tag','numbinsedit','Callback',...
                {@(src,ev)obj.addOrRemoveBinForSplitCategorical(src,ev,hax,hHist)});
            
            pathstr    = fileparts(mfilename('fullpath'));
            AddIcon    = fullfile(pathstr,'icons','AddItem.png');
            RemoveIcon = fullfile(pathstr,'icons','RemoveItem.png');

            uicontrol('Style','pushbutton','Parent',hp,'Units',...
                'characters','Position',[28 30.5 2 1],'tag','removebin','CData',...
                double(imread(RemoveIcon,'BackgroundColor',[0.9 0.9 0.9]))/255,...
                'Callback',{@(src,ev) obj.addOrRemoveBinForSplitCategorical(src,ev,hEdit,hax,hHist)});
            
            uicontrol('Style','pushbutton','Parent',hp,'Units',...
                'characters','Position',[35 30.5 2 1],'tag','addbin','CData',...
                double(imread(AddIcon,'BackgroundColor',[0.9 0.9 0.9]))/255,...
                'Callback',{@(src,ev) obj.addOrRemoveBinForSplitCategorical(src,ev,hEdit,hax,hHist)});
            
            % Bin listboxes
            uicontrol('Style','Text','Parent',hp,'Units','characters',...
                'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinNumber','1'),...
                'Position',[22 13 7 1],'fontweight','bold','Tag','bottombintext1');
            
            uicontrol('Style','Text','Parent',hp,'Units','characters',...
                'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinNumber','2'),...
                'Position',[52 13 7 1],'fontweight','bold','Tag','bottombintext2');
            
            uicontrol('Style','Text','Parent',hp,'Units','characters',...
                'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinNumber','1'),...
                'Position',[22 27 7 2],'fontweight','bold','Tag','topbintext1');
            
            uicontrol('Style','Text','Parent',hp,'Units','characters',...
                'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinNumber','2'),...
                'Position',[52 27 7 2],'fontweight','bold','Tag','topbintext2');
            
            hc1 = uicontrol('Style','listbox','Parent',hp,'String',...
                obj.BinMembers(1:floor(XPos)),'Units','characters',...
                'Position',[17 5 17 6],'Max',floor(XPos),'Tag','listbox1',...
                'Callback',@obj.storeSelectedCategories);
            
            hc2 = uicontrol('Style','listbox','Parent',hp,'String',...
                obj.BinMembers(ceil(XPos):end),'Units','characters',...
                'Position',[55 5 17 6],'Max',length(obj.BinMembers)-...
                floor(XPos),'Tag','listbox2','Callback',@obj.storeSelectedCategories);
            
            % Store bin members 
            obj.SplitCategories = cell(2,2);
            obj.SplitCategories(1,:) = {obj.BinMembers(1:floor(XPos)),1};
            obj.SplitCategories(2,:) = {obj.BinMembers(ceil(XPos):end),2};
            
            % Move between adjacent bins (arrows)
            uicontrol('Style','pushbutton','Parent',hp,'String','>',...
                'Units','characters','Position',[42 8 5 1],'Callback',...
                {@(src,ev)obj.moveFromBinToBin(src,ev,hc1,hc2,hHist,hEdit)},...
                'Tag','pushbutton1');
            
            uicontrol('Style','pushbutton','Parent',hp,'String','<',...
                'Units','characters','Position',[42 6.5 5 1],'Callback',...
                {@(src,ev)obj.moveFromBinToBin(src,ev,hc2,hc1,hHist,hEdit)},...
                'Tag','pushbutton2');
            
            % Select all & checkbox
            uicontrol('Style','checkbox','Parent',hp,'Units','characters',...
                'Position',[17 11.5 15 1],'String',...
                risk.internal.app.utils.getMessage('risk:binningexplorerapp:SelectAll'),'Tag',...
                'checkbox1','Callback',{@(src,ev)obj.selectAllBinMembers(src,ev,hc1)});
            
            uicontrol('Style','checkbox','Parent',hp,'Units','characters',...
                'Position',[55 11.5 15 1],'String',...
                risk.internal.app.utils.getMessage('risk:binningexplorerapp:SelectAll'),'Tag',...
                'checkbox2','Callback',{@(src,ev)obj.selectAllBinMembers(src,ev,hc2)});
            
        end
        
        function obj = addOrRemoveBinForSplitNumeric(obj,varargin)
            % Callback on the "plus" and "minus" pushbutton, as well as the 
            % adjacent edit box. Each click on "plus" increases by one unit 
            % the value displayed in the edit box and each clink on "minus" 
            % decreases it by one. Maximum value is that corresponding to 
            % the total number of categories or discrete numeric value (or
            % 5). Minimum value is 2.
            
             [hax,~,~,nBins] = obj.addOrRemoveBinsHelper(varargin{:});
            
            % Remove, from right, a split line and cutpoint
            delete(obj.SplitBinLine(:))
            delete(obj.CutPointBox(:))
            delete(risk.internal.app.utils.extractChildren(hax,'patch'));

            XPos = zeros(nBins-1,1);
            obj.SplitCutPoints = zeros(nBins-1,1);

            if ~isfinite(obj.LeftEdgeValue)
                [~,T] = predictorinfo(obj.Scorecard,obj.SelectedPredictors{1});
                obj.LeftEdgeValue = T.Value('Min');
            elseif ~isfinite(obj.RightEdgeValue)
                [~,T] = predictorinfo(obj.Scorecard,obj.SelectedPredictors{1});
                obj.RightEdgeValue = T.Value('Max');
            end
            
            for i = 1 : (nBins-1)
                XPos(i) = obj.LeftEdgeValue + ...
                    i*(obj.RightEdgeValue-obj.LeftEdgeValue-1)/nBins; 

                NumWidth = max(2,floor(log10(XPos(i))));
                if mod(XPos(i),1) == 0
                    cpText = num2str(XPos(i)+1);
                else
                    cpText = num2str(ceil(XPos(i)));
                end
                
                obj.SplitCutPoints(i) = str2double(cpText);

                obj.SplitBinLine(i) = line(floor([XPos(i) XPos(i)])+0.5,...
                    [0 hax.YLim(2)],'LineStyle','--','Color',[0 0 0],...
                    'Parent',hax,'tag',['splitline' num2str(i)]);
            
                obj.CutPointBox(i) = uicontrol('Style','Edit','Parent',...
                    hax.Parent,'Units','characters','Position',...
                    [hax.Position(1)+i/nBins*hax.Position(3)-NumWidth ...
                    21 2*NumWidth 2],'String',cpText,'Tag',['cutpoint' ...
                    num2str(i)],'Callback',{@(src,ev)obj.slideSplitLine(src,ev,hax)});

                % Face alpha for patches
                if mod(i,2) == 1
                    fa = 0.1;
                else
                    fa = 0.2;
                end
                
                if i == 1
                    patch([hax.XLim(1) floor(XPos(i))+0.5 floor(XPos(i))+0.5 hax.XLim(1)],...
                        [0 0 hax.YLim(2) hax.YLim(2)],obj.PatchColor,'Parent',...
                        hax,'FaceAlpha',fa,'EdgeColor','none');
                else
                    patch([floor(XPos(i-1)) floor(XPos(i)) floor(XPos(i)) ...
                        floor(XPos(i-1))]+0.5,[0 0 hax.YLim(2) hax.YLim(2)],...
                        obj.PatchColor,'Parent',hax,'FaceAlpha',fa,...
                        'EdgeColor','none');
                end

            end

            patch([floor(XPos(nBins-1))+0.5 hax.XLim(2) hax.XLim(2)...
                floor(XPos(nBins-1))+0.5],[0 0 hax.YLim(2) ...
                hax.YLim(2)],obj.PatchColor,'Parent',hax,...
                'FaceAlpha',abs(0.2-fa)+0.1,'EdgeColor','none');
            
        end
        
        function obj = addOrRemoveBinForSplitCategorical(obj,varargin)
            % Callback on the "plus" and "minus" pushbutton, as well as the
            % adjacent edit box. Each click on "plus" increases by one unit
            % the value displayed in the edit box and each clink on "minus"
            % decreases it by one. Maximum value is that corresponding to
            % the total number of categories or discrete numeric value (or
            % 5). Minimum value is 2.
            
            [hax,hHist,hEdit,nBins] = obj.addOrRemoveBinsHelper(varargin{:});
            
            % Remove, from right, a split line and cutpoint
            hf = obj.SplitFigure;
            hp = risk.internal.app.utils.extractChildren(hf,'uipanel');

            delete(obj.SplitBinLine(:))
            
            delete(risk.internal.app.utils.extractChildren(hax,'patch'));
            delete(risk.internal.app.utils.extractChildren(hp,'uicontrol','Tag','listbox'));
            delete(risk.internal.app.utils.extractChildren(hp,'uicontrol','Tag','bintext'));
            delete(risk.internal.app.utils.extractChildren(hp,'uicontrol','Tag','pushbutton'));
            delete(risk.internal.app.utils.extractChildren(hp,'uicontrol','Tag','checkbox'));
            
            XPos = zeros(nBins-1,1);
            hc   = cell(nBins,1);
            obj.SplitCategories = cell(nBins,2);
            AvgNumBins = floor(length(hHist.Categories)/nBins);
            
            % Update the bin listing in the popupmenu
            obj.DefaultBinListing = cell(nBins+1,1);
            obj.DefaultBinListing{1} = {''};
            
            for i = 1 : nBins
                obj.DefaultBinListing{i+1} = risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinNumber',num2str(i));
            end
            
            x0 = hax.Position(1);
            y  = 4;
            w  = hax.Position(3)/(2*nBins-1);
            h  = 6;
            
            for i = 1 : (nBins-1)
                XPos(i) = i*AvgNumBins+0.5;
                obj.SplitBinLine(i) = line([XPos(i) XPos(i)],...
                    [0 hax.YLim(2)],'LineStyle','--','Color',[0 0 0],...
                    'Parent',hax,'tag',['splitline' num2str(i)]);
                
                if mod(i,2) == 1
                    fa = 0.1;
                else
                    fa = 0.2;
                end
                
                if i == 1
                    Members = hHist.Categories(1:floor(XPos(i)));
                    patch([hax.XLim(1) XPos(i) XPos(i) hax.XLim(1)],...
                        [0 0 hax.YLim(2) hax.YLim(2)],obj.PatchColor,'Parent',...
                        hax,'FaceAlpha',fa,'EdgeColor','none');
                else
                    Members = hHist.Categories(floor(XPos(i-1)+1:XPos(i)));
                    patch([XPos(i-1) XPos(i) XPos(i) XPos(i-1)],...
                        [0 0 hax.YLim(2) hax.YLim(2)],...
                        obj.PatchColor,'Parent',hax,'FaceAlpha',fa,...
                        'EdgeColor','none');
                end
                
                % Place bin number on top of the histogram. Transform XLim
                % units to character units
                xtxt = obj.convertXLimToCharacter(i,XPos,x0,hax,true);
     
                uicontrol('Style','Text','Parent',hp,'Units','characters',...
                    'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinNumber',...
                    num2str(i)),'Position',[xtxt 27 7 1],'Tag',...
                    ['topbintext' num2str(i)],'fontweight','bold');
                
                % Create listboxes and checkboxes 
                hc{i} = uicontrol('Style','listbox','Parent',hp,'String',...
                    Members,'Units','characters','Position',[x0+2*(i-1)*w y w h],...
                    'Max',length(Members),'Tag',['listbox' num2str(i)],...
                    'Callback',@obj.storeSelectedCategories);
                
                uicontrol('Style','checkbox','Parent',hp,'Units','characters',...
                    'Position',[x0+2*(i-1)*w y+h+0.5 15 1],'Tag',...
                    ['checkbox' num2str(i)],'String',....
                    risk.internal.app.utils.getMessage('risk:binningexplorerapp:SelectAll'),'Callback',...
                    {@(src,ev)obj.selectAllBinMembers(src,ev,hc{i})});
                
                uicontrol('Style','Text','Parent',hp,'Units','characters',...
                    'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinNumber',...
                    num2str(i)),'Position',[xtxt y+h+3 7 1],'Tag',...
                    'bottombintext','fontweight','bold');

                % Store the members and their corresponding bin number
                obj.SplitCategories(i,:) = {Members,i};

            end
            
            Members = hHist.Categories(floor(XPos(nBins-1))+1:end);
            
            obj.SplitCategories(nBins,:) = {Members,nBins};
            
            patch([XPos(nBins-1) hax.XLim(2) hax.XLim(2) XPos(nBins-1)],...
                [0 0 hax.YLim(2) hax.YLim(2)],obj.PatchColor,'Parent',hax,...
                'FaceAlpha',abs(0.2-fa)+0.1,'EdgeColor','none');
            
            hc{nBins} = uicontrol('Style','listbox','Parent',hp,'String',...
                Members,'Units','characters','Position',[x0+2*(nBins-1)*w y w h],...
                'Max',length(Members),'Tag',['listbox' num2str(nBins)],...
                'Callback',@obj.storeSelectedCategories);
            
            uicontrol('Style','checkbox','Parent',hp,'Units','characters',...
                'Position',[x0+2*(nBins-1)*w y+h+0.5 15 1],'Tag',...
                ['checkbox' num2str(nBins)],'String',...
                risk.internal.app.utils.getMessage('risk:binningexplorerapp:SelectAll'),'Callback',...
                {@(src,ev)obj.selectAllBinMembers(src,ev,hc{nBins})});
            
            % Bin title: displayed twice
            xtxt = obj.convertXLimToCharacter(nBins,XPos,x0,hax,false);
            
            uicontrol('Style','Text','Parent',hp,'Units','characters',...
                'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinNumber',...
                num2str(nBins)),'Position',[xtxt y+h+3 7 1],'Tag',...
                'bottombintext','fontweight','bold');
            
            uicontrol('Style','Text','Parent',hp,'Units','characters',...
                'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinNumber',...
                num2str(nBins)),'Position',[xtxt 27 7 1],'Tag',...
                ['topbintext' num2str(nBins)],'fontweight','bold');
            
            % Create pushbuttons for interbin member transfer
            for i = 1 : (nBins - 1)
                Pos1 = [x0+(2*i-1)*w+(w-5)/2 7 5 1];
                Pos2 = [x0+(2*i-1)*w+(w-5)/2 5.5 5 1];
                uicontrol('Style','pushbutton','Parent',hp,'String','>',...
                    'Units','characters','Position',Pos1,'Callback',...
                    {@(src,ev)obj.moveFromBinToBin(src,ev,hc{i},hc{i+1},hHist,hEdit)},...
                    'Tag',['pushbutton' num2str(i) '.1']);
                
                uicontrol('Style','pushbutton','Parent',hp,'String','<',...
                    'Units','characters','Position',Pos2,'Callback',...
                    {@(src,ev)obj.moveFromBinToBin(src,ev,hc{i+1},hc{i},hHist,hEdit)},...
                    'Tag',['pushbutton' num2str(i) '.2']);
            end
            
            obj.storeSelectedCategories(hc{1},[]);
            
        end
        
        function [hax,hHist,hEdit,nBins] = addOrRemoveBinsHelper(obj,varargin)
            % Helper function called by 'addOrRemoveBinForSplitCategorical'
            % and 'addOrRemoveBinForSplitNumeric'
            
            src = varargin{1};

            if nargin == 6
                hEdit = varargin{3};
                hax = varargin{4};
            else
                hEdit = src;
                hax = varargin{3};
            end
            
            hHist = varargin{end};
            nBins = str2double(hEdit.String);
            if ismember('numbins',lower(properties(hHist))) % Numeric
                N = hHist.NumBins;
            else
                N = length(hHist.Categories); % Categorical
            end
                
            if strcmpi(src.Tag,'removebin')
                if (nBins > 2 )
                    nBins = nBins - 1;
                    obj.OldNumBins = nBins;
                else
                    return;
                end
            elseif strcmpi(src.Tag,'addbin')
                if (nBins < obj.MaxNumBinsSplit + 1) && (nBins+1<= N)
                    nBins = nBins + 1;
                    obj.OldNumBins = nBins;
                else
                    return;
                end
            else
                if nBins > obj.MaxNumBinsSplit + 1
                    msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:MaxNumBinsSplitMsg'),...
                        risk.internal.app.utils.getMessage('risk:binningexplorerapp:MaxNumBinsSplitTitle'),'warn','modal');
                    nBins = obj.OldNumBins;
                elseif nBins < 2
                    msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:MinNumBinsSplitMsg'),...
                        risk.internal.app.utils.getMessage('risk:binningexplorerapp:MinNumBinsSplitTitle'),'warn','modal');
                    nBins = obj.OldNumBins;
                elseif mod(nBins,1) ~= 0
                    msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:InvalidNumBinsSplitMsg'),...
                        risk.internal.app.utils.getMessage('risk:binningexplorerapp:InvalidNumBinsSplitTitle'),'warn','modal');
                    nBins = obj.OldNumBins;
                elseif nBins > N%~ismember('numbins',lower(properties(hHist))) && nBins > length(hHist.Categories)
                    msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:InvalidNumBinsSplitCategoricalMsg'),...
                        risk.internal.app.utils.getMessage('risk:binningexplorerapp:InvalidNumBinsSplitTitle'),'warn','modal');
                    nBins = obj.OldNumBins;
                end
                    
            end
            
            hEdit.String = num2str(nBins);
            obj.OldNumBins = nBins;
            
        end
    
        function obj = slideSplitLine(obj,src,~,hax)
            % Callback on the cutpoint edit boxes. When the user enters a
            % new value, the split line is shifted accordingly
            
            obj.InvalidCutPoint = false;
            Tag   = src.Tag;
            Num   = str2double(Tag(length('cutpoint')+1:end));
            Value = str2double(src.String);
            BinWidth = 0.5;
            
            % Extract non empty graphic objects in obj.SplitBinLine
            Ind1 = arrayfun(@(c)~isempty(fieldnames(c)),obj.SplitBinLine);
            Ind2 = isvalid(obj.SplitBinLine);
            hLines = obj.SplitBinLine(Ind1&Ind2);
            
            % Make sure the edited cutpoint is valid
            TempInd = obj.SplitCutPoints;
            TempInd(Num) = Value;
            
            % NaN or Inf cutpoints
            if any(isnan(TempInd)) || any(~isfinite(TempInd))
                obj.InvalidCutPoint = true;
                msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:InfiniteNaNCutPointsMsg'),...
                    risk.internal.app.utils.getMessage('risk:binningexplorerapp:InfiniteNaNCutPointsTitle'),'warn','modal');
                src.String = num2str(obj.SplitCutPoints(Num));
                return;
            end
            
            % Duplicate cutpoints
            if any(diff(TempInd) == 0)
                msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:DuplicateCutPointsMsg'),...
                    risk.internal.app.utils.getMessage('risk:binningexplorerapp:DuplicateCutPointsTitle'),'warn','modal');
            end
            
            % Non-increasing cutpoints
            if any(diff(TempInd)<0) || (Value <= obj.LeftEdgeValue) || (Value >= obj.RightEdgeValue)
                obj.InvalidCutPoint = true;
                msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:ShiftCutPointsMsg3',...
                    Value,obj.LeftEdgeValue,obj.RightEdgeValue),...
                    risk.internal.app.utils.getMessage('risk:binningexplorerapp:ShiftCutPointsTitle'),'warn','modal');
                src.String = num2str(obj.SplitCutPoints(Num));
                return;
                
            else
                delete(risk.internal.app.utils.extractChildren(hax,'patch'));
                if Num > 1
                    XLeftLine = unique(obj.SplitBinLine(Num-1).XData-BinWidth);
                    if Num < length(hLines)
                        XRightLine = unique(obj.SplitBinLine(Num+1).XData);
                    else
                        XRightLine = obj.RightEdgeValue;
                    end
                    if (Value > XLeftLine) && (Value < XRightLine)
                        obj.SplitBinLine(Num).XData = [Value Value]-BinWidth;
                    end
                    
                else
                    obj.SplitBinLine(Num).XData = [Value Value]-BinWidth;
                end
                
                % Update the patch
                updatePatchAreas()
                obj.SplitCutPoints(Num) = Value;
                obj.InvalidCutPoint = false;
            end
            
            function updatePatchAreas()
                % Once the split line is shifted, the patch regions must
                % be updated
                
                nBins = length(hLines)+1;
                XPos  = zeros(nBins-1,1);
                
                for i = 1 : (nBins-1)
                    if mod(i,2) == 1
                        fa = 0.1;
                    else
                        fa = 0.2;
                    end
                    XPos(i) = unique(obj.SplitBinLine(i).XData);
                    if i == 1
                        patch([hax.XLim(1) XPos(i) XPos(i) hax.XLim(1)],...
                            [0 0 hax.YLim(2) hax.YLim(2)],obj.PatchColor,'Parent',...
                            hax,'FaceAlpha',fa,'EdgeColor','none');
                    else
                        patch([XPos(i-1) XPos(i) XPos(i) XPos(i-1)],...
                            [0 0 hax.YLim(2) hax.YLim(2)],...
                            obj.PatchColor,'Parent',hax,'FaceAlpha',fa,...
                            'EdgeColor','none');
                    end
                end
                
                patch([XPos(nBins-1) hax.XLim(2) hax.XLim(2) XPos(nBins-1)],...
                    [0 0 hax.YLim(2) hax.YLim(2)],obj.PatchColor,'Parent',...
                    hax,'FaceAlpha',abs(0.2-fa)+0.1,'EdgeColor','none');
            end
            
        end
        
        function obj = storeSelectedCategories(obj,varargin)
            % Callback on the listbox in the split window for categorical
            % predictors. It stores the selected categories into the
            % object's property to move to another bin
            
            src = varargin{1};
            N = src.Tag(length('listbox')+1:end);
            if isempty(src.String)
                obj.SelectedCategories = {N;''};
            else
                obj.SelectedCategories = [N;src.String(src.Value)];
            end
            
            % If the corresponding checkbox is selected, deselect it, if 
            % less than the total elements are selected.
            Num = src.Tag(length('listbox')+1:end);
            hp = risk.internal.app.utils.extractChildren(obj.SplitFigure,'uipanel');
            hCheck = risk.internal.app.utils.extractChildren(hp,'uicontrol','Tag',['checkbox' Num]);
            if (hCheck.Value == 1) && (length(src.Value)<length(src.String))
                hCheck.Value = 0;
            end

        end
      
        function obj = selectAllBinMembers(obj,varargin)
            % Callback on the checkbox to select all elements in the
            % listbox
            
            src = varargin{1};
            hListbox = varargin{3};
            
            if src.Value == 1
                hListbox.Value = 1:hListbox.Max;
                obj.storeSelectedCategories(hListbox,[]);
            else
                hListbox.Value = 1;
            end
            
        end
        
        function obj = moveFromBinToBin(obj,varargin)
            % Callback on the arrowed pushbutton to move the selected item
            % in the listbox from one bin to the adjacent.
            
            % Step 0: Deselect any "Select all" checkbox
            hp  = risk.internal.app.utils.extractChildren(obj.SplitFigure,'uipanel');
            hCheck = risk.internal.app.utils.extractChildren(hp,'uicontrol','Tag','checkbox');
            set(hCheck,'Value',0);
            
            % Step 1: Move elements between bins
            hGive = varargin{3};
            hGet  = varargin{4};
            hHist = varargin{5};            
            nBins = str2double(varargin{6}.String);
            
            N1 = str2double(hGive.Tag(length('listbox')+1:end));
            N2 = str2double(hGet.Tag(length('listbox')+1:end));
            
            if N1 < N2
                LeftBin = hGive;
                RightBin = hGet;
            else
                LeftBin = hGet;
                RightBin = hGive;
            end
            
            List1 = cellstr(hGive.String);
            List2 = cellstr(hGet.String);
            List1(strcmpi('',List1)) = [];
            List2(strcmpi('',List2)) = [];
            
            Values  = 1 : length(List1);
            [~,Ind] = ismember(hGive.Value,Values);
            if all(Ind) > 0
                if isempty(hGet.Value)
                    hGet.Value = 1;
                end
                Values(Ind) = [];
                hGet.String = [List2; List1(hGive.Value)];
                hGive.Value = 1;
                hGive.String = hGive.String(Values);
                hGive.Max = length(hGive.String);
                hGet.Max  = length(hGet.String);
            else
                return;
            end
            
            % Step 2: Update the bin elements and patches. 
            % Sort categories from left to right bin. Find which is left
            % bin by finding which bin the first element in the histogram
            % belongs to.
            Categories = [LeftBin.String;RightBin.String];
            [ok,Ind] = ismember(hHist.Categories,Categories);
            if ok(1)
                hHist.Categories = [Categories;hHist.Categories(Ind==0)'];
            else
                IndElemInCategory = find(Ind);
                FirstNNZhalf = 1:(IndElemInCategory(1)-1);
                SecondNNZhalf = (IndElemInCategory(end)+1):length(Ind);
                hHist.Categories = [hHist.Categories(FirstNNZhalf)';...
                    Categories;hHist.Categories(SecondNNZhalf)'];
            end
            hax = hHist.Parent;
            delete(risk.internal.app.utils.extractChildren(hax,'patch'));
            
            if nBins > 2
                % Find which split line to update
                Num  = str2double(LeftBin.Tag(length('listbox')+1:end));
                Ind1 = arrayfun(@(c)~isempty(fieldnames(c)),obj.SplitBinLine);
                Ind2 = isvalid(obj.SplitBinLine);
                hLines = obj.SplitBinLine(Ind1&Ind2);
                XPos = arrayfun(@(c)unique(c.XData),hLines);
                
                try
                    XPos(Num) = length(LeftBin.String)+length(FirstNNZhalf)+0.5;
                catch 
                    XPos(Num) = length(LeftBin.String)+0.5;
                end
                
                obj.SplitBinLine(Num).XData = [XPos(Num) XPos(Num)];
                
            else
                Num  = 1;
                XPos = length(LeftBin.String)+0.5;
                obj.SplitBinLine(1).XData = [XPos XPos];
            end
            
            % Store selected members if not previously defined. This 
            % happens if the user did not actively click to select the 
            % element to move.
            if isempty(obj.SelectedCategories)
                if isempty(hGive.String)
                    obj.SelectedCategories = {N1;''};
                else
                    obj.SelectedCategories = [N1;hGive.String(hGive.Value)];
                end
            end
            
            % Store the bin members
            storeBinMembers();
            
            % Update the patches
            updatePatchAreas();
            
            % Update the position of the 'topbintext' text object 
            htxt1 = risk.internal.app.utils.extractChildren(hp,'uicontrol','Tag',['topbintext' num2str(Num)]);
            htxt2 = risk.internal.app.utils.extractChildren(hp,'uicontrol','Tag',['topbintext' num2str(Num+1)]);

            x1 = obj.convertXLimToCharacter(Num,XPos,...
                hHist.Parent.Position(1),hHist.Parent,true);
            x2 = obj.convertXLimToCharacter(Num+1,XPos,...
                hHist.Parent.Position(1),hHist.Parent,~(Num+1==nBins));
            
            htxt1.Position(1) = x1;
            htxt2.Position(1) = x2;
            
            % Reset Selected Categories to force user selection
            obj.SelectedCategories = [];
            
            function updatePatchAreas()
                % Once the split line is shifted, the patch regions must
                % be updated
                
                hax = hHist.Parent;
                
                for i = 1 : (nBins-1)
                    if mod(i,2) == 1
                        fa = 0.1;
                    else
                        fa = 0.2;
                    end
                    if i == 1
                        patch([hax.XLim(1) XPos(i) XPos(i) hax.XLim(1)],...
                            [0 0 hax.YLim(2) hax.YLim(2)],obj.PatchColor,'Parent',...
                            hax,'FaceAlpha',fa,'EdgeColor','none');
                    else
                        patch([XPos(i-1) XPos(i) XPos(i) XPos(i-1)],...
                            [0 0 hax.YLim(2) hax.YLim(2)],...
                            obj.PatchColor,'Parent',hax,'FaceAlpha',fa,...
                            'EdgeColor','none');
                    end
                end
                
                patch([XPos(nBins-1) hax.XLim(2) hax.XLim(2) XPos(nBins-1)],...
                    [0 0 hax.YLim(2) hax.YLim(2)],obj.PatchColor,'Parent',...
                    hax,'FaceAlpha',abs(0.2-fa)+0.1,'EdgeColor','none');
            end
            
            function storeBinMembers()
                % Find the listboxes. 
                % Extract the corresponding bin number and sort in ascending order.
                % Extract the bin member and associate to the bin number
                
                hList = risk.internal.app.utils.extractChildren(hp,'uicontrol','Tag','listbox');
                Indices = arrayfun(@(c)str2double(c.Tag(length('listbox')+1:end)),hList);
                [~,ii] = sort(Indices);
                hList = hList(ii);
                obj.SplitCategories = cell(length(hList),2);
                
                for i = 1 : length(hList)
                    obj.SplitCategories(i,:) = {hList(i).String,i};
                end
                
            end
            
        end
        
        function obj = cancelSplitCallback(obj,varargin)
            % Callback on the "Cancel" button in the "Split" window. It
            % cancels the changes and closes the "Split" window.
                     
            obj.closeSplitWindow(obj,varargin{:});
            
        end
                
        function obj = okSplitCallback(obj,varargin)
            % Callback on the "OK" button. Accepts the split rules and
            % close the window.
       
            if obj.InvalidCutPoint
                obj.InvalidCutPoint = false;
                return;
            end
            Predictor = obj.SelectedPredictors{1};
            
            % 2. Store old cutpoints / category groupings
            if ismember(Predictor,obj.getNumericPredictors())
                [~,cp] = bininfo(obj.Scorecard,Predictor);
                obj.OldCutPoints = cp;
                cp = sort([cp; unique(obj.SplitCutPoints)]);
            else
                [~,cg] = bininfo(obj.Scorecard,Predictor);
                obj.OldCatGroupings = cg;
                T = formatSplitCategories();
                [~,Ind1] = ismember(T.Category,cg.Category);
                Ind2 = (cg.BinNumber > obj.SelectedBins);
                % Re-arrange the bin numbering for the new categories.
                % If k = obj.SelectedBins, then:
                % 1. Bins 1..(k-1) : unchanged
                % 2. Bin k becomes k, k+1,...,k+(m-1), "m" being the
                %    number of new sub-bins
                % 3. Bins above k become j = j+k+(m-1).
                cg.BinNumber(Ind1) = T.BinNumber;
                cg.BinNumber(Ind2) = cg.BinNumber(Ind2) + ...
                    length(obj.SplitCategories)-1;

            end
            
            % 3. Update scorecard
            if ismember(Predictor,obj.getNumericPredictors())
                obj.Scorecard = modifybins(obj.Scorecard,Predictor,'CutPoints',cp);
            else               
                if any(diff(cg.BinNumber) < 0) && ...
                    ismember(Predictor,obj.getOrdinalPredictors())
                    w = warning('off','finance:internal:finance:binning:Categorical:NonMonotonicGrouping');
                    c = onCleanup(@() warning(w));
                    uiwait(msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:OrdinalSplitNonAdjacentMsg'),...
                            risk.internal.app.utils.getMessage('risk:binningexplorerapp:OrdinalSplitNonAdjacentTitle'),'warn','modal'));
                end
                
                obj.Scorecard = modifybins(obj.Scorecard,Predictor,'CatGrouping',cg);
            end
            
            obj.isScorecardExported = false;
            obj.isSplitApplied = true;
            
            % 4. Update the zoomed and overview figures
            obj.isPlotChanged = true;
            notify(obj,'SelectedFigureTab');
            obj.isPlotChanged = false;
            
            % 5. Update statistic information
            obj.setBinInfo();
            obj.setPredictorInfo();
            
            % 6. Bring the zoomed figure to the foreground
            figure(obj.ZoomedFigure)
            obj.isSplitApplied = false;
            obj.closeSplitWindow(obj,varargin{:});

            % Helper function: Format SplitCategories
            function T = formatSplitCategories()
                
                C1 = cell(length(obj.BinMembers),1);
                C2 = zeros(length(obj.BinMembers),1);
                T  = table(C1,C2,'VariableNames',...
                    {risk.internal.app.utils.getMessage('risk:binningexplorerapp:Category'),...
                    risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinNumberTable')});
                
                BinSize = cellfun(@(c)length(c),obj.SplitCategories(:,1));
                
                for i = 1 : size(obj.SplitCategories,1)
                    BinNum = obj.SplitCategories{i,2};
                    if i == 1
                        i1 = 1;
                        i2 = BinSize(i);
                    else
                        i1 = 1 + sum(BinSize(1:i-1));
                        i2 = i1 - 1 + BinSize(i);
                    end
                    T.Category(i1:i2) = obj.SplitCategories{i,1};
                    T.BinNumber(i1:i2) = obj.SelectedBins - 1 + BinNum;%/10;
                end
                
            end
        
        end
        
        function obj = closeSplitWindow(obj,varargin)
            % Callback on the CloseRequestFcn of the "Split" figure. Also,
            % called by 'obj.cancelSplitCallback' and 'obj.okSplitCallback'
            
            src = varargin{1};
            if isa(src,'matlab.ui.Figure')
                delete(src);
            else
                delete(obj.SplitFigure);
            end
            
        end
        
        function xtxt = convertXLimToCharacter(~,i,XPos,x0,hax,tag)
            % Converts units between XLim of the current axes and character
            % units to position the bin number string atop the histogram.
            
            if i == 1
                x_ = hax.XLim(1);
            else
                x_ = XPos(i-1);
            end
            
            if tag
                dx   = (XPos(i)-x_)*hax.Position(3)/diff(hax.XLim);
                xi   = x0 + (XPos(i)-hax.XLim(1))*hax.Position(3)/diff(hax.XLim);
            else
                dx   = (hax.XLim(2)-x_)*hax.Position(3)/diff(hax.XLim);
                xi   = x0 + hax.Position(3);
            end
            
            xtxt = xi - ((dx-7)/2 + 7);
            
        end
        
        %% Plot Section
        
        function section = LocalPlotSection(obj,TabName)            
            % 1. Plot button
            PlotBtn = toolpack.component.TSDropDownButton(); 
            PlotBtn.Name = 'plotoptionsbutton';
            PlotBtn.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:PlotOptionsTooltip');
            PlotBtn.Popup = obj.setPlotOptionsPopup('text_only');
            if obj.isInitPlot
                PlotBtn.Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:None');
            else % This is the manual tab
                PlotBtn.Text = obj.CtrlsOverview.PlotButtons(1).Text;
            end
            
            % 2. WOE Checkbox
            WOEBtn = toolpack.component.TSCheckBox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:WOECurve'));
            WOEBtn.Name = 'woecurvecheckbox';
            WOEBtn.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:WOECurveCheckboxTooltip');
            if strcmpi(TabName,'manual') 
                WOEBtn.Selected = obj.CtrlsOverview.PlotButtons(2).Selected;
            else
                WOEBtn.Selected = true;
            end
            
            % 3. Tooltip Checkbox (Manual only)
            if strcmpi(TabName,'manual') 
                TooltipBtn = toolpack.component.TSCheckBox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:Datatip'));
                TooltipBtn.Name = 'datatipcheckbox';
                TooltipBtn.Selected = true;
                TooltipBtn.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:DatatipCheckboxTooltip');
            end

            section = toolpack.component.TSPanel('f:p,1dlu','f:d,1dlu,f:p,1dlu,f:d,1dlu,f:d');
            section.Name = 'PlotSection';
            section.add(PlotBtn,'xy(1,3,''c,c'')');
            section.add(WOEBtn,'xy(1,5,''l,c'')');
            if strcmpi(TabName,'manual') 
                section.add(TooltipBtn,'xy(1,7,''l,c'')');
                obj.ActionListeners.Tooltip = addlistener(TooltipBtn,'ItemStateChanged',@obj.setBinTooltip);
            end
            
            % Callbacks on WOE checkbox and dropdown list
            obj.ActionListeners.PlotOptions = addlistener(PlotBtn.Popup,...
                'ListItemSelected',@(src,ev)obj.setBinText(src,ev,TabName));
            obj.ActionListeners.SetWOE = addlistener(WOEBtn,...
                'ItemStateChanged',@(src,ev)obj.setWOEcurve(src,ev,TabName));

            if strcmpi(TabName,'overview')
                obj.CtrlsOverview.PlotButtons = [PlotBtn;WOEBtn];
            else
                obj.CtrlsManual.PlotButtons = [PlotBtn;WOEBtn;TooltipBtn];
            end
            
        end

        function popup = setPlotOptionsPopup(~,TextOption)
            % Callback on the Plot Options dropdown button. Creates a drop
            % down list with five plot options.
            
            listItems = struct( ...
                'Title', '', ...
                'Style', '', ...
                'Header', cell(5,1));
            
            % None
            listItems(1).Title = risk.internal.app.utils.getMessage('risk:binningexplorerapp:None');
            listItems(1).Style  = TextOption;
            listItems(1).Header = false;
            
            % Bin Count
            listItems(2).Title = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Bincount');
            listItems(2).Style  = TextOption;
            listItems(2).Header = false;
            
            % Bin Level
            listItems(3).Title = risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinLevel');
            listItems(3).Style  = TextOption;
            listItems(3).Header = false;
            
            % Data Level
            listItems(4).Title = risk.internal.app.utils.getMessage('risk:binningexplorerapp:DataLevel');
            listItems(4).Style  = TextOption;
            listItems(4).Header = false;
            
            % Total Count
            listItems(5).Title = risk.internal.app.utils.getMessage('risk:binningexplorerapp:TotalCount');
            listItems(5).Style  = TextOption;
            listItems(5).Header = false;
            
            popup = toolpack.component.TSDropDownPopup(listItems,TextOption);
            popup.Name = 'plotoptionspopup';
            
        end
        
        function obj = setBinText(obj,varargin)
            % Callback on the dropdown list (popup) for plot options.
            % Allows the user to choose how data are displayed on the
            % histogram: Stacked histogram, bin count, count of Goods and 
            % Bads as a percentage of the data, or of the bins.

            if obj.isInitPlot
                return;
            end
            
            src = varargin{1};
            TabName = varargin{3};
            
            % Retrieve the dropdown handle
            if strcmpi(TabName,{'overview'})
                CurrentDropDown = obj.CtrlsOverview.PlotButtons(1);
                if obj.isViewExpanded
                    AltTabDropDown = obj.CtrlsManual.PlotButtons(1);
                else
                    AltTabDropDown = [];
                end
            else
                CurrentDropDown = obj.CtrlsManual.PlotButtons(1);
                AltTabDropDown = obj.CtrlsOverview.PlotButtons(1);
            end
            
            idx = src.SelectedIndex;
            Value = src.Items(idx).Title;
            CurrentDropDown.Text = Value;
            
            % Update the other tab's button
            if ~isempty(AltTabDropDown)
                AltTabDropDown.Text = Value;
            end
            
            % Display the bin count information
            switch lower(Value)
                case 'no labels'
                    obj.PlotSettings.BinText = 'none';
                case 'bin count'
                    obj.PlotSettings.BinText = 'count';
                case '% data level'
                    obj.PlotSettings.BinText = 'percentcols';
                case '% bin level'
                    obj.PlotSettings.BinText = 'percentrows';
                case '% total count'
                    obj.PlotSettings.BinText = 'percenttotal';
            end
              
            obj.isBinTextChanged = true;
            notify(obj,'PlotSettingsChanged');
            obj.isBinTextChanged = false;
            
        end

        function obj = setWOEcurve(obj,varargin)
            % Callback on the WOE checkbox
            
            if obj.isInitPlot || obj.isWOEChanged
                return;
            end

            src  = varargin{1};
            TabName = varargin{3}; 

             % Retrieve the dropdown handle
            CurrentCheckbox = src;
            if strcmpi(TabName,{'overview'})
                if obj.isViewExpanded
                    AltCheckbox = obj.CtrlsManual.PlotButtons(2);
                else
                    AltCheckbox = [];
                end
            else
                AltCheckbox = obj.CtrlsOverview.PlotButtons(2);
            end
            
            if CurrentCheckbox.Selected
                obj.PlotSettings.WOE = 'On';
            else
                obj.PlotSettings.WOE = 'Off';
            end
            
            obj.isWOEChanged = true;
            notify(obj,'PlotSettingsChanged');
            
            % Update the checkbox on the other tab
            AltCheckbox.Selected = CurrentCheckbox.Selected;

            obj.isWOEChanged = false;
            
        end

        function obj = setBinTooltip(obj,varargin)
            % Callback on the listener to the "Tooltip" checkbox under
            % "Plot Options". It is a wrapper on the WindowButtonMotionFcn
            % of the selected axes.
            
            src = varargin{1};
            if src.Selected
                obj.TooltipOn = true;
            else
                obj.TooltipOn = false;
            end
            
        end
        
        function obj = applyPlotSettings(obj,varargin)
            % Listener/Callback on the event 'PlotSettingsChanged'. This
            % method retrieves the plot settings and updates the plots.

            obj.isPlotChanged = true;
            obj.setMainFigureLayout('allpredictors');
            obj.isPlotChanged = false;

        end
        
        function obj = showBinInformation(obj,varargin)
            % Callback on the 'WindowButtonMotionFcn' property of the
            % 'obj.ZoomedFigure'. It shows the datatip information about
            % the bin that the pointer hovers on.
            
            % The data property 'StartTime' is used to decide when to show
            % the tooltip and when to hide it. If the time elapsed between
            % two mouse moves is more than two seconds, the tooltip is
            % hidden.
            
            starttime = getappdata(obj.ZoomedFigure,'StartTime');
            if ~isempty(starttime) && etime(clock,starttime)>2 && ...
                    ~isempty(obj.TooltipHandleBins) && isvalid(obj.TooltipHandleBins)
                obj.TooltipHandleBins.Visible = 'off';
            end
                
            if obj.TooltipOn 
                Predictor = obj.SelectedPredictors{1};
                hax = obj.ZoomedAxes;
                hFig = obj.ZoomedFigure;
                ht = risk.internal.app.utils.extractChildren(hFig,'uicontrol','Tag','bindatatip');
                if ~isempty(ht)
                    ht.Visible = 'Off';
                end
                obj.setBinEdgesAndNumber(hax);
                hax.Units  = 'characters';
                hFig.Units = 'characters';
                XPos = hax.CurrentPoint(1,1);
                YPos = hax.CurrentPoint(1,2);
                BinNumber = find(XPos >= obj.LeftEdgesXData & XPos < obj.RightEdgesXData);
                
                if ~isempty(BinNumber) %%%%% test if ~isempty or isa(handle)&& isvalid 
                    YBins = sum(cell2mat({obj.BarHandles.YData}'));
                    if (YPos >= 0) && (YPos <= YBins(BinNumber))
                        if ~isempty(obj.OldBinNumber) && (BinNumber == obj.OldBinNumber)
                            try
                                obj.TooltipHandleBins.Visible = 'On';
                            catch
                            end
                            return;
                        end
                        % Convert from "histogram" units to characters.
                        % Make sure to convert axes units to character to
                        % d: distance between two X ticks
                        % Set the y-position to the maximum bin height
                        
                        dh = 15;
                        dw = 5;
                        d  = hax.Position(3)/length(obj.LeftEdgesXData);
                        X  = hax.Position(1) + (BinNumber-1+0.1)*d;
                        Y  = hax.Position(2) + hax.Position(4) - dh;
                        
                        bi = bininfo(obj.Scorecard,obj.SelectedPredictors{1});
                        line1 = risk.internal.app.utils.getMessage('risk:binningexplorerapp:GoodInfo',num2str(bi.Good(BinNumber)));
                        line2 = risk.internal.app.utils.getMessage('risk:binningexplorerapp:BadInfo',num2str(bi.Bad(BinNumber)));
                        line3 = risk.internal.app.utils.getMessage('risk:binningexplorerapp:OddsInfo',num2str(bi.Odds(BinNumber)));
                        line4 = risk.internal.app.utils.getMessage('risk:binningexplorerapp:WOEInfo',num2str(bi.WOE(BinNumber)));
                        if ismember(Predictor,obj.getCategoricalPredictors())
                            [~,cg] = bininfo(obj.Scorecard,Predictor);
                            Ind = (cg.BinNumber == BinNumber);
                            Members = cg.Category(Ind);
                            line5 = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Members');
                            if length(Members) == 1
                                line6 = risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinMembers',Members{1});
                            elseif length(Members) == 2
                                line6 = [risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinMembers',Members{1}) ', '...
                                    risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinMembers',Members{2})];
                            else
                                line6 = [risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinMembers',Members{1}) ', '...
                                    risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinMembers',Members{2}) ' ...'];
                            end
                        else
                            line5 = [];
                            line6 = [];
                        end

                        StringInfo = {line1;line2;line3;line4;line5;line6};
                        StringInfo = StringInfo(~cellfun('isempty',StringInfo));
                        if isempty(obj.TooltipHandleBins) || ~isvalid(obj.TooltipHandleBins)
                            ht = uicontrol('Style','Text','Parent',hFig,...
                                'Units','characters','Position',[X Y dw dh],...
                                'BackgroundColor',[255 255 224]/255,'String',StringInfo,...
                                'HorizontalAlignment','left','Tag','bindatatip',...
                                'HitTest','Off');
                            
                            obj.TooltipHandleBins = ht;
                        else
                            obj.TooltipHandleBins.String = StringInfo;
                            obj.TooltipHandleBins.Position = [X Y dw dh];
                        end
                        setappdata(obj.ZoomedFigure,'StartTime',clock)
                        obj.TooltipHandleBins.Visible = 'On';
                        obj.TooltipHandleBins.Position(3) = obj.TooltipHandleBins.Extent(3) + 1;
                        obj.TooltipHandleBins.Position(4) = obj.TooltipHandleBins.Extent(4) + 1;
                        uistack(obj.TooltipHandleBins,'top');
                        obj.OldBinNumber = BinNumber;
                    else
                        % Outside bin range
                        if ~isempty(obj.TooltipHandleBins) && isvalid(obj.TooltipHandleBins)
                            obj.TooltipHandleBins.Visible = 'Off';
                        end
                        setappdata(obj.ZoomedFigure,'StartTime',[])
                    end
                else
                    % Outside and in between bins
                    if ~isempty(obj.TooltipHandleBins) && isvalid(obj.TooltipHandleBins)
                        obj.TooltipHandleBins.Visible = 'Off';
                    end
                end
            end

        end
        
        function StringInfo = getStringPredictorInfo(obj)
            Predictor = obj.SelectedPredictors{1};
            PredInfo  = predictorinfo(obj.Scorecard,Predictor);
            if ismember(Predictor,obj.getNumericPredictors())
                PredType = 'Numeric';
            else
                PredType = 'Categorical';
            end
            line1 = risk.internal.app.utils.getMessage('risk:binningexplorerapp:PredName',Predictor);
            line2 = risk.internal.app.utils.getMessage('risk:binningexplorerapp:PredType',PredType);
            line3 = risk.internal.app.utils.getMessage('risk:binningexplorerapp:LatestBinning',PredInfo.LatestBinning{:});
            
            StringInfo = {line1;line2;line3};
            
        end
        
        %% Table Section
        
        function section = LocalTableSection(obj,TabName)
            TableOptions = {risk.internal.app.utils.getMessage('risk:binningexplorerapp:Odds');...
                risk.internal.app.utils.getMessage('risk:binningexplorerapp:WOE');...
                risk.internal.app.utils.getMessage('risk:binningexplorerapp:InfoValue');...
                risk.internal.app.utils.getMessage('risk:binningexplorerapp:Entropy');...
                risk.internal.app.utils.getMessage('risk:binningexplorerapp:MembersTable')};
            
            RowSpec = ['f:d,5dlu,' repmat('f:d,3dlu,',[1 3]),'1px'];
            section = toolpack.component.TSPanel('f:p,3dlu,f:p',RowSpec);
            section.Name = 'tableoptionpanel';
            
            TableOptionBtn = cell(length(TableOptions),1);
            for  i = 1 : length(TableOptions)
                TableOptionBtn{i} = toolpack.component.TSCheckBox(TableOptions{i});
                TableOptionBtn{i}.Name = [lower(TableOptions{i}) 'checkbox'];
                TableOptionBtn{i}.Description = risk.internal.app.utils.getMessage(['risk:binningexplorerapp:' TableOptions{i} 'Checkbox' 'Tooltip']);
                
                if ismember(lower(TableOptions{i}),{'odds';'woe';'infovalue'})
                    TableOptionBtn{i}.Selected = true;
                end
                
                if strcmpi(TableOptions{i},'members')
                     TableOptionBtn{i}.Enabled = false;
                end
                if i <= 3
                    Location = sprintf('xy(1,%d)',2*i+1);
                else
                    Location = sprintf('xy(3,%d)',2*i-length(TableOptions));
                end
                section.add(TableOptionBtn{i},Location);
                obj.ActionListeners.SetTableView = addlistener(TableOptionBtn{i},'ItemStateChanged',...
                    @(src,ev)obj.setTableView(src,ev,TabName));
                
            end

            if strcmpi(TabName,'manual')
                TableOptionBtn{end}.Enabled = obj.CtrlsOverview.TableButtons(end).Enabled;
                TableOptionBtn{end}.Selected = obj.CtrlsOverview.TableButtons(end).Selected;
            end
            
            if strcmpi(TabName,'overview')
                obj.CtrlsOverview.TableButtons = [TableOptionBtn{:}]';
            else
                obj.CtrlsManual.TableButtons = [TableOptionBtn{:}]';
            end
            
         end
             
        function obj = setTableView(obj,varargin)
            % Callback on each of the radio buttons under 'Table Options'.
            % Allows the user to select which statistics to display in the
            % Bin Information section of the Aap.

            if obj.isInitTable || obj.isTableViewChanged
                return;
            end
            
            src = varargin{1};
            TabName = varargin{3};
            Value = lower(src.Text);
            
            if src.Selected
                if ~strcmpi(Value,'members')
                    A = unique([obj.TableSettings Value],'stable');
                    [~,Ind] = ismember(obj.BinInfoStats,A);
                    obj.TableSettings = A(Ind(Ind>0)); % Keep same order in obj.BinInfoStats
                end
            else
                if length(obj.TableSettings) == 1 && strcmpi(obj.TableSettings,Value)
                    % All are deselected. The reported statistics are
                    % "Good" and "Bad" only.
                    obj.TableSettings = {};
                else
                    A = setdiff(obj.TableSettings,Value,'stable'); 
                    [~,Ind] = ismember(obj.BinInfoStats,A);
                    obj.TableSettings = A(Ind(Ind>0)); % Keep same order in obj.BinInfoStats
                end
            end

            N       = length(src.Name);
            Name    = src.Name(1:N-length('checkbox'));
            [~,Ind] = ismember(Name,obj.BinInfoStats);
            
            if strcmpi(Name,'members')
                obj.MembersCheckbox = TabName;
            end
            
            obj.setBinInfo();
            
            % Keep checkboxes on both tabs (when they exist) in sync
            if strcmpi(TabName,{'overview'})
                if obj.isViewExpanded
                    AltCheckbox = obj.CtrlsManual.TableButtons(Ind);
                else
                    AltCheckbox = [];
                end
            else
                AltCheckbox = obj.CtrlsOverview.TableButtons(Ind);
            end
            
            obj.isTableViewChanged = true;
            AltCheckbox.Selected = src.Selected;
            obj.isTableViewChanged = false;

        end
        
        function obj = setBinInfo(obj,varargin)
            % Called by the method obj.setInitialAppLayout and
            % obj.selectAxes
            % It adds (or updates) a figure object containing the statistics 
            % from the creditscorecard object's method, 'bininfo'.
            % When the selected predictor changes, this method is called
            % wherever the method obj.setMainFigureLayout is called.
            % varargin = {true,String'). The boolean 'true' is just a
            % placeholder to have an additional input argument. It is when
            % this method is called after initialization

            Predictor = obj.SelectedPredictors{1};
            if (nargin > 1) 
                if strcmpi(varargin{1},'initialize')
                    hFig = figure('Menubar','None','IntegerHandle','Off',...
                        'HandleVisibility','Off','Name',...
                        risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinInfo',Predictor),...
                        'Visible','Off','CloseRequestFcn','','Resize','Off');
                elseif strcmpi(varargin{1},'select')
                    return;
                elseif ismember(varargin{1},{'overview','manual'})
                    hFig = obj.BinInfo;
                    hFig.Name = risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinInfo',Predictor);
                    delete(hFig.Children);
                end
            else
                hFig = obj.BinInfo;
                hFig.Name = risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinInfo',Predictor);
                delete(hFig.Children);
            end

            obj.computeBinInfo(Predictor);
            T = obj.StatsTable.(Predictor); 
            RowNames = T.Bin;
            ColNames = T.Properties.VariableNames(2:end);
            data     = table2cell(T(:,2:end));
            [ok,Ind] = ismember('woe',lower(ColNames));
            
            if ok
                data(end,Ind) = {''};
            end

            [~,cg] = bininfo(obj.Scorecard,Predictor);
            
            % Display "Members" for categorical predictors 
            if ismember(Predictor,obj.getCategoricalPredictors())
                if nargin > 1 && strcmpi(varargin{1},{'initialize'}) 
                    [data,ColNames] = addMembersToBininfo(data,ColNames);
                    
                else
                    switch lower(obj.MembersCheckbox)
                        case 'overview'
                            if obj.CtrlsOverview.TableButtons(end).Selected
                                [data,ColNames] = addMembersToBininfo(data,ColNames);
                            end
                        case 'manual'
                            if obj.CtrlsManual.TableButtons(end).Selected
                                [data,ColNames] = addMembersToBininfo(data,ColNames);
                            end
                    end
                end
            end
            
            uitable('Parent',hFig,'Data',data,'RowName',...
                RowNames,'ColumnName',ColNames,'Units','Normalized',...
                'Position',[0.05 0.05 0.9 0.9],'Tag','bininfotable');
            
            obj.BinInfo = hFig;
            
            function [data,ColNames] = addMembersToBininfo(data,ColNames)
                iGroupings = unique(cg.BinNumber);
                nGroups  = length(iGroupings);
                Members = cell(nGroups,1);

                for i = 1 : nGroups
                    Ind = (cg.BinNumber == iGroupings(i));
                    Members{i} = cg.Category(Ind);
                end

                for j = 1 : nGroups
                    s = sprintf('%s,',Members{j}{:});
                    s(end) = '';
                    Members{j} = ['  ' s];
                end

                ColNames  = [ColNames {risk.internal.app.utils.getMessage('risk:binningexplorerapp:MembersTable')}];
                data = [data [Members;{''}]];
            end

        end
        
        function obj = setPredictorInfo(obj,varargin)
            % Called by the method 'obj.setInitialAppLayout'
            % It adds a figure object containing the statistics from the
            % creditscorecard object's method, 'predictorinfo'.
            
            Predictor = obj.SelectedPredictors{1};
            if (nargin > 1)
                if strcmpi(varargin{1},'initialize')
                    hFig = figure('Menubar','None','IntegerHandle','Off',...
                        'HandleVisibility','Off','Visible','Off','Name',...
                        risk.internal.app.utils.getMessage('risk:binningexplorerapp:PredictorInfo',Predictor),...
                        'CloseRequestFcn','','Resize','Off');
                elseif strcmpi(varargin{1},'select')
                    return;
                end
            else
                hFig = obj.PredictorInfo;
                hFig.Name = risk.internal.app.utils.getMessage('risk:binningexplorerapp:PredictorInfo',Predictor);
                delete(hFig.Children);
            end

            [~,T]    = predictorinfo(obj.Scorecard,Predictor);
            RowNames = T.Properties.RowNames;
            ColNames = T.Properties.VariableNames;
            data     = table2array(T);

            [~,cg]   = bininfo(obj.Scorecard,Predictor);
            
            if (ismember(Predictor,obj.getCategoricalPredictors())) && ...
                (length(unique(cg.BinNumber)) < length(cg.Category)) 
            
                ColNames = [ColNames {risk.internal.app.utils.getMessage('risk:binningexplorerapp:Group')}];
                data     = [data cg.BinNumber];
            end
                
            uitable('Parent',hFig,'Data',data,'Units','Normalized',...
                'RowName',RowNames,'ColumnName',ColNames,'ColumnWidth',...
                {90 85},'Position',[0.05 0.05 0.9 0.9],'Tag','predictorinfotable');

            obj.PredictorInfo = hFig;
            
        end
            
        function obj = computeBinInfo(obj,Predictor)
            % Called obj.setMainFigureLayout. This method adds a field 
            % corresponding to Predictor in obj.StatsTable property for 
            % display in the 

            if isempty(obj.TableSettings)
                T = bininfo(obj.Scorecard,Predictor);
                obj.StatsTable.(Predictor) = T(:,...
                    {risk.internal.app.utils.getMessage('risk:binningexplorerapp:Bin'),...
                    risk.internal.app.utils.getMessage('risk:binningexplorerapp:Good'),...
                    risk.internal.app.utils.getMessage('risk:binningexplorerapp:Bad')});
            else
                obj.StatsTable.(Predictor) = bininfo(obj.Scorecard,...
                    Predictor,'Statistics',obj.TableSettings);
            end
            
        end
        
        %% App Layout / Figure and Callbacks
        
        function obj = setInitialAppLayout(obj)
            
            % ---> Step 1: Set the layout of the main figure and subplots     
            obj.setInitialFigureLayout();
                        
            % ---> Step 2: Set the layout of the scorecard's BININFO table
            obj.setBinInfo('initialize');
            
            % ---> Step 3: Set the layout of the predictor statistics 
            obj.setPredictorInfo('initialize');
            
            % Add the figures to the toolgroup
            obj.addFigure(obj.OverviewFigure)
            obj.addFigure(obj.BinInfo)
            obj.addFigure(obj.PredictorInfo)
            
            % Arrange figures (documents)
            grpname = obj.Name;
            md = com.mathworks.mlservices.MatlabDesktopServices.getDesktop;
            
            % One window initially to help with default layout
            md.setDocumentArrangement(grpname,md.TILED,java.awt.Dimension(1,1));
            
            % Main figure fills the entire first column
            md.setDocumentArrangement(grpname,md.TILED,java.awt.Dimension(2,2));   
            
            md.setDocumentColumnSpan(grpname, 0, 0, 2); 
            md.setDocumentRowHeights(grpname, [0.65, 0.35]);
            md.setDocumentColumnWidths(grpname, [0.7, 0.3]);
            
            % Assign documents to their tiles
            loc1 = com.mathworks.widgets.desk.DTLocation.create(0);     
            loc2 = com.mathworks.widgets.desk.DTLocation.create(1); 
            loc3 = com.mathworks.widgets.desk.DTLocation.create(2); 
            
            md.setClientLocation(obj.OverviewFigure.Name,grpname,loc1);
            md.setClientLocation(obj.BinInfo.Name,grpname,loc2);
            md.setClientLocation(obj.PredictorInfo.Name,grpname,loc3);  
            
            % Remove the "x" for closing
            Prop  = com.mathworks.widgets.desk.DTClientProperty.PERMIT_USER_CLOSE;
            state = java.lang.Boolean.FALSE;

            pause(0.5);
            md.getClient(obj.OverviewFigure.Name,grpname).putClientProperty(Prop, state);
            md.getClient(obj.BinInfo.Name,grpname).putClientProperty(Prop, state);
            md.getClient(obj.PredictorInfo.Name,grpname).putClientProperty(Prop, state);
             
            % Turn on the visibility
            obj.OverviewFigure.Visible = 'On';
            obj.BinInfo.Visible = 'On';
            obj.PredictorInfo.Visible = 'On';
            
            % Set the main fgure as the current one
            figure(obj.OverviewFigure);
            
            % Enable the controls on the toolstrip
            obj.enableControls(true);

            % Need to re-adjust the legend position after docking
%             drawnow nocallbacks; 
            obj.setLegend(); % #ok something happens here, at the end of the call to setLegend: it seems the ML interpreter interprets the "drawnow" call before it is executed.

        end
        
        function obj = setInitialFigureLayout(obj)
            % Called by obj.setInitialAppLayout. It creates the main 
            % figure to hold all the subplots and divides the plot area 
            % into a grid.
            
            Predictors = obj.Scorecard.PredictorVars;
            NumCols    = 2;
            NumPreds   = length(Predictors);
            NumRows    = ceil(NumPreds / NumCols);
            BaseRows   = 5;
            Offset     = -2*floor(NumRows/BaseRows);
            x1 = 0.075; x2 = 0.525; w = 0.4;
          
            % Prevent the figure from being closed.
            hFig = figure('Menubar','None','IntegerHandle','Off',...
                'HandleVisibility','Off','Name',...
                risk.internal.app.utils.getMessage('risk:binningexplorerapp:Overview'),'Visible',...
                'off','CloseRequestFcn','','Resize','Off','Renderer','painters');
            
            auxPanel = uipanel('Parent',hFig,'Position',[0 0 1 1],...
                'BorderType','None');
            
            obj.PlotPanel = uipanel('Parent',auxPanel,'Position',[0 Offset...
                1 1-Offset],'BorderType','None');
            PanelPosition0 = obj.PlotPanel.Position;
            
            jScrollBar = javaObjectEDT('javax.swing.JScrollBar');
            jScrollBar.setOrientation(jScrollBar.VERTICAL);
            jScrollBar.setMaximum(100);
            javacomponent(jScrollBar,'East',hFig);
            mScrollBar = handle(jScrollBar);
            obj.ScrollBar = jScrollBar;
            mScrollBar.AdjustmentValueChangedCallback = ...
                {@(src,ev)obj.sliderCallback(src,ev,PanelPosition0)};

            % Setup the status bar to display plotting information
            javaMethodEDT('setSharedStatusBar', obj.JFrame, obj.JStatusBar)
            Delta = 100/NumPreds;
            
            % Display the "loading data" message
            hfTemp = figure('IntegerHandle','Off','HandleVisibility','Off',...
                'Name','Plotting predictors','Visible','Off');
            obj.addFigure(hfTemp);
            grpname = obj.Name;
            md = com.mathworks.mlservices.MatlabDesktopServices.getDesktop;
            md.setDocumentArrangement(grpname,md.TILED,java.awt.Dimension(1,1));
            loc = com.mathworks.widgets.desk.DTLocation.create(0);
            md.setClientLocation('Importing Data',grpname,loc);
            uicontrol('Parent',hfTemp,'Style','Text','Units',...
                'Normalized','Position',[0.25 0.3 0.5 0.2],'String',...
                'Please wait while plotting the data','FontSize',12);
            hfTemp.Visible = 'On';
            hfTemp.Pointer = 'watch';
            pause(1);
            
            for i = 1 : NumPreds
                if mod(i,2) == 0
                    xPos = x2;
                else
                    xPos = x1;
                end

                Value = obj.JProgressBar.getValue()+Delta;
                obj.JProgressBar.setValue(Value);
                obj.JLabel.setText(risk.internal.app.utils.getMessage('risk:binningexplorerapp:JLabelPlot',...
                    Predictors{i},num2str(i),num2str(NumPreds)));

                hf = plotbins(obj.Scorecard,Predictors{i},obj.getPlotSettings{:});
                hf.HandleVisibility = 'Off';
                hf.IntegerHandle = 'Off';
                hf.Visible = 'Off';
                
                hax = risk.internal.app.utils.extractChildren(hf,'axes');
                hl  = risk.internal.app.utils.extractChildren(hf,'legend'); 
                hb  = risk.internal.app.utils.extractChildren(hax,'bar');
                hL  = risk.internal.app.utils.extractChildren(hax,'line');
                set(hb,'PickableParts','none');
                set(hL,'PickableParts','none');

                hTemp = subplot(NumRows,NumCols,i,'Parent',obj.PlotPanel);
                Pos = hTemp.Position;
                Pos = [xPos Pos(2)+0.05 w Pos(4)];
                delete(hTemp)

                % Put the WOE curve on top so it is not hidden behind the
                % histogram
                hax = flipAxesAndAssignTags(hax,Predictors{i});
                
                set([hax.YLabel],'String','');
                set(hax,'YTick',[]);
                set(hax,'Parent',obj.PlotPanel);
                set(hax,'Position',Pos);
                set(hax,'ButtonDownFcn',@obj.selectAxes);
                
                % Make the first predictor as selected
                if i == 1
                    X = xlim(hax(1));
                    Y = ylim(hax(1));
                    rectangle('Parent',hax(1),'Position',[X(1) Y(1) ...
                        diff(X) diff(Y)],'EdgeColor','blue',...
                        'LineWidth',2,'Selected','On');
                    obj.SelectedPredictors = Predictors(1);
                    obj.SelectedAxes = hax(1);
                end

                if i == NumPreds
                    DefaultVisibility = get(groot,'DefaultFigureVisible');
                    set(groot,'DefaultFigureVisible','Off')
                    hl.PickableParts = 'None';
                    obj.Legend = hl;
                    obj.setLegend();
                    set(groot,'DefaultFigureVisible',DefaultVisibility)
                end
                
                obj.AllAxesHandles.(Predictors{i}) = hax;
                delete(hf)
            end             
            
            delete(hfTemp)
            obj.JLabel.setText('');
            obj.JStatusBar.remove(obj.JProgressBar);
            
            % F. Refresh of the ToolGroup is sometimes necessary
            javaMethodEDT('validate', obj.JFrame)
            javaMethodEDT('repaint', obj.JFrame)

            hFig.KeyPressFcn   = @obj.figureKeyPress;
            hFig.KeyReleaseFcn = @obj.figureKeyRelease;
            hFig.WindowScrollWheelFcn  = @obj.scrollMouseWheel;
            
            obj.OverviewFigure = hFig;
            
        end
        
        function obj = setMainFigureLayout(obj,varargin)
            % Called by obj.showPlots, obj.applyPlotView and obj.expandView. 
            % It updates the plots when algorithm or plot settings are 
            % changed.
            
            if obj.isViewExpanded
                notify(obj,'SelectedFigureTab');
            else
                if (nargin > 1) 
                    if strcmpi(varargin{1},'predictortype')
                        Predictors = obj.ModifiedPredictors;
                    elseif strcmpi(varargin{1},'BinningAppAlgorithmSettings')
                        Predictors = obj.SelectedPredictors;
                    else
                        Predictors = obj.PredictorVars;
                    end
                else
                    Predictors = obj.SelectedPredictors;
                end
                
                if obj.isWOEChanged || obj.isBinTextChanged
                    obj.showHideTextOrWOE(Predictors);
                else
                    obj.updatePlot(Predictors,varargin{:});
                end
            end
            
            % No need to update Bin Info and Predictor Info if the call
            % was triggered when the Plot Options were changed
            if ~obj.isBinTextChanged && ~obj.isWOEChanged
                obj.setBinInfo();
                obj.setPredictorInfo();
            end

        end
        
        function obj = showHideTextOrWOE(obj,Predictors)
            % Show or hide the WOE curve and/or the bin count labels.
            % Called by 'obj.setMainFigureLayout'
            
            for i = 1 : length(Predictors)
                if obj.isWOEChanged
                    obj.AllAxesHandles.(Predictors{i})(2).Children.Visible = obj.PlotSettings.WOE;
                end
                
                if obj.isBinTextChanged
                    bi = bininfo(obj.Scorecard,Predictors{i},'Totals','Off');
                    FreqTable = bi{:,{'Good','Bad'}};
                    if isempty(obj.Scorecard.ResponseVar)
                        Bool = false;
                    else
                        Bool = true;
                    end
                    % Create Bin labels
                    BinLabels = internal.finance.binning.utils.FormatBinLabels(FreqTable,Bool,size(FreqTable,1),obj.PlotSettings.BinText);

                    % Display Bin Labels
                    XTagLoc = obj.AllAxesHandles.(Predictors{i})(1).XTick;
                    YTagLoc = sum(FreqTable,2);
                    
                    % In case unbinned data are used, the size of XTick might be
                    % unreliable. Need to check size of XTagLoc.
                    hBar = risk.internal.app.utils.extractChildren(obj.AllAxesHandles.(Predictors{i})(1),'bar');
                    if numel(XTagLoc) ~= numel(YTagLoc)
                        if any(isnan(hBar(1).YData))
                            XTagLoc = 1;
                        else
                            XTagLoc = hBar(1).XData';
                        end
                        obj.AllAxesHandles.(Predictors{i})(1).XTick = XTagLoc;
                    end
                    
                    delete(risk.internal.app.utils.extractChildren(obj.AllAxesHandles.(Predictors{i})(1),'text'));
%                     drawnow;
                    if ~isempty(BinLabels)
                        if Bool 
                            obj.BinTextHandles.(Predictors{i}) = text(XTagLoc,...
                                YTagLoc,strcat(BinLabels(:,1),BinLabels(:,2),...
                                BinLabels(:,3)),'FontWeight','Bold','HorizontalAlignment',...
                                'Center','VerticalAlignment','Bottom','Parent',...
                                obj.AllAxesHandles.(Predictors{i})(1));
%                             drawnow;
                        else
                            obj.BinTextHandles.(Predictors{i}) = text(XTagLoc,...
                                YTagLoc,BinLabels,'FontWeight','Bold',...
                                'HorizontalAlignment','Center','VerticalAlignment',...
                                'Bottom','Parent',obj.AllAxesHandles.(Predictors{i})(1));
%                             drawnow;
                        end
                    end
                end
            end
            
        end
        
        function obj = updatePlot(obj,Predictors,varargin)
            % Auxiliary method, called by 'obj.setMainFigureLayout' and 
            % 'obj.updateZoomedFigure'
            
            if obj.isPlotChanged
                NumPreds = length(Predictors);
                javaMethodEDT('setSharedStatusBar', obj.JFrame, obj.JStatusBar);
                obj.JProgressBar = javaObjectEDT('javax.swing.JProgressBar');
                obj.JStatusBar.add(obj.JProgressBar);
                Delta = 100/NumPreds;

                FigureVisibility = get(groot,'DefaultFigureVisible');
                set(groot,'DefaultFigureVisible','off');
                
                for i = 1 : NumPreds
                    if ~obj.isViewExpanded
                        Value = obj.JProgressBar.getValue()+Delta;
                        obj.JProgressBar.setValue(Value);
                        obj.JLabel.setText(risk.internal.app.utils.getMessage('risk:binningexplorerapp:JLabelPlot',...
                            Predictors{i},num2str(i),num2str(NumPreds)));

                    end
                    HistAxes = obj.AllAxesHandles.(Predictors{i})(1);
                    Pos = HistAxes.Position;
                    delete(obj.AllAxesHandles.(Predictors{i}));
                    hf  = plotbins(obj.Scorecard,Predictors{i},...
                        obj.getPlotSettings{:});
                    hf.HandleVisibility = 'Off';
                    hax = risk.internal.app.utils.extractChildren(hf,'axes');
                    hl  = risk.internal.app.utils.extractChildren(hf,'legend');
                    hb  = risk.internal.app.utils.extractChildren(hax,'bar');
                    hL  = risk.internal.app.utils.extractChildren(hax,'line');
                    
                    if length(hax) == 1
                        hax.YLabel.String = '';
                        hax.YTick = [];
                    else
                        set([hax.YLabel],'String','');
                        set(hax,'YTick',[]);
                        hax = flipAxesAndAssignTags(hax,Predictors{i});
                    end
                    
                    set(hax,'Parent',obj.PlotPanel,'Position',Pos);
                    hax(1).Box = 'On';
                    if i == NumPreds
                        obj.Legend = hl;
                        hl.PickableParts = 'None';
                        obj.setLegend();
                    end
                    delete(hf)

                    % Figure selection
                    if ismember(Predictors{i},obj.SelectedPredictors)
                        X = xlim(hax(1));
                        Y = ylim(hax(1));
                        rectangle('Parent',hax(1),'Position',[X(1) Y(1) ...
                            diff(X) diff(Y)],'EdgeColor','blue',...
                            'LineWidth',2,'Selected','On');
                        if all(isvalid(obj.SelectedAxes))
                            obj.SelectedAxes = [obj.SelectedAxes; hax(1)];
                        else
                            obj.SelectedAxes = hax(1);
                        end
                    end
                    
                    set(hb,'PickableParts','none');
                    set(hL,'PickableParts','none');
                    set(hax,'ButtonDownFcn',@obj.selectAxes);
                    obj.AllAxesHandles.(Predictors{i}) = hax;
                
                end
                
                set(groot,'DefaultFigureVisible',FigureVisibility);
                
                obj.JLabel.setText('');
                obj.JStatusBar.remove(obj.JProgressBar);
                javaMethodEDT('validate', obj.JFrame);
                javaMethodEDT('repaint', obj.JFrame);
            end
            
        end
        
        function obj = updateZoomedFigure(obj,varargin)
            % Listener to the event 'SelectedFigureTab'. Makes sure the
            % selected figure in the 'Overview' tab is the same as in the
            % 'Manual' tab. Called by 'obj.selectAxes' and 
            % 'obj.setMainFigureLayout'
            
            FigureVisibility = get(groot,'DefaultFigureVisible');
            set(groot,'DefaultFigureVisible','off');
            Predictor = obj.SelectedPredictors{1};
            delete(obj.ZoomedFigure.Children);
            hf = plotbins(obj.Scorecard,Predictor,obj.getPlotSettings{:});
            hax = risk.internal.app.utils.extractChildren(hf,'Axes');
            
            hax = flipAxesAndAssignTags(hax,Predictor);

            set(hax,'Parent',obj.ZoomedFigure);
            hax(1).Box = 'On';
            obj.ZoomedFigure.WindowButtonDownFcn   = @obj.selectBin; 
            obj.ZoomedFigure.WindowButtonMotionFcn = @obj.showBinInformation;
            obj.ZoomedFigure.Tag  = Predictor;
            obj.ZoomedFigure.Name = risk.internal.app.utils.getMessage('risk:binningexplorerapp:ManualFigureName',Predictor);
            
            try
                % When the ZoomedFigure is deleted before closing the app.
                obj.ZoomedFigure.CloseRequestFcn = @obj.closeZoomedFigure;
            catch MException
                throw(MException);
            end

            obj.resetBinEdgesMembersAfterReplot(hax);
            
            delete(hf);
            
            if obj.isShiftApplied || obj.isSplitApplied || ...
                    obj.isMergeApplied || obj.isPredictorChanged
                obj.ManualBinningSection.remove(obj.ManualBinningPanel);
                obj.ManualBinningSection.add(obj.LocalSplitMergeSection);
            end
            
            % Update the Predictor type string and the status of the
            % "Members" checkbox
            if ismember(Predictor,obj.getNumericPredictors())
                obj.CtrlsManual.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Numeric');
%                 obj.CtrlsManual.TableButtons(end).Selected = false; %
%                 does not work % re: toolstrip is rebuild with default
%                 "selection" on (default on checkbox)
                obj.CtrlsManual.TableButtons(end).Selected = false;
                obj.CtrlsManual.TableButtons(end).Enabled = false;
            elseif ismember(Predictor,obj.getOrdinalPredictors())
                obj.CtrlsManual.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Ordinal');
                obj.CtrlsOverview.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Ordinal');
                obj.CtrlsManual.TableButtons(end).Enabled = true;
            else
                obj.CtrlsManual.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Categorical');
                obj.CtrlsOverview.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Categorical');
                obj.CtrlsManual.TableButtons(end).Enabled = true;
            end
          
            % Update the Selected Predictor string in the toolstrip
            if length(obj.SelectedAxes) < 2
                obj.CtrlsOverview.PredictorButtons(4).Text = Predictor;
            else
                obj.CtrlsOverview.PredictorButtons(4).Text = '';
            end
            
            set(groot,'DefaultFigureVisible',FigureVisibility);
            
            if obj.isBinTextChanged || obj.isWOEChanged
                obj.showHideTextOrWOE(obj.PredictorVars);
            else
                obj.updatePlot({Predictor});
            end
            
        end
        
        function obj = figureKeyPress(obj,src,ev)
            % Callback on the 'KeyPressFcn' of the main figure

            if strcmpi(ev.Key,'control')
                src.UserData = 'ctrlclickon';
            end
        end
        
        function obj = figureKeyRelease(obj,src,ev)
            % Callback on the 'KeyReleaseFcn' of the main figure

            if strcmpi(ev.Key,'control')
                src.UserData = 'ctrlclickoff';
            end
            
        end
                
        function obj = figureShiftPress(obj,src,ev)
            % Callback on the 'WindowKeyPressFcn' of the zoomed figure.
            % Called through 'obj.selectBin'.

            if strcmpi(ev.Key,'shift')
                src.UserData = 'shiftclickon';
            end
            
        end
                
        function obj = figureShiftRelease(obj,src,ev)
            % Callback on the 'WindowKeyReleaseFcn' of the zoomed figure. 
            % Called through 'obj.selectBin'.

            if strcmpi(ev.Key,'shift')
                src.UserData = 'shiftclickoff';
            end
            
        end
        
        function obj = showPlots(obj,varargin)
            % Called by 'obj.setPredictorType' and 'obj.runAlgorithm' and 
            % 'obj.savePredictorSettings'. It is also called by the class 
            % BinningAppAlgorithmSettings, upon pressing the 'OK' button 
            % on the settings window.
            % This method runs automatic binning and displays the updated
            % plots.
            
            % Get the algorithm name and options
            AlgoName    = obj.Algorithm.AlgoName;
            AlgoOptions = obj.Algorithm.Settings;
            Predictors  = obj.SelectedPredictors;
            
            if nargin > 1
                if strcmpi(varargin{1},'BinningAppAlgorithmSettings')
                    % The boolean 'isAlgoUICalled' helps reset the boolean
                    % 'isPlotChanged' to false, when the user uses algorithm
                    % settings in an expanded view, then returns to the 
                    % overview to update it.
                    obj.isPlotChanged  = true;
                    obj.isAlgoUICalled = true; 
                    
                elseif strcmpi(varargin{1},'predictortype')
                    % Case when called by obj.savePredictorSettings:
                    % When some or all predictors have their types changed,
                    % they need to be updated, not just the selected ones
                    % in the figure area.
                    Predictors = obj.ModifiedPredictors;
                    
                end
            end
            
            % Update the binning
            try
                obj.Scorecard = autobinning(obj.Scorecard,Predictors,...
                    'Algorithm',AlgoName,'AlgorithmOptions',AlgoOptions);
                obj.isScorecardExported = false;
                
            catch MException
                msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:InvalidAlgoOptionsMsg',MException.message),...
                    risk.internal.app.utils.getMessage('risk:binningexplorerapp:InvalidAlgoOptionsTitle'),'warn','modal');
                return;
            end
           
            % Plot or update the toolstrip figure(s)
            obj.setMainFigureLayout(varargin{:});
            
            % Reset flag
            if obj.isAlgoUICalled
                obj.isAlgoUICalled = false;
            end
 
        end
        
        function obj = selectAxes(obj,varargin)
            % ButtonDownFcn callback function on the subplots axes. Toggles 
            % the axes selection to 'On' or 'Off', through setting a border 
            % around the axes.
            % This method is called by 'obj.setInitialAppLayout'
            
            persistent MouseClick
            
            if ~isempty(obj.OverviewFigure.UserData) && ...
                    strcmpi(obj.OverviewFigure.UserData,'ctrlclickon')
                
                obj.CtrlsOverview.ExpandButton.Enabled = false;
                obj.selectMultipleAxes(varargin{:});
                return;
            end
            
            src = varargin{1};
            
            obj.SelectedAxes(1).Box = 'On';
            structfun(@(c)delete(risk.internal.app.utils.extractChildren(c,...
                'rectangle')),obj.AllAxesHandles,'UniformOutput',false);
            X = src.XLim; 
            Y = src.YLim;
            rectangle('Parent',src,'Position',[X(1) Y(1) diff(X) diff(Y)],...
                'EdgeColor','blue','LineWidth',2,'Selected','On');
            
            Predictor = src.Tag;
            obj.PreviousPredictor  = obj.SelectedPredictors(1);
            obj.SelectedPredictors = {Predictor};
            obj.SelectedAxes = src;
            obj.CtrlsOverview.ExpandButton.Enabled = true;

            % Update the Predictor type string and members in bin info table
            if ismember(Predictor,obj.getCategoricalPredictors())
                if isordinal(obj.Scorecard.Data.(Predictor))
                    obj.CtrlsOverview.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Ordinal');
                else
                    obj.CtrlsOverview.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Categorical');
                end
                obj.CtrlsOverview.TableButtons(end).Enabled = true;
            else
                obj.CtrlsOverview.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Numeric');
                obj.CtrlsOverview.TableButtons(end).Enabled = false;
            end
            
            % Update the Selected Predictor in the Toolstrip
            obj.CtrlsOverview.PredictorButtons(4).Text = Predictor;
            
            % Update statistic information
            obj.setBinInfo();
            obj.setPredictorInfo();
            
            % Display predictor information on the status bar
            StringInfo = obj.getStringPredictorInfo();
            obj.JStatusBar.setText(strjoin(StringInfo,' - '));
      
            % If double click, zoom on the selected axes.
            if isempty(MouseClick)
                MouseClick = 1;
                pause(0.5)
                if MouseClick == 1
                    % Single mouse click
                    MouseClick = [];
                end
            else
                % Double mouse click. Bring the obj.ZoomedFigure to the
                % foreground
                MouseClick = [];
                obj.expandView();                            
                figure(obj.ZoomedFigure) % bring up the zoomed figure
                return;
            end

            % Update the 'obj.ZoomedFigure' if open
            if obj.isViewExpanded
                obj.isPredictorChanged = true;
                notify(obj,'SelectedFigureTab');
                obj.isPredictorChanged = false;
            end
            
        end
        
        function obj = selectMultipleAxes(obj,varargin)
            % ButtonDownFcn callback function on the subplots axes, when 
            % the user CTRL-clicks. This method is called by 'obj.selectAxes'
            
            src = varargin{1};
            X  = src.XLim; 
            Y  = src.YLim;
            rectangle('Parent',src,'Position',[X(1) Y(1) diff(X) diff(Y)],...
                'EdgeColor','blue','LineWidth',2,'Selected','On');
            
            Predictor = src.Tag;
            obj.SelectedPredictors = unique([obj.SelectedPredictors ...
                Predictor],'stable');
            obj.SelectedAxes = [obj.SelectedAxes ; src];
            
            % Update the Predictor type string
            if all(ismember(obj.SelectedPredictors,obj.getCategoricalPredictors()))
                Bool = zeros(length(obj.SelectedPredictors),1);
                
                for i = 1 : length(obj.SelectedPredictors)
                    Bool(i) = isordinal(obj.Scorecard.Data.(obj.SelectedPredictors{i}));
                end
                
                if all(Bool)
                    obj.CtrlsOverview.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Ordinal');
                else
                    obj.CtrlsOverview.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Categorical');
                end
                
            elseif all(ismember(obj.SelectedPredictors,obj.getNumericPredictors()))
                obj.CtrlsOverview.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Numeric');
                
            else
                obj.CtrlsOverview.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:MixedType');
            end
            
            % Update the Selected Predictor name
            obj.CtrlsOverview.PredictorButtons(4).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:MultiSelect');
            
            % Update statistic information
            obj.setBinInfo('select');
            obj.setPredictorInfo('select');
            
            % Update the 'obj.ZoomedFigure' if open
            if obj.isViewExpanded
                notify(obj,'SelectedFigureTab');
            end
            
        end
        
        function obj = sliderCallback(obj,varargin)
            % Callback on the Slider UIcontrol on 'obj.PlotPanel'. Updates
            % the position of the panel.
            
            jPos = obj.ScrollBar.getValue();
            OrigPosition = varargin{3};
            obj.PlotPanel.Position(2) = OrigPosition(2) + ...
                (OrigPosition(4)-1)*(jPos/100);
            
        end
        
        function obj = scrollMouseWheel(obj,varargin)
            % Callback on the slider to update the position of the uipanel,
            % parent of all subplots.
            
            ev = varargin{2};
            jPos = obj.ScrollBar.getValue();
            
            if ev.VerticalScrollCount >0
                setValue(obj.ScrollBar,min(jPos+5,100));
            else
                setValue(obj.ScrollBar,max(jPos-5,0));
            end
            
        end
        
        function obj = figureResizeCallback(obj,varargin)
            % Callback on the zoomed figures. It keeps the figure
            % size fixed if the user double-clicks on its header.
            
            src = varargin{1};
            hax = risk.internal.app.utils.extractChildren(src,'axes');
            
            [ok,Ind1] = ismember({hax.Units}','normalized');

            if ~all(ok)
                if length(Ind1) == 2
                    Ind1 = find(Ind1);
                    Ind2 = setdiff([1 2],Ind1);
                    obj.ZoomedFigureAxesPosition = hax(Ind1).Position;
                    hax(Ind2).Units = hax(Ind1).Units;
                    hax(Ind2).Position = hax(Ind1).Position;
                else
                    hax.Units = 'normalized';
                    hax.Position = obj.ZoomedFigureAxesPosition; 
                end
                
            end
            
        end
        
        %% I/O Functions

        function obj = closeZoomedFigure(obj,varargin)
            % Deletes the zoomed figure used for manual binning, and sets
            % the object's property 'isViewExpanded' to false.
            
            try
                delete(obj.ZoomedFigure);
                obj.TabGroup = []; % Do we need this? Used this to avoid the issue with tab that disappears
            catch
                close all force;
                obj.approveClose;
                delete(obj);
            end
            
            obj.isViewExpanded = false;
            obj.OldBinNumber = [];
            obj.TooltipHandleBins = [];
            %obj.CtrlsOverview.ExpandButton.Enabled = true;
        end
        
        function CloseReqFcn(obj,varargin)
            % Close the app window         
            
            src = varargin{1};
            ev  = varargin{2};
            
            if strcmp(ev.EventData.EventType, 'CLOSING') && ...
                    src.isClosingApprovalNeeded
                
                if isempty(obj.OverviewFigure) 
                    obj.approveClose;
                    delete(obj);
%                      c = onCleanup(@() rmappdata(groot,'BinningAppHandle'));
                     
%                     AppData = getappdata(groot);
%                     if  ismember('binningapphandle',lower(fieldnames(AppData)))
%                         rmappdata(groot,'BinningAppHandle');
%                     end
                    return;
                end
                
                if ~obj.isScorecardExported
                    w = warning('off','MATLAB:callback:error');
                    c = onCleanup(@() warning(w));
                    try
                        CloseSelection = questdlg(risk.internal.app.utils.getMessage('risk:binningexplorerapp:CloseApp'));
                        
                        switch lower(CloseSelection)
                            case 'yes'
                                % Close and update the object's creditscorecard
                                cleanupOnYes();
                                
                            case 'no'
                                % Close without updating the object's
                                % creditscorecard
                                if ~isempty(obj.ZoomedFigure)
                                    delete(obj.ZoomedFigure);
                                end
                                obj.OverviewFigure.CloseRequestFcn = 'closereq';
                                obj.BinInfo.CloseRequestFcn = 'closereq';
                                obj.PredictorInfo.CloseRequestFcn = 'closereq';
                                obj.approveClose;
                                delete(obj);
                                
                            case 'cancel'
                                obj.vetoClose;
                        end
                    catch
                        close all force
                        obj.approveClose;
                        delete(obj);
                        if ismember('binningapphandle',lower(fieldnames(getappdata(groot))))
                            rmappdata(groot,'BinningAppHandle')
                        end
                    end
                else
                   cleanupOnYes();
                   
                end
            end
            
            function cleanupOnYes()
                try
                    if isvalid(obj.ZoomedFigure)
                        delete(obj.ZoomedFigure);
                    end
                catch
                end
                obj.OverviewFigure.WindowButtonMotionFcn = '';
                obj.OverviewFigure.CloseRequestFcn = 'closereq';
                obj.BinInfo.CloseRequestFcn = 'closereq';
                obj.PredictorInfo.CloseRequestFcn = 'closereq';
                if ~obj.isScorecardExported
                    obj.exportScorecard();
                end
                obj.approveClose;
                delete(obj);
            end
            
        end
        
        function obj = loadData(obj,varargin)
            % If the user does not call Binningapp from the command line,
            % where they supply the creditscorecard object as input
            % argument, then they can launch the app and load the
            % data either form a table or an existing creditscorecard in the
            % workspace. This is a callback on the 'Load Data' button.
            
            % Disable the button to avoid double clicking if HG engine is
            % launched for the first time
            obj.CtrlsOverview.FileButtons.Enabled = false;
            
            w  = 1100     ; h  = 550;
            x0 = 400      ; y0 = 300;
            x1 = 2        ; y1 = 45;
            w1 = 0.25*w   ; h1 = h-y1-1;
            x2 = x1+w1+x1 ; y2 = y1;
            w2 = 0.45*w   ; h2 = h1;
            x3 = x2+w2+x1 ; y3 = y1;
            w3 = 0.295*w  ; h3 = h1;

            hf = figure('Menubar','None','IntegerHandle','Off',...
                'HandleVisibility','Off','Resize','Off','Name',...
                risk.internal.app.utils.getMessage('risk:binningexplorerapp:ImportDataText'),'Position',...
                [x0 y0 w h],'Visible','Off','CloseRequestFcn',...
                @obj.cancelLoadedData,'WindowStyle','modal');
            movegui(hf,'center');
            
            % Step 1: Data selection panel. The user must first click on the
            % data set to load before the table in Step 2 is displayed and
            % the algorithm radiobuttons are enabled.
            hc = obj.createDataSelectionPanel(hf,h,x1,y1,w1,h1);
            
            if isempty(hc)
                if obj.NumApps == 0
                    obj.NumApps = 1;
                else
                    obj.NumApps = 0;
                end
                return;
            end

            % Step 2: Predictor type panel. Empty panel until the user
            % selects the dataset.
            hf = obj.createPredictorTypePanel(hf,h,x1,x2,y2,w2,h2);
            
            % Step 3: Initial algorithm panel. Disabled until the user
            % selected a dataset.
            obj.createInitialAlgorithmPanel(hf,h,x1,x3,y3,w3,h3);
            
            % Ok/Cancel Buttons
            uicontrol('Parent',hf,'String',...
                risk.internal.app.utils.getMessage('risk:binningexplorerapp:ImportDataText'),'Units',...
                'pixels','Position',[x3 0.08*y3 w3/2-3 38],'Callback',...
                {@(src,ev)obj.saveLoadedData(src,ev,hf)},'Tag','importadatabtn');
            
            uicontrol('Parent',hf,'String',...
                risk.internal.app.utils.getMessage('risk:binningexplorerapp:Cancel'),'Units',...
                'pixels','Position',[x3+w3/2 0.08*y3 w3/2-3 38],'Callback',...
                @obj.cancelLoadedData,'Tag','cancelimportbtn');
            
            % Set the default table selection to the first item in the list
            EventData = struct('Indices',[1 1],'Source',hc,'EventName','CellSelection');
            
            obj.selectData(hc,EventData,hf);
            
            obj.CtrlsOverview.FileButtons.Enabled = true;
              
        end
        
        function obj = saveLoadedData(obj,src,~,hf)
            % Callback on the 'Ok' button in the window invoked by
            % pressing on the button 'Load Data'. Called by the method 
            % 'obj.load Data'
            
            hp = risk.internal.app.utils.extractChildren(hf,'uipanel','Tag','predictorpanel');
            ht = risk.internal.app.utils.extractChildren(hp,'uitable','Tag','predictortable');
            data = obj.OriginalData;
            
            % Set predictors and response
            ht = obj.setPredictorResponse(ht,obj.ResponseChangedEvent,false);
            if isempty(ht)
                return;
            end
            
            Data = ht.Data;
            Vars = ht.RowName;
            
            % Remove excluded variables from the data
            Ind1 = strcmpi(Data(:,2),risk.internal.app.utils.getMessage('risk:binningexplorerapp:DoNotInclude'));
            if any(Ind1)
                Data(Ind1,:) = [];
                ExcludedPredictors = Vars(Ind1);
                if isa(data,'table')
                    for i = 1 : length(ExcludedPredictors)
                        data.(ExcludedPredictors{i}) = [];
                    end
                else
                    data.PredictorVars = setdiff(data.PredictorVars,...
                        ExcludedPredictors,'stable');
                end
                Vars(Ind1) = [];
            end
            
            if isempty(Data) || size(Data,1) < 2
                msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:LoadDataMsg'),...
                    risk.internal.app.utils.getMessage('risk:binningexplorerapp:LoadDataTitle'),'warn','modal');
                return;
            end
            
            Ind2 = strcmpi(Data(:,2),risk.internal.app.utils.getMessage('risk:binningexplorerapp:Response'));
            Response = Vars(Ind2);
            
            % Processing the response data
            if isa(data,'table')
                if isempty(Response)
                    msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:MissingResponseMsg'),...
                    risk.internal.app.utils.getMessage('risk:binningexplorerapp:MissingResponseTitle'),'warn','modal');
                    return;
                end
                RespData = data.(Response{:});
               
                % Put the response at the end of the table and re-arrange the
                % data columns
                data.(Response{:}) = [];
                data(:,end+1) = table(RespData);
                data.Properties.VariableNames(end) = Response;   
            end
            
            % Create creditscorecard to launch the app from
            if isa(data,'table')
                obj.Scorecard = creditscorecard(data,'ResponseVar',Response);
            else
                obj.Scorecard = data;
            end
            
            % Update the initial algorithm
            hp = risk.internal.app.utils.extractChildren(hf,'uipanel','Tag','algorithmpanel');
            hb = risk.internal.app.utils.extractChildren(hp,'uibuttongroup');
            AlgoName = hb.SelectedObject.String;
            obj.Algorithm.AlgoName = regexprep(AlgoName,' ','');

            % Clean up previous session
            if obj.NumApps > 0
                obj.cleanUpPreviousSession();
                obj.NumApps = 0;
            else
                obj.NumApps = 1;
            end
            
            % Close the window
            delete(src.Parent)
            
            % Launch the app's main layout
            obj.setupBinningApp();

        end

        function obj = cancelLoadedData(obj,src,~)
            if isa(src,'matlab.ui.Figure')
                delete(src)
            else
                delete(src.Parent);
            end
            if ~isempty(obj.OverviewFigure)
                obj.NumApps = 1;
            else
                obj.NumApps = 0;
            end
%             obj.NumApps = 0;
        end
        
        function obj = exportScorecard(obj,varargin)
            % Callback on the 'Export Scorecard'
            
            prompt = {risk.internal.app.utils.getMessage('risk:binningexplorerapp:ExportScorecard')};
            title  = risk.internal.app.utils.getMessage('risk:binningexplorerapp:ExportSection');
            lines  = 1;
            WkVars = evalin('base','whos');
            def    = getNameWithIncrement(WkVars);
            answer = inputdlg(prompt, title, lines, def);
            
            if ~isempty(answer)                
                try
                    if ismember(answer,{WkVars.name}')
                        Choice = questdlg(risk.internal.app.utils.getMessage('risk:binningexplorerapp:InvalidExportNameMsg2',answer{:}),...
                            risk.internal.app.utils.getMessage('risk:binningexplorerapp:InvalidExportNameTitle'),...
                            risk.internal.app.utils.getMessage('risk:binningexplorerapp:Yes'),...
                            risk.internal.app.utils.getMessage('risk:binningexplorerapp:No'),...
                            risk.internal.app.utils.getMessage('risk:binningexplorerapp:No'));
                        
                        switch lower(Choice)
                            case 'no'
                                return;
                            case 'yes'
                                assignin('base',answer{:},obj.Scorecard);
                        end
                    else
                        assignin('base',answer{:},obj.Scorecard);
                    end
                catch MException
                    if strcmpi(MException.identifier,'MATLAB:assigninInvalidVariable')
                        FigureVisibility = get(groot,'DefaultFigureVisible');
                        set(groot,'DefaultFigureVisible','Off')
                        hMsg =  msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:InvalidExportNameMsg1'),... %''
                             risk.internal.app.utils.getMessage('risk:binningexplorerapp:InvalidExportNameTitle'),...
                             'warn','modal');
                        createUrlForNameFormat();
                        set(groot,'DefaultFigureVisible',FigureVisibility)
                    else
                        throw(MException)
                    end
                end 
            end
            
            obj.isScorecardExported = true;
           
            % Display on the status bar that the scorecard was saved in the
            % workspace
            obj.JStatusBar.setText(risk.internal.app.utils.getMessage('risk:binningexplorerapp:ExportScorecardStatus',def{:}));
            
            function Name = getNameWithIncrement(W)
                Radical  = 'sc';
                Names    = {W.name}';
                Ind      = ~cellfun('isempty',regexpi(Names,Radical));
                Suffixe  = cellfun(@(c) c(3:end),Names(Ind),'UniformOutput',false);
                Nums     = cellfun(@(c) str2double(c),Suffixe,'UniformOutput',false);
                NumMat   = cell2mat(Nums);
                IndNaN   = isnan(NumMat);
                if isempty(NumMat)
                    Name = {Radical};
                elseif any(IndNaN)
                    Inds = NumMat(~IndNaN);
                    if isempty(Inds)
                        Inds = 0;
                    end
                    Name = {[Radical num2str(1+max(Inds))]};
                else
                    Name = {[Radical num2str(1+max(NumMat))]};
                end
            end
            
            function createUrlForNameFormat()
                hMsg.Position(4) = 100;
                hi = risk.internal.app.utils.extractChildren(hMsg,'Axes','Tag','IconAxes');
                ha = setdiff(risk.internal.app.utils.extractChildren(hMsg,'Axes'),hi);
                ha.Children.Position(2) = 65;
                hi.Position(2) = 50;
                url = 'http://www.mathworks.com/help/matlab/matlab_prog/variable-names.html';
                labelStr = ['<html>More info: <a href="">' sprintf('%s','MATLAB name conventions') '</a></html>'];
                jLabel = javaObjectEDT('javax.swing.JLabel', labelStr);
                [hjLabel,~] = javacomponent(jLabel, [50,40,250,35],hMsg);
                
                FigColor = hMsg.Color*255;
                Color = java.awt.Color(int16(FigColor(1)),int16(FigColor(2)),int16(FigColor(3)));
                hjLabel.setBackground(Color);
                hjLabel.setOpaque(true);
                % Modify the mouse cursor when hovering on the label
                hjLabel.setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.HAND_CURSOR));
                
                % Set the label's tooltip
                hjLabel.setToolTipText(['Visit the: ' url ' website']);
                
                % Set the mouse-click callback
                set(hjLabel, 'MouseClickedCallback', @(h,e)web(['http://' url], '-browser'))
            end
            
        end
        
        function obj = cleanUpPreviousSession(obj)
            % Called by 'obj'saveLoadedData'. Cleans up all previous
            % properties of the App prior to loading new data.
            
            obj.OverviewFigure.CloseRequestFcn = 'closereq';
            obj.BinInfo.CloseRequestFcn = 'closereq';
            obj.PredictorInfo.CloseRequestFcn = 'closereq';
            
            try
                delete(obj.OverviewFigure);
                delete(obj.BinInfo);
                delete(obj.PredictorInfo);
                delete(obj.TabGroup);
                delete(obj.ZoomedFigure);
            catch
                % Do nothing
            end
            
            obj.enableControls(false);
            obj.isAlgoUICalled = false;
            obj.AllAxesHandles = struct();
            obj.BinTextHandles = struct();
            
            obj.LeftEdgeValue = -Inf;
            obj.RightEdgeValue = Inf;
            
            obj.SelectedBins = [];
            obj.BinMembers = {};
            obj.TooltipHandleBins = [];
            
            obj.isMergeApplied = false;
            obj.isShiftApplied = false;
            obj.isSplitApplied = false;
            obj.isDuplicateCutPoint = false;
            obj.isPredictorChanged  = false;

            obj.OldNumBins = 2;
            obj.SelectedCategories = {};
            obj.DefaultBinListing  = {};
            obj.OldBinNumber = [];            
            
            obj.isWOEChanged   = false;
            obj.isBinTextChanged = false;
            
            obj.PlotMenu = [];
            obj.TableMenu = [];
            obj.isInitPlot  = true;  
            obj.isInitTable = true;    

        end
        
        function obj = setupBinningApp(obj)
            % This methods sets the default algorithm, algorithm settings
            % and other plot and statistics settings.
            
            obj.NumApps = 1;
            
            % Set up the default algorithm settings for future use
            obj.setDefaultAlgoSettings();
            
            if isempty(obj.Algorithm.AlgoName)
                obj.Algorithm.AlgoName = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Monotone');   
            end

            if ~strcmpi(obj.Algorithm.AlgoName,'nobinning')
                obj.Algorithm.Settings = obj.DefaultAlgoSettings.(obj.Algorithm.AlgoName);           
            end          
            
            % Step 1: Store predictors
            obj.PredictorVars = obj.Scorecard.PredictorVars(:);
            
            % A. Integrate the waitbar in the status bar.
            % Get the Java frame hosting the ToolGroup
            md = com.mathworks.mlservices.MatlabDesktopServices.getDesktop;
            jFrame = md.getFrameContainingGroup(obj.Name);
            
            % B. Create a new shared status bar
            StatusBar = javaObjectEDT('com.mathworks.mwswing.MJStatusBar');
            javaMethodEDT('setSharedStatusBar', jFrame, StatusBar);
            
            % C: Status bar original text.
            StatusBar.setText(risk.internal.app.utils.getMessage('risk:binningexplorerapp:StatusBar',...
                num2str(length(obj.PredictorVars)),...
                num2str(length(obj.getNumericPredictors())),...
                num2str(length(obj.getCategoricalPredictors()))));
            
            % D. Add Java components to the status bar
            Label = javaObjectEDT('javax.swing.JLabel',risk.internal.app.utils.getMessage('risk:binningexplorerapp:JLabelBin','0'));
            StatusBar.add(Label);
            ProgressBar = javaObjectEDT('javax.swing.JProgressBar');
            StatusBar.add(ProgressBar);
            
            % E. Update the progress bar status
            if ~strcmpi(obj.Algorithm.AlgoName,'nobinning')
                NumPreds = length(obj.PredictorVars);
                Delta   = 100/NumPreds;
                % Display the "loading data" message
                hf = figure('IntegerHandle','Off','HandleVisibility','Off','Name','Binning data');
                obj.addFigure(hf);
                grpname = obj.Name;
                md.setDocumentArrangement(grpname,md.TILED,java.awt.Dimension(1,1));
                loc = com.mathworks.widgets.desk.DTLocation.create(0);     
                md.setClientLocation('Importing Data',grpname,loc);
                uicontrol('Parent',hf,'Style','Text','Units',...
                    'Normalized','Position',[0.25 0.3 0.5 0.2],'String',...
                    'Please wait while binning the data','FontSize',12);
                hf.Pointer = 'watch';
                pause(1); % For small dataset, in case we need to display the message.
                
                for i = 1 : NumPreds
                    obj.Scorecard = autobinning(obj.Scorecard,...
                        obj.PredictorVars{i},'Algorithm',...
                        obj.Algorithm.AlgoName,'AlgorithmOptions',...
                        obj.Algorithm.Settings);
                    Value = ProgressBar.getValue()+Delta;
                    ProgressBar.setValue(Value);
                    Label.setText(risk.internal.app.utils.getMessage('risk:binningexplorerapp:JLabelBin',num2str(Value)));
                end
                
                delete(hf);
                ProgressBar.setValue(0);
                
            else
                % No need to bin, but need to set the default algorithm to
                % 'Monotone' for future use in the Algorithm Options window
                obj.Algorithm.AlgoName = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Monotone');   
                obj.Algorithm.Settings = obj.DefaultAlgoSettings.(obj.Algorithm.AlgoName);
            end
            
            Label.setText('');
            obj.JFrame = jFrame;
            obj.JStatusBar = StatusBar;
            obj.JProgressBar = ProgressBar;
            obj.JLabel = Label;
           
            % F. Refresh of the ToolGroup is sometimes necessary
            javaMethodEDT('validate', jFrame);
            javaMethodEDT('repaint', jFrame);

            % Step 2: Store figure, its default plot setting, the
            % creditscorecard object and table of default statistics.
            obj.SelectedPredictors = obj.Scorecard.PredictorVars(1);
            obj.PreviousPredictor  = {};
            obj.ModifiedPredictors = {};
            obj.PlotSettings   = struct('BinText','None','WOE','On');
            obj.TableSettings  = {'odds','woe','infovalue'};
            obj.isPlotChanged  = false;
            obj.isViewExpanded = false;
            obj.TooltipOn  = true;
            obj.StatsTable = struct(obj.SelectedPredictors{:},...
                bininfo(obj.Scorecard,obj.SelectedPredictors{:},'Statistics',...
                obj.TableSettings));
            
            if strcmpi(obj.Algorithm.AlgoName,'nobinning')
                obj.CtrlsOverview.AlgoButtons(1).Text = ...
                    risk.internal.app.utils.getMessage('risk:binningexplorerapp:ApplyAlgo','Monotone');
            else
                AlgoNameDisp = risk.internal.app.utils.formatNamesWithoutSpace({obj.Algorithm.AlgoName},'equal');
                obj.CtrlsOverview.AlgoButtons(1).Text = ...
                    risk.internal.app.utils.getMessage('risk:binningexplorerapp:ApplyAlgo',AlgoNameDisp{:});
            end
            
            obj.CtrlsOverview.PlotButtons(1).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:None');
            
            % Step 3: Update the predictor type button 
            if ismember(obj.SelectedPredictors{1},obj.getOrdinalPredictors)
                obj.CtrlsOverview.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Ordinal');
            elseif ismember(obj.SelectedPredictors{1},obj.getCategoricalPredictors)
                obj.CtrlsOverview.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Categorical');
            else
                obj.CtrlsOverview.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Numeric');
            end
            
            % Step 4: Setup the predictor list for the predictors
            obj.CtrlsOverview.PredictorButtons(4).Text = obj.SelectedPredictors{1};
            obj.CtrlsOverview.PredictorButtons(4).Popup = obj.setPredictorList('text_only');
            obj.ActionListeners.PredSelection = ...
                addlistener(obj.CtrlsOverview.PredictorButtons(4).Popup,...
                'ListItemSelected',@obj.selectAxesWrapper);            
            
            % Step 5: Plot all predictors on the main figure
            obj.setInitialAppLayout();      

            % Step 6
            obj.isInitPlot  = false;
            obj.isInitTable = false;
            
            % Step 7: Update state of the "Member"s check box
            if ismember(obj.SelectedPredictors{1},obj.getCategoricalPredictors())
                obj.CtrlsOverview.TableButtons(end).Enabled = true;
                obj.CtrlsOverview.TableButtons(end).Selected = true;
            else
                obj.CtrlsOverview.TableButtons(end).Enabled = false;
                obj.CtrlsOverview.TableButtons(end).Selected = false;
            end
            
        end
        
        function obj = enableControls(obj,bool)
            % Called by the constructor and by 'obj.setupBinningApp'.
            % If 'bool' is false, then all controls on the toolstrip are
            % disabled. If 'true', they are enabled.
            
            Fields = setdiff(fieldnames(obj.CtrlsOverview),'FileButtons');
            
            for i = 1 : length(Fields)
                for j = 1 : length(obj.CtrlsOverview.(Fields{i}))
                    obj.CtrlsOverview.(Fields{i})(j).Enabled = bool;
                end
            end
            
        end

        function hc = createDataSelectionPanel(obj,hf,h,x1,y1,w1,h1)
            % Method called by 'obj.loadData'. Sets the panel for Step 1:
            % Data selection.

            Wkspace = evalin('base','whos');
            WkspaceClass = {Wkspace.class};
            [ok,~]  = ismember(lower(WkspaceClass),{'table','creditscorecard'});
            WkspaceVars  = {Wkspace(ok).name};
            
            if isempty(WkspaceVars)
                msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:DataSelectMsg'),...
                    risk.internal.app.utils.getMessage('risk:binningexplorerapp:DataSelectTitle'),...
                    'warn','modal');
                delete(hf);
                hc = [];
                obj.NumApps = 0;
                obj.CtrlsOverview.FileButtons.Enabled = true;
                return;
            else
                hf.Visible = 'On';
            end

            hp = uipanel('Parent',hf,'Units','pixels','Position',...
                [x1 y1 w1 h1],'Tag','datapanel');

            ha = axes('Parent',hp,'Visible','off','Units','Normalized',...
                'Position',[0 0 1 1]);

            text('Parent',ha,'Units','pixels','Position',[x1+1 0.87*h],...
                'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:StepNum','1'),...
                'FontWeight','bold','Fontsize',14);

            text('Parent',ha,'Units','pixels','Position',[x1+1 0.82*h],...
                'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:Step1Text'));

            hc = uitable('Parent',hp,'Units','Normalized','Position',...
                [0.03 0.05 0.95 0.82],'Data',[WkspaceVars' ...
                {Wkspace(ok).class}'],'RowName',[],'ColumnName',...
                {risk.internal.app.utils.getMessage('risk:binningexplorerapp:Variable') ...
                risk.internal.app.utils.getMessage('risk:binningexplorerapp:Class')},'ColumnWidth',...
                {120 134},'Tag','datatable','BackgroundColor',[1 1 1],...
                'CellSelectionCallback',@(src,ev)obj.selectData(src,ev,hf));
            
        end

        function obj = selectData(obj,src,ev,hf)
            % Callback on the listbox for data selection. Called by
            % 'obj.createDataSelectionPanel'.
            
            Ind = ev.Indices(:,1);
            if (length(Ind) > 1) || isempty(Ind)
                Ind = 1;
            end
            
            VarNames = src.Data(:,1);
            DataName = VarNames(Ind);

            data = evalin('base',DataName{:});
            
            % Display the predictor table
            hf = obj.createPredictorTable(hf,data);

            % Enable the radio buttons
            hp = risk.internal.app.utils.extractChildren(hf,'uipanel','Tag','algorithmpanel');
            hb = risk.internal.app.utils.extractChildren(hp,'uibuttongroup');

            arrayfun(@(c)set(c,'Enable','On'),risk.internal.app.utils.extractChildren(hb,...
                'uicontrol','Style','radiobutton'),'UniformOutput',false);

            obj.OriginalData = data;
            
            % If the loaded data is a table, set 'Monotone' as the default
            % algorithm. If the data is a creditscorecard object, set 'No
            % Binning' as the default.
            
            if isa(obj.OriginalData,'table')
                hb.SelectedObject = obj.MonotoneRadioBtn;
            else
                hb.SelectedObject = obj.NoBinningRadioBtn;
            end
            
            % Reset the event for changed response
            obj.ResponseChangedEvent = [];
            
            % Update the title for "Step 2"
            hp = risk.internal.app.utils.extractChildren(hf,'uipanel','Tag','predictorpanel');
            ha = risk.internal.app.utils.extractChildren(hp,'Axes');
            ht = risk.internal.app.utils.extractChildren(ha,'Text');
            set(ht,'Interpreter','None');
            Ind = regexpi('step 2',{ht.String}');
            Ind = cellfun('isempty',Ind);
            ht(Ind).String = [risk.internal.app.utils.getMessage('risk:binningexplorerapp:Step2Text') ...
                ' for "' DataName{:} '".'];

        end

        function hf = createPredictorTypePanel(~,hf,h,x1,x2,y2,w2,h2)
            % Method called by 'obj.loadData'. Sets the panel for Step 2:
            % Setting the predictor type (numeric, categorical, ordinal)

            % Create uipanel, uitable and axes
            hp = uipanel('Parent',hf,'Units','pixels','Position',...
                [x2 y2 w2 h2],'Tag','predictorpanel');

            ha = axes('Parent',hp,'Visible','off','Units','Normalized',...
                'Position',[0 0 1 1]);

            text('Parent',ha,'Units','pixels','Position',[x1+1 0.87*h],...
                'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:StepNum','2'),...
                'FontWeight','bold','FontSize',14);

            text('Parent',ha,'Units','pixels','Position',[x1+1 0.82*h],...
                'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:Step2Text'));

        end

        function hf = createPredictorTable(obj,hf,data)
            % Create a uitable to set the predictor type and determine 
            % which is predictor which is response.

            if isa(data,'table')
                Predictors = data.Properties.VariableNames;
                isNumericPreds = cellfun(@(c)isnumeric(data.(c)),Predictors');
                isOrdinalPreds = getOrdinalPredictorsFromData(data,Predictors);
                DataClass = cellfun(@(c) class(data.(c)),Predictors','UniformOutput',false);
            else 
                Predictors = [data.PredictorVars';data.ResponseVar];
                isNumericPreds = ismember(Predictors,data.NumericPredictors');
                isOrdinalPreds = getOrdinalPredictorsFromData(data.Data,Predictors);
                DataClass = cellfun(@(c) class(data.Data.(c)),Predictors','UniformOutput',false);
            end
            
            Ind = strcmpi(DataClass,'logical');
            isNumericPreds(Ind) = true;
            
            NumPreds = length(Predictors);
            PredType = cell(NumPreds,1);
            PredType(isNumericPreds(:))  = {risk.internal.app.utils.getMessage('risk:binningexplorerapp:Numeric')};
            PredType(~isNumericPreds(:)) = {risk.internal.app.utils.getMessage('risk:binningexplorerapp:Categorical')};  
            PredType(isOrdinalPreds(:)) = {risk.internal.app.utils.getMessage('risk:binningexplorerapp:Ordinal')};  
            
            if isa(data,'creditscorecard')
                if isnumeric(data.Data.(data.ResponseVar))
                    PredType{end} = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Numeric');
                else
                    PredType{end} = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Categorical');
                end
            end

            % Determining if the variable is a predictor or a response 
            VarType  = [repmat({risk.internal.app.utils.getMessage('risk:binningexplorerapp:Predictor')},NumPreds-1,1);...
                {risk.internal.app.utils.getMessage('risk:binningexplorerapp:Response')}];
            
            Data = [PredType VarType];
            if isa(data,'creditscorecard')
                VarTypeColFmt  = [];
                VarTypeColEdit = false;
            else
                VarTypeColFmt = {risk.internal.app.utils.getMessage('risk:binningexplorerapp:Predictor'),...
                    risk.internal.app.utils.getMessage('risk:binningexplorerapp:Response'),...
                    risk.internal.app.utils.getMessage('risk:binningexplorerapp:DoNotInclude')};
                VarTypeColEdit = true;
            end
            
            hp = risk.internal.app.utils.extractChildren(hf,'uipanel','Tag','predictorpanel');
            
            delete(risk.internal.app.utils.extractChildren(hp,'uitable','Tag','predictortable'));
            
            uitable('Parent',hp,'Data',Data,'Units','Normalized',...
                'Position',[0.05 0.05 0.9 0.8],'RowName',Predictors,...
                'ColumnWidth',{115 110},'ColumnName',...
                {risk.internal.app.utils.getMessage('risk:binningexplorerapp:PredictorType'),...
                risk.internal.app.utils.getMessage('risk:binningexplorerapp:VariableType')},...
                'ColumnFormat',{[],VarTypeColFmt},'ColumnEditable',[false VarTypeColEdit],'Tag','predictortable',...
                'CellEditCallback',@(src,ev)obj.setPredictorResponse(src,ev,true));
       
        end

        function hb = createInitialAlgorithmPanel(obj,hf,h,x1,x3,y3,w3,h3)
            % Method called by 'obj.loadData'. Sets the panel for Step 3:
            % Data selection.
    
            hp = uipanel('Parent',hf,'Units','pixels','Position',...
                [x3 y3 w3 h3],'Tag','algorithmpanel');

            ha = axes('Parent',hp,'Visible','off','Units','Normalized',...
                'Position',[0 0 1 1]);

            text('Parent',ha,'Units','pixels','Position',[x1+1 0.87*h],...
                'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:StepNum','3'),...
                'FontWeight','bold','FontSize',14);

            text('Parent',ha,'Units','pixels','Position',[x1+1 0.82*h],...
                'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:Step3Text'));

            hb = uibuttongroup('Parent',hp,'Visible','on','Units','Normalized',...
                'Position',[0.05 0 0.9 0.85],'BorderType','None');

            obj.MonotoneRadioBtn = uicontrol(hb,'Style','radiobutton',...
                'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:Monotone'),...
                'FontWeight','bold','Units','Normalized','Position',...
                [0.05 0.95 0.8 0.05],'Enable','Off','Tag','monotoneradiobtn');

            uicontrol('Parent',hp,'Style','Text','Units','Normalized',...
                'Position',[0.15 0.695 0.75 0.1],'String',...
                risk.internal.app.utils.getMessage('risk:binningexplorerapp:MonotoneDescription'),...
                'HorizontalAlignment','left');

            uicontrol(hb,'Style','radiobutton','String',...
                risk.internal.app.utils.getMessage('risk:binningexplorerapp:EF'),'FontWeight','bold',...
                'Units','Normalized','Position',[0.05 0.73 0.8 0.05],...
                'Enable','Off','Tag','efradiobtn');

            uicontrol('Parent',hp,'Style','Text','Units','Normalized',...
                'Position',[0.15 0.44 0.75 0.17],'String',...
                risk.internal.app.utils.getMessage('risk:binningexplorerapp:EFDescription'),...
                'HorizontalAlignment','left');

            uicontrol(hb,'Style','radiobutton','String',...
                risk.internal.app.utils.getMessage('risk:binningexplorerapp:EW'),'FontWeight','bold',...
                'Units','Normalized','Position',[0.05 0.43 0.8 0.05],...
                'Enable','Off','Tag','ewradiobtn');

            uicontrol('Parent',hp,'Style','Text','Units','Normalized',...
                'Position',[0.15 0.212 0.75 0.14],'String',...
                risk.internal.app.utils.getMessage('risk:binningexplorerapp:EWDescription'),...
                'HorizontalAlignment','left');
            
            obj.NoBinningRadioBtn = uicontrol(hb,'Style','radiobutton',...
                'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:NoBinning'),...
                'FontWeight','bold','Units','Normalized','Position',...
                [0.05 0.155 0.8 0.05],'Enable','Off','Tag','nobinningradiobtn');

            uicontrol('Parent',hp,'Style','Text','Units','Normalized',...
                'Position',[0.15 0.02 0.75 0.11],'String',...
                risk.internal.app.utils.getMessage('risk:binningexplorerapp:NoBinningDescription'),...
                'HorizontalAlignment','left');
            
        end

        function ht = setPredictorResponse(obj,src,ev,bool)
            % Callback on the CellEditCallback for the uitable. This
            % method makes sure to exclude variables marked as 'Do not 
            % include' and only one variable can be set as a response.
            
            if bool
                obj.ResponseChangedEvent = ev;
            end
            
            Data = src.Data;
            Ind2 = strcmpi('response',Data(:,2));
            rInd = false(length(Data),1);

            if isempty(ev)
                rInd(end) = true;
            else
                rInd(ev.Indices(1)) = true;
            end

            if size(Data,1) < 2
                msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:LoadDataMsg'),...
                    risk.internal.app.utils.getMessage('risk:binningexplorerapp:LoadDataTitle'),'warn','modal');
                ht = [];
                return;
            end
            
            % If the user sets one variable to 'response', then set all
            % others, except those excluded, to 'predictor'.
            if (~isempty(ev) && strcmpi(ev.NewData,'response')) || isempty(ev)
                % Validate that the response data is binary
                if isa(obj.OriginalData,'table')
                    RespData = obj.OriginalData.(src.RowName{rInd});
                else
                    RespData = obj.OriginalData.Data.(src.RowName{rInd});
                end
                if iscell(RespData)
                    RespVal = unique(categorical(RespData));
                else
                    RespVal  = unique(RespData);
                end
                
                RespVal(isnan(double(RespVal))) = [];
                
                if length(RespVal) ~= 2
                    msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:SetResponseMsg'),...
                        risk.internal.app.utils.getMessage('risk:binningexplorerapp:SetResponseTitle'),...
                        'warn','modal');
                    src.Data(rInd,2) = {risk.internal.app.utils.getMessage('risk:binningexplorerapp:Predictor')};
                    ht = [];
                    return;
                else
                    PredInd = xor(Ind2,rInd);
                    Data(PredInd,2) = {risk.internal.app.utils.getMessage('risk:binningexplorerapp:Predictor')};
                    src.Data = Data;
                end
            end

            ht = src;

        end
        
    end
    
    %%
    methods (Access = protected, Hidden = true)
        
        function obj = setLegend(obj)
            % Arranges the legend position when the plot is updated. Called
            % by 'obj.UpdatePlots' and 'obj.setInitialFigureLayout'.

            obj.Legend.Parent = obj.PlotPanel;
            obj.Legend.Position(1) = (1-obj.Legend.Position(3))/2;
            obj.Legend.Position(2) = 0.985;
            drawnow;
        end
        
    end
    
end

%% Helper functions

function icon = getIcon(filename)
    pathstr = fileparts(mfilename('fullpath'));
    fullfilename = fullfile(pathstr, 'icons', filename);
    icon = toolpack.component.Icon(fullfilename);
end
        
function hax = flipAxesAndAssignTags(hax,Predictor)
    % Called by 'obj.setInitialFigureLayout', 'obj.updatePlot' and
    % 'obj.updateZoomedFigure'. It makes sure the first axes if
    % the histogram and sets the tags for both axes.

    if find(cellfun(@(c)~isempty(c),regexpi({hax.Tag},'woe'))) == 1
        hax = flipud(hax);
        hax(1).Tag = Predictor;
        hax(2).Tag = [Predictor 'WOE'];
    end
end

function isOrdinalPreds = getOrdinalPredictorsFromData(Data,Predictors)
    % Returns a boolean array to index the input 'Predictors' and retrieve
    % a list of ordinal predictors. 'Data' is a table object.

    NumPreds = length(Predictors);
    isOrdinalPreds = false(NumPreds,1);
    
    for i = 1 : NumPreds
        try
            if isordinal(Data.(Predictors{i}))
                isOrdinalPreds(i) = true;
            else
                isOrdinalPreds(i) = false;
            end
        catch
            isOrdinalPreds(i) = false;
        end
    end
    
end