Gitlab is now using https://gitlab.lcsb.uni.lu as it's primary address. Please update your bookmarks. FAQ.

Commit b8d67217 authored by Sascha Herzinger's avatar Sascha Herzinger
Browse files

Implementation of new parameter API

parent 45a60f2c
Pipeline #6671 passed with stages
in 5 minutes and 55 seconds
......@@ -67,14 +67,25 @@ class FractalJS {
store.dispatch('setSubsets', [])
}
// FIXME: temporarily disabled. Focus on stabilizing core functionality for now.
// // noinspection JSMethodCanBeStatic
// chart2id (vm, callback) {
// return store.getters.stateManager.chart2id(vm, callback)
// }
//
// // noinspection JSMethodCanBeStatic
// id2chart (selector, stateID) {
// return store.getters.stateManager.id2chart(selector, stateID)
// }
// noinspection JSMethodCanBeStatic
chart2id (vm, callback) {
return store.getters.stateManager.chart2id(vm, callback)
getChartParameterDescription (vm) {
return store.getters.chartManager.getChartParamDescr(vm)
}
// noinspection JSMethodCanBeStatic
id2chart (selector, stateID) {
return store.getters.stateManager.id2chart(selector, stateID)
setChartParameter (vm, parameter) {
store.getters.chartManager.setChartParams(vm, parameter)
}
}
......
import Vue from 'vue'
import _ from 'lodash'
export default class {
constructor () {
......@@ -46,4 +47,20 @@ export default class {
getAvailableCharts () {
return Object.keys(this.availableCharts)
}
getChartParamDescr (vm) {
if (typeof vm.params === 'undefined') {
throw new Error('This chart does not expose any parameters. This is a bug that you should report.')
}
return _.cloneDeep(vm.params)
}
setChartParameter (vm, params) {
Object.keys(params).forEach(key => {
if (typeof vm.params[key] === 'undefined') {
throw new Error(`Parameter "${key}" does not exist for this chart.`)
}
vm.params[key].value = params[key]
})
}
}
......@@ -2,52 +2,55 @@
<chart v-on:resize="resize">
<control-panel name="Boxplot Panel">
<data-box header="Numerical Variables"
<data-box :header="params.numVars.label"
:dataTypes="['numerical', 'numerical_array']"
:validRange="[1, Infinity]"
v-on:update="update_numData">
:validRange="[params.numVars.minLength, params.numVars.maxLength]"
v-on:select="updateNumVarsSelection"
v-on:update="updateNumVars">
</data-box>
<data-box header="Categorical Variables"
<data-box :header="params.catVars.label"
:dataTypes="['categorical']"
v-on:update="update_catData">
:validRange="[params.catVars.minLength, params.catVars.maxLength]"
v-on:select="updateCatVarsSelection"
v-on:update="updateCatVars">
</data-box>
<hr class="fjs-seperator"/>
<div class="fjs-parameter-container">
<div>
<label>
Data transformation:
<select class="fjs-transformation-select" v-model="params.transformation">
<option v-for="t in transformations">{{ t }}</option>
<select class="fjs-transformation-select" v-model="params.transformation.value">
<option v-for="t in params.transformation.validValues">{{ t }}</option>
</select>
</label>
</div>
<div>
<label>
<input type="checkbox" v-model="params.showOutliers"/>
<input type="checkbox" v-model="params.showOutliers.value"/>
Show Outliers
</label>
</div>
<div>
<label>
<input type="checkbox" v-model="params.showData"/>
<input type="checkbox" v-model="params.showData.value"/>
Show Points
</label>
</div>
<div>
<label>
<input type="checkbox" v-model="params.jitter"/>
<input type="checkbox" v-model="params.jitter.value"/>
Jitter Data
</label>
</div>
<div>
<label>
<input type="checkbox" v-model="params.showKDE"/>
<input type="checkbox" v-model="params.showKDE.value"/>
Show Density Est.
</label>
</div>
<div>
<label>
<input type="checkbox" v-model="params.ignoreSubsets"/>
<input type="checkbox" v-model="params.ignoreSubsets.value"/>
Ignore Subsets
</label>
</div>
......@@ -153,7 +156,7 @@
</image>
<polyline class="fjs-kde"
:points="kdePolyPoints[label]"
v-if="params.showKDE">
v-if="params.showKDE.value">
</polyline>
</g>
</g>
......@@ -177,16 +180,50 @@
name: 'boxplot',
data () {
return {
numData: [],
catData: [],
transformations: ['identity', 'log2(x)', 'log10(x)', '2^x', '10^x'],
params: {
showOutliers: true,
showData: false,
jitter: false,
showKDE: false,
ignoreSubsets: false,
transformation: 'identity'
numVars: {
type: Array,
elementType: String,
label: 'Numerical Variables',
validValues: [],
minLength: 1,
maxLength: Infinity,
value: []
},
catVars: {
type: Array,
elementType: String,
label: 'Categorical Variables',
validValues: [],
minLength: 0,
maxLength: Infinity,
value: []
},
showOutliers: {
type: Boolean,
value: true
},
showData: {
type: Boolean,
value: false
},
jitter: {
type: Boolean,
value: false
},
showKDE: {
type: Boolean,
value: false
},
ignoreSubsets: {
type: Boolean,
value: false
},
transformation: {
type: String,
value: 'identity',
validValues: ['identity', 'log2(x)', 'log10(x)', '2^x', '10^x']
}
},
width: 0,
height: 0,
......@@ -212,8 +249,8 @@
features: this.numData,
categories: this.catData,
id_filter: this.idFilter.value,
transformation: this.params.transformation,
subsets: this.params.ignoreSubsets ? [] : store.getters.subsets
transformation: this.params.transformation.value,
subsets: this.params.ignoreSubsets.value ? [] : store.getters.subsets
}
},
pointSize () {
......@@ -253,13 +290,13 @@
.filter(d => d.subset === subset &&
d.feature === feature &&
d.category === category &&
(this.params.showOutliers ? true : !d.outlier) &&
(this.params.showOutliers.value ? true : !d.outlier) &&
typeof d.value === 'number')
.map(d => {
return {
id: d.id,
value: d.value,
jitter: Math.max(this.pointSize / 2, (this.params.jitter ? Math.random() * this.boxplotWidth / 2 : this.boxplotWidth / 2) - this.pointSize / 2),
jitter: Math.max(this.pointSize / 2, (this.params.jitter.value ? Math.random() * this.boxplotWidth / 2 : this.boxplotWidth / 2) - this.pointSize / 2),
subset: d.subset,
category: d.category,
outlier: d.outlier
......@@ -294,7 +331,7 @@
},
scales () {
const values = this.results.data
.filter(d => this.params.showOutliers ? true : !d.outlier)
.filter(d => this.params.showOutliers.value ? true : !d.outlier)
.map(d => d.value)
const flattened = [].concat.apply([], values)
const extent = d3.extent(flattened)
......@@ -359,10 +396,10 @@
})
}
},
'params.showData': {
'params.showData.value': {
handler: function () { this.$nextTick(() => this.drawPoints()) }
},
'params.jitter': {
'params.jitter.value': {
handler: function () { this.$nextTick(() => this.drawPoints()) }
},
'points': {
......@@ -391,12 +428,6 @@
hideTooltip (label) {
this.getTippyInstances(label).forEach(d => d.hide())
},
update_numData (ids) {
this.numData = ids
},
update_catData (ids) {
this.catData = ids
},
setIDFilter (label) {
if (label === this.selectedLabel) {
store.dispatch('setFilter', {source: this._uid, filter: 'ids', value: []})
......@@ -413,7 +444,7 @@
const canvas = this.canvas[label]
const ctx = canvas.getContext('2d')
ctx.clearRect(0, 0, canvas.width, canvas.height)
if (this.params.showData) {
if (this.params.showData.value) {
this.points[label].forEach(point => {
ctx.beginPath()
ctx.fillStyle = point.outlier ? '#f00' : '#000'
......@@ -442,6 +473,18 @@
this.results = results
})
.catch(error => console.error(error))
},
updateNumVars (ids) {
this.params.numVars.validValues = ids
},
updateCatVars (ids) {
this.params.catVars.validValues = ids
},
updateNumVarsSelection (ids) {
this.params.numVars.value = ids
},
updateCatVarsSelection (ids) {
this.params.catVars.value = ids
}
},
components: {
......@@ -455,11 +498,6 @@
],
directives: {
tooltip
},
mounted () {
this.registerDataToSave([
'catData', 'numData', 'params'
])
}
}
</script>
......
<template>
<chart v-on:resize="resize">
<control-panel name="Heatmap Panel">
<data-box header="Numerical Variables"
<data-box :header="params.numVars.label"
:dataTypes="['numerical_array']"
:validRange="[1, Infinity]"
v-on:update="update_numericArrayData">
:validRange="[params.numVars.minLength, params.numVars.maxLength]"
v-on:select="updateNumVarsSelection"
v-on:update="updateNumVars">
</data-box>
<hr class="fjs-seperator"/>
......@@ -12,67 +13,12 @@
<span class="fjs-param-header">Ranking Criteria</span>
<fieldset class="fjs-expression-ranking fjs-fieldset">
<legend>Expression Level</legend>
<!--FIXME: Make this dynamic similar to volcanoplot-->
<div>
<div v-for="method in params.rankingMethod.validValues">
<label>
<input type="radio" value="mean" v-model="rankingMethod">
<input type="radio" :value="method" v-model="params.rankingMethod.value">
Mean
</label>
</div>
<div>
<label>
<input type="radio" value="median" v-model="rankingMethod">
Median
</label>
</div>
</fieldset>
<fieldset class="fjs-fieldset">
<legend>Expression Variability</legend>
<div>
<label>
<input type="radio" value="variance" v-model="rankingMethod">
Variance
</label>
</div>
</fieldset>
<fieldset class="fjs-fieldset">
<legend>Differential Expression</legend>
<div>
<label>
<input type="radio" value="logFC" v-model="rankingMethod">
logFC
</label>
</div>
<div>
<label>
<input type="radio" value="t" v-model="rankingMethod">
t
</label>
</div>
<div>
<label>
<input type="radio" value="F" v-model="rankingMethod">
F
</label>
</div>
<div>
<label>
<input type="radio" value="B" v-model="rankingMethod">
B
</label>
</div>
<div>
<label>
<input type="radio" value="P.Val" v-model="rankingMethod">
P.Value
</label>
</div>
<div>
<label>
<input type="radio" value="adj.P.Val" v-model="rankingMethod">
adj.P.Value
</label>
</div>
</fieldset>
</div>
......@@ -80,36 +26,30 @@
<span class="fjs-param-header">Heatmap Clustering</span>
<fieldset class="fjs-fieldset">
<legend>Algorithm</legend>
<div>
<div v-for="algorithm in params.clusterAlgorithm.validValues">
<label>
<input type="radio" value="hclust" v-model="cluster.algorithm"/>
Hierarch.
</label>
</div>
<div>
<label>
<input type="radio" value="kmeans" v-model="cluster.algorithm"/>
KMeans
<input type="radio" :value="algorithm" v-model="params.clusterAlgorithm.value"/>
{{ algorithm }}
</label>
</div>
</fieldset>
<fieldset class="fjs-cluster-option-fieldset fjs-fieldset" v-if="cluster.algorithm === 'hclust'">
<fieldset class="fjs-cluster-option-fieldset fjs-fieldset" v-if="params.clusterAlgorithm.value === 'hclust'">
<legend>Options</legend>
<div class="fjs-hclust-selects">
<select v-model="cluster.options.method">
<select v-model="params.clusterMethod.value">
<option value="" selected disabled>-- Method --</option>
<option :value="value"
v-for="value in ['single', 'complete', 'average', 'weighted', 'centroid', 'median', 'ward']"
v-model="cluster.options.method">
v-for="value in params.clusterMethod.validValues"
v-model="params.clusterMethod.value">
{{ value }}
</option>
</select>
<select v-model="cluster.options.metric">
<select v-model="params.clusterMetric.value">
<option value="" selected disabled>-- Metric --</option>
<option :value="value"
v-for="value in ['euclidean', 'sqeuclidean', 'cityblock', 'correlation', 'cosine']"
v-model="cluster.options.metric">
v-for="value in params.clusterMetric.validValues"
v-model="params.clusterMetric.value">
{{ value }}
</option>
</select>
......@@ -117,17 +57,17 @@
<div class="fjs-cluster-ranges">
<label>
<input type="range"
min="1" max="20"
v-model="cluster.options.n_row_clusters"/>
{{ cluster.options.n_row_clusters }} Row Clusters
:min="params.nRowClusters.min" :max="params.nRowClusters.max"
v-model="params.nRowClusters.value"/>
{{ params.nRowClusters.value }} Row Clusters
</label>
</div>
<div class="fjs-cluster-ranges">
<label>
<input type="range"
min="1" max="20"
v-model="cluster.options.n_col_clusters"/>
{{ cluster.options.n_col_clusters }} Col Clusters
:min="params.nColClusters.min" :max="params.nColClusters.max"
v-model="params.nColClusters.value"/>
{{ params.nColClusters.value }} Col Clusters
</label>
</div>
</fieldset>
......@@ -137,17 +77,17 @@
<div class="fjs-cluster-ranges">
<label>
<input type="range"
min="1" max="20"
v-model="cluster.options.n_row_centroids"/>
{{ cluster.options.n_row_centroids }} Row Centroids
:min="params.nRowCentroids.min" :max="params.nRowCentroids.max"
v-model="params.nRowCentroids.value"/>
{{ params.nRowCentroids.value }} Row Centroids
</label>
</div>
<div class="fjs-cluster-ranges">
<label>
<input type="range"
min="1" max="20"
v-model="cluster.options.n_col_centroids"/>
{{ cluster.options.n_col_centroids }} Col Centroids
:min="params.nColCentroids.min" :max="params.nColCentroids.max"
v-model="params.nColCentroids.value"/>
{{ params.nColCentroids.value }} Col Centroids
</label>
</div>
</fieldset>
......@@ -183,6 +123,7 @@
import deepFreeze from 'deep-freeze-strict'
import getHDPICanvas from '../../utils/high-dpi-canvas'
import StateSaver from '../mixins/state-saver'
import _ from 'lodash'
export default {
name: 'heatmap',
data () {
......@@ -191,18 +132,62 @@
height: 0,
colorScale: d3.interpolateCool,
subsetColors: d3.schemeCategory10,
numericArrayDataIds: [],
rankingMethod: 'mean',
cluster: {
algorithm: 'hclust',
options: {
method: '',
metric: '',
n_row_clusters: 5,
n_col_clusters: 5,
n_row_centroids: 5,
n_col_centroids: 5
params: {
numVars: {
type: Array,
elementType: String,
label: 'Numerical Variables',
validValues: [],
minLength: 1,
maxLength: Infinity,
value: []
},
rankingMethod: {
type: String,
validValues: [],
value: 'mean'
},
clusterAlgorithm: {
type: String,
validValues: ['hclust', 'kmeans'],
value: 'hclust'
},
clusterMethod: {
type: String,
validValues: ['single', 'complete', 'average', 'weighted', 'centroid', 'median', 'ward'],
value: ''
},
clusterMetric: {
type: String,
validValues: ['euclidean', 'sqeuclidean', 'cityblock', 'correlation', 'cosine'],
value: ''
},
nRowClusters: {
type: Number,
min: 1,
max: 20,
value: 5
},
nColClusters: {
type: Number,
min: 1,
max: 20,
value: 5
},
nRowCentroids: {
type: Number,
min: 1,
max: 20,
value: 5
},
nColCentroids: {
type: Number,
min: 1,
max: 20,
value: 5
}
},
cluster: {
colColors: d3.schemeCategory10,
rowColors: d3.schemeCategory10.slice().reverse(),
results: {
......@@ -220,13 +205,13 @@
computed: {
mainArgs () {
return {
numerical_arrays: this.numericArrayDataIds,
numerical_arrays: this.params.numVars.value,
numericals: [],
categoricals: [],
ranking_method: this.rankingMethod,
ranking_method: this.params.rankingMethod.value,
params: {},
id_filter: this.idFilter.value,
max_rows: 100,
max_rows: 100, // FIXME: make this configurable
subsets: store.getters.subsets
}
},
......@@ -240,14 +225,14 @@
})
return {
df: df,
cluster_algo: this.cluster.algorithm,
cluster_algo: this.params.clusterAlgorithm.value,
options: {
method: this.cluster.options.method,
metric: this.cluster.options.metric,
n_row_clusters: parseInt(this.cluster.options.n_row_clusters),
n_col_clusters: parseInt(this.cluster.options.n_col_clusters),
n_row_centroids: parseInt(this.cluster.options.n_row_centroids),
n_col_centroids: parseInt(this.cluster.options.n_col_centroids)
method: this.params.clusterMethod.value,
metric: this.params.clusterMetric.value,
n_row_clusters: parseInt(this.params.nRowClusters.value),
n_col_clusters: parseInt(this.params.nColClusters.value),
n_row_centroids: parseInt(this.params.nRowCentroids.value),
n_col_centroids: parseInt(this.params.nColCentroids.value)
}
}
},
......@@ -271,6 +256,15 @@
canvas () {
return getHDPICanvas(this.padded.width, this.padded.height)
},
statistics () {
if ((this.params.rankingMethod.value === 'limma') && (store.getters.subsets.length === 2)) {
return ['logFC', 'P.Value', 'feature', 'AveExpr', 't', 'adj.P.Val', 'B']
} else if ((this.params.rankingMethod.value === 'limma') && (store.getters.subsets.length > 2)) {
return ['F', 'P.Value', 'feature', 'AveExpr', 'adj.P.Val']
} else {
throw new Error(`Unknown ranking method: ${this.params.rankingMethod.value}`)
}
},
cols () {
let cols = []
if (this.cluster.results.cols.length) {
......@@ -415,13 +409,13 @@
sigBars () {
return this.results.stats.feature.map((d, i) => {
return {
x: -this.sigScales.x(this.results.stats[this.rankingMethod][i]),
x: -this.sigScales.x(this.results.stats[this.params.rankingMethod.value][i]),
y: this.sigScales.y(d),
width: this.sigScales.x(this.results.stats[this.rankingMethod][i]),
width: this.sigScales.x(this.results.stats[this.params.rankingMethod.value][i]),