mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-12-24 13:48:04 +08:00
294 lines
8.0 KiB
TypeScript
294 lines
8.0 KiB
TypeScript
import Chart from 'chart.js';
|
|
|
|
// Modify horizontalBar so that each dataset (fragments, timeRanges) draws on the same row (level, track or buffer)
|
|
Chart.controllers.horizontalBar.prototype.calculateBarValuePixels = function (
|
|
datasetIndex,
|
|
index,
|
|
options
|
|
) {
|
|
const chart = this.chart;
|
|
const scale = this._getValueScale();
|
|
const datasets = chart.data.datasets;
|
|
if (!datasets) {
|
|
throw new Error(`Chart datasets are ${datasets}`);
|
|
}
|
|
scale._parseValue = scaleParseValue;
|
|
const obj = datasets[datasetIndex].data[index];
|
|
const value = scale._parseValue(obj);
|
|
const start =
|
|
value.start === undefined
|
|
? 0
|
|
: value.max >= 0 && value.min >= 0
|
|
? value.min
|
|
: value.max;
|
|
const length =
|
|
value.start === undefined
|
|
? value.end
|
|
: value.max >= 0 && value.min >= 0
|
|
? value.max - value.min
|
|
: value.min - value.max;
|
|
const base = scale.getPixelForValue(start);
|
|
const head = scale.getPixelForValue(start + length);
|
|
const size = head - base;
|
|
|
|
return {
|
|
size: size,
|
|
base: base,
|
|
head: head,
|
|
center: head + size / 2,
|
|
};
|
|
};
|
|
|
|
Chart.controllers.horizontalBar.prototype.calculateBarIndexPixels = function (
|
|
datasetIndex,
|
|
index,
|
|
ruler,
|
|
options
|
|
) {
|
|
const rowHeight = options.barThickness;
|
|
const size = rowHeight * options.categoryPercentage;
|
|
const center = ruler.start + (datasetIndex * rowHeight + rowHeight / 2);
|
|
return {
|
|
base: center - size / 2,
|
|
head: center + size / 2,
|
|
center,
|
|
size,
|
|
};
|
|
};
|
|
|
|
Chart.controllers.horizontalBar.prototype.draw = function () {
|
|
const rects = this.getMeta().data;
|
|
const len = rects.length;
|
|
const dataset = this.getDataset();
|
|
if (len !== dataset.data.length) {
|
|
// View does not match dataset (wait for redraw)
|
|
return;
|
|
}
|
|
const chart = this.chart;
|
|
const scale = this._getValueScale();
|
|
scale._parseValue = scaleParseValue;
|
|
const ctx: CanvasRenderingContext2D = chart.ctx;
|
|
const chartArea: { left; top; right; bottom } = chart.chartArea;
|
|
Chart.helpers.canvas.clipArea(ctx, chartArea);
|
|
if (!this.lineHeight) {
|
|
this.lineHeight =
|
|
Math.ceil(ctx.measureText('0').actualBoundingBoxAscent) + 2;
|
|
}
|
|
const lineHeight = this.lineHeight;
|
|
let range = 0;
|
|
for (let i = 0; i < len; ++i) {
|
|
const rect = rects[i];
|
|
const view = rect._view;
|
|
if (!intersects(view.base, view.x, chartArea.left, chartArea.right)) {
|
|
// Do not draw elements outside of the chart's viewport
|
|
continue;
|
|
}
|
|
const obj = dataset.data[i];
|
|
const val = scale._parseValue(obj);
|
|
if (!isNaN(val.min) && !isNaN(val.max)) {
|
|
const { dataType } = obj;
|
|
let { stats } = obj;
|
|
const isPart = dataType === 'part';
|
|
const isFragmentHint = dataType === 'fragmentHint';
|
|
const isFragment = dataType === 'fragment' || isPart || isFragmentHint;
|
|
const isCue = dataType === 'cue';
|
|
if (isCue) {
|
|
view.y += view.height * 0.5 * (i % 2) - view.height * 0.25;
|
|
} else if (isPart) {
|
|
view.height -= 22;
|
|
}
|
|
const bounds = boundingRects(view);
|
|
const drawText = bounds.w > lineHeight * 1.5 && !isFragmentHint;
|
|
if (isFragment || isCue) {
|
|
if (drawText) {
|
|
view.borderWidth = 1;
|
|
if (i === 0) {
|
|
view.borderSkipped = false;
|
|
}
|
|
} else {
|
|
range =
|
|
range ||
|
|
scale.getValueForPixel(chartArea.right) -
|
|
scale.getValueForPixel(chartArea.left);
|
|
if (range > 300 || isCue) {
|
|
view.borderWidth = 0;
|
|
}
|
|
}
|
|
if (isFragmentHint) {
|
|
view.borderWidth = 0;
|
|
view.backgroundColor = 'rgba(0, 0, 0, 0.1)';
|
|
} else {
|
|
view.backgroundColor = `rgba(0, 0, 0, ${0.05 + (i % 2) / 12})`;
|
|
}
|
|
}
|
|
rect.draw();
|
|
if (isFragment) {
|
|
if (!stats) {
|
|
stats = {};
|
|
}
|
|
if (isPart) {
|
|
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
|
|
ctx.fillRect(bounds.x, bounds.y, bounds.w, bounds.h);
|
|
}
|
|
if (stats.aborted) {
|
|
ctx.fillStyle = 'rgba(100, 0, 0, 0.3)';
|
|
ctx.fillRect(bounds.x, bounds.y, bounds.w, bounds.h);
|
|
}
|
|
if (stats.loaded && stats.total) {
|
|
ctx.fillStyle = 'rgba(50, 20, 100, 0.3)';
|
|
ctx.fillRect(
|
|
bounds.x,
|
|
bounds.y,
|
|
(bounds.w * stats.loaded) / stats.total,
|
|
bounds.h
|
|
);
|
|
}
|
|
} else if (isCue) {
|
|
if (obj.active) {
|
|
ctx.fillStyle = 'rgba(100, 100, 10, 0.4)';
|
|
ctx.fillRect(bounds.x, bounds.y, bounds.w, bounds.h);
|
|
}
|
|
}
|
|
if (drawText) {
|
|
const start = val.start; // obj.start;
|
|
ctx.fillStyle = 'rgb(0, 0, 0)';
|
|
if (stats) {
|
|
const snBounds = Object.assign({}, bounds);
|
|
if (obj.cc) {
|
|
const ccLabel = `cc:${obj.cc}`;
|
|
const ccWidth = Math.min(
|
|
ctx.measureText(ccLabel).width + 2,
|
|
snBounds.w / 2 - 2
|
|
);
|
|
if (ccWidth) {
|
|
ctx.fillText(
|
|
ccLabel,
|
|
snBounds.x + 2,
|
|
snBounds.y + lineHeight,
|
|
snBounds.w / 2 - 4
|
|
);
|
|
snBounds.x += ccWidth;
|
|
snBounds.w -= ccWidth;
|
|
}
|
|
}
|
|
const snLabel = isPart ? `part: ${obj.index}` : `sn: ${obj.sn}`;
|
|
const textWidth = Math.min(
|
|
ctx.measureText(snLabel).width + 2,
|
|
snBounds.w - 2
|
|
);
|
|
ctx.fillText(
|
|
snLabel,
|
|
snBounds.x + snBounds.w - textWidth,
|
|
snBounds.y + lineHeight,
|
|
snBounds.w - 4
|
|
);
|
|
}
|
|
if (isCue) {
|
|
const strLength = Math.min(
|
|
30,
|
|
Math.ceil(bounds.w / (lineHeight / 3))
|
|
);
|
|
ctx.fillText(
|
|
('' + obj.content).slice(0, strLength),
|
|
bounds.x + 2,
|
|
bounds.y + bounds.h - 3,
|
|
bounds.w - 5
|
|
);
|
|
} else if (!isPart) {
|
|
const float = start !== (start | 0);
|
|
const fixedDigits = float
|
|
? Math.min(5, Math.max(1, Math.floor(bounds.w / 10 - 1)))
|
|
: 0;
|
|
const startString = hhmmss(start, fixedDigits);
|
|
ctx.fillText(
|
|
startString,
|
|
bounds.x + 2,
|
|
bounds.y + bounds.h - 3,
|
|
bounds.w - 5
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Chart.helpers.canvas.unclipArea(chart.ctx);
|
|
};
|
|
|
|
export function applyChartInstanceOverrides(chart) {
|
|
Object.keys(chart.scales).forEach((axis) => {
|
|
const scale = chart.scales![axis];
|
|
scale._parseValue = scaleParseValue;
|
|
});
|
|
}
|
|
|
|
function scaleParseValue(value: number[] | any) {
|
|
if (value === undefined) {
|
|
console.warn('Chart values undefined (update chart)');
|
|
return {};
|
|
}
|
|
|
|
let start;
|
|
let end;
|
|
let min;
|
|
let max;
|
|
|
|
if (Array.isArray(value)) {
|
|
start = +this.getRightValue(value[0]);
|
|
end = +this.getRightValue(value[1]);
|
|
min = Math.min(start, end);
|
|
max = Math.max(start, end);
|
|
} else {
|
|
start = +this.getRightValue(value.start);
|
|
if ('end' in value) {
|
|
end = +this.getRightValue(value.end);
|
|
} else {
|
|
end = +this.getRightValue(value.start + value.duration);
|
|
}
|
|
min = Math.min(start, end);
|
|
max = Math.max(start, end);
|
|
}
|
|
|
|
return {
|
|
min,
|
|
max,
|
|
start,
|
|
end,
|
|
};
|
|
}
|
|
|
|
function intersects(x1, x2, x3, x4) {
|
|
return x2 > x3 && x1 < x4;
|
|
}
|
|
|
|
function boundingRects(vm) {
|
|
const half = vm.height / 2;
|
|
const left = Math.min(vm.x, vm.base);
|
|
const right = Math.max(vm.x, vm.base);
|
|
const top = vm.y - half;
|
|
const bottom = vm.y + half;
|
|
return {
|
|
x: left,
|
|
y: top,
|
|
w: right - left,
|
|
h: bottom - top,
|
|
};
|
|
}
|
|
|
|
export function hhmmss(value, fixedDigits) {
|
|
const h = (value / 3600) | 0;
|
|
const m = ((value / 60) | 0) % 60;
|
|
const s = value % 60;
|
|
return `${h}:${pad(m, 2)}:${pad(
|
|
s.toFixed(fixedDigits),
|
|
fixedDigits ? fixedDigits + 3 : 2
|
|
)}`.replace(/^(?:0+:?)*(\d.*?)(?:\.0*)?$/, '$1');
|
|
}
|
|
|
|
function pad(str, length) {
|
|
str = '' + str;
|
|
while (str.length < length) {
|
|
str = '0' + str;
|
|
}
|
|
return str;
|
|
}
|