Trouble reading data from JSON file and displaying it on Canvas - javascript

Background
(New to JS and 1st time using canvas)
I am trying to read a JSON file from https://analytics.usa.gov/ and display it using a canvas. I want just the first 8 records to be populated in a drop down.
I was successful in getting a dump of the file locally on my machine. Code is here (https://jsfiddle.net/uh8jamdo/)
What I was trying to do is :
1) Page Title from the data should be populated in Drop Down
2) When the User selects one and clicks submit. I want to display the Page Title, URL and active users of that particular Page Title.
Below is my attempt. Can some one please guide me. I have written code to display canvas, text and update the data.
window.onload = function() {
var button = document.getElementById("previewButton");
button.onclick = previewHandler;
}
function previewHandler() {
var canvas = document.getElementById("analytics");
var context = canvas.getContext("2d");
drawText(canvas, context);
}
// draws all the text, including the Analytics
function drawText(canvas, context) {
context.font = "50px serif";
context.fillStyle = "black";
context.font = "bold 1em sans-serif";
context.textAlign = "left";
context.fillText("No of People online on Govt Websites", 20, 40);
// draw the analytics!
selectObj = document.getElementById("site");
index = selectObj.selectedIndex;
var site = selectObj[index].value;
context.font = "italic 1.2em serif";
context.fillText(site, 30, 100);
}
function updateAnalytics(site) {
var siteSelection = document.getElementById("site");
// add all data to the site dropdown
for (var i = 0; i < site.length; i++) {
site = site[i];
// create option
var option = document.createElement("option");
option.text = site.text;
// strip any quotes out of the site so they don't mess up our option
option.value = site.text.replace("\"", "'");
// add option to select
siteSelection.options.add(option);
}
// make sure the top tweet is selected
siteSelection.selectedIndex = 0;
}
canvas {
border: 1px solid black;
}
<canvas width="600" height="200" id="analytics">
<p>You need canvas to see the Analytics Data</p>
<p>This example requires a browser that supports the HTML5 Canvas feature.</p>
</canvas>
<p>
<label for="site">Pick a Site</label>
<select id="site"></select>
</p>
<p>
<input type="button" id="previewButton" value="Preview">
</p>
</form>
</br>
<script src="https://analytics.usa.gov/data/live/top-pages-realtime.json">
</script>

You basically have it, but there are enough strange things that I figure I can modify your code and you can study what I've done. A few issues I spotted:
Instead of hardcoding a script into the html document, we can fetch the contents of URL's through XMLHttpRequest. You are going to run into some timing issues.
There is no real need to use form and submit these days. Modern web design will usually maintain a stateful object on the client, and will communicate with various back ends using things like XMLHttpRequest, above. By doing this, we don't have to redraw the page every time we get a response back from the server. When the user clicks submit, you can catch that event, but there is a protocol you need to follow (return false from the handler) to prevent the page from reloading and blowing away the user's selection. The user will not get to see your canvas, because you'll be reloading a fresh canvas every time. They also will not see their selection because you'll be resetting it after every submit.
We don't need to strip quotes when setting the javascript value. First, because these things will usually get escaped for you, but second, because it's actually easier in your case to just store the index of the selection. You really don't need value at all. It may cause issues if you use this modified value as a key.
Minimize the number of global variables. Javascript has really good support for closure, which is a powerful programming construct that allows us to limit the visibility of variables, among other things. This will make it a lot easier to test your code in the debugger because everything will be grouped in one place. (More of a style than an actual issue).
Make sure to clear out your canvas if you plan to reuse it. Successive calls to fillText will clobber the previous text.
There are plenty of other things we could explore, but hopefully this will get you started.
Also, it's great that you are doing all of this without resorting to external libraries, but you should also check them out -- javascript can do some pretty exciting things. I recommend D3.js
Finally, here is the revised javascript, which I believe does the things you've specified, and which takes into account the advice I've given:
(you can play with the jsfiddle, here:
https://jsfiddle.net/dancingplatypus/L92fq305/6/)
var site_analytics = {
run: function () {
var ANALYTICS_SITE = 'https://analytics.usa.gov/data/live/top-pages-realtime.json';
var elem_select = document.getElementById("site");
var elem_title = document.getElementById("site_title");
var elem_preview = document.getElementById("previewButton");
var site_data = null;
elem_preview.onclick = render_selection;
elem_select.onchange = render_selection;
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function () {
console.log('heya');
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
site_data = JSON.parse(xmlhttp.responseText);
sync_selector();
}
}
xmlhttp.open("GET", ANALYTICS_SITE, true);
xmlhttp.send();
function sync_selector() {
console.log('boyo');
elem_select.innerHtml = null;
if (site_data) {
for (var loop = 0; loop < Math.min(8, site_data.data.length); loop++) {
var item = site_data.data[loop];
var option = document.createElement('option');
option.text = item.page_title;
option.value = loop;
elem_select.add(option);
}
}
selected_index = (site_data.data.length > 0) ? 0 : null;
render_selection();
}
function render_selection() {
var index = elem_select.selectedIndex;
var item = site_data ? site_data.data[index] : null;
elem_title.innerText = item ? item.page_title : 'n/a';
var canvas = document.getElementById("analytics");
var context = canvas.getContext("2d");
context.clearRect(0, 0, canvas.width, canvas.height);
drawText(item, canvas, context);
};
// draws all the text, including the Analytics
function drawText(item, canvas, context) {
context.font = "50px serif";
context.fillStyle = "black";
context.font = "bold 1em sans-serif";
context.textAlign = "left";
context.fillText("No of People online on Govt Websites", 20, 40);
// draw the analytics!
context.font = "italic 1.2em serif";
context.fillText(item ? item.active_visitors : '', 30, 100);
}
}
};
window.onload = function () {
site_analytics.run();
};

Related

Why does redrawing my image not work properly?

You helped me get the draw function work properly. Here's the complete program, which works perfectly until the "draw" button is clicked. Instead of drawing a new image, the entire window vanishes. Any help greatly appreciated!
This code is formatted as instructed, 4 spaces per tab. I don't know any more details to add.
<!DOCTYPE html>
<html>
<head>
<title>Digital Holograms</title>
<style>
div {
float: left;
height: 580px;
width: 500px;;
padding: 0 10px;
}
#column1 {
background-color: #FFFFFF;
}
#column2 {
background-color: #FFFFFF;
width: 500px
}
</style>
</head>
<body>
<div id="column1">
<canvas id="my-canvas" width="500" height="500"></canvas>
<script>
let selFun = 0 // selected function to draw
// user may change this by selection box
let canvas = document.querySelector('#my-canvas')
let context = canvas.getContext('2d')
context.fillStyle = 'black'
function draw(n) {
let contourInterval = 1500
let contourWidth = 500
let fsize = 1000
let x0 = fsize / 2
let y0 = fsize / 2
for (j = 0, y = -y0; j < fsize; j++, y++) {
for (i = 0, x = -x0; i < fsize; i++, x++) {
let fun = 0
switch(n) {
case 0:
fun = (x*x + y*y)
break;
case 1:
fun = (x*x - y*y)
break;
default:
fun = (x*x + y*y)
}
if (Math.abs(fun % contourInterval) < contourWidth) {
context.fillRect(x+x0/2, y+y0/2, 1, 1)
}
}
}
document.write('<h4 style="text-align: center;">Hologram of z = x&#x00B2 + y&#x00B2</h4>');
}
draw(selFun)
function getSelection() {
let selFun = document.getElementById("selectFunction").value
//document.write(selFun)
}
</script>
<h3>To view other holograms:</br>Select a function, then press draw</h3>
<select id="selectFunction" onchange="getSelection()">
<option value=0>z = x&#x00B2 + y&#x00B2</option>
<option value=1>z = x&#x00B2 - y&#x00B2</option>
</select>
<button type = "button";
onclick = draw(selFun)>Draw</button>
</div>
</body>
</html>
There a several glitches that are preventing this code working as intended.
1) The loss of content on button press
This is a result of an incorrect use of document.write() (which can only be effectively used by inline scripts to inset text as the page is loading. Running it on a loaded page causes document.open() to be executed, clearing the existing document. See: https://developer.mozilla.org/en-US/docs/Web/API/Document/write
The correct way to update information on the page is to change the innerText or innerHTML of an existing element, or insert a new element with the required text.
In my snippet I added the h4 tag, with an id of updatedTitle, to the html (initially with no content, which will be added by the script):
<h4 id="updatedTitle" style="text-align: center;"></h4>
The innerText of the tag is added by the last line of the draw() function:
document.getElementById("updatedTitle").innerText = `Hologram of ${equation}`;
Note, the template literal ${equation}, this was used to allow the equation to be updated when a new option is drawn, by fetching the markup for the equation from the element (shown later).
2) Drop Down changes not registering
After correcting point 1), there was still a problem: pressing the draw button failed to render a new graph. After some investigation with console.log()s at various parts of the execution cycle I determined that the onchange of the dropdown element was not being received by the javascript.
The reason for this turned out to be a case of very bad luck - the choice of the function name intended to handle the change: getSelection. It turns out that getSelection is a built-in method of the Window object and seemingly cannot be used as a user function name.
Changing the function name to readSelection() cured this problem;
(https://developer.mozilla.org/en-US/docs/Web/API/Window/getSelection)
3) Button onclick event argument not accessible
The button's onlick event calls the draw() function, and attempts to send selFun as an argument. As written, selFun is not accessible from the element.
Instead, the onlick was changed to simply call the draw() function without any argument.
selFun is declared, and assigned with 0, in the global scope of the script and so is accessible from function in the script. Thus the argument n used in the switch block of the draw() function can be replaced directly with selFun (and n also deleted from the function parameter):
4) modification to readSelection()
The original values of the option list items were 0 and 1, entered as (unquotted) numbers in the markup. Although this is allowed, the 0 and 1 are converted to strings when the value attribute is read regardless of whether they were quotted or not (much like the contents of a text input field). Javascript is good at coercing numbers from digit characters but I was getting a bug where selFun was not being read properly for the case statement. Formally setting selFun to an integer value cured this:
selFun = parseInt(document.getElementById("selectFunction").value);
Since readSelection is invoked each time the drop down changes, it is also the place to keep track of which equation should be used in the label on next draw. Hence the following line was included in the function:
equation = document.getElementById("selectFunction").children[selFun].innerText;
with equation being declared (and set to the dropdown initial state) in the global scope of the script, to be read and used to form a label, as discussed in 1) above.
5) Draw only working for first change of equation
Once the above changes were made, changing the dropdown and pressing the button resulted in the new equation being rendered. However, subsequent changes and attempts to draw did not appear to do much.
This was because the second equation was being drawn on top of the first and, once both equations were drawm further drawing didn's show any change.
The solution was to include a standard clear step at the start of the draw() function:
context.clearRect(0, 0, canvas.width, canvas.height);
6) Element accessibility
The entire script was moved to the foot of the html to allow all elements to load before running the script, this prevented some errors where the js was trying to access elements that did not yet exist.
The programme seems to work as intended now. I don't understand anything about the equation plots but they are most impressive and, should you add more to it in future, perhaps you will comment back here so I can have a look!
Working snippet:
let selFun = 0
let equation = document.getElementById("selectFunction").children[selFun].innerHTML; // to be used to update graph label;
let canvas = document.querySelector('#my-canvas');
let context = canvas.getContext('2d');
context.fillStyle = 'black';
function draw() {
// clear canvas'
context.clearRect(0, 0, canvas.width, canvas.height);
let contourInterval = 1500;
let contourWidth = 500;
let fsize = 1000;
let x0 = fsize / 2
let y0 = fsize / 2
for (j = 0, y = -y0; j < fsize; j++, y++) {
for (i = 0, x = -x0; i < fsize; i++, x++) {
let fun = 0
switch(selFun) {
case 0:
fun = (x*x + y*y);
break;
case 1:
fun = (x*x - y*y);
break;
default:
fun = (x*x + y*y);
} // end switch
if (Math.abs(fun % contourInterval) < contourWidth) {
context.fillRect(x+x0/2, y+y0/2, 1, 1)
} // end if;
} // next i;
} // next j;
const titleH4 = document.getElementById("updatedTitle");
document.getElementById("updatedTitle").innerText = `Hologram of ${equation}`;
} // end draw;
draw();
function readSelection() {
selFun = parseInt(document.getElementById("selectFunction").value);
equation = document.getElementById("selectFunction").children[selFun].innerText;
} // end readSelection function;
div {
float: left;
height: 580px;
width: 500px;;
padding: 0 10px;
}
#column1 {
background-color: #FFFFFF;
}
#column2 {
background-color: #FFFFFF;
width: 500px
}
select {
width: 200px;
height: 1.5em;
font-size: 2em;
border-radius: 5px;
background: cyan;
text-align: center;
}
button {
width: 100px;
height: 1.5em;
font-size: 2em;
}
<div id="column1">
<canvas id="my-canvas" width="500" height="500"></canvas>
<h3>To view other holograms:</br>Select a function, then press draw</h3>
<select id="selectFunction" onchange="readSelection()">
<option value="0">z = x&#x00B2 + y&#x00B2</option>
<option value="1">z = x&#x00B2 - y&#x00B2</option>
</select>
<button type = "button" onclick="draw()">Draw</button>
<h4 id="updatedTitle" style="text-align: center;"></h4>
</div>
edit
Regarding the appearance of the dropdown and button - they can be styled much like any other html element. I've added some arbitrary changes to their apperance by adding rules for select and button elements to the css. The elements can be given a class name if more specific styling is needed without affecting other elements of the same kind.
I don't know what your code is trying to do but when the button is pressed you are updating the source completely.
Here is that bad code :)
document.write('<h4 style="text-align: center;">Hologram of z = x&#x00B2 + y&#x00B2</h4>');
Add it inside a div instead of writing it to the whole document.
document.getElementById("example").innerHTML = 'your codes';

FabricJS canvas stops working after new elements are added in the DOM

Problem: Hi, I have 3 canvas elements in my single page. All of them are basically used for signature purposes so I've used fabric JS so user could have a pen like functionality and draw their signatures. Now, upon selection of some other options present at the top of the page new elements are added dynamically and that makes all of my canvas signature element stuck and I can't draw anything on them.
What I Tried: I've tried re-initialising canvas when the DOM is changed(or new divs are added) but it still gets stuck. It works fine when I first load the page and no new element is added.
Reference Link: This dude was having the same issue what I am facing but, the proposed solutions didn't works in my case.
$(document).ready(function() {
canvas_init1();
});
function canvas_init1() {
//alert('canvas initialized...');
var canvas = new fabric.Canvas('c1');
canvas.isDrawingMode = true;
canvas.freeDrawingBrush.width = 3;
console.log(canvas);
var pracCanvas = new fabric.Canvas('prac-canvas');
pracCanvas.isDrawingMode = true;
pracCanvas.freeDrawingBrush.width = 3;
var patCanvas = new fabric.Canvas('pat-canvas');
patCanvas.isDrawingMode = true;
patCanvas.freeDrawingBrush.width = 3;
}
I also tried adding calcOffset() and renderAll() but, it didn't made any difference. Thanks for your help in advance.
I solved it by adding
canvas.on('after:render', function(){ this.calcOffset(); });
on each canvas. The complete code becomes the following:
var canvas = new fabric.Canvas('c1');
canvas.on('after:render', function(){ this.calcOffset(); });
canvas.isDrawingMode = true;
canvas.freeDrawingBrush.width = 1;
console.log(canvas);
var pracCanvas = new fabric.Canvas('prac-canvas');
pracCanvas.on('after:render', function(){ this.calcOffset(); });
pracCanvas.isDrawingMode = true;
pracCanvas.freeDrawingBrush.width = 1;
var patCanvas = new fabric.Canvas('pat-canvas');
patCanvas.on('after:render', function(){ this.calcOffset(); });
patCanvas.isDrawingMode = true;
patCanvas.freeDrawingBrush.width = 1;

How can I make a tampermonkey / embedded js userscript wait before it runs?

If anyone has this similar problem the solution for me was to correctly format set timeout, before I was doing
setTimeout(function() {//program here}, 5000) but that did not work, naming the function and then calling it properly from settimeout did not cause any issues. Also, make sure the bottom of your file doesn't have some random brackets--mine did and causes problems.
I am currently trying to improve a mod menu / cheat for an online virtual school program. It works fine and builds its UI elements by appending to a few different areas on the virtual school page, however, whenever it runs it fails multiple times before succeeding because it is trying to append to objects that are not loaded yet.
The .js file is injected by this tool although I don't really know what it is doing. I can't see any options like "delay before running" etc.
How can I wait for either a certain amount of time or check if the site is loaded without interrupting it's loading process? All the solutions I have tried so far (settimeout, event listener for loaded, while loop for site not being loaded) don't work because they stop the site from loading while they check.
This is the start of the JS file (it will not run here)
(function() {
'use strict';
//==BEGIN UI BUILDING==
window.configElements = []; //Config infomation
function init() {
//Builder Functionsfunction appender(b, p){ //appends things, if p is null defaults to tweaksmenu
if (p == undefined) {
document.getElementById("tweaksmenu").appendChild(b)
} else document.getElementById(p).appendChild(b)
}
function RenderPane(name, id, width, height, margintop, marginleft) { //renders panes
window.pane = document.createElement("div")
window.pane.style.width = width
window.pane.style.height = height
window.pane.style.position = "absolute"
window.pane.style.marginTop = margintop
window.pane.style.marginLeft = marginleft
window.pane.style.border = "1px solid rgb(95, 95, 95)"
window.pane.style.borderRadius = "3px"
window.pane.style.backgroundColor = "rgb(39, 39, 39)"
window.pane.style.overflow = "hidden"
window.pane.style.color = "rgb(249, 166, 25)"
window.pane.style.textAlign = "center"
window.pane.style.overflowY = "scroll"
window.pane.id = id
window.panetitle = document.createElement("h1")
window.panetitle.innerText = " " + name //shitty centering fix
window.pane.appendChild(window.panetitle)
window.pane.appendChild(document.createElement("hr"))
document.getElementById("overlay").appendChild(window.pane)
}
function BuildMenuEntry (name, info, id, configpane, override) { //Creates tickbox element with info and optional (new) config pane (see guesspractice). Override autoappends to tweaksmenu and does not return
window.entry = document.createElement("div")
window.tickbox = document.createElement("input")
window.tickbox.type = "checkbox"
window.tickbox.id = id
window.configElements.push(id)
window.entry.appendChild(window.tickbox)
window.window.label = document.createElement("label")
window.label.innerText = " " + name //spacing fix
window.entry.appendChild(window.label)
if (configpane != undefined) { //If any configpane was passed through try and create a button to attach it.
window.configbutton = document.createElement("button")
window.configbutton.innerText = "Configure"
window.configbutton.style.marginLeft = "7px"
window.configbutton.style.border = "1px solid #5f5f5f"
window.configbutton.style.boxShadow = "inset 0 0 5px rgba(0, 0, 0, 0.6)"
window.configbutton.style.backgroundColor = "rgb(39, 39, 39)"
window.configbutton.style.color = "#f9a619"
window.configbutton.style.borderRadius = "3px"
window.configbutton.onclick = function () {
if (document.getElementById(configpane).style.visibility == "unset") { //visiblitly handler for configpane button
document.getElementById(configpane).style.visibility = "hidden"
} else {
document.getElementById(configpane).style.visibility = "unset"
}
}
window.entry.appendChild(window.configbutton)
}
window.desc = document.createElement("p")
window.desc.innerHTML = info;
window.entry.appendChild(window.desc)
if (override == 1) { //override
window.document.getElementById("tweaksmenu").appendChild(window.entry);
}
else return window.entry;
}

How to recolor an existing canvas element using Javascript, JQuery and/or PHP

Fangraph recently switched from displaying heatmaps in a table, to displaying them in a canvas element. When the heatmap was in table format, I was recoloring it by iterating through each td, like this:
$('.coloredElement').each(function(){
var number = +($(this).text());
if(number > 0.099) {
$(this).parent("td").css({"background-color": "niceColor", "color":"awesomeColor"});
} else if (number > 0.089) {
$(this).parent("td").css({"background-color": "anotherColor", "color":"yetAnotherColor"});
} else if (...etc) {
...etc
}
});
This is an example of the new heatmap format in which a canvas element is apparently colored with a script:
var canvas = document.getElementById("gridCanvas");
var ctx = canvas.getContext("2d");
ctx.fillStyle="#fff"; ctx.fillRect(0,0,600,675);
ctx.fillStyle="#000"; ctx.font = "15px Arial";
ctx.fillText("Mike Trout AVG/P vs All Pitchers", 5, 20);
ctx.fillText("Season: 2017-04-03 to 2017-10-01", 5, 40);
ctx.fillText("Count: All Counts | Total Pitches: 2597 | View: Catcher", 5, 60);
ctx.fillStyle="#0000FF";
etc...
Is it possible to to recolor ctx.fillStyle based on ctx.fillText, using plain javascript, or jQuery? ctx.fillText for the relevant parts of the canvas is in the format ctx.fillText(".062",270,220), with the string ".062" (representing the player's average in that part of the strike zone), being the part I would base the color on.
Alternatively is it possible to address the issue with PHP? It ocurred to me that if I retrieve each script along with its corresponding heatmap, it might be possible to replace each instance of (for example) blue with something else, programmatically. To this end I used simple html dom, parsing out the script and setting it's inner text to a variable (canvas_script). I've tried using both str_replace and preg_replace to change the colors in the string contained by the variable, but I must be making a mistake, because when I echo out the resulting canvas_script, the original colors remain unchanged. This was what I tried using PHP:
include_once('../SHD/simple_html_dom.php');
$url = "pathToHeatmap";
$context = stream_context_create();
stream_context_set_params($context, array('user_agent' => 'my_identity_location_and_purpose'));
$html = file_get_html($url, 0, $context);
if($html) {
foreach($html->find('#gridCanvas') as $heat) {
echo '<div style="text-align: center;">' . $heat . '</div>'; // center map on page
}
$s = 1; // increment this variable to loop through script elements in DOM
foreach($html->find('#content > script') as $script) {
if($s === 9) {
$canvas_script_raw = $script->innertext;
$canvas_script = str_replace("#0000ff", "#4F261B", $canvas_script_raw); // tried to replace blue with something else
$canvas_script = preg_replace('/#0000ff/', '#4F261B', $canvas_script_raw); // tried a different method
}
$s++;
}
} else {
// if the request fails print error message
}
I also looked into using Gradientmaps.js, which seems to recolor an image based on light and dark values, after converting it to greyscale. However as the average per pitch numbers (ctx.fillText) go up, the value of their corresponding color doesn't necesarily go up, so I can't make a satisfactory gradient.

Javascript/Jquery flow text into divs

How do I flow a text stream from one div to the next? Each div has a fixed height and width and I need the data to be passed to the first, then if that is filled overflow to another etc. (The second etc. divs need to be dynamically created). Eventually the divs will be contenteditable, this is the page technique for a simple WYSIWYG style editor.
Please help...
JS Fiddle (note zoom in use on body)(I need to reflow on keyup/down, make pages and remove as necessary and be able to add in the middle)
function loadgo(){
run();
var h = document.getElementById('page'+(document.getElementById("pagecount").value-1)).offsetHeight;
document.getElementById("loadgo").value = h+1;
if (h > 1135){
var pagecount = document.getElementById("pagecount").value;
//$('body').append("<div class=page style='color:#F00' id='page"+pagecount+"' contenteditable>"+this.id+"</div>");
var objTo = document.body;
var divtest = document.createElement("div");
divtest.style.color = '#F00';
divtest.id = "page"+pagecount;
divtest.className = 'page';
divtest.contentEditable = "true";
divtest.innerHTML = "new div";
objTo.appendChild(divtest);
document.getElementById("page"+pagecount).focus();
document.getElementById("pagecount").value++;
zoomin();zoomout();
run();
}
}
function run(){
setTimeout(function() {
loadgo();
}, 500);
}
loadgo();
What you're looking for is called CSS Regions. This allows your text to flow through various containers placed on your site.
You can read about it on Adobe's site, and Apple has a nice WWDC video explaining how to implement it (starts at 8:40). Also check out the Editor's Draft.

Categories