Skip to content

Commit

Permalink
wip: working on headers
Browse files Browse the repository at this point in the history
  • Loading branch information
sehilyi committed Jan 17, 2024
1 parent a7142c2 commit b244ef4
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 3 deletions.
2 changes: 1 addition & 1 deletion editor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1211,7 +1211,7 @@ function Editor(props: RouteComponentProps) {
background: isResponsive ? 'white' : 'none'
}}
>
<gosling.GoslingComponent
<gosling.GoslingComponentWithHeader
ref={gosRef}
spec={goslingSpec}
theme={theme}
Expand Down
2 changes: 1 addition & 1 deletion editor/example/json-spec/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { EX_SPEC_CYTOBANDS } from './ideograms';
import { EX_SPEC_PILEUP } from './pileup';
import { EX_SPEC_TEMPLATE } from './track-template';
import { EX_SPEC_MOUSE_EVENT } from './mouse-event';
import { EX_SPEC_PERF_ALIGNMENT } from './perf-alignment'
import { EX_SPEC_PERF_ALIGNMENT } from './perf-alignment';
import { EX_SPEC_DEBUG } from './debug';

export const JsonExampleSpecs = {
Expand Down
2 changes: 1 addition & 1 deletion src/core/gosling-component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type CompiledCallbackFn = (
goslingSpec: gosling.GoslingSpec,
higlassSpec: gosling.HiGlassSpec,
_additionalData: { _processedSpec: gosling.GoslingSpec }
) => void
) => void;

interface GoslingCompProps {
spec?: gosling.GoslingSpec;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#gosling-component-header {
padding: 3px;
}

#gosling-component-header > select,
#gosling-component-header > button,
#gosling-component-header > input {
margin-right: 4px;
}

.sel-btn-activated,
.sel-btn-activated:hover {
background-color: orange !important;
}

#gosling-component-header > select {
min-width: 100px;
}

#gosling-component-header > button {
cursor: pointer;
border: 1px solid black;
border-radius: 4px;
}

#gosling-component-header > button:hover {
background-color: #ddd;
}
132 changes: 132 additions & 0 deletions src/gosling-component-with-header/gosling-component-with-header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import React, { createContext, useContext, useRef, type ComponentPropsWithRef, useLayoutEffect, useState, forwardRef, useMemo, useEffect } from 'react';
import { GoslingComponent, type GoslingRef } from '../core/gosling-component';
import type { TrackApiData } from '@gosling-lang/gosling-schema'
import './gosling-component-with-header.css';

const ZOOM_DURATION = 300;

type goslingCompoProps = ComponentPropsWithRef<typeof GoslingComponent>;
const GoslingContext = createContext<goslingCompoProps>({});

type GoslingComponentWithHeaderProps = goslingCompoProps & {
// ... anything to add?
};

// forwardRef<GoslingRef, GoslingCompProps>((props, ref) => {
// (props: GoslingComponentWithHeaderProps) {
export const GoslingComponentWithHeader = forwardRef<GoslingRef, GoslingComponentWithHeaderProps>((props) => {
const ref = useRef<GoslingRef>(null);

const [selection, setSelection] = useState(false);
const [tracks, setTracks] = useState<TrackApiData[]>([]);
const [selectedTrackIdx, setSelectedTrackIdx] = useState<number>(0);

useEffect(() => {
document.addEventListener('keydown', (e) => {
switch(e.key) {
case 'ArrowDown':
setSelectedTrackIdx((selectedTrackIdx + 1) % tracks.length);
event?.preventDefault();
break;
case 'ArrowUp':
setSelectedTrackIdx((selectedTrackIdx - 1) % tracks.length);
event?.preventDefault();
break;
}
});
return document.removeEventListener('keydown', () => {});
}, []);

type NavigationType = '<' | '>' | '+' | '-' | string;
const navigationFn = (type: NavigationType) => {
const trackIds = ref.current?.api.getTrackIds();

if(!trackIds || trackIds.length === 0) {
return;
}

const trackId = trackIds[selectedTrackIdx ?? 1];

let [start, end] = ref.current?.hgApi.api.getLocation(trackId).xDomain;
const delta = (end - start) / 3.0;
if(type === '+') {
start += delta;
end -= delta;
} else if(type === '-') {
start -= delta;
end += delta;
} else if(type === '<') {
start -= delta;
end -= delta;
} else if(type === '>') {
start += delta;
end += delta;
} else {
if(type.includes('chr')) {
// chr1:100-200
ref?.current?.api.zoomTo(trackId, type, 0, ZOOM_DURATION);
} else {
// MYC
ref?.current?.api.suggestGene(trackId, type, (genes) => {
if(genes.length > 0) {
ref?.current?.api.zoomToGene(trackId, genes[0].geneName, 0, ZOOM_DURATION);
}
});
}
}
ref?.current?.api.zoomTo(trackId, `chr1:${start}-${end}`, 0, ZOOM_DURATION);
};

const Header = useMemo(() => {
// const { ref } = useContext(GoslingContext);
return (
<div id='gosling-component-header' style={{ border: "1px solid grey" }}>
{'Experimental! '}
<select>{tracks.map((d, i) => <option onSelect={() => setSelectedTrackIdx(i)}>{d.id}</option>)}</select>
<button
className={selection ? 'sel-btn-activated' : 'sel-btn'}
onClick={() => setSelection(!selection)}
>🕹️</button>
<input type='text' placeholder='chr1:100-200'
onKeyDown={(e) => {
if(e.key === 'Enter') navigationFn(e.currentTarget.value);
}}
></input>
{['<', '>', '+', '-'].map((d: NavigationType) => <button onClick={() => navigationFn(d)}>{d}</button>)}
<button onClick={() => ref?.current?.api.exportPng()}>Save PNG</button>
<button onClick={() => ref?.current?.api.exportPng()}>Save PNG</button>
</div>
);
}, [tracks, selection]);

const Selection = useMemo(() => {
if(!selection || !tracks[selectedTrackIdx]) return;
return (
<div style={{
left: tracks[selectedTrackIdx].shape.x + 60 + 4,
top: tracks[selectedTrackIdx].shape.y + 60 + 30 + 4,
width: `${tracks[selectedTrackIdx].shape.width}px`,
height: `${tracks[selectedTrackIdx].shape.height}px`,
border: '2px solid blue',
position: 'absolute',
}}></div>
);
}, [selection, selectedTrackIdx, tracks]);

return (
<GoslingContext.Provider value={{ ...props, ref }}>
{/* XXX: Handle the sizing of Gosling Component */}
{Header}
<GoslingComponent {...props} ref={ref} compiled={(...args) => {
// XXX: Update of track info is one React rendering cycle behind
// Update Track information for API calls from <Header/>
const _tracks = ref.current?.api.getTracks() ?? [];
setTracks(_tracks.filter(d => d.spec.mark !== 'header'));
console.error('compiled');
// Call this function for the user-defined callback
props.compiled?.(...args);
}}/>
{Selection}
</GoslingContext.Provider>
);
});
1 change: 1 addition & 0 deletions src/gosling-component-with-header/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { GoslingComponentWithHeader } from './gosling-component-with-header';
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ export { init } from './core/init';
export { compile } from './compiler/compile';
export { validateGoslingSpec } from '@gosling-lang/gosling-schema';
export { GoslingComponent } from './core/gosling-component';
export { GoslingComponentWithHeader } from './gosling-component-with-header';
export type { GoslingRef } from './core/gosling-component';
export { embed } from './core/gosling-embed';

0 comments on commit b244ef4

Please sign in to comment.