Commit 96b1dc31 authored by Sascha Herzinger's avatar Sascha Herzinger
Browse files

basic heatmap code

parent 6a674264
import Vue from 'vue'
import CorrelationAnalysis from '../vue/charts/CorrelationAnalysis.vue'
import Boxplot from '../vue/charts/Boxplot.vue'
import Heatmap from '../vue/charts/Heatmap.vue'
export default class {
constructor () {
this.AVAILABLE_CHARTS = {
[CorrelationAnalysis.name]: CorrelationAnalysis,
[Boxplot.name]: Boxplot
[Boxplot.name]: Boxplot,
[Heatmap.name]: Heatmap
}
}
......
......@@ -111,12 +111,12 @@
<script>
import DataBox from '../components/DataBox.vue'
import store from '../../store/store'
import requestHandling from '../mixins/run-analysis'
import runAnalysis from '../mixins/run-analysis'
import * as d3 from 'd3'
import { TweenLite } from 'gsap'
import TaskView from '../components/TaskView.vue'
import deepFreeze from 'deep-freeze-strict'
import utils from '../mixins/utils'
import { truncateTextUntil } from '../mixins/utils'
import tooltip from '../directives/tooltip'
export default {
name: 'boxplot',
......@@ -265,7 +265,7 @@
axis () {
const x = d3.axisBottom(this.scales.x).tickFormat(d => {
// noinspection JSSuspiciousNameCombination
return utils.truncateTextUntil({text: d, font: `14px Roboto`, maxWidth: this.margin.bottom})
return truncateTextUntil({text: d, font: `14px Roboto`, maxWidth: this.margin.bottom})
})
const y = d3.axisLeft(this.scales.y)
return { x, y }
......@@ -344,14 +344,13 @@
},
runAnalysisWrapper ({args}) {
// function made available via requestHandling mixin
this.runAnalysis({task_name: 'compute-boxplot', args})
runAnalysis({task_name: 'compute-boxplot', args})
.then(response => {
const results = JSON.parse(response)
const data = JSON.parse(results.data)
results.data = Object.keys(data).map(key => data[key])
deepFreeze(results) // massively improve performance by telling Vue that the objects properties won't change
this.results = results
this.handleResize()
})
.catch(error => console.error(error))
}
......@@ -360,9 +359,6 @@
DataBox,
TaskView
},
mixins: [
requestHandling
],
directives: {
tooltip
},
......
......@@ -116,7 +116,7 @@
<script>
import DataBox from '../components/DataBox.vue'
import store from '../../store/store'
import requestHandling from '../mixins/run-analysis'
import runAnalysis from '../mixins/run-analysis'
import * as d3 from 'd3'
import { TweenLite } from 'gsap'
import tooltip from '../directives/tooltip.js'
......@@ -456,16 +456,13 @@
DataBox,
TaskView
},
mixins: [
requestHandling
],
directives: {
tooltip
},
methods: {
runAnalysisWrapper ({init, args}) {
// function made available via requestHandling mixin
this.runAnalysis({task_name: 'compute-correlation', args})
runAnalysis({task_name: 'compute-correlation', args})
.then(response => {
const results = JSON.parse(response)
const data = JSON.parse(results.data)
......
<template>
<div :class="`fjs-heatmap fjs-vm-uid-${this._uid}`">
<div class="fjs-data-box-container">
<data-box class="fjs-data-box"
header="Numerical Array Data"
dataType="numerical_array"
v-on:update="update_numericArrayData">
</data-box>
</div>
<div class="fjs-parameter-container">
</div>
<div class="fjs-vis-container">
<svg :height="height" :width="width">
<g :transform="`translate(${margin.left}, ${margin.top})`">
<rect class="fjs-cell"
:x="scales.x(d.id)"
:y="scales.y(d.variable)"
:height="width / uniqueIds.length"
:width="width / uniqueIds.length"
v-for="d in results.data">
</rect>
</g>
</svg>
</div>
</div>
</template>
<script>
import DataBox from '../components/DataBox.vue'
import store from '../../store/store'
import runAnalysis from '../mixins/run-analysis'
import * as d3 from 'd3'
import tooltip from '../directives/tooltip.js'
import TaskView from '../components/TaskView.vue'
import deepFreeze from 'deep-freeze-strict'
export default {
name: 'heatmap',
data () {
return {
width: 500,
height: 500,
numericArrayDataIds: [],
results: {}
}
},
computed: {
args () {
return {
numerical_arrays: this.numericArrayDataIds,
numericals: [],
categoricals: []
}
},
margin () {
const left = 50
const top = 50
const right = 50
const bottom = 50
return { left, top, right, bottom }
},
padded () {
const width = this.width - this.margin.left - this.margin.right
const height = this.height - this.margin.top - this.margin.bottom
return { width, height }
},
uniqueIds () {
return [...new Set(this.results.data.map(d => d.id))]
},
uniqueVariables () {
return [...new Set(this.results.data.map(d => d.variable))]
},
scales () {
const x = d3.scaleOrdinal()
.domain(this.uniqueIds)
.range(this.uniqueIds.map((d, i) => i * this.padded.width / this.uniqueIds.length))
const y = d3.scaleOrdinal()
.domain(this.uniqueVariables)
.range(this.uniqueVariables.map((d, i) => i * this.padded.height / this.uniqueVariables.length))
return { x, y }
}
},
methods: {
runAnalysisWrapper (args) {
runAnalysis({task_name: 'compute-heatmap', args})
.then(response => {
const results = JSON.parse(response)
const data = JSON.parse(results.data)
results.data = Object.keys(data).map(key => data[key])
deepFreeze(results) // massively improve performance by telling Vue that the objects properties won't change
this.results = results
})
},
handleResize () {
const container = this.$el.querySelector(`.fjs-vm-uid-${this._uid} .fjs-vis-container svg`)
// noinspection JSSuspiciousNameCombination
this.height = container.getBoundingClientRect().width
this.width = container.getBoundingClientRect().width
},
update_numericArrayData (ids) {
this.numericArrayDataIds = ids
}
},
watch: {
'args': {
handler: function (newArgs, oldArgs) {
if (JSON.stringify(newArgs) !== JSON.stringify(oldArgs)) {
this.runAnalysisWrapper(newArgs)
}
}
}
},
mounted () {
window.addEventListener('resize', this.handleResize)
this.handleResize()
},
beforeDestroy () {
window.removeEventListener('resize', this.handleResize)
},
components: {
DataBox,
TaskView
},
directives: {
tooltip
}
}
</script>
<style lang="sass" scoped>
@import './src/assets/base.sass'
*
font-family: Roboto, sans-serif
.fjs-correlation-analysis
height: 100%
width: 100%
display: flex
flex-direction: column
.fjs-data-box-container
height: 160px
display: flex
justify-content: space-around
.fjs-parameter-container
text-align: center
.fjs-vis-container
flex: 1
display: flex
svg
flex: 1
.fjs-cell
stroke: #fff
stroke-width: 2px
shape-rendering: crispEdges
</style>
\ No newline at end of file
import store from '../../store/store'
export default {
methods: {
/**
* A helper method to submit an analysis.
* This method returns a promise that resolves into the statistic results of the task once it is finished.
* Please pay special attention to the `args` syntax.
* @param task_name The name to execute. For instance `compute-correlation`
* @param args An object containing all parameters for the task. Strings wrapped in '$' characters are treated as
* data ids. The backend will attempt to replace them with a Python Pandas DataFrame.
* Example args = {method: 'sum', x: '$1234-5678-12345678$'} will replace x with a DataFrame, if available. This
* value is usually retrieved from the data-box component, which will emit an `update` signal containing currently
* selected data ids. Note that Arrays of '$' wrapped strings are also valid.
* @returns {Promise.<void>} An ES6 promise. Resolves into the result of the analysis.
*/
async runAnalysis ({task_name, args}) {
function timeout (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
const rv1 = await store.getters.requestManager.createAnalysis({task_name, args})
const taskID = rv1.data.task_id
/**
* A helper method to submit an analysis.
* This method returns a promise that resolves into the statistic results of the task once it is finished.
* Please pay special attention to the `args` syntax.
* @param task_name The name to execute. For instance `compute-correlation`
* @param args An object containing all parameters for the task. Strings wrapped in '$' characters are treated as
* data ids. The backend will attempt to replace them with a Python Pandas DataFrame.
* Example args = {method: 'sum', x: '$1234-5678-12345678$'} will replace x with a DataFrame, if available. This
* value is usually retrieved from the data-box component, which will emit an `update` signal containing currently
* selected data ids. Note that Arrays of '$' wrapped strings are also valid.
* @returns {Promise.<void>} An ES6 promise. Resolves into the result of the analysis.
*/
async function runAnalysis ({task_name, args}) {
function timeout (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
const rv1 = await store.getters.requestManager.createAnalysis({task_name, args})
const taskID = rv1.data.task_id
store.dispatch('setTask', {
taskID,
taskName: task_name,
taskState: 'PENDING'
})
let counter = 0
while (counter++ < 1000) {
await timeout(400)
const rv2 = await store.getters.requestManager.getAnalysisStatus({taskID})
const taskInfo = rv2.data
if (taskInfo.state === 'SUCCESS') {
store.dispatch('setTask', {
taskID,
taskName: task_name,
taskState: taskInfo.state
})
return taskInfo.result
} else if (taskInfo.state === 'FAILURE') {
store.dispatch('setTask', {
taskID,
taskName: task_name,
taskState: 'PENDING'
taskState: taskInfo.state,
taskMessage: taskInfo.result
})
let counter = 0
while (counter++ < 1000) {
await timeout(400)
const rv2 = await store.getters.requestManager.getAnalysisStatus({taskID})
const taskInfo = rv2.data
if (taskInfo.state === 'SUCCESS') {
store.dispatch('setTask', {
taskID,
taskName: task_name,
taskState: taskInfo.state
})
return taskInfo.result
} else if (taskInfo.state === 'FAILURE') {
store.dispatch('setTask', {
taskID,
taskName: task_name,
taskState: taskInfo.state,
taskMessage: taskInfo.result
})
throw new Error(taskInfo.result)
} else if (taskInfo.state === 'PENDING') {
store.dispatch('setTask', {
taskID,
taskName: task_name,
taskState: taskInfo.state})
} else {
throw new Error(`Analysis Task has unknown state: ${taskInfo.state}`)
}
}
throw new Error(taskInfo.result)
} else if (taskInfo.state === 'PENDING') {
store.dispatch('setTask', {
taskID,
taskName: task_name,
taskState: taskInfo.state})
} else {
throw new Error(`Analysis Task has unknown state: ${taskInfo.state}`)
}
}
}
// This code is here because of: https://github.com/babel/babel/issues/3786
export default runAnalysis
export default {
/**
* https://stackoverflow.com/questions/5723154/truncate-a-string-in-the-middle-with-javascript
*/
truncate ({text, strLen, separator}) {
if (text.length <= strLen) return text
/**
* https://stackoverflow.com/questions/5723154/truncate-a-string-in-the-middle-with-javascript
*/
export function truncate ({text, strLen, separator}) {
if (text.length <= strLen) return text
separator = separator || '...'
separator = separator || '...'
const sepLen = separator.length
const charsToShow = strLen - sepLen
const frontChars = Math.ceil(charsToShow / 2)
const backChars = Math.floor(charsToShow / 2)
const sepLen = separator.length
const charsToShow = strLen - sepLen
const frontChars = Math.ceil(charsToShow / 2)
const backChars = Math.floor(charsToShow / 2)
return text.substr(0, frontChars) + separator + text.substr(text.length - backChars)
},
/**
* https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript
*/
getTextWidth ({text, font}) {
// re-use canvas object for better performance
const canvas = this.getTextWidth.canvas || (this.getTextWidth.canvas = document.createElement('canvas'))
const context = canvas.getContext('2d')
context.font = font
const metrics = context.measureText(text)
return metrics.width
},
truncateTextUntil ({text, font, maxWidth}) {
while (this.getTextWidth({text, font}) > maxWidth) {
text = this.truncate({text, strLen: text.length - 5})
}
return text
return text.substr(0, frontChars) + separator + text.substr(text.length - backChars)
}
/**
* https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript
*/
export function getTextWidth ({text, font}) {
// re-use canvas object for better performance
const canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement('canvas'))
const context = canvas.getContext('2d')
context.font = font
const metrics = context.measureText(text)
return metrics.width
}
export function truncateTextUntil ({text, font, maxWidth}) {
while (getTextWidth({text, font}) > maxWidth) {
text = truncate({text, strLen: text.length - 5})
}
return text
}
<!doctype html>
<head>
<script src="http://localhost:8080/credentials.js"></script>
<script src="http://localhost:8080/fractal.js"></script>
</head>
<body>
<input type="button" onclick="loadData()" value="load data"/>
<input type="button" onclick="deleteData()" value="delete data"/>
<div class="fjs-placeholder-container" style="width: 2000px;">
<div style="width: 1000px;">
<div id="placeholder1"></div>
</div>
<div style="width: 1000px;">
<div id="placeholder2"></div>
</div>
</div>
</body>
<script>
/* eslint-disable */
const fjs = fractal.init({
handler: 'ada',
thisBaseURL: 'https://ada.parkinson.lu',
fractalisBaseURL: 'http://127.0.0.1:5000',
getAuth () {
return credentials1
}
})
function loadData () {
fjs.loadData([
{
dictionary: {
name: 'entropyR',
projection: 'entropyR',
label: 'Entropy Right',
fieldType: 'Double',
isArray: true
},
data_set: 'egait.features'
},
{
dictionary: {
name: 'entropyL',
projection: 'entropyL',
label: 'Entropy Left',
fieldType: 'Double',
isArray: true
},
data_set: 'egait.features'
}
])
}
function deleteData () {
fjs.clearCache()
}
fjs.setChart({chart: 'heatmap', selector: '#placeholder1'})
</script>
<style>
.fjs-placeholder-container {
display: flex;
flex-direction: row;
}
</style>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment