未验证 提交 bedaa029 编写于 作者: P Peter Pan 提交者: GitHub

feat: integrate netron (#651)

* chore: update dependencies

* feat: high-dimensional chart will fit screen size now

* fix: scatter chart cannot be rendered properly

* chore: separate api module

* intergrate netron

* intergrate netron

* feat: graphs

* style: fix lint

* chore: update dependencies

* fix: type error

* chore: update dependencies

* chore: update graph color
上级 1cc82207
......@@ -9,7 +9,7 @@ module.exports = {
ecmaVersion: 2018,
sourceType: 'module'
},
ignorePatterns: ['node_modules/', 'dist/', 'output/', '_next'],
ignorePatterns: ['node_modules/', 'dist/', 'output/', '_next', 'packages/core/public/netron'],
rules: {
'no-console': 'warn',
'sort-imports': 'error'
......@@ -25,6 +25,7 @@ module.exports = {
parser: '@typescript-eslint/parser',
rules: {
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'error'
}
},
......@@ -52,6 +53,7 @@ module.exports = {
},
rules: {
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'error',
'react/prop-types': 'off',
'react/react-in-jsx-scope': 'off'
......
......@@ -38,19 +38,19 @@
"version": "yarn format && git add -A"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "2.31.0",
"@typescript-eslint/parser": "2.31.0",
"eslint": "6.8.0",
"@typescript-eslint/eslint-plugin": "3.1.0",
"@typescript-eslint/parser": "3.1.0",
"eslint": "7.1.0",
"eslint-config-prettier": "6.11.0",
"eslint-plugin-prettier": "3.1.3",
"eslint-plugin-react": "7.19.0",
"eslint-plugin-react": "7.20.0",
"eslint-plugin-react-hooks": "4.0.0",
"husky": "4.2.5",
"lerna": "3.20.2",
"lint-staged": "10.2.2",
"lerna": "3.22.0",
"lint-staged": "10.2.8",
"prettier": "2.0.5",
"rimraf": "3.0.2",
"typescript": "3.8.3",
"typescript": "3.9.3",
"yarn": "1.22.4"
},
"engines": {
......
......@@ -35,17 +35,17 @@
],
"dependencies": {
"@visualdl/server": "2.0.0-beta.43",
"open": "7.0.3",
"open": "7.0.4",
"ora": "4.0.4",
"pm2": "4.4.0",
"yargs": "15.3.1"
},
"devDependencies": {
"@types/node": "13.13.5",
"@types/yargs": "15.0.4",
"@types/node": "14.0.10",
"@types/yargs": "15.0.5",
"cross-env": "7.0.2",
"ts-node": "8.10.1",
"typescript": "3.8.3"
"ts-node": "8.10.2",
"typescript": "3.9.3"
},
"engines": {
"node": ">=10",
......
import React, {FunctionComponent} from 'react';
import {WithStyled, asideWidth, borderColor, rem, size} from '~/utils/style';
import styled from 'styled-components';
export const AsideSection = styled.section`
margin: ${rem(20)};
&:not(:last-child) {
border-bottom: 1px solid ${borderColor};
padding-bottom: ${rem(20)};
margin-bottom: 0;
}
`;
const Wrapper = styled.div<{width?: string | number}>`
${props => size('100%', props.width == null ? asideWidth : props.width)}
overflow: hidden;
display: flex;
flex-direction: column;
> .aside-top {
flex: auto;
display: flex;
flex-direction: column;
height: 100%;
overflow: auto;
overflow-x: hidden;
overflow-y: auto;
> ${AsideSection} {
flex: none;
}
}
> .aside-bottom {
flex: none;
box-shadow: 0 -${rem(5)} ${rem(16)} 0 rgba(0, 0, 0, 0.03);
padding: ${rem(20)};
}
`;
type AsideProps = {
width?: string | number;
bottom?: React.ReactNode;
};
const Aside: FunctionComponent<AsideProps & WithStyled> = ({width, bottom, className, children}) => {
return (
<Wrapper width={width} className={className}>
<div className="aside-top">{children}</div>
{bottom && <div className="aside-bottom">{bottom}</div>}
</Wrapper>
);
};
export default Aside;
import React, {FunctionComponent} from 'react';
import {rem} from '~/utils/style';
import styled from 'styled-components';
const Divider = styled.hr<{height?: string | number}>`
background-color: transparent;
margin: 0;
border: none;
height: ${({height}) => (height ? ('number' === height ? rem(height) : height) : rem(30))};
`;
type AsideDividerProps = {
height?: string | number;
};
const AsideDivider: FunctionComponent<AsideDividerProps> = ({height}) => <Divider height={height} />;
export default AsideDivider;
......@@ -5,6 +5,7 @@ import {
borderColor,
borderFocusedColor,
borderRadius,
css,
dangerActiveColor,
dangerColor,
dangerFocusedColor,
......@@ -38,11 +39,26 @@ const colors = {
}
};
const Wrapper = styled.a<{type?: keyof typeof colors; rounded?: boolean; disabled?: boolean}>`
const defaultColor = {
default: borderColor,
active: borderActiveColor,
focused: borderFocusedColor
} as const;
type colorTypes = keyof typeof colors;
const statusButtonColor: (
status: 'focused' | 'active'
) => (props: {disabled?: boolean; type?: colorTypes}) => ReturnType<typeof css> = status => ({disabled, type}) => css`
${disabled || type ? '' : sameBorder({color: defaultColor[status]})}
background-color: ${disabled ? '' : type ? colors[type][status] : 'transparent'};
`;
const Wrapper = styled.a<{type?: colorTypes; rounded?: boolean; disabled?: boolean}>`
height: ${height};
line-height: ${height};
border-radius: ${props => (props.rounded ? half(height) : borderRadius)};
${props => (props.type ? '' : sameBorder({color: borderColor}))}
${props => (props.type ? '' : sameBorder({color: defaultColor.default}))}
background-color: ${props => (props.type ? colors[props.type].default : 'transparent')};
color: ${props => (props.disabled ? textLighterColor : props.type ? textInvertColor : textColor)};
cursor: ${props => (props.disabled ? 'not-allowed' : 'pointer')};
......@@ -53,20 +69,14 @@ const Wrapper = styled.a<{type?: keyof typeof colors; rounded?: boolean; disable
${transitionProps(['background-color', 'border-color'])}
${ellipsis()}
${props =>
props.disabled
? ''
: `
&:hover,
&:focus {
${props.type ? '' : sameBorder({color: borderFocusedColor})}
background-color: ${props.type ? colors[props.type].focused : 'transparent'};
}
&:hover,
&:focus {
${statusButtonColor('focused')}
}
&:active {
${props.type ? '' : sameBorder({color: borderActiveColor})}
background-color: ${props.type ? colors[props.type].active : 'transparent'};
}`}
&:active {
${statusButtonColor('active')}
}
`;
const Icon = styled(RawIcon)`
......@@ -76,7 +86,7 @@ const Icon = styled(RawIcon)`
type ButtonProps = {
rounded?: boolean;
icon?: string;
type?: keyof typeof colors;
type?: colorTypes;
disabled?: boolean;
onClick?: () => unknown;
};
......
......@@ -19,15 +19,15 @@ import ReactTooltip from 'react-tooltip';
import {nanoid} from 'nanoid';
import styled from 'styled-components';
const Toolbox = styled.div`
const Toolbox = styled.div<{reversed?: boolean}>`
font-size: ${em(16)};
height: 1em;
line-height: 1;
margin-bottom: ${rem(18)};
display: flex;
flex-direction: ${props => (props.reversed ? 'row-reverse' : 'row')};
align-items: center;
`;
const ToolboxItem = styled.a<{active?: boolean}>`
const ToolboxItem = styled.a<{active?: boolean; reversed?: boolean}>`
cursor: pointer;
color: ${props => (props.active ? primaryColor : textLighterColor)};
${transitionProps('color')}
......@@ -41,7 +41,7 @@ const ToolboxItem = styled.a<{active?: boolean}>`
}
& + & {
margin-left: ${rem(14)};
${props => `margin-${props.reversed ? 'right' : 'left'}: ${rem(14)};`}
}
`;
......@@ -67,9 +67,17 @@ export type ChartTooboxItem = NormalChartToolboxItem | ToggleChartToolboxItem;
type ChartToolboxProps = {
cid?: string;
items: ChartTooboxItem[];
reversed?: boolean;
tooltipPlace?: 'top' | 'bottom' | 'left' | 'right';
};
const ChartToolbox: FunctionComponent<ChartToolboxProps & WithStyled> = ({cid, items, className}) => {
const ChartToolbox: FunctionComponent<ChartToolboxProps & WithStyled> = ({
cid,
items,
reversed,
tooltipPlace,
className
}) => {
const [activeStatus, setActiveStatus] = useState<boolean[]>(new Array(items.length).fill(false));
const onClick = useCallback(
(index: number) => {
......@@ -92,10 +100,11 @@ const ChartToolbox: FunctionComponent<ChartToolboxProps & WithStyled> = ({cid, i
return (
<>
<Toolbox className={className}>
<Toolbox className={className} reversed={reversed}>
{items.map((item, index) => (
<ToolboxItem
key={index}
reversed={reversed}
active={item.toggle && !item.activeIcon && activeStatus[index]}
onClick={() => onClick(index)}
data-for={item.tooltip ? id : null}
......@@ -113,7 +122,7 @@ const ChartToolbox: FunctionComponent<ChartToolboxProps & WithStyled> = ({cid, i
</Toolbox>
<ReactTooltip
id={id}
place="top"
place={tooltipPlace ?? 'top'}
textColor={tooltipTextColor}
backgroundColor={tooltipBackgroundColor}
effect="solid"
......
import React, {FunctionComponent} from 'react';
import {asideWidth, backgroundColor, headerHeight, math, position, primaryColor, rem, size} from '~/utils/style';
import {backgroundColor, contentHeight, contentMargin, headerHeight, position, primaryColor, size} from '~/utils/style';
import HashLoader from 'react-spinners/HashLoader';
import styled from 'styled-components';
const margin = rem(20);
const Section = styled.section`
/* trigger BFC */
overflow: hidden;
display: flex;
`;
const Article = styled.article<{aside?: boolean}>`
margin: ${margin};
margin-right: ${props => (props.aside ? math(`${margin} + ${asideWidth}`) : margin)};
min-height: calc(100vh - ${math(`${margin} * 2 + ${headerHeight}`)});
const Article = styled.article`
flex: auto;
margin: ${contentMargin};
min-height: ${contentHeight};
`;
const Aside = styled.aside`
flex: none;
background-color: ${backgroundColor};
${size(`calc(100vh - ${headerHeight})`, asideWidth)}
${position('fixed', headerHeight, 0, null, null)}
height: ${`calc(100vh - ${headerHeight})`};
${position('sticky', headerHeight, 0, null, null)}
overflow-x: hidden;
overflow-y: auto;
`;
......@@ -43,7 +41,7 @@ type ContentProps = {
const Content: FunctionComponent<ContentProps> = ({children, aside, loading}) => (
<Section>
<Article aside={!!aside}>{children}</Article>
<Article>{children}</Article>
{aside && <Aside>{aside}</Aside>}
{loading && (
<Loading>
......
import {Argument as ArgumentType, Property as PropertyType} from '~/resource/graphs/types';
import React, {FunctionComponent, useMemo, useState} from 'react';
import {borderColor, em, sameBorder, textLightColor, textLighterColor} from '~/utils/style';
import Icon from '~/components/Icon';
import styled from 'styled-components';
const Wrapper = styled.div`
${sameBorder({radius: true})}
& + & {
margin-top: ${em(10)};
}
> .argument-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: ${em(8)} ${em(10)};
line-height: 1.5;
> .argument-text {
flex: auto;
overflow: hidden;
word-break: break-all;
}
> .argument-raw {
overflow: auto;
width: 100%;
pre {
margin: 0;
}
}
> .argument-operation {
flex: none;
cursor: pointer;
font-size: ${em(14)};
margin-left: ${em(10)};
color: ${textLighterColor};
&:hover,
&:active {
color: ${textLightColor};
}
}
&:not(:first-child) {
border-top: 1px solid ${borderColor};
}
}
`;
type ArgumentProps = {
value: ArgumentType | PropertyType;
expand?: boolean;
showNodeDodumentation?: () => unknown;
};
const Argument: FunctionComponent<ArgumentProps> = ({value, expand, showNodeDodumentation}) => {
const [expanded, setExpanded] = useState(expand ?? false);
const expandable = useMemo(() => {
const argument = value as ArgumentType;
return !!(argument.children && argument.children.length);
}, [value]);
return (
<Wrapper>
<div className="argument-row">
<span className="argument-text">
{value.name ? (
<>
{value.name}: <b>{value.value}</b>
</>
) : (
value.value.split('\n').map((line, index) => (
<React.Fragment key={index}>
{index !== 0 && <br />}
{line}
</React.Fragment>
))
)}
</span>
{(value as PropertyType).documentation && (
<a className="argument-operation" onClick={() => showNodeDodumentation?.()}>
<Icon type="question-circle" />
</a>
)}
{expandable && (
<a className="argument-operation" onClick={() => setExpanded(e => !e)}>
<Icon type={expanded ? 'minus' : 'plus'} />
</a>
)}
</div>
{expandable &&
expanded &&
(value as ArgumentType)?.children?.map((item, index) => (
<div className="argument-row" key={index}>
{item.type === 'raw' ? (
<span className="argument-raw">
<pre>{item.value}</pre>
</span>
) : (
<span className="argument-text">
{item.name ? `${item.name}: ` : ''}
<b>{item.type === 'code' ? <code>{item.value}</code> : item.value}</b>
</span>
)}
</div>
))}
</Wrapper>
);
};
export default Argument;
import {Documentation, Properties, SearchItem, SearchResult} from '~/resource/graphs/types';
import React, {useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
import {backgroundColor, borderColor, contentHeight, primaryColor, rem, size} from '~/utils/style';
import ChartToolbox from '~/components/ChartToolbox';
import HashLoader from 'react-spinners/HashLoader';
import styled from 'styled-components';
import {useTranslation} from '~/utils/i18n';
const toolboxHeight = rem(40);
const Wrapper = styled.div`
position: relative;
height: ${contentHeight};
background-color: ${backgroundColor};
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
`;
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 ${borderColor};
padding: 0 ${rem(20)};
`;
const Content = styled.div`
height: calc(100% - ${toolboxHeight});
> iframe {
${size('100%', '100%')}
border: none;
}
`;
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;
search(value: string): void;
select(item: SearchItem): void;
showModelProperties(): void;
showNodeDocumentation: (data: Properties) => void;
};
type GraphProps = {
files: FileList | null;
uploader: JSX.Element;
showAttributes: boolean;
showInitializers: boolean;
showNames: boolean;
onRendered?: () => unknown;
onSearch?: (data: SearchResult) => unknown;
onShowModelProperties?: (data: Properties) => unknown;
onShowNodeProperties?: (data: Properties) => unknown;
onShowNodeDocumentation?: (data: Documentation) => unknown;
};
const Graph = React.forwardRef<GraphRef, GraphProps>(
(
{
files,
uploader,
showAttributes,
showInitializers,
showNames,
onRendered,
onSearch,
onShowModelProperties,
onShowNodeProperties,
onShowNodeDocumentation
},
ref
) => {
const {t} = useTranslation('graphs');
const [ready, setReady] = useState(false);
const [loading, setLoading] = useState(false);
const [rendered, setRendered] = useState(false);
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);
case 'rendered':
setLoading(false);
setRendered(true);
onRendered?.();
return;
}
return;
case 'search':
return onSearch?.(data);
case 'show-model-properties':
return onShowModelProperties?.(data);
case 'show-node-properties':
return onShowNodeProperties?.(data);
case 'show-node-documentation':
return onShowNodeDocumentation?.(data);
}
}
},
[onRendered, onSearch, onShowModelProperties, onShowNodeProperties, onShowNodeDocumentation]
);
useEffect(() => {
if (process.browser) {
window.addEventListener('message', handler);
return () => window.removeEventListener('message', handler);
}
}, [handler]);
const dispatch = useCallback((type: string, data?: unknown) => {
if (process.browser) {
iframe.current?.contentWindow?.postMessage(
{
type,
data
},
`${window.location.protocol}//${window.location.host}`
);
}
}, []);
useEffect(() => dispatch('change-files', files), [dispatch, files]);
useEffect(() => dispatch('toggle-attributes', showAttributes), [dispatch, showAttributes]);
useEffect(() => dispatch('toggle-initializers', showInitializers), [dispatch, showInitializers]);
useEffect(() => dispatch('toggle-names', showNames), [dispatch, showNames]);
useImperativeHandle(ref, () => ({
export(type) {
dispatch('export', type);
},
search(value) {
dispatch('search', value);
},
select(item) {
dispatch('select', item);
},
showModelProperties() {
dispatch('show-model-properties');
},
showNodeDocumentation(data) {
dispatch('show-node-documentation', data);
}
}));
const content = useMemo(() => {
if (!ready || loading) {
return (
<Loading>
<HashLoader size="60px" color={primaryColor} />
</Loading>
);
}
if (ready && !rendered) {
return uploader;
}
return null;
}, [ready, loading, rendered, uploader]);
return (
<Wrapper>
{content}
<RenderContent show={!loading && rendered}>
<Toolbox
items={[
{
icon: 'restore-size',
tooltip: t('graphs:restore-size'),
onClick: () => dispatch('zoom-reset')
},
{
icon: 'zoom-out',
tooltip: t('graphs:zoom-out'),
onClick: () => dispatch('zoom-out')
},
{
icon: 'zoom-in',
tooltip: t('graphs:zoom-in'),
onClick: () => dispatch('zoom-in')
}
]}
reversed
tooltipPlace="bottom"
/>
<Content>
<iframe
ref={iframe}
src="/netron/index.html"
frameBorder={0}
scrolling="no"
marginWidth={0}
marginHeight={0}
></iframe>
</Content>
</RenderContent>
</Wrapper>
);
}
);
Graph.displayName = 'Graph';
export default Graph;
import React, {FunctionComponent} from 'react';
import {backgroundColor, borderColor, rem, textLightColor} from '~/utils/style';
import styled from 'styled-components';
import {useTranslation} from '~/utils/i18n';
const Sidebar = styled.div`
height: 100%;
background-color: ${backgroundColor};
`;
const Title = styled.div`
height: ${rem(60)};
font-size: ${rem(16)};
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid ${borderColor};
margin: 0 ${rem(20)};
> .close {
flex: none;
color: ${textLightColor};
cursor: pointer;
}
`;
const Content = styled.div`
padding: ${rem(20)};
height: calc(100% - ${rem(60)});
overflow: auto;
`;
type GraphSidebarProps = {
title: string;
onClose?: () => unknown;
};
const GraphSidebar: FunctionComponent<GraphSidebarProps> = ({title, onClose, children}) => {
const {t} = useTranslation('common');
return (
<Sidebar>
<Title>
<span>{title}</span>
<a className="close" onClick={() => onClose?.()}>
{t('common:close')}
</a>
</Title>
<Content>{children}</Content>
</Sidebar>
);
};
export default GraphSidebar;
import React, {FunctionComponent} from 'react';
import {backgroundColor, em, size} from '~/utils/style';
import Icon from '~/components/Icon';
import Properties from '~/components/GraphsPage/Properties';
import {Properties as PropertiesType} from '~/resource/graphs/types';
import styled from 'styled-components';
import {useTranslation} from '~/utils/i18n';
const Dialog = styled.div`
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
overscroll-behavior: none;
background-color: rgba(255, 255, 255, 0.8);
z-index: 999;
> .modal {
width: ${em(536)};
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
box-shadow: 0 2px 20px 0 rgba(0, 0, 0, 0.08);
> .modal-header {
padding: 0 ${em(40, 18)};
height: ${em(47, 18)};
background-color: #eee;
display: flex;
justify-content: space-between;
align-items: center;
font-size: ${em(18)};
> .modal-title {
flex: auto;
}
> .modal-close {
flex: none;
${size(em(14, 18), em(14, 18))}
font-size: ${em(14, 18)};
text-align: center;
cursor: pointer;
}
}
> .modal-body {
padding: ${em(40)};
background-color: ${backgroundColor};
overflow: auto;
max-height: calc(80vh - ${em(47)});
}
}
`;
type ModelPropertiesDialogProps = {
data?: PropertiesType | null;
onClose?: () => unknown;
};
const ModelPropertiesDialog: FunctionComponent<ModelPropertiesDialogProps> = ({data, onClose}) => {
const {t} = useTranslation('graphs');
if (!data) {
return null;
}
return (
<Dialog>
<div className="modal">
<div className="modal-header">
<span className="modal-title">{t('graphs:model-properties')}</span>
<a className="modal-close" onClick={() => onClose?.()}>
<Icon type="close" />
</a>
</div>
<div className="modal-body">
<Properties {...data} expand />
</div>
</div>
</Dialog>
);
};
export default ModelPropertiesDialog;
import React, {FunctionComponent, useCallback} from 'react';
import {Trans, useTranslation} from '~/utils/i18n';
import {borderRadius, em, textLightColor} from '~/utils/style';
import {Documentation as DocumentationType} from '~/resource/graphs/types';
import GraphSidebar from '~/components/GraphsPage/GraphSidebar';
import styled from 'styled-components';
const Documentation = styled.div`
overflow: hidden;
word-break: break-word;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', 'Ubuntu', 'Droid Sans', sans-serif;
h1 {
font-size: ${em(18)};
margin: ${em(10)} 0;
}
h2 {
font-size: ${em(16)};
margin: ${em(10)} 0;
}
h3 {
font-size: ${em(14)};
margin: ${em(10)} 0;
}
p {
line-height: 1.5;
margin: ${em(10)} 0;
}
dl {
line-height: 1.5;
margin: ${em(10)} 0;
> dt {
font-weight: 700;
}
> dd {
margin-left: ${em(20)};
}
}
pre {
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
background-color: rgba(216, 216, 216, 0.5);
color: ${textLightColor};
padding: ${em(10)};
border-radius: ${borderRadius};
overflow: auto;
code {
background-color: transparent;
padding: 0;
border-radius: 0;
}
}
code {
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
background-color: rgba(216, 216, 216, 0.5);
color: ${textLightColor};
padding: ${em(2)} ${em(4)};
border-radius: ${em(2)};
}
`;
type NodeDocumentationSidebarProps = {
data?: DocumentationType | null;
onClose?: () => unknown;
};
const NodeDocumentationSidebar: FunctionComponent<NodeDocumentationSidebarProps> = ({data, onClose}) => {
const {t} = useTranslation('graphs');
const list = useCallback(
(items: {name: string; type?: string | string[]; description: string}[]) =>
items.map((item, index) => (
<dl key={index}>
<dt>
{item.name}
{item.type && (
<>
:{' '}
{'string' === typeof item.type ? (
<code>{item.type}</code>
) : (
item.type.map((i, j) => (
<React.Fragment key={j}>
{j ? ',' : null}
<code>{i}</code>
</React.Fragment>
))
)}
</>
)}
</dt>
<dd dangerouslySetInnerHTML={{__html: item.description}}></dd>
</dl>
)),
[]
);
return (
<GraphSidebar title={t('graphs:node-documentation')} onClose={onClose}>
<Documentation>
<h1>{data?.name}</h1>
{data?.summary && <p dangerouslySetInnerHTML={{__html: data.summary}}></p>}
{data?.description && <p dangerouslySetInnerHTML={{__html: data.description}}></p>}
{data?.attributes && (
<>
<h2>{t('graphs:documentation.attributes')}</h2>
{list(data.attributes)}
</>
)}
{data?.inputs && (
<>
<h2>
{t('graphs:documentation.inputs')}
{data?.inputs_range && ` (${data.inputs_range.replace(/&#8734;/g, '')})`}
</h2>
{list(data.inputs)}
</>
)}
{data?.outputs && (
<>
<h2>
{t('graphs:documentation.outputs')}
{data?.outputs_range && ` (${data.outputs_range.replace(/&#8734;/g, '')})`}
</h2>
{list(data.outputs)}
</>
)}
{data?.type_constraints && (
<>
<h2>{t('graphs:documentation.type-constraints')}</h2>
{list(
data.type_constraints.map(({type_param_str, allowed_type_strs, description}) => ({
name: type_param_str,
type: allowed_type_strs,
description
}))
)}
</>
)}
{data?.examples && (
<>
<h2>{t('graphs:documentation.examples')}</h2>
{data.examples.map((example, index) => (
<React.Fragment key={index}>
<h3>{example.summary}</h3>
<pre>{example.code}</pre>
</React.Fragment>
))}
</>
)}
{data?.references && (
<>
<h2>{t('graphs:documentation.references')}</h2>
<ul>
{data.references.map((reference, index) => (
<li key={index} dangerouslySetInnerHTML={{__html: reference.description}}></li>
))}
</ul>
</>
)}
{data && data.domain && data.since_version && data.support_level && (
<>
<h2>{t('graphs:documentation.support')}</h2>
<dl>
{/* eslint-disable prettier/prettier */}
<Trans i18nKey="graphs:documentation.support-info">
In domain <code>{{domain: data.domain}}</code> since version <code>{{since_version: data.since_version}}</code> at support level <code>{{support_level: data.support_level}}</code>.
</Trans>
{/* eslint-enable prettier/prettier */}
</dl>
</>
)}
</Documentation>
</GraphSidebar>
);
};
export default NodeDocumentationSidebar;
import {NodeType, TypedNode} from '~/resource/graphs';
import React, {FunctionComponent} from 'react';
import {WithStyled, textLightColor} from '~/utils/style';
import styled from 'styled-components';
import {useTranslation} from '~/utils/i18n';
const typeName: {[k in NodeType]: string} = {
[NodeType.Input]: 'input',
[NodeType.Output]: 'output',
[NodeType.Op]: 'operator'
};
export interface NodeInfoProps {
node?: TypedNode | {type: 'unknown'; guessType: NodeType; msg: string};
}
const DataList: FunctionComponent<{items: {key: string; value: string | string[]}[]} & WithStyled> = props => {
return (
<ul className={props.className}>
{props.items.map(({key, value}) => (
<li key={key}>
{key}: {value}
</li>
))}
</ul>
);
};
const PropertyList = styled(DataList)`
padding: 0;
list-style: none;
color: ${textLightColor};
li + li {
margin-top: 1em;
}
`;
const NodeInfo: FunctionComponent<NodeInfoProps> = props => {
const {t} = useTranslation('graphs');
if (!props.node) {
return <p>{t('graphs:click-node')}</p>;
}
const node = props.node;
switch (node.type) {
case NodeType.Input:
case NodeType.Output:
return (
<PropertyList
items={[
{key: t('graphs:node-type'), value: typeName[node.type]},
{key: t('graphs:node-name'), value: node.name},
{key: t('graphs:node-data-shape'), value: node.shape},
{key: t('graphs:node-data-type'), value: node.data_type}
]}
/>
);
case NodeType.Op:
return (
<PropertyList
items={[
{key: t('graphs:node-type'), value: typeName[node.type]},
{key: t('graphs:input'), value: node.input},
{key: t('graphs:op-type'), value: node.opType},
{key: t('graphs:output'), value: node.output}
]}
/>
);
case 'unknown':
return <PropertyList items={[{key: t('graphs:node-type'), value: typeName[node.guessType]}]} />;
default:
return null;
}
};
export default NodeInfo;
import React, {FunctionComponent} from 'react';
import GraphSidebar from '~/components/GraphsPage/GraphSidebar';
import Properties from '~/components/GraphsPage/Properties';
import {Properties as PropertiesType} from '~/resource/graphs/types';
import {useTranslation} from '~/utils/i18n';
type NodePropertiesSidebarProps = {
data?: PropertiesType | null;
onClose?: () => unknown;
showNodeDodumentation?: () => unknown;
};
const NodePropertiesSidebar: FunctionComponent<NodePropertiesSidebarProps> = ({
data,
onClose,
showNodeDodumentation
}) => {
const {t} = useTranslation('graphs');
return (
<GraphSidebar title={t('graphs:node-properties')} onClose={onClose}>
<Properties {...data} showNodeDodumentation={showNodeDodumentation} />
</GraphSidebar>
);
};
export default NodePropertiesSidebar;
import React, {FunctionComponent} from 'react';
import {Properties as PropertiesType} from '~/resource/graphs/types';
import Property from '~/components/GraphsPage/Property';
import {em} from '~/utils/style';
import styled from 'styled-components';
import {useTranslation} from '~/utils/i18n';
const Header = styled.div`
font-size: ${em(16)};
font-weight: 700;
padding: ${em(10)} 0;
`;
type PropertiesProps = PropertiesType & {
expand?: boolean;
showNodeDodumentation?: () => unknown;
};
const Properties: FunctionComponent<PropertiesProps> = ({properties, groups, expand, showNodeDodumentation}) => {
const {t} = useTranslation('graphs');
return (
<>
{properties?.map((property, index) => (
<Property
name={t(`graphs:properties.${property.name}`)}
values={property.values}
key={index}
showNodeDodumentation={showNodeDodumentation}
/>
))}
{groups?.map((group, index) => (
<React.Fragment key={index}>
<Header>{t(`graphs:properties.${group.name}`)}</Header>
{group.properties?.map((property, anotherIndex) => (
<Property
{...property}
key={anotherIndex}
expand={expand}
showNodeDodumentation={showNodeDodumentation}
/>
))}
</React.Fragment>
))}
</>
);
};
export default Properties;
import {Argument as ArgumentType, NameValues, Property as PropertyType} from '~/resource/graphs/types';
import React, {FunctionComponent} from 'react';
import {ellipsis, em, sameBorder} from '~/utils/style';
import Argument from '~/components/GraphsPage/Argument';
import styled from 'styled-components';
const Wrapper = styled.div`
display: flex;
align-items: top;
justify-content: space-between;
width: 100%;
> .property-name {
flex: none;
text-align: right;
width: ${em(80)};
padding: ${em(8)} 0;
${sameBorder({color: 'transparent'})}
${ellipsis()}
}
> .property-values {
flex: auto;
width: calc(100% - ${em(90)});
margin-left: ${em(10)};
}
& + & {
margin-top: ${em(10)};
}
`;
type PropertyProps = NameValues<ArgumentType | PropertyType> & {
expand?: boolean;
showNodeDodumentation?: () => unknown;
};
const Property: FunctionComponent<PropertyProps> = ({name, values, expand, showNodeDodumentation}) => {
return (
<Wrapper>
<label className="property-name" title={name}>
{name}
</label>
<div className="property-values">
{values.map((value, index) => (
<Argument key={index} value={value} expand={expand} showNodeDodumentation={showNodeDodumentation} />
))}
</div>
</Wrapper>
);
};
export default Property;
import React, {FunctionComponent, useCallback, useEffect, useState} from 'react';
import {SearchItem, SearchResult} from '~/resource/graphs/types';
import {
backgroundColor,
backgroundFocusedColor,
css,
ellipsis,
em,
primaryColor,
rem,
sameBorder,
size,
textLightColor,
transitionProps,
triangle
} from '~/utils/style';
import Field from '~/components/Field';
import SearchInput from '~/components/SearchInput';
import styled from 'styled-components';
import useSearchValue from '~/hooks/useSearchValue';
import {useTranslation} from '~/utils/i18n';
const SearchField = styled(Field)`
margin-bottom: ${rem(20)};
display: flex;
justify-content: space-between;
align-items: center;
> :first-child {
flex: auto;
}
> a:last-child {
color: ${primaryColor};
cursor: pointer;
margin-left: ${rem(10)};
flex: none;
}
`;
const Empty = styled.div`
padding: ${rem(100)} 0;
text-align: center;
color: ${textLightColor};
`;
const Wrapper = styled.div`
overflow: auto;
`;
const List = styled.ul`
list-style: none;
margin: 0;
padding: 0;
`;
const Item = styled.li`
padding: ${em(10)} ${em(12)};
cursor: pointer;
width: 100%;
background-color: ${backgroundColor};
display: flex;
align-items: center;
${transitionProps('background-color')}
> span {
flex: auto;
margin-left: ${em(10)};
${ellipsis()}
}
&:hover {
background-color: ${backgroundFocusedColor};
}
`;
const icon = css`
color: #979797;
flex: none;
width: ${em(8)};
`;
const EdgeIcon = styled.i`
${icon}
position: relative;
height: ${em(11)};
overflow: hidden;
&::before {
content: '';
display: block;
${size(em(6), em(1))}
background-color: currentColor;
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
}
&::after {
content: '';
display: block;
${triangle({
pointingDirection: 'bottom',
height: em(5),
width: em(7),
foregroundColor: 'currentColor'
})}
position: absolute;
top: ${em(6)};
left: 50%;
transform: translateX(-50%);
}
`;
const NodeIcon = styled.i`
${icon}
height: ${em(7)};
${sameBorder({radius: em(2), color: 'currentColor'})}
background-color: #f7f7f7;
`;
const InitializerIcon = styled.i`
${icon}
height: ${em(8)};
${sameBorder({radius: em(4), color: 'currentColor'})}
background-color: #f7f7f7;
`;
const icons = {
input: EdgeIcon,
output: EdgeIcon,
node: NodeIcon,
initializer: InitializerIcon
} as const;
type SearchProps = {
text?: string;
data: SearchResult;
onChange?: (value: string) => unknown;
onSelect?: (item: SearchItem) => unknown;
onActive?: () => unknown;
onDeactive?: () => unknown;
};
const Search: FunctionComponent<SearchProps> = ({text, data, onChange, onSelect, onActive, onDeactive}) => {
const {t} = useTranslation(['graphs', 'common']);
const [search, setSearch] = useState(text ?? '');
const [searching, setSearching] = useState(false);
const [searchResult, setSearchResult] = useState<SearchItem[]>(data.result);
const debouncedSearchText = useSearchValue(search);
useEffect(() => setSearch(text ?? ''), [text]);
useEffect(() => {
if (searching) {
onChange?.(debouncedSearchText);
} else {
setSearchResult([]);
}
}, [debouncedSearchText, searching, onChange]);
useEffect(() => {
if (data.text === search) {
setSearchResult(data.result);
}
}, [data, search]);
const focus = useCallback(() => {
setSearching(true);
onActive?.();
}, [onActive]);
const cancel = useCallback(() => {
setSearch('');
onChange?.('');
setSearching(false);
onDeactive?.();
}, [onChange, onDeactive]);
const select = useCallback(
(item: SearchItem) => {
setSearch(item.name);
onSelect?.(item);
setSearching(false);
onDeactive?.();
},
[onSelect, onDeactive]
);
return (
<>
<SearchField>
<SearchInput placeholder={t('common:search')} value={search} onChange={setSearch} onFocus={focus} />
{searching && <a onClick={cancel}>{t('common:cancel')}</a>}
</SearchField>
{searching &&
(searchResult.length ? (
<Wrapper>
<List>
{searchResult.map(item => {
const Icon = icons[item.type];
return (
<Item key={item.id} onClick={() => select(item)} title={item.name}>
<Icon />
<span>{item.name}</span>
</Item>
);
})}
</List>
</Wrapper>
) : (
<Empty>{t('graphs:nothing-matched')}</Empty>
))}
</>
);
};
export default Search;
import React, {FunctionComponent, useCallback, useState} from 'react';
import {em, primaryColor, sameBorder, size, textLightColor} from '~/utils/style';
import Button from '~/components/Button';
import Icon from '~/components/Icon';
import styled from 'styled-components';
import {useTranslation} from '~/utils/i18n';
const DropZone = styled.div<{actived: boolean}>`
${props =>
sameBorder({
width: '1px',
type: 'dashed',
radius: em(16),
color: props.actived ? primaryColor : undefined
})}
background-color: ${props => (props.actived ? '#f2f6ff' : '#f9f9f9')};
${size('43.2%', '68%')}
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
> .upload-icon {
font-size: ${em(64)};
color: ${primaryColor};
}
> span {
font-size: ${em(18)};
line-height: 3.2;
}
> .upload-button {
min-width: ${em(155)};
}
`;
const SupportTable = styled.table`
max-width: 68%;
margin-top: ${em(32)};
td {
vertical-align: text-top;
line-height: 2;
&:first-of-type {
color: ${textLightColor};
text-align: right;
padding-right: ${em(10)};
font-size: ${em(16)};
width: ${em(250)};
}
}
`;
type UploaderProps = {
onClickUpload?: () => unknown;
onDropFiles?: (files: FileList) => unknown;
};
const Uploader: FunctionComponent<UploaderProps> = ({onClickUpload, onDropFiles}) => {
const {t} = useTranslation('graphs');
const [actived, setActived] = useState(false);
const onClick = useCallback(() => onClickUpload?.(), [onClickUpload]);
const onDrop = useCallback(
(e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
setActived(false);
if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length) {
onDropFiles?.(e.dataTransfer.files);
}
},
[onDropFiles]
);
const onDragLeave = useCallback((e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
if (e.currentTarget.contains(e.relatedTarget as Node | null)) {
return;
}
setActived(false);
}, []);
return (
<>
<DropZone
actived={actived}
onDrop={onDrop}
onDragOver={e => e.preventDefault()}
onDragEnter={() => setActived(true)}
onDragLeave={onDragLeave}
>
<Icon type="upload" className="upload-icon" />
<span>{t('upload-tip')}</span>
<Button type="primary" rounded className="upload-button" onClick={onClick}>
{t('upload-model')}
</Button>
</DropZone>
<SupportTable>
<tbody>
<tr>
<td>{t('supported-model')}</td>
<td>{t('supported-model-list')}</td>
</tr>
<tr>
<td>{t('experimental-supported-model')}</td>
<td>{t('experimental-supported-model-list')}</td>
</tr>
</tbody>
</SupportTable>
</>
);
};
export default Uploader;
import {Dimension, DivideParams, Point, Reduction, divide} from '~/resource/high-dimensional';
import React, {FunctionComponent, useMemo} from 'react';
import {primaryColor, rem} from '~/utils/style';
import {contentHeight, primaryColor, rem} from '~/utils/style';
import ScatterChart from '~/components/ScatterChart';
import queryString from 'query-string';
......@@ -9,11 +9,7 @@ import useHeavyWork from '~/hooks/useHeavyWork';
import {useRunningRequest} from '~/hooks/useRequest';
import {useTranslation} from '~/utils/i18n';
const height = rem(600);
const divideWasm = () =>
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
import('@visualdl/wasm').then(({divide}) => (params: DivideParams) =>
(divide(params.points, params.labels, !!params.visibility, params.keyword ?? '') as unknown) as [
Point[],
......@@ -23,7 +19,7 @@ const divideWasm = () =>
const divideWorker = () => new Worker('~/worker/high-dimensional/divide.worker.ts', {type: 'module'});
const StyledScatterChart = styled(ScatterChart)`
height: ${height};
height: ${contentHeight};
`;
const Empty = styled.div`
......@@ -31,7 +27,7 @@ const Empty = styled.div`
justify-content: center;
align-items: center;
font-size: ${rem(20)};
height: ${height};
height: ${contentHeight};
`;
const label = {
......
......@@ -4,10 +4,11 @@ import {WithStyled} from '~/utils/style';
type IconProps = {
type: string;
onClick?: () => unknown;
};
const Icon: FunctionComponent<IconProps & WithStyled> = ({type, className}) => {
return <i className={`vdl-icon icon-${type} ${className ?? ''}`} />;
const Icon: FunctionComponent<IconProps & WithStyled> = ({type, onClick, className}) => {
return <i className={`vdl-icon icon-${type} ${className ?? ''}`} onClick={() => onClick?.()} />;
};
export default Icon;
import React, {FunctionComponent} from 'react';
import {WithStyled, borderFocusedColor, em, half, sameBorder, textLighterColor, transitionProps} from '~/utils/style';
import React from 'react';
import styled from 'styled-components';
export const padding = em(10);
......@@ -25,25 +25,29 @@ const StyledInput = styled.input<{rounded?: boolean}>`
}
`;
export type InputProps = {
type CustomeInputProps = {
rounded?: boolean;
placeholder?: string;
value?: string;
onChange?: (value: string) => unknown;
};
const Input: FunctionComponent<
InputProps & WithStyled & Omit<React.ComponentPropsWithoutRef<'input'>, keyof InputProps>
> = ({rounded, placeholder, value, onChange, className, ...props}) => (
<StyledInput
rounded={rounded}
placeholder={placeholder}
value={value}
type="text"
className={className}
onChange={e => onChange?.(e.target.value)}
{...props}
/>
export type InputProps = Omit<React.ComponentPropsWithoutRef<'input'>, keyof CustomeInputProps | 'type' | 'className'> &
CustomeInputProps;
const Input = React.forwardRef<HTMLInputElement, InputProps & WithStyled>(
({rounded, value, onChange, className, ...props}, ref) => (
<StyledInput
ref={ref}
rounded={rounded}
value={value}
type="text"
className={className}
onChange={e => onChange?.(e.target.value)}
{...props}
/>
)
);
Input.displayName = 'Input';
export default Input;
import * as chart from '~/utils/chart';
import React, {useCallback, useEffect, useImperativeHandle, useLayoutEffect, useRef} from 'react';
import React, {useCallback, useEffect, useImperativeHandle} from 'react';
import {WithStyled, position, primaryColor, size} from '~/utils/style';
import {EChartOption} from 'echarts';
......@@ -56,22 +56,21 @@ const LineChart = React.forwardRef<LineChartRef, LineChartProps & WithStyled>(
({title, legend, data, xAxis, yAxis, xType, yType, xRange, yRange, tooltip, loading, className}, ref) => {
const {i18n} = useTranslation();
const {ref: echartRef, echart} = useECharts<HTMLDivElement>({
const {ref: echartRef, echart, wrapper} = useECharts<HTMLDivElement>({
loading: !!loading,
zoom: true
zoom: true,
autoFit: true
});
useImperativeHandle(ref, () => ({
restore: () => {
echart?.current?.dispatchAction({
echart?.dispatchAction({
type: 'restore'
});
},
saveAsImage: () => {
if (echart?.current) {
const blob = dataURL2Blob(
echart.current.getDataURL({type: 'png', pixelRatio: 2, backgroundColor: '#FFF'})
);
if (echart) {
const blob = dataURL2Blob(echart.getDataURL({type: 'png', pixelRatio: 2, backgroundColor: '#FFF'}));
saveAs(blob, `${title?.replace(/[/\\?%*:|"<>]/g, '_') || 'scalar'}.png`);
}
}
......@@ -84,7 +83,7 @@ const LineChart = React.forwardRef<LineChartRef, LineChartProps & WithStyled>(
useEffect(() => {
if (process.browser) {
echart?.current?.setOption(
echart?.setOption(
{
color: chart.color,
title: {
......@@ -133,22 +132,8 @@ const LineChart = React.forwardRef<LineChartRef, LineChartProps & WithStyled>(
}
}, [data, title, legend, xAxis, yAxis, xType, yType, xAxisFormatter, xRange, yRange, tooltip, echart]);
const wrapperRef = useRef<HTMLDivElement>(null);
useLayoutEffect(() => {
if (process.browser) {
const wrapper = wrapperRef.current;
if (wrapper) {
const observer = new ResizeObserver(() => {
echart?.current?.resize();
});
observer.observe(wrapper);
return () => observer.unobserve(wrapper);
}
}
});
return (
<Wrapper ref={wrapperRef} className={className}>
<Wrapper ref={wrapper} className={className}>
{!echart && (
<div className="loading">
<GridLoader color={primaryColor} size="10px" />
......
import Aside, {AsideSection} from '~/components/Aside';
import React, {FunctionComponent, useCallback, useMemo, useState} from 'react';
import {borderColor, ellipsis, em, rem, size} from '~/utils/style';
import {ellipsis, em, rem, size} from '~/utils/style';
import Checkbox from '~/components/Checkbox';
import Field from '~/components/Field';
......@@ -10,73 +11,53 @@ import styled from 'styled-components';
import uniqBy from 'lodash/uniqBy';
import {useTranslation} from '~/utils/i18n';
const Aside = styled.div`
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
const StyledAside = styled(Aside)`
${AsideSection}.run-section {
flex: auto;
overflow-x: hidden;
overflow-y: auto;
display: flex;
flex-direction: column;
> section {
margin: ${rem(20)} ${rem(20)} 0;
flex: 0 0 auto;
&:not(:last-child) {
border-bottom: 1px solid ${borderColor};
padding-bottom: ${rem(20)};
}
&.run-section {
flex: 1 1 auto;
.run-select {
flex: auto;
overflow-x: hidden;
overflow-y: auto;
display: flex;
flex-direction: column;
.running-toggle {
flex: 0 0 auto;
box-shadow: 0 -${rem(5)} ${rem(16)} 0 rgba(0, 0, 0, 0.03);
> * {
flex: none;
}
.run-select {
flex: 1 1 auto;
.search-input {
margin-bottom: ${rem(15)};
}
.run-list {
flex: auto;
overflow-x: hidden;
overflow-y: auto;
display: flex;
flex-direction: column;
> * {
flex: 0 0 auto;
}
margin-top: ${rem(5)};
.search-input {
margin-bottom: ${rem(15)};
}
> div {
margin-top: ${rem(11)};
.run-list {
flex: 1 1 auto;
overflow-x: hidden;
overflow-y: auto;
margin-top: ${rem(5)};
> div {
margin-top: ${rem(11)};
> * {
width: 100%;
}
> * {
width: 100%;
}
.run-item {
display: flex;
align-items: center;
${ellipsis()}
.run-item {
display: flex;
align-items: center;
${ellipsis()}
> i {
display: inline-block;
${size(em(12), em(12))};
border-radius: ${em(6)};
margin-right: ${em(8)};
}
> i {
display: inline-block;
${size(em(12), em(12))};
border-radius: ${em(6)};
margin-right: ${em(8)};
}
}
}
......@@ -131,10 +112,15 @@ const RunAside: FunctionComponent<RunAsideProps> = ({
[onChangeRuns, selectedRuns]
);
const bottom = useMemo(
() => <RunningToggle className="running-toggle" running={running} onToggle={onToggleRunning} />,
[running, onToggleRunning]
);
return (
<Aside>
<StyledAside bottom={bottom}>
{children}
<section className="run-section">
<AsideSection className="run-section">
<Field className="run-select" label={t('common:select-runs')}>
<SearchInput
className="search-input"
......@@ -163,9 +149,8 @@ const RunAside: FunctionComponent<RunAsideProps> = ({
))}
</div>
</Field>
<RunningToggle className="running-toggle" running={running} onToggle={onToggleRunning} />
</section>
</Aside>
</AsideSection>
</StyledAside>
);
};
......
......@@ -8,7 +8,6 @@ import styled from 'styled-components';
import {useTranslation} from '~/utils/i18n';
const Wrapper = styled.div`
padding: ${rem(20)} 0;
display: flex;
align-items: center;
......
import Image, {ImageRef} from '~/components/Image';
import React, {FunctionComponent, useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {ellipsis, em, primaryColor, size, textLightColor, transitionProps} from '~/utils/style';
import {ellipsis, em, primaryColor, rem, size, textLightColor, transitionProps} from '~/utils/style';
import ChartToolbox from '~/components/ChartToolbox';
import GridLoader from 'react-spinners/GridLoader';
......@@ -69,6 +69,10 @@ const Container = styled.div<{brightness?: number; contrast?: number; fit?: bool
}
`;
const Toolbox = styled(ChartToolbox)`
margin-bottom: ${rem(18)};
`;
type ImageData = {
step: number;
wallTime: number;
......@@ -195,7 +199,7 @@ const SampleChart: FunctionComponent<SampleChartProps> = ({run, tag, brightness,
<Container ref={container} brightness={brightness} contrast={contrast} fit={fit}>
{Content}
</Container>
<ChartToolbox
<Toolbox
items={[
{
icon: 'download',
......
......@@ -62,6 +62,7 @@ const StyledLineChart = styled(LineChart)`
const Toolbox = styled(ChartToolbox)`
margin-left: ${rem(20)};
margin-right: ${rem(20)};
margin-bottom: ${rem(18)};
`;
const Error = styled.div`
......
......@@ -65,9 +65,10 @@ type ScatterChartProps = {
};
const ScatterChart: FunctionComponent<ScatterChartProps & WithStyled> = ({data, loading, gl, className}) => {
const {ref, echart} = useECharts<HTMLDivElement>({
const {ref, echart, wrapper} = useECharts<HTMLDivElement>({
loading,
gl
gl,
autoFit: true
});
const chartOptions = useMemo(
......@@ -84,13 +85,12 @@ const ScatterChart: FunctionComponent<ScatterChartProps & WithStyled> = ({data,
useEffect(() => {
if (process.browser) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
echart?.current?.setOption(chartOptions as any, {notMerge: true});
echart?.setOption(chartOptions);
}
}, [chartOptions, echart]);
return (
<Wrapper className={className}>
<Wrapper ref={wrapper} className={className}>
{!echart && (
<div className="loading">
<GridLoader color={primaryColor} size="10px" />
......
import Input, {InputProps, padding} from '~/components/Input';
import React, {FunctionComponent} from 'react';
import {WithStyled, em, math, position, textLighterColor} from '~/utils/style';
import React, {FunctionComponent, useCallback, useRef} from 'react';
import {WithStyled, math, position, textColor, textLightColor, textLighterColor} from '~/utils/style';
import Icon from '~/components/Icon';
import styled from 'styled-components';
const iconSize = em(16);
const searchIconSize = '1.142857143';
const closeIconSize = '0.857142857';
const StyledInput = styled(Input)`
padding-left: ${math(`${iconSize} + ${padding} * 2`)};
padding-left: ${math(`1em * ${searchIconSize} + ${padding} * 2`)};
padding-right: ${math(`1em * ${closeIconSize} + ${padding} * 2`)};
width: 100%;
`;
......@@ -17,18 +19,45 @@ const Control = styled.div`
`;
const SearchIcon = styled(Icon)`
font-size: ${iconSize};
display: block;
${position('absolute', padding, null, null, padding)}
transform: translateY(-50%) scale(${searchIconSize});
transform-origin: center;
${position('absolute', '50%', null, null, padding)}
pointer-events: none;
color: ${textLighterColor};
`;
const SearchInput: FunctionComponent<InputProps & WithStyled> = ({className, ...props}) => (
<Control className={className}>
<SearchIcon type="search" />
<StyledInput {...props} />
</Control>
);
const CloseIcon = styled(Icon)`
display: block;
transform: translateY(-50%) scale(${closeIconSize});
transform-origin: center;
${position('absolute', '50%', padding, null, null)}
cursor: pointer;
color: ${textLighterColor};
&:hover {
color: ${textLightColor};
}
&:active {
color: ${textColor};
}
`;
const SearchInput: FunctionComponent<InputProps & WithStyled> = ({className, value, onChange, ...props}) => {
const input = useRef<HTMLInputElement | null>(null);
const clear = useCallback(() => {
onChange?.('');
input.current?.focus();
}, [onChange]);
return (
<Control className={className}>
<SearchIcon type="search" />
<StyledInput ref={input} value={value} onChange={onChange} {...props} />
{!!value && <CloseIcon type="close" onClick={clear} />}
</Control>
);
};
export default SearchInput;
......@@ -195,7 +195,7 @@ const Select = <T extends unknown>({
[value, onChange]
);
const ref = useClickOutside(setIsOpenedFalse);
const ref = useClickOutside<HTMLDivElement>(setIsOpenedFalse);
const list = useMemo<SelectListItem<T>[]>(
() =>
......
import {useCallback, useEffect, useRef} from 'react';
const useClickOutside = (callback: () => void) => {
const ref = useRef(null);
const useClickOutside = <T extends HTMLElement>(callback: () => void) => {
const ref = useRef<T | null>(null);
const escapeListener = useCallback(
(e: KeyboardEvent) => {
......
import {MutableRefObject, useCallback, useEffect, useRef, useState} from 'react';
import {MutableRefObject, useCallback, useEffect, useLayoutEffect, useRef, useState} from 'react';
import {maskColor, primaryColor, textColor} from '~/utils/style';
import {ECharts} from 'echarts';
const useECharts = <T extends HTMLElement>(options: {
const useECharts = <T extends HTMLElement, W extends HTMLElement = HTMLDivElement>(options: {
loading?: boolean;
gl?: boolean;
zoom?: boolean;
autoFit?: boolean;
}): {
ref: MutableRefObject<T | null>;
echart: MutableRefObject<ECharts | null> | null;
wrapper: MutableRefObject<W | null>;
echart: ECharts | null;
} => {
const ref = useRef<T | null>(null);
const echartInstance = useRef<ECharts | null>(null);
const [echart, setEchart] = useState<typeof echartInstance | null>(null);
const [echart, setEchart] = useState<ECharts | null>(null);
const createChart = useCallback(() => {
(async () => {
......@@ -34,7 +36,7 @@ const useECharts = <T extends HTMLElement>(options: {
});
}, 0);
}
setEchart(echartInstance);
setEchart(echartInstance.current);
})();
}, [options.gl, options.zoom]);
......@@ -46,12 +48,12 @@ const useECharts = <T extends HTMLElement>(options: {
useEffect(() => {
if (process.browser) {
createChart();
return () => destroyChart();
return destroyChart;
}
}, [createChart, destroyChart]);
useEffect(() => {
if (process.browser && echart) {
if (process.browser) {
if (options.loading) {
echartInstance.current?.showLoading('default', {
text: '',
......@@ -64,9 +66,23 @@ const useECharts = <T extends HTMLElement>(options: {
echartInstance.current?.hideLoading();
}
}
}, [options.loading, echart]);
}, [options.loading]);
const wrapper = useRef<W | null>(null);
useLayoutEffect(() => {
if (options.autoFit && process.browser) {
const w = wrapper.current;
if (w) {
const observer = new ResizeObserver(() => {
echartInstance.current?.resize();
});
observer.observe(w);
return () => observer.unobserve(w);
}
}
}, [options.autoFit]);
return {ref, echart};
return {ref, echart, wrapper};
};
export default useECharts;
......@@ -5,10 +5,11 @@ import {fetcher} from '~/utils/fetch';
import intersection from 'lodash/intersection';
import useRequest from '~/hooks/useRequest';
const allNavItems = ['scalars', 'samples', 'high-dimensional'];
const allNavItems = ['scalars', 'samples', 'graphs', 'high-dimensional'];
export const navMap = {
scalar: 'scalars',
image: 'samples',
graph: 'graphs',
embeddings: 'high-dimensional'
} as const;
......
......@@ -52,7 +52,7 @@ module.exports = {
config.resolve.alias['~'] = path.resolve(__dirname);
config.node = Object.assign({}, config.node, {
// eslint-disable-next-line @typescript-eslint/camelcase
// eslint-disable-next-line @typescript-eslint/naming-convention
child_process: 'empty',
fs: 'empty'
});
......
......@@ -35,19 +35,18 @@
"@visualdl/i18n": "2.0.0-beta.43",
"@visualdl/wasm": "2.0.0-beta.43",
"bignumber.js": "9.0.0",
"dagre-d3": "0.6.4",
"echarts": "4.7.0",
"echarts": "4.8.0",
"echarts-gl": "1.1.1",
"eventemitter3": "4.0.0",
"eventemitter3": "4.0.4",
"file-saver": "2.0.2",
"isomorphic-unfetch": "3.0.0",
"lodash": "4.17.15",
"mime-types": "2.1.27",
"moment": "2.25.3",
"nanoid": "3.1.5",
"next": "9.3.6",
"moment": "2.26.0",
"nanoid": "3.1.9",
"next": "9.4.4",
"nprogress": "0.2.0",
"polished": "3.6.2",
"polished": "3.6.4",
"prop-types": "15.7.2",
"query-string": "6.12.1",
"react": "16.13.1",
......@@ -58,21 +57,19 @@
"react-spinners": "0.8.3",
"react-tooltip": "4.2.6",
"save-svg-as-png": "1.4.17",
"styled-components": "5.1.0",
"swr": "0.2.0"
"styled-components": "5.1.1",
"swr": "0.2.2"
},
"devDependencies": {
"@babel/core": "7.9.6",
"@types/d3": "5.7.2",
"@types/dagre-d3": "0.4.39",
"@types/echarts": "4.6.0",
"@babel/core": "7.10.2",
"@types/echarts": "4.6.1",
"@types/file-saver": "2.0.1",
"@types/lodash": "4.14.150",
"@types/lodash": "4.14.155",
"@types/mime-types": "2.1.0",
"@types/node": "13.13.5",
"@types/node": "14.0.10",
"@types/nprogress": "0.2.0",
"@types/react": "16.9.34",
"@types/react-dom": "16.9.7",
"@types/react": "16.9.35",
"@types/react-dom": "16.9.8",
"@types/styled-components": "5.1.0",
"@visualdl/mock": "2.0.0-beta.43",
"babel-plugin-emotion": "10.0.33",
......@@ -82,7 +79,7 @@
"cross-env": "7.0.2",
"css-loader": "3.5.3",
"ora": "4.0.4",
"typescript": "3.8.3",
"typescript": "3.9.3",
"worker-plugin": "4.0.3"
},
"engines": {
......
import Document, {DocumentContext, DocumentProps, Head, Html, Main, NextScript} from 'next/document';
import Document, {
DocumentContext,
DocumentInitialProps,
DocumentProps,
Head,
Html,
Main,
NextScript
} from 'next/document';
import {ServerStyleSheet} from '~/utils/style';
......@@ -8,7 +16,7 @@ interface VDLDocumentProps extends DocumentProps {
}
export default class VDLDocument extends Document<VDLDocumentProps> {
static async getInitialProps(ctx: DocumentContext) {
static async getInitialProps(ctx: DocumentContext): Promise<DocumentInitialProps> {
// https://github.com/zeit/next.js/blob/canary/examples/with-typescript-styled-components/pages/_document.tsx
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
......@@ -42,7 +50,7 @@ export default class VDLDocument extends Document<VDLDocumentProps> {
}
}
render() {
render(): JSX.Element {
const {language, languageDir} = this.props;
return (
<Html lang={language} dir={languageDir}>
......
import {Graph, NodeType, TypedNode, collectDagFacts} from '~/resource/graphs';
import Aside, {AsideSection} from '~/components/Aside';
import {Documentation, Properties, SearchItem, SearchResult} from '~/resource/graphs/types';
import Graph, {GraphRef} from '~/components/GraphsPage/Graph';
import {NextI18NextPage, useTranslation} from '~/utils/i18n';
import NodeInfo, {NodeInfoProps} from '~/components/GraphsPage/NodeInfo';
import React, {useEffect, useMemo, useState} from 'react';
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import Button from '~/components/Button';
import Checkbox from '~/components/Checkbox';
import Content from '~/components/Content';
import Field from '~/components/Field';
import Preloader from '~/components/Preloader';
import RawButton from '~/components/Button';
import RawRangeSlider from '~/components/RangeSlider';
import ModelPropertiesDialog from '~/components/GraphsPage/ModelPropertiesDialog';
import NodeDocumentationSidebar from '~/components/GraphsPage/NodeDocumentationSidebar';
import NodePropertiesSidebar from '~/components/GraphsPage/NodePropertiesSidebar';
import Search from '~/components/GraphsPage/Search';
import Title from '~/components/Title';
import isEmpty from 'lodash/isEmpty';
import Uploader from '~/components/GraphsPage/Uploader';
import {rem} from '~/utils/style';
import {saveSvgAsPng} from 'save-svg-as-png';
import styled from 'styled-components';
import useRequest from '~/hooks/useRequest';
// eslint-disable-next-line @typescript-eslint/no-empty-function
const dumbFn = () => {};
const AsideSection = styled.section`
padding: ${rem(20)};
`;
const SubSection = styled.div`
margin-bottom: ${rem(30)};
`;
const Button = styled(RawButton)`
const FullWidthButton = styled(Button)`
width: 100%;
text-transform: uppercase;
& + & {
margin-top: ${rem(20)};
}
`;
const Empty = styled.div`
const ExportButtonWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
font-size: ${rem(20)};
height: ${rem(150)};
`;
justify-content: space-between;
const RangeSlider = styled(RawRangeSlider)`
width: 100%;
`;
const GraphSvg = styled('svg')`
width: 100%;
> * {
flex: 1 1 auto;
cursor: grab;
&.grabbing {
cursor: grabbing;
}
.node {
cursor: pointer;
.label-container {
stroke-width: 3px;
stroke: #e6e6e6;
&.rect {
rx: 10;
ry: 10;
}
}
&.operator {
.label-container {
fill: #cdd9da;
}
}
&.output {
.label-container {
stroke-dasharray: 5, 5;
stroke: #e6e6e6;
fill: #cad2d0;
}
}
&.input {
.label-container {
fill: #d5d3d8;
}
}
&.active {
.label-container {
stroke: #25c9ff;
}
&:not(:last-child) {
margin-right: ${rem(20)};
}
}
.edgePath path.path {
stroke: #333;
stroke-width: 1.5px;
}
`;
const loadDagLibs = [import('d3'), import('dagre-d3')] as const;
const MIN_SCALE = 0.1;
const MAX_SCALE = 4;
// TODO: better way to auto fit height
const SearchSection = styled(AsideSection)`
max-height: calc(100% - ${rem(40)});
display: flex;
flex-direction: column;
const useDag = (graph?: Graph) => {
const [displaySwitch, setDisplaySwitch] = useState({
detail: false,
input: false,
output: false
});
const facts = useMemo(() => collectDagFacts(graph), [graph]);
&:not(:last-child) {
padding-bottom: 0;
}
`;
const dagInfo = useMemo(() => {
const {inputLayer, outputLayer, briefLayer, detailLayer, findNode} = facts;
const Graphs: NextI18NextPage = () => {
const {t} = useTranslation(['graphs', 'common']);
const availableLayers = displaySwitch.detail ? [detailLayer] : [briefLayer];
if (displaySwitch.input) {
availableLayers.push(inputLayer);
const graph = useRef<GraphRef>(null);
const file = useRef<HTMLInputElement>(null);
const [files, setFiles] = useState<FileList | null>(null);
const onClickFile = useCallback(() => {
if (file.current) {
file.current.value = '';
file.current.click();
}
if (displaySwitch.output) {
availableLayers.push(outputLayer);
}, []);
const onChangeFile = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const target = e.target;
if (target && target.files && target.files.length) {
setFiles(target.files);
}
return {
...availableLayers.reduce(
(memo, {nodes, edges}) => ({
nodes: memo.nodes.concat(nodes),
edges: memo.edges.concat(edges)
}),
{
nodes: [],
edges: []
}
}, []);
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 [modelData, setModelData] = useState<Properties | null>(null);
const [nodeData, setNodeData] = useState<Properties | null>(null);
const [nodeDocumentation, setNodeDocumentation] = useState<Documentation | null>(null);
useEffect(() => setSearch(''), [showAttributes, showInitializers, showNames]);
const bottom = useMemo(
() =>
searching ? null : (
<FullWidthButton type="primary" rounded onClick={onClickFile}>
{t('graphs:change-model')}
</FullWidthButton>
),
findNode
};
}, [facts, displaySwitch]);
return {
dagInfo,
displaySwitch,
setDisplaySwitch
};
};
const useDagreD3 = (graph?: Graph) => {
const [currentNode, setCurrentNode] = useState<NodeInfoProps['node']>(undefined);
const {dagInfo, displaySwitch, setDisplaySwitch} = useDag(graph);
const [downloadImage, setDownloadImageFn] = useState<() => void>(() => dumbFn);
const [fitScreen, setFitScreenFn] = useState<() => void>(() => dumbFn);
const [scale, setScaleValue] = useState(1);
const [setScale, setScaleFn] = useState<(n: number) => void>(() => dumbFn);
useEffect(() => {
Promise.all(loadDagLibs).then(([d3, {default: dagre}]) => {
if (!dagInfo.nodes.length || !dagInfo.edges.length) {
return;
}
const g = new dagre.graphlib.Graph<{type: NodeType; elem: HTMLElement}>();
g.setGraph({}).setDefaultEdgeLabel(() => ({}));
dagInfo.nodes.forEach(n => g.setNode(n.key, n));
dagInfo.edges.forEach(e => g.setEdge(e[0], e[1]));
const render = new dagre.render();
const svg = d3.select<HTMLElement, any>('svg'); // eslint-disable-line @typescript-eslint/no-explicit-any
const inner = svg.select('svg g');
render(inner, g);
const {width, height} = g.graph();
const scaleFactor = 1;
svg.attr('height', Math.max(640, window.innerHeight + 40));
const zoom = d3
.zoom<HTMLElement, any>() // eslint-disable-line @typescript-eslint/no-explicit-any
.scaleExtent([MIN_SCALE, MAX_SCALE])
.on('zoom', function () {
setScaleValue(d3.event.transform.k / scaleFactor);
inner.attr('transform', d3.event.transform);
})
.on('start', () => svg.classed('grabbing', true))
.on('end', () => svg.classed('grabbing', false));
svg.call(zoom);
let prevDom: HTMLElement | undefined;
// install event listeners
svg.selectAll('g.node').on('click', v => {
const uid = v as string;
const {type, elem: dom} = g.node(uid);
if (prevDom) {
prevDom.classList.remove('active');
}
dom.classList.add('active');
prevDom = dom;
const node = dagInfo.findNode(type, uid);
if (!node) {
setCurrentNode({type: 'unknown', guessType: type, msg: uid});
return;
}
setCurrentNode({...node, type} as TypedNode);
});
const fitScreen = () => {
if (!svg) {
return;
}
const parent = svg.node()?.parentElement;
if (!parent) {
return;
}
const {width: parentWidth} = parent.getBoundingClientRect();
svg.call(
zoom.transform,
d3.zoomIdentity.translate((parentWidth - (width ?? 0) * scaleFactor) / 2, 20).scale(scaleFactor)
);
};
fitScreen();
setFitScreenFn(() => fitScreen);
setDownloadImageFn(() => {
let processing = false;
return async () => {
if (processing) {
return;
}
processing = true;
fitScreen();
const svgNode = svg.node();
if (!svgNode) {
return;
}
const originalHeight = +svg.attr('height');
svg.attr('height', (height ?? 0) + 40);
await saveSvgAsPng(svgNode, 'graph.png');
svg.attr('height', originalHeight);
processing = false;
};
});
setScaleFn(() => (n: number) => {
zoom.scaleTo(svg, scaleFactor * n);
setScaleValue(n);
});
});
}, [dagInfo]);
return {currentNode, displaySwitch, setDisplaySwitch, downloadImage, fitScreen, scale, setScale};
};
const Graphs: NextI18NextPage = () => {
const {t} = useTranslation(['graphs', 'common']);
const {data, error, loading} = useRequest<{data: Graph}>('/graphs/graph');
const graph = useMemo(() => (loading || isEmpty(data?.data) ? undefined : data?.data), [loading, data]);
const {currentNode, downloadImage, fitScreen, scale, setScale} = useDagreD3(graph);
const aside = (
<AsideSection>
<SubSection>
<Button rounded type="primary" icon="download" onClick={downloadImage}>
{t('graphs:download-image')}
</Button>
<Button rounded type="primary" icon="revert" onClick={fitScreen}>
{t('graphs:restore-image')}
</Button>
</SubSection>
<SubSection>
<Field label={`${t('graphs:scale')}:`}>
<RangeSlider min={MIN_SCALE} max={MAX_SCALE} step={0.1} value={scale} onChange={setScale} />
</Field>
</SubSection>
<SubSection>
<Field label={`${t('graphs:node-info')}:`} />
<NodeInfo node={currentNode} />
</SubSection>
</AsideSection>
[t, onClickFile, searching]
);
const ContentInner = useMemo(() => {
if (loading) {
const [rendered, setRendered] = useState(false);
const aside = useMemo(() => {
if (!rendered) {
return null;
}
if (error) {
return <Empty>{t('common:error')}</Empty>;
if (nodeDocumentation) {
return (
<Aside width={rem(360)}>
<NodeDocumentationSidebar data={nodeDocumentation} onClose={() => setNodeDocumentation(null)} />
</Aside>
);
}
if (!graph) {
return <Empty>{t('common:empty')}</Empty>;
if (nodeData) {
return (
<Aside width={rem(360)}>
<NodePropertiesSidebar
data={nodeData}
onClose={() => setNodeData(null)}
showNodeDodumentation={() => graph.current?.showNodeDocumentation(nodeData)}
/>
</Aside>
);
}
return (
<GraphSvg>
<g></g>
</GraphSvg>
<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('graphs:model-properties')}
</FullWidthButton>
</AsideSection>
<AsideSection>
<Field label={t('graphs:display-data')}>
<div>
<Checkbox value={showAttributes} onChange={setShowAttributes}>
{t('graphs:show-attributes')}
</Checkbox>
</div>
<div>
<Checkbox value={showInitializers} onChange={setShowInitializers}>
{t('graphs:show-initializers')}
</Checkbox>
</div>
<div>
<Checkbox value={showNames} onChange={setShowNames}>
{t('graphs:show-node-names')}
</Checkbox>
</div>
</Field>
</AsideSection>
<AsideSection>
<Field label={t('graphs:export-file')}>
<ExportButtonWrapper>
<Button onClick={() => graph.current?.export('png')}>
{t('graphs:export-png')}
</Button>
<Button onClick={() => graph.current?.export('svg')}>
{t('graphs:export-svg')}
</Button>
</ExportButtonWrapper>
</Field>
</AsideSection>
</>
)}
</Aside>
);
}, [loading, error, graph, t]);
}, [
t,
bottom,
search,
searching,
searchResult,
onSearch,
onSelect,
showAttributes,
showInitializers,
showNames,
rendered,
nodeData,
nodeDocumentation
]);
const uploader = useMemo(() => <Uploader onClickUpload={onClickFile} onDropFiles={setFiles} />, [onClickFile]);
return (
<>
<Preloader url="/graphs/graph" />
<Title>{t('common:graphs')}</Title>
<Content aside={aside} loading={loading}>
{ContentInner}
<ModelPropertiesDialog data={modelData} onClose={() => setModelData(null)} />
<Content aside={aside}>
<Graph
ref={graph}
files={files}
uploader={uploader}
showAttributes={showAttributes}
showInitializers={showInitializers}
showNames={showNames}
onRendered={() => setRendered(true)}
onSearch={data => setSearchResult(data)}
onShowModelProperties={data => setModelData(data)}
onShowNodeProperties={data => {
setNodeData(data);
setNodeDocumentation(null);
}}
onShowNodeDocumentation={data => setNodeDocumentation(data)}
/>
<input
ref={file}
type="file"
multiple={false}
onChange={onChangeFile}
style={{
display: 'none'
}}
accept=".onnx, .pb, .meta, .tflite, .lite, .tfl, .bin, .keras, .h5, .hd5, .hdf5, .json, .model, .mar, .params, .param, .armnn, .mnn, .ncnn, .nn, .dnn, .cmf, .mlmodel, .caffemodel, .pbtxt, .prototxt, .pkl, .pt, .pth, .t7, .joblib, .cfg, .xml"
/>
</Content>
</>
);
......
import Aside, {AsideSection} from '~/components/Aside';
import {Dimension, Reduction} from '~/resource/high-dimensional';
import {NextI18NextPage, useTranslation} from '~/utils/i18n';
import React, {useEffect, useMemo, useState} from 'react';
import Select, {SelectProps} from '~/components/Select';
import {em, rem} from '~/utils/style';
import AsideDivider from '~/components/AsideDivider';
import Checkbox from '~/components/Checkbox';
import Content from '~/components/Content';
import Field from '~/components/Field';
......@@ -24,10 +24,6 @@ import useSearchValue from '~/hooks/useSearchValue';
const dimensions = ['2d', '3d'];
const reductions = ['pca', 'tsne'];
const AsideSection = styled.section`
padding: ${rem(20)};
`;
const StyledSelect = styled<React.FunctionComponent<SelectProps<string>>>(Select)`
min-width: ${em(160)};
`;
......@@ -96,49 +92,58 @@ const HighDimensional: NextI18NextPage = () => {
const [reduction, setReduction] = useState(reductions[0] as Reduction);
const [labelVisibility, setLabelVisibility] = useState(true);
const aside = (
<AsideSection>
<AsideTitle>{t('common:select-runs')}</AsideTitle>
<StyledSelect list={labelList} value={label} onChange={setLabel} />
<AsideDivider />
<Field>
<SearchInput placeholder={t('common:search')} value={search} onChange={setSearch} />
</Field>
<Field>
<Checkbox value={labelVisibility} onChange={setLabelVisibility}>
{t('high-dimensional:display-all-label')}
</Checkbox>
</Field>
<AsideDivider />
<AsideTitle>
<StyledIcon type="dimension" />
{t('high-dimensional:dimension')}
</AsideTitle>
<Field>
<RadioGroup value={dimension} onChange={value => setDimension(value)}>
{dimensions.map(item => (
<RadioButton key={item} value={item}>
{t(item)}
</RadioButton>
))}
</RadioGroup>
</Field>
<AsideDivider />
<AsideTitle>
<StyledIcon type="reduction" />
{t('high-dimensional:reduction-method')}
</AsideTitle>
<Field>
<RadioGroup value={reduction} onChange={value => setReduction(value)}>
{reductions.map(item => (
<RadioButton key={item} value={item}>
{t(item)}
</RadioButton>
))}
</RadioGroup>
</Field>
<RunningToggle running={running} onToggle={setRunning} />
</AsideSection>
const bottom = useMemo(() => <RunningToggle running={running} onToggle={setRunning} />, [running, setRunning]);
const aside = useMemo(
() => (
<Aside bottom={bottom}>
<AsideSection>
<AsideTitle>{t('common:select-runs')}</AsideTitle>
<StyledSelect list={labelList} value={label} onChange={setLabel} />
</AsideSection>
<AsideSection>
<Field>
<SearchInput placeholder={t('common:search')} value={search} onChange={setSearch} />
</Field>
<Field>
<Checkbox value={labelVisibility} onChange={setLabelVisibility}>
{t('high-dimensional:display-all-label')}
</Checkbox>
</Field>
</AsideSection>
<AsideSection>
<AsideTitle>
<StyledIcon type="dimension" />
{t('high-dimensional:dimension')}
</AsideTitle>
<Field>
<RadioGroup value={dimension} onChange={value => setDimension(value)}>
{dimensions.map(item => (
<RadioButton key={item} value={item}>
{t(item)}
</RadioButton>
))}
</RadioGroup>
</Field>
</AsideSection>
<AsideSection>
<AsideTitle>
<StyledIcon type="reduction" />
{t('high-dimensional:reduction-method')}
</AsideTitle>
<Field>
<RadioGroup value={reduction} onChange={value => setReduction(value)}>
{reductions.map(item => (
<RadioButton key={item} value={item}>
{t(item)}
</RadioButton>
))}
</RadioGroup>
</Field>
</AsideSection>
</Aside>
),
[t, bottom, dimension, label, labelList, labelVisibility, reduction, search]
);
return (
......
......@@ -4,6 +4,7 @@ import ChartPage, {WithChart} from '~/components/ChartPage';
import {NextI18NextPage, useTranslation} from '~/utils/i18n';
import React, {useCallback, useMemo, useState} from 'react';
import {AsideSection} from '~/components/Aside';
import Checkbox from '~/components/Checkbox';
import Content from '~/components/Content';
import Field from '~/components/Field';
......@@ -47,30 +48,33 @@ const Samples: NextI18NextPage = () => {
const [brightness, setBrightness] = useState(1);
const [contrast, setContrast] = useState(1);
const aside = (
<RunAside
runs={runs}
selectedRuns={selectedRuns}
onChangeRuns={onChangeRuns}
running={running}
onToggleRunning={setRunning}
>
<section>
<Checkbox value={showActualSize} onChange={setShowActualSize}>
{t('samples:show-actual-size')}
</Checkbox>
</section>
<section>
<Field label={t('samples:brightness')}>
<Slider min={0} max={2} step={0.01} value={brightness} onChange={setBrightness} />
</Field>
</section>
<section>
<Field label={t('samples:contrast')}>
<Slider min={0} max={2} step={0.01} value={contrast} onChange={setContrast} />
</Field>
</section>
</RunAside>
const aside = useMemo(
() => (
<RunAside
runs={runs}
selectedRuns={selectedRuns}
onChangeRuns={onChangeRuns}
running={running}
onToggleRunning={setRunning}
>
<AsideSection>
<Checkbox value={showActualSize} onChange={setShowActualSize}>
{t('samples:show-actual-size')}
</Checkbox>
</AsideSection>
<AsideSection>
<Field label={t('samples:brightness')}>
<Slider min={0} max={2} step={0.01} value={brightness} onChange={setBrightness} />
</Field>
</AsideSection>
<AsideSection>
<Field label={t('samples:contrast')}>
<Slider min={0} max={2} step={0.01} value={contrast} onChange={setContrast} />
</Field>
</AsideSection>
</RunAside>
),
[t, brightness, contrast, onChangeRuns, running, runs, selectedRuns, showActualSize]
);
const withChart = useCallback<WithChart<Item>>(
......
import ChartPage, {WithChart} from '~/components/ChartPage';
import {NextI18NextPage, useTranslation} from '~/utils/i18n';
import React, {useCallback, useState} from 'react';
import React, {useCallback, useMemo, useState} from 'react';
import {sortingMethodMap, xAxisMap} from '~/resource/scalars';
import {AsideSection} from '~/components/Aside';
import Checkbox from '~/components/Checkbox';
import Content from '~/components/Content';
import Field from '~/components/Field';
......@@ -51,44 +52,50 @@ const Scalars: NextI18NextPage = () => {
const [ignoreOutliers, setIgnoreOutliers] = useState(false);
const aside = (
<RunAside
runs={runs}
selectedRuns={selectedRuns}
onChangeRuns={onChangeRuns}
running={running}
onToggleRunning={setRunning}
>
<section>
<Checkbox value={ignoreOutliers} onChange={setIgnoreOutliers}>
{t('scalars:ignore-outliers')}
</Checkbox>
<TooltipSortingDiv>
<span>{t('scalars:tooltip-sorting')}</span>
<Select
list={toolTipSortingValues.map(value => ({label: t(`tooltip-sorting-value.${value}`), value}))}
value={tooltipSorting}
onChange={setTooltipSorting}
/>
</TooltipSortingDiv>
</section>
<section>
<Field label={t('scalars:smoothing')}>
<Slider min={0} max={0.99} step={0.01} value={smoothing} onChangeComplete={setSmoothing} />
</Field>
</section>
<section>
<Field label={t('scalars:x-axis')}>
<RadioGroup value={xAxis} onChange={setXAxis}>
{xAxisValues.map(value => (
<RadioButton key={value} value={value}>
{t(`x-axis-value.${value}`)}
</RadioButton>
))}
</RadioGroup>
</Field>
</section>
</RunAside>
const aside = useMemo(
() => (
<RunAside
runs={runs}
selectedRuns={selectedRuns}
onChangeRuns={onChangeRuns}
running={running}
onToggleRunning={setRunning}
>
<AsideSection>
<Checkbox value={ignoreOutliers} onChange={setIgnoreOutliers}>
{t('scalars:ignore-outliers')}
</Checkbox>
<TooltipSortingDiv>
<span>{t('scalars:tooltip-sorting')}</span>
<Select
list={toolTipSortingValues.map(value => ({
label: t(`tooltip-sorting-value.${value}`),
value
}))}
value={tooltipSorting}
onChange={setTooltipSorting}
/>
</TooltipSortingDiv>
</AsideSection>
<AsideSection>
<Field label={t('scalars:smoothing')}>
<Slider min={0} max={0.99} step={0.01} value={smoothing} onChangeComplete={setSmoothing} />
</Field>
</AsideSection>
<AsideSection>
<Field label={t('scalars:x-axis')}>
<RadioGroup value={xAxis} onChange={setXAxis}>
{xAxisValues.map(value => (
<RadioButton key={value} value={value}>
{t(`x-axis-value.${value}`)}
</RadioButton>
))}
</RadioGroup>
</Field>
</AsideSection>
</RunAside>
),
[t, ignoreOutliers, onChangeRuns, running, runs, selectedRuns, smoothing, tooltipSorting, xAxis]
);
const withChart = useCallback<WithChart<Tag>>(
......
{
"cancel": "Cancel",
"close": "Close",
"confirm": "Confirm",
"empty": "Nothing to display",
"error": "Error occurred",
......@@ -12,19 +14,19 @@
"runs": "Runs",
"samples": "Samples",
"scalars": "Scalars",
"search": "Search",
"search-empty": "Nothing found. Please try again with another word. <1/>Or you can <3>see all charts</3>.",
"search-result": "Search Result",
"search-runs": "Search runs",
"search-tags": "Search tags in RegExp",
"select": "Please Select",
"search": "Search",
"select-all": "Select All",
"select-runs": "Select Runs",
"select": "Please Select",
"start-realtime-refresh": "Start realtime refresh",
"stop": "Stop",
"stop-realtime-refresh": "Stop realtime refresh",
"stop": "Stop",
"stopped": "Stopped",
"total-page": "{{count}} page, jump to",
"total-page_plural": "{{count}} pages, jump to",
"total-page": "{{count}} page, jump to",
"unselected-empty": "Nothing selected. <1/>Please select display data from right side."
}
{
"click-node": "Click a node to view its detail",
"download-image": "Download Image",
"input": "Input",
"node-data-shape": "Shape",
"node-data-type": "Data Type",
"node-info": "Node Info",
"node-name": "Node Name",
"node-type": "Node Type",
"op-type": "Operator Type",
"output": "Output",
"restore-image": "Restore Image",
"scale": "Scale"
"change-model": "Change Model",
"model-properties": "Model Properties",
"node-properties": "Node Properties",
"node-documentation": "Documentation",
"nothing-matched": "Nothing matched",
"display-data": "Select Display Data",
"show-attributes": "Show Attributes",
"show-initializers": "Show Initializers",
"show-node-names": "Show Node Names",
"export-file": "Export File",
"export-png": "PNG",
"export-svg": "SVG",
"upload-tip": "Click or Drop file here to view neural network models",
"upload-model": "Upload Model",
"supported-model": "Supported models: ",
"experimental-supported-model": "Experimental supported models: ",
"supported-model-list": "PaddlePaddle, ONNX, Keras, Core ML, Caffe, Caffe2, Darknet, MXNet, ncnn, TensorFlow Lite",
"experimental-supported-model-list": "TorchScript, PyTorch, Torch, ArmNN, BigDL, Chainer, CNTK, Deeplearning4j, MediaPipe, ML.NET, MNN, OpenVINO, Scikit-learn, Tengine, TensorFlow.js, TensorFlow",
"properties": {
"format": "Format",
"producer": "Producer",
"source": "Source",
"name": "Name",
"version": "Version",
"description": "Description",
"author": "Author",
"company": "Company",
"license": "License",
"domain": "Domain",
"imports": "Imports",
"runtime": "Runtime",
"type": "Type",
"tags": "Tags",
"inputs": "Inputs",
"outputs": "Outputs",
"attributes": "Attributes"
},
"documentation": {
"attributes": "Attributes",
"inputs": "Inputs",
"outputs": "Outputs",
"type-constraints": "Type Constraints",
"examples": "Examples",
"references": "References",
"support": "Support",
"support-info": "In domain <1>{{domain}}</1> since version <3>{{since_version}}</3> at support level <5>{{support_level}}</5>."
},
"restore-size": "Restore Size",
"zoom-in": "Zoom In",
"zoom-out": "Zoom Out"
}
{
"cancel": "取消",
"close": "关闭",
"confirm": "确定",
"empty": "暂无数据",
"error": "发生错误",
......@@ -12,19 +14,19 @@
"runs": "数据流",
"samples": "样本数据",
"scalars": "标量数据",
"search": "搜索",
"search-empty": "没有找到您期望的内容,你可以尝试其他搜索词<1/>或者点击<3>查看全部图表</3>",
"search-result": "搜索结果",
"search-runs": "搜索数据流",
"search-tags": "搜索标签(支持正则)",
"select": "请选择",
"search": "搜索",
"select-all": "全选",
"select-runs": "选择数据流",
"select": "请选择",
"start-realtime-refresh": "运行实时数据刷新",
"stop": "停止",
"stop-realtime-refresh": "停止实时数据刷新",
"stop": "停止",
"stopped": "已停止",
"total-page": "共 {{count}} 页,跳转至",
"total-page_plural": "共 {{count}} 页,跳转至",
"total-page": "共 {{count}} 页,跳转至",
"unselected-empty": "未选中任何数据<1/>请在右侧操作栏选择要展示的数据"
}
{
"scale": "比例",
"download-image": "下载图片",
"restore-image": "还原图片",
"node-info": "节点信息",
"node-type": "节点类型",
"node-name": "节点名称",
"node-data-shape": "数据类型",
"input": "输入",
"output": "输出",
"op-type": "算子类型",
"node-data-type": "数据类型",
"click-node": "点击左侧节点,查看节点信息"
"change-model": "更换模型",
"model-properties": "模型属性",
"node-properties": "节点属性",
"node-documentation": "文档",
"nothing-matched": "无匹配的内容",
"display-data": "选择展示数据",
"show-attributes": "显示参数",
"show-initializers": "显示初始化参数",
"show-node-names": "显示节点名称",
"export-file": "导出文件",
"export-png": "PNG",
"export-svg": "SVG",
"upload-tip": "点击或拖拽文件到页面上传模型,进行结构展示",
"upload-model": "上传模型",
"supported-model": "VisualDL支持:",
"experimental-supported-model": "VisualDL实验性支持:",
"supported-model-list": "PaddlePaddle、ONNX、Keras、Core ML、Caffe、Caffe2、Darknet、MXNet、ncnn、TensorFlow Lite",
"experimental-supported-model-list": "TorchScript、PyTorch、Torch、 ArmNN、BigDL、Chainer、CNTK、Deeplearning4j、MediaPipe、ML.NET、MNN、OpenVINO、Scikit-learn、Tengine、TensorFlow.js、TensorFlow",
"properties": {
"format": "格式",
"producer": "框架",
"source": "源",
"name": "名称",
"version": "版本",
"description": "描述",
"author": "作者",
"company": "公司",
"license": "许可证",
"domain": "域名",
"imports": "导入",
"runtime": "运行时",
"type": "类型",
"tags": "标签",
"inputs": "输入",
"outputs": "输出",
"attributes": "属性"
},
"documentation": {
"attributes": "属性",
"inputs": "输入",
"outputs": "输出",
"type-constraints": "类型约束",
"examples": "示例",
"references": "参考",
"support": "支持",
"support-info": "从 <3>{{since_version}}</3> 版本起域名 <1>{{domain}}</1> 的支持等级为 <5>{{support_level}}</5>。"
},
"restore-size": "重置大小",
"zoom-in": "放大",
"zoom-out": "缩小"
}
[
{
"name": "InputLayer",
"schema": {
"bindings": [
{ "name": "layerBindingId", "type": "int", "src": "layerBindingId" }
]
}
},
{
"name": "OutputLayer",
"schema": {
"category": "Tensor",
"bindings": [
{ "name": "layerBindingId", "type": "int", "src": "layerBindingId" }
]
}
},
{
"name": "Pooling2dLayer",
"schema": {
"category": "Pool",
"attributes": [
{ "name": "type", "type": "string", "src": "poolType", "src_type": "PoolingAlgorithm"},
{ "name": "padding", "type": "string", "src": ["padTop", "padRight", "padBottom", "padLeft"] },
{ "name": "width", "type": "string", "src": "poolWidth" },
{ "name": "height", "type": "string", "src": "poolHeight" },
{ "name": "stride", "type": "string", "src": ["strideX", "strideY"] },
{ "name": "outputShapeRounding", "type": "string", "src": "outputShapeRounding", "src_type": "OutputShapeRounding"},
{ "name": "paddingMethod", "type": "string", "src": "paddingMethod", "src_type": "PaddingMethod"},
{ "name": "dataLayout", "type": "string", "src": "dataLayout", "src_type": "DataLayout" }
]
}
},
{
"name": "ReshapeLayer",
"schema": {
"category": "Shape",
"attributes": [
{ "name": "targetShape", "type": "string", "src": "targetShape" }
]
}
},
{
"name": "SoftmaxLayer",
"schema": {
"category": "Activation",
"attributes": [
{ "name": "beta", "type": "float", "src": "beta" }
]
}
},
{
"name": "Convolution2dLayer",
"schema": {
"category": "Layer",
"inputs": [
{ "name": "weight", "src": "weights" },
{ "name": "bias", "src": "biases" }
],
"attributes": [
{ "name": "padding", "type": "string", "src": ["padTop", "padRight", "padBottom", "padLeft"] },
{ "name": "stride", "type": "string", "src": ["strideX", "strideY"] },
{ "name": "dilation", "type": "string", "src": ["dilationX", "dilationY"] },
{ "name": "dataLayout", "type": "string", "src": "dataLayout", "src_type": "DataLayout" }
]
}
},
{
"name": "DepthwiseConvolution2dLayer",
"schema": {
"category": "Layer",
"inputs": [
{ "name": "weight", "src": "weights" },
{ "name": "bias", "src": "biases" }
],
"attributes": [
{ "name": "padding", "type": "string", "src": ["padTop", "padRight", "padBottom", "padLeft"] },
{ "name": "stride", "type": "string", "src": ["strideX", "strideY"] },
{ "name": "dilation", "type": "string", "src": ["dilationX", "dilationY"] },
{ "name": "dataLayout", "type": "string", "src": "dataLayout", "src_type": "DataLayout" }
]
}
},
{
"name": "ActivationLayer",
"schema": {
"category": "Activation",
"attributes": [
{ "name": "function", "type": "string", "src": "activationFunction", "src_type": "ActivationFunction" },
{ "name": "a", "type": "float", "src": "a" },
{ "name": "b", "type": "float", "src": "b" }
]
}
},
{
"name": "PermuteLayer",
"schema": {
"category": "Shape",
"attributes": [
{ "name": "dimMappings", "type": "string", "src": "dimMappings" }
]
}
},
{
"name": "FullyConnectedLayer",
"schema": {
"category": "Layer",
"inputs": [
{ "name": "weights", "src": "weights" },
{ "name": "biases", "src": "biases" }
],
"attributes": [
{ "name": "transposeWeightsMatrix", "type": "bool", "src": "transposeWeightsMatrix" }
]
}
},
{
"name": "ConstantLayer",
"schema": {
"category": "Constant",
"inputs": [
{ "name": "input", "src": "input" }
]
}
},
{
"name": "SpaceToBatchNdLayer",
"schema": {
"category": "Layer",
"attributes": [
{ "name": "blockShape", "type": "string", "src": "blockShape" },
{ "name": "padList", "type": "string", "src": "padList" },
{ "name": "dataLayout", "type": "string", "src": "dataLayout", "src_type": "DataLayout" }
]
}
},
{
"name": "BatchToSpaceNdLayer",
"schema": {
"category": "Layer",
"attributes": [
{ "name": "blockShape", "type": "string", "src": "blockShape" },
{ "name": "crops", "type": "string", "src": "crops" },
{ "name": "dataLayout", "type": "string", "src": "dataLayout", "src_type": "DataLayout" }
]
}
},
{
"name": "DivisionLayer",
"schema": {
"category": "Layer"
}
},
{
"name": "MinimumLayer",
"schema": {
"category": "Layer"
}
},
{
"name": "EqualLayer",
"schema": {
"category": "Layer"
}
},
{
"name": "MaximumLayer",
"schema": {
"category": "Layer"
}
},
{
"name": "NormalizationLayer",
"schema": {
"category": "Normalization",
"attributes": [
{ "name": "normChannelType", "type": "string", "src": "normChannelType", "src_type": "NormalizationAlgorithmChannel" },
{ "name": "normMethodType", "type": "string", "src": "normMethodType", "src_type": "NormalizationAlgorithmMethod" },
{ "name": "normSize", "type": "uint", "src": "normSize" },
{ "name": "alpha", "type": "float", "src": "alpha" },
{ "name": "beta", "type": "float", "src": "beta" },
{ "name": "k", "type": "float", "src": "k" },
{ "name": "dataLayout", "type": "string", "src": "dataLayout", "src_type": "DataLayout" }
]
}
},
{
"name": "PadLayer",
"schema": {
"category": "Layer",
"attributes": [
{ "name": "padList", "type": "uint", "src": "padList" },
{ "name": "padValue", "type": "float", "src": "padValue" }
]
}
},
{
"name": "RsqrtLayer",
"schema": {
"category": "Layer"
}
},
{
"name": "FloorLayer",
"schema": {
"category": "Layer"
}
},
{
"name": "BatchNormalizationLayer",
"schema": {
"category": "Normalization",
"inputs": [
{ "name": "mean", "src": "mean" },
{ "name": "variance", "src": "variance" },
{ "name": "beta", "src": "beta" },
{ "name": "gamma", "src": "gamma" }
],
"attributes": [
{ "name": "eps", "type": "float", "src": "eps" },
{ "name": "dataLayout", "type": "string", "src": "dataLayout", "src_type": "DataLayout" }
]
}
},
{
"name": "GreaterLayer",
"schema": {
"category": "Layer",
"attributes": [
]
}
},
{
"name": "ResizeBilinearLayer",
"schema": {
"category": "Layer",
"attributes": [
{ "name": "targetWidth", "type": "uint", "src": "targetWidth" },
{ "name": "targetHeight", "type": "uint", "src": "targetHeight" },
{ "name": "dataLayout", "type": "string", "src": "dataLayout", "src_type": "DataLayout" }
]
}
},
{
"name": "SubtractionLayer",
"schema": {
}
},
{
"name": "StridedSliceLayer",
"schema": {
"category": "Tensor",
"attributes": [
{ "name": "begin", "type": "int", "src": "begin" },
{ "name": "end", "type": "int", "src": "end" },
{ "name": "stride", "type": "int", "src": "stride" },
{ "name": "beginMask", "type": "int", "src": "beginMask" },
{ "name": "endMask", "type": "int", "src": "endMask" },
{ "name": "shrinkAxisMask", "type": "int", "src": "shrinkAxisMask" },
{ "name": "ellipsisMask", "type": "int", "src": "ellipsisMask" },
{ "name": "newAxisMask", "type": "int", "src": "newAxisMask" },
{ "name": "dataLayout", "type": "string", "src": "dataLayout", "src_type": "DataLayout" }
]
}
},
{
"name": "GatherLayer",
"schema": {
"category": "Tensor"
}
},
{
"name": "MeanLayer",
"schema": {
"attributes": [
{ "name": "axis", "type": "uint", "src": "axis" },
{ "name": "keepDims", "type": "bool", "src": "keepDims" }
]
}
},
{
"name": "MergerLayer",
"schema": {
"category": "Tensor"
}
},
{
"name": "L2NormalizationLayer",
"schema": {
"category": "Normalization",
"attributes": [
{ "name": "eps", "type": "float", "src": "eps" },
{ "name": "dataLayout", "type": "string", "src": "dataLayout", "src_type": "DataLayout" }
]
}
},
{
"name": "SplitterLayer",
"schema": {
"category": "Tensor",
"attributes": [
{ "name": "concatAxis", "type": "uint", "src": "concatAxis" },
{ "name": "numViews", "type": "uint", "src": "numViewes" },
{ "name": "numDimensions", "type": "uint", "src": "numDimensions" }
]
}
},
{
"name": "DetectionPostProcessLayer",
"schema": {
"category": "Custom",
"attributes": [
{ "name": "maxDetections", "type": "uint", "src": "maxDetections" },
{ "name": "maxClassesPerDetection", "type": "uint", "src": "maxClassesPerDetection" },
{ "name": "detectionsPerClass", "type": "uint", "src": "detectionsPerClass" },
{ "name": "nmsScoreThreshold", "type": "float", "src": "nmsScoreThreshold" },
{ "name": "numIouThreshold", "type": "float", "src": "nmsIouThreshold" },
{ "name": "numClasses", "type": "uint", "src": "numClasses" },
{ "name": "useRegularNms", "type": "bool", "src": "useRegularNms" },
{ "name": "scaleX", "type": "float", "src": "scaleX" },
{ "name": "scaleY", "type": "float", "src": "scaleY" },
{ "name": "scaleW", "type": "float", "src": "scaleW" },
{ "name": "scaleH", "type": "float", "src": "scaleH" }
]
}
},
{
"name": "LstmLayer",
"schema": {
"category": "Layer",
"inputs": [
{ "name": "inputToForgetWeights1", "src": "inputToForgetWeights1" },
{ "name": "inputToCellWeights1", "src": "inputToCellWeights1" },
{ "name": "inputToOutputWeights1", "src": "inputToOutputWeights1" },
{ "name": "recurrentToForgetWeights1", "src": "recurrentToForgetWeights1" },
{ "name": "recurrentToCellWeights1", "src": "recurrentToCellWeights1" },
{ "name": "recurrentToOutputWeights1", "src": "recurrentToOutputWeights1" },
{ "name": "forgetGateBias1", "src": "forgetGateBias1" },
{ "name": "cellBias1", "src": "cellBias1" },
{ "name": "outputGateBias1", "src": "outputGateBias1" },
{ "name": "inputToInputWeights1", "src": "inputToInputWeights1" },
{ "name": "recurrentToInputWeights1", "src": "recurrentToInputWeights1" },
{ "name": "cellToInputWeights1", "src": "cellToInputWeights1" },
{ "name": "inputGateBias1", "src": "inputGateBias1" },
{ "name": "projectionWeights1", "src": "projectionWeights1" },
{ "name": "projectionBias1", "src": "projectionBias1" },
{ "name": "cellToForgetWeights1", "src": "cellToForgetWeights1" },
{ "name": "cellToOutputWeights1", "src": "cellToOutputWeights1" },
{ "name": "inputLayerNormWeights1", "src": "inputLayerNormWeights1" },
{ "name": "forgetLayerNormWeights1", "src": "forgetLayerNormWeights1" },
{ "name": "cellLayerNormWeights1", "src": "cellLayerNormWeights1" },
{ "name": "outputLayerNormWeights1", "src": "outputLayerNormWeights1" }
],
"attributes": [
{ "name": "activationFunc", "type": "uint", "src": "activationFunc" },
{ "name": "clippingThresCell", "type": "float", "src": "clippingThresCell" },
{ "name": "clippingThresProj", "type": "float", "src": "clippingThresProj" },
{ "name": "cifgEnabled", "type": "bool", "src": "cifgEnabled" },
{ "name": "peepholeEnabled", "type": "bool", "src": "peepholeEnabled" },
{ "name": "projectionEnabled", "type": "bool", "src": "projectionEnabled" },
{ "name": "layerNormEnabled", "type": "bool", "src": "layerNormEnabled" }
]
}
},
{
"name": "QuantizeLayer",
"schema": {
"category": "Layer"
}
},
{
"name": "DequantizeLayer",
"schema": {
"category": "Layer"
}
},
{
"name": "MergeLayer",
"schema": {
"category": "Layer"
}
},
{
"name": "SwitchLayer",
"schema": {
"category": "Layer"
}
},
{
"name": "ConcatLayer",
"schema": {
"category": "Tensor",
"attributes": [
{ "name": "concatAxis", "type": "uint", "src": "concatAxis" },
{ "name": "numViews", "type": "uint", "src": "numViewes" },
{ "name": "numDimensions", "type": "uint", "src": "numDimensions" }
]
}
},
{
"name": "SpaceToDepthLayer",
"schema": {
"category": "Layer",
"attributes": [
{ "name": "blockSize", "type": "uint", "src": "blockSize" },
{ "name": "dataLayout", "type": "string", "src": "dataLayout", "src_type": "DataLayout" }
]
}
},
{
"name": "PreluLayer",
"schema": {
"category": "Layer"
}
},
{
"name": "TransposeConvolution2dLayer",
"schema": {
"category": "Layer",
"inputs": [
{ "name": "weight", "src": "weights" },
{ "name": "bias", "src": "biases" }
],
"attributes": [
{ "name": "padding", "type": "string", "src": ["padTop", "padRight", "padBottom", "padLeft"] },
{ "name": "stride", "type": "string", "src": ["strideX", "strideY"] },
{ "name": "dataLayout", "type": "string", "src": "dataLayout", "src_type": "DataLayout" }
]
}
},
{
"name": "ResizeLayer",
"schema": {
"category": "Layer",
"attributes": [
{ "name": "targetWidth", "type": "uint", "src": "targetWidth" },
{ "name": "targetHeight", "type": "uint", "src": "targetHeight" },
{ "name": "method", "type": "string", "src": "method", "src_type": "ResizeMethod" },
{ "name": "dataLayout", "type": "string", "src": "dataLayout", "src_type": "DataLayout" }
]
}
},
{
"name": "StackLayer",
"schema": {
"category": "Layer",
"attributes": [
{ "name": "axis", "type": "uint", "src": "axis" },
{ "name": "numInputs", "type": "uint", "src": "numInputs" },
{ "name": "inputShape", "type": "uint", "src": "inputShape" }
]
}
},
{
"name": "QuantizedLstmLayer",
"schema": {
"category": "Layer",
"inputs": [
{ "name": "inputToInputWeights1", "src": "inputToInputWeights1" },
{ "name": "inputToForgetWeights1", "src": "inputToForgetWeights1" },
{ "name": "inputToCellWeights1", "src": "inputToCellWeights1" },
{ "name": "inputToOutputWeights1", "src": "inputToOutputWeights1" },
{ "name": "recurrentToInputWeights1", "src": "recurrentToInputWeights1" },
{ "name": "recurrentToForgetWeights1", "src": "recurrentToForgetWeights1" },
{ "name": "recurrentToCellWeights1", "src": "recurrentToCellWeights1" },
{ "name": "recurrentToOutputWeights1", "src": "recurrentToOutputWeights1" },
{ "name": "inputGateBias1", "src": "inputGateBias1" },
{ "name": "forgetGateBias1", "src": "forgetGateBias1" },
{ "name": "cellBias1", "src": "cellBias1" },
{ "name": "outputGateBias1", "src": "outputGateBias1" }
]
}
}
]
此差异已折叠。
/* jshint esversion: 6 */
/* eslint "indent": [ "error", 4, { "SwitchCase": 1 } ] */
var armnn = armnn || {};
var base = base || require('./base');
var flatbuffers = flatbuffers || require('flatbuffers').flatbuffers;
var long = long || { Long: require('long') };
armnn.ModelFactory = class {
match(context) {
const extension = context.identifier.split('.').pop().toLowerCase();
if (extension == 'armnn') {
return true;
}
return false;
}
open(context, host) {
return host.require('./armnn-schema').then((schema) => {
const identifier = context.identifier;
let model = null;
try {
const buffer = context.buffer;
const byteBuffer = new flatbuffers.ByteBuffer(buffer);
armnn.schema = schema.armnn_schema;
model = armnn.schema.SerializedGraph.getRootAsSerializedGraph(byteBuffer);
}
catch (error) {
host.exception(error, false);
const message = error && error.message ? error.message : error.toString();
throw new armnn.Error(message.replace(/\.$/, '') + " in '" + identifier + "'.");
}
return armnn.Metadata.open(host).then((metadata) => {
try {
return new armnn.Model(model, metadata);
}
catch (error) {
const message = error && error.message ? error.message : error.toString();
throw new new armnn.Error(message.replace(/\.$/, '') + " in '" + identifier + "'.");
}
});
});
}
};
armnn.Model = class {
constructor(model, metadata) {
this._graphs = [];
this._graphs.push(new armnn.Graph(model, metadata));
}
get format() {
return 'Arm NN';
}
get description() {
return '';
}
get graphs() {
return this._graphs;
}
};
armnn.Graph = class {
constructor(graph, metadata) {
this._name = '';
this._nodes = [];
this._inputs = [];
this._outputs = [];
// generate parameters
const args = {};
for (let j = 0; j < graph.layersLength(); j++) {
let base = armnn.Node.getBase(graph.layers(j));
for (let i = 0 ; i < base.outputSlotsLength() ; i++) {
const key = base.index().toString() + ':' + i.toString();
args[key] = new armnn.Argument(key, base.outputSlots(i).tensorInfo(), null);
}
}
for (let j = 0; j < graph.layersLength(); j++) {
this._nodes.push(new armnn.Node(graph.layers(j), args, metadata));
}
for (let k = 0; k < graph.inputIdsLength(); k++) {
// need to do something?
}
for (let l = 0; l < graph.outputIdsLength(); l++) {
// need to do something?
}
}
get name() {
return this._name;
}
get groups() {
return false;
}
get inputs() {
return this._inputs;
}
get outputs() {
return this._outputs;
}
get nodes() {
return this._nodes;
}
};
armnn.Node = class {
constructor(layer, args, metadata) {
this._metadata = metadata;
this._type = armnn.schema.LayerName[layer.layerType()];
this._name = '';
this._outputs = [];
this._inputs = [];
this._attributes = [];
const base = armnn.Node.getBase(layer);
if (base) {
this._name = base.layerName();
for (let i = 0; i < base.inputSlotsLength(); i++) {
const connection = base.inputSlots(i).connection();
const sourceLayerIndex = connection.sourceLayerIndex();
const sourceOutputIndex = connection.outputSlotIndex();
const argument = args[sourceLayerIndex.toString() + ':' + sourceOutputIndex.toString()];
this._inputs.push(new armnn.Parameter('input', [ argument ]));
}
for (let j = 0; j < base.outputSlotsLength(); j++) {
const argument = args[base.index().toString() + ':' + j.toString()];
this._outputs.push(new armnn.Parameter('output', [ argument ]));
}
}
const schema = this._metadata.type(this._type);
if (schema) {
const _layer = armnn.Node.castLayer(layer);
if (schema.bindings) {
for (let i = 0 ; i < schema.bindings.length ; i++) {
const binding = schema.bindings[i];
const value = _layer.base()[binding.src]();
this._attributes.push(new armnn.Attribute(binding.name, binding.type, value));
}
}
if (schema.attributes) {
for (const attribute of schema.attributes) {
const value = this.packAttr(_layer, attribute);
this._attributes.push(new armnn.Attribute(attribute.name, attribute.type, value));
}
}
if (schema.inputs) {
for (let i = 0 ; i < schema.inputs.length ; i++) {
const input = schema.inputs[i];
const initializer = _layer[input.src]();
if (initializer) {
const args = [ new armnn.Argument('', null, initializer) ];
this._inputs.push(new armnn.Parameter(input.name, args));
}
}
}
}
}
get type() {
return this._type.replace(/Layer$/, '');
}
get name() {
return this._name;
}
get domain() {
return null;
}
get metadata() {
return this._metadata.type(this._type);
}
get group() {
return null;
}
get inputs() {
return this._inputs;
}
get outputs() {
return this._outputs;
}
get attributes() {
return this._attributes;
}
static castLayer(layer) {
let layerType = layer.layerType();
for (const k of Object.keys(armnn.schema.Layer)) {
if (layerType == armnn.schema.Layer[k]) {
return layer.layer(new armnn.schema[k]);
}
}
return null;
}
static getBase(layer) {
layer = armnn.Node.castLayer(layer);
return (layer.base().base)? layer.base().base() : layer.base();
}
getAttr(descriptor, key) {
if (typeof descriptor[key] == "undefined")
return "undefined";
const values = descriptor[key]();
if (Array.isArray(values)) {
return values.join(", ");
}
else {
return values;
}
}
packAttr(layer, attr) {
const descriptor = layer === null ? null : layer.descriptor();
const key = attr.src;
const type = attr.src_type;
if (typeof type != "undefined") {
let value = this.getAttr(descriptor, key);
if (typeof armnn.schema[type + "Name"] != "undefined") {
return armnn.schema[type + "Name"][value];
}
else {
return value;
}
}
else if (Array.isArray(key)) {
let values = [];
for (let i = 0 ; i < key.length ; i++) {
values.push(this.getAttr(descriptor, key[i]));
}
return values.join(", ");
}
else {
return this.getAttr(descriptor, key);
}
}
static makeKey(layer_id, index) {
return layer_id.toString() + "_" + index.toString();
}
};
armnn.Attribute = class {
constructor(name, type, value) {
this._name = name;
this._value = value;
this._visible = true;
switch (type) {
case 'int': this._type = 'int32'; break;
case 'uint': this._type = 'uint32'; break;
case 'float': this._type = 'float32'; break;
case 'string': this._type = 'string'; break;
}
}
get name() {
return this._name;
}
get type() {
return this._type;
}
get value() {
return this._value;
}
get visible() {
return this._visible == false ? false : true;
}
};
armnn.Parameter = class {
constructor(name, args) {
this._name = name;
this._arguments = args;
}
get name() {
return this._name;
}
get visible() {
return true;
}
get arguments() {
return this._arguments;
}
};
armnn.Argument = class {
constructor(name, tensorInfo, initializer) {
if (typeof name !== 'string') {
throw new armnn.Error("Invalid argument identifier '" + JSON.stringify(name) + "'.");
}
const info = initializer ? initializer.info() : tensorInfo;
this._name = name;
this._type = new armnn.TensorType(info);
this._initializer = initializer ? new armnn.Tensor(info, initializer) : null;
if (this._type.dataType.startsWith('q') && info) {
this._scale = info.quantizationScale();
this._zeroPoint = info.quantizationOffset();
}
}
get name() {
return this._name;
}
get type() {
return this._type;
}
get quantization() {
if (this._scale !== undefined && this._zeroPoint !== undefined) {
return this._scale.toString() + ' * ' + (this._zeroPoint == 0 ? 'q' : ('(q - ' + this._zeroPoint.toString() + ')'));
}
return undefined;
}
get initializer() {
return this._initializer;
}
};
armnn.Tensor = class {
constructor(tensorInfo, tensor) {
this._name = '';
this._type = new armnn.TensorType(tensorInfo);
this._kind = 'Initializer';
let data = null;
if (tensor.dataType() == armnn.schema.ConstTensorData.ByteData)
data = tensor.data(new armnn.schema.ByteData);
else if (tensor.dataType() == armnn.schema.ConstTensorData.ShortData)
data = tensor.data(new armnn.schema.ShortData);
else if (tensor.dataType() == armnn.schema.ConstTensorData.IntData)
data = tensor.data(new armnn.schema.IntData);
else if (tensor.dataType() == armnn.schema.ConstTensorData.LongData)
data = tensor.data(new armnn.schema.LongData);
this._data = data.dataLength() > 0 ? data.dataArray() : null;
}
get name() {
return this._name;
}
get kind() {
return this._kind;
}
get type() {
return this._type;
}
get state() {
return this._context().state;
}
get value() {
let context = this._context();
if (context.state) {
return null;
}
context.limit = Number.MAX_SAFE_INTEGER;
return this._decode(context, 0);
}
toString() {
let context = this._context();
if (context.state) {
return '';
}
context.limit = 10000;
let value = this._decode(context, 0);
return JSON.stringify(value, null, 4);
}
_context() {
let context = {};
context.state = null;
context.index = 0;
context.count = 0;
if (this._data == null) {
context.state = 'Tensor data is empty.';
return context;
}
context.dataType = this._type.dataType;
context.shape = this._type.shape.dimensions;
context.data = new DataView(this._data.buffer, this._data.byteOffset, this._data.byteLength);
return context;
}
_decode(context, dimension) {
let shape = context.shape;
if (shape.length == 0) {
shape = [ 1 ];
}
let size = shape[dimension];
let results = [];
if (dimension == shape.length - 1) {
for (let i = 0; i < size; i++) {
if (context.count > context.limit) {
results.push('...');
return results;
}
switch (context.dataType) {
case 'float16':
results.push(context.data.getFloat16(context.index, true));
context.index += 2;
context.count++;
break;
case 'float32':
results.push(context.data.getFloat32(context.index, true));
context.index += 4;
context.count++;
break;
case 'quint8':
results.push(context.data.getUint8(context.index));
context.index += 1;
context.count++;
break;
case 'qint16':
results.push(context.data.getInt16(context.index, true));
context.index += 2;
context.count++;
break;
case 'int32':
results.push(context.data.getInt32(context.index, true));
context.index += 4;
context.count++;
break;
case 'boolean':
results.push(context.data.getInt8(context.index));
context.index += 1;
context.count++;
break;
default:
break;
}
}
}
else {
for (let j = 0; j < size; j++) {
if (context.count > context.limit) {
results.push('...');
return results;
}
results.push(this._decode(context, dimension + 1));
}
}
if (context.shape.length == 0) {
return results[0];
}
return results;
}
};
armnn.TensorType = class {
constructor(tensorInfo) {
const dataType = tensorInfo.dataType();
switch (dataType) {
case 0: this._dataType = 'float16'; break;
case 1: this._dataType = 'float32'; break;
case 2: this._dataType = 'quint8'; break; // QuantisedAsymm8
case 3: this._dataType = 'int32'; break;
case 4: this._dataType = 'boolean'; break;
case 5: this._dataType = 'qint16'; break; // QuantisedSymm16
case 6: this._dataType = 'quint8'; break; // QAsymmU8
case 7: this._dataType = 'qint16'; break; // QSymmS16
default: throw new armnn.Error("Unknown data type '" + dataType + "'.");
}
let dimensions = [];
let dimensionsLength = tensorInfo.dimensionsLength();
if (dimensionsLength > 0) {
for (let i = 0; i < dimensionsLength; i++) {
dimensions.push(tensorInfo.dimensions(i));
}
}
this._shape = new armnn.TensorShape(dimensions);
}
get dataType() {
return this._dataType;
}
get shape() {
return this._shape;
}
toString() {
return this.dataType + this._shape.toString();
}
};
armnn.TensorShape = class {
constructor(dimensions) {
this._dimensions = dimensions;
}
get dimensions() {
return this._dimensions;
}
toString() {
if (!this._dimensions || this._dimensions.length == 0) {
return '';
}
return '[' + this._dimensions.map((dimension) => dimension.toString()).join(',') + ']';
}
};
armnn.Metadata = class {
static open(host) {
if (armnn.Metadata._metadata) {
return Promise.resolve(armnn.Metadata._metadata);
}
return host.request(null, 'armnn-metadata.json', 'utf-8').then((data) => {
armnn.Metadata._metadata = new armnn.Metadata(data);
return armnn.Metadata._metadata;
}).catch(() => {
armnn.Metadata._metadata = new armnn.Metadata(null);
return armnn.Metadata._metadata;
});
}
constructor(data) {
this._map = {};
if (data) {
let items = JSON.parse(data);
if (items) {
for (const item of items) {
if (item.name && item.schema) {
item.schema.name = item.name;
this._map[item.name] = item.schema;
}
}
}
}
}
type(name) {
return this._map[name];
}
attribute(type, name) {
const schema = this.type(type);
if (schema) {
let attributeMap = schema.attributeMap;
if (!attributeMap) {
attributeMap = {};
if (schema.attributes) {
for (const attribute of schema.attributes) {
attributeMap[attribute.name] = attribute;
}
}
schema.attributeMap = attributeMap;
}
let attributeSchema = attributeMap[name];
if (attributeSchema) {
return attributeSchema;
}
}
return null;
}
};
armnn.Error = class extends Error {
constructor(message) {
super(message);
this.name = 'Error loading Arm NN model.';
}
};
if (typeof module !== 'undefined' && typeof module.exports === 'object') {
module.exports.ModelFactory = armnn.ModelFactory;
}
/* eslint "indent": [ "error", 4, { "SwitchCase": 1 } ] */
// Experimental
var barracuda = barracuda || {};
var base = base || require('./base');
var long = long || { Long: require('long') };
barracuda.ModelFactory = class {
match(context) {
const identifier = context.identifier;
const extension = identifier.split('.').pop().toLowerCase();
if (extension === 'nn') {
const buffer = context.buffer;
if (buffer.length > 12 && buffer[0] <= 0x10 && buffer.subarray(1, 8).every((v) => v == 0x00)) {
return true;
}
}
return false;
}
open(context /*, host */) {
return barracuda.Metadata.open().then((metadata) => {
try {
const nn = new barracuda.NNModel(context.buffer);
return new barracuda.Model(metadata, nn);
}
catch (error) {
const identifier = context.identifier.toLowerCase();
const message = error && error.message ? error.message : error.toString();
throw new barracuda.Error(message.replace(/\.$/, '') + " in '" + identifier + "'.");
}
});
}
};
barracuda.Model = class {
constructor(metadata, nn) {
this._version = nn.version.toString();
this._graphs = [ new barracuda.Graph(metadata, nn) ];
}
get format() {
return "Barracuda v" + this._version;
}
get graphs() {
return this._graphs;
}
};
barracuda.Graph = class {
constructor(metadata, nn) {
this._inputs = [];
this._outputs = [];
this._nodes = [];
for (const input of nn.inputs) {
this._inputs.push(new barracuda.Parameter(input.name, [
new barracuda.Argument(input.name, new barracuda.TensorType(4, new barracuda.TensorShape(input.shape)))
]));
}
for (const output of nn.outputs) {
this._outputs.push(new barracuda.Parameter(output, [
new barracuda.Argument(output)
]));
}
const layers = [];
const initializers = new Map();
for (const layer of nn.layers) {
if (layer.type !== 255 || layer.inputs.length > 0) {
layers.push(layer);
}
else {
for (const tensor of layer.tensors) {
initializers.set(tensor.name, new barracuda.Tensor(tensor));
}
}
}
for (const layer of layers) {
this._nodes.push(new barracuda.Node(metadata, layer, initializers));
}
}
get name() {
return '';
}
get inputs() {
return this._inputs;
}
get outputs() {
return this._outputs;
}
get nodes() {
return this._nodes;
}
};
barracuda.Parameter = class {
constructor(name, args) {
this._name = name;
this._arguments = args;
}
get name() {
return this._name;
}
get visible() {
return true;
}
get arguments() {
return this._arguments;
}
};
barracuda.Argument = class {
constructor(name, type, initializer) {
this._name = name;
this._type = type || null;
this._initializer = initializer || null;
}
get name() {
return this._name;
}
get type() {
return this._type;
}
get initializer() {
return this._initializer;
}
};
barracuda.Node = class {
constructor(metadata, layer, initializers) {
this._name = layer.name;
this._metadata = metadata.type(layer.type) || { name: layer.type.toString() };
this._type = this._metadata.name;
this._inputs = [];
this._outputs = [];
this._attributes = [];
const inputs = Array.prototype.slice.call(this._metadata.inputs || [ 'input' ]);
if (this._metadata.inputs && this._metadata.inputs.length === 1 && this._metadata.inputs[0] === 'inputs') {
this._inputs.push(new barracuda.Parameter('inputs', layer.inputs.map((input) => {
const initializer = initializers.has(input) ? initializers.get(input) : null;
return new barracuda.Argument(input, initializer ? initializer.type : null, initializer);
})));
}
else {
for (let i = 0; i < layer.inputs.length; i++) {
const input = layer.inputs[i];
const initializer = initializers.has(input) ? initializers.get(input) : null;
this._inputs.push(new barracuda.Parameter(inputs.length > 0 ? inputs.shift() : i.toString(), [
new barracuda.Argument(input, initializer ? initializer.type : null, initializer)
]));
}
}
for (let i = 0; i < layer.tensors.length; i++) {
const tensor = layer.tensors[i];
const initializer = new barracuda.Tensor(tensor);
this._inputs.push(new barracuda.Parameter(inputs.length > 0 ? inputs.shift() : i.toString(), [
new barracuda.Argument(tensor.name, initializer.type, initializer)
]));
}
this._outputs.push(new barracuda.Parameter('output', [
new barracuda.Argument(this._name)
]));
if (this._type === 'Activation') {
if (!barracuda.Activation[layer.activation]) {
throw new barracuda.Error("Unknown activation '" + layer.activation + "'.");
}
this._type = barracuda.Activation[layer.activation];
}
else if (layer.activation !== 0) {
throw new barracuda.Error("Unsupported activation '" + layer.activation + "' for type '" + this._type + "'.");
}
const attribute = (name, type, value, defaultValue) => {
if (Array.isArray(defaultValue) && Array.isArray(value) && value.length == defaultValue.length && value.every((v, i) => v === defaultValue[i])) {
return;
}
if (typeof defaultValue == 'function' && defaultValue(value)) {
return;
}
if (defaultValue === value) {
return;
}
this._attributes.push(new barracuda.Attribute(name, type, value));
};
attribute('strides', 'int32[]', layer.strides, []);
attribute('pads', 'int32[]', layer.pads, (value) => Array.isArray(value) && (value.every((v) => v === 0) || value.every((v) => v === -1)));
attribute('size', 'int32[]', layer.pool_size, []);
attribute('alpha', 'float32', layer.alpha, 1);
attribute('beta', 'float32', layer.beta, 0);
attribute('axis', 'int32', layer.axis, -1);
}
get type() {
return this._type;
}
get name() {
return this._name;
}
get metadata() {
return this._metadata;
}
get attributes() {
return this._attributes;
}
get inputs() {
return this._inputs;
}
get outputs() {
return this._outputs;
}
};
barracuda.Attribute = class {
constructor(name, type, value) {
this._name = name;
this._type = type;
this._value = value;
}
get type() {
return this._type;
}
get name() {
return this._name;
}
get value() {
return this._value;
}
get visible() {
return true;
}
};
barracuda.Tensor = class {
constructor(tensor) {
this._type = new barracuda.TensorType(tensor.itemsize, new barracuda.TensorShape(tensor.shape));
this._data = tensor.data;
}
get kind() {
return '';
}
get type() {
return this._type;
}
get state() {
return this._context().state || null;
}
get value() {
const context = this._context();
if (context.state) {
return null;
}
context.limit = Number.MAX_SAFE_INTEGER;
return this._decode(context, 0);
}
toString() {
const context = this._context();
if (context.state) {
return '';
}
context.limit = 10000;
const value = this._decode(context, 0);
return JSON.stringify(value, null, 4);
}
_context() {
const context = {};
context.index = 0;
context.count = 0;
context.state = null;
if (this._type.dataType == '?') {
context.state = 'Tensor has unknown data type.';
return context;
}
if (!this._type.shape || (this._type.shape.dimensions && this._type.shape.dimensions.length == 0)) {
context.state = 'Tensor has no dimensions.';
return context;
}
if (!this._data) {
context.state = 'Tensor data is empty.';
return context;
}
switch (this._type.dataType) {
case 'float32':
context.data = new DataView(this._data.buffer, this._data.byteOffset, this._data.byteLength);
break;
default:
context.state = 'Tensor data type is not implemented.';
break;
}
context.dataType = this._type.dataType;
context.shape = this._type.shape.dimensions;
return context;
}
_decode(context, dimension) {
const shape = context.shape.length == 0 ? [ 1 ] : context.shape;
const results = [];
const size = shape[dimension];
if (dimension == shape.length - 1) {
for (let i = 0; i < size; i++) {
if (context.count > context.limit) {
results.push('...');
return results;
}
switch (this._type.dataType) {
case 'float32':
results.push(context.data.getFloat32(context.index, true));
context.index += 4;
context.count++;
break;
}
}
}
else {
for (let j = 0; j < size; j++) {
if (context.count > context.limit) {
results.push('...');
return results;
}
results.push(this._decode(context, dimension + 1));
}
}
if (context.shape.length == 0) {
return results[0];
}
return results;
}
};
barracuda.TensorType = class {
constructor(itemsize, shape) {
switch (itemsize) {
case 4: this._dataType = 'float32'; break;
default: throw new barracuda.Error("Unsupported data type size '" + itemsize.toString() + "'.");
}
this._shape = shape;
}
get dataType() {
return this._dataType;
}
get shape() {
return this._shape;
}
toString() {
return this._dataType + this._shape.toString();
}
};
barracuda.TensorShape = class {
constructor(dimensions) {
this._dimensions = dimensions;
}
get dimensions() {
return this._dimensions;
}
toString() {
return this._dimensions ? ('[' + this._dimensions.map((dimension) => dimension ? dimension.toString() : '?').join(',') + ']') : '';
}
};
barracuda.NNModel = class {
constructor(buffer) {
// https://github.com/Unity-Technologies/ml-agents/blob/master/ml-agents/mlagents/trainers/barracuda.py
// https://github.com/Unity-Technologies/ml-agents/blob/master/ml-agents/mlagents/trainers/tensorflow_to_barracuda.py
const reader = new barracuda.BinaryReader(buffer);
this._version = reader.int32();
reader.int32();
this._inputs = [];
const modelInputsLength = reader.int32();
for (let i = 0; i < modelInputsLength; i++) {
this._inputs.push({
name: reader.string(),
shape: reader.shape()
});
}
this._outputs = reader.strings();
this._memories = [];
const memoriesLength = reader.int32();
for (let i = 0; i < memoriesLength; i++) {
// debugger;
this._memories.push({
shape: reader.shape(),
in: reader.string(),
out: reader.string()
});
}
this._layers = [];
const layersLength = reader.int32();
for (let i = 0; i < layersLength; i++) {
const layer = {};
layer.name = reader.string();
layer.type = reader.int32();
layer.activation = reader.int32();
reader.int32();
reader.int32();
layer.pads = reader.int32s();
layer.strides = reader.int32s();
layer.pool_size = reader.int32s();
layer.axis = reader.int32();
layer.alpha = reader.float32();
layer.beta = reader.float32();
reader.int32();
layer.inputs = reader.strings();
layer.tensors = [];
const tensorsLength = reader.int32();
for (let j = 0; j < tensorsLength; j++) {
layer.tensors.push({
name: reader.string(),
shape: reader.shape(),
offset: reader.int64(),
itemsize: reader.int32(),
length: reader.int32()
});
}
this._layers.push(layer);
}
for (const layer of this._layers) {
for (const tensor of layer.tensors) {
tensor.data = reader.bytes(tensor.offset * tensor.itemsize, tensor.length * tensor.itemsize);
}
}
}
get version() {
return this._version;
}
get inputs() {
return this._inputs;
}
get outputs() {
return this._outputs;
}
get memories() {
return this._memories;
}
get layers() {
return this._layers;
}
};
barracuda.Activation = {
0: "Linear", 1: "Relu", 2: "Softmax", 3: "Tanh", 4: "Sigmoid", 5: "Elu", 6: "Relu6", 7: "LeakyRelu",
8: "Selu", 9: "Swish", 10: "LogSoftmax", 11: "Softplus", 12: "Softsign",
100: "Abs", 101: "Neg", 102: "Ceil", 104: "Floor", 111: "Sqrt", 113: "Exp", 114: "Log",
200: "Acos", 201: "Acosh", 202: "Asin", 203: "Asinh", 204: "Atan", 205: "Atanh", 206: "Cos", 207: "Cosh", 208: "Sin", 209: "Sinh", 210: "Tan"
};
barracuda.BinaryReader = class {
constructor(buffer) {
this._buffer = buffer;
this._dataView = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
this._position = 0;
}
skip(offset) {
this._position += offset;
if (this._position > this._buffer.length) {
throw new barracuda.Error('Expected ' + (this._position - this._buffer.length) + ' more bytes. The file might be corrupted. Unexpected end of file.');
}
}
bytes(offset, length) {
const start = this._position + offset;
const end = start + length;
if (end > this._buffer.length) {
throw new barracuda.Error('Expected ' + (end - this._buffer.length) + ' more bytes. The file might be corrupted. Unexpected end of file.');
}
return this._buffer.slice(start, end);
}
int32() {
const position = this._position;
this.skip(4);
return this._dataView.getInt32(position, true);
}
int32s() {
const values = [];
const count = this.int32();
for (let i = 0; i < count; i++) {
values.push(this.int32());
}
return values;
}
int64() {
const value = this.int32();
if (this.int32() !== 0) {
throw new barracuda.Error('Invalid int64 value.');
}
return value;
}
float32() {
const position = this._position;
this.skip(4);
return this._dataView.getFloat32(position, true);
}
string() {
let text = '';
const size = this.int32();
let position = this._position;
this.skip(size);
for (let i = 0; i < size; i++) {
text += String.fromCharCode(this._buffer[position++]);
}
return text;
}
strings() {
const values = [];
const length = this.int32();
for (let i = 0; i < length; i++) {
values.push(this.string());
}
return values;
}
shape() {
return this.int32s();
}
};
barracuda.Metadata = class {
static open() {
barracuda.Metadata._metadata = barracuda.Metadata._metadata || new barracuda.Metadata();
return Promise.resolve(barracuda.Metadata._metadata);
}
constructor() {
this._map = new Map();
this._register(0, 'Nop', '');
this._register(1, 'Dense', 'Layer', [ 'input', 'kernel', 'bias' ]);
this._register(2, 'MatMul', '', [ 'input', 'kernel', 'bias' ]);
this._register(20, 'Conv2D', 'Layer', [ 'input', 'kernel', 'bias' ]);
this._register(21, 'DepthwiseConv2dNative', 'Layer', [ 'input', 'kernel', 'bias' ]);
this._register(22, 'Conv2DBackpropInput', '');
this._register(23, 'Upsample2D', '');
this._register(25, 'MaxPool', 'Pool');
this._register(26, 'AvgPool', 'Pool');
this._register(28, 'GlobalAvgPool', 'Pool');
this._register(29, 'Pad', '');
this._register(50, 'Activation', 'Activation');
this._register(51, 'ScaleBias', 'Normalization', [ 'input', 'scale', 'bias' ]);
this._register(52, 'InstanceNormalization', 'Normalization');
this._register(53, 'LRN', 'Normalization');
this._register(64, 'RandomStandardNormal', '');
this._register(65, 'RandomUniform', '');
this._register(67, 'OneHot', '');
this._register(100, 'Add', '', [ 'inputs' ]);
this._register(101, 'Sub', '', [ 'inputs' ]);
this._register(102, 'Mul', '', [ 'inputs' ]);
this._register(103, 'RealDiv', '', [ 'inputs' ]);
this._register(104, 'Pow', '', [ 'inputs' ]);
this._register(110, 'Minimum', '', [ 'inputs' ]);
this._register(111, 'Maximum', '', [ 'inputs' ]);
this._register(124, 'Max', '', [ 'inputs' ]);
this._register(125, 'Mean', '', [ 'inputs' ]);
this._register(126, 'Min', '', [ 'inputs' ]);
this._register(127, 'Prod', '', [ 'inputs' ]);
this._register(128, 'Sum', '', [ 'inputs' ]);
this._register(200, 'Flatten', 'Shape');
this._register(201, 'Reshape', 'Shape');
this._register(210, 'Concat', 'Tensor', [ 'inputs' ]);
this._register(211, 'StridedSlice', 'Shape');
}
_register(id, name, category, inputs) {
this._map.set(id, { name: name, category: category, inputs: inputs });
}
type(name) {
if (this._map.has(name)) {
return this._map.get(name);
}
return null;
}
};
barracuda.Error = class extends Error {
constructor(message) {
super(message);
this.name = 'Error loading Barracuda model.';
}
};
if (typeof module !== 'undefined' && typeof module.exports === 'object') {
module.exports.ModelFactory = barracuda.ModelFactory;
}
/* jshint esversion: 6 */
var base = base || {};
if (typeof window !== 'undefined' && typeof window.Long != 'undefined') {
window.long = { Long: window.Long };
}
if (!DataView.prototype.getFloat16) {
DataView.prototype.getFloat16 = function(byteOffset, littleEndian) {
const value = this.getUint16(byteOffset, littleEndian);
const e = (value & 0x7C00) >> 10;
let f = value & 0x03FF;
if (e == 0) {
f = 0.00006103515625 * (f / 1024);
}
else if (e == 0x1F) {
f = f ? NaN : Infinity;
}
else {
f = DataView.__float16_pow[e] * (1 + (f / 1024));
}
return value & 0x8000 ? -f : f;
};
DataView.__float16_pow = {
1: 1/16384, 2: 1/8192, 3: 1/4096, 4: 1/2048, 5: 1/1024, 6: 1/512, 7: 1/256, 8: 1/128,
9: 1/64, 10: 1/32, 11: 1/16, 12: 1/8, 13: 1/4, 14: 1/2, 15: 1, 16: 2,
17: 4, 18: 8, 19: 16, 20: 32, 21: 64, 22: 128, 23: 256, 24: 512,
25: 1024, 26: 2048, 27: 4096, 28: 8192, 29: 16384, 30: 32768, 31: 65536
};
}
if (!DataView.prototype.setFloat16) {
DataView.prototype.setFloat16 = function(byteOffset, value, littleEndian) {
DataView.__float16_float[0] = value;
value = DataView.__float16_int[0];
const s = (value >>> 16) & 0x8000;
const e = (value >>> 23) & 0xff;
const f = value & 0x7fffff;
const v = s | DataView.__float16_base[e] | (f >> DataView.__float16_shift[e]);
this.setUint16(byteOffset, v, littleEndian);
};
DataView.__float16_float = new Float32Array(1);
DataView.__float16_int = new Uint32Array(DataView.__float16_float.buffer, 0, DataView.__float16_float.length);
DataView.__float16_base = new Uint32Array(256);
DataView.__float16_shift = new Uint32Array(256);
for (let i = 0; i < 256; ++i) {
let e = i - 127;
if (e < -27) {
DataView.__float16_base[i] = 0x0000;
DataView.__float16_shift[i] = 24;
}
else if (e < -14) {
DataView.__float16_base[i] = 0x0400 >> -e - 14;
DataView.__float16_shift[i] = -e - 1;
}
else if (e <= 15) {
DataView.__float16_base[i] = e + 15 << 10;
DataView.__float16_shift[i] = 13;
}
else if (e < 128) {
DataView.__float16_base[i] = 0x7c00;
DataView.__float16_shift[i] = 24;
}
else {
DataView.__float16_base[i] = 0x7c00;
DataView.__float16_shift[i] = 13;
}
}
}
if (!DataView.prototype.getBits) {
DataView.prototype.getBits = function(offset, bits /*, signed */) {
offset = offset * bits;
const available = (this.byteLength << 3) - offset;
if (bits > available) {
throw new RangeError();
}
let value = 0;
let index = 0;
while (index < bits) {
const remainder = offset & 7;
const size = Math.min(bits - index, 8 - remainder);
value <<= size;
value |= (this.getUint8(offset >> 3) >> (8 - size - remainder)) & ~(0xff << size);
offset += size;
index += size;
}
return value;
};
}
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
因为 它太大了无法显示 source diff 。你可以改为 查看blob
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册