web-dev-qa-db-fra.com

Passer des données de Django à D3

J'essaie d'écrire un graphique à barres très basique en utilisant Django et D3.js. J'ai un objet appelé play avec un champ datetime appelé date. Ce que je veux faire est d'afficher le nombre de lectures au fil du temps regroupés par mois. En gros, j'ai deux questions:

  1. Comment les regrouper par mois avec un décompte du nombre de jeux au cours de ce mois
  2. Quelle est la meilleure façon d'obtenir ces informations de Django en quelque chose utilisable par D3.

Maintenant, j'ai regardé d'autres réponses ici et j'ai essayé

json = (Play.objects.all().extra(select={'month': "extract(month FROM date)"})
.values('month').annotate(count_items=Count('date')))

Cela se rapproche des informations que je veux, mais lorsque j'essaie de les publier dans le modèle, elles apparaissent comme suit (avec Ls) à la fin des mois. Cela signifie qu'évidemment, ce n'est pas un js valide (pas de qoutes) et je ne veux pas vraiment les Ls de toute façon.

Modèle:

    <script>
        var test ={{ json|safe }};
        alert("test");

    </script>

production:

var test = [{'count_items': 10, 'month': 1L}, {'count_items': 5, 'month': 2L}];

J'ai également essayé json.dumps sur ces données mais on me dit que ce n'est pas du JSON valide. Il semble que cela devrait être beaucoup plus simple à faire dans Django alors peut-être que je me dirige entièrement vers le chemin usé.

21
Matt shannon

Étant donné que D3.js v3 possède une belle collection de méthodes pour charger des données à partir de ressources externes ¹, il est préférable de ne pas intégrer de données dans votre page, il vous suffit de les charger.

Ce sera une réponse par exemple.

Commençons par une définition de modèle:

# models.py
from Django.db import models


class Play(models.Model):
    name = models.CharField(max_length=100)
    date = models.DateTimeField()

Un urlconf:

# urls.py
from Django.conf.urls import url


from .views import graph, play_count_by_month

urlpatterns = [
    url(r'^$', graph),
    url(r'^api/play_count_by_month', play_count_by_month, name='play_count_by_month'),
]

Nous utilisons deux URL, l'une pour renvoyer le code HTML (vue graph), et l'autre URL (vue play_count_by_month) en tant qu'api pour renvoyer uniquement les données au format JSON.

Et enfin nos vues:

# views.py
from Django.db import connections
from Django.db.models import Count
from Django.http import JsonResponse
from Django.shortcuts import render

from .models import Play


def graph(request):
    return render(request, 'graph/graph.html')


def play_count_by_month(request):
    data = Play.objects.all() \
        .extra(select={'month': connections[Play.objects.db].ops.date_trunc_sql('month', 'date')}) \
        .values('month') \
        .annotate(count_items=Count('id'))
    return JsonResponse(list(data), safe=False)

Ici, nous avons défini une vue pour renvoyer nos données en JSON, notez que j'ai changé supplémentaire pour être indépendant de la base de données, car j'ai fait des tests avec SQLite.

Et suit notre graph/graph.html modèle qui montre un graphique du nombre de jeux par mois:

<!DOCTYPE html>
<meta charset="utf-8">
<style>

body {
  font: 10px sans-serif;
}

.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}

.x.axis path {
  display: none;
}

.line {
  fill: none;
  stroke: steelblue;
  stroke-width: 1.5px;
}

</style>
<body>
<script src="http://d3js.org/d3.v3.js"></script>
<script>

var margin = {top: 20, right: 20, bottom: 30, left: 50},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

var parseDate = d3.time.format("%Y-%m-%d").parse; // for dates like "2014-01-01"
//var parseDate = d3.time.format("%Y-%m-%dT00:00:00Z").parse;  // for dates like "2014-01-01T00:00:00Z"

var x = d3.time.scale()
    .range([0, width]);

var y = d3.scale.linear()
    .range([height, 0]);

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left");

var line = d3.svg.line()
    .x(function(d) { return x(d.month); })
    .y(function(d) { return y(d.count_items); });

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

d3.json("{% url "play_count_by_month" %}", function(error, data) {
  data.forEach(function(d) {
    d.month = parseDate(d.month);
    d.count_items = +d.count_items;
  });

  x.domain(d3.extent(data, function(d) { return d.month; }));
  y.domain(d3.extent(data, function(d) { return d.count_items; }));

  svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")")
      .call(xAxis);

  svg.append("g")
      .attr("class", "y axis")
      .call(yAxis)
    .append("text")
      .attr("transform", "rotate(-90)")
      .attr("y", 6)
      .attr("dy", ".71em")
      .style("text-anchor", "end")
      .text("Play count");

  svg.append("path")
      .datum(data)
      .attr("class", "line")
      .attr("d", line);
});

</script>
</body>
</html>

Cela retournera un joli graphique comme celui-ci (données aléatoires): Graph of Play counts by month

Mise à jour 1 : D3 v4 déplacera le code pour charger les données externes vers une bibliothèque dédiée, veuillez consulter d3-request . Mise à jour 2 : Afin d'aider, j'ai rassemblé tous les fichiers dans un exemple de projet, sur github: github.com/fgmacedo/Django-d3-exemple

66
Fernando Macedo

J'ai adoré ce que fernando-macedo a mis en place et cela m'a amené à un certain point avec mes données.

Cependant, j'ai eu du mal à filtrer les données au lieu de passer l'ensemble de données via cette configuration API. Ceci est très similaire au problème des autres personnes de transmettre des données JSON à partir d'un Queryset et Pavel Patrin réponse m'a aidé avec cela.

Ainsi, cela permettra désormais aux utilisateurs de filtrer leurs données et de les envoyer en tant que json pour les utiliser en d3. Maintenant, j'utilise le même exemple hypothétique, mais cela devrait fonctionner pour

# views.py
from Django.db import connections
from Django.db.models import Count
# from Django.http import JsonResponse  #no longer needed
from Django.shortcuts import render
import json


from .models import Play


def graph(request):
    data = Play.objects.filter(name__startswith='Test') \ #change here for filter. can be any kind of filter really
        .extra(select={'month': connections[Play.objects.db].ops.date_trunc_sql('month', 'date')}) \
        .values('month') \
        .annotate(count_items=Count('id'))
    formattedData=json.dumps([dict(item) in list(data)]) #This is a two-fer. It converts each item in the Queryset to a dictionary and then formats it using the json from import json above
    #now we can pass formattedData via the render request
    return render(request, 'graph/graph.html',{'formattedData':formattedData})

Maintenant, pour obtenir cela de manière appropriée de l'autre côté (le côté html)

<script src="{% static 'd3.v3.min.js' %}" charset="utf-8"></script>
<script type='text/javascript'> // the type text/javascript is key here!
var data= {{formattedData|safe}} // now you can just reference data with no need to use d3.json.
//Critical that there is no quotation marks here and this is where you denote safe!

//Insert the rest
//of Fernando's code here
//minus the last '});'
//as that ends the d3.json function call
</script>

Quoi qu'il en soit, j'espère que cela fera gagner du temps à quelqu'un avec Django et/ou D3 car cela résout deux problèmes à la fois.

2
jtclaypool