/** * TemperatureViz — DataNet example * * Subscribes to a temperature channel on DataNet and draws a real-time * scrolling line graph. Each incoming message is expected to have the shape: * * { "value": 22.4 } * * Replace API_KEY and CHANNEL with your own values before running. * * Dependencies: * - DataNet library (see README.md for installation) * - Java-WebSocket jar in the library's code/ folder */ import datanet.*; // ── Configuration ───────────────────────────────────────────────────────── final String API_KEY = "ak_your_api_key_here"; final String CHANNEL = "project.your_project_id.temperature"; // ── Graph parameters ─────────────────────────────────────────────────────── final int HISTORY = 120; // number of readings shown at once final float TEMP_MIN = 0.0; // y-axis lower bound (°C) final float TEMP_MAX = 50.0; // y-axis upper bound (°C) final int GRAPH_PAD = 60; // pixels of padding around graph area final color LINE_COL = #FF6B35; final color DOT_COL = #FFD166; final color GRID_COL = #2A2A3A; final color BG_COL = #1A1A2E; final color TEXT_COL = #E0E0E0; // ── State ────────────────────────────────────────────────────────────────── DataNet dn; float[] readings = new float[HISTORY]; // circular buffer of temperature values int head = 0; // index of oldest reading int count = 0; // how many readings received so far float lastTemp = Float.NaN; String status = "Connecting…"; long lastMsgAt = 0; // ── Setup ────────────────────────────────────────────────────────────────── void setup() { size(900, 500); smooth(4); textFont(createFont("Monospaced", 12)); // Initialise the DataNet client. dn = new DataNet(this, API_KEY); dn.onConnect(() -> { status = "Connected — waiting for data"; // Subscribe to the temperature channel. dn.subscribe(CHANNEL, (ch, data, from, ts) -> { float val = data.getFloat("value"); pushReading(val); }); }); dn.onDisconnect(() -> { status = "Disconnected — reconnecting…"; }); dn.onError((e) -> { status = "Error: " + e.getMessage(); println("[DataNet] " + e.getMessage()); }); dn.connect(); } // ── Draw ─────────────────────────────────────────────────────────────────── void draw() { background(BG_COL); drawGrid(); drawGraph(); drawOverlay(); } // ── Graph helpers ────────────────────────────────────────────────────────── /** Push a new reading into the circular buffer. */ void pushReading(float val) { readings[head % HISTORY] = val; head++; if (count < HISTORY) count++; lastTemp = val; lastMsgAt = millis(); } /** Map a temperature value to a y pixel coordinate. */ float tempToY(float temp) { int graphH = height - 2 * GRAPH_PAD; return map(temp, TEMP_MIN, TEMP_MAX, height - GRAPH_PAD, GRAPH_PAD); } /** Map a reading index (0 = oldest) to an x pixel coordinate. */ float indexToX(int i) { int graphW = width - 2 * GRAPH_PAD; return GRAPH_PAD + map(i, 0, HISTORY - 1, 0, graphW); } /** Draw horizontal grid lines and y-axis labels. */ void drawGrid() { stroke(GRID_COL); strokeWeight(1); textAlign(RIGHT, CENTER); fill(TEXT_COL, 140); textSize(10); int steps = 5; for (int i = 0; i <= steps; i++) { float temp = map(i, 0, steps, TEMP_MIN, TEMP_MAX); float y = tempToY(temp); line(GRAPH_PAD - 5, y, width - GRAPH_PAD, y); text(nf(temp, 1, 1) + "°", GRAPH_PAD - 8, y); } } /** Draw the scrolling temperature line and animated dots. */ void drawGraph() { if (count == 0) return; // Draw filled area under the line. noStroke(); fill(LINE_COL, 40); beginShape(); vertex(indexToX(0), height - GRAPH_PAD); for (int i = 0; i < count; i++) { int bufIdx = (head - count + i + HISTORY) % HISTORY; float x = indexToX(i + (HISTORY - count)); float y = tempToY(readings[bufIdx]); vertex(x, y); } vertex(indexToX(HISTORY - 1), height - GRAPH_PAD); endShape(CLOSE); // Draw the line itself. stroke(LINE_COL); strokeWeight(2); noFill(); beginShape(); for (int i = 0; i < count; i++) { int bufIdx = (head - count + i + HISTORY) % HISTORY; float x = indexToX(i + (HISTORY - count)); float y = tempToY(readings[bufIdx]); curveVertex(x, y); } endShape(); // Animated pulsing dot at the latest reading. if (count > 0) { int latestBuf = (head - 1 + HISTORY) % HISTORY; float lx = indexToX(HISTORY - 1); float ly = tempToY(readings[latestBuf]); float pulse = 0.5 + 0.5 * sin(millis() * 0.006); noStroke(); fill(DOT_COL, 60 * pulse); ellipse(lx, ly, 20 + 12 * pulse, 20 + 12 * pulse); fill(DOT_COL); ellipse(lx, ly, 8, 8); } } /** Draw status text, channel name, current value, and time since last message. */ void drawOverlay() { // Title bar. fill(TEXT_COL); textAlign(LEFT, TOP); textSize(14); text("DataNet — Temperature Monitor", GRAPH_PAD, 12); textAlign(RIGHT, TOP); textSize(11); fill(TEXT_COL, 160); text(CHANNEL, width - GRAPH_PAD, 12); // Current value — large display. if (!Float.isNaN(lastTemp)) { textAlign(LEFT, TOP); textSize(36); fill(DOT_COL); text(nf(lastTemp, 1, 2) + " °C", GRAPH_PAD, 30); // Time since last message. long ago = (millis() - lastMsgAt) / 1000; textSize(11); fill(TEXT_COL, 120); text(ago + "s ago", GRAPH_PAD, 72); } // Status bar at bottom. textAlign(LEFT, BOTTOM); textSize(11); fill(TEXT_COL, 160); text(status, GRAPH_PAD, height - 8); // Message count. textAlign(RIGHT, BOTTOM); fill(TEXT_COL, 100); text("msgs: " + (head), width - GRAPH_PAD, height - 8); } // ── Keyboard shortcuts ───────────────────────────────────────────────────── void keyPressed() { if (key == 'd' || key == 'D') { dn.disconnect(); status = "Disconnected"; } if (key == 'r' || key == 'R') { dn.connect(); } }