Files
monibuca/plugin/hls/hls.js/chart/chartjs-horizontal-bar.ts
2024-11-15 14:05:58 +08:00

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;
}