未验证 提交 a6b80b99 编写于 作者: J Jeff Wang 提交者: GitHub

Migrate from san to vue (#281)

* Initial checking to setup Vue basic requirements. (#264)

Add App.vue
Add AppMenu.vue

* Vue with app menu (#265)

* Add vuetify framework
Update AppMenu.vue to include the basic menu item.

* use name as key.

* Add major components (#266)

* Add vuetify framework
Update AppMenu.vue to include the basic menu item.

* use name as key.

* Add Scalars, Images, Histograms, Graph.

* Switch to use the stylus language for CSS

* Port functions from Scalars.san to Scalars.vue

* Add config vue (#267)

* Add vuetify framework
Update AppMenu.vue to include the basic menu item.

* use name as key.

* Add Config Vue

* Integrate chart to scalar with Vue (#268)

* Fix warnings from Vue compiler
* use default prop instead of duplicate data and prop
* use function instead of shorthand declaration with data

* refract naming style, fix scalar options

* Fix config UI

* Add chart and chart page and integrate to scalar

* update with varun and jeff's comments

* Add images config (#270)

* Add vuetify framework
Update AppMenu.vue to include the basic menu item.

* use name as key.

* Add the Config.vue for Images

* Add the CharPage.vue and Image.vue

* Hook up the config to the image.vue so that the image height and width can update

* Show Scalar Data and add ExpandPanel (#272)

* Watch tagInfo changes to update chart and fix tooltip issue

* Add expand panel and maintain isShow state in the panel

* Add Expand Panel in Image

* Add watch group name reg (#273)

* Add vuetify framework
Update AppMenu.vue to include the basic menu item.

* use name as key.

* Add Watch on the groupNameReg

* Allow the nav bar to persist the selected item style. (#274)

* Fix the incorrect pagination issue. (#276)

* Add the Graph.vue and the Config.vue for Graph tab (#277)

* Add the Histogram related vue files. (#278)

* Fix error message on charts (#279)

* Make sure to clear the chart before each use.

* Add key for v-for loop.

* Add theme and fix UI (#280)

-Apply theme color in Vuetify
-Add main.styl to override Vuetify default stylus variables
-Add relative path in web pack config to find variables.styl

* Fix multiple scalar issues: (#283)

* Fix multiple scalar issues:
-download link / outliers should take boolean value instead of array
-download link now show proper selector of runsItem and download button
-Use tag as key to prevent chart re-rendering
-Add missing label in scalar config and fix UI

* simply use index for key because scalar has different tag info structure from image and histogram

* Fix font size and reorganize css and stylus (#287)

* Fix comments

* placeholder index.js

* Fix issue where download data link points to empty run (#294)

-Dropdown should only display non empty runs
-Select first run by default
-Remove drop down if all are empty runs
-Refract download type to runItemForDownload
-Add train run and multiple tags in mock data

* Fix error when scalar data empty (#298)

-Error comes in when trying to access elements inside empty data
-When one of the runs has empty data, the chart will display nothing even other run has non empty data
-Fix by checking length of data before accessing
上级 330eb6a9
......@@ -19,15 +19,27 @@ module.exports = function (path, queryParam, postParam) {
data: {
"test": {
"layer1/Wx_plus_b/pre_activations":
{
"displayName": "layer1/Wx_plus_b/pre_activations",
"description": ""
},
{
"displayName": "layer1/Wx_plus_b/pre_activations",
"description": ""
},
"layer1/Wx_plus_b/pre_activations_1":
{
"displayName": "layer1/Wx_plus_b/pre_activations_1",
"description": ""
}
{
"displayName": "layer1/Wx_plus_b/pre_activations_1",
"description": ""
}
},
"train": {
"layer1/Wx_plus_b/pre_activations":
{
"displayName": "layer1/Wx_plus_b/pre_activations",
"description": ""
},
"layer1/Wx_plus_b/pre_activations_1":
{
"displayName": "layer1/Wx_plus_b/pre_activations_1",
"description": ""
}
}
}
}
......
......@@ -22,6 +22,20 @@ module.exports = function (path, queryParam, postParam) {
"displayName": "layer2/biases/summaries/mean",
"description": ""
}
},
"train": {
"layer2/biases/summaries/mean": {
"displayName": "layer2/biases/summaries/mean",
"description": ""
},
"layer2/biases/summaries/accuracy": {
"displayName": "layer2/biases/summaries/accuracy",
"description": ""
},
"layer2/biases/summaries/cost": {
"displayName": "layer2/biases/summaries/cost",
"description": ""
}
}
}
}
......
......@@ -27,6 +27,9 @@
"san-router": "^1.1.1",
"san-store": "^1.0.1",
"san-update": "^2.1.0",
"vue": "^2.5.2",
"vue-router": "^3.0.1",
"vuetify": "^0.17.7",
"xlsx": "^0.11.3"
},
"devDependencies": {
......@@ -72,6 +75,10 @@
"webpack-dev-server": "^2.4.2",
"webpack-hot-middleware": "^2.19.1",
"webpack-merge": "^4.1.0",
"yargs": "^8.0.2"
"yargs": "^8.0.2",
"vue-jest": "^1.0.2",
"vue-loader": "^13.3.0",
"vue-style-loader": "^3.0.1",
"vue-template-compiler": "^2.5.2"
}
}
<template>
<div id="app">
<v-app>
<AppMenu :initialRoute="initialRoute">
</AppMenu>
<router-view></router-view>
</v-app>
</div>
</template>
<script>
import AppMenu from './common/component/AppMenu'
export default {
name: 'App',
components: {
AppMenu
},
data() {
return {
initialRoute: "scalars"
}
},
created() {
if (location.hash && location.hash != '#/') {
this.initialRoute = /(\#\/)(\w*)([?|&]{0,1})/.exec(location.hash)[2];
}
else {
location.hash = '#/scalars';
}
}
}
</script>
<style lang="stylus">
@import '~style/variables';
@import '../style/main';
@import url('https://fonts.googleapis.com/css?family=Merriweather+Sans:400,700');
#app {
font-family: 'Merriweather Sans', Helvetica, Arial, sans-serif;
font-weight: bold;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: $-content-text-color;
}
+prefix-classes('visual-dl-page-')
.container
padding-right 300px
position relative
background $-left-background-color
.left
width 100%
overflow scroll
border solid 1px $-left-border-color
background $-left-border-color
min-height 300px
padding 2%
box-sizing border-box
.right
overflow scroll
width 300px
min-height 300px
position absolute
right 0
top 0
box-sizing border-box
font-size 14px
font-weight normal
.config-com
color $-right-font-color
</style>
<template>
<div>
<div class="visual-dl-app-menu">
<v-toolbar color="primary" dark>
<v-toolbar-title class="appbar-menu-title"></v-toolbar-title>
<v-toolbar-items>
<v-btn v-for="item in items" :key="item.name"
flat dark :class="selected === item.name ? 'menu-item-selected': 'menu-item'"
@click="handleItemClick(item)"
>{{ item.title}}</v-btn>
</v-toolbar-items>
</v-toolbar>
</div>
</div>
</template>
<script>
export default {
props: ['initialRoute'],
name: 'AppMenu',
data () {
return {
selected: this.initialRoute,
items: [
{
url: '/scalars',
title: 'SCALARS',
name: 'scalars'
},
{
url: '/images',
title: 'IMAGES',
name: 'images'
},
{
url: '/histograms',
title: 'HISTOGRAMS',
name: 'histograms'
},
{
url: '/graphs',
title: 'GRAPHS',
name: 'graphs'
}
]
}
},
methods: {
handleItemClick: function (item) {
this.selected = item.name
this.$router.push(item.url)
}
}
}
</script>
<style scoped lang="stylus">
@import '~style/variables';
.visual-dl-app-menu
.appbar-menu-title
flex none
margin-right 50px
background url('./visualdl-logo.png') no-repeat
background-size cover
width 120px
height 50px
.menu-item
font-size 16px
background none
padding 0 10px
color #fff
opacity 0.6
display flex
flex-direction row
font-weight bold
.menu-item:hover
opacity 1
.menu-item-selected
@extends .visual-dl-app-menu .menu-item
opacity 1
</style>
<template>
<div class="visual-dl-expand-panel">
<h3
class="visual-dl-expand-head"
@click="isShow = !isShow"
>
<span>{{title}}</span>
<span class="visual-dl-expand-head-info">
<v-icon class="visual-dl-expand-head-arrow" size="20">{{iconName}}</v-icon>
<span class="visual-dl-expand-head-num">({{info}})</span>
</span>
</h3>
<div
v-if="isShow"
class="visual-dl-expand-panel-content"
>
<slot></slot>
</div>
</div>
</template>
<script>
export default {
props: {
title: String,
info: Number
},
computed: {
iconName() {
return this.isShow ? 'expand_less' : 'expand_more';
}
},
data() {
return {
isShow: true
};
}
};
</script>
<style lang="stylus">
.visual-dl-expand-panel
.visual-dl-expand-head
border solid 1px #ccc
line-height 50px
height 50px
padding 0 20px
cursor pointer
position relative
.visual-dl-expand-head-info
position absolute
left 90%
.visual-dl-expand-head-arrow
vertical-align middle
.visual-dl-expand-head-num
line-height 20px
font-size 12px
font-weight normal
.visual-dl-expand-panel-content
padding 0 20px
.visual-dl-expand-panel-content:after
content: "";
clear: both;
display: block;
</style>
import Notification from './Notification';
import NotificationItem from './NotificationItem';
export {
NotificationItem,
Notification as default
};
/**
* Created by ludaming on 3/4/18.
*/
<template>
<div class="visual-dl-page-container">
<div class="visual-dl-page-left">
<ui-chart
:fitScreen="fitScreen"
:download="download"
:scale="config.scale"
></ui-chart>
</div>
<div class="visual-dl-page-right">
<div class="visual-dl-page-config-container">
<ui-config
:config="config"
@fitScreen="handleFitScreen"
@download="handleDownload"
></ui-config>
</div>
</div>
</div>
</template>
<script>
import autoAdjustHeight from '../common/util/autoAdjustHeight';
import Config from './ui/Config'
import Chart from './ui/Chart';
export default {
components: {
'ui-config': Config,
'ui-chart': Chart
},
name: 'Graph',
data () {
return {
config: {
scale: 0.5
},
fitScreen: {fitScreen: false},
download: {download: false}
}
},
mounted() {
autoAdjustHeight();
},
methods: {
handleFitScreen() {
this.fitScreen = {fitScreen: true}
this.config.scale = 0.5;
},
handleDownload() {
this.download = {fitScreen: true}
}
}
};
</script>
<style lang="stylus">
</style>
<template>
<div class="visual-dl-graph-charts">
<div v-if="graphUrl" class="visual-dl-img-box">
<div class="small-img-box">
<img class="small-img" ref="small_img" width="30" :src="graphUrl" />
<div class="screen-handler" ref="screen_handler"></div>
</div>
<img class="draggable"
ref="draggable"
:width="computedWidth"
:src="graphUrl" />
</div>
</div>
</template>
<script>
// libs
import echarts from 'echarts';
import {
dragMovelHandler,
tansformElement,
relativeMove
} from './dragHelper';
// service
import {getPluginGraphsGraph} from '../../service';
// https://github.com/taye/interact.js
import interact from 'interactjs';
export default {
props: ['fitScreen', 'download', 'scale'],
computed: {
computedWidth() {
let scale = this.scale;
return Math.floor(scale * 2 * 700);
}
},
data() {
return {
width: 800,
height: 600,
graphUrl: '',
};
},
watch: {
fitScreen: function(val) {
this.clearDragedTransform(this.getBigImgEl());
this.clearDragedTransform(this.getSmallImgDragHandler());
},
download: function(val) {
let aEl = document.createElement('a');
aEl.href = this.graphUrl;
aEl.download = 'graph.png';
aEl.click();
}
},
mounted() {
this.getOriginChartsData();
},
methods: {
createChart() {
let el = this.el.getElementsByClassName('visual-dl-chart-box')[0];
this.myChart = echarts.init(el);
},
initChartOption(data) {
this.setChartOptions(data);
},
setChartOptions(data) {
this.myChart.setOption(data);
},
getOriginChartsData() {
getPluginGraphsGraph().then(({status, data}) => {
if (status === 0 && data.url) {
this.graphUrl = data.url
this.addDragEventForImg();
}
});
},
clearDragedTransform(dragImgEl) {
dragImgEl.style.transform = 'none';
dragImgEl.setAttribute('data-x', 0);
dragImgEl.setAttribute('data-y', 0);
},
getBigImgEl() {
return this.$refs.draggable
},
getSmallImgEl() {
return this.$refs.small_img
},
getSmallImgDragHandler() {
return this.$refs.screen_handler
},
addDragEventForImg() {
let that = this;
// target elements with the "draggable" class
interact('.draggable').draggable({
// enable inertial throwing
inertia: true,
autoScroll: true,
// call this function on every dragmove event
onmove(event) {
dragMovelHandler(event, (target, x, y) => {
tansformElement(target, x, y);
// compute the proportional value
let triggerEl = that.getBigImgEl();
let relativeEl = that.getSmallImgDragHandler();
relativeMove({triggerEl, x, y}, relativeEl);
});
}
});
interact('.screen-handler').draggable({
// enable inertial throwing
inertia: true,
autoScroll: true,
restrict: {
restriction: 'parent',
endOnly: false,
elementRect: {
top: 0,
left: 0,
bottom: 1,
right: 1
}
},
// call this function on every dragmove event
onmove(event) {
dragMovelHandler(event, (target, x, y) => {
tansformElement(target, x, y);
// compute the proportional value
let triggerEl = that.getSmallImgEl();
let relativeEl = that.getBigImgEl();
relativeMove({triggerEl, x, y}, relativeEl);
});
}
});
}
}
};
</script>
<style lang="stylus">
.visual-dl-graph-charts
width 90%
margin 0 auto
margin-bottom 20px
.visual-dl-chart-box
height 600px
.visual-dl-img-box
border solid 1px #e4e4e4
position relative
background #f0f0f0
overflow hidden
img
box-sizing border-box
margin 0 auto
display block
.small-img-box
width 30px
box-sizing border-box
position absolute
right 0
top 0
border-left solid 1px #e4e4e4
border-bottom solid 1px #e4e4e4
background #f0f0f0
z-index 1
.screen-handler
box-sizing border-box
position absolute
width 30px
height 20px
border solid 1px #666
top 0
left -1px
</style>
<template>
<div class="visual-dl-graph-config-com">
<v-btn
class="visual-dl-graph-config-button"
color="primary"
@click="handleFitScreen"
dark>
<v-icon style="margin-right: 6px" size="20">fullscreen</v-icon>
Fit &nbsp; to &nbsp; screen
</v-btn>
<v-btn
class="visual-dl-graph-config-button"
color="primary"
@click="handleDownload"
dark>
<v-icon style="margin-right: 6px">file_download</v-icon>
Download image
</v-btn>
<v-slider label="Scale"
max="1"
min="0.1"
step="0.1"
v-model="config.scale"
dark></v-slider>
</div>
</template>
<script>
export default {
props:['config'],
methods: {
handleFitScreen() {
this.$emit('fitScreen')
},
handleDownload() {
this.$emit('download')
}
}
};
</script>
<style lang="stylus">
@import '../../style/variables';
+prefix-classes('visual-dl-graph-')
.config-com
width 90%
margin 20px auto
color $-right-font-color
.config-button
width 200px
margin-top 20px
.visual-dl-graph-config-com
.sm-icon
width 36px
height 26px
</style>
<template>
<div class="visual-dl-page-container">
<div class="visual-dl-page-left">
<ui-chart-page
:config="config"
:runsItems="runsItems"
:tagList="filteredTagsList"
:title="'Tags matching ' + config.groupNameReg"
></ui-chart-page>
<ui-chart-page
v-for="item in groupedTags"
:key="item.group"
:config="config"
:runsItems="runsItems"
:tagList="item.tags"
:title="item.group"
></ui-chart-page>
</div>
<div class="visual-dl-page-right">
<div class="visual-dl-page-config-container">
Config
<ui-config
:runsItems="runsItems"
:config="config"
></ui-config>
</div>
</div>
</div>
</template>
<script>
import {getPluginHistogramsTags, getRuns} from '../service';
import Config from './ui/Config';
import ChartPage from './ui/ChartPage';
import {debounce, flatten, uniq} from 'lodash';
import autoAdjustHeight from '../common/util/autoAdjustHeight';
export default {
components: {
'ui-config': Config,
'ui-chart-page': ChartPage
},
computed: {
runsItems() {
let runsArray = this.runsArray || [];
return runsArray.map(item => {
return {
name: item,
value: item
};
});
},
tagsList() {
let tags = this.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]
};
}).filter(item => item.tag !== undefined);
return {
tagList,
tag,
group: tag.split('/')[0]
};
});
},
groupedTags() {
let tagsList = this.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]
};
});
}
},
data() {
return {
runsArray: [],
tags: [],
config: {
groupNameReg: '.*',
horizontal: 'step',
chartType: 'offset',
runs: [],
running: true
},
filteredTagsList: []
};
},
created() {
getPluginHistogramsTags().then(({errno, data}) => {
this.tags = data
// filter when inited
let groupNameReg = this.config.groupNameReg;
this.filterTagsList(groupNameReg);
});
getRuns().then(({errno, data}) => {
this.runsArray = data
this.config.runs = data
});
},
mounted() {
autoAdjustHeight();
},
watch: {
'config.groupNameReg': function(val) {
this.throttledFilterTagsList()
}
},
methods: {
filterTagsList(groupNameReg) {
if (!groupNameReg) {
this.filteredTagsList = []
return;
}
let tagsList = this.tagsList || [];
let regExp = new RegExp(groupNameReg);
let filtedTagsList = tagsList.filter(item => regExp.test(item.tag));
this.filteredTagsList = filtedTagsList
},
throttledFilterTagsList: _.debounce(
function() {
this.filterTagsList(this.config.groupNameReg)
}, 300
),
}
};
</script>
<style lang="stylus">
</style>
<template>
<div class="visual-dl-histogram-charts">
<div class="visual-dl-chart-box" ref="visual_dl_chart_box">
</div>
<div class="visual-dl-chart-actions">
<v-btn flat class="sm-button" @click="expandArea">
<v-icon size="20">settings_overscan</v-icon>
</v-btn>
</div>
</div>
</template>
<script>
// libs
import echarts from 'echarts';
import {originDataToChartData} from '../histogramHelper';
import {format, precisionRound} from 'd3-format';
// service
import {getPluginHistogramsHistograms} from '../../service';
let zrDrawElement = {};
zrDrawElement.hoverDots = [];
// the time to refresh chart data
const intervalTime = 15;
const p = Math.max(0, precisionRound(0.01, 1.01) - 1);
const yValueFormat = format('.' + p + 'e');
export default {
props: ['tagInfo', 'runs', 'chartType', 'running', 'runsItems'],
data() {
return {
originData: [],
isExpand: false
};
},
watch: {
originData: function(val) {
this.initChartOption();
},
chartType: function(val) {
this.initChartOption();
},
running: function(val) {
val ? this.startInterval() : this.stopInterval();
}
},
mounted() {
let tagInfo = this.tagInfo;
this.initChart(tagInfo);
if (this.running) {
this.startInterval();
}
},
beforeDestroy() {
this.stopInterval();
},
methods: {
initChart(tagInfo) {
this.createChart();
this.getOriginChartData(tagInfo);
},
createChart() {
let el = this.$refs.visual_dl_chart_box
this.myChart = echarts.init(el);
},
initChartOption() {
this.myChart.clear();
let zr = this.myChart.getZr();
let hoverDots = zrDrawElement.hoverDots;
if (hoverDots != null && hoverDots.length !== 0) {
hoverDots.forEach(dot => zr.remove(dot));
}
let chartType = this.chartType;
let data = this.originData;
let visData = originDataToChartData(data);
let tagInfo = this.tagInfo;
let title = tagInfo.tag.displayName + '(' + tagInfo.run + ')';
this.setChartOptions(visData, title, chartType);
},
setChartOptions(visData, tag, chartType) {
let grid = {
left: '15%',
top: '15%',
right: '10%',
bottom: '8%'
};
let title = {
text: tag,
textStyle: {
fontSize: '12',
fontFamily: 'Merriweather Sans'
}
};
if (chartType === 'overlay') {
this.setOverlayChartOption(visData, title, grid);
}
else if (chartType === 'offset') {
this.setOffsetChartOption(visData, title, grid);
}
},
setOverlayChartOption({chartData, min, max}, title, grid) {
let seriesOption = chartData.map(({time, step, items}) => ({
name: 'step' + step,
type: 'line',
showSymbol: false,
hoverAnimation: false,
z: 0,
data: items,
animationDuration: 100,
lineStyle: {
normal: {
width: 1,
color: '#008c99'
}
},
encode: {
x: [2],
y: [3]
}
})
);
let option = {
title: title,
axisPointer: {
link: {xAxisIndex: 'all'},
show: true,
snap: true,
triggerTooltip: true
},
grid: grid,
xAxis: {
type: 'value'
},
yAxis: {
type: 'value',
axisLine: {
onZero: false
},
axisLabel: {
formatter(value, index) {
return yValueFormat(value);
}
},
axisPointer: {
label: {
formatter({value}) {
return yValueFormat(value);
}
}
}
},
series: seriesOption
};
let zr1 = this.myChart.getZr();
zr1.on('mousemove', function (e) {
zr1.remove(zrDrawElement.hoverLine);
zr1.remove(zrDrawElement.tooltip);
zr1.remove(zrDrawElement.tooltipX);
zr1.remove(zrDrawElement.tooltipY);
zrDrawElement.hoverDots.forEach(dot => zr1.remove(dot));
zrDrawElement.hoverDots.length = 0;
});
this.myChart.setOption(option, {notMerge: true});
},
setOffsetChartOption({chartData, min, max}, title, grid) {
let rawData = [];
let minX = min;
let maxX = max;
let minZ = Infinity;
let maxZ = -Infinity;
let ecChart = this.myChart;
let maxStep = -Infinity;
let minStep = Infinity;
grid.top = '42%';
grid.left = '4%';
grid.right = '15%';
chartData.forEach(function (dataItem) {
let lineData = [];
maxStep = Math.max(dataItem.step, maxStep);
minStep = Math.min(dataItem.step, minStep);
dataItem.items.forEach(([time, step, x, y]) => {
minZ = Math.min(minZ, y);
maxZ = Math.max(maxZ, y);
lineData.push(x, step, y);
});
rawData.push(lineData);
});
let option = {
title,
color: ['#006069'],
visualMap: {
type: 'continuous',
show: false,
min: minStep,
max: maxStep,
dimension: 1,
inRange: {
colorLightness: [0.2, 0.4]
}
},
xAxis: {
min: minX,
max: maxX,
axisLine: {
onZero: false
},
axisLabel: {
formatter: function (value) {
return Math.round(value * 100) / 100;
}
},
splitLine: {
show: false
}
},
yAxis: {
position: 'right',
axisLine: {
onZero: false
},
inverse: true,
splitLine: {
show: false
}
},
grid,
series: [{
type: 'custom',
dimensions: ['x', 'y'],
renderItem: function (params, api) {
let points = makePolyPoints(
params.dataIndex,
api.value,
api.coord,
params.coordSys.y - 10
);
return {
type: 'polygon',
silent: true,
shape: {
points
},
style: api.style({
stroke: '#bbb',
lineWidth: 1
})
};
},
data: rawData
}]
};
function makePolyPoints(dataIndex, getValue, getCoord, yValueMapHeight) {
let points = [];
for (let i = 0; i < rawData[dataIndex].length;) {
let x = getValue(i++);
let y = getValue(i++);
let z = getValue(i++);
points.push(getPoint(x, y, z, getCoord, yValueMapHeight));
}
return points;
}
function getPoint(x, y, z, getCoord, yValueMapHeight) {
let pt = getCoord([x, y]);
// linear map in z axis
pt[1] -= (z - minZ) / (maxZ - minZ) * yValueMapHeight;
return pt;
}
let zr = ecChart.getZr();
function removeTooltip() {
if (zrDrawElement.hoverLine) {
zr.remove(zrDrawElement.hoverLine);
zr.remove(zrDrawElement.tooltip);
zrDrawElement.hoverDots.forEach(dot => zr.remove(dot));
zrDrawElement.hoverDots.length = 0;
zr.remove(zrDrawElement.tooltipX);
zr.remove(zrDrawElement.tooltipY);
}
}
zr.on('mouseout', e => {
removeTooltip();
});
zr.on('mousemove', e => {
removeTooltip();
let nearestIndex = findNearestValue(e.offsetX, e.offsetY);
if (nearestIndex) {
let getCoord = function (pt) {
return ecChart.convertToPixel('grid', pt);
};
let gridRect = ecChart.getModel().getComponent('grid', 0).coordinateSystem.getRect();
let linePoints = makePolyPoints(
nearestIndex.itemIndex,
function (i) {
return rawData[nearestIndex.itemIndex][i];
},
getCoord,
gridRect.y - 10
);
zr.add(zrDrawElement.hoverLine = new echarts.graphic.Polyline({
silent: true,
shape: {
points: linePoints
},
style: {
stroke: '#5c5c5c',
lineWidth: 2
},
z: 999
}));
let itemX;
rawData.forEach(dataItem => {
let binIndex = nearestIndex.binIndex;
let x = dataItem[binIndex * 3];
let y = dataItem[binIndex * 3 + 1];
let z = dataItem[binIndex * 3 + 2];
let pt = getPoint(x, y, z, getCoord, gridRect.y - 10);
itemX = pt[0];
let dot = new echarts.graphic.Circle({
shape: {
cx: pt[0],
cy: pt[1],
r: 3
},
style: {
fill: '#000',
stroke: '#ccc',
lineWidth: 1
},
z: 1000
});
zr.add(dot);
zrDrawElement.hoverDots.push(dot);
});
let hoveredItem = chartData[nearestIndex.itemIndex];
zrDrawElement.tooltip = new echarts.graphic.Text({
position: [e.offsetX + 30, e.offsetY - 50],
style: {
text: yValueFormat(hoveredItem.items[nearestIndex.binIndex][3]),
textFill: '#000',
fontSize: 14,
textBackgroundColor: '#eee',
textBorderColor: '#008c99',
textBorderWidth: 2,
textBorderRadius: 5,
textPadding: 10,
rich: {}
},
z: 2000
});
zr.add(zrDrawElement.tooltip);
zrDrawElement.tooltipX = new echarts.graphic.Text({
position: [
itemX,
gridRect.y + gridRect.height
],
style: {
text: Math.round(hoveredItem.items[nearestIndex.binIndex][2] * 1000) / 1000,
textFill: '#fff',
textAlign: 'center',
fontSize: 12,
textBackgroundColor: '#333',
textBorderWidth: 2,
textPadding: [5, 7],
rich: {}
},
z: 2000
});
zr.add(zrDrawElement.tooltipX);
zrDrawElement.tooltipY = new echarts.graphic.Text({
position: [
gridRect.x + gridRect.width,
linePoints[linePoints.length - 1][1]
],
style: {
text: hoveredItem.step,
textFill: '#fff',
textVerticalAlign: 'middle',
fontSize: 12,
textBackgroundColor: '#333',
textBorderWidth: 2,
textPadding: [5, 7],
rich: {}
},
z: 2000
});
zr.add(zrDrawElement.tooltipY);
}
});
function findNearestValue(px, py) {
let value = ecChart.convertFromPixel('grid', [px, py]);
let itemIndex;
let nearestY = Infinity;
let binIndex;
chartData.forEach((item, index) => {
let dist = Math.abs(value[1] - item.step);
if (dist < nearestY) {
nearestY = dist;
itemIndex = index;
}
});
if (itemIndex != null) {
let dataItem = chartData[itemIndex];
let nearestX = Infinity;
dataItem.items.forEach((item, index) => {
let dist = Math.abs(item[2] - value[0]);
if (dist < nearestX) {
nearestX = dist;
binIndex = index;
}
});
if (binIndex != null) {
return {
itemIndex: itemIndex,
binIndex: binIndex
};
}
}
}
ecChart.setOption(option, {notMerge: true});
},
// get origin data per 60 seconds
startInterval() {
this.getOringDataInterval = setInterval(() => {
let tagInfo = this.tagInfo;
this.getOriginChartData(tagInfo);
}, intervalTime * 1000);
},
stopInterval() {
clearInterval(this.getOringDataInterval);
},
getOriginChartData(tagInfo) {
let run = tagInfo.run
let tag = tagInfo.tag
let params = {
run,
tag: tag.displayName
};
getPluginHistogramsHistograms(params).then(({status, data}) => {
if (status === 0) {
this.originData = data;
}
});
},
expandArea() {
let isExpand = this.isExpand;
let pageBoxWidth = document.getElementsByClassName('visual-dl-chart-page')[0].offsetWidth;
if (!isExpand) {
let el = this.$refs.visual_dl_chart_box;
el.style.width = pageBoxWidth + 'px';
el.style.height = '600px';
this.isExpand = true;
this.myChart.resize({
width: pageBoxWidth,
height: 600
});
}
else {
let el = this.$refs.visual_dl_chart_box;
el.style.width = '400px';
el.style.height = '300px';
this.isExpand = false;
this.myChart.resize({
width: 400,
height: 300
});
}
}
}
};
</script>
<style lang="stylus">
.visual-dl-histogram-charts
float left
margin-bottom 20px
margin 20px 30px 10px 0
background #fff
padding 10px
.visual-dl-chart-box
float left
width 400px
height 300px
.visual-dl-chart-actions
height 50px
margin-left 10%
.sm-form-item
float left
width 100px
margin-top 0px
display block
.sm-button
float left
display block
height 20px
line-height 20px
margin-top 10px
padding 0 10px
</style>
<template>
<!-- ClassName visual-dl-chart-page used in chart.san, change they all if you need!-->
<div class="visual-dl-chart-page">
<ui-expand-panel :info="total" :title="title">
<div class="visual-dl-chart-page-box">
<ui-chart
v-for="(tagInfo, index) in filteredPageList"
:key="index"
:tagInfo="tagInfo"
:runs="config.runs"
:chartType="config.chartType"
:running="config.running"
:runsItems="runsItems"
></ui-chart>
</div>
<v-pagination class="visual-dl-sm-pagination"
v-if="total > pageSize"
v-model="currentPage"
:length="pageLength"
></v-pagination>
</ui-expand-panel>
</div>
</template>
<script>
import ExpandPanel from '../../common/component/ExpandPanel';
import Chart from './Chart';
import {cloneDeep, flatten} from 'lodash';
export default {
props: ['config', 'runsItems', 'tagList', 'title'],
components: {
'ui-chart': Chart,
'ui-expand-panel': ExpandPanel,
},
computed: {
filteredRunsList() {
let tagList = this.tagList || [];
let runs = this.config.runs || [];
let list = cloneDeep(tagList);
return flatten(list.slice().map(item => {
return item.tagList.filter(one => runs.includes(one.run));
}));
},
filteredPageList() {
let list = this.filteredRunsList;
let currentPage = this.currentPage;
let pageSize = this.pageSize;
return list.slice((currentPage - 1) * pageSize, currentPage * pageSize);
},
total() {
let list = this.filteredRunsList || [];
return list.length;
},
pageLength() {
return Math.ceil(this.total / this.pageSize)
}
},
data() {
return {
// current page
currentPage: 1,
// item per page
pageSize: 4
};
},
methods: {
handlePageChange({pageNum}) {
this.currentPage = pageNum;
}
}
};
</script>
<style lang="stylus">
@import '../../style/variables';
+prefix-classes('visual-dl-')
.chart-page
.chart-box
float left
.chart-page-box:after
content: "";
clear: both;
display: block;
</style>
<template>
<div class="visual-dl-page-config-com">
<v-text-field
label="Group name RegExp"
hint="input a tag group name to search"
v-model="config.groupNameReg"
dark
></v-text-field>
<v-radio-group label="Histogram mode" v-model="config.chartType" dark>
<v-radio v-for="mode in chartTypeItems" :key="mode.name"
:label="mode.name" :value="mode.value"></v-radio>
</v-radio-group>
<label class="visual-dl-page-checkbox-group-label">Runs</label>
<v-checkbox v-for="item in runsItems"
:key="item.name"
:label="item.name"
:value="item.value"
v-model="config.runs"
dark
></v-checkbox>
<v-btn class="visual-dl-page-run-toggle"
:color="config.running ? 'primary' : 'error'"
v-model="config.running"
@click="toggleAllRuns"
dark block
>
{{config.running ? 'Running' : 'Stopped'}}
</v-btn>
</div>
</template>
<script>
export default {
props: ['config', 'runsItems'],
data() {
return {
chartTypeItems: [
{
name: 'Overlay',
value: 'overlay'
},
{
name: 'Offset',
value: 'offset'
}
]
};
},
methods: {
toggleAllRuns() {
let running = this.config.running;
this.config.running = !running;
}
}
};
</script>
<style lang="stylus">
+prefix-classes('visual-dl-page-')
.config-com
padding 20px
.run-toggle
margin-top 20px
.checkbox-group-label
display flex
margin-top 20px
margin-bottom 10px
</style>
<template>
<div class="visual-dl-page-container">
<div class="visual-dl-page-left">
<ui-chart-page
:expand="true"
:config="filteredConfig"
:runsItems="runsItems"
:tagList="filteredTagsList"
:title="'Tags matching ' + config.groupNameReg"
></ui-chart-page>
<ui-chart-page
v-for="item in groupedTags"
:key="item.group"
:config="filteredConfig"
:runsItems="runsItems"
:tagList="item.tags"
:title="item.group"
></ui-chart-page>
</div>
<div class="visual-dl-page-right">
<div class="visual-dl-page-config-container">
<ui-config :runsItems="runsItems" :config="config"
></ui-config>
</div>
</div>
</div>
</template>
<script>
import {getPluginImagesTags, getRuns} from '../service';
import {debounce, flatten, uniq, isArray} from 'lodash';
import autoAdjustHeight from '../common/util/autoAdjustHeight';
import Config from './ui/Config'
import ChartPage from './ui/ChartPage';
export default {
name: 'Images',
components: {
'ui-config': Config,
'ui-chart-page': ChartPage
},
data () {
return {
runsArray: [],
tags: [],
config: {
groupNameReg: '.*',
isActualImageSize: false,
runs: [],
running: true
},
filteredTagsList: []
}
},
computed: {
runsItems() {
let runsArray = this.runsArray || [];
return runsArray.map(item => {
return {
name: item,
value: item
};
});
},
tagsList() {
let tags = this.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]
};
}).filter(item => item.tag !== undefined);
return {
tagList,
tag,
group: tag.split('/')[0]
};
});
},
groupedTags() {
let tagsList = this.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]
};
});
},
filteredConfig() {
let tansformArr = ['isActualImageSize'];
let config = this.config || {};
let filteredConfig = {};
Object.keys(config).forEach(key => {
let val = config[key];
filteredConfig[key] = val;
});
return filteredConfig;
}
},
created() {
getPluginImagesTags().then(({errno, data}) => {
this.tags = data;
// filter when inited
let groupNameReg = this.config.groupNameReg;
this.filterTagsList(groupNameReg);
});
getRuns().then(({errno, data}) => {
this.runsArray = data;
this.config.runs = data;
});
},
mounted() {
autoAdjustHeight();
},
watch: {
'config.groupNameReg': function(val) {
this.throttledFilterTagsList()
}
},
methods:{
filterTagsList(groupNameReg) {
if (!groupNameReg) {
this.filteredTagsList = [];
return;
}
let tagsList = this.tagsList || [];
let regExp = new RegExp(groupNameReg);
this.filteredTagsList = tagsList.filter(item => regExp.test(item.tag));
},
throttledFilterTagsList: _.debounce(
function() {
this.filterTagsList(this.config.groupNameReg)
}, 300
),
}
};
</script>
<style lang="stylus">
</style>
<template>
<div class="visual-dl-chart-page">
<ui-expand-panel :info="total" :title="title">
<ui-image
class="visual-dl-chart-image"
v-for="(tagInfo, index) in filteredPageList"
:key="index"
:tagInfo="tagInfo"
:isActualImageSize="config.isActualImageSize"
:runs="config.runs"
:running="config.running"
:runsItems="runsItems"
></ui-image>
<v-pagination class="visual-dl-sm-pagination"
v-if="total > pageSize"
v-model="currentPage"
:length="pageLength"
></v-pagination>
</ui-expand-panel>
</div>
</template>
<script>
import ExpandPanel from '../../common/component/ExpandPanel';
import Image from './Image';
//import Pagination from 'san-mui/Pagination';
import {cloneDeep, flatten} from 'lodash';
export default {
props: ['config', 'runsItems', 'tagList', 'title'],
components: {
'ui-image': Image,
'ui-expand-panel': ExpandPanel,
//'ui-pagination': Pagination
},
computed: {
filteredRunsList() {
let tagList = this.tagList || [];
let runs = this.config.runs || [];
let list = cloneDeep(tagList);
return flatten(list.slice().map(item => {
return item.tagList.filter(one => runs.includes(one.run));
}));
},
filteredPageList() {
let list = this.filteredRunsList || [];
return list.slice((this.currentPage - 1) * this.pageSize, this.currentPage * this.pageSize);
},
total() {
let list = this.filteredRunsList || [];
return list.length;
},
pageLength() {
return Math.ceil(this.total / this.pageSize)
}
},
data() {
return {
// current page
currentPage: 1,
// item per page
pageSize: 8
};
},
};
</script>
<style lang="stylus">
@import '~style/variables';
+prefix-classes('visual-dl-')
.chart-page
.image-chart-box
overflow hidden
float left
.visual-dl-chart-image
float left
.image-chart-box:after
content ""
clear both
display block
.sm-pagination
height 50px
float left
width 100%
</style>
<template>
<div class="visual-dl-page-config-com">
<v-text-field
label="Group name RegExp"
hint="input a tag group name to search"
v-model="config.groupNameReg"
dark
></v-text-field>
<v-checkbox label="Show actual image size" v-model="config.isActualImageSize" dark></v-checkbox>
<label class="visual-dl-page-checkbox-group-label">Runs</label>
<v-checkbox v-for="item in runsItems"
:key="item.name"
:label="item.name"
:value="item.value"
v-model="config.runs"
dark
></v-checkbox>
<v-btn :color="config.running ? 'primary' : 'error'"
v-model="config.running"
@click="toggleAllRuns"
class="visual-dl-page-run-toggle"
dark block
>
{{config.running ? 'Running' : 'Stopped'}}
</v-btn>
</div>
</template>
<script>
// TODO: Maybe migrate the CheckBoxGroup from San to Vue
//import CheckBoxGroup from '../../common/component/CheckBoxGroup';
export default {
props: {
runsItems: Array,
config: Object
},
data() {
return {
};
},
methods: {
toggleAllRuns() {
this.config.running = !this.config.running;
}
}
};
</script>
<style lang="stylus">
+prefix-classes('visual-dl-page-')
.config-com
padding 20px
.run-toggle
margin-top 20px
.checkbox-group-label
display flex
margin-top 20px
margin-bottom 10px
</style>
<template>
<div class="visual-dl-image">
<h3 class="visual-dl-image-title">{{tagInfo.tag.displayName}}
<span class="visual-dl-image-run-icon">{{tagInfo.run}}</span>
</h3>
<p>
<span>Step:</span>
<span>{{imgData.step}};</span>
<span>{{imgData.wall_time | formatTime}}</span>
</p>
<v-slider label="step"
:max="steps"
:min="slider.min"
:step="1"
v-model="currentIndex"
dark></v-slider>
<img :width="imageWidth" :height="imageHeight" :src="imgData.imgSrc" />
</div>
</template>
<script>
import {getPluginImagesImages} from '../../service';
const defaultImgWidth = 400;
const defaultImgHeight = 300;
// the time to refresh chart data
const intervalTime = 30;
export default {
props: ['tagInfo', 'isActualImageSize', 'runs', 'running', 'runsItems'],
computed: {
steps() {
let data = this.data || [];
return data.length - 1;
},
imageWidth() {
return this.isActualImageSize ? this.imgData.width : defaultImgWidth
},
imageHeight() {
return this.isActualImageSize ? this.imgData.height : defaultImgHeight
}
},
filters: {
formatTime: function (value) {
if (!value) {
return;
}
let time = new Date();
time.setTime(value.toString().split('.')[0]);
return time;
}
},
data() {
return {
currentIndex: 0,
slider: {
value: '0',
label: '',
min: 0,
step: 1
},
imgData: {},
data: [],
height: defaultImgHeight,
weight: defaultImgWidth
};
},
created() {
this.getOriginChartsData();
},
mounted() {
if (this.running) {
this.startInterval();
}
},
beforeDestroy() {
this.stopInterval();
},
watch: {
running: function (val) {
val ? this.startInterval() : this.stopInterval();
},
currentIndex: function(index) {
/* eslint-disable fecs-camelcase */
if (this.data && this.data[index]) {
let currentImgInfo = this.data ? this.data[index] : {};
let {height, width, query, step, wall_time} = currentImgInfo;
let url = '/data/plugin/images/individualImage?ts=' + wall_time;
let imgSrc = [url, query].join('&');
this.imgData = {
imgSrc,
height,
width,
step,
wall_time
}
}
/* eslint-enable fecs-camelcase */
}
},
methods: {
stopInterval() {
clearInterval(this.getOringDataInterval);
},
// get origin data per {{intervalTime}} seconds
startInterval() {
this.getOringDataInterval = setInterval(() => {
this.getOriginChartsData();
}, intervalTime * 1000);
},
getOriginChartsData() {
//let {run, tag} = this.tagInfo;
let run = this.tagInfo.run
let tag = this.tagInfo.tag
let {displayName, samples} = tag;
let params = {
run,
tag: displayName,
samples
};
getPluginImagesImages(params).then(({status, data}) => {
if (status === 0) {
this.data = data;
this.currentIndex = data.length - 1;
}
});
}
}
};
</script>
<style lang="stylus">
.visual-dl-image
font-size 12px
width 420px
float left
margin 20px 30px 10px 0
background #fff
padding 10px
.visual-dl-image-title
font-size 14px
line-height 30px
.visual-dl-image-run-icon
background #e4e4e4
float right
margin-right 10px
padding 0 10px
border solid 1px #e4e4e4
border-radius 6px
line-height 20px
margin-top 4px
.visual-dl-chart-actions
.sm-form-item
width 300px
display inline-block
</style>
import 'normalize.css/normalize.css';
import 'san-mui/index.css';
import './common/component/ui-common.styl';
//import 'normalize.css/normalize.css';
//import 'san-mui/index.css';
//import './common/component/ui-common.styl';
//
//import './home/index';
//import './scalars/index';
//import './images/index';
//import './histogram/index';
//import './graph/index';
//
//import App from './App';
//new App({
// data: {
// titleName: 'VisualDL'
// }
//}).attach(document.getElementById('root'));
//
//var vm = new Vue({
// el: "#root",
//
//})
import './home/index';
import './scalars/index';
import './images/index';
import './histogram/index';
import './graph/index';
import App from './App';
new App({
data: {
titleName: 'VisualDL'
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import Vuetify from 'vuetify'
import router from '@/router'
Vue.config.productionTip = false
Vue.use(Vuetify, {
theme: {
primary: '#008c99',
accent: '#008c99'
}
}).attach(document.getElementById('root'));
})
/* eslint-disable no-new */
new Vue({
el: '#root',
router,
components: {App},
template: '<App/>',
})
import Vue from 'vue'
import Router from 'vue-router'
import Scalars from '@/scalars/Scalars'
import Histogram from '@/histogram/Histogram'
import Images from '@/images/Images'
import Graph from '@/graph/Graph'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/scalars',
name: 'Scalars',
component: Scalars
},
{
path: '/histograms',
name: 'Histograms',
component: Histogram
},
{
path: '/images',
name: 'Images',
component: Images
},
{
path: '/graphs',
name: 'Graph',
component: Graph
},
]
})
<template>
<div class="visual-dl-page-container">
<div class="visual-dl-page-left">
<ui-chart-page
:config="config"
:runsItems="runsItems"
:tagList="filteredTagsList"
:title="'Tags matching' + config.groupNameReg"
></ui-chart-page>
<ui-chart-page
v-for="item in groupedTags"
:key="item.group"
:config="config"
:runsItems="runsItems"
:tagList="item.tags"
:title="item.group"
></ui-chart-page>
</div>
<div class="visual-dl-page-right">
<div class="visual-dl-page-config-container">
<ui-config :runsItems="runsItems"
:config="config"
></ui-config>
</div>
</div>
</div>
</template>
<script>
import {getPluginScalarsTags, getRuns} from '../service';
import {debounce, flatten, uniq, isArray} from 'lodash';
import autoAdjustHeight from '../common/util/autoAdjustHeight';
import Config from './ui/Config'
import ChartPage from './ui/ChartPage';
export default {
components: {
'ui-config': Config,
'ui-chart-page': ChartPage
},
data() {
return {
runsArray: [],
tags: [],
config: {
groupNameReg: '.*',
smoothing: 0.6,
horizontal: 'step',
sortingMethod: 'default',
downloadLink: false,
outlier: false,
runs: [],
running: true
},
filteredTagsList: []
}
},
computed: {
runsItems() {
let runsArray = this.runsArray || [];
return runsArray.map(item => {
return {
name: item,
value: item
};
});
},
tagsList() {
let tags = this.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]
};
}).filter(item => item.tag !== undefined);
return {
tagList,
tag,
group: tag.split('/')[0]
};
});
},
groupedTags() {
let tagsList = this.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]
};
});
},
},
created() {
getPluginScalarsTags().then(({errno, data}) => {
this.tags = data;
// filter when inited
let groupNameReg = this.config.groupNameReg;
this.filterTagsList(groupNameReg);
});
getRuns().then(({errno, data}) => {
this.runsArray = data;
this.config.runs = data;
});
},
mounted() {
autoAdjustHeight();
},
watch: {
'config.groupNameReg': function(val) {
this.throttledFilterTagsList()
}
},
methods: {
filterTagsList(groupNameReg) {
if (!groupNameReg) {
this.filteredTagsList = [];
return;
}
let tagsList = this.tagsList || [];
let regExp = new RegExp(groupNameReg);
this.filteredTagsList = tagsList.filter(item => regExp.test(item.tag));
},
throttledFilterTagsList: _.debounce(
function() {
this.filterTagsList(this.config.groupNameReg)
}, 300
),
}
};
</script>
<style lang="stylus">
</style>
此差异已折叠。
<template>
<div class="visual-dl-chart-page">
<ui-expand-panel :info="tagList.length" :title="title">
<div ref="chartPageBox" class="visual-dl-chart-page-box">
<ui-chart
v-for="(tagInfo, index) in filteredTagList"
:key="index"
:tagInfo="tagInfo"
:groupNameReg="config.groupNameReg"
:smoothing="config.smoothing"
:horizontal="config.horizontal"
:sortingMethod="config.sortingMethod"
:downloadLink="config.downloadLink"
:outlier="config.outlier"
:runs="config.runs"
:running="config.running"
:runsItems="runsItems"
></ui-chart>
</div>
<v-pagination
v-if="total > pageSize"
v-model="currentPage"
:length="pageLength"
/>
</ui-expand-panel>
</div>
</template>
<script>
import ExpandPanel from '../../common/component/ExpandPanel';
import Chart from './Chart';
// import Pagination from 'san-mui/Pagination';
import {cloneDeep} from 'lodash';
export default {
components: {
'ui-chart': Chart,
'ui-expand-panel': ExpandPanel,
// 'ui-pagination': Pagination
},
props: ['config', 'runsItems', 'tagList', 'title'],
computed: {
filteredRunsList() {
let tagList = this.tagList || [];
let runs = this.config.runs || [];
let list = cloneDeep(tagList);
return list.slice().map(item => {
item.tagList = item.tagList.filter(one => runs.includes(one.run));
return item;
});
},
filteredTagList() {
let tagList = this.filteredRunsList || [];
return tagList.slice((this.currentPage - 1) * this.pageSize, this.currentPage * this.pageSize);
},
total() {
let tagList = this.tagList || [];
return tagList.length;
},
pageLength() {
return Math.ceil(this.total / this.pageSize)
}
},
data() {
return {
// current page
currentPage: 1,
// item per page
pageSize: 8
};
}
};
</script>
<style lang="stylus">
@import '~style/variables';
+prefix-classes('visual-dl-')
.chart-page
.chart-page-box:after
content: "";
clear: both;
display: block;
</style>
<template>
<div class="visual-dl-page-config-com">
<v-text-field
label="Group name RegExp"
hint="input a tag group name"
v-model="config.groupNameReg"
dark
></v-text-field>
<div class="visual-dl-page-slider-block">
<v-slider label="Smoothing"
:max="0.99"
:min="0"
:step="0.01"
v-model="config.smoothing"
class="visual-dl-page-smoothing-slider"
dark></v-slider>
<span class="visual-dl-page-slider-span">{{config.smoothing}}</span>
</div>
<v-radio-group label="Horizontal" v-model="config.horizontal" dark>
<v-radio label="Step" value="step"></v-radio>
<v-radio label="Relative" value="relative"></v-radio>
<v-radio label="Wall" value="wall"></v-radio>
</v-radio-group>
<v-select
:items="sortingMethodItems"
v-model="config.sortingMethod"
label="Tooltip sorting method"
class="visual-dl-page-config-selector"
dark
></v-select>
<v-checkbox class="visual-dl-page-config-checkbox" label="Show data download links" v-model="config.downloadLink" dark></v-checkbox>
<v-checkbox class="visual-dl-page-config-checkbox" label="Ignore outliers in chart scaling" v-model="config.outlier" dark></v-checkbox>
<label class="visual-dl-page-checkbox-group-label">Runs</label>
<v-checkbox v-for="item in runsItems"
:key="item.name"
:label="item.name"
:value="item.value"
v-model="config.runs"
dark
class="visual-dl-page-runs-checkbox"
></v-checkbox>
<v-btn :color="config.running ? 'primary' : 'error'"
v-model="config.running"
@click="toggleAllRuns"
class="visual-dl-page-run-toggle"
dark block
>
{{config.running ? 'Running' : 'Stopped'}}
</v-btn>
</div>
</template>
<script>
//import TextField from 'san-mui/TextField';
// TODO: Consider create Vue Components for these
//import Slider from '../../common/component/Slider';
//import RadioGroup from '../../common/component/RadioGroup';
//import DropDownMenu from '../../common/component/DropDownMenu';
//import CheckBoxGroup from '../../common/component/CheckBoxGroup';
//import Button from 'san-mui/Button';
export default {
props: {
runsItems: Array,
config: Object
},
data() {
return {
horizontalItems: [
{
name: 'Step',
value: 'step'
},
{
name: 'Relative',
value: 'relative'
},
{
name: 'Wall',
value: 'wall'
}
],
sortingMethodItems: [
'default', 'descending', 'ascending', 'nearest'
]
};
},
methods: {
toggleAllRuns() {
this.config.running = !this.config.running;
}
}
};
</script>
<style lang="stylus">
+prefix-classes('visual-dl-page-')
.config-com
padding 20px
.slider-block
display flex
align-items center
.smoothing-slider
display inline
.slider-span
width 40px
.run-toggle
margin-top 20px
.config-checkbox label
font-size 13px
.config-selector
margin-top 12px
margin-bottom 20px
.checkbox-group-label
display flex
margin-top 20px
margin-bottom 10px
</style>
//stylus variables define before import statement
$input-font-size = 14px
@import '../../node_modules/vuetify/src/stylus/main'
//css define after import statement
.input-group.input-group--slider label
font-size $input-font-size
.input-group__details
min-height 14px
\ No newline at end of file
......@@ -8,6 +8,6 @@ $-left-background-color = #303a3a
$-content-text-color = #303a3a
$-left-border-clor = #e9e9e9
$-left-border-color = #e9e9e9
$-right-font-color = #ccc
$-right-font-color = rgba(#fff, 0.7)
......@@ -5,6 +5,7 @@
<title>Visual DL</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<link href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' rel="stylesheet">
</head>
<body>
<div id="root"></div>
......
......@@ -46,11 +46,13 @@ const config = {
resolve: {
alias: {
'san-mui': 'san-mui/lib',
axios: 'axios/dist/axios.min.js'
axios: 'axios/dist/axios.min.js',
'vue$': 'vue/dist/vue.esm.js',
'@': path.resolve(projectPath, 'src'),
style: path.resolve(__dirname, '../src/style')
},
extensions: ['.js', '.json', '.styl', '.css', '.html', '.san']
extensions: ['.js', '.json', '.styl', '.css', '.html', '.vue']
},
module: {
......@@ -59,13 +61,8 @@ const config = {
],
rules: [
{
test: /\.san$/,
loader: 'san-loader',
options: {
loaders: {
stylus: getLoaders(isDev, 'stylus')
}
}
test: /\.vue$/,
loader: 'vue-loader',
},
{
test: /\.js$/,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册