Published
Edited
Feb 26, 2022
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
ccc = {
const container = html`<div style="width: 1000px; height:${chartHeight}px;"/>`;
echarts.registerTransform(ecStat.transform.regression);
echarts.init(container).setOption({
...timeChartOption,
title: { text: `Daily ${currency} Price` },
dataset: [{
source: dataMap.numbers.map((d, i) => [i, d[1]])
}, {
transform: {
type: 'ecStat:regression',
config: { method: 'polynomial', order: 10 }
}
}],
yAxis: {
...timeChartOption.yAxis,
type: !!useLog.length ? 'log' : 'value'
},
series: [{
name: `Daily ${currency} Price`,
type: 'k',
...minMaxPins,
data: dataMap.numbers
}, {
type: 'line',
datasetIndex: 1,
color: '#000',
lineStyle: { opacity: 0.2, width: 2 },
tooltip: { show: false },
showSymbol: false
}]
});
return container;
};
Insert cell
Insert cell
{
const container = html`<div style="width: 1000px; height:${chartHeight}px;"/>`
const data = dataMap.change.map(d => d[1])
echarts.init(container).setOption({
...timeChartOption,
visualMap: [{
show: false,
color: ['green', 'red'],
type: 'continuous',
seriesIndex: 0,
min: Math.min(...data) / 2,
max: Math.max(...data) / 2
}],
title: { text: `Daily Change in ${currency} Price` },
series: [{
name: `total change of daily ${currency} price`,
type: 'line',
...minMaxPins,
lineStyle: { width: 1 },
showSymbol: false,
data
}]
});
return container
};
Insert cell
{
const container = html`<div style="width: 1000px; height:${chartHeight}px;"/>`
const data = dataMap.pctChange.map(d => d[1])
echarts.init(container).setOption({
...timeChartOption,
visualMap: [{
show: false,
color: ['green', 'red'],
type: 'continuous',
seriesIndex: 0,
min: Math.min(...data) / 2,
max: Math.max(...data) / 2
}],
yAxis: {
name: '% change',
axisLabel: {
formatter: val => val * 100 + '%'
},
axisPointer: {
label: {
formatter: ({ value }) => (value * 100).toFixed(1) + '%'
}
}
},
title: { text: 'Daily %Change in ${currency} Price' },
series: [{
name: 'relative change of daily ${currency} price',
type: 'line',
...minMaxPins,
lineStyle: { width: 1 },
showSymbol: false,
data
}]
});
return container
};
Insert cell
Insert cell
Insert cell
{
const container = html`<div style="width: 1000px; height:${chartHeight}px;"/>`
echarts.init(container).setOption({
...getBoxplotOption('dayOfMonthPivot'),
title: { text: `${currency} prices by day of the month` }
})
return container
};
Insert cell
{
const container = html`<div style="width: 1000px; height:${chartHeight}px;"/>`
echarts.init(container).setOption({
...getBoxplotOption('changePivot'),
title: { text: `${currency} price change in total by day of the month` }
})
return container
};
Insert cell
{
const container = html`<div style="width: 1000px; height:${chartHeight}px;"/>`
echarts.init(container).setOption({
...getBoxplotOption('changePctPivot'),
title: { text: `${currency} price change in percent by day of the month` },
yAxis: {
name: '% change',
axisLabel: {
formatter: val => val * 100 + '%'
}
}
})
return container
};
Insert cell
Insert cell
data = ({
Bitcoin: {
data: await FileAttachment('BTC.csv').csv(),
source: 'https://finance.yahoo.com/quote/BTC-USD/history?p=BTC-USD'
},
Ethereum: {
data: await FileAttachment('ETH.csv').csv(),
source: 'https://finance.yahoo.com/quote/ETH-USD/history?p=ETH-USD'
}
})
Insert cell
dataMap = {
const dayOfMonthPivot = new Map()
const changePivot = new Map()
const changePctPivot = new Map()
const dates = []
const numbers = []
const change = []
const pctChange = []
data[currency].data
.filter(d => {
if (d.Open === 'null') return false // why are there null values in the dataset?
const yearInt = parseInt(d.Date.split('-')[0])
return yearInt >= slider[0] && yearInt <= slider[1]
})
.map(d => ({
Date: d.Date,
Open: parseFloat(d.Open),
Close: parseFloat(d.Close),
Low: parseFloat(d.Low),
High: parseFloat(d.High),
}))
.forEach((d, i, a) => {
dates.push(d.Date)
numbers.push([d.Open, d.Close, d.Low, d.High])
change.push(getChange(d, a[i - 1]))
pctChange.push(getPctChange(d, a[i - 1]))
populatePivot(new Date(d.Date).getDate(), dayOfMonthPivot, d)
const c = getChange(d, a[i - 1])
populatePivot(new Date(d.Date).getDate(), changePivot, { Low: c[0], High: c[1] })
const cpct = getPctChange(d, a[i - 1])
populatePivot(new Date(d.Date).getDate(), changePctPivot, { Low: cpct[0], High: cpct[1] })
})
return {
dates,
numbers,
change,
pctChange,
changePivot: new Map([...changePivot.entries()].sort((a, b) => a[0] - b[0])),
changePctPivot: new Map([...changePctPivot.entries()].sort((a, b) => a[0] - b[0])),
dayOfMonthPivot: new Map([...dayOfMonthPivot.entries()].sort((a, b) => a[0] - b[0]))
};
}
Insert cell
getChange = (d, prevD = d) => {
return [d.Open - prevD.Open, d.Close - prevD.Close, d.Low - prevD.Low, d.High - prevD.High]
}
Insert cell
getPctChange = (d, prevD = d) => {
const getPct = key => (d[key] - prevD[key]) / prevD[key]
return [getPct('Open'), getPct('Close'), getPct('Low'), getPct('High')]
}
Insert cell
populatePivot = (key, pivot, d) => {
if(!pivot.has(key)) {
pivot.set(key, [])
}
pivot.get(key).push(d.Low, d.High)
}
Insert cell
timeChartOption = ({
dataZoom: [
{
type: 'inside',
start: 0,
end: 100
},
{
start: 0,
end: 100
}
],
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
}
},
axisPointer: {
link: { xAxisIndex: 'all' },
label: {
backgroundColor: '#777'
}
},
xAxis: {
data: dataMap.dates
},
yAxis: { name: 'Price USD' },
legend: null,
})
Insert cell
getScatterOption = name => {
const data = []
const entries = Array.from(dataMap[name].entries())
entries.forEach(e => {
e[1].forEach(v => {
data.push([e[0], v])
})
})
return {
xAxis: { type: 'category' },
yAxis: { name: 'Price USD' },
color: ['black'],
series: [
{
type: 'scatter',
symbolSize: 5,
data
}
]
}
}
Insert cell
getBoxplotOption = name => ({
dataset: [{
source: Array.from(dataMap[name].values()),
}, {
transform: {
type: 'boxplot',
config: { itemNameFormatter: params => Array.from(dataMap[name].keys())[params.value] }
}
}, {
fromDatasetIndex: 1,
fromTransformResult: 1
}],
dataZoom: [
{
type: 'inside',
filterMode: 'weakFilter',
start: 0,
end: 100,
yAxisIndex: 0
}
],
tooltip: {
trigger: 'axis',
confine: true,
axisPointer: {
type: 'shadow'
},
formatter: a => {
const v = a[0].value
return `<b>Day of Month</b>: ${v[0]} <br />
<b>min</b>: ${v[1].toFixed(4)}<br />
<b>q1</b>: ${v[2].toFixed(4)}<br />
<b>median</b>: ${v[3].toFixed(4)}<br />
<b>q3</b>: ${v[4].toFixed(4)}<br />
<b>max</b>: ${v[5].toFixed(4)}`
}
},
xAxis: { type: 'category' },
yAxis: { name: 'Price USD' },
color: ['black'],
series: [
{
name: name,
type: 'boxplot',
datasetIndex: 1
},
{
name: 'outliers',
type: showOutliers[0] ? 'scatter' : null,
symbolSize: 5,
datasetIndex: 2,
tooltip: { show: false },
itemStyle: { opacity: .3 }
}
]
})
Insert cell
minMaxPins = (
{
markPoint: {
data: [
{
type: 'max',
name: 'max',
symbolSize: 25,
label: { position: 'top', formatter: a => `${a.name}: ${a.value.toFixed(2)}` },
itemStyle: { color: 'green', opacity: 0.7 }
},
{
type: 'min',
name: 'min',
symbolSize: 25,
label: { position: 'top', formatter: a => `${a.name}: ${a.value.toFixed(2)}` },
itemStyle: { color: 'red', opacity: 0.7 }
}
]
},
markLine: {
data: [
{
type: 'average',
name: 'avg',
symbol: 'none',
label: { position: 'start', formatter: '{b}: {c}' },
lineStyle: { color: 'black', opacity: 0.7 }
}
]
}
}
)
Insert cell
echarts = require("https://cdn.jsdelivr.net/npm/echarts@latest/dist/echarts.min.js")
Insert cell
ecStat = require("https://cdn.jsdelivr.net/npm/echarts-stat@latest/dist/ecStat.min.js")
Insert cell
import { rangeSlider } from '@mootari/range-slider'
Insert cell
import {Checkbox, Range} from "@observablehq/inputs"
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