&& let Some(domain_name) = &dimension.domain
&& let Some(domain) = domains.get(domain_name.as_str())
{
- domain.clone()
+ Some(domain.clone())
} else {
- Domain::default()
+ None
};
- if let Domain::Interval { min, max, exact } = &mut domain
- && (!*exact || min.is_none() || max.is_none())
- {
- for element in &elements {
- if let Some(variable) = &element.variables[axis]
- && !element.ignore_minmax[axis]
- {
- for value in &variable.values {
- if let Some(Some(number)) = value.value.as_number() {
- if min.is_none_or(|min| number < min) {
- *min = Some(number);
- }
- if max.is_none_or(|max| number > max) {
- *max = Some(number);
+ for element in &elements {
+ if let Some(variable) = &element.variables[axis]
+ && !element.ignore_minmax[axis]
+ {
+ let domain = domain.get_or_insert_with(|| {
+ if variable.categorical {
+ Domain::new_categorical()
+ } else {
+ Domain::new_interval()
+ }
+ });
+ match domain {
+ Domain::Interval { min, max, exact } => {
+ if !*exact || min.is_none() || max.is_none() {
+ for value in &variable.values {
+ if let Some(Some(number)) = value.value.as_number() {
+ if min.is_none_or(|min| number < min) {
+ *min = Some(number);
+ }
+ if max.is_none_or(|max| number > max) {
+ *max = Some(number);
+ }
+ }
}
}
}
+ Domain::Categorical { values } => {
+ for value in &variable.values {
+ values
+ .entry(value.value.clone())
+ .or_insert_with(|| value.label.clone());
+ }
+ }
}
}
+ }
+
+ let mut domain = domain.unwrap_or_default();
+ if let Domain::Interval { min, max, exact } = &mut domain
+ && (!*exact || min.is_none() || max.is_none())
+ {
match min {
Some(number) => {
if *number > 0.0 {
})
}
+ fn ranges(
+ rect: &Rect,
+ group: &[Vec<&SeriesValue>],
+ axis_domains: &EnumMap<Axis3, AxisDomain>,
+ ) -> impl Iterator<Item = (Range<f64>, f64)> {
+ group.iter().filter_map(|values| {
+ let x = axis_domains[Axis3::X]
+ .domain
+ .lookup_range(&values.get(0)?.value)?;
+ let x =
+ rect.0[Axis2::X].frac(x.start)..rect.0[Axis2::X].frac(x.end);
+
+ let y = match values.get(1) {
+ Some(value) => {
+ axis_domains[Axis3::Y].domain.lookup(&value.value)?
+ }
+ None => 0.5,
+ };
+ let y = rect.0[Axis2::Y].frac(1.0 - y);
+ Some((x, y))
+ })
+ }
+
let mut element_svg = Group::new().with_id(&element.raw.id);
match element.graphic {
Graphic::Interval => {
- for (x, y) in points(&rect, group, &axis_domains) {
+ // Size defaults to 33%.
+ for (x, y0) in ranges(&rect, group, &axis_domains) {
+ let y1 = rect.0[Axis2::Y].1;
element_svg.append(
- Path::new().set("fill", "none").set("stroke", "black").set(
- "d",
- Data::new()
- .move_to((x, rect.0[Axis2::Y].1))
- .line_to((x, y)),
- ),
+ Rectangle::new()
+ .set("fill", color.display_css().to_string())
+ .set("stroke", color2.display_css().to_string())
+ .set("x", x.start)
+ .set("y", y0)
+ .set("width", (x.end - x.start).max(1.0))
+ .set("height", y1 - y0),
);
}
}
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Series")
.field("name", &self.name)
+ .field("categorical", &self.categorical)
.finish_non_exhaustive()
}
}
impl Default for Domain {
fn default() -> Self {
+ Self::new_interval()
+ }
+}
+
+impl Domain {
+ fn new_categorical() -> Self {
+ Self::Categorical {
+ values: IndexMap::new(),
+ }
+ }
+ fn new_interval() -> Self {
Self::Interval {
min: None,
max: None,
exact: false,
}
}
-}
-
-impl Domain {
fn is_interval(&self) -> bool {
matches!(self, Self::Interval { .. })
}
}
}
fn lookup(&self, value: &Datum<String>) -> Option<f64> {
+ self.lookup_range(value)
+ .map(|range| range.start.midpoint(range.end))
+ }
+ fn lookup_range(&self, value: &Datum<String>) -> Option<Range<f64>> {
match self {
Domain::Categorical { values } => {
if let Some(index) = values.get_index_of(value) {
let left = index as f64 / values.len() as f64;
let right = (index + 1) as f64 / values.len() as f64;
- Some(left.midpoint(right))
+ Some(left..right)
} else {
None
}
if let Some(Some(number)) = value.as_number()
&& (*min..=*max).contains(&number)
{
- Some((number - *min) / (*max - *min))
+ let value = (number - *min) / (*max - *min);
+ Some(value..value)
} else {
None
}
style: Cow<'a, Style>,
graphic: Graphic,
variables: EnumMap<Axis3, Option<Series>>,
+
+ /// Whether to ignore this element for the purpose of computing minimum and
+ /// maximum ordinates on an axis.
+ ///
+ /// This is set to true for statistics that produce data for an axis but
+ /// shouldn't expand the axis range, e.g. probability distribution
+ /// functions.
ignore_minmax: EnumMap<Axis3, bool>,
}
let total = frequencies.values().sum::<f64>();
let mut y = x.clone();
+ y.categorical = false;
for value in &mut y.values {
let frequency =
frequencies.get(&value.value).copied().unwrap_or_default();
LocationLength::Percent(percentage) => parent_length.map(|p| p * (percentage / 100.0)),
}
}
+
+ fn as_percent(&self) -> Option<f64> {
+ match *self {
+ LocationLength::Absolute(_) => None,
+ LocationLength::Percent(percent) => Some(percent),
+ }
+ }
}
impl FromStr for LocationLength {
}
impl StyleCycle {
- /// Constructs the style with the given 1-based `index` in this cycle.
pub fn get_style(&self, index: usize) -> Style {
+ let mut iter = self.get_style_components(index);
+ let style = iter.next().cloned().unwrap_or_default();
+ iter.fold(style, |a, b| a.overridden_with(b))
+ }
+
+ /// Iterates through the styles for the given 1-based `index` in this cycle.
+ ///
+ /// There might be no styles, if `self.cycles` is empty or all of the
+ /// elements in `self.cycles` are empty.
+ pub fn get_style_components(&self, index: usize) -> impl Iterator<Item = &Style> {
// Treat index 0 as the last style in the cycle.
let mut index = index.checked_sub(1).unwrap_or_else(|| self.len() - 1);
- let mut style: Option<Style> = None;
- for cycle in &self.cycles {
- let len = cycle.styles.len();
- if len != 0 {
- let s = &cycle.styles[index % len];
- style = Some(style.map_or_else(|| s.clone(), |style| style.overridden_with(s)));
- index /= len;
- }
- }
- style.unwrap_or_default()
+ self.cycles
+ .iter()
+ .flat_map(move |cycle| match cycle.styles.len() {
+ 0 => None,
+ len => {
+ let s = &cycle.styles[index % len];
+ index /= len;
+ Some(s)
+ }
+ })
}
/// Returns the length of the cycle, that is, the number of styles before
<svg height="500" style="font-family: "Sans Serif"" viewBox="0 0 625 500" width="625" xmlns="http://www.w3.org/2000/svg">
<g id="frame_data_1">
<line stroke="black" x1="70.25" x2="618.75" y1="439" y2="439"/>
-<line stroke="black" x1="95.18181818181819" x2="95.18181818181819" y1="439" y2="449"/>
-<text dominant-baseline="hanging" text-anchor="middle" x="95.18181818181819" y="449">
-0.0
-</text>
-<line stroke="black" x1="344.5" x2="344.5" y1="439" y2="449"/>
-<text dominant-baseline="hanging" text-anchor="middle" x="344.5" y="449">
-1.0
+<line stroke="black" x1="207.375" x2="207.375" y1="439" y2="449"/>
+<text dominant-baseline="hanging" text-anchor="middle" x="207.375" y="449">
+Value0
</text>
-<line stroke="black" x1="593.8181818181818" x2="593.8181818181818" y1="439" y2="449"/>
-<text dominant-baseline="hanging" text-anchor="middle" x="593.8181818181818" y="449">
-2.0
+<line stroke="black" x1="481.625" x2="481.625" y1="439" y2="449"/>
+<text dominant-baseline="hanging" text-anchor="middle" x="481.625" y="449">
+Value1
</text>
<text dominant-baseline="hanging" text-anchor="middle" x="344.5" y="472">
V0
Percent
</text>
<g id="element_bar_1">
-<path d="M344.5,219.5 L344.5,126.93304" fill="none" stroke="black"/>
-<path d="M593.8182,219.5 L593.8182,15.214286" fill="none" stroke="black"/>
+<rect fill="#d3ce97" height="92.56696428571429" stroke="#000000" width="274.25" x="70.25" y="126.93303571428571"/>
+<rect fill="#d3ce97" height="204.2857142857143" stroke="#000000" width="274.25" x="344.5" y="15.214285714285701"/>
</g>
<g id="element_bar_1">
-<path d="M344.5,439 L344.5,317.70535" fill="none" stroke="black"/>
-<path d="M593.8182,439 L593.8182,311.32144" fill="none" stroke="black"/>
+<rect fill="#d3ce97" height="121.29464285714289" stroke="#000000" width="274.25" x="70.25" y="317.7053571428571"/>
+<rect fill="#d3ce97" height="127.67857142857144" stroke="#000000" width="274.25" x="344.5" y="311.32142857142856"/>
</g>
</g>
</svg>