提交 985489c9 编写于 作者: B BingBlog

scalars frontend basically done

上级 5c260bba
# API
## runs
current runs
url: /data/runs
response:
```
["train", "test"]
```
## logdir
url: /data/logdir
response:
```
{
"logdir": "./tmp/tensorflow/mnist/logs/mnist_with_summaries/"
}
```
## data/plugins_listing
url: data/plugins_listing
\ No newline at end of file
此差异已折叠。
/**
* get mock data
*
* @param {string} path request path
* @param {Object} queryParam query params
* @param {Object} postParam post params
* @return {Object}
*/
module.exports = function (path, queryParam, postParam) {
return {
// moock delay
_timeout: 0,
// mock http status
_status: 200,
// mock response data
_data: {
status: 0,
msg: 'SUCCESS',
data: {
"test": {
"layer2/biases/summaries/mean": {
"displayName": "layer2/biases/summaries/mean",
"description": ""
},
"layer1/weights/summaries/min": {
"displayName": "layer1/weights/summaries/min",
"description": ""
},
"layer2/biases/summaries/stddev_1": {
"displayName": "layer2/biases/summaries/stddev_1",
"description": ""
},
"layer1/biases/summaries/mean": {
"displayName": "layer1/biases/summaries/mean",
"description": ""
},
"layer1/weights/summaries/mean": {
"displayName": "layer1/weights/summaries/mean",
"description": ""
},
"dropout/dropout_keep_probability": {
"displayName": "dropout/dropout_keep_probability",
"description": ""
},
"layer1/weights/summaries/max": {
"displayName": "layer1/weights/summaries/max",
"description": ""
}, "layer2/weights/summaries/mean": {
"displayName": "layer2/weights/summaries/mean",
"description": ""
}, "layer2/weights/summaries/stddev_1": {
"displayName": "layer2/weights/summaries/stddev_1",
"description": ""
},
"layer2/weights/summaries/min": {
"displayName": "layer2/weights/summaries/min",
"description": ""
},
"layer1/biases/summaries/max": {
"displayName": "layer1/biases/summaries/max",
"description": ""
},
"layer2/weights/summaries/max": {
"displayName": "layer2/weights/summaries/max",
"description": ""
},
"accuracy_1": {
"displayName": "accuracy_1",
"description": ""
},
"layer2/biases/summaries/min": {
"displayName": "layer2/biases/summaries/min",
"description": ""
},
"cross_entropy_1": {
"displayName": "cross_entropy_1",
"description": ""
},
"layer2/biases/summaries/max": {
"displayName": "layer2/biases/summaries/max",
"description": ""
},
"layer1/weights/summaries/stddev_1": {
"displayName": "layer1/weights/summaries/stddev_1",
"description": ""
},
"layer1/biases/summaries/stddev_1": {
"displayName": "layer1/biases/summaries/stddev_1",
"description": ""
},
"layer1/biases/summaries/min": {
"displayName": "layer1/biases/summaries/min",
"description": ""
}
},
"train": {
"layer2/biases/summaries/mean": {
"displayName": "layer2/biases/summaries/mean",
"description": ""
},
"layer1/weights/summaries/min": {
"displayName": "layer1/weights/summaries/min",
"description": ""
},
"layer2/biases/summaries/stddev_1": {
"displayName": "layer2/biases/summaries/stddev_1",
"description": ""
},
"layer1/biases/summaries/mean": {
"displayName": "layer1/biases/summaries/mean",
"description": ""
},
"layer1/weights/summaries/mean": {
"displayName": "layer1/weights/summaries/mean",
"description": ""
},
"dropout/dropout_keep_probability": {
"displayName": "dropout/dropout_keep_probability",
"description": ""
},
"layer1/weights/summaries/max": {
"displayName": "layer1/weights/summaries/max",
"description": ""
},
"layer2/weights/summaries/mean": {
"displayName": "layer2/weights/summaries/mean",
"description": ""
},
"layer2/weights/summaries/stddev_1": {
"displayName": "layer2/weights/summaries/stddev_1",
"description": ""
},
"layer2/weights/summaries/min": {
"displayName": "layer2/weights/summaries/min",
"description": ""
},
"layer1/biases/summaries/max": {
"displayName": "layer1/biases/summaries/max",
"description": ""
},
"layer2/weights/summaries/max": {
"displayName": "layer2/weights/summaries/max",
"description": ""
},
"accuracy_1": {
"displayName": "accuracy_1",
"description": ""
},
"layer2/biases/summaries/min": {
"displayName": "layer2/biases/summaries/min",
"description": ""
},
"cross_entropy_1": {
"displayName": "cross_entropy_1",
"description": ""
},
"layer2/biases/summaries/max": {
"displayName": "layer2/biases/summaries/max",
"description": ""
},
"layer1/weights/summaries/stddev_1": {
"displayName": "layer1/weights/summaries/stddev_1",
"description": ""
},
"layer1/biases/summaries/stddev_1": {
"displayName": "layer1/biases/summaries/stddev_1",
"description": ""
},
"layer1/biases/summaries/min": {
"displayName": "layer1/biases/summaries/min",
"description": ""
}
}
}
}
};
};
/**
* frontend mock data
* get mock data
*
* @param {string} path request path
* @param {Object} queryParam params
* @param {Object} postParam post post params
* @param {Object} queryParam query params
* @param {Object} postParam post params
* @return {Object}
*/
module.exports = function (path, queryParam, postParam) {
return {
// delay
// moock delay
_timeout: 0,
// http code status
// mock http status
_status: 200,
// response data
// mock response data
_data: {
// 0 for sucsuss, others for error
status: 0,
// error msg
msg: '',
data: ''
msg: 'SUCCESS',
data: ["train", "test", "model"]
}
};
};
......@@ -17,9 +17,11 @@
},
"dependencies": {
"axios": "^0.16.1",
"echarts": "^3.8.5",
"file-saver": "^1.3.3",
"lodash": "^4.17.4",
"normalize.css": "^6.0.0",
"qs": "^6.5.1",
"san": "3.2.3",
"san-mui": "^1.0.4",
"san-router": "^1.1.1",
......@@ -45,7 +47,7 @@
"css-loader": "^0.28.0",
"express": "^4.16.2",
"extract-text-webpack-plugin": "^2.1.0",
"fecs": "^1.5.2",
"fecs": "^1.5.3",
"file-loader": "^0.11.1",
"friendly-errors-webpack-plugin": "^1.6.1",
"glob": "^7.1.1",
......
<template>
<div id="app">
<ui-app-menu
on-item-click="menuChange($event)"
></ui-app-menu>
<div id="content-container" class="visual-dl-content-container">
<div id="app-content" class="visual-dl-app-content">
<div id="content"></div>
</div>
</div>
</div>
</template>
<script>
import AppMenu from './common/ui/AppMenu';
import {router} from 'san-router';
import {routeTo} from './common/util/routeTo';
export default {
components: {
'ui-app-menu': AppMenu
},
initData() {
return {};
},
attached() {
router.start();
},
menuChange({value, url, title}) {
routeTo(url);
}
};
</script>
<style lang="stylus">
// @import './style/variables';
// +prefix-classes(prefix)
</style>
import XLSX from 'xlsx';
import FileSaver from 'file-saver';
// const JSON_TO_SHEET = XLSX.utils.json_to_sheet;
const aoaToSheet = XLSX.utils.aoa_to_sheet;
const saveAs = FileSaver.saveAs;
function s2ab(s) {
......@@ -50,3 +49,22 @@ export const generateXLSXandAutoDownload = function (data, name) {
name + '.xlsx' || 'sheetjs.xlsx'
);
};
/**
* download json
*
* @desc download json
* @param {Array} data the data for the xlsx
* @param {string} name filename
*/
export const generateJsonAndDownload = function (data, name) {
saveAs(
new Blob(
[s2ab(JSON.stringify(data, null, ' '))],
{
type: 'application/octet-stream'
}
),
name + '.json' || 'json.json'
);
};
<template>
<div class="visual-dl-app-menu">
<san-appbar title="VisualDL">
<san-menu slot="right">
<san-menu-item
san-for="item in items"
class="{{selected === item.value ? 'sm-menu-item-selected' : ''}}"
on-click="handleItemClick(item)"
title="{{item.title}}" />
</san-menu>
</san-appbar>
</div>
</template>
<script>
import Appbar from 'san-mui/AppBar';
import {MenuItem, Menu} from 'san-mui/Menu';
export default {
components: {
'san-appbar': Appbar,
'san-menu': Menu,
'san-menu-item': MenuItem
},
initData() {
return {
selected: '1',
items: [
{
value: '1',
url: '/scalars',
title: 'SCALARS'
},
{
value: '2',
url: '/image',
title: 'IMAGES'
}
]
};
},
handleItemClick(item) {
this.data.set('selected', item.value);
this.fire('item-click', item);
}
};
</script>
<style lang="stylus">
@import '../../style/variables';
+prefix-classes(prefix)
.app-menu
width 100%
.visual-dl-app-menu
.sm-appbar-title
flex none
.sm-appbar-right
width 100%
.sm-menu
width 100%
height 100%
display flex
flex-direction row
.sm-menu-item-selected
background #e4e4e4
</style>
<template>
<div class="visual-dl-charts">
<div class="visual-dl-chart-box" style="{{computedStyle}}">
</div>
<div class="visual-dl-chart-actions">
<ui-dropdown-menu
hintText="download type"
items="{{runsItems}}"
value="{=downloadType=}"
/>
<san-button on-click="handleDownLoad">
<san-icon>file_download</san-icon>
</san-button>
</div>
</div>
</template>
<script>
import Button from 'san-mui/Button';
import Icon from 'san-mui/Icon';
import DropDownMenu from '../../../common/ui/DropDownMenu';
import {generateJsonAndDownload} from '../../../common/fun/downLoadFile';
import echarts from 'echarts';
import axios from 'axios';
import {getPluginScalarsScalars} from '../../../service';
export default {
components: {
'ui-dropdown-menu': DropDownMenu,
'san-button': Button,
'san-icon': Icon
},
computed: {
computedStyle() {
let width = this.data.get('width');
let height = this.data.get('height');
return 'height:' + height + 'px;'
+ 'width:' + width + 'px;';
}
},
initData() {
return {
width: 400,
height: 300,
// line config
options: {},
data: [
{
name: 'train',
value: []
}
],
// choose run type for download file
downloadType: ''
};
},
inited() {
this.watch('runsItems', val => {
this.initDownloadType();
});
},
attached() {
let tagInfo = this.data.get('tagInfo');
this.initCharts(tagInfo);
},
initDownloadType() {
let runsItems = this.data.get('runsItems') || [];
if (runsItems.length === 0) {
return;
}
this.data.set('downloadType', runsItems.find((item, index) => index === 0).value);
},
initCharts(tagInfo) {
this.createChart();
this.setChartsOptions(tagInfo);
this.setChartsData(tagInfo);
},
createChart() {
let el = this.el.getElementsByClassName('visual-dl-chart-box')[0];
this.myChart = echarts.init(el);
},
setChartsOptions({tagList, tag}) {
let seriesOption = tagList.map(item => {
return {
name: item.run,
type: 'line',
showSymbol: false,
hoverAnimation: false,
data: [],
smooth: true
};
});
let legendOptions = tagList.map(item => item.run);
let option = {
title: {
text: tag,
textStyle: {
fontSize: '12'
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
animation: false
},
position: [10, 300]
},
toolbox: {
show: true,
showTitle: true,
feature: {
dataZoom: {},
restore: {},
saveAsImage: {}
},
left: 40,
top: 270
},
legend: {
data: legendOptions,
top: 30
},
grid: {
top: 70,
bottom: 50
},
xAxis: [
{
type: 'value',
boundaryGap: false
},
{
type: 'value',
boundaryGap: false
}
],
yAxis: {
type: 'value',
boundaryGap: [0, '100%'],
min(value) {
return value.min;
},
max(value) {
return value.max;
},
axisLabel: {
formatter(value, index) {
// keep 0.11111 to 0.111
return value.toString().slice(0, 5);
}
}
},
series: seriesOption
};
this.myChart.setOption(option);
},
setChartsData({tagList, tag}) {
let requestList = tagList.map(item => {
let params = {
run: item.run,
tag: tag
};
return getPluginScalarsScalars(params);
});
axios.all(requestList).then(resArray => {
let seriesData = resArray.map(res => {
return {
data: res.data,
encode: {
// map 1 to xAixs。
x: [1],
// map 2 to yAixs。
y: [2]
}
};
});
this.myChart.setOption({
series: seriesData
});
});
},
getChartOptions() {
return this.myChart.getOption() || {};
},
handleDownLoad() {
let options = this.getChartOptions();
let series = options.series || [];
let downloadType = this.data.get('downloadType');
let sery = series.find(item => item.name === downloadType) || {};
let tagInfo = this.data.get('tagInfo');
let fileName = tagInfo.tag.replace(/\//g, '-');
generateJsonAndDownload(sery.data, fileName);
}
};
</script>
<style lang="stylus">
.visual-dl-charts
float left
.visual-dl-chart-actions
.sm-form-item
width 300px
display inline-block
</style>
<template>
<div class="sm-form-item">
<label class="label">{{label}}</label>
<div class="group-box">
<san-checkbox
san-if="showAll"
class="checkbox-all"
label="全选"
value="all"
on-change="handleAllChange($event)"
checked="{{All}}"
></san-checkbox>
<div class="san-form-check-group">
<san-checkbox
san-for="item in items"
label="{{item.name}}"
value="{{item.value}}"
disabled="{{item.disabled}}"
checked="{{value}}"
on-change="handleChange($event, item)"
></san-checkbox>
</div>
</div>
</div>
</template>
<script>
import Checkbox from 'san-mui/Checkbox';
import {DataTypes} from 'san';
export default {
components: {
'san-checkbox': Checkbox
},
dataTypes: {
items: DataTypes.array,
value: DataTypes.array
},
initData() {
return {
allValue: [],
items: {},
value: [],
All: [],
showAll: false
};
},
computed: {
allValue() {
let items = this.data.get('items') || [];
return items.map(item => item.value);
}
},
attached() {
this.jugdeAll();
this.watch('value', val => {
this.dispatch('UI:form-item-change');
this.jugdeAll();
});
},
handleChange(e, item) {
let checked = e.target.checked;
let valueItem = item.value;
if (checked) {
this.addValue(valueItem);
}
else {
this.removeValue(valueItem);
}
this.dispatch('UI:form-item-change');
this.jugdeAll();
},
handleAllChange(e) {
let checked = e.target.checked;
let allValue = this.data.get('allValue') || [];
if (checked) {
this.data.set('value', allValue.slice());
this.fire('valueChange', allValue.slice());
}
else {
this.data.set('value', []);
this.fire('valueChange', []);
}
this.dispatch('UI:form-item-change');
},
addValue(itemValue) {
let value = this.data.get('value') || [];
if (!value.includes(itemValue)) {
value.push(itemValue);
this.data.set('value', value.slice());
this.fire('valueChange', value.slice());
}
},
removeValue(itemValue) {
let value = this.data.get('value') || [];
if (value.includes(itemValue)) {
let index = value.indexOf(itemValue);
value.splice(index, 1);
this.data.set('value', value.slice());
this.fire('valueChange', value.slice());
}
},
jugdeAll() {
let allValue = this.data.get('allValue') || [];
let value = this.data.get('value') || [];
let isAll = allValue.every(val => value.includes(val));
if (isAll) {
this.data.set('All', ['all']);
}
else {
this.data.set('All', []);
}
}
};
</script>
<template>
<div class="sm-form-item">
<label class="label">{{label}}</label>
<san-drop-down-menu
error="{{error}}"
disabled="{{disabled}}"
value="{=value=}"
maxHeight="{{200}}"
autoWidth="{{false}}">
<san-menu-item
s-for="item in items"
on-change="menuItemChange(item)"
value="{{item.value}}"
label="{{item.name}}" />
</sm-drop-down-menu>
</div>
</template>
<script>
import {DataTypes} from 'san';
import {MenuItem, DropDownMenu} from 'san-mui/Menu';
export default {
components: {
'san-menu-item': MenuItem,
'san-drop-down-menu': DropDownMenu
},
dataTypes: {
value: DataTypes.string,
items: DataTypes.array,
disabled: DataTypes.bool
},
menuItemChange(item) {
let value = item.value;
this.fire('valueChange', value);
this.dispatch('UI:form-item-change');
},
initData() {
return {
value: '',
items: []
};
}
};
</script>
<template>
<div class="visaul-dl-expand-panel">
<h3
class="visaul-dl-expand-head"
on-click="handleHeadClick()"
>
<span>{{title}}</span>
</h3>
<div
style="display:{{displayStyle}}"
class="visaul-dl-expand-panel-content"
>
<slot></slot>
</div>
</div>
</template>
<script>
export default {
computed: {
displayStyle() {
let isShow = this.data.get('isShow');
return isShow ? 'block' : 'none';
}
},
initData() {
return {
isShow: false
};
},
handleHeadClick() {
this.toogleShow();
},
toogleShow() {
let isShow = this.data.get('isShow');
this.data.set('isShow', !isShow);
}
};
</script>
<style lang="stylus">
.visaul-dl-expand-panel
.visaul-dl-expand-head
border solid 1px #e4e4e4
line-height 50px
height 50px
padding 0 20px
cursor pointer
.visaul-dl-expand-panel-content
padding 0 20px
.visaul-dl-expand-panel-content:after
content: "";
clear: both;
display: block;
</style>
import NotificationItem from './NotificationItem';
let seed = 1;
// instances
const instances = [];
/**
* caculate top dist
*
* @param {number} topDist top dist
* @return {number} final top dist
*/
function calcTopDist(topDist = 16) {
for (let i = 0, len = instances.length; i < len; i++) {
topDist += (instances[i].vm.el.offsetHeight + 16);
}
return topDist;
}
/**
* Notification main func
*
* @param {Object} options options
* @return {NotificationItem} instance
*/
const notification = function (options = {}) {
options.top = calcTopDist(options.offset);
const {onClose, onClick} = options;
delete options.onClick;
delete options.onClose;
delete options.offset;
const instance = {
vm: new NotificationItem({
data: options
}),
id: `notification_${seed++}`
};
if (typeof onClick === 'function') {
instance.vm.on('itemClick', onClick);
}
instance.vm.on('close', () => {
notification.close(instance.id, onClose);
});
instance.vm.attach(document.body);
instances.push(instance);
return instance.vm;
};
/**
* close
*
* @param {string} id instance id
* @param {Function} onClose cusmtom func
*/
notification.close = function (id, onClose) {
let index;
let removedHeight;
let len = instances.length;
for (let i = 0; i < len; i++) {
if (id === instances[i].id) {
if (typeof onClose === 'function') {
onClose(instances[i]);
}
index = i;
removedHeight = instances[i].vm.el.offsetHeight;
// distroy instance
instances[i].vm.dispose();
instances[i] = null;
// reomve instance fron instances
instances.splice(i, 1);
break;
}
}
// change the left notification's height
if (len > 1) {
for (let i = index; i < len - 1; i++) {
instances[i].vm.el.style.top = `${parseInt(instances[i].vm.el.style.top, 10) - removedHeight - 16}px`;
}
}
};
// fout type func
['success', 'warning', 'info', 'error'].forEach(type => {
notification[type] = options => {
if (typeof options === 'string') {
options = {
message: options
};
}
options = options || {};
options.type = type;
return notification(options);
};
});
export default notification;
## Notification
### base usage
```san Notification
<template>
<div>
<san-button
variants="raised info"
on-click="handleClick01">
auto close
</san-button>
<san-button
variants="raised info"
on-click="handleClick02">
no auto close
</san-button>
</div>
</template>
<script>
import Notification from '../src/Notification';
import '../src/Notification/Notification.styl';
import Button from '../src/Button';
import '../src/Button/Button.styl';
export default {
components: {
'san-button': Button
},
// auto close
handleClick01() {
Notification({
message: 'welcome',
title: 'success'
})
},
// no auto close
handleClick02() {
Notification({
message: 'welcome',
title: 'success',
duration: 0
})
}
}
</script>
```
### with icon
```san Notification
<template>
<div>
<san-button
variants="raised info"
on-click="handleClick01">
success
</san-button>
<san-button
variants="raised info"
on-click="handleClick02">
warning
</san-button>
<san-button
variants="raised info"
on-click="handleClick03">
info
</san-button>
<san-button
variants="raised info"
on-click="handleClick04">
error
</san-button>
<san-button
variants="raised info"
on-click="handleClick05">
simplify
</san-button>
</div>
</template>
<script>
import Notification from '../src/Notification';
import '../src/Notification/Notification.styl';
import Button from '../src/Button';
import '../src/Button/Button.styl';
export default {
components: {
'san-button': Button
},
handleClick01() {
Notification({
message: 'welcome',
title: 'success',
type: 'success'
})
},
handleClick02() {
Notification({
message: 'warning',
title: 'warning',
type: 'warning'
})
},
handleClick03() {
Notification({
message: 'info',
title: 'info',
type: 'info'
})
},
handleClick04() {
Notification({
message: '不可以攻击己方水晶',
title: '错误',
type: 'error'
})
},
handleClick05() {
Notification.success('simplify')
}
}
</script>
```
### width offset
```san Notification
<template>
<div>
<san-button
variants="raised info"
on-click="handleClick01">
width offset
</san-button>
</div>
</template>
<script>
import Notification from '../src/Notification';
import '../src/Notification/Notification.styl';
import Button from '../src/Button';
import '../src/Button/Button.styl';
export default {
components: {
'san-button': Button
},
handleClick01() {
Notification({
message: 'offset 100px',
title: 'success',
offset:100
})
}
}
</script>
```
## API
### Props
| name | type | fefault | desc |
| --- | --- | --- | --- |
| title | String | | title |
| message | String | | content |
| customClass |String| | custom class |
| offset | Number | | offset to the top |
| onClick | Function | | on-click callback |
| onClose | Function | | on-close callback |
| duration | Number | 3000 | duration |
| type | String | | include success,error,warning,info, others not use |
.sm-notification
position: fixed
display: flex
z-index: 9999
right: 16px
top 16px
width: 330px
padding: 20px
box-sizing border-box
border-radius 2px
background-color #fff
box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
transition: all .4s;
overflow: hidden
&.state-hidden
transform translateX(346px)
&.state-open
transform translateX(0)
&-group
color: #000
&-type
margin auto 0
margin-right 20px
&.type-success
color: #13ce66
&.type-info
color: #50bfff
&.type-warning
color: #f7ba2a
&.type-error
color: #ff4949
&-title
color: #1f2d3d
font-size 16px
&-content
color: #8d9baf
padding: 5px 0
&-close
position: absolute
cursor: pointer
right 20px
top: 20px
&-btn
transition all .3s
color #bfcbd9
&:hover
color: #777F8C
import {Component} from 'san';
import Icon from 'san-mui/Icon';
const typeMap = {
success: 'check_circle',
info: 'info',
warning: 'error',
error: 'cancel'
};
export default class NotificationItem extends Component {
static template = `
<div
class="sm-notification {{customClass}} {{closed ? 'state-hidden' : 'state-open'}}"
style="top: {{top}}px"
on-mouseenter="clearTimer"
on-mouseleave="startTimer"
on-click="handleClick">
<san-icon
san-if="type"
class="sm-notification-type type-{{type}}"
size="50">{{iconType}}</san-icon>
<div class="sm-notification-group" >
<p class="sm-notification-title">{{title}}</p>
<div class="sm-notification-content">{{message}}</div>
<div on-click="close($event)" class="sm-notification-close">
<san-icon size="20" class="sm-notification-close-btn">close</san-icon>
</div>
</div>
</div>
`;
initData() {
return {
title: '',
message: '',
duration: 3000,
closed: true
};
}
static components = {
'san-icon': Icon
};
static computed = {
iconType() {
return typeMap[this.data.get('type')];
}
};
handleClick() {
this.fire('itemClick');
}
/**
* close
*
* @param {Object} e event Object
*/
close(e) {
if (e) {
e.stopPropagation();
}
this.data.set('closed', true);
this.el.addEventListener('transitionend', () => {
this.fire('close');
});
}
/**
* clear timer
*/
clearTimer() {
clearTimeout(this.timer);
}
/**
* start timet
*/
startTimer() {
const duration = this.data.get('duration');
if (duration > 0) {
this.timer = setTimeout(() => {
if (!this.closed) {
this.close();
}
}, duration);
}
}
attached() {
if (this.el.parentNode !== document.body) {
document.body.appendChild(this.el);
}
requestAnimationFrame(() => {
this.data.set('closed', false);
});
this.startTimer();
}
detached() {
this.clearTimer();
}
}
import Notification from './Notification';
import NotificationItem from './NotificationItem';
export {
NotificationItem,
Notification as default
};
<template>
<div class="sm-form-item">
<label class="label">{{label}}</label>
<div class="group-box">
<san-radio
on-change="handleChange($event)"
san-for="item in items"
label="{{item.name}}"
value="{{item.value}}"
disabled="{{item.disabled}}"
checked="{=value=}"
></san-radio>
</div>
</div>
</template>
<script>
import Radio from 'san-mui/Radio';
import {DataTypes} from 'san';
export default {
components: {
'san-radio': Radio
},
dataTypes: {
items: DataTypes.array,
value: DataTypes.string
},
initData() {
return {
items: {},
value: [],
label: ''
};
},
attached(value) {
this.watch('value', val => {
this.dispatch('UI:form-item-change', val);
});
},
handleChange(val) {
this.fire('valueChange', val);
}
};
</script>
\ No newline at end of file
<template>
<div class="sm-form-item">
<label class="label">{{label}}</label>
<div class="input-box">
<san-slider
on-change="handleSlideChange($event)"
value="{{value}}"
min="{{min}}"
max="{{max}}"
step="{{step}}"
/>
<san-input-number
min="{{min}}"
max="{{max}}"
step="{{step}}"
value="{=value=}"
on-change="handlerChange($event)"
size="small">
</san-input-number>
</div>
</div>
</template>
<script>
import Slider from 'san-mui/Slider';
import InputNumber from 'san-mui/InputNumber';
export default {
components: {
'san-slider': Slider,
'san-input-number': InputNumber
},
initData() {
return {
value: '0',
label: '',
max: 0,
min: 0,
step: 0
};
},
handleSlideChange(val) {
this.data.set('value', val.toString());
}
};
</script>
<style lang="stylus">
.sm-form-item
.input-box
margin-top 10px
.sm-slider
display inline-block
margin-right 20px
margin-top 4px
float left
width 100px
.sm-slider-thumb
transform translate(0, -50%)
.sm-inputNumber
width 130px
display inline-block
</style>
// initual style
body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, dl, dt, dd, ul, ol, li, pre, form, fieldset, legend, button, input, textarea, th, td { margin:0; padding:0; } body, button, input, select, textarea { font:12px/1.5tahoma, arial, \5b8b\4f53; } h1, h2, h3, h4, h5, h6{ font-size:100%; } address, cite, dfn, em, var { font-style:normal; } code, kbd, pre, samp { font-family:couriernew, courier, monospace; } small{ font-size:12px; } ul, ol { list-style:none; } a { text-decoration:none; } a:hover { text-decoration:underline; } sup { vertical-align:text-top; } sub{ vertical-align:text-bottom; } legend { color:#000; } fieldset, img { border:0; } button, input, select, textarea { font-size:100%; } table { border-collapse:collapse; border-spacing:0; }
@import './Notification/Notification.styl'
// modify some styles of san-mui
.sm-icon
width 100%
height 100%
overflow hidden
.sm-radio,
.sm-checkbox
margin-top 2px
margin-bottom 4px
margin-right 10px
float left
width 100%
.sm-radio-wrapper,
.sm-checkbox-wrapper
justify-content start
.sm-radio-label,
.sm-checkbox-label
font-size 12px
.sm-radio-icon
.sm-checkbox-icon
width 20px
height 20px
margin-right 10px
.sm-radio-ripple-wrapper
.sm-checkbox-ripple-wrapper
width 20px
height 20px
top 0px
left 0px
.sm-checkbox-svg-icon,
.sm-radio-svg-icon
width 20px
height 20px
.sm-checkbox-icon-uncheck
.sm-radio-icon-uncheck
color #58666e
.sm-radio-label,
.sm-checkbox-label
color #58666e
.sm-dropdown-menu
.sm-dropdown-menu-icon
.sm-icon
color #58666e
.sm-popover-content
.sm-menu-item
line-height 30px
font-size 14px
color #58666e
.sm-menu-item.state-selected
color #ff4081
.sm-text-field-input
color #58666e
.sm-form-item
margin-top 10px
.label
color rgba(0,0,0,0.54)
font-size 12px
line-height 20px
.group-box
overflow hidden
.collapse-transition
transition: 0.3s height ease-in-out, 0.3s padding-top ease-in-out, 0.3s padding-bottom ease-in-out;
.sm-pagination
margin 30px 0
font-size 12px
\ No newline at end of file
import axios from 'axios';
import qs from 'qs';
import Notification from '../ui/Notification';
const STATUS = 'status';
const STATUSINFO = 'msg';
const instance = axios.create({
baseURL: '/',
timeout: 30000
});
const responseErrorStatus = response => {
const data = response.data;
if (data[STATUS] !== 0) {
Notification.error(data[STATUSINFO]);
return Promise.reject(data);
}
return data;
};
const responseNetError = error => {
Notification.error('net error');
return Promise.reject(error);
};
// post from
const formInstance = axios.create({
baseURL: '/',
timeout: 3000,
transformRequest: [data => qs.stringify(data)],
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json,application/vnd.ms-excel'
}
});
formInstance.interceptors.response.use(responseErrorStatus, responseNetError);
instance.interceptors.response.use(responseErrorStatus, responseNetError);
export const makeService = (url, opt = {method: 'get'}) => (params = {}) => {
if (opt.method === 'delete' || opt.method === 'get') {
params = {params};
}
return instance[opt.method](url, params);
};
export const makeFormService = (url, method = 'post') => (params = {}) => formInstance[method](url, params);
import {router} from 'san-router';
export function routeTo(url, params = {}) {
let paramsArr = Object.keys(params).map(key => `${key}=${params[key]}`);
let urlParams = (url.indexOf('?') > -1 ? '&' : '?') + paramsArr.join('&');
router.locator.redirect(urlParams.length > 1 ? `${url}${urlParams}` : url);
}
......@@ -2,7 +2,7 @@
<article>
<h1>
welcome
{{text}}
</h1>
</article>
......@@ -12,7 +12,11 @@
<script>
export default {
initData() {
return {
text: 'welcome'
};
}
};
</script>
import {router} from 'san-router';
import HomePage from './Home';
router.add({
target: '#content',
rule: '/home',
rule: '/',
Component: HomePage
});
import 'normalize.css/normalize.css';
import 'san-mui/index.css';
let App = require('./App');
import './common/ui/ui-common.styl';
import './home/index';
import './scalars/index';
import App from './App';
new App({
data: {
titleName: 'VisualDL'
......
<template>
<div class="visual-dl-scalar-container">
<div class="visual-dl-scalar-left">
<div class="visual-dl-scalar-config-container">
<ui-config
runsItems="{{runsItems}}"
config="{=config=}"
></ui-config>
</div>
</div>
<div class="visual-dl-scalar-right">
<ui-chart-page
config="{{config}}"
runsItems="{{runsItems}}"
tagList="{{filteredTagsList}}"
title="Tags matching {{config.groupNameReg}}"
></ui-chart-page>
<ui-chart-page
san-for="item in groupedTags"
config="{{config}}"
runsItems="{{runsItems}}"
tagList="{{item.tags}}"
title="{{item.group}}"
></ui-chart-page>
</div>
</div>
</template>
<script>
import {getPluginScalarsTags, getRuns} from '../service';
import config from './ui/config';
import chartPage from './ui/chartPage';
import {debounce, flatten, uniq} from 'lodash';
export default {
components: {
'ui-config': config,
'ui-chart-page': chartPage
},
computed: {
runsItems() {
let runsArray = this.data.get('runsArray') || [];
return runsArray.map(item => {
return {
name: item,
value: item
};
});
},
tagsList() {
let tags = this.data.get('tags');
let runs = Object.keys(tags);
let tagsArray = runs.map(run => Object.keys(tags[run]));
let allUniqTags = uniq(flatten(tagsArray));
// get the data for every chart
return allUniqTags.map(tag => {
let tagList = runs.map(run => {
return {
run,
tag: tags[run][tag]
};
});
return {
tagList,
tag,
group: tag.split('/')[0]
};
});
},
groupedTags() {
let tagsList = this.data.get('tagsList') || [];
// put data in group
let groupData = {};
tagsList.forEach(item => {
let group = item.group;
if (groupData[group] === undefined) {
groupData[group] = [];
groupData[group].push(item);
}
else {
groupData[group].push(item);
}
});
// to array
let groups = Object.keys(groupData);
return groups.map(group => {
return {
group,
tags: groupData[group]
};
});
}
},
initData() {
return {
runsArray: [],
tags: [],
config: {
groupNameReg: '.*',
smoothing: '0.5',
horizontal: '1',
sortingMethod: '2',
link: [],
chart: []
}
};
},
inited() {
getPluginScalarsTags().then(({errno, data}) => {
this.data.set('tags', data);
// filter when inited
let groupNameReg = this.data.get('config.groupNameReg');
this.filterTagsList(groupNameReg);
});
getRuns().then(({errno, data}) => {
this.data.set('runsArray', data);
});
// need debounce, can't use computed
this.watch('config.groupNameReg', debounce(this.filterTagsList, 300));
},
filterTagsList(groupNameReg) {
let tagsList = this.data.get('tagsList') || [];
let regExp = new RegExp(groupNameReg);
let filtedTagsList = tagsList.filter(item => regExp.test(item.tag));
this.data.set('filteredTagsList', filtedTagsList);
}
};
</script>
<style lang="stylus">
@import '../style/variables';
+prefix-classes('visual-dl-scalar-')
.container
padding-left 300px
position relative
.left
width 280px
min-height 300px
border solid 1px #e4e4e4
position absolute
left 0
.right
width 100%
border solid 1px #e4e4e4
min-height 300px
padding 20px
</style>
import {router} from 'san-router';
import Scalar from './Scalars';
router.add({
target: '#content',
rule: '/scalars',
Component: Scalar
});
<template>
<div class="visaul-dl-chart-page">
<ui-expand-panel title="{{title}}">
<div class="visaul-dl-chart-box">
<ui-chart
san-for="tag in filteredTagList"
tagInfo="{{tag}}"
config="{{config}}"
runsItems="{{runsItems}}"
></ui-chart>
</div>
<ui-pagination
san-if="total > pageSize"
on-pageChange="handlePageChange($event)"
current="{{currentPage}}"
pageSize="{{pageSize}}"
total="{{total}}"
showSizeChanger="{{false}}"
/>
</ui-expand-panel>
</div>
</template>
<script>
import ExpandPanel from '../../common/ui/ExpandPanel';
import chart from '../../common/ui/Charts/chart';
import Pagination from 'san-mui/Pagination';
export default {
components: {
'ui-chart': chart,
'ui-expand-panel': ExpandPanel,
'ui-pagination': Pagination
},
computed: {
filteredTagList() {
let tagList = this.data.get('tagList') || [];
let currentPage = this.data.get('currentPage');
let pageSize = this.data.get('pageSize');
return tagList.slice((currentPage - 1) * pageSize, currentPage * pageSize);
},
total() {
let tagList = this.data.get('tagList') || [];
return tagList.length;
}
},
initData() {
return {
// current page
currentPage: 1,
// item per page
pageSize: 8
};
},
handlePageChange({pageNum}) {
this.data.set('currentPage', pageNum);
}
};
</script>
<style lang="stylus">
@import '../../style/variables';
+prefix-classes('visual-dl-')
.visaul-dl-chart-page
.visaul-dl-chart-box:after
content: "";
clear: both;
display: block;
</style>
<template>
<div class="visual-dl-scalar-config-com">
<san-text-field
hintText="input a tag group name to search"
label="Group name RegExp"
inputValue="{=config.groupNameReg=}"
/>
<ui-slider
label="Smoothing"
value="{=config.smoothing=}"
min="{{0}}"
max="{{1}}"
step="{{0.001}}"
/>
<ui-radio-group
label="Horizontal"
value="{{config.horizontal}}"
items="{{horizontalItems}}"
/>
<ui-dropdown-menu
label="Tooltip sorting method"
items="{{sortingMethodItems}}"
value="{=config.sortingMethod=}"
/>
<ui-checkbox-group
value="{=config.link=}"
items="{{lnksItems}}"
/>
<ui-checkbox-group
value="{=config.chart=}"
items="{{chartItems}}"
/>
<ui-checkbox-group
label="Runs"
items="{{runsItems}}"
/>
<san-button class="visual-dl-scalar-run-toggle" variants="raised secondery">Toggle All Runs</san-button>
</div>
</template>
<script>
import TextField from 'san-mui/TextField';
import Slider from '../../common/ui/Slider';
import RadioGroup from '../../common/ui/RadioGroup';
import DropDownMenu from '../../common/ui/DropDownMenu';
import CheckBoxGroup from '../../common/ui/CheckBoxGroup';
import Button from 'san-mui/Button';
export default {
components: {
'san-text-field': TextField,
'ui-slider': Slider,
'ui-radio-group': RadioGroup,
'ui-dropdown-menu': DropDownMenu,
'ui-checkbox-group': CheckBoxGroup,
'san-button': Button
},
initData() {
return {
config: {
groupName: 'aa',
smoothing: '0.5',
horizontal: '1',
sortingMethod: '2',
link: [],
chart: []
},
horizontalItems: [
{
name: 'Step',
value: '1'
},
{
name: 'Relative',
value: '2'
},
{
name: 'Wall',
value: '3'
}
],
sortingMethodItems: [
{
name: 'default',
value: '1'
},
{
name: 'descending',
value: '2'
},
{
name: 'ascending',
value: '3'
},
{
name: 'nearest',
value: '4'
}
],
runsItems: [],
lnksItems: [
{
value: '1',
name: 'Show data download links'
}
],
chartItems: [
{
value: '1',
name: 'Ignore outliers in chart scaling'
}
]
};
}
};
</script>
<style lang="stylus">
@import '../../style/variables';
+prefix-classes('visual-dl-scalar-')
.config-com
width 90%
margin 0 auto
.run-toggle
width 100%
margin-top 20px
</style>
import {makeService} from './common/util/http';
export const getPluginScalarsTags = makeService('/data/plugin/scalars/tags');
export const getRuns = makeService('/data/runs');
export const getPluginScalarsScalars = makeService('/data/plugin/scalars/scalars');
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>VisualDL</title>
......
......@@ -38,47 +38,23 @@ compiler.plugin('compilation', function (compilation) {
});
});
var context = [
'/example',
];
var proxypath = '';
var options = {
target: proxypath,
changeOrigin: true,
};
if (context.length) {
// app.use(proxyMiddleware(context, options));
app.use('/example', proxyMiddleware({
target: 'www.baidu.com',
changeOrigin: true,
}));
// autoresponse
let AutoresponseMatchs = ['data'];
let matchsReg = new RegExp(AutoresponseMatchs.join('\|'));
let excludeReg = /\.(html|js|map)$/;
let isAutoresponseRequest = (path) => {
return !excludeReg.test(path) && matchsReg.test(path);
}
app.use(autoresponse({
logLevel: 'debug',
root: path.dirname(__dirname),
rules: [
{
match: '/example/:id',
method: ['get']
}
],
post: {
match: function (reqPathName) {
return !/\.(html|js|map)$/.test(reqPathName) && /^\/(api)(.*)/.test(reqPathName);
}
},
delete: {
match: function () {
return true;
}
},
get: {
match: function (reqPathName) {
return !/\.(html|js|map)$/.test(reqPathName) && /^\/(api)(.*)/.test(reqPathName);
match: isAutoresponseRequest,
method: ['get', 'post', , 'delete']
}
}
]
}));
// serve webpack bundle output
......
<template>
<div id="app">
<san-appbar title="VisualDL">
<div class="visual-dl-user-info visual-dl-app-follow-list" slot="right"></div>
</san-appbar>
<div id="content-container" class="visual-dl-content-container">
<div id="app-content" class="visual-dl-app-content">
<div id="visual-dl-content"></div>
</div>
</div>
<div class="visual-dl-footer">
<div class="visual-dl-ftext"><a href="https://github.com/VisualDL/VisualDL" target="_blank" class="visual-dl-bluetext">GitHub</a></div>
<div class="visual-dl-ftext visual-dl-ftext3">VisualDL © MIT</div>
</div>
</div>
</template>
<script>
import Appbar from 'san-mui/AppBar';
export default {
components: {
'san-appbar': Appbar
},
initData() {
return {};
}
};
</script>
<style lang="stylus">
@import './style/variables';
+prefix-classes(prefix)
.footer
float: left;
position:relative;
width: 100%
background: #fff
padding-top: 0
margin-top: -2px
background #fff
border solid 1px #e4e4e4
.ftext
width: 30%
float: left
text-align: center
padding: 30px 0
font-size: 14px
.ftext3
margin-left: 5%
.bluetext
text-decoration: none
color: #398bfb
</style>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册