[UI] Replace vue-bar-graph
with chart.js
- The usage of the `vue-bar-graph` is complicated, because of the `GSAP` dependency they pull in, the dependency uses a non-free license. - The code is rewritten to use the `chart.js` library, which is already used to draw other charts in the activity tab. Due to the limitation of `chart.js`, we have to create a plugin in order to have images as labels and do click handling for those images. - The chart isn't the same as the previous one, once again simply due to how `chart.js` works, the amount of commits isn't drawn anymore in the bar, you instead have to hover over it or look at the y-axis. - Resolves #4569
This commit is contained in:
parent
3e8f975345
commit
a83002679d
7 changed files with 121 additions and 94 deletions
|
@ -1,14 +1,36 @@
|
|||
<script>
|
||||
import VueBarGraph from 'vue-bar-graph';
|
||||
import {Bar} from 'vue-chartjs';
|
||||
import {
|
||||
Chart,
|
||||
Tooltip,
|
||||
BarElement,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
} from 'chart.js';
|
||||
import {chartJsColors} from '../utils/color.js';
|
||||
import {createApp} from 'vue';
|
||||
|
||||
Chart.defaults.color = chartJsColors.text;
|
||||
Chart.defaults.borderColor = chartJsColors.border;
|
||||
|
||||
Chart.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
BarElement,
|
||||
Tooltip,
|
||||
);
|
||||
|
||||
const sfc = {
|
||||
components: {VueBarGraph},
|
||||
components: {Bar},
|
||||
props: {
|
||||
locale: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data: () => ({
|
||||
colors: {
|
||||
barColor: 'green',
|
||||
textColor: 'black',
|
||||
textAltColor: 'white',
|
||||
},
|
||||
|
||||
// possible keys:
|
||||
|
@ -18,42 +40,108 @@ const sfc = {
|
|||
// * login: (...)
|
||||
// * name: (...)
|
||||
activityTopAuthors: window.config.pageData.repoActivityTopAuthors || [],
|
||||
i18nCommitActivity: this,
|
||||
}),
|
||||
computed: {
|
||||
methods: {
|
||||
graphPoints() {
|
||||
return this.activityTopAuthors.map((item) => {
|
||||
return {
|
||||
value: item.commits,
|
||||
label: item.name,
|
||||
};
|
||||
});
|
||||
return {
|
||||
datasets: [{
|
||||
label: this.locale.commitActivity,
|
||||
data: this.activityTopAuthors.map((item) => item.commits),
|
||||
backgroundColor: this.colors.barColor,
|
||||
barThickness: 40,
|
||||
borderWidth: 0,
|
||||
tension: 0.3,
|
||||
}],
|
||||
labels: this.activityTopAuthors.map((item) => item.name),
|
||||
};
|
||||
},
|
||||
graphAuthors() {
|
||||
return this.activityTopAuthors.map((item, idx) => {
|
||||
return {
|
||||
position: idx + 1,
|
||||
...item,
|
||||
};
|
||||
});
|
||||
},
|
||||
graphWidth() {
|
||||
return this.activityTopAuthors.length * 40;
|
||||
getOptions() {
|
||||
return {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: true,
|
||||
scales: {
|
||||
x: {
|
||||
type: 'category',
|
||||
grid: {
|
||||
display: false,
|
||||
},
|
||||
ticks: {
|
||||
color: 'transparent', // Disable drawing of labels on the x-axis.
|
||||
},
|
||||
},
|
||||
y: {
|
||||
ticks: {
|
||||
stepSize: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const refStyle = window.getComputedStyle(this.$refs.style);
|
||||
const refAltStyle = window.getComputedStyle(this.$refs.altStyle);
|
||||
|
||||
this.colors.barColor = refStyle.backgroundColor;
|
||||
this.colors.textColor = refStyle.color;
|
||||
this.colors.textAltColor = refAltStyle.color;
|
||||
|
||||
for (const item of this.activityTopAuthors) {
|
||||
const img = new Image();
|
||||
img.src = item.avatar_link;
|
||||
item.avatar_img = img;
|
||||
}
|
||||
|
||||
Chart.register({
|
||||
id: 'image_label',
|
||||
afterDraw: (chart) => {
|
||||
const xAxis = chart.boxes[0];
|
||||
const yAxis = chart.boxes[1];
|
||||
for (const [index] of xAxis.ticks.entries()) {
|
||||
const x = xAxis.getPixelForTick(index);
|
||||
const img = this.activityTopAuthors[index].avatar_img;
|
||||
|
||||
chart.ctx.save();
|
||||
chart.ctx.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight, x - 10, yAxis.bottom + 10, 20, 20);
|
||||
chart.ctx.restore();
|
||||
}
|
||||
},
|
||||
beforeEvent: (chart, args) => {
|
||||
const event = args.event;
|
||||
if (event.type !== 'mousemove' && event.type !== 'click') return;
|
||||
|
||||
const yAxis = chart.boxes[1];
|
||||
if (event.y < yAxis.bottom + 10 || event.y > yAxis.bottom + 30) {
|
||||
chart.canvas.style.cursor = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const xAxis = chart.boxes[0];
|
||||
const pointIdx = xAxis.ticks.findIndex((_, index) => {
|
||||
const x = xAxis.getPixelForTick(index);
|
||||
return event.x >= x - 10 && event.x <= x + 10;
|
||||
});
|
||||
|
||||
if (pointIdx === -1) {
|
||||
chart.canvas.style.cursor = '';
|
||||
return;
|
||||
}
|
||||
|
||||
chart.canvas.style.cursor = 'pointer';
|
||||
if (event.type === 'click' && this.activityTopAuthors[pointIdx].home_link) {
|
||||
window.location.href = this.activityTopAuthors[pointIdx].home_link;
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export function initRepoActivityTopAuthorsChart() {
|
||||
const el = document.getElementById('repo-activity-top-authors-chart');
|
||||
if (el) {
|
||||
createApp(sfc).mount(el);
|
||||
createApp(sfc, {
|
||||
locale: {
|
||||
commitActivity: el.getAttribute('data-locale-commit-activity'),
|
||||
},
|
||||
}).mount(el);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,50 +150,6 @@ export default sfc; // activate the IDE's Vue plugin
|
|||
<template>
|
||||
<div>
|
||||
<div class="activity-bar-graph" ref="style" style="width: 0; height: 0;"/>
|
||||
<div class="activity-bar-graph-alt" ref="altStyle" style="width: 0; height: 0;"/>
|
||||
<vue-bar-graph
|
||||
:points="graphPoints"
|
||||
:show-x-axis="true"
|
||||
:show-y-axis="false"
|
||||
:show-values="true"
|
||||
:width="graphWidth"
|
||||
:bar-color="colors.barColor"
|
||||
:text-color="colors.textColor"
|
||||
:text-alt-color="colors.textAltColor"
|
||||
:height="100"
|
||||
:label-height="20"
|
||||
>
|
||||
<template #label="opt">
|
||||
<g v-for="(author, idx) in graphAuthors" :key="author.position">
|
||||
<a
|
||||
v-if="opt.bar.index === idx && author.home_link"
|
||||
:href="author.home_link"
|
||||
>
|
||||
<image
|
||||
:x="`${opt.bar.midPoint - 10}px`"
|
||||
:y="`${opt.bar.yLabel}px`"
|
||||
height="20"
|
||||
width="20"
|
||||
:href="author.avatar_link"
|
||||
/>
|
||||
</a>
|
||||
<image
|
||||
v-else-if="opt.bar.index === idx"
|
||||
:x="`${opt.bar.midPoint - 10}px`"
|
||||
:y="`${opt.bar.yLabel}px`"
|
||||
height="20"
|
||||
width="20"
|
||||
:href="author.avatar_link"
|
||||
/>
|
||||
</g>
|
||||
</template>
|
||||
<template #title="opt">
|
||||
<tspan v-for="(author, idx) in graphAuthors" :key="author.position">
|
||||
<tspan v-if="opt.bar.index === idx">
|
||||
{{ author.name }}
|
||||
</tspan>
|
||||
</tspan>
|
||||
</template>
|
||||
</vue-bar-graph>
|
||||
<Bar height="150px" :data="graphPoints()" :options="getOptions()"/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue