Published
Edited
May 27, 2022
1 fork
3 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
activeHoverPoint = track_data[pointerOnTrack]?.voronoi.delaunay.find(
pointerPos[0],
relativeMouseY
)
Insert cell
Insert cell
track_data[pointerOnTrack]
Insert cell
Insert cell
relativeMouseY = (yScale.paddingInner() * yScale.bandwidth()) / 2 +
(pointerPos[1] % yScale.step())
Insert cell
mutable pointerPos = [0, 0]
Insert cell
mutable active_zoom_params = null
Insert cell
yScale.step()
Insert cell
75 % yScale.step()
Insert cell
(pointerPos[1]) % yScale.step()
Insert cell
yScale.bandwidth()
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
SPACE_BETWEEN
Insert cell
track_data
Insert cell
(OUTSIDE_BID_ASK,
d3
.selectAll(".mark-group circle")
.filter((d) => d.price > d.ask || d.price < d.bid)
// .selectAll('circle')
.attr("stroke-width", "2")
.attr("stroke", "black")).style("opacity", 1)
// .attr('r', d => (d.price > d.ask ? '20' : 'black'))
// .attr('r', )
Insert cell
// // removes all tooltips
// d3.selectAll("g.mark-group > .mark-tooltip").remove())
// // .on("mouseleave", function () {
// // mutable pointerOnTrack = null;
// // })
Insert cell
`#mark_${pointerOnTrack}_${activeHoverPoint}`
Insert cell
activeTooltipMark = d3
.select(`#mark_${pointerOnTrack}_${activeHoverPoint}`)
.select("circle")
.style("opacity", "1")
.transition()
.delay(1000)
.style("opacity", 0.2)
// .transition()
// .attr('transform', `scale(50)`)
Insert cell
Insert cell
yScale = d3
.scaleBand()
.domain(names)
.rangeRound([m, names.length * unitSize + m])
.align(0.5)
.paddingInner(SPACE_BETWEEN)
.paddingOuter(0)
Insert cell
radialScale = d3
.scaleRadial()
.domain([0, d3.max(DATA_ARRAY, (d) => d.value)])
.range([1, RADIUS])
Insert cell
radialScale(100 / 2)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
strikeScale = d3.scaleLinear(
d3.extent([...new Set(DATA_ARRAY.map((x) => x.strike))].sort()),
[
RADIUS_PADDING ? -RADIUS : 0,
-yScale.bandwidth() + (RADIUS_PADDING ? RADIUS : 0)
]
)
Insert cell
strikeScale(150)
Insert cell
md`## AXIS`
Insert cell
Insert cell
TrackLayout = {
// const { m, pad, width, ColorPalette } = configs;
const names = yScale.domain();
const trackWidth = width - pad - m - m;

const trackAxis = mainGroup
.select(".track-axis")
.select(".y-axis")
// MOVE RIGHT TO SWIMLANE
.attr("transform", () => `translate(${pad + m},${0})`)
.call(
d3
.axisLeft(yScale)
// .tickPadding(-pad)
// TICKS ARE MADE GRAY AND UNIT-SIZED
// BACKGROUND FOR NAME LABELS
.tickSize(pad)
.tickValues(names)
)
.call((g) => {
g.select(".domain").remove();
const label_bg = g
.selectAll("line")
// WIDE AS A TRACK
.attr("stroke-width", yScale.bandwidth())
// LABEL BACKGROUND - GRAY
.attr("stroke", ColorPalette.surface);
const mark_bg = label_bg
.clone()
// COLOR:BLUE
.attr("stroke", ColorPalette.swimlane)
// LABEL BACKGROUND WENT LEFT OF AXIS
// TRACK BACKGROUND GOES RIGHT OF AXIS

// ticks already transitioned by m+pad;
// |---------------------width---------------------|
// |m|---pad---|--------------trackWidth-----------|
.attr("x1", 0)
.attr("x2", trackWidth)
.attr("class", "bg_swimlane")
.style("pointer-events", "none");
// .style("opacity", 0.3);

// g.call(d3.axisRight(track_data.find(d=>d.)))
});
trackAxis.selectAll("text").call(wrap, { pad, m });

yield trackAxis;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
md`## LAYOUT`
Insert cell
Insert cell
Insert cell
//a commonly used wrap function
function wrap(text, { pad, m }) {
text.each(function () {
const content = d3
.select(this)
.style("font-size", "1.2em")
.style("font-weight", "400")
.style("font-family", "Roboto"),
words = content.text().split(/\s+/).reverse();
let word;
let line = [],
lineNumber = 0;
// could use 'em'
const lineHeight = 1,
y = content.attr("y"),
dy = parseFloat(content.attr("dy"));
let tspan = content
.text(null)
.append("tspan")
.attr("x", m + pad / 2)
.attr("y", y)
.attr("dy", `${dy}em`)
.attr("text-anchor", "middle");
while ((word = words.pop())) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > pad - m * 2) {
line.pop();
tspan.text(line.join(" "));
line = [word];
lineNumber = ++lineNumber;
tspan = content
.append("tspan")
.attr("x", m)
.attr("y", y)
.attr("text-anchor", "middle")
.attr("dy", () => content.attr("height") + lineHeight + "em")
.text(word);
}
}
content
// alpha of 0.87 and opacity of 0.7 ensures match with Mat-Design
.attr("fill", "rgba(0,0,0,0.87)")
.attr("opacity", 0.7)
.style(
"transform",
`translate(${-pad}px,-${(lineNumber * lineHeight) / 2}em)`
)
.attr("text-anchor", "middle");
});
}
Insert cell
Insert cell
xScale = d3
.scaleTime()
.range([pad + m, width - m])
.domain(d3.extent(DATA_ARRAY, d => +d.DT))
Insert cell
---
## DATA
Insert cell
DATA_ARRAY = d3
.merge(
d3
.groups(
SalesLog.map((x) => {
x.series = d3.utcFormat("%Y-%m-%d")(x.expiry);
x.value = +x.qty * +x.price;
return x;
}),
(d) => d.series
)
// .sort((a, b) => d3.descending(a[1].length, b[1].length))
// .slice(0, 20)
.sort((a, b) => d3.descending(a[0], b[0]))

.map((x) => x[1].filter((x) => (x.value > 10 ? true : false)))
)
.sort((a, b) => d3.descending(a.expiry, b.expiry))
Insert cell
track_data = d3
.groups(DATA_ARRAY, (d) => d[name])
.sort((a, b) => d3.ascending(a[0], b[0]))
.map((eachGroup) => {
// grouped on name; [[name],[arr]]
const data = eachGroup[1];

// REQUIREMENT: all data should have a value property
const vScale = strikeScale; //create_valueScale(data);
const rScale = d3
.scaleRadial()
.domain(d3.extent(data, (d) => d.price * d.qty))
.range([0, RADIUS]);
const voronoi = d3.Delaunay.from(
data,
(d) => xScale(d.DT),
(d) => vScale(d.strike)
).voronoi([
m + pad,
yScale.paddingInner() * yScale.bandwidth() +
(RADIUS_PADDING ? RADIUS : 0),
width - m,
yScale.bandwidth() - (RADIUS_PADDING ? RADIUS : 0)
]);

const it = { name: eachGroup[0], data, vScale, rScale, voronoi };
return it;
})
.sort((a, b) => d3.descending(a.name, b.name))
Insert cell
[yScale.paddingInner() * yScale.bandwidth() + yScale.bandwidth(), yScale.step()]
Insert cell
names = [...new Set(DATA_ARRAY.map(x => x[name]))]
Insert cell
tickArray = [110, 150, 200]
Insert cell
md`## SETUP`
Insert cell
m = 30
Insert cell
pad = 60

// {
// const longest_name = calculate_longest_name([
// ...new Set(DATA_ARRAY.map((x) => x[name]))
// ]);
// return d3.min([longest_name + 2 * m, width * 0.2 + 2 * m]);

// function calculate_longest_name(names) {
// return Math.ceil(
// d3.max(
// names.map((name) => {
// const text = d3
// .select(".chart")
// .append("text")
// .text(name)
// .attr("stroke", "black");
// // .call(NameLabelStyling);
// const box = text.node().getBBox();
// const labelWidth = box.width;
// text.remove();
// return labelWidth;
// })
// )
// );
// }
// }
Insert cell
viewHeight = d3
.select('.chart-parent')
.node()
.getBoundingClientRect().height - 2
Insert cell
innerHeight = unitSize * names.length
Insert cell
RADIUS = 9
Insert cell
halo_size = RADIUS + 4
Insert cell
unitSize = 50
Insert cell
name = "series"
Insert cell
formatTime = d3.timeFormat('%-I:%-M%p') // d3.timeFormat('%-d/%-m/%Y %-I:%-M%p')
Insert cell
// width = 460
Insert cell
// width
Insert cell
md`### COLORS`
Insert cell
Insert cell
Insert cell
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