in client/src/components/special/timeline-chart/renderer/index.js [734:1080]
distance: getItemDistance(item)
});
}
}
}
});
candidates.sort((a, b) => a.distance - b.distance);
if (candidates.length > 0) {
const x = candidates[0].item.x;
return candidates
.filter((item) => item.item.x === x)
.map(({item, dataset}) => ({
item,
dataset
}));
}
return [];
}
getItemsByMouseEvent = (event) => {
const {
x,
valueX,
y,
hit
} = this.getMousePosition(event);
const result = [];
if (hit) {
switch (this.searchMode) {
case SEARCH_MODE.closest:
const item = this.searchClosestItem(x, y);
if (item) {
result.push(item);
}
break;
case SEARCH_MODE.vertical:
default:
const items = this.searchItemsByXValue(valueX);
result.push(...items);
break;
}
}
return result;
};
onResize () {
this.xAxis.resize(this.width, false);
this.yAxis.resize(this.height, false);
this.requestRender();
}
registerDatasets (datasets = []) {
const processed = datasets
.slice()
.map((dataset, index) => {
const {
data = [],
key = index,
...rest
} = dataset;
return {
...rest,
index,
key,
data: data
.map((item) => {
if (item === undefined || !isDate(item.date)) {
return undefined;
}
const {
value,
date,
...itemRest
} = item;
const {
unix: x,
date: dateValue
} = parseDate(date) || {};
return {
...itemRest,
value,
y: Number(value),
date,
x,
dateValue,
hide: !isNumber(item.value)
};
})
.filter((item) => item === undefined || item.x !== undefined)
};
});
let minimum = Infinity;
for (let processedDataset of processed) {
for (let item of processedDataset.data) {
if (item && minimum > item.x) {
minimum = item.x;
}
}
}
processed.forEach((dataset) => {
dataset.data.forEach((item, index, array) => {
if (item) {
item.x = item.x - minimum;
}
});
});
this.timelineStart = minimum;
this.datasets = processed;
this.xAxis.update(
this.datasets,
{
shift: this.timelineStart
}
);
this.yAxis.update(this.datasets);
this.xAxis.setPixelsOffset(20 + this.yAxis.largestLabelSize, false);
this.yAxis.setPixelsOffset(20 + this.xAxis.largestLabelSize, false);
this.datasetBuffers = [];
this.datasets.forEach((dataset) => {
this.datasetBuffers.push({
dataset,
buffers: this.buildDatasetBuffers(dataset)
});
});
this.coordinatesChanged = true;
this.requestRender();
this.adaptYAxisForVisibleElements();
return this;
}
registerDatasetsOptions (datasetsOptions = []) {
this.datasetsOptions = datasetsOptions.slice().map((datasetOptions, index) => {
const {
key = index,
...rest
} = datasetOptions || {};
return {
...rest,
key,
index
};
});
this.requestRender();
return this;
}
getDatasetOptions (dataset) {
const {
key
} = dataset;
return this.datasetsOptions.find((options) => options.key === key) || {};
}
buildDatasetBuffers (dataset) {
const {
data = []
} = dataset;
const sliced = data.reduce((result, item) => {
let last = result.length > 0 ? result[result.length - 1] : undefined;
if (item === undefined && last && last.length > 0) {
return [...result, []];
}
if (item && !last) {
last = [];
result.push(last);
}
if (item) {
last.push(item);
}
return result;
}, []);
const blocks = [];
sliced.forEach((sliceItem) => {
const maxItemsPerBlock = 1000;
for (let i = 0; i < sliceItem.length; i++) {
const item = sliceItem[i];
let current = blocks.length > 0 ? blocks[blocks.length - 1] : undefined;
if (!current || current.length >= maxItemsPerBlock) {
const last = current ? current[current.length - 1] : undefined;
current = [];
if (last) {
current.push({...last});
}
blocks.push(current);
}
current.push({...item});
}
});
blocks.forEach((block) => {
for (let i = 0; i < block.length; i++) {
const currentIndex = (i < block.length - 1) ? (i) : (block.length - 2);
const nextIndex = currentIndex + 1;
const current = block[currentIndex];
const next = block[nextIndex];
block[i].vector = {
x: next.x - current.x,
y: next.y - current.y
};
}
});
return blocks.map((block) => {
const filtered = block.filter(b => !b.hide);
return buildPathVAO(filtered, this.pathProgram);
});
}
draw () {
if (!this.canvas) {
return;
}
const gl = this.canvas.getContext('webgl2', {antialias: true});
const {
r, g, b
} = parseColor(this.backgroundColor);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.clearColor(r / 255.0, g / 255.0, b / 255.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
this.drawCoordinates(gl);
this.drawSelection(gl);
this.drawLines(gl);
this.drawPoints(gl);
this.drawText();
}
prepareChartContext (gl) {
if (!gl) {
return undefined;
}
gl.viewport(
this.xAxis.pixelsOffset * dpr,
this.yAxis.pixelsOffset * dpr,
this.xAxis.pixelsSize * dpr,
this.yAxis.pixelsSize * dpr
);
const projection = new Float32Array(buildOrthoMatrix({
top: this.yAxis.to,
bottom: this.yAxis.from,
left: this.xAxis.from,
right: this.xAxis.to
}));
const pixelResolution = new Float32Array([
2.0 / this.xAxis.pixelsSize,
2.0 / this.yAxis.pixelsSize
]);
return {
projection,
pixelResolution
};
}
drawLines (gl) {
const drawingContext = this.prepareChartContext(gl);
if (!drawingContext) {
return;
}
const {
projection,
pixelResolution
} = drawingContext;
// eslint-disable-next-line react-hooks/rules-of-hooks
usePathProgram(this.pathProgram, projection, IDENTITY_MATRIX, pixelResolution);
(this.datasetBuffers || []).forEach((datasetBuffer) => {
const {
dataset,
buffers
} = datasetBuffer;
const {
width = DEFAULT_LINE_WIDTH,
color = DEFAULT_CHART_LINE_COLOR
} = this.getDatasetOptions(dataset);
buffers.forEach((buffer) => {
drawPath(
this.pathProgram,
buffer,
{
width,
color
}
);
});
});
}
drawPoints (gl) {
const drawingContext = this.prepareChartContext(gl);
if (!drawingContext) {
return;
}
const {
projection,
pixelResolution
} = drawingContext;
// eslint-disable-next-line react-hooks/rules-of-hooks
usePathProgram(this.pathProgram, projection, IDENTITY_MATRIX, pixelResolution);
const pointsToDraw = [];
const maxItemsToDrawPoints = this.xAxis.pixelsSize / 5.0;
let total = 0;
for (let d = 0; d < (this.datasetBuffers || []).length; d += 1) {
const {
dataset,
buffers = []
} = this.datasetBuffers[d];
const datasetItemsToDraw = [];
for (let i = 0; i < buffers.length; i += 1) {
const {
items = []
} = buffers[i];
const filtered = items.filter((item) => this.xAxis.valueFitsRange(item.x));
total += filtered.length;
if (total > maxItemsToDrawPoints) {
break;
}
datasetItemsToDraw.push(...filtered);
}
if (total > maxItemsToDrawPoints) {
break;
}
pointsToDraw.push({
dataset,
items: datasetItemsToDraw
});
}
if ((this.hoveredItems || []).length || (total > 0 && total < maxItemsToDrawPoints)) {
// eslint-disable-next-line react-hooks/rules-of-hooks
useCircleProgram(this.circleProgram, projection, pixelResolution);
}
if (total > 0 && total < maxItemsToDrawPoints) {
pointsToDraw.forEach((datasetPoints) => {
const {
dataset,
items
} = datasetPoints;
const {
width = DEFAULT_LINE_WIDTH,
color: fill = DEFAULT_CHART_LINE_COLOR,
pointRadius = width
} = this.getDatasetOptions(dataset);
if (pointRadius > 0) {
items.forEach((item) => {
drawCircle(
this.circleProgram,
this.circleBuffer,
this.circleStrokeBuffer,
[item.x, item.y],
pointRadius,
{