highcharts / highcharts-vue

Other
686 stars 150 forks source link

Highcharts within v-for only shows last entry from JSON #105

Closed scryptoking closed 5 years ago

scryptoking commented 5 years ago

Hi,

What I'm trying to do is to re-use a vue component with a prop that gets it's data from a json object. My problem is, that all the components contain the same data instead of the data based on the index of de json object.

My json data looks like this (snippet):

[
    {
        "id_department": 3,
        "code": "Test2",
        "subcode": "Test-new2",
        "fee": "1.794.405",
        "fte": "13.0",
        "fee_per_fte": "138.031",
        "details": "0",
        "indicatorChartData": [
            [
                "A",
                607990
            ],
            [
                "B",
                753637
            ],
            [
                "C",
                25321
            ]
         ]
    },
    {
        "id_department": 9,
        "code": "Test",
        "subcode": "Test-new",
        "fee": "892.571",
        "fte": "18.0",
        "fee_per_fte": "49.587",
        "details": "0",
        "indicatorChartData": [
            [
                "A",
                525829
            ],
            [
                "B",
                68512
            ],
            [
                "C",
                12660
            ]
        ]
    }
]

My main component (which basically holds a v-for loop to fill the component that I want to re-use) looks like this:

<template>
    <div>
        <b-card :bg-variant="themeVariant.cardBG" text-variant="gray" class="card-settings" style="padding: 10px; min-height: 660px">
            <b-row>
                <b-col sm="12">
                    <span class="float-left">
                        <h5><icon name="search-plus" scale="1"/> Details</h5>
                    </span>
                </b-col>
            </b-row>
            <hr>
            <b-row>
                <b-col sm="12">
                    <b-list-group flush>
                        <b-list-group-item v-for="i in tableData" :key="i.id_department" class="flex-column align-items-start" style="background: transparent; text-align: left; border-bottom: 1px; color: rgb(139, 154, 162)">
                            <b-row>
                                <b-col md="2">
                                    <b-button v-b-toggle="i.id_department" size="sm" :variant="themeVariant.colPrimary">
                                        Details
                                    </b-button>
                                </b-col>
                            </b-row>
                            <b-collapse :id="i.id_department">
                                <b-row>
                                    <b-col md="6">
                                        <c-srchartindicatorfee :set-chart-data="i.indicatorChartData"></c-srchartindicatorfee>
                                    </b-col>
                                </b-row>
                            </b-collapse>
                        </b-list-group-item>
                    </b-list-group>
                </b-col>
            </b-row>
        </b-card>
    </div>
</template>

<script>
    import Icon from 'vue-awesome/components/Icon.vue'
    import srchartindicatorfee from "./chartindicatorfee/srchartindicatorfee"
    export default {
        name: "srtable",
        props: {
            tableData: Array
        },
        components: {
            Icon,
            "c-srchartindicatorfee": srchartindicatorfee
        }
    }
</script>

<style scoped>
    .card-settings {
        margin-bottom: 30px;
    }
</style>

And my c-srchartindicatorfee component looks like this:

<template>
    <div>
        <highcharts :options="chartIndicatorFee"></highcharts>
    </div>
</template>

<script>
    import { chartIndicatorFee } from "./chartindicatorfee"
    export default {
        name: "srchartindicatorfee",
        props: {
            setChartData: Array
        },
        data() {
            return {
                chartIndicatorFee: chartIndicatorFee
            }
        },
        created() {
            this.chartIndicatorFee.series[0].data = this.setChartData
        },
        updated() {
            this.chartIndicatorFee.series[0].data = this.setChartData
        },
        watch: {
            setChartData: {
                handler(val) {
                    alert(JSON.stringify(val));
                    this.chartIndicatorFee.series[0].data = val;
                },
                deep: true
            }
        }
    }
</script>

<style scoped>

</style>

In this last component I want to fill the setChartData prop with the indicatorChartData array from my json object.

I would expect that (since vue components can be re-used) every generated c-srchartindicatorfee component would hold it's own data (indicatorChartData) from the json via the setChartData prop, but in this case every component holds the data of the last entry within my json object.

scryptoking commented 5 years ago

update:

I replaced the chart for a simple table and the table does show different data for each entry. This means that the issue is within highcharts

Denyllon commented 5 years ago

Hi @bddexter ,

Thank you for reporting the issue. Could you try to produce the minimal working example where the issue occurs? It would be much convenient to debug it using minified live copy of your project.

Kind regards!

scryptoking commented 5 years ago

@Denyllon Updated from version 1.2.0 to 1.3.5. Solved the issue for some reason. Maybe closed now.

yarielg commented 4 years ago

I have the same issue with highcharts-vue and v-for

// PieChart.vue
<template>
  <highcharts :options="chartOptions" :ref="title" />
</template>

<script>
import { Chart } from 'highcharts-vue';

export default {
  components: {
    highcharts: Chart,
  },

  props: {
    title: {
      type: String,
      default: '',
    },
    data: {
      type: Array,
      default: () => [],
    },
  },

  computed: {
    chartOptions() {
      console.log(this.title);
      let options = Object.assign({}, piechartDefaultOptions);
      if (this.title) {
        options.title.text = this.title;
      }
      if (this.data.length) options.series[0].data = this.data;
      return options;
    },
  },
};

const piechartDefaultOptions = {
  chart: {
    plotBackgroundColor: null,
    plotBorderWidth: null,
    plotShadow: false,
    type: 'pie',
  },
  title: {
    text: 'Pie Chart',
  },
  tooltip: {
    pointFormat: '{series.name}: <b>{point.percentage:.1f}%</b>',
  },
  accessibility: {
    point: {
      valueSuffix: '%',
    },
  },
  plotOptions: {
    pie: {
      allowPointSelect: true,
      cursor: 'pointer',
      dataLabels: {
        enabled: true,
        format: '<b>{point.name}</b>: {point.percentage:.1f} %',
      },
    },
  },
  series: [
    {
      name: 'Brands',
      colorByPoint: true,
      data: [],
    },
  ],
};
</script>

<style lang="scss" scoped></style>
// RiskCharts.vue
<template>
  <v-row>
    <v-col v-for="(item, i) in risks" :key="i" sm="6" md="4" lg="3">
      <pie-chart :key="`chart-${i}`" :title="item.title" :data="item.data" />
    </v-col>
  </v-row>
</template>

<script>
export default {
  components: {
    PieChart: () => import('./PieChart'),
  },

  created() {
    this.risks = risks;
  },
};

const risks = [
  {
    title: 'Conservative',
    data: [
      {
        name: 'Bonds',
        y: 80,
      },
      {
        name: 'Equities',
        y: 20,
      },
    ],
  },
  {
    title: 'Cautious',
    data: [
      {
        name: 'Bonds',
        y: 70,
      },
      {
        name: 'Equities',
        y: 30,
      },
    ],
  },
  {
    title: 'Balanced',
    data: [
      {
        name: 'Bonds',
        y: 50,
      },
      {
        name: 'Equities',
        y: 50,
      },
    ],
  },
  {
    title: 'Assertive',
    data: [
      {
        name: 'Bonds',
        y: 30,
      },
      {
        name: 'Equities',
        y: 70,
      },
    ],
  },
  {
    title: 'Aggressive',
    data: [
      {
        name: 'Bonds',
        y: 20,
      },
      {
        name: 'Equities',
        y: 80,
      },
    ],
  },
];
</script>

You can see the live site here: https://highcharts-vue-v-for-issue.netlify.app/

All of the highcharts have the last data in the array. The issue is the same whether or not having "ref" in PieChart.vue.

Denyllon commented 4 years ago

Hi @yarielg ,

Sorry for a bit of late in reply. Could you reproduce the app you attached above using the Codesandbox/Stackblitz platform, so that I would be able to take a look on the code and its structure? Unfortunately, it's a bit hard to debug the app deployed on Netlify.

Kind regards!

yarielg commented 4 years ago

Hello, @Denyllon

Yes, sure, thank you https://codesandbox.io/s/small-hill-6fe7h?file=/src/App.vue

Could you check this?

Denyllon commented 4 years ago

Hello @yarielg ,

Thank you for your patience. I've just checked it, and found the direct source of the problem within your implementation of the PieChart.vue file, where the shallow copy of the piechartDefaultOptions is done through computed property definition. Please note that the Object.assign copies only the first level of the object, but keeps all internal object references as they're, so all charts refer to the same series and text objects. I also applied quick solution into your code, where used the object spread operator, and now it works correctly. Please take a look on it.

    chartOptions() {
      let options = {
          ...piechartDefaultOptions,
          title: {
            ...piechartDefaultOptions.title,
            text: this.title || ""
          },
          series: [{
            ...piechartDefaultOptions.series[0],
            data: this.data || []
          }]
        }

      return options;
    }

Live example: https://codesandbox.io/s/dawn-sky-ktj1s

Kind regards!

yarielg commented 4 years ago

Hello, @Denyllon You're right. Thank you.