未验证 提交 5b9dacfc 编写于 作者: R RotPublic 提交者: GitHub

Add dynamic graph frontend

上级 96fb036b
......@@ -27,9 +27,9 @@ module.exports = {
},
plugins: ['license-header'],
rules: {
'no-console': 'warn',
'sort-imports': 'error',
'license-header/header': ['error', './license-header.js']
'sort-imports': 'warn',
'no-console': 'warn'
// 'license-header/header': ['error', './license-header.js']
},
overrides: [
{
......
......@@ -26,6 +26,7 @@
"dev": "snowpack dev",
"dev:reload": "yarn dev --reload",
"build": "snowpack build && node builder/post-build.js",
"lint": "eslint --ext .tsx,.jsx.ts,.js,.mjs",
"snowpack": "snowpack",
"test": "web-test-runner \"test/**/*.tsx\""
},
......@@ -48,6 +49,7 @@
"d3-format": "3.0.1",
"echarts": "4.9.0",
"echarts-gl": "1.1.2",
"eslint-plugin-simple-import-sort": "^7.0.0",
"eventemitter3": "4.0.7",
"file-saver": "2.0.5",
"i18next": "20.6.0",
......
......@@ -10,6 +10,8 @@
"empty": "Nothing to display",
"error": "Error occurred",
"graph": "Graphs",
"graphDynamic": "dynamic",
"graphStatic": "static",
"high-dimensional": "High Dimensional",
"histogram": "Histogram",
"hyper-parameter": "Hyper Parameters",
......
......@@ -43,12 +43,15 @@
"type": "Type",
"version": "Version"
},
"restore-size": "Restore Size",
"restore-size": "Fully Shrinked data model",
"expend-size": "Fully expanded Data Model",
"show-attributes": "Show Attributes",
"show-initializers": "Show Initializers",
"show-node-names": "Show Node Names",
"keep-expanded": "keep expanded",
"subgraph": "Select Subgraph",
"supported-model": "Supported models: ",
"Choose-model": "Choose a model",
"supported-model-list": "PaddlePaddle, ONNX, Keras, Core ML, Caffe, Caffe2, Darknet, MXNet, ncnn, TensorFlow Lite",
"upload-model": "Upload Model",
"upload-tip": "Click or Drop file here to view neural network models",
......
......@@ -10,6 +10,8 @@
"empty": "暂无数据",
"error": "发生错误",
"graph": "网络结构",
"graphDynamic": "动态",
"graphStatic": "静态",
"high-dimensional": "数据降维",
"histogram": "直方图",
"hyper-parameter": "超参可视化",
......
......@@ -43,16 +43,19 @@
"type": "类型",
"version": "版本"
},
"restore-size": "重置大小",
"restore-size": "全收缩数据模型",
"expend-size": "全展开数据模型",
"show-attributes": "显示参数",
"show-initializers": "显示初始化参数",
"show-node-names": "显示节点名称",
"subgraph": "选择子图",
"keep-expanded": "保持展开",
"supported-model": "VisualDL支持:",
"supported-model-list": "PaddlePaddle、ONNX、Keras、Core ML、Caffe、Caffe2、Darknet、MXNet、ncnn、TensorFlow Lite",
"upload-model": "上传模型",
"upload-tip": "点击或拖拽文件到页面上传模型,进行结构展示",
"vertical": "垂直",
"Choose-model": "选择模型",
"zoom-in": "放大",
"zoom-out": "缩小"
}
/**
* Copyright 2020 Baidu Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, {FunctionComponent, useCallback} from 'react';
import {ellipsis, em, half, math, position, sameBorder, size, transitionProps} from '~/utils/style';
import styled from 'styled-components';
const height = em(20);
const checkSize = em(16);
const checkMark =
// eslint-disable-next-line
'data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjgiIHZpZXdCb3g9IjAgMCAxMSA4IiB3aWR0aD0iMTEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTkuNDc5NDI3MDggMTAuMTg3NWgtNS4yNXYtMS4zMTI1aDMuOTM3bC4wMDA1LTcuODc1aDEuMzEyNXoiIGZpbGw9IiNmNWY1ZjUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgdHJhbnNmb3JtPSJtYXRyaXgoLjcwNzEwNjc4IC43MDcxMDY3OCAtLjcwNzEwNjc4IC43MDcxMDY3OCA0Ljk2Mjk5NCAtNi4yMDg0NCkiLz48L3N2Zz4=';
const Wrapper = styled.label<{disabled?: boolean}>`
position: relative;
display: inline-flex;
align-items: flex-start;
cursor: ${props => (props.disabled ? 'not-allowed' : 'pointer')};
`;
const Input = styled.input.attrs<{disabled?: boolean}>(props => ({
type: 'checkbox',
disabled: !!props.disabled
}))`
${size(0)}
${position('absolute', 0, null, null, 0)}
opacity: 0;
pointer-events: none;
`;
const Inner = styled.div<{checked?: boolean; size?: string; disabled?: boolean}>`
color: ${props => (props.checked ? 'var(--text-invert-color)' : 'transparent')};
flex-shrink: 0;
${props => size(math(`${checkSize} * ${props.size === 'small' ? 0.875 : 1}`))}
margin: ${half(`${height} - ${checkSize}`)} 0;
margin-right: ${em(10)};
${props =>
sameBorder({color: props.disabled || !props.checked ? 'var(--text-lighter-color)' : 'var(--primary-color)'})};
background-color: ${props =>
props.disabled
? props.checked
? 'var(--text-lighter-color)'
: 'transparent'
: props.checked
? 'var(--primary-color)'
: 'var(--background-color)'};
background-image: ${props => (props.checked ? `url("${checkMark}")` : 'none')};
background-repeat: no-repeat;
background-position: center center;
background-size: ${em(10)} ${em(8)};
position: relative;
${transitionProps(['border-color', 'background-color', 'color'])}
${Wrapper}:hover > & {
border-color: ${props =>
props.disabled
? 'var(--text-lighter-color)'
: props.checked
? 'var(--primary-color)'
: 'var(--text-lighter-color)'};
}
`;
const Content = styled.div<{disabled?: boolean}>`
line-height: ${height};
flex-grow: 1;
${props => (props.disabled ? 'color: var(--text-lighter-color);' : '')}
${transitionProps('color')}
${ellipsis()}
`;
type CheckboxProps = {
value: string;
checked?: boolean;
className?: string;
onChange?: (checked: string) => unknown;
size?: 'small';
title?: string;
disabled?: boolean;
};
const Checkbox: FunctionComponent<CheckboxProps> = ({
value,
checked,
children,
size,
disabled,
className,
title,
onChange
}) => {
const onChangeInput = useCallback(() => {
if (disabled) {
return;
}
if (onChange) {
onChange(value);
}
}, [disabled, onChange]);
return (
<Wrapper disabled={disabled} className={className} title={title}>
<Input onChange={onChangeInput} checked={checked} disabled={disabled} />
<Inner checked={checked} size={size} disabled={disabled} />
<Content disabled={disabled}>{children}</Content>
</Wrapper>
);
};
export default Checkbox;
......@@ -61,7 +61,7 @@ const Wrapper = styled.div`
${transitionProps('color')}
&:hover,
&:active {
&:active {
color: var(--text-light-color);
}
}
......@@ -96,7 +96,7 @@ const Argument: FunctionComponent<ArgumentProps> = ({value, expand, showNodeDocu
{value.name}: <b>{value.value}</b>
</>
) : (
value.value.split('\n').map((line, index) => (
new String(value.value).split('\n').map((line, index) => (
<React.Fragment key={index}>
{index !== 0 && <br />}
{line}
......
/**
* Copyright 2020 Baidu Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type {Documentation, OpenedResult, Properties, SearchItem, SearchResult} from '~/resource/graph/types';
import React, {useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
import {contentHeight, position, primaryColor, rem, size, transitionProps} from '~/utils/style';
import ChartToolbox from '~/components/ChartToolbox';
import HashLoader from 'react-spinners/HashLoader';
import logo from '~/assets/images/netron.png';
import netron from '@visualdl/netron';
import styled from 'styled-components';
import {toast} from 'react-toastify';
import {fetcher} from '~/utils/fetch';
import useTheme from '~/hooks/useTheme';
import {useTranslation} from 'react-i18next';
const PUBLIC_PATH: string = import.meta.env.SNOWPACK_PUBLIC_PATH;
let IFRAME_HOST = `${window.location.protocol}//${window.location.host}`;
if (PUBLIC_PATH.startsWith('http')) {
const url = new URL(PUBLIC_PATH);
IFRAME_HOST = `${url.protocol}//${url.host}`;
}
const toolboxHeight = rem(40);
const Wrapper = styled.div`
position: relative;
height: ${contentHeight};
background-color: var(--background-color);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
${transitionProps('background-color')}
`;
const RenderContent = styled.div<{show: boolean}>`
position: absolute;
top: 0;
left: 0;
${size('100%', '100%')}
opacity: ${props => (props.show ? 1 : 0)};
z-index: ${props => (props.show ? 0 : -1)};
pointer-events: ${props => (props.show ? 'auto' : 'none')};
`;
const Toolbox = styled(ChartToolbox)`
height: ${toolboxHeight};
border-bottom: 1px solid var(--border-color);
padding: 0 ${rem(20)};
${transitionProps('border-color')}
`;
const Content = styled.div`
position: relative;
height: calc(100% - ${toolboxHeight});
> iframe {
${size('100%', '100%')}
border: none;
}
> .powered-by {
display: block;
${position('absolute', null, null, rem(20), rem(30))}
color: var(--graph-copyright-color);
font-size: ${rem(14)};
user-select: none;
img {
height: 1em;
filter: var(--graph-copyright-logo-filter);
vertical-align: middle;
}
}
`;
const Loading = styled.div`
${size('100%', '100%')}
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overscroll-behavior: none;
cursor: progress;
font-size: ${rem(16)};
line-height: ${rem(60)};
`;
export type GraphRef = {
export(type: 'svg' | 'png'): void;
changeGraph(name: string): void;
search(value: string): void;
select(item: SearchItem): void;
setSelectItems(data: Theobj): void;
setLoadings(data: boolean): void;
showModelProperties(): void;
showNodeDocumentation(data: Theobj): void;
};
interface Theobj {
[propname: string]: unknown;
}
type GraphProps = {
files: FileList | File[] | null;
uploader: JSX.Element;
showAttributes: boolean;
showInitializers: boolean;
showNames: boolean;
horizontal: boolean;
isKeepData: boolean;
runs: string[] | undefined;
selectedRuns: string;
onRendered?: () => unknown;
onOpened?: (data: OpenedResult) => unknown;
onSearch?: (data: SearchResult) => unknown;
onShowModelProperties?: (data: Properties) => unknown;
onShowNodeProperties?: (data: Properties) => unknown;
onShowNodeDocumentation?: (data: Documentation) => unknown;
};
const Graph = React.forwardRef<GraphRef, GraphProps>(
(
{
uploader,
showAttributes,
runs,
selectedRuns,
isKeepData,
showInitializers,
showNames,
horizontal,
onRendered,
onOpened,
onSearch,
onShowModelProperties,
onShowNodeProperties,
onShowNodeDocumentation
},
ref
) => {
const {t, i18n} = useTranslation('graph');
const language: string = i18n.language;
const theme = useTheme();
const [ready, setReady] = useState(false);
const [rendered, setRendered] = useState(false);
const [loading, setLoading] = useState(true);
const [item, setSelectItem] = useState<Theobj | null>();
const [isExpend, setIsExpend] = useState(0);
const [isRetract, setIsretract] = useState(0);
const [modelDatas, setModelDatas] = useState<Theobj>();
const [allModelDatas, setAllModelDatas] = useState<Theobj>();
const [selectNodeId, setSelectNodeId] = useState();
const [searchNodeId, setSearchNodeId] = useState<Theobj>();
const iframe = useRef<HTMLIFrameElement>(null);
const handler = useCallback(
(event: MessageEvent) => {
if (event.data) {
const {type, data} = event.data;
switch (type) {
case 'status':
switch (data) {
case 'ready':
return setReady(true);
case 'loading':
// return setLoading(true);
return 1;
case 'rendered':
setLoading(false);
setRendered(true);
// changeSvg()
onRendered?.();
return;
}
return;
case 'opened':
return onOpened?.(data);
case 'search':
return onSearch?.(data);
case 'cancel':
return setLoading(false);
case 'error':
toast.error(data);
setLoading(false);
return;
case 'show-model-properties':
return onShowModelProperties?.(data);
case 'show-node-properties':
return onShowNodeProperties?.(data);
case 'show-node-documentation':
return onShowNodeDocumentation?.(data);
case 'nodeId':
return setSelectNodeId?.(data);
case 'selectItem':
return setSelectItem?.(data);
}
}
},
[onRendered, onOpened, onSearch, onShowModelProperties, onShowNodeProperties, onShowNodeDocumentation]
);
const dispatch = useCallback((type: string, data?: unknown) => {
iframe.current?.contentWindow?.postMessage(
{
type,
data
},
IFRAME_HOST
);
}, []);
useEffect(() => {
keydown();
}, []);
useEffect(() => {
window.addEventListener('message', handler);
dispatch('ready');
return () => {
window.removeEventListener('message', handler);
};
}, [handler, dispatch]);
useEffect(() => {
if (selectedRuns) {
setLoading(true);
getGraph();
// getAllGraph()
}
}, [selectedRuns]);
useEffect(() => {
if (isExpend) {
// debugger
setLoading(true);
const refresh = false;
const expand_all = true;
fetcher(
'/graph/graph' + `?run=${selectedRuns}` + `&refresh=${refresh}` + `&expand_all=${expand_all}`
).then((res: Theobj) => {
setSelectItem(null);
setModelDatas(res);
});
}
}, [isExpend]);
useEffect(() => {
if (isRetract) {
// debugger
setLoading(true);
const refresh = true;
const expand_all = false;
fetcher(
'/graph/graph' + `?run=${selectedRuns}` + `&refresh=${refresh}` + `&expand_all=${expand_all}`
).then((res: Theobj) => {
setSelectItem(null);
setModelDatas(res);
});
}
}, [isRetract]);
useEffect(() => {
if (ready) {
dispatch('change-select', item);
}
}, [dispatch, item, ready]);
useEffect(() => {
if (!allModelDatas) {
return;
}
if (ready) {
dispatch('change-allGraph', allModelDatas);
}
}, [dispatch, allModelDatas, ready]);
useEffect(() => {
if (!modelDatas) {
return;
}
if (ready) {
dispatch('change-graph', modelDatas);
}
}, [dispatch, modelDatas, ready]);
useEffect(() => {
if (!selectNodeId) {
return;
}
// debugger;
setLoading(true);
const selectNodeIds: Theobj = selectNodeId;
fetcher(
'/graph/manipulate' +
`?run=${selectedRuns}` +
`&nodeid=${selectNodeIds.nodeId}` +
`&expand=${selectNodeIds.expand}` +
`&keep_state=${isKeepData}`
).then((res: Theobj) => {
setModelDatas(res);
});
}, [selectNodeId]);
useEffect(() => {
if (!searchNodeId) {
return;
}
// debugger
setLoading(true);
const searchNodeIds: Theobj = searchNodeId;
const is_node = searchNodeIds.type === 'node' ? true : false;
fetcher(
'/graph/search' +
`?run=${selectedRuns}` +
`&nodeid=${searchNodeIds.name}` +
`&keep_state=${isKeepData}` +
`&is_node=${is_node}`
).then((res: Theobj) => {
setModelDatas(res);
});
}, [searchNodeId]);
useEffect(
() => (ready && dispatch('toggle-attributes', showAttributes)) || undefined,
[dispatch, showAttributes, ready]
);
useEffect(
() => (ready && dispatch('toggle-initializers', showInitializers)) || undefined,
[dispatch, showInitializers, ready]
);
useEffect(() => (ready && dispatch('toggle-names', showNames)) || undefined, [dispatch, showNames, ready]);
useEffect(
() => (ready && dispatch('toggle-direction', horizontal)) || undefined,
[dispatch, horizontal, ready]
);
useEffect(() => (ready && dispatch('toggle-theme', theme)) || undefined, [dispatch, theme, ready]);
useEffect(() => (ready && dispatch('toggle-Language', language)) || undefined, [dispatch, language, ready]);
useImperativeHandle(ref, () => ({
export(type) {
dispatch('export', type);
},
changeGraph(name) {
dispatch('change-graph', name);
},
search(value) {
dispatch('search', value);
},
setSelectItems(data: Theobj) {
setSelectItem(data);
},
setLoadings(data: boolean) {
setLoading(data);
},
select(item) {
const a = document.querySelector('iframe') as HTMLIFrameElement;
const documents = a.contentWindow?.document as Document;
if (item.type === 'node') {
for (const node of documents.getElementsByClassName('cluster')) {
if (node.getAttribute('id') === `node-${item.name}`) {
dispatch('select', item);
return;
}
}
for (const node of documents.getElementsByClassName('node')) {
if (node.getAttribute('id') === `node-${item.name}`) {
dispatch('select', item);
return;
}
}
} else if (item.type === 'input') {
for (const node of documents.getElementsByClassName('edge-path')) {
if (node.getAttribute('id') === `edge-${item.name}`) {
dispatch('select', item);
return;
}
}
}
setSelectItem(item);
setSearchNodeId(item);
},
showModelProperties() {
dispatch('show-model-properties');
},
showNodeDocumentation(data) {
dispatch('show-node-documentation', data);
}
}));
const keydown = () => {
document.addEventListener('keydown', e => {
if (
e.code === 'MetaLeft' ||
e.code === 'MetaRight' ||
e.code === 'ControlLeft' ||
e.code === 'AltLeft' ||
e.code === 'AltRight'
) {
dispatch('isAlt', true);
}
});
document.addEventListener('keyup', e => {
if (
e.code === 'MetaLeft' ||
e.code === 'MetaRight' ||
e.code === 'ControlLeft' ||
e.code === 'AltLeft' ||
e.code === 'AltRight'
) {
dispatch('isAlt', false);
}
});
};
const getGraph = async () => {
const refresh = true;
const expand_all = false;
const result = await fetcher(
'/graph/graph' + `?run=${selectedRuns}` + `&refresh=${refresh}` + `&expand_all=${expand_all}`
);
const allResult = await fetcher('/graph/get_all_nodes' + `?run=${selectedRuns}`);
// const allResult = await fetcher('/graph/graph' + `?run=${selectedRuns}`);
setSelectItem(null);
if (result) setModelDatas(result);
if (allResult) setAllModelDatas(allResult);
};
const content = useMemo(() => {
if (loading) {
return (
<Loading>
<HashLoader size="60px" color={primaryColor} />
</Loading>
);
}
return null;
}, [loading]);
const uploaderContent = useMemo(() => {
if (!runs && !loading) {
return uploader;
}
}, [runs, loading, uploader]);
const svgContent = useMemo(() => {
return (
<Content>
<iframe
ref={iframe}
src={PUBLIC_PATH + netron}
frameBorder={0}
scrolling="yes"
marginWidth={0}
marginHeight={0}
></iframe>
<a
className="powered-by"
href="https://github.com/lutzroeder/netron"
target="_blank"
rel="noreferrer"
>
Powered by <img src={PUBLIC_PATH + logo} alt="netron" />
</a>
</Content>
);
}, [rendered]);
return (
<Wrapper>
{content}
{uploaderContent}
<RenderContent show={!loading && rendered}>
<Toolbox
items={[
{
icon: 'zoom-in',
tooltip: t('graph:zoom-in'),
onClick: () => dispatch('zoom-in')
},
{
icon: 'zoom-out',
tooltip: t('graph:zoom-out'),
onClick: () => dispatch('zoom-out')
},
{
icon: 'restore-size',
tooltip: t('expend-size'),
onClick: () => {
const id = isExpend + 1;
setIsExpend(id);
}
},
{
icon: 'shrink',
tooltip: t('restore-size'),
onClick: () => {
const id = isRetract + 1;
setIsretract(id);
}
}
]}
reversed
tooltipPlacement="bottom"
/>
{svgContent}
</RenderContent>
</Wrapper>
);
}
);
Graph.displayName = 'Graph';
export default Graph;
/**
* Copyright 2020 Baidu Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Aside, {AsideSection} from '~/components/Aside';
import type {Documentation, OpenedResult, Properties, SearchItem, SearchResult} from '~/resource/graph/types';
import GraphComponent, {GraphRef} from '~/components/GraphPage/GraphDynamic';
import React, {FunctionComponent, useCallback, useEffect, useMemo, useRef, useState} from 'react';
import Select, {SelectProps} from '~/components/Select';
import {actions, selectors} from '~/store';
import {primaryColor, rem, size} from '~/utils/style';
import {useDispatch, useSelector} from 'react-redux';
import Check from '~/components/Check';
import Button from '~/components/Button';
import Checkbox from '~/components/Checkbox';
import Content from '~/components/Content';
import Field from '~/components/Field';
import ModelPropertiesDialog from '~/components/GraphPage/ModelPropertiesDialog';
import NodeDocumentationSidebar from '~/components/GraphPage/NodeDocumentationSidebar';
import NodePropertiesSidebar from '~/components/GraphPage/NodePropertiesSidebar';
import RadioButton from '~/components/RadioButton';
import RadioGroup from '~/components/RadioGroup';
import Search from '~/components/GraphPage/Search';
import Title from '~/components/Title';
import Uploader from '~/components/GraphPage/Uploader';
import styled from 'styled-components';
import {fetcher} from '~/utils/fetch';
import {useTranslation} from 'react-i18next';
const FullWidthButton = styled(Button)`
width: 100%;
`;
const FullWidthSelect = styled<React.FunctionComponent<SelectProps<NonNullable<OpenedResult['selected']>>>>(Select)`
width: 100%;
`;
const ExportButtonWrapper = styled.div`
display: flex;
justify-content: space-between;
> * {
flex: 1 1 auto;
&:not(:last-child) {
margin-right: ${rem(20)};
}
}
`;
// TODO: better way to auto fit height
const SearchSection = styled(AsideSection)`
max-height: calc(100% - ${rem(40)});
display: flex;
flex-direction: column;
&:not(:last-child) {
padding-bottom: 0;
}
`;
const Graph: FunctionComponent = () => {
const {t} = useTranslation(['graph', 'common']);
const storeDispatch = useDispatch();
const storeModel = useSelector(selectors.graph.model);
const graph = useRef<GraphRef>(null);
const file = useRef<HTMLInputElement>(null);
const [files, setFiles] = useState<FileList | File[] | null>(storeModel);
const [filesId, setFilesId] = useState(0);
const [runs, setRuns] = useState<string[]>();
const [selectedRuns, setSelectedRuns] = useState<string>('');
const [isKeepData, setIsKeepData] = useState(false);
const setModelFile = useCallback(
(f: FileList | File[]) => {
storeDispatch(actions.graph.setModel(f));
setFiles(f);
},
[storeDispatch]
);
const onClickFile = useCallback(() => {
if (file.current) {
file.current.value = '';
file.current.click();
}
}, []);
const onChangeFile = (e: React.ChangeEvent<HTMLInputElement>) => {
const target = e.target as EventTarget & HTMLInputElement;
const file: FileList | null = target.files as FileList;
if (file[0].name.split('.')[1] !== 'pdmodel') {
alert('该页面只能解析paddle的模型,如需解析请跳转网络结构静态图页面');
return;
}
if (target && target.files && target.files.length) {
fileUploader(target.files);
}
};
const fileUploader = (files: FileList) => {
const formData = new FormData();
// 将文件转二进制
formData.append('file', files[0]);
formData.append('filename', files[0].name);
fetcher('/graph/upload', {
method: 'POST',
body: formData
}).then(
res => {
// debugger
const newFilesId = filesId + 1;
setFilesId(newFilesId);
},
res => {
// debugger
const newFilesId = filesId + 1;
setFilesId(newFilesId);
}
);
};
const [modelGraphs, setModelGraphs] = useState<OpenedResult['graphs']>([]);
const [selectedGraph, setSelectedGraph] = useState<NonNullable<OpenedResult['selected']>>('');
const setOpenedModel = useCallback((data: OpenedResult) => {
setModelGraphs(data.graphs);
setSelectedGraph(data.selected || '');
}, []);
const changeGraph = useCallback((name: string) => {
setSelectedGraph(name);
graph.current?.changeGraph(name);
}, []);
const [search, setSearch] = useState('');
const [searching, setSearching] = useState(false);
const [searchResult, setSearchResult] = useState<SearchResult>({text: '', result: []});
const onSearch = useCallback((value: string) => {
setSearch(value);
graph.current?.search(value);
}, []);
const onSelect = useCallback((item: SearchItem) => {
setSearch(item.name);
graph.current?.select(item);
}, []);
const [showAttributes, setShowAttributes] = useState(false);
const [showInitializers, setShowInitializers] = useState(true);
const [showNames, setShowNames] = useState(false);
const [horizontal, setHorizontal] = useState(false);
const [modelData, setModelData] = useState<Properties | null>(null);
const [nodeData, setNodeData] = useState<Properties | null>(null);
const [nodeDocumentation, setNodeDocumentation] = useState<Documentation | null>(null);
useEffect(() => {
// debugger
fetcher('/graph_runs').then((res: unknown) => {
const result = res as string[];
setRuns(result);
setSelectedRuns(result[0]);
});
}, [filesId]);
useEffect(() => {
setSearch('');
setSearchResult({text: '', result: []});
}, [files, showAttributes, showInitializers, showNames]);
const bottom = useMemo(
() =>
searching ? null : (
<FullWidthButton type="primary" rounded onClick={onClickFile}>
{t('graph:change-model')}
</FullWidthButton>
),
[t, onClickFile, searching]
);
const [rendered, setRendered] = useState(false);
const content = (runs: string) => {
return <div>{runs}</div>;
// return (<p>Content</p>)
};
const aside = useMemo(() => {
if (!rendered) {
return null;
}
if (nodeDocumentation) {
return (
<Aside width={rem(360)}>
<NodeDocumentationSidebar data={nodeDocumentation} onClose={() => setNodeDocumentation(null)} />
</Aside>
);
}
if (nodeData) {
return (
<Aside width={rem(360)}>
<NodePropertiesSidebar
data={nodeData}
onClose={() => setNodeData(null)}
showNodeDocumentation={() => graph.current?.showNodeDocumentation(nodeData)}
/>
</Aside>
);
}
return (
<Aside bottom={bottom}>
<SearchSection>
<Search
text={search}
data={searchResult}
onChange={onSearch}
onSelect={onSelect}
onActive={() => setSearching(true)}
onDeactive={() => setSearching(false)}
/>
</SearchSection>
{!searching && (
<>
<AsideSection>
<FullWidthButton onClick={() => graph.current?.showModelProperties()}>
{t('graph:model-properties')}
</FullWidthButton>
</AsideSection>
{modelGraphs.length > 1 && (
<AsideSection>
<Field label={t('graph:subgraph')}>
<FullWidthSelect list={modelGraphs} value={selectedGraph} onChange={changeGraph} />
</Field>
</AsideSection>
)}
<AsideSection>
<Field label={t('graph:display-data')}>
<div>
<Checkbox checked={showAttributes} onChange={setShowAttributes}>
{t('graph:show-attributes')}
</Checkbox>
</div>
<div>
<Checkbox checked={showInitializers} onChange={setShowInitializers}>
{t('graph:show-initializers')}
</Checkbox>
</div>
<div>
<Checkbox checked={showNames} onChange={setShowNames}>
{t('graph:show-node-names')}
</Checkbox>
</div>
<div>
<Checkbox checked={isKeepData} onChange={setIsKeepData}>
{/* {'保持展开状态'} */}
{t('graph:keep-expanded')}
</Checkbox>
</div>
</Field>
</AsideSection>
<AsideSection>
<Field label={t('graph:direction')}>
<RadioGroup value={horizontal} onChange={setHorizontal}>
<RadioButton value={false}>{t('graph:vertical')}</RadioButton>
<RadioButton value={true}>{t('graph:horizontal')}</RadioButton>
</RadioGroup>
</Field>
</AsideSection>
<AsideSection>
<Field label={t('graph:export-file')}>
<ExportButtonWrapper>
<Button onClick={() => graph.current?.export('png')}>
{t('graph:export-png')}
</Button>
<Button onClick={() => graph.current?.export('svg')}>
{t('graph:export-svg')}
</Button>
</ExportButtonWrapper>
</Field>
</AsideSection>
<AsideSection>
<Field label={t('graph:Choose-model')}>
<div className="run-list">
{runs &&
runs.map((run: string, index: number) => (
<div key={index}>
<Check
checked={selectedRuns === run ? true : false}
value={run}
title={run}
onChange={(value: string) => {
setSelectedRuns(run);
}}
>
{/* <Popover content={content(run)}> */}
<span className="run-item">
{/* <i style={{backgroundColor: run.colors[0]}}></i> */}
{run.split('/')[run.split('/').length - 1]}
</span>
{/* </Popover> */}
</Check>
</div>
))}
</div>
</Field>
</AsideSection>
</>
)}
</Aside>
);
}, [
t,
bottom,
search,
searching,
searchResult,
selectedRuns,
modelGraphs,
selectedGraph,
changeGraph,
onSearch,
onSelect,
showAttributes,
showInitializers,
showNames,
horizontal,
rendered,
nodeData,
nodeDocumentation
]);
const uploader = useMemo(
() => <Uploader onClickUpload={onClickFile} onDropFiles={setModelFile} />,
[onClickFile, setModelFile]
);
return (
<>
<Title>{t('common:graph')}</Title>
<ModelPropertiesDialog data={modelData} onClose={() => setModelData(null)} />
<Content aside={aside}>
<GraphComponent
ref={graph}
files={files}
uploader={uploader}
showAttributes={showAttributes}
showInitializers={showInitializers}
showNames={showNames}
isKeepData={isKeepData}
horizontal={horizontal}
selectedRuns={selectedRuns}
onRendered={() => setRendered(true)}
onOpened={setOpenedModel}
onSearch={data => setSearchResult(data)}
onShowModelProperties={data => setModelData(data)}
runs={runs}
onShowNodeProperties={data => {
setNodeData(data);
setNodeDocumentation(null);
}}
onShowNodeDocumentation={data => setNodeDocumentation(data)}
/>
<input
ref={file}
type="file"
multiple={false}
onChange={onChangeFile}
style={{
display: 'none'
}}
/>
</Content>
</>
);
};
export default Graph;
......@@ -16,7 +16,7 @@
import Aside, {AsideSection} from '~/components/Aside';
import type {Documentation, OpenedResult, Properties, SearchItem, SearchResult} from '~/resource/graph/types';
import GraphComponent, {GraphRef} from '~/components/GraphPage/Graph';
import GraphComponent, {GraphRef} from '~/components/GraphPage/GraphStatic';
import React, {FunctionComponent, useCallback, useEffect, useMemo, useRef, useState} from 'react';
import Select, {SelectProps} from '~/components/Select';
import {actions, selectors} from '~/store';
......@@ -311,7 +311,9 @@ const Graph: FunctionComponent = () => {
horizontal={horizontal}
onRendered={() => setRendered(true)}
onOpened={setOpenedModel}
onSearch={data => setSearchResult(data)}
onSearch={data => {
setSearchResult(data);
}}
onShowModelProperties={data => setModelData(data)}
onShowNodeProperties={data => {
setNodeData(data);
......
......@@ -75,8 +75,18 @@ const routes: Route[] = [
},
{
id: Pages.Graph,
path: '/graph',
component: React.lazy(() => import('~/pages/graph'))
children: [
{
id: 'graphDynamic',
path: '/graphDynamic',
component: React.lazy(() => import('~/pages/graphDynamic'))
},
{
id: 'graphStatic',
path: '/graphStatic',
component: React.lazy(() => import('~/pages/graphStatic'))
}
]
},
{
id: Pages.Histogram,
......
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>收起全部</title>
<defs>
<rect id="path-1" x="0" y="0" width="16" height="16"></rect>
</defs>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="1.2网格展开,右下角出现缩略地图" transform="translate(-698.000000, -91.000000)">
<g id="形状结合" transform="translate(698.000000, 91.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use id="蒙版" fill="#D8D8D8" opacity="0" xlink:href="#path-1"></use>
<path d="M7.56666006,9.03333994 C7.80600238,8.79399762 8.20070348,8.80070348 8.44831908,9.04831908 L8.44831908,9.04831908 L13.2085352,13.8085352 C13.4561508,14.0561508 13.4628566,14.4508519 13.2235143,14.6901942 C12.984172,14.9295365 12.5894709,14.9228306 12.3418553,14.6752151 L12.3418553,14.6752151 L8.02995827,10.363318 L3.67312385,14.7201525 C3.43385086,14.9594254 3.03908042,14.9527889 2.79146483,14.7051733 C2.54384923,14.4575577 2.53721271,14.0627873 2.77648569,13.8235143 L2.77648569,13.8235143 Z M2.79146483,1.20853517 C3.03908042,0.96091958 3.433753,0.954185191 3.67306012,1.19349231 L3.67306012,1.19349231 L8.03008573,5.55051792 L12.3419827,1.2386209 C12.5895983,0.991005306 12.9842709,0.984270917 13.223578,1.22357804 C13.4628852,1.46288515 13.4561508,1.85755773 13.2085352,2.10517333 L13.2085352,2.10517333 L8.44831908,6.86538942 C8.20070348,7.11300501 7.8060309,7.1197394 7.56672379,6.88043229 L7.56672379,6.88043229 L2.77642196,2.09013046 C2.53711485,1.85082334 2.54384923,1.45615077 2.79146483,1.20853517 Z" fill="#999999" mask="url(#mask-2)"></path>
</g>
</g>
</g>
</svg>
\ No newline at end of file
/**
* Copyright 2020 Baidu Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export default {status: 0, msg: '', data: ['test_add_graph/', 'test_add_graph/test1']};
......@@ -17,9 +17,8 @@
// cSpell:words actived nextcode
const view = require('./view');
const view2 = require('./view2');
const host = {};
host.BrowserHost = class {
constructor() {
window.eval = () => {
......@@ -54,7 +53,6 @@ host.BrowserHost = class {
this._view = view;
return Promise.resolve();
}
start() {
window.addEventListener(
'message',
......@@ -64,12 +62,19 @@ host.BrowserHost = class {
const type = originalData.type;
const data = originalData.data;
switch (type) {
// 在此书添加一个this._view的事件传递Graph页面过来的数据
case 'change-files':
return this._changeFiles(data);
case 'zoom-in':
return this._view.zoomIn();
case 'zoom-out':
return this._view.zoomOut();
case 'select-item':
return this._view.selectItem(data);
case 'toggle-Language':
return this._view.toggleLanguage(data);
case 'isAlt':
return this._view.changeAlt(data);
case 'zoom-reset':
return this._view.resetZoom();
case 'toggle-attributes':
......@@ -78,6 +83,8 @@ host.BrowserHost = class {
return this._view.toggleInitializers(data);
case 'toggle-names':
return this._view.toggleNames(data);
case 'toggle-KeepData':
return this._view.toggleKeepData(data);
case 'toggle-direction':
return this._view.toggleDirection(data);
case 'toggle-theme':
......@@ -86,6 +93,10 @@ host.BrowserHost = class {
return this._view.export(`${document.title}.${data}`);
case 'change-graph':
return this._view.changeGraph(data);
case 'change-allGraph':
return this._view.changeAllGrap(data);
case 'change-select':
return this._view.changeSelect(data);
case 'search':
return this._view.find(data);
case 'select':
......@@ -116,8 +127,19 @@ host.BrowserHost = class {
}
status(status) {
// 反传回去
this.message('status', status);
}
selectNodeId(nodeInfo) {
// 反传回去
console.log('节点点击事件触发了', nodeInfo);
this.message('nodeId', nodeInfo);
}
selectItems(item) {
// 反传回去
console.log('节点点击事件触发了', item);
this.message('selectItem', item);
}
error(message, detail) {
this.message('error', (message === 'Error' ? '' : message + ' ') + detail);
......@@ -176,6 +198,7 @@ host.BrowserHost = class {
}
_changeFiles(files) {
console.log('files', files);
if (files && files.length) {
files = Array.from(files);
const file = files.find(file => this._view.accept(file.name));
......@@ -498,4 +521,15 @@ class BrowserFileContext {
}
}
window.__view__ = new view.View(new host.BrowserHost());
function getCaption(obj) {
let index = obj.lastIndexOf('/'); //获取-后边的字符串
let newObj = obj.substring(index + 1, obj.length);
return newObj;
}
const hash = getCaption(document.referrer);
console.log('hash', hash);
if (hash === 'graphStatic') {
window.__view__ = new view2.View(new host.BrowserHost());
} else {
window.__view__ = new view.View(new host.BrowserHost());
}
此差异已折叠。
......@@ -547,7 +547,7 @@ sidebar.ModelSidebar = class {
}
}
if (this._model._graphs.length > 1) {
if (this._model) {
// let graphSelector = new sidebar.SelectView(
// this._host,
// this._model.graphs.map(g => g.name),
......@@ -683,23 +683,98 @@ sidebar.FindSidebar = class {
const id = item.id;
const nodesElement = graphElement.getElementById('nodes');
let nodeElement = nodesElement.firstChild;
while (nodeElement) {
if (nodeElement.id == id) {
selection.push(nodeElement);
if (nodesElement) {
let nodeElement = nodesElement.firstChild;
while (nodeElement) {
if (nodeElement.id == id) {
selection.push(nodeElement);
}
nodeElement = nodeElement.nextSibling;
}
}
const clustersElement = graphElement.getElementById('clusters');
if (clustersElement) {
let clusterElement = clustersElement.firstChild;
while (clusterElement) {
if (clusterElement.id == id) {
selection.push(clusterElement);
}
clusterElement = clusterElement.nextSibling;
}
nodeElement = nodeElement.nextSibling;
}
const edgePathsElement = graphElement.getElementById('edge-paths');
let edgePathElement = edgePathsElement.firstChild;
while (edgePathElement) {
if (edgePathElement.id == id) {
selection.push(edgePathElement);
if (edgePathsElement) {
let edgePathElement = edgePathsElement.firstChild;
while (edgePathElement) {
if (edgePathElement.id === id) {
// console.log('edgePathElement',edgePathElement.getAttribute("fromnode"),item);
// if (item.fromnode && edgePathElement.getAttribute("fromnode") === item.fromnode) {
// selection.push(edgePathElement);
// }
// if (item.tonode && edgePathElement.getAttribute("tonode") === item.tonode) {
// selection.push(edgePathElement);
// }
selection.push(edgePathElement);
}
edgePathElement = edgePathElement.nextSibling;
}
edgePathElement = edgePathElement.nextSibling;
}
let initializerElement = graphElement.getElementById(id);
if (initializerElement) {
while (initializerElement.parentElement) {
initializerElement = initializerElement.parentElement;
if (initializerElement.id && initializerElement.id.startsWith('node-')) {
selection.push(initializerElement);
break;
}
}
}
if (selection.length > 0) {
return selection;
}
return null;
}
static selection2(item, graphElement) {
const selection = [];
const id = item.id;
const nodesElement = graphElement.getElementById('nodes');
if (nodesElement) {
let nodeElement = nodesElement.firstChild;
while (nodeElement) {
if (nodeElement.id == id) {
selection.push(nodeElement);
}
nodeElement = nodeElement.nextSibling;
}
}
const clustersElement = graphElement.getElementById('clusters');
if (clustersElement) {
let clusterElement = clustersElement.firstChild;
while (clusterElement) {
if (clusterElement.id == id) {
selection.push(clusterElement);
}
clusterElement = clusterElement.nextSibling;
}
}
const edgePathsElement = graphElement.getElementById('edge-paths');
if (edgePathsElement) {
let edgePathElement = edgePathsElement.firstChild;
while (edgePathElement) {
if (edgePathElement.id === id) {
if (item.fromnode && edgePathElement.getAttribute('fromnode') === item.fromnode) {
selection.push(edgePathElement);
}
if (item.tonode && edgePathElement.getAttribute('tonode') === item.tonode) {
selection.push(edgePathElement);
}
}
edgePathElement = edgePathElement.nextSibling;
}
}
let initializerElement = graphElement.getElementById(id);
if (initializerElement) {
while (initializerElement.parentElement) {
......@@ -725,7 +800,6 @@ sidebar.FindSidebar = class {
const edgeMatches = new Set();
const result = [];
for (const node of this._graph.nodes) {
const initializers = [];
......@@ -744,13 +818,14 @@ sidebar.FindSidebar = class {
});
edgeMatches.add(argument.name);
} else {
initializers.push(argument.initializer);
// initializers.push(argument.initializer);
}
}
}
}
const name = node.name;
console.log('name', node);
const operator = node.type;
if (
!nodeMatches.has(name) &&
......@@ -759,12 +834,55 @@ sidebar.FindSidebar = class {
) {
result.push({
type: 'node',
name: node.name,
id: 'node-' + node.name
name: name,
id: 'node-' + name
});
nodeMatches.add(node.name);
nodeMatches.add(name);
}
// let path = node.name.split('/');
// path.pop();
// let groupName = path.join('/');
// console.log('groupName', groupName);
// const clusterNode = name => {
// if (
// !nodeMatches.has(name) &&
// name &&
// (name.toLowerCase().indexOf(text) != -1 || (operator && operator.toLowerCase().indexOf(text) != -1))
// ) {
// result.push({
// type: 'node',
// name: name,
// id: 'node-' + name
// });
// nodeMatches.add(name);
// let path = name.split('/');
// while (path.length > 0) {
// const name = path.join('/');
// path.pop();
// if (name) {
// clusterNode(name);
// }
// }
// }
// };
// if (groupName) {
// clusterNode(groupName);
// // g.setParent(nodeId, groupName);
// }
// clusterNode(node.show_name);
// if (
// !nodeMatches.has(name) &&
// name &&
// (name.toLowerCase().indexOf(text) != -1 || (operator && operator.toLowerCase().indexOf(text) != -1))
// ) {
// result.push({
// type: 'node',
// name: node.name,
// id: 'node-' + node.name
// });
// //
// nodeMatches.add(node.name);
// }
for (const initializer of initializers) {
result.push({
type: 'initializer',
......@@ -792,7 +910,6 @@ sidebar.FindSidebar = class {
}
}
}
return {
text: searchText,
result: result
......
......@@ -8,8 +8,8 @@ body {
margin: 0;
width: 100vw;
height: 100vh;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', 'Ubuntu', 'Droid Sans',
sans-serif, 'PingFang SC';
font-family: -apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', 'Ubuntu', 'Droid Sans', sans-serif,
'PingFang SC';
font-size: 12px;
text-rendering: geometricPrecision;
background-color: #fff;
......@@ -27,7 +27,7 @@ body {
.canvas {
display: block;
position: absolute;
// position: absolute;
text-rendering: geometricPrecision;
user-select: none;
cursor: grab;
......@@ -46,13 +46,14 @@ line {
}
text {
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif, "PingFang SC";
font-family: -apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', 'Ubuntu', 'Droid Sans', sans-serif,
'PingFang SC';
font-size: 11px;
text-rendering: geometricPrecision;
fill: #000;
.dark & {
fill: #CFCFD1;
fill: #cfcfd1;
}
}
......@@ -70,7 +71,7 @@ text {
&:hover {
path {
fill: #2932E1;
fill: #2932e1;
fill-opacity: 1;
}
......@@ -81,7 +82,7 @@ text {
}
.node-item-function path {
fill: #9BB9E8;
fill: #9bb9e8;
fill-opacity: 0.7;
}
......@@ -89,70 +90,88 @@ text {
cursor: pointer;
path {
fill: #8BB8FF;
fill: #8bb8ff;
fill-opacity: 0.9;
}
}
.node-item-type-constant path {
fill: #B4CCB7;
fill: #b4ccb7;
}
.node-item-type-control path {
fill: #A8E9B8;
fill: #a8e9b8;
}
.node-item-type-layer path {
fill: #DB989A;
fill: #db989a;
fill-opacity: 0.7;
}
.node-item-type-container path {
fill: #db989a;
fill-opacity: 0.7;
}
.node-item-type-wrapper path {
fill: #6DCDE4;
fill: #6dcde4;
fill-opacity: 0.7;
}
.node-item-type-conv path {
fill: #6dcde4;
fill-opacity: 0.7;
}
.node-item-type-activation path {
fill: #93C2CA;
fill: #93c2ca;
fill-opacity: 0.7;
}
.node-item-type-pool path {
fill: #DE7CCE;
fill: #de7cce;
fill-opacity: 0.7;
}
.node-item-type-normalization path {
fill: #DA96BC;
fill: #da96bc;
fill-opacity: 0.7;
}
.node-item-type-dropout path {
fill: #309E51;
fill: #309e51;
fill-opacity: 0.7;
}
.node-item-type-pad path {
fill: #309e51;
fill-opacity: 0.7;
}
.node-item-type-shape path {
fill: #D6C482;
fill: #d6c482;
fill-opacity: 0.7;
}
.node-item-type-tensor path {
fill: #6D7CE4;
fill: #6d7ce4;
fill-opacity: 0.7;
}
.node-item-type-transform path {
fill: #CDCB74;
fill: #cdcb74;
}
.node-item-type-sequence path {
fill: #cdcb74;
}
.node-item-type-data path {
fill: #2576AD;
fill: #2576ad;
fill-opacity: 0.7;
}
.node-item-type-custom path {
fill: #E46D6D;
fill: #e46d6d;
fill-opacity: 0.7;
}
......@@ -176,7 +195,7 @@ text {
cursor: pointer;
path {
fill: #CA5353;
fill: #ca5353;
fill-opacity: 0.7;
}
}
......@@ -203,7 +222,7 @@ text {
cursor: pointer;
path {
fill: #E49D6D;
fill: #e49d6d;
fill-opacity: 0.7;
}
}
......@@ -212,7 +231,7 @@ text {
cursor: pointer;
path {
fill: #E4E06D;
fill: #e4e06d;
fill-opacity: 0.9;
}
}
......@@ -235,18 +254,97 @@ text {
stroke-dasharray: 3, 2;
}
.cluster rect {
stroke: #000;
fill: #000;
fill-opacity: 0.02;
stroke-opacity: 0.06;
.cluster .clusterGroup {
fill: #dce9ff;
stroke: #666;
stroke-width: 1px;
}
.node-item-function path {
fill: #9bb9e8;
fill-opacity: 0.7;
}
.cluster .clusterGroup-constant {
fill: #e8efe9;
}
.select {
.cluster .clusterGroup-control {
fill: #e4f8e9;
}
.cluster .clusterGroup-layer {
fill: #f4e0e0;
}
.cluster .clusterGroup-conv {
fill: #d3f0f6;
}
.cluster .clusterGroup-container {
fill: #f4e0e0;
}
.cluster .clusterGroup-wrapper {
fill: #d3f0f6;
}
.cluster .clusterGroup-activation {
fill: #deecef;
}
.cluster .clusterGroup-pool {
fill: #f5d7f0;
}
.cluster .clusterGroup-normalization {
fill: #f3dfea;
}
.cluster .clusterGroup-dropout {
fill: #c0e1ca;
}
.cluster .clusterGroup-pad {
fill: #c0e1ca;
}
.cluster .clusterGroup-shape {
fill: #f2edd9;
}
.cluster .clusterGroup-tensor {
fill: #d3d7f6;
}
.cluster .clusterGroup-transform {
fill: #f0efd5;
}
.cluster .clusterGroup-sequence {
fill: #f0efd5;
}
.cluster .clusterGroup-data {
fill: #bdd5e6;
}
.cluster .clusterGroup-custom {
fill: #f6d3d3;
}
.cluster .clusterButton {
fill-opacity: 0.3;
fill: #db989a;
stroke: #999;
cursor: pointer;
}
.cluster .button-text {
fill: #999;
}
.cluster.border {
display: none;
}
.select {
&.edge-path {
stroke: #1527C2;
stroke: #1527c2;
stroke-width: 2px;
stroke-dasharray: 6px 3px;
stroke-dashoffset: 0;
......@@ -254,7 +352,15 @@ text {
}
.node.border {
stroke: #1527C2;
stroke: #1527c2;
stroke-width: 2px;
stroke-dasharray: 6px 3px;
stroke-dashoffset: 0;
animation: pulse 4s infinite linear;
}
.cluster.border {
display: block;
stroke: #1527c2;
stroke-width: 2px;
stroke-dasharray: 6px 3px;
stroke-dashoffset: 0;
......
此差异已折叠。
此差异已折叠。
......@@ -6146,6 +6146,11 @@ eslint-plugin-react@7.25.1:
resolve "^2.0.0-next.3"
string.prototype.matchall "^4.0.5"
eslint-plugin-simple-import-sort@^7.0.0:
version "7.0.0"
resolved "https://registry.npmmirror.com/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-7.0.0.tgz#a1dad262f46d2184a90095a60c66fef74727f0f8"
integrity sha512-U3vEDB5zhYPNfxT5TYR7u01dboFZp+HNpnGhkDB2g/2E4wZ/g1Q9Ton8UwCLfRV9yAKyYqDh62oHOamvkFxsvw==
eslint-scope@5.1.1, eslint-scope@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册