├── src ├── views │ ├── not-found.js │ ├── explore-actions.js │ ├── explore-defaults.js │ ├── docs.html │ ├── about.js │ ├── explore-reducers.js │ ├── not-found.html │ ├── configurator.js │ ├── docs.js │ ├── configurator.html │ ├── explore.html │ ├── home.js │ └── home.html ├── app-defaults.js ├── environment.js ├── components │ ├── multi-select │ │ ├── multi-select-defaults.js │ │ └── multi-select.html │ ├── range-select │ │ ├── range-select-defaults.js │ │ ├── range-select.html │ │ └── range-select.js │ ├── footer-main │ │ ├── footer-main.js │ │ └── footer-main.html │ ├── fragments │ │ ├── matrix-defaults.js │ │ ├── pile-menu.html │ │ ├── fragments-state.js │ │ ├── pile-defaults.js │ │ ├── matrix.js │ │ ├── pile-menu.js │ │ ├── pile-colors.js │ │ ├── pile-details.html │ │ └── fragments-defaults.js │ ├── svg-icon │ │ ├── svg-icon.html │ │ └── svg-icon.js │ ├── dialog │ │ ├── dialog.html │ │ └── dialog.js │ ├── higlass │ │ ├── higlass-defaults.js │ │ ├── higlass-actions.js │ │ ├── higlass-reducers.js │ │ └── higlass.html │ ├── spinner.html │ ├── chartlet │ │ ├── chartlet.html │ │ └── chartlet.js │ └── navigation.html ├── utils │ ├── flatten.js │ ├── object-values-to-string.js │ ├── validate-config.js │ ├── scroll-to-anchor.js │ ├── mod.js │ ├── caps.js │ ├── check-text-input-focus.js │ ├── get-url-query-params.js │ ├── download-as-json.js │ ├── download-as-csv.js │ ├── redux-logger.js │ ├── arrays-equal.js │ ├── ping.js │ ├── anchor-link.js │ ├── has-parent.js │ ├── read-json-file.js │ ├── worker-clusterfck.js │ ├── halt-resume.js │ ├── throttle.js │ ├── query-obj.js │ ├── debounce.js │ ├── worker-tsne.js │ ├── deep-clone.js │ ├── drag-drop.js │ ├── hilbert-curve.js │ ├── build-config.js │ ├── dom-el.js │ ├── request-animation-frame.js │ └── basic-higlass-config.js ├── services │ ├── font.js │ ├── export.js │ ├── chrom-info.js │ └── states.js ├── assets │ └── styles │ │ ├── mixins │ │ ├── truncate.scss │ │ ├── ratio.scss │ │ ├── rotate.scss │ │ └── slide-out-in.scss │ │ ├── svg-icon.scss │ │ ├── fragments.scss │ │ ├── transitions.scss │ │ ├── columns.scss │ │ ├── colors.scss │ │ ├── flexbox.scss │ │ ├── higlass.scss │ │ ├── grid.scss │ │ ├── index.scss │ │ ├── configurator.scss │ │ ├── dialog.scss │ │ ├── navigation.scss │ │ ├── range-select.scss │ │ ├── docs.scss │ │ ├── pile-context-menu.scss │ │ ├── range.scss │ │ ├── axis.scss │ │ ├── about.scss │ │ ├── multi-select.scss │ │ ├── pile-details.scss │ │ └── home.scss ├── resources │ └── index.js ├── configs │ ├── nav.js │ ├── app.js │ ├── examples.js │ └── colors.js ├── app-reducer.js ├── main.js ├── app-actions.js └── app.html ├── teaser.png ├── assets ├── images │ ├── arrow.png │ ├── home-bg.png │ ├── about-bg.jpg │ ├── favicon-144.png │ ├── favicon-152.png │ ├── favicon-16.png │ └── favicon-32.png └── videos │ ├── about.mp4 │ └── about.webm ├── aurelia_project ├── environments │ ├── dev.js │ ├── prod.js │ └── stage.js ├── generators │ ├── task.json │ ├── attribute.json │ ├── generator.json │ ├── element.json │ ├── value-converter.json │ ├── binding-behavior.json │ ├── value-converter.js │ ├── binding-behavior.js │ ├── attribute.js │ ├── task.js │ ├── element.js │ └── generator.js └── tasks │ ├── build.json │ ├── test.js │ ├── process-css.js │ ├── test.json │ ├── run.json │ ├── process-markup.js │ ├── build.js │ ├── transpile.js │ └── run.js ├── .gitmodules ├── test ├── unit │ ├── setup.js │ └── app.spec.js └── aurelia-karma.js ├── .gitignore ├── config.json ├── .editorconfig ├── .babelrc ├── CITATION.bib ├── scripts └── cp-prod.sh ├── sitemap.xml ├── .travis.yml ├── .eslintrc ├── LICENSE ├── karma.conf.js ├── favicon.ico ├── README.md ├── index.html └── package.json /src/views/not-found.js: -------------------------------------------------------------------------------- 1 | export class NotFound {} 2 | -------------------------------------------------------------------------------- /src/app-defaults.js: -------------------------------------------------------------------------------- 1 | export const ERROR_DURATION = 3000; 2 | -------------------------------------------------------------------------------- /teaser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flekschas/hipiler/HEAD/teaser.png -------------------------------------------------------------------------------- /src/environment.js: -------------------------------------------------------------------------------- 1 | export default { 2 | debug: false, 3 | testing: false 4 | }; 5 | -------------------------------------------------------------------------------- /assets/images/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flekschas/hipiler/HEAD/assets/images/arrow.png -------------------------------------------------------------------------------- /assets/images/home-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flekschas/hipiler/HEAD/assets/images/home-bg.png -------------------------------------------------------------------------------- /assets/videos/about.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flekschas/hipiler/HEAD/assets/videos/about.mp4 -------------------------------------------------------------------------------- /assets/videos/about.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flekschas/hipiler/HEAD/assets/videos/about.webm -------------------------------------------------------------------------------- /aurelia_project/environments/dev.js: -------------------------------------------------------------------------------- 1 | export default { 2 | debug: true, 3 | testing: true 4 | }; 5 | -------------------------------------------------------------------------------- /src/components/multi-select/multi-select-defaults.js: -------------------------------------------------------------------------------- 1 | export const EVENT_BASE_NAME = 'multiSelect'; 2 | -------------------------------------------------------------------------------- /src/components/range-select/range-select-defaults.js: -------------------------------------------------------------------------------- 1 | export const EVENT_BASE_NAME = 'rangeSelect'; 2 | -------------------------------------------------------------------------------- /src/utils/flatten.js: -------------------------------------------------------------------------------- 1 | const flatten = [(a, b) => a.concat(b), []]; 2 | 3 | export default flatten; 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "wiki"] 2 | path = wiki 3 | url = https://github.com/flekschas/hipiler.wiki 4 | -------------------------------------------------------------------------------- /assets/images/about-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flekschas/hipiler/HEAD/assets/images/about-bg.jpg -------------------------------------------------------------------------------- /aurelia_project/environments/prod.js: -------------------------------------------------------------------------------- 1 | export default { 2 | debug: false, 3 | testing: false 4 | }; 5 | -------------------------------------------------------------------------------- /aurelia_project/environments/stage.js: -------------------------------------------------------------------------------- 1 | export default { 2 | debug: true, 3 | testing: false 4 | }; 5 | -------------------------------------------------------------------------------- /assets/images/favicon-144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flekschas/hipiler/HEAD/assets/images/favicon-144.png -------------------------------------------------------------------------------- /assets/images/favicon-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flekschas/hipiler/HEAD/assets/images/favicon-152.png -------------------------------------------------------------------------------- /assets/images/favicon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flekschas/hipiler/HEAD/assets/images/favicon-16.png -------------------------------------------------------------------------------- /assets/images/favicon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flekschas/hipiler/HEAD/assets/images/favicon-32.png -------------------------------------------------------------------------------- /src/services/font.js: -------------------------------------------------------------------------------- 1 | export default class Font { 2 | constructor () { 3 | this.size = 16; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/unit/setup.js: -------------------------------------------------------------------------------- 1 | import 'aurelia-polyfills'; 2 | import { initialize } from 'aurelia-pal-browser'; 3 | 4 | initialize(); 5 | -------------------------------------------------------------------------------- /aurelia_project/generators/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "task", 3 | "description": "Creates a task and places it in the project tasks folder." 4 | } 5 | -------------------------------------------------------------------------------- /src/assets/styles/mixins/truncate.scss: -------------------------------------------------------------------------------- 1 | @mixin truncate() { 2 | white-space: nowrap; 3 | overflow: hidden; 4 | text-overflow: ellipsis; 5 | } 6 | -------------------------------------------------------------------------------- /src/resources/index.js: -------------------------------------------------------------------------------- 1 | export function configure (config) { 2 | //config.globalResources([]); 3 | } 4 | 5 | export default { 6 | configure 7 | }; 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | build 3 | ghp 4 | node_modules 5 | .idea 6 | .DS_STORE 7 | *.log 8 | *.lock 9 | assets/wiki/* 10 | config.local.json 11 | build.zip 12 | -------------------------------------------------------------------------------- /src/components/footer-main/footer-main.js: -------------------------------------------------------------------------------- 1 | export class FooterMain { 2 | constructor (event) { 3 | this.version = window.hipilerConfig.version; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /aurelia_project/generators/attribute.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "attribute", 3 | "description": "Creates a custom attribute class and places it in the project resources." 4 | } 5 | -------------------------------------------------------------------------------- /aurelia_project/generators/generator.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator", 3 | "description": "Creates a generator class and places it in the project generators folder." 4 | } 5 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "debug": false, 3 | "server": "http://higlass.io/api/v1/", 4 | "testing": false, 5 | "workerClusterfckHash": "", 6 | "workerTsneHash": "" 7 | } 8 | -------------------------------------------------------------------------------- /test/unit/app.spec.js: -------------------------------------------------------------------------------- 1 | // import App from '../../src/app'; 2 | 3 | 4 | describe('Dummy', () => { 5 | it('test', () => { 6 | expect(true).toBe(true); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /aurelia_project/generators/element.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "element", 3 | "description": "Creates a custom element class and template, placing them in the project resources." 4 | } 5 | -------------------------------------------------------------------------------- /aurelia_project/generators/value-converter.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "value-converter", 3 | "description": "Creates a value converter class and places it in the project resources." 4 | } 5 | -------------------------------------------------------------------------------- /aurelia_project/generators/binding-behavior.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "binding-behavior", 3 | "description": "Creates a binding behavior class and places it in the project resources." 4 | } 5 | -------------------------------------------------------------------------------- /src/utils/object-values-to-string.js: -------------------------------------------------------------------------------- 1 | const objValsToStr = obj => Object 2 | .keys(obj) 3 | .map(key => obj[key]) 4 | .reduce((str, value) => str.concat(value), ''); 5 | 6 | export default objValsToStr; 7 | -------------------------------------------------------------------------------- /src/utils/validate-config.js: -------------------------------------------------------------------------------- 1 | export default function validateConfig (fgm, hgl) { 2 | try { 3 | return Object.keys(fgm).length || Object.keys(hgl).length; 4 | } catch (error) { 5 | return false; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/views/explore-actions.js: -------------------------------------------------------------------------------- 1 | export const UPDATE_WIDTH = 'UPDATE_WIDTH'; 2 | 3 | export const updateWidth = (column, width) => ({ 4 | type: UPDATE_WIDTH, 5 | payload: { 6 | column, 7 | width 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /src/components/fragments/matrix-defaults.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Annotation color of matrix 3 | * 4 | * @type {String} 5 | */ 6 | export const ANNOTATION_COLOR = '#aaa'; 7 | 8 | export default { 9 | ANNOTATION_COLOR 10 | }; 11 | -------------------------------------------------------------------------------- /src/configs/nav.js: -------------------------------------------------------------------------------- 1 | export const externalLinks = [ 2 | { 3 | href: 'https://github.com/flekschas/hipiler', 4 | title: 'GitHub', 5 | icon: 'github', 6 | iconOnly: true 7 | } 8 | ]; 9 | 10 | export default { 11 | externalLinks 12 | }; 13 | -------------------------------------------------------------------------------- /aurelia_project/tasks/build.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "build", 3 | "description": "Builds and processes all application assets.", 4 | "flags": [ 5 | { 6 | "name": "env", 7 | "description": "Sets the build environment.", 8 | "type": "string" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/assets/styles/svg-icon.scss: -------------------------------------------------------------------------------- 1 | svg-icon { 2 | display: block; 3 | 4 | svg { 5 | width: 100%; 6 | height: 100%; 7 | 8 | &.mirror-h { 9 | transform: scale(1, -1); 10 | } 11 | 12 | &.mirror-v { 13 | transform: scale(-1, 1); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/services/export.js: -------------------------------------------------------------------------------- 1 | const getters = {}; 2 | 3 | export default class Export { 4 | get (data) { 5 | if (typeof getters[data] === 'function') { 6 | return getters[data](); 7 | } 8 | } 9 | 10 | register (data, getter) { 11 | getters[data] = getter; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/scroll-to-anchor.js: -------------------------------------------------------------------------------- 1 | const scrollToAnchor = (id) => { 2 | const el = document.getElementById(id); 3 | if (el) { 4 | // Re-apply anchor URL such that the browser actually scrolls to the anchor 5 | location.href = `#${id}`; 6 | } 7 | }; 8 | 9 | export default scrollToAnchor; 10 | -------------------------------------------------------------------------------- /src/assets/styles/fragments.scss: -------------------------------------------------------------------------------- 1 | @import 'colors'; 2 | @import 'transitions'; 3 | 4 | fragments { 5 | display: block; 6 | margin: 2rem 0.5rem 0; 7 | 8 | .fragment-plot { 9 | &.is-piles-inspection { 10 | box-shadow: inset 0 0 0 0.25rem lighten($primary, 40%); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/svg-icon/svg-icon.html: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # 2 space indentation 12 | [**.*] 13 | indent_style = space 14 | indent_size = 2 -------------------------------------------------------------------------------- /aurelia_project/tasks/test.js: -------------------------------------------------------------------------------- 1 | import { Server as Karma } from 'karma'; 2 | import { CLIOptions } from 'aurelia-cli'; 3 | 4 | export function unit (done) { 5 | new Karma({ 6 | configFile: __dirname + '/../../karma.conf.js', 7 | singleRun: !CLIOptions.hasFlag('watch') 8 | }, done).start(); 9 | } 10 | 11 | export default unit; 12 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "sourceMap": true, 3 | "moduleIds": false, 4 | "comments": false, 5 | "compact": false, 6 | "code": true, 7 | "presets": [ 8 | ["env", {"loose": true}], 9 | "stage-1" 10 | ], 11 | "plugins": [ 12 | "syntax-flow", 13 | "transform-decorators-legacy", 14 | "transform-flow-strip-types" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/assets/styles/mixins/ratio.scss: -------------------------------------------------------------------------------- 1 | @mixin ratio($height) { 2 | position: relative; 3 | 4 | &:before { 5 | display: block; 6 | content: ""; 7 | width: 100%; 8 | padding-top: $height * 100%; 9 | } 10 | 11 | > .content { 12 | position: absolute; 13 | top: 0; 14 | left: 0; 15 | right: 0; 16 | bottom: 0; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/utils/mod.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Java-inspired Modulo method for negative numbers 3 | * 4 | * @description 5 | * JS: -13 % 64 === -13 6 | * Java (this method): -13 % 64 === 51 7 | * 8 | * @param {number} n - Number to be taken modulo. 9 | * @param {number} m - The modulo. 10 | * @return {number} Result. 11 | */ 12 | export default function mod (n, m) { 13 | return ((n % m) + m) % m; 14 | } 15 | -------------------------------------------------------------------------------- /src/assets/styles/transitions.scss: -------------------------------------------------------------------------------- 1 | // Bezier curves 2 | $ease-in-out-cubic: cubic-bezier(0.3, 0.1, 0.6, 1); 3 | $ease-in-out-quadric: cubic-bezier(0.2, 0.1, 0.2, 1); 4 | 5 | // Transition speeds 6 | $transition-very-fast: 0.15s; 7 | $transition-fast: 0.2s; 8 | $transition-semi-fast: 0.25s; 9 | $transition-normal: 0.33s; 10 | $transition-slow: 0.66s; 11 | $transition-slower: 0.75s; 12 | $transition-slowest: 1s; 13 | -------------------------------------------------------------------------------- /src/utils/caps.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Capitalize string 3 | * 4 | * @param {string} string - String to be capitalized. 5 | * @param {boolean} force - If `true` force lower casing. 6 | * @return {string} Capitalized string 7 | */ 8 | export default function caps (string, force) { 9 | if (force) { 10 | string = string.toLowerCase(); 11 | } 12 | 13 | return string.charAt(0).toUpperCase() + string.slice(1); 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/check-text-input-focus.js: -------------------------------------------------------------------------------- 1 | const checkTextInputFocus = () => { 2 | switch (document.activeElement.tagName) { 3 | case 'INPUT': 4 | if (document.activeElement.type === 'text') { 5 | return true; 6 | } 7 | break; 8 | case 'TEXTAREA': 9 | return true; 10 | default: 11 | // Nothing 12 | } 13 | return false; 14 | }; 15 | 16 | export default checkTextInputFocus; 17 | -------------------------------------------------------------------------------- /aurelia_project/tasks/process-css.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import sourcemaps from 'gulp-sourcemaps'; 3 | import sass from 'gulp-sass'; 4 | import project from '../aurelia.json'; 5 | import {build} from 'aurelia-cli'; 6 | 7 | export default function processCSS() { 8 | return gulp.src(project.cssProcessor.source) 9 | .pipe(sourcemaps.init()) 10 | .pipe(sass().on('error', sass.logError)) 11 | .pipe(build.bundle()); 12 | } 13 | -------------------------------------------------------------------------------- /src/components/dialog/dialog.html: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /src/utils/get-url-query-params.js: -------------------------------------------------------------------------------- 1 | const getUrlQueryParams = (url) => { 2 | if (!url) { return {}; } 3 | 4 | return (/^[?#]/.test(url) ? url.slice(1) : url) 5 | .split('&') 6 | .reduce((params, param) => { 7 | const [key, value] = param.split('='); 8 | params[key] = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : ''; 9 | return params; 10 | }, {}); 11 | }; 12 | 13 | export default getUrlQueryParams; 14 | -------------------------------------------------------------------------------- /src/utils/download-as-json.js: -------------------------------------------------------------------------------- 1 | const downloadAsJson = function (obj) { 2 | const json = encodeURIComponent(JSON.stringify(obj, null, 2)); 3 | const data = `text/json;charset=utf-8,${json}`; 4 | const a = document.createElement('a'); 5 | a.href = `data:${data}`; 6 | a.download = 'hipiler-piles.json'; 7 | a.innerHTML = ''; 8 | document.body.appendChild(a); 9 | a.click(); 10 | a.remove(); 11 | }; 12 | 13 | export default downloadAsJson; 14 | -------------------------------------------------------------------------------- /aurelia_project/tasks/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "description": "Runs all unit tests and reports the results.", 4 | "flags": [ 5 | { 6 | "name": "env", 7 | "description": "Sets the build environment.", 8 | "type": "string" 9 | }, 10 | { 11 | "name": "watch", 12 | "description": "Watches test files for changes and re-runs the tests automatically.", 13 | "type": "boolean" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/download-as-csv.js: -------------------------------------------------------------------------------- 1 | import * as Papa from 'papaparse'; 2 | 3 | const downloadAsCsv = function (arr) { 4 | const csv = Papa.unparse(arr); 5 | const data = `text/csv;charset=utf-8,${csv}`; 6 | const a = document.createElement('a'); 7 | a.href = `data:${data}`; 8 | a.download = 'hipiler-piles.csv'; 9 | a.innerHTML = ''; 10 | document.body.appendChild(a); 11 | a.click(); 12 | a.remove(); 13 | }; 14 | 15 | export default downloadAsCsv; 16 | -------------------------------------------------------------------------------- /src/services/chrom-info.js: -------------------------------------------------------------------------------- 1 | let isReady; 2 | 3 | const ready = new Promise((resolve) => { 4 | isReady = resolve; 5 | }); 6 | 7 | let chromInfo; 8 | 9 | export default class ChromInfo { 10 | get ready () { 11 | return ready; 12 | } 13 | 14 | get () { 15 | return chromInfo; 16 | } 17 | 18 | set (newChromInfo) { 19 | if (!chromInfo && newChromInfo) { 20 | chromInfo = newChromInfo; 21 | isReady(chromInfo); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/redux-logger.js: -------------------------------------------------------------------------------- 1 | const logger = store => next => (action) => { 2 | console.group(action.type); // eslint-disable-line no-console 3 | console.info('%c dispatching', 'color: #7fbcff', action); // eslint-disable-line no-console 4 | let result = next(action); 5 | console.log('%c next state', 'color: #8eff80', store.getState()); // eslint-disable-line no-console 6 | console.groupEnd(action.type); // eslint-disable-line no-console 7 | return result; 8 | }; 9 | 10 | export default logger; 11 | -------------------------------------------------------------------------------- /aurelia_project/tasks/run.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "run", 3 | "description": "Builds the application and serves up the assets via a local web server, watching files for changes as you work.", 4 | "flags": [ 5 | { 6 | "name": "env", 7 | "description": "Sets the build environment.", 8 | "type": "string" 9 | }, 10 | { 11 | "name": "watch", 12 | "description": "Watches source files for changes and refreshes the app automatically.", 13 | "type": "boolean" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/assets/styles/columns.scss: -------------------------------------------------------------------------------- 1 | .column-1 { 2 | width: 100%; 3 | } 4 | 5 | .column-1-2 { 6 | width: 50%; 7 | } 8 | 9 | .column-1-3 { 10 | width: 33.33%; 11 | } 12 | 13 | .column-2-3 { 14 | width: 66.66%; 15 | } 16 | 17 | .column-1-4 { 18 | width: 25%; 19 | } 20 | 21 | .column-3-4 { 22 | width: 75%; 23 | } 24 | 25 | .column-1-5 { 26 | width: 20%; 27 | } 28 | 29 | .column-2-5 { 30 | width: 40%; 31 | } 32 | 33 | .column-3-5 { 34 | width: 60%; 35 | } 36 | 37 | .column-4-5 { 38 | width: 80%; 39 | } 40 | -------------------------------------------------------------------------------- /src/assets/styles/colors.scss: -------------------------------------------------------------------------------- 1 | $black: #000; 2 | $gray: #999; 3 | $white: #fff; 4 | 5 | $gray-light: #bfbfbf; 6 | $gray-lighter: #d9d9d9; 7 | $gray-lightest: #e5e5e5; 8 | 9 | $gray-dark: #666; 10 | $gray-darker: #444; 11 | $gray-darkest: #222; 12 | 13 | $green: #40bf00; 14 | $orange: #ff5500; 15 | $red: #f60029; 16 | $yellow: #ffcc00; 17 | $blue: #4e40ff; 18 | 19 | $primary: $orange; 20 | $secondary: $blue; 21 | 22 | $active: $green; 23 | $ready: $yellow; 24 | 25 | $error-text: darken($red, 25%); 26 | $error-bg: lighten($red, 25%); 27 | -------------------------------------------------------------------------------- /src/assets/styles/flexbox.scss: -------------------------------------------------------------------------------- 1 | .flex-c { 2 | display: flex; 3 | } 4 | 5 | .flex-d-c { 6 | flex-direction: column; 7 | } 8 | 9 | .flex-jc-c { 10 | justify-content: center; 11 | } 12 | 13 | .flex-jc-r { 14 | justify-content: flex-end; 15 | } 16 | 17 | .flex-jc-sb { 18 | justify-content: space-between; 19 | } 20 | 21 | .flex-a-c { 22 | align-items: center; 23 | } 24 | 25 | .flex-a-s { 26 | align-items: stretch; 27 | } 28 | 29 | .flex-w { 30 | flex-wrap: wrap; 31 | } 32 | 33 | .flex-g-1 { 34 | flex-grow: 1; 35 | } 36 | -------------------------------------------------------------------------------- /CITATION.bib: -------------------------------------------------------------------------------- 1 | @article{lekschas2018hipiler, 2 | author = {Fritz Lekschas and Benjamin Bach and Peter Kerpedjiev and Nils Gehlenborg and Hanspeter Pfister}, 3 | title = {HiPiler: Visual Exploration Of Large Genome Interaction Matrices With Interactive Small Multiples}, 4 | journal = {IEEE Transactions on Visualization and Computer Graphics}, 5 | series = {InfoVis ’17}, 6 | year = {2018}, 7 | month = {1}, 8 | day = {1}, 9 | volume = {24}, 10 | number = {1}, 11 | pages = {522-531}, 12 | doi = {10.1109/TVCG.2017.2745978}, 13 | } 14 | -------------------------------------------------------------------------------- /src/views/explore-defaults.js: -------------------------------------------------------------------------------- 1 | export const COLUMN_NAMES = [ 2 | 'matrix', 3 | 'details' 4 | ]; 5 | 6 | export const COLUMNS = { 7 | matrixWidth: 40, 8 | matrixWidthUnit: '%', 9 | detailsWidth: 15, 10 | detailsWidthUnit: 'rem' 11 | }; 12 | 13 | export const CSS = { 14 | details: { 15 | flexBasis: `${COLUMNS.detailsWidth}${COLUMNS.detailsWidthUnit}` 16 | }, 17 | matrix: { 18 | flexBasis: `${COLUMNS.matrixWidth}${COLUMNS.matrixWidthUnit}` 19 | } 20 | }; 21 | 22 | export default { 23 | COLUMN_NAMES, 24 | COLUMNS 25 | }; 26 | -------------------------------------------------------------------------------- /scripts/cp-prod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Move CNAME one dir up 4 | mv ../$1/CNAME ../__CNAME__ 5 | mv ../$1/sitemap.xml ../__sitemap.xml__ 6 | 7 | rm -r ../$1/* 8 | cp -r ghp/* ../$1/ 9 | 10 | # Move CNAME back 11 | mv ../__CNAME__ ../$1/CNAME 12 | mv ../__sitemap.xml__ ../$1/sitemap.xml 13 | 14 | cd ../$1/ 15 | 16 | # Add symlinks to index.html for HTML5 History 17 | ln -s index.html about.html 18 | ln -s index.html configurator.html 19 | ln -s index.html docs.html 20 | ln -s index.html explore.html 21 | ln -s index.html 404.html 22 | 23 | touch .nojekyll 24 | -------------------------------------------------------------------------------- /src/utils/arrays-equal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Check if two arrays are shallowly equal in terms of their values. 3 | * 4 | * @description 5 | * This methid assumes that arrays of primitives are compared. 6 | * 7 | * @param {array} a - First array. 8 | * @param {array} b - Second array, 9 | * @return {boolean} If `true` arrays are equal. 10 | */ 11 | export default function arraysEqual (a, b) { 12 | if (!a || !b) { return false; } 13 | 14 | if (a.length !== b.length) { return false; } 15 | 16 | return a.every((element, index) => element === b[index]); 17 | } 18 | -------------------------------------------------------------------------------- /src/views/docs.html: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /src/assets/styles/mixins/rotate.scss: -------------------------------------------------------------------------------- 1 | @mixin rotate($degree, $speed, $easing) { 2 | -webkit-animation: spin $speed $easing 1; 3 | -moz-animation: spin $speed $easing 1; 4 | animation: spin $speed $easing 1; 5 | -webkit-transform-origin: 50% 50%; 6 | -moz-transform-origin: 50% 50%; 7 | transform-origin: 50% 50%; 8 | 9 | @-moz-keyframes spin { 100% { -moz-transform: rotate($degree); } } 10 | @-webkit-keyframes spin { 100% { -webkit-transform: rotate($degree); } } 11 | @keyframes spin { 100% { -webkit-transform: rotate($degree); transform:rotate($degree); } } 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/ping.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple ping method. 3 | * 4 | * @param {string} host - Address to be pinged. 5 | * @return {object} Promise resolving to `true` if address is available. 6 | */ 7 | export default function ping (host) { 8 | return new Promise((resolve, reject) => { 9 | const img = new Image(); 10 | 11 | img.onload = () => { resolve(); }; 12 | img.onerror = () => { resolve(); }; 13 | img.src = `${host}?cachebreaker=${Date.now()}`; 14 | 15 | setTimeout(() => { 16 | reject(Error('Server not available')); 17 | }, 1500); 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /src/utils/anchor-link.js: -------------------------------------------------------------------------------- 1 | import { Container } from 'aurelia-dependency-injection'; 2 | import { Router } from 'aurelia-router'; 3 | 4 | const container = Container.instance; 5 | const router = container.get(Router); 6 | 7 | const anchorLink = (path, anchor) => (router.history._hasPushState 8 | ? `${router.generate(path)}#${anchor}` 9 | : `${router.generate(path)}/${anchor}` 10 | ); 11 | 12 | export default anchorLink; 13 | 14 | export const getAnchor = (path, anchor) => (router.history._hasPushState 15 | ? anchor 16 | : `${router.generate(path).slice(1)}/${anchor}` 17 | ); 18 | -------------------------------------------------------------------------------- /aurelia_project/tasks/process-markup.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import htmlmin from 'gulp-htmlmin'; 3 | import changedInPlace from 'gulp-changed-in-place'; 4 | import project from '../aurelia.json'; 5 | import {build} from 'aurelia-cli'; 6 | 7 | export default function processMarkup() { 8 | return gulp.src(project.markupProcessor.source) 9 | .pipe(changedInPlace({firstPass:true})) 10 | .pipe(htmlmin({ 11 | removeComments: true, 12 | collapseWhitespace: true, 13 | minifyCSS: true, 14 | minifyJS: true 15 | })) 16 | .pipe(build.bundle()); 17 | } -------------------------------------------------------------------------------- /src/components/higlass/higlass-defaults.js: -------------------------------------------------------------------------------- 1 | export const CONFIG = {}; 2 | 3 | export const FRAGMENTS_HIGHLIGHT = true; 4 | 5 | export const FRAGMENTS_SELECTION = false; 6 | 7 | export const FRAGMENTS_SELECTION_FADE_OUT = false; 8 | 9 | export const FGM_LOCATION_HIGHLIGHT_SIZE = 6; 10 | 11 | export const GRAYSCALE = true; 12 | 13 | export const GRAYSCALE_COLORS = [ 14 | '#fff', 15 | '#bbb', 16 | '#777', 17 | '#222' 18 | ]; 19 | 20 | export const INTERACTIONS = false; 21 | 22 | export const SELECTION_VIEW = []; 23 | 24 | export const SELECTION_DOMAIN_DISPATCH_DEBOUNCE = 100; 25 | -------------------------------------------------------------------------------- /src/utils/has-parent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test whether a DOM element is the parent of another DOM element. 3 | * 4 | * @param {object} el - Potential child element. 5 | * @param {object} target - Target parent element which is tested to have `el` 6 | * as a child. 7 | * @return {Boolean} If `true` `el` has `target` as a parent. 8 | */ 9 | export default function hasParent (el, target) { 10 | let _el = el; 11 | 12 | while (_el !== target && _el.tagName !== 'HTML') { 13 | _el = _el.parentNode; 14 | } 15 | 16 | if (_el === target) { 17 | return true; 18 | } 19 | 20 | return false; 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/read-json-file.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Read a loaded JSON file. 3 | * 4 | * @param {object} file - File object to be read. 5 | * @return {object} JS object. 6 | */ 7 | export default function (file) { 8 | return new Promise((resolve, reject) => { 9 | const reader = new FileReader(); 10 | 11 | reader.addEventListener('load', (event) => { 12 | let json; 13 | 14 | try { 15 | json = JSON.parse(event.target.result); 16 | } catch (e) { 17 | reject(e); 18 | } 19 | 20 | resolve(json); 21 | }); 22 | 23 | reader.readAsText(file); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /aurelia_project/tasks/build.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import transpile from './transpile'; 3 | import processMarkup from './process-markup'; 4 | import processCSS from './process-css'; 5 | import {build} from 'aurelia-cli'; 6 | import project from '../aurelia.json'; 7 | 8 | export default gulp.series( 9 | readProjectConfiguration, 10 | gulp.parallel( 11 | transpile, 12 | processMarkup, 13 | processCSS 14 | ), 15 | writeBundles 16 | ); 17 | 18 | function readProjectConfiguration() { 19 | return build.src(project); 20 | } 21 | 22 | function writeBundles() { 23 | return build.dest(); 24 | } 25 | -------------------------------------------------------------------------------- /src/views/about.js: -------------------------------------------------------------------------------- 1 | // Utils etc. 2 | import anchorLink, { getAnchor } from 'utils/anchor-link'; 3 | import scrollToAnchor from 'utils/scroll-to-anchor'; 4 | 5 | 6 | export class About { 7 | constructor () { 8 | this.anchorLink = anchorLink; 9 | this.getAnchor = getAnchor; 10 | } 11 | 12 | /* ----------------------- Aurelia-specific methods ----------------------- */ 13 | 14 | activate (urlParams) { 15 | if (urlParams.anchor) { 16 | this.anchor = `/about/${urlParams.anchor}`; 17 | } 18 | } 19 | 20 | attached () { 21 | if (this.anchor) { 22 | scrollToAnchor(this.anchor); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/assets/styles/higlass.scss: -------------------------------------------------------------------------------- 1 | higlass { 2 | display: block; 3 | margin: 2rem 0.5rem 0 0; 4 | 5 | .genome-position-search { 6 | .input-group { 7 | display: flex; 8 | 9 | div:first-child { 10 | flex-grow: 1; 11 | } 12 | } 13 | 14 | .search-bar:focus { 15 | outline: 0; 16 | } 17 | 18 | .btn { 19 | width: 30px; 20 | height: 30px; 21 | 22 | &::before { 23 | content: 'Go' 24 | } 25 | 26 | &.btn-sm { 27 | padding: 5px; 28 | } 29 | } 30 | } 31 | } 32 | 33 | .matrix-full-screen-view higlass { 34 | margin-top: 0; 35 | padding-top: 0; 36 | } 37 | -------------------------------------------------------------------------------- /src/utils/worker-clusterfck.js: -------------------------------------------------------------------------------- 1 | /* eslint no-var:0, prefer-arrow-callback:0, object-shorthand:0, no-undef:0 */ 2 | 3 | self.onmessage = function (event) { 4 | var error; 5 | var clusters = []; 6 | var kmeans = new clusterfck.Kmeans(event.data.centroids || []); 7 | 8 | try { 9 | clusters = kmeans.cluster(event.data.data, event.data.numClusters || 3); 10 | } catch (e) { 11 | error = e.message; 12 | } 13 | 14 | clusters = clusters.map(function (cluster) { 15 | return cluster.map(function (entry) { 16 | return entry.id; 17 | }); 18 | }); 19 | 20 | self.postMessage({ 21 | clusters: clusters, 22 | error: error 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | http://hipiler.higlass.io/ 5 | 2018-06-11 6 | monthly 7 | 0.5 8 | 9 | 10 | http://hipiler.higlass.io/about 11 | 2018-06-11 12 | monthly 13 | 0.5 14 | 15 | 16 | http://hipiler.higlass.io/research 17 | 2018-06-11 18 | monthly 19 | 0.5 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/views/explore-reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import { UPDATE_WIDTH } from 'views/explore-actions'; 4 | import { COLUMNS } from 'views/explore-defaults'; 5 | 6 | import fragments from 'components/fragments/fragments-reducers'; 7 | import higlass from 'components/higlass/higlass-reducers'; 8 | 9 | export function columns (state = { ...COLUMNS }, action) { 10 | switch (action.type) { 11 | case UPDATE_WIDTH: 12 | return { 13 | ...state, 14 | [`${action.payload.column}Width`]: action.payload.width 15 | }; 16 | default: 17 | return state; 18 | } 19 | } 20 | 21 | export default combineReducers({ 22 | columns, 23 | fragments, 24 | higlass 25 | }); 26 | -------------------------------------------------------------------------------- /src/views/not-found.html: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /src/utils/halt-resume.js: -------------------------------------------------------------------------------- 1 | // Aurelia 2 | import { LogManager } from 'aurelia-framework'; 3 | 4 | const logger = LogManager.getLogger('higlass'); 5 | 6 | /** 7 | * Simple stack holder that fires all halted functions once it's resumed. 8 | */ 9 | export default function HaltResume () { 10 | const stack = []; 11 | 12 | const halt = function (f, params) { 13 | stack.push([f, params]); 14 | }; 15 | 16 | const resume = function () { 17 | let entry = stack.pop(); 18 | while (entry) { 19 | try { 20 | entry[0](...entry[1]); 21 | } catch (e) { 22 | logger.error(`Could not resume function: ${e}`); 23 | } 24 | entry = stack.pop(); 25 | } 26 | }; 27 | 28 | return { halt, resume }; 29 | } 30 | -------------------------------------------------------------------------------- /src/app-reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import { RESET_STATE } from 'app-actions'; 4 | import explore from 'views/explore-reducers'; 5 | 6 | /** 7 | * The global / app reducer 8 | * 9 | * Add all all the high level reducers here. The state hierarchy is as follows 10 | * ``` 11 | * { 12 | * : { 13 | * , 14 | * ... 15 | * }, 16 | * global: { 17 | * 18 | * } 19 | * } 20 | * ```` 21 | */ 22 | const appReducer = combineReducers({ 23 | // Views 24 | explore 25 | // Components 26 | }); 27 | 28 | const rootReducer = (state, action) => { 29 | if (action.type === RESET_STATE) { 30 | state = undefined; 31 | } 32 | 33 | return appReducer(state, action); 34 | }; 35 | 36 | export default rootReducer; 37 | -------------------------------------------------------------------------------- /src/components/svg-icon/svg-icon.js: -------------------------------------------------------------------------------- 1 | // Aurelia 2 | import { 3 | bindable, 4 | bindingMode 5 | } from 'aurelia-framework'; 6 | 7 | import icons from 'configs/icons'; 8 | 9 | export class SvgIcon { 10 | @bindable({ defaultBindingMode: bindingMode.oneWay }) iconId; // eslint-disable-line 11 | @bindable({ defaultBindingMode: bindingMode.oneWay }) iconMirrorH; // eslint-disable-line 12 | @bindable({ defaultBindingMode: bindingMode.oneWay }) iconMirrorV; // eslint-disable-line 13 | 14 | constructor () { 15 | this.icon = { 16 | viewBox: '0 0 16 16', 17 | fillRule: '', 18 | svg: '' 19 | }; 20 | } 21 | 22 | attached () { 23 | const id = this.iconId.toUpperCase().replace(/-/g, '_'); 24 | this.icon = icons[id] ? icons[id] : icons.WARNING; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/assets/styles/grid.scss: -------------------------------------------------------------------------------- 1 | @import 'colors'; 2 | @import 'transitions'; 3 | 4 | .grid { 5 | position: absolute; 6 | z-index: 1; 7 | overflow: hidden; 8 | display: none; 9 | opacity: 0; 10 | transition: opacity 0 $ease-in-out-cubic; 11 | 12 | &.full-dim { 13 | display: block; 14 | bottom: 100%; 15 | } 16 | 17 | &.is-active { 18 | display: block; 19 | opacity: 1; 20 | transition: opacity $transition-fast $ease-in-out-cubic; 21 | 22 | &.full-dim { 23 | bottom: 0; 24 | } 25 | } 26 | 27 | .column { 28 | position: relative; 29 | border-left: 1px solid $primary; 30 | 31 | &:first-child { 32 | border-left: 0; 33 | } 34 | } 35 | 36 | .row { 37 | position: relative; 38 | border-top: 1px solid $primary; 39 | 40 | &:first-child { 41 | border-top: 0; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { LogManager } from 'aurelia-framework'; 2 | import { ConsoleAppender } from 'aurelia-logging-console'; 3 | 4 | //Configure Bluebird Promises. 5 | if (Promise.config) { 6 | Promise.config({ 7 | warnings: { 8 | wForgottenReturn: false 9 | } 10 | }); 11 | } 12 | 13 | export function configure (aurelia) { 14 | aurelia.use 15 | .standardConfiguration() 16 | .plugin('aurelia-validation') 17 | .feature('resources'); 18 | 19 | if (!window.hipilerConfig) { 20 | window.hipilerConfig = {}; 21 | } 22 | 23 | if (window.hipilerConfig.debug) { 24 | LogManager.addAppender(new ConsoleAppender()); 25 | LogManager.setLevel(LogManager.logLevel.debug); 26 | } 27 | 28 | if (window.hipilerConfig.testing) { 29 | aurelia.use.plugin('aurelia-testing'); 30 | } 31 | 32 | aurelia.start().then(() => aurelia.setRoot()); 33 | } 34 | 35 | export default { 36 | configure 37 | }; 38 | -------------------------------------------------------------------------------- /src/assets/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import '../../../node_modules/higlass/dist/hglib.css'; 2 | 3 | #hipiler { 4 | height: 100vh; 5 | margin: 0; 6 | padding: 0; 7 | background: #fff; 8 | 9 | // Mixins 10 | @import 'mixins/truncate'; 11 | 12 | // Components 13 | @import 'colors'; 14 | @import 'transitions'; 15 | @import 'flexbox'; 16 | @import 'range'; 17 | @import 'multi-select'; 18 | @import 'range-select'; 19 | @import 'svg-icon'; 20 | @import 'axis'; 21 | @import 'grid'; 22 | @import 'pile-context-menu'; 23 | @import 'dialog'; 24 | @import 'columns'; 25 | 26 | // Base style 27 | @import 'base'; 28 | 29 | // Global app styles 30 | @import 'app'; 31 | 32 | // Specific component styles 33 | @import 'navigation'; 34 | @import 'home'; 35 | @import 'about'; 36 | @import 'docs'; 37 | @import 'explore'; 38 | @import 'configurator'; 39 | @import 'higlass'; 40 | @import 'fragments'; 41 | @import 'pile-details'; 42 | } 43 | -------------------------------------------------------------------------------- /src/utils/throttle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Throttle a function call. 3 | * 4 | * @description 5 | * Function calls are delayed by `wait` milliseconds but at least every `wait` 6 | * milliseconds a call is triggered. 7 | * 8 | * @param {functiom} func - Function to be debounced 9 | * @param {number} wait - Number of milliseconds to debounce the function call. 10 | * @param {boolean} immediate - If `true` function is not debounced. 11 | * @return {functiomn} Throttled function. 12 | */ 13 | export default function throttle (func, wait = 250, immediate = false) { 14 | let last; 15 | let deferTimer; 16 | 17 | const throttled = (...args) => { 18 | const now = Date.now(); 19 | 20 | if (last && now < last + wait) { 21 | clearTimeout(deferTimer); 22 | 23 | deferTimer = setTimeout(() => { 24 | last = now; 25 | func(...args); 26 | }, wait); 27 | } else { 28 | last = now; 29 | func(...args); 30 | } 31 | }; 32 | 33 | return throttled; 34 | } 35 | -------------------------------------------------------------------------------- /src/components/spinner.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/components/chartlet/chartlet.html: -------------------------------------------------------------------------------- 1 | 29 | -------------------------------------------------------------------------------- /src/components/footer-main/footer-main.html: -------------------------------------------------------------------------------- 1 | 29 | -------------------------------------------------------------------------------- /src/utils/query-obj.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple object query function. 3 | * 4 | * @description 5 | * Assuming that you have a deeply nested object o: 6 | * ``` 7 | * const o = { 8 | * a: { 9 | * b: { 10 | * c: 'HiPiler rocks!' 11 | * } 12 | * } 13 | * } 14 | * ``` 15 | * This object can be safely queried as follows: 16 | * ``` 17 | * queryObj(o, ['a', 'b', 'c']) // 'HiPiler rocks!' 18 | * queryObj(o, ['a', 'b', 'd'], 'Oh no!') // 'Oh no!' 19 | * ``` 20 | * 21 | * @param {object} obj - Object to be queried. 22 | * @param {array} queries - Array of queries. 23 | * @param {*} defVal - Default value to be returned when query fails. 24 | * @return {*} Value of the queried property or `undefined`. 25 | */ 26 | export default function queryObj (obj, queries, defVal) { 27 | try { 28 | const query = queries[0]; 29 | const nextQueries = queries.slice(1); 30 | 31 | if (nextQueries.length) { 32 | return queryObj(obj[query], nextQueries); 33 | } 34 | 35 | return obj[query]; 36 | } catch (e) { 37 | return defVal; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/utils/debounce.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Debounce a function call. 3 | * 4 | * @description 5 | * Function calls are delayed by `wait` milliseconds and only one out of 6 | * multiple function calls is executed. 7 | * 8 | * @param {functiom} func - Function to be debounced 9 | * @param {number} wait - Number of milliseconds to debounce the function call. 10 | * @param {boolean} immediate - If `true` function is not debounced. 11 | * @return {functiomn} Debounced function. 12 | */ 13 | export default function debounce (func, wait, immediate) { 14 | let timeout; 15 | 16 | const debounced = (...args) => { 17 | const later = () => { 18 | timeout = null; 19 | if (!immediate) { 20 | func(...args); 21 | } 22 | }; 23 | 24 | const callNow = immediate && !timeout; 25 | clearTimeout(timeout); 26 | timeout = setTimeout(later, wait); 27 | 28 | if (callNow) { 29 | func(...args); 30 | } 31 | }; 32 | 33 | debounced.cancel = () => { 34 | clearTimeout(timeout); 35 | timeout = null; 36 | }; 37 | 38 | return debounced; 39 | } 40 | -------------------------------------------------------------------------------- /src/assets/styles/configurator.scss: -------------------------------------------------------------------------------- 1 | @import 'colors'; 2 | @import 'transitions'; 3 | 4 | .configurator { 5 | h4 { 6 | margin: 1.5rem 0 0.5rem 0; 7 | color: $black; 8 | font-weight: bold; 9 | } 10 | 11 | label { 12 | span { 13 | font-size: 0.8em; 14 | } 15 | 16 | input { 17 | margin-bottom: 0.25rem; 18 | padding: 0.25rem; 19 | font-size: 0.9em; 20 | 21 | &[type=text], 22 | &[type=number] { 23 | border: 1px solid $gray-lightest; 24 | border-radius: 0.25rem; 25 | } 26 | 27 | &[type=checkbox] { 28 | height: 1.5rem; 29 | } 30 | } 31 | } 32 | 33 | .info-panel { 34 | margin-bottom: 0.5rem; 35 | } 36 | 37 | .configurator-main > .column-1-2 { 38 | &:first-child { 39 | padding-right: 0.5rem; 40 | border-right: 1px solid $gray-lightest; 41 | } 42 | &:last-child { 43 | padding-left: 0.5rem; 44 | } 45 | } 46 | 47 | .snippets-settings .column-1-3 { 48 | padding-left: 0.5rem; 49 | 50 | &.no-p { 51 | padding-left: 0; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/utils/worker-tsne.js: -------------------------------------------------------------------------------- 1 | /* eslint no-var:0, prefer-arrow-callback:0, no-undef:0 */ 2 | 3 | self.onmessage = function (event) { 4 | var msg = event.data; 5 | var currcost = 100; 6 | var results; 7 | 8 | var model = new TSNE({ 9 | dim: msg.dim || 2, 10 | perplexity: msg.perplexity || 25.0, 11 | earlyExaggeration: msg.earlyExaggeration || 4.0, 12 | learningRate: msg.learningRate || 25.0, 13 | nIter: msg.nIter || 1000, 14 | metric: msg.metric || 'euclidean' 15 | }); 16 | 17 | model.init({ 18 | data: msg.data, 19 | type: 'dense' 20 | }); 21 | 22 | model.on('progressData', function (pos) { 23 | self.postMessage({ pos: model.getOutputScaled() }); 24 | }); 25 | 26 | model.on('progressIter', function (iter) { 27 | currcost = (currcost * 0.9) + iter[1]; 28 | self.postMessage({ 29 | iterations: iter[0], 30 | cost: iter[1], 31 | stop: currcost < 20 32 | }); 33 | }); 34 | 35 | results = model.run(); 36 | 37 | self.postMessage({ 38 | err: results[0], 39 | iterations: results[1], 40 | stop: true 41 | }); 42 | }; 43 | -------------------------------------------------------------------------------- /aurelia_project/tasks/transpile.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import changedInPlace from 'gulp-changed-in-place'; 3 | import plumber from 'gulp-plumber'; 4 | import babel from 'gulp-babel'; 5 | import sourcemaps from 'gulp-sourcemaps'; 6 | import notify from 'gulp-notify'; 7 | import rename from 'gulp-rename'; 8 | import project from '../aurelia.json'; 9 | import {CLIOptions, build} from 'aurelia-cli'; 10 | 11 | function configureEnvironment() { 12 | let env = CLIOptions.getEnvironment(); 13 | 14 | return gulp.src(`aurelia_project/environments/${env}.js`) 15 | .pipe(changedInPlace({firstPass: true})) 16 | .pipe(rename('environment.js')) 17 | .pipe(gulp.dest(project.paths.root)); 18 | } 19 | 20 | function buildJavaScript() { 21 | return gulp.src(project.transpiler.source) 22 | .pipe(plumber({errorHandler: notify.onError('Error: <%= error.message %>')})) 23 | .pipe(changedInPlace({firstPass: true})) 24 | .pipe(sourcemaps.init()) 25 | .pipe(babel(project.transpiler.options)) 26 | .pipe(build.bundle()); 27 | } 28 | 29 | export default gulp.series( 30 | configureEnvironment, 31 | buildJavaScript 32 | ); 33 | -------------------------------------------------------------------------------- /src/components/fragments/pile-menu.html: -------------------------------------------------------------------------------- 1 | 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '8' 4 | branches: 5 | only: 6 | - master 7 | - develop 8 | - "/^v.*$/" 9 | before_install: 10 | - export CHROME_BIN=chromium-browser 11 | services: 12 | - xvfb 13 | before_script: 14 | - npm install 15 | before_deploy: 16 | - npm run prerelease 17 | deploy: 18 | provider: releases 19 | api_key: 20 | secure: wccs2hgDobPEwKUqzSIWGbDUGUFrGaLMu3NFTttgVgDsXQnqEXBg7XqTHML6yW4vdk1ug/frKvKrasyvTxNZ16kFQWNutUL8wiyoUc9dkYyCOt1oUdH9rv8L4ZhJPC/XQ+Y5A9Yi/V+Imcw+P0woukMQYsbADWFvlJh4FF2ObDklxghUp/KbeaKANrqlRg99JvAcJE0JdADuAbtyJUBzMJPqtaFdGW6q8sJzwdSY1tP18Hk8YWZl+kDErRQW/KA/YMDAkf0S009eeLcy3CZkTT1OcO3TuddboevfgxQnXufbEmDg+O4/gzZN9fSSllBnDM+UUyb4ebzlKmPQmXHcH7ab00IKyNfGUNQJ2g/tfHBPLnuL3k2+NKtLzXASu2Lbkp11Ycsgcb3XGE93Nzf/qaPj7KOz88WpWohTs2y3xbSK6i9xn4bdotL4Af9OgVg6NCNoH5z9NLgJO+bUEdyxXXpCghR5iSGu3bir898PU7pLCU8218VNMGRCREptUneYv58X1CQmqdtzyszfx0UkM8sw3TZS7zJUciEulaJElV7Ab60Cd3JqMgdyL2QpQa9sgqpFgZuVko1RANqFgdCLuk5tubFuBS4ZM68sNx+VB5iqmC7f25ltaviVjI7i+C12ZTpbI3U6l2tpWnW15t3DjZPRHKWzVtwerMAnVmfWEBs= 21 | file: build.zip 22 | skip_cleanup: true 23 | on: 24 | repo: flekschas/hipiler 25 | tags: true 26 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "extends": [ 7 | "airbnb/base", 8 | "./node_modules/aurelia-tools/.eslintrc.json" 9 | ], 10 | "globals": { 11 | "describe": false, 12 | "expect": false, 13 | "it": false 14 | }, 15 | "parser": "babel-eslint", 16 | "rules": { 17 | "new-cap": ["error", { "capIsNew": false }], 18 | "class-methods-use-this": 0, 19 | "function-paren-newline": 0, 20 | "import/no-extraneous-dependencies": 0, 21 | "import/prefer-default-export": 0, 22 | "import/extensions": 0, 23 | "import/no-unresolved": 0, 24 | "indent": ["error", 2, { "SwitchCase": 1 }], 25 | "no-console": ["error", { "allow": ["warn", "error"] }], 26 | "no-mixed-operators": ["error", { "allowSamePrecedence": true }], 27 | "no-multi-spaces": ["error", { "ignoreEOLComments": true }], 28 | "no-plusplus": ["error", { "allowForLoopAfterthoughts": true }], 29 | "no-restricted-globals": 0, 30 | "no-undef": ["error"], 31 | "object-curly-newline": 0, 32 | "prefer-destructuring": 0, 33 | "space-before-function-paren": ["error", "always"] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/range-select/range-select.html: -------------------------------------------------------------------------------- 1 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 The President and Fellows of Harvard College 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /aurelia_project/generators/value-converter.js: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-dependency-injection'; 2 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli'; 3 | 4 | @inject(Project, CLIOptions, UI) 5 | export default class ValueConverterGenerator { 6 | constructor(project, options, ui) { 7 | this.project = project; 8 | this.options = options; 9 | this.ui = ui; 10 | } 11 | 12 | execute() { 13 | return this.ui 14 | .ensureAnswer(this.options.args[0], 'What would you like to call the value converter?') 15 | .then(name => { 16 | let fileName = this.project.makeFileName(name); 17 | let className = this.project.makeClassName(name); 18 | 19 | this.project.valueConverters.add( 20 | ProjectItem.text(`${fileName}.js`, this.generateSource(className)) 21 | ); 22 | 23 | return this.project.commitChanges() 24 | .then(() => this.ui.log(`Created ${fileName}.`)); 25 | }); 26 | } 27 | 28 | generateSource(className) { 29 | return `export class ${className}ValueConverter { 30 | toView(value) { 31 | 32 | } 33 | 34 | fromView(value) { 35 | 36 | } 37 | } 38 | 39 | `; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/utils/deep-clone.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Deep clone an object 3 | * 4 | * @param {object} target - Target object or undefined to create a new object. 5 | * @param {object} source - Object to be cloned. 6 | * @return {object} Cloned `source` object 7 | */ 8 | function extend (target, source) { 9 | if (source === null || typeof source !== 'object') { 10 | return source; 11 | } 12 | 13 | if (source.constructor !== Object && source.constructor !== Array) { 14 | return source; 15 | } 16 | 17 | if ( 18 | source.constructor === Date || 19 | source.constructor === RegExp || 20 | source.constructor === Function || 21 | source.constructor === String || 22 | source.constructor === Number || 23 | source.constructor === Boolean 24 | ) { 25 | return new source.constructor(source); 26 | } 27 | 28 | target = target || new source.constructor(); 29 | 30 | Object.keys(source).forEach((attr) => { 31 | target[attr] = typeof target[attr] === 'undefined' ? 32 | extend(undefined, source[attr]) : target[attr]; 33 | }); 34 | 35 | return target; 36 | } 37 | 38 | export default function (source) { 39 | let target; 40 | return extend(target, source); 41 | } 42 | -------------------------------------------------------------------------------- /aurelia_project/generators/binding-behavior.js: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-dependency-injection'; 2 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli'; 3 | 4 | @inject(Project, CLIOptions, UI) 5 | export default class BindingBehaviorGenerator { 6 | constructor(project, options, ui) { 7 | this.project = project; 8 | this.options = options; 9 | this.ui = ui; 10 | } 11 | 12 | execute() { 13 | return this.ui 14 | .ensureAnswer(this.options.args[0], 'What would you like to call the binding behavior?') 15 | .then(name => { 16 | let fileName = this.project.makeFileName(name); 17 | let className = this.project.makeClassName(name); 18 | 19 | this.project.bindingBehaviors.add( 20 | ProjectItem.text(`${fileName}.js`, this.generateSource(className)) 21 | ); 22 | 23 | return this.project.commitChanges() 24 | .then(() => this.ui.log(`Created ${fileName}.`)); 25 | }); 26 | } 27 | 28 | generateSource(className) { 29 | return `export class ${className}BindingBehavior { 30 | bind(binding, source) { 31 | 32 | } 33 | 34 | unbind(binding, source) { 35 | 36 | } 37 | } 38 | 39 | ` 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /aurelia_project/generators/attribute.js: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-dependency-injection'; 2 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli'; 3 | 4 | @inject(Project, CLIOptions, UI) 5 | export default class AttributeGenerator { 6 | constructor(project, options, ui) { 7 | this.project = project; 8 | this.options = options; 9 | this.ui = ui; 10 | } 11 | 12 | execute() { 13 | return this.ui 14 | .ensureAnswer(this.options.args[0], 'What would you like to call the custom attribute?') 15 | .then(name => { 16 | let fileName = this.project.makeFileName(name); 17 | let className = this.project.makeClassName(name); 18 | 19 | this.project.attributes.add( 20 | ProjectItem.text(`${fileName}.js`, this.generateSource(className)) 21 | ); 22 | 23 | return this.project.commitChanges() 24 | .then(() => this.ui.log(`Created ${fileName}.`)); 25 | }); 26 | } 27 | 28 | generateSource(className) { 29 | return `import {inject} from 'aurelia-framework'; 30 | 31 | @inject(Element) 32 | export class ${className}CustomAttribute { 33 | constructor(element) { 34 | this.element = element; 35 | } 36 | 37 | valueChanged(newValue, oldValue) { 38 | 39 | } 40 | } 41 | 42 | `; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/components/navigation.html: -------------------------------------------------------------------------------- 1 | 31 | -------------------------------------------------------------------------------- /src/app-actions.js: -------------------------------------------------------------------------------- 1 | import { 2 | updateConfig as updateHglConfig 3 | } from 'components/higlass/higlass-actions'; 4 | 5 | import { 6 | setDataDims, 7 | setDataPadding, 8 | setDataPercentile, 9 | setDataIgnoreDiags, 10 | selectPile, 11 | updateConfig as updateFgmConfig 12 | } from 'components/fragments/fragments-actions'; 13 | 14 | import FgmState from 'components/fragments/fragments-state'; 15 | 16 | export const UPDATE_CONFIG = 'UPDATE_CONFIG'; 17 | 18 | export const updateConfigs = config => (dispatch) => { 19 | // Reset entire fgm state 20 | FgmState.reset(); 21 | 22 | dispatch(selectPile(null)); 23 | dispatch(updateHglConfig(config.hgl)); 24 | dispatch(updateFgmConfig(config.fgm)); 25 | 26 | if (config.fgm.fragmentsDims) { 27 | dispatch(setDataDims(config.fgm.fragmentsDims)); 28 | } 29 | if (config.fgm.fragmentsPadding) { 30 | dispatch(setDataPadding(config.fgm.fragmentsPadding)); 31 | } 32 | if (config.fgm.fragmentsPercentile) { 33 | dispatch(setDataPercentile(config.fgm.fragmentsPercentile)); 34 | } 35 | if (config.fgm.fragmentsIgnoreDiags) { 36 | dispatch(setDataIgnoreDiags(config.fgm.fragmentsIgnoreDiags)); 37 | } 38 | }; 39 | 40 | 41 | export const RESET_STATE = 'RESET_STATE'; 42 | 43 | export const resetState = () => ({ 44 | type: RESET_STATE, 45 | payload: undefined 46 | }); 47 | -------------------------------------------------------------------------------- /src/configs/app.js: -------------------------------------------------------------------------------- 1 | export const name = 'HiPiler'; 2 | 3 | export const routes = [ 4 | { 5 | route: '', 6 | href: '/', 7 | name: 'home', 8 | title: 'Home', 9 | moduleId: 'views/home', 10 | nav: false 11 | }, 12 | // { 13 | // route: 'configurator/:anchor?', 14 | // href: '/configurator', 15 | // name: 'configurator', 16 | // title: 'Configurator', 17 | // moduleId: 'views/configurator', 18 | // nav: true, 19 | // icon: 'cog' 20 | // }, 21 | { 22 | route: 'about/:anchor?', 23 | href: '/about', 24 | name: 'about', 25 | title: 'About', 26 | moduleId: 'views/about', 27 | nav: true, 28 | icon: 'info' 29 | }, 30 | { 31 | route: 'docs/:anchor?/:anchor2?', 32 | href: '/docs', 33 | name: 'docs', 34 | title: 'Documentation', 35 | moduleId: 'views/docs', 36 | nav: true, 37 | navTitle: 'Docs', 38 | icon: 'help' 39 | }, 40 | { 41 | route: 'explore', 42 | href: '/explore', 43 | name: 'explore', 44 | title: 'Explore', 45 | moduleId: 'views/explore', 46 | nav: false 47 | } 48 | ]; 49 | 50 | export const transition = { 51 | veryFast: 150, 52 | fast: 200, 53 | semiFast: 250, 54 | normal: 330, 55 | slow: 660, 56 | slowest: 1000 57 | }; 58 | 59 | export default { 60 | routes, 61 | transition 62 | }; 63 | -------------------------------------------------------------------------------- /aurelia_project/generators/task.js: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-dependency-injection'; 2 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli'; 3 | 4 | @inject(Project, CLIOptions, UI) 5 | export default class TaskGenerator { 6 | constructor(project, options, ui) { 7 | this.project = project; 8 | this.options = options; 9 | this.ui = ui; 10 | } 11 | 12 | execute() { 13 | return this.ui 14 | .ensureAnswer(this.options.args[0], 'What would you like to call the task?') 15 | .then(name => { 16 | let fileName = this.project.makeFileName(name); 17 | let functionName = this.project.makeFunctionName(name); 18 | 19 | this.project.tasks.add( 20 | ProjectItem.text(`${fileName}.js`, this.generateSource(functionName)) 21 | ); 22 | 23 | return this.project.commitChanges() 24 | .then(() => this.ui.log(`Created ${fileName}.`)); 25 | }); 26 | } 27 | 28 | generateSource(functionName) { 29 | return `import gulp from 'gulp'; 30 | import changed from 'gulp-changed'; 31 | import project from '../aurelia.json'; 32 | 33 | export default function ${functionName}() { 34 | return gulp.src(project.paths.???) 35 | .pipe(changed(project.paths.output, {extension: '.???'})) 36 | .pipe(gulp.dest(project.paths.output)); 37 | } 38 | 39 | `; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/views/configurator.js: -------------------------------------------------------------------------------- 1 | import { inject } from 'aurelia-dependency-injection'; 2 | import { Validator, ValidationControllerFactory, ValidationRules } from 'aurelia-validation'; 3 | 4 | // Utils etc. 5 | import anchorLink, { getAnchor } from 'utils/anchor-link'; 6 | import scrollToAnchor from 'utils/scroll-to-anchor'; 7 | 8 | 9 | @inject(Validator, ValidationControllerFactory) 10 | export class Configurator { 11 | constructor (validator, validationControllerFactory) { 12 | this.anchorLink = anchorLink; 13 | this.getAnchor = getAnchor; 14 | 15 | this.controller = validationControllerFactory.createForCurrentScope(validator); 16 | this.controller.subscribe(event => this.validated()); 17 | 18 | ValidationRules 19 | .ensure('fragmentsServer') 20 | .displayName('Server URL') 21 | .matches(/\d{3}-\d{2}-\d{4}/) 22 | .withMessage('My ASS') 23 | .on(this); 24 | } 25 | 26 | /* ----------------------- Aurelia-specific methods ----------------------- */ 27 | 28 | activate (urlParams) { 29 | if (urlParams.anchor) { 30 | this.anchor = `/about/${urlParams.anchor}`; 31 | } 32 | } 33 | 34 | attached () { 35 | if (this.anchor) { 36 | scrollToAnchor(this.anchor); 37 | } 38 | } 39 | 40 | /* ----------------------- Custom methods ----------------------- */ 41 | 42 | validated () {} 43 | } 44 | -------------------------------------------------------------------------------- /src/assets/styles/dialog.scss: -------------------------------------------------------------------------------- 1 | @import 'colors'; 2 | @import 'transitions'; 3 | 4 | dialog { 5 | z-index: 99; 6 | width: auto; 7 | height: 0; 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | background: transparentize($black, 1); 12 | transition: background $transition-very-fast $ease-in-out-cubic; 13 | 14 | &.full-dim { 15 | position: fixed; 16 | } 17 | 18 | &.is-active { 19 | height: auto; 20 | background: transparentize($black, 0.5); 21 | } 22 | 23 | .dialog-window { 24 | max-width: 75%; 25 | height: 0; 26 | color: $gray-darker; 27 | padding: 1rem; 28 | border-radius: 0.25rem; 29 | background: $white; 30 | opacity: 0; 31 | transform: scale(0); 32 | box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05), 33 | 0 1px 3px 0 rgba(0, 0, 0, 0.075), 34 | 0 3px 9px 0 rgba(0, 0, 0, 0.075); 35 | transition: opacity $transition-very-fast $ease-in-out-cubic, 36 | transform $transition-very-fast $ease-in-out-cubic; 37 | 38 | &.is-open { 39 | height: auto; 40 | opacity: 1; 41 | transform: scale(1); 42 | } 43 | } 44 | 45 | .button { 46 | font-size: inherit; 47 | color: $gray-dark; 48 | } 49 | 50 | .button:first-child { 51 | margin-right: 0.5rem; 52 | } 53 | 54 | .button:last-child { 55 | margin-left: 0.5rem; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/assets/styles/navigation.scss: -------------------------------------------------------------------------------- 1 | @import 'colors'; 2 | 3 | .primary-nav { 4 | text-transform: uppercase; 5 | line-height: 3.25rem; 6 | 7 | li { 8 | position: relative; 9 | margin: 0 0.25rem; 10 | 11 | &.is-active { 12 | &::before { 13 | position: absolute; 14 | content: ''; 15 | right: 0; 16 | bottom: 0; 17 | left: 0; 18 | height: 3px; 19 | background: $primary; 20 | } 21 | 22 | a { 23 | color: $black; 24 | } 25 | } 26 | 27 | &:last-child { 28 | padding-right: 0; 29 | margin-right: 0; 30 | } 31 | } 32 | 33 | a { 34 | height: 3rem; 35 | padding: 0 0.25rem; 36 | border-bottom: 0; 37 | } 38 | 39 | svg-icon { 40 | height: 100%; 41 | padding: 0 0.125rem; 42 | } 43 | 44 | svg { 45 | width: 100%; 46 | height: 100%; 47 | } 48 | 49 | .is-icon-only { 50 | width: 2.5rem; 51 | height: 3rem; 52 | padding: 0 0.5rem; 53 | 54 | a { 55 | display: block; 56 | } 57 | } 58 | 59 | .is-left-separated { 60 | position: relative; 61 | margin-left: 0.25rem; 62 | 63 | &::before { 64 | position: absolute; 65 | content: ''; 66 | top: 0.25rem; 67 | left: 0; 68 | bottom: 0.25rem; 69 | width: 1px; 70 | background: $gray-lighter; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /aurelia_project/generators/element.js: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-dependency-injection'; 2 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli'; 3 | 4 | @inject(Project, CLIOptions, UI) 5 | export default class ElementGenerator { 6 | constructor(project, options, ui) { 7 | this.project = project; 8 | this.options = options; 9 | this.ui = ui; 10 | } 11 | 12 | execute() { 13 | return this.ui 14 | .ensureAnswer(this.options.args[0], 'What would you like to call the custom element?') 15 | .then(name => { 16 | let fileName = this.project.makeFileName(name); 17 | let className = this.project.makeClassName(name); 18 | 19 | this.project.elements.add( 20 | ProjectItem.text(`${fileName}.js`, this.generateJSSource(className)), 21 | ProjectItem.text(`${fileName}.html`, this.generateHTMLSource(className)) 22 | ); 23 | 24 | return this.project.commitChanges() 25 | .then(() => this.ui.log(`Created ${fileName}.`)); 26 | }); 27 | } 28 | 29 | generateJSSource(className) { 30 | return `import {bindable} from 'aurelia-framework'; 31 | 32 | export class ${className} { 33 | @bindable value; 34 | 35 | valueChanged(newValue, oldValue) { 36 | 37 | } 38 | } 39 | 40 | `; 41 | } 42 | 43 | generateHTMLSource(className) { 44 | return ``; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/components/multi-select/multi-select.html: -------------------------------------------------------------------------------- 1 | 35 | -------------------------------------------------------------------------------- /src/components/higlass/higlass-actions.js: -------------------------------------------------------------------------------- 1 | export const SET_GRAYSCALE = 'SET_GRAYSCALE'; 2 | 3 | export const setGrayscale = grayscale => ({ 4 | type: SET_GRAYSCALE, 5 | payload: { grayscale } 6 | }); 7 | 8 | export const SET_FRAGMENTS_HIGHLIGHT = 'SET_FRAGMENTS_HIGHLIGHT'; 9 | 10 | export const setFragmentsHighlight = highlight => ({ 11 | type: SET_FRAGMENTS_HIGHLIGHT, 12 | payload: { highlight } 13 | }); 14 | 15 | export const SET_FRAGMENTS_SELECTION = 'SET_FRAGMENTS_SELECTION'; 16 | 17 | export const setFragmentsSelection = selection => ({ 18 | type: SET_FRAGMENTS_SELECTION, 19 | payload: { selection } 20 | }); 21 | 22 | export const SET_FRAGMENTS_SELECTION_FADE_OUT = 'SET_FRAGMENTS_SELECTION_FADE_OUT'; 23 | 24 | export const setFragmentsSelectionFadeOut = selectionFadeOut => ({ 25 | type: SET_FRAGMENTS_SELECTION_FADE_OUT, 26 | payload: { selectionFadeOut } 27 | }); 28 | 29 | export const SET_INTERACTIONS = 'SET_INTERACTIONS'; 30 | 31 | export const setInteractions = interactions => ({ 32 | type: SET_INTERACTIONS, 33 | payload: { interactions } 34 | }); 35 | 36 | export const SET_SELECTION_VIEW = 'SET_SELECTION_VIEW'; 37 | 38 | export const setSelectionView = domains => ({ 39 | type: SET_SELECTION_VIEW, 40 | payload: { domains } 41 | }); 42 | 43 | export const UPDATE_HGL_CONFIG = 'UPDATE_HGL_CONFIG'; 44 | 45 | export const updateConfig = config => ({ 46 | type: UPDATE_HGL_CONFIG, 47 | payload: { 48 | config 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /src/assets/styles/range-select.scss: -------------------------------------------------------------------------------- 1 | @import 'colors'; 2 | @import 'transitions'; 3 | 4 | range-select { 5 | position: relative; 6 | height: 1rem; 7 | } 8 | 9 | .range-select-from, 10 | .range-select-top { 11 | font-size: 0.8rem; 12 | line-height: 1rem; 13 | } 14 | 15 | .range-select-from { 16 | margin-right: 0.25rem; 17 | } 18 | 19 | .range-select-top { 20 | margin-left: 0.5rem; 21 | } 22 | 23 | .range-selector { 24 | z-index: 2; 25 | position: absolute; 26 | top: 0; 27 | width: 0.25rem; 28 | height: 1rem; 29 | margin-left: -0.25rem; 30 | padding: 0 0.25; 31 | border-radius: 0; 32 | background: transparent; 33 | 34 | &:active, 35 | &:focus, 36 | &:hover { 37 | z-index: 3; 38 | 39 | &:after { 40 | left: 0.125rem; 41 | right: 0.125rem; 42 | background: $black; 43 | } 44 | } 45 | 46 | &:after { 47 | position: absolute; 48 | content: ''; 49 | top: 0; 50 | right: 0.25rem; 51 | bottom: 0; 52 | left: 0.25rem; 53 | border-radius: 0.25rem; 54 | background: $gray; 55 | transition: all $transition-very-fast $ease-in-out-cubic; 56 | } 57 | } 58 | 59 | .range-select-range { 60 | z-index: 0; 61 | position: absolute; 62 | top: 0.375rem; 63 | right: 0; 64 | bottom: 0.375rem; 65 | left: 0; 66 | border-radius: 0.25rem; 67 | } 68 | 69 | .selected-range { 70 | z-index: 1; 71 | background: $gray; 72 | } 73 | 74 | .available-range { 75 | background: $gray-lightest; 76 | } 77 | -------------------------------------------------------------------------------- /src/utils/drag-drop.js: -------------------------------------------------------------------------------- 1 | import $ from './dom-el'; 2 | import hasParent from './has-parent'; 3 | 4 | /** 5 | * Add drag and drop behavior / listener to a DOM element. 6 | * 7 | * @param {object} baseEl - The base element. 8 | * @param {object} dropEl - The element stuff needs to be dropped of (including 9 | * children of this element). 10 | * @param {function} dropCallback - Function to be called when stuff is dropped. 11 | */ 12 | export default function dragDrop (baseEl, dropEl, dropCallback) { 13 | const $baseEl = $(baseEl); 14 | let isDragging = false; 15 | 16 | document.addEventListener('dragenter', (event) => { 17 | if (hasParent(event.target, baseEl)) { 18 | $baseEl.addClass('is-dragging-over'); 19 | isDragging = true; 20 | event.preventDefault(); 21 | } 22 | }); 23 | 24 | document.addEventListener('dragover', (event) => { 25 | if (isDragging) { 26 | event.preventDefault(); 27 | } 28 | }); 29 | 30 | document.addEventListener('dragleave', () => { 31 | if (isDragging && hasParent(event.target, dropEl)) { 32 | $baseEl.removeClass('is-dragging-over'); 33 | isDragging = false; 34 | } 35 | }); 36 | 37 | document.addEventListener('drop', (event) => { 38 | if (isDragging) { 39 | event.preventDefault(); 40 | 41 | if (hasParent(event.target, baseEl)) { 42 | dropCallback(event); 43 | } 44 | 45 | $baseEl.removeClass('is-dragging-over'); 46 | isDragging = false; 47 | } 48 | }, false); 49 | } 50 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const path = require('path'); 3 | const project = require('./aurelia_project/aurelia.json'); 4 | 5 | const testSrc = [ 6 | { pattern: project.unitTestRunner.source, included: false }, 7 | 'test/aurelia-karma.js' 8 | ]; 9 | 10 | const output = project.platform.output; 11 | const appSrc = project.build.bundles.map(x => path.join(output, x.name)); 12 | const entryIndex = appSrc.indexOf(path.join(output, project.build.loader.configTarget)); 13 | const entryBundle = appSrc.splice(entryIndex, 1)[0]; 14 | const files = [entryBundle].concat(testSrc).concat(appSrc); 15 | 16 | module.exports = function (config) { 17 | const cfg = { 18 | basePath: '', 19 | frameworks: [project.testFramework.id], 20 | files, 21 | exclude: [], 22 | preprocessors: { 23 | [project.unitTestRunner.source]: [project.transpiler.id] 24 | }, 25 | babelPreprocessor: { options: project.transpiler.options }, 26 | reporters: ['progress'], 27 | port: 9876, 28 | colors: true, 29 | logLevel: config.LOG_INFO, 30 | autoWatch: true, 31 | browsers: ['Chrome'], 32 | customLaunchers: { 33 | Chrome_travis_ci: { 34 | base: 'Chrome', 35 | flags: ['--no-sandbox'] 36 | } 37 | }, 38 | singleRun: false, 39 | // client.args must be a array of string. 40 | // Leave 'aurelia-root', project.paths.root in this order so we can find 41 | // the root of the aurelia project. 42 | client: { 43 | args: ['aurelia-root', project.paths.root] 44 | } 45 | }; 46 | 47 | if (process.env.TRAVIS) { 48 | cfg.browsers = ['Chrome_travis_ci']; 49 | } 50 | 51 | config.set(cfg); 52 | }; 53 | -------------------------------------------------------------------------------- /src/configs/examples.js: -------------------------------------------------------------------------------- 1 | export const rao2014Gm12878Mbol1kbMresChr22Loops = { 2 | name: 'Loops in chromosome 22 of GM12878', 3 | data: 'Rao et al. (2014) GM12878 Mbol 1kb', 4 | url: 'https://gist.githubusercontent.com/flekschas/8b0163f25fd4ffb067aaba2a595da447/raw/12316079e1adb63450ad38e21fdb51950440e917/rao-2014-gm12878-mbol-1kb-mres-chr22-loops-hipiler-v140.json' 5 | }; 6 | 7 | export const rao2014Gm12878Mbol1kbMresChr4Tads = { 8 | name: 'TADs in chromosome 4 of GM12878', 9 | data: 'Rao et al. (2014) GM12878 Mbol 1kb', 10 | url: 'https://gist.githubusercontent.com/flekschas/37b6293b82b5b3e5fb56de9827100797/raw/9d6f05d5b4dc783560337772a203d66f30a068aa/rao-2014-gm12878-mbol-1kb-mres-chr4-tads-hipiler-v140.json' 11 | }; 12 | 13 | export const rao2014Gm12878Mbol1kbMresTelomeres = { 14 | name: 'Telomere contacts of GM2878', 15 | data: 'Rao et al. (2014) GM12878 Mbol 1kb', 16 | url: 'https://gist.githubusercontent.com/flekschas/3700ffa02aac69b52a2b10300299f967/raw/f0b93a08d0b3f2d7c7a6bcca31b502f1234d235e/rao-2014-gm12878-mbol-1kb-mres-telomeres-hipiler-v140.json' 17 | }; 18 | 19 | export const rao2014Gm12878VsK562Mbol1kbMresTelomeres = { 20 | name: 'Telomere contacts of GM12878 vs K562', 21 | data: 'Rao et al. (2014) GM12878 and K562 Mbol 1kb', 22 | url: 'https://gist.githubusercontent.com/flekschas/a397dded3694fc3984c6acdae6a837fd/raw/021bd894b0d84df8df05e65b442669b39f17b7c7/rao-2014-gm12878-vs-k562-mbol-1kb-mres-telomeres-hipiler-v140.json' 23 | }; 24 | 25 | export default [ 26 | rao2014Gm12878Mbol1kbMresChr22Loops, 27 | rao2014Gm12878Mbol1kbMresChr4Tads, 28 | rao2014Gm12878Mbol1kbMresTelomeres, 29 | rao2014Gm12878VsK562Mbol1kbMresTelomeres 30 | ]; 31 | -------------------------------------------------------------------------------- /src/utils/hilbert-curve.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-plusplus,no-bitwise */ 2 | 3 | // Adapted from Jason Davies: https://www.jasondavies.com/hilbert-curve/ 4 | const hilbert = (function () { 5 | // From Mike Bostock: http://bl.ocks.org/597287 6 | // Adapted from Nick Johnson: http://bit.ly/biWkkq 7 | const pairs = [ 8 | [[0, 3], [1, 0], [3, 1], [2, 0]], 9 | [[2, 1], [1, 1], [3, 0], [0, 2]], 10 | [[2, 2], [3, 3], [1, 2], [0, 1]], 11 | [[0, 0], [3, 2], [1, 3], [2, 3]] 12 | ]; 13 | 14 | // d2xy and rot are from: 15 | // http://en.wikipedia.org/wiki/Hilbert_curve#Applications_and_mapping_algorithms 16 | function rot (n, x, y, rx, ry) { 17 | if (ry === 0) { 18 | if (rx === 1) { 19 | x = n - 1 - x; 20 | y = n - 1 - y; 21 | } 22 | return [y, x]; 23 | } 24 | return [x, y]; 25 | } 26 | 27 | return { 28 | xy2d (x, y, z) { 29 | let quad = 0; 30 | let pair; 31 | let i = 0; 32 | 33 | while (--z >= 0) { 34 | pair = pairs[quad][(x & (1 << z) ? 2 : 0) | (y & (1 << z) ? 1 : 0)]; 35 | i = (i << 2) | pair[0]; 36 | quad = pair[1]; 37 | } 38 | 39 | return i; 40 | }, 41 | d2xy (z, t) { 42 | let n = 1 << z; 43 | let x = 0; 44 | let y = 0; 45 | for (let s = 1; s < n; s *= 2) { 46 | let rx = 1 & (t / 2); 47 | let ry = 1 & (t ^ rx); 48 | let xy = rot(s, x, y, rx, ry); 49 | x = xy[0] + (s * rx); 50 | y = xy[1] + (s * ry); 51 | t /= 4; 52 | } 53 | return [x, y]; 54 | } 55 | }; 56 | })(); 57 | 58 | export default function hilbertCurve (level, index) { 59 | return hilbert.d2xy(level, index); 60 | } 61 | -------------------------------------------------------------------------------- /src/assets/styles/mixins/slide-out-in.scss: -------------------------------------------------------------------------------- 1 | @mixin slide-out-in($speed, $easing) { 2 | -webkit-animation: slide-out-in $speed $easing 1; 3 | -moz-animation: slide-out-in $speed $easing 1; 4 | animation: slide-out-in $speed $easing 1; 5 | -webkit-transform-origin: 50% 50%; 6 | -moz-transform-origin: 50% 50%; 7 | transform-origin: 50% 50%; 8 | 9 | @-moz-keyframes slide-out-in { 10 | 0% { 11 | -moz-transform: translateX(0); 12 | opacity: 1; 13 | } 14 | 50% { 15 | -moz-transform: translateX(75%); 16 | opacity: 0; 17 | } 18 | 51% { 19 | -moz-transform: translateX(-50%); 20 | opacity: 0; 21 | } 22 | 100% { 23 | -moz-transform: translateX(0); 24 | opacity: 1; 25 | } 26 | } 27 | @-webkit-keyframes slide-out-in { 28 | 0% { 29 | -webkit-transform: translateX(0); 30 | opacity: 1; 31 | } 32 | 50% { 33 | -webkit-transform: translateX(75%); 34 | opacity: 0; 35 | } 36 | 51% { 37 | -webkit-transform: translateX(-50%); 38 | opacity: 0; 39 | } 40 | 100% { 41 | -webkit-transform: translateX(0); 42 | opacity: 1; 43 | } 44 | } 45 | @keyframes slide-out-in { 46 | 0% { 47 | -webkit-transform: translateX(0); 48 | transform: translateX(0); 49 | opacity: 1; 50 | } 51 | 50% { 52 | -webkit-transform: translateX(75%); 53 | transform: translateX(75%); 54 | opacity: 0; 55 | } 56 | 51% { 57 | -webkit-transform: translateX(-50%); 58 | transform: translateX(-50%); 59 | opacity: 0; 60 | } 61 | 100% { 62 | -webkit-transform: translateX(0); 63 | transform: translateX(0); 64 | opacity: 1; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /aurelia_project/tasks/run.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import browserSync from 'browser-sync'; 3 | import historyApiFallback from 'connect-history-api-fallback/lib'; 4 | import project from '../aurelia.json'; 5 | import build from './build'; 6 | import {CLIOptions} from 'aurelia-cli'; 7 | 8 | function log(message) { 9 | console.log(message); //eslint-disable-line no-console 10 | } 11 | 12 | function onChange(path) { 13 | log(`File Changed: ${path}`); 14 | } 15 | 16 | function reload(done) { 17 | browserSync.reload(); 18 | done(); 19 | } 20 | 21 | let serve = gulp.series( 22 | build, 23 | done => { 24 | browserSync({ 25 | online: false, 26 | open: false, 27 | port: 9000, 28 | logLevel: 'silent', 29 | server: { 30 | baseDir: ['.'], 31 | middleware: [historyApiFallback(), function(req, res, next) { 32 | res.setHeader('Access-Control-Allow-Origin', '*'); 33 | next(); 34 | }] 35 | } 36 | }, function(err, bs) { 37 | let urls = bs.options.get('urls').toJS(); 38 | log(`Application Available At: ${urls.local}`); 39 | log(`BrowserSync Available At: ${urls.ui}`); 40 | done(); 41 | }); 42 | } 43 | ); 44 | 45 | let refresh = gulp.series( 46 | build, 47 | reload 48 | ); 49 | 50 | let watch = function() { 51 | gulp.watch(project.transpiler.source, refresh).on('change', onChange); 52 | gulp.watch(project.markupProcessor.source, refresh).on('change', onChange); 53 | gulp.watch(project.cssProcessor.source, refresh).on('change', onChange); 54 | }; 55 | 56 | let run; 57 | 58 | if (CLIOptions.hasFlag('watch')) { 59 | run = gulp.series( 60 | serve, 61 | watch 62 | ); 63 | } else { 64 | run = serve; 65 | } 66 | 67 | export default run; 68 | -------------------------------------------------------------------------------- /src/configs/colors.js: -------------------------------------------------------------------------------- 1 | export const BLACK = 0x000000; 2 | export const GRAY = 0x999999; 3 | export const WHITE = 0xffffff; 4 | 5 | export const GRAY_LIGHT = 0xbfbfbf; 6 | export const GRAY_LIGHTER = 0xd9d9d9; 7 | export const GRAY_LIGHTEST = 0xe5e5e5; 8 | export const GRAY_DARK = 0x666666; 9 | export const GRAY_DARKER = 0x444444; 10 | export const GRAY_DARKEST = 0x222222; 11 | 12 | export const CYAN = 0x3ae0e5; 13 | export const CYAN_RGBA = [58, 224, 229, 255]; 14 | export const GREEN = 0x40bf00; 15 | export const GREEN_RGBA = [64, 191, 0, 255]; 16 | export const ORANGE = 0xff5500; 17 | export const ORANGE_RGBA = [255, 85, 0, 255]; 18 | export const PINK = 0xec3bb6; 19 | export const PINK_RGBA = [236, 59, 182, 255]; 20 | export const RED = 0xf60029; 21 | export const RED_RGBA = [246, 0, 41, 255]; 22 | export const YELLOW = 0xffcc00; 23 | export const YELLOW_RGBA = [255, 204, 0, 255]; 24 | export const BLUE = 0x4e40ff; 25 | export const BLUE_RGBA = [78, 64, 255, 255]; 26 | 27 | export const PRIMARY = ORANGE; 28 | export const SECONDARY = BLUE; 29 | 30 | export const LOW_QUALITY_BLUE = 0xdcd9ff; 31 | export const LOW_QUALITY_BLUE_ARR = [0.862745098, 0.850980392, 1]; 32 | export const LOW_QUALITY_BLUE_RGBA = [220, 217, 255, 255]; 33 | export const LOW_QUALITY_ORANGE = 0xffd9cb; 34 | export const LOW_QUALITY_ORANGE_ARR = [1, 0.890196078, 0.835294118]; 35 | export const LOW_QUALITY_ORANGE_RGBA = [255, 227, 213, 255]; 36 | 37 | export default { 38 | BLACK, 39 | GRAY, 40 | WHITE, 41 | GRAY_LIGHT, 42 | GRAY_LIGHTER, 43 | GRAY_LIGHTEST, 44 | GRAY_DARK, 45 | GRAY_DARKER, 46 | GRAY_DARKEST, 47 | CYAN, 48 | CYAN_RGBA, 49 | GREEN, 50 | GREEN_RGBA, 51 | ORANGE, 52 | ORANGE_RGBA, 53 | PINK, 54 | PINK_RGBA, 55 | RED, 56 | RED_RGBA, 57 | YELLOW, 58 | YELLOW_RGBA, 59 | BLUE, 60 | BLUE_RGBA, 61 | PRIMARY, 62 | SECONDARY, 63 | LOW_QUALITY_BLUE, 64 | LOW_QUALITY_BLUE_ARR, 65 | LOW_QUALITY_BLUE_RGBA, 66 | LOW_QUALITY_ORANGE, 67 | LOW_QUALITY_ORANGE_ARR, 68 | LOW_QUALITY_ORANGE_RGBA 69 | }; 70 | -------------------------------------------------------------------------------- /src/services/states.js: -------------------------------------------------------------------------------- 1 | // Aurelia 2 | import { LogManager } from 'aurelia-framework'; 3 | 4 | // Third party 5 | import localforage from 'localForage'; 6 | import { applyMiddleware, compose, createStore } from 'redux'; 7 | import { autoRehydrate, persistStore, purgeStoredState } from 'redux-persist'; 8 | import thunk from 'redux-thunk'; 9 | // import freeze from 'redux-freeze'; 10 | import undoable, { ActionCreators, groupByActionTypes } from 'redux-undo'; 11 | import { enableBatching } from 'redux-batched-actions'; 12 | 13 | import { resetState } from 'app-actions'; 14 | import appReducer from 'app-reducer'; 15 | 16 | import { 17 | ANNOTATE_PILE, 18 | SELECT_PILE 19 | } from 'components/fragments/fragments-actions'; 20 | 21 | import logger from 'utils/redux-logger'; 22 | 23 | const config = { 24 | storage: localforage, 25 | debounce: 25, 26 | keyPrefix: 'hipiler.' 27 | }; 28 | 29 | const debug = LogManager.getLevel() === LogManager.logLevel.debug; 30 | 31 | const middlewares = [autoRehydrate(), applyMiddleware(thunk)]; 32 | 33 | if (debug) { 34 | middlewares.push(applyMiddleware(logger)); 35 | // middleware.push(applyMiddleware(freeze)); 36 | } 37 | 38 | export default class States { 39 | constructor () { 40 | this.store = createStore( 41 | undoable(enableBatching(appReducer), { 42 | groupBy: groupByActionTypes([ANNOTATE_PILE, SELECT_PILE]), 43 | limit: 25 44 | }), 45 | undefined, 46 | compose(...middlewares) 47 | ); 48 | 49 | persistStore(this.store, config, (err, state) => { 50 | // Rehydration is done 51 | this.isRehydrated = Object.keys(true).length > 0; 52 | }); 53 | } 54 | 55 | undo () { 56 | this.store.dispatch(ActionCreators.undo()); 57 | } 58 | 59 | redo () { 60 | this.store.dispatch(ActionCreators.redo()); 61 | } 62 | 63 | reset () { 64 | // Reset state to default values 65 | this.store.dispatch(resetState()); 66 | 67 | // Clear history 68 | this.store.dispatch(ActionCreators.clearHistory()); 69 | 70 | // Purge persistent store 71 | return purgeStoredState(config); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- 1 | �PNG 2 |  3 | IHDR szz�bKGD�C� pHYs  �� vpAg ����TIDATXå�y�W��?���[�u�Pk��K�4�օ��AVqXZ6Q���Z�*��-BEaA�"��*�� mSMm�Xm�E�����03�w��?���Y=�ͽ����~��{ν�]T����f������*�p�1k��xd���}��n��x��M\��MT�x�g�����{��H�]g j: 8b���v�uޠ+�,��v�;fm��fm�#��G�;�x�ƈ�i!,i��t�ڳ\��>�1!�+��ܐ�M`{� �US7WBP�yM��w�Zu� ���x��������qk�8���5�\0��IS���žZJ7������&j��������Vwv��;b��2f[���;Pۢ�ϝQY�9j��X9�bǔO�R`v� 4 | p�=�7�^�y���O=�BsZ�M�c���Eg���|��P��k+�}�~�;V�e(@D ���̮Zq �cvՊ^��"�\F���Ӏor�vS�5I.�i����rͤ��O��r���;ܿ2��-���[� �4\���Y��������]k��AZrh�P�h�h�>��C��;�=�~������e���W�VN�.�O�aA��i��1�]s��Y�N`:pЧ�Y�džo��j��>�]֧��1�1�p.���n���*��|p�"�B�r(eu�V�e~D N�U��'=�o*��C�[�ՎX���%�����oN�}�4@b7�rvh�5|TY��})�X��@$BN�ȭ y� ��ƚ�m�ޱ�{M��e��.x_,&�׎�d{����)}>��&��/z��-U1�bL�59���������U����և\�����Y���5��؈�9�w��������Qa̠�S����祇��ݪ�R� y��=������-o�pH>;�,Ag�� r�ܵ#�9f n���r�� �Z?-��Z5���'� �3�0q��ò�RcI��Y��(|�X�'����,�o�w�[����O { 16 | let fileName = this.project.makeFileName(name); 17 | let className = this.project.makeClassName(name); 18 | 19 | this.project.generators.add( 20 | ProjectItem.text(`${fileName}.js`, this.generateSource(className)) 21 | ); 22 | 23 | return this.project.commitChanges() 24 | .then(() => this.ui.log(`Created ${fileName}.`)); 25 | }); 26 | } 27 | 28 | generateSource(className) { 29 | return `import {inject} from 'aurelia-dependency-injection'; 30 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli'; 31 | 32 | @inject(Project, CLIOptions, UI) 33 | export default class ${className}Generator { 34 | constructor(project, options, ui) { 35 | this.project = project; 36 | this.options = options; 37 | this.ui = ui; 38 | } 39 | 40 | execute() { 41 | return this.ui 42 | .ensureAnswer(this.options.args[0], 'What would you like to call the new item?') 43 | .then(name => { 44 | let fileName = this.project.makeFileName(name); 45 | let className = this.project.makeClassName(name); 46 | 47 | this.project.elements.add( 48 | ProjectItem.text(\`\${fileName}.js\`, this.generateSource(className)) 49 | ); 50 | 51 | return this.project.commitChanges() 52 | .then(() => this.ui.log(\`Created \${fileName}.\`)); 53 | }); 54 | } 55 | 56 | generateSource(className) { 57 | return \`import {bindable} from 'aurelia-framework'; 58 | 59 | export class \${className} { 60 | @bindable value; 61 | 62 | valueChanged(newValue, oldValue) { 63 | 64 | } 65 | } 66 | 67 | \` 68 | } 69 | } 70 | 71 | `; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/utils/build-config.js: -------------------------------------------------------------------------------- 1 | import basicHiglassConfig from './basic-higlass-config'; 2 | 3 | const checkLoci = (header) => { 4 | const minCols = { 5 | chrom1: 0, 6 | start1: 0, 7 | end1: 0, 8 | chrom2: 0, 9 | start2: 0, 10 | end2: 0, 11 | dataset: 0, 12 | zoomoutlevel: 0, 13 | server: 0 14 | }; 15 | const cols = Object.keys(header); 16 | 17 | if (cols.length < 8) return false; 18 | 19 | cols.forEach((col) => { 20 | if (typeof minCols[col] !== 'undefined') minCols[col] += 1; 21 | }); 22 | 23 | return Object.values(minCols).every(col => col > 0); 24 | }; 25 | 26 | const buildConfig = (rows) => { 27 | // Scan loci and extract 28 | const colIds = {}; 29 | const fragments = []; 30 | const noInt = { 31 | chrom1: true, 32 | strand1: true, 33 | chrom2: true, 34 | strand2: true, 35 | dataset: true, 36 | server: true, 37 | coords: true 38 | }; 39 | const datasets = {}; 40 | let server; 41 | 42 | for (let i = 0; i < rows.length; i++) { 43 | const row = rows[i]; 44 | if (i === 0) { 45 | const header = []; 46 | row.forEach((col, j) => { 47 | header.push(col.toLowerCase()); 48 | colIds[col.toLowerCase()] = j; 49 | }); 50 | if (checkLoci(colIds)) { 51 | fragments.push(header); 52 | } else { 53 | return; 54 | } 55 | } else if (row.length === fragments[0].length) { 56 | const parsedRow = []; 57 | row.forEach((val, j) => { 58 | if (noInt[fragments[0][j]] || fragments[0][j][0] === '_') { 59 | parsedRow.push(val); 60 | } else { 61 | parsedRow.push(+val); 62 | } 63 | }); 64 | fragments.push(parsedRow); 65 | datasets[parsedRow[colIds.dataset]] = { 66 | coords: parsedRow[colIds.coords] || 'hg19', 67 | geneAnnotations: parsedRow[colIds._gene_annotations] 68 | }; 69 | server = parsedRow[colIds.server]; 70 | } 71 | } 72 | 73 | const config = { 74 | fgm: { 75 | fragmentsServer: server, 76 | fragmentsPrecision: 2, 77 | fragments 78 | }, 79 | hgl: basicHiglassConfig( 80 | server, Object.keys(datasets) 81 | .map(matrix => ({ matrix, ...datasets[matrix] })) 82 | ) 83 | }; 84 | 85 | return config; 86 | }; 87 | 88 | export default buildConfig; 89 | -------------------------------------------------------------------------------- /src/assets/styles/docs.scss: -------------------------------------------------------------------------------- 1 | @import 'colors'; 2 | @import 'transitions'; 3 | 4 | pre, 5 | code { 6 | padding: 0 0.125rem; 7 | font-family: 'Roboto Mono', monospace; 8 | background: #f3f3f3; 9 | } 10 | 11 | code { 12 | font-size: 0.8em; 13 | } 14 | 15 | pre { 16 | max-height: 30em; 17 | overflow-x: scroll; 18 | padding: 0.25rem; 19 | font-size: 0.85em; 20 | line-height: 1.5em; 21 | } 22 | 23 | .wiki-page { 24 | position: relative; 25 | 26 | &:first-child { 27 | border-top: 0; 28 | } 29 | 30 | h1 { 31 | display: block; 32 | margin: 4rem 0 1rem; 33 | padding: 0; 34 | text-transform: uppercase; 35 | border-bottom: 2px solid #f3f3f3; 36 | 37 | &:hover { 38 | color: #000; 39 | } 40 | 41 | .hidden-anchor > .icon { 42 | height: 3rem; 43 | } 44 | } 45 | 46 | &:first-child h1 { 47 | margin-top: 2rem; 48 | } 49 | 50 | h2, h3, h4 { 51 | margin: 1em 0 0.5em; 52 | } 53 | 54 | h2 { 55 | font-size: 1.5em; 56 | line-height: 1.5em; 57 | } 58 | 59 | h3 { 60 | font-size: 1.25em; 61 | } 62 | 63 | h4 { 64 | font-size: 1em; 65 | font-weight: 500; 66 | color: $black; 67 | } 68 | 69 | *:last-child { 70 | margin-bottom: 1rem; 71 | } 72 | 73 | table { 74 | margin-bottom: 0.5rem; 75 | font-size: 0.85em; 76 | border-collapse: collapse; 77 | 78 | th { 79 | font-weight: bold; 80 | } 81 | 82 | td, th { 83 | padding: 0.25rem; 84 | border: 1px solid #e5e5e5; 85 | outline: 0; 86 | } 87 | 88 | tr:nth-child(even) td { 89 | background: #f3f3f3; 90 | } 91 | } 92 | 93 | li { 94 | margin-left: 1.5rem; 95 | list-style-type: disc; 96 | } 97 | } 98 | 99 | .sidebar { 100 | margin: 2rem 0 0 2rem; 101 | padding: 0.75rem; 102 | font-size: 0.95rem; 103 | border: 1px solid $gray-lightest; 104 | border-radius: 0.25rem; 105 | 106 | p { 107 | margin: 1.5rem 0 0.25rem; 108 | 109 | &:first-child { 110 | margin-top: 0; 111 | } 112 | 113 | &:last-child { 114 | margin-bottom: 0; 115 | } 116 | } 117 | 118 | strong, 119 | strong * { 120 | font-weight: 500; 121 | } 122 | 123 | li { 124 | margin-left: 1.5rem; 125 | list-style-type: disc; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/assets/styles/pile-context-menu.scss: -------------------------------------------------------------------------------- 1 | @import 'colors'; 2 | @import 'transitions'; 3 | 4 | .pile-context-menu { 5 | position: fixed; 6 | z-index: 99; 7 | opacity: 0; 8 | font-family: 'Rubik', sans-serif; 9 | font-size: 0.8rem; 10 | transform: scale(0); 11 | transform-origin: right top; 12 | transition: opacity $transition-very-fast $ease-in-out-cubic, 13 | transform $transition-very-fast $ease-in-out-cubic; 14 | 15 | &.is-active { 16 | opacity: 1; 17 | transform: scale(1); 18 | } 19 | 20 | &.is-align-left button { 21 | text-align: left; 22 | } 23 | 24 | &.is-bottom-up { 25 | transform-origin: right bottom; 26 | ul { 27 | transform: translateY(0); 28 | } 29 | } 30 | 31 | .is-multiple { 32 | text-align: center; 33 | 34 | button { 35 | margin: 0.125rem; 36 | font-weight: bold; 37 | 38 | &:first-child { 39 | margin-left: 0; 40 | } 41 | 42 | &:last-child { 43 | margin-right: 0; 44 | } 45 | } 46 | } 47 | 48 | ul { 49 | padding: 0.125rem; 50 | background: transparentize($white, 0.05); 51 | border-radius: 0.25rem; 52 | box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1), 53 | 0 1px 3px 0 rgba(0, 0, 0, 0.05), 54 | 0 2px 6px 0 rgba(0, 0, 0, 0.05); 55 | transform: translateY(-50%); 56 | } 57 | 58 | button { 59 | margin: 1px 0; 60 | padding: 0.125rem 0.25rem; 61 | font: inherit; 62 | text-align: right; 63 | border-radius: 0.125rem; 64 | background: transparent; 65 | 66 | &:hover { 67 | background: $gray-lightest; 68 | } 69 | 70 | &:active { 71 | color: $black; 72 | background: $primary; 73 | } 74 | } 75 | 76 | li.is-active button, 77 | li.is-active button:hover, 78 | li.is-active button:active { 79 | color: $primary; 80 | background: lighten($primary, 40%); 81 | } 82 | 83 | label { 84 | color: $gray; 85 | text-align: right; 86 | 87 | &:after { 88 | content: ':'; 89 | } 90 | } 91 | 92 | .separator { 93 | margin: 0.25rem -0.125rem; 94 | height: 1px; 95 | background: $gray-lightest; 96 | } 97 | 98 | li:first-child .separator, 99 | li:last-child .separator { 100 | display: none; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/components/dialog/dialog.js: -------------------------------------------------------------------------------- 1 | // Aurelia 2 | import { 3 | bindable, 4 | bindingMode, 5 | inject // eslint-disable-line 6 | } from 'aurelia-framework'; 7 | 8 | import { EventAggregator } from 'aurelia-event-aggregator'; // eslint-disable-line 9 | 10 | 11 | @inject(EventAggregator) 12 | export class Dialog { 13 | @bindable({ defaultBindingMode: bindingMode.twoWay }) deferred = {}; // eslint-disable-line 14 | @bindable({ defaultBindingMode: bindingMode.twoWay }) isOpen = false; // eslint-disable-line 15 | @bindable({ defaultBindingMode: bindingMode.oneWay }) message = ''; // eslint-disable-line 16 | 17 | constructor (event) { 18 | this.event = event; 19 | 20 | this.subscriptions = []; 21 | 22 | this.initEventListeners(); 23 | } 24 | 25 | /* ----------------------- Aurelia-specific methods ----------------------- */ 26 | 27 | /** 28 | * Called once the component is detached. 29 | */ 30 | detached () { 31 | // Unsubscribe from Aurelia events 32 | this.subscriptions.forEach((subscription) => { 33 | subscription.dispose(); 34 | }); 35 | this.subscriptions = undefined; 36 | } 37 | 38 | /* ---------------------------- Class methods ----------------------------- */ 39 | 40 | /** 41 | * Cancel dialog. This will reject the promise and clode the dialog window. 42 | */ 43 | cancel () { 44 | if (this.deferred && this.deferred.reject) { 45 | this.deferred.reject(Error()); 46 | this.isOpen = false; 47 | } 48 | } 49 | 50 | /** 51 | * Initializae event listeners. 52 | */ 53 | initEventListeners () { 54 | this.subscriptions.push( 55 | this.event.subscribe('app.keyUp', this.keyUpHandler.bind(this)) 56 | ); 57 | } 58 | 59 | /** 60 | * Handle key up events and delegate the tasks. 61 | * 62 | * @param {object} event - Event object. 63 | */ 64 | keyUpHandler (event) { 65 | switch (event.keyCode) { 66 | case 13: // Enter 67 | this.okay(); 68 | break; 69 | 70 | case 27: // ESC 71 | this.cancel(); 72 | break; 73 | 74 | default: 75 | // Nothing 76 | break; 77 | } 78 | } 79 | 80 | /** 81 | * Accept dialog. This will resolve the promise and close the dialog. 82 | */ 83 | okay () { 84 | if (this.deferred && this.deferred.resolve) { 85 | this.deferred.resolve(); 86 | this.isOpen = false; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /test/aurelia-karma.js: -------------------------------------------------------------------------------- 1 | (function(global) { 2 | var karma = global.__karma__; 3 | var requirejs = global.requirejs 4 | var locationPathname = global.location.pathname; 5 | var root = 'src'; 6 | karma.config.args.forEach(function(value, index) { 7 | if (value === 'aurelia-root') { 8 | root = karma.config.args[index + 1]; 9 | } 10 | }); 11 | 12 | if (!karma || !requirejs) { 13 | return; 14 | } 15 | 16 | function normalizePath(path) { 17 | var normalized = [] 18 | var parts = path 19 | .split('?')[0] // cut off GET params, used by noext requirejs plugin 20 | .split('/') 21 | 22 | for (var i = 0; i < parts.length; i++) { 23 | if (parts[i] === '.') { 24 | continue 25 | } 26 | 27 | if (parts[i] === '..' && normalized.length && normalized[normalized.length - 1] !== '..') { 28 | normalized.pop() 29 | continue 30 | } 31 | 32 | normalized.push(parts[i]) 33 | } 34 | 35 | return normalized.join('/') 36 | } 37 | 38 | function patchRequireJS(files, originalLoadFn, locationPathname) { 39 | var IS_DEBUG = /debug\.html$/.test(locationPathname) 40 | 41 | requirejs.load = function (context, moduleName, url) { 42 | url = normalizePath(url) 43 | 44 | if (files.hasOwnProperty(url) && !IS_DEBUG) { 45 | url = url + '?' + files[url] 46 | } 47 | 48 | if (url.indexOf('/base') !== 0) { 49 | url = '/base/' + url; 50 | } 51 | 52 | return originalLoadFn.call(this, context, moduleName, url) 53 | } 54 | 55 | var originalDefine = global.define; 56 | global.define = function(name, deps, m) { 57 | if (typeof name === 'string') { 58 | originalDefine('/base/' + root + '/' + name, [name], function (result) { return result; }); 59 | } 60 | 61 | return originalDefine(name, deps, m); 62 | } 63 | } 64 | 65 | function requireTests() { 66 | var TEST_REGEXP = /(spec)\.js$/i; 67 | var allTestFiles = ['/base/test/unit/setup.js']; 68 | 69 | Object.keys(window.__karma__.files).forEach(function(file) { 70 | if (TEST_REGEXP.test(file)) { 71 | allTestFiles.push(file); 72 | } 73 | }); 74 | 75 | require(allTestFiles, window.__karma__.start); 76 | } 77 | 78 | karma.loaded = function() {}; // make it async 79 | patchRequireJS(karma.files, requirejs.load, locationPathname); 80 | requireTests(); 81 | })(window); 82 | -------------------------------------------------------------------------------- /src/views/docs.js: -------------------------------------------------------------------------------- 1 | // Aurelia 2 | import { 3 | inject, // eslint-disable-line 4 | InlineViewStrategy 5 | } from 'aurelia-framework'; 6 | import { EventAggregator } from 'aurelia-event-aggregator'; // eslint-disable-line 7 | 8 | // Docs 9 | import sidebar from 'text!../../assets/wiki/sidebar.html'; // eslint-disable-line import/no-webpack-loader-syntax 10 | import wiki from 'text!../../assets/wiki/wiki.html'; // eslint-disable-line import/no-webpack-loader-syntax 11 | 12 | // Utils etc. 13 | import debounce from 'utils/debounce'; 14 | import scrollToAnchor from 'utils/scroll-to-anchor'; 15 | 16 | 17 | @inject(EventAggregator) 18 | export class Docs { 19 | constructor (event) { 20 | this.event = event; 21 | 22 | this.docs = new InlineViewStrategy(wiki); 23 | 24 | this.sidebar = new InlineViewStrategy(sidebar); 25 | this.sidebarCss = {}; 26 | 27 | this.subscriptions = []; 28 | } 29 | 30 | /* ----------------------- Aurelia-specific methods ----------------------- */ 31 | 32 | activate (urlParams) { 33 | const anchor1 = urlParams.anchor ? urlParams.anchor : ''; 34 | const anchor2 = urlParams.anchor2 ? `/${urlParams.anchor2}` : ''; 35 | 36 | if (anchor1) { 37 | this.anchor = `/docs/${anchor1}${anchor2}`; 38 | } 39 | } 40 | 41 | attached () { 42 | this.sidebarOffsetTop = this.sidebarEl.getBoundingClientRect().top - 43 | document.body.getBoundingClientRect().top; 44 | 45 | this.initEventListeners(); 46 | 47 | if (this.anchor) { 48 | scrollToAnchor(this.anchor); 49 | } 50 | } 51 | 52 | detached () { 53 | this.subscriptions.forEach((subscription) => { 54 | subscription.dispose(); 55 | }); 56 | this.subscriptions = []; 57 | } 58 | 59 | /* ---------------------------- Class methods ----------------------------- */ 60 | 61 | adjustSidebarPos (event) { 62 | this.sidebarMarginTop = Math.abs( 63 | this.sidebarOffsetTop - this.sidebarEl.getBoundingClientRect().top 64 | ); 65 | 66 | this.sidebarMarginTop = (this.sidebarMarginTop - 48) < 0 ? 0 : this.sidebarMarginTop - 48; 67 | 68 | this.sidebarCss = { 69 | 'padding-top': `${this.sidebarMarginTop}px` 70 | }; 71 | } 72 | 73 | initEventListeners () { 74 | const adjustSidebarPosDb = debounce(this.adjustSidebarPos.bind(this), 50); 75 | 76 | this.subscriptions.push( 77 | this.event.subscribe('app.scroll', adjustSidebarPosDb) 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/assets/styles/range.scss: -------------------------------------------------------------------------------- 1 | @import 'colors'; 2 | @import 'transitions'; 3 | 4 | input[type=range] { 5 | -webkit-appearance: none; 6 | margin: 0; 7 | width: 100%; 8 | } 9 | 10 | input[type=range]:focus { 11 | outline: none; 12 | } 13 | 14 | input[type=range]::-webkit-slider-runnable-track { 15 | width: 100%; 16 | height: 1rem; 17 | cursor: pointer; 18 | background: $gray-lighter; 19 | border: 0; 20 | border-radius: 0.1rem; 21 | box-shadow: inset 0 0.375rem 0 0 $white, inset 0 -0.375rem 0 0 $white; 22 | } 23 | 24 | input[type=range]::-webkit-slider-thumb { 25 | height: 1rem; 26 | width: 0.25rem; 27 | background: $gray; 28 | cursor: pointer; 29 | -webkit-appearance: none; 30 | // margin-top: -0.375rem; 31 | border: 0; 32 | border-radius: 0.25rem; 33 | transition: transform $transition-very-fast $ease-in-out-cubic, 34 | background $transition-very-fast $ease-in-out-cubic,; 35 | 36 | &:active, 37 | &:focus, 38 | &:hover { 39 | background: $black; 40 | transform: scaleX(1.5); 41 | } 42 | } 43 | 44 | input[type=range]:hover::-webkit-slider-thumb { 45 | background: $black; 46 | } 47 | 48 | input[type=range]:focus::-webkit-slider-runnable-track { 49 | background: $gray-lighter; 50 | } 51 | 52 | input[type=range]::-moz-range-track { 53 | width: 100%; 54 | height: 1rem; 55 | cursor: pointer; 56 | border: 0; 57 | background: $gray-lighter; 58 | box-shadow: inset 0 0.375rem 0 0 $white, inset 0 -0.375rem 0 0 $white; 59 | } 60 | 61 | input[type=range]::-moz-range-thumb { 62 | border: 0; 63 | height: 1rem; 64 | width: 0.25rem; 65 | background: $gray; 66 | cursor: pointer; 67 | } 68 | 69 | input[type=range]::-ms-track { 70 | width: 100%; 71 | height: 0.25rem; 72 | cursor: pointer; 73 | background: transparent; 74 | border-color: transparent; 75 | color: transparent; 76 | border: 0; 77 | } 78 | 79 | input[type=range]::-ms-fill-lower { 80 | background: $gray-lighter; 81 | } 82 | 83 | input[type=range]::-ms-fill-upper { 84 | background: $gray-lighter; 85 | } 86 | 87 | input[type=range]::-ms-thumb { 88 | border: 0; 89 | height: 2rem; 90 | width: 0.25rem; 91 | background: $gray; 92 | cursor: pointer; 93 | } 94 | 95 | input[type=range]:focus::-ms-fill-lower { 96 | background: $gray-lighter; 97 | } 98 | 99 | input[type=range]:focus::-ms-fill-upper { 100 | background: $gray-lighter; 101 | } 102 | -------------------------------------------------------------------------------- /src/utils/dom-el.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple jQuery-like DOM library 3 | * 4 | * @type {Object} 5 | */ 6 | const DomEl = { 7 | /** 8 | * Add a class to the wrapped DOM element. 9 | * 10 | * @param {string} className - Class name to be added. 11 | * @return {object} Self. 12 | */ 13 | addClass (className) { 14 | if (!this.hasClass(className)) { 15 | const space = this.node.className.length > 0 ? ' ' : ''; 16 | 17 | this.node.className += `${space}${className}`; 18 | } 19 | 20 | return this; 21 | }, 22 | 23 | /** 24 | * Dispatch a manuel DOM event from the wrapper DOM element. 25 | * 26 | * @param {string} eventName - Name of the event. 27 | * @param {string} eventType - Event type. 28 | * @param {boolean} blubbles - If `true` the event bubbles up the DOM tree. 29 | * @param {boolean} cancelable - If `true` the event is cancelable. 30 | * @return {object} Self. 31 | */ 32 | dispatch (eventName, eventType, blubbles, cancelable) { 33 | const event = document.createEvent(eventType || 'Event'); 34 | 35 | event.initEvent(eventName, blubbles || true, cancelable); 36 | 37 | this.node.dispatchEvent(event); 38 | 39 | return this; 40 | }, 41 | 42 | /** 43 | * Check of the wrapped DOM element has a class. 44 | * 45 | * @param {string} className - Class name to be checked. 46 | * @return {boolean} If `true` DOM element has `className`. 47 | */ 48 | hasClass (className, pos) { 49 | const re = new RegExp(`\\s?${className}\\s?`); 50 | 51 | const results = this.node.className.match(re); 52 | 53 | if (results) { 54 | return pos ? { index: results.index, match: results[0] } : true; 55 | } 56 | 57 | return pos ? -1 : false; 58 | }, 59 | 60 | node: undefined, 61 | 62 | /** 63 | * Remove a class from the wrapped DOM element. 64 | * 65 | * @param {string} className - Class name to be removed. 66 | * @return {object} Self. 67 | */ 68 | removeClass (className) { 69 | const re = this.hasClass(className, true); 70 | 71 | if (re.index >= 0) { 72 | this.node.className = `${this.node.className.substr(0, re.index)} ${this.node.className.substr(re.index + re.match.length)}`.trim(); 73 | } 74 | 75 | return this; 76 | } 77 | }; 78 | 79 | export default function DomElFactory (el) { 80 | // This is the factory function's _constructor_ if you will 81 | 82 | const inst = Object.create(DomEl); 83 | inst.node = el; 84 | 85 | return inst; 86 | } 87 | -------------------------------------------------------------------------------- /src/assets/styles/axis.scss: -------------------------------------------------------------------------------- 1 | @import 'colors'; 2 | @import 'transitions'; 3 | 4 | .axis { 5 | position: absolute; 6 | color: $gray; 7 | font-size: 0.8rem; 8 | 9 | &.axis-x { 10 | left: 1rem; 11 | right: 0; 12 | height: 1rem; 13 | box-shadow: inset -3px 0 0 0 $gray-lighter; 14 | 15 | .axis-line { 16 | left: 0; 17 | width: 100%; 18 | height: 1px; 19 | } 20 | 21 | .axis-label-min { 22 | margin-left: 0.25rem; 23 | } 24 | 25 | .axis-label-max { 26 | margin-right: 0.25rem; 27 | } 28 | } 29 | 30 | &.axis-y { 31 | top: 1rem; 32 | bottom: 0.5rem; 33 | width: 1rem; 34 | box-shadow: inset 0 -3px 0 0 $gray-lighter; 35 | 36 | .axis-line { 37 | top: 0; 38 | width: 1px; 39 | height: 100%; 40 | } 41 | } 42 | 43 | &.axis-top { 44 | top: 0; 45 | 46 | .axis-line { 47 | bottom: 0; 48 | } 49 | } 50 | 51 | &.axis-bottom { 52 | bottom: 0; 53 | 54 | .axis-line { 55 | top: 0; 56 | } 57 | } 58 | 59 | &.axis-left { 60 | left: 0; 61 | 62 | .axis-line { 63 | right: 0; 64 | } 65 | 66 | .axis-labels { 67 | height: 100%; 68 | 69 | .axis-label span { 70 | position: absolute; 71 | } 72 | 73 | .axis-label-min span { 74 | bottom: -0.5rem; 75 | transform: rotate(-90deg); 76 | transform-origin: left top; 77 | } 78 | 79 | .axis-label-max span { 80 | top: 0.25rem; 81 | right: 1rem; 82 | transform: rotate(-90deg); 83 | transform-origin: right top; 84 | } 85 | 86 | .axis-label-name { 87 | margin-left: -1px; // To make the text crisp 88 | transform: rotate(-90deg); 89 | } 90 | } 91 | } 92 | 93 | &.axis-right { 94 | right: 0; 95 | 96 | .axis-line { 97 | left: 0; 98 | } 99 | } 100 | 101 | .axis-label-min, 102 | .axis-label-max, 103 | .axis-label-name { 104 | white-space: nowrap; 105 | } 106 | 107 | .axis-line { 108 | position: absolute; 109 | background: $gray-lighter; 110 | } 111 | 112 | .axis-arrow { 113 | right: 0; 114 | } 115 | } 116 | 117 | .axis-switch { 118 | position: absolute; 119 | z-index: 2; 120 | top: 0; 121 | left: 0; 122 | width: 1rem; 123 | height: 1rem; 124 | color: $gray; 125 | background: transparent; 126 | 127 | &:hover, 128 | &:active { 129 | color: $primary; 130 | cursor: pointer; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/components/higlass/higlass-reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import { 4 | SET_GRAYSCALE, 5 | SET_FRAGMENTS_HIGHLIGHT, 6 | SET_FRAGMENTS_SELECTION, 7 | SET_FRAGMENTS_SELECTION_FADE_OUT, 8 | SET_INTERACTIONS, 9 | SET_SELECTION_VIEW, 10 | UPDATE_HGL_CONFIG 11 | } from 'components/higlass/higlass-actions'; 12 | 13 | import { 14 | CONFIG, 15 | FRAGMENTS_HIGHLIGHT, 16 | FRAGMENTS_SELECTION, 17 | FRAGMENTS_SELECTION_FADE_OUT, 18 | GRAYSCALE, 19 | INTERACTIONS, 20 | SELECTION_VIEW 21 | } from 'components/higlass/higlass-defaults'; 22 | 23 | import deepClone from 'utils/deep-clone'; 24 | 25 | function config (state = CONFIG, action) { 26 | switch (action.type) { 27 | case UPDATE_HGL_CONFIG: 28 | if (action.payload.config) return deepClone(action.payload.config); 29 | return state; 30 | 31 | default: 32 | return state; 33 | } 34 | } 35 | 36 | function fragmentsHighlight (state = FRAGMENTS_HIGHLIGHT, action) { 37 | switch (action.type) { 38 | case SET_FRAGMENTS_HIGHLIGHT: 39 | return action.payload.highlight; 40 | 41 | default: 42 | return state; 43 | } 44 | } 45 | 46 | function fragmentsSelection (state = FRAGMENTS_SELECTION, action) { 47 | switch (action.type) { 48 | case SET_FRAGMENTS_SELECTION: 49 | return action.payload.selection; 50 | 51 | default: 52 | return state; 53 | } 54 | } 55 | 56 | function fragmentsSelectionFadeOut ( 57 | state = FRAGMENTS_SELECTION_FADE_OUT, action 58 | ) { 59 | switch (action.type) { 60 | case SET_FRAGMENTS_SELECTION_FADE_OUT: 61 | return action.payload.selectionFadeOut; 62 | 63 | default: 64 | return state; 65 | } 66 | } 67 | 68 | function grayscale (state = GRAYSCALE, action) { 69 | switch (action.type) { 70 | case SET_GRAYSCALE: 71 | return action.payload.grayscale; 72 | 73 | default: 74 | return state; 75 | } 76 | } 77 | 78 | function interactions (state = INTERACTIONS, action) { 79 | switch (action.type) { 80 | case SET_INTERACTIONS: 81 | return action.payload.interactions; 82 | 83 | default: 84 | return state; 85 | } 86 | } 87 | 88 | function selectionView (state = SELECTION_VIEW, action) { 89 | switch (action.type) { 90 | case SET_SELECTION_VIEW: 91 | return action.payload.domains.slice(); 92 | 93 | default: 94 | return state; 95 | } 96 | } 97 | 98 | export default combineReducers({ 99 | config, 100 | fragmentsHighlight, 101 | fragmentsSelection, 102 | fragmentsSelectionFadeOut, 103 | grayscale, 104 | interactions, 105 | selectionView 106 | }); 107 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 75 | -------------------------------------------------------------------------------- /src/components/fragments/fragments-state.js: -------------------------------------------------------------------------------- 1 | import { Scene } from 'three'; 2 | import { scaleLinear } from 'd3'; 3 | 4 | import { 5 | CELL_SIZE, 6 | COLOR_BW, 7 | COLOR_SCALE_FROM, 8 | COLOR_SCALE_TO, 9 | GRID_SIZE, 10 | LOG_TRANSFORM, 11 | MATRIX_ORIENTATION_INITIAL, 12 | MODE_AVERAGE 13 | } from 'components/fragments/fragments-defaults'; 14 | 15 | import deepClone from 'utils/deep-clone'; 16 | 17 | const DEFAULT_STATE = { 18 | annotations: {}, 19 | adjacentDistances: undefined, 20 | cellSize: CELL_SIZE, 21 | colorMap: COLOR_BW, 22 | colorScale: scaleLinear().range([COLOR_SCALE_FROM, COLOR_SCALE_TO]), 23 | colorScaleFrom: COLOR_SCALE_FROM, 24 | colorScaleTo: COLOR_SCALE_TO, 25 | gridSize: GRID_SIZE, 26 | colorsIdx: {}, 27 | coverDispMode: MODE_AVERAGE, 28 | dataMeasuresMax: {}, 29 | dataMeasuresMin: {}, 30 | dragActive: false, 31 | draggingMatrix: undefined, 32 | dragPile: undefined, 33 | font: undefined, 34 | graphMatrices: [], 35 | gridCellHeightInclSpacing: 0, 36 | gridCellHeightInclSpacingHalf: 0, 37 | gridCellWidthInclSpacing: 0, 38 | gridCellWidthInclSpacingHalf: 0, 39 | isHilbertCurve: false, 40 | hoveredCell: undefined, 41 | hoveredGapPile: undefined, 42 | hoveredMatrix: undefined, 43 | hoveredPile: undefined, 44 | hoveredTool: undefined, 45 | isLayout2d: false, 46 | lassoObject: undefined, 47 | logTransform: LOG_TRANSFORM, 48 | matrices: [], 49 | matricesIdx: {}, 50 | matricesPileIndex: [], 51 | matrixFrameEncoding: undefined, 52 | matrixGapMouseover: false, 53 | matrixOrientation: MATRIX_ORIENTATION_INITIAL, 54 | matrixPos: [], 55 | matrixStrings: '', 56 | matrixWidth: undefined, 57 | matrixWidthHalf: undefined, 58 | maxDistance: 0, 59 | measures: [], 60 | mouse: undefined, 61 | pileMeshes: [], 62 | pileMeshesTrash: [], 63 | piles: [], 64 | pilesIdx: {}, 65 | pilesInspection: [], 66 | pilesIdxInspection: {}, 67 | pilesTrash: [], 68 | previewScale: 1, 69 | previousHoveredPile: undefined, 70 | reject: {}, 71 | resolve: {}, 72 | scale: 1, 73 | selectedMatrices: [], 74 | selectedPile: undefined, 75 | strandArrows: [], 76 | strandArrowsTrash: [], 77 | trashIsActive: false, 78 | userSpecificCategories: [], 79 | workerClusterfck: undefined 80 | }; 81 | 82 | DEFAULT_STATE.isReady = new Promise((resolve, reject) => { 83 | DEFAULT_STATE.resolve.isReady = resolve; 84 | DEFAULT_STATE.reject.isReady = reject; 85 | }); 86 | 87 | 88 | class State { 89 | constructor () { 90 | this.reset(); 91 | } 92 | 93 | get () { 94 | return this.state; 95 | } 96 | 97 | reset () { 98 | this.state = deepClone(DEFAULT_STATE); 99 | this.state.scene = new Scene(); 100 | } 101 | } 102 | 103 | const state = new State(); 104 | 105 | export default state; 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HiPiler 2 | 3 | ![HiPiler's interface](teaser.png?raw=true) 4 | 5 | > An interactive web application for exploring and visualizing regions-of-interest in large genome interaction matrices. 6 | 7 | [![Build Status](https://img.shields.io/travis/flekschas/hipiler/master.svg?colorB=6357ff)](https://travis-ci.org/flekschas/hipiler) 8 | [![Demo](https://img.shields.io/badge/demo-running-red.svg?colorB=f25100)](http://hipiler.higlass.io) 9 | [![Video](https://img.shields.io/badge/video-awesome-red.svg?colorB=f25100)](https://youtu.be/qoLqje5OYKg) 10 | [![doi](https://img.shields.io/badge/doi-10.1109%2FTVCG.2017.2745978-red.svg?colorB=f25100)](https://doi.org/10.1109/TVCG.2017.2745978) 11 | 12 | ## Introduction 13 | 14 | HiPiler is an interactive web application for exploring and visualizing many regions-of-interest in large genome interaction matrices. Genome interaction matrices approximate the physical distance of pairs of genomic regions to each other and can contain up to 3 million rows and columns. Traditional matrix aggregation or pan-and-zoom interfaces largely fail in supporting search, inspection, and comparison of local regions-of-interest. HiPiler represents regions-of-interest as thumbnail-like snippets. Snippets can be laid out automatically based on their data and meta attributes. They are linked back to the matrix and can be explored interactively. 15 | 16 | ## Get Started 17 | 18 | **Live demo**: [http://hipiler.higlass.io](http://hipiler.higlass.io) 19 | 20 | **Video introduction**: [https://youtu.be/qoLqje5OYKg](https://youtu.be/qoLqje5OYKg) 21 | 22 | **Project information**: [http://hipiler.lekschas.de](http://hipiler.lekschas.de) 23 | 24 | **User documentation**: [http://hipiler.higlass.io/docs](http://hipiler.higlass.io/docs) 25 | 26 | ## Development 27 | 28 | #### Dependencies 29 | 30 | ```bash 31 | npm run update 32 | ``` 33 | 34 | #### Commands 35 | 36 | ```JavaScript 37 | npm start // Start dev server 38 | npm run build // Build JS bundles 39 | npm run build-app // Build entire app 40 | npm run update // Update to latest code 41 | ``` 42 | 43 | #### Config 44 | 45 | For custom settings copy `config.json`... 46 | 47 | ``` 48 | cp config.json config.local.json 49 | ``` 50 | 51 | ...and adjust `config.local.json` to your liking. 52 | 53 | Dynamic changes of the config are not supported yet. The dev server needs to be resarted to inline the new configuration. 54 | 55 | 56 | #### Browser Support 57 | 58 | HiPiler supports the latest version of Chrome and Firefox. Althought, almost everything works in Safari, t-SNE based clustering is broken. Once we fixed the bug Safari will be supported as well. 59 | 60 | _Note:_ Firefox by default starts searching on a website as soon as you start typing something. Unfortunately, this feature intereferes with keyboard shortcuts and can't be disabled with JS. If you want to use keyboard shortcuts in Firefox go to `Tools > Options > Advanced > General Tab` and disable `Search for text when I start typing`. 61 | -------------------------------------------------------------------------------- /src/utils/request-animation-frame.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Polyfill-safe method for requesting an animation frame 3 | * 4 | * @method requestAnimationFrame 5 | * @author Fritz Lekschas 6 | * @date 2016-09-12 7 | * @param {Function} callback Function to be called after a animation frame 8 | * has been delivered. 9 | * @return {Integer} ID of the request. 10 | */ 11 | export const requestAnimationFrame = (function () { 12 | let lastTime = 0; 13 | 14 | return window.requestAnimationFrame || 15 | window.webkitRequestAnimationFrame || 16 | window.mozRequestAnimationFrame || 17 | window.oRequestAnimationFrame || 18 | window.msRequestAnimationFrame || 19 | function (callback) { 20 | const currTime = new Date().getTime(); 21 | const timeToCall = Math.max(0, 16 - (currTime - lastTime)); 22 | const id = window.setTimeout(() => { 23 | callback(currTime + timeToCall); 24 | }, timeToCall); 25 | lastTime = currTime + timeToCall; 26 | return id; 27 | }; 28 | }()); 29 | 30 | /** 31 | * Polyfill-safe method for canceling a requested animation frame 32 | * 33 | * @method cancelAnimationFrame 34 | * @author Fritz Lekschas 35 | * @date 2016-09-12 36 | * @param {Integer} id ID of the animation frame request to be canceled. 37 | */ 38 | export const cancelAnimationFrame = (function () { 39 | return window.cancelAnimationFrame || 40 | window.webkitCancelAnimationFrame || 41 | window.mozCancelAnimationFrame || 42 | window.oCancelAnimationFrame || 43 | window.msCancelAnimationFrame || 44 | window.cancelAnimationFrame || 45 | window.webkitCancelAnimationFrame || 46 | window.mozCancelAnimationFrame || 47 | window.oCancelAnimationFrame || 48 | window.msCancelAnimationFrame || 49 | function (id) { window.clearTimeout(id); }; 50 | }()); 51 | 52 | /** 53 | * Requests the next animation frame. 54 | * 55 | * @method nextAnimationFrame 56 | * @author Fritz Lekschas 57 | * @date 2016-09-12 58 | * @return {Object} Object holding the _request_ and _cancel_ method for 59 | * requesting the next animation frame. 60 | */ 61 | const nextAnimationFrame = (function () { 62 | const ids = {}; 63 | 64 | function requestId () { 65 | let id; 66 | do { 67 | id = Math.floor(Math.random() * 1E9); 68 | } while (id in ids); 69 | return id; 70 | } 71 | 72 | return { 73 | request: window.requestNextAnimationFrame || function (callback, element) { 74 | const id = requestId(); 75 | 76 | ids[id] = requestAnimationFrame(() => { 77 | ids[id] = requestAnimationFrame((ts) => { 78 | delete ids[id]; 79 | callback(ts); 80 | }, element); 81 | }, element); 82 | 83 | return id; 84 | }, 85 | cancel: window.cancelNextAnimationFrame || function (id) { 86 | if (ids[id]) { 87 | cancelAnimationFrame(ids[id]); 88 | delete ids[id]; 89 | } 90 | } 91 | }; 92 | }()); 93 | 94 | export const requestNextAnimationFrame = nextAnimationFrame.request; 95 | export const cancelNextAnimationFrame = nextAnimationFrame.cancel; 96 | -------------------------------------------------------------------------------- /src/assets/styles/about.scss: -------------------------------------------------------------------------------- 1 | @import 'colors'; 2 | @import 'transitions'; 3 | @import 'mixins/ratio'; 4 | 5 | .about { 6 | .bg-video { 7 | // @include ratio(0.6); 8 | position: absolute; 9 | z-index: 0; 10 | top: 0; 11 | right: 0; 12 | left: 0; 13 | height: 80%; 14 | background: #fff; 15 | overflow: hidden; 16 | 17 | .fade-out { 18 | z-index: 1; 19 | } 20 | 21 | video { 22 | z-index: 0; 23 | /* Make video to at least 100% wide and tall */ 24 | min-width: 100%; 25 | min-height: 100%; 26 | 27 | /* Setting width & height to auto prevents the browser from stretching or squishing the video */ 28 | width: auto; 29 | height: auto; 30 | 31 | /* Center the video */ 32 | position: absolute; 33 | top: 50%; 34 | left: 50%; 35 | transform: translate(-50%,-50%); 36 | } 37 | } 38 | 39 | main { 40 | position: relative; 41 | z-index: 1; 42 | 43 | > *:last-child { 44 | margin-bottom: 4rem; 45 | } 46 | } 47 | 48 | main > p:first-child { 49 | margin: 4rem 4rem 2rem 4rem; 50 | color: $black; 51 | } 52 | 53 | h2 { 54 | margin: 2em 0 0.66em 0; 55 | border-bottom: 1px solid $gray-lightest; 56 | } 57 | 58 | .hidden-anchor svg-icon svg { 59 | width: 100%; 60 | height: 100%; 61 | } 62 | 63 | .publication-title, 64 | #author-list h3 { 65 | margin-bottom: 0.5rem; 66 | font-size: 1em; 67 | 68 | a { 69 | font-weight: 500; 70 | } 71 | } 72 | 73 | .publication-authors { 74 | margin-bottom: 0.5rem; 75 | } 76 | 77 | .publication-journal, 78 | #author-list ul { 79 | color: $gray; 80 | font-size: 1.125rem; 81 | } 82 | 83 | #author-list { 84 | h3 + p { 85 | margin-bottom: 0.25rem; 86 | } 87 | 88 | li { 89 | margin-right: 1rem; 90 | } 91 | } 92 | 93 | #youtube { 94 | @include ratio(.626); 95 | } 96 | 97 | #more-info { 98 | display: block; 99 | margin: 0.5em 0; 100 | padding: 0.25em 0; 101 | text-align: center; 102 | border-radius: 0.25rem; 103 | background: $gray-lighter; 104 | border-bottom: 0; 105 | transition: color $transition-very-fast $ease-in-out-cubic, 106 | background $transition-very-fast $ease-in-out-cubic; 107 | 108 | &:focus, 109 | &:hover { 110 | color: $black; 111 | background: $primary; 112 | } 113 | } 114 | 115 | .slides-container { 116 | @include ratio(.6); 117 | 118 | margin: 0.5rem; 119 | 120 | &:first-child { 121 | margin-left: 0; 122 | } 123 | 124 | &:last-child { 125 | margin-right: 0; 126 | } 127 | } 128 | 129 | .slides { 130 | position: absolute; 131 | top: 0; 132 | left: 0; 133 | width: 100%; 134 | height: 100%; 135 | border: 0; 136 | } 137 | 138 | .p-l { 139 | margin-left: 1rem; 140 | } 141 | } 142 | 143 | @media (min-width: 64rem) { // 1024px 144 | .about { 145 | main > p:first-child { 146 | margin-top: 12rem; 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/assets/styles/multi-select.scss: -------------------------------------------------------------------------------- 1 | @import 'colors'; 2 | @import 'transitions'; 3 | 4 | multi-select { 5 | position: relative; 6 | display: block; 7 | font-size: 0.8rem; 8 | 9 | input { 10 | margin-left: 0.25rem; 11 | width: 6rem; 12 | padding: 0.125rem 0; 13 | background: transparent; 14 | 15 | &:active, 16 | &:focus { 17 | outline: none; 18 | } 19 | } 20 | 21 | >div{ 22 | border-radius: 0 0 0.125rem 0.125rem; 23 | box-shadow: 0 0 0 1px transparentize($gray-lighter, 1); 24 | transition: box-shadow $transition-very-fast $ease-in-out-cubic; 25 | 26 | &.is-active { 27 | box-shadow: 0 0 0 1px $gray-lighter; 28 | } 29 | 30 | &.is-disabled { 31 | opacity: 0.5; 32 | } 33 | } 34 | 35 | >div.is-in-use { 36 | input { 37 | width: 2rem; 38 | } 39 | } 40 | 41 | .options { 42 | position: absolute; 43 | left: 0; 44 | right: 0; 45 | top: 0; 46 | margin: 0; 47 | height: 0; 48 | opacity: 0; 49 | background: $white; 50 | overflow: hidden; 51 | border-radius: 0.125rem 0.125rem 0 0; 52 | box-shadow: 0 0 0 1px transparentize($gray, 1); 53 | transition: opacity $transition-very-fast $ease-in-out-cubic, 54 | box-shadow $transition-very-fast $ease-in-out-cubic; 55 | 56 | &.bottom-up { 57 | top: auto; 58 | bottom: 0.3rem; 59 | } 60 | 61 | &.is-active { 62 | height: auto; 63 | opacity: 1; 64 | overflow: visible; 65 | } 66 | 67 | > li { 68 | padding-left: 0.25rem; 69 | line-height: 1.5rem; 70 | border-top: 1px solid $gray-lightest; 71 | 72 | &:first-child { 73 | border-top: 0; 74 | } 75 | 76 | &:hover { 77 | cursor: pointer; 78 | } 79 | 80 | &:hover, 81 | &.is-focus, 82 | &.is-selected { 83 | color: $black; 84 | border-top-color: $white; 85 | } 86 | 87 | &:hover, 88 | &.is-focus, 89 | &.is-focus.is-selected { 90 | background: $primary; 91 | } 92 | 93 | &.is-selected { 94 | font-weight: bold; 95 | background: $gray-lightest; 96 | 97 | &:after { 98 | content: '✓'; 99 | margin-left: 0.25rem; 100 | } 101 | } 102 | } 103 | } 104 | 105 | >div.is-active .options { 106 | box-shadow: 0 0 0 1px $gray-lighter; 107 | } 108 | 109 | .options-selected { 110 | margin: 0.2rem 0; 111 | line-height: 1.5rem; 112 | 113 | > li { 114 | margin-left: 0.25rem; 115 | border-radius: 0.125rem; 116 | background: $gray-lightest; 117 | } 118 | 119 | span { 120 | color: $gray-dark; 121 | padding-left: 0.25rem; 122 | } 123 | 124 | button { 125 | width: 1rem; 126 | color: $gray; 127 | font-size: 1rem; 128 | background: none; 129 | 130 | svg-icon, 131 | svg-icon > svg { 132 | width: 1rem; 133 | height: 1rem; 134 | } 135 | 136 | svg-icon > svg { 137 | padding: 0.25rem; 138 | } 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/components/chartlet/chartlet.js: -------------------------------------------------------------------------------- 1 | // Aurelia 2 | import { 3 | bindable, 4 | bindingMode, 5 | inject // eslint-disable-line 6 | } from 'aurelia-framework'; 7 | 8 | import { EventAggregator } from 'aurelia-event-aggregator'; // eslint-disable-line 9 | 10 | import { GRAY_DARK } from 'configs/colors'; 11 | 12 | import debounce from 'utils/debounce'; 13 | 14 | import { requestNextAnimationFrame } from 'utils/request-animation-frame'; 15 | 16 | import drawing from 'components/chartlet/chartlet-drawing'; 17 | 18 | const round = (num, dec = 2) => Number( 19 | `${Math.round(`${num}e+${dec}`)}e-${dec}` 20 | ); 21 | 22 | const scientificify = (num, dec = 3) => (num > 9999 ? 23 | num.toExponential(dec) : 24 | round(num, dec) 25 | ); 26 | 27 | 28 | @inject(EventAggregator) 29 | export class Chartlet { 30 | @bindable({ defaultBindingMode: bindingMode.oneWay }) axisX; // eslint-disable-line 31 | @bindable({ defaultBindingMode: bindingMode.oneWay }) axisY; // eslint-disable-line 32 | @bindable({ defaultBindingMode: bindingMode.oneWay }) data; // eslint-disable-line 33 | @bindable({ defaultBindingMode: bindingMode.oneWay }) opts; // eslint-disable-line 34 | @bindable({ defaultBindingMode: bindingMode.oneWay }) update; // eslint-disable-line 35 | @bindable({ defaultBindingMode: bindingMode.oneWay }) width; // eslint-disable-line 36 | @bindable({ defaultBindingMode: bindingMode.oneWay }) height; // eslint-disable-line 37 | @bindable({ defaultBindingMode: bindingMode.oneWay }) scientificify; // eslint-disable-line 38 | @bindable({ defaultBindingMode: bindingMode.oneWay }) numPrecision; // eslint-disable-line 39 | 40 | constructor (event) { 41 | this.color = GRAY_DARK; 42 | this.event = event; 43 | this.indices = []; 44 | this.renderDb = debounce(this.render.bind(this), 150); 45 | } 46 | 47 | attached () { 48 | this.render(); 49 | this.subscribeEventListeners(); 50 | } 51 | 52 | detached () { 53 | this.unsubscribeEventListeners(); 54 | } 55 | 56 | dataChanged (newData) { 57 | const fraction = newData.values.length / 5; 58 | 59 | this.indices = newData.values.length > 10 60 | ? Array.from(Array(6)).map((x, index) => parseInt(index * fraction, 10)) 61 | : Array.from(Array(newData.values.length)).map((x, index) => index); 62 | } 63 | 64 | render () { 65 | requestNextAnimationFrame(() => { 66 | if (!this.plotWrapperEl) return; 67 | 68 | this.width = this.plotWrapperEl.getBoundingClientRect().width; 69 | 70 | requestNextAnimationFrame(() => { 71 | this.range = drawing.render([this.plotEl], [[this.data.values]]).range; 72 | if (this.scientificify) { 73 | this.range = this.range.map( 74 | num => scientificify(num, this.numPrecision || 3) 75 | ); 76 | } 77 | }); 78 | }); 79 | } 80 | 81 | subscribeEventListeners () { 82 | this.subscriptions = []; 83 | this.update.forEach((eventName) => { 84 | this.subscriptions.push(this.event.subscribe(eventName, this.renderDb)); 85 | }); 86 | } 87 | 88 | unsubscribeEventListeners () { 89 | // Remove Aurelia event listeners 90 | this.subscriptions.forEach((subscription) => { 91 | subscription.dispose.call(this); 92 | }); 93 | this.subscriptions = []; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/components/fragments/pile-defaults.js: -------------------------------------------------------------------------------- 1 | import { 2 | MeshBasicMaterial, 3 | Sprite, 4 | SpriteMaterial, 5 | TextureLoader 6 | } from 'three'; 7 | 8 | export const ALPHA_FADED_OUT = 0.25; 9 | 10 | export const ARROW_BASE_OPACITY = 0.25; 11 | 12 | export const ARROW_MAP = new TextureLoader().load( 13 | 'assets/images/arrow.png' 14 | ); 15 | 16 | export const ARROW_X_MATERIAL = new SpriteMaterial({ 17 | map: ARROW_MAP, 18 | opacity: ARROW_BASE_OPACITY, 19 | transparent: true 20 | }); 21 | 22 | export const ARROW_X_MATERIAL_FADED_OUT = new SpriteMaterial({ 23 | map: ARROW_MAP, 24 | opacity: ARROW_BASE_OPACITY * ALPHA_FADED_OUT, 25 | transparent: true 26 | }); 27 | 28 | export const ARROW_X_MATERIAL_REV = new SpriteMaterial({ 29 | map: ARROW_MAP, 30 | opacity: ARROW_BASE_OPACITY, 31 | rotation: Math.PI, 32 | transparent: true 33 | }); 34 | 35 | export const ARROW_X_MATERIAL_REV_FADED_OUT = new SpriteMaterial({ 36 | map: ARROW_MAP, 37 | opacity: ARROW_BASE_OPACITY * ALPHA_FADED_OUT, 38 | rotation: Math.PI, 39 | transparent: true 40 | }); 41 | 42 | export const ARROW_X_MATERIALS = { 43 | NORM: ARROW_X_MATERIAL, 44 | NORM_FADED_OUT: ARROW_X_MATERIAL_FADED_OUT, 45 | REV: ARROW_X_MATERIAL_REV, 46 | REV_FADED_OUT: ARROW_X_MATERIAL_REV_FADED_OUT 47 | }; 48 | 49 | export const ARROW_Y_MATERIAL = new SpriteMaterial({ 50 | map: ARROW_MAP, 51 | opacity: ARROW_BASE_OPACITY, 52 | rotation: Math.PI * 0.5, 53 | transparent: true 54 | }); 55 | 56 | export const ARROW_Y_MATERIAL_FADED_OUT = new SpriteMaterial({ 57 | map: ARROW_MAP, 58 | opacity: ARROW_BASE_OPACITY * ALPHA_FADED_OUT, 59 | rotation: Math.PI * 0.5, 60 | transparent: true 61 | }); 62 | 63 | export const ARROW_Y_MATERIAL_REV = new SpriteMaterial({ 64 | map: ARROW_MAP, 65 | opacity: ARROW_BASE_OPACITY, 66 | rotation: Math.PI * 1.5, 67 | transparent: true 68 | }); 69 | 70 | export const ARROW_Y_MATERIAL_REV_FADED_OUT = new SpriteMaterial({ 71 | map: ARROW_MAP, 72 | opacity: ARROW_BASE_OPACITY * ALPHA_FADED_OUT, 73 | rotation: Math.PI * 1.5, 74 | transparent: true 75 | }); 76 | 77 | export const ARROW_Y_MATERIALS = { 78 | NORM: ARROW_Y_MATERIAL, 79 | NORM_FADED_OUT: ARROW_Y_MATERIAL_FADED_OUT, 80 | REV: ARROW_Y_MATERIAL_REV, 81 | REV_FADED_OUT: ARROW_Y_MATERIAL_REV_FADED_OUT 82 | }; 83 | 84 | export const ARROW_X = new Sprite(ARROW_X_MATERIAL); 85 | export const ARROW_X_REV = new Sprite(ARROW_X_MATERIAL_REV); 86 | 87 | export const ARROW_Y = new Sprite(ARROW_Y_MATERIAL); 88 | export const ARROW_Y_REV = new Sprite(ARROW_Y_MATERIAL_REV); 89 | 90 | ARROW_X.scale.set(16, 16, 1.0); 91 | ARROW_X_REV.scale.set(16, 16, 1.0); 92 | ARROW_Y.scale.set(16, 16, 1.0); 93 | ARROW_Y_REV.scale.set(16, 16, 1.0); 94 | 95 | export const BASE_MATERIAL = new MeshBasicMaterial({ 96 | color: 0xffffff, 97 | transparent: true 98 | }); 99 | 100 | export const COLOR_INDICATOR_HEIGHT = 4; 101 | 102 | export const LABEL_MIN_PILE_SIZE = 50; 103 | 104 | export const PREVIEW_LOW_QUAL_THRESHOLD = 0.5; 105 | 106 | export const PREVIEW_NUM_CLUSTERS = 8; 107 | 108 | export const PREVIEW_GAP_SIZE = 1; 109 | 110 | export const PREVIEW_SIZE = 2; 111 | 112 | export const VALUE_DOMAIN = [0, 1]; 113 | 114 | export const STD_MAX = (VALUE_DOMAIN[1] - VALUE_DOMAIN[0]) / 2; 115 | -------------------------------------------------------------------------------- /src/components/higlass/higlass.html: -------------------------------------------------------------------------------- 1 | 92 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HiPiler 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/views/configurator.html: -------------------------------------------------------------------------------- 1 | 71 | -------------------------------------------------------------------------------- /src/components/fragments/matrix.js: -------------------------------------------------------------------------------- 1 | export default class Matrix { 2 | constructor ( 3 | id, matrix, locus, dataset, zoomOutLevel, orientation, measures, categories 4 | ) { 5 | this.dim = matrix.length; 6 | this.id = id; 7 | this.dataset = dataset; 8 | this.locus = locus; 9 | this.matrix = Matrix.to1d(matrix); 10 | this.measures = measures; 11 | this.categories = categories; 12 | this.orientation = orientation; 13 | this.orientationX = Matrix.isCodingStrand(this.orientation.strand1) ? 14 | 1 : -1; 15 | this.orientationY = Matrix.isCodingStrand(this.orientation.strand2) ? 16 | 1 : -1; 17 | this.visible = true; 18 | this.zoomOutLevel = zoomOutLevel; 19 | } 20 | 21 | /** 22 | * Flip the raw matix's x and y axis in-place. 23 | */ 24 | flip () { 25 | Matrix.flip(this.matrix); 26 | this.orientationX *= -1; 27 | this.orientationY *= -1; 28 | } 29 | 30 | /** 31 | * Flip the raw 1D matix's x and y axis in-place. 32 | * 33 | * @param {array} matrix - 1D matrix to be flipped. 34 | */ 35 | static flip (matrix) { 36 | Matrix.flipX(matrix); 37 | Matrix.flipY(matrix); 38 | } 39 | 40 | /** 41 | * Flip the raw matix's x axis in-place. 42 | */ 43 | flipX () { 44 | Matrix.flipX(this.matrix); 45 | this.orientationX *= -1; 46 | } 47 | 48 | /** 49 | * Flip the raw 1D matix's x axis in-place. 50 | * 51 | * @param {array} matrix - 1D matrix to be flipped. 52 | */ 53 | static flipX (matrix) { 54 | const dims = Math.round(Math.sqrt(matrix.length)); 55 | 56 | for (let i = 0; i < dims; i++) { 57 | const pos = (i * dims); 58 | 59 | matrix.set(matrix.slice(pos, (i + 1) * dims).reverse(), pos); 60 | } 61 | } 62 | 63 | /** 64 | * Flip the raw matix's y axis in-place. 65 | */ 66 | flipY () { 67 | Matrix.flipY(this.matrix); 68 | this.orientationY *= -1; 69 | } 70 | 71 | /** 72 | * Flip the raw 1D matix's y axis in-place. 73 | * 74 | * @param {array} matrix - 1D-array Matrix to be flipped. 75 | */ 76 | static flipY (matrix) { 77 | const dims = Math.round(Math.sqrt(matrix.length)); 78 | const flippedMatrix = new Float32Array(matrix.length); 79 | 80 | for (let i = 0; i < dims; i++) { 81 | flippedMatrix.set( 82 | matrix.slice(i * dims, (i + 1) * dims), 83 | (dims - 1 - i) * dims 84 | ); 85 | } 86 | 87 | matrix.set(flippedMatrix); 88 | } 89 | 90 | /** 91 | * Check if orientation is coding or non-coding. 92 | * 93 | * @param {string} orientation - Orientation string to be checked. 94 | * @return {Boolean} If `true` strand is coding. 95 | */ 96 | static isCodingStrand (orientation) { 97 | switch (orientation) { 98 | case '-': 99 | case 'minus': 100 | case 'noncoding': 101 | case 'non-coding': 102 | return false; 103 | 104 | default: 105 | return true; 106 | } 107 | } 108 | 109 | /** 110 | * Orient matrix to either 5' > 3' or 3' > 5'. 111 | * 112 | * @param {boolean} reverse - If `true` orient matrix in 3' to 5' direction. 113 | */ 114 | orient5To3 (reverse) { 115 | if (reverse) { 116 | if (this.orientationX === 1) { 117 | this.flipX(); 118 | } 119 | if (this.orientationY === 1) { 120 | this.flipY(); 121 | } 122 | } else { 123 | if (this.orientationX === -1) { 124 | this.flipX(); 125 | } 126 | if (this.orientationY === -1) { 127 | this.flipY(); 128 | } 129 | } 130 | } 131 | 132 | /** 133 | * Convert a 2D classic array into a 1D Float32Array. 134 | * 135 | * @param {array} matrix2d - 2D array to be converted into 1D. 136 | * @return {array} 1D Float32Array. 137 | */ 138 | static to1d (matrix2d) { 139 | return Float32Array.from( 140 | matrix2d.reduce((matrix1d, row) => matrix1d.concat(row), []) 141 | ); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hipiler", 3 | "description": "Visual exploration of large genome interaction matrices with interactive small multiples.", 4 | "author": "Fritz Lekschas (https://lekschas.de)", 5 | "version": "1.4.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/flekschas/hipiler.git" 9 | }, 10 | "license": "MIT", 11 | "dependencies": { 12 | "aurelia-animator-css": "1.0.4", 13 | "aurelia-bootstrapper": "2.2.0", 14 | "bluebird": "3.5.1", 15 | "chroma-js": "1.3.7", 16 | "d3": "4.13.0", 17 | "gulp-marked": "flekschas/gulp-marked", 18 | "higlass": "1.7.2", 19 | "hull.js": "0.2.10", 20 | "javascript-detect-element-resize": "0.5.3", 21 | "localforage": "1.7.1", 22 | "marked": "0.3.19", 23 | "normalize-wheel": "1.0.1", 24 | "papaparse": "4.4.0", 25 | "pixi.js": "5.2.0", 26 | "react": "16.6.3", 27 | "react-bootstrap": "0.32.1", 28 | "react-dom": "16.6.3", 29 | "redux": "3.7.2", 30 | "redux-batched-actions": "0.3.0", 31 | "redux-persist": "4.10.2", 32 | "redux-thunk": "2.2.0", 33 | "redux-undo": "1.0.0-beta9-9-7", 34 | "requirejs": "2.3.5", 35 | "tayden-clusterfck": "0.7.0", 36 | "text": "github:requirejs/text#latest", 37 | "three": "0.92.0", 38 | "three-line-2d": "1.1.6", 39 | "three-orbit-controls": "82.1.0", 40 | "tsne-js": "1.0.3" 41 | }, 42 | "peerDependencies": {}, 43 | "devDependencies": { 44 | "aurelia-cli": "0.35.1", 45 | "aurelia-testing": "1.0.0-beta.4.0.0", 46 | "aurelia-tools": "2.0.0", 47 | "aurelia-validation": "1.1.3", 48 | "babel-core": "6.26.3", 49 | "babel-eslint": "8.2.3", 50 | "babel-plugin-syntax-flow": "6.18.0", 51 | "babel-plugin-transform-decorators-legacy": "1.3.4", 52 | "babel-plugin-transform-es2015-modules-amd": "6.24.1", 53 | "babel-plugin-transform-es2015-modules-commonjs": "6.26.2", 54 | "babel-plugin-transform-flow-strip-types": "6.22.0", 55 | "babel-polyfill": "6.26.0", 56 | "babel-preset-env": "1.6.1", 57 | "babel-preset-stage-1": "6.24.1", 58 | "babel-register": "6.26.0", 59 | "browser-sync": "2.24.4", 60 | "connect-history-api-fallback": "1.5.0", 61 | "eslint": "4.19.1", 62 | "eslint-config-airbnb": "16.1.0", 63 | "eslint-plugin-import": "2.11.0", 64 | "eslint-plugin-jsx-a11y": "6.0.3", 65 | "eslint-plugin-react": "7.7.0", 66 | "gulp": "github:gulpjs/gulp#4.0", 67 | "gulp-babel": "7.0.0", 68 | "gulp-changed-in-place": "2.3.0", 69 | "gulp-clean": "0.4.0", 70 | "gulp-concat": "2.6.1", 71 | "gulp-eslint": "4.0.2", 72 | "gulp-htmlmin": "4.0.0", 73 | "gulp-if": "2.0.2", 74 | "gulp-modify": "0.1.1", 75 | "gulp-notify": "3.2.0", 76 | "gulp-order": "1.1.1", 77 | "gulp-plumber": "1.2.0", 78 | "gulp-rename": "1.2.2", 79 | "gulp-sass": "4.0.1", 80 | "gulp-sourcemaps": "2.6.4", 81 | "gulp-wrap": "0.13.0", 82 | "html-minifier": "3.5.15", 83 | "jasmine-core": "2.8.0", 84 | "karma": "1.7.0", 85 | "karma-babel-preprocessor": "6.0.1", 86 | "karma-chrome-launcher": "2.2.0", 87 | "karma-jasmine": "1.1.0", 88 | "merge-stream": "1.0.1", 89 | "minimatch": "3.0.4", 90 | "redux-freeze": "0.1.5", 91 | "through2": "2.0.3", 92 | "uglify-js": "3.4.0", 93 | "vinyl-fs": "2.4.4" 94 | }, 95 | "scripts": { 96 | "build": "npm run update; gulp clean; gulp; au build --env prod; gulp index", 97 | "lint": "eslint src", 98 | "prepublishOnly": "npm run lint", 99 | "prerelease": "npm run update; gulp clean; gulp clean-dist; gulp --build; au build --env prod; gulp build; cd build; zip -r ../build.zip ./*", 100 | "ghp": "npm run update; gulp clean; gulp clean-dist; gulp --ghp; au build --env prod; gulp ghp; scripts/cp-prod.sh hipiler-ghp", 101 | "ghp-no-update": "gulp clean; gulp clean-dist; gulp --ghp; au build --env prod; gulp ghp; scripts/cp-prod.sh hipiler-ghp", 102 | "start": "gulp; au run --watch", 103 | "update": "git pull; git submodule update --init --recursive --remote --merge; npm install; gulp" 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/assets/styles/pile-details.scss: -------------------------------------------------------------------------------- 1 | @import 'colors'; 2 | @import 'transitions'; 3 | @import 'mixins/ratio'; 4 | 5 | pile-details { 6 | display: block; 7 | margin: 2rem 0 0 0.5rem; 8 | 9 | .fragment-plot { 10 | &.is-piles-inspection { 11 | box-shadow: inset 0 0 0 0.25rem lighten($primary, 40%); 12 | } 13 | } 14 | 15 | .no-pile-selected { 16 | z-index: 10; 17 | color: $gray; 18 | background: $gray-lightest; 19 | } 20 | 21 | .collapsed { 22 | z-index: 10; 23 | 24 | p { 25 | margin: 0; 26 | color: $gray-lighter; 27 | font-size: 1.125rem; 28 | text-transform: uppercase; 29 | white-space: nowrap; 30 | transform: rotate(90deg); 31 | } 32 | } 33 | 34 | h5, 35 | h5 label { 36 | color: $gray-darker; 37 | font-size: 1rem; 38 | text-transform: none; 39 | } 40 | 41 | h5 { 42 | margin: 0 0 0.5rem 0; 43 | } 44 | 45 | h5 svg-icon { 46 | width: 1rem; 47 | height: 1rem; 48 | margin-right: 0.25rem; 49 | } 50 | 51 | label { 52 | color: $gray; 53 | font-size: 0.8rem; 54 | text-transform: uppercase; 55 | } 56 | 57 | textarea { 58 | font-size: 0.85rem; 59 | line-height: 1.25em; 60 | max-height: 14em; 61 | } 62 | 63 | .content { 64 | opacity: 0; 65 | transition: opacity $transition-very-fast $ease-in-out-cubic; 66 | 67 | &.is-shown { 68 | opacity: 1; 69 | 70 | // &.is-active { 71 | // opacity: 1; 72 | // } 73 | } 74 | } 75 | 76 | .chartlet-axis-x, 77 | .chartlet-axis-x li, 78 | .chartlet-axis-y { 79 | font-size: 0.7rem; 80 | font-weight: 300; 81 | color: $gray; 82 | } 83 | 84 | .chartlet-axis-x { 85 | margin: 0; 86 | 87 | li { 88 | text-align: center; 89 | } 90 | } 91 | 92 | .chartlet-axis-y { 93 | width: 34%; 94 | margin: 0 0 0.7rem 0.15rem; 95 | } 96 | 97 | .chartlet-plot-wrapper { 98 | width: 66%; 99 | } 100 | 101 | .chartlet-axis-y-bar { 102 | width: 0.2rem; 103 | margin: 0 0.1rem; 104 | border-top: 1px solid $gray-lightest; 105 | border-left: 1px solid $gray-lightest; 106 | border-bottom: 1px solid $gray-lightest; 107 | } 108 | 109 | .one-liner { 110 | p { 111 | margin: 0 0 0 0.5em; 112 | font-size: 0.8rem; 113 | } 114 | } 115 | 116 | .multi-line label { 117 | margin-bottom: 0.25rem; 118 | } 119 | 120 | .entry { 121 | margin-bottom: 0.25rem; 122 | 123 | &.multi-line { 124 | margin-bottom: 0.33rem; 125 | } 126 | } 127 | 128 | .max-three-liner { 129 | overflow: auto; 130 | max-height: 3rem; 131 | } 132 | 133 | .separator { 134 | margin: 0.5rem 0; 135 | height: 1px; 136 | background: $gray-lightest; 137 | } 138 | 139 | .entry + .separator { 140 | margin-top: 0.25rem; 141 | } 142 | 143 | #pile-preview { 144 | .wrapper { 145 | position: relative; 146 | max-width: 20rem; 147 | border-radius: 0.25rem; 148 | 149 | .wrapper-previews, 150 | .wrapper-snippet { 151 | position: relative; 152 | width: 100%; 153 | } 154 | 155 | .wrapper-previews { 156 | padding-top: 0; 157 | } 158 | 159 | .wrapper-snippet { 160 | padding-top: 100%; 161 | } 162 | 163 | .wrapper-border { 164 | z-index: 2; 165 | border: 1px solid $gray-lightest; 166 | border-radius: 0.25rem; 167 | cursor: pointer; 168 | transition: border-color $transition-very-fast $ease-in-out-cubic, 169 | border-width $transition-very-fast $ease-in-out-cubic; 170 | 171 | &:active, 172 | &.is-active { 173 | border-color: $primary; 174 | border-width: 2px; 175 | } 176 | } 177 | } 178 | 179 | canvas { 180 | z-index: 1; 181 | width: 100%; 182 | height: 100%; 183 | image-rendering: optimizeSpeed; 184 | image-rendering: -moz-crisp-edges; 185 | image-rendering: -webkit-optimize-contrast; 186 | image-rendering: optimize-contrast; 187 | image-rendering: pixelated; 188 | -ms-interpolation-mode: nearest-neighbor; 189 | border-radius: 0.25rem; 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/assets/styles/home.scss: -------------------------------------------------------------------------------- 1 | @import 'colors'; 2 | @import 'transitions'; 3 | 4 | .home { 5 | .bg { 6 | position: fixed; 7 | background-image: url('assets/images/home-bg.png'); 8 | background-size: cover; 9 | z-index: -1; 10 | } 11 | 12 | main, 13 | footer-main { 14 | z-index: 1; 15 | } 16 | 17 | footer-main { 18 | margin: 0 -0.5rem -0.5rem -0.5rem; 19 | 20 | .bottom-bar { 21 | height: 3rem; 22 | padding: 0 0.5rem 0.5rem 0.5rem; 23 | border-top-color: rgba(0,0,0,0.1); 24 | background: transparentize($white, 0.25); 25 | } 26 | } 27 | 28 | .wrap-extra { 29 | max-width: 45rem; 30 | margin: 0 auto; 31 | } 32 | 33 | h2 { 34 | margin-top: 1rem; 35 | font-size: 3rem; 36 | line-height: 1.25em; 37 | } 38 | 39 | h3, 40 | h4 { 41 | margin: 2rem 0 1rem 0; 42 | color: $gray-dark; 43 | text-transform: uppercase; 44 | } 45 | 46 | h3 { 47 | font-size: 1.25rem; 48 | } 49 | 50 | h4 { 51 | font-size: 0.9em; 52 | color: $gray; 53 | } 54 | 55 | .text { 56 | text-align: justify; 57 | } 58 | 59 | .lighter li { 60 | color: $gray; 61 | } 62 | 63 | .date { 64 | margin-left: 0.25rem; 65 | font-size: 0.9em; 66 | color: $gray; 67 | } 68 | 69 | .separator { 70 | margin: 3rem 0; 71 | 72 | &:after { 73 | content: ''; 74 | display: block; 75 | width: 50%; 76 | height: 3px; 77 | border-radius: 10px; 78 | background: $gray-lightest; 79 | } 80 | } 81 | 82 | .columns > * { 83 | margin: 0 1rem; 84 | 85 | &:first-child { 86 | margin-left: 0; 87 | } 88 | 89 | &:last-child { 90 | margin-right: 0; 91 | } 92 | } 93 | 94 | p { 95 | margin-bottom: 0; 96 | } 97 | 98 | .box { 99 | position: relative; 100 | margin: 0.5rem 0; 101 | padding: 1rem; 102 | font-size: 1rem; 103 | border-radius: 0.25rem; 104 | background: $gray-lightest; 105 | 106 | &:first-child { 107 | margin-top: 1rem; 108 | } 109 | } 110 | 111 | .box-drop { 112 | &:after { 113 | position: absolute; 114 | content: ''; 115 | top: 0.25rem; 116 | right: 0.25rem; 117 | bottom: 0.25rem; 118 | left: 0.25rem; 119 | border: 2px dashed $white; 120 | border-radius: 0.125rem; 121 | } 122 | } 123 | 124 | .box-select { 125 | transition: background $transition-very-fast $ease-in-out-cubic, 126 | color $transition-very-fast $ease-in-out-cubic; 127 | 128 | &:hover { 129 | color: $primary; 130 | cursor: pointer; 131 | background: lighten($primary, 40%); 132 | } 133 | 134 | svg, 135 | svg-icon { 136 | width: 1.25rem; 137 | height: 1.25rem; 138 | color: $white; 139 | } 140 | 141 | input { 142 | display: none; 143 | } 144 | } 145 | 146 | .box-demo { 147 | ul { 148 | margin: 0.5rem 0 0 0; 149 | } 150 | 151 | li { 152 | padding: 0.5rem; 153 | transition: background $transition-very-fast $ease-in-out-cubic, 154 | box-shadow $transition-very-fast $ease-in-out-cubic, 155 | color $transition-very-fast $ease-in-out-cubic; 156 | 157 | &:nth-child(odd) { 158 | background: lighten($gray-lightest, 5%); 159 | 160 | &:hover { 161 | background: lighten($primary, 40%); 162 | } 163 | } 164 | 165 | &:hover { 166 | color: $primary; 167 | cursor: pointer; 168 | background: lighten($primary, 40%); 169 | box-shadow: -2px 0 0 0 $primary; 170 | } 171 | 172 | &:hover, 173 | &:hover .demo-data, 174 | &:hover .demo-title { 175 | color: $primary; 176 | } 177 | } 178 | 179 | .demo-title { 180 | margin-bottom: 0.25em; 181 | } 182 | 183 | .demo-data { 184 | font-size: 0.85em; 185 | color: $gray; 186 | 187 | svg { 188 | width: 1em; 189 | height: 1em; 190 | margin-right: 0.25em; 191 | } 192 | } 193 | } 194 | 195 | .data-links { 196 | color: $gray; 197 | 198 | a { 199 | font-size: 0.8em; 200 | vertical-align: top; 201 | text-transform: uppercase; 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/components/range-select/range-select.js: -------------------------------------------------------------------------------- 1 | // Aurelia 2 | import { 3 | bindable, 4 | bindingMode, 5 | inject // eslint-disable-line 6 | } from 'aurelia-framework'; 7 | import { EventAggregator } from 'aurelia-event-aggregator'; // eslint-disable-line 8 | 9 | // Injectables 10 | import States from 'services/states'; // eslint-disable-line 11 | 12 | import { EVENT_BASE_NAME } from 'components/range-select/range-select-defaults'; 13 | 14 | 15 | @inject(EventAggregator, States) 16 | export class RangeSelect { 17 | @bindable({ defaultBindingMode: bindingMode.oneWay }) eventId; // eslint-disable-line 18 | @bindable({ defaultBindingMode: bindingMode.oneWay }) from = 0; // eslint-disable-line 19 | @bindable({ defaultBindingMode: bindingMode.oneWay }) to = 1; // eslint-disable-line 20 | @bindable({ defaultBindingMode: bindingMode.oneWay }) selected = [0, 1]; // eslint-disable-line 21 | 22 | constructor (eventAggregator, states) { 23 | this.event = eventAggregator; 24 | 25 | this.store = states.store; 26 | this.unsubscribeStore = this.store.subscribe(this.update.bind(this)); 27 | 28 | this.subscriptions = []; 29 | 30 | this.selectFrom = this.selected[0]; 31 | this.selectTo = this.selected[1]; 32 | 33 | this.update(); 34 | } 35 | 36 | /* ----------------------- Aurelia-specific methods ----------------------- */ 37 | 38 | /** 39 | * Called once the component is detached. 40 | */ 41 | detached () { 42 | // Unsubscribe from redux store 43 | this.unsubscribeStore(); 44 | 45 | // Unsubscribe from Aurelia events 46 | this.subscriptions.forEach((subscription) => { 47 | subscription.dispose(); 48 | }); 49 | this.subscriptions = undefined; 50 | } 51 | 52 | fromChanged () { 53 | this.update(); 54 | } 55 | 56 | toChanged () { 57 | this.update(); 58 | } 59 | 60 | selectedChanged (newVal, oldVal) { 61 | const changed = ( 62 | newVal[0] !== this.selectFrom 63 | || newVal[1] !== this.selectTo 64 | ); 65 | 66 | if (!changed) return; 67 | 68 | this.selectFrom = newVal[0]; 69 | this.selectTo = newVal[1]; 70 | 71 | this.update(); 72 | } 73 | 74 | /* ---------------------------- Class methods ----------------------------- */ 75 | 76 | mouseDownHandler (event, selector) { 77 | this.mouseX = event.clientX; 78 | this.activeSelector = selector; 79 | this.range = this.baseEl.getBoundingClientRect(); 80 | 81 | this.mouseMoveListener = this.event.subscribe( 82 | 'app.mouseMove', this.mouseMoveHandler.bind(this) 83 | ); 84 | this.event.subscribeOnce( 85 | 'app.mouseUp', this.mouseUpHandler.bind(this) 86 | ); 87 | } 88 | 89 | mouseUpHandler (event) { 90 | this.mouseX = undefined; 91 | 92 | if (this.mouseMoveListener) { 93 | this.mouseMoveListener.dispose(); 94 | } 95 | 96 | this.mouseMoveListener = undefined; 97 | this.activeSelector = undefined; 98 | 99 | this.publish(); 100 | } 101 | 102 | mouseMoveHandler (event) { 103 | if (this.mouseX) { 104 | const isLeft = this.activeSelector === 'left'; 105 | const newX = Math.min( 106 | isLeft ? this.selectTo - 0.01 : 1, 107 | Math.max( 108 | isLeft ? 0 : this.selectFrom + 0.01, 109 | (event.clientX - this.range.left) / this.range.width 110 | ) 111 | ); 112 | 113 | if (this.activeSelector === 'left') { 114 | this.selectFrom = newX; 115 | } else { 116 | this.selectTo = newX; 117 | } 118 | 119 | this.update(); 120 | this.publish(); 121 | } 122 | } 123 | 124 | publish () { 125 | if (this.eventId) { 126 | this.event.publish( 127 | `${EVENT_BASE_NAME}.${this.eventId}`, 128 | { 129 | from: this.selectFrom, 130 | to: this.selectTo, 131 | final: !this.mouseX 132 | } 133 | ); 134 | } 135 | } 136 | 137 | update () { 138 | this.rangeSelectorLeftCss = { 139 | left: `${this.selectFrom * 100}%` 140 | }; 141 | this.rangeSelectorRightCss = { 142 | left: `${this.selectTo * 100}%` 143 | }; 144 | this.selectedRangeCss = { 145 | left: `${this.selectFrom * 100}%`, 146 | right: `${(1 - this.selectTo) * 100}%` 147 | }; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/views/explore.html: -------------------------------------------------------------------------------- 1 | 111 | -------------------------------------------------------------------------------- /src/views/home.js: -------------------------------------------------------------------------------- 1 | // Aurelia 2 | import { 3 | inject, // eslint-disable-line 4 | LogManager 5 | } from 'aurelia-framework'; // eslint-disable-line 6 | import { Router } from 'aurelia-router'; // eslint-disable-line 7 | import { EventAggregator } from 'aurelia-event-aggregator'; // eslint-disable-line 8 | 9 | // Third party 10 | import { json } from 'd3'; 11 | import * as Papa from 'papaparse'; 12 | 13 | // Injectable 14 | import States from 'services/states'; // eslint-disable-line 15 | 16 | // Utils 17 | import examples from 'configs/examples'; 18 | import { updateConfigs } from 'app-actions'; 19 | import buildConfig from 'utils/build-config'; 20 | import getUrlQueryParams from 'utils/get-url-query-params'; 21 | import readJsonFile from 'utils/read-json-file'; 22 | import validateConfig from 'utils/validate-config'; 23 | 24 | const logger = LogManager.getLogger('home'); 25 | 26 | 27 | @inject(EventAggregator, Router, States) 28 | export class Home { 29 | constructor (event, router, states) { 30 | this.event = event; 31 | 32 | this.router = router; 33 | 34 | this.store = states.store; 35 | 36 | this.examples = examples; 37 | } 38 | 39 | /* ----------------------- Aurelia-specific methods ----------------------- */ 40 | 41 | /** 42 | * Called once the component is attached. 43 | */ 44 | attached (...args) { 45 | this.selectConfigButton.addEventListener( 46 | 'click', this.selectConfig.bind(this) 47 | ); 48 | 49 | if (this.queryParams.config) { 50 | this.loadExample({ url: this.queryParams.config }); 51 | } 52 | } 53 | 54 | activate (params, routeConfig, navigationInstruction) { 55 | this.queryParams = navigationInstruction.queryParams; 56 | const queryParams = getUrlQueryParams(window.location.search); 57 | 58 | if ( 59 | Object.keys(navigationInstruction.queryParams).length < 60 | Object.keys(queryParams).length 61 | ) { 62 | this.queryParams = queryParams; 63 | } 64 | } 65 | 66 | /* ---------------------------- Class methods ----------------------------- */ 67 | 68 | loadExample (example) { 69 | json(example.url, (error, config) => { 70 | if (error) { 71 | logger.error(error); 72 | this.event.publish( 73 | 'showGlobalError', ['Could not load example config', 5000] 74 | ); 75 | return; 76 | } 77 | 78 | this.setConfig(config); 79 | }); 80 | } 81 | 82 | selectedConfigChanged () { 83 | switch (this.selectedConfig[0].type) { 84 | case 'text/csv': 85 | case 'text/tab-separated-values': 86 | Papa.parse(this.selectedConfig[0], { 87 | complete: (results) => { 88 | const newConfig = buildConfig(results.data); 89 | 90 | if (newConfig) { 91 | this.setConfig(newConfig); 92 | } else { 93 | this.event.publish( 94 | 'showGlobalError', 95 | ['Invalid CSV or TSV file'] 96 | ); 97 | } 98 | }, 99 | error: (error) => { 100 | logger.info(error); 101 | this.event.publish( 102 | 'showGlobalError', 103 | ['Invalid CSV or TSV file'] 104 | ); 105 | } 106 | }); 107 | break; 108 | 109 | case 'application/json': 110 | readJsonFile(this.selectedConfig[0]) 111 | .then(config => this.setConfig(config)) 112 | .catch((error) => { 113 | logger.info(error); 114 | this.event.publish( 115 | 'showGlobalError', 116 | ['Invalid JSON file'] 117 | ); 118 | }); 119 | break; 120 | 121 | default: 122 | this.event.publish( 123 | 'showGlobalError', 124 | ['Unsupported file type. Drop a CSV, TSV, or JSON!'] 125 | ); 126 | break; 127 | } 128 | } 129 | 130 | selectConfig (event) { 131 | if (event.artificial) { return; } 132 | 133 | const newEvent = new MouseEvent(event.type, event); 134 | newEvent.artificial = true; 135 | 136 | this.inputConfigFile.dispatchEvent(newEvent); 137 | } 138 | 139 | setConfig (config) { 140 | if (validateConfig(config.fgm, config.hgl)) { 141 | this.store.dispatch(updateConfigs(config)); 142 | this.router.navigateToRoute('explore', this.queryParams); 143 | } else { 144 | this.event.publish( 145 | 'showGlobalError', ['Corrupted config file', 3000] 146 | ); 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/components/fragments/pile-menu.js: -------------------------------------------------------------------------------- 1 | // Aurelia 2 | import { 3 | bindable, 4 | bindingMode, 5 | inject, // eslint-disable-line 6 | LogManager 7 | } from 'aurelia-framework'; 8 | 9 | import { EventAggregator } from 'aurelia-event-aggregator'; // eslint-disable-line 10 | 11 | import commands from 'components/fragments/pile-menu-commands'; 12 | 13 | import FgmState from 'components/fragments/fragments-state'; 14 | 15 | import { 16 | MODE_AVERAGE, 17 | MODE_VARIANCE 18 | } from 'components/fragments/fragments-defaults'; 19 | 20 | 21 | let fgmState = FgmState.get(); 22 | const logger = LogManager.getLogger('pile-menu'); 23 | 24 | @inject(EventAggregator) 25 | export class PileMenu { 26 | @bindable({ defaultBindingMode: bindingMode.oneWay }) isActive = false; // eslint-disable-line 27 | @bindable({ defaultBindingMode: bindingMode.oneWay }) isAlignLeft = false; // eslint-disable-line 28 | @bindable({ defaultBindingMode: bindingMode.oneWay }) isBottomUp = false; // eslint-disable-line 29 | @bindable({ defaultBindingMode: bindingMode.oneWay }) pile; // eslint-disable-line 30 | @bindable({ defaultBindingMode: bindingMode.oneWay }) position = {}; // eslint-disable-line 31 | 32 | 33 | /* ----------------------- Aurelia-specific methods ----------------------- */ 34 | 35 | /** 36 | * Called once the component is atached. 37 | */ 38 | attached () { 39 | fgmState = FgmState.get(); 40 | 41 | this.subscriptions = []; 42 | this.subscribeEventListeners(); 43 | } 44 | 45 | /** 46 | * Called once the component is detached. 47 | */ 48 | detached () { 49 | this.unsubscribeEventListeners(); 50 | } 51 | 52 | positionChanged () { 53 | this.setCss(); 54 | } 55 | 56 | constructor (event) { 57 | this.commands = commands; 58 | this.css = {}; 59 | this.event = event; 60 | 61 | this.setCss(); 62 | } 63 | 64 | pileChanged () { 65 | this.updateMenu(); 66 | } 67 | 68 | 69 | /* ---------------------------- Custom Methods ---------------------------- */ 70 | 71 | isActiveCmd (command) { 72 | if (!this.pile) { 73 | return false; 74 | } 75 | 76 | if (command.isActiveAvgCover && this.pile.coverDispMode === MODE_AVERAGE) { 77 | return true; 78 | } 79 | 80 | if (command.isActiveVarCover && this.pile.coverDispMode === MODE_VARIANCE) { 81 | return true; 82 | } 83 | 84 | return false; 85 | } 86 | 87 | isVisibleCmd (command) { 88 | if (!this.pile) { 89 | return false; 90 | } 91 | 92 | if (command.isColoredOnly && !this.pile.isColored) { 93 | return false; 94 | } 95 | 96 | if (command.stackedPileOnly && this.pile.pileMatrices.length < 2) { 97 | return false; 98 | } 99 | 100 | if (command.trashedOnly && !this.pile.isTrashed) { 101 | return false; 102 | } 103 | 104 | if (command.notInTrash && this.pile.isTrashed) { 105 | return false; 106 | } 107 | 108 | if (command.inspectionOnly && !fgmState.isPilesInspection) { 109 | return false; 110 | } 111 | 112 | return true; 113 | } 114 | 115 | setCss () { 116 | try { 117 | const top = typeof this.position.top !== 'undefined' ? 118 | `${this.position.top}px` : undefined; 119 | const right = typeof this.position.right !== 'undefined' ? 120 | `${this.position.right}px` : undefined; 121 | const bottom = typeof this.position.bottom !== 'undefined' ? 122 | `${this.position.bottom}px` : undefined; 123 | const left = typeof this.position.left !== 'undefined' ? 124 | `${this.position.left}px` : undefined; 125 | 126 | this.css = { 127 | top, right, bottom, left 128 | }; 129 | } catch (error) { 130 | logger.error(error); 131 | } 132 | } 133 | 134 | trigger (button) { 135 | button.trigger(this.pile); 136 | 137 | if (button.closeOnClick) { 138 | this.isActive = false; 139 | } 140 | } 141 | 142 | subscribeEventListeners () { 143 | this.subscriptions.push( 144 | this.event.subscribe( 145 | 'explore.fgm.pileMenuUpdate', this.updateMenu.bind(this) 146 | ) 147 | ); 148 | } 149 | 150 | unsubscribeEventListeners () { 151 | this.subscriptions.forEach((subscription) => { 152 | subscription.dispose(); 153 | }); 154 | this.subscriptions = undefined; 155 | } 156 | 157 | updateMenu () { 158 | if (this.pile) { 159 | this.commands.forEach((command) => { 160 | command.isActive = this.isActiveCmd(command); 161 | command.isVisible = this.isVisibleCmd(command); 162 | command.pile = this.pile; 163 | 164 | command.buttons.forEach((button) => { 165 | button.isVisible = this.isVisibleCmd(button); 166 | }); 167 | }); 168 | 169 | // Somtimes isActive is not properly recognized. This seems to be a bug 170 | // in Aurelia 171 | this.isActive = true; 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/utils/basic-higlass-config.js: -------------------------------------------------------------------------------- 1 | import deepClone from './deep-clone'; 2 | 3 | const genes = { 4 | hg19: 'OHJakQICQD6gTD7skx4EWA', 5 | hg38: 'P0PLbQMwTYGy-5uPIQid7A', 6 | mm9: 'GUm5aBiLRCyz2PsBea7Yzg', 7 | mm10: 'QDutvmyiSrec5nX4pA5WGQ', 8 | danRer10: 'OwA1ouTzRamg5lOeDFUGIw', 9 | dm6: 'B2skqtzdSLyWYPTYM8t8lQ', 10 | ce11: 'PooX-P8zTXaI927e1mBrnQ' 11 | }; 12 | 13 | const chroms = { 14 | hg19: 'N12wVGG9SPiTkk03yUayUw', 15 | hg38: 'NyITQvZsS_mOFNlz5C2LJg', 16 | mm9: 'WAVhNHYxQVueq6KulwgWiQ', 17 | mm10: 'EtrWT0VtScixmsmwFSd7zg', 18 | danRer10: 'Yl1IWCeVRI65yOEMvmpIZQ', 19 | dm3: 'd2UOjWDsQMe6Ahx8rT_X7g', 20 | dm6: 'f2FZsbCBTbyIx7A-xiRq5Q', 21 | ce11: 'NskcugMOSiqbSqecqMCosw', 22 | b37: 'Ajn_ttUUQbqgtOD4nOt-IA' 23 | }; 24 | 25 | const baseTemplate = { 26 | editable: false, 27 | trackSourceServers: [ 28 | '/api/v1' 29 | ], 30 | exportViewUrl: '/api/v1/viewconfs/', 31 | views: [] 32 | }; 33 | 34 | const viewTemplate = { 35 | uid: '', 36 | zoomFixed: true, 37 | chromInfoPath: '/api/v1/chrom-sizes/?id=', 38 | tracks: { 39 | top: [ 40 | { 41 | uid: '.1', 42 | type: 'horizontal-gene-annotations', 43 | tilesetUid: '', 44 | server: '//higlass.io/api/v1', 45 | options: { 46 | minusStrandColor: '#999', 47 | plusStrandColor: '#999' 48 | } 49 | }, 50 | { 51 | uid: '.2', 52 | chromInfoPath: 53 | '//higlass.io/api/v1/chrom-sizes/?id=', 54 | type: 'horizontal-chromosome-labels' 55 | } 56 | ], 57 | left: [ 58 | { 59 | uid: '.3', 60 | chromInfoPath: 61 | '//higlass.io/api/v1/chrom-sizes/?id=', 62 | type: 'vertical-chromosome-labels' 63 | } 64 | ], 65 | center: [ 66 | { 67 | uid: '.4', 68 | type: 'combined', 69 | contents: [ 70 | { 71 | uid: '.5', 72 | server: '/api/v1', 73 | tilesetUid: '', 74 | type: 'heatmap', 75 | options: { 76 | colorRange: ['#FFFFFF', '#ffed1a', '#ff5500', '#540000'], 77 | backgroundColor: 'white' 78 | } 79 | } 80 | ] 81 | } 82 | ] 83 | }, 84 | layout: { 85 | w: 12, 86 | h: 12, 87 | x: 0, 88 | y: 0 89 | } 90 | }; 91 | 92 | const basicHiglassConfig = (server, dataSets) => { 93 | const base = deepClone(baseTemplate); 94 | base.exportViewUrl = base.exportViewUrl 95 | .replace(//g, server); 96 | 97 | base.views = dataSets.map((dataset, i) => { 98 | const view = deepClone(viewTemplate); 99 | 100 | view.uid = view.uid.replace(//g, i); 101 | [ 102 | ...view.tracks.top, 103 | ...view.tracks.left, 104 | ...view.tracks.center 105 | ].forEach((track) => { 106 | track.uid = track.uid.replace(//g, i); 107 | if (track.type === 'combined') { 108 | track.contents.forEach((childTrack) => { 109 | childTrack.uid = childTrack.uid.replace(//g, i); 110 | }); 111 | } 112 | }); 113 | 114 | view.chromInfoPath = view.chromInfoPath 115 | .replace(//g, server) 116 | .replace(//g, dataset.matrix); 117 | 118 | if (chroms[dataset.coords]) { 119 | view.tracks.top[1].chromInfoPath = view.tracks.top[1].chromInfoPath 120 | .replace(//g, chroms[dataset.coords]); 121 | view.tracks.left[0].chromInfoPath = view.tracks.left[0].chromInfoPath 122 | .replace(//g, chroms[dataset.coords]); 123 | } else { 124 | const chromInfoPath = `${server}/api/v1/chrom-sizes/?id=${dataset.matrix}`; 125 | view.tracks.top[1].chromInfoPath = chromInfoPath; 126 | view.tracks.left[0].chromInfoPath = chromInfoPath; 127 | } 128 | 129 | const geneAnnotationTilesetUuid = ( 130 | genes[dataset.coords] || 131 | genes[dataset.geneAnnotations] || 132 | dataset.geneAnnotations 133 | ); 134 | if (!geneAnnotationTilesetUuid) { 135 | console.warn(`We could't find a gene annotation tileset for the "${dataset.coords}" coordinate system. You can specify the tileset via a "_gene_annotations" column in your CSV file.`); 136 | view.tracks.top.splice(0, 1); 137 | } else { 138 | view.tracks.top[0].tilesetUid = view.tracks.top[0].tilesetUid 139 | .replace(//g, geneAnnotationTilesetUuid); 140 | } 141 | 142 | const centerTrack = view.tracks.center[0].contents[0]; 143 | 144 | centerTrack.server = centerTrack.server 145 | .replace(//g, server); 146 | 147 | centerTrack.tilesetUid = centerTrack.tilesetUid 148 | .replace(//g, dataset.matrix); 149 | 150 | view.layout.w = 12 / dataSets.length; 151 | view.layout.x = i * view.layout.w; 152 | 153 | return view; 154 | }); 155 | 156 | return base; 157 | }; 158 | 159 | export default basicHiglassConfig; 160 | -------------------------------------------------------------------------------- /src/components/fragments/pile-colors.js: -------------------------------------------------------------------------------- 1 | import chroma from 'chroma'; 2 | import { scaleLinear } from 'd3'; 3 | 4 | const COLOR_BW = chroma.scale(); 5 | const COLOR_BW_INV = chroma.bezier( 6 | ['#fff2d9', '#ff4500', '#8b0000', '#330000'] 7 | ).scale().correctLightness(); 8 | 9 | const COLOR_FALL = chroma.bezier( 10 | ['#ffffff', '#fffacd', '#ff4500', '#8b0000', '#000000'] 11 | ).scale().correctLightness(); 12 | 13 | const COLOR_FALL_INVERSE = chroma.bezier( 14 | ['#dcf5e7', '#c5f5e1', '#00cca7', '#006f00', '#004000'] 15 | ).scale().correctLightness(); 16 | 17 | const COLOR_YL_GN_BU = chroma.scale([ 18 | '#ffffff', 19 | '#ffffd9', 20 | '#edf8b1', 21 | '#c7e9b4', 22 | '#7fcdbb', 23 | '#41b6c4', 24 | '#1d91c0', 25 | '#225ea8', 26 | '#253494', 27 | '#081d58' 28 | ]); 29 | 30 | const COLOR_YL_GN_BU_INVERSE = chroma.scale([ 31 | '#fff7bc', 32 | '#fee391', 33 | '#fec44f', 34 | '#fe9929', 35 | '#ec7014', 36 | '#cc4c02', 37 | '#993404', 38 | '#662506' 39 | ]); 40 | 41 | const COLOR_YL_RD_BU = chroma.scale([ 42 | '#ffffff', 43 | '#fff9bf', 44 | '#ffce00', 45 | '#ff9a00', 46 | '#ff622d', 47 | '#ff164a', 48 | '#e10061', 49 | '#b10074', 50 | '#73007f', 51 | '#000080' 52 | ]); 53 | 54 | const COLOR_YL_RD_BU_INVERSE = chroma.scale([ 55 | '#f7fcb9', 56 | '#d9f0a3', 57 | '#addd8e', 58 | '#78c679', 59 | '#41ab5d', 60 | '#238443', 61 | '#006837', 62 | '#004529' 63 | ]); 64 | 65 | const COLOR_PK_PP = chroma.scale([ 66 | '#ffffff', 67 | '#ffb8fb', 68 | '#ff5cfe', 69 | '#cb08e1', 70 | '#81169e', 71 | '#401551', 72 | '#000000' 73 | ]); 74 | 75 | const COLOR_PK_PP_INVERSE = COLOR_YL_RD_BU_INVERSE; 76 | 77 | const COLOR_RD_WH_BU = chroma.scale('RdBu'); 78 | const COLOR_RD_WH_BU_INVERSE = chroma.scale('PRGn'); 79 | 80 | export const bw = value => [...COLOR_BW(value).rgb(), 255]; 81 | bw.inverse = value => [...COLOR_BW_INV(value).rgb(), 255]; 82 | 83 | export const fall = value => [...COLOR_FALL(value).rgb(), 255]; 84 | fall.inverse = value => [...COLOR_FALL_INVERSE(value).rgb(), 255]; 85 | 86 | export const ylGnBu = value => [...COLOR_YL_GN_BU(value).rgb(), 255]; 87 | ylGnBu.inverse = value => [...COLOR_YL_GN_BU_INVERSE(value).rgb(), 255]; 88 | 89 | export const ylRdBu = value => [...COLOR_YL_RD_BU(value).rgb(), 255]; 90 | ylRdBu.inverse = value => [...COLOR_YL_RD_BU_INVERSE(value).rgb(), 255]; 91 | 92 | export const rdWhBu = value => [...COLOR_RD_WH_BU(value).rgb(), 255]; 93 | rdWhBu.inverse = value => [...COLOR_RD_WH_BU_INVERSE(value).rgb(), 255]; 94 | 95 | export const pkPp = value => [...COLOR_PK_PP(value).rgb(), 255]; 96 | pkPp.inverse = value => [...COLOR_PK_PP_INVERSE(value).rgb(), 255]; 97 | 98 | const scale = scaleLinear().range([0, 1]); 99 | 100 | /** 101 | * Map a value to a relative color array for gray. 102 | * 103 | * @param {number} value - Domain value, i.e., value to be mapped. 104 | * @return {array} Relative color array. 105 | */ 106 | export const gray = (value) => { 107 | const scaled = scale(value); 108 | 109 | return [scaled, scaled, scaled]; 110 | }; 111 | 112 | const scaleRgb = scaleLinear().range([0, 255]); 113 | 114 | /** 115 | * Map a value to a relative color array for gray. 116 | * 117 | * @param {number} value - Domain value, i.e., value to be mapped. 118 | * @return {array} Relative color array. 119 | */ 120 | export const grayRgba = (value) => { 121 | const scaled = scaleRgb(value); 122 | 123 | return [scaled, scaled, scaled, 255]; 124 | }; 125 | 126 | const orangeBlackRgbR = scaleLinear().domain([0, 0.4, 0.66, 1]).range([ 127 | 0, 64, 255, 255 128 | ]); 129 | const orangeBlackRgbG = scaleLinear().domain([0, 0.4, 0.66, 1]).range([ 130 | 0, 0, 85, 221 131 | ]); 132 | const orangeBlackRgbB = scaleLinear().domain([0, 0.4, 0.66, 1]).range([ 133 | 0, 0, 0, 204 134 | ]); 135 | 136 | /** 137 | * Map a value to a relative color array for orange. 138 | * 139 | * @param {number} value - Domain value, i.e., value to be mapped. 140 | * @return {array} Relative color array. 141 | */ 142 | export const orangeBlackRgba = value => [ 143 | orangeBlackRgbR(value), 144 | orangeBlackRgbG(value), 145 | orangeBlackRgbB(value), 146 | 255 147 | ]; 148 | 149 | const whiteOrangeBlackRgbR = scaleLinear().domain([0, 0.4, 0.66, 1]).range([ 150 | 255, 255, 64, 0 151 | ]); 152 | const whiteOrangeBlackRgbG = scaleLinear().domain([0, 0.4, 0.66, 1]).range([ 153 | 255, 85, 0, 0 154 | ]); 155 | const whiteOrangeBlackRgbB = scaleLinear().domain([0, 0.4, 0.66, 1]).range([ 156 | 255, 0, 0, 0 157 | ]); 158 | 159 | /** 160 | * Map a value to a relative color array for orange. 161 | * 162 | * @param {number} value - Domain value, i.e., value to be mapped. 163 | * @return {array} Relative color array. 164 | */ 165 | export const whiteOrangeBlackRgba = value => [ 166 | whiteOrangeBlackRgbR(value), 167 | whiteOrangeBlackRgbG(value), 168 | whiteOrangeBlackRgbB(value), 169 | 255 170 | ]; 171 | 172 | export default { 173 | gray, 174 | grayRgba, 175 | orangeBlackRgba, 176 | whiteOrangeBlackRgba, 177 | bw, 178 | fall, 179 | ylGnBu, 180 | ylRdBu, 181 | rdWhBu, 182 | pkPp 183 | }; 184 | -------------------------------------------------------------------------------- /src/components/fragments/pile-details.html: -------------------------------------------------------------------------------- 1 | 130 | -------------------------------------------------------------------------------- /src/views/home.html: -------------------------------------------------------------------------------- 1 | 85 | -------------------------------------------------------------------------------- /src/components/fragments/fragments-defaults.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import threeLine2d from 'three-line-2d'; 3 | import threeLine2dShader from 'three-line-2d-shader'; 4 | 5 | import COLORS from 'configs/colors'; 6 | 7 | 8 | export const ANIMATION = true; 9 | 10 | export const ANNOTATIONS = {}; 11 | 12 | export const API_FRAGMENTS = 'fragments_by_loci'; 13 | 14 | export const ARRANGE_MEASURES = []; 15 | 16 | export const CAT_CHROMOSOME = 'chrom1'; 17 | 18 | export const CAT_DATASET = 'dataset'; 19 | 20 | export const CAT_LOCATION = 'location'; 21 | 22 | export const CAT_ZOOMOUT_LEVEL = 'zoomout-level'; 23 | 24 | export const CELL_SIZE = 6; 25 | 26 | export const CELL_SIZE_HALF = CELL_SIZE / 2; 27 | 28 | export const CELL_THRESHOLD = 0.0; // only cells above are shown 29 | 30 | export const CLICK_DELAY_TIME = 300; 31 | 32 | export const CLUSTER_TSNE = '_cluster_tsne'; 33 | 34 | export const DATA_DIMS = 20; 35 | 36 | export const DATA_PADDING = 0; 37 | 38 | export const DATA_PERCENTILE = 100; 39 | 40 | export const DATA_IGNORE_DIAGS = 0; 41 | 42 | export const COLOR_BW = 'bw'; 43 | 44 | export const COLOR_FALL = 'fall'; 45 | 46 | export const COLOR_YL_GN_BU = 'ylGnBu'; 47 | 48 | export const COLOR_YL_RD_BU = 'ylRdBu'; 49 | 50 | export const COLOR_RD_WH_BY = 'rdWhBu'; 51 | 52 | export const COLOR_MAPS = [ 53 | COLOR_BW, 54 | COLOR_FALL, 55 | COLOR_YL_GN_BU, 56 | COLOR_YL_RD_BU, 57 | COLOR_RD_WH_BY 58 | ]; 59 | 60 | export const COLOR_SCALE_FROM = 0; 61 | 62 | export const COLOR_SCALE_TO = 1; 63 | 64 | export const DBL_CLICK_DELAY_TIME = 250; 65 | 66 | /** 67 | * External config to initialize fragments. 68 | * 69 | * @description 70 | * The config is different from the visual state of the app. 71 | * 72 | * @type {Object} 73 | */ 74 | export const CONFIG = {}; 75 | 76 | export const DIAGONAL_VALUE = 0.11; 77 | 78 | export const DURATION = 250; 79 | 80 | export const FONT_URL = 'assets/fonts/rubik-regular.json'; 81 | 82 | export const FPS = 25; 83 | 84 | export const FRAGMENT_PRECISION = 2; 85 | 86 | export const FRAGMENT_SIZE = 22; 87 | 88 | export const GRID_CELL_SIZE_LOCK = true; 89 | 90 | export const GRID_SIZE = CELL_SIZE; 91 | 92 | export const HILBERT_CURVE = false; 93 | 94 | export const HIGHLIGHT_FRAME_LINE_WIDTH = 5; 95 | 96 | export const HIGLASS_SUB_SELECTION = true; 97 | 98 | export const LABEL_DIST = 60; 99 | 100 | export const LABEL_WIDTH = 130; 101 | 102 | export const LABEL_TEXT_SPEC = { 103 | size: 6, 104 | height: 1, 105 | curveSegments: 3 106 | }; 107 | 108 | export const LASSO_IS_ROUND = true; 109 | 110 | export const LINE = threeLine2d(THREE); 111 | 112 | export const LINE_SHADER = threeLine2dShader(THREE); 113 | 114 | export const LASSO_LINE_WIDTH = 2; 115 | 116 | export const LASSO_MIN_MOVE = 0.033; 117 | 118 | export const LASSO_SHADER = threeLine2dShader(THREE)({ 119 | side: THREE.DoubleSide, 120 | diffuse: COLORS.PRIMARY, 121 | thickness: LASSO_LINE_WIDTH 122 | }); 123 | 124 | export const LASSO_MATERIAL = new THREE.ShaderMaterial(LASSO_SHADER); 125 | 126 | export const LETTER_SPACE = 6; 127 | 128 | export const LOG_TRANSFORM = false; 129 | 130 | export const MARGIN_BOTTOM = 2; 131 | 132 | export const MARGIN_LEFT = 2; 133 | 134 | export const MARGIN_RIGHT = 2; 135 | 136 | export const MARGIN_TOP = 2; 137 | 138 | export const MATRICES_COLORS = {}; 139 | 140 | export const MATRIX_FRAME_ENCODING = null; 141 | 142 | export const MATRIX_FRAME_THICKNESS = 2; 143 | 144 | export const MATRIX_FRAME_THICKNESS_MAX = 10; 145 | 146 | export const MATRIX_GAP_HORIZONTAL = 6; 147 | 148 | export const MATRIX_GAP_VERTICAL = 6; 149 | 150 | export const MATRIX_ORIENTATION_UNDEF = 0; 151 | 152 | export const MATRIX_ORIENTATION_INITIAL = 1; 153 | 154 | export const MATRIX_ORIENTATION_5_TO_3 = 2; 155 | 156 | export const MATRIX_ORIENTATION_3_TO_5 = 3; 157 | 158 | /** 159 | * Mean 160 | * 161 | * @type {Number} 162 | */ 163 | export const MODE_AVERAGE = 0; 164 | 165 | /** 166 | * Standard deviaton 167 | * 168 | * @type {Number} 169 | */ 170 | export const MODE_VARIANCE = 1; 171 | 172 | export const ORDER_DATA = 0; 173 | 174 | export const ORDER_GLOBAL = 1; 175 | 176 | export const ORDER_LOCAL = 2; 177 | 178 | export const PILE_AREA_BG = new THREE.MeshBasicMaterial({ 179 | color: COLORS.PRIMARY, 180 | transparent: true, 181 | opacity: 0.15 182 | }); 183 | 184 | export const PILE_AREA_BORDER = new THREE.ShaderMaterial(LINE_SHADER({ 185 | side: THREE.DoubleSide, 186 | diffuse: COLORS.PRIMARY, 187 | thickness: 1, 188 | transparent: true, 189 | opacity: 0.3 190 | })); 191 | 192 | export const PILE_AREA_POINTS = new THREE.MeshBasicMaterial({ 193 | color: COLORS.PRIMARY, 194 | transparent: true, 195 | opacity: 1 196 | }); 197 | 198 | export const PILES_INSPECTION = []; 199 | 200 | export const PILE_LABEL_HEIGHT = 10; 201 | 202 | export const PILE_MENU_CLOSING_DELAY = 200; 203 | 204 | export const PILES = {}; 205 | 206 | export const PREVIEW_MAX = 8; 207 | 208 | export const SELECTED_PILE = null; 209 | 210 | export const SPECIAL_FIELDS = ['server', 'coords', 'notes', 'pilenotes']; 211 | 212 | export const SHOW_MATRICES = 1000; 213 | 214 | export const SHOW_SPECIAL_CELLS = false; 215 | 216 | export const TIMELINE_HEIGHT = 130; 217 | 218 | export const TSNE_PERPLEXITY = 20.0; 219 | 220 | export const TSNE_EARLY_EXAGGERATION = 4.0; 221 | 222 | export const TSNE_LEARNING_RATE = 100.0; 223 | 224 | export const TSNE_ITERATIONS = 500; 225 | 226 | /** 227 | * Three.js's WebGL config 228 | * 229 | * @type {Object} 230 | */ 231 | export const WEB_GL_CONFIG = { 232 | alpha: true, 233 | antialias: true 234 | }; 235 | 236 | export const Z_BASE = 1; 237 | 238 | export const Z_PILE_MAX = 2; 239 | 240 | export const Z_DRAG = 3; 241 | 242 | export const Z_HIGHLIGHT = 3; 243 | 244 | export const Z_HIGHLIGHT_AREA = 2.5; 245 | 246 | export const Z_LASSO = 9; 247 | 248 | export const Z_STACK_PILE_TARGET = 2; 249 | 250 | export const ZOOM_DELAY_TIME = 500; 251 | --------------------------------------------------------------------------------