Gramene's fork of @ebi-gene-expression-group/anatomogram
2.4.0, published on npm as gramene-anatomogram. It draws the Expression Atlas tissue SVGs next to the heatmaps
of gramene-atlas-heatmap in Gramene sites (SorghumBase first).
What is different from 2.4.0:
- React 18, as a peer dependency, with no React deprecation warnings: recompose, react-svg, styled-components,
ReactDOM.renderand function-componentdefaultPropsare gone. - The SVGs and icons are bundled. Each SVG is its own lazily imported chunk, so only the species/view on screen is
fetched, from wherever the host serves its bundle. There is no
atlasUrl, publicPath or webpack-onlyrequire(), so it builds with Parcel, Vite and webpack alike. - Each SVG is injected once per species/view. Changing
showIds/highlightIds/selectIdsrepaints the tissues in place instead of re-injecting the SVG. - ESM only. The code keeps upstream's module layout; see Fork maintenance.
npm install gramene-anatomogram react@^18.2 react-dom@^18.2import Anatomogram, { isSupportedSpecies } from 'gramene-anatomogram'
{isSupportedSpecies(species) && // e.g. 'sorghum_bicolor' or 'Sorghum bicolor'
<Anatomogram
species={species}
showIds={['PO_0025034', 'PO_0009005']}
highlightIds={highlighted}
onMouseOver={(ids) => setHighlighted(ids)} // ids: string[]
onMouseOut={() => setHighlighted([])}
/>}Without React in the host page:
import { render } from 'gramene-anatomogram'
const root = render({ species: 'sorghum_bicolor', highlightIds: ['PO_0025034'] }, 'target') // element id or element
render({ species: 'sorghum_bicolor', highlightIds: [] }, 'target') // updates the same root
root.unmount()| Prop | Type | Default | Notes |
|---|---|---|---|
species |
string | required | An id such as sorghum_bicolor, or a name such as Sorghum bicolor (see normaliseSpecies). Unsupported species render nothing |
showIds, highlightIds, selectIds |
string[] | [] |
Ontology ids to paint. Compared by value, so new arrays with the same ids do not repaint. Select > highlight > show |
showColour, highlightColour, selectColour |
string | grey, red, purple |
|
showOpacity, highlightOpacity, selectOpacity |
number | 0.4 |
+0.2 while the mouse is over a tissue; shown tissues hover in the highlight colour |
onMouseOver, onMouseOut, onClick |
(ids: string[]) => void |
no-op | All the ids of the tissue under the mouse (several ids can share a shape through <use>). The latest function is always called; a new identity does not re-register listeners |
onInjected |
(error, svg) => void |
no-op | (null, svgElement) once per injected SVG, after painting; (error) when the SVG cannot be loaded |
linkTarget |
string | null | '_blank' |
target of the links in the SVG (the EBI licence attribution); _blank adds rel="noopener noreferrer". null leaves them untouched |
loadSvg |
(species, view) => Promise<string> |
bundled SVGs | SVG text from somewhere else, e.g. hosted or trimmed files. view is null for single-view species. Results are cached per function, so pass a stable one. Trusted sources only: <script>, <foreignObject>, on* attributes and javascript: URLs are removed, but this is not a sanitiser |
atlasUrl |
string | Deprecated, ignored. Accepted so that 2.x callers keep working |
The ids of each SVG are listed in src/json/svgsMetadata.json (also exported as
svgsMetadata).
| Export | |
|---|---|
default |
The component |
render(options, target) |
Renders into target (an element id or an element) with createRoot, one root per container; returns the root |
anatomogramSpecies, supportedSpecies |
The 20 species with an SVG (the same array) |
normaliseSpecies(name) |
Lower-cases and turns each run of whitespace into _: 'Sorghum bicolor' → 'sorghum_bicolor' |
isSupportedSpecies(name) |
supportedSpecies.includes(normaliseSpecies(name)) |
getAnatomogramViews(species), getDefaultView(species) |
Views of a species (['whole_plant', 'flower_parts']), []/null for single-view species, undefined for unsupported ones |
svgsMetadata |
[{filename, species, view, ids}] for the 31 SVGs |
dist/gramene-anatomogram.js(~75 kB) is the entry. Each of the 31 SVGs is a chunk indist/svg/that exports the file's text; the entry reaches it with a plainimport(). The host's bundler emits the chunks next to its own bundle and loads them relative to it, so nothing is fetched from EBI or from anatlasUrl. Only the species/view on screen is loaded, so the animal SVGs cost nothing at runtime. The SVGs total 13.5 MB (5 MB gzipped as a tarball).- The view switcher icons are
data:URLs inside the entry: a host Content-Security-Policy needsimg-src data:. - ESM only: a CommonJS copy would duplicate the SVG strings. Node ≥20.19 or ≥22.12 can
require()it (require(esm)); Jest's module loader cannot. - The package deliberately has no
sourceorbrowserfield: Parcel would then compilesrc/, whoseimport.meta.globonly Vite understands. <use>caveat (animal SVGs only, as upstream): tissues such asUBERON_0000178are<use xlink:href="#CL_0000738">elements. Two instances showing the same animal SVG put duplicate ids in the page. Plant SVGs have no<use>.
- Change the package name. React 18 and an ESM-aware bundler are required.
- Drop
atlasUrland any hosting of the SVG/PNG files. render()now usescreateRoot, returns the root and accepts an element as well as an id.- Callbacks receive an array of ids. They always did; the upstream README said a single string.
- DOM:
div.gxa-anatomogram> (div.gxa-anatomogram-switcher>img[role=button],div.gxa-anatomogram-svg>svg.injected-svg). The svg is one wrapper shallower than react-svg'sdiv > div > svg, and there are no styled-components classes: all styles are inline and no global CSS is injected. normaliseSpeciesreplaces every run of whitespace, not only the first run of spaces.
npm install
npm test # vitest + jsdom; any console.error/warn fails the test unless it calls allowConsole()
npm run build # dist/, then scripts/check-dist.mjs
npm run lint:pkg # publint
npm run pack:local # build, then gramene-anatomogram-3.0.0.tgz
npm run dev # playground (html/) on http://localhost:5176The playground starts on sorghum and lists plants first. It has an event log (also written to the console as
[anatomogram] …), a two-instance switch, a remount button, a linkTarget selector and a switch that passes the
display name (Sorghum bicolor). URL parameters: ?species=<id> and ?strict=1 (StrictMode).
From a workstation: ssh -L 5176:localhost:5176 <host>.
To try an unpublished build in a consumer, install the tarball rather than npm link (a symlink would load a second
React):
npm run pack:local
cd ../atlas-heatmap && npm install --no-save ../anatomogram/gramene-anatomogram-3.0.0.tgz
# gramene-search: npm install --no-save ../anatomogram/gramene-anatomogram-3.0.0.tgz ../atlas-heatmap/gramene-atlas-heatmap-6.0.0.tgz && rm -rf .parcel-cache*The consumer's next npm install puts the registry version back.
After editing an SVG (see the authoring guidelines in src/svg/README.md), run
npm run parseSvgs to regenerate src/json/svgsMetadata.json. The build does not run it, but a test fails when the
JSON is stale. fast-xml-parser stays pinned to 3.14.0 as upstream had it (3.15 broke its validation). npm audit
flags that version; it is a development dependency only.
-
Remotes:
originiswarelab/anatomogram,upstreamisebi-gene-expression-group/anatomogram. -
Branches:
mastermirrors upstream and is never committed to.grameneis the default branch, cut from upstream'sv2.4.0tag; releases are taggedvX.Y.Zon it. -
Edit upstream files in place without reformatting them. Leave
src/svg/,src/img/,src/json/,src/Assets.jsandwiki/byte-identical to upstream, apart from SVG changes made on purpose. Make focused commits. -
Review what the fork changes:
git fetch upstream --tags git diff -M --stat upstream/master...gramene -- . ':(exclude)package-lock.json' git diff -M upstream/master...gramene -- src html scripts __test__ git diff --stat upstream/master -- src/svg src/img src/json src/Assets.js wiki # empty
-
Taking upstream changes:
git merge upstream/master(or cherry-pick) intogramene, thennpm testandnpm run build. -
Releasing: bump
version, add aCHANGELOG.mdentry,npm run pack:localand try the tarball in the consumers, then tag andnpm publish(prepublishOnlyruns the tests, the build with check-dist, and publint).
This is an anatomogram for Expression Atlas that we use to illustrate the experiments.
The ontology IDs are sourced from Uberon, EFO and Plant Ontology.
To install:
npm install --save @ebi-gene-expression-group/anatomogramYou can use it as a React component:
import Anatomogram from '@ebi-gene-expression-group/anatomogram'
...
<Anatomogram .../>Alternatively, if you don’t use React we’re providing a convenience render method:
import {render} from '@ebi-gene-expression-group/anatomogram'
...
render(options, target)Where options are the props passed as an object, and target is an ID of the DOM element that will contain the
anatomogram.
For example code, have a look at the demo component and how we use it in our Expression Atlas Heatmap. If you want to see it in action, go to an organism part experiment in Atlas or a search that returns organism part experiments.
| Name | Type | Default value | Description |
|---|---|---|---|
| species | string | - | This is the only required attribute of the anatomogram |
| showIds | array | [] | |
| highlightIds | array | [] | |
| selectIds | array | [] | |
| showColour | string | grey | |
| highlightColour | string | red | |
| selectColour | string | purple | |
| showOpacity | number | 0.4 | |
| highlightOpacity | number | 0.4 | |
| selectOpacity | number | 0.4 | |
| onMouseOver | function | () => {} |
Callback invoked when the mouse is hovered on a tissue |
| onMouseOut | function | () => {} |
Callback invoked when the mouse is hovered off a tissue |
| onClick | function | () => {} |
Callback invoked when a tissue is clicked |
| onInjected | function | () => {} |
Callback invoked when SVG is injected |
For a list of available species and IDs have a look at
src/json/svgsMetadata.json. The file is generated with npm run parseSvgs (Gramene fork: no longer on every publish; a test checks it is up to date).
All three callbacks take a single argument, the array of IDs of the tissue affected by the mouse event (several IDs
can share a shape through <use> elements). (Gramene fork: corrected; this README used to say a single string.)
The state of a tissue is either not shown, shown, highlighted or selected. On mouseover the opacity is increased by
0.2. You can read a more technically inclined description of the anatomogram behaviour in the repository
wiki.
(Gramene fork: the webpack build and atlasUrl/--output-public-path setup are gone. The SVGs and icons are bundled
with the code; see How the assets load.)
Read carefully our authoring guidelines before adding new tissues or updating IDs in an existing anatomogram or adding a new SVG.
Run npm run parseSvgs to parse the updated IDs. When you are finished run a local copy of the demo page:
npm run devGo to localhost:5176 and see that the tissues show up like you want them to.
Code is distributed under the Apache License version 2.0. All images, anatomograms and switcher icons, are distributed under the Creative Commons Attribution 4.0 International license. See LICENSE and NOTICE.