Public
Edited
Jan 31, 2024
Insert cell
Insert cell
plotConsumption("Erdgas")
Insert cell
plotConsumption("Strom")
Insert cell
Insert cell
Insert cell
data = fetch('https://konfi.kommitment.works/~hannes/smartMeterData/allData.json').then((response) => response.json())
Insert cell
plotConsumption = (type) => {
// Brennwert: Angegeben wird er in der Maßeinheit kWh/m³, also Kilowattstunden pro Kubikmeter. Der Brennwert variiert je nach Qualität des Gases und liegt üblicherweise zwischen 8,0 und 12,5 kWh/m³. Wie hoch der Brennwert Ihres Erdgases ist, können Sie in Ihrer letzten Rechnung nachlesen oder bei Ihrem Netzbetreiber erfragen.
const Brennwert = 10.5 // kWh/m³
const mdata = data.filter(d => d.term === "V39."+type)
// filter data > 30.12.2023
.filter(d => new Date(d.timestamp) > new Date("2024-01-01T00:00:00+00:00"))
const r2d = x => Math.round(100*x)/100
const KWh = type === "Erdgas" ? r2d(mdata.length*Brennwert/1000) : r2d(mdata.length/75)
const title = type+"-Verbrauch "+ d3.min(mdata, d => d.timestamp).substring(0,10) + " - " + d3.max(mdata, d => d.timestamp).substring(0,10) +": "
+ ((type === "Erdgas" )
? r2d(mdata.length/1000) + "m³ Erdgas === " + KWh + "kWh"
: KWh + "kWh")
+ " entries: " + mdata.length + ""
const boxWidth = 700
const boxHeight = boxWidth*1/3
const margin = ({top: 30, right: 30, bottom: 30, left: 50})
// scales
const x = d3.scaleTime()
.domain([new Date(d3.min(mdata, d => d.timestamp)), new Date(d3.max(mdata, d => d.timestamp))])
.range([0, boxWidth ])
const y = d3.scaleSqrt()
.domain([0, d3.max(mdata, d => d.Watt)])
.range([boxHeight , 0])
const canvas = d3.select(DOM.svg(boxWidth + margin.left + margin.right, boxHeight + margin.top + margin.bottom))
canvas.node().style.border = '1px solid lightgrey'
// the title
canvas.append("text")
.attr("transform", "translate("+margin.left+",20)")
.attr("font-size", "14px")
.attr("f0nt-family","sans-serif")
.attr("y", 0)
.attr("x", 0)
.attr("font-family", "monospace")
.text(title)
// the red line
const drawredline = (evt, text="") => {
const xpos = evt.offsetX
const ypos = boxHeight - evt.offsetY
const lineOffsetFromMouse = 0
const redColor = "red"
// remove all red lines, aka all elements with class .redLine
canvas.selectAll(".redLine").remove()
d3.select("#xMouse-value").text(evt.offsetX)
d3.select("#yMouse-value").text(evt.offsetY)
plot.append ("path")
.attr("class", "redLine")
.attr("stroke", redColor)
.attr("d", "M "+(evt.offsetX - lineOffsetFromMouse - margin.left)+","+(boxHeight)+",L "+(evt.offsetX - lineOffsetFromMouse- margin.left)+",0 Z")
// a smal horizintal line where the mouse is
plot.append ("path")
.attr("class", "redLine")
.attr("stroke", redColor)
.attr("d", "M "+(evt.offsetX - 10 - margin.left)+","+(evt.offsetY-margin.top)+",L "+(evt.offsetX + 10- margin.left)+","+(evt.offsetY-margin.top)+" Z")
plot.append ("text")
.attr("class", "redLine")
.text( text )
.attr("x", evt.offsetX+5)
.attr("y", 10)
.attr("dy", ".75em")
.attr("fill", redColor)
.attr("text-anchor", "left")
.style("font", "12px sans-serif")
.append("tspan")
.attr("x", evt.offsetX+5- margin.left)
.attr("dy", "1em")
.text( "Energy: "+Math.round(y.invert(boxHeight-ypos-margin.top)) + " Watt" )
.append("tspan")
.attr("x", evt.offsetX+5- margin.left)
.attr("dy", "1em")
.text( "Date: "+new Date(x.invert(xpos)).toISOString().substring(0,10) )
.append("tspan")
.attr("x", evt.offsetX+5- margin.left)
.attr("dy", "1em")
// I want to print the datee in formt YYYY-MM-DD
// but the date is in the format YYYY-MM-DDTHH:MM:SS+00:00
// so I have to cut the string
.text( "Time: "+new Date(x.invert(xpos)).toISOString().substring(11,19) )
}
// append rectangle inside the canvas for the plot area
const plot = d3.create("svg")
.append("g")
.attr("transform", "translate("+margin.left+","+margin.top+")")
.on("mouseout", canvas.selectAll(".redLine").remove() )
.on("mousemove", evt => drawredline(evt, "") )
canvas.append(() => plot.node())
plot.append("rect")
.attr("width", boxWidth)
.attr("height", boxHeight)
.attr("fill", "lightgrey")
.attr("stroke", "grey")
.attr("stroke-width", 1)
.attr("opacity", 0.2)

// x-Axis
plot.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0," + (boxHeight) + ")")
.call(d3.axisBottom(x))

// y-Axis
plot.append("g")
.attr("class", "y-axis")
// ticks on the left side
.call(d3.axisLeft(y))
.call(g => g.selectAll(".tick:not(:first-of-type) line").attr("stroke", "#ddd").attr('opacity',0.1))

// plot points
/**
plot.selectAll("circle")
.data(mdata)
.enter()
.append("circle")
.attr("cx", d => x(new Date(d.timestamp)))
.attr("cy", d => y(d.Watt))
.attr("r", 1)
.attr("fill", "steelblue")
.attr("stroke", "steelblue")
.attr("stroke-width", 1)
.attr("opacity", 0.5)
/**/

// for each hour between the first and the last timestamp
// calculate the average consumption
const hourlyData = []
const firstTimestamp = new Date(d3.min(mdata, d => d.timestamp))
const lastTimestamp = new Date(d3.max(mdata, d => d.timestamp))
let timeQuant = firstTimestamp
while (timeQuant < lastTimestamp) {
const hourData = mdata.filter(d => d.timestamp.substring(0,14) === timeQuant.toISOString().substring(0,14))
const hourAverage = d3.mean(hourData, d => d.Watt)
hourlyData.push({time: timeQuant.toISOString().substring(0,14)+"00:00+00:00", Watt: hourAverage || 0})
timeQuant = new Date(timeQuant.getTime() + 60*60*1000)
}
// and plot it as a block
plot.selectAll("rect")
.data(hourlyData)
.enter()
.append("rect")
.attr("x", d => x(new Date(d.time)))
.attr("y", d => y(d.Watt))
.attr("width", boxWidth/hourlyData.lenght)
.attr("height", d => boxHeight - y(d.Watt))
.attr("fill", "blue")
.attr("stroke", "blue")
.attr("stroke-width", 1)
.attr("opacity", 0.3)

return canvas.node()
}
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