summaryrefslogtreecommitdiff
path: root/components/Tweaks/Visual
diff options
context:
space:
mode:
Diffstat (limited to 'components/Tweaks/Visual')
-rw-r--r--components/Tweaks/Visual/ColorMenu.tsx88
-rw-r--r--components/Tweaks/Visual/ColorsPanel.tsx237
-rw-r--r--components/Tweaks/Visual/GraphColorSelect.tsx64
-rw-r--r--components/Tweaks/Visual/HighlightingPanel.tsx116
-rw-r--r--components/Tweaks/Visual/LabelsPanel.tsx162
-rw-r--r--components/Tweaks/Visual/NodesNLinksPanel.tsx131
-rw-r--r--components/Tweaks/Visual/ThemeSelect.tsx63
-rw-r--r--components/Tweaks/Visual/VisualsPanel.tsx121
8 files changed, 982 insertions, 0 deletions
diff --git a/components/Tweaks/Visual/ColorMenu.tsx b/components/Tweaks/Visual/ColorMenu.tsx
new file mode 100644
index 0000000..a792f12
--- /dev/null
+++ b/components/Tweaks/Visual/ColorMenu.tsx
@@ -0,0 +1,88 @@
+import { ChevronDownIcon } from '@chakra-ui/icons'
+import {
+ Text,
+ Box,
+ Button,
+ Flex,
+ Menu,
+ MenuButton,
+ MenuItem,
+ MenuList,
+ Portal,
+ PopoverTrigger,
+ PopoverContent,
+ Popover,
+} from '@chakra-ui/react'
+import React, { useCallback } from 'react'
+import { initialVisuals } from '../../config'
+
+export interface ColorMenuProps {
+ label: string
+ colorList: string[]
+ value: string
+ visValue: string
+ setVisuals?: any
+ noEmpty?: boolean
+}
+
+export const ColorMenu = (props: ColorMenuProps) => {
+ const { label, colorList, value, visValue, setVisuals, noEmpty } = props
+
+ const clickCallback = useCallback(
+ (color) =>
+ setVisuals((curr: typeof initialVisuals) => {
+ return {
+ ...curr,
+ [value]: color,
+ }
+ }),
+ [],
+ )
+ return (
+ <Flex alignItems="center" justifyContent="space-between">
+ <Text>{label}</Text>
+ <Popover isLazy placement="right">
+ <PopoverTrigger>
+ <Button colorScheme="" color="black" rightIcon={<ChevronDownIcon />}>
+ {<Box bgColor={visValue} borderRadius="sm" height={6} width={6}></Box>}
+ </Button>
+ </PopoverTrigger>
+ <Portal>
+ <PopoverContent zIndex="tooltip" maxW={36} position="relative">
+ <Flex flexWrap="wrap" bgColor="gray.200">
+ {!noEmpty && (
+ <Box
+ onClick={() => clickCallback('')}
+ justifyContent="space-between"
+ alignItems="center"
+ display="flex"
+ m={1}
+ >
+ <Box
+ height={6}
+ width={6}
+ borderColor="gray.600"
+ borderRadius="xl"
+ borderWidth={1}
+ ></Box>
+ </Box>
+ )}
+ {colorList.map((color: string) => (
+ <Box
+ m={1}
+ key={color}
+ onClick={() => clickCallback(color)}
+ justifyContent="space-between"
+ alignItems="center"
+ display="flex"
+ >
+ <Box bgColor={color} borderRadius="xl" height={6} width={6}></Box>
+ </Box>
+ ))}
+ </Flex>
+ </PopoverContent>
+ </Portal>
+ </Popover>
+ </Flex>
+ )
+}
diff --git a/components/Tweaks/Visual/ColorsPanel.tsx b/components/Tweaks/Visual/ColorsPanel.tsx
new file mode 100644
index 0000000..fe4cd7b
--- /dev/null
+++ b/components/Tweaks/Visual/ColorsPanel.tsx
@@ -0,0 +1,237 @@
+import { ArrowRightIcon, ChevronDownIcon, RepeatIcon } from '@chakra-ui/icons'
+import {
+ Text,
+ Box,
+ Flex,
+ IconButton,
+ StackDivider,
+ Tooltip,
+ VStack,
+ MenuButton,
+ Menu,
+ Portal,
+ MenuList,
+ MenuOptionGroup,
+ MenuItemOption,
+ Button,
+ MenuItem,
+} from '@chakra-ui/react'
+import React from 'react'
+import { ColorMenu } from './ColorMenu'
+import { colorList, initialVisuals } from '../../config'
+
+export interface ColorsPanelProps {
+ visuals: typeof initialVisuals
+ setVisualsCallback: any
+ highlightColor: string
+ setHighlightColor: any
+}
+
+export const ColorsPanel = (props: ColorsPanelProps) => {
+ const { visuals, setVisualsCallback, highlightColor, setHighlightColor } = props
+
+ return (
+ <VStack
+ spacing={2}
+ justifyContent="flex-start"
+ divider={<StackDivider borderColor="gray.500" />}
+ align="stretch"
+ color="gray.800"
+ >
+ <Box>
+ <Flex alignItems="center" justifyContent="space-between">
+ <Text>Nodes</Text>
+ <Tooltip label="Shuffle node colors">
+ <IconButton
+ aria-label="Shuffle node colors"
+ size="sm"
+ icon={<RepeatIcon />}
+ variant="ghost"
+ onClick={() => {
+ const arr = visuals.nodeColorScheme ?? []
+ setVisualsCallback({
+ ...visuals,
+ //shuffle that guy
+ //definitely thought of this myself
+ nodeColorScheme: arr
+ .map((x: any) => [Math.random(), x])
+ .sort(([a], [b]) => a - b)
+ .map(([_, x]) => x),
+ })
+ }}
+ />
+ </Tooltip>
+ <Tooltip label="Cycle node colors">
+ <IconButton
+ aria-label="Shift node colors"
+ icon={<ArrowRightIcon />}
+ size="sm"
+ variant="ghost"
+ onClick={() => {
+ const arr = visuals.nodeColorScheme ?? []
+ setVisualsCallback({
+ ...visuals,
+ nodeColorScheme: [...arr.slice(1, arr.length), arr[0]],
+ })
+ }}
+ />
+ </Tooltip>
+ <Menu isLazy placement="right" closeOnSelect={false} matchWidth>
+ <MenuButton
+ width={20}
+ as={Button}
+ colorScheme=""
+ color="black"
+ rightIcon={<ChevronDownIcon />}
+ >
+ <Flex height={6} width={6} flexDirection="column" flexWrap="wrap">
+ {visuals.nodeColorScheme.map((color) => (
+ <Box key={color} bgColor={color} flex="1 1 8px" borderRadius="2xl"></Box>
+ ))}
+ </Flex>
+ </MenuButton>
+ <Portal>
+ {' '}
+ <MenuList minW={10} zIndex="popover" bgColor="gray.200">
+ <MenuOptionGroup
+ width={500}
+ type="checkbox"
+ defaultValue={visuals.nodeColorScheme}
+ onChange={(colors) => {
+ if (!colors.length) {
+ return
+ }
+ setVisualsCallback({ ...visuals, nodeColorScheme: colors })
+ }}
+ >
+ {colorList.map((color) => (
+ <MenuItemOption
+ key={color}
+ isChecked={visuals.nodeColorScheme.some((c) => c === color)}
+ value={color}
+ isDisabled={
+ visuals.nodeColorScheme.length === 1 && visuals.nodeColorScheme[0] === color
+ }
+ >
+ <Box justifyContent="space-between" alignItems="center" display="flex">
+ <Box bgColor={color} borderRadius="sm" height={6} width={6}></Box>
+ </Box>
+ </MenuItemOption>
+ ))}
+ </MenuOptionGroup>
+ </MenuList>
+ </Portal>
+ </Menu>
+ </Flex>
+ <Flex alignItems="center" justifyContent="space-between">
+ <Text>Links</Text>
+ <Menu isLazy placement="right">
+ <MenuButton as={Button} colorScheme="" color="black" rightIcon={<ChevronDownIcon />}>
+ <Box>
+ {visuals.linkColorScheme ? (
+ <Box
+ bgColor={visuals.linkColorScheme}
+ borderRadius="sm"
+ height={6}
+ width={6}
+ ></Box>
+ ) : (
+ <Flex height={6} width={6} flexDirection="column" flexWrap="wrap">
+ {visuals.nodeColorScheme.map((color) => (
+ <Box key={color} bgColor={color} flex="1 1 8px" borderRadius="2xl"></Box>
+ ))}
+ </Flex>
+ )}
+ </Box>
+ </MenuButton>
+ <Portal>
+ {' '}
+ <MenuList minW={10} zIndex="popover" bgColor="gray.200">
+ <MenuItem
+ onClick={() => setVisualsCallback({ ...visuals, linkColorScheme: '' })}
+ justifyContent="space-between"
+ alignItems="center"
+ display="flex"
+ >
+ <Flex height={6} width={6} flexDirection="column" flexWrap="wrap">
+ {visuals.nodeColorScheme.map((color) => (
+ <Box key={color} bgColor={color} flex="1 1 8px" borderRadius="2xl"></Box>
+ ))}
+ </Flex>
+ </MenuItem>
+ {colorList.map((color) => (
+ <MenuItem
+ key={color}
+ onClick={() =>
+ setVisualsCallback({
+ ...visuals,
+ linkColorScheme: color,
+ })
+ }
+ justifyContent="space-between"
+ alignItems="center"
+ display="flex"
+ >
+ <Box bgColor={color} borderRadius="sm" height={6} width={6}></Box>
+ </MenuItem>
+ ))}
+ </MenuList>
+ </Portal>
+ </Menu>
+ </Flex>
+ <Flex alignItems="center" justifyContent="space-between">
+ <Text>Accent</Text>
+ <Menu isLazy placement="right">
+ <MenuButton as={Button} colorScheme="" color="black" rightIcon={<ChevronDownIcon />}>
+ {<Box bgColor={highlightColor} borderRadius="sm" height={6} width={6}></Box>}
+ </MenuButton>
+ <Portal>
+ {' '}
+ <MenuList minW={10} zIndex="popover" bgColor="gray.200">
+ {colorList.map((color) => (
+ <MenuItem
+ key={color}
+ onClick={() => setHighlightColor(color)}
+ justifyContent="space-between"
+ alignItems="center"
+ display="flex"
+ >
+ <Box bgColor={color} borderRadius="sm" height={6} width={6}></Box>
+ </MenuItem>
+ ))}
+ </MenuList>
+ </Portal>
+ </Menu>
+ </Flex>
+ <ColorMenu
+ colorList={colorList}
+ label="Link highlight"
+ setVisuals={setVisualsCallback}
+ value="linkHighlight"
+ visValue={visuals.linkHighlight}
+ />
+ <ColorMenu
+ colorList={colorList}
+ label="Node highlight"
+ setVisuals={setVisualsCallback}
+ value="nodeHighlight"
+ visValue={visuals.nodeHighlight}
+ />
+ <ColorMenu
+ colorList={colorList}
+ label="Background"
+ setVisuals={setVisualsCallback}
+ value="backgroundColor"
+ visValue={visuals.backgroundColor}
+ />
+ <ColorMenu
+ colorList={colorList}
+ label="Emacs node"
+ setVisuals={setVisualsCallback}
+ value="emacsNodeColor"
+ visValue={visuals.emacsNodeColor}
+ />
+ </Box>
+ </VStack>
+ )
+}
diff --git a/components/Tweaks/Visual/GraphColorSelect.tsx b/components/Tweaks/Visual/GraphColorSelect.tsx
new file mode 100644
index 0000000..5b49672
--- /dev/null
+++ b/components/Tweaks/Visual/GraphColorSelect.tsx
@@ -0,0 +1,64 @@
+import React, { useContext } from 'react'
+import {
+ Box,
+ Button,
+ Flex,
+ Menu,
+ MenuButton,
+ MenuItem,
+ MenuList,
+ Portal,
+ Text,
+} from '@chakra-ui/react'
+import { ChevronDownIcon } from '@chakra-ui/icons'
+import { initialColoring } from '../../config'
+
+export interface GraphColorSelectProps {
+ coloring: typeof initialColoring
+ setColoring: any
+}
+
+export const GraphColorSelect = (props: GraphColorSelectProps) => {
+ type Theme = { [key: string]: string }
+ const { coloring, setColoring } = props
+ return (
+ <Flex alignItems="center" justifyContent="space-between" pl={7} pr={2}>
+ <Text>Graph coloring</Text>
+ <Menu isLazy placement="right">
+ <MenuButton
+ as={Button}
+ size="sm"
+ colorScheme=""
+ color="black"
+ rightIcon={<ChevronDownIcon />}
+ >
+ {coloring.method === 'degree' ? 'Links' : 'Communities'}
+ </MenuButton>
+ <Portal>
+ <MenuList minW={10} zIndex="popover" bgColor="gray.200">
+ <MenuItem
+ onClick={() =>
+ setColoring((curr: typeof initialColoring) => ({ ...curr, method: 'degree' }))
+ }
+ justifyContent="space-between"
+ alignItems="center"
+ display="flex"
+ >
+ Number of links
+ </MenuItem>
+ <MenuItem
+ onClick={() =>
+ setColoring((curr: typeof initialColoring) => ({ ...curr, method: 'community' }))
+ }
+ justifyContent="space-between"
+ alignItems="center"
+ display="flex"
+ >
+ Communities
+ </MenuItem>
+ </MenuList>
+ </Portal>
+ </Menu>
+ </Flex>
+ )
+}
diff --git a/components/Tweaks/Visual/HighlightingPanel.tsx b/components/Tweaks/Visual/HighlightingPanel.tsx
new file mode 100644
index 0000000..357d137
--- /dev/null
+++ b/components/Tweaks/Visual/HighlightingPanel.tsx
@@ -0,0 +1,116 @@
+import { Box, Select, StackDivider, VStack } from '@chakra-ui/react'
+import React from 'react'
+import { initialVisuals } from '../../config'
+import { EnableSection } from '../EnableSection'
+import { SliderWithInfo } from '../SliderWithInfo'
+
+export interface HighlightingPanelProps {
+ visuals: typeof initialVisuals
+ setVisuals: any
+}
+export const HighlightingPanel = (props: HighlightingPanelProps) => {
+ const { visuals, setVisuals } = props
+ return (
+ <VStack
+ spacing={2}
+ justifyContent="flex-start"
+ divider={<StackDivider borderColor="gray.500" />}
+ align="stretch"
+ color="gray.800"
+ >
+ <Box>
+ <EnableSection
+ label="Highlight"
+ onChange={() =>
+ setVisuals((visuals: typeof initialVisuals) => ({
+ ...visuals,
+ highlight: !visuals.highlight,
+ }))
+ }
+ value={visuals.highlight}
+ >
+ <VStack
+ spacing={1}
+ justifyContent="flex-start"
+ divider={<StackDivider borderColor="gray.400" />}
+ align="stretch"
+ paddingLeft={0}
+ >
+ <SliderWithInfo
+ label="Highlight Link Thickness"
+ value={visuals.highlightLinkSize}
+ onChange={(value) =>
+ setVisuals((visuals: typeof initialVisuals) => ({
+ ...visuals,
+ highlightLinkSize: value,
+ }))
+ }
+ />
+ <SliderWithInfo
+ label="Highlight Node Size"
+ value={visuals.highlightNodeSize}
+ onChange={(value) =>
+ setVisuals((visuals: typeof initialVisuals) => ({
+ ...visuals,
+ highlightNodeSize: value,
+ }))
+ }
+ />
+ <SliderWithInfo
+ min={0}
+ max={1}
+ label="Highlight Fade"
+ value={visuals.highlightFade}
+ onChange={(value) =>
+ setVisuals((visuals: typeof initialVisuals) => ({
+ ...visuals,
+ highlightFade: value,
+ }))
+ }
+ />
+ <EnableSection
+ label="Highlight Animation"
+ onChange={() => {
+ setVisuals((visuals: typeof initialVisuals) => ({
+ ...visuals,
+ highlightAnim: !visuals.highlightAnim,
+ }))
+ }}
+ value={visuals.highlightAnim}
+ >
+ <SliderWithInfo
+ label="Animation speed"
+ onChange={(v) =>
+ setVisuals((visuals: typeof initialVisuals) => ({
+ ...visuals,
+ animationSpeed: v,
+ }))
+ }
+ value={visuals.animationSpeed}
+ infoText="Slower speed has a chance of being buggy"
+ min={50}
+ max={1000}
+ step={10}
+ />
+ <Select
+ placeholder={visuals.algorithmName}
+ onChange={(v) => {
+ setVisuals((visuals: typeof initialVisuals) => ({
+ ...visuals,
+ algorithmName: v.target.value,
+ }))
+ }}
+ >
+ {visuals.algorithmOptions.map((opt: string) => (
+ <option key={opt} value={opt}>
+ {opt}
+ </option>
+ ))}
+ </Select>
+ </EnableSection>
+ </VStack>
+ </EnableSection>
+ </Box>
+ </VStack>
+ )
+}
diff --git a/components/Tweaks/Visual/LabelsPanel.tsx b/components/Tweaks/Visual/LabelsPanel.tsx
new file mode 100644
index 0000000..b0044f1
--- /dev/null
+++ b/components/Tweaks/Visual/LabelsPanel.tsx
@@ -0,0 +1,162 @@
+import { ChevronDownIcon } from '@chakra-ui/icons'
+import {
+ Box,
+ Button,
+ Collapse,
+ Flex,
+ Menu,
+ MenuButton,
+ MenuItem,
+ MenuList,
+ Portal,
+ StackDivider,
+ VStack,
+ Text,
+} from '@chakra-ui/react'
+import React from 'react'
+import { ColorMenu } from './ColorMenu'
+import { colorList, initialVisuals } from '../../config'
+import { SliderWithInfo } from '../SliderWithInfo'
+
+export interface LabelsPanelProps {
+ visuals: typeof initialVisuals
+ setVisuals: any
+}
+export const LabelsPanel = (props: LabelsPanelProps) => {
+ const { visuals, setVisuals } = props
+ return (
+ <VStack
+ spacing={2}
+ justifyContent="flex-start"
+ divider={<StackDivider borderColor="gray.400" />}
+ align="stretch"
+ color="gray.800"
+ >
+ <Flex alignItems="center" justifyContent="space-between">
+ <Text>Show labels</Text>
+ <Menu isLazy placement="right">
+ <MenuButton as={Button} colorScheme="" color="black" rightIcon={<ChevronDownIcon />}>
+ {!visuals.labels ? 'Never' : visuals.labels < 2 ? 'On Highlight' : 'Always'}
+ </MenuButton>
+ <Portal>
+ {' '}
+ <MenuList zIndex="popover" bgColor="gray.200">
+ <MenuItem onClick={() => setVisuals({ ...visuals, labels: 0 })}>Never</MenuItem>
+ <MenuItem onClick={() => setVisuals({ ...visuals, labels: 1 })}>
+ On Highlight
+ </MenuItem>
+ <MenuItem onClick={() => setVisuals({ ...visuals, labels: 2 })}>Always</MenuItem>
+ <MenuItem onClick={() => setVisuals({ ...visuals, labels: 3 })}>
+ Always (even in 3D)
+ </MenuItem>
+ </MenuList>
+ </Portal>
+ </Menu>
+ </Flex>
+ <Collapse in={visuals.labels > 1} animateOpacity>
+ <Box paddingTop={2}>
+ <SliderWithInfo
+ label="Label Appearance Scale"
+ value={visuals.labelScale * 2}
+ onChange={(value) => setVisuals({ ...visuals, labelScale: value / 2 })}
+ />
+ </Box>
+ <Box paddingTop={2}>
+ <SliderWithInfo
+ label="Label dynamicity"
+ infoText="By default, labels of nodes with more links will appear earlier than those with fewer. This slider changes the strength of this effect, put it at zero to disable it."
+ value={visuals.labelDynamicStrength}
+ min={0}
+ max={1}
+ step={0.05}
+ onChange={(value) =>
+ setVisuals((curr: typeof initialVisuals) => ({
+ ...curr,
+ labelDynamicStrength: value,
+ }))
+ }
+ />
+ <Collapse in={visuals.labelDynamicStrength > 0}>
+ <SliderWithInfo
+ label="Dynamic zoom degree cap"
+ infoText="The maximum number of links that is considered for the 'dynamic zoom effect'. Past this number all number of links are treated the same."
+ value={visuals.labelDynamicDegree}
+ min={1}
+ max={15}
+ step={1}
+ onChange={(value) =>
+ setVisuals((curr: typeof initialVisuals) => ({
+ ...curr,
+ labelDynamicDegree: value,
+ }))
+ }
+ />
+ </Collapse>
+ </Box>
+ </Collapse>
+ <ColorMenu
+ colorList={colorList}
+ label="Text"
+ setVisuals={setVisuals}
+ value="labelTextColor"
+ visValue={visuals.labelTextColor}
+ />
+ <Box>
+ <ColorMenu
+ colorList={colorList}
+ label="Background"
+ setVisuals={setVisuals}
+ value="labelBackgroundColor"
+ visValue={visuals.labelBackgroundColor}
+ />
+ <Collapse in={!!visuals.labelBackgroundColor} animateOpacity>
+ <Box paddingTop={2}>
+ <SliderWithInfo
+ label="Background opacity"
+ value={visuals.labelBackgroundOpacity}
+ onChange={(value) => {
+ console.log(visuals.labelBackgroundOpacity)
+ setVisuals({ ...visuals, labelBackgroundOpacity: value })
+ }}
+ min={0}
+ max={1}
+ step={0.01}
+ />
+ </Box>
+ </Collapse>
+ </Box>
+ <SliderWithInfo
+ label="Label font size"
+ value={visuals.labelFontSize}
+ min={5}
+ max={20}
+ step={0.5}
+ onChange={(value) => setVisuals({ ...visuals, labelFontSize: value })}
+ />
+ <SliderWithInfo
+ label="Max. label characters"
+ value={visuals.labelLength}
+ min={10}
+ max={100}
+ step={1}
+ onChange={(value) => setVisuals({ ...visuals, labelLength: value })}
+ />
+ <SliderWithInfo
+ label="Max. label line length"
+ value={visuals.labelWordWrap}
+ min={10}
+ max={100}
+ step={1}
+ onChange={(value) => setVisuals({ ...visuals, labelWordWrap: value })}
+ />
+ <SliderWithInfo
+ label="Space between label lines"
+ value={visuals.labelLineSpace}
+ min={0.2}
+ max={3}
+ step={0.1}
+ onChange={(value) => setVisuals({ ...visuals, labelLineSpace: value })}
+ />
+ </VStack>
+ )
+}
diff --git a/components/Tweaks/Visual/NodesNLinksPanel.tsx b/components/Tweaks/Visual/NodesNLinksPanel.tsx
new file mode 100644
index 0000000..40072fb
--- /dev/null
+++ b/components/Tweaks/Visual/NodesNLinksPanel.tsx
@@ -0,0 +1,131 @@
+import { Box, StackDivider, VStack } from '@chakra-ui/react'
+import React from 'react'
+import { ColorMenu } from './ColorMenu'
+import { colorList, initialVisuals } from '../../config'
+import { EnableSection } from '../EnableSection'
+import { SliderWithInfo } from '../SliderWithInfo'
+
+export interface NodesNLinksPanelProps {
+ visuals: typeof initialVisuals
+ setVisuals: any
+ threeDim: boolean
+}
+
+export const NodesNLinksPanel = (props: NodesNLinksPanelProps) => {
+ const { visuals, setVisuals, threeDim } = props
+ return (
+ <VStack
+ spacing={2}
+ justifyContent="flex-start"
+ divider={<StackDivider borderColor="gray.500" />}
+ align="stretch"
+ color="gray.800"
+ >
+ <Box>
+ <SliderWithInfo
+ label="Node size"
+ value={visuals.nodeRel}
+ onChange={(value) => setVisuals({ ...visuals, nodeRel: value })}
+ />
+ <SliderWithInfo
+ label="Node degree size multiplier"
+ value={visuals.nodeSizeLinks}
+ min={0}
+ max={2}
+ onChange={(value) => setVisuals({ ...visuals, nodeSizeLinks: value })}
+ />
+ <SliderWithInfo
+ label="Node zoom invariance"
+ value={visuals.nodeZoomSize}
+ min={0}
+ max={2}
+ infoText="How much the graph will try to keep the nodesize consistent across zoom scales. 0 is no consistency, node will always be their true size, 1 is linear, 2 is quadratic."
+ onChange={(value) =>
+ setVisuals((prev: typeof initialVisuals) => ({
+ ...prev,
+ nodeZoomSize: value,
+ }))
+ }
+ />
+ {threeDim && (
+ <>
+ <SliderWithInfo
+ label="Node opacity"
+ value={visuals.nodeOpacity}
+ min={0}
+ max={1}
+ onChange={(value) => setVisuals({ ...visuals, nodeOpacity: value })}
+ />
+ <SliderWithInfo
+ label="Node resolution"
+ value={visuals.nodeResolution}
+ min={5}
+ max={32}
+ step={1}
+ onChange={(value) => setVisuals({ ...visuals, nodeResolution: value })}
+ />
+ </>
+ )}
+ <SliderWithInfo
+ label="Link width"
+ value={visuals.linkWidth}
+ onChange={(value) => setVisuals({ ...visuals, linkWidth: value })}
+ />
+ {threeDim && (
+ <SliderWithInfo
+ label="Link opacity"
+ min={0}
+ max={1}
+ value={visuals.linkOpacity}
+ onChange={(value) => setVisuals({ ...visuals, linkOpacity: value })}
+ />
+ )}
+ <EnableSection
+ label="Link arrows"
+ value={visuals.arrows}
+ onChange={() => setVisuals({ ...visuals, arrows: !visuals.arrows })}
+ >
+ <SliderWithInfo
+ label="Arrow size"
+ value={visuals.arrowsLength / 10}
+ onChange={(value) => setVisuals({ ...visuals, arrowsLength: 10 * value })}
+ />
+ <SliderWithInfo
+ label="Arrow Position"
+ value={visuals.arrowsPos}
+ min={0}
+ max={1}
+ step={0.01}
+ onChange={(value) => setVisuals({ ...visuals, arrowsPos: value })}
+ />
+ <ColorMenu
+ colorList={colorList}
+ label="Arrow Color"
+ key="arrow"
+ setVisuals={setVisuals}
+ value="arrowsColor"
+ visValue={visuals.arrowsColor}
+ />
+ </EnableSection>
+ <EnableSection
+ label="Directional Particles"
+ value={visuals.particles}
+ onChange={() => setVisuals({ ...visuals, particles: !visuals.particles })}
+ >
+ <SliderWithInfo
+ label="Particle Number"
+ value={visuals.particlesNumber}
+ max={5}
+ step={1}
+ onChange={(value) => setVisuals({ ...visuals, particlesNumber: value })}
+ />
+ <SliderWithInfo
+ label="Particle Size"
+ value={visuals.particlesWidth}
+ onChange={(value) => setVisuals({ ...visuals, particlesWidth: value })}
+ />
+ </EnableSection>
+ </Box>
+ </VStack>
+ )
+}
diff --git a/components/Tweaks/Visual/ThemeSelect.tsx b/components/Tweaks/Visual/ThemeSelect.tsx
new file mode 100644
index 0000000..eed6daf
--- /dev/null
+++ b/components/Tweaks/Visual/ThemeSelect.tsx
@@ -0,0 +1,63 @@
+import React, { useContext } from 'react'
+import {
+ Box,
+ Button,
+ Flex,
+ Menu,
+ MenuButton,
+ MenuItem,
+ MenuList,
+ Portal,
+ Text,
+} from '@chakra-ui/react'
+
+import { themes } from '../../themes'
+import { ChevronDownIcon } from '@chakra-ui/icons'
+import { ThemeContext } from '../../../util/themecontext'
+
+export const ThemeSelect = () => {
+ type Theme = { [key: string]: string }
+ const { emacsTheme, setEmacsTheme, highlightColor } = useContext(ThemeContext)
+ return (
+ <Flex alignItems="center" justifyContent="space-between" pl={7} pr={2}>
+ <Text>Theme</Text>
+ <Menu isLazy placement="bottom" closeOnSelect={false}>
+ <MenuButton
+ as={Button}
+ size="sm"
+ colorScheme=""
+ color="black"
+ rightIcon={<ChevronDownIcon />}
+ >
+ {emacsTheme[0]}
+ </MenuButton>
+ <MenuList minW={10} zIndex="popover" bgColor="gray.200">
+ <MenuItem
+ onClick={() => ''}
+ justifyContent="space-between"
+ alignItems="center"
+ display="flex"
+ >
+ <Box height={6} width={6}></Box>
+ </MenuItem>
+ {Object.keys(themes).map((theme: string, i: number) => (
+ <MenuItem
+ key={theme}
+ onClick={() => setEmacsTheme([theme, themes[theme]])}
+ justifyContent="space-between"
+ alignItems="center"
+ display="flex"
+ >
+ <Text>{theme}</Text>
+ <Flex height={6} width={20} flexDirection="column" flexWrap="wrap">
+ {Object.values(themes[theme as string]).map((color: string) => {
+ return <Box key={color} bgColor={color} flex="1 1 8px"></Box>
+ })}
+ </Flex>
+ </MenuItem>
+ ))}
+ </MenuList>
+ </Menu>
+ </Flex>
+ )
+}
diff --git a/components/Tweaks/Visual/VisualsPanel.tsx b/components/Tweaks/Visual/VisualsPanel.tsx
new file mode 100644
index 0000000..82b365f
--- /dev/null
+++ b/components/Tweaks/Visual/VisualsPanel.tsx
@@ -0,0 +1,121 @@
+import {
+ Text,
+ Accordion,
+ AccordionButton,
+ AccordionItem,
+ Flex,
+ VStack,
+ AccordionIcon,
+ AccordionPanel,
+ MenuButton,
+ Menu,
+ Button,
+ Box,
+ Portal,
+ MenuList,
+ MenuItem,
+} from '@chakra-ui/react'
+import React, { useCallback } from 'react'
+import { HighlightingPanel } from './HighlightingPanel'
+import { ColorsPanel } from './ColorsPanel'
+import { initialColoring, initialVisuals } from '../../config'
+import { NodesNLinksPanel } from './NodesNLinksPanel'
+import { LabelsPanel } from './LabelsPanel'
+import { ThemeSelect } from './ThemeSelect'
+import { CitationsPanel } from '../NodesNLinks/CitationsPanel'
+import { GraphColorSelect } from './GraphColorSelect'
+
+export interface VisualsPanelProps {
+ visuals: typeof initialVisuals
+ setVisuals: any
+ highlightColor: string
+ setHighlightColor: any
+ threeDim: boolean
+ coloring: typeof initialColoring
+ setColoring: any
+}
+
+export const VisualsPanel = (props: VisualsPanelProps) => {
+ const {
+ coloring,
+ setColoring,
+ visuals,
+ setVisuals,
+ highlightColor,
+ setHighlightColor,
+ threeDim,
+ } = props
+ const setVisualsCallback = useCallback((val) => setVisuals(val), [])
+ return (
+ <VStack justifyContent="flex-start" align="stretch">
+ <ThemeSelect />
+ <GraphColorSelect {...{ coloring, setColoring }} />
+ <Accordion allowToggle defaultIndex={[0]} paddingLeft={3}>
+ <AccordionItem>
+ <AccordionButton>
+ <Flex justifyContent="space-between" w="100%">
+ <Text>Colors</Text>
+ <AccordionIcon marginRight={2} />
+ </Flex>
+ </AccordionButton>
+ <AccordionPanel>
+ <ColorsPanel
+ visuals={visuals}
+ setVisualsCallback={setVisualsCallback}
+ highlightColor={highlightColor}
+ setHighlightColor={setHighlightColor}
+ />
+ </AccordionPanel>
+ </AccordionItem>
+ <AccordionItem>
+ <AccordionButton>
+ <Flex justifyContent="space-between" w="100%">
+ <Text>Nodes & Links</Text>
+ <AccordionIcon marginRight={2} />
+ </Flex>
+ </AccordionButton>
+ <AccordionPanel>
+ <NodesNLinksPanel
+ visuals={visuals}
+ setVisuals={setVisualsCallback}
+ threeDim={threeDim}
+ />
+ </AccordionPanel>
+ </AccordionItem>
+ <AccordionItem>
+ <AccordionButton>
+ <Flex justifyContent="space-between" w="100%">
+ <Text>Labels</Text>
+ <AccordionIcon marginRight={2} />
+ </Flex>
+ </AccordionButton>
+ <AccordionPanel>
+ <LabelsPanel visuals={visuals} setVisuals={setVisualsCallback} />
+ </AccordionPanel>
+ </AccordionItem>
+ <AccordionItem>
+ <AccordionButton>
+ <Flex justifyContent="space-between" w="100%">
+ <Text>Highlighting</Text>
+ <AccordionIcon marginRight={2} />
+ </Flex>
+ </AccordionButton>
+ <AccordionPanel>
+ <HighlightingPanel visuals={visuals} setVisuals={setVisualsCallback} />
+ </AccordionPanel>
+ </AccordionItem>
+ <AccordionItem>
+ <AccordionButton>
+ <Flex justifyContent="space-between" w="100%">
+ <Text>Citations</Text>
+ <AccordionIcon marginRight={2} />
+ </Flex>
+ </AccordionButton>
+ <AccordionPanel>
+ <CitationsPanel visuals={visuals} setVisuals={setVisualsCallback} />
+ </AccordionPanel>
+ </AccordionItem>
+ </Accordion>
+ </VStack>
+ )
+}