Published
Edited
Dec 10, 2019
Insert cell
md`# draw line for apple`
Insert cell
c = {
return html`<div id="wrapper" />`;
}
// * 1. create an Dom and render it on display;
// * 2. output c as node for selection
Insert cell
c
Insert cell
setups = md `## helper functions on data processing`
Insert cell
// yAccessor = d => d.closePrice
yAccessor = d => d.Close
Insert cell

dateParser = d3.timeParse("%_d-%b-%Y")
// dateParser = d3.timeParse("%Y-%m-%d")
// https://github.com/d3/d3-time-format/blob/v2.2.2/README.md#locale_format
// Wed Sep 18 2013 08:00:00 GMT+0800 (China Standard Time)
Insert cell
// xAccessor = d => dateParser(d.tradeDate)
xAccessor = d => dateParser(d.Date)
Insert cell
md`## view frame setups`
Insert cell
dimensions = {
let dimensions = {
width: window.innerWidth * 0.9,
height: 400,
margin: {
top: 15,
right: 15,
bottom: 40,
left: 60,
},
}
dimensions.boundedWidth = dimensions.width - dimensions.margin.left - dimensions.margin.right
dimensions.boundedHeight = dimensions.height - dimensions.margin.top - dimensions.margin.bottom
return dimensions;
}
Insert cell
wrapper = { // create a svg wrapper for everything within g #wrapper
return d3.select("#wrapper")
.append("svg")
.attr("width", dimensions.width)
.attr("height", dimensions.height)
}
Insert cell
bounds = {
const bounds = wrapper.append("g") // create a g insider wrapper to be bounds
.style("transform", `translate(${dimensions.margin.left}px, ${dimensions.margin.top}px)`)

bounds.append("defs").append("clipPath") // storing object; restrict painting area; later use for line path
.attr("id", "bounds-clip-path")
.append("rect") // create a rect inside clipPath
.attr("width", dimensions.boundedWidth)
.attr("height", dimensions.boundedHeight)

bounds.append("rect") // create a rect for freezing temp
.attr("class", "freezing")
bounds.append("g") // create a g for x-axis
.attr("class", "x-axis")
.style("transform", `translateY(${dimensions.boundedHeight}px)`)
bounds.append("g") // create a g for y-axis
.attr("class", "y-axis")
return bounds;
}
Insert cell
clip = {
const clip = bounds.append("g")
.attr("clip-path", "url(#bounds-clip-path)")
clip.append("path") // svg element path
.attr("class", "line")
return clip;
}
Insert cell
md`## load and process dataset`
Insert cell
stockData = FileAttachment("stocks.csv").text()
// stockData = FileAttachment("midea.csv").text()
Insert cell
myStockData = d3.csvParse(stockData, d3.autoType)
// myStockData = d3.csvParse(stockData, d3.autoType)
Insert cell
// mydata = FileAttachment("wh_weather_data.json").json()
Insert cell
// mydata.sort((a,b) => a.temperatureMax - b.temperatureMax).slice(0, 10).map(d => [xAccessor(d), d.temperatureMax])
Insert cell
mutable dataset = myStockData.sort((a,b) => xAccessor(a) - xAccessor(b)).slice(0, 100)
// mutable dataset = mydata.sort((a,b) => xAccessor(a) - xAccessor(b)).slice(0, 100)
// sort by date
// at first most datapoint is full of properties, but as time goes, the datapoint will just have date and temperatureMax
Insert cell
// dataset[dataset.length - 1].closePrice
Insert cell
loopData = {
const data = mutable dataset
drawLine(data);
let n = data.length;
while (n <= myStockData.length) {

await Promises.delay(1000);
const data = mutable dataset;
drawLine(data);
n++;
// mutable dataset = data.concat(mydata[n-1])
mutable dataset = [...data, myStockData[n-1]] // this works too
yield true;
}
}
Insert cell
drawLine = (dataset) => {
debugger;
const data = mutable dataset // access the dataset

const yScale = d3.scaleLinear() // create yScale processor
.domain(d3.extent(data, yAccessor))
.range([dimensions.boundedHeight, 0])


// actually draw freezing area
const freezingTemperaturePlacement = yScale(32)
const freezingTemperatures = bounds.select(".freezing") // get the rect inside bounds for the freezing area
.attr("x", 0)
.attr("width", dimensions.boundedWidth)
.attr("y", freezingTemperaturePlacement)
.attr("height", dimensions.boundedHeight - freezingTemperaturePlacement)

const xScale = d3.scaleTime() // create xScale processor
.domain(d3.extent(data, xAccessor))
.range([0, dimensions.boundedWidth])

// 5. Draw data

const lineGenerator = d3.line() // create a line generator
.x(d => xScale(xAccessor(d)))
.y(d => yScale(yAccessor(d)))

const lastTwoPoints = data.slice(-2) // return the last two data points
const pixelsBetweenLastPoints = xScale(xAccessor(lastTwoPoints[1])) - xScale(xAccessor(lastTwoPoints[0]))
const line = bounds.select(".line") // select the clip path for the line
.attr("d", lineGenerator(data)) // use lineGenerator(data) to provide the shape of line

// .style("transform", `translateX(${pixelsBetweenLastPoints}px)`) // move the entire line clip path towards the right for the pixel distance of pixelsBetweenLastPoints
// .transition().duration(1000) // not sure how they (the three lines here) play out together?
// .style("transform", `none`)


// 6. Draw peripherals inside drawLine function

const yAxisGenerator = d3.axisLeft() // create yAxis generator
.scale(yScale)

const yAxis = bounds.select(".y-axis") // draw yAxis
.transition().duration(1000)
.call(yAxisGenerator)

const xAxisGenerator = d3.axisBottom() // creat xAxis generator
.scale(xScale)

const xAxis = bounds.select(".x-axis") // draw xAxis with 1s duration
.transition().duration(1000)
.call(xAxisGenerator)
}

Insert cell
generateNewDataPoint = function(data) {

const lastDataPoint = data[data.length - 1]
const nextDay = d3.timeDay.offset(xAccessor(lastDataPoint), 1)
// debugger;

return { // return an object with date and temperatureMax for the next day after the final datapoint
date: d3.timeFormat("%Y-%m-%d")(nextDay),
temperatureMax: yAccessor(lastDataPoint) + (Math.random() * 6 - 3), // when data runs out use some random data
}
}
Insert cell
stylesheet = html`
<style>
.freezing {
fill: #e0f3f3;
}

.line {
fill: none;
stroke: #af9358;
stroke-width: 2;
}
.x-axis-label {
fill: black;
font-size: 1.4em;
text-transform: capitalize;
}
</style>

`
Insert cell
d3 = require("https://cdn.jsdelivr.net/npm/d3@5.12.0/dist/d3.js")
Insert cell
d3_commented = require(await FileAttachment("d3-5.12commented.js").url());
Insert cell
import {button} from "@embracelife/tutorial-utilities"
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more