mirror of
https://github.com/pds-nest/nest.git
synced 2024-11-25 14:34:19 +00:00
✨ Add chart visualization
This commit is contained in:
parent
eb56dbbfc2
commit
5a27a8e734
8 changed files with 137 additions and 23 deletions
60
nest_frontend/components/base/BoxChart.js
Normal file
60
nest_frontend/components/base/BoxChart.js
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import React, { useRef } from "react"
|
||||||
|
import BoxFull from "./BoxFull"
|
||||||
|
import ChartComponent from "react-chartjs-2"
|
||||||
|
|
||||||
|
|
||||||
|
export default function BoxChart({chartProps, ...props}) {
|
||||||
|
const boxContentsRef = useRef(null)
|
||||||
|
const getCssVar = (variable) => {
|
||||||
|
const computedStyle = window.getComputedStyle(boxContentsRef.current)
|
||||||
|
console.debug(variable, computedStyle.getPropertyValue(variable))
|
||||||
|
return computedStyle.getPropertyValue(variable).trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BoxFull
|
||||||
|
childrenProps={{ref: boxContentsRef}}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{boxContentsRef.current ?
|
||||||
|
<ChartComponent
|
||||||
|
width={boxContentsRef.current.offsetWidth}
|
||||||
|
height={boxContentsRef.current.offsetHeight}
|
||||||
|
options={{
|
||||||
|
responsive: true,
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
beginAtZero: true,
|
||||||
|
grid: {
|
||||||
|
borderColor: getCssVar("--bg-light"),
|
||||||
|
color: getCssVar("--bg-light"),
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
color: getCssVar("--fg-primary"),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
grid: {
|
||||||
|
borderColor: getCssVar("--bg-light"),
|
||||||
|
color: getCssVar("--bg-light"),
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
color: getCssVar("--fg-primary"),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
bar: {
|
||||||
|
backgroundColor: getCssVar("--fg-primary"),
|
||||||
|
borderColor: "transparent",
|
||||||
|
color: getCssVar("--fg-primary"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
{...chartProps}
|
||||||
|
/>
|
||||||
|
: null}
|
||||||
|
</BoxFull>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
import React from "react"
|
||||||
|
import BoxFull from "../base/BoxFull"
|
||||||
|
import BoxChart from "../base/BoxChart"
|
||||||
|
|
||||||
|
|
||||||
|
export default function BoxVisualizationChart({ tweets, ...props }) {
|
||||||
|
// TODO: translate this
|
||||||
|
const hours = [...Array(24).keys()].map(hour => hour.toString())
|
||||||
|
const hourlyTweetCount = Array(24).fill(0)
|
||||||
|
for(const tweet of tweets) {
|
||||||
|
const insertDate = new Date(tweet["insert_time"])
|
||||||
|
const insertHour = insertDate.getHours()
|
||||||
|
console.log(insertHour)
|
||||||
|
hourlyTweetCount[insertHour] += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BoxChart
|
||||||
|
header={"Hourly graph"}
|
||||||
|
chartProps={{
|
||||||
|
type: "bar",
|
||||||
|
data: {
|
||||||
|
labels: hours.map(hour => hour.toString()),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "Tweets",
|
||||||
|
data: hourlyTweetCount,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,14 +0,0 @@
|
||||||
import React from "react"
|
|
||||||
import BoxFull from "../base/BoxFull"
|
|
||||||
|
|
||||||
|
|
||||||
export default function BoxVisualizationGraph({ children, className, ...props }) {
|
|
||||||
// TODO: translate this
|
|
||||||
// TODO: implement this
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BoxFull header={"Hourly graph"} {...props}>
|
|
||||||
{children}
|
|
||||||
</BoxFull>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -41,8 +41,6 @@ export default function BoxVisualizationStats({ tweets, words, totalTweetCount,
|
||||||
[tweetContentCount, tweetCount],
|
[tweetContentCount, tweetCount],
|
||||||
)
|
)
|
||||||
|
|
||||||
console.debug(words)
|
|
||||||
|
|
||||||
const wordCount = useMemo(
|
const wordCount = useMemo(
|
||||||
() => words.map(word => word.value).reduce((a, b) => a + b),
|
() => words.map(word => word.value).reduce((a, b) => a + b),
|
||||||
[words],
|
[words],
|
||||||
|
|
|
@ -6,6 +6,10 @@ import { faChartBar, faCloud, faMap, faStar } from "@fortawesome/free-solid-svg-
|
||||||
export default function PickerVisualization({ currentTab, setTab, ...props }) {
|
export default function PickerVisualization({ currentTab, setTab, ...props }) {
|
||||||
return (
|
return (
|
||||||
<div {...props}>
|
<div {...props}>
|
||||||
|
<ButtonIconOnly
|
||||||
|
onClick={() => setTab("stats")} disabled={currentTab ===
|
||||||
|
"stats"} color={"Grey"} icon={faStar}
|
||||||
|
/>
|
||||||
<ButtonIconOnly
|
<ButtonIconOnly
|
||||||
onClick={() => setTab("wordcloud")} disabled={currentTab ===
|
onClick={() => setTab("wordcloud")} disabled={currentTab ===
|
||||||
"wordcloud"} color={"Grey"} icon={faCloud}
|
"wordcloud"} color={"Grey"} icon={faCloud}
|
||||||
|
@ -15,10 +19,6 @@ export default function PickerVisualization({ currentTab, setTab, ...props }) {
|
||||||
"histogram"} color={"Grey"} icon={faChartBar}
|
"histogram"} color={"Grey"} icon={faChartBar}
|
||||||
/>
|
/>
|
||||||
<ButtonIconOnly onClick={() => setTab("map")} disabled={currentTab === "map"} color={"Grey"} icon={faMap}/>
|
<ButtonIconOnly onClick={() => setTab("map")} disabled={currentTab === "map"} color={"Grey"} icon={faMap}/>
|
||||||
<ButtonIconOnly
|
|
||||||
onClick={() => setTab("stats")} disabled={currentTab ===
|
|
||||||
"stats"} color={"Grey"} icon={faStar}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||||
import { useParams } from "react-router"
|
import { useParams } from "react-router"
|
||||||
import Loading from "../components/base/Loading"
|
import Loading from "../components/base/Loading"
|
||||||
import BoxVisualizationStats from "../components/interactive/BoxVisualizationStats"
|
import BoxVisualizationStats from "../components/interactive/BoxVisualizationStats"
|
||||||
import BoxVisualizationGraph from "../components/interactive/BoxVisualizationGraph"
|
import BoxVisualizationChart from "../components/interactive/BoxVisualizationChart"
|
||||||
import BoxVisualizationMap from "../components/interactive/BoxVisualizationMap"
|
import BoxVisualizationMap from "../components/interactive/BoxVisualizationMap"
|
||||||
import BoxVisualizationWordcloud from "../components/interactive/BoxVisualizationWordcloud"
|
import BoxVisualizationWordcloud from "../components/interactive/BoxVisualizationWordcloud"
|
||||||
import BoxFull from "../components/base/BoxFull"
|
import BoxFull from "../components/base/BoxFull"
|
||||||
|
@ -25,7 +25,7 @@ export default function PageRepository({ className, ...props }) {
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
const { strings } = useContext(ContextLanguage)
|
const { strings } = useContext(ContextLanguage)
|
||||||
|
|
||||||
const [visualizationTab, setVisualizationTab] = useState("wordcloud")
|
const [visualizationTab, setVisualizationTab] = useState("stats")
|
||||||
const [addFilterTab, setAddFilterTab] = useState("hashtag")
|
const [addFilterTab, setAddFilterTab] = useState("hashtag")
|
||||||
|
|
||||||
const repositoryBr = useBackendResource(
|
const repositoryBr = useBackendResource(
|
||||||
|
@ -99,7 +99,7 @@ export default function PageRepository({ className, ...props }) {
|
||||||
/>
|
/>
|
||||||
: null}
|
: null}
|
||||||
{visualizationTab === "histogram" ?
|
{visualizationTab === "histogram" ?
|
||||||
<BoxVisualizationGraph
|
<BoxVisualizationChart
|
||||||
className={Style.Wordcloud}
|
className={Style.Wordcloud}
|
||||||
tweets={tweets}
|
tweets={tweets}
|
||||||
/>
|
/>
|
||||||
|
|
33
package-lock.json
generated
33
package-lock.json
generated
|
@ -22,6 +22,7 @@
|
||||||
"is-string": "^1.0.5",
|
"is-string": "^1.0.5",
|
||||||
"leaflet": "^1.7.1",
|
"leaflet": "^1.7.1",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
|
"react-chartjs-2": "^3.0.3",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-leaflet": ">=3.1.0 <3.2.0 || ^3.2.1",
|
"react-leaflet": ">=3.1.0 <3.2.0 || ^3.2.1",
|
||||||
"react-router": "^5.2.0",
|
"react-router": "^5.2.0",
|
||||||
|
@ -5132,6 +5133,12 @@
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/chart.js": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-XsNDf3854RGZkLCt+5vWAXGAtUdKP2nhfikLGZqud6G4CvRE2ts64TIxTTfspOin2kEZvPgomE29E6oU02dYjQ==",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/check-types": {
|
"node_modules/check-types": {
|
||||||
"version": "11.1.2",
|
"version": "11.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz",
|
||||||
|
@ -16155,6 +16162,18 @@
|
||||||
"react-scripts": ">=2.1.3"
|
"react-scripts": ">=2.1.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-chartjs-2": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-jOFZKwZ8sMLkddewZ/tToxuu4pYimAvvY5I6uK+hCpSFT16Pvo2bdHhUoZ0X87zu9I+dx2I+JCqaLN6XhmrbDg==",
|
||||||
|
"dependencies": {
|
||||||
|
"lodash": "^4.17.19"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"chart.js": "^3.1.0",
|
||||||
|
"react": "^16.8.0 || ^17.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-dev-utils": {
|
"node_modules/react-dev-utils": {
|
||||||
"version": "11.0.4",
|
"version": "11.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz",
|
||||||
|
@ -26645,6 +26664,12 @@
|
||||||
"resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
|
||||||
"integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="
|
"integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="
|
||||||
},
|
},
|
||||||
|
"chart.js": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-XsNDf3854RGZkLCt+5vWAXGAtUdKP2nhfikLGZqud6G4CvRE2ts64TIxTTfspOin2kEZvPgomE29E6oU02dYjQ==",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"check-types": {
|
"check-types": {
|
||||||
"version": "11.1.2",
|
"version": "11.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz",
|
||||||
|
@ -35334,6 +35359,14 @@
|
||||||
"semver": "^5.6.0"
|
"semver": "^5.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-chartjs-2": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-jOFZKwZ8sMLkddewZ/tToxuu4pYimAvvY5I6uK+hCpSFT16Pvo2bdHhUoZ0X87zu9I+dx2I+JCqaLN6XhmrbDg==",
|
||||||
|
"requires": {
|
||||||
|
"lodash": "^4.17.19"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-dev-utils": {
|
"react-dev-utils": {
|
||||||
"version": "11.0.4",
|
"version": "11.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz",
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
"is-string": "^1.0.5",
|
"is-string": "^1.0.5",
|
||||||
"leaflet": "^1.7.1",
|
"leaflet": "^1.7.1",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
|
"react-chartjs-2": "^3.0.3",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-leaflet": ">=3.1.0 <3.2.0 || ^3.2.1",
|
"react-leaflet": ">=3.1.0 <3.2.0 || ^3.2.1",
|
||||||
"react-router": "^5.2.0",
|
"react-router": "^5.2.0",
|
||||||
|
|
Loading…
Reference in a new issue