|
10 | 10 | ### dependencies is very useful to have. |
11 | 11 | ### |
12 | 12 | ### Data is passed to most charts as a "data-frame", which is a table mapping keyword (or any Janet value) column names |
13 | | -### to arrays of data points, usually numbers. |
| 13 | +### to arrays of data points, usually numbers. The columns of a dataframe are considered to be the sorted keys of table or struct. |
| 14 | +### Many visualizations will infer the X column as the "first" column and the Y column as the second column. |
14 | 15 | ### |
15 | 16 | ### Data frame example: |
16 | 17 | ### |
|
1591 | 1592 | (def xs @[]) |
1592 | 1593 | (def ys @[]) |
1593 | 1594 | (eachp [k v] x (array/push xs k) (array/push ys v)) |
1594 | | - @{:x xs :y ys}) |
| 1595 | + {:x xs :y ys}) |
| 1596 | + |
| 1597 | +(defn- convert-to-nested |
| 1598 | + "Convert the dataframes to a nested representation, such that the label-column |
| 1599 | + is used to group rows. The grouped rows will be combined into a sub-dataframe that lives |
| 1600 | + inside another cell in the main dataframe. This is an unnatural representation but is convenient |
| 1601 | + for rendering packing charts with hierarchical data." |
| 1602 | + [df x-column area-column label-column] |
| 1603 | + (def df-columns (sort (keys df))) |
| 1604 | + (def len (length (get df area-column))) |
| 1605 | + (defn append-row [to i] (each col df-columns (array/push (get to col) (get (get df col) i))) to) |
| 1606 | + (defn make-new [] (let [ndf @{}] (each col df-columns (put ndf col @[])) ndf)) |
| 1607 | + (def new-df (make-new)) |
| 1608 | + (def cat-data (assert (or (get df label-column) (array/new-filled len nil)))) |
| 1609 | + (def nested-dfs @{}) |
| 1610 | + # Iterate over the rows and extract/remove rows with a non-nil label |
| 1611 | + (for i 0 len |
| 1612 | + (def label-category (get cat-data i)) |
| 1613 | + (if (= nil label-category) |
| 1614 | + (append-row new-df i) |
| 1615 | + (let [sub-df (or (get nested-dfs label-category) (set (nested-dfs label-category) (make-new)))] |
| 1616 | + (append-row sub-df i)))) |
| 1617 | + # Add the nested dataframes back to our copy of the original data-frame with the groupings removed. |
| 1618 | + (eachp [cat-label nested-df] nested-dfs |
| 1619 | + (def summed-area (sum (get nested-df area-column))) |
| 1620 | + (put nested-df label-column nil) |
| 1621 | + (each col df-columns |
| 1622 | + (def value-array (get new-df col)) |
| 1623 | + (array/push value-array |
| 1624 | + (case col |
| 1625 | + x-column cat-label |
| 1626 | + area-column summed-area |
| 1627 | + label-column nested-df |
| 1628 | + nil)))) |
| 1629 | + new-df) |
1595 | 1630 |
|
1596 | 1631 | (defn plot-packing-chart |
1597 | 1632 | ``` |
|
1605 | 1640 | * :height - (if no canvas provided) - make a new canvas with the given height in pixels |
1606 | 1641 | * :color-map - a color map keyword or function used to map numbers in the range [0, 1] to a color. |
1607 | 1642 |
|
| 1643 | + Data Table Input (quick and easy method): |
| 1644 | + * :data-map - A table or struct that maps keys as categories to values as proportional rectangle areas. |
| 1645 | +
|
1608 | 1646 | Data Frame Input: |
1609 | | - * :data - a dataframe table that contains a grid of cell |
| 1647 | + * :data - a data-frame table that contains a grid of cell |
1610 | 1648 | * :x-column - a column name to use a the category identifiers. Defaults to the first column. |
1611 | 1649 | * :y-column - a column name to use for the area quantities. Defaults to the second column |
1612 | | - * :c-column - a column name to use for color grading. Defaults to the same as the y-column, but mapped to a range from 0 to 1. |
1613 | | -
|
1614 | | - Data Table Input: |
1615 | | - * :data-map - A table or struct that maps keys as categories to values as proportional rectangle areas. |
| 1650 | + * :c-column - a column name to use for color grading. Defaults to the same as the y-column, but mapped to a range from 0 to 1, such that |
| 1651 | + the minimum value is a color paramter of 0 and the maximum has a color parameter of 1. |
| 1652 | + * :group-column - a column name that contains a label for grouping areas. Optional. |
1616 | 1653 |
|
1617 | 1654 | Layout Parameters: |
1618 | 1655 | * :omega - a number between 0 and 1 used to decide how to split rectangular areas. The default is 0.5 |
|
1630 | 1667 | [&named |
1631 | 1668 | canvas width height |
1632 | 1669 | data-map |
1633 | | - data x-column y-column c-column |
| 1670 | + data x-column y-column c-column group-column |
1634 | 1671 | padding inner-padding |
1635 | 1672 | background-color |
1636 | 1673 | text-color |
|
1640 | 1677 | sort-bins] |
1641 | 1678 |
|
1642 | 1679 | (def [canvas canvas-w canvas-h] :shadow (canvas-and-dimensions canvas width height)) |
1643 | | - (def canvas (g/blank canvas-w canvas-h 4)) |
1644 | 1680 | (default padding 2) |
1645 | 1681 | (default inner-padding 2) |
1646 | 1682 | (default background-color (dyn *background-color* default-background-color)) |
|
1654 | 1690 | (def skeys (sort (keys data))) |
1655 | 1691 | (default x-column (first skeys)) |
1656 | 1692 | (default y-column (get skeys 1)) |
1657 | | - (def xs (assert (get data x-column))) |
1658 | 1693 | (def ys (assert (get data y-column))) |
1659 | 1694 |
|
1660 | 1695 | # Allow a custom color column, but use the y-column by default with a reasonable color ramp. |
|
1663 | 1698 | (def value-range (- max-value min-value)) |
1664 | 1699 | (def color-ramp-slope (cond c-column 1 (not= 0 value-range) (/ value-range) 0)) |
1665 | 1700 | (def color-ramp-offset (cond c-column 0 (not= 0 value-range) (- (/ min-value value-range)) 0.5)) |
1666 | | - (default c-column y-column) |
1667 | | - (def cs (assert (get data c-column))) |
1668 | 1701 |
|
1669 | | - # Use zipped data for sorting |
1670 | | - (def zipped-data (map tuple xs ys cs)) |
| 1702 | + # Convert to a nested representation if we have a nested column |
| 1703 | + (def data :shadow (if group-column (convert-to-nested data x-column y-column group-column) data)) |
| 1704 | + (def xs (assert (get data x-column))) |
| 1705 | + (def ys :shadow (assert (get data y-column))) |
| 1706 | + (def ns (or (if group-column (get data group-column)) (array/new-filled (length xs) nil))) |
| 1707 | + (def cs (assert (get data (or c-column y-column)))) |
1671 | 1708 |
|
1672 | | - (var custom-draw nil) |
| 1709 | + # Use zipped data for sorting |
| 1710 | + (def zipped-data (map tuple xs ys cs ns)) |
| 1711 | + |
| 1712 | + (defn- do-subgroup |
| 1713 | + [x y w h lab children] |
| 1714 | + (def text (string lab)) |
| 1715 | + (def [tw th] (text-measure text font)) |
| 1716 | + (def tscale 1) |
| 1717 | + (def tcolor (or text-color (if (< 0.6 (color-value background-color)) g/black g/white))) # black or white, maximizing contrast |
| 1718 | + (text-draw canvas (+ x (div (- w tw) 2)) (+ y inner-padding) |
| 1719 | + text tcolor font tscale 0) |
| 1720 | + (def h :shadow (- h th inner-padding)) |
| 1721 | + (def y :shadow (+ y th inner-padding)) |
| 1722 | + # Recurse on new view port |
| 1723 | + (def group-padding (* 2 inner-padding)) |
| 1724 | + (def view (g/viewport canvas (+ group-padding x) y (- w (* 2 group-padding)) (- h group-padding))) |
| 1725 | + (plot-packing-chart :canvas view |
| 1726 | + :x-column x-column |
| 1727 | + :y-column y-column |
| 1728 | + :c-column c-column |
| 1729 | + :group-column group-column |
| 1730 | + :data children # children-as-data |
| 1731 | + :color-map color-map |
| 1732 | + :sort-bins sort-bins |
| 1733 | + :font font |
| 1734 | + :background-color background-color |
| 1735 | + :inner-padding inner-padding |
| 1736 | + :padding padding |
| 1737 | + :omega omega |
| 1738 | + :no-text-resize no-text-resize |
| 1739 | + :text-color text-color) |
| 1740 | + nil) |
1673 | 1741 |
|
1674 | 1742 | # Preprocess data |
1675 | 1743 | (each y ys (assert (>= y 0) "cannot have area measurements less than 0")) |
|
1687 | 1755 | # Leaf cases |
1688 | 1756 | (when (empty? categories) (break)) |
1689 | 1757 | (when (= 1 (length categories)) |
1690 | | - (def [cat area colort] (first categories)) |
1691 | | - (when custom-draw (break (custom-draw x y w h cat area colort))) |
| 1758 | + (def [cat area colort children] (first categories)) |
| 1759 | + (when (dictionary? children) (break (do-subgroup x y w h cat children))) |
1692 | 1760 | (def text (string cat)) |
1693 | 1761 | (def t (+ color-ramp-offset (* color-ramp-slope colort))) |
1694 | 1762 | (def color (cmap t cat)) |
|
0 commit comments