change directory name and run scripts to account for name change
This commit is contained in:
25
visualization_interface/.gitignore
vendored
Normal file
25
visualization_interface/.gitignore
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
/flask-server/__pycache__
|
||||
70
visualization_interface/README.md
Normal file
70
visualization_interface/README.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Getting Started with Create React App
|
||||
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm start`
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
|
||||
|
||||
The page will reload when you make changes.\
|
||||
You may also see any lint errors in the console.
|
||||
|
||||
### `npm test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.\
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `build` folder.\
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.\
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `npm run eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
|
||||
|
||||
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
|
||||
|
||||
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
|
||||
### Code Splitting
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
|
||||
|
||||
### Analyzing the Bundle Size
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
|
||||
|
||||
### Making a Progressive Web App
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
|
||||
|
||||
### Deployment
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
|
||||
|
||||
### `npm run build` fails to minify
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
|
||||
203
visualization_interface/flask-server/process_input_data.py
Normal file
203
visualization_interface/flask-server/process_input_data.py
Normal file
@@ -0,0 +1,203 @@
|
||||
import pandas as pd
|
||||
import json
|
||||
from os.path import exists
|
||||
|
||||
def read_derivedAttr(ar):
|
||||
derivedFilename = ar+"/derived_variables/derived_output.csv"
|
||||
if not exists(derivedFilename):
|
||||
return pd.DataFrame()
|
||||
derived_df = pd.read_csv(derivedFilename)
|
||||
facial_cols = [col for col in derived_df if "fac_" in col]
|
||||
acoustic_cols = [col for col in derived_df if "aco_" in col]
|
||||
movement_cols = [col for col in derived_df if "mov_" in col]
|
||||
nlp_cols = [col for col in derived_df if "nlp_" in col]
|
||||
derived_facial = derived_df.loc[:,derived_df.columns.isin(facial_cols)]
|
||||
derived_acoustic = derived_df.loc[:,derived_df.columns.isin(acoustic_cols)]
|
||||
derived_movement = derived_df.loc[:,derived_df.columns.isin(movement_cols)]
|
||||
derived_nlp = derived_df.loc[:,derived_df.columns.isin(nlp_cols)]
|
||||
ids = derived_df["Filename"].tolist()
|
||||
return {"ids": ids,
|
||||
"facialAttr": facial_cols, "acousticAttr": acoustic_cols, "movementAttr": movement_cols, "speechAttr": nlp_cols}
|
||||
|
||||
def read_medatada(ar, ar2):
|
||||
metaData={}
|
||||
metdataFilename = ar+"/"+ar2
|
||||
if exists(metdataFilename):
|
||||
metadataDf = pd.read_csv(metdataFilename)
|
||||
metaData = metadataDf.to_json(orient="records")
|
||||
return metaData
|
||||
|
||||
def read_derivedDf(ar):
|
||||
derivedFilename = ar+"/derived_variables/derived_output.csv"
|
||||
if not exists(derivedFilename):
|
||||
return pd.DataFrame()
|
||||
derived_df = pd.read_csv(derivedFilename)
|
||||
return derived_df
|
||||
|
||||
|
||||
def read_rawFacialDf(ar, id):
|
||||
skip_cols = ["frame", "face_id", "error_reason", "timestamp", "confidence", "success", "dbm_master_url", "error_reason", " confidence", " face_id", " success", " timestamp", "s_confidence", ]
|
||||
|
||||
facial_asym_filename = ar + "/raw_variables/"+id+"/facial/face_asymmetry/"+id+"_facasym.csv"
|
||||
facial_au_filename = ar + "/raw_variables/"+id+"/facial/face_au/"+id+"_facau.csv"
|
||||
facial_expr_filename = ar + "/raw_variables/"+id+"/facial/face_expressivity/"+id+"_facemo.csv"
|
||||
landmark_filename = ar + "/raw_variables/"+id+"/facial/face_landmark/"+id+"_faclmk.csv"
|
||||
|
||||
if not exists(facial_asym_filename) or not exists(facial_au_filename) or not exists(facial_expr_filename):
|
||||
return pd.DataFrame()
|
||||
|
||||
facial_asym = pd.read_csv(facial_asym_filename)
|
||||
facial_asym_cols = [col for col in facial_asym if col not in skip_cols]
|
||||
|
||||
facial_au = pd.read_csv(facial_au_filename)
|
||||
facial_au_cols = [col for col in facial_au if col not in skip_cols]
|
||||
|
||||
facial_expr = pd.read_csv(facial_expr_filename)
|
||||
facial_expr_cols = [col for col in facial_expr if col not in skip_cols and "AU" not in col]
|
||||
|
||||
landmark = pd.read_csv(landmark_filename)
|
||||
landmark_cols = [col for col in landmark if col not in skip_cols]
|
||||
|
||||
face_df = landmark.loc[:, ~landmark.columns.isin(skip_cols)].copy()
|
||||
for el in facial_expr_cols:
|
||||
face_df[el] = facial_expr[el]
|
||||
for el in facial_au_cols:
|
||||
face_df[el] = facial_au[el]
|
||||
for el in facial_asym_cols:
|
||||
face_df[el] = facial_asym[el]
|
||||
return face_df[face_df.columns[::-1]].fillna(0)
|
||||
|
||||
|
||||
|
||||
def read_rawMovementDf(ar, id):
|
||||
skip_cols = ["error_reason", "dbm_master_url", "Frames", " Frames", "vid_dur", "fps" ]
|
||||
|
||||
gaze_filename = ar + "/raw_variables/"+id+"/movement/gaze/"+id+"_eyegaze.csv"
|
||||
head_movement_filename = ar + "/raw_variables/"+id+"/movement/head_movement/"+id+"_headmov.csv"
|
||||
head_pose_filename = ar + "/raw_variables/"+id+"/movement/head_pose/"+id+"_headpose.csv"
|
||||
|
||||
blinks_filename = ar + "/raw_variables/"+id+"/movement/eye_blink/"+id+"_eyeblinks.csv"
|
||||
fac_tremor_filename = ar + "/raw_variables/"+id+"/movement/facial_tremor/"+id+"_fac_tremor.csv"
|
||||
voice_tremor_filename = ar + "/raw_variables/"+id+"/movement/voice_tremor/"+id+"_vtremor.csv"
|
||||
|
||||
if not exists(gaze_filename) or not exists(head_movement_filename) or not exists(head_pose_filename):
|
||||
return pd.DataFrame()
|
||||
|
||||
gaze = pd.read_csv(gaze_filename)
|
||||
gaze_cols = [col for col in gaze if col not in skip_cols]
|
||||
|
||||
head_movement = pd.read_csv(head_movement_filename)
|
||||
head_movement_cols = [col for col in head_movement if col not in skip_cols]
|
||||
|
||||
|
||||
blinks = pd.read_csv(blinks_filename)
|
||||
blinks_cols = [col for col in blinks if col not in skip_cols]
|
||||
|
||||
fac_tremor = pd.read_csv(fac_tremor_filename)
|
||||
fac_tremor_cols = [col for col in fac_tremor if col not in skip_cols]
|
||||
|
||||
voice_tremor = pd.read_csv(voice_tremor_filename)
|
||||
voice_tremor_cols = [col for col in voice_tremor if col not in skip_cols]
|
||||
|
||||
|
||||
head_pose = pd.read_csv(head_pose_filename)
|
||||
head_pose_cols = [col for col in head_pose if col not in skip_cols]
|
||||
|
||||
movement_df = head_pose.loc[:, ~head_pose.columns.isin(skip_cols)].copy()
|
||||
for el in head_movement_cols:
|
||||
movement_df[el] = head_movement[el]
|
||||
for el in gaze_cols:
|
||||
movement_df[el] = gaze[el]
|
||||
for el in blinks_cols:
|
||||
movement_df[el] = blinks[el]
|
||||
for el in fac_tremor_cols:
|
||||
movement_df[el] = fac_tremor[el]
|
||||
for el in voice_tremor_cols:
|
||||
movement_df[el] = voice_tremor[el]
|
||||
return movement_df.fillna(0)
|
||||
|
||||
|
||||
|
||||
def read_rawAcousticDf(ar, id):
|
||||
skip_cols = ["error_reason", "dbm_master_url", "Frames", " Frames", "aco_voicelabel"]
|
||||
|
||||
fm_filename = ar + "/raw_variables/"+id+"/acoustic/formant_freq/"+id+"_formant.csv"
|
||||
gne_filename = ar + "/raw_variables/"+id+"/acoustic/glottal_noise/"+id+"_gne.csv"
|
||||
hnr_filename = ar + "/raw_variables/"+id+"/acoustic/harmonic_noise/"+id+"_hnr.csv"
|
||||
intt_filename = ar + "/raw_variables/"+id+"/acoustic/intensity/"+id+"_intensity.csv"
|
||||
mfcc_filename = ar + "/raw_variables/"+id+"/acoustic/mfcc/"+id+"_mfcc.csv"
|
||||
pitch_filename = ar + "/raw_variables/"+id+"/acoustic/pitch/"+id+"_pitch.csv"
|
||||
jitter_filename = ar + "/raw_variables/"+id+"/acoustic/jitter/"+id+"_jitter.csv"
|
||||
pause_segment_filename = ar + "/raw_variables/"+id+"/acoustic/pause_segment/"+id+"_pausechar.csv"
|
||||
shimmer_filename = ar + "/raw_variables/"+id+"/acoustic/shimmer/"+id+"_shimmer.csv"
|
||||
voice_frame_score_filename = ar + "/raw_variables/"+id+"/acoustic/voice_frame_score/"+id+"_voiceprev.csv"
|
||||
|
||||
filename_list = [fm_filename, gne_filename, hnr_filename, intt_filename,mfcc_filename, pitch_filename, pause_segment_filename, shimmer_filename, voice_frame_score_filename]
|
||||
|
||||
if not exists(fm_filename) or not exists(gne_filename) or not exists(hnr_filename) or not exists(intt_filename) or not exists(mfcc_filename) or not exists(pitch_filename):
|
||||
return pd.DataFrame
|
||||
|
||||
fm = pd.read_csv(fm_filename)
|
||||
fm_cols = [col for col in fm if col not in skip_cols]
|
||||
|
||||
gne = pd.read_csv(gne_filename)
|
||||
gne_cols = [col for col in gne if col not in skip_cols]
|
||||
|
||||
hnr = pd.read_csv(hnr_filename)
|
||||
hnr_cols = [col for col in hnr if col not in skip_cols]
|
||||
|
||||
|
||||
intt = pd.read_csv(intt_filename)
|
||||
intt_cols = [col for col in intt if col not in skip_cols]
|
||||
|
||||
|
||||
mfcc = pd.read_csv(mfcc_filename)
|
||||
mfcc_cols = [col for col in mfcc if col not in skip_cols]
|
||||
|
||||
|
||||
pitch = pd.read_csv(pitch_filename)
|
||||
pitch_cols = [col for col in pitch if col not in skip_cols]
|
||||
|
||||
pause_segment = pd.read_csv(pause_segment_filename)
|
||||
pause_segment_cols = [col for col in pause_segment if col not in skip_cols]
|
||||
|
||||
jitter = pd.read_csv(jitter_filename)
|
||||
jitter_cols = [col for col in jitter if col not in skip_cols]
|
||||
|
||||
shimmer = pd.read_csv(shimmer_filename)
|
||||
shimmer_cols = [col for col in shimmer if col not in skip_cols]
|
||||
|
||||
voice_frame_score = pd.read_csv(voice_frame_score_filename)
|
||||
voice_frame_score_cols = [col for col in voice_frame_score if col not in skip_cols]
|
||||
|
||||
acoustic_df = fm.loc[:, ~fm.columns.isin(skip_cols)].copy()
|
||||
for el in gne_cols:
|
||||
acoustic_df[el] = gne[el]
|
||||
for el in hnr_cols:
|
||||
acoustic_df[el] = hnr[el]
|
||||
for el in intt_cols:
|
||||
acoustic_df[el] = intt[el]
|
||||
for el in mfcc_cols:
|
||||
acoustic_df[el] = mfcc[el]
|
||||
for el in pitch_cols:
|
||||
acoustic_df[el] = pitch[el]
|
||||
for el in pause_segment_cols:
|
||||
acoustic_df[el] = pause_segment[el]
|
||||
for el in jitter_cols:
|
||||
acoustic_df[el] = jitter[el]
|
||||
for el in shimmer_cols:
|
||||
acoustic_df[el] = shimmer[el]
|
||||
for el in voice_frame_score_cols:
|
||||
acoustic_df[el] = voice_frame_score[el]
|
||||
return acoustic_df.fillna(0)
|
||||
|
||||
|
||||
def load():
|
||||
global videoIds
|
||||
read_derivedAttr()
|
||||
read_derivedDf()
|
||||
|
||||
|
||||
if __name__=="__main__":
|
||||
load()
|
||||
|
||||
381
visualization_interface/flask-server/server.py
Normal file
381
visualization_interface/flask-server/server.py
Normal file
@@ -0,0 +1,381 @@
|
||||
from flask import Flask
|
||||
from flask import request
|
||||
import pandas as pd
|
||||
import json
|
||||
import numpy as np
|
||||
from sklearn.decomposition import PCA
|
||||
import sys
|
||||
import process_input_data
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/fetchIndividualFacialRawData', methods=["POST"])
|
||||
def fetchIndividualFacialRawData():
|
||||
id = request.json['id']
|
||||
if id:
|
||||
individualFacialRawData = process_input_data.read_rawFacialDf(sys.argv[1], id)
|
||||
else:
|
||||
individualFacialRawData = process_input_data.read_rawFacialDf(sys.argv[1], inputData['Filename'][0])
|
||||
if individualFacialRawData.empty:
|
||||
return {}
|
||||
return individualFacialRawData.fillna(0).to_json(orient="records")
|
||||
|
||||
|
||||
@app.route('/fetchIndividualMovementRawData', methods=["POST"])
|
||||
def fetchIndividualMovementRawData():
|
||||
id = request.json['id']
|
||||
if id:
|
||||
individualMovementRawData = process_input_data.read_rawMovementDf(sys.argv[1], id)
|
||||
else:
|
||||
individualMovementRawData = process_input_data.read_rawMovementDf(sys.argv[1], inputData['Filename'][0])
|
||||
if individualMovementRawData.empty:
|
||||
return {}
|
||||
return individualMovementRawData.fillna(0).to_json(orient="records")
|
||||
|
||||
@app.route('/fetchIndividualAcousticRawData', methods=["POST"])
|
||||
def fetchIndividualAcousticRawData():
|
||||
id = request.json['id']
|
||||
if id:
|
||||
individualAcousticRawData = process_input_data.read_rawAcousticDf(sys.argv[1], id)
|
||||
else:
|
||||
individualAcousticRawData = process_input_data.read_rawAcousticDf(sys.argv[1], inputData['Filename'][0])
|
||||
if individualAcousticRawData.empty:
|
||||
return {}
|
||||
return individualAcousticRawData.fillna(0).to_json(orient="records")
|
||||
|
||||
@app.route('/fetchIndividualDerivedData', methods=["POST"])
|
||||
def fetchIndividualDerivedData():
|
||||
if len(list(inputData.columns)) <2:
|
||||
return {}
|
||||
id = request.json['id']
|
||||
if id:
|
||||
res = inputData.loc[inputData['Filename'] == id, ~inputData.columns.isin(['Filename'])]
|
||||
else:
|
||||
res = inputData.iloc[:1, :].loc[:, ~inputData.columns.isin(['Filename'])]
|
||||
return res.fillna(0).to_json(orient="records")
|
||||
|
||||
|
||||
|
||||
@app.route('/fetchIndividualFacialTimelineData', methods=["POST"])
|
||||
def fetchIndividualFacialTimelineData():
|
||||
id = request.json['id']
|
||||
timepoints = 20
|
||||
if id:
|
||||
dfFace = process_input_data.read_rawFacialDf(sys.argv[1], id)
|
||||
dfMovement = process_input_data.read_rawMovementDf(sys.argv[1], id)
|
||||
|
||||
else:
|
||||
dfFace = process_input_data.read_rawFacialDf(sys.argv[1], inputData['Filename'][0])
|
||||
dfMovement = process_input_data.read_rawMovementDf(sys.argv[1], inputData['Filename'][0])
|
||||
if dfFace.empty:
|
||||
return {}
|
||||
dfFace=dfFace.fillna(0)
|
||||
attrOfInterest= ["fac_angintsoft", "fac_feaintsoft", "fac_disintsoft", "fac_sadintsoft",
|
||||
"fac_conintsoft", "fac_surintsoft", "fac_hapintsoft",
|
||||
"fac_AU01int", "fac_AU02int", "fac_AU04int", "fac_AU05int", "fac_AU06int",
|
||||
"fac_AU07int", "fac_AU09int", "fac_AU10int", "fac_AU12int","fac_AU14int",
|
||||
"fac_AU15int", "fac_AU17int", "fac_AU20int", "fac_AU23int", "fac_AU25int",
|
||||
"fac_AU26int", "fac_asymmaskcom", "fac_asymmaskeye", "fac_asymmaskeyebrow",
|
||||
"fac_asymmaskmouth", "fac_paiintsoft", "fac_comintsoft",
|
||||
"fac_comlowintsoft", "fac_comuppintsoft"]
|
||||
timelineObject = {}
|
||||
for a in attrOfInterest:
|
||||
timelineObject[a] = []
|
||||
seg = len(dfFace)//19
|
||||
reminder = len(dfFace)%19
|
||||
|
||||
for t in range(0,timepoints):
|
||||
for k in attrOfInterest:
|
||||
if t <= reminder:
|
||||
timelineObject[k].append(sum(list(dfFace[t*(seg + 1):(t+1)*(seg+1)][k]))/(seg+1))
|
||||
else:
|
||||
timelineObject[k].append(sum(list(dfFace[t*seg:(t+1)*seg][k]))/seg)
|
||||
if dfMovement.empty:
|
||||
return timelineObject
|
||||
dfMovement=dfMovement.fillna(0)
|
||||
movementAttr = ["mov_hposepitch", "mov_hposeyaw", "mov_hposeroll"]
|
||||
for a in movementAttr:
|
||||
timelineObject[a] = []
|
||||
seg = len(dfMovement)//20
|
||||
reminder = len(dfMovement)%20
|
||||
for t in range(0,timepoints):
|
||||
for k in movementAttr:
|
||||
if t <= reminder:
|
||||
timelineObject[k].append(sum(list(dfMovement[t*(seg + 1):(t+1)*(seg+1)][k]))/(seg+1))
|
||||
else:
|
||||
timelineObject[k].append(sum(list(dfMovement[t*seg:(t+1)*seg][k]))/seg)
|
||||
|
||||
return timelineObject
|
||||
|
||||
|
||||
@app.route('/getRawAttributesAndIds')
|
||||
def getRawAttributesAndIds():
|
||||
result = {}
|
||||
if not individualFacialRawData.empty:
|
||||
result['facial'] = [x for x in list(individualFacialRawData.columns)]
|
||||
else:
|
||||
result['facial'] =[]
|
||||
if not individualAcousticRawData.empty:
|
||||
result['acoustic'] = [x for x in list(individualAcousticRawData.columns)]
|
||||
else:
|
||||
result['acoustic'] = []
|
||||
if not individualMovementRawData.empty:
|
||||
result['movement'] = [x for x in list(individualMovementRawData.columns)]
|
||||
else:
|
||||
result['movement'] = []
|
||||
if len(rawDataArgs) > 0:
|
||||
result['ids'] = rawDataArgs['ids']
|
||||
else:
|
||||
result['ids'] = []
|
||||
return result
|
||||
|
||||
def individualCorrMatrixData(id, corrArgs):
|
||||
individualFacialRawData = pd.DataFrame()
|
||||
individualMovementRawData = pd.DataFrame()
|
||||
individualAcousticRawData = pd.DataFrame()
|
||||
if id:
|
||||
if len([x for x in corrArgs if "fac_" in x])>0:
|
||||
individualFacialRawData = process_input_data.read_rawFacialDf(sys.argv[1], id)
|
||||
if len([x for x in corrArgs if "mov_" in x])>0:
|
||||
individualMovementRawData = process_input_data.read_rawMovementDf(sys.argv[1], id)
|
||||
if len([x for x in corrArgs if "aco_" in x])>0:
|
||||
individualAcousticRawData = process_input_data.read_rawAcousticDf(sys.argv[1], id)
|
||||
else:
|
||||
if len([x for x in corrArgs if "fac_" in x])>0:
|
||||
individualFacialRawData = process_input_data.read_rawFacialDf(sys.argv[1], inputData['Filename'][0])
|
||||
if len([x for x in corrArgs if "mov_" in x])>0:
|
||||
individualMovementRawData = process_input_data.read_rawMovementDf(sys.argv[1], inputData['Filename'][0])
|
||||
if len(list(filter(lambda x : "aco_" in x or "tremor_median" in x or "fac_features_mean" in x or "fac_corr" in x, individualMovementRawData))) >0:
|
||||
individualAcousticRawData = process_input_data.read_rawAcousticDf(sys.argv[1], inputData['Filename'][0])
|
||||
f = individualFacialRawData.loc[:, individualFacialRawData.columns.isin(corrArgs)]
|
||||
m = individualMovementRawData.loc[:, individualMovementRawData.columns.isin(corrArgs)]
|
||||
a = individualAcousticRawData.loc[:, individualAcousticRawData.columns.isin(corrArgs)]
|
||||
if f.empty:
|
||||
f = pd.DataFrame()
|
||||
if m.empty:
|
||||
m = pd.DataFrame()
|
||||
if a.empty:
|
||||
a = pd.DataFrame()
|
||||
min_len = float('inf')
|
||||
if not f.empty:
|
||||
min_len = len(f)
|
||||
if not m.empty:
|
||||
min_len = min(min_len, len(m))
|
||||
if not a.empty:
|
||||
min_len=min(min_len, len(a))
|
||||
if min_len == float('inf'):
|
||||
min_len = 0
|
||||
|
||||
if min_len == len(f):
|
||||
all_df = f.copy()
|
||||
if len(f) == len(m):
|
||||
for x in m.columns:
|
||||
all_df[x] = m[x]
|
||||
else:
|
||||
seg = int(len(m)/(max(len(f),1)))
|
||||
reminder = len(m)%(max(len(f),1))
|
||||
for i, row in all_df.iterrows():
|
||||
for x in m.columns:
|
||||
if i <reminder:
|
||||
all_df.loc[i,x] = sum(list(m[i*(seg + 1):(i+1)*(seg+1)][x]))/(seg+1)
|
||||
else:
|
||||
all_df.loc[i,x] = sum(list(m[i*seg:(i+1)*seg][x]))/seg
|
||||
if len(a) > len(f):
|
||||
seg = int(len(a)/(max(len(f),1)))
|
||||
reminder = len(a)%(max(len(f),1))
|
||||
for i, row in all_df.iterrows():
|
||||
for x in a.columns:
|
||||
if i <reminder:
|
||||
all_df.loc[i,x] = sum(list(a[i*(seg + 1):(i+1)*(seg+1)][x]))/(seg+1)
|
||||
else:
|
||||
all_df.loc[i,x] = sum(list(a[i*seg:(i+1)*seg][x]))/seg
|
||||
else:
|
||||
for x in a.columns:
|
||||
all_df[x] = a[x]
|
||||
elif len(m) == min_len:
|
||||
all_df = m.copy()
|
||||
seg = int(len(f)/(max(len(m),1)))
|
||||
reminder = len(f)%(max(len(m),1))
|
||||
for i, row in all_df.iterrows():
|
||||
for x in f.columns:
|
||||
if i <reminder:
|
||||
all_df.loc[i,x] = sum(list(f[i*(seg + 1):(i+1)*(seg+1)][x]))/(seg+1)
|
||||
else:
|
||||
all_df.loc[i,x] = sum(list(f[i*seg:(i+1)*seg][x]))/seg
|
||||
if len(a) > len(m):
|
||||
seg = int(len(a)/(max(len(m),1)))
|
||||
reminder = len(a)%(max(len(m),1))
|
||||
for i, row in all_df.iterrows():
|
||||
for x in a.columns:
|
||||
if i <reminder:
|
||||
all_df.loc[i,x] = sum(list(a[i*(seg + 1):(i+1)*(seg+1)][x]))/(seg+1)
|
||||
else:
|
||||
all_df.loc[i,x] = sum(list(a[i*seg:(i+1)*seg][x]))/seg
|
||||
else:
|
||||
for x in a.columns:
|
||||
all_df[x] = a[x]
|
||||
else:
|
||||
all_df = a.copy()
|
||||
seg = int(len(f)/(max(len(a),1)))
|
||||
reminder = len(f)%(max(len(a),1))
|
||||
for i, row in all_df.iterrows():
|
||||
for x in f.columns:
|
||||
if i <reminder:
|
||||
all_df.loc[i,x] = sum(list(f[i*(seg + 1):(i+1)*(seg+1)][x]))/(seg+1)
|
||||
else:
|
||||
all_df.loc[i,x] = sum(list(f[i*seg:(i+1)*seg][x]))/seg
|
||||
seg = int(len(m)/(max(len(a),1)))
|
||||
reminder = len(m)%(max(len(a),1))
|
||||
for i, row in all_df.iterrows():
|
||||
for x in m.columns:
|
||||
if i <reminder:
|
||||
all_df.loc[i,x] = sum(list(m[i*(seg + 1):(i+1)*(seg+1)][x]))/(seg+1)
|
||||
else:
|
||||
all_df.loc[i,x] = sum(list(m[i*seg:(i+1)*seg][x]))/seg
|
||||
return all_df.fillna(0)
|
||||
|
||||
|
||||
@app.route("/performPCA")
|
||||
def performPCA(PCAargs=[]):
|
||||
if len(list(inputData.columns)) <2:
|
||||
return {}
|
||||
pca = PCA()
|
||||
if len(PCAargs) == 0:
|
||||
X = pd.DataFrame(columns = [x for x in list(inputData.columns) if x not in ['Filename']])
|
||||
X = inputData.loc[:, ~inputData.columns.isin(['Filename'])]
|
||||
|
||||
else:
|
||||
X = pd.DataFrame(columns = [x for x in PCAargs ])
|
||||
X = inputData.loc[:, inputData.columns.isin(PCAargs)]
|
||||
|
||||
X = X.fillna(0)
|
||||
x_pca = pca.fit_transform(X)
|
||||
x_pca = pd.DataFrame(x_pca)
|
||||
x_pca = x_pca.iloc[:, list(range(2))]
|
||||
x_pca.columns = ['PC1', "PC2"]
|
||||
pc1 = x_pca["PC1"].to_numpy()
|
||||
pc2 = x_pca["PC2"].to_numpy()
|
||||
pc1 = list((pc1 -pc1.mean())/np.std(pc1))
|
||||
pc2 = list((pc2 -pc2.mean())/np.std(pc2))
|
||||
pca_results =[[pc1[i], pc2[i]] for i in range(0, len(pc1))]
|
||||
|
||||
return dict(zip(inputData['Filename'], pca_results))
|
||||
|
||||
|
||||
@app.route('/updatePCA', methods=["POST"])
|
||||
def updatePCA():
|
||||
PCAargs = request.json['PCA_args']
|
||||
return performPCA(PCAargs)
|
||||
|
||||
|
||||
@app.route('/defaultDistribution', methods=["POST"])
|
||||
def defaultDistribution():
|
||||
args = request.json['distributionArgs']
|
||||
return {"data":updateDistribution(args)}
|
||||
|
||||
|
||||
@app.route('/updateDistribution', methods=["POST"])
|
||||
def updateDistribution(attr=[]):
|
||||
if len(list(inputData.columns)) <2:
|
||||
return {}
|
||||
if len(attr) !=0:
|
||||
distributionArgs = attr
|
||||
else:
|
||||
distributionArgs = request.json['distributionArgs']
|
||||
if len(distributionArgs) == 0:
|
||||
df = pd.DataFrame(columns = [x for x in list(inputData.columns) if x not in ['Filename']])
|
||||
df = inputData.loc[:, ~inputData.columns.isin(['Filename'])]
|
||||
|
||||
else:
|
||||
cols = ['Filename']+ distributionArgs
|
||||
df = pd.DataFrame(columns = [c for c in list(cols )])
|
||||
df = inputData.loc[:, inputData.columns.isin(cols)]
|
||||
df = df.fillna(0)
|
||||
if len(distributionArgs) ==0:
|
||||
return {}
|
||||
result = {}
|
||||
for i, row in df.iterrows():
|
||||
result[row['Filename']]={}
|
||||
result[row['Filename']]['id'] = row['Filename']
|
||||
for attr in distributionArgs:
|
||||
result[row['Filename']][attr] = row[attr]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@app.route('/defaultCorrMatrix', methods=["POST"])
|
||||
def defaultCorrMatrix():
|
||||
corrMatrixArgs = request.json['corrMatrix_args']
|
||||
individual_corr = request.json['individual']
|
||||
id= request.json['id']
|
||||
return updateCorrMatrix(corrMatrixArgs, individual_corr, id)
|
||||
|
||||
|
||||
@app.route('/updateCorrMatrix', methods=["POST"])
|
||||
def updateCorrMatrix(attr=[], individual = False, id=None):
|
||||
if len(attr) !=0:
|
||||
corrMatrixArgs = attr
|
||||
else:
|
||||
corrMatrixArgs = request.json['corrMatrix_args']
|
||||
individual = request.json['individual']
|
||||
if len(corrMatrixArgs) < 2:
|
||||
return {}
|
||||
if len(corrMatrixArgs) > 24:
|
||||
return{}
|
||||
|
||||
df = pd.DataFrame(columns = [c for c in corrMatrixArgs])
|
||||
if individual:
|
||||
if not id:
|
||||
id = request.json['id']
|
||||
d = individualCorrMatrixData(id, corrMatrixArgs)
|
||||
df = d
|
||||
# df = d.loc[:, d.columns.isin(corrMatrixArgs)]
|
||||
else:
|
||||
df = inputData.loc[:, inputData.columns.isin(corrMatrixArgs)]
|
||||
|
||||
corrMatrix = df.corr(method="spearman").fillna(0)
|
||||
return corrMatrix.to_dict()
|
||||
|
||||
|
||||
|
||||
@app.route("/getDerivedAttributes")
|
||||
def getDerivedAttributes():
|
||||
if not len(rawDataArgs):
|
||||
return {"facial": [], "acoustic":[], "movement":[], "speech": [], "ids": []}
|
||||
|
||||
res={"facial": rawDataArgs['facialAttr'], "acoustic": rawDataArgs['acousticAttr'], "movement": rawDataArgs['movementAttr'],
|
||||
"speech": rawDataArgs["speechAttr"], "ids": rawDataArgs['ids']}
|
||||
return res
|
||||
|
||||
@app.route("/getMetadata")
|
||||
def getMetadata():
|
||||
if not len(metadata):
|
||||
return {}
|
||||
|
||||
return metadata
|
||||
|
||||
def load():
|
||||
global rawDataArgs, metadata
|
||||
rawDataArgs = process_input_data.read_derivedAttr(sys.argv[1])
|
||||
if len(sys.argv) >2:
|
||||
metadata = process_input_data.read_medatada(sys.argv[1], sys.argv[2])
|
||||
else:
|
||||
metadata=[]
|
||||
global inputData
|
||||
inputData = process_input_data.read_derivedDf(sys.argv[1])
|
||||
if len(inputData):
|
||||
inputData['Filename'] = inputData['Filename'].apply(lambda x: x.split('/')[1].replace(".mp4", ""))
|
||||
else:
|
||||
inputData['Filename'] = [""]
|
||||
global individualFacialRawData, individualDerivedData, individualAcousticRawData, individualMovementRawData
|
||||
individualFacialRawData = process_input_data.read_rawFacialDf(sys.argv[1], inputData['Filename'][0])
|
||||
individualMovementRawData = process_input_data.read_rawMovementDf(sys.argv[1], inputData['Filename'][0])
|
||||
individualAcousticRawData = process_input_data.read_rawAcousticDf(sys.argv[1], inputData['Filename'][0])
|
||||
|
||||
|
||||
if __name__=="__main__":
|
||||
# from waitress import serve
|
||||
|
||||
load()
|
||||
# serve(app, host='127.0.0.1', port=5000)
|
||||
app.run(debug=True)
|
||||
# app.run(debug=False)
|
||||
11729
visualization_interface/package-lock.json
generated
Normal file
11729
visualization_interface/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
55
visualization_interface/package.json
Normal file
55
visualization_interface/package.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "dashboard",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"proxy": "http://localhost:5000",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.1.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.1.1",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^13.3.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"bootstrap": "^5.1.3",
|
||||
"bootstrap-icons": "^1.8.3",
|
||||
"d3": "^7.6.1",
|
||||
"d3-axis": "^3.0.0",
|
||||
"d3-collection": "^1.0.7",
|
||||
"d3-ez": "^3.3.15",
|
||||
"d3-scale-chromatic": "^3.0.0",
|
||||
"d3-simple-slider": "^1.10.4",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jquery": "^3.6.0",
|
||||
"react": "^18.2.0",
|
||||
"react-bootstrap": "^2.4.0",
|
||||
"react-component-export-image": "^1.0.6",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-to-print": "^2.14.7",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
BIN
visualization_interface/public/favicon.ico
Normal file
BIN
visualization_interface/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
42
visualization_interface/public/index.html
Normal file
42
visualization_interface/public/index.html
Normal file
@@ -0,0 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>OpenDBM Dashboard</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
BIN
visualization_interface/public/logo192.png
Normal file
BIN
visualization_interface/public/logo192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
BIN
visualization_interface/public/logo512.png
Normal file
BIN
visualization_interface/public/logo512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
25
visualization_interface/public/manifest.json
Normal file
25
visualization_interface/public/manifest.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
3
visualization_interface/public/robots.txt
Normal file
3
visualization_interface/public/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
39
visualization_interface/src/App.css
Normal file
39
visualization_interface/src/App.css
Normal file
@@ -0,0 +1,39 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
49
visualization_interface/src/App.js
Normal file
49
visualization_interface/src/App.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import { Row, Col } from 'react-bootstrap';
|
||||
import CohortPanel from './components/cohortPanel';
|
||||
import IndividualPanel from './components/individualPanel';
|
||||
|
||||
function App() {
|
||||
|
||||
const [cohortButton, setCohortButton] = useState(true)
|
||||
const [individualButton, setIndividualButton] = useState(false)
|
||||
|
||||
|
||||
|
||||
const handleView = param => {
|
||||
setCohortButton(param === "cohort" ? true : false)
|
||||
setIndividualButton(param !== "cohort" ? true : false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='App' style={{ fontFamily: "monospace", width: "max-content", height: "max-content" }}>
|
||||
<Row style={{}}>
|
||||
<Col className="col-2" style={{ width: "90vh", height: "max-content", marginTop: "10px", marginBottom: "10px", display: "inline-flex", flexDirection: "row", gap: "3px" }}>
|
||||
<div style={{ display: "inline-flex", flexDirection: "row", gap: "10px", marginLeft: "10px" }}>
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-sm ${cohortButton === true ? 'btn-primary' : 'btn-secondary'}`}
|
||||
onClick={() => handleView("cohort")}
|
||||
id="cohortViewButton"
|
||||
style={{ height: "max-content", }}>
|
||||
Cohort
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-sm ${individualButton === true ? 'btn-primary' : 'btn-secondary'}`}
|
||||
onClick={() => handleView("individual")}
|
||||
style={{ height: "max-content", }}
|
||||
id="individualViewButton">
|
||||
Individual
|
||||
</button>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
{cohortButton ? <CohortPanel /> : <IndividualPanel />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
16
visualization_interface/src/App.test.js
Normal file
16
visualization_interface/src/App.test.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { cleanup, render, screen } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
test('render cohort panel', () => {
|
||||
render(<App />);
|
||||
const cohortElem = screen.getByText("Cohort");
|
||||
expect(cohortElem).toHaveClass("btn-primary");
|
||||
});
|
||||
|
||||
test('render individual panel', () => {
|
||||
render(<App />);
|
||||
const individualElem = screen.getByText("Individual");
|
||||
expect(individualElem).toHaveClass("btn-secondary");
|
||||
});
|
||||
6593
visualization_interface/src/DBM_attribute_dict.json
Normal file
6593
visualization_interface/src/DBM_attribute_dict.json
Normal file
File diff suppressed because it is too large
Load Diff
13
visualization_interface/src/IndividualPanel.test.js
Normal file
13
visualization_interface/src/IndividualPanel.test.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { cleanup, render, screen } from '@testing-library/react';
|
||||
import IndividualPanel from './components/individualPanel'
|
||||
|
||||
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
|
||||
test('render Individual components', () => {
|
||||
render(<IndividualPanel/>)
|
||||
const individualElem = screen.getByText("Asym");
|
||||
expect(individualElem).toBeVisible();
|
||||
});
|
||||
148
visualization_interface/src/components/cohortPanel.css
Normal file
148
visualization_interface/src/components/cohortPanel.css
Normal file
@@ -0,0 +1,148 @@
|
||||
#metaDataButton {
|
||||
height: 5%;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 250px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#attributesTag {
|
||||
margin-left: 5px;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
#metadataAttributes {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
margin: auto;
|
||||
margin-left: 20px;
|
||||
opacity: 0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.metadataAttr {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
opacity: 0;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.metadataAttr div {
|
||||
margin-left: 10px;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
#cohortContainer {
|
||||
height: 90vh;
|
||||
width: 95vw;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
#queryPanelContainer {
|
||||
border-radius: 15px;
|
||||
padding-top: 10px;
|
||||
width: max-content;
|
||||
height: 98%;
|
||||
box-shadow: rgba(0, 0, 0, 0.16) 0px 1px 4px;
|
||||
}
|
||||
|
||||
#queryPanelButtonsContainer {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
gap: 3px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#queryListContainer {
|
||||
font-size: 0.8rem;
|
||||
height: 90%;
|
||||
}
|
||||
|
||||
.queryList {
|
||||
height: 95%;
|
||||
}
|
||||
|
||||
.queryUpdateButton {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.queryCheckboxContainer {
|
||||
overflow-y: scroll;
|
||||
height: 97%;
|
||||
}
|
||||
|
||||
#scatterplotCorrMatrixContainer {
|
||||
height: 95%;
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
#scatterplotContainer {
|
||||
height: 35vh;
|
||||
}
|
||||
|
||||
#corrMatrixContainer {
|
||||
height: 50%;
|
||||
margin-top: 10%;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
#idPanelContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 15px;
|
||||
height: 98%;
|
||||
padding-top: 10px;
|
||||
font-size: 0.7rem;
|
||||
width: max-content;
|
||||
box-shadow: rgba(0, 0, 0, 0.16) 0px 1px 4px;
|
||||
}
|
||||
|
||||
#hideIdButton {
|
||||
margin-top: 10px
|
||||
}
|
||||
|
||||
#idListContainer {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
margin-top: 10px;
|
||||
height: 94%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#distributionChartContainer {
|
||||
height: 93%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.distributionChart {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.printButton {
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
#scatterplotComponent {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
}
|
||||
|
||||
|
||||
#corrMatrixComponent {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.queryPanel h6 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
381
visualization_interface/src/components/cohortPanel.js
Normal file
381
visualization_interface/src/components/cohortPanel.js
Normal file
@@ -0,0 +1,381 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import { Row, Col } from 'react-bootstrap';
|
||||
import Scatterplot from './scatterplot'
|
||||
import DistributionChart from './distributionChart';
|
||||
import CorrelationMatrix from './correlationMatrix';
|
||||
import QueryPanel from './queryPanel';
|
||||
import $ from 'jquery';
|
||||
import "./cohortPanel.css"
|
||||
|
||||
function CohortPanel() {
|
||||
|
||||
const [distrData, setDistrData] = useState([])
|
||||
const [corrMatrixData, setCorrMatrixData] = useState([])
|
||||
const [pcaCoords, setPcaCoords] = useState([])
|
||||
|
||||
const distrDefaultArgs = ['fac_asymmaskmouth_mean', 'fac_asymmaskeyebrow_mean', 'fac_asymmaskeye_mean', 'fac_asymmaskcom_mean']
|
||||
const [distrArgs, setDistrArgs] = useState(distrDefaultArgs)
|
||||
|
||||
const [checkedDistrState, setCheckedDistrState] = useState([]);
|
||||
const [checkedPCAState, setCheckedPCAState] = useState([]);
|
||||
const [checkedCorrMatrixState, setCheckedCorrMatrixState] = useState([]);
|
||||
|
||||
const [idData, setIdData] = useState([])
|
||||
const [filteredIdData, setFilteredIdData] = useState([])
|
||||
const [checkedHideIdsState, setCheckedHideIdsState] = useState(false)
|
||||
|
||||
const [pcaButton, setPcaButton] = useState(true)
|
||||
const [metadataButton, setMetadataButton] = useState(false)
|
||||
const [distrButton, setDistrButton] = useState(false)
|
||||
const [corrMatrixButton, setCorrMatrixButton] = useState(false)
|
||||
|
||||
const [allFacialArg, setAllFacialArg] = useState([])
|
||||
const [allAcousticArg, setAllAcousticArg] = useState([])
|
||||
const [allMovementArg, setAllMovementArg] = useState([])
|
||||
const [allSpeechArg, setAllSpeechArg] = useState([])
|
||||
const [allDBMArg, setAllDBMArg] = useState([])
|
||||
|
||||
const [metadata, setMetadata] = useState([])
|
||||
const metadataColorList = ['none', 'gold', 'blue', 'pink', 'darkviolet', 'cyan', 'black', 'brown', 'greenyellow', 'orchid','mediumpurple']
|
||||
const [metadataAttrColor, setMetadataAttrColor] = useState({})
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/getDerivedAttributes").then(
|
||||
res => res.json()
|
||||
).then(
|
||||
data => {
|
||||
setAllFacialArg(data['facial'])
|
||||
setAllAcousticArg(data['acoustic'])
|
||||
setAllMovementArg(data['movement'])
|
||||
setAllSpeechArg(data['speech'])
|
||||
setAllDBMArg([...data['facial'], ...data['movement'], ...data['acoustic'], ...data['speech']])
|
||||
setIdData(data['ids'].map(el => el.split("/")[1].replace(".mp4", "")))
|
||||
setCheckedCorrMatrixState(new Array([...data['facial'], ...data['movement'], ...data['acoustic'], ...data['speech']].length).fill(false))
|
||||
setCheckedDistrState(new Array([...data['facial'], ...data['movement'], ...data['acoustic'], ...data['speech']].length).fill(false))
|
||||
setCheckedPCAState(new Array([...data['facial'], ...data['movement'], ...data['acoustic'], ...data['speech']].length).fill(false))
|
||||
}
|
||||
)
|
||||
}, [])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/getMetadata").then(
|
||||
res => res.json()
|
||||
).then(
|
||||
data => {
|
||||
var attr = [...new Set(Object.values(data).map(el => el['attr']).filter(e => e != null))]
|
||||
if (attr.length <= metadataColorList.length - 1) {
|
||||
setMetadata(data)
|
||||
var metaColorData = {}
|
||||
var attrDict = {}
|
||||
attr.forEach((e, i) => {
|
||||
attrDict[e] = metadataColorList[i + 1]
|
||||
})
|
||||
if (data.length > 0)
|
||||
data.forEach(e => {
|
||||
if (e['attr']) {
|
||||
metaColorData[e['id']] = attrDict[e['attr']]
|
||||
}
|
||||
})
|
||||
}
|
||||
setMetadataAttrColor(metaColorData)
|
||||
}
|
||||
)
|
||||
}, [])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/performPCA").then(
|
||||
res => res.json()
|
||||
).then(
|
||||
data => {
|
||||
setPcaCoords(data)
|
||||
}
|
||||
)
|
||||
}, [])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/defaultDistribution", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-type': "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"distributionArgs": distrArgs
|
||||
})
|
||||
}).then(
|
||||
res => res.json()
|
||||
).then(
|
||||
data => {
|
||||
setDistrData(data['data'])
|
||||
}
|
||||
)
|
||||
}, [])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/defaultCorrMatrix", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-type': "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"corrMatrix_args": distrDefaultArgs,
|
||||
"individual": false,
|
||||
"id": null
|
||||
})
|
||||
}).then(
|
||||
res => res.json()
|
||||
).then(
|
||||
data => {
|
||||
setCorrMatrixData(data)
|
||||
}
|
||||
)
|
||||
}, [])
|
||||
|
||||
|
||||
const handlePCAUpdate = () => {
|
||||
var PCA_args = allDBMArg.filter((el, index) => checkedPCAState[index] === true)
|
||||
if (PCA_args.length !== 1) {
|
||||
fetch("/updatePCA", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-type': "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"PCA_args": PCA_args
|
||||
})
|
||||
}).then(
|
||||
res => res.json()
|
||||
).then(
|
||||
data => {
|
||||
setPcaCoords(data)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const handlePCACheckboxChange = position => {
|
||||
const updatedCheckedState = checkedPCAState.map((item, index) =>
|
||||
index === position ? !item : item)
|
||||
setCheckedPCAState(updatedCheckedState)
|
||||
}
|
||||
|
||||
const handleDistrUpdate = attr => {
|
||||
var args = allDBMArg.filter((el, index) => checkedDistrState[index] === true)
|
||||
if (args.length === 0 & attr.length > 0) {
|
||||
args = attr
|
||||
}
|
||||
fetch("/updateDistribution", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-type': "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"distributionArgs": args
|
||||
})
|
||||
}).then(
|
||||
res => res.json()
|
||||
).then(
|
||||
data => {
|
||||
setDistrData(data)
|
||||
setDistrArgs(args)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const handleDistrCheckboxChange = position => {
|
||||
const updatedCheckedState = checkedDistrState.map((item, index) =>
|
||||
index === position ? !item : item)
|
||||
setCheckedDistrState(updatedCheckedState)
|
||||
}
|
||||
|
||||
|
||||
const handleCorrMatrixUpdate = () => {
|
||||
var corrMatrix_args = allDBMArg.filter((el, index) => checkedCorrMatrixState[index] === true)
|
||||
fetch("/updateCorrMatrix", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-type': "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"corrMatrix_args": corrMatrix_args,
|
||||
"individual": false,
|
||||
"id": null
|
||||
})
|
||||
}).then(
|
||||
res => res.json()
|
||||
).then(
|
||||
data => {
|
||||
setCorrMatrixData(data)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const handleCorrMatrixCheckboxChange = position => {
|
||||
const updatedCheckedState = checkedCorrMatrixState.map((item, index) =>
|
||||
index === position ? !item : item)
|
||||
setCheckedCorrMatrixState(updatedCheckedState)
|
||||
}
|
||||
|
||||
const handlePanel = param => {
|
||||
setDistrButton(param === "Distr" ? true : false)
|
||||
setCorrMatrixButton(param === "Corr" ? true : false)
|
||||
setPcaButton(param === "PCA" ? true : false)
|
||||
}
|
||||
|
||||
|
||||
const handleIdCheckboxChange = ev => {
|
||||
|
||||
$('#' + ev.target.id).is(":checked") ?
|
||||
$(".dot_" + ev.target.id.replace("_id", "")).css("fill", "darkorange") :
|
||||
$(".dot_" + ev.target.id.replace("_id", "")).css("fill",
|
||||
metadataButton
|
||||
? metadataAttrColor[ev.target.id.replace("_id", "")] : "#41ab5d")
|
||||
|
||||
var filteredIdData_aux = idData.filter(id =>
|
||||
$('#' + id + "_id").is(":checked"))
|
||||
setFilteredIdData(filteredIdData_aux)
|
||||
|
||||
}
|
||||
|
||||
const handleMetadata = () => {
|
||||
setMetadataButton(!metadataButton)
|
||||
handlePCAUpdate()
|
||||
handleDistrUpdate(distrDefaultArgs)
|
||||
}
|
||||
|
||||
const handleHideIds = () => {
|
||||
if (checkedHideIdsState) {
|
||||
$('.dot').css("opacity", "0.4")
|
||||
}
|
||||
else {
|
||||
$('.dot').css("opacity", "0")
|
||||
filteredIdData.forEach(id => {
|
||||
$('.dot_' + id).css("opacity", "0.4")
|
||||
})
|
||||
}
|
||||
setCheckedHideIdsState(!checkedHideIdsState)
|
||||
}
|
||||
|
||||
const handleUnselectCheckboxes = () => {
|
||||
const updatedCheckboxes = checkedCorrMatrixState.map(e => false)
|
||||
if (distrButton) setCheckedDistrState(updatedCheckboxes)
|
||||
else if (pcaButton) setCheckedPCAState(updatedCheckboxes)
|
||||
else setCheckedCorrMatrixState(updatedCheckboxes)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{metadata && metadata.length > 0 &&
|
||||
<div id="metaDataButton">
|
||||
<div>
|
||||
<button type="button" className={`btn btn-sm ${metadataButton === true ? 'btn-outline-primary' : 'btn-outline-secondary'}`}
|
||||
id="metadataButton" onClick={handleMetadata}>Extra Attributes</button>
|
||||
</div>
|
||||
<div id="metadataAttributes">
|
||||
{metadataColorList.map((value, index) =>
|
||||
<div id={value + "AttrContainer"} key={value + "color"} className="metadataAttr">
|
||||
<svg height="14" width="14" transform="translate(3,3)" id={value + "circle"}>
|
||||
<circle cx="7" cy="7" r="50%" fill={value === "none" ? "#41ab5d" : value} />
|
||||
</svg>
|
||||
<div
|
||||
id={(value !== "none" ? value : "none") + "color"}>{value === "none" ? "None" : ""}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{allDBMArg.length > 0 &&
|
||||
<Row id="cohortContainer">
|
||||
<Col className="col-2" id="queryPanelContainer">
|
||||
<div id="queryPanelButtonsContainer">
|
||||
<button type="button" className={`btn btn-sm ${pcaButton === true ? 'btn-primary' : 'btn-secondary'}`} onClick={() => handlePanel("PCA")}>PCA</button>
|
||||
<button type="button" className={`btn btn-sm ${distrButton === true ? 'btn-primary' : 'btn-secondary'}`} onClick={() => handlePanel("Distr")}>Distr</button>
|
||||
<button type="button" className={`btn btn-sm ${corrMatrixButton === true ? 'btn-primary' : 'btn-secondary'}`} onClick={() => handlePanel("Corr")}>Corr</button>
|
||||
<button type="button" className="btn-close" aria-label="Close" style={{ marginTop: "5px" }} onClick={handleUnselectCheckboxes}></button>
|
||||
</div>
|
||||
<div id="queryListContainer">
|
||||
{pcaButton &&
|
||||
<div className='queryList'>
|
||||
<div className="queryUpdateButton">
|
||||
<button type="button" className='btn btn-sm btn-outline-primary' onClick={handlePCAUpdate}>Update PCA</button>
|
||||
</div>
|
||||
<div className="queryCheckboxContainer">
|
||||
<QueryPanel allAcousticArg={allAcousticArg} allMovementArg={allMovementArg} allSpeechArg={allSpeechArg} allFacialArg={allFacialArg}
|
||||
checkedState={checkedPCAState} handleCheckboxChange={handlePCACheckboxChange} idVal={"pcaInputArg_"} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{distrButton &&
|
||||
<div className='queryList'>
|
||||
<div className="queryUpdateButton">
|
||||
<button type="button" className='btn btn-sm btn-outline-primary' onClick={handleDistrUpdate}>Update Distributions</button>
|
||||
</div>
|
||||
<div className="queryCheckboxContainer">
|
||||
<QueryPanel allAcousticArg={allAcousticArg} allMovementArg={allMovementArg} allSpeechArg={allSpeechArg} allFacialArg={allFacialArg}
|
||||
checkedState={checkedDistrState} handleCheckboxChange={handleDistrCheckboxChange} idVal={"distributionInputArg_"} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{corrMatrixButton &&
|
||||
<div className='queryList'>
|
||||
<div className="queryUpdateButton">
|
||||
<button type="button" className='btn btn-sm btn-outline-primary' onClick={handleCorrMatrixUpdate}>Update Correlations</button>
|
||||
</div>
|
||||
<div className="queryCheckboxContainer">
|
||||
<QueryPanel allAcousticArg={allAcousticArg} allMovementArg={allMovementArg} allSpeechArg={allSpeechArg} allFacialArg={allFacialArg}
|
||||
checkedState={checkedCorrMatrixState} handleCheckboxChange={handleCorrMatrixCheckboxChange} idVal={"corrmatrixInputArg_"} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</Col>
|
||||
<Col className="col-4" id="scatterplotCorrMatrixContainer">
|
||||
<Row id="scatterplotContainer">
|
||||
{pcaCoords && <Scatterplot data={pcaCoords} filteredIds={filteredIdData}
|
||||
metadata={metadataButton === true ? metadata : []} hideIds={checkedHideIdsState} metadataAttrColor={metadataAttrColor} />}
|
||||
</Row>
|
||||
<Row id="corrMatrixContainer">
|
||||
<CorrelationMatrix data={corrMatrixData} parentId={"corrMatrixContainer"} />
|
||||
</Row>
|
||||
</Col>
|
||||
<Col id="idPanelContainer" className="col-2">
|
||||
<button type="button" className='btn btn-primary btn-sm'>Filter ID(s)</button>
|
||||
<button type="button" className={`btn ${checkedHideIdsState === true ? 'btn-primary' : 'btn-outline-primary'} btn-sm`}
|
||||
id="hideIdButton" onClick={handleHideIds}>Hide Unselected</button>
|
||||
<div id="idListContainer">
|
||||
{idData.map((value, index) =>
|
||||
<label className="id_checkbox_container" id={value + "_id_container"} key={value + "id"}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={value + "_id"}
|
||||
onChange={handleIdCheckboxChange}
|
||||
/>
|
||||
{" " + value}
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
</Col>
|
||||
<Col id="distributionChartContainer">
|
||||
<h6>Attribute Distribution</h6>
|
||||
{distrArgs.map((value) =>
|
||||
<div className="distributionChart" key={value + "distr"}>
|
||||
{<DistributionChart data={distrData} attr={value} id={"distributionChart_" + value}
|
||||
filteredIds={filteredIdData} metadata={metadataButton === true ? metadata : []}
|
||||
hideIds={checkedHideIdsState} metadataAttrColor={metadataAttrColor} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
}
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export default CohortPanel;
|
||||
114
visualization_interface/src/components/colorLegend.d3.js
Normal file
114
visualization_interface/src/components/colorLegend.d3.js
Normal file
@@ -0,0 +1,114 @@
|
||||
import * as d3 from 'd3';
|
||||
import $ from "jquery";
|
||||
import "../index.css"
|
||||
|
||||
export default class ColorLegendD3 {
|
||||
constructor(chart, range, colorScale, derivedData, id, timelineData, timeframe) {
|
||||
if (!d3.select(chart).select('svg').empty()) {
|
||||
d3.select(chart).select('svg').remove()
|
||||
}
|
||||
|
||||
var parentContainer = document.getElementById("colorScaleContainer")
|
||||
var width = parentContainer.clientWidth
|
||||
var height = parentContainer.clientHeight
|
||||
const svg = d3.select(chart)
|
||||
.append('svg')
|
||||
.attr('width', width)
|
||||
.attr('height', height)
|
||||
.attr("id", id)
|
||||
|
||||
if (!derivedData) {
|
||||
return
|
||||
}
|
||||
|
||||
const asymetryColorScale = d3.scaleLinear().domain([0, 40]).range(["white", '#de77ae'])
|
||||
const painColorScale = d3.scaleLinear().domain([0, 1]).range(["white", 'red'])
|
||||
const expressivityColorScale = d3.scaleLinear().domain([0, 1]).range(["white", 'orange'])
|
||||
const AUsColorScale = d3.scaleLinear().domain([0, 5]).range(["white", '#cb181d'])
|
||||
const negMovementColorScale = d3.scaleLinear().domain([-3.14, 0]).range(['#cb181d', "#fff5f0"])
|
||||
const posMovementColorScale = d3.scaleLinear().domain([0, 3.14]).range(["#fff5f0", '#cb181d'])
|
||||
const vissibleAUs = ['AU01', 'AU02', 'AU04', 'AU05', 'AU06', 'AU07', 'AU09', 'AU10', 'AU12', 'AU14', 'AU15', 'AU17', 'AU20', 'AU23', 'AU25', 'AU26']
|
||||
|
||||
if (parseInt(timeframe) === 0) {
|
||||
vissibleAUs.forEach(a => {
|
||||
var el = "fac_" + a + "int"
|
||||
$('.' + a).css("stroke", AUsColorScale(derivedData[el + "_mean"]))
|
||||
})
|
||||
$('#faceAsymetryMask').css("fill", asymetryColorScale(derivedData['fac_asymmaskcom_mean']))
|
||||
$('#leftEyeAsymetryMask').css("fill", asymetryColorScale(derivedData['fac_asymmaskeye_mean']))
|
||||
$('#rightEyeAsymetryMask').css("fill", asymetryColorScale(derivedData['fac_asymmaskeye_mean']))
|
||||
$('#leftEyebrowAsymetryMask').css("fill", asymetryColorScale(derivedData['fac_asymmaskeyebrow_mean']))
|
||||
$('#rightEyebrowAsymetryMask').css("fill", asymetryColorScale(derivedData['fac_asymmaskeyebrow_mean']))
|
||||
$('#mouthAsymetryMask').css("fill", asymetryColorScale(derivedData['fac_asymmaskmouth_mean']))
|
||||
$('#faceExpresivityMask').css("fill", expressivityColorScale(derivedData["fac_comintsoft_mean"]))
|
||||
$('#upperFaceExpresivityMask').css("fill", expressivityColorScale(derivedData["fac_comuppintsoft_mean"]))
|
||||
$('#lowerFaceExpresivityMask').css("fill", expressivityColorScale(derivedData["fac_comlowintsoft_mean"]))
|
||||
$('#facePainExpresivityMask').css("fill", painColorScale(derivedData["fac_paiintsoft_mean"]))
|
||||
$('#pitchUp').css("stroke", posMovementColorScale(derivedData["mov_hposepitch_mean"]))
|
||||
$('#pitchDown').css("stroke", negMovementColorScale(derivedData["mov_hposepitch_mean"]))
|
||||
$('#rollRight').css("stroke", negMovementColorScale(derivedData["mov_hposeroll_mean"]))
|
||||
$('#rollLeft').css("stroke", posMovementColorScale(derivedData["mov_hposeroll_mean"]))
|
||||
$('#yawLeft').css("stroke", posMovementColorScale(derivedData["mov_hposeyaw_mean"]))
|
||||
$('#yawRight').css("stroke", negMovementColorScale(derivedData["mov_hposeyaw_mean"]))
|
||||
}
|
||||
else {
|
||||
vissibleAUs.forEach(a => {
|
||||
var el = "fac_" + a + "int"
|
||||
$('.' + a).css("stroke", AUsColorScale(timelineData[el][parseInt(timeframe) - 1]))
|
||||
})
|
||||
|
||||
if (timelineData["mov_hposeyaw"]) {
|
||||
$('#pitchUp').css("stroke", posMovementColorScale(timelineData["mov_hposepitch"][parseInt(timeframe) - 1]))
|
||||
$('#pitchDown').css("stroke", negMovementColorScale(timelineData["mov_hposepitch"][parseInt(timeframe) - 1]))
|
||||
$('#rollRight').css("stroke", negMovementColorScale(timelineData["mov_hposeroll"][parseInt(timeframe) - 1]))
|
||||
$('#rollLeft').css("stroke", posMovementColorScale(timelineData["mov_hposeroll"][parseInt(timeframe) - 1]))
|
||||
$('#yawLeft').css("stroke", posMovementColorScale(timelineData["mov_hposeyaw"][parseInt(timeframe) - 1]))
|
||||
$('#yawRight').css("stroke", negMovementColorScale(timelineData["mov_hposeyaw"][parseInt(timeframe) - 1]))
|
||||
}
|
||||
|
||||
$('#faceAsymetryMask').css("fill", asymetryColorScale(timelineData['fac_asymmaskcom'][parseInt(timeframe) - 1]))
|
||||
$('#leftEyeAsymetryMask').css("fill", asymetryColorScale(timelineData['fac_asymmaskeye'][parseInt(timeframe) - 1]))
|
||||
$('#rightEyeAsymetryMask').css("fill", asymetryColorScale(timelineData['fac_asymmaskeye'][parseInt(timeframe) - 1]))
|
||||
$('#leftEyebrowAsymetryMask').css("fill", asymetryColorScale(timelineData['fac_asymmaskeyebrow'][parseInt(timeframe) - 1]))
|
||||
$('#rightEyebrowAsymetryMask').css("fill", asymetryColorScale(timelineData['fac_asymmaskeyebrow'][parseInt(timeframe) - 1]))
|
||||
$('#mouthAsymetryMask').css("fill", asymetryColorScale(timelineData['fac_asymmaskmouth'][parseInt(timeframe) - 1]))
|
||||
$('#faceExpresivityMask').css("fill", expressivityColorScale(timelineData["fac_comintsoft"][parseInt(timeframe) - 1]))
|
||||
$('#upperFaceExpresivityMask').css("fill", expressivityColorScale(timelineData["fac_comuppintsoft"][parseInt(timeframe) - 1]))
|
||||
$('#lowerFaceExpresivityMask').css("fill", expressivityColorScale(timelineData["fac_comlowintsoft"][parseInt(timeframe) - 1]))
|
||||
$('#facePainExpresivityMask').css("fill", painColorScale(timelineData["fac_paiintsoft"][parseInt(timeframe) - 1]))
|
||||
}
|
||||
|
||||
|
||||
var linearGradient = svg.append("linearGradient")
|
||||
.attr("id", "linear-gradient")
|
||||
linearGradient
|
||||
.attr("x1", "0%")
|
||||
.attr("y1", "0%")
|
||||
.attr("x2", "100%")
|
||||
.attr("y2", "0%");
|
||||
linearGradient.append("stop")
|
||||
.attr("offset", "0%")
|
||||
.attr("stop-color", colorScale[0])
|
||||
linearGradient.append("stop")
|
||||
.attr("offset", "100%")
|
||||
.attr("stop-color", colorScale[1])
|
||||
|
||||
const colorLegendHeight = height - 10
|
||||
|
||||
svg.append("rect")
|
||||
.attr("width", width - 30)
|
||||
.attr("height", colorLegendHeight)
|
||||
.style("fill", "url(#linear-gradient)")
|
||||
.attr("x", 10)
|
||||
.attr("y", colorLegendHeight - 20)
|
||||
svg.append("text")
|
||||
.text(range[0])
|
||||
.attr("x", 1)
|
||||
.attr("y", height - 10)
|
||||
svg.append("text")
|
||||
.text(range[1])
|
||||
.attr("x", width - 17)
|
||||
.attr("y", height - 10)
|
||||
|
||||
}
|
||||
}
|
||||
19
visualization_interface/src/components/colorLegend.js
Normal file
19
visualization_interface/src/components/colorLegend.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import ColorLegendD3 from './colorLegend.d3';
|
||||
|
||||
const ColorLegend = ({ range, colorScale, derivedData, id, timelineData, timeframe }) => {
|
||||
const ref = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
const currElement = ref.current
|
||||
if (range) {
|
||||
new ColorLegendD3(currElement, range, colorScale, derivedData, id, timelineData, timeframe)
|
||||
}
|
||||
}, [range, colorScale, derivedData, timelineData, timeframe])
|
||||
|
||||
return (
|
||||
<div ref={ref}></div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ColorLegend;
|
||||
@@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
export const ComponentToPrint = React.forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref}></div>
|
||||
);
|
||||
});
|
||||
117
visualization_interface/src/components/correlationMatrix.d3.js
Normal file
117
visualization_interface/src/components/correlationMatrix.d3.js
Normal file
@@ -0,0 +1,117 @@
|
||||
import * as d3 from 'd3';
|
||||
import $ from "jquery";
|
||||
import "../index.css"
|
||||
import DBMDict from '../DBM_attribute_dict.json'
|
||||
|
||||
export default class CorrelationMatrixD3 {
|
||||
constructor(chart, data, parentId) {
|
||||
if (!d3.select(chart).select('svg').empty()) {
|
||||
d3.select(chart).select('svg').remove()
|
||||
}
|
||||
var parentContainer = document.getElementById(parentId)
|
||||
var width = parentContainer.clientWidth
|
||||
var height = parentContainer.clientHeight
|
||||
const svg = d3.select(chart)
|
||||
.append('svg')
|
||||
.attr('width', width)
|
||||
.attr('height', height)
|
||||
|
||||
var values = Object.values(data).map(el => Object.values(el))
|
||||
|
||||
var keys = Object.keys(data).map(e => DBMDict[e]['label'])
|
||||
|
||||
const minRowValue = values.map(el => Math.min(...(el)))
|
||||
const maxRowValue = values.map(el => Math.max(...(el)))
|
||||
const minValue = Math.min(...(minRowValue))
|
||||
const maxValue = Math.max(...(maxRowValue))
|
||||
const colorScale = d3.scaleLinear().domain([minValue, 0, maxValue]).range(["#c34e4c", "#f2f2f2", "#4476a9"])
|
||||
|
||||
var xScale = d3.scaleBand()
|
||||
.domain(keys).range([80, width - 20])
|
||||
.paddingInner(.2)
|
||||
var yScale = d3.scaleBand()
|
||||
.domain(keys).range([height - 80, 10])
|
||||
.paddingInner(.2)
|
||||
|
||||
svg.append("g")
|
||||
.attr("transform", "translate(0," + (height - 70) + ")")
|
||||
.attr("class", "greyAxis")
|
||||
|
||||
.call(d3.axisBottom(xScale))
|
||||
.selectAll("text")
|
||||
.style("text-anchor", "end")
|
||||
.attr("dx", "-.8em")
|
||||
.attr("dy", ".15em")
|
||||
.attr("transform", "rotate(-45)")
|
||||
|
||||
svg.append("g")
|
||||
.attr("transform", "translate(" + 80 + "," + 10 + " )")
|
||||
.attr("class", "greyAxis")
|
||||
.call(d3.axisLeft(yScale))
|
||||
|
||||
values.forEach((row, i) => {
|
||||
row.forEach((el, j) => {
|
||||
if (j >= i)
|
||||
svg.append("rect")
|
||||
.attr("id", "corr_" + keys[j] + "_" + keys[i])
|
||||
.attr("x", xScale(keys[i]) + 2)
|
||||
.attr("y", yScale(keys[j]) + 5)
|
||||
.attr("width", xScale.bandwidth())
|
||||
.attr("height", yScale.bandwidth())
|
||||
.style("fill", d => colorScale(el))
|
||||
.style("opacity", 0.8)
|
||||
.on("mouseover", d => {
|
||||
$("#" + d.target.id).css("stroke", "black")
|
||||
.css("stroke-width", '2px')
|
||||
})
|
||||
.on("mouseout", d => {
|
||||
$("#" + d.target.id).css("stroke-width", '0px')
|
||||
})
|
||||
.append("title")
|
||||
.text(d => keys[j] + " & " + keys[i] + ": " + el)
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
var linearGradient = svg.append("linearGradient")
|
||||
.attr("id", "gradient");
|
||||
linearGradient
|
||||
.attr("x1", "0%")
|
||||
.attr("y1", "0%")
|
||||
.attr("x2", "0%")
|
||||
.attr("y2", "100%");
|
||||
linearGradient.append("stop")
|
||||
.attr("offset", "0%")
|
||||
.attr("stop-color", "#c34e4c")
|
||||
|
||||
linearGradient.append("stop")
|
||||
.attr("offset", "50%")
|
||||
.attr("stop-color", "#f2f2f2")
|
||||
|
||||
linearGradient.append("stop")
|
||||
.attr("offset", "100%")
|
||||
.attr("stop-color", "#4476a9")
|
||||
|
||||
|
||||
const colorLegendHeight = height / 3
|
||||
svg.append("rect")
|
||||
.attr("width", 20)
|
||||
.attr("height", height / 3)
|
||||
.style("fill", "url(#gradient)")
|
||||
.attr("x", width - 50 - 20)
|
||||
.attr("y", height - 75 - colorLegendHeight)
|
||||
svg.append("text")
|
||||
.text("-1")
|
||||
.attr("x", width - 50 + 3)
|
||||
.attr("y", height - 75 - colorLegendHeight + 6)
|
||||
svg.append("text")
|
||||
.text(" 1")
|
||||
.attr("x", width - 50 + 3)
|
||||
.attr("y", height - 75 + 3)
|
||||
svg.append("text")
|
||||
.text(" 0")
|
||||
.attr("x", width - 50 + 3)
|
||||
.attr("y", height - 75 - colorLegendHeight / 2 + 3)
|
||||
|
||||
}
|
||||
}
|
||||
36
visualization_interface/src/components/correlationMatrix.js
Normal file
36
visualization_interface/src/components/correlationMatrix.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import { ComponentToPrint } from './componentToPrint';
|
||||
import { exportComponentAsJPEG } from 'react-component-export-image';
|
||||
import CorrelationMatrixD3 from './correlationMatrix.d3';
|
||||
import "./cohortPanel.css"
|
||||
|
||||
const CorrelationMatrix = ({ data, parentId }) => {
|
||||
const ref = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
const currElement = ref.current
|
||||
if (data) {
|
||||
new CorrelationMatrixD3(currElement, data, parentId)
|
||||
}
|
||||
}, [data])
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div id="corrMatrixComponent">
|
||||
<h6>Correlation Matrix</h6>
|
||||
<button type="button" className='btn btn-outline-secondary btn-sm printButton' onClick={() => exportComponentAsJPEG(ref)}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-printer" viewBox="0 0 16 16">
|
||||
<path d="M2.5 8a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1z" />
|
||||
<path d="M5 1a2 2 0 0 0-2 2v2H2a2 2 0 0 0-2 2v3a2 2 0 0 0 2 2h1v1a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2v-1h1a2 2
|
||||
0 0 0 2-2V7a2 2 0 0 0-2-2h-1V3a2 2 0 0 0-2-2H5zM4 3a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2H4V3zm1 5a2 2 0 0 0-2
|
||||
2v1H2a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1h-1v-1a2 2 0 0 0-2-2H5zm7 2v3a1 1 0 0
|
||||
1-1 1H5a1 1 0 0 1-1-1v-3a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<ComponentToPrint ref={ref} />
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default CorrelationMatrix;
|
||||
125
visualization_interface/src/components/distributionChart.d3.js
Normal file
125
visualization_interface/src/components/distributionChart.d3.js
Normal file
@@ -0,0 +1,125 @@
|
||||
import * as d3 from 'd3';
|
||||
import $ from "jquery";
|
||||
import "../index.css";
|
||||
import DBMDict from '../DBM_attribute_dict.json'
|
||||
|
||||
export default class DistributionChart {
|
||||
|
||||
constructor(chart, data, attr, filteredIds, metadata, hideIds, metadataAttrColor) {
|
||||
if (!d3.select(chart).select('svg').empty()) {
|
||||
d3.select(chart).select('svg').remove()
|
||||
}
|
||||
|
||||
var parentContainer = document.getElementById("distributionChartContainer")
|
||||
var width = parentContainer.clientWidth / 2.8
|
||||
var height = parentContainer.clientHeight / 3.5
|
||||
|
||||
const svg = d3.select(chart)
|
||||
.append('svg')
|
||||
.attr('width', width)
|
||||
.attr('height', height)
|
||||
.append("g")
|
||||
|
||||
var jitterWidth = width / 8
|
||||
|
||||
const values = Object.values(data).map(el => el[attr])
|
||||
const keys = Object.keys(data)
|
||||
|
||||
|
||||
const inputObject = Object.values(data).map(el => [el['id'], el[attr]])
|
||||
|
||||
|
||||
var metadataDict = {}
|
||||
if (metadata.length > 0) {
|
||||
var d = Object.values(metadata).map(el => [el['id'], el['attr']])
|
||||
d.forEach(el => {
|
||||
metadataDict[el[0]] = el[1]
|
||||
})
|
||||
}
|
||||
|
||||
var yScale = d3.scaleLinear()
|
||||
.domain(DBMDict[attr]['range'].length > 0 ? DBMDict[attr]['range'] : [Math.min(...values), Math.max(...values)])
|
||||
.range([height - 30, 20])
|
||||
svg.append("g")
|
||||
.attr("transform", "translate(40 ,0)")
|
||||
.attr("class", "greyAxis")
|
||||
.call(d3.axisLeft(yScale))
|
||||
|
||||
var xScale = d3.scaleBand()
|
||||
.range([1, width - 20])
|
||||
.domain([attr])
|
||||
.padding(0.05)
|
||||
svg.append("g")
|
||||
.attr("transform", "translate(40," + (height - 30) + ")")
|
||||
.attr("class", "greyAxis")
|
||||
.call(d3.axisBottom(xScale))
|
||||
.call(s => s.selectAll("text").attr("dx", "-30").attr("y", "10").text(DBMDict[attr]['label']))
|
||||
|
||||
var histogram = d3.bin()
|
||||
.domain(yScale.domain())
|
||||
.thresholds(yScale.ticks(20))
|
||||
.value(d => d)
|
||||
|
||||
var sumstat = [{ "key": attr, "value": histogram(values) }]
|
||||
|
||||
var binLens = sumstat[0].value.map(l => l.length)
|
||||
|
||||
var xNum = d3.scaleLinear()
|
||||
.range([0, xScale.bandwidth()])
|
||||
.domain([-Math.max(...binLens), Math.max(...binLens)])
|
||||
|
||||
svg.selectAll("distr")
|
||||
.data(sumstat)
|
||||
.enter()
|
||||
.append("g")
|
||||
.attr("transform", d => "translate(" + xScale(d.key) + " ,0)")
|
||||
.append("path")
|
||||
.datum(d => d.value)
|
||||
.style("stroke", "none")
|
||||
.style("fill", "#e7d4e8")
|
||||
.attr("d", d3.area()
|
||||
.x0(xNum(-0.01) + xScale.bandwidth() / 10)
|
||||
.x1(d => (xNum(d.length)) + xScale.bandwidth() / 10)
|
||||
.y(d => Math.abs(d.x0) !== Infinity ? yScale(d.x0) : yScale(0))
|
||||
.curve(d3.curveCatmullRom)
|
||||
)
|
||||
d3.selectAll(".tick line").attr("opacity", 0)
|
||||
|
||||
svg.selectAll("indPoints")
|
||||
.data(inputObject)
|
||||
.enter()
|
||||
.append("circle")
|
||||
.attr("id", d => d[0] + "_distr_" + attr)
|
||||
.attr("class", d => "dot dotDistr dot_" + d[0])
|
||||
.attr("cx", d => (xScale(attr) + xScale.bandwidth() / 2 - Math.random() * jitterWidth - 5))
|
||||
.attr("cy", d => yScale(d[1]))
|
||||
.attr("r", 5.5)
|
||||
.style("fill", d => filteredIds.includes(d[0]) ? "darkorange" : (metadataDict[d[0]] ? metadataAttrColor[d[0]] : "#41ab5d"))
|
||||
.style("opacity", d => filteredIds.includes(d[0]) ? "0.4" : hideIds ? "0" : "0.4")
|
||||
.attr("stroke", "grey")
|
||||
.attr("stroke-width", '1.5px')
|
||||
.on("mouseover", d => {
|
||||
var className = "dot_" + d.target.id.replace("_distr_" + attr, "")
|
||||
d3.selectAll("." + className).style("fill", "darkorange")
|
||||
$("#" + d.target.id.replace("_distr_" + attr, "") + "_id_container").css("color", "#fc6a03")
|
||||
keys.forEach(k => {
|
||||
if ($('#' + k + "_id").is(":checked"))
|
||||
$(".dot_" + k).css("fill", "darkorange")
|
||||
})
|
||||
})
|
||||
.on("mouseout", d => {
|
||||
keys.forEach(k => {
|
||||
$(".dot_" + k).css("fill", $('#' + k+ "_id").is(":checked") ? "darkorange" : (metadataDict[k] ? metadataAttrColor[k] : "#41ab5d"))
|
||||
if ($('#' + k + "_id").is(":checked")) {
|
||||
$(".dot_" + k).css("fill", "darkorange")
|
||||
}
|
||||
})
|
||||
$(".id_checkbox_container").css("color", "black")
|
||||
})
|
||||
.append("title")
|
||||
.text(d => "ID: " + String(d[0]) + "\n" + "Value: " + String(d[1]))
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
35
visualization_interface/src/components/distributionChart.js
Normal file
35
visualization_interface/src/components/distributionChart.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import { ComponentToPrint } from './componentToPrint';
|
||||
import { exportComponentAsJPEG } from 'react-component-export-image';
|
||||
import DistributionChartD3 from './distributionChart.d3';
|
||||
|
||||
const DistributionChart = ({ data, attr, filteredIds, metadata, hideIds, metadataAttrColor }) => {
|
||||
const ref = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
const currElement = ref.current
|
||||
if (data) {
|
||||
new DistributionChartD3(currElement, data, attr, filteredIds, metadata, hideIds, metadataAttrColor)
|
||||
}
|
||||
}, [data])
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ComponentToPrint ref={ref} />
|
||||
<div>
|
||||
<button type="button" className='btn btn-outline-secondary btn-sm printButton' onClick={() => exportComponentAsJPEG(ref)} >
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-printer" viewBox="0 0 16 16">
|
||||
<path d="M2.5 8a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1z" />
|
||||
<path d="M5 1a2 2 0 0 0-2 2v2H2a2 2 0 0 0-2 2v3a2 2 0 0 0 2 2h1v1a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2v-1h1a2 2
|
||||
0 0 0 2-2V7a2 2 0 0 0-2-2h-1V3a2 2 0 0 0-2-2H5zM4 3a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2H4V3zm1 5a2 2 0 0 0-2
|
||||
2v1H2a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1h-1v-1a2 2 0 0 0-2-2H5zm7 2v3a1 1 0 0
|
||||
1-1 1H5a1 1 0 0 1-1-1v-3a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default DistributionChart;
|
||||
325
visualization_interface/src/components/headSVG.js
Normal file
325
visualization_interface/src/components/headSVG.js
Normal file
@@ -0,0 +1,325 @@
|
||||
import React, { useRef } from 'react';
|
||||
import $ from 'jquery';
|
||||
import { exportComponentAsJPEG } from 'react-component-export-image';
|
||||
|
||||
const HeadSVG = ({ width, height }, ref) => {
|
||||
const componentRef = useRef()
|
||||
const showTooltip = () => {
|
||||
if ($('#AUsMaskButton').hasClass("btn-primary") | $('#movMaskButton').hasClass("btn-primary")) {
|
||||
$('.AUtooltip').css("opacity", "1")
|
||||
}
|
||||
}
|
||||
const hideTooltip = () => {
|
||||
$('.AUtooltip').css("opacity", "0")
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div ref={componentRef} style={{ position: "relative", overflow: "none", display: "inline-block", }} onMouseOver={showTooltip} onMouseLeave={hideTooltip}>
|
||||
<svg style={{ position: "absolute" }}
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox={`0 0 ${width} ${parseInt(height) + 100}`}>
|
||||
<g transform="translate(-36,0) scale(0.9,0.9)"
|
||||
fill="#000000" stroke="none">
|
||||
<path fill="#3a3b3c" id="headSketch"
|
||||
d="M1275 3785c-49-8-119-16-155-20-80-8-186-35-276-71-281-112-521-412-595-744-41-182-59-495-39-660 13-97 14-129 7-136-2-2-24 3-49 13-38 14-51 14-75 4-101-42-79-192 82-566 7-16 19-50 25-75 12-46 35-99 120-275 27-55 67-143 89-195 23-52 55-117 71-145 16-27 41-70 54-95 14-25 42-68 63-97 21-28 59-81 86-118l47-66V270c0-233 2-270 15-270s15 35 15 253v252l27-35c67-89 283-276 403-349 89-55 150-70 275-71 137 0 322 18 355 36 44 23 193 147 251 209 122 131 141 152 177 198l37 48 3-270c2-232 4-271 17-271s15 40 15 291v292l33 51c19 28 64 98 102 155 61 93 165 304 165 336 0 14 55 122 75 145 13 16 105 239 105 255 0 9 6 23 79 177 74 159 76 164 76 279 0 116-5 128-61 155-31 15-38 15-80 0l-46-15 6 37c24 159 27 237 17 405-5 98-13 182-16 187s-11 51-16 102c-16 148-24 171-142 407-47 95-61 113-157 204-144 137-232 201-310 227-36 12-74 25-85 30-173 71-539 101-790 65zm398-26c29-6 59-11 67-10 38 1 189-22 225-34 22-7 48-13 58-12 17 0 79-25 165-67 73-36 168-113 254-208 46-50 82-94 81-98s9-18 22-30c14-13 25-27 25-32s15-30 33-56c18-27 41-70 51-98 10-27 21-58 26-69 14-33 40-161 61-300 21-141 26-427 9-501-5-22-12-67-15-100-3-32-8-72-12-88l-5-30-13 30-13 29-1-33c-1-18 4-43 10-54 14-27-3-141-37-240-13-39-21-74-18-77 11-11 42 39 49 79 4 22 11 40 17 40 7 0 8-12 4-32-4-18-11-78-15-133-19-221-74-443-153-620-94-207-299-498-504-712-62-65-226-193-254-199-14-2-44-9-68-14-57-13-412-13-412 0 0 6-8 10-17 10-25 0-110 51-163 97-25 21-75 60-111 85-88 63-197 180-320 347-93 127-197 294-235 381-9 19-37 82-64 140-60 131-79 219-121 545-17 142-20 173-9 140 5-16 15-50 21-75 9-36 13-41 19-25 4 11-3 55-16 104-23 83-25 130-11 241 4 25 4 37 1 28-12-38-22-14-34 80-25 198-16 534 19 702 60 285 199 514 408 672 43 33 105 72 138 88 66 31 221 71 300 77 28 2 71 8 97 13s98 13 160 16 114 7 116 9c8 7 141 3 185-6zM177 2129c44-18 50-25 60-62 6-23 8-58 4-77l-7-34-17 45c-20 51-70 105-107 114-22 5-28 1-42-27s-17-30-17-11c-1 22 44 73 65 73 6 0 34-9 61-21zm2723-24c10-12 9-15-6-15-32 0-81-37-120-89-21-28-40-49-42-46-3 2 3 33 13 69l17 64 52 15c66 20 71 20 86 2zm-2748-42c14-16 39-55 57-87 26-50 31-71 31-127 0-37 4-89 9-116 6-26 15-96 21-154 6-59 17-131 24-160 8-30 13-56 10-58-6-6-71 139-79 177-4 18-27 81-52 140-59 140-92 244-99 313-11 94 26 127 78 72zm2750-10c27-31 38-129 20-182-8-25-20-53-27-61-21-27-103-223-138-332-35-107-96-228-82-163 46 228 56 307 56 460 0 121 2 133 24 167 24 36 114 128 126 128 3 0 13-8 21-17z"
|
||||
transform="matrix(.1 0 0 -.1 0 386)"
|
||||
></path>
|
||||
<path
|
||||
d="M550 2601c-106-44-180-88-180-108 0-17 4-17 48 5 20 10 80 29 133 42 95 23 95 23 185 6 49-10 127-24 174-31 112-17 228-49 286-80 26-14 48-25 50-25 6 0 24 103 18 111-13 21-73 42-217 75-116 27-181 37-277 40l-125 5-95-40zm355-21c90-14 246-49 278-61 15-6 27-18 27-25 0-18-14-18-48 0-41 21-167 53-262 67-101 14-143 28-90 28 19 0 62-4 95-9zM2110 2604c-36-8-85-18-110-23-259-55-269-59-254-113 16-56 25-56 231-5 110 28 231 51 293 57 99 8 112 7 229-21 142-33 141-33 141-16 0 30-55 58-234 117-70 24-198 25-296 4zm65-49c-38-8-133-30-210-49-77-20-148-36-159-36-30 0-2 26 37 33 17 4 95 20 172 36s160 30 185 29c42 0 40-1-25-13zM696 2313c-49-13-186-116-186-139 0-21 9-17 64 29 88 75 118 88 206 94 45 3 80 2 80-3s-27-10-60-11c-49-1-71-7-113-33-46-28-97-85-97-108 0-12 46-51 103-89 49-31 134-53 211-53 41 0 206 74 206 92 0 43-105 139-192 175-29 12-44 22-34 23 11 0 57-18 103-40 86-42 168-112 197-169 9-17 23-31 31-31 23 0 18 14-24 67-72 89-114 122-206 160-79 33-102 38-180 40-49 1-99-1-109-4zm74-51c0-4-9-13-20-20-23-14-28-88-7-114 37-46 100-60 166-37 39 14 65 60 57 100s8 36 55-13c64-69 65-74 22-101-71-45-105-52-196-41-67 9-93 17-138 46-30 19-62 43-72 52-16 17-16 19 3 44 21 26 108 92 123 92 4 0 7-3 7-8zm118-34c18-18 15-42-7-62-26-24-72-17-82 12-16 43 56 83 89 50zM2099 2305c-83-17-155-41-195-66-45-27-123-114-140-154-17-42-17-45-1-45 7 0 21 17 30 38 22 51 91 119 152 150 71 36 178 62 258 62s140-24 238-96c33-25 64-43 67-40 10 11-67 81-120 110-104 56-167 65-289 41z"
|
||||
transform="matrix(.1 0 0 -.1 0 386)"
|
||||
></path>
|
||||
<path
|
||||
d="M2045 2245c-38-14-71-25-73-25-14 0-112-107-112-122 0-13 23-29 87-59 80-37 95-41 168-41 101-1 169 26 251 99l58 52-45 40c-65 59-103 75-189 78-59 2-90-2-145-22zm130-60c0-40 0-40-39-40-51 0-67 22-42 60 14 21 24 26 49 23 30-3 32-5 32-43zm148 9c26-20 47-40 47-43 0-16-69-72-120-97-84-43-155-44-257-5-108 42-113 49-68 99 20 22 49 45 65 50 28 10 28 10 23-22-12-74 97-132 173-93 48 25 65 50 66 101 0 29 5 46 13 46 6 0 33-16 58-36zM1295 2240c-4-7-4-15 1-19 5-3 22-21 39-39l29-32 2-298 2-297-34-85c-18-46-31-86-29-88 14-14 34 16 58 84 27 79 27 80 27 360 0 155-3 294-6 310-8 39-78 122-89 104zM1640 2198l-34-42-7-160c-4-89-8-232-8-319l-1-157 36-71c24-48 39-68 46-61 6 6 1 26-15 58-44 89-50 134-38 335 6 100 11 224 11 274 0 90 1 92 35 132 38 45 42 53 23 53-7 0-29-19-48-42z"
|
||||
transform="matrix(.1 0 0 -.1 0 386)"
|
||||
></path>
|
||||
<path
|
||||
d="M1227 1481c-17-31-27-64-27-90 0-36 6-47 38-77 54-52 75-57 139-39 45 13 60 14 89 3 31-11 40-10 65 5 27 15 32 15 45 2 25-25 110-19 161 10 44 26 47 34 44 95-2 40-49 142-62 134-5-2 1-28 13-57 37-94 36-123-7-151-22-14-28-15-47-2-39 24-88 28-135 10-36-14-49-15-85-4-56 16-104 14-138-7-27-15-29-15-58 8-26 20-31 31-31 69 0 31 7 58 24 85 28 46 30 55 12 55-8 0-26-22-40-49zM1310 949c-110-32-163-52-209-76-24-13-57-23-73-23-19 0-28-4-25-12 2-7 10-13 18-13s46-26 84-58c39-32 96-76 129-98 58-39 59-39 160-40 248-2 341 2 363 16 12 8 59 51 105 96 55 55 91 82 107 83 29 1 41 26 13 26-12 0-58 14-104 30-46 17-112 39-148 50s-78 24-93 29c-22 8-42 5-83-10l-54-20-58 20c-32 12-60 21-63 20-2 0-33-9-69-20zm143-34c38-19 51-19 89 1 17 9 45 16 62 16 33 0 205-49 219-62s-94-34-163-32c-40 1-83-5-112-15-44-16-48-16-106 5-38 14-84 22-128 23-38 0-87 6-109 13l-40 13 45 13c25 7 70 20 100 30 61 19 98 17 143-5zm-15-117c28-11 62-18 75-15 12 3 40 11 62 17 34 9 304 14 313 6 1-2-34-37-79-77l-82-74-213 2-214 1-68 49c-118 83-151 108-152 115 0 3 69 4 153 1 120-4 163-9 205-25z"
|
||||
transform="matrix(.1 0 0 -.1 0 386)">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
||||
<div id="expressivityMaskContainer">
|
||||
<svg height="300" width="100" style={{ position: "absolute" }} id="faceExpresivityMask" transform="translate(-4, 0)" opacity="0.5">
|
||||
<circle cx="205" cy="150" r="49%" transform="translate(-70, -20)" />
|
||||
</svg>
|
||||
<svg height="140" width="100" style={{ position: "absolute" }} transform="translate(107, -2)" id="upperFaceExpresivityMask" opacity="0.5">
|
||||
<circle cx="0" cy="150" r="91%" transform="translate(-43, -20)" />
|
||||
</svg>
|
||||
|
||||
<svg height="140" width="100" style={{ position: "absolute" }} transform="translate(107, 148)" id="lowerFaceExpresivityMask" opacity="0.5">
|
||||
<circle cx="0" cy="150" r="91%" transform="translate(-43, -165)" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<svg height="300" width="200" style={{ position: "absolute" }} id="facePainExpresivityMask" >
|
||||
<ellipse cx="0" cy="150" rx="38%" ry="35%" transform="translate(99, -20)" />
|
||||
</svg>
|
||||
|
||||
<div id="asymetryMaskContainer">
|
||||
<svg height="300" width="200" style={{ position: "absolute" }} id="faceAsymetryMask" opacity="0.5">
|
||||
<ellipse cx="0" cy="150" rx="38%" ry="35%" transform="translate(99, -20)" />
|
||||
</svg>
|
||||
<svg height="300" width="200" style={{ position: "absolute" }} id="leftEyeAsymetryMask" opacity="0.5">
|
||||
<ellipse cx="0" cy="150" ry="5%" rx="14%" transform="translate(58, -31)" />
|
||||
</svg>
|
||||
<svg height="300" width="200" style={{ position: "absolute" }} id="rightEyeAsymetryMask" opacity="0.5">
|
||||
<ellipse cx="0" cy="150" ry="5%" rx="14%" transform="translate(144, -31)" />
|
||||
</svg>
|
||||
<svg height="300" width="200" style={{ position: "absolute" }} id="leftEyebrowAsymetryMask" opacity="0.5">
|
||||
<ellipse cx="0" cy="150" ry="3%" rx="15%" transform="translate(60, -60)" />
|
||||
</svg>
|
||||
<svg height="300" width="200" style={{ position: "absolute" }} id="rightEyebrowAsymetryMask" opacity="0.5">
|
||||
<ellipse cx="0" cy="150" ry="3%" rx="15%" transform="translate(144, -60)" />
|
||||
</svg>
|
||||
<svg height="300" width="200" style={{ position: "absolute" }} id="mouthAsymetryMask" opacity="0.5">
|
||||
<ellipse cx="0" cy="150" rx="20%" ry="7%" transform="translate(100, 55)" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div id="AUHighlightsContainer">
|
||||
<svg height="20" width="20" style={{ position: "absolute" }} id="au12RightHighlight" transform="translate(125, 180)" className="hap_highlight con_highlight emotion_highlight">
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
|
||||
</svg>
|
||||
<svg height="20" width="20" style={{ position: "absolute" }} id="au12LeftHighlight" transform="translate(56, 180)" className="hap_highlight con_highlight emotion_highlight">
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
</svg>
|
||||
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} transform="translate(91, 226)" id="au26Highlight" className="sur_highlight fea_highlight emotion_highlight">
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} transform="translate(58, 145)" id="au9RightHighlight" className='dig_highlight emotion_highlight'>
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} transform="translate(123, 145)" id="au9LeftHighlight" className='dig_highlight emotion_highlight'>
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} transform="translate(43, 90)" id="au4LeftHighlight" className="sad_highlight fea_highlight ang_highlight emotion_highlight">
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} transform="translate(141, 90)" id="au4RightHighlight" className="sad_highlight fea_highlight ang_highlight emotion_highlight">
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} transform="translate(148, 65)" id="au2RightHighlight" className="sur_highlight fea_highlight emotion_highlight">
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} transform="translate(35, 65)" id="au2LeftHighlight" className="sur_highlight fea_highlight emotion_highlight">
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} transform="translate(118, 68)" id="au1RightHighlight" className="sad_highlight sur_highlight fea_highlight emotion_highlight">
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} transform="translate(68, 68)" id="au1LeftHighlight" className="sad_highlight sur_highlight fea_highlight emotion_highlight">
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} transform="translate(162, 104)" id="au5RightHighlight" className="sur_highlight fea_highlight ang_highlight emotion_highlight">
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} transform="translate(16, 104)" id="au5LeftHighlight" className="sur_highlight fea_highlight ang_highlight emotion_highlight">
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} transform="translate(158, 158), rotate(-90)" id="au6RightHighlight" className="hap_highlight emotion_highlight">
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} transform="translate(23, 158),rotate(90)" id="au6LeftHighlight" className="hap_highlight emotion_highlight">
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} transform="translate(135, 188)" id="au20LeftHighlight" className='fea_highlight emotion_highlight'>
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} transform="translate(48, 188)" id="au20RightHighlight" className='fea_highlight emotion_highlight'>
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} transform="translate(134, 200), rotate(-90)" id="au15RightHighlight" className="sad_highlight dig_highlight emotion_highlight">
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} transform="translate(47, 201), rotate(90)" id="au15LeftHighlight" className="sad_highlight dig_highlight emotion_highlight">
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} transform="translate(65, 212), rotate(45)" id="au16LeftHighlight" className='dig_highlight emotion_highlight'>
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} transform="translate(115, 212), rotate(-45)" id="au16RightHighlight" className='dig_highlight emotion_highlight'>
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} transform="translate(108, 180), rotate(45)" id="au23RightUpHighlight" className='ang_highlight emotion_highlight'>
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} transform="translate(72, 180), rotate(-45)" id="au23leftUpHighlight" className='ang_highlight emotion_highlight'>
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} transform="translate(78, 215), rotate(-45)" id="au23LeftDownHighlight" className='ang_highlight emotion_highlight'>
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} transform="translate(102, 215), rotate(45)" id="au23RightDownHighlight" className='ang_highlight emotion_highlight'>
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
</svg>
|
||||
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} transform="translate(28, 125), rotate(-45)" id="au7LeftHighlight" className='fea_highlight ang_highlight emotion_highlight'>
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} transform="translate(150, 125), rotate(45)" id="au7RightHighlight" className='fea_highlight ang_highlight emotion_highlight'>
|
||||
<circle cx="10" cy="10" r="50%" fill="purple" opacity="0.4" />
|
||||
</svg>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="AUContainer" style={{ position: "absolute" }}>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} className="bi bi-arrow-right AU12" viewBox="0 0 20 20" transform="translate(127, 180), rotate(-45)" id="au12Left">
|
||||
<path id="au12Left_path" strokeWidth="12%" d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8z" />
|
||||
<text className='AUtooltip' opacity='0' x="90%" y="1%" transform="rotate(45)" textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2" >12</text>
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} className="bi bi-arrow-left AU12" viewBox="0 0 20 20" transform="translate(54, 182), rotate(45)" id="au12Right">
|
||||
<path id="au12Right_path" strokeWidth="12%" d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8z" />
|
||||
<text className='AUtooltip' opacity='0' x="0%" y="50%" transform="rotate(-45)" textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2" >12</text>
|
||||
</svg>
|
||||
<svg width="16" height="16" style={{ position: "absolute" }} className="bi bi-arrow-down AU26" viewBox="0 0 16 16" transform="translate(93, 226)" id="au26">
|
||||
<path id="au26_path" strokeWidth="12%" d="M8 1a.5.5 0 0 1 .5.5v11.793l3.146-3.147a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 .708-.708L7.5 13.293V1.5A.5.5 0 0 1 8 1z" />
|
||||
<text className='AUtooltip' opacity='0' x="40%" y="50%" textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2" >26</text>
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} className="bi bi-arrow-right AU09" viewBox="0 0 20 20" transform="translate(60, 146)" id="au9Right">
|
||||
<path id="au9Right_path" strokeWidth="12%" d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8z" />
|
||||
<text className='AUtooltip' opacity='0' x="40%" y="50%" textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2" >9</text>
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} className="bi bi-arrow-left AU09" viewBox="0 0 20 20" transform="translate(125, 147)" id="au9Left">
|
||||
<path id="au9Left_path" strokeWidth="12%" d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8z" />
|
||||
<text className='AUtooltip' opacity='0' x="40%" y="50%" textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2">9</text>
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} className="bi bi-arrow-right AU04" viewBox="0 0 20 20" transform="translate(45, 89)" id="au4Left">
|
||||
<path id="au4Left_path" strokeWidth="12%" d="M8 1a.5.5 0 0 1 .5.5v11.793l3.146-3.147a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 .708-.708L7.5 13.293V1.5A.5.5 0 0 1 8 1z" />
|
||||
<text className='AUtooltip' opacity='0' x="40%" y="50%" textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2" >4</text>
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} className="bi bi-arrow-left AU04" viewBox="0 0 20 20" transform="translate(143, 89)" id="au4Right">
|
||||
<path id="au4Right_path" strokeWidth="12%" d="M8 1a.5.5 0 0 1 .5.5v11.793l3.146-3.147a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 .708-.708L7.5 13.293V1.5A.5.5 0 0 1 8 1z" />
|
||||
<text className='AUtooltip' opacity='0' x="40%" y="50%" textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2" >4</text>
|
||||
</svg>
|
||||
<svg width="16" height="16" style={{ position: "absolute" }} className="bi bi-arrow-up AU02" viewBox="0 0 16 16" transform="translate(150, 68)" id="au2Right">
|
||||
<path id="au2Right_path" strokeWidth="12%" d="M8 15a.5.5 0 0 0 .5-.5V2.707l3.146 3.147a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L7.5 2.707V14.5a.5.5 0 0 0 .5.5z" />
|
||||
<text className='AUtooltip' opacity='0' x="40%" y="50%" textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2" >2</text>
|
||||
</svg>
|
||||
<svg width="16" height="16" style={{ position: "absolute" }} className="bi bi-arrow-up AU02" viewBox="0 0 16 16" transform="translate(37, 67)" id="au2Left">
|
||||
<path id="au2Left_path" strokeWidth="12%" d="M8 15a.5.5 0 0 0 .5-.5V2.707l3.146 3.147a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L7.5 2.707V14.5a.5.5 0 0 0 .5.5z" />
|
||||
<text className='AUtooltip' opacity='0' x="40%" y="50%" textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2" >2</text>
|
||||
</svg>
|
||||
<svg width="16" height="16" style={{ position: "absolute" }} className="bi bi-arrow-up AU01" viewBox="0 0 16 16" transform="translate(120, 70)" id="au1Right">
|
||||
<path id="au1Right_path" strokeWidth="12%" d="M8 15a.5.5 0 0 0 .5-.5V2.707l3.146 3.147a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L7.5 2.707V14.5a.5.5 0 0 0 .5.5z" />
|
||||
<text className='AUtooltip' opacity='0' x="40%" y="50%" textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2" >1</text>
|
||||
</svg>
|
||||
<svg width="16" height="16" style={{ position: "absolute" }} className="bi bi-arrow-up AU01" viewBox="0 0 16 16" transform="translate(70, 70)" id="au1Left">
|
||||
<path id="au1Left_path" strokeWidth="12%" d="M8 15a.5.5 0 0 0 .5-.5V2.707l3.146 3.147a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L7.5 2.707V14.5a.5.5 0 0 0 .5.5z" />
|
||||
<text className='AUtooltip' opacity='0' x="40%" y="50%" textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2" >1</text>
|
||||
</svg>
|
||||
<svg width="16" height="16" style={{ position: "absolute" }} className="bi bi-arrow-up AU05" viewBox="0 0 16 16" transform="translate(165, 105)" id="au5Right">
|
||||
<path id="au5Right_path" strokeWidth="12%" d="M8 15a.5.5 0 0 0 .5-.5V2.707l3.146 3.147a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L7.5 2.707V14.5a.5.5 0 0 0 .5.5z" />
|
||||
<text className='AUtooltip' opacity='0' x="40%" y="50%" textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2" >5</text>
|
||||
</svg>
|
||||
<svg width="16" height="16" style={{ position: "absolute" }} className="bi bi-arrow-up AU05" viewBox="0 0 16 16" transform="translate(18, 105)" id="au5Left">
|
||||
<path id="au5Left_path" strokeWidth="12%" d="M8 15a.5.5 0 0 0 .5-.5V2.707l3.146 3.147a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L7.5 2.707V14.5a.5.5 0 0 0 .5.5z" />
|
||||
<text className='AUtooltip' opacity='0' x="40%" y="50%" textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2" >5</text>
|
||||
</svg>
|
||||
<svg width="16" height="16" style={{ position: "absolute" }} className="bi bi-arrow-up AU06" viewBox="0 0 16 16" transform="translate(160, 160), rotate(-90)" id="au6Right">
|
||||
<path id="au6Right_path" strokeWidth="12%" d="M1.5 1.5A.5.5 0 0 0 1 2v4.8a2.5 2.5 0 0 0 2.5 2.5h9.793l-3.347 3.346a.5.5 0 0 0 .708.708l4.2-4.2a.5.5 0 0 0 0-.708l-4-4a.5.5 0 0 0-.708.708L13.293 8.3H3.5A1.5 1.5 0 0 1 2 6.8V2a.5.5 0 0 0-.5-.5z" />
|
||||
<text className='AUtooltip' opacity='0' x="30%" y="-50%" transform='rotate(90)' textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2">6</text>
|
||||
</svg>
|
||||
<svg width="16" height="16" style={{ position: "absolute" }} className="bi bi-arrow-up AU06" viewBox="0 0 16 16" transform="translate(25, 160),rotate(90)" id="au6Left">
|
||||
<path id="au6Left_path" strokeWidth="12%" d="M14.5 1.5a.5.5 0 0 1 .5.5v4.8a2.5 2.5 0 0 1-2.5 2.5H2.707l3.347 3.346a.5.5 0 0 1-.708.708l-4.2-4.2a.5.5 0 0 1 0-.708l4-4a.5.5 0 1 1 .708.708L2.707 8.3H12.5A1.5 1.5 0 0 0 14 6.8V2a.5.5 0 0 1 .5-.5z" />
|
||||
<text className='AUtooltip' opacity='0' x="-30%" y="50%" transform='rotate(-90)' textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2" >6</text>
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} className="bi bi-arrow-right AU20" viewBox="0 0 20 20" transform="translate(135, 190)" id="au20Left">
|
||||
<path id="au20left_path" strokeWidth="12%" d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8z" />
|
||||
<text className='AUtooltip' opacity='0' x="40%" y="50%" textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2" >20</text>
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} className="bi bi-arrow-left AU20" viewBox="0 0 20 20" transform="translate(50, 190)" id="au20Right">
|
||||
<path id="au20Right_path" strokeWidth="12%" d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8z" />
|
||||
<text className='AUtooltip' opacity='0' x="40%" y="50%" textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2" >20</text>
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} className="bi bi-arrow-left AU15" viewBox="0 0 20 20" transform="translate(136, 200), rotate(-90)" id="au15Right">
|
||||
<path id="au15Right_path" strokeWidth="12%" d="M14.5 1.5a.5.5 0 0 1 .5.5v4.8a2.5 2.5 0 0 1-2.5 2.5H2.707l3.347 3.346a.5.5 0 0 1-.708.708l-4.2-4.2a.5.5 0 0 1 0-.708l4-4a.5.5 0 1 1 .708.708L2.707 8.3H12.5A1.5 1.5 0 0 0 14 6.8V2a.5.5 0 0 1 .5-.5z" />
|
||||
<text className='AUtooltip' opacity='0' x="30%" y="-50%" transform='rotate(90)' textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2" >15</text>
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} className="bi bi-arrow-left AU15" viewBox="0 0 20 20" transform="translate(43, 204), rotate(90)" id="au15Left">
|
||||
<path id="au15Left_path" strokeWidth="12%" d="M1.5 1.5A.5.5 0 0 0 1 2v4.8a2.5 2.5 0 0 0 2.5 2.5h9.793l-3.347 3.346a.5.5 0 0 0 .708.708l4.2-4.2a.5.5 0 0 0 0-.708l-4-4a.5.5 0 0 0-.708.708L13.293 8.3H3.5A1.5 1.5 0 0 1 2 6.8V2a.5.5 0 0 0-.5-.5z" />
|
||||
<text className='AUtooltip' opacity='0' x="-30%" y="50%" transform='rotate(-90)' textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2" >15</text>
|
||||
</svg>
|
||||
<svg width="16" height="16" style={{ position: "absolute" }} className="bi bi-arrow-down AU17" viewBox="0 0 16 16" transform="translate(67, 215), rotate(45)" id="au16Left">
|
||||
<path id="au16Left_path" strokeWidth="12%" d="M8 1a.5.5 0 0 1 .5.5v11.793l3.146-3.147a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 .708-.708L7.5 13.293V1.5A.5.5 0 0 1 8 1z" />
|
||||
<text className='AUtooltip' opacity='0' x="0%" y="80%" transform='rotate(-45)' textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2" >16</text>
|
||||
</svg>
|
||||
<svg width="16" height="16" style={{ position: "absolute" }} className="bi bi-arrow-down AU17" viewBox="0 0 16 16" transform="translate(117, 215), rotate(-45)" id="au16Right">
|
||||
<path id="au16Right_path" strokeWidth="12%" d="M8 1a.5.5 0 0 1 .5.5v11.793l3.146-3.147a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 .708-.708L7.5 13.293V1.5A.5.5 0 0 1 8 1z" />
|
||||
<text className='AUtooltip' opacity='0' x="80%" y="10%" transform='rotate(45)' textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2" >16</text>
|
||||
</svg>
|
||||
<svg width="16" height="16" style={{ position: "absolute" }} className="bi bi-arrow-down AU23" viewBox="0 0 16 16" transform="translate(112, 180), rotate(45)" id="au23RightUp">
|
||||
<path id="au23RightUp_path" strokeWidth="12%" d="M8 1a.5.5 0 0 1 .5.5v11.793l3.146-3.147a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 .708-.708L7.5 13.293V1.5A.5.5 0 0 1 8 1z" />
|
||||
<text className='AUtooltip' opacity='0' x="0%" y="80%" transform='rotate(-45)' textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2">23</text>
|
||||
</svg>
|
||||
<svg width="16" height="16" style={{ position: "absolute" }} className="bi bi-arrow-down AU23" viewBox="0 0 16 16" transform="translate(72, 180), rotate(-45)" id="au23leftUp">
|
||||
<path id="au23LeftUp_path" strokeWidth="12%" d="M8 1a.5.5 0 0 1 .5.5v11.793l3.146-3.147a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 .708-.708L7.5 13.293V1.5A.5.5 0 0 1 8 1z" />
|
||||
<text className='AUtooltip' opacity='0' x="80%" y="10%" transform='rotate(45)' textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2">23</text>
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} className="bi bi-arrow-right AU23" viewBox="0 0 20 20" transform="translate(80, 217), rotate(-45)" id="au23LeftDown">
|
||||
<path id="au23LeftDown_path" strokeWidth="12%" d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8z" />
|
||||
<text className='AUtooltip' opacity='0' x="80%" y="10%" transform='rotate(45)' textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2">23</text>
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} className="bi bi-arrow-left AU23" viewBox="0 0 20 20" transform="translate(103, 220), rotate(45)" id="au23RightDown">
|
||||
<path id="au23RightDown_path" strokeWidth="12%" d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8z" />
|
||||
<text className='AUtooltip' opacity='0' x="0%" y="80%" transform='rotate(-45)' textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2" >23</text>
|
||||
</svg>
|
||||
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} className="bi bi-arrow-right AU07" viewBox="0 0 20 20" transform="translate(30, 125), rotate(-45)" id="au7Left">
|
||||
<path id="au7Left_path" strokeWidth="12%" d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8z" />
|
||||
<text className='AUtooltip' opacity='0' x="80%" y="20%" transform='rotate(45)' textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2">7</text>
|
||||
</svg>
|
||||
<svg width="20" height="20" style={{ position: "absolute" }} className="bi bi-arrow-left AU07" viewBox="0 0 20 20" transform="translate(150, 127), rotate(45)" id="au7Right">
|
||||
<path id="au7Right_path" strokeWidth="12%" d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8z" />
|
||||
<text className='AUtooltip' opacity='0' x="0%" y="80%" transform='rotate(-45)' textAnchor="middle" fontSize="10px" stroke="black" strokeWidth="0.2" >7</text>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="headMovementMaskContainer">
|
||||
<svg width="32" height="32" style={{ position: "absolute" }} className="bi bi-arrow-down" viewBox="0 0 32 32" transform="translate(85, -25)" id="pitchUp"
|
||||
>
|
||||
<path id="pitchUp_path" strokeWidth="4px" d="M17.504 26.025l.001-14.287 6.366 6.367L26 15.979 15.997 5.975 6 15.971 8.129 18.1l6.366-6.368v14.291z" />
|
||||
</svg>
|
||||
|
||||
<svg width="302" height="32" style={{ position: "absolute" }} className="bi bi-arrow-down" viewBox="0 0 32 32" transform="translate(-50, 260)" id="pitchDown">
|
||||
<path id="pitchDown_path" strokeWidth="4px" d="m14.496 5.975l-.001 14.287-6.366-6.367L6 16.021l10.003 10.004L26 16.029 23.871 13.9l-6.366 6.368V5.977z" />
|
||||
</svg>
|
||||
|
||||
<svg width="32" height="32" style={{ position: "absolute" }} className="bi bi-arrow-down" viewBox="0 0 32 32" transform="translate(170, 0), rotate(-45)" id="rollRight">
|
||||
<path id="rollRight_path" strokeWidth="4px" d="M5.975 17.504l14.287.001-6.367 6.366L16.021 26l10.004-10.003L16.029 6l-2.128 2.129 6.367 6.366H5.977z" />
|
||||
</svg>
|
||||
|
||||
<svg width="32" height="32" style={{ position: "absolute" }} className="bi bi-arrow-down" viewBox="0 0 32 32" transform="translate(5, 0), rotate(45)" id="rollLeft">
|
||||
<path id="rollLeft_path" strokeWidth="4px" d="M26.025 14.496l-14.286-.001 6.366-6.366L15.979 6 5.975 16.003 15.971 26l2.129-2.129-6.367-6.366h14.29z" />
|
||||
</svg>
|
||||
<svg width="32" height="32" style={{ position: "absolute" }} className="bi bi-arrow-down" viewBox="0 0 32 32" transform="translate(-30, 120)" id="yawLeft">
|
||||
<path id="yawLeft_path" strokeWidth="4px" d="M26.025 14.496l-14.286-.001 6.366-6.366L15.979 6 5.975 16.003 15.971 26l2.129-2.129-6.367-6.366h14.29z" />
|
||||
</svg>
|
||||
<svg width="32" height="32" style={{ position: "absolute" }} className="bi bi-arrow-down" viewBox="0 0 32 32" transform="translate(200, 120)" id="yawRight">
|
||||
<path id="yawRight_path" strokeWidth="4px" d="M5.975 17.504l14.287.001-6.367 6.366L16.021 26l10.004-10.003L16.029 6l-2.128 2.129 6.367 6.366H5.977z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div id="tooltip" display="none"
|
||||
style={{ position: "absolute", display: "none", fontSize: "0.6rem", background: "white", border: "1px solid black", padding: "2px" }}></div>
|
||||
</div>
|
||||
<button type="button" className='btn btn-outline-secondary btn-sm' style={{ width: "max-content", position: "absolute", left: "0", }} onClick={() => exportComponentAsJPEG(componentRef)}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-printer" viewBox="0 0 16 16">
|
||||
<path d="M2.5 8a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1z" />
|
||||
<path d="M5 1a2 2 0 0 0-2 2v2H2a2 2 0 0 0-2 2v3a2 2 0 0 0 2 2h1v1a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2v-1h1a2 2
|
||||
0 0 0 2-2V7a2 2 0 0 0-2-2h-1V3a2 2 0 0 0-2-2H5zM4 3a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2H4V3zm1 5a2 2 0 0 0-2
|
||||
2v1H2a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1h-1v-1a2 2 0 0 0-2-2H5zm7 2v3a1 1 0 0
|
||||
1-1 1H5a1 1 0 0 1-1-1v-3a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1z" />
|
||||
</svg>
|
||||
</button>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
export default HeadSVG;
|
||||
87
visualization_interface/src/components/histogram.d3.js
Normal file
87
visualization_interface/src/components/histogram.d3.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import * as d3 from 'd3';
|
||||
import { axisLeft, axisBottom } from "https://cdn.skypack.dev/d3-axis@3";
|
||||
import "../index.css"
|
||||
import $ from "jquery";
|
||||
import DBMDict from '../DBM_attribute_dict.json'
|
||||
|
||||
export default class HistogramD3 {
|
||||
constructor(chart, data, attr, color, timeframe) {
|
||||
if (!d3.select(chart).select('svg').empty()) {
|
||||
d3.select(chart).select('svg').remove()
|
||||
}
|
||||
|
||||
var parentContainer = attr.includes("fac_") ? document.getElementById("facialActivityContainer") :
|
||||
attr.includes("mov_") ? document.getElementById("headMovementContainer") :
|
||||
document.getElementById("voiceAcousticsContainer")
|
||||
|
||||
var width = parentContainer.clientWidth > 600 ? parentContainer.clientWidth / 3.6 : parentContainer.clientWidth / 2
|
||||
var height = 70
|
||||
var marginLeft = 35
|
||||
|
||||
const svg = d3.select(chart)
|
||||
.append('svg')
|
||||
.attr('width', width)
|
||||
.attr('height', height)
|
||||
|
||||
var values = data.map(el => el[attr])
|
||||
|
||||
var xScale = d3.scaleLinear()
|
||||
.domain([0, values.length - 1])
|
||||
.range([marginLeft, width - 5])
|
||||
|
||||
var xAxis = axisBottom(xScale)
|
||||
xAxis.ticks(5)
|
||||
svg.append("g")
|
||||
.attr("transform", "translate(0," + (height - 20) + ")")
|
||||
.call(xAxis)
|
||||
|
||||
var minVal = DBMDict[attr]['range'].length > 0 ? DBMDict[attr]['range'][0] : Math.min(...values)
|
||||
var maxVal = DBMDict[attr]['range'].length > 0 ? DBMDict[attr]['range'][1] : Math.max(...values)
|
||||
var yScale = d3.scaleLinear()
|
||||
.range([height - 20, 5])
|
||||
.domain(attr ==="aco_int" ? [0, maxVal]: [minVal, maxVal])
|
||||
|
||||
|
||||
var yAxis = axisLeft(yScale)
|
||||
yAxis.ticks(3)
|
||||
svg.append("g")
|
||||
.attr("transform", "translate(" + (marginLeft - 2) + ",0)")
|
||||
.call(yAxis)
|
||||
|
||||
svg.append("path")
|
||||
.datum(values)
|
||||
.attr("fill", color[1])
|
||||
.attr("stroke", color[0])
|
||||
.attr("stroke-width", 1)
|
||||
.attr("d", d3.area()
|
||||
.x((d, i) => xScale(i))
|
||||
.y0(yScale(0))
|
||||
.y1(d => yScale(d))
|
||||
)
|
||||
|
||||
// CHECK IF CORRECT
|
||||
var segmentLength = parseInt(values.length / 19)
|
||||
var valuesAux = values
|
||||
for (var t = 0; t < 20; t++) {
|
||||
svg.append("path")
|
||||
.datum(valuesAux.slice(0, segmentLength))
|
||||
.attr("class", "areaSegment areaSegment_" + (t + 1))
|
||||
.attr("fill", '#cb181d')
|
||||
.attr("stroke", "#fb6a4a")
|
||||
.attr("stroke-width", 0.5)
|
||||
.attr("opacity", 0)
|
||||
.attr("d", d3.area()
|
||||
.x((d, i) => xScale(i + (t * segmentLength)))
|
||||
.y0(yScale(0))
|
||||
.y1(d => yScale(d))
|
||||
)
|
||||
valuesAux.splice(0, segmentLength)
|
||||
}
|
||||
if (parseInt(timeframe) !== 0) {
|
||||
$(".areaSegment").css("opacity", "0")
|
||||
$(`.areaSegment_${parseInt(timeframe)}`).css("opacity", "1")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
42
visualization_interface/src/components/histogram.js
Normal file
42
visualization_interface/src/components/histogram.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import { exportComponentAsJPEG } from 'react-component-export-image';
|
||||
import HistogramD3 from './histogram.d3';
|
||||
import DBMDict from '../DBM_attribute_dict.json'
|
||||
import "./individualPanel.css"
|
||||
|
||||
const Histogram = ({ data, derivedData, attr, color, timeframe }) => {
|
||||
const ref = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
const currElement = ref.current
|
||||
if (data) {
|
||||
new HistogramD3(currElement, data, attr, color, timeframe)
|
||||
}
|
||||
}, [data, attr])
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div ref={ref}>
|
||||
<div>
|
||||
<div>{DBMDict[attr]['label']}</div>
|
||||
<div className="histogramStatisticsContainer">
|
||||
{derivedData[attr + "_mean"] ? <div className="histogramStatistics">{"mean: " + derivedData[attr + "_mean"]}</div> : ""}
|
||||
{derivedData[attr + "_std"] ? <div className="histogramStatistics">{"std: " + derivedData[attr + "_mean"]}</div> : ""}
|
||||
</div>
|
||||
{DBMDict[attr]['range'].length > 0 && <div className="histogramStatistics">{"range: [" + DBMDict[attr]['range'] + "]"}</div>}
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" className='btn btn-outline-secondary btn-sm printButton' onClick={() => exportComponentAsJPEG(ref)} >
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-printer" viewBox="0 0 16 16">
|
||||
<path d="M2.5 8a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1z" />
|
||||
<path d="M5 1a2 2 0 0 0-2 2v2H2a2 2 0 0 0-2 2v3a2 2 0 0 0 2 2h1v1a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2v-1h1a2 2
|
||||
0 0 0 2-2V7a2 2 0 0 0-2-2h-1V3a2 2 0 0 0-2-2H5zM4 3a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2H4V3zm1 5a2 2 0 0 0-2
|
||||
2v1H2a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1h-1v-1a2 2 0 0 0-2-2H5zm7 2v3a1 1 0 0
|
||||
1-1 1H5a1 1 0 0 1-1-1v-3a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1z" />
|
||||
</svg>
|
||||
</button>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default Histogram;
|
||||
337
visualization_interface/src/components/individualPanel.css
Normal file
337
visualization_interface/src/components/individualPanel.css
Normal file
@@ -0,0 +1,337 @@
|
||||
.histogramComponent {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.printButton {
|
||||
width: max-content;
|
||||
height: max-content;
|
||||
}
|
||||
|
||||
.spiderChartLabel {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.spiderChartComponent {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.histogramStatisticsContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.histogramStatistics {
|
||||
font-size: 0.7rem;
|
||||
margin-top: 3px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#timelineContainer {
|
||||
height: 5%;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 250px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.sliderContainer {
|
||||
margin: 5px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#timelineRange {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
#steplist {
|
||||
display: inline-flex;
|
||||
margin-top: -10px !important;
|
||||
}
|
||||
|
||||
#steplist option {
|
||||
margin-left: 2px;
|
||||
margin-right: 4px;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
#timeFramesContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
margin: auto;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
#facialFrameLabel {
|
||||
margin: auto;
|
||||
font-weight: bolder;
|
||||
color: #41ab5d;
|
||||
}
|
||||
|
||||
#movementFrameLabel {
|
||||
margin: auto;
|
||||
font-weight: bolder;
|
||||
color: #4292c6;
|
||||
}
|
||||
|
||||
#acousticFrameLabel {
|
||||
margin: auto;
|
||||
font-weight: bolder;
|
||||
color: #807dba;
|
||||
}
|
||||
|
||||
#frameLabel {
|
||||
margin: auto;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
#headContainer {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
#headAndIdPanelContainer {
|
||||
height: 100%;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
#componentsContainer {
|
||||
height: 90vh;
|
||||
width: 99vw;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
#faceMaskButtonContainer {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
gap: 3px;
|
||||
margin-bottom: 10px;
|
||||
margin-left: -12px;
|
||||
}
|
||||
|
||||
#headComponentContainer {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
#colorLegendAndIdPanelContainer {
|
||||
margin-top: 300px;
|
||||
}
|
||||
|
||||
#AUButtonContainer {
|
||||
width: 100%;
|
||||
height: 3vh;
|
||||
font-size: 0.7rem;
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.id_checkbox_container {
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
#colorScaleContainer {
|
||||
width: 90%;
|
||||
height: 5vh;
|
||||
}
|
||||
|
||||
#idPanelContainerIndividual {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 15px;
|
||||
padding-top: 10px;
|
||||
font-size: 0.7rem;
|
||||
width: 230px;
|
||||
box-shadow: rgba(0, 0, 0, 0.16) 0px 1px 4px;
|
||||
height: calc(90vh - 420px);
|
||||
}
|
||||
|
||||
#idPanelContainerIndividual div {
|
||||
font-size: 0.8rem;
|
||||
width: 210px;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
margin-top: 10px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#facialAndSpeechContainer {
|
||||
height: 100%;
|
||||
margin-right: 10px;
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
#facialActivityContainer {
|
||||
height: 48%;
|
||||
background-color: #f7fcf5;
|
||||
padding: 10px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
#facialContainer {
|
||||
font-size: 0.8rem;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
#facialQueryPanelContainer {
|
||||
margin-left: -10px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
#facialQueryPanelButtonsContainer {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#facialHistogramsContainer {
|
||||
width: 77%;
|
||||
height: 100%;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
|
||||
.histogramContainer {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
padding: 10px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
#facialSpeechAndCorrPanelContainer {
|
||||
margin-top: 30px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 48%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#facialSpeechAnCorrPanelButtons {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#facialSpeechAnCorrPanelButtons .btn {
|
||||
height: max-content;
|
||||
}
|
||||
|
||||
#facialAndSpeechPanelContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#SpiderChartContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.spiderComponent {
|
||||
width: max-content;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.spiderChart {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#speechLabel {
|
||||
margin-bottom: 10px;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
#speechComponent {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.corrMatrixContainer {
|
||||
height: 85%;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#corrQueryPanelContainer {
|
||||
width: 25%;
|
||||
height: 95%;
|
||||
overflow-y: scroll;
|
||||
font-size: 0.8rem;
|
||||
border-radius: 15px;
|
||||
box-shadow: rgba(0, 0, 0, 0.16) 0px 1px 4px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
#corrQuerryButton {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#corrMatrixComponentIndividual {
|
||||
width: 74%;
|
||||
height: 90%;
|
||||
}
|
||||
|
||||
#movementAndAcousticContainer {
|
||||
height: 100%;
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
#headMovementContainer {
|
||||
height: 48%;
|
||||
overflow-Y: scroll;
|
||||
background-color: #f7fbff;
|
||||
display: flex;
|
||||
padding: 10px;
|
||||
|
||||
}
|
||||
|
||||
#movementContainer {
|
||||
width: max-content;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
#movementQueryButtonContainer {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
margin-top: 5px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
#movementHistogramContainer {
|
||||
width: 80%;
|
||||
height: 100%;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
#voiceAcousticsContainer {
|
||||
height: 48%;
|
||||
overflow-y: scroll;
|
||||
background-color: #f7f4f9;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
#acousticsContainer {
|
||||
padding: 10px;
|
||||
margin-left: -10px;
|
||||
font-size: 0.8rem;
|
||||
margin-right: 5px;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
#acousticsQueryButtonContainer {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#acousticsHistogramContainer {
|
||||
width: 79%;
|
||||
height: 100%;
|
||||
overflow-y: hidden;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
584
visualization_interface/src/components/individualPanel.js
Normal file
584
visualization_interface/src/components/individualPanel.js
Normal file
@@ -0,0 +1,584 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import { Row, Col } from 'react-bootstrap';
|
||||
import $ from 'jquery';
|
||||
import HeadSVG from './headSVG.js'
|
||||
import SpiderChart from './spiderChart.js';
|
||||
import Histogram from './histogram.js';
|
||||
import QueryPanel from './queryPanel.js';
|
||||
import ColorLegend from './colorLegend.js';
|
||||
import CorrelationMatrix from './correlationMatrix.js';
|
||||
import DBMDict from "../DBM_attribute_dict.json"
|
||||
import './individualPanel.css'
|
||||
|
||||
function IndividualPanel() {
|
||||
const [movementButton, setMovementButton] = useState(false)
|
||||
const [asymetryButton, setAsymetryButton] = useState(false)
|
||||
const [painButton, setPainButton] = useState(false)
|
||||
const [expressivityButton, setExpressivityButton] = useState(false)
|
||||
const [AUsButton, setAUsButton] = useState(false)
|
||||
const [facialMaskColor, setFacialMaskColor] = useState('white')
|
||||
const [facialMaskVals, setFacialMaskVals] = useState(null)
|
||||
const emotions = ["ang", "fea", "dig", "sad", "con", "sur", "hap"]
|
||||
const [checkedEmotions, setCheckedEmotions] = useState(new Array(emotions.length).fill(false))
|
||||
|
||||
const [rawFacialData, setFacialRawData] = useState(null)
|
||||
const [facialTimelineData, setFacialTimelineData] = useState(null)
|
||||
const [rawMovementData, setMovementRawData] = useState(null)
|
||||
const [rawAcousticData, setAcousticRawData] = useState(null)
|
||||
const [derivedData, setDerivedData] = useState({})
|
||||
const meanEmotionsSoft = ["fac_feaintsoft_mean", "fac_disintsoft_mean", "fac_sadintsoft_mean",
|
||||
"fac_conintsoft_mean", "fac_surintsoft_mean", "fac_hapintsoft_mean", "fac_angintsoft_mean"]
|
||||
const meanAUsSoft = ["fac_AU02int_mean", "fac_AU04int_mean", "fac_AU05int_mean", "fac_AU06int_mean",
|
||||
"fac_AU07int_mean", "fac_AU09int_mean", "fac_AU10int_mean",
|
||||
"fac_AU12int_mean", "fac_AU14int_mean", "fac_AU15int_mean", "fac_AU17int_mean", "fac_AU20int_mean",
|
||||
"fac_AU23int_mean", "fac_AU25int_mean", "fac_AU26int_mean", "fac_AU01int_mean",]
|
||||
|
||||
const [timeslotValue, setTimeslotValue] = useState("0")
|
||||
const timepoints = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20"]
|
||||
const [checkedFacialState, setCheckedFacialState] = useState([]);
|
||||
const [checkedAcousticState, setCheckedAcousticState] = useState([]);
|
||||
const [checkedMovementState, setCheckedMovementState] = useState([]);
|
||||
|
||||
const [facialAttr, setFacialAttr] = useState([])
|
||||
const [movementAttr, setMovementAttr] = useState([])
|
||||
const [acousticAttr, setAcousticAttr] = useState([])
|
||||
const [allFacialAttr, setAllFacialAttr] = useState([])
|
||||
const [allMovementAttr, setAllMovementAttr] = useState([])
|
||||
const [allAcousticAttr, setAllAcousticAttr] = useState([])
|
||||
const [speechDerivedData, setSpeechDerivedData] = useState([])
|
||||
|
||||
|
||||
const [corrMatrixData, setCorrMatrixData] = useState([])
|
||||
const [checkedCorrMatrixState, setCheckedCorrMatrixState] = useState([]);
|
||||
const [allCorrMatrixAttr, setAllCorrMatrixAttr] = useState([])
|
||||
|
||||
|
||||
const [idData, setIdData] = useState([])
|
||||
const [selectedId, setSelectedId] = useState(null)
|
||||
|
||||
const [speechButton, setSpeechButton] = useState(true)
|
||||
const [corrButton, setCorrButton] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
$('#asymetryMaskContainer').css("opacity", "0")
|
||||
$('#expressivityMaskContainer').css("opacity", "0")
|
||||
$('#facePainExpresivityMask').css("opacity", "0")
|
||||
$('#AUContainer').css("opacity", "0")
|
||||
$('#headMovementMaskContainer').css("opacity", "0")
|
||||
$('.emotion_highlight').css("opacity", "0")
|
||||
}, [])
|
||||
|
||||
const handleFaceMask = param => {
|
||||
$('#asymetryMaskContainer').css("opacity", param === "asym" & !asymetryButton ? "1" : "0")
|
||||
$('#facePainExpresivityMask').css("opacity", param === "pain" & !painButton ? "0.5" : "0")
|
||||
$('#expressivityMaskContainer').css("opacity", param === "expr" & !expressivityButton ? "1" : "0")
|
||||
$('#AUContainer').css("opacity", param === "aus" & !AUsButton ? "1" : "0")
|
||||
|
||||
setAsymetryButton(param === "asym" ? !asymetryButton : false)
|
||||
setPainButton(param === "pain" ? !painButton : false)
|
||||
setExpressivityButton(param === "expr" ? !expressivityButton : false)
|
||||
if(param === "aus" & AUsButton){
|
||||
$('.emotion_highlight').css("opacity", "0")
|
||||
}
|
||||
|
||||
setAUsButton(param === "aus" ? !AUsButton : false)
|
||||
setFacialMaskColor(param === "asym" ? "#de77ae" : param === "pain" ? "red" : param === "expr" ? "orange" : "#cb181d")
|
||||
setFacialMaskVals(param === "asym" ? [0, 40] : param === "pain" ? [0, 1] : param === "expr" ? [0, 1] : [0, 1])
|
||||
}
|
||||
|
||||
const handleMovementMask = () => {
|
||||
$('#headMovementMaskContainer').css("opacity", !movementButton ? "1" : "0")
|
||||
setMovementButton(!movementButton)
|
||||
setFacialMaskColor(asymetryButton ? "#de77ae" : painButton ? "red" : expressivityButton ? "orange" : "#cb181d")
|
||||
setFacialMaskVals(asymetryButton ? [0, 40] : painButton ? [0, 1] : expressivityButton ? [0, 1] : [0, 1])
|
||||
}
|
||||
|
||||
const fetchIndividualData = id => {
|
||||
if (id) {
|
||||
fetch("/fetchIndividualFacialTimelineData", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-type': "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"id": id
|
||||
})
|
||||
}).then(
|
||||
res => res.json()
|
||||
).then(
|
||||
data => {
|
||||
setFacialTimelineData(data)
|
||||
}
|
||||
)
|
||||
|
||||
fetch("/fetchIndividualFacialRawData", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-type': "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"id": id
|
||||
})
|
||||
}).then(
|
||||
res => res.json()
|
||||
).then(
|
||||
data => {
|
||||
setFacialRawData(data)
|
||||
}
|
||||
)
|
||||
|
||||
fetch("/fetchIndividualMovementRawData", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-type': "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"id": id
|
||||
})
|
||||
}).then(
|
||||
res => res.json()
|
||||
).then(
|
||||
data => {
|
||||
setMovementRawData(data)
|
||||
}
|
||||
)
|
||||
|
||||
fetch("/fetchIndividualAcousticRawData", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-type': "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"id": id
|
||||
})
|
||||
}).then(
|
||||
res => res.json()
|
||||
).then(
|
||||
data => {
|
||||
setAcousticRawData(data)
|
||||
}
|
||||
)
|
||||
|
||||
fetch("/fetchIndividualDerivedData", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-type': "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"id": id
|
||||
})
|
||||
}).then(
|
||||
res => res.json()
|
||||
).then(
|
||||
data => {
|
||||
setDerivedData(data[0])
|
||||
setSpeechDerivedData(Object.keys(data[0]).filter(e => e.includes("nlp")))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/getRawAttributesAndIds").then(
|
||||
res => res.json()
|
||||
).then(
|
||||
data => {
|
||||
setAllFacialAttr(data['facial'])
|
||||
setAllAcousticAttr(data['acoustic'])
|
||||
setAllMovementAttr(data['movement'])
|
||||
if (data['facial'].length > 6)
|
||||
setFacialAttr(data['facial'].slice(0, 6))
|
||||
if (data['movement'].length > 6)
|
||||
setMovementAttr(data['movement'].slice(0, 6))
|
||||
if (data['acoustic'].length > 6)
|
||||
setAcousticAttr(data['acoustic'].slice(0, 6))
|
||||
setAllCorrMatrixAttr(data['facial'].concat(data['movement']).concat(data['acoustic']))
|
||||
setCheckedAcousticState(new Array(data['acoustic'].length).fill(false))
|
||||
setCheckedMovementState(new Array(data['movement'].length).fill(false))
|
||||
setCheckedFacialState(new Array(data['facial'].length).fill(false))
|
||||
setIdData(data['ids'].map(el => el.split("/")[1].replace(".mp4", "")))
|
||||
setCheckedCorrMatrixState(new Array(data['acoustic'].length + data['facial'].length + data['movement'].length).fill(false))
|
||||
if (data['ids'].length > 0) {
|
||||
var firstId = data['ids'].map(el => el.split("/")[1].replace(".mp4", ""))[0]
|
||||
setSelectedId(firstId)
|
||||
fetchIndividualData(firstId)
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
}, [])
|
||||
|
||||
const setTime = (ev) => {
|
||||
setTimeslotValue(ev.target.value)
|
||||
$(".areaSegment").css("opacity", "0")
|
||||
$(`.areaSegment_${ev.target.value}`).css("opacity", "1")
|
||||
|
||||
var facialSeg = parseInt(rawFacialData.length / 19)
|
||||
var movementSeg = parseInt(rawMovementData.length / 19)
|
||||
var acousticSeg = parseInt(rawAcousticData.length / 19)
|
||||
|
||||
|
||||
const facialReminder = rawFacialData.length - facialSeg * 19
|
||||
const movementReminder = rawMovementData.length - movementSeg * 19
|
||||
const acousticReminder = rawAcousticData.length - acousticSeg * 19
|
||||
|
||||
facialSeg = facialSeg + (parseInt(ev.target.value) <= facialReminder ? 1 : 0)
|
||||
movementSeg = movementSeg + (parseInt(ev.target.value) <= movementReminder ? 1 : 0)
|
||||
acousticSeg = acousticSeg + (parseInt(ev.target.value) <= acousticReminder ? 1 : 0)
|
||||
|
||||
$("#facialFrameLabel").html("Facial: " + String(parseInt(ev.target.value) !== 0 ? (facialSeg * (parseInt(ev.target.value) - 1) + 1) +
|
||||
" - " + (parseInt(ev.target.value) === 20 ? rawFacialData.length : facialSeg * (parseInt(ev.target.value))) : ""))
|
||||
|
||||
$("#movementFrameLabel").html("Movement: " + String(parseInt(ev.target.value) !== 0 ? (movementSeg * (parseInt(ev.target.value) - 1) + 1) +
|
||||
" - " + (parseInt(ev.target.value) === 20 ? rawMovementData.length : movementSeg * (parseInt(ev.target.value))) : ""))
|
||||
|
||||
$("#acousticFrameLabel").html("Acoustic: " + String(parseInt(ev.target.value) !== 0 ? (acousticSeg * (parseInt(ev.target.value) - 1) + 1) +
|
||||
" - " + (parseInt(ev.target.value) === 20 ? rawAcousticData.length : acousticSeg * (parseInt(ev.target.value))) : ""))
|
||||
}
|
||||
|
||||
const handleUpdate = param => {
|
||||
if (param === "facial") {
|
||||
var facialArg = allFacialAttr.filter((el, index) => checkedFacialState[index] === true)
|
||||
setFacialAttr(facialArg)
|
||||
}
|
||||
else if (param === "movement") {
|
||||
var movementArg = allMovementAttr.filter((el, index) => checkedMovementState[index] === true)
|
||||
setMovementAttr(movementArg)
|
||||
}
|
||||
else {
|
||||
var acousticArg = allAcousticAttr.filter((el, index) => checkedAcousticState[index] === true)
|
||||
setAcousticAttr(acousticArg)
|
||||
}
|
||||
}
|
||||
|
||||
const handleFacialCheckboxChange = position => {
|
||||
var updatedCheckedState = checkedFacialState.map((item, index) =>
|
||||
index === position ? !item : item)
|
||||
setCheckedFacialState(updatedCheckedState)
|
||||
}
|
||||
|
||||
const handleAcousticCheckboxChange = position => {
|
||||
var updatedCheckedState = checkedAcousticState.map((item, index) =>
|
||||
index === position ? !item : item)
|
||||
setCheckedAcousticState(updatedCheckedState)
|
||||
}
|
||||
|
||||
const handleMovementCheckboxChange = position => {
|
||||
|
||||
var updatedCheckedState = checkedMovementState.map((item, index) =>
|
||||
index === position ? !item : item)
|
||||
setCheckedMovementState(updatedCheckedState)
|
||||
}
|
||||
|
||||
|
||||
const handleCorrMatrixCheckboxChange = position => {
|
||||
const updatedCheckedState = checkedCorrMatrixState.map((item, index) =>
|
||||
index === position ? !item : item)
|
||||
setCheckedCorrMatrixState(updatedCheckedState)
|
||||
}
|
||||
|
||||
const handleCorrMatrixUpdate = id => {
|
||||
var corrMatrix_args = allCorrMatrixAttr.filter((el, index) => checkedCorrMatrixState[index] === true)
|
||||
if (corrMatrix_args.length === 0) {
|
||||
corrMatrix_args = meanEmotionsSoft.map(e => e.replace("_mean", ""))
|
||||
}
|
||||
var sendId = typeof id === 'string' ? id : selectedId
|
||||
fetch("/updateCorrMatrix", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-type': "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"corrMatrix_args": corrMatrix_args,
|
||||
"individual": true,
|
||||
"id": sendId
|
||||
})
|
||||
}).then(
|
||||
res => res.json()
|
||||
).then(
|
||||
data => {
|
||||
setCorrMatrixData(data)
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
const handleIdCheckboxChange = ev => {
|
||||
$(".individual_checkbox").prop("checked", false)
|
||||
$("#" + ev.target.id).prop("checked", true)
|
||||
var id = ev.target.id.replace("_id_checkbox", "")
|
||||
setSelectedId(id)
|
||||
if (corrButton) {
|
||||
handleCorrMatrixUpdate(id)
|
||||
}
|
||||
setTimeslotValue("0")
|
||||
fetchIndividualData(id)
|
||||
}
|
||||
|
||||
|
||||
const handleEmotionCheckboxChange = position => {
|
||||
const updatededEmotions = checkedEmotions.map((item, index) =>
|
||||
index === position ? !item : false)
|
||||
setCheckedEmotions(updatededEmotions)
|
||||
$(".emotion_highlight").css("opacity", "0")
|
||||
if ($('#' + emotions[position] + "_highlight").is(":checked")) {
|
||||
$("." + emotions[position] + "_highlight").css("opacity", "1")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const handleSpeechPanel = panel => {
|
||||
if (panel === "speech") {
|
||||
setSpeechButton(true)
|
||||
setCorrButton(false)
|
||||
}
|
||||
else {
|
||||
setSpeechButton(false)
|
||||
setCorrButton(true)
|
||||
handleCorrMatrixUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
const handleUnselectCheckboxes = param => {
|
||||
var updatedCheckedState = null
|
||||
if (param === "facial") {
|
||||
updatedCheckedState = checkedFacialState.map(e => false)
|
||||
setCheckedFacialState(updatedCheckedState)
|
||||
}
|
||||
else if (param === "movement") {
|
||||
updatedCheckedState = checkedMovementState.map(e => false)
|
||||
setCheckedMovementState(updatedCheckedState)
|
||||
}
|
||||
else if (param === "acoustics"){
|
||||
updatedCheckedState = checkedAcousticState.map(e => false)
|
||||
setCheckedAcousticState(updatedCheckedState)
|
||||
}
|
||||
else {
|
||||
updatedCheckedState = checkedCorrMatrixState.map(e => false)
|
||||
setCheckedCorrMatrixState(updatedCheckedState)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{rawFacialData && rawFacialData.length > 0 &&
|
||||
<div id="timelineContainer">
|
||||
<div className="sliderContainer">
|
||||
<div>Timeline</div>
|
||||
<div>
|
||||
<input type="range" min='0' max="20" id="timelineRange" className="slider"
|
||||
step="1" value={timeslotValue} onChange={setTime} list="steplist"></input>
|
||||
<datalist id="steplist">
|
||||
{timepoints.map((e, i) =>
|
||||
<option value={e} key={e + "timepoint"}>{i === 0 ? "*" : e}</option>
|
||||
)}
|
||||
</datalist>
|
||||
</div>
|
||||
</div>
|
||||
<div id="timeFramesContainer">
|
||||
<div id="frameLabel">Frames:</div>
|
||||
<h6 id="facialFrameLabel">Facial</h6>
|
||||
<h6 id="movementFrameLabel">Movement</h6>
|
||||
<h6 id="acousticFrameLabel">Acoustics</h6>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<Row id="componentsContainer">
|
||||
{derivedData &&
|
||||
<Col id="headAndIdPanelContainer" className="col-2">
|
||||
<Row id='headContainer'>
|
||||
<div id="faceMaskButtonContainer">
|
||||
<button type="button" id="asymMaskButton"
|
||||
className={`btn btn-sm ${asymetryButton === true ? 'btn-primary' : 'btn-secondary'}`}
|
||||
onClick={() => handleFaceMask("asym")}>Asym</button>
|
||||
<button type="button" id="painMaskButton"
|
||||
className={`btn btn-sm ${painButton === true ? 'btn-primary' : 'btn-secondary'}`}
|
||||
onClick={() => handleFaceMask("pain")}>Pain</button>
|
||||
<button type="button" id="exprMaskButton"
|
||||
className={`btn btn-sm ${expressivityButton === true ? 'btn-primary' : 'btn-secondary'}`}
|
||||
onClick={() => handleFaceMask("expr")}>Expr</button>
|
||||
<button type="button" id="AUsMaskButton"
|
||||
className={`btn btn-sm ${AUsButton === true ? 'btn-primary' : 'btn-secondary'}`}
|
||||
onClick={() => handleFaceMask("aus")}>AUs</button>
|
||||
<button type="button" id="movMaskButton"
|
||||
className={`btn btn-sm ${movementButton === true ? 'btn-primary' : 'btn-secondary'}`}
|
||||
onClick={handleMovementMask}>Mov</button>
|
||||
</div>
|
||||
<div id="headComponentContainer">
|
||||
< HeadSVG width={200} height={300} />
|
||||
</div>
|
||||
</Row>
|
||||
<Row id="colorLegendAndIdPanelContainer">
|
||||
<div id="AUButtonContainer">
|
||||
{AUsButton && emotions.map((value, index) =>
|
||||
<label className="id_checkbox_container" key={value + "AU"}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={value + "_highlight"}
|
||||
className="emotion_checkbox"
|
||||
checked={checkedEmotions[index]}
|
||||
onChange={() => handleEmotionCheckboxChange(index)}
|
||||
/>
|
||||
{value}
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
<div id="colorScaleContainer" style={{ opacity: ((painButton | expressivityButton | AUsButton | asymetryButton) ? "1" : "0") }}>
|
||||
<ColorLegend range={facialMaskVals} colorScale={["white", facialMaskColor]}
|
||||
derivedData={derivedData} id={"faceMaskColorLegend"} timelineData={facialTimelineData} timeframe={timeslotValue} />
|
||||
</div>
|
||||
<Col id="idPanelContainerIndividual" className="col-2">
|
||||
<button type="button" className='btn btn-primary btn-sm'>Filter ID(s)</button>
|
||||
<div >
|
||||
{idData.map((value, index) =>
|
||||
<label className="id_checkbox_container" id={value + "_id_"} key={value + "idIndividual"}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={value + "_id_checkbox"}
|
||||
className="individual_checkbox"
|
||||
onChange={handleIdCheckboxChange}
|
||||
/>
|
||||
{" " + value}
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
}
|
||||
<Col id="facialAndSpeechContainer">
|
||||
{rawFacialData && rawFacialData.length > 0 &&
|
||||
<Row id="facialActivityContainer">
|
||||
<div style={{ display: "flex", }}>
|
||||
<Col id="facialContainer" className='col-2'>
|
||||
{allFacialAttr &&
|
||||
<div id="facialQueryPanelContainer" >
|
||||
<div id="facialQueryPanelButtonsContainer">
|
||||
<button type="button" className='btn btn-sm btn-outline-primary' onClick={() => handleUpdate("facial")}>Update</button>
|
||||
<button type="button" className="btn-close" aria-label="Close"
|
||||
onClick={() => handleUnselectCheckboxes("facial")}></button>
|
||||
</div>
|
||||
<QueryPanel allAcousticArg={[]} allMovementArg={[]} allSpeechArg={[]} allFacialArg={allFacialAttr}
|
||||
checkedState={checkedFacialState} handleCheckboxChange={handleFacialCheckboxChange} idVal={"rawFacialIndividualAttr_"} />
|
||||
</div>}
|
||||
</Col>
|
||||
<Col id="facialHistogramsContainer" className='col-2'>
|
||||
{facialAttr.map((value) =>
|
||||
<div className="histogramContainer" key={value + "facHistogram"}>
|
||||
{<Histogram data={rawFacialData} derivedData={derivedData} attr={value} id={"histogram_" + value}
|
||||
color={["#41ab5d", "#c7e9c0"]} timeframe={timeslotValue} />}
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</div>
|
||||
</Row>
|
||||
}
|
||||
<Row id="facialSpeechAndCorrPanelContainer">
|
||||
<div id="facialSpeechAnCorrPanelButtons">
|
||||
<button type="button" className={`btn btn-sm ${speechButton === true ? 'btn-primary' : 'btn-secondary'}`}
|
||||
onClick={() => handleSpeechPanel("speech")} id="speechPanelButton" >{'Facial & Speech'}</button>
|
||||
<button type="button" className={`btn btn-sm ${corrButton === true ? 'btn-primary' : 'btn-secondary'}`}
|
||||
onClick={() => handleSpeechPanel("corr")} id="corrMatrixPanelButton">Correlation</button>
|
||||
</div>
|
||||
{speechButton &&
|
||||
<div id="facialAndSpeechPanelContainer">
|
||||
<Col id="SpiderChartContainer" className='col-8'>
|
||||
<div className="spiderComponent" >
|
||||
<SpiderChart label={'Emotion Intensity'} derivedData={derivedData}
|
||||
timelineData={facialTimelineData} timeframe={timeslotValue} levels={[0, 0.25, 0.5, 0.75, 1]}
|
||||
axes={meanEmotionsSoft.reverse()} />
|
||||
</div>
|
||||
<div className="spiderComponent" >
|
||||
<SpiderChart className="spiderChart" label={'AU Intensity'} derivedData={derivedData}
|
||||
timelineData={facialTimelineData} timeframe={timeslotValue} levels={[0, 1, 2, 3, 4, 5]}
|
||||
axes={meanAUsSoft.reverse()} />
|
||||
</div>
|
||||
</Col>
|
||||
<Col className='col-4'>
|
||||
<h6 id="speechLabel">Speech</h6>
|
||||
{speechDerivedData && speechDerivedData.map((value, index) =>
|
||||
<div id="speechComponent" key={value + "speech"}>{DBMDict[value]['label'] + ": " + derivedData[value]}</div>
|
||||
)}
|
||||
</Col>
|
||||
</div>}
|
||||
{corrButton && ((rawFacialData && rawFacialData.length > 0) || (rawAcousticData && rawAcousticData.length > 0) || (rawMovementData && rawMovementData.length > 0)) &&
|
||||
<Row className="corrMatrixContainer">
|
||||
<Col id="corrQueryPanelContainer" className='col-2'>
|
||||
<div >
|
||||
<div id="corrQuerryButton">
|
||||
<button type="button" className='btn btn-sm btn-outline-primary' onClick={handleCorrMatrixUpdate}>Update</button>
|
||||
<button type="button" className="btn-close" aria-label="Close"
|
||||
onClick={() => handleUnselectCheckboxes("corr")}></button>
|
||||
</div>
|
||||
<QueryPanel allAcousticArg={allAcousticAttr} allMovementArg={allMovementAttr} allSpeechArg={[]} allFacialArg={allFacialAttr}
|
||||
checkedState={checkedCorrMatrixState} handleCheckboxChange={handleCorrMatrixCheckboxChange} idVal={"corrMatrixIndividual_"} />
|
||||
</div>
|
||||
</Col>
|
||||
<Col className='col-2' id="corrMatrixComponentIndividual" >
|
||||
<CorrelationMatrix data={corrMatrixData} parentId={"corrMatrixComponentIndividual"} />
|
||||
</Col>
|
||||
</Row>}
|
||||
</Row>
|
||||
</Col>
|
||||
<Col id="movementAndAcousticContainer">
|
||||
{rawMovementData && rawMovementData.length > 0 &&
|
||||
<Row id="headMovementContainer">
|
||||
<div style={{ display: "flex", }}>
|
||||
<Col className='col-2' id="movementContainer">
|
||||
{allMovementAttr &&
|
||||
<div>
|
||||
<div id="movementQueryButtonContainer">
|
||||
<button type="button" className='btn btn-sm btn-outline-primary' onClick={() => handleUpdate("movement")}>Update</button>
|
||||
<button type="button" className="btn-close" aria-label="Close" onClick={() => handleUnselectCheckboxes("movement")}></button>
|
||||
</div>
|
||||
<QueryPanel allAcousticArg={[]} allMovementArg={allMovementAttr} allSpeechArg={[]} allFacialArg={[]}
|
||||
checkedState={checkedMovementState} handleCheckboxChange={handleMovementCheckboxChange} idVal={"rawMovementIndividualAttr_"} />
|
||||
</div>
|
||||
}
|
||||
</Col>
|
||||
<Col id="movementHistogramContainer" className='col-2'>
|
||||
{movementAttr.map((value) =>
|
||||
<div className="histogramContainer" key={value + "movHistogram"}>
|
||||
{<Histogram data={rawMovementData} derivedData={derivedData} attr={value}
|
||||
id={"histogram_" + value} color={["#4292c6", "#c6dbef"]} timeframe={timeslotValue} />}
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</div>
|
||||
</Row>
|
||||
}
|
||||
{rawAcousticData && rawAcousticData.length > 0 &&
|
||||
<Row id="voiceAcousticsContainer">
|
||||
<div style={{ display: "flex" }}>
|
||||
<Col className='col-2' id="acousticsContainer">
|
||||
{allAcousticAttr &&
|
||||
<div >
|
||||
<div id="acousticsQueryButtonContainer">
|
||||
<button type="button" className='btn btn-sm btn-outline-primary' onClick={() => handleUpdate("acoustics")}>Update</button>
|
||||
<button type="button" className="btn-close" aria-label="Close" onClick={() => handleUnselectCheckboxes("acoustics")}></button>
|
||||
|
||||
</div>
|
||||
<QueryPanel allAcousticArg={allAcousticAttr} allMovementArg={[]} allSpeechArg={[]} allFacialArg={[]}
|
||||
checkedState={checkedAcousticState} handleCheckboxChange={handleAcousticCheckboxChange} idVal={"rawAcousticIndividualAttr_"} />
|
||||
</div>}
|
||||
</Col>
|
||||
<Col className='col-2' id="acousticsHistogramContainer">
|
||||
{acousticAttr.map((value) =>
|
||||
<div className="histogramContainer" key={value + "acoHistogram"}>
|
||||
{<Histogram data={rawAcousticData} derivedData={derivedData} attr={value}
|
||||
id={"histogram_" + value} color={["#807dba", "#dadaeb"]} timeframe={timeslotValue} />}
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</div>
|
||||
</Row>
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default IndividualPanel;
|
||||
72
visualization_interface/src/components/queryPanel.js
Normal file
72
visualization_interface/src/components/queryPanel.js
Normal file
@@ -0,0 +1,72 @@
|
||||
import DBMDict from "../DBM_attribute_dict.json"
|
||||
import "./cohortPanel.css"
|
||||
|
||||
const QueryPanel = ({ allAcousticArg, allMovementArg, allSpeechArg, allFacialArg, checkedState, handleCheckboxChange, idVal }) => {
|
||||
return (
|
||||
<div className="queryPanel">
|
||||
{allFacialArg.length > 0 && <h6>Facial</h6>}
|
||||
{allFacialArg.length > 0 && allFacialArg.map((value, index) =>
|
||||
<div key={idVal + value + "key"}><label className="">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={idVal + value}
|
||||
|
||||
checked={checkedState[index]}
|
||||
className={idVal}
|
||||
onChange={() => handleCheckboxChange(index)}
|
||||
/>
|
||||
{" " + DBMDict[value]['label']}
|
||||
</label></div>
|
||||
)
|
||||
}
|
||||
{allMovementArg.length > 0 && <h6>Movement</h6>}
|
||||
{allMovementArg.length > 0 && allMovementArg.map((value, index) =>
|
||||
<div key={idVal + value + "key"}><label className="">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={idVal + value}
|
||||
|
||||
checked={checkedState[index + allFacialArg.length]}
|
||||
className={idVal}
|
||||
onChange={() => handleCheckboxChange(index + allFacialArg.length)}
|
||||
/>
|
||||
{" " + DBMDict[value]['label']}
|
||||
</label></div>
|
||||
)
|
||||
}
|
||||
{allAcousticArg.length > 0 && <h6>Acoustics</h6>}
|
||||
{allAcousticArg.length > 0 && allAcousticArg.map((value, index) =>
|
||||
<div key={idVal + value + "key"}><label className="">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={idVal + value}
|
||||
|
||||
checked={checkedState[index + allFacialArg.length + allMovementArg.length]}
|
||||
className={idVal}
|
||||
onChange={() => handleCheckboxChange(index + allFacialArg.length + allMovementArg.length)}
|
||||
/>
|
||||
{" " + DBMDict[value]['label']}
|
||||
</label></div>
|
||||
)
|
||||
}
|
||||
{allSpeechArg.length > 0 && <h6>Speech</h6>}
|
||||
{allSpeechArg.map((value, index) =>
|
||||
<div key={idVal + value + "key"}><label className="">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={idVal + value}
|
||||
|
||||
checked={checkedState[index + allFacialArg.length + allAcousticArg.length + allMovementArg.length]}
|
||||
className={idVal}
|
||||
onChange={() => handleCheckboxChange(index + allFacialArg.length + allAcousticArg.length + allMovementArg.length)}
|
||||
/>
|
||||
{" " + DBMDict[value]['label']}
|
||||
</label></div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default QueryPanel;
|
||||
110
visualization_interface/src/components/scatterplot.d3.js
Normal file
110
visualization_interface/src/components/scatterplot.d3.js
Normal file
@@ -0,0 +1,110 @@
|
||||
import * as d3 from 'd3';
|
||||
import $ from "jquery";
|
||||
import "../index.css"
|
||||
|
||||
export default class ScatterplotD3 {
|
||||
constructor(chart, data, filteredIds, metadata, hideIds, metadataAttrColor) {
|
||||
|
||||
if (data) {
|
||||
if (!d3.select(chart).select('svg').empty()) {
|
||||
d3.select(chart).select('svg').remove()
|
||||
}
|
||||
var parentContainer = document.getElementById("scatterplotContainer")
|
||||
var width = parentContainer.clientWidth
|
||||
var height = parentContainer.clientHeight
|
||||
const svg = d3.select(chart)
|
||||
.append('svg')
|
||||
.attr('width', width)
|
||||
.attr('height', height)
|
||||
var pca1 = Object.values(data).map(el => el[0])
|
||||
var pca2 = Object.values(data).map(el => el[1])
|
||||
var keys = Object.keys(data)
|
||||
var scatterplotObj = keys.map((el, i) => {
|
||||
return { "id": el, "pca1": pca1[i], "pca2": pca2[i] }
|
||||
})
|
||||
|
||||
var metadataDict = {}
|
||||
if (metadata.length > 0) {
|
||||
$('#metadataAttributes').css("opacity", 1)
|
||||
var medataAttr = [...new Set(Object.values(metadata).map(el => el['attr']).filter(e => e != null))]
|
||||
var metadataColor = d3.scaleOrdinal().domain(medataAttr)
|
||||
.range(['gold', 'blue', 'pink', 'darkviolet', 'cyan', 'black', 'brown', 'greenyellow', 'orchid','mediumpurple'])
|
||||
var d = Object.values(metadata).map(el => [el['id'], el['attr']])
|
||||
|
||||
d.forEach(el => {
|
||||
metadataDict[el[0]] = el[1]
|
||||
})
|
||||
medataAttr.forEach(a => {
|
||||
$(`#${metadataColor(a)}AttrContainer`).css("opacity", 0.5)
|
||||
$(`#${metadataColor(a)}color`).text(a)
|
||||
})
|
||||
$('#noneAttrContainer').css("opacity", 0.5)
|
||||
}
|
||||
else {
|
||||
$('#metadataAttributes').css("opacity", 0)
|
||||
}
|
||||
|
||||
var xScale = d3.scaleLinear()
|
||||
.domain([Math.min(...pca1), Math.max(...pca1)]).range([10, width - 20])
|
||||
var yScale = d3.scaleLinear()
|
||||
.domain([Math.min(...pca2), Math.max(...pca2)]).range([height - 10, 10])
|
||||
|
||||
svg.append("g")
|
||||
.attr("transform", "translate(-10," + (height - 3) + ")")
|
||||
.attr("class", "greyAxis")
|
||||
.call(d3.axisBottom(xScale));
|
||||
svg.append("g")
|
||||
.attr("transform", "translate(" + 2 + "," + 5 + " )")
|
||||
.attr("class", "greyAxis")
|
||||
.call(d3.axisLeft(yScale));
|
||||
|
||||
svg.append('g')
|
||||
.selectAll("dot")
|
||||
.data(scatterplotObj)
|
||||
.enter()
|
||||
.append("circle")
|
||||
.attr("id", d => d['id'] + "_pca")
|
||||
.attr("cx", d => xScale(d['pca1']))
|
||||
.attr("cy", d => yScale(d['pca2']))
|
||||
.attr("class", d => "dot dotScatterplot dot_" + d['id'])
|
||||
.attr("r", 5.5)
|
||||
.style("fill", d => filteredIds.includes(d['id']) ? "darkorange" : (metadataDict[d['id']] ? metadataAttrColor[d['id']] : "#41ab5d"))
|
||||
.style("opacity", d => filteredIds.includes(d['id']) ? "0.4" : hideIds ? "0" : "0.4")
|
||||
.attr("stroke", "grey")
|
||||
.attr("stroke-width", '1.5px')
|
||||
|
||||
|
||||
svg.append("g")
|
||||
.call(d3.brush()
|
||||
.extent([[0, 0], [width, height]])
|
||||
.on("start brush end", e => brushed(e)));
|
||||
|
||||
function brushed(e) {
|
||||
let value = [];
|
||||
if (e.selection) {
|
||||
const [[x0, y0], [x1, y1]] = e.selection;
|
||||
// default color for scatterplot points
|
||||
d3.selectAll(".dotScatterplot").style("fill", d => filteredIds.includes(d['id']) ? "darkorange" : (metadataDict[d['id']] ? metadataAttrColor[d['id']] : "#41ab5d"))
|
||||
// default color for distribution chart points
|
||||
d3.selectAll(".dotDistr").style("fill", d => filteredIds.includes(d[0]) ? "darkorange" : (metadataDict[d[0]] ? metadataAttrColor[d[0]] : "#41ab5d"))
|
||||
value = scatterplotObj.filter(d => (x0 <= xScale(d['pca1']) && xScale(d['pca1']) < x1 && y0 <= yScale(d['pca2']) && yScale(d['pca2']) < y1))
|
||||
.map(e => e.id)
|
||||
$(".id_checkbox_container").css("color", "black")
|
||||
value.forEach(e => {
|
||||
d3.selectAll(".dot_" + e).style("fill", "darkorange")
|
||||
$("#" + e + "_id_container").css("color", "#fc6a03")
|
||||
})
|
||||
}
|
||||
keys.forEach(k => {
|
||||
if ($('#' + k + "_id").is(":checked"))
|
||||
$(".dot_" + k).css("fill", "darkorange")
|
||||
})
|
||||
if ($('#metadataButton').hasClass("btn-outline-primary")) {
|
||||
}
|
||||
svg.property("value", value).dispatch("input");
|
||||
}
|
||||
d3.selectAll(".dotDistr").style("fill", d => filteredIds.includes(d[0]) ? "darkorange" : (metadataDict[d[0]] ? metadataAttrColor[d[0]] : "#41ab5d"))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
36
visualization_interface/src/components/scatterplot.js
Normal file
36
visualization_interface/src/components/scatterplot.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import { ComponentToPrint } from './componentToPrint';
|
||||
import { exportComponentAsJPEG } from 'react-component-export-image';
|
||||
import ScatterplotD3 from './scatterplot.d3';
|
||||
import "./cohortPanel.css"
|
||||
|
||||
const Scatterplot = ({ data, filteredIds, metadata, hideIds, metadataAttrColor }) => {
|
||||
const ref = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
const currElement = ref.current
|
||||
if (data) {
|
||||
new ScatterplotD3(currElement, data, filteredIds, metadata, hideIds, metadataAttrColor)
|
||||
}
|
||||
}, [data])
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div id="scatterplotComponent">
|
||||
<h6>PCA</h6>
|
||||
<button type="button" className='btn btn-outline-secondary btn-sm printButton' onClick={() => exportComponentAsJPEG(ref)} >
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-printer" viewBox="0 0 16 16">
|
||||
<path d="M2.5 8a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1z" />
|
||||
<path d="M5 1a2 2 0 0 0-2 2v2H2a2 2 0 0 0-2 2v3a2 2 0 0 0 2 2h1v1a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2v-1h1a2 2
|
||||
0 0 0 2-2V7a2 2 0 0 0-2-2h-1V3a2 2 0 0 0-2-2H5zM4 3a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2H4V3zm1 5a2 2 0 0 0-2
|
||||
2v1H2a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1h-1v-1a2 2 0 0 0-2-2H5zm7 2v3a1 1 0 0
|
||||
1-1 1H5a1 1 0 0 1-1-1v-3a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<ComponentToPrint ref={ref} />
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default Scatterplot;
|
||||
120
visualization_interface/src/components/spiderChart.d3.js
Normal file
120
visualization_interface/src/components/spiderChart.d3.js
Normal file
@@ -0,0 +1,120 @@
|
||||
import * as d3 from 'd3';
|
||||
import "../index.css"
|
||||
import DBMDict from "../DBM_attribute_dict.json"
|
||||
|
||||
export default class SpiderChartD3 {
|
||||
constructor(chart, derivedData, timelineData, timeframe, levels, axes) {
|
||||
if (!d3.select(chart).select('svg').empty()) {
|
||||
d3.select(chart).select('svg').remove()
|
||||
}
|
||||
|
||||
var width = 140
|
||||
var height = 140
|
||||
const radius = Math.min((width - 5) / 2, (height - 5) / 2)
|
||||
const totalAxes = axes.length
|
||||
const radians = 2 * Math.PI
|
||||
var vertices = []
|
||||
var values = null
|
||||
|
||||
const svg = d3.select(chart)
|
||||
.append('svg')
|
||||
.attr('width', width)
|
||||
.attr('height', height)
|
||||
const g = svg.append("g")
|
||||
.attr("transform", "translate(0,5)")
|
||||
|
||||
if (Object.keys(derivedData).length === 0) {
|
||||
return
|
||||
}
|
||||
if (parseInt(timeframe) === 0) {
|
||||
values = Object.keys(derivedData)
|
||||
.reduce((obj, key) => {
|
||||
if (axes.includes(key)) {
|
||||
obj[key] = derivedData[key]
|
||||
}
|
||||
return obj
|
||||
}, {})
|
||||
}
|
||||
else {
|
||||
axes = axes.map(a => a.replace("_mean", ""))
|
||||
values = Object.keys(timelineData)
|
||||
.reduce((obj, key) => {
|
||||
if (axes.includes(key)) {
|
||||
obj[key] = timelineData[key][parseInt(timeframe) - 1]
|
||||
}
|
||||
return obj
|
||||
}, {})
|
||||
}
|
||||
|
||||
for (var level = 0; level < levels.length - 1; level++) {
|
||||
var levelFactor = radius * ((level + 1) / levels.length);
|
||||
for (var i = 0; i < axes.length; i++) {
|
||||
g.append("line").attr("class", "level_line")
|
||||
.attr("x1", levelFactor * (1 - Math.sin(i * radians / totalAxes)))
|
||||
.attr("y1", levelFactor * (1 - Math.cos(i * radians / totalAxes)))
|
||||
.attr("x2", levelFactor * (1 - Math.sin((i + 1) * radians / totalAxes)))
|
||||
.attr("y2", levelFactor * (1 - Math.cos((i + 1) * radians / totalAxes)))
|
||||
.attr("transform", "translate(" + ((width - 2) / 2 - levelFactor) + ", " + ((height - 2) / 2 - levelFactor) + ")")
|
||||
.attr("stroke", "#dedede")
|
||||
.attr("stroke-width", "0.5px");
|
||||
if (i === 1) {
|
||||
g
|
||||
.append("svg:text").classed("level-labels", true)
|
||||
.text(levels[level + 1])
|
||||
.attr("x", levelFactor * (1 - Math.sin(0)))
|
||||
.attr("y", levelFactor * (1 - Math.cos(0)))
|
||||
.attr("transform", "translate(" + ((width - 2) / 2 - levelFactor + 2) + ", " + ((height - 2) / 2 - levelFactor) + ")")
|
||||
.attr("fill", "grey")
|
||||
.attr("font-size", "0.6rem")
|
||||
}
|
||||
}
|
||||
}
|
||||
axes.forEach((a, i) => {
|
||||
g.append("line").classed("axis-lines", true)
|
||||
.attr("x1", (width - 2) / 2)
|
||||
.attr("y1", (height - 2) / 2)
|
||||
.attr("x2", (width - 2) / 2 * (1 - Math.sin(i * radians / totalAxes)))
|
||||
.attr("y2", (height - 2) / 2 * (1 - Math.cos(i * radians / totalAxes)))
|
||||
.attr("stroke", "#dedede")
|
||||
.attr("stroke-width", "1px");
|
||||
|
||||
g
|
||||
.append("text")
|
||||
.text(DBMDict[a]['label'].replace("intsoft", "").replace("_mean", "").replace("int", ""))
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("x", () => {
|
||||
var rez = width / 2 * (1 - Math.sin(i * radians / totalAxes))
|
||||
if (rez < width / 2.2) return rez + 10
|
||||
else return rez - 10
|
||||
})
|
||||
.attr("y", () => {
|
||||
var rez = height / 2 * (1 - Math.cos(i * radians / totalAxes))
|
||||
if (rez < height / 2.2) return rez + 5
|
||||
else return rez - 5
|
||||
})
|
||||
|
||||
|
||||
.attr("font-size", "0.6rem");
|
||||
})
|
||||
|
||||
axes.forEach((a, i) => {
|
||||
var x = (width - 2) / 2 * (1 - (parseFloat(Math.max(values[a], 0)) / Math.max(...levels)) * Math.sin(i * radians / totalAxes))
|
||||
var y = (height - 2) / 2 * (1 - (parseFloat(Math.max(values[a], 0)) / Math.max(...levels)) * Math.cos(i * radians / totalAxes))
|
||||
g.append("circle")
|
||||
.attr("r", 1)
|
||||
.attr("cx", x)
|
||||
.attr("cy", y)
|
||||
.attr("fill", "green")
|
||||
vertices.push([x, y])
|
||||
});
|
||||
|
||||
g.append("polygon")
|
||||
.attr("points", vertices)
|
||||
.attr("stroke-width", "2px")
|
||||
.attr("stroke", "green")
|
||||
.attr("fill", "green")
|
||||
.attr("fill-opacity", 0.5)
|
||||
.attr("stroke-opacity", 0.3)
|
||||
|
||||
}
|
||||
}
|
||||
36
visualization_interface/src/components/spiderChart.js
Normal file
36
visualization_interface/src/components/spiderChart.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import { ComponentToPrint } from './componentToPrint';
|
||||
import { exportComponentAsJPEG } from 'react-component-export-image';
|
||||
import SpiderChartD3 from './spiderChart.d3';
|
||||
import "./individualPanel.css"
|
||||
|
||||
const SpiderChart = ({ derivedData, timelineData, timeframe, levels, axes, label }) => {
|
||||
const ref = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
const currElement = ref.current
|
||||
if (derivedData) {
|
||||
new SpiderChartD3(currElement, derivedData, timelineData, timeframe, levels, axes)
|
||||
}
|
||||
}, [derivedData, timelineData, timeframe])
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="spiderChartComponent">
|
||||
<div className="spiderChartLabel">{label}</div>
|
||||
<button type="button" className='btn btn-outline-secondary btn-sm printButton' onClick={() => exportComponentAsJPEG(ref)}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-printer" viewBox="0 0 16 16">
|
||||
<path d="M2.5 8a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1z" />
|
||||
<path d="M5 1a2 2 0 0 0-2 2v2H2a2 2 0 0 0-2 2v3a2 2 0 0 0 2 2h1v1a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2v-1h1a2 2
|
||||
0 0 0 2-2V7a2 2 0 0 0-2-2h-1V3a2 2 0 0 0-2-2H5zM4 3a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2H4V3zm1 5a2 2 0 0 0-2
|
||||
2v1H2a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1h-1v-1a2 2 0 0 0-2-2H5zm7 2v3a1 1 0 0
|
||||
1-1 1H5a1 1 0 0 1-1-1v-3a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<ComponentToPrint ref={ref} />
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default SpiderChart;
|
||||
23
visualization_interface/src/index.css
Normal file
23
visualization_interface/src/index.css
Normal file
@@ -0,0 +1,23 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.greyAxis line{
|
||||
stroke: #bababa;
|
||||
}
|
||||
|
||||
.greyAxis path{
|
||||
stroke: #bababa;
|
||||
}
|
||||
17
visualization_interface/src/index.js
Normal file
17
visualization_interface/src/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
||||
1
visualization_interface/src/logo.svg
Normal file
1
visualization_interface/src/logo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
13
visualization_interface/src/reportWebVitals.js
Normal file
13
visualization_interface/src/reportWebVitals.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const reportWebVitals = onPerfEntry => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
||||
7
visualization_interface/src/setupTests.js
Normal file
7
visualization_interface/src/setupTests.js
Normal file
@@ -0,0 +1,7 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user