Published
Edited
Sep 6, 2021
Insert cell
Insert cell

All_data = FileAttachment("Average-prices-Property-Type-2021-06.csv").csv({typed: true})
Insert cell
// All_data = All_data.filter(function (d) {
// return d.name !== "_TT" /* && d.TT < 5000 */;
// })
Insert cell
// Filter every 15 days from data to speed up the animation
data = All_data.filter(function (d) {
return d.name !== "_TT" && (d.date.getDate() === 1 || d.date.getDate() === 30) ;
})
Insert cell
// viewof replay = html`<button class="abcon">Replay`
Insert cell
Insert cell
Insert cell
// Speed of animation
viewof duration = html`<input type="range" min="20" max="5000" value="500" title = "Speed of animation"></input>`
Insert cell
chart = {
// replay;

const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);

const updateBars = bars(svg);
const updateAxis = axis(svg);
const updateLabels = labels(svg);
const updateTicker = ticker(svg);

invalidation.then(() => svg.interrupt());

return Object.assign(svg.node(), {
update(keyframe) {
const transition = svg.transition()
.duration(duration)
.ease(d3.easeLinear);

// Extract the top bar’s value.
x.domain([0, keyframe[1][0].value]);

updateAxis(keyframe, transition);
updateBars(keyframe, transition);
updateLabels(keyframe, transition);
updateTicker(keyframe, transition);

// invalidation.then(() => svg.interrupt());
// await transition.end();
}
});
}
Insert cell
// No of visible bars
n = 70
Insert cell
names = new Set(data.map(d => d.name))
Insert cell
datevalues = Array.from(d3.rollup(data, ([d]) => d.value, d => +d.date, d => d.name))
.map(([date, data]) => [new Date(date), data])
.sort(([a], [b]) => d3.ascending(a, b))
Insert cell
function rank(value) {
const data = Array.from(names, name => ({name, value: value(name)}));
data.sort((a, b) => d3.descending(a.value, b.value));
for (let i = 0; i < data.length; ++i) data[i].rank = Math.min(n, i);
return data;
}
Insert cell
// no of dates to be filled between each date
k = 1
Insert cell
keyframes = {
const keyframes = [];
let ka, a, kb, b;
for ([[ka, a], [kb, b]] of d3.pairs(datevalues)) {
for (let i = 0; i < k; ++i) {
const t = i / k;
keyframes.push([
new Date(ka * (1 - t) + kb * t),
rank(name => (a.get(name) || 0) * (1 - t) + (b.get(name) || 0) * t)
]);
}
}
keyframes.push([new Date(kb), rank(name => b.get(name) || 0)]);
return keyframes;
}
Insert cell
nameframes = d3.groups(keyframes.flatMap(([, data]) => data), d => d.name)
Insert cell
Insert cell
Insert cell
function bars(svg) {
let bar = svg.append("g")
.attr("fill-opacity", 0.6)
.selectAll("rect");

return ([date, data], transition) => bar = bar
.data(data.slice(0, n), d => d.name)
.join(
enter => enter.append("rect")
.attr("fill", color)
.attr("height", y.bandwidth())
.attr("x", x(0))
.attr("y", d => y((prev.get(d) || d ).rank) )
.attr("width", d => x((prev.get(d) || d).value) - x(0)),
update => update,
exit => exit.transition(transition).remove()
// .attr("y", d => y((next.get(d) || d).rank))
// .attr("width", d => x((next.get(d) || d).value) - x(0))
)
.call(bar => bar.transition(transition)
.attr("y", d => y(d.rank) )
.attr("width", d => x(d.value) - x(0)));
}
Insert cell
function labels(svg) {
let label = svg.append("g")
.style("font", "bold 12px var(--sans-serif)")
.style("font-variant-numeric", "tabular-nums")
.attr("text-anchor", "end")
.selectAll("text");

return ([date, data], transition) => label = label
.data(data.slice(0, n), d => d.name)
.join(
enter => enter.append("text")
.attr("transform", d => `translate(${x((prev.get(d) || d).value)},${y((prev.get(d) || d).rank)})`)
.attr("y", y.bandwidth() / 2)
.attr("x", -6)
.attr("dy", "-0.25em")
.text(d => d.name)
.call(text => text.append("tspan")
.attr("fill-opacity", 0.7)
.attr("font-weight", "normal")
.attr("x", -6)
.attr("dy", "1.15em")),
update => update,
exit => exit.transition(transition).remove()
.attr("transform", d => `translate(${x((next.get(d) || d).value)},${y((next.get(d) || d).rank)})`)
.call(g => g.select("tspan").tween("text", d => textTween(d.value, (next.get(d) || d).value)))
)
.call(bar => bar.transition(transition)
.attr("transform", d => `translate(${x(d.value)},${y(d.rank)})`)
.call(g => g.select("tspan").tween("text", d => textTween((prev.get(d) || d).value, d.value))));
}
Insert cell
function textTween(a, b) {
const i = d3.interpolateNumber(a, b);
return function(t) {
this.textContent = formatNumber(i(t));
};
}
Insert cell
formatNumber = d3.format(",d")
Insert cell
function axis(svg) {
const g = svg.append("g")
.attr("transform", `translate(0,${margin.top})`);

const axis = d3.axisTop(x)
.ticks(width / 160)
.tickSizeOuter(0)
.tickSizeInner(-barSize * (n + y.padding()));

return (_, transition) => {
g.transition(transition).call(axis);
g.select(".tick:first-of-type text").remove();
g.selectAll(".tick:not(:first-of-type) line").attr("stroke", "");
g.select(".domain").remove();
};
}
Insert cell
function ticker(svg) {
const now = svg.append("text")
.style("font", `bold ${barSize}px var(--sans-serif)`)
.style("font-variant-numeric", "tabular-nums")
.style("font-size", "60px")
.style("font-weight", "bold")
.attr("text-anchor", "end")
.attr("x", width - 6)
.attr("y", margin.top + barSize * (n - 0.45))
.attr("dy", "0.32em")
.text(formatDate(keyframes[0][0]));

return ([date], transition) => {
transition.end().then(() => now.text(formatDate(date)));
};
}
Insert cell
formatDate = d3.utcFormat("%d-%m-%Y")
Insert cell
color = {
// const scale = d3.scaleOrdinal([`#59a14f`, `#4e79a7`, `#f28e2c`, `#ff9da7`, `#9c755f`, `#4e79a7`, `#e15759`, `#76b7b2`, `#bab0ab`, `#f28e2c`, `#edc949`, `#e15759`, `#af7aa1`]);
const scale = d3.scaleOrdinal(d3.schemeCategory10);
//const scale = d3.quantize(d3.interpolateHcl("#fafa6e", "#2A4858"), 10);
if (data.some(d => d.category !== undefined)) {
const categoryByName = new Map(data.map(d => [d.name, d.category]))
scale.domain(categoryByName.values());
return d => scale(categoryByName.get(d.name));
}
return d => scale(d.name);
}
Insert cell
x = d3.scaleLinear([0, 1], [margin.left, width - margin.right])
Insert cell
y = d3.scaleBand()
.domain(d3.range(n + 1))
.rangeRound([margin.top, margin.top + barSize * (n + 1 + 0.1)])
.padding(0.1)
Insert cell
height = margin.top + barSize * n + margin.bottom
Insert cell
barSize = 48
Insert cell
margin = ({top: 16, right: 6, bottom: 6, left: 0})
Insert cell
md`Line chart`
Insert cell
input = aq.fromCSV(
await FileAttachment("average-detached-transpose.tsv").text(),
{ delimiter: '\t' }
)
Insert cell
input.view()
Insert cell
Insert cell
viewof linebar = embed({
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"width": 700, "height": 500,
"data": {"url": "https://raw.githubusercontent.com/Mr-Abdulrehman/vega-datasets/master/data/detached%20south%20east.json"},
"mark": "line",
"params": [{
"name": "abcom",
"select": {"type": "point", "fields": ["name"]},
"bind": "legend"
}],
"encoding": {
"x": {
"timeUnit": "yearmonth", "field": "date",
"axis": {"format": "%Y"}
},
"y": {
"field": "value",
"stack": "bottom",
"type" : 'quantitative',
"domain": true,
"tickCount": 0,
"titleX": 3,
"titleY": 0,
"titleAngle": 0,
"titleAlign": 'left',
"titleBaseline": "bottom",
"type": "quantitative",
},
"color": {
"field":"name",
"scale": {"scheme": "category20b"},
"legend":{"symbolType":"square"},
"scale": {"range": [
"#1f77b4",
"#8c564b",
"#9467bd",
"#2ca02c",
"#ff7f0e",
"#ff7f0e",
"#2ca02c",
"#7f7f7f",
"#d62728",
"#d62728",
"#e377c2",
"#8c564b",
"#17becf",
"#bcbd22",
"#1f77b4",
"#e377c2",
"#7f7f7f",
"#ff7f0e",
"#7f7f7f",
"#bcbd22",
"#17becf",
"#1f77b4",
"#9467bd",
"#e377c2",
"#2ca02c",
"#8c564b",
"#9467bd",
"#bcbd22",
"#9467bd",
"#1f77b4",
"#ff7f0e",
"#d62728",
"#d62728",
"#7f7f7f",
"#8c564b",
"#9467bd",
"#17becf",
"#ff7f0e",
"#ff7f0e",
"#e377c2",
"#1f77b4",
"#7f7f7f",
"#e377c2",
"#bcbd22",
"#17becf",
"#2ca02c",
"#2ca02c",
"#9467bd",
"#2ca02c",
"#d62728",
"#1f77b4",
"#17becf",
"#8c564b",
"#7f7f7f",
"#bcbd22",
"#8c564b",
"#8c564b",
"#2ca02c",
"#9467bd",
"#e377c2",
"#bcbd22",
"#ff7f0e",
"#d62728",
"#17becf",
"#1f77b4",
"#bcbd22",
"#d62728",
"#17becf",
"#7f7f7f",
"#e377c2",
]},
"sort":[
"Elmbridge",
"Oxford",
"Woking",
"Mole Valley",
"Windsor and Maidenhead",
"Epsom and Ewell",
"Guildford",
"Surrey",
"Sevenoaks",
"Reigate and Banstead",
"Tandridge",
"Tunbridge Wells",
"Waverley",
"Runnymede",
"Buckinghamshire",
"Winchester",
"Brighton and Hove",
"South Oxfordshire",
"Spelthorne",
"Hart",
"Bracknell Forest",
"Wokingham",
"Surrey Heath",
"Tonbridge and Malling",
"Mid Sussex",
"Chichester",
"Dartford",
"Reading",
"Horsham",
"Oxfordshire",
"East Hampshire",
"Gravesham",
"West Berkshire",
"Vale of White Horse",
"West Sussex",
"Slough",
"Crawley",
"South Cambridgeshire",
"Worthin",
"Test Valley",
"Wealden",
"Hampshire",
"Kent",
"Rushmoor",
"Maidstone",
"Basingstoke and Deane",
"Adur",
"Cherwell",
"New Forest",
"Fareham",
"East Sussex",
"Arun",
"Lewes",
"Ashford",
"Eastbourne",
"Canterbury",
"Rother",
"South Hams",
"Folkestone and Hythe",
"Eastleigh",
"Havant",
"Medway",
"Portsmouth",
"Hastings",
"Dover",
"Thanet",
"Swale",
"Gosport",
"Isle of Wight",
"Southampton"
]
},
"opacity": {
"condition": {"param": "abcom", "value": 1},
"value": 0.2
}
}
}
)
Insert cell
markLine = FileAttachment("").csv()
Insert cell
d3 = require("d3@6", "d3-color@2", "d3-scale-chromatic@2", "d3-interpolate@2", "d3-scale@3")
Insert cell
embed = require("vega-embed@6")
Insert cell
import {Scrubber} from "@mbostock/scrubber"
Insert cell
import {aq, op} from '@uwdata/arquero'
Insert cell
import {vl} from '@vega/vega-lite-api'
Insert cell
import {slider, menu, checkbox} from '@jheer/dom-utilities'
Insert cell
import {plot} from '@vega/vega-plot';
Insert cell
update = chart.update(keyframe)
Insert cell
viewof source = html`Source:
<a href="https://www.gov.uk/government/statistical-data-sets/uk-house-price-index-data-downloads-january-2021">UK House Price Index: data downloads January 2021</a>`
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