From 2f0a0c42cac236ec802363b72aaff5450c3c23d9 Mon Sep 17 00:00:00 2001 From: JP Rodrigues <70jprodrigues@gmail.com> Date: Sun, 25 Apr 2021 01:47:27 -0300 Subject: [PATCH] Add charts visualization --- package-lock.json | 14 ++- package.json | 1 + src/components/Btn/style.sass | 9 +- src/components/Chart/index.js | 20 ++++ src/contracts/.AnswerPrinter.js.swp | Bin 0 -> 16384 bytes src/contracts/AnswerPrinter.js | 137 +++++++++++++++++++++++++++- src/plugins/color.js | 52 +++++++++++ src/questions/groupsInAnswer.js | 1 + 8 files changed, 225 insertions(+), 9 deletions(-) create mode 100644 src/components/Chart/index.js create mode 100644 src/contracts/.AnswerPrinter.js.swp create mode 100644 src/plugins/color.js diff --git a/package-lock.json b/package-lock.json index 2792c6a..f43fa1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,18 @@ { - "name": "research-organizer", + "name": "tsv-explorer", "version": "0.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { + "name": "tsv-explorer", "version": "0.1.0", "dependencies": { "@popperjs/core": "^2.9.2", "@testing-library/jest-dom": "^5.11.10", "@testing-library/react": "^11.2.6", "@testing-library/user-event": "^12.8.3", + "chart.js": "^3.2.0", "date-fns": "^2.21.1", "gh-pages": "^3.1.0", "node-sass": "^5.0.0", @@ -5193,6 +5195,11 @@ "node": ">=10" } }, + "node_modules/chart.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.2.0.tgz", + "integrity": "sha512-Ml3R47TvOPW6gQ6T8mg/uPvyOASPpPVVF6xb7ZyHkek1c6kJIT5ScT559afXoDf6uwtpDR2BpCommkA5KT8ODg==" + }, "node_modules/check-types": { "version": "11.1.2", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz", @@ -26713,6 +26720,11 @@ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==" }, + "chart.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.2.0.tgz", + "integrity": "sha512-Ml3R47TvOPW6gQ6T8mg/uPvyOASPpPVVF6xb7ZyHkek1c6kJIT5ScT559afXoDf6uwtpDR2BpCommkA5KT8ODg==" + }, "check-types": { "version": "11.1.2", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz", diff --git a/package.json b/package.json index 3a5fddc..23c8e2b 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@testing-library/jest-dom": "^5.11.10", "@testing-library/react": "^11.2.6", "@testing-library/user-event": "^12.8.3", + "chart.js": "^3.2.0", "date-fns": "^2.21.1", "gh-pages": "^3.1.0", "node-sass": "^5.0.0", diff --git a/src/components/Btn/style.sass b/src/components/Btn/style.sass index 0b65bc9..560637d 100644 --- a/src/components/Btn/style.sass +++ b/src/components/Btn/style.sass @@ -26,11 +26,12 @@ background-color: #bee2aa &--negative background-color: #e2aaaa + &--neutral + background-color: #afd4ff + &--unclassified + background-color: #ffe7ce &--active - border-color: #3a84d8 - // & > .c-btn__background - // background-color: rgba(25, 132, 255, .29) - // display: inline + border-color: #0d2e57 &:hover & > .c-btn__background background: rgba(0,0,0,.2) diff --git a/src/components/Chart/index.js b/src/components/Chart/index.js new file mode 100644 index 0000000..544c9c9 --- /dev/null +++ b/src/components/Chart/index.js @@ -0,0 +1,20 @@ +import { Component, createRef } from "react"; + +import Chartjs from "chart.js/auto"; + +export default class Chart extends Component { + chartRef = createRef(); + + componentDidMount() { + const myChartRef = this.chartRef.current.getContext("2d"); + new Chartjs(myChartRef, { + type: this.props.type, + data: this.props.data, + options: this.props.options, + }); + } + + render() { + return ; + } +} diff --git a/src/contracts/.AnswerPrinter.js.swp b/src/contracts/.AnswerPrinter.js.swp new file mode 100644 index 0000000000000000000000000000000000000000..b9d0dd83a6d4276e5469b181d612d23ac46b8881 GIT binary patch literal 16384 zcmeI3ZH(MT8OKd1Z*4;hjZ~p3V!BC#m)?2z0xi*I_oBK<4-?_=Sa;ISg?I`H` z;>7l!BS+7jTsVDZ`o8=3A3u5G%$CNrbvvqsT_@;;wTLomN6zYW5OrgSg|7rez`8LCN8U)}t z*b6G)pKmvepMfue&wxIN!4h~7+y&kRUfpRJzW_f1Ujh+W1xLUEa1VIlR>OD>d>woq z^gstJg7aVj>;t>NwOb71S@13JB&=GoXYecV1#l5WpbmC}TY&*ydlR$-m%y{&+u(8V2)GXra2vRG zlVSV~Tm>(Km%vZKcfpszCxHtd1*gCqxC{K@ji?j!!C9~e>;kVK9xj9LgKvNfK*Y!q zu;D`rpT6sNg1+VYK8-%?cK9lJ>@iZ`X%vh|Cvf5(^|_U99QA0j?|3c_=LM&VB_Fc+ zs@1axrhd3*<7XmZj)m0WXKn5V4gLJMI#EkRc^vt~3A`Y(7{{+ny89oX1JkV@)XNbv zF76UiD7oTBj4#mMyr_qnYm=qj6=JTDCYh;|ef!87nQf7Jm5hpY;x{jh<7A82$!}iR zNjhYfNBu6On)D}cx>K@Pm9%Dy&2?;UZ&o0&RP8Ui zOGUjryG^|Ysq7)AS0Dfj1Nh`~R?$?=Iwe|Ql(Pm|O#3<3?81P#7#<(hMQ(SM>veQo z7JBxuL3k9?tc*vy)bBL%ekAUoeoZcAETt44Kuvfg9azfL(;X>xv000~+0) zh;!MkeEK(QEVO-XaQm?2tCR$x2!3qZoD72)v&o_EI>tzxgq$n1PjNMnlZ;U&G)*;Y zYIRnLHsDHfX{UDJSGsjbgvS5uVK?BZbG_ikU6aG4T$4M(HMO;=Yar{Oc$EQRa`bAX+ z0)$4Nw9And(N65pO=SOXeN(rWf`7A`OWXHzLYVDbwg+3eEaX%Hp5KpPe7e1co2!_E z%vAo#fM;@+%q7<8si|~~u_3Pr$&F8_sVZq=tI9at;g6OF2teV1h zV^n3Am;C-EB41R_wAep{+37ON@odK26&F9270(9Y;o-c^X*Y;m%1k~CsTep+4`I7* zdv1qS)5uP0BqG@T*z@F3*(Kp7X^A6_tZb+TSyYH;47eiyip!uVJUPbN&}cWs#0l4z zZb&WD3tlP~qJ&V7(fqDYbl_?hFV^s)GE)EwqBLDv-^4KfLeMSDCzTf{7a{ZUU3*dS14EP%O99RdBfK%WAAmAU! z`F{??n!qgB1uh}S{|d0dDR47*5xM+NfXMeno_`Xwzzlc~csJM$ZUL_$xBndwIe#0R z1rGp`_qRX|RDj6;{|c^x--6$O=fP9pN$@!Ez=L2ncolv9J$M<2eqRRVr&OR+pj4n# zpj4n#pj6;>p+J&ysk|$FM3-sQB9h%CDU`VdvqJHYmgo4;qvPeJBwx((9+3lVZ?9!U zTU5G*MRr)N1!M~*GMN&UAs@(7mc`0PV=Rwh1q}-WGT%jFirtD#AwuLkc-%fbTtG>4 zG8xS!Xx2LJdbX}c$X06*waliO7nt-_%W?IrZL$rtB9ju)tJbIzy0kJ*mS$Tcu|PXk zYlEycl8c0FjAf6cNah9VfFD>I!8o->BhnYISFqCi{(3zeRK$`?gyaPW>cozDP$A=d zff}z9u=7nsr8NG>W0h#^Q2W@gwwZ?tgG^3?MQqk_p^mJ(Njw<;4ktZ@VFbE zK-RffU&0F`yy%eQC@T?vxywD;AS&^6WgPmYRuyOoDnYDY%8qK@r(;>{agJZZ;OI2Mi-18}<+UW9D^%8QT< nFTK>dMAi_T#A3X$9oB_Fu|04)9k-xmgy literal 0 HcmV?d00001 diff --git a/src/contracts/AnswerPrinter.js b/src/contracts/AnswerPrinter.js index b780d43..119bfab 100644 --- a/src/contracts/AnswerPrinter.js +++ b/src/contracts/AnswerPrinter.js @@ -4,6 +4,8 @@ import AnswerClassifier from "../plugins/AnswerClassifier"; import analyser from "sentiment-ptbr"; import key from "../plugins/key"; +import color from "../plugins/color"; +import Chart from "../components/Chart"; export default class AnswerPrinter extends Component { valids = []; invalids = []; @@ -36,7 +38,8 @@ export default class AnswerPrinter extends Component { ? this.classifier.groupByCategories(type.key, this.valids) : items; const areas = []; - Object.keys(sets).forEach((set) => { + const answersKeys = Object.keys(sets); + answersKeys.forEach((set) => { const content = item(sets[set]); areas.push({ key: `${set}: ${sets[set].length}`, @@ -44,18 +47,81 @@ export default class AnswerPrinter extends Component { content, }); }); + + if (answersKeys.length > 1) { + const data = { + labels: answersKeys, + datasets: [ + { + maxBarThickness: 24, + label: false, + data: answersKeys.map((i) => sets[i].length), + backgroundColor: answersKeys.map((i, index) => { + return color.string2Hex(i); + }), + }, + ], + }; + const options = { + responsive: true, + maintainAspectRatio: false, + plugins: { + title: { + display: false, + text: "Number of valids respondents", + }, + legend: { + display: false, + position: "right", + }, + tooltip: { + callbacks: { + label: function ({ dataset, parsed }) { + const total = dataset.data.reduce((acc, i) => (acc += i), 0); + const percentage = parseFloat( + ((parsed.y / total) * 100).toFixed(2) + ); + + return `${parsed.y} (${percentage}%)` || ""; + }, + title: function (context) { + return context[0].label; + }, + }, + }, + }, + }; + + areas.push({ + key: "Chart", + content: ( +
+ +
+ ), + }); + } return ; } summaryAnswer(answers, maxText, minText) { - const max = Object.keys(answers).reduce( + const answersKeys = Object.keys(answers); + const max = answersKeys.reduce( (acc, key) => { return answers[key] > acc.total ? { key, total: answers[key] } : acc; }, { total: 0 } ); - const min = Object.keys(answers).reduce( + const min = answersKeys.reduce( (acc, key) => { return answers[key] < acc.total || acc.total === 0 ? { key, total: answers[key] } @@ -98,7 +164,7 @@ export default class AnswerPrinter extends Component { key: "Compiled data", content: (
    - {Object.keys(answers).map((i) => ( + {answersKeys.map((i) => (
  • {i}: {answers[i]}
  • @@ -108,6 +174,69 @@ export default class AnswerPrinter extends Component { }, ]; + const data = { + labels: answersKeys, + datasets: [ + { + label: "# Of respondents", + data: answersKeys.map((i) => answers[i]), + backgroundColor: answersKeys.map((i, index) => { + const j = answersKeys.length - index; + if (j < 10 && j >= 0) { + return color.firstTemColors(j); + } + return color.string2Hex(i); + }), + }, + ], + }; + const options = { + responsive: true, + maintainAspectRatio: false, + plugins: { + title: { + display: false, + text: "Number of valids respondents", + }, + legend: { + display: true, + position: "right", + }, + tooltip: { + callbacks: { + label: function ({ dataset, parsed }) { + const total = dataset.data.reduce((acc, i) => (acc += i), 0); + const percentage = parseFloat( + ((parsed / total) * 100).toFixed(2) + ); + + return `${parsed} (${percentage}%)` || ""; + }, + title: function (context) { + return context[0].label; + }, + }, + }, + }, + }; + + areas.push({ + key: "Chart", + content: ( +
    + +
    + ), + }); + return (
    diff --git a/src/plugins/color.js b/src/plugins/color.js new file mode 100644 index 0000000..f8e4554 --- /dev/null +++ b/src/plugins/color.js @@ -0,0 +1,52 @@ +const color = { + firstTemColors(i) { + const colors = [ + "#084887", + "#dc3912", + "#3366cc", + "#7F2CCB", + "#5D2E46", + "#A62639", + "#F58A07", + "#3454D1", + "#34D1BF", + ]; + return colors[i]; + }, + colorContrast(bgColor, lightColor, darkColor) { + var color = bgColor.charAt(0) === "#" ? bgColor.substring(1, 7) : bgColor; + var r = parseInt(color.substring(0, 2), 16); // hexToR + var g = parseInt(color.substring(2, 4), 16); // hexToG + var b = parseInt(color.substring(4, 6), 16); // hexToB + return r * 0.299 + g * 0.587 + b * 0.114 > 186 ? darkColor : lightColor; + }, + string2Hex(str) { + switch (str.toLowerCase()) { + case "positive": + return "#66c333"; + case "negative": + return "#ca3030"; + case "neutral": + return "#285488"; + case "unclassified": + return "#e3e3e4"; + } + + return `#${this.intToRGB(this.hashCode(str))}`; + }, + hashCode(str) { + str = str || ""; + let hash = 0; + for (let i = 0; i < str.length; i++) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + return hash; + }, + intToRGB(i) { + let c = (i & 0x00ffffff).toString(16).toUpperCase(); + + return "00000".substring(0, 6 - c.length) + c; + }, +}; + +export default color; diff --git a/src/questions/groupsInAnswer.js b/src/questions/groupsInAnswer.js index 85e6cc9..39aa1fb 100644 --- a/src/questions/groupsInAnswer.js +++ b/src/questions/groupsInAnswer.js @@ -22,6 +22,7 @@ const groupsInAnswer = { weight: 9, answers: { printStyle: "summarize", + showChart: "Dognut", }, }; export default groupsInAnswer;