first commit
This commit is contained in:
388
dashboard/src/components/cohortPanel.js
Normal file
388
dashboard/src/components/cohortPanel.js
Normal file
@@ -0,0 +1,388 @@
|
||||
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';
|
||||
|
||||
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 [corrMatrixArgs, setCorrMatrixArgs] = 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', 'brown', 'purple', 'orange']
|
||||
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 => {
|
||||
setMetadata(data)
|
||||
var attr = [...new Set(Object.values(data).map(el => el['attr']).filter(e => e != null))]
|
||||
if (attr.length <= metadataColorList.length - 1) {
|
||||
var metaColorData = {}
|
||||
var attrDict = {}
|
||||
attr.forEach((e, i) => {
|
||||
attrDict[e] = metadataColorList[i + 1]
|
||||
})
|
||||
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)
|
||||
setCorrMatrixArgs(corrMatrix_args)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
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", "red") :
|
||||
$(".dot_" + ev.target.id.replace("_id", "")).css("fill", (Object.keys(metadataAttrColor).includes(ev.target.id.replace("_id", "")) & metadataButton) ? metadataAttrColor[ev.target.id.replace("_id", "")] : "#69b3a2")
|
||||
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" style={{ height: "5%", position: "absolute", top: "10px", left: "250px", display: "flex", flexDirection: "row" }}>
|
||||
<div>
|
||||
<button type="button" className={`btn btn-sm ${metadataButton === true ? 'btn-outline-primary' : 'btn-outline-secondary'}`}
|
||||
id="metadataButton" onClick={handleMetadata}>Metadata</button>
|
||||
</div>
|
||||
<div style={{ display: "flex", flexDirection: "row", justifyContent: 'flex-end', margin: "auto", marginLeft: "20px", opacity: "0" }} id="metadataAttributes">
|
||||
<div style={{ marginLeft: "5px", fontWeight: "bolder" }}>Attributes:</div>
|
||||
{metadataColorList.map((value, index) =>
|
||||
<div style={{ display: "flex", flexDirection: "row", opacity: "0", marginLeft: "20px" }} id={value + "AttrContainer"} className="metadataAttr">
|
||||
|
||||
<svg height="14" width="14" transform="translate(3,3)" id="1circle">
|
||||
<circle cx="7" cy="7" r="50%" fill={value === "none" ? "#69b3a2" : value} />
|
||||
</svg>
|
||||
<div style={{ marginLeft: "10px", fontWeight: "bolder", color: (value === "none" ? "#69b3a2" : value) }}
|
||||
id={(value !== "none" ? value : "none") + "color"}>{value === "none" ? "None" : ""}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
{allDBMArg.length > 0 &&
|
||||
<Row style={{ height: "90vh", width: "95vw", marginLeft: "10px" }}>
|
||||
<Col className="col-2" style={{ borderRadius: "15px", paddingTop: "10px", width: "max-content", height: "98%", boxShadow: "rgba(0, 0, 0, 0.16) 0px 1px 4px" }}>
|
||||
<div style={{ display: "inline-flex", flexDirection: "row", gap: "3px", marginBottom: "10px" }}>
|
||||
<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 style={{ fontSize: "0.8rem", height: "90%" }}>
|
||||
{pcaButton &&
|
||||
<div style={{ height: "95%" }}>
|
||||
<div style={{ display: "inline-flex", flexDirection: "row" }}>
|
||||
<button type="button" className='btn btn-sm btn-outline-primary' onClick={handlePCAUpdate}>Update PCA</button>
|
||||
</div>
|
||||
<div style={{ overflowY: "scroll", height: "97%" }}>
|
||||
<QueryPanel allAcousticArg={allAcousticArg} allMovementArg={allMovementArg} allSpeechArg={allSpeechArg} allFacialArg={allFacialArg}
|
||||
checkedState={checkedPCAState} handleCheckboxChange={handlePCACheckboxChange} idVal={"pcaInputArg_"} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{distrButton &&
|
||||
<div style={{ height: "95%" }}>
|
||||
<div style={{ display: "inline-flex", flexDirection: "row" }}>
|
||||
<button type="button" className='btn btn-sm btn-outline-primary' onClick={handleDistrUpdate}>Update Distributions</button>
|
||||
</div>
|
||||
<div style={{ overflowY: "scroll", height: "97%" }}>
|
||||
<QueryPanel allAcousticArg={allAcousticArg} allMovementArg={allMovementArg} allSpeechArg={allSpeechArg} allFacialArg={allFacialArg}
|
||||
checkedState={checkedDistrState} handleCheckboxChange={handleDistrCheckboxChange} idVal={"distributionInputArg_"} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{corrMatrixButton &&
|
||||
<div style={{ height: "95%" }}>
|
||||
<div style={{ display: "inline-flex", flexDirection: "row" }}>
|
||||
<button type="button" className='btn btn-sm btn-outline-primary' onClick={handleCorrMatrixUpdate}>Update Correlations</button>
|
||||
</div>
|
||||
<div style={{ overflowY: "scroll", height: "97%" }}>
|
||||
<QueryPanel allAcousticArg={allAcousticArg} allMovementArg={allMovementArg} allSpeechArg={allSpeechArg} allFacialArg={allFacialArg}
|
||||
checkedState={checkedCorrMatrixState} handleCheckboxChange={handleCorrMatrixCheckboxChange} idVal={"corrmatrixInputArg_"} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</Col>
|
||||
<Col className="col-4" style={{ height: "95%", width: "35%" }}>
|
||||
<Row id="scatterplotContainer" style={{ height: "35vh" }}>
|
||||
{pcaCoords && <Scatterplot data={pcaCoords} filteredIds={filteredIdData}
|
||||
metadata={metadataButton === true ? metadata : []} hideIds={checkedHideIdsState} metadataAttrColor={metadataAttrColor} />}
|
||||
</Row>
|
||||
<Row id="corrMatrixContainer" style={{ height: "50%", marginTop: "10%", fontSize: "0.8rem" }}>
|
||||
<CorrelationMatrix data={corrMatrixData} />
|
||||
</Row>
|
||||
</Col>
|
||||
<Col className="col-2" style={{
|
||||
display: "flex", flexDirection: "column", borderRadius: "15px", height:
|
||||
"98%", paddingTop: "10px", fontSize: "0.7rem", width: "max-content", boxShadow: "rgba(0, 0, 0, 0.16) 0px 1px 4px"
|
||||
}}>
|
||||
<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`} style={{ marginTop: "10px" }} onClick={handleHideIds}>Hide Unselected</button>
|
||||
<div style={{ display: "inline-flex", flexDirection: "column", marginTop: "10px", height: "94%", overflowY: "auto", }}>
|
||||
{idData.map((value, index) =>
|
||||
<label className="id_checkbox_container" id={value + "_id_container"}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={value + "_id"}
|
||||
onChange={handleIdCheckboxChange}
|
||||
/>
|
||||
{" " + value}
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
</Col>
|
||||
<Col id="distributionChartContainer" style={{ height: "93%", overflowY: "auto" }}>
|
||||
<h6 style={{ fontWeight: "bolder" }}>Attribute Distribution</h6>
|
||||
{distrArgs.map((value) =>
|
||||
<div style={{ display: "inline-flex", flexDirection: "row", marginLeft: "10px" }}>
|
||||
{<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
dashboard/src/components/colorLegend.d3.js
Normal file
114
dashboard/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, 10]).range(["white", 'pink'])
|
||||
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([-1, 0]).range(['#cb181d', "#fff5f0"])
|
||||
const posMovementColorScale = d3.scaleLinear().domain([0, 1]).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
dashboard/src/components/colorLegend.js
Normal file
19
dashboard/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;
|
||||
7
dashboard/src/components/componentToPrint.js
Normal file
7
dashboard/src/components/componentToPrint.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
export const ComponentToPrint = React.forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref}></div>
|
||||
);
|
||||
});
|
||||
115
dashboard/src/components/correlationMatrix.d3.js
Normal file
115
dashboard/src/components/correlationMatrix.d3.js
Normal file
@@ -0,0 +1,115 @@
|
||||
import * as d3 from 'd3';
|
||||
import $ from "jquery";
|
||||
import "../index.css"
|
||||
|
||||
export default class CorrelationMatrixD3 {
|
||||
constructor(chart, data) {
|
||||
if (!d3.select(chart).select('svg').empty()) {
|
||||
d3.select(chart).select('svg').remove()
|
||||
}
|
||||
var parentContainer = document.getElementById("corrMatrixContainer")
|
||||
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 => e.replace("aco_", "").replace("fac_", "").replace("mov_", ""))
|
||||
|
||||
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(["#fdae61", "#f7f7f7", "#67a9cf"])
|
||||
|
||||
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", "#fdae61")
|
||||
|
||||
linearGradient.append("stop")
|
||||
.attr("offset", "50%")
|
||||
.attr("stop-color", "#f7f7f7")
|
||||
|
||||
linearGradient.append("stop")
|
||||
.attr("offset", "100%")
|
||||
.attr("stop-color", "#67a9cf")
|
||||
|
||||
|
||||
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)
|
||||
|
||||
}
|
||||
}
|
||||
35
dashboard/src/components/correlationMatrix.js
Normal file
35
dashboard/src/components/correlationMatrix.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 CorrelationMatrixD3 from './correlationMatrix.d3';
|
||||
|
||||
const CorrelationMatrix = ({ data }) => {
|
||||
const ref = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
const currElement = ref.current
|
||||
if (data) {
|
||||
new CorrelationMatrixD3(currElement, data)
|
||||
}
|
||||
}, [data])
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<h6 style={{ fontWeight: "bolder" }}>Correlation Matrix</h6>
|
||||
<button type="button" className='btn btn-outline-secondary btn-sm' onClick={() => exportComponentAsJPEG(ref)} style={{ width: "max-content", }}>
|
||||
<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;
|
||||
128
dashboard/src/components/distributionChart.d3.js
Normal file
128
dashboard/src/components/distributionChart.d3.js
Normal file
@@ -0,0 +1,128 @@
|
||||
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 metadataAttr = [...new Set(Object.values(metadata).map(el => el['attr']).filter(e => e != null))]
|
||||
var metadataColor = d3.scaleOrdinal().domain(metadataAttr)
|
||||
.range(["gold", "blue"])
|
||||
var d = Object.values(metadata).map(el => [el['id'], el['attr']])
|
||||
d.forEach(el => {
|
||||
metadataDict[el[0]] = el[1]
|
||||
})
|
||||
}
|
||||
|
||||
var yScale = d3.scaleLinear()
|
||||
.domain([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]) ? "red" : (metadataDict[d[0]] ? metadataAttrColor[d[0]] : "#69b3a2"))
|
||||
.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", "red")
|
||||
$("#" + d.target.id.replace("_distr_" + attr, "") + "_id_container").css("color", "#fc6a03")
|
||||
keys.forEach(k => {
|
||||
if ($('#' + k + "_id").is(":checked"))
|
||||
$(".dot_" + k).css("fill", "red")
|
||||
})
|
||||
})
|
||||
.on("mouseout", d => {
|
||||
keys.forEach(k => {
|
||||
$(".dot_" + k).css("fill", filteredIds.includes(k) ? "red" : (metadataDict[k] ? metadataAttrColor[k] : "#69b3a2"))
|
||||
if ($('#' + k + "_id").is(":checked")) {
|
||||
$(".dot_" + k).css("fill", "red")
|
||||
}
|
||||
})
|
||||
$(".id_checkbox_container").css("color", "black")
|
||||
})
|
||||
.append("title")
|
||||
.text(d => "ID: " + String(d[0]) + "\n" + "Value: " + d[1])
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
35
dashboard/src/components/distributionChart.js
Normal file
35
dashboard/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' onClick={() => exportComponentAsJPEG(ref)} style={{ width: "max-content", }}>
|
||||
<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;
|
||||
329
dashboard/src/components/headSVG.js
Normal file
329
dashboard/src/components/headSVG.js
Normal file
@@ -0,0 +1,329 @@
|
||||
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="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(35, 65)" 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(118, 68)" 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(68, 68)" 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(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 AU01" viewBox="0 0 16 16" transform="translate(150, 68)" 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(37, 67)" 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 AU02" viewBox="0 0 16 16" transform="translate(120, 70)" 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(70, 70)" 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 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">23</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" >23</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
dashboard/src/components/histogram.d3.js
Normal file
87
dashboard/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 / 3
|
||||
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([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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
45
dashboard/src/components/histogram.js
Normal file
45
dashboard/src/components/histogram.js
Normal file
@@ -0,0 +1,45 @@
|
||||
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'
|
||||
|
||||
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 style={{ display: "flex", flexDirection: "row", }}>
|
||||
|
||||
{derivedData[attr + "_mean"] ? <div style={{ fontSize: "0.7rem", marginTop: "3px", marginRight: "10px" }}>{"mean: " + derivedData[attr + "_mean"]}</div> : ""}
|
||||
{derivedData[attr + "_std"] ? <div style={{ fontSize: "0.7rem", marginTop: "3px", marginRight:"10px" }}>{"std: " + derivedData[attr + "_mean"]}</div>: ""}
|
||||
{DBMDict[attr]['range'].length > 0 && <div style={{ fontSize: "0.7rem", marginTop: "3px" }}>{"range: [" + DBMDict[attr]['range']+"]"}</div>}
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<button type="button" className='btn btn-outline-secondary btn-sm' onClick={() => exportComponentAsJPEG(ref)} style={{ width: "max-content", height:"max-content", marginLeft:"-35px" }}>
|
||||
<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;
|
||||
557
dashboard/src/components/individualPanel.js
Normal file
557
dashboard/src/components/individualPanel.js
Normal file
@@ -0,0 +1,557 @@
|
||||
import React, { useState, useEffect, useRef } 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"
|
||||
|
||||
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 componentRef = useRef();
|
||||
|
||||
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)
|
||||
setAUsButton(param === "aus" ? !AUsButton : false)
|
||||
setFacialMaskColor(param === "asym" ? "pink" : param === "pain" ? "red" : param === "expr" ? "orange" : "#cb181d")
|
||||
setFacialMaskVals(param === "asym" ? [0, 10] : param === "pain" ? [0, 1] : param === "expr" ? [0, 1] : [0, 1])
|
||||
}
|
||||
|
||||
const handleMovementMask = () => {
|
||||
$('#headMovementMaskContainer').css("opacity", !movementButton ? "1" : "0")
|
||||
setMovementButton(!movementButton)
|
||||
setFacialMaskColor(asymetryButton ? "pink" : painButton ? "red" : expressivityButton ? "orange" : "#cb181d")
|
||||
setFacialMaskVals(asymetryButton ? [0, 10] : 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", ""))
|
||||
}
|
||||
fetch("/updateCorrMatrix", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-type': "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"corrMatrix_args": corrMatrix_args,
|
||||
"individual": true,
|
||||
"id": id ? id : selectedId
|
||||
})
|
||||
}).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 {
|
||||
updatedCheckedState = checkedAcousticState.map(e => false)
|
||||
setCheckedAcousticState(updatedCheckedState)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{rawFacialData && rawFacialData.length > 0 &&
|
||||
<div id="timelineContainer" style={{ height: "5%", position: "absolute", top: "0px", left: "250px", display: "flex", flexDirection: "row" }}>
|
||||
|
||||
<div className="sliderContainer" style={{ margin: "5px", display: "flex", flexDirection: "row" }}>
|
||||
<div>Timeline</div>
|
||||
<div>
|
||||
<input type="range" min='0' max="20" id="myRange" className="slider" style={{ width: "400px" }}
|
||||
step="1" value={timeslotValue} onChange={setTime} list="steplist"></input>
|
||||
<datalist id="steplist" style={{ display: "inline-flex", marginTop: "-10px !important" }}>
|
||||
{timepoints.map((e, i) =>
|
||||
<option value={e} style={{ marginLeft: "2px", marginRight: "4px", fontSize: "0.7rem" }}>{i === 0 ? "*" : e}</option>
|
||||
)}
|
||||
</datalist>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: "flex", flexDirection: "row", justifyContent: 'flex-end', margin: "auto", width: "70%" }}>
|
||||
<div style={{ margin: "auto", fontWeight: "bolder" }}>Frames:</div>
|
||||
<div style={{ margin: "auto", fontWeight: "bolder", color: "#41ab5d" }} id="facialFrameLabel">Facial</div>
|
||||
<h6 style={{ margin: "auto", fontWeight: "bolder", color: "#4292c6" }} id="movementFrameLabel">Movement</h6>
|
||||
<h6 style={{ margin: "auto", fontWeight: "bolder", color: "#807dba" }} id="acousticFrameLabel">Acoustics</h6>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<Row style={{ height: "90vh", width: "99vw", marginLeft: "10px" }}>
|
||||
{derivedData &&
|
||||
<Col className="col-2" style={{ height: "100%", width: "250px" }}>
|
||||
<Row style={{ width: "300", }} id='headContainer'>
|
||||
<div style={{ display: "inline-flex", flexDirection: "row", gap: "3px", marginBottom: "10px", marginLeft: "-12px" }}>
|
||||
<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 style={{ marginLeft: "10px" }}>
|
||||
< HeadSVG width={200} height={300}/>
|
||||
</div>
|
||||
</Row>
|
||||
<Row style={{ marginTop: "300px" }}>
|
||||
<div style={{ width: "100%", height: "3vh", fontSize: "0.7rem", paddingLeft: "0px", paddingRight: "0px", marginBottom: "5px" }}>
|
||||
{AUsButton && emotions.map((value, index) =>
|
||||
<label className="id_checkbox_container" style={{ marginRight: "3px" }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={value + "_highlight"}
|
||||
className="emotion_checkbox"
|
||||
checked={checkedEmotions[index]}
|
||||
onChange={() => handleEmotionCheckboxChange(index)}
|
||||
/>
|
||||
{value}
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
<div id="colorScaleContainer" style={{ width: "90%", height: "5vh", opacity: ((painButton | expressivityButton | AUsButton | asymetryButton) ? "1" : "0") }}>
|
||||
<ColorLegend range={facialMaskVals} colorScale={["white", facialMaskColor]} derivedData={derivedData} id={"faceMaskColorLegend"} timelineData={facialTimelineData} timeframe={timeslotValue} />
|
||||
</div>
|
||||
<Col className="col-2" style={{ display: "flex", flexDirection: "column", borderRadius: "15px", paddingTop: "10px", fontSize: "0.7rem", width: "230px", boxShadow: "rgba(0, 0, 0, 0.16) 0px 1px 4px", height: 'calc(90vh - 420px)' }}>
|
||||
<button type="button" className='btn btn-primary btn-sm'>Filter ID(s)</button>
|
||||
<div style={{ fontSize: "0.8rem", width: "210px", display: "inline-flex", flexDirection: "column", marginTop: "10px", overflowY: "auto" }}>
|
||||
{idData.map((value, index) =>
|
||||
<label className="id_checkbox_container" id={value + "_id_"}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={value + "_id_checkbox"}
|
||||
className="individual_checkbox"
|
||||
onChange={handleIdCheckboxChange}
|
||||
/>
|
||||
{" " + value}
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
}
|
||||
<Col style={{ height: "100%", marginRight: "10px", width: "40%" }}>
|
||||
{rawFacialData && rawFacialData.length > 0 &&
|
||||
<Row id="facialActivityContainer" style={{ height: "48%", backgroundColor: "#f7fcf5", padding: "10px", overflowY: "scroll" }}>
|
||||
<div style={{ display: "flex", }}>
|
||||
<Col className='col-2' style={{ fontSize: "0.8rem", width: "max-content", }}>
|
||||
{allFacialAttr &&
|
||||
<div style={{ marginLeft: "-10px", marginRight: "5px" }}>
|
||||
<div style={{ display: "inline-flex", flexDirection: "row" }}>
|
||||
<button type="button" className='btn btn-sm btn-outline-primary' onClick={() => handleUpdate("facial")}>Update</button>
|
||||
<button type="button" className="btn-close" aria-label="Close" style={{ marginTop: "5px", marginLeft: "5px" }} onClick={() => handleUnselectCheckboxes("facial")}></button>
|
||||
</div>
|
||||
<QueryPanel allAcousticArg={[]} allMovementArg={[]} allSpeechArg={[]} allFacialArg={allFacialAttr}
|
||||
checkedState={checkedFacialState} handleCheckboxChange={handleFacialCheckboxChange} idVal={"rawFacialIndividualAttr_"} />
|
||||
</div>}
|
||||
</Col>
|
||||
<Col className='col-2' style={{ width: "77%", height: "100%", overflowY: "hidden" }}>
|
||||
{facialAttr.map((value) =>
|
||||
<div style={{ display: "inline-flex", flexDirection: "row", padding: "10px", fontSize: "0.8rem", }}>
|
||||
{<Histogram data={rawFacialData} derivedData={derivedData} attr={value} id={"histogram_" + value} color={["#41ab5d", "#c7e9c0"]} timeframe={timeslotValue} />}
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</div>
|
||||
</Row>
|
||||
}
|
||||
<Row style={{ marginTop: "30px", display: "flex", flexDirection: "row", height: "48%", overflowY: "auto" }}>
|
||||
<div style={{ display: "inline-flex", flexDirection: "row", gap: "10px", marginTop: "10px" }}>
|
||||
<button type="button" className={`btn btn-sm ${speechButton === true ? 'btn-primary' : 'btn-secondary'}`} onClick={() => handleSpeechPanel("speech")} id="speechPanelButton" style={{ height: "max-content", }}>{'Facial & Speech'}</button>
|
||||
<button type="button" className={`btn btn-sm ${corrButton === true ? 'btn-primary' : 'btn-secondary'}`} onClick={() => handleSpeechPanel("corr")} style={{ height: "max-content", }} id="corrMatrixPanelButton">Correlation</button>
|
||||
</div>
|
||||
{speechButton &&
|
||||
<div style={{ display: "flex", flexDirection: "row" }}>
|
||||
<Col className='col-8' style={{ display: "flex", flexDirection: "row" }}>
|
||||
<div style={{ width: "max-content", marginRight: "20px" }}>
|
||||
<div >
|
||||
<SpiderChart style={{ padding: "10px" }} label={'Emotion Intensity'} derivedData={derivedData} timelineData={facialTimelineData} timeframe={timeslotValue} levels={[0, 0.25, 0.5, 0.75, 1]}
|
||||
axes={meanEmotionsSoft.reverse()} />
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ width: "max-content" }}>
|
||||
<div >
|
||||
<SpiderChart style={{ padding: "10px" }} label={'AU Intensity'} derivedData={derivedData} timelineData={facialTimelineData} timeframe={timeslotValue} levels={[0, 1, 2, 3, 4, 5]}
|
||||
axes={meanAUsSoft.reverse()} />
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col className='col-4'>
|
||||
<h6 style={{ marginBotom: "10px", fontWeight: "bolder" }}>Speech</h6>
|
||||
{speechDerivedData && speechDerivedData.map((value, index) =>
|
||||
<div style={{ fontSize: "0.8rem" }}>{DBMDict[value]['label'] + ": " + derivedData[value]}</div>
|
||||
)}
|
||||
</Col>
|
||||
</div>}
|
||||
{corrButton && ((rawFacialData && rawFacialData.length > 0) || (rawAcousticData && rawAcousticData.length > 0) || (rawMovementData && rawMovementData.length > 0)) &&
|
||||
<Row style={{ height: "85%", marginTop: "5px" }}>
|
||||
<Col className='col-2' style={{ width: "25%", height: "100%", overflowY: "scroll", fontSize: "0.8rem" }}>
|
||||
|
||||
<div >
|
||||
<div style={{ display: "inline-flex", flexDirection: "row" }}>
|
||||
<button type="button" className='btn btn-sm btn-outline-primary' style={{ marginBottom: "10px" }} onClick={handleCorrMatrixUpdate}>Update</button>
|
||||
</div>
|
||||
<QueryPanel allAcousticArg={allAcousticAttr} allMovementArg={allMovementAttr} allSpeechArg={[]} allFacialArg={allFacialAttr}
|
||||
checkedState={checkedCorrMatrixState} handleCheckboxChange={handleCorrMatrixCheckboxChange} idVal={"corrMatrixIndividual_"} />
|
||||
</div>
|
||||
</Col>
|
||||
<Col className='col-2' style={{ width: "75%", height: "90%", }} id="corrMatrixContainer" >
|
||||
<CorrelationMatrix data={corrMatrixData} />
|
||||
</Col>
|
||||
</Row>}
|
||||
</Row>
|
||||
</Col>
|
||||
<Col style={{ height: "100%", width: "40%" }}>
|
||||
{rawMovementData && rawMovementData.length > 0 &&
|
||||
<Row id="headMovementContainer" style={{ height: "48%", overflowY: "scroll", backgroundColor: "#f7fbff" }}>
|
||||
<div style={{ display: "flex", }}>
|
||||
<Col className='col-2' style={{ width: "max-content", fontSize: "0.8rem" }}>
|
||||
{allMovementAttr &&
|
||||
<div style={{ marginLeft: "-10px", marginRight: "5px" }}>
|
||||
<div style={{ display: "inline-flex", flexDirection: "row" }}>
|
||||
<button type="button" className='btn btn-sm btn-outline-primary' style={{ marginBottom: "10px" }} onClick={() => handleUpdate("movement")}>Update</button>
|
||||
<button type="button" className="btn-close" aria-label="Close" style={{ marginTop: "5px", marginLeft: "5px" }} onClick={() => handleUnselectCheckboxes("movement")}></button>
|
||||
</div>
|
||||
<QueryPanel allAcousticArg={[]} allMovementArg={allMovementAttr} allSpeechArg={[]} allFacialArg={[]}
|
||||
checkedState={checkedMovementState} handleCheckboxChange={handleMovementCheckboxChange} idVal={"rawMovementIndividualAttr_"} />
|
||||
</div>}
|
||||
</Col>
|
||||
<Col className='col-2' style={{ width: "80%", height: "100%", overflowY: "hidden" }}>
|
||||
{movementAttr.map((value) =>
|
||||
<div style={{ display: "inline-flex", flexDirection: "row", padding: "10px", fontSize: "0.8rem", }}>
|
||||
{<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" style={{ height: "48%", overflowY: "scroll", backgroundColor: "#f7f4f9", marginTop: "30px" }}>
|
||||
<div style={{ display: "flex" }}>
|
||||
<Col className='col-2' style={{ fontSize: "0.8rem", width: "max-content" }}>
|
||||
{allAcousticAttr &&
|
||||
<div style={{ marginLeft: "-10px", marginRight: "5px" }}>
|
||||
<div style={{ display: "inline-flex", flexDirection: "row" }}>
|
||||
<button type="button" className='btn btn-sm btn-outline-primary' style={{ marginBottom: "10px" }} onClick={() => handleUpdate("acoustics")}>Update</button>
|
||||
<button type="button" className="btn-close" aria-label="Close" style={{ marginTop: "5px", marginLeft: "5px" }} onClick={() => handleUnselectCheckboxes("acoustics")}></button>
|
||||
|
||||
</div>
|
||||
<QueryPanel allAcousticArg={allAcousticAttr} allMovementArg={[]} allSpeechArg={[]} allFacialArg={[]}
|
||||
checkedState={checkedAcousticState} handleCheckboxChange={handleAcousticCheckboxChange} idVal={"rawAcousticIndividualAttr_"} />
|
||||
</div>}
|
||||
</Col>
|
||||
<Col className='col-2' style={{ width: "80%", height: "100%", overflowY: "hidden", fontSize: "0.8rem", }}>
|
||||
{acousticAttr.map((value) =>
|
||||
<div style={{ display: "inline-flex", flexDirection: "row", padding: "10px" }}>
|
||||
{<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;
|
||||
71
dashboard/src/components/queryPanel.js
Normal file
71
dashboard/src/components/queryPanel.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import DBMDict from "../DBM_attribute_dict.json"
|
||||
|
||||
const QueryPanel = ({ allAcousticArg, allMovementArg, allSpeechArg, allFacialArg, checkedState, handleCheckboxChange, idVal }) => {
|
||||
return (
|
||||
<div>
|
||||
{allFacialArg.length > 0 && <h6 style={{ marginTop: "10px", fontWeight: "bolder" }}>Facial</h6>}
|
||||
{allFacialArg.length > 0 && allFacialArg.map((value, index) =>
|
||||
<div><label className="">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={idVal + value}
|
||||
key={idVal + value}
|
||||
checked={checkedState[index]}
|
||||
className={idVal}
|
||||
onChange={() => handleCheckboxChange(index)}
|
||||
/>
|
||||
{" " + DBMDict[value]['label']}
|
||||
</label></div>
|
||||
)
|
||||
}
|
||||
{allMovementArg.length > 0 && <h6 style={{ marginBotom: "10px", fontWeight: "bolder" }}>Movement</h6>}
|
||||
{allMovementArg.length > 0 && allMovementArg.map((value, index) =>
|
||||
<div><label className="">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={idVal + value}
|
||||
key="{idVal + value}"
|
||||
checked={checkedState[index + allFacialArg.length]}
|
||||
className={idVal}
|
||||
onChange={() => handleCheckboxChange(index + allFacialArg.length)}
|
||||
/>
|
||||
{" " + DBMDict[value]['label']}
|
||||
</label></div>
|
||||
)
|
||||
}
|
||||
{allAcousticArg.length > 0 && <h6 style={{ marginBotom: "10px", fontWeight: "bolder" }}>Acoustics</h6>}
|
||||
{allAcousticArg.length > 0 && allAcousticArg.map((value, index) =>
|
||||
<div><label className="">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={idVal + value}
|
||||
key={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 style={{ fontWeight: "bolder" }}>Speech</h6>}
|
||||
{allSpeechArg.map((value, index) =>
|
||||
<div><label className="">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={idVal + value}
|
||||
key={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;
|
||||
111
dashboard/src/components/scatterplot.d3.js
Normal file
111
dashboard/src/components/scatterplot.d3.js
Normal file
@@ -0,0 +1,111 @@
|
||||
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", "brown", "purple", "orange"])
|
||||
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.4)
|
||||
$(`#${metadataColor(a)}color`).text(a)
|
||||
})
|
||||
$('#noneAttrContainer').css("opacity", 1)
|
||||
}
|
||||
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']) ? "red" : (metadataDict[d['id']] ? metadataAttrColor[d['id']] : "#69b3a2"))
|
||||
.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']) ? "red" : (metadataDict[d['id']] ? metadataAttrColor[d['id']] : "#69b3a2"))
|
||||
// default color for distribution chart points
|
||||
d3.selectAll(".dotDistr").style("fill", d => filteredIds.includes(d[0]) ? "red" : (metadataDict[d[0]] ? metadataAttrColor[d[0]] : "#69b3a2"))
|
||||
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", "red")
|
||||
$("#" + e + "_id_container").css("color", "#fc6a03")
|
||||
})
|
||||
}
|
||||
keys.forEach(k => {
|
||||
if ($('#' + k + "_id").is(":checked"))
|
||||
$(".dot_" + k).css("fill", "red")
|
||||
})
|
||||
if($('#metadataButton').hasClass("btn-outline-primary")){
|
||||
console.log("e checked")
|
||||
}
|
||||
svg.property("value", value).dispatch("input");
|
||||
}
|
||||
d3.selectAll(".dotDistr").style("fill", d => filteredIds.includes(d[0]) ? "red" : (metadataDict[d[0]] ? metadataAttrColor[d[0]] : "#69b3a2"))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
36
dashboard/src/components/scatterplot.js
Normal file
36
dashboard/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';
|
||||
|
||||
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 (
|
||||
// <div ref={ref}></div>
|
||||
<React.Fragment>
|
||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<h6 style={{ fontWeight: "bolder" }}>PCA</h6>
|
||||
<button type="button" className='btn btn-outline-secondary btn-sm' onClick={() => exportComponentAsJPEG(ref)} style={{ width: "max-content", }}>
|
||||
<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
dashboard/src/components/spiderChart.d3.js
Normal file
120
dashboard/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)
|
||||
|
||||
}
|
||||
}
|
||||
35
dashboard/src/components/spiderChart.js
Normal file
35
dashboard/src/components/spiderChart.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 SpiderChartD3 from './spiderChart.d3';
|
||||
|
||||
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 style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<div style={{fontSize:"0.8rem"}}>{label}</div>
|
||||
<button type="button" className='btn btn-outline-secondary btn-sm' onClick={() => exportComponentAsJPEG(ref)} style={{ width: "max-content", }}>
|
||||
<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;
|
||||
Reference in New Issue
Block a user