inflation-explorer/shared/js/explorer.js (566 lines of code) (raw):
import * as d3 from "d3"
// import * as d3jetpack from "d3-jetpack"
// import "d3-jetpack"
// import {tspans} from "d3-jetpack"
// import { textwrap } from 'd3-textwrap';
import { textwrap } from 'd3-textwrap';
// d3.textwrap = textwrap;
var categories = {
"Beef and veal": "Food",
"Bread": "Food",
"Breakfast cereals": "Food",
"Cakes and biscuits": "Food",
"Cheese": "Food",
"Coffee, tea and cocoa": "Food",
"Eggs": "Food",
"Fish and other seafood": "Food",
"Food additives and condiments": "Food",
"Fruit": "Food",
"Ice cream and other dairy products": "Food",
"Jams, honey and spreads": "Food",
"Lamb and goat": "Food",
"Milk": "Food",
"Oils and fats": "Food",
"Other cereal products": "Food",
"Other food products n.e.c.": "Food",
"Other meats": "Food",
"Pork": "Food",
"Poultry": "Food",
"Restaurant meals": "Food",
"Snacks and confectionery": "Food",
"Take away and fast foods": "Food",
"Vegetables": "Food",
"Waters, soft drinks and juices": "Food",
"Beer": "Alcohol and tobacco",
"Spirits": "Alcohol and tobacco",
"Tobacco": "Alcohol and tobacco",
"Wine": "Alcohol and tobacco",
"Accessories": "Clothing",
"Cleaning, repair and hire of clothing and footwear": "Clothing",
"Footwear for infants and children": "Clothing",
"Footwear for men": "Clothing",
"Footwear for women": "Clothing",
"Garments for infants and children": "Clothing",
"Garments for men": "Clothing",
"Garments for women": "Clothing",
"Electricity": "Housing",
"Gas and other household fuels": "Housing",
"Maintenance and repair of the dwelling": "Housing",
"New dwelling purchase by owner-occupiers": "Housing",
"Property rates and charges": "Housing",
"Rents": "Housing",
"Water and sewerage": "Housing",
"Carpets and other floor coverings": "Household and services",
"Child care": "Household and services",
"Cleaning and maintenance products": "Household and services",
"Furniture": "Household and services",
"Glassware, tableware and household utensils": "Household and services",
"Hairdressing and personal grooming services": "Household and services",
"Household textiles": "Household and services",
"Major household appliances": "Household and services",
"Other household services": "Household and services",
"Other non-durable household products": "Household and services",
"Personal care products": "Household and services",
"Small electric household appliances": "Household and services",
"Tools and equipment for house and garden": "Household and services",
"Dental services": "Health",
"Medical and hospital services": "Health",
"Pharmaceutical products": "Health",
"Therapeutic appliances and equipment": "Health",
"──Transport──": "divider",
"Automotive fuel": "Transport",
"Maintenance and repair of motor vehicles": "Transport",
"Motor vehicles": "Transport",
"Other services in respect of motor vehicles": "Transport",
"Spare parts and accessories for motor vehicles": "Transport",
"Urban transport fares": "Transport",
"Audio, visual and computing equipment": "Communication",
"Audio, visual and computing media and services": "Communication",
"Postal services": "Communication",
"Telecommunication equipment and services": "Communication",
"Books": "Recreation and culture",
"Domestic holiday travel and accommodation": "Recreation and culture",
"Equipment for sports, camping and open-air recreation": "Recreation and culture",
"Games, toys and hobbies": "Recreation and culture",
"International holiday travel and accommodation": "Recreation and culture",
"Newspapers, magazines and stationery": "Recreation and culture",
"Other recreational, sporting and cultural services": "Recreation and culture",
"Pets and related products": "Recreation and culture",
"Sports participation": "Recreation and culture",
"Veterinary and other services for pets": "Recreation and culture",
"Preschool and primary education": "Education",
"Secondary education": "Education",
"Tertiary education": "Education",
"Insurance": "Financial"
}
export function explorer(data, citySelected, dataSelected, highlight, zoomed, catColors, highlightCat) {
// d3.wordwrap = wordwrap;
// d3.tspans = tspans;
const container = d3.select(`.inflation #graphicContainer`)
var context = d3.select(`.inflation #outer-wrapper`)
console.log("data", data)
console.log("city", citySelected)
var breaks = "no"
var isMobile;
var group = highlight.length > 1 ? true : false
var windowWidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
if (windowWidth < 610) {
isMobile = true;
}
if (windowWidth >= 610){
isMobile = false;
}
var width = document.querySelector(`.inflation #graphicContainer`).getBoundingClientRect().width
function makeIds(str) {
var new_str = str.replace(/,/g, "_")
new_str = new_str.replace(/\s+/g, '_')
new_str = new_str.replace(/,/g, '_')
new_str = new_str.replace(/\./g, '_')
return new_str
}
var height = width*0.6
var margin = {top: 10, right: 150, bottom: 20, left:40}
if (isMobile) {
height = width * 1.2
margin = {top: 10, right: 20, bottom: 20, left:40}
}
var dateParse = d3.timeParse("%Y-%m-%d")
var scaleFactor = 1
if (windowWidth < 820) {
scaleFactor = windowWidth / 860
}
width = width - margin.left - margin.right,
height = height - margin.top - margin.bottom;
context.select("#graphicContainer svg").remove();
var chartKey = context.select("#mobKey");
// chartKey.html("");
var svg = context.select("#graphicContainer").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("id", "svg")
.attr("overflow", "hidden");
var features = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var keys = Object.keys(data[0])
// console.log(keySort)
var xVar = keys[0]
keys.splice(0, 1);
var keySort = []
keys.forEach((key) => {
var row = {}
row['key'] = key
if (highlight.includes(key)) {
row['sortBy'] = 2
}
else if (key == "All groups CPI") {
row['sortBy'] = 1
}
else {
row['sortBy'] = 0
}
keySort.push(row)
})
// console.log(xVar, keys);
keySort.sort(function (a, b) {
return a.sortBy - b.sortBy;
});
var cats = ["Food","Alcohol and tobacco","Clothing","Housing","Household and services","Health","Transport","Communication","Recreation and culture","Education","Financial"]
var colors = ["#4e79a7","#f28e2c","#e15759","#76b7b2","#59a14f","#edc949","#af7aa1","#ff9da7","#9c755f","#bab0ab","#000"];
var color = d3.scaleOrdinal();
color.domain(cats).range(colors);
var x = d3.scaleTime()
.rangeRound([0, width]);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
// var color = d3.scaleOrdinal()
// .range(colors);
var lineGenerators = {};
var allValues = [];
keys.forEach(function(key,i) {
if (breaks === "yes") {
lineGenerators[key] = d3.line()
.defined(function(d) {
return d;
})
.x(function(d) {
return x(d[xVar]);
})
.y(function(d) {
return y(d[key]);
});
}
else if (breaks === "no") {
lineGenerators[key] = d3.line()
.x(function(d) {
return x(d[xVar]);
})
.y(function(d) {
return y(d[key]);
});
}
data.forEach(function(d) {
if (typeof d[key] == 'string') {
if (d[key].includes(",")) {
if (!isNaN((d[key]).replace(/,/g, ""))) {
d[key] = +(d[key]).replace(/,/g, "")
allValues.push(d[key]);
}
}
else if (d[key] != "") {
if (!isNaN(d[key])) {
d[key] = +d[key]
allValues.push(d[key]);
}
}
else if (d[key] == "") {
d[key] = null
}
}
else {
allValues.push(d[key]);
}
});
});
data.forEach(function(d) {
if (typeof d[xVar] == 'string') {
d[xVar] = dateParse(d[xVar])
}
})
var keyData = {}
keys.forEach(function(key,i) {
keyData[key] = []
data.forEach(function(d) {
if (d[key] != null) {
var newData = {}
newData[xVar] = d[xVar]
newData[key] = d[key]
keyData[key].push(newData)
}
else if (breaks == "yes") {
keyData[key].push(null)
}
});
})
allValues.sort((a, b) => a - b);
var min = d3.min(allValues)
var max = d3.max(allValues)
if (zoomed == true) {
if (dataSelected == "pct_year") {
max = 40
min = -30
// max = d3.quantile(allValues, 0.99)
// min = d3.quantile(allValues, 0.01)
console.log(max, min)
}
else if (dataSelected == "index") {
max = 250
min = 40
// max = d3.quantile(allValues, 0.99) + 5
// min = d3.quantile(allValues, 0.01) - 5
console.log(max, min)
}
}
x.domain(d3.extent(data, function(d) { return d[xVar]; }));
y.domain([min, max])
var xAxis;
var yAxis;
const xTicks = isMobile ? 4 : 6
if (isMobile) {
xAxis = d3.axisBottom(x).ticks(4)
// yAxis = d3.axisLeft(y).tickFormat(function (d) { return numberFormat(d)}).ticks(5);
yAxis = d3.axisLeft(y).ticks(5);
}
else {
xAxis = d3.axisBottom(x).ticks(6)
// yAxis = d3.axisLeft(y).tickFormat(function (d) { return numberFormat(d)});
yAxis = d3.axisLeft(y)
}
svg.append("svg:defs").append("svg:marker")
.attr("id", "arrow")
.attr("refX", 6)
.attr("refY", 6)
.attr("markerWidth", 20)
.attr("markerHeight", 20)
.attr("markerUnits","userSpaceOnUse")
.attr("orient", "auto")
.append("path")
.attr("d", "M 0 0 12 6 0 12 3 6")
.style("fill", "black")
features.append("g")
.attr("class","x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
features.append("g")
.attr("class","y")
.call(yAxis)
// features.append("text")
// .attr("transform", "rotate(-90)")
// .attr("y", 6)
// .attr("dy", "0.71em")
// .attr("fill", "#767676")
// .attr("text-anchor", "end")
// .text("Index");
// features.append("text")
// .attr("x", width)
// .attr("y", height - 6)
// .attr("fill", "#767676")
// .attr("text-anchor", "end")
// .text(details[0].xAxisLabel);
context.selectAll(".tick line")
.attr("stroke", "#767676")
context.selectAll(".tick text")
.attr("fill", "#767676")
context.selectAll(".domain")
.attr("stroke", "#767676")
const tooltipHeading = d3.select("#tooltipHeading")
const tooltipType = d3.select("#valueType")
const tooltipValue = d3.select("#value")
const tooltipDate = d3.select("#tooltipDate")
const tooltipContainer = d3.select("#tooltipContainer")
const formatDate = d3.timeFormat("%B %Y");
const getTooltipData = (event, tempData, key) => {
// console.log(tempData)
var date_slice = x.invert(d3.pointer(event)[0])
// console.log(date_slice)
var bisect = d3.bisector(d => d.date)
// const bisectX = d3.bisect((d) => d[xVar], x.invert(d3.pointer(event)[0]))
let i = bisect.right(tempData, date_slice);
// console.log(i, tempData.length)
if (i == tempData.length) {
i = tempData.length -1
}
console.log(tempData[i])
tooltipHeading.text(key)
tooltipType.text(dataSelected == "index" ? "Price index" : "% change")
tooltipValue.text(tempData[i][key])
tooltipDate.text(formatDate(tempData[i][xVar]))
tooltipContainer
.style("visibility", "visible")
if (highlight.includes(key)) {
tooltipContainer
.style("border-color", "red")
}
else if (key == "All groups CPI") {
tooltipContainer
.style("border-color", "black")
}
else {
tooltipContainer
.style("border-color", color(categories[key]))
}
}
function highlightLine(self) {
let temp_key = d3.select(self).attr("data-id")
let temp_class = d3.select(self).attr("class")
temp_class = temp_class.split(" ")[0]
console.log(temp_class)
if (highlight.includes(temp_key)) {
d3.selectAll('.chartLine').style("opacity", 0.2)
d3.selectAll('circle').style("opacity", 0.2)
d3.select(`path.${temp_class}`).attr("stroke", "red").style("opacity", 1)
d3.select(`circle.${temp_class}`).attr("fill", "red").style("opacity", 1)
d3.selectAll(`g.${temp_class}`).style("opacity", 1)
}
else if (temp_key == "All groups CPI") {
// do noting
}
else {
d3.selectAll(`g.All_groups_CPI`).style("opacity", 0.2)
d3.selectAll(`g.highlight`).style("opacity", 0)
d3.selectAll(`path.All_groups_CPI`).style("opacity", 0.7)
d3.selectAll(`path.highlight`).style("opacity", 0.7)
d3.selectAll('.chartLine').style("opacity", 0.2)
d3.selectAll('circle').style("opacity", 0.2)
// d3.selectALL(`circle`).style("opacity", 0.2)
// d3.selectAll(`.lineLabels`).style("opacity", 0.2)
d3.select(`path.${temp_class}`).attr("stroke", color(categories[temp_key])).style("opacity", 1)
d3.select(`circle.${temp_class}`).attr("fill", color(categories[temp_key])).style("opacity", 1)
d3.selectAll(`g.${temp_class}`).style("color", color(categories[temp_key])).style("opacity", 1)
}
}
function fadeLine(d,i) {
let temp_key = d3.select(this).attr("data-id")
let temp_class = d3.select(this).attr("class")
temp_class = temp_class.split(" ")[0]
console.log(temp_class)
if (highlight.includes(temp_key)) {
if (group) {
d3.selectAll(`g.highlight`).style("opacity", 0)
}
else {
d3.selectAll(`g.highlight`).style("opacity", 1)
}
d3.selectAll(`path.highlight`).style("opacity", 1)
d3.selectAll(`circle.highlight`).style("opacity", 1)
}
else if (temp_key == "All groups CPI") {
// do noting
}
else {
// d3.selectAll(`path.chartLine`).style("opacity", 0.2)
// d3.select(`circle`).style("opacity", 0.2)
// d3.selectAll(`.lineLabels`).style("opacity", 0.2)
d3.selectAll('circle').style("opacity", 0.4)
d3.selectAll('.chartLine').style("opacity", 0.4)
d3.selectAll(`path.All_groups_CPI`).style("opacity", 1)
d3.selectAll(`path.highlight`).style("opacity", 1)
d3.selectAll(`g.All_groups_CPI`).style("opacity", 1)
if (group) {
d3.selectAll(`g.highlight`).style("opacity", 0)
}
else {
d3.selectAll(`g.highlight`).style("opacity", 1)
}
d3.selectAll(`circle.All_groups_CPI`).style("opacity", 1)
d3.selectAll(`circle.highlight`).style("opacity", 1)
d3.select(`path.${temp_class}`).attr("stroke", "#bdbdbd").style("opacity", 0.4)
d3.select(`circle.${temp_class}`).attr("fill", "#bdbdbd").style("opacity", 0.4)
d3.selectAll(`g.${temp_class}`).style("opacity", 0)
}
tooltipContainer.style("visibility", "hidden")
}
keySort.forEach(function(key,i) {
// console.log(key)
var lineOpacity = 0.4
var lineColour = "#bdbdbd"
if (catColors) {
lineColour = color(categories[key.key])
}
var labelOpacity = 0
var highlightClass = ""
if (key.sortBy === 2) {
lineOpacity = 1;
lineColour = "red";
if (group) {
labelOpacity = 0;
}
else {
labelOpacity = 1;
}
highlightClass = "highlight"
}
else if (key.sortBy === 1) {
lineOpacity = 1;
labelOpacity = 1;
lineColour = "#000";
}
features.append("path")
.datum(keyData[key.key])
.attr("fill", "none")
.attr("data-id", key.key)
.attr("stroke", lineColour)
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 3)
.attr("class", makeIds(key.key) + " chartLine " + highlightClass)
.style("opacity", lineOpacity)
// .on('mouseover', highlightLine)
.on('mouseout', fadeLine)
.on('mouseover', function(e, d) {
var self = this
highlightLine(self)
getTooltipData(e, d, key.key)
// console.log(tooltipData)
})
.attr("d", lineGenerators[key.key]);
features
.append("circle")
.datum(keyData[key.key])
.attr("data-id", key.key)
.attr("cy", (d) => {
return y(keyData[key.key][keyData[key.key].length - 1][key.key])
})
.attr("fill", lineColour)
.attr("cx", (d) => {
return x(keyData[key.key][keyData[key.key].length - 1][xVar])
})
.attr("r", 4)
.attr("class", makeIds(key.key) + " " + highlightClass)
.on('mouseover', function(e, d) {
var self = this
highlightLine(self)
getTooltipData(e, d, key.key)
// console.log(tooltipData)
})
.on('mouseout', fadeLine)
.style("opacity", lineOpacity)
// features
// .append("text")
// .attr("data-id", key.key)
// .attr("class", makeIds(key.key) + " lineLabels " + highlightClass)
// .attr("y", (d) => {
// return (
// y(keyData[key.key][keyData[key.key].length - 1][key.key]) +
// 4
// )
// })
// .attr("x", (d) => {
// return (
// x(keyData[key.key][keyData[key.key].length - 1][xVar]) + 5
// )
// })
// .attr("fill", "none")
// .attr("stroke", "#FFF")
// .attr("stroke-width", 5)
// .attr("opacity", labelOpacity)
// // .text((d) => {
// // return key.key
// // })
// .tspans(function(d) {
// return d3.wordwrap(key.key, 15); // break line after 15 characters
// })
if (!isMobile) {
features
.append("g")
.attr("class", makeIds(key.key) + " labelWrapper " + highlightClass)
.attr("data-id", key.key)
.style("opacity", labelOpacity)
.style("color", lineColour)
.append("text")
.attr("data-id", key.key)
.attr("class", makeIds(key.key) + " lineLabels " + highlightClass)
.attr("y", (d) => {
var yVal = y(keyData[key.key][keyData[key.key].length - 1][key.key]) - 10
if (yVal < 0) {
yVal = 0
}
else if (yVal > height) {
return height - 50
}
return yVal
})
.attr("x", (d) => {
return (
x(keyData[key.key][keyData[key.key].length - 1][xVar]) + 5
)
})
.style("opacity", labelOpacity)
.attr("fill", lineColour)
.text((d) => {
return key.key
})
}
});
if (!isMobile) {
var wrap = textwrap().bounds({height: 100, width: 145});
d3.selectAll('.lineLabels').call(wrap);
}
if (isMobile) {
chartKey.html("")
const keyDiv1 = chartKey.append("div").attr("class", "keyDiv")
keyDiv1
.append("span")
.attr("class", "keyCircle")
.style("background-color", "red")
keyDiv1.append("span").attr("class", "keyText").text(highlightCat)
const keyDiv2 = chartKey.append("div").attr("class", "keyDiv")
keyDiv2
.append("span")
.attr("class", "keyCircle")
.style("background-color", "black")
keyDiv2.append("span").attr("class", "keyText").text("All groups CPI")
}
// d3.selectAll(".lineLabels").text(function(d) {
// tspans("blah")
// })
// d3.selectAll(".lineLabels tspan")
// .attr("x", (d) => {
// return (
// x(keyData[key.key][keyData[key.key].length - 1][xVar]) + 5
// )
// })
// var wrap = d3.textwrap().bounds({height: 100, width: 150});
// // wrap all text
// d3.selectAll('.lineLabels').call(wrap);
}