提交 c814d33c 编写于 作者: A abigale Li 提交者: GitHub

update histogram layout and style (#84)

* modified something in frontend directory

* modified gitignore

* remove node_modules

* modify the data format for graph

* add label for each edge in the graph data formate

* merge scalars and image

* update histogram layout and style
上级 ae821e5e
......@@ -13,71 +13,103 @@ Each computation data flow graph is structured as a list of nodes that form the
## Rest API data format
Frontend uses rest API to get data from the server. The data format will be JSON. The data structure of a Graph is as below. Each Graph has three vectors:
- `node` represents Operator. It has type, input, and output.
- `node` represents Operator, it has type, input, and output.
- `input` represents input data or parameters, it has shape info.
- `output` represents output data of the graph, it has shape info.
- `edge` represents links in the graph, it has source, target and label.
```json
{
"graph": {
"nodes": [
{
"name": "op_1",
"type": "mul_op",
"input": [
"in1",
"w1"
],
"output": [
"out1",
"out2"
]
},
{
"name": "op_2",
"type": "add_op",
"input": [
"out1",
"b1"
],
"output": [
"out3"
]
}
],
"inputs": [
{
"name": "in1",
"shape": [
-1,
2
]
},
{
"name": "b1",
"shape": [
1,
3
]
},
{
"name": "w1",
"shape": [
2,
3
]
}
],
"outputs": [
{
"name": "out3",
"shape": [
-1,
3
]
}
]
}
"input": [
{
"data_type": "FLOAT",
"name": "X",
"shape": [
"1"
]
},
{
"data_type": "FLOAT",
"name": "W1",
"shape": [
"1"
]
},
{
"data_type": "FLOAT",
"name": "B1",
"shape": [
"1"
]
},
{
"data_type": "FLOAT",
"name": "W2",
"shape": [
"1"
]
},
{
"data_type": "FLOAT",
"name": "B2",
"shape": [
"1"
]
}
],
"name": "MLP",
"node": [
{
"input": [
"X",
"W1",
"B1"
],
"opType": "FC",
"output": [
"H1"
]
},
{
"input": [
"H1"
],
"opType": "Relu",
"output": [
"R1"
]
},
{
"input": [
"R1",
"W2",
"B2"
],
"opType": "FC",
"output": [
"Y"
]
}
],
"edge": [
{"source": "X", "target": "1", "label": "label1"},
{"source": "W1", "target": "1", "label": "label2"},
{"source": "B1", "target": "1", "label": "label3"},
{"source": "1", "target": "2", "label": "label4"},
{"source": "2", "target": "3", "label": "label5"},
{"source": "W2", "target": "3", "label": "label6"},
{"source": "B2", "target": "3", "label": "label7"},
{"source": "3", "target": "Y", "label": "label8"}
],
"output": [
{
"data_type": "FLOAT",
"name": "Y",
"shape": [
"1"
]
}
]
}
```
......
......@@ -107,7 +107,7 @@ export default {
attached() {
let tagInfo = this.data.get('tagInfo');
this.initCharts(tagInfo);
this.initChart(tagInfo);
if (this.data.get('running')) {
this.startInterval();
......@@ -129,10 +129,10 @@ export default {
this.data.set('downloadType', runsItems.find((item, index) => index === 0).value);
},
initCharts(tagInfo) {
initChart(tagInfo) {
this.createChart();
this.setChartsOptions(tagInfo);
this.getOriginChartsData(tagInfo);
this.getOriginChartData(tagInfo);
},
createChart() {
......@@ -248,7 +248,7 @@ export default {
startInterval() {
this.getOringDataInterval = setInterval(() => {
let tagInfo = this.data.get('tagInfo');
this.getOriginChartsData(tagInfo);
this.getOriginChartData(tagInfo);
}, intervalTime * 1000);
},
......@@ -256,7 +256,7 @@ export default {
clearInterval(this.getOringDataInterval);
},
getOriginChartsData({tagList, tag}) {
getOriginChartData({tagList, tag}) {
let requestList = tagList.map(item => {
let params = {
run: item.run,
......
......@@ -25,23 +25,23 @@ export default {
},
inited() {
this.watch('originData', data => {
this.initChartChartOption(data);
this.initChartOption(data);
});
this.watch('filtScreen', filtScreen => {
this.myChart.clear();
let data = this.data.get('originData');
this.initChartChartOption(data);
this.initChartOption(data);
});
},
attached() {
this.initCharts();
this.initChart();
},
initCharts(tagInfo) {
initChart(tagInfo) {
this.createChart();
this.getOriginChartsData();
this.getOriginChartData();
},
createChart() {
......@@ -49,14 +49,14 @@ export default {
this.myChart = echarts.init(el);
},
initChartChartOption(data) {
initChartOption(data) {
this.setChartOptions(data);
},
setChartOptions(data) {
this.myChart.setOption(data);
},
getOriginChartsData() {
getOriginChartData() {
getPluginGraphsGraphs().then(({data}) => {
this.data.set('originData', data);
});
......
import {min, max, range} from 'lodash';
export const tansformHistogramData = hitogramData => {
let [time, step, items] = hitogramData;
export const tansformBackendData = histogramData => {
let [time, step, items] = histogramData;
return {
time,
step,
......@@ -11,57 +11,54 @@ export const tansformHistogramData = hitogramData => {
};
};
export const computeTempDatas = (histogram, min, max, numbers = 30) => {
export const 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) / numbers;
let index = 0;
return range(min, max, stepWidth).map(left => {
let right = left + stepWidth;
let stepWidth = (max - min) / binsNum;
let itemIndex = 0;
return range(min, max, stepWidth).map(binLeft => {
let binRight = binLeft + stepWidth;
let yValue = 0;
while (index < histogram.items.length) {
let itemRight = Math.min(max, histogram.items[index].right);
let itemLeft = Math.max(min, histogram.items[index].left);
let intersect = Math.min(itemRight, right) - Math.max(itemLeft, left);
let count = (intersect / (itemRight - itemLeft))
* histogram.items[index].count;
yValue += intersect > 0 ? count : 0;
// If `bucketRight` is bigger than `binRight`, then this bin is
// finished and there is data for the next bin, so don't increment
// `index`.
if (itemRight > right) {
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;
}
index++;
itemIndex++;
}
return {x: left, dx: stepWidth, y: yValue};
return {x: binLeft, dx: stepWidth, y: yValue};
});
};
export const tansformToChartData
export const tansformToVisData
= (tempData, time, step) => tempData.map(({x, dx, y}) => [time, step, x + dx / 2, Math.floor(y)]);
export const originDataToChartData = originData => {
let tempDatas = originData.map(tansformHistogramData);
let finalMin = min(tempDatas.map(({min}) => min));
let finalMax = max(tempDatas.map(({max}) => max));
let tempDatas = originData.map(tansformBackendData);
let globalMin = min(tempDatas.map(({min}) => min));
let globalMax = max(tempDatas.map(({max}) => max));
let chartData = tempDatas.map(item => {
let computedTempDatas = computeTempDatas(item, finalMin, finalMax);
let histoBins = computeNewHistogram(item, globalMin, globalMax);
let {time, step} = item;
return {
time,
step,
items: tansformToChartData(computedTempDatas, time, step)
items: tansformToVisData(histoBins, time, step)
};
});
return {
min: finalMin,
max: finalMax,
min: globalMin,
max: globalMax,
chartData
};
};
......@@ -17,6 +17,7 @@ import Icon from 'san-mui/Icon';
// libs
import echarts from 'echarts';
import {min, max} from 'lodash';
import {originDataToChartData} from '../histogramHelper';
import {format, precisionRound} from 'd3-format';
......@@ -37,9 +38,11 @@ export default {
'san-button': Button,
'san-icon': Icon
},
computed: {
},
initData() {
return {
width: 400,
......@@ -52,21 +55,22 @@ export default {
]
};
},
inited() {
this.watch('originData', data => {
this.initChartChartOption();
this.initChartOption();
});
this.watch('chartType', chartType => {
this.initChartChartOption();
this.initChartOption();
});
},
attached() {
let tagInfo = this.data.get('tagInfo');
this.initCharts(tagInfo);
this.initChart(tagInfo);
this.initeChartsEvent();
this.initChartEvent();
if (this.data.get('running')) {
this.startInterval();
}
......@@ -79,10 +83,9 @@ export default {
this.stopInterval();
},
initCharts(tagInfo) {
initChart(tagInfo) {
this.createChart();
// this.setChartsOptions(tagInfo);
this.getOriginChartsData(tagInfo);
this.getOriginChartData(tagInfo);
},
createChart() {
......@@ -90,16 +93,17 @@ export default {
this.myChart = echarts.init(el);
},
initChartChartOption() {
initChartOption() {
this.myChart.clear();
let data = this.data.get('originData');
let chartData = originDataToChartData(data);
let visData = originDataToChartData(data);
let tagInfo = this.data.get('tagInfo');
let title = tagInfo.tag.displayName + '(' + tagInfo.run + ')';
let chartType = this.data.get('chartType');
this.setChartOptions(chartData, title, chartType);
this.setChartOptions(visData, title, chartType);
},
setChartOptions(chartData, tag, chartType) {
setChartOptions(visData, tag, chartType) {
let grid = {
left: '15%',
top: '20%',
......@@ -114,10 +118,10 @@ export default {
}
};
if (chartType === 'overlay') {
this.setOverlayChartOption(chartData, title, grid);
this.setOverlayChartOption(visData, title, grid);
}
else if (chartType === 'offset') {
this.setOffsetChartOption(chartData, title, grid);
this.setOffsetChartOption(visData, title, grid);
}
},
......@@ -181,12 +185,15 @@ export default {
let maxX = max;
let minZ = Infinity;
let maxZ = -Infinity;
let ecChart = this.myChart;
let maxStep = -Infinity;
let minStep = Infinity;
grid.top = '50%';
chartData.forEach(({items}) => {
chartData.forEach((dataItem) => {
let lineData = [];
items.forEach(([time, step, x, y]) => {
minX = Math.min(minX, x);
maxX = Math.max(maxX, x);
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);
......@@ -194,22 +201,21 @@ export default {
rawData.push(lineData);
});
// Max height in z axis.
let Z_HEIGHT = 130;
let Z_HEIGHT = 200;
let option = {
title,
color: ['#008c99'],
visualMap: {
type: 'continuous',
show: false,
min: 0,
max: 1000,
min: minStep,
max: maxStep,
dimension: 1,
inRange: {
colorLightness: [0.3, 0.7]
}
},
xAxis: {
// min: minX - (maxX - minX) * 0.3,
// max: maxX + (maxX - minX) * 0.3,
min: minX,
max: maxX,
axisLine: {
......@@ -221,6 +227,9 @@ export default {
},
yAxis: {
position: 'right',
axisLine: {
onZero: false
},
inverse: true,
splitLine: {
show: false
......@@ -230,40 +239,221 @@ export default {
series: [{
type: 'custom',
dimensions: ['x', 'y'],
renderItem(params, api) {
let points = [];
for (let i = 0; i < rawData[params.dataIndex].length;) {
let x = api.value(i++);
let y = api.value(i++);
let z = api.value(i++);
let pt = api.coord([x, y]);
// Linear map in z axis
pt[1] -= (z - minZ) / (maxZ - minZ) * Z_HEIGHT;
points.push(pt);
}
renderItem: function (params, api) {
let points = makePolyPoints(
params.dataIndex,
api.value,
api.coord
);
return {
type: 'polygon',
shape: {
points
},
style: api.style({
stroke: '#bbb',
lineWidth: 1
})
type: 'polygon',
silent: true,
shape: {
points
},
style: api.style({
stroke: '#bbb',
lineWidth: 1
})
};
},
data: rawData
}]
};
this.myChart.setOption(option);
function makePolyPoints(dataIndex, getValue, getCoord) {
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));
}
return points;
}
function getPoint(x, y, z, getCoord) {
let pt = getCoord([x, y]);
// linear map in z axis
pt[1] -= (z - minZ) / (maxZ - minZ) * Z_HEIGHT;
return pt;
}
let hoverLine;
let hoverDots = [];
let tooltip;
let tooltipX;
let tooltipY;
let zr = ecChart.getZr();
function removeTooltip(){
if (hoverLine) {
zr.remove(hoverLine);
zr.remove(tooltip);
hoverDots.forEach(dot => zr.remove(dot));
hoverDots.length = 0;
zr.remove(tooltipX);
zr.remove(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 linePoints = makePolyPoints(
nearestIndex.itemIndex,
function (i) {
return rawData[nearestIndex.itemIndex][i];
},
getCoord
);
zr.add(hoverLine = new echarts.graphic.Polyline({
silent: true,
shape: {
points: linePoints
},
style: {
stroke: '#9b9a9a',
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);
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);
hoverDots.push(dot);
});
let hoveredItem = chartData[nearestIndex.itemIndex];
tooltip = new echarts.graphic.Text({
position: [e.offsetX + 30, e.offsetY - 50],
style: {
text: hoveredItem.items[nearestIndex.binIndex][3],
textFill: '#000',
fontSize: 14,
textBackgroundColor: '#eee',
textBorderColor: '#008c99',
textBorderWidth: 2,
textBorderRadius: 5,
textPadding: 10,
rich: {}
},
z: 2000
});
zr.add(tooltip);
let gridRect = ecChart.getModel().getComponent('grid', 0).coordinateSystem.getRect();
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(tooltipX);
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(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);
},
// get origin data per 60 seconds
startInterval() {
this.getOringDataInterval = setInterval(() => {
let tagInfo = this.data.get('tagInfo');
this.getOriginChartsData(tagInfo);
this.getOriginChartData(tagInfo);
}, intervalTime * 1000);
},
......@@ -271,7 +461,7 @@ export default {
clearInterval(this.getOringDataInterval);
},
getOriginChartsData({run, tag}) {
getOriginChartData({run, tag}) {
let params = {
run,
tag: tag.displayName
......@@ -280,6 +470,7 @@ export default {
this.data.set('originData', data);
});
},
lightHoveredLine({seriesIndex}) {
let series = this.myChart.getOption().series;
let newSeries = series.map((item, index) => {
......@@ -297,7 +488,8 @@ export default {
series: newSeries
});
},
initeChartsEvent() {
initChartEvent() {
this.myChart.on('mousemove', params => {
let chartType = this.data.get('chartType');
if (chartType === 'overlay') {
......@@ -329,7 +521,6 @@ export default {
});
}
}
};
</script>
<style lang="stylus">
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册