Add sortable exercise list to exercise collection new/edit page

This commit is contained in:
Maximilian Grundke
2018-07-03 16:14:19 +02:00
parent 089bf578d3
commit e7b38df0eb
4 changed files with 140 additions and 83 deletions

View File

@ -1,102 +1,135 @@
$(function() { $(function() {
if ($.isController('exercise_collections')) { if ($.isController('exercise_collections')) {
var data = $('#data').data('working-times'); var data = $('#data');
var averageWorkingTimeValue = parseFloat($('#data').data('average-working-time')); var exerciseList = $('#exercise-list');
var margin = { top: 30, right: 40, bottom: 30, left: 50 }, if (data.isPresent()) {
width = 720 - margin.left - margin.right, data.data('working-times');
height = 500 - margin.top - margin.bottom; var averageWorkingTimeValue = parseFloat(data.data('average-working-time'));
var x = d3.scaleBand().range([0, width]); var margin = {top: 30, right: 40, bottom: 30, left: 50},
var y = d3.scaleLinear().range([height, 0]); width = 720 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var xAxis = d3.axisBottom(x); var x = d3.scaleBand().range([0, width]);
var yAxisLeft = d3.axisLeft(y); var y = d3.scaleLinear().range([height, 0]);
var tooltip = d3.select("#graph").append("div").attr("class", "exercise-id-tooltip"); var xAxis = d3.axisBottom(x);
var yAxisLeft = d3.axisLeft(y);
var averageWorkingTime = d3.line() var tooltip = d3.select("#graph").append("div").attr("class", "exercise-id-tooltip");
.x(function (d) { return x(d.index) + x.bandwidth()/2; })
.y(function () { return y(averageWorkingTimeValue); });
var minWorkingTime = d3.line() var averageWorkingTime = d3.line()
.x(function (d) { return x(d.index) + x.bandwidth()/2; }) .x(function (d) {
.y(function () { return y(0.1*averageWorkingTimeValue); }); return x(d.index) + x.bandwidth() / 2;
})
.y(function () {
return y(averageWorkingTimeValue);
});
var maxWorkingTime = d3.line() var minWorkingTime = d3.line()
.x(function (d) { return x(d.index) + x.bandwidth()/2; }) .x(function (d) {
.y(function () { return y(2*averageWorkingTimeValue); }); return x(d.index) + x.bandwidth() / 2;
})
.y(function () {
return y(0.1 * averageWorkingTimeValue);
});
var svg = d3.select('#graph') var maxWorkingTime = d3.line()
.append("svg") .x(function (d) {
.attr("width", width + margin.left + margin.right) return x(d.index) + x.bandwidth() / 2;
.attr("height", height + margin.top + margin.bottom) })
.append("g") .y(function () {
.attr("transform", return y(2 * averageWorkingTimeValue);
"translate(" + margin.left + "," + margin.top + ")"); });
// Get the data var svg = d3.select('#graph')
data = Object.keys(data).map(function (key, index) { .append("svg")
return { .attr("width", width + margin.left + margin.right)
index: index, .attr("height", height + margin.top + margin.bottom)
exercise_id: parseInt(key), .append("g")
working_time: parseFloat(data[key]) .attr("transform",
}; "translate(" + margin.left + "," + margin.top + ")");
});
// Scale the range of the data // Get the data
x.domain(data.map(function (d) { return d.index; })); data = Object.keys(data).map(function (key, index) {
y.domain([0, d3.max(data, function (d) { return d.working_time; })]); return {
index: index,
exercise_id: parseInt(key),
working_time: parseFloat(data[key])
};
});
// Add the X Axis // Scale the range of the data
svg.append("g") x.domain(data.map(function (d) {
.attr("class", "x axis") return d.index;
.attr("transform", "translate(0," + height + ")") }));
.call(xAxis); y.domain([0, d3.max(data, function (d) {
return d.working_time;
})]);
// Add the Y Axis // Add the X Axis
svg.append("g") svg.append("g")
.attr("class", "y axis") .attr("class", "x axis")
.style("fill", "steelblue") .attr("transform", "translate(0," + height + ")")
.call(yAxisLeft); .call(xAxis);
// Draw the bars // Add the Y Axis
svg.selectAll("bar") svg.append("g")
.data(data) .attr("class", "y axis")
.enter() .style("fill", "steelblue")
.append("rect") .call(yAxisLeft);
.attr("class", "value-bar")
.on("mousemove", function (d){
tooltip
.style("left", d3.event.pageX - 50 + "px")
.style("top", d3.event.pageY + 50 + "px")
.style("display", "inline-block")
.html("<%= I18n.t('activerecord.models.exercise.one') %> ID: " + d.exercise_id + "<br>" +
"<%= I18n.t('exercises.statistics.average_worktime') %>: " + d.working_time + "s");
})
.on("mouseout", function (){ tooltip.style("display", "none");})
.on("click", function (d) {
window.location.href = "/exercises/" + d.exercise_id + "/statistics";
})
.attr("x", function (d) { return x(d.index); })
.attr("width", x.bandwidth())
.attr("y", function (d) { return y(d.working_time); })
.attr("height", function (d) { return height - y(d.working_time); });
// Add the average working time path // Draw the bars
svg.append("path") svg.selectAll("bar")
.datum(data) .data(data)
.attr("class", "line average-working-time") .enter()
.attr("d", averageWorkingTime); .append("rect")
.attr("class", "value-bar")
.on("mousemove", function (d) {
tooltip
.style("left", d3.event.pageX - 50 + "px")
.style("top", d3.event.pageY + 50 + "px")
.style("display", "inline-block")
.html("<%= I18n.t('activerecord.models.exercise.one') %> ID: " + d.exercise_id + "<br>" +
"<%= I18n.t('exercises.statistics.average_worktime') %>: " + d.working_time + "s");
})
.on("mouseout", function () {
tooltip.style("display", "none");
})
.on("click", function (d) {
window.location.href = "/exercises/" + d.exercise_id + "/statistics";
})
.attr("x", function (d) {
return x(d.index);
})
.attr("width", x.bandwidth())
.attr("y", function (d) {
return y(d.working_time);
})
.attr("height", function (d) {
return height - y(d.working_time);
});
// Add the anomaly paths (min/max average exercise working time) // Add the average working time path
svg.append("path") svg.append("path")
.datum(data) .datum(data)
.attr("class", "line minimum-working-time") .attr("class", "line average-working-time")
.attr("d", minWorkingTime); .attr("d", averageWorkingTime);
svg.append("path")
.datum(data) // Add the anomaly paths (min/max average exercise working time)
.attr("class", "line maximum-working-time") svg.append("path")
.attr("d", maxWorkingTime); .datum(data)
.attr("class", "line minimum-working-time")
.attr("d", minWorkingTime);
svg.append("path")
.datum(data)
.attr("class", "line maximum-working-time")
.attr("d", maxWorkingTime);
} else if (exerciseList.isPresent()) {
var list = $("#sortable");
list.sortable();
list.disableSelection();
}
} }
}); });

View File

@ -15,4 +15,24 @@
.form-group .form-group
= f.label(t('activerecord.attributes.exercise_collections.exercises')) = f.label(t('activerecord.attributes.exercise_collections.exercises'))
= f.collection_select(:exercise_ids, exercises, :id, :title, {}, {class: 'form-control', multiple: true}) = f.collection_select(:exercise_ids, exercises, :id, :title, {}, {class: 'form-control', multiple: true})
.table-responsive#exercise-list
table.table
thead
tr
th
th = t('activerecord.attributes.exercise_collection_item.exercise')
th = t('activerecord.attributes.exercise.user')
th colspan=2 = t('shared.actions')
tbody#sortable
- @exercise_collection.items.order(:position).each do |item|
tr
td
span.fa.fa-bars
td = item.exercise.title
td = item.exercise.author
td = link_to(t('shared.show'), item.exercise)
td
a.remove-exercise href="#" = t('shared.destroy')
.actions = render('shared/submit_button', f: f, object: @exercise_collection) .actions = render('shared/submit_button', f: f, object: @exercise_collection)

View File

@ -131,6 +131,8 @@ de:
user: "Autor" user: "Autor"
exercise: "Aufgabe" exercise: "Aufgabe"
feedback_text: "Feedback Text" feedback_text: "Feedback Text"
exercise_collection_item:
exercise: "Aufgabe"
models: models:
code_harbor_link: code_harbor_link:
one: CodeHarbor-Link one: CodeHarbor-Link

View File

@ -131,6 +131,8 @@ en:
user: "Author" user: "Author"
exercise: "Exercise" exercise: "Exercise"
feedback_text: "Feedback Text" feedback_text: "Feedback Text"
exercise_collection_item:
exercise: "Exercise"
models: models:
code_harbor_link: code_harbor_link:
one: CodeHarbor Link one: CodeHarbor Link