Comments (5)
As you can see from the release log, v6.5.3 changed the behavior of the internal setOption
.
In your case you may need to apply :update-options="{ notMerge: false }"
to your chart component. Otherwise when you update the option upon legendselectchanged
, ECharts may consider that you are creating a new chart and the instance may lose its internal legend selection state.
from vue-echarts.
You are creating a new option object in the computed function:
So barChartOptions.value
will be a fresh object each time its dependencies change. The legendselectchanged
event is irrelevant here.
from vue-echarts.
Thank you. Your fix worked.
I have read over this part from the docs again:
When update-options is not specified, notMerge: false will be specified by default when the setOption method is called if the option object is modified directly and the reference remains unchanged; otherwise, if a new reference is bound to option, notMerge: true will be specified.
But I'm confused why I need to specify :update-options="{ notMerge: false }"
. In this case shouldn't notMerge: false
be the default setting?
The legendselectchanged
event is used to trigger a change in the option object. The computed reference remains unchanged. Therefore notMerge: false
should be used by default.
from vue-echarts.
Ok thanks I understand now. Thank you for taking the time to help explain this to me :)
from vue-echarts.
Posting this info here for my own benefit when I have a similar problem in the future.
The crux of the issue is that my chart data loads async. Because of this I thought using a Computed Ref instead of a plain Ref to build the options object would be a good idea. But the Computed Ref object had some properties that were reactive variables, and whenever those reactive properties changed the Computed Ref recomputation was creating a new object reference (news to me at the time!). Therefore we need to add :update-options="{ notMerge: false }"
to ensure the new object gets merged with the old object.
Another solution would be to refactor and go back to using a plain Ref with a static options object. Then use watchers and such to update the object properties in place (barChartOptions.value.series = newSeriesData
). In scenario we do not need :update-options="{ notMerge: false }"
because are not creating a new object reference each time. We are just changing the object properties in place, and therefore notMerge: false
will already be used by default.
For completeness sake I will post a couple version of the full SFC here, but a lot of the logic for rounding the stacked bar chart is not needed. That's just what I had with the initial issue before learning the real problem.
Here is my old problematic code from the original issue:
Note, this version can easily be fixed by adding update-options="{ notMerge: false }"
to the v-chart
.
<template>
<v-chart
class="echart-container"
autoresize
:option="barChartOptions"
@legendselectchanged="handleLegendSelectChanged"
/>
</template>
<script setup>
import { computed, onMounted, ref } from "vue";
import { use } from "echarts/core";
import { CanvasRenderer } from "echarts/renderers";
import { BarChart } from "echarts/charts";
import { LegendComponent, GridComponent } from "echarts/components";
import VChart from "vue-echarts";
use([GridComponent, LegendComponent, BarChart, CanvasRenderer]);
// Create some fake async data
const asyncData = ref([]);
onMounted(async () => {
// Pause for fake async load
await new Promise((resolve) => setTimeout(resolve, 500));
// Load the data
asyncData.value = [
{
date: new Date("2023-11-22T17:00:00.000Z"),
appearances: 1,
missedOpportunities: 2,
},
{
date: new Date("2023-11-23T17:00:00.000Z"),
appearances: 2,
missedOpportunities: 1,
},
];
});
// Track series visibility
const seriesVisibility = ref({
"Missed Opportunities": true,
Appearances: true,
});
// Update series visibility when legend is toggled
const handleLegendSelectChanged = (legend) => {
Object.entries(legend.selected).forEach(([selectedKey, selectedValue]) => {
seriesVisibility.value[selectedKey] = selectedValue;
});
};
// Create computed options for the chart
const barChartOptions = computed(() => {
// Create base series (data for this is added later)
const baseSeries = [
{
name: "Appearances",
type: "bar",
color: "#FF0000",
stack: "ranks",
},
{
name: "Missed Opportunities",
type: "bar",
color: "#333333",
stack: "ranks",
},
];
// Function to get the top stacked series for each date
const getTopSeriesForEachDate = () => {
// Object to store top series name for each date
const topSeriesForEachDate = {};
asyncData.value.forEach((dataPoint) => {
let topSeriesName = "";
// Check which series is on top for this data point
if (
seriesVisibility.value["Missed Opportunities"] &&
dataPoint.missedOpportunities > 0
) {
topSeriesName = "Missed Opportunities";
} else if (
seriesVisibility.value["Appearances"] &&
dataPoint.appearances > 0
) {
topSeriesName = "Appearances";
}
// Store the top series name for this date
if (topSeriesName) {
topSeriesForEachDate[dataPoint.date.toDateString()] = topSeriesName;
}
});
return topSeriesForEachDate;
};
// Function to add border radius to the top stacked series
const getSeriesDataWithTopStackBorderRadius = (stackInfo) => {
// Iterate over base series and create a new series
const series = baseSeries.map((seriesItem) => {
// Iterate over asyncData and create a new array of series data
const seriesData = asyncData.value.map((dataPoint) => {
const dataPointDateString = dataPoint.date.toDateString();
const dataPointTopStackName = stackInfo[dataPointDateString];
const isTopStack = dataPointTopStackName === seriesItem.name;
// Return the data item with the border radius applied
return {
value: [
dataPoint.date,
seriesItem.name === "Appearances"
? dataPoint.appearances
: dataPoint.missedOpportunities,
],
itemStyle: {
borderRadius: isTopStack ? [20, 20, 0, 0] : [0, 0, 0, 0],
},
};
});
const seriesOption = {
...seriesItem,
data: seriesData,
};
return seriesOption;
});
return series;
};
// Get the new series data with the top stack border radius applied
const seriesWithTopStackBorderRadius = getSeriesDataWithTopStackBorderRadius(
getTopSeriesForEachDate()
);
// Return the options object
const options = {
xAxis: {
type: "time",
axisLabel: {
formatter: "{d} {MMM} {yy}",
},
minInterval: 3600 * 1000 * 24, // 1 day in milliseconds
},
yAxis: {
type: "value",
show: true,
minInterval: 1,
},
series: seriesWithTopStackBorderRadius,
legend: {
show: true,
},
};
return options;
});
</script>
<style scoped>
.echart-container {
height: 500px;
width: 500px;
}
</style>
Here is an alternate version that uses a plain Ref with a static object and watchers to update the object properties in place:
<template>
<v-chart
class="echart-container"
autoresize
:option="barChartOptions"
@legendselectchanged="handleLegendSelectChanged"
/>
</template>
<script setup>
import { onMounted, ref, watch } from "vue";
import { use } from "echarts/core";
import { CanvasRenderer } from "echarts/renderers";
import { BarChart } from "echarts/charts";
import { LegendComponent, GridComponent } from "echarts/components";
import VChart from "vue-echarts";
use([GridComponent, LegendComponent, BarChart, CanvasRenderer]);
// Create some fake async data
const asyncData = ref([]);
onMounted(async () => {
// Pause for fake async load
await new Promise((resolve) => setTimeout(resolve, 500));
// Load the async data
asyncData.value = [
{
date: new Date("2023-11-22T17:00:00.000Z"),
appearances: 1,
missedOpportunities: 2,
},
{
date: new Date("2023-11-23T17:00:00.000Z"),
appearances: 2,
missedOpportunities: 1,
},
];
// Update the chart options
const seriesWithTopStackBorderRadius = getSeriesDataWithTopStackBorderRadius(
getTopSeriesForEachDate()
);
barChartOptions.value.series = seriesWithTopStackBorderRadius;
});
// Create base series (data for each series will be added later)
const baseSeries = [
{
name: "Appearances",
type: "bar",
color: "#FF0000",
stack: "ranks",
},
{
name: "Missed Opportunities",
type: "bar",
color: "#333333",
stack: "ranks",
},
];
// Track series visibility
const seriesVisibility = ref({
"Missed Opportunities": true,
Appearances: true,
});
// Update series visibility when legend is toggled
const handleLegendSelectChanged = (legend) => {
Object.entries(legend.selected).forEach(([selectedKey, selectedValue]) => {
seriesVisibility.value[selectedKey] = selectedValue;
});
};
// Function to get the top stacked series for each date
const getTopSeriesForEachDate = () => {
// Object to store top series name for each date
const topSeriesForEachDate = {};
asyncData.value.forEach((dataPoint) => {
let topSeriesName = "";
// Check which series is on top for this data point
if (
seriesVisibility.value["Missed Opportunities"] &&
dataPoint.missedOpportunities > 0
) {
topSeriesName = "Missed Opportunities";
} else if (
seriesVisibility.value["Appearances"] &&
dataPoint.appearances > 0
) {
topSeriesName = "Appearances";
}
// Store the top series name for this date
if (topSeriesName) {
topSeriesForEachDate[dataPoint.date.toDateString()] = topSeriesName;
}
});
return topSeriesForEachDate;
};
// Function to add border radius to the top stacked series
const getSeriesDataWithTopStackBorderRadius = (stackInfo) => {
// Iterate over base series and create a new series
const series = baseSeries.map((seriesItem) => {
// Iterate over asyncData and create a new array of series data
const seriesData = asyncData.value.map((dataPoint) => {
const dataPointDateString = dataPoint.date.toDateString();
const dataPointTopStackName = stackInfo[dataPointDateString];
const isTopStack = dataPointTopStackName === seriesItem.name;
// Return the data item with the border radius applied
return {
value: [
dataPoint.date,
seriesItem.name === "Appearances"
? dataPoint.appearances
: dataPoint.missedOpportunities,
],
itemStyle: {
borderRadius: isTopStack ? [20, 20, 0, 0] : [0, 0, 0, 0],
},
};
});
const seriesOption = {
...seriesItem,
data: seriesData,
};
return seriesOption;
});
return series;
};
// Watch for changes to asyncData and update the chart option properties in place
watch(
seriesVisibility,
() => {
console.log("seriesVisibility changed");
const seriesWithTopStackBorderRadius =
getSeriesDataWithTopStackBorderRadius(getTopSeriesForEachDate());
barChartOptions.value.series = seriesWithTopStackBorderRadius;
},
{ deep: true }
);
// Create computed options for the chart
const barChartOptions = ref({
xAxis: {
type: "time",
axisLabel: {
formatter: "{d} {MMM} {yy}",
},
minInterval: 3600 * 1000 * 24, // 1 day in milliseconds
},
yAxis: {
type: "value",
show: true,
minInterval: 1,
},
series: [], // Initial series data is empty, and will be added later
legend: {
show: true,
},
});
</script>
<style scoped>
.echart-container {
height: 500px;
width: 500px;
}
</style>
from vue-echarts.
Related Issues (20)
- Selecting an area HOT 1
- MarkLine and MarkArea HOT 1
- Zoom stops working when using graphic elements HOT 3
- Remove vue demi HOT 1
- From version 6.6.1 setting min-height make the chart disappear (height=0px) HOT 1
- Default ESM package HOT 2
- chart not appear when using option dataset with source HOT 1
- Native listener on VueEcharts Component does not work in Vue 3 HOT 4
- Types broken with ECharts 5.5 + Vue 3 + `moduleResolution: bundler` HOT 1
- useAutoresize throttle switch to requestAnimationFrame method HOT 3
- Nuxt 3 Module HOT 1
- Renderer 'undefined' is not imported. Please import it first. HOT 5
- autoresize prop doesn't work as expected from 6.6.1 HOT 1
- 官网的import code generator似乎不能正常工作了 HOT 1
- h5手机端支持吗?手机端的点击事件能触发吗? HOT 1
- Unable to compile after the introduction of vitepress, error: ERR-REQUIRE-ESM
- 路由缓存时,切换Tab页面,ECHARTS不显示 HOT 3
- 安装报错 Invalid response body while trying to fetch https://registry.npmjs.org/resize-detector: Cannot read properties of null (reading 'pickAlgorithm') HOT 2
- `npm i echarts vue-echarts` not installing for vue3 project HOT 1
- build后无法获取echart的实例 HOT 13
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from vue-echarts.