Repository: digitaldemocracy2030/kouchou-ai
Pull request #487: デザイン変更: セグメントビューでの全体・濃い意見・階層の切り替え機能
Author: login: devin-ai-integration[bot]
Created: 2025-05-12T06:27:57Z | Merged: 2025-05-21T01:47:17Z

# デザイン変更: セグメントビューでの全体・濃い意見・階層の切り替え機能

## 変更内容
- セグメントビューを使用して全体・濃い意見・階層のビュー切り替え機能を実装
- 新しいカスタムアイコンを作成（全体、濃い意見、階層）
- Chakra UIのカラーパレット（gray.100, gray.200, gray.500）を使用
- モバイル表示では縦積みレイアウトに対応
- デスクトップ表示では横並びレイアウトに対応

## デザイン意図
- 「ビューの切り替え機能」だと伝えるためにセグメントビューに変更
- 「当たり前に押す機能」としてアピールするため中央配置 & やや大きめサイズ
- 本プロダクトのキモとなる機能のため1つ1つを主張させるためアイコンで表現

## 実装詳細
- Chakra UIのsegmented-controlコンポーネントを使用
- カスタムアイコンを新規作成
- レスポンシブデザインに対応（モバイルでは縦積み、デスクトップでは横並び）

Link to Devin run: https://app.devin.ai/sessions/8b3acd70772247219783e236a211bc16
Requested by: nsk.smn@gmail.com



Diff:
diff --git a/client/components/charts/SelectChartButton.tsx b/client/components/charts/SelectChartButton.tsx
index 90ce45895..c5b8a6d03 100644
--- a/client/components/charts/SelectChartButton.tsx
+++ b/client/components/charts/SelectChartButton.tsx
@@ -1,7 +1,7 @@
-import { RadioCardItem, RadioCardRoot } from "@/components/ui/radio-card";
+import { AllViewIcon, DenseViewIcon, HierarchyViewIcon } from "@/components/icons/ViewIcons";
 import { Tooltip } from "@/components/ui/tooltip";
-import { Button, HStack, Icon, useBreakpointValue } from "@chakra-ui/react";
-import { ChartScatterIcon, CogIcon, FullscreenIcon, MessageCircleWarningIcon, SquareSquareIcon } from "lucide-react";
+import { Box, Button, HStack, Icon, SegmentGroup, Stack, useBreakpointValue } from "@chakra-ui/react";
+import { CogIcon, FullscreenIcon } from "lucide-react";
 import type React from "react";
 
 type Props = {
@@ -19,74 +19,139 @@ export function SelectChartButton({
   onClickFullscreen,
   isDenseGroupEnabled,
 }: Props) {
-  return (
-    <HStack w={"100%"} maxW={"1200px"} mx={"auto"} justify={"space-between"} align={"center"} mb={2}>
-      <RadioCardRoot
-        orientation="horizontal"
-        align="center"
-        justify="center"
-        w={"100%"}
-        maxW={"xl"}
-        size={"sm"}
-        display={"block"}
-        value={selected}
-        onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChange(e.target.value)}
-        colorPalette={"cyan"}
-      >
-        <HStack align={"stretch"}>
-          <RadioCardItem
-            value={"scatterAll"}
-            label={useBreakpointValue({ base: "", md: "全体図" })}
-            indicator={false}
-            icon={
-              <Icon>
-                <ChartScatterIcon />
-              </Icon>
-            }
-            cursor={"pointer"}
-          />
-          <RadioCardItem
-            value={"scatterDensity"}
-            label={useBreakpointValue({ base: "", md: "濃い意見グループ" })}
-            indicator={false}
-            icon={
-              <Icon>
-                <MessageCircleWarningIcon />
-              </Icon>
-            }
-            cursor={"pointer"}
-            disabled={!isDenseGroupEnabled}
-            disabledReason={"この設定条件では抽出できませんでした"}
-          />
-          <RadioCardItem
-            value={"treemap"}
-            label={useBreakpointValue({ base: "", md: "階層図" })}
-            indicator={false}
-            icon={
-              <Icon>
-                <SquareSquareIcon />
-              </Icon>
-            }
-            cursor={"pointer"}
-          />
+  const isVertical = useBreakpointValue({ base: true, sm: true, md: false }) ?? false;
+  const isMobile = useBreakpointValue({ base: true, sm: true, md: false }) ?? false;
+
+  const items = [
+    {
+      value: "scatterAll",
+      label: isVertical ? (
+        <Stack direction="column" gap={1} alignItems="center" px={4} py={2}>
+          <Icon as={AllViewIcon} w="1.5" h="1.5" />
+          <Box
+            color="gray.500"
+            fontWeight={selected === "scatterAll" ? "bold" : "normal"}
+            fontSize="2xs"
+            lineHeight="1"
+          >
+            全体
+          </Box>
+        </Stack>
+      ) : (
+        <HStack>
+          <Icon as={AllViewIcon} w="6" mr={3} />
+          <Box color="gray.500" fontWeight={selected === "scatterAll" ? "bold" : "normal"} px={4} py={2}>
+            全体
+          </Box>
+        </HStack>
+      ),
+      isDisabled: false,
+    },
+    {
+      value: "scatterDensity",
+      label: isVertical ? (
+        <Stack direction="column" gap={1} alignItems="center" px={4} py={2}>
+          <Icon as={DenseViewIcon} w="1.5" h="1.5" />
+          <Box
+            color="gray.500"
+            fontWeight={selected === "scatterDensity" ? "bold" : "normal"}
+            fontSize="2xs"
+            lineHeight="1"
+          >
+            濃い意見
+          </Box>
+        </Stack>
+      ) : (
+        <HStack>
+          <Icon as={DenseViewIcon} w="6" mr={3} />
+          <Box color="gray.500" fontWeight={selected === "scatterDensity" ? "bold" : "normal"} px={4} py={2}>
+            濃い意見
+          </Box>
+        </HStack>
+      ),
+      isDisabled: !isDenseGroupEnabled,
+      tooltip: !isDenseGroupEnabled ? "この設定条件では抽出できませんでした" : undefined,
+    },
+    {
+      value: "treemap",
+      label: isVertical ? (
+        <Stack direction="column" gap={1} alignItems="center" px={4} py={2}>
+          <Icon as={HierarchyViewIcon} w="1.5" h="1.5" />
+          <Box color="gray.500" fontWeight={selected === "treemap" ? "bold" : "normal"} fontSize="2xs" lineHeight="1">
+            階層
+          </Box>
+        </Stack>
+      ) : (
+        <HStack>
+          <Icon as={HierarchyViewIcon} w="6" mr={3} />
+          <Box color="gray.500" fontWeight={selected === "treemap" ? "bold" : "normal"} px={4} py={2}>
+            階層
+          </Box>
         </HStack>
-      </RadioCardRoot>
-      <HStack>
-        <Tooltip content={"表示設定"} openDelay={0} closeDelay={0}>
-          <Button onClick={onClickDensitySetting} variant={"outline"} h={"50px"}>
-            <Icon>
-              <CogIcon />
-            </Icon>
-          </Button>
-        </Tooltip>
-        <Tooltip content={"全画面表示"} openDelay={0} closeDelay={0}>
-          <Button onClick={onClickFullscreen} variant={"outline"} h={"50px"}>
-            <Icon>
-              <FullscreenIcon />
-            </Icon>
-          </Button>
-        </Tooltip>
-      </HStack>
-    </HStack>
+      ),
+      isDisabled: false,
+    },
+  ];
+
+  const handleChange = (event: React.FormEvent<HTMLDivElement>) => {
+    const value = (event.target as HTMLInputElement).value;
+    onChange(value);
+  };
+
+  return (
+    <Box
+      w={"100%"}
+      maxW={"1200px"}
+      mx={"auto"}
+      mb={isMobile ? 16 : 2} // モバイル表示時は下部にボタンを配置するためのスペースを確保
+      position="relative"
+    >
+      {/* セグメントグループを中央に配置 */}
+      <Box w="100%" display="flex" justifyContent="center">
+        <SegmentGroup.Root
+          value={selected}
+          onChange={handleChange}
+          orientation={isVertical ? "vertical" : "horizontal"}
+          size="md"
+          bg="gray.100"
+        >
+          <SegmentGroup.Indicator bg="white" border="1px solid #E4E4E7" boxShadow="0 4px 6px 0 rgba(0, 0, 0, 0.1)" />
+          <SegmentGroup.Items items={items} />
+        </SegmentGroup.Root>
+      </Box>
+
+      {/* ボタンを配置（モバイル表示時は下部中央に配置） */}
+      {isMobile ? (
+        <Box position="absolute" bottom="-60px" left="0" right="0" display="flex" justifyContent="center" mt={4}>
+          <HStack gap={2}>
+            <Tooltip content={"表示設定"} openDelay={0} closeDelay={0}>
+              <Button onClick={onClickDensitySetting} variant={"outline"} h={"40px"} w={"40px"} p={0}>
+                <Icon as={CogIcon} boxSize={5} />
+              </Button>
+            </Tooltip>
+            <Tooltip content={"全画面表示"} openDelay={0} closeDelay={0}>
+              <Button onClick={onClickFullscreen} variant={"outline"} h={"40px"} w={"40px"} p={0}>
+                <Icon as={FullscreenIcon} boxSize={5} />
+              </Button>
+            </Tooltip>
+          </HStack>
+        </Box>
+      ) : (
+        <Box position="absolute" right="0" top="0">
+          <HStack gap={1}>
+            <Tooltip content={"表示設定"} openDelay={0} closeDelay={0}>
+              <Button onClick={onClickDensitySetting} variant={"outline"} h={"40px"} w={"40px"} p={0}>
+                <Icon as={CogIcon} boxSize={5} />
+              </Button>
+            </Tooltip>
+            <Tooltip content={"全画面表示"} openDelay={0} closeDelay={0}>
+              <Button onClick={onClickFullscreen} variant={"outline"} h={"40px"} w={"40px"} p={0}>
+                <Icon as={FullscreenIcon} boxSize={5} />
+              </Button>
+            </Tooltip>
+          </HStack>
+        </Box>
+      )}
+    </Box>
   );
 }
diff --git a/client/components/icons/ViewIcons.tsx b/client/components/icons/ViewIcons.tsx
new file mode 100644
index 000000000..9ec95b846
--- /dev/null
+++ b/client/components/icons/ViewIcons.tsx
@@ -0,0 +1,22 @@
+import type React from "react";
+
+export const AllViewIcon: React.FC = () => (
+  <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <title>All View</title>
+    <image href="/images/icons/all_icon.svg" width="24" height="24" />
+  </svg>
+);
+
+export const DenseViewIcon: React.FC = () => (
+  <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <title>Dense View</title>
+    <image href="/images/icons/dense_icon.svg" width="24" height="24" />
+  </svg>
+);
+
+export const HierarchyViewIcon: React.FC = () => (
+  <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <title>Hierarchy View</title>
+    <image href="/images/icons/hierarchy_icon.svg" width="24" height="24" />
+  </svg>
+);
diff --git a/client/public/images/icons/all_icon.svg b/client/public/images/icons/all_icon.svg
new file mode 100644
index 000000000..6ece9732b
--- /dev/null
+++ b/client/public/images/icons/all_icon.svg
@@ -0,0 +1,10 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M3 11C4.10457 11 5 10.1046 5 9C5 7.89543 4.10457 7 3 7C1.89543 7 1 7.89543 1 9C1 10.1046 1.89543 11 3 11Z" fill="black"/>
+<path d="M9 5C10.1046 5 11 4.10457 11 3C11 1.89543 10.1046 1 9 1C7.89543 1 7 1.89543 7 3C7 4.10457 7.89543 5 9 5Z" fill="black"/>
+<path d="M15 11C16.1046 11 17 10.1046 17 9C17 7.89543 16.1046 7 15 7C13.8954 7 13 7.89543 13 9C13 10.1046 13.8954 11 15 11Z" fill="black"/>
+<path d="M21 5C22.1046 5 23 4.10457 23 3C23 1.89543 22.1046 1 21 1C19.8954 1 19 1.89543 19 3C19 4.10457 19.8954 5 21 5Z" fill="black"/>
+<path d="M3 23C4.10457 23 5 22.1046 5 21C5 19.8954 4.10457 19 3 19C1.89543 19 1 19.8954 1 21C1 22.1046 1.89543 23 3 23Z" fill="black"/>
+<path d="M9 17C10.1046 17 11 16.1046 11 15C11 13.8954 10.1046 13 9 13C7.89543 13 7 13.8954 7 15C7 16.1046 7.89543 17 9 17Z" fill="black"/>
+<path d="M15 23C16.1046 23 17 22.1046 17 21C17 19.8954 16.1046 19 15 19C13.8954 19 13 19.8954 13 21C13 22.1046 13.8954 23 15 23Z" fill="black"/>
+<path d="M21 17C22.1046 17 23 16.1046 23 15C23 13.8954 22.1046 13 21 13C19.8954 13 19 13.8954 19 15C19 16.1046 19.8954 17 21 17Z" fill="black"/>
+</svg>
diff --git a/client/public/images/icons/dense_icon.svg b/client/public/images/icons/dense_icon.svg
new file mode 100644
index 000000000..6243ab4be
--- /dev/null
+++ b/client/public/images/icons/dense_icon.svg
@@ -0,0 +1,5 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M3.5 11C4.88071 11 6 9.88071 6 8.5C6 7.11929 4.88071 6 3.5 6C2.11929 6 1 7.11929 1 8.5C1 9.88071 2.11929 11 3.5 11Z" fill="black"/>
+<path d="M20.5 6C21.8807 6 23 4.88071 23 3.5C23 2.11929 21.8807 1 20.5 1C19.1193 1 18 2.11929 18 3.5C18 4.88071 19.1193 6 20.5 6Z" fill="black"/>
+<path d="M14.5 23C15.8807 23 17 21.8807 17 20.5C17 19.1193 15.8807 18 14.5 18C13.1193 18 12 19.1193 12 20.5C12 21.8807 13.1193 23 14.5 23Z" fill="black"/>
+</svg>
diff --git a/client/public/images/icons/hierarchy_icon.svg b/client/public/images/icons/hierarchy_icon.svg
new file mode 100644
index 000000000..e658aa5b7
--- /dev/null
+++ b/client/public/images/icons/hierarchy_icon.svg
@@ -0,0 +1,5 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect x="7" y="12" width="15" height="10" rx="2" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+<rect x="2" y="2" width="10" height="10" rx="2" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+<rect x="12" y="2" width="7" height="10" rx="2" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>


Reviews:
user: login: shgtkshruch
body: パッと見て気になったところと疑問点についてコメントさせていただきました！
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T09:20:57Z
user: login: nasuka
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T09:32:51Z

Review comments:
diff_hunk: @@ -19,74 +19,139 @@ export function SelectChartButton({
   onClickFullscreen,
   isDenseGroupEnabled,
 }: Props) {
-  return (
-    <HStack w={"100%"} maxW={"1200px"} mx={"auto"} justify={"space-between"} align={"center"} mb={2}>
-      <RadioCardRoot
-        orientation="horizontal"
-        align="center"
-        justify="center"
-        w={"100%"}
-        maxW={"xl"}
-        size={"sm"}
-        display={"block"}
-        value={selected}
-        onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChange(e.target.value)}
-        colorPalette={"cyan"}
-      >
-        <HStack align={"stretch"}>
-          <RadioCardItem
-            value={"scatterAll"}
-            label={useBreakpointValue({ base: "", md: "全体図" })}
-            indicator={false}
-            icon={
-              <Icon>
-                <ChartScatterIcon />
-              </Icon>
-            }
-            cursor={"pointer"}
-          />
-          <RadioCardItem
-            value={"scatterDensity"}
-            label={useBreakpointValue({ base: "", md: "濃い意見グループ" })}
-            indicator={false}
-            icon={
-              <Icon>
-                <MessageCircleWarningIcon />
-              </Icon>
-            }
-            cursor={"pointer"}
-            disabled={!isDenseGroupEnabled}
-            disabledReason={"この設定条件では抽出できませんでした"}
-          />
-          <RadioCardItem
-            value={"treemap"}
-            label={useBreakpointValue({ base: "", md: "階層図" })}
-            indicator={false}
-            icon={
-              <Icon>
-                <SquareSquareIcon />
-              </Icon>
-            }
-            cursor={"pointer"}
-          />
+  const isVertical = useBreakpointValue({ base: true, sm: true, md: false }) ?? false;
+  const isMobile = useBreakpointValue({ base: true, sm: true, md: false }) ?? false;
+
+  const items = [
+    {
+      value: "scatterAll",
+      label: isVertical ? (
+        <Stack direction="column" gap={1} alignItems="center" px={4} py={2}>
+          <Icon as={AllViewIcon} w="1.5" h="1.5" />
+          <Box
+            color="gray.500"
+            fontWeight={selected === "scatterAll" ? "bold" : "normal"}
+            fontSize="2xs"
+            lineHeight="1"
+          >
+            全体
+          </Box>
+        </Stack>
+      ) : (
+        <HStack>
+          <Icon as={AllViewIcon} w="6" mr={3} />
+          <Box color="gray.500" fontWeight={selected === "scatterAll" ? "bold" : "normal"} px={4} py={2}>
+            全体
+          </Box>
+        </HStack>
+      ),
+      isDisabled: false,
+    },
+    {
+      value: "scatterDensity",
+      label: isVertical ? (
+        <Stack direction="column" gap={1} alignItems="center" px={4} py={2}>
+          <Icon as={DenseViewIcon} w="1.5" h="1.5" />
+          <Box
+            color="gray.500"
+            fontWeight={selected === "scatterDensity" ? "bold" : "normal"}
+            fontSize="2xs"
+            lineHeight="1"
+          >
+            濃い意見
+          </Box>
+        </Stack>
+      ) : (
+        <HStack>
+          <Icon as={DenseViewIcon} w="6" mr={3} />
+          <Box color="gray.500" fontWeight={selected === "scatterDensity" ? "bold" : "normal"} px={4} py={2}>
+            濃い意見
+          </Box>
+        </HStack>
+      ),
+      isDisabled: !isDenseGroupEnabled,
+      tooltip: !isDenseGroupEnabled ? "この設定条件では抽出できませんでした" : undefined,
+    },
+    {
+      value: "treemap",
+      label: isVertical ? (
+        <Stack direction="column" gap={1} alignItems="center" px={4} py={2}>
+          <Icon as={HierarchyViewIcon} w="1.5" h="1.5" />
+          <Box color="gray.500" fontWeight={selected === "treemap" ? "bold" : "normal"} fontSize="2xs" lineHeight="1">
+            階層
+          </Box>
+        </Stack>
+      ) : (
+        <HStack>
+          <Icon as={HierarchyViewIcon} w="6" mr={3} />
+          <Box color="gray.500" fontWeight={selected === "treemap" ? "bold" : "normal"} px={4} py={2}>
+            階層
+          </Box>
         </HStack>
-      </RadioCardRoot>
-      <HStack>
-        <Tooltip content={"表示設定"} openDelay={0} closeDelay={0}>
-          <Button onClick={onClickDensitySetting} variant={"outline"} h={"50px"}>
-            <Icon>
-              <CogIcon />
-            </Icon>
-          </Button>
-        </Tooltip>
-        <Tooltip content={"全画面表示"} openDelay={0} closeDelay={0}>
-          <Button onClick={onClickFullscreen} variant={"outline"} h={"50px"}>
-            <Icon>
-              <FullscreenIcon />
-            </Icon>
-          </Button>
-        </Tooltip>
-      </HStack>
-    </HStack>
+      ),
+      isDisabled: false,
+    },
+  ];
+
+  const handleChange = (event: React.FormEvent<HTMLDivElement>) => {
+    const value = (event.target as HTMLInputElement).value;
+    onChange(value);
+  };
+
+  return (
+    <Box
+      w={"100%"}
+      maxW={"1200px"}
+      mx={"auto"}
+      mb={isMobile ? 16 : 2} // モバイル表示時は下部にボタンを配置するためのスペースを確保
+      position="relative"
+    >
+      {/* セグメントグループを中央に配置 */}
+      <Box w="100%" display="flex" justifyContent="center">
+        <SegmentGroup.Root
+          value={selected}
+          onChange={handleChange}
+          orientation={isVertical ? "vertical" : "horizontal"}
+          size="md"
+          bg="gray.100"
+        >
+          <SegmentGroup.Indicator bg="white" border="1px solid #E4E4E7" boxShadow="0 4px 6px 0 rgba(0, 0, 0, 0.1)" />
+          <SegmentGroup.Items items={items} />
+        </SegmentGroup.Root>
+      </Box>
+
+      {/* ボタンを配置（モバイル表示時は下部中央に配置） */}
+      {isMobile ? (
+        <Box position="absolute" bottom="-60px" left="0" right="0" display="flex" justifyContent="center" mt={4}>
+          <HStack gap={2}>
+            <Tooltip content={"表示設定"} openDelay={0} closeDelay={0}>
+              <Button onClick={onClickDensitySetting} variant={"outline"} h={"40px"} w={"40px"} p={0}>
+                <Icon as={CogIcon} boxSize={5} />
+              </Button>
+            </Tooltip>
+            <Tooltip content={"全画面表示"} openDelay={0} closeDelay={0}>
+              <Button onClick={onClickFullscreen} variant={"outline"} h={"40px"} w={"40px"} p={0}>
+                <Icon as={FullscreenIcon} boxSize={5} />
+              </Button>
+            </Tooltip>
+          </HStack>
+        </Box>
+      ) : (
+        <Box position="absolute" right="0" top="0">
+          <HStack gap={1}>
+            <Tooltip content={"表示設定"} openDelay={0} closeDelay={0}>
+              <Button onClick={onClickDensitySetting} variant={"outline"} h={"40px"} w={"40px"} p={0}>
+                <Icon as={CogIcon} boxSize={5} />
+              </Button>
+            </Tooltip>
+            <Tooltip content={"全画面表示"} openDelay={0} closeDelay={0}>
+              <Button onClick={onClickFullscreen} variant={"outline"} h={"40px"} w={"40px"} p={0}>
+                <Icon as={FullscreenIcon} boxSize={5} />
+              </Button>
+            </Tooltip>
+          </HStack>
+        </Box>
+      )}
+    </Box>
path: client/components/charts/SelectChartButton.tsx
user: login: shgtkshruch
body: suggestion: FlexBox だと Figma のようにレイアウト切り替えるの難しそうですが、Gird を使いつつ、Chakra UI では 、プロパティを配列で渡すとブレイクポイントに応じて切り替えてくれるので、これを合わせると `isMobile` で条件分岐しなくてもレイアウトの切り替えができそうです。
ref: https://chakra-ui.com/docs/styling/responsive-design
created_at: 2025-05-20T08:57:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,22 @@
+import type React from "react";
+
+export const AllViewIcon: React.FC = () => (
+  <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <title>All View</title>
+    <image href="/images/icons/all_icon.svg" width="24" height="24" />
+  </svg>
+);
+
+export const DenseViewIcon: React.FC = () => (
+  <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <title>Dense View</title>
+    <image href="/images/icons/dense_icon.svg" width="24" height="24" />
+  </svg>
+);
+
+export const HierarchyViewIcon: React.FC = () => (
+  <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <title>Hierarchy View</title>
+    <image href="/images/icons/hierarchy_icon.svg" width="24" height="24" />
+  </svg>
path: client/components/icons/ViewIcons.tsx
user: login: shgtkshruch
body: question: ここで `<image>` で埋め込んでいる理由ってありますか？
`<image>` で埋め込むとアイコンごとに HTTP リクエストが飛んでいるので、特段の理由がなければそのまま SVG として埋め込んであげるとよさそうに思いました。
![image](https://github.com/user-attachments/assets/c322c841-8805-43b8-9d21-e84e7ceaac4d)

created_at: 2025-05-20T08:59:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -19,74 +19,139 @@ export function SelectChartButton({
   onClickFullscreen,
   isDenseGroupEnabled,
 }: Props) {
-  return (
-    <HStack w={"100%"} maxW={"1200px"} mx={"auto"} justify={"space-between"} align={"center"} mb={2}>
-      <RadioCardRoot
-        orientation="horizontal"
-        align="center"
-        justify="center"
-        w={"100%"}
-        maxW={"xl"}
-        size={"sm"}
-        display={"block"}
-        value={selected}
-        onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChange(e.target.value)}
-        colorPalette={"cyan"}
-      >
-        <HStack align={"stretch"}>
-          <RadioCardItem
-            value={"scatterAll"}
-            label={useBreakpointValue({ base: "", md: "全体図" })}
-            indicator={false}
-            icon={
-              <Icon>
-                <ChartScatterIcon />
-              </Icon>
-            }
-            cursor={"pointer"}
-          />
-          <RadioCardItem
-            value={"scatterDensity"}
-            label={useBreakpointValue({ base: "", md: "濃い意見グループ" })}
-            indicator={false}
-            icon={
-              <Icon>
-                <MessageCircleWarningIcon />
-              </Icon>
-            }
-            cursor={"pointer"}
-            disabled={!isDenseGroupEnabled}
-            disabledReason={"この設定条件では抽出できませんでした"}
-          />
-          <RadioCardItem
-            value={"treemap"}
-            label={useBreakpointValue({ base: "", md: "階層図" })}
-            indicator={false}
-            icon={
-              <Icon>
-                <SquareSquareIcon />
-              </Icon>
-            }
-            cursor={"pointer"}
-          />
+  const isVertical = useBreakpointValue({ base: true, sm: true, md: false }) ?? false;
+  const isMobile = useBreakpointValue({ base: true, sm: true, md: false }) ?? false;
+
+  const items = [
+    {
+      value: "scatterAll",
+      label: isVertical ? (
+        <Stack direction="column" gap={1} alignItems="center" px={4} py={2}>
+          <Icon as={AllViewIcon} w="1.5" h="1.5" />
+          <Box
+            color="gray.500"
+            fontWeight={selected === "scatterAll" ? "bold" : "normal"}
+            fontSize="2xs"
+            lineHeight="1"
+          >
+            全体
+          </Box>
+        </Stack>
+      ) : (
+        <HStack>
+          <Icon as={AllViewIcon} w="6" mr={3} />
+          <Box color="gray.500" fontWeight={selected === "scatterAll" ? "bold" : "normal"} px={4} py={2}>
+            全体
+          </Box>
+        </HStack>
+      ),
+      isDisabled: false,
+    },
+    {
+      value: "scatterDensity",
+      label: isVertical ? (
+        <Stack direction="column" gap={1} alignItems="center" px={4} py={2}>
+          <Icon as={DenseViewIcon} w="1.5" h="1.5" />
+          <Box
+            color="gray.500"
+            fontWeight={selected === "scatterDensity" ? "bold" : "normal"}
+            fontSize="2xs"
+            lineHeight="1"
+          >
+            濃い意見
+          </Box>
+        </Stack>
+      ) : (
+        <HStack>
+          <Icon as={DenseViewIcon} w="6" mr={3} />
+          <Box color="gray.500" fontWeight={selected === "scatterDensity" ? "bold" : "normal"} px={4} py={2}>
+            濃い意見
+          </Box>
+        </HStack>
+      ),
+      isDisabled: !isDenseGroupEnabled,
+      tooltip: !isDenseGroupEnabled ? "この設定条件では抽出できませんでした" : undefined,
+    },
+    {
+      value: "treemap",
+      label: isVertical ? (
+        <Stack direction="column" gap={1} alignItems="center" px={4} py={2}>
+          <Icon as={HierarchyViewIcon} w="1.5" h="1.5" />
+          <Box color="gray.500" fontWeight={selected === "treemap" ? "bold" : "normal"} fontSize="2xs" lineHeight="1">
+            階層
+          </Box>
+        </Stack>
+      ) : (
+        <HStack>
+          <Icon as={HierarchyViewIcon} w="6" mr={3} />
+          <Box color="gray.500" fontWeight={selected === "treemap" ? "bold" : "normal"} px={4} py={2}>
+            階層
+          </Box>
         </HStack>
-      </RadioCardRoot>
-      <HStack>
-        <Tooltip content={"表示設定"} openDelay={0} closeDelay={0}>
-          <Button onClick={onClickDensitySetting} variant={"outline"} h={"50px"}>
-            <Icon>
-              <CogIcon />
-            </Icon>
-          </Button>
-        </Tooltip>
-        <Tooltip content={"全画面表示"} openDelay={0} closeDelay={0}>
-          <Button onClick={onClickFullscreen} variant={"outline"} h={"50px"}>
-            <Icon>
-              <FullscreenIcon />
-            </Icon>
-          </Button>
-        </Tooltip>
-      </HStack>
-    </HStack>
+      ),
+      isDisabled: false,
+    },
+  ];
+
+  const handleChange = (event: React.FormEvent<HTMLDivElement>) => {
+    const value = (event.target as HTMLInputElement).value;
+    onChange(value);
+  };
+
+  return (
+    <Box
+      w={"100%"}
+      maxW={"1200px"}
+      mx={"auto"}
+      mb={isMobile ? 16 : 2} // モバイル表示時は下部にボタンを配置するためのスペースを確保
+      position="relative"
+    >
+      {/* セグメントグループを中央に配置 */}
+      <Box w="100%" display="flex" justifyContent="center">
+        <SegmentGroup.Root
+          value={selected}
+          onChange={handleChange}
+          orientation={isVertical ? "vertical" : "horizontal"}
+          size="md"
+          bg="gray.100"
+        >
+          <SegmentGroup.Indicator bg="white" border="1px solid #E4E4E7" boxShadow="0 4px 6px 0 rgba(0, 0, 0, 0.1)" />
+          <SegmentGroup.Items items={items} />
+        </SegmentGroup.Root>
+      </Box>
path: client/components/charts/SelectChartButton.tsx
user: login: shgtkshruch
body: IMO: SegmentGroup の余白などのスタイルやレイアウトも Figma と差分があるので、揃えられるとよさそう 👀 

### Figma
![image](https://github.com/user-attachments/assets/e84e4e9a-129e-4e2b-851d-55f4a7556d38)

### 実装
![image](https://github.com/user-attachments/assets/6c38f024-11ff-4cfe-ab3a-ef843fd8af9e)

created_at: 2025-05-20T09:16:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,22 @@
+import type React from "react";
+
+export const AllViewIcon: React.FC = () => (
+  <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <title>All View</title>
+    <image href="/images/icons/all_icon.svg" width="24" height="24" />
+  </svg>
+);
+
+export const DenseViewIcon: React.FC = () => (
+  <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <title>Dense View</title>
+    <image href="/images/icons/dense_icon.svg" width="24" height="24" />
+  </svg>
+);
+
+export const HierarchyViewIcon: React.FC = () => (
+  <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <title>Hierarchy View</title>
+    <image href="/images/icons/hierarchy_icon.svg" width="24" height="24" />
+  </svg>
path: client/components/icons/ViewIcons.tsx
user: login: nasuka
body: @shgtkshruch 
すみません、私のフロント力が低過ぎて ai generatedなコードをそのまま受け入れただけなのでこちらは特に意図はないです... :bow:
created_at: 2025-05-20T09:32:51Z
author_association: CONTRIBUTOR

PR comments:
user: login: devin-ai-integration[bot]
created_at: 2025-05-12T06:28:03Z
body: ### 🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:
- Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
- Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:
- [ ] Disable automatic comment and CI monitoring

author_association: CONTRIBUTOR
user: login: coderabbitai[bot]
created_at: 2025-05-12T06:28:03Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: skip review by coderabbit.ai -->

> [!IMPORTANT]
> ## Review skipped
> 
> Bot user detected.
> 
> To trigger a single review, invoke the `@coderabbitai review` command.
> 
> You can disable this status message by setting the `reviews.review_status` to `false` in the CodeRabbit configuration file.

<!-- end of auto-generated comment: skip review by coderabbit.ai -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=digitaldemocracy2030/kouchou-ai&utm_content=487):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Join our [Discord community](https://discord.gg/coderabbit) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: nasuka
created_at: 2025-05-12T07:28:12Z
body: Devinに実装させてみて、現在調整中
author_association: CONTRIBUTOR
user: login: nasuka
created_at: 2025-05-13T05:08:56Z
body: 手元で少し調整しましたが、自分だと時間がかかりそうだったのでこちらはcloseします
author_association: CONTRIBUTOR
user: login: nasuka
created_at: 2025-05-13T06:26:01Z
body: (aside) 一通り実装した

# UI
figmaデザインに従って以下のように実装。
一応最低限は動作するようになっている。また、各ビューが切り替えられることは確認済み。

PC
![image](https://github.com/user-attachments/assets/50318efa-0a49-4adb-ba67-5270a483cd61)

モバイル
![image](https://github.com/user-attachments/assets/75b29c4b-eb07-4e49-9e13-d3aad0018a8c)

# 困っていること
* レスポンシブ対応・モバイル対応について現状の実装で問題ないか？
  * isMobileの分岐が各所に出現しており、実現したいことに対して煩雑な実装になってしまっている気がする（が、フロントのベストプラクティスがわかっておらず解決方法が思いついていません）
  * よりよい実装方針があれば教えてもらえると助かります（もしくは直接このPRにコミット・pushしてもらっても大丈夫です

author_association: CONTRIBUTOR
user: login: nasuka
created_at: 2025-05-14T07:12:21Z
body: (aside)
一通り実装できたのでどなたかレビューいただけると助かります。

PC
![スクリーンショット 2025-05-14 15 48 14](https://github.com/user-attachments/assets/5ba4c6c0-1c9c-4251-975d-5b2e0aa48539)

mobile
![スクリーンショット 2025-05-14 15 48 07](https://github.com/user-attachments/assets/9a3ec675-669a-43b9-94f4-815ad08f219d)

author_association: CONTRIBUTOR
user: login: nasuka
created_at: 2025-05-14T07:14:34Z
body: DevinのPRで、テンプレートに従っていないので置いておきます。

# マージ前のチェックリスト（レビュアーがマージ前に確認してください）
- [ ] CIが全て通過している
- [ ] 単体テストが実装されているか
今回はコンポーネントの実装のみなので実装していません（もしこういうテストを書いた方が良い、などあれば教えていただけると助かります）
- [ ] 今回実装した機能および影響を受けると思われる機能について、適切な動作確認が行われているかを確認する。
author_association: CONTRIBUTOR
user: login: masatosasano2
created_at: 2025-05-20T05:58:24Z
body: 変更前だとモバイルモードで
- ボタンのキャプションが表示されない
- ビュー切り替えのボタン3つとその横のボタン2つが1列に並んでいる
となっており、こちらの方が視認性が良さそうです。
モバイルモードでのみCSSを変更できたりしないでしょうか。
author_association: CONTRIBUTOR
user: login: nasuka
created_at: 2025-05-20T06:28:56Z
body: @masatosasano2 
コメントありがとうございます！


> ボタンのキャプションが表示されない

個人的には、キャプションが表示されないほうがデザインとしてはスッキリすると思う一方で、アイコンだけだとどのようなビューなのかが伝わりにくいのでキャプションはあっても良さそうに思いました。

>ビュー切り替えのボタン3つとその横のボタン2つが1列に並んでいる

こちらは確かに1列に並んでいたほうが見やすいような気もしました。

上記、デザインいただいた @UtkNggc さんのご意見も伺えますと幸いです！
author_association: CONTRIBUTOR
user: login: UtkNggc
created_at: 2025-05-20T07:35:13Z
body: @nasuka こちら先週に完了してたのですね見逃してました..! おそくなりましたmm

デザイン観点でのレビューとしては、スタイリング調整を依頼したいです！
・アイコンサイズ
・アイコンカラー
・セグメントのpadding
・アイコンと文字の間の余白
などのcssが意図したデザインと異なってるように見えます。
ブラウザ確認の方法がわからず、検証ツールで確認したわけではないので具体的な数値を伝えられず申し訳ないですが、この辺りのスタイリングの調整はお願いできますでしょうか。（もしかしてここからはフロントエンド担当者の作業範囲になりますか？？）
author_association: COLLABORATOR
user: login: UtkNggc
created_at: 2025-05-20T07:47:32Z
body: @masatosasano2 
スマホビューで現状のままがいいのではという案についてうけたまわりました！
アイコンが横一列に並んでる現状の姿は、同じ要素が同じサイズで並んでいるのでたしかに一覧性でいうと高くなりますが、操作性・この機能の重要度・機能学習してもらう手間、などを総合して判断するとデザイン原案をおすすめします。
author_association: COLLABORATOR
user: login: nasuka
created_at: 2025-05-20T08:49:16Z
body: @UtkNggc 
明示的にレビュー依頼できていなかったこちらの落ち度なのでお気になさらないでください...！ご確認いただきありがとうございます！


> 検証ツールで確認したわけではないので具体的な数値を伝えられず申し訳ないですが、この辺りのスタイリングの調整はお願いできますでしょうか。

すみません、こちら一旦私の方で実装してみたのですが自分がフロントに明るくなく、細かいスタイリングについては本職のフロントエンドエンジニアの方に引き継げればと思っています :bow:
後ほどslackで募集をかけますね。

author_association: CONTRIBUTOR
user: login: nasuka
created_at: 2025-05-20T09:19:31Z
body: (aside)
現状に関する補足コメント

* このイシューでは、以下のデザインを実装しようとしている
  * https://github.com/digitaldemocracy2030/kouchou-ai/issues/113#issuecomment-2865049397
  * figmaはこちら
    * https://www.figma.com/design/ZImSumdtUme9loVY5CejWX/%E5%BA%83%E8%81%B4AI%EF%BC%88%E3%83%87%E3%82%B8%E6%B0%912030%EF%BC%89?node-id=171-2&t=GXKzpACguMXTJ59Z-0
* 一通り自分の方で実装したが、スタイリングについてはウタコさんが意図したものと異なっている
author_association: CONTRIBUTOR