Compare commits
6 commits
bf278259e2
...
812e61ed67
Author | SHA1 | Date | |
---|---|---|---|
xenofem | 812e61ed67 | ||
xenofem | 2e3538356c | ||
xenofem | f6da7a7642 | ||
xenofem | e54bf1350c | ||
xenofem | 1662b70992 | ||
xenofem | bac914d731 |
1105
Cargo.lock
generated
1105
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -15,7 +15,7 @@ futures = "0.3"
|
||||||
json = "0.12.4"
|
json = "0.12.4"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
log = "0.4.16"
|
log = "0.4.16"
|
||||||
pdf = "0.7.2"
|
pdf = "0.8"
|
||||||
regex = "1.5.5"
|
regex = "1.5.5"
|
||||||
reqwest = { version = "0.11", features = ["rustls-tls", "stream"], default-features = false }
|
reqwest = { version = "0.11", features = ["rustls-tls", "stream"], default-features = false }
|
||||||
scraper = "0.12"
|
scraper = "0.12"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use pdf::{backend::Backend, content::Operation, primitive::Primitive};
|
use pdf::{backend::Backend, content::{Op, Point, TextDrawAdjusted}};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use time::Date;
|
use time::Date;
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ impl DataSet {
|
||||||
if let Some(text) = doc_iter.next() {
|
if let Some(text) = doc_iter.next() {
|
||||||
if is_new_column_header(&text.text) {
|
if is_new_column_header(&text.text) {
|
||||||
let mut column_name = text.text;
|
let mut column_name = text.text;
|
||||||
let column_x = text.x;
|
let column_x = text.point.x;
|
||||||
while let Some(more) = doc_iter.peek() {
|
while let Some(more) = doc_iter.peek() {
|
||||||
if is_new_column_header(&more.text) || DATE_REGEX.is_match(&more.text) {
|
if is_new_column_header(&more.text) || DATE_REGEX.is_match(&more.text) {
|
||||||
columns.push((Arc::new(column_name), column_x));
|
columns.push((Arc::new(column_name), column_x));
|
||||||
|
@ -51,7 +51,7 @@ impl DataSet {
|
||||||
} else if DATE_REGEX.is_match(&text.text) {
|
} else if DATE_REGEX.is_match(&text.text) {
|
||||||
break (
|
break (
|
||||||
DataPoint::new(&text).expect("Failed to parse date!"),
|
DataPoint::new(&text).expect("Failed to parse date!"),
|
||||||
text.y,
|
text.point.y,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -67,12 +67,12 @@ impl DataSet {
|
||||||
&mut current_datapoint,
|
&mut current_datapoint,
|
||||||
DataPoint::new(&text).expect("Failed to parse date!"),
|
DataPoint::new(&text).expect("Failed to parse date!"),
|
||||||
));
|
));
|
||||||
current_y = text.y;
|
current_y = text.point.y;
|
||||||
} else if VALUE_REGEX.is_match(&text.text) {
|
} else if VALUE_REGEX.is_match(&text.text) {
|
||||||
if (current_y - text.y).abs() > POSITION_ERROR_MARGIN {
|
if (current_y - text.point.y).abs() > POSITION_ERROR_MARGIN {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some((column, _)) = columns.iter().rev().find(|(_, x)| *x < text.x) {
|
if let Some((column, _)) = columns.iter().rev().find(|(_, x)| *x < text.point.x) {
|
||||||
current_datapoint.values.insert(
|
current_datapoint.values.insert(
|
||||||
column.clone(),
|
column.clone(),
|
||||||
text.text.parse().expect("Failed to parse value!"),
|
text.text.parse().expect("Failed to parse value!"),
|
||||||
|
@ -103,26 +103,23 @@ impl DataPoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DocumentText {
|
struct DocumentText {
|
||||||
x: f32,
|
point: Point,
|
||||||
y: f32,
|
|
||||||
text: String,
|
text: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DocumentIterator<'a> {
|
struct DocumentIterator<'a> {
|
||||||
x: f32,
|
point: Point,
|
||||||
y: f32,
|
operations: Box<dyn Iterator<Item = Op> + 'a>,
|
||||||
operations: Box<dyn Iterator<Item = Operation> + 'a>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> DocumentIterator<'a> {
|
impl<'a> DocumentIterator<'a> {
|
||||||
fn new<B: Backend>(document: &'a pdf::file::File<B>) -> Self {
|
fn new<B: Backend>(document: &'a pdf::file::File<B>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
x: 0.0,
|
point: Point { x: 0.0, y: 0.0 },
|
||||||
y: 0.0,
|
|
||||||
operations: Box::new(
|
operations: Box::new(
|
||||||
document
|
document
|
||||||
.pages()
|
.pages()
|
||||||
.filter_map(|page| Some(page.ok()?.contents.clone()?.operations.into_iter()))
|
.filter_map(|page| Some(page.ok()?.contents.clone()?.operations(document).ok()?.into_iter()))
|
||||||
.flatten(),
|
.flatten(),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
@ -133,54 +130,44 @@ impl<'a> Iterator for DocumentIterator<'a> {
|
||||||
type Item = DocumentText;
|
type Item = DocumentText;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
for Operation { operator, operands } in self.operations.as_mut() {
|
for operation in self.operations.as_mut() {
|
||||||
if operator == "Tm" {
|
match operation {
|
||||||
if let (Some(x), Some(y)) = (
|
Op::SetTextMatrix { matrix } => {
|
||||||
operands.get(4).and_then(extract_number),
|
self.point = Point { x: matrix.e, y: matrix.f };
|
||||||
operands.get(5).and_then(extract_number),
|
|
||||||
) {
|
|
||||||
self.x = x;
|
|
||||||
self.y = y;
|
|
||||||
}
|
}
|
||||||
} else if operator == "TJ" || operator == "Tj" {
|
Op::TextDraw { text } => {
|
||||||
if let Some(text) = operands.get(0).and_then(extract_string) {
|
if let Ok(text) = text.to_string() {
|
||||||
return Some(DocumentText {
|
return Some(DocumentText {
|
||||||
x: self.x,
|
point: self.point,
|
||||||
y: self.y,
|
|
||||||
text,
|
text,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Op::TextDrawAdjusted { array } => {
|
||||||
|
if let Some(text) = concatenate_adjusted_text(array) {
|
||||||
|
return Some(DocumentText {
|
||||||
|
point: self.point,
|
||||||
|
text,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_number(p: &Primitive) -> Option<f32> {
|
fn concatenate_adjusted_text(array: Vec<TextDrawAdjusted>) -> Option<String> {
|
||||||
match p {
|
|
||||||
Primitive::Number(n) => Some(*n),
|
|
||||||
Primitive::Integer(n) => Some(*n as f32),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_string(p: &Primitive) -> Option<String> {
|
|
||||||
let result: Box<dyn AsRef<str>> = match p {
|
|
||||||
Primitive::Array(array) => {
|
|
||||||
let mut acc = String::new();
|
let mut acc = String::new();
|
||||||
for element in array.iter() {
|
for element in array.iter() {
|
||||||
if let Primitive::String(s) = element {
|
if let TextDrawAdjusted::Text(s) = element {
|
||||||
acc += s.as_str().ok()?.as_ref();
|
acc += s.to_string().ok()?.as_ref();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Box::new(acc)
|
|
||||||
}
|
|
||||||
Primitive::String(s) => Box::new(s.as_str().ok()?),
|
|
||||||
_ => return None,
|
|
||||||
};
|
};
|
||||||
Some(
|
Some(
|
||||||
WHITESPACE_REGEX
|
WHITESPACE_REGEX
|
||||||
.replace_all((*result).as_ref().trim(), " ")
|
.replace_all(acc.trim(), " ")
|
||||||
.to_string(),
|
.to_string(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
13269
static/chart.js
13269
static/chart.js
File diff suppressed because it is too large
Load diff
14
static/chart.umd.js
Normal file
14
static/chart.umd.js
Normal file
File diff suppressed because one or more lines are too long
1
static/chart.umd.js.map
Normal file
1
static/chart.umd.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
7
static/chartjs-adapter-date-fns.bundle.min.js
vendored
Normal file
7
static/chartjs-adapter-date-fns.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,606 +0,0 @@
|
||||||
/**
|
|
||||||
* chartjs-chart-error-bars
|
|
||||||
* https://github.com/sgratzl/chartjs-chart-error-bars
|
|
||||||
*
|
|
||||||
* Copyright (c) 2021 Samuel Gratzl <samu@sgratzl.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function (global, factory) {
|
|
||||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('chart.js'), require('chart.js/helpers')) :
|
|
||||||
typeof define === 'function' && define.amd ? define(['exports', 'chart.js', 'chart.js/helpers'], factory) :
|
|
||||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ChartErrorBars = {}, global.Chart, global.Chart.helpers));
|
|
||||||
})(this, (function (exports, chart_js, helpers) { 'use strict';
|
|
||||||
|
|
||||||
const allModelKeys = ['xMin', 'xMax', 'yMin', 'yMax'];
|
|
||||||
function modelKeys(horizontal) {
|
|
||||||
return (horizontal ? allModelKeys.slice(0, 2) : allModelKeys.slice(2));
|
|
||||||
}
|
|
||||||
function calculateScale(properties, data, index, scale, reset) {
|
|
||||||
const keys = [`${scale.axis}Min`, `${scale.axis}Max`];
|
|
||||||
const base = scale.getBasePixel();
|
|
||||||
for (const key of keys) {
|
|
||||||
const v = data[key];
|
|
||||||
if (Array.isArray(v)) {
|
|
||||||
properties[key] = v.map((d) => (reset ? base : scale.getPixelForValue(d, index)));
|
|
||||||
}
|
|
||||||
else if (typeof v === 'number') {
|
|
||||||
properties[key] = reset ? base : scale.getPixelForValue(v, index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function calculatePolarScale(properties, data, scale, reset, options) {
|
|
||||||
const animationOpts = options.animation;
|
|
||||||
const keys = [`${scale.axis}Min`, `${scale.axis}Max`];
|
|
||||||
const toAngle = (v) => {
|
|
||||||
const valueRadius = scale.getDistanceFromCenterForValue(v);
|
|
||||||
const resetRadius = animationOpts.animateScale ? 0 : valueRadius;
|
|
||||||
return reset ? resetRadius : valueRadius;
|
|
||||||
};
|
|
||||||
for (const key of keys) {
|
|
||||||
const v = data[key];
|
|
||||||
if (Array.isArray(v)) {
|
|
||||||
properties[key] = v.map(toAngle);
|
|
||||||
}
|
|
||||||
else if (typeof v === 'number') {
|
|
||||||
properties[key] = toAngle(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const errorBarDefaults = {
|
|
||||||
errorBarLineWidth: { v: [1, 3] },
|
|
||||||
errorBarColor: { v: ['#2c2c2c', '#1f1f1f'] },
|
|
||||||
errorBarWhiskerLineWidth: { v: [1, 3] },
|
|
||||||
errorBarWhiskerRatio: { v: [0.2, 0.25] },
|
|
||||||
errorBarWhiskerSize: { v: [20, 24] },
|
|
||||||
errorBarWhiskerColor: { v: ['#2c2c2c', '#1f1f1f'] },
|
|
||||||
};
|
|
||||||
const errorBarDescriptors = {
|
|
||||||
_scriptable: true,
|
|
||||||
_indexable: (name) => name !== 'v',
|
|
||||||
};
|
|
||||||
const styleKeys = Object.keys(errorBarDefaults);
|
|
||||||
function resolveMulti(vMin, vMax) {
|
|
||||||
const vMinArr = Array.isArray(vMin) ? vMin : [vMin];
|
|
||||||
const vMaxArr = Array.isArray(vMax) ? vMax : [vMax];
|
|
||||||
if (vMinArr.length === vMaxArr.length) {
|
|
||||||
return vMinArr.map((v, i) => [v, vMaxArr[i]]);
|
|
||||||
}
|
|
||||||
const max = Math.max(vMinArr.length, vMaxArr.length);
|
|
||||||
return Array(max).map((_, i) => [vMinArr[i % vMinArr.length], vMaxArr[i % vMaxArr.length]]);
|
|
||||||
}
|
|
||||||
function resolveOption(val, index) {
|
|
||||||
if (typeof val === 'string' || typeof val === 'number') {
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
const v = Array.isArray(val) ? val : val.v;
|
|
||||||
return v[index % v.length];
|
|
||||||
}
|
|
||||||
function calculateHalfSize(total, options, i) {
|
|
||||||
const ratio = resolveOption(options.errorBarWhiskerRatio, i);
|
|
||||||
if (total != null && ratio > 0) {
|
|
||||||
return total * ratio * 0.5;
|
|
||||||
}
|
|
||||||
const size = resolveOption(options.errorBarWhiskerSize, i);
|
|
||||||
return size * 0.5;
|
|
||||||
}
|
|
||||||
function drawErrorBarVertical(props, vMin, vMax, options, ctx) {
|
|
||||||
ctx.save();
|
|
||||||
ctx.translate(props.x, 0);
|
|
||||||
const bars = resolveMulti(vMin == null ? props.y : vMin, vMax == null ? props.y : vMax);
|
|
||||||
bars.reverse().forEach(([mi, ma], j) => {
|
|
||||||
const i = bars.length - j - 1;
|
|
||||||
const halfWidth = calculateHalfSize(props.width, options, i);
|
|
||||||
ctx.lineWidth = resolveOption(options.errorBarLineWidth, i);
|
|
||||||
ctx.strokeStyle = resolveOption(options.errorBarColor, i);
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(0, mi);
|
|
||||||
ctx.lineTo(0, ma);
|
|
||||||
ctx.stroke();
|
|
||||||
ctx.lineWidth = resolveOption(options.errorBarWhiskerLineWidth, i);
|
|
||||||
ctx.strokeStyle = resolveOption(options.errorBarWhiskerColor, i);
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(-halfWidth, mi);
|
|
||||||
ctx.lineTo(halfWidth, mi);
|
|
||||||
ctx.moveTo(-halfWidth, ma);
|
|
||||||
ctx.lineTo(halfWidth, ma);
|
|
||||||
ctx.stroke();
|
|
||||||
});
|
|
||||||
ctx.restore();
|
|
||||||
}
|
|
||||||
function drawErrorBarHorizontal(props, vMin, vMax, options, ctx) {
|
|
||||||
ctx.save();
|
|
||||||
ctx.translate(0, props.y);
|
|
||||||
const bars = resolveMulti(vMin == null ? props.x : vMin, vMax == null ? props.x : vMax);
|
|
||||||
bars.reverse().forEach(([mi, ma], j) => {
|
|
||||||
const i = bars.length - j - 1;
|
|
||||||
const halfHeight = calculateHalfSize(props.height, options, i);
|
|
||||||
ctx.lineWidth = resolveOption(options.errorBarLineWidth, i);
|
|
||||||
ctx.strokeStyle = resolveOption(options.errorBarColor, i);
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(mi, 0);
|
|
||||||
ctx.lineTo(ma, 0);
|
|
||||||
ctx.stroke();
|
|
||||||
ctx.lineWidth = resolveOption(options.errorBarWhiskerLineWidth, i);
|
|
||||||
ctx.strokeStyle = resolveOption(options.errorBarWhiskerColor, i);
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(mi, -halfHeight);
|
|
||||||
ctx.lineTo(mi, halfHeight);
|
|
||||||
ctx.moveTo(ma, -halfHeight);
|
|
||||||
ctx.lineTo(ma, halfHeight);
|
|
||||||
ctx.stroke();
|
|
||||||
});
|
|
||||||
ctx.restore();
|
|
||||||
}
|
|
||||||
function renderErrorBar(elem, ctx) {
|
|
||||||
var _a, _b, _c, _d;
|
|
||||||
const props = elem.getProps(['x', 'y', 'width', 'height', 'xMin', 'xMax', 'yMin', 'yMax']);
|
|
||||||
if (props.xMin != null || props.xMax != null) {
|
|
||||||
drawErrorBarHorizontal(props, (_a = props.xMin) !== null && _a !== void 0 ? _a : null, (_b = props.xMax) !== null && _b !== void 0 ? _b : null, elem.options, ctx);
|
|
||||||
}
|
|
||||||
if (props.yMin != null || props.yMax != null) {
|
|
||||||
drawErrorBarVertical(props, (_c = props.yMin) !== null && _c !== void 0 ? _c : null, (_d = props.yMax) !== null && _d !== void 0 ? _d : null, elem.options, ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function drawErrorBarArc(props, vMin, vMax, options, ctx) {
|
|
||||||
ctx.save();
|
|
||||||
ctx.translate(props.x, props.y);
|
|
||||||
const angle = (props.startAngle + props.endAngle) / 2;
|
|
||||||
const cosAngle = Math.cos(angle);
|
|
||||||
const sinAngle = Math.sin(angle);
|
|
||||||
const v = {
|
|
||||||
x: -sinAngle,
|
|
||||||
y: cosAngle,
|
|
||||||
};
|
|
||||||
const length = Math.sqrt(v.x * v.x + v.y * v.y);
|
|
||||||
v.x /= length;
|
|
||||||
v.y /= length;
|
|
||||||
const bars = resolveMulti(vMin !== null && vMin !== void 0 ? vMin : props.outerRadius, vMax !== null && vMax !== void 0 ? vMax : props.outerRadius);
|
|
||||||
bars.reverse().forEach(([mi, ma], j) => {
|
|
||||||
const i = bars.length - j - 1;
|
|
||||||
const minCos = mi * cosAngle;
|
|
||||||
const minSin = mi * sinAngle;
|
|
||||||
const maxCos = ma * cosAngle;
|
|
||||||
const maxSin = ma * sinAngle;
|
|
||||||
const halfHeight = calculateHalfSize(null, options, i);
|
|
||||||
const eX = v.x * halfHeight;
|
|
||||||
const eY = v.y * halfHeight;
|
|
||||||
ctx.lineWidth = resolveOption(options.errorBarLineWidth, i);
|
|
||||||
ctx.strokeStyle = resolveOption(options.errorBarColor, i);
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(minCos, minSin);
|
|
||||||
ctx.lineTo(maxCos, maxSin);
|
|
||||||
ctx.stroke();
|
|
||||||
ctx.lineWidth = resolveOption(options.errorBarWhiskerLineWidth, i);
|
|
||||||
ctx.strokeStyle = resolveOption(options.errorBarWhiskerColor, i);
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(minCos + eX, minSin + eY);
|
|
||||||
ctx.lineTo(minCos - eX, minSin - eY);
|
|
||||||
ctx.moveTo(maxCos + eX, maxSin + eY);
|
|
||||||
ctx.lineTo(maxCos - eX, maxSin - eY);
|
|
||||||
ctx.stroke();
|
|
||||||
});
|
|
||||||
ctx.restore();
|
|
||||||
}
|
|
||||||
function renderErrorBarArc(elem, ctx) {
|
|
||||||
const props = elem.getProps(['x', 'y', 'startAngle', 'endAngle', 'rMin', 'rMax', 'outerRadius']);
|
|
||||||
if (props.rMin != null || props.rMax != null) {
|
|
||||||
drawErrorBarArc(props, props.rMin, props.rMax, elem.options, ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BarWithErrorBar extends chart_js.BarElement {
|
|
||||||
draw(ctx) {
|
|
||||||
super.draw(ctx);
|
|
||||||
renderErrorBar(this, ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BarWithErrorBar.id = 'barWithErrorBar';
|
|
||||||
BarWithErrorBar.defaults = { ...chart_js.BarElement.defaults, ...errorBarDefaults };
|
|
||||||
BarWithErrorBar.defaultRoutes = chart_js.BarElement.defaultRoutes;
|
|
||||||
BarWithErrorBar.descriptors = errorBarDescriptors;
|
|
||||||
|
|
||||||
class PointWithErrorBar extends chart_js.PointElement {
|
|
||||||
draw(ctx, area) {
|
|
||||||
super.draw.call(this, ctx, area);
|
|
||||||
renderErrorBar(this, ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PointWithErrorBar.id = 'pointWithErrorBar';
|
|
||||||
PointWithErrorBar.defaults = { ...chart_js.PointElement.defaults, ...errorBarDefaults };
|
|
||||||
PointWithErrorBar.defaultRoutes = chart_js.PointElement.defaultRoutes;
|
|
||||||
PointWithErrorBar.descriptors = errorBarDescriptors;
|
|
||||||
|
|
||||||
class ArcWithErrorBar extends chart_js.ArcElement {
|
|
||||||
draw(ctx) {
|
|
||||||
super.draw(ctx);
|
|
||||||
renderErrorBarArc(this, ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ArcWithErrorBar.id = 'arcWithErrorBar';
|
|
||||||
ArcWithErrorBar.defaults = { ...chart_js.ArcElement.defaults, ...errorBarDefaults };
|
|
||||||
ArcWithErrorBar.defaultRoutes = chart_js.ArcElement.defaultRoutes;
|
|
||||||
ArcWithErrorBar.descriptors = errorBarDescriptors;
|
|
||||||
|
|
||||||
function reverseOrder(v) {
|
|
||||||
return Array.isArray(v) ? v.slice().reverse() : v;
|
|
||||||
}
|
|
||||||
function generateBarTooltip(item) {
|
|
||||||
const keys = modelKeys(item.element.horizontal);
|
|
||||||
const base = chart_js.Tooltip.defaults.callbacks.label.call(this, item);
|
|
||||||
const v = item.chart.data.datasets[item.datasetIndex].data[item.dataIndex];
|
|
||||||
if (v == null || keys.every((k) => v[k] == null)) {
|
|
||||||
return base;
|
|
||||||
}
|
|
||||||
return `${base} (${reverseOrder(v[keys[0]])} .. ${v[keys[1]]})`;
|
|
||||||
}
|
|
||||||
function generateTooltipScatter(item) {
|
|
||||||
const v = item.chart.data.datasets[item.datasetIndex].data[item.dataIndex];
|
|
||||||
const subLabel = (base, horizontal) => {
|
|
||||||
const keys = modelKeys(horizontal);
|
|
||||||
if (v == null || keys.every((k) => v[k] == null)) {
|
|
||||||
return base;
|
|
||||||
}
|
|
||||||
return `${base} [${reverseOrder(v[keys[0]])} .. ${v[keys[1]]}]`;
|
|
||||||
};
|
|
||||||
return `(${subLabel(item.label, true)}, ${subLabel(item.parsed.y, false)})`;
|
|
||||||
}
|
|
||||||
function generateTooltipPolar(item) {
|
|
||||||
const base = chart_js.PolarAreaController.overrides.plugins.tooltip.callbacks.label.call(this, item);
|
|
||||||
const v = item.chart.data.datasets[item.datasetIndex].data[item.dataIndex];
|
|
||||||
const keys = ['rMin', 'rMax'];
|
|
||||||
if (v == null || keys.every((k) => v[k] == null)) {
|
|
||||||
return base;
|
|
||||||
}
|
|
||||||
return `${base} [${reverseOrder(v[keys[0]])} .. ${v[keys[1]]}]`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const interpolators = {
|
|
||||||
color(from, to, factor) {
|
|
||||||
const f = from || 'transparent';
|
|
||||||
const t = to || 'transparent';
|
|
||||||
if (f === t) {
|
|
||||||
return to;
|
|
||||||
}
|
|
||||||
const c0 = helpers.color(f);
|
|
||||||
const c1 = c0.valid && helpers.color(t);
|
|
||||||
return c1 && c1.valid ? c1.mix(c0, factor).hexString() : to;
|
|
||||||
},
|
|
||||||
number(from, to, factor) {
|
|
||||||
if (from === to) {
|
|
||||||
return to;
|
|
||||||
}
|
|
||||||
return from + (to - from) * factor;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
function interpolateArrayOption(from, to, factor, type, interpolator) {
|
|
||||||
if (typeof from === type && typeof to === type) {
|
|
||||||
return interpolator(from, to, factor);
|
|
||||||
}
|
|
||||||
if (Array.isArray(from) && Array.isArray(to)) {
|
|
||||||
return from.map((f, i) => interpolator(f, to[i], factor));
|
|
||||||
}
|
|
||||||
const isV = (t) => t && Array.isArray(t.v);
|
|
||||||
if (isV(from) && isV(to)) {
|
|
||||||
return { v: from.v.map((f, i) => interpolator(f, to.v[i], factor)) };
|
|
||||||
}
|
|
||||||
return to;
|
|
||||||
}
|
|
||||||
function interpolateNumberOptionArray(from, to, factor) {
|
|
||||||
return interpolateArrayOption(from, to, factor, 'number', interpolators.number);
|
|
||||||
}
|
|
||||||
function interpolateColorOptionArray(from, to, factor) {
|
|
||||||
return interpolateArrayOption(from, to, factor, 'string', interpolators.color);
|
|
||||||
}
|
|
||||||
const animationHints = {
|
|
||||||
animations: {
|
|
||||||
numberArray: {
|
|
||||||
fn: interpolateNumberOptionArray,
|
|
||||||
properties: allModelKeys.concat(styleKeys.filter((d) => !d.endsWith('Color')), ['rMin', 'rMax']),
|
|
||||||
},
|
|
||||||
colorArray: {
|
|
||||||
fn: interpolateColorOptionArray,
|
|
||||||
properties: styleKeys.filter((d) => d.endsWith('Color')),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function getMinMax(scale, superMethod) {
|
|
||||||
const { axis } = scale;
|
|
||||||
scale.axis = `${axis}MinMin`;
|
|
||||||
const { min } = superMethod(scale);
|
|
||||||
scale.axis = `${axis}MaxMax`;
|
|
||||||
const { max } = superMethod(scale);
|
|
||||||
scale.axis = axis;
|
|
||||||
return { min, max };
|
|
||||||
}
|
|
||||||
function computeExtrema(v, vm, op) {
|
|
||||||
if (Array.isArray(vm)) {
|
|
||||||
return op(...vm);
|
|
||||||
}
|
|
||||||
if (typeof vm === 'number') {
|
|
||||||
return vm;
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
function parseErrorNumberData(parsed, scale, data, start, count) {
|
|
||||||
const axis = typeof scale === 'string' ? scale : scale.axis;
|
|
||||||
const vMin = `${axis}Min`;
|
|
||||||
const vMax = `${axis}Max`;
|
|
||||||
const vMinMin = `${axis}MinMin`;
|
|
||||||
const vMaxMax = `${axis}MaxMax`;
|
|
||||||
for (let i = 0; i < count; i += 1) {
|
|
||||||
const index = i + start;
|
|
||||||
const p = parsed[i];
|
|
||||||
p[vMin] = data[index][vMin];
|
|
||||||
p[vMax] = data[index][vMax];
|
|
||||||
p[vMinMin] = computeExtrema(p[axis], p[vMin], Math.min);
|
|
||||||
p[vMaxMax] = computeExtrema(p[axis], p[vMax], Math.max);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function parseErrorLabelData(parsed, scale, start, count) {
|
|
||||||
const { axis } = scale;
|
|
||||||
const labels = scale.getLabels();
|
|
||||||
for (let i = 0; i < count; i += 1) {
|
|
||||||
const index = i + start;
|
|
||||||
const p = parsed[i];
|
|
||||||
p[axis] = scale.parse(labels[index], index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function patchController(type, config, controller, elements = [], scales = []) {
|
|
||||||
chart_js.registry.addControllers(controller);
|
|
||||||
if (Array.isArray(elements)) {
|
|
||||||
chart_js.registry.addElements(...elements);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
chart_js.registry.addElements(elements);
|
|
||||||
}
|
|
||||||
if (Array.isArray(scales)) {
|
|
||||||
chart_js.registry.addScales(...scales);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
chart_js.registry.addScales(scales);
|
|
||||||
}
|
|
||||||
const c = config;
|
|
||||||
c.type = type;
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
class BarWithErrorBarsController extends chart_js.BarController {
|
|
||||||
getMinMax(scale, canStack) {
|
|
||||||
return getMinMax(scale, (patchedScale) => super.getMinMax(patchedScale, canStack));
|
|
||||||
}
|
|
||||||
parseObjectData(meta, data, start, count) {
|
|
||||||
const parsed = super.parseObjectData(meta, data, start, count);
|
|
||||||
parseErrorNumberData(parsed, meta.vScale, data, start, count);
|
|
||||||
parseErrorLabelData(parsed, meta.iScale, start, count);
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
updateElement(element, index, properties, mode) {
|
|
||||||
if (typeof index === 'number') {
|
|
||||||
calculateScale(properties, this.getParsed(index), index, this._cachedMeta.vScale, mode === 'reset');
|
|
||||||
}
|
|
||||||
super.updateElement(element, index, properties, mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BarWithErrorBarsController.id = 'barWithErrorBars';
|
|
||||||
BarWithErrorBarsController.defaults = helpers.merge({}, [
|
|
||||||
chart_js.BarController.defaults,
|
|
||||||
animationHints,
|
|
||||||
{
|
|
||||||
dataElementType: BarWithErrorBar.id,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
BarWithErrorBarsController.overrides = helpers.merge({}, [
|
|
||||||
chart_js.BarController.overrides,
|
|
||||||
{
|
|
||||||
plugins: {
|
|
||||||
tooltip: {
|
|
||||||
callbacks: {
|
|
||||||
label: generateBarTooltip,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
BarWithErrorBarsController.defaultRoutes = chart_js.BarController.defaultRoutes;
|
|
||||||
class BarWithErrorBarsChart extends chart_js.Chart {
|
|
||||||
constructor(item, config) {
|
|
||||||
super(item, patchController('barWithErrorBars', config, BarWithErrorBarsController, BarWithErrorBar, [
|
|
||||||
chart_js.LinearScale,
|
|
||||||
chart_js.CategoryScale,
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BarWithErrorBarsChart.id = BarWithErrorBarsController.id;
|
|
||||||
|
|
||||||
class LineWithErrorBarsController extends chart_js.LineController {
|
|
||||||
getMinMax(scale, canStack) {
|
|
||||||
return getMinMax(scale, (patchedScale) => super.getMinMax(patchedScale, canStack));
|
|
||||||
}
|
|
||||||
parseObjectData(meta, data, start, count) {
|
|
||||||
const parsed = super.parseObjectData(meta, data, start, count);
|
|
||||||
parseErrorNumberData(parsed, meta.vScale, data, start, count);
|
|
||||||
parseErrorLabelData(parsed, meta.iScale, start, count);
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
updateElement(element, index, properties, mode) {
|
|
||||||
if (element instanceof PointWithErrorBar && typeof index === 'number') {
|
|
||||||
calculateScale(properties, this.getParsed(index), index, this._cachedMeta.vScale, mode === 'reset');
|
|
||||||
}
|
|
||||||
super.updateElement(element, index, properties, mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LineWithErrorBarsController.id = 'lineWithErrorBars';
|
|
||||||
LineWithErrorBarsController.defaults = helpers.merge({}, [
|
|
||||||
chart_js.LineController.defaults,
|
|
||||||
animationHints,
|
|
||||||
{
|
|
||||||
dataElementType: PointWithErrorBar.id,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
LineWithErrorBarsController.overrides = helpers.merge({}, [
|
|
||||||
chart_js.LineController.overrides,
|
|
||||||
{
|
|
||||||
plugins: {
|
|
||||||
tooltip: {
|
|
||||||
callbacks: {
|
|
||||||
label: generateBarTooltip,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
LineWithErrorBarsController.defaultRoutes = chart_js.LineController.defaultRoutes;
|
|
||||||
class LineWithErrorBarsChart extends chart_js.Chart {
|
|
||||||
constructor(item, config) {
|
|
||||||
super(item, patchController('lineWithErrorBars', config, LineWithErrorBarsController, PointWithErrorBar, [
|
|
||||||
chart_js.LinearScale,
|
|
||||||
chart_js.CategoryScale,
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LineWithErrorBarsChart.id = LineWithErrorBarsController.id;
|
|
||||||
|
|
||||||
class ScatterWithErrorBarsController extends chart_js.ScatterController {
|
|
||||||
getMinMax(scale, canStack) {
|
|
||||||
return getMinMax(scale, (patchedScale) => super.getMinMax(patchedScale, canStack));
|
|
||||||
}
|
|
||||||
parseObjectData(meta, data, start, count) {
|
|
||||||
const parsed = super.parseObjectData(meta, data, start, count);
|
|
||||||
parseErrorNumberData(parsed, meta.xScale, data, start, count);
|
|
||||||
parseErrorNumberData(parsed, meta.yScale, data, start, count);
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
updateElement(element, index, properties, mode) {
|
|
||||||
if (element instanceof PointWithErrorBar && typeof index === 'number') {
|
|
||||||
calculateScale(properties, this.getParsed(index), index, this._cachedMeta.xScale, mode === 'reset');
|
|
||||||
calculateScale(properties, this.getParsed(index), index, this._cachedMeta.yScale, mode === 'reset');
|
|
||||||
}
|
|
||||||
super.updateElement(element, index, properties, mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ScatterWithErrorBarsController.id = 'scatterWithErrorBars';
|
|
||||||
ScatterWithErrorBarsController.defaults = helpers.merge({}, [
|
|
||||||
chart_js.ScatterController.defaults,
|
|
||||||
animationHints,
|
|
||||||
{
|
|
||||||
dataElementType: PointWithErrorBar.id,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
ScatterWithErrorBarsController.overrides = helpers.merge({}, [
|
|
||||||
chart_js.ScatterController.overrides,
|
|
||||||
{
|
|
||||||
plugins: {
|
|
||||||
tooltip: {
|
|
||||||
callbacks: {
|
|
||||||
label: generateTooltipScatter,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
ScatterWithErrorBarsController.defaultRoutes = chart_js.LineController.defaultRoutes;
|
|
||||||
class ScatterWithErrorBarsChart extends chart_js.Chart {
|
|
||||||
constructor(item, config) {
|
|
||||||
super(item, patchController('scatterWithErrorBars', config, ScatterWithErrorBarsController, PointWithErrorBar, [chart_js.LinearScale]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ScatterWithErrorBarsChart.id = ScatterWithErrorBarsController.id;
|
|
||||||
|
|
||||||
class PolarAreaWithErrorBarsController extends chart_js.PolarAreaController {
|
|
||||||
getMinMax(scale, canStack) {
|
|
||||||
return getMinMax(scale, (patchedScale) => super.getMinMax(patchedScale, canStack));
|
|
||||||
}
|
|
||||||
countVisibleElements() {
|
|
||||||
const meta = this._cachedMeta;
|
|
||||||
return meta.data.reduce((acc, _, index) => {
|
|
||||||
if (!Number.isNaN(meta._parsed[index].r) && this.chart.getDataVisibility(index)) {
|
|
||||||
return acc + 1;
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
parseObjectData(meta, data, start, count) {
|
|
||||||
const parsed = new Array(count);
|
|
||||||
const scale = meta.rScale;
|
|
||||||
for (let i = 0; i < count; i += 1) {
|
|
||||||
const index = i + start;
|
|
||||||
const item = data[index];
|
|
||||||
const v = scale.parse(item[scale.axis], index);
|
|
||||||
parsed[i] = {
|
|
||||||
[scale.axis]: v,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
parseErrorNumberData(parsed, scale, data, start, count);
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
updateElement(element, index, properties, mode) {
|
|
||||||
if (typeof index === 'number') {
|
|
||||||
calculatePolarScale(properties, this.getParsed(index), this._cachedMeta.rScale, mode === 'reset', this.chart.options);
|
|
||||||
}
|
|
||||||
super.updateElement(element, index, properties, mode);
|
|
||||||
}
|
|
||||||
updateElements(arcs, start, count, mode) {
|
|
||||||
const scale = this.chart.scales.r;
|
|
||||||
const bak = scale.getDistanceFromCenterForValue;
|
|
||||||
scale.getDistanceFromCenterForValue = function getDistanceFromCenterForValue(v) {
|
|
||||||
if (typeof v === 'number') {
|
|
||||||
return bak.call(this, v);
|
|
||||||
}
|
|
||||||
return bak.call(this, v.r);
|
|
||||||
};
|
|
||||||
super.updateElements(arcs, start, count, mode);
|
|
||||||
scale.getDistanceFromCenterForValue = bak;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PolarAreaWithErrorBarsController.id = 'polarAreaWithErrorBars';
|
|
||||||
PolarAreaWithErrorBarsController.defaults = helpers.merge({}, [
|
|
||||||
chart_js.PolarAreaController.defaults,
|
|
||||||
animationHints,
|
|
||||||
{
|
|
||||||
dataElementType: ArcWithErrorBar.id,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
PolarAreaWithErrorBarsController.overrides = helpers.merge({}, [
|
|
||||||
chart_js.PolarAreaController.overrides,
|
|
||||||
{
|
|
||||||
plugins: {
|
|
||||||
tooltip: {
|
|
||||||
callbacks: {
|
|
||||||
label: generateTooltipPolar,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
PolarAreaWithErrorBarsController.defaultRoutes = chart_js.PolarAreaController.defaultRoutes;
|
|
||||||
class PolarAreaWithErrorBarsChart extends chart_js.Chart {
|
|
||||||
constructor(item, config) {
|
|
||||||
super(item, patchController('polarAreaWithErrorBars', config, PolarAreaWithErrorBarsController, ArcWithErrorBar, [
|
|
||||||
chart_js.RadialLinearScale,
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PolarAreaWithErrorBarsChart.id = PolarAreaWithErrorBarsController.id;
|
|
||||||
|
|
||||||
chart_js.registry.addControllers(BarWithErrorBarsController, LineWithErrorBarsController, PolarAreaWithErrorBarsController, ScatterWithErrorBarsController);
|
|
||||||
chart_js.registry.addElements(BarWithErrorBar, ArcWithErrorBar, PointWithErrorBar);
|
|
||||||
|
|
||||||
exports.ArcWithErrorBar = ArcWithErrorBar;
|
|
||||||
exports.BarWithErrorBar = BarWithErrorBar;
|
|
||||||
exports.BarWithErrorBarsChart = BarWithErrorBarsChart;
|
|
||||||
exports.BarWithErrorBarsController = BarWithErrorBarsController;
|
|
||||||
exports.LineWithErrorBarsChart = LineWithErrorBarsChart;
|
|
||||||
exports.LineWithErrorBarsController = LineWithErrorBarsController;
|
|
||||||
exports.PointWithErrorBar = PointWithErrorBar;
|
|
||||||
exports.PolarAreaWithErrorBarsChart = PolarAreaWithErrorBarsChart;
|
|
||||||
exports.PolarAreaWithErrorBarsController = PolarAreaWithErrorBarsController;
|
|
||||||
exports.ScatterWithErrorBarsChart = ScatterWithErrorBarsChart;
|
|
||||||
exports.ScatterWithErrorBarsController = ScatterWithErrorBarsController;
|
|
||||||
|
|
||||||
Object.defineProperty(exports, '__esModule', { value: true });
|
|
||||||
|
|
||||||
}));
|
|
||||||
//# sourceMappingURL=index.umd.js.map
|
|
2
static/chartjs-chart-error-bars.umd.min.js
vendored
Normal file
2
static/chartjs-chart-error-bars.umd.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
static/chartjs-chart-error-bars.umd.min.js.map
Normal file
1
static/chartjs-chart-error-bars.umd.min.js.map
Normal file
File diff suppressed because one or more lines are too long
|
@ -3,9 +3,9 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
<script src="chart.js"></script>
|
<script src="chart.umd.js"></script>
|
||||||
<script src="chartjs-adapter-date-fns.bundle.js"></script>
|
<script src="chartjs-adapter-date-fns.bundle.min.js"></script>
|
||||||
<script src="chartjs-chart-error-bars.umd.js"></script>
|
<script src="chartjs-chart-error-bars.umd.min.js"></script>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
@ -192,13 +192,13 @@ function plot(data) {
|
||||||
data: {
|
data: {
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
type: 'scatterWithErrorBars',
|
type: 'lineWithErrorBars',
|
||||||
label: 'Measured value',
|
label: 'Measured value',
|
||||||
data: northData,
|
data: northData,
|
||||||
backgroundColor: 'rgba(0,0,255,0.5)',
|
backgroundColor: 'rgba(0,0,255,0.5)',
|
||||||
color: 'rgba(0,0,255,0.5)',
|
color: 'rgba(0,0,255,0.5)',
|
||||||
borderColor: 'rgba(0,0,255,0.5)',
|
borderColor: 'rgba(0,0,255,0.5)',
|
||||||
spanGaps: true,
|
showLine: false,
|
||||||
},
|
},
|
||||||
/* {
|
/* {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
|
@ -248,13 +248,13 @@ function plot(data) {
|
||||||
data: {
|
data: {
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
type: 'scatterWithErrorBars',
|
type: 'lineWithErrorBars',
|
||||||
label: 'Measured value',
|
label: 'Measured value',
|
||||||
data: southData,
|
data: southData,
|
||||||
backgroundColor: 'rgba(255,0,0,0.5)',
|
backgroundColor: 'rgba(255,0,0,0.5)',
|
||||||
color: 'rgba(255,0,0,0.5)',
|
color: 'rgba(255,0,0,0.5)',
|
||||||
borderColor: 'rgba(255,0,0,0.5)',
|
borderColor: 'rgba(255,0,0,0.5)',
|
||||||
spanGaps: true,
|
showLine: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'line',
|
type: 'line',
|
||||||
|
|
Loading…
Reference in a new issue