geom icon indicating copy to clipboard operation
geom copied to clipboard

Bar plots can't be drawn with a negative y-axis

Open kxygk opened this issue 3 years ago • 0 comments

There is bit of a weird limitation with the bar plots. The bars are at the moment drawn with respect to the first value in the domain - but this causes some usability issues

If you have bar plots that have mostly negative values - for instance:

    (let [left-margin 50
	  width 720
	  height 180]
      (->> {:x-axis (linear-axis
		     {:domain [0 5]
		      :range  [left-margin width]
		      :pos    (/ height 2)})
	    :y-axis (linear-axis
		     {:domain  [0 10] ;; the first values in effect sets the bar's `zero`
		      :range   [(/  height
				    2.0)
				0]
		      :major  1
		      :pos    (+ left-margin 0)})
	    :data [{:values  [[1 4] [2 -6] [3 -3] [4 2]]
		    :attribs {:fill "none" :stroke "#0af" :stroke-width (/ width 6)}
		    :layout  svg-bar-plot}]}
	   (svg-plot2d-cartesian)
	   (svg {:width width :height height })
	   (serialize)
	   (spit "normal-bar-plot.svg")))

normal-bar-plot

Adding a Y axis that spans the full range is impossible b/c the domain range must start with a zero (otherwise the zero point of the bar will be some negative value)

.An alternative that almost works - that will give you just a negative Y axis - is to give it a decreasing :domain and a modified :range

    (let [left-margin 50
	  width 720
	  height 180]
      (->> {:x-axis (linear-axis
		     {:domain [0 5]
		      :range  [left-margin width]
		      :label-style {:fill "none"}
		      :pos    (/ height 2)})
	    :y-axis (linear-axis
		     {:domain  [0 -10] ;; the first values in effect sets the bar's `zero`
		      :range   [(/  height
				    2.0)
				height ]
		      :major  1
		      :pos    (+ left-margin 0)})
	    :data [{:values  [[1 4] [2 -6] [3 -3] [4 2] ]
		    :attribs {:fill "none" :stroke "#0af" :stroke-width (/ width 6)}
		    :layout  svg-bar-plot}]}
	   (svg-plot2d-cartesian)
	   (svg {:width width :height height })
	   (serialize)
	   (spit "negative-bar-plot.svg")))

negative-bar-plot

This however clobbers the axis labels.

It looks like an issue in thi.ng.geom.viz.core/lin-tick-marks

I tried changing the code to look like

(defn lin-tick-marks
  [[d1 d2] delta]
  (if (m/delta= delta 0.0 m/*eps*)
    '()
    (let [dr (- d2 d1)
          d1' (m/roundto d1 delta)]
      (filter #(m/in-range? d1 d2 %) (range d1' (+ d2 delta) (if (< d1 d2) delta (- delta)))))))

(b/c if the first argument to range is smaller than the second, then the incrementor need to be negative)

However this didn't seem to fix the issue. My guess is that thi.ng.math.core/in-range? isn't meant to handle d2 > d1. I didn't want to go deeper and change things in math.core b/c it might break something else in the larger project

In any case, maybe decreasing domain ranges shouldn't be supported :)

Ideally some alternate solution would allow the axis to go from negative to positive in whatever domain the user wants (by setting some sort of :zero-point for the bar plots maybe?).

kxygk avatar Jul 18 '21 08:07 kxygk