kirin-ri / memo

0 stars 0 forks source link

sssssss #23

Open kirin-ri opened 6 months ago

kirin-ri commented 6 months ago

az network nic list --resource-group <リソースグループ名> --query "[].{NICName:name, PublicIPId:ipConfigurations[0].publicIpAddress.id}" -o tsv

az network public-ip show --ids <パブリックIP ID> --query "{PublicIP: ipAddress}"

kirin-ri commented 6 months ago
<html>
<body>
<!--StartFragment-->
Linux version 6.2.0-1014-azure (buildd@lcy02-amd64-030) (x86_64-linux-gnu-gcc-11 (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #14~22.04.1-Ubuntu SMP Wed Sep 13 16:15:26 UTC 2023
--

<!--EndFragment-->
</body>
</html>
kirin-ri commented 6 months ago
import cytoscape, { Collection, NodeCollection } from 'cytoscape';
import cxtmenu from 'cytoscape-cxtmenu';
import dagre from 'cytoscape-dagre';
// @ts-ignore
import cyHtmlLabel from 'cytoscape-node-html-label';
import { useEffect, useRef, useState } from 'react';
import { commonAjax } from '../../../components/commonAjax';

cytoscape.use(dagre);
cytoscape.use(cxtmenu);
cytoscape.use(cyHtmlLabel);

function digitalValueTree() {
  const graphContainer = useRef<HTMLDivElement>(null);
  const [dispElements, setDispElements] = useState<[]>([]);
  const [elementsStyle, setElementsStyle] = useState<[]>([]);
  const [contentHeight, setContentHeight] = useState<number | string>(0);
  const [refreshFlg, setRefreshFlg] = useState<boolean>(true);
  const [perspectiveCheck, setPerspectiveCheck] = useState<boolean>(true);
  const [simulationCheck, setSimulationCheck] = useState<boolean>(false);
  const [targetMetrics, setTargetMetrics] = useState<string>('');

  let tappedBefore: null;
  let tappedTimeout: string | number | NodeJS.Timeout | undefined;

  // 視点フィルター変更時イベント
  function changePerspective() {
    const vals: string[] = [];
    $('input[class="form-check-input"]:checked').each(function () {
      vals.push($(this)[0].id);
    });
    const req = { perspectives: vals };
    (async () => {
      commonAjax
        .axios({ loading: true })
        .post('/api/digitalValueTreeInfo', req)
        .then((res) => {
          setDispElements(res.data.elements);
          setElementsStyle(res.data.style);
          setRefreshFlg(!refreshFlg);
        });
    })();
  }

  // シミュレーションモード変更時変更時イベント
  function changeSimulationMode() {
    const CheckBox = document.querySelector(
      "input[type='checkbox']#simulationCheckBox",
    ) as HTMLInputElement;
    setSimulationCheck(CheckBox.checked);
    setRefreshFlg(!refreshFlg);
  }

  // 全画面モード選択時イベント
  function fullscreen() {
    document.getElementById('contentBody')!.requestFullscreen();
    // setContentHeight(window.outerHeight);
    // setContentHeight('100%');
    // setRefreshFlg(!refreshFlg);
  }

  // フルスクリーン切替時イベント
  function fullscreenchanged() {
    if (document.fullscreenElement) {
      // setContentHeight('100%');
      setContentHeight(window.outerHeight);
      setRefreshFlg(!refreshFlg);
      // document.getElementById('contentBody')!.style.backgroundColor = 'white';
    } else {
      setContentHeight(
        window.innerHeight -
          document.getElementsByClassName('main-header')[0].clientHeight -
          document.getElementsByClassName('page-cover')[0].clientHeight -
          document.getElementsByClassName('content-header')[0].clientHeight -
          42,
      );
      setRefreshFlg(!refreshFlg);
      // document.getElementById('contentBody')!.style.backgroundColor =
      //   'var(--content-bg-color)';
    }
  }

  // フルスクリーン切替イベントリスナー
  document.addEventListener('fullscreenchange', fullscreenchanged);

  useEffect(() => {
    (async () => {
      const vals = [] as any;
      const req = { perspectives: vals };
      commonAjax
        .axios({ loading: true })
        .post('/api/digitalValueTreeInfo', req)
        .then((res) => {
          setDispElements(res.data.elements);
          setElementsStyle(res.data.style);
          setContentHeight(
            window.innerHeight -
              document.getElementsByClassName('main-header')[0].clientHeight -
              document.getElementsByClassName('page-cover')[0].clientHeight -
              document.getElementsByClassName('content-header')[0]
                .clientHeight -
              42,
          );
          setRefreshFlg(!refreshFlg);
        });
    })();
  }, []);

  useEffect(() => {
    if (graphContainer.current) {
      // cytoscape init
      const cy = cytoscape({
        wheelSensitivity: 0.1, // マウスホイール拡大縮小感度
        container: graphContainer.current,
        elements: dispElements,
        style: elementsStyle,
      });

      // ノード幅動的設定
      const labelWidth = (node: NodeCollection): number => {
        const canvas = document.createElement('canvas');
        const context = canvas.getContext('2d')!;
        context.font = ['style', 'weight', 'size', 'family']
          .map((style) => node.style(`font-${style}`))
          .join(' ');
        const { width } = context.measureText(node.data('label'));
        const padding = 20;
        return width + padding;
      };

      // シュミレーションモード分岐
      if (simulationCheck) {
        /* *********************************
         ここにシュミレーション処理追加!!!
         ********************************* */
        // sample-ノードのラベルにHTMLを表示するための設定
        // @ts-ignore
        cy.nodeHtmlLabel(
          [
            {
              query: 'node[group="metrics"]',
              halign: 'center',
              valign: 'center',
              tpl: (
                data: any,
              ) => `<table style="border: 2px solid rgb(140 140 140);">
                      <thead>
                        <tr>
                          <th scope="col" style="border: 1px solid rgb(160 160 160)">head1</th>
                          <th scope="col" style="border: 1px solid rgb(160 160 160)">head2</th>
                        </tr>
                      </thead>
                      <tbody>
                        <tr>
                          <th style="border: 1px solid rgb(160 160 160)">col1</th>
                          <td style="border: 1px solid rgb(160 160 160)">col2</td>
                        </tr>
                      </tbody>
                    </table>
                            `,
            },
            // {
            //   query: "node[id='metrics2']",
            //   halign: 'center',
            //   valign: 'center',
            //   tpl: (data: any) =>
            //     `<div style="background-color: red; border-radius:10px; padding:5; margin:0 0 45 130;">badge</div>`,
            // },
            // {
            //   query: "node[id='metrics3']",
            //   halign: 'center',
            //   valign: 'center',
            //   tpl: (data: any) => `<input id="sssss" style="margin:50 0 0 0;"/>`,
            // },
          ],
          // {
          //   enablePointerEvents: true,
          // },
        );

        cy.nodes("not(node[group='metrics'])").forEach((node) => {
          node.style('width', labelWidth(node));
        });
        cy.nodes('node[group="metrics"]').forEach((ele) => {
          ele.style('width', '140');
          ele.style('height', '150');
          ele.style('text-valign', 'top');
          ele.style('text-halign', 'center');
        });
      } else {
        // cxtmenu
        const metricsCxtmenu = {
          selector: 'node[group="metrics"]',
          menuRadius: 75,
          activePadding: 8,
          openMenuEvents: 'tap',
          // openMenuEvents: 'cxttapstart',
          // openMenuEvents: 'cxttapstart taphold',
          outsideMenuCancel: 8,
          menuItemTextSize: 100,
          commands: (ele: Collection) => [
            {
              content: '詳細',
              contentStyle: {},
              enabled: ele.data('logical_name') !== null,
              select(element: any) {
                const openNewTab = (url: string) => {
                  const newWindow = window.open(url, '_blank');
                  if (newWindow) {
                    newWindow.opener = null;
                  }
                };
                const targetId = element.data('logical_name');
                openNewTab(`/#/scm-metrics-details/${targetId}`);
                // history.push(`/scm-metrics-details/${targetId}`);//新規タブで開かない場合
              },
            },
            {
              content: 'リレーションハイライト',
              contentStyle: {},
              enabled: true,
              select(element: any) {
                cy.elements().removeClass('unhighlighted'); // 既存のクラスをクリア
                cy.elements().removeClass('highlighted'); // 既存のクラスをクリア
                // element.outgoers().addClass('highlighted'); // 接続されているエッジとノードをハイライト
                element.predecessors().addClass('highlighted'); // 上流エッジとノードをハイライト
                element.successors().addClass('highlighted'); // 下流エッジとノードをハイライト
                cy.elements()
                  // .not(element.outgoers())
                  .not(element.predecessors()) // 上流
                  .not(element.successors()) // 下流
                  .not(element) // 選択ノード
                  .not('node[group="hidden_item"]') // 選択ノード
                  .not('node[group="perspective"]') // 選択ノード
                  .addClass('unhighlighted'); // それ以外をグレーアウト
              },
            },
            {
              content: 'シミュレーション',
              contentStyle: {},
              enabled: false,
            },
            {
              content: '参照データ',
              contentStyle: {},
              enabled: ele.data('logical_name') !== null,
              select(element: any) {
                setTargetMetrics(element.data('id'));
              },
            },
          ],
        };
        const otherCxtmenu = {
          selector:
            'node[group="financial_parent"],node[group="financial_child"],node[group="pl"],node[group="bs"],node[group="reform"],node[group="measure_category"],node[group="measure"]',
          menuRadius: 75,
          activePadding: 8,
          openMenuEvents: 'tap',
          // openMenuEvents: 'cxttapstart',
          // openMenuEvents: 'cxttapstart taphold',
          outsideMenuCancel: 8,
          menuItemTextSize: 100,
          commands: [
            {
              content: 'リレーションハイライト',
              contentStyle: {},
              enabled: true,
              select(element: any) {
                cy.elements().removeClass('unhighlighted'); // 既存のクラスをクリア
                cy.elements().removeClass('highlighted'); // 既存のクラスをクリア
                // element.outgoers().addClass('highlighted'); // 接続されているエッジとノードをハイライト
                element.predecessors().addClass('highlighted'); // 上流エッジとノードをハイライト
                element.successors().addClass('highlighted'); // 下流エッジとノードをハイライト
                cy.elements()
                  // .not(element.outgoers())
                  .not(element.predecessors()) // 上流
                  .not(element.successors()) // 下流
                  .not(element) // 選択ノード
                  .not('node[group="hidden_item"]') // 選択ノード
                  .not('node[group="plBs"]') // 選択ノード
                  .not('node[group="perspective"]') // 選択ノード
                  .addClass('unhighlighted'); // それ以外をグレーアウト
              },
            },
          ],
        };
        cy.cxtmenu(metricsCxtmenu);
        cy.cxtmenu(otherCxtmenu);

        // canvasクリック時イベント
        // cy.on('tap', () => {
        //   cy.elements().removeClass('highlighted unhighlighted'); // 選択解除時にクラスをクリア
        // });
        // ダブルクリックでハイライトクラスクリアver
        cy.on('tap', (event) => {
          // NOTE: older cytoscape's event has cyTarget rather than target.
          const tappedNow = event.target;
          if (tappedTimeout && tappedBefore) {
            clearTimeout(tappedTimeout);
          }
          if (tappedBefore === tappedNow) {
            tappedNow.trigger('doubleTap');
            tappedBefore = null;
          } else {
            tappedTimeout = setTimeout(() => {
              tappedBefore = null;
            }, 300);
            tappedBefore = tappedNow;
          }
        });
        cy.on('doubleTap', () => {
          cy.elements().removeClass('highlighted unhighlighted'); // 選択解除時にクラスをクリア
        });

        cy.nodes().forEach((node) => {
          node.style('width', labelWidth(node));
        });
      }

      const layout = {
        name: 'dagre',
        nodeSep: 50,
        edgeSep: 0,
        rankSep: 30,
        // animate: true,
        // padding: 10,
      };
      cy.layout(layout).run();

      return () => {
        cy.destroy();
      };
    }
  }, [refreshFlg]);

  useEffect(() => {
    if (targetMetrics) {
      // (async () => {
      //   const vals = [] as any;
      //   const req = { perspectives: vals };
      //   commonAjax
      //     .axios({ loading: true })
      //     .get(`/api/digitalValueTreeInfo/{targetMetrics}`)
      //     .then((res) => {
      //       setDispElements(res.data.elements);
      //       setElementsStyle(res.data.style);
      //       setContentHeight(
      //         window.innerHeight -
      //           document.getElementsByClassName('main-header')[0].clientHeight -
      //           document.getElementsByClassName('page-cover')[0].clientHeight -
      //           document.getElementsByClassName('content-header')[0]
      //             .clientHeight -
      //           42,
      //       );
      //       setRefreshFlg(!refreshFlg);
      //     });
      // })();
      const parentDivEle = document.getElementById('apiInfoPanelContents');
      const tmpParentDivEle = document.createElement('div');
      const tmpChildDivEle = document.createElement('div');
      const iEle = document.createElement('i');
      tmpParentDivEle.id = targetMetrics;
      tmpParentDivEle.className = 'apiInfoPanel';
      iEle.className = 'fa fa-times-circle fa-lg float-right';
      iEle.ariaHidden = 'true';
      iEle.onclick = function () {
        document.getElementById(tmpParentDivEle.id)?.remove();
      };
      tmpParentDivEle.appendChild(iEle);
      tmpChildDivEle.textContent = `${targetMetrics}`;
      tmpParentDivEle.appendChild(tmpChildDivEle);
      setTargetMetrics('');
      // parentDivEle?.appendChild(tmpParentDivEle);//最下部に追加するパターン
      parentDivEle?.insertBefore(tmpParentDivEle, parentDivEle?.children[0]); // 最上部に追加するパターン
    }
  }, [targetMetrics]);

  return (
    <div className="content-wrapper" id="digital-value-tree">
      <section className="page-cover">
        <h1>Digital value tree</h1>
      </section>
      <section className="content-header">
        <div className="content-header-left">
          <h1>Digital value tree</h1>
          <div className="content-header-desc">
            **************概要**************
          </div>
          {/* <input type="button" onClick={fullscreen} value="全画面モード" /> */}
          {/* <Link to="/metrics-network-tab" className="nav-link" target="_blank">
            全画面モード
          </Link> */}
        </div>
        <div className="btn-right btn-lg">
          <i className={`fa fa-solid fa-expand`}
            aria-hidden="true"
            onClick={fullscreen}
          >
          </i>
        </div>
      </section>
      <section className="content">
        <div
          id="contentBody"
          style={{
            backgroundColor: 'var(--content-bg-color)',
          }}
        >
          <div
            id="legendContens"
            style={{
              top: 10,
              right: 10,
            }}
          >
            <div>
              <ul>
                <li id="legend1">財務指標</li>
                <li id="legend2">P/L</li>
                <li id="legend3">B/S</li>
                <li id="legend4">改革指標</li>
                <li id="legend5">施策カテゴリー</li>
                <li id="legend6">施策</li>
                <li id="legend7">KPI例</li>
              </ul>
            </div>
          </div>
          <div
            id="perspectiveContents"
            style={{
              top: 200,
              right: 10,
            }}
          >
            <div className="form-check">
              <input
                type="checkbox"
                id="scm"
                className="form-check-input"
                defaultChecked={perspectiveCheck}
                onChange={changePerspective}
              />
              <label className="form-check-label" htmlFor="scm">
                SCM(需給/調達)視点
              </label>
            </div>
            <div className="form-check">
              <input
                type="checkbox"
                id="logistics"
                className="form-check-input"
                defaultChecked={perspectiveCheck}
                onChange={changePerspective}
              />
              <label className="form-check-label" htmlFor="logistics">
                ロジスティックス視点
              </label>
            </div>
            <div className="form-check">
              <input
                type="checkbox"
                id="production_quality_assurance"
                className="form-check-input"
                defaultChecked={perspectiveCheck}
                onChange={changePerspective}
              />
              <label
                className="form-check-label"
                htmlFor="production_quality_assurance"
              >
                生産・品質保証視点
              </label>
            </div>
          </div>
          <div
            id="simulationContents"
            style={{
              top: 320,
              right: 10,
            }}
          >
            <div className="form-check">
              <input
                type="checkbox"
                id="simulationCheckBox"
                className="form-check-input"
                defaultChecked={simulationCheck}
                onChange={changeSimulationMode}
              />
              <label className="form-check-label" htmlFor="simulationCheckBox">
                シミュレーションモード
              </label>
            </div>
          </div>
          <div id="apiInfoPanelContents" />
          <div
            ref={graphContainer}
            id="graphContainer"
            style={{
              height: contentHeight,
            }}
          />
        </div>
      </section>
    </div>
  );
}

export default digitalValueTree;
kirin-ri commented 6 months ago
import cytoscape, { Collection, NodeCollection } from 'cytoscape';
import cxtmenu from 'cytoscape-cxtmenu';
import dagre from 'cytoscape-dagre';
import cyHtmlLabel from 'cytoscape-node-html-label';
import { useEffect, useRef, useState } from 'react';
import { commonAjax } from '../../../components/commonAjax';

cytoscape.use(dagre);
cytoscape.use(cxtmenu);
cytoscape.use(cyHtmlLabel);

function DigitalValueTree() {
  const graphContainer = useRef<HTMLDivElement>(null);
  const [dispElements, setDispElements] = useState<[]>([]);
  const [elementsStyle, setElementsStyle] = useState<[]>([]);
  const [contentHeight, setContentHeight] = useState<number | string>(0);
  const [refreshFlg, setRefreshFlg] = useState<boolean>(true);
  const [perspectiveCheck, setPerspectiveCheck] = useState<boolean>(true);
  const [simulationCheck, setSimulationCheck] = useState<boolean>(false);
  const [isSidebarOpen, setIsSidebarOpen] = useState(false);
  const [targetMetrics, setTargetMetrics] = useState<string>('');

  const toggleSidebar = () => setIsSidebarOpen(!isSidebarOpen);

  // 定义侧边栏样式
  const sidebarStyle = {
    transform: isSidebarOpen ? 'translateX(0)' : 'translateX(-100%)',
    transition: 'transform 0.3s ease-in-out',
    width: '250px',
    position: 'fixed',
    top: 0,
    left: 0,
    height: '100%',
    backgroundColor: '#f4f4f4',
    zIndex: 1000,
  };

  // 其他函数和效果…

  useEffect(() => {
    (async () => {
      const req = { perspectives: [] };
      commonAjax
        .axios({ loading: true })
        .post('/api/digitalValueTreeInfo', req)
        .then((res) => {
          setDispElements(res.data.elements);
          setElementsStyle(res.data.style);
          setContentHeight(
            window.innerHeight -
              document.getElementsByClassName('main-header')[0].clientHeight -
              document.getElementsByClassName('page-cover')[0].clientHeight -
              document.getElementsByClassName('content-header')[0].clientHeight -
              42,
          );
          setRefreshFlg(!refreshFlg);
        });
    })();
  }, []);

  // 省略其他相关的 useEffect 和函数定义...

  return (
    <div className="content-wrapper" id="digital-value-tree">
      <section className="page-cover">
        <h1>Digital value tree</h1>
      </section>
      <section className="content-header">
        <div className="content-header-left">
          <h1>Digital value tree</h1>
          <div className="content-header-desc">
            {/* 概要描述 */}
          </div>
        </div>
        <div className="btn-right btn-lg">
          <i className={`fa fa-solid fa-expand`} aria-hidden="true" onClick={toggleSidebar}></i>
        </div>
      </section>
      <section className="content">
        <div style={sidebarStyle} id="sidebar">
          {/* 侧边栏内容 */}
          <div style={{ padding: '20px' }}>
            <h4>财务指标</h4>
            {/* 其他财务指标相关内容 */}
            <h4>模拟模式</h4>
            <div className="form-check">
              <input
                type="checkbox"
                id="simulationCheckBox"
                className="form-check-input"
                checked={simulationCheck}
                onChange={(e) => setSimulationCheck(e.target.checked)}
              />
              <label className="form-check-label" htmlFor="simulationCheckBox">
                模拟模式
              </label>
            </div>
          </div>
        </div>
        <div id="contentBody" style={{ marginLeft: isSidebarOpen ? '250px' : '0', transition: 'margin-left 0.3s ease-in-out' }}>
          <div ref={graphContainer} id="graphContainer" style={{ height: contentHeight }} />
        </div>
      </section>
    </div>
  );
}

export default DigitalValueTree;
kirin-ri commented 6 months ago
import cytoscape, { Collection, NodeCollection } from 'cytoscape';
import cxtmenu from 'cytoscape-cxtmenu';
import dagre from 'cytoscape-dagre';
import cyHtmlLabel from 'cytoscape-node-html-label';
import { useEffect, useRef, useState } from 'react';
import { commonAjax } from '../../../components/commonAjax';

cytoscape.use(dagre);
cytoscape.use(cxtmenu);
cytoscape.use(cyHtmlLabel);

function DigitalValueTree() {
  const graphContainer = useRef<HTMLDivElement>(null);
  const [dispElements, setDispElements] = useState<[]>([]);
  const [elementsStyle, setElementsStyle] = useState<[]>([]);
  const [contentHeight, setContentHeight] = useState<number | string>(0);
  const [refreshFlg, setRefreshFlg] = useState<boolean>(true);
  const [perspectiveCheck, setPerspectiveCheck] = useState<boolean>(true);
  const [simulationCheck, setSimulationCheck] = useState<boolean>(false);
  const [isSidebarOpen, setIsSidebarOpen] = useState(false);
  const [targetMetrics, setTargetMetrics] = useState<string>('');

  const toggleSidebar = () => setIsSidebarOpen(!isSidebarOpen);

  useEffect(() => {
    (async () => {
      const req = { perspectives: [] };
      commonAjax
        .axios({ loading: true })
        .post('/api/digitalValueTreeInfo', req)
        .then((res) => {
          setDispElements(res.data.elements);
          setElementsStyle(res.data.style);
          setContentHeight(
            window.innerHeight -
              document.getElementsByClassName('main-header')[0].clientHeight -
              document.getElementsByClassName('page-cover')[0].clientHeight -
              document.getElementsByClassName('content-header')[0].clientHeight -
              42,
          );
          setRefreshFlg(!refreshFlg);
        });
    })();
  }, []);

  return (
    <div className="content-wrapper" id="digital-value-tree">
      <section className="page-cover">
        <h1>Digital value tree</h1>
      </section>
      <section className="content-header">
        <div className="content-header-left">
          <h1>Digital value tree</h1>
          <div className="content-header-desc">
            **************概要**************
          </div>
        </div>
      </section>
      <section className="content">
        <div
          style={{
            width: isSidebarOpen ? '250px' : '0',
            transition: 'width 0.3s ease-in-out',
            overflow: 'hidden',
            position: 'fixed',
            right: 0,
            top: 0,
            bottom: 0,
            backgroundColor: '#f4f4f4',
            zIndex: 1000,
          }}
        >
          {/* 侧边栏内容 */}
          <div style={{ padding: '20px' }}>
            <h4>财务指标</h4>
            {/* 其他财务指标相关内容 */}
            <h4>模拟模式</h4>
            <div className="form-check">
              <input
                type="checkbox"
                id="simulationCheckBox"
                className="form-check-input"
                checked={simulationCheck}
                onChange={(e) => setSimulationCheck(e.target.checked)}
              />
              <label className="form-check-label" htmlFor="simulationCheckBox">
                模拟模式
              </label>
            </div>
          </div>
        </div>
        <button
          onClick={toggleSidebar}
          style={{
            position: 'fixed',
            right: isSidebarOpen ? '250px' : '0',
            top: '10px',
            zIndex: 1001, // Ensure button is always clickable
            transition: 'right 0.3s ease-in-out',
          }}
        >
          {isSidebarOpen ? '关闭侧边栏' : '打开侧边栏'}
        </button>
        <div id="contentBody" style={{ paddingRight: isSidebarOpen ? '250px' : '0', transition: 'padding-right 0.3s ease-in-out' }}>
          <div ref={graphContainer} id="graphContainer" style={{ height: contentHeight }} />
        </div>
      </section>
    </div>
  );
}

export default DigitalValueTree;
kirin-ri commented 6 months ago
import cytoscape, { Collection, NodeCollection } from 'cytoscape';
import cxtmenu from 'cytoscape-cxtmenu';
import dagre from 'cytoscape-dagre';
// @ts-ignore
import cyHtmlLabel from 'cytoscape-node-html-label';
import { useEffect, useRef, useState } from 'react';
import { commonAjax } from '../../../components/commonAjax';

cytoscape.use(dagre);
cytoscape.use(cxtmenu);
cytoscape.use(cyHtmlLabel);

function digitalValueTree() {
  const graphContainer = useRef<HTMLDivElement>(null);
  const [dispElements, setDispElements] = useState<[]>([]);
  const [elementsStyle, setElementsStyle] = useState<[]>([]);
  const [contentHeight, setContentHeight] = useState<number | string>(0);
  const [refreshFlg, setRefreshFlg] = useState<boolean>(true);
  const [perspectiveCheck, setPerspectiveCheck] = useState<boolean>(true);
  const [simulationCheck, setSimulationCheck] = useState<boolean>(false);
  const [targetMetrics, setTargetMetrics] = useState<string>('');
  const [isSidebarOpen,setIsSidebarOpen] = useState<boolean>(false);
  const toggleSidebar = () => setIsSidebarOpen(!isSidebarOpen)

  let tappedBefore: null;
  let tappedTimeout: string | number | NodeJS.Timeout | undefined;

  // 視点フィルター変更時イベント
  function changePerspective() {
    const vals: string[] = [];
    $('input[class="form-check-input"]:checked').each(function () {
      vals.push($(this)[0].id);
    });
    const req = { perspectives: vals };
    (async () => {
      commonAjax
        .axios({ loading: true })
        .post('/api/digitalValueTreeInfo', req)
        .then((res) => {
          setDispElements(res.data.elements);
          setElementsStyle(res.data.style);
          setRefreshFlg(!refreshFlg);
        });
    })();
  }

  // シミュレーションモード変更時変更時イベント
  function changeSimulationMode() {
    const CheckBox = document.querySelector(
      "input[type='checkbox']#simulationCheckBox",
    ) as HTMLInputElement;
    setSimulationCheck(CheckBox.checked);
    setRefreshFlg(!refreshFlg);
  }

  // 全画面モード選択時イベント
  function fullscreen() {
    document.getElementById('contentBody')!.requestFullscreen();
    // setContentHeight(window.outerHeight);
    // setContentHeight('100%');
    // setRefreshFlg(!refreshFlg);
  }

  // フルスクリーン切替時イベント
  function fullscreenchanged() {
    if (document.fullscreenElement) {
      // setContentHeight('100%');
      setContentHeight(window.outerHeight);
      setRefreshFlg(!refreshFlg);
      // document.getElementById('contentBody')!.style.backgroundColor = 'white';
    } else {
      setContentHeight(
        window.innerHeight -
          document.getElementsByClassName('main-header')[0].clientHeight -
          document.getElementsByClassName('page-cover')[0].clientHeight -
          document.getElementsByClassName('content-header')[0].clientHeight -
          42,
      );
      setRefreshFlg(!refreshFlg);
      // document.getElementById('contentBody')!.style.backgroundColor =
      //   'var(--content-bg-color)';
    }
  }

  // フルスクリーン切替イベントリスナー
  document.addEventListener('fullscreenchange', fullscreenchanged);

  useEffect(() => {
    (async () => {
      const vals = [] as any;
      const req = { perspectives: vals };
      commonAjax
        .axios({ loading: true })
        .post('/api/digitalValueTreeInfo', req)
        .then((res) => {
          setDispElements(res.data.elements);
          setElementsStyle(res.data.style);
          setContentHeight(
            window.innerHeight -
              document.getElementsByClassName('main-header')[0].clientHeight -
              document.getElementsByClassName('page-cover')[0].clientHeight -
              document.getElementsByClassName('content-header')[0]
                .clientHeight -
              42,
          );
          setRefreshFlg(!refreshFlg);
        });
    })();
  }, []);

  useEffect(() => {
    if (graphContainer.current) {
      // cytoscape init
      const cy = cytoscape({
        wheelSensitivity: 0.1, // マウスホイール拡大縮小感度
        container: graphContainer.current,
        elements: dispElements,
        style: elementsStyle,
      });

      // ノード幅動的設定
      const labelWidth = (node: NodeCollection): number => {
        const canvas = document.createElement('canvas');
        const context = canvas.getContext('2d')!;
        context.font = ['style', 'weight', 'size', 'family']
          .map((style) => node.style(`font-${style}`))
          .join(' ');
        const { width } = context.measureText(node.data('label'));
        const padding = 20;
        return width + padding;
      };

      // シュミレーションモード分岐
      if (simulationCheck) {
        /* *********************************
         ここにシュミレーション処理追加!!!
         ********************************* */
        // sample-ノードのラベルにHTMLを表示するための設定
        // @ts-ignore
        cy.nodeHtmlLabel(
          [
            {
              query: 'node[group="metrics"]',
              halign: 'center',
              valign: 'center',
              tpl: (
                data: any,
              ) => `<table style="border: 2px solid rgb(140 140 140);">
                      <thead>
                        <tr>
                          <th scope="col" style="border: 1px solid rgb(160 160 160)">head1</th>
                          <th scope="col" style="border: 1px solid rgb(160 160 160)">head2</th>
                        </tr>
                      </thead>
                      <tbody>
                        <tr>
                          <th style="border: 1px solid rgb(160 160 160)">col1</th>
                          <td style="border: 1px solid rgb(160 160 160)">col2</td>
                        </tr>
                      </tbody>
                    </table>
                            `,
            },
            // {
            //   query: "node[id='metrics2']",
            //   halign: 'center',
            //   valign: 'center',
            //   tpl: (data: any) =>
            //     `<div style="background-color: red; border-radius:10px; padding:5; margin:0 0 45 130;">badge</div>`,
            // },
            // {
            //   query: "node[id='metrics3']",
            //   halign: 'center',
            //   valign: 'center',
            //   tpl: (data: any) => `<input id="sssss" style="margin:50 0 0 0;"/>`,
            // },
          ],
          // {
          //   enablePointerEvents: true,
          // },
        );

        cy.nodes("not(node[group='metrics'])").forEach((node) => {
          node.style('width', labelWidth(node));
        });
        cy.nodes('node[group="metrics"]').forEach((ele) => {
          ele.style('width', '140');
          ele.style('height', '150');
          ele.style('text-valign', 'top');
          ele.style('text-halign', 'center');
        });
      } else {
        // cxtmenu
        const metricsCxtmenu = {
          selector: 'node[group="metrics"]',
          menuRadius: 75,
          activePadding: 8,
          openMenuEvents: 'tap',
          // openMenuEvents: 'cxttapstart',
          // openMenuEvents: 'cxttapstart taphold',
          outsideMenuCancel: 8,
          menuItemTextSize: 100,
          commands: (ele: Collection) => [
            {
              content: '詳細',
              contentStyle: {},
              enabled: ele.data('logical_name') !== null,
              select(element: any) {
                const openNewTab = (url: string) => {
                  const newWindow = window.open(url, '_blank');
                  if (newWindow) {
                    newWindow.opener = null;
                  }
                };
                const targetId = element.data('logical_name');
                openNewTab(`/#/scm-metrics-details/${targetId}`);
                // history.push(`/scm-metrics-details/${targetId}`);//新規タブで開かない場合
              },
            },
            {
              content: 'リレーションハイライト',
              contentStyle: {},
              enabled: true,
              select(element: any) {
                cy.elements().removeClass('unhighlighted'); // 既存のクラスをクリア
                cy.elements().removeClass('highlighted'); // 既存のクラスをクリア
                // element.outgoers().addClass('highlighted'); // 接続されているエッジとノードをハイライト
                element.predecessors().addClass('highlighted'); // 上流エッジとノードをハイライト
                element.successors().addClass('highlighted'); // 下流エッジとノードをハイライト
                cy.elements()
                  // .not(element.outgoers())
                  .not(element.predecessors()) // 上流
                  .not(element.successors()) // 下流
                  .not(element) // 選択ノード
                  .not('node[group="hidden_item"]') // 選択ノード
                  .not('node[group="perspective"]') // 選択ノード
                  .addClass('unhighlighted'); // それ以外をグレーアウト
              },
            },
            {
              content: 'シミュレーション',
              contentStyle: {},
              enabled: false,
            },
            {
              content: '参照データ',
              contentStyle: {},
              enabled: ele.data('logical_name') !== null,
              select(element: any) {
                setTargetMetrics(element.data('id'));
              },
            },
          ],
        };
        const otherCxtmenu = {
          selector:
            'node[group="financial_parent"],node[group="financial_child"],node[group="pl"],node[group="bs"],node[group="reform"],node[group="measure_category"],node[group="measure"]',
          menuRadius: 75,
          activePadding: 8,
          openMenuEvents: 'tap',
          // openMenuEvents: 'cxttapstart',
          // openMenuEvents: 'cxttapstart taphold',
          outsideMenuCancel: 8,
          menuItemTextSize: 100,
          commands: [
            {
              content: 'リレーションハイライト',
              contentStyle: {},
              enabled: true,
              select(element: any) {
                cy.elements().removeClass('unhighlighted'); // 既存のクラスをクリア
                cy.elements().removeClass('highlighted'); // 既存のクラスをクリア
                // element.outgoers().addClass('highlighted'); // 接続されているエッジとノードをハイライト
                element.predecessors().addClass('highlighted'); // 上流エッジとノードをハイライト
                element.successors().addClass('highlighted'); // 下流エッジとノードをハイライト
                cy.elements()
                  // .not(element.outgoers())
                  .not(element.predecessors()) // 上流
                  .not(element.successors()) // 下流
                  .not(element) // 選択ノード
                  .not('node[group="hidden_item"]') // 選択ノード
                  .not('node[group="plBs"]') // 選択ノード
                  .not('node[group="perspective"]') // 選択ノード
                  .addClass('unhighlighted'); // それ以外をグレーアウト
              },
            },
          ],
        };
        cy.cxtmenu(metricsCxtmenu);
        cy.cxtmenu(otherCxtmenu);

        // canvasクリック時イベント
        // cy.on('tap', () => {
        //   cy.elements().removeClass('highlighted unhighlighted'); // 選択解除時にクラスをクリア
        // });
        // ダブルクリックでハイライトクラスクリアver
        cy.on('tap', (event) => {
          // NOTE: older cytoscape's event has cyTarget rather than target.
          const tappedNow = event.target;
          if (tappedTimeout && tappedBefore) {
            clearTimeout(tappedTimeout);
          }
          if (tappedBefore === tappedNow) {
            tappedNow.trigger('doubleTap');
            tappedBefore = null;
          } else {
            tappedTimeout = setTimeout(() => {
              tappedBefore = null;
            }, 300);
            tappedBefore = tappedNow;
          }
        });
        cy.on('doubleTap', () => {
          cy.elements().removeClass('highlighted unhighlighted'); // 選択解除時にクラスをクリア
        });

        cy.nodes().forEach((node) => {
          node.style('width', labelWidth(node));
        });
      }

      const layout = {
        name: 'dagre',
        nodeSep: 50,
        edgeSep: 0,
        rankSep: 30,
        // animate: true,
        // padding: 10,
      };
      cy.layout(layout).run();

      return () => {
        cy.destroy();
      };
    }
  }, [refreshFlg]);

  useEffect(() => {
    if (targetMetrics) {
      // (async () => {
      //   const vals = [] as any;
      //   const req = { perspectives: vals };
      //   commonAjax
      //     .axios({ loading: true })
      //     .get(`/api/digitalValueTreeInfo/{targetMetrics}`)
      //     .then((res) => {
      //       setDispElements(res.data.elements);
      //       setElementsStyle(res.data.style);
      //       setContentHeight(
      //         window.innerHeight -
      //           document.getElementsByClassName('main-header')[0].clientHeight -
      //           document.getElementsByClassName('page-cover')[0].clientHeight -
      //           document.getElementsByClassName('content-header')[0]
      //             .clientHeight -
      //           42,
      //       );
      //       setRefreshFlg(!refreshFlg);
      //     });
      // })();
      const parentDivEle = document.getElementById('apiInfoPanelContents');
      const tmpParentDivEle = document.createElement('div');
      const tmpChildDivEle = document.createElement('div');
      const iEle = document.createElement('i');
      tmpParentDivEle.id = targetMetrics;
      tmpParentDivEle.className = 'apiInfoPanel';
      iEle.className = 'fa fa-times-circle fa-lg float-right';
      iEle.ariaHidden = 'true';
      iEle.onclick = function () {
        document.getElementById(tmpParentDivEle.id)?.remove();
      };
      tmpParentDivEle.appendChild(iEle);
      tmpChildDivEle.textContent = `${targetMetrics}`;
      tmpParentDivEle.appendChild(tmpChildDivEle);
      setTargetMetrics('');
      // parentDivEle?.appendChild(tmpParentDivEle);//最下部に追加するパターン
      parentDivEle?.insertBefore(tmpParentDivEle, parentDivEle?.children[0]); // 最上部に追加するパターン
    }
  }, [targetMetrics]);

  return (
    <div className="content-wrapper" id="digital-value-tree">
      <section className="page-cover">
        <h1>Digital value tree</h1>
      </section>
      <section className="content-header">
        <div className="content-header-left">
          <h1>Digital value tree</h1>
          <div className="content-header-desc">
            **************概要**************
          </div>
          {/* <input type="button" onClick={fullscreen} value="全画面モード" /> */}
          {/* <Link to="/metrics-network-tab" className="nav-link" target="_blank">
            全画面モード
          </Link> */}
        </div>
        <div className="btn-right btn-lg">
            <i className={`fa fa-solid fa-expand`}
              aria-hidden="true"
              onClick={fullscreen}
            >
            </i>
        </div>
      </section>
      <section className="content" style={{position:'relative'}}>
        <div
          id="contentBody"
          style={{
            backgroundColor: 'var(--content-bg-color)',
          }}
        >
        <div
            id="legendContens"
            style={{
              top: 40,
              right: 10,
            }}
          >
            <div>
              <ul>
                <li id="legend1">財務指標</li>
                <li id="legend2">P/L</li>
                <li id="legend3">B/S</li>
                <li id="legend4">改革指標</li>
                <li id="legend5">施策カテゴリー</li>
                <li id="legend6">施策</li>
                <li id="legend7">KPI例</li>
              </ul>
            </div>
          </div>
          {!isSidebarOpen && (
            <i className="fas fa-bars btn-lg" onClick={toggleSidebar} style={{position:'absolute',right:'0',top:'10px',zIndex:1001,}}>
              setting
            </i>
          )}
            <div
              style={{
                width: isSidebarOpen ? '250px' : '0',
                transition: 'width 0.3s ease-in-out',
                overflow: 'hidden',
                position: 'absolute',
                right: 0,
                top: 0,
                bottom: 0,
                backgroundColor: '#f3f3f3',
                zIndex: 1000,
              }}
            >
              <div
                id="perspectiveContents"
                style={{
                  top: 50,
                  right: 10,
                }}
              >
                <div className="form-check">
                  <input
                    type="checkbox"
                    id="scm"
                    className="form-check-input"
                    defaultChecked={perspectiveCheck}
                    onChange={changePerspective}
                  />
                  <label className="form-check-label" htmlFor="scm">
                    SCM(需給/調達)視点
                  </label>
                </div>
                <div className="form-check">
                  <input
                    type="checkbox"
                    id="logistics"
                    className="form-check-input"
                    defaultChecked={perspectiveCheck}
                    onChange={changePerspective}
                  />
                  <label className="form-check-label" htmlFor="logistics">
                    ロジスティックス視点
                  </label>
                </div>
                <div className="form-check">
                  <input
                    type="checkbox"
                    id="production_quality_assurance"
                    className="form-check-input"
                    defaultChecked={perspectiveCheck}
                    onChange={changePerspective}
                  />
                  <label
                    className="form-check-label"
                    htmlFor="production_quality_assurance"
                  >
                    生産・品質保証視点
                  </label>
              </div>
            </div>
              <div
                id="simulationContents"
                style={{
                  top: 120,
                  right: 10,
                }}
              >
                <div className="form-check">
                  <input
                    type="checkbox"
                    id="simulationCheckBox"
                    className="form-check-input"
                    defaultChecked={simulationCheck}
                    onChange={changeSimulationMode}
                  />
                  <label className="form-check-label" htmlFor="simulationCheckBox">
                    シミュレーションモード
                  </label>
                </div>
              </div>
            </div>
            {isSidebarOpen && (
              <i className="fas fa-bars btn-lg" onClick={toggleSidebar} style={{position:'absolute',right:'0',top:'10px',zIndex:1001,}}>
                close
              </i>
            )}
          <div id="apiInfoPanelContents" />
          <div
            ref={graphContainer}
            id="graphContainer"
            style={{
              height: contentHeight,
            }}
          />
        </div>
      </section>
    </div>
  );
}

export default digitalValueTree;
kirin-ri commented 6 months ago
.form-switch {
  position: relative;
  display: inline-block;
  width: 60px;
  height: 34px;
}

.form-switch .form-check-input {
  opacity: 0;
  width: 0;
  height: 0;
}

.slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #ccc;
  transition: .4s;
  border-radius: 34px;
}

.slider:before {
  position: absolute;
  content: "";
  height: 26px;
  width: 26px;
  left: 4px;
  bottom: 4px;
  background-color: white;
  transition: .4s;
  border-radius: 50%;
}

.form-check-input:checked + .slider {
  background-color: #2196F3;
}

.form-check-input:focus + .slider {
  box-shadow: 0 0 1px #2196F3;
}

.form-check-input:checked + .slider:before {
  transform: translateX(26px);
}
kirin-ri commented 6 months ago
<div className="form-switch">
  <input
    type="checkbox"
    id="simulationSwitch"
    className="form-check-input"
    defaultChecked={simulationCheck}
    onChange={changeSimulationMode}
  />
  <label className="slider" htmlFor="simulationSwitch"></label>
</div>
kirin-ri commented 6 months ago
import cytoscape, { Collection, NodeCollection } from 'cytoscape';
import cxtmenu from 'cytoscape-cxtmenu';
import dagre from 'cytoscape-dagre';
// @ts-ignore
import cyHtmlLabel from 'cytoscape-node-html-label';
import { useEffect, useRef, useState } from 'react';
import { commonAjax } from '../../../components/commonAjax';

cytoscape.use(dagre);
cytoscape.use(cxtmenu);
cytoscape.use(cyHtmlLabel);

function digitalValueTree() {
  const graphContainer = useRef<HTMLDivElement>(null);
  const [dispElements, setDispElements] = useState<[]>([]);
  const [elementsStyle, setElementsStyle] = useState<[]>([]);
  const [contentHeight, setContentHeight] = useState<number | string>(0);
  const [refreshFlg, setRefreshFlg] = useState<boolean>(true);
  const [perspectiveCheck, setPerspectiveCheck] = useState<boolean>(true);
  const [simulationCheck, setSimulationCheck] = useState<boolean>(false);
  const [simulationSwitch, setSimulationSwitch] = useState<boolean>(false);
  const [targetMetrics, setTargetMetrics] = useState<string>('');
  const [isSidebarOpen,setIsSidebarOpen] = useState<boolean>(false);
  const toggleSidebar = () => setIsSidebarOpen(!isSidebarOpen)

  let tappedBefore: null;
  let tappedTimeout: string | number | NodeJS.Timeout | undefined;

  // 視点フィルター変更時イベント
  function changePerspective() {
    const vals: string[] = [];
    $('input[class="form-check-input"]:checked').each(function () {
      vals.push($(this)[0].id);
    });
    const req = { perspectives: vals };
    (async () => {
      commonAjax
        .axios({ loading: true })
        .post('/api/digitalValueTreeInfo', req)
        .then((res) => {
          setDispElements(res.data.elements);
          setElementsStyle(res.data.style);
          setRefreshFlg(!refreshFlg);
        });
    })();
  }

  // シミュレーションモード変更時変更時イベント
  function changeSimulationMode() {
    const CheckBox = document.querySelector(
      "input[type='checkbox']#simulationSwitch",
    ) as HTMLInputElement;
    setSimulationCheck(CheckBox.checked);
    setRefreshFlg(!refreshFlg);
  }

  // 全画面モード選択時イベント
  function fullscreen() {
    document.getElementById('contentBody')!.requestFullscreen();
    // setContentHeight(window.outerHeight);
    // setContentHeight('100%');
    // setRefreshFlg(!refreshFlg);
  }

  // フルスクリーン切替時イベント
  function fullscreenchanged() {
    if (document.fullscreenElement) {
      // setContentHeight('100%');
      setContentHeight(window.innerHeight);
      setRefreshFlg(!refreshFlg);
      // document.getElementById('contentBody')!.style.backgroundColor = 'white';
    } else {
      setContentHeight(
        window.innerHeight -
          document.getElementsByClassName('main-header')[0].clientHeight -
          document.getElementsByClassName('page-cover')[0].clientHeight -
          document.getElementsByClassName('content-header')[0].clientHeight -
          42,
      );
      setRefreshFlg(!refreshFlg);
      // document.getElementById('contentBody')!.style.backgroundColor =
      //   'var(--content-bg-color)';
    }
  }

  // フルスクリーン切替イベントリスナー
  document.addEventListener('fullscreenchange', fullscreenchanged);

  useEffect(() => {
    (async () => {
      const vals = [] as any;
      const req = { perspectives: vals };
      commonAjax
        .axios({ loading: true })
        .post('/api/digitalValueTreeInfo', req)
        .then((res) => {
          setDispElements(res.data.elements);
          setElementsStyle(res.data.style);
          setContentHeight(
            window.innerHeight -
              document.getElementsByClassName('main-header')[0].clientHeight -
              document.getElementsByClassName('page-cover')[0].clientHeight -
              document.getElementsByClassName('content-header')[0]
                .clientHeight -
              42,
          );
          setRefreshFlg(!refreshFlg);
        });
    })();
  }, []);

  useEffect(() => {
    if (graphContainer.current) {
      // cytoscape init
      const cy = cytoscape({
        wheelSensitivity: 0.1, // マウスホイール拡大縮小感度
        container: graphContainer.current,
        elements: dispElements,
        style: elementsStyle,
      });

      // ノード幅動的設定
      const labelWidth = (node: NodeCollection): number => {
        const canvas = document.createElement('canvas');
        const context = canvas.getContext('2d')!;
        context.font = ['style', 'weight', 'size', 'family']
          .map((style) => node.style(`font-${style}`))
          .join(' ');
        const { width } = context.measureText(node.data('label'));
        const padding = 20;
        return width + padding;
      };

      // シュミレーションモード分岐
      if (simulationCheck) {
        /* *********************************
         ここにシュミレーション処理追加!!!
         ********************************* */
        // sample-ノードのラベルにHTMLを表示するための設定
        // @ts-ignore
        cy.nodeHtmlLabel(
          [
            {
              query: 'node[group="metrics"]',
              halign: 'center',
              valign: 'center',
              tpl: (
                data: any,
              ) => `<table style="border: 2px solid rgb(140 140 140);">
                      <thead>
                        <tr>
                          <th scope="col" style="border: 1px solid rgb(160 160 160)">head1</th>
                          <th scope="col" style="border: 1px solid rgb(160 160 160)">head2</th>
                        </tr>
                      </thead>
                      <tbody>
                        <tr>
                          <th style="border: 1px solid rgb(160 160 160)">col1</th>
                          <td style="border: 1px solid rgb(160 160 160)">col2</td>
                        </tr>
                      </tbody>
                    </table>
                            `,
            },
            // {
            //   query: "node[id='metrics2']",
            //   halign: 'center',
            //   valign: 'center',
            //   tpl: (data: any) =>
            //     `<div style="background-color: red; border-radius:10px; padding:5; margin:0 0 45 130;">badge</div>`,
            // },
            // {
            //   query: "node[id='metrics3']",
            //   halign: 'center',
            //   valign: 'center',
            //   tpl: (data: any) => `<input id="sssss" style="margin:50 0 0 0;"/>`,
            // },
          ],
          // {
          //   enablePointerEvents: true,
          // },
        );

        cy.nodes("not(node[group='metrics'])").forEach((node) => {
          node.style('width', labelWidth(node));
        });
        cy.nodes('node[group="metrics"]').forEach((ele) => {
          ele.style('width', '140');
          ele.style('height', '150');
          ele.style('text-valign', 'top');
          ele.style('text-halign', 'center');
        });
      } else {
        // cxtmenu
        const metricsCxtmenu = {
          selector: 'node[group="metrics"]',
          menuRadius: 75,
          activePadding: 8,
          openMenuEvents: 'tap',
          // openMenuEvents: 'cxttapstart',
          // openMenuEvents: 'cxttapstart taphold',
          outsideMenuCancel: 8,
          menuItemTextSize: 100,
          commands: (ele: Collection) => [
            {
              content: '詳細',
              contentStyle: {},
              enabled: ele.data('logical_name') !== null,
              select(element: any) {
                const openNewTab = (url: string) => {
                  const newWindow = window.open(url, '_blank');
                  if (newWindow) {
                    newWindow.opener = null;
                  }
                };
                const targetId = element.data('logical_name');
                openNewTab(`/#/scm-metrics-details/${targetId}`);
                // history.push(`/scm-metrics-details/${targetId}`);//新規タブで開かない場合
              },
            },
            {
              content: 'リレーションハイライト',
              contentStyle: {},
              enabled: true,
              select(element: any) {
                cy.elements().removeClass('unhighlighted'); // 既存のクラスをクリア
                cy.elements().removeClass('highlighted'); // 既存のクラスをクリア
                // element.outgoers().addClass('highlighted'); // 接続されているエッジとノードをハイライト
                element.predecessors().addClass('highlighted'); // 上流エッジとノードをハイライト
                element.successors().addClass('highlighted'); // 下流エッジとノードをハイライト
                cy.elements()
                  // .not(element.outgoers())
                  .not(element.predecessors()) // 上流
                  .not(element.successors()) // 下流
                  .not(element) // 選択ノード
                  .not('node[group="hidden_item"]') // 選択ノード
                  .not('node[group="perspective"]') // 選択ノード
                  .addClass('unhighlighted'); // それ以外をグレーアウト
              },
            },
            {
              content: 'シミュレーション',
              contentStyle: {},
              enabled: false,
            },
            {
              content: '参照データ',
              contentStyle: {},
              enabled: ele.data('logical_name') !== null,
              select(element: any) {
                setTargetMetrics(element.data('id'));
              },
            },
          ],
        };
        const otherCxtmenu = {
          selector:
            'node[group="financial_parent"],node[group="financial_child"],node[group="pl"],node[group="bs"],node[group="reform"],node[group="measure_category"],node[group="measure"]',
          menuRadius: 75,
          activePadding: 8,
          openMenuEvents: 'tap',
          // openMenuEvents: 'cxttapstart',
          // openMenuEvents: 'cxttapstart taphold',
          outsideMenuCancel: 8,
          menuItemTextSize: 100,
          commands: [
            {
              content: 'リレーションハイライト',
              contentStyle: {},
              enabled: true,
              select(element: any) {
                cy.elements().removeClass('unhighlighted'); // 既存のクラスをクリア
                cy.elements().removeClass('highlighted'); // 既存のクラスをクリア
                // element.outgoers().addClass('highlighted'); // 接続されているエッジとノードをハイライト
                element.predecessors().addClass('highlighted'); // 上流エッジとノードをハイライト
                element.successors().addClass('highlighted'); // 下流エッジとノードをハイライト
                cy.elements()
                  // .not(element.outgoers())
                  .not(element.predecessors()) // 上流
                  .not(element.successors()) // 下流
                  .not(element) // 選択ノード
                  .not('node[group="hidden_item"]') // 選択ノード
                  .not('node[group="plBs"]') // 選択ノード
                  .not('node[group="perspective"]') // 選択ノード
                  .addClass('unhighlighted'); // それ以外をグレーアウト
              },
            },
          ],
        };
        cy.cxtmenu(metricsCxtmenu);
        cy.cxtmenu(otherCxtmenu);

        // canvasクリック時イベント
        // cy.on('tap', () => {
        //   cy.elements().removeClass('highlighted unhighlighted'); // 選択解除時にクラスをクリア
        // });
        // ダブルクリックでハイライトクラスクリアver
        cy.on('tap', (event) => {
          // NOTE: older cytoscape's event has cyTarget rather than target.
          const tappedNow = event.target;
          if (tappedTimeout && tappedBefore) {
            clearTimeout(tappedTimeout);
          }
          if (tappedBefore === tappedNow) {
            tappedNow.trigger('doubleTap');
            tappedBefore = null;
          } else {
            tappedTimeout = setTimeout(() => {
              tappedBefore = null;
            }, 300);
            tappedBefore = tappedNow;
          }
        });
        cy.on('doubleTap', () => {
          cy.elements().removeClass('highlighted unhighlighted'); // 選択解除時にクラスをクリア
        });

        cy.nodes().forEach((node) => {
          node.style('width', labelWidth(node));
        });
      }

      const layout = {
        name: 'dagre',
        nodeSep: 50,
        edgeSep: 0,
        rankSep: 30,
        // animate: true,
        // padding: 10,
      };
      cy.layout(layout).run();

      return () => {
        cy.destroy();
      };
    }
  }, [refreshFlg]);

  useEffect(() => {
    if (targetMetrics) {
      // (async () => {
      //   const vals = [] as any;
      //   const req = { perspectives: vals };
      //   commonAjax
      //     .axios({ loading: true })
      //     .get(`/api/digitalValueTreeInfo/{targetMetrics}`)
      //     .then((res) => {
      //       setDispElements(res.data.elements);
      //       setElementsStyle(res.data.style);
      //       setContentHeight(
      //         window.innerHeight -
      //           document.getElementsByClassName('main-header')[0].clientHeight -
      //           document.getElementsByClassName('page-cover')[0].clientHeight -
      //           document.getElementsByClassName('content-header')[0]
      //             .clientHeight -
      //           42,
      //       );
      //       setRefreshFlg(!refreshFlg);
      //     });
      // })();
      const parentDivEle = document.getElementById('apiInfoPanelContents');
      const tmpParentDivEle = document.createElement('div');
      const tmpChildDivEle = document.createElement('div');
      const iEle = document.createElement('i');
      tmpParentDivEle.id = targetMetrics;
      tmpParentDivEle.className = 'apiInfoPanel';
      iEle.className = 'fa fa-times-circle fa-lg float-right';
      iEle.ariaHidden = 'true';
      iEle.onclick = function () {
        document.getElementById(tmpParentDivEle.id)?.remove();
      };
      tmpParentDivEle.appendChild(iEle);
      tmpChildDivEle.textContent = `${targetMetrics}`;
      tmpParentDivEle.appendChild(tmpChildDivEle);
      setTargetMetrics('');
      // parentDivEle?.appendChild(tmpParentDivEle);//最下部に追加するパターン
      parentDivEle?.insertBefore(tmpParentDivEle, parentDivEle?.children[0]); // 最上部に追加するパターン
    }
  }, [targetMetrics]);

  return (
    <div className="content-wrapper" id="digital-value-tree">
      <section className="page-cover">
        <h1>Digital value tree</h1>
      </section>
      <section className="content-header">
        <div className="content-header-left">
          <h1>Digital value tree</h1>
          <div className="content-header-desc">
            **************概要**************
          </div>
          {/* <input type="button" onClick={fullscreen} value="全画面モード" /> */}
          {/* <Link to="/metrics-network-tab" className="nav-link" target="_blank">
            全画面モード
          </Link> */}
        </div>
        <div className="btn-right btn-lg">
            <i className={`fa fa-solid fa-expand`}
              aria-hidden="true"
              onClick={fullscreen}
            >
            </i>
        </div>
      </section>
      <section className="content" style={{position:'relative'}}>
        <div
          id="contentBody"
          style={{
            backgroundColor: 'var(--content-bg-color)',
          }}
        >
        <div
            id="legendContens"
            style={{
              top: 60,
              right: 10,
            }}
          >
            <div>
              <ul>
                <li id="legend1">財務指標</li>
                <li id="legend2">P/L</li>
                <li id="legend3">B/S</li>
                <li id="legend4">改革指標</li>
                <li id="legend5">施策カテゴリー</li>
                <li id="legend6">施策</li>
                <li id="legend7">KPI例</li>
              </ul>
            </div>
          </div>
          {!isSidebarOpen && (
            <i className="fas fa-bars btn-lg" onClick={toggleSidebar} style={{position:'absolute',right:'0',top:'10px',zIndex:1001,}}>
            </i>
          )}
            <div
              style={{
                width: isSidebarOpen ? '250px' : '0',
                transition: 'width 0.3s ease-in-out',
                overflow: 'hidden',
                position: 'absolute',
                right: 0,
                top: 0,
                bottom: 0,
                backgroundColor: '#f6f6f6',
                zIndex: 1000,
                borderRadius:'10px'
              }}
            >
            {isSidebarOpen && (
              <i className="fas fa-bars btn-lg" onClick={toggleSidebar} style={{position:'absolute',right:'0',top:'10px',zIndex:1001,}}>
              </i>
            )}
              <div
                id="perspectiveContents"
                style={{
                  top: 50,
                  right: 10,
                }}
              >
                視点フィルター
                <div className="form-check">
                  <input
                    type="checkbox"
                    id="scm"
                    className="form-check-input"
                    defaultChecked={perspectiveCheck}
                    onChange={changePerspective}
                  />
                  <label className="form-check-label" htmlFor="scm">
                    SCM(需給/調達)視点
                  </label>
                </div>
                <div className="form-check">
                  <input
                    type="checkbox"
                    id="logistics"
                    className="form-check-input"
                    defaultChecked={perspectiveCheck}
                    onChange={changePerspective}
                  />
                  <label className="form-check-label" htmlFor="logistics">
                    ロジスティックス視点
                  </label>
                </div>
                <div className="form-check">
                  <input
                    type="checkbox"
                    id="production_quality_assurance"
                    className="form-check-input"
                    defaultChecked={perspectiveCheck}
                    onChange={changePerspective}
                  />
                  <label
                    className="form-check-label"
                    htmlFor="production_quality_assurance"
                  >
                    生産・品質保証視点
                  </label>
              </div>
            </div>
              <div
                id="simulationContents"
                style={{
                  top: 200,
                  right: 10,
                }}
              >
                <div className="form-switch">
                  <input
                    type="checkbox"
                    id="simulationSwitch"
                    className="form-check-input"
                    defaultChecked={simulationCheck}
                    onChange={changeSimulationMode}
                  />
                  <label className="slider" htmlFor="simulationSwitch">シミュレーションモード</label>
                </div>
              </div>
            </div>
          <div id="apiInfoPanelContents" />
          <div
            ref={graphContainer}
            id="graphContainer"
            style={{
              height: contentHeight,
            }}
          />
        </div>
      </section>
    </div>
  );
}

export default digitalValueTree;
kirin-ri commented 6 months ago
#digital-value-tree #contentBody {
  position: relative;
}

#digital-value-tree #graphContainer {
  width: 100%;
}

/*視点選択checkbox*/
#digital-value-tree #perspectiveContents{
  z-index: 1;
  position: absolute;
  right: 0;
  top: 0;
  padding: 3px;
  background-color: white;
  border: 2px solid darkgray;
  border-radius: 10px;
  /* box-shadow: 5px 5px 20px #00000028; */
}
#digital-value-tree #perspectiveContents .form-check {
  padding:5px;
}

/*シミュレーションモードcheckbox*/
#digital-value-tree #simulationContents{
  width: 200px;
  z-index: 1;
  position: absolute;
  right: 0;
  top: 0;
  padding: 3px;
  background-color: white;
  border: 2px solid darkgray;
  border-radius: 10px;
  /* box-shadow: 5px 5px 20px #00000028; */
}
#digital-value-tree #simulationContents .form-check {
  padding:5px;
}

/*凡例スタイル*/
#digital-value-tree #legendContens{
  z-index: 1;
  position: absolute;
  right: 0;
  top: 0;
  padding: 3px;
  background-color: white;
  border: 2px solid darkgray;
  border-radius: 10px;
  /* box-shadow: 5px 5px 20px #00000028; */
}
#digital-value-tree #legendContens li {
  list-style-type: none;
}
#digital-value-tree #legendContens ul {
  margin:0;
  padding:0;
}
#digital-value-tree #legendContens li:before {
  content: "";
  display: inline-block;
  height: 15px;
  width: 50px;
  margin-right: 5px;
  opacity:0.3;
  border-radius:5px;
}
#digital-value-tree #legendContens #legend1:before {
  background-color: fuchsia;
}
#digital-value-tree #legendContens #legend2:before {
  background-color: teal;
}
#digital-value-tree #legendContens #legend3:before {
  background-color: yellow;
}
#digital-value-tree #legendContens #legend4:before {
  background-color: red;
}
#digital-value-tree #legendContens #legend5:before {
  background-color: blue;
}
#digital-value-tree #legendContens #legend6:before {
  background-color: purple;
}
#digital-value-tree #legendContens #legend7:before {
  background-color: gray;
}

/*INPUT情報パネル*/
#digital-value-tree #apiInfoPanelContents{
  z-index: 2;
  position: absolute;
  right: 10px;
  top: 10px;
  /* box-shadow: 5px 5px 20px #00000028; */
}

#digital-value-tree .apiInfoPanel{
  padding: 3px;
  background-color: pink;
  border: 2px solid darkgray;
  border-radius: 10px;
}

/* アイコン fontawesome */
#digital-value-tree .fa{
  /* color: #444; */
  opacity: 0.3;
}

#digital-value-tree .fa:hover{
  /* color: #444; */
  opacity: 0.7;
}

#digital-value-tree .form-switch {
  position: relative;
  display: inline-block;
  width: 50px;
  height: 34px;
}

#digital-value-tree .form-switch .form-check-input {
  opacity: 0;
  width: 0;
  height: 0;
}

#digital-value-tree .slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #ccc;
  transition: .4s;
  border-radius: 34px;
}

#digital-value-tree .slider:before {
  position: absolute;
  content: "";
  height: 26px;
  width: 26px;
  left: 4px;
  bottom: 4px;
  background-color: white;
  transition: .4s;
  border-radius: 50%;
}

#digital-value-tree .form-check-input:checked + .slider {
  background-color: #2196F3;
}

#digital-value-tree .form-check-input:focus + .slider {
  box-shadow: 0 0 1px #2196F3;
}

#digital-value-tree .form-check-input:checked + .slider:before {
  transform: translateX(26px);
}
kirin-ri commented 6 months ago
import cytoscape, { Collection, NodeCollection } from 'cytoscape';
import cxtmenu from 'cytoscape-cxtmenu';
import dagre from 'cytoscape-dagre';
// @ts-ignore
import cyHtmlLabel from 'cytoscape-node-html-label';
import { useEffect, useRef, useState } from 'react';
import { commonAjax } from '../../../components/commonAjax';

cytoscape.use(dagre);
cytoscape.use(cxtmenu);
cytoscape.use(cyHtmlLabel);

function digitalValueTree() {
  const graphContainer = useRef<HTMLDivElement>(null);
  const [dispElements, setDispElements] = useState<[]>([]);
  const [elementsStyle, setElementsStyle] = useState<[]>([]);
  const [contentHeight, setContentHeight] = useState<number | string>(0);
  const [refreshFlg, setRefreshFlg] = useState<boolean>(true);
  const [perspectiveCheck, setPerspectiveCheck] = useState<boolean>(true);
  const [simulationCheck, setSimulationCheck] = useState<boolean>(false);
  const [simulationSwitch, setSimulationSwitch] = useState<boolean>(false);
  const [targetMetrics, setTargetMetrics] = useState<string>('');
  const [isSidebarOpen,setIsSidebarOpen] = useState<boolean>(false);
  const [menuOpen,setMenuOpen] = useState<boolean>(false);
  const toggleSidebar = () => setIsSidebarOpen(!isSidebarOpen)

  let tappedBefore: null;
  let tappedTimeout: string | number | NodeJS.Timeout | undefined;

  // 視点フィルター変更時イベント
  function changePerspective() {
    const vals: string[] = [];
    $('input[class="form-check-input"]:checked').each(function () {
      vals.push($(this)[0].id);
    });
    const req = { perspectives: vals };
    (async () => {
      commonAjax
        .axios({ loading: true })
        .post('/api/digitalValueTreeInfo', req)
        .then((res) => {
          setDispElements(res.data.elements);
          setElementsStyle(res.data.style);
          setRefreshFlg(!refreshFlg);
        });
    })();
  }

  // シミュレーションモード変更時変更時イベント
  function changeSimulationMode() {
    const CheckBox = document.querySelector(
      "input[type='checkbox']#simulationSwitch",
    ) as HTMLInputElement;
    setSimulationCheck(CheckBox.checked);
    setRefreshFlg(!refreshFlg);
  }

  // 全画面モード選択時イベント
  function fullscreen() {
    document.getElementById('contentBody')!.requestFullscreen();
    // setContentHeight(window.outerHeight);
    // setContentHeight('100%');
    // setRefreshFlg(!refreshFlg);
  }

  // フルスクリーン切替時イベント
  function fullscreenchanged() {
    if (document.fullscreenElement) {
      // setContentHeight('100%');
      setContentHeight(window.innerHeight);
      setRefreshFlg(!refreshFlg);
      // document.getElementById('contentBody')!.style.backgroundColor = 'white';
    } else {
      setContentHeight(
        window.innerHeight -
          document.getElementsByClassName('main-header')[0].clientHeight -
          document.getElementsByClassName('page-cover')[0].clientHeight -
          document.getElementsByClassName('content-header')[0].clientHeight -
          42,
      );
      setRefreshFlg(!refreshFlg);
      // document.getElementById('contentBody')!.style.backgroundColor =
      //   'var(--content-bg-color)';
    }
  }

  // フルスクリーン切替イベントリスナー
  document.addEventListener('fullscreenchange', fullscreenchanged);

  useEffect(() => {
    (async () => {
      const vals = [] as any;
      const req = { perspectives: vals };
      commonAjax
        .axios({ loading: true })
        .post('/api/digitalValueTreeInfo', req)
        .then((res) => {
          setDispElements(res.data.elements);
          setElementsStyle(res.data.style);
          setContentHeight(
            window.innerHeight -
              document.getElementsByClassName('main-header')[0].clientHeight -
              document.getElementsByClassName('page-cover')[0].clientHeight -
              document.getElementsByClassName('content-header')[0]
                .clientHeight -
              42,
          );
          setRefreshFlg(!refreshFlg);
        });
    })();
  }, []);

  useEffect(() => {
    if (graphContainer.current) {
      // cytoscape init
      const cy = cytoscape({
        wheelSensitivity: 0.1, // マウスホイール拡大縮小感度
        container: graphContainer.current,
        elements: dispElements,
        style: elementsStyle,
      });

      // ノード幅動的設定
      const labelWidth = (node: NodeCollection): number => {
        const canvas = document.createElement('canvas');
        const context = canvas.getContext('2d')!;
        context.font = ['style', 'weight', 'size', 'family']
          .map((style) => node.style(`font-${style}`))
          .join(' ');
        const { width } = context.measureText(node.data('label'));
        const padding = 20;
        return width + padding;
      };

      // シュミレーションモード分岐
      if (simulationCheck) {
        /* *********************************
         ここにシュミレーション処理追加!!!
         ********************************* */
        // sample-ノードのラベルにHTMLを表示するための設定
        // @ts-ignore
        cy.nodeHtmlLabel(
          [
            {
              query: 'node[group="metrics"]',
              halign: 'center',
              valign: 'center',
              tpl: (
                data: any,
              ) => `<table style="border: 2px solid rgb(140 140 140);">
                      <thead>
                        <tr>
                          <th scope="col" style="border: 1px solid rgb(160 160 160)">head1</th>
                          <th scope="col" style="border: 1px solid rgb(160 160 160)">head2</th>
                        </tr>
                      </thead>
                      <tbody>
                        <tr>
                          <th style="border: 1px solid rgb(160 160 160)">col1</th>
                          <td style="border: 1px solid rgb(160 160 160)">col2</td>
                        </tr>
                      </tbody>
                    </table>
                            `,
            },
            // {
            //   query: "node[id='metrics2']",
            //   halign: 'center',
            //   valign: 'center',
            //   tpl: (data: any) =>
            //     `<div style="background-color: red; border-radius:10px; padding:5; margin:0 0 45 130;">badge</div>`,
            // },
            // {
            //   query: "node[id='metrics3']",
            //   halign: 'center',
            //   valign: 'center',
            //   tpl: (data: any) => `<input id="sssss" style="margin:50 0 0 0;"/>`,
            // },
          ],
          // {
          //   enablePointerEvents: true,
          // },
        );

        cy.nodes("not(node[group='metrics'])").forEach((node) => {
          node.style('width', labelWidth(node));
        });
        cy.nodes('node[group="metrics"]').forEach((ele) => {
          ele.style('width', '140');
          ele.style('height', '150');
          ele.style('text-valign', 'top');
          ele.style('text-halign', 'center');
        });
      } else {
        // cxtmenu
        const metricsCxtmenu = {
          selector: 'node[group="metrics"]',
          menuRadius: 75,
          activePadding: 8,
          openMenuEvents: 'tap',
          // openMenuEvents: 'cxttapstart',
          // openMenuEvents: 'cxttapstart taphold',
          outsideMenuCancel: 8,
          menuItemTextSize: 100,
          commands: (ele: Collection) => [
            {
              content: '詳細',
              contentStyle: {},
              enabled: ele.data('logical_name') !== null,
              select(element: any) {
                const openNewTab = (url: string) => {
                  const newWindow = window.open(url, '_blank');
                  if (newWindow) {
                    newWindow.opener = null;
                  }
                };
                const targetId = element.data('logical_name');
                openNewTab(`/#/scm-metrics-details/${targetId}`);
                // history.push(`/scm-metrics-details/${targetId}`);//新規タブで開かない場合
              },
            },
            {
              content: 'リレーションハイライト',
              contentStyle: {},
              enabled: true,
              select(element: any) {
                cy.elements().removeClass('unhighlighted'); // 既存のクラスをクリア
                cy.elements().removeClass('highlighted'); // 既存のクラスをクリア
                // element.outgoers().addClass('highlighted'); // 接続されているエッジとノードをハイライト
                element.predecessors().addClass('highlighted'); // 上流エッジとノードをハイライト
                element.successors().addClass('highlighted'); // 下流エッジとノードをハイライト
                cy.elements()
                  // .not(element.outgoers())
                  .not(element.predecessors()) // 上流
                  .not(element.successors()) // 下流
                  .not(element) // 選択ノード
                  .not('node[group="hidden_item"]') // 選択ノード
                  .not('node[group="perspective"]') // 選択ノード
                  .addClass('unhighlighted'); // それ以外をグレーアウト
              },
            },
            {
              content: 'シミュレーション',
              contentStyle: {},
              enabled: false,
            },
            {
              content: '参照データ',
              contentStyle: {},
              enabled: ele.data('logical_name') !== null,
              select(element: any) {
                setTargetMetrics(element.data('id'));
              },
            },
          ],
        };
        const otherCxtmenu = {
          selector:
            'node[group="financial_parent"],node[group="financial_child"],node[group="pl"],node[group="bs"],node[group="reform"],node[group="measure_category"],node[group="measure"]',
          menuRadius: 75,
          activePadding: 8,
          openMenuEvents: 'tap',
          // openMenuEvents: 'cxttapstart',
          // openMenuEvents: 'cxttapstart taphold',
          outsideMenuCancel: 8,
          menuItemTextSize: 100,
          commands: [
            {
              content: 'リレーションハイライト',
              contentStyle: {},
              enabled: true,
              select(element: any) {
                cy.elements().removeClass('unhighlighted'); // 既存のクラスをクリア
                cy.elements().removeClass('highlighted'); // 既存のクラスをクリア
                // element.outgoers().addClass('highlighted'); // 接続されているエッジとノードをハイライト
                element.predecessors().addClass('highlighted'); // 上流エッジとノードをハイライト
                element.successors().addClass('highlighted'); // 下流エッジとノードをハイライト
                cy.elements()
                  // .not(element.outgoers())
                  .not(element.predecessors()) // 上流
                  .not(element.successors()) // 下流
                  .not(element) // 選択ノード
                  .not('node[group="hidden_item"]') // 選択ノード
                  .not('node[group="plBs"]') // 選択ノード
                  .not('node[group="perspective"]') // 選択ノード
                  .addClass('unhighlighted'); // それ以外をグレーアウト
              },
            },
          ],
        };
        cy.cxtmenu(metricsCxtmenu);
        cy.cxtmenu(otherCxtmenu);

        // canvasクリック時イベント
        // cy.on('tap', () => {
        //   cy.elements().removeClass('highlighted unhighlighted'); // 選択解除時にクラスをクリア
        // });
        // ダブルクリックでハイライトクラスクリアver
        cy.on('tap', (event) => {
          // NOTE: older cytoscape's event has cyTarget rather than target.
          const tappedNow = event.target;
          if (tappedTimeout && tappedBefore) {
            clearTimeout(tappedTimeout);
          }
          if (tappedBefore === tappedNow) {
            tappedNow.trigger('doubleTap');
            tappedBefore = null;
          } else {
            tappedTimeout = setTimeout(() => {
              tappedBefore = null;
            }, 300);
            tappedBefore = tappedNow;
          }
        });
        cy.on('doubleTap', () => {
          cy.elements().removeClass('highlighted unhighlighted'); // 選択解除時にクラスをクリア
        });

        cy.nodes().forEach((node) => {
          node.style('width', labelWidth(node));
        });
      }

      const layout = {
        name: 'dagre',
        nodeSep: 50,
        edgeSep: 0,
        rankSep: 30,
        // animate: true,
        // padding: 10,
      };
      cy.layout(layout).run();

      return () => {
        cy.destroy();
      };
    }
  }, [refreshFlg]);

  useEffect(() => {
    if (targetMetrics) {
      // (async () => {
      //   const vals = [] as any;
      //   const req = { perspectives: vals };
      //   commonAjax
      //     .axios({ loading: true })
      //     .get(`/api/digitalValueTreeInfo/{targetMetrics}`)
      //     .then((res) => {
      //       setDispElements(res.data.elements);
      //       setElementsStyle(res.data.style);
      //       setContentHeight(
      //         window.innerHeight -
      //           document.getElementsByClassName('main-header')[0].clientHeight -
      //           document.getElementsByClassName('page-cover')[0].clientHeight -
      //           document.getElementsByClassName('content-header')[0]
      //             .clientHeight -
      //           42,
      //       );
      //       setRefreshFlg(!refreshFlg);
      //     });
      // })();
      const parentDivEle = document.getElementById('apiInfoPanelContents');
      const tmpParentDivEle = document.createElement('div');
      const tmpChildDivEle = document.createElement('div');
      const iEle = document.createElement('i');
      tmpParentDivEle.id = targetMetrics;
      tmpParentDivEle.className = 'apiInfoPanel';
      iEle.className = 'fa fa-times-circle fa-lg float-right';
      iEle.ariaHidden = 'true';
      iEle.onclick = function () {
        document.getElementById(tmpParentDivEle.id)?.remove();
      };
      tmpParentDivEle.appendChild(iEle);
      tmpChildDivEle.textContent = `${targetMetrics}`;
      tmpParentDivEle.appendChild(tmpChildDivEle);
      setTargetMetrics('');
      // parentDivEle?.appendChild(tmpParentDivEle);//最下部に追加するパターン
      parentDivEle?.insertBefore(tmpParentDivEle, parentDivEle?.children[0]); // 最上部に追加するパターン
    }
  }, [targetMetrics]);

  return (
    <div className="content-wrapper" id="digital-value-tree">
      <section className="page-cover">
        <h1>Digital value tree</h1>
      </section>
      <section className="content-header">
        <div className="content-header-left">
          <h1>Digital value tree</h1>
          <div className="content-header-desc">
            **************概要**************
          </div>
        </div>
        <div className="btn-right btn-lg" onClick={fullscreen}>
          <i className={`fa fa-solid fa-expand`}
            aria-hidden="true"
          >
          </i>
          <span className="tooltip">
            {'全画面モード'}
          </span>
        </div>
      </section>
      <section className="content" style={{position:'relative'}}>
        <div
          id="contentBody"
          style={{
            backgroundColor: 'var(--content-bg-color)',
          }}
        >
          <div
            id="legendContens"
            style={{
              top: 30,
              right: 10,
            }}
          >
            <div>
              <ul>
                <li id="legend1">財務指標</li>
                <li id="legend2">P/L</li>
                <li id="legend3">B/S</li>
                <li id="legend4">改革指標</li>
                <li id="legend5">施策カテゴリー</li>
                <li id="legend6">施策</li>
                <li id="legend7">KPI例</li>
              </ul>
            </div>
          </div>
          {!isSidebarOpen && (
            <div className="btn-left btn-lg" onClick={toggleSidebar}>
              <i className={`fas fa-cog btn-lg`}
                style={{position:'absolute',right:'0',bottom:'0px',zIndex:1002,}}
              >
              </i>
              <span className="tooltip">
                {'設定'}
              </span>
            </div>
            // {/* <i className="fas fa-cog btn-lg" onClick={toggleSidebar} style={{position:'absolute',right:'0',bottom:'0px',zIndex:1001,}}>
            // </i>
            // <span className="tooltip">
            //   {'設定'}
            // </span> */}
          )}
            <div
              style={{
                width: isSidebarOpen ? '250px' : '0',
                transition: 'width 0.3s ease-in-out',
                overflow: 'hidden',
                position: 'absolute',
                right: 0,
                top: 0,
                bottom: 0,
                backgroundColor: '#f6f6f6',
                zIndex: 1000,
                borderRadius:'10px',
              }}
            >
            {isSidebarOpen && (
              <i className="fas fa-times btn-lg" onClick={toggleSidebar} style={{position:'absolute',right:'0',top:'0px',zIndex:1001,}}>
              </i>
            )}
              <div
                id="perspectiveContents"
                style={{
                  top: 50,
                  right: 5,
                }}
              >
                視点フィルター
                <div className="form-check">
                  <input
                    type="checkbox"
                    id="scm"
                    className="form-check-input"
                    defaultChecked={perspectiveCheck}
                    onChange={changePerspective}
                  />
                  <label className="form-check-label" htmlFor="scm">
                    SCM(需給/調達)視点
                  </label>
                </div>
                <div className="form-check">
                  <input
                    type="checkbox"
                    id="logistics"
                    className="form-check-input"
                    defaultChecked={perspectiveCheck}
                    onChange={changePerspective}
                  />
                  <label className="form-check-label" htmlFor="logistics">
                    ロジスティックス視点
                  </label>
                </div>
                <div className="form-check">
                  <input
                    type="checkbox"
                    id="production_quality_assurance"
                    className="form-check-input"
                    defaultChecked={perspectiveCheck}
                    onChange={changePerspective}
                  />
                  <label
                    className="form-check-label"
                    htmlFor="production_quality_assurance"
                  >
                    生産・品質保証視点
                  </label>
              </div>
            </div>
              <div
                id="simulationContents"
                style={{
                  top: 200,
                  right: 10,
                }}
              >
                <span className='swith-label'>シミュレーションモード</span>
                <div className="form-switch">
                  <input
                    type="checkbox"
                    id="simulationSwitch"
                    className="form-check-input"
                    defaultChecked={simulationCheck}
                    onChange={changeSimulationMode}
                  />
                  <label className="slider" htmlFor="simulationSwitch"></label>
                </div>
              </div>
            </div>
          <div id="apiInfoPanelContents" />
          <div
            ref={graphContainer}
            id="graphContainer"
            style={{
              height: contentHeight,
            }}
          />
        </div>
      </section>
    </div>
  );
}

export default digitalValueTree;
kirin-ri commented 6 months ago
/* ツールチップ */
#digital-value-tree .btn-right { /* 補足説明するテキストのスタイル */
  position: relative;
  display: inline-block;
  cursor: pointer;
}

#digital-value-tree .tooltip { /* ツールチップのスタイル */
  width: 100px; /* 横幅 */
  position: absolute;
  left: 50%;
  bottom: 80%; /* Y軸の位置 */
  transform: translateX(-50%);
  margin-bottom: 8px; /* テキストとの距離 */
  padding: 8px;
  border-radius: 10px; /* 角の丸み */
  background-color: #666;
  font-size: 0.6em;
  color: #fff;
  text-align: center;
  visibility: hidden; /* ツールチップを非表示に */
  opacity: 0; /* 不透明度を0%に */
  z-index: 2;
  transition: 0.5s all; /* マウスオーバー時のアニメーション速度 */
}

#digital-value-tree .btn-right:hover .tooltip { /* マウスオーバー時のスタイル */
  visibility: visible; /* ツールチップを表示 */
  opacity: 1; /* 不透明度を100%に */
}
kirin-ri commented 6 months ago
/* ツールチップ */
#digital-value-tree .btn-right { /* 補足説明するテキストのスタイル */
  display: inline-block;
  cursor: pointer;
}

#digital-value-tree .tooltip { /* ツールチップのスタイル */
  width: 100px; /* 横幅 */
  position: absolute;
  left: 50%;
  bottom: 80%; /* Y軸の位置 */
  transform: translateX(-50%);
  margin-bottom: 8px; /* テキストとの距離 */
  padding: 8px;
  border-radius: 10px; /* 角の丸み */
  background-color: #666;
  font-size: 0.6em;
  color: #fff;
  text-align: center;
  visibility: hidden; /* ツールチップを非表示に */
  opacity: 0; /* 不透明度を0%に */
  z-index: 2000;
  transition: 0.5s all; /* マウスオーバー時のアニメーション速度 */
}

#digital-value-tree .btn-right:hover .tooltip{ /* マウスオーバー時のスタイル */
  visibility: visible; /* ツールチップを表示 */
  opacity: 1; /* 不透明度を100%に */
}