No description provided
The showcase player uses a modified version of Processing.js in combination with jsweet to let students program their apps in Java code while still allowing for browser support.
Content created by students is scaled to fit the showcase frame while maintaining aspect ratio and cursor position. This is why some projects may appear blurry in fullscreen, or why some small details may not be visible on a small screen
<iframe width='500px' height='400px' src='https://nest.ktbyte.com/nest#97211' allowfullscreen></iframe>
color backgroundColor = #333333; // dark background color color dormantColor = #999966; // initial color of the map color highlightColor = #CBCBCB; // color for selected points color unhighlightColor = #66664C; // color for points that are not selected color waitingColor = #CBCBCB; // "please type a zip code" message color badColor = #FFFF66; // text color when nothing found ColorIntegrator faders[]; // border of where the map should be drawn on screen float mapX1, mapY1; float mapX2, mapY2; // column numbers in the data file static final int CODE = 0; static final int X = 1; static final int Y = 2; static final int NAME = 3; int totalCount; // total number of places Place[] places; int placeCount; // number of places loaded // min/max boundary of all points float minX, maxX; float minY, maxY; // typing and selection PFont font; String typedString = ""; char typedChars[] = new char[5]; int typedCount; int typedPartials[] = new int[6]; float messageX, messageY; int foundCount = -1; Place chosen; // smart updates int notUpdatedCount = 0; // zoom boolean zoomEnabled = true; Integrator zoomDepth = new Integrator(); Integrator zoomX1; Integrator zoomY1; Integrator zoomX2; Integrator zoomY2; float targetX1[] = new float[6]; float targetY1[] = new float[6]; float targetX2[] = new float[6]; float targetY2[] = new float[6]; // boundary of currently valid points at this typedCount float boundsX1, boundsY1; float boundsX2, boundsY2; public void setup() { console.log("DERP"); size(720, 453); mapX1 = 30; mapX2 = width - mapX1; mapY1 = 20; mapY2 = height - mapY1; //font = loadFont("ScalaSans-Regular-14.vlw"); //textFont(font); messageX = 40; messageY = height - 40; faders = new ColorIntegrator[6]; // When nothing is typed, all points are shown with a color called // "dormant," which is brighter than when not highlighted, but // not as bright as the highlight color for a selection. faders[0] = new ColorIntegrator(unhighlightColor, dormantColor); faders[0].attraction = 0.5f; faders[0].target(1); for (int i = 1; i < 6; i++) { faders[i] = new ColorIntegrator(unhighlightColor, highlightColor); faders[i].attraction = 0.5; faders[i].target(1); } readData(); zoomX1 = new Integrator(minX); zoomY1 = new Integrator(minY); zoomX2 = new Integrator(maxX); zoomY2 = new Integrator(maxY); targetX1[0] = minX; targetX2[0] = maxX; targetY1[0] = minY; targetY2[0] = maxY; rectMode(CENTER); ellipseMode(CENTER); // frameRate(15); } void readData() { String[] lines = loadStrings("https://static.ktbyte.com/images/zips2.txt"); parseInfo(lines[0]); places = new Place[lines.length-1]; // parse each of the rest of the lines for (String line : lines) { if (line.startsWith("#")) continue; places[placeCount] = parsePlace(line); placeCount++; } } void parseInfo(String line) { String infoString = line.substring(2); // remove the # String[] infoPieces = split(infoString, ','); totalCount = Integer.parseInt(infoPieces[0]); minX = Float.parseFloat(infoPieces[1]); maxX = Float.parseFloat(infoPieces[2]); minY = Float.parseFloat(infoPieces[3]); maxY = Float.parseFloat(infoPieces[4]); } Place parsePlace(String line) { String pieces[] = line.split("!"); int zip = Integer.parseInt(pieces[CODE]); float x = Float.parseFloat(pieces[X]); float y = Float.parseFloat(pieces[Y]); String name = pieces[NAME]; return new Place(zip, name, x, y); } public void draw() { background(backgroundColor); updateAnimation(); for (int i = 0; i < placeCount; i++) { places[i].draw(); } if (typedCount == 0) { fill(waitingColor); textAlign(LEFT); String message = "zipdecode by ben fry"; // if all places are loaded if (placeCount == totalCount) { if (focused) { message = "type the digits of a zip code"; } else { message = "click the map image to begin"; } } text(message, messageX, messageY); } else { if (foundCount > 0) { if (!zoomEnabled && (typedCount == 4)) { // re-draw the chosen ones, because they're often occluded // by the non-selected points for (int i = 0; i < placeCount; i++) { if (places[i].matchDepth == typedCount) { places[i].draw(); } } } if (chosen != null) { chosen.drawChosen(); } fill(highlightColor); textAlign(LEFT); text(typedString, messageX, messageY); } else { fill(badColor); text(typedString, messageX, messageY); } } // draw "zoom" text toggle textAlign(RIGHT); fill(zoomEnabled ? highlightColor : unhighlightColor); text("zoom", width - 40, height - 40); textAlign(LEFT); } void updateAnimation() { boolean updated = false; for (int i = 0; i < 6; i++) { updated |= faders[i].update(); } if (foundCount > 0) { zoomDepth.target(typedCount); } else { zoomDepth.target(typedCount-1); } updated |= zoomDepth.update(); updated |= zoomX1.update(); updated |= zoomY1.update(); updated |= zoomX2.update(); updated |= zoomY2.update(); // if the data is loaded, can optionally call noLoop() to save cpu if (placeCount == totalCount) { // if fully loaded if (!updated) { notUpdatedCount++; // after 20 frames of no updates, shut off the loop if (notUpdatedCount > 20) { frameRate(2); notUpdatedCount = 0; } } else { notUpdatedCount = 0; } } } float TX(float x) { if (zoomEnabled) { return map(x, zoomX1.value, zoomX2.value, mapX1, mapX2); } else { return map(x, minX, maxX, mapX1, mapX2); } } float TY(float y) { if (zoomEnabled) { return map(y, zoomY1.value, zoomY2.value, mapY2, mapY1); } else { return map(y, minY, maxY, mapY2, mapY1); } } void keyPressed() { if ((key == BACKSPACE) || (key == DELETE)) { if (typedCount > 0) { typedCount--; } updateTyped(); } else if ((key >= '0') && (key <= '9')) { if (typedCount != 5) { // only 5 digits if (foundCount != 0) { // don't allow to keep typing bad typedChars[typedCount++] = key; } } } updateTyped(); } void updateTyped() { typedString = new String(typedChars, 0, typedCount); // Un-highlight areas already typed past for (int i = 0; i < typedCount; i++) faders[i].target(0); // Highlight potential dots not yet selected by keys for (int i = typedCount; i < 6; i++) faders[i].target(1); typedPartials[typedCount] = Integer.parseInt(typedString); for (int j = typedCount-1; j > 0; --j) { typedPartials[j] = typedPartials[j + 1] / 10; } foundCount = 0; chosen = null; boundsX1 = maxX; boundsY1 = maxY; boundsX2 = minX; boundsY2 = minY; for (int i = 0; i < placeCount; i++) { // update boundaries of selection // and identify whether a particular place is chosen places[i].check(); } calcZoom(); frameRate(60); // loop(); // re-enable updates } void calcZoom() { if (foundCount != 0) { // given a set of min/max coords, expand in one direction so that the // selected area includes the range with the proper aspect ratio float spanX = (boundsX2 - boundsX1); float spanY = (boundsY2 - boundsY1); float midX = (boundsX1 + boundsX2) / 2; float midY = (boundsY1 + boundsY2) / 2; if ((spanX != 0) && (spanY != 0)) { float screenAspect = width / float(height); float spanAspect = spanX / spanY; if (spanAspect > screenAspect) { spanY = (spanX / width) * height; // wide } else { spanX = (spanY / height) * width; // tall } } else { // if span is zero // use the span from one level previous spanX = targetX2[typedCount-1] - targetX1[typedCount-1]; spanY = targetY2[typedCount-1] - targetY1[typedCount-1]; } targetX1[typedCount] = midX - spanX/2; targetX2[typedCount] = midX + spanX/2; targetY1[typedCount] = midY - spanY/2; targetY2[typedCount] = midY + spanY/2; } else if (typedCount != 0) { // nothing found at this level, so set the zoom identical to the previous targetX1[typedCount] = targetX1[typedCount-1]; targetY1[typedCount] = targetY1[typedCount-1]; targetX2[typedCount] = targetX2[typedCount-1]; targetY2[typedCount] = targetY2[typedCount-1]; } zoomX1.target(targetX1[typedCount]); zoomY1.target(targetY1[typedCount]); zoomX2.target(targetX2[typedCount]); zoomY2.target(targetY2[typedCount]); if (!zoomEnabled) { zoomX1.set(zoomX1.target); zoomY1.set(zoomY1.target); zoomX2.set(zoomX2.target); zoomY2.set(zoomY2.target); } } // Code from Visualizing Data, First Edition, Copyright 2008 Ben Fry. // Code from Visualizing Data, First Edition, Copyright 2008 Ben Fry. public class Integrator { static final float DAMPING = 0.5f; // formerly 0.9f static final float ATTRACTION = 0.2f; // formerly 0.1f float value = 0; float vel = 0; float accel = 0; float force = 0; float mass = 1; float damping; // = DAMPING; float attraction; // = ATTRACTION; boolean targeting; // = false; float target; // = 0; public Integrator() { this.value = 0; this.damping = DAMPING; this.attraction = ATTRACTION; } public Integrator(float value) { this.value = value; this.damping = DAMPING; this.attraction = ATTRACTION; } public Integrator(float value, float damping, float attraction) { this.value = value; this.damping = damping; this.attraction = attraction; } public void set(float v) { value = v; //targeting = false ? } public boolean update() { // default dtime = 1.0 if (targeting) { force += attraction * (target - value); } accel = force / mass; vel = (vel + accel) * damping; /* e.g. 0.90 */ value += vel; force = 0; // implicit reset return (vel > 0.0001f); } public void target(float t) { targeting = true; target = t; } public void noTarget() { targeting = false; } } /* public void attraction(float targetValue, float a) { force += attraction * (targetValue - value); } public void attract(float target, float a) { attraction(target, a); update(); } public void setDecay(float d) { kDecay = d; } public void decay() { force -= kDecay * value; } public void decay(float d) { force -= d * value; } public void setImpulse(float i) { kImpulse = i; } public void impulse() { //printf("kimpulse is %f\n", kImpulse); force += kImpulse; //decay(-kImpulse); // lazy } public void impulse(float i) { force += i; //decay(-i); // lazy } public void setDamping(float d) { kDamping = d; } public void noise(float amount) { force += (float) ((Math.random() * 2) - 1) * amount; } public void add(float v) { value += v; } public void add(Integrator integrator) { value += integrator.value; } */ /* void Integrator1f::updateRK() { // default dtime = 1.0 #define H 0.001 float f1 = force; float f2 = force + H*f1/2; float f3 = force + H*f2/2; float f4 = force + H*f3; velocity = velocity + (H/6)*(f1 + 2*f2 + 2*f3 + f4); } eval(x) is the force i think x should be time, so x is normally 1.0. if dtime were incorporated, that would probably work >> need correct function for force and dtime double f1 = fn.evalX(x); double f2 = fn.evalX(x + h*f1/2); double f3 = fn.evalX(x + h*f2/2); double f4 = fn.evalX(x + h*f3); out = x + (h/6)*(f1 + 2*f2 + 2*f3 + f4); */ /* public void step(double t, double x, double y, Function fn, double h, double out[]) { double f1 = fn.evalX(t, x, y); double g1 = fn.evalY(t, x, y); double f2 = fn.evalX(t + h/2, x + h*f1/2, y + h*g1/2); double g2 = fn.evalY(t + h/2, x + h*f1/2, y + h*g1/2); double f3 = fn.evalX(t + h/2, x + h*f2/2, y + h*g2/2); double g3 = fn.evalY(t + h/2, x + h*f2/2, y + h*g2/2); double f4 = fn.evalX(t + h, x + h*f3, y + h*g3); double g4 = fn.evalY(t + h, x + h*f3, y + h*g3); out[0] = x + (h/6)*(f1 + 2*f2 + 2*f3 + f4); out[1] = y + (h/6)*(g1 + 2*g2 + 2*g3 + g4); } */ //void Integrator1f::update(float dtime) { //velocity += force * dtime; // value += velocity*dtime + 0.5f*force*dtime*dtime; //force = 0; //} // Code from Visualizing Data, First Edition, Copyright 2008 Ben Fry. public class ColorIntegrator extends Integrator { float r0, g0, b0, a0; float rs, gs, bs, as; int colorValue; public ColorIntegrator(int color0, int color1) { int a1 = (color0 >> 24) & 0xff; int r1 = (color0 >> 16) & 0xff; int g1 = (color0 >> 8) & 0xff; int b1 = (color0 ) & 0xff; int a2 = (color1 >> 24) & 0xff; int r2 = (color1 >> 16) & 0xff; int g2 = (color1 >> 8) & 0xff; int b2 = (color1 ) & 0xff; r0 = (float)r1 / 255.0f; g0 = (float)g1 / 255.0f; b0 = (float)b1 / 255.0f; a0 = (float)a1 / 255.0f; rs = (r2 - r1) / 255.0f; gs = (g2 - g1) / 255.0f; bs = (b2 - b1) / 255.0f; as = (a2 - a1) / 255.0f; } public boolean update() { boolean updated = super.update(); if (updated) { colorValue = (((int) ((a0 + as*value) * 255f) << 24) | ((int) ((r0 + rs*value) * 255f) << 16) | ((int) ((g0 + gs*value) * 255f) << 8) | ((int) ((b0 + bs*value) * 255f))); } return updated; } public int get() { return colorValue; } } // Code from Visualizing Data, First Edition, Copyright 2008 Ben Fry. class Place { int code; String name; float x; float y; int partial[]; int matchDepth; public Place(int code, String name, float lon, float lat) { this.code = code; this.name = name; this.x = lon; this.y = lat; partial = new int[6]; partial[5] = code; partial[4] = partial[5] / 10; partial[3] = partial[4] / 10; partial[2] = partial[3] / 10; partial[1] = partial[2] / 10; } void check() { // default to zero levels of depth that match matchDepth = 0; if (typedCount != 0) { // Start from the greatest depth, and work backwards to see how many // items match. Want to figure out the maximum match, so better to // begin from the end. // The multiple levels of matching are important because more than one // depth level might be fading at a time. for (int j = typedCount; j > 0; --j) { if (typedPartials[j] == partial[j]) { matchDepth = j; break; // since starting at end, can stop now } } } //if (partial[typedCount] == partialCode) { if (matchDepth == typedCount) { foundCount++; if (typedCount == 5) { chosen = this; } if (x < boundsX1) boundsX1 = x; if (y < boundsY1) boundsY1 = y; if (x > boundsX2) boundsX2 = x; if (y > boundsY2) boundsY2 = y; } } void draw() { float xx = TX(x); float yy = TY(y); if ((xx < 0) || (yy < 0) || (xx >= width) || (yy >= height)) return; if ((zoomDepth.value < 2.8f) || !zoomEnabled) { // show simple dots //pixels[((int) yy) * width + ((int) xx)] = faders[matchDepth].cvalue; set((int)xx, (int)yy, faders[matchDepth].colorValue); } else { // show slightly more complicated dots noStroke(); fill(faders[matchDepth].colorValue); //rect(TX(nlon), TY(nlat), depther.value-1, depther.value-1); if (matchDepth == typedCount) { if (typedCount == 4) { // on the fourth digit, show nums for the 5th text(code % 10, TX(x), TY(y)); } else { // show a larger box for selections rect(xx, yy, zoomDepth.value, zoomDepth.value); } } else { // show a slightly smaller box for unselected rect(xx, yy, zoomDepth.value-1, zoomDepth.value-1); } } } void drawChosen() { noStroke(); fill(faders[matchDepth].colorValue); // the chosen point has to be a little larger when zooming int size = zoomEnabled ? 6 : 4; rect(TX(x), TY(y), size, size); // calculate position to draw the text, slightly offset from the main point float textX = TX(x); float textY = TY(y) - size - 4; // don't go off the top.. (e.g. 59544) if (textY < 20) { textY = TY(y) + 20; } // don't run off the bottom.. (e.g. 33242) if (textY > height - 5) { textY = TY(y) - 20; } String location = name + " " + nf(code, 5); if (zoomEnabled) { textAlign(CENTER); text(location, textX, textY); } else { float wide = textWidth(location); if (textX > width/3) { textX -= wide + 8; } else { textX += 8; } textAlign(LEFT); fill(highlightColor); text(location, textX, textY); } } }