Skip to content

Commit

Permalink
feat: add class based color settings
Browse files Browse the repository at this point in the history
  • Loading branch information
pedrokohler committed Jun 10, 2024
1 parent 33ff820 commit 4a1ce3a
Show file tree
Hide file tree
Showing 5 changed files with 341 additions and 10 deletions.
67 changes: 64 additions & 3 deletions src/components/AnnotationCategoryItem.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
import React from 'react'
import { Menu, Space, Checkbox, Tooltip } from 'antd'
import { Menu, Space, Checkbox, Tooltip, Popover, Button } from 'antd'
import { SettingOutlined } from '@ant-design/icons'
import { Category, Type } from './AnnotationCategoryList'
import ColorSettingsMenu from './ColorSettingsMenu'

const AnnotationGroupItem = ({
category,
onChange,
checkedAnnotationGroupUids
checkedAnnotationGroupUids,
onStyleChange,
defaultAnnotationGroupStyles
}: {
category: Category
onChange: Function
onStyleChange: Function
defaultAnnotationGroupStyles: {
[annotationGroupUID: string]: {
opacity: number
color: number[]
}
}
checkedAnnotationGroupUids: Set<string>
}): JSX.Element => {
const { types } = category
Expand Down Expand Up @@ -60,6 +71,32 @@ const AnnotationGroupItem = ({
>
{category.CodeMeaning}
</Tooltip>
<Popover
placement='left'
overlayStyle={{ width: '350px' }}
title='Display Settings'
content={() => (
<ColorSettingsMenu
annotationGroupsUIDs={types.reduce(
(acc: string[], type) => {
return [...acc, ...type.uids]
},
[]
)}
onStyleChange={onStyleChange}
defaultStyle={
defaultAnnotationGroupStyles[types[0].uids[0]]
}
/>
)}
>
<Button
type='primary'
shape='circle'
style={{ marginLeft: '10px' }}
icon={<SettingOutlined />}
/>
</Popover>
</Checkbox>
</Space>
{types.map((type: Type) => {
Expand All @@ -72,7 +109,10 @@ const AnnotationGroupItem = ({
!isChecked &&
uids.some((uid: string) => checkedAnnotationGroupUids.has(uid))
return (
<div key={`${type.CodingSchemeDesignator}:${type.CodeMeaning}`} style={{ paddingLeft: '25px' }}>
<div
key={`${type.CodingSchemeDesignator}:${type.CodeMeaning}`}
style={{ paddingLeft: '25px' }}
>
<Checkbox
indeterminate={indeterminateType}
checked={isChecked}
Expand All @@ -88,6 +128,27 @@ const AnnotationGroupItem = ({
>
{CodeMeaning}
</Tooltip>
<Popover
placement='left'
overlayStyle={{ width: '350px' }}
title='Display Settings'
content={() => (
<ColorSettingsMenu
annotationGroupsUIDs={type.uids}
onStyleChange={onStyleChange}
defaultStyle={
defaultAnnotationGroupStyles[type.uids[0]]
}
/>
)}
>
<Button
type='primary'
shape='circle'
style={{ marginLeft: '10px' }}
icon={<SettingOutlined />}
/>
</Popover>
</Checkbox>
</div>
)
Expand Down
11 changes: 11 additions & 0 deletions src/components/AnnotationCategoryList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,19 @@ const getCategories = (annotationGroups: any): Record<string, Category> => {
const AnnotationCategoryList = ({
annotationGroups,
onChange,
onStyleChange,
defaultAnnotationGroupStyles,
checkedAnnotationGroupUids
}: {
annotationGroups: dmv.annotation.AnnotationGroup[]
onChange: Function
onStyleChange: Function
defaultAnnotationGroupStyles: {
[annotationGroupUID: string]: {
opacity: number
color: number[]
}
}
checkedAnnotationGroupUids: Set<string>
}): JSX.Element => {
const categories: Record<string, Category> = getCategories(annotationGroups)
Expand All @@ -84,6 +93,8 @@ const AnnotationCategoryList = ({
key={category.CodeMeaning}
category={category}
onChange={onChange}
onStyleChange={onStyleChange}
defaultAnnotationGroupStyles={defaultAnnotationGroupStyles}
checkedAnnotationGroupUids={checkedAnnotationGroupUids}
/>
)
Expand Down
257 changes: 257 additions & 0 deletions src/components/ColorSettingsMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
import React from 'react'
import { Col, Divider, InputNumber, Row, Slider } from 'antd'

interface ColorSettingsMenuProps {
annotationGroupsUIDs: string[]
defaultStyle: {
opacity: number
color: number[]
}
onStyleChange: Function
}

interface ColorSettingsMenuState {
currentStyle: {
opacity: number
color?: number[]
}
}

/**
* React component representing an Annotation Group.
*/
class ColorSettingsMenu extends React.Component<
ColorSettingsMenuProps,
ColorSettingsMenuState
> {
constructor (props: ColorSettingsMenuProps) {
super(props)
this.handleOpacityChange = this.handleOpacityChange.bind(this)
this.handleColorRChange = this.handleColorRChange.bind(this)
this.handleColorGChange = this.handleColorGChange.bind(this)
this.handleColorBChange = this.handleColorBChange.bind(this)
this.getCurrentColor = this.getCurrentColor.bind(this)
this.state = {
currentStyle: {
opacity: this.props.defaultStyle.opacity,
color: this.props.defaultStyle.color
}
}
}

handleOpacityChange (value: number | null): void {
if (value != null) {
this.props.annotationGroupsUIDs.forEach((uid) => {
this.props.onStyleChange({
annotationGroupUID: uid,
styleOptions: {
opacity: value
}
})
})
this.setState({
currentStyle: {
opacity: value,
color: this.state.currentStyle.color
}
})
}
}

handleColorRChange (value: number | number[] | null): void {
if (value != null && this.state.currentStyle.color !== undefined) {
const color = [
Array.isArray(value) ? value[0] : value,
this.state.currentStyle.color[1],
this.state.currentStyle.color[2]
]
this.setState((state) => ({
currentStyle: {
color: color,
opacity: state.currentStyle.opacity
}
}))
this.props.annotationGroupsUIDs.forEach((uid) => {
this.props.onStyleChange({
annotationGroupUID: uid,
styleOptions: { color: color }
})
})
}
}

handleColorGChange (value: number | number[] | null): void {
if (value != null && this.state.currentStyle.color !== undefined) {
const color = [
this.state.currentStyle.color[0],
Array.isArray(value) ? value[0] : value,
this.state.currentStyle.color[2]
]
this.setState((state) => ({
currentStyle: {
color: color,
opacity: state.currentStyle.opacity
}
}))
this.props.annotationGroupsUIDs.forEach((uid) => {
this.props.onStyleChange({
annotationGroupUID: uid,
styleOptions: { color: color }
})
})
}
}

handleColorBChange (value: number | number[] | null): void {
if (value != null && this.state.currentStyle.color !== undefined) {
const color = [
this.state.currentStyle.color[0],
this.state.currentStyle.color[1],
Array.isArray(value) ? value[0] : value
]
this.setState((state) => ({
currentStyle: {
color: color,
opacity: state.currentStyle.opacity
}
}))

this.props.annotationGroupsUIDs.forEach((uid) => {
this.props.onStyleChange({
annotationGroupUID: uid,
styleOptions: { color: color }
})
})
}
}

getCurrentColor (): string {
const rgb2hex = (values: number[]): string => {
const r = values[0]
const g = values[1]
const b = values[2]
return '#' + (0x1000000 + (r << 16) + (g << 8) + b).toString(16).slice(1)
}

if (this.state.currentStyle.color != null) {
return rgb2hex(this.state.currentStyle.color)
} else {
return 'white'
}
}

render (): React.ReactNode {
let colorSettings
if (this.state.currentStyle.color != null) {
colorSettings = (
<>
<Divider plain>Color</Divider>
<Row justify='center' align='middle' gutter={[8, 8]}>
<Col span={5}>Red</Col>
<Col span={14}>
<Slider
range={false}
min={0}
max={255}
step={1}
value={this.state.currentStyle.color[0]}
onChange={this.handleColorRChange}
/>
</Col>
<Col span={5}>
<InputNumber
min={0}
max={255}
size='small'
style={{ width: '65px' }}
value={this.state.currentStyle.color[0]}
onChange={this.handleColorRChange}
/>
</Col>
</Row>

<Row justify='center' align='middle' gutter={[8, 8]}>
<Col span={5}>Green</Col>
<Col span={14}>
<Slider
range={false}
min={0}
max={255}
step={1}
value={this.state.currentStyle.color[1]}
onChange={this.handleColorGChange}
/>
</Col>
<Col span={5}>
<InputNumber
min={0}
max={255}
size='small'
style={{ width: '65px' }}
value={this.state.currentStyle.color[1]}
onChange={this.handleColorGChange}
/>
</Col>
</Row>

<Row justify='center' align='middle' gutter={[8, 8]}>
<Col span={5}>Blue</Col>
<Col span={14}>
<Slider
range={false}
min={0}
max={255}
step={1}
value={this.state.currentStyle.color[2]}
onChange={this.handleColorBChange}
/>
</Col>
<Col span={5}>
<InputNumber
min={0}
max={255}
size='small'
style={{ width: '65px' }}
value={this.state.currentStyle.color[2]}
onChange={this.handleColorBChange}
/>
</Col>
</Row>
<Divider plain />
</>
)
}

return (
<div>
{colorSettings}
<Row justify='start' align='middle' gutter={[8, 8]}>
<Col span={6}>Opacity</Col>
<Col span={12}>
<Slider
range={false}
min={0}
max={1}
step={0.01}
value={this.state.currentStyle.opacity}
onChange={this.handleOpacityChange}
/>
</Col>
<Col span={6}>
<InputNumber
min={0}
max={1}
size='small'
step={0.1}
style={{ width: '65px' }}
value={this.state.currentStyle.opacity}
onChange={this.handleOpacityChange}
/>
</Col>
</Row>
</div>
)
}
}

export default ColorSettingsMenu
2 changes: 1 addition & 1 deletion src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class Header extends React.Component<HeaderProps, HeaderState> {
this.setState(state => ({
...state,
errorObj: [...state.errorObj, { ...error, source }],
errorCategory: [...state.errorCategory, error.type],
errorCategory: [...state.errorCategory, error.type]
}))
}

Expand Down
Loading

0 comments on commit 4a1ce3a

Please sign in to comment.