Repository: arayabrain/barebone-studio
Pull request #402: Roi time series select colour change
Author: login: milesAraya
Labels: name: enhancement
description: New feature or request
Created: 2024-08-05T05:45:36Z | Merged: 2025-03-14T08:44:11Z

[Issue 359](https://github.com/arayabrain/barebone-studio/issues/359)と[Optinist repo Issue 602](https://github.com/oist/optinist/issues/602)の解決です。

ROI が選択されると色が白に変わり、Fluorescence plotがリンクされていればtime seriesがプロットされます。
Legend ROI が選択されると ROI は白くなります。 
ROI を選択解除にするとtime seriesは消えます。Legend ROIを選択解除すると ROI は通常の色に戻ります。

既存の temp_merge_roi フレームワークと同様に temp_selected_roi を使用することで実現。

When ROI are selected, they change colour to white, and if fluorescence window is linked time-series is plotted.
When the legend ROI is selected the ROI turns white.  
When ROI unselected the time series disappears. When the legend is unselected the ROI returns to normal colour. 

Achieved through using a temp_selected_roi, similar to the existing temp_merge_roi framework. 




Diff:
diff --git a/frontend/src/components/Workspace/FlowChart/Dialog/AlgorithmOutputDialog.tsx b/frontend/src/components/Workspace/FlowChart/Dialog/AlgorithmOutputDialog.tsx
index 7d4eb6a63..23a2c3829 100644
--- a/frontend/src/components/Workspace/FlowChart/Dialog/AlgorithmOutputDialog.tsx
+++ b/frontend/src/components/Workspace/FlowChart/Dialog/AlgorithmOutputDialog.tsx
@@ -59,7 +59,7 @@ export const AlgorithmOutputDialog = memo(function AlgorithmOutputDialog({
         nodeId={nodeId}
       />
       <DialogContent dividers sx={{ pt: 1, px: 2 }}>
-        <BoxFilterProvider>
+        <BoxFilterProvider nodeId={nodeId}>
           {open && <OutputViewer nodeId={nodeId} />}
           {open && dialogFilterNodeId ? <BoxFilter nodeId={nodeId} /> : null}
         </BoxFilterProvider>
diff --git a/frontend/src/components/Workspace/FlowChart/Dialog/BoxFilter.tsx b/frontend/src/components/Workspace/FlowChart/Dialog/BoxFilter.tsx
index badf4039b..c74a6f5e5 100644
--- a/frontend/src/components/Workspace/FlowChart/Dialog/BoxFilter.tsx
+++ b/frontend/src/components/Workspace/FlowChart/Dialog/BoxFilter.tsx
@@ -186,10 +186,6 @@ const BoxFilter = ({ nodeId }: { nodeId: string }) => {
     return { dim1: dim1?.filter(Boolean), roi: roi?.filter(Boolean) }
   }, [filterParam])
 
-  useEffect(() => {
-    setFilterParam(filterSelector)
-  }, [filterSelector, setFilterParam])
-
   const getData = useCallback(
     (value?: TDim[]) =>
       value
diff --git a/frontend/src/components/Workspace/FlowChart/Dialog/FilterContext.tsx b/frontend/src/components/Workspace/FlowChart/Dialog/FilterContext.tsx
index 765aac523..d1619451d 100644
--- a/frontend/src/components/Workspace/FlowChart/Dialog/FilterContext.tsx
+++ b/frontend/src/components/Workspace/FlowChart/Dialog/FilterContext.tsx
@@ -4,32 +4,61 @@ import {
   PropsWithChildren,
   SetStateAction,
   useContext,
+  useEffect,
+  useMemo,
   useState,
 } from "react"
+import { useSelector, useDispatch, shallowEqual } from "react-redux"
 
+import { DialogContext } from "components/Workspace/FlowChart/Dialog/DialogContext"
+import { selectAlgorithmDataFilterParam } from "store/slice/AlgorithmNode/AlgorithmNodeSelectors"
 import { TDataFilterParam } from "store/slice/AlgorithmNode/AlgorithmNodeType"
+import { getRoiData } from "store/slice/DisplayData/DisplayDataActions"
+import { selectOutputFilePathCellRoi } from "store/slice/Pipeline/PipelineSelectors"
+import { selectCurrentWorkspaceId } from "store/slice/Workspace/WorkspaceSelector"
+import { AppDispatch } from "store/store"
 
 const BoxFilterContext = createContext<{
   filterParam?: TDataFilterParam
-  setFilterParam: Dispatch<SetStateAction<TDataFilterParam | undefined>>
-  setRoiPath: Dispatch<SetStateAction<string>>
   roiPath: string
+  setFilterParam: Dispatch<SetStateAction<TDataFilterParam | undefined>>
 }>({
   filterParam: undefined,
   setFilterParam: () => null,
   roiPath: "",
-  setRoiPath: () => null,
 })
 
 export const useBoxFilter = () => useContext(BoxFilterContext)
 
-export const BoxFilterProvider = ({ children }: PropsWithChildren) => {
-  const [filterParam, setFilterParam] = useState<TDataFilterParam>()
-  const [roiPath, setRoiPath] = useState<string>("")
+export const BoxFilterProvider = ({
+  children,
+  nodeId,
+}: PropsWithChildren<{ nodeId: string }>) => {
+  const filterSelector = useSelector(
+    selectAlgorithmDataFilterParam(nodeId),
+    shallowEqual,
+  )
+  const [filterParam, setFilterParam] = useState<TDataFilterParam | undefined>(
+    filterSelector,
+  )
+  const { isOutput } = useContext(DialogContext)
+  const dispatch = useDispatch<AppDispatch>()
+  const workspaceId = useSelector(selectCurrentWorkspaceId)
+  const isExistFilterRoi = useMemo(
+    () => filterParam?.roi?.length,
+    [filterParam?.roi?.length],
+  )
+  const path = useSelector(selectOutputFilePathCellRoi(nodeId))
+
+  useEffect(() => {
+    if (isOutput && path && isExistFilterRoi && workspaceId) {
+      dispatch(getRoiData({ workspaceId, path }))
+    }
+  }, [dispatch, path, isExistFilterRoi, isOutput, workspaceId])
 
   return (
     <BoxFilterContext.Provider
-      value={{ filterParam, setFilterParam, roiPath, setRoiPath }}
+      value={{ filterParam, setFilterParam, roiPath: path }}
     >
       {children}
     </BoxFilterContext.Provider>
diff --git a/frontend/src/components/Workspace/Visualize/Editor/TimeSeriesItemEditor.tsx b/frontend/src/components/Workspace/Visualize/Editor/TimeSeriesItemEditor.tsx
index 3871f6ec0..02074e487 100644
--- a/frontend/src/components/Workspace/Visualize/Editor/TimeSeriesItemEditor.tsx
+++ b/frontend/src/components/Workspace/Visualize/Editor/TimeSeriesItemEditor.tsx
@@ -50,6 +50,7 @@ import {
   setTimeSeriesItemZeroLine,
   setTimeSeriesItemDrawOrderList,
   changeRangeUnit,
+  setClickedData,
 } from "store/slice/VisualizeItem/VisualizeItemSlice"
 import { AppDispatch } from "store/store"
 import { arrayEqualityFn } from "utils/EqualityUtils"
@@ -290,6 +291,14 @@ const LegendSelect: FC = () => {
         drawOrderList: newDrawOrderList,
       }),
     )
+    dispatch(
+      setClickedData({
+        itemId,
+        clickedDataId: event.target.checked ? index : null,
+      }),
+    ),
+      // eslint-disable-next-line no-console
+      console.log("#############\n In TimeSeriesItemEditor.tsx, clickedDataId")
 
     if (filePath !== null) {
       dispatch(getTimeSeriesDataById({ path: filePath, index: index }))
diff --git a/frontend/src/components/Workspace/Visualize/Plot/ImagePlot.tsx b/frontend/src/components/Workspace/Visualize/Plot/ImagePlot.tsx
index 020a2ed7a..38f3f0938 100644
--- a/frontend/src/components/Workspace/Visualize/Plot/ImagePlot.tsx
+++ b/frontend/src/components/Workspace/Visualize/Plot/ImagePlot.tsx
@@ -31,16 +31,17 @@ import { styled } from "@mui/material/styles"
 import Switch from "@mui/material/Switch"
 
 import { DisplayDataContext } from "components/Workspace/Visualize/DataContext"
+import { useVisualize } from "components/Workspace/Visualize/VisualizeContext"
 import {
   addRoi,
   cancelRoi,
   commitRoi,
   deleteRoi,
+  mergeRoi,
   getImageData,
   getRoiData,
   getStatus,
   getTimeSeriesInitData,
-  mergeRoi,
 } from "store/slice/DisplayData/DisplayDataActions"
 import {
   selectImageDataError,
@@ -55,7 +56,7 @@ import {
 } from "store/slice/DisplayData/DisplayDataSelectors"
 import {
   selectingImageArea,
-  setImageItemClikedDataId,
+  setImageItemClickedDataId,
 } from "store/slice/VisualizeItem/VisualizeItemActions"
 import {
   selectImageItemShowticklabels,
@@ -75,7 +76,6 @@ import {
   selectVisualizeSaveFilename,
   selectVisualizeSaveFormat,
   selectImageItemAlpha,
-  selectRoiItemOutputKeys,
   selectVisualizeItems,
   selectImageItemShowRoiLabels,
 } from "store/slice/VisualizeItem/VisualizeItemSelectors"
@@ -91,12 +91,6 @@ import { setDataCancel } from "store/slice/Workspace/WorkspaceSlice"
 import { AppDispatch, RootState } from "store/store"
 import { twoDimarrayEqualityFn } from "utils/EqualityUtils"
 
-interface PointClick {
-  x: number
-  y: number
-  z: number
-}
-
 export type StatusROI = {
   temp_add_roi: number[]
   temp_delete_roi: number[]
@@ -209,8 +203,6 @@ const ImagePlotChart = memo(function ImagePlotChart({
   )
 
   const [roiDataState, setRoiDataState] = useState(roiData)
-  const [pointClick, setPointClick] = useState<PointClick[]>([])
-
   const itemsVisual = useSelector(selectVisualizeItems)
   const showticklabels = useSelector(selectImageItemShowticklabels(itemId))
   const showline = useSelector(selectImageItemShowLine(itemId))
@@ -222,23 +214,39 @@ const ImagePlotChart = memo(function ImagePlotChart({
   const timeDataMaxIndex = useSelector(selectRoiItemIndex(itemId, roiFilePath))
   const width = useSelector(selectVisualizeItemWidth(itemId))
   const height = useSelector(selectVisualizeItemHeight(itemId))
-  const statusRoi = useSelector(selectStatusRoi)
   const [sizeDrag, setSizeDrag] = useState(initSizeDrag)
   const [startDragAddRoi, setStartDragAddRoi] = useState(false)
   const [action, setAction] = useState("")
   const [positionDrag, setChangeSize] = useState<PositionDrag | undefined>()
   const showRoiLabels = useSelector(selectImageItemShowRoiLabels(itemId))
 
-  const outputKey: string | null = useSelector(selectRoiItemOutputKeys(itemId))
+  const allowEditRoi = useMemo(
+    () => roiFilePath?.includes(CELL_ROI),
+    [roiFilePath],
+  )
+  const { setRoisClick, roisClick, resetRoisClick } = useVisualize()
+  const roiClicked = useMemo(() => roisClick[itemId] || [], [itemId, roisClick])
+
+  const selectedStatus = useSelector(selectStatusRoi)
+
+  const statusRoi = useMemo(() => {
+    return (
+      selectedStatus || {
+        temp_add_roi: [],
+        temp_delete_roi: [],
+        temp_merge_roi: [],
+      }
+    )
+  }, [selectedStatus])
 
   const refPageXSize = useRef(0)
   const refPageYSize = useRef(0)
 
-  function getRoiColor(roiIndex: number): string {
+  function getRoiColor(roiIndex: number): number[] {
     const colors = createColormap({
       colormap: "jet",
       nshades: 200,
-      format: "hex",
+      format: "rgba",
     })
     return colors[(Math.abs(roiIndex) * 9) % 200]
   }
@@ -307,38 +315,30 @@ const ImagePlotChart = memo(function ImagePlotChart({
         // hoverinfo: isAddRoi || pointClick.length ? "none" : undefined,
         colorscale: [...Array(timeDataMaxIndex + 1)].map((_, i) => {
           const offset: number = i / timeDataMaxIndex
-          if (!action) {
-            if (statusRoi.temp_delete_roi.includes(i))
-              return [offset, "#ffffff"]
-            if (statusRoi.temp_merge_roi.includes(i)) return [offset, "#e134eb"]
-            if (statusRoi.temp_add_roi.includes(i)) return [offset, "#3483eb"]
+          const rgba = getRoiColor(i)
+          const hex = rgba2hex(rgba, 1)
+
+          const isClickPoint = roiClicked.some((point) => point === i)
+          const isDeleted = statusRoi?.temp_delete_roi?.includes(i) || false
+          const isMerged = statusRoi?.temp_merge_roi?.includes(i) || false
+          const isAdded = statusRoi?.temp_add_roi?.includes(i) || false
+          if (allowEditRoi && isClickPoint && action) {
+            switch (action) {
+              case DELETE_ROI:
+                return [offset, "#FFA500"] // orange
+              case MERGE_ROI:
+                return [offset, "#e134eb"] // purple
+              case ADD_ROI:
+                return [offset, "3483eb"] // red
+            }
           }
-          if (action === ADD_ROI) {
-            if (statusRoi.temp_delete_roi.includes(i))
-              return [offset, "#ffffff"]
-            if (statusRoi.temp_add_roi.includes(i)) return [offset, "#3483eb"]
-            if (statusRoi.temp_merge_roi.includes(i)) return [offset, "#e134eb"]
+          if (isDeleted || isMerged || isAdded) {
+            if (isDeleted) return [offset, "#FFA500"]
+            if (isMerged) return [offset, "#e134eb"]
+            if (isAdded) return [offset, "3483eb"]
           }
-          if (action === DELETE_ROI) {
-            if (
-              pointClick.find((e) => e.z === i) ||
-              statusRoi.temp_delete_roi.includes(i)
-            )
-              return [offset, "#ffffff"]
-            if (statusRoi.temp_add_roi.includes(i)) return [offset, "#3483eb"]
-            if (statusRoi.temp_merge_roi.includes(i)) return [offset, "#e134eb"]
-          }
-          if (action === MERGE_ROI) {
-            if (statusRoi.temp_delete_roi.includes(i))
-              return [offset, "#ffffff"]
-            if (
-              pointClick.find((e) => e.z === i) ||
-              statusRoi.temp_merge_roi.includes(i)
-            )
-              return [offset, "#e134eb"]
-            if (statusRoi.temp_add_roi.includes(i)) return [offset, "#3483eb"]
-          }
-          return [offset, getRoiColor(i)]
+          if (isClickPoint || !roiClicked.length) return [offset, hex]
+          return [offset, rgba2hex(rgba, 0.3)]
         }),
         zmin: 0,
         zmax: timeDataMaxIndex,
@@ -349,15 +349,16 @@ const ImagePlotChart = memo(function ImagePlotChart({
     ],
     [
       imageData,
-      roiDataState,
-      zsmooth,
-      showscale,
       colorscale,
+      showscale,
+      zsmooth,
+      roiDataState,
       timeDataMaxIndex,
       alpha,
-      pointClick,
       action,
+      roiClicked,
       statusRoi,
+      allowEditRoi,
     ],
   )
 
@@ -381,10 +382,30 @@ const ImagePlotChart = memo(function ImagePlotChart({
   const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
     setSelectMode(event.target.checked)
   }
+
+  const setRoiClickWhenSelect = (rois: string[]) => {
+    resetRoisClick(itemId)
+    rois.forEach((roi) => {
+      setRoisClick(itemId, Number(roi))
+      dispatch(
+        setImageItemClickedDataId({
+          itemId,
+          clickedDataId: roi,
+        }),
+      )
+    })
+  }
+
   // debounceでイベントを間引きする。onSelectedはそれっぽい名前だが動かなかった。
   const onSelecting = debounce((event: PlotSelectionEvent) => {
     if (event.range != null) {
-      dispatch(selectingImageArea({ itemId, range: event.range }))
+      dispatch(
+        selectingImageArea({
+          itemId,
+          range: event.range,
+          callback: setRoiClickWhenSelect,
+        }),
+      )
     }
   })
 
@@ -509,40 +530,14 @@ const ImagePlotChart = memo(function ImagePlotChart({
   const onChartClick = (event: PlotMouseEvent) => {
     // use as unknown because original PlotDatum does not have z property
     const point: PlotDatum = event.points[0] as unknown as PlotDatum
-    if (point.curveNumber >= 1 && outputKey === "cell_roi") {
-      setSelectRoi({
-        x: Number(point.x),
-        y: Number(point.y),
-        z: Number(point.z),
-      })
-    }
     if (point.curveNumber >= 1 && point.z >= 0) {
       dispatch(
-        setImageItemClikedDataId({
-          itemId,
-          clickedDataId: point.z.toString(),
-        }),
+        setImageItemClickedDataId({ itemId, clickedDataId: String(point.z) }),
       )
+      setRoisClick(itemId, point.z)
     }
   }
 
-  const setSelectRoi = (point: PointClick) => {
-    if (![MERGE_ROI, DELETE_ROI].includes(action)) return
-    if (isNaN(Number(point.z))) return
-
-    let newPoints
-    if (statusRoi.temp_delete_roi.includes(point.z)) {
-      return
-    }
-    const check = pointClick.findIndex((item) => item.z === point.z)
-    if (check < 0) {
-      newPoints = [...pointClick, point]
-    } else {
-      newPoints = pointClick.filter((item) => item.z !== point.z)
-    }
-    setPointClick(newPoints)
-  }
-
   const onCancel = async () => {
     setAction("")
     if (
@@ -552,7 +547,6 @@ const ImagePlotChart = memo(function ImagePlotChart({
     ) {
       return
     }
-    setPointClick([])
     try {
       await dispatch(cancelRoi({ path: refRoiFilePath.current, workspaceId }))
     } finally {
@@ -587,7 +581,6 @@ const ImagePlotChart = memo(function ImagePlotChart({
     setEdit(true)
     setSizeDrag(initSizeDrag)
     setChangeSize(undefined)
-    setPointClick([])
   }
 
   const onMouseDownDragAddRoi = () => {
@@ -643,7 +636,7 @@ const ImagePlotChart = memo(function ImagePlotChart({
   }
 
   const addOrSelectRoi = async () => {
-    if (!roiFilePath || !workspaceId) return
+    if (!roiFilePath || !workspaceId || !statusRoi) return
     if (action === ADD_ROI) {
       const sizeX = roiDataState[0].length - 1
       const sizeY = roiDataState.length - 1
@@ -665,36 +658,33 @@ const ImagePlotChart = memo(function ImagePlotChart({
       onCancelAdd()
     }
     if (action === MERGE_ROI) {
-      if (pointClick.length < 2) return
+      if (roiClicked.length < 2) return
       dispatch(resetAllOrderList())
       dispatch(
         mergeRoi({
           path: roiFilePath,
           workspaceId,
-          data: {
-            ids: pointClick.map((point) => point.z),
-          },
+          data: { ids: roiClicked },
         }),
       )
-      setPointClick([])
+      resetRoisClick(itemId)
       workspaceId && dispatch(getRoiData({ path: roiFilePath, workspaceId }))
     } else if (action === DELETE_ROI) {
-      if (!pointClick.length) return
+      if (!roiClicked.length) return
       dispatch(resetAllOrderList())
       await dispatch(
         deleteRoi({
           path: roiFilePath,
           workspaceId,
-          data: {
-            ids: pointClick.map((point) => point.z),
-          },
+          data: { ids: roiClicked },
         }),
       )
-      setPointClick([])
+      resetRoisClick(itemId)
       workspaceId && dispatch(getRoiData({ path: roiFilePath, workspaceId }))
     }
     setAction("")
     setEdit(true)
+    resetRoisClick(itemId)
     dispatch(getStatus({ path: roiFilePath, workspaceId }))
   }
 
@@ -721,6 +711,7 @@ const ImagePlotChart = memo(function ImagePlotChart({
         variant: "success",
       })
       resetTimeSeries()
+      resetRoisClick(itemId)
     } catch (error) {
       enqueueSnackbar("Failed to commit Edit ROI.", { variant: "error" })
     } finally {
@@ -745,14 +736,12 @@ const ImagePlotChart = memo(function ImagePlotChart({
   }
 
   const renderActionRoi = () => {
-    if (!roiFilePath || !roiFilePath.includes(CELL_ROI)) return null
+    if (!allowEditRoi) return null
     if (action) {
       return (
         <>
           {action !== ADD_ROI ? (
-            <BoxDiv>
-              ROI Selecteds: [{pointClick.map((item) => item.z).join(",")}]
-            </BoxDiv>
+            <BoxDiv>ROI Selecteds: [{roiClicked?.join(",") || ""}]</BoxDiv>
           ) : null}
           <BoxDiv sx={{ display: "flex", gap: 1 }}>
             <LinkDiv
@@ -775,13 +764,13 @@ const ImagePlotChart = memo(function ImagePlotChart({
             <LinkDiv
               style={{
                 opacity:
-                  (pointClick.length < 2 && action === MERGE_ROI) ||
-                  (pointClick.length < 1 && action === DELETE_ROI)
+                  (roiClicked.length < 2 && action === MERGE_ROI) ||
+                  (roiClicked.length < 1 && action === DELETE_ROI)
                     ? 0.5
                     : 1,
                 cursor:
-                  (pointClick.length < 2 && action === MERGE_ROI) ||
-                  (pointClick.length < 1 && action === DELETE_ROI)
+                  (roiClicked.length < 2 && action === MERGE_ROI) ||
+                  (roiClicked.length < 1 && action === DELETE_ROI)
                     ? "default"
                     : "pointer",
               }}
diff --git a/frontend/src/components/Workspace/Visualize/Plot/RoiPlot.tsx b/frontend/src/components/Workspace/Visualize/Plot/RoiPlot.tsx
index 2a42a0ee6..639f88e2c 100644
--- a/frontend/src/components/Workspace/Visualize/Plot/RoiPlot.tsx
+++ b/frontend/src/components/Workspace/Visualize/Plot/RoiPlot.tsx
@@ -13,6 +13,7 @@ import {
 } from "components/Workspace/FlowChart/Dialog/DialogContext"
 import { useBoxFilter } from "components/Workspace/FlowChart/Dialog/FilterContext"
 import { DisplayDataContext } from "components/Workspace/Visualize/DataContext"
+import { useVisualize } from "components/Workspace/Visualize/VisualizeContext"
 import { getRoiData } from "store/slice/DisplayData/DisplayDataActions"
 import {
   selectRoiData,
@@ -68,11 +69,10 @@ const RoiPlotImple = memo(function RoiPlotImple() {
   const timeDataMaxIndex = useSelector(selectRoiItemIndex(itemId, path))
   const { setRoiSelected, roisSelected, setMaxRoi } = useRoisSelected()
 
-  const { filterParam, setRoiPath } = useBoxFilter()
+  const { filterParam } = useBoxFilter()
+  const { setRoisClickWithGetTime, roisClick, isVisualize } = useVisualize()
 
-  useEffect(() => {
-    setRoiPath(path)
-  }, [path, setRoiPath])
+  const roiVisualSelected = roisClick[itemId]
 
   useEffect(() => {
     setMaxRoi?.(Math.max(...imageDataSelector.flat()) + 1)
@@ -108,33 +108,39 @@ const RoiPlotImple = memo(function RoiPlotImple() {
   const onChartClick = (event: PlotMouseEvent) => {
     const point = event.points[0] as unknown as { z: number }
     setRoiSelected(point.z)
+    setRoisClickWithGetTime(itemId, point.z)
   }
 
   const colorscale = useMemo(() => {
-    if (!dialogFilterNodeId || timeDataMaxIndex < 1) {
-      return colorscaleRoi.map((value, idx) => {
-        if (timeDataMaxIndex < 1 && !roisSelected.includes(0)) {
-          return [
-            String(idx / (nshades - 1)),
-            `${value}${(77).toString(16).toUpperCase()}`,
-          ]
+    if ((dialogFilterNodeId || isVisualize) && timeDataMaxIndex >= 1) {
+      return [...Array(timeDataMaxIndex + 1)].map((_, i) => {
+        const new_i = Math.floor(((i % 10) * 10 + i / 10) % nshades)
+        const offset: number = i / timeDataMaxIndex
+        const rgba = colorscaleRoi[new_i]
+        if (
+          (!dialogFilterNodeId && !roiVisualSelected?.length) ||
+          [...roisSelected, ...(roiVisualSelected || [])].includes(i)
+        ) {
+          return [offset, rgba]
         }
-        return [String(idx / (nshades - 1)), value]
+        return [offset, `${rgba}${(77).toString(16).toUpperCase()}`]
       })
     }
-    return [...Array(timeDataMaxIndex + 1)].map((_, i) => {
-      const new_i = Math.floor(((i % 10) * 10 + i / 10) % nshades)
-      const offset: number = i / timeDataMaxIndex
-      const rgba = colorscaleRoi[new_i]
-      if (!dialogFilterNodeId || roisSelected.includes(i)) {
-        return [offset, rgba]
+    return colorscaleRoi.map((value, idx) => {
+      if (timeDataMaxIndex < 1 && !roisSelected.includes(0)) {
+        return [
+          String(idx / (nshades - 1)),
+          `${value}${(77).toString(16).toUpperCase()}`,
+        ]
       }
-      return [offset, `${rgba}${(77).toString(16).toUpperCase()}`]
+      return [String(idx / (nshades - 1)), value]
     })
   }, [
     colorscaleRoi,
     dialogFilterNodeId,
+    isVisualize,
     nshades,
+    roiVisualSelected,
     roisSelected,
     timeDataMaxIndex,
   ])
@@ -149,14 +155,14 @@ const RoiPlotImple = memo(function RoiPlotImple() {
         hoverongaps: false,
         // zsmooth: zsmooth, // ['best', 'fast', false]
         zsmooth: false,
-        showscale: !dialogFilterNodeId,
+        showscale: false,
         zmin: 0,
         zmax: timeDataMaxIndex,
         showlegend: true,
         hovertemplate: "ROI: %{z}",
       },
     ],
-    [imageData, dialogFilterNodeId, colorscale, timeDataMaxIndex],
+    [imageData, colorscale, timeDataMaxIndex],
   )
 
   const layout = useMemo(
diff --git a/frontend/src/components/Workspace/Visualize/Plot/TimeSeriesPlot.tsx b/frontend/src/components/Workspace/Visualize/Plot/TimeSeriesPlot.tsx
index b270e15be..37f177b3d 100644
--- a/frontend/src/components/Workspace/Visualize/Plot/TimeSeriesPlot.tsx
+++ b/frontend/src/components/Workspace/Visualize/Plot/TimeSeriesPlot.tsx
@@ -15,13 +15,14 @@ import { LegendClickEvent } from "plotly.js"
 
 import { LinearProgress, Typography } from "@mui/material"
 
-import { getRoiDataApi, TimeSeriesData } from "api/outputs/Outputs"
+import { TimeSeriesData } from "api/outputs/Outputs"
 import {
   DialogContext,
   useRoisSelected,
 } from "components/Workspace/FlowChart/Dialog/DialogContext"
 import { useBoxFilter } from "components/Workspace/FlowChart/Dialog/FilterContext"
 import { DisplayDataContext } from "components/Workspace/Visualize/DataContext"
+import { useVisualize } from "components/Workspace/Visualize/VisualizeContext"
 import { selectAlgorithmDataFilterParam } from "store/slice/AlgorithmNode/AlgorithmNodeSelectors"
 import {
   getTimeSeriesDataById,
@@ -39,10 +40,7 @@ import {
   selectTimesSeriesMeta,
 } from "store/slice/DisplayData/DisplayDataSelectors"
 import { selectFrameRate } from "store/slice/Experiments/ExperimentsSelectors"
-import {
-  selectOutputFilePathCellRoi,
-  selectPipelineLatestUid,
-} from "store/slice/Pipeline/PipelineSelectors"
+import { selectPipelineLatestUid } from "store/slice/Pipeline/PipelineSelectors"
 import {
   selectTimeSeriesItemDrawOrderList,
   selectTimeSeriesItemOffset,
@@ -59,8 +57,6 @@ import {
   selectVisualizeSaveFormat,
   selectImageItemRangeUnit,
 } from "store/slice/VisualizeItem/VisualizeItemSelectors"
-import { setTimeSeriesItemDrawOrderList } from "store/slice/VisualizeItem/VisualizeItemSlice"
-import { selectCurrentWorkspaceId } from "store/slice/Workspace/WorkspaceSelector"
 import { AppDispatch } from "store/store"
 
 export const TimeSeriesPlot = memo(function TimeSeriesPlot() {
@@ -103,7 +99,6 @@ const TimeSeriesPlotImple = memo(function TimeSeriesPlotImple() {
     selectAlgorithmDataFilterParam(nodeId),
     shallowEqual,
   )
-  const workspaceId = useSelector(selectCurrentWorkspaceId)
   const meta = useSelector(selectTimesSeriesMeta(path))
   const dataXrange = useSelector(selectTimeSeriesXrange(path))
   const dataStd = useSelector(selectTimeSeriesStd(path))
@@ -127,46 +122,18 @@ const TimeSeriesPlotImple = memo(function TimeSeriesPlotImple() {
   const { dialogFilterNodeId, isOutput } = useContext(DialogContext)
   const { setRoiSelected, setMaxDim } = useRoisSelected()
   const { filterParam = filterSelector, roiPath } = useBoxFilter()
-  const roiUniqueListSelector = useSelector(
-    selectRoiUniqueList(roiPath),
-    shallowEqual,
-  )
-  const [roiUniqueList, setRoiUniqueList] = useState<null | string[]>(
-    roiUniqueListSelector,
-  )
-
-  const pathCellRoi = useSelector(selectOutputFilePathCellRoi(nodeId))
+  const roiUniqueList = useSelector(selectRoiUniqueList(roiPath))
+  const { setRoisClick, links } = useVisualize()
 
   const isExistFilterRoi = useMemo(
     () => filterParam?.roi?.length,
     [filterParam?.roi?.length],
   )
 
-  const getRoiUniqueList = useCallback(() => {
-    if (workspaceId && pathCellRoi) {
-      getRoiDataApi(pathCellRoi, { workspaceId }).then(({ data }) => {
-        const roi1Ddata: number[] = data[0]
-          .map((row) =>
-            Array.from(new Set(row.filter((value) => value != null))),
-          )
-          .flat()
-        const roiUniqueIds = Array.from(new Set(roi1Ddata))
-          .sort((n1, n2) => n1 - n2)
-          .map(String)
-        setRoiUniqueList(roiUniqueIds)
-      })
-    }
-  }, [pathCellRoi, workspaceId])
-
-  useEffect(() => {
-    if (isOutput && isExistFilterRoi) getRoiUniqueList()
-  }, [getRoiUniqueList, isExistFilterRoi, isOutput])
-
-  useEffect(() => {
-    if (!isOutput || dialogFilterNodeId) {
-      setRoiUniqueList(roiUniqueListSelector)
-    }
-  }, [dialogFilterNodeId, isOutput, roiUniqueListSelector])
+  const itemIdRoi = useMemo(
+    () => Object.keys(links).find((key) => links[key] === itemId),
+    [itemId, links],
+  )
 
   useEffect(() => {
     if (!timeSeriesData) return
@@ -418,27 +385,15 @@ const TimeSeriesPlotImple = memo(function TimeSeriesPlotImple() {
   }
 
   const onLegendClick = (event: LegendClickEvent) => {
-    const clickNumber = dataKeys[event.curveNumber]
-
+    const clickedSeriesId = dataKeys[event.curveNumber]
     if (dialogFilterNodeId) {
-      setRoiSelected(Number(clickNumber))
+      setRoiSelected(Number(clickedSeriesId))
       return false
     }
-
-    const newDrawOrderList = drawOrderList.includes(clickNumber)
-      ? drawOrderList.filter((value) => value !== clickNumber)
-      : [...drawOrderList, clickNumber]
-
-    dispatch(
-      setTimeSeriesItemDrawOrderList({
-        itemId,
-        drawOrderList: newDrawOrderList,
-      }),
-    )
-
+    if (itemIdRoi) setRoisClick(Number(itemIdRoi), Number(clickedSeriesId))
     // set DisplayNumbers
-    if (!drawOrderList.includes(clickNumber)) {
-      dispatch(getTimeSeriesDataById({ path, index: clickNumber }))
+    if (!drawOrderList.includes(clickedSeriesId)) {
+      dispatch(getTimeSeriesDataById({ path, index: clickedSeriesId }))
     }
 
     return false
diff --git a/frontend/src/components/Workspace/Visualize/Visualize.tsx b/frontend/src/components/Workspace/Visualize/Visualize.tsx
index 487c51de0..af45200a2 100644
--- a/frontend/src/components/Workspace/Visualize/Visualize.tsx
+++ b/frontend/src/components/Workspace/Visualize/Visualize.tsx
@@ -6,6 +6,7 @@ import { styled } from "@mui/material/styles"
 
 import { CurrentPipelineInfo } from "components/common/CurrentPipelineInfo"
 import { FlexItemList } from "components/Workspace/Visualize/FlexItemList"
+import { VisualizeProvider } from "components/Workspace/Visualize/VisualizeContext"
 import { VisualizeItemEditor } from "components/Workspace/Visualize/VisualizeItemEditor"
 import { CONTENT_HEIGHT, DRAWER_WIDTH } from "const/Layout"
 
@@ -25,7 +26,9 @@ const Visualize: FC = () => {
       </Box>
 
       <MainContents>
-        <FlexItemList />
+        <VisualizeProvider>
+          <FlexItemList />
+        </VisualizeProvider>
       </MainContents>
     </Box>
   )
diff --git a/frontend/src/components/Workspace/Visualize/VisualizeContext.tsx b/frontend/src/components/Workspace/Visualize/VisualizeContext.tsx
new file mode 100644
index 000000000..3be853690
--- /dev/null
+++ b/frontend/src/components/Workspace/Visualize/VisualizeContext.tsx
@@ -0,0 +1,178 @@
+import {
+  createContext,
+  PropsWithChildren,
+  useCallback,
+  useContext,
+  useEffect,
+  useRef,
+  useState,
+} from "react"
+import { useSelector, useDispatch, shallowEqual } from "react-redux"
+
+import { getTimeSeriesDataById } from "store/slice/DisplayData/DisplayDataActions"
+import { selectStatusRoiTempAdd } from "store/slice/DisplayData/DisplayDataSelectors"
+import { selectVisualizeDataFilePath } from "store/slice/VisualizeItem/VisualizeItemSelectors"
+import {
+  setTimeSeriesItemDrawOrder,
+  setTimeSeriesItemDrawOrderList,
+} from "store/slice/VisualizeItem/VisualizeItemSlice"
+import { AppDispatch, RootState, store } from "store/store"
+
+type TVisualizeContext = {
+  roisClick: { [itemId: string]: number[] }
+  setRoisClick: (itemId: number, roi: number) => void
+  setRoisClickWithGetTime: (itemId: number, roi: number) => void
+  resetRoisClick: (itemId: number) => void
+  links: { [linkItemId: string]: string | number }
+  setLinks: (itemId: number, linkItemId: number) => () => void
+  isVisualize?: boolean
+}
+
+const VisualizeContext = createContext<TVisualizeContext>({
+  roisClick: {},
+  setRoisClick: () => null,
+  setRoisClickWithGetTime: () => null,
+  resetRoisClick: () => null,
+  links: {},
+  setLinks: () => () => null,
+  isVisualize: false,
+})
+
+export const useVisualize = () => useContext(VisualizeContext)
+
+export const VisualizeProvider = ({ children }: PropsWithChildren) => {
+  const [rois, setRois] = useState<{ [key: string]: number[] }>({})
+  const [links, _setLinks] = useState<{ [itemId: string]: string | number }>({})
+  const dispatch = useDispatch<AppDispatch>()
+  const selectedStatusTempAdd = useSelector(
+    selectStatusRoiTempAdd,
+    shallowEqual,
+  )
+
+  const linksRef = useRef(links)
+  const roisRef = useRef(rois)
+
+  useEffect(() => {
+    linksRef.current = links
+  }, [links])
+
+  useEffect(() => {
+    roisRef.current = rois
+  }, [rois])
+
+  const setRoisClick = useCallback(
+    (itemId: string | number, roi: number) => {
+      setRois((pre) => ({
+        ...pre,
+        [itemId]: pre[itemId]?.some((e) => e === roi)
+          ? pre[itemId].filter((e) => e !== roi)
+          : [...(pre[itemId] || []), roi],
+      }))
+      const fluorescenceId = linksRef.current[itemId]
+      if (fluorescenceId !== undefined) {
+        dispatch(
+          setTimeSeriesItemDrawOrder({
+            itemId: Number(fluorescenceId),
+            drawOrder: String(roi),
+          }),
+        )
+      }
+    },
+    [dispatch],
+  )
+  const setRoisClickWithGetTime = useCallback(
+    async (itemId: string | number, roi: number) => {
+      setRois((pre) => ({
+        ...pre,
+        [itemId]: pre[itemId]?.some((e) => e === roi)
+          ? pre[itemId].filter((e) => e !== roi)
+          : [...(pre[itemId] || []), roi],
+      }))
+      const fluorescenceId = linksRef.current[itemId]
+      if (fluorescenceId !== undefined) {
+        dispatch(
+          setTimeSeriesItemDrawOrder({
+            itemId: Number(fluorescenceId),
+            drawOrder: String(roi),
+          }),
+        )
+        const path = selectVisualizeDataFilePath(fluorescenceId as number)(
+          store.getState() as RootState,
+        )
+        await dispatch(
+          getTimeSeriesDataById({ path: path!, index: String(roi) }),
+        ).unwrap()
+      }
+    },
+    [dispatch],
+  )
+
+  const resetRoisClick = useCallback(
+    (itemId: string | number) => {
+      setRois((pre) => ({ ...pre, [itemId]: [] }))
+      const fluorescenceId = linksRef.current[itemId]
+      if (fluorescenceId !== undefined) {
+        dispatch(
+          setTimeSeriesItemDrawOrderList({
+            itemId: Number(fluorescenceId),
+            drawOrderList: [],
+          }),
+        )
+      }
+    },
+    [dispatch],
+  )
+
+  const setLinks = useCallback(
+    (itemId: number, linkItemId: number) => {
+      _setLinks((pre) => ({ ...pre, [linkItemId]: itemId }))
+      const drawOrders = roisRef.current[linkItemId] || []
+      dispatch(
+        setTimeSeriesItemDrawOrderList({
+          itemId: Number(itemId),
+          drawOrderList: drawOrders.map((e) => String(e)),
+        }),
+      )
+      drawOrders.forEach(async (e) => {
+        const path = selectVisualizeDataFilePath(itemId)(
+          store.getState() as RootState,
+        )
+        if (!path) return
+        if (!selectedStatusTempAdd.includes(Number(e))) {
+          await dispatch(
+            getTimeSeriesDataById({ path, index: String(e) }),
+          ).unwrap()
+        }
+      })
+      return () => {
+        dispatch(
+          setTimeSeriesItemDrawOrderList({
+            itemId: Number(itemId),
+            drawOrderList: [],
+          }),
+        )
+        _setLinks((pre) => {
+          delete pre[linkItemId]
+          return pre
+        })
+      }
+    },
+    [dispatch, selectedStatusTempAdd],
+  )
+
+  return (
+    <VisualizeContext.Provider
+      value={{
+        roisClick: rois,
+        setRoisClick,
+        links,
+        setLinks,
+        resetRoisClick,
+        setRoisClickWithGetTime,
+        isVisualize: true,
+      }}
+    >
+      {children}
+    </VisualizeContext.Provider>
+  )
+}
diff --git a/frontend/src/components/Workspace/Visualize/VisualizeItem.tsx b/frontend/src/components/Workspace/Visualize/VisualizeItem.tsx
index 8f9b03332..6a77ec1b6 100644
--- a/frontend/src/components/Workspace/Visualize/VisualizeItem.tsx
+++ b/frontend/src/components/Workspace/Visualize/VisualizeItem.tsx
@@ -1,4 +1,11 @@
-import { memo, useCallback, useState, MouseEvent, useEffect } from "react"
+import {
+  memo,
+  useCallback,
+  useState,
+  MouseEvent,
+  useEffect,
+  useRef,
+} from "react"
 import { useDispatch, useSelector } from "react-redux"
 
 import { Close, Numbers } from "@mui/icons-material"
@@ -14,6 +21,7 @@ import Loading from "components/common/Loading"
 import { useMouseDragHandler } from "components/utils/MouseDragUtil"
 import { DisplayDataItem } from "components/Workspace/Visualize/DisplayDataItem"
 import { FilePathSelect } from "components/Workspace/Visualize/FilePathSelect"
+import { useVisualize } from "components/Workspace/Visualize/VisualizeContext"
 import {
   selectLoading,
   selectIsEditRoiCommitting,
@@ -215,6 +223,7 @@ const FilePathSelectItem = memo(function FilePathSelectItem({
   itemId,
 }: ItemIdProps) {
   const dispatch = useDispatch()
+  const { resetRoisClick } = useVisualize()
   const dataType = useSelector(selectVisualizeDataType(itemId))
   const selectedNodeId = useSelector(selectVisualizeDataNodeId(itemId))
   const selectedFilePath = useSelector(selectImageItemFilePath(itemId))
@@ -246,6 +255,7 @@ const FilePathSelectItem = memo(function FilePathSelectItem({
             },
       ),
     )
+    resetRoisClick(itemId)
   }
 
   return (
@@ -260,6 +270,8 @@ const FilePathSelectItem = memo(function FilePathSelectItem({
 const RefImageItemIdSelect = memo(function RefImageItemIdSelect({
   itemId,
 }: ItemIdProps) {
+  const { setLinks } = useVisualize()
+  const refSub = useRef<() => void>()
   const dispatch = useDispatch()
   const itemIdList = useSelector(
     selectVisualizeImageAndRoiItemIdList,
@@ -273,10 +285,20 @@ const RefImageItemIdSelect = memo(function RefImageItemIdSelect({
         refImageItemId: isNaN(value) ? null : value,
       }),
     )
+    refSub.current?.()
+    refSub.current = setLinks(itemId, value)
   }
   const selectedRefImageItemId = useSelector(
     selectTimeSeriesItemRefImageItemId(itemId),
   )
+
+  useEffect(() => {
+    if (selectedRefImageItemId || selectedRefImageItemId === 0) {
+      refSub.current = setLinks(itemId, selectedRefImageItemId)
+    }
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [])
+
   return (
     <FormControl fullWidth variant="standard">
       <InputLabel>Link to box (#)</InputLabel>
@@ -382,6 +404,9 @@ const RoiSelect = memo(function RoiSelect({
   const dispatch = useDispatch()
   const roiItemNodeId = useSelector(selectRoiItemNodeId(itemId))
   const roiItemFilePath = useSelector(selectRoiItemFilePath(itemId))
+
+  const { resetRoisClick } = useVisualize()
+
   useEffect(() => {
     if (!roiItemFilePath) return
     setRoiFilePath?.(roiItemFilePath)
@@ -393,6 +418,7 @@ const RoiSelect = memo(function RoiSelect({
     dataType: string,
     outputKey?: string,
   ) => {
+    resetRoisClick(itemId)
     dispatch(setRoiItemFilePath({ itemId, nodeId, filePath, outputKey }))
   }
   return (
diff --git a/frontend/src/store/slice/DisplayData/DisplayDataSelectors.ts b/frontend/src/store/slice/DisplayData/DisplayDataSelectors.ts
index 3e69eca81..f2e2ef9e2 100644
--- a/frontend/src/store/slice/DisplayData/DisplayDataSelectors.ts
+++ b/frontend/src/store/slice/DisplayData/DisplayDataSelectors.ts
@@ -1,3 +1,6 @@
+import { createSelector } from "@reduxjs/toolkit"
+
+import { StatusROI } from "components/Workspace/Visualize/Plot/ImagePlot"
 import { RootState } from "store/store"
 
 const selectDisplayData = (state: RootState) => state.displayData
@@ -367,4 +370,16 @@ export const selectPolarDataError = (filePath: string) => (state: RootState) =>
     ? selectDisplayData(state).polar[filePath].error
     : null
 
-export const selectStatusRoi = (state: RootState) => state.displayData.statusRoi
+export const selectStatusRoi = createSelector(
+  [(state: RootState) => state.displayData.statusRoi],
+  (statusRoi): StatusROI => ({
+    temp_add_roi: statusRoi?.temp_add_roi || [],
+    temp_delete_roi: statusRoi?.temp_delete_roi || [],
+    temp_merge_roi: statusRoi?.temp_merge_roi || [],
+  }),
+)
+
+export const selectStatusRoiTempAdd = createSelector(
+  [(state: RootState) => state.displayData.statusRoi],
+  (statusRoi): number[] => statusRoi?.temp_add_roi,
+)
diff --git a/frontend/src/store/slice/DisplayData/DisplayDataSlice.ts b/frontend/src/store/slice/DisplayData/DisplayDataSlice.ts
index fb5cc5c8f..24a3a07c2 100644
--- a/frontend/src/store/slice/DisplayData/DisplayDataSlice.ts
+++ b/frontend/src/store/slice/DisplayData/DisplayDataSlice.ts
@@ -50,7 +50,11 @@ const initialState: DisplayData = {
   polar: {},
   loading: false,
   loadingStack: [],
-  statusRoi: { temp_add_roi: [], temp_delete_roi: [], temp_merge_roi: [] },
+  statusRoi: {
+    temp_add_roi: [],
+    temp_delete_roi: [],
+    temp_merge_roi: [],
+  },
   isEditRoiCommitting: false,
 }
 
diff --git a/frontend/src/store/slice/VisualizeItem/VisualizeItemActions.ts b/frontend/src/store/slice/VisualizeItem/VisualizeItemActions.ts
index 2c9566e3b..834f6a7ce 100644
--- a/frontend/src/store/slice/VisualizeItem/VisualizeItemActions.ts
+++ b/frontend/src/store/slice/VisualizeItem/VisualizeItemActions.ts
@@ -1,9 +1,13 @@
 import { createAsyncThunk, createAction } from "@reduxjs/toolkit"
 
 import { getTimeSeriesDataById } from "store/slice/DisplayData/DisplayDataActions"
-import { selectRoiData } from "store/slice/DisplayData/DisplayDataSelectors"
+import {
+  selectRoiData,
+  selectStatusRoiTempAdd,
+} from "store/slice/DisplayData/DisplayDataSelectors"
 import { DATA_TYPE } from "store/slice/DisplayData/DisplayDataType"
 import { selectVisualizeItems } from "store/slice/VisualizeItem/VisualizeItemSelectors"
+import { setClickedData } from "store/slice/VisualizeItem/VisualizeItemSlice"
 import { VISUALIZE_ITEM_SLICE_NAME } from "store/slice/VisualizeItem/VisualizeItemType"
 import {
   isImageItem,
@@ -11,20 +15,24 @@ import {
 } from "store/slice/VisualizeItem/VisualizeItemUtils"
 import { ThunkApiConfig } from "store/store"
 
-export const setImageItemClikedDataId = createAsyncThunk<
+export const setImageItemClickedDataId = createAsyncThunk<
   void,
-  { itemId: number; clickedDataId: string },
+  { itemId: number; clickedDataId: string | null },
   ThunkApiConfig
 >(
-  `${VISUALIZE_ITEM_SLICE_NAME}/setImageItemClikedDataId`,
+  `${VISUALIZE_ITEM_SLICE_NAME}/setImageItemClickedDataId`,
   ({ itemId, clickedDataId }, thunkAPI) => {
+    thunkAPI.dispatch(setClickedData({ itemId, clickedDataId }))
     const items = selectVisualizeItems(thunkAPI.getState())
+    const tempAdd = selectStatusRoiTempAdd(thunkAPI.getState())
     Object.values(items).forEach((item) => {
       if (
         isTimeSeriesItem(item) &&
         item.filePath != null &&
         item.refImageItemId === itemId &&
-        !item.drawOrderList.includes(clickedDataId)
+        clickedDataId &&
+        !item.drawOrderList.includes(clickedDataId) &&
+        !tempAdd?.includes(Number(clickedDataId))
       ) {
         thunkAPI.dispatch(
           getTimeSeriesDataById({ path: item.filePath, index: clickedDataId }),
@@ -42,11 +50,12 @@ export const selectingImageArea = createAsyncThunk<
       x: number[]
       y: number[]
     }
+    callback?: (rois: string[]) => void
   },
   ThunkApiConfig
 >(
   `${VISUALIZE_ITEM_SLICE_NAME}/selectingImageArea`,
-  ({ itemId, range }, thunkAPI) => {
+  ({ itemId, range, callback }, thunkAPI) => {
     const { x, y } = range
     const [x1, x2] = x.map(Math.round)
     const [y1, y2] = y.map(Math.round)
@@ -69,20 +78,8 @@ export const selectingImageArea = createAsyncThunk<
           }
         }
         Object.values(items).forEach((item) => {
-          if (
-            isTimeSeriesItem(item) &&
-            item.filePath != null &&
-            item.refImageItemId === itemId
-          ) {
-            const path = item.filePath
-            selectedZList.forEach((selectedZ) => {
-              thunkAPI.dispatch(
-                getTimeSeriesDataById({
-                  path,
-                  index: String(selectedZ),
-                }),
-              )
-            })
+          if (isTimeSeriesItem(item) && item.filePath != null) {
+            callback?.(selectedZList)
           }
         })
       }
diff --git a/frontend/src/store/slice/VisualizeItem/VisualizeItemSelectors.ts b/frontend/src/store/slice/VisualizeItem/VisualizeItemSelectors.ts
index d06a80a0a..7df8fc4f7 100644
--- a/frontend/src/store/slice/VisualizeItem/VisualizeItemSelectors.ts
+++ b/frontend/src/store/slice/VisualizeItem/VisualizeItemSelectors.ts
@@ -402,8 +402,11 @@ export const selectTimeSeriesItemRefRoiUniqueList =
     if (isTimeSeriesItem(item)) {
       if (item.refImageItemId != null) {
         const imageItem = selectVisualizeItems(state)[item.refImageItemId]
-        if (isImageItem(imageItem) && imageItem.roiItem?.filePath != null) {
-          return selectRoiUniqueList(imageItem.roiItem.filePath)(state)
+        if (isImageItem(imageItem) && imageItem.roiItem?.filePath) {
+          return selectRoiUniqueList(imageItem.roiItem?.filePath)(state)
+        }
+        if (isRoiItem(imageItem) && imageItem.filePath) {
+          return selectRoiUniqueList(imageItem.filePath)(state)
         }
       }
       return null
@@ -619,6 +622,9 @@ export const selectImageItemRangeUnit =
     }
   }
 
+export const selectClickedRoi = (itemId: number) => (state: RootState) =>
+  state.visualaizeItem.clickedRois[itemId] || null
+
 export const selectImageItemShowRoiLabels =
   (itemId: number) => (state: RootState) => {
     const item = selectVisualizeItemById(itemId)(state)
diff --git a/frontend/src/store/slice/VisualizeItem/VisualizeItemSlice.ts b/frontend/src/store/slice/VisualizeItem/VisualizeItemSlice.ts
index b684fd511..e2be44609 100644
--- a/frontend/src/store/slice/VisualizeItem/VisualizeItemSlice.ts
+++ b/frontend/src/store/slice/VisualizeItem/VisualizeItemSlice.ts
@@ -9,7 +9,7 @@ import { run, runByCurrentUid } from "store/slice/Pipeline/PipelineActions"
 import {
   deleteDisplayItem,
   selectingImageArea,
-  setImageItemClikedDataId,
+  setImageItemClickedDataId,
   setNewDisplayDataPath,
 } from "store/slice/VisualizeItem/VisualizeItemActions"
 import {
@@ -50,6 +50,7 @@ export const initialState: VisualaizeItem = {
   items: {},
   selectedItemId: null,
   layout: [],
+  clickedRois: {},
 }
 const displayDataCommonInitialValue = {
   itemType: VISUALIZE_ITEM_TYPE_SET.DISPLAY_DATA,
@@ -688,6 +689,23 @@ export const visualaizeItemSlice = createSlice({
         targetItem.drawOrderList = drawOrderList
       }
     },
+    setTimeSeriesItemDrawOrder: (
+      state,
+      action: PayloadAction<{
+        itemId: number
+        drawOrder: string
+      }>,
+    ) => {
+      const { itemId, drawOrder } = action.payload
+      const targetItem = state.items[itemId]
+      if (isTimeSeriesItem(targetItem)) {
+        if (targetItem.drawOrderList.some((e) => e === drawOrder)) {
+          targetItem.drawOrderList = targetItem.drawOrderList.filter(
+            (e) => e !== drawOrder,
+          )
+        } else targetItem.drawOrderList.push(drawOrder)
+      }
+    },
     resetAllOrderList: (state) => {
       Object.keys(state.items).forEach((id: string | number) => {
         const targetItem = state.items[id]
@@ -720,7 +738,6 @@ export const visualaizeItemSlice = createSlice({
       const targetItem = state.items[itemId]
       if (isTimeSeriesItem(targetItem)) {
         targetItem.refImageItemId = refImageItemId ?? null
-        targetItem.drawOrderList = []
       }
     },
     setHeatMapItemShowScale: (
@@ -858,6 +875,17 @@ export const visualaizeItemSlice = createSlice({
         targetItem.selectedIndex = action.payload.selectedIndex
       }
     },
+
+    setClickedData: (
+      state,
+      action: PayloadAction<{
+        itemId: number
+        clickedDataId: string | null
+      }>,
+    ) => {
+      const { itemId, clickedDataId } = action.payload
+      state.clickedRois[itemId] = clickedDataId
+    },
     setImageItemShowRoiLabels: (
       state,
       action: PayloadAction<{
@@ -919,23 +947,15 @@ export const visualaizeItemSlice = createSlice({
         }
         resetImageActiveIndexFn(state, { itemId })
       })
-      .addCase(setImageItemClikedDataId.fulfilled, (state, action) => {
+      .addCase(setImageItemClickedDataId.fulfilled, (state, action) => {
         const { itemId: imageItemId, clickedDataId } = action.meta.arg
-        const targetItem = state.items[imageItemId]
-        if (isImageItem(targetItem)) {
-          targetItem.clickedDataId = clickedDataId
+        // Update clickedRois
+        if (clickedDataId === state.clickedRois[imageItemId]) {
+          state.clickedRois[imageItemId] = null
+        } else {
+          state.clickedRois[imageItemId] = clickedDataId
         }
-        Object.values(state.items).forEach((item) => {
-          if (isTimeSeriesItem(item)) {
-            if (
-              item.refImageItemId != null &&
-              imageItemId === item.refImageItemId &&
-              !item.drawOrderList.includes(clickedDataId)
-            ) {
-              item.drawOrderList.push(clickedDataId)
-            }
-          }
-        })
+        // Update drawOrderList for related time series items
       })
       .addCase(selectingImageArea.fulfilled, (state, action) => {
         const { itemId: imageItemId } = action.meta.arg
@@ -1025,6 +1045,7 @@ export const {
   setTimeSeriesItemXrangeLeft,
   setTimeSeriesItemXrangeRight,
   setTimeSeriesItemDrawOrderList,
+  setTimeSeriesItemDrawOrder,
   setTimeSeriesItemMaxIndex,
   setTimeSeriesRefImageItemId,
   setHeatMapItemShowScale,
@@ -1038,6 +1059,7 @@ export const {
   setHistogramItemBins,
   setLineItemSelectedIndex,
   setPolartemItemSelectedIndex,
+  setClickedData,
   setImageItemShowRoiLabels,
   resetAllOrderList,
   reset,
diff --git a/frontend/src/store/slice/VisualizeItem/VisualizeItemType.ts b/frontend/src/store/slice/VisualizeItem/VisualizeItemType.ts
index 854a4d28a..2b7584682 100644
--- a/frontend/src/store/slice/VisualizeItem/VisualizeItemType.ts
+++ b/frontend/src/store/slice/VisualizeItem/VisualizeItemType.ts
@@ -11,6 +11,9 @@ export type VisualaizeItem = {
     [itemId: number | string]: VisualaizeItemType
   }
   layout: ItemLayout
+  clickedRois: {
+    [itemId: number | string]: string | null
+  }
 }
 
 export type ItemLayout = number[][] // itemIdをrow,columnで並べる
@@ -77,7 +80,7 @@ export interface ImageItem extends DisplayDataItemBaseType {
   roiItem: RoiItem | null
   roiAlpha: number
   duration: number
-  clickedDataId?: string
+  clickedDataId?: string | null
   showRoiLabels: boolean
 }
 


Reviews:
user: login: itutu-tienday
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-01-06T05:08:59Z
user: login: milesAraya
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-11T01:29:52Z
user: login: sanglevinh
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-14T07:44:14Z
user: login: sanglevinh
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-14T07:45:08Z
user: login: itutu-tienday
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-14T08:43:47Z

Review comments:
diff_hunk: @@ -290,6 +291,14 @@ const LegendSelect: FC = () => {
         drawOrderList: newDrawOrderList,
       }),
     )
+    dispatch(
path: frontend/src/components/Workspace/Visualize/Editor/TimeSeriesItemEditor.tsx
user: login: itutu-tienday
body: - Behavior before Edit Roi is started
  - Flou selected before start of Edit Roi is not released after start of Edit Roi.
    -> The selection of ROI in Edit Roi and the selection of Flou do not match.
    -> When Flou is selected in this state, the ROI selection is canceled, which seems to be a reversed operation.
created_at: 2025-01-06T05:08:30Z
author_association: COLLABORATOR
diff_hunk: @@ -290,6 +291,14 @@ const LegendSelect: FC = () => {
         drawOrderList: newDrawOrderList,
       }),
     )
+    dispatch(
path: frontend/src/components/Workspace/Visualize/Editor/TimeSeriesItemEditor.tsx
user: login: itutu-tienday
body: - Bug
  - React error when Add Roi -> Commit -> Select the added ROI?

created_at: 2025-01-06T05:08:51Z
author_association: COLLABORATOR
diff_hunk: @@ -222,23 +214,39 @@ const ImagePlotChart = memo(function ImagePlotChart({
   const timeDataMaxIndex = useSelector(selectRoiItemIndex(itemId, roiFilePath))
   const width = useSelector(selectVisualizeItemWidth(itemId))
   const height = useSelector(selectVisualizeItemHeight(itemId))
-  const statusRoi = useSelector(selectStatusRoi)
   const [sizeDrag, setSizeDrag] = useState(initSizeDrag)
   const [startDragAddRoi, setStartDragAddRoi] = useState(false)
   const [action, setAction] = useState("")
   const [positionDrag, setChangeSize] = useState<PositionDrag | undefined>()
   const showRoiLabels = useSelector(selectImageItemShowRoiLabels(itemId))
 
-  const outputKey: string | null = useSelector(selectRoiItemOutputKeys(itemId))
+  const allowEditRoi = useMemo(
+    () => roiFilePath?.includes(CELL_ROI),
+    [roiFilePath],
+  )
+  const { setRoisClick, roisClick, resetRoisClick } = useVisualize()
+  const roiClicked = useMemo(() => roisClick[itemId] || [], [itemId, roisClick])
+
+  const selectedStatus = useSelector(selectStatusRoi)
+
+  const statusRoi = useMemo(() => {
+    return (
+      selectedStatus || {
+        temp_add_roi: [],
+        temp_delete_roi: [],
+        temp_merge_roi: [],
path: frontend/src/components/Workspace/Visualize/Plot/ImagePlot.tsx
user: login: milesAraya
body: When merging or deleting, all the ROIs turn orange or purple before selection. Actually, when turning on Merge, the ROI should have their normal colour, then only after selection to turn orange or purple.
created_at: 2025-03-11T01:26:19Z
author_association: COLLABORATOR
diff_hunk: @@ -222,23 +214,39 @@ const ImagePlotChart = memo(function ImagePlotChart({
   const timeDataMaxIndex = useSelector(selectRoiItemIndex(itemId, roiFilePath))
   const width = useSelector(selectVisualizeItemWidth(itemId))
   const height = useSelector(selectVisualizeItemHeight(itemId))
-  const statusRoi = useSelector(selectStatusRoi)
   const [sizeDrag, setSizeDrag] = useState(initSizeDrag)
   const [startDragAddRoi, setStartDragAddRoi] = useState(false)
   const [action, setAction] = useState("")
   const [positionDrag, setChangeSize] = useState<PositionDrag | undefined>()
   const showRoiLabels = useSelector(selectImageItemShowRoiLabels(itemId))
 
-  const outputKey: string | null = useSelector(selectRoiItemOutputKeys(itemId))
+  const allowEditRoi = useMemo(
+    () => roiFilePath?.includes(CELL_ROI),
+    [roiFilePath],
+  )
+  const { setRoisClick, roisClick, resetRoisClick } = useVisualize()
+  const roiClicked = useMemo(() => roisClick[itemId] || [], [itemId, roisClick])
+
+  const selectedStatus = useSelector(selectStatusRoi)
+
+  const statusRoi = useMemo(() => {
+    return (
+      selectedStatus || {
+        temp_add_roi: [],
+        temp_delete_roi: [],
+        temp_merge_roi: [],
+      }
path: frontend/src/components/Workspace/Visualize/Plot/ImagePlot.tsx
user: login: milesAraya
body: When ROI are selected for merge, they change to purple. But after you click OK, the special Edit colour-change disappears and the ROI have their normal colours again. The two merged ROI should have the special colour until after Commit Edit.
created_at: 2025-03-11T01:29:47Z
author_association: COLLABORATOR
diff_hunk: @@ -290,6 +291,14 @@ const LegendSelect: FC = () => {
         drawOrderList: newDrawOrderList,
       }),
     )
+    dispatch(
path: frontend/src/components/Workspace/Visualize/Editor/TimeSeriesItemEditor.tsx
user: login: sanglevinh
body: @itutu-tienday  When adding an ROI, it's just temporary data, and the API for getting fluorescence will encounter an error. Therefore, for the time being, I will not allow this API to be called when adding an ROI
created_at: 2025-03-14T07:44:14Z
author_association: NONE
diff_hunk: @@ -290,6 +291,14 @@ const LegendSelect: FC = () => {
         drawOrderList: newDrawOrderList,
       }),
     )
+    dispatch(
path: frontend/src/components/Workspace/Visualize/Editor/TimeSeriesItemEditor.tsx
user: login: sanglevinh
body: @itutu-tienday It seems like it has been fixed
created_at: 2025-03-14T07:45:08Z
author_association: NONE

PR comments:
user: login: milesAraya
created_at: 2024-08-20T05:11:32Z
body: test case:
https://docs.google.com/spreadsheets/d/127u9f5e1jLAPJRP7iXA0cZMVwpZk6MqW/edit?usp=sharing&ouid=110721479235758822364&rtpof=true&sd=true
author_association: COLLABORATOR
user: login: tsuchiyama-araya
created_at: 2025-03-14T07:29:06Z
body: @sanglevinh 
I checked it and noticed that the color of ROI and Fluo is different can you add a fix for this? Thank you.
![Screenshot 2025-03-14 162247](https://github.com/user-attachments/assets/eeeecaac-aef3-428c-8826-552e66969e52)

author_association: COLLABORATOR
user: login: sanglevinh
created_at: 2025-03-14T07:41:03Z
body: > @sanglevinh I checked it and noticed that the color of ROI and Fluo is different can you add a fix for this? Thank you. ![Screenshot 2025-03-14 162247](https://private-user-images.githubusercontent.com/169987401/422684920-eeeecaac-aef3-428c-8826-552e66969e52.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDE5Mzc4NDAsIm5iZiI6MTc0MTkzNzU0MCwicGF0aCI6Ii8xNjk5ODc0MDEvNDIyNjg0OTIwLWVlZWVjYWFjLWFlZjMtNDI4Yy04ODI2LTU1MmU2Njk2OWU1Mi5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjUwMzE0JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI1MDMxNFQwNzMyMjBaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1lNjYwOTAzNDdjMjA0NTg4NDc3Nzk3NWZlYWY3NmY0OTZlMTc2Y2EwOWQ1NTFjNGI2ZGIwYTgxODgzNTAwNzU0JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.WgvmQhJHJHYz0DKLk6ROpIMBc29HZqRd1tZpF0qHK3w)

@tsuchiyama-araya Currently, I am using the react-plotlyjs-ts library, and the way of setting colors for the two charts is different, so making the colors of both charts match could take a lot of time. I remember you mentioned creating a task for this issue, but it was later paused and replaced with another task.
author_association: NONE
user: login: tsuchiyama-araya
created_at: 2025-03-14T08:44:03Z
body: @sanglevinh 
Oh sorry, yes i remember talking about that before now. We will check it here and fix. Thank you for answering
author_association: COLLABORATOR