chart = {
const width = margins.width + 40;
const div = d3
.create("div")
.attr("id", "theMainShebang")
.attr("width", margins.width + "px");
const svg_div = div
.append("div")
.attr("width", "100%")
.attr("class", "svg-flex");
const svg = svg_div
.append("svg")
.attr("viewBox", [0, 0, width, margins.svg_main_height])
.classed("top", true);
const svgBottom = svg_div
.append("svg")
.attr("viewBox", [0, 0, width, margins.svg_diff_height])
.classed("bottom", true);
const songBox = div
.append("div")
.attr("id", "songBox")
.attr("width", "30%")
.text("Songs");
const text = songBox.append("div").attr("class", "titles");
const title = svg
.append("text")
.attr("transform", `translate(${width / 2}, 20)`)
.attr("text-anchor", "middle");
const xAxisLabel = svg
.append("text")
.attr(
"transform",
`translate(${width / 2}, ${margins.svg_main_height - 20})`
)
.attr("text-anchor", "middle")
.text(`Index of word (token)`);
const yAxisLabel = svg
.append("text")
.attr(
"transform",
`translate(${20}, ${margins.svg_main_height / 2}), rotate(270)`
)
.attr("text-anchor", "middle")
.text(`Cumulative Sentiment (AFINN)`);
const hell = svg // lol i can add the focus on point thing later bc rn i am SUFFERING
.append('rect')
.style("fill", "none")
.style("pointer-events", "all")
.attr('width', width)
.attr('height', margins.height);
// create empty axis for main
const xAxis = svg
.append("g")
.attr("class", "xAxis")
.attr(
"transform",
`translate(0, ${margins.svg_main_height - margins.bottom})`
);
const yAxis = svg
.append("g")
.attr("class", "yAxis")
.attr("transform", `translate(${margins.left}, 0)`);
// create empty axis for other
const xAxisSub = svgBottom
.append("g")
.attr("class", "xAxis")
.attr(
"transform",
`translate(0, ${margins.svg_diff_height - margins.bottom})`
);
const yAxisSub = svgBottom
.append("g")
.attr("class", "yAxis")
.attr("transform", `translate(${margins.left}, 0)`);
const line = svgBottom.append("g").attr("clip-path", "url(#clip)");
div.node().update = album => {
title.text(`Sentiment chart for the album: ${album}`);
// filter down data
let filtered_data = data.filter(
d => d.album == album || selected_songs.has(d.track)
);
console.log(filtered_data);
cumulate(filtered_data, "track", "afinn");
let grouped = d3.group(filtered_data, d => d.track);
// add y axis
let y = d3
.scaleLinear()
.domain(d3.extent(filtered_data, d => d.value))
.range([margins.svg_main_height - margins.bottom, margins.top]);
yAxis
.transition()
.duration(500)
.call(d3.axisLeft(y));
// add x axis
let x = d3
.scaleLinear()
.domain([0, d3.max([...grouped.values()], d => d.length)])
.range([margins.left, width - margins.right]);
xAxis
.transition()
.duration(500)
.call(d3.axisBottom(x))
.attr("transform", `translate(0, ${y(0)})`); // here to re-align x axis to y(0)
// ADD SUB AXIS
// add y axis
let ySub = d3
.scaleLinear()
.domain(d3.extent(filtered_data, d => d.value))
.range([margins.svg_diff_height - margins.bottom, margins.top]);
ySub.ticks(3);
yAxisSub
.transition()
.duration(500)
.call(d3.axisLeft(ySub));
// add x axis
let xSub = d3
.scaleLinear()
.domain([0, d3.max([...grouped.values()], d => d.length)])
.range([margins.left, width - margins.right]);
xAxisSub
.transition()
.duration(500)
.call(d3.axisBottom(x))
.attr("transform", `translate(0, ${ySub(0)})`); // here to re-align x axis to y(0)
// add color
let color = d3
.scaleOrdinal()
.domain([...grouped.keys()])
.range(d3.schemeCategory10);
// add legend
const grouped_keys = enumerate([...grouped.keys()]); // trying to remember why I did this
text
.selectAll(".song")
.data(grouped_keys, d => d.data)
.join(
enter => {
let text = enter
.append("p")
.text(d => d.data)
.attr(
"class",
d =>
`song ${d.data
.replace(/\s+|\W+/g, "_")
.replaceAll(/\d/g, "digit")}`
)
.classed("selected", d => selected_songs.has(d.data))
.classed("locked", d => selected_songs.has(d.data));
text.on("mouseover", (d, i) => {
mouseOver(
d,
i,
line_styles.width_selected,
svg,
svgBottom,
songBox
);
});
text.on("mouseout", (d, i) => {
mouseOut(d, i, line_styles.width, svg, svgBottom, songBox);
});
text.on("click", (d, i) => {
click(d, i, line_styles.width, svg, svgBottom, songBox);
});
return text;
},
update =>
update
.text(d => d.data)
.classed("selected", d => selected_songs.has(d.data)),
exit => exit.remove()
);
let clip = svgBottom
.append("defs")
.append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("width", width - margins.right)
.attr("height", margins.svg_diff_height)
.attr("x", 0)
.attr("y", 0);
let brush = d3
.brushX()
.extent([
[margins.left, margins.top],
[width - margins.right, margins.svg_diff_height - margins.bottom]
])
.on("end", updateChart);
// draw svg
let grouped2 = [...grouped.keys()].map(d => ({
data: d,
arr: grouped.get(d)
}));
console.log("grp");
console.log(grouped2);
svg
.selectAll(".line")
.data(grouped2, d => d.data)
.join(
enter =>
enter
.append("path")
.attr(
"class",
d =>
`line ${d.data
.replace(/\s+|\W+/g, "_")
.replace(/\d/g, "digit")}`
)
.classed("selected", d => selected_songs.has(d.data))
.classed("locked", d => selected_songs.has(d.data))
.attr("opacity", .3)
.attr("stroke", d => color(d.data))
.attr("fill", "none")
.attr("stroke-width", line_styles.width)
.attr("d", d =>
d3
.line()
.x(d => x(d.x))
.y(d => y(d.value))(d.arr)
)
.attr("cursor", "pointer")
.on("click", (d, i) =>
click(d, i, line_styles.width_selected, svg, svgBottom, songBox)
),
update =>
update.call(update =>
update
.transition()
.duration(500)
.attr("d", d =>
d3
.line()
.x(d => x(d.x))
.y(d => y(d.value))(d.arr)
)
),
exit => exit.remove()
);
line
.selectAll(".line")
.data(grouped2, d => d.data)
.join(
enter =>
enter
.append("path")
.attr(
"class",
d =>
`line ${d.data
.replace(/\s+|\W+/g, "_")
.replace(/\d/g, "digit")}`
)
.classed("selected", d => selected_songs.has(d.data))
.attr("opacity", .3)
.attr("stroke", d => color(d.data))
.attr("fill", "none")
.attr("stroke-width", line_styles.width - 1)
.attr("d", d =>
d3
.line()
.x(d => xSub(d.x))
.y(d => ySub(d.value))(d.arr)
),
update =>
update.call(update =>
update
.transition()
.duration(500)
.attr("d", d =>
d3
.line()
.x(d => xSub(d.x))
.y(d => ySub(d.value))(d.arr)
)
),
exit => exit.remove()
);
line
.append("g")
.attr("class", "brush")
.call(brush);
let idleTimeout;
function idled() {
idleTimeout = null;
}
function updateChart() {
let extent = d3.brushSelection(this);
if (!extent) {
if (!idleTimeout) return (idleTimeout = setTimeout(idled, 350));
xSub.domain([4, 8]); // but y tho?
x.domain([4, 8]);
} else {
xSub.domain([x.invert(extent[0]), x.invert(extent[1])]);
x.domain([x.invert(extent[0]), x.invert(extent[1])]);
line.select("brush").call(brush.move, null);
}
// update axis and position for bottom
let domain = xSub.domain();
xAxisSub
.transition()
.duration(1000)
.call(d3.axisBottom(xSub));
line
.selectAll(".line")
.transition()
.duration(1000)
.attr("d", d =>
d3
.line()
.x(d => xSub(d.x))
.y(d => ySub(d.value))(
d.arr.filter(datum => datum.x >= domain[0] || datum.x <= domain[1])
)
);
// update axis and position for TOP
domain = x.domain();
xAxis
.transition()
.duration(1000)
.call(d3.axisBottom(x));
svg
.selectAll(".line")
.transition()
.duration(1000)
.attr("d", d =>
d3
.line()
.x(d => x(d.x))
.y(d => y(d.value))(
d.arr.filter(datum => datum.x >= domain[0] || datum.x <= domain[1])
)
);
}
svgBottom.on("dblclick", function() {
let max = d3.max([...grouped.values()], d => d.length);
xSub.domain([0, max]);
x.domain([0, max]);
xAxisSub
.transition()
.duration(1000)
.call(d3.axisBottom(xSub));
line
.selectAll('.line')
.transition()
.duration(1000)
.attr("d", d =>
d3
.line()
.x(d => xSub(d.x))
.y(d => ySub(d.value))(d.arr)
);
xAxis
.transition()
.duration(1000)
.call(d3.axisBottom(x));
svg
.selectAll('.line')
.transition()
.duration(1000)
.attr("d", d =>
d3
.line()
.x(d => x(d.x))
.y(d => y(d.value))(d.arr)
);
});
};
return div.node();
}