我在React应用程序中使用Web Worker处理数据以在条形图中查看。我有一个BarChart组件。我在componentDidMount上实例化worker,并将消息发布给worker以格式化componentDidMount和componentDidUpdate上的数据。它在初始渲染时效果很好,但是当我从UI修改道具以修改图表视图时,由于某种原因该消息不会发送给工作人员。它会重新呈现,但不会与新数据一起呈现。有人知道为什么吗?
这是我的组成部分:
import React from "react";
import { Bar } from "components/_shared/react-chartjs";
import datalabels from "chartjs-plugin-datalabels";
import { observable, runInAction, toJS } from "mobx";
import { BarChartWorker as Worker } from "_entry/workers";
// import { FlatUiColors } from "./color-schemes";
import { aggregationType, formatDataForBarChart } from "./data-utils";
import { IBarData } from "./client-report";
export interface IBarDatasetSettings {
label: string;
backgroundColor?: string;
borderColor?: string;
data: number[];
type?: string;
fill?: boolean;
yAxisID?: string;
xAxisID?: string;
datalabels?: any;
}
export interface IBarChartSettings {
labels: string[];
datasets: IBarDatasetSettings[];
}
interface IProps {
data: any[];
xAxis?: string | string[]; // function or array of column names
yAxis?: string; // column name or function
dataColNames?: string[]; // alternative to xAxis and yAxis if data is pivoted (E&M report) - TODO: phase this out to use xAxis/yAxis properties
categoryCols: string[];
expectedValCol?: string;
expectedValBy?: string;
lineForExpected?: boolean;
stacked?: boolean;
legendPosition?: "bottom" | "right" | "left" | "top";
hideLegend?: boolean;
percentValues?: boolean;
aggregation?: aggregationType;
reportConfigId: string;
onBarClick?: (barData: IBarData) => void;
}
export interface ISeriesData {
[dataColName: string]: number[];
goal?: any;
}
export default class BarChartWidget extends React.Component<IProps> {
private barChart: Bar;
private barWorker: any;
@observable private barData: any = {};
componentDidMount() {
this.barWorker = new Worker();
this.barWorker.onmessage = event => {
const { action, args } = event.data;
runInAction(() => {
if (action === "formatForBarChart") {
this.barData = args[0];
}
this.barWorker.terminate();
this.forceUpdate();
});
};
this.barWorker.onerror = e => {
console.log("error occurred in bar worker", e);
};
this.setBarData();
}
componentWillUnmount() {
if (this.barWorker) {
console.log("terminating worker");
this.barWorker.terminate();
}
}
componentDidUpdate(prevProps: IProps) {
const { data, xAxis, yAxis, categoryCols, dataColNames, expectedValCol, lineForExpected, expectedValBy, aggregation, reportConfigId } = this.props;
const {
data: prevData,
xAxis: prevXAxis,
yAxis: prevYAxis,
categoryCols: prevCategoryCols,
dataColNames: prevDataColNames,
expectedValCol: prevExpectedValCol,
lineForExpected: prevLineForExpected,
expectedValBy: prevExpectedValBy,
aggregation: prevAggregation,
reportConfigId: prevReportConfigId,
} = prevProps;
if (
prevData !== data ||
prevXAxis !== xAxis ||
prevYAxis !== yAxis ||
prevCategoryCols !== categoryCols ||
prevDataColNames !== dataColNames ||
prevExpectedValCol !== expectedValCol ||
prevLineForExpected !== lineForExpected ||
prevExpectedValBy !== expectedValBy ||
prevAggregation !== aggregation ||
prevReportConfigId !== reportConfigId
) {
this.setBarData();
}
}
setBarData = () => {
// format data for chartjs
const { data, xAxis, yAxis, categoryCols, dataColNames, expectedValCol, lineForExpected, expectedValBy, aggregation, reportConfigId, percentValues, stacked } = this.props;
if ("serviceWorker" in navigator && data && data.length) {
this.barWorker.postMessage({
action: "formatForBarChart",
args: [
data ? data.slice() : [],
xAxis,
yAxis,
categoryCols,
dataColNames,
expectedValCol,
lineForExpected,
expectedValBy,
aggregation,
reportConfigId,
percentValues,
stacked,
],
});
} else {
this.barData = formatDataForBarChart(
data,
xAxis!,
yAxis!,
categoryCols,
dataColNames!,
expectedValCol!,
lineForExpected!,
expectedValBy!,
aggregation!,
reportConfigId,
percentValues || false,
stacked || false
);
}
};
get overlappedBarPlugin() {
const overlappedBarPlugin = {
afterDatasetsUpdate: chart => {
const { expectedValCol, stacked, lineForExpected, expectedValBy } = this.props;
if (!expectedValCol || stacked || lineForExpected) return;
const { datasets } = chart.data;
chart.legend.options.onClick = (_e, legendItem) => {
const index = legendItem.datasetIndex;
const ci = chart;
const meta = ci.getDatasetMeta(index);
const metaGoal = ci.getDatasetMeta(index + datasets.length / 2); // hide goal data too
// See controller.isDatasetVisible comment
meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null;
metaGoal.hidden = metaGoal.hidden === null ? !ci.data.datasets[index + datasets.length / 2].hidden : null;
// We hid a dataset ... rerender the chart
ci.update();
};
let barWidth = null;
// Get the x position for data bars
const xVals: number[][] = datasets.slice(0, datasets.length / 2).map(ds => {
const metaKeys = Object.keys(ds._meta);
const rects = metaKeys.length ? ds._meta[metaKeys[0]].data : [];
return rects.map(rectangle => {
if (!barWidth) barWidth = rectangle._model.width;
rectangle._model.width = rectangle._view.width = 0.6 * barWidth!; // set to 60% of width
return rectangle._model.x;
});
});
// Set x position for goal bars to be behind data bars
datasets.slice(datasets.length / 2).forEach(gds => {
const gMetaKeys = Object.keys(gds._meta);
const goalRects = gMetaKeys.length ? gds._meta[gMetaKeys[0]].data : [];
goalRects.forEach(rectangle => {
const index = rectangle._datasetIndex - datasets.length / 2;
rectangle._model.x = rectangle._view.x = xVals[index][rectangle._index]; // set
if (!expectedValBy || expectedValBy === "bar") rectangle._model.width = rectangle._view.width = barWidth;
});
});
},
};
return overlappedBarPlugin;
}
divideParameters = (dataString: string, params: string[], spacer: string) => {
const data: { [x: string]: any } = {};
const paramValues = dataString.split(spacer);
for (const i of Object.keys(params)) {
data[params[i]] = paramValues[i];
}
return data;
};
handleBarClick = (e: any) => {
// Handle bar click
const { onBarClick, xAxis, expectedValCol, categoryCols } = this.props;
const elem = this.barChart.chartInstance!.getElementAtEvent(e)[0];
if (!elem || !onBarClick) return;
const { datasetLabel: category, label: xValue } = elem._model;
const { _index, _datasetIndex, _chart: chart } = elem;
const yValue = chart.data.datasets[_datasetIndex].data[_index];
const xAxisData = Array.isArray(xAxis) ? this.divideParameters(xValue, xAxis, " | ") : {};
const categoryData = categoryCols ? this.divideParameters(category, categoryCols, " | ") : {};
const isGoalBar = expectedValCol !== undefined && category.indexOf("Goal") !== -1;
const data = { ...xAxisData, ...categoryData, yValue, isGoalBar };
onBarClick(data);
};
get options() {
const options = {
onClick: this.handleBarClick,
hover: {
mode: false,
},
responsive: true,
maintainAspectRatio: false,
layout: {
padding: {
top: 30,
},
},
scales: {
xAxes: [
{
id: "actual",
stacked: this.props.stacked,
ticks: {
callback: value => {
if (value.length > 15) return this.splitStringIntoChunks(value, 15);
return value;
},
fontColor: "#000",
},
categoryPercentage: 0.8,
barPercentage: 0.8,
gridLines: {
offsetGridLines: true,
},
},
{
display: false,
offset: true,
id: "expected",
type: "category",
categoryPercentage: !this.props.expectedValBy || this.props.expectedValBy === "bar" ? 0.8 : 1,
barPercentage: 0.8,
gridLines: {
offsetGridLines: true,
},
},
{
display: false,
id: "expected-line",
type: "category",
categoryPercentage: 1,
barPercentage: 1,
gridLines: {
offsetGridLines: true,
},
},
],
yAxes: [
{
stacked: this.props.stacked,
ticks: {
beginAtZero: true,
fontColor: "#000",
callback: value => {
return this.props.percentValues ? `${value}%` : value;
},
},
},
],
},
};
return options;
}
splitStringIntoChunks = (text: string, maxSize: number) => {
const chunks: string[] = [];
const words = text.split(" ");
let current = "";
for (let i = 0; i <= words.length; i++) {
if (`${current} ${words[i]}`.length <= maxSize) {
current = `${current} ${words[i]}`;
} else {
chunks.push(current.trim());
current = words[i];
}
}
return chunks;
};
render() {
// console.log("rendering bar chart");
const barData = toJS(this.barData);
return (
<div className="bar-chart">
<Bar
ref={el => {
this.barChart = el!;
}}
data={barData}
legend={{
display: !this.props.hideLegend,
position: this.props.legendPosition || "right",
fullWidth: false,
labels: {
boxWidth: 15,
fontColor: "#000",
filter: (itm: any) => {
return itm.text && !itm.text.includes("Goal-");
},
},
}}
options={this.options}
plugins={[this.overlappedBarPlugin]}
/>
</div>
);
}
}
这是我的工作脚本:
import { formatDataForBarChart } from "../../components/reports/data-utils";
const ctx: Worker = self as any;
ctx.addEventListener("message", event => {
const { action, args } = event.data;
console.log("in bar chart worker", action);
if (action === "formatForBarChart")
ctx.postMessage({
action: "formatForBarChart",
args: [formatDataForBarChart(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11])],
});
});
任何想法将不胜感激!谢谢!