开源夏令营-《开源项目动态可视化展示》 #583

Closed
liuyishou wants to merge 5 commits from liuyishou/forgeplus-react:gitlink_server into gitlink_server
8 changed files with 1734 additions and 672 deletions

1344
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@ import SmoothLineTwo from './smoothLineTwo';
import Demo from './demo';
import ProjectScale from './ProjectScale';
import Pie from './pie';
import Echarts from './VisualEcharts/Echarts'
import ActivityItem from './ActivityItem';
@ -257,6 +258,7 @@ class Activity extends Component{
}
</div>
</div>
<Echarts {...this.props}></Echarts>
{
ai_shang_v1_url &&
<div className="normalBox mt20">

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,37 @@
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import '../activity.css';
import { getImageUrl } from 'educoder';
class CommitItem extends Component {
timeTransfer = (time_stamp)=>{
const time = new Date(time_stamp)
return `${time.getFullYear()}-${String(time.getMonth() + 1).padStart(2, '0')}-${String(time.getDate()).padStart(2, '0')} ${String(time.getHours()).padStart(2, '0')}:${String(time.getMinutes()).padStart(2, '0')}`
}
render() {
const { projectsId ,owner } = this.props.match.params;
const { commit } = this.props;
return (
<div className="activity_item">
<div className="flex1">
{
<p className="itemLine">
<Link to={`/${owner}/${projectsId}/tree/${commit.sha}`} className="color-blue font-16">{commit.sha}版本</Link>
</p >
}
<p className="itemLine mt10">
<Link to={`/${commit.author && commit.author.login}`} className="show-user-link">
<img alt="" src={getImageUrl(`/${commit && commit.author.image_url}`)} className="createImage" />
<span className="mr20">{commit && commit.author.name}</span>
</Link>
{commit.commit_time && <span className="color-grey-9">创建于<span className="ml2 color-grey-6">{this.timeTransfer(commit.commit_time * 1000)}</span></span>}
</p >
</div>
</div>
)
}
}
export default CommitItem;

View File

@ -0,0 +1,261 @@
import React, { Component } from 'react';
import echarts from 'echarts';
import '../activity.css'
import axios from 'axios'
import NoneData from '../../Nodata';
class Contributor extends Component {
constructor(props) {
super(props)
this.contributions_list = undefined,
this.contributors_total_count = undefined,
this.pulls_list = undefined,
this.pr_total_count = undefined
this.topBarEchart = undefined
this.PRPlotEchart = undefined
this.topBar = React.createRef();
this.PRPlot = React.createRef()
}
topYClick = (params)=>{
let login_name = undefined
this.contributions_list.forEach(contribution=>{
if(contribution.name == params.value){
login_name = contribution.login
}
})
window.open(`/${login_name}`, "_blank");
}
componentDidMount(){
const topChart = echarts.init(this.topBar.current);
var option;
option = {
dataset: [{
source: [['score','amount', 'product']],
},
],
toolbox: {
feature: {
saveAsImage: {}
},
top: "4%",
right:'5%'
},
title: {
left: 'center',
text: '贡献者排行榜',
textStyle: {
fontSize: 13
},
top: '5%'
},
grid: { containLabel: true },
xAxis: { name: '贡献数', max: 100 },
yAxis: { type: 'category', triggerEvent: true,},
visualMap: {
orient: 'horizontal',
left: 'center',
text: ['High Score', 'Low Score'],
dimension: 0,
inRange: {
color: ['#65B581', '#FFCE34', '#FD665F']
},
bottom: "5%"
},
series: [
{
type: 'bar',
encode: {
x: 'amount',
y: 'product'
}
}
]
};
topChart.setOption(option)
this.topBarEchart = topChart
topChart.on('click', 'yAxis.category', this.topYClick)
const PRPlot = echarts.init(this.PRPlot.current);
var option;
// 由于未提供 PR 信息,暂使用模拟数据替代
let base = +new Date(1988, 9, 3);
let oneDay = 24 * 3600 * 1000;
let data = [[base, Math.random() * 300]];
for (let i = 1; i < 20000; i++) {
let now = new Date((base += oneDay));
data.push([+now, Math.round((Math.random() - 0.5) * 20 + data[i - 1][1])]);
}
option = {
tooltip: {
trigger: 'axis',
position: function (pt) {
return [pt[0], '10%'];
}
},
title: {
left: 'center',
text: '请求(PR)分布',
textStyle: {
fontSize: 13
},
top: '5%'
},
toolbox: {
feature: {
restore: {},
saveAsImage: {}
}
},
xAxis: {
type: 'time',
boundaryGap: false
},
yAxis: {
type: 'value',
boundaryGap: [0, '100%']
},
dataZoom: [
{
type: 'inside',
start: 0,
end: 20
},
{
start: 0,
end: 20
}
],
legend:{
data: ['开启中', '已合并', '已拒绝'],
top: '10%'
},
series: [
{
name: '开启中',
type: 'line',
smooth: true,
symbol: 'none',
areaStyle: {},
data: data
},
{
name: '已合并',
type: 'line',
smooth: true,
symbol: 'none',
areaStyle: {},
data: data
},
{
name: '已拒绝',
type: 'line',
smooth: true,
symbol: 'none',
areaStyle: {},
data: data
},
]
};
PRPlot.setOption(option);
this.PRPlotEchart = PRPlot
}
dataUpdate = (contributions_list, pulls_list)=>{
this.contributions_list = contributions_list
this.pulls_list = pulls_list
let contributions = contributions_list
let top_source = []
contributions.sort((a, b)=> a.contributions - b.contributions)
contributions.forEach(contribution=>{
let name = contribution.name
if(name.length > 8){
name = name.slice(0,8) + '...'
}
top_source.push([contribution.contributions, contribution.contributions, name])
})
const bar_x_max = parseInt(top_source[top_source.length-1][0] * 1.1)
const bar_x_min = parseInt(top_source[0][0] * 0.9)
if (this.topBar.current){
let myChart = this.topBarEchart
let title = undefined
if (top_source.length >= 10){
title = "贡献 Top 10 排行榜 (总数: "+ top_source.length +")"
}else{
title = "贡献值排行榜"
}
myChart.setOption(
{
title: {
left: 'center',
text: title,
textStyle: {
fontSize: 13
},
top: '5%'
},
dataset: [
{
dimensions: ['score', 'amount', 'product'],
source: top_source
},
{
transform: {
type: 'sort',
config: { dimension: 'score', order: 'desc' }
}
},
],
tooltip : {
formatter: function (params) {
return `${params.data[2]}贡献: ${params.data[0]}`
}
},
xAxis: { name: '贡献数', max: bar_x_max, minInterval: 1,},
visualMap: {
orient: 'horizontal',
left: 'center',
text: ['High Score', 'Low Score'],
min: bar_x_min,
max: bar_x_max,
dimension: 0,
inRange: {
color: ['#65B581', '#FFCE34', '#FD665F']
},
bottom: "5%"
},
})
if(top_source.length == 0){
myChart = <NoneData _html="暂时还没有相关数据!" />
}
}
}
render() {
const {contributions_list, contributors_total_count
,pulls_list, pr_total_count} = this.props
// && pulls_list
if(contributions_list){
this.dataUpdate(contributions_list, pulls_list)
}
return (
<div style={{width: '100%', overflow: 'hidden'}}>
<div ref={this.topBar} style={{
height: 400,
width: "50%",
float: 'left'}}></div>
<div ref={this.PRPlot} style={{
height: 400,
width: "50%",
float: 'left'
}}></div>
</div>
)
}
}
export default Contributor

View File

@ -0,0 +1,151 @@
import React, { Component } from 'react';
import echarts from 'echarts';
import '../activity.css'
import axios from 'axios'
import Code from './Code'
import Contributor from './Contributor'
import GitHistory from './GitHistory'
import ForkList from './ForkList'
// 负责数据获取业务,并安排四个子模块的位置
class Echarts extends Component {
constructor(props) {
super(props)
this.state = {
contributions_list: undefined,
contributors_total_count: undefined,
pr_total_count: undefined,
pulls_list: undefined,
pr_open_count: undefined,
pr_close_count: undefined,
pr_merged_count: undefined,
commits: undefined,
commits_count: undefined,
}
this.eChartsRef = React.createRef();
}
// /api/v1/:owner/:repo/commits.json?owner=&repo=&page=&limit=
getInfoCommits = ()=>{
const { projectsId , owner } = this.props.match.params;
const url = `/v1/${owner}/${projectsId}/commits.json`;
const limit = 50
axios.get(url,{
params:{
owner: owner, repo: projectsId
}
}).then(result=>{
if(result){
this.setState({
commits_count: result.data.total_count,
})
const round = Math.ceil(this.state.commits_count / limit)
let commits = new Array()
for (var i = 0; i <= round - 1; i++) {
axios.get(url,{
params:{
owner: owner, repo: projectsId, limit: 50, page: i + 1
}
}).then(result=>{
if(result){
commits = commits.concat(result.data.commits)
if(this.state.commits_count == commits.length){
this.setState({
commits: commits})
}
}
})
}
}
})
}
// GET /api/:owner/:repo/contributors.json
getInfoContributors = ()=>{
const { projectsId , owner } = this.props.match.params;
const url = `/${owner}/${projectsId}/contributors.json`;
axios.get(url,{
params:{
owner: owner, repo: projectsId
}
}).then(result=>{
if(result){
this.setState(
{
contributors_total_count: result.data.total_count,
contributions_list: result.data.list.reverse(),
})
}
})
}
// PR数据接口/api/v1/:owner/:repo/pulls.json?owner=&repo=
getInfoPR = () => {
const { projectsId , owner } = this.props.match.params;
const url = `/${owner}/${projectsId}/pulls.json`;
axios.get(url,{
params:{
owner: owner, repo: projectsId
}
}).then(result=>{
if(result){
this.setState(
{
pr_total_count: result.data.search_count,
pr_open_count:result.data.open_count,
pr_close_count:result.data.close_count,
pr_merged_count:result.data.merged_issues_size,
pulls_list: result.data.issues,
})
}
})
}
componentDidMount(){
this.getInfoContributors()
this.getInfoPR()
this.getInfoCommits()
}
render() {
const {
contributions_list,
contributors_total_count,
pr_total_count,
pulls_list,
commits_count,
commits
} = this.state
const figures = new Array(
<Code {...this.props}></Code>,
<Contributor {...this.props} contributions_list={contributions_list}
contributors_total_count={contributors_total_count}
pr_total_count={pr_total_count}
pulls_list={pulls_list}></Contributor>,
<GitHistory {...this.props} commits={commits}
commits_count={commits_count}></GitHistory>,
<ForkList {...this.props} commits={commits}
commits_count={commits_count}></ForkList>);
const figure_title = new Array("代码统计", "贡献者",
"历史变更图", "复刻列表");
const contents = []
figures.forEach((figure, index) => {
contents.push(<div className="normalBox" style={{marginTop: 10}}>
<div class="normalBox-title">{figure_title[index]}</div>
<div class="boxpart">{figure}</div>
</div>)}
)
return (
<div>
{contents}
</div>
)
}
}
export default Echarts;

View File

@ -0,0 +1,238 @@
import React, { Component } from 'react';
import echarts from 'echarts';
import '../activity.css'
import CommitItem from './CommitItem'
import NoneData from '../../Nodata';
class ForkList extends Component {
constructor(props) {
super(props)
this.state = {
dataZoomStart: undefined,
dataZoomEnd: undefined,
seCommitsList: undefined
}
this.time = {
minDate: undefined,
maxDate:undefined,
during_time_stamp:undefined
}
this.commits = undefined
this.eChartsRef = React.createRef();
this.myChart = undefined;
this.dataZoomFunction = this.dataZoomFunction.bind(this)
}
componentDidMount(){
const myChart = echarts.init(this.eChartsRef.current);
this.myChart = myChart
var option;
option = {
tooltip : {
},
title: {
left: 'center',
text: '复刻(Fork)分布',
textStyle: {
fontSize: 13
},
top: '5%'
},
toolbox: {
feature: {
restore: {},
saveAsImage: {}
}
},
xAxis: {
type: 'time',
boundaryGap: false
},
yAxis: {
type: 'value',
boundaryGap: [0, '100%']
},
dataZoom: [
{
type: 'inside',
start: 0,
end: 1000000
},
{
start: 0,
end: 1000000
}
],
series: [
{
type: 'bar',
symbol: 'none',
areaStyle: {},
data: undefined
},
],
barCategoryGap: '80%'
};
myChart.setOption(option);
myChart.on('dataZoom', "mouseup", this.dataZoomFunction);
myChart.getZr().on('click', this.dataBarFunction)
// 延时两秒初始化commit列表
setTimeout(this.layoutTime, 5000);
}
layoutTime = ()=>{
if(this.commits){
let commits = this.commits
commits.sort((a, b) => b.commit_time - a.commit_time)
this.setState({
seCommitsList: commits
})
}
}
timeTransfer = (time_stamp)=>{
const time = new Date(time_stamp)
return `${time.getFullYear()}-${String(time.getMonth() + 1).padStart(2, '0')}-${String(time.getDate()).padStart(2, '0')}`
}
dataBarFunction = (params)=>{
let pointInPixel = [params.offsetX, params.offsetY]
let xIndex = undefined
if (this.myChart.containPixel('grid', pointInPixel)) {
xIndex = this.myChart.convertFromPixel({ seriesIndex: 0 }, [params.offsetX, params.offsetY])[0]
}
const bar_date = this.timeTransfer(xIndex)
let seCommitsList = new Array()
if(this.commits){
this.commits.forEach(commit=>{
if(this.timeTransfer(commit.commit_time * 1000) == bar_date){
seCommitsList.push(commit)
}
})
}
this.setState({
seCommitsList
})
}
dataZoomFunction = (params)=>{
let start = undefined
let end = undefined
let seCommitsList = new Array()
if (params.batch){
start = params.batch[0].start
end = params.batch[0].end
}
else{
start = params.start
end = params.end
}
let boundary = 3600 * 24 * 1000
start = new Date(+this.time.minDate + (start/100) * this.time.during_time_stamp - boundary)
end = new Date(+this.time.minDate + (end/100) * this.time.during_time_stamp + boundary)
if(this.commits){
this.commits.forEach(commit=>{
if((start <= commit.commit_time * 1000) && (end >= commit.commit_time * 1000)){
seCommitsList.push(commit)
}
})
}
seCommitsList.sort((a, b) => b.commit_time - a.commit_time);
this.setState({
start,
end,
seCommitsList
})
}
dataUpdate = (commits)=>{
let data = new Array()
const countByDay = {};
commits.forEach(commit=>{
const dayString = this.timeTransfer(commit.commit_time * 1000);
countByDay[dayString] = (countByDay[dayString] || 0) + 1;
})
for (const date in countByDay) {
const count = countByDay[date];
data.push([+new Date(date), count]);
}
const dates = Object.keys(countByDay).map(dateString => new Date(dateString));
const minDate = new Date(Math.min(...dates));
const maxDate = new Date(Math.max(...dates));
const during_time_stamp = (+maxDate) - (+minDate)
this.time = {
minDate,
maxDate,
during_time_stamp,
}
if(this.myChart){
let myChart = this.myChart
myChart.setOption(
{
tooltip: {
trigger: 'axis',
formatter: function (params) {
const time = new Date(params[0].axisValue)
return `${time.getFullYear()}${time.getMonth() + 1}${time.getDate()}日</br> 提交次数:${params[0].data[1]}`
}
},
// 数据为仿真数据,后端未提供 fork 信息数据接口
series: [
{
type: 'bar',
symbol: 'none',
areaStyle: {},
data: data
},
],
}
)
}
}
render() {
const {commits, commits_count} = this.props
const {dataZoomStart, dataZoomEnd, seCommitsList} = this.state
if(commits_count && commits &&(commits.length == commits_count)){
this.commits = commits
this.dataUpdate(commits)
}
return (
<div style={{width: '100%', overflow: 'hidden'}}>
<div ref={this.eChartsRef} style={{
height: 400,
width: "50%",
float: 'left'}}></div>
<div style={{
height: 400,
width: "50%",
float: 'left',
overflow: 'auto'
}}>
{
seCommitsList && seCommitsList.length > 0 ?
<div className="activity_list">
{
seCommitsList && seCommitsList.map(commit=>{
return (<CommitItem {...this.props} commit={commit} ></CommitItem>)
})
}
</div>
:
<NoneData _html="暂时还没有相关数据!" />
}
</div>
</div>
)
}
}
export default ForkList

View File

@ -0,0 +1,239 @@
import React, { Component } from 'react';
import echarts from 'echarts';
import '../activity.css'
import CommitItem from './CommitItem'
import NoneData from '../../Nodata';
class GitHistory extends Component {
constructor(props) {
super(props)
this.state = {
dataZoomStart: undefined,
dataZoomEnd: undefined,
seCommitsList: undefined
}
this.time = {
minDate: undefined,
maxDate:undefined,
during_time_stamp:undefined
}
this.commits = undefined
this.myChart = undefined;
this.eChartsRef = React.createRef();
this.dataZoomFunction = this.dataZoomFunction.bind(this)
}
componentDidMount(){
const myChart = echarts.init(this.eChartsRef.current);
this.myChart = myChart
var option;
option = {
tooltip : {
},
title: {
left: 'center',
text: '提交(Commit)分布',
textStyle: {
fontSize: 13
},
top: '5%'
},
toolbox: {
feature: {
restore: {},
saveAsImage: {}
}
},
xAxis: {
type: 'time',
// boundaryGap: false
},
yAxis: {
type: 'value',
boundaryGap: [0, '100%']
},
dataZoom: [
{
type: 'inside',
start: 0,
end: 1000000
},
{
start: 0,
end: 1000000
}
],
series: [
{
type: 'bar',
// smooth: true,
symbol: 'none',
areaStyle: {},
data: undefined
},
],
barCategoryGap: '80%'
};
myChart.setOption(option);
myChart.on('dataZoom', "mouseup", this.dataZoomFunction);
myChart.getZr().on('click', this.dataBarFunction)
// 延时两秒初始化commit列表
setTimeout(this.layoutTime, 5000);
}
layoutTime = ()=>{
if(this.commits){
let commits = this.commits
commits.sort((a, b) => b.commit_time - a.commit_time)
this.setState({
seCommitsList: commits
})
}
}
timeTransfer = (time_stamp)=>{
const time = new Date(time_stamp)
return `${time.getFullYear()}-${String(time.getMonth() + 1).padStart(2, '0')}-${String(time.getDate()).padStart(2, '0')}`
}
dataBarFunction = (params)=>{
let pointInPixel = [params.offsetX, params.offsetY]
let xIndex = undefined
if (this.myChart.containPixel('grid', pointInPixel)) {
xIndex = this.myChart.convertFromPixel({ seriesIndex: 0 }, [params.offsetX, params.offsetY])[0]
}
const bar_date = this.timeTransfer(xIndex)
let seCommitsList = new Array()
if(this.commits){
this.commits.forEach(commit=>{
if(this.timeTransfer(commit.commit_time * 1000) == bar_date){
seCommitsList.push(commit)
}
})
}
this.setState({
seCommitsList
})
}
dataZoomFunction = (params)=>{
let start = undefined
let end = undefined
let seCommitsList = new Array()
if (params.batch){
start = params.batch[0].start
end = params.batch[0].end
}
else{
start = params.start
end = params.end
}
let boundary = 3600 * 24 * 1000
start = new Date(+this.time.minDate + (start/100) * this.time.during_time_stamp - boundary)
end = new Date(+this.time.minDate + (end/100) * this.time.during_time_stamp + boundary)
if(this.commits){
this.commits.forEach(commit=>{
if((start <= commit.commit_time * 1000) && (end >= commit.commit_time * 1000)){
seCommitsList.push(commit)
}
})
}
seCommitsList.sort((a, b) => b.commit_time - a.commit_time);
this.setState({
start,
end,
seCommitsList
})
}
dataUpdate = (commits)=>{
let data = new Array()
const countByDay = {};
commits.forEach(commit=>{
const dayString = this.timeTransfer(commit.commit_time * 1000);
countByDay[dayString] = (countByDay[dayString] || 0) + 1;
})
for (const date in countByDay) {
const count = countByDay[date];
data.push([+new Date(date), count]);
}
const dates = Object.keys(countByDay).map(dateString => new Date(dateString));
const minDate = new Date(Math.min(...dates));
const maxDate = new Date(Math.max(...dates));
const during_time_stamp = (+maxDate) - (+minDate)
this.time = {
minDate,
maxDate,
during_time_stamp,
}
if(this.myChart){
let myChart = this.myChart
myChart.setOption(
{
tooltip: {
trigger: 'axis',
formatter: function (params) {
const time = new Date(params[0].axisValue)
return `${time.getFullYear()}${time.getMonth() + 1}${time.getDate()}日</br> 提交次数:${params[0].data[1]}`
}
},
series: [
{
type: 'bar',
// smooth: true,
symbol: 'none',
areaStyle: {},
data: data
},
],
}
)
}
}
render() {
const {commits, commits_count} = this.props
const {dataZoomStart, dataZoomEnd, seCommitsList} = this.state
if(commits_count && commits &&(commits.length == commits_count)){
this.commits = commits
this.dataUpdate(commits)
}
return (
<div style={{width: '100%', overflow: 'hidden'}}>
<div ref={this.eChartsRef} style={{
height: 400,
width: "50%",
float: 'left'}}></div>
<div style={{
height: 400,
width: "50%",
float: 'left',
overflow: 'auto'
}}>
{
seCommitsList && seCommitsList.length > 0 ?
<div className="activity_list">
{
seCommitsList && seCommitsList.map(commit=>{
return (<CommitItem {...this.props} commit={commit} ></CommitItem>)
})
}
</div>
:
<NoneData _html="暂时还没有相关数据!" />
}
</div>
</div>
)
}
}
export default GitHistory