Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(components): create new LabwareStackRender component #15842

Merged
merged 5 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 192 additions & 0 deletions components/src/hardware-sim/Labware/LabwareStackRender.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import * as React from 'react'
import { WellLabels, StaticLabware } from './labwareInternals'
import { LabwareAdapter } from './LabwareAdapter'
import { COLORS } from '../../helix-design-system'

import type { LabwareDefinition2 } from '@opentrons/shared-data'
import type { HighlightedWellLabels } from './labwareInternals/types'
import type { LabwareAdapterLoadName } from './LabwareAdapter'

export const WELL_LABEL_OPTIONS = {
SHOW_LABEL_INSIDE: 'SHOW_LABEL_INSIDE',
SHOW_LABEL_OUTSIDE: 'SHOW_LABEL_OUTSIDE',
} as const

export type WellLabelOption = keyof typeof WELL_LABEL_OPTIONS

const HIGHLIGHT_COLOR = COLORS.blue30
const STROKE_WIDTH = 1
const SKEW_ANGLE_DEGREES = 30
const SKEW_ANGLE_RADIANS = (SKEW_ANGLE_DEGREES * Math.PI) / 180
const COSINE_SKEW_ANGLE = Math.cos(SKEW_ANGLE_RADIANS)

export interface LabwareRenderProps {
/** Labware definitions in stack to render */
definitionTop: LabwareDefinition2
/** option to highlight well labels with specified color */
highlightedWellLabels?: HighlightedWellLabels
/** highlight top labware */
highlightTop: boolean
/** highlight bottom labware if it exists */
highlightBottom: boolean
gRef?: React.RefObject<SVGGElement>
definitionBottom?: LabwareDefinition2
shouldRotateAdapterOrientation?: boolean
/** option to show well labels inside or outside of labware outline */
wellLabelOption?: WellLabelOption
}

export const LabwareRender = (props: LabwareRenderProps): JSX.Element => {
const {
gRef,
definitionTop,
definitionBottom,
highlightTop,
wellLabelOption,
shouldRotateAdapterOrientation,
highlightBottom = false,
} = props

const labwareLoadNameTop = definitionTop.parameters.loadName
const fillColorTop = highlightTop ? HIGHLIGHT_COLOR : COLORS.white
const fillColorBottom = highlightBottom ? HIGHLIGHT_COLOR : COLORS.white

// only one labware (top)
if (definitionBottom == null) {
const { xDimension, yDimension } = definitionTop.dimensions
const isTopAdapter = definitionTop.metadata.displayCategory === 'adapter'

return isTopAdapter ? (
// adapter render
<g
transform={
shouldRotateAdapterOrientation
? `rotate(180, ${xDimension / 2}, ${yDimension / 2})`
: 'rotate(0, 0, 0)'
}
>
<g>
<LabwareAdapter
labwareLoadName={labwareLoadNameTop as LabwareAdapterLoadName}
/>
</g>
</g>
) : (
// isometric view of labware
<svg>
<g
transform={`translate(55, 28) rotate(SKEW_ANGLE_DEGREES) skewX(-${SKEW_ANGLE_DEGREES}) scale(${COSINE_SKEW_ANGLE}, ${COSINE_SKEW_ANGLE})`}
ref={gRef}
>
<StaticLabware definition={definitionTop} fill={fillColorBottom} />
{wellLabelOption != null ? (
<WellLabels
definition={definitionTop}
wellLabelOption={wellLabelOption}
wellLabelColor={fillColorBottom}
highlightedWellLabels={props.highlightedWellLabels}
/>
) : null}
</g>
<rect
width={definitionTop.dimensions.yDimension - STROKE_WIDTH}
height={definitionTop.dimensions.zDimension - STROKE_WIDTH}
transform={`translate(55, 28) rotate(180) skewY(-${SKEW_ANGLE_DEGREES}) scale(${COSINE_SKEW_ANGLE}, ${COSINE_SKEW_ANGLE})`}
strokeWidth={STROKE_WIDTH}
stroke={COLORS.black90}
fill={fillColorTop}
/>
<rect
width={definitionTop.dimensions.xDimension - STROKE_WIDTH}
height={definitionTop.dimensions.zDimension - STROKE_WIDTH}
transform={`translate(55, 28) skewY(${SKEW_ANGLE_DEGREES}) scale(${
COSINE_SKEW_ANGLE * 0.5
}, -${COSINE_SKEW_ANGLE}) `}
strokeWidth={STROKE_WIDTH}
stroke={COLORS.black90}
fill={fillColorTop}
/>
</svg>
)
}

return (
<svg>
{/* bottom labware/adapter */}
<g
transform={`translate(55, ${
28 - definitionTop.dimensions.zDimension * 0.5 - 10
}) rotate(${SKEW_ANGLE_DEGREES}) skewX(-${SKEW_ANGLE_DEGREES}) scale(${COSINE_SKEW_ANGLE}, ${COSINE_SKEW_ANGLE})`}
ref={gRef}
fill={fillColorBottom}
>
<StaticLabware definition={definitionTop} fill={fillColorBottom} />
{wellLabelOption != null &&
definitionTop.metadata.displayCategory !== 'adapter' ? (
<WellLabels
definition={definitionTop}
wellLabelOption={wellLabelOption}
wellLabelColor={fillColorBottom}
highlightedWellLabels={props.highlightedWellLabels}
/>
) : null}
</g>
<rect
width={definitionTop.dimensions.yDimension - STROKE_WIDTH}
height={definitionTop.dimensions.zDimension - STROKE_WIDTH}
transform={`translate(55, ${
28 - definitionTop.dimensions.zDimension * 0.5 - 10
}) rotate(180) skewY(-${SKEW_ANGLE_DEGREES}) scale(${COSINE_SKEW_ANGLE}, ${COSINE_SKEW_ANGLE})`}
strokeWidth={STROKE_WIDTH}
stroke={COLORS.black90}
fill={fillColorBottom}
/>
<rect
width={definitionTop.dimensions.xDimension - STROKE_WIDTH}
height={definitionTop.dimensions.zDimension - STROKE_WIDTH}
transform={`translate(55, ${
28 - definitionTop.dimensions.zDimension * 0.5 - 10
}) skewY(${SKEW_ANGLE_DEGREES}) scale(${
COSINE_SKEW_ANGLE * 0.5
}, -${COSINE_SKEW_ANGLE}) `}
strokeWidth={STROKE_WIDTH}
stroke={COLORS.black90}
fill={fillColorBottom}
/>
{/* top labware/adapter */}
<g
transform={`translate(55, 28) rotate(${SKEW_ANGLE_DEGREES}) skewX(-${SKEW_ANGLE_DEGREES}) scale(${COSINE_SKEW_ANGLE}, ${COSINE_SKEW_ANGLE})`}
ref={gRef}
>
<StaticLabware definition={definitionTop} fill={fillColorTop} />
{wellLabelOption != null &&
definitionTop.metadata.displayCategory !== 'adapter' ? (
<WellLabels
definition={definitionTop}
wellLabelOption={wellLabelOption}
wellLabelColor={fillColorTop}
highlightedWellLabels={props.highlightedWellLabels}
/>
) : null}
</g>
<rect
width={definitionTop.dimensions.yDimension - STROKE_WIDTH}
height={definitionTop.dimensions.zDimension - STROKE_WIDTH}
transform={`translate(55, 28) rotate(180) skewY(-${SKEW_ANGLE_DEGREES}) scale(${COSINE_SKEW_ANGLE}, ${COSINE_SKEW_ANGLE})`}
strokeWidth={STROKE_WIDTH}
stroke={COLORS.black90}
fill={fillColorTop}
/>
<rect
width={definitionTop.dimensions.xDimension - STROKE_WIDTH}
height={definitionTop.dimensions.zDimension - STROKE_WIDTH}
transform={`translate(55, 28) skewY(${SKEW_ANGLE_DEGREES}) scale(${
COSINE_SKEW_ANGLE * 0.5
}, -${COSINE_SKEW_ANGLE}) `}
strokeWidth={STROKE_WIDTH}
stroke={COLORS.black90}
fill={fillColorTop}
/>
</svg>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface LabwareOutlineProps {
highlight?: boolean
/** [legacy] override the border color */
stroke?: CSSProperties['stroke']
fill?: CSSProperties['fill']
}

const OUTLINE_THICKNESS_MM = 1
Expand All @@ -30,13 +31,19 @@ export function LabwareOutline(props: LabwareOutlineProps): JSX.Element {
isTiprack = false,
highlight = false,
stroke,
fill,
} = props
const {
parameters = { isTiprack },
dimensions = { xDimension: width, yDimension: height },
} = definition ?? {}

const backgroundFill = parameters.isTiprack ? '#CCCCCC' : COLORS.white
let backgroundFill
if (fill != null) {
backgroundFill = fill
} else {
backgroundFill = parameters.isTiprack ? '#CCCCCC' : COLORS.white
}
return (
<>
{highlight ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import flatMap from 'lodash/flatMap'

import { LabwareOutline } from './LabwareOutline'
import { Well } from './Well'
import { STYLE_BY_WELL_CONTENTS } from './StyledWells'
import { COLORS } from '../../../helix-design-system'

import type { LabwareDefinition2, LabwareWell } from '@opentrons/shared-data'
import type { WellMouseEvent } from './types'
import { STYLE_BY_WELL_CONTENTS } from './StyledWells'
import { COLORS } from '../../../helix-design-system'
import type { CSSProperties } from 'styled-components'

export interface StaticLabwareProps {
/** Labware definition to render */
Expand All @@ -22,6 +23,7 @@ export interface StaticLabwareProps {
onMouseEnterWell?: (e: WellMouseEvent) => unknown
/** Optional callback to be executed when mouse leaves a well element */
onMouseLeaveWell?: (e: WellMouseEvent) => unknown
fill?: CSSProperties['fill']
}

const TipDecoration = React.memo(function TipDecoration(props: {
Expand Down Expand Up @@ -55,13 +57,18 @@ export function StaticLabwareComponent(props: StaticLabwareProps): JSX.Element {
onLabwareClick,
onMouseEnterWell,
onMouseLeaveWell,
fill,
} = props

const { isTiprack } = definition.parameters
return (
<g onClick={onLabwareClick}>
<LabwareDetailGroup>
<LabwareOutline definition={definition} highlight={highlight} />
<LabwareOutline
definition={definition}
highlight={highlight}
fill={fill}
/>
</LabwareDetailGroup>
<g>
{flatMap(
Expand All @@ -78,6 +85,7 @@ export function StaticLabwareComponent(props: StaticLabwareProps): JSX.Element {
{...(isTiprack
? STYLE_BY_WELL_CONTENTS.tipPresent
: STYLE_BY_WELL_CONTENTS.defaultWell)}
fill={fill}
/>

{isTiprack ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ export function WellComponent(props: WellProps): JSX.Element {
wellName,
stroke = COLORS.black90,
strokeWidth = 1,
fill = COLORS.white,
fill,
onMouseEnterWell,
onMouseLeaveWell,
isInteractive = onMouseEnterWell != null || onMouseLeaveWell != null,
} = props
const { x, y } = well

const wellFill = fill ?? COLORS.white

const pointerEvents: React.CSSProperties['pointerEvents'] = isInteractive
? 'auto'
: 'none'
Expand All @@ -46,7 +48,7 @@ export function WellComponent(props: WellProps): JSX.Element {
onMouseLeaveWell != null
? (event: React.MouseEvent) => onMouseLeaveWell({ wellName, event })
: undefined,
style: { pointerEvents, stroke, strokeWidth, fill },
style: { pointerEvents, stroke, strokeWidth, fill: wellFill },
}

if (well.shape === 'circular') {
Expand Down
Loading