CorrelationAnalysis.vue 8.44 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 points.shown">
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>
41
42
43
44
        </g>
      </svg>
    </div>

45
  </div>
46
47
48
49
</template>


<script>
50
51
  import DataBox from '../DataBox.vue'
  import requestHandling from '../mixins/request-handling'
52
  import * as d3 from 'd3'
53
  import { TweenLite } from 'gsap'
54
55
  export default {
    name: 'correlation-analysis',
56
    data () {
57
      return {
58
59
        width: 0,
        height: 0,
60
61
62
63
64
65
66
        margin: {
          left: 50,
          top: 50,
          right: 50,
          bottom: 50
        },

67
68
69
70
71
        xyData: [],
        annotationData: [],
        get args () {
          return {
            x: `$${this.xyData[0]}$`,
72
73
            y: `$${this.xyData[1]}$`,
            ids: this.selectedPoints.map(d => d.id)
74
          }
75
        },
76

77
        shownAnalysisResults: {
78
79
80
81
82
83
84
85
86
87
88
89
90
91
          coef: 0,
          p_value: 0,
          slope: 0,
          intercept: 0,
          method: '',
          x_label: '',
          y_label: '',
          get data() {
            return {
              id: [],
              [this.x_label]: [],
              [this.y_label]: []
            }
          }
92
        },
93
94
95
        tmpAnalysisResults: {},
        selectedPoints: [],
        tweenedRegLine: {}
96
97
      }
    },
98
99
100
    computed: {
      disabled () {
        return this.xyData.length !== 2
101
102
103
104
105
      },
      padded () {
        const width = this.width - this.margin.left - this.margin.right
        const height = this.height - this.margin.top - this.margin.bottom
        return { width, height }
106
107
      },
      points () {
108
        const shown = Object.keys(this.shownAnalysisResults.data.id).map(key => {
109
          return {
110
111
112
            x: this.shownAnalysisResults.data[this.shownAnalysisResults.x_label][key],
            y: this.shownAnalysisResults.data[this.shownAnalysisResults.y_label][key],
            id: this.shownAnalysisResults.data.id[key]
113
114
          }
        })
115
116
117
118
119
120
121
122
123
124
125
126
        const temp = Object.keys(this.shownAnalysisResults.data.id).map(key => {
          return {
            x: this.shownAnalysisResults.data[this.shownAnalysisResults.x_label][key],
            y: this.shownAnalysisResults.data[this.shownAnalysisResults.y_label][key],
            id: this.shownAnalysisResults.data.id[key]
          }
        })
        return { shown, temp }
      },
      valid () {
        const tmpAnalysisResults = Object.keys(this.tmpAnalysisResults).length && this.points.temp.length
        return { tmpAnalysisResults }
127
128
129
      },
      scales () {
        const x = d3.scaleLinear()
130
          .domain(d3.extent(this.points.shown.map(d => d.x)))
131
132
          .range([0, this.padded.width])
        const y = d3.scaleLinear()
133
          .domain(d3.extent(this.points.shown.map(d => d.y)))
134
135
136
137
138
139
140
141
142
143
144
145
146
147
          .range([this.padded.height, 0])
        return { x, y }
      },
      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 }
      },
148
      regLine () {
149
150
151
152
        if (! this.valid.tmpAnalysisResults) {
          return { x1: 0, x2: 0, y1: 0, y2: 0 }
        }
        const xarr = this.points.temp.map(d => d.x)
153
154
155
        const minX = Math.min.apply(null, xarr)
        const maxX = Math.max.apply(null, xarr)
        let x1 = this.scales.x(minX)
156
        let y1 = this.scales.y(this.tmpAnalysisResults.intercept + this.tmpAnalysisResults.slope * minX)
157
        let x2 = this.scales.x(maxX)
158
        let y2 = this.scales.y(this.tmpAnalysisResults.intercept + this.tmpAnalysisResults.slope * maxX)
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176

        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]])
177
          .on('end', () => {
178
179
180
181
182
            if (! d3.event.selection) {
              this.selectedPoints = []
              this.runAnalysisWrapper({init: false})
              return
            }
183
            const [[x0, y0], [x1, y1]] = d3.event.selection
184
            this.selectedPoints = this.points.shown.filter(d => {
185
186
187
              const x = this.scales.x(d.x)
              const y = this.scales.y(d.y)
              return x0 <= x && x <= x1 && y0 <= y && y <= y1;
188
            })
189
            this.runAnalysisWrapper({init: false})
190
          })
191
192
193
      }
    },
    watch: {
194
195
196
197
198
199
200
201
202
203
204
205
      '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)
206
207
208
209
210
211
212
213
214
        },
        deep: true
      },
      'regLine': {
        handler: function(newRegLine, oldRegLine) {
          let coords = oldRegLine
          TweenLite.to(coords, 0.5, Object.assign(newRegLine, {onUpdate: () => { this.tweenedRegLine = coords }}))
        },
        deep: true
215
216
      }
    },
217
218
219
    mounted() {
      window.addEventListener('resize', this.onResize)
      this.onResize() // initial call
220
      this.tmpAnalysisResults = this.shownAnalysisResults
221
222
223
224
    },
    beforeDestroy() {
      window.removeEventListener('resize', this.onResize)
    },
225
226
227
228
229
230
231
    components: {
      DataBox
    },
    mixins: [
      requestHandling
    ],
    methods: {
232
      runAnalysisWrapper ({init}) {
233
        // function made available via requestHandling mixin
234
        this.runAnalysis({job_name: 'compute-correlation', args: this.args})
235
          .then(response => {
236
237
            const results = JSON.parse(response)
            results.data = JSON.parse(results.data)
238
239
240
241
242
243
            if (init) {
              this.shownAnalysisResults = results
              this.tmpAnalysisResults = results
            } else {
              this.tmpAnalysisResults = results
            }
244
          })
245
246
247
248
249
250
          .catch(error => console.error(error))
      },
      onResize () {
        const section = this.$el.querySelector('#visualisation-section')
        this.height = section.clientHeight
        this.width = section.clientWidth
251
252
253
254
255
256
      },
      update_xyData (ids) {
        this.xyData = ids
      },
      update_annotationData (ids) {
        this.annotationData = ids
257
      }
258
259
260
261
262
263
    }
  }
</script>


<style scoped>
264
  #data-box-section {
265
266
267
268
    display: flex;
    flex-direction: row;
    justify-content: space-around;
  }
269

270
271
272
273
  #run-analysis-btn {
    width: 100%;
    height: 20px;
  }
274
275
276
277
278
279
280
281
282
283
284
285

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

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