CorrelationAnalysis.vue 10.7 KB
Newer Older
1
<template>
2
3
4
  <div style="height: 100%; width: 100%">

    <div id="data-box-section" style="height: 25%;">
5
6
7
8
9
10
11
12
      <data-box header="X and Y variables"
                dataType="numerical"
                v-on:update="update_xyData">
      </data-box>
      <data-box header="Annotations"
                dataType="categorical"
                v-on:update="update_annotationData">
      </data-box>
13
    </div>
14
15
16

    <input id="run-analysis-btn"
           type="button"
17
           @click="runAnalysisWrapper({init: true})"
18
19
20
21
22
           value="Run Analysis"
           :disabled="disabled"/>

    <div id="visualisation-section" style="height: 75%;">
      <svg width="100%" height="100%">
23
24
25
26
27
        <g :style="{ transform: `translate(${margin.left}px, ${margin.top}px)` }">
          <circle :cx="scales.x(point.x)"
                  :cy="scales.y(point.y)"
                  r="4"
                  :data-idx="idx"
28
                  v-for="(point, idx) in shownPoints.all">
29
          </circle>
30
31
32
33
34
35
          <line id="lin-reg-line"
                :x1="tweenedRegLine.x1"
                :x2="tweenedRegLine.x2"
                :y1="tweenedRegLine.y1"
                :y2="tweenedRegLine.y2">
          </line>
36
37
38
39
40
          <g id="x-axis-1" class="fjs-corr-axis" :style="{ transform: `translate(0px, ${padded.height}px)` }"></g>
          <g id="x-axis-2" class="fjs-corr-axis"></g>
          <g id="y-axis-1" class="fjs-corr-axis"></g>
          <g id="y-axis-2" class="fjs-corr-axis" :style="{ transform: `translate(${padded.width}px, 0px)` }"></g>
          <g id="brush"></g>
Sascha Herzinger's avatar
Sascha Herzinger committed
41
42
43
44
45
46
47
48
49
50
51
52
          <rect :x="scales.x(bin.x0)"
                :y="padded.height"
                :width="scales.x(bin.x1) - scales.x(bin.x0)"
                :height="histogramScales.y(bin.length)"
                v-for="(bin, idx) in histograms.xBins">
          </rect>
          <rect :x="-histogramScales.x(bin.length)"
                :y="scales.y(bin.x1)"
                :width="histogramScales.x(bin.length)"
                :height="scales.y(bin.x0) - scales.y(bin.x1)"
                v-for="(bin, idx) in histograms.yBins">
          </rect>
53
54
55
56
        </g>
      </svg>
    </div>

57
  </div>
58
59
60
61
</template>


<script>
62
63
  import DataBox from '../DataBox.vue'
  import requestHandling from '../mixins/request-handling'
64
  import * as d3 from 'd3'
65
  import { TweenLite } from 'gsap'
66
67
  export default {
    name: 'correlation-analysis',
68
    data () {
69
      return {
70
71
        width: 0,
        height: 0,
72
73
74
75
76
        xyData: [],
        annotationData: [],
        get args () {
          return {
            x: `$${this.xyData[0]}$`,
77
78
            y: `$${this.xyData[1]}$`,
            ids: this.selectedPoints.map(d => d.id)
79
          }
80
        },
81

82
        shownAnalysisResults: {
83
84
85
86
87
88
89
90
91
92
93
94
95
96
          coef: 0,
          p_value: 0,
          slope: 0,
          intercept: 0,
          method: '',
          x_label: '',
          y_label: '',
          get data() {
            return {
              id: [],
              [this.x_label]: [],
              [this.y_label]: []
            }
          }
97
        },
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
        tmpAnalysisResults: {
          coef: 0,
          p_value: 0,
          slope: 0,
          intercept: 0,
          method: '',
          x_label: '',
          y_label: '',
          get data() {
            return {
              id: [],
              [this.x_label]: [],
              [this.y_label]: []
            }
          }
        },
114
115
        selectedPoints: [],
        tweenedRegLine: {}
116
117
      }
    },
118
119
120
    computed: {
      disabled () {
        return this.xyData.length !== 2
121
      },
Sascha Herzinger's avatar
Sascha Herzinger committed
122
123
124
125
126
127
128
      margin () {
        const left = this.width / 3
        const top = 0
        const right = 0
        const bottom = this.height / 3
        return { left, top, right, bottom }
      },
129
130
131
132
      padded () {
        const width = this.width - this.margin.left - this.margin.right
        const height = this.height - this.margin.top - this.margin.bottom
        return { width, height }
133
      },
134
135
136
137
138
139
140
141
142
143
      shownPoints () {
        const xs = [], ys = [], ids = []
        const all = Object.keys(this.shownAnalysisResults.data.id).map(key => {
          const x = this.shownAnalysisResults.data[this.shownAnalysisResults.x_label][key]
          const y = this.shownAnalysisResults.data[this.shownAnalysisResults.y_label][key]
          const id = this.shownAnalysisResults.data.id[key]
          xs.push(x)
          ys.push(y)
          ids.push(id)
          return { x, y, id }
144
        })
145
146
147
148
149
150
151
152
153
154
155
156
        return { xs, ys, ids, all }
      },
      tmpPoints() {
        const xs = [], ys = [], ids = []
        const all = Object.keys(this.tmpAnalysisResults.data.id).map(key => {
          const x = this.tmpAnalysisResults.data[this.tmpAnalysisResults.x_label][key]
          const y = this.tmpAnalysisResults.data[this.tmpAnalysisResults.y_label][key]
          const id = this.tmpAnalysisResults.data.id[key]
          xs.push(x)
          ys.push(y)
          ids.push(id)
          return { x, y, id }
157
        })
158
        return { xs, ys, ids, all }
159
160
      },
      valid () {
161
        const tmpAnalysisResults = this.tmpPoints.all.length  // TODO
162
        return { tmpAnalysisResults }
163
164
165
      },
      scales () {
        const x = d3.scaleLinear()
166
          .domain(d3.extent(this.shownPoints.xs))
167
168
          .range([0, this.padded.width])
        const y = d3.scaleLinear()
169
          .domain(d3.extent(this.shownPoints.ys))
170
171
172
          .range([this.padded.height, 0])
        return { x, y }
      },
173
174
175
176
177
178
179
180
181
      tmpScales () {
        const x = d3.scaleLinear()
          .domain(d3.extent(this.tmpPoints.xs))
          .range([this.scales.x(d3.min(this.tmpPoints.xs)), this.scales.x(d3.max(this.tmpPoints.xs))])
        const y = d3.scaleLinear()
          .domain(d3.extent(this.tmpPoints.ys))
          .range([this.scales.y(this.tmpPoints.max), this.scales.x(this.tmpPoints.min)])
        return { x, y }
      },
182
183
184
185
186
187
188
189
190
191
192
      axis () {
        const x1 = d3.axisTop(this.scales.x)
        const y1 = d3.axisRight(this.scales.y)
        const x2 = d3.axisBottom(this.scales.x)
          .tickSizeInner(this.padded.height - 23)
          .tickFormat('')
        const y2 = d3.axisLeft(this.scales.y)
          .tickSizeInner(this.padded.width - 23)
          .tickFormat('')
        return { x1, x2, y1, y2 }
      },
193
      regLine () {
194
195
196
        if (! this.valid.tmpAnalysisResults) {
          return { x1: 0, x2: 0, y1: 0, y2: 0 }
        }
197
198
        const minX = d3.min(this.tmpPoints.xs)
        const maxX = d3.max(this.tmpPoints.xs)
199
        let x1 = this.scales.x(minX)
200
        let y1 = this.scales.y(this.tmpAnalysisResults.intercept + this.tmpAnalysisResults.slope * minX)
201
        let x2 = this.scales.x(maxX)
202
        let y2 = this.scales.y(this.tmpAnalysisResults.intercept + this.tmpAnalysisResults.slope * maxX)
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220

        x1 = x1 < 0 ? 0 : x1;
        x1 = x1 > this.width ? this.width : x1;

        x2 = x2 < 0 ? 0 : x2;
        x2 = x2 > this.width ? this.width : x2;

        y1 = y1 < 0 ? 0 : y1;
        y1 = y1 > this.height ? this.height : y1;

        y2 = y2 < 0 ? 0 : y2;
        y2 = y2 > this.height ? this.height : y2;

        return { x1, x2, y1, y2 }
      },
      brush () {
        return d3.brush()
          .extent([[0, 0], [this.padded.width, this.padded.height]])
221
          .on('end', () => {
222
223
224
225
226
            if (! d3.event.selection) {
              this.selectedPoints = []
              this.runAnalysisWrapper({init: false})
              return
            }
227
            const [[x0, y0], [x1, y1]] = d3.event.selection
228
            this.selectedPoints = this.shownPoints.all.filter(d => {
229
230
231
              const x = this.scales.x(d.x)
              const y = this.scales.y(d.y)
              return x0 <= x && x <= x1 && y0 <= y && y <= y1;
232
            })
233
            this.runAnalysisWrapper({init: false})
234
          })
Sascha Herzinger's avatar
Sascha Herzinger committed
235
236
237
      },
      histograms () {
        const xBins = d3.histogram()
238
239
          .domain(this.tmpScales.x.domain())
          .thresholds(this.tmpScales.x.ticks(10))(this.tmpPoints.xs)
Sascha Herzinger's avatar
Sascha Herzinger committed
240
        const yBins = d3.histogram()
241
242
          .domain(this.tmpScales.y.domain())
          .thresholds(this.tmpScales.y.ticks(10))(this.tmpPoints.ys)
Sascha Herzinger's avatar
Sascha Herzinger committed
243
244
245
246
247
248
249
250
251
252
253
254
        return { xBins, yBins }
      },
      histogramScales () {
        // no, I didn't mix up xBins and yBins.
        const x = d3.scaleLinear()
          .domain(d3.extent(this.histograms.yBins.map(d => d.length)))
          .range([0, this.margin.left])
        const y = d3.scaleLinear()
          .domain(d3.extent(this.histograms.xBins.map(d => d.length)))
          .range([0, this.margin.bottom])
        return { x, y }
      },
255
256
    },
    watch: {
257
258
259
260
261
262
263
264
265
266
267
268
      'axis': {
        handler: function(newAxis) {
          d3.select('#x-axis-1').call(newAxis.x1)
          d3.select('#x-axis-2').call(newAxis.x2)
          d3.select('#y-axis-1').call(newAxis.y1)
          d3.select('#y-axis-2').call(newAxis.y2)
        },
        deep: true
      },
      'brush': {
        handler: function(newBrush) {
          d3.select('#brush').call(newBrush)
269
270
271
272
273
274
275
276
277
        },
        deep: true
      },
      'regLine': {
        handler: function(newRegLine, oldRegLine) {
          let coords = oldRegLine
          TweenLite.to(coords, 0.5, Object.assign(newRegLine, {onUpdate: () => { this.tweenedRegLine = coords }}))
        },
        deep: true
278
279
      }
    },
280
281
282
    mounted() {
      window.addEventListener('resize', this.onResize)
      this.onResize() // initial call
283
      this.tmpAnalysisResults = this.shownAnalysisResults
284
285
286
287
    },
    beforeDestroy() {
      window.removeEventListener('resize', this.onResize)
    },
288
289
290
291
292
293
294
    components: {
      DataBox
    },
    mixins: [
      requestHandling
    ],
    methods: {
295
      runAnalysisWrapper ({init}) {
296
        // function made available via requestHandling mixin
297
        this.runAnalysis({job_name: 'compute-correlation', args: this.args})
298
          .then(response => {
299
300
            const results = JSON.parse(response)
            results.data = JSON.parse(results.data)
301
302
303
304
305
306
            if (init) {
              this.shownAnalysisResults = results
              this.tmpAnalysisResults = results
            } else {
              this.tmpAnalysisResults = results
            }
307
          })
308
309
310
311
312
313
          .catch(error => console.error(error))
      },
      onResize () {
        const section = this.$el.querySelector('#visualisation-section')
        this.height = section.clientHeight
        this.width = section.clientWidth
314
315
316
317
318
319
      },
      update_xyData (ids) {
        this.xyData = ids
      },
      update_annotationData (ids) {
        this.annotationData = ids
320
      }
321
322
323
324
325
326
    }
  }
</script>


<style scoped>
327
  #data-box-section {
328
329
330
331
    display: flex;
    flex-direction: row;
    justify-content: space-around;
  }
332

333
334
335
336
  #run-analysis-btn {
    width: 100%;
    height: 20px;
  }
337
338
339
340
341
342
343
344
345
346
347
348

  #lin-reg-line {
    stroke: #ff5e00;
    stroke-width: 4px;
  }
</style>

<!--CSS for dynamically created components-->
<style>
  .fjs-corr-axis .tick {
    shape-rendering: crispEdges;
  }
349
</style>