From 6fa53402a0f5d81c569e93664347df2baa603870 Mon Sep 17 00:00:00 2001 From: Patrick Kalita Date: Thu, 19 Dec 2024 11:22:44 -0800 Subject: [PATCH] Add go-ribbon components --- src/components.d.ts | 185 ++++++ src/components/go-ribbon-strips/readme.md | 5 + .../go-ribbon-strips/ribbon-strips.tsx | 3 +- src/components/go-ribbon-strips/utils.ts | 13 - src/components/go-ribbon-table/readme.md | 14 + src/components/go-ribbon/go-ribbon.css | 3 + src/components/go-ribbon/go-ribbon.tsx | 525 ++++++++++++++++++ src/components/go-ribbon/index.html | 46 ++ src/components/go-ribbon/readme.md | 67 +++ src/components/go-ribbon/utils.ts | 67 +++ src/components/go-spinner/readme.md | 2 + src/globals/utils.ts | 12 + src/index.html | 3 + 13 files changed, 931 insertions(+), 14 deletions(-) create mode 100644 src/components/go-ribbon/go-ribbon.css create mode 100644 src/components/go-ribbon/go-ribbon.tsx create mode 100644 src/components/go-ribbon/index.html create mode 100644 src/components/go-ribbon/readme.md create mode 100644 src/components/go-ribbon/utils.ts create mode 100644 src/globals/utils.ts diff --git a/src/components.d.ts b/src/components.d.ts index 6a90712..55e36a0 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -23,6 +23,93 @@ export namespace Components { placeholder: string; value: string; } + interface WcGoRibbon { + /** + * add a cell at the beginning of each row/subject to show all annotations + */ + addCellAll: boolean; + annotationLabels: string; + baseApiUrl: string; + /** + * false = show a gradient of colors to indicate the value of a cell true = show only two colors (minColor; maxColor) to indicate the values of a cell + */ + binaryColor: boolean; + /** + * 0 = Normal 1 = Bold + */ + categoryAllStyle: number; + /** + * Override of the category case 0 (default) = unchanged 1 = to lower case 2 = to upper case + */ + categoryCase: number; + /** + * 0 = Normal 1 = Bold + */ + categoryOtherStyle: number; + classLabels: string; + /** + * Which value to base the cell color on 0 = class count 1 = annotation count + */ + colorBy: number; + /** + * if provided, will override any value provided in subjects and subset + */ + data: string; + excludePB: boolean; + /** + * Filter rows based on the presence of one or more values in a given column The filtering will be based on cell label or id Example: filter-by="evidence:ISS,ISO or multi-step filters: filter-by:evidence:ISS,ISO;term:xxx" Note: if value is "", remove any filtering + */ + filterBy: string; + filterCrossAspect: boolean; + filterReference: string; + /** + * If true, the ribbon will fire an event if a user click an empty cell If false, the ribbon will not fire the event on an empty cell Note: if selectionMode == SELECTION.COLUMN, then the event will trigger if at least one of the selected cells has annotations + */ + fireEventOnEmptyCells: boolean; + groupBaseUrl: string; + /** + * Using this parameter, the table rows can bee grouped based on column ids A multiple step grouping is possible by using a ";" between groups The grouping applies before the ordering Example: hid-1,hid-3 OR hid-1,hid-3;hid-2 Note: if value is "", remove any grouping + */ + groupBy: string; + groupClickable: boolean; + groupMaxLabelSize: number; + groupNewTab: boolean; + /** + * Used to hide specific column of the table + */ + hideColumns: string; + maxColor: string; + maxHeatLevel: number; + minColor: string; + /** + * This is used to sort the table depending of a column The column cells must be single values The ordering applies after the grouping Note: if value is "", remove any ordering + */ + orderBy: string; + /** + * If no value is provided, the ribbon will load without any group selected. If a value is provided, the ribbon will show the requested group as selected The value should be the id of the group to be selected + */ + selected: any; + /** + * Click handling of a cell. 0 = select only the cell (1 subject, 1 group) 1 = select the whole column (all subjects, 1 group) + */ + selectionMode: number; + /** + * add a cell at the end of each row/subject to represent all annotations not mapped to a specific term + */ + showOtherGroup: boolean; + subjectBaseUrl: string; + subjectOpenNewTab: boolean; + /** + * Position the subject label of each row 0 = None 1 = Left 2 = Right 3 = Bottom + */ + subjectPosition: number; + subjectUseTaxonIcon: boolean; + /** + * provide gene ids (e.g. RGD:620474,RGD:3889 or as a list ["RGD:620474", "RGD:3889"]) + */ + subjects: string; + subset: string; + } interface WcLightModal { close: () => Promise; modalAnchor: string; @@ -252,6 +339,13 @@ declare global { prototype: HTMLWcGoAutocompleteElement; new (): HTMLWcGoAutocompleteElement; }; + interface HTMLWcGoRibbonElement + extends Components.WcGoRibbon, + HTMLStencilElement {} + var HTMLWcGoRibbonElement: { + prototype: HTMLWcGoRibbonElement; + new (): HTMLWcGoRibbonElement; + }; interface HTMLWcLightModalElement extends Components.WcLightModal, HTMLStencilElement {} @@ -401,6 +495,7 @@ declare global { }; interface HTMLElementTagNameMap { "wc-go-autocomplete": HTMLWcGoAutocompleteElement; + "wc-go-ribbon": HTMLWcGoRibbonElement; "wc-light-modal": HTMLWcLightModalElement; "wc-ribbon-cell": HTMLWcRibbonCellElement; "wc-ribbon-strips": HTMLWcRibbonStripsElement; @@ -429,6 +524,93 @@ declare namespace LocalJSX { placeholder?: string; value?: string; } + interface WcGoRibbon { + /** + * add a cell at the beginning of each row/subject to show all annotations + */ + addCellAll?: boolean; + annotationLabels?: string; + baseApiUrl?: string; + /** + * false = show a gradient of colors to indicate the value of a cell true = show only two colors (minColor; maxColor) to indicate the values of a cell + */ + binaryColor?: boolean; + /** + * 0 = Normal 1 = Bold + */ + categoryAllStyle?: number; + /** + * Override of the category case 0 (default) = unchanged 1 = to lower case 2 = to upper case + */ + categoryCase?: number; + /** + * 0 = Normal 1 = Bold + */ + categoryOtherStyle?: number; + classLabels?: string; + /** + * Which value to base the cell color on 0 = class count 1 = annotation count + */ + colorBy?: number; + /** + * if provided, will override any value provided in subjects and subset + */ + data?: string; + excludePB?: boolean; + /** + * Filter rows based on the presence of one or more values in a given column The filtering will be based on cell label or id Example: filter-by="evidence:ISS,ISO or multi-step filters: filter-by:evidence:ISS,ISO;term:xxx" Note: if value is "", remove any filtering + */ + filterBy?: string; + filterCrossAspect?: boolean; + filterReference?: string; + /** + * If true, the ribbon will fire an event if a user click an empty cell If false, the ribbon will not fire the event on an empty cell Note: if selectionMode == SELECTION.COLUMN, then the event will trigger if at least one of the selected cells has annotations + */ + fireEventOnEmptyCells?: boolean; + groupBaseUrl?: string; + /** + * Using this parameter, the table rows can bee grouped based on column ids A multiple step grouping is possible by using a ";" between groups The grouping applies before the ordering Example: hid-1,hid-3 OR hid-1,hid-3;hid-2 Note: if value is "", remove any grouping + */ + groupBy?: string; + groupClickable?: boolean; + groupMaxLabelSize?: number; + groupNewTab?: boolean; + /** + * Used to hide specific column of the table + */ + hideColumns?: string; + maxColor?: string; + maxHeatLevel?: number; + minColor?: string; + /** + * This is used to sort the table depending of a column The column cells must be single values The ordering applies after the grouping Note: if value is "", remove any ordering + */ + orderBy?: string; + /** + * If no value is provided, the ribbon will load without any group selected. If a value is provided, the ribbon will show the requested group as selected The value should be the id of the group to be selected + */ + selected?: any; + /** + * Click handling of a cell. 0 = select only the cell (1 subject, 1 group) 1 = select the whole column (all subjects, 1 group) + */ + selectionMode?: number; + /** + * add a cell at the end of each row/subject to represent all annotations not mapped to a specific term + */ + showOtherGroup?: boolean; + subjectBaseUrl?: string; + subjectOpenNewTab?: boolean; + /** + * Position the subject label of each row 0 = None 1 = Left 2 = Right 3 = Bottom + */ + subjectPosition?: number; + subjectUseTaxonIcon?: boolean; + /** + * provide gene ids (e.g. RGD:620474,RGD:3889 or as a list ["RGD:620474", "RGD:3889"]) + */ + subjects?: string; + subset?: string; + } interface WcLightModal { modalAnchor?: string; modalContent?: string; @@ -609,6 +791,7 @@ declare namespace LocalJSX { } interface IntrinsicElements { "wc-go-autocomplete": WcGoAutocomplete; + "wc-go-ribbon": WcGoRibbon; "wc-light-modal": WcLightModal; "wc-ribbon-cell": WcRibbonCell; "wc-ribbon-strips": WcRibbonStrips; @@ -623,6 +806,8 @@ declare module "@stencil/core" { interface IntrinsicElements { "wc-go-autocomplete": LocalJSX.WcGoAutocomplete & JSXBase.HTMLAttributes; + "wc-go-ribbon": LocalJSX.WcGoRibbon & + JSXBase.HTMLAttributes; "wc-light-modal": LocalJSX.WcLightModal & JSXBase.HTMLAttributes; "wc-ribbon-cell": LocalJSX.WcRibbonCell & diff --git a/src/components/go-ribbon-strips/readme.md b/src/components/go-ribbon-strips/readme.md index 3eed0fc..e808a82 100644 --- a/src/components/go-ribbon-strips/readme.md +++ b/src/components/go-ribbon-strips/readme.md @@ -63,6 +63,10 @@ Type: `Promise` ## Dependencies +### Used by + +- [wc-go-ribbon](../go-ribbon) + ### Depends on - [wc-spinner](../go-spinner) @@ -76,6 +80,7 @@ graph TD; wc-ribbon-strips --> wc-spinner wc-ribbon-strips --> wc-ribbon-subject wc-ribbon-strips --> wc-ribbon-cell + wc-go-ribbon --> wc-ribbon-strips style wc-ribbon-strips fill:#f9f,stroke:#333,stroke-width:4px ``` diff --git a/src/components/go-ribbon-strips/ribbon-strips.tsx b/src/components/go-ribbon-strips/ribbon-strips.tsx index bcb6d44..c3f4ce2 100644 --- a/src/components/go-ribbon-strips/ribbon-strips.tsx +++ b/src/components/go-ribbon-strips/ribbon-strips.tsx @@ -9,7 +9,8 @@ import { Watch, } from "@stencil/core"; -import { truncate, groupKey, subjectGroupKey, sameArray } from "./utils"; +import { truncate, groupKey, subjectGroupKey } from "./utils"; +import { sameArray } from "../../globals/utils"; import { RibbonModel, RibbonCategory, diff --git a/src/components/go-ribbon-strips/utils.ts b/src/components/go-ribbon-strips/utils.ts index 749a4d4..7f839c7 100644 --- a/src/components/go-ribbon-strips/utils.ts +++ b/src/components/go-ribbon-strips/utils.ts @@ -30,16 +30,3 @@ export function subjectGroupKey(subject, group) { group.type ); } - -export function sameArray(array1, array2) { - if (array1.length !== array2.length) { - return false; - } - - for (let i = 0; i < array1.length; i++) { - if (array1[i] !== array2[i]) { - return false; - } - } - return true; -} diff --git a/src/components/go-ribbon-table/readme.md b/src/components/go-ribbon-table/readme.md index af46912..1dd854f 100644 --- a/src/components/go-ribbon-table/readme.md +++ b/src/components/go-ribbon-table/readme.md @@ -42,6 +42,20 @@ Type: `Promise` Type: `Promise` +## Dependencies + +### Used by + +- [wc-go-ribbon](../go-ribbon) + +### Graph + +```mermaid +graph TD; + wc-go-ribbon --> wc-ribbon-table + style wc-ribbon-table fill:#f9f,stroke:#333,stroke-width:4px +``` + --- _Built with [StencilJS](https://stenciljs.com/)_ diff --git a/src/components/go-ribbon/go-ribbon.css b/src/components/go-ribbon/go-ribbon.css new file mode 100644 index 0000000..5d4e87f --- /dev/null +++ b/src/components/go-ribbon/go-ribbon.css @@ -0,0 +1,3 @@ +:host { + display: block; +} diff --git a/src/components/go-ribbon/go-ribbon.tsx b/src/components/go-ribbon/go-ribbon.tsx new file mode 100644 index 0000000..d18758e --- /dev/null +++ b/src/components/go-ribbon/go-ribbon.tsx @@ -0,0 +1,525 @@ +import { Component, Element, h, Prop, Watch, State } from "@stencil/core"; + +import { RibbonGroup } from "../../globals/models"; + +import { + COLOR_BY, + POSITION, + SELECTION, + FONT_CASE, + FONT_STYLE, +} from "../../globals/enums"; + +import { getCategory, getCategoryIdLabel, diffAssociations } from "./utils"; + +import { sameArray } from "../../globals/utils"; + +@Component({ + tag: "wc-go-ribbon", + styleUrl: "./go-ribbon.css", + shadow: false, +}) +export class GORibbon { + @Element() GORibbon; + + ribbonStrips: HTMLWcRibbonStripsElement; + ribbonTable: HTMLWcRibbonTableElement; + + @State() loadingTable = false; + + mockup = [ + { + subject: "UniProtKB:P04637", + slim: "GO:0003723", + assocs: [ + { + id: "556e6950726f744b420950303436333709545035330909474f3a3030303337333009504d49443a3136323133323132094944410909460943656c6c756c61722074756d6f7220616e746967656e20703533095035330970726f7465696e097461786f6e3a393630360932303137303132350943414641090909", + subject: { + id: "HGNC:11998", + iri: "http://identifiers.org/uniprot/P04637", + label: "TP53", + taxon: { + id: "NCBITaxon:9606", + iri: "http://purl.obolibrary.org/obo/NCBITaxon_9606", + label: "Homo sapiens", + }, + }, + object: { + id: "GO:0003730", + iri: "http://purl.obolibrary.org/obo/GO_0003730", + label: "mRNA 3'-UTR binding", + category: ["molecular_activity"], + }, + negated: false, + relation: null, + publications: [{ id: "UniProtKB" }], + provided_by: ["CAFA"], + reference: ["PMID:16213212"], + type: "protein", + evidence: "ECO:0000314", + evidence_label: "direct assay evidence used in manual assertion", + evidence_type: "IDA", + evidence_closure: [ + "ECO:0000352", + "ECO:0000000", + "ECO:0000002", + "ECO:0000006", + "ECO:0000314", + "ECO:0000269", + ], + evidence_closure_label: [ + "evidence used in manual assertion", + "evidence", + "direct assay evidence", + "experimental evidence", + "direct assay evidence used in manual assertion", + "experimental evidence used in manual assertion", + ], + evidence_subset_closure: ["ECO:0000006", "ECO:0000314"], + evidence_subset_closure_label: [ + "experimental evidence", + "direct assay evidence used in manual assertion", + ], + evidence_type_closure: [ + "evidence used in manual assertion", + "evidence", + "direct assay evidence", + "experimental evidence", + "direct assay evidence used in manual assertion", + "experimental evidence used in manual assertion", + ], + slim: ["GO:0003723"], + }, + ], + }, + ]; + + @Prop() filterReference = "PMID:,DOI:,GO_REF:,Reactome:"; + + @Prop() excludePB = true; + + @Prop() filterCrossAspect = true; + + @Prop() baseApiUrl = "https://api.geneontology.org/api/ontology/ribbon/"; + + @Prop() subjectBaseUrl: string = + "http://amigo.geneontology.org/amigo/gene_product/"; + @Prop() groupBaseUrl: string = "http://amigo.geneontology.org/amigo/term/"; + + @Prop() subset: string = "goslim_agr"; + + /** + * provide gene ids (e.g. RGD:620474,RGD:3889 or as a list ["RGD:620474", "RGD:3889"]) + */ + @Prop() subjects: string = undefined; + + @Prop() classLabels = "term,terms"; + @Prop() annotationLabels = "annotation,annotations"; + + /** + * Which value to base the cell color on + * 0 = class count + * 1 = annotation count + */ + @Prop() colorBy = COLOR_BY.ANNOTATION_COUNT; + + /** + * false = show a gradient of colors to indicate the value of a cell + * true = show only two colors (minColor; maxColor) to indicate the values of a cell + */ + @Prop() binaryColor = false; + @Prop() minColor = "255,255,255"; + @Prop() maxColor = "24,73,180"; + @Prop() maxHeatLevel = 48; + @Prop() groupMaxLabelSize = 60; + + /** + * Override of the category case + * 0 (default) = unchanged + * 1 = to lower case + * 2 = to upper case + */ + @Prop() categoryCase = FONT_CASE.LOWER_CASE; + + /** + * 0 = Normal + * 1 = Bold + */ + @Prop() categoryAllStyle = FONT_STYLE.NORMAL; + /** + * 0 = Normal + * 1 = Bold + */ + @Prop() categoryOtherStyle = FONT_STYLE.NORMAL; + + /** + * add a cell at the end of each row/subject to represent all annotations not mapped to a specific term + */ + @Prop() showOtherGroup = true; + + /** + * add a cell at the beginning of each row/subject to show all annotations + */ + @Prop() addCellAll: boolean = true; + + /** + * Position the subject label of each row + * 0 = None + * 1 = Left + * 2 = Right + * 3 = Bottom + */ + @Prop() subjectPosition = POSITION.LEFT; + @Prop() subjectUseTaxonIcon: boolean; + @Prop() subjectOpenNewTab: boolean = true; + @Prop() groupNewTab: boolean = true; + @Prop() groupClickable: boolean = true; + + /** + * Click handling of a cell. + * 0 = select only the cell (1 subject, 1 group) + * 1 = select the whole column (all subjects, 1 group) + */ + @Prop() selectionMode = SELECTION.CELL; + + /** + * If no value is provided, the ribbon will load without any group selected. + * If a value is provided, the ribbon will show the requested group as selected + * The value should be the id of the group to be selected + */ + @Prop() selected; + + /** + * If true, the ribbon will fire an event if a user click an empty cell + * If false, the ribbon will not fire the event on an empty cell + * Note: if selectionMode == SELECTION.COLUMN, then the event will trigger if at least one of the selected cells has annotations + */ + @Prop() fireEventOnEmptyCells = false; + + /** + * if provided, will override any value provided in subjects and subset + */ + @Prop() data: string; + + @State() selectedGroup: RibbonGroup; + + onlyExperimental = false; + + /** + * Using this parameter, the table rows can bee grouped based on column ids + * A multiple step grouping is possible by using a ";" between groups + * The grouping applies before the ordering + * Example: hid-1,hid-3 OR hid-1,hid-3;hid-2 + * Note: if value is "", remove any grouping + */ + @Prop() groupBy: string = "term,qualifier"; + + /** + * This is used to sort the table depending of a column + * The column cells must be single values + * The ordering applies after the grouping + * Note: if value is "", remove any ordering + */ + @Prop() orderBy: string = "term"; + + /** + * Filter rows based on the presence of one or more values in a given column + * The filtering will be based on cell label or id + * Example: filter-by="evidence:ISS,ISO or multi-step filters: filter-by:evidence:ISS,ISO;term:xxx" + * Note: if value is "", remove any filtering + */ + @Prop() filterBy: string; + + /** + * Used to hide specific column of the table + */ + @Prop() hideColumns: string = "qualifier"; + + /** + * Must follow the appropriate JSON data model + * Can be given as either JSON or stringified JSON + */ + @State() tableData: string; + + /** + * Reading biolink data. This will trigger a render of the table as would changing data + */ + @State() bioLinkData: string; + + /** + * This method is automatically called whenever the value of "subjects" changes + * @param newValue a new subject is submitted (e.g. gene) + * @param oldValue old value of the subject (e.g. gene or genes) + */ + @Watch("subjects") + subjectsChanged(newValue, oldValue) { + if (newValue != oldValue) { + this.bioLinkData = undefined; + this.tableData = undefined; + } + } + + /** + * Check if a HTML element has a parent with provided id + * @param {} elt HTML element to check + * @param {*} id id to look in the parents of provided element + */ + hasParentElementId(elt, id) { + if (elt.id == id) { + return true; + } + if (!elt.parentElement) { + return false; + } + return this.hasParentElementId(elt.parentElement, id); + } + + /** + * Add listeners to the Ribbon strips + */ + componentWillLoad() { + // if(this.hasParentElementId()) + document.addEventListener("cellClick", this.onCellClick.bind(this)); + document.addEventListener("groupClick", this.onGroupClick.bind(this)); + } + + /** + * Remove listeners to the Ribbon strips + */ + disconnectedCallback() { + document.removeEventListener("cellClick", this.onCellClick); + document.removeEventListener("groupClick", this.onGroupClick); + } + + applyTableFilters(data, group) { + if (this.filterReference != "") { + data = this.applyFilterReference(data); + } + if (this.excludePB) { + data = this.applyFilterPB(data); + } + if (this.filterCrossAspect) { + data = this.applyFilterCrossAspect(data, group); + } + return data; + } + + applyFilterReference(data) { + let filters = this.filterReference.includes(",") + ? this.filterReference.split(",") + : [this.filterReference.trim()]; + + for (let i = 0; i < data.length; i++) { + data[i].assocs = data[i].assocs.filter((assoc) => { + assoc.reference = assoc.reference.filter((ref) => + filters.some((filter) => ref.includes(filter)), + ); + return assoc; + }); + } + return data; + } + + applyFilterPB(data) { + for (let i = 0; i < data.length; i++) { + data[i].assocs = data[i].assocs.filter( + (assoc) => assoc.object.id != "GO:0005515", + ); + } + return data; + } + + applyFilterCrossAspect(data, group) { + let aspect = getCategoryIdLabel( + group, + this.ribbonStrips.ribbonSummary.categories, + ); + for (let i = 0; i < data.length; i++) { + data[i].assocs = data[i].assocs.filter((assoc) => { + let cat = + assoc.object.category[0] == "molecular_activity" + ? "molecular_function" + : assoc.object.category[0]; + return aspect == undefined || cat == aspect[1]; + }); + } + return data; + } + + sameSelection(selection) { + if (!this.previousSelection) { + return false; + } + let sameGroupID = selection.group.id == this.previousSelection.group.id; + let sameGroupType = + selection.group.type == this.previousSelection.group.type; + let sameSubject = sameArray( + selection.subjects, + this.previousSelection.subjects, + ); + + return sameGroupID && sameGroupType && sameSubject; + } + + previousSelection = null; + onCellClick(e) { + console.log("Cell Clicked", e.detail); + this.loadingTable = true; + + let selection = e.detail; + let group = selection.group; + let group_ids = group.id; + let subject_ids = selection.subjects.map((elt) => elt.id); + + if (this.sameSelection(selection)) { + this.bioLinkData = undefined; + this.previousSelection = null; + this.loadingTable = false; + console.log("yep that's the same"); + return; + } + + this.previousSelection = selection; + + if (group.id == "all") { + group_ids = this.ribbonStrips.ribbonSummary.categories.map((elt) => { + return elt.id; + }); + group_ids = group_ids.join("&slim="); + } + + const goApiUrl = "https://api.geneontology.org/api/"; + subject_ids = subject_ids.join("&subject="); + let query = + goApiUrl + + "bioentityset/slimmer/function?slim=" + + group_ids + + "&subject=" + + subject_ids + + "&rows=-1"; + console.log("query: ", query); + + // fetch the json data + fetch(query) + .then((response) => { + return response.json(); + }) + .then((data) => { + data = this.applyTableFilters(data, group); + + if (group.type == "Other") { + let aspect = getCategory( + group, + this.ribbonStrips.ribbonSummary.categories, + ); + let terms = aspect.groups.filter((elt) => { + return elt.type == "Term"; + }); + terms = terms.map((elt) => { + return elt.id; + }); + terms = terms.join("&slim="); + + let query_terms = + goApiUrl + + "bioentityset/slimmer/function?slim=" + + terms + + "&subject=" + + subject_ids + + "&rows=-1"; + console.log("query_terms: ", query_terms); + + // fetch the json data + fetch(query_terms) + .then((response_terms) => { + return response_terms.json(); + }) + .then((data_terms) => { + data_terms = this.applyTableFilters(data_terms, group); + + let concat_assocs = []; + for (let array of data_terms) { + concat_assocs = concat_assocs.concat(array.assocs); + } + + let other_assocs = diffAssociations( + data[0].assocs, + concat_assocs, + ); + data[0].assocs = other_assocs; + this.loadingTable = false; + this.bioLinkData = JSON.stringify(data); + }); + } else { + this.loadingTable = false; + this.bioLinkData = JSON.stringify(data); + } + }); + } + + onGroupClick(e) { + console.log("Group Clicked", e.detail); + } + + render() { + return [ + (this.ribbonStrips = el)} + base-api-url={this.baseApiUrl} + subject-base-url={this.subjectBaseUrl} + group-base-url={this.groupBaseUrl} + add-cell-all={this.addCellAll} + binary-color={this.binaryColor} + color-by={this.colorBy} + min-color={this.minColor} + max-color={this.maxColor} + max-heat-level={this.maxHeatLevel} + annotation-labels={this.annotationLabels} + class-labels={this.classLabels} + data={this.data} + category-case={this.categoryCase} + category-all-style={this.categoryAllStyle} + categoryOtherStyle={this.categoryOtherStyle} + group-max-label-size={this.groupMaxLabelSize} + group-new-tab={this.groupNewTab} + group-clickable={this.groupClickable} + fire-event-on-empty-cells={this.fireEventOnEmptyCells} + subjects={this.subjects} + subject-open-new-tab={this.subjectOpenNewTab} + subject-position={this.subjectPosition} + subject-use-taxon-icon={this.subjectUseTaxonIcon} + selection-mode={this.selectionMode} + selected={this.selected} + subset={this.subset} + show-other-group={this.showOtherGroup} + />, + + (this.subjects && this.subjects.length > 0) || this.data ? ( +
+ Cell color indicative of annotation volume +
+ ) : ( + "" + ), + + this.loadingTable ? ( + + ) : ( + (this.ribbonTable = el)} + base-api-url={this.baseApiUrl} + subject-base-url={this.subjectBaseUrl} + group-base-url={this.groupBaseUrl} + data={this.tableData} + bio-link-data={this.bioLinkData} + group-by={this.groupBy} + order-by={this.orderBy} + filter-by={this.filterBy} + hide-columns={this.hideColumns} + /> + ), + ]; + } +} diff --git a/src/components/go-ribbon/index.html b/src/components/go-ribbon/index.html new file mode 100644 index 0000000..f835480 --- /dev/null +++ b/src/components/go-ribbon/index.html @@ -0,0 +1,46 @@ + + + + + + Web Component GO Ribbon + + + + + + +
+ Add gene to the GO ribbon:
(can use gene symbol or model organism ID such as RGD:3889)
+ + + +
+ + + + + + + + diff --git a/src/components/go-ribbon/readme.md b/src/components/go-ribbon/readme.md new file mode 100644 index 0000000..0105faa --- /dev/null +++ b/src/components/go-ribbon/readme.md @@ -0,0 +1,67 @@ +# wc-go-ribbon + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| ----------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | ----------------------------------------------------- | +| `addCellAll` | `add-cell-all` | add a cell at the beginning of each row/subject to show all annotations | `boolean` | `true` | +| `annotationLabels` | `annotation-labels` | | `string` | `"annotation,annotations"` | +| `baseApiUrl` | `base-api-url` | | `string` | `"https://api.geneontology.org/api/ontology/ribbon/"` | +| `binaryColor` | `binary-color` | false = show a gradient of colors to indicate the value of a cell true = show only two colors (minColor; maxColor) to indicate the values of a cell | `boolean` | `false` | +| `categoryAllStyle` | `category-all-style` | 0 = Normal 1 = Bold | `number` | `FONT_STYLE.NORMAL` | +| `categoryCase` | `category-case` | Override of the category case 0 (default) = unchanged 1 = to lower case 2 = to upper case | `number` | `FONT_CASE.LOWER_CASE` | +| `categoryOtherStyle` | `category-other-style` | 0 = Normal 1 = Bold | `number` | `FONT_STYLE.NORMAL` | +| `classLabels` | `class-labels` | | `string` | `"term,terms"` | +| `colorBy` | `color-by` | Which value to base the cell color on 0 = class count 1 = annotation count | `number` | `COLOR_BY.ANNOTATION_COUNT` | +| `data` | `data` | if provided, will override any value provided in subjects and subset | `string` | `undefined` | +| `excludePB` | `exclude-p-b` | | `boolean` | `true` | +| `filterBy` | `filter-by` | Filter rows based on the presence of one or more values in a given column The filtering will be based on cell label or id Example: filter-by="evidence:ISS,ISO or multi-step filters: filter-by:evidence:ISS,ISO;term:xxx" Note: if value is "", remove any filtering | `string` | `undefined` | +| `filterCrossAspect` | `filter-cross-aspect` | | `boolean` | `true` | +| `filterReference` | `filter-reference` | | `string` | `"PMID:,DOI:,GO_REF:,Reactome:"` | +| `fireEventOnEmptyCells` | `fire-event-on-empty-cells` | If true, the ribbon will fire an event if a user click an empty cell If false, the ribbon will not fire the event on an empty cell Note: if selectionMode == SELECTION.COLUMN, then the event will trigger if at least one of the selected cells has annotations | `boolean` | `false` | +| `groupBaseUrl` | `group-base-url` | | `string` | `"http://amigo.geneontology.org/amigo/term/"` | +| `groupBy` | `group-by` | Using this parameter, the table rows can bee grouped based on column ids A multiple step grouping is possible by using a ";" between groups The grouping applies before the ordering Example: hid-1,hid-3 OR hid-1,hid-3;hid-2 Note: if value is "", remove any grouping | `string` | `"term,qualifier"` | +| `groupClickable` | `group-clickable` | | `boolean` | `true` | +| `groupMaxLabelSize` | `group-max-label-size` | | `number` | `60` | +| `groupNewTab` | `group-new-tab` | | `boolean` | `true` | +| `hideColumns` | `hide-columns` | Used to hide specific column of the table | `string` | `"qualifier"` | +| `maxColor` | `max-color` | | `string` | `"24,73,180"` | +| `maxHeatLevel` | `max-heat-level` | | `number` | `48` | +| `minColor` | `min-color` | | `string` | `"255,255,255"` | +| `orderBy` | `order-by` | This is used to sort the table depending of a column The column cells must be single values The ordering applies after the grouping Note: if value is "", remove any ordering | `string` | `"term"` | +| `selected` | `selected` | If no value is provided, the ribbon will load without any group selected. If a value is provided, the ribbon will show the requested group as selected The value should be the id of the group to be selected | `any` | `undefined` | +| `selectionMode` | `selection-mode` | Click handling of a cell. 0 = select only the cell (1 subject, 1 group) 1 = select the whole column (all subjects, 1 group) | `number` | `SELECTION.CELL` | +| `showOtherGroup` | `show-other-group` | add a cell at the end of each row/subject to represent all annotations not mapped to a specific term | `boolean` | `true` | +| `subjectBaseUrl` | `subject-base-url` | | `string` | `"http://amigo.geneontology.org/amigo/gene_product/"` | +| `subjectOpenNewTab` | `subject-open-new-tab` | | `boolean` | `true` | +| `subjectPosition` | `subject-position` | Position the subject label of each row 0 = None 1 = Left 2 = Right 3 = Bottom | `number` | `POSITION.LEFT` | +| `subjectUseTaxonIcon` | `subject-use-taxon-icon` | | `boolean` | `undefined` | +| `subjects` | `subjects` | provide gene ids (e.g. RGD:620474,RGD:3889 or as a list ["RGD:620474", "RGD:3889"]) | `string` | `undefined` | +| `subset` | `subset` | | `string` | `"goslim_agr"` | + +## Dependencies + +### Depends on + +- [wc-ribbon-strips](../go-ribbon-strips) +- [wc-spinner](../go-spinner) +- [wc-ribbon-table](../go-ribbon-table) + +### Graph + +```mermaid +graph TD; + wc-go-ribbon --> wc-ribbon-strips + wc-go-ribbon --> wc-spinner + wc-go-ribbon --> wc-ribbon-table + wc-ribbon-strips --> wc-spinner + wc-ribbon-strips --> wc-ribbon-subject + wc-ribbon-strips --> wc-ribbon-cell + style wc-go-ribbon fill:#f9f,stroke:#333,stroke-width:4px +``` + +--- + +_Built with [StencilJS](https://stenciljs.com/)_ diff --git a/src/components/go-ribbon/utils.ts b/src/components/go-ribbon/utils.ts new file mode 100644 index 0000000..403e7a2 --- /dev/null +++ b/src/components/go-ribbon/utils.ts @@ -0,0 +1,67 @@ +/** + * Return the category object for a given group + * @param {*} group group object (eg ontology term) + */ +export function getCategory(group, categories) { + let cat = categories.filter((cat) => { + return cat.groups.some((gp) => gp.id == group.id); + }); + return cat.length > 0 ? cat[0] : undefined; +} + +/** + * Return the category [id, label] for a given group + * @param {*} group group object (eg ontology term) + */ +export function getCategoryIdLabel(group, categories) { + let cat = categories.filter((cat) => { + return cat.groups.some((gp) => gp.id == group.id); + }); + return cat.length > 0 ? [cat[0].id, cat[0].label] : undefined; +} + +export function associationKey(assoc) { + if (assoc.qualifier) { + return ( + assoc.subject.id + + "@" + + assoc.object.id + + "@" + + assoc.negated + + "@" + + assoc.qualifier.join("-") + ); + } + return assoc.subject.id + "@" + assoc.object.id + "@" + assoc.negated; +} + +export function fullAssociationKey(assoc) { + var key = + associationKey(assoc) + + "@" + + assoc.evidence_type + + "@" + + assoc.provided_by + + "@" + + assoc.reference.join("#"); + return key; +} + +export function diffAssociations(assocs_all, assocs_exclude) { + var list = []; + for (let assoc of assocs_all) { + let found = false; + let key_all = fullAssociationKey(assoc); + for (let exclude of assocs_exclude) { + let key_exclude = fullAssociationKey(exclude); + if (key_all == key_exclude) { + found = true; + break; + } + } + if (!found) { + list.push(assoc); + } + } + return list; +} diff --git a/src/components/go-spinner/readme.md b/src/components/go-spinner/readme.md index 2887b68..984d701 100644 --- a/src/components/go-spinner/readme.md +++ b/src/components/go-spinner/readme.md @@ -14,12 +14,14 @@ ### Used by +- [wc-go-ribbon](../go-ribbon) - [wc-ribbon-strips](../go-ribbon-strips) ### Graph ```mermaid graph TD; + wc-go-ribbon --> wc-spinner wc-ribbon-strips --> wc-spinner style wc-spinner fill:#f9f,stroke:#333,stroke-width:4px ``` diff --git a/src/globals/utils.ts b/src/globals/utils.ts new file mode 100644 index 0000000..929f698 --- /dev/null +++ b/src/globals/utils.ts @@ -0,0 +1,12 @@ +export function sameArray(array1, array2) { + if (array1.length !== array2.length) { + return false; + } + + for (let i = 0; i < array1.length; i++) { + if (array1[i] !== array2[i]) { + return false; + } + } + return true; +} diff --git a/src/index.html b/src/index.html index b083937..3f1fefc 100644 --- a/src/index.html +++ b/src/index.html @@ -19,6 +19,9 @@
  • Light Modal
  • +
  • + Ribbon +
  • Ribbon Strips