Refactor graphical timeline, by making the data available via doc.json. Also
refactor associated js/css/html. Finally, a lot of display tweaks. - Legacy-Id: 10550
This commit is contained in:
parent
e1ff3a5782
commit
d4d09a2f75
|
@ -3,7 +3,6 @@ import re
|
|||
import urllib
|
||||
import math
|
||||
import datetime
|
||||
import operator
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models.query import EmptyQuerySet
|
||||
|
@ -542,7 +541,7 @@ def uppercase_std_abbreviated_name(name):
|
|||
return name
|
||||
|
||||
def crawl_history(doc):
|
||||
# return document history data for use in ietf/templates/doc/timeline.html
|
||||
# return document history data for inclusion in doc.json (used by timeline)
|
||||
def ancestors(doc):
|
||||
retval = []
|
||||
if hasattr(doc, 'relateddocument_set'):
|
||||
|
@ -559,12 +558,20 @@ def crawl_history(doc):
|
|||
for d in history:
|
||||
for e in d.docevent_set.filter(type='new_revision'):
|
||||
if hasattr(e, 'newrevisiondocevent'):
|
||||
retval.append((d.name, e.newrevisiondocevent.rev, e.time.isoformat()))
|
||||
retval.append({
|
||||
'name': d.name,
|
||||
'rev': e.newrevisiondocevent.rev,
|
||||
'published': e.time.isoformat()
|
||||
})
|
||||
|
||||
if doc.type_id == "draft":
|
||||
e = doc.latest_event(type='published_rfc')
|
||||
else:
|
||||
e = doc.latest_event(type='iesg_approved')
|
||||
if e:
|
||||
retval.append((doc.name, e.doc.canonical_name, e.time.isoformat()))
|
||||
return sorted(retval, key=operator.itemgetter(2))
|
||||
retval.append({
|
||||
'name': e.doc.canonical_name(),
|
||||
'rev': e.doc.canonical_name(),
|
||||
'published': e.time.isoformat()
|
||||
})
|
||||
return sorted(retval, key=lambda x: x['published'])
|
||||
|
|
|
@ -417,7 +417,6 @@ def document_main(request, name, rev=None):
|
|||
search_archive=search_archive,
|
||||
actions=actions,
|
||||
tracking_document=tracking_document,
|
||||
rev_history=crawl_history(latest_revision.doc if latest_revision else doc),
|
||||
),
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
|
@ -443,8 +442,6 @@ def document_main(request, name, rev=None):
|
|||
|
||||
can_manage = can_manage_group_type(request.user, doc.group.type_id)
|
||||
|
||||
latest_revision = doc.latest_event(NewRevisionDocEvent, type="new_revision")
|
||||
|
||||
return render_to_response("doc/document_charter.html",
|
||||
dict(doc=doc,
|
||||
top=top,
|
||||
|
@ -459,7 +456,6 @@ def document_main(request, name, rev=None):
|
|||
group=group,
|
||||
milestones=milestones,
|
||||
can_manage=can_manage,
|
||||
rev_history=crawl_history(latest_revision.doc if latest_revision else doc),
|
||||
),
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
|
@ -477,8 +473,6 @@ def document_main(request, name, rev=None):
|
|||
if doc.get_state_slug() in ("iesgeval") and doc.active_ballot():
|
||||
ballot_summary = needed_ballot_positions(doc, doc.active_ballot().active_ad_positions().values())
|
||||
|
||||
latest_revision = doc.latest_event(NewRevisionDocEvent, type="new_revision")
|
||||
|
||||
return render_to_response("doc/document_conflict_review.html",
|
||||
dict(doc=doc,
|
||||
top=top,
|
||||
|
@ -490,7 +484,6 @@ def document_main(request, name, rev=None):
|
|||
conflictdoc=conflictdoc,
|
||||
ballot_summary=ballot_summary,
|
||||
approved_states=('appr-reqnopub-pend','appr-reqnopub-sent','appr-noprob-pend','appr-noprob-sent'),
|
||||
rev_history=crawl_history(latest_revision.doc if latest_revision else doc),
|
||||
),
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
|
@ -515,8 +508,6 @@ def document_main(request, name, rev=None):
|
|||
else:
|
||||
sorted_relations=None
|
||||
|
||||
latest_revision = doc.latest_event(NewRevisionDocEvent, type="new_revision")
|
||||
|
||||
return render_to_response("doc/document_status_change.html",
|
||||
dict(doc=doc,
|
||||
top=top,
|
||||
|
@ -528,7 +519,6 @@ def document_main(request, name, rev=None):
|
|||
ballot_summary=ballot_summary,
|
||||
approved_states=('appr-pend','appr-sent'),
|
||||
sorted_relations=sorted_relations,
|
||||
rev_history=crawl_history(latest_revision.doc if latest_revision else doc),
|
||||
),
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
|
@ -921,6 +911,9 @@ def document_json(request, name):
|
|||
data["shepherd"] = doc.shepherd.formatted_email() if doc.shepherd else None
|
||||
data["ad"] = doc.ad.role_email("ad").formatted_email() if doc.ad else None
|
||||
|
||||
latest_revision = doc.latest_event(NewRevisionDocEvent, type="new_revision")
|
||||
data["rev_history"] = crawl_history(latest_revision.doc if latest_revision else doc)
|
||||
|
||||
if doc.type_id == "draft":
|
||||
data["iesg_state"] = extract_name(doc.get_state("draft-iesg"))
|
||||
data["rfceditor_state"] = extract_name(doc.get_state("draft-rfceditor"))
|
||||
|
@ -932,7 +925,7 @@ def document_json(request, name):
|
|||
data["consensus"] = e.consensus if e else None
|
||||
data["stream"] = extract_name(doc.stream)
|
||||
|
||||
return HttpResponse(json.dumps(data, indent=2), content_type='text/plain')
|
||||
return HttpResponse(json.dumps(data, indent=2), content_type='application/json')
|
||||
|
||||
class AddCommentForm(forms.Form):
|
||||
comment = forms.CharField(required=True, widget=forms.Textarea)
|
||||
|
|
126
ietf/static/ietf/js/document_timeline.js
Normal file
126
ietf/static/ietf/js/document_timeline.js
Normal file
|
@ -0,0 +1,126 @@
|
|||
var data;
|
||||
|
||||
d3.json("doc.json", function(error, json) {
|
||||
if (error) return console.warn(error);
|
||||
data = json["rev_history"];
|
||||
|
||||
// make js dates out of publication dates
|
||||
data.forEach(function(el) { el.published = new Date(el.published); });
|
||||
|
||||
// add pseudo entry for beginning of year of first publication
|
||||
var year = data[0].published.getFullYear();
|
||||
data.unshift({ name:'', rev: '', published: new Date(year, 0, 0)});
|
||||
|
||||
// add pseudo entry at end of year of last revision
|
||||
year = data[data.length - 1].published.getFullYear();
|
||||
data.push({ name:'', rev: '', published: new Date(year + 1, 0, 0)});
|
||||
|
||||
draw_timeline();
|
||||
});
|
||||
|
||||
|
||||
var xscale;
|
||||
var y;
|
||||
var bar_height;
|
||||
|
||||
|
||||
function offset(d, i) {
|
||||
if (i > 1 && data[i - 1].name !== d.name || d.rev.match("rfc"))
|
||||
y += bar_height;
|
||||
return "translate(" + xscale(d.published) + ", " + y + ")";
|
||||
}
|
||||
|
||||
|
||||
function bar_width(d, i) {
|
||||
if (i > 0 && i < data.length - 1)
|
||||
return xscale(data[i + 1].published) - xscale(d.published);
|
||||
}
|
||||
|
||||
|
||||
function draw_timeline() {
|
||||
var w = $("#timeline").width();
|
||||
// bar_height = parseFloat($("body").css('line-height'));
|
||||
bar_height = 30;
|
||||
|
||||
xscale = d3.time.scale().domain([
|
||||
d3.min(data, function(d) { return d.published; }),
|
||||
d3.max(data, function(d) { return d.published; })
|
||||
]).range([0, w]);
|
||||
|
||||
y = 0;
|
||||
var chart = d3.select("#timeline svg").attr("width", w);
|
||||
var bar = chart.selectAll("g").data(data);
|
||||
|
||||
// update
|
||||
bar
|
||||
.attr("transform", offset)
|
||||
.select("rect")
|
||||
.attr("width", bar_width);
|
||||
|
||||
// enter
|
||||
var g = bar.enter()
|
||||
.append("g")
|
||||
.attr({
|
||||
class: "bar",
|
||||
transform: offset
|
||||
});
|
||||
g.append("rect")
|
||||
.attr({
|
||||
height: bar_height,
|
||||
width: bar_width
|
||||
});
|
||||
g.append("text")
|
||||
.attr({
|
||||
x: 3,
|
||||
y: bar_height/2
|
||||
})
|
||||
.text(function(d) { return d.rev; });
|
||||
|
||||
// exit
|
||||
bar.exit().remove();
|
||||
|
||||
var xaxis = d3.svg.axis()
|
||||
.scale(xscale)
|
||||
.tickValues(data.slice(1, -1).map(function(d) { return d.published; }))
|
||||
.tickFormat(d3.time.format("%b %Y"))
|
||||
.orient("bottom");
|
||||
|
||||
var ids = data
|
||||
.map(function(elem) { return elem.name; })
|
||||
.filter(function(val, i, self) { return self.indexOf(val) === i; });
|
||||
ids.shift(); // first one is pseudo entry (last one, too, but filtered above)
|
||||
console.log(ids);
|
||||
|
||||
var yaxis = d3.svg.axis()
|
||||
.scale(d3.scale.ordinal().domain(ids).rangePoints([0, y - bar_height]))
|
||||
.tickValues(ids)
|
||||
.orient("left");
|
||||
|
||||
chart.append("g")
|
||||
.attr({
|
||||
class: "x axis",
|
||||
transform: "translate(0, " + y + ")"
|
||||
})
|
||||
.call(xaxis)
|
||||
.selectAll("text")
|
||||
.style("text-anchor", "end")
|
||||
.attr("transform", "translate(-18, 8) rotate(-90)");
|
||||
|
||||
chart.append("g")
|
||||
.attr({
|
||||
class: "y axis",
|
||||
transform: "translate(10, " + bar_height/2 + ")"
|
||||
})
|
||||
.call(yaxis)
|
||||
.selectAll("text")
|
||||
.style("text-anchor", "start");
|
||||
|
||||
chart.attr('height', y);
|
||||
}
|
||||
|
||||
|
||||
$(window).on({
|
||||
resize: function (event) {
|
||||
draw_timeline();
|
||||
}
|
||||
});
|
|
@ -10,22 +10,40 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block morecss %}
|
||||
.inline { display: inline; }
|
||||
{% endblock %}
|
||||
.inline { display: inline; }
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
$(window).on({
|
||||
resize: function (event) {
|
||||
draw_timeline();
|
||||
},
|
||||
load: function (event) {
|
||||
draw_timeline();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
#timeline .axis text { font-size: small; }
|
||||
|
||||
#timeline .axis path, #timeline .axis line {
|
||||
fill: none;
|
||||
stroke: black;
|
||||
}
|
||||
|
||||
#timeline .axis.y path, #timeline .axis.y line { stroke: none; }
|
||||
|
||||
#timeline .axis.x text {
|
||||
dominant-baseline: central;
|
||||
fill: darkgrey;
|
||||
}
|
||||
|
||||
#timeline .bar text {
|
||||
fill: white;
|
||||
dominant-baseline: central;
|
||||
}
|
||||
|
||||
#timeline .bar:nth-child(odd) rect {
|
||||
fill: #3abf03;
|
||||
stroke: #32a602;
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
#timeline .bar:nth-child(even) rect {
|
||||
fill: #6b5bad;
|
||||
stroke: #5f4f9f;
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}
|
||||
{% if doc.get_state_slug == "rfc" %}
|
||||
|
@ -40,7 +58,7 @@ $(window).on({
|
|||
{{ top|safe }}
|
||||
|
||||
{% include "doc/revisions_list.html" %}
|
||||
{% include "doc/timeline.html" %}
|
||||
<div id="timeline"><svg></svg></div>
|
||||
|
||||
<table class="table table-condensed">
|
||||
<thead id="message-row">
|
||||
|
@ -509,3 +527,9 @@ $(window).on({
|
|||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static 'd3/d3.min.js' %}"></script>
|
||||
<script src="{% static 'ietf/js/document_timeline.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
{% load staticfiles %}
|
||||
<script src="{% static 'd3/d3.min.js' %}"></script>
|
||||
|
||||
<style>
|
||||
#timeline { width: 100%; }
|
||||
#timeline text { fill: white; }
|
||||
#timeline > :nth-child(odd) { fill: steelblue; }
|
||||
#timeline > :nth-child(even) { fill: red; }
|
||||
|
||||
.axis path,
|
||||
.axis line {
|
||||
fill: none;
|
||||
stroke: black;
|
||||
}
|
||||
|
||||
#timeline .axis text {
|
||||
fill: black;
|
||||
font-size: small;
|
||||
}
|
||||
</style>
|
||||
|
||||
<svg id="timeline"></svg>
|
||||
|
||||
<script>
|
||||
|
||||
var data = [
|
||||
{% for r in rev_history %}
|
||||
{% if forloop.first %}
|
||||
{ name: '{{ r.2 }}'.substring(0, 4), rev: '', time: new Date('{{ r.2 }}'.substring(0, 4))},
|
||||
{% endif %}
|
||||
{ name: '{{r.0}}', rev: '{{r.1}}', time: new Date('{{ r.2 }}')},
|
||||
{% if forloop.last %}
|
||||
{ name: (parseInt('{{ r.2 }}'.substring(0, 4)) + 1).toString(), rev: '', time: new Date((parseInt('{{ r.2 }}'.substring(0, 4)) + 1).toString())},
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
];
|
||||
|
||||
var x;
|
||||
var y;
|
||||
var bar_height;
|
||||
|
||||
|
||||
function offset(d, i) {
|
||||
if (i > 1 && data[i - 1].name !== d.name || d.rev.match("rfc"))
|
||||
y += bar_height;
|
||||
return "translate(" + x(d.time) + ", " + y + ")";
|
||||
}
|
||||
|
||||
|
||||
function bar_width(d, i) {
|
||||
if (i > 0 && i < data.length - 1)
|
||||
return x(data[i + 1].time) - x(d.time);
|
||||
}
|
||||
|
||||
|
||||
function draw_timeline() {
|
||||
var w = $("#timeline").width();
|
||||
bar_height = parseFloat($("body").css('line-height'));
|
||||
|
||||
x = d3.time.scale().domain([
|
||||
d3.min(data, function(d) { return d.time; }),
|
||||
d3.max(data, function(d) { return d.time; })
|
||||
]).range([0, w]);
|
||||
|
||||
y = 0;
|
||||
var chart = d3.select("#timeline");
|
||||
var bar = chart.selectAll("g").data(data);
|
||||
|
||||
// update
|
||||
bar
|
||||
.attr("transform", offset)
|
||||
.select("rect")
|
||||
.attr("width", bar_width);
|
||||
|
||||
// enter
|
||||
var g = bar.enter()
|
||||
.append("g")
|
||||
.attr("transform", offset);
|
||||
g.append("rect")
|
||||
.attr({
|
||||
height: bar_height,
|
||||
width: bar_width
|
||||
});
|
||||
g.append("text")
|
||||
.attr("y", bar_height/2)
|
||||
.text(function (d) { return d.rev; });
|
||||
|
||||
// exit
|
||||
bar.exit().remove();
|
||||
|
||||
chart.attr("height", y + 3*bar_height);
|
||||
|
||||
var axis = d3.svg.axis()
|
||||
.scale(x)
|
||||
.tickValues(data.slice(1, -1).map(function(d) { return d.time; }))
|
||||
.tickFormat(d3.time.format("%b %Y"))
|
||||
.orient("bottom");
|
||||
|
||||
chart.append("g")
|
||||
.attr("class", "x axis")
|
||||
.attr("transform", "translate(0, " + y + ")")
|
||||
.call(axis)
|
||||
.selectAll("text")
|
||||
.style("text-anchor", "end")
|
||||
.attr("transform", "translate(-13, 10) rotate(-90)");
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
Loading…
Reference in a new issue