Public
Edited
Feb 4, 2023
Importers
Insert cell
Insert cell
Insert cell
Insert cell
data = d3.csv(dataLink,d3.autoType)
Insert cell
data2 = d3.csv(data2Link,d3.autoType)
Insert cell
chart1 =LineChart(data, data2, {
x: d => d.percent,
y: d => d.kwh,
z: d=> d.m2,
a: d=> d.fppb,
b: d=> d.bppb,
c: d=> d.tppb,
d: d=> d.co2ppm,

x2: data2 => data2.percent,
//x2: d3.map(data2, d => d.percent),
y2: data2 => data2.kwh,
z2: data2=> data2.m2,
a2: data2=> data2.fppb,
b2: data2=> data2.bppb,
c2: data2=> data2.tppb,
d2: data2=> data2.co2ppm,
//title: d=>d.m2,
//yLabel: "↑ Daily close ($)",
width,
height: 300,
color: "steelblue",
yDomain:[0,250],
xDomain:[0,100],
yLabel:'nEUI kWh/m2',
xLabel:'% Required Greenwall',

yOffset: 330,

//title: d=>d.m2,
//yLabel: "↑ Daily close ($)",
yDomain2:[180,450],
xDomain2:[0,100],
yLabel2:'nEUI kWh/m2',
xLabel2:'percent used'
//yFormat:"hello"
})

Insert cell
aapl = FileAttachment("aapl.csv").csv({typed: true})
Insert cell
Insert cell
focus = Generators.input(chart1) // or say viewof focus = LineChart(…)
Insert cell
Insert cell
// Copyright 2021 Observable, Inc.
// Released under the ISC license.
// https://observablehq.com/@d3/line-with-tooltip
function LineChart(data,data2, {
x = ([x]) => x, // given d in data, returns the (temporal) x-value
y = ([, y]) => y, // given d in data, returns the (quantitative) y-value
z = ([z]) => z,
a = ([a]) => a,
b = ([b]) => b,
c = ([c]) => c,
d = ([d]) => d,
x2 = ([x2]) => x2, // given d in data, returns the (temporal) x-value
y2 = ([, y2]) => y2, // given d in data, returns the (quantitative) y-value
z2 = ([z2]) => z2,
a2 = ([a2]) => a2,
b2 = ([b2]) => b2,
c2 = ([c2]) => c2,
d2 = ([d2]) => d2,
title, // given d in data, returns the title text
title2,
defined, // for gaps in data
defined2,
curve = d3.curveLinear, // method of interpolation between points
marginTop = 10, // top margin, in pixels
marginRight = 30, // right margin, in pixels
marginBottom = 30, // bottom margin, in pixels
marginLeft = 40, // left margin, in pixels
width = 640, // outer width, in pixels
height = 400, // outer height, in pixels
xType = d3.scaleLinear, // type of x-scale
xDomain, // [xmin, xmax]
xDomain2, // [xmin, xmax]
yOffset,
xRange = [marginLeft, width - marginRight], // [left, right]
yType = d3.scaleLinear, // type of y-scale
yDomain, // [ymin, ymax]
yDomain2, // [ymin, ymax]
yRange = [height - marginBottom, marginTop], // [bottom, top]
color = "currentColor", // stroke color of line
strokeWidth = 1.5, // stroke width of line, in pixels
strokeLinejoin = "round", // stroke line join of line
strokeLinecap = "round", // stroke line cap of line
yFormat, // a format specifier string for the y-axis
yLabel, // a label for the y-axis
xLabel, // a label for the x-axis
yLabel2, // a label for the y-axis
xLabel2, // a label for the x-axis
} = {}) {
// Compute values.
const X = d3.map(data, x);
const Y = d3.map(data, y);
const O = d3.map(data, d => d);
const I = d3.map(data, (_, i) => i);
const A = d3.map(data, a);
const B = d3.map(data, b);
const C = d3.map(data, c);
const DD = d3.map(data, d);

const X2 = d3.map(data2, x2);
const Y2 = d3.map(data2, y2);
const O2 = d3.map(data2, d => d);
const I2 = d3.map(data2, (_, i) => i);
const A2 = d3.map(data2, a2);
const B2 = d3.map(data2, b2);
const C2 = d3.map(data2, c2);
const DD2 = d3.map(data2, d2);

const Z = d3.map(data, z);
const Z2 = d3.map(data2, z2);

// Compute which data points are considered defined.
if (defined === undefined) defined = (d, i) => !isNaN(X[i]) && !isNaN(Y[i]);
const D = d3.map(data, defined);

if (defined2 === undefined) defined2 = (d, i) => !isNaN(X2[i]) && !isNaN(Y2[i]);
const D2 = d3.map(data2, defined2);

// Compute default domains.
if (xDomain === undefined) xDomain = d3.extent(X);
if (yDomain === undefined) yDomain = [0, d3.max(Y)];
//if (yDomain === undefined) yDomain2 = [0, d3.max(Y)];

if (xDomain2 === undefined) xDomain2 = d3.extent(X2);
if (yDomain2 === undefined) yDomain2 = [0, d3.max(Y2)];

// Construct scales and axes.
const xScale = xType(xDomain, xRange);
const yScale = yType(yDomain, yRange);
const xAxis = d3.axisBottom(xScale).ticks(width / 90).tickSizeOuter(0);
const yAxis = d3.axisLeft(yScale).ticks(height / 25, yFormat);

// Construct scales and axes.
const xScale2 = xType(xDomain2, xRange);
const yScale2 = yType(yDomain2, yRange);
const xAxis2 = d3.axisBottom(xScale2).ticks(width / 90).tickSizeOuter(0);
const yAxis2 = d3.axisLeft(yScale2).ticks(height / 25, yFormat);

// Compute titles.
if (title === undefined) {
//const formatDate = xScale.tickFormat(null,"%b %-d, %Y");
const formatValue = yScale.tickFormat(100, yFormat);
//title = i => `${formatDate(X[i])}\n${formatValue(Y[i])}`;
title = i => `m2 of greenwall ${formatValue(Z[i])}\nEUI kWh/m2 ${formatValue(Y[i])}\nFormaldehyde ppb ${formatValue(A[i])}\nBenzene ppb ${formatValue(B[i])}\nToluene ppb ${formatValue(C[i])}\nCO2ppm ${formatValue(DD[i])}`;
} else {
const O = d3.map(data, d => d);
const T = title;
title = i => T(O[i], i, data);
}

// Compute titles.
if (title2 === undefined) {
//const formatDate = xScale.tickFormat(null,"%b %-d, %Y");
const formatValue = yScale2.tickFormat(100, yFormat);
//title = i => `${formatDate(X[i])}\n${formatValue(Y[i])}`;
title2 = i => `m2 of greenwall ${formatValue(Z2[i])}\nEUI kWh/m2 ${formatValue(Y2[i])}\nFormaldehyde ppb ${formatValue(A2[i])}\nBenzene ppb ${formatValue(B2[i])}\nToluene ppb ${formatValue(C2[i])}\nCO2ppm ${formatValue(DD2[i])}`;
} else {
const O2 = d3.map(data2, d => d);
const T2 = title2;
title2 = i => T2(O2[i], i, data2);
}

// Construct a line generator.
const line = d3.line()
.defined(i => D[i])
.curve(curve)
.x(i => xScale(X[i]))
.y(i => yScale(Y[i]));
const line2 = d3.line()
.defined(i => D2[i])
.curve(curve)
.x(i => xScale2(X2[i]))
.y(i => yScale2(Y2[i])+yOffset)

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height*2.3)
.attr("viewBox", [0, 0, width, height*2])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.style("-webkit-tap-highlight-color", "transparent")
.style("overflow", "visible")
//.on("pointerenter pointermove", pointermoved2)
.on("pointerenter pointermove", pointermoved)
//.on("pointerenter pointermove", pointermoved2)
.on("pointerleave", pointerleft)
.on("touchstart", event => event.preventDefault());

svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(xAxis);

svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(yAxis)
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick line").clone()
.attr("x2", width - marginLeft - marginRight)
.attr("stroke-opacity", 0.1))
.call(g => g.append("text")
.attr("x", -marginLeft)
.attr("y", 10)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.attr("font-size", '1.2em')
.text(yLabel))
svg.call(g => g.append("text")
.attr("x", width-marginRight-80)
.attr("y", height+10)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.attr("font-size", '1.2em')
.text(xLabel));

svg.append("g")
.attr("transform", `translate(0,${height - marginBottom+yOffset})`)
.call(xAxis2);

svg.append("g")
.attr("transform", `translate(${marginLeft},${yOffset})`)
.call(yAxis2)
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick line").clone()
.attr("x2", width - marginLeft - marginRight)
.attr("stroke-opacity", 0.1))
.call(g => g.append("text")
.attr("x", -marginLeft)
.attr("y", 10)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.attr("font-size", '1.2em')
.text(yLabel))
svg.call(g => g.append("text")
.attr("x", width-marginRight-80)
.attr("y", height+yOffset+10)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.attr("font-size", '1.2em')
.text(xLabel));


//draw the blue lines
svg.append("path")
.attr("fill", "none")
.attr("stroke", color)
.attr("stroke-width", strokeWidth)
.attr("stroke-linejoin", strokeLinejoin)
.attr("stroke-linecap", strokeLinecap)
.attr("d", line(I));

svg.append("path")
.attr("fill", "none")
.attr("stroke", color)
.attr("stroke-width", strokeWidth)
.attr("stroke-linejoin", strokeLinejoin)
.attr("stroke-linecap", strokeLinecap)
.attr("d", line2(I2));

const tooltip = svg.append("g")
.style("pointer-events", "none");
const tooltip2 = svg.append("g")
.style("pointer-events", "none");

function pointermoved(event) {
pointermoved2(event)
//console.log('hello')
const i = d3.bisectCenter(X, xScale.invert(d3.pointer(event)[0]));
tooltip.style("display", null);
tooltip.attr("transform", `translate(${xScale(X[i])},${yScale(Y[i])})`);

const path = tooltip.selectAll("path")
.data([,])
.join("path")
.attr("fill", "white")
.attr("stroke", "black");

const text = tooltip.selectAll("text")
.data([,])
.join("text")
.call(text => text
.selectAll("tspan")
.data(`${title(i)}`.split(/\n/))
.join("tspan")
.attr("x", 0)
.attr("y", (_, i) => `${i * 1.5}em`)
.attr("font-weight", (_, i) => i ? null : "bold")
.attr("font-size", '1.2em')
.text(d => d));

const {x, y, width: w, height: h} = text.node().getBBox();
text.attr("transform", `translate(${-w / 2},${15 - y})`);
path.attr("d", `M${-w / 2 - 10},5H-5l5,-5l5,5H${w / 2 + 10}v${h + 20}h-${w + 20}z`);
svg.property("value", O[i]).dispatch("input", {bubbles: true});
}

function pointermoved2(event) {
//console.log('hello2')
const i2 = d3.bisectCenter(X2, xScale.invert(d3.pointer(event)[0]));
tooltip2.style("display", null);
tooltip2.attr("transform", `translate(${xScale2(X2[i2])},${yScale2(Y2[i2])+yOffset})`);
//tooltip2.attr("transform", "translate(400,400)");

const path2 = tooltip2.selectAll("path")
.data([,])
.join("path")
.attr("fill", "white")
.attr("stroke", "black");

const text2 = tooltip2.selectAll("text")
.data([,])
.join("text")
.call(text => text
.selectAll("tspan")
.data(`${title2(i2)}`.split(/\n/))
.join("tspan")
.attr("x", 0)
.attr("y", (_, i) => `${i * 1.5}em`)
.attr("font-weight", (_, i) => i ? null : "bold")
.attr("font-size", '1.2em')
.text(d => d));

const {x2, y2, width: w, height: h} = text2.node().getBBox();
//text2.attr("transform", `translate(${-w / 2},${15 - y})`);
text2.attr("transform", `translate(${-w / 2},${25})`);
path2.attr("d", `M${-w / 2 - 10},5H-5l5,-5l5,5H${w / 2 + 10}v${h + 20}h-${w + 20}z`);
svg.property("value", O2[i2]).dispatch("input", {bubbles: true});
}





function pointerleft() {
tooltip.style("display", "none");
svg.node().value = null;
svg.dispatch("input", {bubbles: true});
}

return Object.assign(svg.node(), {value: null});
}
Insert cell
x = d3.scaleUtc()
.domain(d3.extent(data, d => d.percent))
Insert cell
import {howto} from "@d3/example-components"
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