matthisk / django-jchart

📈 A Django package for plotting charts using the excellent Chart.JS library.
BSD 3-Clause "New" or "Revised" License
121 stars 23 forks source link

How do I add a JS-callback function? #7

Open ChessSpider opened 7 years ago

ChessSpider commented 7 years ago

Hi, loving the work. Simple & effective. I hope you'll continue working on this. I was making some silly mistakes as get_datasets() only a DataSet instead of a [DataSet, ] and everything just failed silently. Maybe in a future version you could make it throw an error or whatever.

But I have a real question. Chart.JS supports events/callback methods. How can I define a JS-callback function for a chart in the Python code?
http://www.chartjs.org/docs/latest/general/interactions/events.html

Thx,

matthisk commented 7 years ago

Hi ChessSpider,

Thanks for the feedback, I released a new version (0.3.2) type checking the result of get_datasets to assure that a list is returned.

Regarding the events/callbacks. It is not easy to extend the configuration options available in the Chart subclass to support this, since the Python code can't interoperate with the JS code running in the browser. What we could do is expose the Chart.js JavaScript object in the global namespace so you can write custom JS code to append handlers to the chart. Then you could do something like this in your templates:

{{ line_chart.as_html }}

<script>
window.addEventListener("DOMContentLoaded", function() {
  window[{{ line_chart.js_name }}].options.onClick = function() {
    console.log('on click handler');
  };
});
</script>

Right now the Chart.js instance is not exposed in the global namespace, it could be easy to implement this. We would only need to change jchart/templates/charting/js.html and generate a unique property on Chart instances name js_name.

ChessSpider commented 7 years ago

Hmm, that looks a bit ugly honestly. Can't we make a wrapper which works kinda like this:

        onHover =  JsFunction("function(param){ alert(param); } ")
        onClick = JsFunction("function(param2){ alert(param2); } ")

and just shows this as

var chart = new Chart(ctx, {
    type: 'line',
    data: data,
    options: {
        // just a silly example
       onHover: function(param){ alert(param); }
       onClick: function(param2){ alert(param2); }
    }
});

so basically, JsFunction doesnt show the value as a string. Is that maybe possible by subclassing the JsonEncoder used?

matthisk commented 7 years ago

I don't like this approach for several reasons.

You have to maintain JS functions outside of the main JS codebase. This makes it harder to keep track of where code resides and to make changes to the (global) context of the JS application.

IDE/tools won't work on JS code written as strings inside Python code.

It presents a possibility for XSS attacks if people decide to use user input/DB content to generate event handler functions.

The main goal of this project is to allow people to use and configure Chart.JS charts in their Django application without writing JS code, this approach is in contradiction with this goal. I am worried that it will overcomplicate the API of this library.

ChessSpider commented 7 years ago

I would like to define&configure the chart in one place instead of half in Python-code and half in a template. I was kinda thinking like, I can already set & define all other options & behavior in the Python code. So why not this?

Especially with the nested structure of settings in the ChartJS, how would I easily define a custom parser for http://www.chartjs.org/docs/latest/axes/cartesian/time.html ?

In addition of your proposal, one could also override def render_js() and call a custom template. Then you do not have to expose the chart-object in the global namespace