Closed
Description
Here is a hacky code and screenshot:
InverseLogCoord
and InverseLogRange
(only works for values in (0,1)):
use plotters::coord::ranged1d::AsRangedCoord;
use plotters::coord::ranged1d::DefaultFormatting;
use plotters::coord::ranged1d::KeyPointHint;
use plotters::coord::ranged1d::Ranged;
use plotters::coord::types::RangedCoordf64;
use plotters::prelude::LogScalable;
use std::ops::Range;
pub struct InverseLogCoord<V: LogScalable> {
linear: RangedCoordf64,
logic: Range<V>,
marker: std::marker::PhantomData<V>,
}
impl<V: LogScalable> Ranged for InverseLogCoord<V> {
type FormatOption = DefaultFormatting;
type ValueType = V;
fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 {
let value = value.as_f64();
let value = (1.0 - value).max(1.0 - self.logic.end.as_f64()).ln();
let v = self.linear.map(&value, limit);
(limit.0 + limit.1) - v
}
fn key_points<Hint: KeyPointHint>(&self, hint: Hint) -> Vec<Self::ValueType> {
let max_points = hint.max_num_points();
let tier_1 = (self.logic.end.as_f64() / self.logic.start.as_f64())
.log10()
.abs()
.floor()
.max(1.0) as usize;
let tier_2_density = if max_points < tier_1 {
0
} else {
let density = 1 + (max_points - tier_1) / tier_1;
let mut exp = 1;
while exp * 10 <= density {
exp *= 10;
}
exp - 1
};
let mut multiplier = 10.0;
let mut cnt = 1;
while max_points < tier_1 / cnt {
multiplier *= 10.0;
cnt += 1;
}
let mut ret = vec![];
let mut inverse_val = (10f64).powf((1.0 - self.logic.end.as_f64()).log10().ceil());
while inverse_val <= 1.0 - self.logic.start.as_f64() {
ret.push(V::from_f64(1.0 - inverse_val));
for i in 1..=tier_2_density {
let v = inverse_val
* (1.0
+ multiplier / f64::from(tier_2_density as u32 + 1) * f64::from(i as u32));
if v > 1.0 - self.logic.start.as_f64() {
break;
}
ret.push(V::from_f64(1.0 - inverse_val));
}
inverse_val *= multiplier;
}
ret.reverse();
ret
}
fn range(&self) -> Range<Self::ValueType> {
self.logic.clone()
}
}
pub struct InverseLogRange<V: LogScalable>(pub Range<V>);
impl<V: LogScalable> AsRangedCoord for InverseLogRange<V> {
type CoordDescType = InverseLogCoord<V>;
type Value = V;
}
impl<V: LogScalable> From<InverseLogRange<V>> for InverseLogCoord<V> {
fn from(range: InverseLogRange<V>) -> InverseLogCoord<V> {
InverseLogCoord {
linear: ((1.0 - range.0.end.as_f64()).ln()..(1.0 - range.0.start.as_f64()).ln()).into(),
logic: range.0,
marker: std::marker::PhantomData,
}
}
}
drawing with the hdrhistogram
crate:
let mut chart = ChartBuilder::on(&root)
.x_label_area_size(35)
.y_label_area_size(100)
.margin(20)
.caption("Latency by Percentile Distribution", ("sans-serif", 20.0).into_font())
.build_cartesian_2d(
scale::InverseLogRange((0.1f64..0.9999)).with_key_points(vec![0.1,0.5,0.9,0.99,0.999,0.9999]),
(1f64..1_000f64).log_scale()).unwrap();
chart
.configure_mesh()
.disable_x_mesh()
.bold_line_style(&WHITE.mix(0.3))
.y_desc("Latency")
.x_desc("Quantile")
.axis_desc_style(("sans-serif", 15).into_font())
.x_label_formatter(&|x| format!("{}%", x*100.0) )
.y_label_formatter(&|y| format!("{} ms", y) )
.draw().unwrap();
chart.draw_series(
LineSeries::new(hist.iter_quantiles(3).map(|it| (it.quantile_iterated_to() as f64, it.value_iterated_to() as f64)), &RED),
).unwrap();