Skip to content

Commit

Permalink
Merge 416382d into c2d0166
Browse files Browse the repository at this point in the history
  • Loading branch information
akmorrow13 committed Aug 27, 2019
2 parents c2d0166 + 416382d commit c7bb612
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 40 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"q": "^1.1.2",
"react": "^0.14.0",
"react-dom": "^0.14.0",
"react-color": "^2.17.3",
"shallow-equals": "0.0.0",
"underscore": "^1.7.0",
"memory-cache": "0.1.6"
Expand Down
105 changes: 94 additions & 11 deletions src/main/Menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,94 @@

'use strict';


import { SketchPicker } from 'react-color';
import React from 'react';

// RGBA = red, green, blue, alpha
// each value is between 0 and 1
type RGBA = {
r: number, g: number, b: number, a: number
};

// some color pickers require hex as color input,
// others require rgb
type ColorItem = {
hex: string,
rgb: RGBA
};

type MenuItem = {
key: string;
label: string;
checked?: boolean;
color?: ColorItem;
};

type Props = {
header: string;
items: Array<MenuItem|'-'>;
onSelect: (key: string) => void;
onClick: (item: Object) => void;
};

class Menu extends React.Component<Props> {
type State = {
// list of booleans determining whether to show color palette for MenuItem
showPalette: boolean[];
};

class Menu extends React.Component<Props, State> {
props: Props;
state: State;

constructor(props: Object) {
super(props);
this.state = {
showPalette: new Array(props.items.length).fill(false)
};
}

// toggle color picker for menu items that have color enabled
toggleColorPicker(key: number, e: SyntheticEvent<>) {
if (this.state.showPalette[key] == false) {
this.state.showPalette[key] = true
this.setState({showPalette: this.state.showPalette});
} else {
this.state.showPalette[key] = false
this.setState({showPalette: this.state.showPalette});
}
}

clickHandler(idx: number, e: SyntheticMouseEvent<>) {
e.preventDefault();
clickHandler(idx: number, e: SyntheticMouseEvent<>, togglePicker: boolean = true) {
// do not call preventDefault on nullified events to avoid warnings
if (e.eventPhase != null) {
e.preventDefault();
}
var item = this.props.items[idx];
if (typeof(item) == 'string') return; // for flow
this.props.onSelect(item.key);

// propogate root update if new opts do not == old opts
this.props.onClick(item);

if (item.color && togglePicker) {
this.toggleColorPicker(idx, e)
}
}

handleColorChange(idx: number, color: Object, e: SyntheticMouseEvent<>) {
// update both hex and rgb values
if (typeof(this.props.items[idx]) == 'string') return; // for flow not working
if (typeof(this.props.items[idx]) === 'undefined') return; // for flow not working
this.props.items[idx].color = {
'hex': color.hex,
'rgb': color.rgb
};
this.clickHandler(idx, e, false);
};

render(): any {
var makeHandler = i => this.clickHandler.bind(this, i);
var makeColorPickerHandler = i => this.handleColorChange.bind(this, i);

var els = [];
if (this.props.header) {
els.push(<div key='header' className='menu-header'>{this.props.header}</div>);
Expand All @@ -51,12 +113,33 @@ class Menu extends React.Component<Props> {
return <div key={i} className='menu-separator' />;
} else {
var checkClass = 'check' + (item.checked ? ' checked' : '');
return (
<div key={i} className='menu-item' onClick={makeHandler(i)}>
<span className={checkClass}></span>
<span className='menu-item-label'>{item.label}</span>
</div>
);
// initially hide color picker
if (typeof(item) != 'string' && item.color) {
var colorPicker = null

if (this.state.showPalette[i]) {
colorPicker = (
<SketchPicker
color={item.color.rgb}
onChangeComplete={ makeColorPickerHandler(i) }
/>)
}

return (
<div key={i} className='menu-item' >
<span className={checkClass}></span>
<span key={i} onClick={makeHandler(i)} className='menu-item-label'>{item.label}</span>
{colorPicker}
</div>
);
} else {
return (
<div key={i} className='menu-item' onClick={makeHandler(i)}>
<span className={checkClass}></span>
<span className='menu-item-label'>{item.label}</span>
</div>
);
}
}
}));

Expand Down
39 changes: 30 additions & 9 deletions src/main/Root.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import type {GenomeRange} from './types';
import type {TwoBitSource} from './sources/TwoBitDataSource';
import type {VisualizedTrack, VizWithOptions} from './types';

import _ from 'underscore';

import React from 'react';
import Controls from './Controls';
import Menu from './Menu';
Expand All @@ -30,6 +32,8 @@ class Root extends React.Component<Props, State> {
props: Props;
state: State;
trackReactElements: Array<Object>; //it's an array of reactelement that are created for tracks
outsideClickHandler: (a: any) => void;
node: any; // used to track clicking outside this component

constructor(props: Object) {
super(props);
Expand All @@ -40,6 +44,8 @@ class Root extends React.Component<Props, State> {
settingsMenuKey: null
};
this.trackReactElements = [];
this.node = null;
this.outsideClickHandler = this.handleOutsideClick.bind(this);
}

componentDidMount() {
Expand Down Expand Up @@ -71,20 +77,33 @@ class Root extends React.Component<Props, State> {
}).done();
}

toggleSettingsMenu(key: string, e: SyntheticEvent<>) {
// key can be string or null
toggleSettingsMenu(key: any, e: SyntheticEvent<>) {
if (this.state.settingsMenuKey == key) {
this.setState({settingsMenuKey: null});
document.removeEventListener('click', this.outsideClickHandler, false);
} else {
this.setState({settingsMenuKey: key});
// remove event listener for clicking off of menu
document.addEventListener('click', this.outsideClickHandler, false);
}
}

handleSelectOption(trackKey: string, optionKey: string) {
this.setState({settingsMenuKey: null});
handleOutsideClick(e: SyntheticEvent<>) {
// if menu is visible and click is outside of menu component,
// toggle view off
if (this.state.settingsMenuKey != null && this.state.settingsMenuKey != undefined) {
if (!this.node.contains(e.target)) {
this.toggleSettingsMenu(this.state.settingsMenuKey, e);
}
}
}

handleSelectOption(trackKey: string, item: Object) {
var viz = this.props.tracks[Number(trackKey)].visualization;
var oldOpts = viz.options;
// $FlowIgnore: TODO remove flow suppression
var newOpts = viz.component.handleSelectOption(optionKey, oldOpts);
var newOpts = viz.component.handleSelectOption(item, oldOpts);
viz.options = newOpts;
if (newOpts != oldOpts) {
this.forceUpdate();
Expand All @@ -94,7 +113,7 @@ class Root extends React.Component<Props, State> {
makeDivForTrack(key: string, track: VisualizedTrack): React$Element<'div'> {
//this should be improved, but I have no idea how (when trying to
//access this.trackReactElements with string key, flow complains)
var intKey = parseInt(key);
var intKey = parseInt(key);
var trackEl = (
<VisualizationWrapper visualization={track.visualization}
range={this.state.range}
Expand Down Expand Up @@ -132,14 +151,16 @@ class Root extends React.Component<Props, State> {
top: gearY + 'px'
};
// $FlowIgnore: TODO remove flow suppression
var items = track.visualization.component.getOptionsMenu(track.visualization.options);
var items = _.clone(track.visualization.component.getOptionsMenu(track.visualization.options));
settingsMenu = (
<div className='menu-container' style={menuStyle}>
<Menu header={trackName} items={items} onSelect={this.handleSelectOption.bind(this, key)} />
<div className='menu-container' style={menuStyle} ref={node => { this.node = node; }}>
<Menu header={trackName} items={items}
onClick={this.handleSelectOption.bind(this, key)}
/>
</div>
);
}

var className = ['track', track.visualization.component.displayName || '', track.track.cssClass || ''].join(' ');

return (
Expand Down
1 change: 1 addition & 0 deletions src/main/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ function TEXT_STYLE(mode: number, fontSize: number): string {

module.exports = {
TEXT_STYLE,
DEFAULT_COLORPICKER: {hex: '#969696', rgb: {r: 150, g: 150, b: 150, a: 1}}, // grey in hex/rgb

// Colors for individual base pairs
BASE_COLORS: {
Expand Down
24 changes: 22 additions & 2 deletions src/main/viz/CoverageTrack.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ function renderBars(ctx: DataCanvasRenderingContext2D,

if (!lastPos) {
let {barX1} = binPos(pos, bin.count);
ctx.fillStyle = style.COVERAGE_BIN_COLOR;
ctx.fillStyle = `rgba(${options.color.rgb.r}, ${options.color.rgb.g}, ${options.color.rgb.b}, ${options.color.rgb.a})`;
ctx.beginPath();
ctx.moveTo(barX1, vBasePosY);
}
Expand Down Expand Up @@ -188,6 +188,8 @@ class CoverageTrack extends React.Component<VizProps<DataSource<Alignment | Feat
cache: CoverageCache< Alignment | Feature >;
tiles: CoverageTiledCanvas;
static defaultOptions: Object;
static getOptionsMenu: (options: Object) => any;
static handleSelectOption: (item: Object, oldOptions: Object) => Object;

constructor(props: VizProps<DataSource<Alignment | Feature>>) {
super(props);
Expand Down Expand Up @@ -333,7 +335,25 @@ CoverageTrack.defaultOptions = {
// exceeds this amount. When there are >=2 agreeing mismatches, they are
// always rendered. But for mismatches below this threshold, the reference is
// not colored in the bar chart. This draws attention to high-VAF mismatches.
vafColorThreshold: 0.2
vafColorThreshold: 0.2,
color: style.DEFAULT_COLORPICKER
};

CoverageTrack.getOptionsMenu = function(options: Object): any {
return [
{key: 'pick-color', label: 'Change track color', color: options.color}
];
};

CoverageTrack.handleSelectOption = function(item: Object, oldOptions: Object): Object {
var opts = _.clone(oldOptions);
if (item.key == "pick-color") {
// This is all handled by the menu. Do nothing.
opts.color = item.color;
return opts;
}
return oldOptions; // no change
};


module.exports = CoverageTrack;
Loading

0 comments on commit c7bb612

Please sign in to comment.