提交 4cd48246 编写于 作者: N Nicky Chan 提交者: daminglu

Redesign VisualDL for v1.2 (#502)

上级 1c976b75
# RELEASE 1.2.0
## New design
- Cleaner and more organized interface
- Show / select runs in global bar instead of individual tab
- Merge Scalar and Histogram into Metrics
- Merge Image, Audio and Text into Samples
- New Config design, add filter types in config
- New tags bar design instead of expand panel, easier to filter by tags
- New search bar integrated on tags bar
- Improve performance by showing less duplicate charts with tabs bar design
# RELEASE 1.1.0
## New Features
......
......@@ -17,14 +17,14 @@ export default {
},
data() {
return {
initialRoute: 'scalars',
initialRoute: 'metrics',
};
},
created() {
if (location.hash && location.hash != '#/') {
this.initialRoute = /(\#\/)(\w*)([?|&]{0,1})/.exec(location.hash)[2];
} else {
location.hash = '#/scalars';
location.hash = '#/metrics';
}
},
};
......
......@@ -72,29 +72,14 @@ export default {
selected: this.initialRoute,
items: [
{
url: '/scalars',
title: 'SCALARS',
name: 'scalars',
url: '/metrics',
title: 'METRICS',
name: 'metrics',
},
{
url: '/histograms',
title: 'HISTOGRAMS',
name: 'histograms',
},
{
url: '/images',
title: 'IMAGES',
name: 'images',
},
{
url: '/audio',
title: 'AUDIO',
name: 'audio',
},
{
url: '/texts',
title: 'TEXTS',
name: 'texts',
url: '/samples',
title: 'SAMPLES',
name: 'samples',
},
{
url: '/graphs',
......
<template>
<v-card
hover
color="tag_background"
class="visual-dl-tags-tab">
<div
@click="$emit('click')">
<span :class="active ? 'visual-dl-tags-tab-text-active':'visual-dl-tags-tab-text-inactive' ">{{ title }} &nbsp; ({{ total }})</span>
</div>
</v-card>
</template>
<script>
export default {
props: {
title: {
type: String,
required: true,
},
total: {
type: Number,
required: true,
},
active: {
type: Boolean,
required: true,
},
},
};
</script>
<style lang="stylus">
.visual-dl-tags-search-input
outline none
font-size 12px
.visual-dl-tags-tab
border-radius 17px
line-height 34px
height 34px
padding 0 14px
margin-right 16px
cursor pointer
position relative
display inline-block
.visual-dl-tags-tab-text-active
font-size 12px
font-weight bold
.visual-dl-tags-tab-text-inactive
font-size 12px
font-weight normal
color #555555
</style>
......@@ -11,7 +11,8 @@ Vue.use(Vuetify, {
primary: '#008c99',
accent: '#008c99',
toolbox_icon: '#999999',
dark_primary: '#00727c'
dark_primary: '#00727c',
tag_background: '#f5f5f5',
},
});
......
<template>
<div class="visual-dl-page-container">
<div class="visual-dl-page-left">
<div>
<v-card
hover
color="tag_background"
class="visual-dl-tags-tab">
<v-icon>search</v-icon>
<input type="search" v-model="config.groupNameReg"
autocomplete="false"
placeholder="Search tags in RegExp"
class="visual-dl-tags-search-input">
</v-card>
<ui-tags-tab
:total="tagsListCount(allTagsMatchingList)"
:title="config.groupNameReg.trim().length == 0 ? 'All' : config.groupNameReg"
:active="selectedGroup === '' "
@click="selectedGroup = '' "
/>
<ui-tags-tab
v-for="item in groupedTags"
:total="tagsListCount(item.tags)"
:title="item.group"
:active="item.group === selectedGroup"
@click="selectedGroup = item.group"
/>
</div>
<ui-chart-page
:config="config"
:tag-list="finalTagsList"
:total="tagsListCount(finalTagsList)"
/>
</div>
<div class="visual-dl-page-right">
<div class="visual-dl-page-config-container">
<ui-config
:config="config"
/>
</div>
</div>
</div>
</template>
<script>
import {getPluginScalarsTags, getPluginHistogramsTags} from '../service';
import {cloneDeep, flatten, uniq} from 'lodash';
import autoAdjustHeight from '../common/util/autoAdjustHeight';
import TagsTab from '../common/component/TagsTab';
import Config from './ui/Config';
import ChartPage from './ui/ChartPage';
export default {
components: {
'ui-config': Config,
'ui-chart-page': ChartPage,
'ui-tags-tab': TagsTab,
},
props: {
runs: {
type: Array,
required: true,
},
},
data() {
return {
tagInfo: {scalar: {}, histogram: {}},
config: {
groupNameReg: '',
// scalar 'enabled' will be false when no scalar logs available, 'display' is toggled by user in config
scalar: {enabled: false, display: false},
histogram: {enabled: false, display: false},
smoothing: 0.6,
horizontal: 'step',
sortingMethod: 'default',
outlier: false,
runs: [],
running: true,
chartType: 'offset',
},
filteredTagsList: {scalar: [], histogram: []},
selectedGroup: '',
};
},
computed: {
finalTagsList() {
if (this.selectedGroup === '') {
return this.allTagsMatchingList;
} else {
let list;
this.groupedTags.forEach((item) => {
if (item.group === this.selectedGroup) {
list = item.tags;
}
});
return list;
}
},
allTagsMatchingList() {
let list = cloneDeep(this.filteredTagsList);
list.scalar = this.filteredListByRunsForScalar(list.scalar);
list.histogram = this.filteredListByRunsForHistogram(list.histogram);
return list;
},
tagsList() {
let list = {};
Object.keys(this.tagInfo).forEach((type) => {
let tags = this.tagInfo[type];
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
let tagsForEachType = 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],
};
});
list[type] = tagsForEachType;
});
return list;
},
groupedTags() {
let tagsList = this.tagsList || [];
// put data in group
let groupData = {};
Object.keys(tagsList).forEach((type) => {
let tagsForEachType = tagsList[type];
tagsForEachType.forEach((item) => {
let group = item.group;
if (groupData[group] === undefined) {
groupData[group] = {};
}
if (groupData[group][type] === undefined) {
groupData[group][type] = [];
}
groupData[group][type].push(item);
});
});
// to array
let groups = Object.keys(groupData);
let groupList = groups.map((group) => {
groupData[group].scalar = this.filteredListByRunsForScalar(groupData[group].scalar);
groupData[group].histogram = this.filteredListByRunsForHistogram(groupData[group].histogram);
return {
group,
tags: groupData[group],
};
});
return groupList;
},
},
created() {
getPluginScalarsTags().then(({errno, data}) => {
if (!data) return;
this.tagInfo.scalar = data;
this.config.scalar.enabled = true;
this.config.scalar.display = true;
this.filterTagsList(this.config.groupNameReg);
});
getPluginHistogramsTags().then(({errno, data}) => {
if (!data) return;
this.tagInfo.histogram = data;
this.config.histogram.enabled = true;
this.config.histogram.display = true;
this.filterTagsList(this.config.groupNameReg);
});
this.config.runs = this.runs;
},
mounted() {
autoAdjustHeight();
},
watch: {
'config.groupNameReg': function(val) {
this.throttledFilterTagsList();
},
'runs': function(val) {
this.config.runs = val;
},
},
methods: {
filterTagsList(groupNameReg) {
if (!groupNameReg || groupNameReg.trim().length == 0) {
this.filteredTagsList = cloneDeep(this.tagsList);
return;
}
this.selectedGroup = '';
let tagsList = this.tagsList || {};
let regExp = new RegExp(groupNameReg);
Object.keys(tagsList).forEach((type) => {
let tagsForEachType = tagsList[type];
this.filteredTagsList[type] = tagsForEachType.filter((item) => regExp.test(item.tag));
});
},
throttledFilterTagsList: _.debounce(
function() {
this.filterTagsList(this.config.groupNameReg);
}, 300
),
filteredListByRunsForScalar(scalar) {
if (!this.config.scalar.display) return [];
let runs = this.config.runs || [];
let list = cloneDeep(scalar) || [];
list = list.map((item) => {
item.tagList = item.tagList.filter((one) => runs.includes(one.run));
return item;
});
return list.filter((item) => item.tagList.length > 0);
},
filteredListByRunsForHistogram(histogram) {
if (!this.config.histogram.display) return [];
let runs = this.config.runs || [];
let list = cloneDeep(histogram) || [];
return flatten(list.map((item) => {
return item.tagList.filter((one) => runs.includes(one.run));
}));
},
tagsListCount(tagsList) {
let count = 0;
if (tagsList.scalar !== undefined) count += tagsList.scalar.length;
if (tagsList.histogram !== undefined) count += tagsList.histogram.length;
return count;
},
},
};
</script>
<style lang="stylus">
</style>
import {min, max, range} from 'lodash';
export function tansformBackendData(histogramData) {
let [time, step, items] = histogramData;
return {
time,
step,
min: min(items.map(([left, right, count]) => left)),
max: max(items.map(([left, right, count]) => right)),
items: items.map(([left, right, count]) => ({left, right, count})),
};
}
export function computeNewHistogram(histogram, min, max, binsNum = 30) {
if (max === min) {
// Create bins even if all the data has a single value.
max = min * 1.1 + 1;
min = min / 1.1 - 1;
}
let stepWidth = (max - min) / binsNum;
let itemIndex = 0;
return range(min, max, stepWidth).map((binLeft) => {
let binRight = binLeft + stepWidth;
let yValue = 0;
while (itemIndex < histogram.items.length) {
let itemRight = Math.min(max, histogram.items[itemIndex].right);
let itemLeft = Math.max(min, histogram.items[itemIndex].left);
let overlap = Math.min(itemRight, binRight) - Math.max(itemLeft, binLeft);
let count = (overlap / (itemRight - itemLeft)) * histogram.items[itemIndex].count;
yValue += overlap > 0 ? count : 0;
// If `itemRight` is bigger than `binRight`, then this bin is
// finished and there also has data for the next bin, so don't increment
// `itemIndex`.
if (itemRight > binRight) {
break;
}
itemIndex++;
}
return {x: binLeft, dx: stepWidth, y: yValue};
});
}
export function tansformToVisData(tempData, time, step) {
return tempData.map(function(dataItem) {
return [time, step, dataItem.x + dataItem.dx / 2, Math.floor(dataItem.y)];
});
}
export function originDataToChartData(originData) {
let tempData = originData.map(tansformBackendData);
let globalMin = min(tempData.map(({min}) => min));
let globalMax = max(tempData.map(({max}) => max));
let chartData = tempData.map(function(item) {
let histoBins = computeNewHistogram(item, globalMin, globalMax);
let {time, step} = item;
return {
time,
step,
items: tansformToVisData(histoBins, time, step),
};
});
return {
min: globalMin,
max: globalMax,
chartData,
};
}
<template>
<div class="visual-dl-chart-page">
<div
ref="chartPageBox"
class="visual-dl-chart-page-box">
<ui-scalar-chart
v-for="(tagInfo, index) in filteredScalarTagList"
:tag-info="tagInfo"
:smoothing="config.smoothing"
:horizontal="config.horizontal"
:sorting-method="config.sortingMethod"
:outlier="config.outlier"
:runs="config.runs"
:running="config.running"
/>
<ui-histogram-chart
v-for="(tagInfo, index) in filteredHistogramTagList"
:tag-info="tagInfo"
:runs="config.runs"
:chart-type="config.chartType"
:running="config.running"
/>
</div>
<v-pagination
v-if="total > pageSize"
v-model="currentPage"
:length="pageLength"
/>
</div>
</template>
<script>
import ScalarChart from './ScalarChart';
import HistogramChart from './HistogramChart';
export default {
components: {
'ui-scalar-chart': ScalarChart,
'ui-histogram-chart': HistogramChart,
},
props: {
config: {
type: Object,
required: true,
},
tagList: {
type: Object,
required: true,
},
total: {
type: Number,
required: true,
},
},
data() {
return {
// current page
currentPage: 1,
// item per page
pageSize: 12,
};
},
computed: {
filteredScalarTagList() {
return this.tagList.scalar.slice((this.currentPage - 1) * this.pageSize, this.currentPage * this.pageSize);
},
filteredHistogramTagList() {
let offset = this.tagList.scalar.length;
let start = (this.currentPage - 1) * this.pageSize - offset;
if (start < 0) start = 0;
let end = this.currentPage * this.pageSize - offset;
if (end < 0) end = 0;
return this.tagList.histogram.slice(start, end);
},
pageLength() {
return Math.ceil(this.total / this.pageSize);
},
},
watch: {
'config.runs': function(val) {
this.currentPage = 1;
},
tagList: function(val) {
this.currentPage = 1;
},
},
};
</script>
<style lang="stylus">
@import '~style/variables';
+prefix-classes('visual-dl-')
.chart-page
.chart-page-box:after
content: "";
clear: both;
display: block;
padding-bottom: 2%
</style>
<template>
<div class="visual-dl-page-config-com">
<v-checkbox
class="visual-dl-page-config-checkbox"
label="Scalars"
v-model="config.scalar.display"
:disabled="!config.scalar.enabled"
dark/>
<div class="visual-dl-page-component-block">
<div class="visual-dl-page-control-block">
<span :class="'visual-dl-page-control-span' + (config.scalar.display ? '' : ' visual-dl-page-disabled-text')">Smoothing</span>
<v-slider
:max="0.99"
:min="0"
:step="0.01"
v-model="smoothingValue"
class="visual-dl-page-smoothing-slider"
dark
:disabled="!config.scalar.display"/>
<span :class="'visual-dl-page-slider-span' + (config.scalar.display ? '' : ' visual-dl-page-disabled-text')">{{ smoothingValue }}</span>
</div>
<div class="visual-dl-page-control-block">
<span :class="'visual-dl-page-control-span' + (config.scalar.display ? '' : ' visual-dl-page-disabled-text')">X-axis</span>
<v-select
:items="horizontalItems"
v-model="config.horizontal"
class="visual-dl-page-config-selector"
dark
dense
:disabled="!config.scalar.display"
/>
</div>
<div class="visual-dl-page-control-block">
<span :class="'visual-dl-page-control-span' + (config.scalar.display ? '' : ' visual-dl-page-disabled-text')">Tooltip sorting</span>
<v-select
:items="sortingMethodItems"
v-model="config.sortingMethod"
class="visual-dl-page-config-selector"
dark
dense
:disabled="!config.scalar.display"
/>
</div>
<v-checkbox
class="visual-dl-page-outliers-checkbox"
label="Ignore outliers in chart scaling"
v-model="config.outlier"
dark
:disabled="!config.scalar.display"/>
</div>
<v-checkbox
class="visual-dl-page-config-checkbox"
label="Histogram"
v-model="config.histogram.display"
:disabled="!config.histogram.enabled"
dark/>
<div class="visual-dl-page-component-block">
<div class="visual-dl-page-control-block">
<span :class="'visual-dl-page-control-span' + (config.histogram.display ? '' : ' visual-dl-page-disabled-text')">Mode</span>
<v-select
:items="chartTypeItems"
v-model="config.chartType"
class="visual-dl-page-config-selector"
dark
dense
:disabled="!config.histogram.display"
/>
</div>
</div>
<v-btn
:color="config.running ? 'primary' : 'error'"
v-model="config.running"
v-if="!isDemo"
@click="toggleAllRuns"
class="visual-dl-page-run-toggle"
dark
block
>
{{ config.running ? 'Running' : 'Stopped' }}
</v-btn>
</div>
</template>
<script>
export default {
props: {
config: {
type: Object,
required: true,
},
},
data() {
return {
horizontalItems: [
{
text: 'Step',
value: 'step',
},
{
text: 'Relative',
value: 'relative',
},
{
text: 'Wall Time',
value: 'wall',
},
],
sortingMethodItems: [
'default', 'descending', 'ascending', 'nearest',
],
chartTypeItems: [
{
text: 'Overlay',
value: 'overlay',
},
{
text: 'Offset',
value: 'offset',
},
],
smoothingValue: this.config.smoothing,
isDemo: process.env.NODE_ENV === 'demo',
};
},
watch: {
smoothingValue: _.debounce(
function() {
this.config.smoothing = this.smoothingValue;
}, 50
),
},
methods: {
toggleAllRuns() {
this.config.running = !this.config.running;
},
},
};
</script>
<style lang="stylus">
+prefix-classes('visual-dl-page-')
.config-com
padding 20px
.component-block
padding-left 33px
padding-bottom 20px
margin-top -10px
.control-block
height 36px
display flex
align-items center
.control-span
font-size 12px
width 110px
margin-top 8px
.disabled-text
opacity 0.5
.smoothing-slider
display inline
.slider-span
width 35px
font-size 13px
.run-toggle
margin-top 20px
.config-checkbox label
font-size 13px
font-weight bold
.outliers-checkbox
margin-top 10px
.outliers-checkbox label
font-size 12px
.input-group--select .input-group__selections__comma
font-size 12px
</style>
<template>
<v-card
hover
class="visual-dl-page-charts">
<div
class="visual-dl-chart-box"
ref="visual_dl_chart_box"/>
<div class="visual-dl-chart-actions">
<v-btn
color="toolbox_icon"
flat
icon
@click="isExpand = !isExpand"
class="chart-toolbox-icons" >
<img
v-if="!isExpand"
src="../../assets/ic_fullscreen_off.svg">
<img
v-if="isExpand"
src="../../assets/ic_fullscreen_on.svg">
</v-btn>
</div>
</v-card>
</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: {
type: Object,
required: true,
},
runs: {
type: Array,
required: true,
},
running: {
type: Boolean,
required: true,
},
chartType: {
type: String,
required: true,
},
},
data() {
return {
originData: [],
isExpand: false,
isDemo: process.env.NODE_ENV === 'demo',
};
},
watch: {
tagInfo: function(val) {
this.initChart(val);
this.stopInterval();
if (this.running && !this.isDemo) {
this.startInterval();
}
},
originData: function(val) {
this.initChartOption();
},
chartType: function(val) {
this.initChartOption();
},
running: function(val) {
(val && !this.isDemo) ? this.startInterval() : this.stopInterval();
},
isExpand: function(val) {
this.expandArea(val);
},
},
mounted() {
let tagInfo = this.tagInfo;
this.initChart(tagInfo);
if (this.running && !this.isDemo) {
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: 45,
top: 60,
right: 40,
bottom: 36,
};
let title = {
text: tag,
textStyle: {
fontSize: '12',
fontWeight: 'normal',
},
};
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 = 126;
grid.left = 16;
grid.right = 40;
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 = {
textStyle: {
fontFamily: 'Merriweather Sans',
},
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: {
fontSize: '11',
formatter: function(value) {
return Math.round(value * 100) / 100;
},
},
splitLine: {
show: false,
},
},
yAxis: {
position: 'right',
axisLine: {
onZero: false,
},
inverse: true,
splitLine: {
show: false,
},
axisLabel: {
fontSize: '11',
},
},
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: {
fontFamily: 'Merriweather Sans',
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: {
fontFamily: 'Merriweather Sans',
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: {
fontFamily: 'Merriweather Sans',
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(expand) {
let pageBoxWidth = document.getElementsByClassName('visual-dl-chart-page')[0].offsetWidth;
let width = pageBoxWidth * 0.96; // 4% margin
if (expand) {
let el = this.$refs.visual_dl_chart_box;
el.style.width = width + 'px';
el.style.height = '600px';
this.isExpand = true;
this.myChart.resize({
width: width,
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-page-charts
float left
margin 2% 2% 0 0
background #fff
padding 10px
position relative
.visual-dl-chart-box
width 400px
height 300px
.visual-dl-chart-actions
opacity 0
transition: opacity .3s ease-out;
position absolute
top 4px
right 10px
img
width 30px
height 30px
position absolute
top 0
bottom 0
margin auto
.chart-toolbox-icons
width 25px
height 25px
margin-left -4px
margin-right -4px
.visual-dl-page-charts:hover
.visual-dl-chart-actions
opacity 1
</style>
此差异已折叠。
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 Metrics from '@/metrics/Metrics';
import Samples from '@/samples/Samples';
import Graph from '@/graph/Graph';
import Texts from '@/texts/Texts';
import Audio from '@/audio/Audio';
import HighDimensional from '@/high-dimensional/HighDimensional';
Vue.use(Router);
......@@ -14,25 +11,17 @@ Vue.use(Router);
export default new Router({
routes: [
{
path: '/scalars',
name: 'Scalars',
component: Scalars,
path: '/metrics',
name: 'Metrics',
component: Metrics,
props: (route) => ({
runs: route.query.runs
})
},
{
path: '/histograms',
name: 'Histograms',
component: Histogram,
props: (route) => ({
runs: route.query.runs
})
},
{
path: '/images',
name: 'Images',
component: Images,
path: '/samples',
name: 'Samples',
component: Samples,
props: (route) => ({
runs: route.query.runs
})
......@@ -42,22 +31,6 @@ export default new Router({
name: 'Graph',
component: Graph,
},
{
path: '/texts',
name: 'Texts',
component: Texts,
props: (route) => ({
runs: route.query.runs
})
},
{
path: '/audio',
name: 'Audio',
component: Audio,
props: (route) => ({
runs: route.query.runs
})
},
{
path: '/HighDimensional',
name: 'HighDimensional',
......
<template>
<div class="visual-dl-page-container">
<div class="visual-dl-page-left">
<div>
<v-card
hover
color="tag_background"
class="visual-dl-tags-tab">
<v-icon>search</v-icon>
<input type="search" v-model="config.groupNameReg"
autocomplete="false"
placeholder="Search tags in RegExp"
class="visual-dl-tags-search-input">
</v-card>
<ui-tags-tab
:total="tagsListCount(allTagsMatchingList)"
:title="config.groupNameReg.trim().length == 0 ? 'All' : config.groupNameReg"
:active="selectedGroup === '' "
@click="selectedGroup = '' "
/>
<ui-tags-tab
v-for="item in groupedTags"
:total="tagsListCount(item.tags)"
:title="item.group"
:active="item.group === selectedGroup"
@click="selectedGroup = item.group"
/>
</div>
<ui-sample-page
:config="config"
:tag-list="finalTagsList"
:total="tagsListCount(finalTagsList)"
/>
</div>
<div class="visual-dl-page-right">
<div class="visual-dl-page-config-container">
<ui-config
:config="config"
/>
</div>
</div>
</div>
</template>
<script>
import {getPluginImagesTags, getPluginAudioTags, getPluginTextsTags} from '../service';
import {cloneDeep, flatten, uniq} from 'lodash';
import autoAdjustHeight from '../common/util/autoAdjustHeight';
import TagsTab from '../common/component/TagsTab';
import Config from './ui/Config';
import SamplePage from './ui/SamplePage';
export default {
name: 'Samples',
components: {
'ui-config': Config,
'ui-sample-page': SamplePage,
'ui-tags-tab': TagsTab,
},
props: {
runs: {
type: Array,
required: true,
},
},
data() {
return {
tagInfo: { image: {}, audio: {}, text: {} },
config: {
groupNameReg: '',
image: { enabled: false, display: false },
audio: { enabled: false, display: false },
text: { enabled: false, display: false },
isActualImageSize: false,
runs: [],
running: true,
},
filteredTagsList: { image: {}, audio: {}, text: {} },
selectedGroup: '',
};
},
computed: {
finalTagsList() {
if (this.selectedGroup === '') {
return this.allTagsMatchingList;
} else {
let list;
this.groupedTags.forEach((item) => {
if (item.group === this.selectedGroup) {
list = item.tags;
}
});
return list;
}
},
allTagsMatchingList() {
let list = cloneDeep(this.filteredTagsList);
this.filteredListByRuns(list);
return list;
},
tagsList() {
let list = {};
Object.keys(this.tagInfo).forEach((type) => {
let tags = this.tagInfo[type];
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
let tagsForEachType = 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],
};
});
list[type] = tagsForEachType;
});
return list;
},
groupedTags() {
let tagsList = this.tagsList || [];
// put data in group
let groupData = {};
Object.keys(tagsList).forEach((type) => {
let tagsForEachType = tagsList[type];
tagsForEachType.forEach((item) => {
let group = item.group;
if (groupData[group] === undefined) {
groupData[group] = {}
}
if (groupData[group][type] === undefined) {
groupData[group][type] = [];
}
groupData[group][type].push(item);
});
});
// to array
let groups = Object.keys(groupData);
let groupList = groups.map((group) => {
this.filteredListByRuns(groupData[group]);
return {
group,
tags: groupData[group],
};
});
return groupList;
},
},
created() {
getPluginImagesTags().then(({errno, data}) => {
if (!data) return;
this.tagInfo.image = data;
this.config.image.enabled = true;
this.config.image.display = true;
this.filterTagsList(this.config.groupNameReg);
});
getPluginAudioTags().then(({errno, data}) => {
if (!data) return;
this.tagInfo.audio = data;
this.config.audio.enabled = true;
this.config.audio.display = true;
this.filterTagsList(this.config.groupNameReg);
});
getPluginTextsTags().then(({errno, data}) => {
if (!data) return;
this.tagInfo.text = data;
this.config.text.enabled = true;
this.config.text.display = true;
this.filterTagsList(this.config.groupNameReg);
});
this.config.runs = this.runs;
},
mounted() {
autoAdjustHeight();
},
watch: {
'config.groupNameReg': function(val) {
this.throttledFilterTagsList();
},
runs: function(val) {
this.config.runs = val;
}
},
methods: {
filterTagsList(groupNameReg) {
if (!groupNameReg || groupNameReg.trim().length == 0) {
this.filteredTagsList = cloneDeep(this.tagsList);
return;
}
this.selectedGroup = '';
let tagsList = this.tagsList || [];
let regExp = new RegExp(groupNameReg);
Object.keys(tagsList).forEach((type) => {
let tagsForEachType = tagsList[type];
this.filteredTagsList[type] = tagsForEachType.filter((item) => regExp.test(item.tag));
});
},
throttledFilterTagsList: _.debounce(
function() {
this.filterTagsList(this.config.groupNameReg);
}, 300
),
filteredListByRuns(list) {
list.image = !this.config.image.display ? [] : this.filteredTypeByRuns(list.image);
list.audio = !this.config.audio.display ? [] : this.filteredTypeByRuns(list.audio);
list.text = !this.config.text.display ? [] : this.filteredTypeByRuns(list.text);
},
filteredTypeByRuns(tagList) {
let runs = this.config.runs || [];
let list = cloneDeep(tagList) || [];
return flatten(list.map((item) => {
return item.tagList.filter((one) => runs.includes(one.run));
}));
},
tagsListCount(tagsList) {
let count = 0;
if (tagsList.image !== undefined) count += tagsList.image.length;
if (tagsList.audio !== undefined) count += tagsList.audio.length;
if (tagsList.text !== undefined) count += tagsList.text.length;
return count;
},
},
};
</script>
<style lang="stylus">
</style>
<template>
<v-card
hover
class="visual-dl-audio">
<h3 class="visual-dl-audio-title">{{ tagInfo.tag.displayName }}
<span class="visual-dl-audio-run-icon">{{ tagInfo.run }}</span>
</h3>
<p>
<span>Step:</span>
<span>{{ audioData.step }}</span>
<span class="visual-del-audio-time">{{ audioData.wallTime | formatTime }}</span>
</p>
<v-slider
:max="steps"
:min="slider.min"
:step="1"
v-model="currentIndex"
/>
<audio
controls
:src="audioData.audioSrc">
Your browser does not support the audio element.
</audio>
</v-card>
</template>
<script>
import {getPluginAudioAudio} from '../../service';
// the time to refresh chart data
const intervalTime = 30;
export default {
props: {
tagInfo: {
type: Object,
required: true,
},
runs: {
type: Array,
required: true,
},
running: {
type: Boolean,
required: true,
},
},
computed: {
steps() {
let data = this.data || [];
return data.length - 1;
},
},
filters: {
formatTime: function(value) {
if (!value) {
return;
}
// The value was made in seconds, must convert it to milliseconds
let time = new Date(value * 1000);
let options = {
weekday: 'short', year: 'numeric', month: 'short',
day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit',
};
return time.toLocaleDateString('en-US', options);
},
},
data() {
return {
currentIndex: 0,
slider: {
value: '0',
label: '',
min: 0,
step: 1,
},
audioData: {},
data: [],
isDemo: process.env.NODE_ENV === 'demo',
};
},
created() {
this.getOriginAudioData();
},
mounted() {
if (this.running && !this.isDemo) {
this.startInterval();
}
},
beforeDestroy() {
this.stopInterval();
},
watch: {
running: function(val) {
(val && !this.isDemo) ? this.startInterval() : this.stopInterval();
},
currentIndex: function(index) {
if (this.data && this.data[index]) {
let currentAudioInfo = this.data ? this.data[index] : {};
let {query, step, wallTime} = currentAudioInfo;
let url = '/data/plugin/audio/individualAudio?ts=' + wallTime;
let audioSrc = [url, query].join('&');
this.audioData = {
audioSrc,
step,
wallTime,
};
}
},
tagInfo: function(val) {
this.currentIndex = 0;
this.getOriginAudioData();
}
},
methods: {
stopInterval() {
clearInterval(this.getOringDataInterval);
},
// get origin data per {{intervalTime}} seconds
startInterval() {
this.getOringDataInterval = setInterval(() => {
this.getOriginAudioData();
}, intervalTime * 1000);
},
getOriginAudioData() {
// 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,
};
getPluginAudioAudio(params).then(({status, data}) => {
if (status === 0) {
this.data = data;
this.currentIndex = data.length - 1;
}
});
},
},
};
</script>
<style lang="stylus">
.visual-dl-audio
font-size 12px
width 420px
float left
margin 20px 30px 10px 0
background #fff
padding 10px
.visual-dl-audio-title
font-size 14px
line-height 30px
.visual-dl-audio-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-del-audio-time
float right
</style>
<template>
<div class="visual-dl-page-config-com">
<v-checkbox
class="visual-dl-page-config-checkbox"
label="Image"
v-model="config.image.display"
:disabled="!config.image.enabled"
dark/>
<div class="visual-dl-page-component-block">
<v-checkbox
class="visual-dl-page-subconfig-checkbox"
label="Show actual image size"
v-model="config.isActualImageSize"
dark
:disabled="!config.image.display"/>
</div>
<v-checkbox
class="visual-dl-page-config-checkbox"
label="Audio"
v-model="config.audio.display"
:disabled="!config.audio.enabled"
dark/>
<v-checkbox
class="visual-dl-page-config-checkbox"
label="Text"
v-model="config.text.display"
:disabled="!config.text.enabled"
dark/>
<v-btn
:color="config.running ? 'primary' : 'error'"
v-model="config.running"
v-if="!isDemo"
@click="toggleAllRuns"
class="visual-dl-page-run-toggle"
dark
block
>
{{ config.running ? 'Running' : 'Stopped' }}
</v-btn>
</div>
</template>
<script>
export default {
props: {
config: {
type: Object,
required: true,
},
},
data() {
return {
isDemo: process.env.NODE_ENV === 'demo',
};
},
methods: {
toggleAllRuns() {
this.config.running = !this.config.running;
},
},
};
</script>
<style lang="stylus">
+prefix-classes('visual-dl-page-')
.config-com
padding 20px
.component-block
padding-left 33px
padding-bottom 20px
margin-top -10px
.disabled-text
opacity 0.5
.run-toggle
margin-top 20px
.config-checkbox label
font-size 13px
font-weight bold
.subconfig-checkbox
margin-top 10px
.subconfig-checkbox label
font-size 12px
.input-group--select .input-group__selections__comma
font-size 12px
</style>
<template>
<v-card
hover
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 class="visual-del-image-time">{{ imgData.wallTime | formatTime }}</span>
</p>
<v-slider
:max="steps"
:min="slider.min"
:step="1"
v-model="currentIndex"
/>
<img
:width="imageWidth"
:height="imageHeight"
:src="imgData.imgSrc" >
</v-card>
</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: {
type: Object,
required: true,
},
runs: {
type: Array,
required: true,
},
running: {
type: Boolean,
required: true,
},
isActualImageSize: {
type: Boolean,
required: true,
},
},
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;
}
// The value was made in seconds, must convert it to milliseconds
let time = new Date(value * 1000);
let options = {
weekday: 'short', year: 'numeric', month: 'short',
day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit',
};
return time.toLocaleDateString('en-US', options);
},
},
data() {
return {
currentIndex: 0,
slider: {
value: '0',
label: '',
min: 0,
step: 1,
},
imgData: {},
data: [],
height: defaultImgHeight,
weight: defaultImgWidth,
isDemo: process.env.NODE_ENV === 'demo',
};
},
created() {
this.getOriginChartsData();
},
mounted() {
if (this.running && !this.isDemo) {
this.startInterval();
}
},
beforeDestroy() {
this.stopInterval();
},
watch: {
running: function(val) {
(val && !this.isDemo) ? 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, wallTime} = currentImgInfo;
let url = '/data/plugin/images/individualImage?ts=' + wallTime;
let imgSrc = [url, query].join('&');
this.imgData = {
imgSrc,
height,
width,
step,
wallTime,
};
}
/* eslint-enable fecs-camelcase */
},
tagInfo: function(val) {
this.currentIndex = 0;
this.getOriginChartsData();
}
},
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-del-image-time
float right
</style>
<template>
<div class="visual-dl-chart-page">
<div class="visual-dl-sample-chart-box">
<ui-image
v-for="(tagInfo, index) in filteredImageTagList"
:key="index"
:tag-info="tagInfo"
:is-actual-image-size="config.isActualImageSize"
:runs="config.runs"
:running="config.running"
/>
</div>
<div class="visual-dl-sample-chart-box">
<ui-audio
v-for="(tagInfo, index) in filteredAudioTagList"
:key="index"
:tag-info="tagInfo"
:runs="config.runs"
:running="config.running"
/>
</div>
<div class="visual-dl-sample-chart-box">
<ui-text
v-for="(tagInfo, index) in filteredTextTagList"
:key="index"
:tag-info="tagInfo"
:runs="config.runs"
:running="config.running"
/>
</div>
<v-pagination
class="visual-dl-sm-pagination"
v-if="total > pageSize"
v-model="currentPage"
:length="pageLength"
/>
</div>
</template>
<script>
import Image from './Image';
import Audio from './Audio';
import Text from './Text';
export default {
components: {
'ui-image': Image,
'ui-audio': Audio,
'ui-text': Text,
},
props: {
config: {
type: Object,
required: true,
},
tagList: {
type: Object,
required: true,
},
total: {
type: Number,
required: true,
},
},
data() {
return {
// current page
currentPage: 1,
// item per page
pageSize: 12,
};
},
computed: {
filteredImageTagList() {
return this.tagList.image.slice((this.currentPage - 1) * this.pageSize, this.currentPage * this.pageSize);
},
filteredAudioTagList() {
let offset = this.tagList.image.length;
let start = (this.currentPage - 1) * this.pageSize - offset;
if (start < 0) start = 0;
let end = this.currentPage * this.pageSize - offset;
if (end < 0) end = 0;
return this.tagList.audio.slice(start, end);
},
filteredTextTagList() {
let offset = this.tagList.image.length + this.tagList.audio.length;
let start = (this.currentPage - 1) * this.pageSize - offset;
if (start < 0) start = 0;
let end = this.currentPage * this.pageSize - offset;
if (end < 0) end = 0;
return this.tagList.text.slice(start, end);
},
pageLength() {
return Math.ceil(this.total / this.pageSize);
},
},
watch: {
'config.runs': function(val) {
this.currentPage = 1;
},
tagList: function(val) {
this.currentPage = 1;
},
},
};
</script>
<style lang="stylus">
@import '~style/variables';
+prefix-classes('visual-dl-')
.chart-page
.sample-chart-box
overflow hidden
float left
.visual-dl-chart-image
float left
.sample-chart-box:after
content ""
clear both
display block
.sm-pagination
height 50px
float left
width 100%
</style>
<template>
<v-card
hover
class="visual-dl-text">
<h3 class="visual-dl-text-title">{{ tagInfo.tag.displayName }}
<span class="visual-dl-text-run-icon">{{ tagInfo.run }}</span>
</h3>
<p>
<span>Step:</span>
<span>{{ textData.step }}</span>
<span class="visual-del-text-time">{{ textData.wallTime | formatTime }}</span>
</p>
<v-slider
:max="steps"
:min="slider.min"
:step="1"
v-model="currentIndex"
/>
<p> {{ textData.message }} </p>
</v-card>
</template>
<script>
import {getPluginTextsTexts} from '../../service';
// the time to refresh chart data
const intervalTime = 30;
export default {
props: {
tagInfo: {
type: Object,
required: true,
},
runs: {
type: Array,
required: true,
},
running: {
type: Boolean,
required: true,
},
},
computed: {
steps() {
let data = this.data || [];
return data.length - 1;
},
},
filters: {
formatTime: function(value) {
if (!value) {
return;
}
// The value was made in seconds, must convert it to milliseconds
let time = new Date(value * 1000);
let options = {
weekday: 'short', year: 'numeric', month: 'short',
day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit',
};
return time.toLocaleDateString('en-US', options);
},
},
data() {
return {
currentIndex: 0,
slider: {
value: '0',
label: '',
min: 0,
step: 1,
},
textData: {},
data: [],
};
},
created() {
this.getOriginChartsData();
},
mounted() {
if (this.running) {
this.startInterval();
}
},
beforeDestroy() {
this.stopInterval();
},
watch: {
running: function(val) {
val ? this.startInterval() : this.stopInterval();
},
currentIndex: function(index) {
if (this.data && this.data[index]) {
let currentTextInfo = this.data ? this.data[index] : {};
let wallTime = currentTextInfo[0];
let step = currentTextInfo[1];
let message = currentTextInfo[2];
this.textData = {
step,
wallTime,
message,
};
}
},
tagInfo: function(val) {
this.currentIndex = 0;
this.getOriginChartsData();
}
},
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,
};
getPluginTextsTexts(params).then(({status, data}) => {
if (status === 0) {
this.data = data;
this.currentIndex = data.length - 1;
}
});
},
},
};
</script>
<style lang="stylus">
.visual-dl-text
font-size 12px
width 420px
float left
margin 20px 30px 10px 0
background #fff
padding 10px
.visual-dl-text-title
font-size 14px
line-height 30px
.visual-dl-text-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
.visual-del-text-time
float right
</style>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册