Vue-chartjs: Extending chart in order to override draw function

Created on 18 Apr 2018  ยท  8Comments  ยท  Source: apertureless/vue-chartjs

I'd love extend the functionality of the bar chart by making it draw rounded bars instead of the standard ones.
Here is a fiddle of how it can be realized out of vue.js context: http://jsfiddle.net/0dzp3jxw/

How can I possibly do that in vue-chartjs?

Currently I'm even struggling to find out how to create a custom chart and let the actual chart extend from that (intermediate heredity between native chart and used chart).

I created a chart like this:

# RoundedCorners.vue
<script>
import { Bar, mixins } from 'vue-chartjs'

export default {
  extends: Bar,
  mixins: [mixins.reactiveProp],
  props: ['chartData', 'options'],
  mounted () {
    this.renderChart(this.chartData, this.options)
  }
}
</script>

If I then create the actual chart and let it extend the newly created custom chart like this:

# FinancesChart
<script>
import { mixins } from 'vue-chartjs'
import { RoundedCorners} from './RoundedCorners'

export default {
  extends: RoundedCorners,
  mixins: [mixins.reactiveProp],
  props: ['chartData', 'options'],
  mounted () {
    this.renderChart(this.chartData, this.options)
  }
}
</script>

it says Failed to mount component: template or render function not defined. and this.renderChart is not a function.

So two questions:
1.) How would I create a three step heritage
2.) How could I then override the initialize or draw function like in the above code snippet?

Thanks a lot!

โ“ question

Most helpful comment

After having gained a little bit of understanding about the above mentioned (newer?) interface I was able to make it work. The animation is missing and there is a little clipping error but for everyone being interested in this it could be a good starting point.

<script>
  import Chart from 'chart.js'
  import { generateChart } from 'vue-chartjs'

  // Extend on of the default charts
  // http://www.chartjs.org/docs/latest/developers/charts.html
  Chart.defaults.RoundedBarChart = Chart.defaults.bar;
  Chart.controllers.RoundedBarChart = Chart.controllers.bar.extend({
    name: "RoundedBarChart",
    initialize: function (data) {
     Chart.controllers.bar.prototype.initialize.apply(this, arguments)

      let me = this
      let chart = me.chart
      let scale = me.getValueScale()
      let meta = me.getMeta()
      let rects = meta.data
      let dataset = me.getDataset()
      let ilen = rects.length
      let i = 0
      let rectangleDraw = rects[0].draw

      for (; i < ilen; ++i) {
        let rightValue = scale.getRightValue(dataset.data[i])

        if (!isNaN(rightValue)) {
          rects[i].draw = function() {
            if (typeof meta.data[0]._model === 'undefined') {
              return
            }

            const radius =  meta.data[0]._model.width * 0.5;
            const bar = meta.data[this._index]._model
            // draw the original bar a little down (so that our curve brings it to its original position)
            const y = bar.y
            // the min is required so animation does not start from below the axes
            bar.y = Math.min(bar.y + radius, scale.maxHeight - 1)
            // adjust the bar radius depending on how much of a curve we can draw
            const barRadius = (bar.y - y)
            rectangleDraw.apply(this, arguments)

            // draw a rounded rectangle on top
            Chart.helpers.drawRoundedRectangle(chart.ctx, bar.x - bar.width / 2, bar.y - barRadius + 1, bar.width, bar.base, barRadius)
            chart.ctx.fill();

            // restore the y value
            bar.y = y
          }
        }
      }
    }
  })

  export const RoundedCorners = generateChart('rounded-bar-chart', 'RoundedBarChart')
</script>

https://github.com/chartjs/Chart.js/blob/master/src/controllers/controller.bar.js#L445
Having had a look at the implementation of the original draw function was quite helpful for me.

All 8 comments

Well there are two ways.

  1. You really overwrite the default Bar Chart.
  2. You create your own "rounded-bar-chart"

Overwriting

You need to import the Chart.js object into your component. Then you can overwrite everything in there.

However with this approach you will overwrite the default Bar chart. So all your bar charts will have rounded corners.

# RoundedCorners.vue
<script>
import Chart from 'chart.js'
import { Bar, mixins } from 'vue-chartjs'

Chart.types.Bar.extend()


export default {
  extends: Bar,
  mixins: [mixins.reactiveProp],
  props: ['chartData', 'options'],
  mounted () {
    this.renderChart(this.chartData, this.options)
  }
}
</script>

This should work tho.

Custom Chart

As mentioned here #326
You can however also create your own custom chart. By using the generateChart helper.

You create a new chart type LineWithLine or BarRounded extend the default chart.

// 1. Import Chart.js so you can use the global Chart object
import Chart from 'chart.js'
// 2. Import the `generateChart()` method to create the vue component.
import { generateChart } from 'vue-chartjs'

// 3. Extend on of the default charts
// http://www.chartjs.org/docs/latest/developers/charts.html
Chart.defaults.LineWithLine = Chart.defaults.line;
Chart.controllers.LineWithLine = Chart.controllers.line.extend({ /* custom magic here */})

// 4. Generate the vue-chartjs component
// First argument is the chart-id, second the chart type.
const CustomLine = generateChart('custom-line', 'LineWithLine')

// 5. Extend the CustomLine Component just like you do with the default vue-chartjs charts.

export default {
  extends: CustomLine,
  mounted () {
    // ....
  }
}

http://vue-chartjs.org/#/home?id=custom-new-charts

Thanks a lot for the very quick and though amazingly detailed answer!

Sorry to respond after the ticket has been closed but there is actually an issue that occurred now: the bars-Property is not there (in the datasets object). This property is needed in order to change the drawing behavior of the different bars (as done here http://jsfiddle.net/0dzp3jxw/). It seems to work out of the vue context (seen in the fiddle).

<script>
  import Chart from 'chart.js'
  import { generateChart } from 'vue-chartjs'

  Chart.defaults.RoundedBarChart = Chart.defaults.bar;
  Chart.controllers.RoundedBarChart = Chart.controllers.bar.extend({
    name: "RoundedBarChart",
    initialize: function (data) {
     Chart.controllers.bar.prototype.initialize.apply(this, arguments);
     console.log(this.chart.data.datasets); // no bars property :/
    }
  })

  export const RoundedCorners = generateChart('custom-line', 'RoundedBarChart')
</script>

Any ideas?

Well in the example (jsfiddle) the Chart.types.Bar is exteded. In your vue example you extend the controller.

I guess it is related to this. (http://www.chartjs.org/docs/latest/developers/charts.html#dataset-controller-interface)

Btw. have you tried to console.log(this) ? And see if you are in the right context?

After having gained a little bit of understanding about the above mentioned (newer?) interface I was able to make it work. The animation is missing and there is a little clipping error but for everyone being interested in this it could be a good starting point.

<script>
  import Chart from 'chart.js'
  import { generateChart } from 'vue-chartjs'

  // Extend on of the default charts
  // http://www.chartjs.org/docs/latest/developers/charts.html
  Chart.defaults.RoundedBarChart = Chart.defaults.bar;
  Chart.controllers.RoundedBarChart = Chart.controllers.bar.extend({
    name: "RoundedBarChart",
    initialize: function (data) {
     Chart.controllers.bar.prototype.initialize.apply(this, arguments)

      let me = this
      let chart = me.chart
      let scale = me.getValueScale()
      let meta = me.getMeta()
      let rects = meta.data
      let dataset = me.getDataset()
      let ilen = rects.length
      let i = 0
      let rectangleDraw = rects[0].draw

      for (; i < ilen; ++i) {
        let rightValue = scale.getRightValue(dataset.data[i])

        if (!isNaN(rightValue)) {
          rects[i].draw = function() {
            if (typeof meta.data[0]._model === 'undefined') {
              return
            }

            const radius =  meta.data[0]._model.width * 0.5;
            const bar = meta.data[this._index]._model
            // draw the original bar a little down (so that our curve brings it to its original position)
            const y = bar.y
            // the min is required so animation does not start from below the axes
            bar.y = Math.min(bar.y + radius, scale.maxHeight - 1)
            // adjust the bar radius depending on how much of a curve we can draw
            const barRadius = (bar.y - y)
            rectangleDraw.apply(this, arguments)

            // draw a rounded rectangle on top
            Chart.helpers.drawRoundedRectangle(chart.ctx, bar.x - bar.width / 2, bar.y - barRadius + 1, bar.width, bar.base, barRadius)
            chart.ctx.fill();

            // restore the y value
            bar.y = y
          }
        }
      }
    }
  })

  export const RoundedCorners = generateChart('rounded-bar-chart', 'RoundedBarChart')
</script>

https://github.com/chartjs/Chart.js/blob/master/src/controllers/controller.bar.js#L445
Having had a look at the implementation of the original draw function was quite helpful for me.

How do you make this work?
i have this in a seperate js file i then import in my vue component

` import Chart from 'chart.js'
import { generateChart } from 'vue-chartjs'

Chart.defaults.RoundedBar = Chart.defaults.Bar
Chart.controllers.RoundedBar = Chart.controllers.bar.extend({
name: 'RoundedBarChart',
initialize: function (data) {
Chart.controllers.bar.prototype.initialize.apply(this, arguments)

let me = this
let chart = me.chart
let scale = me.getValueScale()
let meta = me.getMeta()
let rects = meta.data
let dataset = me.getDataset()
let ilen = rects.length
let i = 0
let rectangleDraw = rects[0].draw

for (; i < ilen; ++i) {
  let rightValue = scale.getRightValue(dataset.data[i])

  if (!isNaN(rightValue)) {
    rects[i].draw = function () {
      if (typeof meta.data[0]._model === 'undefined') {
        return
      }

      const radius = meta.data[0]._model.width * 0.5
      const bar = meta.data[this._index]._model
      // draw the original bar a little down (so that our curve brings it to its original position)
      const y = bar.y
      // the min is required so animation does not start from below the axes
      bar.y = Math.min(bar.y + radius, scale.maxHeight - 1)
      // adjust the bar radius depending on how much of a curve we can draw
      const barRadius = (bar.y - y)
      rectangleDraw.apply(this, arguments)

      // draw a rounded rectangle on top
      Chart.helpers.drawRoundedRectangle(chart.ctx, bar.x - bar.width / 2, bar.y - barRadius + 1, bar.width, bar.base, barRadius)
      chart.ctx.fill()

      // restore the y value
      bar.y = y
    }
  }
}

}
})
const RoundedBarChart = generateChart('rounded-bar', 'RoundedBarChart')
export default {
extends: RoundedBarChart,
props: ['options'],
mounted () {
this.renderChart(this.data, this.options)
}
}
`

then i render my component like this

`

            :chart-data="datacollection3"

            :options="{

              tooltips: {

                backgroundColor: '#FFF',
                titleFontSize: 16,
                titleFontColor: '#C2C8CE',
                bodyFontColor: '#00C5C7',
                bodyFontSize: 14,
                displayColors: false,
                bodyFontFamily: 'Lato',
                cornerRadius: 5,
                xPadding: 15,
                yPadding: 15
              },
              responsive: true,
              scales: {
                xAxes: [{
                  stacked: true,
                  barPercetage: 1,
                  gridLines: {
                    color: 'rgba(0, 0, 0, 0)'
                  }
                }],
                yAxes: [{
                  position: 'left',
                  id: 'leftSide',
                  stacked: true,
                  gridLines: {
                    color: 'rgba(0, 0, 0, 0)'
                  },
                  ticks: {
                    fontColor: '#C2C8CE',
                    maxTicksLimit: 3,
                    callback: function (value, index, values) {
                      return value + '%'
                    }
                  }
                },
                {
                  id: 'rightSide',
                  position: 'right',
                  gridLines: {
                    color: 'rgba(0, 0, 0, 0)'
                  },
                  ticks: {
                    fontColor: '#C2C8CE',
                    maxTicksLimit: 2,
                    showGridLines: false,
                    callback: function (value, index, values) {
                      return value + 'stk.'
                    }
                  }
                }]
              },
              maintainAspectRatio: false,
              legend: {
                display: false
              }
            }"
              :styles="{position: 'relative', color: '#606060', height: '100%'}">
      </RoundedBarChart>`

For those of you, who are in trouble using the code: You have to use a underscore:

<script>
      ... 
      let scale = me._getValueScale() // instead of me.getValueScale()
      ...
</script>
Was this page helpful?
0 / 5 - 0 ratings

Related issues

jacobmischka picture jacobmischka  ยท  3Comments

jcalonso picture jcalonso  ยท  4Comments

sylvaincaillot picture sylvaincaillot  ยท  3Comments

sergeynilov picture sergeynilov  ยท  3Comments

syxolk picture syxolk  ยท  5Comments