# Source: layouts/index.js

    /**
     * Predefined layouts that describe how to draw common types of data, as well as what interactive features to use.
     *  Each plot contains multiple panels (rows), and each row can stack several kinds of data in layers
     *  (eg scatter plot and line of significance). Layouts provide the building blocks to provide interactive experiences
     *  and user-friendly tooltips for common kinds of genetic data.
     *
     * Many of these layouts (like the standard association plot) assume that field names are the same as those provided
     *  in the UMich [portaldev API](https://portaldev.sph.umich.edu/docs/api/v1/). Although layouts can be used on many
     *  kinds of data, it is often less work to write an adapter that uses the same field names, rather than to modify
     *  every single reference to a field anywhere in the layout.
     *
     * See the Layouts Tutorial for details on how to customize nested layouts.
     *
     * @module LocusZoom_Layouts
     */

    import version from '../version';
    import {deepCopy, merge} from '../helpers/layouts';

    const LZ_SIG_THRESHOLD_LOGP = 7.301; // -log10(.05/1e6)

    /*
     * Tooltip Layouts
     */
    const standard_association_tooltip = {
        closable: true,
        show: { or: ['highlighted', 'selected'] },
        hide: { and: ['unhighlighted', 'unselected'] },
        html: `<strong>{{assoc:variant|htmlescape}}</strong><br>
            P Value: <strong>{{assoc:log_pvalue|logtoscinotation|htmlescape}}</strong><br>
            Ref. Allele: <strong>{{assoc:ref_allele|htmlescape}}</strong><br>
            {{#if lz_is_ld_refvar}}<strong>LD Reference Variant</strong>{{#else}}
            <a href="javascript:void(0);"
            onclick="var data = this.parentNode.__data__;
                     data.getDataLayer().makeLDReference(data);"
                     >Make LD Reference</a>{{/if}}<br>`,
    };

    const standard_association_tooltip_with_label = function() {
        // Add a special "toggle label" button to the base tooltip. This must be used in tandem with a custom layout
        //   directive (label.filters should check a boolean annotation field called "lz_show_label").
        const base = deepCopy(standard_association_tooltip);
        base.html += `<a href="javascript:void(0);"
                      onclick="var item = this.parentNode.__data__, layer = item.getDataLayer();
                      var current = layer.getElementAnnotation(item, 'lz_show_label');
                      layer.setElementAnnotation(item, 'lz_show_label', !current );
                      layer.parent_plot.applyState();">{{#if lz_show_label}}Hide{{#else}}Show{{/if}} label</a>`;
        return base;
    }();

    const standard_genes_tooltip = {
        closable: true,
        show: { or: ['highlighted', 'selected'] },
        hide: { and: ['unhighlighted', 'unselected'] },
        html: '<h4><strong><i>{{gene_name|htmlescape}}</i></strong></h4>'
            + 'Gene ID: <a href="https://useast.ensembl.org/homo_sapiens/Gene/Summary?g={{gene_id|htmlescape}}&db=core" target="_blank" rel="noopener">{{gene_id|htmlescape}}</a><br>'
            + 'Transcript ID: <strong>{{transcript_id|htmlescape}}</strong><br>'
            + '{{#if pLI}}<table>'
            + '<tr><th>Constraint</th><th>Expected variants</th><th>Observed variants</th><th>Const. Metric</th></tr>'
            + '<tr><td>Synonymous</td><td>{{exp_syn}}</td><td>{{obs_syn}}</td><td>z = {{syn_z}}<br>o/e = {{oe_syn}} ({{oe_syn_lower}} - {{oe_syn_upper}})</td></tr>'
            + '<tr><td>Missense</td><td>{{exp_mis}}</td><td>{{obs_mis}}</td><td>z = {{mis_z}}<br>o/e = {{oe_mis}} ({{oe_mis_lower}} - {{oe_mis_upper}})</td></tr>'
            + '<tr><td>pLoF</td><td>{{exp_lof}}</td><td>{{obs_lof}}</td><td>pLI = {{pLI}}<br>o/e = {{oe_lof}} ({{oe_lof_lower}} - {{oe_lof_upper}})</td></tr>'
            + '</table><br>{{/if}}'
            + '<a href="https://gnomad.broadinstitute.org/gene/{{gene_id|htmlescape}}" target="_blank" rel="noopener">More data on gnomAD</a>',
    };

    const catalog_variant_tooltip = {
        closable: true,
        show: { or: ['highlighted', 'selected'] },
        hide: { and: ['unhighlighted', 'unselected'] },
        html: '<strong>{{catalog:variant|htmlescape}}</strong><br>'
            + 'Catalog entries: <strong>{{n_catalog_matches|htmlescape}}</strong><br>'
            + 'Top Trait: <strong>{{catalog:trait|htmlescape}}</strong><br>'
            + 'Top P Value: <strong>{{catalog:log_pvalue|logtoscinotation}}</strong><br>'
            // User note: if a different catalog is used, the tooltip will need to be replaced with a different link URL
            + 'More: <a href="https://www.ebi.ac.uk/gwas/search?query={{catalog:rsid|htmlescape}}" target="_blank" rel="noopener">GWAS catalog</a> / <a href="https://www.ncbi.nlm.nih.gov/snp/{{catalog:rsid|htmlescape}}" target="_blank" rel="noopener">dbSNP</a>',
    };

    const coaccessibility_tooltip = {
        closable: true,
        show: { or: ['highlighted', 'selected'] },
        hide: { and: ['unhighlighted', 'unselected'] },
        // TODO: Is there a more generic terminology? (eg not every technique is in terms of cis-regulatory element)
        html: '<strong>Regulatory element</strong><br>' +
            '{{access:start1|htmlescape}}-{{access:end1|htmlescape}}<br>' +
            '<strong>Promoter</strong><br>' +
            '{{access:start2|htmlescape}}-{{access:end2|htmlescape}}<br>' +
            '{{#if access:target}}<strong>Target</strong>: {{access:target|htmlescape}}<br>{{/if}}' +
            '<strong>Score</strong>: {{access:score|htmlescape}}',
    };

    /*
     * Data Layer Layouts: represent specific information given provided data.
     */

    /**
     * A horizontal line of GWAS significance at the standard threshold of p=5e-8
     * @name significance
     * @type data_layer
     */
    const significance_layer = {
        id: 'significance',
        type: 'orthogonal_line',
        tag: 'significance',
        orientation: 'horizontal',
        offset: LZ_SIG_THRESHOLD_LOGP,
    };

    /**
     * A simple curve representing the genetic recombination rate, drawn from the UM API
     * @name recomb_rate
     * @type data_layer
     */
    const recomb_rate_layer = {
        id: 'recombrate',
        namespace: { 'recomb': 'recomb' },
        data_operations: [
            { type: 'fetch', from: ['recomb'] },
        ],
        type: 'line',
        tag: 'recombination',
        z_index: 1,
        style: {
            'stroke': '#0000FF',
            'stroke-width': '1.5px',
        },
        x_axis: {
            field: 'recomb:position',
        },
        y_axis: {
            axis: 2,
            field: 'recomb:recomb_rate',
            floor: 0,
            ceiling: 100,
        },
    };

    /**
     * A scatter plot of GWAS association summary statistics, with preset field names matching the UM portaldev api
     * @name association_pvalues
     * @type data_layer
     */
    const association_pvalues_layer = {
        namespace: { 'assoc': 'assoc', 'ld': 'ld' },
        data_operations: [
            {
                type: 'fetch',
                from: ['assoc', 'ld(assoc)'],
            },
            {
                type: 'left_match',
                name: 'assoc_plus_ld',
                requires: ['assoc', 'ld'],
                params: ['assoc:position', 'ld:position2'],  // FIXME: old LZ used position, because it was less sensitive to format. We'd like to match assoc:variant = ld:variant2, but not every assoc source provides variant data in the way we need. This would need to be fixed via special formatting adjustment later.
            },
        ],
        id: 'associationpvalues',
        type: 'scatter',
        tag: 'association',
        id_field: 'assoc:variant',
        coalesce: {
            active: true,
        },
        point_shape: [
            {
                scale_function: 'if',
                field: 'lz_is_ld_refvar',
                parameters: {
                    field_value: true,
                    then: 'diamond',
                },
            },
            {
                // Not every dataset will provide these params
                scale_function: 'effect_direction',
                parameters: {
                    '+': 'triangle',
                    '-': 'triangledown',
                    // The scale function receives the entire datum object, so it needs to be told where to find individual fields
                    beta_field: 'assoc:beta',
                    stderr_beta_field: 'assoc:se',
                },
            },
            'circle',
        ],
        point_size: {
            scale_function: 'if',
            field: 'lz_is_ld_refvar',
            parameters: {
                field_value: true,
                then: 80,
                else: 40,
            },
        },
        color: [
            {
                scale_function: 'if',
                field: 'lz_is_ld_refvar',
                parameters: {
                    field_value: true,
                    then: '#9632b8',
                },
            },
            {
                scale_function: 'numerical_bin',
                field: 'ld:correlation',
                parameters: {
                    breaks: [0, 0.2, 0.4, 0.6, 0.8],
                    // Derived from Google "Turbo" colormap, breakpoints [0.05, 0.25, 0.45, 0.65, 0.85]
                    values: ['rgb(70, 54, 153)', 'rgb(38, 188, 225)', 'rgb(110, 254, 104)', 'rgb(248, 195, 42)', 'rgb(219, 61, 17)'],
                },
            },
            '#AAAAAA',
        ],
        legend: [
            {  label: 'LD (r²)', label_size: 14 }, // We're omitting the refvar symbol for now, but can show it with // shape: 'diamond', color: '#9632b8'
            {
                shape: 'ribbon',
                orientation: 'vertical',
                width: 10,
                height: 15,
                color_stops: ['rgb(70, 54, 153)', 'rgb(38, 188, 225)', 'rgb(110, 254, 104)', 'rgb(248, 195, 42)', 'rgb(219, 61, 17)'],
                tick_labels: [0, 0.2, 0.4, 0.6, 0.8, 1.0],
            },
        ],
        label: null,
        z_index: 2,
        x_axis: {
            field: 'assoc:position',
        },
        y_axis: {
            axis: 1,
            field: 'assoc:log_pvalue',
            floor: 0,
            upper_buffer: 0.10,
            min_extent: [0, 10],
        },
        behaviors: {
            onmouseover: [
                { action: 'set', status: 'highlighted' },
            ],
            onmouseout: [
                { action: 'unset', status: 'highlighted' },
            ],
            onclick: [
                { action: 'toggle', status: 'selected', exclusive: true },
            ],
        },
        tooltip: deepCopy(standard_association_tooltip),
    };

    /**
     * An arc track that shows arcs representing chromatic coaccessibility
     * @name coaccessibility
     * @type data_layer
     */
    const coaccessibility_layer = {
        id: 'coaccessibility',
        type: 'arcs',
        tag: 'coaccessibility',
        namespace: { 'access': 'access' },
        data_operations: [
            { type: 'fetch', from: ['access'] },
        ],
        match: { send: 'access:target', receive: 'access:target' },
        // Note: in the datasets this was tested with, these fields together defined a unique loop. Other datasets might work differently and need a different ID.
        id_field: '{{access:start1}}_{{access:end1}}_{{access:start2}}_{{access:end2}}_{{access:score}}_{{access:target}}',
        filters: [
            { field: 'access:score', operator: '!=', value: null },
        ],
        color: [
            {
                field: 'lz_is_match', // Special field name whose presence triggers custom rendering
                scale_function: 'if',
                parameters: {
                    field_value: true,
                    then: '#4285f4',
                },
            },
            {
                field: 'lz_is_match', // Special field name whose presence triggers custom rendering
                scale_function: 'if',
                parameters: {
                    field_value: false,
                    then: '#EAE6E6',
                },
            },
            {
                scale_function: 'ordinal_cycle',
                parameters: {
                    values: ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'], // Drawn from d3v3 "category20"
                },
            },
        ],
        x_axis: {
            field1: 'access:start1',
            field2: 'access:start2',
        },
        y_axis: {
            axis: 1,
            field: 'access:score',
            upper_buffer: 0.1,
            min_extent: [0, 1],
        },
        behaviors: {
            onmouseover: [
                { action: 'set', status: 'highlighted' },
            ],
            onmouseout: [
                { action: 'unset', status: 'highlighted' },
            ],
            onclick: [
                { action: 'toggle', status: 'selected', exclusive: true },
            ],
        },
        tooltip: deepCopy(coaccessibility_tooltip),
    };

    /**
     * A scatter plot of GWAS summary statistics, with additional tooltip fields showing GWAS catalog annotations
     * @name association_pvalues_catalog
     * @type data_layer
     */
    const association_pvalues_catalog_layer = function () {
        // Slightly modify an existing layout
        let base = deepCopy(association_pvalues_layer);
        base = merge({ id: 'associationpvaluescatalog', fill_opacity: 0.7 }, base);

        base.data_operations.push({
            type: 'assoc_to_gwas_catalog',
            name: 'assoc_catalog',
            requires: ['assoc_plus_ld', 'catalog'],
            params: ['assoc:position', 'catalog:pos', 'catalog:log_pvalue'],
        });

        base.tooltip.html += '{{#if catalog:rsid}}<br><a href="https://www.ebi.ac.uk/gwas/search?query={{catalog:rsid|htmlescape}}" target="_blank" rel="noopener">See hits in GWAS catalog</a>{{/if}}';
        base.namespace.catalog = 'catalog';
        return base;
    }();

    /**
     * A scatter plot of PheWAS pvalues, with preset field names matching the UM Portaldev API
     * @name phewas_pvalues
     * @type data_layer
     */
    const phewas_pvalues_layer = {
        id: 'phewaspvalues',
        type: 'category_scatter',
        tag: 'phewas',
        namespace: { 'phewas': 'phewas' },
        data_operations: [
            { type: 'fetch', from: ['phewas'] },
        ],
        point_shape: [
            {
                scale_function: 'effect_direction',
                parameters: {
                    '+': 'triangle',
                    '-': 'triangledown',
                    // The scale function receives the entire datum object, so it needs to be told where to find individual fields
                    beta_field: 'phewas:beta',
                    stderr_beta_field: 'phewas:se',
                },
            },
            'circle',
        ],
        point_size: 70,
        tooltip_positioning: 'vertical',
        id_field: '{{phewas:trait_group}}_{{phewas:trait_label}}',
        x_axis: {
            field: 'lz_auto_x', // Automatically added by the category_scatter layer
            category_field: 'phewas:trait_group',
            lower_buffer: 0.025,
            upper_buffer: 0.025,
        },
        y_axis: {
            axis: 1,
            field: 'phewas:log_pvalue',
            floor: 0,
            upper_buffer: 0.15,
        },
        color: [{
            field: 'phewas:trait_group',
            scale_function: 'categorical_bin',
            parameters: {
                categories: [],
                values: [],
                null_value: '#B8B8B8',
            },
        }],
        fill_opacity: 0.7,
        tooltip: {
            closable: true,
            show: { or: ['highlighted', 'selected'] },
            hide: { and: ['unhighlighted', 'unselected'] },
            html: `<strong>Trait:</strong> {{phewas:trait_label|htmlescape}}<br>
    <strong>Trait Category:</strong> {{phewas:trait_group|htmlescape}}<br>
    <strong>P-value:</strong> {{phewas:log_pvalue|logtoscinotation|htmlescape}}
    {{#if phewas:beta|is_numeric}}<br><strong>&beta;:</strong> {{phewas:beta|scinotation|htmlescape}}<br>{{/if}}
    {{#if phewas:se|is_numeric}}<strong>SE (&beta;):</strong> {{phewas:se|scinotation|htmlescape}}{{/if}}`,
        },
        behaviors: {
            onmouseover: [
                { action: 'set', status: 'highlighted' },
            ],
            onmouseout: [
                { action: 'unset', status: 'highlighted' },
            ],
            onclick: [
                { action: 'toggle', status: 'selected', exclusive: true },
            ],
        },
        label: {
            text: '{{phewas:trait_label}}',
            spacing: 6,
            lines: {
                style: {
                    'stroke-width': '2px',
                    'stroke': '#333333',
                    'stroke-dasharray': '2px 2px',
                },
            },
            filters: [
                {
                    field: 'phewas:log_pvalue',
                    operator: '>=',
                    value: 20,
                },
            ],
            style: {
                'font-size': '14px',
                'font-weight': 'bold',
                'fill': '#333333',
            },
        },
    };

    /**
     * Shows genes in the specified region, with names and formats drawn from the UM Portaldev API and GENCODE datasource
     * @type data_layer
     */
    const genes_layer = {
        namespace: { 'gene': 'gene', 'constraint': 'constraint' },
        data_operations: [
            {
                type: 'fetch',
                from: ['gene', 'constraint(gene)'],
            },
            {
                name: 'gene_constraint',
                type: 'genes_to_gnomad_constraint',
                requires: ['gene', 'constraint'],
            },
        ],
        id: 'genes',
        type: 'genes',
        tag: 'genes',
        id_field: 'gene_id',
        behaviors: {
            onmouseover: [
                { action: 'set', status: 'highlighted' },
            ],
            onmouseout: [
                { action: 'unset', status: 'highlighted' },
            ],
            onclick: [
                { action: 'toggle', status: 'selected', exclusive: true },
            ],
        },
        tooltip: deepCopy(standard_genes_tooltip),
    };

    /**
     * A genes data layer that uses filters to limit what information is shown by default. This layer hides a curated
     *  list of  GENCODE gene_types that are of less interest to most analysts.
     * Often used in tandem with a panel-level toolbar "show all" button so that the user can toggle to a full view.
     * @name genes_filtered
     * @type data_layer
     */
    const genes_layer_filtered = merge({
        filters: [
            {
                field: 'gene_type',
                operator: 'in',
                // A manually curated subset of Gencode biotypes, based on user suggestions
                //  See full list: https://www.gencodegenes.org/human/stats.html
                // This is approximately intended to cover elements of generally known function, and exclude things
                //  like pseudogenes.
                value: [
                    'protein_coding',
                    'IG_C_gene', 'IG_D_gene', 'IG_J_gene', 'IG_V_gene',
                    'TR_C_gene', 'TR_D_gene', 'TR_J_gene', 'TR_V_gene',
                    'rRNA',
                    'Mt_rRNA', 'Mt_tRNA',
                ],
            },
        ],
    }, deepCopy(genes_layer));

    /**
     * An annotation / rug track that shows tick marks for each position in which a variant is present in the provided
     *  association data, *and* has a significant claim in the EBI GWAS catalog.
     * @type data_layer
     */
    const annotation_catalog_layer = {
        // Identify GWAS hits that are present in the GWAS catalog
        namespace: { 'assoc': 'assoc', 'catalog': 'catalog' },
        data_operations: [
            {
                type: 'fetch', from: ['assoc', 'catalog'],
            },
            {
                type: 'assoc_to_gwas_catalog',
                name: 'assoc_plus_ld',
                requires: ['assoc', 'catalog'],
                params: ['assoc:position', 'catalog:pos', 'catalog:log_pvalue'],
            },
        ],
        id: 'annotation_catalog',
        type: 'annotation_track',
        tag: 'gwascatalog',
        id_field: 'assoc:variant',
        x_axis: {
            field: 'assoc:position',
        },
        color: '#0000CC',
        filters: [
            // Specify which points to show on the track. Any selection must satisfy ALL filters
            { field: 'catalog:rsid', operator: '!=', value: null },
            { field: 'catalog:log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP },
        ],
        behaviors: {
            onmouseover: [
                { action: 'set', status: 'highlighted' },
            ],
            onmouseout: [
                { action: 'unset', status: 'highlighted' },
            ],
            onclick: [
                { action: 'toggle', status: 'selected', exclusive: true },
            ],
        },
        tooltip: deepCopy(catalog_variant_tooltip),
        tooltip_positioning: 'top',
    };

    /*
     * Individual toolbar buttons
     */

    /**
     * A dropdown menu that can be used to control the LD population used with the LDServer Adapter. Population
     *  names are provided for the 1000G dataset that is used by the offical UM LD Server.
     * @name ldlz2_pop_selector
     * @type toolbar_widgets
     */
    const ldlz2_pop_selector_menu = {
        // **Note**: this widget is aimed at the LDServer datasource, and the UM 1000G LDServer
        type: 'set_state',
        tag: 'ld_population',
        position: 'right',
        color: 'blue',
        button_html: 'LD Population: ',
        show_selected: true,
        button_title: 'Select LD Population: ',
        custom_event_name: 'widget_set_ldpop',
        state_field: 'ld_pop',
        // This list below is hardcoded to work with the UMich LDServer, default 1000G populations
        //  It can be customized to work with other LD servers that specify population differently
        // https://portaldev.sph.umich.edu/ld/genome_builds/GRCh37/references/1000G/populations
        options: [
            { display_name: 'ALL (default)', value: 'ALL' },
            { display_name: 'AFR', value: 'AFR' },
            { display_name: 'AMR', value: 'AMR' },
            { display_name: 'EAS', value: 'EAS' },
            { display_name: 'EUR', value: 'EUR' },
            { display_name: 'SAS', value: 'SAS' },
        ],
    };

    /**
     * A dropdown menu that selects which types of genes to show in the plot. The provided options are curated sets of
     *   interesting gene types based on the GENCODE dataset.
     * @type toolbar_widgets
     */
    const gene_selector_menu = {
        type: 'display_options',
        tag: 'gene_filter',
        custom_event_name: 'widget_gene_filter_choice',
        position: 'right',
        color: 'blue',
        // Below: special config specific to this widget
        button_html: 'Filter...',
        button_title: 'Choose which genes to show',
        layer_name: 'genes',
        default_config_display_name: 'Coding genes & rRNA',
        options: [
            {
                display_name: 'All features',
                display: {
                    filters: null,
                },
            },
        ],
    };

    /*
     * Toolbar Layouts: Collections of toolbar buttons etc
     */

    /**
     * Basic options to remove and reorder panels
     * @name standard_panel
     * @type toolbar
     */
    const standard_panel_toolbar = {
        widgets: [
            {
                type: 'remove_panel',
                position: 'right',
                color: 'red',
                group_position: 'end',
            },
            {
                type: 'move_panel_up',
                position: 'right',
                group_position: 'middle',
            },
            {
                type: 'move_panel_down',
                position: 'right',
                group_position: 'start',
                style: { 'margin-left': '0.75em' },
            },
        ],
    };

    /**
     * A simple plot toolbar with buttons to download as image
     * @name standard_plot
     * @type toolbar
     */
    const standard_plot_toolbar = {
        // Suitable for most any type of plot drawn with LZ. Title and download buttons.
        widgets: [
            {
                type: 'title',
                title: 'LocusZoom',
                subtitle: `<a href="https://statgen.github.io/locuszoom/" target="_blank" rel="noopener">v${version}</a>`,
                position: 'left',
            },
            {
                type: 'download',
                position: 'right',
                group_position: 'end',
            },
            {
                type: 'download_png',
                position: 'right',
                group_position: 'start',
            },
        ],
    };

    /**
     * A plot toolbar that adds a button for controlling LD population. This is useful for plots intended to show
     *  GWAS summary stats, which is one of the most common usages of LocusZoom.
     * @type toolbar
     */
    const standard_association_toolbar = function () {
        // Suitable for association plots (adds a button for LD data)
        const base = deepCopy(standard_plot_toolbar);
        base.widgets.push(deepCopy(ldlz2_pop_selector_menu));
        return base;
    }();

    /**
     * A basic plot toolbar with buttons to scroll sideways or zoom in. Useful for all region-based plots.
     * @name region_nav_plot
     * @type toolbar
     */
    const region_nav_plot_toolbar = function () {
        // Generic region nav buttons
        const base = deepCopy(standard_plot_toolbar);
        base.widgets.push(
            {
                type: 'shift_region',
                step: 500000,
                button_html: '>>',
                position: 'right',
                group_position: 'end',
            }, {
                type: 'shift_region',
                step: 50000,
                button_html: '>',
                position: 'right',
                group_position: 'middle',
            },
            {
                type: 'zoom_region',
                step: 0.2,
                position: 'right',
                group_position: 'middle',
            },
            {
                type: 'zoom_region',
                step: -0.2,
                position: 'right',
                group_position: 'middle',
            },
            {
                type: 'shift_region',
                step: -50000,
                button_html: '<',
                position: 'right',
                group_position: 'middle',
            },
            {
                type: 'shift_region',
                step: -500000,
                button_html: '<<',
                position: 'right',
                group_position: 'start',
            },
        );
        return base;
    }();

    /*
     * Panel Layouts
     */

    /**
     * A panel that describes the most common kind of LocusZoom plot, with line of GWAS significance, recombination rate,
     *  and a scatter plot superimposed.
     * @name association
     * @type panel
     */
    const association_panel = {
        id: 'association',
        tag: 'association',
        min_height: 200,
        height: 300,
        margin: { top: 35, right: 55, bottom: 40, left: 70 },
        inner_border: 'rgb(210, 210, 210)',
        toolbar: (function () {
            const base = deepCopy(standard_panel_toolbar);
            base.widgets.push({
                type: 'toggle_legend',
                position: 'right',
            });
            return base;
        })(),
        axes: {
            x: {
                label: 'Chromosome {{chr}} (Mb)',
                label_offset: 38,
                tick_format: 'region',
                extent: 'state',
            },
            y1: {
                label: '-log10 p-value',
                label_offset: 50,
            },
            y2: {
                label: 'Recombination Rate (cM/Mb)',
                label_offset: 46,
            },
        },
        legend: {
            orientation: 'vertical',
            origin: { x: 75, y: 40 },
            hidden: true,
        },
        interaction: {
            drag_background_to_pan: true,
            drag_x_ticks_to_scale: true,
            drag_y1_ticks_to_scale: true,
            drag_y2_ticks_to_scale: true,
            scroll_to_zoom: true,
            x_linked: true,
        },
        data_layers: [
            deepCopy(significance_layer),
            deepCopy(recomb_rate_layer),
            deepCopy(association_pvalues_layer),
        ],
    };

    /**
     * A panel showing chromatin coaccessibility arcs with some common display options
     * @type panel
     */
    const coaccessibility_panel = {
        id: 'coaccessibility',
        tag: 'coaccessibility',
        min_height: 150,
        height: 180,
        margin: { top: 35, right: 55, bottom: 40, left: 70 },
        inner_border: 'rgb(210, 210, 210)',
        toolbar: deepCopy(standard_panel_toolbar),
        axes: {
            x: {
                label: 'Chromosome {{chr}} (Mb)',
                label_offset: 38,
                tick_format: 'region',
                extent: 'state',
            },
            y1: {
                label: 'Score',
                label_offset: 40,
                render: false,  // We are mainly concerned with the relative magnitudes: hide y axis to avoid clutter.
            },
        },
        interaction: {
            drag_background_to_pan: true,
            drag_x_ticks_to_scale: true,
            drag_y1_ticks_to_scale: true,
            scroll_to_zoom: true,
            x_linked: true,
        },
        data_layers: [
            deepCopy(coaccessibility_layer),
        ],
    };

    /**
     * A panel showing GWAS summary statistics, plus annotations for connecting it to the EBI GWAS catalog
     * @type panel
     */
    const association_catalog_panel = function () {
        let base = deepCopy(association_panel);
        base = merge({
            id: 'associationcatalog',
        }, base);

        base.toolbar.widgets.push({
            type: 'display_options',
            position: 'right',
            color: 'blue',
            // Below: special config specific to this widget
            button_html: 'Display options...',
            button_title: 'Control how plot items are displayed',

            layer_name: 'associationpvaluescatalog',
            default_config_display_name: 'No catalog labels (default)', // display name for the default plot color option (allow user to revert to plot defaults)

            options: [
                {
                    // First dropdown menu item
                    display_name: 'Label catalog traits',  // Human readable representation of field name
                    display: {  // Specify layout directives that control display of the plot for this option
                        label: {
                            text: '{{catalog:trait}}',
                            spacing: 6,
                            lines: {
                                style: {
                                    'stroke-width': '2px',
                                    'stroke': '#333333',
                                    'stroke-dasharray': '2px 2px',
                                },
                            },
                            filters: [
                                // Only label points if they are significant for some trait in the catalog, AND in high LD
                                //  with the top hit of interest
                                { field: 'catalog:trait', operator: '!=', value: null },
                                { field: 'catalog:log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP },
                                { field: 'ld:correlation', operator: '>', value: 0.4 },
                            ],
                            style: {
                                'font-size': '12px',
                                'font-weight': 'bold',
                                'fill': '#333333',
                            },
                        },
                    },
                },
            ],
        });
        base.data_layers = [
            deepCopy(significance_layer),
            deepCopy(recomb_rate_layer),
            deepCopy(association_pvalues_catalog_layer),
        ];
        return base;
    }();

    /**
     * A panel showing genes in the specified region. This panel lets the user choose which genes are shown.
     * @type panel
     */
    const genes_panel = {
        id: 'genes',
        tag: 'genes',
        min_height: 150,
        height: 225,
        margin: { top: 20, right: 55, bottom: 20, left: 70 },
        axes: {},
        interaction: {
            drag_background_to_pan: true,
            scroll_to_zoom: true,
            x_linked: true,
        },
        toolbar: (function () {
            const base = deepCopy(standard_panel_toolbar);
            base.widgets.push(
                {
                    type: 'resize_to_data',
                    position: 'right',
                    button_html: 'Resize',
                },
                deepCopy(gene_selector_menu),
            );
            return base;
        })(),
        data_layers: [
            deepCopy(genes_layer_filtered),
        ],
    };

    /**
     * A panel that displays PheWAS scatter plots and automatically generates a color scheme
     * @type panel
     */
    const phewas_panel = {
        id: 'phewas',
        tag: 'phewas',
        min_height: 300,
        height: 300,
        margin: { top: 20, right: 55, bottom: 120, left: 70 },
        inner_border: 'rgb(210, 210, 210)',
        axes: {
            x: {
                ticks: {  // Object based config (shared defaults; allow layers to specify ticks)
                    style: {
                        'font-weight': 'bold',
                        'font-size': '11px',
                        'text-anchor': 'start',
                    },
                    transform: 'rotate(50)',
                    position: 'left',  // Special param recognized by `category_scatter` layers
                },
            },
            y1: {
                label: '-log10 p-value',
                label_offset: 50,
            },
        },
        data_layers: [
            deepCopy(significance_layer),
            deepCopy(phewas_pvalues_layer),
        ],
    };

    /**
     * A panel that shows a simple annotation track connecting GWAS results
     * @name annotation_catalog
     * @type panel
     */
    const annotation_catalog_panel = {
        id: 'annotationcatalog',
        tag: 'gwascatalog',
        min_height: 50,
        height: 50,
        margin: { top: 25, right: 55, bottom: 10, left: 70 },
        inner_border: 'rgb(210, 210, 210)',
        toolbar: deepCopy(standard_panel_toolbar),
        axes: {
            x: { extent: 'state', render: false },
        },
        interaction: {
            drag_background_to_pan: true,
            scroll_to_zoom: true,
            x_linked: true,
        },
        data_layers: [
            deepCopy(annotation_catalog_layer),
        ],
    };

    /*
     * Plot Layouts
     */

    /**
     * Describes how to fetch and draw each part of the most common LocusZoom plot (with field names that reference the portaldev API)
     * @name standard_association
     * @type plot
     */
    const standard_association_plot = {
        state: {},
        width: 800,
        responsive_resize: true,
        min_region_scale: 20000,
        max_region_scale: 1000000,
        toolbar: standard_association_toolbar,
        panels: [
            deepCopy(association_panel),
            deepCopy(genes_panel),
        ],
    };

    /**
     * A modified version of the standard LocusZoom plot, which adds a track that shows which SNPs in the plot also have claims in the EBI GWAS catalog.
     * @name association_catalog
     * @type plot
     */
    const association_catalog_plot = {
        state: {},
        width: 800,
        responsive_resize: true,
        min_region_scale: 20000,
        max_region_scale: 1000000,
        toolbar: standard_association_toolbar,
        panels: [
            annotation_catalog_panel,
            association_catalog_panel,
            genes_panel,
        ],
    };

    /**
     * A PheWAS scatter plot with an additional track showing nearby genes, to put the region in biological context.
     * @name standard_phewas
     * @type plot
     */
    const standard_phewas_plot = {
        width: 800,
        responsive_resize: true,
        toolbar: standard_plot_toolbar,
        panels: [
            deepCopy(phewas_panel),
            merge({
                height: 300,
                margin: { bottom: 40 },
                axes: {
                    x: {
                        label: 'Chromosome {{chr}} (Mb)',
                        label_offset: 38,
                        tick_format: 'region',
                        extent: 'state',
                    },
                },
            }, deepCopy(genes_panel)),
        ],
        mouse_guide: false,
    };

    /**
     * Show chromatin coaccessibility arcs, with additional features that connect these arcs to nearby genes to show regulatory interactions.
     * @name coaccessibility
     * @type plot
     */
    const coaccessibility_plot = {
        state: {},
        width: 800,
        responsive_resize: true,
        min_region_scale: 20000,
        max_region_scale: 1000000,
        toolbar: deepCopy(standard_plot_toolbar),
        panels: [
            deepCopy(coaccessibility_panel),
            function () {
                // Take the default genes panel, and add a custom feature to highlight gene tracks based on short name
                // This is a companion to the "match" directive in the coaccessibility panel
                const base = Object.assign(
                    { height: 270 },
                    deepCopy(genes_panel),
                );
                const layer = base.data_layers[0];
                layer.match = { send: 'gene_name', receive: 'gene_name' };
                const color_config = [
                    {
                        field: 'lz_is_match', // Special field name whose presence triggers custom rendering
                        scale_function: 'if',
                        parameters: {
                            field_value: true,
                            then: '#4285f4',
                        },
                    },
                    {
                        field: 'lz_is_match', // Special field name whose presence triggers custom rendering
                        scale_function: 'if',
                        parameters: {
                            field_value: false,
                            then: '#EAE6E6',
                        },
                    },
                    '#363696',
                ];
                layer.color = color_config;
                layer.stroke = color_config;
                return base;
            }(),
        ],
    };

    export const tooltip = {
        standard_association: standard_association_tooltip,
        standard_association_with_label: standard_association_tooltip_with_label,
        standard_genes: standard_genes_tooltip,
        catalog_variant: catalog_variant_tooltip,
        coaccessibility: coaccessibility_tooltip,
    };

    export const toolbar_widgets = {
        ldlz2_pop_selector: ldlz2_pop_selector_menu,
        gene_selector_menu,
    };

    export const toolbar = {
        standard_panel: standard_panel_toolbar,
        standard_plot: standard_plot_toolbar,
        standard_association: standard_association_toolbar,
        region_nav_plot: region_nav_plot_toolbar,
    };

    export const data_layer = {
        significance: significance_layer,
        recomb_rate: recomb_rate_layer,
        association_pvalues: association_pvalues_layer,
        coaccessibility: coaccessibility_layer,
        association_pvalues_catalog: association_pvalues_catalog_layer,
        phewas_pvalues: phewas_pvalues_layer,
        genes: genes_layer,
        genes_filtered: genes_layer_filtered,
        annotation_catalog: annotation_catalog_layer,
    };

    export const panel = {
        association: association_panel,
        coaccessibility: coaccessibility_panel,
        association_catalog: association_catalog_panel,
        genes: genes_panel,
        phewas: phewas_panel,
        annotation_catalog: annotation_catalog_panel,
    };

    export const plot = {
        standard_association: standard_association_plot,
        association_catalog: association_catalog_plot,
        standard_phewas: standard_phewas_plot,
        coaccessibility: coaccessibility_plot,
    };