From 9b8457beb3cb2f80d7f56555253943ce50a0922c Mon Sep 17 00:00:00 2001
From: Xiao Gui <xgui3783@gmail.com>
Date: Fri, 5 Feb 2021 16:06:58 +0100
Subject: [PATCH] added tests to plotly

---
 spec/test.ts                 |   3 +
 webpack/webpack.common.js    |  10 --
 worker/worker-plotly.js      | 194 +++++++++++++++++++++++++++++++
 worker/worker-plotly.spec.js |  36 ++++++
 worker/worker.js             | 216 ++++-------------------------------
 5 files changed, 254 insertions(+), 205 deletions(-)
 create mode 100644 worker/worker-plotly.js
 create mode 100644 worker/worker-plotly.spec.js

diff --git a/spec/test.ts b/spec/test.ts
index 16b7c5500..478be25bb 100644
--- a/spec/test.ts
+++ b/spec/test.ts
@@ -21,4 +21,7 @@ getTestBed().initTestEnvironment(
 const testContext = require.context('../src', true, /\.spec\.ts$/)
 testContext.keys().map(testContext)
 
+const workerCtx = require.context('../worker', true, /\.spec\.js$/)
+workerCtx.keys().map(workerCtx)
+
 require('../common/util.spec.js')
diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js
index dee98b82a..1896b962e 100644
--- a/webpack/webpack.common.js
+++ b/webpack/webpack.common.js
@@ -9,16 +9,6 @@ module.exports = {
         loaders : ['ts-loader','angular2-template-loader?keepUrl=true'],
         exclude : /node_modules|[Ss]pec\.ts$/
       },
-      {
-        test : /third_party|.*?worker.*?\.js$/,
-        exclude: /[Ss]pec\.js$/,
-        use : {
-          loader : 'file-loader',
-          options: {
-            name : '[name].[ext]'
-          }
-        }
-      }
     ]
   },
   plugins : [
diff --git a/worker/worker-plotly.js b/worker/worker-plotly.js
new file mode 100644
index 000000000..3c7a6be2a
--- /dev/null
+++ b/worker/worker-plotly.js
@@ -0,0 +1,194 @@
+(function(exports){
+  /**
+   * units in mm --> convert to nm
+   */
+  const plotyMultiple = 1e6
+  const { vtkHeader } = globalThis.constants || {}
+
+  const getPerpendicularPointsForLine = (A, B, scale) => {
+
+    const lineWeight = 1e6
+    const lineWidth = scale * lineWeight
+    const lineHeight = scale * lineWeight
+  
+    let u = A.map((item, index) => {
+      return item - B[index];
+    })
+    const uLength = Math.sqrt((u[0] * u[0]) + (u[1] * u[1]) + (u[2] * u[2]))
+    u = u.map((item, index) => {
+      return item/uLength
+    })
+  
+    const n = []
+    if(Math.abs(u[0]) <= Math.abs(u[1]) && Math.abs(u[0]) <= Math.abs(u[2])) {
+      n[0] = u[1] * u[1] + u[2] * u[2]
+      n[1] = -u[1] * u[0]
+      n[2] = -u[2] * u[0]
+    }
+    else if(Math.abs(u[1])<=Math.abs(u[0])&&Math.abs(u[1])<=Math.abs(u[2]))
+    {
+      n[0] = -u[0] * u[2]
+      n[1] = u[0] * u[0] + u[2] * u[2]
+      n[2] = -u[2] * u[1]
+    }
+    else if(Math.abs(u[2])<=Math.abs(u[0])&&Math.abs(u[2])<=Math.abs(u[1]))
+    {
+      n[0] = -u[0] * u[2]
+      n[1] = -u[1] * u[2]
+      n[2] = u[0] * u[0] + u[1] * u[1]
+    }
+  
+    const v = [ u[1] * n[2] - u[2] * n[1], u[2] * n[0] - u[0] * n[2], u[0] * n[1] - u[1] * n[0] ]
+  
+    const RMul = (k) => {
+      const res = []
+      res[0] = v[0]*k[0] + n[0]*k[1] + u[0]*k[2]
+      res[1] = v[1]*k[0] + n[1]*k[1] + u[1]*k[2]
+      res[2] = v[2]*k[0] + n[2]*k[1] + u[2]*k[2]
+      return res
+    }
+  
+    const sumArrays = (a1, a2) => {
+      return a1.map((item, index) => {
+        return item + a2[index];
+      })
+    }
+  
+    const a = sumArrays(A, RMul([lineWidth,lineHeight,0]))
+    const b = sumArrays(A, RMul([-lineWidth,lineHeight,0]))
+    const c = sumArrays(A, RMul([lineWidth,-lineHeight,0]))
+    const d = sumArrays(A, RMul([-lineWidth,-lineHeight,0]))
+  
+    const e = sumArrays(B, RMul([lineWidth,lineHeight,0]))
+    const f = sumArrays(B, RMul([-lineWidth,lineHeight,0]))
+    const g = sumArrays(B, RMul([lineWidth,-lineHeight,0]))
+    const h = sumArrays(B, RMul([-lineWidth,-lineHeight,0]))
+  
+    return `${a.join(' ')}\n ${b.join(' ')}\n ${c.join(' ')}\n ${d.join(' ')}\n ${e.join(' ')}\n ${f.join(' ')}\n ${g.join(' ')}\n ${h.join(' ')}\n `
+  }
+
+  const getFragmentColorString = (colors) => {
+  
+    const hexToRgb = (hex) => {
+      const [r, g, b] = hex.match(/\w\w/g).map(x => parseInt(x, 16))
+      return `emitRGB(vec3(${r/255}, ${g/255}, ${b/255}))`
+    }
+  
+    const colorsUnique = colors.filter((cf, i) => colors[i-1] !== cf)
+      .map((color, j) => {
+        return `if (label > ${j - 0.01} && label < ${j + 0.01}) { ${hexToRgb(color)}; }`
+      })
+  
+    const fragmentColorString = `${colorsUnique.join(' else ')} else {emitRGB(vec3(1.0, 0.1, 0.12));}`
+    return fragmentColorString
+  }
+
+  const getLineDataVtkPolygonStringWithNumber = (neuronCoordinateLength) => {
+    let returnString = ''
+    for (let i = 0; i < neuronCoordinateLength; i++) {
+      const neuronNumber = 8*i
+      returnString +=
+        `3 ${0 + neuronNumber} ${1 + neuronNumber} ${3 + neuronNumber}\n` +
+        `3 ${0 + neuronNumber} ${2 + neuronNumber} ${3 + neuronNumber}\n` +
+        `3 ${4 + neuronNumber} ${5 + neuronNumber} ${7 + neuronNumber}\n` +
+        `3 ${4 + neuronNumber} ${6 + neuronNumber} ${7 + neuronNumber}\n` +
+        `3 ${2 + neuronNumber} ${6 + neuronNumber} ${7 + neuronNumber}\n` +
+        `3 ${2 + neuronNumber} ${3 + neuronNumber} ${7 + neuronNumber}\n` +
+        `3 ${3 + neuronNumber} ${1 + neuronNumber} ${7 + neuronNumber}\n` +
+        `3 ${1 + neuronNumber} ${5 + neuronNumber} ${7 + neuronNumber}\n` +
+        `3 ${2 + neuronNumber} ${0 + neuronNumber} ${6 + neuronNumber}\n` +
+        `3 ${0 + neuronNumber} ${4 + neuronNumber} ${6 + neuronNumber}\n` +
+        `3 ${1 + neuronNumber} ${0 + neuronNumber} ${4 + neuronNumber}\n` +
+        `3 ${1 + neuronNumber} ${4 + neuronNumber} ${5 + neuronNumber}\n`
+    }
+    return returnString
+  }
+
+  const getColorIds = (colors) => {
+    let returnString = ''
+  
+    let colorId = 0
+  
+    for (let i=0; i < colors.length; i++){
+      if (i > 0 && colors[i] !== colors[i-1]) {
+        colorId += 1
+      }
+      for (let j=0; j < 8; j++){
+        if (i === colors.length-1 && j === 7) {
+          returnString += colorId
+        } else {
+          returnString += colorId + '\n'
+        }
+      }
+    }
+  
+    return returnString
+  }
+  
+  const parseLineDataToVtk = (data, scale= 1, plotyMultiple) => {
+    const lineCoordinates = []
+    const colors = []
+
+    for (let i = 1; i < data.x.length; i++) {
+
+      if (data.x[i] !== null && data.x[i-1] !== null) {
+        lineCoordinates.push([[
+          data.x[i-1] * plotyMultiple,
+          data.y[i-1] * plotyMultiple,
+          data.z[i-1] * plotyMultiple,
+        ], [
+          data.x[i] * plotyMultiple,
+          data.y[i] * plotyMultiple,
+          data.z[i] * plotyMultiple,
+        ]])
+
+        colors.push(data.marker.color[i-1])
+      }
+    }
+
+    const coordinateLength = lineCoordinates.length
+
+    const lineCoordinatesArrayToString = (() => {
+      let returnString = ''
+      lineCoordinates.forEach(lc => {
+        returnString += getPerpendicularPointsForLine(lc[0], lc[1], scale)
+      })
+      return returnString
+    })()
+
+    const customFragmentColor = getFragmentColorString(colors)
+
+    const vtkString = `${vtkHeader}\n` +
+      `POINTS ${coordinateLength*8} float\n` +
+      lineCoordinatesArrayToString +
+      `POLYGONS ${coordinateLength*12} ${coordinateLength*48}\n` +
+      getLineDataVtkPolygonStringWithNumber(coordinateLength) +
+      `POINT_DATA ${coordinateLength*8}\n` +
+      'SCALARS label unsigned_char 1\n' +
+      'LOOKUP_TABLE none\n' +
+      getColorIds(colors)
+
+    return {vtkString, customFragmentColor}
+  }
+
+  exports.plotly = {
+    plotyMultiple,
+    convert: plotlyData => {
+      const { x, y, z } = plotlyData.traces[0]
+      const lm = []
+      for (const idx in x) {
+        if (typeof x !== 'undefined' && x !== null) {
+          lm.push([x[idx]*plotyMultiple, y[idx]*plotyMultiple, z[idx]*plotyMultiple])
+        }
+      }
+      const { vtkString, customFragmentColor } = parseLineDataToVtk(plotlyData.traces[0], 5e-3, plotyMultiple)
+
+      return {
+        vtkString,
+        customFragmentColor,
+      }
+    }
+  }
+})(
+  typeof exports === 'undefined' ? module.exports : exports
+)
diff --git a/worker/worker-plotly.spec.js b/worker/worker-plotly.spec.js
new file mode 100644
index 000000000..c00f81922
--- /dev/null
+++ b/worker/worker-plotly.spec.js
@@ -0,0 +1,36 @@
+const vtkHeader = `# vtk DataFile Version 2.0
+Created by worker thread at https://github.com/HumanBrainProject/interactive-viewer
+ASCII
+DATASET POLYDATA`
+
+globalThis.constants = {
+  vtkHeader,
+}
+
+const { plotly } = require('./worker-plotly')
+
+const input = {
+  traces: [{
+    "type":"scatter3d",
+    "mode":"markers",
+    "name":"points",
+    "x":[-0.9557710289955139,null,-0.9557710289955139,-0.972806990146637,null],
+    "y":[4.165919780731201,null,4.165919780731201,4.160162925720215,null],
+    "z":[1.925400972366333,null,1.925400972366333,1.9260079860687256,null],"opacity":1,
+    "marker":{"size":1,"color":["#00dd00",null,"#ff0000","#ff0000",null],
+    "reversescale":false}
+  }]  
+}
+const expectedOutput = {
+  customFragmentColor: "if (label > -0.01 && label < 0.01) { emitRGB(vec3(1, 0, 0)); } else {emitRGB(vec3(1.0, 0.1, 0.12));}",
+  vtkString: "# vtk DataFile Version 2.0\nCreated by worker thread at https://github.com/HumanBrainProject/interactive-viewer\nASCII\nDATASET POLYDATA\nPOINTS 8 float\n-954011.5298146864 4161239.595879443 1930395.281492923\n -957211.0971719137 4170707.908889603 1930395.281492923\n -954330.9608191141 4161131.6525727995 1920406.663239743\n -957530.5281763414 4170599.9655829594 1920406.663239743\n -971047.4909658094 4155482.7408684567 1931002.2951953155\n -974247.0583230368 4164951.0538786165 1931002.2951953155\n -971366.9219702372 4155374.797561813 1921013.6769421357\n -974566.4893274645 4164843.110571973 1921013.6769421357\n POLYGONS 12 48\n3 0 1 3\n3 0 2 3\n3 4 5 7\n3 4 6 7\n3 2 6 7\n3 2 3 7\n3 3 1 7\n3 1 5 7\n3 2 0 6\n3 0 4 6\n3 1 0 4\n3 1 4 5\nPOINT_DATA 8\nSCALARS label unsigned_char 1\nLOOKUP_TABLE none\n0\n0\n0\n0\n0\n0\n0\n0"
+}
+
+describe('> worker-plotly.js', () => {
+  it('> expect input === output', () => {
+
+    expect(
+      plotly.convert(input)
+    ).toEqual(expectedOutput)
+  })
+})
diff --git a/worker/worker.js b/worker/worker.js
index 973d7831c..e60464fa3 100644
--- a/worker/worker.js
+++ b/worker/worker.js
@@ -1,4 +1,18 @@
-self.importScripts('./worker-second.js')
+const vtkHeader = `# vtk DataFile Version 2.0
+Created by worker thread at https://github.com/HumanBrainProject/interactive-viewer
+ASCII
+DATASET POLYDATA`
+
+globalThis.constants = {
+  vtkHeader
+}
+
+if (typeof self.importScripts === 'function')  self.importScripts('./worker-second.js')
+
+/**
+ * TODO migrate processing functionalities to other scripts
+ * see worker-plotly.js
+ */
 
 const validTypes = [
   'GET_LANDMARKS_VTK',
@@ -22,11 +36,6 @@ const validOutType = [
   'UPDATE_PARCELLATION_REGIONS'
 ]
 
-const vtkHeader = `# vtk DataFile Version 2.0
-Created by worker thread at https://github.com/HumanBrainProject/interactive-viewer
-ASCII
-DATASET POLYDATA`
-
 const getVertexHeader = (numVertex) => `POINTS ${numVertex} float`
 
 const getPolyHeader = (numPoly) => `POLYGONS ${numPoly} ${4 * numPoly}`
@@ -294,204 +303,21 @@ const processParcRegionAttr = (payload) => {
   })
 }
 
-const parseLineDataToVtk = (data, scale= 1, plotyMultiple) => {
-  const lineCoordinates = []
-  const colors = []
-
-  for (let i = 1; i < data.x.length; i++) {
-
-    if (data.x[i] !== null && data.x[i-1] !== null) {
-      lineCoordinates.push([[
-        data.x[i-1] * plotyMultiple,
-        data.y[i-1] * plotyMultiple,
-        data.z[i-1] * plotyMultiple,
-      ], [
-        data.x[i] * plotyMultiple,
-        data.y[i] * plotyMultiple,
-        data.z[i] * plotyMultiple,
-      ]])
-
-      colors.push(data.marker.color[i-1])
-    }
-  }
-
-  const coordinateLength = lineCoordinates.length
-
-  const lineCoordinatesArrayToString = (() => {
-    let returnString = ''
-    lineCoordinates.forEach(lc => {
-      returnString += getPerpendicularPointsForLine(lc[0], lc[1], scale)
-    })
-    return returnString
-  })()
-
-  const customFragmentColor = getFragmentColorString(colors)
-
-  const vtkString = `${vtkHeader}\n` +
-    `POINTS ${coordinateLength*8} float\n` +
-    lineCoordinatesArrayToString +
-    `POLYGONS ${coordinateLength*12} ${coordinateLength*48}\n` +
-    getLineDataVtkPolygonStringWithNumber(coordinateLength) +
-    `POINT_DATA ${coordinateLength*8}\n` +
-    'SCALARS label unsigned_char 1\n' +
-    'LOOKUP_TABLE none\n' +
-    getColorIds(colors)
-
-  return {vtkString, customFragmentColor}
-}
-
-const getFragmentColorString = (colors) => {
-
-  const hexToRgb = (hex) => {
-    const [r, g, b] = hex.match(/\w\w/g).map(x => parseInt(x, 16))
-    return `emitRGB(vec3(${r/255}, ${g/255}, ${b/255}))`
-  }
-
-  const colorsUnique = colors.filter((cf, i) => colors[i-1] !== cf)
-    .map((color, j) => {
-      return `if (label > ${j - 0.01} && label < ${j + 0.01}) { ${hexToRgb(color)}; }`
-    })
-
-  const fragmentColorString = `${colorsUnique.join(' else ')} else {emitRGB(vec3(1.0, 0.1, 0.12));}`
-  return fragmentColorString
-}
-
-const getColorIds = (colors) => {
-  let returnString = ''
-
-  let colorId = 0
-
-  for (let i=0; i < colors.length; i++){
-    if (i > 0 && colors[i] !== colors[i-1]) {
-      colorId += 1
-    }
-    for (let j=0; j < 8; j++){
-      if (i === colors.length-1 && j === 7) {
-        returnString += colorId
-      } else {
-        returnString += colorId + '\n'
-      }
-    }
-  }
-
-  return returnString
-}
-
-const getLineDataVtkPolygonStringWithNumber = (neuronCoordinateLength) => {
-  let returnString = ''
-  for (let i = 0; i < neuronCoordinateLength; i++) {
-    const neuronNumber = 8*i
-    returnString +=
-      `3 ${0 + neuronNumber} ${1 + neuronNumber} ${3 + neuronNumber}\n` +
-      `3 ${0 + neuronNumber} ${2 + neuronNumber} ${3 + neuronNumber}\n` +
-      `3 ${4 + neuronNumber} ${5 + neuronNumber} ${7 + neuronNumber}\n` +
-      `3 ${4 + neuronNumber} ${6 + neuronNumber} ${7 + neuronNumber}\n` +
-      `3 ${2 + neuronNumber} ${6 + neuronNumber} ${7 + neuronNumber}\n` +
-      `3 ${2 + neuronNumber} ${3 + neuronNumber} ${7 + neuronNumber}\n` +
-      `3 ${3 + neuronNumber} ${1 + neuronNumber} ${7 + neuronNumber}\n` +
-      `3 ${1 + neuronNumber} ${5 + neuronNumber} ${7 + neuronNumber}\n` +
-      `3 ${2 + neuronNumber} ${0 + neuronNumber} ${6 + neuronNumber}\n` +
-      `3 ${0 + neuronNumber} ${4 + neuronNumber} ${6 + neuronNumber}\n` +
-      `3 ${1 + neuronNumber} ${0 + neuronNumber} ${4 + neuronNumber}\n` +
-      `3 ${1 + neuronNumber} ${4 + neuronNumber} ${5 + neuronNumber}\n`
-  }
-  return returnString
-}
-
-const getPerpendicularPointsForLine = (A, B, scale) => {
-
-  const lineWeight = 1e6
-  const lineWidth = scale * lineWeight
-  const lineHeight = scale * lineWeight
-
-  let u = A.map((item, index) => {
-    return item - B[index];
-  })
-  const uLength = Math.sqrt((u[0] * u[0]) + (u[1] * u[1]) + (u[2] * u[2]))
-  u = u.map((item, index) => {
-    return item/uLength
-  })
-
-  const n = []
-  if(Math.abs(u[0]) <= Math.abs(u[1]) && Math.abs(u[0]) <= Math.abs(u[2])) {
-    n[0] = u[1] * u[1] + u[2] * u[2]
-    n[1] = -u[1] * u[0]
-    n[2] = -u[2] * u[0]
-  }
-  else if(Math.abs(u[1])<=Math.abs(u[0])&&Math.abs(u[1])<=Math.abs(u[2]))
-  {
-    n[0] = -u[0] * u[2]
-    n[1] = u[0] * u[0] + u[2] * u[2]
-    n[2] = -u[2] * u[1]
-  }
-  else if(Math.abs(u[2])<=Math.abs(u[0])&&Math.abs(u[2])<=Math.abs(u[1]))
-  {
-    n[0] = -u[0] * u[2]
-    n[1] = -u[1] * u[2]
-    n[2] = u[0] * u[0] + u[1] * u[1]
-  }
-
-  const v = [ u[1] * n[2] - u[2] * n[1], u[2] * n[0] - u[0] * n[2], u[0] * n[1] - u[1] * n[0] ]
-
-  const RMul = (k) => {
-    const res = []
-    res[0] = v[0]*k[0] + n[0]*k[1] + u[0]*k[2]
-    res[1] = v[1]*k[0] + n[1]*k[1] + u[1]*k[2]
-    res[2] = v[2]*k[0] + n[2]*k[1] + u[2]*k[2]
-    return res
-  }
-
-  const sumArrays = (a1, a2) => {
-    return a1.map((item, index) => {
-      return item + a2[index];
-    })
-  }
-
-  const a = sumArrays(A, RMul([lineWidth,lineHeight,0]))
-  const b = sumArrays(A, RMul([-lineWidth,lineHeight,0]))
-  const c = sumArrays(A, RMul([lineWidth,-lineHeight,0]))
-  const d = sumArrays(A, RMul([-lineWidth,-lineHeight,0]))
-
-  const e = sumArrays(B, RMul([lineWidth,lineHeight,0]))
-  const f = sumArrays(B, RMul([-lineWidth,lineHeight,0]))
-  const g = sumArrays(B, RMul([lineWidth,-lineHeight,0]))
-  const h = sumArrays(B, RMul([-lineWidth,-lineHeight,0]))
-
-  return `${a.join(' ')}\n ${b.join(' ')}\n ${c.join(' ')}\n ${d.join(' ')}\n ${e.join(' ')}\n ${f.join(' ')}\n ${g.join(' ')}\n ${h.join(' ')}\n `
-}
-
-
 let plotyVtkUrl
 
 onmessage = (message) => {
 
-  if (message.data.method === 'ping') {
-    return postMessage({
-      id: message.data.id,
-      result: `pong ${self.meaningOfLife}`
-    })
-  }
-
   if (message.data.method && VALID_METHODS.indexOf(message.data.method) >= 0) {
     const { id } = message.data
     if (message.data.method === VALID_METHOD.PROCESS_PLOTLY) {
-      /**
-       * units in mm --> convert to nm
-       */
-      const plotyMultiple=1e6
       try {
-        const { data: plotlyData } = message.data.param
-        const { x, y, z } = plotlyData.traces[0]
-        const lm = []
-        for (const idx in x) {
-          if (typeof x !== 'undefined' && x !== null) {
-            lm.push([x[idx]*plotyMultiple, y[idx]*plotyMultiple, z[idx]*plotyMultiple])
-          }
-        }
         if (plotyVtkUrl) URL.revokeObjectURL(plotyVtkUrl)
-        const { vtkString, customFragmentColor} = parseLineDataToVtk(plotlyData.traces[0], 5e-3, plotyMultiple)
-
-        plotyVtkUrl = URL.createObjectURL(
+        const { data: plotlyData } = message.data.param
+        const {
+          vtkString,
+          customFragmentColor
+        } = self.plotly.convert(plotlyData)
+        const plotyVtkUrl = URL.createObjectURL(
           new Blob([ encoder.encode(vtkString) ], { type: 'application/octet-stream' })
         )
         postMessage({
-- 
GitLab