Is there a way to invoke a Django view and pass it a parameter(s) from a D3.js onclick event handler associated with a heatmap? For my heatmap, I'm using the code found at the following:
https://www.d3-graph-gallery.com/graph/heatmap_style.html
Any help would be greatly appreciated. Thanks.
This is javascript code, which makes HTTP request when click on rectangle. Use jquery library (I am using here, in this example $.ajax({}) is jQuery's function). You can use Javascript's XMLHttpRequest class also, to make AJAX call. You should just pass correct URL of Django's view. Also, use GET request for testing purposes to avoid CSRF Features of Django.
<script>
// set the dimensions and margins of the graph
var margin = {top: 80, right: 25, bottom: 30, left: 40},
width = 450 - margin.left - margin.right,
height = 450 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
//Read the data
d3.csv("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/heatmap_data.csv", function(data) {
// Labels of row and columns -> unique identifier of the column called 'group' and 'variable'
var myGroups = d3.map(data, function(d){return d.group;}).keys()
var myVars = d3.map(data, function(d){return d.variable;}).keys()
// Build X scales and axis:
var x = d3.scaleBand()
.range([ 0, width ])
.domain(myGroups)
.padding(0.05);
svg.append("g")
.style("font-size", 15)
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickSize(0))
.select(".domain").remove()
// Build Y scales and axis:
var y = d3.scaleBand()
.range([ height, 0 ])
.domain(myVars)
.padding(0.05);
svg.append("g")
.style("font-size", 15)
.call(d3.axisLeft(y).tickSize(0))
.select(".domain").remove()
// Build color scale
var myColor = d3.scaleSequential()
.interpolator(d3.interpolateInferno)
.domain([1,100])
// create a tooltip
var tooltip = d3.select("#my_dataviz")
.append("div")
.style("opacity", 0)
.attr("class", "tooltip")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "2px")
.style("border-radius", "5px")
.style("padding", "5px")
// Three function that change the tooltip when user hover / move / leave a cell
var mouseover = function(d) {
tooltip
.style("opacity", 1)
d3.select(this)
.style("stroke", "black")
.style("opacity", 1)
}
var mousemove = function(d) {
tooltip
.html("The exact value of<br>this cell is: " + d.value)
.style("left", (d3.mouse(this)[0]+70) + "px")
.style("top", (d3.mouse(this)[1]) + "px")
}
var mouseleave = function(d) {
tooltip
.style("opacity", 0)
d3.select(this)
.style("stroke", "none")
.style("opacity", 0.8)
}
// add the squares
svg.selectAll()
.data(data, function(d) {return d.group+':'+d.variable;})
.enter()
.append("rect")
.attr("x", function(d) { return x(d.group) })
.attr("y", function(d) { return y(d.variable) })
.attr("rx", 4)
.attr("ry", 4)
.attr("width", x.bandwidth() )
.attr("height", y.bandwidth() )
.style("fill", function(d) { return myColor(d.value)} )
.style("stroke-width", 4)
.style("stroke", "none")
.style("opacity", 0.8)
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseleave", mouseleave)
.on("click", function(arg1, arg2, arg3) {
console.log('clicked !!!');
// make AJAX call now !
$.ajax({
type: 'GET',
url: '<DJANGO_VIEW_URL_HERE>',
data: {'key1': 'val1', 'key2': 'val2'},
success: function(data) {
console.log('server returned status code 200');
},
error: function() {
console.log('problem on server during processing');
}
});
})
})
// Add title to graph
svg.append("text")
.attr("x", 0)
.attr("y", -50)
.attr("text-anchor", "left")
.style("font-size", "22px")
.text("A d3.js heatmap");
// Add subtitle to graph
svg.append("text")
.attr("x", 0)
.attr("y", -20)
.attr("text-anchor", "left")
.style("font-size", "14px")
.style("fill", "grey")
.style("max-width", 400)
.text("A short description of the take-away message of this chart.");
</script>
Good luck.
Related
I have made a custom plugin to always show the tooltips since that was what I needed.
The only thing I would like to have now is a on-hover action.
const alwaysShowTooltipPlugin = {
id: 'alwaysShowTooltip',
onHover: function (evt, item, legend) {
console.log('hovered')
},
afterDraw(chart, args, options) {
const { ctx } = chart
ctx.save()
chart.data.datasets.forEach((dataset, i) => {
chart.getDatasetMeta(i).data.forEach((datapoint, index) => {
const { x, y } = datapoint.tooltipPosition();
const text = dataset.label
const textWidth = ctx.measureText(text).width
ctx.fillStyle = 'rgba(0, 0, 0, 0.8)'
ctx.fillRect(x - ((textWidth + 20) / 2), y - 40, textWidth + 20, 30)
// Triangle
ctx.beginPath()
ctx.moveTo(x, y )
ctx.lineTo(x - 5, y - 10)
ctx.lineTo(x + 5, y - 10)
ctx.fill()
ctx.restore()
// Text
ctx.font = '14px Arial'
ctx.fillStyle = 'white'
ctx.fillText(text, x - (textWidth / 2), y - 19)
ctx.restore()
})
})
},
}
How can I add a eventListener for a onClick so that I can use the data that was clicked on? The onHover I have currently does not work. It doesn't do anything at all.
I am using Nuxt by the way and Vue-ChartJS latest version.
I am working on an application with Django. There in this application, I am first using Django to create a database with points and extract a JSON file (It is called "markers.json"). Then, using this JSON file, I am creating markers on a map with Leaflet. When I finished entering all the points to the database they will be around 5000 thousand. So, I decided that it is a good idea to be able to search this markers with an input tag and a search button. I enter the "site_name" as input and when I click the "search" button the related marker should popup. However, always the same marker pops up and I don't know where I am doing wrong.
Could you please help me on that?
HTML PART
<input type="text" id="mast_find" name="mastName" placeholder="Search or masts...">
<button type="submit" id="mast_button">Search</button>
JAVASCRIPT PART
var streets = L.tileLayer( 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap',
subdomains: ['a', 'b', 'c']
}),
esri = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'
}),
topo = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
maxZoom: 17,
attribution: 'Map data: © OpenStreetMap, SRTM | Map style: © OpenTopoMap (CC-BY-SA)'
});
var map = L.map( 'map', {
center: [20.0, 5.0],
minZoom: 2,
zoom: 2,
layers: [streets, esri, topo]
})
var baseMaps = {
"Streets": streets,
"Esri": esri,
"Topo": topo
};
$('.leaflet-control-attribution').hide()
L.control.scale().addTo(map);
L.control.layers(baseMaps).addTo(map);
var myURL = jQuery( 'script[src$="leaf.js"]' ).attr( 'src' ).replace( 'leaf.js', '' )
var myIcon = L.icon({
iconUrl: myURL + '/images/pin24.png',
iconRetinaUrl: myURL + '/images/pin48.png',
iconSize: [29, 24],
iconAnchor: [9, 21],
popupAnchor: [0, -14]
})
for ( var i=0; i < markers.length; ++i )
{
var deneme = [];
var meleme = L.marker( [markers[i].fields.latitude, markers[i].fields.longitude], {icon: myIcon} )
.bindPopup( "<b>" + "Mast name: " + "</b>" + markers[i].fields.site_name + "<b>" + "<br>" + "A: " + "</b>" + markers[i].fields.a_measured_height_lt + "<br>" + "<b>" + "k: " + "</b>" + markers[i].fields.k_measured_height_lt )
.addTo( map );
deneme.push(meleme);
document.getElementById("mast_button").onclick = mastFunct;
function mastFunct(){
var data = document.getElementById("mast_find");
for (var i=0; i < markers.length; ++i ){
var markerID = markers[i].fields.site_name;
if (markerID = data.value){
deneme[i].openPopup()
}
}
};
if (markerID = data.value){
should be
if (markerID == data.value){
the only issue that i see is this with the if (markerID = data.value){.
But you can try this alternative:
instead your for-loop:
map.eachLayer(function(marker){
if(marker.options){
var markerID = marker.options.site_name;
if (markerID == data.value){
marker.openPopup();
}
}
});
and add this to your marker creation:
L.marker([51.493782, -0.089951],{icon: myIcon, site_name: 'test'}).addTo(map)
In my Django application I'm creating a csv file. When I try to use that file with a D3.js boilerplate it can't find the csv file?
I've tried moving where the csv file is to the root and into it's own folder but it doesn't do anything different
{% extends "lm_test/base.html" %}
{% block content %}
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.y.axis path {
display: none;
}
.y.axis line {
stroke: #fff;
stroke-opacity: .2;
shape-rendering: crispEdges;
}
.y.axis .zero line {
stroke: #000;
stroke-opacity: 1;
}
.title {
font: 300 78px Helvetica Neue;
fill: #666;
}
.birthyear,
.age {
text-anchor: middle;
}
.birthyear {
fill: #fff;
}
rect {
fill-opacity: .6;
fill: #e377c2;
}
rect:first-child {
fill: #1f77b4;
}
</style>
<body>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 40, bottom: 30, left: 20},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
barWidth = Math.floor(width / 19) - 1;
var x = d3.scale.linear()
.range([barWidth / 2, width - barWidth / 2]);
var y = d3.scale.linear()
.range([height, 0]);
var yAxis = d3.svg.axis()
.scale(y)
.orient("right")
.tickSize(-width)
.tickFormat(function(d) { return Math.round(d / 1e6) + "M"; });
// An SVG element with a bottom-right origin.
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 + ")");
// A sliding container to hold the bars by birthyear.
var birthyears = svg.append("g")
.attr("class", "birthyears");
// A label for the current year.
var title = svg.append("text")
.attr("class", "title")
.attr("dy", ".71em")
.text(2000);
d3.csv("../temp.csv", function(error, data) {
// Convert strings to numbers.
data.forEach(function(d) {
d.people = +d.people;
d.year = +d.year;
d.age = +d.age;
});
// Compute the extent of the data set in age and years.
var age1 = d3.max(data, function(d) { return d.age; }),
year0 = d3.min(data, function(d) { return d.year; }),
year1 = d3.max(data, function(d) { return d.year; }),
year = year1;
// Update the scale domains.
x.domain([year1 - age1, year1]);
y.domain([0, d3.max(data, function(d) { return d.people; })]);
// Produce a map from year and birthyear to [male, female].
data = d3.nest()
.key(function(d) { return d.year; })
.key(function(d) { return d.year - d.age; })
.rollup(function(v) { return v.map(function(d) { return d.people; }); })
.map(data);
// Add an axis to show the population values.
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width + ",0)")
.call(yAxis)
.selectAll("g")
.filter(function(value) { return !value; })
.classed("zero", true);
// Add labeled rects for each birthyear (so that no enter or exit is required).
var birthyear = birthyears.selectAll(".birthyear")
.data(d3.range(year0 - age1, year1 + 1, 5))
.enter().append("g")
.attr("class", "birthyear")
.attr("transform", function(birthyear) { return "translate(" + x(birthyear) + ",0)"; });
birthyear.selectAll("rect")
.data(function(birthyear) { return data[year][birthyear] || [0, 0]; })
.enter().append("rect")
.attr("x", -barWidth / 2)
.attr("width", barWidth)
.attr("y", y)
.attr("height", function(value) { return height - y(value); });
// Add labels to show birthyear.
birthyear.append("text")
.attr("y", height - 4)
.text(function(birthyear) { return birthyear; });
// Add labels to show age (separate; not animated).
svg.selectAll(".age")
.data(d3.range(0, age1 + 1, 5))
.enter().append("text")
.attr("class", "age")
.attr("x", function(age) { return x(year - age); })
.attr("y", height + 4)
.attr("dy", ".71em")
.text(function(age) { return age; });
// Allow the arrow keys to change the displayed year.
window.focus();
d3.select(window).on("keydown", function() {
switch (d3.event.keyCode) {
case 37: year = Math.max(year0, year - 10); break;
case 39: year = Math.min(year1, year + 10); break;
}
update();
});
function update() {
if (!(year in data)) return;
title.text(year);
birthyears.transition()
.duration(750)
.attr("transform", "translate(" + (x(year1) - x(year)) + ",0)");
birthyear.selectAll("rect")
.data(function(birthyear) { return data[year][birthyear] || [0, 0]; })
.transition()
.duration(750)
.attr("y", y)
.attr("height", function(value) { return height - y(value); });
}
});
</script>
{% endblock content %}
The view is:
def results(request):
disease = request.GET.get('disease_name')
year_from = int(request.GET.get('year_from'))
year_to = int(request.GET.get('year_to'))
if year_from < year_to:
years = range(year_from, year_to, +1)
else:
years = range(year_from, year_to, -1)
Entrez.email = "chrisgbeldam#gmail.com" #Required by NCBI
results_file = open('temp.csv', 'w') #Open csv file
result_writer = csv.writer(results_file, delimiter=',')
result_writer.writerow(['Year', 'Number Of Results'])
for year in years: #Checks the number of results for each year and then loops
handle = Entrez.esearch(
db="pubmed",
sort="relevance",
term=disease,
mindate=year,
maxdate=year,
retmode="xml",
)
results = Entrez.read(handle)
results_count = results['Count'] # Total number of results for the search
results_yearly = print(f"Number of papers in {year} is {results_count}")
handle.close() #Close E Search
result_writer.writerow([year,results_count]) # Writes out the results to csv file
results_file.close()
context = {
'disease': disease,
'year': year,
'results': results,
'results_count': results_count,
'results_file': results_file,
}
return render(request, 'lm_test/results.html', context)
I expect the outcome to be that D3 can see my temp.csv file but it refuses to and gives me a 'Failed to load resource: the server responded with a status of 404 (Not Found)' error and also a Uncaught TypeError: Cannot read property 'forEach' of undefined
at Object. (?csrfmiddlewaretoken=4I4wgoSynXQX6FyeSsDJWgNnjH6CgQyoZ0U7pvJV0D8vEBRynwYSalwrCZPjtw7v&disease_name=Cancer&year_from=2016&year_to=2000&submit=:117)
at Object.t (d3.v3.min.js:1)
at XMLHttpRequest.i (d3.v3.min.js:1)' error
Seem to work properly after closing and rerunning the virtual environment! Odd
I am new to Chart.Js and facing issues on click event on bar chart. I would like to get the repective x-axis lable on click function . But I am receiving error "barchart.getElementsAtEvent is not a function at HTMLCanvasElement.Graphinit.document.getElementById.onclick". I really do not understand what exactly is wrong in my code.
I am calling the below function to generate bar chart The min, item are data array for x-axis & yaxis.
cdn : "https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.js"
var barchart;
function Graphinit(min, item, labelstring, stepcount, scaleStepWidth, reporttype, min1, item1) {
$('#myChart').remove(); // this is my <canvas> element
$('#showgraph').append(' <canvas id="myChart" width="1200" height="400"></canvas>');
inputdata = min;
xaxislabel = item;
Chart.types.Bar.extend({
name: "BarAlt",
draw: function () {
Chart.types.Bar.prototype.draw.apply(this, arguments);
var ctx = this.chart.ctx;
ctx.save();
// text alignment and color
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
ctx.fillStyle = this.options.scaleFontColor;
// position
var x = this.scale.xScalePaddingLeft * 0.4;
var y = this.chart.height / 2;
// change origin
ctx.translate(x, y)
// rotate text
ctx.rotate(-90 * Math.PI / 180);
ctx.fillText(this.datasets[0].label, 0, 0);
ctx.restore();
}
});
var bridgeDataPoints = [];
if ((reporttype === "Lines picked per user") || (reporttype === "Picks per item") || (reporttype === "Lines Picked by item class") || (reporttype == "Lines picked by customer") || (reporttype == "Total Lines Picked") || reporttype == "Skipped Lines") {
for (var j = 0; j < min.length; j++) {
var dataPoint = new DataPoint1(item[j], min[j]);
bridgeDataPoints.push(dataPoint);
}
}
var data = {
labels: bridgeDataPoints,
datasets: [{
label: labelstring,
fillColor: "rgba(255, 255, 255, 1)",
strokeColor: "rgba(220,220,220,1)",
pointColor: "rgba(220,220,220,1)",
pointStrokeColor: "#fff",
pointHighlightFill: "#fff",
pointHighlightStroke: "rgba(220,220,220,1)",
data: inputdata
}]
};
var barchartoptions = {
//Boolean - If we show the scale above the chart data
scaleOverlay: false,
//Boolean - If we want to override with a hard coded scale
scaleOverride: true,
//** Required if scaleOverride is true **
//Number - The number of steps in a hard coded scale
scaleSteps: stepcount,
//Number - The value jump in the hard coded scale
scaleStepWidth: scaleStepWidth,
//Number - The scale starting value
scaleStartValue: 0,
scaleEndValue: stepcount,
//String - Colour of the scale line
scaleLineColor: "rgba(0,0,0,.1)",
//Number - Pixel width of the scale line
scaleLineWidth: 1,
//Boolean - Whether to show labels on the scale
scaleShowLabels: true,
//Interpolated JS string - can access value
scaleLabel: " <%=value%>",
//String - Scale label font declaration for the scale label
scaleFontFamily: "'Arial'",
//Number - Scale label font size in pixels
scaleFontSize: 12,
//String - Scale label font weight style
scaleFontStyle: "normal",
//String - Scale label font colour
scaleFontColor: "#666",
///Boolean - Whether grid lines are shown across the chart
scaleShowGridLines: true,
//String - Colour of the grid lines
scaleGridLineColor: "rgba(0,0,0,.05)",
//Number - Width of the grid lines
scaleGridLineWidth: 1,
//Boolean - If there is a stroke on each bar
barShowStroke: true,
//Number - Pixel width of the bar stroke
barStrokeWidth: 2,
//Number - Spacing between each of the X value sets
barValueSpacing: 10,
//Number - Spacing between data sets within X values
barDatasetSpacing: 3,
//Boolean - Whether to animate the chart
animation: true,
//Number - Number of animation steps
animationSteps: 60,
//String - Animation easing effect
animationEasing: "easeOutQuart",
//Function - Fires when the animation is complete
onAnimationComplete: function (animation) {
//alert('onAnimationComplete')
},
tooltipTemplate: "<%if (label){%><%=label.tooltip%><%}%>"
};
barchartoptions.datasetFill = false;
var ctx = $("#myChart").get(0).getContext('2d');
ctx.canvas.height = 400; // setting height of canvas
ctx.canvas.width = 1200; // setting width of canvas
//var config = Chart.defaults.bar;
barchart = new Chart(ctx).BarAlt(data, barchartoptions);
var length = inputdata.length; //min1.length;
var backgroundcolorset = [];
for (var i = 0; i < length; i++) {
var rgb = [];
for (var j = 0; j < 3; j++) {
rgb.push(Math.floor(Math.random() * 255));
}
backgroundcolorset.push('rgb(' + rgb.join(',') + ')');
}
for (var j = 0; j < length; j++) {
barchart.datasets[0].bars[j].fillColor = backgroundcolorset[j]; //bar 1
}
barchart.update();
console.log()
//console.log(barchart.getElementAtEvent(e));
document.getElementById("myChart").onclick = function (evt) {
// var activePoints =
console.log(barchart);
var activePoints = barchart.getElementsAtEvent(evt);
};
} // end Graphinit
Famous.Engine = famous.core.Engine;
Famous.Surface = famous.core.Surface;
Famous.RenderNode = famous.core.RenderNode;
Famous.ContainerSurface = famous.surfaces.ContainerSurface;
Famous.ScrollView = famous.views.Scrollview;
Famous.SequentialLayout = famous.views.SequentialLayout;
Famous.Transform = famous.core.Transform;
Famous.Transitionable = famous.transitions.Transitionable;
Famous.SnapTransition = famous.transitions.SnapTransition;
Famous.TransitionableTransform = famous.transitions.TransitionableTransform;
Famous.Modifier = famous.core.Modifier;
Famous.StateModifier = famous.modifiers.StateModifier;
Famous.Easing = famous.transitions.Easing;
Famous.EventHandler = famous.core.EventHandler;
var projectsList = document.getElementById('projects-list');
var mainContext = Famous.Engine.createContext(projectsList);
var sequentialLayout = new Famous.SequentialLayout({
direction: 0
});
Famous.Transitionable.registerMethod('snap', Famous.SnapTransition);
var snap = { method: 'snap', period: 600, dampingRatio: 0.6 }
var surfaces = [];
for (var i = 0; i < 20; i++) {
var surface = new Famous.Surface({
content: "Surface: " + (i + 1),
size: [300, $(window).height()],
properties: {
backgroundColor: "hsl(" + (i * 360 / 40) + ", 100%, 50%)",
textAlign: "center"
}
});
surface.open = false;
surface.state = new Famous.Modifier({
size: [undefined, undefined]
});
surface.trans = new Famous.Transitionable(300);
surface.state.sizeFrom(function(){
return [this.trans.get(), undefined];
}.bind(surface));
surface.node = new Famous.RenderNode();
surface.node.add(surface.state).add(surface);
surface.pipe(sequentialLayout);
surface.on('click',function(e){
if (this.open) {
this.trans.halt();
this.trans.set(300, snap);
} else {
this.trans.halt();
this.trans.set($(window).width(), snap);
}
this.open = !this.open;
}.bind(surface));
surfaces.push(surface.node);
sequentialLayout.sequenceFrom(surfaces);
}
mainContext.add(sequentialLayout);
The surfaces translate their X positions fine, but do not change their width from 300px. I would also like the surface that's clicked on to change its width to fill the window's width if its in a closed state. I would like the surface to go back to its initial width of 300px if it is clicked in an open state. How do I accomplish this so the width animates rather than jumps to the specified sizes?
Remove the size of the Surface
var surface = new Famous.Surface({
content: "Surface: " + (i + 1),
properties: {
backgroundColor: "hsl(" + (i * 360 / 40) + ", 100%, 50%)",
textAlign: "center"
}
});
The key is to not specify a static width or height when creating the surfaces:
var surface = new Famous.Surface({
content: "Surface: " + (i + 1),
size: [undefined, undefined], /* removed the original [300, $(window).height()] */
properties: {
backgroundColor: "hsl(" + (i * 360 / 40) + ", 100%, 50%)",
textAlign: "center"
}
});