123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734 |
- <template>
-
- <div
- :class="className"
- :style="{ height: '100%', width: width, padding: '20px' }"
- >
- <div class="select-container">
- <el-form
- style="
- display: grid;
- grid-template-columns: 1fr 1fr 1fr;
- align-items: center;
- grid-gap: 20px;
- "
- label-width="100px"
- >
- <el-form-item label="评估指标" style="margin-bottom: 0">
- <el-select v-model="selectedDataKey" style="width: 100%">
- <el-option
- v-for="key in dataKeys"
- :key="key"
- :value="key"
- :label="keyToChinese[key]"
- >
- {{ keyToChinese[key] }}
- </el-option>
- </el-select>
- </el-form-item>
- <el-form-item label="年度" style="margin-bottom: 0"
- ><el-select v-model="selectedYear" style="width: 100%">
- <el-option v-for="year in availableYears" :key="year" :value="year">
- {{ year }}
- </el-option>
- </el-select></el-form-item
- >
- <el-form-item label="行业代码" style="margin-bottom: 0"
- ><el-input
- v-model="selectedCode"
- placeholder="请输入行业代码"
- clearable
- style="width: 100%"
- /></el-form-item>
- <el-form-item label="检测区间" style="margin-bottom: 0">
- <div style="display: flex">
- <el-input v-model="detectMin" placeholder="最小值" clearable />
- <span style="margin: 0 10px">~</span>
- <el-input v-model="detectMax" placeholder="最大值" clearable />
- </div>
- </el-form-item>
-
- <div style="display: inline; padding-left: 10px">
- 当前行业: {{ industryMap.get(selectedCode) }}
- </div>
- <el-form-item>
- <el-button type="primary" icon="el-icon-search" @click="updateChart"
- >搜索</el-button
- >
- </el-form-item>
- </el-form>
- </div>
- <div ref="chart" :style="{ height: height, width: width }"></div>
- <el-table :data="tableData" border style="width: 100%">
- <el-table-column
- prop="industryName"
- label="行业名称"
- width="200"
- ></el-table-column>
- <el-table-column prop="year" label="年度" width="200"> </el-table-column>
- <el-table-column prop="enterpriseId" label="企业ID"></el-table-column>
- <el-table-column
- prop="value"
- label="Z-score得分"
- width="200"
- ></el-table-column>
- <el-table-column
- prop="level"
- label="水平评价"
- width="200"
- ></el-table-column>
- </el-table>
- </div>
- </template>
- <script>
- import * as echarts from "echarts";
- import { listScore } from "@/api/score/score";
- import { listIndustry } from "@/api/industry/industry";
- require("echarts/theme/macarons");
- import resize from "./mixins/resize";
- export default {
- mixins: [resize],
- props: {
- className: {
- type: String,
- default: "chart",
- },
- width: {
- type: String,
- default: "100%",
- },
- height: {
- type: String,
- default: "400px",
- },
- },
- data() {
- return {
- chart: null,
- chartData: [],
- industryData: [],
- industryMap: new Map(),
- selectedDataKey: "totalIndustrialValueScore",
- selectedCode: null,
- dataKeys: [
-
-
- "totalIndustrialValueScore",
-
- "taxableIncomeScore",
- "paidTaxScore",
-
-
-
-
- "fundingScore",
- "energyConsumeScore",
- "powerConsumeScore",
-
- ],
- keyToChinese: {
- mainBusinessScore: "主营业务活动得分",
- landAreaScore: "用地面积得分",
- totalIndustrialValueScore: "工业总产值得分",
- gdpScore: "工业增加值得分",
- taxableIncomeScore: "应税收入得分",
- paidTaxScore: "实缴税金得分",
- mainBusinessIncomeScore: "主营业务收入得分",
- employeeNumberScore: "从业人员数得分",
- profitScore: "利润总额得分",
- ownerEquityScore: "所有者权益得分",
- fundingScore: "研发经费得分",
- energyConsumeScore: "能源消费量得分",
- powerConsumeScore: "电力消费量得分",
- },
- keyToLevel: {
- totalIndustrialValueScore: "totalIndustrialValueLevel",
- taxableIncomeScore: "taxableIncomeLevel",
- paidTaxScore: "paidTaxLevel",
- fundingScore: "fundingLevel",
- energyConsumeScore: "energyConsumeLevel",
- powerConsumeScore: "powerConsumeLevel",
- },
- selectedYear: null,
- queryParams: {
- pageNum: 1,
- pageSize: 2000000,
- enterpriseName: null,
- location: null,
- code: null,
- mainBusiness: null,
- landArea: null,
- totalIndustrialValue: null,
- gdp: null,
- taxableIncome: null,
- paidTax: null,
- mainBusinessIncome: null,
- employeeNumber: null,
- profit: null,
- ownerEquity: null,
- funding: null,
- energyConsume: null,
- year: null,
- month: null,
- },
- industryQueryParams: {
- pageNum: 1,
- pageSize: 2000000,
- industryName: null,
- code: null,
- },
- availableYears: [],
- selectedRange: 0.1,
-
- detectMin: null,
- detectMax: null,
- tableData: [],
- };
- },
- mounted() {
- this.$nextTick(() => {
- this.fetchData();
- });
- },
- beforeDestroy() {
- if (this.chart) {
- this.chart.dispose();
- }
- },
- methods: {
- fetchData() {
- listScore(this.queryParams)
- .then((response) => {
- this.chartData = response.rows;
- this.availableYears = [
- ...new Set(this.chartData.map((item) => item.year)),
- ];
- this.selectedYear = this.availableYears[0] || null;
-
-
-
-
- if (Array.isArray(this.chartData)) {
- this.selectedCode = [
- ...new Set(this.chartData.map((item) => item.code)),
- ][0];
- } else {
- console.warn("this.x is not an array or is undefined.");
- this.selectedCode = null;
- }
- this.initChart();
- })
- .catch((error) => {
- console.error("Error fetching data:", error);
- });
- listIndustry(this.industryQueryParams)
- .then((response) => {
- this.industryData = response.rows;
-
- this.industryMap = this.industryData.reduce((map, item) => {
- const key = `${item.code}`;
- map.set(key, item.industryName);
- return map;
- }, new Map());
- })
- .catch((error) => {
- console.error("Error fetching data:", error);
- });
- },
- initChart() {
- if (this.chart) {
- this.chart.dispose();
- }
- this.chart = echarts.init(this.$refs.chart, "macarons");
- this.updateChart();
- },
- updateChart() {
- const that = this;
- if (!this.chart) {
- return;
- }
- let flag = this.checkNumber();
- if (flag == 2) {
- return;
- }
-
- let filteredData = this.chartData;
- if (this.selectedYear) {
- filteredData = filteredData.filter(
- (item) =>
- item.year === this.selectedYear && item.code === this.selectedCode
- );
- }
- const values = filteredData.map((item) => item[this.selectedDataKey]);
- console.log(filteredData, values, "filteredData")
- const minVal = Math.min(...values).toFixed(2);
- const maxVal = Math.max(...values).toFixed(2);
-
-
-
-
-
-
-
-
-
- if (
- this.selectedRange === null ||
- this.selectedRange === "" ||
- isNaN(+this.selectedRange)
- )
- this.selectedRange = "0.1";
- else if (Number(this.selectedRange) <= 0) this.selectedRange = "0.1";
- const binCount = Math.ceil(
- (maxVal - minVal) / Number(this.selectedRange)
- );
- let maxNum = -1;
-
- function generateRangeArray(x1, x2, interval) {
-
- x1 = parseFloat(x1);
- x2 = parseFloat(x2);
- interval = parseFloat(interval);
- const result = [];
-
- let current = x1;
-
- while (current < x2) {
- let next = parseFloat((current + interval).toFixed(2));
-
- result.push({
- name: `${current.toFixed(2)}-${next.toFixed(2)}`,
- min: current,
- max: next,
- });
- current = next;
- }
-
- if (current < x2) {
- result.push({
- name: `${current.toFixed(2)}-${x2.toFixed(2)}`,
- min: current,
- max: x2,
- });
- }
- return result;
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- const histogramData = [];
- let result = generateRangeArray(minVal, maxVal, 0.1);
-
- result.forEach((item) => {
- let count = 0;
- count += values.filter(
- (value) => value >= item.min && value < item.max
- ).length;
-
- if (item.max == maxVal) {
- count += values.filter((value) => value === item.max).length;
- }
- histogramData.push({ name: item.name, value: count });
- });
- const maxHistogramValue = Math.max(
- ...histogramData.map((item) => item.value)
- );
- const totalHistogramValue = histogramData.reduce(
- (sum, item) => sum + item.value,
- 0
- );
-
-
- const mean = values.reduce((acc, val) => acc + val, 0) / values.length;
-
-
- const stdDev = Math.sqrt(
- values.reduce((acc, val) => acc + Math.pow(val - mean, 2), 0) /
- values.length
- );
-
- const center = mean.toFixed(2)
- const interval = 0.1;
- let minLimit = Math.floor(Math.min(...values) * 10) / 10;
- let maxLimit = Math.ceil(Math.max(...values) * 10) / 10;
- console.log("中心", center,minLimit,maxLimit)
- if(center-minLimit != maxLimit-center){
- minLimit = Math.floor(minLimit - (maxLimit-center - (center-minLimit)))
- }
- const intervals = [];
- console.log(minLimit,maxLimit, interval)
-
- for (let i = minLimit * 1000 ; i <= maxLimit*1000 ; i += (interval*1000)) {
- intervals.push(parseFloat((i/1000).toFixed(2)))
- }
-
- const frequency = new Array(intervals.length - 1).fill(0);
- let intervalArr = intervals.slice(0,-1)
- const intervalStrings = [];
- for (let i = 0; i < intervalArr.length; i++) {
- const start = intervalArr[i].toFixed(1);
- const end = ((intervalArr[i]*1000 + interval*1000)/1000).toFixed(1);
- intervalStrings.push({
- name: `${start}-${end}`,
- max: end,
- min: start
- });
- }
- console.log(intervals,intervalArr)
- values.forEach((value) => {
- for (let j = 0; j < intervalStrings.length - 1; j++) {
-
-
-
- if (value >= intervalStrings[j].min && value < intervalStrings[j].max) {
-
- frequency[j]++;
- break;
- }
- }
- });
-
- if (values.some((value) => value == maxLimit)) {
- frequency[frequency.length - 1]++;
- }
-
- const normalDistributionValues = intervals
- .slice(0, -1)
- .map((intervalStart, index) => {
- const intervalEnd = intervals[index + 1];
- const midpoint = (intervalStart + intervalEnd) / 2;
- return (
- (1 / (stdDev * Math.sqrt(2 * Math.PI))) *
- Math.exp(-0.5 * Math.pow((midpoint - mean) / stdDev, 2))
- );
- });
-
-
-
-
-
-
-
- const option = {
- color: ["rgba(245,0,0,0.6)"],
- dataZoom: [
- {
- type: "inside",
- start: 0,
- end: 100,
- xAxisIndex: [0],
- },
- ],
- tooltip: {
- trigger: "axis",
- axisPointer: {
- type: "shadow",
- },
- formatter: (params) => {
-
- const seriesName = params[0].name;
- const dataIndex = params[0].dataIndex;
- const value = params[0].data;
- const xAxisLabel = params[0].axisValueLabel;
- const yAxisLabel = params[0].seriesName;
-
- return `${xAxisLabel}</br> ${yAxisLabel}: ${value} `;
- },
- },
- legend: {
- data: [
- `${this.selectedYear}年 ${
- this.keyToChinese[this.selectedDataKey]
- }企业数统计`,
- "正态分布"
- ],
- },
- grid: {
- left: "3%",
- right: "4%",
- bottom: "50px",
- containLabel: true,
- },
- xAxis: {
- type: "category",
- name: "分布区间",
-
- data: intervalStrings.map(item=>item.name),
- axisLabel: {
- fontSize: 12,
- interval: 0,
- rotate: 20,
-
- },
- },
- yAxis: [{
- name: "企业数",
- type: "value",
- minInterval: 1,
- },{
- name: '正态分布',
- type: 'value'
- }],
- series: [
- {
- name: `${this.selectedYear}年 ${
- this.keyToChinese[this.selectedDataKey]
- }企业数统计`,
- type: "bar",
- barWidth: "30%",
- itemStyle: {
-
- normal: {
- color: function (params) {
- const value = params.dataIndex;
- if (flag == 1) {
- let min = parseFloat(intervalStrings[value].min)*1000
- let max = parseFloat(intervalStrings[value].max)*1000
- const detectMin = parseFloat(that.detectMin)*1000
- const detectMax = parseFloat(that.detectMax)*1000
- if (
- min >= detectMin &&
- max <= detectMax
- ) {
- return "#ffcc00";
- }else if(min == detectMax){
- return "#ffcc00";
- }
- return "rgba(245,0,0,0.6)";
- }
- return "rgba(245,0,0,0.6)";
-
- },
- },
- },
-
- data: frequency,
- label: {
- show: false,
- },
- },
- {
- name:"正态分布",
- yAxisIndex: 1,
- type: "line",
- data: normalDistributionValues,
- lineStyle: {
- color: "#1a7cc8",
- },
- symbol: "none",
- },
- ],
- };
- this.isChartShow = true;
- this.chart.setOption(option);
- this.chart.on("click", function (param) {
-
- if(typeof param.dataIndex === "number" &&
- param.dataIndex >= 0 &&
- param.dataIndex < intervalStrings.length
- ){
- let dataIndexValue = intervalStrings[param.dataIndex];
- const filteredArray = filteredData.filter((item)=>{
- const isMaxValEqual = parseFloat(dataIndexValue.max) == parseFloat(maxVal);
- const lowerBoundCheck = item[that.selectedDataKey] >= dataIndexValue.min;
- const upperBoundCheck = isMaxValEqual
- ? item[that.selectedDataKey] <= dataIndexValue.max
- : item[that.selectedDataKey] < dataIndexValue.max;
- return lowerBoundCheck && upperBoundCheck;
- })
-
- that.tableData = filteredArray.map((item) => {
- return {
- industryName: item.code,
- enterpriseId: item.enterpriseName,
- year: item.year,
- value: item[that.selectedDataKey],
- level: item[that.keyToLevel[that.selectedDataKey]],
- };
- });
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- });
- },
- generateQuadraticData(binCount, maxHistogramValue, totalHistogramValue) {
- const a = -0.01;
- const h = binCount / 2;
- const k = maxHistogramValue * 0.75;
- const sideValue = (maxHistogramValue * 0.15) / 2;
- const data = [];
- for (let i = 0; i < binCount; i++) {
- const x = i;
- const y = a * (x - h) ** 2 + k;
-
- if (i === 0 || i === binCount - 1) {
- data.push([x, sideValue]);
- } else {
- data.push([x, y]);
- }
- }
- return data;
- },
-
- checkNumber() {
- const maxInput = parseFloat(this.detectMax);
- const minInput = parseFloat(this.detectMin);
- let flag;
- if (!this.detectMin && !this.detectMax) {
- return (flag = 0);
- }
- if (isNaN(maxInput) || isNaN(minInput)) {
- this.$message({
- message: "请输入有效的数字。",
- type: "error",
- });
- return (flag = 2);
- }
-
- if (minInput >= maxInput) {
- this.$message({
- message: "监测区间最大值不可小于或等于最小值",
- type: "error",
- });
- return (flag = 2);
- }
- return (flag = 1);
- },
- },
- watch: {
-
-
-
-
-
-
-
-
-
-
-
-
- },
- };
- </script>
- <style scoped>
- .select-container {
- margin: 10px 0;
- font-size: 16px;
- }
- .select-container select {
- padding: 10px 20px;
- margin-right: 10px;
- border: 1px solid #121315;
- border-radius: 4px;
- background-color: white;
- color: #121315;
- font-size: 16px;
- cursor: pointer;
- outline: none;
- }
- .select-container select:hover {
- border-color: #1a7cc8;
- }
- .select-container select:focus {
- border-color: #121315;
- box-shadow: 0 0 0 2px rgba(64, 159, 255, 0.2);
- }
- </style>
|