Python 将数据从 Django 传递到 D3

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/26453916/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 00:29:46  来源:igfitidea点击:

Passing data from Django to D3

javascriptpythonjsondjangod3.js

提问by Matt shannon

I'm trying to write a very basic bar graph using Django and D3.js. I have an object called play with a datetime field called date. What I want to do is show number of plays over time grouped by month. Basically I have two questions:

我正在尝试使用 Django 和 D3.js 编写一个非常基本的条形图。我有一个名为 play 的对象,带有一个名为 date 的日期时间字段。我想要做的是显示按月分组的时间段内的播放次数。基本上我有两个问题:

  1. How do I get these grouped by month with a count of the number of plays in that month
  2. What is the best way to get this information from Django into something usable by D3.
  1. 我如何将这些按月分组并计算该月的播放次数
  2. 从 Django 获取这些信息到 D3 可用的东西的最佳方法是什么?

Now I have looked at some other answers on here and have tried

现在我在这里查看了其他一些答案并尝试过

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

This gets close to trhe information I want but when I try to output it in the template it comes out as the following (with Ls) on the end of the months. This means that obviously it isn't valid js (no qoutes) and I don't really want the Ls on the end there anyway.

这接近我想要的信息,但是当我尝试在模板中输出它时,它会在月末显示如下(带有 Ls)。这意味着显然它不是有效的 js(没有 qoutes),无论如何我真的不希望 Ls 出现在那里。

Template:

模板:

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

    </script>

output:

输出:

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

I have also tried json.dumps on this data but I get told it isn't valid JSON. This feels like it should be a lot more straightforward to do in Django so maybe I am headed down the worng path entirely.

我也尝试过对这些数据使用 json.dumps,但我被告知它不是有效的 JSON。这感觉在 Django 中应该更简单,所以也许我完全走上了破旧的道路。

采纳答案by Fernando Macedo

Since D3.js v3 has a nice collection of methods to load data from external resources1, It's better to you not embed data into your page, you just load it.

由于 D3.js v3 有一个很好的从外部资源加载数据方法集合1,你最好不要将数据嵌入到你的页面中,你只是加载它。

This will be an answer by example.

这将是一个例子的答案。

Let's start with a model definition:

让我们从模型定义开始:

# models.py
from django.db import models


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

A urlconf:

一个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'),
]

We are using two urls, one to return the html (view graph), and the other url (view play_count_by_month) as an api to return only data as JSON.

我们使用了两个 url,一个返回 html (view graph),另一个 url (view play_count_by_month) 作为 api 仅以 JSON 形式返回数据。

And finally our views:

最后我们的观点:

# 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)

Here we defined an view to return our data as JSON, note that I changed extra to be database agnostic, since I did tests with SQLite.

在这里,我们定义了一个视图以将我们的数据返回为 JSON,请注意,我将额外更改为与数据库无关,因为我使用 SQLite 进行了测试。

And follows our graph/graph.htmltemplate that shows a graph of play counts by month:

并遵循我们的graph/graph.html模板,该模板按月显示播放次数图表:

<!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>

This will return a nice graph like this (random data): Graph of Play counts by month

这将返回一个像这样的漂亮图表(随机数据): 按月播放次数图表

Update 1: D3 v4 will move the code to load external data to a dedicated lib, please see d3-request. Update 2: In order to help, I've put all files together into an example project, on github: github.com/fgmacedo/django-d3-example

更新 1:D3 v4 将代码移动以将外部数据加载到专用库,请参阅d3-request更新 2:为了提供帮助,我将所有文件放在一个示例项目中,在 github 上:github.com/fgmacedo/django-d3-example

回答by jtclaypool

I loved what fernando-macedo put together and it got me to a certain point with my data.

我喜欢 fernando-macedo 组合在一起的东西,它让我的数据达到了一定程度。

However I struggled with filtering of data as opposed to passing the entire dataset via this api setup. This is very similar to other peoples problem of passing JSON data from a Queryset and Pavel Patrin's answerhelped me with that.

但是,我在过滤数据方面遇到了困难,而不是通过此 api 设置传递整个数据集。这与其他人从 Queryset 传递 JSON 数据的问题非常相似,Pavel Patrin 的回答帮助我解决了这个问题。

So this will now allow people to filtertheir data and send it as a json for use in d3. Now I am using the same hypothetical example but it should work for

所以现在这将允许人们过滤他们的数据并将其作为 json 发送以在 d3 中使用。现在我使用相同的假设示例,但它应该适用于

# 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})

Now to get that appropriately on the other side (the html side)

现在在另一侧(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>

Anyways, I hope this saves someone some time with Django and/or D3 as this solves two issues at once.

无论如何,我希望这可以为 Django 和/或 D3 节省一些时间,因为这可以同时解决两个问题。