Chart.js: Feature Request: bubble radius scaling based on value/size property

Created on 23 Sep 2016  路  11Comments  路  Source: chartjs/Chart.js

Setting a radius based on the value prop and the size of the canvas would be awesome. Currently using d3 to accomplish this instead. Simple example

data: [
  {x:foo, y:bar, value: 100},
  {x:fizz, y:buzz, value: 50},
  {x:foobar, y:baz, value: 25},
]

// ... outputs

data: [
  {x:foo, y:bar, r: 20},
  {x:fizz, y:buzz, r: 10},
  x:foobar, y:baz, r: 5},
]
help wanted enhancement

Most helpful comment

Drawback with the above solution is if you hover over a bubble, the radius is shown as "undefined". Can we please get a resolution for that too?

All 11 comments

@timelf123 this sounds like a good idea and probably relatively simple to implement. If anyone wants to tackle this hop on slack and let us know so that we can all be on the same page.

Example - Using D3 to scale the chart on the server

Ended using d3 to scale the data for me on the server and pass the results back to the client. I'm sure there is a more efficient way with the usual functional suspects, however this worked as I had lodash already in this environment. Below is just an example

The problem with generating the config server side is passing callback functions becomes difficult in JSON (needing to stringify the function etc then restore it on the frontend), so I may end up letting the server handle 99% of the work getting the graph object together, then tacking the callback functions onto the object in the frontend rather then Express -> JSON config object with callback functions stringified -> unstringify and Function(unstringified)

var d3 = require('d3')
var list = [...]
var objValues = list.map(function(obj) {
  return Math.round(obj.value)
})
var objValuesOverZero = objValues.filter(function(value) {
    return Math.round(value > 0);
})
var minValue = _.min(objValuesOverZero)
var maxValue = _.max(objValuesOverZero)

var range = [5, 50] // range in pixels for bubble radius, min/max
var domain = [minValue, maxValue]
/**
  * scaleRadius - scale for bubble chart because chart js doesn't calculate radius, must set in pixels...
  *
  * @param {Object}
  * @returns {Number} scaled radius value r to be used in chartjs bubble chart
  */
function scaleRadius(obj) {
  // var scaleSqrt = d3.scaleSqrt()
  //     .domain()
  //     .range(range)
  // var scaleLinear = d3.scaleLinear()
  //     .domain([minValue,maxValue])
  //     .range(range)
  // var scalePow = d3.scalePow()
  //     .domain([minValue,maxValue])
  //     .range(range)
  //     .exponent(3)
   var scaleLog = d3.scaleLog()
       .domain([minValue,maxValue])
       .range(range)
  return scaleLog(obj.value)
}

Later:

datasets[0].data.push({ r:scaleRadius(obj), y: foo, x: bar});

running into this myself, any tips on scaling factors / good ranges? trying to think through how to get the scaling to look good at different media sizes. wouldn't mind saving a little trial and error ;)

@ralphcallaway @timelf123 Not sure it's exactly what you are looking for but the recently introduced scriptable options (v2.7) might help to implement this (see this fiddle):

options: {
  elements: {
    point: {
      radius: function(context) {
        var index = context.dataIndex;
        var data = context.dataset.data[index];
        var size = context.chart.width;
        var base = data.value / 100; 
        return (size / 24) * base;
      }
    }
  }
}

100 being the max value (you might still want to compute it from your data or decide of one that works fine in your case) and 24 is arbitrary. As you can see, scriptable options also allow to compute the radius based on any data value (data.value in your case), so no need to convert data.value to data.r.

Drawback with the above solution is if you hover over a bubble, the radius is shown as "undefined". Can we please get a resolution for that too?

I am using the latest version of chart.js which is 2.9.3. I used the same function provided above.

elements: 
    { 
        point: 
        {
        radius: function(context) {
        var index = context.dataIndex;
        var data = context.dataset.data[index];
        var size = context.chart.width;
        var base = Math.abs(data.value) / 30;
        var value =  Math.round((size /5) * base);
        return value;
        },                      
      }
    },

I am getting the following error. Request support
Type '(context: any) => number' is not assignable to type 'number'.

@dagdashboard when do you get that error? At build time or runtime? It sounds to me like an error with TypeScript types provided by Definitely Typed

Hi Ben McCann,

Thanks for your response.
During build time itself I am getting the error. There is not much of logic/code. I have a simple bubble chart generated with static data. I am just trying to manage the Radius size of the Bubble in the bubble chart and utilized the code posted by Simon Brunel. I am unable to interpret the error.

error TS2322: Type '(context: any) => number' is not assignable to type 'number'

@dagdashboard this is not a bug with Chart.js, but with the type definitions you are using, which are not maintained by this group. I'm guessing you're using https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/chart.js/index.d.ts and will need to file a bug there. You can stop using the types and use just regular JavaScript and it hopefully will work

I went and looked at the types we brought into the v3 code. It appears like radius is correctly defined when accessed via the dataset options, but it is incorrectly defined when accessed via options.elements.point.

Easy to scale using the scales of the charts too:

data: [{x: 10, y: 11, size: 1}, {x: 20, y: 5, size: 3}],
radius(context) {
  var scale = context.chart.scales.y;
  var zero = scale.getPixelForValue(0);
  var value = scale.getPixelForValue(context.dataset.data[context.dataIndex].size);
  return Math.abs(value - zero);
}

https://codepen.io/kurkle/pen/GRjJQZE

Was this page helpful?
0 / 5 - 0 ratings